-- 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 " 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 -- ---- 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