Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a073ae047 | |||
| 322f7b1c30 | |||
| 293d66156f | |||
| af71548b0b |
@@ -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,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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,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
@@ -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 |
|
||||
|
||||
@@ -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
@@ -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 |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user