529 lines
30 KiB
Lua
529 lines
30 KiB
Lua
-- Kør: luajit tests/test_adOutbound.lua (fra repo-roden)
|
|
-- Udkørsels-hooks: turvalg + AD-tilstand, hold ved Wait, objektlager-udtag, opsamlingsrunde,
|
|
-- advarsler på skærmen. Fake AD-miljø; adSources/adPrices/adLoadSwap er fakes, adWaitPool og
|
|
-- adOutboundPlanner er de rigtige.
|
|
|
|
local SUGAR, FORAGE, BASKET, CRATE = 5, 6, 7, 8
|
|
local NAMES = {[SUGAR] = "SUGAR", [FORAGE] = "FORAGE", [BASKET] = "STRAW_BASKET", [CRATE] = "STRAW_CRATE"}
|
|
g_fillTypeManager = {
|
|
getFillTypeNameByIndex = function(_, i) return NAMES[i] end,
|
|
getFillTypeByIndex = function(_, i) return {name = NAMES[i], palletFilename = (i == BASKET or i == CRATE) and "p.xml" or nil} end,
|
|
}
|
|
local logLines = {}
|
|
Logging = {
|
|
info = function(fmt, ...) table.insert(logLines, string.format(fmt, ...)) end,
|
|
warning = function(fmt, ...) table.insert(logLines, "WARN " .. string.format(fmt, ...)) end,
|
|
}
|
|
getWorldTranslation = function(node) return node.x, 0, node.z end
|
|
g_currentMission = {getFarmId = function() return 1 end}
|
|
|
|
dofile("FS25_ADSmartPickup/adSupplyPlanner.lua")
|
|
dofile("FS25_ADSmartPickup/adRuns.lua")
|
|
dofile("FS25_ADSmartPickup/adWaitPool.lua")
|
|
dofile("FS25_ADSmartPickup/adOutboundPlanner.lua")
|
|
dofile("FS25_ADSmartPickup/adOutbound.lua")
|
|
|
|
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 function lastLog(pattern)
|
|
for i = #logLines, 1, -1 do
|
|
if string.find(logLines[i], pattern, 1, true) then return logLines[i] end
|
|
end
|
|
return nil
|
|
end
|
|
local function countLog(pattern)
|
|
local n = 0
|
|
for _, line in ipairs(logLines) do if string.find(line, pattern, 1, true) then n = n + 1 end end
|
|
return n
|
|
end
|
|
|
|
-- ---- AD-miljø ----
|
|
local markers, wayPoints, unreachable = {}, {}, {}
|
|
local function addMarker(id, name, group, x, z)
|
|
wayPoints[id] = {id = id, x = x, z = z}
|
|
table.insert(markers, {id = id, markerIndex = #markers + 1, name = name, group = group})
|
|
return markers[#markers]
|
|
end
|
|
local notifications = {}
|
|
local RT_NONE, RT_ONLYPICKUP = 1, 2
|
|
local adEnv = {
|
|
AutoDrive = {
|
|
RT_NONE = RT_NONE, RT_ONLYPICKUP = RT_ONLYPICKUP,
|
|
getAllUnits = function(vehicle) return vehicle.trailers end,
|
|
getAllFillLevels = function(trailers) return trailers.level, trailers.capacity, trailers.level >= trailers.capacity * 0.99, trailers.capacity - trailers.level end,
|
|
getSupportedFillTypesOfAllUnitsAlphabetically = function(vehicle) return vehicle.carriable end,
|
|
hasAL = function(_, trailer) return trailer.ual ~= nil end,
|
|
activateALTrailers = function(vehicle, trailers) trailers.activated = (trailers.activated or 0) + 1 end,
|
|
},
|
|
ADGraphManager = {
|
|
getMapMarkers = function() return markers end,
|
|
getMapMarkerByWayPointId = function(_, id) for _, m in ipairs(markers) do if m.id == id then return m end end end,
|
|
getWayPointById = function(_, id) return wayPoints[id] end,
|
|
getPathTo = function(_, vehicle, id) if unreachable[id] then return {} end return {{id = id}} end,
|
|
pathFromTo = function(_, from, to) if unreachable[to] then return {} end return {{id = to}} end,
|
|
},
|
|
AutoDriveMessageEvent = {sendMessageOrNotification = function(vehicle, kind, text, duration, ...) table.insert(notifications, {vehicle = vehicle, kind = kind, text = string.format(text, ...)}) end},
|
|
ADMessagesManager = {messageTypes = {INFO = 1, WARN = 2, ERROR = 3}},
|
|
LoadAtDestinationTask = {STATE_DRIVING = 2, update = function() end, finished = function(self) self.finishedCalls = (self.finishedCalls or 0) + 1 end},
|
|
UnloadAtDestinationTask = {STATE_DRIVING = 2, update = function() end},
|
|
}
|
|
|
|
local function newVehicle(name, x, z, capacity, ual)
|
|
local v = {name = name, rootNode = {x = x, z = z}, lastSpeedReal = 0, carriable = {SUGAR, FORAGE}, farmId = 1}
|
|
v.getName = function() return name end
|
|
v.trailers = {level = 0, capacity = capacity, ual = ual and {spec = {loadedObjects = {}}} or nil}
|
|
v.trailers[1] = {spec_universalAutoload = ual and v.trailers.ual.spec or nil, ualIsFull = ual and function() return v.trailers.full == true end or nil, ual = ual and true or nil}
|
|
v.ad = {
|
|
stateModule = {isActive = function() return v.active ~= false end, selectedFillTypes = {}, fillType = nil,
|
|
setFillType = function(self, ft) self.fillType = ft end, getFillType = function(self) return self.fillType end,
|
|
setSecondMarkerByWayPointId = function(self, id) self.secondMarkerId = id end,
|
|
getSecondMarker = function(self) return self.secondMarkerId and {id = self.secondMarkerId} or nil end,
|
|
raiseDirtyFlag = function() end},
|
|
drivePathModule = {isTargetReached = function() return v.atTarget == true end, setPathTo = function(_, id) v.pathTo = id end},
|
|
specialDrivingModule = {stopVehicle = function() v.stopped = true end, update = function() end},
|
|
trailerModule = {isActiveAtTrigger = function() return false end},
|
|
}
|
|
return v
|
|
end
|
|
local function newTask(vehicle, destinationID)
|
|
return {vehicle = vehicle, destinationID = destinationID, trailers = vehicle.trailers, state = 2, activatedUALLoading = false,
|
|
finished = function(self) return adEnv.LoadAtDestinationTask.finished(self) end}
|
|
end
|
|
|
|
-- kilder, priser, siloer som fakes
|
|
local sources, products, quotes, dumpMarker = {}, {}, {}, nil
|
|
ADSources = {
|
|
identify = function(_, id) return sources[id] end,
|
|
getProducts = function(_, source) return products[source.wayPointId] or {} end,
|
|
getSellMarkers = function() local r = {} for _, m in ipairs(markers) do if m.group == "selling" then table.insert(r, {marker = m, station = {name = m.name}}) end end return r end,
|
|
stationAccepts = function(station, ft) return quotes[station.name] ~= nil and quotes[station.name][ft] ~= nil end,
|
|
getSourcePlaceables = function() return {} end,
|
|
getObjectInfoIndex = function(source, ft) local s = source.stored or {} return s[ft] ~= nil and s[ft] > 0 and 1 or nil, s[ft] end,
|
|
countStored = function(source, ft) return (source.stored or {})[ft] or 0 end,
|
|
spawnOne = function(source, index) source.spawnCalls = (source.spawnCalls or 0) + 1 return source.spawnFails ~= true end,
|
|
}
|
|
ADPrices = {
|
|
getQuote = function(station, ft) return quotes[station.name] and quotes[station.name][ft] end,
|
|
bestQuote = function(list) local best for _, q in ipairs(list) do if best == nil or q.percent > best.percent then best = q end end return best end,
|
|
}
|
|
ADLoadSwap = {findDumpMarker = function() return dumpMarker, "ingen silo" end}
|
|
local runs = {}
|
|
ADRunsController = {getRunForVehicle = function(vehicle) return runs[vehicle] end}
|
|
local activePickupCalls = {}
|
|
ADSmartPickup = {getFarmId = function(v) return v.farmId end, setActivePickup = function(vehicle, choice) table.insert(activePickupCalls, choice) end,
|
|
getUnloadStationAtWayPointId = function(_, id) for _, m in ipairs(markers) do if m.id == id and (m.group == "silo" or m.group == "selling") then return {name = m.name} end end end}
|
|
|
|
local mSugar = addMarker(1, "Suger Load", "Pickup points", 0, 0)
|
|
local mSilo = addMarker(2, "silo 4", "silo", 50, 0)
|
|
local mObj = addMarker(3, "Objektlager", "Pickup points", 100, 0)
|
|
local mSell = addMarker(10, "selling station", "selling", 200, 0)
|
|
local mSell2 = addMarker(11, "Far station", "selling", 300, 0)
|
|
local mWait = addMarker(20, "Wait 3", "General Wait", 20, 0)
|
|
local mStore = addMarker(30, "silo 6", "silo", 60, 0)
|
|
sources[1] = {kind = "factory", wayPointId = 1, station = {name = "factoryStation"}, name = "Sugar mill"}
|
|
sources[2] = {kind = "silo", wayPointId = 2, station = {name = "siloStation"}, name = "Farma"}
|
|
sources[3] = {kind = "objectStorage", wayPointId = 3, station = nil, name = "Objektlager", stored = {[BASKET] = 3, [CRATE] = 1}}
|
|
products[1] = {{fillType = SUGAR, name = "SUGAR", available = 5000, level = 5000, capacity = 180000, isPallet = false}}
|
|
products[2] = {{fillType = SUGAR, name = "SUGAR", available = 9000, level = 9000, capacity = 12000000, isPallet = false}}
|
|
products[3] = {{fillType = BASKET, name = "STRAW_BASKET", available = 3000, level = 4, capacity = 4000, isPallet = true}, {fillType = CRATE, name = "STRAW_CRATE", available = 1000, level = 4, capacity = 4000, isPallet = true}}
|
|
quotes["selling station"] = {[SUGAR] = {percent = 92, price = 480}, [BASKET] = {percent = 95, price = 1}, [CRATE] = {percent = 95, price = 1}}
|
|
quotes["Far station"] = {[SUGAR] = {percent = 99, price = 600}}
|
|
|
|
local function outboundRun(id, sourceIds, productsList, overrides)
|
|
local run = {id = id, name = "Out " .. id, kind = "outbound", waitPoolGroup = "", sourceWayPointIds = sourceIds, products = productsList, sellAtPercent = 90, storeAbovePercent = 80, collectRound = false}
|
|
for k, v in pairs(overrides or {}) do run[k] = v end
|
|
return run
|
|
end
|
|
|
|
-- O1: tur -> AD's varevalg og 2. markør sat, choice = kilden; næstbedste salgssted når bedste er uopnåeligt
|
|
unreachable[11] = true
|
|
local v1 = newVehicle("MT635", 0, 0, 20000)
|
|
runs[v1] = outboundRun(1, {1}, {{fillType = "SUGAR", policy = "sell"}})
|
|
check("O1 er udkørsel", ADOutbound.isOutboundVehicle(v1), true)
|
|
local choice = ADOutbound.choosePickup(adEnv, v1)
|
|
check("O1 kilde-markør", choice.markerIndex, mSugar.markerIndex)
|
|
check("O1 station med (bulk-greb)", choice.station.name, "factoryStation")
|
|
check("O1 vare", v1.ad.stateModule.fillType, SUGAR)
|
|
check("O1 varevalg", #v1.ad.stateModule.selectedFillTypes, 1)
|
|
check("O1 2. markør = nåeligt salgssted", v1.ad.stateModule.secondMarkerId, 10)
|
|
check("O1 ingen advarsel (næstbedste kunne nås)", #notifications, 0)
|
|
check("O1 log", lastLog("udkørsel") ~= nil, true)
|
|
local trip = ADOutbound.getTripFor(v1)
|
|
check("O1 trip kind", trip.kind, "sell")
|
|
unreachable[11] = nil
|
|
|
|
-- O2: ingen tur -> Wait-markør reserveret, hold registreret
|
|
local v2 = newVehicle("ARION", 25, 0, 20000)
|
|
runs[v2] = outboundRun(2, {1}, {{fillType = "SUGAR", policy = "sell"}})
|
|
quotes["selling station"][SUGAR].percent = 70
|
|
quotes["Far station"][SUGAR].percent = 60
|
|
local choice2 = ADOutbound.choosePickup(adEnv, v2)
|
|
check("O2 Wait-markør", choice2.markerIndex, mWait.markerIndex)
|
|
check("O2 uden station", choice2.station, nil)
|
|
check("O2 reserveret", ADWaitPool.getOwner(20), v2)
|
|
local hold = ADOutbound.getHoldFor(v2)
|
|
check("O2 hold", hold.markerName, "Wait 3")
|
|
check("O2 begrundelse", hold.reasons[1], "SUGAR 70 % af top (kræver 90), lager 3 %")
|
|
check("O2 run-status", ADOutbound.getRunStatus(2).reasons[1], hold.reasons[1])
|
|
check("O2 hold-log én gang", countLog("holder ved Wait 3"), 1)
|
|
ADOutbound.choosePickup(adEnv, v2)
|
|
check("O2 samme begrundelse logges ikke igen", countLog("holder ved Wait 3"), 1)
|
|
|
|
-- O3: holder ved Wait: vognen stoppes; efter 5 s med tur -> omdirigeret til kilden, hold sluppet
|
|
local task2 = newTask(v2, 20)
|
|
v2.atTarget = true
|
|
check("O3 håndteret", ADOutbound.beforeLoadUpdate(adEnv, task2, 1000), true)
|
|
check("O3 stoppet", v2.stopped, true)
|
|
quotes["selling station"][SUGAR].percent = 92
|
|
ADOutbound.beforeLoadUpdate(adEnv, task2, 4500)
|
|
check("O3 omdirigeret til kilden", task2.destinationID, 1)
|
|
check("O3 sti sat", v2.pathTo, 1)
|
|
check("O3 hold væk", ADOutbound.getHoldFor(v2), nil)
|
|
check("O3 reservation frigivet", ADWaitPool.getOwner(20), nil)
|
|
check("O3 activePickup opdateret", activePickupCalls[#activePickupCalls].station.name, "factoryStation")
|
|
check("O3 UAL-flag nulstillet", task2.activatedUALLoading, false)
|
|
v2.atTarget = false
|
|
check("O3 undervejs ikke håndteret", ADOutbound.beforeLoadUpdate(adEnv, task2, 100), false)
|
|
|
|
-- O4: objektlager: én palle ad gangen; stored falder + loaded stiger -> næste; ualIsFull -> finished
|
|
local v3 = newVehicle("Fendt", 100, 0, 8000, true)
|
|
runs[v3] = outboundRun(3, {3}, {{fillType = "STRAW_BASKET", policy = "sell"}, {fillType = "STRAW_CRATE", policy = "sell"}}, {collectRound = true})
|
|
local choice3 = ADOutbound.choosePickup(adEnv, v3)
|
|
check("O4 kilde = objektlager", choice3.markerIndex, mObj.markerIndex)
|
|
check("O4 kun én vare valgt ad gangen ved objektlager", #v3.ad.stateModule.selectedFillTypes, 1)
|
|
check("O4 turen har begge varer", #ADOutbound.getTripFor(v3).fillTypes, 2)
|
|
local task3 = newTask(v3, 3)
|
|
v3.atTarget = true
|
|
ADOutbound.beforeLoadUpdate(adEnv, task3, 100)
|
|
check("O4 intet spawn før UAL er aktiveret", sources[3].spawnCalls, nil)
|
|
task3.activatedUALLoading = true
|
|
ADOutbound.beforeLoadUpdate(adEnv, task3, 100)
|
|
check("O4 første spawn", sources[3].spawnCalls, 1)
|
|
ADOutbound.beforeLoadUpdate(adEnv, task3, 100)
|
|
check("O4 venter på pallen", sources[3].spawnCalls, 1)
|
|
sources[3].stored[BASKET] = 2
|
|
v3.trailers.ual.spec.loadedObjects[{}] = 1
|
|
ADOutbound.beforeLoadUpdate(adEnv, task3, 100)
|
|
ADOutbound.beforeLoadUpdate(adEnv, task3, 100)
|
|
check("O4 andet spawn efter læsning", sources[3].spawnCalls, 2)
|
|
sources[3].stored[BASKET] = 0
|
|
v3.trailers.ual.spec.loadedObjects[{}] = 1
|
|
ADOutbound.beforeLoadUpdate(adEnv, task3, 100)
|
|
ADOutbound.beforeLoadUpdate(adEnv, task3, 100)
|
|
check("O4 skifter til kasser når kurvene er væk", v3.ad.stateModule.fillType, CRATE)
|
|
check("O4 UAL genaktiveret ved vareskift", v3.trailers.activated, 1)
|
|
ADOutbound.beforeLoadUpdate(adEnv, task3, 100)
|
|
check("O4 tredje spawn (kasse)", sources[3].spawnCalls, 3)
|
|
v3.trailers.full = true
|
|
sources[3].stored[CRATE] = 0
|
|
v3.trailers.ual.spec.loadedObjects[{}] = 1
|
|
ADOutbound.beforeLoadUpdate(adEnv, task3, 100)
|
|
ADOutbound.beforeLoadUpdate(adEnv, task3, 100)
|
|
check("O4 fuld -> finished", task3.finishedCalls, 1)
|
|
|
|
-- O5: spawn uden fremdrift i 30 s -> finished + WARN
|
|
local v4 = newVehicle("Valtra", 100, 0, 8000, true)
|
|
runs[v4] = outboundRun(4, {3}, {{fillType = "STRAW_BASKET", policy = "sell"}})
|
|
sources[3].stored[BASKET] = 5
|
|
sources[3].spawnCalls = 0
|
|
ADOutbound.choosePickup(adEnv, v4)
|
|
local task4 = newTask(v4, 3)
|
|
v4.atTarget, task4.activatedUALLoading = true, true
|
|
ADOutbound.beforeLoadUpdate(adEnv, task4, 100)
|
|
check("O5 spawn kaldt", sources[3].spawnCalls, 1)
|
|
ADOutbound.beforeLoadUpdate(adEnv, task4, 31000)
|
|
ADOutbound.beforeLoadUpdate(adEnv, task4, 100)
|
|
check("O5 finished efter stall", task4.finishedCalls, 1)
|
|
check("O5 WARN", lastLog("spawner ikke") ~= nil, true)
|
|
|
|
-- O6: opsamlingsrunde, bulk: kilde A fylder vognen -> finished uden redirect; kilde A giver 30 % -> videre til B
|
|
local v5 = newVehicle("Claas", 0, 0, 10000)
|
|
runs[v5] = outboundRun(5, {1, 2}, {{fillType = "SUGAR", policy = "sell"}}, {collectRound = true})
|
|
local choice5 = ADOutbound.choosePickup(adEnv, v5)
|
|
check("O6 mest først = silo 4", choice5.markerIndex, mSilo.markerIndex)
|
|
local task5 = newTask(v5, 2)
|
|
v5.trailers.level = 10000
|
|
check("O6 fuld: ingen runde", ADOutbound.beforeLoadFinished(adEnv, task5), false)
|
|
local v6 = newVehicle("Claas2", 0, 0, 10000)
|
|
runs[v6] = outboundRun(6, {1, 2}, {{fillType = "SUGAR", policy = "sell"}}, {collectRound = true})
|
|
ADOutbound.choosePickup(adEnv, v6)
|
|
local task6 = newTask(v6, 2)
|
|
v6.trailers.level = 3000
|
|
check("O6 plads tilbage: videre til næste kilde", ADOutbound.beforeLoadFinished(adEnv, task6), true)
|
|
check("O6 destination = Suger Load", task6.destinationID, 1)
|
|
check("O6 activePickup = fabrik", activePickupCalls[#activePickupCalls].station.name, "factoryStation")
|
|
check("O6 alle besøgt: afslut", ADOutbound.beforeLoadFinished(adEnv, task6), false)
|
|
|
|
-- O7: intet nåeligt salgssted -> rød notifikation én gang
|
|
notifications = {}
|
|
unreachable[10], unreachable[11] = true, true
|
|
local v7 = newVehicle("Deutz", 0, 0, 20000)
|
|
runs[v7] = outboundRun(7, {1}, {{fillType = "SUGAR", policy = "sell"}})
|
|
local choice7 = ADOutbound.choosePickup(adEnv, v7)
|
|
check("O7 holder", ADOutbound.getHoldFor(v7) ~= nil, true)
|
|
check("O7 én notifikation", #notifications, 1)
|
|
check("O7 ERROR", notifications[1].kind, 3)
|
|
check("O7 tekst", string.find(notifications[1].text, "kan ikke nå salgssted", 1, true) ~= nil, true)
|
|
check("O7 status-advarsel", ADOutbound.getRunStatus(7).warning ~= nil, true)
|
|
ADOutbound.choosePickup(adEnv, v7)
|
|
check("O7 ikke gentaget", #notifications, 1)
|
|
unreachable[10] = nil
|
|
ADOutbound.choosePickup(adEnv, v7)
|
|
check("O7 tur når vejen er der", ADOutbound.getTripFor(v7) ~= nil, true)
|
|
check("O7 advarsel ryddet", ADOutbound.getRunStatus(7).warning, nil)
|
|
unreachable[11] = nil
|
|
|
|
-- O8: ingen silo med plads ved lagerbehov -> notifikation; med silo -> store-tur
|
|
notifications = {}
|
|
local v8 = newVehicle("JD", 0, 0, 20000)
|
|
runs[v8] = outboundRun(8, {2}, {{fillType = "SUGAR", policy = "store"}})
|
|
products[2][1].level, products[2][1].capacity = 11000000, 12000000
|
|
ADOutbound.choosePickup(adEnv, v8)
|
|
check("O8 advarsel ingen silo", #notifications, 1)
|
|
dumpMarker = mStore
|
|
ADOutbound.choosePickup(adEnv, v8)
|
|
check("O8 store-tur", ADOutbound.getTripFor(v8).kind, "store")
|
|
check("O8 mål = silo 6", v8.ad.stateModule.secondMarkerId, 30)
|
|
|
|
-- O9: releaseInactive rydder hold + reservation
|
|
local v9 = newVehicle("Case", 25, 0, 20000)
|
|
runs[v9] = outboundRun(9, {1}, {{fillType = "FORAGE", policy = "sell"}})
|
|
ADOutbound.choosePickup(adEnv, v9)
|
|
check("O9 reserveret", ADWaitPool.getOwner(20), v9)
|
|
v9.active = false
|
|
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)
|
|
|
|
-- O17 (reserve + paller til lager): buildPlanInput henter reserve og palle-lager fra ADSources
|
|
local demandCalls = {}
|
|
ADSources.getFarmDemand = function(_, ft, farmId, excluded) table.insert(demandCalls, ft) return {liters = ft == SUGAR and 4000 or 0, consumers = {"TMR-fabrik"}} end
|
|
local palletStoreMarker = nil
|
|
ADSources.findPalletStore = function(_, vehicle, ft, excluded, isReachable, farmId) return palletStoreMarker end
|
|
local v17 = newVehicle("Reserve", 0, 0, 20000)
|
|
runs[v17] = outboundRun(17, {1}, {{fillType = "SUGAR", policy = "sell"}})
|
|
local input17 = ADOutbound.buildPlanInput(adEnv, runs[v17], v17)
|
|
check("O17 reserve på salgsvare", input17.products[1].reserve, 4000)
|
|
check("O17 reserveret til", input17.products[1].reservedFor[1], "TMR-fabrik")
|
|
local v18 = newVehicle("PalletStore", 100, 0, 8000, true)
|
|
runs[v18] = outboundRun(18, {3}, {{fillType = "STRAW_BASKET", policy = "store"}})
|
|
palletStoreMarker = {wayPointId = 40, name = "Palleskur", free = 100}
|
|
sources[3].stored[BASKET] = 4
|
|
products[3][1].available, products[3][1].level, products[3][1].capacity = 4000, 3900, 4000
|
|
local input18 = ADOutbound.buildPlanInput(adEnv, runs[v18], v18)
|
|
check("O18 palle-lager som store-mål", input18.products[1].store ~= nil and input18.products[1].store.wayPointId, 40)
|
|
check("O18 reserve spørges ikke for lager-politik", demandCalls[#demandCalls] ~= BASKET, true)
|
|
|
|
-- O19: pallebil (kun UAL) ved en fabrik der både har bulk-læssestation og sukkerpaller -> paller
|
|
local v19 = newVehicle("Dragon", 0, 0, 6000, true)
|
|
v19.carriable = {}
|
|
runs[v19] = outboundRun(19, {1}, {{fillType = "SUGAR", policy = "sell"}})
|
|
products[1] = {{fillType = SUGAR, name = "SUGAR", available = 150000, level = 150000, capacity = 180000, isPallet = false, canPallet = true, palletLiters = 3000, palletCount = 3}}
|
|
quotes["selling station"][SUGAR].percent = 92
|
|
local input19 = ADOutbound.buildPlanInput(adEnv, runs[v19], v19)
|
|
check("O19 varen tages som paller", input19.products[1] ~= nil and input19.products[1].isPallet, true)
|
|
check("O19 mængde = paller ved spawner", input19.products[1].sources[1].available, 3000)
|
|
check("O19 palle-antal følger med", input19.products[1].sources[1].palletCount, 3)
|
|
local v19b = newVehicle("Tipvogn", 0, 0, 20000)
|
|
runs[v19b] = outboundRun(20, {1}, {{fillType = "SUGAR", policy = "sell"}})
|
|
local input19b = ADOutbound.buildPlanInput(adEnv, runs[v19b], v19b)
|
|
check("O19 tipvogn tager bulk", input19b.products[1].isPallet, false)
|
|
check("O19 bulk-mængde", input19b.products[1].sources[1].available, 150000)
|
|
products[1] = {{fillType = SUGAR, name = "SUGAR", available = 5000, level = 5000, capacity = 180000, isPallet = false}}
|
|
|
|
-- O20: UAL-bil: AD's "kapacitet" er antal paller (læssede + 1) -> liter-kapacitet ukendt i planen
|
|
local v20 = newVehicle("Dragon2", 100, 0, 1, true)
|
|
v20.carriable = {}
|
|
runs[v20] = outboundRun(21, {3}, {{fillType = "STRAW_BASKET", policy = "sell"}})
|
|
local input20 = ADOutbound.buildPlanInput(adEnv, runs[v20], v20)
|
|
check("O20 kapacitet ukendt for pallebil", input20.capacity, nil)
|
|
check("O20 har autoload", input20.hasAutoload, true)
|
|
check("O20 turen planlægges alligevel", ADOutboundPlanner.plan(input20) ~= nil, true)
|
|
|
|
-- O21: autoload-bil på vej til aflæsning: stropperne løsnes UNSTRAP_BEFORE_M før målet (ladet passerer
|
|
-- triggeren før markøren), kun én gang pr. tur, og aldrig for tipvogne
|
|
local v21 = newVehicle("Dragon3", 0, 0, 1, true)
|
|
v21.carriable = {}
|
|
v21.trailers[1].setAllTensionBeltsActive = function(self, active) self.beltsActive = active end
|
|
runs[v21] = outboundRun(22, {3}, {{fillType = "STRAW_BASKET", policy = "sell"}})
|
|
sources[3].stored[BASKET] = 3
|
|
quotes["selling station"][BASKET] = {percent = 95, price = 1}
|
|
ADOutbound.choosePickup(adEnv, v21)
|
|
check("O21 tur mod salgssted", v21.ad.stateModule.secondMarkerId, 10)
|
|
local unloadTask21 = {vehicle = v21, destinationID = 10, trailers = v21.trailers}
|
|
v21.rootNode = {x = 200 - 40, z = 0} -- 40 m fra markør 10 (x=200)
|
|
ADOutbound.beforeUnloadUpdate(adEnv, unloadTask21, 16)
|
|
check("O21 40 m: stropper stadig på", v21.trailers[1].beltsActive, nil)
|
|
v21.rootNode = {x = 200 - 20, z = 0}
|
|
ADOutbound.beforeUnloadUpdate(adEnv, unloadTask21, 16)
|
|
check("O21 20 m: stropper løsnet", v21.trailers[1].beltsActive, false)
|
|
v21.trailers[1].beltsActive = nil
|
|
ADOutbound.beforeUnloadUpdate(adEnv, unloadTask21, 16)
|
|
check("O21 kun én gang", v21.trailers[1].beltsActive, nil)
|
|
local v21b = newVehicle("Tip", 0, 0, 20000)
|
|
v21b.trailers[1].setAllTensionBeltsActive = function(self, active) self.beltsActive = active end
|
|
runs[v21b] = outboundRun(23, {1}, {{fillType = "SUGAR", policy = "sell"}})
|
|
quotes["selling station"][SUGAR].percent = 92
|
|
ADOutbound.choosePickup(adEnv, v21b)
|
|
v21b.rootNode = {x = 195, z = 0}
|
|
ADOutbound.beforeUnloadUpdate(adEnv, {vehicle = v21b, destinationID = 10, trailers = v21b.trailers}, 16)
|
|
check("O21 tipvogn: ingen stropper", v21b.trailers[1].beltsActive, nil)
|
|
|
|
-- O22: lagermål med en anden silo-markør inden for AD's globale trigger-afstand -> advarsel én gang
|
|
-- (AD tipper ved første trigger inden for afstanden; 10 m løser det når siloer står tæt)
|
|
local triggerDistance = 25
|
|
adEnv.AutoDrive.getSetting = function(name) if name == "maxTriggerDistance" then return triggerDistance end end
|
|
local v22 = newVehicle("Tip2", 0, 0, 20000)
|
|
runs[v22] = outboundRun(24, {1}, {{fillType = "SUGAR", policy = "store"}})
|
|
products[1] = {{fillType = SUGAR, name = "SUGAR", available = 170000, level = 170000, capacity = 180000, isPallet = false}}
|
|
dumpMarker = mStore -- silo 6 (x=60); silo 4 (x=50) står 10 m derfra
|
|
logLines = {}
|
|
ADOutbound.choosePickup(adEnv, v22)
|
|
check("O22 lager-tur", ADOutbound.getTripFor(v22) ~= nil and ADOutbound.getTripFor(v22).kind, "store")
|
|
check("O22 advarsel om trigger-afstand", lastLog("Max trigger distance") ~= nil, true)
|
|
logLines = {}
|
|
ADOutbound.choosePickup(adEnv, v22)
|
|
check("O22 kun én gang", lastLog("Max trigger distance"), nil)
|
|
triggerDistance = 10
|
|
local v22b = newVehicle("Tip3", 0, 0, 20000)
|
|
runs[v22b] = outboundRun(25, {1}, {{fillType = "SUGAR", policy = "store"}})
|
|
logLines = {}
|
|
ADOutbound.choosePickup(adEnv, v22b)
|
|
check("O22 ved 10 m ingen advarsel", lastLog("Max trigger distance"), nil)
|
|
adEnv.AutoDrive.getSetting = nil
|
|
products[1] = {{fillType = SUGAR, name = "SUGAR", available = 5000, level = 5000, capacity = 180000, isPallet = false}}
|
|
|
|
-- O23: intet salgssted tager varen -> log én gang med de fundne salgsmarkører og hvad de tager
|
|
ADSources.describeAccepted = function(station) return "SUGAR, FORAGE" end
|
|
local v23 = newVehicle("Dragon4", 100, 0, 1, true)
|
|
v23.carriable = {}
|
|
runs[v23] = outboundRun(26, {3}, {{fillType = "STRAW_CRATE", policy = "sell"}})
|
|
quotes["selling station"][CRATE] = nil; quotes["Far station"][CRATE] = nil
|
|
sources[3].stored[CRATE] = 2
|
|
logLines = {}
|
|
ADOutbound.choosePickup(adEnv, v23)
|
|
local noSell = lastLog("intet salgssted tager STRAW_CRATE")
|
|
check("O23 log om manglende salgssted", noSell ~= nil, true)
|
|
check("O23 lister salgsmarkører", noSell ~= nil and noSell:find("selling station (SUGAR, FORAGE)", 1, true) ~= nil, true)
|
|
logLines = {}
|
|
ADOutbound.choosePickup(adEnv, v23)
|
|
check("O23 kun én gang", lastLog("intet salgssted tager STRAW_CRATE"), nil)
|
|
|
|
-- 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
|
|
adEnv.LoadAtDestinationTask.finished = function(self) calls.finished = (calls.finished or 0) + 1 end
|
|
ADOutbound.install(adEnv)
|
|
local vX = newVehicle("X", 0, 0, 1000)
|
|
adEnv.LoadAtDestinationTask.update(newTask(vX, 1), 16)
|
|
check("O10 original update kaldt", calls.update, 1)
|
|
adEnv.LoadAtDestinationTask.finished(newTask(vX, 1))
|
|
check("O10 original finished kaldt", calls.finished, 1)
|
|
adEnv.UnloadAtDestinationTask.update = function(self, dt) calls.unload = (calls.unload or 0) + 1 end
|
|
ADOutbound.install(adEnv)
|
|
check("O10 unload-wrap ikke installeret dobbelt", adEnv.UnloadAtDestinationTask.update ~= nil, true)
|
|
|
|
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
|
os.exit(failures == 0 and 0 or 1)
|