928299c8ee
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.
191 lines
8.9 KiB
Lua
191 lines
8.9 KiB
Lua
-- 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
|