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

428 lines
23 KiB
Lua

-- Smart Pickup — fanen Marker
-- Oversigt: tabel over alle gårdens marker (Mark · Afgrøde · Plan · Kørsel · Status · Maskiner); klik på en
-- række viser markens resumé (én linje pr. emne — klik på en linje for at rette netop det trin), hele
-- statusteksten og markøren. [Opsæt mark] åbner guiden (gui/SmartPickupFieldGuide.lua + SmartPickupGuide.lua),
-- [Markflåde] viser den fælles markflåde. Al logik ligger i ADFieldJobs/ADFieldModel/ADFieldView/ADFieldGuide.
-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua).
-- Markflådens rækker (vehiclePrefab/ingredientPrefab) routes til onClickFieldRow.
SmartPickupFrame.FIELD_ROW_KINDS = {fleetVehicle = true, fleetMode = true, fleetManual = true}
SmartPickupFrame.ROLE_KEYS = {
mower = "spu_field_role_mower", tedder = "spu_field_role_tedder", rake = "spu_field_role_rake",
collector = "spu_field_role_collector", harvester = "spu_field_role_harvester", unloader = "spu_field_role_unloader",
mulcher = "spu_field_role_mulcher", limer = "spu_field_role_limer", plow = "spu_field_role_plow",
cultivator = "spu_field_role_cultivator", seeder = "spu_field_role_seeder", roller = "spu_field_role_roller",
weeder = "spu_field_role_weeder", fertilizer = "spu_field_role_fertilizer",
baler = "spu_field_role_baler", baleCollector = "spu_field_role_baleCollector",
}
-- ---- tekster ----
function SmartPickupFrame:roleText(role)
return self.i18n:getText(SmartPickupFrame.ROLE_KEYS[role] or "spu_field_role_mower")
end
function SmartPickupFrame:fieldReasonText(reason)
if type(reason) ~= "table" then return nil end
if reason.text ~= nil then return reason.text end
local first = reason.name or (reason.role ~= nil and self:roleText(reason.role)) or ""
local template = self.i18n:getText(reason.key)
local args = reason.args or {}
local ok, text = pcall(string.format, template, first, args[1] or 0, args[2] or 0)
return ok and text or template
end
function SmartPickupFrame:soilText(info)
local texts = {}
for _, part in ipairs(ADFieldView.soilParts(info)) do
table.insert(texts, self.i18n:getText("spu_field_need_" .. part))
end
return table.concat(texts, ", ")
end
-- Kort årsag til tabellens statuskolonne ("bredde ukendt" i stedet for hele forklaringen, som står i
-- markens detaljer). Fritekst-årsager (fx "… er tom for X") forkortes til "se detaljer".
function SmartPickupFrame:fieldReasonShort(reason)
if type(reason) ~= "table" then return nil end
if reason.text ~= nil then return self.i18n:getText("spu_field_reasonShort_text") end
local shortKey = reason.key ~= nil and string.gsub(reason.key, "^spu_field_reason_", "spu_field_reasonShort_") or nil
if shortKey ~= nil then
local okHas, has = pcall(function() return self.i18n.hasText == nil or self.i18n:hasText(shortKey) end)
if okHas and has then return self.i18n:getText(shortKey) end
end
return self:fieldReasonText(reason)
end
-- short = tabellens korte udgave (trin + kort årsag); ellers hele teksten (markens detaljer).
function SmartPickupFrame:fieldStatusText(field, info, short)
local text = self.i18n:getText(ADFieldView.statusKey(field, info))
if info.markerId == nil then
return string.format(text, info.id)
end
if short then
if (field.stage == "idle" or field.stage == "ready") and info.kind ~= "bare" and info.readyRatio ~= nil then
text = string.format("%s %d %%", text, math.floor((info.readyRatio or 0) * 100 + 0.5))
end
local reason = self:fieldReasonShort(field.reason)
return reason ~= nil and (text .. ": " .. reason) or text
end
if (field.stage == "idle" or field.stage == "ready") and info.kind ~= "bare" and info.readyRatio ~= nil then
text = string.format("%s %d %%", text, math.floor((info.readyRatio or 0) * 100 + 0.5))
end
local decision = ADFieldJobs.lastDecision[info.id]
if decision ~= nil and decision.care ~= nil and not ADFieldModel.isWorking(field) then
text = text .. " — " .. string.format(self.i18n:getText("spu_field_careNeed"), self.i18n:getText("spu_field_stage_" .. decision.care))
end
local target = field.plan.targetFruit
if target ~= nil and target ~= "" and info.kind == "bare" and field.stage == "idle" and not ADFieldJobs.environment().plantable(target) then
text = text .. " — " .. string.format(self.i18n:getText("spu_field_waitSowing"), self:fruitTitle(target))
end
local reason = self:fieldReasonText(field.reason)
if reason ~= nil then text = text .. ": " .. reason end
return text
end
function SmartPickupFrame:fruitTitle(name)
local fruit = g_fruitTypeManager ~= nil and name ~= nil and name ~= "" and g_fruitTypeManager:getFruitTypeByName(name) or nil
return fruit ~= nil and fruit.fillType ~= nil and tostring(fruit.fillType.title) or tostring(name)
end
function SmartPickupFrame:fieldPlanText(field, info)
if field.plan.targetFruit ~= nil and field.plan.targetFruit ~= "" then
return "-> " .. self:fruitTitle(field.plan.targetFruit)
end
local text = self.i18n:getText(ADFieldView.planKey(field.plan, info.kind))
if field.plan.rake and info.kind == "mowable" then text = text .. " + " .. self.i18n:getText("spu_field_plan_rake") end
if field.plan.sow and info.kind ~= "mowable" then text = text .. " + " .. self.i18n:getText("spu_field_plan_sow") end
if (field.plan.rigs or 1) > 1 then text = string.format("%s (x%d)", text, field.plan.rigs) end
return text
end
function SmartPickupFrame:fieldCells(row)
local info, field = row.info, row.field
local names = {}
for _, assignment in ipairs(ADFieldJobs.assignmentsFor(info.id)) do
table.insert(names, ADRuns.vehicleLabel(assignment.vehicle))
end
local name = string.format("%sF%d", info.markerId == nil and "! " or "", info.id)
local crop = ADFieldView.cropText(info, function(key) return self.i18n:getText(key) end)
return {
name, crop, self:fieldPlanText(field, info),
self.i18n:getText(field.plan.mode == "auto" and "spu_field_mode_auto" or "spu_field_mode_manual"),
self:fieldStatusText(field, info, true) .. ((ADFieldJobs.state.paused and not ADFieldJobs.isFieldResumed(info.id)
and ADFieldModel.isWorking(field)) and (" " .. self.i18n:getText("spu_field_pausedSuffix")) or ""),
ADFlowView.joinCounted(names, ", ", "-"),
self:fieldActionText(info.id),
}
end
-- ---- opbygning ----
function SmartPickupFrame:fillFieldRows(adEnv)
self.fieldRows = {}
if not self.editPanelOpen and self:hasGuideOn(SmartPickupFrame.TAB_FIELDS) and self:fillGuideRows(adEnv) then return end
if self.fieldView == "fleet" then
self:fillFleetRows(adEnv)
return
end
local i18n = self.i18n
local infos = ADFields.list()
if #infos == 0 then
self:addTextRow(i18n:getText("spu_field_scanning"))
else
self:addTextRow(i18n:getText("spu_field_hint"))
-- Stop alt-pausen gemmes med savegamet: sig det tydeligt her (in-game 29/9: "jeg har ikke trykket stop")
if ADFieldJobs.state.paused then self:addTextRow(i18n:getText("spu_field_pausedBanner")) end
-- markflåden på "kun med flueben" uden flueben: intet kan sendes ud (in-game 26/9 — alle marker "no machine")
if ADFieldJobs.state.fleetMode == "ticked" and next(ADFieldJobs.state.fleet or {}) == nil then
self:addTextRow(i18n:getText("spu_field_fleetEmpty"))
end
self:addCellRow(self.fieldListPrefab, {i18n:getText("spu_col_field"), i18n:getText("spu_col_crop"), i18n:getText("spu_col_plan"), i18n:getText("spu_col_mode"), i18n:getText("spu_col_status"), i18n:getText("spu_col_tractors"), ""})
for _, row in ipairs(ADFieldView.rows(infos, ADFieldJobs.state.fields)) do
local cells = self:addCellRow(self.fieldListPrefab, self:fieldCells(row))
local fieldId = row.info.id
if cells[1] ~= nil and cells[1].parent ~= nil then
local actionCell = cells[7]
self:makeRowClickable(cells[1].parent, function(frame)
-- klik på [Start]/[Stop]-feltet = handling (kun denne mark); ellers åbn redigeringsvinduet
if frame.lastClickX ~= nil and actionCell ~= nil and actionCell.absPosition ~= nil and frame.lastClickX >= actionCell.absPosition[1] then
frame:toggleField(fieldId)
else
frame.selectedFieldId = fieldId
frame:openEditPanel("field", fieldId)
end
end)
end
table.insert(self.fieldRows, {id = fieldId, cells = cells})
end
end
self:refreshFieldLevels()
end
-- Prik og statusfarve pr. mark i listen.
function SmartPickupFrame:refreshFieldLevels()
local unpackFn = table.unpack or unpack
for _, entry in ipairs(self.fieldRows or {}) do
local color = ADFlowView.LEVEL_COLORS[self:fieldLevel(entry.id)]
local row = entry.cells[1] ~= nil and entry.cells[1].parent or nil
local dot = row ~= nil and row:getDescendantByName("dot") or nil
if dot ~= nil and dot.setImageColor ~= nil then dot:setImageColor(nil, unpackFn(color)) end
if entry.cells[5] ~= nil and entry.cells[5].setTextColor ~= nil then entry.cells[5]:setTextColor(unpackFn(color)) end
end
end
-- Arbejder marken reelt (ikke bare et plan-trin under Stop alt-pause)?
function SmartPickupFrame:fieldIsActive(id, field)
field = field or ADFieldJobs.getField(id)
return field ~= nil and ADFieldView.isActiveWork({working = ADFieldModel.isWorking(field),
paused = ADFieldJobs.state.paused == true, resumed = ADFieldJobs.isFieldResumed(id)})
end
-- [Start] kræver en markør (Stop/Overtag altid muligt når marken arbejder).
function SmartPickupFrame:fieldCanStart(id)
local field, info = ADFieldJobs.getField(id), ADFields.getInfo(id)
if field == nil or info == nil then return false end
return ADFieldView.canStart({hasMarker = info.markerId ~= nil,
working = self:fieldIsActive(id, field) or ADFieldJobs.awaitingPlayer(id) ~= nil})
end
function SmartPickupFrame:fieldLevel(id)
local field, info = ADFieldJobs.getField(id), ADFields.getInfo(id)
if field == nil or info == nil then return "stopped" end
local reason = type(field.reason) == "table" and field.reason.key or nil
return ADFieldView.fieldStatus({hasMarker = info.markerId ~= nil, working = ADFieldModel.isWorking(field),
paused = ADFieldJobs.state.paused == true, resumed = ADFieldJobs.isFieldResumed(id),
awaiting = ADFieldJobs.awaitingPlayer(id) ~= nil,
startRequested = ADFieldJobs.isStartRequested ~= nil and ADFieldJobs.isStartRequested(id) or false,
reasonKey = reason})
end
-- forButton: knaplinjens tekst (Start/Stop) i stedet for listens [Start]/[Stop].
function SmartPickupFrame:fieldActionText(id, forButton)
local field = ADFieldJobs.getField(id)
if ADFieldJobs.awaitingPlayer(id) ~= nil then return self.i18n:getText("spu_field_button_takeover") end
local isWorking = self:fieldIsActive(id, field)
if forButton then return self.i18n:getText(isWorking and "spu_field_button_stop" or "spu_field_button_start") end
return self.i18n:getText(isWorking and "spu_flowList_stop" or "spu_flowList_start")
end
-- Den valgte marks resumé: én klikbar linje pr. emne ([Ret] åbner kun det trin), hele status, markør og
-- advarsel om marksilo der ikke tømmes hjem.
function SmartPickupFrame:fillFieldOverview(adEnv, info)
local i18n = self.i18n
local field = ADFieldJobs.getField(info.id)
local crop = ADFieldView.cropText(info, function(key) return i18n:getText(key) end)
if not self.editPanelOpen then
self:addTextRow(string.format(i18n:getText("spu_field_section"), info.id, crop))
end
self:addTextRow(" " .. i18n:getText("spu_guide_sum_hint"))
local context = self:fieldGuideContext(adEnv, info, field.plan, field.plan.storeBuildingIds)
local guide = ADFieldGuide.open(field, context)
local getText = function(key) return i18n:getText(key) end
local fieldId = info.id
for _, line in ipairs(ADFieldGuide.summary(guide)) do
local step = line.step
self:addLinkRow(" " .. ADGuide.lineText(line, getText) .. i18n:getText("spu_guide_editSuffix"), function(frame)
frame:openFieldStep(fieldId, step)
end)
end
self:addTextRow(" " .. string.format(i18n:getText("spu_field_statusFull"), self:fieldStatusText(field, info)))
if info.markerId ~= nil then
self:addTextRow(string.format(i18n:getText("spu_field_marker"), tostring(info.markerName), info.markerDistance or 0))
else
self:addTextRow(" " .. string.format(i18n:getText("spu_guide_noMarker"), info.id))
end
-- marksilo-advarsler vises ikke her: de gælder ikke marken, og Field Silos-fanen tømmer marksiloerne selv
end
-- Ekstra oplysning pr. sæt i markflåden (afgrøder, frø, strigle/sprøjte, bredde over 50 m, mark).
function SmartPickupFrame:rigDetails(rig)
local i18n = self.i18n
local parts = {}
if rig.role == "harvester" then
local count = 0
for _ in pairs(rig.fruits or {}) do count = count + 1 end
table.insert(parts, string.format(i18n:getText("spu_field_rig_crops"), count))
elseif rig.role == "seeder" then
table.insert(parts, i18n:getText(rig.hasSeeds and "spu_field_rig_seeds" or "spu_field_rig_noSeeds"))
elseif rig.role == "weeder" then
table.insert(parts, i18n:getText(rig.mechanical and "spu_field_rig_mechanical" or "spu_field_rig_herbicide"))
end
-- samme grænse som udsendelsen (50,0 m med afrunding er ikke "over 50 m")
if (rig.width or 0) > ADFieldModel.CP_MAX_WIDTH + ADFieldModel.WIDTH_TOLERANCE then table.insert(parts, i18n:getText("spu_field_tooWideShort")) end
if (rig.width or 0) < ADFieldModel.MIN_WORK_WIDTH and not ADFieldModel.NO_WIDTH_ROLES[rig.role] then table.insert(parts, i18n:getText("spu_field_widthUnknownShort")) end
-- fast på en mark (valgt under markens "Faste maskiner") og hvor den arbejder nu
for fieldId, field in pairs(ADFieldJobs.state.fields) do
for _, id in ipairs(field.plan ~= nil and field.plan.rigIds or {}) do
if id == rig.id then table.insert(parts, string.format(i18n:getText("spu_field_dedicatedElsewhere"), tostring(fieldId))) end
end
end
local assignment = ADFieldJobs.state.assignments[rig.id]
if assignment ~= nil then table.insert(parts, string.format(i18n:getText("spu_field_rig_working"), tostring(assignment.fieldId))) end
return #parts > 0 and (" — " .. table.concat(parts, " — ")) or ""
end
-- Markflåden (egen visning): alle maskiner med flueben, "fast på F22", "i gang på F108", bredde-advarsler.
function SmartPickupFrame:fillFleetRows(adEnv)
local i18n = self.i18n
self:addTextRow(i18n:getText("spu_field_section_fleet"))
local useAll = ADFieldJobs.state.fleetMode ~= "ticked"
self:addToggleRow("fleetMode", i18n:getText("spu_field_fleetAll"), {i18n:getText("ui_no"), i18n:getText("ui_yes")}, useAll)
local rigs = {}
for _, rig in ipairs(ADFieldJobs.getRigs(adEnv)) do table.insert(rigs, rig) end
table.sort(rigs, function(a, b)
if a.role ~= b.role then return a.role < b.role end
return ADFlowView.naturalLess(a.label, b.label)
end)
for _, rig in ipairs(rigs) do
local row = self:addRow(self.vehiclePrefab, {kind = "fleetVehicle", vehicleId = rig.id})
local title = string.format(" %s — %s, %.1f m%s", tostring(rig.label), self:roleText(rig.role), rig.width or 0, self:rigDetails(rig))
row:getDescendantByName("title"):setText(title)
row:getDescendantByName("option"):setState(rig.enabled and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
if rig.role == "harvester" then
-- Ja = må køre selv på vejen (som resten af flåden: ja = må sendes ud); Nej = manuel transport —
-- for bred med skærebord, spilleren kører den til marken (in-game 27/9: omvendt betydning forvirrede)
local option = self:addToggleRow("fleetManual", string.format(i18n:getText("spu_field_manualTransport"), tostring(rig.label)),
{i18n:getText("ui_no"), i18n:getText("ui_yes")}, not rig.manual)
self.rowData[option] = {kind = "fleetManual", vehicleId = rig.id}
end
end
if #rigs == 0 then
self:addTextRow(" " .. i18n:getText("spu_field_noRigs"))
end
if ADPool ~= nil and ADPool.overview ~= nil then self:fillPoolRows(adEnv) end
end
-- Maskinpuljen: solo-traktorer (flueben som resten af flåden) og redskaber med status og hvorfor de ikke er med.
function SmartPickupFrame:fillPoolRows(adEnv)
local i18n = self.i18n
local ok, implements, tractors = pcall(ADPool.overview, adEnv)
if not ok then return end
local byLabel = function(a, b) return ADFlowView.naturalLess(a.label, b.label) end
table.sort(tractors, byLabel)
table.sort(implements, function(a, b)
if a.role ~= b.role then return a.role < b.role end
return byLabel(a, b)
end)
self:addTextRow(i18n:getText("spu_pool_section_tractors"))
for _, tractor in ipairs(tractors) do
local row = self:addRow(self.vehiclePrefab, {kind = "fleetVehicle", vehicleId = tractor.id})
local status = string.format(i18n:getText("spu_pool_tractor_" .. tractor.status), tostring(tractor.arg))
local hp = tractor.hp ~= nil and string.format(", %.0f hk", tractor.hp) or ""
row:getDescendantByName("title"):setText(string.format(" %s%s — %s", tostring(tractor.label), hp, status))
row:getDescendantByName("option"):setState(tractor.enabled and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end
if #tractors == 0 then self:addTextRow(" " .. i18n:getText("spu_pool_noTractors")) end
self:addTextRow(i18n:getText("spu_pool_section_implements"))
for _, implement in ipairs(implements) do
local status = string.format(i18n:getText("spu_pool_implement_" .. implement.status), tostring(implement.arg))
self:addTextRow(string.format(" %s — %s: %s", tostring(implement.label), self:roleText(implement.role), status))
end
if #implements == 0 then self:addTextRow(" " .. i18n:getText("spu_pool_noImplements")) end
end
-- ---- live ----
function SmartPickupFrame:refreshFieldRows(adEnv)
if (not self.editPanelOpen and self:hasGuideOn(SmartPickupFrame.TAB_FIELDS)) or self.fieldView == "fleet" then
self:setStatusText(self.statusOverride or "")
return
end
local rowsById = {}
for _, row in ipairs(ADFieldView.rows(ADFields.list(), ADFieldJobs.state.fields)) do rowsById[row.info.id] = row end
local working, total = 0, 0
for _, entry in ipairs(self.fieldRows or {}) do
local row = rowsById[entry.id]
if row ~= nil then
SmartPickupFrame.setCells(entry.cells, self:fieldCells(row))
total = total + 1
if self:fieldIsActive(entry.id, row.field) then working = working + 1 end
end
end
self:refreshFieldLevels()
self:setStatusText(self.statusOverride or string.format(self.i18n:getText("spu_field_status"), working, total))
local selected = self.selectedFieldId ~= nil and ADFieldJobs.getField(self.selectedFieldId) or nil
local info = self.selectedFieldId ~= nil and ADFields.getInfo(self.selectedFieldId) or nil
local isWorking = selected ~= nil and ADFieldModel.isWorking(selected)
local awaiting = self.selectedFieldId ~= nil and ADFieldJobs.awaitingPlayer(self.selectedFieldId) ~= nil
self.fieldStartButtonInfo.text = self.i18n:getText(awaiting and "spu_field_button_takeover"
or (isWorking and "spu_field_button_stop" or "spu_field_button_start"))
self.fieldStartButtonInfo.disabled = selected == nil or info == nil or (not isWorking and info.markerId == nil)
end
-- ---- klik (markflåden) ----
function SmartPickupFrame:onClickFieldRow(state, data)
self:safe(function(frame)
local isOn = state == SmartPickupFrame.STATE_ON
if data.kind == "fleetVehicle" then
ADFieldJobs.toggleFleet(data.vehicleId)
elseif data.kind == "fleetMode" then
ADFieldJobs.setFleetMode(isOn and "all" or "ticked")
elseif data.kind == "fleetManual" then
local wantManual = not isOn -- Ja = kører selv på vejen
if ((ADFieldJobs.state.manual or {})[data.vehicleId] == true) ~= wantManual then ADFieldJobs.toggleManual(data.vehicleId) end
end
frame:requestRebuild()
end)
end
-- Start nu: pleje hvis marken trænger (og vokser), ellers hele kæden (høst/slå, eller klargøring på bar mark).
function SmartPickupFrame:onClickFieldStartStop()
self:toggleField(self.selectedFieldId)
end
-- Start/Stop/Overtag for én mark (rækkens felt, vinduets knap og knaplinjen).
function SmartPickupFrame:toggleField(id)
self:safe(function(frame)
if id == nil or not frame:fieldCanStart(id) then return end
local field = ADFieldJobs.getField(id)
if ADFieldJobs.awaitingPlayer(id) ~= nil then
-- manuel transport: overtag nu (skærebordet tjekkes stadig)
ADFieldJobs.requestTakeover(id)
frame.statusOverride = string.format(frame.i18n:getText("spu_field_takeoverRequested"), id)
frame.statusOverrideTicks = SmartPickupFrame.STATUS_OVERRIDE_TICKS
elseif frame:fieldIsActive(id, field) then
ADFieldJobs.stopField(frame.getAdEnv(), id)
ADFieldJobs.setFieldResumed(id, false)
elseif ADFieldJobs.state.paused and ADFieldModel.isWorking(field) then
-- Stop alt-pause: kun denne mark genoptages fra sit trin (de andre venter på Start alle)
ADFieldJobs.setFieldResumed(id, true)
frame.statusOverride = string.format(frame.i18n:getText("spu_field_startRequested"), id)
frame.statusOverrideTicks = SmartPickupFrame.STATUS_OVERRIDE_TICKS
else
if ADFieldJobs.state.paused then ADFieldJobs.setFieldResumed(id, true) end
local decision = ADFieldJobs.lastDecision[id]
if decision == nil or decision.care == nil then
ADFieldJobs.forceReady(id)
end
ADFieldJobs.requestStart(id)
frame.statusOverride = string.format(frame.i18n:getText("spu_field_startRequested"), id)
frame.statusOverrideTicks = SmartPickupFrame.STATUS_OVERRIDE_TICKS
end
frame:requestRebuild()
end)
end
-- Redigeringsvinduet for en mark (gui/SmartPickupEditPanel.lua).
SmartPickupFrame.EDIT_PANEL_KINDS = SmartPickupFrame.EDIT_PANEL_KINDS or {}
SmartPickupFrame.EDIT_PANEL_KINDS.field = {
canDelete = false,
exists = function(frame, id) return ADFields.getInfo(id) ~= nil and ADFieldJobs.getField(id) ~= nil end,
title = function(frame, id) return string.format(frame.i18n:getText("spu_panel_fieldTitle"), id) end,
fill = function(frame, adEnv, id)
frame:fillFieldOverview(adEnv, ADFields.getInfo(id))
frame:addLinkRow(" " .. frame.i18n:getText("spu_guide_field_open"), function(f)
f.selectedFieldId = id
f:onClickFieldSetup()
end)
end,
status = function(frame, adEnv, id)
return {text = frame:fieldStatusText(ADFieldJobs.getField(id), ADFields.getInfo(id), true), level = frame:fieldLevel(id)}
end,
startStopText = function(frame, adEnv, id) return frame:fieldActionText(id, true) end,
startStop = function(frame, id) frame:toggleField(id) end,
canStart = function(frame, id) return frame:fieldCanStart(id) end,
delete = function() end,
}