feat(kæde): kædemodel sporer produkter baglæns til råvarer
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
-- AD Chain Model (ren logik)
|
||||
-- Sporer baglæns fra de produktioner spilleren selv har tændt (rødder) gennem gårdens egne fabrikker og stalde
|
||||
-- til råvarerne. Alle mængder er liter pr. måned; varer og afgrøder er NAVNE. Se spec 2026-09-30-produktionskaeder.
|
||||
|
||||
ADChainModel = {}
|
||||
ADChainModel.MAX_DEPTH = 8
|
||||
|
||||
local function byKey(a, b) return tostring(a.key) < tostring(b.key) end
|
||||
|
||||
-- producenter pr. vare: tændte først, så stabil rækkefølge
|
||||
local function indexProducers(recipes)
|
||||
local index = {}
|
||||
for _, recipe in ipairs(recipes or {}) do
|
||||
for name, amount in pairs(recipe.outputs or {}) do
|
||||
if (amount or 0) > 0 and (recipe.cyclesPerMonth or 0) > 0 then
|
||||
index[name] = index[name] or {}
|
||||
table.insert(index[name], recipe)
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, list in pairs(index) do
|
||||
table.sort(list, function(a, b)
|
||||
if (a.enabled == true) ~= (b.enabled == true) then return a.enabled == true end
|
||||
return byKey(a, b)
|
||||
end)
|
||||
end
|
||||
return index
|
||||
end
|
||||
|
||||
local function indexHerds(husbandry)
|
||||
local index = {}
|
||||
for _, herd in ipairs(husbandry or {}) do
|
||||
for name, perMonth in pairs(herd.outputs or {}) do
|
||||
if (perMonth or 0) > 0 then
|
||||
index[name] = index[name] or {}
|
||||
table.insert(index[name], {building = herd.building, buildingName = herd.buildingName, left = perMonth})
|
||||
end
|
||||
end
|
||||
end
|
||||
return index
|
||||
end
|
||||
|
||||
local function sortedNames(map)
|
||||
local names = {}
|
||||
for name in pairs(map or {}) do table.insert(names, name) end
|
||||
table.sort(names)
|
||||
return names
|
||||
end
|
||||
|
||||
function ADChainModel.build(recipes, husbandry, crops)
|
||||
local chain = {roots = {}, demand = {}, fieldDemand = {}, needRecipes = {}, problems = {}}
|
||||
local producers, herds = indexProducers(recipes), indexHerds(husbandry)
|
||||
local capacity, roots = {}, {}
|
||||
for _, recipe in ipairs(recipes or {}) do
|
||||
capacity[recipe.key] = recipe.cyclesPerMonth or 0
|
||||
if recipe.enabled and not recipe.byChain then table.insert(roots, recipe) end
|
||||
end
|
||||
table.sort(roots, byKey)
|
||||
for _, root in ipairs(roots) do capacity[root.key] = 0 end -- rødderne kører for sig selv
|
||||
|
||||
local function problem(kind, fillType, rootKey, detail)
|
||||
table.insert(chain.problems, {kind = kind, fillType = fillType, rootKey = rootKey, detail = detail})
|
||||
end
|
||||
|
||||
local need
|
||||
local function inputsOf(recipe, cycles, depth, path, rootKey)
|
||||
local children = {}
|
||||
for _, name in ipairs(sortedNames(recipe.inputs)) do
|
||||
local amount = recipe.inputs[name] or 0
|
||||
if amount > 0 and cycles > 0 then
|
||||
local child = need(name, amount * cycles, depth + 1, path, rootKey)
|
||||
child.consumer, child.consumerName = recipe.building, recipe.buildingName
|
||||
table.insert(children, child)
|
||||
end
|
||||
end
|
||||
return children
|
||||
end
|
||||
|
||||
need = function(name, perMonth, depth, path, rootKey)
|
||||
local node = {fillType = name, perMonth = perMonth, coveredPerMonth = 0, producers = {}, herds = {}, children = {}}
|
||||
chain.demand[name] = (chain.demand[name] or 0) + perMonth
|
||||
if depth > ADChainModel.MAX_DEPTH or path[name] then
|
||||
node.source = "cycle"
|
||||
problem("cycle", name, rootKey)
|
||||
return node
|
||||
end
|
||||
path[name] = true
|
||||
local rest = perMonth
|
||||
-- a) gårdens fabrikker
|
||||
local makers = producers[name]
|
||||
if makers ~= nil then
|
||||
node.source = "production"
|
||||
for _, recipe in ipairs(makers) do
|
||||
local perCycle = recipe.outputs[name]
|
||||
local cycles = math.min(rest / perCycle, capacity[recipe.key] or 0)
|
||||
if cycles > 0 then
|
||||
capacity[recipe.key] = capacity[recipe.key] - cycles
|
||||
rest = rest - cycles * perCycle
|
||||
chain.needRecipes[recipe.key] = (chain.needRecipes[recipe.key] or 0) + cycles
|
||||
table.insert(node.producers, {key = recipe.key, building = recipe.building, buildingName = recipe.buildingName, cycles = cycles})
|
||||
for _, child in ipairs(inputsOf(recipe, cycles, depth, path, rootKey)) do table.insert(node.children, child) end
|
||||
end
|
||||
if rest <= 1e-9 then break end
|
||||
end
|
||||
if rest > 1e-9 and #node.producers > 0 then
|
||||
problem("bottleneck", name, rootKey, rest)
|
||||
node.shortPerMonth = rest
|
||||
end
|
||||
end
|
||||
-- b) stalde
|
||||
if rest > 1e-9 and #node.producers == 0 and herds[name] ~= nil then
|
||||
node.source = "husbandry"
|
||||
for _, herd in ipairs(herds[name]) do
|
||||
local take = math.min(rest, herd.left)
|
||||
if take > 0 then
|
||||
herd.left = herd.left - take
|
||||
rest = rest - take
|
||||
table.insert(node.herds, {building = herd.building, buildingName = herd.buildingName, perMonth = take})
|
||||
end
|
||||
end
|
||||
end
|
||||
-- c) mark
|
||||
if rest > 1e-9 and #node.producers == 0 and #node.herds == 0 and crops ~= nil and crops[name] ~= nil then
|
||||
node.source, node.fruit = "field", crops[name]
|
||||
chain.fieldDemand[node.fruit] = (chain.fieldDemand[node.fruit] or 0) + rest
|
||||
rest = 0
|
||||
end
|
||||
-- d) ingen kilde (eller rest efter stald)
|
||||
if rest > 1e-9 and (node.source == nil or node.source == "husbandry") then
|
||||
if node.source == nil then node.source = "buy" end
|
||||
node.shortPerMonth = rest
|
||||
problem("buy", name, rootKey, rest)
|
||||
end
|
||||
node.coveredPerMonth = perMonth - (node.shortPerMonth or 0)
|
||||
path[name] = nil
|
||||
return node
|
||||
end
|
||||
|
||||
for _, root in ipairs(roots) do
|
||||
local cycles = root.cyclesPerMonth or 0
|
||||
table.insert(chain.roots, {key = root.key, name = root.name, building = root.building, buildingName = root.buildingName,
|
||||
children = inputsOf(root, cycles, 0, {}, root.key)})
|
||||
end
|
||||
return chain
|
||||
end
|
||||
@@ -55,6 +55,7 @@
|
||||
<sourceFile filename="adFieldJobs.lua"/>
|
||||
<sourceFile filename="adMonthView.lua"/>
|
||||
<sourceFile filename="adProfiler.lua"/>
|
||||
<sourceFile filename="adChainModel.lua"/>
|
||||
<sourceFile filename="adSmartPickup.lua"/>
|
||||
</extraSourceFiles>
|
||||
</modDesc>
|
||||
|
||||
Reference in New Issue
Block a user