From dba25b95ea941aa929c5f6b4ae395140924012f9 Mon Sep 17 00:00:00 2001 From: "(Jip) Willem Wijnia" Date: Mon, 4 Sep 2023 17:25:20 +0200 Subject: [PATCH] Abstract the ringing feature to make it easier to use (#5390) --- lua/SimCallbacks.lua | 4 +- lua/sim/commands/ring-with-fabricators.lua | 123 ------------------ .../ringing/ring-with-fabricators.lua | 53 ++++++++ .../commands/ringing/ring-with-storages.lua | 52 ++++++++ .../shared.lua} | 108 +++++++++------ lua/sim/commands/shared.lua | 53 -------- 6 files changed, 177 insertions(+), 216 deletions(-) delete mode 100644 lua/sim/commands/ring-with-fabricators.lua create mode 100644 lua/sim/commands/ringing/ring-with-fabricators.lua create mode 100644 lua/sim/commands/ringing/ring-with-storages.lua rename lua/sim/commands/{ring-with-storages.lua => ringing/shared.lua} (64%) diff --git a/lua/SimCallbacks.lua b/lua/SimCallbacks.lua index 7ed2222d52..84aefbad2a 100644 --- a/lua/SimCallbacks.lua +++ b/lua/SimCallbacks.lua @@ -286,7 +286,7 @@ Callbacks.RingWithStorages = function(data, selection) return end - import("/lua/sim/commands/ring-with-storages.lua").RingExtractor(extractor, engineers) + import("/lua/sim/commands/ringing/ring-with-storages.lua").RingExtractor(extractor, engineers) end ---@param data { target: EntityId, allFabricators: boolean } @@ -314,7 +314,7 @@ Callbacks.RingWithFabricators = function(data, selection) return end - import("/lua/sim/commands/ring-with-fabricators.lua").RingExtractor(extractor, engineers, data.allFabricators) + import("/lua/sim/commands/ringing/ring-with-fabricators.lua").RingExtractor(extractor, engineers, data.allFabricators) end ---@param data any diff --git a/lua/sim/commands/ring-with-fabricators.lua b/lua/sim/commands/ring-with-fabricators.lua deleted file mode 100644 index a2aea184f9..0000000000 --- a/lua/sim/commands/ring-with-fabricators.lua +++ /dev/null @@ -1,123 +0,0 @@ ---****************************************************************************************************** ---** Copyright (c) 2022 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. ---****************************************************************************************************** - -local SortUnitsByTech = import("/lua/sim/commands/shared.lua").SortUnitsByTech -local FindNearestUnit = import("/lua/sim/commands/shared.lua").FindNearestUnit -local FindBuildingSkirts = import("/lua/sim/commands/shared.lua").FindBuildingSkirts -local SortOffsetsByDistanceToPoint = import("/lua/sim/commands/shared.lua").SortOffsetsByDistanceToPoint - -local InnerBuildOffsets = { { -2, 2 }, { 2, 2 }, { 2, -2 }, { -2, -2 }, } -local AllBuildOffsets = { { -2, 2 }, { 2, 2 }, { 2, -2 }, { -2, -2 }, { -4, 0 }, { 0, 4 }, { 4, 0 }, { 0, -4 }, } - --- upvalue scope for performance -local IssueGuard = IssueGuard -local GetTerrainHeight = GetTerrainHeight -local EntityCategoryFilterDown = EntityCategoryFilterDown -local IssueBuildAllMobile = IssueBuildAllMobile - --- cached for performance -local CacheX1 = { } -local CacheZ1 = { } -local CacheX2 = { } -local CacheZ2 = { } - -local BuildLocation = { } -local EmptyTable = { } - ----@param extractor Unit ----@param engineers Unit[] ----@param allFabricators boolean -RingExtractor = function(extractor, engineers, allFabricators) - - --------------------------------------------------------------------------- - -- defensive programming - - -- confirm we have an extractor - if (not extractor) or (IsDestroyed(extractor)) then - return - end - - -- confirm that we have one engineer that can build the unit - SortUnitsByTech(engineers) - local fabricator = engineers[1].Blueprint.BlueprintId:sub(1, 2) .. 'b1104' - if (not __blueprints[fabricator]) or - (not engineers[1]:CanBuild(fabricator)) - then - return - end - - --------------------------------------------------------------------------- - -- determine all units in surroundings that may block construction - - local blueprint = extractor:GetBlueprint() - local skirtSize = blueprint.Physics.SkirtSizeX - local cx, _, cz = extractor:GetPositionXYZ() - local cx1, cz1, cx2, cz2, buildingSkirtCount = FindBuildingSkirts(cx, cz, skirtSize, CacheX1, CacheZ1, CacheX2, CacheZ2) - - --------------------------------------------------------------------------- - -- filter engineers and sort offsets - - local faction = engineers[1].Blueprint.FactionCategory - local engineersOfFaction = EntityCategoryFilterDown(categories[faction], engineers) - local engineersOther = EntityCategoryFilterDown(categories.ALLUNITS - categories[faction], engineers) - - local offsets = (allFabricators and AllBuildOffsets) or InnerBuildOffsets - local nearestEngineer = FindNearestUnit(engineersOfFaction, cx, cz) - if nearestEngineer then - local ex, _, ez = nearestEngineer:GetPositionXYZ() - SortOffsetsByDistanceToPoint(offsets, cx, cz, ex, ez) - end - - --------------------------------------------------------------------------- - -- issue the build orders - - local buildLocation = BuildLocation - local emptyTable = EmptyTable - - for k, location in offsets do - local bx = cx + location[1] - local bz = cz + location[2] - - -- determine if location is free to build - local freeToBuild = true - for k = 1, buildingSkirtCount do - if bx > cx1[k] and bx < cx2[k] then - if bz > cz1[k] and bz < cz2[k] then - freeToBuild = false - break - end - end - end - - if freeToBuild then - buildLocation[1] = bx - buildLocation[3] = bz - buildLocation[2] = GetTerrainHeight(bx, bz) - IssueBuildAllMobile(engineersOfFaction, buildLocation, fabricator, emptyTable) - end - end - - --------------------------------------------------------------------------- - -- issue assist orders for remaining engineers - - IssueGuard(engineersOther, engineersOfFaction[1]) -end diff --git a/lua/sim/commands/ringing/ring-with-fabricators.lua b/lua/sim/commands/ringing/ring-with-fabricators.lua new file mode 100644 index 0000000000..4ab86509ba --- /dev/null +++ b/lua/sim/commands/ringing/ring-with-fabricators.lua @@ -0,0 +1,53 @@ +--****************************************************************************************************** +--** Copyright (c) 2022 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. +--****************************************************************************************************** + +local SortUnitsByTech = import("/lua/sim/commands/shared.lua").SortUnitsByTech +local RingUnit = import("/lua/sim/commands/ringing/shared.lua").RingUnit + +local InnerBuildOffsets = { { -2, 2 }, { 2, 2 }, { 2, -2 }, { -2, -2 }, } +local AllBuildOffsets = { { -2, 2 }, { 2, 2 }, { 2, -2 }, { -2, -2 }, { -4, 0 }, { 0, 4 }, { 4, 0 }, { 0, -4 }, } + +---@param extractor Unit +---@param engineers Unit[] +---@param allFabricators boolean +RingExtractor = function(extractor, engineers, allFabricators) + + --------------------------------------------------------------------------- + -- defensive programming + + -- confirm we have an extractor + if (not extractor) or (IsDestroyed(extractor)) then + return + end + + -- confirm that we have one engineer that can build the unit + SortUnitsByTech(engineers) + local fabricator = engineers[1].Blueprint.BlueprintId:sub(1, 2) .. 'b1104' + if (not __blueprints[fabricator]) or + (not engineers[1]:CanBuild(fabricator)) + then + return + end + + local offsets = (allFabricators and AllBuildOffsets) or InnerBuildOffsets + RingUnit(extractor, engineers, offsets, fabricator) +end diff --git a/lua/sim/commands/ringing/ring-with-storages.lua b/lua/sim/commands/ringing/ring-with-storages.lua new file mode 100644 index 0000000000..1319222f8a --- /dev/null +++ b/lua/sim/commands/ringing/ring-with-storages.lua @@ -0,0 +1,52 @@ + +--****************************************************************************************************** +--** Copyright (c) 2022 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. +--****************************************************************************************************** + +local SortUnitsByTech = import("/lua/sim/commands/shared.lua").SortUnitsByTech +local RingUnit = import("/lua/sim/commands/ringing/shared.lua").RingUnit + +local BuildOffsets = { { 2, 0 }, { 0, 2 }, { -2, 0 }, { 0, -2 } } + +---@param extractor Unit +---@param engineers Unit[] +RingExtractor = function(extractor, engineers) + + --------------------------------------------------------------------------- + -- defensive programming + + -- confirm we have an extractor + if (not extractor) or (IsDestroyed(extractor)) then + return + end + + -- confirm that we have one engineer that can build the unit + SortUnitsByTech(engineers) + local storage = engineers[1].Blueprint.BlueprintId:sub(1, 2) .. 'b1106' + if (not __blueprints[storage]) or + (not engineers[1]:CanBuild(storage)) + then + return + end + + RingUnit(extractor, engineers, BuildOffsets, storage) +end + diff --git a/lua/sim/commands/ring-with-storages.lua b/lua/sim/commands/ringing/shared.lua similarity index 64% rename from lua/sim/commands/ring-with-storages.lua rename to lua/sim/commands/ringing/shared.lua index c76eff461c..0549fd1f02 100644 --- a/lua/sim/commands/ring-with-storages.lua +++ b/lua/sim/commands/ringing/shared.lua @@ -1,4 +1,3 @@ - --****************************************************************************************************** --** Copyright (c) 2022 Willem 'Jip' Wijnia --** @@ -21,9 +20,7 @@ --** SOFTWARE. --****************************************************************************************************** -local SortUnitsByTech = import("/lua/sim/commands/shared.lua").SortUnitsByTech local FindNearestUnit = import("/lua/sim/commands/shared.lua").FindNearestUnit -local FindBuildingSkirts = import("/lua/sim/commands/shared.lua").FindBuildingSkirts local SortOffsetsByDistanceToPoint = import("/lua/sim/commands/shared.lua").SortOffsetsByDistanceToPoint -- upvalue scope for performance @@ -33,53 +30,88 @@ local EntityCategoryFilterDown = EntityCategoryFilterDown local IssueBuildAllMobile = IssueBuildAllMobile -- cached for performance -local CacheX1 = { } -local CacheZ1 = { } -local CacheX2 = { } -local CacheZ2 = { } - -local BuildLocation = { } -local EmptyTable = { } - -local BuildOffsets = { { 2, 0 }, { 0, 2 }, { -2, 0 }, { 0, -2 } } - ----@param extractor Unit ----@param engineers Unit[] -RingExtractor = function(extractor, engineers) - - --------------------------------------------------------------------------- - -- defensive programming +local CacheX1 = {} +local CacheZ1 = {} +local CacheX2 = {} +local CacheZ2 = {} + +local BuildLocation = {} +local EmptyTable = {} + +-- Scan and gather build skirts in the surrounding area. This is used as an alternative +-- to relying on `brain:CanBuildStructureAt(...)` as it returns false positives. +---@param cx number +---@param cz number +---@param skirtSize number +---@param cx1 number[] # re-useable array +---@param cz1 number[] # re-useable array +---@param cx2 number[] # re-useable array +---@param cz2 number[] # re-useable array +---@return number[] +---@return number[] +---@return number[] +---@return number[] +---@return number +function FindBuildingSkirts(cx, cz, skirtSize, cx1, cz1, cx2, cz2) + + local x1 = cx - (skirtSize + 10) + local z1 = cz - (skirtSize + 10) + local x2 = cx + (skirtSize + 10) + local z2 = cz + (skirtSize + 10) + + -- clear out the cache + for k = 1, table.getn(cx1) do + cx1[k] = nil + cz1[k] = nil + cx2[k] = nil + cz2[k] = nil + end - -- confirm we have an extractor - if (not extractor) or (IsDestroyed(extractor)) then - return + -- find all units that may prevent us from building + local structures = GetUnitsInRect(x1, z1, x2, z2) + if not structures then + return cx1, cz1, cx2, cz2, 0 end - -- confirm that we have one engineer that can build the unit - SortUnitsByTech(engineers) - local storage = engineers[1].Blueprint.BlueprintId:sub(1, 2) .. 'b1106' - if (not __blueprints[storage]) or - (not engineers[1]:CanBuild(storage)) - then - return + structures = EntityCategoryFilterDown(categories.STRUCTURE + categories.EXPERIMENTAL, structures) + + -- populate the skirts to check + local buildingSkirtCount = 0 + for k, unit in structures do + local blueprint = unit:GetBlueprint() + local px, _, pz = unit:GetPositionXYZ() + local sx, sz = 0.5 * blueprint.Physics.SkirtSizeX, 0.5 * blueprint.Physics.SkirtSizeZ + cx1[k] = px - sx + cz1[k] = pz - sz + cx2[k] = px + sz + cz2[k] = pz + sz + buildingSkirtCount = buildingSkirtCount + 1 end + return cx1, cz1, cx2, cz2, buildingSkirtCount +end + +---@param target Unit +---@param engineers Unit[] +---@param offsets { [1]: number, [2]: number }[] +---@param blueprintId UnitId +function RingUnit(target, engineers, offsets, blueprintId) + local faction = engineers[1].Blueprint.FactionCategory + local engineersOfFaction = EntityCategoryFilterDown(categories[faction], engineers) + local engineersOther = EntityCategoryFilterDown(categories.ALLUNITS - categories[faction], engineers) + --------------------------------------------------------------------------- -- determine all units in surroundings that may block construction - local blueprint = extractor:GetBlueprint() + local blueprint = target:GetBlueprint() local skirtSize = blueprint.Physics.SkirtSizeX - local cx, _, cz = extractor:GetPositionXYZ() - local cx1, cz1, cx2, cz2, buildingSkirtCount = FindBuildingSkirts(cx, cz, skirtSize, CacheX1, CacheZ1, CacheX2, CacheZ2) + local cx, _, cz = target:GetPositionXYZ() + local cx1, cz1, cx2, cz2, buildingSkirtCount = FindBuildingSkirts(cx, cz, skirtSize, CacheX1, CacheZ1, CacheX2, + CacheZ2) --------------------------------------------------------------------------- -- filter engineers and sort offsets - local faction = engineers[1].Blueprint.FactionCategory - local engineersOfFaction = EntityCategoryFilterDown(categories[faction], engineers) - local engineersOther = EntityCategoryFilterDown(categories.ALLUNITS - categories[faction], engineers) - - local offsets = BuildOffsets local nearestEngineer = FindNearestUnit(engineersOfFaction, cx, cz) if nearestEngineer then local ex, _, ez = nearestEngineer:GetPositionXYZ() @@ -111,7 +143,7 @@ RingExtractor = function(extractor, engineers) buildLocation[1] = bx buildLocation[3] = bz buildLocation[2] = GetTerrainHeight(bx, bz) - IssueBuildAllMobile(engineersOfFaction, buildLocation, storage, emptyTable) + IssueBuildAllMobile(engineersOfFaction, buildLocation, blueprintId, emptyTable) end end diff --git a/lua/sim/commands/shared.lua b/lua/sim/commands/shared.lua index faa6e4c11a..685948713d 100644 --- a/lua/sim/commands/shared.lua +++ b/lua/sim/commands/shared.lua @@ -357,56 +357,3 @@ function FindNearestUnit(units, px, pz) return nearest end - --- Scan and gather build skirts in the surrounding area. This is used as an alternative --- to relying on `brain:CanBuildStructureAt(...)` as it returns false positives. ----@param cx number ----@param cz number ----@param skirtSize number ----@param cx1 number[] # re-useable array ----@param cz1 number[] # re-useable array ----@param cx2 number[] # re-useable array ----@param cz2 number[] # re-useable array ----@return number[] ----@return number[] ----@return number[] ----@return number[] ----@return number -function FindBuildingSkirts(cx, cz, skirtSize, cx1, cz1, cx2, cz2) - - local x1 = cx - (skirtSize + 10) - local z1 = cz - (skirtSize + 10) - local x2 = cx + (skirtSize + 10) - local z2 = cz + (skirtSize + 10) - - -- clear out the cache - for k = 1, table.getn(cx1) do - cx1[k] = nil - cz1[k] = nil - cx2[k] = nil - cz2[k] = nil - end - - -- find all units that may prevent us from building - local structures = GetUnitsInRect(x1, z1, x2, z2) - if not structures then - return cx1, cz1, cx2, cz2, 0 - end - - structures = EntityCategoryFilterDown(categories.STRUCTURE + categories.EXPERIMENTAL, structures) - - -- populate the skirts to check - local buildingSkirtCount = 0 - for k, unit in structures do - local blueprint = unit:GetBlueprint() - local px, _, pz = unit:GetPositionXYZ() - local sx, sz = 0.5 * blueprint.Physics.SkirtSizeX, 0.5 * blueprint.Physics.SkirtSizeZ - cx1[k] = px - sx - cz1[k] = pz - sz - cx2[k] = px + sz - cz2[k] = pz + sz - buildingSkirtCount = buildingSkirtCount + 1 - end - - return cx1, cz1, cx2, cz2, buildingSkirtCount -end \ No newline at end of file