Files
fs25-adsmartpickup/tests/test_adSources.lua
T

364 lines
22 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- 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/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)
-- 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)
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)
-- 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)
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)