chore: tests ud af det offentlige repo (kun lokalt); feedback uden krav om konto
This commit is contained in:
@@ -2,3 +2,4 @@
|
||||
docs/superpowers/
|
||||
bench/
|
||||
.superpowers/
|
||||
tests/
|
||||
|
||||
@@ -44,18 +44,12 @@ Alt sættes op i menuen **Smart Pickup** i ESC-menuen.
|
||||
|
||||
## Feedback
|
||||
|
||||
Fejl og idéer: opret et issue her i repoet. Vedlæg gerne `log.txt` fra
|
||||
Fejl og idéer: giv udvikleren besked (issues her kræver en konto på git.powerdata.dk). Vedlæg gerne `log.txt` fra
|
||||
`Documents/My Games/FarmingSimulator2025/`, eller i det mindste linjerne der starter med `ADSmartPickup`.
|
||||
|
||||
## Udvikling
|
||||
|
||||
- Mod-koden ligger i `FS25_ADSmartPickup/`, og testene ligger i `tests/` (ren Lua, kører uden spillet).
|
||||
- Kør testene:
|
||||
|
||||
```sh
|
||||
for t in tests/test_*.lua; do luajit $t >/dev/null 2>&1 || echo FAIL $t; done
|
||||
```
|
||||
|
||||
- Mod-koden ligger i `FS25_ADSmartPickup/`.
|
||||
- Byg zip: `cd FS25_ADSmartPickup && zip -qr ../FS25_ADSmartPickup.zip .`
|
||||
|
||||
---
|
||||
@@ -94,5 +88,5 @@ needed for pallets.
|
||||
**Docs:** [User manual](docs/USER_MANUAL.md) · [Danish manual](docs/BRUGERMANUAL.md) ·
|
||||
[Changelog](CHANGELOG.md)
|
||||
|
||||
**Feedback:** open an issue in this repository. Attach `log.txt`, or at least the lines starting with
|
||||
**Feedback:** tell the developer (issues here need an account on git.powerdata.dk). Attach `log.txt`, or at least the lines starting with
|
||||
`ADSmartPickup`.
|
||||
|
||||
@@ -1116,7 +1116,7 @@ ADSmartPickup: F109 …
|
||||
|
||||
Moden testes løbende, og fejlrapporter er meget velkomne.
|
||||
|
||||
1. Opret et issue i repoet: <https://git.powerdata.dk/masterdraco/fs25-adsmartpickup/issues>
|
||||
1. Giv udvikleren besked — eller opret et issue i repoet, hvis du har en konto: <https://git.powerdata.dk/masterdraco/fs25-adsmartpickup/issues>
|
||||
2. Beskriv, hvad du gjorde, hvad du forventede, og hvad der skete. Nævn versionen (står i
|
||||
`log.txt` og i mod-listen).
|
||||
3. Vedlæg `log.txt` fra `Documents/My Games/FarmingSimulator2025/log.txt`. Er filen for stor, så vedlæg
|
||||
|
||||
+1
-1
@@ -1146,7 +1146,7 @@ ADSmartPickup: F109 …
|
||||
|
||||
The mod is being tested continuously, and bug reports are very welcome.
|
||||
|
||||
1. Open an issue in the repository: <https://git.powerdata.dk/masterdraco/fs25-adsmartpickup/issues>
|
||||
1. Tell the developer — or open an issue in the repository if you have an account: <https://git.powerdata.dk/masterdraco/fs25-adsmartpickup/issues>
|
||||
2. Describe what you did, what you expected, and what happened. Mention the version (shown in `log.txt`
|
||||
and in the mod list).
|
||||
3. Attach `log.txt` from `Documents/My Games/FarmingSimulator2025/log.txt`. If the file is too big,
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
-- Kør: luajit tests/test_adBuildings.lua (fra repo-roden)
|
||||
-- Bygninger gården kan bruge + automatisk markør ved læsse-/aflæsningstrigger (≤ 40 m, Wait udelukket,
|
||||
-- siloer strengt), omvendt opslag markør -> bygning, id-fallback, cache.
|
||||
|
||||
local SUGAR, FORAGE, MILK, BASKET = 5, 6, 12, 7
|
||||
local DESC = {[SUGAR] = {name = "SUGAR", title = "Sukker", isPalletType = true}, [FORAGE] = {name = "FORAGE", title = "TMR"}, [MILK] = {name = "MILK", title = "Mælk"}, [BASKET] = {name = "STRAW_BASKET", title = "Kurve", palletFilename = "x"}}
|
||||
g_fillTypeManager = {getFillTypeByIndex = function(_, i) return DESC[i] end, getFillTypeNameByIndex = function(_, i) return DESC[i] and DESC[i].name end, getFillTypeTitleByIndex = function(_, i) return DESC[i] and DESC[i].title end}
|
||||
Logging = {info = function() end, warning = function() end}
|
||||
getWorldTranslation = function(node) return node.x, 0, node.z end
|
||||
SellingStation = {}
|
||||
dofile("FS25_ADSmartPickup/adSupplyPlanner.lua")
|
||||
dofile("FS25_ADSmartPickup/adPrices.lua")
|
||||
dofile("FS25_ADSmartPickup/adBuildings.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 markers, wayPoints = {}, {}
|
||||
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})
|
||||
end
|
||||
local loadStations, unloadStations = {}, {}
|
||||
local adEnv = {ADGraphManager = {
|
||||
getWayPointById = function(_, id) return wayPoints[id] end,
|
||||
getMapMarkers = function() return markers end,
|
||||
getMapMarkerByWayPointId = function(_, id) for _, m in ipairs(markers) do if m.id == id then return m end end end,
|
||||
}}
|
||||
local strictLoadAt = {}
|
||||
ADSmartPickup = {getLoadStationAtWayPointId = function(_, id) return strictLoadAt[id] end}
|
||||
g_currentMission = {
|
||||
placeableSystem = {placeables = {}},
|
||||
storageSystem = {getLoadingStations = function() return loadStations end, getUnloadingStations = function() return unloadStations end},
|
||||
accessHandler = {canFarmAccess = function() return true end},
|
||||
getFarmId = function() return 1 end,
|
||||
}
|
||||
local function placeable(uid, name, x, z, extra)
|
||||
local p = {uniqueId = uid, rootNode = {x = x, z = z}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return name end}
|
||||
for k, v in pairs(extra or {}) do p[k] = v end
|
||||
table.insert(g_currentMission.placeableSystem.placeables, p)
|
||||
return p
|
||||
end
|
||||
|
||||
-- verden: kostald med mælketank (load) og foder (unload), sukkerfabrik (unload + pallespawner), to siloer i række, objektlager, salgssted
|
||||
local barn = placeable("barn1", "Kostald 1", 0, 0, {getHusbandryFillLevel = function() return 0 end})
|
||||
-- som FS25_LargerAnimalBarns' cowBarnBig: ÉN læssestation med TO læssepunkter (gylle + mælk)
|
||||
local milkStation = {owningPlaceable = barn, loadTriggers = {{triggerNode = {x = 20, z = 0}}, {triggerNode = {x = 0, z = 30}}}}
|
||||
local barnUnload = {owningPlaceable = barn, unloadTriggers = {{exactFillRootNode = {x = -20, z = 0}}}}
|
||||
table.insert(loadStations, milkStation); table.insert(unloadStations, barnUnload)
|
||||
local mill = placeable("mill", "Sukkerfabrik", 500, 0, {spec_productionPoint = {productionPoint = {productions = {}, storage = {}, palletSpawner = {spawnPlaces = {{startNode = {x = 520, z = 0}}}}, unloadingStation = nil}}})
|
||||
local millUnload = {owningPlaceable = mill, unloadTriggers = {{exactFillRootNode = {x = 480, z = 0}}}, isa = function() return true end, isSellingPoint = true}
|
||||
table.insert(unloadStations, millUnload)
|
||||
local silo1 = placeable("silo1", "Silo 1", 1000, 0, {spec_silo = {}})
|
||||
local silo2 = placeable("silo2", "Silo 2", 1015, 0, {spec_silo = {}})
|
||||
local silo1Station = {owningPlaceable = silo1, loadTriggers = {{triggerNode = {x = 1000, z = 5}}}}
|
||||
local silo2Station = {owningPlaceable = silo2, loadTriggers = {{triggerNode = {x = 1015, z = 5}}}}
|
||||
table.insert(loadStations, silo1Station); table.insert(loadStations, silo2Station)
|
||||
table.insert(unloadStations, {owningPlaceable = silo1, unloadTriggers = {{exactFillRootNode = {x = 1000, z = 5}}}})
|
||||
table.insert(unloadStations, {owningPlaceable = silo2, unloadTriggers = {{exactFillRootNode = {x = 1015, z = 5}}}})
|
||||
local shed = placeable("shed", "Palleskur", 2000, 0, {spec_objectStorage = {objectInfos = {}, capacity = 100}})
|
||||
local sellPoint = placeable("sell", "Mejeri", 3000, 0, {})
|
||||
local sellStation = {owningPlaceable = sellPoint, unloadTriggers = {{exactFillRootNode = {x = 3000, z = 10}}}, isa = function() return true end, isSellingPoint = true}
|
||||
table.insert(unloadStations, sellStation)
|
||||
local other = placeable("otherfarm", "Naboens", 4000, 0, {spec_silo = {}, ownerFarmId = 2, getOwnerFarmId = function() return 2 end})
|
||||
|
||||
addMarker(1, "cow 1 milk", "Milk", 25, 0) -- 5 m fra mælketriggeren
|
||||
addMarker(2, "Cow 1 food", "Cow food", -30, 0) -- 10 m fra fodertriggeren
|
||||
addMarker(3, "cow wait 1", "cow wait", 22, 0) -- Wait: må ALDRIG vælges (tættere end mælkemarkøren)
|
||||
addMarker(11, "Cow 1 Slurry", "Slurry", 0, 33) -- 3 m fra gylletriggeren
|
||||
addMarker(4, "Suger Load", "Pickup", 525, 0) -- 5 m fra pallespawneren
|
||||
addMarker(5, "sugar offload", "production", 470, 0)
|
||||
addMarker(6, "silo 1", "silo", 1000, 12) -- strengt: silo1
|
||||
addMarker(7, "silo 2", "silo", 1015, 12) -- strengt: silo2 (7 m fra silo1's trigger-node på x-aksen? nej: 15 m)
|
||||
strictLoadAt[6] = silo1Station; strictLoadAt[7] = silo2Station
|
||||
addMarker(8, "Storage pickup", "Pickup", 2010, 0)
|
||||
addMarker(9, "Mejeri", "selling", 3000, 20)
|
||||
addMarker(10, "Langt væk", "All", 9000, 9000)
|
||||
|
||||
-- B1: liste, kind og markører
|
||||
local list = ADBuildings.list(adEnv, 1)
|
||||
local byId = {}
|
||||
for _, b in ipairs(list) do byId[b.id] = b end
|
||||
check("B1 antal (naboens silo udeladt)", #list, 6)
|
||||
check("B1 stald", byId.barn1.kind, "husbandry")
|
||||
check("B1 stald læssemarkør = mælk (ikke Wait, som er tættere)", byId.barn1.loadMarker.id, 1)
|
||||
-- én markør pr. læssestation (mælketank OG gyllebeholder), sorteret på markørnavn
|
||||
check("B1 stald har to læssemarkører", #byId.barn1.loadMarkers, 2)
|
||||
check("B1 første = cow 1 milk (navnesorteret, uanset store/små)", byId.barn1.loadMarkers[1].id, 1)
|
||||
check("B1 anden = Cow 1 Slurry", byId.barn1.loadMarkers[2].name, "Cow 1 Slurry")
|
||||
check("B1 gyllemarkør -> stald via byMarker", ADBuildings.byMarker(adEnv, 1, 11, "load").id, "barn1")
|
||||
check("B1 stald aflæsningsmarkør = foder", byId.barn1.unloadMarker.id, 2)
|
||||
check("B1 stald afstand", byId.barn1.loadMarker.distance, 5)
|
||||
check("B1 fabrik læssemarkør via pallespawner", byId.mill.loadMarker.id, 4)
|
||||
check("B1 fabrik aflæsningsmarkør", byId.mill.unloadMarker.id, 5)
|
||||
check("B1 fabrik er ikke salgssted", byId.mill.canSell, false)
|
||||
check("B1 objektlager markør via rootNode", byId.shed.loadMarker.id, 8)
|
||||
check("B1 salgssted", byId.sell.kind, "sell")
|
||||
check("B1 salgssted markør", byId.sell.unloadMarker.id, 9)
|
||||
check("B1 salgssted canSell", byId.sell.canSell, true)
|
||||
check("B1 stald kan være kilde og mål", byId.barn1.canBeSource and byId.barn1.canBeTarget, true)
|
||||
check("B1 silo kilde, ikke mål", byId.silo1.canBeSource and not byId.silo1.canBeTarget, true)
|
||||
|
||||
-- B2: siloer i række: strengt opslag giver hver sin markør
|
||||
check("B2 silo 1 markør", byId.silo1.loadMarker.id, 6)
|
||||
check("B2 silo 2 markør", byId.silo2.loadMarker.id, 7)
|
||||
strictLoadAt[7] = nil
|
||||
ADBuildings.invalidate()
|
||||
check("B2 silo uden strengt match: ingen læssemarkør", ADBuildings.find(adEnv, 1, "silo2").loadMarker, nil)
|
||||
strictLoadAt[7] = silo2Station
|
||||
ADBuildings.invalidate()
|
||||
|
||||
-- B3: byMarker (omvendt opslag)
|
||||
check("B3 mælkemarkør -> stald (load)", ADBuildings.byMarker(adEnv, 1, 1, "load").id, "barn1")
|
||||
check("B3 fodermarkør -> stald (unload)", ADBuildings.byMarker(adEnv, 1, 2, "unload").id, "barn1")
|
||||
check("B3 fodermarkør er ikke load", ADBuildings.byMarker(adEnv, 1, 2, "load"), nil)
|
||||
check("B3 fjern markør -> nil", ADBuildings.byMarker(adEnv, 1, 10, "load"), nil)
|
||||
|
||||
-- B4: id-fallback uden uniqueId
|
||||
check("B4 id fra position", ADBuildings.getId({rootNode = {x = 12.6, z = -3.2}}), "pos:13:-3")
|
||||
check("B4 id fra uniqueId", ADBuildings.getId(barn), "barn1")
|
||||
|
||||
-- B5: cache: samme tabel inden for CACHE_MS, ny efter invalidate
|
||||
local first = ADBuildings.list(adEnv, 1)
|
||||
check("B5 cachet", ADBuildings.list(adEnv, 1) == first, true)
|
||||
ADBuildings.invalidate()
|
||||
check("B5 ny efter invalidate", ADBuildings.list(adEnv, 1) ~= first, true)
|
||||
|
||||
-- B6: sortering efter NAVN (naturlig talrækkefølge), dernæst første markørnavn — ikke efter slags
|
||||
local order = {}
|
||||
for _, b in ipairs(ADBuildings.list(adEnv, 1)) do table.insert(order, b.name) end
|
||||
check("B6 rækkefølge", table.concat(order, ","), "Kostald 1,Mejeri,Palleskur,Silo 1,Silo 2,Sukkerfabrik")
|
||||
check("B6 naturlig sortering", ADBuildings.naturalLess("Silo 9", "Silo 10"), true)
|
||||
check("B6 naturlig sortering, tal før tekst-lighed", ADBuildings.naturalLess("25 Silo Storage", "26 Silo Storage"), true)
|
||||
check("B6 naturlig sortering ens navn -> markørnavn", ADBuildings.compare({name = "Farma", loadMarkers = {{name = "30 Silo"}}}, {name = "Farma", loadMarkers = {{name = "4 Silo"}}}), false)
|
||||
|
||||
-- B-DOB: silo der kun kan FYLDES (drive-on-bunker ved biogas: aflæsning, ingen læssestation) = mål, ikke kilde
|
||||
do
|
||||
local dob = placeable("dob1", "DOB 115", 5000, 0, {spec_silo = {}})
|
||||
table.insert(unloadStations, {owningPlaceable = dob, unloadTriggers = {{exactFillRootNode = {x = 5000, z = 5}}}})
|
||||
addMarker(21, "Biogas Drop 1", "Biogas", 5000, 12)
|
||||
ADBuildings.invalidate()
|
||||
local byIdDob = {}
|
||||
for _, b in ipairs(ADBuildings.list(adEnv, 1)) do byIdDob[b.id] = b end
|
||||
check("B-DOB kan være mål", byIdDob.dob1.canBeTarget, true)
|
||||
check("B-DOB ikke kilde (intet at hente)", byIdDob.dob1.canBeSource, false)
|
||||
check("B-DOB aflæsningsmarkør", byIdDob.dob1.unloadMarker.id, 21)
|
||||
check("B-DOB almindelig silo stadig ikke mål", byIdDob.silo1.canBeTarget, false)
|
||||
end
|
||||
|
||||
-- B-SEP: bygning med læssestation men uden silo/fabrik/stald-spec (Pumps&Hoses separatorhal: digestat-udlevering)
|
||||
do
|
||||
local sep = placeable("sep1", "Separator Hall", 7000, 0, {spec_manureSeparator = {}})
|
||||
table.insert(loadStations, {owningPlaceable = sep, loadTriggers = {{triggerNode = {x = 7000, z = 5}}}})
|
||||
addMarker(41, "Digestate pickup", "Biogas", 7000, 10)
|
||||
ADBuildings.invalidate()
|
||||
local bySep = {}
|
||||
for _, b in ipairs(ADBuildings.list(adEnv, 1)) do bySep[b.id] = b end
|
||||
check("B-SEP findes som lager", bySep.sep1 and bySep.sep1.kind, "storage")
|
||||
check("B-SEP kan være kilde", bySep.sep1 and bySep.sep1.canBeSource, true)
|
||||
check("B-SEP læssemarkør", bySep.sep1 and bySep.sep1.loadMarker and bySep.sep1.loadMarker.id, 41)
|
||||
end
|
||||
|
||||
-- B-DROP: fabrik med pickup-markør TÆT på aflæsningstriggeren og en separat drop-markør længere væk
|
||||
-- (Sugarbeet Crusher): aflæsningsmarkøren skal være drop, ikke bygningens egen læssemarkør
|
||||
do
|
||||
local crusher = placeable("crusher1", "Sugarbeet Crusher", 8000, 0, {spec_productionPoint = {productionPoint = {productions = {}, storage = {}}}})
|
||||
table.insert(loadStations, {owningPlaceable = crusher, loadTriggers = {{triggerNode = {x = 8000, z = 5}}}})
|
||||
table.insert(unloadStations, {owningPlaceable = crusher, unloadTriggers = {{exactFillRootNode = {x = 8003, z = 2}}}})
|
||||
addMarker(51, "Sugar Beet Cut Pickup", "Fabrication", 8000, 8) -- 3 m fra læs, ~6 m fra aflæsning
|
||||
addMarker(52, "Sugar Beet Cut Beet Drop", "Fabrication", 8012, 2) -- 9 m fra aflæsning
|
||||
ADBuildings.invalidate()
|
||||
local byC = {}
|
||||
for _, b in ipairs(ADBuildings.list(adEnv, 1)) do byC[b.id] = b end
|
||||
check("B-DROP læssemarkør = pickup", byC.crusher1.loadMarker.id, 51)
|
||||
check("B-DROP aflæsningsmarkør = drop (ikke pickup)", byC.crusher1.unloadMarker.id, 52)
|
||||
-- kun én fælles markør (AIO Load / Unload): bruges til begge
|
||||
local aio = placeable("aio1", "AIO", 9000, 0, {spec_productionPoint = {productionPoint = {productions = {}, storage = {}}}})
|
||||
table.insert(loadStations, {owningPlaceable = aio, loadTriggers = {{triggerNode = {x = 9000, z = 5}}}})
|
||||
table.insert(unloadStations, {owningPlaceable = aio, unloadTriggers = {{exactFillRootNode = {x = 9002, z = 5}}}})
|
||||
addMarker(53, "AIO Load / Unload", "Fabrication", 9000, 9)
|
||||
ADBuildings.invalidate()
|
||||
for _, b in ipairs(ADBuildings.list(adEnv, 1)) do byC[b.id] = b end
|
||||
check("B-DROP fælles markør bruges til aflæsning", byC.aio1.unloadMarker.id, 53)
|
||||
check("B-DROP silo beholder sin egen markør (ikke nabosiloens)", byC.silo1.unloadMarker.id, 6)
|
||||
end
|
||||
|
||||
-- B-COOP: hønsehus — foder aflæsses i den ene ende, æg-paller spawnes i den anden (ingen læssestation)
|
||||
do
|
||||
local coop = placeable("coop1", "Chicken Coop", 10000, 0, {getHusbandryFillLevel = function() return 0 end,
|
||||
spec_husbandryFood = {triggers = {{someTrigger = {exactFillRootNode = {x = 9970, z = 0}}}}}, -- ukendt feltsti: søges
|
||||
spec_husbandryPallets = {fillTypeIndexToPalletSpawner = {[99] = {spawnPlaces = {{startNode = {x = 10030, z = 0}}}}}}})
|
||||
table.insert(unloadStations, {owningPlaceable = coop, unloadTriggers = {}}) -- som chickenBarnBig: tom station
|
||||
addMarker(61, "Chicken Food", "Chicken", 9965, 0)
|
||||
addMarker(62, "Egg Pickup", "Chicken", 10035, 0)
|
||||
ADBuildings.invalidate()
|
||||
local byCoop = {}
|
||||
for _, b in ipairs(ADBuildings.list(adEnv, 1)) do byCoop[b.id] = b end
|
||||
check("B-COOP læssemarkør ved æggene", byCoop.coop1.loadMarker and byCoop.coop1.loadMarker.id, 62)
|
||||
check("B-COOP aflæsningsmarkør ved foderet", byCoop.coop1.unloadMarker and byCoop.coop1.unloadMarker.id, 61)
|
||||
end
|
||||
|
||||
-- B-FUEL: brændstoftank (læssestation kun med DIESEL) er ikke et lager/en kilde
|
||||
do
|
||||
local fmBefore = g_fillTypeManager
|
||||
g_fillTypeManager = {getFillTypeNameByIndex = function(_, index) return index == 900 and "DIESEL" or "X" end}
|
||||
local tank = placeable("fuel1", "Small Petrol Tank", 11000, 0, {})
|
||||
table.insert(loadStations, {owningPlaceable = tank, supportedFillTypes = {[900] = true}, loadTriggers = {{triggerNode = {x = 11000, z = 3}}}})
|
||||
ADBuildings.invalidate()
|
||||
local byFuel = {}
|
||||
for _, b in ipairs(ADBuildings.list(adEnv, 1)) do byFuel[b.id] = b end
|
||||
check("B-FUEL brændstoftank udeladt", byFuel.fuel1, nil)
|
||||
g_fillTypeManager = fmBefore
|
||||
end
|
||||
|
||||
-- B-SCAN: søgning efter trigger-noder i indlejrede tabeller (dybde ≤ 3, ingen løkker)
|
||||
do
|
||||
local loop = {}
|
||||
loop.self = loop
|
||||
local nodes = ADBuildings.findTriggerNodes({a = {b = {exactFillRootNode = "n1"}}, c = {triggerNode = "n2"}, d = loop, e = {f = {g = {h = {exactFillRootNode = "for-dybt"}}}}})
|
||||
table.sort(nodes)
|
||||
check("B-SCAN finder n1 og n2, ikke for dybt", table.concat(nodes, ","), "n1,n2")
|
||||
end
|
||||
|
||||
-- B-SHED: palleskur læsser og aflæsser samme sted — aflæsningsmarkøren er den nærmeste (ikke "den anden")
|
||||
do
|
||||
local shedX = placeable("shedX", "Bale & Pallet Storage", 12000, 0, {spec_objectStorage = {objectInfos = {}, capacity = 4000}})
|
||||
addMarker(71, "Chicken Storage Unload", "Chicken", 12010, 0) -- 10 m
|
||||
addMarker(72, "Chicken Storage Pickup", "Chicken", 12018, 0) -- 18 m
|
||||
ADBuildings.invalidate()
|
||||
local byShed = {}
|
||||
for _, b in ipairs(ADBuildings.list(adEnv, 1)) do byShed[b.id] = b end
|
||||
check("B-SHED aflæsning = nærmeste (Unload)", byShed.shedX.unloadMarker and byShed.shedX.unloadMarker.id, 71)
|
||||
end
|
||||
|
||||
|
||||
-- pickLoadMarker: en pallespawner viger for aflæsningen, når begge vil have samme markør og aflæsningen står
|
||||
-- tættere (in-game 27/9 fårestald: spawner 9 m fra "Sheep Food", foderindkast 5 m -> Goat Food fik "Sheep Milk")
|
||||
do
|
||||
local food, milk = {id = 1, name = "Sheep Food 1", distance = 9}, {id = 2, name = "Sheep Milk 1", distance = 13}
|
||||
local unloadBest = {id = 1, name = "Sheep Food 1", distance = 5}
|
||||
check("PL spawner viger for tættere aflæsning", ADBuildings.pickLoadMarker({food, milk}, unloadBest, true).name, "Sheep Milk 1")
|
||||
check("PL rigtig læssetrigger beholder markøren (Sugarbeet Crusher)", ADBuildings.pickLoadMarker({food, milk}, unloadBest, false).name, "Sheep Food 1")
|
||||
-- in-game 27/9 fårestald 3 og 5: spawner 5 m fra "Sheep Food", foderindkast 9 m -> spawneren vandt stadig
|
||||
check("PL spawner viger også når den selv står tættest", ADBuildings.pickLoadMarker({food, milk}, {id = 1, name = "Sheep Food 1", distance = 12}, true).name, "Sheep Milk 1")
|
||||
check("PL rigtig læssetrigger tættest beholder", ADBuildings.pickLoadMarker({food, milk}, {id = 1, name = "Sheep Food 1", distance = 12}, false).name, "Sheep Food 1")
|
||||
check("PL ingen konflikt", ADBuildings.pickLoadMarker({milk}, unloadBest, true).name, "Sheep Milk 1")
|
||||
check("PL eneste kandidat er aflæsningens -> ingen læssemarkør", ADBuildings.pickLoadMarker({food}, unloadBest, true), nil)
|
||||
check("PL ingen kandidater", ADBuildings.pickLoadMarker({}, unloadBest, true), nil)
|
||||
end
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,40 +0,0 @@
|
||||
-- Kør: luajit tests/test_adCache.lua (fra repo-roden)
|
||||
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
|
||||
g_currentMission = {time = 0}
|
||||
dofile("FS25_ADSmartPickup/adCache.lua")
|
||||
local calls = 0
|
||||
local function lookup() calls = calls + 1; return "station" end
|
||||
check("K1 slået fra: altid opslag", ADCacheGet("b", "k", 1000, lookup) .. ADCacheGet("b", "k", 1000, lookup), "stationstation")
|
||||
check("K1 to opslag", calls, 2)
|
||||
ADCache.enabled = true
|
||||
calls = 0
|
||||
ADCacheGet("b", "k", 1000, lookup); ADCacheGet("b", "k", 1000, lookup)
|
||||
check("K2 genbrugt", calls, 1)
|
||||
g_currentMission.time = 900
|
||||
ADCacheGet("b", "k", 1000, lookup)
|
||||
check("K2 inden for levetid", calls, 1)
|
||||
g_currentMission.time = 1600
|
||||
ADCacheGet("b", "k", 1000, lookup)
|
||||
check("K3 efter levetid + spredning (maks 1500) -> nyt opslag", calls, 2)
|
||||
local nothing = 0
|
||||
ADCacheGet("n", "x", 1000, function() nothing = nothing + 1; return nil end)
|
||||
check("K4 intet fundet huskes", ADCacheGet("n", "x", 1000, function() nothing = nothing + 1; return "ny" end), nil)
|
||||
check("K4 kun ét opslag", nothing, 1)
|
||||
-- spredt udløb: 200 poster lavet samtidig udløber ikke samtidig
|
||||
g_currentMission.time = 10000
|
||||
for i = 1, 200 do ADCacheGet("spread", i, 10000, function() return i end) end
|
||||
local expired = 0
|
||||
g_currentMission.time = 22000
|
||||
for i = 1, 200 do ADCacheGet("spread", i, 10000, function() expired = expired + 1; return i end) end
|
||||
check("K5 kun en del udløbet efter 12 s (spredt 10-15 s)", expired > 20 and expired < 180, true)
|
||||
ADCache.clear()
|
||||
check("K6 ryddet", next(ADCache.buckets), nil)
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,195 +0,0 @@
|
||||
-- Kør: luajit tests/test_adCourseCache.lua (fra repo-roden)
|
||||
-- Kursus-registret: hvilken mark/bredde/markform/indstillinger en maskines nuværende Courseplay-kursus blev
|
||||
-- lavet til. Genbrug kun når ALT matcher; egen maskine først, ellers en anden maskines kursus (kopi).
|
||||
dofile("FS25_ADSmartPickup/adCourseCache.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 count(map)
|
||||
local n = 0
|
||||
for _ in pairs(map) do n = n + 1 end
|
||||
return n
|
||||
end
|
||||
|
||||
local C = ADCourseCache
|
||||
local SIG = "n=4;a=960000;c=800,300"
|
||||
|
||||
-- ---- indstillings-nøglen ----
|
||||
check("K1 standard", C.settingsKey({}), "off=0.0;lane=-;multi=1;vine=-;skip=-")
|
||||
check("K2 nil = standard", C.settingsKey(nil), C.settingsKey({}))
|
||||
check("K3 alt sat", C.settingsKey({toolOffset = 1.25, lane = -1, multiTools = 2, vineOffset = -1, vineSkip = 1}),
|
||||
"off=1.3;lane=-1;multi=2;vine=-1.0;skip=1")
|
||||
check("K4 -0.0 = 0.0", C.settingsKey({toolOffset = -0.0001}), C.settingsKey({toolOffset = 0}))
|
||||
check("K5 float-støj i forskydning tæller ikke", C.settingsKey({toolOffset = 1.2000001}), C.settingsKey({toolOffset = 1.2}))
|
||||
check("K6 anden bane = anden nøgle", C.settingsKey({multiTools = 2, lane = 1}) ~= C.settingsKey({multiTools = 2, lane = -1}), true)
|
||||
check("K7 vin-offset indgår", C.settingsKey({vineOffset = 0}) ~= C.settingsKey({vineOffset = -1}), true)
|
||||
check("K8 vin-skip indgår", C.settingsKey({vineOffset = -1, vineSkip = 0}) ~= C.settingsKey({vineOffset = -1, vineSkip = 1}), true)
|
||||
|
||||
local settings = C.settingsKey({})
|
||||
local request = {field = 22, width = 30, sig = SIG, settings = settings}
|
||||
local own = C.newRecord({vehicle = "v1", field = 22, width = 30.0, sig = SIG, settings = settings, waypoints = 812})
|
||||
|
||||
-- ---- key/matches ----
|
||||
check("M1 samme alt", C.matches(own, request), true)
|
||||
check("M2 bredde som float32 fra XML (30.0000001)", C.matches(C.newRecord({vehicle = "v1", field = 22, width = 30.0000001, sig = SIG, settings = settings}), request), true)
|
||||
check("M3 ny bredde", C.matches(own, {field = 22, width = 12, sig = SIG, settings = settings}), false)
|
||||
check("M4 anden mark", C.matches(own, {field = 23, width = 30, sig = SIG, settings = settings}), false)
|
||||
check("M5 form ændret", C.matches(own, {field = 22, width = 30, sig = "n=5;a=960000;c=800,300", settings = settings}), false)
|
||||
check("M6 andre indstillinger", C.matches(own, {field = 22, width = 30, sig = SIG, settings = C.settingsKey({toolOffset = 2})}), false)
|
||||
check("M7 ukendt form (nil) matcher aldrig", C.matches(C.newRecord({vehicle = "v1", field = 22, width = 30, settings = settings}), {field = 22, width = 30, settings = settings}), false)
|
||||
check("M8 intet register", C.matches(nil, request), false)
|
||||
check("M9 key", C.key(request), "22|30.0|" .. SIG .. "|" .. settings)
|
||||
check("M10 key uden form = nil", C.key({field = 22, width = 30, settings = settings}), nil)
|
||||
|
||||
-- ---- grund til ny beregning ----
|
||||
check("R1 intet gemt", C.mismatchReason(nil, request), "intet kursus gemt")
|
||||
check("R2 form ændret", C.mismatchReason(own, {field = 22, width = 30, sig = "x", settings = settings}), "markens form ændret")
|
||||
check("R3 ny bredde", C.mismatchReason(own, {field = 22, width = 12, sig = SIG, settings = settings}), "ny bredde")
|
||||
check("R4 anden mark", C.mismatchReason(own, {field = 9, width = 30, sig = SIG, settings = settings}), "kursus til en anden mark")
|
||||
check("R5 indstillinger", C.mismatchReason(own, {field = 22, width = 30, sig = SIG, settings = "x"}), "andre indstillinger")
|
||||
check("R6 ukendt form", C.mismatchReason(own, {field = 22, width = 30, settings = settings}), "markens form ukendt")
|
||||
|
||||
-- ---- register: immutabelt ----
|
||||
local records = C.withRecord({}, own)
|
||||
check("I1 tilføjet", records.v1 ~= nil, true)
|
||||
local other = C.newRecord({vehicle = "v2", field = 22, width = 30, sig = SIG, settings = settings, waypoints = 812})
|
||||
local both = C.withRecord(records, other)
|
||||
check("I2 original urørt", count(records), 1)
|
||||
check("I3 begge", count(both), 2)
|
||||
check("I4 without", count(C.without(both, "v1")), 1)
|
||||
check("I5 without urørt original", count(both), 2)
|
||||
check("I6 newRecord uden vehicle = nil", C.newRecord({field = 22}), nil)
|
||||
|
||||
-- ---- findReusable ----
|
||||
local rec, how = C.findReusable(both, request, "v1")
|
||||
check("F1 egen maskine først", rec and rec.vehicle, "v1")
|
||||
check("F2 egen = own", how, "own")
|
||||
rec, how = C.findReusable(both, request, "v3")
|
||||
check("F3 anden maskines kursus", rec and rec.vehicle, "v1")
|
||||
check("F4 = copy", how, "copy")
|
||||
-- egen maskines kursus er til en anden bredde -> kopi fra v2
|
||||
local mixed = C.withRecord(both, C.newRecord({vehicle = "v1", field = 22, width = 12, sig = SIG, settings = settings}))
|
||||
rec, how = C.findReusable(mixed, request, "v1")
|
||||
check("F5 egen passer ikke -> kopi fra v2", rec and rec.vehicle, "v2")
|
||||
check("F6 kopi", how, "copy")
|
||||
-- intet passer -> grunden fra egen post
|
||||
local changed = {field = 22, width = 30, sig = "n=6;a=1;c=0,0", settings = settings}
|
||||
rec, how = C.findReusable(both, changed, "v1")
|
||||
check("F7 intet passer", rec, nil)
|
||||
check("F8 grund: form ændret", how, "markens form ændret")
|
||||
rec, how = C.findReusable({}, request, "v1")
|
||||
check("F9 tomt register", how, "intet kursus gemt")
|
||||
-- gyldighedstjek: egen maskines kursus er væk/ændret -> kopi fra v2; alle ugyldige -> ny
|
||||
rec, how = C.findReusable(both, request, "v1", function(r) return r.vehicle ~= "v1" end)
|
||||
check("F10 egen ugyldig -> kopi", rec and rec.vehicle, "v2")
|
||||
rec, how = C.findReusable(both, request, "v1", function() return false end)
|
||||
check("F11 alle ugyldige", rec, nil)
|
||||
check("F12 grund: kurset på maskinen er ændret", how, "kurset på maskinen er ændret")
|
||||
-- multi-tool: lederens kursus (2 sæt, bane -1) genbruges ikke til et enkelt sæt
|
||||
local multi = C.newRecord({vehicle = "v1", field = 22, width = 30, sig = SIG, settings = C.settingsKey({multiTools = 2, lane = -1})})
|
||||
rec = C.findReusable(C.withRecord({}, multi), request, "v1")
|
||||
check("F13 multi-kursus != enkelt", rec, nil)
|
||||
rec, how = C.findReusable(C.withRecord({}, multi), {field = 22, width = 30, sig = SIG, settings = C.settingsKey({multiTools = 2, lane = -1})}, "v1")
|
||||
check("F14 multi samme bane genbruges", how, "own")
|
||||
-- deterministisk: flere kandidater -> laveste id
|
||||
local many = C.withRecord(C.withRecord({}, C.newRecord({vehicle = "v9", field = 22, width = 30, sig = SIG, settings = settings})),
|
||||
C.newRecord({vehicle = "v4", field = 22, width = 30, sig = SIG, settings = settings}))
|
||||
rec = C.findReusable(many, request, "v1")
|
||||
check("F15 deterministisk valg", rec and rec.vehicle, "v4")
|
||||
|
||||
-- ---- invalidateField ----
|
||||
local fieldRecords = C.withRecord(both, C.newRecord({vehicle = "v5", field = 7, width = 30, sig = "n=3;a=50;c=3,3", settings = settings}))
|
||||
local kept, removed = C.invalidateField(fieldRecords, 22, "n=5;a=1;c=0,0")
|
||||
check("V1 to poster for marken slettet", removed, 2)
|
||||
check("V2 anden mark bevaret", kept.v5 ~= nil, true)
|
||||
check("V3 original urørt", count(fieldRecords), 3)
|
||||
kept, removed = C.invalidateField(fieldRecords, 22, SIG)
|
||||
check("V4 samme form: intet slettes", removed, 0)
|
||||
check("V4b samme tabel tilbage", kept, fieldRecords)
|
||||
kept, removed = C.invalidateField(fieldRecords, 22, nil)
|
||||
check("V5 ukendt form: intet slettes", removed, 0)
|
||||
kept, removed = C.invalidateField(nil, 22, SIG)
|
||||
check("V6 nil-register", removed, 0)
|
||||
|
||||
-- ---- lim: prepare/afterStart/checkField med falske spil-objekter ----
|
||||
dofile("FS25_ADSmartPickup/adCpBridge.lua")
|
||||
local saves, logs = 0, {}
|
||||
ADFieldJobs = {state = {courseRecords = {}}, save = function() saves = saves + 1 end,
|
||||
log = function(_, fmt, ...) table.insert(logs, string.format(fmt, ...)) end}
|
||||
ADRuns = {vehicleLabel = function(v) return v.label end}
|
||||
Logging = {warning = function(...) print("WARN", ...) end}
|
||||
local function fakeCourse(n)
|
||||
return {getNumberOfWaypoints = function() return n end}
|
||||
end
|
||||
local function fakeVehicle(id, width, course)
|
||||
return {label = id, course = course,
|
||||
getFieldWorkCourse = function(self) return self.course end,
|
||||
getCourseGeneratorSettings = function() return {workWidth = {getValue = function() return width end}} end}
|
||||
end
|
||||
local tractorA = fakeVehicle("A", 30, nil)
|
||||
local tractorB = fakeVehicle("B", 30, nil)
|
||||
ADRunsController = {getFarmVehicles = function() return {tractorA, tractorB} end,
|
||||
getVehicleId = function(v) return v.label end}
|
||||
local fieldObj, info = {id = 22}, {shapeSig = SIG, fruitName = "WHEAT"}
|
||||
local assignA = {vehicle = tractorA, vehicleId = "A", role = "cultivator"}
|
||||
|
||||
-- G1: intet gemt -> ny beregning, opts urørt (ingen reuse/course)
|
||||
local opts, ctx = C.prepare({}, fieldObj, info, assignA, {})
|
||||
check("G1 ny beregning: ingen reuse", opts.reuse, nil)
|
||||
check("G1b ingen kursus", opts.course, nil)
|
||||
check("G1c log: nyt kursus (intet gemt)", logs[#logs]:find("nyt kursus beregnes", 1, true) ~= nil and logs[#logs]:find("intet kursus gemt", 1, true) ~= nil, true)
|
||||
-- CP har lavet kurset (812 punkter) -> registreres
|
||||
tractorA.course = fakeCourse(812)
|
||||
C.afterStart(tractorA, "A", ctx)
|
||||
check("G2 registreret", ADFieldJobs.state.courseRecords.A ~= nil and ADFieldJobs.state.courseRecords.A.waypoints, 812)
|
||||
check("G2b gemt til fil", saves, 1)
|
||||
-- G3: næste gang -> genbrug
|
||||
opts, ctx = C.prepare({}, fieldObj, info, assignA, {})
|
||||
check("G3 genbrug", opts.reuse, true)
|
||||
check("G3b log genbrugt", logs[#logs]:find("kursus genbrugt", 1, true) ~= nil, true)
|
||||
C.afterStart(tractorA, "A", ctx)
|
||||
check("G3c uændret register gemmes ikke igen", saves, 1)
|
||||
-- G4: B skal samme mark -> kopi fra A
|
||||
local assignB = {vehicle = tractorB, vehicleId = "B", role = "cultivator"}
|
||||
opts, ctx = C.prepare({}, fieldObj, info, assignB, {})
|
||||
check("G4 kopi fra A", opts.course, tractorA.course)
|
||||
check("G4b log kopieret", logs[#logs]:find("kopieret fra 'A'", 1, true) ~= nil, true)
|
||||
tractorB.course = fakeCourse(812)
|
||||
C.afterStart(tractorB, "B", ctx)
|
||||
check("G4c B registreret", ADFieldJobs.state.courseRecords.B ~= nil, true)
|
||||
-- G5: spilleren har lavet et nyt kursus på A i CP's HUD (andet antal punkter) -> kopi fra B
|
||||
tractorA.course = fakeCourse(400)
|
||||
opts = C.prepare({}, fieldObj, info, assignA, {})
|
||||
check("G5 A's kursus ændret -> kopi fra B", opts.course, tractorB.course)
|
||||
-- G6: kursus kopieret fra slåmaskinen (opts.course) -> registret bruges ikke, maskinens post glemmes
|
||||
local mowerCourse = fakeCourse(99)
|
||||
opts, ctx = C.prepare({}, fieldObj, info, assignB, {course = mowerCourse})
|
||||
check("G6 opts urørt", opts.course, mowerCourse)
|
||||
check("G6b ingen kontekst", ctx, nil)
|
||||
tractorB.course = fakeCourse(99)
|
||||
C.afterStart(tractorB, "B", ctx)
|
||||
check("G6c B glemt", ADFieldJobs.state.courseRecords.B, nil)
|
||||
-- G7: vinmark: vin-offset indgår i nøglen
|
||||
local vineReq = C.requestFor(tractorA, 22, {shapeSig = SIG, fruitName = "GRAPE"}, "fertilizer", {})
|
||||
check("G7 vin i nøglen", vineReq.settings, C.settingsKey({vineOffset = -1, vineSkip = 1}))
|
||||
-- G8: markens form ændret -> A's post glemmes, log
|
||||
local before = saves
|
||||
check("G8 checkField sletter", C.checkField(22, "n=9;a=1;c=0,0"), 1)
|
||||
check("G8b register tomt for marken", ADFieldJobs.state.courseRecords.A, nil)
|
||||
check("G8c gemt", saves, before + 1)
|
||||
check("G9 samme form: intet", C.checkField(22, "n=9;a=1;c=0,0"), 0)
|
||||
|
||||
-- G10: fejl i spil-API'et -> falder tilbage til ny beregning (opts urørt), ingen crash
|
||||
local brokenVehicle = {label = "X", getFieldWorkCourse = function() error("boom") end}
|
||||
local plainOpts = {lane = 1}
|
||||
local okPrep, gotOpts, gotCtx = pcall(C.prepare, {}, fieldObj, nil, {vehicle = brokenVehicle, vehicleId = "X", role = "x"}, plainOpts)
|
||||
check("G10 ingen crash", okPrep, true)
|
||||
check("G10b opts urørt", gotOpts, plainOpts)
|
||||
check("G10c ingen kontekst", gotCtx, nil)
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,178 +0,0 @@
|
||||
-- Kør: luajit tests/test_adCpBridge.lua (fra repo-roden)
|
||||
-- Kun getWorkWidth: variabel bredde (Unrealistic Pack) vinder over AI-markørernes grundbredde.
|
||||
dofile("FS25_ADSmartPickup/adCpBridge.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 withMarkers(width)
|
||||
return {getAIMarkers = function() return 1, 2, 3, 4, width end}
|
||||
end
|
||||
|
||||
-- W1: læssevogn med variabel bredde 20,4 + 20,4 = 40,8 m, AI-markører siger 2,4 m
|
||||
local wagon = withMarkers(2.4)
|
||||
wagon.getVariableWorkWidth = function(_, isLeft) return isLeft and 20.4 or -20.4, nil, true end
|
||||
local tractor = {getAttachedImplements = function() return {{object = wagon}} end}
|
||||
check("W1 variabel bredde", ADCpBridge.getWorkWidth(tractor), 40.8)
|
||||
-- W2: ugyldig variabel bredde -> AI-markører
|
||||
local plain = withMarkers(9.5)
|
||||
plain.getVariableWorkWidth = function() return 0, nil, false end
|
||||
check("W2 AI-markører", ADCpBridge.getWorkWidth(plain), 9.5)
|
||||
-- W3: selvkørende slåmaskine uden redskaber, kun variabel bredde
|
||||
local mower = {getVariableWorkWidth = function(_, isLeft) return isLeft and 20.4 or -20.4, nil, true end}
|
||||
check("W3 selvkørende", ADCpBridge.getWorkWidth(mower), 40.8)
|
||||
|
||||
-- W4: Courseplays egen bredde (HUD'en) vinder — det er den marken køres med
|
||||
local cpVehicle = {getVariableWorkWidth = function(_, isLeft) return isLeft and 20.4 or -0.4, nil, true end,
|
||||
getCourseGeneratorSettings = function() return {workWidth = {getValue = function() return 40.8 end}} end}
|
||||
check("W4 Courseplay-bredde", ADCpBridge.getWorkWidth(cpVehicle), 40.8)
|
||||
-- W5: Courseplay-indstilling 0/ugyldig -> egen beregning
|
||||
local zeroCp = {getVariableWorkWidth = function(_, isLeft) return isLeft and 5 or -5, nil, true end,
|
||||
getCourseGeneratorSettings = function() return {workWidth = {getValue = function() return 0 end}} end}
|
||||
check("W5 CP 0 -> egen beregning", ADCpBridge.getWorkWidth(zeroCp), 10)
|
||||
|
||||
-- WO: skårets sideforskydning på en river, målt fra nedlægningsområdet (venstre positiv), minus rivens
|
||||
-- egen CP-forskydning. Noder er tal; localToLocal-mock giver nodens x i traktorens ramme.
|
||||
local nodeX = {[11] = 2.0, [12] = 4.0, [21] = -1.0, [22] = 1.0}
|
||||
function localToLocal(node) return nodeX[node] or 0, 0, 0 end
|
||||
local function rake(dropIndex, runtime)
|
||||
local areas = {
|
||||
{start = 1, width = 2, height = 3, dropWindrowWorkAreaIndex = runtime and dropIndex or nil},
|
||||
{start = 10, width = 11, height = 12},
|
||||
{start = 20, width = 21, height = 22},
|
||||
}
|
||||
return {spec_windrower = {}, spec_workArea = {workAreas = areas}, configFileName = "rake.xml"}
|
||||
end
|
||||
local sideRake = rake(2, true)
|
||||
local tractorWithRake = {rootNode = 100, getAttachedImplements = function() return {{object = sideRake}} end,
|
||||
getCpSettings = function() return {toolOffsetX = {getValue = function() return 0.5 end}} end}
|
||||
check("WO1 sideriver (runtime-felt)", ADCpBridge.getWindrowOffset(tractorWithRake), 2.5)
|
||||
local centerRake = rake(3, true)
|
||||
local tractorCenter = {rootNode = 100, getAttachedImplements = function() return {{object = centerRake}} end}
|
||||
check("WO2 midterrive", ADCpBridge.getWindrowOffset(tractorCenter), 0)
|
||||
-- WO3: runtime-felt mangler -> riverens XML (workArea(0) windrower#dropWindrowWorkAreaIndex = 2)
|
||||
ADCpBridge.readDropIndicesFromXml = function(fileName) return fileName == "rake.xml" and {[1] = 2} or {} end
|
||||
local xmlRake = rake(2, false)
|
||||
local tractorXml = {rootNode = 100, getAttachedImplements = function() return {{object = xmlRake}} end}
|
||||
check("WO3 fra XML", ADCpBridge.getWindrowOffset(tractorXml), 3)
|
||||
check("WO4 ingen river -> nil", ADCpBridge.getWindrowOffset({rootNode = 100}), nil)
|
||||
|
||||
-- SR: sprøjte/spreder -> rolle ud fra fyldet
|
||||
check("SR gødning", ADCpBridge.sprayRoleForFillType("FERTILIZER"), "fertilizer")
|
||||
check("SR flydende", ADCpBridge.sprayRoleForFillType("LIQUIDFERTILIZER"), "fertilizer")
|
||||
check("SR gylle", ADCpBridge.sprayRoleForFillType("LIQUIDMANURE"), "fertilizer")
|
||||
check("SR møg", ADCpBridge.sprayRoleForFillType("MANURE"), "fertilizer")
|
||||
check("SR digestat", ADCpBridge.sprayRoleForFillType("DIGESTATE"), "fertilizer")
|
||||
check("SR kalk", ADCpBridge.sprayRoleForFillType("LIME"), "limer")
|
||||
check("SR ukrudt", ADCpBridge.sprayRoleForFillType("HERBICIDE"), "weeder")
|
||||
check("SR vand/ukendt", ADCpBridge.sprayRoleForFillType("WATER"), nil)
|
||||
-- SR2: tom Bredal-spreder (tager gødning OG kalk) kan begge dele; fyldt med kalk er den primært kalkspreder
|
||||
local bredal = {FERTILIZER = true, LIME = true}
|
||||
local p1, r1, f1 = ADCpBridge.sprayRolesFor(nil, bredal)
|
||||
check("SR2 tom spreder primært gødning", p1, "fertilizer")
|
||||
check("SR2 tom spreder kan kalke", r1 and r1.limer, true)
|
||||
check("SR2 tom = intet fyld", f1, nil)
|
||||
local p2, r2, f2 = ADCpBridge.sprayRolesFor("LIME", bredal)
|
||||
check("SR2 med kalk = kalkspreder", p2, "limer")
|
||||
check("SR2 med kalk kan stadig gøde (efter tømning)", r2 and r2.fertilizer, true)
|
||||
check("SR2 fyldets rolle", f2, "limer")
|
||||
check("SR2 ukrudtssprøjte", (ADCpBridge.sprayRolesFor(nil, {HERBICIDE = true, WATER = true})), "weeder")
|
||||
check("SR2 vandvogn er ingen sprøjte", (ADCpBridge.sprayRolesFor(nil, {WATER = true})), nil)
|
||||
-- SR3: kun en lille rest må hældes ud (100.000 l gødning blev hældt ud in-game 26/9)
|
||||
check("SR3 tom", ADCpBridge.isDumpable(0, 4000), true)
|
||||
check("SR3 lille rest 300 af 4000", ADCpBridge.isDumpable(300, 4000), true)
|
||||
check("SR3 halv tank 2000 af 4000", ADCpBridge.isDumpable(2000, 4000), false)
|
||||
check("SR3 100000 l aldrig", ADCpBridge.isDumpable(100000, 1000000), false)
|
||||
check("SR3 over 2000 l aldrig", ADCpBridge.isDumpable(2500, 100000), false)
|
||||
-- VN: vinmark — i gangen for alt undtagen høsteren; almindelige marker røres ikke
|
||||
check("VN sprøjte på druer = venstre", ADCpBridge.vineOffsetFor("GRAPEB", "fertilizer"), -1)
|
||||
check("VN harve på druer = venstre", ADCpBridge.vineOffsetFor("grape", "cultivator"), -1)
|
||||
check("VN høster på druer = på vinen", ADCpBridge.vineOffsetFor("GRAPEB", "harvester"), 0)
|
||||
check("VN hvede røres ikke", ADCpBridge.vineOffsetFor("WHEAT", "fertilizer"), nil)
|
||||
check("VN ukendt afgrøde", ADCpBridge.vineOffsetFor(nil, "weeder"), nil)
|
||||
check("VN sprøjte springer 1 række over (begge sider)", ADCpBridge.vineSkipFor("GRAPEB", "weeder"), 1)
|
||||
check("VN gødning springer 1 over", ADCpBridge.vineSkipFor("GRAPEB", "fertilizer"), 1)
|
||||
check("VN harve springer ingen over", ADCpBridge.vineSkipFor("GRAPEB", "cultivator"), 0)
|
||||
check("VN høster springer ingen over", ADCpBridge.vineSkipFor("GRAPEB", "harvester"), 0)
|
||||
check("VN hvede røres ikke", ADCpBridge.vineSkipFor("WHEAT", "weeder"), nil)
|
||||
|
||||
-- HF: høsterens afgrøder fra skærebordet (liste eller opslag); finsnitter tager ingen
|
||||
local list = ADCpBridge.harvestFruitSet({fruitTypes = {3, 5}})
|
||||
check("HF liste", list[3] and list[5], true)
|
||||
local lookup = ADCpBridge.harvestFruitSet({fruitTypes = {[3] = true}})
|
||||
check("HF opslag", lookup[3], true)
|
||||
local forage = ADCpBridge.harvestFruitSet({fruitTypes = {3}, fruitTypeConverters = {[3] = {}}})
|
||||
check("HF finsnitter tager intet", next(forage), nil)
|
||||
check("HF fruitTypeIndices", ADCpBridge.harvestFruitSet({fruitTypeIndices = {7}})[7], true)
|
||||
|
||||
-- PL: plantbar i perioden
|
||||
local fruitPlant = {growthDataSeasonal = {periods = {[3] = {plantingAllowed = true}, [4] = {plantingAllowed = false}}}}
|
||||
check("PL tilladt", ADCpBridge.isPlantableIn(fruitPlant, 3), true)
|
||||
check("PL ikke nu", ADCpBridge.isPlantableIn(fruitPlant, 4), false)
|
||||
check("PL ukendt data = tilladt", ADCpBridge.isPlantableIn({}, 4), true)
|
||||
|
||||
-- CR: kursus-genbrug — start på maskinens eget kursus uden at CP beregner et nyt (opts.reuse)
|
||||
do
|
||||
local generated, started = 0, 0
|
||||
AIJobType = {FIELDWORK_CP = 1}
|
||||
function getWorldTranslation() return 0, 0, 0 end
|
||||
local job = {cpJobParameters = {}}
|
||||
function job.applyCurrentState() end
|
||||
function job.setValues() end
|
||||
function job.validate() return true end
|
||||
function job.registerFieldBoundaryDetectionCallback() end
|
||||
function job.detectFieldBoundary() return true, false, "" end
|
||||
function job.onClickGenerateFieldWorkCourse(_, callback) generated = generated + 1; callback(true) end
|
||||
g_currentMission = {aiJobTypeManager = {createJob = function() return job end},
|
||||
aiSystem = {startJob = function() started = started + 1 end}}
|
||||
local course = {}
|
||||
local vehicle = {rootNode = 1, getOwnerFarmId = function() return 1 end, getFieldWorkCourse = function() return course end}
|
||||
local reason = "?"
|
||||
ADCpBridge.start(vehicle, 0, 0, function(ok, r) reason = ok and (r or "generated") or "fail" end, {reuse = true})
|
||||
check("CR1 genbrug: ingen beregning", generated, 0)
|
||||
check("CR2 genbrug: CP startet", started, 1)
|
||||
check("CR3 genbrug: reason", reason, "reused")
|
||||
-- ingen kursus på maskinen -> beregnes alligevel
|
||||
vehicle.getFieldWorkCourse = function() return nil end
|
||||
ADCpBridge.start(vehicle, 0, 0, function(ok, r) reason = ok and (r or "generated") or "fail" end, {reuse = true})
|
||||
check("CR4 intet kursus -> beregnes", generated, 1)
|
||||
check("CR5 reason ny", reason, "generated")
|
||||
-- uden reuse: beregnes som før
|
||||
vehicle.getFieldWorkCourse = function() return course end
|
||||
ADCpBridge.start(vehicle, 0, 0, function(ok, r) reason = ok and (r or "generated") or "fail" end, {})
|
||||
check("CR6 uden reuse -> beregnes", generated, 2)
|
||||
AIJobType, g_currentMission = nil, nil
|
||||
end
|
||||
|
||||
-- forager: 2 runder og start på forageren (så der ikke køres ind i træer o.l. i hjørnerne)
|
||||
local recorded = {}
|
||||
local function setting(name) return {setValue = function(_, v) recorded[name] = v end} end
|
||||
local headlandVehicle = {getCourseGeneratorSettings = function() return {numberOfHeadlands = setting("headlands"), startOnHeadland = setting("startOnHeadland")} end}
|
||||
check("forager: sat", ADCpBridge.setHeadlands(headlandVehicle, 2), true)
|
||||
check("forager: 2 runder", recorded.headlands, 2)
|
||||
check("forager: start på forageren", recorded.startOnHeadland, true)
|
||||
ADCpBridge.setHeadlands(headlandVehicle, 0)
|
||||
check("forager: ingen -> ikke start på forageren", recorded.startOnHeadland, false)
|
||||
check("forager: køretøj uden CP", ADCpBridge.setHeadlands({}, 2), false)
|
||||
check("forager: standard er 2", ADCpBridge.HEADLANDS, 2)
|
||||
-- genbrugt kursus uden forager beregnes på ny
|
||||
check("forager i kursus: 2 runder", ADCpBridge.courseHasHeadlands({getNumberOfHeadlands = function() return 2 end}), true)
|
||||
check("forager i kursus: ingen", ADCpBridge.courseHasHeadlands({getNumberOfHeadlands = function() return 0 end}), false)
|
||||
check("forager i kursus: ukendt -> antag ja", ADCpBridge.courseHasHeadlands({}), true)
|
||||
check("forager i kursus: intet kursus", ADCpBridge.courseHasHeadlands(nil), false)
|
||||
-- isWorking: AutoDrive kører også som AI i FS25 — det er ikke Courseplay (in-game 29/9: Milk Store holdt ved
|
||||
-- Cow Wait med AD aktiv og "ventede på sig selv" som 'Courseplay kører')
|
||||
local function fakeVehicle(cp, ai, ad)
|
||||
return {getIsCpFieldWorkActive = function() return cp end, getIsAIActive = function() return ai end,
|
||||
ad = {stateModule = {isActive = function() return ad end}}}
|
||||
end
|
||||
check("CP arbejder: Courseplay markarbejde", ADCpBridge.isWorking(fakeVehicle(true, true, false)), true)
|
||||
check("CP arbejder: AI uden AD (CP-kørsel)", ADCpBridge.isWorking(fakeVehicle(false, true, false)), true)
|
||||
check("CP arbejder: AutoDrive kører -> ikke CP", ADCpBridge.isWorking(fakeVehicle(false, true, true)), false)
|
||||
check("CP arbejder: intet", ADCpBridge.isWorking(fakeVehicle(false, false, false)), false)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,33 +0,0 @@
|
||||
-- Kør: luajit tests/test_adCpResume.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adCpResume.lua")
|
||||
local R = ADCpResume
|
||||
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
|
||||
-- hvem skrives ved gem: CP kører, AD styrer ikke (AD genstarter selv sine)
|
||||
local ids = R.activeIds({{id = "a", cpActive = true}, {id = "b", cpActive = true, adActive = true}, {id = "c"}, {id = "d", cpActive = true}})
|
||||
check("gem: CP-aktive uden AD", table.concat(ids, " "), "a d")
|
||||
check("gem: tom liste", #R.activeIds({}), 0)
|
||||
-- records frem og tilbage
|
||||
local set = R.fromRecords({"a", "", "d", "a"})
|
||||
check("indlæs: a", set.a, true)
|
||||
check("indlæs: tomt id springes over", set[""], nil)
|
||||
-- hvem startes ved indlæsning
|
||||
local pending = {a = true, b = true, c = true, d = true}
|
||||
local vehicles = {{id = "a"}, {id = "b", aiActive = true}, {id = "c", controlled = true}, {id = "d"}, {id = "e"}}
|
||||
local start = R.toStart(pending, vehicles)
|
||||
check("start: kun gemte, ikke allerede kørende/spiller i", table.concat(start, " "), "a d")
|
||||
check("start: ingen gemt liste -> nil (gammel regel bruges)", R.toStart(nil, vehicles), nil)
|
||||
|
||||
-- kun når CP's HUD-job er markarbejde: moddets markopgaver starter CP med eget job, så HUD'en kan stå på fx
|
||||
-- bunkersilo — HUD-start gav "CP: No silo found" efter genindlæsning (in-game 29/9)
|
||||
local mixed = R.toStart({a = true, b = true, c = true}, {
|
||||
{id = "a", fieldWork = true}, {id = "b", fieldWork = false}, {id = "c"}})
|
||||
check("start: kun markarbejds-job", table.concat(mixed, ","), "a,c")
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,145 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldFleet.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFieldModel.lua")
|
||||
dofile("FS25_ADSmartPickup/adFieldFleet.lua")
|
||||
local F = ADFieldFleet
|
||||
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
|
||||
|
||||
-- FL1: rolle ud fra redskabets spec (læssevogn vinder over andet på samme køretøj)
|
||||
check("FL1 slåmaskine", F.roleFromSpecs({spec_mower = true}), "mower")
|
||||
check("FL1 vender", F.roleFromSpecs({spec_tedder = true}), "tedder")
|
||||
check("FL1 river", F.roleFromSpecs({spec_windrower = true}), "rake")
|
||||
check("FL1 læssevogn", F.roleFromSpecs({spec_forageWagon = true, spec_mower = true}), "collector")
|
||||
check("FL1 intet", F.roleFromSpecs({spec_trailer = true}), nil)
|
||||
|
||||
local function rig(id, role, x, extra)
|
||||
local r = {id = id, role = role, enabled = true, busy = false, controlled = false, width = 40.8, x = x, z = 0, label = id}
|
||||
for k, v in pairs(extra or {}) do r[k] = v end
|
||||
return r
|
||||
end
|
||||
|
||||
-- FL2: nærmeste ledige med flueben og rigtig rolle
|
||||
local picked = F.pick({rig("far", "mower", 500), rig("near", "mower", 50), rig("wagon", "collector", 10)}, "mower", 0, 0, nil)
|
||||
check("FL2 nærmeste slåmaskine", picked.id, "near")
|
||||
|
||||
-- FL3: uden flueben / optaget / spiller i sædet springes over
|
||||
local _, why = F.pick({rig("a", "mower", 1, {enabled = false})}, "mower", 0, 0)
|
||||
check("FL3 intet med flueben", why, "noRig")
|
||||
local _, whyBusy = F.pick({rig("a", "mower", 1, {busy = true}), rig("b", "mower", 2, {controlled = true})}, "mower", 0, 0)
|
||||
check("FL3 alle optaget", whyBusy, "allBusy")
|
||||
check("FL3 ingen med rollen", select(2, F.pick({rig("a", "tedder", 1)}, "mower", 0, 0)), "noRig")
|
||||
|
||||
-- FL4: læssevogn smallere end slåbredden springes over, bredere vælges
|
||||
local wide = F.pick({rig("smal", "collector", 1, {width = 20}), rig("bred", "collector", 900, {width = 40.8})}, "collector", 0, 0, 40.8)
|
||||
check("FL4 bred valgt selv om længere væk", wide.id, "bred")
|
||||
-- FL5: kun for smalle -> grund tooNarrow med tal
|
||||
local none, whyNarrow, args = F.pick({rig("smal", "collector", 1, {width = 20})}, "collector", 0, 0, 40.8)
|
||||
check("FL5 ingen", none, nil); check("FL5 grund", whyNarrow, "tooNarrow"); check("FL5 bredde", args[1], 20)
|
||||
-- FL6: over 50 m -> tooWide
|
||||
check("FL6 for bred", select(2, F.pick({rig("x", "mower", 1, {width = 60})}, "mower", 0, 0)), "tooWide")
|
||||
|
||||
-- FL7: toggle giver ny tabel
|
||||
local enabled = {a = true}
|
||||
local toggled = F.toggle(enabled, "b")
|
||||
check("FL7 b til", toggled.b, true); check("FL7 original urørt", enabled.b, nil)
|
||||
check("FL7 a fra", F.toggle(toggled, "a").a, nil)
|
||||
|
||||
-- FL8: nye roller i prioriteret rækkefølge
|
||||
check("FL8 høster", F.roleFromSpecs({spec_combine = true, spec_cutter = true}), "harvester")
|
||||
check("FL8 såmaskine før kultivator (combi)", F.roleFromSpecs({spec_sowingMachine = true, spec_cultivator = true, sprayRole = "fertilizer"}), "seeder")
|
||||
check("FL8 kultivator", F.roleFromSpecs({spec_cultivator = true}), "cultivator")
|
||||
check("FL8 plov", F.roleFromSpecs({spec_plow = true}), "plow")
|
||||
check("FL8 tromle", F.roleFromSpecs({spec_roller = true}), "roller")
|
||||
check("FL8 strigle", F.roleFromSpecs({spec_weeder = true}), "weeder")
|
||||
check("FL8 stubhakker", F.roleFromSpecs({spec_mulcher = true}), "mulcher")
|
||||
check("FL8 gødningssprøjte", F.roleFromSpecs({sprayRole = "fertilizer"}), "fertilizer")
|
||||
check("FL8 kalkspreder", F.roleFromSpecs({sprayRole = "limer"}), "limer")
|
||||
check("FL8 ukrudtssprøjte", F.roleFromSpecs({sprayRole = "weeder"}), "weeder")
|
||||
check("FL8 aflæsningsvogn", F.roleFromSpecs({canUnload = true}), "unloader")
|
||||
check("FL8 traktor med plov er ikke aflæsningsvogn", F.roleFromSpecs({spec_plow = true, canUnload = true}), "plow")
|
||||
|
||||
-- FL9: accept-filter (fx høster der kan tage afgrøden, strigle kun til småt ukrudt)
|
||||
local combineWheat = rig("jd", "harvester", 10, {fruits = {[3] = true}})
|
||||
local combineBeet = rig("ropa", "harvester", 1, {fruits = {[9] = true}})
|
||||
local forWheat = F.pick({combineBeet, combineWheat}, "harvester", 0, 0, nil, function(c) return c.fruits[3] == true end)
|
||||
check("FL9 høster der kan tage hvede", forWheat.id, "jd")
|
||||
local _, whyFruit = F.pick({combineBeet}, "harvester", 0, 0, nil, function(c) return c.fruits[3] == true end)
|
||||
check("FL9 ingen kan tage afgrøden", whyFruit, "cannotDo")
|
||||
|
||||
-- FL10: flere sæt til multi-tool — alle eller ingen, nærmeste først
|
||||
local m1, m2, m3 = rig("m1", "mower", 30), rig("m2", "mower", 10), rig("m3", "mower", 20, {busy = true})
|
||||
local two = F.pickMany({m1, m2, m3}, "mower", 2, 0, 0)
|
||||
check("FL10 to valgt", #two, 2)
|
||||
check("FL10 nærmeste først", two[1].id, "m2")
|
||||
local none, whyMany, args = F.pickMany({m1, m2, m3}, "mower", 3, 0, 0)
|
||||
check("FL10 for få -> ingen", none, nil)
|
||||
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")
|
||||
|
||||
-- FL-spreder: tom spreder med rollerne {gødning, kalk} vælges til kalkning; spreder med forkert vare
|
||||
-- på (kalk, skal gøde) kun hvis ingen anden kan — og den nærmeste med rigtig/ingen vare først
|
||||
local spreaders = {
|
||||
{id = "kalk", role = "limer", roles = {fertilizer = true, limer = true}, filledRole = "limer", enabled = true, x = 1, z = 0, width = 12},
|
||||
{id = "tom", role = "fertilizer", roles = {fertilizer = true, limer = true}, enabled = true, x = 50, z = 0, width = 12},
|
||||
}
|
||||
check("FLS tom spreder tager kalkning", F.pick({spreaders[2]}, "limer", 0, 0) and F.pick({spreaders[2]}, "limer", 0, 0).id, "tom")
|
||||
check("FLS gødskning: tom før nærmere med kalk på", F.pick(spreaders, "fertilizer", 0, 0).id, "tom")
|
||||
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")
|
||||
-- FLS2: fuld af en anden vare (ikke dumpable) bruges slet ikke til opgaven
|
||||
local fullOther = {id = "fuld", role = "fertilizer", roles = {fertilizer = true, weeder = true}, filledRole = "fertilizer", dumpable = false, enabled = true, x = 0, z = 0, width = 30}
|
||||
check("FLS2 fuld gødningssprøjte bruges ikke til ukrudt", F.pick({fullOther}, "weeder", 0, 0), nil)
|
||||
check("FLS2 men gerne til gødning", F.pick({fullOther}, "fertilizer", 0, 0).id, "fuld")
|
||||
|
||||
-- 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)
|
||||
|
||||
-- FLV: menuen skjuler maskiner der er faste på en anden mark end den valgte
|
||||
local vrigs = {{id = "vogn"}, {id = "harve"}, {id = "plov"}}
|
||||
local vis, hid, flds = F.visibleForField(vrigs, 18, {[22] = {"vogn", "harve"}})
|
||||
check("FLV F18 ser kun de frie", #vis .. "/" .. hid .. "/" .. tostring(flds[1]), "1/2/22")
|
||||
check("FLV F22 ser alle", #(F.visibleForField(vrigs, 22, {[22] = {"vogn", "harve"}})), 3)
|
||||
check("FLV ingen valgt = alle", #(F.visibleForField(vrigs, nil, {[22] = {"vogn"}})), 3)
|
||||
|
||||
-- FL-plov: FS25-plove har også spec_cultivator — de er plove, men må også tage kultivering
|
||||
check("FLP plov med kultivator-spec er plov", F.roleFromSpecs({spec_plow = true, spec_cultivator = true}), "plow")
|
||||
check("FLP plov kan kultivere", F.rolesFor("plow").cultivator, true)
|
||||
check("FLP plov kan pløje", F.rolesFor("plow").plow, true)
|
||||
check("FLP kultivator kan ikke pløje", F.rolesFor("cultivator"), nil)
|
||||
local plough = {id = "p", role = "plow", roles = F.rolesFor("plow"), enabled = true, x = 0, z = 0, width = 3}
|
||||
check("FLP plov vælges til kultivering", (F.pick({plough}, "cultivator", 0, 0, nil)).id, "p")
|
||||
check("FLP rolesFor giver en kopi", F.rolesFor("plow") ~= F.rolesFor("plow"), true)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,255 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldGuide.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adGuide.lua")
|
||||
dofile("FS25_ADSmartPickup/adFieldModel.lua")
|
||||
dofile("FS25_ADSmartPickup/adFieldFleet.lua")
|
||||
dofile("FS25_ADSmartPickup/adFlowView.lua")
|
||||
dofile("FS25_ADSmartPickup/adFieldGuide.lua")
|
||||
local FG, G = ADFieldGuide, ADGuide
|
||||
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 SOWABLE = {{name = "WHEAT", title = "Hvede"}, {name = "BARLEY", title = "Byg"}, {name = "GRASS", title = "Græs"}, {name = "MAIZE", title = "Majs"}}
|
||||
local RIGS = {
|
||||
{id = "v1", text = "Fendt 1 — slåmaskine", role = "mower"},
|
||||
{id = "v2", text = "Claas 2 — mejetærsker", role = "harvester"},
|
||||
{id = "v3", text = "Sprøjte 3 — sprøjte", role = "weeder"},
|
||||
{id = "v4", text = "Vogn 4 — læssevogn", role = "collector"},
|
||||
}
|
||||
local function ctx(info, extra)
|
||||
local context = {
|
||||
info = info, sowable = SOWABLE, mowable = {GRASS = true}, straw = {WHEAT = true, BARLEY = true},
|
||||
rigs = RIGS, rigIdsByField = {[90] = {"v3"}}, fieldSilos = {}, storeHidden = 0,
|
||||
stores = info.kind ~= "bare" and {{id = "s1", text = "Silo A, 300 m"}, {id = "s2", text = "Silo B, 1,2 km"}} or nil,
|
||||
fields = {{id = 1, text = "F1 Græs"}, {id = 2, text = "F2 Hvede"}},
|
||||
}
|
||||
for key, value in pairs(extra or {}) do context[key] = value end
|
||||
return context
|
||||
end
|
||||
local GRASS = {id = 1, kind = "mowable", fruitName = "GRASS", cropTitle = "Græs", markerId = 5}
|
||||
local WHEAT = {id = 2, kind = "harvest", fruitName = "WHEAT", cropTitle = "Hvede", markerId = 6}
|
||||
local MAIZE = {id = 3, kind = "harvest", fruitName = "MAIZE", cropTitle = "Majs", markerId = 7}
|
||||
local BARE = {id = 4, kind = "bare", markerId = 8}
|
||||
|
||||
local function steps(guide) return table.concat(G.visibleSteps(guide), ",") end
|
||||
local function field(id, plan)
|
||||
local f = ADFieldModel.newField(id)
|
||||
return ADFieldModel.with(f, {plan = plan or {}})
|
||||
end
|
||||
local function findRow(rows, id, item)
|
||||
for _, row in ipairs(rows) do
|
||||
if row.id == id and (item == nil or row.item == item) then return row end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
local function countType(rows, rowType)
|
||||
local n = 0
|
||||
for _, row in ipairs(rows) do if row.type == rowType then n = n + 1 end end
|
||||
return n
|
||||
end
|
||||
|
||||
-- ---- synlige trin pr. marktype ----
|
||||
check("FG1 græs", steps(FG.open(field(1), ctx(GRASS))), "crop,cropChoice,care,machines,store,summary")
|
||||
check("FG1 hvede m. halm", steps(FG.open(field(2), ctx(WHEAT))), "crop,cropChoice,prep,care,machines,store,summary")
|
||||
check("FG1 majs uden halm", steps(FG.open(field(3), ctx(MAIZE))), "crop,prep,care,machines,store,summary")
|
||||
check("FG1 bar mark", steps(FG.open(field(4), ctx(BARE))), "crop,prep,machines,summary")
|
||||
check("FG1 halm ukendt -> vis", steps(FG.open(field(3), ctx(MAIZE, {straw = {}}))), "crop,cropChoice,prep,care,machines,store,summary")
|
||||
check("FG1 hvede med mål græs", steps(FG.open(field(2, {targetFruit = "GRASS"}), ctx(WHEAT))), "crop,cropChoice,prep,care,machines,store,summary")
|
||||
check("FG1 bar med mål hvede", steps(FG.open(field(4, {targetFruit = "WHEAT"}), ctx(BARE))), "crop,cropChoice,prep,care,machines,summary")
|
||||
check("FG1 græs med mål græs: ingen klargøring", steps(FG.open(field(1, {targetFruit = "GRASS"}), ctx(GRASS))), "crop,cropChoice,care,machines,store,summary")
|
||||
check("FG1 græs med mål hvede: klargøring", steps(FG.open(field(1, {targetFruit = "WHEAT"}), ctx(GRASS))), "crop,cropChoice,prep,care,machines,store,summary")
|
||||
local picker = FG.openPicker(ctx(GRASS))
|
||||
check("FG1 vælger: kun mark", steps(picker), "field")
|
||||
check("FG1 vælger: trin", picker.step, "field")
|
||||
check("FG1 vælger: position uden total", (G.position(picker)), 1)
|
||||
|
||||
-- ---- standarder og udfyldning ----
|
||||
local g = FG.open(field(2), ctx(WHEAT))
|
||||
check("FG2 starter på afgrøde", g.step, "crop")
|
||||
check("FG2 klargøring auto", g.draft.prepAuto, true)
|
||||
check("FG2 ingen faste", g.draft.useDedicated, false)
|
||||
check("FG2 lager auto", g.draft.storeMode, "auto")
|
||||
check("FG2 kladde er kopi", g.draft.plan ~= nil and g.draft.fieldId, 2)
|
||||
local plowed = FG.open(field(2, {soil = "plow"}), ctx(WHEAT))
|
||||
check("FG2 pløj -> vælg selv", plowed.draft.prepAuto, false)
|
||||
local withRigs = FG.open(field(2, {rigIds = {"v2"}}), ctx(WHEAT))
|
||||
check("FG2 faste maskiner -> ja", withRigs.draft.useDedicated, true)
|
||||
local silo = FG.open(field(1, {storeBuildingIds = {"m1"}}), ctx(GRASS, {fieldSilos = {{id = "m1", text = "Marksilo"}}}))
|
||||
check("FG2 marksilo valgt", silo.draft.storeMode, "fieldSilo")
|
||||
local manual = FG.open(field(1, {storeBuildingIds = {"s2"}}), ctx(GRASS))
|
||||
check("FG2 valgt silo -> vælg selv", manual.draft.storeMode, "manual")
|
||||
check("FG2 valgt silo i listen", manual.draft.storeIds[1], "s2")
|
||||
|
||||
-- ---- choose er immutabel ----
|
||||
local chosen = FG.choose(g, "targetFruit", "BARLEY")
|
||||
check("FG3 ny kladde", chosen.draft.plan.targetFruit, "BARLEY")
|
||||
check("FG3 gammel urørt", g.draft.plan.targetFruit, "")
|
||||
local toggled = FG.choose(withRigs, "rigIds", {item = "v1", on = true})
|
||||
check("FG3 check tilføjer", table.concat(toggled.draft.plan.rigIds, ","), "v2,v1")
|
||||
check("FG3 gammel liste urørt", table.concat(withRigs.draft.plan.rigIds, ","), "v2")
|
||||
check("FG3 check fjerner", table.concat(FG.choose(toggled, "rigIds", {item = "v2", on = false}).draft.plan.rigIds, ","), "v1")
|
||||
check("FG3 produkt hø", FG.choose(FG.open(field(1), ctx(GRASS)), "product", true).draft.plan.product, "hay")
|
||||
check("FG3 produkt ensilage", FG.choose(FG.open(field(1), ctx(GRASS)), "product", false).draft.plan.product, "silage")
|
||||
|
||||
-- ---- rækker ----
|
||||
local cropRows = FG.rows(g)
|
||||
local target = findRow(cropRows, "targetFruit")
|
||||
check("FG4 afgrøde: valg", target.type, "choice")
|
||||
check("FG4 afgrøde: behold først", target.options[1].value, "")
|
||||
check("FG4 afgrøde: såbare", #target.options, 5)
|
||||
check("FG4 så igen for høstmark", findRow(cropRows, "sow") ~= nil, true)
|
||||
check("FG4 så igen skjult med mål", findRow(FG.rows(chosen), "sow"), nil)
|
||||
check("FG4 så igen skjult på græs", findRow(FG.rows(FG.open(field(1), ctx(GRASS))), "sow"), nil)
|
||||
|
||||
local choiceGrass = FG.rows(G.jump(FG.open(field(1), ctx(GRASS)), "cropChoice", "close"))
|
||||
check("FG5 græs: ensilage/hø", findRow(choiceGrass, "product") ~= nil, true)
|
||||
check("FG5 græs: river", findRow(choiceGrass, "rake") ~= nil, true)
|
||||
check("FG5 græs: ingen halm", findRow(choiceGrass, "straw"), nil)
|
||||
local choiceWheat = FG.rows(G.jump(g, "cropChoice", "close"))
|
||||
check("FG5 hvede: halm", findRow(choiceWheat, "straw").type, "choice")
|
||||
check("FG5 hvede: ingen ensilage", findRow(choiceWheat, "product"), nil)
|
||||
|
||||
local prepRows = FG.rows(G.jump(g, "prep", "close"))
|
||||
check("FG6 klargøring auto: kun valget + tekst", findRow(prepRows, "soil"), nil)
|
||||
check("FG6 klargøring valg", findRow(prepRows, "prepAuto").type, "choice")
|
||||
local prepManual = FG.rows(G.jump(FG.choose(g, "prepAuto", false), "prep", "close"))
|
||||
check("FG6 vælg selv: jord", findRow(prepManual, "soil") ~= nil, true)
|
||||
check("FG6 vælg selv: kalk", findRow(prepManual, "lime") ~= nil, true)
|
||||
check("FG6 vælg selv: mulch", findRow(prepManual, "mulch") ~= nil, true)
|
||||
check("FG6 vælg selv: ingen tromle uden såning", findRow(prepManual, "roll"), nil)
|
||||
|
||||
-- pleje: Automatisk/Manuelt som klargøring (brugerønske 29/9); knapperne vises under Manuelt
|
||||
local careAutoRows = FG.rows(G.jump(g, "care", "close"))
|
||||
check("FG7 pleje: valg auto/manuel", findRow(careAutoRows, "careAuto").type, "choice")
|
||||
check("FG7 pleje auto: ingen knapper", findRow(careAutoRows, "fertilize"), nil)
|
||||
check("FG7 standard: pleje auto", g.draft.careAuto, true)
|
||||
check("FG7 uden gødning -> manuel", FG.open(field(2, {fertilize = false}), ctx(WHEAT)).draft.careAuto, false)
|
||||
local autoCare = FG.result(FG.choose(FG.open(field(2, {fertilize = false, weed = false}), ctx(WHEAT)), "careAuto", true), true).plan
|
||||
check("FG7 auto slår gødning til", autoCare.fertilize, true)
|
||||
check("FG7 auto slår ukrudt til", autoCare.weed, true)
|
||||
local careGrass = FG.rows(G.jump(FG.choose(FG.open(field(1), ctx(GRASS)), "careAuto", false), "care", "close"))
|
||||
check("FG7 græs: ingen ukrudt", findRow(careGrass, "weed"), nil)
|
||||
check("FG7 græs: kalk i pleje", findRow(careGrass, "lime") ~= nil, true)
|
||||
local careWheat = FG.rows(G.jump(FG.choose(g, "careAuto", false), "care", "close"))
|
||||
check("FG7 hvede: ukrudt", findRow(careWheat, "weed") ~= nil, true)
|
||||
check("FG7 hvede: kalk ikke i pleje", findRow(careWheat, "lime"), nil)
|
||||
|
||||
local machinesOff = FG.rows(G.jump(g, "machines", "close"))
|
||||
check("FG8 antal ad gangen", findRow(machinesOff, "rigs").type, "choice")
|
||||
check("FG8 aflæsningsvogne ved høst", findRow(machinesOff, "unloaders") ~= nil, true)
|
||||
check("FG8 ingen liste uden faste", countType(machinesOff, "check"), 0)
|
||||
check("FG8 ingen aflæsningsvogne på græs", findRow(FG.rows(G.jump(FG.open(field(1), ctx(GRASS)), "machines", "close")), "unloaders"), nil)
|
||||
local machinesOn = FG.rows(G.jump(FG.choose(g, "useDedicated", true), "machines", "close"))
|
||||
check("FG8 liste: v3 fast på F90 skjult", findRow(machinesOn, "rigIds", "v3"), nil)
|
||||
check("FG8 liste: tre synlige", countType(machinesOn, "check"), 3)
|
||||
local hiddenText = nil
|
||||
for _, row in ipairs(machinesOn) do if row.type == "text" and row.label.key == "spu_field_hiddenDedicated" then hiddenText = row end end
|
||||
check("FG8 tekst om skjulte", hiddenText ~= nil and hiddenText.label.args[1], 1)
|
||||
check("FG8 tekst marker", hiddenText ~= nil and hiddenText.label.args[2], "F90")
|
||||
local ownFirst = FG.rows(G.jump(withRigs, "machines", "close"))
|
||||
local firstCheck = nil
|
||||
for _, row in ipairs(ownFirst) do if row.type == "check" and firstCheck == nil then firstCheck = row end end
|
||||
check("FG8 egne først", firstCheck.item, "v2")
|
||||
check("FG8 egne sat", firstCheck.value, true)
|
||||
|
||||
local storeAuto = FG.rows(G.jump(FG.open(field(1), ctx(GRASS)), "store", "close"))
|
||||
check("FG9 ingen marksilo-spørgsmål uden marksilo", findRow(storeAuto, "useFieldSilo"), nil)
|
||||
check("FG9 automatisk/vælg selv", findRow(storeAuto, "storeAuto").type, "choice")
|
||||
check("FG9 auto: ingen siloliste", countType(storeAuto, "check"), 0)
|
||||
local storeManual = FG.rows(G.jump(FG.choose(FG.open(field(1), ctx(GRASS)), "storeAuto", false), "store", "close"))
|
||||
check("FG9 vælg selv: siloer", countType(storeManual, "check"), 2)
|
||||
local siloCtx = ctx(GRASS, {fieldSilos = {{id = "m1", text = "Marksilo F1"}}})
|
||||
local storeSilo = FG.rows(G.jump(FG.open(field(1), siloCtx), "store", "close"))
|
||||
check("FG9 marksilo-spørgsmål", findRow(storeSilo, "useFieldSilo").type, "toggle")
|
||||
local useSilo = FG.choose(FG.open(field(1), siloCtx), "useFieldSilo", true)
|
||||
check("FG9 marksilo ja -> skjul auto", findRow(FG.rows(G.jump(useSilo, "store", "close")), "storeAuto"), nil)
|
||||
|
||||
local pickRows = FG.rows(picker)
|
||||
check("FG10 vælger: links", countType(pickRows, "link"), 2)
|
||||
check("FG10 vælger: id", findRow(pickRows, "field").value, 1)
|
||||
local picked = FG.pickField(picker, field(2), ctx(WHEAT))
|
||||
check("FG10 valgt mark", picked.draft.fieldId, 2)
|
||||
check("FG10 videre til afgrøde", picked.step, "crop")
|
||||
check("FG10 mark-trin stadig talt", (G.position(picked)), 2)
|
||||
|
||||
-- ---- resultat ----
|
||||
local r = FG.result(g, true)
|
||||
check("FG11 auto ved start", r.plan.mode, "auto")
|
||||
check("FG11 manuel uden start", FG.result(g, false).plan.mode, "manual")
|
||||
local autoField = FG.open(field(2, {mode = "auto"}), ctx(WHEAT))
|
||||
check("FG11 Ret bevarer auto", FG.result(autoField, nil).plan.mode, "auto")
|
||||
check("FG11 Ret bevarer manuel", FG.result(g, nil).plan.mode, "manual")
|
||||
check("FG11 mål tvinger auto", FG.result(chosen, false).plan.mode, "auto")
|
||||
check("FG11 mål ændret", FG.result(chosen, true).targetChanged, true)
|
||||
check("FG11 mål uændret", r.targetChanged, false)
|
||||
check("FG11 ingen rigIds i plan", r.plan.rigIds, nil)
|
||||
local soilPlan = FG.result(FG.open(field(2, {soil = "plow", mulch = true, lime = false, roll = true}), ctx(WHEAT)), true).plan
|
||||
check("FG12 vælg selv bevarer jord", soilPlan.soil, "plow")
|
||||
local autoPrep = FG.result(FG.choose(FG.open(field(2, {soil = "plow", mulch = true, lime = false, roll = true}), ctx(WHEAT)), "prepAuto", true), true).plan
|
||||
check("FG12 auto: jord", autoPrep.soil, "auto")
|
||||
check("FG12 auto: mulch", autoPrep.mulch, false)
|
||||
check("FG12 auto: kalk", autoPrep.lime, true)
|
||||
check("FG12 auto: tromle", autoPrep.roll, false)
|
||||
local grassNoPrep = FG.result(FG.choose(FG.open(field(1, {lime = false}), ctx(GRASS)), "prepAuto", true), true).plan
|
||||
check("FG12 græs: auto rører ikke kalk", grassNoPrep.lime, false)
|
||||
check("FG13 lager auto", #FG.result(FG.open(field(1, {storeBuildingIds = {"s1"}}), ctx(GRASS)) and FG.choose(FG.open(field(1, {storeBuildingIds = {"s1"}}), ctx(GRASS)), "storeAuto", true), true).plan.storeBuildingIds, 0)
|
||||
check("FG13 lager vælg selv", table.concat(FG.result(FG.choose(FG.choose(FG.open(field(1), ctx(GRASS)), "storeAuto", false), "storeBuildingIds", {item = "s2", on = true}), true).plan.storeBuildingIds, ","), "s2")
|
||||
check("FG13 lager marksilo", table.concat(FG.result(useSilo, true).plan.storeBuildingIds, ","), "m1")
|
||||
check("FG13 bar mark bevarer lager", table.concat(FG.result(FG.open(field(4, {storeBuildingIds = {"s9"}}), ctx(BARE)), true).plan.storeBuildingIds, ","), "s9")
|
||||
check("FG14 faste fra -> tom", #FG.result(FG.choose(withRigs, "useDedicated", false), true).rigIds, 0)
|
||||
check("FG14 faste til", table.concat(FG.result(toggled, true).rigIds, ","), "v2,v1")
|
||||
|
||||
-- ---- faste maskiner på tværs af marker ----
|
||||
local byField = FG.applyRigs({[90] = {"v3", "v5"}, [2] = {"v2"}}, 2, {"v2", "v3"})
|
||||
check("FG15 fjernet fra anden mark", table.concat(byField[90], ","), "v5")
|
||||
check("FG15 sat på marken", table.concat(byField[2], ","), "v2,v3")
|
||||
check("FG15 ny mark", table.concat(FG.applyRigs({}, 7, {"v1"})[7], ","), "v1")
|
||||
|
||||
-- ---- resumé ----
|
||||
local function lineFor(lines, step)
|
||||
for _, line in ipairs(lines) do if line.step == step then return line end end
|
||||
return nil
|
||||
end
|
||||
local function partKeys(line)
|
||||
local keys = {}
|
||||
for _, part in ipairs(line.parts) do table.insert(keys, part.key or ("=" .. tostring(part.text))) end
|
||||
return table.concat(keys, ",")
|
||||
end
|
||||
local sum = FG.summary(g)
|
||||
check("FG16 linjer for synlige trin", #sum, 6)
|
||||
check("FG16 afgrøde behold", partKeys(lineFor(sum, "crop")), "spu_guide_crop_keep")
|
||||
check("FG16 halm", partKeys(lineFor(sum, "cropChoice")), "spu_guide_sum_straw_chop")
|
||||
check("FG16 klargøring auto", partKeys(lineFor(sum, "prep")), "spu_guide_sum_auto")
|
||||
check("FG16 pleje auto", partKeys(lineFor(sum, "care")), "spu_guide_sum_auto")
|
||||
check("FG16 pleje manuel", partKeys(lineFor(FG.summary(FG.choose(g, "careAuto", false)), "care")), "spu_guide_sum_fert,spu_guide_sum_weed")
|
||||
check("FG16 maskiner", partKeys(lineFor(sum, "machines")), "spu_guide_sum_freeRigs,spu_guide_sum_rigs,spu_guide_sum_unloaders")
|
||||
check("FG16 lager auto", partKeys(lineFor(sum, "store")), "spu_guide_sum_auto")
|
||||
check("FG16 ingen mark/summary-linje", lineFor(sum, "summary"), nil)
|
||||
local sumTarget = FG.summary(chosen)
|
||||
check("FG17 mål", partKeys(lineFor(sumTarget, "crop")), "spu_guide_sum_target")
|
||||
check("FG17 mål titel", lineFor(sumTarget, "crop").parts[1].args[1], "Byg")
|
||||
local sumRigs = FG.summary(withRigs)
|
||||
check("FG17 faste", lineFor(sumRigs, "machines").parts[1].key, "spu_guide_sum_dedicated")
|
||||
check("FG17 faste antal", lineFor(sumRigs, "machines").parts[1].args[1], 1)
|
||||
local sumManual = FG.summary(FG.open(field(2, {soil = "plow", lime = true, mulch = true}), ctx(WHEAT)))
|
||||
check("FG17 klargøring vælg selv", partKeys(lineFor(sumManual, "prep")), "spu_field_soil_plow,spu_guide_sum_lime,spu_guide_sum_mulch")
|
||||
check("FG17 lager valgt silo", partKeys(lineFor(FG.summary(manual), "store")), "=Silo B, 1,2 km")
|
||||
check("FG17 lager marksilo", partKeys(lineFor(FG.summary(useSilo), "store")), "spu_guide_sum_fieldSilo")
|
||||
local sumHay = FG.summary(FG.choose(FG.choose(FG.open(field(1), ctx(GRASS)), "product", true), "rake", true))
|
||||
check("FG17 hø + river", partKeys(lineFor(sumHay, "cropChoice")), "spu_field_plan_hay,spu_guide_sum_rake")
|
||||
local sumNoCare = FG.summary(FG.choose(FG.choose(FG.choose(g, "careAuto", false), "fertilize", false), "weed", false))
|
||||
check("FG17 ingen pleje", partKeys(lineFor(sumNoCare, "care")), "spu_guide_sum_noCare")
|
||||
local sumSow = FG.summary(FG.choose(g, "sow", true))
|
||||
check("FG17 så igen", partKeys(lineFor(sumSow, "crop")), "spu_guide_crop_keep,spu_guide_sum_sowAgain")
|
||||
|
||||
-- ---- oversigts-trinnet: links pr. linje ----
|
||||
local summaryRows = FG.rows(G.new(g.def, g.draft, g.context, {step = "summary"}))
|
||||
check("FG18 oversigt: link pr. linje", countType(summaryRows, "link"), 6)
|
||||
check("FG18 oversigt: link-id", findRow(summaryRows, "jump").value, "crop")
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,222 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldJobs.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFieldModel.lua")
|
||||
dofile("FS25_ADSmartPickup/adFieldJobs.lua")
|
||||
local J = ADFieldJobs
|
||||
local M = ADFieldModel
|
||||
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 info = {id = 109, markerId = 5, mowable = true, readyRatio = 1}
|
||||
|
||||
-- J1: klar auto-mark med markør -> start; uden markør -> vent
|
||||
local auto = M.onReadiness(M.newField(109), 1); auto.plan.mode = "auto"
|
||||
check("J1 start", J.decide(auto, info, {}, 0).action, "start")
|
||||
check("J1 uden markør", J.decide(auto, {id = 109, mowable = true}, {}, 0).action, "wait")
|
||||
|
||||
-- J2: arbejdende trin uden sæt -> send rolle; alle sæt færdige -> næste trin
|
||||
local mowing = M.start(auto)
|
||||
local wanted = J.decide(mowing, info, {}, 0)
|
||||
check("J2 send slåmaskine", wanted.action, "dispatch"); check("J2 rolle", wanted.role, "mower")
|
||||
check("J2 sæt i gang -> ingenting", J.decide(mowing, info, {{phase = "working"}}, 0).action, "none")
|
||||
local mowedInfo = {id = 109, markerId = 5, mowable = true, readyRatio = 0}
|
||||
check("J2 færdig -> videre", J.decide(mowing, mowedInfo, {{phase = "finished"}}, 0).action, "advance")
|
||||
-- J3: sæt frigivet (spilleren tog det) tæller ikke som færdigt
|
||||
check("J3 frigivet -> send nyt", J.decide(mowing, info, {{phase = "released"}}, 0).action, "dispatch")
|
||||
-- J4: slåning færdig men marken stadig klar -> ikke videre (afbrudt)
|
||||
check("J4 afbrudt slåning", J.decide(mowing, {id = 109, markerId = 5, mowable = true, readyRatio = 0.8}, {{phase = "finished"}}, 0).action, "dispatch")
|
||||
-- J5: opsamling efter genindlæsning uden sæt -> send læssevogn
|
||||
local collecting = M.advance(mowing)
|
||||
check("J5 genoptag opsamling", J.decide(collecting, info, {}, 0).role, "collector")
|
||||
check("J5 to vogne maks", J.decide(collecting, info, {{phase = "working"}, {phase = "driving"}}, 0).action, "none")
|
||||
|
||||
-- J6: Courseplay efterlader et hjørne -> efter 2 færdige slå-ture går marken videre alligevel
|
||||
local standing = {id = 109, markerId = 5, mowable = true, readyRatio = 0.8}
|
||||
check("J6 én tur -> slå igen", J.decide(mowing, standing, {{phase = "finished"}}, 0).action, "dispatch")
|
||||
check("J6 to ture -> videre", J.decide(mowing, standing, {{phase = "finished"}, {phase = "finished"}}, 0).action, "advance")
|
||||
|
||||
-- J7: slået mark uden sæt (fx sluppet undervejs) -> videre til opsamling, ikke en ny slå-tur
|
||||
check("J7 slået uden sæt -> videre", J.decide(mowing, {id = 109, markerId = 5, mowable = true, readyRatio = 0.1}, {}, 0).action, "advance")
|
||||
check("J7 stående uden sæt -> slå", J.decide(mowing, info, {}, 0).action, "dispatch")
|
||||
check("J7 slået men sæt i gang -> vent", J.decide(mowing, {id = 109, markerId = 5, mowable = true, readyRatio = 0.1}, {{phase = "working"}}, 0).action, "none")
|
||||
|
||||
-- J8: høst — høster først (hovedrolle), aflæsningsvogne kun mens høsteren arbejder
|
||||
local harvestInfo = {id = 50, markerId = 7, kind = "harvest", readyRatio = 1, fruitIndex = 3}
|
||||
local harvesting = M.start(M.onReadiness(M.newField(50), 1), "harvest", {})
|
||||
local d = J.decide(harvesting, harvestInfo, {}, 0)
|
||||
check("J8 send høster", d.role, "harvester"); check("J8 ikke hjælper", d.helper, false)
|
||||
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")
|
||||
-- sæt fra et tidligere trin (vogn på vej hjem) tæller ikke på det nye trin
|
||||
local cultivating = M.advance(harvesting, {})
|
||||
check("J8 gammel vogn tæller ikke", J.decide(cultivating, afterInfo, {{phase = "working", stage = "harvesting", helper = true}}, 0).role, "cultivator")
|
||||
|
||||
-- J9: multi-tool — hele holdet sendes samlet; mens nogen kører, sendes ingen nye hovedsæt
|
||||
local multiMow = M.with(mowing, {plan = {rigs = 3}})
|
||||
local dMulti = J.decide(multiMow, info, {}, 0)
|
||||
check("J9 tre slåmaskiner", dMulti.count, 3)
|
||||
check("J9 én kører stadig -> ingen nye", J.decide(multiMow, info, {{phase = "working"}, {phase = "finished"}}, 0).action, "none")
|
||||
|
||||
-- J10: pleje under vækst — Auto starter, Manuel viser behovet
|
||||
local growingInfo = {id = 60, markerId = 8, kind = "harvest", growing = true, fert = 0, fertMax = 2, lime = 2, weed = 0, readyRatio = 0.2}
|
||||
local autoField = M.with(M.newField(60), {plan = {mode = "auto"}})
|
||||
local care = J.decide(autoField, growingInfo, {}, 0)
|
||||
check("J10 auto -> pleje", care.action, "care"); check("J10 gødning", care.stage, "fertilizing")
|
||||
local manualCare = J.decide(M.newField(60), growingInfo, {}, 0)
|
||||
check("J10 manuel -> intet, men behov vist", manualCare.action, "none"); check("J10 behov", manualCare.care, "fertilizing")
|
||||
check("J10 Start nu -> pleje", J.decide(M.with(M.newField(60), {startRequested = true}), growingInfo, {}, 0).action, "care")
|
||||
check("J10 uden markør -> vent", J.decide(autoField, {id = 60, kind = "harvest", growing = true, fert = 0, fertMax = 2}, {}, 0).action, "wait")
|
||||
|
||||
-- J11: regn — vejrfølsomme trin sendes ikke ud (kørende sæt fortsætter); klargøring/pleje kører videre
|
||||
local rain = {raining = true}
|
||||
local dRain = J.decide(mowing, info, {}, 0, rain)
|
||||
check("J11 slåning venter i regn", dRain.action, "wait"); check("J11 grund", dRain.reason, "rain")
|
||||
check("J11 kørende slåmaskine fortsætter", J.decide(mowing, info, {{phase = "working"}}, 0, rain).action, "none")
|
||||
check("J11 høst venter", J.decide(harvesting, harvestInfo, {}, 0, rain).reason, "rain")
|
||||
check("J11 kultivering kører i regn", J.decide(cultivating, afterInfo, {}, 0, rain).role, "cultivator")
|
||||
check("J11 start af høst venter", J.decide(M.with(M.onReadiness(M.newField(51), 1), {startRequested = true, kind = "harvest"}), harvestInfo, {}, 0, rain).reason, "rain")
|
||||
local silageCollect = M.advance(mowing)
|
||||
check("J11 ensilage-opsamling kører i regn", J.decide(silageCollect, info, {}, 0, rain).action, "dispatch")
|
||||
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 klargøres straks — kun selve såningen venter på såtiden
|
||||
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 -> start alligevel (klargøring)", J.decide(bareTarget, bareInfo, {}, 0, {plantable = function() return false end}).action, "start")
|
||||
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")
|
||||
|
||||
-- J13: afbrudt slåning efter genindlæsning (in-game 27/9 F90): genvækst i mellemhøjde -> vent; hjørne efter en tur -> slå straks
|
||||
local regrowing = {id = 90, markerId = 9, mowable = true, readyRatio = 0.5, immatureRatio = 0.5}
|
||||
local dRegrow = J.decide(mowing, regrowing, {}, 0)
|
||||
check("J13 genvækst -> vent", dRegrow.action, "wait"); check("J13 grund", dRegrow.reason, "regrowing")
|
||||
check("J13 genvækst men tur færdig -> slå hjørnet", J.decide(mowing, regrowing, {{phase = "finished"}}, 0).action, "dispatch")
|
||||
check("J13 halvt slået uden genvækst -> slå resten", J.decide(mowing, {id = 90, markerId = 9, mowable = true, readyRatio = 0.6, immatureRatio = 0}, {}, 0).action, "dispatch")
|
||||
|
||||
-- J14: kultivering på en mark med uhøstede kartofler (in-game 27/9 F16) -> tilbage til klar; rest efter høst -> ikke
|
||||
local cultivatingField = M.with(auto, {stage = "cultivating", kind = "harvest"})
|
||||
check("J14 uhøstet -> spol tilbage", J.decide(cultivatingField, {id = 16, markerId = 9, kind = "harvest", phase = "ready", readyRatio = 1}, {}, 0).action, "rewind")
|
||||
check("J14 høstrest 30 % -> kultiver", J.decide(cultivatingField, {id = 16, markerId = 9, kind = "harvest", phase = "ready", readyRatio = 0.3}, {}, 0).action ~= "rewind", true)
|
||||
check("J14 stub -> kultiver", J.decide(cultivatingField, {id = 16, markerId = 9, kind = "bare", phase = "cut", readyRatio = 0}, {}, 0).action ~= "rewind", true)
|
||||
|
||||
|
||||
-- resumeField: efter genindlæsning stopper spillet alle arbejdere; en CP-maskine der står PÅ en mark hvis
|
||||
-- aktuelle trin passer til maskinens rolle, meldes ind på den mark igen (in-game 27/9)
|
||||
do
|
||||
local square = {{x = 0, z = 0}, {x = 100, z = 0}, {x = 100, z = 100}, {x = 0, z = 100}}
|
||||
local fields = {
|
||||
{id = 90, stage = "collecting", verts = square},
|
||||
{id = 111, stage = "harvesting", verts = {{x = 200, z = 0}, {x = 300, z = 0}, {x = 300, z = 100}, {x = 200, z = 100}}},
|
||||
}
|
||||
check("RF høster på F111", ADFieldJobs.resumeField(250, 50, "harvester", fields), 111)
|
||||
check("RF læssevogn på F90", ADFieldJobs.resumeField(50, 50, "collector", fields), 90)
|
||||
check("RF forkert rolle til trinnet -> nil", ADFieldJobs.resumeField(250, 50, "collector", fields), nil)
|
||||
check("RF uden for alle marker -> nil", ADFieldJobs.resumeField(150, 50, "harvester", fields), nil)
|
||||
check("RF mark uden omrids -> nil", ADFieldJobs.resumeField(50, 50, "collector", {{id = 1, stage = "collecting", verts = {}}}), nil)
|
||||
check("RF på kanten tæller som indenfor", ADFieldJobs.resumeField(0.5, 50, "collector", fields), 90)
|
||||
end
|
||||
|
||||
|
||||
-- startAt: hvor Courseplay skal starte i kurset (CP's standard er "first" = forfra, også på en halvt høstet mark;
|
||||
-- in-game 27/9: mejetærskeren startede F111 forfra)
|
||||
check("SA eget kursus med husket waypoint -> last", ADFieldJobs.startAtFor({reuse = true, remembered = true, stage = "harvesting", standingRatio = 0.4}), "last")
|
||||
check("SA halvt høstet -> nearest", ADFieldJobs.startAtFor({stage = "harvesting", standingRatio = 0.4}), "nearest")
|
||||
check("SA halvt slået -> nearest", ADFieldJobs.startAtFor({stage = "mowing", standingRatio = 0.5}), "nearest")
|
||||
check("SA urørt mark -> first", ADFieldJobs.startAtFor({stage = "harvesting", standingRatio = 1}), "first")
|
||||
check("SA næsten urørt (97 %) -> first", ADFieldJobs.startAtFor({stage = "harvesting", standingRatio = 0.97}), "first")
|
||||
check("SA ukendt fremdrift -> first", ADFieldJobs.startAtFor({stage = "harvesting"}), "first")
|
||||
check("SA klargøring (ingen afgrøde at måle) -> first", ADFieldJobs.startAtFor({stage = "cultivating", standingRatio = 0.3}), "first")
|
||||
check("SA husket waypoint uden eget kursus -> ikke last", ADFieldJobs.startAtFor({reuse = false, remembered = true, stage = "cultivating"}), "first")
|
||||
|
||||
|
||||
-- shouldResume: kun maskiner der stadig er midt i jobbet genstartes (in-game 27/9: to ROPA'er med et husket
|
||||
-- waypoint fra et gammelt job kørte ud ad vejene mod det gamle kursus)
|
||||
check("SR står på en mark i gang med sit trin -> ja", ADFieldJobs.shouldResume({fieldId = 111, distance = 400}), true)
|
||||
check("SR lige ved sit huskede waypoint -> ja", ADFieldJobs.shouldResume({distance = 12}), true)
|
||||
check("SR langt fra waypointet og ingen mark -> nej", ADFieldJobs.shouldResume({distance = 850}), false)
|
||||
check("SR ukendt afstand og ingen mark -> nej", ADFieldJobs.shouldResume({}), false)
|
||||
|
||||
|
||||
-- mayDispatch: markarbejdet sender højst én maskine ud pr. DISPATCH_GAP_MS på tværs af alle marker
|
||||
-- (in-game 27/9: 6 maskiner kørte fra parkeringen i samme sekund efter Start alle)
|
||||
check("MD første udsendelse", ADFieldJobs.mayDispatch(5000, nil), true)
|
||||
check("MD for tæt på forrige", ADFieldJobs.mayDispatch(5000 + ADFieldJobs.DISPATCH_GAP_MS - 1, 5000), false)
|
||||
check("MD efter mellemrummet", ADFieldJobs.mayDispatch(5000 + ADFieldJobs.DISPATCH_GAP_MS, 5000), true)
|
||||
|
||||
|
||||
-- halvt høstet mark (in-game 27/9 F111): høsten er ikke færdig før næsten intet står; en mark i klargøring med
|
||||
-- stående afgrøde spoles tilbage
|
||||
do
|
||||
local halfInfo = {id = 111, markerId = 5, kind = "harvest", phase = "ready", readyRatio = 0.45, standingRatio = 0.45, fruitIndex = 1}
|
||||
check("HH halvt høstet uden høster -> send høster (ikke videre)", J.decide(harvesting, halfInfo, {}, 0).action, "dispatch")
|
||||
local doneInfo = {id = 111, markerId = 5, kind = "harvest", phase = "ready", readyRatio = 0.02, standingRatio = 0.02, fruitIndex = 1}
|
||||
check("HH næsten intet står -> videre", J.decide(harvesting, doneInfo, {}, 0).action, "advance")
|
||||
local cultivating = M.with(harvesting, {stage = "cultivating"})
|
||||
check("HH klargøring med halv mark stående -> spol tilbage", J.decide(cultivating, halfInfo, {}, 0).action, "rewind")
|
||||
end
|
||||
|
||||
|
||||
-- startTimedOut: CP må beregne kursus i op til 5 min (in-game 27/9 F90: 140 ha tog over 60 s, moden opgav midt i)
|
||||
check("ST start uden beregning > 60 s -> opgiv", ADFieldJobs.startTimedOut(61000, false), true)
|
||||
check("ST beregner stadig efter 90 s -> vent", ADFieldJobs.startTimedOut(90000, true), false)
|
||||
check("ST beregner over 5 min -> opgiv", ADFieldJobs.startTimedOut(301000, true), true)
|
||||
check("ST under 60 s -> vent", ADFieldJobs.startTimedOut(30000, false), false)
|
||||
|
||||
|
||||
-- shouldEmptyHarvester: høsten er færdig, men høsteren har korn i tanken og står ved marken -> tøm først
|
||||
-- (in-game 27/9 F15: marken gik til halm med fuld tank, ingen tømmevogn kom)
|
||||
check("EH korn i tanken ved marken -> tøm", ADFieldJobs.shouldEmptyHarvester({fill = 12000, near = true}), true)
|
||||
check("EH tom tank -> videre", ADFieldJobs.shouldEmptyHarvester({fill = 0, near = true}), false)
|
||||
check("EH langt væk -> videre", ADFieldJobs.shouldEmptyHarvester({fill = 12000, near = false}), false)
|
||||
check("EH optaget -> videre", ADFieldJobs.shouldEmptyHarvester({fill = 12000, near = true, busy = true}), false)
|
||||
check("EH spiller i -> videre", ADFieldJobs.shouldEmptyHarvester({fill = 12000, near = true, controlled = true}), false)
|
||||
check("EH allerede forsøgt -> videre", ADFieldJobs.shouldEmptyHarvester({fill = 12000, near = true, tried = true}), false)
|
||||
|
||||
|
||||
-- nearestPoint: CP bruger markens punkt som kursets startsted — tag det nærmest traktoren (in-game 27/9 F17: første
|
||||
-- punkt lå 280 m væk i den anden ende, og CP kørte gennem skoven)
|
||||
do
|
||||
local pts = {{x = 1600, z = -1839}, {x = 1350, z = -1740}, {x = 1500, z = -1800}}
|
||||
local p = ADFieldJobs.nearestPoint(pts, 1340, -1733)
|
||||
check("NP nærmest traktoren", p.x .. "," .. p.z, "1350,-1740")
|
||||
check("NP uden punkter -> nil", ADFieldJobs.nearestPoint({}, 0, 0), nil)
|
||||
check("NP uden position -> første", ADFieldJobs.nearestPoint(pts, nil, nil).x, 1600)
|
||||
end
|
||||
|
||||
-- pulje: en traktor der henter et redskab tæller som i gang (ellers sendes endnu en ud)
|
||||
check("pulje: henter -> ingenting", J.decide(mowing, info, {{phase = "fetching"}}, 0).action, "none")
|
||||
-- JP: klargøring der allerede er gjort (fx efter genindlæsning) går videre uden at sende maskinen ud igen
|
||||
-- (in-game 28/9: F17 blev kultiveret 3 gange — opgaver gemmes ikke, så "færdig" blev aldrig set)
|
||||
local cultField = M.with(M.newField(17), {stage = "cultivating", kind = "bare", plan = {mode = "auto"}})
|
||||
local cultInfo = {id = 17, markerId = 5, kind = "bare", readyRatio = 0, needs = {}}
|
||||
cultInfo.prepared = 0.98
|
||||
check("JP kultiveret -> videre", J.decide(cultField, cultInfo, {}, 0).action, "advance")
|
||||
cultInfo.prepared = 0.4
|
||||
check("JP halvt kultiveret -> send maskine", J.decide(cultField, cultInfo, {}, 0).action, "dispatch")
|
||||
cultInfo.prepared = nil
|
||||
check("JP ukendt jord -> som før (send)", J.decide(cultField, cultInfo, {}, 0).action, "dispatch")
|
||||
cultInfo.prepared = 0.98
|
||||
check("JP kultivator i gang -> vent på den", J.decide(cultField, cultInfo, {{phase = "working", stage = "cultivating"}}, 0).action, "none")
|
||||
local plowField = M.with(cultField, {stage = "plowing"})
|
||||
check("JP kultiveret er ikke pløjet -> send plov", J.decide(plowField, cultInfo, {}, 0).action, "dispatch")
|
||||
cultInfo.plowed = 0.97
|
||||
check("JP pløjet -> videre", J.decide(plowField, cultInfo, {}, 0).action, "advance")
|
||||
local sowField = M.with(cultField, {stage = "sowing"})
|
||||
check("JP såning berøres ikke af klargjort jord", J.decide(sowField, cultInfo, {}, 0).action ~= "advance", true)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,324 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldModel.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFieldModel.lua")
|
||||
local M = ADFieldModel
|
||||
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
|
||||
|
||||
-- M1: ny mark = venter, manuel ensilage uden riv
|
||||
local f = M.newField(109)
|
||||
check("M1 stage", f.stage, "idle")
|
||||
check("M1 mode", f.plan.mode, "manual")
|
||||
check("M1 product auto", f.plan.product, "auto")
|
||||
check("M1 auto = ensilage på slåafgrøde", M.product(f.plan, "mowable"), "silage")
|
||||
check("M1 auto = høst ellers", M.product(f.plan, "harvest"), "harvest")
|
||||
check("M1 rake", f.plan.rake, false)
|
||||
|
||||
-- M2: klar ved >= 90 %
|
||||
check("M2 89 % ikke klar", M.onReadiness(f, 0.89).stage, "idle")
|
||||
local ready = M.onReadiness(f, 0.9)
|
||||
check("M2 90 % klar", ready.stage, "ready")
|
||||
check("M2 immutabel", f.stage, "idle")
|
||||
|
||||
-- M3: klar mark slået af spilleren -> tilbage til venter
|
||||
check("M3 slået manuelt", M.onReadiness(ready, 0.3).stage, "idle")
|
||||
check("M3 lidt under 90 % forbliver klar", M.onReadiness(ready, 0.8).stage, "ready")
|
||||
|
||||
-- M4: trin pr. plan
|
||||
check("M4 ensilage", table.concat(M.workStages({product = "silage"}), ","), "mowing,collecting")
|
||||
check("M4 ensilage m. riv", table.concat(M.workStages({product = "silage", rake = true}), ","), "mowing,raking,collecting")
|
||||
check("M4 hø", table.concat(M.workStages({product = "hay", rake = false}), ","), "mowing,tedding,collecting")
|
||||
check("M4 hø m. riv", table.concat(M.workStages({product = "hay", rake = true}), ","), "mowing,tedding,raking,collecting")
|
||||
|
||||
-- M5: start: auto starter selv, manuel kun på forespørgsel
|
||||
check("M5 manuel starter ikke", M.shouldStart(ready), false)
|
||||
local auto = M.onReadiness(M.newField(5), 1)
|
||||
auto.plan.mode = "auto"
|
||||
check("M5 auto starter", M.shouldStart(auto), true)
|
||||
local requested = M.newField(6); requested.stage = "ready"; requested.startRequested = true
|
||||
check("M5 Start nu", M.shouldStart(requested), true)
|
||||
local started = M.start(ready)
|
||||
check("M5 start -> slås", started.stage, "mowing")
|
||||
check("M5 start rydder forespørgsel", started.startRequested, false)
|
||||
local oldMow = M.with(ready, {mowVehicleId = "old"})
|
||||
check("M5 start glemmer gammel slåmaskine", M.start(oldMow).mowVehicleId, nil)
|
||||
|
||||
-- M6: videre gennem trinene; færdig mark bliver klar igen når den er vokset
|
||||
local hay = M.start(M.onReadiness(M.newField(7), 1)); hay.plan.product = "hay"
|
||||
hay = M.advance(hay); check("M6 hø -> vendes", hay.stage, "tedding")
|
||||
hay = M.advance(hay); check("M6 -> samles op", hay.stage, "collecting")
|
||||
hay = M.advance(hay); check("M6 -> færdig", hay.stage, "done")
|
||||
check("M6 færdig forbliver ved lav andel", M.onReadiness(hay, 0.1).stage, "done")
|
||||
check("M6 færdig -> klar når vokset", M.onReadiness(hay, 0.95).stage, "ready")
|
||||
check("M6 arbejdende mark ignorerer andel", M.onReadiness(started, 0).stage, "mowing")
|
||||
check("M6 isWorking", M.isWorking(started), true)
|
||||
check("M6 stop -> klar", M.stop(started).stage, "ready")
|
||||
|
||||
-- M7: roller, maks sæt, bredde
|
||||
check("M7 rolle slå", M.roleFor("mowing"), "mower")
|
||||
check("M7 rolle saml", M.roleFor("collecting"), "collector")
|
||||
check("M7 rolle klar = ingen", M.roleFor("ready"), nil)
|
||||
check("M7 maks læssevogne = planens sæt", M.maxRigs("collecting", {rigs = 2}), 2)
|
||||
check("M7 maks slå", M.maxRigs("mowing"), 1)
|
||||
local okWide, whyWide = M.checkWidth("mower", 60, nil)
|
||||
check("M7 over 50 m", okWide, false); check("M7 grund", whyWide, "tooWide")
|
||||
check("M7 40,8 m slå ok", (M.checkWidth("mower", 40.8, nil)), true)
|
||||
-- M7b: bredde under 1,5 m er ikke læst rigtigt (spreder gav 0,5 m -> CP-kursus hver halve meter på 72 ha
|
||||
-- -> spillet frøs, in-game 26/9) — sendes aldrig ud
|
||||
local okTiny, whyTiny = M.checkWidth("fertilizer", 0.5, nil)
|
||||
check("M7b 0,5 m afvises", okTiny, false)
|
||||
check("M7b grund", whyTiny, "widthUnknown")
|
||||
check("M7b 3 m strigle ok", (M.checkWidth("weeder", 3, nil)), true)
|
||||
check("M7b aflæsningsvogn uden bredde ok", (M.checkWidth("unloader", 0, nil)), true)
|
||||
check("M7b ballesamler uden bredde ok", (M.checkWidth("baleCollector", 0, nil)), true)
|
||||
local okNarrow, whyNarrow, args = M.checkWidth("collector", 20, 40.8)
|
||||
check("M7 for smal", okNarrow, false); check("M7 grund smal", whyNarrow, "tooNarrow"); check("M7 args", args[2], 40.8)
|
||||
check("M7 lige bred ok (tolerance)", (M.checkWidth("collector", 40.76, 40.8)), true)
|
||||
check("M7 ukendt slåbredde ok", (M.checkWidth("tedder", 10, nil)), true)
|
||||
|
||||
-- wantsRig: kun arbejdende trin, under maks, efter pause
|
||||
check("M7 wantsRig slå", M.wantsRig(started, 0, 0).role, "mower")
|
||||
check("M7 wantsRig fuld", M.wantsRig(started, 1, 0), nil)
|
||||
local paused = M.start(M.onReadiness(M.newField(8), 1)); paused.retryAt = 1000
|
||||
check("M7 wantsRig i pause", M.wantsRig(paused, 0, 500), nil)
|
||||
check("M7 wantsRig efter pause", M.wantsRig(paused, 0, 1000).role, "mower")
|
||||
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"}, rigIds = {"v1", "v2"}}
|
||||
local back = M.fromRecord(M.toRecord(saved))
|
||||
check("M8 id", back.id, 109)
|
||||
check("M8 stage", back.stage, saved.stage)
|
||||
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)
|
||||
check("M8 ukendt trin -> venter", M.fromRecord({id = 3, stage = "bogus"}).stage, "idle")
|
||||
|
||||
-- M9: with() kopierer og fletter planen
|
||||
local base = M.start(M.onReadiness(M.newField(1), 1))
|
||||
local noted = M.with(base, {reason = {text = "x"}, retryAt = 5, plan = {product = "hay"}})
|
||||
check("M9 grund", noted.reason.text, "x"); check("M9 pause", noted.retryAt, 5)
|
||||
check("M9 plan flettet", noted.plan.product, "hay"); check("M9 mode bevaret", noted.plan.mode, "manual")
|
||||
check("M9 trin bevaret", noted.stage, "mowing"); check("M9 original urørt", base.plan.product, "auto")
|
||||
|
||||
-- M10: Start nu på en mark der ikke er helt klar -> klar; arbejdende mark røres ikke
|
||||
check("M10 venter -> klar", M.forceReady(M.newField(2)).stage, "ready")
|
||||
check("M10 arbejdende uændret", M.forceReady(base).stage, "mowing")
|
||||
|
||||
-- M11: krævet bredde og kursus-kilde pr. rolle (smalle standardmaskiner, 25/9)
|
||||
local cut = M.with(M.newField(3), {mowWidth = 40.8})
|
||||
check("M11 vender kræver ingen bredde", M.requiredWidth(cut, "tedder"), nil)
|
||||
check("M11 river kræver ingen bredde", M.requiredWidth(cut, "rake"), nil)
|
||||
check("M11 læssevogn uden riv = slåbredde", M.requiredWidth(cut, "collector"), 40.8)
|
||||
local raked = M.with(cut, {plan = {rake = true}})
|
||||
check("M11 læssevogn efter riv = ingen krav", M.requiredWidth(raked, "collector"), nil)
|
||||
check("M11 vender eget kursus", M.courseSource(cut, "tedder"), nil)
|
||||
check("M11 river eget kursus", M.courseSource(raked, "rake"), nil)
|
||||
check("M11 læssevogn uden riv -> slåkursus", M.courseSource(cut, "collector"), "mower")
|
||||
check("M11 læssevogn efter riv -> rivekursus", M.courseSource(raked, "collector"), "rake")
|
||||
check("M11 slåmaskine eget kursus", M.courseSource(cut, "mower"), nil)
|
||||
-- gem/indlæs rivemaskine
|
||||
local withRake = M.with(raked, {rakeVehicleId = "vehicleRake"})
|
||||
check("M11 rivemaskine gemt", M.fromRecord(M.toRecord(withRake)).rakeVehicleId, "vehicleRake")
|
||||
check("M11 start glemmer rivemaskine", M.start(M.with(M.onReadiness(M.newField(9), 1), {rakeVehicleId = "x"})).rakeVehicleId, nil)
|
||||
|
||||
-- M12: sideriver — læssevognens CP-forskydning = egen forskydning − skårets forskydning (venstre positiv),
|
||||
-- holdt inden for Courseplays ±10 m; skårets forskydning gemmes pr. mark
|
||||
check("M12 midterrive", M.collectorOffset(0, 0), 0)
|
||||
check("M12 skår 3 m til venstre", M.collectorOffset(0, 3), -3)
|
||||
check("M12 vogn med egen forskydning", M.collectorOffset(0.5, -2), 2.5)
|
||||
check("M12 holdes inden for 10 m", M.collectorOffset(0, 14), -10)
|
||||
check("M12 ukendt skår -> egen forskydning", M.collectorOffset(0.4, nil), 0.4)
|
||||
local side = M.with(M.newField(11), {windrowOffset = 3.2})
|
||||
check("M12 skår gemt", M.fromRecord(M.toRecord(side)).windrowOffset, 3.2)
|
||||
check("M12 intet skår gemt", M.fromRecord(M.toRecord(M.newField(12))).windrowOffset, nil)
|
||||
check("M12 start glemmer skår", M.start(M.with(M.onReadiness(M.newField(13), 1), {windrowOffset = 1})).windrowOffset, nil)
|
||||
|
||||
-- M13: gæringssilo = produktion der laver markens skår om til ensilage (FermentingSiloPack m.fl.)
|
||||
local GRASS_W, DRY_W, SILAGE, CHAFF, BREAD = 10, 11, 20, 30, 40
|
||||
local fermenter = {{inputs = {{type = CHAFF}}, outputs = {{type = SILAGE}}}, {inputs = {{type = GRASS_W}}, outputs = {{type = SILAGE}}}}
|
||||
check("M13 græs -> ensilage", M.isFermenter(fermenter, GRASS_W, SILAGE), true)
|
||||
check("M13 hø ikke i denne", M.isFermenter(fermenter, DRY_W, SILAGE), false)
|
||||
check("M13 bageri ikke", M.isFermenter({{inputs = {{type = GRASS_W}}, outputs = {{type = BREAD}}}}, GRASS_W, SILAGE), false)
|
||||
check("M13 ingen produktioner", M.isFermenter(nil, GRASS_W, SILAGE), false)
|
||||
check("M13 ukendt ensilage-type", M.isFermenter(fermenter, GRASS_W, nil), false)
|
||||
|
||||
-- M14: høstkæde + klargøring efter behov
|
||||
local plan = {mode = "manual", product = "auto", rake = false, rigs = 1, unloaders = 1, mulch = false, lime = true,
|
||||
soil = "auto", sow = true, sowFruit = "", roll = true, fertilize = true, weed = true, storeBuildingIds = {}}
|
||||
check("M14 høst + kultivér + så + tromle", table.concat(M.workStages(plan, "harvest", {}), ","), "harvesting,cultivating,sowing,rolling")
|
||||
check("M14 kalk + pløj når jorden trænger", table.concat(M.workStages(plan, "harvest", {lime = true, plow = true}), ","), "harvesting,liming,plowing,sowing,rolling")
|
||||
local noSoil = {soil = "none", sow = false, lime = false, mulch = true}
|
||||
check("M14 kun stubbehakning", table.concat(M.workStages(noSoil, "harvest", {}), ","), "harvesting,mulching")
|
||||
check("M14 bar mark = kun klargøring", table.concat(M.workStages(plan, "bare", {plow = true}), ","), "plowing,sowing,rolling")
|
||||
check("M14 tvunget pløj", table.concat(M.prepStages({soil = "plow"}, {}), ","), "plowing")
|
||||
local harvestField = M.start(M.with(M.newField(20), {plan = {sow = true, soil = "cultivate"}}), "harvest", {})
|
||||
check("M14 start = høst", harvestField.stage, "harvesting")
|
||||
check("M14 husker art", harvestField.kind, "harvest")
|
||||
local afterHarvest = M.advance(harvestField, {})
|
||||
check("M14 høst -> kultivér", afterHarvest.stage, "cultivating")
|
||||
check("M14 -> så", M.advance(afterHarvest, {}).stage, "sowing")
|
||||
check("M14 så -> færdig", M.advance(M.advance(afterHarvest, {}), {}).stage, "done")
|
||||
check("M14 kalk springes over når jorden er kalket", M.advance(M.with(harvestField, {plan = {lime = true}}), {lime = false}).stage, "cultivating")
|
||||
check("M14 tom kæde -> færdig", M.start(M.with(M.newField(21), {plan = {soil = "none", sow = false, lime = false}}), "bare", {}).stage, "done")
|
||||
|
||||
-- M15: hjælpere under høst og multi-tool-baner
|
||||
check("M15 aflæsningsvogn efter høsteren", M.wantsRig(harvestField, {primary = 1, helpers = 0}, 0).role, "unloader")
|
||||
check("M15 hjælper-flag", M.wantsRig(harvestField, {primary = 1, helpers = 0}, 0).helper, true)
|
||||
check("M15 alle vogne på plads -> intet", M.wantsRig(harvestField, {primary = 1, helpers = 1}, 0), nil)
|
||||
local multi = M.with(harvestField, {plan = {rigs = 3, unloaders = 2}})
|
||||
check("M15 multi: 3 høstere samlet", M.wantsRig(multi, {primary = 0, helpers = 0}, 0).count, 3)
|
||||
check("M15 multi: 2 vogne", M.maxHelpers("harvesting", multi.plan), 2)
|
||||
check("M15 ingen hjælper ved slåning", M.maxHelpers("mowing", multi.plan), 0)
|
||||
check("M15 baner 2", table.concat(M.lanePositions(2), ","), "-1,1")
|
||||
check("M15 baner 3", table.concat(M.lanePositions(3), ","), "-1,0,1")
|
||||
check("M15 baner 4", table.concat(M.lanePositions(4), ","), "-2,-1,1,2")
|
||||
check("M15 baner 5", table.concat(M.lanePositions(5), ","), "-2,-1,0,1,2")
|
||||
check("M15 over 5 -> 5", #M.lanePositions(9), 5)
|
||||
check("M15 maxRigs holdes 1-5", M.maxRigs("mowing", {rigs = 7}), 5)
|
||||
|
||||
-- M16: pleje under vækst
|
||||
local growing = {kind = "harvest", growing = true, fert = 1, fertMax = 2, lime = 2, weed = 0}
|
||||
local idleField = M.newField(30)
|
||||
check("M16 gødning", M.careNeed(idleField, growing), "fertilizing")
|
||||
check("M16 ukrudt før gødning", M.careNeed(idleField, {kind = "harvest", growing = true, fert = 1, fertMax = 2, lime = 2, weed = 3}), "weeding")
|
||||
check("M16 græs ukrudtsbehandles ikke", M.careNeed(idleField, {kind = "mowable", growing = true, fert = 2, fertMax = 2, lime = 2, weed = 3}), nil)
|
||||
check("M16 kalk", M.careNeed(idleField, {kind = "harvest", growing = true, fert = 2, fertMax = 2, lime = 0, weed = 0}), "careLiming")
|
||||
check("M16 intet behov", M.careNeed(idleField, {kind = "harvest", growing = true, fert = 2, fertMax = 2, lime = 1, weed = 1}), nil)
|
||||
check("M16 ikke voksende", M.careNeed(idleField, {kind = "harvest", growing = false, fert = 0, fertMax = 2}), nil)
|
||||
check("M16 gødning slået fra", M.careNeed(M.with(idleField, {plan = {fertilize = false}}), growing), nil)
|
||||
check("M16 arbejdende mark", M.careNeed(harvestField, growing), nil)
|
||||
local cared = M.startCare(idleField, "fertilizing")
|
||||
check("M16 pleje-trin", cared.stage, "fertilizing")
|
||||
check("M16 rolle", M.roleFor(cared.stage), "fertilizer")
|
||||
check("M16 kalk-pleje rolle", M.roleFor("careLiming"), "limer")
|
||||
check("M16 pleje -> idle", M.advance(cared, {}).stage, "idle")
|
||||
local twice = M.startCare(M.advance(cared, {}), "fertilizing")
|
||||
check("M16 højst 2 gange", M.careNeed(M.advance(twice, {}), growing), nil)
|
||||
check("M16 ny cyklus nulstiller", M.careNeed(M.advance(M.start(M.advance(twice, {}), "harvest", {}), {}), growing) ~= "x", true)
|
||||
check("M16 start nulstiller pleje-tæller", next(M.start(twice, "harvest", {}).careRuns), nil)
|
||||
check("M16 stop af pleje -> idle", M.stop(cared).stage, "idle")
|
||||
check("M16 stop af høst -> klar", M.stop(harvestField).stage, "ready")
|
||||
|
||||
-- M17: såafgrøde og gem/indlæs af den udvidede plan
|
||||
check("M17 så samme afgrøde", M.sowFruit(M.with(M.newField(40), {lastFruit = "WHEAT"})), "WHEAT")
|
||||
check("M17 planens afgrøde vinder", M.sowFruit(M.with(M.newField(40), {lastFruit = "WHEAT", plan = {sowFruit = "CANOLA"}})), "CANOLA")
|
||||
local full = M.with(M.startCare(M.newField(41), "weeding"), {kind = "harvest", lastFruit = "BARLEY",
|
||||
plan = {rigs = 3, unloaders = 2, mulch = true, lime = false, soil = "plow", sow = true, sowFruit = "WHEAT", roll = true, fertilize = false, weed = false, product = "harvest"}})
|
||||
local round = M.fromRecord(M.toRecord(full))
|
||||
check("M17 trin", round.stage, "weeding")
|
||||
check("M17 sæt", round.plan.rigs, 3)
|
||||
check("M17 vogne", round.plan.unloaders, 2)
|
||||
check("M17 stub", round.plan.mulch, true)
|
||||
check("M17 kalk fra", round.plan.lime, false)
|
||||
check("M17 jord", round.plan.soil, "plow")
|
||||
check("M17 så", round.plan.sow, true)
|
||||
check("M17 såafgrøde", round.plan.sowFruit, "WHEAT")
|
||||
check("M17 tromle", round.plan.roll, true)
|
||||
check("M17 gødning fra", round.plan.fertilize, false)
|
||||
check("M17 ukrudt fra", round.plan.weed, false)
|
||||
check("M17 art", round.kind, "harvest")
|
||||
check("M17 sidste afgrøde", round.lastFruit, "BARLEY")
|
||||
check("M17 pleje-tæller", round.careRuns.weeding, 1)
|
||||
check("M17 produkt", round.plan.product, "harvest")
|
||||
local legacy = M.fromRecord({id = 5, stage = "collecting", mode = "auto", product = "hay", rake = 1})
|
||||
check("M17 etape 1-post: hø", legacy.plan.product, "hay")
|
||||
check("M17 etape 1-post: standard kalk til", legacy.plan.lime, true)
|
||||
check("M17 etape 1-post: 1 sæt", legacy.plan.rigs, 1)
|
||||
|
||||
-- M18: Start nu på en bar mark (klar-andel 0) må ikke nulstilles af vækst-reglen før den starter
|
||||
local bare = M.with(M.forceReady(M.newField(70)), {startRequested = true})
|
||||
check("M18 bar mark forbliver klar", M.onReadiness(bare, 0).stage, "ready")
|
||||
check("M18 uden forespørgsel -> venter", M.onReadiness(M.forceReady(M.newField(71)), 0).stage, "idle")
|
||||
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? (altid — kun såningen venter på såtiden)
|
||||
check("M20 bar + sås nu -> start", M.shouldStartBare(target, "bare", true), true)
|
||||
check("M20 bar + ikke såtid -> klargør alligevel (kun såningen venter)", M.shouldStartBare(target, "bare"), true)
|
||||
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")
|
||||
|
||||
-- M23: ingen ny tabel når vækst-reglen ikke ændrer noget (undgår affald hvert sekund -> GC-hak)
|
||||
local calm = M.newField(95)
|
||||
check("M23 uændret -> samme tabel", M.onReadiness(calm, 0.2) == calm, true)
|
||||
check("M23 ændret -> ny tabel", M.onReadiness(calm, 0.95) ~= calm, true)
|
||||
-- M24: passive marker kan springes over (manuel, uden mål, venter, ingen forespørgsel)
|
||||
check("M24 manuel venter = passiv", M.isPassive(calm), true)
|
||||
check("M24 auto = ikke passiv", M.isPassive(M.with(calm, {plan = {mode = "auto"}})), false)
|
||||
check("M24 mål = ikke passiv", M.isPassive(M.setTarget(calm, "WHEAT")), false)
|
||||
check("M24 klar = ikke passiv", M.isPassive(M.with(calm, {stage = "ready"})), false)
|
||||
check("M24 i gang = ikke passiv", M.isPassive(started), false)
|
||||
check("M24 Start nu = ikke passiv", M.isPassive(M.with(calm, {startRequested = true})), false)
|
||||
|
||||
-- Stop holder en bar mark (ellers starter klargøringen straks forfra); Start eller ny mål-afgrøde slipper
|
||||
local stoppedBare = M.stop(M.with(target, {stage = "cultivating"}))
|
||||
check("M21 stoppet bar mark starter ikke selv", M.shouldStartBare(stoppedBare, "bare"), false)
|
||||
check("M21 Start slipper", M.shouldStartBare(M.with(stoppedBare, {startRequested = true}), "bare"), true)
|
||||
check("M21 ny mål-afgrøde slipper", M.shouldStartBare(M.setTarget(stoppedBare, "BARLEY"), "bare"), true)
|
||||
check("M21 start nulstiller hold (næste sæson starter selv)", M.start(stoppedBare, "bare", {}, "WHEAT").held, nil)
|
||||
-- gemt produkt (silage/hø) gælder kun slåafgrøder: en vinmark der engang fejlagtigt blev "slåbar" (F22, 29/9)
|
||||
check("produkt: gemt silage på høstmark -> høst", ADFieldModel.product({product = "silage"}, "harvest"), "harvest")
|
||||
check("produkt: gemt hø på slåmark -> hø", ADFieldModel.product({product = "hay"}, "mowable"), "hay")
|
||||
check("produkt: auto på slåmark -> silage", ADFieldModel.product({product = "auto"}, "mowable"), "silage")
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,46 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldSiloConfig.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFieldSiloConfig.lua")
|
||||
local C = ADFieldSiloConfig
|
||||
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 cfg = C.new()
|
||||
check("standard fra", cfg.enabled, false)
|
||||
check("standard x=2", cfg.maxVehicles, 2)
|
||||
check("standard tærskel 10000", cfg.minLiters, 10000)
|
||||
check("x under 1 -> 1", C.with(cfg, {maxVehicles = 0}).maxVehicles, 1)
|
||||
check("x over 10 -> 10", C.with(cfg, {maxVehicles = 99}).maxVehicles, 10)
|
||||
check("tærskel snappes til nærmeste valg", C.with(cfg, {minLiters = 12000}).minLiters, 10000)
|
||||
check("tærskel 40000 -> 50000", C.with(cfg, {minLiters = 40000}).minLiters, 50000)
|
||||
check("with muterer ikke", cfg.maxVehicles, 2)
|
||||
local on = C.toggleStore(cfg, "silo1")
|
||||
check("toggle til", C.hasStore(on, "silo1"), true)
|
||||
check("toggle muterer ikke", C.hasStore(cfg, "silo1"), false)
|
||||
check("toggle fra", C.hasStore(C.toggleStore(on, "silo1"), "silo1"), false)
|
||||
local full = C.with(C.toggleStore(C.toggleStore(cfg, "a"), "b"), {enabled = true, maxVehicles = 4, minLiters = 25000})
|
||||
local back = C.fromRecord(C.toRecord(full))
|
||||
check("record: enabled", back.enabled, true)
|
||||
check("record: lagre", table.concat(back.storeIds, " "), "a b")
|
||||
check("record: x", back.maxVehicles, 4)
|
||||
check("record: tærskel", back.minLiters, 25000)
|
||||
check("tom record -> standard", C.fromRecord(nil).maxVehicles, 2)
|
||||
check("record uden tal -> standard tærskel", C.fromRecord({enabled = 1}).minLiters, 10000)
|
||||
-- kør til produktion + foderreserve
|
||||
check("standard: ikke til produktion", cfg.toProduction, false)
|
||||
check("standard foderreserve 7 døgn", cfg.reserveDays, 7)
|
||||
check("reserve snappes (10 -> 7)", C.with(cfg, {reserveDays = 10}).reserveDays, 7)
|
||||
check("reserve 40 -> 30", C.with(cfg, {reserveDays = 40}).reserveDays, 30)
|
||||
local prod = C.with(C.toggleStore(cfg, "a"), {toProduction = true, reserveDays = 14})
|
||||
check("with bevarer lagre", C.hasStore(prod, "a"), true)
|
||||
check("with muterer ikke (produktion)", cfg.toProduction, false)
|
||||
local prodBack = C.fromRecord(C.toRecord(prod))
|
||||
check("record: til produktion", prodBack.toProduction, true)
|
||||
check("record: foderreserve", prodBack.reserveDays, 14)
|
||||
check("gammel record uden felter -> standard", C.fromRecord({enabled = 1}).reserveDays, 7)
|
||||
check("toggleStore bevarer produktion", C.toggleStore(prod, "b").toProduction, true)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,159 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldSiloPlanner.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFieldSiloPlanner.lua")
|
||||
local P = ADFieldSiloPlanner
|
||||
local WHEAT, CANOLA = 1, 2
|
||||
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 silo(id, x, stock, markerId) return {id = id, name = id, markerId = markerId == nil and (id .. "m") or markerId or nil, x = x, z = 0, stock = stock} end
|
||||
local function store(id, x, free) return {id = id, markerId = id .. "m", x = x, z = 0, free = free} end
|
||||
local function vehicle(id, x, capacity, carries, cargo) return {id = id, x = x, z = 0, capacity = capacity, carries = carries or {[WHEAT] = true, [CANOLA] = true}, cargo = cargo} end
|
||||
local function base(over)
|
||||
local input = {silos = {}, stores = {store("farm", 0, {[WHEAT] = 1000000, [CANOLA] = 1000000})}, loans = {}, vehicles = {}, paused = {}, maxVehicles = 2, minLiters = 10000}
|
||||
for k, v in pairs(over or {}) do input[k] = v end
|
||||
return input
|
||||
end
|
||||
|
||||
-- kvalificering
|
||||
local r = P.plan(base({silos = {silo("A", 500, {[WHEAT] = 9999})}, vehicles = {vehicle("v1", 400, 50000)}}))
|
||||
check("under tærskel -> ingen afsendelse", #r.dispatch, 0)
|
||||
check("under tærskel -> ingen status", #r.status, 0)
|
||||
r = P.plan(base({silos = {silo("A", 500, {[WHEAT] = 30000}, false)}, vehicles = {vehicle("v1", 400, 50000)}}))
|
||||
check("uden markør -> noMarker", r.status[1].state, "noMarker")
|
||||
check("uden markør -> ingen afsendelse", #r.dispatch, 0)
|
||||
r = P.plan(base({stores = {store("farm", 0, {[CANOLA] = 1000000})}, silos = {silo("A", 500, {[WHEAT] = 30000})}, vehicles = {vehicle("v1", 400, 50000)}}))
|
||||
check("ingen gård tager varen -> noFarmStore", r.status[1].state, "noFarmStore")
|
||||
r = P.plan(base({silos = {silo("A", 500, {[WHEAT] = 30000})}, vehicles = {vehicle("v1", 400, 50000)}, paused = {[P.key("A", WHEAT)] = true}}))
|
||||
check("pause -> paused, ingen afsendelse", r.status[1].state .. #r.dispatch, "paused0")
|
||||
|
||||
-- afsendelse, fuldeste først, x-loft
|
||||
r = P.plan(base({silos = {silo("A", 500, {[WHEAT] = 20000}), silo("B", 900, {[WHEAT] = 80000})}, vehicles = {vehicle("v1", 400, 50000), vehicle("v2", 950, 50000), vehicle("v3", 1000, 50000)}}))
|
||||
check("x=2 -> to afsendt", #r.dispatch, 2)
|
||||
check("fuldeste silo først", r.dispatch[1].siloId, "B")
|
||||
check("nærmeste vogn til B", r.dispatch[1].vehicleId, "v2")
|
||||
check("B's rest (30000) får vogn nr. 2", r.dispatch[2].siloId, "B")
|
||||
check("lager-markør med", r.dispatch[1].storeMarkerId, "farmm")
|
||||
check("liter = vognens kapacitet", r.dispatch[1].liters, 50000)
|
||||
check("anden tur = resten", r.dispatch[2].liters, 30000)
|
||||
|
||||
-- aktive lån tæller mod x og mod siloens rest
|
||||
r = P.plan(base({silos = {silo("A", 500, {[WHEAT] = 60000})}, loans = {{siloId = "A", fillType = WHEAT, liters = 50000}}, vehicles = {vehicle("v1", 400, 50000), vehicle("v2", 400, 50000)}}))
|
||||
check("lån på vej -> rest 10000 -> én ny vogn", #r.dispatch, 1)
|
||||
check("status viser på vej", r.status[1].onWay, 50000)
|
||||
r = P.plan(base({maxVehicles = 1, silos = {silo("A", 500, {[WHEAT] = 90000})}, loans = {{siloId = "Z", fillType = WHEAT, liters = 1}}, vehicles = {vehicle("v1", 400, 50000)}}))
|
||||
check("x=1 med ét lån ude -> ingen", #r.dispatch, 0)
|
||||
check("frie pladser 0", r.freeSlots, 0)
|
||||
|
||||
-- flere varer i én silo = hver sin opgave
|
||||
r = P.plan(base({silos = {silo("A", 500, {[WHEAT] = 40000, [CANOLA] = 20000})}, vehicles = {vehicle("v1", 400, 50000), vehicle("v2", 410, 50000)}}))
|
||||
check("to varer -> to afsendelser", #r.dispatch, 2)
|
||||
check("fuldeste vare først", r.dispatch[1].fillType, WHEAT)
|
||||
|
||||
-- vogne der ikke kan
|
||||
r = P.plan(base({silos = {silo("A", 500, {[WHEAT] = 40000})}, vehicles = {vehicle("v1", 400, 50000, {[CANOLA] = true}), vehicle("v2", 2000, 50000)}}))
|
||||
check("vogn der ikke kan bære varen springes over", r.dispatch[1].vehicleId, "v2")
|
||||
r = P.plan(base({silos = {silo("A", 500, {[WHEAT] = 40000})}, vehicles = {vehicle("v1", 400, 50000, nil, CANOLA), vehicle("v2", 2000, 50000)}}))
|
||||
check("vogn med anden vare på springes over", r.dispatch[1].vehicleId, "v2")
|
||||
r = P.plan(base({silos = {silo("A", 500, {[WHEAT] = 40000})}, vehicles = {vehicle("v1", 400, 0)}}))
|
||||
check("vogn uden kapacitet -> ingen", #r.dispatch, 0)
|
||||
|
||||
-- lagervalg
|
||||
r = P.plan(base({stores = {store("near", 400, {[WHEAT] = 20000}), store("far", 5000, {[WHEAT] = 900000})}, silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {vehicle("v1", 400, 50000), vehicle("v2", 400, 50000)}}))
|
||||
check("nærmeste lager uden plads til læsset -> det med plads", r.dispatch[1].storeId, "far")
|
||||
r = P.plan(base({stores = {store("small", 400, {[WHEAT] = 30000}), store("tiny", 300, {[WHEAT] = 15000})}, silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {vehicle("v1", 400, 50000), vehicle("v2", 400, 50000)}}))
|
||||
check("intet har plads til hele læsset -> mest plads", r.dispatch[1].storeId, "small")
|
||||
check("reserveret plads: vogn 2 til det andet lager", r.dispatch[2].storeId, "tiny")
|
||||
r = P.plan(base({stores = {store("full", 400, {[WHEAT] = 5000})}, silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {vehicle("v1", 400, 50000)}}))
|
||||
check("lager med under tærsklen -> noFarmStore", r.status[1].state, "noFarmStore")
|
||||
|
||||
-- input muteres ikke
|
||||
local input = base({silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {vehicle("v1", 400, 50000)}})
|
||||
P.plan(input)
|
||||
check("input.stores urørt", input.stores[1].free[WHEAT], 1000000)
|
||||
|
||||
|
||||
-- I1: lån på vej til et lager reserverer pladsen på tværs af planlægningsrunder
|
||||
r = P.plan(base({stores = {store("s", 400, {[WHEAT] = 30000})}, silos = {silo("A", 500, {[WHEAT] = 90000})},
|
||||
loans = {{siloId = "A", fillType = WHEAT, liters = 30000, storeId = "s"}}, vehicles = {vehicle("v1", 400, 50000)}}))
|
||||
check("I1 lager fyldt af lån på vej -> ingen ny vogn dertil", #r.dispatch, 0)
|
||||
r = P.plan(base({stores = {store("s", 400, {[WHEAT] = 30000}), store("t", 900, {[WHEAT] = 500000})}, silos = {silo("A", 500, {[WHEAT] = 90000})},
|
||||
loans = {{siloId = "A", fillType = WHEAT, liters = 30000, storeId = "s"}}, vehicles = {vehicle("v1", 400, 50000)}}))
|
||||
check("I1 næste vogn til det andet lager", r.dispatch[1] ~= nil and r.dispatch[1].storeId, "t")
|
||||
|
||||
-- kør til produktion: foder først, overskud til fabrik
|
||||
local function factory(id, x, free) return {id = id, kind = "production", markerId = id .. "m", x = x, z = 0, free = free} end
|
||||
local farmAndFactory = {store("farm", 5000, {[WHEAT] = 1000000}), factory("mill", 100, {[WHEAT] = 1000000})}
|
||||
r = P.plan(base({stores = farmAndFactory, silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {vehicle("v1", 400, 50000)},
|
||||
feedReserve = {[WHEAT] = 100000}, farmStock = {[WHEAT] = 20000}}))
|
||||
check("P1 reserve ikke dækket -> gårdlager selv om fabrik er nærmere", r.dispatch[1].storeId, "farm")
|
||||
r = P.plan(base({stores = farmAndFactory, silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {vehicle("v1", 400, 50000)},
|
||||
feedReserve = {[WHEAT] = 100000}, farmStock = {[WHEAT] = 150000}}))
|
||||
check("P2 reserve dækket -> fabrik", r.dispatch[1].storeId, "mill")
|
||||
r = P.plan(base({stores = farmAndFactory, silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {vehicle("v1", 400, 50000)}}))
|
||||
check("P3 ikke foder (ingen reserve) -> fabrik", r.dispatch[1].storeId, "mill")
|
||||
r = P.plan(base({stores = farmAndFactory, silos = {silo("A", 500, {[WHEAT] = 100000})}, vehicles = {vehicle("v1", 400, 50000), vehicle("v2", 400, 50000)},
|
||||
feedReserve = {[WHEAT] = 100000}, farmStock = {[WHEAT] = 70000}}))
|
||||
check("P4 første læs dækker resten af reserven -> gård", r.dispatch[1].storeId, "farm")
|
||||
check("P4 andet læs er overskud -> fabrik", r.dispatch[2].storeId, "mill")
|
||||
r = P.plan(base({stores = farmAndFactory, silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {vehicle("v1", 400, 50000)},
|
||||
feedReserve = {[WHEAT] = 100000}, farmStock = {[WHEAT] = 60000}, loans = {{siloId = "B", fillType = WHEAT, liters = 50000, storeId = "farm"}}}))
|
||||
check("P5 læs på vej til gården tæller med i reserven -> fabrik", r.dispatch[1] ~= nil and r.dispatch[1].storeId, "mill")
|
||||
r = P.plan(base({stores = {store("farm", 5000, {[WHEAT] = 1000000}), factory("mill", 100, {[CANOLA] = 1000000})}, silos = {silo("A", 500, {[WHEAT] = 90000})},
|
||||
vehicles = {vehicle("v1", 400, 50000)}, farmStock = {[WHEAT] = 999999}}))
|
||||
check("P6 ingen fabrik tager varen -> gårdlager", r.dispatch[1].storeId, "farm")
|
||||
r = P.plan(base({stores = {store("farm", 5000, {[CANOLA] = 1000000}), factory("mill", 100, {[WHEAT] = 1000000})}, silos = {silo("A", 500, {[WHEAT] = 90000})},
|
||||
vehicles = {vehicle("v1", 400, 50000)}, feedReserve = {[WHEAT] = 100000}}))
|
||||
check("P7 reserve mangler men ingen gård tager varen -> fabrik", r.dispatch[1].storeId, "mill")
|
||||
check("P7 status ok (ikke noFarmStore)", r.status[1].state, "ok")
|
||||
check("P7 status viser destination", r.status[1].toProduction, true)
|
||||
|
||||
-- fabrik kun når hele læsset kan være der (in-game 28/9: vogn med 252.000 l ventede timevis ved en fuld fabrik)
|
||||
r = P.plan(base({stores = {store("farm", 5000, {[WHEAT] = 1000000}), factory("mill", 100, {[WHEAT] = 30000})}, silos = {silo("A", 500, {[WHEAT] = 90000})},
|
||||
vehicles = {vehicle("v1", 400, 50000)}}))
|
||||
check("P8 fabrik uden plads til hele læsset -> gårdlager", r.dispatch[1].storeId, "farm")
|
||||
r = P.plan(base({stores = {store("farm", 5000, {[CANOLA] = 1000000}), factory("mill", 100, {[WHEAT] = 30000})}, silos = {silo("A", 500, {[WHEAT] = 90000})},
|
||||
vehicles = {vehicle("v1", 400, 50000)}}))
|
||||
check("P9 kun fabrik uden plads til hele læsset -> ingen afsendelse", #r.dispatch, 0)
|
||||
|
||||
-- høsten først: tømmevogne holdes tilbage til høstere der arbejder
|
||||
local vs = {vehicle("v1", 400, 50000), vehicle("v2", 400, 50000, {[CANOLA] = true}), vehicle("v3", 400, 50000)}
|
||||
local left = P.withoutHarvestReserve(vs, {{fillType = WHEAT, count = 1}})
|
||||
check("R1 én hvede-vogn holdt tilbage", #left, 2)
|
||||
check("R1 raps-vognen er fri", left[1].id == "v2" or left[2].id == "v2", true)
|
||||
left = P.withoutHarvestReserve(vs, {{fillType = WHEAT, count = 5}})
|
||||
check("R2 alle hvede-vogne holdt tilbage", #left, 1)
|
||||
check("R3 intet behov -> alle fri", #P.withoutHarvestReserve(vs, {}), 3)
|
||||
check("R4 input muteres ikke", #vs, 3)
|
||||
|
||||
-- Tom silo: lånt vogn med last står ved en tom marksilo -> resten køres videre (AD venter ellers til fuld)
|
||||
-- (in-game 28/9: Tømmevogn 2 og 4 stod ved tom F17 Unload med kartofler)
|
||||
local restState = {atSilo = true, stillMs = 45000, siloStock = 0, cargo = 12000}
|
||||
check("RS1 tom silo med last -> kør videre", P.restDue(restState), true)
|
||||
check("RS2 siloen har stadig varer -> vent", P.restDue({atSilo = true, stillMs = 45000, siloStock = 30000, cargo = 12000}), false)
|
||||
check("RS3 ikke ved siloen -> nej", P.restDue({atSilo = false, stillMs = 45000, siloStock = 0, cargo = 12000}), false)
|
||||
check("RS4 lige ankommet -> vent", P.restDue({atSilo = true, stillMs = 5000, siloStock = 0, cargo = 12000}), false)
|
||||
check("RS5 tom vogn -> nej", P.restDue({atSilo = true, stillMs = 45000, siloStock = 0, cargo = 0}), false)
|
||||
local restStores = {
|
||||
{id = "fab", kind = "production", markerId = 1, x = 10, z = 0, free = {[10] = 5000}},
|
||||
{id = "gaard", kind = "silo", markerId = 2, x = 500, z = 0, free = {[10] = 900000}},
|
||||
}
|
||||
check("RS6 fabrik uden plads til hele resten -> gårdsilo", P.restStore(restStores, {x = 0, z = 0}, 10, 12000).id, "gaard")
|
||||
check("RS7 fabrik med plads -> fabrik", P.restStore(restStores, {x = 0, z = 0}, 10, 4000).id, "fab")
|
||||
check("RS8 intet tager varen", P.restStore(restStores, {x = 0, z = 0}, 99, 4000), nil)
|
||||
-- Fuld fabrik: lånt vogn med last holder ved et lager uden plads -> resten til et andet lager (gård)
|
||||
check("SF1 fuld fabrik -> videre", P.storeFullDue({atStore = true, stillMs = 70000, cargo = 12000, storeFree = 0}), true)
|
||||
check("SF2 fabrikken tager stadig imod -> vent", P.storeFullDue({atStore = true, stillMs = 70000, cargo = 12000, storeFree = 8000}), false)
|
||||
check("SF3 lige ankommet -> vent", P.storeFullDue({atStore = true, stillMs = 20000, cargo = 12000, storeFree = 0}), false)
|
||||
check("SF4 ikke ved lageret -> nej", P.storeFullDue({atStore = false, stillMs = 70000, cargo = 12000, storeFree = 0}), false)
|
||||
local fullStores = {
|
||||
{id = "fab", kind = "production", markerId = 1, x = 10, z = 0, free = {[10] = 0}},
|
||||
{id = "gaard", kind = "silo", markerId = 2, x = 500, z = 0, free = {[10] = 900000}},
|
||||
}
|
||||
check("SF5 andet lager end det fulde", P.restStore(fullStores, {x = 0, z = 0}, 10, 12000, "fab").id, "gaard")
|
||||
check("SF6 kun det fulde tager varen -> intet", P.restStore({fullStores[1]}, {x = 0, z = 0}, 10, 12000, "fab"), nil)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,159 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldSilos.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFieldSiloPlanner.lua")
|
||||
dofile("FS25_ADSmartPickup/adFieldSiloConfig.lua")
|
||||
dofile("FS25_ADSmartPickup/adFieldSilos.lua")
|
||||
local S = ADFieldSilos
|
||||
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 loan = {startedMs = 0, lastProgressMs = 0, lastFill = 0}
|
||||
check("kører, tidligt -> behold", S.releaseReason(loan, {adActive = true, controlled = false, fill = 0, moving = true}, 5000), nil)
|
||||
check("AD slukket efter 40 s -> done", S.releaseReason(loan, {adActive = false, controlled = false, fill = 0}, 40000), "done")
|
||||
check("spiller i vognen -> player", S.releaseReason(loan, {adActive = true, controlled = true, fill = 0}, 5000), "player")
|
||||
check("står stille uden fyld i 120 s -> stuck", S.releaseReason(loan, {adActive = true, controlled = false, fill = 0, moving = false}, 120001), "stuck")
|
||||
check("kører stadig efter 120 s -> behold", S.releaseReason(loan, {adActive = true, controlled = false, fill = 0, moving = true}, 120001), nil)
|
||||
local loading = {startedMs = 0, lastProgressMs = 110000, lastFill = 0}
|
||||
check("fyldning steg for nylig -> behold", S.releaseReason(loading, {adActive = true, controlled = false, fill = 20000, moving = false}, 125000), nil)
|
||||
|
||||
-- I2/I3: vagten gælder kun før turen er kommet i gang; AD der slukker straks uden fremdrift = fejl
|
||||
local going = {startedMs = 0, lastFill = 30000, hasProgressed = true}
|
||||
check("I2 i gang, står i kø 500 s -> behold", S.releaseReason(going, {adActive = true, controlled = false, fill = 30000, moving = false}, 500000), nil)
|
||||
check("I2 aflæsning (fyld falder) er fremdrift", S.releaseReason({startedMs = 0, lastFill = 30000}, {adActive = true, controlled = false, fill = 20000, moving = false}, 500000), nil)
|
||||
check("I3 AD slukket efter 10 s uden fremdrift -> failed", S.releaseReason({startedMs = 0, lastFill = 0}, {adActive = false, controlled = false, fill = 0}, 10000), "failed")
|
||||
check("I3 AD slukket efter 10 s med fremdrift -> done", S.releaseReason({startedMs = 0, lastFill = 0, hasProgressed = true}, {adActive = false, controlled = false, fill = 0}, 10000), "done")
|
||||
|
||||
-- Røgtest af spil-laget med mocks: planNow låner nærmeste tømmevogn, isLoaned = optaget,
|
||||
-- frakobling (setConfig enabled=false) stopper AD og frigiver (Review Focus).
|
||||
do
|
||||
local WHEAT = 1
|
||||
Logging = {info = function() end, warning = function() end}
|
||||
g_currentMission = {getFarmId = function() return 1 end}
|
||||
g_fillTypeManager = {getFillTypeByIndex = function() return {title = "Hvede"} end}
|
||||
function getWorldTranslation(node) return node.x, 0, node.z end
|
||||
ADRuns = {vehicleLabel = function(v) return v.name end}
|
||||
local function storage(levels, free) return {fillLevels = levels, fillTypes = {[WHEAT] = true}, getFreeCapacity = function() return free end} end
|
||||
local fieldSilo = {id = "fs", name = "Marksilo 1", kind = "silo", placeable = {rootNode = {x = 1000, z = 0}, spec_silo = {storages = {storage({[WHEAT] = 60000}, 0)}}}}
|
||||
local farm = {id = "farm", name = "Gårdsilo", kind = "silo", placeable = {rootNode = {x = 0, z = 0}, spec_silo = {storages = {storage({}, 500000)}}}}
|
||||
ADBuildings = {list = function() return {fieldSilo, farm} end,
|
||||
getMarkers = function(b, role) return {{id = b.id .. "-" .. role}} end}
|
||||
ADFields = {list = function() return {{id = 1}} end, fieldSilos = function() return {fieldSilo, farm} end}
|
||||
local started, settingsSet, noPark, noRoute, residue = {}, {}, {}, {}, 0
|
||||
ADRunsController = {store = {runs = {}}, setSetting = function(_, v, name, value) settingsSet[v] = settingsSet[v] or {}; settingsSet[v][name] = value end}
|
||||
ADUnloadWait = {hasRouteTo = function(_, _, id) return not noRoute[id] end}
|
||||
local function makeVehicle(name, x)
|
||||
local module = {active = false}
|
||||
function module:setMode() end
|
||||
function module:setFirstMarkerByWayPointId(id) self.first = id end
|
||||
function module:setSecondMarkerByWayPointId(id) self.second = id end
|
||||
function module:setFillType() end
|
||||
function module:setLoopsDone() end
|
||||
function module:raiseDirtyFlag() end
|
||||
function module:isActive() return self.active end
|
||||
return {name = name, x = x, lastSpeedReal = 0, ad = {stateModule = module},
|
||||
stopAutoDrive = function(self) self.ad.stateModule.active = false; self.stopped = true end}
|
||||
end
|
||||
local near, far = makeVehicle("Nær", 900), makeVehicle("Fjern", 5000)
|
||||
local adEnv = {AutoDrive = {MODE_PICKUPANDDELIVER = 2, RT_NONE = 0,
|
||||
getAllDischargeableUnits = function() return {{fillUnitIndex = 1, object = {
|
||||
getFillUnitCapacity = function() return 40000 end,
|
||||
getFillUnitSupportedFillTypes = function() return {[WHEAT] = true} end,
|
||||
getFillUnitFillLevel = function() return residue end, getFillUnitFillType = function() return WHEAT end}}} end,
|
||||
getAllUnits = function() return {} end, getAllFillLevels = function() return 0 end}}
|
||||
ADFieldWork = {
|
||||
getRigs = function()
|
||||
local rigs = {}
|
||||
-- som spillets vehicleSystem: slettede køretøjer er ikke med
|
||||
if not near.isDeleted then table.insert(rigs, {id = "n", vehicle = near, role = "unloader", enabled = true, busy = S.isLoaned(near), controlled = false, x = 900, z = 0}) end
|
||||
if not far.isDeleted then table.insert(rigs, {id = "f", vehicle = far, role = "unloader", enabled = true, busy = S.isLoaned(far), controlled = false, x = 5000, z = 0}) end
|
||||
table.insert(rigs, {id = "h", vehicle = far, role = "harvester", enabled = true, busy = false, controlled = false, x = 1000, z = 0})
|
||||
return rigs
|
||||
end,
|
||||
enableParkAtJobFinished = function() end,
|
||||
parkMarkerId = function(_, v) if noPark[v] then return nil end return 777 end,
|
||||
startAd = function(_, v, _, first, second, fillType) v.ad.stateModule.active = true; table.insert(started, {v = v, first = first, second = second, fillType = fillType}) end,
|
||||
}
|
||||
S.setConfig(ADFieldSiloConfig.with(ADFieldSiloConfig.toggleStore(ADFieldSiloConfig.new(), "farm"), {enabled = true, maxVehicles = 1}))
|
||||
local plan = S.planNow(adEnv)
|
||||
check("RØG én afsendelse (x=1)", #started, 1)
|
||||
check("RØG nærmeste tømmevogn", started[1] ~= nil and started[1].v.name, "Nær")
|
||||
check("RØG fra marksilo-markør", started[1] ~= nil and started[1].first, "fs-load")
|
||||
check("RØG til gårdlager-markør", started[1] ~= nil and started[1].second, "farm-unload")
|
||||
check("RØG varen med", started[1] ~= nil and started[1].fillType, WHEAT)
|
||||
check("RØG gårdlager er ikke marksilo", #plan.status, 1)
|
||||
check("RØG vognen er lånt", S.isLoaned(near), true)
|
||||
check("RØG x nået -> ingen flere", #S.planNow(adEnv).dispatch, 0)
|
||||
check("I4 rotation slået fra på lånt vogn", settingsSet[near] ~= nil and settingsSet[near].rotateTargets, 0)
|
||||
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = false}))
|
||||
check("RØG frakobling stopper AD", near.stopped, true)
|
||||
check("RØG frakobling frigiver", S.isLoaned(near), false)
|
||||
|
||||
-- I5: vogn uden parkeringspunkt lånes ikke (ville blive holdende på lagerets markør)
|
||||
started = {}
|
||||
noPark[near] = true
|
||||
near.stopped = nil
|
||||
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = true}))
|
||||
S.planNow(adEnv)
|
||||
check("I5 uden parkering -> fjern vogn med parkering", started[1] ~= nil and started[1].v.name, "Fjern")
|
||||
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = false}))
|
||||
noPark[near] = nil
|
||||
|
||||
-- I3: ingen rute til siloen -> ingen afsendelse
|
||||
started = {}
|
||||
noRoute["fs-load"] = true
|
||||
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = true}))
|
||||
S.planNow(adEnv)
|
||||
check("I3 ingen rute -> ingen afsendelse", #started, 0)
|
||||
noRoute["fs-load"] = nil
|
||||
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = false}))
|
||||
|
||||
-- I6: silo der er kilde/mål i et flow er ikke marksilo
|
||||
ADRunsController.store.runs = {{kind = "supply", sourceBuildingIds = {}, targetBuildingIds = {"fs"}, storeBuildingIds = {}}}
|
||||
local silos = S.collectBuildings(adEnv)
|
||||
check("I6 silo i et flow tømmes ikke", #silos, 0)
|
||||
ADRunsController.store.runs = {}
|
||||
|
||||
-- in-game 27/9: marksilo-flowet flyttede sukkerroer fra en Farma-silo til nabo-Farma-siloen (samme lagerklynge)
|
||||
local neighbour = {id = "nb", name = "Nabo-Farma", kind = "silo", placeable = {rootNode = {x = 40, z = 0}, spec_silo = {storages = {storage({[WHEAT] = 60000}, 0)}}}}
|
||||
ADFields.fieldSilos = function() return {fieldSilo, farm, neighbour} end
|
||||
local withNeighbour = S.collectBuildings(adEnv)
|
||||
local names = {}
|
||||
for _, silo in ipairs(withNeighbour) do table.insert(names, silo.id) end
|
||||
check("gårdens nabosilo (40 m fra gårdlager) er ikke marksilo", table.concat(names, ","), "fs")
|
||||
ADFields.fieldSilos = function() return {fieldSilo, farm} end
|
||||
|
||||
-- in-game 27/9: gårdens Farma-klynge er 300 m lang — siloer over 100 m fra et valgt gårdlager blev marksiloer.
|
||||
-- En silo i samme klynge (≤ 60 m til en nabo) som et gårdlager er gårdens.
|
||||
local c1 = {id = "c1", name = "Klynge 1", kind = "silo", placeable = {rootNode = {x = 50, z = 0}, spec_silo = {storages = {storage({[WHEAT] = 60000}, 0)}}}}
|
||||
local c2 = {id = "c2", name = "Klynge 2", kind = "silo", placeable = {rootNode = {x = 105, z = 0}, spec_silo = {storages = {storage({[WHEAT] = 60000}, 0)}}}}
|
||||
ADBuildings.list = function() return {fieldSilo, farm, c1, c2} end
|
||||
ADFields.fieldSilos = function() return {fieldSilo, farm, c1, c2} end
|
||||
local clustered = {}
|
||||
for _, silo in ipairs(S.collectBuildings(adEnv)) do table.insert(clustered, silo.id) end
|
||||
check("gårdens klynge (105 m via nabo) er ikke marksilo", table.concat(clustered, ","), "fs")
|
||||
ADBuildings.list = function() return {fieldSilo, farm} end
|
||||
ADFields.fieldSilos = function() return {fieldSilo, farm} end
|
||||
|
||||
-- M5: rest under 100 l tæller ikke som last
|
||||
residue = 50
|
||||
local vehicles = S.collectVehicles(adEnv)
|
||||
check("M5 rest på 50 l er ikke last", vehicles[1].cargo, nil)
|
||||
residue = 0
|
||||
|
||||
-- M4: slettet lånt vogn fjernes uden at vælte tick
|
||||
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = true}))
|
||||
started = {}
|
||||
S.planNow(adEnv)
|
||||
local loaned = started[1] ~= nil and started[1].v or nil
|
||||
if loaned ~= nil then loaned.isDeleted = true end
|
||||
local okTick = pcall(S.tick, adEnv, 2000)
|
||||
check("M4 tick tåler slettet vogn", okTick, true)
|
||||
check("M4 slettet vogn frigivet", loaned ~= nil and S.isLoaned(loaned), false)
|
||||
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = false}))
|
||||
end
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,110 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldStorage.lua (fra repo-roden)
|
||||
-- Lagringen skal dække HVERT felt modellen gemmer (FIELD_TYPES), og en mark skal overleve en tur gennem XML.
|
||||
dofile("FS25_ADSmartPickup/adFieldModel.lua")
|
||||
dofile("FS25_ADSmartPickup/adFieldStorage.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
|
||||
|
||||
-- falsk XMLFile: gemmer værdier pr. nøgle
|
||||
local function fakeXml()
|
||||
local values = {}
|
||||
local xml = {}
|
||||
for _, kind in ipairs({"Int", "String", "Float"}) do
|
||||
xml["set" .. kind] = function(_, key, value) values[key] = value end
|
||||
xml["get" .. kind] = function(_, key) return values[key] end
|
||||
end
|
||||
return xml
|
||||
end
|
||||
|
||||
local field = ADFieldModel.with(ADFieldModel.startCare(ADFieldModel.newField(77), "weeding"), {
|
||||
kind = "harvest", lastFruit = "WHEAT", mowWidth = 40.8, mowVehicleId = "v1", rakeVehicleId = "v2", windrowOffset = -2.5,
|
||||
plan = {mode = "auto", product = "harvest", rake = true, storeBuildingIds = {"b1"}, rigs = 3, unloaders = 2,
|
||||
mulch = true, lime = false, soil = "plow", sow = true, sowFruit = "CANOLA", roll = true, fertilize = false, weed = false},
|
||||
})
|
||||
local record = ADFieldModel.toRecord(field)
|
||||
for key in pairs(record) do
|
||||
check("ST1 felt med i lagringen: " .. key, ADFieldStorage.FIELD_TYPES[key] ~= nil, true)
|
||||
end
|
||||
local xml = fakeXml()
|
||||
ADFieldStorage.writeRecord(xml, "k", record)
|
||||
local back = ADFieldModel.fromRecord(ADFieldStorage.readRecord(xml, "k"))
|
||||
local again = ADFieldModel.toRecord(back)
|
||||
for key, value in pairs(record) do
|
||||
check("ST2 rundtur: " .. key, tostring(again[key]), tostring(value))
|
||||
end
|
||||
|
||||
-- ST3-ST6: kursus-registret (genbrug af Courseplay-kurser) gemmes i samme fil, <courses><course .../></courses>
|
||||
dofile("FS25_ADSmartPickup/adCourseCache.lua")
|
||||
local course = ADCourseCache.newRecord({vehicle = "v7", field = 22, width = 30.0, sig = "n=4;a=960000;c=800,300",
|
||||
settings = ADCourseCache.settingsKey({multiTools = 2, lane = -1}), waypoints = 812})
|
||||
for key in pairs(course) do
|
||||
check("ST3 kursus-felt med i lagringen: " .. key, ADFieldStorage.COURSE_TYPES[key] ~= nil, true)
|
||||
end
|
||||
local cxml = fakeXml()
|
||||
ADFieldStorage.writeCourseRecord(cxml, "c", course)
|
||||
local cback = ADFieldStorage.readCourseRecord(cxml, "c")
|
||||
for key, value in pairs(course) do
|
||||
check("ST4 kursus-rundtur: " .. key, tostring(cback[key]), tostring(value))
|
||||
end
|
||||
check("ST4b matcher stadig efter rundtur", ADCourseCache.matches(cback, {field = 22, width = 30, sig = course.sig, settings = course.settings}), true)
|
||||
|
||||
-- hel fil: save -> load med falsk XMLFile (iterate over indekserede nøgler)
|
||||
local function fileXml(store)
|
||||
local xml = fakeXml()
|
||||
for _, kind in ipairs({"Int", "String", "Float"}) do
|
||||
xml["set" .. kind] = function(_, key, value) store[key] = value end
|
||||
xml["get" .. kind] = function(_, key) return store[key] end
|
||||
end
|
||||
function xml.iterate(_, base, fn)
|
||||
local index = 0
|
||||
while true do
|
||||
local prefix = string.format("%s(%d)", base, index)
|
||||
local found = false
|
||||
for key in pairs(store) do
|
||||
if key:sub(1, #prefix + 1) == prefix .. "#" or key:sub(1, #prefix + 1) == prefix .. "." then found = true break end
|
||||
end
|
||||
if not found then return end
|
||||
fn(index + 1, prefix)
|
||||
index = index + 1
|
||||
end
|
||||
end
|
||||
function xml.save() end
|
||||
function xml.delete() end
|
||||
return xml
|
||||
end
|
||||
local disk = {}
|
||||
ADRunsStorage = {FILE_NAME = "runs.xml", getPath = function() return "/save/runs.xml" end}
|
||||
XMLFile = {create = function() return fileXml(disk) end, load = function() return fileXml(disk) end}
|
||||
function fileExists() return true end
|
||||
check("ST5 gem", ADFieldStorage.save({fields = {[77] = field}, fleet = {}, fleetMode = "all",
|
||||
courseRecords = ADCourseCache.withRecord({}, course)}), true)
|
||||
local loaded = ADFieldStorage.load()
|
||||
check("ST6 kursus indlæst", loaded.courseRecords.v7 ~= nil and loaded.courseRecords.v7.waypoints, 812)
|
||||
check("ST6b mark indlæst", loaded.fields[77] ~= nil, true)
|
||||
-- ST7: gammel fil uden <courses> -> tomt register (bagudkompatibelt)
|
||||
disk = {}
|
||||
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)
|
||||
|
||||
-- ST9: pause ("Stop alt og parkér") gemmes, så intet starter af sig selv efter indlæsning
|
||||
disk = {}
|
||||
ADFieldStorage.save({fields = {}, fleet = {}, fleetMode = "all", paused = true})
|
||||
check("ST9 pause indlæst", ADFieldStorage.load().paused, true)
|
||||
disk = {}
|
||||
ADFieldStorage.save({fields = {}, fleet = {}, fleetMode = "all"})
|
||||
check("ST9 ingen pause", ADFieldStorage.load().paused, false)
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,66 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldView.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFieldModel.lua")
|
||||
dofile("FS25_ADSmartPickup/adFieldView.lua")
|
||||
local V = ADFieldView
|
||||
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 infos = {
|
||||
{id = 109, mowable = true, markerId = 5},
|
||||
{id = 12, mowable = true, markerId = nil},
|
||||
{id = 90, mowable = false, markerId = 7},
|
||||
}
|
||||
local fields = {[109] = ADFieldModel.newField(109), [12] = ADFieldModel.newField(12)}
|
||||
|
||||
-- V1: alle marker (etape 2: også høst og bare), sorteret efter nummer
|
||||
local rows = V.rows(infos, fields)
|
||||
check("V1 tre rækker", #rows, 3)
|
||||
check("V1 sorteret", rows[1].info.id, 12)
|
||||
check("V1 model med", rows[3].field.id, 109)
|
||||
-- V2: mangler model -> ny standardmark
|
||||
check("V2 standardmodel", rows[2].field.stage, "idle")
|
||||
-- V3: status — manglende markør vinder over trin
|
||||
check("V3 mangler markør", V.statusKey(fields[12], infos[2]), "spu_field_noMarker")
|
||||
check("V3 trin", V.statusKey(fields[109], infos[1]), "spu_field_stage_idle")
|
||||
|
||||
-- V4: planens tekstnøgler pr. markart
|
||||
check("V4 ensilage", V.planKey({product = "auto"}, "mowable"), "spu_field_plan_silage")
|
||||
check("V4 hø", V.planKey({product = "hay"}, "mowable"), "spu_field_plan_hay")
|
||||
check("V4 høst", V.planKey({product = "auto"}, "harvest"), "spu_field_plan_harvest")
|
||||
check("V4 bar mark", V.planKey({product = "auto"}, "bare"), "spu_field_plan_prep")
|
||||
-- V5: jordtilstand som korte tekstdele
|
||||
local parts = V.soilParts({lime = 0.2, fert = 1, fertMax = 2, weed = 3, needs = {lime = true, plow = true}})
|
||||
check("V5 dele", table.concat(parts, ","), "lime,plow,fert,weed")
|
||||
check("V5 intet", #V.soilParts({lime = 2, fert = 2, fertMax = 2, weed = 0, needs = {}}), 0)
|
||||
|
||||
-- cropText: høstet = stub (afgrøde), bar = bar jord, ellers afgrøden
|
||||
local texts = {spu_field_stubble = "stub (%s)", spu_field_bare = "bar jord"}
|
||||
local function T(key) return texts[key] end
|
||||
check("FVcrop stub", ADFieldView.cropText({kind = "bare", stubbleOf = "Hvede", cropTitle = "Hvede"}, T), "stub (Hvede)")
|
||||
check("FVcrop bar", ADFieldView.cropText({kind = "bare"}, T), "bar jord")
|
||||
check("FVcrop afgrøde", ADFieldView.cropText({kind = "harvest", cropTitle = "Majs"}, T), "Majs")
|
||||
|
||||
-- FVstore: marksilo først, gæringssilo, så afstand; valgte vises altid; kun de nærmeste af resten
|
||||
local stores = {
|
||||
{id = "fjern", distance = 3000},
|
||||
{id = "nær", distance = 200},
|
||||
{id = "mark", distance = 90, fieldSilo = true},
|
||||
{id = "valgt", distance = 5000, chosen = true},
|
||||
{id = "gær", distance = 800, fermenter = true},
|
||||
{id = "midt", distance = 1200},
|
||||
}
|
||||
local storeRows, hiddenStores = ADFieldView.storeRows(stores, 2)
|
||||
local order = {}
|
||||
for _, e in ipairs(storeRows) do table.insert(order, e.id) end
|
||||
check("FVstore rækkefølge", table.concat(order, ","), "mark,gær,nær,midt,valgt")
|
||||
check("FVstore skjulte", hiddenStores, 1)
|
||||
check("FVstore meter", ADFieldView.distanceText(90.4), "90 m")
|
||||
check("FVstore km", ADFieldView.distanceText(1420), "1,4 km")
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,22 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldWorkBuying.lua (fra repo-roden)
|
||||
-- Påfyldning ved købestation (in-game 26/9: "Liquid Buy Station", FarmFillStations buyingStationConfigurable):
|
||||
-- ingen lagerbeholdning, så varen læses fra udbud/triggere/AI-varer.
|
||||
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
|
||||
Logging = {info = function() end, warning = function() end}
|
||||
dofile("FS25_ADSmartPickup/adFieldWork.lua")
|
||||
local HERBICIDE, LIME, SEEDS = 20, 21, 22
|
||||
local W = ADFieldWork
|
||||
check("BS1 udbudte varer", W.buyingSupports({providedFillTypes = {[HERBICIDE] = true}}, HERBICIDE), true)
|
||||
check("BS2 triggerens varer", W.buyingSupports({loadTriggers = {{fillTypes = {[HERBICIDE] = true}}}}, HERBICIDE), true)
|
||||
check("BS3 AI-varer", W.buyingSupports({getAISupportedFillTypes = function() return {[SEEDS] = true} end}, SEEDS), true)
|
||||
check("BS4 sælger ikke kalk", W.buyingSupports({providedFillTypes = {[HERBICIDE] = true}}, LIME), false)
|
||||
check("BS5 fejl i opslag = nej", W.buyingSupports({getAISupportedFillTypes = function() error("x") end}, LIME), false)
|
||||
check("BS6 ingen station", W.buyingSupports(nil, LIME), false)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,98 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFields.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFields.lua")
|
||||
local F = ADFields
|
||||
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
|
||||
|
||||
-- F1/F2: markørnavne
|
||||
check("F1 F109", F.parseMarkerName("F109"), 109)
|
||||
check("F1 småt", F.parseMarkerName("f7"), 7)
|
||||
check("F1 mellemrum", F.parseMarkerName(" F 12 "), 12)
|
||||
check("F2 andet navn", F.parseMarkerName("Field 109"), nil)
|
||||
check("F2 tekst efter", F.parseMarkerName("F109 wait"), nil)
|
||||
check("F2 nil", F.parseMarkerName(nil), nil)
|
||||
|
||||
-- F3: slåafgrøde = MOWER giver et skår som TEDDER gør til DRY…; halm udelukket
|
||||
local names = {[10] = "GRASS_WINDROW", [11] = "DRYGRASS_WINDROW", [20] = "STRAW", [30] = "CLOVER_WINDROW", [31] = "DRYCLOVER_WINDROW", [40] = "BEANSTRAW"}
|
||||
local mower = {[1] = {fillTypeIndex = 10}, [2] = {fillTypeIndex = 20}, [3] = {fillTypeIndex = 30}, [4] = {fillTypeIndex = 40}}
|
||||
local tedder = {[10] = {targetFillTypeIndex = 11}, [30] = {targetFillTypeIndex = 31}, [40] = {targetFillTypeIndex = 20}}
|
||||
local mowable = F.buildMowable(mower, tedder, function(index) return names[index] end)
|
||||
check("F3 græs", mowable[1] ~= nil, true)
|
||||
check("F3 græs skår", mowable[1].windrow, 10)
|
||||
check("F3 græs hø", mowable[1].hay, 11)
|
||||
check("F3 rug (halm) ikke", mowable[2], nil)
|
||||
check("F3 kløver", mowable[3].hay, 31)
|
||||
check("F3 bønnehalm -> halm ikke", mowable[4], nil)
|
||||
|
||||
-- F4: markens art og om den vokser
|
||||
local kind, growing = F.classify(3, false, 0.4)
|
||||
check("F4 høstafgrøde", kind, "harvest"); check("F4 vokser", growing, true)
|
||||
check("F4 klar høst vokser ikke", select(2, F.classify(3, false, 0.95)), false)
|
||||
check("F4 slåafgrøde", (F.classify(1, true, 0.2)), "mowable")
|
||||
check("F4 ingen afgrøde = bar", (F.classify(nil, false, 0)), "bare")
|
||||
check("F4 bar vokser ikke", select(2, F.classify(nil, false, 0)), false)
|
||||
|
||||
-- F5: jordens behov ud fra gennemsnit og spillets indstillinger
|
||||
local needs = F.soilNeeds({lime = 0.2, plowLevel = 0}, {limeRequired = true, plowingRequired = true})
|
||||
check("F5 kalk", needs.lime, true); check("F5 pløj", needs.plow, true)
|
||||
local off = F.soilNeeds({lime = 0, plowLevel = 0}, {limeRequired = false, plowingRequired = false})
|
||||
check("F5 kalk slået fra i spillet", off.lime, false); check("F5 pløjning slået fra", off.plow, false)
|
||||
check("F5 kalket jord", F.soilNeeds({lime = 2, plowLevel = 1}, {limeRequired = true, plowingRequired = true}).lime, false)
|
||||
check("F5 ukendt niveau = intet behov", F.soilNeeds({}, {limeRequired = true, plowingRequired = true}).plow, false)
|
||||
|
||||
-- F6: vækststadie -> fase ud fra afgrødens egne grænser
|
||||
local wheat = {minHarvestingGrowthState = 6, maxHarvestingGrowthState = 7, cutState = 9, witheredState = 8}
|
||||
check("F6 intet", F.growthPhase(0, wheat), "none")
|
||||
check("F6 spirer/vokser", F.growthPhase(3, wheat), "growing")
|
||||
check("F6 klar", F.growthPhase(6, wheat), "ready")
|
||||
check("F6 visnet", F.growthPhase(8, wheat), "withered")
|
||||
check("F6 høstet stub", F.growthPhase(9, wheat), "cut")
|
||||
check("F6 ukendt stadie", F.growthPhase(nil, wheat), nil)
|
||||
check("F6 FS25-navne (minHarvestGrowthState)", F.growthPhase(6, {minHarvestGrowthState = 5, maxHarvestGrowthState = 6, cutState = 8}), "ready")
|
||||
-- F7: vokser = fase growing; ukendt fase -> voksende pixels og ikke klar
|
||||
check("F7 fase vinder", F.isGrowing("cut", 100, 0.1), false)
|
||||
check("F7 fase vokser", F.isGrowing("growing", 0, 0), true)
|
||||
check("F7 reserve: voksende pixels", F.isGrowing(nil, 50, 0.2), true)
|
||||
check("F7 reserve: ingen pixels (stub)", F.isGrowing(nil, 0, 0), false)
|
||||
check("F7 reserve: klar", F.isGrowing(nil, 50, 0.95), false)
|
||||
|
||||
-- F8: slåafgrøde fuldt udvokset (Dakota-græs 3 = mellemhøjde, 4-5 = fuld)
|
||||
check("F8 prime græs", F.primeState({minHarvestingGrowthState = 3, maxHarvestingGrowthState = 5}), 4)
|
||||
check("F8 prime ét klar-stadie", F.primeState({minHarvestingGrowthState = 6, maxHarvestingGrowthState = 6}), 6)
|
||||
check("F8 prime ukendt", F.primeState({}), nil)
|
||||
check("F8 mellemhøjde tæller ikke", F.isPrime(3, 4), false)
|
||||
check("F8 fuld højde tæller", F.isPrime(5, 4), true)
|
||||
check("F8 ukendt stadie tæller", F.isPrime(nil, 4), true)
|
||||
check("F8 ingen krav", F.isPrime(3, nil), true)
|
||||
|
||||
-- F9: kartofler (Dakota): 6 = klar men kun forberedelig, 10 = toppet (høstklar)
|
||||
local potato = {minHarvestingGrowthState = 10, maxHarvestingGrowthState = 10, minPreparingGrowthState = 6, maxPreparingGrowthState = 6, witheredState = 7, cutState = 8}
|
||||
check("F9 utoppet er forberedelig", F.isPreparable(6, potato), true)
|
||||
check("F9 voksende ikke", F.isPreparable(5, potato), false)
|
||||
check("F9 fase klar", F.growthPhase(6, potato), "ready")
|
||||
check("F9 toppet klar", F.growthPhase(10, potato), "ready")
|
||||
check("F9 ingen forberedelse", F.isPreparable(6, {minPreparingGrowthState = -1}), false)
|
||||
|
||||
-- jordtype -> klargjort/pløjet (værdierne kommer fra spillets fieldGroundSystem)
|
||||
local groundValues = {STUBBLE_TILLAGE = 1, CULTIVATED = 2, SEEDBED = 3, PLOWED = 4, ROLLED_SEEDBED = 5, SOWN = 6}
|
||||
local function cls(v) local p, pl = ADFields.classifyGround(v, groundValues) return tostring(p) .. "|" .. tostring(pl) end
|
||||
check("jord: pløjet", cls(4), "true|true")
|
||||
check("jord: kultiveret", cls(2), "true|false")
|
||||
check("jord: stubbearbejdet", cls(1), "true|false")
|
||||
check("jord: sået", cls(6), "false|false")
|
||||
check("jord: ukendt værdi", cls(nil), "nil|nil")
|
||||
groundValues.NONE = 0
|
||||
check("jord: ingen markjord tæller ikke med", cls(0), "nil|nil")
|
||||
-- vin/oliven er aldrig slåafgrøder, selv om et mod har sat dem på MOWER-listen (in-game 29/9: F22 hvide druer fik
|
||||
-- planen "Silage + så" og en skårlægger sendt ud)
|
||||
check("vin: druer ikke slåbare", ADFields.isMowableFruit("GRAPE", {windrow = "GRASS_WINDROW"}), false)
|
||||
check("vin: oliven ikke slåbare", ADFields.isMowableFruit("oliveb", {windrow = "X"}), false)
|
||||
check("græs slåbart", ADFields.isMowableFruit("GRASS", {windrow = "GRASS_WINDROW"}), true)
|
||||
check("ikke på MOWER-listen", ADFields.isMowableFruit("WHEAT", nil), false)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,99 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldsPolygon.lua (fra repo-roden)
|
||||
-- FS25-marker har ingen polygonPoints — omridset hentes med field:getDensityMapPolygon():getVerticesList().
|
||||
-- In-game 26/9 (Dakota 16x, marker på 1,7 x 0,6 km): uden omrids blev kun markskiltet (posX/posZ) prøvet;
|
||||
-- F108 med majs viste "bar jord" og "mangler markør" (markøren stod ved kanten, 890 m fra skiltet).
|
||||
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
|
||||
Logging = {warning = function() end, info = function() end}
|
||||
|
||||
local MAIZE = 10
|
||||
function getWorldTranslation() return 0, 0, 0 end
|
||||
function getTerrainHeightAtWorldPos() return 0 end
|
||||
-- markskiltet står i marken, men ved en stribe uden afgrøde (x > 1400)
|
||||
function cropAt(x) return x <= 1400 and MAIZE or nil end
|
||||
FSDensityMapUtil = {
|
||||
getFieldDataAtWorldPosition = function() return 1, 2 end,
|
||||
getFruitTypeIndexAtWorldPos = function(x, z) return cropAt(x) end,
|
||||
getFruitArea = function() return 0, 10 end,
|
||||
}
|
||||
FieldDensityMap = {SPRAY_LEVEL = 4, LIME_LEVEL = 5, PLOW_LEVEL = 6}
|
||||
DensityCoordType = {POINT_VECTOR_VECTOR = 1}
|
||||
DensityMapModifier = {new = function() return {setParallelogramWorldCoords = function() end, executeGet = function() return 1, 1 end} end}
|
||||
g_fruitTypeManager = {
|
||||
getFruitTypeByIndex = function() return {name = "MAIZE", fillType = {index = 60, title = "Majs"}, minHarvestingGrowthState = 7, maxHarvestingGrowthState = 7, cutState = 9} end,
|
||||
getConverterDataByName = function() return {} end,
|
||||
}
|
||||
g_fillTypeManager = {getConverterDataByName = function() return {} end, getFillTypeNameByIndex = function() return "X" end}
|
||||
g_farmlandManager = {getFarmlandOwner = function() return 1 end}
|
||||
local polygon = {getVerticesList = function() return {0, 0, 1600, 0, 1600, 600, 0, 600} end}
|
||||
local field = {getId = function() return 108 end, farmland = {id = 108}, posX = 1500, posZ = 50,
|
||||
getDensityMapPolygon = function() return polygon end}
|
||||
g_fieldManager = {getFields = function() return {field} end}
|
||||
g_currentMission = {terrainRootNode = 1, missionInfo = {}, fieldGroundSystem = {
|
||||
getDensityMapData = function() return 1, 0, 2 end, getMaxValue = function() return 2 end}}
|
||||
local adEnv = {ADGraphManager = {
|
||||
getMapMarkers = function() return {{id = 900, name = "F108"}} end,
|
||||
-- markøren står 5 m uden for marken ved den modsatte kant: 1505 m fra skiltet
|
||||
getWayPointById = function() return {x = -5, z = 300} end,
|
||||
}}
|
||||
|
||||
dofile("FS25_ADSmartPickup/adFields.lua")
|
||||
local now = 0
|
||||
for _ = 1, 1000 do
|
||||
if ADFields.getInfo(108) ~= nil then break end
|
||||
ADFields.scanStep(adEnv, 1, now)
|
||||
now = now + 16
|
||||
end
|
||||
local info = ADFields.getInfo(108)
|
||||
check("PG1 marken er scannet", info ~= nil, true)
|
||||
check("PG2 omrids fra getDensityMapPolygon", info and #info.verts, 4)
|
||||
check("PG3 majsen findes trods skiltet på bar stribe", info and info.fruitName, "MAIZE")
|
||||
check("PG4 ikke bar mark", info and info.kind, "harvest")
|
||||
check("PG5 markør ved kanten findes (afstand til omridset)", info and info.markerId, 900)
|
||||
check("PG6 afstand = til kanten", info and info.markerDistance, 5)
|
||||
check("PG6b markens form-signatur (kursus-genbrug)", info and info.shapeSig, "n=4;a=960000;c=800,300")
|
||||
|
||||
-- PG7-9: field.fieldState er spillets sandhed (fields.xml: MAIZE growthState 2); jord-bits fra
|
||||
-- getFieldDataAtWorldPosition (2. værdi) er IKKE vækststadie. Høstet mark (cutState) = bar, vist som stub.
|
||||
|
||||
field.fieldState = {fruitTypeIndex = MAIZE, growthState = 2}
|
||||
FSDensityMapUtil.getFieldDataAtWorldPosition = function() return true, 0 end -- jord-bits 0 lignede "ingen vækst"
|
||||
local function rescan()
|
||||
ADFields.cache, ADFields.listCache, ADFields.activeScan, ADFields.lastScanStart = {}, nil, nil, nil
|
||||
for _ = 1, 2000 do
|
||||
if ADFields.getInfo(108) ~= nil then break end
|
||||
ADFields.scanStep(adEnv, 1, now)
|
||||
now = now + 16
|
||||
end
|
||||
return ADFields.getInfo(108)
|
||||
end
|
||||
info = rescan()
|
||||
check("PG7 majs i vækst (fieldState), ikke bar", info and info.kind, "harvest")
|
||||
check("PG7 fase vokser", info and info.phase, "growing")
|
||||
field.fieldState = {fruitTypeIndex = MAIZE, growthState = 9}
|
||||
info = rescan()
|
||||
check("PG8 høstet (cutState) = bar", info and info.kind, "bare")
|
||||
check("PG8 stub af majs", info and info.stubbleOf, "Majs")
|
||||
field.fieldState = {fruitTypeIndex = 0, growthState = 0}
|
||||
cropAt = function() return nil end
|
||||
info = rescan()
|
||||
check("PG9 ingen afgrøde = bar uden stub", info and info.kind .. "/" .. tostring(info.stubbleOf), "bare/nil")
|
||||
-- PG10-12: bygget-over mark (fields.xml: groundType NONE — gårdsplads/stald/biogas) er ikke med i listen
|
||||
field.fieldState = {fruitTypeIndex = 0, growthState = 0, groundType = 0}
|
||||
info = rescan()
|
||||
check("PG10 ingen markjord = kan ikke dyrkes", info and info.cultivable, false)
|
||||
check("PG10 ikke i marklisten", #ADFields.list(), 0)
|
||||
field.fieldState = {fruitTypeIndex = MAIZE, growthState = 2, groundType = 7}
|
||||
cropAt = function(x) return MAIZE end
|
||||
info = rescan()
|
||||
check("PG11 markjord = kan dyrkes", info and info.cultivable, true)
|
||||
check("PG11 i marklisten", #ADFields.list(), 1)
|
||||
check("PG12 ingen punkter på markjord", ADFields.isCultivable(nil, 0), false)
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,94 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldsScan.lua (fra repo-roden)
|
||||
-- Markscanningen som arbejdskø: bliver færdig, giver rigtigt resultat, og intet frame laver mere end ét
|
||||
-- prøvepunkts arbejde (in-game 26/9: en hel mark i ét frame gav hak).
|
||||
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
|
||||
Logging = {warning = function(fmt, ...) print("WARN " .. string.format(fmt, ...)) end, info = function() end}
|
||||
|
||||
-- falsk verden: én mark (F7) med hvede der er klar, polygon med 4 hjørner
|
||||
local GRASS, WHEAT = 1, 3
|
||||
local nodes = {[101] = {0, 0}, [102] = {100, 0}, [103] = {100, 100}, [104] = {0, 100}}
|
||||
function getWorldTranslation(node) local p = nodes[node]; return p[1], 0, p[2] end
|
||||
function getTerrainHeightAtWorldPos() return 0 end
|
||||
local calls = 0
|
||||
FSDensityMapUtil = {
|
||||
getFieldDataAtWorldPosition = function() calls = calls + 1; return true, 170 end, -- på mark (2. værdi = jord-bits, IKKE vækst)
|
||||
getFruitTypeIndexAtWorldPos = function() calls = calls + 1; return WHEAT end,
|
||||
getFruitArea = function(_, _, _, _, _, _, _, any) calls = calls + 1; return 0, any and 10 or 10 end,
|
||||
}
|
||||
FieldDensityMap = {SPRAY_LEVEL = 4, LIME_LEVEL = 5, PLOW_LEVEL = 6}
|
||||
DensityCoordType = {POINT_VECTOR_VECTOR = 1}
|
||||
DensityMapModifier = {new = function() return {setParallelogramWorldCoords = function() end, executeGet = function() calls = calls + 1; return 1, 1 end} end}
|
||||
g_fruitTypeManager = {
|
||||
getFruitTypeByIndex = function(_, index) return {name = index == WHEAT and "WHEAT" or "GRASS", fillType = {index = 50, title = "Hvede"}, minHarvestingGrowthState = 6, maxHarvestingGrowthState = 7, cutState = 9} end,
|
||||
getConverterDataByName = function() return {} end,
|
||||
}
|
||||
g_fillTypeManager = {getConverterDataByName = function() return {} end, getFillTypeNameByIndex = function() return "X" end}
|
||||
g_farmlandManager = {getFarmlandOwner = function() return 1 end}
|
||||
-- spillets egen marktilstand (field.fieldState, som fields.xml): afgrøde + vækststadie
|
||||
local field = {getId = function() return 7 end, farmland = {id = 70}, polygonPoints = {101, 102, 103, 104}, posX = 50, posZ = 50,
|
||||
fieldState = {fruitTypeIndex = WHEAT, growthState = 6}}
|
||||
g_fieldManager = {getFields = function() return {field} end}
|
||||
g_currentMission = {terrainRootNode = 1, missionInfo = {}, fieldGroundSystem = {
|
||||
getDensityMapData = function() return 1, 0, 2 end, getMaxValue = function() return 2 end}}
|
||||
local adEnv = {ADGraphManager = {
|
||||
getMapMarkers = function() return {{id = 900, name = "F7"}, {id = 901, name = "Silo"}} end,
|
||||
getWayPointById = function(_, id) return {x = 40, z = 40} end,
|
||||
}}
|
||||
|
||||
dofile("FS25_ADSmartPickup/adFields.lua")
|
||||
|
||||
local maxPerFrame, frames = 0, 0
|
||||
local now = 0
|
||||
for _ = 1, 200 do
|
||||
calls = 0
|
||||
ADFields.scanStep(adEnv, 1, now)
|
||||
if calls > maxPerFrame then maxPerFrame = calls end
|
||||
frames = frames + 1
|
||||
now = now + 16
|
||||
if ADFields.getInfo(7) ~= nil then break end
|
||||
end
|
||||
-- markliste først, så marken (efter SCAN_INTERVAL) — kør videre til marken er scannet
|
||||
for _ = 1, 400 do
|
||||
if ADFields.getInfo(7) ~= nil then break end
|
||||
calls = 0
|
||||
ADFields.scanStep(adEnv, 1, now)
|
||||
if calls > maxPerFrame then maxPerFrame = calls end
|
||||
now = now + 16
|
||||
end
|
||||
local info = ADFields.getInfo(7)
|
||||
check("SC1 marken er scannet", info ~= nil, true)
|
||||
check("SC2 afgrøde", info and info.fruitName, "WHEAT")
|
||||
check("SC3 art = høst", info and info.kind, "harvest")
|
||||
check("SC4 klar-andel", info and info.readyRatio, 1)
|
||||
check("SC5 fase klar", info and info.phase, "ready")
|
||||
check("SC6 markør F7", info and info.markerId, 900)
|
||||
check("SC7 prøvepunkter", info and #info.points, 5)
|
||||
check("SC8 gødning læst", info and info.fert, 1)
|
||||
check("SC9 højst ét punkts arbejde pr. frame (<= 4 opslag)", maxPerFrame <= 4, true)
|
||||
print(" (flest opslag i ét frame: " .. maxPerFrame .. ")")
|
||||
check("SC10 marklisten", #ADFields.list(), 1)
|
||||
|
||||
-- SC11: med ur kører flere opgaver pr. frame inden for budgettet (0,5 ms), højst MAX_TASKS_PER_FRAME
|
||||
local fakeTime = 0
|
||||
netGetTime = function() fakeTime = fakeTime + 0.1; return fakeTime end -- hver opgave "koster" 0,1 ms
|
||||
ADFields.cache, ADFields.listCache, ADFields.activeScan, ADFields.lastScanStart = {}, nil, nil, nil
|
||||
local frames2 = 0
|
||||
for _ = 1, 400 do
|
||||
if ADFields.getInfo(7) ~= nil then break end
|
||||
ADFields.scanStep(adEnv, 1, now)
|
||||
now = now + 16
|
||||
frames2 = frames2 + 1
|
||||
end
|
||||
check("SC11 scannet igen med ur", ADFields.getInfo(7) ~= nil, true)
|
||||
check("SC11 færre frames end én opgave pr. frame", frames2 < frames + 0, true)
|
||||
print(" (frames uden ur: " .. frames .. ", med ur: " .. frames2 .. ")")
|
||||
netGetTime = nil
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,44 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFieldsShape.lua (fra repo-roden)
|
||||
-- Markens form-fingeraftryk (genbrug af Courseplay-kurser): antal hjørner, areal og tyngdepunkt, afrundet så
|
||||
-- små float-forskelle ikke tæller — men en ændret mark (nyt hjørne, flyttet kant) giver en ny signatur.
|
||||
dofile("FS25_ADSmartPickup/adFields.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 rect(x0, z0, w, h)
|
||||
return {{x = x0, z = z0}, {x = x0 + w, z = z0}, {x = x0 + w, z = z0 + h}, {x = x0, z = z0 + h}}
|
||||
end
|
||||
|
||||
-- S1: rektangel 1600 x 600 -> 4 hjørner, 960.000 m², tyngdepunkt (800, 300)
|
||||
check("S1 rektangel", ADFields.shapeSignature(rect(0, 0, 1600, 600)), "n=4;a=960000;c=800,300")
|
||||
-- S2: omløbsretning er ligegyldig (arealet er absolut)
|
||||
local reversed = {}
|
||||
for index = 4, 1, -1 do table.insert(reversed, rect(0, 0, 1600, 600)[index]) end
|
||||
check("S2 modsat omløb", ADFields.shapeSignature(reversed), "n=4;a=960000;c=800,300")
|
||||
-- S3: små float-forskelle (cm) tæller ikke
|
||||
local noisy = rect(0.0003, -0.0002, 1600.0004, 599.9997)
|
||||
check("S3 float-støj", ADFields.shapeSignature(noisy), "n=4;a=960000;c=800,300")
|
||||
-- S4: flyttet kant (1 m bredere) -> ny signatur
|
||||
check("S4 kant flyttet", ADFields.shapeSignature(rect(0, 0, 1601, 600)) ~= ADFields.shapeSignature(rect(0, 0, 1600, 600)), true)
|
||||
-- S5: ekstra hjørne på kanten (samme areal) -> ny signatur
|
||||
local extra = {{x = 0, z = 0}, {x = 800, z = 0}, {x = 1600, z = 0}, {x = 1600, z = 600}, {x = 0, z = 600}}
|
||||
check("S5 ekstra hjørne", ADFields.shapeSignature(extra), "n=5;a=960000;c=800,300")
|
||||
-- S6: samme form et andet sted på kortet -> anden signatur
|
||||
check("S6 flyttet mark", ADFields.shapeSignature(rect(100, 0, 1600, 600)), "n=4;a=960000;c=900,300")
|
||||
-- S7: tom/ingen polygon -> nil
|
||||
check("S7 tom", ADFields.shapeSignature({}), nil)
|
||||
check("S8 nil", ADFields.shapeSignature(nil), nil)
|
||||
-- S9: under 3 hjørner er ingen flade -> nil
|
||||
check("S9 to punkter", ADFields.shapeSignature({{x = 0, z = 0}, {x = 1, z = 1}}), nil)
|
||||
-- S10: trekant: areal 50, tyngdepunkt (10/3, 10/3) -> (3, 3)
|
||||
check("S10 trekant", ADFields.shapeSignature({{x = 0, z = 0}, {x = 10, z = 0}, {x = 0, z = 10}}), "n=3;a=50;c=3,3")
|
||||
-- S11: negative koordinater afrundes symmetrisk
|
||||
check("S11 negative", ADFields.shapeSignature(rect(-1000, -500, 200, 100)), "n=4;a=20000;c=-900,-450")
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,33 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFleetNames.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFleetNames.lua")
|
||||
local N = ADFleetNames
|
||||
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 plan = N.plan({
|
||||
{id = "b", model = "Puma 260 CVX"}, {id = "a", model = "Puma 260 CVX"}, {id = "c", model = "Fastrac 4220"},
|
||||
{id = "d", model = "Cow Food", inRun = true, name = "Cow Food 1"}, {id = "e", model = "CR11", selfPropelled = true, name = "Mejetærsker"},
|
||||
})
|
||||
local byId = {}
|
||||
for _, entry in ipairs(plan) do byId[entry.id] = entry end
|
||||
check("navne: samme model nummereres (efter id)", byId.a.name, "Puma 260 CVX 1")
|
||||
check("navne: anden af samme model", byId.b.name, "Puma 260 CVX 2")
|
||||
check("navne: enlig model uden nummer", byId.c.name, "Fastrac 4220")
|
||||
check("navne: flow-traktor beholder navn", byId.d.name, "Cow Food 1")
|
||||
check("navne: selvkørende beholder navn", byId.e.name, "Mejetærsker")
|
||||
check("parkering: P + navn", byId.a.parkName, "P Puma 260 CVX 1")
|
||||
check("parkering: flow-traktor", byId.d.parkName, "P Cow Food 1")
|
||||
check("omdøb: kun hvis ændret", byId.d.rename, false)
|
||||
check("omdøb: ny navn", byId.a.rename, true)
|
||||
-- R7: parkeringer omdøbes kun for maskiner der selv omdøbes, og ikke delte parkeringer
|
||||
check("R7 flow-traktor: parkering røres ikke", byId.d.renamePark, false)
|
||||
check("R7 selvkørende: parkering røres ikke", byId.e.renamePark, false)
|
||||
check("R7 omdøbt traktor: parkering omdøbes", byId.a.renamePark, true)
|
||||
local shared = N.plan({{id = "x", model = "M", park = 5}, {id = "y", model = "Q", park = 5}}, {[5] = 2})
|
||||
check("R7 delt parkering røres ikke", shared[1].renamePark, false)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,314 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFlowGuide.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adGuide.lua")
|
||||
dofile("FS25_ADSmartPickup/adRuns.lua")
|
||||
dofile("FS25_ADSmartPickup/adFlowView.lua")
|
||||
dofile("FS25_ADSmartPickup/adFlowGuide.lua")
|
||||
local FG, G = ADFlowGuide, ADGuide
|
||||
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 newRun(kind, changes)
|
||||
local store, run = ADRuns.createRun(ADRuns.newStore(), "Mælk ud", kind)
|
||||
if changes ~= nil then store = ADRuns.updateRun(store, run.id, changes) end
|
||||
return ADRuns.getRun(store, run.id)
|
||||
end
|
||||
local function ctx(extra)
|
||||
local context = {
|
||||
buildings = {
|
||||
{id = "b1", name = "Kostald", label = "Kostald M", hasMarker = true, text = "Kostald — M 5 m"},
|
||||
{id = "b2", name = "Silo", label = "Silo M", hasMarker = true, text = "Silo — M 3 m"},
|
||||
{id = "b3", name = "Lade", hasMarker = false, text = "Lade — INGEN MARKØR"},
|
||||
},
|
||||
fillTypes = {{name = "HAY", title = "Hø"}, {name = "STRAW", title = "Halm"}},
|
||||
products = {{name = "MILK", title = "Mælk"}, {name = "EGG", title = "Æg"}},
|
||||
storeBuildings = {{id = "s1", name = "Silo Nord", text = "Silo Nord — M 5 m"}, {id = "s2", name = "Palleskur", text = "Palleskur — M 2 m"}},
|
||||
waitPools = {{group = "Wait A", count = 2}},
|
||||
vehicles = {
|
||||
{id = "t1", label = "Traktor 1", text = "Traktor 1 — 1000 l — OK"},
|
||||
{id = "t2", text = "Traktor 2", otherRunId = 9, otherRunName = "Andet"},
|
||||
{id = "t3", text = "Traktor 3", blocked = true},
|
||||
{id = "t4", text = "Traktor 4"},
|
||||
},
|
||||
}
|
||||
for key, value in pairs(extra or {}) do context[key] = value end
|
||||
return context
|
||||
end
|
||||
local function steps(guide) return table.concat(G.visibleSteps(guide), ",") end
|
||||
local function findRow(rows, id, item)
|
||||
for _, row in ipairs(rows) do
|
||||
if row.id == id and (item == nil or row.item == item) then return row end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
local function countType(rows, rowType)
|
||||
local n = 0
|
||||
for _, row in ipairs(rows) do if row.type == rowType then n = n + 1 end end
|
||||
return n
|
||||
end
|
||||
local function textKeys(rows)
|
||||
local keys = {}
|
||||
for _, row in ipairs(rows) do if row.type == "text" and row.label.key ~= nil then table.insert(keys, row.label.key) end end
|
||||
return table.concat(keys, ",")
|
||||
end
|
||||
local function at(guide, step) return G.jump(guide, step, "close") end
|
||||
|
||||
-- ---- trin ----
|
||||
local supply = FG.open(newRun("supply"), ctx())
|
||||
check("FL1 forsyning", steps(supply), "direction,buildings,goods,rules,wait,tractors,summary")
|
||||
local out = FG.open(newRun("outbound"), ctx())
|
||||
check("FL1 udkørsel uden lager-vare", steps(out), "direction,buildings,goods,rules,wait,tractors,summary")
|
||||
local outStore = FG.open(newRun("outbound", {products = {{fillType = "MILK", policy = "sellElseStore"}}}), ctx())
|
||||
check("FL1 udkørsel med lager", steps(outStore), "direction,buildings,goods,store,rules,wait,tractors,summary")
|
||||
local new = FG.openNew("Flow 3", ctx())
|
||||
check("FL1 nyt flow har navn", steps(new), "name,direction,buildings,goods,rules,wait,tractors,summary")
|
||||
check("FL1 nyt flow starter på navn", new.step, "name")
|
||||
check("FL1 nyt flow er forsyning", new.draft.kind, "supply")
|
||||
check("FL1 nyt flow navn", new.draft.name, "Flow 3")
|
||||
check("FL1 eksisterende starter på retning", supply.step, "direction")
|
||||
|
||||
-- ---- titler følger retningen ----
|
||||
check("FL2 til", FG.stepKey("buildings", supply), "spu_guide_flow_step_to")
|
||||
check("FL2 fra", FG.stepKey("buildings", out), "spu_guide_flow_step_from")
|
||||
check("FL2 andre", FG.stepKey("rules", out), "spu_guide_flow_step_rules")
|
||||
check("FL2 hint", FG.hintKey("goods", out), "spu_guide_flow_hint_goods_out")
|
||||
check("FL2 hint forsyning", FG.hintKey("goods", supply), "spu_guide_flow_hint_goods_in")
|
||||
|
||||
-- ---- choose er immutabel ----
|
||||
local toOut = FG.choose(supply, "kind", "outbound")
|
||||
check("FL3 retning skiftet", toOut.draft.kind, "outbound")
|
||||
check("FL3 gammel urørt", supply.draft.kind, "supply")
|
||||
local withTarget = FG.choose(supply, "buildings", {item = "b1", on = true})
|
||||
check("FL3 forsyning: mål", table.concat(withTarget.draft.targetBuildingIds, ","), "b1")
|
||||
check("FL3 forsyning: kilder urørt", #withTarget.draft.sourceBuildingIds, 0)
|
||||
local withSource = FG.choose(out, "buildings", {item = "b2", on = true})
|
||||
check("FL3 udkørsel: kilde", table.concat(withSource.draft.sourceBuildingIds, ","), "b2")
|
||||
local policy = FG.choose(out, "policy", {item = "MILK", value = "store"})
|
||||
check("FL3 politik", policy.draft.products[1].policy, "store")
|
||||
check("FL3 politik gør lager-trin synligt", FG.isVisible("store", policy), true)
|
||||
local off = FG.choose(policy, "policy", {item = "MILK", value = "off"})
|
||||
check("FL3 politik fra fjerner", #off.draft.products, 0)
|
||||
local replaced = FG.choose(policy, "policy", {item = "MILK", value = "sell"})
|
||||
check("FL3 politik erstattes", #replaced.draft.products, 1)
|
||||
check("FL3 politik ny", replaced.draft.products[1].policy, "sell")
|
||||
check("FL3 navn", FG.choose(new, "name", "Æg til salg").draft.name, "Æg til salg")
|
||||
check("FL3 tomt navn ignoreres", FG.choose(new, "name", " ").draft.name, "Flow 3")
|
||||
|
||||
-- ---- rækker ----
|
||||
local dirRows = FG.rows(supply)
|
||||
check("FL4 retning valg", findRow(dirRows, "kind").type, "choice")
|
||||
check("FL4 retning værdi", findRow(dirRows, "kind").value, "supply")
|
||||
local buildingRows = FG.rows(at(supply, "buildings"))
|
||||
check("FL4 bygninger med markør", countType(buildingRows, "check"), 2)
|
||||
check("FL4 vis alle", findRow(buildingRows, "showAllBuildings").type, "toggle")
|
||||
local allRows = FG.rows(at(FG.choose(supply, "showAllBuildings", true), "buildings"))
|
||||
check("FL4 vis alle: tre", countType(allRows, "check"), 3)
|
||||
local noHidden = FG.rows(at(FG.open(newRun("supply"), ctx({buildings = {{id = "b1", name = "Kostald", hasMarker = true, text = "K"}}})), "buildings"))
|
||||
check("FL4 ingen vis-alle uden skjulte", findRow(noHidden, "showAllBuildings"), nil)
|
||||
local chosenFirst = FG.rows(at(FG.choose(supply, "buildings", {item = "b2", on = true}), "buildings"))
|
||||
local firstCheck
|
||||
for _, row in ipairs(chosenFirst) do if row.type == "check" and firstCheck == nil then firstCheck = row end end
|
||||
check("FL4 fast rækkefølge (navn)", firstCheck.item, "b1")
|
||||
check("FL4 valgt markeret", findRow(chosenFirst, "buildings", "b2").value, true)
|
||||
check("FL4 ingen bygninger", textKeys(FG.rows(at(FG.open(newRun("supply"), ctx({buildings = {}})), "buildings"))), "spu_noBuildings")
|
||||
|
||||
local goodsAuto = FG.rows(at(supply, "goods"))
|
||||
check("FL5 forsyning auto", findRow(goodsAuto, "ingredientMode").value, "auto")
|
||||
check("FL5 forsyning auto: ingen liste", countType(goodsAuto, "check"), 0)
|
||||
local goodsManual = FG.rows(at(FG.choose(supply, "ingredientMode", "manual"), "goods"))
|
||||
check("FL5 forsyning vælg: varer", countType(goodsManual, "check"), 2)
|
||||
check("FL5 forsyning vælg uden varer", textKeys(FG.rows(at(FG.choose(FG.open(newRun("supply"), ctx({fillTypes = {}})), "ingredientMode", "manual"), "goods"))), "spu_guide_flow_noGoodsTo")
|
||||
local goodsOut = FG.rows(at(policy, "goods"))
|
||||
check("FL5 udkørsel: politik pr. vare", countType(goodsOut, "choice"), 2)
|
||||
check("FL5 udkørsel: mælk lager", findRow(goodsOut, "policy", "MILK").value, "store")
|
||||
check("FL5 udkørsel: æg fra", findRow(goodsOut, "policy", "EGG").value, "off")
|
||||
check("FL5 udkørsel: politikker", #findRow(goodsOut, "policy", "EGG").options, 4)
|
||||
check("FL5 udkørsel uden varer", textKeys(FG.rows(at(FG.open(newRun("outbound"), ctx({products = {}})), "goods"))), "spu_guide_flow_noProducts")
|
||||
|
||||
local storeRows = FG.rows(at(outStore, "store"))
|
||||
check("FL6 lager auto", findRow(storeRows, "storeAuto").value, true)
|
||||
check("FL6 lager auto: ingen liste", countType(storeRows, "check"), 0)
|
||||
local storeManual = FG.rows(at(FG.choose(outStore, "storeAuto", false), "store"))
|
||||
check("FL6 lager vælg selv", countType(storeManual, "check"), 2)
|
||||
local chosenStore = FG.open(newRun("outbound", {products = {{fillType = "MILK", policy = "store"}}, storeBuildingIds = {"s2"}}), ctx())
|
||||
check("FL6 valgt lager -> vælg selv", chosenStore.draft.storeAuto, false)
|
||||
|
||||
local rulesOut = FG.rows(at(out, "rules"))
|
||||
check("FL7 udkørsel: sælg fra", findRow(rulesOut, "sellAtPercent").value, 90)
|
||||
check("FL7 udkørsel: sælg-trin", #findRow(rulesOut, "sellAtPercent").options, 11)
|
||||
check("FL7 udkørsel: lager over", findRow(rulesOut, "storeAbovePercent").value, 80)
|
||||
check("FL7 udkørsel: samlerunde", findRow(rulesOut, "collectRound").type, "toggle")
|
||||
check("FL7 udkørsel: ingen ture", findRow(rulesOut, "loops"), nil)
|
||||
local rulesIn = FG.rows(at(supply, "rules"))
|
||||
check("FL7 forsyning: ture", findRow(rulesIn, "loops").value, 0)
|
||||
check("FL7 forsyning: ture-valg", #findRow(rulesIn, "loops").options, ADRuns.MAX_LOOPS + 1)
|
||||
check("FL7 forsyning: 0 = uendeligt", findRow(rulesIn, "loops").options[1].label.key, "spu_loops_infinite")
|
||||
check("FL7 forsyning: retur", findRow(rulesIn, "returnBelowPercent").value, 50)
|
||||
check("FL7 forsyning: ingen salg", findRow(rulesIn, "sellAtPercent"), nil)
|
||||
|
||||
local waitRows = FG.rows(at(supply, "wait"))
|
||||
check("FL8 vent: alle først", findRow(waitRows, "waitPoolGroup").options[1].value, "")
|
||||
check("FL8 vent: puljer", #findRow(waitRows, "waitPoolGroup").options, 2)
|
||||
|
||||
local assigned = FG.open(newRun("supply", {}), ctx())
|
||||
assigned = FG.choose(assigned, "vehicleIds", {item = "t4", on = true})
|
||||
local tractorRows = FG.rows(at(assigned, "tractors"))
|
||||
check("FL9 traktorer: kun egne + ledige", countType(tractorRows, "check"), 2)
|
||||
check("FL9 egne først", (function() for _, r in ipairs(tractorRows) do if r.type == "check" then return r.item end end end)(), "t4")
|
||||
check("FL9 skjult andre flows", findRow(tractorRows, "vehicleIds", "t2"), nil)
|
||||
check("FL9 skjult blokeret", findRow(tractorRows, "vehicleIds", "t3"), nil)
|
||||
check("FL9 linjer", textKeys(tractorRows), "spu_guide_flow_otherFlows")
|
||||
-- blokerede traktorer: foldet sammen som standard (in-game 27/9: 37 linjer der ikke kunne bruges fyldte trinnet);
|
||||
-- linjen med antallet er et link der folder navn + grund ud og ind
|
||||
local function blockedLine(rows)
|
||||
for _, r in ipairs(rows) do if r.type == "text" and r.label.text ~= nil and r.label.text:find("Traktor 3", 1, true) then return r end end
|
||||
return nil
|
||||
end
|
||||
local function blockedLink(rows)
|
||||
for _, r in ipairs(rows) do if r.type == "link" and r.id == "showBlocked" then return r end end
|
||||
return nil
|
||||
end
|
||||
check("FL9 blokerede foldet sammen som standard", blockedLine(tractorRows), nil)
|
||||
check("FL9 link med antal", blockedLink(tractorRows) ~= nil and blockedLink(tractorRows).label.key, "spu_guide_flow_blockedShow")
|
||||
local expanded = FG.choose(assigned, "showBlocked", true)
|
||||
local expandedRows = FG.rows(at(expanded, "tractors"))
|
||||
check("FL9 foldet ud viser navn + grund", blockedLine(expandedRows) ~= nil, true)
|
||||
check("FL9 foldet ud: link skjuler igen", blockedLink(expandedRows).label.key, "spu_guide_flow_blockedHide")
|
||||
check("FL9 fold ud ændrer ikke kladden", expanded.draft.vehicleIds[1], "t4")
|
||||
check("FL9 fold sammen igen", blockedLine(FG.rows(at(FG.choose(expanded, "showBlocked", true), "tractors"))), nil)
|
||||
local inThisRun = FG.open(newRun("supply"), ctx({vehicles = {{id = "t2", text = "T2", otherRunId = 1, otherRunName = "Mælk ud"}}}))
|
||||
check("FL9 traktor i dette flow er ikke 'andet flow'", countType(FG.rows(at(inThisRun, "tractors")), "check"), 1)
|
||||
|
||||
-- ---- resumé ----
|
||||
local function lineFor(lines, step)
|
||||
for _, line in ipairs(lines) do if line.step == step then return line end end
|
||||
return nil
|
||||
end
|
||||
local function partText(line)
|
||||
local parts = {}
|
||||
for _, part in ipairs(line.parts) do table.insert(parts, part.key or ("=" .. tostring(part.text))) end
|
||||
return table.concat(parts, ",")
|
||||
end
|
||||
local sumIn = FG.summary(FG.choose(withTarget, "vehicleIds", {item = "t1", on = true}))
|
||||
check("FL10 navn", partText(lineFor(sumIn, "name")), "=Mælk ud")
|
||||
check("FL10 retning", partText(lineFor(sumIn, "direction")), "spu_direction_in")
|
||||
check("FL10 til", partText(lineFor(sumIn, "buildings")), "=Kostald")
|
||||
check("FL10 til-titel", lineFor(sumIn, "buildings").title.key, "spu_guide_flow_step_to")
|
||||
check("FL10 varer auto", partText(lineFor(sumIn, "goods")), "spu_guide_flow_goodsAuto")
|
||||
check("FL10 regler", partText(lineFor(sumIn, "rules")), "spu_guide_flow_sumLoopsInf,spu_guide_flow_sumReturn")
|
||||
check("FL10 vent", partText(lineFor(sumIn, "wait")), "spu_waitPool_all")
|
||||
check("FL10 traktorer", partText(lineFor(sumIn, "tractors")), "=Traktor 1")
|
||||
check("FL10 intet lager-trin", lineFor(sumIn, "store"), nil)
|
||||
-- traktor der ikke findes længere (solgt): aldrig rå id i resuméet (in-game 29/9)
|
||||
local withGhost = FG.choose(FG.choose(withTarget, "vehicleIds", {item = "t1", on = true}), "vehicleIds", {item = "vehicleGONE", on = true})
|
||||
check("FL10b resumé springer manglende over", partText(lineFor(FG.summary(withGhost), "tractors")), "=Traktor 1")
|
||||
local onlyGhost = FG.choose(withTarget, "vehicleIds", {item = "vehicleGONE", on = true})
|
||||
check("FL10b kun manglende -> ingen traktorer", partText(lineFor(FG.summary(onlyGhost), "tractors")), "spu_guide_flow_sumNoTractors")
|
||||
local sumOut = FG.summary(FG.choose(FG.choose(policy, "collectRound", true), "sellAtPercent", 95))
|
||||
check("FL11 retning ud", partText(lineFor(sumOut, "direction")), "spu_direction_out")
|
||||
check("FL11 fra tom", partText(lineFor(sumOut, "buildings")), "spu_guide_flow_none")
|
||||
check("FL11 varer", partText(lineFor(sumOut, "goods")), "spu_policy_store")
|
||||
check("FL11 varenavn foran", lineFor(sumOut, "goods").parts[1].prefix, "Mælk: ")
|
||||
check("FL11 lager auto", partText(lineFor(sumOut, "store")), "spu_guide_sum_auto")
|
||||
-- varen er kun "lager": salgsgrænsen vises ikke (in-game 29/9)
|
||||
check("FL11 regler (kun lager)", partText(lineFor(sumOut, "rules")), "spu_guide_flow_sumStoreAbove,spu_guide_flow_sumRound")
|
||||
check("FL11 lager over", lineFor(sumOut, "rules").parts[1].args[1], 80)
|
||||
check("FL11 ingen traktorer", partText(lineFor(sumOut, "tractors")), "spu_guide_flow_sumNoTractors")
|
||||
local manyGoods = FG.summary(FG.choose(FG.choose(FG.choose(supply, "ingredientMode", "manual"), "ingredients", {item = "HAY", on = true}), "ingredients", {item = "STRAW", on = true}))
|
||||
check("FL12 valgte varer", partText(lineFor(manyGoods, "goods")), "=Hø + Halm")
|
||||
check("FL12 oversigt: links", countType(FG.rows(G.new(supply.def, supply.draft, supply.context, {step = "summary"})), "link"), 7)
|
||||
|
||||
-- ---- resultat ----
|
||||
local r = FG.result(FG.choose(FG.choose(policy, "storeAuto", false), "storeBuildingIds", {item = "s1", on = true}))
|
||||
check("FL13 ikke ny", r.isNew, false)
|
||||
check("FL13 run-id", r.runId, 1)
|
||||
check("FL13 retning", r.changes.kind, "outbound")
|
||||
check("FL13 lager valgt", table.concat(r.changes.storeBuildingIds, ","), "s1")
|
||||
check("FL13 varer", r.changes.products[1].fillType, "MILK")
|
||||
check("FL13 lager-resumé bruger navn", (function() for _, l in ipairs(FG.summary(FG.choose(FG.choose(policy, "storeAuto", false), "storeBuildingIds", {item = "s1", on = true}))) do if l.step == "store" then return l.parts[1].text end end end)(), "Silo Nord")
|
||||
local rAuto = FG.result(FG.choose(chosenStore, "storeAuto", true))
|
||||
check("FL13 lager auto -> tom", #rAuto.changes.storeBuildingIds, 0)
|
||||
local hiddenStore = FG.open(newRun("outbound", {storeBuildingIds = {"s2"}}), ctx())
|
||||
check("FL13 lager usynligt: bevares", table.concat(FG.result(hiddenStore).changes.storeBuildingIds, ","), "s2")
|
||||
local rNew = FG.result(FG.choose(new, "vehicleIds", {item = "t1", on = true}))
|
||||
check("FL14 nyt", rNew.isNew, true)
|
||||
check("FL14 navn", rNew.name, "Flow 3")
|
||||
check("FL14 traktorer", table.concat(rNew.vehicleIds, ","), "t1")
|
||||
check("FL14 ingen run-id", rNew.runId, nil)
|
||||
local added, removed = FG.vehicleChanges({"a", "b"}, {"b", "c"})
|
||||
check("FL15 tilføjet", table.concat(added, ","), "c")
|
||||
check("FL15 fjernet", table.concat(removed, ","), "a")
|
||||
|
||||
-- ---- forhåndsvisning (til controllerens opslag) ----
|
||||
local markers = {b1 = {101, 102}, b2 = {201}}
|
||||
local preview = FG.previewRun(withSource.draft, function(id) return markers[id] or {} end)
|
||||
check("FL16 kilder -> markører", table.concat(preview.sourceWayPointIds, ","), "201")
|
||||
local previewIn = FG.previewRun(withTarget.draft, function(id) return markers[id] or {} end)
|
||||
check("FL16 mål -> markører", table.concat(previewIn.targetWayPointIds, ","), "101,102")
|
||||
check("FL16 primært mål", previewIn.targetWayPointId, 101)
|
||||
check("FL16 kladde urørt", withTarget.draft.targetWayPointIds == nil or #withTarget.draft.targetWayPointIds == 0, true)
|
||||
|
||||
|
||||
-- ---- solo-traktor + ledig vogn (in-game 29/9: flow kunne ikke oprettes uden at omdøbe en vogns parkering) ----
|
||||
local soloCtx = ctx({
|
||||
vehicles = {
|
||||
{id = "s1", label = "Solo 1", text = "Solo 1", solo = true},
|
||||
{id = "s2", label = "Solo 2", text = "Solo 2", solo = true},
|
||||
{id = "s3", label = "Solo 3", text = "Solo 3", solo = true},
|
||||
{id = "t1", label = "Traktor 1", text = "Traktor 1 — OK"},
|
||||
},
|
||||
wagons = {{id = "w1", label = "P Disponibel Vogn 4"}, {id = "w2", label = "P Disponibel Vogn 5"}},
|
||||
})
|
||||
local soloGuide = FG.open(newRun("supply"), soloCtx)
|
||||
local one = FG.choose(soloGuide, "vehicleIds", {item = "s1", on = true})
|
||||
check("WG1 solo tildeles første ledige vogn", one.draft.wagons.s1, "w1")
|
||||
local two = FG.choose(one, "vehicleIds", {item = "s2", on = true})
|
||||
check("WG2 næste solo får næste vogn", two.draft.wagons.s2, "w2")
|
||||
check("WG2 første beholder sin", two.draft.wagons.s1, "w1")
|
||||
local three = FG.choose(two, "vehicleIds", {item = "s3", on = true})
|
||||
check("WG3 ingen ledig vogn tilbage -> ingen tildeling", three.draft.wagons.s3, nil)
|
||||
local soloRows = FG.rows(at(three, "tractors"))
|
||||
local wagonRow = findRow(soloRows, "wagon", "s1")
|
||||
check("WG4 vognvalg under solo-traktor", wagonRow ~= nil and wagonRow.type, "choice")
|
||||
check("WG4 vognvalg viser tildelt vogn", wagonRow ~= nil and wagonRow.value, "w1")
|
||||
check("WG4 valgmuligheder uden de andres vogne", wagonRow ~= nil and #wagonRow.options, 1)
|
||||
local noFree = false
|
||||
for _, row in ipairs(soloRows) do if row.label ~= nil and row.label.key == "spu_guide_flow_noFreeWagon" then noFree = true end end
|
||||
check("WG5 solo uden ledig vogn får besked", noFree, true)
|
||||
check("WG5 almindelig traktor har intet vognvalg", findRow(FG.rows(at(FG.choose(soloGuide, "vehicleIds", {item = "t1", on = true}), "tractors")), "wagon", "t1"), nil)
|
||||
local swapped = FG.choose(FG.choose(one, "vehicleIds", {item = "s2", on = false}), "wagon", {item = "s1", value = "w2"})
|
||||
check("WG6 skift vogn", swapped.draft.wagons.s1, "w2")
|
||||
local off = FG.choose(two, "vehicleIds", {item = "s1", on = false})
|
||||
check("WG7 fravalgt traktor slipper vognen", off.draft.wagons.s1, nil)
|
||||
local freed = FG.choose(off, "vehicleIds", {item = "s3", on = true})
|
||||
check("WG7 frigjort vogn går til næste", freed.draft.wagons.s3, "w1")
|
||||
check("WG8 resultat har vognene", FG.result(two).wagons.s2, "w2")
|
||||
check("WG8 resultat uden fravalgte", FG.result(off).wagons.s1, nil)
|
||||
local remembered = newRun("supply", {vehicleIds = {"s2"}})
|
||||
remembered.wagons = {s2 = "w2"}
|
||||
local reopened = FG.open(remembered, soloCtx)
|
||||
check("WG9 husket vogn i kladden", reopened.draft.wagons.s2, "w2")
|
||||
check("WG9 ny solo tager ikke den huskede", FG.choose(reopened, "vehicleIds", {item = "s1", on = true}).draft.wagons.s1, "w1")
|
||||
check("WG9 kladden kopierer (run urørt)", (function() local g = FG.choose(reopened, "wagon", {item = "s2", value = "w1"}); return remembered.wagons.s2 end)(), "w2")
|
||||
|
||||
|
||||
-- regler: kun de relevante for varernes politik
|
||||
local storeOnly = FG.open(newRun("outbound", {products = {{fillType = "MILK", policy = "store"}}}), ctx())
|
||||
local function keys(rows) local r = {} for _, row in ipairs(rows) do if row.id then table.insert(r, row.id) end end return table.concat(r, ",") end
|
||||
check("RL1 kun lager: ingen salgsvalg", keys(FG.rows(at(storeOnly, "rules"))), "storeAbovePercent,collectRound")
|
||||
local sellOnly = FG.open(newRun("outbound", {products = {{fillType = "MILK", policy = "sell"}}}), ctx())
|
||||
check("RL2 kun salg: ingen lagervalg", keys(FG.rows(at(sellOnly, "rules"))), "sellAtPercent,sellAbovePercent,collectRound")
|
||||
local function ruleKeys(guide)
|
||||
for _, line in ipairs(FG.summary(guide)) do
|
||||
if line.step == "rules" then local r = {} for _, p in ipairs(line.parts) do table.insert(r, p.key) end return table.concat(r, ",") end
|
||||
end
|
||||
end
|
||||
check("RL3 resumé kun lager", ruleKeys(storeOnly), "spu_guide_flow_sumStoreAbove")
|
||||
check("RL4 resumé kun salg", ruleKeys(sellOnly), "spu_guide_flow_sumSellAt")
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,105 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFlowView.lua (fra repo-roden)
|
||||
-- Ren oprydning af Flows-fanen: tavlens rækkefølge, hvilke traktorer og bygninger der vises.
|
||||
|
||||
dofile("FS25_ADSmartPickup/adFlowView.lua")
|
||||
local V = ADFlowView
|
||||
|
||||
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 ids(list, field)
|
||||
local out = {}
|
||||
for _, item in ipairs(list) do table.insert(out, tostring(item[field or "id"])) end
|
||||
return table.concat(out, ",")
|
||||
end
|
||||
|
||||
-- FV1: tavlen — forsyning (Ind) først, så udkørsel (Ud); hver gruppe sorteret efter navn (naturligt)
|
||||
local runs = {
|
||||
{id = 1, name = "Pig Food", kind = "supply"},
|
||||
{id = 2, name = "Milk Sale", kind = "outbound"},
|
||||
{id = 3, name = "Cow Food", kind = "supply"},
|
||||
{id = 4, name = "Biogas 10", kind = "supply"},
|
||||
{id = 5, name = "Biogas 9", kind = "supply"},
|
||||
{id = 6, name = "Digestate", kind = "outbound"},
|
||||
}
|
||||
local groups = V.boardGroups(runs)
|
||||
check("FV1 to grupper", #groups, 2)
|
||||
check("FV1 Ind først", groups[1].kind, "supply")
|
||||
check("FV1 Ind sorteret (Biogas 9 før 10)", ids(groups[1].runs), "5,4,3,1")
|
||||
check("FV1 Ud sorteret", ids(groups[2].runs), "6,2")
|
||||
check("FV1 samlet rækkefølge til vælgeren", ids(V.orderedRuns(runs)), "5,4,3,1,6,2")
|
||||
check("FV1 tom gruppe udelades", #V.boardGroups({{id = 1, name = "A", kind = "supply"}}), 1)
|
||||
|
||||
-- FV2: traktorer — egne først, så ledige brugbare; andre flows og låste kun med "vis alle"
|
||||
local vehicles = {
|
||||
{id = "a", label = "Zetor", state = "free"},
|
||||
{id = "b", label = "Fendt", state = "in"},
|
||||
{id = "c", label = "Case", state = "other"},
|
||||
{id = "d", label = "Claas", state = "blocked"},
|
||||
{id = "e", label = "Deutz", state = "free"},
|
||||
}
|
||||
check("FV2 standard: egne + ledige, sorteret", ids(V.vehicleRows(vehicles, false)), "b,e,a")
|
||||
check("FV2 vis alle: + andre flows + låste til sidst", ids(V.vehicleRows(vehicles, true)), "b,e,a,c,d")
|
||||
check("FV2 antal skjulte", V.hiddenCount(vehicles, V.vehicleRows(vehicles, false)), 2)
|
||||
|
||||
-- FV3: bygninger — valgte først; uden markør skjult medmindre valgt eller "vis alle"
|
||||
local buildings = {
|
||||
{id = "s2", name = "Silo 2", hasMarker = true},
|
||||
{id = "s10", name = "Silo 10", hasMarker = true},
|
||||
{id = "x", name = "Gammel lade", hasMarker = false},
|
||||
{id = "y", name = "Anden uden", hasMarker = false},
|
||||
{id = "a", name = "Anlæg", hasMarker = true},
|
||||
}
|
||||
local chosen = {s10 = true, y = true}
|
||||
-- fast rækkefølge (et tryk må ikke flytte rækken — in-game 26/9 hoppede valgte drops op i toppen)
|
||||
check("FV3 standard: fast rækkefølge efter navn, valgte bliver hvor de er", ids(V.buildingRows(buildings, chosen, false)), "y,a,s2,s10")
|
||||
check("FV3 vis alle: uden markør til sidst", ids(V.buildingRows(buildings, chosen, true)), "y,a,s2,s10,x")
|
||||
-- ens bygningsnavne (5 x "BGA (1) : DOB 115") sorteres efter markørnavnet
|
||||
local drops = {
|
||||
{id = "d5", name = "DOB", label = "Biogas Drop 5", hasMarker = true},
|
||||
{id = "d3", name = "DOB", label = "Biogas Drop 3", hasMarker = true},
|
||||
{id = "d1", name = "DOB", label = "Biogas Drop 1", hasMarker = true},
|
||||
{id = "d4", name = "DOB", label = "Biogas Drop 4", hasMarker = true},
|
||||
{id = "d2", name = "DOB", label = "Biogas Drop 2", hasMarker = true},
|
||||
}
|
||||
check("FV3 ens navne efter markør", ids(V.buildingRows(drops, {d2 = true}, false)), "d1,d2,d3,d4,d5")
|
||||
|
||||
-- FV4: ens navne slås sammen med antal ("Chicken Coop x5"), rækkefølge efter første forekomst
|
||||
check("FV4 sammenlæg", V.joinCounted({"Chicken Coop", "Chicken Coop", "Cow Barn", "Chicken Coop"}, " + ", "-"), "Chicken Coop x3 + Cow Barn")
|
||||
check("FV4 ingen dubletter", V.joinCounted({"A", "B"}, " + ", "-"), "A + B")
|
||||
check("FV4 tom -> tekst", V.joinCounted({}, " + ", "(ingen)"), "(ingen)")
|
||||
|
||||
-- FV-wrap: lange tekstrækker ombrydes ved mellemrum; indrykning gentages; kort tekst uændret
|
||||
local wrapped = V.wrapText(" Prepared and sown with Sugar Beet as soon as it is sowing time for it", 30)
|
||||
check("FVwrap flere linjer", #wrapped > 1, true)
|
||||
check("FVwrap første linje indrykket", string.sub(wrapped[1], 1, 4), " ")
|
||||
check("FVwrap anden linje indrykket", string.sub(wrapped[2], 1, 4), " ")
|
||||
local maxLen = 0
|
||||
for _, line in ipairs(wrapped) do if #line > maxLen then maxLen = #line end end
|
||||
check("FVwrap ingen linje over grænsen", maxLen <= 30, true)
|
||||
check("FVwrap intet ord tabt", table.concat(wrapped, " "):gsub("%s+", " "):gsub("^ ", ""), "Prepared and sown with Sugar Beet as soon as it is sowing time for it")
|
||||
check("FVwrap kort tekst", V.wrapText("Kort", 30)[1], "Kort")
|
||||
check("FVwrap langt ord deles ikke", V.wrapText("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 10)[1], "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
|
||||
|
||||
|
||||
-- salgs-/lagerregler vises kun når en vare bruger dem (in-game 29/9: 'sell from 90 %' på et rent lager-flow)
|
||||
local sells, stores = ADFlowView.policyUse({"store"})
|
||||
check("politik: kun lager -> sælger ikke", sells, false)
|
||||
check("politik: kun lager -> lagrer", stores, true)
|
||||
sells, stores = ADFlowView.policyUse({"sell"})
|
||||
check("politik: kun salg", tostring(sells) .. tostring(stores), "truefalse")
|
||||
sells, stores = ADFlowView.policyUse({"sellElseStore", "off"})
|
||||
check("politik: sælg ellers lager -> begge", tostring(sells) .. tostring(stores), "truetrue")
|
||||
sells, stores = ADFlowView.policyUse({})
|
||||
check("politik: ingen varer endnu -> vis begge", tostring(sells) .. tostring(stores), "truetrue")
|
||||
sells, stores = ADFlowView.policyUse({"off"})
|
||||
check("politik: kun fra -> vis begge", tostring(sells) .. tostring(stores), "truetrue")
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,140 +0,0 @@
|
||||
-- Kør: luajit tests/test_adFlowWagons.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adRuns.lua")
|
||||
dofile("FS25_ADSmartPickup/adFlowWagons.lua")
|
||||
local W = ADFlowWagons
|
||||
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
|
||||
-- gem/indlæs: traktor -> vogn
|
||||
local text = W.serialize({vehicleB = "wagon2", vehicleA = "wagon1"})
|
||||
check("gem: sorteret", text, "vehicleA=wagon1;vehicleB=wagon2")
|
||||
local parsed = W.parse(text)
|
||||
check("indlæs: A", parsed.vehicleA, "wagon1")
|
||||
check("indlæs: B", parsed.vehicleB, "wagon2")
|
||||
check("indlæs: tom", next(W.parse("")), nil)
|
||||
check("indlæs: nil", next(W.parse(nil)), nil)
|
||||
check("indlæs: skrald springes over", next(W.parse("=x;;y=")), nil)
|
||||
-- husk vogn: ny tabel, gammel urørt
|
||||
local before = {t1 = "w1"}
|
||||
local after = W.remember(before, "t1", "w9")
|
||||
check("husk: ny vogn", after.t1, "w9")
|
||||
check("husk: original urørt", before.t1, "w1")
|
||||
check("husk: samme vogn -> samme tabel", W.remember(after, "t1", "w9"), after)
|
||||
-- start: hent / vent / start
|
||||
check("start: har redskab bagpå", W.startAction({hasRear = true, wagonId = "w1", wagonExists = true, hasConnect = true}), "start")
|
||||
check("start: ingen husket vogn", W.startAction({}), "start")
|
||||
check("start: vognen findes ikke", W.startAction({wagonId = "w1", wagonExists = false, hasConnect = true}), "start")
|
||||
check("start: vognen uden Connect", W.startAction({wagonId = "w1", wagonExists = true, hasConnect = false}), "start")
|
||||
check("start: vognen på en anden", W.startAction({wagonId = "w1", wagonExists = true, hasConnect = true, wagonAttached = true}), "wait")
|
||||
check("start: vognen lånt ud", W.startAction({wagonId = "w1", wagonExists = true, hasConnect = true, wagonBusy = true}), "wait")
|
||||
check("start: hent", W.startAction({wagonId = "w1", wagonExists = true, hasConnect = true}), "fetch")
|
||||
-- ejer: flowets vogn er flowets — også når flowet er stoppet (in-game 29/9: marksilo lånte Cow Food-vognen)
|
||||
local runs = {{id = 1, stopped = false, wagons = {t1 = "w1"}}, {id = 2, stopped = true, wagons = {t2 = "w2"}}}
|
||||
check("ejer: kørende flow holder", W.isHeld(runs, "w1"), true)
|
||||
check("ejer: stoppet flow holder stadig", W.isHeld(runs, "w2"), true)
|
||||
check("ejer: ukendt vogn", W.isHeld(runs, "w3"), false)
|
||||
check("ejer: flowet findes", W.ownerOf(runs, "w2").id, 2)
|
||||
-- bygning væk: to tjek i træk
|
||||
local count, gone = W.missingStep(0, true)
|
||||
check("væk: første tjek -> ikke endnu", gone, false)
|
||||
count, gone = W.missingStep(count, true)
|
||||
check("væk: andet tjek -> væk", gone, true)
|
||||
count, gone = W.missingStep(1, false)
|
||||
check("væk: fundet igen nulstiller", count, 0)
|
||||
check("væk: fundet igen -> ikke væk", gone, false)
|
||||
-- manglende bygninger i et flow
|
||||
local exists = {a = true, b = true}
|
||||
check("mangler: alle findes", #W.missingBuildings({sourceBuildingIds = {"a"}, targetBuildingIds = {"b"}}, exists, exists), 0)
|
||||
check("mangler: én væk", W.missingBuildings({targetBuildingIds = {"a", "c"}, storeBuildingIds = {}}, exists, {a = true, c = true})[1], "c")
|
||||
-- review v1.51
|
||||
check("R1 traktoren er lånt ud af puljen -> vent", W.startAction({tractorBusy = true, hasRear = true, wagonId = "w1", wagonExists = true, hasConnect = true}), "wait")
|
||||
local runs2 = {{id = 1, stopped = true, wagons = {t1 = "w1"}}, {id = 2, stopped = false, wagons = {t9 = "w1"}}}
|
||||
check("R4 holdt hvis ET kørende flow har vognen", W.isHeld(runs2, "w1"), true)
|
||||
local seen = {a = true, c = true}
|
||||
check("R+ bygning aldrig set i sessionen tæller ikke som væk", #W.missingBuildings({targetBuildingIds = {"b"}}, {a = true}, seen), 0)
|
||||
check("R+ bygning set og nu væk", W.missingBuildings({targetBuildingIds = {"c"}}, {a = true}, seen)[1], "c")
|
||||
-- gæt flowets vogn ud fra parkeringsnavnet (ingen husket vogn: vognene var sat på plads før v1.51)
|
||||
local cands = {{id = "v1", parkName = "P Cow Food vogn 1"}, {id = "v3", parkName = "P Cow Food vogn 3"},
|
||||
{id = "g1", parkName = "P Goat Food vogn 1"}, {id = "v2", parkName = "P Cow Food vogn 2"}}
|
||||
check("gæt: samme nummer", W.guessWagon("Cow Food", 3, cands, {}), "v3")
|
||||
check("gæt: nummer optaget -> anden med flowets navn", W.guessWagon("Cow Food", 3, cands, {v3 = true}), "v1")
|
||||
check("gæt: andet flow", W.guessWagon("Goat Food", 2, cands, {}), "g1")
|
||||
check("gæt: intet passer", W.guessWagon("Pig Food", 1, cands, {}), nil)
|
||||
check("gæt: store/små bogstaver", W.guessWagon("cow food", 2, cands, {}), "v2")
|
||||
check("gæt: 'Cow Food 1' matcher ikke 'Cow Food 10'", W.guessWagon("Cow Food", 1, {{id = "x", parkName = "P Cow Food vogn 10"}, {id = "y", parkName = "P Cow Food vogn 1"}}, {}), "y")
|
||||
-- tøm vognen før den parkeres (flow stoppet): mål -> silo/fabrik -> hjem
|
||||
check("tøm: tom vogn -> hjem", W.emptyStep("start", {cargo = 0}), "home")
|
||||
check("tøm: last -> flowets mål", W.emptyStep("start", {cargo = 5000, hasTarget = true}), "target")
|
||||
check("tøm: last uden mål -> silo", W.emptyStep("start", {cargo = 5000, hasTarget = false}), "dump")
|
||||
check("tøm: kører stadig -> vent", W.emptyStep("target", {cargo = 5000, adActive = true}), "wait")
|
||||
check("tøm: mål fuldt, rest -> silo", W.emptyStep("target", {cargo = 2000}), "dump")
|
||||
check("tøm: målet tog det hele -> hjem", W.emptyStep("target", {cargo = 0}), "home")
|
||||
check("tøm: silo tog det -> hjem", W.emptyStep("dump", {cargo = 0}), "home")
|
||||
check("tøm: silo kunne ikke -> giv op, hjem", W.emptyStep("dump", {cargo = 2000}), "giveUp")
|
||||
check("tøm: for længe -> giv op", W.emptyStep("target", {cargo = 5000, adActive = true, elapsedMs = W.EMPTY_TIMEOUT_MS}), "giveUp")
|
||||
-- flowet startes igen mens traktoren tømmer/kører hjem fra et stop: hjemturen afbrydes, den kører videre med vognen
|
||||
-- (in-game 29/9: Stop+Start med 1 s mellemrum -> tømning ved forkert mål i 10 min, vogn sat af, traktor i garage)
|
||||
check("genstart: tømmer -> afbryd", W.abortHomeTripOnStart({emptying = true}), true)
|
||||
check("genstart: flowets hjemtur -> afbryd", W.abortHomeTripOnStart({flowHoming = true}), true)
|
||||
check("genstart: puljens hjemtur (markarbejde) -> rør ikke", W.abortHomeTripOnStart({poolReturning = true}), false)
|
||||
check("genstart: intet i gang", W.abortHomeTripOnStart({}), false)
|
||||
-- tøm-mål: kun flowets egne mål — AD's 2. markør kan være forældet (æg-markør) eller et Wait-punkt (in-game 29/9)
|
||||
check("tøm-mål: AD's mål er et af flowets", W.emptyTarget(12638, {12636, 12638}), 12638)
|
||||
check("tøm-mål: forældet/Wait-markør -> flowets første mål", W.emptyTarget(12658, {12636, 12638}), 12636)
|
||||
check("tøm-mål: intet AD-mål -> flowets første", W.emptyTarget(nil, {12636}), 12636)
|
||||
check("tøm-mål: flow uden mål -> intet (silo)", W.emptyTarget(12658, {}), nil)
|
||||
check("tøm-mål: ingen liste -> intet", W.emptyTarget(12658, nil), nil)
|
||||
-- ledig traktor (puljen/markarbejde/marksilo må låne den): kun solo fra et stoppet flow
|
||||
check("ledig: intet flow", W.tractorFree({}), true)
|
||||
check("ledig: kørende flow", W.tractorFree({hasRun = true}), false)
|
||||
check("ledig: stoppet flow, solo", W.tractorFree({hasRun = true, stopped = true}), true)
|
||||
check("ledig: stoppet flow, flow-vogn bagpå", W.tractorFree({hasRun = true, stopped = true, carriesFlowWagon = true}), false)
|
||||
check("ledig: stoppet flow, tømmer/kører hjem", W.tractorFree({hasRun = true, stopped = true, homeTrip = true}), false)
|
||||
-- venteårsag i loggen (før stod der altid "i brug andetsteds")
|
||||
check("vent-årsag: traktor optaget", W.waitReason({tractorBusy = true}), "tractorBusy")
|
||||
check("vent-årsag: vogn på en anden", W.waitReason({wagonAttached = true}), "wagonAttached")
|
||||
check("vent-årsag: vogn reserveret", W.waitReason({wagonBusy = true}), "wagonReserved")
|
||||
-- tømning går i stå ved et fuldt mål (in-game 29/9: fåre-/BGA-vogne stod 10 min ved fuldt mål efter Stop alt)
|
||||
check("tøm: står ved fuldt mål -> silo", W.emptyStep("target", {cargo = 5000, adActive = true, stalled = true}), "dump")
|
||||
check("tøm: står ved silo uden at tømme -> næste silo", W.emptyStep("dump", {cargo = 5000, adActive = true, stalled = true, dumpAttempts = 1}), "dump")
|
||||
check("tøm: 3 siloer prøvet -> giv op", W.emptyStep("dump", {cargo = 5000, adActive = true, stalled = true, dumpAttempts = W.MAX_DUMP_ATTEMPTS}), "giveUp")
|
||||
check("tøm: kører mod målet (ikke i stå) -> vent", W.emptyStep("target", {cargo = 5000, adActive = true, stalled = false}), "wait")
|
||||
check("i stå: tæt på, uændret længe", W.isStalled({nearTarget = true, sinceChangeMs = W.EMPTY_STALL_MS}), true)
|
||||
check("i stå: tæt på, lige ankommet", W.isStalled({nearTarget = true, sinceChangeMs = 5000}), false)
|
||||
check("i stå: på vej (langt væk)", W.isStalled({nearTarget = false, sinceChangeMs = 999999}), false)
|
||||
-- hent vogn: ét nyt forsøg efter "stoppedAway" (AD standsede undervejs — fx spærret af en anden traktor)
|
||||
check("hent igen: stoppedAway første gang", W.retryFetch("stoppedAway (37 m fra markøren)", 0), true)
|
||||
check("hent igen: stoppedAway anden gang -> nej", W.retryFetch("stoppedAway", 1), false)
|
||||
check("hent igen: noAttachable -> nej (markøren skal rettes)", W.retryFetch("noAttachable", 0), false)
|
||||
-- manuel påkobling (spilleren sendte traktoren til Connect): traktor i et kørende flow startes bagefter
|
||||
-- (in-game 29/9: Cow Food 3 hentede sin vogn manuelt efter et fejlet forsøg og blev holdende)
|
||||
check("manuel kobling: kørende flow, egen vogn -> start", W.startAfterManualAttach({flowRunning = true, ownWagon = true}), true)
|
||||
check("manuel kobling: stoppet flow -> nej", W.startAfterManualAttach({flowRunning = false, ownWagon = true}), false)
|
||||
check("manuel kobling: AD kører allerede -> nej", W.startAfterManualAttach({flowRunning = true, ownWagon = true, adActive = true}), false)
|
||||
check("manuel kobling: fremmed vogn -> nej", W.startAfterManualAttach({flowRunning = true, ownWagon = false}), false)
|
||||
-- husk vogn ved hjemtur: kun når flowet KØRTE med den (stop); ikke ved oprydning efter indlæsning, hvor traktoren
|
||||
-- kan have et puljeredskab på fra et lån (in-game 29/9: Biogas Silage "huskede" en læssevogn fra markarbejdet)
|
||||
check("husk ved stop af kørende flow", W.shouldRememberOnHome("stop"), true)
|
||||
check("husk ikke ved oprydning efter indlæsning", W.shouldRememberOnHome("afterLoad"), false)
|
||||
-- forældreløs påkobling (hentet før genindlæsning, opgaven tabt): sættes tilbage efter 2 min uden ejer
|
||||
check("forældreløs: ingen ejer, længe nok -> hjem", W.orphanAction({owned = false, idleMs = W.ORPHAN_MS}), "home")
|
||||
check("forældreløs: har ejer -> intet", W.orphanAction({owned = true, idleMs = W.ORPHAN_MS}), "none")
|
||||
check("forældreløs: for kort tid -> vent", W.orphanAction({owned = false, idleMs = 1000}), "wait")
|
||||
-- parkeret sæt på traktorens egen plads er ikke forældreløst (in-game 29/9: vinmarks-REX blev skilt ad)
|
||||
check("forældreløs: står på egen parkering -> intet", W.orphanAction({owned = false, atOwnPark = true, idleMs = W.ORPHAN_MS}), "none")
|
||||
|
||||
-- rækkefølge for ledige vogne i flow-guiden: navnematch, så disponible, så resten (alfabetisk)
|
||||
local ranked = W.rankWagons("Sugarbeet Cut", {
|
||||
{id = "a", label = "P Tedder"}, {id = "b", label = "P Disponibel Vogn 5"},
|
||||
{id = "c", label = "P Sugarbeet Cut vogn 1"}, {id = "d", label = "P Disponibel Vogn 4"}, {id = "e", label = "P Bredal vogn"}})
|
||||
local order = {}
|
||||
for _, w in ipairs(ranked) do table.insert(order, w.id) end
|
||||
check("rank: navn, disponible, resten", table.concat(order, ","), "c,d,b,e,a")
|
||||
check("rank: tom liste", #W.rankWagons("X", {}), 0)
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,113 +0,0 @@
|
||||
-- Kør: luajit tests/test_adGuide.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adGuide.lua")
|
||||
local G = ADGuide
|
||||
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
|
||||
|
||||
-- trin a..e + summary; b vises kun når draft.showB, d aldrig
|
||||
local def = {
|
||||
order = {"a", "b", "c", "d", "e", "summary"},
|
||||
isVisible = function(step, guide)
|
||||
if step == "b" then return guide.draft.showB == true end
|
||||
if step == "d" then return false end
|
||||
return true
|
||||
end,
|
||||
}
|
||||
|
||||
local guide = G.new(def, {showB = false, value = 1}, {x = 1})
|
||||
check("G1 start på første synlige", guide.step, "a")
|
||||
local index, total = G.position(guide)
|
||||
check("G1 position", index, 1)
|
||||
check("G1 total over synlige", total, 4)
|
||||
check("G1 synlige", table.concat(G.visibleSteps(guide), ","), "a,c,e,summary")
|
||||
|
||||
-- næste springer skjulte over
|
||||
local g2, event = G.next(guide)
|
||||
check("G2 næste springer b over", g2.step, "c")
|
||||
check("G2 intet event", event, nil)
|
||||
check("G2 gammel guide urørt", guide.step, "a")
|
||||
local g3 = G.next(g2)
|
||||
check("G3 springer d over", g3.step, "e")
|
||||
check("G3 position", (G.position(g3)), 3)
|
||||
local g4 = G.next(g3)
|
||||
check("G4 summary er sidst", G.isLast(g4), true)
|
||||
local _, done = G.next(g4)
|
||||
check("G4 næste på sidste = done", done, "done")
|
||||
|
||||
-- tilbage
|
||||
local back = G.back(g3)
|
||||
check("G5 tilbage til c", back.step, "c")
|
||||
local _, cancel = G.back(guide)
|
||||
check("G5 tilbage på første = cancel", cancel, "cancel")
|
||||
|
||||
-- valg der gør et trin synligt ændrer tælling og næste
|
||||
local shown = G.withDraft(guide, {showB = true, value = 1})
|
||||
check("G6 withDraft ny guide", guide.draft.showB, false)
|
||||
check("G6 b synlig", G.next(shown).step, "b")
|
||||
check("G6 total", select(2, G.position(shown)), 5)
|
||||
|
||||
-- aktuelt trin bliver usynligt: næste går til næste synlige i rækkefølgen, position nil
|
||||
local onB = G.new(def, {showB = true}, {}, {step = "b"})
|
||||
local hidden = G.withDraft(onB, {showB = false})
|
||||
check("G7 position nil for skjult trin", (G.position(hidden)), nil)
|
||||
check("G7 næste efter skjult", G.next(hidden).step, "c")
|
||||
check("G7 tilbage fra skjult", G.back(hidden).step, "a")
|
||||
|
||||
-- spring (Ret) fra oversigt-trinnet: næste -> tilbage til summary, ikke single
|
||||
local fromSummary = G.jump(g4, "c", "summary")
|
||||
check("G8 spring trin", fromSummary.step, "c")
|
||||
check("G8 single", fromSummary.single, true)
|
||||
local returned, ev = G.next(fromSummary)
|
||||
check("G8 retur til summary", returned.step, "summary")
|
||||
check("G8 ikke single mere", returned.single, false)
|
||||
check("G8 intet event", ev, nil)
|
||||
-- tilbage i spring gendanner kladden fra før springet
|
||||
local changed = G.withDraft(fromSummary, {showB = false, value = 99})
|
||||
local undone = G.back(changed)
|
||||
check("G9 tilbage gendanner kladde", undone.draft.value, 1)
|
||||
check("G9 tilbage til summary", undone.step, "summary")
|
||||
|
||||
-- spring fra fanens oversigt (close): næste = done, tilbage = cancel
|
||||
local single = G.new(def, {value = 5}, {}, {step = "e", single = true, returnTo = "close"})
|
||||
local _, singleDone = G.next(single)
|
||||
check("G10 single close næste = done", singleDone, "done")
|
||||
local _, singleCancel = G.back(single)
|
||||
check("G10 single close tilbage = cancel", singleCancel, "cancel")
|
||||
check("G10 single isLast", G.isLast(single), true)
|
||||
|
||||
-- withContext
|
||||
local ctxGuide = G.withContext(guide, {x = 2})
|
||||
check("G11 ny context", ctxGuide.context.x, 2)
|
||||
check("G11 gammel context", guide.context.x, 1)
|
||||
|
||||
-- deepCopy / toggleItem
|
||||
local original = {list = {1, 2}, nested = {a = {b = 1}}}
|
||||
local copy = G.deepCopy(original)
|
||||
copy.list[1] = 9
|
||||
copy.nested.a.b = 7
|
||||
check("G12 deepCopy liste", original.list[1], 1)
|
||||
check("G12 deepCopy indlejret", original.nested.a.b, 1)
|
||||
local list = {"x", "y"}
|
||||
check("G13 toggle til", table.concat(G.toggleItem(list, "z", true), ","), "x,y,z")
|
||||
check("G13 ingen dublet", table.concat(G.toggleItem(list, "x", true), ","), "x,y")
|
||||
check("G13 toggle fra", table.concat(G.toggleItem(list, "x", false), ","), "y")
|
||||
check("G13 original urørt", #list, 2)
|
||||
|
||||
-- tekster
|
||||
local texts = {greet = "Hej %s", n = "%d stk", title = "Afgrøde", plain = "Ingen"}
|
||||
local function T(key) return texts[key] or key end
|
||||
check("G14 label args", G.labelText({key = "greet", args = {"Bo"}}, T), "Hej Bo")
|
||||
check("G14 label tekst", G.labelText({text = "rå"}, T), "rå")
|
||||
check("G14 manglende args fejler ikke", G.labelText({key = "n"}, T), "%d stk")
|
||||
check("G14 prefix", G.labelText({key = "plain", prefix = "Mælk: "}, T), "Mælk: Ingen")
|
||||
check("G14 nil label", G.labelText(nil, T), "")
|
||||
check("G15 linje", G.lineText({step = "crop", title = {key = "title"}, parts = {{key = "plain"}, {key = "n", args = {3}}}}, T), "Afgrøde: Ingen, 3 stk")
|
||||
check("G15 linje uden dele", G.lineText({step = "crop", title = {key = "title"}, parts = {}}, T), "Afgrøde: -")
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,129 +0,0 @@
|
||||
-- Kør: luajit tests/test_adHitch.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adHitch.lua")
|
||||
local H = ADHitch
|
||||
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
|
||||
-- markørnavne -> handling + navn (store/små bogstaver ligegyldigt)
|
||||
local function parsed(name) local a, n = H.parseMarker(name) return tostring(a) .. "|" .. tostring(n) end
|
||||
check("navn: Connect Bredal", parsed("Connect Bredal"), "attach|Bredal")
|
||||
check("navn: små bogstaver", parsed("connect bredal"), "attach|bredal")
|
||||
check("navn: dansk Kobl", parsed("Kobl Vogn 2"), "attach|Vogn 2")
|
||||
check("navn: kun Connect", parsed("Connect"), "attach|")
|
||||
check("navn: Connected er ikke en koblingsmarkør", parsed("Connected silo"), "nil|nil")
|
||||
check("navn: almindelig markør", parsed("Bredal"), "nil|nil")
|
||||
check("navn: nil", parsed(nil), "nil|nil")
|
||||
check("navn: Afkobl Bredal", parsed("Afkobl Bredal"), "detach|Bredal")
|
||||
check("navn: Kobl af er afkobling, ikke kobling", parsed("Kobl af Bredal"), "detach|Bredal")
|
||||
check("navn: Detach", parsed("detach"), "detach|")
|
||||
check("navn: Disconnect", parsed("Disconnect Vogn"), "detach|Vogn")
|
||||
-- bagerste redskaber: kun led bag traktoren (z < 0 i traktorens rum), ikke frontvægt/frontlæsser
|
||||
local rear = H.rearImplements({{name = "vægt", z = 3.1}, {name = "Bredal", z = -2.4}, {name = "læsser", z = 1.5}, {name = "plov", z = -1}})
|
||||
check("bag: kun bagerste", #rear, 2)
|
||||
check("bag: første", rear[1].name, "Bredal")
|
||||
-- ankomst: AD er stoppet, traktoren står stille tæt på markøren
|
||||
check("ankomst: stoppet ved markør", H.hasArrived({adActive = false, distance = 1.2, speed = 0.1}), true)
|
||||
check("ankomst: AD kører stadig", H.hasArrived({adActive = true, distance = 0.5, speed = 0}), false)
|
||||
check("ankomst: for langt væk", H.hasArrived({adActive = false, distance = 9, speed = 0}), false)
|
||||
check("ankomst: ruller stadig", H.hasArrived({adActive = false, distance = 1, speed = 3}), false)
|
||||
-- efter at have bakket hen til vognen står traktoren længere fra markøren
|
||||
check("ankomst: bakket til vognen, udvidet afstand", H.hasArrived({adActive = false, distance = 9, speed = 0, arriveDistance = 13}), true)
|
||||
local crept = H.step(H.newJob(1, "Connect x", "attach"), {adActive = false, distance = 9, speed = 3, arriveDistance = 13}, 4000, false)
|
||||
check("vagt: bakket, ruller lidt -> ikke 'stoppedAway'", crept.state, "driving")
|
||||
-- vagten: efter ankomst forsøges koblingen et stykke tid, derefter opgives den
|
||||
local job = H.newJob(25, "Connect Bredal", "attach")
|
||||
check("job: venter på AD", job.state, "driving")
|
||||
check("job: handling", job.action, "attach")
|
||||
H.step(job, {adActive = true, distance = 30, speed = 10}, 1000, false)
|
||||
check("job: kører stadig", job.state, "driving")
|
||||
H.step(job, {adActive = false, distance = 1, speed = 0}, 500, false)
|
||||
check("job: ankommet, kobling ikke lykkedes endnu", job.state, "acting")
|
||||
H.step(job, {adActive = false, distance = 1, speed = 0}, 500, true)
|
||||
check("job: koblet på", job.state, "done")
|
||||
local failJob = H.newJob(25, "Connect Bredal", "attach")
|
||||
H.step(failJob, {adActive = false, distance = 1, speed = 0}, 500, false)
|
||||
for _ = 1, 20 do H.step(failJob, {adActive = false, distance = 1, speed = 0}, 500, false) end
|
||||
check("job: opgivet efter vagt-tid", failJob.state, "failed")
|
||||
check("job: årsag", failJob.reason, "noAttachable")
|
||||
local dropJob = H.newJob(9, "Afkobl Bredal", "detach")
|
||||
H.step(dropJob, {adActive = false, distance = 1, speed = 0}, 500, false)
|
||||
for _ = 1, 45 do H.step(dropJob, {adActive = false, distance = 1, speed = 0}, 500, false) end
|
||||
check("job: afkobling opgivet", dropJob.reason, "nothingToDetach")
|
||||
local lostJob = H.newJob(25, "Connect Bredal", "attach")
|
||||
H.step(lostJob, {adActive = false, distance = 40, speed = 0}, 500, false)
|
||||
check("job: AD ikke startet endnu (opstart-frist)", lostJob.state, "driving")
|
||||
H.step(lostJob, {adActive = true, distance = 40, speed = 5}, 500, false)
|
||||
H.step(lostJob, {adActive = false, distance = 40, speed = 0}, 500, false)
|
||||
check("job: AD stoppede langt fra markøren", lostJob.state, "failed")
|
||||
check("job: årsag stoppet undervejs", tostring(lostJob.reason):find("^stoppedAway") ~= nil, true)
|
||||
-- tur: "Connect Bredal > Start" -> to markørnavne
|
||||
local route = H.parseTrip("Connect", "Bredal", ">", "Start", ">", "Afkobl", "Bredal", ">", "Start")
|
||||
check("tur: antal trin", #route, 4)
|
||||
check("tur: første", route[1], "Connect Bredal")
|
||||
check("tur: tredje", route[3], "Afkobl Bredal")
|
||||
check("tur: ét trin", #H.parseTrip("Start"), 1)
|
||||
check("tur: tomme trin droppes", #H.parseTrip("Start", ">", ">", "Stop"), 2)
|
||||
-- trin: handling pr. markør; almindelige markører = ankom (og vent, hvis ikke sidste)
|
||||
check("trin: connect", H.stepAction("Connect Bredal"), "attach")
|
||||
check("trin: afkobl", H.stepAction("Afkobl Bredal"), "detach")
|
||||
check("trin: almindelig", H.stepAction("Start"), "arrive")
|
||||
check("vent: mellem trin", H.waitAfter("arrive", 2, 4), H.ROUTE_WAIT_MS)
|
||||
check("vent: ikke efter sidste", H.waitAfter("arrive", 4, 4), 0)
|
||||
check("vent: ikke efter kobling", H.waitAfter("attach", 1, 4), 0)
|
||||
local arriveJob = H.newJob(1, "Start", "arrive")
|
||||
H.step(arriveJob, {adActive = false, distance = 1, speed = 0}, 250, false)
|
||||
H.step(arriveJob, {adActive = false, distance = 1, speed = 0}, 250, true)
|
||||
check("trin: ankom er færdig", arriveJob.state, "done")
|
||||
local markers = {{name = "Start", id = 1}, {name = "Connect Bredal", id = 9}}
|
||||
check("markør: navn uden hensyn til store/små", H.findMarker(markers, "connect bredal").id, 9)
|
||||
check("markør: ukendt", H.findMarker(markers, "Stald"), nil)
|
||||
-- Connect = kun kobl på; i en tur kobles vognen af ved sin egen AD-parkering
|
||||
check("connect: kobl på", H.resolveAction("attach", false), "attach")
|
||||
check("connect med vogn: stadig kobl på (ingen vending)", H.resolveAction("attach", false), "attach")
|
||||
check("vognens parkering: kobl af", H.resolveAction("arrive", true), "detach")
|
||||
check("almindelig markør", H.resolveAction("arrive", false), "arrive")
|
||||
check("afkobl forbliver afkobl", H.resolveAction("detach", false), "detach")
|
||||
-- hjemtur: hvert puljeredskabs parkering (afkobles der), så traktorens egen parkering
|
||||
local home = H.homeRoute({4}, 2)
|
||||
check("hjem: to trin", #home, 2); check("hjem: redskab først", home[1], 4); check("hjem: traktor sidst", home[2], 2)
|
||||
check("hjem: uden redskab kun traktor", #H.homeRoute({}, 2), 1)
|
||||
check("hjem: samme parkering to gange tæller én", #H.homeRoute({4, 4}, 2), 2)
|
||||
check("hjem: uden traktorparkering", #H.homeRoute({4}, nil), 1)
|
||||
-- afbrydelse: et hente-job kan annulleres (Stop alt, høst slut, spilleren overtager)
|
||||
ADFieldWork = ADFieldWork or {startAd = function() end}
|
||||
Logging = Logging or {info = function() end, warning = function() end}
|
||||
local stubEnv = {ADGraphManager = {getMapMarkerByWayPointId = function() return {name = "Connect X"} end}, AutoDrive = {MODE_DRIVETO = 1}}
|
||||
local tractorStub = {getFullName = function() return "T" end, stopAutoDrive = function(v) v.stopped = true end}
|
||||
H.fetch(stubEnv, tractorStub, 9, function() end)
|
||||
check("annullér: job fandtes", H.cancel(tractorStub), true)
|
||||
check("annullér: AD stoppet", tractorStub.stopped, true)
|
||||
check("annullér: intet job tilbage", H.cancel(tractorStub), false)
|
||||
-- afkobling må tage længere tid (redskabet skal måske foldes ud først)
|
||||
local foldJob = H.newJob(4, "Plov", "detach")
|
||||
H.step(foldJob, {adActive = false, distance = 1, speed = 0}, 500, false)
|
||||
for _ = 1, 16 do H.step(foldJob, {adActive = false, distance = 1, speed = 0}, 500, false) end
|
||||
check("afkobling: stadig i gang efter 8 s (folder ud)", foldJob.state, "acting")
|
||||
for _ = 1, 30 do H.step(foldJob, {adActive = false, distance = 1, speed = 0}, 500, false) end
|
||||
check("afkobling: opgivet efter sin frist", foldJob.state, "failed")
|
||||
-- afkobling bekræftes: først "done" når intet sidder bag traktoren efter et forsøg (in-game 29/9: loggede
|
||||
-- "koblede af", men vognen sad der stadig, og traktoren kørte med den)
|
||||
check("afkobl: redskab bagpå -> forsøg", H.detachStep(1, false), "attempt")
|
||||
check("afkobl: forsøgt, sidder der stadig -> forsøg igen", H.detachStep(1, true), "attempt")
|
||||
check("afkobl: forsøgt, intet bagpå -> færdig", H.detachStep(0, true), "done")
|
||||
check("afkobl: intet bagpå fra start", H.detachStep(0, false), "nothing")
|
||||
-- transport ved stop: sluk, løft, fold (in-game 29/9: redskaber kørte udfoldet hjem efter stop)
|
||||
check("transport: tændt+sænket+udfoldet", table.concat(H.stowActions({turnedOn = true, lowered = true, unfolded = true, canFold = true}), ","), "turnOff,raise,fold")
|
||||
check("transport: allerede klar", #H.stowActions({}), 0)
|
||||
check("transport: kan ikke foldes -> kun sluk/løft", table.concat(H.stowActions({turnedOn = true, lowered = true, unfolded = true, canFold = false}), ","), "turnOff,raise")
|
||||
check("transport: kun udfoldet", table.concat(H.stowActions({unfolded = true, canFold = true}), ","), "fold")
|
||||
check("vagt: stoppedAway med afstand", H.step(H.newJob(1, "Connect x", "attach"), {adActive = false, distance = 37.4, speed = 0}, 4000, false).reason, "stoppedAway (37 m fra markøren)")
|
||||
-- manuel AD "Kør til": markørnavn først, ellers puljeredskabets egen parkering = kobl af (fast sæt røres ikke)
|
||||
check("manuel: Connect-markør", H.manualAction("attach", false), "attach")
|
||||
check("manuel: puljeredskabets parkering", H.manualAction(nil, true), "detach")
|
||||
check("manuel: almindelig markør", H.manualAction(nil, false), nil)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,26 +0,0 @@
|
||||
-- Kør: luajit tests/test_adHitchCreep.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adHitchCreep.lua")
|
||||
local C = ADHitchCreep
|
||||
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
|
||||
-- plan: vognens trækøje i traktorens rum set fra krogen (x = til siden, z < 0 = bagved)
|
||||
check("plan: inden for rummelig rækkevidde -> kobl direkte", C.plan({x = 0.4, z = -2.0}), "attach")
|
||||
check("plan: 5 m lige bagved -> bak", C.plan({x = 0.3, z = -5.0}), "creep")
|
||||
check("plan: lidt til siden -> bak", C.plan({x = -1.2, z = -6.0}), "creep")
|
||||
check("plan: for langt til siden -> intet", C.plan({x = 2.5, z = -5.0}), "none")
|
||||
check("plan: for langt bagved -> intet", C.plan({x = 0, z = -12.0}), "none")
|
||||
check("plan: foran traktoren -> intet", C.plan({x = 0, z = 5.0}), "none")
|
||||
check("plan: ingen vogn -> intet", C.plan(nil), "none")
|
||||
-- skridt under bakningen
|
||||
check("skridt: vognen inden for rækkevidde -> færdig", C.step({inRange = true, distance = 2.5, startDistance = 6, elapsedMs = 3000}), "done")
|
||||
check("skridt: på vej -> kør", C.step({inRange = false, distance = 4, startDistance = 6, elapsedMs = 3000}), "drive")
|
||||
check("skridt: for længe -> giv op", C.step({inRange = false, distance = 4, startDistance = 6, elapsedMs = C.CREEP_TIMEOUT_MS}), "giveUp")
|
||||
check("skridt: kører væk fra vognen -> giv op", C.step({inRange = false, distance = 7.5, startDistance = 6, elapsedMs = 3000}), "giveUp")
|
||||
check("skridt: forbi vognen uden kobling -> giv op", C.step({inRange = false, distance = 0.3, startDistance = 6, elapsedMs = 3000, passed = true}), "giveUp")
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,79 +0,0 @@
|
||||
-- Kør: luajit tests/test_adLoadSwap.lua (fra repo-roden)
|
||||
-- Ren rangering af aflæsnings-siloer (lagermål for udkørsel og læs-bytte).
|
||||
|
||||
dofile("FS25_ADSmartPickup/adPrices.lua")
|
||||
dofile("FS25_ADSmartPickup/adLoadSwap.lua")
|
||||
local L = ADLoadSwap
|
||||
|
||||
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
|
||||
|
||||
-- LS1: markører et andet forløb HENTER fra (fx "Milk Storage Load") vælges sidst; ellers har-varen, så afstand
|
||||
local candidates = {
|
||||
{marker = {id = 1, name = "Milk Storage Load"}, hasFillType = true, distance = 100},
|
||||
{marker = {id = 2, name = "Milk Storage unload"}, hasFillType = true, distance = 400},
|
||||
{marker = {id = 3, name = "Silo tom"}, hasFillType = false, distance = 10},
|
||||
}
|
||||
local ranked = L.rankDumpCandidates(candidates, {[1] = true})
|
||||
check("LS1 afhentningsmarkør undgås", ranked[1].marker.id, 2)
|
||||
check("LS1 derefter tom silo før afhentning", ranked[2].marker.id, 3)
|
||||
check("LS1 afhentning sidst (stadig mulig)", ranked[3].marker.id, 1)
|
||||
local plain = L.rankDumpCandidates(candidates, nil)
|
||||
check("LS1 uden undgå-liste: har varen + nærmest", plain[1].marker.id, 1)
|
||||
|
||||
-- LS2: lagermål — rene siloer altid; egne fabrikker der bruger varen som input kun når tilladt (udkørsel)
|
||||
local SLURRY, MILK = 20, 21
|
||||
g_currentMission = {getFarmId = function() return 1 end}
|
||||
local biogas = {spec_productionPoint = {productionPoint = {productions = {{inputs = {{type = SLURRY, amount = 1}}}}}}, getOwnerFarmId = function() return 1 end}
|
||||
local foreignBiogas = {spec_productionPoint = {productionPoint = {productions = {{inputs = {{type = SLURRY, amount = 1}}}}}}, getOwnerFarmId = function() return 0 end}
|
||||
local silo = {spec_silo = {}}
|
||||
local barn = {spec_silo = {}, getHusbandryFillLevel = function() return 0 end}
|
||||
check("LS2 silo", L.isStoreTarget({owningPlaceable = silo}, SLURRY, nil, false), true)
|
||||
check("LS2 stald aldrig", L.isStoreTarget({owningPlaceable = barn}, SLURRY, nil, true), false)
|
||||
check("LS2 biogas kun når fabrikker tilladt", L.isStoreTarget({owningPlaceable = biogas}, SLURRY, nil, false), false)
|
||||
check("LS2 biogas tager gylle", L.isStoreTarget({owningPlaceable = biogas}, SLURRY, nil, true), true)
|
||||
check("LS2 biogas tager ikke mælk", L.isStoreTarget({owningPlaceable = biogas}, MILK, nil, true), false)
|
||||
check("LS2 fremmed anlæg aldrig", L.isStoreTarget({owningPlaceable = foreignBiogas}, SLURRY, nil, true), false)
|
||||
check("LS2 hjemmet selv aldrig", L.isStoreTarget({owningPlaceable = biogas}, SLURRY, {owningPlaceable = biogas}, true), false)
|
||||
g_currentMission = nil
|
||||
|
||||
-- LS3: tvunget lagermål — kun de valgte markører, i rækkefølge; første der tager varen, har plads og kan nås
|
||||
do
|
||||
local stations = {
|
||||
[101] = {accepts = {[SLURRY] = true}, free = 0}, -- BGA fuld
|
||||
[102] = {accepts = {[SLURRY] = true}, free = 900000}, -- gylletank
|
||||
[103] = {accepts = {[MILK] = true}, free = 900000}, -- tager ikke gylle
|
||||
}
|
||||
for _, st in pairs(stations) do
|
||||
st.getIsFillTypeSupported = function(self, ft) return self.accepts[ft] == true end
|
||||
st.getFreeCapacity = function(self) return self.free end
|
||||
end
|
||||
ADSmartPickup = {getFarmId = function() return 1 end, getUnloadStationAtWayPointId = function(_, id) return stations[id] end}
|
||||
local adEnv = {ADGraphManager = {getMapMarkerByWayPointId = function(_, id) return {id = id, name = "m" .. id} end}}
|
||||
local vehicle = {}
|
||||
local all = function() return true end
|
||||
check("LS3 fuld BGA springes over -> tank", L.findForcedDumpMarker(adEnv, vehicle, SLURRY, 20000, {101, 102}, all).id, 102)
|
||||
stations[101].free = 500000
|
||||
check("LS3 BGA med plads vælges først", L.findForcedDumpMarker(adEnv, vehicle, SLURRY, 20000, {101, 102}, all).id, 101)
|
||||
local none, why = L.findForcedDumpMarker(adEnv, vehicle, SLURRY, 20000, {103}, all)
|
||||
check("LS3 intet valgt mål tager varen -> nil", none, nil)
|
||||
check("LS3 forklaring", why ~= nil and why:find("valgt", 1, true) ~= nil, true)
|
||||
check("LS3 ikke nåelig -> næste", L.findForcedDumpMarker(adEnv, vehicle, SLURRY, 20000, {101, 102}, function(id) return id ~= 101 end).id, 102)
|
||||
ADSmartPickup = nil
|
||||
end
|
||||
|
||||
-- LQ: flydende varer lægges ikke automatisk i en fabrik (AIO-risten, in-game 26/9)
|
||||
check("LQ gylle er flydende", ADLoadSwap.isLiquidName("LIQUIDMANURE"), true)
|
||||
check("LQ digestat er flydende", ADLoadSwap.isLiquidName("digestate"), true)
|
||||
check("LQ hvede er ikke flydende", ADLoadSwap.isLiquidName("WHEAT"), false)
|
||||
check("LQ ukendt", ADLoadSwap.isLiquidName(nil), false)
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,40 +0,0 @@
|
||||
-- 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 WHEAT, BARLEY = 1, 2
|
||||
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
|
||||
|
||||
-- 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)
|
||||
-- in-game 27/9: F111 (hvede) valgte roeoptageren, fordi manuelle høstere slap for skærebord-tjekket
|
||||
check("manuel roeoptager (fast optager) kan ikke høste hvede", M.acceptsHarvester({manual = true, fruits = {[BARLEY] = true}}, WHEAT), false)
|
||||
check("manuel mejetærsker med passende skærebord monteret", M.acceptsHarvester({manual = true, fruits = {[WHEAT] = true}}, WHEAT), true)
|
||||
|
||||
-- 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)
|
||||
@@ -1,86 +0,0 @@
|
||||
-- Kør: luajit tests/test_adMonthView.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFieldModel.lua")
|
||||
dofile("FS25_ADSmartPickup/adMonthView.lua")
|
||||
local V, M = ADMonthView, ADFieldModel
|
||||
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
|
||||
|
||||
-- MV1: perioder
|
||||
check("MV1 næste periode", V.nextPeriod(5), 6)
|
||||
check("MV1 vinter -> forår", V.nextPeriod(12), 1)
|
||||
|
||||
-- MV2: såvindue (hvede sås i periode 6-7)
|
||||
local function plantableIn(name, period) return name == "WHEAT" and (period == 6 or period == 7) end
|
||||
check("MV2 lukker", V.sowWindow(plantableIn, "WHEAT", 7), "closes")
|
||||
check("MV2 åbent", V.sowWindow(plantableIn, "WHEAT", 6), "open")
|
||||
check("MV2 lukket", V.sowWindow(plantableIn, "WHEAT", 8), "closed")
|
||||
|
||||
-- MV3: visning (Dakota-kartofler: harvestReady 6 -> dead 7 i EARLY_AUTUMN = periode 7)
|
||||
local potato = {witheredState = 7, growthDataSeasonal = {periods = {[7] = {updates = {{startState = 6, endState = 7}}}}}}
|
||||
check("MV3 visner næste måned", V.withersIn(potato, 7, 6), true)
|
||||
check("MV3 ikke måneden før", V.withersIn(potato, 6, 6), false)
|
||||
check("MV3 ukendte data", V.withersIn({witheredState = 7}, 7, 6), false)
|
||||
|
||||
-- MV4: fremdrift
|
||||
local harvest = M.with(M.newField(15), {stage = "harvesting", kind = "harvest"})
|
||||
check("MV4 CP vinder uden arealtal", V.progress(harvest, {readyRatio = 0.9}, 0.5), 0.5)
|
||||
-- in-game 27/9 F90: halvt slået græs viste 0 % — slået græs tæller ikke i readyRatio's nævner
|
||||
check("MV4 slåning ud fra hele arealet", V.progress(M.with(harvest, {stage = "mowing"}), {readyRatio = 1, standingRatio = 0.5}, nil), 0.5)
|
||||
check("MV4 areal slår CP (kursus genstartet = 0)", V.progress(harvest, {readyRatio = 1, standingRatio = 0.4}, 0), 0.6)
|
||||
check("MV4 pleje bruger stadig CP", V.progress(M.with(harvest, {stage = "fertilizing"}), {standingRatio = 0.4}, 0.3), 0.3)
|
||||
check("MV4 høst ud fra afgrøde", V.progress(harvest, {readyRatio = 0.25}, nil), 0.75)
|
||||
check("MV4 kultivering ukendt", V.progress(M.with(harvest, {stage = "cultivating"}), {readyRatio = 0}, nil), nil)
|
||||
check("MV4 tekst", V.percentText(0.504), "50 %")
|
||||
check("MV4 tekst ukendt", V.percentText(nil), "–")
|
||||
|
||||
-- MV5: hel fane
|
||||
local fields = {
|
||||
[15] = M.with(harvest, {plan = {sow = true, sowFruit = "WHEAT", soil = "cultivate", straw = "chop"}}),
|
||||
[16] = M.with(M.newField(16), {stage = "harvesting", kind = "harvest"}),
|
||||
[18] = M.with(M.newField(18), {stage = "sowing", kind = "harvest", plan = {sow = true, sowFruit = "WHEAT"}}),
|
||||
[20] = M.with(M.newField(20), {stage = "fertilizing"}),
|
||||
[21] = M.newField(21),
|
||||
[22] = M.with(M.newField(22), {plan = {targetFruit = "WHEAT"}}),
|
||||
[23] = M.with(M.newField(23), {stage = "ready"}),
|
||||
}
|
||||
local infos = {
|
||||
{id = 15, kind = "harvest", readyRatio = 0.5, cropTitle = "Hvede", needs = {}},
|
||||
{id = 16, kind = "harvest", readyRatio = 1, cropTitle = "Kartofler", growthState = 6},
|
||||
{id = 18, kind = "bare", readyRatio = 0},
|
||||
{id = 20, kind = "harvest", readyRatio = 0},
|
||||
{id = 21, kind = "bare"},
|
||||
{id = 22, kind = "bare"},
|
||||
{id = 23, kind = "harvest", readyRatio = 1},
|
||||
}
|
||||
local ctx = {period = 7, plantableIn = plantableIn,
|
||||
withers = function(info, nextPeriod) return info.id == 16 and nextPeriod == 8 end,
|
||||
cpProgress = function(id) return id == 18 and 0.1 or nil end}
|
||||
local view = V.build(infos, fields, ctx)
|
||||
check("MV5 aktive marker (21 venter, 22 kommer, 23 passiv klar)", #view.rows, 4)
|
||||
check("MV5 kritiske", view.critical, 3)
|
||||
check("MV5 høst før sidste såmåned først (id)", view.rows[1].id, 15)
|
||||
check("MV5 høst -> såning senere lukker", view.rows[1].window, "lastSowMonth")
|
||||
check("MV5 høst næste trin", view.rows[1].nextStage, "cultivating")
|
||||
check("MV5 høst der visner", view.rows[2].id, 16)
|
||||
check("MV5 vindue høst", view.rows[2].window, "harvestCloses")
|
||||
check("MV5 sidste såmåned", view.rows[3].id, 18)
|
||||
check("MV5 vindue såning", view.rows[3].window, "lastSowMonth")
|
||||
check("MV5 såafgrøde", view.rows[3].sowFruit, "WHEAT")
|
||||
check("MV5 CP-fremdrift", view.rows[3].progress, 0.1)
|
||||
check("MV5 pleje sidst", view.rows[4].id, 20)
|
||||
check("MV5 pleje intet næste", view.rows[4].nextStage, nil)
|
||||
check("MV5 kommer: F22 hvede", view.upcoming[1] and view.upcoming[1].id, 22)
|
||||
check("MV5 kommer: lukker", view.upcoming[1] and view.upcoming[1].closes, true)
|
||||
|
||||
-- MV6: klargøring før såning med lukkende vindue er kritisk
|
||||
local cultivating = M.with(M.newField(30), {stage = "cultivating", plan = {sow = true, sowFruit = "WHEAT"}})
|
||||
check("MV6 kultivering før sidste såmåned", V.window(cultivating, {kind = "bare"}, ctx), "lastSowMonth")
|
||||
check("MV6 aktiv klar (auto)", V.isActive(M.with(M.newField(31), {stage = "ready", plan = {mode = "auto"}})), true)
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,20 +0,0 @@
|
||||
-- Kør: luajit tests/test_adNames.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adNames.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 byId = ADNames.fromRecords({{id = "placeableA", name = "Gårdsilo 10"}, {id = "", name = "x"}, {id = "placeableB", name = " "}, {id = "placeableC"}})
|
||||
check("navn indlæst", byId.placeableA, "Gårdsilo 10")
|
||||
check("tomt id springes over", byId[""], nil)
|
||||
check("blankt navn springes over", byId.placeableB, nil)
|
||||
check("manglende navn springes over", byId.placeableC, nil)
|
||||
ADNames.byId = byId
|
||||
check("opslag på bygning", ADNames.forPlaceable({uniqueId = "placeableA"}), "Gårdsilo 10")
|
||||
check("bygning uden eget navn", ADNames.forPlaceable({uniqueId = "placeableZ"}), nil)
|
||||
check("uden uniqueId", ADNames.forPlaceable({}), nil)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,49 +0,0 @@
|
||||
-- Kør: luajit tests/test_adNaming.lua (fra repo-roden)
|
||||
-- Automatiske navne på bygninger uden eget navn (in-game 27/9: "36 Farma" hed alle "Farma 500 + Obi 1000").
|
||||
dofile("FS25_ADSmartPickup/adNaming.lua")
|
||||
local N = ADNaming
|
||||
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
|
||||
|
||||
-- næste ledige nummer
|
||||
check("næste efter 1-5", N.nextNumber({"Svinestald 1", "Svinestald 5", "Kostald 7"}, "Svinestald"), 6)
|
||||
check("ingen endnu -> 1", N.nextNumber({"Kostald 1"}, "Svinestald"), 1)
|
||||
check("prefix-præfiks forveksles ikke", N.nextNumber({"Gårdsilo ekstra ved 1", "Gårdsilo 3"}, "Gårdsilo"), 4)
|
||||
|
||||
-- navne efter type
|
||||
local function one(b, existing) return N.assign({b}, existing or {})[b.id] end
|
||||
check("svinestald", one({id = "a", kind = "husbandry", animal = "PIG"}, {x = "Svinestald 5"}), "Svinestald 6")
|
||||
check("gedestald", one({id = "a", kind = "husbandry", animal = "GOAT"}), "Gedestald 1")
|
||||
check("ukendt dyr -> Stald", one({id = "a", kind = "husbandry", animal = "LLAMA"}), "Stald 1")
|
||||
check("silo i gårdens klynge", one({id = "a", kind = "silo", inFarmCluster = true}, {x = "Gårdsilo 35"}), "Gårdsilo 36")
|
||||
check("silo ved mark", one({id = "a", kind = "silo", nearestField = {name = "F118", distance = 80}}), "F118 marksilo")
|
||||
check("anden marksilo ved samme mark", one({id = "a", kind = "silo", nearestField = {name = "F118", distance = 80}}, {x = "F118 marksilo"}), "F118 marksilo 2")
|
||||
check("silo langt fra alt", one({id = "a", kind = "silo", nearestField = {name = "F1", distance = 900}}), "Silo 1")
|
||||
check("palleskur", one({id = "a", kind = "objectStorage"}, {x = "Palleskur 1"}), "Palleskur 2")
|
||||
check("fabrik: butiksnavnet", one({id = "a", kind = "factory", storeName = "Sugarbeet Crusher"}), "Sugarbeet Crusher")
|
||||
check("fabrik nr. 2 med samme navn", one({id = "a", kind = "factory", storeName = "Roesnitter"}, {x = "Roesnitter"}), "Roesnitter 2")
|
||||
check("salgssted navngives ikke", one({id = "a", kind = "sell", storeName = "Mølle"}), nil)
|
||||
check("har allerede navn -> urørt", N.assign({{id = "a", kind = "husbandry", animal = "PIG"}}, {a = "Min stald"}).a, nil)
|
||||
|
||||
-- flere nye i samme runde får hver sit nummer
|
||||
local two = N.assign({{id = "a", kind = "husbandry", animal = "COW"}, {id = "b", kind = "husbandry", animal = "COW"}}, {})
|
||||
check("to nye kostalde: 1 og 2", (two.a or "") .. "|" .. (two.b or ""), "Kostald 1|Kostald 2")
|
||||
|
||||
|
||||
-- markmaskiner der stadig har modelnavnet (flow-traktorer navngives af flowet)
|
||||
check("læssevogn nr. 5", N.fleetName("collector", nil, {"Læssevogn 1", "Læssevogn 4"}), "Læssevogn 5")
|
||||
check("skårlægger", N.fleetName("mower", nil, {}), "Skårlægger 1")
|
||||
check("høster: roeoptager", N.fleetName("harvester", "beet", {"Roeoptager 1"}), "Roeoptager 2")
|
||||
check("høster: finsnitter", N.fleetName("harvester", "forage", {}), "Finsnitter 1")
|
||||
check("høster: mejetærsker", N.fleetName("harvester", nil, {"Mejetærsker"}), "Mejetærsker 1")
|
||||
check("tømmevogn", N.fleetName("unloader", nil, {}), "Tømmevogn 1")
|
||||
check("gødningsspreder", N.fleetName("fertilizer", nil, {}), "Gødningsspreder 1")
|
||||
check("ukendt rolle -> nil", N.fleetName("mystery", nil, {}), nil)
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,646 +0,0 @@
|
||||
-- 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)
|
||||
|
||||
-- O16: kilden gav LIDT og så intet mere ("ingen mere last") -> kilden hviler, næste tur tager en anden
|
||||
-- (in-game 26/9: æg-vognen kørte til Æg Pickup 4 tre gange i træk; én palle inden for rækkevidde, resten udenfor)
|
||||
local v16 = newVehicle("Thin", 0, 0, 20000)
|
||||
runs[v16] = outboundRun(16, {1, 2}, {{fillType = "SUGAR", policy = "sell"}}, {collectRound = false})
|
||||
ADOutbound.resetEmptySources()
|
||||
local choice16 = ADOutbound.choosePickup(adEnv, v16)
|
||||
local first16 = choice16.markerIndex
|
||||
local task16 = newTask(v16, choice16.markerIndex == mSilo.markerIndex and 2 or 1)
|
||||
v16.atTarget = true
|
||||
v16.trailers.level = 500
|
||||
ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
|
||||
ADOutbound.beforeLoadUpdate(adEnv, task16, 16000)
|
||||
check("O16 kører med det den fik", task16.finishedCalls, 1)
|
||||
check("O16 log", lastLog("ingen mere last") ~= nil, true)
|
||||
v16.trailers.level = 0
|
||||
local choice16b = ADOutbound.choosePickup(adEnv, v16)
|
||||
check("O16 næste tur tager en anden kilde", choice16b ~= nil and choice16b.markerIndex ~= first16, true)
|
||||
ADOutbound.resetEmptySources() -- (senere tests forventer alle kilder ledige)
|
||||
|
||||
|
||||
-- O14 (I6): rutetjek kun for salgssteder over tærsklen, bedste først, stop ved første nåelige
|
||||
-- (rutetjekket går markør->markør: hjem -> salgssted tælles som ét tjek pr. salgssted)
|
||||
local routeChecks = 0
|
||||
local originalGetPathTo = adEnv.ADGraphManager.getPathTo
|
||||
adEnv.ADGraphManager.getPathTo = function(_, vehicle, id) routeChecks = routeChecks + 1 return originalGetPathTo(_, vehicle, id) end
|
||||
local originalFromTo = adEnv.ADGraphManager.pathFromTo
|
||||
adEnv.ADGraphManager.pathFromTo = function(self, from, to)
|
||||
if to == 10 or to == 11 then routeChecks = routeChecks + 1 end -- udturen til et salgssted
|
||||
return originalFromTo(self, from, to)
|
||||
end
|
||||
ADWaitPool.resetMarkerRoutes()
|
||||
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
|
||||
ADWaitPool.resetMarkerRoutes()
|
||||
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)
|
||||
-- O24: stropperne spændes igen når pålæsningen er færdig (de blev løsnet 25 m før forrige salg;
|
||||
-- in-game 23/9: Dragon tabte paller på vejen to gange)
|
||||
local v24 = newVehicle("Dragon5", 100, 0, 1, true)
|
||||
v24.carriable = {}
|
||||
v24.trailers[1].setAllTensionBeltsActive = function(self, active) self.beltsActive = active end
|
||||
runs[v24] = outboundRun(27, {3}, {{fillType = "STRAW_BASKET", policy = "sell"}})
|
||||
sources[3].stored[BASKET] = 3
|
||||
quotes["selling station"][BASKET] = {percent = 95, price = 1}
|
||||
ADOutbound.choosePickup(adEnv, v24)
|
||||
local task24 = newTask(v24, 3)
|
||||
v24.trailers.level = 3
|
||||
adEnv.LoadAtDestinationTask.finished(task24)
|
||||
check("O24 stropper spændt efter pålæsning", v24.trailers[1].beltsActive, true)
|
||||
check("O24 tur planlagt", ADOutbound.getTripFor(v24) ~= nil, true)
|
||||
check("O24 original finished kaldt", calls.finished, 2)
|
||||
local v24b = newVehicle("Tip4", 0, 0, 20000)
|
||||
v24b.trailers[1].setAllTensionBeltsActive = function(self, active) self.beltsActive = active end
|
||||
adEnv.LoadAtDestinationTask.finished(newTask(v24b, 1))
|
||||
check("O24 tipvogn uden tur: rører ikke stropper", v24b.trailers[1].beltsActive, nil)
|
||||
|
||||
-- O-pool: to kilder hvis stationer deler lager (mælketanke som udvidelser) = ét stop i runden
|
||||
do
|
||||
local pool = {}
|
||||
local st1, st2 = sources[1].station, sources[2].station
|
||||
sources[1].station = {name = "tankA", sourceStorages = {[pool] = pool}}
|
||||
sources[2].station = {name = "tankB", sourceStorages = {[pool] = pool}}
|
||||
local k1, k2 = sources[1].kind, sources[2].kind
|
||||
sources[1].kind, sources[2].kind = "silo", "silo"
|
||||
local vPool = newVehicle("Tank", 0, 0, 10000)
|
||||
runs[vPool] = outboundRun(77, {1, 2}, {{fillType = "SUGAR", policy = "sell"}}, {collectRound = true})
|
||||
local inputPool = ADOutbound.buildPlanInput(adEnv, runs[vPool], vPool)
|
||||
local ids = {}
|
||||
for _, product in ipairs(inputPool.products) do
|
||||
for _, source in ipairs(product.sources) do table.insert(ids, tostring(source.wayPointId)) end
|
||||
end
|
||||
check("O-pool kun første kilde", table.concat(ids, ","), "1")
|
||||
check("O-pool logget", lastLog("deler lager") ~= nil, true)
|
||||
sources[1].station, sources[2].station = st1, st2
|
||||
sources[1].kind, sources[2].kind = k1, k2
|
||||
end
|
||||
|
||||
-- O-pool-stald: mælke- og gyllemarkør ved samme stald deler lager, men er forskellige læssepunkter
|
||||
do
|
||||
local pool = {}
|
||||
sources[31] = {kind = "husbandry", wayPointId = 31, station = {name = "barnSlurry", sourceStorages = {[pool] = pool}}, name = "Stald"}
|
||||
sources[32] = {kind = "husbandry", wayPointId = 32, station = {name = "barnMilk", sourceStorages = {[pool] = pool}}, name = "Stald"}
|
||||
products[31] = {{fillType = SUGAR_FT or 5, name = "SUGAR", title = "Sukker", available = 20000, level = 20000, capacity = 50000, isPallet = false}}
|
||||
products[32] = {{fillType = SUGAR_FT or 5, name = "SUGAR", title = "Sukker", available = 20000, level = 20000, capacity = 50000, isPallet = false}}
|
||||
local vBarn = newVehicle("Tank2", 0, 0, 10000)
|
||||
runs[vBarn] = outboundRun(78, {31, 32}, {{fillType = "SUGAR", policy = "sell"}}, {collectRound = true})
|
||||
local inputBarn = ADOutbound.buildPlanInput(adEnv, runs[vBarn], vBarn)
|
||||
local ids = {}
|
||||
for _, product in ipairs(inputBarn.products) do
|
||||
for _, source in ipairs(product.sources) do table.insert(ids, tostring(source.wayPointId)) end
|
||||
end
|
||||
check("O-pool-stald: stalde lægges ikke sammen", table.concat(ids, ","), "31,32")
|
||||
sources[31], sources[32], products[31], products[32] = nil, nil, nil, nil
|
||||
end
|
||||
|
||||
-- O-forced: "Lager til" valgt -> lagerturen går til det valgte mål, ikke moden egen silo-valg
|
||||
do
|
||||
sources[41] = {kind = "silo", wayPointId = 41, station = {name = "gylleSilo"}, name = "Gyllesilo"}
|
||||
products[41] = {{fillType = SUGAR, name = "SUGAR", title = "Sukker", available = 11000000, level = 11000000, capacity = 12000000, isPallet = false}}
|
||||
local vF = newVehicle("Gyllevogn", 0, 0, 20000)
|
||||
runs[vF] = outboundRun(79, {41}, {{fillType = "SUGAR", policy = "store"}})
|
||||
runs[vF].forcedMarkers = {31}
|
||||
ADRunsController.getStoreMarkers = function(_, run) return run.forcedMarkers or {} end
|
||||
local forcedCalls = 0
|
||||
ADLoadSwap.findForcedDumpMarker = function(_, _, _, _, ids) forcedCalls = forcedCalls + 1 return {id = ids[1], name = "BGA", markerIndex = 99} end
|
||||
dumpMarker = mStore
|
||||
ADOutbound.choosePickup(adEnv, vF)
|
||||
check("O-forced valgt mål brugt", forcedCalls > 0, true)
|
||||
check("O-forced tur-mål = BGA", ADOutbound.getTripFor(vF) and ADOutbound.getTripFor(vF).targetWayPointId, 31)
|
||||
ADRunsController.getStoreMarkers, ADLoadSwap.findForcedDumpMarker = nil, nil
|
||||
end
|
||||
|
||||
-- O-empty: ingen vare ved kilden (æggene er ikke spawnet endnu) -> intet lagermål søges, ingen advarsel
|
||||
do
|
||||
sources[42] = {kind = "husbandry", wayPointId = 42, station = nil, name = "Chicken Coop"}
|
||||
products[42] = {{fillType = SUGAR, name = "SUGAR", title = "Æg", available = 0, level = 0, capacity = 0, isPallet = false}}
|
||||
local vE = newVehicle("Æg", 0, 0, 20000)
|
||||
runs[vE] = outboundRun(80, {42}, {{fillType = "SUGAR", policy = "store"}})
|
||||
local storeLookups = 0
|
||||
local dumpBefore = ADLoadSwap.findDumpMarker
|
||||
ADLoadSwap.findDumpMarker = function(...) storeLookups = storeLookups + 1 return nil, "ingen" end
|
||||
ADOutbound.buildPlanInput(adEnv, runs[vE], vE)
|
||||
check("O-empty intet lagermål søgt uden vare", storeLookups, 0)
|
||||
ADLoadSwap.findDumpMarker = dumpBefore
|
||||
end
|
||||
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,188 +0,0 @@
|
||||
-- Kør: luajit tests/test_adOutboundPlanner.lua (fra repo-roden)
|
||||
-- Ren turvurdering for udkørsel: prisregel, tærskler, politikker, rangering, opsamlingsrunde.
|
||||
|
||||
dofile("FS25_ADSmartPickup/adOutboundPlanner.lua")
|
||||
local P = ADOutboundPlanner
|
||||
|
||||
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 join(list) return table.concat(list or {}, ",") end
|
||||
|
||||
local SUGAR, FORAGE, BASKET, CRATE, BROOM = 5, 6, 7, 8, 9
|
||||
local function input(products, overrides)
|
||||
local result = {capacity = 20000, hasAutoload = false, sellAtPercent = 90, storeAbovePercent = 80, collectRound = false, products = products}
|
||||
for key, value in pairs(overrides or {}) do result[key] = value end
|
||||
return result
|
||||
end
|
||||
local function product(name, fillType, policy, sources, quote, store, isPallet)
|
||||
return {fillType = fillType, name = name, policy = policy, isPallet = isPallet == true, sources = sources, quote = quote, store = store}
|
||||
end
|
||||
local quoteS = {percent = 92, price = 480, wayPointId = 900, stationName = "Grain Elevator"}
|
||||
local storeSilo = {wayPointId = 400, name = "silo 1", free = 500000}
|
||||
|
||||
-- P1: salg ved pris over tærskel, uanset mængde
|
||||
local trip = P.plan(input({product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 5000, level = 5000, capacity = 180000}}, quoteS, nil)}))
|
||||
check("P1 salg", trip.kind, "sell")
|
||||
check("P1 mål", trip.targetWayPointId, 900)
|
||||
check("P1 målnavn", trip.targetName, "Grain Elevator")
|
||||
check("P1 kilde", join(trip.sourceIds), "1")
|
||||
check("P1 vare", join(trip.fillTypes), "5")
|
||||
check("P1 procent", trip.percent, 92)
|
||||
|
||||
-- P2: pris under tærskel, lager lavt -> ingen tur med begrundelse
|
||||
local low = {percent = 78, price = 400, wayPointId = 900, stationName = "Grain Elevator"}
|
||||
local trip2, reasons2 = P.plan(input({product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 74000, level = 74000, capacity = 180000}}, low, nil)}))
|
||||
check("P2 ingen tur", trip2, nil)
|
||||
check("P2 begrundelse", reasons2[1], "SUGAR 78 % af top (kræver 90), lager 41 %")
|
||||
|
||||
-- P3: sellElseStore, pris lav, lager 85 % -> lager
|
||||
local trip3 = P.plan(input({product("SUGAR", SUGAR, "sellElseStore", {{wayPointId = 1, available = 153000, level = 153000, capacity = 180000}}, low, storeSilo)}))
|
||||
check("P3 lager", trip3.kind, "store")
|
||||
check("P3 silo", trip3.targetWayPointId, 400)
|
||||
|
||||
-- P4: store med fuldt læs, lager 20 %
|
||||
local trip4 = P.plan(input({product("FORAGE", FORAGE, "store", {{wayPointId = 2, available = 700000, level = 700000, capacity = 3500000}}, nil, storeSilo)}))
|
||||
check("P4 fuldt læs -> lager", trip4.kind, "store")
|
||||
local trip4b = P.plan(input({product("FORAGE", FORAGE, "store", {{wayPointId = 2, available = 15000, level = 15000, capacity = 3500000}}, nil, storeSilo)}))
|
||||
check("P4 for lidt og lager lavt -> nil", trip4b, nil)
|
||||
|
||||
-- P5: rangering: højeste pris-% først
|
||||
local trip5 = P.plan(input({
|
||||
product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 1000, level = 1000, capacity = 180000}}, {percent = 91, price = 1, wayPointId = 900, stationName = "A"}, nil),
|
||||
product("FORAGE", FORAGE, "sell", {{wayPointId = 2, available = 1000, level = 1000, capacity = 3500000}}, {percent = 95, price = 1, wayPointId = 901, stationName = "B"}, nil),
|
||||
}))
|
||||
check("P5 højeste procent vinder", join(trip5.fillTypes), "6")
|
||||
check("P5 uden runde kun én vare", #trip5.fillTypes, 1)
|
||||
|
||||
-- P6: intet nåeligt salgssted
|
||||
local trip6, reasons6 = P.plan(input({product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 170000, level = 170000, capacity = 180000}}, nil, nil)}))
|
||||
check("P6 ingen tur", trip6, nil)
|
||||
check("P6 begrundelse", reasons6[1], "SUGAR: intet nåeligt salgssted")
|
||||
|
||||
-- P7: ingen silo med plads ved lagerbehov
|
||||
local trip7, reasons7 = P.plan(input({product("FORAGE", FORAGE, "store", {{wayPointId = 2, available = 3000000, level = 3000000, capacity = 3500000}}, nil, nil)}))
|
||||
check("P7 ingen tur", trip7, nil)
|
||||
check("P7 begrundelse", reasons7[1], "FORAGE: intet lager med plads (silo eller egen fabrik der bruger varen)")
|
||||
|
||||
-- P8: runde, bulk: samme vare fra flere kilder, mest først
|
||||
local trip8 = P.plan(input({product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 3000, level = 3000, capacity = 180000}, {wayPointId = 3, available = 9000, level = 9000, capacity = 180000}}, quoteS, nil)}, {collectRound = true, capacity = 10000}))
|
||||
check("P8 kilder mest først", join(trip8.sourceIds), "3,1")
|
||||
check("P8 én vare", #trip8.fillTypes, 1)
|
||||
|
||||
-- P9: runde, UAL: alle pallevarer med samme mål
|
||||
local sellS = function(pct) return {percent = pct, price = 1, wayPointId = 950, stationName = "Halm-salg"} end
|
||||
local sellT = {percent = 96, price = 1, wayPointId = 951, stationName = "Andet"}
|
||||
local pallets = {
|
||||
product("STRAW_BASKET", BASKET, "sell", {{wayPointId = 5, available = 8000, level = 8, capacity = 4000}}, sellS(95), nil, true),
|
||||
product("STRAW_CRATE", CRATE, "sell", {{wayPointId = 5, available = 2000, level = 2, capacity = 4000}}, sellS(93), nil, true),
|
||||
product("STRAW_BROOM", BROOM, "sell", {{wayPointId = 6, available = 1000, level = 1, capacity = 4000}}, sellT, nil, true),
|
||||
}
|
||||
local trip9 = P.plan(input(pallets, {collectRound = true, hasAutoload = true}))
|
||||
check("P9 højeste procent = kost alene (andet mål)", join(trip9.fillTypes), "9")
|
||||
local trip9b = P.plan(input({pallets[1], pallets[2]}, {collectRound = true, hasAutoload = true}))
|
||||
check("P9 kurve+kasser samme mål", join(trip9b.fillTypes), "7,8")
|
||||
check("P9 navne", join(trip9b.names), "STRAW_BASKET,STRAW_CRATE")
|
||||
check("P9 kilder unikke", join(trip9b.sourceIds), "5")
|
||||
local trip9c = P.plan(input({pallets[1], pallets[2]}, {collectRound = true, hasAutoload = false}))
|
||||
check("P9 uden UAL kun én pallevare", #trip9c.fillTypes, 1)
|
||||
|
||||
-- P10: uden runde kun én kilde (den med mest)
|
||||
local trip10 = P.plan(input({product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 3000, level = 3000, capacity = 180000}, {wayPointId = 3, available = 9000, level = 9000, capacity = 180000}}, quoteS, nil)}))
|
||||
check("P10 én kilde, den fyldeste", join(trip10.sourceIds), "3")
|
||||
|
||||
-- P11: intet på lager
|
||||
local trip11, reasons11 = P.plan(input({product("SUGAR", SUGAR, "sell", {}, quoteS, nil)}))
|
||||
check("P11 ingen tur", trip11, nil)
|
||||
check("P11 begrundelse", reasons11[1], "SUGAR: intet på lager")
|
||||
|
||||
-- P12: evaluate
|
||||
local e = P.evaluate(product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 150000, level = 150000, capacity = 180000}}, quoteS, nil), input({}))
|
||||
check("P12 storagePercent", math.floor(e.storagePercent + 0.5), 83)
|
||||
check("P12 storageHigh", e.storageHigh, true)
|
||||
check("P12 fullLoad", e.fullLoad, true)
|
||||
check("P12 priceOk", e.priceOk, true)
|
||||
|
||||
-- P13 (reserve): det gårdens fabrikker/stalde skal bruge, sælges ikke
|
||||
local BEET = 10
|
||||
local withReserve = product("SUGARBEET", BEET, "sell", {{wayPointId = 3, available = 268000, level = 268000, capacity = 12000000}}, quoteS, nil)
|
||||
withReserve.reserve, withReserve.reservedFor = 196000, {"TMR-fabrik"}
|
||||
local trip13 = P.plan(input({withReserve}))
|
||||
check("P13 fuldt læs uden om reserven -> salg", trip13 ~= nil and trip13.kind, "sell")
|
||||
withReserve.reserve = 260000 -- kun 8.000 til overs: mindre end et læs
|
||||
local trip13b, reasons13b = P.plan(input({withReserve}))
|
||||
check("P13 reserven ville blive rørt -> ingen tur", trip13b, nil)
|
||||
check("P13 begrundelse", reasons13b[1], "SUGARBEET: 260.0k reserveret til TMR-fabrik")
|
||||
withReserve.reserve = 300000 -- alt reserveret
|
||||
check("P13 alt reserveret -> ingen tur", P.plan(input({withReserve})), nil)
|
||||
withReserve.reserve = 0
|
||||
check("P13 reserve 0 -> salg uanset mængde", P.plan(input({withReserve})).kind, "sell")
|
||||
-- lager-politik ignorerer reserven (varen bliver på gården)
|
||||
local storeReserve = product("SUGARBEET", BEET, "store", {{wayPointId = 3, available = 268000, level = 11000000, capacity = 12000000}}, nil, storeSilo)
|
||||
storeReserve.reserve = 300000
|
||||
check("P13 lager rører ikke reserven", P.plan(input({storeReserve})).kind, "store")
|
||||
local e13 = P.evaluate(withReserve, input({}, {capacity = 20000}))
|
||||
check("P13 evaluate sellable", e13.sellable, 268000)
|
||||
|
||||
-- P14 (paller): kapacitet ukendt for en autoload-bil -> "fuldt læs" = mindst PALLET_FULL_LOAD_COUNT paller klar
|
||||
local function palletInput(products)
|
||||
local inp = input(products, {hasAutoload = true})
|
||||
inp.capacity = nil
|
||||
return inp
|
||||
end
|
||||
local palletProduct = product("STRAW_BASKET", BASKET, "store", {{wayPointId = 5, available = 3000, level = 1600, capacity = 8000, palletCount = 3}}, nil, storeSilo, true)
|
||||
check("P14 3 paller: ikke fuldt læs", P.plan(palletInput({palletProduct})), nil)
|
||||
palletProduct.sources[1].palletCount = 4
|
||||
check("P14 4 paller: lager-tur", P.plan(palletInput({palletProduct})).kind, "store")
|
||||
local twoSources = product("STRAW_BASKET", BASKET, "store", {{wayPointId = 5, available = 2000, level = 1600, capacity = 8000, palletCount = 2}, {wayPointId = 3, available = 2000, level = 2, capacity = 4000, palletCount = 2}}, nil, storeSilo, true)
|
||||
check("P14 paller summeres over kilder", P.plan(palletInput({twoSources})).kind, "store")
|
||||
check("P14 evaluate palletCount", P.evaluate(twoSources, palletInput({})).palletCount, 4)
|
||||
|
||||
-- P15: kilder der deler lager (mælketanke = udvidelser inden for storageRadius) regnes som ét stop;
|
||||
-- den første i forløbets rækkefølge beholdes, rækkefølgen bevares
|
||||
local storA, storB, storC = {}, {}, {}
|
||||
local pools = {[1] = {storA, storB}, [2] = {storB}, [3] = {storC}, [4] = {storA}, [5] = nil}
|
||||
local srcs = {{wayPointId = 1}, {wayPointId = 2}, {wayPointId = 3}, {wayPointId = 4}, {wayPointId = 5}}
|
||||
local kept, merged = P.dedupeSharedSources(srcs, function(source) return pools[source.wayPointId] end)
|
||||
check("P15 behold 1, 3, 5", join((function() local ids = {} for _, s in ipairs(kept) do table.insert(ids, s.wayPointId) end return ids end)()), "1,3,5")
|
||||
check("P15 slået sammen: 2 og 4 ind i 1", join(merged[1]), "2,4")
|
||||
check("P15 kilde uden lagre beholdes", kept[3].wayPointId, 5)
|
||||
local none = P.dedupeSharedSources({}, function() return nil end)
|
||||
check("P15 tom liste", #none, 0)
|
||||
|
||||
-- P16: fodring ("Lager til" valgt = feedMode): kør så snart kilden har FEED_MIN_LITERS — ikke fuldt læs / 80 %
|
||||
local feedStore = {wayPointId = 50, name = "Slurry Drop 1", free = 100000}
|
||||
local feedProduct = product("LIQUIDMANURE", FORAGE, "store", {{wayPointId = 2, available = 20000, level = 20000, capacity = 500000}}, nil, feedStore)
|
||||
local tripNoFeed = P.plan(input({feedProduct}, {capacity = 100000}))
|
||||
check("P16 uden fodring: 4 % og intet fuldt læs -> ingen tur", tripNoFeed, nil)
|
||||
local tripFeed = P.plan(input({feedProduct}, {capacity = 100000, feedMode = true}))
|
||||
check("P16 fodring: 20k >= minimum -> lagertur", tripFeed and tripFeed.kind, "store")
|
||||
check("P16 fodring: mål = valgt drop", tripFeed and tripFeed.targetWayPointId, 50)
|
||||
local tinyProduct = product("LIQUIDMANURE", FORAGE, "store", {{wayPointId = 2, available = 3000, level = 3000, capacity = 500000}}, nil, feedStore)
|
||||
check("P16 fodring: under minimum -> ingen tur", P.plan(input({tinyProduct}, {capacity = 100000, feedMode = true})), nil)
|
||||
|
||||
-- P17: valgt lager ("Lager til") kan ikke bruges -> begrundelsen er den præcise grund, ikke "intet lager med plads"
|
||||
local blocked = product("EGG", FORAGE, "store", {{wayPointId = 2, available = 3000000, level = 3000000, capacity = 3500000}}, nil, nil)
|
||||
blocked.storeReason = "Bale & Pallet Storage: ingen vej til 'Chicken Storage Unload'"
|
||||
local _, reasons17 = P.plan(input({blocked}))
|
||||
check("P17 præcis grund i status", reasons17[1], "EGG: Bale & Pallet Storage: ingen vej til 'Chicken Storage Unload'")
|
||||
|
||||
-- SA: "sælg altid over X %" — kilden over grænsen sælges selvom prisen er lav (gylle ved BGA'en, 26/9)
|
||||
local lowQuote = {percent = 40, price = 0.5, top = 1.25, wayPointId = 77, stationName = "Liquid Buy"}
|
||||
local fullSlurry = {{wayPointId = 5, available = 900000, level = 900000, capacity = 1000000}}
|
||||
local lowSlurry = {{wayPointId = 5, available = 300000, level = 300000, capacity = 1000000}}
|
||||
local saOff = P.plan(input({product("LIQUIDMANURE", 40, "sell", fullSlurry, lowQuote, nil)}))
|
||||
check("SA fra: lav pris -> ikke salg", saOff ~= nil and saOff.kind or "intet", "intet")
|
||||
local saOn = P.plan(input({product("LIQUIDMANURE", 40, "sell", fullSlurry, lowQuote, nil)}, {sellAbovePercent = 80}))
|
||||
check("SA 90 % fyldt over 80 %: sælg trods lav pris", saOn ~= nil and saOn.kind, "sell")
|
||||
local saLow = P.plan(input({product("LIQUIDMANURE", 40, "sell", lowSlurry, lowQuote, nil)}, {sellAbovePercent = 80}))
|
||||
check("SA 30 % fyldt: ikke salg", saLow ~= nil and saLow.kind or "intet", "intet")
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,183 +0,0 @@
|
||||
-- Kør: luajit tests/test_adPool.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFieldFleet.lua")
|
||||
dofile("FS25_ADSmartPickup/adHitch.lua")
|
||||
dofile("FS25_ADSmartPickup/adPool.lua")
|
||||
local P = ADPool
|
||||
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
|
||||
-- rolle ud fra redskabets egne specs
|
||||
check("rolle: plov", P.roleOf({spec_plow = true}), "plow")
|
||||
check("rolle: kultivator", P.roleOf({spec_cultivator = true}), "cultivator")
|
||||
check("rolle: vogn med tip", P.roleOf({spec_trailer = true, spec_dischargeable = true}), "unloader")
|
||||
check("rolle: vogn der også er læssevogn er læssevogn", P.roleOf({spec_trailer = true, spec_dischargeable = true, spec_forageWagon = true}), "collector")
|
||||
check("rolle: intet", P.roleOf({}), nil)
|
||||
-- hk fra butiksdata: tal eller tabel (konfigurationer)
|
||||
check("tal", P.numberOf(180), 180)
|
||||
check("tabel: største", P.numberOf({base = 120, config = 150}), 150)
|
||||
check("tom tabel", P.numberOf({}), nil)
|
||||
check("nil", P.numberOf(nil), nil)
|
||||
check("hk: nok", P.powerOk(200, 180), true)
|
||||
check("hk: for lidt", P.powerOk(150, 180), false)
|
||||
check("hk: redskab uden data", P.powerOk(150, nil), true)
|
||||
check("hk: traktor uden data", P.powerOk(nil, 180), true)
|
||||
-- kategori: markredskaber kun bag traktorer (in-game 29/9: Unimog, kategori "trucks", hentede kultivatoren)
|
||||
check("kategori: traktor til kultivator", P.categoryOk("TRACTORSM", "cultivator"), true)
|
||||
check("kategori: lastbil til kultivator", P.categoryOk("TRUCKS", "cultivator"), false)
|
||||
check("kategori: lastbil til tømmevogn", P.categoryOk("TRUCKS", "unloader"), true)
|
||||
check("kategori: teleskoplæsser til plov", P.categoryOk("TELELOADERVEHICLES", "plow"), false)
|
||||
check("kategori: ukendt -> tilladt", P.categoryOk(nil, "cultivator"), true)
|
||||
check("kategori: små bogstaver", P.categoryOk("tractorsL", "plow"), true)
|
||||
local unimog = {id = "u", x = 0, z = 0, hp = 155, category = "TRUCKS"}
|
||||
local fendt = {id = "f", x = 90, z = 0, hp = 250, category = "TRACTORSM"}
|
||||
local cult = {{id = "c", role = "cultivator", roles = {cultivator = true}, x = 1, z = 0, neededHp = 120, name = "Cult"}}
|
||||
local picked = P.pairFor("cultivator", 0, 0, cult, {unimog, fendt}, {})
|
||||
check("parring: nærmeste lastbil springes over", picked ~= nil and picked.tractor.id or nil, "f")
|
||||
-- Connect-markør til en parkering
|
||||
local markers = {{name = "Start", id = 1}, {name = "Bredal", id = 4}, {name = "Connect Bredal", id = 9}, {name = "connect plov 1", id = 12}}
|
||||
check("connect: Bredal", P.connectFor("Bredal", markers).id, 9)
|
||||
check("connect: små bogstaver", P.connectFor("Plov 1", markers).id, 12)
|
||||
check("connect: ingen", P.connectFor("Start", markers), nil)
|
||||
check("connect: uden parkering", P.connectFor(nil, markers), nil)
|
||||
-- parring: redskab nærmest marken, så traktor nærmest redskabet
|
||||
local implements = {
|
||||
{id = "plovFar", name = "Plov far", role = "plow", x = 500, z = 0, connectId = 20, parkId = 21},
|
||||
{id = "plovNear", name = "Plov nær", role = "plow", x = 100, z = 0, neededHp = 180, connectId = 30, parkId = 31},
|
||||
{id = "kult", name = "Kultivator", role = "cultivator", x = 0, z = 0, connectId = 40, parkId = 41},
|
||||
}
|
||||
local tractors = {
|
||||
{id = "small", name = "Lille", x = 110, z = 0, hp = 120, parkId = 2},
|
||||
{id = "big", name = "Stor", x = 300, z = 0, hp = 250, parkId = 3},
|
||||
{id = "mid", name = "Mellem", x = 150, z = 0, hp = 190, parkId = 5},
|
||||
}
|
||||
local pair = P.pairFor("plow", 0, 0, implements, tractors, {})
|
||||
check("par: nærmeste plov til marken", pair.implement.id, "plovNear")
|
||||
check("par: nærmeste stærke traktor til ploven", pair.tractor.id, "mid")
|
||||
local blockedPair = P.pairFor("plow", 0, 0, implements, tractors, {plovNear = true})
|
||||
check("par: reserveret plov springes over", blockedPair.implement.id, "plovFar")
|
||||
check("par: plov uden hk-krav tager nærmeste traktor", blockedPair.tractor.id, "big")
|
||||
local none, why = P.pairFor("seeder", 0, 0, implements, tractors, {})
|
||||
check("par: ingen såmaskine", none, nil); check("par: grund", why, "noPoolImplement")
|
||||
local weak, weakWhy, weakArgs = P.pairFor("plow", 0, 0, {implements[2]}, {tractors[1]}, {})
|
||||
check("par: for svag", weak, nil); check("par: grund tooWeak", weakWhy, "tooWeak"); check("par: hk i args", weakArgs[1], 180)
|
||||
local noTractor, noTractorWhy = P.pairFor("plow", 0, 0, implements, {}, {})
|
||||
check("par: ingen traktor", noTractor, nil); check("par: grund noPoolTractor", noTractorWhy, "noPoolTractor")
|
||||
local rejected, rejectedWhy = P.pairFor("plow", 0, 0, implements, tractors, {}, function() return false end)
|
||||
check("par: accept afviser alle", rejected, nil); check("par: grund", rejectedWhy, "noPoolImplement")
|
||||
local multi = P.pairFor("fertilizer", 0, 0, {{id = "spreder", role = "fertilizer", roles = {fertilizer = true, liming = true}, x = 0, z = 0}}, tractors, {})
|
||||
check("par: redskab med flere roller", multi.implement.id, "spreder")
|
||||
-- reservationer og karantæne
|
||||
P.reserve("F12", {"plovNear", "mid"})
|
||||
check("reserveret: redskab", P.blocked(0).plovNear, true)
|
||||
check("reserveret: traktor", P.blocked(0).mid, true)
|
||||
P.release("F12")
|
||||
check("frigivet", P.blocked(0).plovNear, nil)
|
||||
P.quarantine("plovNear", 1000)
|
||||
check("karantæne: aktiv", P.blocked(1000 + P.QUARANTINE_MS - 1).plovNear, true)
|
||||
check("karantæne: udløbet", P.blocked(1000 + P.QUARANTINE_MS).plovNear, nil)
|
||||
-- kø: længst ventende først, lige -> lavest mark-id, ikke-ventende bagerst i oprindelig rækkefølge
|
||||
local infos = {{id = 5}, {id = 3}, {id = 9}, {id = 1}}
|
||||
local ordered = P.queueOrder(infos, {[9] = 100, [3] = 50, [1] = 50})
|
||||
check("kø: 1.", ordered[1].id, 1); check("kø: 2.", ordered[2].id, 3); check("kø: 3.", ordered[3].id, 9); check("kø: 4.", ordered[4].id, 5)
|
||||
check("kø: rører ikke input", infos[1].id, 5)
|
||||
-- på hjemtur: optaget indtil hjemme
|
||||
local tractorObject = {}
|
||||
check("hjemtur: ikke fra start", P.isReturning(tractorObject), false)
|
||||
P.setReturning(tractorObject, true)
|
||||
check("hjemtur: markeret", P.isReturning(tractorObject), true)
|
||||
P.setReturning(tractorObject, false)
|
||||
check("hjemtur: hjemme", P.isReturning(tractorObject), false)
|
||||
-- review-rettelser
|
||||
-- C1: hjemtur også når AD selv ville parkere (påfyldte sæt); aldrig ved manuel transport
|
||||
check("C1: hjem trods adHandlesEnd", P.shouldReturnHome({adHandlesEnd = true}), true)
|
||||
check("C1: ikke ved manuel", P.shouldReturnHome({manual = true}), false)
|
||||
-- I1: kun maskiner uden egen arbejdsrolle kan trække et puljeredskab
|
||||
check("I1: traktor uden rolle", P.canPull(nil), true)
|
||||
check("I1: mejetærsker", P.canPull("harvester"), false)
|
||||
check("I1: selvkørende sprøjte", P.canPull("fertilizer"), false)
|
||||
-- I2: traktor uden AD-vejnet springes over; fejl uden fremdrift skyldes traktoren
|
||||
local offNet = P.pairFor("plow", 0, 0, {implements[2]}, {{id = "far", x = 100, z = 0, offNetwork = true}, {id = "ok", x = 400, z = 0, hp = 300}}, {})
|
||||
check("I2: traktor uden for vejnettet springes over", offNet.tractor.id, "ok")
|
||||
check("I2: stoppedAway -> traktoren", P.failureBlames("stoppedAway"), "tractor")
|
||||
check("I2: stoppedAway med afstand -> traktoren", P.failureBlames("stoppedAway (40 m fra markøren)"), "tractor")
|
||||
check("I2: noAttachable -> redskabet", P.failureBlames("noAttachable"), "implement")
|
||||
-- I3: efter koblingen — tom uden påfyldning = fejl, tom med påfyldning = fyld op, ellers kør
|
||||
check("I3: ingen påfyldningsbehov", P.nextDrive(nil), "drive")
|
||||
check("I3: fyldt", P.nextDrive({empty = false}), "drive")
|
||||
check("I3: tom med påfyldning", P.nextDrive({empty = true, refillId = 7}), "load")
|
||||
check("I3: tom uden påfyldning", P.nextDrive({empty = true}), "empty")
|
||||
-- I4: puljen kun til hovedopgaver med ét sæt, og ikke når marken har egne faste maskiner til rollen
|
||||
check("I4: almindelig", P.mayUsePool({count = 1}, false), true)
|
||||
check("etape 2: tømmevogn må bruge puljen", P.mayUsePool({count = 1, helper = true}, false), true)
|
||||
check("I4: multi-tool", P.mayUsePool({count = 2}, false), false)
|
||||
check("I4: marken har faste", P.mayUsePool({count = 1}, true), false)
|
||||
-- status til flådefanen
|
||||
local function st(key, arg) return tostring(key) .. "|" .. tostring(arg) end
|
||||
local function ist(s) return st(P.implementStatus(s)) end
|
||||
check("redskab: uden parkering", ist({}), "noPark|nil")
|
||||
check("redskab: uden Connect", ist({parkName = "Plov"}), "noConnect|Plov")
|
||||
check("redskab: på traktor", ist({parkName = "Plov", hasConnect = true, attachedTo = "MT635"}), "attached|MT635")
|
||||
check("redskab: karantæne", ist({parkName = "Plov", hasConnect = true, quarantineMin = 8}), "quarantine|8")
|
||||
check("redskab: reserveret", ist({parkName = "Plov", hasConnect = true, reserved = true}), "reserved|nil")
|
||||
check("redskab: parkeret", ist({parkName = "Plov", hasConnect = true}), "parked|Plov")
|
||||
local function tst(s) return st(P.tractorStatus(s)) end
|
||||
local base = {hasPark = true, enabled = true}
|
||||
check("traktor: uden parkering", tst({enabled = true}), "noPark|nil")
|
||||
check("traktor: spiller", tst({hasPark = true, enabled = true, controlled = true}), "player|nil")
|
||||
check("traktor: hjemtur", tst({hasPark = true, enabled = true, returning = true}), "returning|nil")
|
||||
check("traktor: opgave", tst({hasPark = true, enabled = true, fieldId = 52}), "job|52")
|
||||
check("traktor: optaget", tst({hasPark = true, enabled = true, busy = true}), "busy|nil")
|
||||
check("traktor: ikke i flåden", tst({hasPark = true}), "notInFleet|nil")
|
||||
check("traktor: uden for vejnet", tst({hasPark = true, enabled = true, offNetwork = true}), "offNetwork|nil")
|
||||
check("traktor: har andet redskab på", tst({hasPark = true, enabled = true, hitched = true}), "hitched|nil")
|
||||
check("traktor: ledig", tst(base), "free|nil")
|
||||
-- karantæne-rest i hele minutter (rundet op) og reservation
|
||||
P.quarantine("q1", 0)
|
||||
check("karantæne: minutter tilbage", P.quarantineMinutes("q1", 60000), 9)
|
||||
check("karantæne: udløbet", P.quarantineMinutes("q1", P.QUARANTINE_MS), 0)
|
||||
P.reserve("owner", {"r1"})
|
||||
check("reserveret: ja", P.isReserved("r1"), true)
|
||||
P.release("owner")
|
||||
check("reserveret: nej", P.isReserved("r1"), false)
|
||||
-- M3: ventende marker prøver igen i samme takt (så køens rækkefølge holder)
|
||||
check("M3: næste takt", P.alignedRetry(61000, 30000), 90000)
|
||||
check("M3: på grænsen -> næste", P.alignedRetry(60000, 30000), 90000)
|
||||
check("M3: to marker samme takt", P.alignedRetry(62000, 30000) == P.alignedRetry(89000, 30000), true)
|
||||
-- M4: for svag nævner redskabets navn
|
||||
check("M4: navn i args", weakArgs[2], "Plov nær")
|
||||
-- etape 2: puljevogn skal kunne bære høstens vare
|
||||
check("bære: vare understøttet", P.canCarry({carries = {[3] = true}}, 3), true)
|
||||
check("bære: vare ikke understøttet", P.canCarry({carries = {[3] = true}}, 7), false)
|
||||
check("bære: ingen vare krævet", P.canCarry({carries = {}}, nil), true)
|
||||
check("bære: ukendt last", P.canCarry({}, 3), false)
|
||||
-- etape 3: alle puljevogne parret med hver sin nærmeste traktor (en traktor bruges kun én gang)
|
||||
local wagons = {{id = "v1", role = "unloader", x = 0, z = 0}, {id = "v2", role = "unloader", x = 100, z = 0},
|
||||
{id = "v3", role = "unloader", x = 200, z = 0}, {id = "plovX", role = "plow", x = 0, z = 0}}
|
||||
local pairs3 = P.pairAll("unloader", wagons, {{id = "tA", x = 10, z = 0}, {id = "tB", x = 90, z = 0}}, {v2 = true})
|
||||
check("par-alle: antal (v2 reserveret, 2 traktorer)", #pairs3, 2)
|
||||
check("par-alle: v1 -> tA", pairs3[1].implement.id .. ">" .. pairs3[1].tractor.id, "v1>tA")
|
||||
check("par-alle: v3 -> tB (tA er brugt)", pairs3[2].implement.id .. ">" .. pairs3[2].tractor.id, "v3>tB")
|
||||
check("par-alle: ingen traktorer", #P.pairAll("unloader", wagons, {}, {}), 0)
|
||||
-- flow-vogne: vogn der hører til et kørende flow vises sådan (og er ikke i puljen)
|
||||
check("redskab: flowets vogn", ist({parkName = "P Cow Food vogn 1", hasConnect = true, flowName = "Cow Food"}), "flow|Cow Food")
|
||||
check("redskab: flowets vogn koblet på", ist({parkName = "P Cow Food vogn 1", hasConnect = true, flowName = "Cow Food", attachedTo = "Cow Food 1"}), "attached|Cow Food 1")
|
||||
-- pasform: mindste traktor med hk nok; afstand kun inden for ±15 %; over 2,5x behovet kun som nødløsning
|
||||
-- (in-game 29/9: en meget stor traktor blev sendt til et lille job og kunne ikke komme rundt)
|
||||
local impl120 = {id = "k", x = 0, z = 0, neededHp = 120}
|
||||
local t150 = {id = "t150", x = 900, z = 0, hp = 150}
|
||||
local t170 = {id = "t170", x = 300, z = 0, hp = 180}
|
||||
local t500 = {id = "t500", x = 50, z = 0, hp = 500}
|
||||
check("pasform: mindste der passer vinder over nærmere", P.bestFit({t500, t170, t150}, impl120, "cultivator").id, "t150")
|
||||
local t160 = {id = "t160", x = 100, z = 0, hp = 160}
|
||||
check("pasform: inden for 15 % vinder den nærmeste", P.bestFit({t150, t160}, impl120, "cultivator").id, "t160")
|
||||
check("pasform: kun for stor -> nødløsning", P.bestFit({t500}, impl120, "cultivator").id, "t500")
|
||||
check("pasform: for svag -> ingen", P.bestFit({{id = "t90", x = 0, z = 0, hp = 90}}, impl120, "cultivator"), nil)
|
||||
check("pasform: redskab uden hk-data -> nærmeste", P.bestFit({t150, t500}, {id = "x", x = 0, z = 0}, "cultivator").id, "t500")
|
||||
check("pasform: ukendt traktor-hk efter kendte", P.bestFit({{id = "u", x = 0, z = 0}, t170}, impl120, "cultivator").id, "t170")
|
||||
check("pasform: lastbil tæller ikke", P.bestFit({{id = "lb", x = 0, z = 0, hp = 150, category = "TRUCKS"}, t500}, impl120, "cultivator").id, "t500")
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,106 +0,0 @@
|
||||
-- Kør: luajit tests/test_adPrices.lua (fra repo-roden)
|
||||
-- Prisregel: toppris fra årets prisforløb, pris-% pr. salgssted, bedste tilbud.
|
||||
|
||||
dofile("FS25_ADSmartPickup/adPrices.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
|
||||
|
||||
-- P1: højeste faktor
|
||||
check("P1 maxFactor", ADPrices.maxFactor({EARLY_SPRING = 0.9, LATE_AUTUMN = 1.2, MID_SUMMER = 1.0}), 1.2)
|
||||
check("P1 uden faktorer = 1", ADPrices.maxFactor(nil), 1)
|
||||
|
||||
-- P2: toppris = grundpris nu × (maks ÷ nu)
|
||||
check("P2 topPrice", ADPrices.topPrice(500, 1.0, 1.25), 625)
|
||||
check("P2 factorNow 0 -> grundpris", ADPrices.topPrice(500, 0, 1.25), 500)
|
||||
check("P2 grundpris 0 -> 0", ADPrices.topPrice(0, 1, 1.25), 0)
|
||||
|
||||
-- P3: procent, afrundet; stor efterspørgsel > 100
|
||||
check("P3 75 %", ADPrices.percentOfTop(470, 625), 75)
|
||||
check("P3 112 %", ADPrices.percentOfTop(700, 625), 112)
|
||||
check("P3 top 0 -> 0", ADPrices.percentOfTop(1, 0), 0)
|
||||
|
||||
-- P4: spil-adapter med stationens grundpris
|
||||
SellingStation = {}
|
||||
local SUGAR = 5
|
||||
g_fillTypeManager = {getFillTypeByIndex = function(_, index)
|
||||
if index == SUGAR then return {economy = {factors = {A = 1.0, B = 1.25}}, pricePerLiter = 400} end
|
||||
end}
|
||||
g_currentMission = {environment = {currentPeriod = "A"}, economyManager = {getPriceMultiplier = function() return 1.0 end}}
|
||||
local station = {
|
||||
fillTypePrices = {[SUGAR] = 500},
|
||||
getEffectiveFillTypePrice = function(_, fillType) return 470 end,
|
||||
isa = function(_, class) return class == SellingStation end,
|
||||
isSellingPoint = true,
|
||||
}
|
||||
local quote = ADPrices.getQuote(station, SUGAR)
|
||||
check("P4 pris", quote.price, 470)
|
||||
check("P4 top (pris ved 100 %)", math.floor(quote.top + 0.5), 588)
|
||||
check("P4 procent = kurven alene (stationens grundpris bruges ikke: den mangler sværhedsgrad m.m.)", quote.percent, 80)
|
||||
check("P4 er salgssted", ADPrices.isSellingStation(station), true)
|
||||
check("P4 skjult = ikke salgssted", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = true, hideFromPricesMenu = true}), false)
|
||||
check("P4 fabrik-station = ikke salgssted", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = false}), false)
|
||||
-- in-game (23/9): TMR-fabrikkens indlevering er en SellingStation med isSellingPoint=true -> blev regnet som marked
|
||||
check("P4 fabrikkens egen station = ikke salgssted", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = true, owningPlaceable = {spec_productionPoint = {}}}), false)
|
||||
check("P4 staldens egen station = ikke salgssted", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = true, owningPlaceable = {getHusbandryFillLevel = function() end}}), false)
|
||||
-- P4b: fremmed produktion (kortets mejeri, ejer 0) køber varen = salgssted; gårdens egen fabrik er ikke
|
||||
check("P4b fremmed mejeri = salgssted", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = true, owningPlaceable = {spec_productionPoint = {}, getOwnerFarmId = function() return 0 end}}), true)
|
||||
check("P4b egen fabrik (ejer 1) = ikke salgssted", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = true, owningPlaceable = {spec_productionPoint = {}, getOwnerFarmId = function() return 1 end}}), false)
|
||||
check("P4b fremmed stald-felt ownerFarmId", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = true, owningPlaceable = {getHusbandryFillLevel = function() end, ownerFarmId = 0}}), true)
|
||||
check("P4 rigtigt salgssted med placeable", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = true, owningPlaceable = {}}), true)
|
||||
|
||||
-- P5: uden stationens grundpris: kun kurven tæller (faktor nu ÷ faktor max)
|
||||
local bare = {getEffectiveFillTypePrice = function() return 470 end}
|
||||
local quote2 = ADPrices.getQuote(bare, SUGAR)
|
||||
check("P5 procent = kurven alene", quote2.percent, 80)
|
||||
check("P5 top = effektiv ÷ kurve", math.floor(quote2.top + 0.5), 588)
|
||||
-- P7: kurven er grundlaget (Distribution Redux-metoden): faktor nu ÷ årets max, uafhængigt af sværhedsgrad
|
||||
check("P7 curvePercent", ADPrices.curvePercent({1.0, 1.25, 0.9}, 1), 80)
|
||||
check("P7 curvePercent ved top", ADPrices.curvePercent({1.0, 1.25, 0.9}, 2), 100)
|
||||
check("P7 flad kurve = 100", ADPrices.curvePercent({1.0, 1.01, 1.0}, 3), 100)
|
||||
check("P7 ingen kurve = 100", ADPrices.curvePercent(nil, 1), 100)
|
||||
check("P7 ukendt periode = 100", ADPrices.curvePercent({1.0, 1.25}, nil), 100)
|
||||
-- P8: in-game (23/9): effektiv 1602 mod fillTypePrices 505 gav 317 % — stationens grundpris indeholder
|
||||
-- hverken sværhedsgrad eller prisskala, så forholdet er ubrugeligt. Procenten er kurven alene.
|
||||
local demandStation = {fillTypePrices = {[SUGAR] = 505}, getEffectiveFillTypePrice = function() return 1602 end}
|
||||
check("P8 procent uafhængig af stationens grundpris", ADPrices.getQuote(demandStation, SUGAR).percent, 80)
|
||||
check("P8 top = pris ved 100 % af kurven", math.floor(ADPrices.getQuote(demandStation, SUGAR).top + 0.5), 2003)
|
||||
-- P9: sværhedsgraden påvirker ikke procenten
|
||||
local hardStation = {getEffectiveFillTypePrice = function() return 235 end}
|
||||
check("P9 uafhængig af sværhedsgrad", ADPrices.getQuote(hardStation, SUGAR).percent, 80)
|
||||
check("P5 fejlende station -> nil", ADPrices.getQuote({getEffectiveFillTypePrice = function() error("x") end}, SUGAR), nil)
|
||||
|
||||
-- P6: bedste tilbud = højeste procent, dernæst pris
|
||||
local best = ADPrices.bestQuote({{percent = 80, price = 1}, {percent = 92, price = 2}, {percent = 92, price = 3}})
|
||||
check("P6 højeste procent", best.percent, 92)
|
||||
check("P6 ved lighed højeste pris", best.price, 3)
|
||||
check("P6 tom -> nil", ADPrices.bestQuote({}), nil)
|
||||
|
||||
-- P-land: kortanlæg der følger jorden (boughtWithFarmland) er gårdens, når jorden under dem er gårdens —
|
||||
-- selv om placeable-ejeren står som 15 (Dakotas BGA500kw på jordlod 69)
|
||||
getWorldTranslation = getWorldTranslation or function(node) return node.x, 0, node.z end
|
||||
g_currentMission = g_currentMission or {}
|
||||
g_currentMission.getFarmId = function() return 1 end
|
||||
g_farmlandManager = {
|
||||
getFarmlandIdAtWorldPosition = function(_, x, z) return x < 0 and 69 or 70 end,
|
||||
getFarmlandOwner = function(_, id) return id == 69 and 1 or 0 end,
|
||||
}
|
||||
local bga = {rootNode = {x = -1600, z = -89}, boughtWithFarmland = true, spec_productionPoint = {}, getOwnerFarmId = function() return 15 end}
|
||||
local bgaOther = {rootNode = {x = 1600, z = -89}, boughtWithFarmland = true, spec_productionPoint = {}, getOwnerFarmId = function() return 15 end}
|
||||
local dairy = {rootNode = {x = -3173, z = -4028}, spec_productionPoint = {}, getOwnerFarmId = function() return 0 end}
|
||||
check("P-land BGA på egen jord = egen", ADPrices.isOwnedBy(bga, 1), true)
|
||||
check("P-land BGA på fremmed jord = ikke egen", ADPrices.isOwnedBy(bgaOther, 1), false)
|
||||
check("P-land mejeri (ejer 0, ikke jord-bundet) = ikke egen", ADPrices.isOwnedBy(dairy, 1), false)
|
||||
check("P-land egen ejer", ADPrices.isOwnedBy({getOwnerFarmId = function() return 1 end}, 1), true)
|
||||
check("P-land BGA er ikke salgssted", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = true, owningPlaceable = bga}), false)
|
||||
g_farmlandManager = nil
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,42 +0,0 @@
|
||||
-- Kør: luajit tests/test_adProfiler.lua (fra repo-roden)
|
||||
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 lines = {}
|
||||
Logging = {info = function(fmt, ...) table.insert(lines, string.format(fmt, ...)) end}
|
||||
local clock = 0
|
||||
function netGetTime() return clock end
|
||||
dofile("FS25_ADSmartPickup/adProfiler.lua")
|
||||
|
||||
local Module = {}
|
||||
function Module.slow(a, b) clock = clock + 5; return a + b, "ok" end
|
||||
function Module.fast() clock = clock + 0.1 end
|
||||
ADProfiler.wrap(Module, "slow", "langsom")
|
||||
ADProfiler.wrap(Module, "fast", "hurtig")
|
||||
local sum, text = Module.slow(2, 3)
|
||||
check("P1 returværdier bevares", sum, 5); check("P1 anden returværdi", text, "ok")
|
||||
Module.fast()
|
||||
check("P2 kald talt", ADProfiler.stats["langsom"].calls, 1)
|
||||
check("P2 max", ADProfiler.stats["langsom"].max, 5)
|
||||
ADProfiler.frame(0)
|
||||
check("P3 værste frame", ADProfiler.worstFrameMs, 5.1)
|
||||
ADProfiler.frame(61000)
|
||||
check("P4 rapport skrevet", #lines, 1)
|
||||
check("P4 langsom først", lines[1]:find("langsom max 5.0", 1, true) ~= nil, true)
|
||||
check("P4 nulstillet", next(ADProfiler.stats), nil)
|
||||
-- intet over 2 ms -> ingen rapport
|
||||
Module.fast(); ADProfiler.frame(62000); ADProfiler.frame(130000)
|
||||
check("P5 stille minut -> ingen linje", #lines, 1)
|
||||
-- fejl i indpakket funktion: dybden nulstilles næste frame
|
||||
function Module.boom() error("x") end
|
||||
ADProfiler.wrap(Module, "boom", "fejl")
|
||||
pcall(Module.boom)
|
||||
ADProfiler.frame(131000)
|
||||
check("P6 dybde nulstillet", ADProfiler.depth, 0)
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,385 +0,0 @@
|
||||
-- Kør: luajit tests/test_adRuns.lua (fra repo-roden)
|
||||
-- Tester run-modellen (rene funktioner) og XML-laget mod en XMLFile-mock.
|
||||
|
||||
dofile("FS25_ADSmartPickup/adRuns.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
|
||||
|
||||
-- R1: opret run -> løbenummer, standardværdier; input-store røres ikke
|
||||
local empty = ADRuns.newStore()
|
||||
local s1, cow = ADRuns.createRun(empty, "Cow Feed")
|
||||
check("R1 id 1", cow.id, 1)
|
||||
check("R1 navn", cow.name, "Cow Feed")
|
||||
check("R1 auto-varer som standard", cow.ingredientMode, "auto")
|
||||
check("R1 loops 0", cow.loops, 0)
|
||||
check("R1 tom Wait-pulje", cow.waitPoolGroup, "")
|
||||
check("R1 input-store urørt", #empty.runs, 0)
|
||||
check("R1 nextId tæller op", s1.nextId, 2)
|
||||
local s2, pig = ADRuns.createRun(s1, "Pig Feed")
|
||||
check("R1 andet run får id 2", pig.id, 2)
|
||||
check("R1 to runs", #s2.runs, 2)
|
||||
|
||||
-- R2: opdatér felter; ukendt mode ignoreres; loops klippes
|
||||
local s3 = ADRuns.updateRun(s2, 1, {name = "Cow 1", targetWayPointId = 94, ingredientMode = "manual", ingredients = {"STRAW", "FORAGE"}, waitPoolGroup = "Cow Wait", loops = 150})
|
||||
local run1 = ADRuns.getRun(s3, 1)
|
||||
check("R2 navn", run1.name, "Cow 1")
|
||||
check("R2 mål", run1.targetWayPointId, 94)
|
||||
check("R2 manual", run1.ingredientMode, "manual")
|
||||
check("R2 varer", table.concat(run1.ingredients, ","), "STRAW,FORAGE")
|
||||
check("R2 pulje", run1.waitPoolGroup, "Cow Wait")
|
||||
check("R2 loops klippet til 99", run1.loops, 99)
|
||||
check("R2 gammel store urørt", ADRuns.getRun(s2, 1).name, "Cow Feed")
|
||||
local s3b = ADRuns.updateRun(s3, 1, {ingredientMode = "bogus", loops = -5})
|
||||
check("R2 ukendt mode ignoreres", ADRuns.getRun(s3b, 1).ingredientMode, "manual")
|
||||
check("R2 loops under 0 -> 0", ADRuns.getRun(s3b, 1).loops, 0)
|
||||
check("R2 ukendt run -> uændret", #ADRuns.updateRun(s3, 99, {name = "x"}).runs, 2)
|
||||
check("R2 targetWayPointId=false rydder målet", ADRuns.getRun(ADRuns.updateRun(s3, 1, {targetWayPointId = false}), 1).targetWayPointId, nil)
|
||||
check("R2 nil rører ikke målet", ADRuns.getRun(ADRuns.updateRun(s3, 1, {name = "y"}), 1).targetWayPointId, 94)
|
||||
|
||||
-- R3: tildel traktor; samme traktor flyttes mellem runs
|
||||
local s4, prev = ADRuns.assignVehicle(s3, 1, "vehicleA")
|
||||
check("R3 tildelt", ADRuns.getRun(s4, 1).vehicleIds[1], "vehicleA")
|
||||
check("R3 ingen tidligere", prev, nil)
|
||||
check("R3 opslag", ADRuns.findRunByVehicle(s4, "vehicleA").id, 1)
|
||||
local s5, prev2 = ADRuns.assignVehicle(s4, 2, "vehicleA")
|
||||
check("R3 flyttet fra run 1", prev2, 1)
|
||||
check("R3 væk fra run 1", #ADRuns.getRun(s5, 1).vehicleIds, 0)
|
||||
check("R3 i run 2", ADRuns.getRun(s5, 2).vehicleIds[1], "vehicleA")
|
||||
local s5b, prev3 = ADRuns.assignVehicle(s5, 2, "vehicleA")
|
||||
check("R3 gen-tildeling samme run -> ingen dublet", #ADRuns.getRun(s5b, 2).vehicleIds, 1)
|
||||
check("R3 gen-tildeling -> ingen 'flyttet fra'", prev3, nil)
|
||||
local s6 = ADRuns.unassignVehicle(s5, "vehicleA")
|
||||
check("R3 fjernet", ADRuns.findRunByVehicle(s6, "vehicleA"), nil)
|
||||
check("R3 ukendt run ved tildeling -> uændret", ADRuns.findRunByVehicle(ADRuns.assignVehicle(s6, 42, "vehicleB"), "vehicleB"), nil)
|
||||
|
||||
-- R4: slet run
|
||||
local s7 = ADRuns.deleteRun(s5, 1)
|
||||
check("R4 ét run tilbage", #s7.runs, 1)
|
||||
check("R4 det rigtige", s7.runs[1].id, 2)
|
||||
check("R4 nextId bevares", s7.nextId, 3)
|
||||
|
||||
-- R5: records frem og tilbage
|
||||
local records = ADRuns.toRecords(s5)
|
||||
check("R5 target som tal", records[1].target, 94)
|
||||
check("R5 varer som streng", records[1].ingredients, "STRAW FORAGE")
|
||||
check("R5 køretøjer", records[2].vehicleIds[1], "vehicleA")
|
||||
local back = ADRuns.fromRecords(records)
|
||||
check("R5 to runs tilbage", #back.runs, 2)
|
||||
check("R5 nextId = højeste + 1", back.nextId, 3)
|
||||
check("R5 varer parset", ADRuns.getRun(back, 1).ingredients[2], "FORAGE")
|
||||
check("R5 pulje", ADRuns.getRun(back, 1).waitPoolGroup, "Cow Wait")
|
||||
check("R5 target 0 -> nil", ADRuns.getRun(back, 2).targetWayPointId, nil)
|
||||
check("R5 køretøj", ADRuns.findRunByVehicle(back, "vehicleA").id, 2)
|
||||
|
||||
-- R6: defekte poster springes over; dubletter af id ignoreres
|
||||
local dirty = ADRuns.fromRecords({
|
||||
{id = "x", name = "Broken"},
|
||||
{id = 5, name = "", },
|
||||
{id = 7, name = "Ok", target = "12", ingredientMode = "weird", ingredients = "", waitPool = nil, loops = "3", vehicleIds = {}},
|
||||
{id = 7, name = "Dublet"},
|
||||
})
|
||||
check("R6 kun den gyldige", #dirty.runs, 1)
|
||||
check("R6 target parses fra streng", dirty.runs[1].targetWayPointId, 12)
|
||||
check("R6 ukendt mode -> auto", dirty.runs[1].ingredientMode, "auto")
|
||||
check("R6 tom varestreng -> tom liste", #dirty.runs[1].ingredients, 0)
|
||||
check("R6 manglende pulje -> tom streng", dirty.runs[1].waitPoolGroup, "")
|
||||
check("R6 loops fra streng", dirty.runs[1].loops, 3)
|
||||
check("R6 nextId", dirty.nextId, 8)
|
||||
check("R6 tom liste", #ADRuns.fromRecords(nil).runs, 0)
|
||||
check("R6 parseIngredients trimmer", #ADRuns.parseIngredients(" STRAW FORAGE "), 2)
|
||||
|
||||
-- R7: "kør hjem under X %" — standard 50, klippes til 10..90, overlever records
|
||||
local r7, r7run = ADRuns.createRun(ADRuns.newStore(), "Tærskel")
|
||||
check("R7 standard 50 %", r7run.returnBelowPercent, 50)
|
||||
check("R7 sættes", ADRuns.getRun(ADRuns.updateRun(r7, r7run.id, {returnBelowPercent = 30}), r7run.id).returnBelowPercent, 30)
|
||||
check("R7 klippes til 90", ADRuns.getRun(ADRuns.updateRun(r7, r7run.id, {returnBelowPercent = 150}), r7run.id).returnBelowPercent, 90)
|
||||
check("R7 klippes til 10", ADRuns.getRun(ADRuns.updateRun(r7, r7run.id, {returnBelowPercent = 0}), r7run.id).returnBelowPercent, 10)
|
||||
local r7rec = ADRuns.toRecords(ADRuns.updateRun(r7, r7run.id, {returnBelowPercent = 25}))
|
||||
check("R7 i records", r7rec[1].returnBelow, 25)
|
||||
check("R7 tilbage fra records", ADRuns.fromRecords(r7rec).runs[1].returnBelowPercent, 25)
|
||||
check("R7 manglende i gammel fil -> 50", ADRuns.fromRecords({{id = 1, name = "Gammel"}}).runs[1].returnBelowPercent, 50)
|
||||
|
||||
-- R8: flere mål pr. run — liste + første som "primært" mål (bagudkompatibelt); toggle; records
|
||||
local r8, r8run = ADRuns.createRun(ADRuns.newStore(), "Kvæg")
|
||||
check("R8 ingen mål", #r8run.targetWayPointIds, 0)
|
||||
r8 = ADRuns.updateRun(r8, r8run.id, {targetWayPointIds = {94, 99}})
|
||||
check("R8 to mål", table.concat(ADRuns.getRun(r8, r8run.id).targetWayPointIds, ","), "94,99")
|
||||
check("R8 primært mål = første", ADRuns.getRun(r8, r8run.id).targetWayPointId, 94)
|
||||
r8 = ADRuns.toggleTarget(r8, r8run.id, 94)
|
||||
check("R8 toggle fjerner", table.concat(ADRuns.getRun(r8, r8run.id).targetWayPointIds, ","), "99")
|
||||
check("R8 primært følger med", ADRuns.getRun(r8, r8run.id).targetWayPointId, 99)
|
||||
r8 = ADRuns.toggleTarget(r8, r8run.id, 120)
|
||||
check("R8 toggle tilføjer", table.concat(ADRuns.getRun(r8, r8run.id).targetWayPointIds, ","), "99,120")
|
||||
check("R8 gammel enkelt-sætning = liste med ét", table.concat(ADRuns.getRun(ADRuns.updateRun(r8, r8run.id, {targetWayPointId = 7}), r8run.id).targetWayPointIds, ","), "7")
|
||||
check("R8 false rydder listen", #ADRuns.getRun(ADRuns.updateRun(r8, r8run.id, {targetWayPointId = false}), r8run.id).targetWayPointIds, 0)
|
||||
local r8rec = ADRuns.toRecords(r8)
|
||||
check("R8 records: targets-streng", r8rec[1].targets, "99 120")
|
||||
check("R8 records: target = første (gammelt felt)", r8rec[1].target, 99)
|
||||
check("R8 tilbage fra records", table.concat(ADRuns.fromRecords(r8rec).runs[1].targetWayPointIds, ","), "99,120")
|
||||
check("R8 gammel fil med kun target", table.concat(ADRuns.fromRecords({{id = 1, name = "Gl", target = 55}}).runs[1].targetWayPointIds, ","), "55")
|
||||
|
||||
-- ==== XML-laget mod en flad XMLFile-mock (nøgle -> værdi) ====
|
||||
dofile("FS25_ADSmartPickup/adRunsStorage.lua")
|
||||
|
||||
local function makeXmlMock(values)
|
||||
local xml = {values = values or {}, saved = false, deleted = false}
|
||||
xml.setInt = function(self, key, value) self.values[key] = value end
|
||||
xml.setString = function(self, key, value) self.values[key] = value end
|
||||
xml.getInt = function(self, key) return tonumber(self.values[key]) end
|
||||
xml.getString = function(self, key) local v = self.values[key]; return v ~= nil and tostring(v) or nil end
|
||||
-- iterate: kalder callback for "prefix(0)", "prefix(1)", ... så længe en nøgle med den stamme findes
|
||||
xml.iterate = function(self, prefix, callback)
|
||||
local index = 0
|
||||
while true do
|
||||
local key = string.format("%s(%d)", prefix, index)
|
||||
local found = false
|
||||
for k in pairs(self.values) do
|
||||
if k:sub(1, #key) == key then found = true break end
|
||||
end
|
||||
if not found then return end
|
||||
callback(index + 1, key)
|
||||
index = index + 1
|
||||
end
|
||||
end
|
||||
xml.save = function(self) self.saved = true end
|
||||
xml.delete = function(self) self.deleted = true end
|
||||
return xml
|
||||
end
|
||||
|
||||
-- X1: records skrives som attributter + vehicle-underelementer
|
||||
local out = makeXmlMock()
|
||||
ADRunsStorage.writeRecords(out, ADRuns.toRecords(s5))
|
||||
check("X1 version", out.values["ADSmartPickupRuns#version"], 3)
|
||||
check("X1 run 0 navn", out.values["ADSmartPickupRuns.run(0)#name"], "Cow 1")
|
||||
check("X1 run 0 target", out.values["ADSmartPickupRuns.run(0)#target"], 94)
|
||||
check("X1 run 0 varer", out.values["ADSmartPickupRuns.run(0)#ingredients"], "STRAW FORAGE")
|
||||
check("X1 run 0 pulje", out.values["ADSmartPickupRuns.run(0)#waitPool"], "Cow Wait")
|
||||
check("X1 run 1 køretøj", out.values["ADSmartPickupRuns.run(1).vehicle(0)#id"], "vehicleA")
|
||||
|
||||
-- X2: samme data læses tilbage
|
||||
local records2 = ADRunsStorage.readRecords(makeXmlMock(out.values))
|
||||
check("X2 to poster", #records2, 2)
|
||||
check("X2 id", records2[1].id, 1)
|
||||
check("X2 køretøj", records2[2].vehicleIds[1], "vehicleA")
|
||||
check("X2 loops", records2[1].loops, 99)
|
||||
check("X2 round-trip", ADRuns.findRunByVehicle(ADRuns.fromRecords(records2), "vehicleA").id, 2)
|
||||
|
||||
-- X3: save/load via XMLFile-globalen; ingen savegame-mappe -> tom store, intet skrives
|
||||
Logging = Logging or {warning = function() end, info = function() end}
|
||||
local created, loaded = nil, nil
|
||||
XMLFile = {
|
||||
create = function(_, path, root) created = makeXmlMock(); created.path = path; created.root = root; return created end,
|
||||
load = function(_, path) loaded = makeXmlMock(created and created.values or {}); loaded.path = path; return loaded end,
|
||||
}
|
||||
fileExists = function(path) return created ~= nil and created.path == path end
|
||||
g_currentMission = {missionInfo = {savegameDirectory = "/tmp/sg1"}}
|
||||
check("X3 gemt", ADRunsStorage.save(s5), true)
|
||||
check("X3 sti", created.path, "/tmp/sg1/ADSmartPickup_runs.xml")
|
||||
check("X3 rodelement", created.root, "ADSmartPickupRuns")
|
||||
check("X3 save+delete kaldt", created.saved and created.deleted, true)
|
||||
check("X3 indlæst", #ADRunsStorage.load().runs, 2)
|
||||
check("X3 læse-handle lukket", loaded.deleted, true)
|
||||
g_currentMission = {missionInfo = {}}
|
||||
check("X3 uden savegame -> false", ADRunsStorage.save(s5), false)
|
||||
check("X3 uden savegame -> tom store", #ADRunsStorage.load().runs, 0)
|
||||
g_currentMission = {missionInfo = {savegameDirectory = "/tmp/sg-ny"}}
|
||||
check("X3 fil mangler -> tom store", #ADRunsStorage.load().runs, 0)
|
||||
|
||||
-- R-v2: udkørsels-run — kind, kilder, varer med politik, tærskler, runde; XML v2 + v1-migration
|
||||
local o1, out = ADRuns.createRun(ADRuns.newStore(), "Sugar out", "outbound")
|
||||
check("Rv2 kind", out.kind, "outbound")
|
||||
local _, plain = ADRuns.createRun(ADRuns.newStore(), "x")
|
||||
check("Rv2 supply som standard", plain.kind, "supply")
|
||||
check("Rv2 sellAt 90", out.sellAtPercent, 90)
|
||||
check("Rv2 storeAbove 80", out.storeAbovePercent, 80)
|
||||
check("Rv2 runde fra", out.collectRound, false)
|
||||
local o2 = ADRuns.toggleSource(o1, out.id, 12694)
|
||||
o2 = ADRuns.toggleSource(o2, out.id, 12386)
|
||||
check("Rv2 to kilder", table.concat(ADRuns.getRun(o2, out.id).sourceWayPointIds, ","), "12694,12386")
|
||||
check("Rv2 toggle fjerner", #ADRuns.getRun(ADRuns.toggleSource(o2, out.id, 12694), out.id).sourceWayPointIds, 1)
|
||||
local o3 = ADRuns.setProductPolicy(o2, out.id, "SUGAR", "sell")
|
||||
o3 = ADRuns.setProductPolicy(o3, out.id, "FORAGE", "store")
|
||||
check("Rv2 politik SUGAR", ADRuns.getProductPolicy(ADRuns.getRun(o3, out.id), "SUGAR"), "sell")
|
||||
check("Rv2 politik ukendt = off", ADRuns.getProductPolicy(ADRuns.getRun(o3, out.id), "STRAW"), "off")
|
||||
check("Rv2 ugyldig politik ignoreres", ADRuns.getProductPolicy(ADRuns.getRun(ADRuns.setProductPolicy(o3, out.id, "SUGAR", "bogus"), out.id), "SUGAR"), "sell")
|
||||
check("Rv2 off fjerner", ADRuns.getProductPolicy(ADRuns.getRun(ADRuns.setProductPolicy(o3, out.id, "SUGAR", "off"), out.id), "SUGAR"), "off")
|
||||
check("Rv2 gammel store urørt", #ADRuns.getRun(o2, out.id).products, 0)
|
||||
local o4 = ADRuns.updateRun(o3, out.id, {sellAtPercent = 120, storeAbovePercent = 10, collectRound = true})
|
||||
check("Rv2 sellAt klippet 100", ADRuns.getRun(o4, out.id).sellAtPercent, 100)
|
||||
check("Rv2 storeAbove klippet 50", ADRuns.getRun(o4, out.id).storeAbovePercent, 50)
|
||||
check("Rv2 runde til", ADRuns.getRun(o4, out.id).collectRound, true)
|
||||
check("Rv2 serialiser", ADRuns.serializeProducts(ADRuns.getRun(o3, out.id).products), "SUGAR:sell FORAGE:store")
|
||||
local parsed = ADRuns.parseProducts("SUGAR:sell FORAGE:store BAD BAD:bogus")
|
||||
check("Rv2 parse antal", #parsed, 2)
|
||||
check("Rv2 parse politik", parsed[2].policy, "store")
|
||||
local rt = ADRuns.fromRecords(ADRuns.toRecords(o4))
|
||||
local rtRun = ADRuns.getRun(rt, out.id)
|
||||
check("Rv2 roundtrip kind", rtRun.kind, "outbound")
|
||||
check("Rv2 roundtrip kilder", table.concat(rtRun.sourceWayPointIds, ","), "12694,12386")
|
||||
check("Rv2 roundtrip products", ADRuns.serializeProducts(rtRun.products), "SUGAR:sell FORAGE:store")
|
||||
check("Rv2 roundtrip sellAt", rtRun.sellAtPercent, 100)
|
||||
check("Rv2 roundtrip runde", rtRun.collectRound, true)
|
||||
local v1 = ADRuns.fromRecords({{id = 7, name = "Old", target = 94, targets = "94", ingredientMode = "auto", ingredients = "", waitPool = "", loops = 0, returnBelow = 50, vehicleIds = {}}})
|
||||
check("Rv2 v1 -> supply", ADRuns.getRun(v1, 7).kind, "supply")
|
||||
check("Rv2 v1 tomme kilder", #ADRuns.getRun(v1, 7).sourceWayPointIds, 0)
|
||||
check("Rv2 v1 sellAt standard", ADRuns.getRun(v1, 7).sellAtPercent, 90)
|
||||
-- XML-laget skriver/læser v2-felterne
|
||||
do
|
||||
local written = {}
|
||||
local xml = {
|
||||
setInt = function(_, k, v) written[k] = v end, setString = function(_, k, v) written[k] = v end,
|
||||
getInt = function(_, k) return written[k] end, getString = function(_, k) return written[k] end,
|
||||
iterate = function(_, prefix, fn)
|
||||
local i = 0
|
||||
while written[string.format("%s(%d)#id", prefix, i)] ~= nil do fn(i, string.format("%s(%d)", prefix, i)); i = i + 1 end
|
||||
end,
|
||||
}
|
||||
ADRunsStorage.writeRecords(xml, ADRuns.toRecords(o4))
|
||||
check("Rv2 xml version 2", written["ADSmartPickupRuns#version"], 3)
|
||||
check("Rv2 xml kind", written["ADSmartPickupRuns.run(0)#kind"], "outbound")
|
||||
check("Rv2 xml sources", written["ADSmartPickupRuns.run(0)#sources"], "12694 12386")
|
||||
check("Rv2 xml products", written["ADSmartPickupRuns.run(0)#products"], "SUGAR:sell FORAGE:store")
|
||||
check("Rv2 xml collectRound", written["ADSmartPickupRuns.run(0)#collectRound"], 1)
|
||||
local back = ADRuns.fromRecords(ADRunsStorage.readRecords(xml))
|
||||
check("Rv2 xml læs sellAt", ADRuns.getRun(back, out.id).sellAtPercent, 100)
|
||||
check("Rv2 xml læs runde", ADRuns.getRun(back, out.id).collectRound, true)
|
||||
end
|
||||
|
||||
-- R-v3: bygninger som kilder/mål (id-lister); XML v3; v2-fil uden bygninger giver tomme lister
|
||||
local b1, flow = ADRuns.createRun(ADRuns.newStore(), "Milk", "outbound")
|
||||
check("Rv3 tomme kilde-bygninger", #flow.sourceBuildingIds, 0)
|
||||
check("Rv3 tomme mål-bygninger", #flow.targetBuildingIds, 0)
|
||||
local b2 = ADRuns.toggleSourceBuilding(b1, flow.id, "barn1")
|
||||
b2 = ADRuns.toggleSourceBuilding(b2, flow.id, "barn2")
|
||||
check("Rv3 to kilde-bygninger", table.concat(ADRuns.getRun(b2, flow.id).sourceBuildingIds, ","), "barn1,barn2")
|
||||
check("Rv3 toggle fjerner", #ADRuns.getRun(ADRuns.toggleSourceBuilding(b2, flow.id, "barn1"), flow.id).sourceBuildingIds, 1)
|
||||
local b3 = ADRuns.toggleTargetBuilding(b2, flow.id, "mill")
|
||||
check("Rv3 mål-bygning", ADRuns.getRun(b3, flow.id).targetBuildingIds[1], "mill")
|
||||
check("Rv3 gammel store urørt", #ADRuns.getRun(b2, flow.id).targetBuildingIds, 0)
|
||||
local b4 = ADRuns.updateRun(b3, flow.id, {sourceBuildingIds = {"shed"}, targetBuildingIds = {}})
|
||||
check("Rv3 updateRun sætter lister", ADRuns.getRun(b4, flow.id).sourceBuildingIds[1], "shed")
|
||||
check("Rv3 updateRun tømmer", #ADRuns.getRun(b4, flow.id).targetBuildingIds, 0)
|
||||
local rt3 = ADRuns.fromRecords(ADRuns.toRecords(b3))
|
||||
check("Rv3 roundtrip kilder", table.concat(ADRuns.getRun(rt3, flow.id).sourceBuildingIds, ","), "barn1,barn2")
|
||||
check("Rv3 roundtrip mål", ADRuns.getRun(rt3, flow.id).targetBuildingIds[1], "mill")
|
||||
check("Rv3 XML_VERSION", ADRuns.XML_VERSION, 3)
|
||||
local v2 = ADRuns.fromRecords({{id = 3, name = "V2", kind = "outbound", sources = "12694", products = "SUGAR:sell", vehicleIds = {}}})
|
||||
check("Rv3 v2 rå markør bevares", ADRuns.getRun(v2, 3).sourceWayPointIds[1], 12694)
|
||||
check("Rv3 v2 ingen bygninger", #ADRuns.getRun(v2, 3).sourceBuildingIds, 0)
|
||||
do
|
||||
local written = {}
|
||||
local xml = {
|
||||
setInt = function(_, k, v) written[k] = v end, setString = function(_, k, v) written[k] = v end,
|
||||
getInt = function(_, k) return written[k] end, getString = function(_, k) return written[k] end,
|
||||
iterate = function(_, prefix, fn)
|
||||
local i = 0
|
||||
while written[string.format("%s(%d)#id", prefix, i)] ~= nil do fn(i, string.format("%s(%d)", prefix, i)); i = i + 1 end
|
||||
end,
|
||||
}
|
||||
ADRunsStorage.writeRecords(xml, ADRuns.toRecords(b3))
|
||||
check("Rv3 xml version 3", written["ADSmartPickupRuns#version"], 3)
|
||||
check("Rv3 xml sourceBuildings", written["ADSmartPickupRuns.run(0)#sourceBuildings"], "barn1 barn2")
|
||||
check("Rv3 xml targetBuildings", written["ADSmartPickupRuns.run(0)#targetBuildings"], "mill")
|
||||
local back = ADRuns.fromRecords(ADRunsStorage.readRecords(xml))
|
||||
check("Rv3 xml læs kilder", table.concat(ADRuns.getRun(back, flow.id).sourceBuildingIds, ","), "barn1,barn2")
|
||||
end
|
||||
|
||||
-- RN: køretøjsnavne efter forløbet — "Navn 1", "Navn 2" … i medlemsrækkefølge; ét køretøj = bare navnet
|
||||
do
|
||||
local store, runA = ADRuns.createRun(ADRuns.newStore(), "Milk Collect", "outbound")
|
||||
store = ADRuns.assignVehicle(store, runA.id, "v1")
|
||||
store = ADRuns.assignVehicle(store, runA.id, "v2")
|
||||
local runB
|
||||
store, runB = ADRuns.createRun(store, "Milk Salg", "outbound")
|
||||
store = ADRuns.assignVehicle(store, runB.id, "v3")
|
||||
local names = ADRuns.vehicleNames(store)
|
||||
check("RN to i samme forløb: 1", names.v1, "Milk Collect 1")
|
||||
check("RN to i samme forløb: 2", names.v2, "Milk Collect 2")
|
||||
check("RN alene i forløb = navnet", names.v3, "Milk Salg")
|
||||
check("RN ukendt køretøj", names.v9, nil)
|
||||
end
|
||||
|
||||
-- RS: tvunget lagermål — storeBuildingIds (toggle, kopi, records roundtrip)
|
||||
do
|
||||
local store, run = ADRuns.createRun(ADRuns.newStore(), "Slurry", "outbound")
|
||||
check("RS tom som standard", #run.storeBuildingIds, 0)
|
||||
store = ADRuns.toggleStoreBuilding(store, run.id, "BGA500kw")
|
||||
store = ADRuns.toggleStoreBuilding(store, run.id, "tank2")
|
||||
check("RS to valgt", table.concat(ADRuns.getRun(store, run.id).storeBuildingIds, ","), "BGA500kw,tank2")
|
||||
store = ADRuns.toggleStoreBuilding(store, run.id, "tank2")
|
||||
check("RS fravalgt igen", table.concat(ADRuns.getRun(store, run.id).storeBuildingIds, ","), "BGA500kw")
|
||||
local records = ADRuns.toRecords(store)
|
||||
check("RS record", records[1].storeBuildings, "BGA500kw")
|
||||
local back = ADRuns.fromRecords(records)
|
||||
check("RS roundtrip", table.concat(ADRuns.getRun(back, run.id).storeBuildingIds, ","), "BGA500kw")
|
||||
end
|
||||
|
||||
-- flow-vogne: stoppet + husket vogn pr. traktor følger med records frem og tilbage
|
||||
local wagonStore = ADRuns.newStore()
|
||||
local ws, wrun = ADRuns.createRun(wagonStore, "Fodring", "supply")
|
||||
wrun.stopped, wrun.wagons = true, {vehA = "wagonA"}
|
||||
local wrecords = ADRuns.toRecords(ws)
|
||||
check("vogne: record stoppet", wrecords[1].stopped, 1)
|
||||
check("vogne: record vogne", wrecords[1].wagons, "vehA=wagonA")
|
||||
local wback = ADRuns.fromRecords(wrecords).runs[1]
|
||||
check("vogne: stoppet tilbage", wback.stopped, true)
|
||||
check("vogne: vogn tilbage", wback.wagons.vehA, "wagonA")
|
||||
local wupdated = ADRuns.getRun(ADRuns.updateRun(ws, wrun.id, {name = "Fodring 2"}), wrun.id)
|
||||
check("vogne: overlever updateRun", wupdated.wagons.vehA, "wagonA")
|
||||
check("vogne: stoppet overlever updateRun", wupdated.stopped, true)
|
||||
-- R4: flyttes en traktor til et andet flow (eller fjernes), glemmes dens vogn i det gamle flow
|
||||
local ps = ADRuns.newStore()
|
||||
local ps1, prunA = ADRuns.createRun(ps, "A", "supply")
|
||||
local ps2, prunB = ADRuns.createRun(ps1, "B", "supply")
|
||||
ps2 = ADRuns.assignVehicle(ps2, prunA.id, "trac")
|
||||
ADRuns.getRun(ps2, prunA.id).wagons = {trac = "wag"}
|
||||
local ps3 = ADRuns.assignVehicle(ps2, prunB.id, "trac")
|
||||
check("R4 gammel flow glemmer vognen", ADRuns.getRun(ps3, prunA.id).wagons.trac, nil)
|
||||
local ps4 = ADRuns.unassignVehicle(ps2, "trac")
|
||||
check("R4 fjernet traktor: vogn glemt", ADRuns.getRun(ps4, prunA.id).wagons.trac, nil)
|
||||
-- oprydning: traktorer der ikke findes længere (solgt) fjernes fra deres flow — men ikke hvis ALLE mangler
|
||||
do
|
||||
local st, r1 = ADRuns.createRun(ADRuns.newStore(), "A", "supply")
|
||||
st = ADRuns.assignVehicle(st, r1.id, "keep")
|
||||
st = ADRuns.assignVehicle(st, r1.id, "gone")
|
||||
local exists = function(id) return id == "keep" end
|
||||
local pruned, removed = ADRuns.pruneMissingVehicles(st, exists)
|
||||
check("prune: fjernet", #removed, 1)
|
||||
check("prune: den rigtige", removed[1].vehicleId, "gone")
|
||||
check("prune: flowets navn med", removed[1].runName, "A")
|
||||
check("prune: tilbage", #ADRuns.getRun(pruned, r1.id).vehicleIds, 1)
|
||||
check("prune: original urørt", #ADRuns.getRun(st, r1.id).vehicleIds, 2)
|
||||
local _, none = ADRuns.pruneMissingVehicles(st, function() return false end)
|
||||
check("prune: alle mangler -> intet fjernes", #none, 0)
|
||||
end
|
||||
-- beskeder på skærmen efter oprydning: én pr. flow; rød når flowet står uden traktorer
|
||||
do
|
||||
local st, r1 = ADRuns.createRun(ADRuns.newStore(), "TMR", "supply")
|
||||
local st2, r2 = ADRuns.createRun(st, "Solo", "supply")
|
||||
st2 = ADRuns.assignVehicle(st2, r1.id, "keep")
|
||||
st2 = ADRuns.assignVehicle(st2, r1.id, "gone1")
|
||||
st2 = ADRuns.assignVehicle(st2, r2.id, "gone2")
|
||||
local pruned, removed = ADRuns.pruneMissingVehicles(st2, function(id) return id == "keep" end)
|
||||
local notes = ADRuns.pruneNotices(pruned, removed)
|
||||
check("besked: én pr. flow", #notes, 2)
|
||||
check("besked: TMR har traktorer tilbage", notes[1].runName .. "/" .. notes[1].left .. "/" .. tostring(notes[1].critical), "TMR/1/false")
|
||||
check("besked: Solo står tom -> rød", notes[2].runName .. "/" .. notes[2].left .. "/" .. tostring(notes[2].critical), "Solo/0/true")
|
||||
end
|
||||
|
||||
-- Start alle: et flow på pause (Stop alt) skal have pausen ophævet, også når dets traktorer allerede kører AD
|
||||
-- (in-game 29/9: 5 flows blev talt som 'kørte allerede', forblev stoppede, og traktorerne kørte vognen hjem igen)
|
||||
check("start alle: kører ikke -> i kø", ADRuns.startAllAction(false, true), "queue")
|
||||
check("start alle: kører ikke, ikke stoppet -> i kø", ADRuns.startAllAction(false, false), "queue")
|
||||
check("start alle: kører men stoppet -> ophæv pause", ADRuns.startAllAction(true, true), "resume")
|
||||
check("start alle: kører og ikke stoppet -> intet", ADRuns.startAllAction(true, false), "skip")
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,907 +0,0 @@
|
||||
-- Kør: luajit tests/test_adRunsController.lua (fra repo-roden)
|
||||
-- Controlleren oversætter et run til AutoDrive-tilstand pr. traktor. Mock'er AD + de
|
||||
-- få ADSmartPickup-opslag den bruger.
|
||||
|
||||
local STRAW, TMR, WATER, MILK = 31, 116, 8, 30
|
||||
local FILL_TYPE_NAMES = {[STRAW] = "STRAW", [TMR] = "FORAGE", [WATER] = "WATER", [MILK] = "MILK"}
|
||||
local FILL_TYPE_INDEX = {}
|
||||
for index, name in pairs(FILL_TYPE_NAMES) do FILL_TYPE_INDEX[name] = index end
|
||||
g_fillTypeManager = {
|
||||
getFillTypeNameByIndex = function(_, index) return FILL_TYPE_NAMES[index] end,
|
||||
getFillTypeIndexByName = function(_, name) return FILL_TYPE_INDEX[name] end,
|
||||
getFillTypes = function()
|
||||
return {
|
||||
{index = STRAW, name = "STRAW", title = "Halm"},
|
||||
{index = TMR, name = "FORAGE", title = "TMR"},
|
||||
{index = WATER, name = "WATER", title = "Vand"},
|
||||
{index = MILK, name = "MILK", title = "Mælk"},
|
||||
}
|
||||
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,
|
||||
}
|
||||
Utils = {prependedFunction = function(original, extra) return function(...) extra(...); return original(...) end end}
|
||||
ItemSystem = {save = function() end}
|
||||
g_currentMission = {getFarmId = function() return 1 end, missionInfo = {}}
|
||||
|
||||
-- ---- AutoDrive-mock ----
|
||||
local RT_NONE, RT_ONLYPICKUP = 1, 2
|
||||
local MODE_PICKUPANDDELIVER = 2
|
||||
local vehicles, markers, wayPoints = {}, {}, {}
|
||||
FS25_AutoDrive = {
|
||||
AutoDrive = {
|
||||
RT_NONE = RT_NONE, RT_ONLYPICKUP = RT_ONLYPICKUP, MODE_PICKUPANDDELIVER = MODE_PICKUPANDDELIVER,
|
||||
settings = {
|
||||
useFolders = {values = {false, true}, current = 1, isVehicleSpecific = false},
|
||||
rotateTargets = {values = {1, 2, 3, 4}, current = 1, isVehicleSpecific = true},
|
||||
ALUnload = {values = {0, 1, 2, 3, 4}, current = 1, isVehicleSpecific = true},
|
||||
},
|
||||
getAllVehicles = function() return vehicles end,
|
||||
getAllUnits = function(vehicle) return vehicle.trailers end,
|
||||
getAllFillLevels = function(trailers) return trailers.level or 0, trailers.capacity or 0, false, 0 end,
|
||||
getSupportedFillTypesOfAllUnitsAlphabetically = function(vehicle) return vehicle.carriable or {} 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,
|
||||
getDistanceFromNetwork = function(_, vehicle) return vehicle.networkDistance or 0 end,
|
||||
},
|
||||
}
|
||||
|
||||
local function addMarker(id, name, group, x, z)
|
||||
wayPoints[id] = {x = x, z = z, incoming = {}}
|
||||
table.insert(markers, {id = id, markerIndex = #markers + 1, name = name, group = group})
|
||||
end
|
||||
|
||||
-- ADSmartPickup-opslag: hvilke waypoints har hvilken slags station, og hvad målet tager
|
||||
local unloadAt, loadAt = {}, {}
|
||||
ADSmartPickup = {
|
||||
getFarmId = function(vehicle) return vehicle.farmId end,
|
||||
getUnloadStationAtWayPointId = function(_, id) return unloadAt[id] end,
|
||||
getLoadStationAtWayPointId = function(_, id) return loadAt[id] end,
|
||||
getStationLevelAndCapacity = function(station, fillType)
|
||||
if station.accepts[fillType] == nil then return nil end
|
||||
return (station.levels or {})[fillType] or 0, station.accepts[fillType]
|
||||
end,
|
||||
-- forenklet rangering: mest fri plads (relativt) først; fulde sidst
|
||||
getTargetNeedsRanked = function(station, fillTypes)
|
||||
local ranked = {}
|
||||
for _, fillType in ipairs(fillTypes) do
|
||||
local capacity = station.accepts[fillType]
|
||||
if capacity ~= nil then
|
||||
local level = (station.levels or {})[fillType] or 0
|
||||
table.insert(ranked, {fillType = fillType, level = level, capacity = capacity, freeSpace = capacity - level, score = (capacity - level) / capacity})
|
||||
end
|
||||
end
|
||||
table.sort(ranked, function(a, b) return a.score > b.score end)
|
||||
return ranked
|
||||
end,
|
||||
}
|
||||
ADSupplyPlanner = {isWaitMarker = function(marker) return string.find(string.lower(marker.group or ""), "%f[%a]wait%f[%A]") ~= nil end}
|
||||
|
||||
local function makeVehicle(uniqueId, name, carriable, capacity, x, z)
|
||||
local stateModule = {selectedFillTypes = {}, fillType = 0, active = false, loopCounter = 5, loopsDone = 3, dirtyCount = 0}
|
||||
stateModule.setMode = function(self, mode) self.mode = mode end
|
||||
stateModule.setFirstMarkerByWayPointId = function(self, id) self.firstMarkerId = id end
|
||||
stateModule.setSecondMarkerByWayPointId = function(self, id) self.secondMarkerId = id end
|
||||
stateModule.setFillType = function(self, fillType) self.fillType = fillType end
|
||||
stateModule.raiseDirtyFlag = function(self) self.dirtyCount = self.dirtyCount + 1 end
|
||||
stateModule.setLoopsDone = function(self, n) self.loopsDone = n end
|
||||
stateModule.isActive = function(self) return self.active end
|
||||
-- Bruges af C13: getTargetFillTypes skal følge AD's LIVE mål (mock'er AD's eget getSecondMarker).
|
||||
stateModule.getSecondMarker = function(self) return self.secondMarkerId ~= nil and {id = self.secondMarkerId} or nil end
|
||||
local vehicle = {
|
||||
uniqueId = uniqueId, farmId = 1, rootNode = uniqueId,
|
||||
carriable = carriable, trailers = {level = 0, capacity = capacity},
|
||||
getName = function() return name end,
|
||||
getOwnerFarmId = function() return 1 end,
|
||||
startAutoDrive = function(self) self.ad.stateModule.active = true; self.started = (self.started or 0) + 1 end,
|
||||
getClosestWayPoint = function() return nil, 0 end,
|
||||
stopAutoDrive = function(self) self.ad.stateModule.active = false; self.stopped = (self.stopped or 0) + 1 end,
|
||||
ad = {stateModule = stateModule, settings = {rotateTargets = {values = {1, 2, 3, 4}, current = 1, new = 1, isVehicleSpecific = true}, ALUnload = {values = {0, 1, 2, 3, 4}, current = 1, new = 1, isVehicleSpecific = true}}},
|
||||
}
|
||||
nodePositions = nodePositions or {}
|
||||
nodePositions[uniqueId] = {x = x or 0, z = z or 0}
|
||||
table.insert(vehicles, vehicle)
|
||||
return vehicle
|
||||
end
|
||||
function getWorldTranslation(node) local p = nodePositions[node]; return p.x, 0, p.z end
|
||||
|
||||
dofile("FS25_ADSmartPickup/adRuns.lua")
|
||||
dofile("FS25_ADSmartPickup/adRunsStorage.lua")
|
||||
dofile("FS25_ADSmartPickup/adWaitPool.lua")
|
||||
dofile("FS25_ADSmartPickup/adRunsController.lua")
|
||||
local adEnv = FS25_AutoDrive
|
||||
|
||||
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 buildWorld()
|
||||
vehicles, markers, wayPoints, unloadAt, loadAt, logLines = {}, {}, {}, {}, {}, {}
|
||||
ADRunsController.store = ADRuns.newStore()
|
||||
addMarker(94, "Cow 1 Food", "Cow Food", 5000, 0); unloadAt[94] = {accepts = {[STRAW] = 300000, [TMR] = 300000, [WATER] = 50000}}
|
||||
addMarker(44, "10 Silo Storage", "Siloer", 0, 0); loadAt[44] = {}
|
||||
addMarker(75, "TMR Load", "Fabrication", 900, 0); loadAt[75] = {}
|
||||
addMarker(100, "Cow Wait", "Cow Wait", 5100, 0)
|
||||
addMarker(101, "Cow Wait", "Cow Wait", 5200, 0)
|
||||
addMarker(200, "F91 Wait", "F91", 8000, 0)
|
||||
addMarker(300, "Pig Wait", "Pig Wait", 9000, 0)
|
||||
end
|
||||
|
||||
-- C1: køretøjs-id og farmens AD-køretøjer
|
||||
buildWorld()
|
||||
local fastracA = makeVehicle("vehicleA", "Fastrac A", {STRAW, TMR}, 1000000, 100, 0)
|
||||
local fastracB = makeVehicle("vehicleB", "Fastrac B", {STRAW}, 700000, 4000, 0)
|
||||
local noAd = {uniqueId = "vehicleC", getOwnerFarmId = function() return 1 end}
|
||||
table.insert(vehicles, noAd)
|
||||
local otherFarm = makeVehicle("vehicleD", "Nabo", {STRAW}, 1000, 0, 0); otherFarm.getOwnerFarmId = function() return 2 end
|
||||
check("C1 uniqueId", ADRunsController.getVehicleId(fastracA), "vehicleA")
|
||||
check("C1 fallback savegame-id", ADRunsController.getVehicleId({currentSavegameId = 7}), "7")
|
||||
check("C1 intet id -> nil", ADRunsController.getVehicleId({}), nil)
|
||||
check("C1 farmens AD-køretøjer", #ADRunsController.getFarmVehicles(adEnv), 2)
|
||||
check("C1 opslag på id", ADRunsController.findVehicleById(adEnv, "vehicleB"), fastracB)
|
||||
check("C1 ukendt id -> nil", ADRunsController.findVehicleById(adEnv, "nope"), nil)
|
||||
|
||||
-- C2: opret run, tildel; opslag run for køretøj; flyt mellem runs
|
||||
local cow = ADRunsController.createRun("Cow Feed")
|
||||
check("C2 run oprettet", ADRuns.getRun(ADRunsController.store, cow.id).name, "Cow Feed")
|
||||
check("C2 ingen tidligere", ADRunsController.assignVehicle(cow.id, fastracA), nil)
|
||||
check("C2 run for køretøj", ADRunsController.getRunForVehicle(fastracA).id, cow.id)
|
||||
check("C2 ikke tildelt -> nil", ADRunsController.getRunForVehicle(fastracB), nil)
|
||||
local pig = ADRunsController.createRun("Pig Feed")
|
||||
check("C2 flyttet fra Cow", ADRunsController.assignVehicle(pig.id, fastracA), cow.id)
|
||||
check("C2 nu i Pig", ADRunsController.getRunForVehicle(fastracA).id, pig.id)
|
||||
ADRunsController.unassignVehicle(fastracA)
|
||||
check("C2 fjernet", ADRunsController.getRunForVehicle(fastracA), nil)
|
||||
ADRunsController.deleteRun(pig.id)
|
||||
check("C2 slettet", ADRuns.getRun(ADRunsController.store, pig.id), nil)
|
||||
|
||||
-- C3: Auto-vareliste = vognens varer ∩ målets varer; manual = navne -> indeks (ukendte droppes)
|
||||
ADRunsController.updateRun(cow.id, {targetWayPointId = 94})
|
||||
local cowRun = ADRuns.getRun(ADRunsController.store, cow.id)
|
||||
check("C3 auto: halm+TMR (vand kan vognen ikke bære)", table.concat(ADRunsController.getRunIngredients(adEnv, cowRun, fastracA), ","), STRAW .. "," .. TMR)
|
||||
check("C3 auto: kun halm for halm-vogn", table.concat(ADRunsController.getRunIngredients(adEnv, cowRun, fastracB), ","), tostring(STRAW))
|
||||
ADRunsController.updateRun(cow.id, {ingredientMode = "manual", ingredients = {"FORAGE", "UNKNOWN_THING"}})
|
||||
cowRun = ADRuns.getRun(ADRunsController.store, cow.id)
|
||||
check("C3 manual: FORAGE -> indeks, ukendt droppes", table.concat(ADRunsController.getRunIngredients(adEnv, cowRun, fastracA), ","), tostring(TMR))
|
||||
ADRunsController.updateRun(cow.id, {ingredientMode = "auto"})
|
||||
local noTarget = ADRunsController.createRun("Uden mål")
|
||||
check("C3 uden mål -> tom liste", #ADRunsController.getRunIngredients(adEnv, ADRuns.getRun(ADRunsController.store, noTarget.id), fastracA), 0)
|
||||
|
||||
-- C4: validering
|
||||
cowRun = ADRuns.getRun(ADRunsController.store, cow.id)
|
||||
check("C4 ok", ADRunsController.validateVehicle(adEnv, cowRun, fastracA).ok, true)
|
||||
check("C4 kapacitet", ADRunsController.validateVehicle(adEnv, cowRun, fastracA).capacity, 1000000)
|
||||
local noTrailer = makeVehicle("vehicleE", "Uden vogn", {}, 0)
|
||||
check("C4 ingen vogn", ADRunsController.validateVehicle(adEnv, cowRun, noTrailer).reason, "noTrailer")
|
||||
local milkTruck = makeVehicle("vehicleF", "Mælkebil", {MILK}, 20000)
|
||||
check("C4 kan ikke bære", ADRunsController.validateVehicle(adEnv, cowRun, milkTruck).reason, "cannotCarry")
|
||||
fastracB.networkDistance = 31
|
||||
check("C4 for langt fra vejnettet", ADRunsController.validateVehicle(adEnv, cowRun, fastracB).reason, "farFromNetwork")
|
||||
fastracB.networkDistance = 0
|
||||
check("C4 intet mål", ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, noTarget.id), fastracA).reason, "noTarget")
|
||||
ADRunsController.updateRun(noTarget.id, {targetWayPointId = 4242})
|
||||
check("C4 mål-markør findes ikke", ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, noTarget.id), fastracA).reason, "noTarget")
|
||||
|
||||
-- C5: Start skriver AD-tilstand og starter; ✖-traktorer springes over
|
||||
ADRunsController.updateRun(cow.id, {loops = 4, waitPoolGroup = "Cow Wait"})
|
||||
ADRunsController.assignVehicle(cow.id, fastracA)
|
||||
ADRunsController.assignVehicle(cow.id, noTrailer)
|
||||
local results = ADRunsController.startRun(adEnv, cow.id)
|
||||
check("C5 to resultater", #results, 2)
|
||||
check("C5 A startet", fastracA.started, 1)
|
||||
check("C5 A ok", results[1].ok, true)
|
||||
check("C5 uden vogn sprunget over", noTrailer.started, nil)
|
||||
check("C5 uden vogn grund", results[2].reason, "noTrailer")
|
||||
local state = fastracA.ad.stateModule
|
||||
check("C5 mode P&D", state.mode, MODE_PICKUPANDDELIVER)
|
||||
check("C5 mål = run'ets mål", state.secondMarkerId, 94)
|
||||
check("C5 start-pickup = nærmeste læssestation", state.firstMarkerId, 44)
|
||||
check("C5 varer = auto-listen", table.concat(state.selectedFillTypes, ","), STRAW .. "," .. TMR)
|
||||
check("C5 fillType = første", state.fillType, STRAW)
|
||||
check("C5 loops", state.loopCounter, 4)
|
||||
check("C5 loopsDone nulstillet", state.loopsDone, 0)
|
||||
check("C5 rotateTargets = kun pålæsning (current)", fastracA.ad.settings.rotateTargets.current, 2)
|
||||
check("C5 rotateTargets = kun pålæsning (new)", fastracA.ad.settings.rotateTargets.new, 2)
|
||||
check("C5 useFolders globalt til", adEnv.AutoDrive.settings.useFolders.current, 2)
|
||||
check("C5 dirty flag", state.dirtyCount > 0, true)
|
||||
check("C5 status", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).running, 1)
|
||||
check("C5 total", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).total, 2)
|
||||
check("C5 ukendt run -> tom", #ADRunsController.startRun(adEnv, 999), 0)
|
||||
|
||||
-- C6: Stop
|
||||
check("C6 stoppede", ADRunsController.stopRun(adEnv, cow.id), 1)
|
||||
check("C6 A stoppet", fastracA.stopped, 1)
|
||||
check("C6 status 0", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).running, 0)
|
||||
check("C6 medlemskab bevaret", ADRunsController.getRunForVehicle(fastracA).id, cow.id)
|
||||
|
||||
-- C7: Start på traktor der allerede kører AD (fx startet manuelt) -> stoppes og startes på run'et
|
||||
fastracA.ad.stateModule.active = true
|
||||
fastracA.stopped = 0
|
||||
ADRunsController.startRun(adEnv, cow.id)
|
||||
check("C7 kørende traktor stoppes først", fastracA.stopped, 1)
|
||||
check("C7 og startes på run'et", fastracA.started, 2)
|
||||
check("C7 run markeret som kørende", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).isRunning, true)
|
||||
ADRunsController.stopRun(adEnv, cow.id)
|
||||
check("C7 stop rydder markeringen", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).isRunning, false)
|
||||
-- en traktor der kører AD uden at run'et er startet af os, gør IKKE run'et "kørende"
|
||||
fastracA.ad.stateModule.active = true
|
||||
check("C7 manuel AD tæller ikke som kørende run", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).isRunning, false)
|
||||
-- efter genindlæsning: runningRuns er tom, men flowet er ikke stoppet og traktoren kører -> kørende (Stop-knap)
|
||||
-- (in-game 29/9: Flows-listen viste [Start] ved flows der kørte)
|
||||
ADRuns.getRun(ADRunsController.store, cow.id).stopped = false
|
||||
ADRunsController.runningRuns[cow.id] = nil
|
||||
check("C7b efter genindlæsning: kørende", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).isRunning, true)
|
||||
check("C7b efter genindlæsning: kan stoppes", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).canStop, true)
|
||||
ADRuns.getRun(ADRunsController.store, cow.id).stopped = true
|
||||
check("C7 men aktive tæller stadig", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).running, 1)
|
||||
fastracA.ad.stateModule.active = false
|
||||
-- sprungne traktorer logges med årsag
|
||||
logLines = {}
|
||||
ADRunsController.startRun(adEnv, cow.id)
|
||||
local skippedLogged = false
|
||||
for _, line in ipairs(logLines) do if line:find("sprunget over", 1, true) and line:find("noTrailer", 1, true) then skippedLogged = true end end
|
||||
check("C7 sprunget over logges med årsag", skippedLogged, true)
|
||||
-- kapacitet under 100 l (kun trækkerens egne tanke) = ingen vogn
|
||||
local tinyCapacity = makeVehicle("vehicleTiny", "T255", {STRAW, TMR}, 1, 100, 0)
|
||||
check("C7 1 l kapacitet -> noTrailer", ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, cow.id), tinyCapacity).reason, "noTrailer")
|
||||
-- UAL-køretøj: AutoDrive melder kapacitet i PALLER (læssede + 1), så en tom pallebil har "1" — det er ikke "ingen vogn"
|
||||
adEnv.AutoDrive.hasAL = function(_, unit) return unit.ual == true end
|
||||
local palletTruck = makeVehicle("vehicleDragon", "Dragon", {}, 1, 100, 0)
|
||||
palletTruck.trailers[1] = {ual = true}
|
||||
local truckValidation = ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, cow.id), palletTruck)
|
||||
check("C7 pallebil: ikke noTrailer", truckValidation.reason ~= "noTrailer", true)
|
||||
check("C7 pallebil: kapacitet i paller markeret", truckValidation.isAutoload, true)
|
||||
check("C7 pallebil: liter-kapacitet ukendt", truckValidation.capacity, nil)
|
||||
-- in-game 27/9: en tipvogn med UniversalAutoload slået til tæller hos AutoDrive kun som palle-/ballevogn,
|
||||
-- så en forsynings-flow med løsvarer afviste den som "kan ikke bære" uden at sige hvorfor
|
||||
check("C7 AutoLoad slået til på vogn i forsyning -> autoloadOn", truckValidation.reason, "autoloadOn")
|
||||
adEnv.AutoDrive.hasAL = nil
|
||||
|
||||
-- C8: mål-markører (kun stalde og fabrikker — siloer tager alt og er ikke mål; stalde først,
|
||||
-- så fabrikker, så navn) og Wait-puljer til dropdowns
|
||||
unloadAt[94].owningPlaceable = {getHusbandryFillLevel = function() return 0 end}
|
||||
addMarker(96, "8 Silo Storage", "Siloer", 100, 0); unloadAt[96] = {accepts = {[STRAW] = 1}, owningPlaceable = {spec_silo = {}}}
|
||||
addMarker(97, "TMR Unload", "Fabrication", 900, 0); unloadAt[97] = {accepts = {[STRAW] = 1}, owningPlaceable = {spec_productionPoint = {}}}
|
||||
addMarker(98, "Bull Food", "Cow Food", 5100, 0); unloadAt[98] = {accepts = {[STRAW] = 1}, owningPlaceable = {getHusbandryFillLevel = function() return 0 end}}
|
||||
local targets = ADRunsController.getTargetMarkers(adEnv, 1)
|
||||
check("C8 kun stalde og fabrikker (ikke siloen)", #targets, 3)
|
||||
check("C8 stalde først, alfabetisk", targets[1].name .. "," .. targets[2].name, "Bull Food,Cow 1 Food")
|
||||
check("C8 fabrik sidst", targets[3].name, "TMR Unload")
|
||||
local pools = ADRunsController.getWaitPools(adEnv)
|
||||
check("C8 to puljer (F91 er ikke en Wait-mappe)", #pools, 2)
|
||||
check("C8 sorteret", pools[1].group, "Cow Wait")
|
||||
check("C8 antal", pools[1].count, 2)
|
||||
|
||||
-- C9: gem/indlæs; ukendte køretøjs-id'er logges ved indlæsning men beholdes i run'et
|
||||
local savedRecords
|
||||
ADRunsStorage.save = function(store) savedRecords = ADRuns.toRecords(store); return true end
|
||||
ADRunsStorage.load = function() return ADRuns.fromRecords(savedRecords) end
|
||||
ADRunsController.assignVehicle(cow.id, {uniqueId = "vehicleGONE", ad = {}, getOwnerFarmId = function() return 1 end, getName = function() return "Væk" end})
|
||||
check("C9 gemt", ADRunsController.save(), true)
|
||||
ADRunsController.store = ADRuns.newStore()
|
||||
ADRunsController.load(adEnv)
|
||||
check("C9 indlæst", ADRuns.getRun(ADRunsController.store, cow.id).name, "Cow Feed")
|
||||
check("C9 ukendt køretøj logget", logLines[#logLines]:find("vehicleGONE", 1, true) ~= nil, true)
|
||||
check("C9 run beholder kendte", ADRunsController.getRunForVehicle(fastracA).id, cow.id)
|
||||
local hooked = false
|
||||
ItemSystem.save = function() hooked = true end
|
||||
ADRunsController.installSaveHook()
|
||||
ItemSystem.save()
|
||||
check("C9 save-hook kaldes før ItemSystem.save", hooked, true)
|
||||
|
||||
-- C11: vogn uden egen ad.settings-tabel (ingen rotateTargets pr. køretøj) -> den
|
||||
-- køretøjs-specifikke indstilling skrives IKKE til den globale settings-tabel
|
||||
adEnv.AutoDrive.settings.rotateTargets.current = 1
|
||||
adEnv.AutoDrive.settings.useFolders.current = 1
|
||||
local settingsRun = ADRunsController.createRun("Settings Test")
|
||||
ADRunsController.updateRun(settingsRun.id, {targetWayPointId = 94, loops = 1})
|
||||
local noSettings = makeVehicle("vehicleH", "Uden AD-settings", {STRAW}, 500000, 200, 0)
|
||||
noSettings.ad.settings = {}
|
||||
ADRunsController.assignVehicle(settingsRun.id, noSettings)
|
||||
ADRunsController.startRun(adEnv, settingsRun.id)
|
||||
check("C11 global rotateTargets urørt", adEnv.AutoDrive.settings.rotateTargets.current, 1)
|
||||
check("C11 useFolders global stadig skrevet", adEnv.AutoDrive.settings.useFolders.current, 2)
|
||||
check("C11 køretøjet startet", noSettings.started, 1)
|
||||
|
||||
-- C12: startAutoDrive() kaster for én traktor -> pcall'et applyAndStart fanger fejlen,
|
||||
-- resultatet markeres ok=false/reason="error", og de ØVRIGE traktorer i run'et startes stadig
|
||||
-- (fejlen må ikke undslippe startRun-loopet).
|
||||
buildWorld()
|
||||
local brokenRun = ADRunsController.createRun("Broken Start")
|
||||
ADRunsController.updateRun(brokenRun.id, {targetWayPointId = 94, loops = 2})
|
||||
local broken = makeVehicle("vehicleBroken", "Fejl-traktor", {STRAW, TMR}, 500000, 100, 0)
|
||||
broken.startAutoDrive = function() error("boom") end
|
||||
local normal = makeVehicle("vehicleNormal", "Normal-traktor", {STRAW, TMR}, 500000, 200, 0)
|
||||
ADRunsController.assignVehicle(brokenRun.id, broken)
|
||||
ADRunsController.assignVehicle(brokenRun.id, normal)
|
||||
local c12Results = ADRunsController.startRun(adEnv, brokenRun.id)
|
||||
check("C12 to resultater", #c12Results, 2)
|
||||
check("C12 fejl-traktor ok=false", c12Results[1].ok, false)
|
||||
check("C12 fejl-traktor reason=error", c12Results[1].reason, "error")
|
||||
check("C12 fejl-traktor ikke startet", broken.started, nil)
|
||||
check("C12 normal-traktor ok=true", c12Results[2].ok, true)
|
||||
check("C12 normal-traktor startet", normal.started, 1)
|
||||
|
||||
-- C13: Auto-listen følger traktorens LIVE AD-mål (stateModule:getSecondMarker()) mens den kører,
|
||||
-- IKKE run'ets gemte targetWayPointId — ellers afviger listen fra det choosePickup/findSupplyPickup
|
||||
-- faktisk bruger, hvis brugeren redigerer run'ets Mål mens traktoren kører.
|
||||
addMarker(95, "Alt Cow Food", "Cow Food", 5300, 0)
|
||||
unloadAt[95] = {accepts = {[STRAW] = 1000}}
|
||||
local liveRun = ADRunsController.createRun("Live Target")
|
||||
ADRunsController.updateRun(liveRun.id, {targetWayPointId = 94})
|
||||
local liveRunData = ADRuns.getRun(ADRunsController.store, liveRun.id)
|
||||
local liveVehicle = makeVehicle("vehicleLive", "Live-traktor", {STRAW, TMR}, 500000, 300, 0)
|
||||
check("C13 inaktiv: auto-liste fra run'ets mål (94)", table.concat(ADRunsController.getRunIngredients(adEnv, liveRunData, liveVehicle), ","), STRAW .. "," .. TMR)
|
||||
liveVehicle.ad.stateModule.active = true
|
||||
liveVehicle.ad.stateModule.secondMarkerId = 95
|
||||
check("C13 aktiv: auto-liste fra AD's live mål (95)", table.concat(ADRunsController.getRunIngredients(adEnv, liveRunData, liveVehicle), ","), tostring(STRAW))
|
||||
liveVehicle.ad.stateModule.active = false
|
||||
liveVehicle.ad.stateModule.secondMarkerId = nil
|
||||
|
||||
-- C10: målets accepterede varer (til afkrydsning i GUI'et) + køretøjs-label
|
||||
buildWorld()
|
||||
local labelA = makeVehicle("vehicleA", "Fastrac A", {STRAW, TMR}, 1000000)
|
||||
local labelB = makeVehicle("vehicleB", "Fastrac B", {STRAW}, 700000)
|
||||
local accRun = ADRunsController.createRun("Cow Feed")
|
||||
check("C10 uden mål -> tom", #ADRunsController.getTargetAcceptedFillTypes(adEnv, ADRuns.getRun(ADRunsController.store, accRun.id)), 0)
|
||||
ADRunsController.updateRun(accRun.id, {targetWayPointId = 94})
|
||||
local accepted = ADRunsController.getTargetAcceptedFillTypes(adEnv, ADRuns.getRun(ADRunsController.store, accRun.id))
|
||||
check("C10 tre varer (halm, TMR, vand)", #accepted, 3)
|
||||
check("C10 sorteret på titel", accepted[1].title .. "," .. accepted[2].title .. "," .. accepted[3].title, "Halm,TMR,Vand")
|
||||
check("C10 navn med", accepted[1].name, "STRAW")
|
||||
check("C10 label", ADRunsController.getVehicleLabel(adEnv, labelB), "Fastrac B (#2)")
|
||||
|
||||
-- C14: Start går gennem AD's mode:start(USER_PLAYER) (som HUD'en), ikke kun startAutoDrive
|
||||
buildWorld()
|
||||
local modeVehicle = makeVehicle("vehicleM", "Mode-traktor", {STRAW, TMR}, 1000000, 100, 0)
|
||||
local modeStarts = {}
|
||||
modeVehicle.ad.stateModule.getCurrentMode = function() return {start = function(_, user) table.insert(modeStarts, user); modeVehicle:startAutoDrive() end} end
|
||||
modeVehicle.ad.stateModule.setActualFarmId = function(self, farmId) self.actualFarmId = farmId end
|
||||
adEnv.AutoDrive.USER_PLAYER = 1
|
||||
local modeRun = ADRunsController.createRun("Mode")
|
||||
ADRunsController.updateRun(modeRun.id, {targetWayPointId = 94})
|
||||
ADRunsController.assignVehicle(modeRun.id, modeVehicle)
|
||||
ADRunsController.startRun(adEnv, modeRun.id)
|
||||
check("C14 mode:start kaldt med USER_PLAYER", modeStarts[1], 1)
|
||||
check("C14 traktor aktiv", modeVehicle.started, 1)
|
||||
check("C14 farm-id sat", modeVehicle.ad.stateModule.actualFarmId, 1)
|
||||
check("C14 loopsDone nulstillet", modeVehicle.ad.stateModule.loopsDone, 0)
|
||||
|
||||
-- C15: aktivitet til overblikket — stoppet / venter / med læs / tom på vej efter vare
|
||||
ADUnloadWait = {getWaitFor = function(v) return v.waitInfo end, getRestLoad = function(_, v) return v.loadType, v.loadLiters or 0 end}
|
||||
ADSmartPickup.getActivePickup = function(v) return v.pickupInfo end
|
||||
buildWorld()
|
||||
local actVehicle = makeVehicle("vehicleAct", "Akt", {STRAW, TMR}, 1000000, 100, 0)
|
||||
check("C15 stoppet", ADRunsController.getVehicleActivity(adEnv, actVehicle).kind, "stopped")
|
||||
actVehicle.ad.stateModule.active = true
|
||||
actVehicle.ad.stateModule.secondMarkerId = 94
|
||||
actVehicle.loadType, actVehicle.loadLiters = STRAW, 8000
|
||||
local loaded = ADRunsController.getVehicleActivity(adEnv, actVehicle)
|
||||
check("C15 med læs", loaded.kind .. "/" .. loaded.fillType .. "/" .. loaded.liters, "loaded/" .. STRAW .. "/8000")
|
||||
actVehicle.loadLiters = 0
|
||||
actVehicle.pickupInfo = {markerName = "Silo 3", fillType = TMR}
|
||||
local empty = ADRunsController.getVehicleActivity(adEnv, actVehicle)
|
||||
check("C15 tom på vej efter TMR", empty.kind .. "/" .. tostring(empty.place) .. "/" .. tostring(empty.fillType), "empty/Silo 3/" .. TMR)
|
||||
actVehicle.waitInfo = {markerName = "Cow Wait", fillType = STRAW, rest = 5000}
|
||||
check("C15 venter", ADRunsController.getVehicleActivity(adEnv, actVehicle).kind, "waiting")
|
||||
ADUnloadWait = nil
|
||||
|
||||
-- C16: flere mål pr. run — Auto-varer = union; chooseTarget = den stald der mangler mest; validering
|
||||
buildWorld()
|
||||
addMarker(99, "Cow 2 Food", "Cow Food", 5200, 0); unloadAt[99] = {accepts = {[STRAW] = 300000, [TMR] = 300000}, levels = {[STRAW] = 0, [TMR] = 0}}
|
||||
unloadAt[94].levels = {[STRAW] = 250000, [TMR] = 250000, [WATER] = 50000}
|
||||
local multiVehicle = makeVehicle("vehicleMulti", "Multi", {STRAW, TMR, WATER}, 1000000, 100, 0)
|
||||
local multi = ADRunsController.createRun("Kvæg")
|
||||
ADRunsController.updateRun(multi.id, {targetWayPointIds = {94, 99}})
|
||||
local multiRun = ADRuns.getRun(ADRunsController.store, multi.id)
|
||||
check("C16 auto-varer = union (vand kun i stald 1)", table.concat(ADRunsController.getRunIngredients(adEnv, multiRun, multiVehicle), ","), WATER .. "," .. STRAW .. "," .. TMR)
|
||||
check("C16 accepterede varer = union", #ADRunsController.getTargetAcceptedFillTypes(adEnv, multiRun), 3)
|
||||
check("C16 vælger stald 2 (tom)", ADRunsController.chooseTarget(adEnv, multiRun, multiVehicle), 99)
|
||||
unloadAt[99].levels = {[STRAW] = 290000, [TMR] = 290000}
|
||||
check("C16 vælger stald 1 når stald 2 er fuld", ADRunsController.chooseTarget(adEnv, multiRun, multiVehicle), 94)
|
||||
check("C16 validering ok med flere mål", ADRunsController.validateVehicle(adEnv, multiRun, multiVehicle).ok, true)
|
||||
-- C16b: en anden vogn er allerede på vej til stald 2 — så vælges stald 1 selvom stald 2 er mindre fyldt
|
||||
-- (drive-on-bunker: kun én kan tippe ad gangen; in-game 26/9 kørte 3 biogas-vogne til samme drop)
|
||||
unloadAt[99].levels = {[STRAW] = 100000, [TMR] = 100000}
|
||||
unloadAt[94].levels = {[STRAW] = 200000, [TMR] = 200000, [WATER] = 50000}
|
||||
ADSmartPickup.countEnRoute = function(id, exclude) return id == 99 and 1 or 0 end
|
||||
check("C16b ledigt mål foran mindre fyldt mål med vogn på vej", ADRunsController.chooseTarget(adEnv, multiRun, multiVehicle), 94)
|
||||
ADSmartPickup.countEnRoute = function() return 1 end
|
||||
check("C16b lige mange på vej -> mindst fyldte", ADRunsController.chooseTarget(adEnv, multiRun, multiVehicle), 99)
|
||||
ADSmartPickup.countEnRoute = nil
|
||||
-- C16c: fyldte traktorer startet i samme run fordeles på målene (in-game 27/9: tre fyldte biogas-traktorer
|
||||
-- kørte alle til Drop 1, fordi læs der allerede lå på vognen aldrig blev registreret "på vej")
|
||||
do
|
||||
unloadAt[99].levels = {[STRAW] = 100000, [TMR] = 100000}
|
||||
unloadAt[94].levels = {[STRAW] = 200000, [TMR] = 200000, [WATER] = 50000}
|
||||
local onWay = {}
|
||||
ADSmartPickup.countEnRoute = function(id, exclude)
|
||||
local n = 0
|
||||
for v, target in pairs(onWay) do if v ~= exclude and target == id then n = n + 1 end end
|
||||
return n
|
||||
end
|
||||
ADSmartPickup.registerCargo = function(_, v, targetId) onWay[v] = targetId; return true end
|
||||
local loadedA = makeVehicle("vehicleLoadedA", "Fyldt A", {STRAW, TMR}, 1000000, 100, 0)
|
||||
local loadedB = makeVehicle("vehicleLoadedB", "Fyldt B", {STRAW, TMR}, 1000000, 110, 0)
|
||||
local both = ADRunsController.createRun("To fyldte")
|
||||
ADRunsController.updateRun(both.id, {targetWayPointIds = {94, 99}})
|
||||
ADRunsController.assignVehicle(both.id, loadedA)
|
||||
ADRunsController.assignVehicle(both.id, loadedB)
|
||||
ADRunsController.startRun(adEnv, both.id)
|
||||
local a, b = loadedA.ad.stateModule.secondMarkerId, loadedB.ad.stateModule.secondMarkerId
|
||||
check("C16c første fyldte -> mindst fyldte mål", a, 99)
|
||||
check("C16c anden fyldte -> det andet mål", b, 94)
|
||||
ADSmartPickup.countEnRoute, ADSmartPickup.registerCargo = nil, nil
|
||||
end
|
||||
ADRunsController.updateRun(multi.id, {targetWayPointIds = {4242}})
|
||||
check("C16 kun ukendte mål -> noTarget", ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, multi.id), multiVehicle).reason, "noTarget")
|
||||
|
||||
-- C17: kørende traktor der fjernes fra run stoppes; slettes run, stoppes dets traktorer; Stop logger
|
||||
buildWorld()
|
||||
local runVehicle = makeVehicle("vehicleR", "Kørende", {STRAW, TMR}, 1000000, 100, 0)
|
||||
local stopRun = ADRunsController.createRun("Stop-test")
|
||||
ADRunsController.updateRun(stopRun.id, {targetWayPointId = 94})
|
||||
ADRunsController.assignVehicle(stopRun.id, runVehicle)
|
||||
ADRunsController.startRun(adEnv, stopRun.id)
|
||||
check("C17 kører", runVehicle.started, 1)
|
||||
ADRunsController.unassignVehicle(runVehicle)
|
||||
check("C17 fjernet fra run -> stoppet", runVehicle.stopped, 1)
|
||||
ADRunsController.assignVehicle(stopRun.id, runVehicle)
|
||||
ADRunsController.startRun(adEnv, stopRun.id)
|
||||
ADRunsController.deleteRun(stopRun.id, adEnv)
|
||||
check("C17 run slettet -> stoppet", runVehicle.stopped, 2)
|
||||
check("C17 stop logget", logLines[#logLines]:find("stoppet", 1, true) ~= nil, true)
|
||||
|
||||
-- C18: forkert last ved Start (halm på vognen, run mod sukkerfabrik) -> første stop = silo der tager halm
|
||||
ADUnloadWait = {getRestLoad = function(_, v) return v.loadType, v.loadLiters or 0 end, getWaitFor = function() return nil end,
|
||||
hasRouteBothWays = function() return true end}
|
||||
ADLoadSwap = {findDumpMarker = function(_, v, fillType, rest, homeId, isReachable) return {id = 44, name = "10 Silo Storage", markerIndex = 2} end}
|
||||
buildWorld()
|
||||
addMarker(77, "sugar offload", "Fabrication", 950, 0); unloadAt[77] = {accepts = {[TMR] = 360000}} -- "fabrik" der ikke tager halm (TMR står for roer her)
|
||||
local sugarVehicle = makeVehicle("vehicleSugar", "Roe-traktor", {STRAW, TMR}, 20000, 100, 0)
|
||||
sugarVehicle.loadType, sugarVehicle.loadLiters = STRAW, 8000
|
||||
local sugar = ADRunsController.createRun("Sugar")
|
||||
ADRunsController.updateRun(sugar.id, {targetWayPointId = 77})
|
||||
ADRunsController.assignVehicle(sugar.id, sugarVehicle)
|
||||
ADRunsController.startRun(adEnv, sugar.id)
|
||||
check("C18 første stop = siloen (aflæs forkert last)", sugarVehicle.ad.stateModule.secondMarkerId, 44)
|
||||
check("C18 logget", logLines[#logLines]:find("læsser", 1, true) ~= nil or logLines[#logLines - 1]:find("læsser", 1, true) ~= nil, true)
|
||||
-- rigtig last (TMR=roer) -> direkte til fabrikken
|
||||
sugarVehicle.loadType = TMR
|
||||
sugarVehicle.ad.stateModule.active = false
|
||||
ADRunsController.startRun(adEnv, sugar.id)
|
||||
check("C18 rigtig last -> målet", sugarVehicle.ad.stateModule.secondMarkerId, 77)
|
||||
-- C18b: målet TAGER varen, men den er ikke en af run'ets valgte varer (gødning i en ensilage-DOB)
|
||||
-- -> aflæs i en silo der tager den, ikke i målet
|
||||
unloadAt[77].accepts = {[TMR] = 360000, [STRAW] = 360000}
|
||||
ADRunsController.updateRun(sugar.id, {ingredientMode = "manual", ingredients = {"FORAGE"}})
|
||||
sugarVehicle.loadType = STRAW
|
||||
sugarVehicle.ad.stateModule.active = false
|
||||
ADRunsController.startRun(adEnv, sugar.id)
|
||||
check("C18b forkert vare for run'et -> silo, ikke målet", sugarVehicle.ad.stateModule.secondMarkerId, 44)
|
||||
-- auto-varer: alt målet tager hører til -> målet
|
||||
ADRunsController.updateRun(sugar.id, {ingredientMode = "auto", ingredients = {}})
|
||||
sugarVehicle.ad.stateModule.active = false
|
||||
ADRunsController.startRun(adEnv, sugar.id)
|
||||
check("C18b auto: målet tager varen -> målet", sugarVehicle.ad.stateModule.secondMarkerId, 77)
|
||||
ADUnloadWait = nil; ADLoadSwap = nil
|
||||
|
||||
-- C19: Wait-markører er aldrig mål — heller ikke når de står inden for 40 m af staldens trigger
|
||||
buildWorld()
|
||||
-- staldens station med bygning; Wait-markøren ligger i triggerens rækkevidde og "mangler mere" end 94
|
||||
local barnPlaceable = {getHusbandryFillLevel = function() return 0 end}
|
||||
unloadAt[94].owningPlaceable = barnPlaceable
|
||||
addMarker(400, "Cow Wait", "Cow Wait", 5005, 0); unloadAt[400] = {accepts = {[STRAW] = 300000, [TMR] = 300000}, levels = {}, owningPlaceable = barnPlaceable}
|
||||
local targetNames = {}
|
||||
for _, marker in ipairs(ADRunsController.getTargetMarkers(adEnv, 1)) do table.insert(targetNames, marker.name) end
|
||||
check("C19 Wait-markør ikke i mål-listen", table.concat(targetNames, ","):find("Cow Wait", 1, true), nil)
|
||||
local waitTargetRun = ADRunsController.createRun("Wait som mål")
|
||||
ADRunsController.updateRun(waitTargetRun.id, {targetWayPointIds = {94, 400}})
|
||||
local waitVehicle = makeVehicle("vehicleW", "W", {STRAW, TMR}, 20000, 5000, 0)
|
||||
check("C19 chooseTarget springer Wait over", ADRunsController.chooseTarget(adEnv, ADRuns.getRun(ADRunsController.store, waitTargetRun.id), waitVehicle), 94)
|
||||
|
||||
-- C-out: udkørsels-runs — oprettelse, kilder, produkter, validering, start, status
|
||||
local BASKET = 7
|
||||
FILL_TYPE_NAMES[BASKET] = "STRAW_BASKET"; FILL_TYPE_INDEX["STRAW_BASKET"] = BASKET
|
||||
local sourcesAt, productsAt = {}, {}
|
||||
ADSources = {
|
||||
identify = function(_, id) return sourcesAt[id] end,
|
||||
getProducts = function(_, source) return productsAt[source.wayPointId] or {} end,
|
||||
getSellMarkers = function() return {} end,
|
||||
stationAccepts = function() return false end,
|
||||
getSourcePlaceables = function() return {} end,
|
||||
}
|
||||
local outboundStatus = {reasons = {"SUGAR 78 % af top (kræver 90), lager 41 %"}, warning = nil}
|
||||
local holdFor = {}
|
||||
ADOutbound = {
|
||||
isOutboundVehicle = function(v) local r = ADRunsController.getRunForVehicle(v) return r ~= nil and r.kind == "outbound" end,
|
||||
getRunStatus = function() return outboundStatus end,
|
||||
getHoldFor = function(v) return holdFor[v] end,
|
||||
getTripFor = function() return nil end,
|
||||
releaseInactive = function() end,
|
||||
}
|
||||
ADUnloadWait = {getRestLoad = function() return nil, 0 end, getWaitFor = function() return nil end}
|
||||
buildWorld()
|
||||
addMarker(1, "Suger Load", "Pickup points", 100, 0); loadAt[1] = {}
|
||||
addMarker(3, "Objektlager", "Pickup points", 300, 0)
|
||||
addMarker(10, "selling station", "selling", 500, 0)
|
||||
sourcesAt[1] = {kind = "factory", wayPointId = 1, station = loadAt[1], name = "Sugar mill"}
|
||||
sourcesAt[3] = {kind = "objectStorage", wayPointId = 3, name = "Objektlager"}
|
||||
productsAt[1] = {{fillType = TMR, name = "FORAGE", title = "TMR", available = 150000, level = 150000, capacity = 180000, isPallet = false}}
|
||||
productsAt[3] = {{fillType = BASKET, name = "STRAW_BASKET", title = "Halmkurve", available = 8000, level = 8, capacity = 4000, isPallet = true}}
|
||||
local bulkTractor = makeVehicle("vehicleBulk", "MT635", {STRAW, TMR}, 20000, 100, 0)
|
||||
local ualTractor = makeVehicle("vehicleUal", "Fendt", {}, 8000, 300, 0)
|
||||
ualTractor.trailers[1] = {ual = true}
|
||||
adEnv.AutoDrive.hasAL = function(_, trailer) return trailer.ual == true end
|
||||
|
||||
local out = ADRunsController.createRun("Sugar out", "outbound")
|
||||
check("Cout1 kind", ADRuns.getRun(ADRunsController.store, out.id).kind, "outbound")
|
||||
check("Cout1 supply-run har kind supply", ADRunsController.createRun("Cows").kind, "supply")
|
||||
check("Cout1 kilde-markører", #ADRunsController.getSourceMarkers(adEnv, 1), 2)
|
||||
check("Cout1 første kilde-markør", ADRunsController.getSourceMarkers(adEnv, 1)[1].marker.name, "Objektlager")
|
||||
|
||||
-- Cout2: validering
|
||||
local outRun = ADRuns.getRun(ADRunsController.store, out.id)
|
||||
-- diagnose: hvad moden ser på enhederne, når ingen autoload findes (logges én gang pr. traktor,
|
||||
-- første gang varerne slås op — dvs. allerede ved noProducts-tjekket nedenfor)
|
||||
logLines = {}
|
||||
bulkTractor.trailers[1] = {spec_universalAutoload = {isAutoloadAvailable = false, autoloadDisabled = true}, getName = function() return "Vogn" end}
|
||||
check("Cout2 ingen kilder -> noSource", ADRunsController.validateVehicle(adEnv, outRun, bulkTractor).reason, "noSource")
|
||||
ADRunsController.store = ADRuns.toggleSource(ADRunsController.store, out.id, 1)
|
||||
ADRunsController.store = ADRuns.toggleSource(ADRunsController.store, out.id, 3)
|
||||
outRun = ADRuns.getRun(ADRunsController.store, out.id)
|
||||
check("Cout2 ingen vare valgt -> noProducts", ADRunsController.validateVehicle(adEnv, outRun, bulkTractor).reason, "noProducts")
|
||||
ADRunsController.store = ADRuns.setProductPolicy(ADRunsController.store, out.id, "STRAW_BASKET", "sell")
|
||||
outRun = ADRuns.getRun(ADRunsController.store, out.id)
|
||||
check("Cout2 kun paller uden UAL -> needsAutoload", ADRunsController.validateVehicle(adEnv, outRun, bulkTractor).reason, "needsAutoload")
|
||||
local diag = nil
|
||||
for _, line in ipairs(logLines) do if line:find("ingen autoload", 1, true) then diag = line end end
|
||||
check("Cout2 diagnose logges", diag ~= nil, true)
|
||||
check("Cout2 diagnose viser UAL-flag", diag ~= nil and diag:find("isAutoloadAvailable=false", 1, true) ~= nil, true)
|
||||
logLines = {}
|
||||
ADRunsController.validateVehicle(adEnv, outRun, bulkTractor)
|
||||
check("Cout2 diagnose kun én gang", #logLines, 0)
|
||||
bulkTractor.trailers[1] = nil
|
||||
check("Cout2 UAL-vogn ok", ADRunsController.validateVehicle(adEnv, outRun, ualTractor).ok, true)
|
||||
ADRunsController.store = ADRuns.setProductPolicy(ADRunsController.store, out.id, "FORAGE", "store")
|
||||
outRun = ADRuns.getRun(ADRunsController.store, out.id)
|
||||
check("Cout2 bulk-vogn ok med TMR", ADRunsController.validateVehicle(adEnv, outRun, bulkTractor).ok, true)
|
||||
local strawOnly = makeVehicle("vehicleStraw", "Halm", {STRAW}, 20000, 100, 0)
|
||||
check("Cout2 kan ikke bære -> cannotCarry", ADRunsController.validateVehicle(adEnv, outRun, strawOnly).reason, "cannotCarry")
|
||||
|
||||
-- Cout3: start sætter mode, 1./2. markør = første kilde, varevalg = det vognen kan bære af run'ets varer
|
||||
ADRunsController.assignVehicle(out.id, bulkTractor)
|
||||
ADRunsController.assignVehicle(out.id, ualTractor)
|
||||
local results = ADRunsController.startRun(adEnv, out.id)
|
||||
check("Cout3 to startet", #results, 2)
|
||||
check("Cout3 mode P&D", bulkTractor.ad.stateModule.mode, MODE_PICKUPANDDELIVER)
|
||||
check("Cout3 1. markør = første kilde", bulkTractor.ad.stateModule.firstMarkerId, 1)
|
||||
check("Cout3 2. markør = første kilde (pladsholder)", bulkTractor.ad.stateModule.secondMarkerId, 1)
|
||||
check("Cout3 bulk-vogn: kun TMR", table.concat(bulkTractor.ad.stateModule.selectedFillTypes, ","), tostring(TMR))
|
||||
check("Cout3 UAL-vogn: kurve", table.concat(ualTractor.ad.stateModule.selectedFillTypes, ","), tostring(BASKET))
|
||||
check("Cout3 rotation kun pålæsning", bulkTractor.ad.settings.rotateTargets.current, 2)
|
||||
check("Cout3 startet", bulkTractor.started, 1)
|
||||
-- AD's "AL unload" er slået FRA som standard (values[1] = 0): autoload-vognen ville aldrig læsse af
|
||||
-- ved salgsstedet. Ved Start sættes den til "center" hvis den er fra; et brugervalg (left/right/behind) røres ikke.
|
||||
check("Cout3 ALUnload sat til center for autoload-bil", ualTractor.ad.settings.ALUnload.current, 2)
|
||||
check("Cout3 ALUnload rører ikke tipvogn", bulkTractor.ad.settings.ALUnload.current, 1)
|
||||
ualTractor.ad.settings.ALUnload.current = 4
|
||||
ualTractor.ad.stateModule.active = false
|
||||
ADRunsController.startRun(adEnv, out.id)
|
||||
check("Cout3 ALUnload brugervalg bevares", ualTractor.ad.settings.ALUnload.current, 4)
|
||||
|
||||
-- Cout4: produkter til fanen (politik slået op, sorteret på titel) og status
|
||||
local list = ADRunsController.getRunProducts(adEnv, outRun)
|
||||
check("Cout4 to produkter", #list, 2)
|
||||
check("Cout4 første = Halmkurve", list[1].title, "Halmkurve")
|
||||
check("Cout4 politik", list[1].policy, "sell")
|
||||
check("Cout4 TMR lager", list[2].policy, "store")
|
||||
check("Cout4 available", list[2].available, 150000)
|
||||
local st = ADRunsController.getOutboundStatus(adEnv, outRun)
|
||||
check("Cout4 status-begrundelse", st.reasons[1], "SUGAR 78 % af top (kræver 90), lager 41 %")
|
||||
holdFor[bulkTractor] = {markerName = "Wait 3", reasons = {"SUGAR 78 % af top (kræver 90), lager 41 %"}}
|
||||
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
|
||||
|
||||
-- C-flow: bygninger -> markører (resolveMarkers), migration af gamle markør-runs, noMarker, flow-rækker
|
||||
local buildings = {
|
||||
{id = "barn1", name = "Kostald 1", kind = "husbandry", loadMarker = {id = 10, name = "cow 1 milk", distance = 5}, loadMarkers = {{id = 10, name = "cow 1 milk", distance = 5}, {id = 11, name = "cow 1 slurry", distance = 3}}, unloadMarker = {id = 94, name = "Cow 1 Food", distance = 8}, canBeSource = true, canBeTarget = true, canSell = false},
|
||||
{id = "shed", name = "Palleskur", kind = "objectStorage", loadMarker = nil, unloadMarker = nil, canBeSource = true, canBeTarget = false, canSell = false},
|
||||
{id = "mill", name = "Sukkerfabrik", kind = "factory", loadMarker = {id = 12694, name = "Suger Load", distance = 3}, unloadMarker = {id = 77, name = "sugar offload", distance = 4}, canBeSource = true, canBeTarget = true, canSell = false},
|
||||
}
|
||||
ADBuildings = {
|
||||
list = function() return buildings end,
|
||||
find = function(_, _, id) for _, b in ipairs(buildings) do if b.id == id then return b end end end,
|
||||
byMarker = function(_, _, wayPointId, role)
|
||||
for _, b in ipairs(buildings) do
|
||||
if role == "unload" then
|
||||
if b.unloadMarker ~= nil and b.unloadMarker.id == wayPointId then return b end
|
||||
else
|
||||
for _, m in ipairs(b.loadMarkers or {b.loadMarker}) do if m ~= nil and m.id == wayPointId then return b end end
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
ADSources = {identify = function(_, id) return (id == 10 or id == 12694 or id == 555) and {kind = "x", wayPointId = id, name = "k"} or nil end, getProducts = function() return {} end, getSellMarkers = function() return {} end, stationAccepts = function() return false end, getSourcePlaceables = function() return {} end}
|
||||
ADOutbound = {isOutboundVehicle = function() return false end, getRunStatus = function() return {reasons = {"SUGAR 78 % af top"}} end, getHoldFor = function() return nil end, getTripFor = function() return nil end, releaseInactive = function() end}
|
||||
buildWorld()
|
||||
addMarker(10, "cow 1 milk", "Milk", 100, 0); loadAt[10] = {}
|
||||
addMarker(555, "raa markoer", "Pickup", 200, 0); loadAt[555] = {}
|
||||
addMarker(77, "sugar offload", "production", 950, 0); unloadAt[77] = {accepts = {[TMR] = 360000}}
|
||||
-- CF1: udkørsel: bygninger + rå markør -> sourceWayPointIds; manglende markør rapporteres
|
||||
local flowOut = ADRunsController.createRun("Milk", "outbound")
|
||||
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"barn1", "shed"}, sourceWayPointIds = {555}})
|
||||
local resolved = ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id))
|
||||
check("CF1 kildemarkører = ALLE bygningens læssemarkører + rå", table.concat(ADRuns.getRun(ADRunsController.store, flowOut.id).sourceWayPointIds, ","), "10,11,555")
|
||||
check("CF1 manglende markør", resolved.missing[1], "Palleskur")
|
||||
-- en markør der HØRER til en bygning som ikke længere er valgt, fjernes (den er ikke rå)
|
||||
ADRunsController.updateRun(flowOut.id, {sourceWayPointIds = {12694, 555}})
|
||||
ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id))
|
||||
check("CF1 fravalgt bygnings markør fjernes", table.concat(ADRuns.getRun(ADRunsController.store, flowOut.id).sourceWayPointIds, ","), "10,11,555")
|
||||
-- CF1b: gamle rå markører ryddes: ikke-kilde (identify = nil) og markør der hører til en valgt bygning
|
||||
ADSources.identify = function(_, id)
|
||||
if id == 10 or id == 12694 or id == 555 then return {kind = "x", wayPointId = id, name = "k"} end
|
||||
if id == 556 then return {kind = "husbandry", wayPointId = id, name = "Kostald 1", placeable = "barn1-placeable"} end
|
||||
return nil
|
||||
end
|
||||
buildings[1].placeable = "barn1-placeable"
|
||||
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"barn1"}, sourceWayPointIds = {10, 11, 555, 556, 999}})
|
||||
logLines = {}
|
||||
ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id))
|
||||
check("CF1b rå ikke-kilde og dublet-bygning fjernet", table.concat(ADRuns.getRun(ADRunsController.store, flowOut.id).sourceWayPointIds, ","), "10,11,555")
|
||||
local pruneLogged = false
|
||||
for _, line in ipairs(logLines) do if line:find("gamle markører fjernet", 1, true) then pruneLogged = true end end
|
||||
check("CF1b logget", pruneLogged, true)
|
||||
|
||||
-- CF1c: et gammelt kilde-id der ikke længere er en AD-MARKØR (AutoDrive har omnummereret waypoints)
|
||||
-- fjernes, selv om punktet ligger ved en kilde — ellers kører traktoren til et tilfældigt vejpunkt
|
||||
local identifyBefore = ADSources.identify
|
||||
ADSources.identify = function(adEnv_, id, farmId)
|
||||
if id == 10873 then return {kind = "husbandry", wayPointId = id, name = "Anden stald", placeable = "anden-stald"} end
|
||||
return identifyBefore(adEnv_, id, farmId)
|
||||
end
|
||||
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"barn1"}, sourceWayPointIds = {10, 11, 555, 10873}})
|
||||
ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id))
|
||||
check("CF1c vejpunkt uden markør fjernet", table.concat(ADRuns.getRun(ADRunsController.store, flowOut.id).sourceWayPointIds, ","), "10,11,555")
|
||||
ADSources.identify = identifyBefore
|
||||
|
||||
-- CF2: forsyning: mål-bygninger -> targetWayPointIds + targetWayPointId
|
||||
local flowIn = ADRunsController.createRun("Foder", "supply")
|
||||
ADRunsController.updateRun(flowIn.id, {targetBuildingIds = {"barn1", "mill"}})
|
||||
ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, flowIn.id))
|
||||
check("CF2 målmarkører", table.concat(ADRunsController.getRunTargets(ADRuns.getRun(ADRunsController.store, flowIn.id)), ","), "94,77")
|
||||
check("CF2 primært mål", ADRuns.getRun(ADRunsController.store, flowIn.id).targetWayPointId, 94)
|
||||
-- CF2b: gamle rå mål der ikke er en markør (slettet i AD) eller hører til en valgt bygning, ryddes
|
||||
addMarker(78, "Anden stald food", "Cow Food", 3000, 0); unloadAt[78] = {accepts = {[TMR] = 1000}, owningPlaceable = "anden-stald"}
|
||||
unloadAt[11062] = {accepts = {[TMR] = 1000}, owningPlaceable = "anden-stald"} -- waypoint uden markør
|
||||
ADRunsController.updateRun(flowIn.id, {targetWayPointIds = {94, 77, 11062, 78}})
|
||||
ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, flowIn.id))
|
||||
check("CF2b ikke-markør fjernet, rå markør med station beholdt", table.concat(ADRunsController.getRunTargets(ADRuns.getRun(ADRunsController.store, flowIn.id)), ","), "94,77,78")
|
||||
ADRunsController.updateRun(flowIn.id, {targetWayPointIds = {94, 77}})
|
||||
-- CF3: migration: gamle markør-runs får bygninger
|
||||
local oldOut = ADRunsController.createRun("Gammel ud", "outbound")
|
||||
ADRunsController.updateRun(oldOut.id, {sourceWayPointIds = {12694, 555}})
|
||||
local oldIn = ADRunsController.createRun("Gammel ind", "supply")
|
||||
ADRunsController.updateRun(oldIn.id, {targetWayPointIds = {94}})
|
||||
ADRunsController.migrateBuildings(adEnv)
|
||||
check("CF3 ud: markør -> bygning", ADRuns.getRun(ADRunsController.store, oldOut.id).sourceBuildingIds[1], "mill")
|
||||
check("CF3 ud: rå markør uden bygning beholdes", table.concat(ADRuns.getRun(ADRunsController.store, oldOut.id).sourceWayPointIds, ","), "12694,555")
|
||||
check("CF3 ind: markør -> bygning", ADRuns.getRun(ADRunsController.store, oldIn.id).targetBuildingIds[1], "barn1")
|
||||
ADRunsController.migrateBuildings(adEnv)
|
||||
check("CF3 migration er idempotent", #ADRuns.getRun(ADRunsController.store, oldOut.id).sourceBuildingIds, 1)
|
||||
-- CF4: validering: valgt bygning uden markør -> noMarker
|
||||
local cfVehicle = makeVehicle("vehicleCF", "CF", {STRAW, TMR}, 20000, 100, 0)
|
||||
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"shed"}, sourceWayPointIds = {}})
|
||||
local cfValidation = ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id), cfVehicle)
|
||||
check("CF4 noMarker", cfValidation.reason, "noMarker")
|
||||
check("CF4 hvilken bygning", cfValidation.missingBuilding, "Palleskur")
|
||||
-- CF4b: ÉN bygning uden markør blandt flere med markør -> start alligevel, advar om den ene
|
||||
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"barn1", "shed"}, sourceWayPointIds = {}})
|
||||
logLines = {}
|
||||
local cfValidation2 = ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id), cfVehicle)
|
||||
check("CF4b ikke afvist pga. markør", cfValidation2.reason ~= "noMarker", true)
|
||||
local warned = false
|
||||
for _, line in ipairs(logLines) do if line:find("Palleskur", 1, true) and line:find("springes over", 1, true) then warned = true end end
|
||||
check("CF4b advarsel nævner bygningen", warned, true)
|
||||
-- CF5: flow-række til tavlen
|
||||
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"barn1"}, products = {{fillType = "SUGAR", policy = "sell"}, {fillType = "FORAGE", policy = "store"}}, collectRound = true})
|
||||
ADRunsController.assignVehicle(flowOut.id, cfVehicle)
|
||||
local row = ADRunsController.getFlowRow(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id))
|
||||
check("CF5 vare-navne", table.concat(row.productNames, ","), "SUGAR,FORAGE")
|
||||
check("CF5 fra", row.fromNames[1], "Kostald 1")
|
||||
check("CF5 politikker", table.concat(row.policies, ","), "sell,store")
|
||||
check("CF5 traktor", row.vehicleNames[1], "CF")
|
||||
check("CF5 begrundelse", row.reason, "SUGAR 78 % af top")
|
||||
check("CF5 runde", row.collectRound, true)
|
||||
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"barn1", "shed"}})
|
||||
check("CF5 manglende bygning markeres", ADRunsController.getFlowRow(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id)).missing[1], "Palleskur")
|
||||
local rowIn = ADRunsController.getFlowRow(adEnv, ADRuns.getRun(ADRunsController.store, flowIn.id))
|
||||
check("CF5 forsyning fra = alle siloer", rowIn.fromAllSilos, true)
|
||||
check("CF5 forsyning til", table.concat(rowIn.toNames, ","), "Kostald 1,Sukkerfabrik")
|
||||
check("CF5 forsyning auto-varer", rowIn.productAuto, true)
|
||||
-- SB: "Lager til" — valgte bygninger -> deres aflæsningsmarkører (i valgt rækkefølge; ukendte udelades)
|
||||
do
|
||||
local sbRun = ADRunsController.createRun("Gylle", "outbound")
|
||||
ADRunsController.updateRun(sbRun.id, {storeBuildingIds = {"mill", "findes-ikke", "barn1"}})
|
||||
check("SB markører", table.concat(ADRunsController.getStoreMarkers(adEnv, ADRuns.getRun(ADRunsController.store, sbRun.id)), ","), "77,94")
|
||||
ADRunsController.updateRun(sbRun.id, {storeBuildingIds = {}})
|
||||
check("SB tom = ingen tvang", #ADRunsController.getStoreMarkers(adEnv, ADRuns.getRun(ADRunsController.store, sbRun.id)), 0)
|
||||
end
|
||||
-- CS: "Lager til" viser kun bygninger der kan tage flowets lagervarer (æg-paller: kun palleskure der tager æg)
|
||||
do
|
||||
local EGG, SILAGE = 501, 502
|
||||
local shed = {id = "shed1", kind = "objectStorage", placeable = {takesEggs = true}}
|
||||
local shedNo = {id = "shed2", kind = "objectStorage", placeable = {takesEggs = false}}
|
||||
local silo = {id = "silo9", kind = "silo", placeable = {}, unloadMarker = {id = 901}}
|
||||
unloadAt[901] = {accepts = {[SILAGE] = 100000}, getIsFillTypeSupported = function(_, ft) return ft == SILAGE end}
|
||||
local accBefore, storeBefore = ADSources.objectStorageAccepts, ADLoadSwap and ADLoadSwap.isStoreTarget
|
||||
ADSources.objectStorageAccepts = function(placeable, ft) return ft == EGG and placeable.takesEggs end
|
||||
ADLoadSwap = ADLoadSwap or {}
|
||||
ADLoadSwap.isStoreTarget = function(station, ft) return station.getIsFillTypeSupported(station, ft) end
|
||||
local eggs = {{fillType = EGG, isPallet = true}}
|
||||
check("CS palleskur der tager æg", ADRunsController.canStoreAt(adEnv, shed, eggs), true)
|
||||
check("CS palleskur der ikke tager æg", ADRunsController.canStoreAt(adEnv, shedNo, eggs), false)
|
||||
check("CS silo kan ikke tage æg-paller", ADRunsController.canStoreAt(adEnv, silo, eggs), false)
|
||||
check("CS silo tager ensilage", ADRunsController.canStoreAt(adEnv, silo, {{fillType = SILAGE, isPallet = false}}), true)
|
||||
ADSources.objectStorageAccepts = accBefore
|
||||
ADLoadSwap.isStoreTarget = storeBefore
|
||||
end
|
||||
ADBuildings = nil; ADSources = nil; ADOutbound = nil
|
||||
|
||||
-- RC-navne: køretøjer får AD-førernavn efter forløbet; ude af forløb -> modelnavnet igen
|
||||
do
|
||||
local named = makeVehicle("vehicleNamed", "Virtus 135", {MILK or 1}, 20000, 0, 0)
|
||||
named.ad.stateModule.driverName = "Virtus 135"
|
||||
named.ad.stateModule.setName = function(self, n) self.driverName = n end
|
||||
named.ad.stateModule.getName = function(self) return self.driverName end
|
||||
local run = ADRunsController.createRun("Mælk ind", "outbound")
|
||||
ADRunsController.assignVehicle(run.id, named)
|
||||
ADRunsController.applyVehicleNames(adEnv)
|
||||
check("RC-navne sat efter forløb", named.ad.stateModule.driverName, "Mælk ind")
|
||||
check("RC-navne label bruger førernavn", ADRuns.vehicleLabel(named), "Mælk ind")
|
||||
ADRunsController.store = ADRuns.unassignVehicle(ADRunsController.store, "vehicleNamed")
|
||||
ADRunsController.applyVehicleNames(adEnv)
|
||||
check("RC-navne ude af forløb -> modelnavn", named.ad.stateModule.driverName, "Virtus 135")
|
||||
check("RC-navne label uden AD-navn", ADRuns.vehicleLabel({getName = function() return "Fendt" end}), "Fendt")
|
||||
end
|
||||
|
||||
-- PM: afhentningsmarkører fra udkørsels-forløb (lagerture undgår dem som aflæsningssted)
|
||||
do
|
||||
local sale = ADRunsController.createRun("Salg-test", "outbound")
|
||||
ADRunsController.updateRun(sale.id, {sourceWayPointIds = {4711, 4712}})
|
||||
local supply = ADRunsController.createRun("Forsyning-test", "supply")
|
||||
ADRunsController.updateRun(supply.id, {sourceWayPointIds = {9999}})
|
||||
local pickups = ADRunsController.getPickupMarkers()
|
||||
check("PM udkørsels-kilde med", pickups[4711], true)
|
||||
check("PM forsyning tæller ikke", pickups[9999], nil)
|
||||
end
|
||||
|
||||
-- SA: Start/Stop alle flows — kørende flows røres ikke ved Start alle; Stop alle stopper alt aktivt
|
||||
do
|
||||
buildWorld()
|
||||
local idle = makeVehicle("vehicleAllA", "Alle A", {STRAW, TMR}, 1000000, 100, 0)
|
||||
local busy = makeVehicle("vehicleAllB", "Alle B", {STRAW, TMR}, 1000000, 100, 0)
|
||||
local runA = ADRunsController.createRun("Alle-A")
|
||||
ADRunsController.updateRun(runA.id, {targetWayPointId = 94})
|
||||
ADRunsController.assignVehicle(runA.id, idle)
|
||||
local runB = ADRunsController.createRun("Alle-B")
|
||||
ADRunsController.updateRun(runB.id, {targetWayPointId = 94})
|
||||
ADRunsController.assignVehicle(runB.id, busy)
|
||||
ADRunsController.createRun("Alle-tom") -- ingen traktorer: tæller ikke med
|
||||
ADRunsController.startRun(adEnv, runB.id)
|
||||
local busyStarts = busy.started
|
||||
local summary = ADRunsController.startAllRuns(adEnv)
|
||||
check("SA idle startet", idle.started, 1)
|
||||
check("SA kørende flow ikke genstartet", busy.started, busyStarts)
|
||||
check("SA mindst ét flow startet", summary.runsStarted >= 1, true)
|
||||
check("SA kørende flow talt", summary.alreadyRunning >= 1, true)
|
||||
check("SA traktorer startet tæller idle", summary.vehiclesStarted >= 1, true)
|
||||
check("SA resultater med", type(summary.results), "table")
|
||||
check("SA noget aktivt", ADRunsController.hasActiveRuns(adEnv), true)
|
||||
local stopped = ADRunsController.stopAllRuns(adEnv)
|
||||
check("SA stop alle stopper begge", stopped >= 2, true)
|
||||
check("SA idle stoppet", idle.ad.stateModule.active, false)
|
||||
check("SA busy stoppet", busy.ad.stateModule.active, false)
|
||||
check("SA intet aktivt bagefter", ADRunsController.hasActiveRuns(adEnv), false)
|
||||
check("SA isRunning ryddet", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, runA.id)).isRunning, false)
|
||||
end
|
||||
|
||||
|
||||
-- C20: Start alle i kø — én traktor ad gangen med STAGGER_MS mellemrum (in-game 27/9: ellers kører de
|
||||
-- sammen på parkeringspladsen); pause ophæves
|
||||
do
|
||||
buildWorld()
|
||||
local q1 = makeVehicle("vehicleQ1", "Q1", {STRAW, TMR}, 1000000, 100, 0)
|
||||
local q2 = makeVehicle("vehicleQ2", "Q2", {STRAW, TMR}, 1000000, 110, 0)
|
||||
local q3 = makeVehicle("vehicleQ3", "Q3", {}, 0, 120, 0) -- ingen vogn -> springes over, ikke i kø
|
||||
local qrun = ADRunsController.createRun("Kø")
|
||||
ADRunsController.updateRun(qrun.id, {targetWayPointIds = {94}})
|
||||
ADRunsController.assignVehicle(qrun.id, q1)
|
||||
ADRunsController.assignVehicle(qrun.id, q2)
|
||||
ADRunsController.assignVehicle(qrun.id, q3)
|
||||
local summary = ADRunsController.queueStartAll(adEnv)
|
||||
check("C20 to i kø", summary.queued, 2)
|
||||
check("C20 en sprunget over", #summary.skipped, 1)
|
||||
check("C20 intet startet før første tick", (q1.started or 0) + (q2.started or 0), 0)
|
||||
ADRunsController.processStartQueue(adEnv, 0)
|
||||
check("C20 første startes straks", (q1.started or 0) + (q2.started or 0), 1)
|
||||
ADRunsController.processStartQueue(adEnv, ADRunsController.STAGGER_MS - 1)
|
||||
check("C20 næste venter mellemrummet", (q1.started or 0) + (q2.started or 0), 1)
|
||||
ADRunsController.processStartQueue(adEnv, 1)
|
||||
check("C20 næste efter mellemrummet", (q1.started or 0) + (q2.started or 0), 2)
|
||||
check("C20 kø tom", #ADRunsController.startQueue, 0)
|
||||
check("C20 run markeret kørende", ADRunsController.runningRuns[qrun.id], true)
|
||||
end
|
||||
|
||||
|
||||
-- C21: Stop alt og parkér — stopper flow-traktorerne, sender dem til AD-parkering, sætter pause
|
||||
do
|
||||
buildWorld()
|
||||
local p1 = makeVehicle("vehicleP1", "P1", {STRAW, TMR}, 1000000, 100, 0)
|
||||
local p2 = makeVehicle("vehicleP2", "P2", {STRAW, TMR}, 1000000, 110, 0)
|
||||
local prun = ADRunsController.createRun("Park")
|
||||
ADRunsController.updateRun(prun.id, {targetWayPointIds = {94}})
|
||||
ADRunsController.assignVehicle(prun.id, p1)
|
||||
ADRunsController.assignVehicle(prun.id, p2)
|
||||
ADRunsController.startRun(adEnv, prun.id)
|
||||
local parked, paused = {}, nil
|
||||
local oldWork, oldJobs, oldSilos = ADFieldWork, ADFieldJobs, ADFieldSilos
|
||||
ADFieldWork = {
|
||||
parkMarkerId = function(_, v) return v == p1 and 700 or nil end,
|
||||
startAd = function(_, v, mode, first) parked[v] = first end,
|
||||
stopAllAssignments = function() return {} end,
|
||||
}
|
||||
ADFieldJobs = {setPaused = function(value) paused = value end}
|
||||
ADFieldSilos = {stopAll = function() end}
|
||||
ADRunsController.queueStartAll(adEnv)
|
||||
local summary = ADRunsController.stopAllAndPark(adEnv)
|
||||
check("C21 begge stoppet", (p1.stopped or 0) >= 1 and (p2.stopped or 0) >= 1, true)
|
||||
check("C21 P1 kører til parkering", parked[p1], 700)
|
||||
check("C21 P2 uden parkering bliver stående", parked[p2], nil)
|
||||
check("C21 tæller parkerede", summary.parked, 1)
|
||||
check("C21 nævner dem uden parkering", summary.noPark[1], "P2")
|
||||
check("C21 pause sat", paused, true)
|
||||
check("C21 startkø tømt", #ADRunsController.startQueue, 0)
|
||||
check("C21 run ikke længere kørende", ADRunsController.runningRuns[prun.id], nil)
|
||||
ADFieldWork, ADFieldJobs, ADFieldSilos = oldWork, oldJobs, oldSilos
|
||||
end
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,596 +0,0 @@
|
||||
-- Kør: luajit tests/test_adSources.lua (fra repo-roden)
|
||||
-- Kilde-genkendelse ved en AD-markør (fabrik / silo / objektlager), lagerstatus pr. vare,
|
||||
-- paller ved fabrikkens spawner, salgssteder, objektlager-udtag.
|
||||
|
||||
local SUGAR, FORAGE, BASKET, CRATE = 5, 6, 7, 8
|
||||
local DESC = {
|
||||
[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"},
|
||||
}
|
||||
g_fillTypeManager = {
|
||||
getFillTypeByIndex = function(_, index) return DESC[index] end,
|
||||
getFillTypeNameByIndex = function(_, index) return DESC[index] and DESC[index].name end,
|
||||
getFillTypeTitleByIndex = function(_, index) return DESC[index] and DESC[index].title end,
|
||||
}
|
||||
Logging = {info = function() end, warning = function() end}
|
||||
getWorldTranslation = function(node) return node.x, 0, node.z end
|
||||
SellingStation = {}
|
||||
|
||||
dofile("FS25_ADSmartPickup/adSupplyPlanner.lua")
|
||||
dofile("FS25_ADSmartPickup/adPrices.lua")
|
||||
dofile("FS25_ADSmartPickup/adStorageStats.lua")
|
||||
dofile("FS25_ADSmartPickup/adSources.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
|
||||
|
||||
-- ---- fakes ----
|
||||
local wayPoints, markers = {}, {}
|
||||
local function addMarker(id, name, x, z)
|
||||
wayPoints[id] = {id = id, x = x, z = z}
|
||||
table.insert(markers, {id = id, markerIndex = #markers + 1, name = name, group = "g"})
|
||||
end
|
||||
local adEnv = {ADGraphManager = {
|
||||
getWayPointById = function(_, id) return wayPoints[id] end,
|
||||
getMapMarkers = function() return markers end,
|
||||
getMapMarkerByWayPointId = function(_, id) for _, m in ipairs(markers) do if m.id == id then return m end end end,
|
||||
}}
|
||||
|
||||
local function storageOf(levels, capacities)
|
||||
return {
|
||||
fillLevels = levels,
|
||||
getFillLevel = function(_, ft) return levels[ft] or 0 end,
|
||||
getCapacity = function(_, ft) return capacities[ft] or 0 end,
|
||||
}
|
||||
end
|
||||
|
||||
local sugarStorage = storageOf({[SUGAR] = 150000, [BASKET] = 0}, {[SUGAR] = 180000, [BASKET] = 8000})
|
||||
local factory = {
|
||||
rootNode = {x = 100, z = 100}, getName = function() return "Sugar mill" end,
|
||||
spec_productionPoint = {productionPoint = {
|
||||
productions = {{outputs = {{type = SUGAR, amount = 1}}}, {outputs = {{type = BASKET, amount = 1}}}},
|
||||
storage = sugarStorage,
|
||||
palletSpawner = {spawnPlaces = {{startNode = {x = 110, z = 100}}}},
|
||||
}},
|
||||
}
|
||||
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 = {}
|
||||
local objectStorage = {
|
||||
rootNode = {x = 300, z = 300}, ownerFarmId = 1, getName = function() return "Objektlager" end,
|
||||
spec_objectStorage = {
|
||||
objectInfos = {
|
||||
{objects = {{palletAttributes = {fillType = BASKET, fillLevel = 1000}}}, numObjects = 8},
|
||||
{objects = {{baleAttributes = {fillType = FORAGE, fillLevel = 4000}}}, numObjects = 2},
|
||||
},
|
||||
numStoredObjects = 10, capacity = 4000,
|
||||
},
|
||||
removeAbstractObjectsFromStorage = function(self, index, amount, connection) table.insert(spawnCalls, {index, amount, connection}) end,
|
||||
}
|
||||
local sellStation = {owningPlaceable = {rootNode = {x = 500, z = 500}}, isa = function(_, c) return c == SellingStation end, isSellingPoint = true, acceptedFillTypes = {[SUGAR] = true}, getName = function() return "Grain Elevator" end}
|
||||
local factoryUnload = {owningPlaceable = factory, isa = function() return true end, isSellingPoint = false}
|
||||
|
||||
addMarker(1, "Suger Load", 100, 90)
|
||||
addMarker(2, "silo 2", 200, 190)
|
||||
addMarker(3, "Objektlager", 300, 280) -- 20 m fra lageret
|
||||
addMarker(4, "Langt væk", 300, 240) -- 60 m fra lageret
|
||||
addMarker(5, "selling station", 500, 490)
|
||||
addMarker(6, "sugar offload", 100, 80)
|
||||
|
||||
local loadAt = {[1] = factoryStation, [2] = siloStation}
|
||||
local unloadAt = {[5] = sellStation, [6] = factoryUnload}
|
||||
ADSmartPickup = {
|
||||
getLoadStationAtWayPointId = function(_, id) return loadAt[id] end,
|
||||
getUnloadStationAtWayPointId = function(_, id) return unloadAt[id] end,
|
||||
getOwnStorageLevel = function(station, fillTypes)
|
||||
local total = 0
|
||||
for _, storage in pairs(station.owningPlaceable.spec_silo.storages) do
|
||||
for _, ft in ipairs(fillTypes) do total = total + storage:getFillLevel(ft) end
|
||||
end
|
||||
return total
|
||||
end,
|
||||
}
|
||||
local pallets = {}
|
||||
local function addPallet(fillType, level, x, z, farmId)
|
||||
table.insert(pallets, {spec_pallet = {}, rootNode = {x = x, z = z}, getOwnerFarmId = function() return farmId or 1 end,
|
||||
getFillUnitFillType = function() return fillType end, getFillUnitFillLevel = function() return level end})
|
||||
end
|
||||
g_currentMission = {
|
||||
placeableSystem = {placeables = {factory, silo, objectStorage}},
|
||||
accessHandler = {canFarmAccess = function(_, farmId, placeable) return true end},
|
||||
vehicleSystem = {vehicles = pallets},
|
||||
}
|
||||
|
||||
-- S1: fabrik
|
||||
local source1 = ADSources.identify(adEnv, 1, 1)
|
||||
check("S1 fabrik", source1.kind, "factory")
|
||||
check("S1 navn", source1.name, "Sugar mill")
|
||||
local products1 = ADSources.getProducts(adEnv, source1, 1)
|
||||
check("S1 to produkter", #products1, 2)
|
||||
check("S1 SUGAR available", products1[1].available, 150000)
|
||||
check("S1 SUGAR level", products1[1].level, 150000)
|
||||
check("S1 SUGAR capacity", products1[1].capacity, 180000)
|
||||
check("S1 SUGAR bulk", products1[1].isPallet, false)
|
||||
check("S1 SUGAR kan også stå på paller", products1[1].canPallet, true)
|
||||
check("S1 SUGAR paller ved spawner (ingen endnu)", products1[1].palletLiters, 0)
|
||||
check("S1 TMR-lignende bulk kan ikke paller", products1[2].canPallet, true)
|
||||
check("S1 BASKET er palle", products1[2].isPallet, true)
|
||||
check("S1 BASKET tomt", products1[2].available, 0)
|
||||
|
||||
-- S2: silo
|
||||
local source2 = ADSources.identify(adEnv, 2, 1)
|
||||
check("S2 silo", source2.kind, "silo")
|
||||
local products2 = ADSources.getProducts(adEnv, source2, 1)
|
||||
check("S2 én vare", #products2, 1)
|
||||
check("S2 FORAGE", products2[1].fillType, FORAGE)
|
||||
check("S2 level", products2[1].level, 74500)
|
||||
check("S2 capacity", products2[1].capacity, 12000000)
|
||||
|
||||
-- S3: objektlager 20 m fra markøren
|
||||
local source3 = ADSources.identify(adEnv, 3, 1)
|
||||
check("S3 objektlager", source3.kind, "objectStorage")
|
||||
local products3 = ADSources.getProducts(adEnv, source3, 1)
|
||||
check("S3 to varer", #products3, 2)
|
||||
check("S3 kurve available", products3[1].available, 8000)
|
||||
check("S3 kurve er palle", products3[1].isPallet, true)
|
||||
check("S3 level = antal objekter", products3[1].level, 10)
|
||||
check("S3 capacity", products3[1].capacity, 4000)
|
||||
check("S3 baller tæller som palle", products3[2].isPallet, true)
|
||||
local index, count = ADSources.getObjectInfoIndex(source3, BASKET)
|
||||
check("S3 objectInfo-indeks", index, 1)
|
||||
check("S3 antal", count, 8)
|
||||
check("S3 countStored", ADSources.countStored(source3, FORAGE), 2)
|
||||
check("S3 ukendt vare", ADSources.getObjectInfoIndex(source3, SUGAR), nil)
|
||||
|
||||
-- S4: paller ved fabrikkens spawner tæller med (inden for 25 m af spawn-noden)
|
||||
addPallet(BASKET, 1000, 112, 100)
|
||||
addPallet(BASKET, 1000, 115, 105)
|
||||
addPallet(BASKET, 1000, 160, 100) -- 50 m væk: tæller ikke
|
||||
addPallet(BASKET, 1000, 112, 102, 2) -- anden farm: tæller ikke
|
||||
local products4 = ADSources.getProducts(adEnv, source1, 1)
|
||||
check("S4 paller ved spawner", products4[2].available, 2000)
|
||||
check("S4 antal paller ved spawner", products4[2].palletCount, 2)
|
||||
check("S3 antal paller i objektlager", products3[1].palletCount, 8)
|
||||
check("S4 bulk uændret", products4[1].available, 150000)
|
||||
addPallet(SUGAR, 2000, 111, 101)
|
||||
local products4b = ADSources.getProducts(adEnv, source1, 1)
|
||||
check("S4 sukkerpaller tælles separat", products4b[1].palletLiters, 2000)
|
||||
check("S4 bulk-mængde uden paller", products4b[1].available, 150000)
|
||||
|
||||
-- S5: salgssteder = kun markører hvis aflæsningsstation er et rigtigt salgssted
|
||||
local sells = ADSources.getSellMarkers(adEnv, 1)
|
||||
check("S5 ét salgssted", #sells, 1)
|
||||
check("S5 markør", sells[1].marker.id, 5)
|
||||
check("S5 tager SUGAR", ADSources.stationAccepts(sells[1].station, SUGAR), true)
|
||||
check("S5 tager ikke FORAGE", ADSources.stationAccepts(sells[1].station, FORAGE), false)
|
||||
-- palletriggerens varer ligger i supportedFillTypes (det AutoDrive bruger), ikke i acceptedFillTypes (kun bulk-trigger)
|
||||
local palletSell = {acceptedFillTypes = {[SUGAR] = true}, supportedFillTypes = {[SUGAR] = true, [BASKET] = true}}
|
||||
check("S5 pallevare via supportedFillTypes", ADSources.stationAccepts(palletSell, BASKET), true)
|
||||
check("S5 ukendt vare stadig nej", ADSources.stationAccepts(palletSell, FORAGE), false)
|
||||
check("S5 acceptedNames", ADSources.describeAccepted(palletSell), "STRAW_BASKET, SUGAR")
|
||||
|
||||
-- S6: spawnOne kalder spillets udtag med 1 objekt og uden klientforbindelse
|
||||
check("S6 spawn ok", ADSources.spawnOne(source3, 1), true)
|
||||
check("S6 indeks", spawnCalls[1][1], 1)
|
||||
check("S6 antal", spawnCalls[1][2], 1)
|
||||
check("S6 connection nil", spawnCalls[1][3], nil)
|
||||
check("S6 fejl -> false", ADSources.spawnOne({kind = "objectStorage", placeable = {removeAbstractObjectsFromStorage = function() error("x") end}}, 1), false)
|
||||
|
||||
-- S7: markør uden station og >40 m fra objektlager -> nil
|
||||
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 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)
|
||||
-- S9b: fabrik fundet på afstand, men produktionspunktet HAR en læssestation (sukkerfabrik hvor markøren
|
||||
-- ikke rammer triggeren): stationen hentes fra produktionspunktet, og sukker er bulk, ikke paller
|
||||
local mill2Storage = storageOf({[SUGAR] = 1000}, {[SUGAR] = 180000})
|
||||
local mill2 = {
|
||||
rootNode = {x = 1200, z = 1200}, getName = function() return "Sugar mill 2" end,
|
||||
spec_productionPoint = {productionPoint = {productions = {{outputs = {{type = SUGAR, amount = 1}}}}, storage = mill2Storage,
|
||||
loadingStation = {supportedFillTypes = {[SUGAR] = true}}}},
|
||||
}
|
||||
table.insert(g_currentMission.placeableSystem.placeables, mill2)
|
||||
addMarker(9, "Mill 2 load", 1210, 1190)
|
||||
local source9b = ADSources.identify(adEnv, 9, 1)
|
||||
check("S9b fabrik", source9b.kind, "factory")
|
||||
check("S9b station fra produktionspunktet", source9b.station, mill2.spec_productionPoint.productionPoint.loadingStation)
|
||||
check("S9b sukker = bulk", ADSources.isPalletAt(source9b, SUGAR), false)
|
||||
check("S9b produkt bulk", ADSources.getProducts(adEnv, source9b, 1)[1].isPallet, false)
|
||||
local excluded = ADSources.getSourcePlaceables({source1, source2})
|
||||
check("S8 udelukkede placeables", excluded[factory] and excluded[silo], true)
|
||||
|
||||
-- S10 (paller til lager): objektlager med plads der tager varen; nærmeste nåelige fra traktoren
|
||||
objectStorage.getObjectStorageSupportsFillType = function(self, ft) return ft == BASKET or ft == FORAGE end
|
||||
check("S10 tager kurve", ADSources.objectStorageAccepts(objectStorage, BASKET), true)
|
||||
check("S10 tager ikke sukker", ADSources.objectStorageAccepts(objectStorage, SUGAR), false)
|
||||
check("S10 fri pladser", ADSources.objectStorageFreeSlots(objectStorage), 3990)
|
||||
local fullShed = {rootNode = {x = 700, z = 700}, spec_objectStorage = {capacity = 10, numStoredObjects = 10, objectInfos = {}}, getName = function() return "Fuldt skur" end}
|
||||
table.insert(g_currentMission.placeableSystem.placeables, fullShed)
|
||||
addMarker(8, "Fuldt skur", 700, 690)
|
||||
check("S10 fuldt skur 0 pladser", ADSources.objectStorageFreeSlots(fullShed), 0)
|
||||
local truck = {rootNode = {x = 650, z = 650}}
|
||||
local store = ADSources.findPalletStore(adEnv, truck, BASKET, {}, function() return true end, 1)
|
||||
check("S10 nærmeste med plads = Objektlager (skuret er fuldt)", store.wayPointId, 3)
|
||||
check("S10 navn", store.name, "Objektlager")
|
||||
check("S10 udelukket kilde -> nil", ADSources.findPalletStore(adEnv, truck, BASKET, {[objectStorage] = true}, function() return true end, 1), nil)
|
||||
check("S10 uopnåelig -> nil", ADSources.findPalletStore(adEnv, truck, BASKET, {}, function() return false end, 1), nil)
|
||||
check("S10 sukker: intet skur tager det", ADSources.findPalletStore(adEnv, truck, SUGAR, {}, function() return true end, 1), nil)
|
||||
|
||||
-- S11 (reserve): gårdens forbrug det næste døgn af en vare, minus hvad forbrugerne allerede har
|
||||
local BEET, STRAW, WATER = 9, 10, 11
|
||||
DESC[BEET] = {index = BEET, name = "SUGARBEET", title = "Roer"}; DESC[STRAW] = {index = STRAW, name = "STRAW", title = "Halm"}; DESC[WATER] = {index = WATER, name = "WATER", title = "Vand"}
|
||||
local tmrStorage = storageOf({[BEET] = 20000, [FORAGE] = 500000}, {[BEET] = 2500000, [FORAGE] = 3500000})
|
||||
local tmrFactory = {
|
||||
rootNode = {x = 800, z = 800}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "TMR-fabrik" end,
|
||||
spec_productionPoint = {productionPoint = {
|
||||
productions = {{id = "tmr3", cyclesPerMinute = 200 / 60, inputs = {{type = BEET, amount = 45}}, outputs = {{type = FORAGE, amount = 240}}}},
|
||||
storage = tmrStorage,
|
||||
getIsProductionEnabled = function(self, id) return id == "tmr3" end,
|
||||
}},
|
||||
}
|
||||
local cowBarn = {
|
||||
rootNode = {x = 900, z = 900}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Kostald" end,
|
||||
spec_husbandryStraw = {inputLitersPerHour = 500}, spec_husbandryWater = {litersPerHour = 100, automaticWaterSupply = true},
|
||||
getHusbandryIsFillTypeSupported = function(self, ft) return ft == STRAW or ft == WATER end,
|
||||
getHusbandryFillLevel = function(self, ft) return ft == STRAW and 2000 or 0 end,
|
||||
getHusbandryFreeCapacity = function(self, ft) return ft == STRAW and 19375 or 0 end,
|
||||
}
|
||||
local otherFarmBarn = {rootNode = {x = 950, z = 950}, ownerFarmId = 2, getOwnerFarmId = function() return 2 end, spec_husbandryStraw = {inputLitersPerHour = 500}, getHusbandryIsFillTypeSupported = function() return true end, getHusbandryFillLevel = function() return 0 end, getHusbandryFreeCapacity = function() return 99999 end}
|
||||
table.insert(g_currentMission.placeableSystem.placeables, tmrFactory)
|
||||
table.insert(g_currentMission.placeableSystem.placeables, cowBarn)
|
||||
table.insert(g_currentMission.placeableSystem.placeables, otherFarmBarn)
|
||||
local demand = ADSources.getFarmDemand(adEnv, BEET, 1, {})
|
||||
-- 45 l × (200/60) pr. min × 1440 min = 216.000 l/døgn; har 20.000 -> 196.000
|
||||
check("S11 roer: døgnforbrug minus beholdning", demand.liters, 196000)
|
||||
check("S11 forbruger-navn", demand.consumers[1], "TMR-fabrik")
|
||||
local strawDemand = ADSources.getFarmDemand(adEnv, STRAW, 1, {})
|
||||
-- 500 l/t × 24 = 12.000; har 2.000 -> 10.000 (under fri plads 19.375)
|
||||
check("S11 halm: stald", strawDemand.liters, 10000)
|
||||
check("S11 anden farm tæller ikke", #strawDemand.consumers, 1)
|
||||
check("S11 automatisk vand = intet behov", ADSources.getFarmDemand(adEnv, WATER, 1, {}).liters, 0)
|
||||
check("S11 output er ikke behov", ADSources.getFarmDemand(adEnv, FORAGE, 1, {}).liters, 0)
|
||||
check("S11 udelukket forbruger", ADSources.getFarmDemand(adEnv, BEET, 1, {[tmrFactory] = true}).liters, 0)
|
||||
tmrStorage.fillLevels[BEET] = 300000
|
||||
check("S11 nok på lager -> 0", ADSources.getFarmDemand(adEnv, BEET, 1, {}).liters, 0)
|
||||
tmrStorage.fillLevels[BEET] = 20000
|
||||
tmrFactory.spec_productionPoint.productionPoint.getIsProductionEnabled = function() return false end
|
||||
check("S11 slukket produktion -> 0", ADSources.getFarmDemand(adEnv, BEET, 1, {}).liters, 0)
|
||||
|
||||
-- S11b: staldens OUTPUT (mælk, gylle) er aldrig et behov — kun varer i foderkrybben er foder
|
||||
local MILK_OUT = 13
|
||||
local feedBarn = {
|
||||
rootNode = {x = 960, z = 960}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Malkestald" end,
|
||||
spec_husbandryFood = {litersPerHour = 2000, fillLevels = {[FORAGE] = 1000}},
|
||||
getHusbandryIsFillTypeSupported = function(self, ft) return ft == MILK_OUT or ft == FORAGE end,
|
||||
getHusbandryFillLevel = function(self, ft) return ft == MILK_OUT and 200000 or ft == FORAGE and 1000 or 0 end,
|
||||
getHusbandryFreeCapacity = function(self, ft) return 9999999 end,
|
||||
}
|
||||
table.insert(g_currentMission.placeableSystem.placeables, feedBarn)
|
||||
check("S11b mælk er ikke behov", ADSources.getFarmDemand(adEnv, MILK_OUT, 1, {}).liters, 0)
|
||||
check("S11b foder er behov (2000 l/t × 24 − 1000)", ADSources.getFarmDemand(adEnv, FORAGE, 1, {}).liters, 47000)
|
||||
-- tom krybbe: foderet kendes fra dyretypens foderdefinition
|
||||
feedBarn.spec_husbandryFood.fillLevels = {}
|
||||
feedBarn.getHusbandryFillLevel = function(self, ft) return ft == MILK_OUT and 200000 or 0 end
|
||||
g_currentMission.animalFoodSystem = {getAnimalFood = function() return {groups = {{fillTypes = {FORAGE}}}} end}
|
||||
check("S11b tom krybbe: foder stadig behov", ADSources.getFarmDemand(adEnv, FORAGE, 1, {}).liters, 48000)
|
||||
check("S11b tom krybbe: mælk stadig ikke behov", ADSources.getFarmDemand(adEnv, MILK_OUT, 1, {}).liters, 0)
|
||||
g_currentMission.animalFoodSystem = nil
|
||||
table.remove(g_currentMission.placeableSystem.placeables)
|
||||
|
||||
-- S12 (inventar): alt gården har liggende, pr. bygning, kun bygninger med indhold
|
||||
factory.ownerFarmId = 1; silo.ownerFarmId = 1; objectStorage.ownerFarmId = 1
|
||||
local inventory = ADSources.getFarmInventory(adEnv, 1)
|
||||
local byName = {}
|
||||
for _, entry in ipairs(inventory) do byName[entry.name] = entry end
|
||||
check("S12 sukkerfabrik med", byName["Sugar mill"] ~= nil, true)
|
||||
check("S12 fabrik-kind", byName["Sugar mill"].kind, "factory")
|
||||
check("S12 fabrik sukker", byName["Sugar mill"].entries[1].level, 150000)
|
||||
check("S12 fabrik sukker er output", byName["Sugar mill"].entries[1].isOutput, true)
|
||||
local beetEntry
|
||||
for _, e in ipairs(byName["TMR-fabrik"].entries) do if e.fillType == BEET then beetEntry = e end end
|
||||
check("S12 TMR-fabrik roer er input", beetEntry.isOutput, false)
|
||||
check("S12 outputs først", byName["TMR-fabrik"].entries[1].isOutput, true)
|
||||
check("S12 silo", byName["Farma"].entries[1].fillType, FORAGE)
|
||||
check("S12 objektlager kurve som antal", byName["Objektlager"].entries[1].count, 8)
|
||||
-- 10 af 4000 pladser optaget -> kurvene har 8 + 3990 ledige pladser; liter-kapacitet = pladser x 1000 l
|
||||
check("S12 objektlager pladser = egne + ledige", byName["Objektlager"].entries[1].slots, 3998)
|
||||
check("S12 objektlager kapacitet i liter", byName["Objektlager"].entries[1].capacity, 3998000)
|
||||
check("S12 stald halm", byName["Kostald"].entries[1].level, 2000)
|
||||
check("S12 stald kapacitet = niveau + fri", byName["Kostald"].entries[1].capacity, 21375)
|
||||
check("S12 tomt skur udeladt", byName["Fuldt skur"], nil)
|
||||
check("S12 anden farm udeladt", #inventory, 5)
|
||||
|
||||
-- S13: Wait-markører er hverken kilder eller lagermål, selv om et objektlager står inden for 40 m
|
||||
-- (in-game 23/9: 'cow wait 1' blev valgt som palle-lager -> Dragon holdt ved Wait med stropperne af)
|
||||
wayPoints[20] = {id = 20, x = 300, z = 275}
|
||||
table.insert(markers, {id = 20, markerIndex = #markers + 1, name = "cow wait 1", group = "cow wait"})
|
||||
check("S13 Wait-markør er ikke en kilde", ADSources.identify(adEnv, 20, 1), nil)
|
||||
local truck2 = {rootNode = {x = 300, z = 270}}
|
||||
local store13 = ADSources.findPalletStore(adEnv, truck2, BASKET, {}, function() return true end, 1)
|
||||
check("S13 palle-lager = objektlagerets egen markør, ikke Wait", store13.wayPointId, 3)
|
||||
|
||||
-- S14: stald som kilde — markør ved staldens læssestation (mælketank/gylle); varer = det stationen udleverer
|
||||
local MILK = 12
|
||||
DESC[MILK] = {index = MILK, name = "MILK", title = "Mælk"}
|
||||
local barnLevels = {[MILK] = 4200, [STRAW] = 2000}
|
||||
local milkBarn = {
|
||||
rootNode = {x = 1500, z = 1500}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Kostald" end,
|
||||
getHusbandryFillLevel = function(self, ft) return barnLevels[ft] or 0 end,
|
||||
getHusbandryCapacity = function(self, ft) return ft == MILK and 25000 or ft == STRAW and 21375 or 0 end,
|
||||
getHusbandryFreeCapacity = function(self, ft) return ft == MILK and 20800 or 0 end,
|
||||
getHusbandryIsFillTypeSupported = function(self, ft) return ft == MILK or ft == STRAW end,
|
||||
}
|
||||
local milkStation = {owningPlaceable = milkBarn, supportedFillTypes = {[MILK] = true}}
|
||||
addMarker(10, "cow 1 milk", 1500, 1490); loadAt[10] = milkStation
|
||||
local source14 = ADSources.identify(adEnv, 10, 1)
|
||||
check("S14 stald", source14.kind, "husbandry")
|
||||
check("S14 navn", source14.name, "Kostald")
|
||||
local products14 = ADSources.getProducts(adEnv, source14, 1)
|
||||
check("S14 én vare (kun det tanken udleverer)", #products14, 1)
|
||||
check("S14 mælk", products14[1].fillType, MILK)
|
||||
check("S14 niveau", products14[1].level, 4200)
|
||||
check("S14 kapacitet", products14[1].capacity, 25000)
|
||||
check("S14 bulk", products14[1].isPallet, false)
|
||||
check("S14 isPalletAt stald = bulk", ADSources.isPalletAt(source14, MILK), false)
|
||||
check("S14 kilde-markør i listen", ADRunsController ~= nil and true or true, true)
|
||||
|
||||
-- S14b: mælketank-udvidelse (siloExtension) — læssestationen trækker fra stald + tank; niveauet skal
|
||||
-- være stationens samlede mængde, ikke kun staldens egen beholder
|
||||
milkStation.getAllFillLevels = function(self, farmId) return {[MILK] = 1275 + 136912}, 10025000 end
|
||||
local products14b = ADSources.getProducts(adEnv, source14, 1)
|
||||
check("S14b stald + udvidelsestank", products14b[1].level, 138187)
|
||||
check("S14b tilgængeligt = samlet", products14b[1].available, 138187)
|
||||
milkStation.getAllFillLevels = nil
|
||||
|
||||
-- S14c: mælketank (silo med udvidelses-lager) — tanken selv er tom, puljen via læssestationen har mælken
|
||||
local tank = {spec_silo = {storages = {{fillLevels = {}, getCapacity = function() return 10000000 end}}}}
|
||||
local tankSource = {kind = "silo", placeable = tank, wayPointId = 999, station = {getAllFillLevels = function() return {[MILK] = 500000}, 70000000 end}}
|
||||
local tankProducts = ADSources.getProducts(adEnv, tankSource, 1)
|
||||
check("S14c tom tank viser puljens mælk", tankProducts[1] and tankProducts[1].level, 500000)
|
||||
check("S14c kapacitet = puljens", tankProducts[1] and tankProducts[1].capacity, 70000000)
|
||||
|
||||
-- S14d: generelt lager (separatorhal): kilde via læssestationen, varer = stationens niveauer
|
||||
local DIGESTATE = 30
|
||||
DESC[DIGESTATE] = {index = DIGESTATE, name = "DIGESTATE", title = "Digestat"}
|
||||
local separator = {rootNode = {x = 7000, z = 0}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Separator Hall" end, spec_manureSeparator = {}}
|
||||
local sepStation = {owningPlaceable = separator, supportedFillTypes = {[DIGESTATE] = true}, getAllFillLevels = function() return {[DIGESTATE] = 42000}, 200000 end}
|
||||
addMarker(77, "Digestate pickup", 7000, 10); loadAt[77] = sepStation
|
||||
local sepSource = ADSources.identify(adEnv, 77, 1)
|
||||
check("S14d separator = lager-kilde", sepSource and sepSource.kind, "storage")
|
||||
local sepProducts = sepSource and ADSources.getProducts(adEnv, sepSource, 1) or {}
|
||||
check("S14d digestat fra stationen", sepProducts[1] and sepProducts[1].fillType, DIGESTATE)
|
||||
check("S14d mængde", sepProducts[1] and sepProducts[1].level, 42000)
|
||||
|
||||
-- S14e: hønsehus — æg kommer som PALLER fra staldens egen pallespawner (spec_husbandryPallets), ingen
|
||||
-- læssestation ved æggene: markøren ved spawneren er en staldkilde, æggene er pallevarer
|
||||
local EGGS = 31
|
||||
DESC[EGGS] = {index = EGGS, name = "EGG", title = "Æg", isPallet = true}
|
||||
local coopSpawner = {spawnPlaces = {{startNode = {x = 8000, z = 0}}}}
|
||||
local coop = {rootNode = {x = 8040, z = 0}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Chicken Coop" end,
|
||||
getHusbandryFillLevel = function() return 0 end,
|
||||
spec_husbandryPallets = {fillTypeIndexToPalletSpawner = {[EGGS] = coopSpawner}, activeFillTypes = {EGGS}, fillLevels = {[EGGS] = 0}, capacities = {[EGGS] = 5000}}}
|
||||
table.insert(g_currentMission.placeableSystem.placeables, coop)
|
||||
addPallet(EGGS, 1000, 8003, 2); addPallet(EGGS, 1000, 8006, 2); addPallet(EGGS, 800, 8009, 2)
|
||||
addMarker(78, "Egg Pickup", 8000, 12)
|
||||
local coopSource = ADSources.identify(adEnv, 78, 1)
|
||||
check("S14e hønsehus-kilde via pallespawner", coopSource and coopSource.kind, "husbandry")
|
||||
local coopProducts = coopSource and ADSources.getProducts(adEnv, coopSource, 1) or {}
|
||||
local eggs = nil
|
||||
for _, item in ipairs(coopProducts) do if item.fillType == EGGS then eggs = item end end
|
||||
check("S14e æg som pallevare", eggs and eggs.isPallet, true)
|
||||
check("S14e æg på paller ved spawneren", eggs and eggs.available, 2800)
|
||||
check("S14e tre paller", eggs and eggs.palletCount, 3)
|
||||
check("S14e isPalletAt æg", coopSource and ADSources.isPalletAt(coopSource, EGGS), true)
|
||||
|
||||
-- S14f: hønsehuse på række 17 m fra hinanden (inden for PALLET_RADIUS) — en palle tæller kun for den
|
||||
-- NÆRMESTE spawner, ellers "ser" et tomt hus naboernes æg og vognen kører forgæves derhen
|
||||
local coop2Spawner = {spawnPlaces = {{startNode = {x = 8020, z = 0}}}}
|
||||
local coop2 = {rootNode = {x = 8060, z = 0}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Chicken Coop" end,
|
||||
getHusbandryFillLevel = function() return 0 end,
|
||||
spec_husbandryPallets = {fillTypeIndexToPalletSpawner = {[EGGS] = coop2Spawner}, activeFillTypes = {EGGS}, fillLevels = {[EGGS] = 0}, capacities = {[EGGS] = 5000}}}
|
||||
local emptyCoopSpawner = {spawnPlaces = {{startNode = {x = 8037, z = 0}}}}
|
||||
local emptyCoop = {rootNode = {x = 8074, z = 0}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Chicken Coop" end,
|
||||
getHusbandryFillLevel = function() return 0 end,
|
||||
spec_husbandryPallets = {fillTypeIndexToPalletSpawner = {[EGGS] = emptyCoopSpawner}, activeFillTypes = {EGGS}, fillLevels = {[EGGS] = 0}, capacities = {[EGGS] = 5000}}}
|
||||
table.insert(g_currentMission.placeableSystem.placeables, coop2)
|
||||
table.insert(g_currentMission.placeableSystem.placeables, emptyCoop)
|
||||
addPallet(EGGS, 1000, 8021, 2); addPallet(EGGS, 1000, 8023, 2)
|
||||
local function eggsAt(placeable)
|
||||
for _, item in ipairs(ADSources.getProducts(adEnv, {kind = "husbandry", placeable = placeable, station = nil}, 1)) do
|
||||
if item.fillType == EGGS then return item end
|
||||
end
|
||||
end
|
||||
check("S14f første hus kun egne paller", eggsAt(coop).available, 2800)
|
||||
check("S14f nabohus kun egne paller", eggsAt(coop2).available, 2000)
|
||||
check("S14f tomt hus ser ikke naboens æg", eggsAt(emptyCoop).available, 0)
|
||||
check("S14f tomt hus 0 paller", eggsAt(emptyCoop).palletCount, 0)
|
||||
table.remove(g_currentMission.placeableSystem.placeables)
|
||||
table.remove(g_currentMission.placeableSystem.placeables)
|
||||
|
||||
-- S14g: spawnerens område er ALLE rækker fra start til slut — ikke kun første rækkes start. Hus A's
|
||||
-- række 2 slutter tæt på hus B's start; en palle for enden af A's række 2 er stadig A's.
|
||||
local rowA = {spawnPlaces = {{startNode = {x = 9000, z = 0}, endNode = {x = 9008, z = 0}}, {startNode = {x = 9000, z = 3}, endNode = {x = 9008, z = 3}}}}
|
||||
local rowB = {spawnPlaces = {{startNode = {x = 9014, z = 3}, endNode = {x = 9022, z = 3}}}}
|
||||
local function rowCoop(spawner, x)
|
||||
return {rootNode = {x = x, z = -10}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Chicken Coop" end,
|
||||
getHusbandryFillLevel = function() return 0 end,
|
||||
spec_husbandryPallets = {fillTypeIndexToPalletSpawner = {[EGGS] = spawner}, activeFillTypes = {EGGS}, fillLevels = {[EGGS] = 0}, capacities = {[EGGS] = 5000}}}
|
||||
end
|
||||
local coopA, coopB = rowCoop(rowA, 9004), rowCoop(rowB, 9018)
|
||||
table.insert(g_currentMission.placeableSystem.placeables, coopA)
|
||||
table.insert(g_currentMission.placeableSystem.placeables, coopB)
|
||||
addPallet(EGGS, 1000, 9008, 3) -- enden af A's række 2: 6 m til B's start, 8 m til A's første start
|
||||
check("S14g palle for enden af egen række 2 er husets egen", eggsAt(coopA).available, 1000)
|
||||
check("S14g naboen får den ikke", eggsAt(coopB).available, 0)
|
||||
-- PlacementUtil-form (startX/startZ + dirX/dirZ*width) i stedet for noder
|
||||
local placeC = {spawnPlaces = {{startX = 9000, startZ = 50, dirX = 1, dirZ = 0, width = 8}}}
|
||||
local coopC = rowCoop(placeC, 9004)
|
||||
table.insert(g_currentMission.placeableSystem.placeables, coopC)
|
||||
addPallet(EGGS, 700, 9007, 51)
|
||||
check("S14g PlacementUtil-plads tæller", eggsAt(coopC).available, 700)
|
||||
table.remove(g_currentMission.placeableSystem.placeables)
|
||||
table.remove(g_currentMission.placeableSystem.placeables)
|
||||
table.remove(g_currentMission.placeableSystem.placeables)
|
||||
|
||||
-- S15: forklaring af en markør til advarslen (hvad opslaget ser)
|
||||
check("S15 forklaring stald", ADSources.explainMarker(adEnv, 10, 1), "læssestation ved markøren hører til 'Kostald' (stald)")
|
||||
check("S15 forklaring ingen station", ADSources.explainMarker(adEnv, 4, 1), "ingen læssestation ved markøren, og ingen fabrik/objektlager inden for 40 m")
|
||||
loadAt[11] = {owningPlaceable = nil}
|
||||
wayPoints[11] = {id = 11, x = 2000, z = 2000}; table.insert(markers, {id = 11, markerIndex = #markers + 1, name = "løs", group = "g"})
|
||||
check("S15 station uden placeable", ADSources.explainMarker(adEnv, 11, 1), "læssestation ved markøren uden bygning (owningPlaceable = nil)")
|
||||
|
||||
-- S16: strengt opslag fejler, løst opslag (40 m, uanset retning) finder mælketanken -> stald; en SILO
|
||||
-- accepteres aldrig løst (tætte rækker -> nabosilo)
|
||||
local looseAt = {}
|
||||
ADSmartPickup.getLoadStationNearWayPointId = function(_, id, farmId, maxDistance) return looseAt[id] end
|
||||
addMarker(12, "cow 2 milk", 1600, 1600)
|
||||
looseAt[12] = milkStation
|
||||
check("S16 stald via løst opslag", ADSources.identify(adEnv, 12, 1).kind, "husbandry")
|
||||
addMarker(13, "silo løst", 1700, 1700)
|
||||
looseAt[13] = siloStation
|
||||
check("S16 silo aldrig via løst opslag", ADSources.identify(adEnv, 13, 1), nil)
|
||||
check("S16 forklaring nævner den løse station", ADSources.explainMarker(adEnv, 13, 1):find("silo", 1, true) ~= nil, true)
|
||||
|
||||
-- S16b: "Æg Pickup 1" står 12 m fra hønsehusets palleområde og 23 m fra svinestaldens gyllestation;
|
||||
-- det nærmeste vinder (in-game 26/9: Pickup 1+2 blev genkendt som Pigsty og æggene aldrig hentet)
|
||||
local looseDistance = {}
|
||||
ADSmartPickup.getLoadStationNearWayPointId = function(_, id, farmId, maxDistance) return looseAt[id], looseDistance[id] end
|
||||
local pigStation = {owningPlaceable = cowBarn}
|
||||
addMarker(14, "Æg Pickup 1", 8000, 12)
|
||||
looseAt[14], looseDistance[14] = pigStation, 23
|
||||
local eggSource = ADSources.identify(adEnv, 14, 1)
|
||||
check("S16b hønsehuset vinder over fjernere gyllestation", eggSource and eggSource.placeable, coop)
|
||||
addMarker(15, "Slurry ved hønsehus", 8000, 30)
|
||||
looseAt[15], looseDistance[15] = pigStation, 5
|
||||
check("S16b nærmere gyllestation vinder stadig", ADSources.identify(adEnv, 15, 1).placeable, cowBarn)
|
||||
|
||||
-- S17: stald med ÉN station og TO læssepunkter (mælk i den ene ende, gylle i den anden): hver markør
|
||||
-- giver kun de varer dens nærmeste læssepunkt udleverer (in-game 24/9, LargerAnimalBarns cowBarnBig)
|
||||
local SLURRY = 13
|
||||
DESC[SLURRY] = {index = SLURRY, name = "LIQUIDMANURE", title = "Gylle"}
|
||||
barnLevels[SLURRY] = 9000
|
||||
local twoEndBarn = {
|
||||
rootNode = {x = 2500, z = 2500}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Stor kostald" end,
|
||||
getHusbandryFillLevel = function(self, ft) return barnLevels[ft] or 0 end,
|
||||
getHusbandryCapacity = function() return 25000 end,
|
||||
}
|
||||
local twoEndStation = {owningPlaceable = twoEndBarn, supportedFillTypes = {[MILK] = true, [SLURRY] = true},
|
||||
loadTriggers = {{triggerNode = {x = 2500, z = 2470}, fillTypes = {[MILK] = true}}, {triggerNode = {x = 2500, z = 2530}, fillTypes = {[SLURRY] = true}}}}
|
||||
addMarker(40, "Cow Milk", 2500, 2465); loadAt[40] = twoEndStation
|
||||
addMarker(41, "Cow Slurry", 2500, 2535); loadAt[41] = twoEndStation
|
||||
local milkEnd = ADSources.getProducts(adEnv, ADSources.identify(adEnv, 40, 1), 1)
|
||||
check("S17 mælke-enden: én vare", #milkEnd, 1)
|
||||
check("S17 mælke-enden: mælk", milkEnd[1].fillType, MILK)
|
||||
local slurryEnd = ADSources.getProducts(adEnv, ADSources.identify(adEnv, 41, 1), 1)
|
||||
check("S17 gylle-enden: én vare", #slurryEnd, 1)
|
||||
check("S17 gylle-enden: gylle", slurryEnd[1].fillType, SLURRY)
|
||||
-- læssepunkt uden fillTypes-tabel: stationens varer som før
|
||||
twoEndStation.loadTriggers[1].fillTypes = nil
|
||||
check("S17 uden punkt-varer: stationens varer", #ADSources.getProducts(adEnv, ADSources.identify(adEnv, 40, 1), 1), 2)
|
||||
|
||||
-- S18: gårdens forbrug og produktion pr. døgn (fabrikker + stalde) og bedste markedspris
|
||||
tmrFactory.spec_productionPoint.productionPoint.getIsProductionEnabled = function(self, id) return id == "tmr3" end
|
||||
tmrFactory.spec_productionPoint.productionPoint.productions[1].outputs = {{type = FORAGE, amount = 240}}
|
||||
cowBarn.spec_husbandryWater.automaticWaterSupply = false
|
||||
local rates = ADSources.getFarmRates(adEnv, 1)
|
||||
-- TMR-fabrik: 45 l roer × 200/60 cyklus/min × 1440 = 216.000 l/døgn; TMR ud 240 × 200/60 × 1440 = 1.152.000
|
||||
check("S18 roer forbrug", math.floor(rates[BEET].consumption + 0.5), 216000)
|
||||
check("S18 TMR produktion", math.floor(rates[FORAGE].production + 0.5), 1152000)
|
||||
check("S18 halm i stald 500 l/t", rates[STRAW].consumption, 12000)
|
||||
check("S18 vand 100 l/t", rates[WATER].consumption, 2400)
|
||||
check("S18 anden farm tæller ikke", rates[STRAW].consumption, 12000)
|
||||
-- pris: bedste salgssted der tager varen; top = prisen ved 100 % af kurven
|
||||
g_currentMission.storageSystem = {getUnloadingStations = function() return {sellStation, {isa = function() return true end, isSellingPoint = true, owningPlaceable = {}, acceptedFillTypes = {[SUGAR] = true}, getEffectiveFillTypePrice = function() return 2.0 end}} end}
|
||||
sellStation.getEffectiveFillTypePrice = function() return 1.5 end
|
||||
g_currentMission.environment = {currentPeriod = 1}
|
||||
DESC[SUGAR].economy = {factors = {0.8, 1.0}}
|
||||
local quote = ADSources.getMarketQuote(SUGAR)
|
||||
check("S18 bedste pris", quote.price, 2.0)
|
||||
check("S18 top ved 80 % af kurven", quote.top, 2.5)
|
||||
check("S18 intet salgssted -> nil", ADSources.getMarketQuote(FORAGE), nil)
|
||||
|
||||
-- S19: staldenes produktion pr. døgn fra dyrenes output-kurver (liter pr. dyr pr. døgn efter alder)
|
||||
local function curve(value) return {get = function(_, age) return age >= 12 and value or 0 end} end
|
||||
g_currentMission.animalSystem = {getSubTypeByIndex = function(_, index)
|
||||
if index == 1 then return {output = {milk = {fillType = MILK, curve = curve(360)}, liquidManure = curve(500)}} end
|
||||
end}
|
||||
g_fillTypeManager.getFillTypeIndexByName = function(_, name) for index, desc in pairs(DESC) do if desc.name == name then return index end end end
|
||||
local producingBarn = {
|
||||
rootNode = {x = 5000, z = 5000}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Malkestald" end,
|
||||
getHusbandryFillLevel = function() return 0 end,
|
||||
getClusters = function() return {
|
||||
{getSubTypeIndex = function() return 1 end, getAge = function() return 24 end, getNumAnimals = function() return 10 end},
|
||||
{getSubTypeIndex = function() return 1 end, getAge = function() return 6 end, getNumAnimals = function() return 5 end}, -- kalve: 0
|
||||
} end,
|
||||
}
|
||||
table.insert(g_currentMission.placeableSystem.placeables, producingBarn)
|
||||
local rates2 = ADSources.getFarmRates(adEnv, 1)
|
||||
check("S19 mælk 10 køer × 360", rates2[MILK].production, 3600)
|
||||
check("S19 gylle via navn", rates2[SLURRY].production, 5000)
|
||||
|
||||
-- S19b: andre dyretyper — høns: æg som "pallets" med fillType-indeks + gødning via navn
|
||||
local EGG, MANURE_FT = 21, 22
|
||||
DESC[EGG] = {index = EGG, name = "EGG", title = "Æg"}
|
||||
DESC[MANURE_FT] = {index = MANURE_FT, name = "MANURE", title = "Gødning"}
|
||||
local baseGetSubType = g_currentMission.animalSystem.getSubTypeByIndex
|
||||
g_currentMission.animalSystem.getSubTypeByIndex = function(system, index)
|
||||
if index == 2 then return {output = {pallets = {fillType = EGG, curve = curve(30)}, manure = curve(10)}} end
|
||||
return baseGetSubType(system, index)
|
||||
end
|
||||
table.insert(g_currentMission.placeableSystem.placeables, {
|
||||
rootNode = {x = 6000, z = 6000}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Hønsehus" end,
|
||||
getHusbandryFillLevel = function() return 0 end,
|
||||
getClusters = function() return {{subTypeIndex = 2, age = 12, numAnimals = 100}} end, -- felter i stedet for metoder
|
||||
})
|
||||
local rates3 = ADSources.getFarmRates(adEnv, 1)
|
||||
check("S19b æg 100 høns × 30", rates3[EGG] and rates3[EGG].production, 3000)
|
||||
check("S19b gødning via navn", rates3[MANURE_FT] and rates3[MANURE_FT].production, 1000)
|
||||
check("S19b mælk uændret", rates3[MILK].production, 3600)
|
||||
|
||||
-- S20: statistik tæller kun produktioner der KØRER (ikke "mangler input"/slukket), og rater
|
||||
-- ganges med spillets tidsjustering (1 / dage pr. periode)
|
||||
ProductionPoint = {PROD_STATUS = {INACTIVE = 0, RUNNING = 1, MISSING_INPUTS = 2}}
|
||||
local tmrProduction = tmrFactory.spec_productionPoint.productionPoint.productions[1]
|
||||
tmrProduction.status = ProductionPoint.PROD_STATUS.MISSING_INPUTS
|
||||
local idle = ADSources.getFarmRates(adEnv, 1)
|
||||
check("S20 fabrik der mangler input tæller ikke", idle[FORAGE], nil)
|
||||
tmrProduction.status = ProductionPoint.PROD_STATUS.RUNNING
|
||||
g_currentMission.environment = {timeAdjustment = 0.5}
|
||||
local running = ADSources.getFarmRates(adEnv, 1)
|
||||
check("S20 kørende fabrik halveret ved 2 dage/periode", math.floor(running[FORAGE].production + 0.5), 576000)
|
||||
check("S20 stald halveret", running[STRAW].consumption, 6000)
|
||||
g_currentMission.environment, ProductionPoint = nil, nil
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,206 +0,0 @@
|
||||
-- Kør: luajit tests/test_adStorageStats.lua (fra repo-roden)
|
||||
-- Lagerstatistik pr. vare: fyldning, værdi nu / ved toppris, gårdens samlede beholdning, dage tilbage.
|
||||
|
||||
dofile("FS25_ADSmartPickup/adStorageStats.lua")
|
||||
local S = ADStorageStats
|
||||
|
||||
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
|
||||
|
||||
-- ST1: fuld beregning
|
||||
local st = S.itemStats({fillType = 5, level = 150000, capacity = 180000}, 412000, {consumption = 130000, production = 48000}, {price = 1.6, top = 2.0})
|
||||
check("ST1 procent", st.percent, 83)
|
||||
check("ST1 værdi nu", st.valueNow, 240000)
|
||||
check("ST1 værdi top", st.valueTop, 300000)
|
||||
check("ST1 gårdens beholdning", st.farmTotal, 412000)
|
||||
check("ST1 dage på netto (412k / 82k)", math.floor(st.days * 10 + 0.5) / 10, 5.0)
|
||||
check("ST1 netto pr. døgn", st.net, -82000)
|
||||
|
||||
-- ST2: ingen salgssted, intet forbrug, ingen kapacitet
|
||||
local st2 = S.itemStats({fillType = 6, level = 5000, capacity = 0}, 5000, nil, nil)
|
||||
check("ST2 ingen pris -> nil", st2.valueNow, nil)
|
||||
check("ST2 intet forbrug -> dage nil", st2.days, nil)
|
||||
check("ST2 kapacitet 0 -> procent nil", st2.percent, nil)
|
||||
|
||||
-- ST3: bygningens samlede værdi (kun varer med pris)
|
||||
local total = S.buildingTotals({st, st2, S.itemStats({fillType = 7, level = 1000, capacity = 2000}, 1000, nil, {price = 2, top = 3})})
|
||||
check("ST3 samlet nu", total.valueNow, 242000)
|
||||
check("ST3 samlet top", total.valueTop, 303000)
|
||||
|
||||
-- ST4: gårdens totaler fra inventar
|
||||
local totals = S.farmTotals({{entries = {{fillType = 5, level = 100}, {fillType = 6, level = 5}}}, {entries = {{fillType = 5, level = 50}}}})
|
||||
check("ST4 sum pr. vare", totals[5], 150)
|
||||
check("ST4 anden vare", totals[6], 5)
|
||||
|
||||
-- ST5: foder-forbrug i et fælles trug fordeles efter truget indhold; tomt trug -> bedste foder
|
||||
local split = S.splitTroughConsumption(2400, {[116] = 3000, [7] = 1000}, {[116] = 1.0, [7] = 0.8})
|
||||
check("ST5 TMR 75 %", split[116], 1800)
|
||||
check("ST5 ensilage 25 %", split[7], 600)
|
||||
local empty = S.splitTroughConsumption(2400, {}, {[116] = 1.0, [7] = 0.8})
|
||||
check("ST5 tomt trug -> bedste foder", empty[116], 2400)
|
||||
check("ST5 tomt trug -> intet på ensilage", empty[7], nil)
|
||||
|
||||
-- ST6: gitter — varer i rækker á n celler, tom liste giver én tom række
|
||||
local rows = S.chunk({"a", "b", "c", "d"}, 3)
|
||||
check("ST6 to rækker", #rows, 2)
|
||||
check("ST6 første række 3", #rows[1], 3)
|
||||
check("ST6 anden række 1", rows[2][1], "d")
|
||||
check("ST6 tom liste -> én tom række", #S.chunk({}, 3), 1)
|
||||
|
||||
-- ST7: rate-kolonne: forbrug med minus, produktion med plus, intet -> streg
|
||||
local fmt = function(v) return string.format("%.1fk", v / 1000) end
|
||||
check("ST7 begge", S.rateText(14400, 3600, fmt), "-14.4k +3.6k")
|
||||
check("ST7 kun prod", S.rateText(0, 3600, fmt), "+3.6k")
|
||||
check("ST7 kun forbrug", S.rateText(2000, 0, fmt), "-2.0k")
|
||||
check("ST7 intet", S.rateText(0, 0, fmt), "-")
|
||||
|
||||
-- ST8: fane pr. bygningstype — filtrér inventar på kind, rækkefølgen bevares
|
||||
local inv = {{kind = "silo", name = "A"}, {kind = "factory", name = "B"}, {kind = "silo", name = "C"}}
|
||||
local silos = S.filterKind(inv, "silo")
|
||||
check("ST8 to siloer", #silos, 2)
|
||||
check("ST8 rækkefølge", silos[2].name, "C")
|
||||
check("ST8 ingen stalde", #S.filterKind(inv, "husbandry"), 0)
|
||||
|
||||
-- ST9: produktion ≥ forbrug -> beholdningen stiger, ingen dage
|
||||
local rising = S.itemStats({fillType = 8, level = 1000, capacity = 2000}, 6700000, {consumption = 11500000, production = 19200000}, nil)
|
||||
check("ST9 stiger -> dage nil", rising.days, nil)
|
||||
check("ST9 stiger-flag", rising.rising, true)
|
||||
check("ST1 falder -> ikke stiger", st.rising, false)
|
||||
check("ST2 intet forbrug -> ikke stiger", st2.rising, false)
|
||||
|
||||
-- ST3: palleskur — procent af PLADSER (24 paller af 4000), ikke liter mod pladsantal (600 %)
|
||||
local egg = S.itemStats({fillType = 8, level = 24000, capacity = 4000000, count = 24, slots = 4000}, 24000, nil, nil)
|
||||
check("ST3 paller med", egg.count, 24)
|
||||
check("ST3 pladser med", egg.slots, 4000)
|
||||
check("ST3 procent af pladser", egg.percent, 1)
|
||||
|
||||
|
||||
-- ---- v1.52 vareoversigt ----
|
||||
|
||||
-- SV1: gruppér fanens bygninger pr. vare; bygninger sorteret mest først, varer efter titel
|
||||
local titles = {[31] = "Straw", [5] = "Sugarbeet", [9] = "Milk"}
|
||||
local titleOf = function(ft) return titles[ft] end
|
||||
local siloInv = {
|
||||
{name = "Farma #1", placeable = "p1", entries = {{fillType = 31, level = 300, capacity = 1000}, {fillType = 5, level = 50, capacity = 100}}},
|
||||
{name = "Farma #2", placeable = "p2", entries = {{fillType = 31, level = 700, capacity = 1000}}},
|
||||
{name = "Tom", placeable = "p3", entries = {}},
|
||||
}
|
||||
local groups = S.groupByFillType(siloInv, titleOf)
|
||||
check("SV1 to varer", #groups, 2)
|
||||
check("SV1 alfabetisk: Straw først", groups[1].title, "Straw")
|
||||
check("SV1 straw total", groups[1].level, 1000)
|
||||
check("SV1 straw kapacitet", groups[1].capacity, 2000)
|
||||
check("SV1 straw procent", groups[1].percent, 50)
|
||||
check("SV1 to siloer med straw", #groups[1].buildings, 2)
|
||||
check("SV1 flest først", groups[1].buildings[1].name, "Farma #2")
|
||||
check("SV1 bygning procent", groups[1].buildings[1].percent, 70)
|
||||
check("SV1 placeable med", groups[1].buildings[2].placeable, "p1")
|
||||
check("SV1 tomt inventar", #S.groupByFillType({}, titleOf), 0)
|
||||
|
||||
-- SV2: gårdens kapacitet pr. vare
|
||||
local caps = S.farmCapacities(siloInv)
|
||||
check("SV2 kapacitet straw", caps[31], 2000)
|
||||
|
||||
-- SV3: varens statistik: døgn til tom på gårdens netto, pris i % af top
|
||||
local straw = S.groupStats(groups[1], {level = 1500, capacity = 3000}, {consumption = 1000, production = 0}, {price = 0.5, top = 1.0})
|
||||
check("SV3 døgn til tom", straw.days, 1.5)
|
||||
check("SV3 døgn til fuld nil (falder)", straw.daysToFull, nil)
|
||||
check("SV3 værdi nu", straw.valueNow, 500)
|
||||
check("SV3 pris % af top", straw.priceRatio, 0.5)
|
||||
check("SV3 tomt-niveau gul (< 3 døgn)", straw.emptyLevel, "yellow")
|
||||
check("SV3 samlet niveau", straw.severity, "yellow")
|
||||
|
||||
-- SV4: produceret vare -> døgn til fuld på gårdens ledige kapacitet
|
||||
local sugar = S.groupStats({fillType = 5, title = "Sugar", level = 950, capacity = 1000, percent = 95, buildings = {}},
|
||||
{level = 950, capacity = 1000}, {consumption = 0, production = 100}, nil)
|
||||
check("SV4 døgn til fuld", sugar.daysToFull, 0.5)
|
||||
check("SV4 producerer", sugar.producing, true)
|
||||
check("SV4 fuld-niveau rød (95 %)", sugar.fullLevel, "red")
|
||||
check("SV4 ingen pris -> nil", sugar.valueNow, nil)
|
||||
|
||||
-- SV5: fuld lagervare der ikke produceres er ikke et problem
|
||||
local stock = S.groupStats({fillType = 31, title = "Straw", level = 990, capacity = 1000, percent = 99, buildings = {}}, nil, nil, nil)
|
||||
check("SV5 fuld lager uden produktion -> intet", stock.fullLevel, nil)
|
||||
check("SV5 severity nil", stock.severity, nil)
|
||||
|
||||
-- SV6: tærskler
|
||||
check("SV6 < 1 døgn rød", S.emptyLevel(0.5), "red")
|
||||
check("SV6 < 3 døgn gul", S.emptyLevel(2.9), "yellow")
|
||||
check("SV6 5 døgn intet", S.emptyLevel(5), nil)
|
||||
check("SV6 nil intet", S.emptyLevel(nil), nil)
|
||||
check("SV6 fuld 85 % gul", S.fullLevel(85, true), "yellow")
|
||||
check("SV6 fuld 84 % intet", S.fullLevel(84, true), nil)
|
||||
|
||||
-- SV7: bygningsniveau: stald-foder næsten tomt, fabrik-output næsten fuldt
|
||||
check("SV7 stald 8 % rød", S.buildingLevel({percent = 8}, "husbandry", false), "red")
|
||||
check("SV7 stald 20 % gul", S.buildingLevel({percent = 20}, "husbandry", false), "yellow")
|
||||
check("SV7 stald-output (mælk) 8 % intet", S.buildingLevel({percent = 8}, "husbandry", true), nil)
|
||||
check("SV7 output 96 % rød", S.buildingLevel({percent = 96, isOutput = true}, "factory", false), "red")
|
||||
check("SV7 silo 50 % intet", S.buildingLevel({percent = 50}, "silo", false), nil)
|
||||
|
||||
-- SV8: kræver handling — rød tom > rød fuld > gul tom > gul fuld > sælg (efter kroner); højst max
|
||||
local mk = function(t) t.buildings = {}; return t end
|
||||
local list = S.attentionList({
|
||||
mk({title = "A", emptyLevel = "yellow", days = 2, consumption = 1}),
|
||||
mk({title = "B", fullLevel = "red", percent = 97, producing = true}),
|
||||
mk({title = "C", emptyLevel = "red", days = 0.4, consumption = 1}),
|
||||
mk({title = "D", priceRatio = 0.95, valueNow = 50000, net = 0}),
|
||||
mk({title = "E", priceRatio = 0.97, valueNow = 900000, net = 0}),
|
||||
mk({title = "F", priceRatio = 0.99, valueNow = 900000, net = -5}), -- gården æder den: sælg ikke
|
||||
mk({title = "G", priceRatio = 0.99, valueNow = 5000, net = 0}), -- for lille
|
||||
}, 4)
|
||||
check("SV8 højst 4", #list, 4)
|
||||
check("SV8 1: rød tom", list[1].stat.title, "C")
|
||||
check("SV8 1: art", list[1].kind, "empty")
|
||||
check("SV8 2: rød fuld", list[2].stat.title, "B")
|
||||
check("SV8 3: gul tom", list[3].stat.title, "A")
|
||||
check("SV8 4: største salg", list[4].stat.title, "E")
|
||||
check("SV8 4: art", list[4].kind, "sell")
|
||||
|
||||
-- SV9: nøgletal for fanen
|
||||
local kpi = S.kpis({
|
||||
mk({valueNow = 100, valueTop = 150, emptyLevel = "red"}),
|
||||
mk({valueNow = 50, valueTop = 50, fullLevel = "yellow"}),
|
||||
mk({}),
|
||||
})
|
||||
check("SV9 værdi nu", kpi.valueNow, 150)
|
||||
check("SV9 ekstra ved top", kpi.gain, 50)
|
||||
check("SV9 løber tør", kpi.emptyCount, 1)
|
||||
check("SV9 fyldt", kpi.fullCount, 1)
|
||||
|
||||
-- SV10: fyldningsbrøk til bjælker, klemt til [0,1]
|
||||
check("SV10 halv", S.fraction(50), 0.5)
|
||||
check("SV10 over 100", S.fraction(130), 1)
|
||||
check("SV10 nil", S.fraction(nil), 0)
|
||||
|
||||
-- SV11: skur uden pladsgrænse (kapacitet 0) -> varens procent ukendt, ikke 940 %
|
||||
local shedGroups = S.groupByFillType({
|
||||
{name = "A", entries = {{fillType = 9, level = 40, capacity = 100}}},
|
||||
{name = "B", entries = {{fillType = 9, level = 900, capacity = 0}}},
|
||||
}, titleOf)
|
||||
check("SV11 procent nil ved ubegrænset skur", shedGroups[1].percent, nil)
|
||||
|
||||
-- "kræver handling" -> opret flow: hvem bruger varen, og hjælper et flow overhovedet? (brugerønske 29/9)
|
||||
do
|
||||
local inv = {
|
||||
{name = "Mølle", kind = "factory", placeable = "pMill", entries = {{fillType = 7, level = 1000, isOutput = false}, {fillType = 8, level = 50, isOutput = true}}},
|
||||
{name = "Silo", kind = "silo", placeable = "pSilo", entries = {{fillType = 7, level = 90000}}},
|
||||
{name = "Kostald", kind = "husbandry", placeable = "pBarn", entries = {{fillType = 7, level = 500, isOutput = false}}},
|
||||
}
|
||||
local consumers = S.consumersOf(inv, 7)
|
||||
check("forbrugere: fabrik + stald (ikke silo)", #consumers, 2)
|
||||
check("forbrugere: output tæller ikke", #S.consumersOf(inv, 8), 0)
|
||||
check("råd: eksisterende flow -> åbn det", S.flowAdvice({farmLevel = 91500, consumerLevel = 1500, perDay = 20000, consumerCount = 2, existingRunId = 4}), "open")
|
||||
check("råd: lager andre steder -> opret flow", S.flowAdvice({farmLevel = 91500, consumerLevel = 1500, perDay = 20000, consumerCount = 2}), "flow")
|
||||
check("råd: intet at flytte -> producér/køb", S.flowAdvice({farmLevel = 12700, consumerLevel = 12700, perDay = 228000, consumerCount = 1}), "produce")
|
||||
check("råd: lidt andre steder, stort forbrug -> producér/køb", S.flowAdvice({farmLevel = 20000, consumerLevel = 12000, perDay = 228000, consumerCount = 1}), "produce")
|
||||
check("råd: ingen forbrugere -> intet", S.flowAdvice({farmLevel = 5000, consumerLevel = 0, perDay = 0, consumerCount = 0}), "none")
|
||||
end
|
||||
-- "sælg nu" -> udkørsels-flow (brugerønske 29/9)
|
||||
check("salg: eksisterende salgsflow -> åbn", S.sellAdvice({sourceCount = 1, existingRunId = 3}), "open")
|
||||
check("salg: ingen -> opret flow", S.sellAdvice({sourceCount = 2}), "flow")
|
||||
check("salg: ingen bygning med varen -> intet", S.sellAdvice({sourceCount = 0}), "none")
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,266 +0,0 @@
|
||||
-- Kør: luajit tests/test_adSupplyPlanner.lua (fra repo-roden)
|
||||
-- Tester de rene behovs-funktioner; ingen spil- eller AutoDrive-mocks nødvendige.
|
||||
|
||||
dofile("FS25_ADSmartPickup/adSupplyPlanner.lua")
|
||||
|
||||
local HAY, GRASS, SILAGE = 20, 21, 22
|
||||
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
|
||||
|
||||
-- scoreNeed: tommere lager scorer højere; ugyldig kapacitet giver 0
|
||||
check("tomt scorer højere end halvfuldt", ADSupplyPlanner.scoreNeed(0, 1000, 1000) > ADSupplyPlanner.scoreNeed(500, 1000, 500), true)
|
||||
check("kapacitet 0 giver score 0", ADSupplyPlanner.scoreNeed(0, 0, 0), 0)
|
||||
|
||||
-- rankNeeds: mest trængende først, fulde springes over, input muteres ikke
|
||||
local needs = {
|
||||
{fillType = HAY, level = 80000, capacity = 100000},
|
||||
{fillType = GRASS, level = 20000, capacity = 100000},
|
||||
{fillType = SILAGE, level = 99500, capacity = 100000},
|
||||
}
|
||||
local ranked = ADSupplyPlanner.rankNeeds(needs)
|
||||
check("fuld ingrediens springes over", #ranked, 2)
|
||||
check("mest trængende først", ranked[1].fillType, GRASS)
|
||||
check("dernæst næstmest trængende", ranked[2].fillType, HAY)
|
||||
check("freeSpace beregnes", ranked[1].freeSpace, 80000)
|
||||
check("input muteres ikke", needs[1].freeSpace, nil)
|
||||
check("tom liste giver tom liste", #ADSupplyPlanner.rankNeeds({}), 0)
|
||||
|
||||
-- rankNeeds: deterministisk ved lige score (laveste fillType først)
|
||||
local tie = ADSupplyPlanner.rankNeeds({
|
||||
{fillType = SILAGE, level = 0, capacity = 5000},
|
||||
{fillType = HAY, level = 0, capacity = 5000},
|
||||
})
|
||||
check("lige score -> laveste fillType først", tie[1].fillType, HAY)
|
||||
|
||||
-- resolveIngredients
|
||||
local multi = {HAY, GRASS}
|
||||
local resolved = ADSupplyPlanner.resolveIngredients(nil, multi)
|
||||
check("multi-valg bliver ingrediensliste", #resolved, 2)
|
||||
check("listen er en kopi", resolved ~= multi, true)
|
||||
check("enkelt-valg uden tilstand -> nil", ADSupplyPlanner.resolveIngredients(nil, {HAY}), nil)
|
||||
local state = {ingredients = {HAY, GRASS, SILAGE}, narrowedTo = GRASS}
|
||||
check("indsnævret valg genbruger gemt liste", #ADSupplyPlanner.resolveIngredients(state, {GRASS}), 3)
|
||||
check("bruger valgte andet -> nil", ADSupplyPlanner.resolveIngredients(state, {HAY}), nil)
|
||||
check("nyt multi-valg erstatter gemt liste", #ADSupplyPlanner.resolveIngredients(state, {HAY, SILAGE}), 2)
|
||||
check("ugyldigt valg -> nil", ADSupplyPlanner.resolveIngredients(state, nil), nil)
|
||||
|
||||
-- Kildestrategi: lager-siloer tømmes mindst-først; tanke der løber fulde (mælk, gylle) fyldest-først
|
||||
check("mælk hentes fyldest-først", ADSupplyPlanner.prefersFullestSource({"MILK"}), true)
|
||||
check("gylle hentes fyldest-først", ADSupplyPlanner.prefersFullestSource({"LIQUIDMANURE"}), true)
|
||||
check("roer hentes mindst-først", ADSupplyPlanner.prefersFullestSource({"SUGARBEET"}), false)
|
||||
check("blandet valg -> mindst-først", ADSupplyPlanner.prefersFullestSource({"MILK", "WHEAT"}), false)
|
||||
check("ukendt/tomt valg -> mindst-først", ADSupplyPlanner.prefersFullestSource({}), false)
|
||||
check("mindst-først: lavere niveau er bedre", ADSupplyPlanner.isBetterSource(100, 500, false), true)
|
||||
check("mindst-først: højere niveau er ikke bedre", ADSupplyPlanner.isBetterSource(900, 500, false), false)
|
||||
check("fyldest-først: højere niveau er bedre", ADSupplyPlanner.isBetterSource(900, 500, true), true)
|
||||
check("første kandidat er altid bedre end ingen", ADSupplyPlanner.isBetterSource(900, nil, false), true)
|
||||
|
||||
-- Fabriksstyret forsyning: hvilke produktioner "ønsker" input?
|
||||
local MISSING_INPUTS, INACTIVE = 2, 0
|
||||
check("tændt produktion ønskes", ADSupplyPlanner.isProductionWanted(true, INACTIVE, MISSING_INPUTS), true)
|
||||
check("auto-slukket pga. mangel ønskes stadig", ADSupplyPlanner.isProductionWanted(false, MISSING_INPUTS, MISSING_INPUTS), true)
|
||||
check("manuelt slukket ønskes ikke", ADSupplyPlanner.isProductionWanted(false, INACTIVE, MISSING_INPUTS), false)
|
||||
check("ukendt status uden tænd -> ikke ønsket", ADSupplyPlanner.isProductionWanted(false, nil, MISSING_INPUTS), false)
|
||||
|
||||
-- collectProductionDemand: liter/min pr. vare fra ønskede produktioner, kun det vognen kan bære
|
||||
local STRAW, MINERAL, WATER = 23, 24, 25
|
||||
local productions = {
|
||||
{id = "tmr", cyclesPerMinute = 2, inputs = {{type = SILAGE, amount = 100}, {type = STRAW, amount = 10}, {type = MINERAL, amount = 1}}},
|
||||
{id = "mix", cyclesPerMinute = 1, inputs = {{type = SILAGE, amount = 50}, {type = WATER, amount = 500}}},
|
||||
{id = "off", cyclesPerMinute = 5, inputs = {{type = HAY, amount = 100}}},
|
||||
}
|
||||
local wanted = {tmr = true, mix = true}
|
||||
local canCarry = {[SILAGE] = true, [STRAW] = true, [MINERAL] = true, [HAY] = true}
|
||||
local demand = ADSupplyPlanner.collectProductionDemand(productions, function(p) return wanted[p.id] == true end, canCarry)
|
||||
check("forbrug summeres på tværs af produktioner", demand[SILAGE], 250)
|
||||
check("forbrug ganges med cyklusser/min", demand[STRAW], 20)
|
||||
check("uønsket produktion tæller ikke", demand[HAY], nil)
|
||||
check("vare vognen ikke kan bære udelades", demand[WATER], nil)
|
||||
check("tom produktionsliste -> tomt behov", next(ADSupplyPlanner.collectProductionDemand({}, function() return true end, canCarry)), nil)
|
||||
check("produktion uden inputs tåles", next(ADSupplyPlanner.collectProductionDemand({{id = "x", cyclesPerMinute = 1}}, function() return true end, canCarry)), nil)
|
||||
|
||||
-- minutesToEmpty
|
||||
check("minutter til tom", ADSupplyPlanner.minutesToEmpty(1000, 250), 4)
|
||||
check("intet forbrug -> nil", ADSupplyPlanner.minutesToEmpty(1000, 0), nil)
|
||||
check("ukendt forbrug -> nil", ADSupplyPlanner.minutesToEmpty(1000, nil), nil)
|
||||
|
||||
-- rankNeeds med forbrug: den der løber tør først vinder, selv om den er relativt fyldest
|
||||
local timed = ADSupplyPlanner.rankNeeds({
|
||||
{fillType = MINERAL, level = 500, capacity = 10000, litersPerMinute = 2}, -- 250 min, 95 % tom
|
||||
{fillType = SILAGE, level = 50000, capacity = 100000, litersPerMinute = 250}, -- 200 min, 50 % tom
|
||||
{fillType = STRAW, level = 90000, capacity = 100000, litersPerMinute = 20}, -- 4500 min
|
||||
})
|
||||
check("kortest tid til tom først", timed[1].fillType, SILAGE)
|
||||
check("dernæst næstkortest", timed[2].fillType, MINERAL)
|
||||
check("minutesToEmpty følger med", timed[1].minutesToEmpty, 200)
|
||||
check("vare med forbrug slår vare uden", ADSupplyPlanner.rankNeeds({
|
||||
{fillType = HAY, level = 0, capacity = 100000},
|
||||
{fillType = STRAW, level = 90000, capacity = 100000, litersPerMinute = 20},
|
||||
})[1].fillType, STRAW)
|
||||
check("fuldt mål springes over trods forbrug", #ADSupplyPlanner.rankNeeds({
|
||||
{fillType = STRAW, level = 99500, capacity = 100000, litersPerMinute = 20},
|
||||
}), 0)
|
||||
|
||||
-- isProductionInput: en fabriks input må aldrig stjæles som kilde — medmindre
|
||||
-- fabrikken også selv producerer varen (så er den et output, der må hentes)
|
||||
local TMR = 26
|
||||
local factory = {
|
||||
{id = "tmr", inputs = {{type = SILAGE, amount = 100}, {type = STRAW, amount = 10}}, outputs = {{type = TMR, amount = 50}}},
|
||||
{id = "pellets", inputs = {{type = TMR, amount = 10}}, outputs = {{type = HAY, amount = 5}}},
|
||||
}
|
||||
check("input til fabrikken er beskyttet", ADSupplyPlanner.isProductionInput(factory, STRAW), true)
|
||||
check("output må hentes", ADSupplyPlanner.isProductionInput(factory, HAY), false)
|
||||
check("både input og output (kæde) -> må hentes", ADSupplyPlanner.isProductionInput(factory, TMR), false)
|
||||
check("vare fabrikken ikke kender -> ikke beskyttet", ADSupplyPlanner.isProductionInput(factory, WATER), false)
|
||||
check("ingen produktioner -> ikke beskyttet", ADSupplyPlanner.isProductionInput({}, STRAW), false)
|
||||
check("produktion uden inputs/outputs tåles", ADSupplyPlanner.isProductionInput({{id = "x"}}, STRAW), false)
|
||||
|
||||
-- rankNeeds med læs-størrelse: varer hvor et FULDT læs passer går forud, ellers mest trængende
|
||||
local sized = ADSupplyPlanner.rankNeeds({
|
||||
{fillType = STRAW, level = 40000, capacity = 50000, litersPerMinute = 500}, -- 80 min, kun 10000 fri
|
||||
{fillType = TMR, level = 20000, capacity = 100000, litersPerMinute = 50}, -- 400 min, 80000 fri
|
||||
}, 40000)
|
||||
check("fuldt læs der passer vinder over mere trængende der ikke passer", sized[1].fillType, TMR)
|
||||
check("den der ikke passer bevares som reserve", sized[2].fillType, STRAW)
|
||||
check("fits markeres", sized[1].fits, true)
|
||||
check("ikke-fits markeres", sized[2].fits, false)
|
||||
local noneFit = ADSupplyPlanner.rankNeeds({
|
||||
{fillType = STRAW, level = 40000, capacity = 50000, litersPerMinute = 500},
|
||||
{fillType = TMR, level = 90000, capacity = 100000, litersPerMinute = 50},
|
||||
}, 40000)
|
||||
check("passer intet -> mest trængende først", noneFit[1].fillType, STRAW)
|
||||
check("uden læs-størrelse -> som før", ADSupplyPlanner.rankNeeds({{fillType = STRAW, level = 40000, capacity = 50000}})[1].fits, nil)
|
||||
|
||||
-- Wait-punkter: MAPPEN har ordet "Wait" (uanset store/små); markørnavnet er ligegyldigt.
|
||||
-- (Brugeren har mark-ventepladser som "F91 Wait" i mark-mapperne — de må ikke ryge i puljen.)
|
||||
check("mappen 'Cow Wait' tæller", ADSupplyPlanner.isWaitMarker({name = "Cow Wait", group = "Cow Wait"}), true)
|
||||
check("mappen 'Wait' tæller", ADSupplyPlanner.isWaitMarker({name = "P2", group = "Wait"}), true)
|
||||
check("mappen 'wait stalde' tæller", ADSupplyPlanner.isWaitMarker({name = "3", group = "wait stalde"}), true)
|
||||
check("mark-venteplads i mark-mappe tæller IKKE", ADSupplyPlanner.isWaitMarker({name = "F91 Wait", group = "Græs Marker"}), false)
|
||||
check("mappen 'Waiter' tæller ikke", ADSupplyPlanner.isWaitMarker({name = "x", group = "Waiter"}), false)
|
||||
check("uden mappe -> nej", ADSupplyPlanner.isWaitMarker({name = "Wait 1"}), false)
|
||||
|
||||
-- nærmeste ledige wait-punkt; candidates = {{marker, x, z}}, claims = {[markerId] = vehicle}
|
||||
local V1, V2 = {}, {}
|
||||
local waitPoints = {
|
||||
{marker = {id = 1, name = "Wait A"}, x = 0, z = 0},
|
||||
{marker = {id = 2, name = "Wait B"}, x = 100, z = 0},
|
||||
{marker = {id = 3, name = "Wait C"}, x = 300, z = 0},
|
||||
}
|
||||
check("nærmeste ledige vælges", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {}, 10, 0, V1).marker.id, 1)
|
||||
check("reserveret springes over", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {[1] = V2}, 10, 0, V1).marker.id, 2)
|
||||
check("egen reservation tæller som ledig", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {[1] = V1}, 10, 0, V1).marker.id, 1)
|
||||
check("alle optaget -> nil", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {[1] = V2, [2] = V2, [3] = V2}, 10, 0, V1), nil)
|
||||
check("ingen punkter -> nil", ADSupplyPlanner.pickNearestFreeWaitPoint({}, {}, 0, 0, V1), nil)
|
||||
-- fysisk optaget: et køretøj holder på punktet uden reservation (fx efter omplanlægning) -> spring over
|
||||
local occ = ADSupplyPlanner.markOccupiedWaitPoints(waitPoints, {{x = waitPoints[1].x + 2, z = waitPoints[1].z}}, 6)
|
||||
check("optaget punkt markeres", occ[waitPoints[1].marker.id], true)
|
||||
check("optaget springes over", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {}, 10, 0, V1, nil, occ).marker.id, 2)
|
||||
check("egen reservation vinder over optaget (det er en selv)", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {[1] = V1}, 10, 0, V1, nil, occ).marker.id, 1)
|
||||
check("køretøj langt væk optager intet", next(ADSupplyPlanner.markOccupiedWaitPoints(waitPoints, {{x = 9999, z = 9999}}, 6)), nil)
|
||||
-- ét køretøj mellem to punkter optager kun det NÆRMESTE
|
||||
local between = ADSupplyPlanner.markOccupiedWaitPoints({{marker = {id = 7}, x = 0, z = 0}, {marker = {id = 8}, x = 6, z = 0}}, {{x = 2, z = 0}}, 6)
|
||||
check("kun nærmeste punkt optages", (between[7] == true and between[8] == nil), true)
|
||||
-- nåelighed: et punkt AD ikke kan finde vej til/fra springes over (ellers slukker AD sig selv)
|
||||
local unreachableA = function(candidate) return candidate.marker.id ~= 1 end
|
||||
check("unåeligt nærmeste springes over", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {}, 10, 0, V1, unreachableA).marker.id, 2)
|
||||
check("intet nåeligt -> nil", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {}, 10, 0, V1, function() return false end), nil)
|
||||
|
||||
-- Hjem fra Wait: fri plads >= min(rest, halvdelen af målets kapacitet)
|
||||
-- (en 700.000 l-vogn kan aldrig tømme HELE resten i en 300.000 l-stald)
|
||||
check("hele resten passer -> hjem", ADSupplyPlanner.canUnloadRest(35000, 35000, 300000), true)
|
||||
check("resten passer ikke endnu -> vent", ADSupplyPlanner.canUnloadRest(5000, 20000, 50000), false)
|
||||
check("kæmpe rest: halv stald fri -> hjem", ADSupplyPlanner.canUnloadRest(150000, 700000, 300000), true)
|
||||
check("kæmpe rest: under halv stald fri -> vent", ADSupplyPlanner.canUnloadRest(100000, 700000, 300000), false)
|
||||
check("ukendt kapacitet -> kun hele resten tæller", ADSupplyPlanner.canUnloadRest(100000, 700000, nil), false)
|
||||
|
||||
-- canUnloadRest med run-tærskel: "under 30 %" = fri >= 70 % af kapaciteten
|
||||
check("tærskel 30 %: 60 % fri er ikke nok", ADSupplyPlanner.canUnloadRest(60000, 90000, 100000, 0.7), false)
|
||||
check("tærskel 30 %: 70 % fri er nok", ADSupplyPlanner.canUnloadRest(70000, 90000, 100000, 0.7), true)
|
||||
check("hele resten passer vinder altid", ADSupplyPlanner.canUnloadRest(5000, 5000, 100000, 0.7), true)
|
||||
check("uden tærskel = 50 %-reglen", ADSupplyPlanner.canUnloadRest(50000, 90000, 100000), true)
|
||||
|
||||
-- rankNeeds: fælles trug (scoreOverride) + fodervægt (priority): TMR før hø før græs,
|
||||
-- men et separat halmlager der er tommere vinder over truget
|
||||
local TMR, HAYX = 40, 41
|
||||
local trough = ADSupplyPlanner.rankNeeds({
|
||||
{fillType = GRASS, level = 0, capacity = 30000, scoreOverride = 0.3, priority = 0.4},
|
||||
{fillType = TMR, level = 0, capacity = 30000, scoreOverride = 0.3, priority = 1.0},
|
||||
{fillType = HAYX, level = 0, capacity = 30000, scoreOverride = 0.3, priority = 0.8},
|
||||
{fillType = HAY, level = 20000, capacity = 100000}, -- halm-agtigt separat lager, 80 % tomt
|
||||
})
|
||||
check("separat lager 80 % tomt vinder over trug 30 % tomt", trough[1].fillType, HAY)
|
||||
check("i truget: TMR (1.0) først", trough[2].fillType, TMR)
|
||||
check("så hø (0.8)", trough[3].fillType, HAYX)
|
||||
check("så græs (0.4)", trough[4].fillType, GRASS)
|
||||
check("scoreOverride bruges som score", trough[2].score, 0.3)
|
||||
|
||||
-- Bedste foder først: lavere foder (ensilage 0,8) må kun hentes når truget er nødlidende
|
||||
-- (fri ≥ EMERGENCY_TROUGH_FREE af kapaciteten), hvis der er bedre foder (TMR 1,0) i samme trug
|
||||
local tmrNeed = {fillType = 116, priority = 1.0, score = 0.6, trough = true}
|
||||
local silageNeed = {fillType = 7, priority = 0.8, score = 0.6, trough = true}
|
||||
local strawNeed = {fillType = 31, priority = 0, score = 0.9}
|
||||
check("NF1 bedste foder tillades altid", ADSupplyPlanner.isFoodAllowed(tmrNeed, 1.0), true)
|
||||
check("NF1 ensilage afvist ved halvfuldt trug", ADSupplyPlanner.isFoodAllowed(silageNeed, 1.0), false)
|
||||
silageNeed.score = 0.8
|
||||
check("NF1 ensilage tilladt når truget er næsten tomt", ADSupplyPlanner.isFoodAllowed(silageNeed, 1.0), true)
|
||||
check("NF1 ikke-foder (halm) påvirkes ikke", ADSupplyPlanner.isFoodAllowed(strawNeed, 1.0), true)
|
||||
check("NF1 intet bedre foder på listen -> tilladt", ADSupplyPlanner.isFoodAllowed({fillType = 7, priority = 0.8, score = 0.1, trough = true}, 0.8), true)
|
||||
check("NF1 bedste foderprioritet", ADSupplyPlanner.bestFoodPriority({tmrNeed, silageNeed, strawNeed}), 1.0)
|
||||
|
||||
-- Læs på vej (andre traktorer): lægges oven i målets niveau; foder deler trug -> hele trugets fri plads falder
|
||||
local pendingNeeds = {
|
||||
{fillType = 31, level = 5000, capacity = 20000}, -- halm
|
||||
{fillType = 116, level = 0, capacity = 30000, scoreOverride = 1.0, troughCapacity = 30000}, -- TMR, tomt trug
|
||||
{fillType = 7, level = 0, capacity = 30000, scoreOverride = 1.0, troughCapacity = 30000}, -- ensilage
|
||||
}
|
||||
local adjusted = ADSupplyPlanner.applyPending(pendingNeeds, {[116] = 20000, [31] = 20000})
|
||||
check("AP halm: niveau + på vej, højst kapacitet", adjusted[1].level, 20000)
|
||||
check("AP TMR: niveau + på vej", adjusted[2].level, 20000)
|
||||
check("AP TMR: kapacitet = niveau + resterende fri", adjusted[2].capacity, 30000)
|
||||
check("AP ensilage: truget har kun 10000 fri tilbage", adjusted[3].capacity - adjusted[3].level, 10000)
|
||||
check("AP trug-score falder", math.floor(adjusted[3].scoreOverride * 100 + 0.5), 33)
|
||||
check("AP input urørt", pendingNeeds[2].level, 0)
|
||||
check("AP intet på vej -> samme værdier", ADSupplyPlanner.applyPending(pendingNeeds, {})[1].level, 5000)
|
||||
local ranked = ADSupplyPlanner.rankNeeds(adjusted, 8000)
|
||||
check("AP halm fuld (på vej) -> kun trug-varer tilbage", #ranked, 2)
|
||||
|
||||
-- PF: parallelt foder (høns: korn 80 % / roesnitter 20 % af det de spiser) — hver gruppe har sin andel af truget
|
||||
do
|
||||
local WHEAT, BARLEY, BEETCUT = 1, 2, 3
|
||||
local groups = {{fillTypes = {WHEAT, BARLEY}, eatWeight = 0.8}, {fillTypes = {BEETCUT}, eatWeight = 0.2}}
|
||||
local levels = {[WHEAT] = 60000, [BARLEY] = 20000, [BEETCUT] = 0}
|
||||
local lvl, cap = ADSupplyPlanner.parallelFoodShare(groups, levels, 100000, BEETCUT)
|
||||
check("PF roesnitter: andel 20k, har 0", lvl .. "/" .. cap, "0/20000")
|
||||
lvl, cap = ADSupplyPlanner.parallelFoodShare(groups, levels, 100000, BARLEY)
|
||||
check("PF korn-gruppen samlet: 80k af 80k", lvl .. "/" .. cap, "80000/80000")
|
||||
check("PF ukendt vare -> nil", ADSupplyPlanner.parallelFoodShare(groups, levels, 100000, 99), nil)
|
||||
end
|
||||
|
||||
-- cargoFor: læs der allerede er på vognen ved start tæller som "på vej" (in-game 27/9: tre fyldte
|
||||
-- biogas-traktorer startet fra fanen kørte alle til Drop 1 — ingen var registreret på vej)
|
||||
check("CF run-vare på vognen", select(2, ADSupplyPlanner.cargoFor(SILAGE, 427215, {SILAGE})), 427215)
|
||||
check("CF vare uden for run'et -> nil", ADSupplyPlanner.cargoFor(HAY, 5000, {SILAGE}), nil)
|
||||
check("CF tom vogn -> nil", ADSupplyPlanner.cargoFor(SILAGE, 0, {SILAGE}), nil)
|
||||
check("CF ukendt vare -> nil", ADSupplyPlanner.cargoFor(nil, 5000, {SILAGE}), nil)
|
||||
|
||||
-- kilde med nok til et rigtigt læs: mindst fyldte først kun blandt kilder med >= 25 % af et læs; ellers fyldeste
|
||||
-- (in-game 29/9: 4 foder-traktorer hentede 2-10k l i en gæringssilo der løbende fyldes, og kørte "tomme")
|
||||
local SP = ADSupplyPlanner
|
||||
check("kilde-grænse: 25 % af læsset", SP.ampleLevel(1000000), 250000)
|
||||
check("kilde-grænse: ukendt læs -> kun minimum", SP.ampleLevel(nil), SP.MIN_AMPLE_LITERS)
|
||||
check("kilde-grænse: lille vogn -> mindst minimum", SP.ampleLevel(10000), SP.MIN_AMPLE_LITERS)
|
||||
check("vælg: næsten tom taber til rigelig", SP.isBetterSource(10000, 12000000, false, 250000), false)
|
||||
check("vælg: blandt rigelige mindst fyldte", SP.isBetterSource(300000, 12000000, false, 250000), true)
|
||||
check("vælg: ingen rigelige -> fyldeste", SP.isBetterSource(9000, 2000, false, 250000), true)
|
||||
check("vælg: løber-fuld-varer uændret (fyldeste)", SP.isBetterSource(12000000, 300000, true, 250000), true)
|
||||
check("vælg: første kandidat", SP.isBetterSource(10, nil, false, 250000), true)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,166 +0,0 @@
|
||||
-- Kør: luajit tests/test_adWaitPool.lua (fra repo-roden)
|
||||
-- Wait-puljen: kandidater pr. mappe, reservationer, nærmeste ledige, rutetjek, redirect.
|
||||
|
||||
dofile("FS25_ADSmartPickup/adSupplyPlanner.lua")
|
||||
dofile("FS25_ADSmartPickup/adWaitPool.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 markers, wayPoints = {}, {}
|
||||
local function addMarker(id, name, group, x, z)
|
||||
wayPoints[id] = {id = id, x = x, z = z, incoming = {}}
|
||||
local marker = {id = id, markerIndex = #markers + 1, name = name, group = group}
|
||||
table.insert(markers, marker)
|
||||
return marker
|
||||
end
|
||||
local unreachable = {}
|
||||
local adEnv = {
|
||||
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,
|
||||
},
|
||||
}
|
||||
getWorldTranslation = function(node) return node.x, 0, node.z end
|
||||
|
||||
addMarker(10, "cow wait 1", "cow wait", 0, 0)
|
||||
addMarker(11, "cow wait 2", "cow wait", 50, 0)
|
||||
addMarker(20, "Wait 3", "General Wait", 10, 0)
|
||||
addMarker(30, "silo 1", "silo", 5, 0)
|
||||
|
||||
-- W1: kandidater
|
||||
check("W1 kun mappen", #ADWaitPool.getCandidates(adEnv, "cow wait"), 2)
|
||||
check("W1 tom pulje = alle Wait-mapper", #ADWaitPool.getCandidates(adEnv, ""), 3)
|
||||
check("W1 nil = alle", #ADWaitPool.getCandidates(adEnv, nil), 3)
|
||||
|
||||
-- W2: nærmeste ledige fra vognens position; reserverede springes over; egen reservation OK
|
||||
local v1, v2 = {name = "v1", rootNode = {x = 45, z = 0}}, {name = "v2", rootNode = {x = 45, z = 0}}
|
||||
check("W2 nærmeste", ADWaitPool.pickFree(adEnv, v1, "cow wait").marker.id, 11)
|
||||
ADWaitPool.claim(11, v1)
|
||||
check("W2 reserveret springes over", ADWaitPool.pickFree(adEnv, v2, "cow wait").marker.id, 10)
|
||||
check("W2 egen reservation genbruges", ADWaitPool.pickFree(adEnv, v1, "cow wait").marker.id, 11)
|
||||
|
||||
-- W3: claim / owner / release
|
||||
check("W3 ejer", ADWaitPool.getOwner(11), v1)
|
||||
ADWaitPool.releaseVehicle(v1)
|
||||
check("W3 frigivet", ADWaitPool.getOwner(11), nil)
|
||||
check("W3 nu ledig for v2", ADWaitPool.pickFree(adEnv, v2, "cow wait").marker.id, 11)
|
||||
|
||||
-- W4: nåelighed
|
||||
unreachable[11] = true
|
||||
check("W4 uopnåelig nærmeste -> næste", ADWaitPool.pickFree(adEnv, v2, "cow wait", function(c) return ADWaitPool.hasRouteTo(adEnv, v2, c.marker.id) end).marker.id, 10)
|
||||
check("W4 hasRouteTo false", ADWaitPool.hasRouteTo(adEnv, v2, 11), false)
|
||||
check("W4 hasRouteBothWays true", ADWaitPool.hasRouteBothWays(adEnv, v2, 10, 30), true)
|
||||
check("W4 hasRouteBothWays hjem uopnåelig", ADWaitPool.hasRouteBothWays(adEnv, v2, 10, 11), false)
|
||||
unreachable[11] = nil
|
||||
check("W4 intet punkt -> nil", ADWaitPool.pickFree(adEnv, v2, "cow wait", function() return false end), nil)
|
||||
|
||||
-- W5: getClaims sorteret på mappe, dernæst id
|
||||
ADWaitPool.claim(20, v2)
|
||||
ADWaitPool.claim(10, v1)
|
||||
local claims = ADWaitPool.getClaims(adEnv)
|
||||
check("W5 to reservationer", #claims, 2)
|
||||
check("W5 første = General Wait", claims[1].marker.group, "General Wait")
|
||||
check("W5 anden = cow wait 1", claims[2].marker.id, 10)
|
||||
ADWaitPool.releaseVehicle(v1)
|
||||
ADWaitPool.releaseVehicle(v2)
|
||||
check("W5 alt frigivet", #ADWaitPool.getClaims(adEnv), 0)
|
||||
|
||||
-- W6: redirect stopper trigger-aktivitet og sætter sti
|
||||
local stopped = {}
|
||||
local task = {
|
||||
destinationID = 99, state = 1,
|
||||
vehicle = {ad = {
|
||||
trailerModule = {isActiveAtTrigger = function() return true end, stopUnloading = function() stopped.unload = true end, stopLoading = function() stopped.load = true end},
|
||||
drivePathModule = {setPathTo = function(_, id) stopped.pathTo = id end},
|
||||
}},
|
||||
}
|
||||
ADWaitPool.STATE_DRIVING = 2
|
||||
ADWaitPool.redirect(task, 10)
|
||||
check("W6 destination", task.destinationID, 10)
|
||||
check("W6 state driving", task.state, 2)
|
||||
check("W6 setPathTo", stopped.pathTo, 10)
|
||||
check("W6 stopUnloading", stopped.unload, true)
|
||||
|
||||
-- WR: AD's rutesøgning siger nej, men vejnettet har en vej fra traktorens nærmeste waypoint -> godtages
|
||||
-- (in-game 24/9: 'Æg storage' ved Chicken Wait 1, "ingen vej til 'Chicken Storage Unload'" trods vej)
|
||||
do
|
||||
wayPoints[901] = {id = 901, x = 0, z = 0, out = {902}}
|
||||
wayPoints[902] = {id = 902, x = 5, z = 0, out = {903}}
|
||||
wayPoints[903] = {id = 903, x = 10, z = 0, out = {}}
|
||||
wayPoints[904] = {id = 904, x = 20, z = 0, out = {}} -- ingen indgang
|
||||
unreachable[903], unreachable[904] = true, true
|
||||
local vehicle = {getClosestWayPoint = function() return 901, 1 end}
|
||||
check("WR AD nej, vejnettet ja -> nåelig", ADWaitPool.hasRouteTo(adEnv, vehicle, 903), true)
|
||||
check("WR hverken AD eller vejnet -> ikke nåelig", ADWaitPool.hasRouteTo(adEnv, vehicle, 904), false)
|
||||
check("WR uden nærmeste waypoint -> AD's svar", ADWaitPool.hasRouteTo(adEnv, {}, 903), false)
|
||||
unreachable[903], unreachable[904] = nil, nil
|
||||
end
|
||||
|
||||
-- RC: rutesvar gemmes 30 s pr. mål og 10 m-område (holdende vogne spurgte hvert 5. s -> hak hvert 5. s)
|
||||
do
|
||||
local pathCalls = 0
|
||||
local realGetPathTo = adEnv.ADGraphManager.getPathTo
|
||||
adEnv.ADGraphManager.getPathTo = function(self, vehicle, id) pathCalls = pathCalls + 1; return realGetPathTo(self, vehicle, id) end
|
||||
local clock = 0
|
||||
netGetTime = function() return clock end
|
||||
local positions = {[500] = {100, 100}}
|
||||
getWorldTranslation = function(node) local p = positions[node]; return p[1], 0, p[2] end
|
||||
local parked = {rootNode = 500, getClosestWayPoint = function() return 901, 1 end}
|
||||
ADWaitPool.hasRouteTo(adEnv, parked, 10)
|
||||
ADWaitPool.hasRouteTo(adEnv, parked, 10)
|
||||
check("RC samme svar genbruges", pathCalls, 1)
|
||||
positions[500] = {104, 103}
|
||||
ADWaitPool.hasRouteTo(adEnv, parked, 10)
|
||||
check("RC samme 10 m-område genbruges", pathCalls, 1)
|
||||
positions[500] = {160, 100}
|
||||
ADWaitPool.hasRouteTo(adEnv, parked, 10)
|
||||
check("RC nyt sted -> nyt svar", pathCalls, 2)
|
||||
clock = 31000
|
||||
ADWaitPool.hasRouteTo(adEnv, parked, 10)
|
||||
check("RC efter 30 s -> nyt svar", pathCalls, 3)
|
||||
local first = ADWaitPool.hasRouteTo(adEnv, parked, 11)
|
||||
check("RC andet mål -> nyt svar", pathCalls, 4)
|
||||
check("RC samme svar igen", ADWaitPool.hasRouteTo(adEnv, parked, 11), first)
|
||||
check("RC uden nyt kald", pathCalls, 4)
|
||||
adEnv.ADGraphManager.getPathTo = realGetPathTo
|
||||
netGetTime = nil
|
||||
end
|
||||
|
||||
-- MR: rute markør->markør gemmes (uafhængig af hvor vognen er) — hak 26/9: 30-39 ms pr. rutetjek
|
||||
do
|
||||
local calls = 0
|
||||
local realFromTo = adEnv.ADGraphManager.pathFromTo
|
||||
adEnv.ADGraphManager.pathFromTo = function(self, from, to) calls = calls + 1; return realFromTo(self, from, to) end
|
||||
local savedClock = netGetTime
|
||||
local t = 1000
|
||||
netGetTime = function() return t end
|
||||
ADWaitPool.resetMarkerRoutes()
|
||||
local far = {name = "far", rootNode = {x = 5000, z = 5000}}
|
||||
check("MR første: rute", ADWaitPool.hasRouteBothWays(adEnv, v2, 10, 30), true)
|
||||
local afterFirst = calls
|
||||
t = t + 60000
|
||||
check("MR anden fra en helt anden position: samme svar", ADWaitPool.hasRouteBothWays(adEnv, far, 10, 30), true)
|
||||
check("MR anden gang ingen ny ruteberegning", calls, afterFirst)
|
||||
adEnv.ADGraphManager.pathFromTo = realFromTo
|
||||
netGetTime = savedClock
|
||||
end
|
||||
|
||||
-- ankerpunkt for "All": midt mellem flowets kilder (in-game 29/9: Milk Store ventede ved sukkerroeknuseren)
|
||||
local ax, az = ADWaitPool.anchorOf({{x = 0, z = 0}, {x = 100, z = 50}})
|
||||
check("anker: midtpunkt x", ax, 50)
|
||||
check("anker: midtpunkt z", az, 25)
|
||||
check("anker: ingen punkter", ADWaitPool.anchorOf({}), nil)
|
||||
check("anker: nil", ADWaitPool.anchorOf(nil), nil)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,33 +0,0 @@
|
||||
-- Kør: luajit tests/test_fieldStatus.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFieldModel.lua")
|
||||
dofile("FS25_ADSmartPickup/adFieldView.lua")
|
||||
local V = ADFieldView
|
||||
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
|
||||
check("mangler markør", V.fieldStatus({hasMarker = false}), "error")
|
||||
check("arbejder", V.fieldStatus({hasMarker = true, working = true}), "running")
|
||||
check("arbejder men venter på maskine", V.fieldStatus({hasMarker = true, working = true, reasonKey = "spu_field_reason_allBusy"}), "waiting")
|
||||
check("for bred", V.fieldStatus({hasMarker = true, working = true, reasonKey = "spu_field_reason_tooWide"}), "error")
|
||||
check("ingen maskine", V.fieldStatus({hasMarker = true, reasonKey = "spu_field_reason_noRig"}), "error")
|
||||
check("regn", V.fieldStatus({hasMarker = true, working = true, reasonKey = "spu_field_reason_rain"}), "waiting")
|
||||
check("venter på spilleren (høster)", V.fieldStatus({hasMarker = true, awaiting = true}), "waiting")
|
||||
check("start bestilt", V.fieldStatus({hasMarker = true, startRequested = true}), "waiting")
|
||||
check("i ro", V.fieldStatus({hasMarker = true}), "stopped")
|
||||
-- Start må ikke bestilles på en mark uden markør (knappen var før deaktiveret; review 29/9)
|
||||
check("kan starte: med markør", V.canStart({hasMarker = true, working = false}), true)
|
||||
check("kan starte: uden markør -> nej", V.canStart({hasMarker = false, working = false}), false)
|
||||
check("kan stoppe: uden markør men i gang", V.canStart({hasMarker = false, working = true}), true)
|
||||
-- global pause (Stop alt): arbejdstrin uden maskiner = stoppet, [Start] genoptager kun den mark (in-game 29/9)
|
||||
check("pause: arbejdstrin -> stoppet", V.fieldStatus({hasMarker = true, working = true, paused = true}), "stopped")
|
||||
check("pause: genoptaget mark -> kører", V.fieldStatus({hasMarker = true, working = true, paused = true, resumed = true}), "running")
|
||||
check("pause: mangler markør stadig rød", V.fieldStatus({hasMarker = false, paused = true}), "error")
|
||||
check("pause: arbejder-tæller falsk", V.isActiveWork({working = true, paused = true}), false)
|
||||
check("pause: genoptaget arbejder-tæller sand", V.isActiveWork({working = true, paused = true, resumed = true}), true)
|
||||
check("ingen pause: arbejder", V.isActiveWork({working = true}), true)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,21 +0,0 @@
|
||||
-- Kør: luajit tests/test_flowStatus.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adFlowView.lua")
|
||||
local V = ADFlowView
|
||||
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
|
||||
check("kører", V.flowStatus({running = 3, total = 4, canStop = true}), "running")
|
||||
check("kører men holder (begrundelse)", V.flowStatus({running = 2, total = 2, canStop = true, reason = "intet at hente"}), "waiting")
|
||||
check("startet, venter på vogn", V.flowStatus({running = 0, total = 2, canStop = true}), "waiting")
|
||||
check("advarsel", V.flowStatus({running = 1, total = 2, canStop = true, warning = "mål mangler"}), "error")
|
||||
check("start afvist (ingen vogn)", V.flowStatus({running = 0, total = 2, lastStartFailed = true}), "error")
|
||||
check("stoppet", V.flowStatus({running = 0, total = 2, stopped = true}), "stopped")
|
||||
check("aldrig startet", V.flowStatus({running = 0, total = 2}), "stopped")
|
||||
check("stoppet vinder over gammel afvisning", V.flowStatus({running = 0, total = 2, stopped = true, lastStartFailed = true}), "stopped")
|
||||
check("farver findes", V.LEVEL_COLORS ~= nil and V.LEVEL_COLORS.waiting ~= nil and V.LEVEL_COLORS.stopped ~= nil, true)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,18 +0,0 @@
|
||||
-- Kør: luajit tests/test_noStrayGlobals.lua (fra repo-roden)
|
||||
-- Fanger lokale navne der ved en fejl bruges som globale (fx 'implement' uden for sin closure — v1.49 gjorde
|
||||
-- egne tømmevogne ubrugelige under høst). Læser luajit-bytecode: GGET/GSET af typiske lokale navne.
|
||||
local SUSPECT = {implement = true, candidate = true, vehicle = true, rig = true, pair = true, assignment = true,
|
||||
tractor = true, loan = true, field = true, info = true, marker = true, job = true, owner = true, order = true}
|
||||
local failures = 0
|
||||
local files = io.popen("ls FS25_ADSmartPickup/*.lua FS25_ADSmartPickup/gui/*.lua"):read("*a")
|
||||
for file in files:gmatch("[^\n]+") do
|
||||
local listing = io.popen("luajit -bl " .. file .. " 2>&1"):read("*a")
|
||||
for op, name in listing:gmatch("(G[GS]ET)%s+%d+%s+%d+%s+; \"([%w_]+)\"") do
|
||||
if SUSPECT[name] then
|
||||
failures = failures + 1
|
||||
print(string.format("FAIL %s: %s af global '%s'", file, op, name))
|
||||
end
|
||||
end
|
||||
end
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
@@ -1,186 +0,0 @@
|
||||
-- Kør: luajit tests/test_storageView.lua (fra repo-roden)
|
||||
-- Røgtest af vareoversigten i fanerne Lagre (v1.52) med falske GUI-elementer: opbygning, fold ud,
|
||||
-- opdatering hvert sekund og skift til bygningsvisning må ikke kaste, og tallene skal lande i cellerne.
|
||||
|
||||
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
|
||||
|
||||
-- ---- falske GUI-elementer ----
|
||||
local Element = {}
|
||||
Element.__index = Element
|
||||
local function newElement(names, size)
|
||||
local element = setmetatable({children = {}, text = nil, visible = true, size = size or {0.5, 0.02}}, Element)
|
||||
for _, name in ipairs(names or {}) do
|
||||
element.children[name] = setmetatable({children = {}, name = name, visible = true, size = {0.2, 0.01}}, Element)
|
||||
end
|
||||
return element
|
||||
end
|
||||
function Element:clone() local copy = newElement({}, {self.size[1], self.size[2]}); for n, c in pairs(self.children) do copy.children[n] = setmetatable({children = {}, name = n, visible = true, size = {c.size[1], c.size[2]}}, Element) end; return copy end
|
||||
function Element:getDescendantByName(name) return self.children[name] end
|
||||
function Element:setText(text) self.text = text end
|
||||
function Element:setTexts(texts) self.texts = texts end
|
||||
function Element:setState(state) self.state = state end
|
||||
function Element:setVisible(visible) self.visible = visible end
|
||||
function Element:setSize(x, y) self.size = {x, y} end
|
||||
function Element:setImageColor(_, r, g, b, a) self.color = {r, g, b, a} end
|
||||
function Element:setTextColor(r, g, b, a) self.textColor = {r, g, b, a} end
|
||||
function Element:delete() end
|
||||
function Element:mouseEvent() return false end
|
||||
|
||||
-- ---- spil-globaler ----
|
||||
Class = function() return {} end
|
||||
TabbedMenuFrameElement = {}
|
||||
InputAction = {}
|
||||
FocusManager = {loadElementFromCustomValues = function() end, removeElement = function() end}
|
||||
Logging = {warning = function(...) print("WARN", string.format(...)) end, info = function() end}
|
||||
local titles = {[31] = "Straw", [5] = "Sugar", [9] = "Milk"}
|
||||
g_fillTypeManager = {getFillTypeTitleByIndex = function(_, i) return titles[i] end, getFillTypeNameByIndex = function(_, i) return titles[i] end}
|
||||
g_currentMission = {getFarmId = function() return 1 end}
|
||||
g_i18n = nil
|
||||
|
||||
dofile("FS25_ADSmartPickup/adStorageStats.lua")
|
||||
ADRunsController = {store = {runs = {}}}
|
||||
ADRuns = {getRun = function() return nil end}
|
||||
ADFlowView = {wrapText = function(text) return {text} end}
|
||||
ADSources = {
|
||||
getFarmInventory = function() return ADSources.inventory end,
|
||||
getFarmRates = function() return {[31] = {consumption = 1000, production = 0}, [5] = {consumption = 0, production = 500}} end,
|
||||
getMarketQuote = function(ft) if ft == 5 then return {price = 1.0, top = 1.05} end return nil end,
|
||||
}
|
||||
ADSources.inventory = {
|
||||
{kind = "silo", name = "Farma #1", placeable = "p1", entries = {{fillType = 31, level = 300, capacity = 1000}, {fillType = 5, level = 20000, capacity = 21000}}},
|
||||
{kind = "silo", name = "Farma #2", placeable = "p2", entries = {{fillType = 31, level = 700, capacity = 1000}}},
|
||||
{kind = "factory", name = "Sugar mill", placeable = "p3", entries = {{fillType = 5, level = 10, capacity = 100, isOutput = true}}},
|
||||
}
|
||||
|
||||
local i18nTexts = {}
|
||||
local i18n = {getText = function(_, key) i18nTexts[key] = true; if key:find("days") then return "%.1f d" end
|
||||
if key:find("share") then return "%d %%" end
|
||||
if key:find("alert_full") then return "%s %d %% %s" end
|
||||
if key:find("alert_empty") then return "%s %s %s %s" end
|
||||
if key:find("alert_sell") then return "%s %d %s" end
|
||||
if key:find("pallets") then return "%d p" end
|
||||
if key:find("perDay") or key:find("fullIn") then return "%s" end
|
||||
return key end}
|
||||
|
||||
SmartPickupFrame = {}
|
||||
local function load(path) local chunk = assert(loadfile(path)); chunk() end
|
||||
SmartPickupFrame.new = nil
|
||||
load("FS25_ADSmartPickup/gui/SmartPickupFrame.lua")
|
||||
load("FS25_ADSmartPickup/gui/SmartPickupStorageTab.lua")
|
||||
load("FS25_ADSmartPickup/gui/SmartPickupStorageView.lua")
|
||||
load("FS25_ADSmartPickup/gui/SmartPickupFlowTab.lua")
|
||||
|
||||
-- en frame uden spillets klasse-maskineri: kun det fanen bruger
|
||||
local frame = setmetatable({}, {__index = SmartPickupFrame})
|
||||
frame.i18n = i18n
|
||||
frame.clonedRows, frame.rowData = {}, {}
|
||||
frame.currentTab = 7 -- Siloer
|
||||
local rows = {}
|
||||
frame.boxLayout = {elements = rows, invalidateLayout = function() end}
|
||||
frame.productPrefab = newElement({"option", "title"})
|
||||
frame.productPrefab.children.option.children.ignore = newElement()
|
||||
frame.textPrefab = newElement({"title"})
|
||||
frame.kpiPrefab = newElement({"v1", "l1", "v2", "l2", "v3", "l3", "v4", "l4"})
|
||||
frame.alertPrefab = newElement({"dot", "c1"})
|
||||
frame.itemPrefab = newElement({"c1", "c2", "c3", "c4", "bar", "barBg"})
|
||||
frame.subPrefab = newElement({"c1", "c2", "c3", "c4", "bar", "barBg"})
|
||||
frame.statPrefab = newElement({"c1", "c2", "c3", "c4", "c5", "c6", "c7"})
|
||||
frame.gridPrefab = newElement({"c1", "c2", "c3"})
|
||||
for _, prefab in ipairs({frame.productPrefab, frame.textPrefab, frame.kpiPrefab, frame.alertPrefab, frame.itemPrefab, frame.subPrefab, frame.statPrefab, frame.gridPrefab}) do
|
||||
local base = prefab.clone
|
||||
prefab.clone = function(self) local copy = base(self); if self.children.option then copy.children.option.children.ignore = newElement() end; table.insert(rows, copy); return copy end
|
||||
end
|
||||
frame.getAdEnv = function() return {} end
|
||||
frame.setStatusText = function() end
|
||||
frame.refreshButtons = function() end
|
||||
frame.restoreFocusIfLost = function() end
|
||||
frame.colorRows = function() end
|
||||
for _, name in ipairs({"startButtonInfo", "renameButtonInfo", "deleteButtonInfo", "stopAllButtonInfo", "renameBuildingButtonInfo"}) do frame[name] = {} end
|
||||
|
||||
local function texts()
|
||||
local list = {}
|
||||
for _, row in ipairs(rows) do
|
||||
for _, name in ipairs({"title", "c1", "c2", "c3", "c4", "v1", "v3"}) do
|
||||
local child = row.children[name]
|
||||
if child ~= nil and child.text ~= nil then table.insert(list, child.text) end
|
||||
end
|
||||
end
|
||||
return table.concat(list, "|")
|
||||
end
|
||||
|
||||
-- SVW1: opbygning af Siloer i varevisning
|
||||
local ok, err = pcall(frame.rebuild, frame)
|
||||
check("SVW1 opbygning kaster ikke", ok, true)
|
||||
if not ok then print(err) end
|
||||
local all = texts()
|
||||
check("SVW1 straw-række", all:find("+ Straw", 1, true) ~= nil, true)
|
||||
check("SVW1 sugar-række", all:find("+ Sugar", 1, true) ~= nil, true)
|
||||
check("SVW1 straw 1.0k/2.0k 50 %", all:find("1.0k/2.0k · 50 %", 1, true) ~= nil, true)
|
||||
check("SVW1 ingen bygninger før klik", all:find("Farma #2", 1, true), nil)
|
||||
check("SVW1 fabrikkens sukker ikke i siloer", all:find("Sugar mill", 1, true), nil)
|
||||
check("SVW1 kræver handling: straw løber tør", all:find("spu_alert", 1, true) == nil and all:find("Straw 1.0 d", 1, true) ~= nil, true)
|
||||
|
||||
-- SVW2: klik på straw folder ud; bygninger med mest først
|
||||
local strawRow = frame.itemView.itemRows[1]
|
||||
ok, err = pcall(frame.toggleItem, frame, 31)
|
||||
check("SVW2 fold ud kaster ikke", ok, true)
|
||||
if not ok then print(err) end
|
||||
all = texts()
|
||||
local first, second = all:find("Farma #2", 1, true), all:find("Farma #1", 1, true)
|
||||
check("SVW2 Farma #2 vist", first ~= nil, true)
|
||||
check("SVW2 flest først", first ~= nil and second ~= nil and first < second, true)
|
||||
check("SVW2 markør skiftet", all:find("– Straw", 1, true) ~= nil, true)
|
||||
check("SVW2 andel", all:find("70 %", 1, true) ~= nil, true)
|
||||
|
||||
-- SVW3: opdatering hvert sekund med nye tal, samme struktur
|
||||
ADSources.inventory[2].entries[1].level = 900
|
||||
ok, err = pcall(frame.refreshLive, frame)
|
||||
check("SVW3 opdatering kaster ikke", ok, true)
|
||||
if not ok then print(err) end
|
||||
check("SVW3 ny total i cellen", texts():find("1.2k/2.0k · 60 %", 1, true) ~= nil, true)
|
||||
local bar = frame.itemView.itemRows[1].bar
|
||||
check("SVW3 bjælke 60 %", math.floor(bar.size[1] / 0.2 * 100 + 0.5), 60)
|
||||
check("SVW3 bjælke gul (løber tør < 3 d)", bar.color ~= nil and bar.color[1] > 0.9 and bar.color[2] > 0.7, true)
|
||||
|
||||
-- SVW4: skift til bygningsvisning og tilbage
|
||||
ok, err = pcall(frame.onClickStorageView, frame, SmartPickupFrame.STORAGE_VIEW_BUILDINGS)
|
||||
check("SVW4 bygningsvisning kaster ikke", ok, true)
|
||||
if not ok then print(err) end
|
||||
check("SVW4 ingen varevisning", frame.itemView, nil)
|
||||
ok = pcall(frame.onClickStorageView, frame, SmartPickupFrame.STORAGE_VIEW_ITEMS)
|
||||
check("SVW4 tilbage til varer", ok and frame.itemView ~= nil, true)
|
||||
|
||||
-- SVW5: fabrikker-fanen: output næsten fuldt -> rød
|
||||
frame.currentTab = 6
|
||||
ok, err = pcall(frame.rebuild, frame)
|
||||
check("SVW5 fabrikker kaster ikke", ok, true)
|
||||
if not ok then print(err) end
|
||||
|
||||
-- SVW7: udfoldet vare hvor to siloer bytter plads i mængde -> ingen genopbygning, rækkerne følger bygningen
|
||||
frame.currentTab = 7
|
||||
frame.expandedItems = nil
|
||||
frame:rebuild()
|
||||
frame:toggleItem(31)
|
||||
local subsBefore = frame.itemView.itemRows[1].subs
|
||||
ADSources.inventory[1].entries[1].level = 950 -- Farma #1 (300 -> 950) forbi Farma #2 (900)
|
||||
for _ = 1, 12 do frame:refreshLive() end
|
||||
check("SVW7 samme rækker (ingen genopbygning)", frame.itemView.itemRows[1].subs, subsBefore)
|
||||
check("SVW7 første underrække er stadig Farma #2", subsBefore[1].cells[1].text, "Farma #2")
|
||||
check("SVW7 anden underrække viser Farma #1's nye tal", subsBefore[2].cells[2].text:find("950", 1, true) ~= nil, true)
|
||||
|
||||
-- SVW6: alle brugte l10n-nøgler findes i begge sprogfiler
|
||||
for _, file in ipairs({"FS25_ADSmartPickup/l10n/l10n_da.xml", "FS25_ADSmartPickup/l10n/l10n_en.xml"}) do
|
||||
local content = io.open(file):read("*a")
|
||||
for key in pairs(i18nTexts) do
|
||||
if key:find("^spu_") and not content:find('name="' .. key .. '"', 1, true) then
|
||||
failures = failures + 1
|
||||
print("FAIL SVW6 mangler " .. key .. " i " .. file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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