147 lines
7.2 KiB
Lua
147 lines
7.2 KiB
Lua
-- 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. Høst/slåning: andelen af HELE arealet der stadig står (standingRatio) — readyRatio duer ikke,
|
||
-- for slået/høstet afgrøde forsvinder også fra dens nævner (in-game 27/9 F90: halvt slået = 0 %), og
|
||
-- Courseplays tal er kun positionen i kurset (starter forfra på 0 ved genstart). Øvrige trin: Courseplay.
|
||
function ADMonthView.progress(field, info, cpProgress)
|
||
local cropStage = field.stage == "harvesting" or field.stage == "mowing"
|
||
if cropStage and type(info.standingRatio) == "number" then
|
||
return math.max(0, math.min(1, 1 - info.standingRatio))
|
||
end
|
||
if type(cpProgress) == "number" then return math.max(0, math.min(1, cpProgress)) end
|
||
if cropStage 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
|