diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index 56a43848..bed274b0 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -1,6 +1,7 @@ #include "dConfig.h" #include +#include #include "BinaryPathFinder.h" #include "GeneralUtils.h" diff --git a/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h b/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h new file mode 100644 index 00000000..b99687d0 --- /dev/null +++ b/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h @@ -0,0 +1,21 @@ +#ifndef __EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__ +#define __EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__ + +#include + +enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t { + SUCCESS, + FAIL_GENERIC, + FAIL_INV_FULL, + FAIL_ITEM_NOT_FOUND, + FAIL_CANT_MOVE_TO_THAT_INV_TYPE, + FAIL_NOT_NEAR_BANK, + FAIL_CANT_SWAP_ITEMS, + FAIL_SOURCE_TYPE, + FAIL_WRONG_DEST_TYPE, + FAIL_SWAP_DEST_TYPE, + FAIL_CANT_MOVE_THINKING_HAT, + FAIL_DISMOUNT_BEFORE_MOVING +}; + +#endif //!__EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__ diff --git a/dGame/dBehaviors/BehaviorContext.cpp b/dGame/dBehaviors/BehaviorContext.cpp index a3721d8f..abba74be 100644 --- a/dGame/dBehaviors/BehaviorContext.cpp +++ b/dGame/dBehaviors/BehaviorContext.cpp @@ -105,7 +105,7 @@ void BehaviorContext::ExecuteUpdates() { this->scheduledUpdates.clear(); } -void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bitStream) { +bool BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bitStream) { BehaviorSyncEntry entry; auto found = false; @@ -128,7 +128,7 @@ void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bit if (!found) { LOG("Failed to find behavior sync entry with sync id (%i)!", syncId); - return; + return false; } auto* behavior = entry.behavior; @@ -137,10 +137,11 @@ void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bit if (behavior == nullptr) { LOG("Invalid behavior for sync id (%i)!", syncId); - return; + return false; } behavior->Sync(this, bitStream, branch); + return true; } diff --git a/dGame/dBehaviors/BehaviorContext.h b/dGame/dBehaviors/BehaviorContext.h index 3e6c0b1d..4922f736 100644 --- a/dGame/dBehaviors/BehaviorContext.h +++ b/dGame/dBehaviors/BehaviorContext.h @@ -93,7 +93,7 @@ struct BehaviorContext void ExecuteUpdates(); - void SyncBehavior(uint32_t syncId, RakNet::BitStream& bitStream); + bool SyncBehavior(uint32_t syncId, RakNet::BitStream& bitStream); void Update(float deltaTime); diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index d926ff2c..58b13dc5 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -38,7 +38,7 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s context->skillID = skillID; - this->m_managedBehaviors.insert_or_assign(skillUid, context); + this->m_managedBehaviors.insert({ skillUid, context }); auto* behavior = Behavior::CreateBehavior(behaviorId); @@ -52,17 +52,24 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s } void SkillComponent::SyncPlayerSkill(const uint32_t skillUid, const uint32_t syncId, RakNet::BitStream& bitStream) { - const auto index = this->m_managedBehaviors.find(skillUid); + const auto index = this->m_managedBehaviors.equal_range(skillUid); - if (index == this->m_managedBehaviors.end()) { + if (index.first == this->m_managedBehaviors.end()) { LOG("Failed to find skill with uid (%i)!", skillUid, syncId); return; } - auto* context = index->second; + bool foundSyncId = false; + for (auto it = index.first; it != index.second && !foundSyncId; ++it) { + const auto& context = it->second; - context->SyncBehavior(syncId, bitStream); + foundSyncId = context->SyncBehavior(syncId, bitStream); + } + + if (!foundSyncId) { + LOG("Failed to find sync id (%i) for skill with uid (%i)!", syncId, skillUid); + } } @@ -138,7 +145,7 @@ void SkillComponent::Update(const float deltaTime) { for (const auto& pair : this->m_managedBehaviors) pair.second->UpdatePlayerSyncs(deltaTime); } - std::map keep{}; + std::multimap keep{}; for (const auto& pair : this->m_managedBehaviors) { auto* context = pair.second; @@ -176,7 +183,7 @@ void SkillComponent::Update(const float deltaTime) { } } - keep.insert_or_assign(pair.first, context); + keep.insert({ pair.first, context }); } this->m_managedBehaviors = keep; @@ -285,7 +292,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior( return { false, 0 }; } - this->m_managedBehaviors.insert_or_assign(context->skillUId, context); + this->m_managedBehaviors.insert({ context->skillUId, context }); if (!clientInitalized) { // Echo start skill diff --git a/dGame/dComponents/SkillComponent.h b/dGame/dComponents/SkillComponent.h index 24d92148..f74b4411 100644 --- a/dGame/dComponents/SkillComponent.h +++ b/dGame/dComponents/SkillComponent.h @@ -188,7 +188,7 @@ private: /** * All of the active skills mapped by their unique ID. */ - std::map m_managedBehaviors; + std::multimap m_managedBehaviors; /** * All active projectiles. diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index a97407d8..4bd667b4 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -99,6 +99,7 @@ #include "ActivityManager.h" #include "PlayerManager.h" #include "eVendorTransactionResult.h" +#include "eReponseMoveItemBetweenInventoryTypeCode.h" #include "CDComponentsRegistryTable.h" #include "CDObjectsTable.h" @@ -4565,16 +4566,31 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& if (inStream.ReadBit()) inStream.Read(itemLOT); if (invTypeDst == invTypeSrc) { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC); return; } auto* inventoryComponent = entity->GetComponent(); - if (inventoryComponent != nullptr) { + if (inventoryComponent) { if (itemID != LWOOBJID_EMPTY) { auto* item = inventoryComponent->FindItemById(itemID); - if (!item) return; + if (!item) { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_ITEM_NOT_FOUND); + return; + } + + if (item->GetLot() == 6086) { // Thinking hat + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_CANT_MOVE_THINKING_HAT); + return; + } + + auto* destInv = inventoryComponent->GetInventory(invTypeDst); + if (destInv && destInv->GetEmptySlots() == 0) { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_INV_FULL); + return; + } // Despawn the pet if we are moving that pet to the vault. auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID()); @@ -4583,10 +4599,32 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& } inventoryComponent->MoveItemToInventory(item, invTypeDst, iStackCount, showFlyingLoot, false, false, destSlot); + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::SUCCESS); } + } else { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC); } } +void GameMessages::SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, const SystemAddress& sysAddr, eInventoryType inventoryTypeDestination, eInventoryType inventoryTypeSource, eReponseMoveItemBetweenInventoryTypeCode response) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(eGameMessageType::RESPONSE_MOVE_ITEM_BETWEEN_INVENTORY_TYPES); + + bitStream.Write(inventoryTypeDestination != eInventoryType::ITEMS); + if (inventoryTypeDestination != eInventoryType::ITEMS) bitStream.Write(inventoryTypeDestination); + + bitStream.Write(inventoryTypeSource != eInventoryType::ITEMS); + if (inventoryTypeSource != eInventoryType::ITEMS) bitStream.Write(inventoryTypeSource); + + bitStream.Write(response != eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC); + if (response != eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC) bitStream.Write(response); + + SEND_PACKET; +} + void GameMessages::SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr) { CBITSTREAM; @@ -6212,3 +6250,18 @@ void GameMessages::SendSlashCommandFeedbackText(Entity* entity, std::u16string t auto sysAddr = entity->GetSystemAddress(); SEND_PACKET; } + +void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(eGameMessageType::FORCE_CAMERA_TARGET_CYCLE); + bitStream.Write(bForceCycling); + bitStream.Write(cyclingMode != eCameraTargetCyclingMode::ALLOW_CYCLE_TEAMMATES); + if (cyclingMode != eCameraTargetCyclingMode::ALLOW_CYCLE_TEAMMATES) bitStream.Write(cyclingMode); + bitStream.Write(optionalTargetID); + + auto sysAddr = entity->GetSystemAddress(); + SEND_PACKET; +} diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index b842710e..e3c16471 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -39,6 +39,12 @@ enum class eQuickBuildFailReason : uint32_t; enum class eQuickBuildState : uint32_t; enum class BehaviorSlot : int32_t; enum class eVendorTransactionResult : uint32_t; +enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t; + +enum class eCameraTargetCyclingMode : int32_t { + ALLOW_CYCLE_TEAMMATES, + DISALLOW_CYCLING +}; namespace GameMessages { class PropertyDataMessage; @@ -589,6 +595,7 @@ namespace GameMessages { //NT: void HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); + void SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, const SystemAddress& sysAddr, eInventoryType inventoryTypeDestination, eInventoryType inventoryTypeSource, eReponseMoveItemBetweenInventoryTypeCode response); void SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr); @@ -666,6 +673,7 @@ namespace GameMessages { void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity); void SendSlashCommandFeedbackText(Entity* entity, std::u16string text); + void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID); }; #endif // GAMEMESSAGES_H diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 428ccbcb..ca4e03d4 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -31,7 +31,6 @@ void SlashCommandHandler::RegisterCommand(Command command) { } for (const auto& alias : command.aliases) { - LOG_DEBUG("Registering command %s", alias.c_str()); auto [_, success] = RegisteredCommands.try_emplace(alias, command); // Don't allow duplicate commands if (!success) { @@ -929,6 +928,15 @@ void SlashCommandHandler::Startup() { }; RegisterCommand(FindPlayerCommand); + Command SpectateCommand{ + .help = "Spectate a player", + .info = "Specify a player name to spectate. They must be in the same world as you. Leave blank to stop spectating", + .aliases = { "spectate", "follow" }, + .handle = GMGreaterThanZeroCommands::Spectate, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(SpectateCommand); + // Register GM Zero Commands Command HelpCommand{ diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp index ea33aa03..b9eaf7bf 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -322,4 +322,19 @@ namespace GMGreaterThanZeroCommands { request.Serialize(bitStream); Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); } + + void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) { + GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, entity->GetObjectID()); + return; + } + + auto player = PlayerManager::GetPlayer(args); + if (!player) { + GameMessages::SendSlashCommandFeedbackText(entity, u"Player not found"); + return; + } + GameMessages::SendSlashCommandFeedbackText(entity, u"Spectating Player"); + GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, player->GetObjectID()); + } } diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h index 7cb3d8d7..c278fc0a 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h @@ -15,6 +15,7 @@ namespace GMGreaterThanZeroCommands { void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args); void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args); } #endif //!GMGREATERTHANZEROCOMMANDS_H diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index cb893da3..6043fe63 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -285,7 +285,6 @@ void ParseXml(const std::string& file) { } if (zoneID.value() != currentZoneID) { - LOG_DEBUG("Skipping (%s) %i location because it is in %i and not the current zone (%i)", name, lot, zoneID.value(), currentZoneID); continue; } diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 09d1bba9..0f36ee37 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -40,6 +40,7 @@ #include "BitStreamUtils.h" #include "Start.h" #include "Server.h" +#include "CDZoneTableTable.h" namespace Game { Logger* logger = nullptr; @@ -277,6 +278,17 @@ int main(int argc, char** argv) { PersistentIDManager::Initialize(); Game::im = new InstanceManager(Game::logger, Game::server->GetIP()); + //Get CDClient initial information + try { + CDClientManager::LoadValuesFromDatabase(); + } catch (CppSQLite3Exception& e) { + LOG("Failed to initialize CDServer SQLite Database"); + LOG("May be caused by corrupted file: %s", (Game::assetManager->GetResPath() / "CDServer.sqlite").string().c_str()); + LOG("Error: %s", e.errorMessage()); + LOG("Error Code: %i", e.errorCode()); + return EXIT_FAILURE; + } + //Depending on the config, start up servers: if (Game::config->GetValue("prestart_servers") != "0") { StartChatServer(); diff --git a/dNet/BitStreamUtils.h b/dNet/BitStreamUtils.h index 1322ec95..33fde564 100644 --- a/dNet/BitStreamUtils.h +++ b/dNet/BitStreamUtils.h @@ -5,6 +5,7 @@ #include "MessageIdentifiers.h" #include "BitStream.h" #include +#include enum class eConnectionType : uint16_t; diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 41135a80..f6056476 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -23,6 +23,11 @@ if(NOT WIN32) target_include_directories(bcrypt PRIVATE "libbcrypt/include/bcrypt") endif() +# Need to define this on Clang and GNU for 'strdup' support +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_definitions(bcrypt PRIVATE "_POSIX_C_SOURCE=200809L") +endif() + target_include_directories(bcrypt INTERFACE "libbcrypt/include") target_include_directories(bcrypt PRIVATE "libbcrypt/src")