139 lines
7.1 KiB
Lua
139 lines
7.1 KiB
Lua
-- AD Load Swap
|
|
-- En traktor der venter ved et Wait-punkt med en vare målet er fuldt af, mens målet
|
|
-- MANGLER en anden vare i traktorens liste, låser forsyningen (alle venter med halm,
|
|
-- ingen henter TMR). Her afleveres resten i en silo der tager varen, så vognen er tom
|
|
-- og næste tur henter det målet mangler.
|
|
|
|
ADLoadSwap = {}
|
|
|
|
ADLoadSwap.LOG_PREFIX = "ADSmartPickup: "
|
|
-- Opslaget går over alle markører; ventende traktorer tjekker derfor kun så tit.
|
|
ADLoadSwap.CHECK_INTERVAL_MS = 5000
|
|
-- Byt også når et fuldt læs ikke passer, hvis den manglende vare mangler mindst så stor en andel af sit lager.
|
|
ADLoadSwap.MIN_MISSING_FRACTION = 0.25
|
|
|
|
local warnedNoSilo = setmetatable({}, {__mode = "k"})
|
|
|
|
-- Er en anden vare i listen så tom i målet, at et FULDT læs passer, og findes den i en silo?
|
|
function ADLoadSwap.findMissingIngredient(adEnv, vehicle, restFillType, homeWayPointId)
|
|
local ingredients = ADSmartPickup.getIngredientsFor(adEnv, vehicle)
|
|
if ingredients == nil or #ingredients < 2 then
|
|
return nil
|
|
end
|
|
local choice = ADSmartPickup.findSupplyPickup(adEnv, vehicle, ingredients, homeWayPointId)
|
|
if choice == nil or choice.fillType == nil or choice.fillType == restFillType then
|
|
return nil
|
|
end
|
|
-- Et fuldt læs der passer er det bedste; men er vognen større end staldens lager, passer det aldrig
|
|
-- (Fastrac med stor vogn ved en kostald) — så er det nok at varen mangler en tydelig del.
|
|
local need = choice.need
|
|
local missesEnough = need.capacity ~= nil and need.capacity > 0 and need.freeSpace ~= nil
|
|
and need.freeSpace >= need.capacity * ADLoadSwap.MIN_MISSING_FRACTION
|
|
if need.fits ~= true and not missesEnough then
|
|
return nil
|
|
end
|
|
return choice
|
|
end
|
|
|
|
-- Kun rene lager-siloer: stalde og fabrikker har egne regler, og målet selv er fuldt.
|
|
local function isSilo(station, homeStation)
|
|
local placeable = station.owningPlaceable
|
|
return placeable ~= nil and placeable.spec_silo ~= nil and placeable.getHusbandryFillLevel == nil
|
|
and placeable.spec_productionPoint == nil and (homeStation == nil or placeable ~= homeStation.owningPlaceable)
|
|
end
|
|
|
|
local function acceptsRest(station, fillType, rest, farmId)
|
|
if station.getIsFillTypeSupported ~= nil and not station:getIsFillTypeSupported(fillType) then
|
|
return false
|
|
end
|
|
local free = station.getFreeCapacity ~= nil and station:getFreeCapacity(fillType, farmId) or 0
|
|
return free >= rest
|
|
end
|
|
|
|
-- Nærmeste silo med plads til resten og vejnet til/fra; siloer der allerede har varen foretrækkes.
|
|
-- Nærmeste silo med plads til resten og vejnet fra traktoren; siloer der allerede har varen
|
|
-- foretrækkes. Returnerer marker — eller nil + en forklaring (til loggen).
|
|
-- excludedPlaceables (valgfri): {[placeable] = true} der aldrig må vælges (fx udkørslens kilder).
|
|
function ADLoadSwap.findDumpMarker(adEnv, vehicle, fillType, rest, homeWayPointId, isReachable, excludedPlaceables)
|
|
if vehicle.rootNode == nil then
|
|
return nil, "køretøjet har ingen position"
|
|
end
|
|
local farmId = ADSmartPickup.getFarmId(vehicle)
|
|
local homeStation = homeWayPointId ~= nil and ADSmartPickup.getUnloadStationAtWayPointId(adEnv, homeWayPointId, farmId) or nil
|
|
local x, _, z = getWorldTranslation(vehicle.rootNode)
|
|
local candidates = {}
|
|
local silos, withSpace = 0, 0
|
|
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
|
local station = ADSmartPickup.getUnloadStationAtWayPointId(adEnv, marker.id, farmId)
|
|
local isExcluded = excludedPlaceables ~= nil and excludedPlaceables[station ~= nil and station.owningPlaceable or false] == true
|
|
if station ~= nil and isSilo(station, homeStation) and not isExcluded then
|
|
silos = silos + 1
|
|
if acceptsRest(station, fillType, rest, farmId) then
|
|
withSpace = withSpace + 1
|
|
local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id)
|
|
if wayPoint ~= nil then
|
|
local dx, dz = wayPoint.x - x, wayPoint.z - z
|
|
table.insert(candidates, {
|
|
marker = marker,
|
|
hasFillType = ADSmartPickup.getOwnStorageLevel(station, {fillType}) > 0,
|
|
distance = dx * dx + dz * dz,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
table.sort(candidates, function(a, b)
|
|
if a.hasFillType ~= b.hasFillType then
|
|
return a.hasFillType
|
|
end
|
|
return a.distance < b.distance
|
|
end)
|
|
for _, candidate in ipairs(candidates) do
|
|
if isReachable(candidate.marker.id) then
|
|
return candidate.marker
|
|
end
|
|
end
|
|
if silos == 0 then
|
|
return nil, "ingen silo med aflæsningsmarkør fundet (siloen skal have en AD-markør ved aflæsningstriggeren)"
|
|
elseif withSpace == 0 then
|
|
return nil, string.format("%d silo(er) fundet, men ingen tager varen med plads til %.0f l", silos, rest)
|
|
end
|
|
return nil, string.format("%d silo(er) tager varen, men AutoDrive finder ingen rute fra traktoren til dem", withSpace)
|
|
end
|
|
|
|
-- Kaldes fra Wait-logikken hvert tick; redirect(task, id) og release(vehicle) er dens egne.
|
|
-- true = traktoren er sendt til en silo (Wait-punktet er frigivet).
|
|
function ADLoadSwap.tryFromWait(adEnv, task, wait, dt, redirect, release, isReachable)
|
|
wait.swapTimerMs = (wait.swapTimerMs or 0) + (dt or 0)
|
|
if wait.swapTimerMs < ADLoadSwap.CHECK_INTERVAL_MS then
|
|
return false
|
|
end
|
|
wait.swapTimerMs = 0
|
|
local vehicle = task.vehicle
|
|
-- Tager målet slet ikke varen (forkert last), skal den af uanset hvad der ellers mangler.
|
|
local missing = ADLoadSwap.findMissingIngredient(adEnv, vehicle, wait.fillType, wait.homeDestinationID)
|
|
if missing == nil and not wait.isRejected then
|
|
return false
|
|
end
|
|
local marker, why = ADLoadSwap.findDumpMarker(adEnv, vehicle, wait.fillType, wait.rest, wait.homeDestinationID, isReachable)
|
|
local restName = g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeNameByIndex(wait.fillType) or tostring(wait.fillType)
|
|
local missingName = missing ~= nil and (g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeNameByIndex(missing.fillType) or tostring(missing.fillType)) or "-"
|
|
if marker == nil then
|
|
if not warnedNoSilo[vehicle] then
|
|
warnedNoSilo[vehicle] = true
|
|
Logging.warning(ADLoadSwap.LOG_PREFIX .. "'%s' venter med %.0f l %s (%s) — %s; den bliver ved Wait-punktet", tostring(vehicle:getName()), wait.rest, tostring(restName), missing ~= nil and ("målet mangler " .. tostring(missingName)) or "målet tager ikke varen", tostring(why))
|
|
end
|
|
return false
|
|
end
|
|
warnedNoSilo[vehicle] = nil
|
|
release(vehicle)
|
|
redirect(task, marker.id)
|
|
local why = missing ~= nil and ("målet mangler " .. tostring(missingName)) or "målet tager ikke varen"
|
|
Logging.info(ADLoadSwap.LOG_PREFIX .. "'%s' bytter læs: %.0f l %s tilbage i %s (markør %s), %s", tostring(vehicle:getName()), wait.rest, tostring(restName), tostring(marker.name), tostring(marker.markerIndex or marker.id), why)
|
|
return true
|
|
end
|
|
|
|
function ADLoadSwap.clearWarning(vehicle)
|
|
warnedNoSilo[vehicle] = nil
|
|
end
|