From 5df4fdc7c34cc841e0bca328047407bd45a50320 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Sun, 27 Sep 2026 09:12:49 +0200 Subject: [PATCH] =?UTF-8?q?feat(gui):=20fanen=20M=C3=A5ned=20=E2=80=94=20m?= =?UTF-8?q?arker=20i=20gang=20med=20fremdrift,=20n=C3=A6ste=20trin=20og=20?= =?UTF-8?q?om=20h=C3=B8st-/s=C3=A5vinduet=20lukker=20ved=20m=C3=A5nedsskif?= =?UTF-8?q?tet;=20dom=20'du=20kan=20sove'=20+=20marker=20der=20kan=20s?= =?UTF-8?q?=C3=A5s=20nu=20(v1.35.0.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adMonthView.lua | 141 ++++++++++++++++++ FS25_ADSmartPickup/adSmartPickup.lua | 1 + FS25_ADSmartPickup/gui/SmartPickupFrame.lua | 28 +++- .../gui/SmartPickupMonthTab.lua | 107 +++++++++++++ FS25_ADSmartPickup/l10n/l10n_da.xml | 18 +++ FS25_ADSmartPickup/l10n/l10n_en.xml | 18 +++ FS25_ADSmartPickup/modDesc.xml | 3 +- tests/test_adMonthView.lua | 82 ++++++++++ 8 files changed, 394 insertions(+), 4 deletions(-) create mode 100644 FS25_ADSmartPickup/adMonthView.lua create mode 100644 FS25_ADSmartPickup/gui/SmartPickupMonthTab.lua create mode 100644 tests/test_adMonthView.lua diff --git a/FS25_ADSmartPickup/adMonthView.lua b/FS25_ADSmartPickup/adMonthView.lua new file mode 100644 index 0000000..21ac50c --- /dev/null +++ b/FS25_ADSmartPickup/adMonthView.lua @@ -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 diff --git a/FS25_ADSmartPickup/adSmartPickup.lua b/FS25_ADSmartPickup/adSmartPickup.lua index bec0b33..0ad5b78 100644 --- a/FS25_ADSmartPickup/adSmartPickup.lua +++ b/FS25_ADSmartPickup/adSmartPickup.lua @@ -25,6 +25,7 @@ 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/SmartPickupMonthTab.lua") source(ADSmartPickup.dir .. "gui/SmartPickupGuide.lua") source(ADSmartPickup.dir .. "gui/SmartPickupFieldGuide.lua") source(ADSmartPickup.dir .. "gui/SmartPickupFlowGuide.lua") diff --git a/FS25_ADSmartPickup/gui/SmartPickupFrame.lua b/FS25_ADSmartPickup/gui/SmartPickupFrame.lua index bae73f4..cc62ba5 100644 --- a/FS25_ADSmartPickup/gui/SmartPickupFrame.lua +++ b/FS25_ADSmartPickup/gui/SmartPickupFrame.lua @@ -11,10 +11,12 @@ SmartPickupFrame.STATUS_OVERRIDE_TICKS = 6 SmartPickupFrame.MAX_STATUS_REASONS = 3 SmartPickupFrame.LOG_PREFIX = "ADSmartPickup: " SmartPickupFrame.STATE_OFF, SmartPickupFrame.STATE_ON = 1, 2 -SmartPickupFrame.TAB_OVERVIEW, SmartPickupFrame.TAB_FLOWS, SmartPickupFrame.TAB_FIELDS = 1, 2, 3 +SmartPickupFrame.TAB_OVERVIEW, SmartPickupFrame.TAB_FLOWS, SmartPickupFrame.TAB_FIELDS, SmartPickupFrame.TAB_MONTH = 1, 2, 3, 4 +-- Måned-fanen genopbygges hvert N. sekund (ingen klikbare rækker, så fokus går ikke tabt) +SmartPickupFrame.MONTH_REBUILD_TICKS = 5 -- Lagerfaner: én pr. bygningstype (indeks -> kind i ADSources.getFarmInventory) -SmartPickupFrame.TAB_NAMES = {"spu_tab_overview", "spu_tab_flows", "spu_tab_fields", "spu_tab_factories", "spu_tab_silos", "spu_tab_barns", "spu_tab_sheds"} -SmartPickupFrame.TAB_KINDS = {[4] = "factory", [5] = "silo", [6] = "husbandry", [7] = "objectStorage"} +SmartPickupFrame.TAB_NAMES = {"spu_tab_overview", "spu_tab_flows", "spu_tab_fields", "spu_tab_month", "spu_tab_factories", "spu_tab_silos", "spu_tab_barns", "spu_tab_sheds"} +SmartPickupFrame.TAB_KINDS = {[5] = "factory", [6] = "silo", [7] = "husbandry", [8] = "objectStorage"} function SmartPickupFrame.getTabKind(tab) return SmartPickupFrame.TAB_KINDS[tab] @@ -49,6 +51,7 @@ function SmartPickupFrame.new(i18n, getAdEnv) -- Oversigt: Start/Stop alle i stedet for knapperne der virker på ét valgt flow. self.startAllButtonInfo = {inputAction = InputAction.MENU_ACTIVATE, text = i18n:getText("spu_button_startAll"), callback = function() self:onClickStartAll() end} self.stopAllButtonInfo = {inputAction = InputAction.MENU_EXTRA_2, text = i18n:getText("spu_button_stopAll"), callback = function() self:onClickStopAll() end} + self.monthButtons = {self.backButtonInfo} 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 @@ -409,6 +412,17 @@ function SmartPickupFrame:rebuild() self:restoreFocusIfLost() return end + if self.currentTab == SmartPickupFrame.TAB_MONTH then + if adEnv ~= nil then + self:fillMonthRows(adEnv) + end + self.monthRebuildTicks = SmartPickupFrame.MONTH_REBUILD_TICKS + self:colorRows() + self.boxLayout:invalidateLayout() + self:refreshButtons() + self:restoreFocusIfLost() + return + end if self.currentTab == SmartPickupFrame.TAB_OVERVIEW then if adEnv ~= nil then self:fillOverviewRows(adEnv) @@ -540,6 +554,8 @@ function SmartPickupFrame:refreshButtons() wanted = self.overviewButtons elseif self.currentTab == SmartPickupFrame.TAB_FIELDS then wanted = self:getFieldButtons() + elseif self.currentTab == SmartPickupFrame.TAB_MONTH then + wanted = self.monthButtons end if self.menuButtonInfo ~= wanted then self.menuButtonInfo = wanted @@ -560,6 +576,12 @@ function SmartPickupFrame:refreshLive() self:setStatusText(self.i18n:getText("spu_status_noAd")) return end + if self.currentTab == SmartPickupFrame.TAB_MONTH then + self.monthRebuildTicks = (self.monthRebuildTicks or 0) - 1 + if self.monthRebuildTicks <= 0 then self:requestRebuild() end + self:refreshButtons() + return + end local isOverview = self.currentTab == SmartPickupFrame.TAB_OVERVIEW if isOverview then self:refreshOverview(adEnv) diff --git a/FS25_ADSmartPickup/gui/SmartPickupMonthTab.lua b/FS25_ADSmartPickup/gui/SmartPickupMonthTab.lua new file mode 100644 index 0000000..b5c8384 --- /dev/null +++ b/FS25_ADSmartPickup/gui/SmartPickupMonthTab.lua @@ -0,0 +1,107 @@ +-- Smart Pickup — fanen Måned +-- Hvad skal nås før månedsskiftet? Høst og såning har korte vinduer; fanen viser markerne med arbejde i +-- gang, fremdrift, næste trin og om vinduet lukker. Kun visning — reglerne ligger i ADMonthView. + +-- Periodens navn: spillets egen tekst (fx "Sen sommer") hvis den findes, ellers "Periode 6". +-- Periode 1 = tidligt forår = marts (nordlig halvkugle) -> "(aug)". +function SmartPickupFrame:monthPeriodText(period) + local month = (period + 1) % 12 + 1 + local names = {} + for name in string.gmatch(self.i18n:getText("spu_month_months"), "[^,]+") do table.insert(names, name) end + local suffix = names[month] ~= nil and (" (" .. names[month] .. ")") or "" + local ok, text = pcall(function() return g_i18n:formatPeriod(period, false) end) + if ok and type(text) == "string" and text ~= "" then return text .. suffix end + return string.format(self.i18n:getText("spu_month_period"), period) .. suffix +end + +-- {period, day, days, hour, minute}; nil hvis miljøet ikke kan læses. +function SmartPickupFrame.monthClock() + local ok, clock = pcall(function() + local environment = g_currentMission.environment + local dayTime = environment.dayTime or 0 + return {period = environment.currentPeriod, day = environment.currentDayInPeriod, days = environment.daysPerPeriod, + hour = math.floor(dayTime / 3600000), minute = math.floor(dayTime / 60000) % 60} + end) + if ok and type(clock) == "table" and type(clock.period) == "number" then return clock end + return nil +end + +-- Courseplays fremdrift for markens sæt (gennemsnit), nil hvis ingen melder noget. +function SmartPickupFrame.monthCpProgress(fieldId) + local sum, count = 0, 0 + for _, assignment in ipairs(ADFieldJobs.assignmentsFor(fieldId)) do + local vehicle = assignment.vehicle + if not assignment.helper and vehicle ~= nil and vehicle.getCpFieldWorkProgress ~= nil then + local ok, progress = pcall(vehicle.getCpFieldWorkProgress, vehicle) + if ok and type(progress) == "number" then + sum, count = sum + progress, count + 1 + end + end + end + return count > 0 and sum / count or nil +end + +function SmartPickupFrame.monthContext(period) + return { + period = period, + plantableIn = function(name, forPeriod) + local fruit = g_fruitTypeManager ~= nil and g_fruitTypeManager:getFruitTypeByName(name) or nil + return fruit ~= nil and ADCpBridge.isPlantableIn(fruit, forPeriod) + end, + withers = function(info, nextPeriod) + local fruit = info.fruitIndex ~= nil and g_fruitTypeManager:getFruitTypeByIndex(info.fruitIndex) or nil + local ok, result = pcall(ADMonthView.withersIn, fruit, nextPeriod, info.growthState) + return ok and result == true + end, + cpProgress = SmartPickupFrame.monthCpProgress, + } +end + +function SmartPickupFrame:monthStageText(stage) + if stage == nil then return "–" end + return self.i18n:getText("spu_field_stage_" .. tostring(stage)) +end + +function SmartPickupFrame:monthCells(row) + local crop = row.sowFruit ~= nil and self:fruitTitle(row.sowFruit) or (row.crop or "") + return {"F" .. tostring(row.id), self:monthStageText(row.stage), crop, ADMonthView.percentText(row.progress), + self:monthStageText(row.nextStage), self.i18n:getText("spu_month_window_" .. row.window)} +end + +function SmartPickupFrame:fillMonthRows(adEnv) + local i18n = self.i18n + local clock = SmartPickupFrame.monthClock() + if clock == nil then + self:addTextRow(i18n:getText("spu_month_noClock")) + return + end + self:addTextRow(string.format(i18n:getText("spu_month_header"), self:monthPeriodText(clock.period), + clock.day or 1, clock.days or 1, clock.hour, clock.minute)) + local infos = ADFields.list() + if #infos == 0 then + self:addTextRow(i18n:getText("spu_field_scanning")) + return + end + local view = ADMonthView.build(infos, ADFieldJobs.state.fields, SmartPickupFrame.monthContext(clock.period)) + if view.critical > 0 then + self:addTextRow(string.format(i18n:getText("spu_month_verdictUrgent"), view.critical)) + else + self:addTextRow(i18n:getText("spu_month_verdictCalm")) + end + if #view.rows == 0 then + self:addTextRow(i18n:getText("spu_month_noWork")) + else + self:addCellRow(self.fieldPrefab, {i18n:getText("spu_col_field"), i18n:getText("spu_month_col_work"), i18n:getText("spu_col_crop"), + i18n:getText("spu_month_col_progress"), i18n:getText("spu_month_col_next"), i18n:getText("spu_month_col_window")}) + for _, row in ipairs(view.rows) do + self:addCellRow(self.fieldPrefab, self:monthCells(row)) + end + end + if #view.upcoming > 0 then + self:addTextRow(i18n:getText("spu_month_upcoming")) + for _, entry in ipairs(view.upcoming) do + local key = entry.closes and "spu_month_upcomingLast" or "spu_month_upcomingOpen" + self:addTextRow(string.format(i18n:getText(key), entry.id, self:fruitTitle(entry.fruit))) + end + end +end diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml index 57a282b..0c98df5 100644 --- a/FS25_ADSmartPickup/l10n/l10n_da.xml +++ b/FS25_ADSmartPickup/l10n/l10n_da.xml @@ -40,6 +40,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml index f232a4f..c482904 100644 --- a/FS25_ADSmartPickup/l10n/l10n_en.xml +++ b/FS25_ADSmartPickup/l10n/l10n_en.xml @@ -40,6 +40,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 8ec4501..37ceb6a 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.34.2.0 + 1.35.0.0 <en>AD Smart Pickup</en> @@ -39,6 +39,7 @@ + diff --git a/tests/test_adMonthView.lua b/tests/test_adMonthView.lua new file mode 100644 index 0000000..e28249a --- /dev/null +++ b/tests/test_adMonthView.lua @@ -0,0 +1,82 @@ +-- Kør: luajit tests/test_adMonthView.lua (fra repo-roden) +dofile("FS25_ADSmartPickup/adFieldModel.lua") +dofile("FS25_ADSmartPickup/adMonthView.lua") +local V, M = ADMonthView, ADFieldModel +local failures = 0 +local function check(name, actual, expected) + if actual == expected then print("OK " .. name) else + failures = failures + 1 + print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual))) + end +end + +-- MV1: perioder +check("MV1 næste periode", V.nextPeriod(5), 6) +check("MV1 vinter -> forår", V.nextPeriod(12), 1) + +-- MV2: såvindue (hvede sås i periode 6-7) +local function plantableIn(name, period) return name == "WHEAT" and (period == 6 or period == 7) end +check("MV2 lukker", V.sowWindow(plantableIn, "WHEAT", 7), "closes") +check("MV2 åbent", V.sowWindow(plantableIn, "WHEAT", 6), "open") +check("MV2 lukket", V.sowWindow(plantableIn, "WHEAT", 8), "closed") + +-- MV3: visning (Dakota-kartofler: harvestReady 6 -> dead 7 i EARLY_AUTUMN = periode 7) +local potato = {witheredState = 7, growthDataSeasonal = {periods = {[7] = {updates = {{startState = 6, endState = 7}}}}}} +check("MV3 visner næste måned", V.withersIn(potato, 7, 6), true) +check("MV3 ikke måneden før", V.withersIn(potato, 6, 6), false) +check("MV3 ukendte data", V.withersIn({witheredState = 7}, 7, 6), false) + +-- MV4: fremdrift +local harvest = M.with(M.newField(15), {stage = "harvesting", kind = "harvest"}) +check("MV4 CP vinder", V.progress(harvest, {readyRatio = 0.9}, 0.5), 0.5) +check("MV4 høst ud fra afgrøde", V.progress(harvest, {readyRatio = 0.25}, nil), 0.75) +check("MV4 kultivering ukendt", V.progress(M.with(harvest, {stage = "cultivating"}), {readyRatio = 0}, nil), nil) +check("MV4 tekst", V.percentText(0.504), "50 %") +check("MV4 tekst ukendt", V.percentText(nil), "–") + +-- MV5: hel fane +local fields = { + [15] = M.with(harvest, {plan = {sow = true, sowFruit = "WHEAT", soil = "cultivate", straw = "chop"}}), + [16] = M.with(M.newField(16), {stage = "harvesting", kind = "harvest"}), + [18] = M.with(M.newField(18), {stage = "sowing", kind = "harvest", plan = {sow = true, sowFruit = "WHEAT"}}), + [20] = M.with(M.newField(20), {stage = "fertilizing"}), + [21] = M.newField(21), + [22] = M.with(M.newField(22), {plan = {targetFruit = "WHEAT"}}), + [23] = M.with(M.newField(23), {stage = "ready"}), +} +local infos = { + {id = 15, kind = "harvest", readyRatio = 0.5, cropTitle = "Hvede", needs = {}}, + {id = 16, kind = "harvest", readyRatio = 1, cropTitle = "Kartofler", growthState = 6}, + {id = 18, kind = "bare", readyRatio = 0}, + {id = 20, kind = "harvest", readyRatio = 0}, + {id = 21, kind = "bare"}, + {id = 22, kind = "bare"}, + {id = 23, kind = "harvest", readyRatio = 1}, +} +local ctx = {period = 7, plantableIn = plantableIn, + withers = function(info, nextPeriod) return info.id == 16 and nextPeriod == 8 end, + cpProgress = function(id) return id == 18 and 0.1 or nil end} +local view = V.build(infos, fields, ctx) +check("MV5 aktive marker (21 venter, 22 kommer, 23 passiv klar)", #view.rows, 4) +check("MV5 kritiske", view.critical, 3) +check("MV5 høst før sidste såmåned først (id)", view.rows[1].id, 15) +check("MV5 høst -> såning senere lukker", view.rows[1].window, "lastSowMonth") +check("MV5 høst næste trin", view.rows[1].nextStage, "cultivating") +check("MV5 høst der visner", view.rows[2].id, 16) +check("MV5 vindue høst", view.rows[2].window, "harvestCloses") +check("MV5 sidste såmåned", view.rows[3].id, 18) +check("MV5 vindue såning", view.rows[3].window, "lastSowMonth") +check("MV5 såafgrøde", view.rows[3].sowFruit, "WHEAT") +check("MV5 CP-fremdrift", view.rows[3].progress, 0.1) +check("MV5 pleje sidst", view.rows[4].id, 20) +check("MV5 pleje intet næste", view.rows[4].nextStage, nil) +check("MV5 kommer: F22 hvede", view.upcoming[1] and view.upcoming[1].id, 22) +check("MV5 kommer: lukker", view.upcoming[1] and view.upcoming[1].closes, true) + +-- MV6: klargøring før såning med lukkende vindue er kritisk +local cultivating = M.with(M.newField(30), {stage = "cultivating", plan = {sow = true, sowFruit = "WHEAT"}}) +check("MV6 kultivering før sidste såmåned", V.window(cultivating, {kind = "bare"}, ctx), "lastSowMonth") +check("MV6 aktiv klar (auto)", V.isActive(M.with(M.newField(31), {stage = "ready", plan = {mode = "auto"}})), true) + +print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) +os.exit(failures == 0 and 0 or 1)