feat: AD Smart Pickup — vare-bevidst mappe-rotation (mindst fyldte silo først)

This commit is contained in:
2026-09-21 00:32:59 +02:00
commit 63afbba1f8
6 changed files with 353 additions and 0 deletions
+186
View File
@@ -0,0 +1,186 @@
-- AD Smart Pickup
-- Gør AutoDrives mappe-rotation for pålæsning vare-bevidst: i stedet for
-- "næste markør i mappen" vælges den markør, hvis silo har den valgte vare —
-- og blandt dem den med MINDST i (så rester tømmes først og siloer frigives).
-- Findes ingen silo med varen, bruges AutoDrives originale rotation uændret.
ADSmartPickup = {}
-- Markøren er dér hvor trækkerens næse stopper; læssetriggeren ligger et
-- vogntog bagved. Siloer tættere end dette regnes som "markørens silo".
ADSmartPickup.MAX_MARKER_TO_TRIGGER_DISTANCE = 40
-- Under dette niveau regnes siloen som tom (undgår ture efter støv-rester).
ADSmartPickup.MIN_USEFUL_LITERS = 1
ADSmartPickup.LOG_PREFIX = "ADSmartPickup: "
local isHooked = false
-- Cross-mod adgang: FS25-mods er sandboxede; AutoDrives globals ligger
-- i den globale tabel FS25_AutoDrive (zip-navnet).
local function getAutoDriveEnv()
if FS25_AutoDrive ~= nil and FS25_AutoDrive.ADMultipleTargetsManager ~= nil then
return FS25_AutoDrive
end
return nil
end
local function getFarmId(vehicle)
if vehicle.getOwnerFarmId ~= nil then
local farmId = vehicle:getOwnerFarmId()
if farmId ~= nil and farmId > 0 then
return farmId
end
end
if vehicle.spec_aiVehicle ~= nil and vehicle.spec_aiVehicle.startedFarmId ~= nil and vehicle.spec_aiVehicle.startedFarmId > 0 then
return vehicle.spec_aiVehicle.startedFarmId
end
return nil
end
local function getWantedFillTypes(driver)
local wanted = {}
local selected = driver.ad.stateModule:getSelectedFillTypes()
if type(selected) == "table" then
for _, fillType in pairs(selected) do
table.insert(wanted, fillType)
end
end
if #wanted == 0 then
local fillType = driver.ad.stateModule:getFillType()
if fillType ~= nil then
table.insert(wanted, fillType)
end
end
return wanted
end
local function getDistanceToStation(station, x, z)
local closest = math.huge
for _, loadTrigger in pairs(station.loadTriggers or {}) do
local node = loadTrigger.triggerNode
if node ~= nil and entityExists(node) then
local tx, _, tz = getWorldTranslation(node)
closest = math.min(closest, MathUtil.vector2Length(x - tx, z - tz))
end
end
return closest
end
local function getStationAtMarker(wayPoint, farmId)
local bestStation = nil
local bestDistance = ADSmartPickup.MAX_MARKER_TO_TRIGGER_DISTANCE
for _, station in pairs(g_currentMission.storageSystem:getLoadingStations()) do
if g_currentMission.accessHandler:canFarmAccess(farmId, station) then
local distance = getDistanceToStation(station, wayPoint.x, wayPoint.z)
if distance < bestDistance then
bestDistance = distance
bestStation = station
end
end
end
return bestStation
end
-- Siloens EGET indhold. Udvidelses-siloer (isExtension) deler lager med naboer
-- inden for storageRadius, så stationens niveau er en nabo-sum — eget lager er
-- det tal spilleren ser på siloen og giver en præcis "mindst først"-rangering.
local function getOwnStorageLevel(station, fillTypes)
local placeable = station.owningPlaceable
local storages = placeable ~= nil and placeable.spec_silo ~= nil and placeable.spec_silo.storages or nil
if storages == nil then
return 0
end
local total = 0
for _, storage in pairs(storages) do
for _, fillType in ipairs(fillTypes) do
total = total + (storage:getFillLevel(fillType) or 0)
end
end
return total
end
-- Returnerer niveau + om det er siloens eget lager (true) eller kun nabo-lagre (false).
local function getStationFillLevel(station, fillTypes, farmId)
local ownLevel = getOwnStorageLevel(station, fillTypes)
if ownLevel >= ADSmartPickup.MIN_USEFUL_LITERS then
return ownLevel, true
end
-- intet i eget lager: stationen kan stadig læsse fra nabo-lagre
local total = 0
for _, fillType in ipairs(fillTypes) do
total = total + (station:getFillLevel(fillType, farmId) or 0)
end
return total, false
end
-- Returnerer markerIndex for mappens mindst fyldte silo der har varen, ellers nil.
function ADSmartPickup.findLeastFilledPickup(adEnv, driver)
local firstMarker = driver.ad.stateModule:getFirstMarker()
if firstMarker == nil or firstMarker.group == nil or firstMarker.group == "All" then
return nil
end
local farmId = getFarmId(driver)
local fillTypes = getWantedFillTypes(driver)
if farmId == nil or #fillTypes == 0 then
return nil
end
-- siloer med varen i EGET lager går altid forud for dem der kun kan låne fra naboer
local best = {[true] = {level = math.huge}, [false] = {level = math.huge}}
for _, marker in ipairs(adEnv.ADGraphManager:getMapMarkersInGroup(firstMarker.group)) do
local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id)
local station = wayPoint ~= nil and getStationAtMarker(wayPoint, farmId) or nil
if station ~= nil then
local level, isOwn = getStationFillLevel(station, fillTypes, farmId)
if level >= ADSmartPickup.MIN_USEFUL_LITERS and level < best[isOwn].level then
best[isOwn] = {level = level, marker = marker}
end
end
end
local choice = best[true].marker ~= nil and best[true] or best[false]
if choice.marker == nil then
return nil
end
return choice.marker.markerIndex, choice.marker.name, choice.level
end
local function installHook(adEnv)
local manager = adEnv.ADMultipleTargetsManager
local originalGetNextPickup = manager.getNextPickup
manager.getNextPickup = function(self, driver, forcedSkip)
local ok, markerIndex, markerName, level = pcall(ADSmartPickup.findLeastFilledPickup, adEnv, driver)
if not ok then
Logging.warning(ADSmartPickup.LOG_PREFIX .. "fejl i silo-opslag, bruger AutoDrives rotation: %s", tostring(markerIndex))
return originalGetNextPickup(self, driver, forcedSkip)
end
if markerIndex == nil then
return originalGetNextPickup(self, driver, forcedSkip)
end
-- hold AutoDrives egen rotations-tilstand konsistent med vores valg
local groupName = driver.ad.stateModule:getFirstMarker().group
self.pickups[groupName] = self.pickups[groupName] or {}
self.pickups[groupName].lastTarget = markerIndex
self.pickups[groupName].lastVehicle = driver
Logging.info(ADSmartPickup.LOG_PREFIX .. "'%s' -> %s (%.0f l)", tostring(driver:getName()), tostring(markerName), level)
return markerIndex
end
Logging.info(ADSmartPickup.LOG_PREFIX .. "aktiv (mindst fyldte silo med varen vælges i mappe-rotation)")
end
function ADSmartPickup:update(dt)
if isHooked then
return
end
local adEnv = getAutoDriveEnv()
if adEnv ~= nil and g_currentMission ~= nil and g_currentMission.storageSystem ~= nil then
installHook(adEnv)
isHooked = true
end
end
addModEventListener(ADSmartPickup)