164 lines
5.4 KiB
Lua
164 lines
5.4 KiB
Lua
-- AD Field Model
|
|
-- Markarbejdets regler som ren data (ingen spil-API): en marks plan og trin, hvornår den er klar,
|
|
-- hvilket trin der følger, hvor mange sæt et trin må have, og bredde-reglerne. Alle funktioner
|
|
-- returnerer nye tabeller; job-løkken (adFieldJobs) og GUI'et kalder kun herind.
|
|
|
|
ADFieldModel = {}
|
|
|
|
ADFieldModel.READY_RATIO = 0.9
|
|
ADFieldModel.CUT_RATIO = 0.5
|
|
ADFieldModel.CP_MAX_WIDTH = 50
|
|
ADFieldModel.WIDTH_TOLERANCE = 0.05
|
|
ADFieldModel.RETRY_MS = 300000
|
|
ADFieldModel.MAX_COLLECTORS = 2
|
|
|
|
ADFieldModel.ROLES = {mowing = "mower", tedding = "tedder", raking = "rake", collecting = "collector"}
|
|
local KNOWN_STAGES = {idle = true, ready = true, mowing = true, tedding = true, raking = true, collecting = true, done = true}
|
|
|
|
local function copyList(list)
|
|
local copy = {}
|
|
for index, value in ipairs(list or {}) do copy[index] = value end
|
|
return copy
|
|
end
|
|
|
|
local function copyField(field)
|
|
local copy = {}
|
|
for key, value in pairs(field) do copy[key] = value end
|
|
copy.plan = {
|
|
mode = field.plan.mode, product = field.plan.product, rake = field.plan.rake,
|
|
storeBuildingIds = copyList(field.plan.storeBuildingIds),
|
|
}
|
|
return copy
|
|
end
|
|
|
|
function ADFieldModel.newField(id)
|
|
return {
|
|
id = id, stage = "idle", mowWidth = nil, retryAt = 0, reason = nil, startRequested = false,
|
|
plan = {mode = "manual", product = "silage", rake = false, storeBuildingIds = {}},
|
|
}
|
|
end
|
|
|
|
function ADFieldModel.workStages(plan)
|
|
if plan.product == "hay" then
|
|
if plan.rake then
|
|
return {"mowing", "tedding", "raking", "collecting"}
|
|
end
|
|
return {"mowing", "tedding", "collecting"}
|
|
end
|
|
return {"mowing", "collecting"}
|
|
end
|
|
|
|
function ADFieldModel.roleFor(stage)
|
|
return ADFieldModel.ROLES[stage]
|
|
end
|
|
|
|
function ADFieldModel.isWorking(field)
|
|
return ADFieldModel.ROLES[field.stage] ~= nil
|
|
end
|
|
|
|
function ADFieldModel.maxRigs(stage)
|
|
return stage == "collecting" and ADFieldModel.MAX_COLLECTORS or 1
|
|
end
|
|
|
|
-- Venter/færdig -> klar ved fuld vækst; klar -> venter hvis marken blev slået uden os.
|
|
-- Arbejdende marker styres af job-løkken, ikke af vækst.
|
|
function ADFieldModel.onReadiness(field, ratio)
|
|
local copy = copyField(field)
|
|
if (field.stage == "idle" or field.stage == "done") and ratio >= ADFieldModel.READY_RATIO then
|
|
copy.stage = "ready"
|
|
elseif field.stage == "ready" and ratio < ADFieldModel.CUT_RATIO then
|
|
copy.stage = "idle"
|
|
copy.startRequested = false
|
|
end
|
|
return copy
|
|
end
|
|
|
|
function ADFieldModel.shouldStart(field)
|
|
return field.stage == "ready" and (field.plan.mode == "auto" or field.startRequested == true)
|
|
end
|
|
|
|
function ADFieldModel.start(field)
|
|
local copy = copyField(field)
|
|
copy.stage = ADFieldModel.workStages(field.plan)[1]
|
|
copy.startRequested = false
|
|
copy.retryAt = 0
|
|
copy.reason = nil
|
|
copy.mowWidth = nil
|
|
return copy
|
|
end
|
|
|
|
function ADFieldModel.advance(field)
|
|
local copy = copyField(field)
|
|
local stages = ADFieldModel.workStages(field.plan)
|
|
copy.stage = "done"
|
|
for index, stage in ipairs(stages) do
|
|
if stage == field.stage and stages[index + 1] ~= nil then
|
|
copy.stage = stages[index + 1]
|
|
end
|
|
end
|
|
copy.retryAt = 0
|
|
copy.reason = nil
|
|
return copy
|
|
end
|
|
|
|
-- Stop fra menuen: arbejdet afbrydes; marken står klar igen (manuel start kræves).
|
|
function ADFieldModel.stop(field)
|
|
local copy = copyField(field)
|
|
if ADFieldModel.isWorking(field) then
|
|
copy.stage = "ready"
|
|
end
|
|
copy.startRequested = false
|
|
copy.reason = nil
|
|
return copy
|
|
end
|
|
|
|
-- Et sæt må ikke over Courseplays grænse; efter-trin skal dække mindst slåbredden.
|
|
function ADFieldModel.checkWidth(role, width, mowWidth)
|
|
if width > ADFieldModel.CP_MAX_WIDTH + ADFieldModel.WIDTH_TOLERANCE then
|
|
return false, "tooWide", {width, ADFieldModel.CP_MAX_WIDTH}
|
|
end
|
|
if role ~= "mower" and mowWidth ~= nil and width + ADFieldModel.WIDTH_TOLERANCE < mowWidth then
|
|
return false, "tooNarrow", {width, mowWidth}
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ADFieldModel.wantsRig(field, assignedCount, now)
|
|
local role = ADFieldModel.roleFor(field.stage)
|
|
if role == nil or (field.retryAt or 0) > now or assignedCount >= ADFieldModel.maxRigs(field.stage) then
|
|
return nil
|
|
end
|
|
return role
|
|
end
|
|
|
|
function ADFieldModel.with(field, changes)
|
|
local copy = copyField(field)
|
|
for key, value in pairs(changes) do
|
|
if key == "plan" then
|
|
for planKey, planValue in pairs(value) do copy.plan[planKey] = planValue end
|
|
else
|
|
copy[key] = value
|
|
end
|
|
end
|
|
return copy
|
|
end
|
|
|
|
function ADFieldModel.toRecord(field)
|
|
return {
|
|
id = field.id, stage = field.stage, mode = field.plan.mode, product = field.plan.product,
|
|
rake = field.plan.rake and 1 or 0, store = table.concat(field.plan.storeBuildingIds, " "),
|
|
mowWidth = field.mowWidth or 0,
|
|
}
|
|
end
|
|
|
|
function ADFieldModel.fromRecord(record)
|
|
local field = ADFieldModel.newField(record.id)
|
|
field.stage = KNOWN_STAGES[record.stage] and record.stage or "idle"
|
|
field.plan.mode = record.mode == "auto" and "auto" or "manual"
|
|
field.plan.product = record.product == "hay" and "hay" or "silage"
|
|
field.plan.rake = record.rake == 1 or record.rake == true
|
|
for id in string.gmatch(record.store or "", "%S+") do table.insert(field.plan.storeBuildingIds, id) end
|
|
field.mowWidth = (record.mowWidth or 0) > 0 and record.mowWidth or nil
|
|
return field
|
|
end
|