feat(udkørsel): 'Lager til' — tvunget aflæsningssted pr. flow (siloer/egne fabrikker; første med plads og vej), XML #storeBuildings (v1.18.0.0)

This commit is contained in:
2026-09-24 05:13:09 +02:00
parent c814557ae1
commit d0485caa33
16 changed files with 169 additions and 11 deletions
+24
View File
@@ -139,6 +139,30 @@ function ADLoadSwap.findDumpMarker(adEnv, vehicle, fillType, rest, homeWayPointI
return nil, string.format("%d silo(er) tager varen, men AutoDrive finder ingen rute fra traktoren til dem", withSpace)
end
-- Tvunget lagermål (flowets "Lager til"): kun de valgte aflæsningsmarkører, i rækkefølge. Første der
-- tager varen, har plads til læsset og kan nås, vinder. Returnerer marker — eller nil + forklaring.
function ADLoadSwap.findForcedDumpMarker(adEnv, vehicle, fillType, liters, markerIds, isReachable)
local farmId = ADSmartPickup.getFarmId(vehicle)
local reasons = {}
for _, wayPointId in ipairs(markerIds or {}) do
local marker = adEnv.ADGraphManager:getMapMarkerByWayPointId(wayPointId)
local station = marker ~= nil and ADSmartPickup.getUnloadStationAtWayPointId(adEnv, wayPointId, farmId) or nil
local name = marker ~= nil and tostring(marker.name) or tostring(wayPointId)
if station == nil then
table.insert(reasons, name .. ": ingen aflæsning ved markøren")
elseif station.getIsFillTypeSupported ~= nil and not station:getIsFillTypeSupported(fillType) then
table.insert(reasons, name .. ": tager ikke varen")
elseif not acceptsRest(station, fillType, liters, farmId) then
table.insert(reasons, name .. ": fuld")
elseif not isReachable(wayPointId) then
table.insert(reasons, name .. ": ingen vej")
else
return marker
end
end
return nil, "valgt lager: " .. (#reasons > 0 and table.concat(reasons, "; ") or "ingen markør")
end
-- Kaldes fra Wait-logikken hvert tick; redirect(task, id) og release(vehicle) er dens egne.
-- true = traktoren er sendt til en silo (Wait-punktet er frigivet).
function ADLoadSwap.tryFromWait(adEnv, task, wait, dt, redirect, release, isReachable)
+13 -3
View File
@@ -179,7 +179,7 @@ local function findSellQuote(adEnv, vehicle, product, sellMarkers, sellAtPercent
return quotes[1], nil
end
local function findStoreMarker(adEnv, vehicle, product, capacity, sources, litersOverride)
local function findStoreMarker(adEnv, vehicle, product, capacity, sources, litersOverride, run)
local isReachable = function(markerId)
return ADWaitPool.hasRouteTo(adEnv, vehicle, markerId)
end
@@ -200,6 +200,16 @@ local function findStoreMarker(adEnv, vehicle, product, capacity, sources, liter
end
end
local liters = math.max(1, math.min(available, capacity or available))
-- "Lager til" valgt i flowet: KUN de bygninger (fx gylle -> BGA, aldrig AIO-fabrikken)
local forced = run ~= nil and ADRunsController ~= nil and ADRunsController.getStoreMarkers ~= nil and ADRunsController.getStoreMarkers(adEnv, run) or {}
if #forced > 0 and ADLoadSwap.findForcedDumpMarker ~= nil then
local chosen, why = ADLoadSwap.findForcedDumpMarker(adEnv, vehicle, product.fillType, liters, forced, isReachable)
if chosen == nil then
warnOnce(adEnv, vehicle, "forced:" .. tostring(product.name) .. ":" .. tostring(why), string.format("'%s': %s kan ikke læsses af — %s", tostring(ADRuns.vehicleLabel(vehicle)), tostring(product.name), tostring(why)))
return nil
end
return {wayPointId = chosen.id, name = tostring(chosen.name), free = liters}
end
local avoid = ADRunsController ~= nil and ADRunsController.getPickupMarkers ~= nil and ADRunsController.getPickupMarkers() or nil
local marker = ADLoadSwap.findDumpMarker(adEnv, vehicle, product.fillType, liters, nil, isReachable, ADSources.getSourcePlaceables(sources), avoid, true)
if marker == nil then
@@ -293,7 +303,7 @@ function ADOutbound.buildPlanInput(adEnv, run, vehicle)
end
end
if wantsStore(product.policy) then
product.store = findStoreMarker(adEnv, vehicle, product, capacity, sources)
product.store = findStoreMarker(adEnv, vehicle, product, capacity, sources, nil, run)
end
end
local input = {
@@ -331,7 +341,7 @@ function ADOutbound.planCargoTarget(adEnv, run, vehicle, fillType, liters)
end
end
if wantsStore(policy) then
local store = findStoreMarker(adEnv, vehicle, product, capacity, sources, liters or capacity)
local store = findStoreMarker(adEnv, vehicle, product, capacity, sources, liters or capacity, run)
if store ~= nil then
return store.wayPointId
end
+12
View File
@@ -47,6 +47,7 @@ local function copyRun(run)
sourceWayPointIds = copyList(run.sourceWayPointIds),
sourceBuildingIds = copyList(run.sourceBuildingIds),
targetBuildingIds = copyList(run.targetBuildingIds),
storeBuildingIds = copyList(run.storeBuildingIds),
products = copyProducts(run.products),
sellAtPercent = run.sellAtPercent,
storeAbovePercent = run.storeAbovePercent,
@@ -93,6 +94,7 @@ function ADRuns.createRun(store, name, kind)
sourceWayPointIds = {},
sourceBuildingIds = {},
targetBuildingIds = {},
storeBuildingIds = {},
products = {},
sellAtPercent = ADRuns.DEFAULT_SELL_AT_PERCENT,
storeAbovePercent = ADRuns.DEFAULT_STORE_ABOVE_PERCENT,
@@ -158,6 +160,7 @@ function ADRuns.updateRun(store, runId, changes)
if changes.sourceWayPointIds ~= nil then run.sourceWayPointIds = copyList(changes.sourceWayPointIds) end
if changes.sourceBuildingIds ~= nil then run.sourceBuildingIds = copyList(changes.sourceBuildingIds) end
if changes.targetBuildingIds ~= nil then run.targetBuildingIds = copyList(changes.targetBuildingIds) end
if changes.storeBuildingIds ~= nil then run.storeBuildingIds = copyList(changes.storeBuildingIds) end
if changes.products ~= nil then run.products = copyProducts(changes.products) end
if changes.sellAtPercent ~= nil then run.sellAtPercent = clampRange(changes.sellAtPercent, ADRuns.DEFAULT_SELL_AT_PERCENT, ADRuns.MIN_SELL_AT_PERCENT, ADRuns.MAX_SELL_AT_PERCENT) end
if changes.storeAbovePercent ~= nil then run.storeAbovePercent = clampRange(changes.storeAbovePercent, ADRuns.DEFAULT_STORE_ABOVE_PERCENT, ADRuns.MIN_STORE_ABOVE_PERCENT, ADRuns.MAX_STORE_ABOVE_PERCENT) end
@@ -182,6 +185,13 @@ function ADRuns.toggleSourceBuilding(store, runId, buildingId)
return ADRuns.updateRun(store, runId, {sourceBuildingIds = toggleInList(run.sourceBuildingIds, buildingId)})
end
-- Tvunget lagermål for udkørsel: lagerture kører KUN til disse bygninger (tom = moden vælger selv).
function ADRuns.toggleStoreBuilding(store, runId, buildingId)
local run = ADRuns.getRun(store, runId)
if run == nil then return copyStore(store) end
return ADRuns.updateRun(store, runId, {storeBuildingIds = toggleInList(run.storeBuildingIds, buildingId)})
end
function ADRuns.toggleTargetBuilding(store, runId, buildingId)
local run = ADRuns.getRun(store, runId)
if run == nil then return copyStore(store) end
@@ -350,6 +360,7 @@ function ADRuns.toRecords(store)
sources = table.concat(run.sourceWayPointIds or {}, " "),
sourceBuildings = table.concat(run.sourceBuildingIds or {}, " "),
targetBuildings = table.concat(run.targetBuildingIds or {}, " "),
storeBuildings = table.concat(run.storeBuildingIds or {}, " "),
products = ADRuns.serializeProducts(run.products),
sellAt = run.sellAtPercent,
storeAbove = run.storeAbovePercent,
@@ -392,6 +403,7 @@ function ADRuns.fromRecords(records)
sourceWayPointIds = sources,
sourceBuildingIds = parseIds(record.sourceBuildings),
targetBuildingIds = parseIds(record.targetBuildings),
storeBuildingIds = parseIds(record.storeBuildings),
products = ADRuns.parseProducts(record.products),
sellAtPercent = clampRange(record.sellAt, ADRuns.DEFAULT_SELL_AT_PERCENT, ADRuns.MIN_SELL_AT_PERCENT, ADRuns.MAX_SELL_AT_PERCENT),
storeAbovePercent = clampRange(record.storeAbove, ADRuns.DEFAULT_STORE_ABOVE_PERCENT, ADRuns.MIN_STORE_ABOVE_PERCENT, ADRuns.MAX_STORE_ABOVE_PERCENT),
+14
View File
@@ -147,6 +147,20 @@ function ADRunsController.getPickupMarkers()
return result
end
-- "Lager til": aflæsningsmarkører for flowets valgte lagerbygninger, i valgt rækkefølge (tom = ingen tvang).
function ADRunsController.getStoreMarkers(adEnv, run)
local result = {}
if ADBuildings == nil or run == nil then return result end
local farmId = g_currentMission ~= nil and g_currentMission.getFarmId ~= nil and g_currentMission:getFarmId() or 1
for _, buildingId in ipairs(run.storeBuildingIds or {}) do
local building = ADBuildings.find(adEnv, farmId, buildingId)
if building ~= nil and building.unloadMarker ~= nil then
table.insert(result, building.unloadMarker.id)
end
end
return result
end
-- ---- køretøjsnavne ----
ADRunsController.NAME_INTERVAL_MS = 2000
+2
View File
@@ -42,6 +42,7 @@ function ADRunsStorage.writeRecords(xmlFile, records)
-- v3: bygninger (flow-tavlen)
xmlFile:setString(key .. "#sourceBuildings", record.sourceBuildings or "")
xmlFile:setString(key .. "#targetBuildings", record.targetBuildings or "")
xmlFile:setString(key .. "#storeBuildings", record.storeBuildings or "")
xmlFile:setString(key .. "#products", record.products or "")
xmlFile:setInt(key .. "#sellAt", record.sellAt or ADRuns.DEFAULT_SELL_AT_PERCENT)
xmlFile:setInt(key .. "#storeAbove", record.storeAbove or ADRuns.DEFAULT_STORE_ABOVE_PERCENT)
@@ -69,6 +70,7 @@ function ADRunsStorage.readRecords(xmlFile)
sources = xmlFile:getString(key .. "#sources"),
sourceBuildings = xmlFile:getString(key .. "#sourceBuildings"),
targetBuildings = xmlFile:getString(key .. "#targetBuildings"),
storeBuildings = xmlFile:getString(key .. "#storeBuildings"),
products = xmlFile:getString(key .. "#products"),
sellAt = xmlFile:getInt(key .. "#sellAt"),
storeAbove = xmlFile:getInt(key .. "#storeAbove"),
+15 -4
View File
@@ -122,22 +122,25 @@ function SmartPickupFrame:fillBuildingRows(adEnv, run, role)
return
end
local chosen = {}
local ids = role == "load" and run.sourceBuildingIds or run.targetBuildingIds
local ids = role == "load" and run.sourceBuildingIds or role == "store" and run.storeBuildingIds or run.targetBuildingIds
local rowKind = role == "load" and "sourceBuilding" or role == "store" and "storeBuilding" or "targetBuilding"
for _, id in ipairs(ids or {}) do chosen[id] = true end
local count = 0
for _, building in ipairs(ADBuildings.list(adEnv, g_currentMission:getFarmId())) do
-- "Lager til": siloer og gårdens fabrikker (fx BGA) — markøren er bygningens aflæsningsmarkør
local fits = (role == "load" and building.canBeSource) or (role == "unload" and building.canBeTarget)
or (role == "store" and (building.kind == "silo" or building.kind == "factory"))
if fits then
count = count + 1
local row = self:addRow(self.ingredientPrefab, {kind = role == "load" and "sourceBuilding" or "targetBuilding", buildingId = building.id})
row:getDescendantByName("title"):setText(self:describeBuilding(building, role))
local row = self:addRow(self.ingredientPrefab, {kind = rowKind, buildingId = building.id})
row:getDescendantByName("title"):setText(self:describeBuilding(building, role == "store" and "unload" or role))
row:getDescendantByName("option"):setState(chosen[building.id] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end
end
-- valgte bygninger der ikke længere findes (solgt)
for _, id in ipairs(ids or {}) do
if ADBuildings.find(adEnv, g_currentMission:getFarmId(), id) == nil then
local row = self:addRow(self.ingredientPrefab, {kind = role == "load" and "sourceBuilding" or "targetBuilding", buildingId = id})
local row = self:addRow(self.ingredientPrefab, {kind = rowKind, buildingId = id})
row:getDescendantByName("title"):setText(string.format(" %s — %s", tostring(id), self.i18n:getText("spu_buildingGone")))
row:getDescendantByName("option"):setState(SmartPickupFrame.STATE_ON, false)
end
@@ -162,6 +165,8 @@ function SmartPickupFrame:rebuildFlowDetail(adEnv, run)
self.sellAtOption:setState(SmartPickupFrame.getStepIndex(self.sellAtSteps, run.sellAtPercent, ADRuns.DEFAULT_SELL_AT_PERCENT), false)
self.storeAboveOption:setState(SmartPickupFrame.getStepIndex(self.storeAboveSteps, run.storeAbovePercent, ADRuns.DEFAULT_STORE_ABOVE_PERCENT), false)
self.collectRoundRowOption = self:addToggleRow("collectRound", self.i18n:getText("spu_collectRound"), {self.i18n:getText("ui_no"), self.i18n:getText("ui_yes")}, run.collectRound == true)
self:addTextRow(self.i18n:getText("spu_section_store"))
self:fillBuildingRows(adEnv, run, "store")
self:fillOutboundStatusRows(adEnv, run)
else
self:addTextRow(self.i18n:getText("spu_section_to"))
@@ -193,6 +198,12 @@ function SmartPickupFrame:onClickBuilding(state, element)
if run == nil or data == nil then return end
if data.kind == "sourceBuilding" then
ADRunsController.store = ADRuns.toggleSourceBuilding(ADRunsController.store, run.id, data.buildingId)
elseif data.kind == "storeBuilding" then
ADRunsController.store = ADRuns.toggleStoreBuilding(ADRunsController.store, run.id, data.buildingId)
local updatedStore = ADRuns.getRun(ADRunsController.store, run.id)
Logging.info(SmartPickupFrame.LOG_PREFIX .. "run '%s': storeBuildings=%s", tostring(run.name), table.concat(updatedStore.storeBuildingIds or {}, " "))
frame:requestRebuild()
return
else
ADRunsController.store = ADRuns.toggleTargetBuilding(ADRunsController.store, run.id, data.buildingId)
end
+1 -1
View File
@@ -678,7 +678,7 @@ function SmartPickupFrame:onClickIngredient(state, element)
return self:onClickCollectRound(state)
elseif data ~= nil and data.kind == "direction" then
return self:onClickDirection(state)
elseif data ~= nil and (data.kind == "sourceBuilding" or data.kind == "targetBuilding") then
elseif data ~= nil and (data.kind == "sourceBuilding" or data.kind == "targetBuilding" or data.kind == "storeBuilding") then
return self:onClickBuilding(state, element)
end
self:safe(function(frame)
+1
View File
@@ -134,6 +134,7 @@
<text name="spu_stat_use" text="forbrug %s/døgn"/>
<text name="spu_stat_prod" text="prod. %s/døgn"/>
<text name="spu_stat_total" text="Bygningen i alt"/>
<text name="spu_section_store" text="— Lager til (valgfrit; intet valgt = moden vælger selv) —"/>
<text name="spu_stat_rising" text="stiger"/>
<text name="spu_tab_factories" text="Fabrikker"/>
<text name="spu_tab_silos" text="Siloer"/>
+1
View File
@@ -134,6 +134,7 @@
<text name="spu_stat_use" text="use %s/day"/>
<text name="spu_stat_prod" text="prod. %s/day"/>
<text name="spu_stat_total" text="Building total"/>
<text name="spu_section_store" text="— Store to (optional; none chosen = the mod picks) —"/>
<text name="spu_stat_rising" text="rising"/>
<text name="spu_tab_factories" text="Factories"/>
<text name="spu_tab_silos" text="Silos"/>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107">
<author>masterdraco</author>
<version>1.17.7.0</version>
<version>1.18.0.0</version>
<title>
<en>AD Smart Pickup</en>
</title>