From 337db161780241c2b65b8019521202cb48c3a060 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Wed, 23 Sep 2026 10:02:24 +0200 Subject: [PATCH] =?UTF-8?q?feat(gui):=20faner=20Oversigt/Forsyning/Udk?= =?UTF-8?q?=C3=B8rsel;=20udk=C3=B8rsels-run=20med=20kilder,=20varepolitik,?= =?UTF-8?q?=20regler=20og=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/gui/SmartPickupFrame.lua | 231 ++++++++---------- FS25_ADSmartPickup/gui/SmartPickupFrame.xml | 26 ++ .../gui/SmartPickupOutboundTab.lua | 194 +++++++++++++++ .../gui/SmartPickupSupplyTab.lua | 109 +++++++++ FS25_ADSmartPickup/l10n/l10n_da.xml | 37 ++- FS25_ADSmartPickup/l10n/l10n_en.xml | 37 ++- 6 files changed, 508 insertions(+), 126 deletions(-) create mode 100644 FS25_ADSmartPickup/gui/SmartPickupOutboundTab.lua create mode 100644 FS25_ADSmartPickup/gui/SmartPickupSupplyTab.lua diff --git a/FS25_ADSmartPickup/gui/SmartPickupFrame.lua b/FS25_ADSmartPickup/gui/SmartPickupFrame.lua index 5efa4ca..63c9f56 100644 --- a/FS25_ADSmartPickup/gui/SmartPickupFrame.lua +++ b/FS25_ADSmartPickup/gui/SmartPickupFrame.lua @@ -9,8 +9,11 @@ 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"} +SmartPickupFrame.TAB_OVERVIEW, SmartPickupFrame.TAB_SUPPLY, SmartPickupFrame.TAB_OUTBOUND = 1, 2, 3 +SmartPickupFrame.TAB_NAMES = {"spu_tab_overview", "spu_tab_supply", "spu_tab_outbound"} +-- Hver fane viser kun runs af sin egen slags; Oversigt opretter forsynings-runs. +SmartPickupFrame.TAB_KIND = {"supply", "supply", "outbound"} +SmartPickupFrame.POLICIES = {"off", "store", "sell", "sellElseStore"} function SmartPickupFrame.new(i18n, getAdEnv) local self = SmartPickupFrame:superClass().new(nil, SmartPickupFrame._mt) @@ -52,7 +55,7 @@ end -- Prefabs skal ud af layoutet før de klones, ellers vises de selv som rækker. function SmartPickupFrame:initialize() - for _, name in ipairs({"ingredientPrefab", "vehiclePrefab", "textPrefab"}) do + for _, name in ipairs({"ingredientPrefab", "vehiclePrefab", "textPrefab", "productPrefab"}) do local prefab = self[name] if prefab ~= nil then prefab:unlinkElement() @@ -91,6 +94,34 @@ function SmartPickupFrame:initialize() table.insert(percentTexts, string.format("%d %%", percent)) end self.returnBelowOption:setTexts(percentTexts) + self.sellAtSteps, self.storeAboveSteps = {}, {} + local sellTexts, storeTexts = {}, {} + for percent = ADRuns.MIN_SELL_AT_PERCENT, ADRuns.MAX_SELL_AT_PERCENT, 5 do + table.insert(self.sellAtSteps, percent) + table.insert(sellTexts, string.format("%d %%", percent)) + end + for percent = ADRuns.MIN_STORE_ABOVE_PERCENT, ADRuns.MAX_STORE_ABOVE_PERCENT, 5 do + table.insert(self.storeAboveSteps, percent) + table.insert(storeTexts, string.format("%d %%", percent)) + end + if self.sellAtOption ~= nil then self.sellAtOption:setTexts(sellTexts) end + if self.storeAboveOption ~= nil then self.storeAboveOption:setTexts(storeTexts) end + if self.collectRoundOption ~= nil then self.collectRoundOption:setTexts({self.i18n:getText("ui_no"), self.i18n:getText("ui_yes")}) end +end + +-- Indeks i en trin-liste for en procent (største trin <= værdien). +function SmartPickupFrame.getStepIndex(steps, value, default) + local index = 1 + for position, percent in ipairs(steps or {}) do + if percent <= (value or default) then + index = position + end + end + return index +end + +function SmartPickupFrame:getTabKind() + return SmartPickupFrame.TAB_KIND[self.currentTab] or "supply" end function SmartPickupFrame:getReturnBelowIndex(run) @@ -214,15 +245,35 @@ 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 +-- De faste rækker hører til hver sin fane: Run + Wait-pulje er fælles, Varer/Ture/tærskel er +-- Forsyning, Sælg fra/Kør til lager/Saml op er Udkørsel. Oversigt viser ingen. +function SmartPickupFrame:setStaticRowsVisible(tab) + local isSupply, isOutbound = tab == SmartPickupFrame.TAB_SUPPLY, tab == SmartPickupFrame.TAB_OUTBOUND + local visibility = { + {self.runOption, isSupply or isOutbound}, + {self.waitPoolOption, isSupply or isOutbound}, + {self.ingredientModeOption, isSupply}, {self.loopsOption, isSupply}, {self.returnBelowOption, isSupply}, + {self.sellAtOption, isOutbound}, {self.storeAboveOption, isOutbound}, {self.collectRoundOption, isOutbound}, + } + for _, entry in ipairs(visibility) do + local option = entry[1] if option ~= nil and option.parent ~= nil then - option.parent:setVisible(isVisible) + option.parent:setVisible(entry[2]) end end end +-- Run'et der vises: kun runs af fanens slags; skifter fanen, vælges fanens første run. +function SmartPickupFrame:getRunsForTab() + local kind, result = self:getTabKind(), {} + for _, run in ipairs(ADRunsController.store.runs) do + if (run.kind or "supply") == kind then + table.insert(result, run) + end + end + return result +end + function SmartPickupFrame:getRun() return self.selectedRunId ~= nil and ADRuns.getRun(ADRunsController.store, self.selectedRunId) or nil end @@ -244,6 +295,7 @@ function SmartPickupFrame:clearRows() self.statusRow = nil self.needsRows = {} self.overviewRows = {} + self.productRows, self.outboundStatusRows = {}, {} end function SmartPickupFrame:addRow(prefab, data) @@ -285,12 +337,14 @@ end function SmartPickupFrame:rebuild() local adEnv = self.getAdEnv() local store = ADRunsController.store - if self.selectedRunId == nil or ADRuns.getRun(store, self.selectedRunId) == nil then - self.selectedRunId = store.runs[1] ~= nil and store.runs[1].id or nil + local tabRuns = self:getRunsForTab() + local selected = self.selectedRunId ~= nil and ADRuns.getRun(store, self.selectedRunId) or nil + if selected == nil or (selected.kind or "supply") ~= self:getTabKind() then + self.selectedRunId = tabRuns[1] ~= nil and tabRuns[1].id or nil end self:clearRows() local isOverview = self.currentTab == SmartPickupFrame.TAB_OVERVIEW - self:setStaticRowsVisible(not isOverview) + self:setStaticRowsVisible(self.currentTab) if isOverview then if adEnv ~= nil then self:fillOverviewRows(adEnv) @@ -307,20 +361,21 @@ function SmartPickupFrame:rebuild() self:fillRunOption(adEnv) local run = self:getRun() local hasRun = run ~= nil and adEnv ~= nil - for _, element in ipairs({self.ingredientModeOption, self.waitPoolOption, self.loopsOption, self.returnBelowOption}) do - element:setDisabled(not hasRun) + for _, element in ipairs({self.ingredientModeOption, self.waitPoolOption, self.loopsOption, self.returnBelowOption, self.sellAtOption, self.storeAboveOption, self.collectRoundOption}) do + if element ~= nil then + element:setDisabled(not hasRun) + end end if hasRun then - self.ingredientModeOption:setState(run.ingredientMode == "manual" and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false) 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")) + if self.currentTab == SmartPickupFrame.TAB_OUTBOUND then + self:rebuildOutboundTab(adEnv, run) + else + self:rebuildSupplyTab(adEnv, run) + end + self:addTextRow(self.i18n:getText("spu_section_vehicles")) self:fillVehicleRows(adEnv, run) + self:addTextRow(self.i18n:getText("spu_section_status")) end self.statusRow = self:addTextRow("") self:colorRows() @@ -332,7 +387,7 @@ end function SmartPickupFrame:fillRunOption(adEnv) local texts, index = {}, 1 self.runIds = {} - for position, run in ipairs(ADRunsController.store.runs) do + for position, run in ipairs(self:getRunsForTab()) do local status = adEnv ~= nil and ADRunsController.getRunStatus(adEnv, run) or {running = 0, total = #run.vehicleIds} table.insert(texts, string.format("%s (%d/%d)", run.name, status.running, status.total)) table.insert(self.runIds, run.id) @@ -348,32 +403,6 @@ function SmartPickupFrame:fillRunOption(adEnv) self.runOption:setDisabled(#self.runIds == 0) end --- 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 - 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 - -- Overblik: "Cow 1 food: TMR 18.0k/27.8k · Silage 3.0k/…" (mest trængende først) function SmartPickupFrame:describeTargetLevels(target) if #target.levels == 0 then @@ -382,40 +411,6 @@ function SmartPickupFrame:describeTargetLevels(target) return string.format("%s: %s", target.name, self:formatLevels(target.levels)) 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 -end - -function SmartPickupFrame:fillIngredientRows(adEnv, run) - if run.ingredientMode ~= "manual" then - return - end - local chosen = {} - for _, name in ipairs(run.ingredients) do - chosen[name] = true - end - for _, fillType in ipairs(ADRunsController.getTargetAcceptedFillTypes(adEnv, run)) do - local row = self:addRow(self.ingredientPrefab, {kind = "ingredient", name = fillType.name}) - row:getDescendantByName("title"):setText(tostring(fillType.title)) - row:getDescendantByName("option"):setState(chosen[fillType.name] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false) - end -end - function SmartPickupFrame:fillWaitPoolOption(adEnv, run) local texts, index = {self.i18n:getText("spu_waitPool_all")}, 1 self.poolGroups = {""} @@ -502,12 +497,18 @@ function SmartPickupFrame:syncOptionsFromModel(adEnv, run) option:setState(wantedIndex, false) end end - 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 if group == run.waitPoolGroup then poolIndex = index end end sync(self.waitPoolOption, poolIndex) + if run.kind == "outbound" then + sync(self.sellAtOption, SmartPickupFrame.getStepIndex(self.sellAtSteps, run.sellAtPercent, ADRuns.DEFAULT_SELL_AT_PERCENT)) + sync(self.storeAboveOption, SmartPickupFrame.getStepIndex(self.storeAboveSteps, run.storeAbovePercent, ADRuns.DEFAULT_STORE_ABOVE_PERCENT)) + sync(self.collectRoundOption, run.collectRound and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF) + return + end + sync(self.ingredientModeOption, run.ingredientMode == "manual" and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF) sync(self.loopsOption, run.loops + 1) sync(self.returnBelowOption, self:getReturnBelowIndex(run)) end @@ -545,6 +546,8 @@ function SmartPickupFrame:describeActivity(adEnv, vehicle) return string.format("%s: %s", label, string.format(self.i18n:getText("spu_activity_waiting"), place, g_i18n:formatNumber(activity.liters or 0, 0), fillTypeName)) elseif activity.kind == "loaded" then return string.format("%s: %s", label, string.format(self.i18n:getText("spu_activity_loaded"), g_i18n:formatNumber(activity.liters or 0, 0), fillTypeName, place)) + elseif activity.kind == "holding" then + return string.format("%s: %s", label, string.format(self.i18n:getText("spu_activity_holding"), place, tostring(activity.reason or ""))) end if activity.fillType ~= nil then return string.format("%s: %s", label, string.format(self.i18n:getText("spu_activity_empty"), place, fillTypeName)) @@ -573,8 +576,12 @@ function SmartPickupFrame:fillOverviewRows(adEnv) table.insert(entry.vehicles, {vehicle = vehicle, title = add(" " .. self:describeActivity(adEnv, vehicle))}) end end - for _, target in ipairs(ADRunsController.getTargetsLevels(adEnv, run)) do - table.insert(entry.targets, {wayPointId = target.wayPointId, title = add(" " .. self:describeTargetLevels(target))}) + if run.kind == "outbound" then + entry.outbound = add(" " .. self:describeOutboundOverview(adEnv, run)) + else + for _, target in ipairs(ADRunsController.getTargetsLevels(adEnv, run)) do + table.insert(entry.targets, {wayPointId = target.wayPointId, title = add(" " .. self:describeTargetLevels(target))}) + end end table.insert(self.overviewRows, entry) end @@ -601,6 +608,9 @@ function SmartPickupFrame:refreshOverview(adEnv) for _, item in ipairs(entry.vehicles) do item.title:setText(" " .. self:describeActivity(adEnv, item.vehicle)) end + if entry.outbound ~= nil then + entry.outbound:setText(" " .. self:describeOutboundOverview(adEnv, entry.run)) + end local byId = {} for _, target in ipairs(ADRunsController.getTargetsLevels(adEnv, entry.run)) do byId[target.wayPointId] = target @@ -658,7 +668,11 @@ function SmartPickupFrame:refreshLive() local isRunning = false if run ~= nil then self:syncOptionsFromModel(adEnv, run) - self:refreshNeedsRows(adEnv, run) + if run.kind == "outbound" then + self:refreshOutboundRows(adEnv, run) + else + self:refreshNeedsRows(adEnv, run) + end 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) @@ -692,30 +706,13 @@ function SmartPickupFrame:onClickRun(state) self:requestRebuild() end -function SmartPickupFrame:onClickTarget(state, element) - self:safe(function(frame) - 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 - -function SmartPickupFrame:onClickIngredientMode(state) - self:safe(function(frame) - local run = frame:getRun() - if run == nil then return end - ADRunsController.updateRun(run.id, {ingredientMode = state == SmartPickupFrame.STATE_ON and "manual" or "auto"}) - frame:requestRebuild() - end) -end - function SmartPickupFrame:onClickIngredient(state, element) - -- mål-rækkerne deler prefab (og dermed onClick) med vare-rækkerne + -- mål- og kilde-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) + elseif data ~= nil and data.kind == "source" then + return self:onClickSource(state, element) end self:safe(function(frame) local run, data = frame:getRun(), frame.rowData[element] @@ -739,22 +736,6 @@ function SmartPickupFrame:onClickWaitPool(state) end) end -function SmartPickupFrame:onClickLoops(state) - self:safe(function(frame) - local run = frame:getRun() - if run == nil then return end - ADRunsController.updateRun(run.id, {loops = state - 1}) - end) -end - -function SmartPickupFrame:onClickReturnBelow(state) - self:safe(function(frame) - local run = frame:getRun() - if run == nil then return end - ADRunsController.updateRun(run.id, {returnBelowPercent = frame.returnBelowSteps[state] or ADRuns.DEFAULT_RETURN_BELOW_PERCENT}) - end) -end - function SmartPickupFrame:onClickVehicle(state, element) self:safe(function(frame) local run, data = frame:getRun(), frame.rowData[element] @@ -807,11 +788,13 @@ function SmartPickupFrame:onNewRunName(value, clickOk) if not clickOk or value == nil or not value:match("%S") then return end - local run = ADRunsController.createRun(value) + local kind = frame:getTabKind() + local run = ADRunsController.createRun(value, kind) frame.selectedRunId = run.id frame.statusOverride = nil - frame.currentTab = SmartPickupFrame.TAB_RUNS - frame:switchTab(SmartPickupFrame.TAB_RUNS) + local tab = kind == "outbound" and SmartPickupFrame.TAB_OUTBOUND or SmartPickupFrame.TAB_SUPPLY + frame.currentTab = tab + frame:switchTab(tab) end) end diff --git a/FS25_ADSmartPickup/gui/SmartPickupFrame.xml b/FS25_ADSmartPickup/gui/SmartPickupFrame.xml index a10ce2a..56c1a7c 100644 --- a/FS25_ADSmartPickup/gui/SmartPickupFrame.xml +++ b/FS25_ADSmartPickup/gui/SmartPickupFrame.xml @@ -49,6 +49,25 @@ + + + + + + + + + + + + + + + + + + + @@ -65,6 +84,13 @@ + + + + + + + diff --git a/FS25_ADSmartPickup/gui/SmartPickupOutboundTab.lua b/FS25_ADSmartPickup/gui/SmartPickupOutboundTab.lua new file mode 100644 index 0000000..f40725d --- /dev/null +++ b/FS25_ADSmartPickup/gui/SmartPickupOutboundTab.lua @@ -0,0 +1,194 @@ +-- Smart Pickup — fanen Udkørsel +-- Kilder (afkrydsning), varer med politik (Fra/Lager/Salg/Salg, ellers lager) og aktuel pris i +-- % af årets top, regler (pristærskel, lagertærskel, opsamlingsrunde) og status/advarsler. +-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua). + +-- Bedste pris-% for varen over alle salgssteder (uanset vejnet — det er til visning; turen +-- bruger kun nåelige). {percent, stationName} eller nil. +function SmartPickupFrame:getProductQuote(adEnv, fillType) + if ADSources == nil or ADPrices == nil then + return nil + end + local best = nil + for _, entry in ipairs(ADSources.getSellMarkers(adEnv, g_currentMission:getFarmId())) do + if ADSources.stationAccepts(entry.station, fillType) then + local quote = ADPrices.getQuote(entry.station, fillType) + if quote ~= nil and (best == nil or quote.percent > best.percent) then + best = {percent = quote.percent, stationName = tostring(entry.marker.name)} + end + end + end + return best +end + +-- "Sukker — 150.0k l — 78 % af top (Grain Elevator)" +function SmartPickupFrame:describeProduct(adEnv, product) + local price = self.i18n:getText("spu_price_none") + local quote = self:getProductQuote(adEnv, product.fillType) + if quote ~= nil then + price = string.format(self.i18n:getText("spu_price_pct"), quote.percent, quote.stationName) + end + return string.format(self.i18n:getText("spu_product_row"), tostring(product.title), SmartPickupFrame.formatCompact(product.available or 0), price) +end + +function SmartPickupFrame:getPolicyIndex(policy) + for index, name in ipairs(SmartPickupFrame.POLICIES) do + if name == policy then + return index + end + end + return 1 +end + +function SmartPickupFrame:fillSourceRows(adEnv, run) + local chosen = {} + for _, id in ipairs(run.sourceWayPointIds or {}) do + chosen[id] = true + end + local entries = ADRunsController.getSourceMarkers(adEnv, g_currentMission:getFarmId()) + for _, entry in ipairs(entries) do + local row = self:addRow(self.ingredientPrefab, {kind = "source", wayPointId = entry.marker.id}) + local kindText = self.i18n:getText("spu_source_kind_" .. tostring(entry.kind)) + row:getDescendantByName("title"):setText(string.format(" %s — %s (%s)", tostring(entry.marker.name), tostring(entry.name), kindText)) + row:getDescendantByName("option"):setState(chosen[entry.marker.id] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false) + end + if #entries == 0 then + self:addTextRow(self.i18n:getText("spu_noSources")) + end +end + +function SmartPickupFrame:fillProductRows(adEnv, run) + self.productRows = {} + local policyTexts = {} + for _, policy in ipairs(SmartPickupFrame.POLICIES) do + table.insert(policyTexts, self.i18n:getText("spu_policy_" .. policy)) + end + local products = ADRunsController.getRunProducts(adEnv, run) + for _, product in ipairs(products) do + local row = self:addRow(self.productPrefab, {kind = "product", name = product.name}) + local option = row:getDescendantByName("option") + option:setTexts(policyTexts) + option:setState(self:getPolicyIndex(product.policy), false) + local title = row:getDescendantByName("title") + title:setText(self:describeProduct(adEnv, product)) + table.insert(self.productRows, {name = product.name, title = title, option = option}) + end + if #products == 0 then + self:addTextRow(#(run.sourceWayPointIds or {}) == 0 and self.i18n:getText("spu_noSources") or self.i18n:getText("spu_noProducts")) + end +end + +function SmartPickupFrame:fillOutboundStatusRows(adEnv, run) + self.outboundStatusRows = {} + local status = ADRunsController.getOutboundStatus(adEnv, run) + for _, reason in ipairs(status.reasons or {}) do + table.insert(self.outboundStatusRows, self:addTextRow(string.format(self.i18n:getText("spu_outbound_waiting"), reason))) + end + if status.warning ~= nil then + table.insert(self.outboundStatusRows, self:addTextRow(string.format(self.i18n:getText("spu_outbound_warning"), status.warning))) + end +end + +function SmartPickupFrame:rebuildOutboundTab(adEnv, run) + 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.collectRoundOption:setState(run.collectRound and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false) + self:addTextRow(self.i18n:getText("spu_section_sources")) + self:fillSourceRows(adEnv, run) + self:addTextRow(self.i18n:getText("spu_section_products")) + self:fillProductRows(adEnv, run) + self:fillOutboundStatusRows(adEnv, run) +end + +-- Hvert sekund: produktrækkernes mængde/pris og statuslinjerne (antal linjer ændres kun ved rebuild). +function SmartPickupFrame:refreshOutboundRows(adEnv, run) + local byName = {} + for _, product in ipairs(ADRunsController.getRunProducts(adEnv, run)) do + byName[product.name] = product + end + for _, row in ipairs(self.productRows or {}) do + if byName[row.name] ~= nil then + row.title:setText(self:describeProduct(adEnv, byName[row.name])) + end + end + local status = ADRunsController.getOutboundStatus(adEnv, run) + local lines = {} + for _, reason in ipairs(status.reasons or {}) do + table.insert(lines, string.format(self.i18n:getText("spu_outbound_waiting"), reason)) + end + if status.warning ~= nil then + table.insert(lines, string.format(self.i18n:getText("spu_outbound_warning"), status.warning)) + end + for index, row in ipairs(self.outboundStatusRows or {}) do + row:setText(lines[index] or "") + end +end + +-- Oversigt: "SUGAR 78 % af top (kræver 90), lager 41 % · ⚠ …" eller "Sukker 150.0k l — 92 % af top (…)". +function SmartPickupFrame:describeOutboundOverview(adEnv, run) + local status = ADRunsController.getOutboundStatus(adEnv, run) + local parts = {} + for _, reason in ipairs(status.reasons or {}) do + table.insert(parts, reason) + end + if #parts == 0 then + for _, product in ipairs(status.products or {}) do + if product.policy ~= "off" then + table.insert(parts, self:describeProduct(adEnv, product)) + end + end + end + if status.warning ~= nil then + table.insert(parts, string.format(self.i18n:getText("spu_outbound_warning"), status.warning)) + end + if #parts == 0 then + return self.i18n:getText("spu_noSources") + end + return table.concat(parts, " · ") +end + +-- ---- callbacks ---- + +function SmartPickupFrame:onClickSource(state, element) + self:safe(function(frame) + local run, data = frame:getRun(), frame.rowData[element] + if run == nil or data == nil then return end + ADRunsController.store = ADRuns.toggleSource(ADRunsController.store, run.id, data.wayPointId) + Logging.info(SmartPickupFrame.LOG_PREFIX .. "run '%s': sources=%s", tostring(run.name), table.concat(ADRuns.getRun(ADRunsController.store, run.id).sourceWayPointIds, " ")) + frame:requestRebuild() + end) +end + +function SmartPickupFrame:onClickProduct(state, element) + self:safe(function(frame) + local run, data = frame:getRun(), frame.rowData[element] + if run == nil or data == nil or data.kind ~= "product" then return end + local policy = SmartPickupFrame.POLICIES[state] or "off" + ADRunsController.store = ADRuns.setProductPolicy(ADRunsController.store, run.id, data.name, policy) + Logging.info(SmartPickupFrame.LOG_PREFIX .. "run '%s': %s=%s", tostring(run.name), tostring(data.name), policy) + end) +end + +function SmartPickupFrame:onClickSellAt(state) + self:safe(function(frame) + local run = frame:getRun() + if run == nil then return end + ADRunsController.updateRun(run.id, {sellAtPercent = frame.sellAtSteps[state] or ADRuns.DEFAULT_SELL_AT_PERCENT}) + end) +end + +function SmartPickupFrame:onClickStoreAbove(state) + self:safe(function(frame) + local run = frame:getRun() + if run == nil then return end + ADRunsController.updateRun(run.id, {storeAbovePercent = frame.storeAboveSteps[state] or ADRuns.DEFAULT_STORE_ABOVE_PERCENT}) + end) +end + +function SmartPickupFrame:onClickCollectRound(state) + self:safe(function(frame) + local run = frame:getRun() + if run == nil then return end + ADRunsController.updateRun(run.id, {collectRound = state == SmartPickupFrame.STATE_ON}) + end) +end diff --git a/FS25_ADSmartPickup/gui/SmartPickupSupplyTab.lua b/FS25_ADSmartPickup/gui/SmartPickupSupplyTab.lua new file mode 100644 index 0000000..3521749 --- /dev/null +++ b/FS25_ADSmartPickup/gui/SmartPickupSupplyTab.lua @@ -0,0 +1,109 @@ +-- Smart Pickup — fanen Forsyning +-- Mål, varer (Auto/Vælg), behovsrække, ture og "kør hjem"-tærskel for et forsynings-run. +-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua). + +function SmartPickupFrame:rebuildSupplyTab(adEnv, run) + self.ingredientModeOption:setState(run.ingredientMode == "manual" and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false) + self.loopsOption:setState(run.loops + 1, false) + self.returnBelowOption:setState(self:getReturnBelowIndex(run), false) + self:addTextRow(self.i18n:getText("spu_section_targets")) + self:addTextRow(self.i18n:getText("spu_targets")) + self:fillTargetRows(adEnv, run) + self:fillIngredientRows(adEnv, run) + self:fillNeedsRows(adEnv, run) +end + +-- 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 + 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 +end + +function SmartPickupFrame:fillIngredientRows(adEnv, run) + if run.ingredientMode ~= "manual" then + return + end + local chosen = {} + for _, name in ipairs(run.ingredients) do + chosen[name] = true + end + for _, fillType in ipairs(ADRunsController.getTargetAcceptedFillTypes(adEnv, run)) do + local row = self:addRow(self.ingredientPrefab, {kind = "ingredient", name = fillType.name}) + row:getDescendantByName("title"):setText(tostring(fillType.title)) + row:getDescendantByName("option"):setState(chosen[fillType.name] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false) + end +end + +function SmartPickupFrame:onClickTarget(state, element) + self:safe(function(frame) + 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 + +function SmartPickupFrame:onClickIngredientMode(state) + self:safe(function(frame) + local run = frame:getRun() + if run == nil then return end + ADRunsController.updateRun(run.id, {ingredientMode = state == SmartPickupFrame.STATE_ON and "manual" or "auto"}) + frame:requestRebuild() + end) +end + +function SmartPickupFrame:onClickLoops(state) + self:safe(function(frame) + local run = frame:getRun() + if run == nil then return end + ADRunsController.updateRun(run.id, {loops = state - 1}) + end) +end + +function SmartPickupFrame:onClickReturnBelow(state) + self:safe(function(frame) + local run = frame:getRun() + if run == nil then return end + ADRunsController.updateRun(run.id, {returnBelowPercent = frame.returnBelowSteps[state] or ADRuns.DEFAULT_RETURN_BELOW_PERCENT}) + end) +end diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml index 0506705..b31a658 100644 --- a/FS25_ADSmartPickup/l10n/l10n_da.xml +++ b/FS25_ADSmartPickup/l10n/l10n_da.xml @@ -43,7 +43,7 @@ - + @@ -58,5 +58,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml index 9e9dfef..2164f9a 100644 --- a/FS25_ADSmartPickup/l10n/l10n_en.xml +++ b/FS25_ADSmartPickup/l10n/l10n_en.xml @@ -43,7 +43,7 @@ - + @@ -58,5 +58,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +