feat(kæde): advarsel når en fabrik gården er afhængig af slukkes

Slukker spilleren en produktion, hvis vare et tændt flow kører (manuel vare
eller målet tager den i auto), en stald æder eller en aktiv kæde bruger,
kommer én besked pr. slukning med brugere og lagerdage, og en rød post
'Slukket men nødvendig' i Kræver handling og statusvinduet så længe den er
slukket og nødvendig. Ingen advarsel når en anden tændt produktion dækker,
lageret rækker over 60 dage, kæden selv slukkede den eller den var slukket
ved indlæsning. Tjekket kører i kædens tick også uden kæder; ren logik i
adDependency.lua.
This commit is contained in:
2026-09-30 15:08:28 +02:00
parent ba2a3e94f1
commit 979b7f72d5
14 changed files with 488 additions and 14 deletions
+127 -4
View File
@@ -32,8 +32,9 @@ end
-- En læsefejl i dette kald: data.partial = true (også når advarslen allerede er logget), så kæden ikke
-- slukker eller stopper noget på et ufuldstændigt billede af gården.
-- data.readFailed skelner en ægte læsefejl fra en ufærdig markscanning (afhængighedstjekket bruger ikke markerne).
local function failed(data, key, fmt, ...)
data.partial = true
data.partial, data.readFailed = true, true
ADChainData.warnOnce(key, fmt, ...)
end
@@ -118,11 +119,27 @@ local function readHerd(placeable, nameOf)
return {building = uid, buildingName = placeableName(placeable, uid), outputs = outputs}
end
-- Stald-foder: {[vare] = {staldnavn...}} for det truget æder nu (forbrug > 0) — til afhængighedstjekket.
local function readBarnFood(data, placeable, nameOf)
if ADSources.getHusbandryConsumption == nil then return end
local barnName
for index, perDay in pairs(ADSources.getHusbandryConsumption(placeable) or {}) do
local name = nameOf(index)
if name ~= nil and (perDay or 0) > 0 then
barnName = barnName or placeableName(placeable, placeableId(placeable) or "?")
data.barnFood[name] = data.barnFood[name] or {}
table.insert(data.barnFood[name], barnName)
end
end
end
local function readPlaceables(data, farmId, byChain, nameOf)
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
for index, placeable in pairs(placeables) do
local ok, err = pcall(function()
if not ADPrices.isOwnedBy(placeable, farmId) then return end
local uid = placeableId(placeable)
if uid ~= nil then data.placeables[uid] = placeable end
local point = placeable.spec_productionPoint ~= nil and placeable.spec_productionPoint.productionPoint or nil
if point ~= nil then
local recipes, points = readFactory(placeable, point, byChain, nameOf)
@@ -131,6 +148,7 @@ local function readPlaceables(data, farmId, byChain, nameOf)
elseif placeable.spec_husbandry ~= nil or placeable.getHusbandryFillLevel ~= nil then
local herd = readHerd(placeable, nameOf)
if herd ~= nil then table.insert(data.husbandry, herd) end
readBarnFood(data, placeable, nameOf)
end
end)
if not ok then
@@ -182,13 +200,30 @@ end
local function readStock(data, adEnv, farmId, nameOf)
local ok, err = pcall(function()
for index, liters in pairs(ADStorageStats.farmTotals(ADSources.getFarmInventory(adEnv, farmId))) do
local fruit = data.crops[nameOf(index) or ""]
local name = nameOf(index)
if name ~= nil then data.stockByFill[name] = (data.stockByFill[name] or 0) + liters end
local fruit = data.crops[name or ""]
if fruit ~= nil then data.stock[fruit] = (data.stock[fruit] or 0) + liters end
end
end)
if not ok then failed(data, "stock", "lager kunne ikke læses: %s", tostring(err)) end
end
-- Gårdens forbrug og produktion pr. døgn (fabrikker der kører + stalde) pr. varenavn og spillets tidsfaktor
-- (rå rater pr. måned × faktor = pr. døgn) — til lagerdage og dækning i afhængighedstjekket.
local function readRates(data, adEnv, farmId, nameOf)
local environment = g_currentMission ~= nil and g_currentMission.environment or nil
local adjustment = environment ~= nil and tonumber(environment.timeAdjustment) or nil
data.timeAdjustment = adjustment ~= nil and adjustment > 0 and adjustment or 1
local ok, err = pcall(function()
for index, rate in pairs(ADSources.getFarmRates(adEnv, farmId, false) or {}) do
local name = nameOf(index)
if name ~= nil then data.rates[name] = {consumption = rate.consumption or 0, production = rate.production or 0} end
end
end)
if not ok then failed(data, "allRates", "gårdens forbrug kunne ikke læses: %s", tostring(err)) end
end
-- Afgrøder gården selv bruger: input i en tændt produktion eller foder/halm i en stald (kun staldenes forbrug
-- læses: feedOnly springer fabrikkernes rater over — dem dækker opskrifterne allerede).
local function readUsedFruits(data, adEnv, farmId, nameOf)
@@ -231,19 +266,107 @@ local function readFields(data)
end
end
-- ---- afhængighedstjek (spec afsnit 8) ----
local function unloadStationsOf(placeable)
local storage = g_currentMission ~= nil and g_currentMission.storageSystem or nil
local result = {}
if storage == nil or storage.getUnloadingStations == nil then return result end
for _, station in pairs(storage:getUnloadingStations() or {}) do
if station.owningPlaceable == placeable then table.insert(result, station) end
end
return result
end
-- Hvad auto-flowenes mål tager af varerne `goods` (navne): {[bygnings-id] = {[vare] = true}}.
-- Kun tændte forsyningsflows i auto-tilstand; hvert mål slås op én gang.
function ADChainData.acceptsMap(data, flows, goods)
local result = {}
for _, flow in ipairs(flows or {}) do
if flow.kind ~= "outbound" and not flow.stopped and flow.ingredientMode ~= "manual" then
for _, id in ipairs(flow.targetBuildingIds or {}) do
local placeable = data.placeables[id]
if result[id] == nil and placeable ~= nil then
result[id] = {}
local ok, err = pcall(function()
local stations = unloadStationsOf(placeable)
for _, name in ipairs(goods) do
local index = data.fillIndex[name]
for _, station in ipairs(stations) do
if index ~= nil and ADSources.stationAccepts(station, index) then result[id][name] = true; break end
end
end
end)
if not ok then ADChainData.warnOnce("accepts:" .. tostring(id), "mål %s kunne ikke læses: %s", tostring(id), tostring(err)) end
end
end
end
end
return result
end
-- Kædens input-varer (alle varer i de aktive kæders træer) -> {[vare] = {kædenavn...}}
function ADChainData.chainInputs(chain, titles)
local result = {}
local function walk(nodes, rootTitle)
for _, node in ipairs(nodes or {}) do
result[node.fillType] = result[node.fillType] or {}
local list = result[node.fillType]
if list[#list] ~= rootTitle then table.insert(list, rootTitle) end
walk(node.children, rootTitle)
end
end
for _, root in ipairs(chain ~= nil and chain.roots or {}) do
walk(root.children, tostring((titles or {})[root.name] or root.name))
end
return result
end
-- Alt afhængighedstjekket regner på, i liter pr. døgn: {needed, stockDays, capacity, demand}.
-- flows = ADChain.runsView(...); chain = kædemodellen (kan være uden rødder).
function ADChainData.dependencyInputs(data, chain, flows)
local goods, seen = {}, {}
for _, recipe in ipairs(data.recipes or {}) do
if not recipe.enabled and not recipe.byChain then
for name in pairs(recipe.outputs or {}) do
if not seen[name] then seen[name] = true; table.insert(goods, name) end
end
end
end
table.sort(goods)
local needed = ADDependency.neededOutputs(flows, data.barnFood, ADChainData.chainInputs(chain, data.fillTitles),
#goods > 0 and ADChainData.acceptsMap(data, flows, goods) or {})
local adjustment = data.timeAdjustment or 1
local capacity, demand, stockDays = {}, {}, {}
for name, perMonth in pairs(ADDependency.capacity(data.recipes)) do capacity[name] = perMonth * adjustment end
for _, name in ipairs(goods) do
local rate = (data.rates or {})[name] or {}
local consumption, production = rate.consumption or 0, rate.production or 0
local chainDemand = ((chain ~= nil and chain.demand or {})[name] or 0) * adjustment
demand[name] = math.max(consumption, chainDemand)
if consumption > production then
stockDays[name] = ((data.stockByFill or {})[name] or 0) / (consumption - production)
elseif consumption > 0 then
stockDays[name] = math.huge
end
end
return {needed = needed, stockDays = stockDays, capacity = capacity, demand = demand}
end
-- ---- samlet ----
-- byChain = {[recipeKey] = true} for produktioner kæden selv har tændt (ikke rødder).
-- data.partial = true når noget ikke kunne læses i dette kald; data.fieldsIncomplete = true mens markscanningen
-- data.partial = true når noget ikke kunne læses i dette kald (data.readFailed) eller markscanningen kører; data.fieldsIncomplete = true mens markscanningen
-- ikke har nået alle marker.
function ADChainData.collect(adEnv, farmId, byChain)
local data = {recipes = {}, husbandry = {}, crops = {}, stock = {}, fields = {}, yields = {}, fruitFillIndex = {},
usedFruits = {}, points = {}, fillTitles = {}, fillIndex = {}}
usedFruits = {}, points = {}, fillTitles = {}, fillIndex = {}, placeables = {}, barnFood = {}, stockByFill = {}, rates = {}}
local nameOf = nameResolver(data)
data.priceOf = priceResolver(data)
readPlaceables(data, farmId, byChain, nameOf)
readCrops(data, nameOf)
readStock(data, adEnv, farmId, nameOf)
readRates(data, adEnv, farmId, nameOf)
readUsedFruits(data, adEnv, farmId, nameOf)
readFields(data)
return data