225 lines
9.5 KiB
Lua
225 lines
9.5 KiB
Lua
-- AD Pool
|
|
-- Maskinpulje: plove, kultivatorer, såmaskiner … holder frakoblet på deres AD-parkering "X", og en ledig
|
|
-- solo-traktor henter dem via markøren "Connect X", når markarbejdet mangler en færdig kombination.
|
|
-- Ren logik øverst (testes med luajit), spil-adapter nederst.
|
|
|
|
ADPool = {}
|
|
ADPool.LOG_PREFIX = "ADSmartPickup: pulje: "
|
|
ADPool.QUARANTINE_MS = 600000 -- redskab der ikke kunne kobles på, springes over så længe
|
|
|
|
-- ---- ren logik ----
|
|
|
|
-- Rolle for et frakoblet redskab ud fra dets egne specs (samme prioritet som markflåden); en ren vogn med
|
|
-- tip/snegl er tømmevogn.
|
|
function ADPool.roleOf(specs)
|
|
local role = ADFieldFleet.roleFromSpecs(specs)
|
|
if role ~= nil and role ~= "unloader" then return role end
|
|
if specs.spec_trailer and specs.spec_dischargeable then return "unloader" end
|
|
return role
|
|
end
|
|
|
|
-- Butiksdata er enten et tal eller en tabel pr. konfiguration: største tal, ellers nil.
|
|
function ADPool.numberOf(value)
|
|
if type(value) == "number" then return value end
|
|
if type(value) ~= "table" then return nil end
|
|
local best = nil
|
|
for _, entry in pairs(value) do
|
|
if type(entry) == "number" and (best == nil or entry > best) then best = entry end
|
|
end
|
|
return best
|
|
end
|
|
|
|
-- Mangler et af tallene, tillades det (intet må gå i stå på manglende data).
|
|
function ADPool.powerOk(tractorHp, neededHp)
|
|
if tractorHp == nil or neededHp == nil or tractorHp <= 0 or neededHp <= 0 then return true end
|
|
return tractorHp >= neededHp
|
|
end
|
|
|
|
-- Markøren "Connect <parkering>" for en parkering (navneregel).
|
|
function ADPool.connectFor(parkName, markers)
|
|
if type(parkName) ~= "string" then return nil end
|
|
local wanted = parkName:lower()
|
|
for _, marker in pairs(markers or {}) do
|
|
local action, target = ADHitch.parseMarker(marker.name)
|
|
if action == "attach" and target ~= nil and target:lower() == wanted then return marker end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function hasRole(item, role)
|
|
return item.role == role or (item.roles ~= nil and item.roles[role] == true)
|
|
end
|
|
|
|
local function distanceSq(ax, az, bx, bz)
|
|
return (ax - bx) ^ 2 + (az - bz) ^ 2
|
|
end
|
|
|
|
local function nearest(items, x, z, keep)
|
|
local best, bestDistance = nil, math.huge
|
|
for _, item in ipairs(items) do
|
|
if keep(item) then
|
|
local distance = distanceSq(item.x, item.z, x, z)
|
|
if distance < bestDistance then best, bestDistance = item, distance end
|
|
end
|
|
end
|
|
return best
|
|
end
|
|
|
|
-- Redskab med rollen nærmest marken, derefter nærmeste ledige traktor med nok hk til redskabet.
|
|
-- blocked = {[id] = true} (reserverede og redskaber i karantæne). accept (valgfri): implement -> bool.
|
|
function ADPool.pairFor(role, fieldX, fieldZ, implements, tractors, blocked, accept)
|
|
blocked = blocked or {}
|
|
local free = {}
|
|
for _, tractor in ipairs(tractors or {}) do
|
|
if not blocked[tractor.id] then table.insert(free, tractor) end
|
|
end
|
|
local candidates = {}
|
|
for _, implement in ipairs(implements or {}) do
|
|
if hasRole(implement, role) and not blocked[implement.id] and (accept == nil or accept(implement)) then
|
|
table.insert(candidates, implement)
|
|
end
|
|
end
|
|
if #candidates == 0 then return nil, "noPoolImplement" end
|
|
if #free == 0 then return nil, "noPoolTractor" end
|
|
table.sort(candidates, function(a, b)
|
|
return distanceSq(a.x, a.z, fieldX, fieldZ) < distanceSq(b.x, b.z, fieldX, fieldZ)
|
|
end)
|
|
local weakest = nil
|
|
for _, implement in ipairs(candidates) do
|
|
local tractor = nearest(free, implement.x, implement.z, function(t) return ADPool.powerOk(t.hp, implement.neededHp) end)
|
|
if tractor ~= nil then return {implement = implement, tractor = tractor} end
|
|
if weakest == nil or (implement.neededHp or 0) < weakest then weakest = implement.neededHp end
|
|
end
|
|
return nil, "tooWeak", {weakest or 0}
|
|
end
|
|
|
|
-- Ventende marker først (længst ventende først, lige -> lavest id); resten bagefter i oprindelig rækkefølge.
|
|
function ADPool.queueOrder(infos, waitingSince)
|
|
local waiting, rest = {}, {}
|
|
for _, info in ipairs(infos or {}) do
|
|
if waitingSince ~= nil and waitingSince[info.id] ~= nil then table.insert(waiting, info) else table.insert(rest, info) end
|
|
end
|
|
table.sort(waiting, function(a, b)
|
|
local wa, wb = waitingSince[a.id], waitingSince[b.id]
|
|
if wa ~= wb then return wa < wb end
|
|
return a.id < b.id
|
|
end)
|
|
for _, info in ipairs(rest) do table.insert(waiting, info) end
|
|
return waiting
|
|
end
|
|
|
|
-- Reservationer (i hukommelsen): owner -> {id, ...}. Karantæne: id -> udløbstid.
|
|
local reservations, quarantined = {}, {}
|
|
|
|
function ADPool.reserve(owner, ids)
|
|
reservations[owner] = ids
|
|
end
|
|
|
|
function ADPool.release(owner)
|
|
reservations[owner] = nil
|
|
end
|
|
|
|
function ADPool.quarantine(id, nowMs)
|
|
quarantined[id] = nowMs + ADPool.QUARANTINE_MS
|
|
end
|
|
|
|
function ADPool.blocked(nowMs)
|
|
local result = {}
|
|
for _, ids in pairs(reservations) do
|
|
for _, id in ipairs(ids) do result[id] = true end
|
|
end
|
|
for id, untilMs in pairs(quarantined) do
|
|
if nowMs < untilMs then result[id] = true end
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- Traktorer på hjemtur (sætter redskab af, kører hjem): optaget for markarbejdet til de er hjemme.
|
|
local returning = setmetatable({}, {__mode = "k"})
|
|
|
|
function ADPool.setReturning(vehicle, isOn)
|
|
returning[vehicle] = isOn and true or nil
|
|
end
|
|
|
|
function ADPool.isReturning(vehicle)
|
|
return returning[vehicle] == true
|
|
end
|
|
|
|
-- ---- spil ----
|
|
|
|
local function parkMarker(adEnv, vehicle)
|
|
local index = vehicle.advd ~= nil and vehicle.advd.parkDestination or nil
|
|
if type(index) ~= "number" or index < 1 then return nil end
|
|
return adEnv.ADGraphManager:getMapMarkerById(index)
|
|
end
|
|
|
|
local function positionOf(vehicle)
|
|
local x, _, z = getWorldTranslation(vehicle.rootNode)
|
|
return x, z
|
|
end
|
|
|
|
-- Butiksdata: redskabets krævede effekt (hk). specs indlæses først, når butikken har vist varen.
|
|
local function neededHpOf(vehicle)
|
|
if g_storeManager == nil or vehicle.configFileName == nil then return nil end
|
|
local item = g_storeManager:getItemByXMLFilename(vehicle.configFileName)
|
|
if item == nil then return nil end
|
|
if item.specs == nil and StoreItemUtil ~= nil and StoreItemUtil.loadSpecsFromXML ~= nil then
|
|
pcall(StoreItemUtil.loadSpecsFromXML, item)
|
|
end
|
|
return item.specs ~= nil and ADPool.numberOf(item.specs.neededPower) or nil
|
|
end
|
|
|
|
-- Traktorens effekt i hk: motorens topeffekt (kW) omregnet; ellers butiksdata.
|
|
local function tractorHpOf(vehicle)
|
|
local motor = vehicle.spec_motorized ~= nil and vehicle.spec_motorized.motor or nil
|
|
local kw = motor ~= nil and motor.peakMotorPower or nil
|
|
if type(kw) == "number" and kw > 0 then return kw * 1.35962 end
|
|
if g_storeManager == nil or vehicle.configFileName == nil then return nil end
|
|
local item = g_storeManager:getItemByXMLFilename(vehicle.configFileName)
|
|
return item ~= nil and item.specs ~= nil and ADPool.numberOf(item.specs.power) or nil
|
|
end
|
|
|
|
local function isAttached(vehicle)
|
|
return vehicle.getAttacherVehicle ~= nil and vehicle:getAttacherVehicle() ~= nil
|
|
end
|
|
|
|
-- Puljeredskaber (frakoblet, parkering "X", markør "Connect X") og puljetraktorer.
|
|
-- isBusy(vehicle): markarbejdets optaget-regel (opgave, AD/CP kører, lånt af marksilo, på hjemtur).
|
|
function ADPool.collect(adEnv, isBusy)
|
|
local markers = adEnv.ADGraphManager:getMapMarkers()
|
|
local farmId = g_currentMission:getFarmId()
|
|
local implements, tractors = {}, {}
|
|
-- spillets egen liste: frakoblede redskaber er ikke nødvendigvis i AutoDrive's køretøjsliste
|
|
for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do
|
|
local owned = vehicle.getOwnerFarmId ~= nil and vehicle:getOwnerFarmId() == farmId
|
|
if owned and vehicle.spec_motorized == nil and vehicle.spec_attachable ~= nil and not isAttached(vehicle) then
|
|
local park = parkMarker(adEnv, vehicle)
|
|
local connect = park ~= nil and ADPool.connectFor(park.name, markers) or nil
|
|
local specs = connect ~= nil and ADCpBridge.getSpecs(vehicle) or nil
|
|
local role = specs ~= nil and ADPool.roleOf(specs) or nil
|
|
if role ~= nil then
|
|
local x, z = positionOf(vehicle)
|
|
table.insert(implements, {
|
|
id = ADRunsController.getVehicleId(vehicle), vehicle = vehicle, name = ADRuns.vehicleLabel(vehicle),
|
|
role = role, roles = specs.sprayRoles, x = x, z = z, width = ADCpBridge.getWorkWidth(vehicle),
|
|
neededHp = neededHpOf(vehicle), connectId = connect.id, parkId = park.id,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
local state = ADFieldJobs.state
|
|
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
|
|
local id = ADRunsController.getVehicleId(vehicle)
|
|
local park = parkMarker(adEnv, vehicle)
|
|
local controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled()
|
|
if id ~= nil and park ~= nil and vehicle.spec_attacherJoints ~= nil and #ADHitch.rearImplementsOf(vehicle) == 0
|
|
and ADRunsController.getRunForVehicle(vehicle) == nil and not controlled and not isBusy(vehicle)
|
|
and not ADPool.isReturning(vehicle) and ADFieldFleet.isEnabled(state.fleetMode, state.fleet, id) then
|
|
local x, z = positionOf(vehicle)
|
|
table.insert(tractors, {id = id, vehicle = vehicle, name = ADRuns.vehicleLabel(vehicle), x = x, z = z,
|
|
hp = tractorHpOf(vehicle), parkId = park.id})
|
|
end
|
|
end
|
|
return implements, tractors
|
|
end
|