Files
fs25-adsmartpickup/FS25_ADSmartPickup/adSources.lua
T

329 lines
14 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
-- 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" then
return false
elseif source.kind == "objectStorage" then
return true
end
if stationSupports(source.station, fillType) then
return false
end
return ADSources.isPalletFillType(fillType)
end
local function getProductionPoint(placeable)
return placeable ~= nil and placeable.spec_productionPoint ~= nil and placeable.spec_productionPoint.productionPoint or nil
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
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
-- 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
return {kind = "factory", placeable = factory, station = nil, 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
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 = 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
end
end
end
return total
end
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}
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 available = level + (isPallet and getPalletsNearSpawner(source.placeable, fillType, farmId) or 0)
table.insert(products, newProduct(fillType, available, level, capacity, isPallet))
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
table.insert(products, newProduct(fillType, count * perObject, spec.numStoredObjects or count, spec.capacity or 0, true))
end
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 == "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
function ADSources.stationAccepts(station, fillType)
if station.acceptedFillTypes ~= nil then
return station.acceptedFillTypes[fillType] == true
end
if station.getIsFillTypeSupported ~= nil then
return station:getIsFillTypeSupported(fillType) == true
end
return station.supportedFillTypes ~= nil and station.supportedFillTypes[fillType] == true
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
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