-- 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