feat(marker): markflåde — alle markroller, accept-filter (afgrøde/ukrudt), pickMany til multi-tool

This commit is contained in:
2026-09-25 23:51:32 +02:00
parent 9cab4b937d
commit 9be60d4b1c
2 changed files with 99 additions and 22 deletions
+60 -16
View File
@@ -1,48 +1,92 @@
-- AD Field Fleet -- AD Field Fleet
-- Markflåden som ren logik: hvilken rolle et sæt har ud fra redskabernes specs, og hvilket ledigt sæt -- Markflåden som ren logik: hvilken rolle et sæt har ud fra redskabernes specs, og hvilke ledige sæt der
-- der sendes til en mark. Kun sæt med flueben bruges; et sæt spilleren sidder i, røres aldrig. -- sendes til en mark. Kun sæt med flueben bruges; et sæt spilleren sidder i, røres aldrig.
ADFieldFleet = {} ADFieldFleet = {}
-- Rækkefølge = prioritet når et køretøj har flere (læssevogn med egen pickup m.m.) -- Rækkefølge = prioritet når et køretøj har flere (såmaskine med gødning er såmaskine; traktor med plov er
-- plov, ikke aflæsningsvogn). sprayRole kommer fra sprøjtens/sprederens fyld (gødning/kalk/ukrudt).
ADFieldFleet.ROLE_SPECS = { ADFieldFleet.ROLE_SPECS = {
{role = "harvester", spec = "spec_combine"},
{role = "collector", spec = "spec_forageWagon"}, {role = "collector", spec = "spec_forageWagon"},
{role = "rake", spec = "spec_windrower"}, {role = "rake", spec = "spec_windrower"},
{role = "tedder", spec = "spec_tedder"}, {role = "tedder", spec = "spec_tedder"},
{role = "mower", spec = "spec_mower"}, {role = "mower", spec = "spec_mower"},
{role = "seeder", spec = "spec_sowingMachine"},
{role = "roller", spec = "spec_roller"},
{role = "weeder", spec = "spec_weeder"},
{role = "sprayRole"},
{role = "mulcher", spec = "spec_mulcher"},
{role = "cultivator", spec = "spec_cultivator"},
{role = "plow", spec = "spec_plow"},
{role = "unloader", spec = "canUnload"},
} }
function ADFieldFleet.roleFromSpecs(specs) function ADFieldFleet.roleFromSpecs(specs)
for _, entry in ipairs(ADFieldFleet.ROLE_SPECS) do for _, entry in ipairs(ADFieldFleet.ROLE_SPECS) do
if specs[entry.spec] then return entry.role end if entry.role == "sprayRole" then
if specs.sprayRole ~= nil then return specs.sprayRole end
elseif specs[entry.spec] then
return entry.role
end
end end
return nil return nil
end end
-- Nærmeste brugbare sæt til rollen. Grund når intet passer: noRig (ingen med flueben og rollen), -- Kandidater med rollen, sorteret nærmest først, delt i brugbare og afviste (med første grund).
-- allBusy (alle optaget/i brug af spilleren), tooWide/tooNarrow (bredden, med tal fra den første). local function sortCandidates(candidates, role, x, z, requiredWidth, accept)
function ADFieldFleet.pick(candidates, role, x, z, mowWidth) local usable, sawRole, sawAccepted = {}, false, false
local best, bestDistance = nil, math.huge local widthReason, widthArgs = nil, nil
local sawRole, widthReason, widthArgs = false, nil, nil
for _, candidate in ipairs(candidates) do for _, candidate in ipairs(candidates) do
if candidate.role == role and candidate.enabled then if candidate.role == role and candidate.enabled then
sawRole = true sawRole = true
if accept == nil or accept(candidate) then
sawAccepted = true
if not candidate.busy and not candidate.controlled then if not candidate.busy and not candidate.controlled then
local ok, reason, args = ADFieldModel.checkWidth(role, candidate.width or 0, mowWidth) local ok, reason, args = ADFieldModel.checkWidth(role, candidate.width or 0, requiredWidth)
if ok then if ok then
local dx, dz = candidate.x - x, candidate.z - z local dx, dz = candidate.x - x, candidate.z - z
local distance = dx * dx + dz * dz table.insert(usable, {candidate = candidate, distance = dx * dx + dz * dz})
if distance < bestDistance then best, bestDistance = candidate, distance end
elseif widthReason == nil then elseif widthReason == nil then
widthReason, widthArgs = reason, args widthReason, widthArgs = reason, args
end end
end end
end end
end end
if best ~= nil then return best end end
if not sawRole then return nil, "noRig" end table.sort(usable, function(a, b) return a.distance < b.distance end)
if widthReason ~= nil then return nil, widthReason, widthArgs end local reason, args = nil, nil
return nil, "allBusy" if not sawRole then
reason = "noRig"
elseif not sawAccepted then
reason = "cannotDo"
elseif widthReason ~= nil then
reason, args = widthReason, widthArgs
else
reason = "allBusy"
end
return usable, reason, args
end
-- Nærmeste brugbare sæt. Grund når intet passer: noRig (ingen med flueben og rollen), cannotDo (ingen kan
-- tage opgaven, fx afgrøden), allBusy, tooWide/tooNarrow (bredden, med tal fra den første).
-- accept (valgfri): candidate -> bool, fx "høsteren kan tage hvede".
function ADFieldFleet.pick(candidates, role, x, z, requiredWidth, accept)
local usable, reason, args = sortCandidates(candidates, role, x, z, requiredWidth, accept)
if usable[1] ~= nil then return usable[1].candidate end
return nil, reason, args
end
-- n sæt på én gang (multi-tool kræver alle baner): de n nærmeste, ellers ingen. notEnough har {ledige, n}.
function ADFieldFleet.pickMany(candidates, role, n, x, z, requiredWidth, accept)
local usable, reason, args = sortCandidates(candidates, role, x, z, requiredWidth, accept)
if #usable >= n then
local picked = {}
for index = 1, n do picked[index] = usable[index].candidate end
return picked
end
if #usable > 0 then return nil, "notEnough", {#usable, n} end
return nil, reason, args
end end
function ADFieldFleet.toggle(enabledIds, id) function ADFieldFleet.toggle(enabledIds, id)
+34 -1
View File
@@ -15,7 +15,7 @@ check("FL1 slåmaskine", F.roleFromSpecs({spec_mower = true}), "mower")
check("FL1 vender", F.roleFromSpecs({spec_tedder = true}), "tedder") check("FL1 vender", F.roleFromSpecs({spec_tedder = true}), "tedder")
check("FL1 river", F.roleFromSpecs({spec_windrower = true}), "rake") check("FL1 river", F.roleFromSpecs({spec_windrower = true}), "rake")
check("FL1 læssevogn", F.roleFromSpecs({spec_forageWagon = true, spec_mower = true}), "collector") check("FL1 læssevogn", F.roleFromSpecs({spec_forageWagon = true, spec_mower = true}), "collector")
check("FL1 intet", F.roleFromSpecs({spec_plow = true}), nil) check("FL1 intet", F.roleFromSpecs({spec_trailer = true}), nil)
local function rig(id, role, x, extra) local function rig(id, role, x, extra)
local r = {id = id, role = role, enabled = true, busy = false, controlled = false, width = 40.8, x = x, z = 0, label = id} local r = {id = id, role = role, enabled = true, busy = false, controlled = false, width = 40.8, x = x, z = 0, label = id}
@@ -49,5 +49,38 @@ local toggled = F.toggle(enabled, "b")
check("FL7 b til", toggled.b, true); check("FL7 original urørt", enabled.b, nil) check("FL7 b til", toggled.b, true); check("FL7 original urørt", enabled.b, nil)
check("FL7 a fra", F.toggle(toggled, "a").a, nil) check("FL7 a fra", F.toggle(toggled, "a").a, nil)
-- FL8: nye roller i prioriteret rækkefølge
check("FL8 høster", F.roleFromSpecs({spec_combine = true, spec_cutter = true}), "harvester")
check("FL8 såmaskine før kultivator (combi)", F.roleFromSpecs({spec_sowingMachine = true, spec_cultivator = true, sprayRole = "fertilizer"}), "seeder")
check("FL8 kultivator", F.roleFromSpecs({spec_cultivator = true}), "cultivator")
check("FL8 plov", F.roleFromSpecs({spec_plow = true}), "plow")
check("FL8 tromle", F.roleFromSpecs({spec_roller = true}), "roller")
check("FL8 strigle", F.roleFromSpecs({spec_weeder = true}), "weeder")
check("FL8 stubhakker", F.roleFromSpecs({spec_mulcher = true}), "mulcher")
check("FL8 gødningssprøjte", F.roleFromSpecs({sprayRole = "fertilizer"}), "fertilizer")
check("FL8 kalkspreder", F.roleFromSpecs({sprayRole = "limer"}), "limer")
check("FL8 ukrudtssprøjte", F.roleFromSpecs({sprayRole = "weeder"}), "weeder")
check("FL8 aflæsningsvogn", F.roleFromSpecs({canUnload = true}), "unloader")
check("FL8 traktor med plov er ikke aflæsningsvogn", F.roleFromSpecs({spec_plow = true, canUnload = true}), "plow")
-- FL9: accept-filter (fx høster der kan tage afgrøden, strigle kun til småt ukrudt)
local combineWheat = rig("jd", "harvester", 10, {fruits = {[3] = true}})
local combineBeet = rig("ropa", "harvester", 1, {fruits = {[9] = true}})
local forWheat = F.pick({combineBeet, combineWheat}, "harvester", 0, 0, nil, function(c) return c.fruits[3] == true end)
check("FL9 høster der kan tage hvede", forWheat.id, "jd")
local _, whyFruit = F.pick({combineBeet}, "harvester", 0, 0, nil, function(c) return c.fruits[3] == true end)
check("FL9 ingen kan tage afgrøden", whyFruit, "cannotDo")
-- FL10: flere sæt til multi-tool — alle eller ingen, nærmeste først
local m1, m2, m3 = rig("m1", "mower", 30), rig("m2", "mower", 10), rig("m3", "mower", 20, {busy = true})
local two = F.pickMany({m1, m2, m3}, "mower", 2, 0, 0)
check("FL10 to valgt", #two, 2)
check("FL10 nærmeste først", two[1].id, "m2")
local none, whyMany, args = F.pickMany({m1, m2, m3}, "mower", 3, 0, 0)
check("FL10 for få -> ingen", none, nil)
check("FL10 grund", whyMany, "notEnough")
check("FL10 ledige/krævet", args[1] .. "/" .. args[2], "2/3")
check("FL10 ingen med rollen", select(2, F.pickMany({m1}, "tedder", 1, 0, 0)), "noRig")
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1) os.exit(failures == 0 and 0 or 1)