236 lines
9.2 KiB
Lua
236 lines
9.2 KiB
Lua
-- AD Flow View
|
|
-- Rene regler for Flows-fanens oprydning (ingen spil-API): tavlens grupper og rækkefølge, hvilke
|
|
-- traktorer og bygninger der vises som standard, og hvad "Vis alle" tilføjer.
|
|
|
|
ADFlowView = {}
|
|
|
|
-- Naturlig sortering: tal i navne sammenlignes som tal ("Biogas 9" < "Biogas 10").
|
|
local function naturalKey(text)
|
|
local parts = {}
|
|
local value = string.lower(tostring(text or ""))
|
|
local position = 1
|
|
while position <= #value do
|
|
local digits = string.match(value, "^%d+", position)
|
|
if digits ~= nil then
|
|
table.insert(parts, {number = tonumber(digits)})
|
|
position = position + #digits
|
|
else
|
|
local chunk = string.match(value, "^%D+", position)
|
|
table.insert(parts, {text = chunk})
|
|
position = position + #chunk
|
|
end
|
|
end
|
|
return parts
|
|
end
|
|
|
|
function ADFlowView.naturalLess(a, b)
|
|
local ka, kb = naturalKey(a), naturalKey(b)
|
|
for index = 1, math.max(#ka, #kb) do
|
|
local pa, pb = ka[index], kb[index]
|
|
if pa == nil then return true end
|
|
if pb == nil then return false end
|
|
if pa.number ~= nil and pb.number ~= nil then
|
|
if pa.number ~= pb.number then return pa.number < pb.number end
|
|
elseif pa.number ~= nil or pb.number ~= nil then
|
|
return pa.number ~= nil
|
|
elseif pa.text ~= pb.text then
|
|
return pa.text < pb.text
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function sortedBy(list, field)
|
|
local copy = {}
|
|
for _, item in ipairs(list or {}) do table.insert(copy, item) end
|
|
table.sort(copy, function(a, b)
|
|
if a[field] ~= b[field] then return ADFlowView.naturalLess(a[field], b[field]) end
|
|
return tostring(a.id) < tostring(b.id)
|
|
end)
|
|
return copy
|
|
end
|
|
|
|
-- Tavlen: forsyning ("Ind til bygning") først, så udkørsel ("Ud fra bygning"); hver gruppe efter navn.
|
|
-- {{kind, runs}, ...}; tomme grupper udelades.
|
|
-- Spillerens egne flows først (forsyning, udkørsel), så kædernes egne flows (run.chain) samlet til sidst
|
|
-- (in-game 30/9: "Kæde: …"-flows blandede sig alfabetisk ind mellem de flows spilleren selv har sat op).
|
|
function ADFlowView.boardGroups(runs)
|
|
local byKind = {supply = {}, outbound = {}}
|
|
local chainRuns = {}
|
|
for _, run in ipairs(runs or {}) do
|
|
if run.chain ~= nil and run.chain ~= "" then
|
|
table.insert(chainRuns, run)
|
|
else
|
|
table.insert(byKind[run.kind == "outbound" and "outbound" or "supply"], run)
|
|
end
|
|
end
|
|
local groups = {}
|
|
for _, kind in ipairs({"supply", "outbound"}) do
|
|
if #byKind[kind] > 0 then
|
|
table.insert(groups, {kind = kind, chain = false, runs = sortedBy(byKind[kind], "name")})
|
|
end
|
|
end
|
|
if #chainRuns > 0 then table.insert(groups, {kind = "supply", chain = true, runs = sortedBy(chainRuns, "name")}) end
|
|
return groups
|
|
end
|
|
|
|
-- Kort maskinnavn til lister: " Unrealistic" og parenteser som "(+ Custom Crops)" fjernes (lange navne blev ombrudt
|
|
-- med traktorens nummer alene på næste linje, in-game 30/9). Bliver intet tilbage, bruges det fulde navn.
|
|
function ADFlowView.shortVehicleName(name)
|
|
local full = tostring(name or "")
|
|
local short = full:gsub("%s*%b()", ""):gsub("%s+[Uu]nrealistic", ""):gsub("^%s+", ""):gsub("%s+$", "")
|
|
if short == "" then return full end
|
|
return short
|
|
end
|
|
|
|
-- Kædeflows uden traktor foldes sammen i listen (de venter på en ledig traktor/vogn). -> active, idle
|
|
function ADFlowView.splitIdle(runs)
|
|
local active, idle = {}, {}
|
|
for _, run in ipairs(runs or {}) do
|
|
table.insert(#(run.vehicleIds or {}) > 0 and active or idle, run)
|
|
end
|
|
return active, idle
|
|
end
|
|
|
|
-- Navn i listen: kædeflows står under overskriften "Kædernes egne flows", så "Kæde: " fjernes (ellers skjuler
|
|
-- afkortningen hvilken bygning det gælder).
|
|
function ADFlowView.listName(run)
|
|
local name = tostring(run.name or "")
|
|
if run.chain ~= nil and run.chain ~= "" then
|
|
name = name:gsub("^Kæde:%s*", ""):gsub("^Chain:%s*", "")
|
|
end
|
|
return name
|
|
end
|
|
|
|
-- Samme rækkefølge som tavlen, som én liste (flow-vælgeren).
|
|
function ADFlowView.orderedRuns(runs)
|
|
local result = {}
|
|
for _, group in ipairs(ADFlowView.boardGroups(runs)) do
|
|
for _, run in ipairs(group.runs) do table.insert(result, run) end
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- Traktorer: vehicles = {{id, label, state = "in"|"free"|"other"|"blocked"}}. Standard: flowets egne,
|
|
-- så ledige der kan bruges. showAll tilføjer traktorer i andre flows og låste (vogn passer ikke).
|
|
local STATE_ORDER = {"in", "free", "other", "blocked"}
|
|
|
|
function ADFlowView.vehicleRows(vehicles, showAll)
|
|
local byState = {}
|
|
for _, entry in ipairs(vehicles or {}) do
|
|
byState[entry.state] = byState[entry.state] or {}
|
|
table.insert(byState[entry.state], entry)
|
|
end
|
|
local rows = {}
|
|
for _, state in ipairs(STATE_ORDER) do
|
|
if showAll or state == "in" or state == "free" then
|
|
for _, entry in ipairs(sortedBy(byState[state], "label")) do table.insert(rows, entry) end
|
|
end
|
|
end
|
|
return rows
|
|
end
|
|
|
|
function ADFlowView.hiddenCount(all, shown)
|
|
return #(all or {}) - #(shown or {})
|
|
end
|
|
|
|
-- Bygninger: buildings = {{id, name, hasMarker}}, chosen = {[id] = true}. Valgte først; bygninger uden
|
|
-- markør kun hvis valgte eller showAll (de kan alligevel ikke bruges før en markør er sat).
|
|
function ADFlowView.buildingRows(buildings, chosen, showAll)
|
|
local shown, withoutMarker = {}, {}
|
|
for _, building in ipairs(buildings or {}) do
|
|
if (chosen ~= nil and chosen[building.id]) or building.hasMarker then
|
|
table.insert(shown, building)
|
|
elseif showAll then
|
|
table.insert(withoutMarker, building)
|
|
end
|
|
end
|
|
-- fast rækkefølge: bygningsnavn, så markørnavn (label) — et tryk må ikke flytte rækken under markøren
|
|
local function byNameThenLabel(list)
|
|
local copy = {}
|
|
for _, item in ipairs(list) do table.insert(copy, item) end
|
|
table.sort(copy, function(a, b)
|
|
if a.name ~= b.name then return ADFlowView.naturalLess(a.name, b.name) end
|
|
if (a.label or "") ~= (b.label or "") then return ADFlowView.naturalLess(a.label or "", b.label or "") end
|
|
return tostring(a.id) < tostring(b.id)
|
|
end)
|
|
return copy
|
|
end
|
|
local rows = {}
|
|
for _, list in ipairs({byNameThenLabel(shown), byNameThenLabel(withoutMarker)}) do
|
|
for _, building in ipairs(list) do table.insert(rows, building) end
|
|
end
|
|
return rows
|
|
end
|
|
|
|
-- "Chicken Coop x3 + Cow Barn": ens navne slås sammen med antal, i rækkefølge efter første forekomst.
|
|
function ADFlowView.joinCounted(names, separator, empty)
|
|
local order, counts = {}, {}
|
|
for _, name in ipairs(names or {}) do
|
|
local key = tostring(name)
|
|
if counts[key] == nil then
|
|
counts[key] = 0
|
|
table.insert(order, key)
|
|
end
|
|
counts[key] = counts[key] + 1
|
|
end
|
|
if #order == 0 then
|
|
return empty
|
|
end
|
|
local parts = {}
|
|
for _, key in ipairs(order) do
|
|
table.insert(parts, counts[key] > 1 and string.format("%s x%d", key, counts[key]) or key)
|
|
end
|
|
return table.concat(parts, separator)
|
|
end
|
|
|
|
-- Lang tekst -> linjer på højst maxChars (ombrydes ved mellemrum; indrykning gentages på hver linje,
|
|
-- så en indrykket forklaring forbliver indrykket). Ét ord længere end linjen deles ikke.
|
|
function ADFlowView.wrapText(text, maxChars)
|
|
text = tostring(text or "")
|
|
if #text <= maxChars then return {text} end
|
|
local indent = string.match(text, "^%s*") or ""
|
|
local lines, line = {}, indent
|
|
for word in string.gmatch(string.sub(text, #indent + 1), "%S+") do
|
|
if line ~= indent and #line + 1 + #word > maxChars then
|
|
table.insert(lines, line)
|
|
line = indent .. word
|
|
else
|
|
line = line == indent and (indent .. word) or (line .. " " .. word)
|
|
end
|
|
end
|
|
if line ~= indent then table.insert(lines, line) end
|
|
return lines
|
|
end
|
|
|
|
-- ---- status pr. flow (Flows-listen) ----
|
|
|
|
ADFlowView.LEVEL_COLORS = {
|
|
running = {0.30, 0.75, 0.35, 1},
|
|
waiting = {0.95, 0.80, 0.25, 1},
|
|
error = {0.90, 0.35, 0.30, 1},
|
|
stopped = {0.55, 0.55, 0.55, 1},
|
|
}
|
|
|
|
-- s = {running, total, stopped, canStop, warning, reason, lastStartFailed} -> "running"|"waiting"|"error"|"stopped"
|
|
-- Hvilke udkørselsregler betyder noget for varernes politik? policies = {"store"|"sell"|"sellElseStore"|"off"}
|
|
-- -> sells, stores. Ingen varer med salg/lager endnu = begge (reglerne kan sættes før varerne vælges).
|
|
function ADFlowView.policyUse(policies)
|
|
local sells, stores = false, false
|
|
for _, policy in ipairs(policies or {}) do
|
|
if policy == "sell" or policy == "sellElseStore" then sells = true end
|
|
if policy == "store" or policy == "sellElseStore" then stores = true end
|
|
end
|
|
if not sells and not stores then return true, true end
|
|
return sells, stores
|
|
end
|
|
|
|
function ADFlowView.flowStatus(s)
|
|
if s.stopped then return "stopped" end
|
|
if s.warning ~= nil then return "error" end
|
|
if (s.running or 0) == 0 and s.lastStartFailed then return "error" end
|
|
if (s.running or 0) > 0 and s.reason == nil then return "running" end
|
|
if (s.running or 0) > 0 or s.canStop then return "waiting" end
|
|
return "stopped"
|
|
end
|