4 Commits

16 changed files with 474 additions and 38 deletions
+57 -1
View File
@@ -5,7 +5,7 @@
ADCpBridge = {}
ADCpBridge.SPECS = {"spec_mower", "spec_tedder", "spec_windrower", "spec_forageWagon", "spec_combine",
ADCpBridge.SPECS = {"spec_mower", "spec_tedder", "spec_windrower", "spec_forageWagon", "spec_combine", "spec_baler",
"spec_sowingMachine", "spec_cultivator", "spec_plow", "spec_roller", "spec_weeder", "spec_mulcher"}
-- Sprøjtens/sprederens fyld -> rolle (tom sprøjte har ingen rolle; den kan ikke sendes ud).
@@ -86,6 +86,10 @@ function ADCpBridge.getSpecs(vehicle)
local ok, can = pcall(vehicle.getCanStartCpCombineUnloader, vehicle)
specs.canUnload = ok and can == true or nil
end
if vehicle.getCanStartCpBaleFinder ~= nil then
local ok, can = pcall(vehicle.getCanStartCpBaleFinder, vehicle)
specs.canBaleCollect = ok and can == true or nil
end
return specs
end
@@ -189,6 +193,58 @@ function ADCpBridge.getRefillLevel(vehicle, fillType)
return total
end
-- Halmskår bag mejetærskeren (Courseplays køretøjsindstilling strawSwath: 1 = fra/hakkes, 2 = til).
function ADCpBridge.setStrawSwath(vehicle, on)
return (pcall(function() vehicle:getCpSettings().strawSwath:setValue(on and 2 or 1) end))
end
-- Ballesamler: Courseplays BALE_FINDER_CP (ballevogn eller Universal Autoload) samler markens baller.
function ADCpBridge.startBaleFinder(vehicle, fieldX, fieldZ, onDone)
if AIJobType == nil or AIJobType.BALE_FINDER_CP == nil then
return nil, "Courseplay (BALE_FINDER_CP) findes ikke"
end
local farmId = vehicle:getOwnerFarmId()
local job = g_currentMission.aiJobTypeManager:createJob(AIJobType.BALE_FINDER_CP)
if job == nil then return nil, "kunne ikke oprette ballesamler-job" end
pcall(function() job:applyCurrentState(vehicle, g_currentMission, farmId, false, false) end)
local params = job.cpJobParameters
if params ~= nil and params.fieldPosition ~= nil then pcall(function() params.fieldPosition:setPosition(fieldX, fieldZ) end) end
if params ~= nil and params.startPosition ~= nil then
local vx, _, vz = getWorldTranslation(vehicle.rootNode)
pcall(function() params.startPosition:setPosition(vx, vz) end)
end
pcall(function() job:setValues() end)
local handle = {cancelled = false, fired = false, finished = false}
local function finish(success, reason)
if handle.finished then return end
handle.finished = true
if not handle.cancelled then onDone(success, reason) end
end
local function startJob()
if handle.cancelled then return end
local valid, err = job:validate(farmId)
if not valid then return finish(false, "Courseplay: " .. tostring(err)) end
g_currentMission.aiSystem:startJob(job, farmId)
finish(true)
end
handle.onBoundary = function(_, success, message)
if handle.fired then return end
handle.fired = true
if not success then return finish(false, "Courseplay fandt ikke markgrænsen: " .. tostring(message)) end
startJob()
end
if job.registerFieldBoundaryDetectionCallback ~= nil then
job:registerFieldBoundaryDetectionCallback(handle, handle.onBoundary)
end
local isValid, isRunning, message = job:detectFieldBoundary()
if isValid and not isRunning then
startJob()
elseif not isRunning then
return nil, "Courseplay: " .. tostring(message)
end
return handle
end
-- Stop køretøjets AI-job (Courseplay/hjælper).
function ADCpBridge.stop(vehicle)
pcall(function()
+8
View File
@@ -8,6 +8,7 @@ ADFieldFleet = {}
-- plov, ikke aflæsningsvogn). sprayRole kommer fra sprøjtens/sprederens fyld (gødning/kalk/ukrudt).
ADFieldFleet.ROLE_SPECS = {
{role = "harvester", spec = "spec_combine"},
{role = "baler", spec = "spec_baler"},
{role = "collector", spec = "spec_forageWagon"},
{role = "rake", spec = "spec_windrower"},
{role = "tedder", spec = "spec_tedder"},
@@ -19,6 +20,7 @@ ADFieldFleet.ROLE_SPECS = {
{role = "mulcher", spec = "spec_mulcher"},
{role = "cultivator", spec = "spec_cultivator"},
{role = "plow", spec = "spec_plow"},
{role = "baleCollector", spec = "canBaleCollect"},
{role = "unloader", spec = "canUnload"},
}
@@ -89,6 +91,12 @@ function ADFieldFleet.pickMany(candidates, role, n, x, z, requiredWidth, accept)
return nil, reason, args
end
-- Må sættet sendes ud? "all" (standard): alle ledige maskiner; "ticked": kun med flueben.
function ADFieldFleet.isEnabled(mode, enabledIds, id)
if mode == "ticked" then return (enabledIds or {})[id] == true end
return true
end
function ADFieldFleet.toggle(enabledIds, id)
local copy = {}
for key, value in pairs(enabledIds or {}) do copy[key] = value end
+31 -3
View File
@@ -9,7 +9,8 @@ ADFieldJobs.LOG_PREFIX = "ADSmartPickup: "
ADFieldJobs.TICK_MS = 1000
-- Courseplay kan efterlade hjørner på skæve marker: efter så mange færdige ture pr. sæt går marken videre.
ADFieldJobs.MAX_PASSES = 2
ADFieldJobs.state = {fields = {}, fleet = {}, assignments = {}}
-- fleetMode: "all" = alle ledige maskiner (standard, "vælg og glem"), "ticked" = kun med flueben
ADFieldJobs.state = {fields = {}, fleet = {}, assignments = {}, fleetMode = "all"}
-- mark-id -> {mower = kursus, rake = kursus, <trin> = fælles multi-tool-kursus}: kopier (kun i hukommelsen;
-- efter genindlæsning hentes slå-/rivekurset fra maskinen, som CP selv gemmer kurset på)
ADFieldJobs.courses = {}
@@ -46,6 +47,14 @@ function ADFieldJobs.decide(field, info, assignments, now, env)
end
return {action = "start"}
end
-- mål-afgrøde på en bar mark: klargør og så så snart målet kan sås
if info ~= nil and info.kind == "bare" and field.plan.targetFruit ~= nil and field.plan.targetFruit ~= "" then
local plantable = env ~= nil and env.plantable ~= nil and env.plantable(field.plan.targetFruit) or false
if ADFieldModel.shouldStartBare(field, "bare", plantable) then
if not hasMarker then return {action = "wait"} end
return {action = "start"}
end
end
if not ADFieldModel.isWorking(field) then
local care = ADFieldModel.careNeed(field, info)
if care == nil then return {action = "none"} end
@@ -124,6 +133,7 @@ function ADFieldJobs.load()
local ok, loaded = pcall(ADFieldStorage.load)
if ok then
ADFieldJobs.state.fields, ADFieldJobs.state.fleet = loaded.fields, loaded.fleet
ADFieldJobs.state.fleetMode = loaded.fleetMode or "all"
else
Logging.warning(ADFieldJobs.LOG_PREFIX .. "marker kunne ikke indlæses: %s", tostring(loaded))
end
@@ -143,6 +153,17 @@ function ADFieldJobs.forceReady(id)
ADFieldJobs.setField(ADFieldModel.forceReady(ADFieldJobs.getField(id)))
end
function ADFieldJobs.setTarget(id, fruitName)
ADFieldJobs.setField(ADFieldModel.setTarget(ADFieldJobs.getField(id), fruitName))
ADFieldJobs.log(id, "mål-afgrøde: %s", fruitName ~= "" and fruitName or "ingen")
ADFieldJobs.save()
end
function ADFieldJobs.setFleetMode(mode)
ADFieldJobs.state.fleetMode = mode
ADFieldJobs.save()
end
function ADFieldJobs.toggleFleet(vehicleId)
ADFieldJobs.state.fleet = ADFieldFleet.toggle(ADFieldJobs.state.fleet, vehicleId)
ADFieldJobs.save()
@@ -192,7 +213,14 @@ function ADFieldJobs.environment()
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}
return {
raining = ok and raining == true,
-- kan afgrøden (navn) sås i den aktuelle periode?
plantable = function(name)
local fruit = g_fruitTypeManager ~= nil and g_fruitTypeManager:getFruitTypeByName(name) or nil
return fruit ~= nil and ADCpBridge.isPlantableNow(fruit.index)
end,
}
end
-- Seneste beslutning pr. mark (GUI viser pleje-behov og grunde).
@@ -206,7 +234,7 @@ local function stepField(adEnv, info)
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)
local started = ADFieldModel.start(field, info.kind, info.needs, info.fruitName)
started.lastFruit = info.kind == "harvest" and info.fruitName or field.lastFruit
ADFieldJobs.setField(started)
ADFieldJobs.log(info.id, "starter (%s: %s)", tostring(info.kind), started.stage)
+66 -8
View File
@@ -28,7 +28,7 @@ ADFieldModel.ROLES = {
mowing = "mower", tedding = "tedder", raking = "rake", collecting = "collector",
harvesting = "harvester", mulching = "mulcher", liming = "limer", plowing = "plow",
cultivating = "cultivator", sowing = "seeder", rolling = "roller", weeding = "weeder",
fertilizing = "fertilizer",
fertilizing = "fertilizer", strawCollecting = "collector", baling = "baler", baleCollecting = "baleCollector",
}
ADFieldModel.HELPERS = {harvesting = "unloader"}
ADFieldModel.CARE_STAGES = {fertilizing = true, weeding = true, careLiming = true}
@@ -42,8 +42,11 @@ KNOWN_STAGES.careLiming = true
local DEFAULT_PLAN = {
mode = "manual", product = "auto", rake = false, rigs = 1, unloaders = 1,
mulch = false, lime = true, soil = "auto", sow = false, sowFruit = "", roll = false,
fertilize = true, weed = true,
fertilize = true, weed = true, targetFruit = "", prepAfterMow = false, straw = "chop",
}
local STRAW_VALUES = {chop = true, collect = true, bale = true}
-- halm-trin efter høst (mejetærskeren lægger skår når halmen skal bruges)
local STRAW_STAGES = {collect = {"strawCollecting"}, bale = {"baling", "baleCollecting"}}
local SOIL_VALUES = {auto = true, cultivate = true, plow = true, none = true}
local PRODUCTS = {auto = true, silage = true, hay = true, harvest = true}
@@ -116,6 +119,7 @@ function ADFieldModel.workStages(plan, kind, needs)
local product = ADFieldModel.product(plan, kind)
if product == "harvest" then
local stages = {"harvesting"}
for _, stage in ipairs(STRAW_STAGES[plan.straw or "chop"] or {}) do table.insert(stages, stage) end
for _, stage in ipairs(ADFieldModel.prepStages(plan, needs)) do table.insert(stages, stage) end
return stages
end
@@ -123,12 +127,55 @@ function ADFieldModel.workStages(plan, kind, needs)
if product == "hay" then table.insert(stages, "tedding") end
if plan.rake then table.insert(stages, "raking") end
table.insert(stages, "collecting")
-- mål-afgrøde på en slåmark med en anden afgrøde: efter sidste slæt klargøres og sås målet
if plan.prepAfterMow then
for _, stage in ipairs(ADFieldModel.prepStages(plan, needs)) do table.insert(stages, stage) end
end
return stages
end
-- Krævet bredde for et sæt: kun læssevognen uden riv skal dække slåmaskinens baner.
-- Mål-afgrøde ("vælg mark + afgrøde, glem den"): planen udfyldes ud fra hvad der står på marken nu.
-- Står målet der allerede som slåafgrøde (græs -> græs): bare slå igen og igen. Ellers: høst/slå det der
-- står, klargør og så målet. Kører altid automatisk. Uden mål returneres planen uændret.
function ADFieldModel.applyTarget(plan, kind, currentFruit)
local copy = copyPlan(plan)
if copy.targetFruit == nil or copy.targetFruit == "" then return copy end
copy.mode = "auto"
copy.sowFruit = copy.targetFruit
if kind == "mowable" then
copy.prepAfterMow = currentFruit ~= copy.targetFruit
copy.sow = copy.prepAfterMow
else
copy.prepAfterMow = false
copy.sow = true
end
return copy
end
-- Vælg mål-afgrøde fra menuen ("" = ingen). Med mål kører marken automatisk; en færdig/klar mark sættes
-- til venter, så den kan starte forfra. Uden mål: tilbage til manuel.
function ADFieldModel.setTarget(field, fruitName)
local copy = copyField(field)
copy.plan.targetFruit = fruitName or ""
copy.plan.mode = copy.plan.targetFruit ~= "" and "auto" or "manual"
if not ADFieldModel.isWorking(field) and field.stage == "done" then
copy.stage = "idle"
end
return copy
end
-- Bar mark med mål-afgrøde starter selv klargøring + såning, når målet kan sås nu.
function ADFieldModel.shouldStartBare(field, kind, plantableNow)
if field.plan.targetFruit == nil or field.plan.targetFruit == "" then return false end
-- kun fra idle: efter egen såning står marken "done" (nysået kan læses som bar) — ingen dobbelt-såning
if kind ~= "bare" or field.stage ~= "idle" then return false end
return plantableNow == true
end
-- Krævet bredde for et sæt: kun læssevognen uden riv skal dække slåmaskinens baner (halmskår ligger i
-- høsterens spor — der er breddekravet ligegyldigt).
function ADFieldModel.requiredWidth(field, role)
if role == "collector" and not field.plan.rake then
if role == "collector" and not field.plan.rake and field.stage ~= "strawCollecting" then
return field.mowWidth
end
return nil
@@ -136,6 +183,8 @@ end
-- Hvilket tidligere kursus sættet skal køre: læssevognen følger skårene; de andre laver deres eget.
function ADFieldModel.courseSource(field, role)
-- halm: læssevogn og presser kører høsterens kursus (skåret ligger i mejetærskerens spor)
if field.stage == "strawCollecting" or role == "baler" then return "harvester" end
if role ~= "collector" then return nil end
return field.plan.rake and "rake" or "mower"
end
@@ -205,14 +254,17 @@ local function freshCycle(copy)
copy.mowWidth = nil
copy.mowVehicleId = nil
copy.rakeVehicleId = nil
copy.harvestVehicleId = nil
copy.windrowOffset = nil
copy.careRuns = {}
end
-- Første trin i kæden. En tom kæde (bar mark uden jordbehandling eller såning) -> færdig.
function ADFieldModel.start(field, kind, needs)
-- currentFruit (valgfri): afgrøden på marken nu; med mål-afgrøde udfyldes planen ud fra den.
function ADFieldModel.start(field, kind, needs, currentFruit)
local copy = copyField(field)
local stages = ADFieldModel.workStages(field.plan, kind, needs)
copy.plan = ADFieldModel.applyTarget(copy.plan, kind, currentFruit)
local stages = ADFieldModel.workStages(copy.plan, kind, needs)
copy.stage = stages[1] or "done"
copy.kind = kind or "mowable"
freshCycle(copy)
@@ -232,7 +284,7 @@ function ADFieldModel.advance(field, needs)
local stages = ADFieldModel.workStages(field.plan, field.kind, needs)
copy.stage = "done"
local passed = false
local order = {"mowing", "tedding", "raking", "collecting", "harvesting", "mulching", "liming", "plowing", "cultivating", "sowing", "rolling"}
local order = {"mowing", "tedding", "raking", "collecting", "harvesting", "strawCollecting", "baling", "baleCollecting", "mulching", "liming", "plowing", "cultivating", "sowing", "rolling"}
local rank = {}
for index, stage in ipairs(order) do rank[stage] = index end
for _, stage in ipairs(stages) do
@@ -259,7 +311,7 @@ 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}
local WEATHER_STAGES = {harvesting = true, mowing = true, tedding = true, raking = true, strawCollecting = true, baling = 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"
@@ -379,10 +431,12 @@ function ADFieldModel.toRecord(field)
rigs = plan.rigs, unloaders = plan.unloaders, mulch = flag(plan.mulch), lime = flag(plan.lime),
soil = plan.soil, sow = flag(plan.sow), sowFruit = plan.sowFruit or "", roll = flag(plan.roll),
fertilize = flag(plan.fertilize), weed = flag(plan.weed),
targetFruit = plan.targetFruit or "", prepAfterMow = flag(plan.prepAfterMow), straw = plan.straw or "chop",
kind = field.kind or "", lastFruit = field.lastFruit or "", care = table.concat(care, " "),
mowWidth = field.mowWidth or 0,
mowVehicle = field.mowVehicleId or "",
rakeVehicle = field.rakeVehicleId or "",
harvestVehicle = field.harvestVehicleId or "",
windrowOffset = field.windrowOffset ~= nil and string.format("%.2f", field.windrowOffset) or "",
}
end
@@ -415,12 +469,16 @@ function ADFieldModel.fromRecord(record)
plan.roll = readFlag(record.roll, false)
plan.fertilize = readFlag(record.fertilize, true)
plan.weed = readFlag(record.weed, true)
plan.targetFruit = record.targetFruit or ""
plan.prepAfterMow = readFlag(record.prepAfterMow, false)
plan.straw = STRAW_VALUES[record.straw] and record.straw or "chop"
field.kind = readText(record.kind)
field.lastFruit = readText(record.lastFruit)
for stage, runs in string.gmatch(record.care or "", "(%w+)=(%d+)") do field.careRuns[stage] = tonumber(runs) end
field.mowWidth = (record.mowWidth or 0) > 0 and record.mowWidth or nil
field.mowVehicleId = readText(record.mowVehicle)
field.rakeVehicleId = readText(record.rakeVehicle)
field.harvestVehicleId = readText(record.harvestVehicle)
field.windrowOffset = tonumber(record.windrowOffset)
return field
end
+5 -3
View File
@@ -11,8 +11,8 @@ ADFieldStorage.FIELD_TYPES = {
id = "Int", stage = "String", mode = "String", product = "String", rake = "Int", store = "String",
rigs = "Int", unloaders = "Int", mulch = "Int", lime = "Int", soil = "String", sow = "Int",
sowFruit = "String", roll = "Int", fertilize = "Int", weed = "Int",
kind = "String", lastFruit = "String", care = "String",
mowWidth = "Float", mowVehicle = "String", rakeVehicle = "String", windrowOffset = "String",
kind = "String", lastFruit = "String", care = "String", targetFruit = "String", prepAfterMow = "Int", straw = "String",
mowWidth = "Float", mowVehicle = "String", rakeVehicle = "String", harvestVehicle = "String", windrowOffset = "String",
}
function ADFieldStorage.getPath()
@@ -46,6 +46,7 @@ function ADFieldStorage.save(state)
ADFieldStorage.writeRecord(xmlFile, string.format("%s.field(%d)", ADFieldStorage.ROOT, index), ADFieldModel.toRecord(field))
index = index + 1
end
xmlFile:setString(ADFieldStorage.ROOT .. "#fleetMode", state.fleetMode or "all")
local vehicleIndex = 0
for vehicleId in pairs(state.fleet) do
xmlFile:setString(string.format("%s.fleet.vehicle(%d)#id", ADFieldStorage.ROOT, vehicleIndex), vehicleId)
@@ -57,11 +58,12 @@ function ADFieldStorage.save(state)
end
function ADFieldStorage.load()
local result = {fields = {}, fleet = {}}
local result = {fields = {}, fleet = {}, fleetMode = "all"}
local path = ADFieldStorage.getPath()
if path == nil or not fileExists(path) then return result end
local xmlFile = XMLFile.load("ADSmartPickupFields", path)
if xmlFile == nil then return result end
result.fleetMode = xmlFile:getString(ADFieldStorage.ROOT .. "#fleetMode") == "ticked" and "ticked" or "all"
xmlFile:iterate(ADFieldStorage.ROOT .. ".field", function(_, key)
local field = ADFieldModel.fromRecord(ADFieldStorage.readRecord(xmlFile, key))
if field.id ~= nil then result.fields[field.id] = field end
+61 -3
View File
@@ -113,7 +113,7 @@ function ADFieldWork.getRigs(adEnv)
local x, z = vehiclePosition(vehicle)
local rig = {
id = id, vehicle = vehicle, role = role, label = ADRuns.vehicleLabel(vehicle),
enabled = state.fleet[id] == true,
enabled = ADFieldFleet.isEnabled(state.fleetMode, state.fleet, id),
-- optaget: vores egen opgave, et Smart Pickup-flow, eller AutoDrive/Courseplay kører den allerede
-- (fx spillerens egen parkeringstur) — den må ikke kapres midt i en anden kørsel
busy = state.assignments[id] ~= nil or ADRunsController.getRunForVehicle(vehicle) ~= nil
@@ -166,6 +166,9 @@ end
-- Hvilken vare markens trin fører til lager: skår (ensilage), tørskår (hø) eller høstet afgrøde.
function ADFieldWork.storeFillType(field, info)
if field.stage == "harvesting" then return info.harvestFillType end
if field.stage == "strawCollecting" or field.stage == "baling" or field.stage == "baleCollecting" then
return g_fillTypeManager:getFillTypeIndexByName("STRAW")
end
local product = ADFieldModel.product(field.plan, field.kind or info.kind)
return product == "hay" and info.hay or info.windrow
end
@@ -202,11 +205,12 @@ local function coursesOf(fieldId)
return ADFieldJobs.courses[fieldId]
end
-- Kurset et trin skal genbruge ("mower"/"rake"): gemt kopi, ellers maskinens nuværende kursus.
-- Kurset et trin skal genbruge ("mower"/"rake"/"harvester"): gemt kopi, ellers maskinens nuværende kursus.
local function sourceCourse(adEnv, field, source)
local saved = coursesOf(field.id)[source]
if saved ~= nil then return saved end
local vehicleId = source == "rake" and field.rakeVehicleId or field.mowVehicleId
local vehicleIds = {mower = field.mowVehicleId, rake = field.rakeVehicleId, harvester = field.harvestVehicleId}
local vehicleId = vehicleIds[source]
if vehicleId == nil then return nil end
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
if ADRunsController.getVehicleId(vehicle) == vehicleId then return ADCpBridge.getCourse(vehicle) end
@@ -435,6 +439,51 @@ local function beginUnloader(adEnv, field, info, assignment)
assignment.handle = handle
end
-- Ballesamler: Courseplays ballefinder; fuld vogn tømmes af AutoDrive i nærmeste ballelager (palleskur der
-- tager halmballer), som læssevognen. Uden lager stopper den når den er fuld.
local function beginBaleCollector(adEnv, field, info, assignment)
local vehicle = assignment.vehicle
local farmId = vehicle:getOwnerFarmId()
local straw = g_fillTypeManager:getFillTypeIndexByName("STRAW")
local isReachable = function(markerId) return ADUnloadWait.hasRouteTo(adEnv, vehicle, markerId) end
local store = ADSources.findPalletStore(adEnv, vehicle, straw, nil, isReachable, farmId)
if store ~= nil then
pcall(function()
local module = stateModule(vehicle)
module:setMode(adEnv.AutoDrive.MODE_PICKUPANDDELIVER)
module:setFirstMarkerByWayPointId(info.markerId)
module:setSecondMarkerByWayPointId(store.wayPointId)
module:setStartHelper(true)
module.usedHelper = helperCp(adEnv)
enableParkAtJobFinished(vehicle)
module:raiseDirtyFlag()
end)
assignment.adHandlesEnd = true
log(field.id, "ballesamler '%s' kører fulde læs til %s", ADRuns.vehicleLabel(vehicle), tostring(store.name))
else
log(field.id, "intet ballelager med plads og vej fundet — ballesamleren stopper når den er fuld")
end
local point = info.points[1] or {x = info.x, z = info.z}
assignment.phase = "starting"
assignment.since = now()
local handle, err = ADCpBridge.startBaleFinder(vehicle, point.x, point.z, function(ok, reason)
if ok then
assignment.phase = "working"
assignment.since = now()
log(field.id, "Courseplay startet (ballesamler)")
else
ADFieldWork.release(assignment)
fail(field.id, "ballesamler: " .. tostring(reason))
end
end)
if handle == nil then
ADFieldWork.release(assignment)
fail(field.id, "ballesamler: " .. tostring(err))
return
end
assignment.handle = handle
end
-- Multi-tool: følgere venter på lederens kursus for trinnet og kopierer det med deres bane.
local function courseOptions(adEnv, field, assignment)
local source = ADFieldModel.courseSource(field, assignment.role)
@@ -459,6 +508,12 @@ end
local function beginWork(adEnv, field, info, assignment)
local vehicle = assignment.vehicle
if assignment.role == "unloader" then return beginUnloader(adEnv, field, info, assignment) end
if assignment.role == "baleCollector" then return beginBaleCollector(adEnv, field, info, assignment) end
-- høsteren: halmskår efter planen (hakkes = fra; samles op/presses = skår)
if assignment.role == "harvester" then
local keepStraw = field.plan.straw == "collect" or field.plan.straw == "bale"
ADCpBridge.setStrawSwath(vehicle, keepStraw)
end
local opts, source = courseOptions(adEnv, field, assignment)
if opts == nil then
-- følger uden lederens kursus endnu
@@ -504,6 +559,9 @@ local function beginWork(adEnv, field, info, assignment)
elseif assignment.role == "rake" then
note(field.id, {rakeVehicleId = assignment.vehicleId})
rememberCourse(field, "rake", vehicle)
elseif assignment.role == "harvester" then
note(field.id, {harvestVehicleId = assignment.vehicleId})
rememberCourse(field, "harvester", vehicle)
end
if assignment.leader and (assignment.groupSize or 1) > 1 then
rememberCourse(field, field.stage, vehicle)
+66 -11
View File
@@ -9,6 +9,7 @@ SmartPickupFrame.FIELD_ROW_KINDS = {
fieldProduct = true, fieldMode = true, fieldRake = true, fieldStore = true, fleetVehicle = true,
fieldRigs = true, fieldUnloaders = true, fieldMulch = true, fieldLime = true, fieldSoil = true,
fieldSow = true, fieldSowFruit = true, fieldRoll = true, fieldFertilize = true, fieldWeed = true,
fieldTarget = true, fleetMode = true, fieldStraw = true,
}
SmartPickupFrame.ROLE_KEYS = {
mower = "spu_field_role_mower", tedder = "spu_field_role_tedder", rake = "spu_field_role_rake",
@@ -16,6 +17,7 @@ SmartPickupFrame.ROLE_KEYS = {
mulcher = "spu_field_role_mulcher", limer = "spu_field_role_limer", plow = "spu_field_role_plow",
cultivator = "spu_field_role_cultivator", seeder = "spu_field_role_seeder", roller = "spu_field_role_roller",
weeder = "spu_field_role_weeder", fertilizer = "spu_field_role_fertilizer",
baler = "spu_field_role_baler", baleCollector = "spu_field_role_baleCollector",
}
SmartPickupFrame.SOIL_VALUES = {"auto", "cultivate", "plow", "none"}
-- simple Ja/Nej-valg på planen: rækketype -> plan-felt
@@ -24,7 +26,8 @@ SmartPickupFrame.PLAN_TOGGLES = {
fieldFertilize = "fertilize", fieldWeed = "weed",
}
-- valg med flere muligheder: rækketype -> plan-felt
SmartPickupFrame.PLAN_CHOICES = {fieldRigs = "rigs", fieldUnloaders = "unloaders", fieldSoil = "soil", fieldSowFruit = "sowFruit"}
SmartPickupFrame.PLAN_CHOICES = {fieldRigs = "rigs", fieldUnloaders = "unloaders", fieldSoil = "soil", fieldSowFruit = "sowFruit", fieldStraw = "straw"}
SmartPickupFrame.STRAW_VALUES = {"chop", "collect", "bale"}
-- ---- tekster ----
@@ -62,12 +65,36 @@ function SmartPickupFrame:fieldStatusText(field, info)
if decision ~= nil and decision.care ~= nil and not ADFieldModel.isWorking(field) then
text = text .. " — " .. string.format(self.i18n:getText("spu_field_careNeed"), self.i18n:getText("spu_field_stage_" .. decision.care))
end
local target = field.plan.targetFruit
if target ~= nil and target ~= "" and info.kind == "bare" and field.stage == "idle" and not ADFieldJobs.environment().plantable(target) then
text = text .. " — " .. string.format(self.i18n:getText("spu_field_waitSowing"), self:fruitTitle(target))
end
local reason = self:fieldReasonText(field.reason)
if reason ~= nil then text = text .. ": " .. reason end
return text
end
-- Hvad moden gør med marken frem mod mål-afgrøden (én linje under valget).
function SmartPickupFrame:targetSummary(field, info)
local target = self:fruitTitle(field.plan.targetFruit)
if info.kind == "bare" then
return string.format(self.i18n:getText("spu_field_target_bare"), target)
end
if info.fruitName == field.plan.targetFruit and info.kind == "mowable" then
return string.format(self.i18n:getText("spu_field_target_keep"), target)
end
return string.format(self.i18n:getText("spu_field_target_switch"), tostring(info.cropTitle or "-"), target)
end
function SmartPickupFrame:fruitTitle(name)
local fruit = g_fruitTypeManager ~= nil and name ~= nil and name ~= "" and g_fruitTypeManager:getFruitTypeByName(name) or nil
return fruit ~= nil and fruit.fillType ~= nil and tostring(fruit.fillType.title) or tostring(name)
end
function SmartPickupFrame:fieldPlanText(field, info)
if field.plan.targetFruit ~= nil and field.plan.targetFruit ~= "" then
return "-> " .. self:fruitTitle(field.plan.targetFruit)
end
local text = self.i18n:getText(ADFieldView.planKey(field.plan, info.kind))
if field.plan.rake and info.kind == "mowable" then text = text .. " + " .. self.i18n:getText("spu_field_plan_rake") end
if field.plan.sow and info.kind ~= "mowable" then text = text .. " + " .. self.i18n:getText("spu_field_plan_sow") end
@@ -116,9 +143,9 @@ local function numberTexts(from, to)
return texts, values
end
-- Afgrøder der kan sås (første valg: samme som den høstede).
function SmartPickupFrame:sowFruitChoices()
local texts, values = {self.i18n:getText("spu_field_sowSame")}, {""}
-- Afgrøder der kan sås (første valg: samme som den høstede / ingen mål-afgrøde).
function SmartPickupFrame:sowFruitChoices(firstKey)
local texts, values = {self.i18n:getText(firstKey or "spu_field_sowSame")}, {""}
for _, fruit in ipairs(g_fruitTypeManager ~= nil and g_fruitTypeManager.fruitTypes or {}) do
if fruit.allowsSeeding ~= false and fruit.fillType ~= nil and fruit.name ~= nil then
table.insert(texts, tostring(fruit.fillType.title or fruit.name))
@@ -165,27 +192,49 @@ function SmartPickupFrame:fillFieldDetail(adEnv, info)
local yesNo = {i18n:getText("ui_no"), i18n:getText("ui_yes")}
local crop = info.kind == "bare" and i18n:getText("spu_field_bare") or tostring(info.cropTitle or "-")
self:addTextRow(string.format(i18n:getText("spu_field_section"), info.id, crop))
self:addToggleRow("fieldMode", i18n:getText("spu_field_mode"), {i18n:getText("spu_field_mode_manual"), i18n:getText("spu_field_mode_auto")}, plan.mode == "auto")
local targetTexts, targetValues = self:sowFruitChoices("spu_field_target_none")
self:addChoiceRow("fieldTarget", i18n:getText("spu_field_target"), targetTexts, targetValues, plan.targetFruit or "")
local hasTarget = plan.targetFruit ~= nil and plan.targetFruit ~= ""
if hasTarget then
self:addTextRow(" " .. self:targetSummary(field, info))
else
self:addToggleRow("fieldMode", i18n:getText("spu_field_mode"), {i18n:getText("spu_field_mode_manual"), i18n:getText("spu_field_mode_auto")}, plan.mode == "auto")
end
local rigTexts, rigValues = numberTexts(1, ADFieldModel.MAX_RIGS)
self:addChoiceRow("fieldRigs", i18n:getText("spu_field_rigs"), rigTexts, rigValues, plan.rigs or 1)
if info.kind == "mowable" then
-- græs-valgene (ensilage/hø, riv) også når målet er en slåafgrøde (fx græs på en kornmark)
local targetMowable = false
if hasTarget and ADFields ~= nil and g_fruitTypeManager ~= nil then
local targetFruit = g_fruitTypeManager:getFruitTypeByName(plan.targetFruit)
targetMowable = targetFruit ~= nil and ADFields.getMowable()[targetFruit.index] ~= nil
end
if info.kind == "mowable" or targetMowable then
local isHay = ADFieldModel.product(plan, "mowable") == "hay"
self:addToggleRow("fieldProduct", i18n:getText("spu_field_plan"), {i18n:getText("spu_field_plan_silage"), i18n:getText("spu_field_plan_hay")}, isHay)
self:addToggleRow("fieldRake", i18n:getText("spu_field_rake"), yesNo, plan.rake == true)
else
end
-- klargøring: høst-/bare marker, og græsmarker der skal skifte til en anden mål-afgrøde
if info.kind ~= "mowable" or (hasTarget and info.fruitName ~= plan.targetFruit) then
if info.kind == "harvest" then
local unloaderTexts, unloaderValues = numberTexts(1, ADFieldModel.MAX_UNLOADERS)
self:addChoiceRow("fieldUnloaders", i18n:getText("spu_field_unloaders"), unloaderTexts, unloaderValues, plan.unloaders or 1)
local strawTexts = {}
for _, value in ipairs(SmartPickupFrame.STRAW_VALUES) do table.insert(strawTexts, i18n:getText("spu_field_straw_" .. value)) end
self:addChoiceRow("fieldStraw", i18n:getText("spu_field_straw"), strawTexts, SmartPickupFrame.STRAW_VALUES, plan.straw or "chop")
end
self:addTextRow(i18n:getText(info.kind == "harvest" and "spu_field_section_after" or "spu_field_section_prep"))
self:addToggleRow("fieldMulch", i18n:getText("spu_field_mulch"), yesNo, plan.mulch == true)
local soilTexts = {}
for _, value in ipairs(SmartPickupFrame.SOIL_VALUES) do table.insert(soilTexts, i18n:getText("spu_field_soil_" .. value)) end
self:addChoiceRow("fieldSoil", i18n:getText("spu_field_soil"), soilTexts, SmartPickupFrame.SOIL_VALUES, plan.soil or "auto")
self:addToggleRow("fieldSow", i18n:getText("spu_field_sow"), yesNo, plan.sow == true)
if plan.sow then
local fruitTexts, fruitValues = self:sowFruitChoices()
self:addChoiceRow("fieldSowFruit", i18n:getText("spu_field_sowFruit"), fruitTexts, fruitValues, plan.sowFruit or "")
if not hasTarget then
self:addToggleRow("fieldSow", i18n:getText("spu_field_sow"), yesNo, plan.sow == true)
if plan.sow then
local fruitTexts, fruitValues = self:sowFruitChoices()
self:addChoiceRow("fieldSowFruit", i18n:getText("spu_field_sowFruit"), fruitTexts, fruitValues, plan.sowFruit or "")
end
end
if hasTarget or plan.sow then
self:addToggleRow("fieldRoll", i18n:getText("spu_field_roll"), yesNo, plan.roll == true)
end
end
@@ -286,6 +335,8 @@ end
function SmartPickupFrame:fillFleetRows(adEnv)
local i18n = self.i18n
self:addTextRow(i18n:getText("spu_field_section_fleet"))
local useAll = ADFieldJobs.state.fleetMode ~= "ticked"
self:addToggleRow("fleetMode", i18n:getText("spu_field_fleetAll"), {i18n:getText("ui_no"), i18n:getText("ui_yes")}, useAll)
local rigs = ADFieldJobs.getRigs(adEnv)
table.sort(rigs, function(a, b)
if a.role ~= b.role then return a.role < b.role end
@@ -332,8 +383,12 @@ function SmartPickupFrame:onClickFieldRow(state, data)
local id = frame.selectedFieldId
if data.kind == "fleetVehicle" then
ADFieldJobs.toggleFleet(data.vehicleId)
elseif data.kind == "fleetMode" then
ADFieldJobs.setFleetMode(isOn and "all" or "ticked")
elseif id == nil then
return
elseif data.kind == "fieldTarget" then
ADFieldJobs.setTarget(id, data.values ~= nil and data.values[state] or "")
elseif SmartPickupFrame.PLAN_CHOICES[data.kind] ~= nil then
local value = data.values ~= nil and data.values[state] or nil
if value ~= nil then ADFieldJobs.setPlan(id, {[SmartPickupFrame.PLAN_CHOICES[data.kind]] = value}) end
+16
View File
@@ -153,6 +153,22 @@
<text name="spu_field_weed" text="Bekæmp ukrudt"/>
<text name="spu_field_notice_done" text="er færdig"/>
<text name="spu_field_reason_rain" text="venter på tørvejr"/>
<text name="spu_field_target" text="Afgrøde på marken (moden passer resten)"/>
<text name="spu_field_target_none" text="Ingen — planlæg selv"/>
<text name="spu_field_target_bare" text="Klargøres og tilsås med %s, så snart det er såtid"/>
<text name="spu_field_target_keep" text="%s står allerede: slås og samles op, hver gang den er klar"/>
<text name="spu_field_target_switch" text="%s får lov at vokse færdig og høstes/slås; derefter klargøres og tilsås med %s"/>
<text name="spu_field_waitSowing" text="venter på såtid for %s"/>
<text name="spu_field_fleetAll" text="Brug alle ledige maskiner (Nej = kun med flueben)"/>
<text name="spu_field_role_baler" text="ballepresser"/>
<text name="spu_field_role_baleCollector" text="ballesamler"/>
<text name="spu_field_stage_strawCollecting" text="Halm samles"/>
<text name="spu_field_stage_baling" text="Presses"/>
<text name="spu_field_stage_baleCollecting" text="Baller samles"/>
<text name="spu_field_straw" text="Halm efter høst"/>
<text name="spu_field_straw_chop" text="Hakkes (spredes på marken)"/>
<text name="spu_field_straw_collect" text="Samles op til silo"/>
<text name="spu_field_straw_bale" text="Presses til baller og samles"/>
<text name="spu_button_start" text="Start run"/>
<text name="spu_button_stop" text="Stop run"/>
<text name="spu_button_new" text="Nyt run"/>
+16
View File
@@ -153,6 +153,22 @@
<text name="spu_field_weed" text="Control weeds"/>
<text name="spu_field_notice_done" text="is done"/>
<text name="spu_field_reason_rain" text="waiting for dry weather"/>
<text name="spu_field_target" text="Crop on the field (the mod handles the rest)"/>
<text name="spu_field_target_none" text="None — plan it yourself"/>
<text name="spu_field_target_bare" text="Prepared and sown with %s as soon as it is sowing time"/>
<text name="spu_field_target_keep" text="%s is already there: mowed and collected every time it is ready"/>
<text name="spu_field_target_switch" text="%s is left to finish growing and is harvested/mowed; then the field is prepared and sown with %s"/>
<text name="spu_field_waitSowing" text="waiting for sowing time for %s"/>
<text name="spu_field_fleetAll" text="Use all free machines (No = only ticked ones)"/>
<text name="spu_field_role_baler" text="baler"/>
<text name="spu_field_role_baleCollector" text="bale collector"/>
<text name="spu_field_stage_strawCollecting" text="Collecting straw"/>
<text name="spu_field_stage_baling" text="Baling"/>
<text name="spu_field_stage_baleCollecting" text="Collecting bales"/>
<text name="spu_field_straw" text="Straw after harvest"/>
<text name="spu_field_straw_chop" text="Chopped (spread on the field)"/>
<text name="spu_field_straw_collect" text="Collected to a silo"/>
<text name="spu_field_straw_bale" text="Baled and collected"/>
<text name="spu_button_start" text="Start run"/>
<text name="spu_button_stop" text="Stop run"/>
<text name="spu_button_new" text="New run"/>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107">
<author>masterdraco</author>
<version>1.26.0.0</version>
<version>1.27.0.0</version>
<title>
<en>AD Smart Pickup</en>
</title>
+21 -3
View File
@@ -1,6 +1,6 @@
# AD Smart Pickup — brugermanual
Version 1.26.0 · Farming Simulator 25 · tillæg til AutoDrive
Version 1.27.0 · Farming Simulator 25 · tillæg til AutoDrive
*English version: [USER_MANUAL.md](USER_MANUAL.md)*
@@ -538,14 +538,31 @@ maskiner. Kræver **Courseplay** ud over AutoDrive.
| **Efter høst / bar mark** | (Stub hakkes) → (Kalkes) → Pløjes eller Kultiveres → (Sås) → (Tromles) |
| **Pleje mens afgrøden vokser** | Gødes · Ukrudt bekæmpes · Kalkes — når jorden trænger |
### Vælg og glem: afgrøde på marken
Det nemmeste: vælg **Afgrøde på marken** (fx *Hvede*) øverst under marken. Så passer moden resten:
- **Vokser der noget nu**, får det lov at vokse færdig, bliver passet (gødning, ukrudt, kalk) og høstet
eller slået, når det er klart.
- **Bagefter** klargøres marken og tilsås med din afgrøde. Er det ikke såtid, venter marken ("venter på
såtid for …").
- **Står din afgrøde der allerede** (græs → græs), bliver den bare slået og samlet op, hver gang den er klar.
- **Bar mark:** klargøres og tilsås, så snart det er såtid.
- Derefter kører cyklussen af sig selv: vækst → pleje → høst → klargøring → såning → …
Med en afgrøde valgt kører marken automatisk. Linjen under valget fortæller, hvad moden vil gøre. Du kan
stadig vælge ensilage/hø, halm, antal maskiner, jordbehandling og pleje. Vælg *Ingen — planlæg selv* for
at styre marken manuelt som før.
### Opsætning
1. **En markør pr. mark.** Lav en AutoDrive-markør ved markkanten med navnet `F` + marknummeret, fx
`F109`. Maskinerne kører dertil ad vejen, før Courseplay tager over. Højst 400 m fra marken. Mangler
den, står marken med `!` og sendes ikke ud.
2. **Faste sæt.** Redskabet skal sidde på traktoren (eller være selvkørende). Moden kobler aldrig på.
3. **Markflåde.** Nederst på fanen står alle køretøjer med et markredskab, med rolle og bredde. Sæt
flueben ved dem, moden må sende ud. Køretøjer i et flow, køretøjer AutoDrive/Courseplay allerede kører,
3. **Markflåde.** Nederst på fanen står alle køretøjer med et markredskab, med rolle og bredde. Med
**Brug alle ledige maskiner** = Ja (standard) finder moden selv maskinerne; med Nej bruges kun dem med
flueben. Køretøjer i et flow, køretøjer AutoDrive/Courseplay allerede kører,
og køretøjer du selv kører, bruges aldrig. En sprøjte/spreder har først en rolle, når den har fyld på
(gødning → gødningsspreder, kalk → kalkspreder, sprøjtemiddel → ukrudtsmaskine).
4. **Parkering.** Sæt en AutoDrive-parkering på hvert sæt (på redskabet). Færdige sæt kører dertil.
@@ -571,6 +588,7 @@ Klik på en mark i tabellen. Under tabellen står:
| **Plan** (slåafgrøde) | Ensilage eller Hø |
| **Riv** (slåafgrøde) | Riv skårene sammen før opsamling |
| **Aflæsningsvogne** (høst) | 1–3 vogne der følger høsteren og kører til lager |
| **Halm efter høst** (høst) | Hakkes (spredes), Samles op til silo, eller Presses til baller, som en ballesamler kører til et ballelager |
| **Stubbehakning** | Hak stubben efter høst |
| **Jordbehandling** | Auto (pløj når jorden trænger, ellers kultivér), Kultivér, Pløj eller Ingen |
| **Så bagefter** + **Afgrøde at så** | Så efter jordbehandlingen. Standard: samme afgrøde som blev høstet |
+29 -2
View File
@@ -1,4 +1,4 @@
# Testplan — Markarbejde (fanen Marker), version 1.26.0.0
# Testplan — Markarbejde (fanen Marker), version 1.27.0.0
Stor test-session. Kør testene i rækkefølge: de første bygger opsætningen, de senere bygger på den.
Hver test har **Gør**, **Forvent** og **Log** (linjer i `log.txt`, der beviser at det virkede). Loglinjer
@@ -11,7 +11,7 @@ samme rækkefølge.
## 0. Forberedelse
- [ ] **0.1 Version.** Log: `Available mod: … (Version: 1.26.0.0) FS25_ADSmartPickup`.
- [ ] **0.1 Version.** Log: `Available mod: … (Version: 1.27.0.0) FS25_ADSmartPickup`.
- [ ] **0.2 Fanen Marker åbner.** ESC → Smart Pickup → fanen **Marker**. Forvent: "Marker indlæses …"
i få sekunder, derefter én række pr. ejet mark (også korn-, roe- og bare marker, ikke kun græs).
Afgrøde-kolonnen viser afgrøden eller "bar jord".
@@ -73,6 +73,13 @@ Rest fra 25/9: slåning og opsamlingsstart virkede; afslutningen blev aldrig set
- [ ] **3.3 Forkert skærebord.** Høster der ikke kan tage afgrøden → status "ingen høster i
markflåden kan tage opgaven (afgrøde/frø/ukrudt)". Ingen udsendelse.
- [ ] **3.4 Flere aflæsningsvogne.** "Aflæsningsvogne under høst" = 2 → to vogne sendes (én ad gangen).
- [ ] **3.6 Halm samles op.** Halm efter høst = *Samles op til silo*. Mejetærskeren lægger skår
(Courseplay-indstilling halmskår = til), derefter `harvesting -> strawCollecting`, en læssevogn kører
**høsterens kursus** (`harvester-kursus kopieret`) og kører halmen til en silo der tager STRAW.
- [ ] **3.7 Halm til baller.** Halm = *Presses til baller*: `-> baling` (ballepresser på høsterens
kursus) → `-> baleCollecting` (ballesamler/Universal Autoload, `Courseplay startet (ballesamler)`),
fulde læs til ballelager (`ballesamler … kører fulde læs til …`).
- [ ] **3.8 Halm hakkes.** Halm = *Hakkes*: mejetærskeren lægger IKKE skår (Courseplay halmskår = fra).
- [ ] **3.5 Regn.** Når det regner: høst/slå/vend/riv sendes ikke ud, status "venter på tørvejr".
Kørende maskiner fortsætter.
@@ -122,6 +129,24 @@ Afgrøde "Samme som høstet", Tromle Ja. Kalk Ja.
- [ ] **7.2 For få maskiner.** 3 pr. trin men kun 2 ledige → status "multi-tool: slåmaskine — kun 2 af 3 ledige".
- [ ] **7.3 Læssevogne på multi-kursus.** Efter 7.1 sendes 2 læssevogne, én pr. bane.
## 9. Vælg og glem (afgrøde på marken)
- [ ] **9.1 Kornmark → hvede.** En mark med byg der vokser: vælg *Afgrøde på marken* = Hvede. Linjen siger
"Byg får lov at vokse færdig og høstes; derefter klargøres og tilsås med Hvede". Kørsel skjules (auto).
Når byggen er moden: høst → (halm) → klargøring → såning af **hvede** (ikke byg). Log:
`mål-afgrøde: WHEAT`, `starter (harvest: harvesting)`, `sowing`.
- [ ] **9.2 Bar mark.** Bar mark med mål = Hvede: starter selv klargøring + såning, når det er hvede-såtid.
Uden for såtid: status "venter på såtid for Hvede".
- [ ] **9.3 Græs → græs.** Græsmark med mål = Græs: slås og samles op hver gang, ingen pløjning/såning.
- [ ] **9.4 Græs → hvede.** Græsmark med mål = Hvede: slås og samles op én gang, derefter klargøring
(jordbehandling-valget vises) og såning af hvede.
- [ ] **9.5 Hele året.** Lad en mark med mål køre en hel sæson uden at røre den: vækst → pleje → høst →
klargøring → såning → ... uden at hænge og uden dobbelt-såning.
- [ ] **9.6 Alle ledige maskiner.** Markflåde: *Brug alle ledige maskiner* = Ja (standard): maskiner uden
flueben sendes også ud. = Nej: kun maskiner med flueben. Maskiner du selv kører, maskiner i flows og
maskiner AutoDrive/Courseplay allerede kører, bruges aldrig.
- [ ] **9.7 Fjern målet.** Vælg *Ingen — planlæg selv*: marken går tilbage til Manuel.
## 8. Robusthed
- [ ] **8.1 Genindlæsning midt i et trin.** Gem og genstart under fx opsamling → marken fortsætter på
@@ -146,3 +171,5 @@ Afgrøde "Samme som høstet", Tromle Ja. Kalk Ja.
| Sideriver | fortegn på forskydningen | Læssevognen kører ved siden af skåret |
| Påfyldning | AutoDrive LOAD til den læssestation moden fandt (AutoDrives eget automatiske mål er slået fra i AutoDrive) | Sættet står ved påfyldningen og læsser ikke, eller kører ikke tilbage |
| Ukrudt | `weedSystem`-værdi ≥ 2 = tæt | Ukrudtsbehandling uden ukrudt, eller aldrig |
| Nysået som "bar" | nysået afgrøde læses som voksende | Marken står "Færdig" efter såning (ingen dobbelt-såning, men ingen næste høst-start før den er moden) |
| Ballesamler | Courseplays BALE_FINDER + AutoDrive til ballelager | Ballesamleren starter ikke, eller kører ikke fulde læs væk |
+21 -3
View File
@@ -1,6 +1,6 @@
# AD Smart Pickup — user manual
Version 1.26.0 · Farming Simulator 25 · add-on for AutoDrive
Version 1.27.0 · Farming Simulator 25 · add-on for AutoDrive
*Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)*
@@ -544,14 +544,31 @@ and which machines. Requires **Courseplay** in addition to AutoDrive.
| **After harvest / bare field** | (Mulch) → (Lime) → Plow or Cultivate → (Sow) → (Roll) |
| **Care while the crop grows** | Fertilize · Control weeds · Lime — when the soil needs it |
### Select and forget: crop on the field
The easiest way: pick **Crop on the field** (such as *Wheat*) at the top of the field. The mod does the rest:
- **Whatever grows now** is left to finish, cared for (fertilizer, weeds, lime) and harvested or mowed when
ready.
- **Afterwards** the field is prepared and sown with your crop. If it is not sowing time, the field waits
("waiting for sowing time for …").
- **If your crop is already there** (grass → grass), it is simply mowed and collected every time it is ready.
- **Bare field:** prepared and sown as soon as it is sowing time.
- After that the cycle runs by itself: growth → care → harvest → preparation → sowing → …
With a crop chosen, the field runs automatically. The line below the choice tells you what the mod will do.
You can still choose silage/hay, straw, number of machines, soil work and care. Choose *None — plan it
yourself* to run the field manually as before.
### Setup
1. **One marker per field.** Create an AutoDrive marker at the field edge named `F` + the field number,
such as `F109`. Machines drive there on the road before Courseplay takes over. At most 400 m from the
field. Without it, the field shows `!` and nothing is sent.
2. **Fixed rigs.** The implement must be attached (or self-propelled). The mod never attaches.
3. **Field fleet.** At the bottom are all vehicles with a field implement, with role and width. Tick the
ones the mod may send out. Vehicles in a flow, vehicles AutoDrive/Courseplay are already driving, and
3. **Field fleet.** At the bottom are all vehicles with a field implement, with role and width. With
**Use all free machines** = Yes (default) the mod finds the machines itself; with No only ticked ones
are used. Vehicles in a flow, vehicles AutoDrive/Courseplay are already driving, and
vehicles you drive are never used. A sprayer/spreader only has a role once it holds product
(fertilizer → fertilizer spreader, lime → lime spreader, herbicide → weeding machine).
4. **Parking.** Set an AutoDrive parking spot on each rig (on the implement). Finished rigs drive there.
@@ -577,6 +594,7 @@ Click a field in the table. Below the table:
| **Plan** (mowable) | Silage or Hay |
| **Rake** (mowable) | Rake the swaths before collecting |
| **Unloading trailers** (harvest) | 1–3 trailers that follow the harvester and drive to storage |
| **Straw after harvest** (harvest) | Chopped (spread), Collected to a silo, or Baled, and a bale collector takes the bales to a bale store |
| **Mulch stubble** | Mulch the stubble after harvest |
| **Soil work** | Auto (plow when the soil needs it, else cultivate), Cultivate, Plow or None |
| **Sow afterwards** + **Crop to sow** | Sow after soil work. Default: the crop that was harvested |
+12
View File
@@ -82,5 +82,17 @@ check("FL10 grund", whyMany, "notEnough")
check("FL10 ledige/krævet", args[1] .. "/" .. args[2], "2/3")
check("FL10 ingen med rollen", select(2, F.pickMany({m1}, "tedder", 1, 0, 0)), "noRig")
-- FL11: markflådens tilstand: alle ledige maskiner (standard) eller kun med flueben
check("FL11 alle: uden flueben med", F.isEnabled("all", {}, "v1"), true)
check("FL11 flueben: uden flueben ude", F.isEnabled("ticked", {}, "v1"), false)
check("FL11 flueben: med flueben", F.isEnabled("ticked", {v1 = true}, "v1"), true)
check("FL11 ukendt tilstand = alle", F.isEnabled(nil, {}, "v1"), true)
-- FL12: ballepresser og ballesamler (ballevogn/Universal Autoload som Courseplays ballesamler kan bruge)
check("FL12 ballepresser", F.roleFromSpecs({spec_baler = true}), "baler")
check("FL12 ballesamler", F.roleFromSpecs({canBaleCollect = true}), "baleCollector")
check("FL12 ballesamler før aflæsningsvogn", F.roleFromSpecs({canBaleCollect = true, canUnload = true}), "baleCollector")
check("FL12 høster før presser", F.roleFromSpecs({spec_combine = true, spec_baler = true}), "harvester")
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)
+9
View File
@@ -89,5 +89,14 @@ 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")
-- J12: bar mark med mål-afgrøde starter selv når målet kan sås; ellers venter den
local bareInfo = {id = 90, markerId = 9, kind = "bare", readyRatio = 0, needs = {}}
local bareTarget = M.with(M.newField(90), {plan = {targetFruit = "WHEAT", mode = "auto"}})
local canSow = {plantable = function(name) return name == "WHEAT" end}
check("J12 bar + såtid -> start", J.decide(bareTarget, bareInfo, {}, 0, canSow).action, "start")
check("J12 bar + ikke såtid -> intet", J.decide(bareTarget, bareInfo, {}, 0, {plantable = function() return false end}).action, "none")
check("J12 bar uden markør -> vent", J.decide(bareTarget, {id = 90, kind = "bare"}, {}, 0, canSow).action, "wait")
check("J12 voksende mark med mål -> lad den vokse", J.decide(bareTarget, {id = 90, markerId = 9, kind = "harvest", growing = true, readyRatio = 0.3}, {}, 0, canSow).action, "none")
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)
+55
View File
@@ -234,5 +234,60 @@ check("M18 uden forespørgsel -> venter", M.onReadiness(M.forceReady(M.newField(
check("M18 vejrfølsom: høst", M.isWeatherSensitive("harvesting", {}, "harvest"), true)
check("M18 vejrfølsom: kultivering ikke", M.isWeatherSensitive("cultivating", {}, "harvest"), false)
-- M19: mål-afgrøde — "vælg mark + afgrøde, glem den"
local target = M.with(M.newField(80), {plan = {targetFruit = "WHEAT"}})
local wheatNow = M.applyTarget(target.plan, "harvest", "BARLEY")
check("M19 mål -> auto", wheatNow.mode, "auto")
check("M19 høst byg -> så hvede", wheatNow.sow, true)
check("M19 såafgrøde = mål", wheatNow.sowFruit, "WHEAT")
check("M19 kæde: høst -> kultivér -> så", table.concat(M.workStages(wheatNow, "harvest", {}), ","), "harvesting,cultivating,sowing")
local grassToWheat = M.applyTarget(target.plan, "mowable", "GRASS")
check("M19 græs -> hvede: slå, saml op, så klargør og så", table.concat(M.workStages(grassToWheat, "mowable", {}), ","), "mowing,collecting,cultivating,sowing")
local grassTarget = M.applyTarget(M.with(M.newField(81), {plan = {targetFruit = "GRASS"}}).plan, "mowable", "GRASS")
check("M19 græs -> græs: kun slå og saml op", table.concat(M.workStages(grassTarget, "mowable", {}), ","), "mowing,collecting")
check("M19 græs -> græs: ingen såning", grassTarget.sow, false)
local bareTarget = M.applyTarget(target.plan, "bare", nil)
check("M19 bar mark -> klargør og så", table.concat(M.workStages(bareTarget, "bare", {plow = true}), ","), "plowing,sowing")
check("M19 uden mål: plan urørt", M.applyTarget(M.newField(82).plan, "harvest", "BARLEY").mode, "manual")
local started = M.start(target, "harvest", {}, "BARLEY")
check("M19 start anvender målet", started.plan.sowFruit, "WHEAT")
check("M19 start: høst først", started.stage, "harvesting")
check("M19 målet gemmes", M.fromRecord(M.toRecord(started)).plan.targetFruit, "WHEAT")
check("M19 klargøring efter slåning gemmes", M.fromRecord(M.toRecord(M.start(target, "mowable", {}, "GRASS"))).plan.prepAfterMow, true)
-- M20: skal en bar mark med mål starte? (kun når målafgrøden kan sås nu)
check("M20 bar + sås nu -> start", M.shouldStartBare(target, "bare", true), true)
check("M20 bar + ikke såtid -> vent", M.shouldStartBare(target, "bare", false), false)
check("M20 uden mål -> ingen auto-start", M.shouldStartBare(M.newField(83), "bare", true), false)
check("M20 voksende mark -> nej", M.shouldStartBare(target, "harvest", true), false)
check("M20 arbejdende mark -> nej", M.shouldStartBare(started, "bare", true), false)
check("M20 færdig (lige sået) -> nej, ingen dobbelt-såning", M.shouldStartBare(M.with(target, {stage = "done"}), "bare", true), false)
-- M21: vælg mål-afgrøde fra menuen: auto, og en færdig mark nulstilles så den kan starte
local chosen = M.setTarget(M.with(M.newField(84), {stage = "done"}), "CANOLA")
check("M21 mål sat", chosen.plan.targetFruit, "CANOLA"); check("M21 auto", chosen.plan.mode, "auto")
check("M21 færdig -> venter", chosen.stage, "idle")
check("M21 arbejdende mark beholder trin", M.setTarget(started, "CANOLA").stage, "harvesting")
local cleared = M.setTarget(chosen, "")
check("M21 mål fjernet", cleared.plan.targetFruit, ""); check("M21 tilbage til manuel", cleared.plan.mode, "manual")
-- M22: halm efter høst — hakkes (standard), samles op, eller presses til baller og samles
local strawBase = {soil = "none", sow = false, lime = false}
check("M22 hakkes: kun høst", table.concat(M.workStages(strawBase, "harvest", {}), ","), "harvesting")
check("M22 samles op", table.concat(M.workStages(M.with(M.newField(1), {plan = {soil = "none", lime = false, straw = "collect"}}).plan, "harvest", {}), ","), "harvesting,strawCollecting")
check("M22 baller", table.concat(M.workStages(M.with(M.newField(1), {plan = {soil = "none", lime = false, straw = "bale"}}).plan, "harvest", {}), ","), "harvesting,baling,baleCollecting")
local baleChain = M.start(M.with(M.newField(2), {plan = {straw = "bale", soil = "cultivate", lime = false}}), "harvest", {})
check("M22 høst -> presses", M.advance(baleChain, {}).stage, "baling")
check("M22 presses -> baller samles", M.advance(M.advance(baleChain, {}), {}).stage, "baleCollecting")
check("M22 -> kultivér", M.advance(M.advance(M.advance(baleChain, {}), {}), {}).stage, "cultivating")
check("M22 roller", M.roleFor("baling") .. "," .. M.roleFor("baleCollecting") .. "," .. M.roleFor("strawCollecting"), "baler,baleCollector,collector")
check("M22 halm-læssevogn følger høsterens kursus", M.courseSource(M.with(M.newField(3), {stage = "strawCollecting"}), "collector"), "harvester")
check("M22 presser følger høsterens kursus", M.courseSource(M.with(M.newField(3), {stage = "baling"}), "baler"), "harvester")
check("M22 græs-læssevogn følger stadig slåkurset", M.courseSource(M.with(M.newField(3), {stage = "collecting"}), "collector"), "mower")
check("M22 halm-læssevogn uden breddekrav", M.requiredWidth(M.with(M.newField(3), {stage = "strawCollecting", mowWidth = 40}), "collector"), nil)
check("M22 presning vejrfølsom", M.isWeatherSensitive("baling", {}, "harvest"), true)
check("M22 ballesamling ikke vejrfølsom", M.isWeatherSensitive("baleCollecting", {}, "harvest"), false)
check("M22 gemmes", M.fromRecord(M.toRecord(M.with(M.newField(4), {plan = {straw = "bale"}}))).plan.straw, "bale")
check("M22 ukendt -> hakkes", M.fromRecord({id = 5, straw = "x"}).plan.straw, "chop")
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)