-- 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 eller (med "kør til produktion") hvilken fabrik. 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 local function isProduction(store) return store ~= nil and store.kind == "production" end -- Nærmeste lager af den ønskede slags (fabrik/gård) med plads til hele turen; ellers det med mest plads. local function chooseStore(stores, silo, fillType, liters, minLiters, reserved, production) 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 and isProduction(store) == production 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 -- fabrikker kun med plads til hele læsset: en fabrik tager kun det den forbruger, så en vogn med resten -- holder ved den i timevis (in-game 28/9: 252.000 l kartofler ved en fuld snackfabrik) if production then return best end return best or most end -- Tom silo: en lånt vogn med last, der har stået stille ved en tom marksilo i REST_IDLE_MS, kører resten videre -- (AutoDrive venter ellers til vognen er fuld). s = {atSilo, stillMs, siloStock, cargo} ADFieldSiloPlanner.REST_IDLE_MS = 30000 ADFieldSiloPlanner.REST_MIN_LITERS = 100 function ADFieldSiloPlanner.restDue(s) return s.atSilo == true and (s.stillMs or 0) >= ADFieldSiloPlanner.REST_IDLE_MS and (s.cargo or 0) >= ADFieldSiloPlanner.REST_MIN_LITERS and (s.siloStock or 0) < ADFieldSiloPlanner.REST_MIN_LITERS end -- Lager til en rest: fabrik kun med plads til hele resten, ellers gårdlager (nærmeste med plads, ellers mest plads). function ADFieldSiloPlanner.restStore(stores, silo, fillType, liters) return chooseStore(stores, silo, fillType, liters, 1, {}, true) or chooseStore(stores, silo, fillType, liters, 1, {}, false) 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 -- Foder først: mangler gården stadig foderreserve for varen (reserve − beholdning − læs på vej hjem), køres til -- gårdlager; ellers til fabrik. Findes kun den ene slags, bruges den. local function pickStore(input, farmIncoming, task, liters, minLiters, reserved) local fillType = task.fillType local short = ((input.feedReserve or {})[fillType] or 0) - ((input.farmStock or {})[fillType] or 0) - (farmIncoming[fillType] or 0) local wantProduction = short <= 0 return chooseStore(input.stores, task.silo, fillType, liters, minLiters, reserved, wantProduction) or chooseStore(input.stores, task.silo, fillType, liters, minLiters, reserved, not wantProduction) end -- Høsten først: for hvert behov {fillType, count} (høstere i arbejde, der mangler tømmevogne) holdes så mange -- vogne der kan bære afgrøden tilbage; resten må marksilo-flowet låne. Ny liste; input muteres ikke. function ADFieldSiloPlanner.withoutHarvestReserve(vehicles, needs) local held = {} for _, need in ipairs(needs or {}) do local left = need.count or 0 for _, vehicle in ipairs(vehicles or {}) do if left <= 0 then break end if not held[vehicle.id] and canCarry(vehicle, need.fillType) then held[vehicle.id] = true left = left - 1 end end end local free = {} for _, vehicle in ipairs(vehicles or {}) do if not held[vehicle.id] then table.insert(free, vehicle) end end return free end function ADFieldSiloPlanner.plan(input) local minLiters = input.minLiters or 10000 local storesById = {} for _, store in ipairs(input.stores or {}) do storesById[store.id] = store end local reserved, onWay, farmIncoming = {}, {}, {} 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) if loan.storeId ~= nil and not isProduction(storesById[loan.storeId]) then farmIncoming[loan.fillType] = (farmIncoming[loan.fillType] or 0) + (loan.liters or 0) end -- læs på vej til et lager optager pladsen, til de er aflæsset (også i senere runder) if loan.storeId ~= nil then local reserveKey = loan.storeId .. ":" .. tostring(loan.fillType) reserved[reserveKey] = (reserved[reserveKey] or 0) + (loan.liters or 0) end end local status, tasks, statusByKey = {}, {}, {} 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) statusByKey[k] = 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 = pickStore(input, farmIncoming, task, liters, minLiters, reserved) if store == nil then break end if isProduction(store) then statusByKey[key(task.silo.id, task.fillType)].toProduction = true else farmIncoming[task.fillType] = (farmIncoming[task.fillType] or 0) + liters 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