From a17c891c2cad6821957b9e17071f899cb9b987c5 Mon Sep 17 00:00:00 2001 From: Rushi <44952533+Skyliegirl33@users.noreply.github.com> Date: Thu, 16 Feb 2023 22:16:01 +0100 Subject: [PATCH] [3.x] Basic map icon support (MapMgr backport) (#896) * Fix several structs * Preliminary map icon support (only quests work) Backport of #721 to 3.x * MapMgr cleanup * Add missing null check for ENpcBase * Use CLASSJOB_SLOTS in PlayerSetup instead * Use util function for getting bit fields --- deps/datReader/Exd/Structs.h | 31 +- src/common/Common.h | 2 +- src/world/Actor/Player.cpp | 18 + src/world/Actor/Player.h | 3 + src/world/Actor/PlayerQuest.cpp | 8 + src/world/Event/EventHandler.h | 10 +- src/world/Manager/MapMgr.cpp | 671 ++++++++++++++++++ src/world/Manager/MapMgr.h | 80 +++ src/world/Manager/WarpMgr.cpp | 3 + .../PacketWrappers/PlayerSetupPacket.h | 2 +- src/world/Script/NativeScriptApi.cpp | 5 + src/world/Script/NativeScriptApi.h | 2 + src/world/Territory/InstanceObjectCache.cpp | 39 +- src/world/Territory/InstanceObjectCache.h | 17 + src/world/WorldServer.cpp | 11 + 15 files changed, 878 insertions(+), 24 deletions(-) create mode 100644 src/world/Manager/MapMgr.cpp create mode 100644 src/world/Manager/MapMgr.h diff --git a/deps/datReader/Exd/Structs.h b/deps/datReader/Exd/Structs.h index a93aca6b..078d26fa 100644 --- a/deps/datReader/Exd/Structs.h +++ b/deps/datReader/Exd/Structs.h @@ -1738,8 +1738,7 @@ namespace Excel struct ClassJobCategory { ClassJobCategoryTextStruct Text; - bool ClassJob[31]; - int8_t padding0[1]; + bool ClassJob[34]; }; /* 264228 */ @@ -1902,15 +1901,13 @@ namespace Excel uint8_t ItemStainId[7]; uint8_t OptionalItemNum[5]; uint8_t OptionalItemStainId[5]; - uint8_t CompanyPointType; uint8_t Emote; - uint8_t unknown; uint8_t GeneralAction[2]; + uint8_t CompanyPointType; uint8_t AllaganTomestoneType; uint8_t AllaganTomestoneNum; - uint8_t Unknown95E; uint8_t BeastReputationValueNum; - uint8_t unknown1[4]; + uint8_t unknown[6]; }; /* 264323 */ @@ -1927,27 +1924,28 @@ namespace Excel uint32_t InstanceContent[3]; uint32_t Client; uint32_t Finish; - uint32_t Header; + uint32_t Image; uint32_t Inlay; int32_t Mount; uint16_t ClassLevel; uint16_t Unknown9A2; uint16_t ClassLevel2; - uint16_t AcquiredReward; + uint16_t Header; uint16_t TimeBegin; uint16_t TimeEnd; uint16_t BeastReputationValue; uint16_t ClientBehavior; uint16_t Area; uint16_t Sort; + uint8_t Expansion; uint8_t ClassJob; uint8_t QuestLevelOffset; uint8_t ClassJob2; uint8_t PrevQuestOperator; uint8_t ExcludeQuestOperator; uint8_t StartTown; - uint8_t FirstClassOperator; - uint8_t FirstClass; + uint8_t ClassJobUnlockFlag; + uint8_t ClassJobUnlock; uint8_t GrandCompany; uint8_t GrandCompanyRank; uint8_t InstanceContentOperator; @@ -1956,20 +1954,20 @@ namespace Excel uint8_t FestivalPhaseEnd; uint8_t BeastTribe; uint8_t BeastReputationRank; + uint8_t DeliveryQuest; uint8_t RepeatCycle; uint8_t RepeatFlag; uint8_t ExpClass; uint8_t Genre; + uint8_t Unknown9CA; uint8_t IconType; - uint8_t Quality; - uint8_t Unknown9C9; + uint8_t Type; uint8_t padding2 : 3; uint8_t HideOfferIcon : 1; uint8_t Cancellable : 1; uint8_t Introduction : 1; uint8_t Repeatable : 1; uint8_t House : 1; - int8_t padding3[3]; }; /* 264324 */ @@ -3328,10 +3326,11 @@ namespace Excel /* 362341 */ struct EventIconType { - uint32_t Announce; - uint32_t Map; + uint32_t NpcAvailable; + uint32_t MapAvailable; + uint32_t NpcInvalid; + uint32_t MapInvalid; uint8_t Num; - int8_t padding0[3]; }; /* 362342 */ diff --git a/src/common/Common.h b/src/common/Common.h index d5f970af..e34e4606 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -24,7 +24,7 @@ namespace Sapphire::Common const uint16_t MAX_PLAYER_LEVEL = 60; const uint8_t CURRENT_EXPANSION_ID = 1; - const uint8_t CLASSJOB_TOTAL = 23; + const uint8_t CLASSJOB_TOTAL = 34; const uint8_t CLASSJOB_SLOTS = 23; const uint8_t TOWN_COUNT = 6; diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index 6f9d7fe3..8cb20304 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -21,6 +21,7 @@ #include "Manager/PartyMgr.h" #include "Manager/WarpMgr.h" #include "Manager/FreeCompanyMgr.h" +#include "Manager/MapMgr.h" #include "Territory/Territory.h" #include "Territory/InstanceContent.h" @@ -617,6 +618,21 @@ bool Player::hasReward( Common::UnlockEntry unlockId ) const return ( m_unlocks[ index ] & value ) != 0; } +bool Player::hasMount( uint32_t mountId ) const +{ + auto& exdData = Common::Service< Data::ExdData >::ref(); + auto mount = exdData.getRow< Excel::Mount >( mountId ); + + if( !mount || mount->data().MountOrder == -1 || mount->data().Model == 0 ) + return false; + + uint16_t index; + uint8_t value; + Util::valueToFlagByteIndexValue( mount->data().MountOrder, value, index ); + + return m_mountGuide[ index ] & value; +} + void Player::gainExp( uint32_t amount ) { uint32_t currentExp = getExp(); @@ -665,6 +681,7 @@ void Player::levelUp() setLevel( getLevel() + 1 ); Service< World::Manager::PlayerMgr >::ref().onLevelUp( *this ); + Service< World::Manager::MapMgr >::ref().updateQuests( *this ); } void Player::sendStatusUpdate() @@ -743,6 +760,7 @@ void Player::setClassJob( Common::ClassJob classJob ) Service< World::Manager::PlayerMgr >::ref().onPlayerStatusUpdate( *this ); Service< World::Manager::PlayerMgr >::ref().onChangeClass( *this ); + Service< World::Manager::MapMgr >::ref().updateQuests( *this ); } void Player::setLevel( uint8_t level ) diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index cf90a4bb..652ea0d7 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -464,6 +464,9 @@ namespace Sapphire::Entity /*! check if an action is already unlocked in the bitmask. */ bool hasReward( Common::UnlockEntry unlockId ) const; + /*! check if a mount is already unlocked in the bitmask. */ + bool hasMount( uint32_t mountId ) const; + /*! return a const reference to the unlock bitmask array */ const UnlockList& getUnlockBitmask() const; diff --git a/src/world/Actor/PlayerQuest.cpp b/src/world/Actor/PlayerQuest.cpp index a1d6f68a..ea04173e 100644 --- a/src/world/Actor/PlayerQuest.cpp +++ b/src/world/Actor/PlayerQuest.cpp @@ -3,6 +3,7 @@ #include #include "Manager/QuestMgr.h" +#include "Manager/MapMgr.h" #include "Player.h" @@ -41,11 +42,13 @@ void Sapphire::Entity::Player::removeQuest( uint16_t questId ) } auto& questMgr = Common::Service< World::Manager::QuestMgr >::ref(); + auto& mapMgr = Common::Service< World::Manager::MapMgr >::ref(); m_quests[ idx ] = World::Quest(); removeQuestTracking( idx ); deleteDbQuest( questId ); + mapMgr.updateQuests( *this ); questMgr.onRemoveQuest( *this, idx ); } @@ -77,11 +80,13 @@ int8_t Sapphire::Entity::Player::getQuestIndex( uint16_t questId ) void Sapphire::Entity::Player::updateQuest( const World::Quest& quest ) { auto& questMgr = Common::Service< World::Manager::QuestMgr >::ref(); + auto& mapMgr = Common::Service< World::Manager::MapMgr >::ref(); if( hasQuest( quest.getId() ) ) { uint8_t index = getQuestIndex( quest.getId() ); m_quests[ index ] = quest; + mapMgr.updateQuests( *this ); questMgr.onUpdateQuest( *this, index ); } else if( quest.getSeq() != 0 ) @@ -99,12 +104,14 @@ bool Sapphire::Entity::Player::addQuest( const World::Quest& quest ) } auto& questMgr = Common::Service< World::Manager::QuestMgr >::ref(); + auto& mapMgr = Common::Service< World::Manager::MapMgr >::ref(); m_quests[ idx ] = quest; insertDbQuest( quest, idx ); addQuestTracking( idx ); + mapMgr.updateQuests( *this ); questMgr.onUpdateQuest( *this, idx ); return true; @@ -164,6 +171,7 @@ void Sapphire::Entity::Player::removeQuestsCompleted( uint32_t questId ) m_questCompleteFlags[ index ] ^= value; + Common::Service< World::Manager::MapMgr >::ref().updateQuests( *this ); } Sapphire::World::Quest& Sapphire::Entity::Player::getQuestByIndex( uint16_t index ) diff --git a/src/world/Event/EventHandler.h b/src/world/Event/EventHandler.h index 7f1d27bb..efd96c2a 100644 --- a/src/world/Event/EventHandler.h +++ b/src/world/Event/EventHandler.h @@ -107,10 +107,18 @@ namespace Sapphire::Event SwitchTalk = 0x001F, Adventure = 0x0020, DailyQuestSupply = 0x0021, + TripleTriad = 0x0023, ICDirector = 0x8003, QuestBattleDirector = 0x8006, }; + enum class QuestAvailability : uint8_t + { + Invisible, + Available, + Locked + }; + using SceneReturnCallback = std::function< void( Entity::Player&, const SceneResult& ) >; using QuestSceneReturnCallback = std::function< void( World::Quest&, Entity::Player&, const SceneResult& ) >; using SceneChainCallback = std::function< void( Entity::Player& ) >; @@ -182,4 +190,4 @@ namespace Sapphire::Event QuestSceneReturnCallback m_questReturnCallback; }; -} \ No newline at end of file +} diff --git a/src/world/Manager/MapMgr.cpp b/src/world/Manager/MapMgr.cpp new file mode 100644 index 00000000..d4990d3b --- /dev/null +++ b/src/world/Manager/MapMgr.cpp @@ -0,0 +1,671 @@ +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "Territory/InstanceObjectCache.h" +#include "Script/ScriptMgr.h" +#include "Script/NativeScriptMgr.h" + +#include "MapMgr.h" + +#include "Session.h" +#include "WorldServer.h" + +using namespace Sapphire::Event; +using namespace Sapphire::Network::Packets; +using namespace Sapphire::Network::Packets::WorldPackets::Server; +using namespace Sapphire::World::Manager; + +bool MapMgr::loadQuests() +{ + auto& exdData = Common::Service< Data::ExdData >::ref(); + auto idList = exdData.getIdList< Excel::Quest >(); + + for( auto id : idList ) + { + auto questExdData = exdData.getRow< Excel::Quest >( id ); + + m_quests.emplace( id, std::move( questExdData ) ); + } + + return true; +} + +void MapMgr::updateAll( Entity::Player& player ) +{ + auto& exdData = Common::Service< Data::ExdData >::ref(); + auto& objectCache = Common::Service< Sapphire::InstanceObjectCache >::ref(); + + EventSet mapData; + + auto eventNpcs = objectCache.getAllENpc( player.getTerritoryTypeId() ); + if( !eventNpcs ) + return; + + for( const auto& eventNpc : *eventNpcs ) + { + auto eNpc = exdData.getRow< Excel::ENpcBase >( eventNpc.second->data.enpcId ); + if( !eNpc ) + continue; + + auto eNpcData = eNpc->data().EventHandler; + for( int npcEvent = 0; npcEvent < 32; npcEvent++ ) + { + auto npcData = eNpcData[ npcEvent ].EventHandler; + + if( npcData == 0 ) + continue; // Some npcs have data gaps, so we have to iterate through the entire array + + EventData eventData; + eventData.layoutId = npcData; + eventData.handlerId = eventNpc.first; + + auto eventHandlerType = static_cast< EventHandler::EventHandlerType >( npcData >> 16 ); + + switch( eventHandlerType ) + { + case EventHandler::EventHandlerType::Quest: + { + auto& quest = m_quests[ npcData ]->data(); + + if( quest.Client == eventNpc.second->data.enpcId ) + { + insertQuest( player, npcData, eventNpc.first, mapData ); + } + break; + } + case EventHandler::EventHandlerType::GuildLeveAssignment: + { + if( player.hasReward( static_cast< Common::UnlockEntry >( 5 ) ) ) + { + auto& guildLeve = exdData.getRow< Excel::GuildleveAssignment >( npcData )->data(); + + eventData.iconId = exdData.getRow< Excel::EventIconType >( 5 )->data().MapAvailable + 1; + + if( player.isQuestCompleted( guildLeve.UnlockQuest ) || + ( ( guildLeve.NeedGrandCompanyRank > 0 || npcData == 393217 || npcData == 393223 || npcData == 393225 ) && // Leve npc locations: Bentbranch / Horizon / Swiftperch + ( player.isQuestCompleted( 220 ) || player.isQuestCompleted( 687 ) || player.isQuestCompleted( 693 ) ) ) ) + { + if( guildLeve.NeedGrandCompanyRank > 0 && player.getGc() != 0 ) + { + for( int8_t i = 0; i < 3; i++ ) + { + if( player.getGcRankArray()[ i ] >= guildLeve.NeedGrandCompanyRank ) + { + mapData.insert( eventData ); + break; + } + } + } + else + { + mapData.insert( eventData ); + } + } + } + break; + } + case EventHandler::EventHandlerType::CustomTalk: + { + // Include only the beginner arena icon yet. There a few other ones, that aren't referenced in the game files (Some examples are: The Triple Triad Tournament npc which has multiple icons and the ocean fishing icon) + if( npcData == 721223 ) + { + auto customTalk = exdData.getRow< Excel::CustomTalk >( npcData )->data(); + + eventData.iconId = customTalk.MapIcon; + + mapData.insert( eventData ); + } + break; + } + case EventHandler::EventHandlerType::GuildOrderGuide: + { + if( player.hasReward( static_cast< Common::UnlockEntry>( 7 ) ) ) + { + eventData.iconId = exdData.getRow< Excel::EventIconType >( 6 )->data().MapAvailable + 1; + + mapData.insert( eventData ); + } + break; + } + case EventHandler::EventHandlerType::TripleTriad: + { + if( npcData == 2293771 ) // Triple Triad Master npc for now only + { + eventData.iconId = exdData.getRow< Excel::EventIconType >( 7 )->data().MapAvailable + 1; + + mapData.insert( eventData ); + } + break; + } + } + } + } + + auto eventObjs = objectCache.getAllEObj( player.getTerritoryTypeId() ); + if( !eventObjs ) + return; + + for( const auto& eventObj : *eventObjs ) + { + auto eObj = exdData.getRow< Excel::EObj >( eventObj.second->data.BaseId ); + if( !eObj ) + return; + + auto eObjData = eObj->data(); + EventData eventData; + eventData.handlerId = eObjData.EventHandler; + eventData.layoutId = eventObj.first; + + auto eventHandlerType = static_cast< EventHandler::EventHandlerType >( eObjData.EventHandler >> 16 ); + + if( eventHandlerType == EventHandler::EventHandlerType::Quest ) + { + auto& quest = m_quests[ eObjData.EventHandler ]->data(); + + if( quest.Client == eventObj.second->data.BaseId ) + { + insertQuest( player, eObjData.EventHandler, eventObj.first, mapData ); + } + } + } + + sendPackets( player, mapData, All ); +} + +void MapMgr::updateQuests( Entity::Player& player ) +{ + auto& exdData = Common::Service< Data::ExdData >::ref(); + auto& objectCache = Common::Service< Sapphire::InstanceObjectCache >::ref(); + + EventSet mapData; + + auto eventNpcs = objectCache.getAllENpc( player.getTerritoryTypeId() ); + if( !eventNpcs ) + return; + + for( const auto& eventNpc : *eventNpcs ) + { + auto eNpc = exdData.getRow< Excel::ENpcBase >( eventNpc.second->data.enpcId ); + if( !eNpc ) + continue; + + auto eNpcData = eNpc->data().EventHandler; + + for( int npcEvent = 0; npcEvent < 32; npcEvent++ ) + { + auto npcData = eNpcData[ npcEvent ].EventHandler; + + if( npcData == 0 ) + continue; // Some npcs have data gaps, so we have to iterate through the entire array + + EventData eventData; + eventData.handlerId = npcData; + eventData.layoutId = eventNpc.first; + + auto eventHandlerType = static_cast< EventHandler::EventHandlerType >( npcData >> 16 ); + + if( eventHandlerType == EventHandler::EventHandlerType::Quest ) + { + auto& quest = m_quests[ npcData ]->data(); + + if( quest.Client == eventNpc.second->data.enpcId ) + { + insertQuest( player, npcData, eventNpc.first, mapData ); + } + } + } + } + + auto eventObjs = objectCache.getAllEObj( player.getTerritoryTypeId() ); + if( !eventObjs ) + return; + + for( const auto& eventObj : *eventObjs ) + { + auto eObj = exdData.getRow< Excel::EObj >( eventObj.second->data.BaseId ); + if( !eObj ) + return; + + auto eObjData = eObj->data(); + EventData eventData; + eventData.handlerId = eObjData.EventHandler; + eventData.layoutId = eventObj.first; + + auto eventHandlerType = static_cast< EventHandler::EventHandlerType >( eObjData.EventHandler >> 16 ); + + if( eventHandlerType == EventHandler::EventHandlerType::Quest ) + { + auto& quest = m_quests[ eObjData.EventHandler ]->data(); + + if( quest.Client == eventObj.second->data.BaseId ) + { + insertQuest( player, eObjData.EventHandler, eventObj.first, mapData ); + } + } + } + + sendPackets( player, mapData, Quest ); +} + +void MapMgr::insertQuest( Entity::Player& player, uint32_t questId, uint32_t layoutId, EventSet& mapData ) +{ + auto& exdData = Common::Service< Data::ExdData >::ref(); + auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref(); + + auto& quest = m_quests[ questId ]->data(); + + if( isQuestVisible( player, questId, quest ) ) + { + auto script = scriptMgr.getNativeScriptHandler().getScript< Sapphire::ScriptAPI::QuestScript >( questId ); + + // Just don't show quests on map, that aren't implemented yet + if( !script ) + return; + + EventData eventData; + eventData.handlerId = questId; + eventData.layoutId = layoutId; + + auto eventState = script->getQuestAvailability( player, questId ); + + if( eventState == Event::EventHandler::QuestAvailability::Available || eventState == Event::EventHandler::QuestAvailability::Locked ) + { + if( eventState == Event::EventHandler::QuestAvailability::Available && isQuestAvailable( player, questId, quest ) ) + eventData.iconId = exdData.getRow< Excel::EventIconType >( quest.IconType )->data().MapAvailable + 1 + quest.Repeatable; + else + eventData.iconId = exdData.getRow< Excel::EventIconType >( quest.IconType )->data().MapInvalid + 1 + quest.Repeatable; + + mapData.insert( eventData ); + } + } +} + +bool MapMgr::isQuestAvailable( Entity::Player& player, uint32_t questId, Excel::Quest& quest ) +{ + auto& exdData = Common::Service< Data::ExdData >::ref(); + + if( quest.GrandCompany || quest.GrandCompanyRank ) + { + if( quest.GrandCompany != player.getGc() && quest.GrandCompanyRank > player.getGcRankArray()[ player.getGc() - 1 ] ) + return false; + } + + if( quest.InstanceContentOperator == 1 ) + { + for( int32_t i = 0; i < 3; i++ ) + { + if( quest.InstanceContent[ i ] == 0 ) + continue; + + return false; + } + + return true; + } + else if( quest.InstanceContentOperator == 2 ) + { + for( int32_t i = 0; i < 3; i++ ) + { + if( quest.InstanceContent[ i ] == 0 ) + continue; + + return false; + } + } + + if( quest.TimeBegin || quest.TimeEnd ) + { + uint64_t curEorzeaTime = Util::getEorzeanTimeStamp(); + uint32_t convTime = 100 * ( curEorzeaTime / 3600 % 24 ) + curEorzeaTime / 60 % 60; + + if( quest.TimeBegin <= quest.TimeEnd ) + { + if( convTime < quest.TimeBegin || convTime >= quest.TimeEnd ) + return false; + } + else + { + if( convTime < quest.TimeBegin && convTime >= quest.TimeEnd ) + return false; + } + } + + auto classJobCategory = exdData.getRow< Excel::ClassJobCategory >( quest.ClassJob )->data().ClassJob; + if( !classJobCategory[ static_cast< uint8_t >( player.getClass() ) ] ) + return false; + + if( quest.ClassJob2 > 1 ) + { + classJobCategory = exdData.getRow< Excel::ClassJobCategory >( quest.ClassJob2 )->data().ClassJob; + if( !classJobCategory[ static_cast< uint8_t >( player.getClass() ) ] ) + return false; + } + + return true; +} + +bool MapMgr::isQuestVisible( Entity::Player& player, uint32_t questId, Excel::Quest& quest ) +{ + auto& exdData = Common::Service< Data::ExdData >::ref(); + + if( ( player.isQuestCompleted( questId ) && ( !quest.Repeatable && questId != 67114 ) ) || player.hasQuest( questId ) ) + return false; + + if( quest.ClassJobUnlock && quest.ClassJob != 1 ) + { + if( quest.ClassJobUnlockFlag == 3 ) + if( static_cast< uint8_t >( player.getClass() ) != quest.ClassJobUnlock ) + return false; + else if( quest.ClassJobUnlockFlag == 4 ) + if ( static_cast< uint8_t >( player.getClass() ) == quest.ClassJobUnlock ) + return false; + else + return false; + } + + // Was this really ever used? + if( quest.StartTown && quest.StartTown != player.getStartTown() ) + return false; + + if( Common::CURRENT_EXPANSION_ID < quest.Expansion ) + return false; + + if( quest.Mount && !player.hasMount( quest.Mount ) ) + return false; + + if( quest.GrandCompany && quest.GrandCompany != player.getGc() ) + return false; + + if( quest.Header != 0 && !player.hasReward( static_cast< Common::UnlockEntry >( quest.Header ) ) ) + return false; + + if( quest.PrevQuestOperator == 1 ) + { + for( int32_t i = 0; i < 3; i++ ) + { + if( quest.PrevQuest[ i ] == 0 ) + continue; + + if( !player.isQuestCompleted( quest.PrevQuest[ i ] ) ) + { + // todo: see if this can be done in 3.x + /*if( i == 0 && questPtr->previousQuest0Sequence != 0 ) + { + if( player.getQuestSeq( questPtr->previousQuest[ i ] ) < questPtr->previousQuest0Sequence ) + return false; + } + else + { + return false; + }*/ + return false; + } + } + } + else if( quest.PrevQuestOperator == 2 ) + { + for( int32_t i = 0; i < 3; i++ ) + { + if( quest.PrevQuest[ i ] == 0 ) + continue; + + if( player.isQuestCompleted( quest.PrevQuest[ i ] ) ) + break; + + if( i == 2 ) + return false; + } + } + + if( quest.ExcludeQuestOperator == 1 ) + { + for( int32_t i = 0; i < 2; i++ ) + { + if( quest.ExcludeQuest[ i ] == 0 ) + continue; + + if( !player.isQuestCompleted( quest.ExcludeQuest[ i ] ) && !player.hasQuest( quest.ExcludeQuest[ i ] ) ) + break; + + if( i == 1 ) + return false; + } + } + else if( quest.ExcludeQuestOperator == 2 ) + { + for( int32_t i = 0; i < 2; i++ ) + { + if( quest.ExcludeQuest[ i ] == 0 ) + continue; + + if( player.isQuestCompleted( quest.ExcludeQuest[ i ] ) || player.hasQuest( quest.ExcludeQuest[ i ] ) ) + return false; + } + } + + if( quest.Festival ) + { + auto& territoryMgr = Common::Service< Manager::TerritoryMgr >::ref(); + auto& festival = territoryMgr.getCurrentFestival(); + + if( quest.Festival != festival.first && quest.Festival != festival.second ) + return false; + + // Don't show festivals with begin state other than 0 yet + if( quest.FestivalPhaseBegin != 0 ) + return false; + } + + if( ( quest.Type & 1 ) == 0 ) + { + auto classJobCategory = exdData.getRow< Excel::ClassJobCategory >( quest.ClassJob )->data().ClassJob; + + for( int32_t i = 1; i <= Common::CLASSJOB_TOTAL; i++ ) + { + if( i == Common::CLASSJOB_TOTAL ) + return false; + + if( classJobCategory[ i ] ) + { + if( player.getLevelForClass( static_cast< Common::ClassJob >( i ) ) >= quest.ClassLevel ) + break; + } + } + } + else + { + if( player.getLevel() < quest.ClassLevel ) + return false; + } + + // TODO: I think this changed in 3.x to be for all relics, more research is needed. + for( int32_t i = 0; i < Common::CLASSJOB_TOTAL; i++ ) + { + auto classJob = exdData.getRow< Excel::ClassJob >( i ); + + if( classJob->data().ARRRelicQuestId == questId ) + { + for( int32_t j = 0; i < Common::CLASSJOB_TOTAL; i++ ) + { + classJob = exdData.getRow< Excel::ClassJob >( i ); + + if( player.hasQuest( classJob->data().ARRRelicQuestId ) ) + return false; + } + + break; + } + } + + if( quest.BeastTribe ) + return false; + + if( quest.House ) + return false; + + if( quest.DeliveryQuest ) + return false; + + // TODO: dunno if 3.x has this, have to check + /*if( player.getQuestSeq( questId ) == 0 ) + { + auto& questAccept = exdData.getRow< Excel::QuestAcceptAdditionCondition >( questId ); + + if( questAccept ) + { + for( int32_t i = 0; i < 2; i++ ) + { + if( ( &questAccept->requirement0 )[ i ] >= 65536 ) + { + if( !player.isActionLearned( 245 ) && !player.isQuestCompleted( ( &questAccept->requirement0 )[ i ] ) ) + return false; + } + else + { + if( !player.isActionLearned( ( &questAccept->requirement0 )[ i ] ) ) + return false; + } + } + } + }*/ + + return true; +} + +bool MapMgr::isTripleTriadAvailable( Entity::Player& player, uint32_t tripleTriadId ) +{ + // TODO: map out Triple Triad sheet + /*auto& exdData = Common::Service< Data::ExdData >::ref(); + auto tripleTriad = exdData.getRow< Excel::TripleTriad >( tripleTriadId ); + + if( tripleTriad->previousQuestJoin == 1 ) + { + for( int32_t i = 0; i < 3; i++ ) + { + if( tripleTriad->previousQuest[ i ] == 0 ) + continue; + + if( !player.isQuestCompleted( tripleTriad->previousQuest[ i ] ) ) + return false; + } + } + else if( tripleTriad->previousQuestJoin == 2 ) + { + for( int32_t i = 0; i < 3; i++ ) + { + if( tripleTriad->previousQuest[ i ] == 0 ) + continue; + + if( player.isQuestCompleted( tripleTriad->previousQuest[ i ] ) ) + break; + + if( i == 2 ) + return false; + } + }*/ + + return true; +} + +void MapMgr::fillPacket( EventSet& mapData, uint32_t* iconIds, uint32_t* layoutIds, uint32_t* handlerIds ) +{ + int32_t i = 0; + for( auto& eventData : mapData ) + { + iconIds[ i ] = eventData.iconId; + layoutIds[ i ] = eventData.layoutId; + handlerIds[ i ] = eventData.handlerId; + + i++; + } +} + +void MapMgr::sendPackets( Entity::Player& player, EventSet& mapData, UpdateMode updateMode ) +{ + auto& server = Common::Service< World::WorldServer >::ref(); + + server.queueForPlayer( player.getCharacterId(), makeActorControlSelf( player.getId(), Network::ActorControl::BeginMapUpdate, updateMode ) ); + + if( mapData.size() <= 2 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapMarker2 >( player.getId() ); + mapUpdatePacket->data().numOfMarkers = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().layoutIds, mapUpdatePacket->data().handlerIds ); + + server.queueForPlayer( player.getCharacterId(), mapUpdatePacket ); + } + else if( mapData.size() <= 4 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapMarker4 >( player.getId() ); + mapUpdatePacket->data().numOfMarkers = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().layoutIds, mapUpdatePacket->data().handlerIds ); + + server.queueForPlayer( player.getCharacterId(), mapUpdatePacket ); + } + else if( mapData.size() <= 8 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapMarker8 >( player.getId() ); + mapUpdatePacket->data().numOfMarkers = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().layoutIds, mapUpdatePacket->data().handlerIds ); + + server.queueForPlayer( player.getCharacterId(), mapUpdatePacket ); + } + else if( mapData.size() <= 16 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapMarker16 >( player.getId() ); + mapUpdatePacket->data().numOfMarkers = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().layoutIds, mapUpdatePacket->data().handlerIds ); + + server.queueForPlayer( player.getCharacterId(), mapUpdatePacket ); + } + else if( mapData.size() <= 32 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapMarker32 >( player.getId() ); + mapUpdatePacket->data().numOfMarkers = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().layoutIds, mapUpdatePacket->data().handlerIds ); + + server.queueForPlayer( player.getCharacterId(), mapUpdatePacket ); + } + else if( mapData.size() <= 64 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapMarker64 >( player.getId() ); + mapUpdatePacket->data().numOfMarkers = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().layoutIds, mapUpdatePacket->data().handlerIds ); + + server.queueForPlayer( player.getCharacterId(), mapUpdatePacket ); + } + else if( mapData.size() <= 128 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapMarker128 >( player.getId() ); + mapUpdatePacket->data().numOfMarkers = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().layoutIds, mapUpdatePacket->data().handlerIds ); + + server.queueForPlayer( player.getCharacterId(), mapUpdatePacket ); + } + + server.queueForPlayer( player.getCharacterId(), makeActorControlSelf( player.getId(), Network::ActorControl::FinishMapUpdate ) ); +} \ No newline at end of file diff --git a/src/world/Manager/MapMgr.h b/src/world/Manager/MapMgr.h new file mode 100644 index 00000000..6981ecf0 --- /dev/null +++ b/src/world/Manager/MapMgr.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include "ForwardsZone.h" + +#include "Territory/Territory.h" + +#include +#include + +namespace Sapphire::World::Manager +{ + using QuestMap = std::unordered_map< uint32_t, std::shared_ptr< Excel::ExcelStruct< Excel::Quest > > >; + + class MapMgr + { + public: + enum UpdateMode : uint8_t + { + Quest = 1, + GuildLeveAssignment = 2, + GuildOrderGuide = 4, + TripleTriad = 8, + CustomTalk = 16, + PreHandler = 32, + + Fates = 0x0F, + + All = 0x3F + }; + + MapMgr() = default; + + bool loadQuests(); + + void updateAll( Entity::Player& player ); + void updateQuests( Entity::Player& player ); + + private: + struct EventData + { + uint32_t iconId; + uint32_t layoutId; + uint32_t handlerId; + }; + + struct less + { + constexpr bool operator()( const EventData& _Left, const EventData& _Right ) const + { + const uint16_t left = _Left.handlerId; + const uint16_t right = _Right.handlerId; + + if( left == right ) + { + const uint16_t typeLeft = _Left.handlerId >> 16; + const uint16_t typeRight = _Right.handlerId >> 16; + + return typeLeft < typeRight; + } + + return left < right; + } + }; + + using EventSet = std::multiset< EventData, less >; + + QuestMap m_quests; + + void insertQuest( Entity::Player& player, uint32_t questId, uint32_t layoutId, EventSet& mapData ); + + bool isQuestVisible( Entity::Player& player, uint32_t questId, Excel::Quest& quest ); + bool isQuestAvailable( Entity::Player& player, uint32_t questId, Excel::Quest& quest ); + bool isTripleTriadAvailable( Entity::Player& player, uint32_t tripleTriadId ); + + void fillPacket( EventSet& mapData, uint32_t* iconIds, uint32_t* levelIds, uint32_t* eventIds ); + void sendPackets( Entity::Player& player, EventSet& mapData, UpdateMode updateMode ); + }; + +} \ No newline at end of file diff --git a/src/world/Manager/WarpMgr.cpp b/src/world/Manager/WarpMgr.cpp index fdc45a71..584df21c 100644 --- a/src/world/Manager/WarpMgr.cpp +++ b/src/world/Manager/WarpMgr.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "Territory/Territory.h" #include "Actor/Player.h" @@ -103,6 +104,8 @@ void WarpMgr::finishWarp( Entity::Player& player ) player.sendToInRangeSet( setStatusPacket, true ); playerMgr.onUnsetStateFlag( player, PlayerStateFlag::BetweenAreas ); + + Common::Service< MapMgr >::ref().updateAll( player ); } void WarpMgr::requestPlayerTeleport( Entity::Player& player, uint16_t aetheryteId, uint8_t teleportType ) diff --git a/src/world/Network/PacketWrappers/PlayerSetupPacket.h b/src/world/Network/PacketWrappers/PlayerSetupPacket.h index 8e810042..221c09c1 100644 --- a/src/world/Network/PacketWrappers/PlayerSetupPacket.h +++ b/src/world/Network/PacketWrappers/PlayerSetupPacket.h @@ -49,7 +49,7 @@ namespace Sapphire::Network::Packets::WorldPackets::Server memcpy( m_data.Aetheryte, player.getAetheryteArray().data(), sizeof( m_data.Aetheryte ) ); // Set the class levels and exp. - for( uint8_t i = 0; i < Common::CLASSJOB_TOTAL; ++i ) + for( uint8_t i = 0; i < Common::CLASSJOB_SLOTS; ++i ) { m_data.Lv[ i ] = player.getClassArray()[ i ]; m_data.Exp[ i ] = player.getExpArray()[ i ]; diff --git a/src/world/Script/NativeScriptApi.cpp b/src/world/Script/NativeScriptApi.cpp index d93663c6..87997387 100644 --- a/src/world/Script/NativeScriptApi.cpp +++ b/src/world/Script/NativeScriptApi.cpp @@ -191,6 +191,11 @@ namespace Sapphire::ScriptAPI { } + Event::EventHandler::QuestAvailability QuestScript::getQuestAvailability( Sapphire::Entity::Player& player, uint32_t eventId ) + { + return Event::EventHandler::QuestAvailability::Available; + } + /////////////////////////////////////////////////////////////////// EventObjectScript::EventObjectScript( uint32_t eobjId ) : ScriptObject( eobjId, typeid( EventObjectScript ).hash_code() ) diff --git a/src/world/Script/NativeScriptApi.h b/src/world/Script/NativeScriptApi.h index 2ec2c3bc..531a9b40 100644 --- a/src/world/Script/NativeScriptApi.h +++ b/src/world/Script/NativeScriptApi.h @@ -273,6 +273,8 @@ namespace Sapphire::ScriptAPI virtual void onEObjHit( World::Quest& quest, Sapphire::Entity::Player& player, uint64_t actorId, uint32_t actionId ); + virtual Event::EventHandler::QuestAvailability getQuestAvailability( Sapphire::Entity::Player& player, uint32_t eventId ); + World::Manager::EventMgr& eventMgr() { return Common::Service< World::Manager::EventMgr >::ref(); diff --git a/src/world/Territory/InstanceObjectCache.cpp b/src/world/Territory/InstanceObjectCache.cpp index 71d418bc..455975b2 100644 --- a/src/world/Territory/InstanceObjectCache.cpp +++ b/src/world/Territory/InstanceObjectCache.cpp @@ -43,13 +43,16 @@ Sapphire::InstanceObjectCache::InstanceObjectCache() std::string bgLgbPath( path + "/level/bg.lgb" ); std::string planmapLgbPath( path + "/level/planmap.lgb" ); std::string planeventLgbPath( path + "/level/planevent.lgb" ); + std::string plannerLgbPath( path + "/level/planner.lgb" ); std::vector< char > bgSection; std::vector< char > planmapSection; std::vector< char > planeventSection; + std::vector< char > plannerSection; std::unique_ptr< xiv::dat::File > bgFile; std::unique_ptr< xiv::dat::File > planmap_file; std::unique_ptr< xiv::dat::File > planevent_file; + std::unique_ptr< xiv::dat::File > planner_file; try { @@ -75,9 +78,23 @@ Sapphire::InstanceObjectCache::InstanceObjectCache() LGB_FILE planmapLgb( &planmapSection[ 0 ], "planmap" ); LGB_FILE planeventLgb( &planeventSection[ 0 ], "planevent" ); - std::vector< LGB_FILE > lgbList{ bgLgb, planmapLgb, planeventLgb }; uint32_t max_index = 0; + std::vector< LGB_FILE > lgbList; + + try + { + planner_file = exdData.getGameData()->getFile( plannerLgbPath ); + plannerSection = planner_file->access_data_sections().at( 0 ); + LGB_FILE plannerLgb( &plannerSection[ 0 ], "planner" ); + + lgbList = { bgLgb, planmapLgb, planeventLgb, plannerLgb }; + } + catch( std::runtime_error& ) + { + lgbList = { bgLgb, planmapLgb, planeventLgb }; + } + for( const auto& lgb : lgbList ) { for( const auto& group : lgb.groups ) @@ -110,12 +127,12 @@ Sapphire::InstanceObjectCache::InstanceObjectCache() else if( pEntry->getType() == LgbEntryType::EventObject ) { auto pEObj = std::reinterpret_pointer_cast< LGB_EOBJ_ENTRY >( pEntry ); - m_eobjCache.insert( 0, pEObj ); + m_eobjCache.insert( id, pEObj ); } else if( pEntry->getType() == LgbEntryType::EventNpc ) { auto pENpc = std::reinterpret_pointer_cast< LGB_ENPC_ENTRY >( pEntry ); - m_enpcCache.insert( 0, pENpc ); + m_enpcCache.insert( id, pENpc ); } else if( pEntry->getType() == LgbEntryType::EventRange ) { @@ -129,8 +146,8 @@ Sapphire::InstanceObjectCache::InstanceObjectCache() std::cout << "\n"; Logger::debug( - "InstanceObjectCache Cached: MapRange: {} ExitRange: {} PopRange: {} EventNpc: {} EventRange: {}", - m_mapRangeCache.size(), m_exitRangeCache.size(), m_popRangeCache.size(), m_enpcCache.size(), m_eventRangeCache.size() + "InstanceObjectCache Cached: MapRange: {} ExitRange: {} PopRange: {} EventObj: {} EventNpc: {} EventRange: {}", + m_mapRangeCache.size(), m_exitRangeCache.size(), m_popRangeCache.size(), m_eobjCache.size(), m_enpcCache.size(), m_eventRangeCache.size() ); } @@ -165,6 +182,18 @@ Sapphire::InstanceObjectCache::ENpcPtr return m_enpcCache.get( 0, eNpcId ); } +Sapphire::InstanceObjectCache::EObjMapPtr + Sapphire::InstanceObjectCache::getAllEObj( uint16_t zoneId ) +{ + return m_eobjCache.getAll( zoneId ); +} + +Sapphire::InstanceObjectCache::ENpcMapPtr + Sapphire::InstanceObjectCache::getAllENpc( uint16_t zoneId ) +{ + return m_enpcCache.getAll( zoneId ); +} + Sapphire::InstanceObjectCache::EventRangePtr Sapphire::InstanceObjectCache::getEventRange( uint32_t eventRangeId ) { return m_eventRangeCache.get( 0, eventRangeId ); diff --git a/src/world/Territory/InstanceObjectCache.h b/src/world/Territory/InstanceObjectCache.h index a8b24dc6..9010681e 100644 --- a/src/world/Territory/InstanceObjectCache.h +++ b/src/world/Territory/InstanceObjectCache.h @@ -41,6 +41,16 @@ namespace Sapphire return nullptr; } + ObjectMap* getAll( uint16_t zoneId ) + { + auto it = m_objectCache.find( zoneId ); + if( it != m_objectCache.end() ) + { + return &it->second; + } + return nullptr; + } + void insert( uint16_t zoneId, std::shared_ptr< T > entry ) { if( m_objectCache.find( zoneId ) == m_objectCache.end() ) @@ -76,6 +86,9 @@ namespace Sapphire using ENpcPtr = std::shared_ptr< LGB_ENPC_ENTRY >; using EventRangePtr = std::shared_ptr< LGB_EVENT_RANGE_ENTRY >; + using EObjMapPtr = std::unordered_map< uint32_t, EObjPtr >*; + using ENpcMapPtr = std::unordered_map< uint32_t, ENpcPtr >*; + struct PopRangeInfo { Common::FFXIVARR_POSITION3 m_pos; @@ -95,6 +108,10 @@ namespace Sapphire EObjPtr getEObj( uint32_t eObjId ); ENpcPtr getENpc( uint32_t eNpcId ); + + EObjMapPtr getAllEObj( uint16_t zoneId ); + ENpcMapPtr getAllENpc( uint16_t zoneId ); + EventRangePtr getEventRange( uint32_t eventRangeId ); private: diff --git a/src/world/WorldServer.cpp b/src/world/WorldServer.cpp index 32f56f40..6ca5b355 100644 --- a/src/world/WorldServer.cpp +++ b/src/world/WorldServer.cpp @@ -52,6 +52,7 @@ #include "Manager/BlacklistMgr.h" #include "Manager/WarpMgr.h" #include "Manager/FreeCompanyMgr.h" +#include "Manager/MapMgr.h" #include "ContentFinder/ContentFinder.h" @@ -223,6 +224,16 @@ void WorldServer::run( int32_t argc, char* argv[] ) } Common::Service< Manager::ActionMgr >::set( pActionMgr ); + auto pMapMgr = std::make_shared< Manager::MapMgr >(); + + Logger::info( "MapMgr: Caching quests" ); + if( !pMapMgr->loadQuests() ) + { + Logger::fatal( "Unable to cache quests!" ); + return; + } + Common::Service< Manager::MapMgr >::set( pMapMgr ); + auto pNaviMgr = std::make_shared< Manager::NaviMgr >(); Common::Service< Manager::NaviMgr >::set( pNaviMgr );