From e8c7c831971b3e116eba781f1b2602f7341c49f7 Mon Sep 17 00:00:00 2001 From: Taezen <86413840+Taezen@users.noreply.github.com> Date: Mon, 30 Aug 2021 10:16:05 +0200 Subject: [PATCH] Map support --- src/common/Network/CommonActorControl.h | 14 +- src/common/Network/PacketDef/Ipcs.h | 8 + .../Network/PacketDef/Zone/ServerZoneDef.h | 60 +- src/world/Actor/Player.cpp | 16 +- src/world/Actor/Player.h | 7 +- src/world/Actor/PlayerInventory.cpp | 5 + src/world/Actor/PlayerQuest.cpp | 19 +- src/world/Event/EventHandler.h | 2 + src/world/Manager/MapMgr.cpp | 889 ++++++++++++++++++ src/world/Manager/MapMgr.h | 132 +++ src/world/ServerMgr.cpp | 5 + 11 files changed, 1148 insertions(+), 9 deletions(-) create mode 100644 src/world/Manager/MapMgr.cpp create mode 100644 src/world/Manager/MapMgr.h diff --git a/src/common/Network/CommonActorControl.h b/src/common/Network/CommonActorControl.h index 27657d42..c713148c 100644 --- a/src/common/Network/CommonActorControl.h +++ b/src/common/Network/CommonActorControl.h @@ -215,7 +215,19 @@ namespace Sapphire::Network::ActorControl SetFavorite = 0x1FC, LearnTeleport = 0x1FD, - OpenRecommendationGuide = 0x200, + /*! + * param1 = event type bitmask + * 1 = Quest + * 2 = GuildLeveAssignment + * 4 = GuildOrderGuide + * 8 = TripleTriad + * 16 = CustomTalk + * 32 = PreHandler + */ + BeginMapUpdate = 0x1FF, + FinishMapUpdate = 0x200, + + //OpenRecommendationGuide = 0x200, ArmoryErrorMsg = 0x201, AchievementPopup = 0x203, diff --git a/src/common/Network/PacketDef/Ipcs.h b/src/common/Network/PacketDef/Ipcs.h index 7bce247e..6d61cc27 100644 --- a/src/common/Network/PacketDef/Ipcs.h +++ b/src/common/Network/PacketDef/Ipcs.h @@ -282,6 +282,14 @@ namespace Sapphire::Network::Packets DailyQuests = 0x0331, // updated 5.58 DailyQuestRepeatFlags = 0x01D1, // updated 5.58 + MapUpdate = 0x03A2, // updated 5.58 + MapUpdate4 = 0x0284, // updated 5.58 + MapUpdate8 = 0x01BC, // updated 5.58 + MapUpdate16 = 0x02D1, // updated 5.58 + MapUpdate32 = 0x00DB, // updated 5.58 + MapUpdate64 = 0x0368, // updated 5.58 + MapUpdate128 = 0x0349, // updated 5.58 + /// Doman Mahjong ////////////////////////////////////// MahjongOpenGui = 0x02A4, // only available in mahjong instance MahjongNextRound = 0x02BD, // initial hands(baipai), # of riichi(wat), winds, honba, score and stuff diff --git a/src/common/Network/PacketDef/Zone/ServerZoneDef.h b/src/common/Network/PacketDef/Zone/ServerZoneDef.h index c0899b1e..6a0652a6 100644 --- a/src/common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ServerZoneDef.h @@ -928,12 +928,12 @@ namespace Sapphire::Network::Packets::Server uint8_t unknown5; uint32_t unknown8; - uint16_t festivalId; - uint16_t additionalFestivalId; uint32_t unknown9; uint32_t unknown10; + uint32_t festivalId; uint32_t unknown11; - uint32_t unknown12[4]; + uint32_t unknown12[3]; + uint32_t additionalFestivalId; uint32_t unknown13[3]; Common::FFXIVARR_POSITION3 pos; uint32_t unknown14[3]; @@ -2289,6 +2289,60 @@ namespace Sapphire::Network::Packets::Server uint16_t padding3; } actors[2]; }; + + //For quests this is only used for pre-accepted ones. Accepted quests are getting handled by the client. + template< int ArgCount > + struct FFXIVIpcMapUpdateN + { + uint8_t entryCount; + uint8_t padding[ 3 ]; + uint32_t iconIds[ ArgCount ]; + uint32_t levelIds[ ArgCount ]; + uint32_t eventIds[ ArgCount ]; // possible event ids for this: Quest, GuildLeveAssignment, GuildOrderGuide, TripleTriad, CustomTalk, PreHandler + uint8_t additionalData[ ArgCount ]; // use unknown + }; + + struct FFXIVIpcMapUpdate : + FFXIVIpcBasePacket< MapUpdate >, + FFXIVIpcMapUpdateN< 2 > + { + }; + + struct FFXIVIpcMapUpdate4 : + FFXIVIpcBasePacket< MapUpdate4 >, + FFXIVIpcMapUpdateN< 4 > + { + }; + + struct FFXIVIpcMapUpdate8 : + FFXIVIpcBasePacket< MapUpdate8 >, + FFXIVIpcMapUpdateN< 8 > + { + }; + + struct FFXIVIpcMapUpdate16 : + FFXIVIpcBasePacket< MapUpdate16 >, + FFXIVIpcMapUpdateN< 16 > + { + }; + + struct FFXIVIpcMapUpdate32 : + FFXIVIpcBasePacket< MapUpdate32 >, + FFXIVIpcMapUpdateN< 32 > + { + }; + + struct FFXIVIpcMapUpdate64 : + FFXIVIpcBasePacket< MapUpdate64 >, + FFXIVIpcMapUpdateN< 64 > + { + }; + + struct FFXIVIpcMapUpdate128 : + FFXIVIpcBasePacket< MapUpdate128 >, + FFXIVIpcMapUpdateN< 128 > + { + }; } #endif /*_CORE_NETWORK_PACKETS_SERVER_IPC_H*/ diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index a71184a5..d358aa1c 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -19,6 +19,7 @@ #include "Manager/HousingMgr.h" #include "Manager/TerritoryMgr.h" #include "Manager/RNGMgr.h" +#include "Manager/MapMgr.h" #include "Territory/Territory.h" #include "Territory/ZonePosition.h" @@ -730,7 +731,7 @@ void Sapphire::Entity::Player::learnSong( uint8_t songId, uint32_t itemId ) queuePacket( makeActorControlSelf( getId(), ToggleOrchestrionUnlock, songId, 1, itemId ) ); } -bool Sapphire::Entity::Player::isActionLearned( uint8_t actionId ) const +bool Sapphire::Entity::Player::isActionLearned( uint16_t actionId ) const { uint16_t index; uint8_t value; @@ -1286,6 +1287,17 @@ const uint8_t* Sapphire::Entity::Player::getMountGuideBitmask() const return m_mountGuide; } +const bool Sapphire::Entity::Player::hasMount( int16_t mountId ) const +{ + auto& exdData = Common::Service< Data::ExdDataGenerated >::ref(); + auto mount = exdData.get< Data::Mount >( mountId ); + + if( mount->order == -1 || mount->modelChara == 0 ) + return false; + + return m_mountGuide[ mount->order / 8 ] & ( 1 << ( mount->order % 8 ) ); +} + uint64_t Sapphire::Entity::Player::getContentId() const { return m_contentId; @@ -1890,6 +1902,8 @@ Sapphire::Entity::Player::sendZoneInPackets( uint32_t param1, uint32_t param2 = setZoningType( Common::ZoneingType::None ); unsetStateFlag( PlayerStateFlag::BetweenAreas ); + + Common::Service< MapMgr >::ref().updateAll( *this ); } void Sapphire::Entity::Player::finishZoning() diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index 63f41eb6..fbb2aedb 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -194,6 +194,8 @@ namespace Sapphire::Entity /*! remove a given quest */ void removeQuest( uint16_t questId ); + bool isQuestCompleted( uint16_t questId ); + /*! add a quest to the completed quests mask */ void updateQuestsCompleted( uint32_t questId ); @@ -644,7 +646,7 @@ namespace Sapphire::Entity void learnSong( uint8_t songId, uint32_t itemId ); /*! check if an action is already unlocked in the bitmask. */ - bool isActionLearned( uint8_t actionId ) const; + bool isActionLearned( uint16_t actionId ) const; /*! return a const pointer to the unlock bitmask array */ const uint8_t* getUnlockBitmask() const; @@ -655,6 +657,8 @@ namespace Sapphire::Entity /*! return a const pointer to the mount guide bitmask array */ const uint8_t* getMountGuideBitmask() const; + const bool hasMount( int16_t mountId ) const; + bool checkAction() override; bool hasQueuedAction() const; @@ -950,6 +954,7 @@ namespace Sapphire::Entity uint16_t calculateEquippedGearItemLevel(); ItemPtr getEquippedWeapon(); + ItemPtr getEquippedSecondaryWeapon(); /*! return the current amount of currency of type */ uint32_t getCurrency( Common::CurrencyType type ); diff --git a/src/world/Actor/PlayerInventory.cpp b/src/world/Actor/PlayerInventory.cpp index 2d4763fc..f342ea00 100644 --- a/src/world/Actor/PlayerInventory.cpp +++ b/src/world/Actor/PlayerInventory.cpp @@ -939,6 +939,11 @@ Sapphire::ItemPtr Sapphire::Entity::Player::getEquippedWeapon() return m_storageMap[ GearSet0 ]->getItem( GearSetSlot::MainHand ); } +Sapphire::ItemPtr Sapphire::Entity::Player::getEquippedSecondaryWeapon() +{ + return m_storageMap[ InventoryType::GearSet0 ]->getItem( GearSetSlot::OffHand ); +} + uint8_t Sapphire::Entity::Player::getFreeSlotsInBags() { uint8_t slots = 0; diff --git a/src/world/Actor/PlayerQuest.cpp b/src/world/Actor/PlayerQuest.cpp index 6eaafc91..e99d27f9 100644 --- a/src/world/Actor/PlayerQuest.cpp +++ b/src/world/Actor/PlayerQuest.cpp @@ -7,6 +7,8 @@ #include "Network/GameConnection.h" #include "Network/PacketWrappers/QuestMessagePacket.h" +#include "Manager/MapMgr.h" + #include "Session.h" using namespace Sapphire::Common; @@ -42,6 +44,11 @@ void Sapphire::Entity::Player::removeQuest( uint16_t questId ) if( ( idx != -1 ) && ( m_activeQuests[ idx ] != nullptr ) ) { + std::shared_ptr< QuestActive > pQuest = m_activeQuests[ idx ]; + m_activeQuests[ idx ].reset(); + + Common::Service< World::Manager::MapMgr >::ref().updateQuests( *this ); + auto questUpdatePacket = makeZonePacket< FFXIVIpcQuestUpdate >( getId() ); questUpdatePacket->data().slot = static_cast< uint8_t >( idx ); questUpdatePacket->data().questInfo.c.questId = 0; @@ -54,9 +61,6 @@ void Sapphire::Entity::Player::removeQuest( uint16_t questId ) m_questTracking[ ii ] = -1; } - std::shared_ptr< QuestActive > pQuest = m_activeQuests[ idx ]; - m_activeQuests[ idx ].reset(); - m_questIdToQuestIdx.erase( questId ); m_questIdxToQuestId.erase( idx ); @@ -916,6 +920,8 @@ void Sapphire::Entity::Player::updateQuest( uint16_t questId, uint8_t sequence ) m_questIdToQuestIdx[ questId ] = idx; m_questIdxToQuestId[ idx ] = questId; + Common::Service< World::Manager::MapMgr >::ref().updateQuests( *this ); + auto questUpdatePacket = makeZonePacket< FFXIVIpcQuestUpdate >( getId() ); questUpdatePacket->data().slot = idx; questUpdatePacket->data().questInfo = *pNewQuest; @@ -1013,6 +1019,11 @@ Sapphire::Entity::Player::sendQuestMessage( uint32_t questId, int8_t msgId, uint } +bool Sapphire::Entity::Player::isQuestCompleted( uint16_t questId ) +{ + return ( m_questCompleteFlags[ questId / 8 ] & ( 0x80 >> ( questId % 8 ) ) ); +} + void Sapphire::Entity::Player::updateQuestsCompleted( uint32_t questId ) { uint16_t index = questId / 8; @@ -1032,6 +1043,8 @@ void Sapphire::Entity::Player::removeQuestsCompleted( uint32_t questId ) m_questCompleteFlags[ index ] ^= value; + Common::Service< World::Manager::MapMgr >::ref().updateQuests( *this ); + } bool Sapphire::Entity::Player::giveQuestRewards( uint32_t questId, uint32_t optionalChoice ) diff --git a/src/world/Event/EventHandler.h b/src/world/Event/EventHandler.h index 4afc4480..cec7d9a2 100644 --- a/src/world/Event/EventHandler.h +++ b/src/world/Event/EventHandler.h @@ -78,6 +78,8 @@ namespace Sapphire::Event FcTalk = 0x001F, Adventure = 0x0021, DailyQuestSupply = 0x0022, + TripleTriad = 0x0023, + PreHandler = 0x0036, ICDirector = 0x8003, PublicContentDirector = 0x8004, QuestBattleDirector = 0x8006, diff --git a/src/world/Manager/MapMgr.cpp b/src/world/Manager/MapMgr.cpp new file mode 100644 index 00000000..8e81c1e6 --- /dev/null +++ b/src/world/Manager/MapMgr.cpp @@ -0,0 +1,889 @@ +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "MapMgr.h" +#include "TerritoryMgr.h" + +#include "ServerMgr.h" + +using namespace Sapphire::Event; +using namespace Sapphire::Network::Packets; +using namespace Sapphire::Network::Packets::Server; + +Sapphire::World::Manager::MapMgr::MapMgr() +{ + auto& exdData = Common::Service< Data::ExdDataGenerated >::ref(); + + size_t count = 0; + + for( auto quest : exdData.m_QuestDat.get_rows() ) + { + if( exdData.getField< std::string >( quest.second, 1 ).empty() ) // id + continue; + + auto& questData = m_questData[ quest.first ]; + + questData.previousQuestJoin = exdData.getField< uint8_t >( quest.second, 8 ); + questData.previousQuestKeys[ 0 ] = exdData.getField< uint32_t >( quest.second, 9 ); + questData.previousQuest0Sequence = exdData.getField< uint8_t >( quest.second, 10 ); + questData.previousQuestKeys[ 1 ] = exdData.getField< uint32_t >( quest.second, 11 ); + questData.previousQuestKeys[ 2 ] = exdData.getField< uint32_t >( quest.second, 12 ); + + questData.questLockJoin = exdData.getField< uint8_t >( quest.second, 13 ); + questData.questLockKeys[ 0 ] = exdData.getField< uint32_t >( quest.second, 14 ); + questData.questLockKeys[ 1 ] = exdData.getField< uint32_t >( quest.second, 15 ); + + questData.classJobRequirements[ 0 ].classJobLevel = exdData.getField< uint16_t >( quest.second, 4 ); + auto classJobsCategory = exdData.get< Data::ClassJobCategory >( exdData.getField< uint8_t >( quest.second, 3 ) ); + for( int32_t i = 0; i <= Common::CLASSJOB_TOTAL; i++ ) + questData.classJobRequirements[ 0 ].classJobCategoryMask.set( i, ( &classJobsCategory->aDV )[ i ] ); + + questData.classJobRequirements[ 1 ].classJobLevel = exdData.getField< uint16_t >( quest.second, 7 ); + classJobsCategory = exdData.get< Data::ClassJobCategory >( exdData.getField< uint8_t >( quest.second, 6 ) ); + for( int32_t i = 0; i <= Common::CLASSJOB_TOTAL; i++ ) + questData.classJobRequirements[ 1 ].classJobCategoryMask.set( i, ( &classJobsCategory->aDV )[ i ] ); + + questData.column18 = exdData.getField< uint8_t >( quest.second, 18 ); + + questData.classJobUnlock = exdData.getField< uint8_t >( quest.second, 19 ); + + questData.requiredGC = exdData.getField< uint8_t >( quest.second, 20 ); + questData.requiredGCRank = exdData.getField< uint8_t >( quest.second, 21 ); + + questData.startTown = exdData.getField< uint8_t >( quest.second, 17 ); + + questData.header = exdData.getField< uint16_t >( quest.second, 16 ); + + questData.instanceContentJoin = exdData.getField< uint8_t >( quest.second, 22 ); + questData.instanceContent[ 0 ] = exdData.getField< uint32_t >( quest.second, 23 ); + questData.instanceContent[ 1 ] = exdData.getField< uint32_t >( quest.second, 24 ); + questData.instanceContent[ 2 ] = exdData.getField< uint32_t >( quest.second, 25 ); + + questData.festival = exdData.getField< uint8_t >( quest.second, 26 ); + questData.festivalBegin = exdData.getField< uint8_t >( quest.second, 27 ); + questData.festivalEnd = exdData.getField< uint8_t >( quest.second, 28 ); + questData.bellStart = exdData.getField< uint16_t >( quest.second, 29 ); + questData.bellEnd = exdData.getField< uint16_t >( quest.second, 30 ); + + questData.repeatIntervalType = exdData.getField< uint8_t >( quest.second, 44 ); + questData.questRepeatFlag = exdData.getField< uint8_t >( quest.second, 45 ); + + questData.beastTribe = exdData.getField< uint8_t >( quest.second, 31 ); + questData.beastReputationRank = exdData.getField< uint8_t >( quest.second, 32 ); + questData.beastReputationValue = exdData.getField< uint16_t >( quest.second, 33 ); + + questData.mount = exdData.getField< int32_t >( quest.second, 36 ); + + questData.satisfactionNpc = exdData.getField< uint8_t >( quest.second, 34 ); + questData.satisfactionRank = exdData.getField< uint8_t >( quest.second, 35 ); + + questData.issuer = exdData.getField< uint32_t >( quest.second, 39 ); + + questData.deliveryQuest = exdData.getField< uint8_t >( quest.second, 38 ); + + questData.expansion = exdData.getField< uint8_t >( quest.second, 2 ); + + questData.type = exdData.getField< uint8_t >(quest.second, 47); + + questData.isRepeatable = exdData.getField< bool >( quest.second, 43 ); + questData.isHouseRequired = exdData.getField< bool >( quest.second, 37 ); + + questData.iconValid = exdData.get< Data::EventIconType >( exdData.getField< uint8_t >( quest.second, 1512 ) )->mapIconAvailable + 1 + questData.isRepeatable; + questData.iconInvalid = exdData.get< Data::EventIconType >( exdData.getField< uint8_t >( quest.second, 1512 ) )->mapIconInvalid + 1 + questData.isRepeatable; + + uint32_t issuerLevelId = exdData.getField< uint32_t >( quest.second, 40 ); + if( issuerLevelId != 0 ) + { + if( count++ % 100 == 0 ) + std::cout << "."; + + auto level = exdData.get< Data::Level >( issuerLevelId ); + auto territory = level->territory; + + if( territory == 0 ) + territory = exdData.get< Data::Map >( level->map )->territoryType; + + EventData eventData; + eventData.iconId = questData.iconValid; + eventData.levelId = issuerLevelId; + eventData.actorId = questData.issuer; + + m_mapData[ territory ].emplace( quest.first, eventData ); + } + } + + auto& serverMgr = Common::Service< World::ServerMgr >::ref(); + auto m_gameData = new xiv::dat::GameData( serverMgr.getConfig().global.general.dataPath ); + + std::set< std::string > bgSet; + + for( auto territoryTypeId : exdData.getTerritoryTypeIdList() ) + { + auto territoryType = exdData.get< Data::TerritoryType >( territoryTypeId ); + if( territoryType->bg.empty() ) + continue; + + if( bgSet.find( territoryType->bg ) != bgSet.end() ) + continue; + else + bgSet.insert( territoryType->bg ); + + std::string planeventLgbPath( "bg/" + territoryType->bg.substr( 0, territoryType->bg.rfind( '/' ) ) + "/planevent.lgb" ); + auto planeventFile = m_gameData->getFile( planeventLgbPath ); + auto planeventData = planeventFile->access_data_sections().at( 0 ); + LGB_FILE planeventLgb( &planeventData[ 0 ], "planevent" ); + + for( const auto& group : planeventLgb.groups ) + { + for( const auto& pEntry : group.entries ) + { + if( pEntry->getType() == LgbEntryType::EventNpc ) + { + auto pNpc = reinterpret_cast< LGB_ENPC_ENTRY* >( pEntry.get() ); + + auto eNpcData = exdData.get< Data::ENpcBase >( pNpc->data.enpcId )->eNpcData; + + for( auto npcData : eNpcData ) + { + if( npcData == 0 ) + continue; // Some npcs have data gaps, so we have to iterate through the entire array + + if( count++ % 1000 == 0 ) + std::cout << "."; + + EventData eventData; + eventData.levelId = pNpc->data.instanceId; + eventData.actorId = pNpc->data.enpcId; + + auto eventHandlerType = static_cast< EventHandler::EventHandlerType >( npcData >> 16 ); + + switch( eventHandlerType ) + { + case EventHandler::EventHandlerType::GuildLeveAssignment: + { + eventData.iconId = exdData.get< Data::EventIconType >( 5 )->mapIconAvailable + 1; + + m_mapData[ territoryTypeId ].insert( std::make_pair( npcData, 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.get< Data::CustomTalk >( npcData ); + + eventData.iconId = customTalk->iconMap; + + m_mapData[ territoryTypeId ].insert( std::make_pair( npcData, eventData ) ); + } + break; + } + + case EventHandler::EventHandlerType::GuildOrderGuide: + { + eventData.iconId = exdData.get< Data::EventIconType >( 6 )->mapIconAvailable + 1; + + m_mapData[ territoryTypeId ].insert( std::make_pair( npcData, eventData ) ); + break; + } + + case EventHandler::EventHandlerType::TripleTriad: + { + eventData.iconId = exdData.get< Data::EventIconType >( 7 )->mapIconAvailable + 1; + + m_mapData[ territoryTypeId ].insert( std::make_pair( npcData, eventData ) ); + break; + } + + case EventHandler::EventHandlerType::PreHandler: + { + //I think this is used in Bozja and Zadnor, need evidence + //m_mapData[ territoryTypeId ].insert( std::make_pair( eventData, 0 ) ); + break; + } + } + } + } + } + } + } + std::cout << "\n"; +} + +void Sapphire::World::Manager::MapMgr::updateAll( Entity::Player& player ) +{ + auto& mapData = m_mapData[ player.getTerritoryTypeId() ]; + std::multimap< uint32_t, EventData, less > newMapData; + + for( auto eventData : mapData ) + { + switch( static_cast< EventHandler::EventHandlerType >( eventData.first >> 16 ) ) + { + case EventHandler::EventHandlerType::Quest: + { + if( isQuestAvailable( player, eventData ) ) + newMapData.insert( eventData ); + + break; + } + + case EventHandler::EventHandlerType::GuildLeveAssignment: + { + auto& exdData = Common::Service< Data::ExdDataGenerated >::ref(); + auto guildLeve = exdData.get< Data::GuildleveAssignment >( eventData.first ); + + if( player.isActionLearned( 5 ) ) + { + if( player.isQuestCompleted( guildLeve->quest[ 0 ] ) ) + { + if( eventData.first >= 393239 && eventData.first <= 393247 ) + { + if( player.getGc() != 0 ) + newMapData.insert( eventData ); + } + else + { + newMapData.insert( eventData ); + } + } + else if( eventData.first == 393217 || eventData.first == 393223 || eventData.first == 393225 ) // Leve npc locations: Bentbranch / Horizon / Swiftperch + { + if( player.isQuestCompleted( 220 ) || player.isQuestCompleted( 687 ) || player.isQuestCompleted( 693 ) ) + newMapData.insert( eventData ); + } + } + + break; + } + + case EventHandler::EventHandlerType::CustomTalk: + { + newMapData.insert( eventData ); + + break; + } + + case EventHandler::EventHandlerType::GuildOrderGuide: + { + if( player.isActionLearned( 7 ) ) + newMapData.insert( eventData ); + + break; + } + + case EventHandler::EventHandlerType::TripleTriad: + { + if( eventData.first == 2293771 ) // Triple Triad Master npc for now only + newMapData.insert( eventData ); + + break; + } + } + } + + sendPackets( player, newMapData, All ); +} + +void Sapphire::World::Manager::MapMgr::updateQuests( Entity::Player& player ) +{ + auto& mapData = m_mapData[ player.getTerritoryTypeId() ]; + std::multimap< uint32_t, EventData, less > newMapData; + + for( auto& eventData : mapData ) + { + if( ( eventData.first >> 16 ) == static_cast< uint16_t >( EventHandler::EventHandlerType::Quest ) ) + { + if( isQuestAvailable( player, eventData ) ) + newMapData.insert( eventData ); + } + } + + sendPackets( player, newMapData, Quest );; +} + +bool Sapphire::World::Manager::MapMgr::isQuestAvailable( Entity::Player& player, std::pair< const uint32_t, EventData >& eventData ) +{ + auto& exdData = Common::Service< Data::ExdDataGenerated >::ref(); + + auto& quest = m_questData[ eventData.first ]; + + if( ( player.isQuestCompleted( eventData.first ) && ( !quest.isRepeatable && eventData.first != 67114 ) ) || player.hasQuest( eventData.first ) || + ( quest.repeatIntervalType == 1 && quest.questRepeatFlag == 0 ) ) // Don't show daily beast tribe quests on the map yet. + return false; + + if( quest.classJobUnlock ) + { + if( quest.column18 == 3 ) + if( static_cast< uint8_t >( player.getClass() ) != quest.classJobUnlock ) + return false; + else if( quest.column18 == 4 ) + if ( static_cast< uint8_t >( player.getClass() ) == quest.classJobUnlock ) + return false; + else + return false; + } + + // Was this really ever used? + if( quest.startTown ) + { + if( quest.startTown != player.getStartTown() ) + return false; + } + + if( Common::CURRENT_EXPANSION_ID < quest.expansion ) + return false; + + if( quest.mount ) + { + if( !player.hasMount( quest.mount ) ) + return false; + } + + if( eventData.first == 65968 ) // Quest: A Legend for a Legend + { + uint16_t requiredMounts[] = { 28, 29, 30, 31, 40, 43 }; + + for( int32_t i = 0; i < 6; i++ ) + { + if( !player.hasMount( requiredMounts[ i ] ) ) + return false; + } + } + else if( eventData.first == 67086 ) // Quest: Fiery Wings, Fiery Hearts + { + uint16_t requiredMounts[] = { 75, 76, 77, 78, 90, 98, 104 }; + + for( int32_t i = 0; i < 6; i++ ) + { + if( !player.hasMount( requiredMounts[ i ] ) ) + return false; + } + } + else if( eventData.first == 68736 ) // Quest: A Lone Wolf No More + { + uint16_t requiredMounts[] = { 115, 116, 133, 144, 158, 172, 182 }; + + for( int32_t i = 0; i < 6; i++ ) + { + if( !player.hasMount( requiredMounts[ i ] ) ) + return false; + } + } + + if( quest.requiredGC || quest.requiredGCRank ) + { + if( quest.requiredGC != player.getGc() ) + return false; + + if( quest.requiredGCRank > player.getGcRankArray()[ player.getGc() - 1 ] ) + eventData.second.iconId = quest.iconInvalid; + } + + if( quest.header != 0 ) + { + if ( !player.isActionLearned( quest.header ) ) + return false; + } + + //Required previous quests for ARR relic + if( eventData.first == 66971 ) // Quest: Up in Arms + { + for( int32_t i = 0; i <= Common::CLASSJOB_TOTAL; i++ ) + { + auto classJob = exdData.get< Data::ClassJob >( i ); + + if( player.isQuestCompleted( classJob->relicQuest ) ) + break; + + if( i == Common::CLASSJOB_TOTAL ) + return false; + } + } + else if( eventData.first == 65897 ) // Quest: His Dark Materia + { + // Quests in the following order: A Ponze of Flesh, Labor of Love, Method in His Malice, A Treasured Mother + if( !player.isQuestCompleted( 357 ) || !player.isQuestCompleted( 358 ) || !player.isQuestCompleted( 359 ) || !player.isQuestCompleted( 360 ) ) + return false; + } + + if( quest.previousQuestJoin == 1 ) + { + for( int32_t i = 0; i < 3; i++ ) + { + if( quest.previousQuestKeys[ i ] == 0 ) + continue; + + if( !player.isQuestCompleted( quest.previousQuestKeys[ i ] ) ) + { + if( i == 0 && quest.previousQuest0Sequence != 0 ) + { + if( player.getQuestSeq( quest.previousQuestKeys[ i ] ) < quest.previousQuest0Sequence ) + return false; + } + else + { + return false; + } + } + } + } + else if( quest.previousQuestJoin == 2 ) + { + for( int32_t i = 0; i < 3; i++ ) + { + if( quest.previousQuestKeys[ i ] == 0 ) + continue; + + if( player.isQuestCompleted( quest.previousQuestKeys[ i ] ) ) + break; + + if( i == 2 ) + return false; + } + } + + if( quest.questLockJoin == 1 ) + { + for( int32_t i = 0; i < 2; i++ ) + { + if( quest.questLockKeys[ i ] == 0 ) + continue; + + if( !player.isQuestCompleted( quest.questLockKeys[ i ] ) && !player.hasQuest( quest.questLockKeys[ i ] ) ) + break; + + if( i == 1 ) + return false; + } + } + else if( quest.questLockJoin == 2 ) + { + for( int32_t i = 0; i < 2; i++ ) + { + if( quest.questLockKeys[ i ] == 0 ) + continue; + + if( player.isQuestCompleted( quest.questLockKeys[ i ] ) || player.hasQuest( quest.questLockKeys[ i ] ) ) + return false; + } + } + + if( quest.instanceContentJoin == 1 ) + { + for( int32_t i = 0; i < 3; i++ ) + { + if( quest.instanceContent[ i ] == 0 ) + continue; + + eventData.second.iconId = quest.iconInvalid; + } + } + else if( quest.instanceContentJoin == 2 ) + { + for( int32_t i = 0; i < 3; i++ ) + { + if( quest.instanceContent[ i ] == 0 ) + continue; + + eventData.second.iconId = quest.iconInvalid; + } + } + + 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.festivalBegin != 0 ) + return false; + } + + if( quest.bellStart || quest.bellEnd ) + { + uint64_t curEorzeaTime = Util::getEorzeanTimeStamp(); + uint32_t convTime = 100 * (curEorzeaTime / 3600 % 24) + curEorzeaTime / 60 % 60; + + if( quest.bellStart <= quest.bellEnd ) + { + if( convTime < quest.bellStart || convTime >= quest.bellEnd ) + eventData.second.iconId = quest.iconInvalid; + } + else + { + if( convTime < quest.bellStart && convTime >= quest.bellEnd ) + eventData.second.iconId = quest.iconInvalid; + } + } + + if( !quest.classJobRequirements[0].classJobCategoryMask.test( static_cast< uint8_t >( player.getClass() ) ) ) + eventData.second.iconId = quest.iconInvalid; + + if( player.getQuestSeq( eventData.first ) || ( quest.type & 1 ) == 0 ) + { + for( int32_t i = 1; i <= Common::CLASSJOB_TOTAL; i++ ) + { + if( quest.classJobRequirements[0].classJobCategoryMask.test( i ) ) + { + if( player.getLevelForClass( static_cast< Common::ClassJob >( i ) ) >= quest.classJobRequirements[0].classJobLevel ) + break; + } + + if( i == Common::CLASSJOB_TOTAL ) + return false; + } + } + else + { + if( player.getLevel() < quest.classJobRequirements[0].classJobLevel ) + return false; + } + + if( quest.classJobRequirements[1].classJobCategoryMask.any() ) + { + for( int32_t i = 1; i <= Common::CLASSJOB_TOTAL; i++ ) + { + if( quest.classJobRequirements[1].classJobCategoryMask.test( i ) ) + { + if( player.getLevelForClass( static_cast< Common::ClassJob >( i ) ) >= quest.classJobRequirements[1].classJobLevel ) + break; + } + + if( i == Common::CLASSJOB_TOTAL ) + return false; + } + } + + for( int32_t i = 0; i <= Common::CLASSJOB_TOTAL; i++ ) + { + auto classJob = exdData.get< Data::ClassJob >( i ); + + if( classJob->relicQuest == eventData.first ) + { + for( int32_t j = 0; i <= Common::CLASSJOB_TOTAL; i++ ) + { + classJob = exdData.get< Data::ClassJob >( i ); + + if( player.hasQuest( classJob->relicQuest ) ) + return false; + } + + break; + } + } + + if( quest.beastTribe ) + return false; + + if( quest.satisfactionNpc ) + return false; + + auto isRelicEquipped = [ &player, &eventData ]( uint32_t* mainWeaponId, uint32_t secondaryWeaponId ) + { + for( int32_t i = 0; i < 10; i++ ) + { + if( i == 0 ) + { + if( player.getEquippedSecondaryWeapon() == nullptr ) + { + if( player.getEquippedWeapon()->getId() == mainWeaponId[ i ] && secondaryWeaponId == 0 ) + return true; + } + else if( player.getEquippedWeapon()->getId() == mainWeaponId[ i ] && player.getEquippedSecondaryWeapon()->getId() == secondaryWeaponId ) + { + return true; + } + } + else + { + if( player.getEquippedWeapon()->getId() == mainWeaponId[ i ] ) + return true; + } + } + + return false; + }; + + switch( eventData.first ) + { + case 65742: // Quest: Mmmmmm, Soulglazed Relics + { + uint32_t relicItemIds[] = { 7863, 7864, 7865, 7866, 7867, 9253, 7868, 7869, 7870, 7871 }; + + if( !isRelicEquipped( relicItemIds, 7872 ) ) + eventData.second.iconId = quest.iconInvalid; + + break; + } + + case 65892: // Quest: Wherefore Art Thou, Zodiac + case 65897: // Quest: His Dark Materia + { + uint32_t relicItemIds[] = { 8649, 8650, 8651, 8652, 8653, 9254, 8654, 8655, 8656, 8657 }; + + if( !isRelicEquipped( relicItemIds, 8658 ) ) + eventData.second.iconId = quest.iconInvalid; + + break; + } + + case 66096: // Quest: Rise and Shine + { + uint32_t relicItemIds[] = { 9491, 9492, 9493, 9494, 9495, 9501, 9496, 9497, 9498, 9499 }; + + if( !isRelicEquipped( relicItemIds, 9500 ) ) + eventData.second.iconId = quest.iconInvalid; + + break; + } + + case 66097: // Quest: The Vital Title + { + uint32_t relicItemIds[] = { 10054, 10055, 10056, 10057, 10058, 10064, 10059, 10060, 10061, 10062 }; + + if( !isRelicEquipped( relicItemIds, 10063 ) ) + eventData.second.iconId = quest.iconInvalid; + + break; + } + + case 66971: // Quest: Up in Arms + { + uint32_t relicItemIds[] = { 6257, 6258, 6259, 6260, 6261, 9250, 6262, 6263, 6264, 6265 }; + + if( !isRelicEquipped( relicItemIds, 0 ) ) + eventData.second.iconId = quest.iconInvalid; + + break; + } + + case 66972: // Quest: Trials of the Braves + { + uint32_t relicItemIds[] = { 7824, 7825, 7826, 7827, 7828, 9251, 7829, 7830, 7831, 7832 }; + + if( !isRelicEquipped( relicItemIds, 7833 ) ) + eventData.second.iconId = quest.iconInvalid; + + break; + } + + case 66998: // Quest: Celestial Radiance + case 67000: // Quest: Star Light, Star Bright + { + uint32_t relicItemIds[] = { 7834, 7835, 7836, 7837, 7838, 9252, 7839, 7840, 7841, 7842 }; + + if( !isRelicEquipped( relicItemIds, 7843 ) ) + eventData.second.iconId = quest.iconInvalid; + + break; + } + + case 67823: // Quest: The Vital Title + { + uint32_t relicItemIds[] = { 12124, 12133, 12142, 12151, 12160, 12169, 12178, 12187, 12196 }; + + if( !isRelicEquipped( relicItemIds, 12213 ) ) + eventData.second.iconId = quest.iconInvalid; + + break; + } + } + + if( eventData.first >= 67001 && eventData.first <= 67003 ) // Quest: Call of the Wild + { + // Quests in the following order: Martial Perfection / Feathers and Folly / Like Clutchfather, Like Son / Revenge of the Furred / Spread Your Wings and Soar + if( !player.isQuestCompleted( 1221 ) || !player.isQuestCompleted( 1256 ) || !player.isQuestCompleted( 1378 ) || !player.isQuestCompleted( 1324 ) || !player.isQuestCompleted( 1493 ) ) + return false; + } + else if( eventData.first == 67918 ) // Quest: When Good Dragons Go Bad + { + // Quests in the following order: The Nest of Honor / A Symbiotic Friendship / The Zenith of Craftsmanship / Heavensward + if( !player.isQuestCompleted( 2225 ) || !player.isQuestCompleted( 2260 ) || !player.isQuestCompleted( 2327 ) || !player.isQuestCompleted( 1669 ) ) + return false; + } + + if( eventData.first >= 66968 && eventData.first <= 66970 ) // Quest: An Ill-conceived Venture + return false; + + if( quest.isHouseRequired ) + return false; + + if( quest.deliveryQuest ) + return false; + + if( eventData.first == 67114 ) // Quest: The Ties That Bind + return false; + + if( eventData.first == 66112 ) // Quest: Like Sire Like Fledgling + { + if( !( player.getHowToArray()[ 198 / 8 ] & ( 1 << ( 198 % 8 ) ) ) ) + return false; + } + + if( player.getQuestSeq( eventData.first ) == 0 ) + { + auto questAccept = exdData.get< Data::QuestAcceptAdditionCondition >( eventData.first ); + + 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; + } + } + } + } + + // Quests in the following order: Open and Inviting / The Adventurer with All the Cards + if( eventData.first == 69566 || eventData.first == 69617 ) + return false; + + return true; +} + +bool Sapphire::World::Manager::MapMgr::isTripleTriadAvailable( Entity::Player& player, std::pair< const uint32_t, EventData >& eventData ) +{ + auto& exdData = Common::Service< Data::ExdDataGenerated >::ref(); + auto tripleTriad = exdData.get< Data::TripleTriad >( eventData.first ); + + 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 Sapphire::World::Manager::MapMgr::fillPacket( std::multimap< uint32_t, EventData, less >& mapData, uint32_t* iconIds, uint32_t* levelIds, uint32_t* eventIds ) +{ + int32_t i = 0; + for( auto& eventData : mapData ) + { + iconIds[ i ] = eventData.second.iconId; + levelIds[ i ] = eventData.second.levelId; + eventIds[ i ] = eventData.first; + + i++; + } +} + +void Sapphire::World::Manager::MapMgr::sendPackets( Entity::Player& player, std::multimap< uint32_t, EventData, less >& mapData, UpdateMode updateMode ) +{ + player.queuePacket( makeActorControlSelf( player.getId(), Network::ActorControl::BeginMapUpdate, updateMode ) ); + + if( mapData.size() <= 2 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapUpdate >( player.getId() ); + mapUpdatePacket->data().entryCount = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().levelIds, mapUpdatePacket->data().eventIds ); + + player.queuePacket( mapUpdatePacket ); + } + else if( mapData.size() <= 4 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapUpdate4 >( player.getId() ); + mapUpdatePacket->data().entryCount = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().levelIds, mapUpdatePacket->data().eventIds ); + + player.queuePacket( mapUpdatePacket ); + } + else if( mapData.size() <= 8 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapUpdate8 >( player.getId() ); + mapUpdatePacket->data().entryCount = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().levelIds, mapUpdatePacket->data().eventIds ); + + player.queuePacket( mapUpdatePacket ); + } + else if( mapData.size() <= 16 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapUpdate16 >( player.getId() ); + mapUpdatePacket->data().entryCount = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().levelIds, mapUpdatePacket->data().eventIds ); + + player.queuePacket( mapUpdatePacket ); + } + else if( mapData.size() <= 32 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapUpdate32 >( player.getId() ); + mapUpdatePacket->data().entryCount = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().levelIds, mapUpdatePacket->data().eventIds ); + + player.queuePacket( mapUpdatePacket ); + } + else if( mapData.size() <= 64 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapUpdate64 >( player.getId() ); + mapUpdatePacket->data().entryCount = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().levelIds, mapUpdatePacket->data().eventIds ); + + player.queuePacket( mapUpdatePacket ); + } + else if( mapData.size() <= 128 ) + { + auto mapUpdatePacket = makeZonePacket< FFXIVIpcMapUpdate128 >( player.getId() ); + mapUpdatePacket->data().entryCount = mapData.size(); + + fillPacket( mapData, mapUpdatePacket->data().iconIds, mapUpdatePacket->data().levelIds, mapUpdatePacket->data().eventIds ); + + player.queuePacket( mapUpdatePacket ); + } + + player.queuePacket( makeActorControlSelf( player.getId(), Network::ActorControl::FinishMapUpdate ) ); +} diff --git a/src/world/Manager/MapMgr.h b/src/world/Manager/MapMgr.h new file mode 100644 index 00000000..a3315486 --- /dev/null +++ b/src/world/Manager/MapMgr.h @@ -0,0 +1,132 @@ +#ifndef SAPPHIRE_MAPMGR_H +#define SAPPHIRE_MAPMGR_H + +#include "ForwardsZone.h" + +#include +#include + +namespace Sapphire::World::Manager +{ + + class MapMgr + { + public: + enum UpdateMode : uint8_t + { + Quest = 1, + GuildLeveAssignment = 2, + GuildOrderGuide = 4, + TripleTriad = 8, + CustomTalk = 16, + PreHandler = 32, + + All = 0x3F + }; + + MapMgr(); + + void updateAll( Entity::Player& player ); + void updateQuests( Entity::Player& player ); + + private: + struct EventData + { + uint32_t iconId; + uint32_t levelId; + uint32_t actorId; + }; + + struct QuestData + { + uint8_t previousQuestJoin; // 1 = requires all previous quest, 2 = requires any previous quest + uint32_t previousQuestKeys[3]; + uint8_t previousQuest0Sequence; + + uint8_t questLockJoin; // 1 = only locks when all previous quests are done, 2 = locks when any previous quest is done + uint32_t questLockKeys[2]; + + struct + { + std::bitset< Common::CLASSJOB_TOTAL + 1 > classJobCategoryMask; + uint16_t classJobLevel; + } classJobRequirements[2]; + + uint8_t column18; + uint8_t classJobUnlock; + + uint8_t requiredGC; + uint8_t requiredGCRank; + + uint8_t startTown; + + uint16_t header; + + uint8_t instanceContentJoin; // 1 = requires all needed instances to be completed, 2 = requires any needed instance to be completed + uint32_t instanceContent[3]; + + uint8_t festival; + uint8_t festivalBegin; + uint8_t festivalEnd; + uint16_t bellStart; + uint16_t bellEnd; + + uint8_t repeatIntervalType; + uint8_t questRepeatFlag; + + uint8_t beastTribe; + uint8_t beastReputationRank; + uint16_t beastReputationValue; + + int32_t mount; + + uint8_t satisfactionNpc; + uint8_t satisfactionRank; + + uint32_t issuer; + + uint8_t deliveryQuest; + + uint8_t expansion; + + uint8_t type; + + bool isRepeatable; + bool isHouseRequired; + + uint32_t iconValid; + uint32_t iconInvalid; + }; + + struct less + { + constexpr bool operator()( const uint32_t& _Left, const uint32_t& _Right ) const + { + const uint16_t left = _Left; + const uint16_t right = _Right; + + if( left == right ) + { + const uint16_t typeLeft = _Left >> 16; + const uint16_t typeRight = _Right >> 16; + + return typeLeft < typeRight; + } + + return left < right; + } + }; + + std::map< uint16_t, std::multimap< uint32_t, EventData, less > > m_mapData; + std::map< uint32_t, QuestData > m_questData; + + bool isQuestAvailable( Entity::Player& player, std::pair< const uint32_t, EventData >& eventData ); + bool isTripleTriadAvailable( Entity::Player& player, std::pair< const uint32_t, EventData >& eventData ); + + void fillPacket( std::multimap< uint32_t, EventData, less >& mapData, uint32_t* iconIds, uint32_t* levelIds, uint32_t* eventIds ); + void sendPackets( Entity::Player& player, std::multimap< uint32_t, EventData, less >& mapData, UpdateMode updateMode ); + }; + +} + +#endif // SAPPHIRE_MAPMGR_H \ No newline at end of file diff --git a/src/world/ServerMgr.cpp b/src/world/ServerMgr.cpp index 4d539b59..4033f7c2 100644 --- a/src/world/ServerMgr.cpp +++ b/src/world/ServerMgr.cpp @@ -41,6 +41,7 @@ #include "Manager/RNGMgr.h" #include "Manager/NaviMgr.h" #include "Manager/ActionMgr.h" +#include "Manager/MapMgr.h" #include "Territory/InstanceObjectCache.h" @@ -173,6 +174,10 @@ void Sapphire::World::ServerMgr::run( int32_t argc, char* argv[] ) auto pInstanceObjCache = std::make_shared< Sapphire::InstanceObjectCache >(); Common::Service< Sapphire::InstanceObjectCache >::set( pInstanceObjCache ); + Logger::info( "MapMgr: Caching map data" ); + auto pMapMgr = std::make_shared< Manager::MapMgr >(); + Common::Service< Manager::MapMgr >::set( pMapMgr ); + auto pActionMgr = std::make_shared< Manager::ActionMgr >(); Common::Service< Manager::ActionMgr >::set( pActionMgr );