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