795c03663c
Er aflæsningsmålet et produktionspunkt, erstattes ingredienslisten af inputs fra ønskede produktioner (tændte, eller auto-slukkede af kortet med status MISSING_INPUTS — Dakotas ExtendedProductionPoint), begrænset til det vognen kan bære. Brugerens multi-valg er kun tændknappen og gendannes ved stop som hidtil. Prioritet: tid til tom = lager / (Σ amount × cyclesPerMinute); varer med kendt forbrug scorer i ]1,2] og går altid forud for relativ tomhed [0,1]. Loggen viser forbrug og minutter; fabrik uden ønskede produktioner logges én gang. API-navne verificeret mod EasyDevControls + Dakota16x ExtendedProductionPoint.
145 lines
5.5 KiB
Lua
145 lines
5.5 KiB
Lua
-- AD Supply Planner
|
|
-- Rene funktioner (ingen spil-API) til behovsstyret forsyning: hvilken ingrediens
|
|
-- har aflæsningsmålet mest brug for, og hvilken ingrediensliste gælder for køretøjet.
|
|
|
|
ADSupplyPlanner = {}
|
|
|
|
-- Mindre fri plads end dette i målet regnes som "fuldt" (undgår ture med læs der ikke kan komme af).
|
|
ADSupplyPlanner.MIN_FREE_LITERS = 1000
|
|
|
|
-- Varer hvis kilder LØBER FULDE (dyr/produktion fylder tanken løbende og går i stå
|
|
-- når den er fuld): her hentes den fyldeste kilde først. Alt andet er lagervarer,
|
|
-- hvor den mindst fyldte silo tømmes først så rester ryddes og siloer frigives.
|
|
ADSupplyPlanner.FULLEST_FIRST_FILL_TYPES = {
|
|
MILK = true, GOATMILK = true, BUFFALOMILK = true, SHEEPMILK = true,
|
|
LIQUIDMANURE = true, DIGESTATE = true,
|
|
}
|
|
|
|
-- fillTypeNames: navnene på de varer der hentes på turen. Kun når ALLE er
|
|
-- "løber fuld"-varer vendes strategien; blandet eller ukendt valg = mindst først.
|
|
function ADSupplyPlanner.prefersFullestSource(fillTypeNames)
|
|
if #fillTypeNames == 0 then
|
|
return false
|
|
end
|
|
for _, name in ipairs(fillTypeNames) do
|
|
if not ADSupplyPlanner.FULLEST_FIRST_FILL_TYPES[name] then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- Er en kilde med dette niveau bedre end den hidtil bedste (nil = ingen endnu)?
|
|
function ADSupplyPlanner.isBetterSource(level, bestLevel, prefersFullest)
|
|
if bestLevel == nil then
|
|
return true
|
|
end
|
|
if prefersFullest then
|
|
return level > bestLevel
|
|
end
|
|
return level < bestLevel
|
|
end
|
|
|
|
function ADSupplyPlanner.copyList(list)
|
|
local copy = {}
|
|
for index, value in ipairs(list) do
|
|
copy[index] = value
|
|
end
|
|
return copy
|
|
end
|
|
|
|
-- Ønsker en produktion input? Tændt, eller slukket af kortet fordi den MANGLEDE
|
|
-- input (Dakotas ExtendedProductionPoint slår den fra automatisk, men lader
|
|
-- status stå på MISSING_INPUTS). Manuelt slukket = status INACTIVE -> nej.
|
|
function ADSupplyPlanner.isProductionWanted(isEnabled, status, missingInputsStatus)
|
|
if isEnabled then
|
|
return true
|
|
end
|
|
return status ~= nil and status == missingInputsStatus
|
|
end
|
|
|
|
-- productions: produktionspunktets liste ({id, cyclesPerMinute, inputs = {{type, amount}}}).
|
|
-- isWanted(production) afgør om den tæller; canCarry[fillType] = det vognen kan bære.
|
|
-- Returnerer {[fillType] = liter pr. minut} summeret over ønskede produktioner.
|
|
function ADSupplyPlanner.collectProductionDemand(productions, isWanted, canCarry)
|
|
local demand = {}
|
|
for _, production in ipairs(productions) do
|
|
if isWanted(production) then
|
|
local cyclesPerMinute = production.cyclesPerMinute or 0
|
|
for _, input in ipairs(production.inputs or {}) do
|
|
if canCarry[input.type] then
|
|
demand[input.type] = (demand[input.type] or 0) + (input.amount or 0) * cyclesPerMinute
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return demand
|
|
end
|
|
|
|
-- Minutter før målets lager af varen er tomt ved nuværende forbrug; nil uden forbrug.
|
|
function ADSupplyPlanner.minutesToEmpty(level, litersPerMinute)
|
|
if litersPerMinute == nil or litersPerMinute <= 0 then
|
|
return nil
|
|
end
|
|
return level / litersPerMinute
|
|
end
|
|
|
|
-- Hvor meget trænger målet til denne ingrediens? Højere = hentes først.
|
|
-- Alle tal er liter i aflæsningsmålets lager. Standard: relativ tomhed (0..1),
|
|
-- så en lille næsten-tom tank vinder over en stor halvfuld. Kendes forbruget
|
|
-- (fabriksstyret), tæller i stedet tid til tom: score i ]1, 2], så varer med
|
|
-- forbrug altid går forud for varer uden, og kortest tid vinder.
|
|
function ADSupplyPlanner.scoreNeed(level, capacity, freeSpace, minutesToEmpty)
|
|
if minutesToEmpty ~= nil then
|
|
return 1 + 1 / (1 + minutesToEmpty)
|
|
end
|
|
if capacity <= 0 then
|
|
return 0
|
|
end
|
|
return freeSpace / capacity
|
|
end
|
|
|
|
-- needs: {{fillType, level, capacity[, litersPerMinute]}, ...}. Returnerer NY liste,
|
|
-- mest trængende først; ingredienser uden plads i målet er sorteret fra.
|
|
function ADSupplyPlanner.rankNeeds(needs)
|
|
local ranked = {}
|
|
for _, need in ipairs(needs) do
|
|
local freeSpace = need.capacity - need.level
|
|
if freeSpace >= ADSupplyPlanner.MIN_FREE_LITERS then
|
|
local minutesToEmpty = ADSupplyPlanner.minutesToEmpty(need.level, need.litersPerMinute)
|
|
table.insert(ranked, {
|
|
fillType = need.fillType,
|
|
level = need.level,
|
|
capacity = need.capacity,
|
|
freeSpace = freeSpace,
|
|
litersPerMinute = need.litersPerMinute,
|
|
minutesToEmpty = minutesToEmpty,
|
|
score = ADSupplyPlanner.scoreNeed(need.level, need.capacity, freeSpace, minutesToEmpty),
|
|
})
|
|
end
|
|
end
|
|
table.sort(ranked, function(a, b)
|
|
if a.score ~= b.score then
|
|
return a.score > b.score
|
|
end
|
|
return a.fillType < b.fillType
|
|
end)
|
|
return ranked
|
|
end
|
|
|
|
-- state: nil eller {ingredients, narrowedTo} fra forrige tur. selected: AD's aktuelle varevalg.
|
|
-- >1 valgt = (ny) ingrediensliste. Præcis den vare vi selv indsnævrede til = gemt liste.
|
|
-- Alt andet = brugeren har valgt noget andet -> nil (behovsstyring fra).
|
|
function ADSupplyPlanner.resolveIngredients(state, selected)
|
|
if type(selected) ~= "table" then
|
|
return nil
|
|
end
|
|
if #selected > 1 then
|
|
return ADSupplyPlanner.copyList(selected)
|
|
end
|
|
if state ~= nil and #selected == 1 and selected[1] == state.narrowedTo then
|
|
return ADSupplyPlanner.copyList(state.ingredients)
|
|
end
|
|
return nil
|
|
end
|