feat(gui): fanen Måned — marker i gang med fremdrift, næste trin og om høst-/såvinduet lukker ved månedsskiftet; dom 'du kan sove' + marker der kan sås nu (v1.35.0.0)

This commit is contained in:
2026-09-27 09:12:49 +02:00
parent 5274925f2a
commit 5df4fdc7c3
8 changed files with 394 additions and 4 deletions
+141
View File
@@ -0,0 +1,141 @@
-- AD Month View
-- Rene regler for fanen Måned: hvilke marker har arbejde i gang, hvor langt er de, hvad kommer bagefter,
-- og lukker vinduet (høst/såning) ved månedsskiftet? GUI'et oversætter nøglerne; spillet kaldes kun via
-- ctx-funktionerne (testbart uden FS).
ADMonthView = {}
ADMonthView.PERIODS = 12
-- Trin der kræver afgrøden eller såvinduet: sorteres før pleje/klargøring
ADMonthView.MAIN_STAGES = {harvesting = true, mowing = true, sowing = true}
-- Trin før såningen i klargøringskæden (lukker såvinduet, bliver marken ikke sået)
ADMonthView.BEFORE_SOWING = {mowing = true, tedding = true, raking = true, collecting = true, harvesting = true,
strawCollecting = true, baling = true, baleCollecting = true, mulching = true,
liming = true, plowing = true, cultivating = true, sowing = true}
function ADMonthView.nextPeriod(period)
if type(period) ~= "number" or period < 1 then return 1 end
return period % ADMonthView.PERIODS + 1
end
-- Visse FS25-sæsondata gemmer overgangene som lister af {startState, endState}; strukturen er ikke
-- dokumenteret, så alle tabeller i periodens data gennemsøges. Ukendt = nej (ingen falsk alarm).
local function findsTransition(data, fromState, toState, depth)
if type(data) ~= "table" or depth > 3 then return false end
if data.startState == fromState and data.endState == toState then return true end
for _, value in pairs(data) do
if type(value) == "table" and findsTransition(value, fromState, toState, depth + 1) then return true end
end
return false
end
-- Visner afgrøden (stadie state) ved overgangen til periode nextPeriod?
function ADMonthView.withersIn(fruit, nextPeriod, state)
if fruit == nil or type(state) ~= "number" or state <= 0 then return false end
local withered = fruit.witheredState
if type(withered) ~= "number" or withered <= 0 then return false end
local periods = fruit.growthDataSeasonal ~= nil and fruit.growthDataSeasonal.periods or nil
local data = periods ~= nil and periods[nextPeriod] or nil
return findsTransition(data, state, withered, 0)
end
-- Såvindue for en afgrøde: "closes" (kan sås nu, ikke næste måned), "open" (også næste måned), "closed".
function ADMonthView.sowWindow(plantableIn, fruitName, period)
if fruitName == nil or fruitName == "" or plantableIn == nil then return "open" end
local now = plantableIn(fruitName, period)
if not now then return "closed" end
return plantableIn(fruitName, ADMonthView.nextPeriod(period)) and "open" or "closes"
end
-- Er marken i gang (vises i tabellen)? Venter/færdig og passiv "klar" (manuel uden Start nu) er ikke.
function ADMonthView.isActive(field)
if field == nil then return false end
if field.stage == "idle" or field.stage == "done" then return false end
if field.stage == "ready" then return ADFieldModel.shouldStart(field) end
return true
end
-- Næste trin i kæden (uden at ændre marken). nil = intet (pleje går tilbage til venter, sidste trin -> færdig).
function ADMonthView.nextStage(field, info)
if field.stage == "ready" then
local stages = ADFieldModel.workStages(ADFieldModel.applyTarget(field.plan, info.kind, info.fruitName), info.kind, info.needs)
return stages[1]
end
if ADFieldModel.isCare(field.stage) then return nil end
local nextField = ADFieldModel.advance(field, info.needs)
if nextField.stage == "done" or nextField.stage == "idle" then return nil end
return nextField.stage
end
-- Vindue for en aktiv mark: "harvestCloses" | "lastSowMonth" | "ok".
-- ctx = {period, plantableIn(name, period), withers(info, nextPeriod)}
function ADMonthView.window(field, info, ctx)
local stage = field.stage
local cropStage = stage == "harvesting" or stage == "mowing" or stage == "ready"
if cropStage and info.kind ~= "bare" and ctx.withers ~= nil and ctx.withers(info, ADMonthView.nextPeriod(ctx.period)) then
return "harvestCloses"
end
local sowsLater = field.plan ~= nil and field.plan.sow and ADMonthView.BEFORE_SOWING[stage]
if sowsLater and ADMonthView.sowWindow(ctx.plantableIn, ADFieldModel.sowFruit(field), ctx.period) == "closes" then
return "lastSowMonth"
end
return "ok"
end
-- Fremdrift 0-1: Courseplays egen (gennemsnit af sættene) vinder; høst/slåning ellers ud fra hvor meget
-- af afgrøden der er væk; ellers ukendt (nil).
function ADMonthView.progress(field, info, cpProgress)
if type(cpProgress) == "number" then return math.max(0, math.min(1, cpProgress)) end
if (field.stage == "harvesting" or field.stage == "mowing") and type(info.readyRatio) == "number" then
return math.max(0, math.min(1, 1 - info.readyRatio))
end
return nil
end
local function priority(row)
if row.window ~= "ok" then return 0 end
if ADMonthView.MAIN_STAGES[row.stage] then return 1 end
return 2
end
-- Hele fanen: {rows = {...}, critical = n, upcoming = {...}}.
-- infos = ADFields.list(), fieldsById = ADFieldJobs.state.fields,
-- ctx = {period, plantableIn(name, period), withers(info, nextPeriod), cpProgress(fieldId) -> 0-1|nil}
function ADMonthView.build(infos, fieldsById, ctx)
local rows, upcoming, critical = {}, {}, 0
for _, info in ipairs(infos or {}) do
local field = fieldsById[info.id]
if field ~= nil and ADMonthView.isActive(field) then
local row = {id = info.id, stage = field.stage, crop = info.cropTitle, sowFruit = nil,
progress = ADMonthView.progress(field, info, ctx.cpProgress ~= nil and ctx.cpProgress(info.id) or nil),
nextStage = ADMonthView.nextStage(field, info), window = ADMonthView.window(field, info, ctx)}
if field.stage == "sowing" or (info.kind == "bare" and ADMonthView.BEFORE_SOWING[field.stage]) then
row.sowFruit = field.plan.sow and ADFieldModel.sowFruit(field) or nil
end
if row.window ~= "ok" then critical = critical + 1 end
table.insert(rows, row)
elseif field ~= nil and info.kind == "bare" and (field.stage == "idle" or field.stage == "done") then
local target = field.plan.targetFruit
if target ~= nil and target ~= "" and ctx.plantableIn ~= nil and ctx.plantableIn(target, ctx.period) then
table.insert(upcoming, {id = info.id, fruit = target,
closes = ADMonthView.sowWindow(ctx.plantableIn, target, ctx.period) == "closes"})
end
end
end
table.sort(rows, function(a, b)
local pa, pb = priority(a), priority(b)
if pa ~= pb then return pa < pb end
return a.id < b.id
end)
table.sort(upcoming, function(a, b)
if a.closes ~= b.closes then return a.closes end
return a.id < b.id
end)
return {rows = rows, critical = critical, upcoming = upcoming}
end
-- "50 %" / "–"
function ADMonthView.percentText(progress)
if progress == nil then return "–" end
return string.format("%d %%", math.floor(progress * 100 + 0.5))
end