feat(gui): guiden "Opsæt flow" — Flows-fanen får tavle + resumé (klik = ret ét trin, Navn = omdøb) og trin-for-trin-guide på samme ramme som markguiden; Nyt flow oprettes først ved Gem (v1.32.0.0)

This commit is contained in:
2026-09-26 20:28:12 +02:00
parent 92af260a79
commit 9b51a68731
15 changed files with 1203 additions and 275 deletions
+447
View File
@@ -0,0 +1,447 @@
-- AD Flow Guide
-- "Opsæt flow": flowguidens trin oven på ADGuide (ren Lua, ingen spil-API) — samme trin-skærm og knapper
-- som markguiden. Kladden er en kopi af flowets felter, så intet ændres før [Gem]/[Start flow]. Det
-- spil-afhængige kommer i `context` (GUI'et henter det ud fra kladden, se previewRun):
-- {buildings = {{id, name, label, hasMarker, text}} -- kandidater til Fra (udkørsel) / Til (forsyning)
-- fillTypes = {{name, title}} -- varer målet tager (forsyning, Vælg selv)
-- products = {{name, title}} -- varer kilderne har (udkørsel)
-- storeBuildings = {{id, name, text}} -- lagre der kan tage flowets lagervarer
-- waitPools = {{group, count}}
-- vehicles = {{id, label, text, otherRunId, otherRunName, blocked}}} -- label = kort navn (resumé)
-- Gem: ADRunsController.createRun/updateRun + assignVehicle/unassignVehicle (GUI'et).
ADFlowGuide = {}
ADFlowGuide.ORDER = {"name", "direction", "buildings", "goods", "store", "rules", "wait", "tractors", "summary"}
ADFlowGuide.POLICIES = {"off", "store", "sell", "sellElseStore"}
ADFlowGuide.MAX_NAMED_TRACTORS = 3
ADFlowGuide.MAX_NAMED_GOODS = 3
local function L(key, args) return {key = key, args = args} end
local function T(text) return {text = text} end
local function isOut(guide) return guide.draft.kind == "outbound" end
local function hasStorePolicy(draft)
for _, product in ipairs(draft.products or {}) do
if product.policy == "store" or product.policy == "sellElseStore" then return true end
end
return false
end
local VISIBLE = {
name = function(guide) return guide.draft.isNew == true end,
store = function(guide) return isOut(guide) and hasStorePolicy(guide.draft) end,
}
function ADFlowGuide.isVisible(step, guide)
local rule = VISIBLE[step]
if rule ~= nil then return rule(guide) == true end
for _, id in ipairs(ADFlowGuide.ORDER) do
if id == step then return true end
end
return false
end
ADFlowGuide.DEF = {order = ADFlowGuide.ORDER, isVisible = ADFlowGuide.isVisible}
-- Overskrift pr. trin; "Fra"/"Til" følger retningen.
function ADFlowGuide.stepKey(step, guide)
if step == "buildings" then
return (guide ~= nil and isOut(guide)) and "spu_guide_flow_step_from" or "spu_guide_flow_step_to"
end
return "spu_guide_flow_step_" .. tostring(step)
end
local KIND_HINTS = {buildings = true, goods = true, rules = true}
function ADFlowGuide.hintKey(step, guide)
if KIND_HINTS[step] then
return "spu_guide_flow_hint_" .. step .. ((guide ~= nil and isOut(guide)) and "_out" or "_in")
end
return "spu_guide_flow_hint_" .. tostring(step)
end
-- ---- kladde ----
local RUN_FIELDS = {
"id", "name", "kind", "sourceBuildingIds", "targetBuildingIds", "storeBuildingIds", "products",
"ingredientMode", "ingredients", "sellAtPercent", "storeAbovePercent", "collectRound", "loops",
"returnBelowPercent", "waitPoolGroup", "vehicleIds",
}
local function draftFrom(run, isNew)
local draft = {}
for _, key in ipairs(RUN_FIELDS) do draft[key] = ADGuide.deepCopy(run[key]) end
for _, key in ipairs({"sourceBuildingIds", "targetBuildingIds", "storeBuildingIds", "products", "ingredients", "vehicleIds"}) do
draft[key] = draft[key] or {}
end
draft.kind = draft.kind == "outbound" and "outbound" or "supply"
draft.ingredientMode = draft.ingredientMode == "manual" and "manual" or "auto"
draft.sellAtPercent = draft.sellAtPercent or ADRuns.DEFAULT_SELL_AT_PERCENT
draft.storeAbovePercent = draft.storeAbovePercent or ADRuns.DEFAULT_STORE_ABOVE_PERCENT
draft.returnBelowPercent = draft.returnBelowPercent or ADRuns.DEFAULT_RETURN_BELOW_PERCENT
draft.loops = draft.loops or 0
draft.waitPoolGroup = draft.waitPoolGroup or ""
draft.collectRound = draft.collectRound == true
draft.isNew = isNew == true
draft.showAllBuildings = false
draft.storeAuto = #draft.storeBuildingIds == 0
return draft
end
function ADFlowGuide.open(run, context, options)
return ADGuide.new(ADFlowGuide.DEF, draftFrom(run, false), context, options)
end
-- Nyt flow: intet oprettes før [Gem]; kladden har standardværdierne fra ADRuns.createRun.
function ADFlowGuide.openNew(name, context)
local _, run = ADRuns.createRun(ADRuns.newStore(), name, "supply")
local draft = draftFrom(run, true)
draft.id = nil
return ADGuide.new(ADFlowGuide.DEF, draft, context)
end
-- Run-formet tabel til controllerens opslag (varer, vogne) før noget er gemt: markører fra de valgte
-- bygninger. markerIds(buildingId, role) -> {wayPointId, ...}.
function ADFlowGuide.previewRun(draft, markerIds)
local run = ADGuide.deepCopy(draft)
local function resolve(ids, role)
local result, seen = {}, {}
for _, buildingId in ipairs(ids or {}) do
for _, wayPointId in ipairs(markerIds(buildingId, role) or {}) do
if not seen[wayPointId] then
seen[wayPointId] = true
table.insert(result, wayPointId)
end
end
end
return result
end
run.sourceWayPointIds = resolve(draft.sourceBuildingIds, "load")
run.targetWayPointIds = resolve(draft.targetBuildingIds, "unload")
run.targetWayPointId = run.targetWayPointIds[1]
return run
end
-- ---- valg ----
local SET_VALUES = {
kind = true, ingredientMode = true, sellAtPercent = true, storeAbovePercent = true, loops = true,
returnBelowPercent = true, waitPoolGroup = true,
}
local BOOL_VALUES = {showAllBuildings = true, storeAuto = true, collectRound = true}
local LIST_VALUES = {ingredients = true, storeBuildingIds = true, vehicleIds = true}
local function setPolicy(products, name, policy)
local result = {}
for _, product in ipairs(products or {}) do
if product.fillType ~= name then table.insert(result, {fillType = product.fillType, policy = product.policy}) end
end
if policy ~= "off" then table.insert(result, {fillType = name, policy = policy}) end
return result
end
function ADFlowGuide.choose(guide, id, value)
local draft = ADGuide.deepCopy(guide.draft)
if SET_VALUES[id] then
draft[id] = value
elseif BOOL_VALUES[id] then
draft[id] = value == true
elseif LIST_VALUES[id] then
draft[id] = ADGuide.toggleItem(draft[id], value.item, value.on)
elseif id == "buildings" then
local key = draft.kind == "outbound" and "sourceBuildingIds" or "targetBuildingIds"
draft[key] = ADGuide.toggleItem(draft[key], value.item, value.on)
elseif id == "policy" then
draft.products = setPolicy(draft.products, value.item, value.value)
elseif id == "name" then
if type(value) ~= "string" or not value:match("%S") then return guide end
draft.name = value
else
return guide
end
return ADGuide.withDraft(guide, draft)
end
-- ---- rækker ----
local function choice(id, title, options, value, item)
return {type = "choice", id = id, title = title, options = options, value = value, item = item}
end
local function toggle(id, title, value)
return {type = "toggle", id = id, title = title, off = L("ui_no"), on = L("ui_yes"), value = value == true}
end
local function check(id, item, text, value)
return {type = "check", id = id, item = item, title = T(text), value = value == true}
end
local function text(key, args) return {type = "text", label = L(key, args)} end
local function percentOptions(from, to, step)
local options = {}
for percent = from, to, step do table.insert(options, {label = T(string.format("%d %%", percent)), value = percent}) end
return options
end
local function chosenSet(list)
local set = {}
for _, value in ipairs(list or {}) do set[value] = true end
return set
end
local function buildingIds(draft)
return draft.kind == "outbound" and draft.sourceBuildingIds or draft.targetBuildingIds
end
local function rowsName(guide)
return {
{type = "text", label = T(guide.draft.name or "")},
{type = "link", id = "rename", value = true, label = L("spu_guide_flow_rename")},
}
end
local function rowsDirection(guide)
return {choice("kind", L("spu_direction"), {
{label = L("spu_direction_in"), value = "supply"}, {label = L("spu_direction_out"), value = "outbound"}}, guide.draft.kind)}
end
-- Bygninger i fast rækkefølge (navn, markør); uden markør kun med "Vis alle" (eller når valgt).
local function rowsBuildings(guide)
local draft, rows = guide.draft, {}
local chosen = chosenSet(buildingIds(draft))
local candidates = guide.context.buildings or {}
local shown = ADFlowView.buildingRows(candidates, chosen, draft.showAllBuildings)
local hidden = ADFlowView.hiddenCount(candidates, shown)
if hidden > 0 or draft.showAllBuildings then
table.insert(rows, toggle("showAllBuildings", L("spu_showAllBuildings", {hidden}), draft.showAllBuildings))
end
for _, building in ipairs(shown) do
table.insert(rows, check("buildings", building.id, building.text or building.name, chosen[building.id]))
end
if #shown == 0 then table.insert(rows, text("spu_noBuildings")) end
return rows
end
local function rowsGoods(guide)
local draft, context, rows = guide.draft, guide.context, {}
if isOut(guide) then
local products = context.products or {}
if #products == 0 then return {text("spu_guide_flow_noProducts")} end
local options = {}
for _, policy in ipairs(ADFlowGuide.POLICIES) do table.insert(options, {label = L("spu_policy_" .. policy), value = policy}) end
for _, product in ipairs(products) do
table.insert(rows, choice("policy", T(product.text or product.title), options, ADRuns.getProductPolicy(draft, product.name), product.name))
end
return rows
end
table.insert(rows, choice("ingredientMode", L("spu_guide_flow_goods"), {
{label = L("spu_guide_flow_goodsAutoChoice"), value = "auto"}, {label = L("spu_guide_manual"), value = "manual"}}, draft.ingredientMode))
if draft.ingredientMode ~= "manual" then
table.insert(rows, text("spu_guide_flow_goodsAutoText"))
return rows
end
local chosen = chosenSet(draft.ingredients)
for _, fillType in ipairs(context.fillTypes or {}) do
table.insert(rows, check("ingredients", fillType.name, " " .. tostring(fillType.title), chosen[fillType.name]))
end
if #(context.fillTypes or {}) == 0 then table.insert(rows, text("spu_guide_flow_noGoodsTo")) end
return rows
end
local function rowsStore(guide)
local draft, rows = guide.draft, {}
table.insert(rows, choice("storeAuto", L("spu_guide_store"), {
{label = L("spu_guide_flow_storeAutoChoice"), value = true}, {label = L("spu_guide_manual"), value = false}}, draft.storeAuto))
if draft.storeAuto then
table.insert(rows, text("spu_guide_flow_storeAutoText"))
return rows
end
local chosen = chosenSet(draft.storeBuildingIds)
for _, building in ipairs(guide.context.storeBuildings or {}) do
table.insert(rows, check("storeBuildingIds", building.id, building.text, chosen[building.id]))
end
if #(guide.context.storeBuildings or {}) == 0 then table.insert(rows, text("spu_noBuildings")) end
return rows
end
local function rowsRules(guide)
local draft = guide.draft
if isOut(guide) then
return {
choice("sellAtPercent", L("spu_sellAt"), percentOptions(ADRuns.MIN_SELL_AT_PERCENT, ADRuns.MAX_SELL_AT_PERCENT, 5), draft.sellAtPercent),
choice("storeAbovePercent", L("spu_storeAbove"), percentOptions(ADRuns.MIN_STORE_ABOVE_PERCENT, ADRuns.MAX_STORE_ABOVE_PERCENT, 5), draft.storeAbovePercent),
toggle("collectRound", L("spu_collectRound"), draft.collectRound),
}
end
local loops = {{label = L("spu_loops_infinite"), value = 0}}
for count = 1, ADRuns.MAX_LOOPS do table.insert(loops, {label = T(tostring(count)), value = count}) end
return {
choice("loops", L("spu_loops"), loops, draft.loops),
choice("returnBelowPercent", L("spu_returnBelow"), percentOptions(ADRuns.MIN_RETURN_BELOW_PERCENT, ADRuns.MAX_RETURN_BELOW_PERCENT, 10), draft.returnBelowPercent),
}
end
local function rowsWait(guide)
local options = {{label = L("spu_waitPool_all"), value = ""}}
for _, pool in ipairs(guide.context.waitPools or {}) do
table.insert(options, {label = T(string.format("%s (%d)", tostring(pool.group), pool.count or 0)), value = pool.group})
end
return {choice("waitPoolGroup", L("spu_waitPool"), options, guide.draft.waitPoolGroup or "")}
end
-- Traktorer: flowets egne først, så de ledige. Traktorer i andre flows og blokerede (vognen passer ikke)
-- skjules med en linje hver.
local function rowsTractors(guide)
local draft, rows = guide.draft, {}
local inRun = chosenSet(draft.vehicleIds)
local entries = {}
local other, blocked = 0, 0
for _, vehicle in ipairs(guide.context.vehicles or {}) do
local state = "free"
if inRun[vehicle.id] then
state = "in"
elseif vehicle.otherRunId ~= nil and vehicle.otherRunId ~= draft.id then
state = "other"
other = other + 1
elseif vehicle.blocked then
state = "blocked"
blocked = blocked + 1
end
table.insert(entries, {id = vehicle.id, label = tostring(vehicle.text), state = state})
end
for _, entry in ipairs(ADFlowView.vehicleRows(entries, false)) do
table.insert(rows, check("vehicleIds", entry.id, entry.label, entry.state == "in"))
end
if #rows == 0 then table.insert(rows, text("spu_guide_flow_noTractors")) end
if other > 0 then table.insert(rows, text("spu_guide_flow_otherFlows", {other})) end
if blocked > 0 then table.insert(rows, text("spu_guide_flow_blocked", {blocked})) end
return rows
end
local function rowsSummary(guide)
local rows = {}
for _, line in ipairs(ADFlowGuide.summary(guide)) do
table.insert(rows, {type = "link", id = "jump", value = line.step, line = line})
end
return rows
end
local ROWS = {
name = rowsName, direction = rowsDirection, buildings = rowsBuildings, goods = rowsGoods, store = rowsStore,
rules = rowsRules, wait = rowsWait, tractors = rowsTractors, summary = rowsSummary,
}
function ADFlowGuide.rows(guide)
local builder = ROWS[guide.step]
return builder ~= nil and builder(guide) or {}
end
-- ---- resumé ----
-- Korte navne til resuméet (field, ellers navn, ellers rækkens tekst).
local function namesFor(ids, entries, field)
local byId = {}
for _, entry in ipairs(entries or {}) do byId[entry.id or entry.name] = entry[field] or entry.name or entry.text end
local names = {}
for _, id in ipairs(ids or {}) do table.insert(names, tostring(byId[id] or id)) end
return names
end
local SUMMARY = {
name = function(guide) return {T(guide.draft.name or "")} end,
direction = function(guide) return {L(isOut(guide) and "spu_direction_out" or "spu_direction_in")} end,
buildings = function(guide)
local names = namesFor(buildingIds(guide.draft), guide.context.buildings, "name")
if #names == 0 then return {L("spu_guide_flow_none")} end
return {T(ADFlowView.joinCounted(names, " + ", ""))}
end,
goods = function(guide)
local draft = guide.draft
if isOut(guide) then
local parts = {}
local titles = {}
for _, product in ipairs(guide.context.products or {}) do titles[product.name] = product.title end
for _, product in ipairs(draft.products or {}) do
table.insert(parts, {key = "spu_policy_" .. product.policy, prefix = tostring(titles[product.fillType] or product.fillType) .. ": "})
end
if #parts == 0 then return {L("spu_guide_flow_none")} end
return parts
end
if draft.ingredientMode ~= "manual" then return {L("spu_guide_flow_goodsAuto")} end
if #draft.ingredients == 0 then return {L("spu_guide_flow_none")} end
if #draft.ingredients > ADFlowGuide.MAX_NAMED_GOODS then return {L("spu_guide_flow_sumGoods", {#draft.ingredients})} end
local byName = {}
for _, fillType in ipairs(guide.context.fillTypes or {}) do byName[fillType.name] = fillType.title end
local names = {}
for _, name in ipairs(draft.ingredients) do table.insert(names, tostring(byName[name] or name)) end
return {T(table.concat(names, " + "))}
end,
store = function(guide)
local draft = guide.draft
if draft.storeAuto or #draft.storeBuildingIds == 0 then return {L("spu_guide_sum_auto")} end
if #draft.storeBuildingIds > 2 then return {L("spu_guide_sum_stores", {#draft.storeBuildingIds})} end
return {T(table.concat(namesFor(draft.storeBuildingIds, guide.context.storeBuildings, "name"), " + "))}
end,
rules = function(guide)
local draft = guide.draft
if isOut(guide) then
local parts = {L("spu_guide_flow_sumSellAt", {draft.sellAtPercent}), L("spu_guide_flow_sumStoreAbove", {draft.storeAbovePercent})}
if draft.collectRound then table.insert(parts, L("spu_guide_flow_sumRound")) end
return parts
end
local loops = (draft.loops or 0) > 0 and L("spu_guide_flow_sumLoops", {draft.loops}) or L("spu_guide_flow_sumLoopsInf")
return {loops, L("spu_guide_flow_sumReturn", {draft.returnBelowPercent})}
end,
wait = function(guide)
local group = guide.draft.waitPoolGroup or ""
return {group == "" and L("spu_waitPool_all") or T(group)}
end,
tractors = function(guide)
local ids = guide.draft.vehicleIds
if #ids == 0 then return {L("spu_guide_flow_sumNoTractors")} end
if #ids > ADFlowGuide.MAX_NAMED_TRACTORS then return {L("spu_guide_flow_sumTractors", {#ids})} end
return {T(table.concat(namesFor(ids, guide.context.vehicles, "label"), ", "))}
end,
}
-- Resumé-linjer {step, title, parts}: Navn (altid) + de synlige trin.
function ADFlowGuide.summary(guide)
local lines = {}
for _, step in ipairs(ADFlowGuide.ORDER) do
local build = SUMMARY[step]
if build ~= nil and (step == "name" or ADFlowGuide.isVisible(step, guide)) then
table.insert(lines, {step = step, title = L(ADFlowGuide.stepKey(step, guide)), parts = build(guide)})
end
end
return lines
end
-- ---- gem ----
function ADFlowGuide.result(guide)
local draft = guide.draft
local storeIds = ADGuide.deepCopy(draft.storeBuildingIds)
if ADFlowGuide.isVisible("store", guide) and draft.storeAuto then storeIds = {} end
local changes = {
kind = draft.kind, sourceBuildingIds = ADGuide.deepCopy(draft.sourceBuildingIds),
targetBuildingIds = ADGuide.deepCopy(draft.targetBuildingIds), storeBuildingIds = storeIds,
products = ADGuide.deepCopy(draft.products), ingredientMode = draft.ingredientMode,
ingredients = ADGuide.deepCopy(draft.ingredients), sellAtPercent = draft.sellAtPercent,
storeAbovePercent = draft.storeAbovePercent, collectRound = draft.collectRound, loops = draft.loops,
returnBelowPercent = draft.returnBelowPercent, waitPoolGroup = draft.waitPoolGroup or "",
}
if draft.name ~= nil and draft.name:match("%S") then changes.name = draft.name end
return {isNew = draft.isNew == true, runId = draft.id, name = draft.name, kind = draft.kind, changes = changes,
vehicleIds = ADGuide.deepCopy(draft.vehicleIds)}
end
-- Traktorer der skal tildeles/fjernes efter gem.
function ADFlowGuide.vehicleChanges(oldIds, newIds)
local old, new = chosenSet(oldIds), chosenSet(newIds)
local added, removed = {}, {}
for _, id in ipairs(newIds or {}) do if not old[id] then table.insert(added, id) end end
for _, id in ipairs(oldIds or {}) do if not new[id] then table.insert(removed, id) end end
return added, removed
end
+7 -1
View File
@@ -140,9 +140,15 @@ function ADGuide.withContext(guide, context)
return clone(guide, {context = context}) return clone(guide, {context = context})
end end
-- label = {key = "...", args = {...}} (oversættes) eller {text = "..."} (færdig tekst). -- label = {key = "...", args = {...}} (oversættes) eller {text = "..."} (færdig tekst); valgfri prefix
-- (færdig tekst foran, fx "Mælk: " foran den oversatte politik).
function ADGuide.labelText(label, getText) function ADGuide.labelText(label, getText)
if type(label) ~= "table" then return "" end if type(label) ~= "table" then return "" end
if label.prefix ~= nil then
local rest = {}
for key, value in pairs(label) do if key ~= "prefix" then rest[key] = value end end
return tostring(label.prefix) .. ADGuide.labelText(rest, getText)
end
if label.text ~= nil then return tostring(label.text) end if label.text ~= nil then return tostring(label.text) end
local template = getText(label.key) local template = getText(label.key)
if label.args == nil or #label.args == 0 then return tostring(template) end if label.args == nil or #label.args == 0 then return tostring(template) end
+1
View File
@@ -27,6 +27,7 @@ if ADSmartPickup.dir ~= nil and source ~= nil then
source(ADSmartPickup.dir .. "gui/SmartPickupFieldTab.lua") source(ADSmartPickup.dir .. "gui/SmartPickupFieldTab.lua")
source(ADSmartPickup.dir .. "gui/SmartPickupGuide.lua") source(ADSmartPickup.dir .. "gui/SmartPickupGuide.lua")
source(ADSmartPickup.dir .. "gui/SmartPickupFieldGuide.lua") source(ADSmartPickup.dir .. "gui/SmartPickupFieldGuide.lua")
source(ADSmartPickup.dir .. "gui/SmartPickupFlowGuide.lua")
end end
-- Markøren er dér hvor trækkerens næse stopper; læssetriggeren ligger et -- Markøren er dér hvor trækkerens næse stopper; læssetriggeren ligger et
@@ -0,0 +1,303 @@
-- 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
self.flowButtons = {self.backButtonInfo, self.startButtonInfo, self.flowSetupButtonInfo, self.flowNewButtonInfo, self.deleteButtonInfo}
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) 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é ----
function SmartPickupFrame:fillFlowTabRows(adEnv)
if self:hasGuideOn(SmartPickupFrame.TAB_FLOWS) and self:fillGuideRows(adEnv) then return end
self:fillOverviewRows(adEnv)
local run = self:getRun()
if run ~= nil then self:fillFlowSummary(adEnv, run) end
end
function SmartPickupFrame:fillFlowSummary(adEnv, run)
local i18n = self.i18n
self:addTextRow(string.format(i18n:getText("spu_guide_flow_section"), tostring(run.name)))
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])))
end
-- Hvert sekund: tavlens celler, statuslinje og knapper.
function SmartPickupFrame:refreshFlowTab(adEnv)
local run = self:getRun()
local isRunning = false
if self:hasGuideOn(SmartPickupFrame.TAB_FLOWS) then
self:setStatusText(self.statusOverride or "")
else
self:refreshOverview(adEnv)
if run ~= nil then
local status = ADRunsController.getRunStatus(adEnv, run)
isRunning = status.isRunning == true
local text = 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)
self:setStatusText(self.statusOverride or text)
else
-- tavlen siger allerede "Ingen flows"
self:setStatusText(self.statusOverride or "")
end
end
self.startButtonInfo.text = self.i18n:getText(isRunning and "spu_button_stop" or "spu_button_start")
self.startButtonInfo.disabled = run == nil
self.flowSetupButtonInfo.disabled = run == nil
self.deleteButtonInfo.disabled = run == nil
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
+3 -83
View File
@@ -1,7 +1,7 @@
-- Smart Pickup — fanen Flows -- Smart Pickup — fanen Flows
-- Tavle øverst: én tekstrække pr. flow (Vare · Fra → Til · Regel · Traktorer · Status), det valgte -- Tavlen (én række pr. flow, det valgte markeret med ">") bruges af fanerne Oversigt og Flows; Flows-fanens
-- markeret med ">". Under tavlen: detail-blok for det valgte flow — Retning, Fra/Til som BYGNINGER -- resumé og guiden "Opsæt flow" ligger i gui/SmartPickupFlowGuide.lua. Her også bygnings-teksten og de
-- (markøren findes automatisk), Varer, Regler, Vent ved, Traktorer, Status — og Wait-puljerne. -- gamle klik-callbacks for retning/bygninger.
-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua). -- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua).
-- ---- tavlen ---- -- ---- tavlen ----
@@ -137,86 +137,6 @@ function SmartPickupFrame:describeBuilding(building, role)
return string.format(" %s — %s", building.name, table.concat(parts, " · ")) return string.format(" %s — %s", building.name, table.concat(parts, " · "))
end 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 i fast rækkefølge (navn, 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, label = markers[1] ~= nil and tostring(markers[1].name) or nil, 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(" " .. 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 ---- -- ---- callbacks ----
function SmartPickupFrame:onClickDirection(state) function SmartPickupFrame:onClickDirection(state)
+15 -160
View File
@@ -55,6 +55,8 @@ function SmartPickupFrame.new(i18n, getAdEnv)
-- Marker: [Start nu/Stop], [Opsæt mark], [Markflåde] (gui/SmartPickupFieldGuide.lua); guidens knapper -- Marker: [Start nu/Stop], [Opsæt mark], [Markflåde] (gui/SmartPickupFieldGuide.lua); guidens knapper
self:initFieldButtons(i18n) self:initFieldButtons(i18n)
self:initGuideButtons(i18n) self:initGuideButtons(i18n)
-- Flows: [Start/Stop] [Opsæt flow] [Nyt flow] [Slet] (gui/SmartPickupFlowGuide.lua)
self:initFlowButtons(i18n)
-- Egen reference til listen: fallback'en i refreshButtons skal kunne sende den igen. -- Egen reference til listen: fallback'en i refreshButtons skal kunne sende den igen.
self.menuButtonInfo = self.overviewButtons self.menuButtonInfo = self.overviewButtons
self:setMenuButtonInfo(self.menuButtonInfo) self:setMenuButtonInfo(self.menuButtonInfo)
@@ -267,7 +269,8 @@ end
-- De faste rækker: Flow-vælger + Wait-pulje for alle flows; Ture/kør hjem for forsyning; Sælg fra/ -- De faste rækker: Flow-vælger + Wait-pulje for alle flows; Ture/kør hjem for forsyning; Sælg fra/
-- Kør til lager for udkørsel. Fanen Lagre viser ingen. -- Kør til lager for udkørsel. Fanen Lagre viser ingen.
function SmartPickupFrame:setStaticRowsVisible(tab, run) function SmartPickupFrame:setStaticRowsVisible(tab, run)
local isFlows = tab == SmartPickupFrame.TAB_FLOWS -- Flows-fanen bruger tavle + resumé + guide (v1.32); de faste XML-rækker vises ikke længere
local isFlows = false
local hasRun = isFlows and run ~= nil local hasRun = isFlows and run ~= nil
local isSupply, isOutbound = hasRun and run.kind ~= "outbound", hasRun and run.kind == "outbound" local isSupply, isOutbound = hasRun and run.kind ~= "outbound", hasRun and run.kind == "outbound"
local visibility = { local visibility = {
@@ -419,19 +422,8 @@ function SmartPickupFrame:rebuild()
self:restoreFocusIfLost() self:restoreFocusIfLost()
return return
end end
self:fillRunOption(adEnv) if adEnv ~= nil then
local hasRun = run ~= nil and adEnv ~= nil self:fillFlowTabRows(adEnv)
for _, element in ipairs({self.waitPoolOption, self.loopsOption, self.returnBelowOption, self.sellAtOption, self.storeAboveOption}) do
if element ~= nil then
element:setDisabled(not hasRun)
end
end
if hasRun then
self:fillWaitPoolOption(adEnv, run)
self:rebuildFlowDetail(adEnv, run)
self:addTextRow(self.i18n:getText("spu_section_vehicles"))
self:fillVehicleRows(adEnv, run)
self:addTextRow(self.i18n:getText("spu_section_status"))
end end
self.statusRow = self:addTextRow("") self.statusRow = self:addTextRow("")
self:colorRows() self:colorRows()
@@ -440,25 +432,6 @@ function SmartPickupFrame:rebuild()
self:restoreFocusIfLost() self:restoreFocusIfLost()
end end
function SmartPickupFrame:fillRunOption(adEnv)
local texts, index = {}, 1
self.runIds = {}
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)
if run.id == self.selectedRunId then
index = position
end
end
if #texts == 0 then
texts = {self.i18n:getText("spu_noRuns")}
end
self.runOption:setTexts(texts)
self.runOption:setState(index, false)
self.runOption:setDisabled(#self.runIds == 0)
end
-- Overblik: "Cow 1 food: TMR 18.0k/27.8k · Silage 3.0k/…" (mest trængende først) -- Overblik: "Cow 1 food: TMR 18.0k/27.8k · Silage 3.0k/…" (mest trængende først)
function SmartPickupFrame:describeTargetLevels(target) function SmartPickupFrame:describeTargetLevels(target)
if #target.levels == 0 then if #target.levels == 0 then
@@ -467,114 +440,6 @@ function SmartPickupFrame:describeTargetLevels(target)
return string.format("%s: %s", target.name, self:formatLevels(target.levels)) return string.format("%s: %s", target.name, self:formatLevels(target.levels))
end end
function SmartPickupFrame:fillWaitPoolOption(adEnv, run)
local texts, index = {self.i18n:getText("spu_waitPool_all")}, 1
self.poolGroups = {""}
for _, pool in ipairs(ADRunsController.getWaitPools(adEnv)) do
table.insert(texts, string.format("%s (%d)", pool.group, pool.count))
table.insert(self.poolGroups, pool.group)
if pool.group == run.waitPoolGroup then
index = #texts
end
end
self.waitPoolOption:setTexts(texts)
self.waitPoolOption:setState(index, false)
end
-- Traktorer: som standard flowets egne og de ledige der kan bruges; "Vis alle" tilføjer traktorer i andre
-- flows og låste (vognen passer ikke) — de er tit mange og gør listen uoverskuelig.
function SmartPickupFrame:fillVehicleRows(adEnv, run)
local inRun = {}
for _, id in ipairs(run.vehicleIds) do
inRun[id] = true
end
local entries = {}
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
local id = ADRunsController.getVehicleId(vehicle)
local otherRun = ADRunsController.getRunForVehicle(vehicle)
local state = "free"
if inRun[id] then
state = "in"
elseif otherRun ~= nil and otherRun.id ~= run.id then
state = "other"
elseif SmartPickupFrame.VEHICLE_BLOCKING_REASONS[ADRunsController.validateVehicle(adEnv, run, vehicle).reason] then
state = "blocked"
end
table.insert(entries, {id = id, label = ADRunsController.getVehicleLabel(adEnv, vehicle), state = state, vehicle = vehicle, otherRun = otherRun})
end
local rows = ADFlowView.vehicleRows(entries, self.showAllVehicles == true)
local hidden = ADFlowView.hiddenCount(entries, rows)
if hidden > 0 or self.showAllVehicles then
self.showAllVehiclesOption = self:addToggleRow("showAllVehicles", string.format(self.i18n:getText("spu_showAllVehicles"), hidden), {self.i18n:getText("ui_no"), self.i18n:getText("ui_yes")}, self.showAllVehicles == true)
end
for _, entry in ipairs(rows) do
local vehicle = entry.vehicle
local row = self:addRow(self.vehiclePrefab, {kind = "vehicle", vehicle = vehicle, row = nil})
local option, title = row:getDescendantByName("option"), row:getDescendantByName("title")
self.rowData[option].row = row
option:setState(entry.state == "in" and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
if entry.state == "other" then
-- én traktor = ét flow; rækken viser hvor den er
option:setDisabled(true)
self.rowData[option].locked = true
title:setText(string.format("%s — %s", entry.label, string.format(self.i18n:getText("spu_inRun"), tostring(entry.otherRun.name))))
else
title:setText(self:describeVehicle(adEnv, run, vehicle, entry.state == "in"))
if entry.state == "blocked" then
option:setDisabled(true)
self.rowData[option].locked = true
end
end
end
end
-- "Fastrac 2135 4WS (#1) — 1.000.000 l — ✔" / "… — ✖ ingen vogn"; reservationsnote fra Start bevares.
-- "Fastrac 2135 4WS (#1) — 1.000.000 l — kan bære: Halm, TMR — ✔" / "… — ✖ ingen vogn".
-- Vises for ALLE traktorer, så man kan se om vognen passer, før man siger Ja.
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
-- Felterne følger modellen: når et klik ikke er nået ind i run'et, springer feltet tilbage, -- Felterne følger modellen: når et klik ikke er nået ind i run'et, springer feltet tilbage,
-- og loggen ("run '…': waitPoolGroup=…") viser om klikket blev registreret. -- og loggen ("run '…': waitPoolGroup=…") viser om klikket blev registreret.
function SmartPickupFrame:syncOptionsFromModel(adEnv, run) function SmartPickupFrame:syncOptionsFromModel(adEnv, run)
@@ -706,6 +571,13 @@ function SmartPickupFrame:refreshLive()
self:refreshButtons() self:refreshButtons()
return return
end end
if self.currentTab == SmartPickupFrame.TAB_FLOWS then
self:refreshFlowTab(adEnv)
self.statusOverrideTicks = (self.statusOverrideTicks or 0) - 1
if self.statusOverrideTicks <= 0 then self.statusOverride = nil end
self:refreshButtons()
return
end
self:refreshStorageRows(adEnv) self:refreshStorageRows(adEnv)
local isRunning = false local isRunning = false
if isOverview then if isOverview then
@@ -919,26 +791,9 @@ function SmartPickupFrame:openFlow(runId)
self:switchTab(SmartPickupFrame.TAB_FLOWS) self:switchTab(SmartPickupFrame.TAB_FLOWS)
end end
-- Nyt run: navnet vælges FØR run'et oprettes (annulleres dialogen, oprettes intet). -- Nyt flow: guiden "Opsæt flow" (gui/SmartPickupFlowGuide.lua) — intet oprettes før [Gem].
-- Forslaget tæller efter antal runs, ikke efter løbenummeret, så "Run 2" kan genbruges.
function SmartPickupFrame:onClickNew() function SmartPickupFrame:onClickNew()
self:safe(function(frame) self:onClickFlowNew()
local suggestion = string.format(frame.i18n:getText("spu_newRunName"), #ADRunsController.store.runs + 1)
TextInputDialog.show(SmartPickupFrame.onNewRunName, frame, suggestion, frame.i18n:getText("spu_renameTitle"), nil, 30, g_i18n:getText("button_ok"))
end)
end
function SmartPickupFrame:onNewRunName(value, clickOk)
self:safe(function(frame)
if not clickOk or value == nil or not value:match("%S") then
return
end
local run = ADRunsController.createRun(value, "supply")
frame.selectedRunId = run.id
frame.statusOverride = nil
frame.currentTab = SmartPickupFrame.TAB_FLOWS
frame:switchTab(SmartPickupFrame.TAB_FLOWS)
end)
end end
function SmartPickupFrame:onClickRename() function SmartPickupFrame:onClickRename()
+4 -2
View File
@@ -103,7 +103,7 @@ function SmartPickupFrame:fillGuideRows(adEnv)
self.guide = ADGuide.withContext(self.guide, context) self.guide = ADGuide.withContext(self.guide, context)
local guide, model = self.guide, hooks.model local guide, model = self.guide, hooks.model
self:addTextRow(hooks.heading(self, guide)) self:addTextRow(hooks.heading(self, guide))
local stepTitle = self.i18n:getText(model.stepKey(guide.step)) local stepTitle = self.i18n:getText(model.stepKey(guide.step, guide))
local index, total = ADGuide.position(guide) local index, total = ADGuide.position(guide)
local header local header
if guide.single then if guide.single then
@@ -114,7 +114,7 @@ function SmartPickupFrame:fillGuideRows(adEnv)
header = string.format(self.i18n:getText("spu_guide_stepOne"), index or 1, stepTitle) header = string.format(self.i18n:getText("spu_guide_stepOne"), index or 1, stepTitle)
end end
self:addTextRow(header) self:addTextRow(header)
self:addTextRow(" " .. self.i18n:getText(model.hintKey(guide.step))) self:addTextRow(" " .. self.i18n:getText(model.hintKey(guide.step, guide)))
for _, row in ipairs(model.rows(guide)) do for _, row in ipairs(model.rows(guide)) do
self:addGuideRow(row) self:addGuideRow(row)
end end
@@ -140,6 +140,8 @@ function SmartPickupFrame:onClickGuideRow(state, data)
local option = row.options[state] local option = row.options[state]
if option == nil then return end if option == nil then return end
value = option.value value = option.value
-- valg pr. vare (fx politik): modellen skal vide hvilken
if row.item ~= nil then value = {item = row.item, value = option.value} end
elseif row.type == "toggle" then elseif row.type == "toggle" then
value = state == SmartPickupFrame.STATE_ON value = state == SmartPickupFrame.STATE_ON
elseif row.type == "check" then elseif row.type == "check" then
+56 -7
View File
@@ -4,7 +4,7 @@
<text name="spu_title" text="Smart Pickup"/> <text name="spu_title" text="Smart Pickup"/>
<text name="spu_run" text="Run"/> <text name="spu_run" text="Run"/>
<text name="spu_run_tooltip" text="Vælg det run du vil se eller ændre."/> <text name="spu_run_tooltip" text="Vælg det run du vil se eller ændre."/>
<text name="spu_noRuns" text="Ingen runs — tryk 'Nyt run'"/> <text name="spu_noRuns" text="Ingen flows — tryk &apos;Nyt flow&apos;"/>
<text name="spu_target" text="Mål"/> <text name="spu_target" text="Mål"/>
<text name="spu_target_tooltip" text="AutoDrive-markør ved stalden eller fabrikken der skal fyldes."/> <text name="spu_target_tooltip" text="AutoDrive-markør ved stalden eller fabrikken der skal fyldes."/>
<text name="spu_noTarget" text="(vælg mål)"/> <text name="spu_noTarget" text="(vælg mål)"/>
@@ -189,13 +189,13 @@
<text name="spu_field_straw_chop" text="Hakkes (spredes på marken)"/> <text name="spu_field_straw_chop" text="Hakkes (spredes på marken)"/>
<text name="spu_field_straw_collect" text="Samles op til silo"/> <text name="spu_field_straw_collect" text="Samles op til silo"/>
<text name="spu_field_straw_bale" text="Presses til baller og samles"/> <text name="spu_field_straw_bale" text="Presses til baller og samles"/>
<text name="spu_button_start" text="Start run"/> <text name="spu_button_start" text="Start flow"/>
<text name="spu_button_stop" text="Stop run"/> <text name="spu_button_stop" text="Stop flow"/>
<text name="spu_button_new" text="Nyt run"/> <text name="spu_button_new" text="Nyt flow"/>
<text name="spu_button_rename" text="Omdøb"/> <text name="spu_button_rename" text="Omdøb"/>
<text name="spu_button_delete" text="Slet run"/> <text name="spu_button_delete" text="Slet flow"/>
<text name="spu_newRunName" text="Run %d"/> <text name="spu_newRunName" text="Flow %d"/>
<text name="spu_renameTitle" text="Navn på run"/> <text name="spu_renameTitle" text="Navn på flow"/>
<text name="spu_deleteConfirm" text="Slet run '%s'? Dets kørende traktorer stoppes. Kan ikke fortrydes."/> <text name="spu_deleteConfirm" text="Slet run '%s'? Dets kørende traktorer stoppes. Kan ikke fortrydes."/>
<text name="spu_capacity" text="%s l"/> <text name="spu_capacity" text="%s l"/>
<text name="spu_capacity_pallets" text="autoload (paller)"/> <text name="spu_capacity_pallets" text="autoload (paller)"/>
@@ -390,5 +390,54 @@
<text name="spu_guide_sum_unloaders" text="%d aflæsningsvogne"/> <text name="spu_guide_sum_unloaders" text="%d aflæsningsvogne"/>
<text name="spu_guide_sum_fieldSilo" text="Marksilo"/> <text name="spu_guide_sum_fieldSilo" text="Marksilo"/>
<text name="spu_guide_sum_stores" text="%d valgte siloer"/> <text name="spu_guide_sum_stores" text="%d valgte siloer"/>
<text name="spu_guide_flow_open" text="Opsæt flow"/>
<text name="spu_guide_flow_title" text="— Opsæt flow: %s —"/>
<text name="spu_guide_flow_section" text="— Flow: %s —"/>
<text name="spu_guide_flow_saved" text="Flow '%s' gemt"/>
<text name="spu_guide_flow_start" text="Start flow"/>
<text name="spu_guide_flow_rename" text="Skift navn …"/>
<text name="spu_guide_flow_step_name" text="Navn"/>
<text name="spu_guide_flow_step_direction" text="Retning"/>
<text name="spu_guide_flow_step_from" text="Fra"/>
<text name="spu_guide_flow_step_to" text="Til"/>
<text name="spu_guide_flow_step_goods" text="Varer"/>
<text name="spu_guide_flow_step_store" text="Lager"/>
<text name="spu_guide_flow_step_rules" text="Regler"/>
<text name="spu_guide_flow_step_wait" text="Vent ved"/>
<text name="spu_guide_flow_step_tractors" text="Traktorer"/>
<text name="spu_guide_flow_step_summary" text="Oversigt"/>
<text name="spu_guide_flow_hint_name" text="Flowets navn (traktorerne får navn efter det)."/>
<text name="spu_guide_flow_hint_direction" text="Ind til bygning: fyld stalde/fabrikker. Ud fra bygning: tøm, lagr og sælg."/>
<text name="spu_guide_flow_hint_buildings_in" text="Hvilke stalde/fabrikker skal fyldes? Markøren findes automatisk."/>
<text name="spu_guide_flow_hint_buildings_out" text="Hvor skal der hentes fra? Markøren findes automatisk."/>
<text name="spu_guide_flow_hint_goods_in" text="Automatisk henter hver tur den vare målet mangler mest."/>
<text name="spu_guide_flow_hint_goods_out" text="For hver vare: Lager, Salg, eller Salg ellers lager."/>
<text name="spu_guide_flow_hint_store" text="Hvor skal lagervarerne hen?"/>
<text name="spu_guide_flow_hint_rules_in" text="Antal ture, og hvornår traktoren kører hjem fra Wait."/>
<text name="spu_guide_flow_hint_rules_out" text="Hvornår der sælges, og hvornår der køres til lager."/>
<text name="spu_guide_flow_hint_wait" text="Hvor venter traktoren med et rest-læs?"/>
<text name="spu_guide_flow_hint_tractors" text="Hvilke traktorer kører flowet? Traktorer i andre flows vises ikke."/>
<text name="spu_guide_flow_hint_summary" text="Tjek valgene. Tryk på en linje for at rette den."/>
<text name="spu_guide_flow_goods" text="Varer"/>
<text name="spu_guide_flow_goodsAutoChoice" text="Automatisk (det bygningen mangler)"/>
<text name="spu_guide_flow_goodsAutoText" text=" Hver tur hentes den vare målet mangler mest."/>
<text name="spu_guide_flow_noGoodsTo" text=" Vælg først hvor der skal køres til (trinnet Til)"/>
<text name="spu_guide_flow_noProducts" text=" Vælg først hvor der hentes fra (trinnet Fra)"/>
<text name="spu_guide_flow_storeAutoChoice" text="Automatisk (nærmeste med plads)"/>
<text name="spu_guide_flow_storeAutoText" text=" Lagerturene kører til nærmeste lager med plads."/>
<text name="spu_guide_flow_noTractors" text=" Ingen ledige traktorer"/>
<text name="spu_guide_flow_otherFlows" text=" %d traktorer kører i andre flows og vises ikke"/>
<text name="spu_guide_flow_blocked" text=" %d traktorer kan ikke bruges her (vognen passer ikke)"/>
<text name="spu_guide_flow_none" text="ingen valgt"/>
<text name="spu_guide_flow_goodsAuto" text="Automatisk"/>
<text name="spu_guide_flow_sumGoods" text="%d varer"/>
<text name="spu_guide_flow_sumSellAt" text="sælg fra %d %%"/>
<text name="spu_guide_flow_sumStoreAbove" text="lager over %d %%"/>
<text name="spu_guide_flow_sumRound" text="samlerunde"/>
<text name="spu_guide_flow_sumLoops" text="%d ture"/>
<text name="spu_guide_flow_sumLoopsInf" text="uendelige ture"/>
<text name="spu_guide_flow_sumReturn" text="hjem fra Wait under %d %%"/>
<text name="spu_guide_flow_sumNoTractors" text="ingen"/>
<text name="spu_guide_flow_sumTractors" text="%d traktorer"/>
</texts> </texts>
</l10n> </l10n>
+56 -7
View File
@@ -4,7 +4,7 @@
<text name="spu_title" text="Smart Pickup"/> <text name="spu_title" text="Smart Pickup"/>
<text name="spu_run" text="Run"/> <text name="spu_run" text="Run"/>
<text name="spu_run_tooltip" text="Select the run to view or edit."/> <text name="spu_run_tooltip" text="Select the run to view or edit."/>
<text name="spu_noRuns" text="No runs — press 'New run'"/> <text name="spu_noRuns" text="No flows — press &apos;New flow&apos;"/>
<text name="spu_target" text="Target"/> <text name="spu_target" text="Target"/>
<text name="spu_target_tooltip" text="AutoDrive marker at the barn or factory to keep filled."/> <text name="spu_target_tooltip" text="AutoDrive marker at the barn or factory to keep filled."/>
<text name="spu_noTarget" text="(choose target)"/> <text name="spu_noTarget" text="(choose target)"/>
@@ -189,13 +189,13 @@
<text name="spu_field_straw_chop" text="Chopped (spread on the field)"/> <text name="spu_field_straw_chop" text="Chopped (spread on the field)"/>
<text name="spu_field_straw_collect" text="Collected to a silo"/> <text name="spu_field_straw_collect" text="Collected to a silo"/>
<text name="spu_field_straw_bale" text="Baled and collected"/> <text name="spu_field_straw_bale" text="Baled and collected"/>
<text name="spu_button_start" text="Start run"/> <text name="spu_button_start" text="Start flow"/>
<text name="spu_button_stop" text="Stop run"/> <text name="spu_button_stop" text="Stop flow"/>
<text name="spu_button_new" text="New run"/> <text name="spu_button_new" text="New flow"/>
<text name="spu_button_rename" text="Rename"/> <text name="spu_button_rename" text="Rename"/>
<text name="spu_button_delete" text="Delete run"/> <text name="spu_button_delete" text="Delete flow"/>
<text name="spu_newRunName" text="Run %d"/> <text name="spu_newRunName" text="Flow %d"/>
<text name="spu_renameTitle" text="Run name"/> <text name="spu_renameTitle" text="Flow name"/>
<text name="spu_deleteConfirm" text="Delete run '%s'? Its running tractors will be stopped. This cannot be undone."/> <text name="spu_deleteConfirm" text="Delete run '%s'? Its running tractors will be stopped. This cannot be undone."/>
<text name="spu_capacity" text="%s l"/> <text name="spu_capacity" text="%s l"/>
<text name="spu_capacity_pallets" text="autoload (pallets)"/> <text name="spu_capacity_pallets" text="autoload (pallets)"/>
@@ -390,5 +390,54 @@
<text name="spu_guide_sum_unloaders" text="%d unloaders"/> <text name="spu_guide_sum_unloaders" text="%d unloaders"/>
<text name="spu_guide_sum_fieldSilo" text="Field silo"/> <text name="spu_guide_sum_fieldSilo" text="Field silo"/>
<text name="spu_guide_sum_stores" text="%d chosen silos"/> <text name="spu_guide_sum_stores" text="%d chosen silos"/>
<text name="spu_guide_flow_open" text="Set up flow"/>
<text name="spu_guide_flow_title" text="— Set up flow: %s —"/>
<text name="spu_guide_flow_section" text="— Flow: %s —"/>
<text name="spu_guide_flow_saved" text="Flow '%s' saved"/>
<text name="spu_guide_flow_start" text="Start flow"/>
<text name="spu_guide_flow_rename" text="Change name …"/>
<text name="spu_guide_flow_step_name" text="Name"/>
<text name="spu_guide_flow_step_direction" text="Direction"/>
<text name="spu_guide_flow_step_from" text="From"/>
<text name="spu_guide_flow_step_to" text="To"/>
<text name="spu_guide_flow_step_goods" text="Goods"/>
<text name="spu_guide_flow_step_store" text="Storage"/>
<text name="spu_guide_flow_step_rules" text="Rules"/>
<text name="spu_guide_flow_step_wait" text="Wait at"/>
<text name="spu_guide_flow_step_tractors" text="Tractors"/>
<text name="spu_guide_flow_step_summary" text="Summary"/>
<text name="spu_guide_flow_hint_name" text="The flow's name (tractors are named after it)."/>
<text name="spu_guide_flow_hint_direction" text="Into building: keep barns/factories filled. Out of building: empty, store and sell."/>
<text name="spu_guide_flow_hint_buildings_in" text="Which barns/factories should be filled? The marker is found automatically."/>
<text name="spu_guide_flow_hint_buildings_out" text="Where should goods be picked up? The marker is found automatically."/>
<text name="spu_guide_flow_hint_goods_in" text="Automatic fetches whatever the target lacks most on each trip."/>
<text name="spu_guide_flow_hint_goods_out" text="For each good: Store, Sell, or Sell else store."/>
<text name="spu_guide_flow_hint_store" text="Where should stored goods go?"/>
<text name="spu_guide_flow_hint_rules_in" text="Number of trips, and when the tractor leaves Wait."/>
<text name="spu_guide_flow_hint_rules_out" text="When to sell, and when to drive to storage."/>
<text name="spu_guide_flow_hint_wait" text="Where does the tractor wait with a leftover load?"/>
<text name="spu_guide_flow_hint_tractors" text="Which tractors run this flow? Tractors in other flows are hidden."/>
<text name="spu_guide_flow_hint_summary" text="Check your choices. Click a line to change it."/>
<text name="spu_guide_flow_goods" text="Goods"/>
<text name="spu_guide_flow_goodsAutoChoice" text="Automatic (what the building needs)"/>
<text name="spu_guide_flow_goodsAutoText" text=" Each trip fetches the good the target lacks most."/>
<text name="spu_guide_flow_noGoodsTo" text=" First choose where to deliver (step To)"/>
<text name="spu_guide_flow_noProducts" text=" First choose where to pick up (step From)"/>
<text name="spu_guide_flow_storeAutoChoice" text="Automatic (nearest with room)"/>
<text name="spu_guide_flow_storeAutoText" text=" Storage trips go to the nearest store with room."/>
<text name="spu_guide_flow_noTractors" text=" No free tractors"/>
<text name="spu_guide_flow_otherFlows" text=" %d tractors run in other flows and are hidden"/>
<text name="spu_guide_flow_blocked" text=" %d tractors cannot be used here (trailer does not fit)"/>
<text name="spu_guide_flow_none" text="none chosen"/>
<text name="spu_guide_flow_goodsAuto" text="Automatic"/>
<text name="spu_guide_flow_sumGoods" text="%d goods"/>
<text name="spu_guide_flow_sumSellAt" text="sell from %d %%"/>
<text name="spu_guide_flow_sumStoreAbove" text="store above %d %%"/>
<text name="spu_guide_flow_sumRound" text="collection round"/>
<text name="spu_guide_flow_sumLoops" text="%d trips"/>
<text name="spu_guide_flow_sumLoopsInf" text="endless trips"/>
<text name="spu_guide_flow_sumReturn" text="leave Wait below %d %%"/>
<text name="spu_guide_flow_sumNoTractors" text="none"/>
<text name="spu_guide_flow_sumTractors" text="%d tractors"/>
</texts> </texts>
</l10n> </l10n>
+2 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107"> <modDesc descVersion="107">
<author>masterdraco</author> <author>masterdraco</author>
<version>1.31.0.0</version> <version>1.32.0.0</version>
<title> <title>
<en>AD Smart Pickup</en> <en>AD Smart Pickup</en>
</title> </title>
@@ -24,6 +24,7 @@
<sourceFile filename="adStorageStats.lua"/> <sourceFile filename="adStorageStats.lua"/>
<sourceFile filename="adFlowView.lua"/> <sourceFile filename="adFlowView.lua"/>
<sourceFile filename="adGuide.lua"/> <sourceFile filename="adGuide.lua"/>
<sourceFile filename="adFlowGuide.lua"/>
<sourceFile filename="adSources.lua"/> <sourceFile filename="adSources.lua"/>
<sourceFile filename="adBuildings.lua"/> <sourceFile filename="adBuildings.lua"/>
<sourceFile filename="adOutbound.lua"/> <sourceFile filename="adOutbound.lua"/>
+30 -7
View File
@@ -159,12 +159,17 @@ samme rækkefølge.
### Knapper ### Knapper
Fanen **Flows** viser tavlen (én linje pr. flow) og under den det valgte flows **resumé**: Navn, Retning,
Fra/Til, Varer, (Lager), Regler, Vent ved, Traktorer og Status. **Klik på et flow** i tavlen for at vælge det,
og **klik på en resumé-linje** for at rette netop det trin (**Gem** gemmer og går tilbage). Klik på
**Navn** for at omdøbe flowet.
| Knap | Funktion | | Knap | Funktion |
|---|---| |---|---|
| **Nyt run** | Opretter et nyt flow. Det starter som *Ind til bygning* | | **Start flow / Stop flow** | Starter eller stopper alle traktorer i flowet |
| **Omdøb** | Giver det valgte flow et nyt navn | | **Opsæt flow** | Guiden for det valgte flow med de nuværende valg |
| **Slet run** | Sletter flowet og stopper dets kørende traktorer. Kan ikke fortrydes | | **Nyt flow** | Guiden for et nyt flow. Intet oprettes før du trykker **Start flow** eller **Gem uden at starte** |
| **Start run / Stop run** | Starter eller stopper alle traktorer i flowet | | **Slet flow** | Sletter flowet og stopper dets kørende traktorer. Kan ikke fortrydes |
På fanen **Overblik** er knapperne i stedet: På fanen **Overblik** er knapperne i stedet:
@@ -172,12 +177,30 @@ På fanen **Overblik** er knapperne i stedet:
|---|---| |---|---|
| **Start alle flows** | Starter alle flows med traktorer. Flows, der allerede kører, røres ikke, så ingen tur afbrydes | | **Start alle flows** | Starter alle flows med traktorer. Flows, der allerede kører, røres ikke, så ingen tur afbrydes |
| **Stop alle flows** | Stopper alle aktive traktorer i alle flows | | **Stop alle flows** | Stopper alle aktive traktorer i alle flows |
| **Nyt run** | Opretter et nyt flow og hopper til fanen **Flows** | | **Nyt flow** | Hopper til fanen **Flows** og åbner guiden for et nyt flow |
Statuslinjen viser bagefter, hvor mange flows og traktorer der blev startet, og op til tre grunde til Statuslinjen viser bagefter, hvor mange flows og traktorer der blev startet, og op til tre grunde til
traktorer, der blev sprunget over. Alle grunde står i `log.txt`. traktorer, der blev sprunget over. Alle grunde står i `log.txt`.
Vælg flow i feltet **Run** øverst. Under tavlen står detaljerne for det valgte flow. ### Guiden "Opsæt flow"
Samme trin-skærm og knapper som markguiden (**Næste**, **Tilbage**, **Annullér**; ét spørgsmål ad gangen,
trin der ikke passer springes over):
| Trin | Vises når | Valg |
|---|---|---|
| **Navn** | nyt flow | *Skift navn …* åbner tekstfeltet |
| **Retning** | altid | *Ind til bygning* (forsyning) / *Ud fra bygning* (udkørsel) |
| **Til** / **Fra** | altid | Bygningerne (se *Bygningslisten* nedenfor) |
| **Varer** | altid | Forsyning: *Automatisk (det bygningen mangler)* eller *Vælg selv*. Udkørsel: pr. vare Fra / Lager / Salg / Salg, ellers lager |
| **Lager** | udkørsel med en vare på Lager | *Automatisk (nærmeste med plads)* eller *Vælg selv* blandt lagrene |
| **Regler** | altid | Udkørsel: sælg fra %, lager over %, saml op på én tur. Forsyning: ture, kør hjem fra Wait under % |
| **Vent ved** | altid | Wait-puljen |
| **Traktorer** | altid | Ledige traktorer og flowets egne. Traktorer i andre flows (og dem hvis vogn ikke passer) er skjult med en linje |
| **Oversigt** | altid | Resuméet; klik på en linje for at rette. **Start flow** eller **Gem uden at starte** |
Moden spørger aldrig om markøren ved bygningen, rutetjek, bedste foder først, læs-bytte, kildevalg,
fordeling på flere mål, hvile af tomme kilder eller traktornavne.
### Retning ### Retning
@@ -207,7 +230,7 @@ Et forsyningsflow holder én eller flere bygninger fyldt med alt det, de bruger.
### Opsætning ### Opsætning
1. **Nyt run**, og lad Retning stå på **Ind til bygning**. 1. **Nyt flow** (guiden), og lad Retning stå på **Ind til bygning**.
2. Under **Til**: sæt Ja ud for de stalde og fabrikker, der skal fyldes. Listen viser også siloer, 2. Under **Til**: sæt Ja ud for de stalde og fabrikker, der skal fyldes. Listen viser også siloer,
der kun kan fyldes og ikke tømmes, fx Pumps & Hoses' drive-on-bunkere (DOB 115), der fodrer en der kun kan fyldes og ikke tømmes, fx Pumps & Hoses' drive-on-bunkere (DOB 115), der fodrer en
biogas-fermentor. For dem er **Vælg** med de varer, fermentoren skal have, bedst. biogas-fermentor. For dem er **Vælg** med de varer, fermentoren skal have, bedst.
+16
View File
@@ -187,6 +187,22 @@ Afgrøde "Samme som høstet", Tromle Ja. Kalk Ja.
**Til marker** går tilbage. **Til marker** går tilbage.
- [ ] **10.12 Klik routes.** Alle Ja/Nej, valg og flueben i guiden reagerer (ingen "GUI-fejl" i statuslinjen/log). - [ ] **10.12 Klik routes.** Alle Ja/Nej, valg og flueben i guiden reagerer (ingen "GUI-fejl" i statuslinjen/log).
## 11. Guiden "Opsæt flow" (v1.32)
- [ ] **11.1 Flows-fanen.** Tavlen øverst; valgt flow (">") har resumé-linjer med "[Ret]" og Status. Ingen
lang detaljeliste og ingen faste felter (Run, Wait-pulje …). Knapper: Start flow, Opsæt flow, Nyt flow, Slet flow.
- [ ] **11.2 Nyt flow.** Nyt flow → "Trin 1 af 8 — Navn" → *Skift navn …* → skriv navn → Næste gennem Retning
(Ud fra bygning), Fra (en fabrik), Varer (sæt en vare på Lager → trinnet Lager dukker op, tællingen bliver 9),
Regler, Vent ved, Traktorer (flueben) → Oversigt → **Start flow**. Forvent: flowet findes, kører, log
`run '…': …` med kind/products/storeBuildingIds.
- [ ] **11.3 Annullér nyt flow.** Nyt flow → Annullér (eller ESC) → intet nyt flow i tavlen.
- [ ] **11.4 Ret én linje.** Klik "Traktorer: …" → "Ret: Traktorer" → sæt flueben → **Gem** → tilbage; traktoren er i flowet.
- [ ] **11.5 Omdøb.** Klik "Navn: …" → tekstfeltet åbner → nyt navn står i tavlen.
- [ ] **11.6 Andre flows skjult.** Traktor i et andet flow vises ikke i trinnet Traktorer; linje "N traktorer kører i andre flows".
- [ ] **11.7 Forsyning.** Opsæt et forsyningsflow: Til = stald, Varer = Vælg selv → varerne stalden tager vises; Regler viser Ture og "Kør hjem fra Wait under".
- [ ] **11.8 Nyt flow fra Oversigt-fanen** hopper til Flows med guiden åben.
- [ ] **11.9 Klik routes.** Alle valg (også pr. vare-politik) reagerer; ingen "GUI-fejl".
--- ---
## Kendte usikkerheder (første gang i spillet) ## Kendte usikkerheder (første gang i spillet)
+30 -7
View File
@@ -161,12 +161,17 @@ the same order.
### Buttons ### Buttons
The **Flows** tab shows the board (one line per flow) and below it the selected flow's **summary**: Name,
Direction, From/To, Goods, (Storage), Rules, Wait at, Tractors and Status. **Click a flow** on the board to
select it, and **click a summary line** to change just that step (**Save** saves and returns). Click
**Name** to rename the flow.
| Button | What it does | | Button | What it does |
|---|---| |---|---|
| **New run** | Creates a new flow. It starts as *Into building* | | **Start flow / Stop flow** | Starts or stops all tractors in the flow |
| **Rename** | Renames the selected flow | | **Set up flow** | The guide for the selected flow with its current choices |
| **Delete run** | Deletes the flow and stops its running tractors. Cannot be undone | | **New flow** | The guide for a new flow. Nothing is created before you press **Start flow** or **Save without starting** |
| **Start run / Stop run** | Starts or stops all tractors in the flow | | **Delete flow** | Deletes the flow and stops its running tractors. Cannot be undone |
On the **Overview** tab the buttons are instead: On the **Overview** tab the buttons are instead:
@@ -174,12 +179,30 @@ On the **Overview** tab the buttons are instead:
|---|---| |---|---|
| **Start all flows** | Starts every flow that has tractors. Flows already running are left alone, so no trip is interrupted | | **Start all flows** | Starts every flow that has tractors. Flows already running are left alone, so no trip is interrupted |
| **Stop all flows** | Stops all active tractors in all flows | | **Stop all flows** | Stops all active tractors in all flows |
| **New run** | Creates a new flow and jumps to the **Flows** tab | | **New flow** | Jumps to the **Flows** tab and opens the guide for a new flow |
Afterwards the status line shows how many flows and tractors were started, plus up to three reasons for Afterwards the status line shows how many flows and tractors were started, plus up to three reasons for
tractors that were skipped. All reasons are in `log.txt`. tractors that were skipped. All reasons are in `log.txt`.
Pick a flow in the **Run** field at the top. The details of the selected flow are shown below the board. ### The "Set up flow" guide
Same step screen and buttons as the field guide (**Next**, **Back**, **Cancel**; one question at a time,
steps that do not apply are skipped):
| Step | Shown when | Choices |
|---|---|---|
| **Name** | new flow | *Change name …* opens the text field |
| **Direction** | always | *Into building* (supply) / *Out of building* (delivery) |
| **To** / **From** | always | The buildings (see *The building list* below) |
| **Goods** | always | Supply: *Automatic (what the building needs)* or *Choose myself*. Delivery: per good Off / Store / Sell / Sell, else store |
| **Storage** | delivery with a good on Store | *Automatic (nearest with room)* or *Choose myself* among the stores |
| **Rules** | always | Delivery: sell from %, store above %, collect in one trip. Supply: trips, leave Wait below % |
| **Wait at** | always | The Wait pool |
| **Tractors** | always | Free tractors and the flow's own. Tractors in other flows (and those whose trailer does not fit) are hidden with a line |
| **Summary** | always | The summary; click a line to change it. **Start flow** or **Save without starting** |
The mod never asks about the marker at the building, route checks, best feed first, load swap, source
choice, spreading over several targets, resting empty sources or tractor names.
### Direction ### Direction
@@ -209,7 +232,7 @@ A supply flow keeps one or more buildings filled with everything they use.
### Setup ### Setup
1. **New run**, and leave Direction on **Into building**. 1. **New flow** (the guide), and leave Direction on **Into building**.
2. Under **To**: set Yes on the barns and factories to fill. The list also shows silos that can only 2. Under **To**: set Yes on the barns and factories to fill. The list also shows silos that can only
be filled and not emptied, such as the Pumps & Hoses drive-on bunkers (DOB 115) that feed a biogas be filled and not emptied, such as the Pumps & Hoses drive-on bunkers (DOB 115) that feed a biogas
fermenter. For those, **Select** with the goods the fermenter needs works best. fermenter. For those, **Select** with the goods the fermenter needs works best.
+232
View File
@@ -0,0 +1,232 @@
-- Kør: luajit tests/test_adFlowGuide.lua (fra repo-roden)
dofile("FS25_ADSmartPickup/adGuide.lua")
dofile("FS25_ADSmartPickup/adRuns.lua")
dofile("FS25_ADSmartPickup/adFlowView.lua")
dofile("FS25_ADSmartPickup/adFlowGuide.lua")
local FG, G = ADFlowGuide, ADGuide
local failures = 0
local function check(name, actual, expected)
if actual == expected then print("OK " .. name) else
failures = failures + 1
print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual)))
end
end
local function newRun(kind, changes)
local store, run = ADRuns.createRun(ADRuns.newStore(), "Mælk ud", kind)
if changes ~= nil then store = ADRuns.updateRun(store, run.id, changes) end
return ADRuns.getRun(store, run.id)
end
local function ctx(extra)
local context = {
buildings = {
{id = "b1", name = "Kostald", label = "Kostald M", hasMarker = true, text = "Kostald — M 5 m"},
{id = "b2", name = "Silo", label = "Silo M", hasMarker = true, text = "Silo — M 3 m"},
{id = "b3", name = "Lade", hasMarker = false, text = "Lade — INGEN MARKØR"},
},
fillTypes = {{name = "HAY", title = "Hø"}, {name = "STRAW", title = "Halm"}},
products = {{name = "MILK", title = "Mælk"}, {name = "EGG", title = "Æg"}},
storeBuildings = {{id = "s1", name = "Silo Nord", text = "Silo Nord — M 5 m"}, {id = "s2", name = "Palleskur", text = "Palleskur — M 2 m"}},
waitPools = {{group = "Wait A", count = 2}},
vehicles = {
{id = "t1", label = "Traktor 1", text = "Traktor 1 — 1000 l — OK"},
{id = "t2", text = "Traktor 2", otherRunId = 9, otherRunName = "Andet"},
{id = "t3", text = "Traktor 3", blocked = true},
{id = "t4", text = "Traktor 4"},
},
}
for key, value in pairs(extra or {}) do context[key] = value end
return context
end
local function steps(guide) return table.concat(G.visibleSteps(guide), ",") end
local function findRow(rows, id, item)
for _, row in ipairs(rows) do
if row.id == id and (item == nil or row.item == item) then return row end
end
return nil
end
local function countType(rows, rowType)
local n = 0
for _, row in ipairs(rows) do if row.type == rowType then n = n + 1 end end
return n
end
local function textKeys(rows)
local keys = {}
for _, row in ipairs(rows) do if row.type == "text" and row.label.key ~= nil then table.insert(keys, row.label.key) end end
return table.concat(keys, ",")
end
local function at(guide, step) return G.jump(guide, step, "close") end
-- ---- trin ----
local supply = FG.open(newRun("supply"), ctx())
check("FL1 forsyning", steps(supply), "direction,buildings,goods,rules,wait,tractors,summary")
local out = FG.open(newRun("outbound"), ctx())
check("FL1 udkørsel uden lager-vare", steps(out), "direction,buildings,goods,rules,wait,tractors,summary")
local outStore = FG.open(newRun("outbound", {products = {{fillType = "MILK", policy = "sellElseStore"}}}), ctx())
check("FL1 udkørsel med lager", steps(outStore), "direction,buildings,goods,store,rules,wait,tractors,summary")
local new = FG.openNew("Flow 3", ctx())
check("FL1 nyt flow har navn", steps(new), "name,direction,buildings,goods,rules,wait,tractors,summary")
check("FL1 nyt flow starter på navn", new.step, "name")
check("FL1 nyt flow er forsyning", new.draft.kind, "supply")
check("FL1 nyt flow navn", new.draft.name, "Flow 3")
check("FL1 eksisterende starter på retning", supply.step, "direction")
-- ---- titler følger retningen ----
check("FL2 til", FG.stepKey("buildings", supply), "spu_guide_flow_step_to")
check("FL2 fra", FG.stepKey("buildings", out), "spu_guide_flow_step_from")
check("FL2 andre", FG.stepKey("rules", out), "spu_guide_flow_step_rules")
check("FL2 hint", FG.hintKey("goods", out), "spu_guide_flow_hint_goods_out")
check("FL2 hint forsyning", FG.hintKey("goods", supply), "spu_guide_flow_hint_goods_in")
-- ---- choose er immutabel ----
local toOut = FG.choose(supply, "kind", "outbound")
check("FL3 retning skiftet", toOut.draft.kind, "outbound")
check("FL3 gammel urørt", supply.draft.kind, "supply")
local withTarget = FG.choose(supply, "buildings", {item = "b1", on = true})
check("FL3 forsyning: mål", table.concat(withTarget.draft.targetBuildingIds, ","), "b1")
check("FL3 forsyning: kilder urørt", #withTarget.draft.sourceBuildingIds, 0)
local withSource = FG.choose(out, "buildings", {item = "b2", on = true})
check("FL3 udkørsel: kilde", table.concat(withSource.draft.sourceBuildingIds, ","), "b2")
local policy = FG.choose(out, "policy", {item = "MILK", value = "store"})
check("FL3 politik", policy.draft.products[1].policy, "store")
check("FL3 politik gør lager-trin synligt", FG.isVisible("store", policy), true)
local off = FG.choose(policy, "policy", {item = "MILK", value = "off"})
check("FL3 politik fra fjerner", #off.draft.products, 0)
local replaced = FG.choose(policy, "policy", {item = "MILK", value = "sell"})
check("FL3 politik erstattes", #replaced.draft.products, 1)
check("FL3 politik ny", replaced.draft.products[1].policy, "sell")
check("FL3 navn", FG.choose(new, "name", "Æg til salg").draft.name, "Æg til salg")
check("FL3 tomt navn ignoreres", FG.choose(new, "name", " ").draft.name, "Flow 3")
-- ---- rækker ----
local dirRows = FG.rows(supply)
check("FL4 retning valg", findRow(dirRows, "kind").type, "choice")
check("FL4 retning værdi", findRow(dirRows, "kind").value, "supply")
local buildingRows = FG.rows(at(supply, "buildings"))
check("FL4 bygninger med markør", countType(buildingRows, "check"), 2)
check("FL4 vis alle", findRow(buildingRows, "showAllBuildings").type, "toggle")
local allRows = FG.rows(at(FG.choose(supply, "showAllBuildings", true), "buildings"))
check("FL4 vis alle: tre", countType(allRows, "check"), 3)
local noHidden = FG.rows(at(FG.open(newRun("supply"), ctx({buildings = {{id = "b1", name = "Kostald", hasMarker = true, text = "K"}}})), "buildings"))
check("FL4 ingen vis-alle uden skjulte", findRow(noHidden, "showAllBuildings"), nil)
local chosenFirst = FG.rows(at(FG.choose(supply, "buildings", {item = "b2", on = true}), "buildings"))
local firstCheck
for _, row in ipairs(chosenFirst) do if row.type == "check" and firstCheck == nil then firstCheck = row end end
check("FL4 fast rækkefølge (navn)", firstCheck.item, "b1")
check("FL4 valgt markeret", findRow(chosenFirst, "buildings", "b2").value, true)
check("FL4 ingen bygninger", textKeys(FG.rows(at(FG.open(newRun("supply"), ctx({buildings = {}})), "buildings"))), "spu_noBuildings")
local goodsAuto = FG.rows(at(supply, "goods"))
check("FL5 forsyning auto", findRow(goodsAuto, "ingredientMode").value, "auto")
check("FL5 forsyning auto: ingen liste", countType(goodsAuto, "check"), 0)
local goodsManual = FG.rows(at(FG.choose(supply, "ingredientMode", "manual"), "goods"))
check("FL5 forsyning vælg: varer", countType(goodsManual, "check"), 2)
check("FL5 forsyning vælg uden varer", textKeys(FG.rows(at(FG.choose(FG.open(newRun("supply"), ctx({fillTypes = {}})), "ingredientMode", "manual"), "goods"))), "spu_guide_flow_noGoodsTo")
local goodsOut = FG.rows(at(policy, "goods"))
check("FL5 udkørsel: politik pr. vare", countType(goodsOut, "choice"), 2)
check("FL5 udkørsel: mælk lager", findRow(goodsOut, "policy", "MILK").value, "store")
check("FL5 udkørsel: æg fra", findRow(goodsOut, "policy", "EGG").value, "off")
check("FL5 udkørsel: politikker", #findRow(goodsOut, "policy", "EGG").options, 4)
check("FL5 udkørsel uden varer", textKeys(FG.rows(at(FG.open(newRun("outbound"), ctx({products = {}})), "goods"))), "spu_guide_flow_noProducts")
local storeRows = FG.rows(at(outStore, "store"))
check("FL6 lager auto", findRow(storeRows, "storeAuto").value, true)
check("FL6 lager auto: ingen liste", countType(storeRows, "check"), 0)
local storeManual = FG.rows(at(FG.choose(outStore, "storeAuto", false), "store"))
check("FL6 lager vælg selv", countType(storeManual, "check"), 2)
local chosenStore = FG.open(newRun("outbound", {products = {{fillType = "MILK", policy = "store"}}, storeBuildingIds = {"s2"}}), ctx())
check("FL6 valgt lager -> vælg selv", chosenStore.draft.storeAuto, false)
local rulesOut = FG.rows(at(out, "rules"))
check("FL7 udkørsel: sælg fra", findRow(rulesOut, "sellAtPercent").value, 90)
check("FL7 udkørsel: sælg-trin", #findRow(rulesOut, "sellAtPercent").options, 11)
check("FL7 udkørsel: lager over", findRow(rulesOut, "storeAbovePercent").value, 80)
check("FL7 udkørsel: samlerunde", findRow(rulesOut, "collectRound").type, "toggle")
check("FL7 udkørsel: ingen ture", findRow(rulesOut, "loops"), nil)
local rulesIn = FG.rows(at(supply, "rules"))
check("FL7 forsyning: ture", findRow(rulesIn, "loops").value, 0)
check("FL7 forsyning: ture-valg", #findRow(rulesIn, "loops").options, ADRuns.MAX_LOOPS + 1)
check("FL7 forsyning: 0 = uendeligt", findRow(rulesIn, "loops").options[1].label.key, "spu_loops_infinite")
check("FL7 forsyning: retur", findRow(rulesIn, "returnBelowPercent").value, 50)
check("FL7 forsyning: ingen salg", findRow(rulesIn, "sellAtPercent"), nil)
local waitRows = FG.rows(at(supply, "wait"))
check("FL8 vent: alle først", findRow(waitRows, "waitPoolGroup").options[1].value, "")
check("FL8 vent: puljer", #findRow(waitRows, "waitPoolGroup").options, 2)
local assigned = FG.open(newRun("supply", {}), ctx())
assigned = FG.choose(assigned, "vehicleIds", {item = "t4", on = true})
local tractorRows = FG.rows(at(assigned, "tractors"))
check("FL9 traktorer: kun egne + ledige", countType(tractorRows, "check"), 2)
check("FL9 egne først", (function() for _, r in ipairs(tractorRows) do if r.type == "check" then return r.item end end end)(), "t4")
check("FL9 skjult andre flows", findRow(tractorRows, "vehicleIds", "t2"), nil)
check("FL9 skjult blokeret", findRow(tractorRows, "vehicleIds", "t3"), nil)
check("FL9 linjer", textKeys(tractorRows), "spu_guide_flow_otherFlows,spu_guide_flow_blocked")
local inThisRun = FG.open(newRun("supply"), ctx({vehicles = {{id = "t2", text = "T2", otherRunId = 1, otherRunName = "Mælk ud"}}}))
check("FL9 traktor i dette flow er ikke 'andet flow'", countType(FG.rows(at(inThisRun, "tractors")), "check"), 1)
-- ---- resumé ----
local function lineFor(lines, step)
for _, line in ipairs(lines) do if line.step == step then return line end end
return nil
end
local function partText(line)
local parts = {}
for _, part in ipairs(line.parts) do table.insert(parts, part.key or ("=" .. tostring(part.text))) end
return table.concat(parts, ",")
end
local sumIn = FG.summary(FG.choose(withTarget, "vehicleIds", {item = "t1", on = true}))
check("FL10 navn", partText(lineFor(sumIn, "name")), "=Mælk ud")
check("FL10 retning", partText(lineFor(sumIn, "direction")), "spu_direction_in")
check("FL10 til", partText(lineFor(sumIn, "buildings")), "=Kostald")
check("FL10 til-titel", lineFor(sumIn, "buildings").title.key, "spu_guide_flow_step_to")
check("FL10 varer auto", partText(lineFor(sumIn, "goods")), "spu_guide_flow_goodsAuto")
check("FL10 regler", partText(lineFor(sumIn, "rules")), "spu_guide_flow_sumLoopsInf,spu_guide_flow_sumReturn")
check("FL10 vent", partText(lineFor(sumIn, "wait")), "spu_waitPool_all")
check("FL10 traktorer", partText(lineFor(sumIn, "tractors")), "=Traktor 1")
check("FL10 intet lager-trin", lineFor(sumIn, "store"), nil)
local sumOut = FG.summary(FG.choose(FG.choose(policy, "collectRound", true), "sellAtPercent", 95))
check("FL11 retning ud", partText(lineFor(sumOut, "direction")), "spu_direction_out")
check("FL11 fra tom", partText(lineFor(sumOut, "buildings")), "spu_guide_flow_none")
check("FL11 varer", partText(lineFor(sumOut, "goods")), "spu_policy_store")
check("FL11 varenavn foran", lineFor(sumOut, "goods").parts[1].prefix, "Mælk: ")
check("FL11 lager auto", partText(lineFor(sumOut, "store")), "spu_guide_sum_auto")
check("FL11 regler", partText(lineFor(sumOut, "rules")), "spu_guide_flow_sumSellAt,spu_guide_flow_sumStoreAbove,spu_guide_flow_sumRound")
check("FL11 sælg 95", lineFor(sumOut, "rules").parts[1].args[1], 95)
check("FL11 ingen traktorer", partText(lineFor(sumOut, "tractors")), "spu_guide_flow_sumNoTractors")
local manyGoods = FG.summary(FG.choose(FG.choose(FG.choose(supply, "ingredientMode", "manual"), "ingredients", {item = "HAY", on = true}), "ingredients", {item = "STRAW", on = true}))
check("FL12 valgte varer", partText(lineFor(manyGoods, "goods")), "=Hø + Halm")
check("FL12 oversigt: links", countType(FG.rows(G.new(supply.def, supply.draft, supply.context, {step = "summary"})), "link"), 7)
-- ---- resultat ----
local r = FG.result(FG.choose(FG.choose(policy, "storeAuto", false), "storeBuildingIds", {item = "s1", on = true}))
check("FL13 ikke ny", r.isNew, false)
check("FL13 run-id", r.runId, 1)
check("FL13 retning", r.changes.kind, "outbound")
check("FL13 lager valgt", table.concat(r.changes.storeBuildingIds, ","), "s1")
check("FL13 varer", r.changes.products[1].fillType, "MILK")
check("FL13 lager-resumé bruger navn", (function() for _, l in ipairs(FG.summary(FG.choose(FG.choose(policy, "storeAuto", false), "storeBuildingIds", {item = "s1", on = true}))) do if l.step == "store" then return l.parts[1].text end end end)(), "Silo Nord")
local rAuto = FG.result(FG.choose(chosenStore, "storeAuto", true))
check("FL13 lager auto -> tom", #rAuto.changes.storeBuildingIds, 0)
local hiddenStore = FG.open(newRun("outbound", {storeBuildingIds = {"s2"}}), ctx())
check("FL13 lager usynligt: bevares", table.concat(FG.result(hiddenStore).changes.storeBuildingIds, ","), "s2")
local rNew = FG.result(FG.choose(new, "vehicleIds", {item = "t1", on = true}))
check("FL14 nyt", rNew.isNew, true)
check("FL14 navn", rNew.name, "Flow 3")
check("FL14 traktorer", table.concat(rNew.vehicleIds, ","), "t1")
check("FL14 ingen run-id", rNew.runId, nil)
local added, removed = FG.vehicleChanges({"a", "b"}, {"b", "c"})
check("FL15 tilføjet", table.concat(added, ","), "c")
check("FL15 fjernet", table.concat(removed, ","), "a")
-- ---- forhåndsvisning (til controllerens opslag) ----
local markers = {b1 = {101, 102}, b2 = {201}}
local preview = FG.previewRun(withSource.draft, function(id) return markers[id] or {} end)
check("FL16 kilder -> markører", table.concat(preview.sourceWayPointIds, ","), "201")
local previewIn = FG.previewRun(withTarget.draft, function(id) return markers[id] or {} end)
check("FL16 mål -> markører", table.concat(previewIn.targetWayPointIds, ","), "101,102")
check("FL16 primært mål", previewIn.targetWayPointId, 101)
check("FL16 kladde urørt", withTarget.draft.targetWayPointIds == nil or #withTarget.draft.targetWayPointIds == 0, true)
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)
+1
View File
@@ -104,6 +104,7 @@ local function T(key) return texts[key] or key end
check("G14 label args", G.labelText({key = "greet", args = {"Bo"}}, T), "Hej Bo") check("G14 label args", G.labelText({key = "greet", args = {"Bo"}}, T), "Hej Bo")
check("G14 label tekst", G.labelText({text = "rå"}, T), "rå") check("G14 label tekst", G.labelText({text = "rå"}, T), "rå")
check("G14 manglende args fejler ikke", G.labelText({key = "n"}, T), "%d stk") check("G14 manglende args fejler ikke", G.labelText({key = "n"}, T), "%d stk")
check("G14 prefix", G.labelText({key = "plain", prefix = "Mælk: "}, T), "Mælk: Ingen")
check("G14 nil label", G.labelText(nil, T), "") check("G14 nil label", G.labelText(nil, T), "")
check("G15 linje", G.lineText({step = "crop", title = {key = "title"}, parts = {{key = "plain"}, {key = "n", args = {3}}}}, T), "Afgrøde: Ingen, 3 stk") check("G15 linje", G.lineText({step = "crop", title = {key = "title"}, parts = {{key = "plain"}, {key = "n", args = {3}}}}, T), "Afgrøde: Ingen, 3 stk")
check("G15 linje uden dele", G.lineText({step = "crop", title = {key = "title"}, parts = {}}, T), "Afgrøde: -") check("G15 linje uden dele", G.lineText({step = "crop", title = {key = "title"}, parts = {}}, T), "Afgrøde: -")