From c2da9fc0edaa3cf48cdb62c26eb103bfe33ccc7a Mon Sep 17 00:00:00 2001 From: clyf Date: Thu, 30 May 2024 03:55:04 -0400 Subject: [PATCH] Assisting engineers track new construction orders (#6176) --- changelog/snippets/features.6169.md | 3 + lua/sim/Unit.lua | 16 +- lua/sim/commands/abort-navigation.lua | 1 + lua/sim/units/uef/TConstructionPodUnit.lua | 199 +++++++++++++++++++++ lua/terranunits.lua | 1 + units/UEA0001/UEA0001_script.lua | 42 +---- units/UEA0003/UEA0003_script.lua | 42 +---- units/UEL0001/UEL0001_script.lua | 73 ++++++++ units/UEL0301/UEL0301_script.lua | 78 ++++++++ 9 files changed, 371 insertions(+), 84 deletions(-) create mode 100644 changelog/snippets/features.6169.md create mode 100644 lua/sim/units/uef/TConstructionPodUnit.lua diff --git a/changelog/snippets/features.6169.md b/changelog/snippets/features.6169.md new file mode 100644 index 0000000000..aa85e0e86e --- /dev/null +++ b/changelog/snippets/features.6169.md @@ -0,0 +1,3 @@ +(#6169) Shoulder pods (ACU drones) now track the focus of their parent unit more closely and will automatically switch to any engineering task their parent starts. Additionally, pods can now be properly assisted by other engineering units, and assist commands targeting them will persist when the pod attaches/detaches from the parent. + +(#6169) The abort pathfinding key action on a lead unit will refocus any assisting engineers on the leader's task. \ No newline at end of file diff --git a/lua/sim/Unit.lua b/lua/sim/Unit.lua index fe55f788e1..c33ccdb02e 100644 --- a/lua/sim/Unit.lua +++ b/lua/sim/Unit.lua @@ -846,14 +846,6 @@ Unit = ClassUnit(moho.unit_methods, IntelComponent, VeterancyComponent) { self:CheckAssistersFocus() self:DoUnitCallbacks('OnStartReclaim', target) - -- Force me to move on to the guard properly when done - local guard = self:GetGuardedUnit() - if guard then - IssueToUnitClearCommands(self) - IssueReclaim({self}, target) - IssueGuard({self}, guard) - end - -- add state to be able to show the amount reclaimed in the UI if target.IsProp then self.OnStartReclaimPropStartTick = GetGameTick() + 2 @@ -2767,6 +2759,14 @@ Unit = ClassUnit(moho.unit_methods, IntelComponent, VeterancyComponent) { end end, + ---Called via hotkey to refocus any assisting engineers + ---@param self Unit + RefocusAssisters = function(self) + local engineerGuards = EntityCategoryFilterDown(categories.ENGINEER, self:GetGuards()) + IssueClearCommands(engineerGuards) + IssueGuard(engineerGuards, self) + end, + ---@param self Unit ---@param built Unit ---@param order string diff --git a/lua/sim/commands/abort-navigation.lua b/lua/sim/commands/abort-navigation.lua index fa136d1195..9d136814a9 100644 --- a/lua/sim/commands/abort-navigation.lua +++ b/lua/sim/commands/abort-navigation.lua @@ -43,6 +43,7 @@ function AbortNavigation(units, doPrint) if not IsDestroyed(unit) then local navigator = unit:GetNavigator() navigator:AbortMove() + unit:RefocusAssisters() end end diff --git a/lua/sim/units/uef/TConstructionPodUnit.lua b/lua/sim/units/uef/TConstructionPodUnit.lua new file mode 100644 index 0000000000..f58a18b08f --- /dev/null +++ b/lua/sim/units/uef/TConstructionPodUnit.lua @@ -0,0 +1,199 @@ +local TConstructionUnit = import("/lua/terranunits.lua").TConstructionUnit +local oldGetGuards = TConstructionUnit.GetGuards + +---@class TConstructionPodUnit : TConstructionUnit +---@field Pod string +---@field Parent Unit +---@field guardCache table +---@field guardDummy Unit +TConstructionPodUnit = ClassUnit(TConstructionUnit) { + Parent = nil, + + ---@param self TConstructionPodUnit + OnCreate = function(self) + TConstructionUnit.OnCreate(self) + self.guardDummy = CreateUnitHPR('ZXA0003', self:GetArmy(), 0,0,0,0,0,0) + self.guardDummy:AttachTo(self, -1) + self.Trash:Add(self.guardDummy) + end, + + ---@param self TConstructionPodUnit + ---@param bit number + OnScriptBitSet = function(self, bit) + TConstructionUnit.OnScriptBitSet(self, bit) + if bit == 1 then + self.rebuildDrone = true + end + end, + + ---@param self TConstructionPodUnit + ---@param bit number + OnScriptBitClear = function(self, bit) + TConstructionUnit.OnScriptBitClear(self, bit) + if bit == 1 then + self.rebuildDrone = false + end + end, + + ---@param self TConstructionPodUnit + ---@param transport Unit + ---@param bone number + OnAttachedToTransport = function(self, transport, bone) + local guards = self:GetGuards() + IssueClearCommands(guards) + IssueGuard(guards, self.guardDummy) + TConstructionUnit.OnAttachedToTransport(self, transport, bone) + end, + + ---@param self TConstructionPodUnit + ---@param transport Unit + ---@param bone number + OnDetachedFromTransport = function(self, transport, bone) + TConstructionUnit.OnDetachedFromTransport(self, transport, bone) + local guards = self.guardDummy:GetGuards() + IssueClearCommands(guards) + IssueGuard(guards, self) + end, + + ---@param self TConstructionPodUnit + ---@param parent Unit + ---@param podName string + SetParent = function(self, parent, podName) + self.Parent = parent + self.Pod = podName + self:SetScriptBit('RULEUTC_WeaponToggle', true) + end, + + ---@param self TConstructionPodUnit + ---@param unitBeingBuilt Unit + ---@param order string + OnStartBuild = function(self, unitBeingBuilt, order) + TConstructionUnit.OnStartBuild(self, unitBeingBuilt, order) + self:FocusAssistersOnCurrentTask() + end, + + ---@param self TConstructionPodUnit + ---@param built Unit + ---@param order string + OnStopBuild = function(self, built, order) + TConstructionUnit.OnStopBuild(self, built, order) + -- Check if we finished our build task and clear our cached command if so + if self.guardCache and built:GetFractionComplete() == 1 then + self.guardCache = nil + end + end, + + ---@param self TConstructionPodUnit + ---@param target Unit|Prop + OnStartReclaim = function(self, target) + TConstructionUnit.OnStartReclaim(self, target) + self:FocusAssistersOnCurrentTask() + end, + + ---@param self TConstructionPodUnit + ---@param target Unit|Prop + OnStopReclaim = function(self, target) + TConstructionUnit.OnStopReclaim(self, target) + -- Check if we finished our reclaim task and clear our cached commaand if so + if self.guardCache and table.empty(target) then + self.guardCache = nil + end + end, + + ---@param self TConstructionPodUnit + ---@param unitBeingRepaired Unit + OnStartRepair = function(self, unitBeingRepaired) + TConstructionUnit.OnStartRepair(self, unitBeingRepaired) + self:FocusAssistersOnCurrentTask() + end, + + ---@param self TConstructionPodUnit + FocusAssistersOnCurrentTask = function(self) + + if self.Dead then + return + end + + local engineerGuards = self:GetGuards() + + -- Make sure we've got some assisters to work with + if not next(engineerGuards) then + self.guardCache = nil + return + end + + -- Make sure we're performing an engineering task + if not (self:IsUnitState('Reclaiming') + or self:IsUnitState('Building') + or self:IsUnitState('Repairing')) then + return + end + + local command + if self:IsUnitState('Reclaiming') then + command = IssueReclaim + elseif self:IsUnitState('Repairing') or self:IsUnitState('Building') then + command = IssueRepair + end + + -- We only need to worry about refocusing our guards if we currently have an engineering target + local target = self:GetFocusUnit() or self:GetCommandQueue()[1].target + if target then + IssueClearCommands(engineerGuards) + command(engineerGuards, target) + IssueGuard(engineerGuards, self) + self.guardCache = engineerGuards + self.guardCache.target = target + self.guardCache.command = command + end + end, + + ---Called via hotkey to refocus assisters on our current task + ---@param self TConstructionPodUnit + RefocusAssisters = function(self) + local engineerGuards = EntityCategoryFilterDown(categories.ENGINEER, self:GetGuards()) + IssueClearCommands(engineerGuards) + if self.guardCache then + LOG('We have a guard cache') + self.guardCache.command(engineerGuards, self.guardCache.target) + end + IssueGuard(engineerGuards, self) + end, + + ---Override get guards to pick up our assist cache + ---@param self TConstructionPodUnit + GetGuards = function(self) + local guards = oldGetGuards(self) + local count = 0 + if self.guardCache then + local firstCommand, secondCommand + local target = self.guardCache.target + for _, guard in ipairs(self.guardCache) do + firstCommand, secondCommand = unpack(guard:GetCommandQueue()) + if firstCommand.target == target + and secondCommand.target == self then + table.insert(guards, guard) + count = count + 1 + end + end + end + if count > 0 then + print(string.format('Found %d cached guards', count)) + end + return guards + end, + + ---@param self TConstructionPodUnit + ---@param instigator Unit + ---@param type string + ---@param overkillRatio number + OnKilled = function(self, instigator, type, overkillRatio) + self.Parent:NotifyOfPodDeath(self.Pod, self.rebuildDrone) + self.Parent = nil + TConstructionUnit.OnKilled(self, instigator, type, overkillRatio) + end, + + CreateWreckage = function (self, overkillRatio) + -- Don't make wreckage + end, +} \ No newline at end of file diff --git a/lua/terranunits.lua b/lua/terranunits.lua index a97d7d61af..2706d18d63 100644 --- a/lua/terranunits.lua +++ b/lua/terranunits.lua @@ -14,6 +14,7 @@ TAirStagingPlatformUnit = import('/lua/sim/units/uef/TAirStagingPlatformUnit.lua TAirUnit = import('/lua/sim/units/uef/TAirUnit.lua').TAirUnit TConcreteStructureUnit = import('/lua/sim/units/uef/TConcreteStructureUnit.lua').TConcreteStructureUnit TConstructionUnit = import('/lua/sim/units/uef/TConstructionUnit.lua').TConstructionUnit +TConstructionPodUnit = import('/lua/sim/units/uef/TConstructionPodUnit.lua').TConstructionPodUnit TEnergyCreationUnit = import('/lua/sim/units/uef/TEnergyCreationUnit.lua').TEnergyCreationUnit TEnergyStorageUnit = import('/lua/sim/units/uef/TEnergyStorageUnit.lua').TEnergyStorageUnit THoverLandUnit = import('/lua/sim/units/uef/THoverLandUnit.lua').THoverLandUnit diff --git a/units/UEA0001/UEA0001_script.lua b/units/UEA0001/UEA0001_script.lua index 61777fc66d..1633b67240 100644 --- a/units/UEA0001/UEA0001_script.lua +++ b/units/UEA0001/UEA0001_script.lua @@ -5,43 +5,9 @@ -- Copyright © 2005 Gas Powered Games, Inc. All rights reserved. ----------------------------------------------------------------- -local TConstructionUnit = import("/lua/terranunits.lua").TConstructionUnit +local TConstructionPodUnit = import("/lua/terranunits.lua").TConstructionPodUnit ----@class UEA0001 : TConstructionUnit -UEA0001 = ClassUnit(TConstructionUnit) { - Parent = nil, +---@class UEA0001 : TConstructionPodUnit +UEA0001 = ClassUnit(TConstructionPodUnit) {} - OnScriptBitSet = function(self, bit) - TConstructionUnit.OnScriptBitSet(self, bit) - if bit == 1 then - self.rebuildDrone = true - end - end, - - OnScriptBitClear = function(self, bit) - TConstructionUnit.OnScriptBitClear(self, bit) - if bit == 1 then - self.rebuildDrone = false - end - end, - - SetParent = function(self, parent, podName) - self.Parent = parent - self.Pod = podName - self:SetScriptBit('RULEUTC_WeaponToggle', true) - end, - - OnKilled = function(self, instigator, type, overkillRatio) - self.Parent:NotifyOfPodDeath(self.Pod, self.rebuildDrone) - self.Parent = nil - TConstructionUnit.OnKilled(self, instigator, type, overkillRatio) - end, - - -- Don't make wreckage - CreateWreckage = function (self, overkillRatio) - overkillRatio = 1.1 - TConstructionUnit.CreateWreckage(self, overkillRatio) - end, -} - -TypeClass = UEA0001 +TypeClass = UEA0001 \ No newline at end of file diff --git a/units/UEA0003/UEA0003_script.lua b/units/UEA0003/UEA0003_script.lua index e80e2677ab..c46809f3bd 100644 --- a/units/UEA0003/UEA0003_script.lua +++ b/units/UEA0003/UEA0003_script.lua @@ -4,43 +4,9 @@ -- Copyright © 2005 Gas Powered Games, Inc. All rights reserved. ----------------------------------------------------------------- -local TConstructionUnit = import("/lua/terranunits.lua").TConstructionUnit +local TConstructionPodUnit = import("/lua/terranunits.lua").TConstructionPodUnit ----@class UEA0003 : TConstructionUnit -UEA0003 = ClassUnit(TConstructionUnit) { - Parent = nil, +---@class UEA0003 : TConstructionPodUnit +UEA0003 = ClassUnit(TConstructionPodUnit) {} - OnScriptBitSet = function(self, bit) - TConstructionUnit.OnScriptBitSet(self, bit) - if bit == 1 then - self.rebuildDrone = true - end - end, - - OnScriptBitClear = function(self, bit) - TConstructionUnit.OnScriptBitClear(self, bit) - if bit == 1 then - self.rebuildDrone = false - end - end, - - SetParent = function(self, parent, podName) - self.Parent = parent - self.Pod = podName - self:SetScriptBit('RULEUTC_WeaponToggle', true) - end, - - OnKilled = function(self, instigator, type, overkillRatio) - self.Parent:NotifyOfPodDeath(self.Pod, self.rebuildDrone) - self.Parent = nil - TConstructionUnit.OnKilled(self, instigator, type, overkillRatio) - end, - - -- Don't make wreckage - CreateWreckage = function (self, overkillRatio) - overkillRatio = 1.1 - TConstructionUnit.CreateWreckage(self, overkillRatio) - end, -} - -TypeClass = UEA0003 +TypeClass = UEA0003 \ No newline at end of file diff --git a/units/UEL0001/UEL0001_script.lua b/units/UEL0001/UEL0001_script.lua index 96c4f4b481..80dfcab0bc 100644 --- a/units/UEL0001/UEL0001_script.lua +++ b/units/UEL0001/UEL0001_script.lua @@ -26,6 +26,8 @@ local EffectUtil = import("/lua/effectutilities.lua") local Buff = import("/lua/sim/buff.lua") ---@class UEL0001 : ACUUnit +---@field LeftPod TConstructionPodUnit +---@field RightPod TConstructionPodUnit UEL0001 = ClassUnit(ACUUnit) { Weapons = { DeathWeapon = ClassWeapon(ACUDeathWeapon) {}, @@ -102,10 +104,12 @@ UEL0001 = ClassUnit(ACUUnit) { }, }, + ---@param self UEL0001 __init = function(self) ACUUnit.__init(self, 'RightZephyr') end, + ---@param self UEL0001 OnCreate = function(self) ACUUnit.OnCreate(self) self:SetCapturable(false) @@ -127,6 +131,9 @@ UEL0001 = ClassUnit(ACUUnit) { end end, + ---@param self UEL0001 + ---@param builder Unit + ---@param layer string OnStopBeingBuilt = function(self, builder, layer) ACUUnit.OnStopBeingBuilt(self, builder, layer) if self:BeenDestroyed() then return end @@ -139,13 +146,56 @@ UEL0001 = ClassUnit(ACUUnit) { self:ForkThread(self.GiveInitialResources) end, + ---@param self UEL0001 + ---@param unitBeingBuilt Unit + ---@param order string OnStartBuild = function(self, unitBeingBuilt, order) ACUUnit.OnStartBuild(self, unitBeingBuilt, order) if self.Animator then self.Animator:SetRate(0) end + self:RefreshPodFocus() + end, + + ---@param self UEL0001 + ---@param unitBeingBuilt Unit + ---@param order string + OnStopBuild = function(self, unitBeingBuilt, order) + ACUUnit.OnStopBuild(self, unitBeingBuilt, order) + self:RefreshPodFocus() + end, + + ---@param self UEL0001 + ---@param unitBeingRepaired Unit + OnStartRepair = function(self, unitBeingRepaired) + ACUUnit.OnStartRepair(self, unitBeingRepaired) + self:RefreshPodFocus() + end, + + ---@param self UEL0001 + ---@param unitBeingRepaired Unit + OnStopRepair = function(self, unitBeingRepaired) + ACUUnit.OnStopRepair(self, unitBeingRepaired) + self:RefreshPodFocus() + end, + + ---@param self UEL0001 + ---@param target Unit|Prop + OnStartReclaim = function(self, target) + ACUUnit.OnStartReclaim(self, target) + self:RefreshPodFocus() end, + ---@param self UEL0001 + ---@param target Unit|Prop + OnStopReclaim = function(self, target) + ACUUnit.OnStopReclaim(self, target) + self:RefreshPodFocus() + end, + + ---@param self UEL0001 + ---@param unitBeingBuilt Unit + ---@param order string CreateBuildEffects = function(self, unitBeingBuilt, order) -- Different effect if we have building cube if unitBeingBuilt.BuildingCube then @@ -156,6 +206,8 @@ UEL0001 = ClassUnit(ACUUnit) { end end, + ---@param self UEL0001 + ---@param PodNumber integer RebuildPod = function(self, PodNumber) if PodNumber == 1 then -- Force pod rebuilds to queue up @@ -199,6 +251,9 @@ UEL0001 = ClassUnit(ACUUnit) { self:RequestRefreshUI() end, + ---@param self UEL0001 + ---@param pod string + ---@param rebuildDrone boolean NotifyOfPodDeath = function(self, pod, rebuildDrone) if rebuildDrone == true then if pod == 'LeftPod' then @@ -215,6 +270,22 @@ UEL0001 = ClassUnit(ACUUnit) { end end, + ---Calling this function will pull any pods without explicit orders to our current task + ---@param self UEL0001 + RefreshPodFocus = function(self) + for _, pod in self:GetPods() do + if not pod.Dead and pod:GetCommandQueue()[1].commandType == 29 then + IssueToUnitClearCommands(pod) + end + end + end, + + ---@param self UEL0001 + ---@return Unit[]? pods + GetPods = function(self) + return {self.LeftPod, self.RightPod} + end, + ---@param self UEL0001 ---@param bone Bone ---@param attachee Unit @@ -231,6 +302,8 @@ UEL0001 = ClassUnit(ACUUnit) { attachee:SetDoNotTarget(false) end, + ---@param self UEL0001 + ---@param enh string CreateEnhancement = function(self, enh) ACUUnit.CreateEnhancement(self, enh) diff --git a/units/UEL0301/UEL0301_script.lua b/units/UEL0301/UEL0301_script.lua index 2f06740314..6a78aa6eed 100644 --- a/units/UEL0301/UEL0301_script.lua +++ b/units/UEL0301/UEL0301_script.lua @@ -29,6 +29,7 @@ UEL0301 = ClassUnit(CommandUnit) { DeathWeapon = ClassWeapon(SCUDeathWeapon) {}, }, + ---@param self UEL0301 OnCreate = function(self) CommandUnit.OnCreate(self) self:SetCapturable(false) @@ -37,16 +38,23 @@ UEL0301 = ClassUnit(CommandUnit) { self:SetupBuildBones() end, + ---@param self UEL0301 __init = function(self) CommandUnit.__init(self, 'RightHeavyPlasmaCannon') end, + ---@param self UEL0301 + ---@param builder Unit + ---@param layer Layer OnStopBeingBuilt = function(self, builder, layer) CommandUnit.OnStopBeingBuilt(self, builder, layer) -- Block Jammer until Enhancement is built self:DisableUnitIntel('Enhancement', 'Jammer') end, + ---@param self UEL0301 + ---@param unitBeingBuilt Unit + ---@param order string CreateBuildEffects = function(self, unitBeingBuilt, order) -- Different effect if we have building cube if unitBeingBuilt.BuildingCube then @@ -56,6 +64,51 @@ UEL0301 = ClassUnit(CommandUnit) { end end, + ---@param self UEL0301 + ---@param unitBeingBuilt Unit + ---@param order string + OnStartBuild = function(self, unitBeingBuilt, order) + CommandUnit.OnStartBuild(self, unitBeingBuilt, order) + self:RefreshPodFocus() + end, + + ---@param self UEL0301 + ---@param unitBeingBuilt Unit + ---@param order string + OnStopBuild = function(self, unitBeingBuilt, order) + CommandUnit.OnStopBuild(self, unitBeingBuilt, order) + self:RefreshPodFocus() + end, + + ---@param self UEL0301 + ---@param unitBeingRepaired Unit + OnStartRepair = function(self, unitBeingRepaired) + CommandUnit.OnStartRepair(self, unitBeingRepaired) + self:RefreshPodFocus() + end, + + ---@param self UEL0301 + ---@param unitBeingRepaired Unit + OnStopRepair = function(self, unitBeingRepaired) + CommandUnit.OnStopRepair(self, unitBeingRepaired) + self:RefreshPodFocus() + end, + + ---@param self UEL0301 + ---@param target Unit|Prop + OnStartReclaim = function(self, target) + CommandUnit.OnStartReclaim(self, target) + self:RefreshPodFocus() + end, + + ---@param self UEL0301 + ---@param target Unit|Prop + OnStopReclaim = function(self, target) + CommandUnit.OnStopReclaim(self, target) + self:RefreshPodFocus() + end, + + ---@param self UEL0301 RebuildPod = function(self) if self.HasPod == true then self.RebuildingPod = CreateEconomyEvent(self, 1600, 160, 10, self.SetWorkProgress) @@ -73,6 +126,9 @@ UEL0301 = ClassUnit(CommandUnit) { end end, + ---@param self UEL0301 + ---@param pod TConstructionPodUnit + ---@param rebuildDrone boolean NotifyOfPodDeath = function(self, pod, rebuildDrone) if rebuildDrone == true then if self.HasPod == true then @@ -83,6 +139,22 @@ UEL0301 = ClassUnit(CommandUnit) { end end, + ---Calling this function will pull any pods without explicit orders to our current task + ---@param self UEL0301 + RefreshPodFocus = function(self) + for _, pod in self:GetPods() do + if not pod.Dead and pod:GetCommandQueue()[1].commandType == 29 then + IssueToUnitClearCommands(pod) + end + end + end, + + ---@param self UEL0301 + ---@return Unit[]? pods + GetPods = function(self) + return {self.Pod} + end, + ---@param self UEL0301 ---@param bone Bone ---@param attachee Unit @@ -99,6 +171,8 @@ UEL0301 = ClassUnit(CommandUnit) { attachee:SetDoNotTarget(false) end, + ---@param self UEL0301 + ---@param enh string CreateEnhancement = function(self, enh) CommandUnit.CreateEnhancement(self, enh) local bp = self:GetBlueprint().Enhancements[enh] @@ -191,6 +265,8 @@ UEL0301 = ClassUnit(CommandUnit) { end end, + ---@param self UEL0301 + ---@param intel IntelType OnIntelEnabled = function(self, intel) CommandUnit.OnIntelEnabled(self, intel) if self.RadarJammerEnh and self:IsIntelEnabled('Jammer') then @@ -203,6 +279,8 @@ UEL0301 = ClassUnit(CommandUnit) { end end, + ---@param self UEL0301 + ---@param intel IntelType OnIntelDisabled = function(self, intel) CommandUnit.OnIntelDisabled(self, intel) if self.RadarJammerEnh and not self:IsIntelEnabled('Jammer') then