feat: adBuildings — gårdens bygninger med automatisk markør ved læsse-/aflæsningstrigger
This commit is contained in:
@@ -0,0 +1,229 @@
|
|||||||
|
-- AD Buildings
|
||||||
|
-- Gårdens bygninger som brugeren vælger imellem (i stedet for markører): fabrik, silo, stald,
|
||||||
|
-- objektlager og salgssted, hver med den AD-markør der står nærmest bygningens læsse- og
|
||||||
|
-- aflæsningstrigger (≤ MAX_MARKER_DISTANCE; Wait-markører aldrig; siloers læssemarkør via det
|
||||||
|
-- strenge opslag, så tætte rækker ikke blandes). Spil-adapter med 1 s cache.
|
||||||
|
|
||||||
|
ADBuildings = {}
|
||||||
|
|
||||||
|
ADBuildings.LOG_PREFIX = "ADSmartPickup: "
|
||||||
|
ADBuildings.MAX_MARKER_DISTANCE = 40
|
||||||
|
ADBuildings.CACHE_MS = 1000
|
||||||
|
|
||||||
|
local cache = {list = nil, farmId = nil, time = -math.huge}
|
||||||
|
|
||||||
|
local function now()
|
||||||
|
if g_currentMission ~= nil and type(g_currentMission.time) == "number" then
|
||||||
|
return g_currentMission.time
|
||||||
|
end
|
||||||
|
return os.clock() * 1000
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getNodePosition(node)
|
||||||
|
if node == nil or (entityExists ~= nil and not entityExists(node)) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local x, _, z = getWorldTranslation(node)
|
||||||
|
return x, z
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getName(placeable, fallback)
|
||||||
|
if placeable ~= nil and placeable.getName ~= nil then
|
||||||
|
local ok, name = pcall(placeable.getName, placeable)
|
||||||
|
if ok and name ~= nil then return tostring(name) end
|
||||||
|
end
|
||||||
|
return fallback
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADBuildings.getId(placeable)
|
||||||
|
if placeable.uniqueId ~= nil then
|
||||||
|
return tostring(placeable.uniqueId)
|
||||||
|
end
|
||||||
|
local x, z = getNodePosition(placeable.rootNode)
|
||||||
|
if x == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return string.format("pos:%d:%d", math.floor(x + 0.5), math.floor(z + 0.5))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getProductionPoint(placeable)
|
||||||
|
return placeable.spec_productionPoint ~= nil and placeable.spec_productionPoint.productionPoint or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isOwnedBy(placeable, farmId)
|
||||||
|
if placeable.getOwnerFarmId ~= nil then
|
||||||
|
local ok, owner = pcall(placeable.getOwnerFarmId, placeable)
|
||||||
|
return ok and owner == farmId
|
||||||
|
end
|
||||||
|
return placeable.ownerFarmId == farmId
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getKind(placeable)
|
||||||
|
if getProductionPoint(placeable) ~= nil then return "factory" end
|
||||||
|
if placeable.spec_silo ~= nil then return "silo" end
|
||||||
|
if placeable.getHusbandryFillLevel ~= nil then return "husbandry" end
|
||||||
|
if placeable.spec_objectStorage ~= nil then return "objectStorage" end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Trigger-noder pr. bygning: læsse (loadTriggers.triggerNode, pallespawner, objektlager) og
|
||||||
|
-- aflæsning (unloadTriggers exactFillRootNode/triggerNode/aiNode).
|
||||||
|
local function collectNodes(placeable, loadStations, unloadStations)
|
||||||
|
local loadNodes, unloadNodes = {}, {}
|
||||||
|
for _, station in pairs(loadStations) do
|
||||||
|
if station.owningPlaceable == placeable then
|
||||||
|
for _, trigger in pairs(station.loadTriggers or {}) do
|
||||||
|
if trigger.triggerNode ~= nil then table.insert(loadNodes, trigger.triggerNode) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, station in pairs(unloadStations) do
|
||||||
|
if station.owningPlaceable == placeable then
|
||||||
|
for _, trigger in pairs(station.unloadTriggers or {}) do
|
||||||
|
for _, field in ipairs({"exactFillRootNode", "triggerNode", "aiNode"}) do
|
||||||
|
if trigger[field] ~= nil then table.insert(unloadNodes, trigger[field]) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local productionPoint = getProductionPoint(placeable)
|
||||||
|
local spawner = productionPoint ~= nil and productionPoint.palletSpawner or nil
|
||||||
|
if spawner ~= nil then
|
||||||
|
for _, place in ipairs(spawner.spawnPlaces or {}) do
|
||||||
|
if place.startNode ~= nil then table.insert(loadNodes, place.startNode) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if placeable.spec_objectStorage ~= nil and placeable.rootNode ~= nil then
|
||||||
|
table.insert(loadNodes, placeable.rootNode)
|
||||||
|
table.insert(unloadNodes, placeable.rootNode)
|
||||||
|
end
|
||||||
|
return loadNodes, unloadNodes
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Nærmeste ikke-Wait-markør til en af noderne, ≤ MAX_MARKER_DISTANCE.
|
||||||
|
local function nearestMarker(adEnv, nodes)
|
||||||
|
local best, bestDistance = nil, ADBuildings.MAX_MARKER_DISTANCE
|
||||||
|
if #nodes == 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local positions = {}
|
||||||
|
for _, node in ipairs(nodes) do
|
||||||
|
local x, z = getNodePosition(node)
|
||||||
|
if x ~= nil then table.insert(positions, {x = x, z = z}) end
|
||||||
|
end
|
||||||
|
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
||||||
|
if not ADSupplyPlanner.isWaitMarker(marker) then
|
||||||
|
local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id)
|
||||||
|
if wayPoint ~= nil then
|
||||||
|
for _, position in ipairs(positions) do
|
||||||
|
local dx, dz = wayPoint.x - position.x, wayPoint.z - position.z
|
||||||
|
local distance = math.sqrt(dx * dx + dz * dz)
|
||||||
|
if distance < bestDistance then
|
||||||
|
best, bestDistance = marker, distance
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if best == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return {id = best.id, name = tostring(best.name), distance = math.floor(bestDistance + 0.5)}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Silo: den markør hvis STRENGE opslag (trigger bag næsen) rammer siloens læssestation.
|
||||||
|
local function siloLoadMarker(adEnv, farmId, placeable, loadStations)
|
||||||
|
local own = {}
|
||||||
|
for _, station in pairs(loadStations) do
|
||||||
|
if station.owningPlaceable == placeable then own[station] = true end
|
||||||
|
end
|
||||||
|
local best = nil
|
||||||
|
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
||||||
|
if not ADSupplyPlanner.isWaitMarker(marker) then
|
||||||
|
local station = ADSmartPickup.getLoadStationAtWayPointId(adEnv, marker.id, farmId)
|
||||||
|
if station ~= nil and own[station] then
|
||||||
|
best = {id = marker.id, name = tostring(marker.name), distance = 0}
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return best
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isSellPlaceable(placeable, unloadStations)
|
||||||
|
for _, station in pairs(unloadStations) do
|
||||||
|
if station.owningPlaceable == placeable and ADPrices.isSellingStation(station) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local ORDER = {factory = 1, husbandry = 2, objectStorage = 3, silo = 4, sell = 5}
|
||||||
|
|
||||||
|
local function build(adEnv, farmId)
|
||||||
|
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
|
||||||
|
local storage = g_currentMission ~= nil and g_currentMission.storageSystem or nil
|
||||||
|
local loadStations = storage ~= nil and storage:getLoadingStations() or {}
|
||||||
|
local unloadStations = storage ~= nil and storage:getUnloadingStations() or {}
|
||||||
|
local result = {}
|
||||||
|
for _, placeable in pairs(placeables) do
|
||||||
|
local kind = getKind(placeable)
|
||||||
|
local isSell = kind == nil and isSellPlaceable(placeable, unloadStations)
|
||||||
|
if isSell then kind = "sell" end
|
||||||
|
if kind ~= nil and (kind == "sell" or isOwnedBy(placeable, farmId)) then
|
||||||
|
local id = ADBuildings.getId(placeable)
|
||||||
|
if id ~= nil then
|
||||||
|
local loadNodes, unloadNodes = collectNodes(placeable, loadStations, unloadStations)
|
||||||
|
local loadMarker
|
||||||
|
if kind == "silo" then
|
||||||
|
loadMarker = siloLoadMarker(adEnv, farmId, placeable, loadStations)
|
||||||
|
else
|
||||||
|
loadMarker = nearestMarker(adEnv, loadNodes)
|
||||||
|
end
|
||||||
|
table.insert(result, {
|
||||||
|
id = id, name = getName(placeable, id), kind = kind, placeable = placeable,
|
||||||
|
loadMarker = loadMarker,
|
||||||
|
unloadMarker = nearestMarker(adEnv, unloadNodes),
|
||||||
|
canBeSource = kind ~= "sell",
|
||||||
|
canBeTarget = kind == "husbandry" or kind == "factory",
|
||||||
|
canSell = kind == "sell",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(result, function(a, b)
|
||||||
|
if ORDER[a.kind] ~= ORDER[b.kind] then return ORDER[a.kind] < ORDER[b.kind] end
|
||||||
|
return a.name < b.name
|
||||||
|
end)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADBuildings.invalidate()
|
||||||
|
cache.list, cache.time = nil, -math.huge
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADBuildings.list(adEnv, farmId)
|
||||||
|
local time = now()
|
||||||
|
if cache.list ~= nil and cache.farmId == farmId and (time - cache.time) < ADBuildings.CACHE_MS then
|
||||||
|
return cache.list
|
||||||
|
end
|
||||||
|
cache.list, cache.farmId, cache.time = build(adEnv, farmId), farmId, time
|
||||||
|
return cache.list
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADBuildings.find(adEnv, farmId, id)
|
||||||
|
for _, building in ipairs(ADBuildings.list(adEnv, farmId)) do
|
||||||
|
if building.id == id then return building end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- role "load": bygningen hvis læssemarkør er wayPointId; "unload": aflæsningsmarkør.
|
||||||
|
function ADBuildings.byMarker(adEnv, farmId, wayPointId, role)
|
||||||
|
for _, building in ipairs(ADBuildings.list(adEnv, farmId)) do
|
||||||
|
local marker = role == "unload" and building.unloadMarker or building.loadMarker
|
||||||
|
if marker ~= nil and marker.id == wayPointId then return building end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
-- 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})
|
||||||
|
local milkStation = {owningPlaceable = barn, loadTriggers = {{triggerNode = {x = 20, z = 0}}}}
|
||||||
|
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(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)
|
||||||
|
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: fabrik, stald, objektlager, silo, salg; navn inden for slags
|
||||||
|
local order = {}
|
||||||
|
for _, b in ipairs(ADBuildings.list(adEnv, 1)) do table.insert(order, b.kind) end
|
||||||
|
check("B6 rækkefølge", table.concat(order, ","), "factory,husbandry,objectStorage,silo,silo,sell")
|
||||||
|
|
||||||
|
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