727 lines
34 KiB
Lua
727 lines
34 KiB
Lua
-- AD Sources
|
||
-- Kilder til udkørsel: hvad står der ved en AD-markør (fabrik, silo eller objektlager),
|
||
-- hvilke varer rummer/producerer den, hvor meget, og hvordan tages paller ud af et
|
||
-- objektlager. Spil-adapter — de rene beslutninger ligger i adOutboundPlanner.
|
||
|
||
ADSources = {}
|
||
|
||
ADSources.LOG_PREFIX = "ADSmartPickup: "
|
||
-- Objektlager tættere end dette på markøren regnes som markørens (samme som triggere).
|
||
ADSources.MAX_DISTANCE = 40
|
||
-- Paller der står inden for denne afstand af fabrikkens pallespawner tæller som fabrikkens output.
|
||
ADSources.PALLET_RADIUS = 25
|
||
|
||
local function distance2(ax, az, bx, bz)
|
||
local dx, dz = ax - bx, az - bz
|
||
return dx * dx + dz * dz
|
||
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 getPlaceableName(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
|
||
|
||
local function getProductionPoint(placeable)
|
||
return placeable ~= nil and placeable.spec_productionPoint ~= nil and placeable.spec_productionPoint.productionPoint or nil
|
||
end
|
||
|
||
-- Varer der KAN stå på paller (spillets fillType-definition: isPalletType eller pallefil).
|
||
-- Sukker og mel er palletyper, men læsses som bulk ved en fabrik med læssestation — så om
|
||
-- varen er palle eller bulk afgøres pr. kilde i isPalletAt, ikke her.
|
||
function ADSources.isPalletFillType(fillType)
|
||
local desc = g_fillTypeManager ~= nil and g_fillTypeManager.getFillTypeByIndex ~= nil and g_fillTypeManager:getFillTypeByIndex(fillType) or nil
|
||
return desc ~= nil and (desc.isPalletType == true or desc.palletFilename ~= nil)
|
||
end
|
||
|
||
local function stationSupports(station, fillType)
|
||
if station == nil then
|
||
return false
|
||
end
|
||
if station.supportedFillTypes ~= nil then
|
||
return station.supportedFillTypes[fillType] == true
|
||
end
|
||
if station.getIsFillTypeSupported ~= nil then
|
||
local ok, value = pcall(station.getIsFillTypeSupported, station, fillType)
|
||
return ok and value == true
|
||
end
|
||
return false
|
||
end
|
||
|
||
-- Palle eller bulk ved DENNE kilde: silo = bulk; objektlager = palle; fabrik = bulk hvis
|
||
-- markørens læssestation udleverer varen, ellers palle hvis varen er en palletype.
|
||
function ADSources.isPalletAt(source, fillType)
|
||
if source.kind == "silo" or source.kind == "husbandry" then
|
||
return false
|
||
elseif source.kind == "objectStorage" then
|
||
return true
|
||
end
|
||
local productionPoint = getProductionPoint(source.placeable)
|
||
local loadingStation = productionPoint ~= nil and productionPoint.loadingStation or nil
|
||
if stationSupports(source.station, fillType) or stationSupports(loadingStation, fillType) then
|
||
return false
|
||
end
|
||
return ADSources.isPalletFillType(fillType)
|
||
end
|
||
|
||
-- Placeable'ns "nærmeste punkt" for kildegenkendelse: pallespawnerens første plads (fabrik)
|
||
-- eller rodnoden.
|
||
local function getPlaceableAnchor(placeable)
|
||
local productionPoint = getProductionPoint(placeable)
|
||
local spawner = productionPoint ~= nil and productionPoint.palletSpawner or nil
|
||
local place = spawner ~= nil and spawner.spawnPlaces ~= nil and spawner.spawnPlaces[1] or nil
|
||
local node = place ~= nil and place.startNode or placeable.rootNode
|
||
local x, z = getNodePosition(node)
|
||
if x == nil and node ~= placeable.rootNode then
|
||
x, z = getNodePosition(placeable.rootNode)
|
||
end
|
||
return x, z
|
||
end
|
||
|
||
-- Nærmeste placeable inden for MAX_DISTANCE af waypointet som farmen må bruge og predicate godkender.
|
||
local function findPlaceableNear(wayPoint, farmId, predicate)
|
||
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
|
||
local access = g_currentMission ~= nil and g_currentMission.accessHandler or nil
|
||
local best, bestDistance = nil, ADSources.MAX_DISTANCE * ADSources.MAX_DISTANCE
|
||
for _, placeable in pairs(placeables) do
|
||
if predicate(placeable) and (access == nil or access:canFarmAccess(farmId, placeable)) then
|
||
local x, z = getPlaceableAnchor(placeable)
|
||
if x ~= nil then
|
||
local d = distance2(wayPoint.x, wayPoint.z, x, z)
|
||
if d < bestDistance then
|
||
best, bestDistance = placeable, d
|
||
end
|
||
end
|
||
end
|
||
end
|
||
return best
|
||
end
|
||
|
||
local function isObjectStorage(placeable)
|
||
return placeable.spec_objectStorage ~= nil
|
||
end
|
||
|
||
local function isProductionPoint(placeable)
|
||
return getProductionPoint(placeable) ~= nil
|
||
end
|
||
|
||
-- {kind = "factory"|"silo"|"objectStorage", placeable, station|nil, wayPointId, name} eller nil.
|
||
function ADSources.identify(adEnv, wayPointId, farmId)
|
||
local wayPoint = adEnv.ADGraphManager:getWayPointById(wayPointId)
|
||
if wayPoint == nil then
|
||
return nil
|
||
end
|
||
local marker = adEnv.ADGraphManager.getMapMarkerByWayPointId ~= nil and adEnv.ADGraphManager:getMapMarkerByWayPointId(wayPointId) or nil
|
||
-- Wait-punkter er ventepladser, aldrig kilder eller lagermål — også når de står tæt på et lager
|
||
if marker ~= nil and ADSupplyPlanner ~= nil and ADSupplyPlanner.isWaitMarker ~= nil and ADSupplyPlanner.isWaitMarker(marker) then
|
||
return nil
|
||
end
|
||
local markerName = marker ~= nil and tostring(marker.name) or tostring(wayPointId)
|
||
local station = ADSmartPickup.getLoadStationAtWayPointId(adEnv, wayPointId, farmId)
|
||
local placeable = station ~= nil and station.owningPlaceable or nil
|
||
if placeable ~= nil and getProductionPoint(placeable) ~= nil then
|
||
return {kind = "factory", placeable = placeable, station = station, wayPointId = wayPointId, name = getPlaceableName(placeable, markerName)}
|
||
end
|
||
if placeable ~= nil and placeable.spec_silo ~= nil then
|
||
return {kind = "silo", placeable = placeable, station = station, wayPointId = wayPointId, name = getPlaceableName(placeable, markerName)}
|
||
end
|
||
-- stald: mælketank, gyllebeholder m.m. (læssestation på en husbandry-placeable)
|
||
if placeable ~= nil and placeable.getHusbandryFillLevel ~= nil then
|
||
return {kind = "husbandry", placeable = placeable, station = station, wayPointId = wayPointId, name = getPlaceableName(placeable, markerName)}
|
||
end
|
||
-- Det strenge opslag (trigger bag næsen) passer til silorækker; en mælketank eller fabrik ligger
|
||
-- ofte ved siden af markøren. Løst opslag inden for 40 m — men aldrig for siloer.
|
||
if placeable == nil and ADSmartPickup.getLoadStationNearWayPointId ~= nil then
|
||
local loose = ADSmartPickup.getLoadStationNearWayPointId(adEnv, wayPointId, farmId, ADSources.MAX_DISTANCE)
|
||
local loosePlaceable = loose ~= nil and loose.owningPlaceable or nil
|
||
if loosePlaceable ~= nil and loosePlaceable.spec_silo == nil then
|
||
if getProductionPoint(loosePlaceable) ~= nil then
|
||
return {kind = "factory", placeable = loosePlaceable, station = loose, wayPointId = wayPointId, name = getPlaceableName(loosePlaceable, markerName)}
|
||
elseif loosePlaceable.getHusbandryFillLevel ~= nil then
|
||
return {kind = "husbandry", placeable = loosePlaceable, station = loose, wayPointId = wayPointId, name = getPlaceableName(loosePlaceable, markerName)}
|
||
end
|
||
end
|
||
end
|
||
-- Rene pallefabrikker (halmvarer, bageri ...) har ingen læssestation: find dem på afstand
|
||
-- til pallespawneren; dernæst objektlagre.
|
||
local factory = findPlaceableNear(wayPoint, farmId, isProductionPoint)
|
||
if factory ~= nil then
|
||
-- produktionspunktets egen læssestation (bulk-outputs), også når markøren ikke rammer triggeren
|
||
local loadingStation = getProductionPoint(factory).loadingStation
|
||
return {kind = "factory", placeable = factory, station = loadingStation, wayPointId = wayPointId, name = getPlaceableName(factory, markerName)}
|
||
end
|
||
local storage = findPlaceableNear(wayPoint, farmId, isObjectStorage)
|
||
if storage ~= nil then
|
||
return {kind = "objectStorage", placeable = storage, station = nil, wayPointId = wayPointId, name = getPlaceableName(storage, markerName)}
|
||
end
|
||
return nil
|
||
end
|
||
|
||
-- Til advarslen "kilde-markør X er ikke …": hvad opslaget faktisk ser ved markøren.
|
||
function ADSources.explainMarker(adEnv, wayPointId, farmId)
|
||
local wayPoint = adEnv.ADGraphManager:getWayPointById(wayPointId)
|
||
if wayPoint == nil then
|
||
return "markøren har intet waypoint (slettet?)"
|
||
end
|
||
local station = ADSmartPickup.getLoadStationAtWayPointId(adEnv, wayPointId, farmId)
|
||
if station == nil then
|
||
local loose = ADSmartPickup.getLoadStationNearWayPointId ~= nil and ADSmartPickup.getLoadStationNearWayPointId(adEnv, wayPointId, farmId, ADSources.MAX_DISTANCE) or nil
|
||
if loose ~= nil and loose.owningPlaceable ~= nil and loose.owningPlaceable.spec_silo ~= nil then
|
||
return string.format("nærmeste læssestation inden for 40 m er en silo ('%s'), som kræver at markøren står i køresporet med triggeren bag næsen", getPlaceableName(loose.owningPlaceable, "?"))
|
||
end
|
||
local factory = findPlaceableNear(wayPoint, farmId, isProductionPoint)
|
||
local storage = findPlaceableNear(wayPoint, farmId, isObjectStorage)
|
||
if factory == nil and storage == nil then
|
||
return "ingen læssestation ved markøren, og ingen fabrik/objektlager inden for 40 m"
|
||
end
|
||
return string.format("ingen læssestation ved markøren; nærmeste bygning: %s", getPlaceableName(factory or storage, "?"))
|
||
end
|
||
local placeable = station.owningPlaceable
|
||
if placeable == nil then
|
||
return "læssestation ved markøren uden bygning (owningPlaceable = nil)"
|
||
end
|
||
local kind = "ukendt bygningstype"
|
||
if getProductionPoint(placeable) ~= nil then kind = "fabrik"
|
||
elseif placeable.spec_silo ~= nil then kind = "silo"
|
||
elseif placeable.getHusbandryFillLevel ~= nil then kind = "stald"
|
||
elseif placeable.spec_objectStorage ~= nil then kind = "objektlager" end
|
||
return string.format("læssestation ved markøren hører til '%s' (%s)", getPlaceableName(placeable, "?"), kind)
|
||
end
|
||
|
||
local function isPalletVehicle(vehicle)
|
||
return vehicle ~= nil and (vehicle.spec_pallet ~= nil or vehicle.isPallet == true)
|
||
end
|
||
|
||
local function getPalletContent(vehicle)
|
||
if vehicle.getFillUnitFillType ~= nil and vehicle.getFillUnitFillLevel ~= nil then
|
||
return vehicle:getFillUnitFillType(1), vehicle:getFillUnitFillLevel(1) or 0
|
||
end
|
||
local units = vehicle.spec_fillUnit ~= nil and vehicle.spec_fillUnit.fillUnits or nil
|
||
if units ~= nil and units[1] ~= nil then
|
||
return units[1].fillType, units[1].fillLevel or 0
|
||
end
|
||
return nil, 0
|
||
end
|
||
|
||
-- Liter af varen på paller der står ved fabrikkens spawner (pallespawneren tømmer lageret
|
||
-- ud på jorden løbende, så det er dér varen fysisk er).
|
||
local function getPalletsNearSpawner(placeable, fillType, farmId)
|
||
local productionPoint = getProductionPoint(placeable)
|
||
local spawner = productionPoint ~= nil and productionPoint.palletSpawner or nil
|
||
local node = spawner ~= nil and spawner.spawnPlaces ~= nil and spawner.spawnPlaces[1] ~= nil and spawner.spawnPlaces[1].startNode or placeable.rootNode
|
||
local sx, sz = getNodePosition(node)
|
||
if sx == nil then
|
||
return 0
|
||
end
|
||
local vehicles = g_currentMission ~= nil and g_currentMission.vehicleSystem ~= nil and g_currentMission.vehicleSystem.vehicles or {}
|
||
local total, count = 0, 0
|
||
local radius2 = ADSources.PALLET_RADIUS * ADSources.PALLET_RADIUS
|
||
for _, vehicle in pairs(vehicles) do
|
||
if isPalletVehicle(vehicle) and (vehicle.getOwnerFarmId == nil or vehicle:getOwnerFarmId() == farmId) then
|
||
local palletFillType, level = getPalletContent(vehicle)
|
||
local x, z = getNodePosition(vehicle.rootNode)
|
||
if palletFillType == fillType and x ~= nil and distance2(sx, sz, x, z) <= radius2 then
|
||
total = total + level
|
||
count = count + 1
|
||
end
|
||
end
|
||
end
|
||
return total, count
|
||
end
|
||
|
||
-- canPallet: varen er en palletype (kan stå på paller); palletLiters: liter på paller ved
|
||
-- kilden når varen ELLERS læsses som bulk (fabrik med både læssestation og pallespawner).
|
||
local function newProduct(fillType, available, level, capacity, isPallet)
|
||
local name = g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeNameByIndex(fillType) or tostring(fillType)
|
||
local title = g_fillTypeManager ~= nil and g_fillTypeManager.getFillTypeTitleByIndex ~= nil and g_fillTypeManager:getFillTypeTitleByIndex(fillType) or nil
|
||
return {fillType = fillType, name = name, title = title or name, available = available, level = level, capacity = capacity, isPallet = isPallet, canPallet = isPallet or ADSources.isPalletFillType(fillType), palletLiters = 0, palletCount = 0}
|
||
end
|
||
|
||
local function getFactoryProducts(source, farmId)
|
||
local productionPoint = getProductionPoint(source.placeable)
|
||
local storage = productionPoint ~= nil and productionPoint.storage or nil
|
||
local seen, products = {}, {}
|
||
for _, production in ipairs(productionPoint ~= nil and productionPoint.productions or {}) do
|
||
for _, output in ipairs(production.outputs or {}) do
|
||
local fillType = output.type
|
||
if fillType ~= nil and not seen[fillType] then
|
||
seen[fillType] = true
|
||
local level = storage ~= nil and storage.getFillLevel ~= nil and (storage:getFillLevel(fillType) or 0) or 0
|
||
local capacity = storage ~= nil and storage.getCapacity ~= nil and (storage:getCapacity(fillType) or 0) or 0
|
||
local isPallet = ADSources.isPalletAt(source, fillType)
|
||
local palletLiters, palletCount = 0, 0
|
||
if ADSources.isPalletFillType(fillType) then
|
||
palletLiters, palletCount = getPalletsNearSpawner(source.placeable, fillType, farmId)
|
||
end
|
||
local available = level + (isPallet and palletLiters or 0)
|
||
local product = newProduct(fillType, available, level, capacity, isPallet)
|
||
product.palletLiters, product.palletCount = palletLiters, palletCount
|
||
table.insert(products, product)
|
||
end
|
||
end
|
||
end
|
||
return products
|
||
end
|
||
|
||
local function getSiloProducts(source)
|
||
local storages = source.placeable.spec_silo ~= nil and source.placeable.spec_silo.storages or {}
|
||
local levels, capacities = {}, {}
|
||
for _, storage in pairs(storages) do
|
||
for fillType, level in pairs(storage.fillLevels or {}) do
|
||
if (level or 0) > 0 then
|
||
levels[fillType] = (levels[fillType] or 0) + level
|
||
capacities[fillType] = (capacities[fillType] or 0) + (storage.getCapacity ~= nil and (storage:getCapacity(fillType) or 0) or 0)
|
||
end
|
||
end
|
||
end
|
||
local products = {}
|
||
for fillType, level in pairs(levels) do
|
||
table.insert(products, newProduct(fillType, level, level, capacities[fillType] or 0, false))
|
||
end
|
||
table.sort(products, function(a, b) return a.fillType < b.fillType end)
|
||
return products
|
||
end
|
||
|
||
-- Et objektlagers post: {fillType, fillLevel pr. objekt} for paller og baller.
|
||
local function getObjectInfoContent(objectInfo)
|
||
local object = objectInfo.objects ~= nil and objectInfo.objects[1] or nil
|
||
if object == nil then
|
||
return nil, 0
|
||
end
|
||
local attributes = object.palletAttributes or object.baleAttributes
|
||
if attributes ~= nil then
|
||
return attributes.fillType, attributes.fillLevel or 0
|
||
end
|
||
return object.fillType, object.fillLevel or 0
|
||
end
|
||
|
||
local function getObjectStorageProducts(source)
|
||
local spec = source.placeable.spec_objectStorage
|
||
local products = {}
|
||
for _, objectInfo in ipairs(spec ~= nil and spec.objectInfos or {}) do
|
||
local fillType, perObject = getObjectInfoContent(objectInfo)
|
||
local count = objectInfo.numObjects or 0
|
||
if fillType ~= nil and count > 0 then
|
||
local product = newProduct(fillType, count * perObject, spec.numStoredObjects or count, spec.capacity or 0, true)
|
||
product.palletCount = count
|
||
table.insert(products, product)
|
||
end
|
||
end
|
||
return products
|
||
end
|
||
|
||
-- Stald: det læssestationen udleverer (mælk, gylle ...) med staldens eget niveau/kapacitet.
|
||
local function getHusbandryProducts(source)
|
||
local placeable, station = source.placeable, source.station
|
||
local fillTypes = {}
|
||
for fillType, supported in pairs(station ~= nil and station.supportedFillTypes or {}) do
|
||
if supported == true then table.insert(fillTypes, fillType) end
|
||
end
|
||
if #fillTypes == 0 and station ~= nil and station.getIsFillTypeSupported ~= nil and g_fillTypeManager ~= nil then
|
||
for _, name in ipairs({"MILK", "GOATMILK", "BUFFALOMILK", "SHEEPMILK", "LIQUIDMANURE", "MANURE", "DIGESTATE"}) do
|
||
local fillType = g_fillTypeManager.getFillTypeIndexByName ~= nil and g_fillTypeManager:getFillTypeIndexByName(name) or nil
|
||
if fillType ~= nil then
|
||
local ok, value = pcall(station.getIsFillTypeSupported, station, fillType)
|
||
if ok and value == true then table.insert(fillTypes, fillType) end
|
||
end
|
||
end
|
||
end
|
||
table.sort(fillTypes)
|
||
local products = {}
|
||
for _, fillType in ipairs(fillTypes) do
|
||
local level = placeable:getHusbandryFillLevel(fillType) or 0
|
||
local capacity = placeable.getHusbandryCapacity ~= nil and (placeable:getHusbandryCapacity(fillType) or 0) or 0
|
||
if capacity <= 0 and placeable.getHusbandryFreeCapacity ~= nil then
|
||
capacity = level + (placeable:getHusbandryFreeCapacity(fillType) or 0)
|
||
end
|
||
table.insert(products, newProduct(fillType, level, level, capacity, false))
|
||
end
|
||
return products
|
||
end
|
||
|
||
-- Varer ved kilden med tilgængelig mængde: { {fillType, name, title, available, level, capacity, isPallet} }.
|
||
function ADSources.getProducts(adEnv, source, farmId)
|
||
if source.kind == "factory" then
|
||
return getFactoryProducts(source, farmId)
|
||
elseif source.kind == "silo" then
|
||
return getSiloProducts(source)
|
||
elseif source.kind == "husbandry" then
|
||
return getHusbandryProducts(source)
|
||
elseif source.kind == "objectStorage" then
|
||
return getObjectStorageProducts(source)
|
||
end
|
||
return {}
|
||
end
|
||
|
||
-- Objektlager: objectInfo-indeks + antal for varen; nil når lageret ikke har den.
|
||
function ADSources.getObjectInfoIndex(source, fillType)
|
||
local spec = source.placeable ~= nil and source.placeable.spec_objectStorage or nil
|
||
for index, objectInfo in ipairs(spec ~= nil and spec.objectInfos or {}) do
|
||
local infoFillType = getObjectInfoContent(objectInfo)
|
||
if infoFillType == fillType and (objectInfo.numObjects or 0) > 0 then
|
||
return index, objectInfo.numObjects
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
function ADSources.countStored(source, fillType)
|
||
local _, count = ADSources.getObjectInfoIndex(source, fillType)
|
||
return count or 0
|
||
end
|
||
|
||
-- Bed lageret spawne ÉT objekt (det knappen i spillet gør). connection = nil, så
|
||
-- AutomaticBaleStorage-moddens kø (der kun fanger klient-kald) ikke blander sig.
|
||
function ADSources.spawnOne(source, objectInfoIndex)
|
||
local placeable = source.placeable
|
||
if placeable == nil or placeable.removeAbstractObjectsFromStorage == nil then
|
||
return false
|
||
end
|
||
local ok = pcall(placeable.removeAbstractObjectsFromStorage, placeable, objectInfoIndex, 1, nil)
|
||
return ok == true
|
||
end
|
||
|
||
-- acceptedFillTypes rummer kun bulk-triggerens varer; palletriggerens varer ligger i
|
||
-- supportedFillTypes (det AutoDrive selv slår op i). Begge tæller.
|
||
function ADSources.stationAccepts(station, fillType)
|
||
if station.acceptedFillTypes ~= nil and station.acceptedFillTypes[fillType] == true then
|
||
return true
|
||
end
|
||
if station.supportedFillTypes ~= nil and station.supportedFillTypes[fillType] == true then
|
||
return true
|
||
end
|
||
if station.getIsFillTypeSupported ~= nil then
|
||
local ok, value = pcall(station.getIsFillTypeSupported, station, fillType)
|
||
return ok and value == true
|
||
end
|
||
return false
|
||
end
|
||
|
||
-- Til loggen: navnene på de varer stationen tager (sorteret), fx "SUGAR, STRAW_BASKET".
|
||
function ADSources.describeAccepted(station)
|
||
local set = {}
|
||
for _, field in ipairs({"acceptedFillTypes", "supportedFillTypes"}) do
|
||
for fillType, accepted in pairs(station[field] or {}) do
|
||
if accepted == true then set[fillType] = true end
|
||
end
|
||
end
|
||
local names = {}
|
||
for fillType in pairs(set) do
|
||
table.insert(names, g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeNameByIndex(fillType) or tostring(fillType))
|
||
end
|
||
table.sort(names, function(a, b)
|
||
-- stabil rækkefølge efter indeks hvor det kendes, ellers navn
|
||
return tostring(a) < tostring(b)
|
||
end)
|
||
return table.concat(names, ", ")
|
||
end
|
||
|
||
-- Markører hvis aflæsningsstation er et rigtigt salgssted: { {marker, station} }.
|
||
function ADSources.getSellMarkers(adEnv, farmId)
|
||
local result = {}
|
||
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
||
local station = ADSmartPickup.getUnloadStationAtWayPointId(adEnv, marker.id, farmId)
|
||
if station ~= nil and ADPrices.isSellingStation(station) then
|
||
table.insert(result, {marker = marker, station = station})
|
||
end
|
||
end
|
||
return result
|
||
end
|
||
|
||
-- ---- paller til lager: objektlager (palleskur) som mål ----
|
||
|
||
function ADSources.objectStorageAccepts(placeable, fillType)
|
||
local spec = placeable ~= nil and placeable.spec_objectStorage or nil
|
||
if spec == nil or spec.supportsPallets == false then
|
||
return false
|
||
end
|
||
if placeable.getObjectStorageSupportsFillType ~= nil then
|
||
local ok, value = pcall(placeable.getObjectStorageSupportsFillType, placeable, fillType)
|
||
return ok and value == true
|
||
end
|
||
return true
|
||
end
|
||
|
||
-- Ledige pladser (objekter); uendeligt når lageret ingen kapacitet opgiver.
|
||
function ADSources.objectStorageFreeSlots(placeable)
|
||
local spec = placeable ~= nil and placeable.spec_objectStorage or nil
|
||
if spec == nil then
|
||
return 0
|
||
end
|
||
local capacity = spec.capacity or 0
|
||
if capacity <= 0 then
|
||
return math.huge
|
||
end
|
||
local stored = spec.storedObjects ~= nil and #spec.storedObjects or (spec.numStoredObjects or 0)
|
||
return math.max(0, capacity - stored)
|
||
end
|
||
|
||
-- Nærmeste markør ved et objektlager der tager varen og har plads, med rute fra traktoren;
|
||
-- kilderne selv udelukkes. {wayPointId, name, free = pladser} eller nil.
|
||
function ADSources.findPalletStore(adEnv, vehicle, fillType, excludedPlaceables, isReachable, farmId)
|
||
local x, z = getNodePosition(vehicle.rootNode)
|
||
if x == nil then
|
||
return nil
|
||
end
|
||
local candidates = {}
|
||
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
||
local source = ADSources.identify(adEnv, marker.id, farmId)
|
||
if source ~= nil and source.kind == "objectStorage" and not (excludedPlaceables ~= nil and excludedPlaceables[source.placeable])
|
||
and ADSources.objectStorageAccepts(source.placeable, fillType) and ADSources.objectStorageFreeSlots(source.placeable) >= 1 then
|
||
local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id)
|
||
table.insert(candidates, {marker = marker, source = source, distance = distance2(x, z, wayPoint.x, wayPoint.z)})
|
||
end
|
||
end
|
||
table.sort(candidates, function(a, b) return a.distance < b.distance end)
|
||
for _, candidate in ipairs(candidates) do
|
||
if isReachable == nil or isReachable(candidate.marker.id) then
|
||
return {wayPointId = candidate.marker.id, name = tostring(candidate.marker.name), free = ADSources.objectStorageFreeSlots(candidate.source.placeable)}
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
-- ---- reserve: hvad gårdens egne fabrikker og stalde skal bruge det næste døgn ----
|
||
|
||
ADSources.RESERVE_HOURS = 24
|
||
|
||
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 isProductionWanted(productionPoint, production)
|
||
local isEnabled = false
|
||
if productionPoint.getIsProductionEnabled ~= nil then
|
||
isEnabled = productionPoint:getIsProductionEnabled(production.id) == true
|
||
else
|
||
for _, active in ipairs(productionPoint.activeProductions or {}) do
|
||
if active == production then isEnabled = true end
|
||
end
|
||
end
|
||
local missingInputs = ProductionPoint ~= nil and ProductionPoint.PROD_STATUS ~= nil and ProductionPoint.PROD_STATUS.MISSING_INPUTS or nil
|
||
return isEnabled or (missingInputs ~= nil and production.status == missingInputs)
|
||
end
|
||
|
||
-- Fabrik: døgnforbrug af varen i ønskede produktioner minus det den har, højst fri plads.
|
||
local function getProductionReserve(placeable, fillType)
|
||
local productionPoint = getProductionPoint(placeable)
|
||
if productionPoint == nil or productionPoint.storage == nil then
|
||
return 0
|
||
end
|
||
local perMinute = 0
|
||
for _, production in ipairs(productionPoint.productions or {}) do
|
||
if isProductionWanted(productionPoint, production) then
|
||
for _, input in ipairs(production.inputs or {}) do
|
||
if input.type == fillType then
|
||
local cyclesPerMinute = production.cyclesPerMinute or ((production.cyclesPerHour or 0) / 60)
|
||
perMinute = perMinute + (input.amount or 0) * cyclesPerMinute
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if perMinute <= 0 then
|
||
return 0
|
||
end
|
||
local storage = productionPoint.storage
|
||
local level = storage.getFillLevel ~= nil and (storage:getFillLevel(fillType) or 0) or ((storage.fillLevels or {})[fillType] or 0)
|
||
local free = storage.getFreeCapacity ~= nil and storage:getFreeCapacity(fillType) or nil
|
||
if free == nil then
|
||
local capacity = storage.getCapacity ~= nil and (storage:getCapacity(fillType) or 0) or 0
|
||
free = math.max(0, capacity - level)
|
||
end
|
||
local need = perMinute * 60 * ADSources.RESERVE_HOURS - level
|
||
return math.max(0, math.min(need, free))
|
||
end
|
||
|
||
-- Stald: foder/halm/vand-rate × timer minus beholdning, højst fri plads (spec-felter som
|
||
-- Distribution Redux læser dem); automatisk vandforsyning = intet behov.
|
||
local function getHusbandryReserve(placeable, fillType)
|
||
if placeable.getHusbandryIsFillTypeSupported == nil or placeable.getHusbandryFillLevel == nil then
|
||
return 0
|
||
end
|
||
local okSupported, supported = pcall(placeable.getHusbandryIsFillTypeSupported, placeable, fillType)
|
||
if not okSupported or supported ~= true then
|
||
return 0
|
||
end
|
||
local name = g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeNameByIndex(fillType) or ""
|
||
local rate, level = 0, placeable:getHusbandryFillLevel(fillType) or 0
|
||
local food, straw, water = placeable.spec_husbandryFood, placeable.spec_husbandryStraw, placeable.spec_husbandryWater
|
||
if name == "WATER" then
|
||
if water == nil or water.automaticWaterSupply then return 0 end
|
||
rate = water.litersPerHour or 0
|
||
elseif name == "STRAW" and straw ~= nil then
|
||
rate = straw.inputLitersPerHour or 0
|
||
elseif food ~= nil then
|
||
rate = food.litersPerHour or 0
|
||
level = 0
|
||
for _, value in pairs(food.fillLevels or {}) do level = level + (value or 0) end
|
||
end
|
||
if rate <= 0 then
|
||
return 0
|
||
end
|
||
local free = placeable.getHusbandryFreeCapacity ~= nil and (placeable:getHusbandryFreeCapacity(fillType) or 0) or math.huge
|
||
return math.max(0, math.min(rate * ADSources.RESERVE_HOURS - level, free))
|
||
end
|
||
|
||
-- {liters, consumers = {navne}} — gårdens samlede behov for varen; udelukkede placeables (kilderne) tæller ikke.
|
||
function ADSources.getFarmDemand(adEnv, fillType, farmId, excludedPlaceables)
|
||
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
|
||
local total, consumers = 0, {}
|
||
for _, placeable in pairs(placeables) do
|
||
if isOwnedBy(placeable, farmId) and not (excludedPlaceables ~= nil and excludedPlaceables[placeable]) then
|
||
local reserve = getProductionReserve(placeable, fillType) + getHusbandryReserve(placeable, fillType)
|
||
if reserve > 0 then
|
||
total = total + reserve
|
||
table.insert(consumers, getPlaceableName(placeable, "?"))
|
||
end
|
||
end
|
||
end
|
||
return {liters = total, consumers = consumers}
|
||
end
|
||
|
||
-- ---- inventar: alt gården har liggende, pr. bygning ----
|
||
|
||
local function addEntry(entries, fillType, level, capacity, extra)
|
||
local entry = newProduct(fillType, level, level, capacity, false)
|
||
entry.isOutput = extra ~= nil and extra.isOutput or false
|
||
entry.count = extra ~= nil and extra.count or nil
|
||
table.insert(entries, entry)
|
||
end
|
||
|
||
local function factoryEntries(placeable)
|
||
local productionPoint = getProductionPoint(placeable)
|
||
local storage = productionPoint ~= nil and productionPoint.storage or nil
|
||
local entries = {}
|
||
if storage == nil then return entries end
|
||
local outputs = {}
|
||
for _, production in ipairs(productionPoint.productions or {}) do
|
||
for _, output in ipairs(production.outputs or {}) do outputs[output.type] = true end
|
||
end
|
||
for fillType, level in pairs(storage.fillLevels or {}) do
|
||
if (level or 0) > 0 then
|
||
local capacity = storage.getCapacity ~= nil and (storage:getCapacity(fillType) or 0) or 0
|
||
addEntry(entries, fillType, level, capacity, {isOutput = outputs[fillType] == true})
|
||
end
|
||
end
|
||
return entries
|
||
end
|
||
|
||
local function siloEntries(placeable)
|
||
local entries, levels, capacities = {}, {}, {}
|
||
for _, storage in pairs(placeable.spec_silo.storages or {}) do
|
||
for fillType, level in pairs(storage.fillLevels or {}) do
|
||
if (level or 0) > 0 then
|
||
levels[fillType] = (levels[fillType] or 0) + level
|
||
capacities[fillType] = (capacities[fillType] or 0) + (storage.getCapacity ~= nil and (storage:getCapacity(fillType) or 0) or 0)
|
||
end
|
||
end
|
||
end
|
||
for fillType, level in pairs(levels) do addEntry(entries, fillType, level, capacities[fillType] or 0) end
|
||
return entries
|
||
end
|
||
|
||
local function objectStorageEntries(placeable)
|
||
local entries = {}
|
||
local spec = placeable.spec_objectStorage
|
||
for _, objectInfo in ipairs(spec.objectInfos or {}) do
|
||
local fillType, perObject = getObjectInfoContent(objectInfo)
|
||
local count = objectInfo.numObjects or 0
|
||
if fillType ~= nil and count > 0 then
|
||
addEntry(entries, fillType, count * perObject, spec.capacity or 0, {count = count})
|
||
end
|
||
end
|
||
return entries
|
||
end
|
||
|
||
local function husbandryEntries(placeable)
|
||
local entries, seen = {}, {}
|
||
local food = placeable.spec_husbandryFood
|
||
if food ~= nil then
|
||
local total = 0
|
||
for _, level in pairs(food.fillLevels or {}) do total = total + (level or 0) end
|
||
for fillType, level in pairs(food.fillLevels or {}) do
|
||
if (level or 0) > 0 then
|
||
seen[fillType] = true
|
||
addEntry(entries, fillType, level, level + math.max(0, (food.capacity or 0) - total))
|
||
end
|
||
end
|
||
end
|
||
for _, name in ipairs({"STRAW", "WATER", "MILK", "LIQUIDMANURE", "MANURE"}) do
|
||
local fillType = g_fillTypeManager ~= nil and g_fillTypeManager.getFillTypeIndexByName ~= nil and g_fillTypeManager:getFillTypeIndexByName(name) or nil
|
||
if fillType == nil and g_fillTypeManager ~= nil then
|
||
-- test-fakes uden getFillTypeIndexByName: slå navnet op baglæns
|
||
for index = 1, 64 do
|
||
if g_fillTypeManager:getFillTypeNameByIndex(index) == name then fillType = index break end
|
||
end
|
||
end
|
||
if fillType ~= nil and not seen[fillType] and placeable.getHusbandryIsFillTypeSupported ~= nil and placeable:getHusbandryIsFillTypeSupported(fillType) then
|
||
local level = placeable.getHusbandryFillLevel ~= nil and (placeable:getHusbandryFillLevel(fillType) or 0) or 0
|
||
if level > 0 then
|
||
local free = placeable.getHusbandryFreeCapacity ~= nil and (placeable:getHusbandryFreeCapacity(fillType) or 0) or 0
|
||
addEntry(entries, fillType, level, level + free)
|
||
end
|
||
end
|
||
end
|
||
return entries
|
||
end
|
||
|
||
-- { {name, kind = "factory"|"silo"|"objectStorage"|"husbandry", entries = {{fillType, name, title, level, capacity, isOutput, count}}} }
|
||
-- kun gårdens bygninger med indhold; sorteret på slags, dernæst navn.
|
||
function ADSources.getFarmInventory(adEnv, farmId)
|
||
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
|
||
local result = {}
|
||
for _, placeable in pairs(placeables) do
|
||
if isOwnedBy(placeable, farmId) then
|
||
local kind, entries = nil, nil
|
||
if getProductionPoint(placeable) ~= nil then
|
||
kind, entries = "factory", factoryEntries(placeable)
|
||
elseif placeable.spec_silo ~= nil then
|
||
kind, entries = "silo", siloEntries(placeable)
|
||
elseif placeable.spec_objectStorage ~= nil then
|
||
kind, entries = "objectStorage", objectStorageEntries(placeable)
|
||
elseif placeable.getHusbandryFillLevel ~= nil then
|
||
kind, entries = "husbandry", husbandryEntries(placeable)
|
||
end
|
||
if entries ~= nil and #entries > 0 then
|
||
table.sort(entries, function(a, b)
|
||
if a.isOutput ~= b.isOutput then return a.isOutput end
|
||
return tostring(a.title) < tostring(b.title)
|
||
end)
|
||
table.insert(result, {name = getPlaceableName(placeable, "?"), kind = kind, entries = entries, placeable = placeable})
|
||
end
|
||
end
|
||
end
|
||
local order = {factory = 1, husbandry = 2, objectStorage = 3, silo = 4}
|
||
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 ADSources.getSourcePlaceables(sources)
|
||
local set = {}
|
||
for _, source in ipairs(sources or {}) do
|
||
if source.placeable ~= nil then
|
||
set[source.placeable] = true
|
||
end
|
||
end
|
||
return set
|
||
end
|