115 lines
4.9 KiB
Lua
115 lines
4.9 KiB
Lua
-- AD Field Silo Planner
|
|
-- Ren planlægning for marksilo-flowet: hvilke (silo, vare) skal hjem, hvor mange lånte vogne sendes, og til
|
|
-- hvilket gårdlager. Ingen spil-kald; input muteres ikke.
|
|
|
|
ADFieldSiloPlanner = {}
|
|
|
|
function ADFieldSiloPlanner.key(siloId, fillType)
|
|
return tostring(siloId) .. ":" .. tostring(fillType)
|
|
end
|
|
local key = ADFieldSiloPlanner.key
|
|
|
|
local function dist2(a, b)
|
|
return ((a.x or 0) - (b.x or 0)) ^ 2 + ((a.z or 0) - (b.z or 0)) ^ 2
|
|
end
|
|
|
|
local function freeIn(store, fillType, reserved)
|
|
return ((store.free or {})[fillType] or 0) - (reserved[store.id .. ":" .. tostring(fillType)] or 0)
|
|
end
|
|
|
|
local function anyStoreTakes(stores, fillType, minLiters, reserved)
|
|
for _, store in ipairs(stores or {}) do
|
|
if store.markerId ~= nil and freeIn(store, fillType, reserved) >= minLiters then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Nærmeste lager med plads til hele turen; ellers det med mest plads (over tærsklen).
|
|
local function chooseStore(stores, silo, fillType, liters, minLiters, reserved)
|
|
local best, bestD, most, mostFree = nil, nil, nil, nil
|
|
for _, store in ipairs(stores or {}) do
|
|
local free = freeIn(store, fillType, reserved)
|
|
if store.markerId ~= nil and free >= minLiters then
|
|
local d = dist2(store, silo)
|
|
if free >= liters and (bestD == nil or d < bestD) then best, bestD = store, d end
|
|
if mostFree == nil or free > mostFree then most, mostFree = store, free end
|
|
end
|
|
end
|
|
return best or most
|
|
end
|
|
|
|
local function canCarry(vehicle, fillType)
|
|
if (vehicle.capacity or 0) <= 0 then return false end
|
|
if vehicle.cargo ~= nil and vehicle.cargo ~= fillType then return false end
|
|
return (vehicle.carries or {})[fillType] == true
|
|
end
|
|
|
|
local function nearestVehicle(vehicles, used, silo, fillType)
|
|
local best, bestD = nil, nil
|
|
for _, vehicle in ipairs(vehicles or {}) do
|
|
if not used[vehicle.id] and canCarry(vehicle, fillType) then
|
|
local d = dist2(vehicle, silo)
|
|
if bestD == nil or d < bestD then best, bestD = vehicle, d end
|
|
end
|
|
end
|
|
return best
|
|
end
|
|
|
|
function ADFieldSiloPlanner.plan(input)
|
|
local minLiters = input.minLiters or 10000
|
|
local reserved, onWay = {}, {}
|
|
for _, loan in ipairs(input.loans or {}) do
|
|
local k = key(loan.siloId, loan.fillType)
|
|
onWay[k] = (onWay[k] or 0) + (loan.liters or 0)
|
|
end
|
|
local status, tasks = {}, {}
|
|
for _, silo in ipairs(input.silos or {}) do
|
|
for fillType, liters in pairs(silo.stock or {}) do
|
|
if (liters or 0) >= minLiters then
|
|
local k = key(silo.id, fillType)
|
|
local entry = {siloId = silo.id, siloName = silo.name, fillType = fillType, liters = liters, onWay = onWay[k] or 0}
|
|
if silo.markerId == nil then
|
|
entry.state = "noMarker"
|
|
elseif not anyStoreTakes(input.stores, fillType, minLiters, reserved) then
|
|
entry.state = "noFarmStore"
|
|
elseif (input.paused or {})[k] then
|
|
entry.state = "paused"
|
|
else
|
|
entry.state = "ok"
|
|
local rest = liters - entry.onWay
|
|
if rest >= minLiters then table.insert(tasks, {silo = silo, fillType = fillType, rest = rest}) end
|
|
end
|
|
table.insert(status, entry)
|
|
end
|
|
end
|
|
end
|
|
table.sort(tasks, function(a, b)
|
|
if a.rest ~= b.rest then return a.rest > b.rest end
|
|
if tostring(a.silo.id) ~= tostring(b.silo.id) then return tostring(a.silo.id) < tostring(b.silo.id) end
|
|
return a.fillType < b.fillType
|
|
end)
|
|
table.sort(status, function(a, b)
|
|
if tostring(a.siloId) ~= tostring(b.siloId) then return tostring(a.siloId) < tostring(b.siloId) end
|
|
return a.fillType < b.fillType
|
|
end)
|
|
local freeSlots = math.max(0, (input.maxVehicles or 2) - #(input.loans or {}))
|
|
local used, dispatch = {}, {}
|
|
for _, task in ipairs(tasks) do
|
|
while freeSlots > 0 and task.rest >= minLiters do
|
|
local vehicle = nearestVehicle(input.vehicles, used, task.silo, task.fillType)
|
|
if vehicle == nil then break end
|
|
local liters = math.min(vehicle.capacity, task.rest)
|
|
local store = chooseStore(input.stores, task.silo, task.fillType, liters, minLiters, reserved)
|
|
if store == nil then break end
|
|
used[vehicle.id] = true
|
|
local reserveKey = store.id .. ":" .. tostring(task.fillType)
|
|
reserved[reserveKey] = (reserved[reserveKey] or 0) + liters
|
|
table.insert(dispatch, {vehicleId = vehicle.id, siloId = task.silo.id, siloMarkerId = task.silo.markerId,
|
|
fillType = task.fillType, storeId = store.id, storeMarkerId = store.markerId, liters = liters})
|
|
task.rest = task.rest - liters
|
|
freeSlots = freeSlots - 1
|
|
end
|
|
end
|
|
return {dispatch = dispatch, status = status, freeSlots = freeSlots}
|
|
end
|