-- AD Flow Guide -- "Opsæt flow": flowguidens trin oven på ADGuide (ren Lua, ingen spil-API) — samme trin-skærm og knapper -- som markguiden. Kladden er en kopi af flowets felter, så intet ændres før [Gem]/[Start flow]. Det -- spil-afhængige kommer i `context` (GUI'et henter det ud fra kladden, se previewRun): -- {buildings = {{id, name, label, hasMarker, text}} -- kandidater til Fra (udkørsel) / Til (forsyning) -- fillTypes = {{name, title}} -- varer målet tager (forsyning, Vælg selv) -- products = {{name, title}} -- varer kilderne har (udkørsel) -- storeBuildings = {{id, name, text}} -- lagre der kan tage flowets lagervarer -- waitPools = {{group, count}} -- vehicles = {{id, label, text, otherRunId, otherRunName, blocked}}} -- label = kort navn (resumé) -- Gem: ADRunsController.createRun/updateRun + assignVehicle/unassignVehicle (GUI'et). ADFlowGuide = {} ADFlowGuide.ORDER = {"name", "direction", "buildings", "goods", "store", "rules", "wait", "tractors", "summary"} ADFlowGuide.POLICIES = {"off", "store", "sell", "sellElseStore"} ADFlowGuide.MAX_NAMED_TRACTORS = 3 ADFlowGuide.MAX_NAMED_GOODS = 3 local function L(key, args) return {key = key, args = args} end local function T(text) return {text = text} end local function isOut(guide) return guide.draft.kind == "outbound" end local function hasStorePolicy(draft) for _, product in ipairs(draft.products or {}) do if product.policy == "store" or product.policy == "sellElseStore" then return true end end return false end local VISIBLE = { name = function(guide) return guide.draft.isNew == true end, store = function(guide) return isOut(guide) and hasStorePolicy(guide.draft) end, } function ADFlowGuide.isVisible(step, guide) local rule = VISIBLE[step] if rule ~= nil then return rule(guide) == true end for _, id in ipairs(ADFlowGuide.ORDER) do if id == step then return true end end return false end ADFlowGuide.DEF = {order = ADFlowGuide.ORDER, isVisible = ADFlowGuide.isVisible} -- Overskrift pr. trin; "Fra"/"Til" følger retningen. function ADFlowGuide.stepKey(step, guide) if step == "buildings" then return (guide ~= nil and isOut(guide)) and "spu_guide_flow_step_from" or "spu_guide_flow_step_to" end return "spu_guide_flow_step_" .. tostring(step) end local KIND_HINTS = {buildings = true, goods = true, rules = true} function ADFlowGuide.hintKey(step, guide) if KIND_HINTS[step] then return "spu_guide_flow_hint_" .. step .. ((guide ~= nil and isOut(guide)) and "_out" or "_in") end return "spu_guide_flow_hint_" .. tostring(step) end -- ---- kladde ---- local RUN_FIELDS = { "id", "name", "kind", "sourceBuildingIds", "targetBuildingIds", "storeBuildingIds", "products", "ingredientMode", "ingredients", "sellAtPercent", "storeAbovePercent", "collectRound", "loops", "returnBelowPercent", "waitPoolGroup", "vehicleIds", } local function draftFrom(run, isNew) local draft = {} for _, key in ipairs(RUN_FIELDS) do draft[key] = ADGuide.deepCopy(run[key]) end for _, key in ipairs({"sourceBuildingIds", "targetBuildingIds", "storeBuildingIds", "products", "ingredients", "vehicleIds"}) do draft[key] = draft[key] or {} end draft.kind = draft.kind == "outbound" and "outbound" or "supply" draft.ingredientMode = draft.ingredientMode == "manual" and "manual" or "auto" draft.sellAtPercent = draft.sellAtPercent or ADRuns.DEFAULT_SELL_AT_PERCENT draft.storeAbovePercent = draft.storeAbovePercent or ADRuns.DEFAULT_STORE_ABOVE_PERCENT draft.returnBelowPercent = draft.returnBelowPercent or ADRuns.DEFAULT_RETURN_BELOW_PERCENT draft.loops = draft.loops or 0 draft.waitPoolGroup = draft.waitPoolGroup or "" draft.collectRound = draft.collectRound == true draft.isNew = isNew == true draft.showAllBuildings = false draft.storeAuto = #draft.storeBuildingIds == 0 return draft end function ADFlowGuide.open(run, context, options) return ADGuide.new(ADFlowGuide.DEF, draftFrom(run, false), context, options) end -- Nyt flow: intet oprettes før [Gem]; kladden har standardværdierne fra ADRuns.createRun. function ADFlowGuide.openNew(name, context) local _, run = ADRuns.createRun(ADRuns.newStore(), name, "supply") local draft = draftFrom(run, true) draft.id = nil return ADGuide.new(ADFlowGuide.DEF, draft, context) end -- Run-formet tabel til controllerens opslag (varer, vogne) før noget er gemt: markører fra de valgte -- bygninger. markerIds(buildingId, role) -> {wayPointId, ...}. function ADFlowGuide.previewRun(draft, markerIds) local run = ADGuide.deepCopy(draft) local function resolve(ids, role) local result, seen = {}, {} for _, buildingId in ipairs(ids or {}) do for _, wayPointId in ipairs(markerIds(buildingId, role) or {}) do if not seen[wayPointId] then seen[wayPointId] = true table.insert(result, wayPointId) end end end return result end run.sourceWayPointIds = resolve(draft.sourceBuildingIds, "load") run.targetWayPointIds = resolve(draft.targetBuildingIds, "unload") run.targetWayPointId = run.targetWayPointIds[1] return run end -- ---- valg ---- local SET_VALUES = { kind = true, ingredientMode = true, sellAtPercent = true, storeAbovePercent = true, loops = true, returnBelowPercent = true, waitPoolGroup = true, } local BOOL_VALUES = {showAllBuildings = true, storeAuto = true, collectRound = true} local LIST_VALUES = {ingredients = true, storeBuildingIds = true, vehicleIds = true} local function setPolicy(products, name, policy) local result = {} for _, product in ipairs(products or {}) do if product.fillType ~= name then table.insert(result, {fillType = product.fillType, policy = product.policy}) end end if policy ~= "off" then table.insert(result, {fillType = name, policy = policy}) end return result end function ADFlowGuide.choose(guide, id, value) local draft = ADGuide.deepCopy(guide.draft) if SET_VALUES[id] then draft[id] = value elseif BOOL_VALUES[id] then draft[id] = value == true elseif LIST_VALUES[id] then draft[id] = ADGuide.toggleItem(draft[id], value.item, value.on) elseif id == "buildings" then local key = draft.kind == "outbound" and "sourceBuildingIds" or "targetBuildingIds" draft[key] = ADGuide.toggleItem(draft[key], value.item, value.on) elseif id == "policy" then draft.products = setPolicy(draft.products, value.item, value.value) elseif id == "name" then if type(value) ~= "string" or not value:match("%S") then return guide end draft.name = value else return guide end return ADGuide.withDraft(guide, draft) end -- ---- rækker ---- local function choice(id, title, options, value, item) return {type = "choice", id = id, title = title, options = options, value = value, item = item} end local function toggle(id, title, value) return {type = "toggle", id = id, title = title, off = L("ui_no"), on = L("ui_yes"), value = value == true} end local function check(id, item, text, value) return {type = "check", id = id, item = item, title = T(text), value = value == true} end local function text(key, args) return {type = "text", label = L(key, args)} end local function percentOptions(from, to, step) local options = {} for percent = from, to, step do table.insert(options, {label = T(string.format("%d %%", percent)), value = percent}) end return options end local function chosenSet(list) local set = {} for _, value in ipairs(list or {}) do set[value] = true end return set end local function buildingIds(draft) return draft.kind == "outbound" and draft.sourceBuildingIds or draft.targetBuildingIds end local function rowsName(guide) return { {type = "text", label = T(guide.draft.name or "")}, {type = "link", id = "rename", value = true, label = L("spu_guide_flow_rename")}, } end local function rowsDirection(guide) return {choice("kind", L("spu_direction"), { {label = L("spu_direction_in"), value = "supply"}, {label = L("spu_direction_out"), value = "outbound"}}, guide.draft.kind)} end -- Bygninger i fast rækkefølge (navn, markør); uden markør kun med "Vis alle" (eller når valgt). local function rowsBuildings(guide) local draft, rows = guide.draft, {} local chosen = chosenSet(buildingIds(draft)) local candidates = guide.context.buildings or {} local shown = ADFlowView.buildingRows(candidates, chosen, draft.showAllBuildings) local hidden = ADFlowView.hiddenCount(candidates, shown) if hidden > 0 or draft.showAllBuildings then table.insert(rows, toggle("showAllBuildings", L("spu_showAllBuildings", {hidden}), draft.showAllBuildings)) end for _, building in ipairs(shown) do table.insert(rows, check("buildings", building.id, building.text or building.name, chosen[building.id])) end if #shown == 0 then table.insert(rows, text("spu_noBuildings")) end return rows end local function rowsGoods(guide) local draft, context, rows = guide.draft, guide.context, {} if isOut(guide) then local products = context.products or {} if #products == 0 then return {text("spu_guide_flow_noProducts")} end local options = {} for _, policy in ipairs(ADFlowGuide.POLICIES) do table.insert(options, {label = L("spu_policy_" .. policy), value = policy}) end for _, product in ipairs(products) do table.insert(rows, choice("policy", T(product.text or product.title), options, ADRuns.getProductPolicy(draft, product.name), product.name)) end return rows end table.insert(rows, choice("ingredientMode", L("spu_guide_flow_goods"), { {label = L("spu_guide_flow_goodsAutoChoice"), value = "auto"}, {label = L("spu_guide_manual"), value = "manual"}}, draft.ingredientMode)) if draft.ingredientMode ~= "manual" then table.insert(rows, text("spu_guide_flow_goodsAutoText")) return rows end local chosen = chosenSet(draft.ingredients) for _, fillType in ipairs(context.fillTypes or {}) do table.insert(rows, check("ingredients", fillType.name, " " .. tostring(fillType.title), chosen[fillType.name])) end if #(context.fillTypes or {}) == 0 then table.insert(rows, text("spu_guide_flow_noGoodsTo")) end return rows end local function rowsStore(guide) local draft, rows = guide.draft, {} table.insert(rows, choice("storeAuto", L("spu_guide_store"), { {label = L("spu_guide_flow_storeAutoChoice"), value = true}, {label = L("spu_guide_manual"), value = false}}, draft.storeAuto)) if draft.storeAuto then table.insert(rows, text("spu_guide_flow_storeAutoText")) return rows end local chosen = chosenSet(draft.storeBuildingIds) for _, building in ipairs(guide.context.storeBuildings or {}) do table.insert(rows, check("storeBuildingIds", building.id, building.text, chosen[building.id])) end if #(guide.context.storeBuildings or {}) == 0 then table.insert(rows, text("spu_noBuildings")) end return rows end local function rowsRules(guide) local draft = guide.draft if isOut(guide) then return { choice("sellAtPercent", L("spu_sellAt"), percentOptions(ADRuns.MIN_SELL_AT_PERCENT, ADRuns.MAX_SELL_AT_PERCENT, 5), draft.sellAtPercent), choice("storeAbovePercent", L("spu_storeAbove"), percentOptions(ADRuns.MIN_STORE_ABOVE_PERCENT, ADRuns.MAX_STORE_ABOVE_PERCENT, 5), draft.storeAbovePercent), toggle("collectRound", L("spu_collectRound"), draft.collectRound), } end local loops = {{label = L("spu_loops_infinite"), value = 0}} for count = 1, ADRuns.MAX_LOOPS do table.insert(loops, {label = T(tostring(count)), value = count}) end return { choice("loops", L("spu_loops"), loops, draft.loops), choice("returnBelowPercent", L("spu_returnBelow"), percentOptions(ADRuns.MIN_RETURN_BELOW_PERCENT, ADRuns.MAX_RETURN_BELOW_PERCENT, 10), draft.returnBelowPercent), } end local function rowsWait(guide) local options = {{label = L("spu_waitPool_all"), value = ""}} for _, pool in ipairs(guide.context.waitPools or {}) do table.insert(options, {label = T(string.format("%s (%d)", tostring(pool.group), pool.count or 0)), value = pool.group}) end return {choice("waitPoolGroup", L("spu_waitPool"), options, guide.draft.waitPoolGroup or "")} end -- Traktorer: flowets egne først, så de ledige. Traktorer i andre flows og blokerede (vognen passer ikke) -- skjules med en linje hver. local function rowsTractors(guide) local draft, rows = guide.draft, {} local inRun = chosenSet(draft.vehicleIds) local entries = {} local other, blocked = 0, 0 for _, vehicle in ipairs(guide.context.vehicles or {}) do local state = "free" if inRun[vehicle.id] then state = "in" elseif vehicle.otherRunId ~= nil and vehicle.otherRunId ~= draft.id then state = "other" other = other + 1 elseif vehicle.blocked then state = "blocked" blocked = blocked + 1 end table.insert(entries, {id = vehicle.id, label = tostring(vehicle.text), state = state}) end for _, entry in ipairs(ADFlowView.vehicleRows(entries, false)) do table.insert(rows, check("vehicleIds", entry.id, entry.label, entry.state == "in")) end if #rows == 0 then table.insert(rows, text("spu_guide_flow_noTractors")) end if other > 0 then table.insert(rows, text("spu_guide_flow_otherFlows", {other})) end if blocked > 0 then table.insert(rows, text("spu_guide_flow_blocked", {blocked})) end return rows end local function rowsSummary(guide) local rows = {} for _, line in ipairs(ADFlowGuide.summary(guide)) do table.insert(rows, {type = "link", id = "jump", value = line.step, line = line}) end return rows end local ROWS = { name = rowsName, direction = rowsDirection, buildings = rowsBuildings, goods = rowsGoods, store = rowsStore, rules = rowsRules, wait = rowsWait, tractors = rowsTractors, summary = rowsSummary, } function ADFlowGuide.rows(guide) local builder = ROWS[guide.step] return builder ~= nil and builder(guide) or {} end -- ---- resumé ---- -- Korte navne til resuméet (field, ellers navn, ellers rækkens tekst). local function namesFor(ids, entries, field) local byId = {} for _, entry in ipairs(entries or {}) do byId[entry.id or entry.name] = entry[field] or entry.name or entry.text end local names = {} for _, id in ipairs(ids or {}) do table.insert(names, tostring(byId[id] or id)) end return names end local SUMMARY = { name = function(guide) return {T(guide.draft.name or "")} end, direction = function(guide) return {L(isOut(guide) and "spu_direction_out" or "spu_direction_in")} end, buildings = function(guide) local names = namesFor(buildingIds(guide.draft), guide.context.buildings, "name") if #names == 0 then return {L("spu_guide_flow_none")} end return {T(ADFlowView.joinCounted(names, " + ", ""))} end, goods = function(guide) local draft = guide.draft if isOut(guide) then local parts = {} local titles = {} for _, product in ipairs(guide.context.products or {}) do titles[product.name] = product.title end for _, product in ipairs(draft.products or {}) do table.insert(parts, {key = "spu_policy_" .. product.policy, prefix = tostring(titles[product.fillType] or product.fillType) .. ": "}) end if #parts == 0 then return {L("spu_guide_flow_none")} end return parts end if draft.ingredientMode ~= "manual" then return {L("spu_guide_flow_goodsAuto")} end if #draft.ingredients == 0 then return {L("spu_guide_flow_none")} end if #draft.ingredients > ADFlowGuide.MAX_NAMED_GOODS then return {L("spu_guide_flow_sumGoods", {#draft.ingredients})} end local byName = {} for _, fillType in ipairs(guide.context.fillTypes or {}) do byName[fillType.name] = fillType.title end local names = {} for _, name in ipairs(draft.ingredients) do table.insert(names, tostring(byName[name] or name)) end return {T(table.concat(names, " + "))} end, store = function(guide) local draft = guide.draft if draft.storeAuto or #draft.storeBuildingIds == 0 then return {L("spu_guide_sum_auto")} end if #draft.storeBuildingIds > 2 then return {L("spu_guide_sum_stores", {#draft.storeBuildingIds})} end return {T(table.concat(namesFor(draft.storeBuildingIds, guide.context.storeBuildings, "name"), " + "))} end, rules = function(guide) local draft = guide.draft if isOut(guide) then local parts = {L("spu_guide_flow_sumSellAt", {draft.sellAtPercent}), L("spu_guide_flow_sumStoreAbove", {draft.storeAbovePercent})} if draft.collectRound then table.insert(parts, L("spu_guide_flow_sumRound")) end return parts end local loops = (draft.loops or 0) > 0 and L("spu_guide_flow_sumLoops", {draft.loops}) or L("spu_guide_flow_sumLoopsInf") return {loops, L("spu_guide_flow_sumReturn", {draft.returnBelowPercent})} end, wait = function(guide) local group = guide.draft.waitPoolGroup or "" return {group == "" and L("spu_waitPool_all") or T(group)} end, tractors = function(guide) local ids = guide.draft.vehicleIds if #ids == 0 then return {L("spu_guide_flow_sumNoTractors")} end if #ids > ADFlowGuide.MAX_NAMED_TRACTORS then return {L("spu_guide_flow_sumTractors", {#ids})} end return {T(table.concat(namesFor(ids, guide.context.vehicles, "label"), ", "))} end, } -- Resumé-linjer {step, title, parts}: Navn (altid) + de synlige trin. function ADFlowGuide.summary(guide) local lines = {} for _, step in ipairs(ADFlowGuide.ORDER) do local build = SUMMARY[step] if build ~= nil and (step == "name" or ADFlowGuide.isVisible(step, guide)) then table.insert(lines, {step = step, title = L(ADFlowGuide.stepKey(step, guide)), parts = build(guide)}) end end return lines end -- ---- gem ---- function ADFlowGuide.result(guide) local draft = guide.draft local storeIds = ADGuide.deepCopy(draft.storeBuildingIds) if ADFlowGuide.isVisible("store", guide) and draft.storeAuto then storeIds = {} end local changes = { kind = draft.kind, sourceBuildingIds = ADGuide.deepCopy(draft.sourceBuildingIds), targetBuildingIds = ADGuide.deepCopy(draft.targetBuildingIds), storeBuildingIds = storeIds, products = ADGuide.deepCopy(draft.products), ingredientMode = draft.ingredientMode, ingredients = ADGuide.deepCopy(draft.ingredients), sellAtPercent = draft.sellAtPercent, storeAbovePercent = draft.storeAbovePercent, collectRound = draft.collectRound, loops = draft.loops, returnBelowPercent = draft.returnBelowPercent, waitPoolGroup = draft.waitPoolGroup or "", } if draft.name ~= nil and draft.name:match("%S") then changes.name = draft.name end return {isNew = draft.isNew == true, runId = draft.id, name = draft.name, kind = draft.kind, changes = changes, vehicleIds = ADGuide.deepCopy(draft.vehicleIds)} end -- Traktorer der skal tildeles/fjernes efter gem. function ADFlowGuide.vehicleChanges(oldIds, newIds) local old, new = chosenSet(oldIds), chosenSet(newIds) local added, removed = {}, {} for _, id in ipairs(newIds or {}) do if not old[id] then table.insert(added, id) end end for _, id in ipairs(oldIds or {}) do if not new[id] then table.insert(removed, id) end end return added, removed end