Files
fs25-adsmartpickup/FS25_ADSmartPickup/gui/SmartPickupFrame.lua
T

408 lines
16 KiB
Lua

-- Smart Pickup — ESC-fane
-- Viser og redigerer runs og starter/stopper dem. Al logik ligger i ADRunsController;
-- her oversættes kun mellem GUI-elementer og run-felter. Alt kører i pcall så en GUI-fejl
-- aldrig tager spillet med sig.
SmartPickupFrame = {}
SmartPickupFrame._mt = Class(SmartPickupFrame, TabbedMenuFrameElement)
SmartPickupFrame.REFRESH_INTERVAL_MS = 1000
SmartPickupFrame.LOG_PREFIX = "ADSmartPickup: "
SmartPickupFrame.STATE_OFF, SmartPickupFrame.STATE_ON = 1, 2
function SmartPickupFrame.new(i18n, getAdEnv)
local self = SmartPickupFrame:superClass().new(nil, SmartPickupFrame._mt)
self.name = "SmartPickupFrame"
self.i18n = i18n
self.getAdEnv = getAdEnv
self.selectedRunId = nil
self.runIds, self.targetIds, self.poolGroups = {}, {}, {}
self.rowData = {} -- element -> {kind = "ingredient"|"vehicle", ...}
self.clonedRows = {}
self.refreshTimer = 0
self.statusOverride = nil
self.backButtonInfo = {inputAction = InputAction.MENU_BACK}
self.startButtonInfo = {inputAction = InputAction.MENU_ACTIVATE, text = i18n:getText("spu_button_start"), callback = function() self:onClickStartStop() end}
self.newButtonInfo = {inputAction = InputAction.MENU_EXTRA_1, text = i18n:getText("spu_button_new"), callback = function() self:onClickNew() end}
self.renameButtonInfo = {inputAction = InputAction.MENU_EXTRA_2, text = i18n:getText("spu_button_rename"), callback = function() self:onClickRename() end}
self.deleteButtonInfo = {inputAction = InputAction.MENU_CANCEL, text = i18n:getText("spu_button_delete"), callback = function() self:onClickDelete() end}
self:setMenuButtonInfo({self.backButtonInfo, self.startButtonInfo, self.newButtonInfo, self.renameButtonInfo, self.deleteButtonInfo})
return self
end
function SmartPickupFrame:delete()
SmartPickupFrame:superClass().delete(self)
end
function SmartPickupFrame:copyAttributes(src)
SmartPickupFrame:superClass().copyAttributes(self, src)
self.i18n, self.getAdEnv = src.i18n, src.getAdEnv
end
-- Prefabs skal ud af layoutet før de klones, ellers vises de selv som rækker.
function SmartPickupFrame:initialize()
for _, name in ipairs({"ingredientPrefab", "vehiclePrefab", "poolPrefab"}) do
local prefab = self[name]
if prefab ~= nil then
prefab:unlinkElement()
FocusManager:removeElement(prefab)
end
end
self.ingredientModeOption:setTexts({self.i18n:getText("spu_mode_auto"), self.i18n:getText("spu_mode_manual")})
local loopTexts = {self.i18n:getText("spu_loops_infinite")}
for loops = 1, ADRuns.MAX_LOOPS do
table.insert(loopTexts, tostring(loops))
end
self.loopsOption:setTexts(loopTexts)
end
function SmartPickupFrame:onFrameOpen()
SmartPickupFrame:superClass().onFrameOpen(self)
self.statusOverride = nil
self:safe(self.rebuild)
FocusManager:setFocus(self.runOption)
end
function SmartPickupFrame:onFrameClose()
SmartPickupFrame:superClass().onFrameClose(self)
end
-- Live-opdatering af status/traktor-rækker mens siden er åben (uden at genopbygge rækkerne).
function SmartPickupFrame:update(dt)
SmartPickupFrame:superClass().update(self, dt)
self.refreshTimer = self.refreshTimer + dt
if self.refreshTimer >= SmartPickupFrame.REFRESH_INTERVAL_MS then
self.refreshTimer = 0
self:safe(self.refreshLive)
end
end
function SmartPickupFrame:safe(fn, ...)
local ok, err = pcall(fn, self, ...)
if not ok then
Logging.warning(SmartPickupFrame.LOG_PREFIX .. "GUI-fejl: %s", tostring(err))
self.statusOverride = string.format(self.i18n:getText("spu_status_error"), tostring(err))
if self.statusText ~= nil then
self.statusText:setText(self.statusOverride)
end
end
end
function SmartPickupFrame:getRun()
return self.selectedRunId ~= nil and ADRuns.getRun(ADRunsController.store, self.selectedRunId) or nil
end
-- ---- opbygning ----
function SmartPickupFrame:clearRows()
for _, row in ipairs(self.clonedRows) do
row:delete()
end
self.clonedRows, self.rowData = {}, {}
end
function SmartPickupFrame:addRow(prefab, parent, data)
local row = prefab:clone(parent)
FocusManager:loadElementFromCustomValues(row)
table.insert(self.clonedRows, row)
local option = row:getDescendantByName("option")
if option ~= nil then
option:setTexts({self.i18n:getText("ui_no"), self.i18n:getText("ui_yes")})
self.rowData[option] = data
end
return row
end
function SmartPickupFrame:rebuild()
local adEnv = self.getAdEnv()
local store = ADRunsController.store
if self.selectedRunId == nil or ADRuns.getRun(store, self.selectedRunId) == nil then
self.selectedRunId = store.runs[1] ~= nil and store.runs[1].id or nil
end
self:clearRows()
self:fillRunOption(adEnv)
local run = self:getRun()
local hasRun = run ~= nil and adEnv ~= nil
for _, element in ipairs({self.targetOption, self.ingredientModeOption, self.waitPoolOption, self.loopsOption}) do
element:setDisabled(not hasRun)
end
if hasRun then
self:fillTargetOption(adEnv, run)
self.ingredientModeOption:setState(run.ingredientMode == "manual" and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, true)
self:fillIngredientRows(adEnv, run)
self:fillWaitPoolOption(adEnv, run)
self.loopsOption:setState(run.loops + 1, true)
self:fillVehicleRows(adEnv, run)
end
self:fillPoolRows(adEnv)
for _, box in ipairs({self.ingredientBox, self.vehicleBox, self.poolBox, self.boxLayout}) do
box:invalidateLayout()
end
self:refreshLive()
end
function SmartPickupFrame:fillRunOption(adEnv)
local texts, index = {}, 1
self.runIds = {}
for position, run in ipairs(ADRunsController.store.runs) do
local status = adEnv ~= nil and ADRunsController.getRunStatus(adEnv, run) or {running = 0, total = #run.vehicleIds}
table.insert(texts, string.format("%s (%d/%d)", run.name, status.running, status.total))
table.insert(self.runIds, run.id)
if run.id == self.selectedRunId then
index = position
end
end
if #texts == 0 then
texts = {self.i18n:getText("spu_noRuns")}
end
self.runOption:setTexts(texts)
self.runOption:setState(index, true)
self.runOption:setDisabled(#self.runIds == 0)
end
function SmartPickupFrame:fillTargetOption(adEnv, run)
local texts, index = {self.i18n:getText("spu_noTarget")}, 1
self.targetIds = {false}
for _, marker in ipairs(ADRunsController.getTargetMarkers(adEnv, g_currentMission:getFarmId())) do
table.insert(texts, tostring(marker.name))
table.insert(self.targetIds, marker.id)
if marker.id == run.targetWayPointId then
index = #texts
end
end
self.targetOption:setTexts(texts)
self.targetOption:setState(index, true)
end
function SmartPickupFrame:fillIngredientRows(adEnv, run)
if run.ingredientMode ~= "manual" then
return
end
local chosen = {}
for _, name in ipairs(run.ingredients) do
chosen[name] = true
end
for _, fillType in ipairs(ADRunsController.getTargetAcceptedFillTypes(adEnv, run)) do
local row = self:addRow(self.ingredientPrefab, self.ingredientBox, {kind = "ingredient", name = fillType.name})
row:getDescendantByName("title"):setText(tostring(fillType.title))
row:getDescendantByName("option"):setState(chosen[fillType.name] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, true)
end
end
function SmartPickupFrame:fillWaitPoolOption(adEnv, run)
local texts, index = {self.i18n:getText("spu_waitPool_all")}, 1
self.poolGroups = {""}
for _, pool in ipairs(ADRunsController.getWaitPools(adEnv)) do
table.insert(texts, string.format("%s (%d)", pool.group, pool.count))
table.insert(self.poolGroups, pool.group)
if pool.group == run.waitPoolGroup then
index = #texts
end
end
self.waitPoolOption:setTexts(texts)
self.waitPoolOption:setState(index, true)
end
function SmartPickupFrame:fillVehicleRows(adEnv, run)
local inRun = {}
for _, id in ipairs(run.vehicleIds) do
inRun[id] = true
end
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
local id = ADRunsController.getVehicleId(vehicle)
local row = self:addRow(self.vehiclePrefab, self.vehicleBox, {kind = "vehicle", vehicle = vehicle, row = nil})
self.rowData[row:getDescendantByName("option")].row = row
row:getDescendantByName("option"):setState(inRun[id] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, true)
row:getDescendantByName("title"):setText(self:describeVehicle(adEnv, run, vehicle, inRun[id]))
end
end
-- "Fastrac 2135 4WS (#1) — 1.000.000 l — ✔" / "… — ✖ ingen vogn"; reservationsnote fra Start bevares.
function SmartPickupFrame:describeVehicle(adEnv, run, vehicle, isInRun)
local label = ADRunsController.getVehicleLabel(adEnv, vehicle)
if not isInRun then
return label
end
local validation = ADRunsController.validateVehicle(adEnv, run, vehicle)
local capacity = validation.capacity ~= nil and string.format(self.i18n:getText("spu_capacity"), g_i18n:formatNumber(validation.capacity, 0)) or nil
local parts = {label}
if capacity ~= nil then
table.insert(parts, capacity)
end
if validation.ok then
table.insert(parts, "✔")
else
table.insert(parts, "✖ " .. self.i18n:getText("spu_reason_" .. tostring(validation.reason)))
end
return table.concat(parts, " — ")
end
function SmartPickupFrame:fillPoolRows(adEnv)
if adEnv == nil then
return
end
local occupied = {}
for _, claim in ipairs(ADUnloadWait.getClaims()) do
occupied[claim.marker.group] = occupied[claim.marker.group] or {}
table.insert(occupied[claim.marker.group], ADRunsController.getVehicleLabel(adEnv, claim.vehicle))
end
for _, pool in ipairs(ADRunsController.getWaitPools(adEnv)) do
local names = occupied[pool.group] or {}
local suffix = #names > 0 and (" (" .. table.concat(names, ", ") .. ")") or ""
local row = self:addRow(self.poolPrefab, self.poolBox, {kind = "pool"})
row:getDescendantByName("title"):setText(string.format(self.i18n:getText("spu_waitPoolRow"), pool.group, pool.count, #names, suffix))
end
end
-- Status + Start/Stop-knappens tekst, kaldes hvert sekund.
function SmartPickupFrame:refreshLive()
local adEnv = self.getAdEnv()
local run = self:getRun()
if adEnv == nil then
self.statusText:setText(self.i18n:getText("spu_status_noAd"))
return
end
local isRunning = false
if run ~= nil then
local status = ADRunsController.getRunStatus(adEnv, run)
isRunning = status.running > 0
local text = isRunning and string.format(self.i18n:getText("spu_status_running"), status.running, status.total)
or string.format(self.i18n:getText("spu_status_stopped"), status.total)
self.statusText:setText(self.statusOverride or text)
for option, data in pairs(self.rowData) do
if data.kind == "vehicle" and data.row ~= nil then
data.row:getDescendantByName("title"):setText(self:describeVehicle(adEnv, run, data.vehicle, option:getState() == SmartPickupFrame.STATE_ON))
end
end
else
self.statusText:setText(self.statusOverride or self.i18n:getText("spu_noRuns"))
end
self.startButtonInfo.text = self.i18n:getText(isRunning and "spu_button_stop" or "spu_button_start")
self.startButtonInfo.disabled = run == nil
self.renameButtonInfo.disabled = run == nil
self.deleteButtonInfo.disabled = run == nil
self:setMenuButtonInfoDirty()
end
-- ---- callbacks fra XML (state = 1-baseret indeks; element = det klikkede element) ----
function SmartPickupFrame:onClickRun(state)
self.selectedRunId = self.runIds[state]
self.statusOverride = nil
self:safe(self.rebuild)
end
function SmartPickupFrame:onClickTarget(state)
local run = self:getRun()
if run == nil then return end
-- targetIds[1] er false = "(vælg mål)"; modellen tolker false som "ryd målet"
ADRunsController.updateRun(run.id, {targetWayPointId = self.targetIds[state] or false})
self:safe(self.rebuild)
end
function SmartPickupFrame:onClickIngredientMode(state)
local run = self:getRun()
if run == nil then return end
ADRunsController.updateRun(run.id, {ingredientMode = state == SmartPickupFrame.STATE_ON and "manual" or "auto"})
self:safe(self.rebuild)
end
function SmartPickupFrame:onClickIngredient(state, element)
local run, data = self:getRun(), self.rowData[element]
if run == nil or data == nil then return end
local names = {}
for _, name in ipairs(run.ingredients) do
if name ~= data.name then table.insert(names, name) end
end
if state == SmartPickupFrame.STATE_ON then
table.insert(names, data.name)
end
ADRunsController.updateRun(run.id, {ingredients = names})
end
function SmartPickupFrame:onClickWaitPool(state)
local run = self:getRun()
if run == nil then return end
ADRunsController.updateRun(run.id, {waitPoolGroup = self.poolGroups[state] or ""})
end
function SmartPickupFrame:onClickLoops(state)
local run = self:getRun()
if run == nil then return end
ADRunsController.updateRun(run.id, {loops = state - 1})
end
function SmartPickupFrame:onClickVehicle(state, element)
local run, data = self:getRun(), self.rowData[element]
if run == nil or data == nil then return end
if state == SmartPickupFrame.STATE_ON then
local previousRunId = ADRunsController.assignVehicle(run.id, data.vehicle)
if previousRunId ~= nil then
local previous = ADRuns.getRun(ADRunsController.store, previousRunId)
self.statusOverride = string.format(self.i18n:getText("spu_movedFrom"), previous ~= nil and previous.name or tostring(previousRunId))
end
else
ADRunsController.unassignVehicle(data.vehicle)
end
self:safe(self.refreshLive)
end
-- ---- knapper i bundlinjen ----
function SmartPickupFrame:onClickStartStop()
self:safe(function(frame)
local adEnv, run = frame.getAdEnv(), frame:getRun()
if adEnv == nil or run == nil then return end
if ADRunsController.getRunStatus(adEnv, run).running > 0 then
ADRunsController.stopRun(adEnv, run.id)
frame.statusOverride = nil
else
local started, skipped = 0, 0
for _, result in ipairs(ADRunsController.startRun(adEnv, run.id)) do
if result.ok then started = started + 1 else skipped = skipped + 1 end
end
frame.statusOverride = string.format(frame.i18n:getText("spu_status_started"), started, skipped)
end
ADRunsController.save()
frame:rebuild()
end)
end
function SmartPickupFrame:onClickNew()
self:safe(function(frame)
local run = ADRunsController.createRun(string.format(frame.i18n:getText("spu_newRunName"), ADRunsController.store.nextId - 1))
frame.selectedRunId = run.id
frame.statusOverride = nil
frame:rebuild()
end)
end
function SmartPickupFrame:onClickRename()
local run = self:getRun()
if run == nil then return end
TextInputDialog.show(SmartPickupFrame.onRenameResult, self, run.name, self.i18n:getText("spu_renameTitle"), nil, 30, g_i18n:getText("button_ok"))
end
function SmartPickupFrame:onRenameResult(value, clickOk)
local run = self:getRun()
if clickOk and run ~= nil and value ~= nil and value:match("%S") then
ADRunsController.updateRun(run.id, {name = value})
self:safe(self.rebuild)
end
end
function SmartPickupFrame:onClickDelete()
local run = self:getRun()
if run == nil then return end
YesNoDialog.show(SmartPickupFrame.onDeleteResult, self, string.format(self.i18n:getText("spu_deleteConfirm"), run.name), self.i18n:getText("spu_button_delete"))
end
function SmartPickupFrame:onDeleteResult(yes)
local run = self:getRun()
if yes and run ~= nil then
ADRunsController.deleteRun(run.id)
self.selectedRunId = nil
self:safe(self.rebuild)
end
end