feat(gui): guiden "Opsæt mark" — markfanen får oversigt med resumé (klik på en linje = ret ét trin), trin-for-trin-guide på fælles guide-ramme (ADGuide/ADFieldGuide) og egen markflåde-visning (v1.31.0.0)

This commit is contained in:
2026-09-26 20:20:56 +02:00
parent 7cbd8fb878
commit 92af260a79
17 changed files with 1953 additions and 329 deletions
+471
View File
@@ -0,0 +1,471 @@
-- AD Field Guide
-- "Opsæt mark": markguidens trin oven på ADGuide (ren Lua, ingen spil-API). Kladden er en kopi af
-- markens plan (+ faste maskiner), så intet ændres før [Gem]. Alt spil-afhængigt kommer i `context`:
-- {info, sowable = {{name, title}}, mowable = {[name] = true}, straw = {[name] = true} | nil,
-- rigs = {{id, text, role}}, rigIdsByField = {[fieldId] = ids}, fieldSilos = {{id, text}},
-- stores = {{id, text}} | nil (nil = markens vare kendes ikke), storeHidden, fields = {{id, text}}}
-- Rækker er data: {type = "choice"|"toggle"|"check"|"text"|"link", id, title/label, ...}; GUI'et tegner dem.
ADFieldGuide = {}
ADFieldGuide.ORDER = {"field", "crop", "cropChoice", "prep", "care", "machines", "store", "summary"}
ADFieldGuide.SOIL_VALUES = {"auto", "cultivate", "plow", "none"}
ADFieldGuide.STRAW_VALUES = {"chop", "collect", "bale"}
local function L(key, args) return {key = key, args = args} end
local function T(text) return {text = text} end
local YES_NO = {off = L("ui_no"), on = L("ui_yes")}
-- ---- afledte egenskaber ----
local function target(draft)
return draft.plan ~= nil and draft.plan.targetFruit or ""
end
local function isMowable(context, name)
return name ~= nil and name ~= "" and (context.mowable or {})[name] == true
end
-- Ukendt halm-tabel (tom/nil): alle høstafgrøder regnes med halm (som før guiden).
local function hasStraw(context, name)
if context.straw == nil or next(context.straw) == nil then return true end
return name ~= nil and context.straw[name] == true
end
local function info(guide) return guide.context.info or {} end
local function mowOptions(guide)
local t = target(guide.draft)
return info(guide).kind == "mowable" or isMowable(guide.context, t)
end
local function strawOptions(guide)
local t, i = target(guide.draft), info(guide)
if t ~= "" then
if isMowable(guide.context, t) then return false end
return hasStraw(guide.context, t)
end
return i.kind == "harvest" and hasStraw(guide.context, i.fruitName)
end
-- Der høstes (mejetærsker + aflæsningsvogne) nu eller senere.
local function harvestInvolved(guide)
local t = target(guide.draft)
return info(guide).kind == "harvest" or (t ~= "" and not isMowable(guide.context, t))
end
local function prepVisible(guide)
local t, i = target(guide.draft), info(guide)
return i.kind ~= "mowable" or (t ~= "" and t ~= i.fruitName)
end
local function careVisible(guide)
return info(guide).kind ~= "bare" or target(guide.draft) ~= ""
end
-- Ukrudt giver kun mening på afgrøder der ikke slås (samme regel som før).
local function weedVisible(guide)
local t = target(guide.draft)
if t ~= "" then return not isMowable(guide.context, t) end
return info(guide).kind ~= "mowable"
end
local VISIBLE = {
field = function(guide) return guide.draft.pickField == true end,
crop = function(guide) return guide.draft.fieldId ~= nil end,
cropChoice = function(guide) return guide.draft.fieldId ~= nil and (mowOptions(guide) or strawOptions(guide)) end,
prep = function(guide) return guide.draft.fieldId ~= nil and prepVisible(guide) end,
care = function(guide) return guide.draft.fieldId ~= nil and careVisible(guide) end,
machines = function(guide) return guide.draft.fieldId ~= nil end,
store = function(guide) return guide.draft.fieldId ~= nil and guide.context.stores ~= nil and info(guide).kind ~= "bare" end,
summary = function(guide) return guide.draft.fieldId ~= nil end,
}
function ADFieldGuide.isVisible(step, guide)
local rule = VISIBLE[step]
return rule ~= nil and rule(guide) == true
end
ADFieldGuide.DEF = {order = ADFieldGuide.ORDER, isVisible = ADFieldGuide.isVisible}
function ADFieldGuide.stepKey(step) return "spu_guide_field_step_" .. tostring(step) end
function ADFieldGuide.hintKey(step) return "spu_guide_field_hint_" .. tostring(step) end
-- ---- kladde ----
local function isPrepAuto(plan)
return (plan.soil or "auto") == "auto" and plan.mulch ~= true and plan.lime ~= false and plan.roll ~= true
end
local function storeModeOf(ids, fieldSilos)
if #ids == 0 then return "auto" end
local silo = {}
for _, entry in ipairs(fieldSilos or {}) do silo[entry.id] = true end
for _, id in ipairs(ids) do
if not silo[id] then return "manual" end
end
return "fieldSilo"
end
local function draftFrom(field, context)
local plan = ADGuide.deepCopy(field.plan or {})
plan.storeBuildingIds = plan.storeBuildingIds or {}
plan.rigIds = plan.rigIds or {}
local mode = storeModeOf(plan.storeBuildingIds, context.fieldSilos)
return {
fieldId = field.id, plan = plan, originalTarget = plan.targetFruit or "",
prepAuto = isPrepAuto(plan), useDedicated = #plan.rigIds > 0,
storeMode = mode, storeIds = mode == "manual" and ADGuide.deepCopy(plan.storeBuildingIds) or {},
}
end
function ADFieldGuide.open(field, context, options)
return ADGuide.new(ADFieldGuide.DEF, draftFrom(field, context), context, options)
end
-- Guiden åbnet uden valgt mark: trin 1 "Mark".
function ADFieldGuide.openPicker(context)
return ADGuide.new(ADFieldGuide.DEF, {pickField = true}, context, {step = "field"})
end
-- Mark valgt i trin 1: kladden bygges fra markens plan; trin 1 tæller stadig med.
function ADFieldGuide.pickField(guide, field, context)
local draft = draftFrom(field, context)
draft.pickField = true
local picked = ADGuide.withContext(ADGuide.withDraft(guide, draft), context)
return (ADGuide.next(picked))
end
-- ---- valg ----
local PLAN_VALUES = {
targetFruit = true, sow = true, rake = true, mulch = true, lime = true, roll = true,
fertilize = true, weed = true, straw = true, soil = true, rigs = true, unloaders = true,
}
function ADFieldGuide.choose(guide, id, value)
local draft = ADGuide.deepCopy(guide.draft)
if PLAN_VALUES[id] then
draft.plan[id] = value
elseif id == "product" then
draft.plan.product = value and "hay" or "silage"
elseif id == "prepAuto" or id == "useDedicated" then
draft[id] = value == true
elseif id == "rigIds" then
draft.plan.rigIds = ADGuide.toggleItem(draft.plan.rigIds, value.item, value.on)
elseif id == "useFieldSilo" then
draft.storeMode = value and "fieldSilo" or "auto"
elseif id == "storeAuto" then
draft.storeMode = value and "auto" or "manual"
elseif id == "storeBuildingIds" then
draft.storeIds = ADGuide.toggleItem(draft.storeIds, value.item, value.on)
else
return guide
end
return ADGuide.withDraft(guide, draft)
end
-- ---- rækker pr. trin ----
local function numberOptions(from, to)
local options = {}
for value = from, to do table.insert(options, {label = T(tostring(value)), value = value}) end
return options
end
local function choice(id, title, options, value)
return {type = "choice", id = id, title = title, options = options, value = value}
end
local function toggle(id, title, value, off, on)
return {type = "toggle", id = id, title = title, off = off or YES_NO.off, on = on or YES_NO.on, value = value == true}
end
local function rowsField(guide)
local rows = {}
for _, entry in ipairs(guide.context.fields or {}) do
table.insert(rows, {type = "link", id = "field", value = entry.id, label = T(entry.text)})
end
if #rows == 0 then table.insert(rows, {type = "text", label = L("spu_field_scanning")}) end
return rows
end
local function rowsCrop(guide)
local plan = guide.draft.plan
local options = {{label = L("spu_guide_crop_keep"), value = ""}}
for _, fruit in ipairs(guide.context.sowable or {}) do
table.insert(options, {label = T(fruit.title), value = fruit.name})
end
local rows = {choice("targetFruit", L("spu_guide_crop"), options, plan.targetFruit or "")}
if target(guide.draft) == "" and info(guide).kind ~= "mowable" then
table.insert(rows, toggle("sow", L("spu_guide_crop_sowAgain"), plan.sow))
end
return rows
end
local function rowsCropChoice(guide)
local plan, rows = guide.draft.plan, {}
if mowOptions(guide) then
table.insert(rows, toggle("product", L("spu_field_plan"), ADFieldModel.product(plan, "mowable") == "hay",
L("spu_field_plan_silage"), L("spu_field_plan_hay")))
table.insert(rows, toggle("rake", L("spu_field_rake"), plan.rake))
end
if strawOptions(guide) then
local options = {}
for _, value in ipairs(ADFieldGuide.STRAW_VALUES) do
table.insert(options, {label = L("spu_field_straw_" .. value), value = value})
end
table.insert(rows, choice("straw", L("spu_field_straw"), options, plan.straw or "chop"))
end
return rows
end
local function rowsPrep(guide)
local draft, plan = guide.draft, guide.draft.plan
local rows = {choice("prepAuto", L("spu_guide_prep"), {
{label = L("spu_guide_auto"), value = true}, {label = L("spu_guide_manual"), value = false}}, draft.prepAuto)}
if draft.prepAuto then
table.insert(rows, {type = "text", label = L("spu_guide_prep_autoText")})
return rows
end
local soil = {}
for _, value in ipairs(ADFieldGuide.SOIL_VALUES) do
table.insert(soil, {label = L("spu_field_soil_" .. value), value = value})
end
table.insert(rows, choice("soil", L("spu_field_soil"), soil, plan.soil or "auto"))
table.insert(rows, toggle("lime", L("spu_field_lime"), plan.lime ~= false))
table.insert(rows, toggle("mulch", L("spu_field_mulch"), plan.mulch))
if target(draft) ~= "" or plan.sow then
table.insert(rows, toggle("roll", L("spu_field_roll"), plan.roll))
end
return rows
end
local function rowsCare(guide)
local plan = guide.draft.plan
local rows = {toggle("fertilize", L("spu_field_fertilize"), plan.fertilize ~= false)}
if weedVisible(guide) then table.insert(rows, toggle("weed", L("spu_field_weed"), plan.weed ~= false)) end
if not prepVisible(guide) then table.insert(rows, toggle("lime", L("spu_field_lime"), plan.lime ~= false)) end
return rows
end
-- Faste maskiner: kun ledige og dem der allerede er faste her; faste på andre marker skjules med en linje.
local function dedicatedRows(guide)
local draft, rows = guide.draft, {}
local byField = {}
for fieldId, ids in pairs(guide.context.rigIdsByField or {}) do byField[fieldId] = ids end
byField[draft.fieldId] = draft.plan.rigIds
local own = {}
for _, id in ipairs(draft.plan.rigIds) do own[id] = true end
local visible, hidden, fields = ADFieldFleet.visibleForField(guide.context.rigs or {}, draft.fieldId, byField)
local sorted = {}
for _, rig in ipairs(visible) do table.insert(sorted, rig) end
table.sort(sorted, function(a, b)
if (own[a.id] == true) ~= (own[b.id] == true) then return own[a.id] == true end
if (a.role or "") ~= (b.role or "") then return (a.role or "") < (b.role or "") end
return ADFlowView.naturalLess(tostring(a.text), tostring(b.text))
end)
for _, rig in ipairs(sorted) do
table.insert(rows, {type = "check", id = "rigIds", item = rig.id, title = T(rig.text), value = own[rig.id] == true})
end
if #sorted == 0 then table.insert(rows, {type = "text", label = L("spu_field_noRigs")}) end
if hidden > 0 then
local names = {}
for _, fieldId in ipairs(fields) do table.insert(names, "F" .. tostring(fieldId)) end
table.insert(rows, {type = "text", label = L("spu_field_hiddenDedicated", {hidden, table.concat(names, ", ")})})
end
return rows
end
local function rowsMachines(guide)
local draft, plan = guide.draft, guide.draft.plan
local rows = {choice("rigs", L("spu_guide_rigs"), numberOptions(1, ADFieldModel.MAX_RIGS), plan.rigs or 1)}
if harvestInvolved(guide) then
table.insert(rows, choice("unloaders", L("spu_field_unloaders"), numberOptions(1, ADFieldModel.MAX_UNLOADERS), plan.unloaders or 1))
end
table.insert(rows, toggle("useDedicated", L("spu_guide_dedicated"), draft.useDedicated))
if draft.useDedicated then
for _, row in ipairs(dedicatedRows(guide)) do table.insert(rows, row) end
end
return rows
end
local function rowsStore(guide)
local draft, context, rows = guide.draft, guide.context, {}
local silos = context.fieldSilos or {}
if #silos > 0 then
local names = {}
for _, silo in ipairs(silos) do table.insert(names, silo.text) end
table.insert(rows, toggle("useFieldSilo", L("spu_guide_useFieldSilo"), draft.storeMode == "fieldSilo"))
table.insert(rows, {type = "text", label = T(" " .. table.concat(names, ", "))})
if draft.storeMode == "fieldSilo" then return rows end
end
table.insert(rows, choice("storeAuto", L("spu_guide_store"), {
{label = L("spu_guide_auto"), value = true}, {label = L("spu_guide_manual"), value = false}}, draft.storeMode ~= "manual"))
if draft.storeMode ~= "manual" then
table.insert(rows, {type = "text", label = L("spu_guide_store_autoText")})
return rows
end
local chosen = {}
for _, id in ipairs(draft.storeIds) do chosen[id] = true end
for _, store in ipairs(context.stores or {}) do
table.insert(rows, {type = "check", id = "storeBuildingIds", item = store.id, title = T(store.text), value = chosen[store.id] == true})
end
if #(context.stores or {}) == 0 then table.insert(rows, {type = "text", label = L("spu_field_noStore")}) end
if (context.storeHidden or 0) > 0 then
table.insert(rows, {type = "text", label = L("spu_field_storeHidden", {context.storeHidden})})
end
return rows
end
local function rowsSummary(guide)
local rows = {}
for _, line in ipairs(ADFieldGuide.summary(guide)) do
table.insert(rows, {type = "link", id = "jump", value = line.step, line = line})
end
return rows
end
local ROWS = {
field = rowsField, crop = rowsCrop, cropChoice = rowsCropChoice, prep = rowsPrep, care = rowsCare,
machines = rowsMachines, store = rowsStore, summary = rowsSummary,
}
function ADFieldGuide.rows(guide)
local builder = ROWS[guide.step]
return builder ~= nil and builder(guide) or {}
end
-- ---- resumé ----
local function fruitTitle(context, name)
for _, fruit in ipairs(context.sowable or {}) do
if fruit.name == name then return fruit.title end
end
return tostring(name)
end
local function storeText(guide)
local draft, context = guide.draft, guide.context
if draft.storeMode == "fieldSilo" then return {L("spu_guide_sum_fieldSilo")} end
if draft.storeMode ~= "manual" or #draft.storeIds == 0 then return {L("spu_guide_sum_auto")} end
local names = {}
for _, id in ipairs(draft.storeIds) do
for _, store in ipairs(context.stores or {}) do
if store.id == id then table.insert(names, store.text) end
end
end
if #names == 0 or #names > 2 or #names ~= #draft.storeIds then
return {L("spu_guide_sum_stores", {#draft.storeIds})}
end
return {T(table.concat(names, " + "))}
end
local SUMMARY = {
crop = function(guide)
local plan, t = guide.draft.plan, target(guide.draft)
if t ~= "" then return {L("spu_guide_sum_target", {fruitTitle(guide.context, t)})} end
local parts = {L("spu_guide_crop_keep")}
if plan.sow and info(guide).kind ~= "mowable" then table.insert(parts, L("spu_guide_sum_sowAgain")) end
return parts
end,
cropChoice = function(guide)
local plan, parts = guide.draft.plan, {}
if mowOptions(guide) then
table.insert(parts, L(ADFieldModel.product(plan, "mowable") == "hay" and "spu_field_plan_hay" or "spu_field_plan_silage"))
table.insert(parts, L(plan.rake and "spu_guide_sum_rake" or "spu_guide_sum_noRake"))
end
if strawOptions(guide) then table.insert(parts, L("spu_guide_sum_straw_" .. (plan.straw or "chop"))) end
return parts
end,
prep = function(guide)
local plan = guide.draft.plan
if guide.draft.prepAuto then return {L("spu_guide_sum_auto")} end
local parts = {L("spu_field_soil_" .. (plan.soil or "auto"))}
if plan.lime ~= false then table.insert(parts, L("spu_guide_sum_lime")) end
if plan.mulch then table.insert(parts, L("spu_guide_sum_mulch")) end
if plan.roll and (plan.sow or target(guide.draft) ~= "") then table.insert(parts, L("spu_guide_sum_roll")) end
return parts
end,
care = function(guide)
local plan, parts = guide.draft.plan, {}
if plan.fertilize ~= false then table.insert(parts, L("spu_guide_sum_fert")) end
if weedVisible(guide) and plan.weed ~= false then table.insert(parts, L("spu_guide_sum_weed")) end
if not prepVisible(guide) and plan.lime ~= false then table.insert(parts, L("spu_guide_sum_lime")) end
if #parts == 0 then parts = {L("spu_guide_sum_noCare")} end
return parts
end,
machines = function(guide)
local draft, plan = guide.draft, guide.draft.plan
local parts = {}
if draft.useDedicated and #plan.rigIds > 0 then
table.insert(parts, L("spu_guide_sum_dedicated", {#plan.rigIds}))
else
table.insert(parts, L("spu_guide_sum_freeRigs"))
end
table.insert(parts, L("spu_guide_sum_rigs", {plan.rigs or 1}))
if harvestInvolved(guide) then table.insert(parts, L("spu_guide_sum_unloaders", {plan.unloaders or 1})) end
return parts
end,
store = storeText,
}
-- Resumé-linjer {step, title, parts} for de synlige trin (ikke "Mark" og "Oversigt").
function ADFieldGuide.summary(guide)
local lines = {}
for _, step in ipairs(ADGuide.visibleSteps(guide)) do
local build = SUMMARY[step]
if build ~= nil then
table.insert(lines, {step = step, title = L(ADFieldGuide.stepKey(step)), parts = build(guide)})
end
end
return lines
end
-- ---- gem ----
-- start: true = [Kør automatisk], false = [Gem uden at starte], nil = [Ret] (tilstanden bevares).
-- Mål-afgrøde kører altid automatisk (ADFieldModel.setTarget).
function ADFieldGuide.result(guide, start)
local draft = guide.draft
local plan = ADGuide.deepCopy(draft.plan)
local t = plan.targetFruit or ""
if t ~= "" then
plan.mode = "auto"
elseif start ~= nil then
plan.mode = start and "auto" or "manual"
end
if draft.prepAuto and ADFieldGuide.isVisible("prep", guide) then
plan.soil, plan.mulch, plan.lime, plan.roll = "auto", false, true, false
end
if ADFieldGuide.isVisible("store", guide) then
if draft.storeMode == "fieldSilo" then
local ids = {}
for _, silo in ipairs(guide.context.fieldSilos or {}) do table.insert(ids, silo.id) end
plan.storeBuildingIds = ids
elseif draft.storeMode == "manual" then
plan.storeBuildingIds = ADGuide.deepCopy(draft.storeIds)
else
plan.storeBuildingIds = {}
end
end
local rigIds = draft.useDedicated and ADGuide.deepCopy(plan.rigIds) or {}
plan.rigIds = nil
return {fieldId = draft.fieldId, targetFruit = t, targetChanged = t ~= (draft.originalTarget or ""), plan = plan, rigIds = rigIds}
end
-- Faste maskiner for alle marker efter gem: marken får rigIds, og de fjernes fra andre marker.
function ADFieldGuide.applyRigs(rigIdsByField, fieldId, rigIds)
local mine = {}
for _, id in ipairs(rigIds or {}) do mine[id] = true end
local result = {}
for otherId, ids in pairs(rigIdsByField or {}) do
if otherId ~= fieldId then
local copy = {}
for _, id in ipairs(ids or {}) do if not mine[id] then table.insert(copy, id) end end
result[otherId] = copy
end
end
result[fieldId] = ADGuide.deepCopy(rigIds or {})
return result
end
+162
View File
@@ -0,0 +1,162 @@
-- AD Guide
-- Fælles guide-motor (ren Lua, ingen spil-API) for "Opsæt mark" og "Opsæt flow": ét trin ad gangen,
-- trin der ikke er relevante springes over, tælling "Trin 3 af 7" over de synlige trin, og [Ret] der
-- åbner ét enkelt trin. Motoren kender ikke marker eller flows; en trin-definition giver rækkefølgen og
-- hvornår et trin vises:
-- def = {order = {stepId, ...}, isVisible = function(stepId, guide) -> bool}
-- Guiden er en tabel {def, draft, context, step, single, returnTo, snapshot}; alle funktioner returnerer
-- en ny guide (kladden kopieres), så [Annullér] aldrig har ændret noget.
ADGuide = {}
-- Kopi af rene data (tabeller uden cykler); funktioner og tal/tekster deles.
function ADGuide.deepCopy(value)
if type(value) ~= "table" then return value end
local copy = {}
for key, item in pairs(value) do copy[key] = ADGuide.deepCopy(item) end
return copy
end
-- Ny liste med item tilføjet (on) eller fjernet; aldrig dubletter.
function ADGuide.toggleItem(list, item, on)
local result, found = {}, false
for _, value in ipairs(list or {}) do
if value == item then
found = true
if on then table.insert(result, value) end
else
table.insert(result, value)
end
end
if on and not found then table.insert(result, item) end
return result
end
local function clone(guide, changes)
local copy = {}
for key, value in pairs(guide) do copy[key] = value end
for key, value in pairs(changes or {}) do
if value == false and (key == "snapshot") then copy[key] = nil else copy[key] = value end
end
return copy
end
local function isVisible(guide, step)
return guide.def.isVisible == nil or guide.def.isVisible(step, guide) == true
end
function ADGuide.visibleSteps(guide)
local steps = {}
for _, step in ipairs(guide.def.order) do
if isVisible(guide, step) then table.insert(steps, step) end
end
return steps
end
local function orderIndex(guide, step)
for index, id in ipairs(guide.def.order) do
if id == step then return index end
end
return 0
end
-- Første synlige trin efter (dir = 1) eller før (dir = -1) det aktuelle i rækkefølgen.
local function neighbour(guide, dir)
local order = guide.def.order
local index = orderIndex(guide, guide.step) + dir
while index >= 1 and index <= #order do
if isVisible(guide, order[index]) then return order[index] end
index = index + dir
end
return nil
end
-- options = {step = id, single = bool, returnTo = "close"|"summary"}
function ADGuide.new(def, draft, context, options)
options = options or {}
local guide = {def = def, draft = draft, context = context, step = options.step,
single = options.single == true, returnTo = options.returnTo or "close"}
if guide.step == nil then
guide.step = ADGuide.visibleSteps(guide)[1] or def.order[1]
end
if guide.single then guide.snapshot = ADGuide.deepCopy(draft) end
return guide
end
-- index (nil hvis det aktuelle trin ikke er synligt), total synlige trin.
function ADGuide.position(guide)
local steps = ADGuide.visibleSteps(guide)
local index = nil
for position, step in ipairs(steps) do
if step == guide.step then index = position end
end
return index, #steps
end
-- Sidste trin: ingen synlige efter, eller et [Ret]-trin der gemmer direkte.
function ADGuide.isLast(guide)
if guide.single and guide.returnTo == "close" then return true end
if guide.single then return false end
return neighbour(guide, 1) == nil
end
-- Næste: event "done" når der gemmes (sidste trin, eller [Ret] fra fanens oversigt).
function ADGuide.next(guide)
if guide.single then
if guide.returnTo == "summary" then
return clone(guide, {step = "summary", single = false, snapshot = false}), nil
end
return guide, "done"
end
local step = neighbour(guide, 1)
if step == nil then return guide, "done" end
return clone(guide, {step = step}), nil
end
-- Tilbage: event "cancel" når guiden lukkes (første trin, eller [Ret] fra fanens oversigt).
-- I et [Ret]-trin fra guidens eget oversigts-trin fortrydes trinnets ændringer.
function ADGuide.back(guide)
if guide.single then
if guide.returnTo == "summary" then
return clone(guide, {step = "summary", single = false, draft = guide.snapshot or guide.draft, snapshot = false}), nil
end
return guide, "cancel"
end
local step = neighbour(guide, -1)
if step == nil then return guide, "cancel" end
return clone(guide, {step = step}), nil
end
-- [Ret]: åbn kun ét trin. returnTo "summary" (guidens oversigt) eller "close" (gem og luk).
function ADGuide.jump(guide, step, returnTo)
return clone(guide, {step = step, single = true, returnTo = returnTo or "close", snapshot = ADGuide.deepCopy(guide.draft)})
end
function ADGuide.withDraft(guide, draft)
return clone(guide, {draft = draft})
end
function ADGuide.withContext(guide, context)
return clone(guide, {context = context})
end
-- label = {key = "...", args = {...}} (oversættes) eller {text = "..."} (færdig tekst).
function ADGuide.labelText(label, getText)
if type(label) ~= "table" then return "" end
if label.text ~= nil then return tostring(label.text) end
local template = getText(label.key)
if label.args == nil or #label.args == 0 then return tostring(template) end
local unpackFn = table.unpack or unpack
local ok, text = pcall(string.format, template, unpackFn(label.args))
return ok and text or tostring(template)
end
-- Resumé-linje {step, title, parts} -> "Afgrøde: Behold det der gror, så igen".
function ADGuide.lineText(line, getText)
local parts = {}
for _, part in ipairs(line.parts or {}) do
local text = ADGuide.labelText(part, getText)
if text ~= "" then table.insert(parts, text) end
end
return ADGuide.labelText(line.title, getText) .. ": " .. (#parts > 0 and table.concat(parts, ", ") or "-")
end
+2
View File
@@ -25,6 +25,8 @@ if ADSmartPickup.dir ~= nil and source ~= nil then
source(ADSmartPickup.dir .. "gui/SmartPickupStorageTab.lua")
source(ADSmartPickup.dir .. "gui/SmartPickupFlowTab.lua")
source(ADSmartPickup.dir .. "gui/SmartPickupFieldTab.lua")
source(ADSmartPickup.dir .. "gui/SmartPickupGuide.lua")
source(ADSmartPickup.dir .. "gui/SmartPickupFieldGuide.lua")
end
-- Markøren er dér hvor trækkerens næse stopper; læssetriggeren ligger et
@@ -0,0 +1,236 @@
-- Smart Pickup — guiden "Opsæt mark" og fanen Markers knapper
-- Henter det spil-afhængige (afgrøder der kan sås, slåafgrøder, halm, maskiner, marksiloer, siloer i nærheden)
-- som `context` til ADFieldGuide og gemmer guidens resultat via ADFieldJobs. Selve trin-skærmen er fælles
-- (gui/SmartPickupGuide.lua). Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua).
-- lagervalget viser de nærmeste siloer (plus valgte, marksiloer og gæringssiloer)
SmartPickupFrame.FIELD_STORE_ROWS = 8
function SmartPickupFrame:initFieldButtons(i18n)
self.fieldStartButtonInfo = {inputAction = InputAction.MENU_ACTIVATE, text = i18n:getText("spu_field_button_start"), callback = function() self:onClickFieldStartStop() end}
self.fieldSetupButtonInfo = {inputAction = InputAction.MENU_EXTRA_1, text = i18n:getText("spu_guide_field_open"), callback = function() self:onClickFieldSetup() end}
self.fieldFleetButtonInfo = {inputAction = InputAction.MENU_EXTRA_2, text = i18n:getText("spu_guide_fleet_open"), callback = function() self:onClickFieldFleet(true) end}
self.fieldFleetCloseButtonInfo = {inputAction = InputAction.MENU_EXTRA_2, text = i18n:getText("spu_guide_fleet_close"), callback = function() self:onClickFieldFleet(false) end}
self.fieldButtons = {self.backButtonInfo, self.fieldStartButtonInfo, self.fieldSetupButtonInfo, self.fieldFleetButtonInfo}
self.fieldFleetButtons = {self.backButtonInfo, self.fieldFleetCloseButtonInfo}
end
function SmartPickupFrame:getFieldButtons()
return self.fieldView == "fleet" and self.fieldFleetButtons or self.fieldButtons
end
-- ---- context til ADFieldGuide ----
-- {[fieldId] = rigIds} for alle marker
function SmartPickupFrame.rigIdsByField()
local byField = {}
for fieldId, field in pairs(ADFieldJobs.state.fields) do byField[fieldId] = field.plan ~= nil and field.plan.rigIds or {} end
return byField
end
local function fruitTypes()
return g_fruitTypeManager ~= nil and g_fruitTypeManager.fruitTypes or {}
end
-- Afgrøder der kan sås, slåafgrøder (navne) og afgrøder med halm (skår efter mejetærskeren).
function SmartPickupFrame:fruitContext()
local sowable, straw, mowable = {}, {}, {}
for _, fruit in ipairs(fruitTypes()) do
if fruit.allowsSeeding ~= false and fruit.fillType ~= nil and fruit.name ~= nil then
table.insert(sowable, {name = fruit.name, title = tostring(fruit.fillType.title or fruit.name)})
end
local ok, hasStraw = pcall(function() return fruit.hasWindrow == true or fruit.windrowName ~= nil end)
if ok and hasStraw and fruit.name ~= nil then straw[fruit.name] = true end
end
if ADFields ~= nil and g_fruitTypeManager ~= nil then
for index in pairs(ADFields.getMowable()) do
local fruit = g_fruitTypeManager:getFruitTypeByIndex(index)
if fruit ~= nil and fruit.name ~= nil then mowable[fruit.name] = true end
end
end
return sowable, mowable, straw
end
-- Markens vare ud fra kladdens plan (ensilage -> skår, hø -> tørskår, høst -> afgrøden); nil på bar mark.
local function fieldFillType(info, plan)
if info.kind == "mowable" then
return ADFieldModel.product(plan, "mowable") == "hay" and info.hay or info.windrow
end
if info.kind == "harvest" then return info.harvestFillType end
return nil
end
-- Siloer der tager markens vare: marksilo først, gæringssiloer, så de nærmeste (samme regel som før).
function SmartPickupFrame:fieldStoreContext(adEnv, info, plan, chosenIds)
local fillType = fieldFillType(info, plan)
local farmId = g_currentMission:getFarmId()
local fieldSilos, silosById = {}, {}
for _, building in ipairs(ADFields.fieldSilos(adEnv, farmId, info)) do
local markers = ADBuildings.getMarkers ~= nil and ADBuildings.getMarkers(building, "unload") or {}
table.insert(fieldSilos, {id = building.id, name = tostring(building.name),
text = markers[1] ~= nil and tostring(markers[1].name) or tostring(building.name)})
silosById[building.id] = true
end
if fillType == nil then return nil, 0, fieldSilos end
local chosen = {}
for _, id in ipairs(chosenIds or {}) do chosen[id] = true end
local fermenterIds = {}
if info.kind == "mowable" and ADFieldModel.product(plan, "mowable") == "silage" then
for _, building in ipairs(ADFieldJobs.getFermenters(adEnv, farmId, fillType, info.x, info.z)) do fermenterIds[building.id] = true end
end
local entries = {}
for _, building in ipairs(ADBuildings.list(adEnv, farmId)) do
local isSilo = building.kind == "silo" and ADRunsController.canStoreAt(adEnv, building, {{fillType = fillType, isPallet = false}})
if chosen[building.id] or isSilo or fermenterIds[building.id] then
local distance = nil
local node = building.placeable ~= nil and building.placeable.rootNode or nil
if node ~= nil and info.x ~= nil then
local bx, _, bz = getWorldTranslation(node)
distance = math.sqrt((bx - info.x) ^ 2 + (bz - info.z) ^ 2)
end
table.insert(entries, {id = building.id, building = building, distance = distance, chosen = chosen[building.id] == true,
fieldSilo = silosById[building.id] == true, fermenter = fermenterIds[building.id] == true})
end
end
local shown, hidden = ADFieldView.storeRows(entries, SmartPickupFrame.FIELD_STORE_ROWS)
local stores = {}
for _, entry in ipairs(shown) do
local building = entry.building
local markers = ADBuildings.getMarkers ~= nil and ADBuildings.getMarkers(building, "unload") or {}
-- det der skelner rækkerne først (rækken afkorter bagfra): markør, afstand, art — så bygningens navn
local label = (markers[1] ~= nil and tostring(markers[1].name) or tostring(building.name)) .. ", " .. ADFieldView.distanceText(entry.distance)
if entry.fieldSilo then label = label .. " (" .. self.i18n:getText("spu_field_fieldSilo") .. ")" end
if entry.fermenter then label = label .. " (" .. self.i18n:getText("spu_field_fermenter") .. ")" end
if markers[1] ~= nil then label = label .. " — " .. tostring(building.name) end
table.insert(stores, {id = building.id, text = label})
end
return stores, hidden, fieldSilos
end
-- Markerne til trin 1 ("F22 — Hvede — Venter").
function SmartPickupFrame:fieldPickList()
local fields = {}
for _, row in ipairs(ADFieldView.rows(ADFields.list(), ADFieldJobs.state.fields)) do
local crop = ADFieldView.cropText(row.info, function(key) return self.i18n:getText(key) end)
table.insert(fields, {id = row.info.id, text = string.format("F%d — %s — %s", row.info.id, crop, self:fieldStatusText(row.field, row.info, true))})
end
return fields
end
-- context for en mark (info) med kladdens plan; info nil = trin 1 uden valgt mark.
function SmartPickupFrame:fieldGuideContext(adEnv, info, plan, chosenStoreIds)
local sowable, mowable, straw = self:fruitContext()
local context = {info = info or {}, sowable = sowable, mowable = mowable, straw = straw,
rigIdsByField = SmartPickupFrame.rigIdsByField(), fieldSilos = {}, storeHidden = 0, fields = self:fieldPickList(), rigs = {}}
for _, rig in ipairs(ADFieldJobs.getRigs(adEnv)) do
table.insert(context.rigs, {id = rig.id, role = rig.role, text = string.format("%s — %s", tostring(rig.label), self:roleText(rig.role))})
end
if info ~= nil then
context.stores, context.storeHidden, context.fieldSilos = self:fieldStoreContext(adEnv, info, plan or {}, chosenStoreIds)
end
return context
end
-- ---- hooks til den fælles guide-skærm ----
local function chosenStoreIds(guide)
local ids = {}
for _, id in ipairs(guide.draft.storeIds or {}) do table.insert(ids, id) end
for _, id in ipairs(guide.draft.plan ~= nil and guide.draft.plan.storeBuildingIds or {}) do table.insert(ids, id) end
return ids
end
SmartPickupFrame.GUIDE_HOOKS = SmartPickupFrame.GUIDE_HOOKS or {}
SmartPickupFrame.GUIDE_HOOKS.field = {
model = ADFieldGuide,
tab = SmartPickupFrame.TAB_FIELDS,
cannotAdvanceKey = "spu_guide_pickField",
heading = function(frame, guide)
if guide.draft.fieldId == nil then return frame.i18n:getText("spu_guide_field_titleNone") end
return string.format(frame.i18n:getText("spu_guide_field_title"), guide.draft.fieldId)
end,
context = function(frame, adEnv, guide)
local fieldId = guide.draft.fieldId
if fieldId == nil then return frame:fieldGuideContext(adEnv, nil) end
local info = ADFields.getInfo(fieldId)
-- marken er solgt/bygget over mens guiden var åben
if info == nil or info.cultivable == false then return nil end
return frame:fieldGuideContext(adEnv, info, guide.draft.plan, chosenStoreIds(guide))
end,
choose = function(frame, id, value)
if id ~= "field" then return false end
local info = ADFields.getInfo(value)
if info == nil then return true end
local field = ADFieldJobs.getField(value)
frame.selectedFieldId = value
frame.statusOverride = nil
frame.guide = ADFieldGuide.pickField(frame.guide, field, frame:fieldGuideContext(frame.getAdEnv(), info, field.plan, field.plan.storeBuildingIds))
return true
end,
canAdvance = function(guide) return guide.draft.fieldId ~= nil end,
startKey = function() return "spu_guide_runAuto" end,
-- mål-afgrøde kører altid automatisk: ingen "Gem uden at starte"
showSaveOnly = function(guide) return (guide.draft.plan.targetFruit or "") == "" end,
save = function(frame, guide, start) frame:saveFieldGuide(guide, start) end,
afterRows = function(frame, adEnv, guide)
if guide.step ~= "summary" or guide.draft.fieldId == nil then return end
local info = guide.context.info
if info ~= nil and info.markerId == nil then
frame:addTextRow(" " .. string.format(frame.i18n:getText("spu_guide_noMarker"), guide.draft.fieldId))
end
end,
}
-- Gem: mål-afgrøde (hvis ændret), planen, og faste maskiner (fjernes fra andre marker).
function SmartPickupFrame:saveFieldGuide(guide, start)
local result = ADFieldGuide.result(guide, start)
if result.fieldId == nil then return end
if result.targetChanged then ADFieldJobs.setTarget(result.fieldId, result.targetFruit) end
ADFieldJobs.setPlan(result.fieldId, result.plan)
local byField = SmartPickupFrame.rigIdsByField()
for fieldId, ids in pairs(ADFieldGuide.applyRigs(byField, result.fieldId, result.rigIds)) do
if table.concat(byField[fieldId] or {}, " ") ~= table.concat(ids, " ") then
ADFieldJobs.setPlan(fieldId, {rigIds = ids})
end
end
ADFieldJobs.log(result.fieldId, "guide gemt: mode=%s mål=%s faste=%s lager=%s", tostring(result.plan.mode), result.targetFruit ~= "" and result.targetFruit or "-",
table.concat(result.rigIds, " "), table.concat(result.plan.storeBuildingIds or {}, " "))
self.selectedFieldId = result.fieldId
self.statusOverride = string.format(self.i18n:getText("spu_guide_saved"), result.fieldId)
self.statusOverrideTicks = SmartPickupFrame.STATUS_OVERRIDE_TICKS
end
-- ---- knapper og [Ret] ----
-- [Opsæt mark]: den valgte mark med de nuværende valg, ellers trin 1 (vælg mark).
function SmartPickupFrame:onClickFieldSetup()
self:safe(function(frame)
local adEnv = frame.getAdEnv()
if adEnv == nil then return end
local info = frame.selectedFieldId ~= nil and ADFields.getInfo(frame.selectedFieldId) or nil
frame.fieldView = nil
if info == nil then
frame:openGuide("field", ADFieldGuide.openPicker(frame:fieldGuideContext(adEnv, nil)))
return
end
local field = ADFieldJobs.getField(info.id)
frame:openGuide("field", ADFieldGuide.open(field, frame:fieldGuideContext(adEnv, info, field.plan, field.plan.storeBuildingIds)))
end)
end
-- [Ret] på en resumé-linje i fanens oversigt: kun det ene trin; [Gem] gemmer og går tilbage.
function SmartPickupFrame:openFieldStep(fieldId, step)
local adEnv = self.getAdEnv()
local info = ADFields.getInfo(fieldId)
if adEnv == nil or info == nil then return end
local field = ADFieldJobs.getField(fieldId)
local context = self:fieldGuideContext(adEnv, info, field.plan, field.plan.storeBuildingIds)
self:openGuide("field", ADFieldGuide.open(field, context, {step = step, single = true, returnTo = "close"}))
end
function SmartPickupFrame:onClickFieldFleet(show)
self:safe(function(frame)
frame.fieldView = show and "fleet" or nil
frame:requestRebuild()
end)
end
+41 -278
View File
@@ -1,16 +1,12 @@
-- Smart Pickup — fanen Marker
-- Tabel over alle gårdens marker (Mark · Afgrøde · Plan · Kørsel · Status · Maskiner); klik på en række
-- viser markens detaljer under tabellen: plan efter markens art (slåafgrøde, høst, bar), maskiner pr. trin,
-- aflæsningsvogne, efter høst, såning, pleje, tving lager, jordtilstand. Nederst markflåden med flueben.
-- Al logik ligger i ADFieldJobs/ADFieldWork/ADFieldModel/ADFieldView.
-- Oversigt: tabel over alle gårdens marker (Mark · Afgrøde · Plan · Kørsel · Status · Maskiner); klik på en
-- række viser markens resumé (én linje pr. emne — klik på en linje for at rette netop det trin), hele
-- statusteksten og markøren. [Opsæt mark] åbner guiden (gui/SmartPickupFieldGuide.lua + SmartPickupGuide.lua),
-- [Markflåde] viser den fælles markflåde. Al logik ligger i ADFieldJobs/ADFieldModel/ADFieldView/ADFieldGuide.
-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua).
SmartPickupFrame.FIELD_ROW_KINDS = {
fieldProduct = true, fieldMode = true, fieldRake = true, fieldStore = true, fleetVehicle = true,
fieldRigs = true, fieldUnloaders = true, fieldMulch = true, fieldLime = true, fieldSoil = true,
fieldSow = true, fieldSowFruit = true, fieldRoll = true, fieldFertilize = true, fieldWeed = true,
fieldTarget = true, fleetMode = true, fieldStraw = true, fieldRig = true,
}
-- Markflådens rækker (vehiclePrefab/ingredientPrefab) routes til onClickFieldRow.
SmartPickupFrame.FIELD_ROW_KINDS = {fleetVehicle = true, fleetMode = true}
SmartPickupFrame.ROLE_KEYS = {
mower = "spu_field_role_mower", tedder = "spu_field_role_tedder", rake = "spu_field_role_rake",
collector = "spu_field_role_collector", harvester = "spu_field_role_harvester", unloader = "spu_field_role_unloader",
@@ -19,15 +15,6 @@ SmartPickupFrame.ROLE_KEYS = {
weeder = "spu_field_role_weeder", fertilizer = "spu_field_role_fertilizer",
baler = "spu_field_role_baler", baleCollector = "spu_field_role_baleCollector",
}
SmartPickupFrame.SOIL_VALUES = {"auto", "cultivate", "plow", "none"}
-- simple Ja/Nej-valg på planen: rækketype -> plan-felt
SmartPickupFrame.PLAN_TOGGLES = {
fieldRake = "rake", fieldMulch = "mulch", fieldLime = "lime", fieldSow = "sow", fieldRoll = "roll",
fieldFertilize = "fertilize", fieldWeed = "weed",
}
-- valg med flere muligheder: rækketype -> plan-felt
SmartPickupFrame.PLAN_CHOICES = {fieldRigs = "rigs", fieldUnloaders = "unloaders", fieldSoil = "soil", fieldSowFruit = "sowFruit", fieldStraw = "straw"}
SmartPickupFrame.STRAW_VALUES = {"chop", "collect", "bale"}
-- ---- tekster ----
@@ -95,18 +82,6 @@ function SmartPickupFrame:fieldStatusText(field, info, short)
return text
end
-- Hvad moden gør med marken frem mod mål-afgrøden (én linje under valget).
function SmartPickupFrame:targetSummary(field, info)
local target = self:fruitTitle(field.plan.targetFruit)
if info.kind == "bare" then
return string.format(self.i18n:getText("spu_field_target_bare"), target)
end
if info.fruitName == field.plan.targetFruit and info.kind == "mowable" then
return string.format(self.i18n:getText("spu_field_target_keep"), target)
end
return string.format(self.i18n:getText("spu_field_target_switch"), tostring(info.cropTitle or "-"), target)
end
function SmartPickupFrame:fruitTitle(name)
local fruit = g_fruitTypeManager ~= nil and name ~= nil and name ~= "" and g_fruitTypeManager:getFruitTypeByName(name) or nil
return fruit ~= nil and fruit.fillType ~= nil and tostring(fruit.fillType.title) or tostring(name)
@@ -139,47 +114,15 @@ function SmartPickupFrame:fieldCells(row)
}
end
-- ---- rækker ----
-- Valg med flere muligheder (klon af productPrefab): values[i] er værdien for texts[i].
function SmartPickupFrame:addChoiceRow(kind, title, texts, values, current)
local row = self:addRow(self.productPrefab, {kind = kind, values = values})
local option = row:getDescendantByName("option")
option:setTexts(texts)
local index = 1
for position, value in ipairs(values) do
if value == current then index = position end
end
option:setState(index, false)
row:getDescendantByName("title"):setText(title)
return option
end
local function numberTexts(from, to)
local texts, values = {}, {}
for value = from, to do
table.insert(texts, tostring(value))
table.insert(values, value)
end
return texts, values
end
-- Afgrøder der kan sås (første valg: samme som den høstede / ingen mål-afgrøde).
function SmartPickupFrame:sowFruitChoices(firstKey)
local texts, values = {self.i18n:getText(firstKey or "spu_field_sowSame")}, {""}
for _, fruit in ipairs(g_fruitTypeManager ~= nil and g_fruitTypeManager.fruitTypes or {}) do
if fruit.allowsSeeding ~= false and fruit.fillType ~= nil and fruit.name ~= nil then
table.insert(texts, tostring(fruit.fillType.title or fruit.name))
table.insert(values, fruit.name)
end
end
return texts, values
end
-- ---- opbygning ----
function SmartPickupFrame:fillFieldRows(adEnv)
self.fieldRows = {}
if self:hasGuideOn(SmartPickupFrame.TAB_FIELDS) and self:fillGuideRows(adEnv) then return end
if self.fieldView == "fleet" then
self:fillFleetRows(adEnv)
return
end
local i18n = self.i18n
local infos = ADFields.list()
if #infos == 0 then
@@ -201,161 +144,48 @@ function SmartPickupFrame:fillFieldRows(adEnv)
end
local selected = self.selectedFieldId ~= nil and ADFields.getInfo(self.selectedFieldId) or nil
if selected ~= nil then
self:fillFieldDetail(adEnv, selected)
self:fillFieldOverview(adEnv, selected)
end
self:fillFleetRows(adEnv)
end
-- lagervalget viser de nærmeste siloer (plus valgte, marksiloer og gæringssiloer)
SmartPickupFrame.FIELD_STORE_ROWS = 8
function SmartPickupFrame:fillFieldDetail(adEnv, info)
-- Den valgte marks resumé: én klikbar linje pr. emne ([Ret] åbner kun det trin), hele status, markør og
-- advarsel om marksilo der ikke tømmes hjem.
function SmartPickupFrame:fillFieldOverview(adEnv, info)
local i18n = self.i18n
local field = ADFieldJobs.getField(info.id)
local plan = field.plan
local yesNo = {i18n:getText("ui_no"), i18n:getText("ui_yes")}
local crop = ADFieldView.cropText(info, function(key) return i18n:getText(key) end)
self:addTextRow(string.format(i18n:getText("spu_field_section"), info.id, crop))
-- hele statusteksten (tabellens celle afkorter den)
self:addTextRow(" " .. i18n:getText("spu_guide_sum_hint"))
local context = self:fieldGuideContext(adEnv, info, field.plan, field.plan.storeBuildingIds)
local guide = ADFieldGuide.open(field, context)
local getText = function(key) return i18n:getText(key) end
local fieldId = info.id
for _, line in ipairs(ADFieldGuide.summary(guide)) do
local step = line.step
self:addLinkRow(" " .. ADGuide.lineText(line, getText) .. i18n:getText("spu_guide_editSuffix"), function(frame)
frame:openFieldStep(fieldId, step)
end)
end
self:addTextRow(" " .. string.format(i18n:getText("spu_field_statusFull"), self:fieldStatusText(field, info)))
local targetTexts, targetValues = self:sowFruitChoices("spu_field_target_none")
self:addChoiceRow("fieldTarget", i18n:getText("spu_field_target"), targetTexts, targetValues, plan.targetFruit or "")
local hasTarget = plan.targetFruit ~= nil and plan.targetFruit ~= ""
if hasTarget then
self:addTextRow(" " .. self:targetSummary(field, info))
else
self:addToggleRow("fieldMode", i18n:getText("spu_field_mode"), {i18n:getText("spu_field_mode_manual"), i18n:getText("spu_field_mode_auto")}, plan.mode == "auto")
end
local rigTexts, rigValues = numberTexts(1, ADFieldModel.MAX_RIGS)
self:addChoiceRow("fieldRigs", i18n:getText("spu_field_rigs"), rigTexts, rigValues, plan.rigs or 1)
-- græs-valgene (ensilage/hø, riv) også når målet er en slåafgrøde (fx græs på en kornmark)
local targetMowable = false
if hasTarget and ADFields ~= nil and g_fruitTypeManager ~= nil then
local targetFruit = g_fruitTypeManager:getFruitTypeByName(plan.targetFruit)
targetMowable = targetFruit ~= nil and ADFields.getMowable()[targetFruit.index] ~= nil
end
if info.kind == "mowable" or targetMowable then
local isHay = ADFieldModel.product(plan, "mowable") == "hay"
self:addToggleRow("fieldProduct", i18n:getText("spu_field_plan"), {i18n:getText("spu_field_plan_silage"), i18n:getText("spu_field_plan_hay")}, isHay)
self:addToggleRow("fieldRake", i18n:getText("spu_field_rake"), yesNo, plan.rake == true)
end
-- klargøring: høst-/bare marker, og græsmarker der skal skifte til en anden mål-afgrøde
if info.kind ~= "mowable" or (hasTarget and info.fruitName ~= plan.targetFruit) then
if info.kind == "harvest" then
local unloaderTexts, unloaderValues = numberTexts(1, ADFieldModel.MAX_UNLOADERS)
self:addChoiceRow("fieldUnloaders", i18n:getText("spu_field_unloaders"), unloaderTexts, unloaderValues, plan.unloaders or 1)
local strawTexts = {}
for _, value in ipairs(SmartPickupFrame.STRAW_VALUES) do table.insert(strawTexts, i18n:getText("spu_field_straw_" .. value)) end
self:addChoiceRow("fieldStraw", i18n:getText("spu_field_straw"), strawTexts, SmartPickupFrame.STRAW_VALUES, plan.straw or "chop")
end
self:addTextRow(i18n:getText(info.kind == "harvest" and "spu_field_section_after" or "spu_field_section_prep"))
self:addToggleRow("fieldMulch", i18n:getText("spu_field_mulch"), yesNo, plan.mulch == true)
local soilTexts = {}
for _, value in ipairs(SmartPickupFrame.SOIL_VALUES) do table.insert(soilTexts, i18n:getText("spu_field_soil_" .. value)) end
self:addChoiceRow("fieldSoil", i18n:getText("spu_field_soil"), soilTexts, SmartPickupFrame.SOIL_VALUES, plan.soil or "auto")
if not hasTarget then
self:addToggleRow("fieldSow", i18n:getText("spu_field_sow"), yesNo, plan.sow == true)
if plan.sow then
local fruitTexts, fruitValues = self:sowFruitChoices()
self:addChoiceRow("fieldSowFruit", i18n:getText("spu_field_sowFruit"), fruitTexts, fruitValues, plan.sowFruit or "")
end
end
if hasTarget or plan.sow then
self:addToggleRow("fieldRoll", i18n:getText("spu_field_roll"), yesNo, plan.roll == true)
end
end
self:addTextRow(i18n:getText("spu_field_section_care"))
self:addToggleRow("fieldFertilize", i18n:getText("spu_field_fertilize"), yesNo, plan.fertilize ~= false)
self:addToggleRow("fieldLime", i18n:getText("spu_field_lime"), yesNo, plan.lime ~= false)
if info.kind ~= "mowable" then
self:addToggleRow("fieldWeed", i18n:getText("spu_field_weed"), yesNo, plan.weed ~= false)
end
local soil = self:soilText(info)
self:addTextRow(" " .. string.format(i18n:getText("spu_field_soilState"), soil ~= "" and soil or i18n:getText("spu_field_soilOk")))
if info.markerId ~= nil then
self:addTextRow(string.format(i18n:getText("spu_field_marker"), tostring(info.markerName), info.markerDistance or 0))
else
self:addTextRow(" " .. string.format(i18n:getText("spu_field_noMarker"), info.id))
self:addTextRow(" " .. string.format(i18n:getText("spu_guide_noMarker"), info.id))
end
if field.mowWidth ~= nil then
self:addTextRow(string.format(i18n:getText("spu_field_mowWidth"), field.mowWidth))
end
if field.plan.rake and field.windrowOffset ~= nil then
self:addTextRow(string.format(i18n:getText("spu_field_windrowOffset"), math.abs(field.windrowOffset),
i18n:getText(math.abs(field.windrowOffset) <= 0.05 and "spu_field_side_center" or (field.windrowOffset > 0 and "spu_field_side_left" or "spu_field_side_right"))))
end
self:fillFieldStoreRows(adEnv, info, field)
self:addFieldSiloWarnings(context.fieldSilos)
end
-- Tving lager: siloer der tager markens vare (skår/tørskår/høst); ved ensilage også gæringssiloer.
function SmartPickupFrame:fillFieldStoreRows(adEnv, info, field)
local i18n = self.i18n
local product = ADFieldModel.product(field.plan, info.kind)
local fillType
if info.kind == "mowable" then
fillType = product == "hay" and info.hay or info.windrow
else
fillType = info.harvestFillType
end
if fillType == nil then return end
self:fillDedicatedRows(adEnv, field)
self:addTextRow(i18n:getText("spu_field_section_store"))
local chosen = {}
for _, id in ipairs(field.plan.storeBuildingIds) do chosen[id] = true end
local farmId = g_currentMission:getFarmId()
local fieldSilos = {}
for _, building in ipairs(ADFields.fieldSilos(adEnv, farmId, info)) do fieldSilos[building.id] = building end
local fermenterIds = {}
if info.kind == "mowable" and product == "silage" then
for _, building in ipairs(ADFieldJobs.getFermenters(adEnv, farmId, fillType, info.x, info.z)) do fermenterIds[building.id] = true end
end
-- kandidater med markørnavn og afstand til marken (34 ens "Farma 500 + Obi 1000" kan ellers ikke skelnes)
local entries = {}
for _, building in ipairs(ADBuildings.list(adEnv, farmId)) do
local isSilo = building.kind == "silo" and ADRunsController.canStoreAt(adEnv, building, {{fillType = fillType, isPallet = false}})
if chosen[building.id] or isSilo or fermenterIds[building.id] then
local distance = nil
local node = building.placeable ~= nil and building.placeable.rootNode or nil
if node ~= nil and info.x ~= nil then
local bx, _, bz = getWorldTranslation(node)
distance = math.sqrt((bx - info.x) ^ 2 + (bz - info.z) ^ 2)
end
table.insert(entries, {id = building.id, building = building, distance = distance, chosen = chosen[building.id] == true,
fieldSilo = fieldSilos[building.id] ~= nil, fermenter = fermenterIds[building.id] == true})
end
end
local storeRows, hiddenStores = ADFieldView.storeRows(entries, SmartPickupFrame.FIELD_STORE_ROWS)
local count = #storeRows
for _, entry in ipairs(storeRows) do
local building = entry.building
local row = self:addRow(self.ingredientPrefab, {kind = "fieldStore", buildingId = building.id})
local markers = ADBuildings.getMarkers ~= nil and ADBuildings.getMarkers(building, "unload") or {}
-- det der skelner rækkerne først (rækken afkorter bagfra): markør, afstand, art — så bygningens navn
local label = (markers[1] ~= nil and tostring(markers[1].name) or tostring(building.name)) .. ", " .. ADFieldView.distanceText(entry.distance)
if entry.fieldSilo then label = label .. " (" .. i18n:getText("spu_field_fieldSilo") .. ")" end
if entry.fermenter then label = label .. " (" .. i18n:getText("spu_field_fermenter") .. ")" end
if markers[1] ~= nil then label = label .. " — " .. tostring(building.name) end
row:getDescendantByName("title"):setText(" " .. label)
row:getDescendantByName("option"):setState(entry.chosen and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end
if hiddenStores > 0 then
self:addTextRow(" " .. string.format(i18n:getText("spu_field_storeHidden"), hiddenStores))
end
if count == 0 then
self:addTextRow(" " .. i18n:getText("spu_field_noStore"))
elseif #field.plan.storeBuildingIds == 0 then
self:addTextRow(" " .. i18n:getText((info.kind == "mowable" and product == "silage") and "spu_field_storeAutoSilage" or "spu_field_storeAuto"))
end
-- marksiloer skal tømmes hjem af et udkørselsflow
for id, building in pairs(fieldSilos) do
-- Marksiloer skal tømmes hjem af et udkørselsflow.
function SmartPickupFrame:addFieldSiloWarnings(fieldSilos)
for _, silo in ipairs(fieldSilos or {}) do
local emptied = false
for _, run in ipairs(ADRunsController.store.runs) do
for _, sourceId in ipairs(run.kind == "outbound" and run.sourceBuildingIds or {}) do
if sourceId == id then emptied = true end
if sourceId == silo.id then emptied = true end
end
end
if not emptied then
self:addTextRow(" " .. string.format(i18n:getText("spu_field_fieldSiloNotEmptied"), tostring(building.name)))
self:addTextRow(" " .. string.format(self.i18n:getText("spu_field_fieldSiloNotEmptied"), tostring(silo.name or silo.text)))
end
end
end
@@ -387,54 +217,14 @@ function SmartPickupFrame:rigDetails(rig)
return #parts > 0 and (" — " .. table.concat(parts, " — ")) or ""
end
-- {[fieldId] = rigIds} for alle marker
local function rigIdsByField()
local byField = {}
for fieldId, field in pairs(ADFieldJobs.state.fields) do byField[fieldId] = field.plan ~= nil and field.plan.rigIds or {} end
return byField
end
-- "3 maskiner er faste på andre marker (F22, F90)"
function SmartPickupFrame:addHiddenDedicatedRow(hidden, fields)
if hidden <= 0 then return end
local names = {}
for _, fieldId in ipairs(fields) do table.insert(names, "F" .. tostring(fieldId)) end
self:addTextRow(" " .. string.format(self.i18n:getText("spu_field_hiddenDedicated"), hidden, table.concat(names, ", ")))
end
-- Faste maskiner til den valgte mark: en fast maskine bruges kun her, og marken venter på sin egen hellere
-- end at låne en anden med samme opgave. Maskiner der er faste på en anden mark er markeret.
function SmartPickupFrame:fillDedicatedRows(adEnv, field)
local i18n = self.i18n
self:addTextRow(i18n:getText("spu_field_section_dedicated"))
local owner = {}
for fieldId, other in pairs(ADFieldJobs.state.fields) do
for _, id in ipairs(other.plan ~= nil and other.plan.rigIds or {}) do owner[id] = fieldId end
end
local rigs, hiddenRigs, hiddenFields = ADFieldFleet.visibleForField(ADFieldJobs.getRigs(adEnv), field.id, rigIdsByField())
table.sort(rigs, function(a, b)
local ma, mb = owner[a.id] == field.id, owner[b.id] == field.id
if ma ~= mb then return ma end
if a.role ~= b.role then return a.role < b.role end
return ADFlowView.naturalLess(a.label, b.label)
end)
for _, rig in ipairs(rigs) do
local row = self:addRow(self.vehiclePrefab, {kind = "fieldRig", vehicleId = rig.id})
local elsewhere = owner[rig.id] ~= nil and owner[rig.id] ~= field.id
and (" — " .. string.format(i18n:getText("spu_field_dedicatedElsewhere"), tostring(owner[rig.id]))) or ""
row:getDescendantByName("title"):setText(string.format(" %s — %s%s", tostring(rig.label), self:roleText(rig.role), elsewhere))
row:getDescendantByName("option"):setState(owner[rig.id] == field.id and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end
self:addHiddenDedicatedRow(hiddenRigs, hiddenFields)
end
-- Markflåden (egen visning): alle maskiner med flueben, "fast på F22", "i gang på F108", bredde-advarsler.
function SmartPickupFrame:fillFleetRows(adEnv)
local i18n = self.i18n
self:addTextRow(i18n:getText("spu_field_section_fleet"))
local useAll = ADFieldJobs.state.fleetMode ~= "ticked"
self:addToggleRow("fleetMode", i18n:getText("spu_field_fleetAll"), {i18n:getText("ui_no"), i18n:getText("ui_yes")}, useAll)
-- valgt mark: maskiner der er faste på en anden mark skjules (de kan ikke bruges her)
local rigs, hiddenRigs, hiddenFields = ADFieldFleet.visibleForField(ADFieldJobs.getRigs(adEnv), self.selectedFieldId, rigIdsByField())
local rigs = {}
for _, rig in ipairs(ADFieldJobs.getRigs(adEnv)) do table.insert(rigs, rig) end
table.sort(rigs, function(a, b)
if a.role ~= b.role then return a.role < b.role end
return ADFlowView.naturalLess(a.label, b.label)
@@ -448,12 +238,15 @@ function SmartPickupFrame:fillFleetRows(adEnv)
if #rigs == 0 then
self:addTextRow(" " .. i18n:getText("spu_field_noRigs"))
end
self:addHiddenDedicatedRow(hiddenRigs, hiddenFields)
end
-- ---- live ----
function SmartPickupFrame:refreshFieldRows(adEnv)
if self:hasGuideOn(SmartPickupFrame.TAB_FIELDS) or self.fieldView == "fleet" then
self:setStatusText(self.statusOverride or "")
return
end
local rowsById = {}
for _, row in ipairs(ADFieldView.rows(ADFields.list(), ADFieldJobs.state.fields)) do rowsById[row.info.id] = row end
local working, total = 0, 0
@@ -473,45 +266,15 @@ function SmartPickupFrame:refreshFieldRows(adEnv)
self.fieldStartButtonInfo.disabled = selected == nil or info == nil or (not isWorking and info.markerId == nil)
end
-- ---- klik ----
-- ---- klik (markflåden) ----
function SmartPickupFrame:onClickFieldRow(state, data)
self:safe(function(frame)
local isOn = state == SmartPickupFrame.STATE_ON
local id = frame.selectedFieldId
if data.kind == "fleetVehicle" then
ADFieldJobs.toggleFleet(data.vehicleId)
elseif data.kind == "fleetMode" then
ADFieldJobs.setFleetMode(isOn and "all" or "ticked")
elseif id == nil then
return
elseif data.kind == "fieldTarget" then
ADFieldJobs.setTarget(id, data.values ~= nil and data.values[state] or "")
elseif SmartPickupFrame.PLAN_CHOICES[data.kind] ~= nil then
local value = data.values ~= nil and data.values[state] or nil
if value ~= nil then ADFieldJobs.setPlan(id, {[SmartPickupFrame.PLAN_CHOICES[data.kind]] = value}) end
elseif data.kind == "fieldProduct" then
ADFieldJobs.setPlan(id, {product = isOn and "hay" or "silage"})
elseif data.kind == "fieldMode" then
ADFieldJobs.setPlan(id, {mode = isOn and "auto" or "manual"})
elseif SmartPickupFrame.PLAN_TOGGLES[data.kind] ~= nil then
ADFieldJobs.setPlan(id, {[SmartPickupFrame.PLAN_TOGGLES[data.kind]] = isOn})
elseif data.kind == "fieldRig" then
-- fast på denne mark (og fjernes fra en anden mark)
local byField = {}
for fieldId, other in pairs(ADFieldJobs.state.fields) do byField[fieldId] = other.plan ~= nil and other.plan.rigIds or {} end
local updated = ADFieldFleet.dedicate(byField, id, data.vehicleId, isOn)
for fieldId, ids in pairs(updated) do
local before = table.concat(byField[fieldId] or {}, " ")
if before ~= table.concat(ids, " ") then ADFieldJobs.setPlan(fieldId, {rigIds = ids}) end
end
elseif data.kind == "fieldStore" then
local ids = {}
for _, buildingId in ipairs(ADFieldJobs.getField(id).plan.storeBuildingIds) do
if buildingId ~= data.buildingId then table.insert(ids, buildingId) end
end
if isOn then table.insert(ids, data.buildingId) end
ADFieldJobs.setPlan(id, {storeBuildingIds = ids})
end
frame:requestRebuild()
end)
+16 -5
View File
@@ -52,8 +52,9 @@ function SmartPickupFrame.new(i18n, getAdEnv)
self.flowButtons = {self.backButtonInfo, self.startButtonInfo, self.newButtonInfo, self.renameButtonInfo, self.deleteButtonInfo}
self.overviewButtons = {self.backButtonInfo, self.startAllButtonInfo, self.stopAllButtonInfo, self.newButtonInfo}
-- Marker: Start nu / Stop for den valgte mark
self.fieldStartButtonInfo = {inputAction = InputAction.MENU_ACTIVATE, text = i18n:getText("spu_field_button_start"), callback = function() self:onClickFieldStartStop() end}
self.fieldButtons = {self.backButtonInfo, self.fieldStartButtonInfo}
-- Marker: [Start nu/Stop], [Opsæt mark], [Markflåde] (gui/SmartPickupFieldGuide.lua); guidens knapper
self:initFieldButtons(i18n)
self:initGuideButtons(i18n)
-- Egen reference til listen: fallback'en i refreshButtons skal kunne sende den igen.
self.menuButtonInfo = self.overviewButtons
self:setMenuButtonInfo(self.menuButtonInfo)
@@ -197,6 +198,8 @@ end
function SmartPickupFrame:onFrameClose()
self.frameOpen = false
-- en åben guide kasseres (intet er gemt før [Gem])
self:closeGuide()
SmartPickupFrame:superClass().onFrameClose(self)
end
@@ -243,6 +246,7 @@ function SmartPickupFrame:requestRebuild()
end
function SmartPickupFrame:switchTab(tabIndex)
if self.guide ~= nil and not self:hasGuideOn(tabIndex) then self:closeGuide() end
self.currentTab = tabIndex
for index, tab in ipairs(self.subCategoryTabs) do
tab:setSelected(index == tabIndex)
@@ -665,10 +669,12 @@ end
-- sendes listen igen. Kaldes hvert sekund, så den må aldrig kaste.
function SmartPickupFrame:refreshButtons()
local wanted = self.flowButtons
if self.currentTab == SmartPickupFrame.TAB_OVERVIEW then
if self:hasGuideOn(self.currentTab) then
wanted = self:getGuideButtons()
elseif self.currentTab == SmartPickupFrame.TAB_OVERVIEW then
wanted = self.overviewButtons
elseif self.currentTab == SmartPickupFrame.TAB_FIELDS then
wanted = self.fieldButtons
wanted = self:getFieldButtons()
end
if self.menuButtonInfo ~= wanted then
self.menuButtonInfo = wanted
@@ -757,7 +763,9 @@ end
function SmartPickupFrame:onClickIngredient(state, element)
-- mål- og kilde-rækkerne deler prefab (og dermed onClick) med vare-rækkerne
local data = self.rowData[element]
if data ~= nil and data.kind == "target" then
if data ~= nil and data.kind == "guideRow" then
return self:onClickGuideRow(state, data)
elseif data ~= nil and data.kind == "target" then
return self:onClickTarget(state, element)
elseif data ~= nil and data.kind == "source" then
return self:onClickSource(state, element)
@@ -806,6 +814,9 @@ function SmartPickupFrame:onClickVehicle(state, element)
if clicked ~= nil and (clicked.kind == "fleetVehicle" or clicked.kind == "fieldRig") then
return self:onClickFieldRow(state, clicked)
end
if clicked ~= nil and clicked.kind == "guideRow" then
return self:onClickGuideRow(state, clicked)
end
self:safe(function(frame)
local run, data = frame:getRun(), frame.rowData[element]
-- kun flowets traktorrækker (de har et køretøj); alt andet ignoreres
+229
View File
@@ -0,0 +1,229 @@
-- Smart Pickup — fælles guide-skærm ("Opsæt mark" / "Opsæt flow")
-- Ét trin ad gangen: overskrift, "Trin 3 af 7 — …", kort forklaring, valgene som rækker og bundknapperne
-- Næste/Gem, Tilbage, Annullér (+ Gem uden at starte på oversigts-trinnet). Al logik ligger i ADGuide og
-- trin-definitionerne (ADFieldGuide/ADFlowGuide); her tegnes kun rækkerne og klik routes.
-- Hver guide registrerer sig i SmartPickupFrame.GUIDE_HOOKS[kind] = {
-- model, tab, heading(frame, guide), context(frame, adEnv, guide) -> context|nil (nil = luk guiden),
-- choose(frame, id, value) -> handled, save(frame, guide, start), startKey(guide), showSaveOnly(guide),
-- canAdvance(guide), afterRows(frame, adEnv, guide) }
-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua).
SmartPickupFrame.GUIDE_HOOKS = SmartPickupFrame.GUIDE_HOOKS or {}
function SmartPickupFrame:initGuideButtons(i18n)
self.guideNextButtonInfo = {inputAction = InputAction.MENU_ACTIVATE, text = i18n:getText("spu_guide_next"), callback = function() self:onClickGuideNext(true) end}
self.guideSaveOnlyButtonInfo = {inputAction = InputAction.MENU_EXTRA_1, text = i18n:getText("spu_guide_saveOnly"), callback = function() self:onClickGuideNext(false) end}
self.guidePrevButtonInfo = {inputAction = InputAction.MENU_EXTRA_2, text = i18n:getText("spu_guide_back"), callback = function() self:onClickGuideBack() end}
self.guideCancelButtonInfo = {inputAction = InputAction.MENU_CANCEL, text = i18n:getText("spu_guide_cancel"), callback = function() self:onClickGuideCancel() end}
self.guideButtons = {self.backButtonInfo, self.guideNextButtonInfo, self.guidePrevButtonInfo, self.guideCancelButtonInfo}
self.guideSummaryButtons = {self.backButtonInfo, self.guideNextButtonInfo, self.guideSaveOnlyButtonInfo, self.guidePrevButtonInfo, self.guideCancelButtonInfo}
end
function SmartPickupFrame:guideHooks()
return self.guideKind ~= nil and SmartPickupFrame.GUIDE_HOOKS[self.guideKind] or nil
end
-- Er der en åben guide på fanen?
function SmartPickupFrame:hasGuideOn(tab)
local hooks = self:guideHooks()
return self.guide ~= nil and hooks ~= nil and hooks.tab == tab
end
function SmartPickupFrame:openGuide(kind, guide)
self.guideKind, self.guide = kind, guide
self.statusOverride = nil
self:requestRebuild()
end
-- Kladden kasseres (Annullér, fanebyt, lukket menu).
function SmartPickupFrame:closeGuide()
self.guideKind, self.guide = nil, nil
end
function SmartPickupFrame:guideText(label)
return ADGuide.labelText(label, function(key) return self.i18n:getText(key) end)
end
-- Klikbar tekstrække (ombrydes; alle linjer er klikbare). Handlingen køres fra update() (pendingClick).
function SmartPickupFrame:addLinkRow(text, action)
local first = nil
for _, line in ipairs(ADFlowView.wrapText(text, SmartPickupFrame.TEXT_ROW_CHARS)) do
local row = self:addRow(self.textPrefab, {kind = "text"})
local title = row:getDescendantByName("title")
title:setText(line)
self:makeRowClickable(row, action)
first = first or title
end
return first
end
local function optionIndex(options, value)
for index, option in ipairs(options) do
if option.value == value then return index end
end
return 1
end
function SmartPickupFrame:addGuideRow(row)
local data = {kind = "guideRow", row = row}
if row.type == "choice" then
local element = self:addRow(self.productPrefab, data)
local option = element:getDescendantByName("option")
local texts = {}
for _, entry in ipairs(row.options) do table.insert(texts, self:guideText(entry.label)) end
option:setTexts(texts)
option:setState(optionIndex(row.options, row.value), false)
element:getDescendantByName("title"):setText(self:guideText(row.title))
elseif row.type == "toggle" or row.type == "check" then
local element = self:addRow(self.ingredientPrefab, data)
local option = element:getDescendantByName("option")
if row.type == "toggle" then option:setTexts({self:guideText(row.off), self:guideText(row.on)}) end
option:setState(row.value and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
element:getDescendantByName("title"):setText((row.type == "check" and " " or "") .. self:guideText(row.title))
elseif row.type == "link" then
local text = row.label ~= nil and self:guideText(row.label)
or (ADGuide.lineText(row.line, function(key) return self.i18n:getText(key) end) .. self.i18n:getText("spu_guide_editSuffix"))
self:addLinkRow(" " .. text, function(frame) frame:onGuideLink(row) end)
else
self:addTextRow(self:guideText(row.label))
end
end
-- Tegner den åbne guide. false = guiden er lukket (fx marken findes ikke længere) — fanen viser oversigten.
function SmartPickupFrame:fillGuideRows(adEnv)
local hooks = self:guideHooks()
if hooks == nil or self.guide == nil then return false end
local context = hooks.context(self, adEnv, self.guide)
if context == nil then
self:closeGuide()
self.statusOverride = self.i18n:getText("spu_guide_closed")
self.statusOverrideTicks = SmartPickupFrame.STATUS_OVERRIDE_TICKS
return false
end
self.guide = ADGuide.withContext(self.guide, context)
local guide, model = self.guide, hooks.model
self:addTextRow(hooks.heading(self, guide))
local stepTitle = self.i18n:getText(model.stepKey(guide.step))
local index, total = ADGuide.position(guide)
local header
if guide.single then
header = string.format(self.i18n:getText("spu_guide_editStep"), stepTitle)
elseif index ~= nil and total > 1 then
header = string.format(self.i18n:getText("spu_guide_stepOf"), index, total, stepTitle)
else
header = string.format(self.i18n:getText("spu_guide_stepOne"), index or 1, stepTitle)
end
self:addTextRow(header)
self:addTextRow(" " .. self.i18n:getText(model.hintKey(guide.step)))
for _, row in ipairs(model.rows(guide)) do
self:addGuideRow(row)
end
if hooks.afterRows ~= nil then hooks.afterRows(self, adEnv, guide) end
return true
end
-- ---- klik ----
function SmartPickupFrame:applyGuideChoice(id, value)
local hooks = self:guideHooks()
if hooks == nil or self.guide == nil then return end
if hooks.choose ~= nil and hooks.choose(self, id, value) then return end
self.guide = hooks.model.choose(self.guide, id, value)
end
function SmartPickupFrame:onClickGuideRow(state, data)
self:safe(function(frame)
local row = data ~= nil and data.row or nil
if row == nil or frame.guide == nil then return end
local value
if row.type == "choice" then
local option = row.options[state]
if option == nil then return end
value = option.value
elseif row.type == "toggle" then
value = state == SmartPickupFrame.STATE_ON
elseif row.type == "check" then
value = {item = row.item, on = state == SmartPickupFrame.STATE_ON}
else
return
end
frame:applyGuideChoice(row.id, value)
frame:requestRebuild()
end)
end
-- Klikbare linjer: [Ret] på oversigts-trinnet åbner ét trin; andre links er valg (fx mark i trin 1).
function SmartPickupFrame:onGuideLink(row)
if self.guide == nil then return end
if row.id == "jump" then
self.guide = ADGuide.jump(self.guide, row.value, "summary")
else
self:applyGuideChoice(row.id, row.value)
end
self:requestRebuild()
end
local function finish(frame, hooks, guide, start)
hooks.save(frame, guide, start)
frame:closeGuide()
frame:requestRebuild()
end
-- Næste / Gem / Kør automatisk (start = true) og Gem uden at starte (start = false).
function SmartPickupFrame:onClickGuideNext(start)
self:safe(function(frame)
local hooks, guide = frame:guideHooks(), frame.guide
if hooks == nil or guide == nil then return end
if hooks.canAdvance ~= nil and not hooks.canAdvance(guide) then
frame:showOverride(frame.i18n:getText(hooks.cannotAdvanceKey or "spu_guide_cannotAdvance"))
return
end
if guide.single and guide.returnTo == "close" then
if start == false then return end
return finish(frame, hooks, guide, nil)
end
if guide.step == "summary" and not guide.single then
return finish(frame, hooks, guide, start)
end
if start == false then return end
local nextGuide, event = ADGuide.next(guide)
if event == "done" then return finish(frame, hooks, guide, true) end
frame.guide = nextGuide
frame:requestRebuild()
end)
end
function SmartPickupFrame:onClickGuideBack()
self:safe(function(frame)
if frame.guide == nil then return end
local previous, event = ADGuide.back(frame.guide)
if event == "cancel" then frame:closeGuide() else frame.guide = previous end
frame:requestRebuild()
end)
end
function SmartPickupFrame:onClickGuideCancel()
self:safe(function(frame)
frame:closeGuide()
frame:requestRebuild()
end)
end
-- Knapperne følger trinnet: Næste / OK (tilbage til oversigten) / Gem ([Ret] fra fanen) / Kør automatisk.
function SmartPickupFrame:getGuideButtons()
local hooks, guide = self:guideHooks(), self.guide
local i18n = self.i18n
local isSummary = guide.step == "summary" and not guide.single
local key = "spu_guide_next"
if guide.single then
key = guide.returnTo == "close" and "spu_guide_save" or "spu_guide_ok"
elseif isSummary then
key = hooks.startKey ~= nil and hooks.startKey(guide) or "spu_guide_save"
end
self.guideNextButtonInfo.text = i18n:getText(key)
self.guideNextButtonInfo.disabled = hooks.canAdvance ~= nil and not hooks.canAdvance(guide)
if isSummary and (hooks.showSaveOnly == nil or hooks.showSaveOnly(guide)) then
return self.guideSummaryButtons
end
return self.guideButtons
end
@@ -179,6 +179,9 @@ end
function SmartPickupFrame:onClickProduct(state, element)
local clicked = self.rowData[element]
if clicked ~= nil and clicked.kind == "guideRow" then
return self:onClickGuideRow(state, clicked)
end
if clicked ~= nil and clicked.kind == "storageBuilding" then
return self:safe(function(frame) frame:onClickStorageBuilding(state) end)
end
+71 -1
View File
@@ -77,7 +77,7 @@
<text name="spu_field_mode_manual" text="Manuel"/>
<text name="spu_field_notMowable" text="ikke græs/kløver"/>
<text name="spu_field_scanning" text="Marker indlæses ... (én mark pr. sekund)"/>
<text name="spu_field_hint" text="Klik på en mark for detaljer, plan og faste maskiner."/>
<text name="spu_field_hint" text="Klik på en mark for at se og rette dens opsætning."/>
<text name="spu_field_showAll" text="Vis alle marker (%d skjult)"/>
<text name="spu_col_field" text="Mark"/>
<text name="spu_col_crop" text="Afgrøde"/>
@@ -320,5 +320,75 @@
<text name="spu_col_lasts" text="Rækker til"/>
<text name="spu_col_rate" text="Forbrug/prod. døgn"/>
<text name="spu_storage_select_tooltip" text="Vælg bygningen tabellen viser."/>
<text name="spu_guide_next" text="Næste"/>
<text name="spu_guide_save" text="Gem"/>
<text name="spu_guide_ok" text="OK"/>
<text name="spu_guide_back" text="Tilbage"/>
<text name="spu_guide_cancel" text="Annullér"/>
<text name="spu_guide_saveOnly" text="Gem uden at starte"/>
<text name="spu_guide_runAuto" text="Kør automatisk"/>
<text name="spu_guide_stepOf" text="Trin %d af %d — %s"/>
<text name="spu_guide_stepOne" text="Trin %d — %s"/>
<text name="spu_guide_editStep" text="Ret: %s"/>
<text name="spu_guide_editSuffix" text=" [Ret]"/>
<text name="spu_guide_sum_hint" text="Tryk på en linje for at rette netop det."/>
<text name="spu_guide_auto" text="Automatisk (anbefalet)"/>
<text name="spu_guide_manual" text="Vælg selv"/>
<text name="spu_guide_closed" text="Guiden er lukket — marken/flowet findes ikke længere"/>
<text name="spu_guide_cannotAdvance" text="Vælg først"/>
<text name="spu_guide_pickField" text="Vælg en mark først"/>
<text name="spu_guide_saved" text="F%d gemt"/>
<text name="spu_guide_noMarker" text="! Sæt en AD-markør ved F%d — arbejdet venter på markøren"/>
<text name="spu_guide_field_open" text="Opsæt mark"/>
<text name="spu_guide_fleet_open" text="Markflåde"/>
<text name="spu_guide_fleet_close" text="Til marker"/>
<text name="spu_guide_field_title" text="— Opsæt mark F%d —"/>
<text name="spu_guide_field_titleNone" text="— Opsæt mark —"/>
<text name="spu_guide_field_step_field" text="Mark"/>
<text name="spu_guide_field_step_crop" text="Afgrøde"/>
<text name="spu_guide_field_step_cropChoice" text="Afgrødens valg"/>
<text name="spu_guide_field_step_prep" text="Klargøring"/>
<text name="spu_guide_field_step_care" text="Pleje"/>
<text name="spu_guide_field_step_machines" text="Maskiner"/>
<text name="spu_guide_field_step_store" text="Lager"/>
<text name="spu_guide_field_step_summary" text="Oversigt"/>
<text name="spu_guide_field_hint_field" text="Vælg den mark du vil sætte op."/>
<text name="spu_guide_field_hint_crop" text="Hvad skal gro på marken? 'Behold' kører videre med det der står nu."/>
<text name="spu_guide_field_hint_cropChoice" text="Hvad skal der ske med afgrøden ved slåning eller høst?"/>
<text name="spu_guide_field_hint_prep" text="Efter høst / på bar mark. Automatisk pløjer eller kultiverer og kalker efter behov."/>
<text name="spu_guide_field_hint_care" text="Mens afgrøden gror. Der gødes/sprøjtes kun når marken trænger."/>
<text name="spu_guide_field_hint_machines" text="Hvor mange ad gangen, og skal marken have sine egne faste maskiner?"/>
<text name="spu_guide_field_hint_store" text="Hvor skal det høstede/slåede hen?"/>
<text name="spu_guide_field_hint_summary" text="Tjek valgene. Tryk på en linje for at rette den."/>
<text name="spu_guide_crop" text="Afgrøde"/>
<text name="spu_guide_crop_keep" text="Behold det der gror"/>
<text name="spu_guide_crop_sowAgain" text="Så samme afgrøde igen efter høst"/>
<text name="spu_guide_prep" text="Klargøring"/>
<text name="spu_guide_prep_autoText" text=" Pløjer når jorden trænger, ellers kultiverer; kalker ved lav pH."/>
<text name="spu_guide_rigs" text="Maskiner ad gangen (multi-tool)"/>
<text name="spu_guide_dedicated" text="Faste maskiner til marken?"/>
<text name="spu_guide_useFieldSilo" text="Brug marksiloen?"/>
<text name="spu_guide_store" text="Lager"/>
<text name="spu_guide_store_autoText" text=" Nærmeste silo med plads (ensilage: gæringssilo)."/>
<text name="spu_guide_sum_target" text="-&gt; %s"/>
<text name="spu_guide_sum_sowAgain" text="så igen"/>
<text name="spu_guide_sum_rake" text="river"/>
<text name="spu_guide_sum_noRake" text="uden river"/>
<text name="spu_guide_sum_straw_chop" text="halm hakkes"/>
<text name="spu_guide_sum_straw_collect" text="halm samles"/>
<text name="spu_guide_sum_straw_bale" text="halm presses"/>
<text name="spu_guide_sum_auto" text="Automatisk"/>
<text name="spu_guide_sum_lime" text="kalk"/>
<text name="spu_guide_sum_mulch" text="mulch"/>
<text name="spu_guide_sum_roll" text="tromle"/>
<text name="spu_guide_sum_fert" text="gødning"/>
<text name="spu_guide_sum_weed" text="ukrudt"/>
<text name="spu_guide_sum_noCare" text="ingen pleje"/>
<text name="spu_guide_sum_dedicated" text="%d faste"/>
<text name="spu_guide_sum_freeRigs" text="ledige maskiner"/>
<text name="spu_guide_sum_rigs" text="%d ad gangen"/>
<text name="spu_guide_sum_unloaders" text="%d aflæsningsvogne"/>
<text name="spu_guide_sum_fieldSilo" text="Marksilo"/>
<text name="spu_guide_sum_stores" text="%d valgte siloer"/>
</texts>
</l10n>
+71 -1
View File
@@ -77,7 +77,7 @@
<text name="spu_field_mode_manual" text="Manual"/>
<text name="spu_field_notMowable" text="not grass/clover"/>
<text name="spu_field_scanning" text="Loading fields ... (one field per second)"/>
<text name="spu_field_hint" text="Click a field for details, plan and dedicated machines."/>
<text name="spu_field_hint" text="Click a field to see and change its setup."/>
<text name="spu_field_showAll" text="Show all fields (%d hidden)"/>
<text name="spu_col_field" text="Field"/>
<text name="spu_col_crop" text="Crop"/>
@@ -320,5 +320,75 @@
<text name="spu_col_lasts" text="Lasts"/>
<text name="spu_col_rate" text="Use/prod. per day"/>
<text name="spu_storage_select_tooltip" text="Choose the building shown in the table."/>
<text name="spu_guide_next" text="Next"/>
<text name="spu_guide_save" text="Save"/>
<text name="spu_guide_ok" text="OK"/>
<text name="spu_guide_back" text="Back"/>
<text name="spu_guide_cancel" text="Cancel"/>
<text name="spu_guide_saveOnly" text="Save without starting"/>
<text name="spu_guide_runAuto" text="Run automatically"/>
<text name="spu_guide_stepOf" text="Step %d of %d — %s"/>
<text name="spu_guide_stepOne" text="Step %d — %s"/>
<text name="spu_guide_editStep" text="Edit: %s"/>
<text name="spu_guide_editSuffix" text=" [Edit]"/>
<text name="spu_guide_sum_hint" text="Click a line to change just that."/>
<text name="spu_guide_auto" text="Automatic (recommended)"/>
<text name="spu_guide_manual" text="Choose myself"/>
<text name="spu_guide_closed" text="Guide closed — the field/flow no longer exists"/>
<text name="spu_guide_cannotAdvance" text="Choose first"/>
<text name="spu_guide_pickField" text="Pick a field first"/>
<text name="spu_guide_saved" text="F%d saved"/>
<text name="spu_guide_noMarker" text="! Place an AD marker at F%d — work waits for the marker"/>
<text name="spu_guide_field_open" text="Set up field"/>
<text name="spu_guide_fleet_open" text="Field fleet"/>
<text name="spu_guide_fleet_close" text="Back to fields"/>
<text name="spu_guide_field_title" text="— Set up field F%d —"/>
<text name="spu_guide_field_titleNone" text="— Set up field —"/>
<text name="spu_guide_field_step_field" text="Field"/>
<text name="spu_guide_field_step_crop" text="Crop"/>
<text name="spu_guide_field_step_cropChoice" text="Crop options"/>
<text name="spu_guide_field_step_prep" text="Preparation"/>
<text name="spu_guide_field_step_care" text="Crop care"/>
<text name="spu_guide_field_step_machines" text="Machines"/>
<text name="spu_guide_field_step_store" text="Storage"/>
<text name="spu_guide_field_step_summary" text="Summary"/>
<text name="spu_guide_field_hint_field" text="Pick the field you want to set up."/>
<text name="spu_guide_field_hint_crop" text="What should grow here? 'Keep' carries on with the current crop."/>
<text name="spu_guide_field_hint_cropChoice" text="What happens to the crop when mowing or harvesting?"/>
<text name="spu_guide_field_hint_prep" text="After harvest / on bare land. Automatic plows or cultivates and limes as needed."/>
<text name="spu_guide_field_hint_care" text="While the crop grows. Fertilizing/spraying only happens when needed."/>
<text name="spu_guide_field_hint_machines" text="How many at a time, and should the field have its own dedicated machines?"/>
<text name="spu_guide_field_hint_store" text="Where should the harvest go?"/>
<text name="spu_guide_field_hint_summary" text="Check your choices. Click a line to change it."/>
<text name="spu_guide_crop" text="Crop"/>
<text name="spu_guide_crop_keep" text="Keep what grows"/>
<text name="spu_guide_crop_sowAgain" text="Sow the same crop again after harvest"/>
<text name="spu_guide_prep" text="Preparation"/>
<text name="spu_guide_prep_autoText" text=" Plows when the soil needs it, otherwise cultivates; limes at low pH."/>
<text name="spu_guide_rigs" text="Machines at a time (multi-tool)"/>
<text name="spu_guide_dedicated" text="Dedicated machines for this field?"/>
<text name="spu_guide_useFieldSilo" text="Use the field silo?"/>
<text name="spu_guide_store" text="Storage"/>
<text name="spu_guide_store_autoText" text=" Nearest silo with room (silage: fermenting silo)."/>
<text name="spu_guide_sum_target" text="-&gt; %s"/>
<text name="spu_guide_sum_sowAgain" text="sow again"/>
<text name="spu_guide_sum_rake" text="rake"/>
<text name="spu_guide_sum_noRake" text="no rake"/>
<text name="spu_guide_sum_straw_chop" text="straw chopped"/>
<text name="spu_guide_sum_straw_collect" text="straw collected"/>
<text name="spu_guide_sum_straw_bale" text="straw baled"/>
<text name="spu_guide_sum_auto" text="Automatic"/>
<text name="spu_guide_sum_lime" text="lime"/>
<text name="spu_guide_sum_mulch" text="mulch"/>
<text name="spu_guide_sum_roll" text="roll"/>
<text name="spu_guide_sum_fert" text="fertilizer"/>
<text name="spu_guide_sum_weed" text="weeds"/>
<text name="spu_guide_sum_noCare" text="no care"/>
<text name="spu_guide_sum_dedicated" text="%d dedicated"/>
<text name="spu_guide_sum_freeRigs" text="free machines"/>
<text name="spu_guide_sum_rigs" text="%d at a time"/>
<text name="spu_guide_sum_unloaders" text="%d unloaders"/>
<text name="spu_guide_sum_fieldSilo" text="Field silo"/>
<text name="spu_guide_sum_stores" text="%d chosen silos"/>
</texts>
</l10n>
+3 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107">
<author>masterdraco</author>
<version>1.30.4.0</version>
<version>1.31.0.0</version>
<title>
<en>AD Smart Pickup</en>
</title>
@@ -23,6 +23,7 @@
<sourceFile filename="adOutboundPlanner.lua"/>
<sourceFile filename="adStorageStats.lua"/>
<sourceFile filename="adFlowView.lua"/>
<sourceFile filename="adGuide.lua"/>
<sourceFile filename="adSources.lua"/>
<sourceFile filename="adBuildings.lua"/>
<sourceFile filename="adOutbound.lua"/>
@@ -30,6 +31,7 @@
<sourceFile filename="adFieldModel.lua"/>
<sourceFile filename="adFieldFleet.lua"/>
<sourceFile filename="adFieldView.lua"/>
<sourceFile filename="adFieldGuide.lua"/>
<sourceFile filename="adFields.lua"/>
<sourceFile filename="adCpBridge.lua"/>
<sourceFile filename="adFieldStorage.lua"/>