feat: flere mål pr. run (afkrydsning; den stald der mangler mest får turen, AD's mål skiftes ved turstart) + under-faner Overblik/Runs (v1.11.0.0)

This commit is contained in:
2026-09-23 01:56:47 +02:00
parent 69a98984bb
commit bcd7de8344
12 changed files with 376 additions and 113 deletions
+135 -55
View File
@@ -9,6 +9,8 @@ SmartPickupFrame._mt = Class(SmartPickupFrame, TabbedMenuFrameElement)
SmartPickupFrame.REFRESH_INTERVAL_MS = 1000
SmartPickupFrame.LOG_PREFIX = "ADSmartPickup: "
SmartPickupFrame.STATE_OFF, SmartPickupFrame.STATE_ON = 1, 2
SmartPickupFrame.TAB_OVERVIEW, SmartPickupFrame.TAB_RUNS = 1, 2
SmartPickupFrame.TAB_NAMES = {"spu_tab_overview", "spu_tab_runs"}
function SmartPickupFrame.new(i18n, getAdEnv)
local self = SmartPickupFrame:superClass().new(nil, SmartPickupFrame._mt)
@@ -22,10 +24,12 @@ function SmartPickupFrame.new(i18n, getAdEnv)
self.refreshTimer = 0
self.statusOverride = nil
self.statusRow = nil -- Text-elementet i den klonede statusrække
self.needsRow = nil
self.needsRows = {}
self.pendingRebuild = false
self.frameOpen = false
self.focusLost = false
self.currentTab = SmartPickupFrame.TAB_OVERVIEW
self.subCategoryTabs = {}
self.backButtonInfo = {inputAction = InputAction.MENU_BACK}
self.startButtonInfo = {inputAction = InputAction.MENU_ACTIVATE, text = i18n:getText("spu_button_start"), callback = function() self:onClickStartStop() end}
self.newButtonInfo = {inputAction = InputAction.MENU_EXTRA_1, text = i18n:getText("spu_button_new"), callback = function() self:onClickNew() end}
@@ -55,6 +59,25 @@ function SmartPickupFrame:initialize()
FocusManager:removeElement(prefab)
end
end
if self.selectorPrefab ~= nil and self.subCategoryBox ~= nil and self.subCategoryPaging ~= nil then
self.selectorPrefab:unlinkElement()
FocusManager:removeElement(self.selectorPrefab)
for index, key in ipairs(SmartPickupFrame.TAB_NAMES) do
self.subCategoryPaging:addText(tostring(index))
local tab = self.selectorPrefab:clone(self.subCategoryBox)
FocusManager:loadElementFromCustomValues(tab)
tab:setText(self.i18n:getText(key))
local background = tab:getDescendantByName("background")
if background ~= nil then
background:setSize(tab.size[1], tab.size[2])
end
local tabIndex = index
tab.onClickCallback = function() self:switchTab(tabIndex) end
self.subCategoryTabs[index] = tab
end
self.subCategoryBox:invalidateLayout()
self.subCategoryPaging:setSize(self.subCategoryBox.maxFlowSize + 140 * g_pixelSizeScaledX)
end
self.ingredientModeOption:setTexts({self.i18n:getText("spu_mode_auto"), self.i18n:getText("spu_mode_manual")})
local loopTexts = {self.i18n:getText("spu_loops_infinite")}
for loops = 1, ADRuns.MAX_LOOPS do
@@ -173,6 +196,33 @@ function SmartPickupFrame:requestRebuild()
self:safe(self.rebuild)
end
function SmartPickupFrame:switchTab(tabIndex)
self.currentTab = tabIndex
for index, tab in ipairs(self.subCategoryTabs) do
tab:setSelected(index == tabIndex)
end
if self.subCategoryPaging ~= nil then
self.subCategoryPaging:setState(tabIndex, false)
end
self:requestRebuild()
self:safe(function(frame)
FocusManager:setFocus(frame.boxLayout)
end)
end
function SmartPickupFrame:onClickTab(state)
self:switchTab(state)
end
-- De faste rækker (Run, Varer, Wait-pulje, Ture, tærskel) hører til Runs-fanen.
function SmartPickupFrame:setStaticRowsVisible(isVisible)
for _, option in ipairs({self.runOption, self.ingredientModeOption, self.waitPoolOption, self.loopsOption, self.returnBelowOption}) do
if option ~= nil and option.parent ~= nil then
option.parent:setVisible(isVisible)
end
end
end
function SmartPickupFrame:getRun()
return self.selectedRunId ~= nil and ADRuns.getRun(ADRunsController.store, self.selectedRunId) or nil
end
@@ -192,7 +242,7 @@ function SmartPickupFrame:clearRows()
end
self.clonedRows, self.rowData = {}, {}
self.statusRow = nil
self.needsRow = nil
self.needsRows = {}
self.overviewRows = {}
end
@@ -239,30 +289,39 @@ function SmartPickupFrame:rebuild()
self.selectedRunId = store.runs[1] ~= nil and store.runs[1].id or nil
end
self:clearRows()
if adEnv ~= nil then
self:fillOverviewRows(adEnv)
local isOverview = self.currentTab == SmartPickupFrame.TAB_OVERVIEW
self:setStaticRowsVisible(not isOverview)
if isOverview then
if adEnv ~= nil then
self:fillOverviewRows(adEnv)
end
self:addTextRow(self.i18n:getText("spu_waitPools"))
self:fillPoolRows(adEnv)
self.statusRow = self:addTextRow("")
self:colorRows()
self.boxLayout:invalidateLayout()
self:restoreFocusIfLost()
self:refreshLive()
return
end
self:fillRunOption(adEnv)
local run = self:getRun()
local hasRun = run ~= nil and adEnv ~= nil
for _, element in ipairs({self.targetOption, self.ingredientModeOption, self.waitPoolOption, self.loopsOption, self.returnBelowOption}) do
for _, element in ipairs({self.ingredientModeOption, self.waitPoolOption, self.loopsOption, self.returnBelowOption}) do
element:setDisabled(not hasRun)
end
if hasRun then
self:fillTargetOption(adEnv, run)
self.ingredientModeOption:setState(run.ingredientMode == "manual" and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
self:fillIngredientRows(adEnv, run)
self.needsRow = self:addTextRow(self:describeNeeds(adEnv, run))
self:fillWaitPoolOption(adEnv, run)
self.loopsOption:setState(run.loops + 1, false)
self.returnBelowOption:setState(self:getReturnBelowIndex(run), false)
self:addTextRow(self.i18n:getText("spu_targets"))
self:fillTargetRows(adEnv, run)
self:fillIngredientRows(adEnv, run)
self:fillNeedsRows(adEnv, run)
self:addTextRow(self.i18n:getText("spu_vehicles"))
self:fillVehicleRows(adEnv, run)
end
if adEnv ~= nil then
self:addTextRow(self.i18n:getText("spu_waitPools"))
self:fillPoolRows(adEnv)
end
self.statusRow = self:addTextRow("")
self:colorRows()
self.boxLayout:invalidateLayout()
@@ -289,18 +348,49 @@ function SmartPickupFrame:fillRunOption(adEnv)
self.runOption:setDisabled(#self.runIds == 0)
end
function SmartPickupFrame:fillTargetOption(adEnv, run)
local texts, index = {self.i18n:getText("spu_noTarget")}, 1
self.targetIds = {false}
-- Mål = afkrydsning (flere stalde/fabrikker pr. run). Ved hver tur vælges den der mangler mest.
function SmartPickupFrame:fillTargetRows(adEnv, run)
local chosen = {}
for _, id in ipairs(ADRunsController.getRunTargets(run)) do
chosen[id] = true
end
for _, marker in ipairs(ADRunsController.getTargetMarkers(adEnv, g_currentMission:getFarmId())) do
table.insert(texts, tostring(marker.name))
table.insert(self.targetIds, marker.id)
if marker.id == run.targetWayPointId then
index = #texts
local row = self:addRow(self.ingredientPrefab, {kind = "target", wayPointId = marker.id})
row:getDescendantByName("title"):setText(" " .. tostring(marker.name))
row:getDescendantByName("option"):setState(chosen[marker.id] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end
end
-- Behovsrække pr. mål ("Cow 1 Food: Forage 0/33.750 l · …"), opdateres hvert sekund.
function SmartPickupFrame:fillNeedsRows(adEnv, run)
self.needsRows = {}
local targets = ADRunsController.getTargetsLevels(adEnv, run)
if #targets == 0 then
self:addTextRow(self.i18n:getText("spu_needs_noTarget"))
return
end
for _, target in ipairs(targets) do
table.insert(self.needsRows, {wayPointId = target.wayPointId, title = self:addTextRow(self:describeTargetNeeds(target))})
end
end
function SmartPickupFrame:describeTargetNeeds(target)
if #target.levels == 0 then
return string.format("%s: %s", target.name, self.i18n:getText("spu_needs_none"))
end
return string.format(self.i18n:getText("spu_needs"), target.name .. ": " .. self:formatLevels(target.levels))
end
function SmartPickupFrame:refreshNeedsRows(adEnv, run)
local byId = {}
for _, target in ipairs(ADRunsController.getTargetsLevels(adEnv, run)) do
byId[target.wayPointId] = target
end
for _, row in ipairs(self.needsRows or {}) do
if byId[row.wayPointId] ~= nil then
row.title:setText(self:describeTargetNeeds(byId[row.wayPointId]))
end
end
self.targetOption:setTexts(texts)
self.targetOption:setState(index, false)
end
function SmartPickupFrame:fillIngredientRows(adEnv, run)
@@ -404,11 +494,6 @@ function SmartPickupFrame:syncOptionsFromModel(adEnv, run)
option:setState(wantedIndex, false)
end
end
local targetIndex = 1
for index, id in ipairs(self.targetIds) do
if id == run.targetWayPointId then targetIndex = index end
end
sync(self.targetOption, targetIndex)
sync(self.ingredientModeOption, run.ingredientMode == "manual" and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF)
local poolIndex = 1
for index, group in ipairs(self.poolGroups) do
@@ -429,17 +514,6 @@ function SmartPickupFrame:formatLevels(levels)
return table.concat(parts, " · ")
end
function SmartPickupFrame:describeNeeds(adEnv, run)
if run.targetWayPointId == nil then
return self.i18n:getText("spu_needs_noTarget")
end
local levels = ADRunsController.getTargetLevels(adEnv, run)
if #levels == 0 then
return self.i18n:getText("spu_needs_none")
end
return string.format(self.i18n:getText("spu_needs"), self:formatLevels(levels))
end
-- ---- overblik (øverst): ét afsnit pr. run — status, traktorernes aktivitet, målets niveauer ----
function SmartPickupFrame:describeActivity(adEnv, vehicle)
@@ -469,24 +543,22 @@ function SmartPickupFrame:fillOverviewRows(adEnv)
return title
end
add(self.i18n:getText("spu_overview"))
local targetMarkerName = function(run)
local marker = run.targetWayPointId ~= nil and adEnv.ADGraphManager:getMapMarkerByWayPointId(run.targetWayPointId) or nil
return marker ~= nil and tostring(marker.name) or self.i18n:getText("spu_noTarget")
end
self.overviewRows = {}
for _, run in ipairs(ADRunsController.store.runs) do
local status = ADRunsController.getRunStatus(adEnv, run)
local statusText = status.running > 0 and string.format(self.i18n:getText("spu_status_running"), status.running, status.total)
or string.format(self.i18n:getText("spu_status_stopped"), status.total)
local header = add(string.format("%s — %s", run.name, statusText))
local entry = {run = run, header = header, vehicles = {}, target = nil}
local entry = {run = run, header = header, vehicles = {}, targets = {}}
for _, vehicleId in ipairs(run.vehicleIds) do
local vehicle = ADRunsController.findVehicleById(adEnv, vehicleId)
if vehicle ~= nil then
table.insert(entry.vehicles, {vehicle = vehicle, title = add(" " .. self:describeActivity(adEnv, vehicle))})
end
end
entry.target = add(string.format(" %s: %s", targetMarkerName(run), self:formatLevels(ADRunsController.getTargetLevels(adEnv, run))))
for _, target in ipairs(ADRunsController.getTargetsLevels(adEnv, run)) do
table.insert(entry.targets, {wayPointId = target.wayPointId, title = add(" " .. self:describeTargetNeeds(target))})
end
table.insert(self.overviewRows, entry)
end
-- forrest i layoutet: fjern de klonede rækker bagfra og sæt dem ind i rækkefølge først
@@ -512,9 +584,14 @@ function SmartPickupFrame:refreshOverview(adEnv)
for _, item in ipairs(entry.vehicles) do
item.title:setText(" " .. self:describeActivity(adEnv, item.vehicle))
end
if entry.target ~= nil then
local marker = entry.run.targetWayPointId ~= nil and adEnv.ADGraphManager:getMapMarkerByWayPointId(entry.run.targetWayPointId) or nil
entry.target:setText(string.format(" %s: %s", marker ~= nil and tostring(marker.name) or self.i18n:getText("spu_noTarget"), self:formatLevels(ADRunsController.getTargetLevels(adEnv, entry.run))))
local byId = {}
for _, target in ipairs(ADRunsController.getTargetsLevels(adEnv, entry.run)) do
byId[target.wayPointId] = target
end
for _, item in ipairs(entry.targets) do
if byId[item.wayPointId] ~= nil then
item.title:setText(" " .. self:describeTargetNeeds(byId[item.wayPointId]))
end
end
end
end
@@ -564,9 +641,7 @@ function SmartPickupFrame:refreshLive()
local isRunning = false
if run ~= nil then
self:syncOptionsFromModel(adEnv, run)
if self.needsRow ~= nil then
self.needsRow:setText(self:describeNeeds(adEnv, run))
end
self:refreshNeedsRows(adEnv, run)
local status = ADRunsController.getRunStatus(adEnv, run)
isRunning = status.running > 0
local text = isRunning and string.format(self.i18n:getText("spu_status_running"), status.running, status.total)
@@ -600,12 +675,12 @@ function SmartPickupFrame:onClickRun(state)
self:requestRebuild()
end
function SmartPickupFrame:onClickTarget(state)
function SmartPickupFrame:onClickTarget(state, element)
self:safe(function(frame)
local run = frame:getRun()
if run == nil then return end
-- targetIds[1] er false = "(vælg mål)"; modellen tolker false som "ryd målet"
ADRunsController.updateRun(run.id, {targetWayPointId = frame.targetIds[state] or false})
local run, data = frame:getRun(), frame.rowData[element]
if run == nil or data == nil then return end
ADRunsController.store = ADRuns.toggleTarget(ADRunsController.store, run.id, data.wayPointId)
Logging.info(SmartPickupFrame.LOG_PREFIX .. "run '%s': targets=%s", tostring(run.name), table.concat(ADRunsController.getRunTargets(ADRuns.getRun(ADRunsController.store, run.id)), " "))
frame:requestRebuild()
end)
end
@@ -620,9 +695,14 @@ function SmartPickupFrame:onClickIngredientMode(state)
end
function SmartPickupFrame:onClickIngredient(state, element)
-- mål-rækkerne deler prefab (og dermed onClick) med vare-rækkerne
local data = self.rowData[element]
if data ~= nil and data.kind == "target" then
return self:onClickTarget(state, element)
end
self:safe(function(frame)
local run, data = frame:getRun(), frame.rowData[element]
if run == nil or data == nil then return end
if run == nil or data == nil or data.kind ~= "ingredient" then return end
local names = {}
for _, name in ipairs(run.ingredients) do
if name ~= data.name then table.insert(names, name) end
+9 -7
View File
@@ -10,19 +10,21 @@
<!-- Samme opbygning som AutoDrive's egne ESC-sider: ScrollingLayout direkte i
menuContainer, alle rækker som direkte børn (ingen indlejrede layouts). -->
<ScrollingLayout profile="fs25_settingsLayout" id="boxLayout">
<!-- under-faner som basisspillets Priser-side (mønster fra FS25_FarmOperationsDashboard) -->
<BoxLayout profile="fs25_subCategorySelectorTabbedBox" id="subCategoryBox"/>
<ThreePartBitmap profile="fs25_lineSeparatorTopHighlighted" position="0px -57px"/>
<MultiTextOption profile="fs25_subCategorySelectorTabbed" id="subCategoryPaging" position="0px -10px" onClick="onClickTab"/>
<Button profile="fs25_subCategorySelectorTabbedTab" id="selectorPrefab">
<ThreePartBitmap profile="fs25_subCategorySelectorTabbedTabBg" name="background"/>
</Button>
<ScrollingLayout profile="fs25_settingsLayout" id="boxLayout" position="0px -90px" absoluteSizeOffset="0px 90px">
<Bitmap profile="fs25_multiTextOptionContainer">
<MultiTextOption profile="fs25_settingsMultiTextOption" onClick="onClickRun" id="runOption">
<Text profile="fs25_multiTextOptionTooltip" name="ignore" text="$l10n_spu_run_tooltip"/>
</MultiTextOption>
<Text profile="fs25_settingsMultiTextOptionTitle" text="$l10n_spu_run"/>
</Bitmap>
<Bitmap profile="fs25_multiTextOptionContainer">
<MultiTextOption profile="fs25_settingsMultiTextOption" onClick="onClickTarget" id="targetOption">
<Text profile="fs25_multiTextOptionTooltip" name="ignore" text="$l10n_spu_target_tooltip"/>
</MultiTextOption>
<Text profile="fs25_settingsMultiTextOptionTitle" text="$l10n_spu_target"/>
</Bitmap>
<Bitmap profile="fs25_multiTextOptionContainer">
<BinaryOption profile="fs25_settingsBinaryOption" onClick="onClickIngredientMode" id="ingredientModeOption">
<Text profile="fs25_multiTextOptionTooltip" name="ignore" text="$l10n_spu_ingredientMode_tooltip"/>