From f35862b596f37d6549c83d1cbb46aa07f13d6f7b Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 29 Sep 2026 09:25:37 -0500 Subject: [PATCH] feat(scripts): the Assembly Engineer turret works as TURRET.lua The turret (LOT 9303-9305, 12618) had no script: it fought as soon as it was spawned and lasted the generic 60 seconds. As TURRET.lua: its combat AI is off until it is built, it can't be stunned, interrupted, knocked back or pulled, and it dies 30 seconds after it was placed once nobody is building it. Kill credit going to the builder is not done. Check: summon the turret with Engineer gear: it doesn't shoot until built, then fights and goes away after about 30 seconds. Co-Authored-By: Claude Opus 5.5 --- dScripts/CppScripts.cpp | 2 + dScripts/EquipmentScripts/CMakeLists.txt | 1 + dScripts/EquipmentScripts/Turret.cpp | 52 ++++++++++++++++++++++++ dScripts/EquipmentScripts/Turret.h | 15 +++++++ 4 files changed, 70 insertions(+) create mode 100644 dScripts/EquipmentScripts/Turret.cpp create mode 100644 dScripts/EquipmentScripts/Turret.h diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 93f138cc1..ce0ae133f 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -295,6 +295,7 @@ #include "CauldronOfLife.h" #include "FountainOfImagination.h" #include "Sunflower.h" +#include "Turret.h" #include "BootyDigServer.h" #include "PersonalFortress.h" #include "PropertyDevice.h" @@ -661,6 +662,7 @@ namespace { //Scripted Equipment {"scripts\\EquipmentScripts\\Sunflower.lua", []() {return new Sunflower();}}, + {"scripts\\EquipmentScripts\\TURRET.lua", []() {return new Turret();}}, {"scripts/EquipmentScripts/AnvilOfArmor.lua", []() {return new AnvilOfArmor();}}, {"scripts/EquipmentScripts/FountainOfImagination.lua", []() {return new FountainOfImagination();}}, {"scripts/EquipmentScripts/CauldronOfLife.lua", []() {return new CauldronOfLife();}}, diff --git a/dScripts/EquipmentScripts/CMakeLists.txt b/dScripts/EquipmentScripts/CMakeLists.txt index 60f315035..09c3cce84 100644 --- a/dScripts/EquipmentScripts/CMakeLists.txt +++ b/dScripts/EquipmentScripts/CMakeLists.txt @@ -1,5 +1,6 @@ set(DSCRIPTS_SOURCES_EQUIPMENTSCRIPTS "Sunflower.cpp" + "Turret.cpp" "AnvilOfArmor.cpp" "FountainOfImagination.cpp" "CauldronOfLife.cpp" diff --git a/dScripts/EquipmentScripts/Turret.cpp b/dScripts/EquipmentScripts/Turret.cpp new file mode 100644 index 000000000..39938fcb2 --- /dev/null +++ b/dScripts/EquipmentScripts/Turret.cpp @@ -0,0 +1,52 @@ +#include "Turret.h" + +#include "BaseCombatAIComponent.h" +#include "ControllablePhysicsComponent.h" +#include "DestroyableComponent.h" +#include "Entity.h" +#include "eQuickBuildState.h" +#include "eStateChangeType.h" + +void Turret::SetCombatAI(Entity* self, bool enabled) { + if (auto* combatAI = self->GetComponent()) combatAI->SetDisabled(!enabled); +} + +void Turret::OnStartup(Entity* self) { + SetCombatAI(self, false); + self->SetVar(u"currentTime", 1); + self->SetVar(u"building", false); + self->AddTimer("TickTime", 1.0f); + + // SetStunImmunity (attack, interrupt) and SetStatusImmunity (pull to point, knockback); DLU keeps interrupt with the status ones + if (auto* controllablePhysics = self->GetComponent()) { + controllablePhysics->SetStunImmunity(eStateChangeType::PUSH, self->GetObjectID(), true, false, false, false, false, false, false); + } + if (auto* destroyable = self->GetComponent()) { + destroyable->SetStatusImmunity(eStateChangeType::PUSH, false, false, true, true, false, false, false, false, true); + } +} + +void Turret::OnQuickBuildStart(Entity* self, Entity* target) { + self->SetVar(u"building", true); +} + +void Turret::OnQuickBuildNotifyState(Entity* self, eQuickBuildState state) { + if (state != eQuickBuildState::COMPLETED) return; + SetCombatAI(self, true); + self->SetVar(u"building", false); + self->CancelAllTimers(); + self->AddTimer("TickTime", 1.0f); +} + +void Turret::OnTimerDone(Entity* self, std::string timerName) { + if (timerName != "TickTime") return; + const auto currentTime = self->GetVar(u"currentTime") + 1; + self->SetVar(u"currentTime", currentTime); + if (currentTime >= KILL_TIME && !self->GetVar(u"building")) { + SetCombatAI(self, false); + self->SetVar(u"currentTime", 0); + self->Smash(self->GetObjectID()); + return; + } + self->AddTimer("TickTime", 1.0f); +} diff --git a/dScripts/EquipmentScripts/Turret.h b/dScripts/EquipmentScripts/Turret.h new file mode 100644 index 000000000..6c0a8dee1 --- /dev/null +++ b/dScripts/EquipmentScripts/Turret.h @@ -0,0 +1,15 @@ +#pragma once +#include "CppScripts.h" + +// TURRET.lua: the Assembly Engineer turret. Its combat AI is off until it is built; it can't be stunned, interrupted, +// knocked back or pulled; it dies 30 seconds after it was placed, once nobody is building it. +class Turret : public CppScripts::Script { +public: + void OnStartup(Entity* self) override; + void OnQuickBuildStart(Entity* self, Entity* target) override; + void OnQuickBuildNotifyState(Entity* self, eQuickBuildState state) override; + void OnTimerDone(Entity* self, std::string timerName) override; +private: + static void SetCombatAI(Entity* self, bool enabled); + static constexpr int32_t KILL_TIME = 30; +};