e9f1251471
- Auto-flowets mål tæller kun for varer gården forbruger (eller en kæde vil have); stald-foder/auto-behov uden kendt forbrug er ikke nødvendigt. Manuel flow-vare og kæde-input gælder også uden kendte lagerdage. - En slukket opskrift springes over, når en anden tændt opskrift i samme fabrik laver varen. - Behov kun fra kæder giver ingen advarsel/post (Task 10 melder kæderne).
189 lines
8.6 KiB
Lua
189 lines
8.6 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}}
|
||
-- 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 "?")
|
||
if flow.ingredientMode == "manual" then
|
||
for _, name in ipairs(flow.ingredients or {}) do add(name, user, false, true) end
|
||
else
|
||
for _, building in ipairs(flow.targetBuildingIds or {}) do
|
||
for _, name in ipairs(sortedKeys((accepts or {})[building])) do add(name, user, false) 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
|