From ae1362d1647f16566ae826e05d920db62975f69c Mon Sep 17 00:00:00 2001 From: masterdraco Date: Sat, 26 Sep 2026 00:04:37 +0200 Subject: [PATCH] =?UTF-8?q?feat(marker):=20vejr=20(vent=20p=C3=A5=20t?= =?UTF-8?q?=C3=B8rvejr),=20p=C3=A5fyldning=20via=20AutoDrive=20LOAD+CP,=20?= =?UTF-8?q?beskeder=20i=20spillet;=20Start=20nu=20virker=20p=C3=A5=20bare?= =?UTF-8?q?=20marker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adCpBridge.lua | 17 ++++++++++++ FS25_ADSmartPickup/adFieldJobs.lua | 42 ++++++++++++++++++++++++++--- FS25_ADSmartPickup/adFieldModel.lua | 11 +++++++- FS25_ADSmartPickup/adFieldWork.lua | 38 +++++++++++++++++++++++--- FS25_ADSmartPickup/l10n/l10n_da.xml | 2 ++ FS25_ADSmartPickup/l10n/l10n_en.xml | 2 ++ tests/test_adFieldJobs.lua | 14 ++++++++++ tests/test_adFieldModel.lua | 7 +++++ 8 files changed, 126 insertions(+), 7 deletions(-) diff --git a/FS25_ADSmartPickup/adCpBridge.lua b/FS25_ADSmartPickup/adCpBridge.lua index 5e6e6c9..71fe46d 100644 --- a/FS25_ADSmartPickup/adCpBridge.lua +++ b/FS25_ADSmartPickup/adCpBridge.lua @@ -159,6 +159,23 @@ function ADCpBridge.isPlantableNow(fruitIndex) return ADCpBridge.isPlantableIn(fruit, environment ~= nil and environment.currentPeriod or 0) end +-- Hvilken vare sættet bruger og kan få fyldt på af AutoDrive: frø (såmaskine) eller sprøjtens/sprederens +-- nuværende fyld (gødning, kalk, sprøjtemiddel). nil = intet at fylde på (strigle, plov ...). +function ADCpBridge.getRefillFillType(vehicle) + for _, object in ipairs(collect(vehicle)) do + if object.spec_sowingMachine ~= nil and g_fillTypeManager ~= nil then + return g_fillTypeManager:getFillTypeIndexByName("SEEDS") + end + end + for _, object in ipairs(collect(vehicle)) do + if object.spec_sprayer ~= nil and object.getSprayerFillUnitIndex ~= nil then + local ok, fillType = pcall(function() return object:getFillUnitFillType(object:getSprayerFillUnitIndex()) end) + if ok and fillType ~= nil and fillType ~= 0 then return fillType end + end + end + return nil +end + -- Stop køretøjets AI-job (Courseplay/hjælper). function ADCpBridge.stop(vehicle) pcall(function() diff --git a/FS25_ADSmartPickup/adFieldJobs.lua b/FS25_ADSmartPickup/adFieldJobs.lua index c03233c..99d62e0 100644 --- a/FS25_ADSmartPickup/adFieldJobs.lua +++ b/FS25_ADSmartPickup/adFieldJobs.lua @@ -31,12 +31,19 @@ end -- Beslutning for én mark. assignments = sæt på marken ({phase, stage, helper}); kun sæt på det aktuelle -- trin tæller (en aflæsningsvogn på vej hjem fra høsten forstyrrer ikke kultiveringen). --- Svar: {action = "start"|"care"|"advance"|"dispatch"|"wait"|"none", role, count, helper, stage, care}. -function ADFieldJobs.decide(field, info, assignments, now) +-- env = {raining = bool}: vejrfølsomme trin (høst/slå/vend/riv/hø) sendes ikke ud i regn. +-- Svar: {action = "start"|"care"|"advance"|"dispatch"|"wait"|"none", role, count, helper, stage, care, reason}. +function ADFieldJobs.decide(field, info, assignments, now, env) local hasMarker = info ~= nil and info.markerId ~= nil + local raining = env ~= nil and env.raining == true + local kind = field.kind or (info ~= nil and info.kind) or "mowable" if field.stage == "ready" then if not ADFieldModel.shouldStart(field) then return {action = "none"} end if not hasMarker then return {action = "wait"} end + local first = ADFieldModel.workStages(field.plan, info.kind, info.needs)[1] + if raining and first ~= nil and ADFieldModel.isWeatherSensitive(first, field.plan, info.kind) then + return {action = "wait", reason = "rain"} + end return {action = "start"} end if not ADFieldModel.isWorking(field) then @@ -68,6 +75,9 @@ function ADFieldJobs.decide(field, info, assignments, now) end if not hasMarker then return {action = "wait"} end if (field.retryAt or 0) > now then return {action = "none"} end + if raining and ADFieldModel.isWeatherSensitive(field.stage, field.plan, kind) then + return {action = "wait", reason = "rain"} + end return {action = "dispatch", role = ADFieldModel.roleFor(field.stage), count = ADFieldModel.maxRigs(field.stage, field.plan), helper = false} end -- hovedsættene arbejder: fyld op med hjælpere (aflæsningsvogne) en ad gangen @@ -166,6 +176,25 @@ function ADFieldJobs.stopField(adEnv, id) ADFieldJobs.log(id, "stoppet fra menuen") end +-- Besked i spillet (højre side). critical = rød. Samme tekst vises kun én gang i træk. +function ADFieldJobs.notify(text, critical) + if ADFieldJobs.lastNotice == text then return end + ADFieldJobs.lastNotice = text + pcall(function() + local kind = critical and FSBaseMission.INGAME_NOTIFICATION_CRITICAL or FSBaseMission.INGAME_NOTIFICATION_OK + g_currentMission:addIngameNotification(kind, "Smart Pickup: " .. text) + end) +end + +-- Vejret lige nu (regn), læst en gang pr. tick. +function ADFieldJobs.environment() + local ok, raining = pcall(function() + local weather = g_currentMission.environment.weather + return weather ~= nil and weather.getIsRaining ~= nil and weather:getIsRaining() or false + end) + return {raining = ok and raining == true} +end + -- Seneste beslutning pr. mark (GUI viser pleje-behov og grunde). ADFieldJobs.lastDecision = {} @@ -174,7 +203,7 @@ local function stepField(adEnv, info) ADFieldJobs.setField(field) local assignments = ADFieldJobs.assignmentsFor(info.id) for _, done in ipairs(finishedFor(info.id)) do table.insert(assignments, done) end - local decision = ADFieldJobs.decide(field, info, assignments, ADFieldJobs.now) + local decision = ADFieldJobs.decide(field, info, assignments, ADFieldJobs.now, ADFieldJobs.environment()) ADFieldJobs.lastDecision[info.id] = decision if decision.action == "start" then local started = ADFieldModel.start(field, info.kind, info.needs) @@ -192,7 +221,14 @@ local function stepField(adEnv, info) ADFieldJobs.forgetFinished(info.id) ADFieldWork.windDownHelpers(adEnv, info.id, field.stage) ADFieldJobs.log(info.id, "%s -> %s", field.stage, nextField.stage) + if nextField.stage == "done" or (ADFieldModel.isCare(field.stage) and nextField.stage == "idle") then + ADFieldJobs.notify(string.format("F%d %s", info.id, g_i18n ~= nil and g_i18n:getText("spu_field_notice_done") or "done"), false) + end ADFieldJobs.save() + elseif decision.action == "wait" and decision.reason == "rain" then + if field.reason == nil or field.reason.key ~= "spu_field_reason_rain" then + ADFieldJobs.note(info.id, {reason = {key = "spu_field_reason_rain"}}) + end elseif decision.action == "dispatch" then ADFieldWork.dispatch(adEnv, field, info, decision) end diff --git a/FS25_ADSmartPickup/adFieldModel.lua b/FS25_ADSmartPickup/adFieldModel.lua index d85a30a..897459f 100644 --- a/FS25_ADSmartPickup/adFieldModel.lua +++ b/FS25_ADSmartPickup/adFieldModel.lua @@ -178,7 +178,8 @@ function ADFieldModel.onReadiness(field, ratio) local copy = copyField(field) if (field.stage == "idle" or field.stage == "done") and ratio >= ADFieldModel.READY_RATIO then copy.stage = "ready" - elseif field.stage == "ready" and ratio < ADFieldModel.CUT_RATIO then + elseif field.stage == "ready" and ratio < ADFieldModel.CUT_RATIO and not field.startRequested then + -- (en Start nu-forespørgsel holder marken klar: bare marker har klar-andel 0) copy.stage = "idle" copy.startRequested = false end @@ -256,6 +257,14 @@ function ADFieldModel.stop(field) return copy end +-- Vejrfølsomme trin: afgrøde og skår skal være tørre (høst, slåning, vending, rivning, hø-opsamling). +-- Jordbehandling, såning, pleje og ensilage-opsamling kan køre i regn. +local WEATHER_STAGES = {harvesting = true, mowing = true, tedding = true, raking = true} +function ADFieldModel.isWeatherSensitive(stage, plan, kind) + if WEATHER_STAGES[stage] then return true end + return stage == "collecting" and ADFieldModel.product(plan, kind or "mowable") == "hay" +end + -- Pleje under vækst: hvilket pleje-trin marken trænger til nu (eller nil). info fra adFields: -- {kind, growing, fert, fertMax, lime, weed}. Højst MAX_CARE_RUNS pr. pleje pr. cyklus. function ADFieldModel.careNeed(field, info) diff --git a/FS25_ADSmartPickup/adFieldWork.lua b/FS25_ADSmartPickup/adFieldWork.lua index c816242..52e3ce5 100644 --- a/FS25_ADSmartPickup/adFieldWork.lua +++ b/FS25_ADSmartPickup/adFieldWork.lua @@ -242,6 +242,7 @@ end local function fail(fieldId, text) note(fieldId, {retryAt = now() + ADFieldModel.RETRY_MS, reason = {text = tostring(text)}}) log(fieldId, "%s", tostring(text)) + ADFieldJobs.notify(string.format("F%s: %s", tostring(fieldId), tostring(text)), true) end -- Hvad et sæt skal kunne ud over rollen (afgrøde, frø, ukrudtets størrelse). @@ -339,6 +340,32 @@ local function prepareCollector(adEnv, field, info, assignment) return true end +-- Påfyldning undervejs (såmaskine/sprøjte/spreder): AutoDrive LOAD med automatisk påfyldningsmål og +-- hjælper = Courseplay. Når CP melder tom (onCpEmpty), kører AD til nærmeste påfyldning af varen, fylder +-- op, kører tilbage til F-markøren og genstarter CP. Courseplay og AutoDrive er bygget til netop dette. +local function prepareRefill(adEnv, field, info, assignment) + local vehicle = assignment.vehicle + local fillType = ADCpBridge.getRefillFillType(vehicle) + if fillType == nil then return end + local module = stateModule(vehicle) + local ok = pcall(function() + module:setMode(adEnv.AutoDrive.MODE_LOAD) + module:setFirstMarkerByWayPointId(info.markerId) + module:setSecondMarkerByWayPointId(info.markerId) + module:setAutomaticPickupTarget(true) + module:setFillType(fillType) + module:setStartHelper(true) + module.usedHelper = helperCp(adEnv) + enableParkAtJobFinished(vehicle) + module:raiseDirtyFlag() + end) + if ok then + assignment.adHandlesEnd = true + local name = g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeNameByIndex(fillType) or tostring(fillType) + log(field.id, "'%s' fyldes op med %s af AutoDrive når den er tom", ADRuns.vehicleLabel(vehicle), tostring(name)) + end +end + -- Aflæsningsvogn under høst: Courseplays combine-unloader med stationen fra silovalget. local function beginUnloader(adEnv, field, info, assignment) local vehicle = assignment.vehicle @@ -407,7 +434,12 @@ local function beginWork(adEnv, field, info, assignment) end return end - if assignment.role == "collector" and not prepareCollector(adEnv, field, info, assignment) then return end + if assignment.role == "collector" then + if not prepareCollector(adEnv, field, info, assignment) then return end + assignment.adHandlesEnd = true + elseif assignment.role == "seeder" or assignment.role == "fertilizer" or assignment.role == "limer" or assignment.role == "weeder" then + prepareRefill(adEnv, field, info, assignment) + end if assignment.role == "seeder" then local okSeed, seedError = ADCpBridge.setSeedFruit(vehicle, ADFieldWork.sowFruitIndex(field)) if not okSeed then @@ -462,8 +494,8 @@ end local function finishAssignment(adEnv, assignment) local vehicle = assignment.vehicle log(assignment.fieldId, "%s '%s' færdig", assignment.role, ADRuns.vehicleLabel(vehicle)) - -- læssevognen parkeres af AutoDrive selv (onCpFinished med parkering sat) - local park = assignment.role ~= "collector" and parkMarkerId(adEnv, vehicle) or nil + -- læssevogn/påfyldte sæt parkeres af AutoDrive selv (onCpFinished med parkering sat) + local park = not assignment.adHandlesEnd and parkMarkerId(adEnv, vehicle) or nil if park ~= nil then startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end restoreToolOffset(assignment) assignment.phase = "finished" diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml index 3d41ef3..7b47f7f 100644 --- a/FS25_ADSmartPickup/l10n/l10n_da.xml +++ b/FS25_ADSmartPickup/l10n/l10n_da.xml @@ -151,6 +151,8 @@ + + diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml index a3c2e1c..34fd83c 100644 --- a/FS25_ADSmartPickup/l10n/l10n_en.xml +++ b/FS25_ADSmartPickup/l10n/l10n_en.xml @@ -151,6 +151,8 @@ + + diff --git a/tests/test_adFieldJobs.lua b/tests/test_adFieldJobs.lua index b3c5971..a56e6e0 100644 --- a/tests/test_adFieldJobs.lua +++ b/tests/test_adFieldJobs.lua @@ -75,5 +75,19 @@ check("J10 manuel -> intet, men behov vist", manualCare.action, "none"); check(" check("J10 Start nu -> pleje", J.decide(M.with(M.newField(60), {startRequested = true}), growingInfo, {}, 0).action, "care") check("J10 uden markør -> vent", J.decide(autoField, {id = 60, kind = "harvest", growing = true, fert = 0, fertMax = 2}, {}, 0).action, "wait") +-- J11: regn — vejrfølsomme trin sendes ikke ud (kørende sæt fortsætter); klargøring/pleje kører videre +local rain = {raining = true} +local dRain = J.decide(mowing, info, {}, 0, rain) +check("J11 slåning venter i regn", dRain.action, "wait"); check("J11 grund", dRain.reason, "rain") +check("J11 kørende slåmaskine fortsætter", J.decide(mowing, info, {{phase = "working"}}, 0, rain).action, "none") +check("J11 høst venter", J.decide(harvesting, harvestInfo, {}, 0, rain).reason, "rain") +check("J11 kultivering kører i regn", J.decide(cultivating, afterInfo, {}, 0, rain).role, "cultivator") +check("J11 start af høst venter", J.decide(M.with(M.onReadiness(M.newField(51), 1), {startRequested = true, kind = "harvest"}), harvestInfo, {}, 0, rain).reason, "rain") +local silageCollect = M.advance(mowing) +check("J11 ensilage-opsamling kører i regn", J.decide(silageCollect, info, {}, 0, rain).action, "dispatch") +local hayCollect = M.with(silageCollect, {plan = {product = "hay"}}) +check("J11 hø-opsamling venter", J.decide(hayCollect, info, {}, 0, rain).reason, "rain") +check("J11 tørvejr", J.decide(mowing, info, {}, 0, {raining = false}).action, "dispatch") + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1) diff --git a/tests/test_adFieldModel.lua b/tests/test_adFieldModel.lua index ebaaebe..219df64 100644 --- a/tests/test_adFieldModel.lua +++ b/tests/test_adFieldModel.lua @@ -227,5 +227,12 @@ check("M17 etape 1-post: hø", legacy.plan.product, "hay") check("M17 etape 1-post: standard kalk til", legacy.plan.lime, true) check("M17 etape 1-post: 1 sæt", legacy.plan.rigs, 1) +-- M18: Start nu på en bar mark (klar-andel 0) må ikke nulstilles af vækst-reglen før den starter +local bare = M.with(M.forceReady(M.newField(70)), {startRequested = true}) +check("M18 bar mark forbliver klar", M.onReadiness(bare, 0).stage, "ready") +check("M18 uden forespørgsel -> venter", M.onReadiness(M.forceReady(M.newField(71)), 0).stage, "idle") +check("M18 vejrfølsom: høst", M.isWeatherSensitive("harvesting", {}, "harvest"), true) +check("M18 vejrfølsom: kultivering ikke", M.isWeatherSensitive("cultivating", {}, "harvest"), false) + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)