diff --git a/engine/Sim.lua b/engine/Sim.lua index 5647bd0247..a4685b2d52 100644 --- a/engine/Sim.lua +++ b/engine/Sim.lua @@ -626,8 +626,7 @@ function GetMapSize() end ---@overload fun(x0: number, z0: number, x1: number, z1: number): ReclaimObject[] | nil ---- Returns the reclaimable objects inside the given rectangle. ---- This includes props, units, wreckages. +--- Returns the reclaimable objects inside the given rectangle. This includes props, units and wrecks. Unlike the brain functions, this function uses either the collision box (OO) or the visual box (AAB) for the query and is therefore much more accurate. ---@param rectangle Rectangle ---@return ReclaimObject[] | nil function GetReclaimablesInRect(rectangle) diff --git a/lua/SimCallbacks.lua b/lua/SimCallbacks.lua index 8b0f9917fd..9eae8edd5c 100644 --- a/lua/SimCallbacks.lua +++ b/lua/SimCallbacks.lua @@ -714,6 +714,33 @@ do end end +do + + ---@param data table + ---@param selection Unit[] + Callbacks.ExtendReclaimOrder = function(data, selection) + -- verify selection + selection = SecureUnits(selection) + if (not selection) or TableEmpty(selection) then + return + end + + -- verify the command queue + local unit = selection[1] + local queue = unit:GetCommandQueue() + local lastCommand = queue[table.getn(queue)] + + reprsl(lastCommand) + if not (lastCommand and lastCommand.commandType == 19 and lastCommand.target) then + return + end + + local target = lastCommand.target --[[@as Unit | Prop]] + import("/lua/sim/commands/area-reclaim-order.lua").AreaReclaimOrder(selection, target, true) + end + +end + --#endregion ------------------------------------------------------------------------------- diff --git a/lua/proptree.lua b/lua/proptree.lua index 56d44425fc..b110ad4c2a 100644 --- a/lua/proptree.lua +++ b/lua/proptree.lua @@ -43,6 +43,8 @@ local EffectSetEmitterCurveParam = EffectMethods.SetEmitterCurveParam ---@field NoBurn? boolean Tree = Class(Prop) { + IsTree = true, + ---@param self Tree OnDestroy = function(self) Prop.OnDestroy(self) @@ -269,6 +271,9 @@ Tree = Class(Prop) { ---@class TreeGroup : Prop TreeGroup = Class(Prop) { + IsTree = true, + IsTreeGroup = true, + --- Break when colliding with a projectile of some sort ---@param self TreeGroup ---@param other string diff --git a/lua/sim/commands/area-reclaim-order.lua b/lua/sim/commands/area-reclaim-order.lua new file mode 100644 index 0000000000..00746b8a64 --- /dev/null +++ b/lua/sim/commands/area-reclaim-order.lua @@ -0,0 +1,128 @@ +--****************************************************************************************************** +--** Copyright (c) 2024 Willem 'Jip' Wijnia +--** +--** Permission is hereby granted, free of charge, to any person obtaining a copy +--** of this software and associated documentation files (the "Software"), to deal +--** in the Software without restriction, including without limitation the rights +--** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +--** copies of the Software, and to permit persons to whom the Software is +--** furnished to do so, subject to the following conditions: +--** +--** The above copyright notice and this permission notice shall be included in all +--** copies or substantial portions of the Software. +--** +--** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +--** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +--** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +--** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +--** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +--** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +--** SOFTWARE. +--****************************************************************************************************** + +-- upvalue scope for performance +local TableGetn = table.getn +local TableSetn = table.setn +local TableInsert = table.insert +local TableRemove = table.remove + +local StringFormat = string.format + +local IssueReclaim = IssueReclaim + +---@type table +local seen = { } + +---@type Unit[] +local stack = { } + +--- Applies a reclaim order to all adjacent units +---@param units Unit[] +---@param target StructureUnit +---@param doPrint boolean +local function ReclaimAdjacentUnits (units, target, doPrint) + -- clean up previous iterations + TableSetn(stack, 0) + for entityId, _ in seen do + seen[entityId] = nil + end + + -- prepare the stack + seen[target.EntityId] = true + TableInsert(stack, target) + + local processed = 0 + + -- exclude the expansion and faction identifier + local blueprintIdPostfix = string.sub(target.Blueprint.BlueprintId, 2) + + while TableGetn(stack) > 0 do + local current = TableRemove(stack) + if current != target then + IssueReclaim(units, current) + end + + local adjacentUnits = current.AdjacentUnits + if adjacentUnits then + for _, unit in adjacentUnits do + if blueprintIdPostfix == string.sub(target.Blueprint.BlueprintId, 2) then + if not seen[unit.EntityId] then + seen[unit.EntityId] = true + TableInsert(stack, unit) + processed = processed + 1 + end + end + end + end + end + + if doPrint and processed > 0 and (GetFocusArmy() == GetCurrentCommandSource()) then + print(StringFormat("Reclaiming %d adjacent units", processed)) + end +end + +--- Applies a reclaim order to all nearby props +---@param units Unit[] +---@param target Prop +---@param doPrint boolean +local function ReclaimNearbyProps (units, target, doPrint) + local radius = 1.5 + if target.IsTreeGroup then + radius = 0.1 + end + + local px, _, pz = target:GetPositionXYZ() + local adjacentReclaim = GetReclaimablesInRect(px - radius, pz - radius, px + radius, pz + radius) + local processed = 0 + + if adjacentReclaim then + for k = 1, TableGetn(adjacentReclaim) do + local entity = adjacentReclaim[k] --[[@as Prop]] + if target != entity and IsProp(entity) and entity.MaxMassReclaim > 0 and (entity.IsTree == target.IsTree) then + IssueReclaim(units, entity) + processed = processed + 1 + end + end + end + + if doPrint and processed > 0 and (GetFocusArmy() == GetCurrentCommandSource()) then + print(StringFormat("Reclaiming %d nearby props", processed)) + end +end + +--- Applies additional reclaim orders to nearby similar entities that are similar to the target +---@param units Unit[] +---@param target Unit | Prop +---@param doPrint boolean # if true, prints information about the order +function AreaReclaimOrder(units, target, doPrint) + local unitCount = TableGetn(units) + if unitCount == 0 then + return + end + + if IsUnit(target) and EntityCategoryContains(categories.STRUCTURE, target) then + return ReclaimAdjacentUnits(units, target, doPrint) + elseif IsProp(target) then + return ReclaimNearbyProps(units, target, doPrint) + end +end diff --git a/lua/ui/game/commandmode.lua b/lua/ui/game/commandmode.lua index a8557fabea..7abcf3ffb8 100644 --- a/lua/ui/game/commandmode.lua +++ b/lua/ui/game/commandmode.lua @@ -499,6 +499,10 @@ end ---@return boolean function OnCommandIssued(command) + if command.CommandType == 'Reclaim' and command.Target.EntityId then + SimCallback({ Func = 'ExtendReclaimOrder', Args = { TargetId = command.Target.EntityId } }, true) + end + -- if we're trying to upgrade hives then this allows us to force the upgrade to happen immediately if command.CommandType == "Upgrade" and (command.Blueprint == "xrb0204" or command.Blueprint == "xrb0304") then if not IsKeyDown('Shift') then