feat(gui): guiden "Opsæt mark" — markfanen får oversigt med resumé (klik på en linje = ret ét trin), trin-for-trin-guide på fælles guide-ramme (ADGuide/ADFieldGuide) og egen markflåde-visning (v1.31.0.0)

This commit is contained in:
2026-09-26 20:20:56 +02:00
parent 7cbd8fb878
commit 92af260a79
17 changed files with 1953 additions and 329 deletions
+41 -278
View File
@@ -1,16 +1,12 @@
-- Smart Pickup — fanen Marker
-- Tabel over alle gårdens marker (Mark · Afgrøde · Plan · Kørsel · Status · Maskiner); klik på en række
-- viser markens detaljer under tabellen: plan efter markens art (slåafgrøde, høst, bar), maskiner pr. trin,
-- aflæsningsvogne, efter høst, såning, pleje, tving lager, jordtilstand. Nederst markflåden med flueben.
-- Al logik ligger i ADFieldJobs/ADFieldWork/ADFieldModel/ADFieldView.
-- Oversigt: tabel over alle gårdens marker (Mark · Afgrøde · Plan · Kørsel · Status · Maskiner); klik på en
-- række viser markens resumé (én linje pr. emne — klik på en linje for at rette netop det trin), hele
-- statusteksten og markøren. [Opsæt mark] åbner guiden (gui/SmartPickupFieldGuide.lua + SmartPickupGuide.lua),
-- [Markflåde] viser den fælles markflåde. Al logik ligger i ADFieldJobs/ADFieldModel/ADFieldView/ADFieldGuide.
-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua).
SmartPickupFrame.FIELD_ROW_KINDS = {
fieldProduct = true, fieldMode = true, fieldRake = true, fieldStore = true, fleetVehicle = true,
fieldRigs = true, fieldUnloaders = true, fieldMulch = true, fieldLime = true, fieldSoil = true,
fieldSow = true, fieldSowFruit = true, fieldRoll = true, fieldFertilize = true, fieldWeed = true,
fieldTarget = true, fleetMode = true, fieldStraw = true, fieldRig = true,
}
-- Markflådens rækker (vehiclePrefab/ingredientPrefab) routes til onClickFieldRow.
SmartPickupFrame.FIELD_ROW_KINDS = {fleetVehicle = true, fleetMode = true}
SmartPickupFrame.ROLE_KEYS = {
mower = "spu_field_role_mower", tedder = "spu_field_role_tedder", rake = "spu_field_role_rake",
collector = "spu_field_role_collector", harvester = "spu_field_role_harvester", unloader = "spu_field_role_unloader",
@@ -19,15 +15,6 @@ SmartPickupFrame.ROLE_KEYS = {
weeder = "spu_field_role_weeder", fertilizer = "spu_field_role_fertilizer",
baler = "spu_field_role_baler", baleCollector = "spu_field_role_baleCollector",
}
SmartPickupFrame.SOIL_VALUES = {"auto", "cultivate", "plow", "none"}
-- simple Ja/Nej-valg på planen: rækketype -> plan-felt
SmartPickupFrame.PLAN_TOGGLES = {
fieldRake = "rake", fieldMulch = "mulch", fieldLime = "lime", fieldSow = "sow", fieldRoll = "roll",
fieldFertilize = "fertilize", fieldWeed = "weed",
}
-- valg med flere muligheder: rækketype -> plan-felt
SmartPickupFrame.PLAN_CHOICES = {fieldRigs = "rigs", fieldUnloaders = "unloaders", fieldSoil = "soil", fieldSowFruit = "sowFruit", fieldStraw = "straw"}
SmartPickupFrame.STRAW_VALUES = {"chop", "collect", "bale"}
-- ---- tekster ----
@@ -95,18 +82,6 @@ function SmartPickupFrame:fieldStatusText(field, info, short)
return text
end
-- Hvad moden gør med marken frem mod mål-afgrøden (én linje under valget).
function SmartPickupFrame:targetSummary(field, info)
local target = self:fruitTitle(field.plan.targetFruit)
if info.kind == "bare" then
return string.format(self.i18n:getText("spu_field_target_bare"), target)
end
if info.fruitName == field.plan.targetFruit and info.kind == "mowable" then
return string.format(self.i18n:getText("spu_field_target_keep"), target)
end
return string.format(self.i18n:getText("spu_field_target_switch"), tostring(info.cropTitle or "-"), target)
end
function SmartPickupFrame:fruitTitle(name)
local fruit = g_fruitTypeManager ~= nil and name ~= nil and name ~= "" and g_fruitTypeManager:getFruitTypeByName(name) or nil
return fruit ~= nil and fruit.fillType ~= nil and tostring(fruit.fillType.title) or tostring(name)
@@ -139,47 +114,15 @@ function SmartPickupFrame:fieldCells(row)
}
end
-- ---- rækker ----
-- Valg med flere muligheder (klon af productPrefab): values[i] er værdien for texts[i].
function SmartPickupFrame:addChoiceRow(kind, title, texts, values, current)
local row = self:addRow(self.productPrefab, {kind = kind, values = values})
local option = row:getDescendantByName("option")
option:setTexts(texts)
local index = 1
for position, value in ipairs(values) do
if value == current then index = position end
end
option:setState(index, false)
row:getDescendantByName("title"):setText(title)
return option
end
local function numberTexts(from, to)
local texts, values = {}, {}
for value = from, to do
table.insert(texts, tostring(value))
table.insert(values, value)
end
return texts, values
end
-- Afgrøder der kan sås (første valg: samme som den høstede / ingen mål-afgrøde).
function SmartPickupFrame:sowFruitChoices(firstKey)
local texts, values = {self.i18n:getText(firstKey or "spu_field_sowSame")}, {""}
for _, fruit in ipairs(g_fruitTypeManager ~= nil and g_fruitTypeManager.fruitTypes or {}) do
if fruit.allowsSeeding ~= false and fruit.fillType ~= nil and fruit.name ~= nil then
table.insert(texts, tostring(fruit.fillType.title or fruit.name))
table.insert(values, fruit.name)
end
end
return texts, values
end
-- ---- opbygning ----
function SmartPickupFrame:fillFieldRows(adEnv)
self.fieldRows = {}
if self:hasGuideOn(SmartPickupFrame.TAB_FIELDS) and self:fillGuideRows(adEnv) then return end
if self.fieldView == "fleet" then
self:fillFleetRows(adEnv)
return
end
local i18n = self.i18n
local infos = ADFields.list()
if #infos == 0 then
@@ -201,161 +144,48 @@ function SmartPickupFrame:fillFieldRows(adEnv)
end
local selected = self.selectedFieldId ~= nil and ADFields.getInfo(self.selectedFieldId) or nil
if selected ~= nil then
self:fillFieldDetail(adEnv, selected)
self:fillFieldOverview(adEnv, selected)
end
self:fillFleetRows(adEnv)
end
-- lagervalget viser de nærmeste siloer (plus valgte, marksiloer og gæringssiloer)
SmartPickupFrame.FIELD_STORE_ROWS = 8
function SmartPickupFrame:fillFieldDetail(adEnv, info)
-- Den valgte marks resumé: én klikbar linje pr. emne ([Ret] åbner kun det trin), hele status, markør og
-- advarsel om marksilo der ikke tømmes hjem.
function SmartPickupFrame:fillFieldOverview(adEnv, info)
local i18n = self.i18n
local field = ADFieldJobs.getField(info.id)
local plan = field.plan
local yesNo = {i18n:getText("ui_no"), i18n:getText("ui_yes")}
local crop = ADFieldView.cropText(info, function(key) return i18n:getText(key) end)
self:addTextRow(string.format(i18n:getText("spu_field_section"), info.id, crop))
-- hele statusteksten (tabellens celle afkorter den)
self:addTextRow(" " .. i18n:getText("spu_guide_sum_hint"))
local context = self:fieldGuideContext(adEnv, info, field.plan, field.plan.storeBuildingIds)
local guide = ADFieldGuide.open(field, context)
local getText = function(key) return i18n:getText(key) end
local fieldId = info.id
for _, line in ipairs(ADFieldGuide.summary(guide)) do
local step = line.step
self:addLinkRow(" " .. ADGuide.lineText(line, getText) .. i18n:getText("spu_guide_editSuffix"), function(frame)
frame:openFieldStep(fieldId, step)
end)
end
self:addTextRow(" " .. string.format(i18n:getText("spu_field_statusFull"), self:fieldStatusText(field, info)))
local targetTexts, targetValues = self:sowFruitChoices("spu_field_target_none")
self:addChoiceRow("fieldTarget", i18n:getText("spu_field_target"), targetTexts, targetValues, plan.targetFruit or "")
local hasTarget = plan.targetFruit ~= nil and plan.targetFruit ~= ""
if hasTarget then
self:addTextRow(" " .. self:targetSummary(field, info))
else
self:addToggleRow("fieldMode", i18n:getText("spu_field_mode"), {i18n:getText("spu_field_mode_manual"), i18n:getText("spu_field_mode_auto")}, plan.mode == "auto")
end
local rigTexts, rigValues = numberTexts(1, ADFieldModel.MAX_RIGS)
self:addChoiceRow("fieldRigs", i18n:getText("spu_field_rigs"), rigTexts, rigValues, plan.rigs or 1)
-- græs-valgene (ensilage/hø, riv) også når målet er en slåafgrøde (fx græs på en kornmark)
local targetMowable = false
if hasTarget and ADFields ~= nil and g_fruitTypeManager ~= nil then
local targetFruit = g_fruitTypeManager:getFruitTypeByName(plan.targetFruit)
targetMowable = targetFruit ~= nil and ADFields.getMowable()[targetFruit.index] ~= nil
end
if info.kind == "mowable" or targetMowable then
local isHay = ADFieldModel.product(plan, "mowable") == "hay"
self:addToggleRow("fieldProduct", i18n:getText("spu_field_plan"), {i18n:getText("spu_field_plan_silage"), i18n:getText("spu_field_plan_hay")}, isHay)
self:addToggleRow("fieldRake", i18n:getText("spu_field_rake"), yesNo, plan.rake == true)
end
-- klargøring: høst-/bare marker, og græsmarker der skal skifte til en anden mål-afgrøde
if info.kind ~= "mowable" or (hasTarget and info.fruitName ~= plan.targetFruit) then
if info.kind == "harvest" then
local unloaderTexts, unloaderValues = numberTexts(1, ADFieldModel.MAX_UNLOADERS)
self:addChoiceRow("fieldUnloaders", i18n:getText("spu_field_unloaders"), unloaderTexts, unloaderValues, plan.unloaders or 1)
local strawTexts = {}
for _, value in ipairs(SmartPickupFrame.STRAW_VALUES) do table.insert(strawTexts, i18n:getText("spu_field_straw_" .. value)) end
self:addChoiceRow("fieldStraw", i18n:getText("spu_field_straw"), strawTexts, SmartPickupFrame.STRAW_VALUES, plan.straw or "chop")
end
self:addTextRow(i18n:getText(info.kind == "harvest" and "spu_field_section_after" or "spu_field_section_prep"))
self:addToggleRow("fieldMulch", i18n:getText("spu_field_mulch"), yesNo, plan.mulch == true)
local soilTexts = {}
for _, value in ipairs(SmartPickupFrame.SOIL_VALUES) do table.insert(soilTexts, i18n:getText("spu_field_soil_" .. value)) end
self:addChoiceRow("fieldSoil", i18n:getText("spu_field_soil"), soilTexts, SmartPickupFrame.SOIL_VALUES, plan.soil or "auto")
if not hasTarget then
self:addToggleRow("fieldSow", i18n:getText("spu_field_sow"), yesNo, plan.sow == true)
if plan.sow then
local fruitTexts, fruitValues = self:sowFruitChoices()
self:addChoiceRow("fieldSowFruit", i18n:getText("spu_field_sowFruit"), fruitTexts, fruitValues, plan.sowFruit or "")
end
end
if hasTarget or plan.sow then
self:addToggleRow("fieldRoll", i18n:getText("spu_field_roll"), yesNo, plan.roll == true)
end
end
self:addTextRow(i18n:getText("spu_field_section_care"))
self:addToggleRow("fieldFertilize", i18n:getText("spu_field_fertilize"), yesNo, plan.fertilize ~= false)
self:addToggleRow("fieldLime", i18n:getText("spu_field_lime"), yesNo, plan.lime ~= false)
if info.kind ~= "mowable" then
self:addToggleRow("fieldWeed", i18n:getText("spu_field_weed"), yesNo, plan.weed ~= false)
end
local soil = self:soilText(info)
self:addTextRow(" " .. string.format(i18n:getText("spu_field_soilState"), soil ~= "" and soil or i18n:getText("spu_field_soilOk")))
if info.markerId ~= nil then
self:addTextRow(string.format(i18n:getText("spu_field_marker"), tostring(info.markerName), info.markerDistance or 0))
else
self:addTextRow(" " .. string.format(i18n:getText("spu_field_noMarker"), info.id))
self:addTextRow(" " .. string.format(i18n:getText("spu_guide_noMarker"), info.id))
end
if field.mowWidth ~= nil then
self:addTextRow(string.format(i18n:getText("spu_field_mowWidth"), field.mowWidth))
end
if field.plan.rake and field.windrowOffset ~= nil then
self:addTextRow(string.format(i18n:getText("spu_field_windrowOffset"), math.abs(field.windrowOffset),
i18n:getText(math.abs(field.windrowOffset) <= 0.05 and "spu_field_side_center" or (field.windrowOffset > 0 and "spu_field_side_left" or "spu_field_side_right"))))
end
self:fillFieldStoreRows(adEnv, info, field)
self:addFieldSiloWarnings(context.fieldSilos)
end
-- Tving lager: siloer der tager markens vare (skår/tørskår/høst); ved ensilage også gæringssiloer.
function SmartPickupFrame:fillFieldStoreRows(adEnv, info, field)
local i18n = self.i18n
local product = ADFieldModel.product(field.plan, info.kind)
local fillType
if info.kind == "mowable" then
fillType = product == "hay" and info.hay or info.windrow
else
fillType = info.harvestFillType
end
if fillType == nil then return end
self:fillDedicatedRows(adEnv, field)
self:addTextRow(i18n:getText("spu_field_section_store"))
local chosen = {}
for _, id in ipairs(field.plan.storeBuildingIds) do chosen[id] = true end
local farmId = g_currentMission:getFarmId()
local fieldSilos = {}
for _, building in ipairs(ADFields.fieldSilos(adEnv, farmId, info)) do fieldSilos[building.id] = building end
local fermenterIds = {}
if info.kind == "mowable" and product == "silage" then
for _, building in ipairs(ADFieldJobs.getFermenters(adEnv, farmId, fillType, info.x, info.z)) do fermenterIds[building.id] = true end
end
-- kandidater med markørnavn og afstand til marken (34 ens "Farma 500 + Obi 1000" kan ellers ikke skelnes)
local entries = {}
for _, building in ipairs(ADBuildings.list(adEnv, farmId)) do
local isSilo = building.kind == "silo" and ADRunsController.canStoreAt(adEnv, building, {{fillType = fillType, isPallet = false}})
if chosen[building.id] or isSilo or fermenterIds[building.id] then
local distance = nil
local node = building.placeable ~= nil and building.placeable.rootNode or nil
if node ~= nil and info.x ~= nil then
local bx, _, bz = getWorldTranslation(node)
distance = math.sqrt((bx - info.x) ^ 2 + (bz - info.z) ^ 2)
end
table.insert(entries, {id = building.id, building = building, distance = distance, chosen = chosen[building.id] == true,
fieldSilo = fieldSilos[building.id] ~= nil, fermenter = fermenterIds[building.id] == true})
end
end
local storeRows, hiddenStores = ADFieldView.storeRows(entries, SmartPickupFrame.FIELD_STORE_ROWS)
local count = #storeRows
for _, entry in ipairs(storeRows) do
local building = entry.building
local row = self:addRow(self.ingredientPrefab, {kind = "fieldStore", buildingId = building.id})
local markers = ADBuildings.getMarkers ~= nil and ADBuildings.getMarkers(building, "unload") or {}
-- det der skelner rækkerne først (rækken afkorter bagfra): markør, afstand, art — så bygningens navn
local label = (markers[1] ~= nil and tostring(markers[1].name) or tostring(building.name)) .. ", " .. ADFieldView.distanceText(entry.distance)
if entry.fieldSilo then label = label .. " (" .. i18n:getText("spu_field_fieldSilo") .. ")" end
if entry.fermenter then label = label .. " (" .. i18n:getText("spu_field_fermenter") .. ")" end
if markers[1] ~= nil then label = label .. " — " .. tostring(building.name) end
row:getDescendantByName("title"):setText(" " .. label)
row:getDescendantByName("option"):setState(entry.chosen and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end
if hiddenStores > 0 then
self:addTextRow(" " .. string.format(i18n:getText("spu_field_storeHidden"), hiddenStores))
end
if count == 0 then
self:addTextRow(" " .. i18n:getText("spu_field_noStore"))
elseif #field.plan.storeBuildingIds == 0 then
self:addTextRow(" " .. i18n:getText((info.kind == "mowable" and product == "silage") and "spu_field_storeAutoSilage" or "spu_field_storeAuto"))
end
-- marksiloer skal tømmes hjem af et udkørselsflow
for id, building in pairs(fieldSilos) do
-- Marksiloer skal tømmes hjem af et udkørselsflow.
function SmartPickupFrame:addFieldSiloWarnings(fieldSilos)
for _, silo in ipairs(fieldSilos or {}) do
local emptied = false
for _, run in ipairs(ADRunsController.store.runs) do
for _, sourceId in ipairs(run.kind == "outbound" and run.sourceBuildingIds or {}) do
if sourceId == id then emptied = true end
if sourceId == silo.id then emptied = true end
end
end
if not emptied then
self:addTextRow(" " .. string.format(i18n:getText("spu_field_fieldSiloNotEmptied"), tostring(building.name)))
self:addTextRow(" " .. string.format(self.i18n:getText("spu_field_fieldSiloNotEmptied"), tostring(silo.name or silo.text)))
end
end
end
@@ -387,54 +217,14 @@ function SmartPickupFrame:rigDetails(rig)
return #parts > 0 and (" — " .. table.concat(parts, " — ")) or ""
end
-- {[fieldId] = rigIds} for alle marker
local function rigIdsByField()
local byField = {}
for fieldId, field in pairs(ADFieldJobs.state.fields) do byField[fieldId] = field.plan ~= nil and field.plan.rigIds or {} end
return byField
end
-- "3 maskiner er faste på andre marker (F22, F90)"
function SmartPickupFrame:addHiddenDedicatedRow(hidden, fields)
if hidden <= 0 then return end
local names = {}
for _, fieldId in ipairs(fields) do table.insert(names, "F" .. tostring(fieldId)) end
self:addTextRow(" " .. string.format(self.i18n:getText("spu_field_hiddenDedicated"), hidden, table.concat(names, ", ")))
end
-- Faste maskiner til den valgte mark: en fast maskine bruges kun her, og marken venter på sin egen hellere
-- end at låne en anden med samme opgave. Maskiner der er faste på en anden mark er markeret.
function SmartPickupFrame:fillDedicatedRows(adEnv, field)
local i18n = self.i18n
self:addTextRow(i18n:getText("spu_field_section_dedicated"))
local owner = {}
for fieldId, other in pairs(ADFieldJobs.state.fields) do
for _, id in ipairs(other.plan ~= nil and other.plan.rigIds or {}) do owner[id] = fieldId end
end
local rigs, hiddenRigs, hiddenFields = ADFieldFleet.visibleForField(ADFieldJobs.getRigs(adEnv), field.id, rigIdsByField())
table.sort(rigs, function(a, b)
local ma, mb = owner[a.id] == field.id, owner[b.id] == field.id
if ma ~= mb then return ma end
if a.role ~= b.role then return a.role < b.role end
return ADFlowView.naturalLess(a.label, b.label)
end)
for _, rig in ipairs(rigs) do
local row = self:addRow(self.vehiclePrefab, {kind = "fieldRig", vehicleId = rig.id})
local elsewhere = owner[rig.id] ~= nil and owner[rig.id] ~= field.id
and (" — " .. string.format(i18n:getText("spu_field_dedicatedElsewhere"), tostring(owner[rig.id]))) or ""
row:getDescendantByName("title"):setText(string.format(" %s — %s%s", tostring(rig.label), self:roleText(rig.role), elsewhere))
row:getDescendantByName("option"):setState(owner[rig.id] == field.id and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end
self:addHiddenDedicatedRow(hiddenRigs, hiddenFields)
end
-- Markflåden (egen visning): alle maskiner med flueben, "fast på F22", "i gang på F108", bredde-advarsler.
function SmartPickupFrame:fillFleetRows(adEnv)
local i18n = self.i18n
self:addTextRow(i18n:getText("spu_field_section_fleet"))
local useAll = ADFieldJobs.state.fleetMode ~= "ticked"
self:addToggleRow("fleetMode", i18n:getText("spu_field_fleetAll"), {i18n:getText("ui_no"), i18n:getText("ui_yes")}, useAll)
-- valgt mark: maskiner der er faste på en anden mark skjules (de kan ikke bruges her)
local rigs, hiddenRigs, hiddenFields = ADFieldFleet.visibleForField(ADFieldJobs.getRigs(adEnv), self.selectedFieldId, rigIdsByField())
local rigs = {}
for _, rig in ipairs(ADFieldJobs.getRigs(adEnv)) do table.insert(rigs, rig) end
table.sort(rigs, function(a, b)
if a.role ~= b.role then return a.role < b.role end
return ADFlowView.naturalLess(a.label, b.label)
@@ -448,12 +238,15 @@ function SmartPickupFrame:fillFleetRows(adEnv)
if #rigs == 0 then
self:addTextRow(" " .. i18n:getText("spu_field_noRigs"))
end
self:addHiddenDedicatedRow(hiddenRigs, hiddenFields)
end
-- ---- live ----
function SmartPickupFrame:refreshFieldRows(adEnv)
if self:hasGuideOn(SmartPickupFrame.TAB_FIELDS) or self.fieldView == "fleet" then
self:setStatusText(self.statusOverride or "")
return
end
local rowsById = {}
for _, row in ipairs(ADFieldView.rows(ADFields.list(), ADFieldJobs.state.fields)) do rowsById[row.info.id] = row end
local working, total = 0, 0
@@ -473,45 +266,15 @@ function SmartPickupFrame:refreshFieldRows(adEnv)
self.fieldStartButtonInfo.disabled = selected == nil or info == nil or (not isWorking and info.markerId == nil)
end
-- ---- klik ----
-- ---- klik (markflåden) ----
function SmartPickupFrame:onClickFieldRow(state, data)
self:safe(function(frame)
local isOn = state == SmartPickupFrame.STATE_ON
local id = frame.selectedFieldId
if data.kind == "fleetVehicle" then
ADFieldJobs.toggleFleet(data.vehicleId)
elseif data.kind == "fleetMode" then
ADFieldJobs.setFleetMode(isOn and "all" or "ticked")
elseif id == nil then
return
elseif data.kind == "fieldTarget" then
ADFieldJobs.setTarget(id, data.values ~= nil and data.values[state] or "")
elseif SmartPickupFrame.PLAN_CHOICES[data.kind] ~= nil then
local value = data.values ~= nil and data.values[state] or nil
if value ~= nil then ADFieldJobs.setPlan(id, {[SmartPickupFrame.PLAN_CHOICES[data.kind]] = value}) end
elseif data.kind == "fieldProduct" then
ADFieldJobs.setPlan(id, {product = isOn and "hay" or "silage"})
elseif data.kind == "fieldMode" then
ADFieldJobs.setPlan(id, {mode = isOn and "auto" or "manual"})
elseif SmartPickupFrame.PLAN_TOGGLES[data.kind] ~= nil then
ADFieldJobs.setPlan(id, {[SmartPickupFrame.PLAN_TOGGLES[data.kind]] = isOn})
elseif data.kind == "fieldRig" then
-- fast på denne mark (og fjernes fra en anden mark)
local byField = {}
for fieldId, other in pairs(ADFieldJobs.state.fields) do byField[fieldId] = other.plan ~= nil and other.plan.rigIds or {} end
local updated = ADFieldFleet.dedicate(byField, id, data.vehicleId, isOn)
for fieldId, ids in pairs(updated) do
local before = table.concat(byField[fieldId] or {}, " ")
if before ~= table.concat(ids, " ") then ADFieldJobs.setPlan(fieldId, {rigIds = ids}) end
end
elseif data.kind == "fieldStore" then
local ids = {}
for _, buildingId in ipairs(ADFieldJobs.getField(id).plan.storeBuildingIds) do
if buildingId ~= data.buildingId then table.insert(ids, buildingId) end
end
if isOn then table.insert(ids, data.buildingId) end
ADFieldJobs.setPlan(id, {storeBuildingIds = ids})
end
frame:requestRebuild()
end)