fix: en fabriks inputs stjæles aldrig som kilde; stalde læses via getHusbandry* (v1.6.1.0)
In-game 2026-09-22: fire Fastrac'er med halm+TMR til to kostalde kørte med 'Rotér mål: kun aflæsning' og fast pålæsning ved 'TMR Load' -> AutoDrive læssede halm (fabrikkens input) dér uden at modden var i spil. Med modden i spil ville 'mindst fyldte først' have valgt samme lager. - ADSupplyPlanner.isProductionInput: vare der er input (og ikke output) i en fabriks produktioner udelukkes som kilde i findBestSource (mappe + behov). - Stald som mål: niveau/kapacitet/understøttelse fra placeable:getHusbandry* (stationen selv siger nej til halm) -> halm rangeres og hentes. - README: to stalde = kun pålæsnings-rotation (AD vælger pickup FØR deliver roteres).
This commit is contained in:
@@ -206,6 +206,30 @@ local function getFillTypeNames(fillTypes)
|
|||||||
return names
|
return names
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function getProductionPoint(station)
|
||||||
|
local placeable = station.owningPlaceable
|
||||||
|
if placeable ~= nil and placeable.spec_productionPoint ~= nil then
|
||||||
|
return placeable.spec_productionPoint.productionPoint
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- En fabriks læssestation udleverer også dens INPUTS (fx halm i en TMR-fabrik).
|
||||||
|
-- Dem stjæler vi aldrig — fabrikken ville løbe tør for det, den skal bruge.
|
||||||
|
-- Outputs (og varer der både er input og output i en kæde) må gerne hentes.
|
||||||
|
local function isProtectedProductionInput(station, fillTypes)
|
||||||
|
local productionPoint = getProductionPoint(station)
|
||||||
|
if productionPoint == nil or productionPoint.productions == nil then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
for _, fillType in ipairs(fillTypes) do
|
||||||
|
if ADSupplyPlanner.isProductionInput(productionPoint.productions, fillType) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
-- Bedste kilde der har varen: mindst fyldte først for lagervarer, fyldeste først for
|
-- Bedste kilde der har varen: mindst fyldte først for lagervarer, fyldeste først for
|
||||||
-- varer hvis tanke løber fulde (ADSupplyPlanner). Siloer med varen i EGET lager går
|
-- varer hvis tanke løber fulde (ADSupplyPlanner). Siloer med varen i EGET lager går
|
||||||
-- altid forud for dem der kun kan låne fra naboer. Returnerer et valg eller nil.
|
-- altid forud for dem der kun kan låne fra naboer. Returnerer et valg eller nil.
|
||||||
@@ -214,7 +238,8 @@ local function findBestSource(markerStations, fillTypes, farmId)
|
|||||||
local best = {[true] = {}, [false] = {}}
|
local best = {[true] = {}, [false] = {}}
|
||||||
for _, entry in ipairs(markerStations) do
|
for _, entry in ipairs(markerStations) do
|
||||||
local level, isOwn = getStationFillLevel(entry.station, fillTypes, farmId)
|
local level, isOwn = getStationFillLevel(entry.station, fillTypes, farmId)
|
||||||
if level >= ADSmartPickup.MIN_USEFUL_LITERS and ADSupplyPlanner.isBetterSource(level, best[isOwn].level, prefersFullest) then
|
if level >= ADSmartPickup.MIN_USEFUL_LITERS and not isProtectedProductionInput(entry.station, fillTypes)
|
||||||
|
and ADSupplyPlanner.isBetterSource(level, best[isOwn].level, prefersFullest) then
|
||||||
best[isOwn] = {level = level, marker = entry.marker, station = entry.station, isOwn = isOwn}
|
best[isOwn] = {level = level, marker = entry.marker, station = entry.station, isOwn = isOwn}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -250,14 +275,6 @@ function ADSmartPickup.findLeastFilledPickup(adEnv, driver)
|
|||||||
return findBestSource(collectMarkerStations(adEnv, groupMarkers, farmId, nil), fillTypes, farmId)
|
return findBestSource(collectMarkerStations(adEnv, groupMarkers, farmId, nil), fillTypes, farmId)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getProductionPoint(station)
|
|
||||||
local placeable = station.owningPlaceable
|
|
||||||
if placeable ~= nil and placeable.spec_productionPoint ~= nil then
|
|
||||||
return placeable.spec_productionPoint.productionPoint
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Lagrene bag aflæsningsstationen; produktionspunkter uden targetStorages læses direkte.
|
-- Lagrene bag aflæsningsstationen; produktionspunkter uden targetStorages læses direkte.
|
||||||
local function getTargetStorages(station)
|
local function getTargetStorages(station)
|
||||||
if station.targetStorages ~= nil and next(station.targetStorages) ~= nil then
|
if station.targetStorages ~= nil and next(station.targetStorages) ~= nil then
|
||||||
@@ -323,12 +340,33 @@ local function logFactoryFallbackOnce(driver, station)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Stalde (kostald m.fl.) holder foder/halm/vand i egne lagre bag én aflæsningsstation,
|
||||||
|
-- der ikke nødvendigvis siger ja til alle varer. Stalden selv svarer for alle.
|
||||||
|
local function getHusbandry(station)
|
||||||
|
local placeable = station.owningPlaceable
|
||||||
|
if placeable ~= nil and placeable.getHusbandryFillLevel ~= nil and placeable.getHusbandryCapacity ~= nil then
|
||||||
|
return placeable
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isFillTypeAccepted(station, husbandry, fillType)
|
||||||
|
if husbandry ~= nil and husbandry.getHusbandryIsFillTypeSupported ~= nil then
|
||||||
|
return husbandry:getHusbandryIsFillTypeSupported(fillType)
|
||||||
|
end
|
||||||
|
return station.getIsFillTypeSupported == nil or station:getIsFillTypeSupported(fillType)
|
||||||
|
end
|
||||||
|
|
||||||
-- Niveau + kapacitet pr. ingrediens i aflæsningsmålets lager; demand giver forbrug/min.
|
-- Niveau + kapacitet pr. ingrediens i aflæsningsmålets lager; demand giver forbrug/min.
|
||||||
local function getTargetNeeds(station, ingredients, farmId, demand)
|
local function getTargetNeeds(station, ingredients, farmId, demand)
|
||||||
local needs = {}
|
local needs = {}
|
||||||
local storages = getTargetStorages(station)
|
local storages = getTargetStorages(station)
|
||||||
|
local husbandry = getHusbandry(station)
|
||||||
for _, fillType in ipairs(ingredients) do
|
for _, fillType in ipairs(ingredients) do
|
||||||
if station.getIsFillTypeSupported == nil or station:getIsFillTypeSupported(fillType) then
|
if husbandry ~= nil and isFillTypeAccepted(station, husbandry, fillType) then
|
||||||
|
local level = husbandry:getHusbandryFillLevel(fillType) or 0
|
||||||
|
table.insert(needs, {fillType = fillType, level = level, capacity = husbandry:getHusbandryCapacity(fillType) or level, litersPerMinute = demand[fillType]})
|
||||||
|
elseif isFillTypeAccepted(station, husbandry, fillType) then
|
||||||
local level, freeSpace = 0, 0
|
local level, freeSpace = 0, 0
|
||||||
for _, storage in pairs(storages) do
|
for _, storage in pairs(storages) do
|
||||||
level = level + (storage:getFillLevel(fillType) or 0)
|
level = level + (storage:getFillLevel(fillType) or 0)
|
||||||
|
|||||||
@@ -76,6 +76,21 @@ function ADSupplyPlanner.collectProductionDemand(productions, isWanted, canCarry
|
|||||||
return demand
|
return demand
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Er varen et INPUT til fabrikken (og ikke også et output)? Så må den aldrig
|
||||||
|
-- hentes derfra — det ville tømme fabrikken for det, den skal bruge.
|
||||||
|
function ADSupplyPlanner.isProductionInput(productions, fillType)
|
||||||
|
local isInput, isOutput = false, false
|
||||||
|
for _, production in ipairs(productions) do
|
||||||
|
for _, input in ipairs(production.inputs or {}) do
|
||||||
|
if input.type == fillType then isInput = true end
|
||||||
|
end
|
||||||
|
for _, output in ipairs(production.outputs or {}) do
|
||||||
|
if output.type == fillType then isOutput = true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return isInput and not isOutput
|
||||||
|
end
|
||||||
|
|
||||||
-- Minutter før målets lager af varen er tomt ved nuværende forbrug; nil uden forbrug.
|
-- Minutter før målets lager af varen er tomt ved nuværende forbrug; nil uden forbrug.
|
||||||
function ADSupplyPlanner.minutesToEmpty(level, litersPerMinute)
|
function ADSupplyPlanner.minutesToEmpty(level, litersPerMinute)
|
||||||
if litersPerMinute == nil or litersPerMinute <= 0 then
|
if litersPerMinute == nil or litersPerMinute <= 0 then
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
<modDesc descVersion="107">
|
<modDesc descVersion="107">
|
||||||
<author>masterdraco</author>
|
<author>masterdraco</author>
|
||||||
<version>1.6.0.0</version>
|
<version>1.6.1.0</version>
|
||||||
<title>
|
<title>
|
||||||
<en>AD Smart Pickup</en>
|
<en>AD Smart Pickup</en>
|
||||||
</title>
|
</title>
|
||||||
|
|||||||
@@ -38,6 +38,22 @@ tændknappen (og det gendannes ved stop). Listen genudledes hver tur, begrænset
|
|||||||
den vare der løber tør først hentes først, uanset relativ fyldning. Loggen viser forbrug og minutter.
|
den vare der løber tør først hentes først, uanset relativ fyldning. Loggen viser forbrug og minutter.
|
||||||
- Fabrik uden ønskede produktioner, eller inputs vognen ikke kan bære → dit multi-valg som hidtil.
|
- Fabrik uden ønskede produktioner, eller inputs vognen ikke kan bære → dit multi-valg som hidtil.
|
||||||
|
|
||||||
|
### Kilder: en fabriks inputs stjæles aldrig (v1.6.1)
|
||||||
|
|
||||||
|
En fabriks læssestation udleverer også dens **inputs** (halm i en TMR-fabrik). Sådan en vare hentes
|
||||||
|
aldrig derfra — kun fabrikkens outputs og rigtige siloer er kilder. Gælder både mappe-logik og
|
||||||
|
behovsstyring. Varer der både er input og output (kæde) må hentes.
|
||||||
|
|
||||||
|
### Stalde som mål (v1.6.1)
|
||||||
|
|
||||||
|
Kostald m.fl. svarer selv for niveau/kapacitet (`getHusbandryFillLevel/Capacity`), så halm+TMR til en
|
||||||
|
stald rangeres korrekt, selv om staldens aflæsningsstation kun "kender" foder.
|
||||||
|
|
||||||
|
**Opsætning med flere stalde:** én traktor (eller flere) pr. stald med *Rotér mål: kun pålæsning*.
|
||||||
|
Brug IKKE "pålæsning og aflæsning": AutoDrive vælger næste pålæsning FØR den roterer aflæsningsmålet,
|
||||||
|
så behovet ville blive regnet for den forkerte stald. Pålæsningsmarkøren må ikke være fast på en
|
||||||
|
fabrik ("kun aflæsning") — så kalder AutoDrive aldrig modden, og alt valgt læsses dér.
|
||||||
|
|
||||||
## Fælles
|
## Fælles
|
||||||
|
|
||||||
- Wrapper `ADMultipleTargetsManager.getNextPickup` (via den globale `FS25_AutoDrive`-tabel); rører ikke AutoDrive-zip'en.
|
- Wrapper `ADMultipleTargetsManager.getNextPickup` (via den globale `FS25_AutoDrive`-tabel); rører ikke AutoDrive-zip'en.
|
||||||
|
|||||||
@@ -355,6 +355,68 @@ fallbackPp.getIsProductionEnabled = nil
|
|||||||
fallbackPp.activeProductions = {fallbackPp.productions[1]}
|
fallbackPp.activeProductions = {fallbackPp.productions[1]}
|
||||||
check("F6 activeProductions som fallback", manager:getNextPickup(fallbackDriver, false), 4)
|
check("F6 activeProductions som fallback", manager:getNextPickup(fallbackDriver, false), 4)
|
||||||
|
|
||||||
|
-- ==== En fabriks inputs stjæles aldrig (halm+TMR til kostald; TMR-fabrikken har halm som input) ====
|
||||||
|
local STRAW, TMR = 41, 42
|
||||||
|
FILL_TYPE_NAMES[STRAW], FILL_TYPE_NAMES[TMR] = "STRAW", "FORAGE"
|
||||||
|
local cowLevels
|
||||||
|
local function buildCowWorld()
|
||||||
|
resetWorld()
|
||||||
|
local tmrFactory = {
|
||||||
|
getName = function() return "TMR-fabrik" end,
|
||||||
|
spec_productionPoint = {productionPoint = {productions = {
|
||||||
|
{id = "tmr", cyclesPerMinute = 1, inputs = {{type = STRAW, amount = 10}, {type = SILAGE, amount = 100}}, outputs = {{type = TMR, amount = 50}}},
|
||||||
|
}}},
|
||||||
|
}
|
||||||
|
-- fabrikkens læssestation har BÅDE halm (input, mindst fyldt) og TMR (output)
|
||||||
|
addMarker(20, "TMR Load", "Fabrication", 0, 0)
|
||||||
|
table.insert(stations, makeStation(120, 0, 15, {[STRAW] = 5000, [TMR] = 30000}))
|
||||||
|
stations[#stations].owningPlaceable = tmrFactory
|
||||||
|
addMarker(21, "Halmlade", "Lader", 500, 0)
|
||||||
|
table.insert(stations, makeStation(121, 500, 15, {[STRAW] = 50000}))
|
||||||
|
addMarker(30, "Cow 1 Food", "Cow Food", 5000, 0)
|
||||||
|
cowLevels = {[STRAW] = 0, [TMR] = 40000}
|
||||||
|
table.insert(unloadStations, makeUnloadStation(130, 5000, 10, cowLevels, {[STRAW] = 100000, [TMR] = 100000}, {name = "kostald"}))
|
||||||
|
local driver = makeSupplyDriver(markerByIndex(21), markerByIndex(30), {TMR, STRAW})
|
||||||
|
driver.carriable = {TMR, STRAW}
|
||||||
|
return driver
|
||||||
|
end
|
||||||
|
|
||||||
|
-- P1: halm mangler mest -> hentes fra halmladen, IKKE fra fabrikken (selv om den er mindst fyldt)
|
||||||
|
local cowDriver = buildCowWorld()
|
||||||
|
check("P1 halm hentes fra siloen, ikke fabrikkens input", manager:getNextPickup(cowDriver, false), 21)
|
||||||
|
|
||||||
|
-- P2: halm fuld -> TMR hentes gerne fra fabrikken (det er dens output)
|
||||||
|
cowLevels[STRAW] = 99500
|
||||||
|
check("P2 fabrikkens output må hentes", manager:getNextPickup(cowDriver, false), 20)
|
||||||
|
|
||||||
|
-- P3: mappe-logik (én vare) beskytter også fabrikkens input
|
||||||
|
cowDriver = buildCowWorld()
|
||||||
|
addMarker(22, "Halmlade 2", "Fabrication", 1000, 0)
|
||||||
|
table.insert(stations, makeStation(122, 1000, 15, {[STRAW] = 60000}))
|
||||||
|
local strawDriver = makeDriver(markerByIndex(20), {STRAW})
|
||||||
|
check("P3 mappe-logik springer fabrikkens input over", manager:getNextPickup(strawDriver, false), 22)
|
||||||
|
|
||||||
|
-- P4: stald som mål — niveau/kapacitet læses fra stalden selv (getHusbandry*), ikke fra
|
||||||
|
-- stationens lagre; stationen selv "understøtter" kun foder, men stalden tager også halm
|
||||||
|
cowDriver = buildCowWorld()
|
||||||
|
local barnLevels = {[STRAW] = 45000, [TMR] = 10000}
|
||||||
|
local barnCapacity = {[STRAW] = 50000, [TMR] = 100000}
|
||||||
|
local barn = {
|
||||||
|
getName = function() return "Kostald" end,
|
||||||
|
getHusbandryFillLevel = function(_, fillType) return barnLevels[fillType] or 0 end,
|
||||||
|
getHusbandryCapacity = function(_, fillType) return barnCapacity[fillType] or 0 end,
|
||||||
|
getHusbandryFreeCapacity = function(_, fillType) return (barnCapacity[fillType] or 0) - (barnLevels[fillType] or 0) end,
|
||||||
|
getHusbandryIsFillTypeSupported = function(_, fillType) return barnCapacity[fillType] ~= nil end,
|
||||||
|
}
|
||||||
|
local barnStation = unloadStations[1]
|
||||||
|
barnStation.targetStorages = nil
|
||||||
|
barnStation.owningPlaceable = barn
|
||||||
|
barnStation.getIsFillTypeSupported = function(_, fillType) return fillType == TMR end
|
||||||
|
barnStation.getFreeCapacity = function(_, fillType) return 999999 end
|
||||||
|
check("P4 stald: TMR (90 % tom) hentes før halm (10 % tom)", manager:getNextPickup(cowDriver, false), 20)
|
||||||
|
barnLevels[TMR] = 95000
|
||||||
|
check("P4 stald: halm hentes når TMR er fuld", manager:getNextPickup(cowDriver, false), 21)
|
||||||
|
|
||||||
-- L1: moden valgte siloen -> AutoDrive skal stå og læsse (rotation skjules under opgaven)
|
-- L1: moden valgte siloen -> AutoDrive skal stå og læsse (rotation skjules under opgaven)
|
||||||
resetWorld()
|
resetWorld()
|
||||||
local loadLevels = {[SUGARBEET] = 5000}
|
local loadLevels = {[SUGARBEET] = 5000}
|
||||||
|
|||||||
@@ -108,5 +108,19 @@ check("fuldt mål springes over trods forbrug", #ADSupplyPlanner.rankNeeds({
|
|||||||
{fillType = STRAW, level = 99500, capacity = 100000, litersPerMinute = 20},
|
{fillType = STRAW, level = 99500, capacity = 100000, litersPerMinute = 20},
|
||||||
}), 0)
|
}), 0)
|
||||||
|
|
||||||
|
-- isProductionInput: en fabriks input må aldrig stjæles som kilde — medmindre
|
||||||
|
-- fabrikken også selv producerer varen (så er den et output, der må hentes)
|
||||||
|
local TMR = 26
|
||||||
|
local factory = {
|
||||||
|
{id = "tmr", inputs = {{type = SILAGE, amount = 100}, {type = STRAW, amount = 10}}, outputs = {{type = TMR, amount = 50}}},
|
||||||
|
{id = "pellets", inputs = {{type = TMR, amount = 10}}, outputs = {{type = HAY, amount = 5}}},
|
||||||
|
}
|
||||||
|
check("input til fabrikken er beskyttet", ADSupplyPlanner.isProductionInput(factory, STRAW), true)
|
||||||
|
check("output må hentes", ADSupplyPlanner.isProductionInput(factory, HAY), false)
|
||||||
|
check("både input og output (kæde) -> må hentes", ADSupplyPlanner.isProductionInput(factory, TMR), false)
|
||||||
|
check("vare fabrikken ikke kender -> ikke beskyttet", ADSupplyPlanner.isProductionInput(factory, WATER), false)
|
||||||
|
check("ingen produktioner -> ikke beskyttet", ADSupplyPlanner.isProductionInput({}, STRAW), false)
|
||||||
|
check("produktion uden inputs/outputs tåles", ADSupplyPlanner.isProductionInput({{id = "x"}}, STRAW), false)
|
||||||
|
|
||||||
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