Files
fs25-adsmartpickup/FS25_ADSmartPickup/gui/SmartPickupFlowTab.lua
T
masterdraco cc35b157bd feat(oversigt): Start/Stop alle flows + klik på flow hopper til redigering; palleskure viser paller af pladser (v1.20.0.0)
- ADRunsController.startAllRuns/stopAllRuns/hasActiveRuns (kørende flows røres ikke ved Start alle)
- Oversigt: knapsæt Start alle / Stop alle / Nyt run, samlet status, klikbare rækker (hop via update)
- Palleskur-statistik: kapacitet = (egne + ledige pladser) x indhold; 24/4000 paller 1 % i stedet for 600 %
- Manualer da/en opdateret
2026-09-25 21:03:30 +02:00

251 lines
14 KiB
Lua

-- Smart Pickup — fanen Flows
-- Tavle øverst: én tekstrække pr. flow (Vare · Fra → Til · Regel · Traktorer · Status), det valgte
-- markeret med ">". Under tavlen: detail-blok for det valgte flow — Retning, Fra/Til som BYGNINGER
-- (markøren findes automatisk), Varer, Regler, Vent ved, Traktorer, Status — og Wait-puljerne.
-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua).
-- ---- tavlen ----
local function joinOr(list, empty)
if #list == 0 then return empty end
return table.concat(list, " + ")
end
-- Én række i Oversigt: {flow, varer, fra, til, traktorer "kører/i alt", status}.
-- Status = advarsel (!!), ellers begrundelse, ellers flowets regel.
function SmartPickupFrame:flowCells(adEnv, run)
local row = ADRunsController.getFlowRow(adEnv, run)
local i18n = self.i18n
local products
if row.productAuto then
products = i18n:getText("spu_flow_needBased")
else
local titles = {}
for _, name in ipairs(row.productNames) do
local index = g_fillTypeManager:getFillTypeIndexByName(name)
local title = index ~= nil and g_fillTypeManager.getFillTypeTitleByIndex ~= nil and g_fillTypeManager:getFillTypeTitleByIndex(index) or name
table.insert(titles, tostring(title))
end
products = joinOr(titles, i18n:getText("spu_flow_noProducts"))
end
local missing = {}
for _, name in ipairs(row.missing) do missing[name] = true end
local function mark(names)
local out = {}
for _, name in ipairs(names) do table.insert(out, missing[name] and (name .. " !") or name) end
return out
end
local from, to
if row.kind == "outbound" then
from = ADFlowView.joinCounted(mark(row.fromNames), " + ", i18n:getText("spu_flow_noSource"))
local seen, policies = {}, {}
for _, policy in ipairs(row.policies) do
if not seen[policy] then seen[policy] = true; table.insert(policies, i18n:getText("spu_policy_" .. policy)) end
end
to = joinOr(policies, i18n:getText("spu_flow_noProducts"))
else
from = i18n:getText("spu_from_allSilos")
to = ADFlowView.joinCounted(mark(row.toNames), " + ", i18n:getText("spu_flow_noTarget"))
end
local rule
if row.kind == "outbound" then
rule = string.format(i18n:getText("spu_flow_ruleOut"), row.sellAtPercent or 90, row.storeAbovePercent or 80)
if row.collectRound then rule = rule .. ", " .. i18n:getText("spu_flow_round") end
else
rule = row.loops ~= nil and row.loops > 0 and string.format(i18n:getText("spu_flow_ruleInLoops"), row.loops) or i18n:getText("spu_flow_ruleIn")
end
local status = rule
if row.warning ~= nil then
status = "!! " .. row.warning
elseif row.reason ~= nil then
status = row.reason
end
local name = (run.id == self.selectedRunId and "> " or "") .. tostring(run.name)
return {name, products, from, to, string.format("%d/%d", row.running, row.total), status}
end
-- Hele rækken klikbar med musen (Bitmap-rækker har ingen onClick). Klikket gemmes kun som
-- pendingJumpRunId; hoppet sker i update(), fordi genopbygningen sletter netop denne række.
local MOUSE_LEFT = Input ~= nil and Input.MOUSE_BUTTON_LEFT or 1
local function isInside(element, posX, posY)
local position, size = element.absPosition, element.absSize
if position == nil or size == nil then return false end
return posX >= position[1] and posX <= position[1] + size[1] and posY >= position[2] and posY <= position[2] + size[2]
end
function SmartPickupFrame:makeRowClickable(row, runId)
local frame = self
local baseMouseEvent = row.mouseEvent
row.mouseEvent = function(element, posX, posY, isDown, isUp, button, eventUsed)
local used = baseMouseEvent(element, posX, posY, isDown, isUp, button, eventUsed)
-- også inden for listens synlige udsnit: rækker scrollet ud af syne må ikke fange klik
if not used and not eventUsed and isUp and button == MOUSE_LEFT and (element.getIsVisible == nil or element:getIsVisible())
and isInside(element, posX, posY) and isInside(frame.boxLayout, posX, posY) then
frame.pendingJumpRunId = runId
return true
end
return used
end
end
-- Fanen Oversigt: alle flows som tabel, grupperet Ind/Ud efter navn. Klik på et flow = redigér det.
function SmartPickupFrame:fillOverviewRows(adEnv)
self.overviewRows = {}
local i18n = self.i18n
self:addTextRow(i18n:getText("spu_overview_hint"))
self:addCellRow(self.flowPrefab, {i18n:getText("spu_col_flow"), i18n:getText("spu_col_goods"), i18n:getText("spu_col_from"), i18n:getText("spu_col_to"), i18n:getText("spu_col_tractors"), i18n:getText("spu_col_status")})
for _, group in ipairs(ADFlowView.boardGroups(ADRunsController.store.runs)) do
self:addTextRow(i18n:getText(group.kind == "outbound" and "spu_board_outbound" or "spu_board_supply"))
for _, run in ipairs(group.runs) do
local cells = self:addCellRow(self.flowPrefab, self:flowCells(adEnv, run))
if cells[1] ~= nil and cells[1].parent ~= nil then
self:makeRowClickable(cells[1].parent, run.id)
end
table.insert(self.overviewRows, {runId = run.id, cells = cells})
end
end
if #ADRunsController.store.runs == 0 then
self:addTextRow(i18n:getText("spu_noRuns"))
end
end
function SmartPickupFrame:refreshOverview(adEnv)
for _, entry in ipairs(self.overviewRows or {}) do
local run = ADRuns.getRun(ADRunsController.store, entry.runId)
if run ~= nil then
SmartPickupFrame.setCells(entry.cells, self:flowCells(adEnv, run))
end
end
end
-- ---- detail-blok ----
-- "Kostald 1 (stald) — markør 'cow 1 milk' 12 m" / "… — INGEN MARKØR inden for 40 m"
function SmartPickupFrame:describeBuilding(building, role)
local markers = ADBuildings.getMarkers ~= nil and ADBuildings.getMarkers(building, role) or {}
local kindText = self.i18n:getText("spu_source_kind_" .. tostring(building.kind))
if markers[1] == nil then
return string.format(" %s (%s) — %s", building.name, kindText, self.i18n:getText("spu_noMarker"))
end
-- kort form, så flere markører kan stå på én række: "Cow Barn (large) — Cow 1 Milk 6 m · Cow 1 Slurry 4 m"
local parts = {}
for _, marker in ipairs(markers) do
table.insert(parts, string.format(self.i18n:getText("spu_markerAt"), marker.name, marker.distance))
end
return string.format(" %s — %s", building.name, table.concat(parts, " · "))
end
-- Afkrydsning pr. bygning: kilder (udkørsel, role "load") eller mål (forsyning, role "unload").
function SmartPickupFrame:fillBuildingRows(adEnv, run, role)
if ADBuildings == nil then
self:addTextRow(self.i18n:getText("spu_status_noAd"))
return
end
local chosen = {}
local ids = role == "load" and run.sourceBuildingIds or role == "store" and run.storeBuildingIds or run.targetBuildingIds
local rowKind = role == "load" and "sourceBuilding" or role == "store" and "storeBuilding" or "targetBuilding"
for _, id in ipairs(ids or {}) do chosen[id] = true end
local count = 0
-- kandidater -> valgte først, så med markør; uden markør kun med "Vis alle bygninger"
local candidates = {}
-- "Lager til": kun bygninger der kan tage flowets lagervarer (æg-paller -> palleskure der tager æg)
local storeGoods = role == "store" and ADRunsController.getStoreGoods(adEnv, run) or nil
for _, building in ipairs(ADBuildings.list(adEnv, g_currentMission:getFarmId())) do
local isStoreKind = building.kind == "silo" or building.kind == "factory" or building.kind == "objectStorage"
local fits = (role == "load" and building.canBeSource) or (role == "unload" and building.canBeTarget)
or (role == "store" and isStoreKind and (chosen[building.id] or ADRunsController.canStoreAt(adEnv, building, storeGoods)))
if fits then
local markers = ADBuildings.getMarkers ~= nil and ADBuildings.getMarkers(building, role == "store" and "unload" or role) or {}
table.insert(candidates, {id = building.id, name = building.name, hasMarker = markers[1] ~= nil, building = building})
end
end
local shown = ADFlowView.buildingRows(candidates, chosen, self.showAllBuildings == true)
local hiddenBuildings = ADFlowView.hiddenCount(candidates, shown)
if hiddenBuildings > 0 or self.showAllBuildings then
self:addToggleRow("showAllBuildings", string.format(self.i18n:getText("spu_showAllBuildings"), hiddenBuildings), {self.i18n:getText("ui_no"), self.i18n:getText("ui_yes")}, self.showAllBuildings == true)
end
for _, entry in ipairs(shown) do
local building = entry.building
count = count + 1
local row = self:addRow(self.ingredientPrefab, {kind = rowKind, buildingId = building.id})
row:getDescendantByName("title"):setText(self:describeBuilding(building, role == "store" and "unload" or role))
row:getDescendantByName("option"):setState(chosen[building.id] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end
-- valgte bygninger der ikke længere findes (solgt)
for _, id in ipairs(ids or {}) do
if ADBuildings.find(adEnv, g_currentMission:getFarmId(), id) == nil then
local row = self:addRow(self.ingredientPrefab, {kind = rowKind, buildingId = id})
row:getDescendantByName("title"):setText(string.format(" %s — %s", tostring(id), self.i18n:getText("spu_buildingGone")))
row:getDescendantByName("option"):setState(SmartPickupFrame.STATE_ON, false)
end
end
if count == 0 then
self:addTextRow(self.i18n:getText("spu_noBuildings"))
end
end
function SmartPickupFrame:rebuildFlowDetail(adEnv, run)
ADRunsController.resolveMarkers(adEnv, run)
run = self:getRun()
local isOut = run.kind == "outbound"
self:addTextRow(string.format(self.i18n:getText("spu_section_flow"), run.name))
self.directionRowOption = self:addToggleRow("direction", self.i18n:getText("spu_direction"), {self.i18n:getText("spu_direction_in"), self.i18n:getText("spu_direction_out")}, isOut)
if isOut then
self:addTextRow(self.i18n:getText("spu_section_from"))
self:fillBuildingRows(adEnv, run, "load")
self:addTextRow(self.i18n:getText("spu_section_products"))
self:fillProductRows(adEnv, run)
self:addTextRow(self.i18n:getText("spu_section_rules"))
self.sellAtOption:setState(SmartPickupFrame.getStepIndex(self.sellAtSteps, run.sellAtPercent, ADRuns.DEFAULT_SELL_AT_PERCENT), false)
self.storeAboveOption:setState(SmartPickupFrame.getStepIndex(self.storeAboveSteps, run.storeAbovePercent, ADRuns.DEFAULT_STORE_ABOVE_PERCENT), false)
self.collectRoundRowOption = self:addToggleRow("collectRound", self.i18n:getText("spu_collectRound"), {self.i18n:getText("ui_no"), self.i18n:getText("ui_yes")}, run.collectRound == true)
self:addTextRow(self.i18n:getText("spu_section_store"))
self:fillBuildingRows(adEnv, run, "store")
self:fillOutboundStatusRows(adEnv, run)
else
self:addTextRow(self.i18n:getText("spu_section_to"))
self:fillBuildingRows(adEnv, run, "unload")
self:addTextRow(self.i18n:getText("spu_section_targets"))
self.ingredientModeRowOption = self:addToggleRow("ingredientMode", self.i18n:getText("spu_ingredientMode"), {self.i18n:getText("spu_mode_auto"), self.i18n:getText("spu_mode_manual")}, run.ingredientMode == "manual")
self:fillIngredientRows(adEnv, run)
self:fillNeedsRows(adEnv, run)
self:addTextRow(self.i18n:getText("spu_section_rules"))
self.loopsOption:setState(run.loops + 1, false)
self.returnBelowOption:setState(self:getReturnBelowIndex(run), false)
end
end
-- ---- callbacks ----
function SmartPickupFrame:onClickDirection(state)
self:safe(function(frame)
local run = frame:getRun()
if run == nil then return end
ADRunsController.updateRun(run.id, {kind = state == SmartPickupFrame.STATE_ON and "outbound" or "supply"})
frame:requestRebuild()
end)
end
function SmartPickupFrame:onClickBuilding(state, element)
self:safe(function(frame)
local run, data = frame:getRun(), frame.rowData[element]
if run == nil or data == nil then return end
if data.kind == "sourceBuilding" then
ADRunsController.store = ADRuns.toggleSourceBuilding(ADRunsController.store, run.id, data.buildingId)
elseif data.kind == "storeBuilding" then
ADRunsController.store = ADRuns.toggleStoreBuilding(ADRunsController.store, run.id, data.buildingId)
local updatedStore = ADRuns.getRun(ADRunsController.store, run.id)
Logging.info(SmartPickupFrame.LOG_PREFIX .. "run '%s': storeBuildings=%s", tostring(run.name), table.concat(updatedStore.storeBuildingIds or {}, " "))
frame:requestRebuild()
return
else
ADRunsController.store = ADRuns.toggleTargetBuilding(ADRunsController.store, run.id, data.buildingId)
end
local updated = ADRuns.getRun(ADRunsController.store, run.id)
Logging.info(SmartPickupFrame.LOG_PREFIX .. "run '%s': %s=%s", tostring(run.name), data.kind == "sourceBuilding" and "sourceBuildings" or "targetBuildings", table.concat(data.kind == "sourceBuilding" and updated.sourceBuildingIds or updated.targetBuildingIds, " "))
ADRunsController.resolveMarkers(frame.getAdEnv(), updated)
frame:requestRebuild()
end)
end