feat(marksilo): låner ledige tømmevogne hvert minut, AD hent-og-aflever til gårdlager, frigiver ved færdig/spiller/2 min stilstand; opsætning gemmes
This commit is contained in:
@@ -0,0 +1,315 @@
|
|||||||
|
-- AD Field Silos
|
||||||
|
-- Marksilo-flowet i spillet: samler marksiloer, gårdlagre og ledige vogne, lader ADFieldSiloPlanner vælge,
|
||||||
|
-- låner vogne (AutoDrive "Hent og aflever", én tur, parkering bagefter) og frigiver dem igen.
|
||||||
|
-- Lån ligger kun i hukommelsen; opsætningen gemmes i ADSmartPickup_fieldsilos.xml.
|
||||||
|
|
||||||
|
ADFieldSilos = {}
|
||||||
|
|
||||||
|
ADFieldSilos.PLAN_INTERVAL_MS = 60000
|
||||||
|
ADFieldSilos.CHECK_INTERVAL_MS = 2000
|
||||||
|
ADFieldSilos.STUCK_MS = 120000
|
||||||
|
ADFieldSilos.PAUSE_MS = 600000
|
||||||
|
ADFieldSilos.MOVING_SPEED = 0.00028 -- m/ms, samme grænse som AutoDrive
|
||||||
|
ADFieldSilos.FILE_NAME = "ADSmartPickup_fieldsilos.xml"
|
||||||
|
ADFieldSilos.ROOT = "ADSmartPickupFieldSilos"
|
||||||
|
ADFieldSilos.LOG_PREFIX = "ADSmartPickup: marksilo: "
|
||||||
|
|
||||||
|
ADFieldSilos.config = ADFieldSiloConfig.new()
|
||||||
|
ADFieldSilos.lastPlan = nil
|
||||||
|
local loans = setmetatable({}, {__mode = "k"}) -- vehicle -> {siloId, siloName, fillType, liters, startedMs, lastProgressMs, lastFill}
|
||||||
|
local paused = {} -- key -> untilMs
|
||||||
|
local planTimerMs, checkTimerMs, nowMs = ADFieldSilos.PLAN_INTERVAL_MS, 0, 0
|
||||||
|
|
||||||
|
-- ---- ren frigivelsesregel ----
|
||||||
|
|
||||||
|
-- vehicleState = {adActive, controlled, fill, moving}
|
||||||
|
function ADFieldSilos.releaseReason(loan, vehicleState, now)
|
||||||
|
if vehicleState.controlled then return "player" end
|
||||||
|
if not vehicleState.adActive then return "done" end
|
||||||
|
if vehicleState.moving then return nil end
|
||||||
|
if (vehicleState.fill or 0) > (loan.lastFill or 0) then return nil end
|
||||||
|
if now - (loan.lastProgressMs or loan.startedMs or 0) > ADFieldSilos.STUCK_MS then return "stuck" end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ---- opsætning ----
|
||||||
|
|
||||||
|
function ADFieldSilos.setConfig(cfg)
|
||||||
|
local wasEnabled = ADFieldSilos.config.enabled
|
||||||
|
ADFieldSilos.config = cfg
|
||||||
|
if wasEnabled and not cfg.enabled then ADFieldSilos.stopAll(ADFieldSilos.adEnv) end
|
||||||
|
planTimerMs = ADFieldSilos.PLAN_INTERVAL_MS -- planlæg straks ved næste tick
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADFieldSilos.isLoaned(vehicle)
|
||||||
|
return loans[vehicle] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADFieldSilos.loanCount()
|
||||||
|
local n = 0
|
||||||
|
for _ in pairs(loans) do n = n + 1 end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ---- spil-input ----
|
||||||
|
|
||||||
|
local function farmId()
|
||||||
|
return g_currentMission ~= nil and g_currentMission.getFarmId ~= nil and g_currentMission:getFarmId() or 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function position(node)
|
||||||
|
if node == nil then return 0, 0 end
|
||||||
|
local x, _, z = getWorldTranslation(node)
|
||||||
|
return x, z
|
||||||
|
end
|
||||||
|
|
||||||
|
local function siloStock(placeable)
|
||||||
|
local stock = {}
|
||||||
|
local spec = placeable ~= nil and placeable.spec_silo or nil
|
||||||
|
for _, storage in pairs(spec ~= nil and spec.storages or {}) do
|
||||||
|
for fillType, level in pairs(storage.fillLevels or {}) do
|
||||||
|
if (level or 0) > 0 then stock[fillType] = (stock[fillType] or 0) + level end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return stock
|
||||||
|
end
|
||||||
|
|
||||||
|
local function siloFree(placeable)
|
||||||
|
local free = {}
|
||||||
|
local spec = placeable ~= nil and placeable.spec_silo or nil
|
||||||
|
for _, storage in pairs(spec ~= nil and spec.storages or {}) do
|
||||||
|
for fillType in pairs(storage.fillTypes or {}) do
|
||||||
|
local amount = storage.getFreeCapacity ~= nil and (storage:getFreeCapacity(fillType) or 0) or 0
|
||||||
|
free[fillType] = (free[fillType] or 0) + amount
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return free
|
||||||
|
end
|
||||||
|
|
||||||
|
local function firstMarker(building, role)
|
||||||
|
local markers = ADBuildings.getMarkers(building, role)
|
||||||
|
return markers[1] ~= nil and markers[1].id or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Gårdlagre fra opsætningen + marksiloer (alle markers marksiloer, uden dubletter og uden gårdlagre).
|
||||||
|
function ADFieldSilos.collectBuildings(adEnv)
|
||||||
|
local fid = farmId()
|
||||||
|
local stores, byId = {}, {}
|
||||||
|
for _, building in ipairs(ADBuildings.list(adEnv, fid)) do
|
||||||
|
byId[building.id] = building
|
||||||
|
if building.kind == "silo" and ADFieldSiloConfig.hasStore(ADFieldSilos.config, building.id) then
|
||||||
|
local x, z = position(building.placeable ~= nil and building.placeable.rootNode or nil)
|
||||||
|
table.insert(stores, {id = building.id, name = building.name, markerId = firstMarker(building, "unload"),
|
||||||
|
x = x, z = z, free = siloFree(building.placeable)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local silos, seen = {}, {}
|
||||||
|
for _, info in ipairs(ADFields.list()) do
|
||||||
|
for _, building in ipairs(ADFields.fieldSilos(adEnv, fid, info)) do
|
||||||
|
if not seen[building.id] and not ADFieldSiloConfig.hasStore(ADFieldSilos.config, building.id) then
|
||||||
|
seen[building.id] = true
|
||||||
|
local x, z = position(building.placeable ~= nil and building.placeable.rootNode or nil)
|
||||||
|
table.insert(silos, {id = building.id, name = building.name, markerId = firstMarker(building, "load"),
|
||||||
|
x = x, z = z, stock = siloStock(building.placeable)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return silos, stores
|
||||||
|
end
|
||||||
|
|
||||||
|
local function trailerInfo(adEnv, vehicle)
|
||||||
|
local capacity, carries, cargo = 0, {}, nil
|
||||||
|
for _, unit in ipairs(adEnv.AutoDrive.getAllDischargeableUnits(vehicle) or {}) do
|
||||||
|
local object, index = unit.object, unit.fillUnitIndex
|
||||||
|
if object ~= nil and object.getFillUnitCapacity ~= nil then
|
||||||
|
capacity = capacity + (object:getFillUnitCapacity(index) or 0)
|
||||||
|
for fillType in pairs(object.getFillUnitSupportedFillTypes ~= nil and object:getFillUnitSupportedFillTypes(index) or {}) do
|
||||||
|
carries[fillType] = true
|
||||||
|
end
|
||||||
|
if (object:getFillUnitFillLevel(index) or 0) > 0 then cargo = object:getFillUnitFillType(index) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return capacity, carries, cargo
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fillLevel(adEnv, vehicle)
|
||||||
|
local ok, level = pcall(function() return (adEnv.AutoDrive.getAllFillLevels(adEnv.AutoDrive.getAllUnits(vehicle))) end)
|
||||||
|
return ok and tonumber(level) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADFieldSilos.collectVehicles(adEnv)
|
||||||
|
local vehicles, byId = {}, {}
|
||||||
|
for _, rig in ipairs(ADFieldWork.getRigs(adEnv)) do
|
||||||
|
if rig.role == "unloader" and rig.enabled and not rig.busy and not rig.controlled then
|
||||||
|
local capacity, carries, cargo = trailerInfo(adEnv, rig.vehicle)
|
||||||
|
table.insert(vehicles, {id = rig.id, x = rig.x, z = rig.z, capacity = capacity, carries = carries, cargo = cargo})
|
||||||
|
byId[rig.id] = rig.vehicle
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return vehicles, byId
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ---- lån ----
|
||||||
|
|
||||||
|
local function fillTypeName(fillType)
|
||||||
|
local desc = g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeByIndex(fillType) or nil
|
||||||
|
return desc ~= nil and (desc.title or desc.name) or tostring(fillType)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dispatchOne(adEnv, order, vehicle, silosById, storesById)
|
||||||
|
local module = vehicle.ad.stateModule
|
||||||
|
module:setMode(adEnv.AutoDrive.MODE_PICKUPANDDELIVER)
|
||||||
|
module:setFirstMarkerByWayPointId(order.siloMarkerId)
|
||||||
|
module:setSecondMarkerByWayPointId(order.storeMarkerId)
|
||||||
|
module.selectedFillTypes = {order.fillType}
|
||||||
|
module:setFillType(order.fillType)
|
||||||
|
module.loopCounter = 1
|
||||||
|
if module.setLoopsDone ~= nil then module:setLoopsDone(0) end
|
||||||
|
ADFieldWork.enableParkAtJobFinished(vehicle)
|
||||||
|
module:raiseDirtyFlag()
|
||||||
|
ADFieldWork.startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_PICKUPANDDELIVER, order.siloMarkerId, order.storeMarkerId, order.fillType)
|
||||||
|
local silo, store = silosById[order.siloId], storesById[order.storeId]
|
||||||
|
loans[vehicle] = {siloId = order.siloId, siloName = silo ~= nil and silo.name or order.siloId, fillType = order.fillType,
|
||||||
|
liters = order.liters, startedMs = nowMs, lastProgressMs = nowMs, lastFill = fillLevel(adEnv, vehicle)}
|
||||||
|
Logging.info(ADFieldSilos.LOG_PREFIX .. "'%s' henter %d l %s i %s -> %s", tostring(ADRuns.vehicleLabel(vehicle)),
|
||||||
|
math.floor(order.liters), fillTypeName(order.fillType), tostring(silo ~= nil and silo.name or order.siloId),
|
||||||
|
tostring(store ~= nil and store.name or order.storeId))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function release(adEnv, vehicle, reason)
|
||||||
|
local loan = loans[vehicle]
|
||||||
|
loans[vehicle] = nil
|
||||||
|
if loan == nil then return end
|
||||||
|
if reason == "stuck" then
|
||||||
|
local k = ADFieldSiloPlanner.key(loan.siloId, loan.fillType)
|
||||||
|
paused[k] = nowMs + ADFieldSilos.PAUSE_MS
|
||||||
|
pcall(vehicle.stopAutoDrive, vehicle)
|
||||||
|
Logging.warning(ADFieldSilos.LOG_PREFIX .. "'%s' kom ikke i gang ved %s (%s) på 2 min — stoppet; siloen springes over i 10 min",
|
||||||
|
tostring(ADRuns.vehicleLabel(vehicle)), tostring(loan.siloName), fillTypeName(loan.fillType))
|
||||||
|
elseif reason == "stopped" then
|
||||||
|
pcall(vehicle.stopAutoDrive, vehicle)
|
||||||
|
end
|
||||||
|
Logging.info(ADFieldSilos.LOG_PREFIX .. "'%s' frigivet (%s)", tostring(ADRuns.vehicleLabel(vehicle)), tostring(reason))
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADFieldSilos.stopAll(adEnv)
|
||||||
|
local list = {}
|
||||||
|
for vehicle in pairs(loans) do table.insert(list, vehicle) end
|
||||||
|
for _, vehicle in ipairs(list) do release(adEnv, vehicle, "stopped") end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function checkLoans(adEnv)
|
||||||
|
local list = {}
|
||||||
|
for vehicle in pairs(loans) do table.insert(list, vehicle) end
|
||||||
|
for _, vehicle in ipairs(list) do
|
||||||
|
local loan = loans[vehicle]
|
||||||
|
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
||||||
|
local fill = fillLevel(adEnv, vehicle)
|
||||||
|
local vehicleState = {
|
||||||
|
adActive = stateModule ~= nil and stateModule:isActive(),
|
||||||
|
controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled() or false,
|
||||||
|
fill = fill, moving = (vehicle.lastSpeedReal or 0) >= ADFieldSilos.MOVING_SPEED,
|
||||||
|
}
|
||||||
|
if vehicleState.moving or fill > (loan.lastFill or 0) then loan.lastProgressMs = nowMs end
|
||||||
|
loan.lastFill = math.max(loan.lastFill or 0, fill)
|
||||||
|
local reason = ADFieldSilos.releaseReason(loan, vehicleState, nowMs)
|
||||||
|
if reason ~= nil then release(adEnv, vehicle, reason) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function currentPaused()
|
||||||
|
local result = {}
|
||||||
|
for k, untilMs in pairs(paused) do
|
||||||
|
if untilMs > nowMs then result[k] = true else paused[k] = nil end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local function loanList()
|
||||||
|
local list = {}
|
||||||
|
for _, loan in pairs(loans) do table.insert(list, {siloId = loan.siloId, fillType = loan.fillType, liters = loan.liters}) end
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADFieldSilos.planNow(adEnv)
|
||||||
|
local cfg = ADFieldSilos.config
|
||||||
|
local silos, stores = ADFieldSilos.collectBuildings(adEnv)
|
||||||
|
local vehicles, vehiclesById = ADFieldSilos.collectVehicles(adEnv)
|
||||||
|
local plan = ADFieldSiloPlanner.plan({silos = silos, stores = stores, loans = loanList(), vehicles = vehicles,
|
||||||
|
paused = currentPaused(), maxVehicles = cfg.maxVehicles, minLiters = cfg.minLiters})
|
||||||
|
ADFieldSilos.lastPlan = plan
|
||||||
|
if not cfg.enabled then return plan end
|
||||||
|
local silosById, storesById = {}, {}
|
||||||
|
for _, silo in ipairs(silos) do silosById[silo.id] = silo end
|
||||||
|
for _, store in ipairs(stores) do storesById[store.id] = store end
|
||||||
|
for _, order in ipairs(plan.dispatch) do
|
||||||
|
local vehicle = vehiclesById[order.vehicleId]
|
||||||
|
if vehicle ~= nil then
|
||||||
|
local ok, err = pcall(dispatchOne, adEnv, order, vehicle, silosById, storesById)
|
||||||
|
if not ok then
|
||||||
|
loans[vehicle] = nil
|
||||||
|
Logging.warning(ADFieldSilos.LOG_PREFIX .. "kunne ikke sende '%s': %s", tostring(ADRuns.vehicleLabel(vehicle)), tostring(err))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return plan
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADFieldSilos.tick(adEnv, dt)
|
||||||
|
if adEnv == nil then return end
|
||||||
|
ADFieldSilos.adEnv = adEnv
|
||||||
|
nowMs = nowMs + (dt or 0)
|
||||||
|
checkTimerMs = checkTimerMs + (dt or 0)
|
||||||
|
if checkTimerMs >= ADFieldSilos.CHECK_INTERVAL_MS then
|
||||||
|
checkTimerMs = 0
|
||||||
|
checkLoans(adEnv)
|
||||||
|
end
|
||||||
|
planTimerMs = planTimerMs + (dt or 0)
|
||||||
|
if planTimerMs >= ADFieldSilos.PLAN_INTERVAL_MS then
|
||||||
|
planTimerMs = 0
|
||||||
|
ADFieldSilos.planNow(adEnv)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ---- gem / indlæs (samme sti-opslag som runs) ----
|
||||||
|
|
||||||
|
local function getPath()
|
||||||
|
local path = ADRunsStorage ~= nil and ADRunsStorage.getPath() or nil
|
||||||
|
if path == nil then return nil end
|
||||||
|
return (path:gsub(ADRunsStorage.FILE_NAME .. "$", ADFieldSilos.FILE_NAME))
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADFieldSilos.save()
|
||||||
|
local path = getPath()
|
||||||
|
if path == nil then return false end
|
||||||
|
local xmlFile = XMLFile.create("ADSmartPickupFieldSilos", path, ADFieldSilos.ROOT)
|
||||||
|
if xmlFile == nil then return false end
|
||||||
|
local record = ADFieldSiloConfig.toRecord(ADFieldSilos.config)
|
||||||
|
xmlFile:setInt(ADFieldSilos.ROOT .. "#enabled", record.enabled)
|
||||||
|
xmlFile:setString(ADFieldSilos.ROOT .. "#stores", record.stores)
|
||||||
|
xmlFile:setInt(ADFieldSilos.ROOT .. "#maxVehicles", record.maxVehicles)
|
||||||
|
xmlFile:setInt(ADFieldSilos.ROOT .. "#minLiters", record.minLiters)
|
||||||
|
xmlFile:save()
|
||||||
|
xmlFile:delete()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADFieldSilos.load()
|
||||||
|
local path = getPath()
|
||||||
|
if path == nil or not fileExists(path) then
|
||||||
|
ADFieldSilos.config = ADFieldSiloConfig.new()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local xmlFile = XMLFile.load("ADSmartPickupFieldSilos", path)
|
||||||
|
if xmlFile == nil then
|
||||||
|
ADFieldSilos.config = ADFieldSiloConfig.new()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
ADFieldSilos.config = ADFieldSiloConfig.fromRecord({
|
||||||
|
enabled = xmlFile:getInt(ADFieldSilos.ROOT .. "#enabled"),
|
||||||
|
stores = xmlFile:getString(ADFieldSilos.ROOT .. "#stores"),
|
||||||
|
maxVehicles = xmlFile:getInt(ADFieldSilos.ROOT .. "#maxVehicles"),
|
||||||
|
minLiters = xmlFile:getInt(ADFieldSilos.ROOT .. "#minLiters"),
|
||||||
|
})
|
||||||
|
xmlFile:delete()
|
||||||
|
end
|
||||||
@@ -88,6 +88,9 @@ local function startAd(adEnv, vehicle, mode, firstId, secondId, fillType)
|
|||||||
wake(vehicle, true)
|
wake(vehicle, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ADFieldWork.startAd = startAd
|
||||||
|
ADFieldWork.enableParkAtJobFinished = enableParkAtJobFinished
|
||||||
|
|
||||||
-- AD-parkering: sidder på redskabet og kopieres af AD til traktorens stateModule (markør-INDEKS).
|
-- AD-parkering: sidder på redskabet og kopieres af AD til traktorens stateModule (markør-INDEKS).
|
||||||
local function parkMarkerId(adEnv, vehicle)
|
local function parkMarkerId(adEnv, vehicle)
|
||||||
local module = stateModule(vehicle)
|
local module = stateModule(vehicle)
|
||||||
@@ -130,7 +133,8 @@ function ADFieldWork.getRigs(adEnv)
|
|||||||
enabled = ADFieldFleet.isEnabled(state.fleetMode, state.fleet, id),
|
enabled = ADFieldFleet.isEnabled(state.fleetMode, state.fleet, id),
|
||||||
-- optaget: vores egen opgave, eller AutoDrive/Courseplay kører den allerede (fx spillerens
|
-- optaget: vores egen opgave, eller AutoDrive/Courseplay kører den allerede (fx spillerens
|
||||||
-- egen parkeringstur) — den må ikke kapres midt i en anden kørsel
|
-- egen parkeringstur) — den må ikke kapres midt i en anden kørsel
|
||||||
busy = state.assignments[id] ~= nil or isAdActive(vehicle) or ADCpBridge.isWorking(vehicle),
|
busy = state.assignments[id] ~= nil or isAdActive(vehicle) or ADCpBridge.isWorking(vehicle)
|
||||||
|
or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle)),
|
||||||
controlled = isControlledByPlayer(vehicle),
|
controlled = isControlledByPlayer(vehicle),
|
||||||
width = ADCpBridge.getWorkWidth(vehicle), x = x, z = z,
|
width = ADCpBridge.getWorkWidth(vehicle), x = x, z = z,
|
||||||
-- tom spreder kan flere ting (gødning og kalk): alle dens roller
|
-- tom spreder kan flere ting (gødning og kalk): alle dens roller
|
||||||
|
|||||||
@@ -1318,6 +1318,7 @@ function ADRunsController.save()
|
|||||||
if ADFieldJobs ~= nil and ADFieldJobs.loaded then
|
if ADFieldJobs ~= nil and ADFieldJobs.loaded then
|
||||||
pcall(ADFieldJobs.save)
|
pcall(ADFieldJobs.save)
|
||||||
end
|
end
|
||||||
|
if ADFieldSilos ~= nil then pcall(ADFieldSilos.save) end
|
||||||
return saved
|
return saved
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1330,6 +1331,7 @@ function ADRunsController.load(adEnv)
|
|||||||
end
|
end
|
||||||
ADRunsController.store = store
|
ADRunsController.store = store
|
||||||
pcall(ADRunsController.migrateBuildings, adEnv)
|
pcall(ADRunsController.migrateBuildings, adEnv)
|
||||||
|
if ADFieldSilos ~= nil then pcall(ADFieldSilos.load) end
|
||||||
local vehiclesById = getFarmVehiclesById(adEnv)
|
local vehiclesById = getFarmVehiclesById(adEnv)
|
||||||
for _, run in ipairs(store.runs) do
|
for _, run in ipairs(store.runs) do
|
||||||
for _, vehicleId in ipairs(run.vehicleIds) do
|
for _, vehicleId in ipairs(run.vehicleIds) do
|
||||||
|
|||||||
@@ -1262,6 +1262,12 @@ function ADSmartPickup:update(dt)
|
|||||||
Logging.warning(ADSmartPickup.LOG_PREFIX .. "markarbejde fejlede: %s", tostring(errFields))
|
Logging.warning(ADSmartPickup.LOG_PREFIX .. "markarbejde fejlede: %s", tostring(errFields))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if ADFieldSilos ~= nil then
|
||||||
|
local okSilos, errSilos = pcall(ADFieldSilos.tick, getAutoDriveEnv(), dt)
|
||||||
|
if not okSilos then
|
||||||
|
Logging.warning(ADSmartPickup.LOG_PREFIX .. "marksilo-flow fejlede: %s", tostring(errSilos))
|
||||||
|
end
|
||||||
|
end
|
||||||
if ADStuckDiag ~= nil then pcall(ADStuckDiag.tick, dt) end
|
if ADStuckDiag ~= nil then pcall(ADStuckDiag.tick, dt) end
|
||||||
if ADRunsController ~= nil and ADRunsController.adoptLoadedThrottled ~= nil then
|
if ADRunsController ~= nil and ADRunsController.adoptLoadedThrottled ~= nil then
|
||||||
pcall(ADRunsController.adoptLoadedThrottled, getAutoDriveEnv(), dt)
|
pcall(ADRunsController.adoptLoadedThrottled, getAutoDriveEnv(), dt)
|
||||||
|
|||||||
@@ -39,6 +39,9 @@
|
|||||||
<sourceFile filename="adCourseCache.lua"/>
|
<sourceFile filename="adCourseCache.lua"/>
|
||||||
<sourceFile filename="adFieldStorage.lua"/>
|
<sourceFile filename="adFieldStorage.lua"/>
|
||||||
<sourceFile filename="adFieldWork.lua"/>
|
<sourceFile filename="adFieldWork.lua"/>
|
||||||
|
<sourceFile filename="adFieldSiloPlanner.lua"/>
|
||||||
|
<sourceFile filename="adFieldSiloConfig.lua"/>
|
||||||
|
<sourceFile filename="adFieldSilos.lua"/>
|
||||||
<sourceFile filename="adFieldJobs.lua"/>
|
<sourceFile filename="adFieldJobs.lua"/>
|
||||||
<sourceFile filename="adMonthView.lua"/>
|
<sourceFile filename="adMonthView.lua"/>
|
||||||
<sourceFile filename="adProfiler.lua"/>
|
<sourceFile filename="adProfiler.lua"/>
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
-- Kør: luajit tests/test_adFieldSilos.lua (fra repo-roden)
|
||||||
|
dofile("FS25_ADSmartPickup/adFieldSiloPlanner.lua")
|
||||||
|
dofile("FS25_ADSmartPickup/adFieldSiloConfig.lua")
|
||||||
|
dofile("FS25_ADSmartPickup/adFieldSilos.lua")
|
||||||
|
local S = ADFieldSilos
|
||||||
|
local failures = 0
|
||||||
|
local function check(name, actual, expected)
|
||||||
|
if actual == expected then print("OK " .. name) else
|
||||||
|
failures = failures + 1
|
||||||
|
print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local loan = {startedMs = 0, lastProgressMs = 0, lastFill = 0}
|
||||||
|
check("kører, tidligt -> behold", S.releaseReason(loan, {adActive = true, controlled = false, fill = 0, moving = true}, 5000), nil)
|
||||||
|
check("AD slukket -> done", S.releaseReason(loan, {adActive = false, controlled = false, fill = 0}, 5000), "done")
|
||||||
|
check("spiller i vognen -> player", S.releaseReason(loan, {adActive = true, controlled = true, fill = 0}, 5000), "player")
|
||||||
|
check("står stille uden fyld i 120 s -> stuck", S.releaseReason(loan, {adActive = true, controlled = false, fill = 0, moving = false}, 120001), "stuck")
|
||||||
|
check("kører stadig efter 120 s -> behold", S.releaseReason(loan, {adActive = true, controlled = false, fill = 0, moving = true}, 120001), nil)
|
||||||
|
local loading = {startedMs = 0, lastProgressMs = 110000, lastFill = 0}
|
||||||
|
check("fyldning steg for nylig -> behold", S.releaseReason(loading, {adActive = true, controlled = false, fill = 20000, moving = false}, 125000), nil)
|
||||||
|
|
||||||
|
-- Røgtest af spil-laget med mocks: planNow låner nærmeste tømmevogn, isLoaned = optaget,
|
||||||
|
-- frakobling (setConfig enabled=false) stopper AD og frigiver (Review Focus).
|
||||||
|
do
|
||||||
|
local WHEAT = 1
|
||||||
|
Logging = {info = function() end, warning = function() end}
|
||||||
|
g_currentMission = {getFarmId = function() return 1 end}
|
||||||
|
g_fillTypeManager = {getFillTypeByIndex = function() return {title = "Hvede"} end}
|
||||||
|
function getWorldTranslation(node) return node.x, 0, node.z end
|
||||||
|
ADRuns = {vehicleLabel = function(v) return v.name end}
|
||||||
|
local function storage(levels, free) return {fillLevels = levels, fillTypes = {[WHEAT] = true}, getFreeCapacity = function() return free end} end
|
||||||
|
local fieldSilo = {id = "fs", name = "Marksilo 1", kind = "silo", placeable = {rootNode = {x = 1000, z = 0}, spec_silo = {storages = {storage({[WHEAT] = 60000}, 0)}}}}
|
||||||
|
local farm = {id = "farm", name = "Gårdsilo", kind = "silo", placeable = {rootNode = {x = 0, z = 0}, spec_silo = {storages = {storage({}, 500000)}}}}
|
||||||
|
ADBuildings = {list = function() return {fieldSilo, farm} end,
|
||||||
|
getMarkers = function(b, role) return {{id = b.id .. "-" .. role}} end}
|
||||||
|
ADFields = {list = function() return {{id = 1}} end, fieldSilos = function() return {fieldSilo, farm} end}
|
||||||
|
local started = {}
|
||||||
|
local function makeVehicle(name, x)
|
||||||
|
local module = {active = false}
|
||||||
|
function module:setMode() end
|
||||||
|
function module:setFirstMarkerByWayPointId(id) self.first = id end
|
||||||
|
function module:setSecondMarkerByWayPointId(id) self.second = id end
|
||||||
|
function module:setFillType() end
|
||||||
|
function module:setLoopsDone() end
|
||||||
|
function module:raiseDirtyFlag() end
|
||||||
|
function module:isActive() return self.active end
|
||||||
|
return {name = name, x = x, lastSpeedReal = 0, ad = {stateModule = module},
|
||||||
|
stopAutoDrive = function(self) self.ad.stateModule.active = false; self.stopped = true end}
|
||||||
|
end
|
||||||
|
local near, far = makeVehicle("Nær", 900), makeVehicle("Fjern", 5000)
|
||||||
|
local adEnv = {AutoDrive = {MODE_PICKUPANDDELIVER = 2,
|
||||||
|
getAllDischargeableUnits = function() return {{fillUnitIndex = 1, object = {
|
||||||
|
getFillUnitCapacity = function() return 40000 end,
|
||||||
|
getFillUnitSupportedFillTypes = function() return {[WHEAT] = true} end,
|
||||||
|
getFillUnitFillLevel = function() return 0 end, getFillUnitFillType = function() return WHEAT end}}} end,
|
||||||
|
getAllUnits = function() return {} end, getAllFillLevels = function() return 0 end}}
|
||||||
|
ADFieldWork = {
|
||||||
|
getRigs = function() return {
|
||||||
|
{id = "n", vehicle = near, role = "unloader", enabled = true, busy = S.isLoaned(near), controlled = false, x = 900, z = 0},
|
||||||
|
{id = "f", vehicle = far, role = "unloader", enabled = true, busy = S.isLoaned(far), controlled = false, x = 5000, z = 0},
|
||||||
|
{id = "h", vehicle = far, role = "harvester", enabled = true, busy = false, controlled = false, x = 1000, z = 0}} end,
|
||||||
|
enableParkAtJobFinished = function() end,
|
||||||
|
startAd = function(_, v, _, first, second, fillType) v.ad.stateModule.active = true; table.insert(started, {v = v, first = first, second = second, fillType = fillType}) end,
|
||||||
|
}
|
||||||
|
S.setConfig(ADFieldSiloConfig.with(ADFieldSiloConfig.toggleStore(ADFieldSiloConfig.new(), "farm"), {enabled = true, maxVehicles = 1}))
|
||||||
|
local plan = S.planNow(adEnv)
|
||||||
|
check("RØG én afsendelse (x=1)", #started, 1)
|
||||||
|
check("RØG nærmeste tømmevogn", started[1] ~= nil and started[1].v.name, "Nær")
|
||||||
|
check("RØG fra marksilo-markør", started[1] ~= nil and started[1].first, "fs-load")
|
||||||
|
check("RØG til gårdlager-markør", started[1] ~= nil and started[1].second, "farm-unload")
|
||||||
|
check("RØG varen med", started[1] ~= nil and started[1].fillType, WHEAT)
|
||||||
|
check("RØG gårdlager er ikke marksilo", #plan.status, 1)
|
||||||
|
check("RØG vognen er lånt", S.isLoaned(near), true)
|
||||||
|
check("RØG x nået -> ingen flere", #S.planNow(adEnv).dispatch, 0)
|
||||||
|
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = false}))
|
||||||
|
check("RØG frakobling stopper AD", near.stopped, true)
|
||||||
|
check("RØG frakobling frigiver", S.isLoaned(near), false)
|
||||||
|
end
|
||||||
|
|
||||||
|
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||||
|
os.exit(failures == 0 and 0 or 1)
|
||||||
Reference in New Issue
Block a user