Skip to content

Commit

Permalink
Re-introduces limited version of old capping behavior (FAForever#5317)
Browse files Browse the repository at this point in the history
  • Loading branch information
Garanas authored Aug 15, 2023
1 parent 86d57d4 commit 6320973
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 9 deletions.
31 changes: 30 additions & 1 deletion lua/SimCallbacks.lua
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,37 @@ end

Callbacks.WeaponPriorities = import("/lua/weaponpriorities.lua").SetWeaponPriorities

---@param data { target: EntityId }
---@param selection Unit[]
Callbacks.RingExtractor = function(data, selection)
-- verify selection
if (not selection) or
TableEmpty(selection)
then
return
end

-- verify we have engineers
local engineers = EntityCategoryFilterDown(categories.ENGINEER, selection)
if TableEmpty(engineers) then
return
end

-- verify the extractor
local extractor = GetUnitById(data.target) --[[@as Unit]]
if (not extractor) or
(not extractor.Army) or
(not OkayToMessWithArmy(extractor.Army)) or
(not EntityCategoryContains(categories.MASSEXTRACTION, extractor))
then
return
end

import("/lua/sim/commands/ring-extractor.lua").RingExtractor(extractor, engineers)
end

---@param data any
---@param selection any
---@param selection Unit[]
Callbacks.SelectHighestEngineerAndAssist = function(data, selection)
if selection then
-- check for cheats
Expand Down
13 changes: 13 additions & 0 deletions lua/options/options.lua
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,19 @@ options = {
},
},

{
title = "<LOC OPTIONS_0273>Right click to ring extractors with storages",
key = 'structure_capping_feature_01',
type = 'toggle',
default = "on",
custom = {
states = {
{text = "<LOC _Off>Off", key = "off"},
{text = "<LOC _Off>On", key = "on"} -- only-storages-extractors full-suite
},
},
},

