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).
|
-- UAL kan først læsse når vognen holder helt stille (AutoDrives egen grænse).
|
||||||
ADOutbound.MAX_SPEED_FOR_LOADING = 0.0005
|
ADOutbound.MAX_SPEED_FOR_LOADING = 0.0005
|
||||||
ADOutbound.NOTIFICATION_MS = 8000
|
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}
|
-- køretøj -> {run, trip, sources, queue, visited, current, lastFill, noProgressMs, spawn, done}
|
||||||
local trips = setmetatable({}, {__mode = "k"})
|
local trips = setmetatable({}, {__mode = "k"})
|
||||||
@@ -30,8 +34,17 @@ local holds = setmetatable({}, {__mode = "k"})
|
|||||||
local status = {}
|
local status = {}
|
||||||
-- køretøj -> {[nøgle] = tekst} (advarsler vises én gang pr. ændret tekst)
|
-- køretøj -> {[nøgle] = tekst} (advarsler vises én gang pr. ændret tekst)
|
||||||
local warned = setmetatable({}, {__mode = "k"})
|
local warned = setmetatable({}, {__mode = "k"})
|
||||||
|
-- "kilde-id:fillType" -> tidspunkt (ms) hvor udelukkelsen udløber
|
||||||
|
local emptyUntil = {}
|
||||||
local isInstalled = false
|
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)
|
local function getRun(vehicle)
|
||||||
return ADRunsController ~= nil and ADRunsController.getRunForVehicle ~= nil and ADRunsController.getRunForVehicle(vehicle) or nil
|
return ADRunsController ~= nil and ADRunsController.getRunForVehicle ~= nil and ADRunsController.getRunForVehicle(vehicle) or nil
|
||||||
end
|
end
|
||||||
@@ -95,33 +108,50 @@ end
|
|||||||
local function wantsSell(policy) return policy == "sell" or policy == "sellElseStore" end
|
local function wantsSell(policy) return policy == "sell" or policy == "sellElseStore" end
|
||||||
local function wantsStore(policy) return policy == "store" 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.
|
-- Bedste salgssted for varen. Rutetjek (A*) laves kun for stationer over pristærsklen, bedste
|
||||||
local function findSellQuote(adEnv, vehicle, product, farmId)
|
-- først, og stopper ved den første nåelige. Er ingen over tærsklen, returneres det bedste
|
||||||
local quotes, unreachableName = {}, nil
|
-- (unåelighed er ligegyldig, turen kører alligevel ikke) — til begrundelse og UI.
|
||||||
local homeId = product.sources[1] ~= nil and product.sources[1].wayPointId or nil
|
-- unreachableName = det bedst betalende over tærsklen, når intet over tærsklen kan nås.
|
||||||
for _, entry in ipairs(ADSources.getSellMarkers(adEnv, farmId)) do
|
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
|
if ADSources.stationAccepts(entry.station, product.fillType) then
|
||||||
local quote = ADPrices.getQuote(entry.station, product.fillType)
|
local quote = ADPrices.getQuote(entry.station, product.fillType)
|
||||||
if quote ~= nil then
|
if quote ~= nil then
|
||||||
local reachable = homeId == nil or ADWaitPool.hasRouteBothWays(adEnv, vehicle, entry.marker.id, homeId)
|
table.insert(quotes, {percent = quote.percent, price = quote.price, top = quote.top, wayPointId = entry.marker.id, stationName = tostring(entry.marker.name)})
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
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
|
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
|
if product.isPallet or ADLoadSwap == nil or ADLoadSwap.findDumpMarker == nil then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local available = 0
|
local available = litersOverride or 0
|
||||||
for _, source in ipairs(product.sources) do
|
if litersOverride == nil then
|
||||||
available = available + (source.available or 0)
|
for _, source in ipairs(product.sources) do
|
||||||
|
available = available + (source.available or 0)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
local liters = math.max(1, math.min(available, capacity or available))
|
local liters = math.max(1, math.min(available, capacity or available))
|
||||||
local isReachable = function(markerId)
|
local isReachable = function(markerId)
|
||||||
@@ -154,11 +184,14 @@ function ADOutbound.buildPlanInput(adEnv, run, vehicle)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
local byName, products = {}, {}
|
local byName, products = {}, {}
|
||||||
|
local time = now()
|
||||||
for _, source in ipairs(sources) do
|
for _, source in ipairs(sources) do
|
||||||
for _, item in ipairs(ADSources.getProducts(adEnv, source, farmId)) do
|
for _, item in ipairs(ADSources.getProducts(adEnv, source, farmId)) do
|
||||||
local policy = ADRuns.getProductPolicy(run, item.name)
|
local policy = ADRuns.getProductPolicy(run, item.name)
|
||||||
local canCarry = (item.isPallet and hasAutoload) or (not item.isPallet and carriable[item.fillType] == true)
|
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]
|
local product = byName[item.name]
|
||||||
if product == nil then
|
if product == nil then
|
||||||
product = {fillType = item.fillType, name = item.name, policy = policy, isPallet = item.isPallet, sources = {}}
|
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
|
||||||
end
|
end
|
||||||
table.sort(products, function(a, b) return a.name < b.name 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
|
for _, product in ipairs(products) do
|
||||||
if wantsSell(product.policy) then
|
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
|
product.quote = quote
|
||||||
if quote == nil and unreachableName ~= nil then
|
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))
|
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
|
return input, sourcesById, sources
|
||||||
end
|
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 ----
|
-- ---- tur ----
|
||||||
|
|
||||||
local function copyList(list)
|
local function copyList(list)
|
||||||
@@ -301,8 +369,15 @@ function ADOutbound.choosePickup(adEnv, vehicle)
|
|||||||
end
|
end
|
||||||
releaseHold(vehicle)
|
releaseHold(vehicle)
|
||||||
runStatus.warning = nil
|
runStatus.warning = nil
|
||||||
warned[vehicle] = nil
|
|
||||||
applyTrip(adEnv, vehicle, run, trip, sourcesById)
|
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])
|
local marker = getMarker(adEnv, trip.sourceIds[1])
|
||||||
if marker == nil then
|
if marker == nil then
|
||||||
return nil
|
return nil
|
||||||
@@ -346,6 +421,13 @@ local function updateHold(adEnv, task, hold, dt)
|
|||||||
local state = trips[vehicle]
|
local state = trips[vehicle]
|
||||||
if state ~= nil then
|
if state ~= nil then
|
||||||
goToSource(adEnv, state, task, state.current)
|
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
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@@ -384,7 +466,8 @@ local function updateSpawn(adEnv, state, task, source, dt)
|
|||||||
if spawn.pending ~= nil then
|
if spawn.pending ~= nil then
|
||||||
spawn.pending.ms = spawn.pending.ms + (dt or 0)
|
spawn.pending.ms = spawn.pending.ms + (dt or 0)
|
||||||
local stored, loaded = ADSources.countStored(source, fillType), countLoaded(task.trailers)
|
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
|
spawn.pending = nil
|
||||||
elseif spawn.pending.ms > ADOutbound.SPAWN_STALL_MS then
|
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)))
|
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
|
||||||
end
|
end
|
||||||
if ADSources.spawnOne(source, index) then
|
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
|
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)))
|
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
|
state.done = true
|
||||||
@@ -445,6 +528,16 @@ function ADOutbound.beforeLoadUpdate(adEnv, task, dt)
|
|||||||
state.noProgressMs = state.noProgressMs + (dt or 0)
|
state.noProgressMs = state.noProgressMs + (dt or 0)
|
||||||
end
|
end
|
||||||
local spawnPending = state.spawn ~= nil and state.spawn.pending ~= nil
|
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
|
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)
|
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
|
state.noProgressMs, state.completed = 0, true
|
||||||
|
|||||||
@@ -357,6 +357,18 @@ local function getOutboundFillTypes(adEnv, run, vehicle)
|
|||||||
return result, chosen, palletCount, canLoadPallets
|
return result, chosen, palletCount, canLoadPallets
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Last på vognen ved Start: nil = tom vogn, waypoint-id = mål for lasten, false = intet mål.
|
||||||
|
local function getCargoTarget(adEnv, run, vehicle)
|
||||||
|
if ADUnloadWait == nil or ADUnloadWait.getRestLoad == nil or ADOutbound == nil or ADOutbound.planCargoTarget == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local fillType, liters = ADUnloadWait.getRestLoad(adEnv, vehicle)
|
||||||
|
if fillType == nil or liters < 100 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return ADOutbound.planCargoTarget(adEnv, run, vehicle, fillType, liters) or false
|
||||||
|
end
|
||||||
|
|
||||||
local function validateOutboundVehicle(adEnv, run, vehicle, result)
|
local function validateOutboundVehicle(adEnv, run, vehicle, result)
|
||||||
if #getRunSources(adEnv, run, ADSmartPickup.getFarmId(vehicle)) == 0 then
|
if #getRunSources(adEnv, run, ADSmartPickup.getFarmId(vehicle)) == 0 then
|
||||||
result.reason = "noSource"
|
result.reason = "noSource"
|
||||||
@@ -375,6 +387,15 @@ local function validateOutboundVehicle(adEnv, run, vehicle, result)
|
|||||||
result.reason = (palletCount == chosen and not canLoadPallets) and "needsAutoload" or "cannotCarry"
|
result.reason = (palletCount == chosen and not canLoadPallets) and "needsAutoload" or "cannotCarry"
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
-- Uden Wait-punkt ville traktoren holde ved kilden og læsse selv om planlæggeren sagde "vent".
|
||||||
|
if ADWaitPool ~= nil and #ADWaitPool.getCandidates(adEnv, run.waitPoolGroup) == 0 then
|
||||||
|
result.reason = "noWaitPoint"
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
if getCargoTarget(adEnv, run, vehicle) == false then
|
||||||
|
result.reason = "cargoNoTarget"
|
||||||
|
return result
|
||||||
|
end
|
||||||
if isFarFromNetwork(adEnv, vehicle) then
|
if isFarFromNetwork(adEnv, vehicle) then
|
||||||
result.reason = "farFromNetwork"
|
result.reason = "farFromNetwork"
|
||||||
return result
|
return result
|
||||||
@@ -523,7 +544,9 @@ local function applyOutboundRunToVehicle(adEnv, run, vehicle)
|
|||||||
stateModule:setMode(adEnv.AutoDrive.MODE_PICKUPANDDELIVER)
|
stateModule:setMode(adEnv.AutoDrive.MODE_PICKUPANDDELIVER)
|
||||||
local firstId = run.sourceWayPointIds[1]
|
local firstId = run.sourceWayPointIds[1]
|
||||||
stateModule:setFirstMarkerByWayPointId(firstId)
|
stateModule:setFirstMarkerByWayPointId(firstId)
|
||||||
stateModule:setSecondMarkerByWayPointId(firstId)
|
-- Læsset vogn: AD kører først til aflæsning (2. markør) — dér skal lastens mål stå.
|
||||||
|
local cargoTarget = getCargoTarget(adEnv, run, vehicle)
|
||||||
|
stateModule:setSecondMarkerByWayPointId(cargoTarget or firstId)
|
||||||
local fillTypes = getOutboundFillTypes(adEnv, run, vehicle)
|
local fillTypes = getOutboundFillTypes(adEnv, run, vehicle)
|
||||||
stateModule.selectedFillTypes = copyList(fillTypes)
|
stateModule.selectedFillTypes = copyList(fillTypes)
|
||||||
if fillTypes[1] ~= nil then
|
if fillTypes[1] ~= nil then
|
||||||
|
|||||||
@@ -34,24 +34,68 @@ local function getPlaceableName(placeable, fallback)
|
|||||||
return fallback
|
return fallback
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Pallevarer har en pallefil i spillets fillType-definition (halmkurve, mel, brød ...).
|
-- Varer der KAN stå på paller (spillets fillType-definition: isPalletType eller pallefil).
|
||||||
|
-- Sukker og mel er palletyper, men læsses som bulk ved en fabrik med læssestation — så om
|
||||||
|
-- varen er palle eller bulk afgøres pr. kilde i isPalletAt, ikke her.
|
||||||
function ADSources.isPalletFillType(fillType)
|
function ADSources.isPalletFillType(fillType)
|
||||||
local desc = g_fillTypeManager ~= nil and g_fillTypeManager.getFillTypeByIndex ~= nil and g_fillTypeManager:getFillTypeByIndex(fillType) or nil
|
local desc = g_fillTypeManager ~= nil and g_fillTypeManager.getFillTypeByIndex ~= nil and g_fillTypeManager:getFillTypeByIndex(fillType) or nil
|
||||||
return desc ~= nil and desc.palletFilename ~= nil
|
return desc ~= nil and (desc.isPalletType == true or desc.palletFilename ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stationSupports(station, fillType)
|
||||||
|
if station == nil then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if station.supportedFillTypes ~= nil then
|
||||||
|
return station.supportedFillTypes[fillType] == true
|
||||||
|
end
|
||||||
|
if station.getIsFillTypeSupported ~= nil then
|
||||||
|
local ok, value = pcall(station.getIsFillTypeSupported, station, fillType)
|
||||||
|
return ok and value == true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Palle eller bulk ved DENNE kilde: silo = bulk; objektlager = palle; fabrik = bulk hvis
|
||||||
|
-- markørens læssestation udleverer varen, ellers palle hvis varen er en palletype.
|
||||||
|
function ADSources.isPalletAt(source, fillType)
|
||||||
|
if source.kind == "silo" then
|
||||||
|
return false
|
||||||
|
elseif source.kind == "objectStorage" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if stationSupports(source.station, fillType) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return ADSources.isPalletFillType(fillType)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getProductionPoint(placeable)
|
local function getProductionPoint(placeable)
|
||||||
return placeable ~= nil and placeable.spec_productionPoint ~= nil and placeable.spec_productionPoint.productionPoint or nil
|
return placeable ~= nil and placeable.spec_productionPoint ~= nil and placeable.spec_productionPoint.productionPoint or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Nærmeste objektlager inden for MAX_DISTANCE af waypointet, som farmen må bruge.
|
-- Placeable'ns "nærmeste punkt" for kildegenkendelse: pallespawnerens første plads (fabrik)
|
||||||
local function findObjectStorageNear(wayPoint, farmId)
|
-- eller rodnoden.
|
||||||
|
local function getPlaceableAnchor(placeable)
|
||||||
|
local productionPoint = getProductionPoint(placeable)
|
||||||
|
local spawner = productionPoint ~= nil and productionPoint.palletSpawner or nil
|
||||||
|
local place = spawner ~= nil and spawner.spawnPlaces ~= nil and spawner.spawnPlaces[1] or nil
|
||||||
|
local node = place ~= nil and place.startNode or placeable.rootNode
|
||||||
|
local x, z = getNodePosition(node)
|
||||||
|
if x == nil and node ~= placeable.rootNode then
|
||||||
|
x, z = getNodePosition(placeable.rootNode)
|
||||||
|
end
|
||||||
|
return x, z
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Nærmeste placeable inden for MAX_DISTANCE af waypointet som farmen må bruge og predicate godkender.
|
||||||
|
local function findPlaceableNear(wayPoint, farmId, predicate)
|
||||||
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
|
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
|
||||||
local access = g_currentMission ~= nil and g_currentMission.accessHandler or nil
|
local access = g_currentMission ~= nil and g_currentMission.accessHandler or nil
|
||||||
local best, bestDistance = nil, ADSources.MAX_DISTANCE * ADSources.MAX_DISTANCE
|
local best, bestDistance = nil, ADSources.MAX_DISTANCE * ADSources.MAX_DISTANCE
|
||||||
for _, placeable in pairs(placeables) do
|
for _, placeable in pairs(placeables) do
|
||||||
if placeable.spec_objectStorage ~= nil and (access == nil or access:canFarmAccess(farmId, placeable)) then
|
if predicate(placeable) and (access == nil or access:canFarmAccess(farmId, placeable)) then
|
||||||
local x, z = getNodePosition(placeable.rootNode)
|
local x, z = getPlaceableAnchor(placeable)
|
||||||
if x ~= nil then
|
if x ~= nil then
|
||||||
local d = distance2(wayPoint.x, wayPoint.z, x, z)
|
local d = distance2(wayPoint.x, wayPoint.z, x, z)
|
||||||
if d < bestDistance then
|
if d < bestDistance then
|
||||||
@@ -63,6 +107,14 @@ local function findObjectStorageNear(wayPoint, farmId)
|
|||||||
return best
|
return best
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function isObjectStorage(placeable)
|
||||||
|
return placeable.spec_objectStorage ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isProductionPoint(placeable)
|
||||||
|
return getProductionPoint(placeable) ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
-- {kind = "factory"|"silo"|"objectStorage", placeable, station|nil, wayPointId, name} eller nil.
|
-- {kind = "factory"|"silo"|"objectStorage", placeable, station|nil, wayPointId, name} eller nil.
|
||||||
function ADSources.identify(adEnv, wayPointId, farmId)
|
function ADSources.identify(adEnv, wayPointId, farmId)
|
||||||
local wayPoint = adEnv.ADGraphManager:getWayPointById(wayPointId)
|
local wayPoint = adEnv.ADGraphManager:getWayPointById(wayPointId)
|
||||||
@@ -79,7 +131,13 @@ function ADSources.identify(adEnv, wayPointId, farmId)
|
|||||||
if placeable ~= nil and placeable.spec_silo ~= nil then
|
if placeable ~= nil and placeable.spec_silo ~= nil then
|
||||||
return {kind = "silo", placeable = placeable, station = station, wayPointId = wayPointId, name = getPlaceableName(placeable, markerName)}
|
return {kind = "silo", placeable = placeable, station = station, wayPointId = wayPointId, name = getPlaceableName(placeable, markerName)}
|
||||||
end
|
end
|
||||||
local storage = findObjectStorageNear(wayPoint, farmId)
|
-- Rene pallefabrikker (halmvarer, bageri ...) har ingen læssestation: find dem på afstand
|
||||||
|
-- til pallespawneren; dernæst objektlagre.
|
||||||
|
local factory = findPlaceableNear(wayPoint, farmId, isProductionPoint)
|
||||||
|
if factory ~= nil then
|
||||||
|
return {kind = "factory", placeable = factory, station = nil, wayPointId = wayPointId, name = getPlaceableName(factory, markerName)}
|
||||||
|
end
|
||||||
|
local storage = findPlaceableNear(wayPoint, farmId, isObjectStorage)
|
||||||
if storage ~= nil then
|
if storage ~= nil then
|
||||||
return {kind = "objectStorage", placeable = storage, station = nil, wayPointId = wayPointId, name = getPlaceableName(storage, markerName)}
|
return {kind = "objectStorage", placeable = storage, station = nil, wayPointId = wayPointId, name = getPlaceableName(storage, markerName)}
|
||||||
end
|
end
|
||||||
@@ -143,7 +201,7 @@ local function getFactoryProducts(source, farmId)
|
|||||||
seen[fillType] = true
|
seen[fillType] = true
|
||||||
local level = storage ~= nil and storage.getFillLevel ~= nil and (storage:getFillLevel(fillType) or 0) or 0
|
local level = storage ~= nil and storage.getFillLevel ~= nil and (storage:getFillLevel(fillType) or 0) or 0
|
||||||
local capacity = storage ~= nil and storage.getCapacity ~= nil and (storage:getCapacity(fillType) or 0) or 0
|
local capacity = storage ~= nil and storage.getCapacity ~= nil and (storage:getCapacity(fillType) or 0) or 0
|
||||||
local isPallet = ADSources.isPalletFillType(fillType)
|
local isPallet = ADSources.isPalletAt(source, fillType)
|
||||||
local available = level + (isPallet and getPalletsNearSpawner(source.placeable, fillType, farmId) or 0)
|
local available = level + (isPallet and getPalletsNearSpawner(source.placeable, fillType, farmId) or 0)
|
||||||
table.insert(products, newProduct(fillType, available, level, capacity, isPallet))
|
table.insert(products, newProduct(fillType, available, level, capacity, isPallet))
|
||||||
end
|
end
|
||||||
@@ -165,7 +223,7 @@ local function getSiloProducts(source)
|
|||||||
end
|
end
|
||||||
local products = {}
|
local products = {}
|
||||||
for fillType, level in pairs(levels) do
|
for fillType, level in pairs(levels) do
|
||||||
table.insert(products, newProduct(fillType, level, level, capacities[fillType] or 0, ADSources.isPalletFillType(fillType)))
|
table.insert(products, newProduct(fillType, level, level, capacities[fillType] or 0, false))
|
||||||
end
|
end
|
||||||
table.sort(products, function(a, b) return a.fillType < b.fillType end)
|
table.sort(products, function(a, b) return a.fillType < b.fillType end)
|
||||||
return products
|
return products
|
||||||
|
|||||||
@@ -5,12 +5,24 @@
|
|||||||
|
|
||||||
-- Bedste pris-% for varen over alle salgssteder (uanset vejnet — det er til visning; turen
|
-- Bedste pris-% for varen over alle salgssteder (uanset vejnet — det er til visning; turen
|
||||||
-- bruger kun nåelige). {percent, stationName} eller nil.
|
-- bruger kun nåelige). {percent, stationName} eller nil.
|
||||||
|
SmartPickupFrame.SELL_MARKERS_CACHE_MS = 30000
|
||||||
|
|
||||||
|
-- Salgsstederne slås op over alle markører × stationer; cachet, så det ikke sker pr. vare pr. sekund.
|
||||||
|
function SmartPickupFrame:getSellMarkersCached(adEnv)
|
||||||
|
local time = g_time or (os.clock ~= nil and os.clock() * 1000) or 0
|
||||||
|
if self.sellMarkersCache == nil or (time - (self.sellMarkersCacheTime or 0)) > SmartPickupFrame.SELL_MARKERS_CACHE_MS then
|
||||||
|
self.sellMarkersCache = ADSources.getSellMarkers(adEnv, g_currentMission:getFarmId())
|
||||||
|
self.sellMarkersCacheTime = time
|
||||||
|
end
|
||||||
|
return self.sellMarkersCache
|
||||||
|
end
|
||||||
|
|
||||||
function SmartPickupFrame:getProductQuote(adEnv, fillType)
|
function SmartPickupFrame:getProductQuote(adEnv, fillType)
|
||||||
if ADSources == nil or ADPrices == nil then
|
if ADSources == nil or ADPrices == nil then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local best = nil
|
local best = nil
|
||||||
for _, entry in ipairs(ADSources.getSellMarkers(adEnv, g_currentMission:getFarmId())) do
|
for _, entry in ipairs(self:getSellMarkersCached(adEnv)) do
|
||||||
if ADSources.stationAccepts(entry.station, fillType) then
|
if ADSources.stationAccepts(entry.station, fillType) then
|
||||||
local quote = ADPrices.getQuote(entry.station, fillType)
|
local quote = ADPrices.getQuote(entry.station, fillType)
|
||||||
if quote ~= nil and (best == nil or quote.percent > best.percent) then
|
if quote ~= nil and (best == nil or quote.percent > best.percent) then
|
||||||
@@ -31,8 +43,15 @@ function SmartPickupFrame:describeProduct(adEnv, product)
|
|||||||
return string.format(self.i18n:getText("spu_product_row"), tostring(product.title), SmartPickupFrame.formatCompact(product.available or 0), price)
|
return string.format(self.i18n:getText("spu_product_row"), tostring(product.title), SmartPickupFrame.formatCompact(product.available or 0), price)
|
||||||
end
|
end
|
||||||
|
|
||||||
function SmartPickupFrame:getPolicyIndex(policy)
|
-- Paller kan ikke lagres i en silo: kun Fra/Salg for pallevarer.
|
||||||
for index, name in ipairs(SmartPickupFrame.POLICIES) do
|
SmartPickupFrame.PALLET_POLICIES = {"off", "sell"}
|
||||||
|
|
||||||
|
function SmartPickupFrame.getPoliciesFor(product)
|
||||||
|
return product.isPallet and SmartPickupFrame.PALLET_POLICIES or SmartPickupFrame.POLICIES
|
||||||
|
end
|
||||||
|
|
||||||
|
function SmartPickupFrame.getPolicyIndex(policies, policy)
|
||||||
|
for index, name in ipairs(policies) do
|
||||||
if name == policy then
|
if name == policy then
|
||||||
return index
|
return index
|
||||||
end
|
end
|
||||||
@@ -59,16 +78,17 @@ end
|
|||||||
|
|
||||||
function SmartPickupFrame:fillProductRows(adEnv, run)
|
function SmartPickupFrame:fillProductRows(adEnv, run)
|
||||||
self.productRows = {}
|
self.productRows = {}
|
||||||
local policyTexts = {}
|
|
||||||
for _, policy in ipairs(SmartPickupFrame.POLICIES) do
|
|
||||||
table.insert(policyTexts, self.i18n:getText("spu_policy_" .. policy))
|
|
||||||
end
|
|
||||||
local products = ADRunsController.getRunProducts(adEnv, run)
|
local products = ADRunsController.getRunProducts(adEnv, run)
|
||||||
for _, product in ipairs(products) do
|
for _, product in ipairs(products) do
|
||||||
local row = self:addRow(self.productPrefab, {kind = "product", name = product.name})
|
local policies = SmartPickupFrame.getPoliciesFor(product)
|
||||||
|
local policyTexts = {}
|
||||||
|
for _, policy in ipairs(policies) do
|
||||||
|
table.insert(policyTexts, self.i18n:getText("spu_policy_" .. policy))
|
||||||
|
end
|
||||||
|
local row = self:addRow(self.productPrefab, {kind = "product", name = product.name, policies = policies})
|
||||||
local option = row:getDescendantByName("option")
|
local option = row:getDescendantByName("option")
|
||||||
option:setTexts(policyTexts)
|
option:setTexts(policyTexts)
|
||||||
option:setState(self:getPolicyIndex(product.policy), false)
|
option:setState(SmartPickupFrame.getPolicyIndex(policies, product.policy), false)
|
||||||
local title = row:getDescendantByName("title")
|
local title = row:getDescendantByName("title")
|
||||||
title:setText(self:describeProduct(adEnv, product))
|
title:setText(self:describeProduct(adEnv, product))
|
||||||
table.insert(self.productRows, {name = product.name, title = title, option = option})
|
table.insert(self.productRows, {name = product.name, title = title, option = option})
|
||||||
@@ -163,7 +183,7 @@ function SmartPickupFrame:onClickProduct(state, element)
|
|||||||
self:safe(function(frame)
|
self:safe(function(frame)
|
||||||
local run, data = frame:getRun(), frame.rowData[element]
|
local run, data = frame:getRun(), frame.rowData[element]
|
||||||
if run == nil or data == nil or data.kind ~= "product" then return end
|
if run == nil or data == nil or data.kind ~= "product" then return end
|
||||||
local policy = SmartPickupFrame.POLICIES[state] or "off"
|
local policy = (data.policies or SmartPickupFrame.POLICIES)[state] or "off"
|
||||||
ADRunsController.store = ADRuns.setProductPolicy(ADRunsController.store, run.id, data.name, policy)
|
ADRunsController.store = ADRuns.setProductPolicy(ADRunsController.store, run.id, data.name, policy)
|
||||||
Logging.info(SmartPickupFrame.LOG_PREFIX .. "run '%s': %s=%s", tostring(run.name), tostring(data.name), policy)
|
Logging.info(SmartPickupFrame.LOG_PREFIX .. "run '%s': %s=%s", tostring(run.name), tostring(data.name), policy)
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -83,6 +83,8 @@
|
|||||||
<text name="spu_noProducts" text="Kilderne har ingen varer endnu"/>
|
<text name="spu_noProducts" text="Kilderne har ingen varer endnu"/>
|
||||||
<text name="spu_reason_noSource" text="ingen genkendt kilde"/>
|
<text name="spu_reason_noSource" text="ingen genkendt kilde"/>
|
||||||
<text name="spu_reason_noProducts" text="ingen vare valgt"/>
|
<text name="spu_reason_noProducts" text="ingen vare valgt"/>
|
||||||
|
<text name="spu_reason_noWaitPoint" text="intet Wait-punkt i puljen (markør i en mappe med 'Wait' i navnet)"/>
|
||||||
|
<text name="spu_reason_cargoNoTarget" text="lasten på vognen har intet mål (pris for lav / ingen silo) — tøm vognen"/>
|
||||||
<text name="spu_reason_needsAutoload" text="pallevarer kræver Universal Autoload på vognen"/>
|
<text name="spu_reason_needsAutoload" text="pallevarer kræver Universal Autoload på vognen"/>
|
||||||
<text name="spu_product_row" text="%s — %s l — %s"/>
|
<text name="spu_product_row" text="%s — %s l — %s"/>
|
||||||
<text name="spu_price_pct" text="%d %% af top (%s)"/>
|
<text name="spu_price_pct" text="%d %% af top (%s)"/>
|
||||||
|
|||||||
@@ -83,6 +83,8 @@
|
|||||||
<text name="spu_noProducts" text="The sources hold no goods yet"/>
|
<text name="spu_noProducts" text="The sources hold no goods yet"/>
|
||||||
<text name="spu_reason_noSource" text="no recognised source"/>
|
<text name="spu_reason_noSource" text="no recognised source"/>
|
||||||
<text name="spu_reason_noProducts" text="no good selected"/>
|
<text name="spu_reason_noProducts" text="no good selected"/>
|
||||||
|
<text name="spu_reason_noWaitPoint" text="no Wait point in the pool (marker in a folder with 'Wait' in its name)"/>
|
||||||
|
<text name="spu_reason_cargoNoTarget" text="the load on the trailer has no destination (price too low / no silo) — empty the trailer"/>
|
||||||
<text name="spu_reason_needsAutoload" text="pallet goods need Universal Autoload on the trailer"/>
|
<text name="spu_reason_needsAutoload" text="pallet goods need Universal Autoload on the trailer"/>
|
||||||
<text name="spu_product_row" text="%s — %s l — %s"/>
|
<text name="spu_product_row" text="%s — %s l — %s"/>
|
||||||
<text name="spu_price_pct" text="%d %% of top (%s)"/>
|
<text name="spu_price_pct" text="%d %% of top (%s)"/>
|
||||||
|
|||||||
@@ -300,6 +300,100 @@ ADOutbound.releaseInactive()
|
|||||||
check("O9 frigivet", ADWaitPool.getOwner(20), nil)
|
check("O9 frigivet", ADWaitPool.getOwner(20), nil)
|
||||||
check("O9 hold væk", ADOutbound.getHoldFor(v9), nil)
|
check("O9 hold væk", ADOutbound.getHoldFor(v9), nil)
|
||||||
|
|
||||||
|
-- O11 (I2): hold-markøren skifter (Wait-punkt bliver ledigt) -> opgaven omdirigeres
|
||||||
|
ADWaitPool.releaseVehicle(v9)
|
||||||
|
local v11 = newVehicle("Hold", 25, 0, 20000)
|
||||||
|
runs[v11] = outboundRun(11, {1}, {{fillType = "FORAGE", policy = "sell"}}) -- FORAGE findes ikke ved kilden -> aldrig tur
|
||||||
|
local blocker = newVehicle("Blocker", 0, 0, 1)
|
||||||
|
ADWaitPool.claim(20, blocker)
|
||||||
|
local choice11 = ADOutbound.choosePickup(adEnv, v11)
|
||||||
|
check("O11 uden ledigt Wait: holder ved kilden", choice11.markerIndex, mSugar.markerIndex)
|
||||||
|
local task11 = newTask(v11, 1)
|
||||||
|
v11.atTarget = true
|
||||||
|
ADOutbound.beforeLoadUpdate(adEnv, task11, 100)
|
||||||
|
ADWaitPool.releaseVehicle(blocker)
|
||||||
|
ADOutbound.beforeLoadUpdate(adEnv, task11, 5000)
|
||||||
|
check("O11 Wait-punkt ledigt -> omdirigeret", task11.destinationID, 20)
|
||||||
|
check("O11 hold peger på Wait", ADOutbound.getHoldFor(v11).markerName, "Wait 3")
|
||||||
|
ADWaitPool.releaseVehicle(v11)
|
||||||
|
|
||||||
|
-- O12 (I3): mål for last der allerede er på vognen ved Start
|
||||||
|
local v12 = newVehicle("Loaded", 0, 0, 20000)
|
||||||
|
runs[v12] = outboundRun(12, {1}, {{fillType = "SUGAR", policy = "sell"}})
|
||||||
|
quotes["selling station"][SUGAR].percent = 92
|
||||||
|
check("O12 salgsmål for sukker-last", ADOutbound.planCargoTarget(adEnv, runs[v12], v12, SUGAR), 10)
|
||||||
|
quotes["selling station"][SUGAR].percent = 50
|
||||||
|
check("O12 pris lav, ingen lager-politik -> nil", ADOutbound.planCargoTarget(adEnv, runs[v12], v12, SUGAR), nil)
|
||||||
|
runs[v12].products = {{fillType = "SUGAR", policy = "sellElseStore"}}
|
||||||
|
check("O12 pris lav -> silo", ADOutbound.planCargoTarget(adEnv, runs[v12], v12, SUGAR), 30)
|
||||||
|
check("O12 last der ikke er i run'et -> nil", ADOutbound.planCargoTarget(adEnv, runs[v12], v12, FORAGE), nil)
|
||||||
|
quotes["selling station"][SUGAR].percent = 92
|
||||||
|
|
||||||
|
-- O13 (I4): tom vogn 30 s ved kilden -> kilden udelukkes for varen og turen afsluttes
|
||||||
|
local v13 = newVehicle("Empty", 0, 0, 20000)
|
||||||
|
runs[v13] = outboundRun(13, {1, 2}, {{fillType = "SUGAR", policy = "sell"}}, {collectRound = false})
|
||||||
|
local choice13 = ADOutbound.choosePickup(adEnv, v13)
|
||||||
|
check("O13 første kilde = silo 4 (mest)", choice13.markerIndex, mSilo.markerIndex)
|
||||||
|
local task13 = newTask(v13, 2)
|
||||||
|
v13.atTarget = true
|
||||||
|
ADOutbound.beforeLoadUpdate(adEnv, task13, 100)
|
||||||
|
ADOutbound.beforeLoadUpdate(adEnv, task13, 31000)
|
||||||
|
check("O13 finished efter 30 s uden last", task13.finishedCalls, 1)
|
||||||
|
check("O13 WARN", lastLog("intet at læsse") ~= nil, true)
|
||||||
|
local choice13b = ADOutbound.choosePickup(adEnv, v13)
|
||||||
|
check("O13 næste plan udelukker den tomme kilde", choice13b.markerIndex, mSugar.markerIndex)
|
||||||
|
|
||||||
|
-- O14 (I6): rutetjek kun for salgssteder over tærsklen, bedste først, stop ved første nåelige
|
||||||
|
local routeChecks = 0
|
||||||
|
local originalGetPathTo = adEnv.ADGraphManager.getPathTo
|
||||||
|
adEnv.ADGraphManager.getPathTo = function(_, vehicle, id) routeChecks = routeChecks + 1 return originalGetPathTo(_, vehicle, id) end
|
||||||
|
local v14 = newVehicle("Routes", 0, 0, 20000)
|
||||||
|
runs[v14] = outboundRun(14, {1}, {{fillType = "SUGAR", policy = "sell"}})
|
||||||
|
quotes["Far station"][SUGAR].percent = 99
|
||||||
|
quotes["selling station"][SUGAR].percent = 92
|
||||||
|
ADOutbound.choosePickup(adEnv, v14)
|
||||||
|
check("O14 bedste nåelig først: én rute tjekket", routeChecks, 1)
|
||||||
|
check("O14 mål = bedst betalende", v14.ad.stateModule.secondMarkerId, 11)
|
||||||
|
routeChecks = 0
|
||||||
|
quotes["Far station"][SUGAR].percent = 60
|
||||||
|
ADOutbound.choosePickup(adEnv, v14)
|
||||||
|
check("O14 under tærsklen tjekkes ikke", routeChecks, 1)
|
||||||
|
adEnv.ADGraphManager.getPathTo = originalGetPathTo
|
||||||
|
|
||||||
|
-- O15 (M1): advarsel for vare A gentages ikke efter en tur med vare B
|
||||||
|
notifications = {}
|
||||||
|
local v15 = newVehicle("Two", 0, 0, 20000)
|
||||||
|
runs[v15] = outboundRun(15, {1, 2}, {{fillType = "SUGAR", policy = "sell"}, {fillType = "FORAGE", policy = "sell"}})
|
||||||
|
products[1] = {{fillType = SUGAR, name = "SUGAR", available = 5000, level = 5000, capacity = 180000, isPallet = false}, {fillType = FORAGE, name = "FORAGE", available = 5000, level = 5000, capacity = 180000, isPallet = false}}
|
||||||
|
quotes["selling station"][FORAGE] = {percent = 95, price = 1}
|
||||||
|
quotes["Far station"][SUGAR].percent = 99 -- (O14 satte den til 60)
|
||||||
|
unreachable[10] = true; unreachable[11] = true
|
||||||
|
ADOutbound.choosePickup(adEnv, v15) -- SUGAR uopnåelig -> advarsel, FORAGE uopnåelig -> advarsel
|
||||||
|
local firstCount = #notifications
|
||||||
|
unreachable[11] = nil -- Far station kan nås for SUGAR (FORAGE sælges kun på selling station)
|
||||||
|
ADOutbound.choosePickup(adEnv, v15) -- tur med SUGAR
|
||||||
|
check("O15 tur", ADOutbound.getTripFor(v15).kind, "sell")
|
||||||
|
ADOutbound.choosePickup(adEnv, v15) -- igen: FORAGE stadig uopnåelig, men allerede advaret
|
||||||
|
check("O15 FORAGE-advarsel gentages ikke", #notifications, firstCount)
|
||||||
|
unreachable[10] = nil
|
||||||
|
products[1] = {{fillType = SUGAR, name = "SUGAR", available = 5000, level = 5000, capacity = 180000, isPallet = false}}
|
||||||
|
|
||||||
|
-- O16 (M6): pallens ankomst ses også på fyldniveau når UAL's interne liste mangler
|
||||||
|
local v16 = newVehicle("NoList", 100, 0, 8000, true)
|
||||||
|
v16.trailers[1].spec_universalAutoload = {}
|
||||||
|
runs[v16] = outboundRun(16, {3}, {{fillType = "STRAW_BASKET", policy = "sell"}})
|
||||||
|
sources[3].stored[BASKET] = 3; sources[3].spawnCalls = 0
|
||||||
|
ADOutbound.choosePickup(adEnv, v16)
|
||||||
|
local task16 = newTask(v16, 3)
|
||||||
|
v16.atTarget, task16.activatedUALLoading = true, true
|
||||||
|
ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
|
||||||
|
check("O16 første spawn", sources[3].spawnCalls, 1)
|
||||||
|
sources[3].stored[BASKET] = 2
|
||||||
|
v16.trailers.level = 1000
|
||||||
|
ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
|
||||||
|
ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
|
||||||
|
check("O16 næste spawn via fyldniveau", sources[3].spawnCalls, 2)
|
||||||
|
|
||||||
-- O10: install wrapper: fejl i hook stopper ikke AD
|
-- O10: install wrapper: fejl i hook stopper ikke AD
|
||||||
local calls = {}
|
local calls = {}
|
||||||
adEnv.LoadAtDestinationTask.update = function(self, dt) calls.update = (calls.update or 0) + 1 end
|
adEnv.LoadAtDestinationTask.update = function(self, dt) calls.update = (calls.update or 0) + 1 end
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ function getWorldTranslation(node) local p = nodePositions[node]; return p.x, 0,
|
|||||||
|
|
||||||
dofile("FS25_ADSmartPickup/adRuns.lua")
|
dofile("FS25_ADSmartPickup/adRuns.lua")
|
||||||
dofile("FS25_ADSmartPickup/adRunsStorage.lua")
|
dofile("FS25_ADSmartPickup/adRunsStorage.lua")
|
||||||
|
dofile("FS25_ADSmartPickup/adWaitPool.lua")
|
||||||
dofile("FS25_ADSmartPickup/adRunsController.lua")
|
dofile("FS25_ADSmartPickup/adRunsController.lua")
|
||||||
local adEnv = FS25_AutoDrive
|
local adEnv = FS25_AutoDrive
|
||||||
|
|
||||||
@@ -496,6 +497,28 @@ local activity = ADRunsController.getVehicleActivity(adEnv, bulkTractor)
|
|||||||
check("Cout4 aktivitet = holder", activity.kind, "holding")
|
check("Cout4 aktivitet = holder", activity.kind, "holding")
|
||||||
check("Cout4 aktivitet sted", activity.place, "Wait 3")
|
check("Cout4 aktivitet sted", activity.place, "Wait 3")
|
||||||
check("Cout4 udkørsel har ingen mål", #ADRunsController.getRunTargets(outRun), 0)
|
check("Cout4 udkørsel har ingen mål", #ADRunsController.getRunTargets(outRun), 0)
|
||||||
|
-- Cout5 (I2): Start kræver et Wait-punkt i puljen; Cout6 (I3): last på vognen ved Start får eget mål
|
||||||
|
ADSupplyPlanner.isWaitMarker = function(marker) return string.find(string.lower(marker.group or ""), "%f[%a]wait%f[%A]") ~= nil end
|
||||||
|
local noWait = ADRunsController.createRun("No wait", "outbound")
|
||||||
|
ADRunsController.store = ADRuns.toggleSource(ADRunsController.store, noWait.id, 1)
|
||||||
|
ADRunsController.store = ADRuns.setProductPolicy(ADRunsController.store, noWait.id, "FORAGE", "store")
|
||||||
|
ADRunsController.store = ADRuns.updateRun(ADRunsController.store, noWait.id, {waitPoolGroup = "Pig Wait"})
|
||||||
|
ADRunsController.store = ADRuns.updateRun(ADRunsController.store, noWait.id, {waitPoolGroup = "Findes ikke"})
|
||||||
|
local noWaitRun = ADRuns.getRun(ADRunsController.store, noWait.id)
|
||||||
|
check("Cout5 ingen Wait-punkt i puljen -> noWaitPoint", ADRunsController.validateVehicle(adEnv, noWaitRun, bulkTractor).reason, "noWaitPoint")
|
||||||
|
ADRunsController.store = ADRuns.updateRun(ADRunsController.store, noWait.id, {waitPoolGroup = "Cow Wait"})
|
||||||
|
noWaitRun = ADRuns.getRun(ADRunsController.store, noWait.id)
|
||||||
|
check("Cout5 Wait-punkt findes -> ok", ADRunsController.validateVehicle(adEnv, noWaitRun, bulkTractor).ok, true)
|
||||||
|
ADUnloadWait.getRestLoad = function(_, v) return v.loadType, v.loadLiters or 0 end
|
||||||
|
ADOutbound.planCargoTarget = function(adEnv, run, vehicle, fillType) return fillType == TMR and 10 or nil end
|
||||||
|
bulkTractor.loadType, bulkTractor.loadLiters = TMR, 8000
|
||||||
|
ADRunsController.assignVehicle(noWait.id, bulkTractor)
|
||||||
|
bulkTractor.ad.stateModule.active = false
|
||||||
|
ADRunsController.startRun(adEnv, noWait.id)
|
||||||
|
check("Cout6 last på vognen -> 2. markør = lastens mål", bulkTractor.ad.stateModule.secondMarkerId, 10)
|
||||||
|
bulkTractor.loadType = STRAW
|
||||||
|
check("Cout6 last uden mål -> cargoNoTarget", ADRunsController.validateVehicle(adEnv, noWaitRun, bulkTractor).reason, "cargoNoTarget")
|
||||||
|
bulkTractor.loadType = nil
|
||||||
ADUnloadWait = nil; ADOutbound = nil; ADSources = nil
|
ADUnloadWait = nil; ADOutbound = nil; ADSources = nil
|
||||||
|
|
||||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
local SUGAR, FORAGE, BASKET, CRATE = 5, 6, 7, 8
|
local SUGAR, FORAGE, BASKET, CRATE = 5, 6, 7, 8
|
||||||
local DESC = {
|
local DESC = {
|
||||||
[SUGAR] = {index = SUGAR, name = "SUGAR", title = "Sukker"},
|
[SUGAR] = {index = SUGAR, name = "SUGAR", title = "Sukker", isPalletType = true},
|
||||||
[FORAGE] = {index = FORAGE, name = "FORAGE", title = "TMR"},
|
[FORAGE] = {index = FORAGE, name = "FORAGE", title = "TMR"},
|
||||||
[BASKET] = {index = BASKET, name = "STRAW_BASKET", title = "Halmkurve", palletFilename = "basketPallet.xml"},
|
[BASKET] = {index = BASKET, name = "STRAW_BASKET", title = "Halmkurve", palletFilename = "basketPallet.xml"},
|
||||||
[CRATE] = {index = CRATE, name = "STRAW_CRATE", title = "Halmkasser", palletFilename = "cratePallet.xml"},
|
[CRATE] = {index = CRATE, name = "STRAW_CRATE", title = "Halmkasser", palletFilename = "cratePallet.xml"},
|
||||||
@@ -60,7 +60,7 @@ local factory = {
|
|||||||
palletSpawner = {spawnPlaces = {{startNode = {x = 110, z = 100}}}},
|
palletSpawner = {spawnPlaces = {{startNode = {x = 110, z = 100}}}},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
local factoryStation = {owningPlaceable = factory}
|
local factoryStation = {owningPlaceable = factory, supportedFillTypes = {[SUGAR] = true}}
|
||||||
local silo = {rootNode = {x = 200, z = 200}, spec_silo = {storages = {storageOf({[FORAGE] = 74500}, {[FORAGE] = 12000000})}}, getName = function() return "Farma" end}
|
local silo = {rootNode = {x = 200, z = 200}, spec_silo = {storages = {storageOf({[FORAGE] = 74500}, {[FORAGE] = 12000000})}}, getName = function() return "Farma" end}
|
||||||
local siloStation = {owningPlaceable = silo}
|
local siloStation = {owningPlaceable = silo}
|
||||||
local spawnCalls = {}
|
local spawnCalls = {}
|
||||||
@@ -175,8 +175,27 @@ check("S7 ingen kilde", ADSources.identify(adEnv, 4, 1), nil)
|
|||||||
check("S7 ukendt waypoint", ADSources.identify(adEnv, 99, 1), nil)
|
check("S7 ukendt waypoint", ADSources.identify(adEnv, 99, 1), nil)
|
||||||
|
|
||||||
-- S8: hjælpere
|
-- S8: hjælpere
|
||||||
check("S8 isPalletFillType", ADSources.isPalletFillType(BASKET), true)
|
check("S8 isPalletFillType palle", ADSources.isPalletFillType(BASKET), true)
|
||||||
check("S8 bulk", ADSources.isPalletFillType(SUGAR), false)
|
check("S8 isPalletFillType palletype uden fil", ADSources.isPalletFillType(SUGAR), true)
|
||||||
|
check("S8 isPalletFillType bulk", ADSources.isPalletFillType(FORAGE), false)
|
||||||
|
-- C1: palle/bulk afgøres pr. kilde: fabrik med læssestation der tager varen = bulk; silo = altid bulk; objektlager = palle
|
||||||
|
check("S8 sukker ved fabrik med læssestation = bulk", ADSources.isPalletAt(source1, SUGAR), false)
|
||||||
|
check("S8 kurve ved fabrik (station tager dem ikke) = palle", ADSources.isPalletAt(source1, BASKET), true)
|
||||||
|
check("S8 silo altid bulk", ADSources.isPalletAt(source2, SUGAR), false)
|
||||||
|
check("S8 objektlager altid palle", ADSources.isPalletAt(source3, FORAGE), true)
|
||||||
|
-- S9 (I1): ren pallefabrik uden læssestation genkendes via afstand til spawner/rootNode
|
||||||
|
local basketFactory = {
|
||||||
|
rootNode = {x = 600, z = 600}, getName = function() return "Straw factory" end,
|
||||||
|
spec_productionPoint = {productionPoint = {productions = {{outputs = {{type = BASKET, amount = 1}}}}, storage = storageOf({[BASKET] = 0}, {[BASKET] = 8000}), palletSpawner = {spawnPlaces = {{startNode = {x = 610, z = 600}}}}}},
|
||||||
|
}
|
||||||
|
table.insert(g_currentMission.placeableSystem.placeables, basketFactory)
|
||||||
|
addMarker(7, "Straw factory pickup", 605, 590)
|
||||||
|
local source9 = ADSources.identify(adEnv, 7, 1)
|
||||||
|
check("S9 pallefabrik uden station = factory", source9.kind, "factory")
|
||||||
|
check("S9 station nil", source9.station, nil)
|
||||||
|
local products9 = ADSources.getProducts(adEnv, source9, 1)
|
||||||
|
check("S9 kurve er palle", products9[1].isPallet, true)
|
||||||
|
check("S9 fabrik foretrækkes frem for objektlager i nærheden", ADSources.identify(adEnv, 7, 1).placeable, basketFactory)
|
||||||
local excluded = ADSources.getSourcePlaceables({source1, source2})
|
local excluded = ADSources.getSourcePlaceables({source1, source2})
|
||||||
check("S8 udelukkede placeables", excluded[factory] and excluded[silo], true)
|
check("S8 udelukkede placeables", excluded[factory] and excluded[silo], true)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user