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