fix(review): palle/bulk pr. kilde, pallefabrikker uden læssestation, hold-omdirigering, last ved Start, tomme kilder udelukkes, færre rutetjek, advarsler huskes, paller kun Fra/Salg

This commit is contained in:
2026-09-23 10:18:36 +02:00
parent d8d2ff3c3e
commit 43483bda76
9 changed files with 379 additions and 45 deletions
+94
View File
@@ -300,6 +300,100 @@ ADOutbound.releaseInactive()
check("O9 frigivet", ADWaitPool.getOwner(20), nil)
check("O9 hold væk", ADOutbound.getHoldFor(v9), nil)
-- O11 (I2): hold-markøren skifter (Wait-punkt bliver ledigt) -> opgaven omdirigeres
ADWaitPool.releaseVehicle(v9)
local v11 = newVehicle("Hold", 25, 0, 20000)
runs[v11] = outboundRun(11, {1}, {{fillType = "FORAGE", policy = "sell"}}) -- FORAGE findes ikke ved kilden -> aldrig tur
local blocker = newVehicle("Blocker", 0, 0, 1)
ADWaitPool.claim(20, blocker)
local choice11 = ADOutbound.choosePickup(adEnv, v11)
check("O11 uden ledigt Wait: holder ved kilden", choice11.markerIndex, mSugar.markerIndex)
local task11 = newTask(v11, 1)
v11.atTarget = true
ADOutbound.beforeLoadUpdate(adEnv, task11, 100)
ADWaitPool.releaseVehicle(blocker)
ADOutbound.beforeLoadUpdate(adEnv, task11, 5000)
check("O11 Wait-punkt ledigt -> omdirigeret", task11.destinationID, 20)
check("O11 hold peger på Wait", ADOutbound.getHoldFor(v11).markerName, "Wait 3")
ADWaitPool.releaseVehicle(v11)
-- O12 (I3): mål for last der allerede er på vognen ved Start
local v12 = newVehicle("Loaded", 0, 0, 20000)
runs[v12] = outboundRun(12, {1}, {{fillType = "SUGAR", policy = "sell"}})
quotes["selling station"][SUGAR].percent = 92
check("O12 salgsmål for sukker-last", ADOutbound.planCargoTarget(adEnv, runs[v12], v12, SUGAR), 10)
quotes["selling station"][SUGAR].percent = 50
check("O12 pris lav, ingen lager-politik -> nil", ADOutbound.planCargoTarget(adEnv, runs[v12], v12, SUGAR), nil)
runs[v12].products = {{fillType = "SUGAR", policy = "sellElseStore"}}
check("O12 pris lav -> silo", ADOutbound.planCargoTarget(adEnv, runs[v12], v12, SUGAR), 30)
check("O12 last der ikke er i run'et -> nil", ADOutbound.planCargoTarget(adEnv, runs[v12], v12, FORAGE), nil)
quotes["selling station"][SUGAR].percent = 92
-- O13 (I4): tom vogn 30 s ved kilden -> kilden udelukkes for varen og turen afsluttes
local v13 = newVehicle("Empty", 0, 0, 20000)
runs[v13] = outboundRun(13, {1, 2}, {{fillType = "SUGAR", policy = "sell"}}, {collectRound = false})
local choice13 = ADOutbound.choosePickup(adEnv, v13)
check("O13 første kilde = silo 4 (mest)", choice13.markerIndex, mSilo.markerIndex)
local task13 = newTask(v13, 2)
v13.atTarget = true
ADOutbound.beforeLoadUpdate(adEnv, task13, 100)
ADOutbound.beforeLoadUpdate(adEnv, task13, 31000)
check("O13 finished efter 30 s uden last", task13.finishedCalls, 1)
check("O13 WARN", lastLog("intet at læsse") ~= nil, true)
local choice13b = ADOutbound.choosePickup(adEnv, v13)
check("O13 næste plan udelukker den tomme kilde", choice13b.markerIndex, mSugar.markerIndex)
-- O14 (I6): rutetjek kun for salgssteder over tærsklen, bedste først, stop ved første nåelige
local routeChecks = 0
local originalGetPathTo = adEnv.ADGraphManager.getPathTo
adEnv.ADGraphManager.getPathTo = function(_, vehicle, id) routeChecks = routeChecks + 1 return originalGetPathTo(_, vehicle, id) end
local v14 = newVehicle("Routes", 0, 0, 20000)
runs[v14] = outboundRun(14, {1}, {{fillType = "SUGAR", policy = "sell"}})
quotes["Far station"][SUGAR].percent = 99
quotes["selling station"][SUGAR].percent = 92
ADOutbound.choosePickup(adEnv, v14)
check("O14 bedste nåelig først: én rute tjekket", routeChecks, 1)
check("O14 mål = bedst betalende", v14.ad.stateModule.secondMarkerId, 11)
routeChecks = 0
quotes["Far station"][SUGAR].percent = 60
ADOutbound.choosePickup(adEnv, v14)
check("O14 under tærsklen tjekkes ikke", routeChecks, 1)
adEnv.ADGraphManager.getPathTo = originalGetPathTo
-- O15 (M1): advarsel for vare A gentages ikke efter en tur med vare B
notifications = {}
local v15 = newVehicle("Two", 0, 0, 20000)
runs[v15] = outboundRun(15, {1, 2}, {{fillType = "SUGAR", policy = "sell"}, {fillType = "FORAGE", policy = "sell"}})
products[1] = {{fillType = SUGAR, name = "SUGAR", available = 5000, level = 5000, capacity = 180000, isPallet = false}, {fillType = FORAGE, name = "FORAGE", available = 5000, level = 5000, capacity = 180000, isPallet = false}}
quotes["selling station"][FORAGE] = {percent = 95, price = 1}
quotes["Far station"][SUGAR].percent = 99 -- (O14 satte den til 60)
unreachable[10] = true; unreachable[11] = true
ADOutbound.choosePickup(adEnv, v15) -- SUGAR uopnåelig -> advarsel, FORAGE uopnåelig -> advarsel
local firstCount = #notifications
unreachable[11] = nil -- Far station kan nås for SUGAR (FORAGE sælges kun på selling station)
ADOutbound.choosePickup(adEnv, v15) -- tur med SUGAR
check("O15 tur", ADOutbound.getTripFor(v15).kind, "sell")
ADOutbound.choosePickup(adEnv, v15) -- igen: FORAGE stadig uopnåelig, men allerede advaret
check("O15 FORAGE-advarsel gentages ikke", #notifications, firstCount)
unreachable[10] = nil
products[1] = {{fillType = SUGAR, name = "SUGAR", available = 5000, level = 5000, capacity = 180000, isPallet = false}}
-- O16 (M6): pallens ankomst ses også på fyldniveau når UAL's interne liste mangler
local v16 = newVehicle("NoList", 100, 0, 8000, true)
v16.trailers[1].spec_universalAutoload = {}
runs[v16] = outboundRun(16, {3}, {{fillType = "STRAW_BASKET", policy = "sell"}})
sources[3].stored[BASKET] = 3; sources[3].spawnCalls = 0
ADOutbound.choosePickup(adEnv, v16)
local task16 = newTask(v16, 3)
v16.atTarget, task16.activatedUALLoading = true, true
ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
check("O16 første spawn", sources[3].spawnCalls, 1)
sources[3].stored[BASKET] = 2
v16.trailers.level = 1000
ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
check("O16 næste spawn via fyldniveau", sources[3].spawnCalls, 2)
-- O10: install wrapper: fejl i hook stopper ikke AD
local calls = {}
adEnv.LoadAtDestinationTask.update = function(self, dt) calls.update = (calls.update or 0) + 1 end
+23
View File
@@ -112,6 +112,7 @@ function getWorldTranslation(node) local p = nodePositions[node]; return p.x, 0,
dofile("FS25_ADSmartPickup/adRuns.lua")
dofile("FS25_ADSmartPickup/adRunsStorage.lua")
dofile("FS25_ADSmartPickup/adWaitPool.lua")
dofile("FS25_ADSmartPickup/adRunsController.lua")
local adEnv = FS25_AutoDrive
@@ -496,6 +497,28 @@ local activity = ADRunsController.getVehicleActivity(adEnv, bulkTractor)
check("Cout4 aktivitet = holder", activity.kind, "holding")
check("Cout4 aktivitet sted", activity.place, "Wait 3")
check("Cout4 udkørsel har ingen mål", #ADRunsController.getRunTargets(outRun), 0)
-- Cout5 (I2): Start kræver et Wait-punkt i puljen; Cout6 (I3): last på vognen ved Start får eget mål
ADSupplyPlanner.isWaitMarker = function(marker) return string.find(string.lower(marker.group or ""), "%f[%a]wait%f[%A]") ~= nil end
local noWait = ADRunsController.createRun("No wait", "outbound")
ADRunsController.store = ADRuns.toggleSource(ADRunsController.store, noWait.id, 1)
ADRunsController.store = ADRuns.setProductPolicy(ADRunsController.store, noWait.id, "FORAGE", "store")
ADRunsController.store = ADRuns.updateRun(ADRunsController.store, noWait.id, {waitPoolGroup = "Pig Wait"})
ADRunsController.store = ADRuns.updateRun(ADRunsController.store, noWait.id, {waitPoolGroup = "Findes ikke"})
local noWaitRun = ADRuns.getRun(ADRunsController.store, noWait.id)
check("Cout5 ingen Wait-punkt i puljen -> noWaitPoint", ADRunsController.validateVehicle(adEnv, noWaitRun, bulkTractor).reason, "noWaitPoint")
ADRunsController.store = ADRuns.updateRun(ADRunsController.store, noWait.id, {waitPoolGroup = "Cow Wait"})
noWaitRun = ADRuns.getRun(ADRunsController.store, noWait.id)
check("Cout5 Wait-punkt findes -> ok", ADRunsController.validateVehicle(adEnv, noWaitRun, bulkTractor).ok, true)
ADUnloadWait.getRestLoad = function(_, v) return v.loadType, v.loadLiters or 0 end
ADOutbound.planCargoTarget = function(adEnv, run, vehicle, fillType) return fillType == TMR and 10 or nil end
bulkTractor.loadType, bulkTractor.loadLiters = TMR, 8000
ADRunsController.assignVehicle(noWait.id, bulkTractor)
bulkTractor.ad.stateModule.active = false
ADRunsController.startRun(adEnv, noWait.id)
check("Cout6 last på vognen -> 2. markør = lastens mål", bulkTractor.ad.stateModule.secondMarkerId, 10)
bulkTractor.loadType = STRAW
check("Cout6 last uden mål -> cargoNoTarget", ADRunsController.validateVehicle(adEnv, noWaitRun, bulkTractor).reason, "cargoNoTarget")
bulkTractor.loadType = nil
ADUnloadWait = nil; ADOutbound = nil; ADSources = nil
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
+23 -4
View File
@@ -4,7 +4,7 @@
local SUGAR, FORAGE, BASKET, CRATE = 5, 6, 7, 8
local DESC = {
[SUGAR] = {index = SUGAR, name = "SUGAR", title = "Sukker"},
[SUGAR] = {index = SUGAR, name = "SUGAR", title = "Sukker", isPalletType = true},
[FORAGE] = {index = FORAGE, name = "FORAGE", title = "TMR"},
[BASKET] = {index = BASKET, name = "STRAW_BASKET", title = "Halmkurve", palletFilename = "basketPallet.xml"},
[CRATE] = {index = CRATE, name = "STRAW_CRATE", title = "Halmkasser", palletFilename = "cratePallet.xml"},
@@ -60,7 +60,7 @@ local factory = {
palletSpawner = {spawnPlaces = {{startNode = {x = 110, z = 100}}}},
}},
}
local factoryStation = {owningPlaceable = factory}
local factoryStation = {owningPlaceable = factory, supportedFillTypes = {[SUGAR] = true}}
local silo = {rootNode = {x = 200, z = 200}, spec_silo = {storages = {storageOf({[FORAGE] = 74500}, {[FORAGE] = 12000000})}}, getName = function() return "Farma" end}
local siloStation = {owningPlaceable = silo}
local spawnCalls = {}
@@ -175,8 +175,27 @@ check("S7 ingen kilde", ADSources.identify(adEnv, 4, 1), nil)
check("S7 ukendt waypoint", ADSources.identify(adEnv, 99, 1), nil)
-- S8: hjælpere
check("S8 isPalletFillType", ADSources.isPalletFillType(BASKET), true)
check("S8 bulk", ADSources.isPalletFillType(SUGAR), false)
check("S8 isPalletFillType palle", ADSources.isPalletFillType(BASKET), true)
check("S8 isPalletFillType palletype uden fil", ADSources.isPalletFillType(SUGAR), true)
check("S8 isPalletFillType bulk", ADSources.isPalletFillType(FORAGE), false)
-- C1: palle/bulk afgøres pr. kilde: fabrik med læssestation der tager varen = bulk; silo = altid bulk; objektlager = palle
check("S8 sukker ved fabrik med læssestation = bulk", ADSources.isPalletAt(source1, SUGAR), false)
check("S8 kurve ved fabrik (station tager dem ikke) = palle", ADSources.isPalletAt(source1, BASKET), true)
check("S8 silo altid bulk", ADSources.isPalletAt(source2, SUGAR), false)
check("S8 objektlager altid palle", ADSources.isPalletAt(source3, FORAGE), true)
-- S9 (I1): ren pallefabrik uden læssestation genkendes via afstand til spawner/rootNode
local basketFactory = {
rootNode = {x = 600, z = 600}, getName = function() return "Straw factory" end,
spec_productionPoint = {productionPoint = {productions = {{outputs = {{type = BASKET, amount = 1}}}}, storage = storageOf({[BASKET] = 0}, {[BASKET] = 8000}), palletSpawner = {spawnPlaces = {{startNode = {x = 610, z = 600}}}}}},
}
table.insert(g_currentMission.placeableSystem.placeables, basketFactory)
addMarker(7, "Straw factory pickup", 605, 590)
local source9 = ADSources.identify(adEnv, 7, 1)
check("S9 pallefabrik uden station = factory", source9.kind, "factory")
check("S9 station nil", source9.station, nil)
local products9 = ADSources.getProducts(adEnv, source9, 1)
check("S9 kurve er palle", products9[1].isPallet, true)
check("S9 fabrik foretrækkes frem for objektlager i nærheden", ADSources.identify(adEnv, 7, 1).placeable, basketFactory)
local excluded = ADSources.getSourcePlaceables({source1, source2})
check("S8 udelukkede placeables", excluded[factory] and excluded[silo], true)