From 3c26b5df24ca34f5756ff36f6f69ed6354813f2c Mon Sep 17 00:00:00 2001 From: Pursche Date: Sat, 30 Aug 2025 16:43:05 +0200 Subject: [PATCH] Add ProximityTriggers Add ProximityTriggers with OnEnter, OnExit and OnStay callbacks The client has completely clientside triggers and server authorative (sends OnEnter) Client gets replicated triggers from server Add cheat commands to Add and Remove triggers Add PlayerTag and LocalPlayerTag Update Engine submodule --- .../ECS/Components/ProximityTrigger.h | 20 ++ .../Game-Lib/Game-Lib/ECS/Components/Tags.h | 2 + Source/Game-Lib/Game-Lib/ECS/Scheduler.cpp | 6 +- .../Singletons/ProximityTriggerSingleton.h | 18 ++ .../ECS/Systems/CharacterController.cpp | 5 + .../ECS/Systems/NetworkConnection.cpp | 68 +++++++ .../ECS/Systems/ProximityTriggers.cpp | 153 +++++++++++++++ .../Game-Lib/ECS/Systems/ProximityTriggers.h | 12 ++ .../Game-Lib/ECS/Util/MessageBuilderUtil.cpp | 38 +++- .../Game-Lib/ECS/Util/MessageBuilderUtil.h | 7 + .../ECS/Util/ProximityTriggerUtil.cpp | 83 +++++++++ .../Game-Lib/ECS/Util/ProximityTriggerUtil.h | 15 ++ .../GameConsole/GameConsoleCommandHandler.cpp | 2 + .../GameConsole/GameConsoleCommands.cpp | 37 ++++ .../GameConsole/GameConsoleCommands.h | 2 + .../Handlers/TriggerEventHandler.cpp | 176 ++++++++++++++++++ .../Scripting/Handlers/TriggerEventHandler.h | 40 ++++ .../Game-Lib/Game-Lib/Scripting/LuaDefines.h | 22 +++ .../Game-Lib/Scripting/LuaManager.cpp | 2 + Source/Resources/Scripts/Types.def | 11 ++ Submodules/Engine | 2 +- 21 files changed, 718 insertions(+), 3 deletions(-) create mode 100644 Source/Game-Lib/Game-Lib/ECS/Components/ProximityTrigger.h create mode 100644 Source/Game-Lib/Game-Lib/ECS/Singletons/ProximityTriggerSingleton.h create mode 100644 Source/Game-Lib/Game-Lib/ECS/Systems/ProximityTriggers.cpp create mode 100644 Source/Game-Lib/Game-Lib/ECS/Systems/ProximityTriggers.h create mode 100644 Source/Game-Lib/Game-Lib/ECS/Util/ProximityTriggerUtil.cpp create mode 100644 Source/Game-Lib/Game-Lib/ECS/Util/ProximityTriggerUtil.h create mode 100644 Source/Game-Lib/Game-Lib/Scripting/Handlers/TriggerEventHandler.cpp create mode 100644 Source/Game-Lib/Game-Lib/Scripting/Handlers/TriggerEventHandler.h diff --git a/Source/Game-Lib/Game-Lib/ECS/Components/ProximityTrigger.h b/Source/Game-Lib/Game-Lib/ECS/Components/ProximityTrigger.h new file mode 100644 index 0000000..c0c6058 --- /dev/null +++ b/Source/Game-Lib/Game-Lib/ECS/Components/ProximityTrigger.h @@ -0,0 +1,20 @@ +#pragma once +#include + +#include +DECLARE_GENERIC_BITWISE_OPERATORS(Generated::ProximityTriggerFlagEnum); + +#include +#include + +namespace ECS::Components +{ + struct ProximityTrigger + { + static const u32 INVALID_NETWORK_ID = std::numeric_limits().max(); + + u32 networkID = INVALID_NETWORK_ID; + Generated::ProximityTriggerFlagEnum flags = Generated::ProximityTriggerFlagEnum::None; + std::set playersInside; // Entities currently inside the trigger + }; +} \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/ECS/Components/Tags.h b/Source/Game-Lib/Game-Lib/ECS/Components/Tags.h index 112b161..29c9dc6 100644 --- a/Source/Game-Lib/Game-Lib/ECS/Components/Tags.h +++ b/Source/Game-Lib/Game-Lib/ECS/Components/Tags.h @@ -7,4 +7,6 @@ namespace ECS::Components struct AnimatingTag {}; struct UnitRebuildSkinTexture {}; struct UnitRebuildGeosets {}; + struct LocalPlayerTag {}; + struct PlayerTag {}; } \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/ECS/Scheduler.cpp b/Source/Game-Lib/Game-Lib/ECS/Scheduler.cpp index 5ecd51a..a90c17f 100644 --- a/Source/Game-Lib/Game-Lib/ECS/Scheduler.cpp +++ b/Source/Game-Lib/Game-Lib/ECS/Scheduler.cpp @@ -7,21 +7,23 @@ #include "Game-Lib/ECS/Singletons/DayNightCycle.h" #include "Game-Lib/ECS/Singletons/EngineStats.h" #include "Game-Lib/ECS/Singletons/JoltState.h" +#include "Game-Lib/ECS/Singletons/ProximityTriggerSingleton.h" #include "Game-Lib/ECS/Singletons/RenderState.h" #include "Game-Lib/ECS/Systems/Animation.h" #include "Game-Lib/ECS/Systems/UpdateAreaLights.h" #include "Game-Lib/ECS/Systems/CalculateCameraMatrices.h" #include "Game-Lib/ECS/Systems/CalculateShadowCameraMatrices.h" +#include "Game-Lib/ECS/Systems/CalculateTransformMatrices.h" #include "Game-Lib/ECS/Systems/UpdateDayNightCycle.h" #include "Game-Lib/ECS/Systems/DrawDebugMesh.h" #include "Game-Lib/ECS/Systems/FreeflyingCamera.h" #include "Game-Lib/ECS/Systems/OrbitalCamera.h" #include "Game-Lib/ECS/Systems/NetworkConnection.h" +#include "Game-Lib/ECS/Systems/ProximityTriggers.h" #include "Game-Lib/ECS/Systems/UpdateUnitEntities.h" #include "Game-Lib/ECS/Systems/UpdatePhysics.h" #include "Game-Lib/ECS/Systems/UpdateScripts.h" #include "Game-Lib/ECS/Systems/UpdateSkyboxes.h" -#include "Game-Lib/ECS/Systems/CalculateTransformMatrices.h" #include "Game-Lib/ECS/Systems/UpdateAABBs.h" #include "Game-Lib/ECS/Systems/CharacterController.h" #include "Game-Lib/ECS/Systems/UI/HandleInput.h" @@ -63,6 +65,7 @@ namespace ECS entt::registry::context& ctx = gameRegistry.ctx(); ctx.emplace(); ctx.emplace(); + ctx.emplace(); // UI entt::registry& uiRegistry = *registries.uiRegistry; @@ -99,6 +102,7 @@ namespace ECS Systems::CalculateTransformMatrices::Update(gameRegistry, clampedDeltaTime); Systems::UpdateAABBs::Update(gameRegistry, clampedDeltaTime); Systems::UpdatePhysics::Update(gameRegistry, clampedDeltaTime); + Systems::ProximityTriggers::Update(gameRegistry, clampedDeltaTime); // Note: For now UpdateScripts should always be run last Systems::UpdateScripts::Update(gameRegistry, clampedDeltaTime); diff --git a/Source/Game-Lib/Game-Lib/ECS/Singletons/ProximityTriggerSingleton.h b/Source/Game-Lib/Game-Lib/ECS/Singletons/ProximityTriggerSingleton.h new file mode 100644 index 0000000..26b4e95 --- /dev/null +++ b/Source/Game-Lib/Game-Lib/ECS/Singletons/ProximityTriggerSingleton.h @@ -0,0 +1,18 @@ +#pragma once +#include + +#include +#include +#include + +namespace ECS::Singletons +{ + struct ProximityTriggerSingleton + { + public: + RTree proximityTriggers; + + robin_hood::unordered_map> entityToProximityTriggers; // Maps entity to the triggers it is currently inside + robin_hood::unordered_map triggerIDToEntity; // Maps trigger ID to entity + }; +} \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/ECS/Systems/CharacterController.cpp b/Source/Game-Lib/Game-Lib/ECS/Systems/CharacterController.cpp index 6bce685..d31ad87 100644 --- a/Source/Game-Lib/Game-Lib/ECS/Systems/CharacterController.cpp +++ b/Source/Game-Lib/Game-Lib/ECS/Systems/CharacterController.cpp @@ -9,6 +9,7 @@ #include "Game-Lib/ECS/Components/Model.h" #include "Game-Lib/ECS/Components/MovementInfo.h" #include "Game-Lib/ECS/Components/Name.h" +#include "Game-Lib/ECS/Components/Tags.h" #include "Game-Lib/ECS/Components/Unit.h" #include "Game-Lib/ECS/Components/UnitCustomization.h" #include "Game-Lib/ECS/Components/UnitEquipment.h" @@ -689,6 +690,7 @@ namespace ECS::Systems auto& name = registry.get_or_emplace(characterSingleton.moverEntity); auto& aabb = registry.get_or_emplace(characterSingleton.moverEntity); + registry.emplace_or_replace(characterSingleton.moverEntity); auto& transform = registry.get_or_emplace(characterSingleton.moverEntity); auto& moverModel = registry.get_or_emplace(characterSingleton.moverEntity); auto& movementInfo = registry.get_or_emplace(characterSingleton.moverEntity); @@ -720,7 +722,10 @@ namespace ECS::Systems ModelLoader* modelLoader = ServiceLocator::GetGameRenderer()->GetModelLoader(); modelLoader->LoadDisplayIDForEntity(characterSingleton.moverEntity, moverModel, Database::Unit::DisplayInfoType::Creature, 50); + + registry.emplace_or_replace(characterSingleton.moverEntity); } + registry.emplace_or_replace(characterSingleton.moverEntity); f32 width = 0.4166f; f32 height = 1.9134f; diff --git a/Source/Game-Lib/Game-Lib/ECS/Systems/NetworkConnection.cpp b/Source/Game-Lib/Game-Lib/ECS/Systems/NetworkConnection.cpp index b2cf601..3a694b1 100644 --- a/Source/Game-Lib/Game-Lib/ECS/Systems/NetworkConnection.cpp +++ b/Source/Game-Lib/Game-Lib/ECS/Systems/NetworkConnection.cpp @@ -11,6 +11,8 @@ #include "Game-Lib/ECS/Components/MovementInfo.h" #include "Game-Lib/ECS/Components/Name.h" #include "Game-Lib/ECS/Components/Item.h" +#include "Game-Lib/ECS/Components/ProximityTrigger.h" +#include "Game-Lib/ECS/Components/Tags.h" #include "Game-Lib/ECS/Components/Unit.h" #include "Game-Lib/ECS/Components/UnitCustomization.h" #include "Game-Lib/ECS/Components/UnitEquipment.h" @@ -18,8 +20,10 @@ #include "Game-Lib/ECS/Components/UnitStatsComponent.h" #include "Game-Lib/ECS/Singletons/CharacterSingleton.h" #include "Game-Lib/ECS/Singletons/NetworkState.h" +#include "Game-Lib/ECS/Singletons/ProximityTriggerSingleton.h" #include "Game-Lib/ECS/Singletons/Database/ClientDBSingleton.h" #include "Game-Lib/ECS/Util/MessageBuilderUtil.h" +#include "Game-Lib/ECS/Util/ProximityTriggerUtil.h" #include "Game-Lib/ECS/Util/Transforms.h" #include "Game-Lib/Rendering/GameRenderer.h" #include "Game-Lib/Rendering/Model/ModelLoader.h" @@ -33,6 +37,8 @@ #include +#include + #include #include @@ -180,6 +186,7 @@ namespace ECS::Systems entt::entity newEntity = registry->create(); registry->emplace(newEntity); + registry->emplace(newEntity); registry->emplace(newEntity); registry->emplace(newEntity); registry->emplace(newEntity); @@ -196,6 +203,11 @@ namespace ECS::Systems unit.networkID = networkID; unit.targetEntity = entt::null; + if (unit.networkID.GetType() == GameDefine::ObjectGuid::Type::Player) + { + registry->emplace_or_replace(newEntity); + } + TransformSystem& transformSystem = TransformSystem::Get(*registry); transformSystem.SetWorldPosition(newEntity, position); transformSystem.SetWorldRotation(newEntity, rotation); @@ -1127,6 +1139,48 @@ namespace ECS::Systems } return true; } + bool HandleOnTriggerCreate(Network::SocketID socketID, Network::Message& message) + { + u32 triggerID; + std::string name; + Generated::ProximityTriggerFlagEnum flags; + u16 mapID; + vec3 position; + vec3 extents; + + if (!message.buffer->GetU32(triggerID)) + return false; + + if (!message.buffer->GetString(name)) + return false; + + if (!message.buffer->Get(flags)) + return false; + + if (!message.buffer->GetU16(mapID)) + return false; + + if (!message.buffer->Get(position)) + return false; + + if (!message.buffer->Get(extents)) + return false; + + entt::registry& registry = *ServiceLocator::GetEnttRegistries()->gameRegistry; + ECS::Util::ProximityTriggerUtil::CreateTrigger(registry, triggerID, name, flags, mapID, position, extents); + return true; + } + bool HandleOnTriggerDestroy(Network::SocketID socketID, Network::Message& message) + { + u32 triggerID; + + if (!message.buffer->GetU32(triggerID)) + return false; + + entt::registry& registry = *ServiceLocator::GetEnttRegistries()->gameRegistry; + ECS::Util::ProximityTriggerUtil::DestroyTrigger(registry, triggerID); + return true; + } void NetworkConnection::Init(entt::registry& registry) { @@ -1166,6 +1220,9 @@ namespace ECS::Systems networkState.gameMessageRouter->SetMessageHandler(Network::GameOpcode::Server_SendSpellCastResult, Network::GameMessageHandler(Network::ConnectionStatus::Connected, 0u, -1, &HandleOnSpellCastResult)); networkState.gameMessageRouter->SetMessageHandler(Network::GameOpcode::Server_SendCombatEvent, Network::GameMessageHandler(Network::ConnectionStatus::Connected, 0u, -1, &HandleOnCombatEvent)); + + networkState.gameMessageRouter->SetMessageHandler(Network::GameOpcode::Server_TriggerCreate, Network::GameMessageHandler(Network::ConnectionStatus::Connected, 0u, -1, &HandleOnTriggerCreate)); + networkState.gameMessageRouter->SetMessageHandler(Network::GameOpcode::Server_TriggerDestroy, Network::GameMessageHandler(Network::ConnectionStatus::Connected, 0u, -1, &HandleOnTriggerDestroy)); } } @@ -1265,6 +1322,17 @@ namespace ECS::Systems registry.destroy(entity); } + // Clean up any networked proximity triggers + auto& proximityTriggerSingleton = ctx.get(); + auto triggerView = registry.view(); + triggerView.each([&](entt::entity triggerEntity, Components::ProximityTrigger& proximityTrigger) + { + if (proximityTrigger.networkID == Components::ProximityTrigger::INVALID_NETWORK_ID) + return; + + proximityTriggerSingleton.proximityTriggers.Remove(triggerEntity); + }); + networkState.lastPingTime = 0u; networkState.lastPongTime = 0u; networkState.ping = 0; diff --git a/Source/Game-Lib/Game-Lib/ECS/Systems/ProximityTriggers.cpp b/Source/Game-Lib/Game-Lib/ECS/Systems/ProximityTriggers.cpp new file mode 100644 index 0000000..66baf14 --- /dev/null +++ b/Source/Game-Lib/Game-Lib/ECS/Systems/ProximityTriggers.cpp @@ -0,0 +1,153 @@ +#include "ProximityTriggers.h" + +#include "Game-Lib/ECS/Components/AABB.h" +#include "Game-Lib/ECS/Components/Events.h" +#include "Game-Lib/ECS/Components/Name.h" +#include "Game-Lib/ECS/Components/ProximityTrigger.h" +#include "Game-Lib/ECS/Components/Tags.h" +#include "Game-Lib/ECS/Singletons/CharacterSingleton.h" +#include "Game-Lib/ECS/Singletons/NetworkState.h" +#include "Game-Lib/ECS/Singletons/ProximityTriggerSingleton.h" +#include "Game-Lib/ECS/Util/EventUtil.h" +#include "Game-Lib/ECS/Util/MessageBuilderUtil.h" +#include "Game-Lib/ECS/Util/Transforms.h" +#include "Game-Lib/Scripting/LuaDefines.h" +#include "Game-Lib/Scripting/LuaManager.h" +#include "Game-Lib/Scripting/Handlers/TriggerEventHandler.h" + +#include +#include + +#include + +#include +#include + + +namespace ECS::Systems +{ + bool IsTransformInAABB(const Components::Transform& transform, const Components::WorldAABB& aabb) + { + const vec3& p = transform.GetWorldPosition(); + + // Center/half-extents form reduces comparisons against two bounds to one abs compare per axis. + const vec3 center = (aabb.min + aabb.max) * 0.5f; + const vec3 half = (aabb.max - aabb.min) * 0.5f; + + const vec3 d = glm::abs(p - center); // component-wise abs + return (d.x <= half.x) && (d.y <= half.y) && (d.z <= half.z); + } + + void OnEnter(Scripting::TriggerEventHandler* triggerEventHandler, Scripting::LuaManager* luaManager, ECS::Singletons::NetworkState& networkState, entt::entity triggerEntity, Components::ProximityTrigger& trigger, entt::entity playerEntity) + { + // This is an optimization so the server doesn't need to repeatedly test all triggers for all players + if ((trigger.flags & Generated::ProximityTriggerFlagEnum::IsServerAuthorative) != Generated::ProximityTriggerFlagEnum::None) + { + // Serverside event for sure + std::shared_ptr buffer = Bytebuffer::Borrow<16>(); + + if (!ECS::Util::MessageBuilder::ProximityTrigger::BuildProximityTriggerEnter(buffer, trigger.networkID)) + return; + + networkState.client->Send(buffer); + } + + // Clientside event + Scripting::LuaTriggerEventOnTriggerEnterData eventData = + { + .triggerID = entt::to_integral(triggerEntity), + .playerID = entt::to_integral(playerEntity) + }; + triggerEventHandler->CallEvent(luaManager->GetInternalState(), static_cast(Generated::LuaTriggerEventEnum::OnEnter), &eventData); + } + + void OnExit(Scripting::TriggerEventHandler* triggerEventHandler, Scripting::LuaManager* luaManager, ECS::Singletons::NetworkState& networkState, entt::entity triggerEntity, Components::ProximityTrigger& trigger, entt::entity playerEntity) + { + Scripting::LuaTriggerEventOnTriggerExitData eventData = + { + .triggerID = entt::to_integral(triggerEntity), + .playerID = entt::to_integral(playerEntity) + }; + triggerEventHandler->CallEvent(luaManager->GetInternalState(), static_cast(Generated::LuaTriggerEventEnum::OnExit), &eventData); + } + + void OnStay(Scripting::TriggerEventHandler* triggerEventHandler, Scripting::LuaManager* luaManager, ECS::Singletons::NetworkState& networkState, entt::entity triggerEntity, Components::ProximityTrigger& trigger, entt::entity playerEntity) + { + Scripting::LuaTriggerEventOnTriggerStayData eventData = + { + .triggerID = entt::to_integral(triggerEntity), + .playerID = entt::to_integral(playerEntity) + }; + triggerEventHandler->CallEvent(luaManager->GetInternalState(), static_cast(Generated::LuaTriggerEventEnum::OnStay), &eventData); + } + + void ProximityTriggers::Update(entt::registry& registry, f32 deltaTime) + { + ZoneScopedN("ECS::ProximityTriggers"); + + entt::registry::context& ctx = registry.ctx(); + auto& proximityTriggerSingleton = ctx.get(); + + auto view = registry.view(); + auto dirtyTriggerView = registry.view(); + + // Update all dirty triggers in the RTree + dirtyTriggerView.each([&](entt::entity triggerEntity, Components::WorldAABB& triggerAABB, Components::ProximityTrigger& proximityTrigger, Components::DirtyTransform&) + { + proximityTriggerSingleton.proximityTriggers.Remove(triggerEntity); + proximityTriggerSingleton.proximityTriggers.Insert(reinterpret_cast(&triggerAABB.min), reinterpret_cast(&triggerAABB.max), triggerEntity); + }); + + auto& characterSingleton = ctx.get(); + + entt::entity playerEntity = characterSingleton.moverEntity; + + if (playerEntity == entt::null || !registry.valid(playerEntity)) + { + return; + } + + auto* luaManager = ServiceLocator::GetLuaManager(); + auto* triggerEventHandler = luaManager->GetLuaHandler(Scripting::LuaHandlerType::TriggerEvent); + auto& playerAABB = registry.get(playerEntity); + auto& networkState = ctx.get(); + + // Get a COPY of the set of proximity triggers this player is inside of + auto& triggerList = proximityTriggerSingleton.entityToProximityTriggers[playerEntity]; + auto previousTriggerList = triggerList; + + proximityTriggerSingleton.proximityTriggers.Search(reinterpret_cast(&playerAABB.min), reinterpret_cast(&playerAABB.max), [&](const entt::entity triggerEntity) -> bool + { + auto& proximityTrigger = registry.get(triggerEntity); + auto& playersInTrigger = proximityTrigger.playersInside; + + bool wasInside = proximityTrigger.playersInside.contains(playerEntity); + if (wasInside) + { + OnStay(triggerEventHandler, luaManager, networkState, triggerEntity, proximityTrigger, playerEntity); + } + else + { + OnEnter(triggerEventHandler, luaManager, networkState, triggerEntity, proximityTrigger, playerEntity); + + proximityTrigger.playersInside.insert(playerEntity); + triggerList.insert(triggerEntity); + } + + // Remove the trigger from the set of triggers exited + previousTriggerList.erase(triggerEntity); + return true; + }); + + // Now we have a set of triggers that the player was inside of, but is no longer + for (const auto triggerEntity : previousTriggerList) + { + auto& proximityTrigger = registry.get(triggerEntity); + + OnExit(triggerEventHandler, luaManager, networkState, triggerEntity, proximityTrigger, playerEntity); + + proximityTrigger.playersInside.erase(playerEntity); + triggerList.erase(triggerEntity); + } + } +} \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/ECS/Systems/ProximityTriggers.h b/Source/Game-Lib/Game-Lib/ECS/Systems/ProximityTriggers.h new file mode 100644 index 0000000..09703b9 --- /dev/null +++ b/Source/Game-Lib/Game-Lib/ECS/Systems/ProximityTriggers.h @@ -0,0 +1,12 @@ +#pragma once +#include +#include + +namespace ECS::Systems +{ + class ProximityTriggers + { + public: + static void Update(entt::registry& registry, f32 deltaTime); + }; +} \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/ECS/Util/MessageBuilderUtil.cpp b/Source/Game-Lib/Game-Lib/ECS/Util/MessageBuilderUtil.cpp index 24daea1..6213921 100644 --- a/Source/Game-Lib/Game-Lib/ECS/Util/MessageBuilderUtil.cpp +++ b/Source/Game-Lib/Game-Lib/ECS/Util/MessageBuilderUtil.cpp @@ -411,7 +411,43 @@ namespace ECS::Util::MessageBuilder buffer->PutU32(itemCount); }); + return result; + } + bool BuildCheatTriggerAdd(std::shared_ptr& buffer, const std::string& name, u16 flags, u16 mapID, const vec3& position, const vec3& extents) + { + bool result = CreatePacket(buffer, Network::GameOpcode::Client_SendCheatCommand, [&, mapID]() + { + buffer->Put(Network::CheatCommands::TriggerAdd); + buffer->PutString(name); + buffer->PutU16(flags); + buffer->PutU16(mapID); + buffer->Put(position); + buffer->Put(extents); + }); + + return result; + } + bool BuildCheatTriggerRemove(std::shared_ptr& buffer, u32 triggerID) + { + bool result = CreatePacket(buffer, Network::GameOpcode::Client_SendCheatCommand, [&, triggerID]() + { + buffer->Put(Network::CheatCommands::TriggerRemove); + buffer->PutU32(triggerID); + }); + + return result; + } + } + namespace ProximityTrigger + { + bool BuildProximityTriggerEnter(std::shared_ptr& buffer, u32 triggerID) + { + bool result = CreatePacket(buffer, Network::GameOpcode::Client_TriggerEnter, [&, triggerID]() + { + buffer->PutU32(triggerID); + }); + return result; } } -} \ No newline at end of file +} diff --git a/Source/Game-Lib/Game-Lib/ECS/Util/MessageBuilderUtil.h b/Source/Game-Lib/Game-Lib/ECS/Util/MessageBuilderUtil.h index 44f0f2f..246ce07 100644 --- a/Source/Game-Lib/Game-Lib/ECS/Util/MessageBuilderUtil.h +++ b/Source/Game-Lib/Game-Lib/ECS/Util/MessageBuilderUtil.h @@ -88,6 +88,13 @@ namespace ECS bool BuildCheatSetItemShieldTemplate(std::shared_ptr& buffer, ClientDB::Data* shieldTemplateStorage, u32 shieldTemplateID, const Generated::ItemShieldTemplateRecord& shieldTemplate); bool BuildCheatAddItem(std::shared_ptr& buffer, u32 itemID, u32 itemCount); bool BuildCheatRemoveItem(std::shared_ptr& buffer, u32 itemID, u32 itemCount); + bool BuildCheatTriggerAdd(std::shared_ptr& buffer, const std::string& name, u16 flags, u16 mapID, const vec3& position, const vec3& extents); + bool BuildCheatTriggerRemove(std::shared_ptr& buffer, u32 triggerID); + } + + namespace ProximityTrigger + { + bool BuildProximityTriggerEnter(std::shared_ptr& buffer, u32 triggerID); } } } diff --git a/Source/Game-Lib/Game-Lib/ECS/Util/ProximityTriggerUtil.cpp b/Source/Game-Lib/Game-Lib/ECS/Util/ProximityTriggerUtil.cpp new file mode 100644 index 0000000..b58cc4d --- /dev/null +++ b/Source/Game-Lib/Game-Lib/ECS/Util/ProximityTriggerUtil.cpp @@ -0,0 +1,83 @@ +#include "ProximityTriggerUtil.h" +#include "Game-Lib/ECS/Components/AABB.h" +#include "Game-Lib/ECS/Components/Name.h" +#include "Game-Lib/ECS/Components/ProximityTrigger.h" +#include "Game-Lib/ECS/Singletons/CharacterSingleton.h" +#include "Game-Lib/ECS/Singletons/ProximityTriggerSingleton.h" +#include "Game-Lib/ECS/Util/Transforms.h" +#include "Game-Lib/Scripting/LuaDefines.h" +#include "Game-Lib/Scripting/LuaManager.h" +#include "Game-Lib/Scripting/Handlers/TriggerEventHandler.h" +#include "Game-Lib/Util/ServiceLocator.h" + +#include + +void ECS::Util::ProximityTriggerUtil::CreateTrigger(entt::registry& registry, u32 triggerID, const std::string& name, Generated::ProximityTriggerFlagEnum flags, u16 mapID, const vec3& position, const vec3& extents) +{ + entt::entity entity = registry.create(); + + auto& nameComp = registry.emplace(entity); + nameComp.name = name; + registry.emplace(entity); + auto& aabb = registry.emplace(entity); + aabb.centerPos = vec3(0.0f, 0.0f, 0.0f); + aabb.extents = extents; + registry.emplace(entity); + auto& trigger = registry.emplace(entity); + trigger.networkID = triggerID; + trigger.flags = flags; + + auto& transformSystem = ECS::TransformSystem::Get(registry); + transformSystem.SetWorldPosition(entity, position); + + auto& proximityTriggerSingleton = registry.ctx().get(); + proximityTriggerSingleton.triggerIDToEntity[triggerID] = entity; +} + +void ECS::Util::ProximityTriggerUtil::DestroyTrigger(entt::registry& registry, u32 triggerID) +{ + auto& ctx = registry.ctx(); + auto& proximityTriggerSingleton = ctx.get(); + + if (!proximityTriggerSingleton.triggerIDToEntity.contains(triggerID)) + return; // Trigger does not exist + + entt::entity triggerEntity = proximityTriggerSingleton.triggerIDToEntity[triggerID]; + if (!registry.valid(triggerEntity)) + return; // Entity is not valid + + proximityTriggerSingleton.triggerIDToEntity.erase(triggerID); + proximityTriggerSingleton.proximityTriggers.Remove(triggerEntity); + + // Remove this trigger from all entityToProximityTriggers + for (auto& [_, triggers] : proximityTriggerSingleton.entityToProximityTriggers) + { + triggers.erase(triggerEntity); + } + + // Send on trigger exit events + auto& characterSingleton = ctx.get(); + + entt::entity playerEntity = characterSingleton.moverEntity; + + if (playerEntity == entt::null || !registry.valid(playerEntity)) + { + registry.destroy(triggerEntity); + return; + } + + auto& proximityTrigger = registry.get(triggerEntity); + if (proximityTrigger.playersInside.contains(playerEntity) ) + { + auto* luaManager = ServiceLocator::GetLuaManager(); + auto triggerEventHandler = luaManager->GetLuaHandler(Scripting::LuaHandlerType::TriggerEvent); + Scripting::LuaTriggerEventOnTriggerExitData eventData = + { + .triggerID = entt::to_integral(triggerEntity), + .playerID = entt::to_integral(playerEntity) + }; + triggerEventHandler->CallEvent(luaManager->GetInternalState(), static_cast(Generated::LuaTriggerEventEnum::OnExit), &eventData); + } + + registry.destroy(triggerEntity); +} diff --git a/Source/Game-Lib/Game-Lib/ECS/Util/ProximityTriggerUtil.h b/Source/Game-Lib/Game-Lib/ECS/Util/ProximityTriggerUtil.h new file mode 100644 index 0000000..ffea6b5 --- /dev/null +++ b/Source/Game-Lib/Game-Lib/ECS/Util/ProximityTriggerUtil.h @@ -0,0 +1,15 @@ +#pragma once +#include + +#include + +#include + +namespace ECS::Util +{ + namespace ProximityTriggerUtil + { + void CreateTrigger(entt::registry& registry, u32 triggerID, const std::string& name, Generated::ProximityTriggerFlagEnum flags, u16 mapID, const vec3& position, const vec3& extents); + void DestroyTrigger(entt::registry& registry, u32 triggerID); + } +} \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/Gameplay/GameConsole/GameConsoleCommandHandler.cpp b/Source/Game-Lib/Game-Lib/Gameplay/GameConsole/GameConsoleCommandHandler.cpp index 9728815..9636e98 100644 --- a/Source/Game-Lib/Game-Lib/Gameplay/GameConsole/GameConsoleCommandHandler.cpp +++ b/Source/Game-Lib/Game-Lib/Gameplay/GameConsole/GameConsoleCommandHandler.cpp @@ -38,6 +38,8 @@ GameConsoleCommandHandler::GameConsoleCommandHandler() RegisterCommand(GameConsoleCommands::HandleForceSyncItems); RegisterCommand(GameConsoleCommands::HandleAddItem); RegisterCommand(GameConsoleCommands::HandleRemoveItem); + RegisterCommand(GameConsoleCommands::HandleTriggerAdd); + RegisterCommand(GameConsoleCommands::HandleTriggerRemove); } bool GameConsoleCommandHandler::HandleCommand(GameConsole* gameConsole, std::string& command) diff --git a/Source/Game-Lib/Game-Lib/Gameplay/GameConsole/GameConsoleCommands.cpp b/Source/Game-Lib/Game-Lib/Gameplay/GameConsole/GameConsoleCommands.cpp index 92e430b..73dcfd9 100644 --- a/Source/Game-Lib/Game-Lib/Gameplay/GameConsole/GameConsoleCommands.cpp +++ b/Source/Game-Lib/Game-Lib/Gameplay/GameConsole/GameConsoleCommands.cpp @@ -818,3 +818,40 @@ bool GameConsoleCommands::HandleRemoveItem(GameConsole* gameConsole, Generated:: networkState.client->Send(buffer); return true; } + +bool GameConsoleCommands::HandleTriggerAdd(GameConsole* gameConsole, Generated::TriggerAddCommand& command) +{ + entt::registry* registry = ServiceLocator::GetEnttRegistries()->gameRegistry; + ECS::Singletons::NetworkState& networkState = registry->ctx().get(); + + if (!networkState.client || !networkState.client->IsConnected()) + return false; + + std::shared_ptr buffer = Bytebuffer::Borrow<64>(); + + vec3 position = vec3(command.positionX, command.positionY, command.positionZ); + vec3 extents = vec3(command.extentsX, command.extentsY, command.extentsZ); + + if (!ECS::Util::MessageBuilder::Cheat::BuildCheatTriggerAdd(buffer, command.name, command.flags, command.mapID, position, extents)) + return false; + + networkState.client->Send(buffer); + return true; +} + +bool GameConsoleCommands::HandleTriggerRemove(GameConsole* gameConsole, Generated::TriggerRemoveCommand& command) +{ + entt::registry* registry = ServiceLocator::GetEnttRegistries()->gameRegistry; + ECS::Singletons::NetworkState& networkState = registry->ctx().get(); + + if (!networkState.client || !networkState.client->IsConnected()) + return false; + + std::shared_ptr buffer = Bytebuffer::Borrow<32>(); + + if (!ECS::Util::MessageBuilder::Cheat::BuildCheatTriggerRemove(buffer, command.triggerID)) + return false; + + networkState.client->Send(buffer); + return true; +} diff --git a/Source/Game-Lib/Game-Lib/Gameplay/GameConsole/GameConsoleCommands.h b/Source/Game-Lib/Game-Lib/Gameplay/GameConsole/GameConsoleCommands.h index 24946a7..b7bc096 100644 --- a/Source/Game-Lib/Game-Lib/Gameplay/GameConsole/GameConsoleCommands.h +++ b/Source/Game-Lib/Game-Lib/Gameplay/GameConsole/GameConsoleCommands.h @@ -38,4 +38,6 @@ class GameConsoleCommands static bool HandleForceSyncItems(GameConsole* gameConsole, Generated::ForceSyncItemsCommand& command); static bool HandleAddItem(GameConsole* gameConsole, Generated::AddItemCommand& command); static bool HandleRemoveItem(GameConsole* gameConsole, Generated::RemoveItemCommand& command); + static bool HandleTriggerAdd(GameConsole* gameConsole, Generated::TriggerAddCommand& command); + static bool HandleTriggerRemove(GameConsole* gameConsole, Generated::TriggerRemoveCommand& command); }; \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/Scripting/Handlers/TriggerEventHandler.cpp b/Source/Game-Lib/Game-Lib/Scripting/Handlers/TriggerEventHandler.cpp new file mode 100644 index 0000000..d1ac0d1 --- /dev/null +++ b/Source/Game-Lib/Game-Lib/Scripting/Handlers/TriggerEventHandler.cpp @@ -0,0 +1,176 @@ +#include "TriggerEventHandler.h" +#include "Game-Lib/Scripting/LuaState.h" +#include "Game-Lib/Scripting/LuaManager.h" +#include "Game-Lib/Scripting/Systems/LuaSystemBase.h" +#include "Game-Lib/Util/ServiceLocator.h" + +#include + +namespace Scripting +{ + void TriggerEventHandler::Register(lua_State* state) + { + // Register Functions + { + LuaMethodTable::Set(state, triggerEventMethods); + } + + // Set Event Handlers + { + SetEventHandler(static_cast(Generated::LuaTriggerEventEnum::OnEnter), std::bind(&TriggerEventHandler::OnTriggerEnter, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + SetEventHandler(static_cast(Generated::LuaTriggerEventEnum::OnExit), std::bind(&TriggerEventHandler::OnTriggerExit, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + SetEventHandler(static_cast(Generated::LuaTriggerEventEnum::OnStay), std::bind(&TriggerEventHandler::OnTriggerStay, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + } + + CreateTriggerEventTable(state); + } + + void TriggerEventHandler::SetEventHandler(u32 eventID, EventHandlerFn fn) + { + Generated::LuaTriggerEventEnum triggerEventID = static_cast(eventID); + + if (triggerEventID == Generated::LuaTriggerEventEnum::Invalid || triggerEventID >= Generated::LuaTriggerEventEnum::Count) + { + return; + } + + _eventToFuncHandlerList[eventID] = fn; + } + void TriggerEventHandler::CallEvent(lua_State* state, u32 eventID, LuaEventData* data) + { + if (eventID >= _eventToFuncHandlerList.size()) + return; + + Generated::LuaTriggerEventEnum triggerEventID = static_cast(eventID); + + if (triggerEventID == Generated::LuaTriggerEventEnum::Invalid || triggerEventID >= Generated::LuaTriggerEventEnum::Count) + { + return; + } + + EventHandlerFn& fn = _eventToFuncHandlerList[eventID]; + if (fn) + fn(state, eventID, data); + } + void TriggerEventHandler::RegisterEventCallback(lua_State* state, u32 eventID, i32 funcHandle) + { + LuaManager* luaManager = ServiceLocator::GetLuaManager(); + + u32 id = eventID; + u64 key = reinterpret_cast(state); + + std::vector& funcRefList = _eventToLuaStateFuncRefList[id][key]; + funcRefList.push_back(funcHandle); + } + + i32 TriggerEventHandler::RegisterTriggerEvent(lua_State* state) + { + LuaState ctx(state); + + u32 eventIDFromLua = ctx.Get(0u, 1); + + Generated::LuaTriggerEventEnum triggerEventID = static_cast(eventIDFromLua); + if (triggerEventID == Generated::LuaTriggerEventEnum::Invalid || triggerEventID >= Generated::LuaTriggerEventEnum::Count) + { + return 0; + } + + if (!lua_isfunction(ctx.RawState(), 2)) + { + return 0; + } + + i32 funcHandle = ctx.GetRef(2); + + LuaManager* luaManager = ServiceLocator::GetLuaManager(); + auto eventHandler = luaManager->GetLuaHandler(LuaHandlerType::TriggerEvent); + eventHandler->RegisterEventCallback(state, eventIDFromLua, funcHandle); + + return 0; + } + + i32 TriggerEventHandler::OnTriggerEnter(lua_State* state, u32 eventID, LuaEventData* data) + { + LuaManager* luaManager = ServiceLocator::GetLuaManager(); + auto eventData = reinterpret_cast(data); + + u32 id = eventID; + u64 key = reinterpret_cast(state); + + LuaState ctx(state); + + std::vector& funcRefList = _eventToLuaStateFuncRefList[id][key]; + for (i32 funcHandle : funcRefList) + { + ctx.GetRawI(LUA_REGISTRYINDEX, funcHandle); + ctx.Push(id); + ctx.Push(eventData->triggerID); + ctx.Push(eventData->playerID); + + ctx.PCall(3); + } + + return 0; + } + + i32 TriggerEventHandler::OnTriggerExit(lua_State* state, u32 eventID, LuaEventData* data) + { + LuaManager* luaManager = ServiceLocator::GetLuaManager(); + auto eventData = reinterpret_cast(data); + + u32 id = eventID; + u64 key = reinterpret_cast(state); + + LuaState ctx(state); + + std::vector& funcRefList = _eventToLuaStateFuncRefList[id][key]; + for (i32 funcHandle : funcRefList) + { + ctx.GetRawI(LUA_REGISTRYINDEX, funcHandle); + ctx.Push(id); + ctx.Push(eventData->triggerID); + ctx.Push(eventData->playerID); + + ctx.PCall(3); + } + + return 0; + } + + i32 TriggerEventHandler::OnTriggerStay(lua_State* state, u32 eventID, LuaEventData* data) + { + LuaManager* luaManager = ServiceLocator::GetLuaManager(); + auto eventData = reinterpret_cast(data); + + u32 id = eventID; + u64 key = reinterpret_cast(state); + + LuaState ctx(state); + + std::vector& funcRefList = _eventToLuaStateFuncRefList[id][key]; + for (i32 funcHandle : funcRefList) + { + ctx.GetRawI(LUA_REGISTRYINDEX, funcHandle); + ctx.Push(id); + ctx.Push(eventData->triggerID); + ctx.Push(eventData->playerID); + + ctx.PCall(3); + } + + return 0; + } + + void TriggerEventHandler::CreateTriggerEventTable(lua_State* state) + { + LuaState ctx(state); + + ctx.CreateTableAndPopulate(Generated::LuaTriggerEventEnumMeta::EnumName.data(), [&]() + { + for (const auto& pair : Generated::LuaTriggerEventEnumMeta::EnumList) + { + ctx.SetTable(pair.first.data(), pair.second); + } + }); + } +} diff --git a/Source/Game-Lib/Game-Lib/Scripting/Handlers/TriggerEventHandler.h b/Source/Game-Lib/Game-Lib/Scripting/Handlers/TriggerEventHandler.h new file mode 100644 index 0000000..76eb2e0 --- /dev/null +++ b/Source/Game-Lib/Game-Lib/Scripting/Handlers/TriggerEventHandler.h @@ -0,0 +1,40 @@ +#pragma once +#include "LuaEventHandlerBase.h" +#include "Game-Lib/Scripting/LuaDefines.h" +#include "Game-Lib/Scripting/LuaMethodTable.h" + +#include +#include + +namespace Scripting +{ + class TriggerEventHandler : public LuaEventHandlerBase + { + public: + TriggerEventHandler() : LuaEventHandlerBase(static_cast(Generated::LuaTriggerEventEnum::Count)) { } + + public: + void SetEventHandler(u32 eventID, EventHandlerFn fn); + void CallEvent(lua_State* state, u32 eventID, LuaEventData* data); + void RegisterEventCallback(lua_State* state, u32 eventID, i32 funcHandle); + + public: // Registered Functions + static i32 RegisterTriggerEvent(lua_State* state); + + private: + void Register(lua_State* state); + + private: // Event Handlers (Called by CallEvent) + i32 OnTriggerEnter(lua_State* state, u32 eventID, LuaEventData* data); + i32 OnTriggerExit(lua_State* state, u32 eventID, LuaEventData* data); + i32 OnTriggerStay(lua_State* state, u32 eventID, LuaEventData* data); + + private: // Utility Functions + void CreateTriggerEventTable(lua_State* state); + }; + + static LuaMethod triggerEventMethods[] = + { + { "RegisterTriggerEvent", TriggerEventHandler::RegisterTriggerEvent } + }; +} \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/Scripting/LuaDefines.h b/Source/Game-Lib/Game-Lib/Scripting/LuaDefines.h index fec5446..4f59bae 100644 --- a/Source/Game-Lib/Game-Lib/Scripting/LuaDefines.h +++ b/Source/Game-Lib/Game-Lib/Scripting/LuaDefines.h @@ -18,6 +18,7 @@ namespace Scripting Global, GameEvent, PlayerEvent, + TriggerEvent, UI, Unit, Database, @@ -100,5 +101,26 @@ namespace Scripting u32 destSlotIndex; }; + struct LuaTriggerEventOnTriggerEnterData : LuaEventData + { + public: + u32 triggerID; + u32 playerID; + }; + + struct LuaTriggerEventOnTriggerExitData : LuaEventData + { + public: + u32 triggerID; + u32 playerID; + }; + + struct LuaTriggerEventOnTriggerStayData : LuaEventData + { + public: + u32 triggerID; + u32 playerID; + }; + using LuaGameEventHandlerFn = std::function; } \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/Scripting/LuaManager.cpp b/Source/Game-Lib/Game-Lib/Scripting/LuaManager.cpp index 0899f99..34e6742 100644 --- a/Source/Game-Lib/Game-Lib/Scripting/LuaManager.cpp +++ b/Source/Game-Lib/Game-Lib/Scripting/LuaManager.cpp @@ -6,6 +6,7 @@ #include "Handlers/GameHandler.h" #include "Handlers/GlobalHandler.h" #include "Handlers/PlayerEventHandler.h" +#include "Handlers/TriggerEventHandler.h" #include "Handlers/UIHandler.h" #include "Handlers/UnitHandler.h" #include "Systems/GenericSystem.h" @@ -47,6 +48,7 @@ namespace Scripting SetLuaHandler(LuaHandlerType::Global, new GlobalHandler()); SetLuaHandler(LuaHandlerType::GameEvent, new GameEventHandler()); SetLuaHandler(LuaHandlerType::PlayerEvent, new PlayerEventHandler()); + SetLuaHandler(LuaHandlerType::TriggerEvent, new TriggerEventHandler()); SetLuaHandler(LuaHandlerType::UI, new UI::UIHandler()); SetLuaHandler(LuaHandlerType::Unit, new UnitHandler()); SetLuaHandler(LuaHandlerType::Database, new Database::DatabaseHandler()); diff --git a/Source/Resources/Scripts/Types.def b/Source/Resources/Scripts/Types.def index 0d57370..d2cd9d8 100644 --- a/Source/Resources/Scripts/Types.def +++ b/Source/Resources/Scripts/Types.def @@ -46,8 +46,19 @@ type ContainerItemEventData = } declare PlayerEvent : PlayerEvent +type TriggerEvent = +{ + Invalid : number, + OnEnter : number, + OnExit : number, + OnStay : number, + Count : number +}; +declare TriggerEvent : TriggerEvent + declare function RegisterGameEvent(event : GameEvent, callback : ((event : GameEvent, ...any) -> any)) : () declare function RegisterPlayerEvent(event : PlayerEvent, callback : ((event : PlayerEvent, ...any) -> any)) : () +declare function RegisterTriggerEvent(event : TriggerEvent, callback : ((event : TriggerEvent, ...any) -> any)) : () declare function AddCursor(name : string, texture : string) : () declare function SetCursor(name : string) : boolean declare function GetCurrentMap() : string diff --git a/Submodules/Engine b/Submodules/Engine index 07d90e8..45479e2 160000 --- a/Submodules/Engine +++ b/Submodules/Engine @@ -1 +1 @@ -Subproject commit 07d90e806a8dfc0e5e218005d3c3f227039b80f8 +Subproject commit 45479e2b9b18d721b3ba3bc1a5d14b55fad7c5d4