-- Smart Pickup — fanen Flows: tavle + resumé, og guiden "Opsæt flow" -- Tavlen øverst (én række pr. flow), det valgte flows resumé (klik på en linje = ret ét trin; Navn = omdøb) -- og status. [Opsæt flow] / [Nyt flow] åbner guiden på den fælles trin-skærm (gui/SmartPickupGuide.lua). -- Henter det spil-afhængige som `context` til ADFlowGuide og gemmer via ADRunsController. -- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua). function SmartPickupFrame:initFlowButtons(i18n) self.flowSetupButtonInfo = {inputAction = InputAction.MENU_EXTRA_1, text = i18n:getText("spu_guide_flow_open"), callback = function() self:onClickFlowSetup() end} self.flowNewButtonInfo = {inputAction = InputAction.MENU_EXTRA_2, text = i18n:getText("spu_button_new"), callback = function() self:onClickFlowNew() end} -- Flows-fanen: [Start/Stop] [Opsæt flow] [Nyt flow] [Slet]; omdøb ligger på resuméets Navn-linje -- v1.58: listen har Start/Stop pr. række; Opsæt/Slet ligger i redigeringsvinduet self.flowButtons = {self.backButtonInfo, self.flowNewButtonInfo} end -- ---- traktor-tekst (også til guidens traktorliste) ---- -- "Fastrac 2135 4WS (#1) — 1.000.000 l — kan bære: Halm, TMR — OK" / "… — X ingen vogn". function SmartPickupFrame:describeVehicle(adEnv, run, vehicle, isInRun) local label = ADRunsController.getVehicleLabel(adEnv, vehicle) local validation = ADRunsController.validateVehicle(adEnv, run, vehicle) local parts = {label} if validation.isAutoload and validation.capacity == nil then table.insert(parts, self.i18n:getText("spu_capacity_pallets")) elseif validation.capacity ~= nil then table.insert(parts, string.format(self.i18n:getText("spu_capacity"), g_i18n:formatNumber(validation.capacity, 0))) local carriable = self:describeCarriable(adEnv, run, vehicle, validation.carriable) if carriable ~= nil then table.insert(parts, carriable) end end if validation.ok then table.insert(parts, "OK") else local reason = self.i18n:getText("spu_reason_" .. tostring(validation.reason)) if validation.reason == "noMarker" and validation.missingBuilding ~= nil then reason = string.format(reason, tostring(validation.missingBuilding)) end table.insert(parts, "X " .. reason) end return table.concat(parts, " — ") end -- Hvilke af run'ets varer vognen kan bære (titler); nil når ingen. function SmartPickupFrame:describeCarriable(adEnv, run, vehicle, carriable) local canCarry = {} for _, fillType in ipairs(carriable or {}) do canCarry[fillType] = true end local titles = {} for _, fillType in ipairs(ADRunsController.getRunIngredients(adEnv, run, vehicle)) do if canCarry[fillType] then local title = g_fillTypeManager.getFillTypeTitleByIndex ~= nil and g_fillTypeManager:getFillTypeTitleByIndex(fillType) or nil table.insert(titles, tostring(title or g_fillTypeManager:getFillTypeNameByIndex(fillType))) end end if #titles == 0 then return nil end return string.format(self.i18n:getText("spu_canCarry"), table.concat(titles, ", ")) end -- ---- context til ADFlowGuide ---- local function trim(text) return (string.gsub(tostring(text or ""), "^%s+", "")) end local function idSet(list) local set = {} for _, id in ipairs(list or {}) do set[id] = true end return set end function SmartPickupFrame:flowBuildingContext(adEnv, draft, farmId) local isOut = draft.kind == "outbound" local role = isOut and "load" or "unload" local chosen = idSet(isOut and draft.sourceBuildingIds or draft.targetBuildingIds) local buildings, found = {}, {} for _, building in ipairs(ADBuildings.list(adEnv, farmId)) do local fits = (isOut and building.canBeSource) or (not isOut and building.canBeTarget) if fits or chosen[building.id] then local markers = ADBuildings.getMarkers ~= nil and ADBuildings.getMarkers(building, role) or {} found[building.id] = true table.insert(buildings, {id = building.id, name = tostring(building.name), label = markers[1] ~= nil and tostring(markers[1].name) or nil, hasMarker = markers[1] ~= nil, text = trim(self:describeBuilding(building, role))}) end end -- valgte bygninger der ikke længere findes (solgt): kan fravælges for id in pairs(chosen) do if not found[id] then local gone = self.i18n:getText("spu_buildingGone") table.insert(buildings, {id = id, name = gone, hasMarker = false, text = gone}) end end return buildings end function SmartPickupFrame:flowStoreContext(adEnv, preview, farmId) local goods = ADRunsController.getStoreGoods(adEnv, preview) local chosen = idSet(preview.storeBuildingIds) local result = {} for _, building in ipairs(ADBuildings.list(adEnv, farmId)) do local isStoreKind = building.kind == "silo" or building.kind == "factory" or building.kind == "objectStorage" if isStoreKind and (chosen[building.id] or ADRunsController.canStoreAt(adEnv, building, goods)) then table.insert(result, {id = building.id, name = tostring(building.name), text = trim(self:describeBuilding(building, "unload"))}) end end return result end function SmartPickupFrame:flowVehicleContext(adEnv, preview) local result = {} for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do local ok, entry = pcall(function() local text = self:describeVehicle(adEnv, preview, vehicle, false) local validation = ADRunsController.validateVehicle(adEnv, preview, vehicle) local other = ADRunsController.getRunForVehicle(vehicle) return {id = ADRunsController.getVehicleId(vehicle), text = text, label = ADRunsController.getVehicleLabel(adEnv, vehicle), otherRunId = other ~= nil and other.id or nil, otherRunName = other ~= nil and other.name or nil, blocked = SmartPickupFrame.VEHICLE_BLOCKING_REASONS[validation.reason] == true} end) if ok and entry.id ~= nil then table.insert(result, entry) elseif not ok then Logging.warning(SmartPickupFrame.LOG_PREFIX .. "traktor '%s' kunne ikke vises i flowets traktorliste: %s", tostring(vehicle.getName ~= nil and vehicle:getName() or "?"), tostring(entry)) end end return result end -- draft: kladden (eller selve run'et — samme feltnavne). function SmartPickupFrame:flowGuideContext(adEnv, draft) local farmId = g_currentMission:getFarmId() local preview = ADFlowGuide.previewRun(draft, function(buildingId, role) local building = ADBuildings.find(adEnv, farmId, buildingId) local ids = {} for _, marker in ipairs(building ~= nil and ADBuildings.getMarkers ~= nil and ADBuildings.getMarkers(building, role) or {}) do table.insert(ids, marker.id) end return ids end) local context = {buildings = self:flowBuildingContext(adEnv, draft, farmId), fillTypes = {}, products = {}, storeBuildings = {}, waitPools = ADRunsController.getWaitPools(adEnv), vehicles = self:flowVehicleContext(adEnv, preview)} if draft.kind == "outbound" then for _, product in ipairs(ADRunsController.getRunProducts(adEnv, preview)) do local title = tostring(product.title) table.insert(context.products, {name = product.name, title = title, text = string.format("%s — %s l", title, SmartPickupFrame.formatCompact(product.available or 0))}) end context.storeBuildings = self:flowStoreContext(adEnv, preview, farmId) else for _, fillType in ipairs(ADRunsController.getTargetAcceptedFillTypes(adEnv, preview)) do table.insert(context.fillTypes, {name = fillType.name, title = tostring(fillType.title)}) end end return context end -- ---- fanen: tavle + resumé ---- -- Flows: det valgte flow øverst (indstillinger som "ret"-linjer + status), derunder en kort liste til at vælge -- et andet flow. Tavlen med alle flows står i Oversigt (in-game 27/9: de to faner så ens ud). function SmartPickupFrame:fillFlowTabRows(adEnv) if not self.editPanelOpen and self:hasGuideOn(SmartPickupFrame.TAB_FLOWS) and self:fillGuideRows(adEnv) then return end self.flowListRows = {} local i18n = self.i18n if #ADRunsController.store.runs == 0 then self:addTextRow(i18n:getText("spu_noRuns")) return end self:addTextRow(i18n:getText("spu_flowList_hint")) 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 row = self:addRow(self.flowListPrefab, {kind = "flowList"}) local entry = {runId = run.id, row = row, dot = row:getDescendantByName("dot"), cells = {row:getDescendantByName("c1"), row:getDescendantByName("c2"), row:getDescendantByName("c3"), row:getDescendantByName("c4"), row:getDescendantByName("c5")}} local runId = run.id local actionCell = entry.cells[5] self:makeRowClickable(row, function(frame) -- klik på [Start]/[Stop]-feltet = handling (kun dette flow); ellers åbn redigeringsvinduet if frame.lastClickX ~= nil and actionCell.absPosition ~= nil and frame.lastClickX >= actionCell.absPosition[1] then frame:toggleRun(frame.getAdEnv(), runId) frame:requestRebuild() else frame.selectedRunId = runId frame:openEditPanel("flow", runId) end end) table.insert(self.flowListRows, entry) end end self:refreshFlowListRows(adEnv) end -- Listens celler, prik og statusfarve (hvert sekund og efter opbygning). function SmartPickupFrame:refreshFlowListRows(adEnv) local unpackFn = table.unpack or unpack for _, entry in ipairs(self.flowListRows or {}) do local run = ADRuns.getRun(ADRunsController.store, entry.runId) if run ~= nil then local cells = self:flowCells(adEnv, run) local color = ADFlowView.LEVEL_COLORS[self:flowLevel(adEnv, run)] local canStop = ADRunsController.getRunStatus(adEnv, run).canStop entry.cells[1]:setText(tostring(run.name)) entry.cells[2]:setText(tostring(cells[2])) entry.cells[3]:setText(tostring(cells[5])) entry.cells[4]:setText(tostring(cells[6])) entry.cells[5]:setText(self.i18n:getText(canStop and "spu_flowList_stop" or "spu_flowList_start")) if entry.dot ~= nil and entry.dot.setImageColor ~= nil then entry.dot:setImageColor(nil, unpackFn(color)) end if entry.cells[4].setTextColor ~= nil then entry.cells[4]:setTextColor(unpackFn(color)) end end end end -- Start/Stop for ét flow (rækkens felt og vinduets knap). Husker om sidste start blev helt afvist (rød). function SmartPickupFrame:toggleRun(adEnv, runId) local run = ADRuns.getRun(ADRunsController.store, runId) if adEnv == nil or run == nil then return end self.lastStartFailed = self.lastStartFailed or {} if ADRunsController.getRunStatus(adEnv, run).canStop then ADRunsController.stopRun(adEnv, run.id) self.lastStartFailed[run.id] = nil self.statusOverride = nil else local started, skipped, reasons = self:summarizeStartResults(ADRunsController.startRun(adEnv, run.id)) self.lastStartFailed[run.id] = started == 0 and skipped > 0 local text = string.format(self.i18n:getText("spu_status_started"), started, skipped) self:showOverride(text, reasons) -- statuslinjen kan ligge uden for skærmen: sprunget over vises også som besked i højre side if skipped > 0 and ADFieldJobs ~= nil and ADFieldJobs.notify ~= nil then ADFieldJobs.notify(string.format("%s: %s — %s", tostring(run.name), text, table.concat(reasons, "; ")), started == 0) end end ADRunsController.save() end function SmartPickupFrame:flowLevel(adEnv, run) local row = ADRunsController.getFlowRow(adEnv, run) local status = ADRunsController.getRunStatus(adEnv, run) -- en gammel afvisning gælder ikke længere når flowet kører (fx startet fra Oversigt bagefter) if row.running > 0 and self.lastStartFailed ~= nil then self.lastStartFailed[run.id] = nil end return ADFlowView.flowStatus({running = row.running, total = row.total, stopped = run.stopped == true, canStop = status.canStop == true, warning = row.warning, reason = row.reason, lastStartFailed = (self.lastStartFailed or {})[run.id] == true}) end function SmartPickupFrame:fillFlowSummary(adEnv, run) local i18n = self.i18n if not self.editPanelOpen then self:addTextRow(string.format(i18n:getText("spu_guide_flow_section"), tostring(run.name))) end self:addTextRow(" " .. i18n:getText("spu_guide_sum_hint")) local guide = ADFlowGuide.open(run, self:flowGuideContext(adEnv, run)) local getText = function(key) return i18n:getText(key) end local runId = run.id for _, line in ipairs(ADFlowGuide.summary(guide)) do local step = line.step self:addLinkRow(" " .. ADGuide.lineText(line, getText) .. i18n:getText("spu_guide_editSuffix"), function(frame) if step == "name" then frame:onClickRename() else frame:openFlowStep(runId, step) end end) end local cells = self:flowCells(adEnv, run) self:addTextRow(" " .. string.format(i18n:getText("spu_field_statusFull"), tostring(cells[6]))) self:addLinkRow(" " .. i18n:getText("spu_guide_flow_open"), function(frame) frame.selectedRunId = runId frame:onClickFlowSetup() end) end -- Redigeringsvinduet for et flow (gui/SmartPickupEditPanel.lua). SmartPickupFrame.EDIT_PANEL_KINDS = SmartPickupFrame.EDIT_PANEL_KINDS or {} SmartPickupFrame.EDIT_PANEL_KINDS.flow = { canDelete = true, exists = function(frame, id) return ADRuns.getRun(ADRunsController.store, id) ~= nil end, title = function(frame, id) local run = ADRuns.getRun(ADRunsController.store, id) return string.format(frame.i18n:getText("spu_panel_flowTitle"), tostring(run ~= nil and run.name or "?")) end, fill = function(frame, adEnv, id) local run = ADRuns.getRun(ADRunsController.store, id) if run ~= nil then frame:fillFlowSummary(adEnv, run) end end, status = function(frame, adEnv, id) local run = ADRuns.getRun(ADRunsController.store, id) return {text = tostring(frame:flowCells(adEnv, run)[6]), level = frame:flowLevel(adEnv, run)} end, startStopText = function(frame, adEnv, id) local run = ADRuns.getRun(ADRunsController.store, id) return frame.i18n:getText(ADRunsController.getRunStatus(adEnv, run).canStop and "spu_button_stop" or "spu_button_start") end, startStop = function(frame, id) frame:toggleRun(frame.getAdEnv(), id) end, delete = function(frame, id) frame.selectedRunId = id frame:onClickDelete() end, } -- Hvert sekund: tavlens celler, statuslinje og knapper. function SmartPickupFrame:refreshFlowTab(adEnv) if not self.editPanelOpen and self:hasGuideOn(SmartPickupFrame.TAB_FLOWS) then self:setStatusText(self.statusOverride or "") return end self:refreshFlowListRows(adEnv) local running, total = 0, 0 for _, run in ipairs(ADRunsController.store.runs) do total = total + 1 if ADRunsController.getRunStatus(adEnv, run).running > 0 then running = running + 1 end end self:setStatusText(self.statusOverride or string.format(self.i18n:getText("spu_flowList_status"), running, total)) end -- ---- hooks til den fælles guide-skærm ---- SmartPickupFrame.GUIDE_HOOKS = SmartPickupFrame.GUIDE_HOOKS or {} SmartPickupFrame.GUIDE_HOOKS.flow = { model = ADFlowGuide, tab = SmartPickupFrame.TAB_FLOWS, heading = function(frame, guide) return string.format(frame.i18n:getText("spu_guide_flow_title"), tostring(guide.draft.name)) end, context = function(frame, adEnv, guide) -- flowet er slettet mens guiden var åben if not guide.draft.isNew and ADRuns.getRun(ADRunsController.store, guide.draft.id) == nil then return nil end return frame:flowGuideContext(adEnv, guide.draft) end, choose = function(frame, id) if id ~= "rename" then return false end TextInputDialog.show(SmartPickupFrame.onGuideNameResult, frame, frame.guide.draft.name, frame.i18n:getText("spu_renameTitle"), nil, 30, g_i18n:getText("button_ok")) return true end, startKey = function() return "spu_guide_flow_start" end, save = function(frame, guide, start) frame:saveFlowGuide(guide, start) end, } function SmartPickupFrame:onGuideNameResult(value, clickOk) self:safe(function(frame) if not clickOk or frame.guide == nil or frame.guideKind ~= "flow" then return end frame.guide = ADFlowGuide.choose(frame.guide, "name", value) frame:requestRebuild() end) end -- Gem: nyt flow oprettes først nu; felterne via updateRun; traktorer tildeles/fjernes; markører findes. -- start = true: flowet startes bagefter ([Start flow]). function SmartPickupFrame:saveFlowGuide(guide, start) local adEnv = self.getAdEnv() local result = ADFlowGuide.result(guide) local runId = result.runId local oldIds = {} if result.isNew then runId = ADRunsController.createRun(result.name, result.kind).id else local existing = ADRuns.getRun(ADRunsController.store, runId) if existing == nil then return end oldIds = existing.vehicleIds end ADRunsController.updateRun(runId, result.changes) local added, removed = ADFlowGuide.vehicleChanges(oldIds, result.vehicleIds) for _, id in ipairs(removed) do local vehicle = adEnv ~= nil and ADRunsController.findVehicleById(adEnv, id) or nil if vehicle ~= nil then ADRunsController.unassignVehicle(vehicle) else ADRunsController.store = ADRuns.unassignVehicle(ADRunsController.store, id) end end for _, id in ipairs(added) do local vehicle = adEnv ~= nil and ADRunsController.findVehicleById(adEnv, id) or nil if vehicle ~= nil then ADRunsController.assignVehicle(runId, vehicle) end end ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, runId)) self.selectedRunId = runId local saved = string.format(self.i18n:getText("spu_guide_flow_saved"), tostring(result.name)) if start and adEnv ~= nil and not ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, runId)).isRunning then local started, skipped, reasons = self:summarizeStartResults(ADRunsController.startRun(adEnv, runId)) self:showOverride(saved .. " — " .. string.format(self.i18n:getText("spu_status_started"), started, skipped), reasons) else self:showOverride(saved) end ADRunsController.save() end -- ---- knapper og [Ret] ---- function SmartPickupFrame:onClickFlowSetup() self:safe(function(frame) local adEnv, run = frame.getAdEnv(), frame:getRun() if adEnv == nil or run == nil then return end frame:openGuide("flow", ADFlowGuide.open(run, frame:flowGuideContext(adEnv, run))) end) end -- [Nyt flow] (også fra fanen Oversigt): guiden på Flows-fanen; intet oprettes før [Gem]. function SmartPickupFrame:onClickFlowNew() self:safe(function(frame) local adEnv = frame.getAdEnv() if adEnv == nil then return end local name = string.format(frame.i18n:getText("spu_newRunName"), #ADRunsController.store.runs + 1) local guide = ADFlowGuide.openNew(name, {}) guide = ADGuide.withContext(guide, frame:flowGuideContext(adEnv, guide.draft)) if frame.currentTab ~= SmartPickupFrame.TAB_FLOWS then frame:switchTab(SmartPickupFrame.TAB_FLOWS) end frame:openGuide("flow", guide) end) end function SmartPickupFrame:openFlowStep(runId, step) local adEnv = self.getAdEnv() local run = ADRuns.getRun(ADRunsController.store, runId) if adEnv == nil or run == nil then return end self:openGuide("flow", ADFlowGuide.open(run, self:flowGuideContext(adEnv, run), {step = step, single = true, returnTo = "close"})) end