Files
fs25-adsmartpickup/FS25_ADSmartPickup/adDependency.lua
T
masterdraco 928299c8ee fix(kæde): kædens egne flows giver ikke dobbelt advarsel
Flows med run.chain (Kæde: X → Y) tæller som kæde-behov i neededOutputs,
så onlyChain forbliver sand, når kun kæden bruger varen, og Task 10 alene
melder det. Et almindeligt flow med samme vare advarer stadig.
2026-09-30 15:14:42 +02:00

191 lines
8.9 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- AD Dependency (ren logik)
-- Advarsel når spilleren slukker en produktion gården er afhængig af (spec 2026-09-30-produktionskaeder afsnit 8).
-- En vare er "nødvendig" når et tændt forsyningsflow kører den (manuel vare, eller flowets mål tager den i
-- auto-tilstand), en stald æder den, eller en aktiv kæde har den som input. Varer er NAVNE; kapacitet og forbrug
-- i samme enhed (adapteren i adChain.lua vælger), lagerdage i spillets døgn. Ingen spiladgang.
ADDependency = {}
ADDependency.STOCK_DAYS_OK = 60 -- lager til mere end så mange døgn: ingen advarsel
ADDependency.MAX_USERS = 2 -- brugere nævnt i beskeden; resten "+k"
local function contains(list, value)
for _, item in ipairs(list or {}) do if item == value then return true end end
return false
end
local function sortedKeys(map)
local keys = {}
for key in pairs(map or {}) do table.insert(keys, key) end
table.sort(keys)
return keys
end
-- flows = {{name, kind, stopped, targetBuildingIds, ingredientMode, ingredients, chain}} (chain = kædens rodnøgle for kædens flows)
-- barnFood = {[vare] = {staldnavn...}}, chainInputs = {[vare] = {kædenavn...}}
-- accepts = {[bygnings-id] = {[vare] = true}}: hvad auto-flowenes mål tager (adapteren slår op)
-- -> {[vare] = {users = {navn...}, onlyChain, strong}} (brugere i rækkefølgen flows, stalde, kæder; hver kun én gang).
-- strong = varen er valgt som manuel vare i et flow eller er kæde-input: behovet gælder også uden kendt forbrug.
-- Stald-foder og auto-flowenes mål er "svage": uden kendt forbrug (lagerdage) er de ikke nødvendige.
function ADDependency.neededOutputs(flows, barnFood, chainInputs, accepts)
local result = {}
local function add(name, user, isChain, isStrong)
if name == nil or user == nil then return end
local entry = result[name]
if entry == nil then
entry = {users = {}, onlyChain = true, strong = false}
result[name] = entry
end
if not isChain then entry.onlyChain = false end
if isStrong then entry.strong = true end
if not contains(entry.users, user) then table.insert(entry.users, user) end
end
for _, flow in ipairs(flows or {}) do
if flow.kind ~= "outbound" and not flow.stopped then
local user = tostring(flow.name or "?")
-- kædens egne flows (flow.chain) er kæde-behov: Task 10 melder kæderne, så de gør ikke onlyChain falsk
local isChainFlow = flow.chain ~= nil and flow.chain ~= ""
if flow.ingredientMode == "manual" then
for _, name in ipairs(flow.ingredients or {}) do add(name, user, isChainFlow, true) end
else
for _, building in ipairs(flow.targetBuildingIds or {}) do
for _, name in ipairs(sortedKeys((accepts or {})[building])) do add(name, user, isChainFlow, isChainFlow) end
end
end
end
end
for _, name in ipairs(sortedKeys(barnFood)) do
for _, barn in ipairs(barnFood[name]) do add(name, tostring(barn), false) end
end
for _, name in ipairs(sortedKeys(chainInputs)) do
for _, root in ipairs(chainInputs[name]) do add(name, tostring(root), true, true) end
end
return result
end
-- Tændte produktioner der ikke er kædens (samme regel som ADChain.enabledKeys) -> {[nøgle] = true}
function ADDependency.enabledKeys(recipes)
local result = {}
for _, recipe in ipairs(recipes or {}) do
if recipe.enabled and not recipe.byChain then result[recipe.key] = true end
end
return result
end
-- Tændte produktioners kapacitet pr. vare (outputs × cyklusser pr. måned) -> {[vare] = mængde}
function ADDependency.capacity(recipes)
local result = {}
for _, recipe in ipairs(recipes or {}) do
if recipe.enabled then
for name, amount in pairs(recipe.outputs or {}) do
result[name] = (result[name] or 0) + (amount or 0) * (recipe.cyclesPerMonth or 0)
end
end
end
return result
end
-- Dækker andre tændte produktioner varen? Ukendt/intet forbrug: enhver kapacitet dækker.
local function isCovered(name, otherCapacity, demand)
local capacity = (otherCapacity or {})[name] or 0
return capacity > 0 and capacity >= ((demand or {})[name] or 0)
end
-- Rækker lageret længe nok? nil = ukendt (advar, uden dage).
local function stockLasts(name, stockDays)
local days = (stockDays or {})[name]
return days ~= nil and days > ADDependency.STOCK_DAYS_OK
end
-- {[bygning|vare] = true} for tændte opskrifter: spilleren har valgt hvilken opskrift i fabrikken der kører.
local function enabledInBuilding(recipes)
local result = {}
for _, recipe in ipairs(recipes or {}) do
if recipe.enabled then
for name, amount in pairs(recipe.outputs or {}) do
if (amount or 0) > 0 then result[tostring(recipe.building) .. "|" .. name] = true end
end
end
end
return result
end
-- Er behovet for varen reelt? Kun kæder: nej (Task 10 melder kæderne). Svagt behov (stald-foder, auto-flowets mål)
-- kræver kendt forbrug (lagerdage); manuel flow-vare og kæde-input gælder også uden.
local function isNeeded(need, name, stockDays)
if need == nil or need.onlyChain then return false end
return need.strong == true or (stockDays or {})[name] ~= nil
end
-- Første nødvendige, udækkede vare (efter navn) for en slukket produktion (ikke kædens) -> advarsel eller nil.
local function check(recipe, needed, stockDays, otherCapacity, demand, alternatives)
if recipe.enabled or recipe.byChain then return nil end
for _, name in ipairs(sortedKeys(recipe.outputs)) do
local need = (needed or {})[name]
if isNeeded(need, name, stockDays) and (recipe.outputs[name] or 0) > 0
and not alternatives[tostring(recipe.building) .. "|" .. name]
and not isCovered(name, otherCapacity, demand) and not stockLasts(name, stockDays) then
local days = (stockDays or {})[name]
return {recipeKey = recipe.key, building = recipe.building, buildingName = recipe.buildingName, fillType = name,
users = need.users, onlyChain = need.onlyChain == true, days = days}
end
end
return nil
end
-- Alle slukkede, nødvendige produktioner (én pr. fabrik) — "Slukket men nødvendig" i Kræver handling.
function ADDependency.offNeeded(recipes, needed, stockDays, otherCapacity, demand)
local result, seen = {}, {}
local alternatives = enabledInBuilding(recipes)
for _, recipe in ipairs(recipes or {}) do
local warning = check(recipe, needed, stockDays, otherCapacity, demand, alternatives)
if warning ~= nil and not seen[tostring(warning.building)] then
seen[tostring(warning.building)] = true
table.insert(result, warning)
end
end
return result
end
-- Fælles regler (warnings og offNeeded): ingen advarsel når en anden opskrift i SAMME fabrik laver varen og er
-- tændt, når andre tændte produktioner dækker behovet, når lageret rækker > 60 dage, eller når kun kæder bruger varen.
-- Nye slukninger siden sidste evaluering: tændt i prevEnabled, slukket nu, nødvendig og ikke dækket.
-- prevEnabled = nil (første evaluering efter indlæsning) -> ingen. Én pr. fabrik og vare.
function ADDependency.warnings(prevEnabled, recipes, needed, stockDays, otherCapacity, demand)
local result, seen = {}, {}
if prevEnabled == nil then return result end
local alternatives = enabledInBuilding(recipes)
for _, recipe in ipairs(recipes or {}) do
if prevEnabled[recipe.key] then
local warning = check(recipe, needed, stockDays, otherCapacity, demand, alternatives)
local id = warning ~= nil and (tostring(warning.building) .. "|" .. warning.fillType) or nil
if warning ~= nil and not seen[id] then
seen[id] = true
table.insert(result, warning)
end
end
end
return result
end
-- "A, B +2"
function ADDependency.usersText(users)
local shown = {}
for index = 1, math.min(#(users or {}), ADDependency.MAX_USERS) do shown[index] = tostring(users[index]) end
if #shown == 0 then return "?" end
local text = table.concat(shown, ", ")
if #users > #shown then text = text .. " +" .. tostring(#users - #shown) end
return text
end
-- Beskedtekst (spu_dep_warn med dage, spu_dep_warnNoDays uden).
function ADDependency.notifyText(warning, textOf, titleOf)
local title = tostring(titleOf ~= nil and titleOf(warning.fillType) or warning.fillType)
local users = ADDependency.usersText(warning.users)
local building = tostring(warning.buildingName or warning.building or "?")
local days = warning.days
if days ~= nil and days < math.huge then
return string.format(textOf("spu_dep_warn"), building, title, users, math.max(0, math.floor(days)))
end
return string.format(textOf("spu_dep_warnNoDays"), building, title, users)
end