feat(markarbejde): manuel transport af brede høstere — flueben pr. høster i markflåden; moden beder spilleren køre den til marken og montere skærebordet (dialog + påmindelse hvert 5. min), overtager når den står på marken med passende skærebord og spilleren er steget ud (eller ved Start nu), tømmevogne først når høsten kører, ingen AD-parkering bagefter (v1.38.0.0)

This commit is contained in:
2026-09-27 14:05:27 +02:00
parent 12647087ef
commit 6afcae6fe1
11 changed files with 231 additions and 34 deletions
+30 -6
View File
@@ -16,7 +16,8 @@ ADFieldJobs.tickCount = 0
ADFieldJobs.MAX_PASSES = 2
-- fleetMode: "all" = alle ledige maskiner (standard, "vælg og glem"), "ticked" = kun med flueben
-- courseRecords: vehicleId -> post i kursus-registret (ADCourseCache)
ADFieldJobs.state = {fields = {}, fleet = {}, assignments = {}, fleetMode = "all", courseRecords = {}}
-- manual: {[vehicleId] = true} = høstere spilleren selv kører til marken (for brede til vejene)
ADFieldJobs.state = {fields = {}, fleet = {}, assignments = {}, fleetMode = "all", courseRecords = {}, manual = {}}
-- 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 = {}
@@ -26,7 +27,8 @@ ADFieldJobs.timer = 500
ADFieldJobs.now = 0
ADFieldJobs.loaded = false
ADFieldJobs.ACTIVE_PHASES = {driving = true, starting = true, waitingCourse = true, working = true}
-- awaitingPlayer: høster med manuel transport, spilleren kører den til marken (tæller som i gang)
ADFieldJobs.ACTIVE_PHASES = {driving = true, starting = true, waitingCourse = true, working = true, awaitingPlayer = true}
-- Andel græs i mellemhøjde (høstbart, men halv ydelse) der får en afbrudt slåning til at vente.
ADFieldJobs.REGROW_RATIO = 0.1
@@ -45,7 +47,7 @@ local function cropGone(stage, info)
end
-- Ligger (x, z) inden for omridset? (lige-ulige-reglen; kanten tæller som inde)
local function insidePolygon(x, z, verts)
function ADFieldJobs.insidePolygon(x, z, verts)
local inside, j = false, #verts
for i = 1, #verts do
local a, b = verts[i], verts[j]
@@ -59,7 +61,7 @@ end
-- fields = {{id, stage, verts = {{x, z}}}}
function ADFieldJobs.resumeField(x, z, role, fields)
for _, field in ipairs(fields or {}) do
if #(field.verts or {}) >= 3 and ADFieldModel.roleFor(field.stage) == role and insidePolygon(x, z, field.verts) then
if #(field.verts or {}) >= 3 and ADFieldModel.roleFor(field.stage) == role and ADFieldJobs.insidePolygon(x, z, field.verts) then
return field.id
end
end
@@ -104,7 +106,7 @@ function ADFieldJobs.decide(field, info, assignments, now, env)
if not hasMarker then return {action = "wait", care = care} end
return {action = "care", stage = care}
end
local active, finished, helpers = 0, 0, 0
local active, finished, helpers, awaiting = 0, 0, 0, 0
for _, assignment in ipairs(assignments) do
if assignment.stage == nil or assignment.stage == field.stage then
local isActive = ADFieldJobs.ACTIVE_PHASES[assignment.phase]
@@ -112,6 +114,7 @@ function ADFieldJobs.decide(field, info, assignments, now, env)
if isActive then helpers = helpers + 1 end
else
if isActive then active = active + 1 end
if assignment.phase == "awaitingPlayer" then awaiting = awaiting + 1 end
if assignment.phase == "finished" then finished = finished + 1 end
end
end
@@ -138,7 +141,8 @@ function ADFieldJobs.decide(field, info, assignments, now, env)
end
-- hovedsættene arbejder: fyld op med hjælpere (aflæsningsvogne) en ad gangen
local helperRole = ADFieldModel.helperFor(field.stage)
if helperRole ~= nil and helpers < ADFieldModel.maxHelpers(field.stage, field.plan) and hasMarker and (field.retryAt or 0) <= now then
-- tømmevogne først når en høster faktisk er i gang (ikke mens spilleren kører den derud)
if helperRole ~= nil and active > awaiting and helpers < ADFieldModel.maxHelpers(field.stage, field.plan) and hasMarker and (field.retryAt or 0) <= now then
return {action = "dispatch", role = helperRole, count = 1, helper = true}
end
return {action = "none"}
@@ -182,6 +186,7 @@ function ADFieldJobs.load()
ADFieldJobs.state.fields, ADFieldJobs.state.fleet = loaded.fields, loaded.fleet
ADFieldJobs.state.fleetMode = loaded.fleetMode or "all"
ADFieldJobs.state.courseRecords = loaded.courseRecords or {}
ADFieldJobs.state.manual = loaded.manual or {}
else
Logging.warning(ADFieldJobs.LOG_PREFIX .. "marker kunne ikke indlæses: %s", tostring(loaded))
end
@@ -193,6 +198,20 @@ function ADFieldJobs.setPlan(id, changes)
ADFieldJobs.save()
end
-- "Start nu" på en mark hvor moden venter på spilleren (manuel transport): overtag uden at vente
ADFieldJobs.takeoverRequested = {}
function ADFieldJobs.requestTakeover(id)
ADFieldJobs.takeoverRequested[id] = true
end
-- Venter moden på at spilleren kører en høster til marken? (assignment eller nil)
function ADFieldJobs.awaitingPlayer(fieldId)
for _, assignment in pairs(ADFieldJobs.state.assignments) do
if assignment.fieldId == fieldId and assignment.phase == "awaitingPlayer" then return assignment end
end
return nil
end
function ADFieldJobs.requestStart(id)
ADFieldJobs.note(id, {startRequested = true})
end
@@ -212,6 +231,11 @@ function ADFieldJobs.setFleetMode(mode)
ADFieldJobs.save()
end
function ADFieldJobs.toggleManual(vehicleId)
ADFieldJobs.state.manual = ADFieldFleet.toggle(ADFieldJobs.state.manual or {}, vehicleId)
ADFieldJobs.save()
end
function ADFieldJobs.toggleFleet(vehicleId)
ADFieldJobs.state.fleet = ADFieldFleet.toggle(ADFieldJobs.state.fleet, vehicleId)
ADFieldJobs.save()
+10 -1
View File
@@ -73,6 +73,11 @@ function ADFieldStorage.save(state)
xmlFile:setString(string.format("%s.fleet.vehicle(%d)#id", ADFieldStorage.ROOT, vehicleIndex), vehicleId)
vehicleIndex = vehicleIndex + 1
end
local manualIndex = 0
for vehicleId in pairs(state.manual or {}) do
xmlFile:setString(string.format("%s.manual.vehicle(%d)#id", ADFieldStorage.ROOT, manualIndex), vehicleId)
manualIndex = manualIndex + 1
end
local courseIndex = 0
for _, record in pairs(state.courseRecords or {}) do
ADFieldStorage.writeCourseRecord(xmlFile, string.format("%s.courses.course(%d)", ADFieldStorage.ROOT, courseIndex), record)
@@ -84,7 +89,7 @@ function ADFieldStorage.save(state)
end
function ADFieldStorage.load()
local result = {fields = {}, fleet = {}, fleetMode = "all", courseRecords = {}}
local result = {fields = {}, fleet = {}, fleetMode = "all", courseRecords = {}, manual = {}}
local path = ADFieldStorage.getPath()
if path == nil or not fileExists(path) then return result end
local xmlFile = XMLFile.load("ADSmartPickupFields", path)
@@ -98,6 +103,10 @@ function ADFieldStorage.load()
local id = xmlFile:getString(key .. "#id")
if id ~= nil and id ~= "" then result.fleet[id] = true end
end)
xmlFile:iterate(ADFieldStorage.ROOT .. ".manual.vehicle", function(_, key)
local id = xmlFile:getString(key .. "#id")
if id ~= nil and id ~= "" then result.manual[id] = true end
end)
xmlFile:iterate(ADFieldStorage.ROOT .. ".courses.course", function(_, key)
local record = ADCourseCache ~= nil and ADCourseCache.newRecord(ADFieldStorage.readCourseRecord(xmlFile, key)) or nil
if record ~= nil then result.courseRecords[record.vehicle] = record end
+68 -4
View File
@@ -138,6 +138,8 @@ function ADFieldWork.getRigs(adEnv)
busy = state.assignments[id] ~= nil or isAdActive(vehicle) or ADCpBridge.isWorking(vehicle)
or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle)),
controlled = isControlledByPlayer(vehicle),
-- manuel transport: spilleren kører den til marken (for bred til vejene med skærebord)
manual = (state.manual or {})[id] == true,
width = ADCpBridge.getWorkWidth(vehicle), x = x, z = z,
-- tom spreder kan flere ting (gødning og kalk): alle dens roller
roles = specs.sprayRoles, filledRole = specs.filledRole, dumpable = specs.dumpable,
@@ -300,7 +302,7 @@ end
local function acceptFor(field, info, role)
if role == "harvester" then
local fruit = info.fruitIndex
return function(candidate) return fruit ~= nil and candidate.fruits ~= nil and candidate.fruits[fruit] == true end
return function(candidate) return ADManualTransport.acceptsHarvester(candidate, fruit) end
elseif role == "seeder" then
local fruitIndex = ADFieldWork.sowFruitIndex(field)
return function(candidate) return candidate.hasSeeds and fruitIndex ~= nil and candidate.seedFruits[fruitIndex] == true end
@@ -351,6 +353,19 @@ local function refillPlan(adEnv, fieldId, vehicle, role, x, z)
return {fillType = fillType, empty = empty, refillId = refillId}
end
-- Manuel transport: bed spilleren køre høsteren til marken (dialog første gang, ellers besked i hjørnet).
local function askPlayer(field, info, assignment, withDialog)
local text = string.format("F%s skal høstes: kør '%s' til %s, montér skærebordet og stig ud — så overtager Smart Pickup",
tostring(field.id), ADRuns.vehicleLabel(assignment.vehicle), tostring(info.markerName or ("F" .. tostring(field.id))))
assignment.remindedAt = now()
log(field.id, "venter på spilleren: %s", text)
if withDialog and InfoDialog ~= nil and InfoDialog.show ~= nil then
pcall(InfoDialog.show, "Smart Pickup: " .. text)
else
ADFieldJobs.notify(text, true)
end
end
function ADFieldWork.dispatch(adEnv, field, info, decision)
local role = decision.role
local ok, reason = preflight(field, role)
@@ -409,6 +424,13 @@ function ADFieldWork.dispatch(adEnv, field, info, decision)
groupSize = decision.helper and 1 or count, lane = count > 1 and lanes[index] or nil, leader = index == 1,
fillType = decision.helper and info.harvestFillType or nil,
}
if role == "harvester" and rig.manual and not assignment.helper then
-- for bred til vejene: spilleren kører den selv til marken og monterer skærebordet
assignment.phase, assignment.manual = "awaitingPlayer", true
assignment.startX, assignment.startZ = rig.x, rig.z
ADFieldJobs.state.assignments[rig.id] = assignment
askPlayer(field, info, assignment, true)
else
local refill = refillPlan(adEnv, field.id, rig.vehicle, role, info.x, info.z)
if refill ~= nil and refill.empty and refill.refillId == nil then
-- tom og intet sted at fylde op: ud på marken ville den bare køre kurset uden at sprede
@@ -431,6 +453,7 @@ function ADFieldWork.dispatch(adEnv, field, info, decision)
assignment.lane ~= nil and string.format(", bane %d", assignment.lane) or "")
end
end
end
note(field.id, {reason = false})
end
@@ -790,14 +813,53 @@ local function finishAssignment(adEnv, assignment)
end
log(assignment.fieldId, "%s '%s' færdig", assignment.role, ADRuns.vehicleLabel(vehicle))
-- læssevogn/påfyldte sæt parkeres af AutoDrive selv (onCpFinished med parkering sat)
local park = not assignment.adHandlesEnd and parkMarkerId(adEnv, vehicle) or nil
-- manuel transport: ALDRIG ud på vejen med AutoDrive (skærebordet sidder på) — spilleren henter den
local park = not assignment.adHandlesEnd and not assignment.manual and parkMarkerId(adEnv, vehicle) or nil
if park ~= nil then startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end
if assignment.manual then
ADFieldJobs.notify(string.format("F%s høstet — afmontér skærebordet og kør '%s' hjem", tostring(assignment.fieldId), ADRuns.vehicleLabel(vehicle)), true)
end
restoreToolOffset(assignment)
assignment.phase = "finished"
table.insert(ADFieldJobs.finished, assignment)
ADFieldJobs.state.assignments[assignment.vehicleId] = nil
end
local MANUAL_REASON_TEXT = {
notThere = "kør den til marken",
noHeader = "montér skærebordet",
wrongHeader = "skærebordet kan ikke høste afgrøden — montér det rigtige",
playerInside = "stig ud, så overtager Smart Pickup",
}
-- Manuel transport: overtag når høsteren står på marken med passende skærebord og spilleren er steget ud
-- (eller når spilleren trykker "Start nu"); påmind hvert 5. minut indtil da.
local function followManual(adEnv, field, info, assignment)
if field == nil or info == nil then return ADFieldWork.release(assignment) end
local vehicle = assignment.vehicle
local x, z = vehiclePosition(vehicle)
local atField = #(info.verts or {}) >= 3 and ADFieldJobs.insidePolygon(x, z, info.verts)
if not atField and info.markerId ~= nil then
local wayPoint = adEnv.ADGraphManager:getWayPointById(info.markerId)
atField = wayPoint ~= nil and math.sqrt((wayPoint.x - x) ^ 2 + (wayPoint.z - z) ^ 2) <= ADFieldWork.ARRIVE_DISTANCE
end
local forced = ADFieldJobs.takeoverRequested[field.id] == true
ADFieldJobs.takeoverRequested[field.id] = nil
local ok, why = ADManualTransport.readiness({atField = atField, fruits = ADCpBridge.getHarvestFruits(vehicle) or {},
fruit = info.fruitIndex, controlled = isControlledByPlayer(vehicle), forced = forced})
if ok then
log(field.id, "overtager '%s'%s", ADRuns.vehicleLabel(vehicle), forced and " (Start nu)" or "")
return beginWork(adEnv, field, info, assignment)
end
local reason = MANUAL_REASON_TEXT[why] or tostring(why)
note(field.id, {reason = {text = string.format("venter på dig: '%s' — %s", ADRuns.vehicleLabel(vehicle), reason)}})
if forced then
ADFieldJobs.notify(string.format("F%s: kan ikke starte '%s' endnu — %s", tostring(field.id), ADRuns.vehicleLabel(vehicle), reason), true)
elseif ADManualTransport.shouldRemind(assignment.remindedAt, now()) then
askPlayer(field, info, assignment, false)
end
end
local function followWorking(adEnv, field, assignment)
local vehicle = assignment.vehicle
if assignment.role == "rake" and field.windrowOffset == nil and now() - assignment.since >= ADFieldWork.WINDROW_MEASURE_MS
@@ -859,7 +921,7 @@ function ADFieldWork.resumeAfterLoad(adEnv)
if fieldId ~= nil then
ADFieldJobs.state.assignments[rig.id] = {vehicleId = rig.id, vehicle = vehicle, fieldId = fieldId, role = rig.role,
stage = stageById[fieldId], helper = false, phase = "working", since = now(), width = rig.width,
groupSize = 1, leader = true, resumed = true}
groupSize = 1, leader = true, resumed = true, manual = rig.manual == true}
-- lager til læssevognens rest, som ved en normal start
local assignment = ADFieldJobs.state.assignments[rig.id]
local field, info = ADFieldJobs.getField(fieldId), ADFields.getInfo(fieldId)
@@ -886,7 +948,7 @@ function ADFieldWork.follow(adEnv, assignment)
-- førersædet og kigge må ikke slippe det (in-game 25/9).
local steeredByAi = ADCpBridge.isWorking(vehicle) or isAdActive(vehicle) or assignment.phase == "starting"
local speed = vehicle.getLastSpeed ~= nil and vehicle:getLastSpeed() or 0
if isControlledByPlayer(vehicle) and not steeredByAi and speed > ADFieldWork.PLAYER_DRIVING_KMH then
if assignment.phase ~= "awaitingPlayer" and isControlledByPlayer(vehicle) and not steeredByAi and speed > ADFieldWork.PLAYER_DRIVING_KMH then
log(assignment.fieldId, "'%s' frigivet — spilleren kører det", ADRuns.vehicleLabel(vehicle))
return ADFieldWork.release(assignment)
end
@@ -935,6 +997,8 @@ function ADFieldWork.follow(adEnv, assignment)
end
elseif assignment.phase == "working" then
followWorking(adEnv, field, assignment)
elseif assignment.phase == "awaitingPlayer" then
followManual(adEnv, field, info, assignment)
end
end
+32
View File
@@ -0,0 +1,32 @@
-- AD Manual Transport
-- Brede høstere (mejetærsker med skærebord) kan ikke køre på vejene: spilleren kører dem til marken, monterer
-- skærebordet og stiger ud; moden overtager derfra. Rene regler (testbare uden spillet); spil-delen ligger i
-- adFieldWork.
ADManualTransport = {}
ADManualTransport.REMIND_MS = 300000 -- påmindelse hvert 5. minut, mens moden venter på spilleren
-- Må høsteren vælges til marken? Manuel: ja — skærebordet monteres først på marken (tjekkes ved overtagelse).
-- Ellers skal det monterede skærebord kunne tage afgrøden.
function ADManualTransport.acceptsHarvester(candidate, fruit)
if fruit == nil then return false end
if candidate.manual then return true end
return candidate.fruits ~= nil and candidate.fruits[fruit] == true
end
-- Klar til overtagelse? s = {atField, fruits = {[fruit] = true}, fruit, controlled, forced}.
-- Returnerer true, eller false + grund: "notThere" | "noHeader" | "wrongHeader" | "playerInside".
-- forced ("Start nu"): sted og spiller springes over, skærebordet tjekkes stadig.
function ADManualTransport.readiness(s)
if not s.forced and not s.atField then return false, "notThere" end
local fruits = s.fruits or {}
if next(fruits) == nil then return false, "noHeader" end
if s.fruit == nil or not fruits[s.fruit] then return false, "wrongHeader" end
if not s.forced and s.controlled then return false, "playerInside" end
return true
end
function ADManualTransport.shouldRemind(lastAt, now)
return lastAt == nil or now - lastAt >= ADManualTransport.REMIND_MS
end
+18 -3
View File
@@ -6,7 +6,7 @@
-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua).
-- Markflådens rækker (vehiclePrefab/ingredientPrefab) routes til onClickFieldRow.
SmartPickupFrame.FIELD_ROW_KINDS = {fleetVehicle = true, fleetMode = true}
SmartPickupFrame.FIELD_ROW_KINDS = {fleetVehicle = true, fleetMode = true, fleetManual = true}
SmartPickupFrame.ROLE_KEYS = {
mower = "spu_field_role_mower", tedder = "spu_field_role_tedder", rake = "spu_field_role_rake",
collector = "spu_field_role_collector", harvester = "spu_field_role_harvester", unloader = "spu_field_role_unloader",
@@ -238,6 +238,12 @@ function SmartPickupFrame:fillFleetRows(adEnv)
local title = string.format(" %s — %s, %.1f m%s", tostring(rig.label), self:roleText(rig.role), rig.width or 0, self:rigDetails(rig))
row:getDescendantByName("title"):setText(title)
row:getDescendantByName("option"):setState(rig.enabled and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
if rig.role == "harvester" then
-- for bred til vejene med skærebord: spilleren kører den til marken, moden overtager derfra
local option = self:addToggleRow("fleetManual", string.format(i18n:getText("spu_field_manualTransport"), tostring(rig.label)),
{i18n:getText("ui_no"), i18n:getText("ui_yes")}, rig.manual)
self.rowData[option] = {kind = "fleetManual", vehicleId = rig.id}
end
end
if #rigs == 0 then
self:addTextRow(" " .. i18n:getText("spu_field_noRigs"))
@@ -266,7 +272,9 @@ function SmartPickupFrame:refreshFieldRows(adEnv)
local selected = self.selectedFieldId ~= nil and ADFieldJobs.getField(self.selectedFieldId) or nil
local info = self.selectedFieldId ~= nil and ADFields.getInfo(self.selectedFieldId) or nil
local isWorking = selected ~= nil and ADFieldModel.isWorking(selected)
self.fieldStartButtonInfo.text = self.i18n:getText(isWorking and "spu_field_button_stop" or "spu_field_button_start")
local awaiting = self.selectedFieldId ~= nil and ADFieldJobs.awaitingPlayer(self.selectedFieldId) ~= nil
self.fieldStartButtonInfo.text = self.i18n:getText(awaiting and "spu_field_button_takeover"
or (isWorking and "spu_field_button_stop" or "spu_field_button_start"))
self.fieldStartButtonInfo.disabled = selected == nil or info == nil or (not isWorking and info.markerId == nil)
end
@@ -279,6 +287,8 @@ function SmartPickupFrame:onClickFieldRow(state, data)
ADFieldJobs.toggleFleet(data.vehicleId)
elseif data.kind == "fleetMode" then
ADFieldJobs.setFleetMode(isOn and "all" or "ticked")
elseif data.kind == "fleetManual" then
if ((ADFieldJobs.state.manual or {})[data.vehicleId] == true) ~= isOn then ADFieldJobs.toggleManual(data.vehicleId) end
end
frame:requestRebuild()
end)
@@ -290,7 +300,12 @@ function SmartPickupFrame:onClickFieldStartStop()
local id = frame.selectedFieldId
if id == nil then return end
local field = ADFieldJobs.getField(id)
if ADFieldModel.isWorking(field) then
if ADFieldJobs.awaitingPlayer(id) ~= nil then
-- manuel transport: overtag nu (skærebordet tjekkes stadig)
ADFieldJobs.requestTakeover(id)
frame.statusOverride = string.format(frame.i18n:getText("spu_field_takeoverRequested"), id)
frame.statusOverrideTicks = SmartPickupFrame.STATUS_OVERRIDE_TICKS
elseif ADFieldModel.isWorking(field) then
ADFieldJobs.stopField(frame.getAdEnv(), id)
else
local decision = ADFieldJobs.lastDecision[id]
+3
View File
@@ -59,6 +59,9 @@
<text name="spu_month_upcomingOpen" text="F%d: %s — kan også sås næste måned"/>
<text name="spu_month_upcomingLast" text="F%d: %s — OBS: sidste såmåned"/>
<text name="spu_field_button_start" text="Start nu"/>
<text name="spu_field_manualTransport" text=" Manuel transport af %s (for bred til vejene)"/>
<text name="spu_field_button_takeover" text="Start nu"/>
<text name="spu_field_takeoverRequested" text="F%d: overtager høsteren nu, hvis skærebordet passer"/>
<text name="spu_field_button_stop" text="Stop mark"/>
<text name="spu_field_noMarker" text="! mangler markør F%d"/>
<text name="spu_field_stage_idle" text="Venter"/>
+3
View File
@@ -59,6 +59,9 @@
<text name="spu_month_upcomingOpen" text="F%d: %s — can also be sown next month"/>
<text name="spu_month_upcomingLast" text="F%d: %s — NOTE: last sowing month"/>
<text name="spu_field_button_start" text="Start now"/>
<text name="spu_field_manualTransport" text=" Manual transport of %s (too wide for roads)"/>
<text name="spu_field_button_takeover" text="Start now"/>
<text name="spu_field_takeoverRequested" text="F%d: taking over the harvester now if the header fits"/>
<text name="spu_field_button_stop" text="Stop field"/>
<text name="spu_field_noMarker" text="! missing marker F%d"/>
<text name="spu_field_stage_idle" text="Waiting"/>
+2 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107">
<author>masterdraco</author>
<version>1.37.2.0</version>
<version>1.38.0.0</version>
<title>
<en>AD Smart Pickup</en>
</title>
@@ -38,6 +38,7 @@
<sourceFile filename="adCpBridge.lua"/>
<sourceFile filename="adCourseCache.lua"/>
<sourceFile filename="adFieldStorage.lua"/>
<sourceFile filename="adManualTransport.lua"/>
<sourceFile filename="adFieldWork.lua"/>
<sourceFile filename="adFieldSiloPlanner.lua"/>
<sourceFile filename="adFieldSiloConfig.lua"/>
+3
View File
@@ -52,6 +52,9 @@ local withHarvester = {{phase = "working", stage = "harvesting", helper = false}
local dHelper = J.decide(harvesting, harvestInfo, withHarvester, 0)
check("J8 så aflæsningsvogn", dHelper.role, "unloader"); check("J8 hjælper", dHelper.helper, true)
check("J8 vogn på plads -> intet", J.decide(harvesting, harvestInfo, {withHarvester[1], {phase = "working", stage = "harvesting", helper = true}}, 0).action, "none")
-- manuel transport: høsteren venter på spilleren -> ingen høster mere og endnu ingen tømmevogn
local awaiting = {{phase = "awaitingPlayer", stage = "harvesting", helper = false}}
check("J8b venter på spilleren -> intet (ingen ny høster, ingen tømmevogn)", J.decide(harvesting, harvestInfo, awaiting, 0).action, "none")
-- høsteren færdig, afgrøden væk (marken er nu bar) -> videre, selv om vognen stadig kører
local afterInfo = {id = 50, markerId = 7, kind = "bare", readyRatio = 0}
check("J8 høst færdig -> videre", J.decide(harvesting, afterInfo, {{phase = "finished", stage = "harvesting"}, {phase = "working", stage = "harvesting", helper = true}}, 0).action, "advance")
+6
View File
@@ -92,5 +92,11 @@ ADFieldStorage.save({fields = {}, fleet = {}, fleetMode = "all"})
local old = ADFieldStorage.load()
check("ST7 gammel fil: tomt register", next(old.courseRecords), nil)
-- ST8: manuel transport pr. maskine gemmes og indlæses; gammel fil uden -> tom
disk = {}
ADFieldStorage.save({fields = {}, fleet = {}, fleetMode = "all", manual = {vehicleCombine = true}})
check("ST8 manuel transport indlæst", ADFieldStorage.load().manual.vehicleCombine, true)
check("ST8 gammel fil: ingen manuelle", next(old.manual or {}), nil)
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)
+37
View File
@@ -0,0 +1,37 @@
-- Kør: luajit tests/test_adManualTransport.lua (fra repo-roden)
-- Manuel transport af brede høstere (in-game 27/9: mejetærskere med skærebord er for brede til vejene).
dofile("FS25_ADSmartPickup/adManualTransport.lua")
local M = ADManualTransport
local failures = 0
local function check(name, actual, expected)
if actual == expected then print("OK " .. name) else
failures = failures + 1
print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual)))
end
end
local WHEAT, BARLEY = 1, 2
-- valg af høster
check("manuel høster uden skærebord kan vælges", M.acceptsHarvester({manual = true, fruits = {}}, WHEAT), true)
check("automatisk høster uden skærebord kan ikke", M.acceptsHarvester({fruits = {}}, WHEAT), false)
check("automatisk høster med passende skærebord", M.acceptsHarvester({fruits = {[WHEAT] = true}}, WHEAT), true)
check("ukendt afgrøde -> ingen", M.acceptsHarvester({manual = true}, nil), false)
-- klar til overtagelse
local header = {[WHEAT] = true}
local function ready(s) local ok, why = M.readiness(s); return ok and "ok" or why end
check("hjemme -> notThere", ready({atField = false, fruits = {}, fruit = WHEAT}), "notThere")
check("på marken uden skærebord -> noHeader", ready({atField = true, fruits = {}, fruit = WHEAT}), "noHeader")
check("forkert skærebord -> wrongHeader", ready({atField = true, fruits = {[BARLEY] = true}, fruit = WHEAT}), "wrongHeader")
check("spilleren sidder i -> playerInside", ready({atField = true, fruits = header, fruit = WHEAT, controlled = true}), "playerInside")
check("klar", ready({atField = true, fruits = header, fruit = WHEAT}), "ok")
check("Start nu: spiller i og ikke på marken -> ok", ready({atField = false, fruits = header, fruit = WHEAT, controlled = true, forced = true}), "ok")
check("Start nu tjekker stadig skærebordet", ready({atField = false, fruits = {}, fruit = WHEAT, forced = true}), "noHeader")
-- påmindelser
check("første gang -> påmind", M.shouldRemind(nil, 1000), true)
check("for tidligt", M.shouldRemind(1000, 1000 + M.REMIND_MS - 1), false)
check("efter 5 min", M.shouldRemind(1000, 1000 + M.REMIND_MS), true)
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)