feat: behovsstyret forsyning — én traktor holder alle input fyldt
This commit is contained in:
@@ -3,6 +3,10 @@
|
|||||||
-- "næste markør i mappen" vælges den markør, hvis silo har den valgte vare —
|
-- "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).
|
-- 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.
|
-- Findes ingen silo med varen, bruges AutoDrives originale rotation uændret.
|
||||||
|
--
|
||||||
|
-- Behovsstyret forsyning: er der valgt FLERE varer i AutoDrive, ses de som
|
||||||
|
-- ingredienser til aflæsningsmålet (fx en TMR-blander). Pr. tur hentes den
|
||||||
|
-- ingrediens målet mangler mest, fra en silo hvor som helst på kortet.
|
||||||
|
|
||||||
ADSmartPickup = {}
|
ADSmartPickup = {}
|
||||||
|
|
||||||
@@ -13,7 +17,13 @@ ADSmartPickup.MAX_MARKER_TO_TRIGGER_DISTANCE = 40
|
|||||||
ADSmartPickup.MIN_USEFUL_LITERS = 1
|
ADSmartPickup.MIN_USEFUL_LITERS = 1
|
||||||
ADSmartPickup.LOG_PREFIX = "ADSmartPickup: "
|
ADSmartPickup.LOG_PREFIX = "ADSmartPickup: "
|
||||||
|
|
||||||
|
local LOAD_TRIGGER_NODE_FIELDS = {"triggerNode"}
|
||||||
|
local UNLOAD_TRIGGER_NODE_FIELDS = {"exactFillRootNode", "triggerNode", "aiNode"}
|
||||||
|
|
||||||
local isHooked = false
|
local isHooked = false
|
||||||
|
-- køretøj -> {ingredients, narrowedTo}. AutoDrive roterer selv vare ved triggeren
|
||||||
|
-- når flere er valgt, så valget indsnævres til én pr. tur; listen huskes her.
|
||||||
|
local supplyState = setmetatable({}, {__mode = "k"})
|
||||||
|
|
||||||
-- Cross-mod adgang: FS25-mods er sandboxede; AutoDrives globals ligger
|
-- Cross-mod adgang: FS25-mods er sandboxede; AutoDrives globals ligger
|
||||||
-- i den globale tabel FS25_AutoDrive (zip-navnet).
|
-- i den globale tabel FS25_AutoDrive (zip-navnet).
|
||||||
@@ -54,24 +64,26 @@ local function getWantedFillTypes(driver)
|
|||||||
return wanted
|
return wanted
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getDistanceToStation(station, x, z)
|
local function getDistanceToTriggers(triggers, nodeFields, x, z)
|
||||||
local closest = math.huge
|
local closest = math.huge
|
||||||
for _, loadTrigger in pairs(station.loadTriggers or {}) do
|
for _, trigger in pairs(triggers or {}) do
|
||||||
local node = loadTrigger.triggerNode
|
for _, field in ipairs(nodeFields) do
|
||||||
|
local node = trigger[field]
|
||||||
if node ~= nil and entityExists(node) then
|
if node ~= nil and entityExists(node) then
|
||||||
local tx, _, tz = getWorldTranslation(node)
|
local tx, _, tz = getWorldTranslation(node)
|
||||||
closest = math.min(closest, MathUtil.vector2Length(x - tx, z - tz))
|
closest = math.min(closest, MathUtil.vector2Length(x - tx, z - tz))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
return closest
|
return closest
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getStationAtMarker(wayPoint, farmId)
|
local function getClosestStation(candidateStations, triggersField, nodeFields, wayPoint, farmId)
|
||||||
local bestStation = nil
|
local bestStation = nil
|
||||||
local bestDistance = ADSmartPickup.MAX_MARKER_TO_TRIGGER_DISTANCE
|
local bestDistance = ADSmartPickup.MAX_MARKER_TO_TRIGGER_DISTANCE
|
||||||
for _, station in pairs(g_currentMission.storageSystem:getLoadingStations()) do
|
for _, station in pairs(candidateStations) do
|
||||||
if g_currentMission.accessHandler:canFarmAccess(farmId, station) then
|
if g_currentMission.accessHandler:canFarmAccess(farmId, station) then
|
||||||
local distance = getDistanceToStation(station, wayPoint.x, wayPoint.z)
|
local distance = getDistanceToTriggers(station[triggersField], nodeFields, wayPoint.x, wayPoint.z)
|
||||||
if distance < bestDistance then
|
if distance < bestDistance then
|
||||||
bestDistance = distance
|
bestDistance = distance
|
||||||
bestStation = station
|
bestStation = station
|
||||||
@@ -81,6 +93,14 @@ local function getStationAtMarker(wayPoint, farmId)
|
|||||||
return bestStation
|
return bestStation
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function getLoadStationAtMarker(wayPoint, farmId)
|
||||||
|
return getClosestStation(g_currentMission.storageSystem:getLoadingStations(), "loadTriggers", LOAD_TRIGGER_NODE_FIELDS, wayPoint, farmId)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getUnloadStationAtMarker(wayPoint, farmId)
|
||||||
|
return getClosestStation(g_currentMission.storageSystem:getUnloadingStations(), "unloadTriggers", UNLOAD_TRIGGER_NODE_FIELDS, wayPoint, farmId)
|
||||||
|
end
|
||||||
|
|
||||||
-- Siloens EGET indhold. Udvidelses-siloer (isExtension) deler lager med naboer
|
-- Siloens EGET indhold. Udvidelses-siloer (isExtension) deler lager med naboer
|
||||||
-- inden for storageRadius, så stationens niveau er en nabo-sum — eget lager er
|
-- 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.
|
-- det tal spilleren ser på siloen og giver en præcis "mindst først"-rangering.
|
||||||
@@ -113,7 +133,45 @@ local function getStationFillLevel(station, fillTypes, farmId)
|
|||||||
return total, false
|
return total, false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Returnerer markerIndex for mappens mindst fyldte silo der har varen, ellers nil.
|
-- Parrer markører med deres læssestation (markører uden station udelades).
|
||||||
|
-- excludedPlaceable: aflæsningsmålets egen placeable må ikke være kilde.
|
||||||
|
local function collectMarkerStations(adEnv, candidateMarkers, farmId, excludedPlaceable)
|
||||||
|
local markerStations = {}
|
||||||
|
for _, marker in pairs(candidateMarkers) do
|
||||||
|
local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id)
|
||||||
|
local station = wayPoint ~= nil and getLoadStationAtMarker(wayPoint, farmId) or nil
|
||||||
|
local isExcluded = station ~= nil and excludedPlaceable ~= nil and station.owningPlaceable == excludedPlaceable
|
||||||
|
if station ~= nil and not isExcluded then
|
||||||
|
table.insert(markerStations, {marker = marker, station = station})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return markerStations
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Mindst fyldte silo der har varen; siloer med varen i EGET lager går altid
|
||||||
|
-- forud for dem der kun kan låne fra naboer. Returnerer et valg eller nil.
|
||||||
|
local function findLeastFilled(markerStations, fillTypes, farmId)
|
||||||
|
local best = {[true] = {level = math.huge}, [false] = {level = math.huge}}
|
||||||
|
for _, entry in ipairs(markerStations) do
|
||||||
|
local level, isOwn = getStationFillLevel(entry.station, fillTypes, farmId)
|
||||||
|
if level >= ADSmartPickup.MIN_USEFUL_LITERS and level < best[isOwn].level then
|
||||||
|
best[isOwn] = {level = level, marker = entry.marker}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local choice = best[true].marker ~= nil and best[true] or best[false]
|
||||||
|
if choice.marker == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
markerIndex = choice.marker.markerIndex,
|
||||||
|
markerName = choice.marker.name,
|
||||||
|
group = choice.marker.group,
|
||||||
|
level = choice.level,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Mappe-logik: mappens mindst fyldte silo der har den/de valgte varer, ellers nil.
|
||||||
function ADSmartPickup.findLeastFilledPickup(adEnv, driver)
|
function ADSmartPickup.findLeastFilledPickup(adEnv, driver)
|
||||||
local firstMarker = driver.ad.stateModule:getFirstMarker()
|
local firstMarker = driver.ad.stateModule:getFirstMarker()
|
||||||
if firstMarker == nil or firstMarker.group == nil or firstMarker.group == "All" then
|
if firstMarker == nil or firstMarker.group == nil or firstMarker.group == "All" then
|
||||||
@@ -124,25 +182,92 @@ function ADSmartPickup.findLeastFilledPickup(adEnv, driver)
|
|||||||
if farmId == nil or #fillTypes == 0 then
|
if farmId == nil or #fillTypes == 0 then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
local groupMarkers = adEnv.ADGraphManager:getMapMarkersInGroup(firstMarker.group)
|
||||||
-- siloer med varen i EGET lager går altid forud for dem der kun kan låne fra naboer
|
return findLeastFilled(collectMarkerStations(adEnv, groupMarkers, farmId, nil), fillTypes, farmId)
|
||||||
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
|
end
|
||||||
|
|
||||||
local choice = best[true].marker ~= nil and best[true] or best[false]
|
-- Niveau + kapacitet pr. ingrediens i aflæsningsmålets lager.
|
||||||
if choice.marker == nil then
|
local function getTargetNeeds(station, ingredients, farmId)
|
||||||
|
local needs = {}
|
||||||
|
for _, fillType in ipairs(ingredients) do
|
||||||
|
if station.getIsFillTypeSupported == nil or station:getIsFillTypeSupported(fillType) then
|
||||||
|
local level, freeSpace = 0, 0
|
||||||
|
for _, storage in pairs(station.targetStorages or {}) do
|
||||||
|
level = level + (storage:getFillLevel(fillType) or 0)
|
||||||
|
freeSpace = freeSpace + (storage:getFreeCapacity(fillType) or 0)
|
||||||
|
end
|
||||||
|
if level + freeSpace <= 0 and station.getFreeCapacity ~= nil then
|
||||||
|
-- ukendt lagertype: brug stationens egen opgørelse af fri plads
|
||||||
|
level, freeSpace = 0, station:getFreeCapacity(fillType, farmId) or 0
|
||||||
|
end
|
||||||
|
table.insert(needs, {fillType = fillType, level = level, capacity = level + freeSpace})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return needs
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Behovsstyret: den ingrediens aflæsningsmålet mangler mest, som også findes i en silo.
|
||||||
|
function ADSmartPickup.findSupplyPickup(adEnv, driver, ingredients)
|
||||||
|
local secondMarker = driver.ad.stateModule:getSecondMarker()
|
||||||
|
local farmId = getFarmId(driver)
|
||||||
|
if secondMarker == nil or farmId == nil then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
return choice.marker.markerIndex, choice.marker.name, choice.level
|
local targetWayPoint = adEnv.ADGraphManager:getWayPointById(secondMarker.id)
|
||||||
|
local target = targetWayPoint ~= nil and getUnloadStationAtMarker(targetWayPoint, farmId) or nil
|
||||||
|
if target == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local allMarkers = adEnv.ADGraphManager:getMapMarkers()
|
||||||
|
local markerStations = collectMarkerStations(adEnv, allMarkers, farmId, target.owningPlaceable)
|
||||||
|
for _, need in ipairs(ADSupplyPlanner.rankNeeds(getTargetNeeds(target, ingredients, farmId))) do
|
||||||
|
local choice = findLeastFilled(markerStations, {need.fillType}, farmId)
|
||||||
|
if choice ~= nil then
|
||||||
|
return {
|
||||||
|
markerIndex = choice.markerIndex,
|
||||||
|
markerName = choice.markerName,
|
||||||
|
group = choice.group,
|
||||||
|
level = choice.level,
|
||||||
|
fillType = need.fillType,
|
||||||
|
need = need,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function applySupplyChoice(driver, ingredients, choice)
|
||||||
|
local stateModule = driver.ad.stateModule
|
||||||
|
stateModule:setFillType(choice.fillType)
|
||||||
|
stateModule.selectedFillTypes = {choice.fillType}
|
||||||
|
stateModule:raiseDirtyFlag()
|
||||||
|
supplyState[driver] = {ingredients = ingredients, narrowedTo = choice.fillType}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Samlet valg: behovsstyret når flere varer er valgt, ellers mappe-logik. nil = AutoDrives original.
|
||||||
|
function ADSmartPickup.choosePickup(adEnv, driver)
|
||||||
|
local selected = driver.ad.stateModule:getSelectedFillTypes()
|
||||||
|
local ingredients = ADSupplyPlanner.resolveIngredients(supplyState[driver], selected)
|
||||||
|
if ingredients == nil then
|
||||||
|
supplyState[driver] = nil
|
||||||
|
return ADSmartPickup.findLeastFilledPickup(adEnv, driver)
|
||||||
|
end
|
||||||
|
|
||||||
|
local choice = ADSmartPickup.findSupplyPickup(adEnv, driver, ingredients)
|
||||||
|
if choice == nil then
|
||||||
|
return ADSmartPickup.findLeastFilledPickup(adEnv, driver)
|
||||||
|
end
|
||||||
|
applySupplyChoice(driver, ingredients, choice)
|
||||||
|
return choice
|
||||||
|
end
|
||||||
|
|
||||||
|
local function describeChoice(choice)
|
||||||
|
if choice.fillType == nil then
|
||||||
|
return string.format("%.0f l", choice.level)
|
||||||
|
end
|
||||||
|
local fillTypeName = g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeNameByIndex(choice.fillType) or tostring(choice.fillType)
|
||||||
|
return string.format("%s: kilde %.0f l, målet har %.0f/%.0f l", tostring(fillTypeName), choice.level, choice.need.level, choice.need.capacity)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function installHook(adEnv)
|
local function installHook(adEnv)
|
||||||
@@ -150,37 +275,58 @@ local function installHook(adEnv)
|
|||||||
local originalGetNextPickup = manager.getNextPickup
|
local originalGetNextPickup = manager.getNextPickup
|
||||||
|
|
||||||
manager.getNextPickup = function(self, driver, forcedSkip)
|
manager.getNextPickup = function(self, driver, forcedSkip)
|
||||||
local ok, markerIndex, markerName, level = pcall(ADSmartPickup.findLeastFilledPickup, adEnv, driver)
|
local ok, choice = pcall(ADSmartPickup.choosePickup, adEnv, driver)
|
||||||
if not ok then
|
if not ok then
|
||||||
Logging.warning(ADSmartPickup.LOG_PREFIX .. "fejl i silo-opslag, bruger AutoDrives rotation: %s", tostring(markerIndex))
|
Logging.warning(ADSmartPickup.LOG_PREFIX .. "fejl i silo-opslag, bruger AutoDrives rotation: %s", tostring(choice))
|
||||||
return originalGetNextPickup(self, driver, forcedSkip)
|
return originalGetNextPickup(self, driver, forcedSkip)
|
||||||
end
|
end
|
||||||
if markerIndex == nil then
|
if choice == nil then
|
||||||
return originalGetNextPickup(self, driver, forcedSkip)
|
return originalGetNextPickup(self, driver, forcedSkip)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- hold AutoDrives egen rotations-tilstand konsistent med vores valg
|
-- hold AutoDrives egen rotations-tilstand konsistent med vores valg
|
||||||
local groupName = driver.ad.stateModule:getFirstMarker().group
|
self.pickups[choice.group] = self.pickups[choice.group] or {}
|
||||||
self.pickups[groupName] = self.pickups[groupName] or {}
|
self.pickups[choice.group].lastTarget = choice.markerIndex
|
||||||
self.pickups[groupName].lastTarget = markerIndex
|
self.pickups[choice.group].lastVehicle = driver
|
||||||
self.pickups[groupName].lastVehicle = driver
|
|
||||||
|
|
||||||
Logging.info(ADSmartPickup.LOG_PREFIX .. "'%s' -> %s (%.0f l)", tostring(driver:getName()), tostring(markerName), level)
|
Logging.info(ADSmartPickup.LOG_PREFIX .. "'%s' -> %s (%s)", tostring(driver:getName()), tostring(choice.markerName), describeChoice(choice))
|
||||||
return markerIndex
|
return choice.markerIndex
|
||||||
end
|
end
|
||||||
|
|
||||||
Logging.info(ADSmartPickup.LOG_PREFIX .. "aktiv (mindst fyldte silo med varen vælges i mappe-rotation)")
|
Logging.info(ADSmartPickup.LOG_PREFIX .. "aktiv (mindst fyldte silo med varen; flere valgte varer = behovsstyret forsyning)")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Når AutoDrive stopper, får køretøjet hele ingredienslisten tilbage i varevalget
|
||||||
|
-- (AutoDrive gemmer valget i savegame, så listen overlever også en genindlæsning).
|
||||||
|
local function restoreStoppedVehicles()
|
||||||
|
for vehicle, state in pairs(supplyState) do
|
||||||
|
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
||||||
|
if stateModule == nil or not stateModule:isActive() then
|
||||||
|
supplyState[vehicle] = nil
|
||||||
|
local selected = stateModule ~= nil and stateModule:getSelectedFillTypes() or nil
|
||||||
|
if type(selected) == "table" and #selected == 1 and selected[1] == state.narrowedTo then
|
||||||
|
stateModule.selectedFillTypes = ADSupplyPlanner.copyList(state.ingredients)
|
||||||
|
stateModule:raiseDirtyFlag()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function ADSmartPickup:update(dt)
|
function ADSmartPickup:update(dt)
|
||||||
if isHooked then
|
if not isHooked then
|
||||||
return
|
|
||||||
end
|
|
||||||
local adEnv = getAutoDriveEnv()
|
local adEnv = getAutoDriveEnv()
|
||||||
if adEnv ~= nil and g_currentMission ~= nil and g_currentMission.storageSystem ~= nil then
|
if adEnv ~= nil and g_currentMission ~= nil and g_currentMission.storageSystem ~= nil then
|
||||||
installHook(adEnv)
|
installHook(adEnv)
|
||||||
isHooked = true
|
isHooked = true
|
||||||
end
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, err = pcall(restoreStoppedVehicles)
|
||||||
|
if not ok then
|
||||||
|
Logging.warning(ADSmartPickup.LOG_PREFIX .. "kunne ikke gendanne varevalg: %s", tostring(err))
|
||||||
|
supplyState = setmetatable({}, {__mode = "k"})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
addModEventListener(ADSmartPickup)
|
addModEventListener(ADSmartPickup)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
-- Mock'er de få FS25-/AutoDrive-API'er moden rører og tester valg-logikken.
|
-- Mock'er de få FS25-/AutoDrive-API'er moden rører og tester valg-logikken.
|
||||||
|
|
||||||
local SUGARBEET, WHEAT = 10, 11
|
local SUGARBEET, WHEAT = 10, 11
|
||||||
|
local HAY, GRASS, SILAGE = 20, 21, 22
|
||||||
local logLines = {}
|
local logLines = {}
|
||||||
|
|
||||||
-- ---- FS25-mocks ----
|
-- ---- FS25-mocks ----
|
||||||
@@ -31,9 +32,30 @@ local function makeStation(node, x, z, levels, isBroken, ownLevels)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
local stations = {}
|
-- Aflæsningsmål (fx TMR-blander): ét lager med niveau + kapacitet pr. vare.
|
||||||
|
local function makeUnloadStation(node, x, z, levels, capacities, placeable, isBroken)
|
||||||
|
nodePositions[node] = {x = x, z = z}
|
||||||
|
local storage = {
|
||||||
|
getFillLevel = function(_, fillType)
|
||||||
|
if isBroken then error("boom") end
|
||||||
|
return levels[fillType] or 0
|
||||||
|
end,
|
||||||
|
getFreeCapacity = function(_, fillType) return (capacities[fillType] or 0) - (levels[fillType] or 0) end,
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
owningPlaceable = placeable,
|
||||||
|
unloadTriggers = {{exactFillRootNode = node}},
|
||||||
|
targetStorages = {[storage] = storage},
|
||||||
|
getIsFillTypeSupported = function(_, fillType) return capacities[fillType] ~= nil end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local stations, unloadStations = {}, {}
|
||||||
g_currentMission = {
|
g_currentMission = {
|
||||||
storageSystem = {getLoadingStations = function() return stations end},
|
storageSystem = {
|
||||||
|
getLoadingStations = function() return stations end,
|
||||||
|
getUnloadingStations = function() return unloadStations end,
|
||||||
|
},
|
||||||
accessHandler = {canFarmAccess = function() return true end},
|
accessHandler = {canFarmAccess = function() return true end},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +67,7 @@ local function addMarker(index, name, group, x, z)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function resetWorld()
|
local function resetWorld()
|
||||||
stations, markers, wayPoints, nodePositions, logLines = {}, {}, {}, {}, {}
|
stations, unloadStations, markers, wayPoints, nodePositions, logLines = {}, {}, {}, {}, {}, {}
|
||||||
FS25_AutoDrive.ADMultipleTargetsManager.pickups = {}
|
FS25_AutoDrive.ADMultipleTargetsManager.pickups = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -58,6 +80,7 @@ FS25_AutoDrive = {
|
|||||||
return result
|
return result
|
||||||
end,
|
end,
|
||||||
getWayPointById = function(_, id) return wayPoints[id] end,
|
getWayPointById = function(_, id) return wayPoints[id] end,
|
||||||
|
getMapMarkers = function() return markers end,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,12 +90,31 @@ local function makeDriver(firstMarker, fillTypes)
|
|||||||
getOwnerFarmId = function() return 1 end,
|
getOwnerFarmId = function() return 1 end,
|
||||||
ad = {stateModule = {
|
ad = {stateModule = {
|
||||||
getFirstMarker = function() return firstMarker end,
|
getFirstMarker = function() return firstMarker end,
|
||||||
|
getSecondMarker = function() return nil end,
|
||||||
getSelectedFillTypes = function() return fillTypes end,
|
getSelectedFillTypes = function() return fillTypes end,
|
||||||
getFillType = function() return fillTypes[1] end,
|
getFillType = function() return fillTypes[1] end,
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Muterbar stateModule-mock: behovsstyringen skifter vare og indsnævrer AD's valg.
|
||||||
|
local function makeSupplyDriver(firstMarker, secondMarker, fillTypes)
|
||||||
|
local stateModule = {selectedFillTypes = fillTypes, fillType = fillTypes[1], active = true, dirtyCount = 0}
|
||||||
|
stateModule.getFirstMarker = function() return firstMarker end
|
||||||
|
stateModule.getSecondMarker = function() return secondMarker end
|
||||||
|
stateModule.getSelectedFillTypes = function(self) return self.selectedFillTypes end
|
||||||
|
stateModule.getFillType = function(self) return self.fillType end
|
||||||
|
stateModule.setFillType = function(self, fillType) self.fillType = fillType end
|
||||||
|
stateModule.raiseDirtyFlag = function(self) self.dirtyCount = self.dirtyCount + 1 end
|
||||||
|
stateModule.isActive = function(self) return self.active end
|
||||||
|
return {
|
||||||
|
getName = function() return "Fodertraktor" end,
|
||||||
|
getOwnerFarmId = function() return 1 end,
|
||||||
|
ad = {stateModule = stateModule},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
dofile("FS25_ADSmartPickup/adSupplyPlanner.lua")
|
||||||
dofile("FS25_ADSmartPickup/adSmartPickup.lua")
|
dofile("FS25_ADSmartPickup/adSmartPickup.lua")
|
||||||
listener:update(16) -- installerer hook'en
|
listener:update(16) -- installerer hook'en
|
||||||
local manager = FS25_AutoDrive.ADMultipleTargetsManager
|
local manager = FS25_AutoDrive.ADMultipleTargetsManager
|
||||||
@@ -134,5 +176,83 @@ check("fejl logges som advarsel", logLines[#logLines]:sub(1, 4), "WARN")
|
|||||||
check("gruppe All -> fallback", manager:getNextPickup(makeDriver({group = "All", markerIndex = 1}, {SUGARBEET}), false), "ORIGINAL")
|
check("gruppe All -> fallback", manager:getNextPickup(makeDriver({group = "All", markerIndex = 1}, {SUGARBEET}), false), "ORIGINAL")
|
||||||
check("ingen første markør -> fallback", manager:getNextPickup(makeDriver(nil, {SUGARBEET}), false), "ORIGINAL")
|
check("ingen første markør -> fallback", manager:getNextPickup(makeDriver(nil, {SUGARBEET}), false), "ORIGINAL")
|
||||||
|
|
||||||
|
-- ==== Behovsstyret forsyning (flere varer valgt + aflæsningsmål med lager) ====
|
||||||
|
local BLANDER = {name = "TMR-blander placeable"}
|
||||||
|
local FULL = {[HAY] = 100000, [GRASS] = 100000, [SILAGE] = 100000}
|
||||||
|
local blanderLevels
|
||||||
|
|
||||||
|
local function buildFeedWorld()
|
||||||
|
resetWorld()
|
||||||
|
blanderLevels = {[HAY] = 80000, [GRASS] = 20000, [SILAGE] = 50000}
|
||||||
|
addMarker(1, "Hølade", "Lader", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[HAY] = 50000}))
|
||||||
|
addMarker(2, "Græssilo stor", "Siloer", 500, 0); table.insert(stations, makeStation(102, 500, 15, {[GRASS] = 300000}))
|
||||||
|
addMarker(3, "Græssilo lille", "Andet", 1000, 0);table.insert(stations, makeStation(103, 1000, 15, {[GRASS] = 40000}))
|
||||||
|
addMarker(4, "Ensilagesilo", "Siloer", 1500, 0); table.insert(stations, makeStation(104, 1500, 15, {[SILAGE] = 200000}))
|
||||||
|
addMarker(10, "TMR-blander", "Produktion", 5000, 0)
|
||||||
|
table.insert(unloadStations, makeUnloadStation(110, 5000, 10, blanderLevels, FULL, BLANDER))
|
||||||
|
end
|
||||||
|
local function markerByIndex(index)
|
||||||
|
for _, m in ipairs(markers) do if m.markerIndex == index then return m end end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- S1: mest trængende ingrediens vælges, kilde findes på tværs af mapper, AD's vare skiftes
|
||||||
|
buildFeedWorld()
|
||||||
|
local feeder = makeSupplyDriver(markerByIndex(1), markerByIndex(10), {HAY, GRASS, SILAGE})
|
||||||
|
check("S1 mest trængende vare, mindst fyldte kilde", manager:getNextPickup(feeder, false), 3)
|
||||||
|
check("S1 AD's vare skiftet til græs", feeder.ad.stateModule.fillType, GRASS)
|
||||||
|
check("S1 AD's valg indsnævret til én vare", #feeder.ad.stateModule.selectedFillTypes, 1)
|
||||||
|
check("S1 ændring synkroniseres (dirty-flag)", feeder.ad.stateModule.dirtyCount > 0, true)
|
||||||
|
check("S1 rotationstilstand følger valgt markørs mappe", manager.pickups["Andet"].lastTarget, 3)
|
||||||
|
|
||||||
|
-- S2: næste tur — gemt liste genbruges; græs er nu fuldt og springes over -> ensilage
|
||||||
|
blanderLevels[GRASS] = 99500
|
||||||
|
check("S2 fuld ingrediens springes over", manager:getNextPickup(feeder, false), 4)
|
||||||
|
check("S2 vare skiftet til ensilage", feeder.ad.stateModule.fillType, SILAGE)
|
||||||
|
|
||||||
|
-- S3: ingrediens uden kilde springes over -> næste i behovs-rækkefølgen
|
||||||
|
blanderLevels[SILAGE] = 10000
|
||||||
|
stations[4] = makeStation(104, 1500, 15, {[SILAGE] = 0})
|
||||||
|
check("S3 ingen kilde -> næste ingrediens", manager:getNextPickup(feeder, false), 1)
|
||||||
|
check("S3 vare skiftet til hø", feeder.ad.stateModule.fillType, HAY)
|
||||||
|
|
||||||
|
-- S4: AD stopper -> hele ingredienslisten gendannes i AD's varevalg
|
||||||
|
feeder.ad.stateModule.active = false
|
||||||
|
listener:update(16)
|
||||||
|
check("S4 valg gendannet ved stop", #feeder.ad.stateModule.selectedFillTypes, 3)
|
||||||
|
|
||||||
|
-- S5: aflæsningsmålets egen læssestation bruges ikke som kilde
|
||||||
|
buildFeedWorld()
|
||||||
|
addMarker(11, "Blander udtag", "Produktion", 5030, 0)
|
||||||
|
table.insert(stations, makeStation(111, 5030, 15, {[GRASS] = 10}, false, nil))
|
||||||
|
stations[#stations].owningPlaceable = BLANDER
|
||||||
|
feeder = makeSupplyDriver(markerByIndex(1), markerByIndex(10), {HAY, GRASS})
|
||||||
|
check("S5 målets egen station udelukkes som kilde", manager:getNextPickup(feeder, false), 3)
|
||||||
|
|
||||||
|
-- S6: brugeren vælger selv en anden enkelt vare -> behovsstyring fra (mappe-logik)
|
||||||
|
feeder.ad.stateModule.selectedFillTypes = {HAY}
|
||||||
|
feeder.ad.stateModule.fillType = HAY
|
||||||
|
check("S6 brugerens enkelt-valg -> mappe-logik", manager:getNextPickup(feeder, false), 1)
|
||||||
|
check("S6 vare røres ikke", feeder.ad.stateModule.fillType, HAY)
|
||||||
|
|
||||||
|
-- S7: intet behov (alt fuldt) -> mappe-logik, valg indsnævres ikke
|
||||||
|
buildFeedWorld()
|
||||||
|
blanderLevels[HAY], blanderLevels[GRASS], blanderLevels[SILAGE] = 99900, 99900, 99900
|
||||||
|
feeder = makeSupplyDriver(markerByIndex(1), markerByIndex(10), {HAY, GRASS, SILAGE})
|
||||||
|
check("S7 intet behov -> mappe-logik", manager:getNextPickup(feeder, false), 1)
|
||||||
|
check("S7 valg ikke indsnævret", #feeder.ad.stateModule.selectedFillTypes, 3)
|
||||||
|
|
||||||
|
-- S8: ingen aflæsningsstation ved målet -> mappe-logik
|
||||||
|
buildFeedWorld()
|
||||||
|
unloadStations = {}
|
||||||
|
feeder = makeSupplyDriver(markerByIndex(2), markerByIndex(10), {GRASS, SILAGE})
|
||||||
|
check("S8 ingen station ved mål -> mappe-logik", manager:getNextPickup(feeder, false), 4)
|
||||||
|
|
||||||
|
-- S9: fejl i målets lager-API -> AutoDrives original + advarsel
|
||||||
|
buildFeedWorld()
|
||||||
|
unloadStations = {makeUnloadStation(110, 5000, 10, blanderLevels, FULL, BLANDER, true)}
|
||||||
|
feeder = makeSupplyDriver(markerByIndex(1), markerByIndex(10), {HAY, GRASS})
|
||||||
|
check("S9 fejl giver fallback", manager:getNextPickup(feeder, false), "ORIGINAL")
|
||||||
|
check("S9 fejl logges som advarsel", logLines[#logLines]:sub(1, 4), "WARN")
|
||||||
|
|
||||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||||
os.exit(failures == 0 and 0 or 1)
|
os.exit(failures == 0 and 0 or 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user