feat(kæde): manglende led forklares i Kræver handling, fanen og som besked
problemText giver årsag og råd for køb/flaskehals/kredsløb og 'slukket af dig' (userOff-kopi på ADChain.last); modellens problemer får need. Fanen Kæder viser teksten som rød tekstrække under varen (foldet ud). Kræver handling får én rød post pr. kæde (højst 2, score 3500+alvor mellem rød tom og rød fuld); klik åbner fanen med kæden foldet ud. Engangsbesked pr. nyt led (rod|vare|art) kun fra tick, ikke i opstartsfristen eller på delvise data/ufærdig markscanning. Manualer og CHANGELOG opdateret.
This commit is contained in:
+1
-1
@@ -8,7 +8,7 @@ står i [docs/BRUGERMANUAL.md](docs/BRUGERMANUAL.md) (dansk) og [docs/USER_MANUA
|
|||||||
|
|
||||||
## 1.61 — Produktionskæder
|
## 1.61 — Produktionskæder
|
||||||
|
|
||||||
- **1.61.0** Produktionskæder: slå et produkt til i en fabrik, og moden holder det kørende helt tilbage til marken. Kæden regner baglæns gennem dine egne fabrikker og stalde til afgrøderne og ser 12 måneder frem. Ny fane **Kæder** med træ pr. rod-produktion, farvet status, fold ud/ind og **[Pause]**/**[Start]**. Marker markeret **Fri til kæden** skifter afgrøde automatisk (højst 2 i minuttet); andre marker får forslag med **[Godkend]**/**[Afvis]** (højst 3 pr. afgrøde) i fanen og som ét samlet punkt i **Kræver handling**. En afgrøde, du selv har valgt, ændres aldrig. Mellemled tændes og slukkes kun igen, hvis kæden tændte dem. Flows **Kæde: <vare> → <bygning>** oprettes med en ledig pool-traktor og tipvogn (ellers stoppet: *mangler traktor/vogn*; paller altid stoppet) og stoppes, ikke slettes, når kæden slås fra. Dine egne tændte produktioner bruges som kilde først; slukker du en produktion, tænder kæden den ikke igen, før du tænder en ny. Stoppede kædeflows slippes af kæden. Ingen handlinger de første 2 minutter, ingen markskift før alle marker er scannet, og intet slukkes ved delvise læsefejl. Begrænsninger: intet køb, ingen dyrekøb, flerårige afgrøder og græsmarker skiftes aldrig, grundudbytte uden gødningsbonus. **Vigtigt:** hver produktion, du tænder i en fabrik, prøver moden at forsyne — tænd kun det, du vil have produceret; sluk produktionen eller tryk [Pause] for at stoppe kæden. Manual og ordliste opdateret.
|
- **1.61.0** Produktionskæder: slå et produkt til i en fabrik, og moden holder det kørende helt tilbage til marken. Kæden regner baglæns gennem dine egne fabrikker og stalde til afgrøderne og ser 12 måneder frem. Ny fane **Kæder** med træ pr. rod-produktion, farvet status, fold ud/ind og **[Pause]**/**[Start]**. Marker markeret **Fri til kæden** skifter afgrøde automatisk (højst 2 i minuttet); andre marker får forslag med **[Godkend]**/**[Afvis]** (højst 3 pr. afgrøde) i fanen og som ét samlet punkt i **Kræver handling**. En afgrøde, du selv har valgt, ændres aldrig. Mellemled tændes og slukkes kun igen, hvis kæden tændte dem. Flows **Kæde: <vare> → <bygning>** oprettes med en ledig pool-traktor og tipvogn (ellers stoppet: *mangler traktor/vogn*; paller altid stoppet) og stoppes, ikke slettes, når kæden slås fra. Dine egne tændte produktioner bruges som kilde først; slukker du en produktion, tænder kæden den ikke igen, før du tænder en ny. Stoppede kædeflows slippes af kæden. Ingen handlinger de første 2 minutter, ingen markskift før alle marker er scannet, og intet slukkes ved delvise læsefejl. Begrænsninger: intet køb, ingen dyrekøb, flerårige afgrøder og græsmarker skiftes aldrig, grundudbytte uden gødningsbonus. **Vigtigt:** hver produktion, du tænder i en fabrik, prøver moden at forsyne — tænd kun det, du vil have produceret; sluk produktionen eller tryk [Pause] for at stoppe kæden. Manglende led forklares i klart sprog med råd (*Ingen af dine fabrikker laver Mel — byg en eller køb*, *Mølle kan kun lave 800 af 1.0k/md*, *… er slukket af dig*, *Kredsløb omkring …*): som rød tekst under varen i fanen Kæder, som én rød linje pr. kæde i **Kræver handling** (højst 2; klik åbner kæden foldet ud) og som engangsbesked, når et nyt led mangler (ikke i opstartsfristen eller mens markerne scannes). Manual og ordliste opdateret.
|
||||||
|
|
||||||
## 1.60 — "Kræver handling" → flow, menuplacering, flow-vogne i guiden
|
## 1.60 — "Kræver handling" → flow, menuplacering, flow-vogne i guiden
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ ADChain.switchCount = 0
|
|||||||
ADChain.now = 0
|
ADChain.now = 0
|
||||||
ADChain.timer = 0
|
ADChain.timer = 0
|
||||||
ADChain.collectFailed = false
|
ADChain.collectFailed = false
|
||||||
|
-- {[rodnøgle|vare|art] = true}: manglende led der allerede er meldt som besked (kun i hukommelsen; nulstilles ved indlæsning)
|
||||||
|
ADChain.problemKeys = {}
|
||||||
|
|
||||||
local function contains(list, value)
|
local function contains(list, value)
|
||||||
for _, item in ipairs(list or {}) do if item == value then return true end end
|
for _, item in ipairs(list or {}) do if item == value then return true end end
|
||||||
@@ -258,6 +260,24 @@ function ADChain.runsView(runs)
|
|||||||
return list
|
return list
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Manglende led der ikke var der sidst (nøgle = rodnøgle|vare|art). -> nye problemer (hver nøgle én gang), nøglerne nu.
|
||||||
|
-- Et problem der forsvinder, glemmes; kommer det igen, er det nyt. prevKeys røres ikke.
|
||||||
|
function ADChain.problemKey(problem)
|
||||||
|
return tostring(problem.rootKey) .. "|" .. tostring(problem.fillType) .. "|" .. tostring(problem.kind)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADChain.newProblems(prevKeys, problems)
|
||||||
|
local fresh, keys = {}, {}
|
||||||
|
for _, problem in ipairs(problems or {}) do
|
||||||
|
local key = ADChain.problemKey(problem)
|
||||||
|
if not keys[key] then
|
||||||
|
keys[key] = true
|
||||||
|
if not (prevKeys or {})[key] then table.insert(fresh, problem) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return fresh, keys
|
||||||
|
end
|
||||||
|
|
||||||
-- ---- evaluering ----
|
-- ---- evaluering ----
|
||||||
|
|
||||||
local function farmId()
|
local function farmId()
|
||||||
@@ -325,7 +345,7 @@ function ADChain.evaluate(adEnv, act)
|
|||||||
for key in pairs(state.paused) do paused[key] = true end
|
for key in pairs(state.paused) do paused[key] = true end
|
||||||
ADChain.last = {chain = result.chain, shortages = result.shortages, actions = result.actions, edges = result.edges,
|
ADChain.last = {chain = result.chain, shortages = result.shortages, actions = result.actions, edges = result.edges,
|
||||||
missingFlows = ADChainActions.missingFlows(result.edges, runs), flowStatus = ADChain.flowStatus(result.edges, runs, ADChain.noVehicle),
|
missingFlows = ADChainActions.missingFlows(result.edges, runs), flowStatus = ADChain.flowStatus(result.edges, runs, ADChain.noVehicle),
|
||||||
paused = paused, data = data, fields = data.fields, fieldsIncomplete = data.fieldsIncomplete == true, titles = data.fillTitles, at = ADChain.now, switchCount = ADChain.switchCount}
|
paused = paused, userOff = copySet(state.userOff), data = data, fields = data.fields, fieldsIncomplete = data.fieldsIncomplete == true, titles = data.fillTitles, at = ADChain.now, switchCount = ADChain.switchCount}
|
||||||
return ADChain.last
|
return ADChain.last
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -335,13 +355,33 @@ function ADChain.lastAge()
|
|||||||
return ADChain.now - (ADChain.last.at or 0)
|
return ADChain.now - (ADChain.last.at or 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Engangsbesked pr. nyt manglende led (kun fra tick). Ikke i opstartsfristen og ikke mens gården er læst delvist
|
||||||
|
-- eller markscanningen kører (ellers falske alarmer lige efter indlæsning) — så huskes intet, og det meldes senere.
|
||||||
|
function ADChain.notifyProblems(last)
|
||||||
|
if last == nil or ADChain.now < ADChain.STARTUP_GRACE_MS then return 0 end
|
||||||
|
if last.fieldsIncomplete or (last.data ~= nil and last.data.partial) then return 0 end
|
||||||
|
local fresh, keys = ADChain.newProblems(ADChain.problemKeys, last.chain ~= nil and last.chain.problems or {})
|
||||||
|
ADChain.problemKeys = keys
|
||||||
|
if #fresh == 0 or ADFieldJobs == nil or ADFieldJobs.notify == nil then return 0 end
|
||||||
|
local textOf = function(key) return g_i18n ~= nil and g_i18n:getText(key) or key end
|
||||||
|
local fmt = SmartPickupFrame ~= nil and SmartPickupFrame.formatCompact or nil
|
||||||
|
for _, problem in ipairs(fresh) do
|
||||||
|
local text = ADChainView.problemText(problem, last, textOf, nil, fmt)
|
||||||
|
Logging.info(ADChain.LOG_PREFIX .. "manglende led: %s", text)
|
||||||
|
ADFieldJobs.notify(text, true)
|
||||||
|
end
|
||||||
|
return #fresh
|
||||||
|
end
|
||||||
|
|
||||||
function ADChain.tick(adEnv, dt)
|
function ADChain.tick(adEnv, dt)
|
||||||
if adEnv == nil then return end
|
if adEnv == nil then return end
|
||||||
ADChain.now = ADChain.now + (dt or 0)
|
ADChain.now = ADChain.now + (dt or 0)
|
||||||
ADChain.timer = ADChain.timer + (dt or 0)
|
ADChain.timer = ADChain.timer + (dt or 0)
|
||||||
if ADChain.now < ADChain.STARTUP_GRACE_MS or ADChain.timer < ADChain.TICK_MS then return end
|
if ADChain.now < ADChain.STARTUP_GRACE_MS or ADChain.timer < ADChain.TICK_MS then return end
|
||||||
ADChain.timer = 0
|
ADChain.timer = 0
|
||||||
ADChain.evaluate(adEnv, true)
|
local last = ADChain.evaluate(adEnv, true)
|
||||||
|
local ok, err = pcall(ADChain.notifyProblems, last)
|
||||||
|
if not ok then Logging.warning(ADChain.LOG_PREFIX .. "besked om manglende led fejlede: %s", tostring(err)) end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ---- GUI-handlinger ----
|
-- ---- GUI-handlinger ----
|
||||||
@@ -436,6 +476,7 @@ function ADChain.load()
|
|||||||
ADChain.state = ADChain.newState()
|
ADChain.state = ADChain.newState()
|
||||||
ADChain.noVehicle = {}
|
ADChain.noVehicle = {}
|
||||||
ADChain.prevEnabled, ADChain.afterLoad = nil, true
|
ADChain.prevEnabled, ADChain.afterLoad = nil, true
|
||||||
|
ADChain.problemKeys = {}
|
||||||
local path = getPath()
|
local path = getPath()
|
||||||
if path == nil or not fileExists(path) then return end
|
if path == nil or not fileExists(path) then return end
|
||||||
local xmlFile = XMLFile.load("ADSmartPickupChain", path)
|
local xmlFile = XMLFile.load("ADSmartPickupChain", path)
|
||||||
|
|||||||
@@ -63,8 +63,9 @@ function ADChainModel.build(recipes, husbandry, crops)
|
|||||||
end
|
end
|
||||||
table.sort(roots, byKey)
|
table.sort(roots, byKey)
|
||||||
|
|
||||||
local function problem(kind, fillType, rootKey, detail)
|
-- detail = manglende liter/md (flaskehals/køb), need = varens behov pr. måned i den gren
|
||||||
table.insert(chain.problems, {kind = kind, fillType = fillType, rootKey = rootKey, detail = detail})
|
local function problem(kind, fillType, rootKey, detail, need)
|
||||||
|
table.insert(chain.problems, {kind = kind, fillType = fillType, rootKey = rootKey, detail = detail, need = need})
|
||||||
end
|
end
|
||||||
|
|
||||||
local need
|
local need
|
||||||
@@ -86,7 +87,7 @@ function ADChainModel.build(recipes, husbandry, crops)
|
|||||||
chain.demand[name] = (chain.demand[name] or 0) + perMonth
|
chain.demand[name] = (chain.demand[name] or 0) + perMonth
|
||||||
if depth > ADChainModel.MAX_DEPTH or path[name] then
|
if depth > ADChainModel.MAX_DEPTH or path[name] then
|
||||||
node.source = "cycle"
|
node.source = "cycle"
|
||||||
problem("cycle", name, rootKey)
|
problem("cycle", name, rootKey, nil, perMonth)
|
||||||
return node
|
return node
|
||||||
end
|
end
|
||||||
path[name] = true
|
path[name] = true
|
||||||
@@ -110,7 +111,7 @@ function ADChainModel.build(recipes, husbandry, crops)
|
|||||||
if rest <= 1e-9 then break end
|
if rest <= 1e-9 then break end
|
||||||
end
|
end
|
||||||
if rest > 1e-9 and #node.producers > 0 then
|
if rest > 1e-9 and #node.producers > 0 then
|
||||||
problem("bottleneck", name, rootKey, rest)
|
problem("bottleneck", name, rootKey, rest, perMonth)
|
||||||
node.shortPerMonth = rest
|
node.shortPerMonth = rest
|
||||||
end
|
end
|
||||||
if rest > 1e-9 and #node.producers == 0 then
|
if rest > 1e-9 and #node.producers == 0 then
|
||||||
@@ -139,11 +140,11 @@ function ADChainModel.build(recipes, husbandry, crops)
|
|||||||
if rest > 1e-9 and makers ~= nil and #node.producers == 0 and #node.herds == 0 then
|
if rest > 1e-9 and makers ~= nil and #node.producers == 0 and #node.herds == 0 then
|
||||||
node.source = "production"
|
node.source = "production"
|
||||||
node.shortPerMonth = rest
|
node.shortPerMonth = rest
|
||||||
problem("bottleneck", name, rootKey, rest)
|
problem("bottleneck", name, rootKey, rest, perMonth)
|
||||||
elseif rest > 1e-9 and (node.source == nil or node.source == "husbandry") then
|
elseif rest > 1e-9 and (node.source == nil or node.source == "husbandry") then
|
||||||
if node.source == nil then node.source = "buy" end
|
if node.source == nil then node.source = "buy" end
|
||||||
node.shortPerMonth = rest
|
node.shortPerMonth = rest
|
||||||
problem("buy", name, rootKey, rest)
|
problem("buy", name, rootKey, rest, perMonth)
|
||||||
end
|
end
|
||||||
node.coveredPerMonth = perMonth - (node.shortPerMonth or 0)
|
node.coveredPerMonth = perMonth - (node.shortPerMonth or 0)
|
||||||
path[name] = nil
|
path[name] = nil
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
-- vises altid (under roden eller under afgrøden). Pausede rødder (som modellen ikke længere ser som rødder)
|
-- vises altid (under roden eller under afgrøden). Pausede rødder (som modellen ikke længere ser som rødder)
|
||||||
-- får en grå linje med [Genoptag]. Nederst frie marker + antal automatiske skift (+ "marker scannes endnu"). Ingen spiladgang:
|
-- får en grå linje med [Genoptag]. Nederst frie marker + antal automatiske skift (+ "marker scannes endnu"). Ingen spiladgang:
|
||||||
-- titleOf(navn) -> titel, fmt(liter) -> kompakt tal, textOf(l10n-nøgle) -> tekst gives udefra.
|
-- titleOf(navn) -> titel, fmt(liter) -> kompakt tal, textOf(l10n-nøgle) -> tekst gives udefra.
|
||||||
-- Række: {kind="header"|"root"|"node"|"suggest"|"auto"|"info"|"empty", indent, level, cells={c1..c5}, text,
|
-- Manglende led (Task 10): foldet ud står problemteksten (problemText) som en rød tekstrække ("problem", ingen celler)
|
||||||
|
-- lige under varen — teksten er for lang til en celle og ombrydes af fanen.
|
||||||
|
-- Række: {kind="header"|"root"|"node"|"suggest"|"auto"|"info"|"empty"|"problem", indent, level, cells={c1..c5}, text,
|
||||||
-- actions={[celle]={kind="approve"|"dismiss"|"pause"|"resume", fieldId|rootKey}}, toggle=rootKey, fillType}
|
-- actions={[celle]={kind="approve"|"dismiss"|"pause"|"resume", fieldId|rootKey}}, toggle=rootKey, fillType}
|
||||||
|
|
||||||
ADChainView = {}
|
ADChainView = {}
|
||||||
@@ -23,7 +25,8 @@ ADChainView.TEXT_KEYS = {"spu_chain_empty", "spu_chain_approve", "spu_chain_dism
|
|||||||
"spu_chain_buy", "spu_chain_cycle", "spu_chain_fieldShort", "spu_chain_tight", "spu_chain_noField", "spu_chain_buySource",
|
"spu_chain_buy", "spu_chain_cycle", "spu_chain_fieldShort", "spu_chain_tight", "spu_chain_noField", "spu_chain_buySource",
|
||||||
"spu_chain_pause", "spu_chain_resume", "spu_chain_scanning", "spu_chain_rootOk", "spu_chain_rootError", "spu_chain_rootWaiting",
|
"spu_chain_pause", "spu_chain_resume", "spu_chain_scanning", "spu_chain_rootOk", "spu_chain_rootError", "spu_chain_rootWaiting",
|
||||||
"spu_chain_rootPaused", "spu_chain_rootSummary", "spu_chain_free", "spu_chain_switches", "spu_chain_gone", "spu_chain_col_item",
|
"spu_chain_rootPaused", "spu_chain_rootSummary", "spu_chain_free", "spu_chain_switches", "spu_chain_gone", "spu_chain_col_item",
|
||||||
"spu_chain_col_source", "spu_chain_col_perMonth", "spu_chain_col_status", "spu_chain_col_stock"}
|
"spu_chain_col_source", "spu_chain_col_perMonth", "spu_chain_col_status", "spu_chain_col_stock",
|
||||||
|
"spu_chain_problem_buy", "spu_chain_problem_bottleneck", "spu_chain_problem_off", "spu_chain_problem_cycle"}
|
||||||
|
|
||||||
local FLOW_KEYS = {ok = "spu_chain_flowOk", missing = "spu_chain_flowNone", stopped = "spu_chain_flowStopped", noVehicle = "spu_chain_flowMissing",
|
local FLOW_KEYS = {ok = "spu_chain_flowOk", missing = "spu_chain_flowNone", stopped = "spu_chain_flowStopped", noVehicle = "spu_chain_flowMissing",
|
||||||
pallet = "spu_chain_flowPallet"}
|
pallet = "spu_chain_flowPallet"}
|
||||||
@@ -31,6 +34,14 @@ local ROOT_STATUS = {running = "spu_chain_rootOk", error = "spu_chain_rootError"
|
|||||||
|
|
||||||
local function identity(value) return tostring(value) end
|
local function identity(value) return tostring(value) end
|
||||||
|
|
||||||
|
-- samme som SmartPickupFrame.formatCompact (standard-fmt når ingen gives, fx i beskeden fra kædens tick)
|
||||||
|
function ADChainView.compact(liters)
|
||||||
|
liters = math.floor((liters or 0) + 0.5)
|
||||||
|
if liters >= 1000000 then return string.format("%.1fM", liters / 1000000)
|
||||||
|
elseif liters >= 1000 then return string.format("%.1fk", liters / 1000) end
|
||||||
|
return string.format("%d", liters)
|
||||||
|
end
|
||||||
|
|
||||||
-- ---- UTF-8-sikker længde/afkortning (æøå og → er flere bytes) ----
|
-- ---- UTF-8-sikker længde/afkortning (æøå og → er flere bytes) ----
|
||||||
|
|
||||||
function ADChainView.textLength(text)
|
function ADChainView.textLength(text)
|
||||||
@@ -127,6 +138,42 @@ local function joinCapped(names)
|
|||||||
return text
|
return text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Opskrifter (alle, også slukkede) der laver varen, i datarækkefølge.
|
||||||
|
local function producersOf(last, fillType)
|
||||||
|
local list = {}
|
||||||
|
for _, recipe in ipairs(last.data ~= nil and last.data.recipes or {}) do
|
||||||
|
if ((recipe.outputs or {})[fillType] or 0) > 0 then table.insert(list, recipe) end
|
||||||
|
end
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Årsag i klart sprog med råd for et problem fra kædemodellen ({kind="buy"|"bottleneck"|"cycle", fillType, rootKey,
|
||||||
|
-- detail = manglende l/md, need = behov l/md}). En producent spilleren selv har slukket (last.userOff, stadig
|
||||||
|
-- slukket) slår igennem som "slukket af dig" — det er den reelle årsag, uanset om modellen så køb eller flaskehals.
|
||||||
|
function ADChainView.problemText(problem, last, textOf, titleOf, fmt)
|
||||||
|
last, textOf, fmt = last or {}, textOf or identity, fmt or ADChainView.compact
|
||||||
|
titleOf = titleOf or function(name) return (last.titles or {})[name] or tostring(name) end
|
||||||
|
local item = titleOf(problem.fillType)
|
||||||
|
if problem.kind == "cycle" then return string.format(textOf("spu_chain_problem_cycle"), item) end
|
||||||
|
local userOff, names, seen = last.userOff or {}, {}, {}
|
||||||
|
for _, recipe in ipairs(producersOf(last, problem.fillType)) do
|
||||||
|
if userOff[recipe.key] and not recipe.enabled then
|
||||||
|
return string.format(textOf("spu_chain_problem_off"), tostring(recipe.buildingName), titleOf(recipe.name))
|
||||||
|
end
|
||||||
|
local name = tostring(recipe.buildingName)
|
||||||
|
if (recipe.cyclesPerMonth or 0) > 0 and not seen[name] then
|
||||||
|
seen[name] = true
|
||||||
|
table.insert(names, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if problem.kind == "bottleneck" and #names > 0 then
|
||||||
|
local need = problem.need or problem.detail or 0
|
||||||
|
local made = math.max(0, need - (problem.detail or 0))
|
||||||
|
return string.format(textOf("spu_chain_problem_bottleneck"), joinCapped(names), fmt(math.floor(made + 0.5)), fmt(math.floor(need + 0.5)))
|
||||||
|
end
|
||||||
|
return string.format(textOf("spu_chain_problem_buy"), item)
|
||||||
|
end
|
||||||
|
|
||||||
local function sourceText(node, last, textOf)
|
local function sourceText(node, last, textOf)
|
||||||
local names = {}
|
local names = {}
|
||||||
if node.source == "production" then
|
if node.source == "production" then
|
||||||
@@ -225,7 +272,20 @@ local function rootRows(rows, root, last, ctx, isExpanded)
|
|||||||
for _, action in ipairs(suggestions) do table.insert(rows, suggestRow(action, 1, ctx.titleOf, ctx.textOf)) end
|
for _, action in ipairs(suggestions) do table.insert(rows, suggestRow(action, 1, ctx.titleOf, ctx.textOf)) end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local shown = {}
|
local shown, problems, told = {}, {}, {}
|
||||||
|
for _, problem in ipairs((last.chain or {}).problems or {}) do
|
||||||
|
if problem.rootKey == root.key then table.insert(problems, problem) end
|
||||||
|
end
|
||||||
|
local function problemRows(fillType, indent)
|
||||||
|
for _, problem in ipairs(problems) do
|
||||||
|
local key = tostring(problem.fillType) .. "|" .. tostring(problem.kind)
|
||||||
|
if (fillType == nil or problem.fillType == fillType) and not told[key] then
|
||||||
|
told[key] = true
|
||||||
|
table.insert(rows, {kind = "problem", indent = indent, level = "error",
|
||||||
|
text = string.rep(ADChainView.INDENT, indent) .. ADChainView.problemText(problem, last, ctx.textOf, ctx.titleOf, ctx.fmt)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
eachNode(root.children, function(node, depth)
|
eachNode(root.children, function(node, depth)
|
||||||
local text, nodeLevel = nodeStatus(node, last, ctx.fmt, ctx.textOf, ctx.short)
|
local text, nodeLevel = nodeStatus(node, last, ctx.fmt, ctx.textOf, ctx.short)
|
||||||
local stock = node.source == "field" and stockOf(last, node.fruit) or nil
|
local stock = node.source == "field" and stockOf(last, node.fruit) or nil
|
||||||
@@ -233,6 +293,7 @@ local function rootRows(rows, root, last, ctx, isExpanded)
|
|||||||
sourceText(node, last, ctx.textOf), ctx.fmt(node.perMonth or 0), text, stock ~= nil and ctx.fmt(stock) or ""})
|
sourceText(node, last, ctx.textOf), ctx.fmt(node.perMonth or 0), text, stock ~= nil and ctx.fmt(stock) or ""})
|
||||||
row.fillType = node.fillType
|
row.fillType = node.fillType
|
||||||
table.insert(rows, row)
|
table.insert(rows, row)
|
||||||
|
problemRows(node.fillType, depth + 1)
|
||||||
if node.source == "field" and node.fruit ~= nil and not shown[node.fruit] then
|
if node.source == "field" and node.fruit ~= nil and not shown[node.fruit] then
|
||||||
shown[node.fruit] = true
|
shown[node.fruit] = true
|
||||||
for _, action in ipairs(actionsFor(last, node.fruit, {suggest = true, auto = true})) do
|
for _, action in ipairs(actionsFor(last, node.fruit, {suggest = true, auto = true})) do
|
||||||
@@ -240,6 +301,8 @@ local function rootRows(rows, root, last, ctx, isExpanded)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
-- problemer uden en vist vare (bør ikke ske) står sidst under roden
|
||||||
|
problemRows(nil, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Pausede rødder: modelRecipes markerer dem paused, så modellen ser dem ikke som rødder -> grå linje med [Genoptag].
|
-- Pausede rødder: modelRecipes markerer dem paused, så modellen ser dem ikke som rødder -> grå linje med [Genoptag].
|
||||||
|
|||||||
@@ -279,15 +279,75 @@ end
|
|||||||
-- "Kræver handling" fra kæden (ADChain.last): ÉT gult punkt "Kæde: N forslag" (klik -> fanen Kæder), så
|
-- "Kræver handling" fra kæden (ADChain.last): ÉT gult punkt "Kæde: N forslag" (klik -> fanen Kæder), så
|
||||||
-- forslagene ikke fylder listen. Rangeres under alle røde punkter (>= 3000), øverst blandt de gule, over "sælg nu".
|
-- forslagene ikke fylder listen. Rangeres under alle røde punkter (>= 3000), øverst blandt de gule, over "sælg nu".
|
||||||
ADStorageStats.CHAIN_SCORE = 2000
|
ADStorageStats.CHAIN_SCORE = 2000
|
||||||
|
-- Manglende led: én rød post pr. kæde ("Brød mangler et led: Mel"), højst MAX_CHAIN_PROBLEMS (resten står i
|
||||||
|
-- fanen Kæder; den sidste viste post får more = antal skjulte). Score CHAIN_PROBLEM_SCORE + alvor (0-99): under
|
||||||
|
-- "rød tom" (3900-4000: løber tør inden et døgn), over "rød fuld" (3000-3100). Alvor = manglende andel af behovet;
|
||||||
|
-- kredsløb (kan ikke beregnes) = 99.
|
||||||
|
ADStorageStats.CHAIN_PROBLEM_SCORE = 3500
|
||||||
|
ADStorageStats.MAX_CHAIN_PROBLEMS = 2
|
||||||
|
ADStorageStats.MAX_CHAIN_PROBLEM_ITEMS = 2
|
||||||
|
|
||||||
|
local function severity(problems)
|
||||||
|
local missing, need = 0, 0
|
||||||
|
for _, problem in ipairs(problems) do
|
||||||
|
if problem.kind == "cycle" then return 99 end
|
||||||
|
missing, need = missing + (problem.detail or 0), need + (problem.need or 0)
|
||||||
|
end
|
||||||
|
if need <= 0 then return 99 end
|
||||||
|
return math.floor(99 * math.min(1, missing / need))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function problemEntry(root, problems, titleOf)
|
||||||
|
local items, seen = {}, {}
|
||||||
|
for _, problem in ipairs(problems) do
|
||||||
|
local title = titleOf(problem.fillType)
|
||||||
|
if not seen[title] then
|
||||||
|
seen[title] = true
|
||||||
|
table.insert(items, title)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local shown = {}
|
||||||
|
for index = 1, math.min(#items, ADStorageStats.MAX_CHAIN_PROBLEM_ITEMS) do shown[index] = items[index] end
|
||||||
|
local itemsText = table.concat(shown, ", ")
|
||||||
|
if #items > #shown then itemsText = itemsText .. " +" .. tostring(#items - #shown) end
|
||||||
|
local rootTitle = titleOf(root.name)
|
||||||
|
return {kind = "chainProblem", level = "red", score = ADStorageStats.CHAIN_PROBLEM_SCORE + severity(problems), rootKey = root.key,
|
||||||
|
rootTitle = rootTitle, itemsText = itemsText, text = rootTitle .. ": " .. itemsText}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function chainProblemEntries(last)
|
||||||
|
local titles = last.titles or {}
|
||||||
|
local titleOf = function(name) return tostring(titles[name] or name) end
|
||||||
|
local byRoot = {}
|
||||||
|
for _, problem in ipairs(last.chain ~= nil and last.chain.problems or {}) do
|
||||||
|
if problem.rootKey ~= nil then
|
||||||
|
byRoot[problem.rootKey] = byRoot[problem.rootKey] or {}
|
||||||
|
table.insert(byRoot[problem.rootKey], problem)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local entries = {}
|
||||||
|
for _, root in ipairs(last.chain ~= nil and last.chain.roots or {}) do
|
||||||
|
if byRoot[root.key] ~= nil then table.insert(entries, problemEntry(root, byRoot[root.key], titleOf)) end
|
||||||
|
end
|
||||||
|
entries = ADStorageStats.rankAttention(entries)
|
||||||
|
local result = {}
|
||||||
|
for index = 1, math.min(#entries, ADStorageStats.MAX_CHAIN_PROBLEMS) do result[index] = entries[index] end
|
||||||
|
if #entries > #result then result[#result].more = #entries - #result end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
function ADStorageStats.chainEntries(last)
|
function ADStorageStats.chainEntries(last)
|
||||||
|
if last == nil then return {} end
|
||||||
|
local result = chainProblemEntries(last)
|
||||||
local count = 0
|
local count = 0
|
||||||
for _, action in ipairs(last ~= nil and last.actions or {}) do
|
for _, action in ipairs(last.actions or {}) do
|
||||||
if action.mode == "suggest" then count = count + 1 end
|
if action.mode == "suggest" then count = count + 1 end
|
||||||
end
|
end
|
||||||
if count == 0 then return {} end
|
if count > 0 then
|
||||||
return {{kind = "chainSuggest", level = "yellow", score = ADStorageStats.CHAIN_SCORE, count = count,
|
table.insert(result, {kind = "chainSuggest", level = "yellow", score = ADStorageStats.CHAIN_SCORE, count = count,
|
||||||
text = string.format("Kæde: %d", count)}}
|
text = string.format("Kæde: %d", count)})
|
||||||
|
end
|
||||||
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Varepunkterne (allerede rangeret) + kædens punkter, samme loft.
|
-- Varepunkterne (allerede rangeret) + kædens punkter, samme loft.
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ end
|
|||||||
|
|
||||||
-- entry fra ADStorageStats.attentionList -> advice ("open"|"flow"|"produce"|"none"), info
|
-- entry fra ADStorageStats.attentionList -> advice ("open"|"flow"|"produce"|"none"), info
|
||||||
function SmartPickupFrame:attentionAdvice(entry)
|
function SmartPickupFrame:attentionAdvice(entry)
|
||||||
-- kæde-forslag har ingen vare (stat): klik fører til fanen Kæder
|
-- kæde-forslag og manglende led har ingen vare (stat): klik fører til fanen Kæder
|
||||||
if entry ~= nil and entry.kind == "chainSuggest" then return "chain", nil end
|
if entry ~= nil and (entry.kind == "chainSuggest" or entry.kind == "chainProblem") then return "chain", nil end
|
||||||
if entry ~= nil and entry.kind == "sell" and entry.stat ~= nil then return sellAdviceFor(entry.stat) end
|
if entry ~= nil and entry.kind == "sell" and entry.stat ~= nil then return sellAdviceFor(entry.stat) end
|
||||||
if entry == nil or entry.kind ~= "empty" or entry.stat == nil then return "none", nil end
|
if entry == nil or entry.kind ~= "empty" or entry.stat == nil then return "none", nil end
|
||||||
local stat = entry.stat
|
local stat = entry.stat
|
||||||
@@ -94,7 +94,12 @@ end
|
|||||||
|
|
||||||
function SmartPickupFrame:onAttentionClick(entry)
|
function SmartPickupFrame:onAttentionClick(entry)
|
||||||
self:safe(function(frame)
|
self:safe(function(frame)
|
||||||
if entry ~= nil and entry.kind == "chainSuggest" then
|
if entry ~= nil and (entry.kind == "chainSuggest" or entry.kind == "chainProblem") then
|
||||||
|
-- manglende led: kæden foldes ud, så problemteksten står under den røde linje
|
||||||
|
if entry.kind == "chainProblem" and entry.rootKey ~= nil then
|
||||||
|
frame.chainExpanded = frame.chainExpanded or {}
|
||||||
|
frame.chainExpanded[entry.rootKey] = true
|
||||||
|
end
|
||||||
frame:switchTab(SmartPickupFrame.TAB_CHAINS)
|
frame:switchTab(SmartPickupFrame.TAB_CHAINS)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -87,6 +87,17 @@ function SmartPickupFrame:addChainRow(entry)
|
|||||||
return row
|
return row
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Manglende led: problemteksten som (ombrudt) tekstrække i rød under varelinjen.
|
||||||
|
function SmartPickupFrame:addChainProblemRow(entry)
|
||||||
|
local unpackFn = table.unpack or unpack
|
||||||
|
local color = ADFlowView.LEVEL_COLORS ~= nil and ADFlowView.LEVEL_COLORS[entry.level] or nil
|
||||||
|
for _, line in ipairs(ADFlowView.wrapText(entry.text, SmartPickupFrame.TEXT_ROW_CHARS)) do
|
||||||
|
local title = self:addRow(self.textPrefab, {kind = "text"}):getDescendantByName("title")
|
||||||
|
title:setText(line)
|
||||||
|
if color ~= nil and title.setTextColor ~= nil then title:setTextColor(unpackFn(color)) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function SmartPickupFrame:fillChainRows(adEnv)
|
function SmartPickupFrame:fillChainRows(adEnv)
|
||||||
local i18n = self.i18n
|
local i18n = self.i18n
|
||||||
local last = self:chainLast(adEnv, false)
|
local last = self:chainLast(adEnv, false)
|
||||||
@@ -95,6 +106,8 @@ function SmartPickupFrame:fillChainRows(adEnv)
|
|||||||
for _, entry in ipairs(rows) do
|
for _, entry in ipairs(rows) do
|
||||||
if entry.kind == "info" or entry.kind == "empty" then
|
if entry.kind == "info" or entry.kind == "empty" then
|
||||||
self:addTextRow(entry.text)
|
self:addTextRow(entry.text)
|
||||||
|
elseif entry.kind == "problem" then
|
||||||
|
self:addChainProblemRow(entry)
|
||||||
else
|
else
|
||||||
self:addChainRow(entry)
|
self:addChainRow(entry)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -183,7 +183,8 @@ function SmartPickupFrame:addKpiRow(stats)
|
|||||||
return row
|
return row
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Varepunkterne + ét samlet punkt for kædens afgrødeforslag (ADChain.last; ingen evaluering her), samme loft MAX_ATTENTION.
|
-- Varepunkterne + kædens punkter (røde manglende led, højst 2, og ét samlet punkt for afgrødeforslag; ADChain.last,
|
||||||
|
-- ingen evaluering her), samme loft MAX_ATTENTION.
|
||||||
function SmartPickupFrame:attentionFor(stats)
|
function SmartPickupFrame:attentionFor(stats)
|
||||||
local attention = ADStorageStats.attentionList(stats, SmartPickupFrame.MAX_ATTENTION)
|
local attention = ADStorageStats.attentionList(stats, SmartPickupFrame.MAX_ATTENTION)
|
||||||
if ADChain == nil or ADChain.last == nil then return attention end
|
if ADChain == nil or ADChain.last == nil then return attention end
|
||||||
@@ -195,6 +196,11 @@ function SmartPickupFrame:alertText(entry)
|
|||||||
if entry.kind == "chainSuggest" then
|
if entry.kind == "chainSuggest" then
|
||||||
return string.format(i18n:getText("spu_attention_chainSuggests"), entry.count or 0)
|
return string.format(i18n:getText("spu_attention_chainSuggests"), entry.count or 0)
|
||||||
end
|
end
|
||||||
|
if entry.kind == "chainProblem" then
|
||||||
|
local text = string.format(i18n:getText("spu_attention_chainProblem"), tostring(entry.rootTitle), tostring(entry.itemsText))
|
||||||
|
if (entry.more or 0) > 0 then text = text .. string.format(" (+%d)", entry.more) end
|
||||||
|
return text
|
||||||
|
end
|
||||||
if entry.kind == "empty" then
|
if entry.kind == "empty" then
|
||||||
return string.format(i18n:getText("spu_alert_empty"), stat.title, self:formatDays(stat.days), fc(stat.farmLevel or 0), fc(math.abs(stat.net or 0)))
|
return string.format(i18n:getText("spu_alert_empty"), stat.title, self:formatDays(stat.days), fc(stat.farmLevel or 0), fc(math.abs(stat.net or 0)))
|
||||||
elseif entry.kind == "full" then
|
elseif entry.kind == "full" then
|
||||||
|
|||||||
@@ -610,5 +610,10 @@
|
|||||||
<text name="spu_chain_scanning" text="Marker scannes endnu – kæden skifter ingen marker før alle er set"/>
|
<text name="spu_chain_scanning" text="Marker scannes endnu – kæden skifter ingen marker før alle er set"/>
|
||||||
<text name="spu_attention_chainSuggests" text="Kæde: %d forslag"/>
|
<text name="spu_attention_chainSuggests" text="Kæde: %d forslag"/>
|
||||||
<text name="spu_attention_hintChain" text=" → klik: se Kæder"/>
|
<text name="spu_attention_hintChain" text=" → klik: se Kæder"/>
|
||||||
|
<text name="spu_chain_problem_buy" text="Ingen af dine fabrikker laver %s — byg en eller køb"/>
|
||||||
|
<text name="spu_chain_problem_bottleneck" text="%s kan kun lave %s af %s/md"/>
|
||||||
|
<text name="spu_chain_problem_off" text="%s (%s) er slukket af dig — tænd den eller sæt kæden på pause"/>
|
||||||
|
<text name="spu_chain_problem_cycle" text="Kredsløb omkring %s — kæden kan ikke beregnes"/>
|
||||||
|
<text name="spu_attention_chainProblem" text="%s mangler et led: %s"/>
|
||||||
</texts>
|
</texts>
|
||||||
</l10n>
|
</l10n>
|
||||||
|
|||||||
@@ -610,5 +610,10 @@
|
|||||||
<text name="spu_chain_scanning" text="Fields still being scanned – no field changes until all are seen"/>
|
<text name="spu_chain_scanning" text="Fields still being scanned – no field changes until all are seen"/>
|
||||||
<text name="spu_attention_chainSuggests" text="Chain: %d suggestions"/>
|
<text name="spu_attention_chainSuggests" text="Chain: %d suggestions"/>
|
||||||
<text name="spu_attention_hintChain" text=" → click: see Chains"/>
|
<text name="spu_attention_hintChain" text=" → click: see Chains"/>
|
||||||
|
<text name="spu_chain_problem_buy" text="None of your factories makes %s — build one or buy it"/>
|
||||||
|
<text name="spu_chain_problem_bottleneck" text="%s can only make %s of %s/mo"/>
|
||||||
|
<text name="spu_chain_problem_off" text="%s (%s) was turned off by you — turn it on or pause the chain"/>
|
||||||
|
<text name="spu_chain_problem_cycle" text="Loop around %s — the chain cannot be calculated"/>
|
||||||
|
<text name="spu_attention_chainProblem" text="%s is missing a link: %s"/>
|
||||||
</texts>
|
</texts>
|
||||||
</l10n>
|
</l10n>
|
||||||
|
|||||||
@@ -650,6 +650,23 @@ slå et produkt til i en fabrik*.
|
|||||||
kæder kører videre. **[Start]** genoptager den.
|
kæder kører videre. **[Start]** genoptager den.
|
||||||
- Nederst står *Automatiske skift siden indlæsning*.
|
- Nederst står *Automatiske skift siden indlæsning*.
|
||||||
|
|
||||||
|
#### Manglende led (røde beskeder)
|
||||||
|
|
||||||
|
Mangler et led i en kæde, forklarer moden hvorfor og hvad du kan gøre. Teksten står tre steder:
|
||||||
|
|
||||||
|
- **I fanen Kæder**: fold kæden ud; under den røde vare står årsagen i rødt.
|
||||||
|
- **I Kræver handling**: én rød linje pr. kæde, fx *Brød mangler et led: Mel*. Der vises højst 2 kæder; resten
|
||||||
|
står i fanen Kæder. Et klik åbner fanen Kæder med kæden foldet ud.
|
||||||
|
- **Som besked på skærmen**: første gang et nyt led mangler. Samme besked kommer først igen, når problemet har
|
||||||
|
været væk og vender tilbage. De første 2 minutter efter indlæsning og mens markerne scannes, sendes ingen besked.
|
||||||
|
|
||||||
|
Beskederne:
|
||||||
|
|
||||||
|
- *Ingen af dine fabrikker laver Mel — byg en eller køb*: ingen fabrik, stald eller mark leverer varen.
|
||||||
|
- *Mølle kan kun lave 800 af 1.0k/md*: fabrikken kan ikke følge med (flaskehals).
|
||||||
|
- *Mølle (Mel) er slukket af dig — tænd den eller sæt kæden på pause*: du har selv slukket produktionen.
|
||||||
|
- *Kredsløb omkring Mel — kæden kan ikke beregnes*: varen bruges til at lave sig selv.
|
||||||
|
|
||||||
#### Markér en mark fri til kæden
|
#### Markér en mark fri til kæden
|
||||||
|
|
||||||
Åbn marken i markguiden og slå **Fri til kæden (moden må skifte afgrøde)** til. Kun sådan en mark skifter
|
Åbn marken i markguiden og slå **Fri til kæden (moden må skifte afgrøde)** til. Kun sådan en mark skifter
|
||||||
|
|||||||
@@ -663,6 +663,24 @@ enable a product in a factory*.
|
|||||||
Other chains carry on. **[Resume]** continues it.
|
Other chains carry on. **[Resume]** continues it.
|
||||||
- At the bottom: *Automatic switches since load*.
|
- At the bottom: *Automatic switches since load*.
|
||||||
|
|
||||||
|
#### Missing links (red messages)
|
||||||
|
|
||||||
|
If a link in a chain is missing, the mod explains why and what you can do. The text appears in three places:
|
||||||
|
|
||||||
|
- **In the Chains tab**: fold the chain out; the reason is shown in red below the red goods row.
|
||||||
|
- **In Needs attention**: one red line per chain, for example *Bread is missing a link: Flour*. At most 2 chains
|
||||||
|
are shown; the rest are in the Chains tab. Clicking opens the Chains tab with that chain folded out.
|
||||||
|
- **As an on-screen message**: the first time a new link goes missing. The same message only returns after the
|
||||||
|
problem has gone away and come back. No messages during the first 2 minutes after loading or while fields are
|
||||||
|
being scanned.
|
||||||
|
|
||||||
|
The messages:
|
||||||
|
|
||||||
|
- *None of your factories makes Flour — build one or buy it*: no factory, barn or field supplies the goods.
|
||||||
|
- *Mill can only make 800 of 1.0k/mo*: the factory cannot keep up (bottleneck).
|
||||||
|
- *Mill (Flour) was turned off by you — turn it on or pause the chain*: you switched the production off yourself.
|
||||||
|
- *Loop around Flour — the chain cannot be calculated*: the goods are needed to make themselves.
|
||||||
|
|
||||||
#### Mark a field free for chains
|
#### Mark a field free for chains
|
||||||
|
|
||||||
Open the field in the field guide and switch on **Free for chains (mod may change the crop)**. Only such a
|
Open the field in the field guide and switch on **Free for chains (mod may change the crop)**. Only such a
|
||||||
|
|||||||
Reference in New Issue
Block a user