feat(pulje): bedste pasform — mindste traktor med hk nok vælges; afstand kun inden for ±15 %; over 2,5x redskabets behov kun som nødløsning (v1.56.0.0)

This commit is contained in:
2026-09-29 14:33:43 +02:00
parent 6af89c3034
commit 3e7e3d588e
3 changed files with 54 additions and 6 deletions
+39 -5
View File
@@ -70,7 +70,7 @@ end
local function nearest(items, x, z, keep) local function nearest(items, x, z, keep)
local best, bestDistance = nil, math.huge local best, bestDistance = nil, math.huge
for _, item in ipairs(items) do for _, item in ipairs(items) do
if keep(item) then if keep == nil or keep(item) then
local distance = distanceSq(item.x, item.z, x, z) local distance = distanceSq(item.x, item.z, x, z)
if distance < bestDistance then best, bestDistance = item, distance end if distance < bestDistance then best, bestDistance = item, distance end
end end
@@ -80,6 +80,38 @@ end
-- Når opgaven slutter, sættes puljeredskabet af — også når AutoDrive ellers selv ville parkere (påfyldte -- Når opgaven slutter, sættes puljeredskabet af — også når AutoDrive ellers selv ville parkere (påfyldte
-- sæt): AD parkerer ved redskabets plads med redskabet på. Aldrig ved manuel transport. -- sæt): AD parkerer ved redskabets plads med redskabet på. Aldrig ved manuel transport.
-- Bedste pasform: den mindste traktor med hk nok; afstand afgør kun mellem traktorer inden for FIT_BAND af den
-- mindste. Over OVERSIZE_FACTOR x behovet kun når intet andet passer; ukendt traktor-hk efter de kendte.
-- Redskab uden hk-data: nærmeste (intet at matche på). tractors = allerede filtrerede kandidater.
ADPool.FIT_BAND = 1.15
ADPool.OVERSIZE_FACTOR = 2.5
function ADPool.bestFit(tractors, implement, role)
local fitting = {}
for _, tractor in ipairs(tractors or {}) do
if suits(tractor, implement, role) then table.insert(fitting, tractor) end
end
local needed = implement.neededHp
if needed == nil or needed <= 0 then return nearest(fitting, implement.x, implement.z) end
local sized, oversized, unknown = {}, {}, {}
for _, tractor in ipairs(fitting) do
if tractor.hp == nil or tractor.hp <= 0 then table.insert(unknown, tractor)
elseif tractor.hp > needed * ADPool.OVERSIZE_FACTOR then table.insert(oversized, tractor)
else table.insert(sized, tractor) end
end
local pool = #sized > 0 and sized or (#unknown > 0 and unknown or oversized)
if pool ~= unknown then
local smallest = math.huge
for _, tractor in ipairs(pool) do smallest = math.min(smallest, tractor.hp) end
local band = {}
for _, tractor in ipairs(pool) do
if tractor.hp <= smallest * ADPool.FIT_BAND then table.insert(band, tractor) end
end
pool = band
end
return nearest(pool, implement.x, implement.z)
end
function ADPool.shouldReturnHome(assignment) function ADPool.shouldReturnHome(assignment)
return not assignment.manual return not assignment.manual
end end
@@ -160,7 +192,7 @@ function ADPool.pairFor(role, fieldX, fieldZ, implements, tractors, blocked, acc
end) end)
local weakest = nil local weakest = nil
for _, implement in ipairs(candidates) do for _, implement in ipairs(candidates) do
local tractor = nearest(free, implement.x, implement.z, function(t) return suits(t, implement, role) end) local tractor = ADPool.bestFit(free, implement, role)
if tractor ~= nil then return {implement = implement, tractor = tractor} end if tractor ~= nil then return {implement = implement, tractor = tractor} end
if weakest == nil or (implement.neededHp or 0) < (weakest.neededHp or 0) then weakest = implement end if weakest == nil or (implement.neededHp or 0) < (weakest.neededHp or 0) then weakest = implement end
end end
@@ -174,9 +206,11 @@ function ADPool.pairAll(role, implements, tractors, blocked)
local used, result = {}, {} local used, result = {}, {}
for _, implement in ipairs(implements or {}) do for _, implement in ipairs(implements or {}) do
if hasRole(implement, role) and not blocked[implement.id] then if hasRole(implement, role) and not blocked[implement.id] then
local tractor = nearest(tractors or {}, implement.x, implement.z, function(t) local open = {}
return not used[t.id] and not blocked[t.id] and not t.offNetwork and suits(t, implement, role) for _, t in ipairs(tractors or {}) do
end) if not used[t.id] and not blocked[t.id] and not t.offNetwork then table.insert(open, t) end
end
local tractor = ADPool.bestFit(open, implement, role)
if tractor ~= nil then if tractor ~= nil then
used[tractor.id] = true used[tractor.id] = true
table.insert(result, {implement = implement, tractor = tractor}) table.insert(result, {implement = implement, tractor = tractor})
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107"> <modDesc descVersion="107">
<author>masterdraco</author> <author>masterdraco</author>
<version>1.55.1.0</version> <version>1.56.0.0</version>
<title> <title>
<en>AD Smart Pickup</en> <en>AD Smart Pickup</en>
</title> </title>
+14
View File
@@ -164,5 +164,19 @@ check("par-alle: ingen traktorer", #P.pairAll("unloader", wagons, {}, {}), 0)
-- flow-vogne: vogn der hører til et kørende flow vises sådan (og er ikke i puljen) -- flow-vogne: vogn der hører til et kørende flow vises sådan (og er ikke i puljen)
check("redskab: flowets vogn", ist({parkName = "P Cow Food vogn 1", hasConnect = true, flowName = "Cow Food"}), "flow|Cow Food") check("redskab: flowets vogn", ist({parkName = "P Cow Food vogn 1", hasConnect = true, flowName = "Cow Food"}), "flow|Cow Food")
check("redskab: flowets vogn koblet på", ist({parkName = "P Cow Food vogn 1", hasConnect = true, flowName = "Cow Food", attachedTo = "Cow Food 1"}), "attached|Cow Food 1") check("redskab: flowets vogn koblet på", ist({parkName = "P Cow Food vogn 1", hasConnect = true, flowName = "Cow Food", attachedTo = "Cow Food 1"}), "attached|Cow Food 1")
-- pasform: mindste traktor med hk nok; afstand kun inden for ±15 %; over 2,5x behovet kun som nødløsning
-- (in-game 29/9: en meget stor traktor blev sendt til et lille job og kunne ikke komme rundt)
local impl120 = {id = "k", x = 0, z = 0, neededHp = 120}
local t150 = {id = "t150", x = 900, z = 0, hp = 150}
local t170 = {id = "t170", x = 300, z = 0, hp = 180}
local t500 = {id = "t500", x = 50, z = 0, hp = 500}
check("pasform: mindste der passer vinder over nærmere", P.bestFit({t500, t170, t150}, impl120, "cultivator").id, "t150")
local t160 = {id = "t160", x = 100, z = 0, hp = 160}
check("pasform: inden for 15 % vinder den nærmeste", P.bestFit({t150, t160}, impl120, "cultivator").id, "t160")
check("pasform: kun for stor -> nødløsning", P.bestFit({t500}, impl120, "cultivator").id, "t500")
check("pasform: for svag -> ingen", P.bestFit({{id = "t90", x = 0, z = 0, hp = 90}}, impl120, "cultivator"), nil)
check("pasform: redskab uden hk-data -> nærmeste", P.bestFit({t150, t500}, {id = "x", x = 0, z = 0}, "cultivator").id, "t500")
check("pasform: ukendt traktor-hk efter kendte", P.bestFit({{id = "u", x = 0, z = 0}, t170}, impl120, "cultivator").id, "t170")
check("pasform: lastbil tæller ikke", P.bestFit({{id = "lb", x = 0, z = 0, hp = 150, category = "TRUCKS"}, t500}, impl120, "cultivator").id, "t500")
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)