-- 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 -- Pleje "Automatisk" = al pleje slået til (gødning, ukrudt, kalk). Kalk tæller kun her når den ikke hører -- under klargøring — ellers afgør klargøringens valg den. local function isCareAuto(plan) return plan.fertilize ~= false and plan.weed ~= false and plan.lime ~= false 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), careAuto = isCareAuto(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, chainFree = 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 == "careAuto" 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 table.insert(rows, toggle("chainFree", L("spu_field_chainFree"), plan.chainFree)) 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 draft, plan = guide.draft, guide.draft.plan local rows = {choice("careAuto", L("spu_guide_care"), { {label = L("spu_guide_auto"), value = true}, {label = L("spu_guide_manual"), value = false}}, draft.careAuto)} if draft.careAuto then table.insert(rows, {type = "text", label = L("spu_guide_care_autoText")}) return rows end table.insert(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) if guide.draft.careAuto then return {L("spu_guide_sum_auto")} end 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) -- kilden ejes af setTarget; kladdens gamle værdi må ikke overskrive den plan.targetSource = nil 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 draft.careAuto and ADFieldGuide.isVisible("care", guide) then plan.fertilize, plan.weed = true, true if not prepVisible(guide) then plan.lime = true end 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