Skip to content

Commit

Permalink
Abstract the ringing feature to make it easier to use (FAForever#5390)
Browse files Browse the repository at this point in the history
  • Loading branch information
Garanas authored Sep 4, 2023
1 parent 80c706d commit dba25b9
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 216 deletions.
4 changes: 2 additions & 2 deletions lua/SimCallbacks.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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
Expand Down
123 changes: 0 additions & 123 deletions lua/sim/commands/ring-with-fabricators.lua

This file was deleted.

53 changes: 53 additions & 0 deletions lua/sim/commands/ringing/ring-with-fabricators.lua
Original file line number Diff line number Diff line change
@@ -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
52 changes: 52 additions & 0 deletions lua/sim/commands/ringing/ring-with-storages.lua
Original file line number Diff line number Diff line change
@@ -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

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

--******************************************************************************************************
--** Copyright (c) 2022 Willem 'Jip' Wijnia
--**
Expand All @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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

Expand Down
Loading

0 comments on commit dba25b9

Please sign in to comment.