diff --git a/FS25_ADSmartPickup/adFieldFleet.lua b/FS25_ADSmartPickup/adFieldFleet.lua
index abfa943..4908644 100644
--- a/FS25_ADSmartPickup/adFieldFleet.lua
+++ b/FS25_ADSmartPickup/adFieldFleet.lua
@@ -78,6 +78,43 @@ end
-- Nærmeste brugbare sæt. Grund når intet passer: noRig (ingen med flueben og rollen), cannotDo (ingen kan
-- tage opgaven, fx afgrøden), allBusy, tooWide/tooNarrow (bredden, med tal fra den første).
-- accept (valgfri): candidate -> bool, fx "høsteren kan tage hvede".
+-- Faste maskiner: rigIdsByField = {[fieldId] = {id, ...}}. En maskine der er fast på en ANDEN mark må
+-- ikke bruges her. Har marken selv faste maskiner med rollen, bruges kun de (marken venter på sin egen);
+-- ellers de frie. (in-game 26/9: sprøjten "til F22" blev sendt til F109.)
+function ADFieldFleet.filterDedicated(candidates, fieldId, role, rigIdsByField)
+ local ownerOf, own = {}, {}
+ for otherId, ids in pairs(rigIdsByField or {}) do
+ for _, id in ipairs(ids or {}) do
+ ownerOf[id] = otherId
+ if otherId == fieldId then own[id] = true end
+ end
+ end
+ local ownWithRole = {}
+ for _, candidate in ipairs(candidates or {}) do
+ local hasRole = candidate.role == role or (candidate.roles ~= nil and candidate.roles[role])
+ if own[candidate.id] and hasRole then table.insert(ownWithRole, candidate) end
+ end
+ if #ownWithRole > 0 then return ownWithRole end
+ local free = {}
+ for _, candidate in ipairs(candidates or {}) do
+ if ownerOf[candidate.id] == nil then table.insert(free, candidate) end
+ end
+ return free
+end
+
+-- Gør en maskine fast på én mark (fjernes fra andre marker). Returnerer ny tabel {[fieldId] = ids}.
+function ADFieldFleet.dedicate(rigIdsByField, fieldId, vehicleId, isOn)
+ local result = {}
+ for otherId, ids in pairs(rigIdsByField or {}) do
+ local copy = {}
+ for _, id in ipairs(ids or {}) do if id ~= vehicleId then table.insert(copy, id) end end
+ result[otherId] = copy
+ end
+ result[fieldId] = result[fieldId] or {}
+ if isOn then table.insert(result[fieldId], vehicleId) end
+ return result
+end
+
function ADFieldFleet.pick(candidates, role, x, z, requiredWidth, accept)
local usable, reason, args = sortCandidates(candidates, role, x, z, requiredWidth, accept)
if usable[1] ~= nil then return usable[1].candidate end
diff --git a/FS25_ADSmartPickup/adFieldModel.lua b/FS25_ADSmartPickup/adFieldModel.lua
index 9fb3ca5..79be375 100644
--- a/FS25_ADSmartPickup/adFieldModel.lua
+++ b/FS25_ADSmartPickup/adFieldModel.lua
@@ -61,6 +61,8 @@ local function copyPlan(plan)
for key, value in pairs(DEFAULT_PLAN) do copy[key] = value end
for key, value in pairs(plan or {}) do copy[key] = value end
copy.storeBuildingIds = copyList(plan ~= nil and plan.storeBuildingIds or nil)
+ -- faste maskiner (køretøjs-id'er) til marken; en fast maskine bruges kun her
+ copy.rigIds = copyList(plan ~= nil and plan.rigIds or nil)
return copy
end
@@ -448,6 +450,7 @@ function ADFieldModel.toRecord(field)
return {
id = field.id, stage = field.stage, mode = plan.mode, product = plan.product,
rake = flag(plan.rake), store = table.concat(plan.storeBuildingIds, " "),
+ dedicated = table.concat(plan.rigIds or {}, " "),
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),
@@ -479,6 +482,7 @@ function ADFieldModel.fromRecord(record)
plan.product = PRODUCTS[record.product] and record.product or "auto"
plan.rake = readFlag(record.rake, false)
for id in string.gmatch(record.store or "", "%S+") do table.insert(plan.storeBuildingIds, id) end
+ for id in string.gmatch(record.dedicated or "", "%S+") do table.insert(plan.rigIds, id) end
plan.rigs = clamp(record.rigs or 1, 1, ADFieldModel.MAX_RIGS)
plan.unloaders = clamp(record.unloaders or 1, 1, ADFieldModel.MAX_UNLOADERS)
plan.mulch = readFlag(record.mulch, false)
diff --git a/FS25_ADSmartPickup/adFieldStorage.lua b/FS25_ADSmartPickup/adFieldStorage.lua
index 9d7f2cd..d6cd50d 100644
--- a/FS25_ADSmartPickup/adFieldStorage.lua
+++ b/FS25_ADSmartPickup/adFieldStorage.lua
@@ -13,6 +13,7 @@ ADFieldStorage.FIELD_TYPES = {
sowFruit = "String", roll = "Int", fertilize = "Int", weed = "Int",
kind = "String", lastFruit = "String", care = "String", targetFruit = "String", prepAfterMow = "Int", straw = "String",
mowWidth = "Float", mowVehicle = "String", rakeVehicle = "String", harvestVehicle = "String", windrowOffset = "String",
+ dedicated = "String",
}
function ADFieldStorage.getPath()
diff --git a/FS25_ADSmartPickup/adFieldWork.lua b/FS25_ADSmartPickup/adFieldWork.lua
index 9a513bc..e15fbbb 100644
--- a/FS25_ADSmartPickup/adFieldWork.lua
+++ b/FS25_ADSmartPickup/adFieldWork.lua
@@ -15,6 +15,8 @@ ADFieldWork.PLAYER_DRIVING_KMH = 3
ADFieldWork.WINDROW_MEASURE_MS = 10000
-- mangler der sæt, prøves igen efter så lang tid (ikke hvert sekund over hele flåden)
ADFieldWork.NO_RIG_RETRY_MS = 30000
+-- AutoDrive der stopper før markøren startes igen så mange gange, før turen opgives
+ADFieldWork.DRIVE_RETRIES = 2
ADFieldWork.REFILL_ROLES = {seeder = true, fertilizer = true, limer = true, weeder = true}
local function log(...) ADFieldJobs.log(...) end
@@ -57,12 +59,18 @@ local function helperCp(adEnv)
return adEnv.ADStateModule ~= nil and adEnv.ADStateModule.HELPER_CP or 1
end
-local function startAd(adEnv, vehicle, mode, firstId, secondId)
+-- fillType (valgfri, LOAD): varen AutoDrive skal læsse — uden den kører AD til påfyldningen og hjem tom
+-- (in-game 26/9: sprøjten kom tom tilbage fra Buying Station Multifruit)
+local function startAd(adEnv, vehicle, mode, firstId, secondId, fillType)
local module = stateModule(vehicle)
stopAd(vehicle)
module:setMode(mode)
module:setFirstMarkerByWayPointId(firstId)
if secondId ~= nil then module:setSecondMarkerByWayPointId(secondId) end
+ if fillType ~= nil then
+ module:setFillType(fillType)
+ module.selectedFillTypes = {fillType}
+ end
module:setStartHelper(false)
if module.setActualFarmId ~= nil then module:setActualFarmId(vehicle:getOwnerFarmId()) end
module:raiseDirtyFlag()
@@ -316,7 +324,10 @@ function ADFieldWork.dispatch(adEnv, field, info, decision)
note(field.id, {retryAt = now() + ADFieldModel.RETRY_MS, reason = reason})
return
end
- local candidates = ADFieldWork.getRigs(adEnv)
+ -- faste maskiner: andre markers faste bruges ikke; har marken selv faste med rollen, kun dem
+ local rigIdsByField = {}
+ for fieldId, other in pairs(ADFieldJobs.state.fields) do rigIdsByField[fieldId] = other.plan ~= nil and other.plan.rigIds or nil end
+ local candidates = ADFieldFleet.filterDedicated(ADFieldWork.getRigs(adEnv), field.id, role, rigIdsByField)
local required = ADFieldModel.requiredWidth(field, role)
local accept = acceptFor(field, info, role)
local rigs, why, args
@@ -349,12 +360,14 @@ function ADFieldWork.dispatch(adEnv, field, info, decision)
ADFieldJobs.state.assignments[rig.id] = assignment
if refill ~= nil and refill.empty then
-- AutoDrive LOAD: påfyldningen (2. markør), fyld op, videre til F-markøren (1.) — så Courseplay
- startAd(adEnv, rig.vehicle, adEnv.AutoDrive.MODE_LOAD, info.markerId, refill.refillId)
+ startAd(adEnv, rig.vehicle, adEnv.AutoDrive.MODE_LOAD, info.markerId, refill.refillId, refill.fillType)
+ assignment.drive = {mode = adEnv.AutoDrive.MODE_LOAD, first = info.markerId, second = refill.refillId, fillType = refill.fillType}
local marker = adEnv.ADGraphManager:getMapMarkerByWayPointId(refill.refillId)
log(field.id, "%s '%s' er tom — fyldes med %s ved %s og kører så til %s", role, rig.label, fillTypeName(refill.fillType),
tostring(marker ~= nil and marker.name or refill.refillId), tostring(info.markerName))
else
startAd(adEnv, rig.vehicle, adEnv.AutoDrive.MODE_DRIVETO, info.markerId, nil)
+ assignment.drive = {mode = adEnv.AutoDrive.MODE_DRIVETO, first = info.markerId}
log(field.id, "%s '%s' sendt til %s (%.1f m%s)", role, rig.label, tostring(info.markerName), rig.width or 0,
assignment.lane ~= nil and string.format(", bane %d", assignment.lane) or "")
end
@@ -751,9 +764,20 @@ function ADFieldWork.follow(adEnv, assignment)
local distance = wayPoint ~= nil and math.sqrt((wayPoint.x - x) ^ 2 + (wayPoint.z - z) ^ 2) or math.huge
if distance <= ADFieldWork.ARRIVE_DISTANCE then
beginWork(adEnv, field, info, assignment)
+ elseif assignment.drive ~= nil and (assignment.driveRetries or 0) < ADFieldWork.DRIVE_RETRIES then
+ -- AutoDrive stoppede undervejs (spilleren byggede, trafik, ...): kør videre i stedet for at opgive
+ assignment.driveRetries = (assignment.driveRetries or 0) + 1
+ assignment.since = now()
+ local d = assignment.drive
+ startAd(adEnv, vehicle, d.mode, d.first, d.second, d.fillType)
+ log(assignment.fieldId, "'%s' stoppede %.0f m fra %s — AutoDrive startes igen (%d/%d)", ADRuns.vehicleLabel(vehicle), distance,
+ tostring(info ~= nil and info.markerName or "?"), assignment.driveRetries, ADFieldWork.DRIVE_RETRIES)
else
ADFieldWork.release(assignment)
fail(assignment.fieldId, string.format("%s nåede ikke %s (%.0f m væk)", ADRuns.vehicleLabel(vehicle), tostring(info ~= nil and info.markerName or "?"), distance))
+ -- ikke efterladt midt på vejen/ved en mark: hjem til parkeringen
+ local park = parkMarkerId(adEnv, vehicle)
+ if park ~= nil then startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end
end
end
elseif assignment.phase == "waitingCourse" then
diff --git a/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua b/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua
index c4d8eef..46ba6a7 100644
--- a/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua
+++ b/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua
@@ -9,7 +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,
+ fieldTarget = true, fleetMode = true, fieldStraw = true, fieldRig = true,
}
SmartPickupFrame.ROLE_KEYS = {
mower = "spu_field_role_mower", tedder = "spu_field_role_tedder", rake = "spu_field_role_rake",
@@ -277,6 +277,7 @@ function SmartPickupFrame:fillFieldStoreRows(adEnv, info, field)
fillType = info.harvestFillType
end
if fillType == nil then return end
+ self:fillDedicatedRows(adEnv, field)
self:addTextRow(i18n:getText("spu_field_section_store"))
local chosen = {}
for _, id in ipairs(field.plan.storeBuildingIds) do chosen[id] = true end
@@ -358,6 +359,31 @@ function SmartPickupFrame:rigDetails(rig)
return #parts > 0 and (" — " .. table.concat(parts, " — ")) or ""
end
+-- Faste maskiner til den valgte mark: en fast maskine bruges kun her, og marken venter på sin egen hellere
+-- end at låne en anden med samme opgave. Maskiner der er faste på en anden mark er markeret.
+function SmartPickupFrame:fillDedicatedRows(adEnv, field)
+ local i18n = self.i18n
+ self:addTextRow(i18n:getText("spu_field_section_dedicated"))
+ local owner = {}
+ for fieldId, other in pairs(ADFieldJobs.state.fields) do
+ for _, id in ipairs(other.plan ~= nil and other.plan.rigIds or {}) do owner[id] = fieldId end
+ end
+ local rigs = ADFieldJobs.getRigs(adEnv)
+ table.sort(rigs, function(a, b)
+ local ma, mb = owner[a.id] == field.id, owner[b.id] == field.id
+ if ma ~= mb then return ma end
+ if a.role ~= b.role then return a.role < b.role end
+ return ADFlowView.naturalLess(a.label, b.label)
+ end)
+ for _, rig in ipairs(rigs) do
+ local row = self:addRow(self.vehiclePrefab, {kind = "fieldRig", vehicleId = rig.id})
+ local elsewhere = owner[rig.id] ~= nil and owner[rig.id] ~= field.id
+ and (" — " .. string.format(i18n:getText("spu_field_dedicatedElsewhere"), tostring(owner[rig.id]))) or ""
+ row:getDescendantByName("title"):setText(string.format(" %s — %s%s", tostring(rig.label), self:roleText(rig.role), elsewhere))
+ row:getDescendantByName("option"):setState(owner[rig.id] == field.id and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
+ end
+end
+
function SmartPickupFrame:fillFleetRows(adEnv)
local i18n = self.i18n
self:addTextRow(i18n:getText("spu_field_section_fleet"))
@@ -424,6 +450,15 @@ function SmartPickupFrame:onClickFieldRow(state, data)
ADFieldJobs.setPlan(id, {mode = isOn and "auto" or "manual"})
elseif SmartPickupFrame.PLAN_TOGGLES[data.kind] ~= nil then
ADFieldJobs.setPlan(id, {[SmartPickupFrame.PLAN_TOGGLES[data.kind]] = isOn})
+ elseif data.kind == "fieldRig" then
+ -- fast på denne mark (og fjernes fra en anden mark)
+ local byField = {}
+ for fieldId, other in pairs(ADFieldJobs.state.fields) do byField[fieldId] = other.plan ~= nil and other.plan.rigIds or {} end
+ local updated = ADFieldFleet.dedicate(byField, id, data.vehicleId, isOn)
+ for fieldId, ids in pairs(updated) do
+ local before = table.concat(byField[fieldId] or {}, " ")
+ if before ~= table.concat(ids, " ") then ADFieldJobs.setPlan(fieldId, {rigIds = ids}) end
+ end
elseif data.kind == "fieldStore" then
local ids = {}
for _, buildingId in ipairs(ADFieldJobs.getField(id).plan.storeBuildingIds) do
diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml
index 03c4caf..ebe82fc 100644
--- a/FS25_ADSmartPickup/l10n/l10n_da.xml
+++ b/FS25_ADSmartPickup/l10n/l10n_da.xml
@@ -80,6 +80,8 @@
+
+
diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml
index 38861ec..2cbae9f 100644
--- a/FS25_ADSmartPickup/l10n/l10n_en.xml
+++ b/FS25_ADSmartPickup/l10n/l10n_en.xml
@@ -80,6 +80,8 @@
+
+
diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml
index 4b6982a..a5e4d7b 100644
--- a/FS25_ADSmartPickup/modDesc.xml
+++ b/FS25_ADSmartPickup/modDesc.xml
@@ -1,7 +1,7 @@
masterdraco
- 1.29.6.0
+ 1.30.0.0
AD Smart Pickup
diff --git a/tests/test_adFieldFleet.lua b/tests/test_adFieldFleet.lua
index 1ca86de..2f4382a 100644
--- a/tests/test_adFieldFleet.lua
+++ b/tests/test_adFieldFleet.lua
@@ -105,5 +105,22 @@ check("FLS gødskning: tom før nærmere med kalk på", F.pick(spreaders, "ferti
check("FLS kun kalk-sprederen: bruges (tømmes)", F.pick({spreaders[1]}, "fertilizer", 0, 0).id, "kalk")
check("FLS kalkning: den med kalk på foretrækkes", F.pick(spreaders, "limer", 0, 0).id, "kalk")
+-- FLD: faste maskiner pr. mark
+local sprayA = {id = "sprøjte", role = "fertilizer"}
+local sprayB = {id = "spreder", role = "fertilizer"}
+local harve = {id = "harve", role = "cultivator"}
+local all = {sprayA, sprayB, harve}
+local byField = {[22] = {"sprøjte"}}
+local function ids(list) local out = {} for _, c in ipairs(list) do table.insert(out, c.id) end return table.concat(out, ",") end
+check("FLD F22 gødskning: kun sin faste", ids(F.filterDedicated(all, 22, "fertilizer", byField)), "sprøjte")
+check("FLD F109 gødskning: ikke F22's", ids(F.filterDedicated(all, 109, "fertilizer", byField)), "spreder,harve")
+check("FLD F22 kultivering: ingen fast med rollen -> frie", ids(F.filterDedicated(all, 22, "cultivator", byField)), "spreder,harve")
+check("FLD uden faste = alle", ids(F.filterDedicated(all, 5, "fertilizer", {})), "sprøjte,spreder,harve")
+local moved = F.dedicate(byField, 109, "sprøjte", true)
+check("FLD flyt: væk fra F22", #moved[22], 0)
+check("FLD flyt: fast på F109", moved[109][1], "sprøjte")
+check("FLD original urørt", byField[22][1], "sprøjte")
+check("FLD fjern", #F.dedicate(moved, 109, "sprøjte", false)[109], 0)
+
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)
diff --git a/tests/test_adFieldModel.lua b/tests/test_adFieldModel.lua
index f6e8ad7..23d1f0d 100644
--- a/tests/test_adFieldModel.lua
+++ b/tests/test_adFieldModel.lua
@@ -90,7 +90,7 @@ check("M7 wantsRig klar = nil", M.wantsRig(ready, 0, 0), nil)
-- M8: gem/indlæs bevarer trin, plan og slåbredde
local saved = M.advance(M.start(M.onReadiness(M.newField(109), 1))); saved.mowWidth = 40.8; saved.mowVehicleId = "vehicleBigM"
-saved.plan = {mode = "auto", product = "hay", rake = true, storeBuildingIds = {"b12", "b7"}}
+saved.plan = {mode = "auto", product = "hay", rake = true, storeBuildingIds = {"b12", "b7"}, rigIds = {"v1", "v2"}}
local back = M.fromRecord(M.toRecord(saved))
check("M8 id", back.id, 109)
check("M8 stage", back.stage, saved.stage)
@@ -98,6 +98,7 @@ check("M8 mode", back.plan.mode, "auto")
check("M8 product", back.plan.product, "hay")
check("M8 rake", back.plan.rake, true)
check("M8 lager", table.concat(back.plan.storeBuildingIds, " "), "b12 b7")
+check("M8 faste maskiner", table.concat(back.plan.rigIds, " "), "v1 v2")
check("M8 slåbredde", back.mowWidth, 40.8)
check("M8 slåmaskine", back.mowVehicleId, "vehicleBigM")
check("M8 ingen slåmaskine", M.fromRecord({id = 4, stage = "idle"}).mowVehicleId, nil)