From 6f0ba8e896dec6e3c7d91e7d851e396c68bc3446 Mon Sep 17 00:00:00 2001 From: Anton Popovichenko Date: Mon, 27 Jul 2026 18:51:04 +0200 Subject: [PATCH] feat(Core): Add clustering support (#16832) Co-authored-by: 3kynox Co-authored-by: nox Co-authored-by: Ludwig --- conf/dist/config.cmake | 1 + deps/CMakeLists.txt | 2 + deps/PackageList.txt | 4 + deps/libsidecar/CMakeLists.txt | 66 ++++ deps/libsidecar/include/battleground-api.h | 77 ++++ deps/libsidecar/include/events-group.h | 66 ++++ deps/libsidecar/include/events-guild.h | 31 ++ .../include/events-servers-registry.h | 23 ++ deps/libsidecar/include/libsidecar.h | 116 ++++++ deps/libsidecar/include/monitoring.h | 36 ++ .../include/player-interactions-api.h | 42 ++ deps/libsidecar/include/player-items-api.h | 76 ++++ deps/libsidecar/include/player-money-api.h | 43 ++ deps/libsidecar/stub/battleground-api.c | 55 +++ deps/libsidecar/stub/battleground-api.h | 69 ++++ deps/libsidecar/stub/events-group.c | 117 ++++++ deps/libsidecar/stub/events-group.h | 58 +++ deps/libsidecar/stub/events-guild.c | 43 ++ deps/libsidecar/stub/events-guild.h | 23 ++ .../libsidecar/stub/events-servers-registry.c | 15 + .../libsidecar/stub/events-servers-registry.h | 15 + deps/libsidecar/stub/libsidecar.c | 138 +++++++ deps/libsidecar/stub/libsidecar.h | 84 ++++ deps/libsidecar/stub/monitoring.c | 16 + deps/libsidecar/stub/monitoring.h | 28 ++ .../libsidecar/stub/player-interactions-api.c | 31 ++ .../libsidecar/stub/player-interactions-api.h | 34 ++ deps/libsidecar/stub/player-items-api.c | 44 +++ deps/libsidecar/stub/player-items-api.h | 68 ++++ deps/libsidecar/stub/player-money-api.c | 31 ++ deps/libsidecar/stub/player-money-api.h | 35 ++ src/cmake/showoptions.cmake | 6 + src/server/apps/CMakeLists.txt | 3 +- src/server/apps/worldserver/Main.cpp | 16 +- .../apps/worldserver/worldserver.conf.dist | 55 +++ .../Implementation/CharacterDatabase.cpp | 7 + .../Implementation/CharacterDatabase.h | 2 + .../game/Battlegrounds/Battleground.cpp | 21 + src/server/game/Battlegrounds/Battleground.h | 2 +- src/server/game/CMakeLists.txt | 3 +- src/server/game/Entities/Item/Item.cpp | 52 +-- src/server/game/Entities/Object/Object.cpp | 6 +- src/server/game/Entities/Object/Object.h | 1 + .../game/Entities/Object/ObjectGuid.cpp | 16 + src/server/game/Entities/Object/ObjectGuid.h | 13 +- src/server/game/Entities/Player/Player.cpp | 42 +- .../game/Entities/Player/PlayerStorage.cpp | 72 ++-- .../game/Entities/Transport/Transport.cpp | 87 +++- .../game/Entities/Transport/Transport.h | 5 + src/server/game/Groups/Group.cpp | 106 ++++- src/server/game/Groups/Group.h | 5 + src/server/game/Groups/GroupMgr.cpp | 6 +- src/server/game/Handlers/ChannelHandler.cpp | 19 + src/server/game/Handlers/CharacterHandler.cpp | 109 ++--- src/server/game/Handlers/ChatHandler.cpp | 15 +- src/server/game/Handlers/TradeHandler.cpp | 98 ++++- src/server/game/Instances/InstanceSaveMgr.cpp | 155 ++++++++ src/server/game/Instances/InstanceSaveMgr.h | 31 ++ src/server/game/Instances/InstanceScript.cpp | 4 + src/server/game/Maps/Map.cpp | 49 ++- src/server/game/Maps/Map.h | 10 + src/server/game/Maps/MapMgr.cpp | 4 + src/server/game/Maps/TransportMgr.cpp | 3 + src/server/game/Server/Protocol/Opcodes.cpp | 2 + src/server/game/Server/Protocol/Opcodes.h | 4 +- src/server/game/Server/WorldSession.cpp | 124 ++++-- src/server/game/Server/WorldSession.h | 4 +- src/server/game/Server/WorldSocket.cpp | 169 ++++---- src/server/game/TC9Sidecar/AsyncTask.h | 93 +++++ src/server/game/TC9Sidecar/TC9GroupHooks.cpp | 115 ++++++ src/server/game/TC9Sidecar/TC9GroupHooks.h | 40 ++ src/server/game/TC9Sidecar/TC9GrpcHandler.cpp | 374 ++++++++++++++++++ src/server/game/TC9Sidecar/TC9GrpcHandler.h | 50 +++ src/server/game/TC9Sidecar/TC9GuildHooks.cpp | 47 +++ src/server/game/TC9Sidecar/TC9GuildHooks.h | 34 ++ src/server/game/TC9Sidecar/TC9Sidecar.cpp | 268 +++++++++++++ src/server/game/TC9Sidecar/TC9Sidecar.h | 78 ++++ src/server/game/World/World.cpp | 23 +- src/server/game/World/WorldState.cpp | 34 +- src/server/scripts/CMakeLists.txt | 3 +- 80 files changed, 3675 insertions(+), 297 deletions(-) create mode 100644 deps/libsidecar/CMakeLists.txt create mode 100644 deps/libsidecar/include/battleground-api.h create mode 100644 deps/libsidecar/include/events-group.h create mode 100644 deps/libsidecar/include/events-guild.h create mode 100644 deps/libsidecar/include/events-servers-registry.h create mode 100644 deps/libsidecar/include/libsidecar.h create mode 100644 deps/libsidecar/include/monitoring.h create mode 100644 deps/libsidecar/include/player-interactions-api.h create mode 100644 deps/libsidecar/include/player-items-api.h create mode 100644 deps/libsidecar/include/player-money-api.h create mode 100644 deps/libsidecar/stub/battleground-api.c create mode 100644 deps/libsidecar/stub/battleground-api.h create mode 100644 deps/libsidecar/stub/events-group.c create mode 100644 deps/libsidecar/stub/events-group.h create mode 100644 deps/libsidecar/stub/events-guild.c create mode 100644 deps/libsidecar/stub/events-guild.h create mode 100644 deps/libsidecar/stub/events-servers-registry.c create mode 100644 deps/libsidecar/stub/events-servers-registry.h create mode 100644 deps/libsidecar/stub/libsidecar.c create mode 100644 deps/libsidecar/stub/libsidecar.h create mode 100644 deps/libsidecar/stub/monitoring.c create mode 100644 deps/libsidecar/stub/monitoring.h create mode 100644 deps/libsidecar/stub/player-interactions-api.c create mode 100644 deps/libsidecar/stub/player-interactions-api.h create mode 100644 deps/libsidecar/stub/player-items-api.c create mode 100644 deps/libsidecar/stub/player-items-api.h create mode 100644 deps/libsidecar/stub/player-money-api.c create mode 100644 deps/libsidecar/stub/player-money-api.h create mode 100644 src/server/game/TC9Sidecar/AsyncTask.h create mode 100644 src/server/game/TC9Sidecar/TC9GroupHooks.cpp create mode 100644 src/server/game/TC9Sidecar/TC9GroupHooks.h create mode 100644 src/server/game/TC9Sidecar/TC9GrpcHandler.cpp create mode 100644 src/server/game/TC9Sidecar/TC9GrpcHandler.h create mode 100644 src/server/game/TC9Sidecar/TC9GuildHooks.cpp create mode 100644 src/server/game/TC9Sidecar/TC9GuildHooks.h create mode 100644 src/server/game/TC9Sidecar/TC9Sidecar.cpp create mode 100644 src/server/game/TC9Sidecar/TC9Sidecar.h diff --git a/conf/dist/config.cmake b/conf/dist/config.cmake index ce2e079f3..e2f24fc6c 100644 --- a/conf/dist/config.cmake +++ b/conf/dist/config.cmake @@ -107,6 +107,7 @@ option(WITH_STRICT_DATABASE_TYPE_CHECKS "Enable strict checking of database fiel option(WITHOUT_METRICS "Disable metrics reporting (i.e. InfluxDB and Grafana)" 0) option(WITH_DETAILED_METRICS "Enable detailed metrics reporting (i.e. time each session takes to update)" 0) option(TOOL_CONFIG_MERGER "Install the Python config merger tool alongside config files" 0) +option(USE_REAL_LIBSIDECAR "Use real libsidecar and expect that compiled shared lib is in deps/libsidecar folder." 0) CheckApplicationsBuildList() CheckToolsBuildList() diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 869351a37..34c489e5c 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -47,3 +47,5 @@ if (BUILD_TOOLS_MAPS) add_subdirectory(libmpq) add_subdirectory(fkYAML) endif() + +add_subdirectory(libsidecar) diff --git a/deps/PackageList.txt b/deps/PackageList.txt index a0a57b6c3..ce7024616 100644 --- a/deps/PackageList.txt +++ b/deps/PackageList.txt @@ -85,3 +85,7 @@ recastnavigation (Recast is state of the art navigation mesh construction toolse fkYAML (A C++ header-only YAML library) https://github.com/fktn-k/fkYAML Version: 721edb3e1a817e527fd9e1e18a3bea300822522e + +libsidecar (enables interaction with other components when the worldserver is running in cluster mode. It requires building a shared library first and putting it in the corresponding folder.) + https://github.com/walkline/ToCloud9/tree/master/game-server/libsidecar + Version: master diff --git a/deps/libsidecar/CMakeLists.txt b/deps/libsidecar/CMakeLists.txt new file mode 100644 index 000000000..170e0cc3b --- /dev/null +++ b/deps/libsidecar/CMakeLists.txt @@ -0,0 +1,66 @@ +# +# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# + +# Use stub implementation only when the real libsidecar is disabled. +if (NOT USE_REAL_LIBSIDECAR) + + file(GLOB sources stub/*.c stub/*.h) + + add_library(libsidecar STATIC ${sources}) + + set_target_properties(libsidecar + PROPERTIES + LINKER_LANGUAGE + CXX + INTERFACE_INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR}/stub) + +else() + add_library(libsidecar SHARED IMPORTED GLOBAL) + + # Determine the correct library extension based on platform + if(WIN32) + set(LIBSIDECAR_EXTENSION "dll") + set(LIBSIDECAR_IMPORT_LIB "libsidecar.lib") + elseif(APPLE) + set(LIBSIDECAR_EXTENSION "dylib") + else() + set(LIBSIDECAR_EXTENSION "so") + endif() + + if(WIN32) + set_target_properties(libsidecar + PROPERTIES + IMPORTED_LOCATION + ${CMAKE_CURRENT_SOURCE_DIR}/libsidecar.${LIBSIDECAR_EXTENSION} + IMPORTED_IMPLIB + ${CMAKE_CURRENT_SOURCE_DIR}/${LIBSIDECAR_IMPORT_LIB} + INTERFACE_INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR}/include) + else() + set_target_properties(libsidecar + PROPERTIES + IMPORTED_LOCATION + ${CMAKE_CURRENT_SOURCE_DIR}/libsidecar.${LIBSIDECAR_EXTENSION} + INTERFACE_INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR}/include + IMPORTED_NO_SONAME + true) + + set_target_properties(libsidecar PROPERTIES + IMPORTED_LOCATION_NOCONFIG + "${CMAKE_INSTALL_RPATH}/libsidecar.${LIBSIDECAR_EXTENSION}" + IMPORTED_SONAME_NOCONFIG + "libsidecar.${LIBSIDECAR_EXTENSION}") + endif() + +endif() diff --git a/deps/libsidecar/include/battleground-api.h b/deps/libsidecar/include/battleground-api.h new file mode 100644 index 000000000..0a9f607bb --- /dev/null +++ b/deps/libsidecar/include/battleground-api.h @@ -0,0 +1,77 @@ +#ifndef __BATTLEGROUND_API__ +#define __BATTLEGROUND_API__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum BattlegroundErrorCode { + BattlegroundErrorCodeNoError = 0, + BattlegroundErrorCodeNoHandler = 1, + BattlegroundErrorFailedToCreateBG = 2, + BattlegroundErrorBattlegroundNotFound = 3, +} BattlegroundErrorCode; + +typedef struct { + uint8_t battlegroundTypeID; + uint32_t arenaType; + bool isRated; + uint32_t mapID; + uint8_t bracketLvl; + uint64_t *hordePlayersToAdd; + int hordePlayersToAddSize; + uint64_t *alliancePlayersToAdd; + int alliancePlayersToAddSize; + uint64_t *randomBGPlayers; + int randomBGPlayersSize; +} BattlegroundStartRequest; + +typedef struct { + int errorCode; + uint64_t instanceID; + uint64_t instanceClientID; +} BattlegroundStartResponse; + +typedef BattlegroundStartResponse (*BattlegroundStartHandler) (BattlegroundStartRequest* request); +void SetBattlegroundStartHandler(BattlegroundStartHandler h); +BattlegroundStartResponse CallBattlegroundStartHandler(BattlegroundStartRequest* request); + +typedef struct { + uint8_t battlegroundTypeID; + uint64_t instanceID; + uint64_t *hordePlayersToAdd; + int hordePlayersToAddSize; + uint64_t *alliancePlayersToAdd; + int alliancePlayersToAddSize; + uint64_t *randomBGPlayers; + int randomBGPlayersSize; +} BattlegroundAddPlayersRequest; + +typedef BattlegroundErrorCode (*BattlegroundAddPlayersHandler) (BattlegroundAddPlayersRequest* request); +void SetBattlegroundAddPlayersHandler(BattlegroundAddPlayersHandler h); +BattlegroundErrorCode CallBattlegroundAddPlayersHandler(BattlegroundAddPlayersRequest* request); + +typedef enum BattlegroundJoinCheckErrorCode { + BattlegroundJoinCheckErrorCodeOK = 0, + BattlegroundJoinCheckErrorCodeNoHook = 1, + BattlegroundJoinCheckErrorCodeResponseIsFalse = 2, + BattlegroundJoinCheckErrorCodePlayerNotFound = 3, +} BattlegroundJoinCheckErrorCode; + +typedef BattlegroundJoinCheckErrorCode (*CanPlayerJoinBattlegroundQueueHandler)(uint64_t playerGuid); +void SetCanPlayerJoinBattlegroundQueueHandler(CanPlayerJoinBattlegroundQueueHandler h); +BattlegroundJoinCheckErrorCode CallCanPlayerJoinBattlegroundQueueHandler(uint64_t playerGuid); + +typedef BattlegroundJoinCheckErrorCode (*CanPlayerTeleportToBattlegroundHandler)(uint64_t playerGuid); +void SetCanPlayerTeleportToBattlegroundHandler(CanPlayerTeleportToBattlegroundHandler h); +BattlegroundJoinCheckErrorCode CallCanPlayerTeleportToBattlegroundHandler(uint64_t playerGuid); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/libsidecar/include/events-group.h b/deps/libsidecar/include/events-group.h new file mode 100644 index 000000000..cf821aa41 --- /dev/null +++ b/deps/libsidecar/include/events-group.h @@ -0,0 +1,66 @@ +#ifndef __EVENT_GROUP__ +#define __EVENT_GROUP__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum GroupStatus { + GroupHookStatusOK = 0, + GroupHookStatusNoHook = 1 +}; + +typedef struct { + uint32_t guid; + uint64_t leader; + uint8_t lootMethod; + uint64_t looterGuid; + uint8_t lootThreshold; + uint8_t groupType; + uint8_t difficulty; + uint8_t raidDifficulty; + uint64_t masterLooterGuid; + uint64_t *members; + uint8_t membersSize; +} EventObjectGroup; + +typedef void (*OnGroupCreatedHook) (EventObjectGroup *group); +void SetOnGroupCreatedHook(OnGroupCreatedHook h); +int CallOnGroupCreatedHook(EventObjectGroup *group); + +typedef void (*OnGroupMemberAddedHook) (uint32_t guid, uint64_t newMemberGuid); +void SetOnGroupMemberAddedHook(OnGroupMemberAddedHook h); +int CallOnGroupMemberAddedHook(uint32_t guid, uint64_t newMemberGuid); + +typedef void (*OnGroupMemberRemovedHook) (uint32_t guid, uint64_t removedMemberGuid, uint64_t newLeaderGuid); +void SetOnGroupMemberRemovedHook(OnGroupMemberRemovedHook h); +int CallOnGroupMemberRemovedHook(uint32_t guid, uint64_t removedMemberGuid, uint64_t newLeaderGuid); + +typedef void (*OnGroupDisbandedHook) (uint32_t guid); +void SetOnGroupDisbandedHook(OnGroupDisbandedHook h); +int CallOnGroupDisbandedHook(uint32_t guid); + +typedef void (*OnGroupLootTypeChangedHook) (uint32_t guid, uint8_t lootMethod, uint64_t looter, uint8_t lootThreshold); +void SetOnGroupLootTypeChangedHook(OnGroupLootTypeChangedHook h); +int CallOnGroupLootTypeChangedHook(uint32_t guid, uint8_t lootMethod, uint64_t looter, uint8_t lootThreshold); + +typedef void (*OnGroupDungeonDifficultyChangedHook) (uint32_t guid, uint8_t difficulty); +void SetOnGroupDungeonDifficultyChangedHook(OnGroupDungeonDifficultyChangedHook h); +int CallOnGroupDungeonDifficultyChangedHook(uint32_t guid, uint8_t difficulty); + +typedef void (*OnGroupRaidDifficultyChangedHook) (uint32_t guid, uint8_t difficulty); +void SetOnGroupRaidDifficultyChangedHook(OnGroupRaidDifficultyChangedHook h); +int CallOnGroupRaidDifficultyChangedHook(uint32_t guid, uint8_t difficulty); + +typedef void (*OnGroupConvertedToRaidHook) (uint32_t guid); +void SetOnGroupConvertedToRaidHook(OnGroupConvertedToRaidHook h); +int CallOnGroupConvertedToRaidHook(uint32_t guid); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/libsidecar/include/events-guild.h b/deps/libsidecar/include/events-guild.h new file mode 100644 index 000000000..e6066f00d --- /dev/null +++ b/deps/libsidecar/include/events-guild.h @@ -0,0 +1,31 @@ +#ifndef __EVENT_GUILD__ +#define __EVENT_GUILD__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum GuildHookStatus { + GuildHookStatusOK = 0, + GuildHookStatusNoHook = 1 +}; + +typedef void (*OnGuildMemberAddedHook) (uint64_t /*guild_id*/, uint64_t /*player_guid*/); +void SetOnGuildMemberAddedHook(OnGuildMemberAddedHook h); +int CallOnGuildMemberAddedHook(uint64_t guild_id, uint64_t player_guid); + +typedef void (*OnGuildMemberLeftHook) (uint64_t /*guild_id*/, uint64_t /*player_guid*/); +void SetOnGuildMemberLeftHook(OnGuildMemberLeftHook h); +int CallOnGuildMemberLeftHook(uint64_t guild_id, uint64_t player_guid); + +typedef void (*OnGuildMemberRemovedHook) (uint64_t /*guild_id*/, uint64_t /*player_guid*/); +void SetOnGuildMemberRemovedHook(OnGuildMemberRemovedHook h); +int CallOnGuildMemberRemovedHook(uint64_t guild_id, uint64_t player_guid); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/libsidecar/include/events-servers-registry.h b/deps/libsidecar/include/events-servers-registry.h new file mode 100644 index 000000000..578bc336a --- /dev/null +++ b/deps/libsidecar/include/events-servers-registry.h @@ -0,0 +1,23 @@ +#ifndef __EVENT_SERVERS_REGISTRY__ +#define __EVENT_SERVERS_REGISTRY__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum ServersRegistryStatus { + ServersRegistryHookStatusOK = 0, + ServersRegistryHookStatusNoHook = 1 +}; + +typedef void (*OnMapsReassignedHook) (uint32_t* /*maps_added*/, int /*maps_added_size*/, uint32_t* /*maps_removed*/, int /*maps_removed_size*/); +void SetOnMapsReassignedHook(OnMapsReassignedHook h); +int CallOnMapsReassignedHook(uint32_t* maps_added, int maps_added_size, uint32_t* maps_removed, int maps_removed_size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/libsidecar/include/libsidecar.h b/deps/libsidecar/include/libsidecar.h new file mode 100644 index 000000000..ef477c76d --- /dev/null +++ b/deps/libsidecar/include/libsidecar.h @@ -0,0 +1,116 @@ +#ifndef __LIBSIDECAR_H__ +#define __LIBSIDECAR_H__ + +#include +#include + +/* Export/import decoration for Windows DLL */ +#ifdef _WIN32 + #ifdef TC9_BUILDING_DLL + #define TC9_API __declspec(dllexport) + #else + #define TC9_API __declspec(dllimport) + #endif +#else + #ifdef TC9_BUILDING_DLL + #define TC9_API __attribute__((visibility("default"))) + #else + #define TC9_API + #endif +#endif + +/* Include all API headers */ +#include "battleground-api.h" +#include "events-group.h" +#include "events-guild.h" +#include "events-servers-registry.h" +#include "monitoring.h" +#include "player-interactions-api.h" +#include "player-items-api.h" +#include "player-money-api.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Main library functions */ +TC9_API void TC9InitLib(uint16_t port, uint32_t realmID, uint8_t isCrossRealm, char* availableMaps, uint32_t** assignedMaps, int* assignedMapsSize); +TC9_API void TC9GracefulShutdown(); +TC9_API void TC9ProcessGRPCOrHTTPRequests(); +TC9_API void TC9ProcessEventsHooks(); + +/* GUID generation */ +TC9_API uint64_t TC9GetNextAvailableCharacterGuid(int realmID); +TC9_API uint64_t TC9GetNextAvailableItemGuid(int realmID); +TC9_API uint64_t TC9GetNextAvailableInstanceGuid(int realmID); + +/* Map loading notification */ +TC9_API void TC9ReadyToAcceptPlayersFromMaps(uint32_t* maps, int mapsLen); + +/* Generic NATS pub/sub. Payloads are opaque bytes, subjects are arbitrary. + * Subscription callbacks run on the thread draining TC9ProcessEventsHooks + * (the world update thread), not on the NATS delivery thread. Both return + * 0 on success, -1 on failure. + * + * Example — a mod broadcasting and consuming its own events: + * + * // Publish (any thread): + * const char msg[] = "{\"zone\":1519,\"boss\":466}"; + * TC9NatsPublish("mymod.boss.spawned", msg, sizeof(msg) - 1); + * + * // Subscribe once at startup; the handler runs on the world update + * // thread, so it is safe to touch game state from it: + * void OnBossSpawned(const char* subject, const char* payload, int payloadLen) + * { + * std::string data(payload, payloadLen); // payload is not NUL-terminated + * // ... react to the event ... + * } + * TC9NatsSubscribe("mymod.boss.spawned", &OnBossSpawned); + */ +typedef void (*TC9NatsMessageHandler)(const char* subject, const char* payload, int payloadLen); +TC9_API int TC9NatsPublish(const char* subject, const char* payload, int payloadLen); +TC9_API int TC9NatsSubscribe(const char* subject, TC9NatsMessageHandler handler); + +/* Matchmaking notifications */ +TC9_API void TC9PlayerLeftBattleground(uint64_t playerGUID, uint32_t realmID, uint32_t instanceID); +TC9_API void TC9BattlegroundStatusChanged(uint32_t instanceID, uint8_t status); + +/* Event hooks registration */ +TC9_API void TC9SetOnGroupCreatedHook(OnGroupCreatedHook h); +TC9_API void TC9SetOnGroupMemberAddedHook(OnGroupMemberAddedHook h); +TC9_API void TC9SetOnGroupMemberRemovedHook(OnGroupMemberRemovedHook h); +TC9_API void TC9SetOnGroupDisbandedHook(OnGroupDisbandedHook h); +TC9_API void TC9SetOnGroupLootTypeChangedHook(OnGroupLootTypeChangedHook h); +TC9_API void TC9SetOnGroupDungeonDifficultyChangedHook(OnGroupDungeonDifficultyChangedHook h); +TC9_API void TC9SetOnGroupRaidDifficultyChangedHook(OnGroupRaidDifficultyChangedHook h); +TC9_API void TC9SetOnGroupConvertedToRaidHook(OnGroupConvertedToRaidHook h); + +TC9_API void TC9SetOnGuildMemberAddedHook(OnGuildMemberAddedHook h); +TC9_API void TC9SetOnGuildMemberRemovedHook(OnGuildMemberRemovedHook h); +TC9_API void TC9SetOnGuildMemberLeftHook(OnGuildMemberLeftHook h); + +TC9_API void TC9SetOnMapsReassignedHook(OnMapsReassignedHook h); + +/* Handler registration for gRPC requests */ +TC9_API void TC9SetBattlegroundStartHandler(BattlegroundStartHandler h); +TC9_API void TC9SetBattlegroundAddPlayersHandler(BattlegroundAddPlayersHandler h); +TC9_API void TC9SetCanPlayerJoinBattlegroundQueueHandler(CanPlayerJoinBattlegroundQueueHandler h); +TC9_API void TC9SetCanPlayerTeleportToBattlegroundHandler(CanPlayerTeleportToBattlegroundHandler h); + +TC9_API void TC9SetMonitoringDataCollectorHandler(MonitoringDataCollectorHandler h); + +TC9_API void TC9SetCanPlayerInteractWithNPCAndFlagsHandler(CanPlayerInteractWithNPCAndFlagsHandler h); +TC9_API void TC9SetCanPlayerInteractWithGOAndTypeHandler(CanPlayerInteractWithGOAndTypeHandler h); + +TC9_API void TC9SetGetPlayerItemsByGuidsHandler(GetPlayerItemsByGuidsHandler h); +TC9_API void TC9SetRemoveItemsWithGuidsFromPlayerHandler(RemoveItemsWithGuidsFromPlayerHandler h); +TC9_API void TC9SetAddExistingItemToPlayerHandler(AddExistingItemToPlayerHandler h); + +TC9_API void TC9SetGetMoneyForPlayerHandler(GetMoneyForPlayerHandler h); +TC9_API void TC9SetModifyMoneyForPlayerHandler(ModifyMoneyForPlayerHandler h); + +#ifdef __cplusplus +} +#endif + +#endif /* __LIBSIDECAR_H__ */ diff --git a/deps/libsidecar/include/monitoring.h b/deps/libsidecar/include/monitoring.h new file mode 100644 index 000000000..c87c47721 --- /dev/null +++ b/deps/libsidecar/include/monitoring.h @@ -0,0 +1,36 @@ +#ifndef __MONITORING__ +#define __MONITORING__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum MonitoringErrorCode { + MonitoringErrorCodeNoError = 0, + MonitoringErrorCodeNoHandler = 1, +} MonitoringErrorCode; + +// MonitoringDataCollectorResponse request. +typedef struct { + int errorCode; + uint32_t connectedPlayers; + uint32_t diffMean; + uint32_t diffMedian; + uint32_t diff95Percentile; + uint32_t diff99Percentile; + uint32_t diffMaxPercentile; +} MonitoringDataCollectorResponse; + +typedef MonitoringDataCollectorResponse (*MonitoringDataCollectorHandler)(); +void SetMonitoringDataCollectorHandler(MonitoringDataCollectorHandler h); +MonitoringDataCollectorResponse CallMonitoringDataCollectorHandler(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/libsidecar/include/player-interactions-api.h b/deps/libsidecar/include/player-interactions-api.h new file mode 100644 index 000000000..4289b708a --- /dev/null +++ b/deps/libsidecar/include/player-interactions-api.h @@ -0,0 +1,42 @@ +#ifndef __PLAYER_INTERACTIONS_API__ +#define __PLAYER_INTERACTIONS_API__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum PlayerInteractionErrorCode { + PlayerInteractionErrorCodeNoError = 0, + PlayerInteractionErrorCodeNoHandler = 1, + PlayerInteractionErrorCodeCodePlayerNotFound = 2, +} PlayerInteractionErrorCode; + +// CanPlayerInteractWithNPCAndFlags request. +typedef struct { + int errorCode; + bool canInteract; +} CanPlayerInteractWithNPCAndFlagsResponse; + +typedef CanPlayerInteractWithNPCAndFlagsResponse (*CanPlayerInteractWithNPCAndFlagsHandler) (uint64_t /*playerGUID*/, uint64_t /*npcGUID*/, uint32_t /*npcFlags*/); +void SetCanPlayerInteractWithNPCAndFlagsHandler(CanPlayerInteractWithNPCAndFlagsHandler h); +CanPlayerInteractWithNPCAndFlagsResponse CallCanPlayerInteractWithNPCAndFlagsHandler(uint64_t player_guid, uint64_t npc_guid, uint32_t npc_flags); + +// CanPlayerInteractWithGOAndType request. +typedef struct { + int errorCode; + bool canInteract; +} CanPlayerInteractWithGOAndTypeResponse; + +typedef CanPlayerInteractWithGOAndTypeResponse (*CanPlayerInteractWithGOAndTypeHandler) (uint64_t /*playerGUID*/, uint64_t /*goGUID*/, uint8_t /*goType*/); +void SetCanPlayerInteractWithGOAndTypeHandler(CanPlayerInteractWithGOAndTypeHandler h); +CanPlayerInteractWithGOAndTypeResponse CallCanPlayerInteractWithGOAndTypeHandler(uint64_t player_guid, uint64_t go_guid, uint8_t go_type); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/libsidecar/include/player-items-api.h b/deps/libsidecar/include/player-items-api.h new file mode 100644 index 000000000..22d158bd4 --- /dev/null +++ b/deps/libsidecar/include/player-items-api.h @@ -0,0 +1,76 @@ +#ifndef __PLAYER_ITEMS_API__ +#define __PLAYER_ITEMS_API__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum PlayerItemErrorCode { + PlayerItemErrorCodeNoError = 0, + PlayerItemErrorCodeNoHandler = 1, + PlayerItemErrorCodePlayerNotFound = 2, + PlayerItemErrorNoInventorySpace = 3, + PlayerItemErrorUnknownTemplate = 4, + PlayerItemErrorFailedToCreateItem = 5 +} PlayerItemErrorCode; + +// GetPlayerItemsByGuids request. +typedef struct { + uint64_t guid; + uint32_t entry; + uint64_t owner; + uint8_t bagSlot; + uint8_t slot; + bool isTradable; + uint32_t count; + uint16_t flags; + uint8_t durability; + int8_t randomPropertyID; + const char* text; +} PlayerItem; + +typedef struct { + int errorCode; + PlayerItem* items; + int itemsSize; +} GetPlayerItemsByGuidsResponse; + +typedef GetPlayerItemsByGuidsResponse (*GetPlayerItemsByGuidsHandler) (uint64_t /*player_guid*/, uint64_t* /*items_guids*/, int /*items_guids_size*/); +void SetGetPlayerItemsByGuidsHandler(GetPlayerItemsByGuidsHandler h); +GetPlayerItemsByGuidsResponse CallGetPlayerItemsByGuidsHandler(uint64_t player_guid, uint64_t* items_guids, int items_guids_size); + +// RemoveItemsWithGuidsFromPlayer request. +typedef struct { + int errorCode; + uint64_t* updatedItems; + int updatedItemsSize; +} RemoveItemsWithGuidsFromPlayerResponse; + +typedef RemoveItemsWithGuidsFromPlayerResponse (*RemoveItemsWithGuidsFromPlayerHandler) (uint64_t /*player_guid*/, uint64_t* /*items_guids*/, int /*items_guids_size*/, uint64_t /*assign_player_guid*/); +void SetRemoveItemsWithGuidsFromPlayerHandler(RemoveItemsWithGuidsFromPlayerHandler h); +RemoveItemsWithGuidsFromPlayerResponse CallRemoveItemsWithGuidsFromPlayerHandler(uint64_t player_guid, uint64_t* items_guids, int items_guids_size, uint64_t assign_player_guid); + +// AddExistingItemToPlayer request. +typedef struct { + uint64_t playerGuid; + uint64_t itemGuid; + uint32_t itemEntry; + uint32_t itemCount; + uint16_t itemFlags; + uint8_t itemDurability; + int8_t itemRandomPropertyID; +} AddExistingItemToPlayerRequest; + +typedef PlayerItemErrorCode (*AddExistingItemToPlayerHandler) (AddExistingItemToPlayerRequest*); +void SetAddExistingItemToPlayerHandler(AddExistingItemToPlayerHandler h); +PlayerItemErrorCode CallAddExistingItemToPlayerHandler(AddExistingItemToPlayerRequest*); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/libsidecar/include/player-money-api.h b/deps/libsidecar/include/player-money-api.h new file mode 100644 index 000000000..395b881fa --- /dev/null +++ b/deps/libsidecar/include/player-money-api.h @@ -0,0 +1,43 @@ +#ifndef __PLAYER_MONEY_API__ +#define __PLAYER_MONEY_API__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum PlayerMoneyErrorCode { + PlayerMoneyErrorCodeNoError = 0, + PlayerMoneyErrorCodeNoHandler = 1, + PlayerMoneyErrorCodePlayerNotFound = 2, + PlayerMoneyErrorCodeTooMuchMoney = 3, +} PlayerMoneyErrorCode; + +// GetMoneyForPlayer request. +typedef struct { + int errorCode; + uint32_t money; +} GetMoneyForPlayerResponse; + +typedef GetMoneyForPlayerResponse (*GetMoneyForPlayerHandler) (uint64_t /*player_guid*/); +void SetGetMoneyForPlayerHandler(GetMoneyForPlayerHandler h); +GetMoneyForPlayerResponse CallGetMoneyForPlayerHandler(uint64_t player_guid); + +// ModifyMoneyForPlayer request. +typedef struct { + int errorCode; + uint32_t newMoneyValue; +} ModifyMoneyForPlayerResponse; + +typedef ModifyMoneyForPlayerResponse (*ModifyMoneyForPlayerHandler) (uint64_t /*player_guid*/, int32_t /*amount*/); +void SetModifyMoneyForPlayerHandler(ModifyMoneyForPlayerHandler h); +ModifyMoneyForPlayerResponse CallModifyMoneyForPlayerHandler(uint64_t player_guid, int32_t amount); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/libsidecar/stub/battleground-api.c b/deps/libsidecar/stub/battleground-api.c new file mode 100644 index 000000000..cde84c658 --- /dev/null +++ b/deps/libsidecar/stub/battleground-api.c @@ -0,0 +1,55 @@ +#include "battleground-api.h" + +static BattlegroundStartHandler battlegroundStartHandler; +void SetBattlegroundStartHandler(BattlegroundStartHandler h) { + battlegroundStartHandler = h; +} + +BattlegroundStartResponse CallBattlegroundStartHandler(BattlegroundStartRequest* request) { + if (battlegroundStartHandler == 0) { + BattlegroundStartResponse resp; + resp.errorCode = BattlegroundErrorCodeNoHandler; + return resp; + } + + return battlegroundStartHandler(request); +} + +static BattlegroundAddPlayersHandler battlegroundAddPlayersHandler; +void SetBattlegroundAddPlayersHandler(BattlegroundAddPlayersHandler h) { + battlegroundAddPlayersHandler = h; +} + +BattlegroundErrorCode CallBattlegroundAddPlayersHandler(BattlegroundAddPlayersRequest* request) { + if (battlegroundAddPlayersHandler == 0) { + return BattlegroundErrorCodeNoHandler; + } + + return battlegroundAddPlayersHandler(request); +} + +static CanPlayerJoinBattlegroundQueueHandler canPlayerJoinBattlegroundQueueHandler = 0; + +void SetCanPlayerJoinBattlegroundQueueHandler(CanPlayerJoinBattlegroundQueueHandler h) { + canPlayerJoinBattlegroundQueueHandler = h; +} + +BattlegroundJoinCheckErrorCode CallCanPlayerJoinBattlegroundQueueHandler(uint64_t playerGuid) { + if (canPlayerJoinBattlegroundQueueHandler == 0) { + return BattlegroundJoinCheckErrorCodeNoHook; + } + return canPlayerJoinBattlegroundQueueHandler(playerGuid); +} + +static CanPlayerTeleportToBattlegroundHandler canPlayerTeleportToBattlegroundHandler = 0; + +void SetCanPlayerTeleportToBattlegroundHandler(CanPlayerTeleportToBattlegroundHandler h) { + canPlayerTeleportToBattlegroundHandler = h; +} + +BattlegroundJoinCheckErrorCode CallCanPlayerTeleportToBattlegroundHandler(uint64_t playerGuid) { + if (canPlayerTeleportToBattlegroundHandler == 0) { + return BattlegroundJoinCheckErrorCodeNoHook; + } + return canPlayerTeleportToBattlegroundHandler(playerGuid); +} diff --git a/deps/libsidecar/stub/battleground-api.h b/deps/libsidecar/stub/battleground-api.h new file mode 100644 index 000000000..6134db993 --- /dev/null +++ b/deps/libsidecar/stub/battleground-api.h @@ -0,0 +1,69 @@ +#ifndef __BATTLEGROUND_API__ +#define __BATTLEGROUND_API__ + +#include +#include +#include + +typedef enum BattlegroundErrorCode { + BattlegroundErrorCodeNoError = 0, + BattlegroundErrorCodeNoHandler = 1, + BattlegroundErrorFailedToCreateBG = 2, + BattlegroundErrorBattlegroundNotFound = 3, +} BattlegroundErrorCode; + +typedef struct { + uint8_t battlegroundTypeID; + uint32_t arenaType; + bool isRated; + uint32_t mapID; + uint8_t bracketLvl; + uint64_t *hordePlayersToAdd; + int hordePlayersToAddSize; + uint64_t *alliancePlayersToAdd; + int alliancePlayersToAddSize; + uint64_t *randomBGPlayers; + int randomBGPlayersSize; +} BattlegroundStartRequest; + +typedef struct { + int errorCode; + uint64_t instanceID; + uint64_t instanceClientID; +} BattlegroundStartResponse; + +typedef BattlegroundStartResponse (*BattlegroundStartHandler) (BattlegroundStartRequest* request); +void SetBattlegroundStartHandler(BattlegroundStartHandler h); +BattlegroundStartResponse CallBattlegroundStartHandler(BattlegroundStartRequest* request); + +typedef struct { + uint8_t battlegroundTypeID; + uint64_t instanceID; + uint64_t *hordePlayersToAdd; + int hordePlayersToAddSize; + uint64_t *alliancePlayersToAdd; + int alliancePlayersToAddSize; + uint64_t *randomBGPlayers; + int randomBGPlayersSize; +} BattlegroundAddPlayersRequest; + +typedef BattlegroundErrorCode (*BattlegroundAddPlayersHandler) (BattlegroundAddPlayersRequest* request); +void SetBattlegroundAddPlayersHandler(BattlegroundAddPlayersHandler h); +BattlegroundErrorCode CallBattlegroundAddPlayersHandler(BattlegroundAddPlayersRequest* request); + +typedef enum BattlegroundJoinCheckErrorCode { + BattlegroundJoinCheckErrorCodeOK = 0, + BattlegroundJoinCheckErrorCodeNoHook = 1, + BattlegroundJoinCheckErrorCodeResponseIsFalse = 2, + BattlegroundJoinCheckErrorCodePlayerNotFound = 3, +} BattlegroundJoinCheckErrorCode; + +typedef BattlegroundJoinCheckErrorCode (*CanPlayerJoinBattlegroundQueueHandler)(uint64_t playerGuid); +void SetCanPlayerJoinBattlegroundQueueHandler(CanPlayerJoinBattlegroundQueueHandler h); +BattlegroundJoinCheckErrorCode CallCanPlayerJoinBattlegroundQueueHandler(uint64_t playerGuid); + +typedef BattlegroundJoinCheckErrorCode (*CanPlayerTeleportToBattlegroundHandler)(uint64_t playerGuid); +void SetCanPlayerTeleportToBattlegroundHandler(CanPlayerTeleportToBattlegroundHandler h); +BattlegroundJoinCheckErrorCode CallCanPlayerTeleportToBattlegroundHandler(uint64_t playerGuid); + +#endif diff --git a/deps/libsidecar/stub/events-group.c b/deps/libsidecar/stub/events-group.c new file mode 100644 index 000000000..49030bba1 --- /dev/null +++ b/deps/libsidecar/stub/events-group.c @@ -0,0 +1,117 @@ +#include "events-group.h" + +// OnGroupCreatedHook +OnGroupCreatedHook groupCreatedHook; +void SetOnGroupCreatedHook(OnGroupCreatedHook h) { + groupCreatedHook = h; +} + +int CallOnGroupCreatedHook(EventObjectGroup *group) { + if (groupCreatedHook == 0) { + return GroupHookStatusNoHook; + } + groupCreatedHook(group); + return GroupHookStatusOK; +} + +// GroupMemberAdded +static OnGroupMemberAddedHook groupMemberAddedHook; +void SetOnGroupMemberAddedHook(OnGroupMemberAddedHook h) { + groupMemberAddedHook = h; +} + +int CallOnGroupMemberAddedHook(uint32_t guid, uint64_t newMemberGuid) { + if (groupMemberAddedHook == 0) { + return GroupHookStatusNoHook; + } + groupMemberAddedHook(guid, newMemberGuid); + return GroupHookStatusOK; +} + +// GroupMemberRemoved +static OnGroupMemberRemovedHook groupMemberRemovedHook; +void SetOnGroupMemberRemovedHook(OnGroupMemberRemovedHook h) { + groupMemberRemovedHook = h; +} + +int CallOnGroupMemberRemovedHook(uint32_t guid, uint64_t removedMemberGuid, uint64_t newLeaderGuid) { + if (groupMemberRemovedHook == 0) { + return GroupHookStatusNoHook; + } + groupMemberRemovedHook(guid, removedMemberGuid, newLeaderGuid); + return GroupHookStatusOK; +} + + +typedef void (*OnGroupDisbandedHook) (uint32_t guid); +void SetOnGroupDisbandedHook(OnGroupDisbandedHook h); +int CallOnGroupDisbandedHook(uint32_t guid); + +static OnGroupDisbandedHook groupDisbandedHook; +void SetOnGroupDisbandedHook(OnGroupDisbandedHook h) { + groupDisbandedHook = h; +} + +int CallOnGroupDisbandedHook(uint32_t guid) { + if (groupDisbandedHook == 0) { + return GroupHookStatusNoHook; + } + groupDisbandedHook(guid); + return GroupHookStatusOK; +} + +static OnGroupLootTypeChangedHook groupLootTypeChanged; +void SetOnGroupLootTypeChangedHook(OnGroupLootTypeChangedHook h) { + groupLootTypeChanged = h; +} + +int CallOnGroupLootTypeChangedHook(uint32_t guid, uint8_t lootMethod, uint64_t looter, uint8_t lootThreshold) { + if (groupLootTypeChanged == 0) { + return GroupHookStatusNoHook; + } + groupLootTypeChanged(guid, lootMethod, looter, lootThreshold); + return GroupHookStatusOK; +} + +static OnGroupDungeonDifficultyChangedHook groupDungeonDifficultyChanged; +void SetOnGroupDungeonDifficultyChangedHook(OnGroupDungeonDifficultyChangedHook h) { + groupDungeonDifficultyChanged = h; +} + +int CallOnGroupDungeonDifficultyChangedHook(uint32_t guid, uint8_t difficulty) { + if (groupDungeonDifficultyChanged == 0) { + return GroupHookStatusNoHook; + } + groupDungeonDifficultyChanged(guid, difficulty); + return GroupHookStatusOK; +} + +static OnGroupRaidDifficultyChangedHook groupRaidDifficultyChanged; +void SetOnGroupRaidDifficultyChangedHook(OnGroupRaidDifficultyChangedHook h) { + groupRaidDifficultyChanged = h; +} + +int CallOnGroupRaidDifficultyChangedHook(uint32_t guid, uint8_t difficulty) { + if (groupRaidDifficultyChanged == 0) { + return GroupHookStatusNoHook; + } + groupRaidDifficultyChanged(guid, difficulty); + return GroupHookStatusOK; +} + +typedef void (*OnGroupConvertedToRaidHook) (uint32_t guid); +void SetOnGroupConvertedToRaidHook(OnGroupConvertedToRaidHook h); +int CallOnGroupConvertedToRaidHook(uint32_t guid); + +static OnGroupConvertedToRaidHook groupConvertedToRaid; +void SetOnGroupConvertedToRaidHook(OnGroupConvertedToRaidHook h) { + groupConvertedToRaid = h; +} + +int CallOnGroupConvertedToRaidHook(uint32_t guid) { + if (groupConvertedToRaid == 0) { + return GroupHookStatusNoHook; + } + groupConvertedToRaid(guid); + return GroupHookStatusOK; +} diff --git a/deps/libsidecar/stub/events-group.h b/deps/libsidecar/stub/events-group.h new file mode 100644 index 000000000..05b13b3a0 --- /dev/null +++ b/deps/libsidecar/stub/events-group.h @@ -0,0 +1,58 @@ +#ifndef __EVENT_GROUP__ +#define __EVENT_GROUP__ + +#include +#include + +enum GroupStatus { + GroupHookStatusOK = 0, + GroupHookStatusNoHook = 1 +}; + +typedef struct { + uint32_t guid; + uint64_t leader; + uint8_t lootMethod; + uint64_t looterGuid; + uint8_t lootThreshold; + uint8_t groupType; + uint8_t difficulty; + uint8_t raidDifficulty; + uint64_t masterLooterGuid; + uint64_t *members; + uint8_t membersSize; +} EventObjectGroup; + +typedef void (*OnGroupCreatedHook) (EventObjectGroup *group); +void SetOnGroupCreatedHook(OnGroupCreatedHook h); +int CallOnGroupCreatedHook(EventObjectGroup *group); + +typedef void (*OnGroupMemberAddedHook) (uint32_t guid, uint64_t newMemberGuid); +void SetOnGroupMemberAddedHook(OnGroupMemberAddedHook h); +int CallOnGroupMemberAddedHook(uint32_t guid, uint64_t newMemberGuid); + +typedef void (*OnGroupMemberRemovedHook) (uint32_t guid, uint64_t removedMemberGuid, uint64_t newLeaderGuid); +void SetOnGroupMemberRemovedHook(OnGroupMemberRemovedHook h); +int CallOnGroupMemberRemovedHook(uint32_t guid, uint64_t removedMemberGuid, uint64_t newLeaderGuid); + +typedef void (*OnGroupDisbandedHook) (uint32_t guid); +void SetOnGroupDisbandedHook(OnGroupDisbandedHook h); +int CallOnGroupDisbandedHook(uint32_t guid); + +typedef void (*OnGroupLootTypeChangedHook) (uint32_t guid, uint8_t lootMethod, uint64_t looter, uint8_t lootThreshold); +void SetOnGroupLootTypeChangedHook(OnGroupLootTypeChangedHook h); +int CallOnGroupLootTypeChangedHook(uint32_t guid, uint8_t lootMethod, uint64_t looter, uint8_t lootThreshold); + +typedef void (*OnGroupDungeonDifficultyChangedHook) (uint32_t guid, uint8_t difficulty); +void SetOnGroupDungeonDifficultyChangedHook(OnGroupDungeonDifficultyChangedHook h); +int CallOnGroupDungeonDifficultyChangedHook(uint32_t guid, uint8_t difficulty); + +typedef void (*OnGroupRaidDifficultyChangedHook) (uint32_t guid, uint8_t difficulty); +void SetOnGroupRaidDifficultyChangedHook(OnGroupRaidDifficultyChangedHook h); +int CallOnGroupRaidDifficultyChangedHook(uint32_t guid, uint8_t difficulty); + +typedef void (*OnGroupConvertedToRaidHook) (uint32_t guid); +void SetOnGroupConvertedToRaidHook(OnGroupConvertedToRaidHook h); +int CallOnGroupConvertedToRaidHook(uint32_t guid); + +#endif diff --git a/deps/libsidecar/stub/events-guild.c b/deps/libsidecar/stub/events-guild.c new file mode 100644 index 000000000..728a1b0d5 --- /dev/null +++ b/deps/libsidecar/stub/events-guild.c @@ -0,0 +1,43 @@ +#include "events-guild.h" + +// GuildMemberAddedHook +OnGuildMemberAddedHook guildMemberAddedHook; +void SetOnGuildMemberAddedHook(OnGuildMemberAddedHook h) { + guildMemberAddedHook = h; +} + +int CallOnGuildMemberAddedHook(uint64_t guild_id, uint64_t player_guid) { + if (guildMemberAddedHook == 0) { + return GuildHookStatusNoHook; + } + guildMemberAddedHook(guild_id, player_guid); + return GuildHookStatusOK; +} + +// GuildMemberLeft +static OnGuildMemberLeftHook guildMemberLeftHook; +void SetOnGuildMemberLeftHook(OnGuildMemberLeftHook h) { + guildMemberLeftHook = h; +} + +int CallOnGuildMemberLeftHook(uint64_t guild_id, uint64_t player_guid) { + if (guildMemberLeftHook == 0) { + return GuildHookStatusNoHook; + } + guildMemberLeftHook(guild_id, player_guid); + return GuildHookStatusOK; +} + +// GuildMemberRemoved +static OnGuildMemberRemovedHook guildMemberRemovedHook; +void SetOnGuildMemberRemovedHook(OnGuildMemberRemovedHook h) { + guildMemberRemovedHook = h; +} + +int CallOnGuildMemberRemovedHook(uint64_t guild_id, uint64_t player_guid) { + if (guildMemberRemovedHook == 0) { + return GuildHookStatusNoHook; + } + guildMemberRemovedHook(guild_id, player_guid); + return GuildHookStatusOK; +} diff --git a/deps/libsidecar/stub/events-guild.h b/deps/libsidecar/stub/events-guild.h new file mode 100644 index 000000000..569389170 --- /dev/null +++ b/deps/libsidecar/stub/events-guild.h @@ -0,0 +1,23 @@ +#ifndef __EVENT_GUILD__ +#define __EVENT_GUILD__ + +#include + +enum GuildHookStatus { + GuildHookStatusOK = 0, + GuildHookStatusNoHook = 1 +}; + +typedef void (*OnGuildMemberAddedHook) (uint64_t /*guild_id*/, uint64_t /*player_guid*/); +void SetOnGuildMemberAddedHook(OnGuildMemberAddedHook h); +int CallOnGuildMemberAddedHook(uint64_t guild_id, uint64_t player_guid); + +typedef void (*OnGuildMemberLeftHook) (uint64_t /*guild_id*/, uint64_t /*player_guid*/); +void SetOnGuildMemberLeftHook(OnGuildMemberLeftHook h); +int CallOnGuildMemberLeftHook(uint64_t guild_id, uint64_t player_guid); + +typedef void (*OnGuildMemberRemovedHook) (uint64_t /*guild_id*/, uint64_t /*player_guid*/); +void SetOnGuildMemberRemovedHook(OnGuildMemberRemovedHook h); +int CallOnGuildMemberRemovedHook(uint64_t guild_id, uint64_t player_guid); + +#endif diff --git a/deps/libsidecar/stub/events-servers-registry.c b/deps/libsidecar/stub/events-servers-registry.c new file mode 100644 index 000000000..9856e6437 --- /dev/null +++ b/deps/libsidecar/stub/events-servers-registry.c @@ -0,0 +1,15 @@ +#include "events-servers-registry.h" + +// MapsReassignedHook +OnMapsReassignedHook mapsReassignedHook; +void SetOnMapsReassignedHook(OnMapsReassignedHook h) { + mapsReassignedHook = h; +} + +int CallOnMapsReassignedHook(uint32_t* maps_added, int maps_added_size, uint32_t* maps_removed, int maps_removed_size) { + if (mapsReassignedHook == 0) { + return ServersRegistryHookStatusNoHook; + } + mapsReassignedHook(maps_added, maps_added_size, maps_removed, maps_removed_size); + return ServersRegistryHookStatusOK; +} diff --git a/deps/libsidecar/stub/events-servers-registry.h b/deps/libsidecar/stub/events-servers-registry.h new file mode 100644 index 000000000..04889e0e5 --- /dev/null +++ b/deps/libsidecar/stub/events-servers-registry.h @@ -0,0 +1,15 @@ +#ifndef __EVENT_SERVERS_REGISTRY__ +#define __EVENT_SERVERS_REGISTRY__ + +#include + +enum ServersRegistryStatus { + ServersRegistryHookStatusOK = 0, + ServersRegistryHookStatusNoHook = 1 +}; + +typedef void (*OnMapsReassignedHook) (uint32_t* /*maps_added*/, int /*maps_added_size*/, uint32_t* /*maps_removed*/, int /*maps_removed_size*/); +void SetOnMapsReassignedHook(OnMapsReassignedHook h); +int CallOnMapsReassignedHook(uint32_t* maps_added, int maps_added_size, uint32_t* maps_removed, int maps_removed_size); + +#endif diff --git a/deps/libsidecar/stub/libsidecar.c b/deps/libsidecar/stub/libsidecar.c new file mode 100644 index 000000000..ed13fedd5 --- /dev/null +++ b/deps/libsidecar/stub/libsidecar.c @@ -0,0 +1,138 @@ +#include "libsidecar.h" +#include +#include + +void panicWithTC9Unavailable(const char* message) { + fprintf(stderr, "Tried to call '%s', but using stub for libsidecar. Use -DUSE_REAL_LIBSIDECAR=ON in cmake to use the real one.\n", message); + exit(EXIT_FAILURE); +} + +// TC9SetBattlegroundStartHandler sets handler for starting battleground. +// +extern void TC9SetBattlegroundStartHandler(BattlegroundStartHandler h) { panicWithTC9Unavailable("TC9SetBattlegroundStartHandler"); } + +// TC9SetBattlegroundAddPlayersHandler sets handler for adding players to battleground. +// +extern void TC9SetBattlegroundAddPlayersHandler(BattlegroundAddPlayersHandler h) { panicWithTC9Unavailable("TC9SetBattlegroundAddPlayersHandler"); } + +void TC9SetOnGroupCreatedHook(OnGroupCreatedHook h) { panicWithTC9Unavailable("TC9SetOnGroupCreatedHook"); } + +// TC9SetOnGroupMemberAddedHook sets hook for member added event. +// +void TC9SetOnGroupMemberAddedHook(OnGroupMemberAddedHook h) { panicWithTC9Unavailable("TC9SetOnGroupMemberAddedHook"); } + +// TC9SetOnGroupMemberRemovedHook sets hook for member left/kicked event. +// +void TC9SetOnGroupMemberRemovedHook(OnGroupMemberRemovedHook h) { panicWithTC9Unavailable("TC9SetOnGroupMemberRemovedHook"); } + +// TC9SetOnGroupDisbandedHook sets hook for group disbanded event. +// +void TC9SetOnGroupDisbandedHook(OnGroupDisbandedHook h) { panicWithTC9Unavailable("TC9SetOnGroupDisbandedHook"); } + +// TC9SetOnGroupLootTypeChangedHook sets hook for group loot type changed event. +// +void TC9SetOnGroupLootTypeChangedHook(OnGroupLootTypeChangedHook h) { panicWithTC9Unavailable("TC9SetOnGroupLootTypeChangedHook"); } + +// TC9SetOnGroupDungeonDifficultyChangedHook sets hook for group dungeon difficulty changed event. +// +void TC9SetOnGroupDungeonDifficultyChangedHook(OnGroupDungeonDifficultyChangedHook h) { panicWithTC9Unavailable("TC9SetOnGroupDungeonDifficultyChangedHook"); } + +// TC9SetOnGroupRaidDifficultyChangedHook sets hook for group raid difficulty changed event. +// +void TC9SetOnGroupRaidDifficultyChangedHook(OnGroupRaidDifficultyChangedHook h) { panicWithTC9Unavailable("TC9SetOnGroupRaidDifficultyChangedHook"); } + +// TC9SetOnGroupConvertedToRaidHook sets hook for group converted to raid event. +// +void TC9SetOnGroupConvertedToRaidHook(OnGroupConvertedToRaidHook h) { panicWithTC9Unavailable("TC9SetOnGroupConvertedToRaidHook"); } + +void TC9SetOnGuildMemberAddedHook(OnGuildMemberAddedHook h) { panicWithTC9Unavailable("TC9SetOnGuildMemberAddedHook"); } + +// TC9SetOnGuildMemberRemovedHook sets hook for guild member removed (kicked) event. +void TC9SetOnGuildMemberRemovedHook(OnGuildMemberRemovedHook h) { panicWithTC9Unavailable("TC9SetOnGuildMemberRemovedHook"); } + +// TC9SetOnGuildMemberLeftHook sets hook for guild member left event. +void TC9SetOnGuildMemberLeftHook(OnGuildMemberLeftHook h) { panicWithTC9Unavailable("TC9SetOnGuildMemberLeftHook"); } + +// TC9ProcessEventsHooks calls all events hooks. +void TC9ProcessEventsHooks() { panicWithTC9Unavailable("TC9ProcessEventsHooks"); } + +// TC9ProcessGRPCOrHTTPRequests calls all grpc or http handlers in queue. +// +void TC9ProcessGRPCOrHTTPRequests() { panicWithTC9Unavailable("TC9ProcessGRPCOrHTTPRequests"); } + +// TC9GetNextAvailableCharacterGuid returns next available characters GUID. Thread unsafe. +uint64_t TC9GetNextAvailableCharacterGuid(int realmID) { panicWithTC9Unavailable("TC9GetNextAvailableCharacterGuid"); return 0; } + +// TC9GetNextAvailableItemGuid returns next available item GUID. Thread unsafe. +uint64_t TC9GetNextAvailableItemGuid(int realmID) { panicWithTC9Unavailable("TC9GetNextAvailableItemGuid"); return 0; } + +// TC9GetNextAvailableInstanceGuid returns next available dungeon/raid instance GUID. Thread unsafe. +uint64_t TC9GetNextAvailableInstanceGuid(int realmID) { panicWithTC9Unavailable("TC9GetNextAvailableInstanceGuid"); return 0; } + +// TC9InitLib inits lib by starting services like grpc and healthcheck. +// Adds game server to the servers registry that will make this server visible for game load balancer. +// +void TC9InitLib(uint16_t port, uint32_t realmID, uint8_t isCrossRealm, char* availableMaps, uint32_t** assignedMaps, int* assignedMapsSize) { panicWithTC9Unavailable("TC9InitLib"); } + +// TC9GracefulShutdown gracefully stops all running services. +// +void TC9GracefulShutdown() { panicWithTC9Unavailable("TC9GracefulShutdown"); } + +// TC9ReadyToAcceptPlayersFromMaps notifies servers registry that this server +// loaded maps related data and ready to accept players from those maps. +// +void TC9ReadyToAcceptPlayersFromMaps(uint32_t* maps, int mapsLen) { panicWithTC9Unavailable("TC9ReadyToAcceptPlayersFromMaps"); } + +// TC9SetCanPlayerInteractWithNPCAndFlagsHandler sets handler for can player interact with NPC and with given NPC flags request. +// +void TC9SetCanPlayerInteractWithNPCAndFlagsHandler(CanPlayerInteractWithNPCAndFlagsHandler h) { panicWithTC9Unavailable("TC9SetCanPlayerInteractWithNPCAndFlagsHandler"); } + +// TC9SetCanPlayerInteractWithGOAndTypeHandler sets handler for can player interact with GameObject and with given object type request. +// +void TC9SetCanPlayerInteractWithGOAndTypeHandler(CanPlayerInteractWithGOAndTypeHandler h) { panicWithTC9Unavailable("TC9SetCanPlayerInteractWithGOAndTypeHandler"); } + +// TC9SetGetPlayerItemsByGuidsHandler sets handler for getting players item by guids request. +void TC9SetGetPlayerItemsByGuidsHandler(GetPlayerItemsByGuidsHandler h) { panicWithTC9Unavailable("TC9SetGetPlayerItemsByGuidsHandler"); } + +// TC9SetRemoveItemsWithGuidsFromPlayerHandler sets handler for removing items by guids from player request. +void TC9SetRemoveItemsWithGuidsFromPlayerHandler(RemoveItemsWithGuidsFromPlayerHandler h) { panicWithTC9Unavailable("TC9SetRemoveItemsWithGuidsFromPlayerHandler"); } + +// TC9SetAddExistingItemToPlayerHandler sets handler for adding item to player request. +void TC9SetAddExistingItemToPlayerHandler(AddExistingItemToPlayerHandler h) { panicWithTC9Unavailable("TC9SetAddExistingItemToPlayerHandler"); } + +// TC9SetGetMoneyForPlayerHandler sets handler for getting money for player request. +// +void TC9SetGetMoneyForPlayerHandler(GetMoneyForPlayerHandler h) { panicWithTC9Unavailable("TC9SetGetMoneyForPlayerHandler"); } + +// TC9SetModifyMoneyForPlayerHandler sets handler for modify money for given player request. +// +void TC9SetModifyMoneyForPlayerHandler(ModifyMoneyForPlayerHandler h) { panicWithTC9Unavailable("TC9SetModifyMoneyForPlayerHandler"); } + + +// TC9SetOnMapsReassignedHook sets hook for maps reassigning by servers registry event. +// +void TC9SetOnMapsReassignedHook(OnMapsReassignedHook h) { panicWithTC9Unavailable("TC9SetOnMapsReassignedHook"); } + +// TC9SetMonitoringDataCollectorHandler sets handler for getting data to handle monitoring request. +// +void TC9SetMonitoringDataCollectorHandler(MonitoringDataCollectorHandler h) { panicWithTC9Unavailable("TC9SetMonitoringDataCollectorHandler"); } + +// TC9NatsPublish/Subscribe generic NATS pub/sub for in-process extensions +int TC9NatsPublish(const char* subject, const char* payload, int payloadLen) { panicWithTC9Unavailable("TC9NatsPublish"); return -1; } +int TC9NatsSubscribe(const char* subject, TC9NatsMessageHandler handler) { panicWithTC9Unavailable("TC9NatsSubscribe"); return -1; } + +// TC9PlayerLeftBattleground notifies matchmaking server that player left battleground +// +void TC9PlayerLeftBattleground(uint64_t playerGUID, uint32_t realmID, uint32_t instanceID) { panicWithTC9Unavailable("TC9PlayerLeftBattleground"); } + +// TC9BattlegroundStatusChanged notifies matchmaking server that battleground status changed +// +void TC9BattlegroundStatusChanged(uint32_t instanceID, uint8_t status) { panicWithTC9Unavailable("TC9BattlegroundStatusChanged"); } + +// TC9SetCanPlayerJoinBattlegroundQueueHandler sets handler for checking if player can join to battleground queue. +// +void TC9SetCanPlayerJoinBattlegroundQueueHandler(CanPlayerJoinBattlegroundQueueHandler h) { panicWithTC9Unavailable("TC9SetCanPlayerJoinBattlegroundQueueHandler"); } + +// TC9SetCanPlayerTeleportToBattlegroundHandler sets handler for checking if player can teleport to battleground. +// +void TC9SetCanPlayerTeleportToBattlegroundHandler(CanPlayerTeleportToBattlegroundHandler h) { panicWithTC9Unavailable("TC9SetCanPlayerTeleportToBattlegroundHandler"); } diff --git a/deps/libsidecar/stub/libsidecar.h b/deps/libsidecar/stub/libsidecar.h new file mode 100644 index 000000000..220d3fa94 --- /dev/null +++ b/deps/libsidecar/stub/libsidecar.h @@ -0,0 +1,84 @@ +#ifndef __LIBSIDECAR_H__ +#define __LIBSIDECAR_H__ + +#include +#include + +/* Include all API headers */ +#include "battleground-api.h" +#include "events-group.h" +#include "events-guild.h" +#include "events-servers-registry.h" +#include "monitoring.h" +#include "player-interactions-api.h" +#include "player-items-api.h" +#include "player-money-api.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Main library functions */ +void TC9InitLib(uint16_t port, uint32_t realmID, uint8_t isCrossRealm, char* availableMaps, uint32_t** assignedMaps, int* assignedMapsSize); +void TC9GracefulShutdown(); +void TC9ProcessGRPCOrHTTPRequests(); +void TC9ProcessEventsHooks(); + +/* GUID generation */ +uint64_t TC9GetNextAvailableCharacterGuid(int realmID); +uint64_t TC9GetNextAvailableItemGuid(int realmID); +uint64_t TC9GetNextAvailableInstanceGuid(int realmID); + +/* Map loading notification */ +void TC9ReadyToAcceptPlayersFromMaps(uint32_t* maps, int mapsLen); + +/* Generic NATS pub/sub for in-process extensions. Subscribe callbacks run + * on the thread that calls TC9ProcessEventsHooks. Call after TC9InitLib. + * Both return 0 on success, -1 on error. */ +typedef void (*TC9NatsMessageHandler)(const char* subject, const char* payload, int payloadLen); +int TC9NatsPublish(const char* subject, const char* payload, int payloadLen); +int TC9NatsSubscribe(const char* subject, TC9NatsMessageHandler handler); + +/* Matchmaking notifications */ +void TC9PlayerLeftBattleground(uint64_t playerGUID, uint32_t realmID, uint32_t instanceID); +void TC9BattlegroundStatusChanged(uint32_t instanceID, uint8_t status); + +/* Event hooks registration */ +void TC9SetOnGroupCreatedHook(OnGroupCreatedHook h); +void TC9SetOnGroupMemberAddedHook(OnGroupMemberAddedHook h); +void TC9SetOnGroupMemberRemovedHook(OnGroupMemberRemovedHook h); +void TC9SetOnGroupDisbandedHook(OnGroupDisbandedHook h); +void TC9SetOnGroupLootTypeChangedHook(OnGroupLootTypeChangedHook h); +void TC9SetOnGroupDungeonDifficultyChangedHook(OnGroupDungeonDifficultyChangedHook h); +void TC9SetOnGroupRaidDifficultyChangedHook(OnGroupRaidDifficultyChangedHook h); +void TC9SetOnGroupConvertedToRaidHook(OnGroupConvertedToRaidHook h); + +void TC9SetOnGuildMemberAddedHook(OnGuildMemberAddedHook h); +void TC9SetOnGuildMemberRemovedHook(OnGuildMemberRemovedHook h); +void TC9SetOnGuildMemberLeftHook(OnGuildMemberLeftHook h); + +void TC9SetOnMapsReassignedHook(OnMapsReassignedHook h); + +/* Handler registration for gRPC requests */ +void TC9SetBattlegroundStartHandler(BattlegroundStartHandler h); +void TC9SetBattlegroundAddPlayersHandler(BattlegroundAddPlayersHandler h); +void TC9SetCanPlayerJoinBattlegroundQueueHandler(CanPlayerJoinBattlegroundQueueHandler h); +void TC9SetCanPlayerTeleportToBattlegroundHandler(CanPlayerTeleportToBattlegroundHandler h); + +void TC9SetMonitoringDataCollectorHandler(MonitoringDataCollectorHandler h); + +void TC9SetCanPlayerInteractWithNPCAndFlagsHandler(CanPlayerInteractWithNPCAndFlagsHandler h); +void TC9SetCanPlayerInteractWithGOAndTypeHandler(CanPlayerInteractWithGOAndTypeHandler h); + +void TC9SetGetPlayerItemsByGuidsHandler(GetPlayerItemsByGuidsHandler h); +void TC9SetRemoveItemsWithGuidsFromPlayerHandler(RemoveItemsWithGuidsFromPlayerHandler h); +void TC9SetAddExistingItemToPlayerHandler(AddExistingItemToPlayerHandler h); + +void TC9SetGetMoneyForPlayerHandler(GetMoneyForPlayerHandler h); +void TC9SetModifyMoneyForPlayerHandler(ModifyMoneyForPlayerHandler h); + +#ifdef __cplusplus +} +#endif + +#endif /* __LIBSIDECAR_H__ */ diff --git a/deps/libsidecar/stub/monitoring.c b/deps/libsidecar/stub/monitoring.c new file mode 100644 index 000000000..6d347b3f7 --- /dev/null +++ b/deps/libsidecar/stub/monitoring.c @@ -0,0 +1,16 @@ +#include "monitoring.h" + +static MonitoringDataCollectorHandler monitoringDataCollectorHandler; +void SetMonitoringDataCollectorHandler(MonitoringDataCollectorHandler h) { + monitoringDataCollectorHandler = h; +} + +MonitoringDataCollectorResponse CallMonitoringDataCollectorHandler() { + if (monitoringDataCollectorHandler == 0) { + MonitoringDataCollectorResponse resp; + resp.errorCode = MonitoringErrorCodeNoHandler; + return resp; + } + + return monitoringDataCollectorHandler(); +} diff --git a/deps/libsidecar/stub/monitoring.h b/deps/libsidecar/stub/monitoring.h new file mode 100644 index 000000000..2cd66b493 --- /dev/null +++ b/deps/libsidecar/stub/monitoring.h @@ -0,0 +1,28 @@ +#ifndef __MONITORING__ +#define __MONITORING__ + +#include +#include +#include + +typedef enum MonitoringErrorCode { + MonitoringErrorCodeNoError = 0, + MonitoringErrorCodeNoHandler = 1, +} MonitoringErrorCode; + +// MonitoringDataCollectorResponse request. +typedef struct { + int errorCode; + uint32_t connectedPlayers; + uint32_t diffMean; + uint32_t diffMedian; + uint32_t diff95Percentile; + uint32_t diff99Percentile; + uint32_t diffMaxPercentile; +} MonitoringDataCollectorResponse; + +typedef MonitoringDataCollectorResponse (*MonitoringDataCollectorHandler)(); +void SetMonitoringDataCollectorHandler(MonitoringDataCollectorHandler h); +MonitoringDataCollectorResponse CallMonitoringDataCollectorHandler(); + +#endif diff --git a/deps/libsidecar/stub/player-interactions-api.c b/deps/libsidecar/stub/player-interactions-api.c new file mode 100644 index 000000000..96c4bab88 --- /dev/null +++ b/deps/libsidecar/stub/player-interactions-api.c @@ -0,0 +1,31 @@ +#include "player-interactions-api.h" + +static CanPlayerInteractWithNPCAndFlagsHandler canPlayerInteractWithNPCAndFlagsHandler; +void SetCanPlayerInteractWithNPCAndFlagsHandler(CanPlayerInteractWithNPCAndFlagsHandler h) { + canPlayerInteractWithNPCAndFlagsHandler = h; +} + +CanPlayerInteractWithNPCAndFlagsResponse CallCanPlayerInteractWithNPCAndFlagsHandler(uint64_t player_guid, uint64_t npc_guid, uint32_t npc_flags) { + if (canPlayerInteractWithNPCAndFlagsHandler == 0) { + CanPlayerInteractWithNPCAndFlagsResponse resp; + resp.errorCode = PlayerInteractionErrorCodeNoHandler; + return resp; + } + + return canPlayerInteractWithNPCAndFlagsHandler(player_guid, npc_guid, npc_flags); +} + +static CanPlayerInteractWithGOAndTypeHandler canPlayerInteractWithGOAndTypeHandler; +void SetCanPlayerInteractWithGOAndTypeHandler(CanPlayerInteractWithGOAndTypeHandler h) { + canPlayerInteractWithGOAndTypeHandler = h; +} + +CanPlayerInteractWithGOAndTypeResponse CallCanPlayerInteractWithGOAndTypeHandler(uint64_t player_guid, uint64_t go_guid, uint8_t go_type) { + if (canPlayerInteractWithGOAndTypeHandler == 0) { + CanPlayerInteractWithGOAndTypeResponse resp; + resp.errorCode = PlayerInteractionErrorCodeNoHandler; + return resp; + } + + return canPlayerInteractWithGOAndTypeHandler(player_guid, go_guid, go_type); +} diff --git a/deps/libsidecar/stub/player-interactions-api.h b/deps/libsidecar/stub/player-interactions-api.h new file mode 100644 index 000000000..f60a916f8 --- /dev/null +++ b/deps/libsidecar/stub/player-interactions-api.h @@ -0,0 +1,34 @@ +#ifndef __PLAYER_INTERACTIONS_API__ +#define __PLAYER_INTERACTIONS_API__ + +#include +#include +#include + +typedef enum PlayerInteractionErrorCode { + PlayerInteractionErrorCodeNoError = 0, + PlayerInteractionErrorCodeNoHandler = 1, + PlayerInteractionErrorCodeCodePlayerNotFound = 2, +} PlayerInteractionErrorCode; + +// CanPlayerInteractWithNPCAndFlags request. +typedef struct { + int errorCode; + bool canInteract; +} CanPlayerInteractWithNPCAndFlagsResponse; + +typedef CanPlayerInteractWithNPCAndFlagsResponse (*CanPlayerInteractWithNPCAndFlagsHandler) (uint64_t /*playerGUID*/, uint64_t /*npcGUID*/, uint32_t /*npcFlags*/); +void SetCanPlayerInteractWithNPCAndFlagsHandler(CanPlayerInteractWithNPCAndFlagsHandler h); +CanPlayerInteractWithNPCAndFlagsResponse CallCanPlayerInteractWithNPCAndFlagsHandler(uint64_t player_guid, uint64_t npc_guid, uint32_t npc_flags); + +// CanPlayerInteractWithGOAndType request. +typedef struct { + int errorCode; + bool canInteract; +} CanPlayerInteractWithGOAndTypeResponse; + +typedef CanPlayerInteractWithGOAndTypeResponse (*CanPlayerInteractWithGOAndTypeHandler) (uint64_t /*playerGUID*/, uint64_t /*goGUID*/, uint8_t /*goType*/); +void SetCanPlayerInteractWithGOAndTypeHandler(CanPlayerInteractWithGOAndTypeHandler h); +CanPlayerInteractWithGOAndTypeResponse CallCanPlayerInteractWithGOAndTypeHandler(uint64_t player_guid, uint64_t go_guid, uint8_t go_type); + +#endif diff --git a/deps/libsidecar/stub/player-items-api.c b/deps/libsidecar/stub/player-items-api.c new file mode 100644 index 000000000..ae992c53f --- /dev/null +++ b/deps/libsidecar/stub/player-items-api.c @@ -0,0 +1,44 @@ +#include "player-items-api.h" + +static GetPlayerItemsByGuidsHandler getPlayerItemsByGuidsHandler; +void SetGetPlayerItemsByGuidsHandler(GetPlayerItemsByGuidsHandler h) { + getPlayerItemsByGuidsHandler = h; +} + +GetPlayerItemsByGuidsResponse CallGetPlayerItemsByGuidsHandler(uint64_t player_guid, uint64_t* items_guids, int items_guids_size) { + if (getPlayerItemsByGuidsHandler == 0) { + GetPlayerItemsByGuidsResponse resp; + resp.errorCode = PlayerItemErrorCodeNoHandler; + return resp; + } + + return getPlayerItemsByGuidsHandler(player_guid, items_guids, items_guids_size); +} + +static RemoveItemsWithGuidsFromPlayerHandler removeItemsWithGuidsFromPlayerHandler; +void SetRemoveItemsWithGuidsFromPlayerHandler(RemoveItemsWithGuidsFromPlayerHandler h) { + removeItemsWithGuidsFromPlayerHandler = h; +} + +RemoveItemsWithGuidsFromPlayerResponse CallRemoveItemsWithGuidsFromPlayerHandler(uint64_t player_guid, uint64_t* items_guids, int items_guids_size, uint64_t assign_player_guid) { + if (removeItemsWithGuidsFromPlayerHandler == 0) { + RemoveItemsWithGuidsFromPlayerResponse resp; + resp.errorCode = PlayerItemErrorCodeNoHandler; + return resp; + } + + return removeItemsWithGuidsFromPlayerHandler(player_guid, items_guids, items_guids_size, assign_player_guid); +} + +static AddExistingItemToPlayerHandler addExistingItemToPlayerHandler; +void SetAddExistingItemToPlayerHandler(AddExistingItemToPlayerHandler h) { + addExistingItemToPlayerHandler = h; +} + +PlayerItemErrorCode CallAddExistingItemToPlayerHandler(AddExistingItemToPlayerRequest *r) { + if (addExistingItemToPlayerHandler == 0) { + return PlayerItemErrorCodeNoHandler; + } + + return addExistingItemToPlayerHandler(r); +} diff --git a/deps/libsidecar/stub/player-items-api.h b/deps/libsidecar/stub/player-items-api.h new file mode 100644 index 000000000..8f5dd8ded --- /dev/null +++ b/deps/libsidecar/stub/player-items-api.h @@ -0,0 +1,68 @@ +#ifndef __PLAYER_ITEMS_API__ +#define __PLAYER_ITEMS_API__ + +#include +#include +#include + +typedef enum PlayerItemErrorCode { + PlayerItemErrorCodeNoError = 0, + PlayerItemErrorCodeNoHandler = 1, + PlayerItemErrorCodePlayerNotFound = 2, + PlayerItemErrorNoInventorySpace = 3, + PlayerItemErrorUnknownTemplate = 4, + PlayerItemErrorFailedToCreateItem = 5 +} PlayerItemErrorCode; + +// GetPlayerItemsByGuids request. +typedef struct { + uint64_t guid; + uint32_t entry; + uint64_t owner; + uint8_t bagSlot; + uint8_t slot; + bool isTradable; + uint32_t count; + uint16_t flags; + uint8_t durability; + int8_t randomPropertyID; + const char* text; +} PlayerItem; + +typedef struct { + int errorCode; + PlayerItem* items; + int itemsSize; +} GetPlayerItemsByGuidsResponse; + +typedef GetPlayerItemsByGuidsResponse (*GetPlayerItemsByGuidsHandler) (uint64_t /*player_guid*/, uint64_t* /*items_guids*/, int /*items_guids_size*/); +void SetGetPlayerItemsByGuidsHandler(GetPlayerItemsByGuidsHandler h); +GetPlayerItemsByGuidsResponse CallGetPlayerItemsByGuidsHandler(uint64_t player_guid, uint64_t* items_guids, int items_guids_size); + +// RemoveItemsWithGuidsFromPlayer request. +typedef struct { + int errorCode; + uint64_t* updatedItems; + int updatedItemsSize; +} RemoveItemsWithGuidsFromPlayerResponse; + +typedef RemoveItemsWithGuidsFromPlayerResponse (*RemoveItemsWithGuidsFromPlayerHandler) (uint64_t /*player_guid*/, uint64_t* /*items_guids*/, int /*items_guids_size*/, uint64_t /*assign_player_guid*/); +void SetRemoveItemsWithGuidsFromPlayerHandler(RemoveItemsWithGuidsFromPlayerHandler h); +RemoveItemsWithGuidsFromPlayerResponse CallRemoveItemsWithGuidsFromPlayerHandler(uint64_t player_guid, uint64_t* items_guids, int items_guids_size, uint64_t assign_player_guid); + +// AddExistingItemToPlayer request. +typedef struct { + uint64_t playerGuid; + uint64_t itemGuid; + uint32_t itemEntry; + uint32_t itemCount; + uint16_t itemFlags; + uint8_t itemDurability; + int8_t itemRandomPropertyID; +} AddExistingItemToPlayerRequest; + +typedef PlayerItemErrorCode (*AddExistingItemToPlayerHandler) (AddExistingItemToPlayerRequest*); +void SetAddExistingItemToPlayerHandler(AddExistingItemToPlayerHandler h); +PlayerItemErrorCode CallAddExistingItemToPlayerHandler(AddExistingItemToPlayerRequest*); + +#endif diff --git a/deps/libsidecar/stub/player-money-api.c b/deps/libsidecar/stub/player-money-api.c new file mode 100644 index 000000000..a7836dbd6 --- /dev/null +++ b/deps/libsidecar/stub/player-money-api.c @@ -0,0 +1,31 @@ +#include "player-money-api.h" + +static GetMoneyForPlayerHandler getMoneyForPlayerHandler; +void SetGetMoneyForPlayerHandler(GetMoneyForPlayerHandler h) { + getMoneyForPlayerHandler = h; +} + +GetMoneyForPlayerResponse CallGetMoneyForPlayerHandler(uint64_t player_guid) { + if (getMoneyForPlayerHandler == 0) { + GetMoneyForPlayerResponse resp; + resp.errorCode = PlayerMoneyErrorCodeNoHandler; + return resp; + } + + return getMoneyForPlayerHandler(player_guid); +} + +static ModifyMoneyForPlayerHandler modifyMoneyForPlayerHandler; +void SetModifyMoneyForPlayerHandler(ModifyMoneyForPlayerHandler h) { + modifyMoneyForPlayerHandler = h; +} + +ModifyMoneyForPlayerResponse CallModifyMoneyForPlayerHandler(uint64_t player_guid, int32_t amount) { + if (modifyMoneyForPlayerHandler == 0) { + ModifyMoneyForPlayerResponse resp; + resp.errorCode = PlayerMoneyErrorCodeNoHandler; + return resp; + } + + return modifyMoneyForPlayerHandler(player_guid, amount); +} diff --git a/deps/libsidecar/stub/player-money-api.h b/deps/libsidecar/stub/player-money-api.h new file mode 100644 index 000000000..dc909c58d --- /dev/null +++ b/deps/libsidecar/stub/player-money-api.h @@ -0,0 +1,35 @@ +#ifndef __PLAYER_MONEY_API__ +#define __PLAYER_MONEY_API__ + +#include +#include +#include + +typedef enum PlayerMoneyErrorCode { + PlayerMoneyErrorCodeNoError = 0, + PlayerMoneyErrorCodeNoHandler = 1, + PlayerMoneyErrorCodePlayerNotFound = 2, + PlayerMoneyErrorCodeTooMuchMoney = 3, +} PlayerMoneyErrorCode; + +// GetMoneyForPlayer request. +typedef struct { + int errorCode; + uint32_t money; +} GetMoneyForPlayerResponse; + +typedef GetMoneyForPlayerResponse (*GetMoneyForPlayerHandler) (uint64_t /*player_guid*/); +void SetGetMoneyForPlayerHandler(GetMoneyForPlayerHandler h); +GetMoneyForPlayerResponse CallGetMoneyForPlayerHandler(uint64_t player_guid); + +// ModifyMoneyForPlayer request. +typedef struct { + int errorCode; + uint32_t newMoneyValue; +} ModifyMoneyForPlayerResponse; + +typedef ModifyMoneyForPlayerResponse (*ModifyMoneyForPlayerHandler) (uint64_t /*player_guid*/, int32_t /*amount*/); +void SetModifyMoneyForPlayerHandler(ModifyMoneyForPlayerHandler h); +ModifyMoneyForPlayerResponse CallModifyMoneyForPlayerHandler(uint64_t player_guid, int32_t amount); + +#endif diff --git a/src/cmake/showoptions.cmake b/src/cmake/showoptions.cmake index 443584127..f8e41d361 100644 --- a/src/cmake/showoptions.cmake +++ b/src/cmake/showoptions.cmake @@ -121,6 +121,12 @@ else() message("* Use GIT revision hash : Yes (default)") endif() +if ( USE_REAL_LIBSIDECAR ) + message("* Use stub for libsidecar : No") +else() + message("* Use stub for libsidecar : Yes") +endif() + if ( NOJEM ) message("") message(" *** NOJEM - WARNING!") diff --git a/src/server/apps/CMakeLists.txt b/src/server/apps/CMakeLists.txt index 188e83093..ebd7f3a65 100644 --- a/src/server/apps/CMakeLists.txt +++ b/src/server/apps/CMakeLists.txt @@ -139,7 +139,8 @@ foreach(APPLICATION_NAME ${APPLICATIONS_BUILD_LIST}) game gsoap readline - gperftools) + gperftools + libsidecar) if (UNIX AND NOT NOJEM) set(${APP_PROJECT_NAME}_LINK_FLAGS "-pthread -lncurses ${${APP_PROJECT_NAME}_LINK_FLAGS}") diff --git a/src/server/apps/worldserver/Main.cpp b/src/server/apps/worldserver/Main.cpp index 511c17e36..e3182d65d 100644 --- a/src/server/apps/worldserver/Main.cpp +++ b/src/server/apps/worldserver/Main.cpp @@ -49,10 +49,12 @@ #include "SharedDefines.h" #include "SteadyTimer.h" #include "Systemd.h" +#include "TC9Sidecar.h" #include "World.h" #include "WorldSessionMgr.h" #include "WorldSocket.h" #include "WorldSocketMgr.h" +#include "libsidecar.h" #include #include #include @@ -365,7 +367,8 @@ int main(int argc, char** argv) sWorldSocketMgr.StopNetwork(); ///- Clean database before leaving - ClearOnlineAccounts(); + if (!sToCloud9Sidecar->ClusterModeEnabled()) + ClearOnlineAccounts(); }); // Set server online (allow connecting now) @@ -397,11 +400,15 @@ int main(int argc, char** argv) cliThread.reset(new std::thread(CliThread), &ShutdownCLIThread); } + sToCloud9Sidecar->Init(worldPort, realm.Id.Realm); + WorldUpdateLoop(); // Shutdown starts here threadPool.reset(); + sToCloud9Sidecar->Deinit(); + sLog->SetSynchronous(); sScriptMgr->OnShutdown(); @@ -455,8 +462,11 @@ bool StartDB() LOG_INFO("server.loading", "Loading World Information..."); LOG_INFO("server.loading", "> RealmID: {}", realm.Id.Realm); - ///- Clean the database before starting - ClearOnlineAccounts(); + ///- Clean the database before starting. + /// Cluster.Enabled is read from config here because sToCloud9Sidecar->Init() + /// has not run yet; ClusterModeEnabled() would still be the default false. + if (!sConfigMgr->GetOption("Cluster.Enabled", false)) + ClearOnlineAccounts(); ///- Insert version info into DB WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_VERSION); diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index 713dbf5fb..d3b9db4c1 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -72,6 +72,8 @@ # DEBUG # DYNAMIC RESPAWN SETTINGS # +# CLUSTER SETTINGS +# ################################################################################################### ################################################################################################### @@ -4893,3 +4895,56 @@ Respawn.ForceCompatibilityMode = 0 # GAME SETTINGS END # # # ################################################################################################### + +################################################################################################### +# # +# CLUSTER SETTINGS BEGIN # +# # +################################################################################################### + +################################################################################################### +# CLUSTER SETTINGS +# +# This feature is experimental and still under development. It enables cluster mode, allowing multiple +# worldservers to run for a single realm, distributing the load between them. Alongside worldservers, +# additional services are required for proper functionality. If you encounter any issues, please report +# them to the ToCloud9 project: https://github.com/walkline/ToCloud9. +# +# Cluster.Enabled +# Description: Enables/disables cluster mode. +# SECURITY: in cluster mode the ToCloud9 gateway is the trusted +# authentication boundary. This worldserver then skips session-key +# digest verification, packet encryption, warden, IP/country locks, +# ban and minimum-security-level enforcement, and the +# character-ownership check on login. The worldserver port must +# only be reachable by the gateway, never directly by players. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Cluster.Enabled=0 + +# +# Cluster.AvailableMaps +# Description: List of available maps id on this server. +# Examples: "" - (Can handle any map) +# "0,1,573" + +Cluster.AvailableMaps="" + +# +# Cluster.IsCrossrealm +# Description: Enables cross-realm functionality for a cross-realm setup. +# When enabled, a connection to a MySQL cross-realm reverse proxy is required. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Cluster.IsCrossrealm=0 + +# +################################################################################################### + +################################################################################################### +# # +# CLUSTER SETTINGS END # +# # +################################################################################################### diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index f70764724..9b323b766 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -634,6 +634,13 @@ void CharacterDatabaseConnection::DoPrepareStatements() // world_state PrepareStatement(CHAR_SEL_WORLD_STATE, "SELECT Id, Data FROM world_state", CONNECTION_SYNCH); PrepareStatement(CHAR_REP_WORLD_STATE, "REPLACE INTO world_state (Id, Data) VALUES(?, ?)", CONNECTION_ASYNC); + + // CHAR_NO_OP_PROVIDE_REALM_CONTEXT is a no-op query that accepts a single parameter: the realm ID. + // This query is used specifically in cross-realm scenarios when the database transaction + // lacks sufficient context to determine which realm's database the query should target. + // By providing the realm ID explicitly, this ensures that mysql reverse proxy will use + // correct realm database for the transaction. + PrepareStatement(CHAR_NO_OP_PROVIDE_REALM_CONTEXT, "SELECT ? AS no_op", CONNECTION_ASYNC); } CharacterDatabaseConnection::CharacterDatabaseConnection(MySQLConnectionInfo& connInfo) : MySQLConnection(connInfo) diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 0ca6e40c9..e9fe3d705 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -543,6 +543,8 @@ enum CharacterDatabaseStatements : uint32 CHAR_SEL_WORLD_STATE, CHAR_REP_WORLD_STATE, + CHAR_NO_OP_PROVIDE_REALM_CONTEXT, + MAX_CHARACTERDATABASE_STATEMENTS }; diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp index caa95df99..9937f3441 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -35,10 +35,12 @@ #include "ObjectMgr.h" #include "Pet.h" #include "Player.h" +#include "Realm.h" #include "RBAC.h" #include "ReputationMgr.h" #include "ScriptMgr.h" #include "SpellAuras.h" +#include "TC9Sidecar.h" #include "Transport.h" #include "Util.h" #include "World.h" @@ -277,6 +279,12 @@ void Battleground::Update(uint32 diff) if (!GetInvitedCount(TEAM_HORDE) && !GetInvitedCount(TEAM_ALLIANCE)) { m_SetDeleteThis = true; + + // Only needed for the sidecar notify inside SetStatus; queue and + // spectator code read the status within this manager pass, so do + // not change it on non-cluster servers. + if (sToCloud9Sidecar->ClusterModeEnabled()) + SetStatus(STATUS_WAIT_LEAVE); } return; @@ -1071,6 +1079,11 @@ void Battleground::RemovePlayerAtLeave(Player* player) // if the player was a match participant if (participant) { + if (sToCloud9Sidecar->ClusterModeEnabled()) + sToCloud9Sidecar->OnPlayerLeftBattleground(player->GetGUID().GetCounter(), + player->GetGUID().GetRealmID(), + GetInstanceID()); + player->ClearAfkReports(); WorldPacket data; @@ -1889,3 +1902,11 @@ uint8 Battleground::GetUniqueBracketId() const { return GetMaxLevel() / 10; } + +void Battleground::SetStatus(BattlegroundStatus Status) +{ + m_Status = Status; + + if (sToCloud9Sidecar->ClusterModeEnabled() && GetInstanceID() != 0) + sToCloud9Sidecar->OnBattlegroundStatusChanged(GetInstanceID(), Status); +} diff --git a/src/server/game/Battlegrounds/Battleground.h b/src/server/game/Battlegrounds/Battleground.h index 84b2f1f53..f45a32b5a 100644 --- a/src/server/game/Battlegrounds/Battleground.h +++ b/src/server/game/Battlegrounds/Battleground.h @@ -366,7 +366,7 @@ public: void SetRandomTypeID(BattlegroundTypeId TypeID) { m_RandomTypeID = TypeID; } void SetBracket(PvPDifficultyEntry const* bracketEntry); void SetInstanceID(uint32 InstanceID) { m_InstanceID = InstanceID; } - void SetStatus(BattlegroundStatus Status) { m_Status = Status; } + void SetStatus(BattlegroundStatus Status); void SetClientInstanceID(uint32 InstanceID) { m_ClientInstanceID = InstanceID; } void SetStartTime(uint32 Time) { m_StartTime = Time; } void SetEndTime(uint32 Time) { m_EndTime = Time; } diff --git a/src/server/game/CMakeLists.txt b/src/server/game/CMakeLists.txt index 3ac1d0d35..d1ae863c9 100644 --- a/src/server/game/CMakeLists.txt +++ b/src/server/game/CMakeLists.txt @@ -54,7 +54,8 @@ target_link_libraries(game PRIVATE acore-core-interface PUBLIC - game-interface) + game-interface + libsidecar) set_target_properties(game PROPERTIES diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index 2dc3047e2..7b5b34e06 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -26,6 +26,7 @@ #include "SpellInfo.h" #include "SpellMgr.h" #include "StringConvert.h" +#include "TC9Sidecar.h" #include "Tokenize.h" #include "WorldPacket.h" @@ -348,7 +349,7 @@ void Item::SaveToDB(CharacterDatabaseTransaction trans) uint8 index = 0; CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(uState == ITEM_NEW ? CHAR_REP_ITEM_INSTANCE : CHAR_UPD_ITEM_INSTANCE); stmt->SetData( index, GetEntry()); - stmt->SetData(++index, GetOwnerGUID().GetCounter()); + stmt->SetData(++index, GetOwnerGUID().GetRawValue()); stmt->SetData(++index, GetGuidValue(ITEM_FIELD_CREATOR).GetCounter()); stmt->SetData(++index, GetGuidValue(ITEM_FIELD_GIFTCREATOR).GetCounter()); stmt->SetData(++index, GetCount()); @@ -381,7 +382,7 @@ void Item::SaveToDB(CharacterDatabaseTransaction trans) if ((uState == ITEM_CHANGED) && IsWrapped()) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GIFT_OWNER); - stmt->SetData(0, GetOwnerGUID().GetCounter()); + stmt->SetData(0, GetOwnerGUID().GetRawValue()); stmt->SetData(1, guid); trans->Append(stmt); } @@ -1096,29 +1097,32 @@ Item* Item::CreateItem(uint32 item, uint32 count, Player const* player, bool clo return nullptr; //don't create item at zero count ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(item); - if (pProto) - { - if (count > pProto->GetMaxStackSize()) - count = pProto->GetMaxStackSize(); - - ASSERT_NODEBUGINFO(count != 0 && "pProto->Stackable == 0 but checked at loading already"); - - Item* pItem = NewItemOrBag(pProto); - if (pItem->Create(sObjectMgr->GetGenerator().Generate(), item, player)) - { - pItem->SetCount(count); - if (!clone) - pItem->SetItemRandomProperties(randomPropertyId ? randomPropertyId : Item::GenerateItemRandomPropertyId(item)); - else if (randomPropertyId) - pItem->SetItemRandomProperties(randomPropertyId); - return pItem; - } - else - delete pItem; - } - else + if (!pProto) ABORT(); - return nullptr; + + if (count > pProto->GetMaxStackSize()) + count = pProto->GetMaxStackSize(); + + ASSERT_NODEBUGINFO(count != 0 && "pProto->Stackable == 0 but checked at loading already"); + + uint16 realmId = DEFAULT_NON_CROSSREALM_REALM_ID; + if (sToCloud9Sidecar->IsCrossrealm() && player) + realmId = player->GetGUID().GetRealmID(); + + Item* pItem = NewItemOrBag(pProto); + if (!pItem->Create(sObjectMgr->GetGenerator().Generate(realmId), item, player)) + { + delete pItem; + return nullptr; + } + + pItem->SetCount(count); + if (!clone) + pItem->SetItemRandomProperties(randomPropertyId ? randomPropertyId : Item::GenerateItemRandomPropertyId(item)); + else if (randomPropertyId) + pItem->SetItemRandomProperties(randomPropertyId); + + return pItem; } Item* Item::CloneItem(uint32 count, Player const* player) const diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index abbae3d26..9bdd47d4a 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -121,10 +121,14 @@ void Object::_InitValues() } void Object::_Create(ObjectGuid::LowType guidlow, uint32 entry, HighGuid guidhigh) +{ + _Create(ObjectGuid(guidhigh, entry, guidlow)); +} + +void Object::_Create(ObjectGuid guid) { if (!m_uint32Values) _InitValues(); - ObjectGuid guid(guidhigh, entry, guidlow); SetGuidValue(OBJECT_FIELD_GUID, guid); SetUInt32Value(OBJECT_FIELD_TYPE, m_objectType); m_PackGUID.Set(guid); diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 8d57e1d0d..ce311a2ef 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -241,6 +241,7 @@ protected: void _InitValues(); void _Create(ObjectGuid::LowType guidlow, uint32 entry, HighGuid guidhigh); + void _Create(ObjectGuid guid); [[nodiscard]] std::string _ConcatFields(uint16 startIndex, uint16 size) const; bool _LoadIntoDataField(std::string const& data, uint32 startOffset, uint32 count); diff --git a/src/server/game/Entities/Object/ObjectGuid.cpp b/src/server/game/Entities/Object/ObjectGuid.cpp index 2a6e23072..b6e12b6e2 100644 --- a/src/server/game/Entities/Object/ObjectGuid.cpp +++ b/src/server/game/Entities/Object/ObjectGuid.cpp @@ -17,6 +17,7 @@ #include "ObjectGuid.h" #include "Log.h" +#include "TC9Sidecar.h" #include "World.h" #include #include @@ -96,6 +97,21 @@ void ObjectGuidGeneratorBase::HandleCounterOverflow(HighGuid high) World::StopNow(ERROR_EXIT_CODE); } +bool ObjectGuidGeneratorBase::GetClusterGuid(HighGuid high, uint16 realmId, ObjectGuid::LowType& clusterGuid) +{ + if (!sToCloud9Sidecar->ClusterModeEnabled()) + return false; + + if (high == HighGuid::Player) + clusterGuid = ObjectGuid::LowType(sToCloud9Sidecar->GenerateCharacterGuid(realmId)); + else if (high == HighGuid::Item) + clusterGuid = ObjectGuid::LowType(sToCloud9Sidecar->GenerateItemGuid(realmId)); + else + return false; + + return true; +} + #define GUID_TRAIT_INSTANTIATE_GUID( HIGH_GUID ) \ template class ObjectGuidGenerator< HIGH_GUID >; diff --git a/src/server/game/Entities/Object/ObjectGuid.h b/src/server/game/Entities/Object/ObjectGuid.h index f92229e99..a32bb2fc9 100644 --- a/src/server/game/Entities/Object/ObjectGuid.h +++ b/src/server/game/Entities/Object/ObjectGuid.h @@ -27,6 +27,9 @@ #include #include +// Realm id packed into bits 32-47 of a player ObjectGuid; 0 means local / non-crossrealm. +constexpr uint16 DEFAULT_NON_CROSSREALM_REALM_ID = 0; + enum TypeID { TYPEID_OBJECT = 0, @@ -142,6 +145,7 @@ class ObjectGuid [[nodiscard]] uint64 GetRawValue() const { return _guid; } [[nodiscard]] HighGuid GetHigh() const { return HighGuid((_guid >> 48) & 0x0000FFFF); } [[nodiscard]] uint32 GetEntry() const { return HasEntry() ? uint32((_guid >> 24) & UI64LIT(0x0000000000FFFFFF)) : 0; } + [[nodiscard]] uint16 GetRealmID() const { return IsPlayer() ? uint16((_guid >> 32) & UI64LIT(0xFFFF)) : 0; } [[nodiscard]] LowType GetCounter() const { return HasEntry() @@ -283,12 +287,13 @@ public: ObjectGuidGeneratorBase(ObjectGuid::LowType start = 1) : _nextGuid(start) { } virtual void Set(ObjectGuid::LowType val) { _nextGuid = val; } - virtual ObjectGuid::LowType Generate() = 0; + virtual ObjectGuid::LowType Generate(uint16 realmId = DEFAULT_NON_CROSSREALM_REALM_ID) = 0; [[nodiscard]] ObjectGuid::LowType GetNextAfterMaxUsed() const { return _nextGuid; } virtual ~ObjectGuidGeneratorBase() = default; protected: static void HandleCounterOverflow(HighGuid high); + static bool GetClusterGuid(HighGuid high, uint16 realmId, ObjectGuid::LowType& clusterGuid); ObjectGuid::LowType _nextGuid; }; @@ -298,8 +303,12 @@ class ObjectGuidGenerator : public ObjectGuidGeneratorBase public: explicit ObjectGuidGenerator(ObjectGuid::LowType start = 1) : ObjectGuidGeneratorBase(start) { } - ObjectGuid::LowType Generate() override + ObjectGuid::LowType Generate(uint16 realmId = DEFAULT_NON_CROSSREALM_REALM_ID) override { + ObjectGuid::LowType clusterGuid = 0; + if (GetClusterGuid(high, realmId, clusterGuid)) + return clusterGuid; + if (_nextGuid >= ObjectGuid::GetMaxCounter(high) - 1) HandleCounterOverflow(high); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 5dc4d9ae9..aede63b5f 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -3783,7 +3783,7 @@ void Player::_LoadSpellCooldowns(PreparedQueryResult result) void Player::_SaveSpellCooldowns(CharacterDatabaseTransaction trans, bool logout) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_COOLDOWN); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); trans->Append(stmt); time_t curTime = GameTime::GetGameTime().count(); @@ -4770,7 +4770,7 @@ void Player::SpawnCorpseBones(bool triggerSave /*= true*/) // pussywizard: update only ghost flag instead of whole character table entry! data integrity is crucial CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_REMOVE_GHOST); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); trans->Append(stmt); _SaveAuras(trans, false); @@ -6455,7 +6455,7 @@ void Player::ModifyHonorPoints(int32 value, CharacterDatabaseTransaction trans) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_HONOR_POINTS); stmt->SetData(0, newValue); - stmt->SetData(1, GetGUID().GetCounter()); + stmt->SetData(1, GetGUID().GetRawValue()); trans->Append(stmt); } } @@ -6471,7 +6471,7 @@ void Player::ModifyArenaPoints(int32 value, CharacterDatabaseTransaction trans) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_ARENA_POINTS); stmt->SetData(0, newValue); - stmt->SetData(1, GetGUID().GetCounter()); + stmt->SetData(1, GetGUID().GetRawValue()); trans->Append(stmt); } } @@ -9324,7 +9324,7 @@ void Player::RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent) // Handle removing pet while it is in "temporarily unsummoned" state, for example on mount CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); stmt->SetData(0, PET_SAVE_NOT_IN_SLOT); - stmt->SetData(1, GetGUID().GetCounter()); + stmt->SetData(1, GetGUID().GetRawValue()); stmt->SetData(2, m_petStable->CurrentPet->PetNumber); CharacterDatabase.Execute(stmt); @@ -11502,7 +11502,7 @@ void Player::LeaveBattleground(Battleground* bg) if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_TRACK_DESERTERS)) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_DESERTER_TRACK); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, BG_DESERTION_TYPE_LEAVE_BG); CharacterDatabase.Execute(stmt); } @@ -14026,7 +14026,7 @@ void Player::_LoadSkills(PreparedQueryResult result) CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_SKILL); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, skill); CharacterDatabase.Execute(stmt); @@ -14971,7 +14971,7 @@ void Player::_SaveEquipmentSets(CharacterDatabaseTransaction trans) stmt->SetData(j++, eqset.IgnoreMask); for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i) stmt->SetData(j++, eqset.Items[i].GetCounter()); - stmt->SetData(j++, GetGUID().GetCounter()); + stmt->SetData(j++, GetGUID().GetRawValue()); stmt->SetData(j++, eqset.Guid); stmt->SetData(j, index); trans->Append(stmt); @@ -14980,7 +14980,7 @@ void Player::_SaveEquipmentSets(CharacterDatabaseTransaction trans) break; case EQUIPMENT_SET_NEW: stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_EQUIP_SET); - stmt->SetData(j++, GetGUID().GetCounter()); + stmt->SetData(j++, GetGUID().GetRawValue()); stmt->SetData(j++, eqset.Guid); stmt->SetData(j++, index); stmt->SetData(j++, eqset.Name.c_str()); @@ -15010,11 +15010,11 @@ void Player::_SaveEntryPoint(CharacterDatabaseTransaction trans) return; CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_ENTRY_POINT); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PLAYER_ENTRY_POINT); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData (1, m_entryPointData.joinPos.GetPositionX()); stmt->SetData (2, m_entryPointData.joinPos.GetPositionY()); stmt->SetData (3, m_entryPointData.joinPos.GetPositionZ()); @@ -15050,7 +15050,7 @@ void Player::RemoveAtLoginFlag(AtLoginFlags flags, bool persist /*= false*/) CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_REM_AT_LOGIN_FLAG); stmt->SetData(0, uint16(flags)); - stmt->SetData(1, GetGUID().GetCounter()); + stmt->SetData(1, GetGUID().GetRawValue()); CharacterDatabase.Execute(stmt); } @@ -15097,7 +15097,7 @@ void Player::_SaveCharacter(bool create, CharacterDatabaseTransaction trans) //! Insert query //! TO DO: Filter out more redundant fields that can take their default value at player create stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER); - stmt->SetData(index++, GetGUID().GetCounter()); + stmt->SetData(index++, GetGUID().GetRawValue()); stmt->SetData(index++, GetSession()->GetAccountId()); stmt->SetData(index++, GetName()); stmt->SetData(index++, getRace(true)); @@ -15355,7 +15355,7 @@ void Player::_SaveCharacter(bool create, CharacterDatabaseTransaction trans) stmt->SetData(index++, IsInWorld() && !GetSession()->PlayerLogout() ? 1 : 0); // Index - stmt->SetData(index++, GetGUID().GetCounter()); + stmt->SetData(index++, GetGUID().GetRawValue()); } trans->Append(stmt); @@ -15390,7 +15390,7 @@ void Player::_SaveGlyphs(CharacterDatabaseTransaction trans) return; CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_GLYPHS); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); trans->Append(stmt); for (uint8 spec = 0; spec < m_specsCount; ++spec) @@ -15398,7 +15398,7 @@ void Player::_SaveGlyphs(CharacterDatabaseTransaction trans) uint8 index = 0; stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_GLYPHS); - stmt->SetData(index++, GetGUID().GetCounter()); + stmt->SetData(index++, GetGUID().GetRawValue()); stmt->SetData(index++, spec); for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i) @@ -15445,7 +15445,7 @@ void Player::_SaveTalents(CharacterDatabaseTransaction trans) if (itr->second->State == PLAYERSPELL_REMOVED || itr->second->State == PLAYERSPELL_CHANGED) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT_BY_SPELL); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, itr->first); trans->Append(stmt); } @@ -15454,7 +15454,7 @@ void Player::_SaveTalents(CharacterDatabaseTransaction trans) if (itr->second->State == PLAYERSPELL_NEW || itr->second->State == PLAYERSPELL_CHANGED) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TALENT); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, itr->first); stmt->SetData(2, itr->second->specMask); trans->Append(stmt); @@ -15627,7 +15627,7 @@ void Player::ActivateSpec(uint8 spec) // load them asynchronously { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, m_activeSpec); WorldSession* mySess = GetSession(); @@ -16142,7 +16142,7 @@ void Player::SetRandomWinner(bool isWinner) if (m_IsBGRandomWinner) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_BATTLEGROUND_RANDOM); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); CharacterDatabase.Execute(stmt); } } @@ -16259,7 +16259,7 @@ void Player::_LoadBrewOfTheMonth(PreparedQueryResult result) // Update Event Id CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_BREW_OF_THE_MONTH); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, uint32(eventId)); trans->Append(stmt); diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index 8def7f56d..7083bfbd3 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -4978,7 +4978,7 @@ void Player::SetHomebind(WorldLocation const& loc, uint32 areaId) stmt->SetData (2, m_homebindX); stmt->SetData (3, m_homebindY); stmt->SetData (4, m_homebindZ); - stmt->SetData(5, GetGUID().GetCounter()); + stmt->SetData(5, GetGUID().GetRawValue()); CharacterDatabase.Execute(stmt); } @@ -5027,9 +5027,9 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons return false; } - ObjectGuid::LowType guid = playerGuid.GetCounter(); + uint64 guid = playerGuid.GetRawValue(); - Object::_Create(guid, 0, HighGuid::Player); + Object::_Create(playerGuid); m_name = fields[2].Get(); @@ -6101,7 +6101,7 @@ Item* Player::_LoadItem(CharacterDatabaseTransaction trans, uint32 zoneId, uint3 // xinef: sync query stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEM_REFUNDS); stmt->SetData(0, item->GetGUID().GetCounter()); - stmt->SetData(1, GetGUID().GetCounter()); + stmt->SetData(1, GetGUID().GetRawValue()); if (PreparedQueryResult result = CharacterDatabase.Query(stmt)) { item->SetRefundRecipient((*result)[0].Get()); @@ -7152,7 +7152,7 @@ bool Player::_LoadHomeBind(PreparedQueryResult result) else { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_HOMEBIND); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); CharacterDatabase.Execute(stmt); } } @@ -7166,7 +7166,7 @@ bool Player::_LoadHomeBind(PreparedQueryResult result) m_homebindZ = info->positionZ; CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PLAYER_HOMEBIND); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, m_homebindMapId); stmt->SetData(2, m_homebindAreaId); stmt->SetData (3, m_homebindX); @@ -7265,7 +7265,7 @@ void Player::SaveGoldToDB(CharacterDatabaseTransaction trans) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_MONEY); stmt->SetData(0, GetMoney()); - stmt->SetData(1, GetGUID().GetCounter()); + stmt->SetData(1, GetGUID().GetRawValue()); trans->Append(stmt); } @@ -7279,7 +7279,7 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans) { case ACTIONBUTTON_NEW: stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_ACTION); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, m_activeSpec); stmt->SetData(2, itr->first); stmt->SetData(3, itr->second.GetAction()); @@ -7293,7 +7293,7 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans) stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_ACTION); stmt->SetData(0, itr->second.GetAction()); stmt->SetData(1, uint8(itr->second.GetType())); - stmt->SetData(2, GetGUID().GetCounter()); + stmt->SetData(2, GetGUID().GetRawValue()); stmt->SetData(3, itr->first); stmt->SetData(4, m_activeSpec); trans->Append(stmt); @@ -7303,7 +7303,7 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans) break; case ACTIONBUTTON_DELETED: stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION_BY_BUTTON_SPEC); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, itr->first); stmt->SetData(2, m_activeSpec); trans->Append(stmt); @@ -7320,7 +7320,7 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans) void Player::_SaveAuras(CharacterDatabaseTransaction trans, bool logout) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_AURA); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); trans->Append(stmt); for (AuraMap::const_iterator itr = m_ownedAuras.begin(); itr != m_ownedAuras.end(); ++itr) @@ -7355,7 +7355,7 @@ void Player::_SaveAuras(CharacterDatabaseTransaction trans, bool logout) uint8 index = 0; stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AURA); - stmt->SetData(index++, GetGUID().GetCounter()); + stmt->SetData(index++, GetGUID().GetRawValue()); stmt->SetData(index++, itr->second->GetCasterGUID().GetRawValue()); stmt->SetData(index++, itr->second->GetCastItemGUID().GetRawValue()); stmt->SetData(index++, itr->second->GetId()); @@ -7440,7 +7440,7 @@ void Player::_SaveInventory(CharacterDatabaseTransaction trans) if (m_itemUpdateQueue.empty()) return; - ObjectGuid::LowType lowGuid = GetGUID().GetCounter(); + uint64 guid = GetGUID().GetRawValue(); for (std::size_t i = 0; i < m_itemUpdateQueue.size(); ++i) { Item* item = m_itemUpdateQueue[i]; @@ -7459,12 +7459,12 @@ void Player::_SaveInventory(CharacterDatabaseTransaction trans) if (Item* test2 = GetItemByPos(INVENTORY_SLOT_BAG_0, item->GetBagSlot())) bagTestGUID = test2->GetGUID().GetCounter(); LOG_ERROR("entities.player", "Player(GUID: {} Name: {})::_SaveInventory - the bag({}) and slot({}) values for the item {} (state {}) are incorrect, the player doesn't have an item at that position!", - lowGuid, GetName(), item->GetBagSlot(), item->GetSlot(), item->GetGUID().ToString(), (int32)item->GetState()); + guid, GetName(), item->GetBagSlot(), item->GetSlot(), item->GetGUID().ToString(), (int32)item->GetState()); // according to the test that was just performed nothing should be in this slot, delete stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_BAG_SLOT); stmt->SetData(0, bagTestGUID); stmt->SetData(1, item->GetSlot()); - stmt->SetData(2, lowGuid); + stmt->SetData(2, guid); trans->Append(stmt); RemoveTradeableItem(item); // pussywizard @@ -7480,7 +7480,7 @@ void Player::_SaveInventory(CharacterDatabaseTransaction trans) else if (test != item) { LOG_ERROR("entities.player", "Player(GUID: {} Name: {})::_SaveInventory - the bag({}) and slot({}) values for the item ({}) are incorrect, the item ({}) is there instead!", - lowGuid, GetName(), item->GetBagSlot(), item->GetSlot(), item->GetGUID().ToString(), test->GetGUID().ToString()); + guid, GetName(), item->GetBagSlot(), item->GetSlot(), item->GetGUID().ToString(), test->GetGUID().ToString()); // save all changes to the item... if (item->GetState() != ITEM_NEW) // only for existing items, no dupes item->SaveToDB(trans); @@ -7494,7 +7494,7 @@ void Player::_SaveInventory(CharacterDatabaseTransaction trans) case ITEM_NEW: case ITEM_CHANGED: stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_INVENTORY_ITEM); - stmt->SetData(0, lowGuid); + stmt->SetData(0, guid); stmt->SetData(1, bag_guid); stmt->SetData (2, item->GetSlot()); stmt->SetData(3, item->GetGUID().GetCounter()); @@ -7610,7 +7610,7 @@ void Player::_SaveQuestStatus(CharacterDatabaseTransaction trans) uint8 index = 0; stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_QUESTSTATUS); - stmt->SetData(index++, GetGUID().GetCounter()); + stmt->SetData(index++, GetGUID().GetRawValue()); stmt->SetData(index++, statusItr->first); stmt->SetData(index++, uint8(statusItr->second.Status)); stmt->SetData(index++, statusItr->second.Explored); @@ -7629,7 +7629,7 @@ void Player::_SaveQuestStatus(CharacterDatabaseTransaction trans) else { stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_BY_QUEST); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, saveItr->first); trans->Append(stmt); } @@ -7644,7 +7644,7 @@ void Player::_SaveQuestStatus(CharacterDatabaseTransaction trans) else // xinef: what the is this? quest can be removed by spelleffect if (!keepAbandoned) stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, saveItr->first); trans->Append(stmt); } @@ -7666,14 +7666,14 @@ void Player::_SaveDailyQuestStatus(CharacterDatabaseTransaction trans) // we don't need transactions here. CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_STATUS_DAILY_CHAR); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); trans->Append(stmt); for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx) { if (GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx)) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_DAILYQUESTSTATUS); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx)); stmt->SetData(2, uint64(m_lastDailyQuestTime)); trans->Append(stmt); @@ -7685,7 +7685,7 @@ void Player::_SaveDailyQuestStatus(CharacterDatabaseTransaction trans) for (DFQuestsDoneList::iterator itr = m_DFQuests.begin(); itr != m_DFQuests.end(); ++itr) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_DAILYQUESTSTATUS); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, (*itr)); stmt->SetData(2, uint64(m_lastDailyQuestTime)); trans->Append(stmt); @@ -7700,7 +7700,7 @@ void Player::_SaveWeeklyQuestStatus(CharacterDatabaseTransaction trans) // we don't need transactions here. CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_STATUS_WEEKLY_CHAR); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); trans->Append(stmt); for (QuestSet::const_iterator iter = m_weeklyquests.begin(); iter != m_weeklyquests.end(); ++iter) @@ -7708,7 +7708,7 @@ void Player::_SaveWeeklyQuestStatus(CharacterDatabaseTransaction trans) uint32 quest_id = *iter; stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_WEEKLYQUESTSTATUS); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, quest_id); trans->Append(stmt); } @@ -7725,7 +7725,7 @@ void Player::_SaveSeasonalQuestStatus(CharacterDatabaseTransaction trans) // we don't need transactions here. CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_STATUS_SEASONAL_CHAR); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); trans->Append(stmt); m_SeasonalQuestChanged = false; @@ -7744,7 +7744,7 @@ void Player::_SaveSeasonalQuestStatus(CharacterDatabaseTransaction trans) uint32 questId = *itr; stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_SEASONALQUESTSTATUS); - stmt->SetArguments(GetGUID().GetCounter(), questId, eventId); + stmt->SetArguments(GetGUID().GetRawValue(), questId, eventId); trans->Append(stmt); } } @@ -7757,14 +7757,14 @@ void Player::_SaveMonthlyQuestStatus(CharacterDatabaseTransaction trans) // we don't need transactions here. CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_STATUS_MONTHLY_CHAR); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); trans->Append(stmt); for (QuestSet::const_iterator iter = m_monthlyquests.begin(); iter != m_monthlyquests.end(); ++iter) { uint32 quest_id = *iter; stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_MONTHLYQUESTSTATUS); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, quest_id); trans->Append(stmt); } @@ -7787,7 +7787,7 @@ void Player::_SaveSkills(CharacterDatabaseTransaction trans) if (itr->second.uState == SKILL_DELETED) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SKILL_BY_SKILL); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, itr->first); trans->Append(stmt); @@ -7803,7 +7803,7 @@ void Player::_SaveSkills(CharacterDatabaseTransaction trans) { case SKILL_NEW: stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_SKILLS); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, uint16(itr->first)); stmt->SetData(2, value); stmt->SetData(3, max); @@ -7814,7 +7814,7 @@ void Player::_SaveSkills(CharacterDatabaseTransaction trans) stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_SKILLS); stmt->SetData(0, value); stmt->SetData(1, max); - stmt->SetData(2, GetGUID().GetCounter()); + stmt->SetData(2, GetGUID().GetRawValue()); stmt->SetData(3, uint16(itr->first)); trans->Append(stmt); @@ -7845,7 +7845,7 @@ void Player::_SaveSpells(CharacterDatabaseTransaction trans) if (itr->second->State == PLAYERSPELL_REMOVED || itr->second->State == PLAYERSPELL_CHANGED) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_BY_SPELL); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, itr->first); trans->Append(stmt); } @@ -7854,7 +7854,7 @@ void Player::_SaveSpells(CharacterDatabaseTransaction trans) if (itr->second->State == PLAYERSPELL_NEW || itr->second->State == PLAYERSPELL_CHANGED) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_SPELL); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); stmt->SetData(1, itr->first); stmt->SetData(2, itr->second->specMask); trans->Append(stmt); @@ -7884,13 +7884,13 @@ void Player::_SaveStats(CharacterDatabaseTransaction trans) CharacterDatabasePreparedStatement* stmt = nullptr; stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_STATS); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, GetGUID().GetRawValue()); trans->Append(stmt); uint8 index = 0; stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_STATS); - stmt->SetData(index++, GetGUID().GetCounter()); + stmt->SetData(index++, GetGUID().GetRawValue()); stmt->SetData(index++, GetMaxHealth()); for (uint8 i = 0; i < MAX_POWERS; ++i) diff --git a/src/server/game/Entities/Transport/Transport.cpp b/src/server/game/Entities/Transport/Transport.cpp index 0bbe0fae6..552fd4432 100644 --- a/src/server/game/Entities/Transport/Transport.cpp +++ b/src/server/game/Entities/Transport/Transport.cpp @@ -19,6 +19,7 @@ #include "Cell.h" #include "CellImpl.h" #include "Common.h" +#include "Config.h" #include "DBCStores.h" #include "GameObjectAI.h" #include "GameTime.h" @@ -30,8 +31,28 @@ #include "Spell.h" #include "Vehicle.h" #include "WorldModel.h" +#include -MotionTransport::MotionTransport() : Transport(), _transportInfo(nullptr), _isMoving(true), _pendingStop(false), _triggeredArrivalEvent(false), _triggeredDepartureEvent(false), _passengersLoaded(false), _delayedTeleport(false) +namespace +{ + // Any fixed reference epoch works; this one is 2023-07-12 05:20:00 UTC. + std::time_t const startTimestamp = 1689139200; + std::chrono::system_clock::time_point const transportStartDate = std::chrono::system_clock::from_time_t(startTimestamp); + + // Calculates time of the next departure cycle. + std::chrono::system_clock::time_point calculateNextDepartureTime(int oneIterationInterval) + { + std::chrono::system_clock::time_point currentTime = std::chrono::system_clock::now(); + std::chrono::milliseconds interval(oneIterationInterval); + std::chrono::milliseconds timeSinceStart = std::chrono::duration_cast(currentTime - transportStartDate); + int64 intervalsPassed = timeSinceStart.count() / oneIterationInterval; + std::chrono::system_clock::time_point nextDeparture = transportStartDate + (interval * (intervalsPassed + 1)); + return nextDeparture; + } +} + +MotionTransport::MotionTransport() : Transport(), _transportInfo(nullptr), _isMoving(true), _pendingStop(false), _triggeredArrivalEvent(false), _triggeredDepartureEvent(false), _passengersLoaded(false), _delayedTeleport(false), + _requiresFirstDepartureSync(false), _firstDepartureTime(transportStartDate) { m_updateFlag = UPDATEFLAG_TRANSPORT | UPDATEFLAG_LOWGUID | UPDATEFLAG_STATIONARY_POSITION | UPDATEFLAG_ROTATION; } @@ -76,6 +97,13 @@ bool MotionTransport::CreateMoTrans(ObjectGuid::LowType guidlow, uint32 entry, u _transportInfo = tInfo; + // Enable transport sync only in cluster mode and for transport with several maps. + if (sConfigMgr->GetOption("Cluster.Enabled", false) && tInfo->mapsUsed.size() > 1) + { + _requiresFirstDepartureSync = true; + this->SetPhaseMask(2, true); + } + // initialize waypoints _nextFrame = tInfo->keyFrames.begin(); _currentFrame = _nextFrame++; @@ -108,6 +136,56 @@ bool MotionTransport::CreateMoTrans(ObjectGuid::LowType guidlow, uint32 entry, u return true; } +// Delays first departure to make sure that transport follows strict schedule +// and with that makes transport synced between cluster nodes. +uint32 MotionTransport::HandleFirstDepartureSync(uint32 diff) +{ + if (!_requiresFirstDepartureSync) + return diff; + + if (_firstDepartureTime == transportStartDate) + { + _firstDepartureTime = calculateNextDepartureTime(_transportInfo->pathTime); + return diff; + } + + // Making system call for current time to be more accurate. + // Shouldn't be an issue since it runs only before the very first departure. + int32 millLeftToDeparture = std::chrono::duration_cast(_firstDepartureTime - std::chrono::system_clock::now()).count(); + if (millLeftToDeparture > 0) + return diff; + + // At this point we are ready for first departure. + _requiresFirstDepartureSync = false; + this->SetPhaseMask(1, true); + + // Players don't know about transport because it was in a different phase. + // We need to notify players that object exists before departure. + Map::PlayerList const& players = this->GetMap()->GetPlayers(); + if (!players.IsEmpty()) + { + for (Map::PlayerList::const_iterator i = players.begin(); i != players.end(); ++i) + { + if (Player* player = i->GetSource()) + { + // Same phase filter as Map::SendInitTransports: players outside + // the transport's phase must not get a create-block for it. + if (!player->InSamePhase(this)) + continue; + + UpdateData transData; + this->BuildCreateUpdateBlockForPlayer(&transData, player); + + WorldPacket packet; + transData.BuildPacket(packet); + player->SendDirectMessage(&packet); + } + } + } + + return millLeftToDeparture * -1; +} + void MotionTransport::CleanupsBeforeDelete(bool finalCleanup /*= true*/) { UnloadStaticPassengers(); @@ -137,6 +215,13 @@ void MotionTransport::BuildUpdate(UpdateDataMapType& data_map) void MotionTransport::Update(uint32 diff) { + if (_requiresFirstDepartureSync) + { + diff = HandleFirstDepartureSync(diff); + if (_requiresFirstDepartureSync) + return; + } + uint32 const positionUpdateDelay = 1; if (AI()) diff --git a/src/server/game/Entities/Transport/Transport.h b/src/server/game/Entities/Transport/Transport.h index 8287699df..dfdf094db 100644 --- a/src/server/game/Entities/Transport/Transport.h +++ b/src/server/game/Entities/Transport/Transport.h @@ -90,6 +90,8 @@ private: void UpdatePassengerPositions(PassengerSet& passengers); void DoEventIfAny(KeyFrame const& node, bool departure); + uint32 HandleFirstDepartureSync(uint32 diff); + //! Helpers to know if stop frame was reached bool IsMoving() const { return _isMoving; } void SetMoving(bool val) { _isMoving = val; } @@ -109,6 +111,9 @@ private: mutable std::mutex Lock; bool _passengersLoaded; bool _delayedTeleport; + + bool _requiresFirstDepartureSync; + std::chrono::system_clock::time_point _firstDepartureTime; }; class StaticTransport : public Transport diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index a194f57de..4da913a96 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -34,6 +34,7 @@ #include "Player.h" #include "ScriptMgr.h" #include "SharedDefines.h" +#include "TC9Sidecar.h" #include "UpdateFieldFlags.h" #include "Util.h" #include "World.h" @@ -329,7 +330,7 @@ void Group::ConvertToRaid() _initRaidSubGroupsCounter(); - if (!isBGGroup() && !isBFGroup()) + if (!sToCloud9Sidecar->ClusterModeEnabled() && !isBGGroup() && !isBFGroup()) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_TYPE); @@ -577,6 +578,52 @@ bool Group::AddMember(Player* player, uint8 roles /* = 0 */) return true; } +void Group::AddMemberWithGuid(ObjectGuid guid) +{ + // Idempotent under sidecar event redelivery: never duplicate a MemberSlot. + if (IsMember(guid)) + return; + + if (Player* player = ObjectAccessor::FindPlayer(guid)) + { + AddMember(player); + return; + } + + // Get first not-full group + uint8 subGroup = 0; + if (m_subGroupsCounts) + { + bool groupFound = false; + for (; subGroup < MAX_RAID_SUBGROUPS; ++subGroup) + { + if (m_subGroupsCounts[subGroup] < MAXGROUPSIZE) + { + groupFound = true; + break; + } + } + // We are raid group and no one slot is free + if (!groupFound) + return; + } + + MemberSlot member; + member.guid = guid; + sCharacterCache->GetCharacterNameByGuid(guid, member.name); + member.group = subGroup; + member.flags = 0; + member.roles = 0; + m_memberSlots.push_back(member); + + if (!isBGGroup() && !isBFGroup()) + { + sCharacterCache->UpdateCharacterGroup(guid, GetGUID()); + } + + SubGroupCounterIncrease(subGroup); +} + bool Group::RemoveMember(ObjectGuid guid, RemoveMethod const& method /*= GROUP_REMOVEMETHOD_DEFAULT*/, ObjectGuid kicker /*= ObjectGuid::Empty*/, char const* reason /*= nullptr*/) { BroadcastGroupUpdate(); @@ -588,8 +635,14 @@ bool Group::RemoveMember(ObjectGuid guid, RemoveMethod const& method /*= GROUP_R return m_memberSlots.size() > 0; } - // remove member and change leader (if need) only if strong more 2 members _before_ member remove (BG/BF allow 1 member group) - if (GetMembersCount() > ((isBGGroup() || isLFGGroup() || isBFGroup()) ? 1u : 2u)) + // remove member and change leader (if need) only if strong more 2 members _before_ member remove (BG/BF allow 1 member group), + // except in cluster mode, where this branch is taken for any size: the group + // service owns the group lifecycle and local Disband() is a no-op, so always + // unlink the removed member here; waiting for the disband event leaves the + // last two members with a dangling group pointer whenever that event is lost + // (group service restart). + if (GetMembersCount() > ((isBGGroup() || isLFGGroup() || isBFGroup()) ? 1u : 2u) + || (sToCloud9Sidecar->ClusterModeEnabled() && !isBGGroup() && !isBFGroup())) { Player* player = ObjectAccessor::FindConnectedPlayer(guid); if (player) @@ -609,19 +662,24 @@ bool Group::RemoveMember(ObjectGuid guid, RemoveMethod const& method /*= GROUP_R player->UpdateForQuestWorldObjects(); } - WorldPacket data; - - if (method == GROUP_REMOVEMETHOD_KICK || method == GROUP_REMOVEMETHOD_KICK_LFG) + // BG/BF groups stay locally owned in cluster mode (see the gates above), + // so their removal packets must not be delegated to the group service. + if (!sToCloud9Sidecar->ClusterModeEnabled() || isBGGroup() || isBFGroup()) { - data.Initialize(SMSG_GROUP_UNINVITE, 0); - player->SendDirectMessage(&data); - } + WorldPacket data; - // Do we really need to send this opcode? - data.Initialize(SMSG_GROUP_LIST, 1 + 1 + 1 + 1 + 8 + 4 + 4 + 8); - data << uint8(0x10) << uint8(0) << uint8(0) << uint8(0); - data << m_guid << uint32(m_counter) << uint32(0) << uint64(0); - player->SendDirectMessage(&data); + if (method == GROUP_REMOVEMETHOD_KICK || method == GROUP_REMOVEMETHOD_KICK_LFG) + { + data.Initialize(SMSG_GROUP_UNINVITE, 0); + player->GetSession()->SendPacket(&data); + } + + // Do we really need to send this opcode? + data.Initialize(SMSG_GROUP_LIST, 1 + 1 + 1 + 1 + 8 + 4 + 4 + 8); + data << uint8(0x10) << uint8(0) << uint8(0) << uint8(0); + data << m_guid << uint32(m_counter) << uint32(0) << uint64(0); + player->GetSession()->SendPacket(&data); + } } // Remove player from group in DB @@ -756,7 +814,7 @@ void Group::ChangeLeader(ObjectGuid newLeaderGuid) sScriptMgr->OnGroupChangeLeader(this, newLeaderGuid, m_leaderGuid); // This hook should be executed at the end - Not used anywhere in the original core } -void Group::Disband(bool hideDestroy /* = false */) +void Group::ForcedDisband(bool hideDestroy /* = false */) { sScriptMgr->OnGroupDisband(this); @@ -852,6 +910,14 @@ void Group::Disband(bool hideDestroy /* = false */) delete this; } +void Group::Disband(bool hideDestroy /* = false */) +{ + if (sToCloud9Sidecar->ClusterModeEnabled() && !this->isBFGroup() && !this->isBGGroup()) + return; + + ForcedDisband(hideDestroy); +} + /*********************************************************/ /*** LOOT SYSTEM ***/ /*********************************************************/ @@ -1791,6 +1857,12 @@ void Group::SendTargetIconList(WorldSession* session) void Group::SendUpdate() { + if (sToCloud9Sidecar->ClusterModeEnabled() && !this->isBFGroup() && !this->isBGGroup()) + { + // Group service responsible for sending these updates. + return; + } + for (member_witerator witr = m_memberSlots.begin(); witr != m_memberSlots.end(); ++witr) SendUpdateToPlayer(witr->guid, &(*witr)); } @@ -2207,7 +2279,7 @@ void Roll::targetObjectBuildLink() void Group::SetDungeonDifficulty(Difficulty difficulty) { m_dungeonDifficulty = difficulty; - if (!isBGGroup() && !isBFGroup()) + if (!sToCloud9Sidecar->ClusterModeEnabled() && !isBGGroup() && !isBFGroup()) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_DIFFICULTY); @@ -2228,7 +2300,7 @@ void Group::SetDungeonDifficulty(Difficulty difficulty) void Group::SetRaidDifficulty(Difficulty difficulty) { m_raidDifficulty = difficulty; - if (!isBGGroup() && !isBFGroup()) + if (!sToCloud9Sidecar->ClusterModeEnabled() && !isBGGroup() && !isBFGroup()) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_RAID_DIFFICULTY); diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h index 50c51c451..32da01291 100644 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -37,6 +37,7 @@ class Unit; class WorldObject; class WorldPacket; class WorldSession; +class ToCloud9GroupHooks; struct MapEntry; @@ -174,6 +175,7 @@ public: /** todo: uninvite people that not accepted invite **/ class Group { + friend class ToCloud9GroupHooks; public: struct MemberSlot { @@ -344,6 +346,9 @@ protected: void SubGroupCounterDecrease(uint8 subgroup); void ToggleGroupMemberFlag(member_witerator slot, uint8 flag, bool apply); + void AddMemberWithGuid(ObjectGuid guid); + void ForcedDisband(bool hideDestroy = false); + MemberSlotList m_memberSlots; GroupRefMgr m_memberMgr; InvitesList m_invitees; diff --git a/src/server/game/Groups/GroupMgr.cpp b/src/server/game/Groups/GroupMgr.cpp index 4ce847538..c4bf0e173 100644 --- a/src/server/game/Groups/GroupMgr.cpp +++ b/src/server/game/Groups/GroupMgr.cpp @@ -53,7 +53,11 @@ void GroupMgr::InitGroupIds() void GroupMgr::RegisterGroupId(ObjectGuid::LowType groupId) { - // Allocation was done in InitGroupIds() + // InitGroupIds() sizes the bitmap to the local MAX(guid) at startup. In cluster mode the + // group service assigns ids (auto-increment), which can exceed that bound, so grow on demand. + if (groupId >= _groupIds.size()) + _groupIds.resize(groupId + 1); + _groupIds[groupId] = true; // Groups are pulled in ascending order from db and _nextGroupId is initialized with 1, diff --git a/src/server/game/Handlers/ChannelHandler.cpp b/src/server/game/Handlers/ChannelHandler.cpp index 2193daaab..9319929b7 100644 --- a/src/server/game/Handlers/ChannelHandler.cpp +++ b/src/server/game/Handlers/ChannelHandler.cpp @@ -18,6 +18,8 @@ #include "ChannelMgr.h" #include "ObjectMgr.h" // for normalizePlayerName #include "Player.h" +#include "Language.h" +#include "TC9Sidecar.h" #include void WorldSession::HandleJoinChannel(WorldPacket& recvPacket) @@ -38,6 +40,23 @@ void WorldSession::HandleJoinChannel(WorldPacket& recvPacket) AreaTableEntry const* zone = sAreaTableStore.LookupEntry(GetPlayer()->GetZoneId()); if (!zone || !GetPlayer()->CanJoinConstantChannelInZone(channel, zone)) return; + + // Cluster mode rebuilds the localized channel name so nodes agree on it; + // stock servers keep the client-supplied name. + if (sToCloud9Sidecar->ClusterModeEnabled()) + { + auto const locale = GetSessionDbcLocale(); + std::string const& zoneName = zone->area_name[locale]; + std::string const cityName = sObjectMgr->GetAcoreStringForDBCLocale(LANG_CHANNEL_CITY); + char const* nameExt = (channel->flags & CHANNEL_DBC_FLAG_CITY_ONLY) ? cityName.c_str() : zoneName.c_str(); + + std::array buffer{}; + if (char const* pattern = channel->pattern[locale]) + { + std::snprintf(buffer.data(), buffer.size(), pattern, nameExt); + channelName = buffer.data(); + } + } } if (channelName.empty()) diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 48ed07edc..ec41e893f 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -54,6 +54,7 @@ #include "SpellAuraEffects.h" #include "SpellAuras.h" #include "StringConvert.h" +#include "TC9Sidecar.h" #include "Tokenize.h" #include "Transport.h" #include "Util.h" @@ -81,125 +82,126 @@ bool LoginQueryHolder::Initialize() SetSize(MAX_PLAYER_LOGIN_QUERY); bool res = true; - ObjectGuid::LowType lowGuid = m_guid.GetCounter(); + uint64 rawGUID = m_guid.GetRawValue(); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_FROM, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_AURAS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_AURAS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SPELL); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_SPELLS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_QUESTSTATUS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_DAILYQUESTSTATUS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_DAILY_QUEST_STATUS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_WEEKLYQUESTSTATUS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_WEEKLY_QUEST_STATUS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_MONTHLYQUESTSTATUS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_MONTHLY_QUEST_STATUS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SEASONALQUESTSTATUS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_SEASONAL_QUEST_STATUS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_REPUTATION); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_REPUTATION, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_INVENTORY); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_INVENTORY, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACTIONS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ACTIONS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAIL); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_MAILS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SOCIALLIST); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_SOCIAL_LIST, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_HOMEBIND); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_HOME_BIND, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SPELLCOOLDOWNS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_SPELL_COOLDOWNS, stmt); if (sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED)) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_DECLINEDNAMES); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_DECLINED_NAMES, stmt); } stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACHIEVEMENTS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ACHIEVEMENTS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_CRITERIAPROGRESS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CRITERIA_PROGRESS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_EQUIPMENTSETS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_EQUIPMENT_SETS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ENTRY_POINT); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ENTRY_POINT, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_GLYPHS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_GLYPHS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_TALENTS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_TALENTS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PLAYER_ACCOUNT_DATA); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ACCOUNT_DATA, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SKILLS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_SKILLS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_RANDOMBG); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_RANDOM_BG, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_BANNED); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_BANNED, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_QUESTSTATUSREW); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_REW, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_BREW_OF_THE_MONTH); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_BREW_OF_THE_MONTH, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ACCOUNT_INSTANCELOCKTIMES); @@ -207,19 +209,19 @@ bool LoginQueryHolder::Initialize() res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_INSTANCE_LOCK_TIMES, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CORPSE_LOCATION); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_SETTINGS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CHARACTER_SETTINGS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PETS); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_ACHIEVEMENT_OFFLINE_UPDATES); - stmt->SetData(0, lowGuid); + stmt->SetData(0, rawGUID); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_OFFLINE_ACHIEVEMENTS_UPDATES, stmt); return res; @@ -699,7 +701,9 @@ void WorldSession::HandlePlayerLoginOpcode(WorldPacket& recvData) ObjectGuid playerGuid; recvData >> playerGuid; - if (!IsLegitCharacterForAccount(playerGuid)) + // The ownership check is delegated to the gateway in cluster mode, but a + // non-player GUID is invalid regardless of who authenticated the session. + if (!playerGuid.IsPlayer() || (!sToCloud9Sidecar->ClusterModeEnabled() && !IsLegitCharacterForAccount(playerGuid))) { LOG_ERROR("network", "Account ({}) can't login with that character ({}).", GetAccountId(), playerGuid.ToString()); KickPlayer("Account can't login with this character"); @@ -847,20 +851,28 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) chH.PSendSysMessage("{}", GitRevision::GetFullVersion()); } - if (uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(pCurrChar->GetGUID())) + if (!sToCloud9Sidecar->ClusterModeEnabled()) { - Guild* guild = sGuildMgr->GetGuildById(guildId); - Guild::Member const* member = guild ? guild->GetMember(pCurrChar->GetGUID()) : nullptr; - if (member) + if (uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(pCurrChar->GetGUID())) { - pCurrChar->SetInGuild(guildId); - pCurrChar->SetRank(member->GetRankId()); - guild->SendLoginInfo(this); + Guild* guild = sGuildMgr->GetGuildById(guildId); + Guild::Member const* member = guild ? guild->GetMember(pCurrChar->GetGUID()) : nullptr; + if (member) + { + pCurrChar->SetInGuild(guildId); + pCurrChar->SetRank(member->GetRankId()); + guild->SendLoginInfo(this); + } + else + { + LOG_ERROR("network.opcode", "Player {} ({}) marked as member of not existing guild (id: {}), removing guild membership for player.", + pCurrChar->GetName(), pCurrChar->GetGUID().ToString(), guildId); + pCurrChar->SetInGuild(0); + pCurrChar->SetRank(0); + } } else { - LOG_ERROR("network.opcode", "Player {} ({}) marked as member of not existing guild (id: {}), removing guild membership for player.", - pCurrChar->GetName(), pCurrChar->GetGUID().ToString(), guildId); pCurrChar->SetInGuild(0); pCurrChar->SetRank(0); } @@ -914,9 +926,12 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) pCurrChar->SendInitialPacketsAfterAddToMap(); - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_ONLINE); - stmt->SetData(0, pCurrChar->GetGUID().GetCounter()); - CharacterDatabase.Execute(stmt); + if (!sToCloud9Sidecar->ClusterModeEnabled()) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_ONLINE); + stmt->SetData(0, pCurrChar->GetGUID().GetCounter()); + CharacterDatabase.Execute(stmt); + } LoginDatabasePreparedStatement* loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_ONLINE); loginStmt->SetData(0, realm.Id.Realm); diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index a9c9acb71..8adb05144 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -713,6 +713,18 @@ namespace Acore void WorldSession::HandleTextEmoteOpcode(WorldPacket& recvData) { + uint32 text_emote; + recvData >> text_emote; + + constexpr uint32 readyEmote = 126; + + // Handle confirmation redirect when the player types "/ready" after the new node becomes available for redirection. + if (text_emote == readyEmote && GetPlayer()->GetMap()->IsPlayerRedirectKickTimerActive()) + { + HandleTC9PrepareForRedirect(recvData); + return; + } + if (!GetPlayer()->IsAlive()) return; @@ -728,10 +740,9 @@ void WorldSession::HandleTextEmoteOpcode(WorldPacket& recvData) if (GetPlayer()->IsSpectator()) return; - uint32 text_emote, emoteNum; + uint32 emoteNum; ObjectGuid guid; - recvData >> text_emote; recvData >> emoteNum; recvData >> guid; diff --git a/src/server/game/Handlers/TradeHandler.cpp b/src/server/game/Handlers/TradeHandler.cpp index ef7590f65..991d6211c 100644 --- a/src/server/game/Handlers/TradeHandler.cpp +++ b/src/server/game/Handlers/TradeHandler.cpp @@ -27,6 +27,7 @@ #include "SocialMgr.h" #include "Spell.h" #include "SpellMgr.h" +#include "TC9Sidecar.h" #include "World.h" #include "WorldPacket.h" #include "WorldSession.h" @@ -505,6 +506,79 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/) } } + bool needsCrossrealmHandling = sToCloud9Sidecar->IsCrossrealm() && _player->GetGUID().GetRealmID() != trader->GetGUID().GetRealmID(); + + // Create new items for crossrealm usage. + if (needsCrossrealmHandling) + { + CharacterDatabasePreparedStatement* stmt = nullptr; + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_NO_OP_PROVIDE_REALM_CONTEXT); + stmt->SetData(0, _player->GetGUID().GetRealmID()); + trans->Append(stmt); + + for (uint8 i = 0; i < TRADE_SLOT_TRADED_COUNT; i++) + { + if (myItems[i]) + { + Item* newItem = myItems[i]->CloneItem(myItems[i]->GetCount(), trader); + if (!newItem) + { + ItemPosCountVec playerDst; + if (_player->CanStoreItem(NULL_BAG, NULL_SLOT, playerDst, myItems[i], false) == EQUIP_ERR_OK) + _player->MoveItemToInventory(playerDst, myItems[i], true, true); + + // Should we handle else statement here? + + continue; + } + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->SetData(0, myItems[i]->GetGUID().GetCounter()); + trans->Append(stmt); + + delete myItems[i]; + + myItems[i] = newItem; + } + } + CharacterDatabase.CommitTransaction(trans); + + trans = CharacterDatabase.BeginTransaction(); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_NO_OP_PROVIDE_REALM_CONTEXT); + stmt->SetData(0, trader->GetGUID().GetRealmID()); + trans->Append(stmt); + + for (uint8 i = 0; i < TRADE_SLOT_TRADED_COUNT; i++) + { + if (hisItems[i]) + { + Item* newItem = hisItems[i]->CloneItem(hisItems[i]->GetCount(), _player); + if (!newItem) + { + ItemPosCountVec playerDst; + if (trader->CanStoreItem(NULL_BAG, NULL_SLOT, playerDst, hisItems[i], false) == EQUIP_ERR_OK) + trader->MoveItemToInventory(playerDst, hisItems[i], true, true); + + // Should we handle else statement here? + + continue; + } + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->SetData(0, hisItems[i]->GetGUID().GetCounter()); + trans->Append(stmt); + + delete hisItems[i]; + + hisItems[i] = newItem; + } + } + CharacterDatabase.CommitTransaction(trans); + + } + // execute trade: 2. store moveItems(myItems, hisItems); @@ -575,11 +649,25 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/) delete trader->m_trade; trader->m_trade = nullptr; - // desynchronized with the other saves here (SaveInventoryAndGoldToDB() not have own transaction guards) - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - _player->SaveInventoryAndGoldToDB(trans); - trader->SaveInventoryAndGoldToDB(trans); - CharacterDatabase.CommitTransaction(trans); + // We can't use single transaction with different databases. + if (needsCrossrealmHandling) + { + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + _player->SaveInventoryAndGoldToDB(trans); + CharacterDatabase.CommitTransaction(trans); + + trans = CharacterDatabase.BeginTransaction(); + trader->SaveInventoryAndGoldToDB(trans); + CharacterDatabase.CommitTransaction(trans); + } + else + { + // desynchronized with the other saves here (SaveInventoryAndGoldToDB() not have own transaction guards) + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + _player->SaveInventoryAndGoldToDB(trans); + trader->SaveInventoryAndGoldToDB(trans); + CharacterDatabase.CommitTransaction(trans); + } info.Status = TRADE_STATUS_TRADE_COMPLETE; trader->GetSession()->SendTradeStatus(info); diff --git a/src/server/game/Instances/InstanceSaveMgr.cpp b/src/server/game/Instances/InstanceSaveMgr.cpp index 76e0d2db0..d93f2fd72 100644 --- a/src/server/game/Instances/InstanceSaveMgr.cpp +++ b/src/server/game/Instances/InstanceSaveMgr.cpp @@ -29,6 +29,7 @@ #include "ObjectMgr.h" #include "Player.h" #include "ScriptMgr.h" +#include "TC9Sidecar.h" #include "Timer.h" #include "Transport.h" #include "World.h" @@ -164,6 +165,9 @@ InstanceSave::~InstanceSave() void InstanceSave::InsertToDB() { + if (sToCloud9Sidecar->ClusterModeEnabled() && !sToCloud9Sidecar->IsMapAssigned(GetMapId())) + return; + std::string data; uint32 completedEncounters = 0; @@ -454,6 +458,157 @@ void InstanceSaveMgr::LoadCharacterBinds() lock_instLists = false; } +// Runs on a std::async worker thread. Only the two synchronous CharacterDatabase +// reads happen here (safe: the synch connection pool is mutex-guarded per connection). +// All manager-state access is deferred to MergeWithNewInstanceSaves on the world thread. +InstanceMapLoadRows InstanceSaveMgr::LoadInstanceSavesAndBindsForMapIDs(std::vector const& mapIDs) +{ + InstanceMapLoadRows rows; + + std::stringstream mapIDsStr; + for (size_t i = 0; i < mapIDs.size(); ++i) + { + mapIDsStr << mapIDs[i]; + if (i < mapIDs.size() - 1) + mapIDsStr << ","; + } + + QueryResult result = CharacterDatabase.Query("SELECT id, map, resettime, difficulty, completedEncounters, data FROM instance WHERE map IN ({}) ORDER BY id ASC", mapIDsStr.str()); + if (result) + { + do + { + Field* fields = result->Fetch(); + + InstanceMapLoadRows::InstanceRow row; + row.instanceId = fields[0].Get(); + row.mapId = fields[1].Get(); + row.resetTime = time_t(fields[2].Get()); + row.difficulty = fields[3].Get(); + row.completedEncounters = fields[4].Get(); + row.data = fields[5].Get(); + rows.instances.push_back(std::move(row)); + } while (result->NextRow()); + } + + result = CharacterDatabase.Query("SELECT guid, instance, permanent, extended FROM character_instance WHERE instance IN (SELECT id FROM instance WHERE map IN ({}))", mapIDsStr.str()); + if (result) + { + do + { + Field* fields = result->Fetch(); + + InstanceMapLoadRows::BindRow row; + row.guidLow = fields[0].Get(); + row.instanceId = fields[1].Get(); + row.perm = fields[2].Get(); + row.extended = fields[3].Get(); + rows.binds.push_back(row); + } while (result->NextRow()); + } + + return rows; +} + +// Runs on the world thread (TC9 async completion callback). Safe to touch +// m_instanceSaveById, playerBindStorage, m_resetExtendedTimeByMapDifficulty and MapMgr here. +void InstanceSaveMgr::MergeWithNewInstanceSaves(InstanceMapLoadRows const& loadResult) +{ + // Same guard as LoadCharacterBinds: without it, an unbind cascading into + // DeleteInstanceSaveIfNeeded would delete the very DB rows being merged. + lock_instLists = true; + + for (InstanceMapLoadRows::InstanceRow const& row : loadResult.instances) + { + MapEntry const* entry = sMapStore.LookupEntry(row.mapId); + if (!entry) + { + LOG_ERROR("instance.save", "InstanceSaveMgr::MergeWithNewInstanceSaves: wrong mapid = {}, instanceid = {}!", row.mapId, row.instanceId); + continue; + } + + // Same row validation as AddInstanceSave for rows loaded on reassignment. + if (row.instanceId == 0) + { + LOG_ERROR("instance.save", "InstanceSaveMgr::MergeWithNewInstanceSaves: mapid = {}, wrong instanceid = {}!", row.mapId, row.instanceId); + continue; + } + + if (row.difficulty >= (entry->IsRaid() ? MAX_RAID_DIFFICULTY : MAX_DUNGEON_DIFFICULTY)) + { + LOG_ERROR("instance.save", "InstanceSaveMgr::MergeWithNewInstanceSaves: mapid = {}, instanceid = {}, wrong difficulty {}!", row.mapId, row.instanceId, row.difficulty); + continue; + } + + time_t extendedResetTime = 0; + if (entry->IsRaid() || row.difficulty > DUNGEON_DIFFICULTY_NORMAL) + extendedResetTime = GetExtendedResetTimeFor(row.mapId, Difficulty(row.difficulty)); + + InstanceSave* save = new InstanceSave(row.mapId, row.instanceId, Difficulty(row.difficulty), row.resetTime, extendedResetTime); + save->SetCompletedEncounterMask(row.completedEncounters); + save->SetInstanceData(row.data); + if (row.resetTime > 0) + save->SetResetTime(row.resetTime); + + InstanceSaveHashMap::iterator currentSave = m_instanceSaveById.find(row.instanceId); + if (currentSave != m_instanceSaveById.end()) + { + InstanceSave* oldSave = currentSave->second; + + // Unbind every player still pointing at the stale save before it is + // freed, in-memory only: the DB rows were just refetched and are + // rebound below. Iterate a copy, PlayerUnbindInstance mutates the list. + GuidList players = oldSave->m_playerList; + for (ObjectGuid const& playerGuid : players) + PlayerUnbindInstance(playerGuid, oldSave->GetMapId(), oldSave->GetDifficulty(), false); + + m_instanceSaveById.erase(currentSave); + delete oldSave; + } + m_instanceSaveById[row.instanceId] = save; + } + + for (InstanceMapLoadRows::BindRow const& row : loadResult.binds) + { + InstanceSaveHashMap::iterator itr = m_instanceSaveById.find(row.instanceId); + InstanceSave* save = itr != m_instanceSaveById.end() ? itr->second : nullptr; + if (!save) + continue; + + ObjectGuid guid = ObjectGuid::Create(row.guidLow); + + PlayerCreateBoundInstancesMaps(guid); + InstancePlayerBind& bind = playerBindStorage[guid]->m[save->GetDifficulty()][save->GetMapId()]; + if (bind.save) // pussywizard: another bind for the same map and difficulty! may happen because of mysql thread races + { + if (bind.perm) // already loaded perm -> delete currently checked one from db + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID); + stmt->SetData(0, guid.GetCounter()); + stmt->SetData(1, row.instanceId); + CharacterDatabase.Execute(stmt); + continue; + } + else // override temp bind by newest one + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID); + stmt->SetData(0, guid.GetCounter()); + stmt->SetData(1, bind.save->GetInstanceId()); + CharacterDatabase.Execute(stmt); + bind.save->RemovePlayer(guid, this); + } + } + bind.save = save; + bind.perm = row.perm; + bind.extended = row.extended; + save->AddPlayer(guid); + if (row.perm) + save->SetCanReset(false); + } + + lock_instLists = false; +} + void InstanceSaveMgr::ScheduleReset(time_t time, InstResetEvent event) { m_resetTimeQueue.insert(std::pair(time, event)); diff --git a/src/server/game/Instances/InstanceSaveMgr.h b/src/server/game/Instances/InstanceSaveMgr.h index ce9cd722a..341d3d18e 100644 --- a/src/server/game/Instances/InstanceSaveMgr.h +++ b/src/server/game/Instances/InstanceSaveMgr.h @@ -27,6 +27,7 @@ #include #include #include +#include struct InstanceTemplate; struct MapEntry; @@ -103,6 +104,31 @@ private: typedef std::unordered_map ResetTimeByMapDifficultyMap; +// Raw rows fetched on a worker thread by LoadInstanceSavesAndBindsForMapIDs. +// Contains no manager-owned state, so it is safe to build off the world thread +// and consume later on the world thread in MergeWithNewInstanceSaves. +struct InstanceMapLoadRows +{ + struct InstanceRow + { + uint32 instanceId; + uint16 mapId; + time_t resetTime; + uint8 difficulty; + uint32 completedEncounters; + std::string data; + }; + struct BindRow + { + uint32 guidLow; + uint32 instanceId; + bool perm; + bool extended; + }; + std::vector instances; + std::vector binds; +}; + class InstanceSaveMgr { friend class InstanceSave; @@ -133,6 +159,11 @@ public: void LoadInstanceSaves(); void LoadCharacterBinds(); + // Worker thread: performs only the (blocking) DB reads, touches no manager state. + [[nodiscard]] InstanceMapLoadRows LoadInstanceSavesAndBindsForMapIDs(std::vector const& mapIDs); + // World thread: builds InstanceSaves and player binds from the fetched rows and merges them in. + void MergeWithNewInstanceSaves(InstanceMapLoadRows const& loadResult); + [[nodiscard]] time_t GetResetTimeFor(uint32 mapid, Difficulty d) const { ResetTimeByMapDifficultyMap::const_iterator itr = m_resetTimeByMapDifficulty.find(MAKE_PAIR32(mapid, d)); diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index 14a5fac34..cf4d56aa0 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -31,6 +31,7 @@ #include "RBAC.h" #include "ScriptMgr.h" #include "Spell.h" +#include "TC9Sidecar.h" #include "WorldSession.h" BossBoundaryData::~BossBoundaryData() @@ -41,6 +42,9 @@ BossBoundaryData::~BossBoundaryData() void InstanceScript::SaveToDB() { + if (sToCloud9Sidecar->ClusterModeEnabled() && !sToCloud9Sidecar->IsMapAssigned(instance->GetEntry()->MapID)) + return; + std::string data = GetSaveData(); //if (data.empty()) // pussywizard: encounterMask can be updated and theres no reason to not save // return; diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index cc045f67f..113215aa8 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -38,6 +38,7 @@ #include "Pet.h" #include "PoolMgr.h" #include "ScriptMgr.h" +#include "TC9Sidecar.h" #include "Transport.h" #include "VMapFactory.h" #include "Vehicle.h" @@ -518,6 +519,8 @@ void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/) HandleDelayedVisibility(); + UpdatePlayersRedirectKickEvent(t_diff); + UpdateWeather(t_diff); UpdateExpiredCorpses(t_diff); @@ -1670,7 +1673,7 @@ void Map::SendInitTransports(Player* player) // Hack to send out transports UpdateData transData; for (TransportsContainer::const_iterator itr = _transports.begin(); itr != _transports.end(); ++itr) - if (*itr != player->GetTransport()) + if (*itr != player->GetTransport() && (!sToCloud9Sidecar->ClusterModeEnabled() || player->InSamePhase(*itr))) (*itr)->BuildCreateUpdateBlockForPlayer(&transData, player); if (!transData.HasData()) @@ -1848,6 +1851,50 @@ uint32 Map::GetPlayersCountExceptGMs(bool aliveOnly /*= false*/) const return count; } +void Map::StartPlayersRedirectKickTimer() +{ + for (MapRefMgr::iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr) + itr->GetSource()->SendSystemMessage("Preparing to enter parallel dimension... One minute!\nAccelerate transfer: Teleport or type \"/ready\" in chat."); + + _redirectKickTimer.Reset(60 * SECOND * IN_MILLISECONDS); + _lastAnnounceRedirectKickTimer.Reset(55 * SECOND * IN_MILLISECONDS); + + _lastAnnounceRedirectKickTimer.Update(1); + +} + +void Map::StopPlayersRedirectKickTimer() +{ + _redirectKickTimer.Reset(0); + _lastAnnounceRedirectKickTimer.Reset(0); +} + +void Map::UpdatePlayersRedirectKickEvent(uint32 diff) +{ + if (_redirectKickTimer.Passed()) + return; + + _redirectKickTimer.Update(diff); + + if (_redirectKickTimer.Passed()) + { + auto emptyPacket = WorldPacket(); + for (MapRefMgr::iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr) + itr->GetSource()->GetSession()->HandleTC9PrepareForRedirect(emptyPacket); + + return; + } + + if (_lastAnnounceRedirectKickTimer.Passed()) + return; + + _lastAnnounceRedirectKickTimer.Update(diff); + + if (_lastAnnounceRedirectKickTimer.Passed()) + for (MapRefMgr::iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr) + itr->GetSource()->SendSystemMessage("Dimensional shift incoming! Prepare to transition in 5 seconds..."); +} + void Map::SendToPlayers(WorldPacket const* data) const { for (MapRefMgr::const_iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr) diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index 3575b91ff..09342e6fa 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -27,6 +27,7 @@ #include "GameObjectModel.h" #include "GridDefines.h" #include "GridRefMgr.h" +#include "Timer.h" #include "MapCollisionData.h" #include "MapGridManager.h" #include "MapRefMgr.h" @@ -325,6 +326,10 @@ public: void SendToPlayers(WorldPacket const* data) const; + void StartPlayersRedirectKickTimer(); + void StopPlayersRedirectKickTimer(); + bool IsPlayerRedirectKickTimerActive() { return !_redirectKickTimer.Passed(); } + typedef MapRefMgr PlayerList; [[nodiscard]] PlayerList const& GetPlayers() const { return m_mapRefMgr; } @@ -585,6 +590,8 @@ private: void SendObjectUpdates(); + void UpdatePlayersRedirectKickEvent(uint32 diff); + protected: // Type specific code for add/remove to/from grid template @@ -693,6 +700,9 @@ private: PendingAddUpdatableObjectList _pendingAddUpdatableObjectList; IntervalTimer _updatableObjectListRecheckTimer; ZoneWideVisibleWorldObjectsMap _zoneWideVisibleWorldObjectsMap; + + TimeTrackerSmall _redirectKickTimer; + TimeTrackerSmall _lastAnnounceRedirectKickTimer; }; enum InstanceResetMethod diff --git a/src/server/game/Maps/MapMgr.cpp b/src/server/game/Maps/MapMgr.cpp index 94044f799..46f627a10 100644 --- a/src/server/game/Maps/MapMgr.cpp +++ b/src/server/game/Maps/MapMgr.cpp @@ -31,6 +31,7 @@ #include "Opcodes.h" #include "Player.h" #include "ScriptMgr.h" +#include "TC9Sidecar.h" #include "Transport.h" #include "World.h" #include "WorldPacket.h" @@ -405,6 +406,9 @@ void MapMgr::RegisterInstanceId(uint32 instanceId) uint32 MapMgr::GenerateInstanceId() { + if (sToCloud9Sidecar->ClusterModeEnabled()) + return sToCloud9Sidecar->GenerateInstanceGuid(); + uint32 newInstanceId = _nextInstanceId; // find the lowest available id starting from the current _nextInstanceId diff --git a/src/server/game/Maps/TransportMgr.cpp b/src/server/game/Maps/TransportMgr.cpp index 00afdf2b6..ca5ef2d8b 100644 --- a/src/server/game/Maps/TransportMgr.cpp +++ b/src/server/game/Maps/TransportMgr.cpp @@ -21,6 +21,9 @@ #include "MoveSpline.h" #include "QueryResult.h" #include "Transport.h" +#include "TaskScheduler.h" +#include "Config.h" +#include TransportTemplate::~TransportTemplate() { diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 55113a806..c2d99161e 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -1439,6 +1439,8 @@ void OpcodeTable::Initialize() /*0x51C*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_COMMENTATOR_SKIRMISH_QUEUE_RESULT1, STATUS_NEVER); /*0x51D*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_COMMENTATOR_SKIRMISH_QUEUE_RESULT2, STATUS_NEVER); /*0x51E*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_MULTIPLE_MOVES, STATUS_NEVER); + /*0x51F*/ DEFINE_HANDLER(TC9_CMSG_PREPARE_FOR_REDIRECT, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleTC9PrepareForRedirect); + /*0x520*/ DEFINE_SERVER_OPCODE_HANDLER(TC9_SMSG_READY_FOR_REDIRECT, STATUS_NEVER); #undef DEFINE_HANDLER #undef DEFINE_SERVER_OPCODE_HANDLER diff --git a/src/server/game/Server/Protocol/Opcodes.h b/src/server/game/Server/Protocol/Opcodes.h index cb994b3e8..0b34344c4 100644 --- a/src/server/game/Server/Protocol/Opcodes.h +++ b/src/server/game/Server/Protocol/Opcodes.h @@ -1338,7 +1338,9 @@ enum Opcodes : uint16 SMSG_COMMENTATOR_SKIRMISH_QUEUE_RESULT1 = 0x51C, SMSG_COMMENTATOR_SKIRMISH_QUEUE_RESULT2 = 0x51D, SMSG_MULTIPLE_MOVES = 0x51E, // uncompressed version of SMSG_COMPRESSED_MOVES - NUM_MSG_TYPES = 0x51F + TC9_CMSG_PREPARE_FOR_REDIRECT = 0x51F, + TC9_SMSG_READY_FOR_REDIRECT = 0x520, + NUM_MSG_TYPES = 0x521 }; enum OpcodeMisc : uint16 diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index b144d483f..3982998d1 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -19,6 +19,7 @@ \ingroup u2w */ +#include "TC9Sidecar.h" #include "WorldSession.h" #include "AccountMgr.h" #include "BattlegroundMgr.h" @@ -673,7 +674,7 @@ void WorldSession::SendPlayTimeWarning(PlayTimeFlag flag, int32 playTimeRemainin } /// %Log the player out -void WorldSession::LogoutPlayer(bool save) +void WorldSession::LogoutPlayer(bool save, bool redirecting) { // finish pending transfers before starting the logout while (_player && _player->IsBeingTeleportedFar()) @@ -759,23 +760,26 @@ void WorldSession::LogoutPlayer(bool save) // there are some positive auras from boss encounters that can be kept by logging out and logging in after boss is dead, and may be used on next bosses _player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_CHANGE_MAP); - if (Group *group = _player->GetGroupInvite()) - sWorld->getBoolConfig(CONFIG_LEAVE_GROUP_ON_LOGOUT) - ? _player->UninviteFromGroup() // Can disband group. - : group->RemoveInvite(_player); // Just removes invite. + if (!redirecting) + { + if (Group *group = _player->GetGroupInvite()) + sWorld->getBoolConfig(CONFIG_LEAVE_GROUP_ON_LOGOUT) + ? _player->UninviteFromGroup() // Can disband group. + : group->RemoveInvite(_player); // Just removes invite. - // remove player from the group if he is: - // a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected) d) LeaveGroupOnLogout is enabled - if (_player->GetGroup() && !_player->GetGroup()->isRaidGroup() && !_player->GetGroup()->isLFGGroup() && m_Socket && sWorld->getBoolConfig(CONFIG_LEAVE_GROUP_ON_LOGOUT)) - _player->RemoveFromGroup(); - // Remove player from active loot rolls in LFG groups (player stays in group but should not block rolls) - else if (Group* group = _player->GetGroup()) - if (group->isLFGGroup()) - group->RemovePlayerFromRolls(_player->GetGUID()); + // remove player from the group if he is: + // a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected) d) LeaveGroupOnLogout is enabled + if (!sToCloud9Sidecar->ClusterModeEnabled() && _player->GetGroup() && !_player->GetGroup()->isRaidGroup() && !_player->GetGroup()->isLFGGroup() && m_Socket && sWorld->getBoolConfig(CONFIG_LEAVE_GROUP_ON_LOGOUT)) + _player->RemoveFromGroup(); + // Remove player from active loot rolls in LFG groups (player stays in group but should not block rolls) + else if (Group* group = _player->GetGroup()) + if (group->isLFGGroup()) + group->RemovePlayerFromRolls(_player->GetGUID()); - // pussywizard: checked second time after being removed from a group - if (!_player->IsBeingTeleportedFar() && !_player->m_InstanceValid && !_player->IsGameMaster()) - _player->RepopAtGraveyard(); + // pussywizard: checked second time after being removed from a group + if (!_player->IsBeingTeleportedFar() && !_player->m_InstanceValid && !_player->IsGameMaster()) + _player->RepopAtGraveyard(); + } // Repop at Graveyard or other player far teleport will prevent saving player because of not present map // Teleport player immediately for correct player save @@ -814,12 +818,15 @@ void WorldSession::LogoutPlayer(bool save) } } - //! Broadcast a logout message to the player's friends - sSocialMgr->SendFriendStatus(_player, FRIEND_OFFLINE, _player->GetGUID(), true); - sSocialMgr->RemovePlayerSocial(_player->GetGUID()); + if (!redirecting) + { + //! Broadcast a logout message to the player's friends + sSocialMgr->SendFriendStatus(_player, FRIEND_OFFLINE, _player->GetGUID(), true); + sSocialMgr->RemovePlayerSocial(_player->GetGUID()); - //! Call script hook before deletion - sScriptMgr->OnPlayerLogout(_player); + //! Call script hook before deletion + sScriptMgr->OnPlayerLogout(_player); + } METRIC_EVENT("player_events", "Logout", _player->GetName()); @@ -845,9 +852,12 @@ void WorldSession::LogoutPlayer(bool save) LOG_DEBUG("network", "SESSION: Sent SMSG_LOGOUT_COMPLETE Message"); //! Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ACCOUNT_ONLINE); - stmt->SetData(0, GetAccountId()); - CharacterDatabase.Execute(stmt); + if (!redirecting) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ACCOUNT_ONLINE); + stmt->SetData(0, GetAccountId()); + CharacterDatabase.Execute(stmt); + } } m_playerLogout = false; @@ -1563,21 +1573,69 @@ void WorldSession::InitializeSessionCallback(CharacterDatabaseQueryHolder const& LoadAccountData(realmHolder.GetPreparedResult(AccountInfoQueryHolderPerRealm::GLOBAL_ACCOUNT_DATA), GLOBAL_CACHE_MASK); LoadTutorialsData(realmHolder.GetPreparedResult(AccountInfoQueryHolderPerRealm::TUTORIALS)); - if (!m_inQueue) + if (!sToCloud9Sidecar->ClusterModeEnabled()) { - SendAuthResponse(AUTH_OK, true); - } - else - { - SendAuthWaitQueue(0); + if (!m_inQueue) + { + SendAuthResponse(AUTH_OK, true); + } + else + { + SendAuthWaitQueue(0); + } } SetInQueue(false); ResetTimeOutTime(false); - SendAddonsInfo(); - SendClientCacheVersion(clientCacheVersion); - SendTutorialsData(); + if (!sToCloud9Sidecar->ClusterModeEnabled()) + { + SendAddonsInfo(); + SendClientCacheVersion(clientCacheVersion); + SendTutorialsData(); + } +} + +void WorldSession::HandleTC9PrepareForRedirect(WorldPacket& /*recvData*/) +{ + if (!sToCloud9Sidecar->ClusterModeEnabled()) + return; + + Player* player = this->GetPlayer(); + if (player == nullptr) + { + WorldPacket data(TC9_SMSG_READY_FOR_REDIRECT, 1); + data << uint8(1); // 1 - Failed. + SendPacket(&data); + return; + } + + LOG_DEBUG("network", "Starting saving, AccountId = {}", GetAccountId()); + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + player->SaveToDB(trans, false, true); + AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete([this](bool success) + { + WorldPacket data(TC9_SMSG_READY_FOR_REDIRECT, 1); + data << uint8(!success); // 0 - Success, 1 - Failed. + SendPacket(&data); + + if (!success) + { + LOG_ERROR("network", "Failed to save player, AccountId = {}", GetAccountId()); + return; + } + + LOG_DEBUG("network", "Saved, AccountId = {}", GetAccountId()); + + Player* player = GetPlayer(); + if (!player) + return; + + player->m_Events.AddEventAtOffset([this](){ + KickPlayer("HandlePrepareForRedirect client redirected"); + }, 100ms); + }); } void WorldSession::SetPacketLogging(bool state) diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 8e6ec9be5..0abedb40c 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -524,7 +524,7 @@ public: return (_logoutTime > 0 && currTime >= _logoutTime + 20); } - void LogoutPlayer(bool save); + void LogoutPlayer(bool save, bool redirecting = false); void KickPlayer(bool setKicked = true) { return this->KickPlayer("Unknown reason", setKicked); } void KickPlayer(std::string const& reason, bool setKicked = true); @@ -687,6 +687,8 @@ public: // opcodes handlers void SendCharFactionChange(ResponseCodes result, CharacterFactionChangeInfo const* factionChangeInfo); void SendSetPlayerDeclinedNamesResult(DeclinedNameResult result, ObjectGuid guid); + void HandleTC9PrepareForRedirect(WorldPacket& recvData); + // played time void HandlePlayedTime(WorldPackets::Character::PlayedTimeClient& packet); diff --git a/src/server/game/Server/WorldSocket.cpp b/src/server/game/Server/WorldSocket.cpp index e4ca47d10..9553b8bee 100644 --- a/src/server/game/Server/WorldSocket.cpp +++ b/src/server/game/Server/WorldSocket.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "TC9Sidecar.h" #include "WorldSocket.h" #include "AccountMgr.h" #include "Config.h" @@ -579,8 +580,9 @@ void WorldSocket::HandleAuthSessionCallback(std::shared_ptr a LoginDatabase.Execute(stmt); // This also allows to check for possible "hack" attempts on account - // even if auth credentials are bad, try using the session key we have - client cannot read auth response error without it - _authCrypt.Init(account.SessionKey); + if (!sToCloud9Sidecar->ClusterModeEnabled()) + // even if auth credentials are bad, try using the session key we have - client cannot read auth response error without it + _authCrypt.Init(account.SessionKey); // First reject the connection if packet contains invalid data or realm state doesn't allow logging in if (sWorld->IsClosed()) @@ -591,7 +593,7 @@ void WorldSocket::HandleAuthSessionCallback(std::shared_ptr a return; } - if (authSession->RealmID != realm.Id.Realm) + if (!sToCloud9Sidecar->ClusterModeEnabled() && authSession->RealmID != realm.Id.Realm) { SendAuthResponseError(REALM_LIST_REALM_NOT_FOUND); LOG_ERROR("network", "WorldSocket::HandleAuthSession: Client {} requested connecting with realm id {} but this realm has id {} set in config.", @@ -600,105 +602,107 @@ void WorldSocket::HandleAuthSessionCallback(std::shared_ptr a return; } - // Must be done before WorldSession is created bool wardenActive = sWorld->getBoolConfig(CONFIG_WARDEN_ENABLED); - if (wardenActive && account.OS != "Win" && account.OS != "OSX") + if (!sToCloud9Sidecar->ClusterModeEnabled()) { - SendAuthResponseError(AUTH_REJECT); - LOG_ERROR("network", "WorldSocket::HandleAuthSession: Client {} attempted to log in using invalid client OS ({}).", address, account.OS); - DelayedCloseSocket(); - return; - } + // Must be done before WorldSession is created + if (wardenActive && account.OS != "Win" && account.OS != "OSX") + { + SendAuthResponseError(AUTH_REJECT); + LOG_ERROR("network", "WorldSocket::HandleAuthSession: Client {} attempted to log in using invalid client OS ({}).", address, account.OS); + DelayedCloseSocket(); + return; + } - // Check that Key and account name are the same on client and server - uint8 t[4] = { 0x00,0x00,0x00,0x00 }; + // Check that Key and account name are the same on client and server + uint8 t[4] = { 0x00,0x00,0x00,0x00 }; - Acore::Crypto::SHA1 sha; - sha.UpdateData(authSession->Account); - sha.UpdateData(t); - sha.UpdateData(authSession->LocalChallenge); - sha.UpdateData(_authSeed); - sha.UpdateData(account.SessionKey); - sha.Finalize(); + Acore::Crypto::SHA1 sha; + sha.UpdateData(authSession->Account); + sha.UpdateData(t); + sha.UpdateData(authSession->LocalChallenge); + sha.UpdateData(_authSeed); + sha.UpdateData(account.SessionKey); + sha.Finalize(); - if (sha.GetDigest() != authSession->Digest) - { - SendAuthResponseError(AUTH_FAILED); - LOG_ERROR("network", "WorldSocket::HandleAuthSession: Authentication failed for account: {} ('{}') address: {}", account.Id, authSession->Account, address); - DelayedCloseSocket(); - return; - } - - if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(address)) - _ipCountry = location->CountryCode; - - ///- Re-check ip locking (same check as in auth). - if (account.IsLockedToIP) - { - if (account.LastIP != address) + if (sha.GetDigest() != authSession->Digest) { SendAuthResponseError(AUTH_FAILED); - LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account IP differs. Original IP: {}, new IP: {}).", account.LastIP, address); - // We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well + LOG_ERROR("network", "WorldSocket::HandleAuthSession: Authentication failed for account: {} ('{}') address: {}", account.Id, authSession->Account, address); + DelayedCloseSocket(); + return; + } + + if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(address)) + _ipCountry = location->CountryCode; + + ///- Re-check ip locking (same check as in auth). + if (account.IsLockedToIP) + { + if (account.LastIP != address) + { + SendAuthResponseError(AUTH_FAILED); + LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account IP differs. Original IP: {}, new IP: {}).", account.LastIP, address); + // We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well + sScriptMgr->OnFailedAccountLogin(account.Id); + DelayedCloseSocket(); + return; + } + } + else if (!account.LockCountry.empty() && account.LockCountry != "00" && !_ipCountry.empty()) + { + if (account.LockCountry != _ipCountry) + { + SendAuthResponseError(AUTH_FAILED); + LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account country differs. Original country: {}, new country: {}).", account.LockCountry, _ipCountry); + // We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well + sScriptMgr->OnFailedAccountLogin(account.Id); + DelayedCloseSocket(); + return; + } + } + + //! Negative mutetime indicates amount of minutes to be muted effective on next login - which is now. + if (account.MuteTime < 0) + { + account.MuteTime = GameTime::GetGameTime().count() + std::llabs(account.MuteTime); + + auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_MUTE_TIME_LOGIN); + stmt->SetData(0, account.MuteTime); + stmt->SetData(1, account.Id); + LoginDatabase.Execute(stmt); + } + + if (account.IsBanned) + { + SendAuthResponseError(AUTH_BANNED); + LOG_ERROR("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account banned)."); sScriptMgr->OnFailedAccountLogin(account.Id); DelayedCloseSocket(); return; } - } - else if (!account.LockCountry.empty() && account.LockCountry != "00" && !_ipCountry.empty()) - { - if (account.LockCountry != _ipCountry) + + // Check locked state for server + AccountTypes allowedAccountType = sWorld->GetPlayerSecurityLimit(); + LOG_DEBUG("network", "Allowed Level: {} Player Level {}", allowedAccountType, account.Security); + if (allowedAccountType > SEC_PLAYER && account.Security < allowedAccountType) { - SendAuthResponseError(AUTH_FAILED); - LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account country differs. Original country: {}, new country: {}).", account.LockCountry, _ipCountry); - // We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well + SendAuthResponseError(AUTH_UNAVAILABLE); + LOG_DEBUG("network", "WorldSocket::HandleAuthSession: User tries to login but his security level is not enough"); sScriptMgr->OnFailedAccountLogin(account.Id); DelayedCloseSocket(); return; } - } - //! Negative mutetime indicates amount of minutes to be muted effective on next login - which is now. - if (account.MuteTime < 0) - { - account.MuteTime = GameTime::GetGameTime().count() + std::llabs(account.MuteTime); + LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Client '{}' authenticated successfully from {}.", authSession->Account, address); - auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_MUTE_TIME_LOGIN); - stmt->SetData(0, account.MuteTime); - stmt->SetData(1, account.Id); + // Update the last_ip in the database as it was successful for login + stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_IP); + stmt->SetData(0, address); + stmt->SetData(1, authSession->Account); LoginDatabase.Execute(stmt); } - if (account.IsBanned) - { - SendAuthResponseError(AUTH_BANNED); - LOG_ERROR("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account banned)."); - sScriptMgr->OnFailedAccountLogin(account.Id); - DelayedCloseSocket(); - return; - } - - // Check locked state for server - AccountTypes allowedAccountType = sWorld->GetPlayerSecurityLimit(); - LOG_DEBUG("network", "Allowed Level: {} Player Level {}", allowedAccountType, account.Security); - if (allowedAccountType > SEC_PLAYER && account.Security < allowedAccountType) - { - SendAuthResponseError(AUTH_UNAVAILABLE); - LOG_DEBUG("network", "WorldSocket::HandleAuthSession: User tries to login but his security level is not enough"); - sScriptMgr->OnFailedAccountLogin(account.Id); - DelayedCloseSocket(); - return; - } - - LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Client '{}' authenticated successfully from {}.", authSession->Account, address); - - // Update the last_ip in the database as it was successful for login - stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_IP); - stmt->SetData(0, address); - stmt->SetData(1, authSession->Account); - - LoginDatabase.Execute(stmt); - // At this point, we can safely hook a successful login sScriptMgr->OnAccountLogin(account.Id); @@ -712,8 +716,9 @@ void WorldSocket::HandleAuthSessionCallback(std::shared_ptr a _worldSession->ReadAddonsInfo(authSession->AddonInfo); // Initialize Warden system only if it is enabled by config - if (wardenActive) + if (!sToCloud9Sidecar->ClusterModeEnabled() && wardenActive) { + // TODO: move warden outside of a node? _worldSession->InitWarden(account.SessionKey, account.OS); } diff --git a/src/server/game/TC9Sidecar/AsyncTask.h b/src/server/game/TC9Sidecar/AsyncTask.h new file mode 100644 index 000000000..1736e1548 --- /dev/null +++ b/src/server/game/TC9Sidecar/AsyncTask.h @@ -0,0 +1,93 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef _ASYNC_TASK_H +#define _ASYNC_TASK_H + +#include "Errors.h" +#include "Log.h" +#include +#include +#include + +template +class AsyncTask +{ +public: + using AsyncFunction = std::function; + using CallbackFunction = std::function; + + AsyncTask(AsyncFunction asyncFunc, CallbackFunction callbackFunc) + : asyncFunc(std::move(asyncFunc)), callbackFunc(std::move(callbackFunc)), isReady(false) + { + } + + ~AsyncTask() + { + // Ensure that the asynchronous task has completed before destruction + if (asyncTask.valid() && asyncTask.wait_for(std::chrono::seconds(0)) != std::future_status::ready) + { + asyncTask.wait(); // Wait for the task to complete + } + } + + bool InvokeIfReady() + { + if (!isReady) + { + // Check if the asynchronous task is ready + if (asyncTask.valid() && asyncTask.wait_for(std::chrono::seconds(0)) == std::future_status::ready) + { + // get() rethrows anything the async function threw. Swallowing it + // would wedge the cluster handoff (the completion callback signals + // map readiness to the registry), so fail fast with context and let + // the registry's crash recovery rebalance this node. + try + { + callbackFunc(asyncTask.get()); + } + catch (std::exception const& e) + { + LOG_ERROR("server.tc9", "AsyncTask failed: {}", e.what()); + ABORT("AsyncTask failed: {}", e.what()); + } + isReady = true; + return true; + } + } + return false; + } + + void ExecuteAsync() + { + // Capture the function by value so a moved AsyncTask does not leave + // the in-flight async holding a dangling this pointer. + AsyncFunction fn = asyncFunc; + asyncTask = std::async(std::launch::async, [fn = std::move(fn)]() mutable + { + return fn(); + }); + } + +private: + AsyncFunction asyncFunc; + CallbackFunction callbackFunc; + std::shared_future asyncTask; + bool isReady; +}; + +#endif // _ASYNC_TASK_H diff --git a/src/server/game/TC9Sidecar/TC9GroupHooks.cpp b/src/server/game/TC9Sidecar/TC9GroupHooks.cpp new file mode 100644 index 000000000..939e4e47f --- /dev/null +++ b/src/server/game/TC9Sidecar/TC9GroupHooks.cpp @@ -0,0 +1,115 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "TC9GroupHooks.h" +#include "CharacterCache.h" +#include "Group.h" +#include "GroupMgr.h" +#include "Log.h" + +void ToCloud9GroupHooks::OnGroupCreated(EventObjectGroup *group) +{ + LOG_INFO("server", "Group created. ID: {}; Leader: {}.", group->guid, group->leader); + + // Idempotent under sidecar event redelivery: a replayed create must not leak a second Group. + if (sGroupMgr->GetGroupByGUID(group->guid)) + return; + + Group* g = new Group(); + g->m_guid = ObjectGuid(HighGuid::Group, group->guid); + g->m_leaderGuid = ObjectGuid(group->leader); + sCharacterCache->GetCharacterNameByGuid(g->m_leaderGuid, g->m_leaderName); + g->m_dungeonDifficulty = Difficulty(group->difficulty); + g->m_raidDifficulty = Difficulty(group->raidDifficulty); + g->m_lootMethod = LootMethod(group->lootMethod); + g->m_lootThreshold = ItemQualities(group->lootThreshold); + g->m_looterGuid = ObjectGuid(group->looterGuid); + g->m_masterLooterGuid = ObjectGuid(group->masterLooterGuid); + g->m_groupType = GroupType(group->groupType); + + // Must precede member insertion: it zeroes the subgroup counters that AddMemberWithGuid increments. + if (g->m_groupType & GROUPTYPE_RAID) + g->_initRaidSubGroupsCounter(); + + for (int i = 0; i < group->membersSize; i++) + g->AddMemberWithGuid(ObjectGuid(group->members[i])); + + sGroupMgr->AddGroup(g); + // Mark the service-assigned id used so a locally generated group can't collide with it. + sGroupMgr->RegisterGroupId(g->GetGUID().GetCounter()); +} + +void ToCloud9GroupHooks::OnGroupDisbanded(uint32 group) +{ + LOG_INFO("server", "Group disbanded. ID: {}.", group); + + if (Group* g = sGroupMgr->GetGroupByGUID(group)) + g->ForcedDisband(true); +} + +void ToCloud9GroupHooks::OnGroupMemberAdded(uint32 group, uint64 member) +{ + LOG_INFO("server", "Group member added. ID: {}; Member: {}.", group, member); + + if (Group* g = sGroupMgr->GetGroupByGUID(group)) + g->AddMemberWithGuid(ObjectGuid(member)); +} + +void ToCloud9GroupHooks::OnGroupMemberRemoved(uint32 group, uint64 member, uint64 newLeader) +{ + LOG_INFO("server", "Group member removed. ID: {}; Member: {}; NewLeader: {}.", group, member, newLeader); + + if (Group* g = sGroupMgr->GetGroupByGUID(group)) + g->RemoveMember(ObjectGuid(member)); +} + +void ToCloud9GroupHooks::OnGroupLootTypeChanged(uint32 group, uint8 lootType, uint64 looter, uint8 lootThreshold) +{ + LOG_INFO("server", "Group loot type changed. ID: {}; LootType: {}; Looter: {}; LootThreshold: {}.", + group, lootType, looter, lootThreshold); + + if (Group* g = sGroupMgr->GetGroupByGUID(group)) + { + g->SetLootMethod((LootMethod)lootType); + g->SetMasterLooterGuid(ObjectGuid(looter)); + g->SetLootThreshold((ItemQualities)lootThreshold); + } +} + +void ToCloud9GroupHooks::OnGroupConvertedToRaid(uint32 group) +{ + LOG_INFO("server", "Group converted to raid. ID: {}.", group); + + if (Group* g = sGroupMgr->GetGroupByGUID(group)) + g->ConvertToRaid(); +} + +void ToCloud9GroupHooks::OnGroupRaidDifficultyChanged(uint32 group, uint8 difficulty) +{ + LOG_INFO("server", "Raid difficulty changed. ID: {}; Difficulty: {}.", group, difficulty); + + if (Group* g = sGroupMgr->GetGroupByGUID(group)) + g->SetRaidDifficulty((Difficulty)difficulty); +} + +void ToCloud9GroupHooks::OnGroupDungeonDifficultyChanged(uint32 group, uint8 difficulty) +{ + LOG_INFO("server", "Dungeon difficulty changed. ID: {}; Difficulty: {}.", group, difficulty); + + if (Group* g = sGroupMgr->GetGroupByGUID(group)) + g->SetDungeonDifficulty((Difficulty)difficulty); +} diff --git a/src/server/game/TC9Sidecar/TC9GroupHooks.h b/src/server/game/TC9Sidecar/TC9GroupHooks.h new file mode 100644 index 000000000..2af1e342e --- /dev/null +++ b/src/server/game/TC9Sidecar/TC9GroupHooks.h @@ -0,0 +1,40 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef _TC9_GROUP_HOOKS_H +#define _TC9_GROUP_HOOKS_H + +#include "Common.h" +#include "libsidecar.h" + +class ToCloud9GroupHooks +{ +public: + ToCloud9GroupHooks() {}; + ~ToCloud9GroupHooks() {}; + + static void OnGroupCreated(EventObjectGroup *group); + static void OnGroupDisbanded(uint32 group); + static void OnGroupMemberAdded(uint32 group, uint64 member); + static void OnGroupMemberRemoved(uint32 group, uint64 member, uint64 newLeader); + static void OnGroupLootTypeChanged(uint32 group, uint8 lootType, uint64 looter, uint8 lootThreshold); + static void OnGroupConvertedToRaid(uint32 group); + static void OnGroupRaidDifficultyChanged(uint32 group, uint8 difficulty); + static void OnGroupDungeonDifficultyChanged(uint32 group, uint8 difficulty); +}; + +#endif /* TC9GroupHooks_h */ diff --git a/src/server/game/TC9Sidecar/TC9GrpcHandler.cpp b/src/server/game/TC9Sidecar/TC9GrpcHandler.cpp new file mode 100644 index 000000000..90059924f --- /dev/null +++ b/src/server/game/TC9Sidecar/TC9GrpcHandler.cpp @@ -0,0 +1,374 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "TC9GrpcHandler.h" +#include "Bag.h" +#include "BattlegroundMgr.h" +#include "Item.h" +#include "ObjectAccessor.h" +#include "Player.h" + +GetPlayerItemsByGuidsResponse ToCloud9GrpcHandler::GetPlayerItemsByGuids(uint64 playerGuid, uint64* items, int itemsLen) +{ + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid)); + if (!player) + { + GetPlayerItemsByGuidsResponse resp; + resp.errorCode = PlayerItemErrorCodePlayerNotFound; + return resp; + } + + if (itemsLen <= 0) + { + GetPlayerItemsByGuidsResponse resp; + resp.errorCode = PlayerItemErrorCodeNoError; + resp.items = nullptr; + resp.itemsSize = 0; + return resp; + } + + int itemsFound = 0; + std::unique_ptr foundItems(new Item * [itemsLen]); + for (int i = 0; i < itemsLen; i++) + { + foundItems[i] = player->GetItemByGuid(ObjectGuid(items[i])); + if (foundItems[i]) + itemsFound++; + } + + // Don't forget to delete on "that" side. + PlayerItem* itemsResult = static_cast(malloc(sizeof(PlayerItem) * itemsFound)); + int itemsResultsItr = 0; + for (int i = 0; i < itemsLen; i++) + { + if (!foundItems[i]) + continue; + + Item* pItem = foundItems[i]; + + PlayerItem item; + item.guid = pItem->GetGUID().GetRawValue(); + item.entry = pItem->GetEntry(); + item.owner = playerGuid; + item.bagSlot = pItem->GetBagSlot(); + item.slot = pItem->GetSlot(); + item.isTradable = pItem->CanBeTraded(true); + item.count = pItem->GetCount(); + item.flags = pItem->GetUInt32Value(ITEM_FIELD_FLAGS); + item.durability = pItem->GetUInt32Value(ITEM_FIELD_DURABILITY); + item.randomPropertyID = pItem->GetItemRandomPropertyId(); + + // Don't forget to delete on "that" side. + char *text = (char*)malloc(sizeof(char) * (pItem->GetText().length() + 1)); + strcpy(text, pItem->GetText().c_str()); + item.text = text; + + itemsResult[itemsResultsItr] = item; + + itemsResultsItr++; + } + + GetPlayerItemsByGuidsResponse resp; + resp.errorCode = PlayerItemErrorCodeNoError; + resp.items = itemsResult; + resp.itemsSize = itemsFound; + return resp; +} + +RemoveItemsWithGuidsFromPlayerResponse ToCloud9GrpcHandler::RemoveItemsWithGuidsFromPlayer(uint64 playerGuid, uint64* items, int itemsLen, uint64 assignToPlayerGuid) +{ + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid)); + if (!player) + { + RemoveItemsWithGuidsFromPlayerResponse resp; + resp.errorCode = PlayerItemErrorCodePlayerNotFound; + return resp; + } + + if (itemsLen <= 0) + { + RemoveItemsWithGuidsFromPlayerResponse resp; + resp.errorCode = PlayerItemErrorCodeNoError; + resp.updatedItems = nullptr; + resp.updatedItemsSize = 0; + return resp; + } + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + + int itemsFound = 0; + std::unique_ptr deletedItems(new uint64 [itemsLen]); + for (int i = 0; i < itemsLen; i++) + { + Item *item = player->GetItemByGuid(ObjectGuid(items[i])); + if (!item) + { + deletedItems[i] = 0; + continue; + } + + itemsFound++; + deletedItems[i] = item->GetGUID().GetRawValue(); + + item->SetNotRefundable(player); + player->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true); + + item->DeleteFromInventoryDB(trans); + item->SetOwnerGUID(ObjectGuid(assignToPlayerGuid)); + item->SetState(ITEM_CHANGED); + item->SaveToDB(trans); + + delete item; + } + + if (itemsFound > 0) + { + player->SaveInventoryAndGoldToDB(trans); + CharacterDatabase.CommitTransaction(trans); + } + + // Don't forget to delete on "that" side. + uint64_t* itemsResult = (uint64_t*)malloc(sizeof(uint64_t) * itemsFound); + int itemsResultsItr = 0; + for (int i = 0; i < itemsLen; i++) + { + if (deletedItems[i] == 0) + continue; + + itemsResult[itemsResultsItr] = deletedItems[i]; + itemsResultsItr++; + } + + RemoveItemsWithGuidsFromPlayerResponse resp; + resp.errorCode = PlayerItemErrorCodeNoError; + resp.updatedItems = itemsResult; + resp.updatedItemsSize = itemsResultsItr; + return resp; + +} + +PlayerItemErrorCode ToCloud9GrpcHandler::AddExistingItemToPlayer(AddExistingItemToPlayerRequest* request) +{ + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(request->playerGuid)); + if (!player) + return PlayerItemErrorCodePlayerNotFound; + + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(request->itemEntry); + if (!proto) + return PlayerItemErrorUnknownTemplate; + + Item* item = NewItemOrBag(proto); + if (!item->Create(ObjectGuid(request->itemGuid).GetCounter(), request->itemEntry, player)) + { + delete item; + return PlayerItemErrorFailedToCreateItem; + } + + item->SetUInt32Value(ITEM_FIELD_FLAGS, request->itemFlags); + item->SetUInt32Value(ITEM_FIELD_DURABILITY, request->itemDurability); + item->SetItemRandomProperties(request->itemRandomPropertyID); + item->SetCount(request->itemCount); + + // TODO: Add text. + + ItemPosCountVec dest; + uint8 msg = player->CanStoreItem(NULL_BAG, NULL_SLOT, dest, item, false); + if (msg != EQUIP_ERR_OK) + { + delete item; + return PlayerItemErrorNoInventorySpace; + } + + player->MoveItemToInventory(dest, item, true); + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + player->SaveInventoryAndGoldToDB(trans); + CharacterDatabase.CommitTransaction(trans); + + return PlayerItemErrorCodeNoError; +} + +GetMoneyForPlayerResponse ToCloud9GrpcHandler::GetMoneyForPlayer(uint64 playerGuid) +{ + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid)); + if (!player) + { + GetMoneyForPlayerResponse resp; + resp.errorCode = PlayerMoneyErrorCodePlayerNotFound; + return resp; + } + + GetMoneyForPlayerResponse resp; + resp.errorCode = PlayerMoneyErrorCodeNoError; + resp.money = player->GetMoney(); + return resp; +} + +ModifyMoneyForPlayerResponse ToCloud9GrpcHandler::ModifyMoneyForPlayer(uint64 playerGuid, int32 value) +{ + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid)); + if (!player) + { + ModifyMoneyForPlayerResponse resp; + resp.errorCode = PlayerMoneyErrorCodePlayerNotFound; + return resp; + } + + if (!player->ModifyMoney(value, true)) + { + ModifyMoneyForPlayerResponse resp; + resp.errorCode = PlayerMoneyErrorCodeTooMuchMoney; + resp.newMoneyValue = player->GetMoney(); + return resp; + } + + ModifyMoneyForPlayerResponse resp; + resp.errorCode = PlayerMoneyErrorCodeNoError; + resp.newMoneyValue = player->GetMoney(); + return resp; +} + +CanPlayerInteractWithGOAndTypeResponse ToCloud9GrpcHandler::CanPlayerInteractWithGOAndType(uint64 playerGuid, uint64 go, uint8 goType) +{ + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid)); + if (!player) + { + CanPlayerInteractWithGOAndTypeResponse resp; + resp.errorCode = PlayerInteractionErrorCodeCodePlayerNotFound; + return resp; + } + + CanPlayerInteractWithGOAndTypeResponse resp; + resp.errorCode = PlayerInteractionErrorCodeNoError; + resp.canInteract = player->GetGameObjectIfCanInteractWith(ObjectGuid(go), (GameobjectTypes)goType) != nullptr; + return resp; +} + +CanPlayerInteractWithNPCAndFlagsResponse ToCloud9GrpcHandler::CanPlayerInteractWithNPCAndFlags(uint64 playerGuid, uint64 npc, uint32 unitFlags) +{ + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid)); + if (!player) + { + CanPlayerInteractWithNPCAndFlagsResponse resp; + resp.errorCode = PlayerInteractionErrorCodeCodePlayerNotFound; + return resp; + } + + CanPlayerInteractWithNPCAndFlagsResponse resp; + resp.errorCode = PlayerInteractionErrorCodeNoError; + resp.canInteract = player->GetNPCIfCanInteractWith(ObjectGuid(npc), (NPCFlags)unitFlags) != nullptr; + return resp; +} + +BattlegroundStartResponse ToCloud9GrpcHandler::StartBattleground(BattlegroundStartRequest* req) +{ + PvPDifficultyEntry const* pvpEntry = GetBattlegroundBracketByLevel(req->mapID, req->bracketLvl); + if (!pvpEntry) + { + BattlegroundStartResponse resp; + resp.errorCode = BattlegroundErrorFailedToCreateBG; + return resp; + } + + BattlegroundTypeId bgTypeId = BattlegroundTypeId(req->battlegroundTypeID); + + Battleground* bg = sBattlegroundMgr->CreateNewBattleground(bgTypeId, pvpEntry, req->arenaType, req->isRated); + if (!bg) + { + BattlegroundStartResponse resp; + resp.errorCode = BattlegroundErrorFailedToCreateBG; + return resp; + } + + bg->StartBattleground(); + + bg->IncreaseInvitedCount(TEAM_HORDE); + bg->IncreaseInvitedCount(TEAM_ALLIANCE); + + BattlegroundStartResponse resp; + resp.errorCode = BattlegroundErrorCodeNoError; + resp.instanceID = bg->GetInstanceID(); + resp.instanceClientID = bg->GetClientInstanceID(); + return resp; +} + +BattlegroundErrorCode ToCloud9GrpcHandler::AddPlayersToBattleground(BattlegroundAddPlayersRequest* request) +{ + BattlegroundTypeId bgTypeId = BattlegroundTypeId(request->battlegroundTypeID); + + Battleground* bg = sBattlegroundMgr->GetBattleground(request->instanceID, BATTLEGROUND_TYPE_NONE); + if (!bg) + return BattlegroundErrorBattlegroundNotFound; + + for (int i = 0; i < request->alliancePlayersToAddSize; i++) + { + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(request->alliancePlayersToAdd[i])); + if (player) + { + player->SetEntryPoint(); + player->SetBattlegroundId(bg->GetInstanceID(), bg->GetBgTypeID(), 1, true, bgTypeId == BATTLEGROUND_RB, player->GetTeamId(true)); + sBattlegroundMgr->SendToBattleground(player, bg->GetInstanceID(), bgTypeId); + } + } + + for (int i = 0; i < request->hordePlayersToAddSize; i++) + { + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(request->hordePlayersToAdd[i])); + if (player) + { + player->SetEntryPoint(); + player->SetBattlegroundId(bg->GetInstanceID(), bg->GetBgTypeID(), 1, true, bgTypeId == BATTLEGROUND_RB, player->GetTeamId(true)); + sBattlegroundMgr->SendToBattleground(player, bg->GetInstanceID(), bgTypeId); + } + } + + return BattlegroundErrorCodeNoError; +} + +BattlegroundJoinCheckErrorCode ToCloud9GrpcHandler::CanPlayerJoinBattlegroundQueue(uint64 playerGuid) +{ + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid)); + if (!player) + return BattlegroundJoinCheckErrorCodePlayerNotFound; + + // Lets ignore RBAC checks for now. + Battleground* bg = sBattlegroundMgr->GetBattlegroundTemplate(BATTLEGROUND_RB); + if (!bg) + return BattlegroundJoinCheckErrorCodeResponseIsFalse; + + // has deserter debuff + if (!player->CanJoinToBattleground(bg)) + return BattlegroundJoinCheckErrorCodeResponseIsFalse; + + // don't let Death Knights join BG queues when they are not allowed to be teleported yet + if (player->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_TELEPORT) && player->GetMapId() == 609 && !player->IsGameMaster() && !player->HasSpell(50977)) + return BattlegroundJoinCheckErrorCodeResponseIsFalse; + + return BattlegroundJoinCheckErrorCodeOK; +} + +BattlegroundJoinCheckErrorCode ToCloud9GrpcHandler::CanPlayerTeleportToBattleground(uint64 playerGuid) +{ + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid)); + if (!player) + return BattlegroundJoinCheckErrorCodePlayerNotFound; + + if (player->GetCharmGUID() || player->IsInCombat()) + return BattlegroundJoinCheckErrorCodeResponseIsFalse; + + return BattlegroundJoinCheckErrorCodeOK; +} diff --git a/src/server/game/TC9Sidecar/TC9GrpcHandler.h b/src/server/game/TC9Sidecar/TC9GrpcHandler.h new file mode 100644 index 000000000..438ea6550 --- /dev/null +++ b/src/server/game/TC9Sidecar/TC9GrpcHandler.h @@ -0,0 +1,50 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef _TC9_GRPC_HANDLER_H +#define _TC9_GRPC_HANDLER_H + +#include "Common.h" +#include "libsidecar.h" + +class ToCloud9GrpcHandler +{ +public: + ToCloud9GrpcHandler() {}; + ~ToCloud9GrpcHandler() {}; + + // Items + static GetPlayerItemsByGuidsResponse GetPlayerItemsByGuids(uint64 player, uint64* items, int items_len); + static RemoveItemsWithGuidsFromPlayerResponse RemoveItemsWithGuidsFromPlayer(uint64 player, uint64* items, int itemsLen, uint64 assignToPlayer); + static PlayerItemErrorCode AddExistingItemToPlayer(AddExistingItemToPlayerRequest*); + + // Money + static GetMoneyForPlayerResponse GetMoneyForPlayer(uint64 player); + static ModifyMoneyForPlayerResponse ModifyMoneyForPlayer(uint64 player, int32 value); + + // Interactions + static CanPlayerInteractWithGOAndTypeResponse CanPlayerInteractWithGOAndType(uint64 player, uint64 go, uint8 goType); + static CanPlayerInteractWithNPCAndFlagsResponse CanPlayerInteractWithNPCAndFlags(uint64 player, uint64 npc, uint32 unitFlags); + + // Battlegrounds + static BattlegroundStartResponse StartBattleground(BattlegroundStartRequest* request); + static BattlegroundErrorCode AddPlayersToBattleground(BattlegroundAddPlayersRequest* request); + static BattlegroundJoinCheckErrorCode CanPlayerJoinBattlegroundQueue(uint64 player); + static BattlegroundJoinCheckErrorCode CanPlayerTeleportToBattleground(uint64 player); +}; + +#endif // _TC9_GRPC_HANDLER_H diff --git a/src/server/game/TC9Sidecar/TC9GuildHooks.cpp b/src/server/game/TC9Sidecar/TC9GuildHooks.cpp new file mode 100644 index 000000000..73abcb89b --- /dev/null +++ b/src/server/game/TC9Sidecar/TC9GuildHooks.cpp @@ -0,0 +1,47 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "TC9GuildHooks.h" +#include "ObjectAccessor.h" +#include "Player.h" + +void ToCloud9GuildHooks::OnGuildMemberAdded(uint64 guild, uint64 character) +{ + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(character)); + if (!player) + return; + + player->SetInGuild(guild); +} + +void ToCloud9GuildHooks::OnGuildMemberRemoved(uint64 /*guild*/, uint64 character) +{ + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(character)); + if (!player) + return; + + player->SetInGuild(0); +} + +void ToCloud9GuildHooks::OnGuildMemberLeft(uint64 /*guild*/, uint64 character) +{ + Player *player = ObjectAccessor::FindPlayer(ObjectGuid(character)); + if (!player) + return; + + player->SetInGuild(0); +} diff --git a/src/server/game/TC9Sidecar/TC9GuildHooks.h b/src/server/game/TC9Sidecar/TC9GuildHooks.h new file mode 100644 index 000000000..e02da31fd --- /dev/null +++ b/src/server/game/TC9Sidecar/TC9GuildHooks.h @@ -0,0 +1,34 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef _TC9_GUILD_HOOKS_H +#define _TC9_GUILD_HOOKS_H + +#include "Common.h" + +class ToCloud9GuildHooks +{ +public: + ToCloud9GuildHooks() {}; + ~ToCloud9GuildHooks() {}; + + static void OnGuildMemberAdded(uint64 guild, uint64 character); + static void OnGuildMemberRemoved(uint64 guild, uint64 character); + static void OnGuildMemberLeft(uint64 guild, uint64 character); +}; + +#endif // _TC9_GUILD_HOOKS_H diff --git a/src/server/game/TC9Sidecar/TC9Sidecar.cpp b/src/server/game/TC9Sidecar/TC9Sidecar.cpp new file mode 100644 index 000000000..ef808a9c5 --- /dev/null +++ b/src/server/game/TC9Sidecar/TC9Sidecar.cpp @@ -0,0 +1,268 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "TC9Sidecar.h" +#include "Config.h" +#include "InstanceSaveMgr.h" +#include "libsidecar.h" +#include "Log.h" +#include "MapMgr.h" +#include "Player.h" +#include "TC9GroupHooks.h" +#include "TC9GrpcHandler.h" +#include "TC9GuildHooks.h" +#include "UpdateTime.h" +#include "WorldSessionMgr.h" + +#include + +#define AVAILABLE_MAPS_ALL_MAPS "" + +MonitoringDataCollectorResponse HandleMonitoringRequest(); + +ToCloud9Sidecar* ToCloud9Sidecar::instance() +{ + static ToCloud9Sidecar instance; + return &instance; +} + +ToCloud9Sidecar::ToCloud9Sidecar() : _clusterModeEnabled(false), _isCrossrealm(false) +{ +} + +void ToCloud9Sidecar::Init(uint16 port, int realmId) +{ + _clusterModeEnabled = sConfigMgr->GetOption("Cluster.Enabled", false); + + if (_clusterModeEnabled) + { + uint32 *assignedMaps; + int assignedMapsSize = 0; + + _isCrossrealm = sConfigMgr->GetOption("Cluster.IsCrossrealm", false); + + std::string availableMaps = sConfigMgr->GetOption("Cluster.AvailableMaps", AVAILABLE_MAPS_ALL_MAPS); + TC9InitLib(port, realmId, _isCrossrealm, availableMaps.data(), &assignedMaps, &assignedMapsSize); + + for (int i = 0; i < MAX_MAP_ID; i++) + _assignedMapsByID[i] = false; + + for (int i = 0; i < assignedMapsSize; i++) + { + uint32 mapId = assignedMaps[i]; + if (mapId >= MAX_MAP_ID) + { + LOG_ERROR("server", "ToCloud9Sidecar::Init: map id {} out of range [0, {}), ignored", + mapId, MAX_MAP_ID); + continue; + } + _assignedMapsByID[mapId] = true; + } + + if (assignedMapsSize > 0) + free(assignedMaps); + + SetupHooks(); + SetupGrpcHandlers(); + } +} + +void ToCloud9Sidecar::Deinit() +{ + if (_clusterModeEnabled) + TC9GracefulShutdown(); +} + +void ToCloud9Sidecar::SetupHooks() +{ + TC9SetOnMapsReassignedHook(&ToCloud9Sidecar::OnMapsReassigned); + + TC9SetOnGuildMemberLeftHook(&ToCloud9GuildHooks::OnGuildMemberLeft); + TC9SetOnGuildMemberAddedHook(&ToCloud9GuildHooks::OnGuildMemberAdded); + TC9SetOnGuildMemberRemovedHook(&ToCloud9GuildHooks::OnGuildMemberRemoved); + + TC9SetOnGroupCreatedHook(&ToCloud9GroupHooks::OnGroupCreated); + TC9SetOnGroupDisbandedHook(&ToCloud9GroupHooks::OnGroupDisbanded); + TC9SetOnGroupMemberAddedHook(&ToCloud9GroupHooks::OnGroupMemberAdded); + TC9SetOnGroupMemberRemovedHook(&ToCloud9GroupHooks::OnGroupMemberRemoved); + TC9SetOnGroupLootTypeChangedHook(&ToCloud9GroupHooks::OnGroupLootTypeChanged); + TC9SetOnGroupConvertedToRaidHook(&ToCloud9GroupHooks::OnGroupConvertedToRaid); + TC9SetOnGroupRaidDifficultyChangedHook(&ToCloud9GroupHooks::OnGroupRaidDifficultyChanged); + TC9SetOnGroupDungeonDifficultyChangedHook(&ToCloud9GroupHooks::OnGroupDungeonDifficultyChanged); +} + +void ToCloud9Sidecar::SetupGrpcHandlers() +{ + TC9SetGetPlayerItemsByGuidsHandler(&ToCloud9GrpcHandler::GetPlayerItemsByGuids); + TC9SetRemoveItemsWithGuidsFromPlayerHandler(&ToCloud9GrpcHandler::RemoveItemsWithGuidsFromPlayer); + TC9SetAddExistingItemToPlayerHandler(&ToCloud9GrpcHandler::AddExistingItemToPlayer); + + TC9SetGetMoneyForPlayerHandler(&ToCloud9GrpcHandler::GetMoneyForPlayer); + TC9SetModifyMoneyForPlayerHandler(&ToCloud9GrpcHandler::ModifyMoneyForPlayer); + + TC9SetCanPlayerInteractWithGOAndTypeHandler(&ToCloud9GrpcHandler::CanPlayerInteractWithGOAndType); + TC9SetCanPlayerInteractWithNPCAndFlagsHandler(&ToCloud9GrpcHandler::CanPlayerInteractWithNPCAndFlags); + + TC9SetBattlegroundStartHandler(&ToCloud9GrpcHandler::StartBattleground); + TC9SetBattlegroundAddPlayersHandler(&ToCloud9GrpcHandler::AddPlayersToBattleground); + TC9SetCanPlayerJoinBattlegroundQueueHandler(&ToCloud9GrpcHandler::CanPlayerJoinBattlegroundQueue); + TC9SetCanPlayerTeleportToBattlegroundHandler(&ToCloud9GrpcHandler::CanPlayerTeleportToBattleground); + + TC9SetMonitoringDataCollectorHandler(&HandleMonitoringRequest); +} + +void ToCloud9Sidecar::ProcessHooks() +{ + TC9ProcessEventsHooks(); +} + +void ToCloud9Sidecar::ProcessGrpcOrHttpRequests() +{ + TC9ProcessGRPCOrHTTPRequests(); +} + +void ToCloud9Sidecar::ProcessAsyncTasks() +{ + _asyncTasksProcessor.ProcessReadyCallbacks(); +} + +bool ToCloud9Sidecar::IsMapAssigned(uint32 mapId) +{ + if (mapId >= MAX_MAP_ID) + return false; + + return _assignedMapsByID[mapId]; +} + +uint32 ToCloud9Sidecar::GenerateCharacterGuid(uint16 realmId) +{ + return uint32(TC9GetNextAvailableCharacterGuid(realmId)); +} + +uint32 ToCloud9Sidecar::GenerateItemGuid(uint16 realmId) +{ + return uint32(TC9GetNextAvailableItemGuid(realmId)); +} + +uint32 ToCloud9Sidecar::GenerateInstanceGuid(uint16 realmId) +{ + return uint32(TC9GetNextAvailableInstanceGuid(realmId)); +} + +void ToCloud9Sidecar::OnPlayerLeftBattleground(uint64 playerGUID, uint32 realmID, uint32 instanceID) +{ + TC9PlayerLeftBattleground(playerGUID, realmID, instanceID); +} + +void ToCloud9Sidecar::OnBattlegroundStatusChanged(uint32 instanceID, uint8 status) +{ + TC9BattlegroundStatusChanged(instanceID, status); +} + +bool ToCloud9Sidecar::NatsPublish(std::string const& subject, std::string const& payload) +{ + if (!_clusterModeEnabled) + return false; + + if (payload.size() > size_t(std::numeric_limits::max())) + return false; + + return TC9NatsPublish(subject.c_str(), payload.c_str(), int(payload.size())) == 0; +} + +bool ToCloud9Sidecar::NatsSubscribe(std::string const& subject, void (*handler)(char const*, char const*, int)) +{ + if (!_clusterModeEnabled || !handler) + return false; + + return TC9NatsSubscribe(subject.c_str(), handler) == 0; +} + +void ToCloud9Sidecar::OnMapsReassigned(uint32* addedMaps, int addedMapsSize, uint32* removedMaps, int removedMapsSize) +{ + std::vector newMapIDs; + newMapIDs.reserve(addedMapsSize > 0 ? addedMapsSize : 0); + + for (int i = 0; i < addedMapsSize; i++) + { + uint32 mapId = addedMaps[i]; + if (mapId >= MAX_MAP_ID) + { + LOG_ERROR("server", "ToCloud9Sidecar::OnMapsReassigned: added map id {} out of range [0, {}), ignored", + mapId, MAX_MAP_ID); + continue; + } + + sToCloud9Sidecar->_assignedMapsByID[mapId] = true; + newMapIDs.push_back(mapId); + + if (Map* map = sMapMgr->FindBaseNonInstanceMap(mapId)) + map->StopPlayersRedirectKickTimer(); + } + + for (int i = 0; i < removedMapsSize; i++) + { + uint32 mapId = removedMaps[i]; + if (mapId >= MAX_MAP_ID) + { + LOG_ERROR("server", "ToCloud9Sidecar::OnMapsReassigned: removed map id {} out of range [0, {}), ignored", + mapId, MAX_MAP_ID); + continue; + } + + sToCloud9Sidecar->_assignedMapsByID[mapId] = false; + + if (Map* map = sMapMgr->FindBaseNonInstanceMap(mapId)) + map->StartPlayersRedirectKickTimer(); + } + + if (!newMapIDs.empty()) + { + auto loadRowsPtr = std::make_shared(); + + AsyncTask task( + [loadRowsPtr, newMapIDs]() -> bool { + LOG_INFO("server", "Starting to load data for newly assigned maps..."); + + *loadRowsPtr = sInstanceSaveMgr->LoadInstanceSavesAndBindsForMapIDs(newMapIDs); + return true; + }, + [loadRowsPtr, newMapIDs](bool) { + sInstanceSaveMgr->MergeWithNewInstanceSaves(*loadRowsPtr); + TC9ReadyToAcceptPlayersFromMaps((uint32_t*)newMapIDs.data(), newMapIDs.size()); + + LOG_INFO("server", "Finished loading data for newly assigned maps."); + } + ); + + task.ExecuteAsync(); + sToCloud9Sidecar->_asyncTasksProcessor.AddCallback(std::move(task)); + } +} + +MonitoringDataCollectorResponse HandleMonitoringRequest() +{ + MonitoringDataCollectorResponse res; + res.errorCode = MonitoringErrorCodeNoError; + res.diffMean = sWorldUpdateTime.GetAverageUpdateTime(); + res.diffMedian = sWorldUpdateTime.GetPercentile(50); + res.diff95Percentile = sWorldUpdateTime.GetPercentile(95); + res.diff99Percentile = sWorldUpdateTime.GetPercentile(99); + res.diffMaxPercentile = sWorldUpdateTime.GetPercentile(100); + res.connectedPlayers = sWorldSessionMgr->GetActiveSessionCount(); + return res; +} diff --git a/src/server/game/TC9Sidecar/TC9Sidecar.h b/src/server/game/TC9Sidecar/TC9Sidecar.h new file mode 100644 index 000000000..417c88914 --- /dev/null +++ b/src/server/game/TC9Sidecar/TC9Sidecar.h @@ -0,0 +1,78 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef _TC9_SIDECAR_H +#define _TC9_SIDECAR_H + +#include "AsyncCallbackProcessor.h" +#include "AsyncTask.h" +#include "Common.h" +#include "ObjectGuid.h" + +#define MAX_MAP_ID 800 // Probably too much, but let's lean towards caution. + +class ToCloud9Sidecar +{ +private: + ToCloud9Sidecar(); + ~ToCloud9Sidecar() {}; + +public: + static ToCloud9Sidecar* instance(); + + void Init(uint16 port, int realmId); + void Deinit(); + + bool ClusterModeEnabled() { return _clusterModeEnabled; } + bool IsCrossrealm() { return _isCrossrealm; } + + bool IsMapAssigned(uint32 mapId); + + void SetupHooks(); + void SetupGrpcHandlers(); + + void ProcessHooks(); + void ProcessGrpcOrHttpRequests(); + void ProcessAsyncTasks(); + + uint32 GenerateCharacterGuid(uint16 realmId = DEFAULT_NON_CROSSREALM_REALM_ID); + uint32 GenerateItemGuid(uint16 realmId = DEFAULT_NON_CROSSREALM_REALM_ID); + uint32 GenerateInstanceGuid(uint16 realmId = DEFAULT_NON_CROSSREALM_REALM_ID); + + void OnPlayerLeftBattleground(uint64 playerGUID, uint32 realmID, uint32 instanceID); + void OnBattlegroundStatusChanged(uint32 instanceID, uint8 status); + + // Generic NATS pub/sub (single choke point for in-process modules). + // No-ops outside cluster mode. Subscribe callbacks run on the world + // thread (ProcessHooks). + bool NatsPublish(std::string const& subject, std::string const& payload); + bool NatsSubscribe(std::string const& subject, void (*handler)(char const* subject, char const* payload, int payloadLen)); + +private: + static void OnMapsReassigned(uint32* addedMaps, int addedMapsSize, uint32* removedMaps, int removedMapsSize); + + bool _clusterModeEnabled; + bool _isCrossrealm; + + bool _assignedMapsByID[MAX_MAP_ID]; + + AsyncCallbackProcessor> _asyncTasksProcessor; +}; + +#define sToCloud9Sidecar ToCloud9Sidecar::instance() + +#endif // _TC9_SIDECAR_H diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index ad54e8364..28351daab 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -23,8 +23,8 @@ #include "AccountMgr.h" #include "AchievementMgr.h" #include "AddonMgr.h" -#include "ArenaTeamMgr.h" #include "ArenaSeasonMgr.h" +#include "ArenaTeamMgr.h" #include "AuctionHouseMgr.h" #include "AutobroadcastMgr.h" #include "BattlefieldMgr.h" @@ -53,8 +53,8 @@ #include "GridNotifiersImpl.h" #include "GroupMgr.h" #include "GuildMgr.h" -#include "IPLocation.h" #include "InstanceSaveMgr.h" +#include "IPLocation.h" #include "ItemEnchantmentMgr.h" #include "LFGMgr.h" #include "Language.h" @@ -82,6 +82,7 @@ #include "SmartAI.h" #include "SpellMgr.h" #include "TaskScheduler.h" +#include "TC9Sidecar.h" #include "TicketMgr.h" #include "Transport.h" #include "TransportMgr.h" @@ -1341,6 +1342,24 @@ void World::Update(uint32 diff) sScriptMgr->OnWorldUpdate(diff); } + if (sToCloud9Sidecar->ClusterModeEnabled()) + { + { + METRIC_TIMER("world_update_time", METRIC_TAG("type", "Process TC9 async tasks")); + sToCloud9Sidecar->ProcessAsyncTasks(); + } + + { + METRIC_TIMER("world_update_time", METRIC_TAG("type", "Process TC9 hooks")); + sToCloud9Sidecar->ProcessHooks(); + } + + { + METRIC_TIMER("world_update_time", METRIC_TAG("type", "Process TC9 gRPC and HTTP requests")); + sToCloud9Sidecar->ProcessGrpcOrHttpRequests(); + } + } + { METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update metrics")); // Stats logger update diff --git a/src/server/game/World/WorldState.cpp b/src/server/game/World/WorldState.cpp index 269e53cc7..5441466b7 100644 --- a/src/server/game/World/WorldState.cpp +++ b/src/server/game/World/WorldState.cpp @@ -22,6 +22,7 @@ #include "MapMgr.h" #include "Player.h" #include "SharedDefines.h" +#include "TC9Sidecar.h" #include "UnitAI.h" #include "Weather.h" #include "WorldState.h" @@ -160,20 +161,27 @@ void WorldState::LoadWorldStates() // Setting a worldstate will save it to DB void WorldState::setWorldState(uint32 index, uint64 timeValue) { - auto const& it = _worldstates.find(index); - if (it != _worldstates.end()) + // Crossrealm nodes must not persist worldstates: their CharacterDatabase is + // the routing proxy and the write would land in an arbitrary realm DB. The + // in-memory value still has to be updated so read-modify-write users + // (e.g. the Wintergrasp clock) keep working. + if (!sToCloud9Sidecar->IsCrossrealm()) { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_WORLDSTATE); - stmt->SetData(0, uint32(timeValue)); - stmt->SetData(1, index); - CharacterDatabase.Execute(stmt); - } - else - { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_WORLDSTATE); - stmt->SetData(0, index); - stmt->SetData(1, uint32(timeValue)); - CharacterDatabase.Execute(stmt); + auto const& it = _worldstates.find(index); + if (it != _worldstates.end()) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_WORLDSTATE); + stmt->SetData(0, uint32(timeValue)); + stmt->SetData(1, index); + CharacterDatabase.Execute(stmt); + } + else + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_WORLDSTATE); + stmt->SetData(0, index); + stmt->SetData(1, uint32(timeValue)); + CharacterDatabase.Execute(stmt); + } } _worldstates[index] = timeValue; diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt index e2320d251..51ab9a08f 100644 --- a/src/server/scripts/CMakeLists.txt +++ b/src/server/scripts/CMakeLists.txt @@ -232,7 +232,8 @@ target_link_libraries(scripts PRIVATE acore-core-interface PUBLIC - game-interface) + game-interface + libsidecar) target_include_directories(scripts PUBLIC