Files
fs25-adsmartpickup/FS25_ADSmartPickup/adFieldGuide.lua
T

492 lines
21 KiB
Lua

-- 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