fix(review): palle/bulk pr. kilde, pallefabrikker uden læssestation, hold-omdirigering, last ved Start, tomme kilder udelukkes, færre rutetjek, advarsler huskes, paller kun Fra/Salg
This commit is contained in:
@@ -21,6 +21,10 @@ ADOutbound.SPAWN_STALL_MS = 30000
|
||||
-- UAL kan først læsse når vognen holder helt stille (AutoDrives egen grænse).
|
||||
ADOutbound.MAX_SPEED_FOR_LOADING = 0.0005
|
||||
ADOutbound.NOTIFICATION_MS = 8000
|
||||
-- Tom vogn ved kilden så længe uden at få noget = kilden har ikke varen alligevel (paller ikke
|
||||
-- spawnet, forkert farm, ...): kilden udelukkes for varen i EMPTY_SOURCE_TTL_MS og turen afsluttes.
|
||||
ADOutbound.EMPTY_SOURCE_MS = 30000
|
||||
ADOutbound.EMPTY_SOURCE_TTL_MS = 300000
|
||||
|
||||
-- køretøj -> {run, trip, sources, queue, visited, current, lastFill, noProgressMs, spawn, done}
|
||||
local trips = setmetatable({}, {__mode = "k"})
|
||||
@@ -30,8 +34,17 @@ local holds = setmetatable({}, {__mode = "k"})
|
||||
local status = {}
|
||||
-- køretøj -> {[nøgle] = tekst} (advarsler vises én gang pr. ændret tekst)
|
||||
local warned = setmetatable({}, {__mode = "k"})
|
||||
-- "kilde-id:fillType" -> tidspunkt (ms) hvor udelukkelsen udløber
|
||||
local emptyUntil = {}
|
||||
local isInstalled = false
|
||||
|
||||
local function now()
|
||||
if g_currentMission ~= nil and type(g_currentMission.time) == "number" then
|
||||
return g_currentMission.time
|
||||
end
|
||||
return os.clock() * 1000
|
||||
end
|
||||
|
||||
local function getRun(vehicle)
|
||||
return ADRunsController ~= nil and ADRunsController.getRunForVehicle ~= nil and ADRunsController.getRunForVehicle(vehicle) or nil
|
||||
end
|
||||
@@ -95,33 +108,50 @@ end
|
||||
local function wantsSell(policy) return policy == "sell" or policy == "sellElseStore" end
|
||||
local function wantsStore(policy) return policy == "store" or policy == "sellElseStore" end
|
||||
|
||||
-- Bedste NÅELIGE salgssted for varen; unreachableName = navnet på det bedste der ikke kan nås.
|
||||
local function findSellQuote(adEnv, vehicle, product, farmId)
|
||||
local quotes, unreachableName = {}, nil
|
||||
local homeId = product.sources[1] ~= nil and product.sources[1].wayPointId or nil
|
||||
for _, entry in ipairs(ADSources.getSellMarkers(adEnv, farmId)) do
|
||||
-- Bedste salgssted for varen. Rutetjek (A*) laves kun for stationer over pristærsklen, bedste
|
||||
-- først, og stopper ved den første nåelige. Er ingen over tærsklen, returneres det bedste
|
||||
-- (unåelighed er ligegyldig, turen kører alligevel ikke) — til begrundelse og UI.
|
||||
-- unreachableName = det bedst betalende over tærsklen, når intet over tærsklen kan nås.
|
||||
local function findSellQuote(adEnv, vehicle, product, sellMarkers, sellAtPercent, homeId)
|
||||
local quotes = {}
|
||||
for _, entry in ipairs(sellMarkers) do
|
||||
if ADSources.stationAccepts(entry.station, product.fillType) then
|
||||
local quote = ADPrices.getQuote(entry.station, product.fillType)
|
||||
if quote ~= nil then
|
||||
local reachable = homeId == nil or ADWaitPool.hasRouteBothWays(adEnv, vehicle, entry.marker.id, homeId)
|
||||
if reachable then
|
||||
table.insert(quotes, {percent = quote.percent, price = quote.price, top = quote.top, wayPointId = entry.marker.id, stationName = tostring(entry.marker.name)})
|
||||
elseif unreachableName == nil then
|
||||
unreachableName = tostring(entry.marker.name)
|
||||
end
|
||||
table.insert(quotes, {percent = quote.percent, price = quote.price, top = quote.top, wayPointId = entry.marker.id, stationName = tostring(entry.marker.name)})
|
||||
end
|
||||
end
|
||||
end
|
||||
return ADPrices.bestQuote(quotes), unreachableName
|
||||
table.sort(quotes, function(a, b)
|
||||
if a.percent ~= b.percent then return a.percent > b.percent end
|
||||
if a.price ~= b.price then return a.price > b.price end
|
||||
return a.wayPointId < b.wayPointId
|
||||
end)
|
||||
local unreachableName = nil
|
||||
for _, quote in ipairs(quotes) do
|
||||
if quote.percent < (sellAtPercent or 90) then
|
||||
break
|
||||
end
|
||||
if homeId == nil or ADWaitPool.hasRouteBothWays(adEnv, vehicle, quote.wayPointId, homeId) then
|
||||
return quote, nil
|
||||
end
|
||||
unreachableName = unreachableName or quote.stationName
|
||||
end
|
||||
if unreachableName ~= nil then
|
||||
return nil, unreachableName
|
||||
end
|
||||
return quotes[1], nil
|
||||
end
|
||||
|
||||
local function findStoreMarker(adEnv, vehicle, product, capacity, sources)
|
||||
local function findStoreMarker(adEnv, vehicle, product, capacity, sources, litersOverride)
|
||||
if product.isPallet or ADLoadSwap == nil or ADLoadSwap.findDumpMarker == nil then
|
||||
return nil
|
||||
end
|
||||
local available = 0
|
||||
for _, source in ipairs(product.sources) do
|
||||
available = available + (source.available or 0)
|
||||
local available = litersOverride or 0
|
||||
if litersOverride == nil then
|
||||
for _, source in ipairs(product.sources) do
|
||||
available = available + (source.available or 0)
|
||||
end
|
||||
end
|
||||
local liters = math.max(1, math.min(available, capacity or available))
|
||||
local isReachable = function(markerId)
|
||||
@@ -154,11 +184,14 @@ function ADOutbound.buildPlanInput(adEnv, run, vehicle)
|
||||
end
|
||||
end
|
||||
local byName, products = {}, {}
|
||||
local time = now()
|
||||
for _, source in ipairs(sources) do
|
||||
for _, item in ipairs(ADSources.getProducts(adEnv, source, farmId)) do
|
||||
local policy = ADRuns.getProductPolicy(run, item.name)
|
||||
local canCarry = (item.isPallet and hasAutoload) or (not item.isPallet and carriable[item.fillType] == true)
|
||||
if policy ~= "off" and canCarry then
|
||||
local emptyKey = tostring(source.wayPointId) .. ":" .. tostring(item.fillType)
|
||||
local isMarkedEmpty = emptyUntil[emptyKey] ~= nil and emptyUntil[emptyKey] > time
|
||||
if policy ~= "off" and canCarry and not isMarkedEmpty then
|
||||
local product = byName[item.name]
|
||||
if product == nil then
|
||||
product = {fillType = item.fillType, name = item.name, policy = policy, isPallet = item.isPallet, sources = {}}
|
||||
@@ -170,9 +203,12 @@ function ADOutbound.buildPlanInput(adEnv, run, vehicle)
|
||||
end
|
||||
end
|
||||
table.sort(products, function(a, b) return a.name < b.name end)
|
||||
-- salgsstederne findes én gang pr. plan (opslag over alle markører × stationer)
|
||||
local sellMarkers = ADSources.getSellMarkers(adEnv, farmId)
|
||||
for _, product in ipairs(products) do
|
||||
if wantsSell(product.policy) then
|
||||
local quote, unreachableName = findSellQuote(adEnv, vehicle, product, farmId)
|
||||
local homeId = product.sources[1] ~= nil and product.sources[1].wayPointId or nil
|
||||
local quote, unreachableName = findSellQuote(adEnv, vehicle, product, sellMarkers, run.sellAtPercent, homeId)
|
||||
product.quote = quote
|
||||
if quote == nil and unreachableName ~= nil then
|
||||
warnOnce(adEnv, vehicle, "sell:" .. product.name, string.format("'%s' kan ikke nå salgssted '%s' for %s – tjek vejnettet", tostring(vehicle:getName()), unreachableName, product.name))
|
||||
@@ -195,6 +231,38 @@ function ADOutbound.buildPlanInput(adEnv, run, vehicle)
|
||||
return input, sourcesById, sources
|
||||
end
|
||||
|
||||
-- Mål for last der ALLEREDE er på vognen (ved Start efter genindlæsning/stop): salgssted hvis
|
||||
-- prisen holder, ellers silo hvis politikken tillader lager. nil = intet mål (Start afvises).
|
||||
function ADOutbound.planCargoTarget(adEnv, run, vehicle, fillType, liters)
|
||||
local name = fillTypeName(fillType)
|
||||
local policy = ADRuns.getProductPolicy(run, name)
|
||||
if policy == "off" then
|
||||
return nil
|
||||
end
|
||||
local farmId = ADSmartPickup.getFarmId(vehicle)
|
||||
local capacity = getVehicleCapacity(adEnv, vehicle)
|
||||
local sources = {}
|
||||
for _, wayPointId in ipairs(run.sourceWayPointIds or {}) do
|
||||
local source = ADSources.identify(adEnv, wayPointId, farmId)
|
||||
if source ~= nil then table.insert(sources, source) end
|
||||
end
|
||||
local product = {fillType = fillType, name = name, policy = policy, isPallet = false, sources = {}}
|
||||
if wantsSell(policy) then
|
||||
local homeId = run.sourceWayPointIds ~= nil and run.sourceWayPointIds[1] or nil
|
||||
local quote = findSellQuote(adEnv, vehicle, product, ADSources.getSellMarkers(adEnv, farmId), run.sellAtPercent, homeId)
|
||||
if quote ~= nil and quote.percent >= (run.sellAtPercent or 90) then
|
||||
return quote.wayPointId
|
||||
end
|
||||
end
|
||||
if wantsStore(policy) then
|
||||
local store = findStoreMarker(adEnv, vehicle, product, capacity, sources, liters or capacity)
|
||||
if store ~= nil then
|
||||
return store.wayPointId
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- ---- tur ----
|
||||
|
||||
local function copyList(list)
|
||||
@@ -301,8 +369,15 @@ function ADOutbound.choosePickup(adEnv, vehicle)
|
||||
end
|
||||
releaseHold(vehicle)
|
||||
runStatus.warning = nil
|
||||
warned[vehicle] = nil
|
||||
applyTrip(adEnv, vehicle, run, trip, sourcesById)
|
||||
-- Prisgrundlaget logges, så det kan holdes op mod spillets Priser-menu (I5 i review).
|
||||
if trip.kind == "sell" then
|
||||
for _, product in ipairs(input.products) do
|
||||
if product.fillType == trip.fillTypes[1] and product.quote ~= nil then
|
||||
Logging.info(ADOutbound.LOG_PREFIX .. "'%s' pris %s: %.0f kr/1000 l ved %s, årets top %.0f -> %d %% (kræver %d)", tostring(vehicle:getName()), product.name, (product.quote.price or 0) * 1000, tostring(product.quote.stationName), (product.quote.top or 0) * 1000, product.quote.percent or 0, run.sellAtPercent or 90)
|
||||
end
|
||||
end
|
||||
end
|
||||
local marker = getMarker(adEnv, trip.sourceIds[1])
|
||||
if marker == nil then
|
||||
return nil
|
||||
@@ -346,6 +421,13 @@ local function updateHold(adEnv, task, hold, dt)
|
||||
local state = trips[vehicle]
|
||||
if state ~= nil then
|
||||
goToSource(adEnv, state, task, state.current)
|
||||
return true
|
||||
end
|
||||
-- stadig hold, men ved en anden markør (et Wait-punkt blev ledigt): kør derhen
|
||||
local newHold = holds[vehicle]
|
||||
if newHold ~= nil and newHold.markerId ~= task.destinationID then
|
||||
ADWaitPool.redirect(task, newHold.markerId)
|
||||
task.activatedUALLoading = false
|
||||
end
|
||||
return true
|
||||
end
|
||||
@@ -384,7 +466,8 @@ local function updateSpawn(adEnv, state, task, source, dt)
|
||||
if spawn.pending ~= nil then
|
||||
spawn.pending.ms = spawn.pending.ms + (dt or 0)
|
||||
local stored, loaded = ADSources.countStored(source, fillType), countLoaded(task.trailers)
|
||||
if stored < spawn.pending.stored and loaded > spawn.pending.loaded then
|
||||
local level = adEnv.AutoDrive.getAllFillLevels(task.trailers) or 0
|
||||
if stored < spawn.pending.stored and (loaded > spawn.pending.loaded or level > spawn.pending.level) then
|
||||
spawn.pending = nil
|
||||
elseif spawn.pending.ms > ADOutbound.SPAWN_STALL_MS then
|
||||
warnOnce(adEnv, vehicle, "spawn", string.format("'%s': objektlageret %s spawner ikke %s (30 s uden fremdrift); kører med det der er", tostring(vehicle:getName()), tostring(source.name), fillTypeName(fillType)))
|
||||
@@ -411,7 +494,7 @@ local function updateSpawn(adEnv, state, task, source, dt)
|
||||
end
|
||||
end
|
||||
if ADSources.spawnOne(source, index) then
|
||||
spawn.pending = {stored = ADSources.countStored(source, fillType), loaded = countLoaded(task.trailers), ms = 0}
|
||||
spawn.pending = {stored = ADSources.countStored(source, fillType), loaded = countLoaded(task.trailers), level = adEnv.AutoDrive.getAllFillLevels(task.trailers) or 0, ms = 0}
|
||||
else
|
||||
warnOnce(adEnv, vehicle, "spawn", string.format("'%s': kunne ikke tage %s ud af %s; kører med det der er", tostring(vehicle:getName()), fillTypeName(fillType), tostring(source.name)))
|
||||
state.done = true
|
||||
@@ -445,6 +528,16 @@ function ADOutbound.beforeLoadUpdate(adEnv, task, dt)
|
||||
state.noProgressMs = state.noProgressMs + (dt or 0)
|
||||
end
|
||||
local spawnPending = state.spawn ~= nil and state.spawn.pending ~= nil
|
||||
if level <= 0 and state.noProgressMs > ADOutbound.EMPTY_SOURCE_MS and not spawnPending then
|
||||
local expires = now() + ADOutbound.EMPTY_SOURCE_TTL_MS
|
||||
for _, fillType in ipairs(state.trip.fillTypes) do
|
||||
emptyUntil[tostring(state.current) .. ":" .. tostring(fillType)] = expires
|
||||
end
|
||||
Logging.warning(ADOutbound.LOG_PREFIX .. "'%s': intet at læsse ved %s efter %d s; kilden springes over i %d min", tostring(vehicle:getName()), tostring(source ~= nil and source.name or state.current), math.floor(ADOutbound.EMPTY_SOURCE_MS / 1000), math.floor(ADOutbound.EMPTY_SOURCE_TTL_MS / 60000))
|
||||
state.noProgressMs, state.completed = 0, true
|
||||
task:finished()
|
||||
return true
|
||||
end
|
||||
if state.noProgressMs > ADOutbound.NO_PROGRESS_MS and level > 0 and not spawnPending then
|
||||
Logging.info(ADOutbound.LOG_PREFIX .. "'%s': ingen mere last ved %s; kører med %.0f l", tostring(vehicle:getName()), tostring(source ~= nil and source.name or state.current), level)
|
||||
state.noProgressMs, state.completed = 0, true
|
||||
|
||||
Reference in New Issue
Block a user