{
title = "<LOC ASSIST_TO_UPGRADE>Hold alt to force attack move",
key = 'alt_to_force_attack_move',
Expand Down
99 changes: 99 additions & 0 deletions lua/sim/commands/ring-extractor.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@

local TableGetn = table.getn

local BuildOffsets = { { 2, 0 }, { 0, 2 }, { -2, 0 }, { 0, -2 } }

---@param extractor Unit
---@param engineers Unit[]
RingExtractor = function(extractor, engineers)

-- verify the storage
local storage = engineers[1].Blueprint.BlueprintId:sub(1, 2) .. 'b1106'
if (not __blueprints[storage]) or
(not engineers[1]:CanBuild(storage))
then
return
end

-- split engineers by faction
local faction = engineers[1].Blueprint.FactionCategory
local engineersOfFaction = EntityCategoryFilterDown(categories[faction], engineers)
local engineersOther = EntityCategoryFilterDown(categories.ALLUNITS - categories[faction], engineers)

local blueprint = extractor:GetBlueprint()
local skirtSize = blueprint.Physics.SkirtSizeX
local cx, _, cz = extractor:GetPositionXYZ()

-- we manually scan for build skirts in the surrounding area. The function brain:CanBuildStructureAt(...) does
-- not always return correct results: it may end up returning true after factories upgraded

local x1 = cx - (skirtSize + 10)
local z1 = cz - (skirtSize + 10)
local x2 = cx + (skirtSize + 10)
local z2 = cz + (skirtSize + 10)

-- find all units that may prevent us from building
local structures = GetUnitsInRect(x1, z1, x2, z2)
if not structures then
return
end

structures = EntityCategoryFilterDown(categories.STRUCTURE + categories.EXPERIMENTAL, structures)

-- populate the skirts to check
local skirts = {}
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
local rect = {
px - sx, -- top left
pz - sz, -- top left
px + sx, -- bottom right
pz + sz -- bottom right
}

skirts[k] = rect
end

local buildLocation = {}
local engineerTable = {}
local emptyTable = {}

-- loop over build locations in given layer
for k, location in BuildOffsets do
buildLocation[1] = cx + location[1]
buildLocation[3] = cz + location[2]
buildLocation[2] = GetTerrainHeight(buildLocation[1], buildLocation[3])

local freeToBuild = true
for _, skirt in skirts do
if buildLocation[1] > skirt[1] and buildLocation[1] < skirt[3] then
if buildLocation[3] > skirt[2] and buildLocation[3] < skirt[4] then
freeToBuild = false
break
end
end
end

if freeToBuild then
for _, engineer in engineersOfFaction do
engineerTable[1] = engineer
IssueBuildMobile(engineerTable, buildLocation, storage, emptyTable)
end
end
end

-- assist for all other builders, spread over the number of actual builders
local builderIndex = 1
local builderCount = TableGetn(engineersOfFaction)
for _, builder in engineersOther do
engineerTable[1] = builder
IssueGuard(engineerTable, engineersOfFaction[builderIndex])

builderIndex = builderIndex + 1
if builderIndex > builderCount then
builderIndex = 1
end
end
end
7 changes: 5 additions & 2 deletions lua/sim/defaultweapons.lua
Original file line number Diff line number Diff line change
Expand Up @@ -706,8 +706,11 @@ DefaultProjectileWeapon = ClassWeapon(Weapon) {
local unit = self.unit
if unit.Dead then return end
unit:SetBusy(false)
self:WaitForAndDestroyManips()

-- at this point salvo is always done so reset the data
self.CurrentSalvoData = nil

self:WaitForAndDestroyManips()
local bp = self.Blueprint
for _, rack in bp.RackBones do
if rack.HideMuzzle then
Expand Down Expand Up @@ -994,7 +997,7 @@ DefaultProjectileWeapon = ClassWeapon(Weapon) {
end
end
end
self.CurrentSalvoData = nil -- once the salvo is done, reset the data

self:PlayFxRackReloadSequence()
local currentRackSalvoNumber = self.CurrentRackSalvoNumber
if currentRackSalvoNumber <= rackBoneCount then
Expand Down
89 changes: 83 additions & 6 deletions lua/ui/game/commandmode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ local MathAtan = math.atan
---@alias CommandMode 'order' | 'build' | 'buildanchored' | false

---@class CommandModeDataBase
---@field cursor? CommandCap # Similar to the field 'name'
---@field cursor? CommandCap # Similar to the field 'name'
---@field altCursor string # Allows for an alternative cursor

---@class CommandModeDataOrder : CommandModeDataBase
Expand Down Expand Up @@ -271,9 +271,81 @@ local function CheatSpawn(command, data)
}, true)
end

--- Allows us to detect a double / triple click
local pStructure1 = nil
function RingExtractor(command)
-- retrieve the option in question, can have values: 'off', 'only-storages-extractors' and 'full-suite'
local option = Prefs.GetFromCurrentProfile('options.structure_capping_feature_01')

-- bail out - we're not interested
if option == 'off' then
return
end

-- check if we have engineers
local units = EntityCategoryFilterDown(categories.ENGINEER, command.Units)
if not units[1] then return end

-- check if we have a building that we target
local structure = GetUnitById(command.Target.EntityId)
if not structure or IsDestroyed(structure) then return end

-- various conditions written out for maintainability
local isShiftDown = IsKeyDown('Shift')
local isDoubleTapped = structure ~= nil and (pStructure1 == structure)
local isUpgrading = structure:GetFocus() ~= nil

local isTech1 = structure:IsInCategory('TECH1')
local isTech2 = structure:IsInCategory('TECH2')
local isTech3 = structure:IsInCategory('TECH3')

if structure:IsInCategory('STRUCTURE') then
if structure:IsInCategory('MASSEXTRACTION') then
local buildStorages =
(
(isTech1 and isUpgrading and isDoubleTapped and isShiftDown)
or (isTech2 and isUpgrading and isDoubleTapped and isShiftDown)
or (isTech2 and not isUpgrading)
or isTech3
)

if buildStorages then

-- prevent consecutive calls
local gameTick = GameTick()
if structure.RingStoragesStamp then
if structure.RingStoragesStamp + 5 > gameTick then
return
end
end

structure.RingStoragesStamp = gameTick

print("Ringing extractor with storages")
SimCallback({ Func = 'RingExtractor', Args = { target = command.Target.EntityId } }, true)

if (isTech1 and isUpgrading) or (isTech2 and not isUpgrading) then
structure = nil
pStructure1 = nil
end
end
end
end

-- keep track of previous structure to identify a 2nd / 3rd click
pStructure1 = structure

-- prevent building up state when upgrading but shift isn't pressed
if isUpgrading and not isShiftDown then
structure = nil
pStructure1 = nil
end
end

-- cached category strings for performance
local categoriesFactories = categories.STRUCTURE * categories.FACTORY
local categoriesShields = categories.MOBILE * categories.SHIELD
local categoriesStructure = categories.STRUCTURE

--- Upgrades a tech 1 extractor that is being assisted
---@param unit UserUnit
Expand Down Expand Up @@ -319,7 +391,7 @@ local function OnGuardUnpause(guardees, target)
if not target.ThreadUnpause then
local id = target:GetEntityId()
target.ThreadUnpause = ForkThread(
function ()
function()
WaitSeconds(1.0)
local target = GetUnitById(id)
while target do
Expand All @@ -335,16 +407,16 @@ local function OnGuardUnpause(guardees, target)
SetPaused({ target }, false)
break
end
-- engineer is idle, died, we switch armies, ...
-- engineer is idle, died, we switch armies, ...
else
candidates[id] = nil
end
end
else
target.ThreadUnpauseCandidates = nil
target.ThreadUnpause = nil
break;
end
break
;end

WaitSeconds(1.0)
target = GetUnitById(id)
Expand All @@ -354,7 +426,7 @@ local function OnGuardUnpause(guardees, target)
end

-- add these to keep track
target.ThreadUnpauseCandidates = target.ThreadUnpauseCandidates or { }
target.ThreadUnpauseCandidates = target.ThreadUnpauseCandidates or {}
for k, guardee in guardees do
target.ThreadUnpauseCandidates[guardee:GetEntityId()] = true
end
Expand Down Expand Up @@ -425,6 +497,11 @@ function OnCommandIssued(command)
SimCallback(cb, true)
end

-- see if we can cap a structure
if EntityCategoryContains(categoriesStructure, command.Blueprint) then
RingExtractor(command)
end

-- called when:
-- - a construction is started
elseif command.CommandType == 'BuildMobile' then
Expand Down
22 changes: 22 additions & 0 deletions lua/ui/game/commands/context-based-templates-data.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@

--******************************************************************************************************
--** Copyright (c) 2023 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.
--******************************************************************************************************

---@class ContextBasedTemplate
---@field Name string # Printed on screen when cycling the templates
---@field TemplateData BuildTemplate # A regular build template, except that it is written in Pascal Case and usually the first unit is removed
Expand Down
22 changes: 22 additions & 0 deletions lua/ui/game/commands/context-based-templates.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@

--******************************************************************************************************
--** Copyright (c) 2023 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.
--******************************************************************************************************

-- performance related imports
local type = type
local import = import
Expand Down

0 comments on commit 6320973

Please sign in to comment.