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) return nil, string.format("%d silo(er) tager varen, men AutoDrive finder ingen rute fra traktoren til dem", withSpace)
end 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. -- 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). -- true = traktoren er sendt til en silo (Wait-punktet er frigivet).
function ADLoadSwap.tryFromWait(adEnv, task, wait, dt, redirect, release, isReachable) 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 return quotes[1], nil
end end
local function findStoreMarker(adEnv, vehicle, product, capacity, sources, litersOverride) local function findStoreMarker(adEnv, vehicle, product, capacity, sources, litersOverride, run)
local isReachable = function(markerId) local isReachable = function(markerId)
return ADWaitPool.hasRouteTo(adEnv, vehicle, markerId) return ADWaitPool.hasRouteTo(adEnv, vehicle, markerId)
end end
@@ -200,6 +200,16 @@ local function findStoreMarker(adEnv, vehicle, product, capacity, sources, liter
end 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))
-- "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 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) local marker = ADLoadSwap.findDumpMarker(adEnv, vehicle, product.fillType, liters, nil, isReachable, ADSources.getSourcePlaceables(sources), avoid, true)
if marker == nil then if marker == nil then
@@ -293,7 +303,7 @@ function ADOutbound.buildPlanInput(adEnv, run, vehicle)
end end
end end
if wantsStore(product.policy) then 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
end end
local input = { local input = {
@@ -331,7 +341,7 @@ function ADOutbound.planCargoTarget(adEnv, run, vehicle, fillType, liters)
end end
end end
if wantsStore(policy) then 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 if store ~= nil then
return store.wayPointId return store.wayPointId
end end
+12
View File
@@ -47,6 +47,7 @@ local function copyRun(run)
sourceWayPointIds = copyList(run.sourceWayPointIds), sourceWayPointIds = copyList(run.sourceWayPointIds),
sourceBuildingIds = copyList(run.sourceBuildingIds), sourceBuildingIds = copyList(run.sourceBuildingIds),
targetBuildingIds = copyList(run.targetBuildingIds), targetBuildingIds = copyList(run.targetBuildingIds),
storeBuildingIds = copyList(run.storeBuildingIds),
products = copyProducts(run.products), products = copyProducts(run.products),
sellAtPercent = run.sellAtPercent, sellAtPercent = run.sellAtPercent,
storeAbovePercent = run.storeAbovePercent, storeAbovePercent = run.storeAbovePercent,
@@ -93,6 +94,7 @@ function ADRuns.createRun(store, name, kind)
sourceWayPointIds = {}, sourceWayPointIds = {},
sourceBuildingIds = {}, sourceBuildingIds = {},
targetBuildingIds = {}, targetBuildingIds = {},
storeBuildingIds = {},
products = {}, products = {},
sellAtPercent = ADRuns.DEFAULT_SELL_AT_PERCENT, sellAtPercent = ADRuns.DEFAULT_SELL_AT_PERCENT,
storeAbovePercent = ADRuns.DEFAULT_STORE_ABOVE_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.sourceWayPointIds ~= nil then run.sourceWayPointIds = copyList(changes.sourceWayPointIds) end
if changes.sourceBuildingIds ~= nil then run.sourceBuildingIds = copyList(changes.sourceBuildingIds) 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.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.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.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 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)}) return ADRuns.updateRun(store, runId, {sourceBuildingIds = toggleInList(run.sourceBuildingIds, buildingId)})
end 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) function ADRuns.toggleTargetBuilding(store, runId, buildingId)
local run = ADRuns.getRun(store, runId) local run = ADRuns.getRun(store, runId)
if run == nil then return copyStore(store) end if run == nil then return copyStore(store) end
@@ -350,6 +360,7 @@ function ADRuns.toRecords(store)
sources = table.concat(run.sourceWayPointIds or {}, " "), sources = table.concat(run.sourceWayPointIds or {}, " "),
sourceBuildings = table.concat(run.sourceBuildingIds or {}, " "), sourceBuildings = table.concat(run.sourceBuildingIds or {}, " "),
targetBuildings = table.concat(run.targetBuildingIds or {}, " "), targetBuildings = table.concat(run.targetBuildingIds or {}, " "),
storeBuildings = table.concat(run.storeBuildingIds or {}, " "),
products = ADRuns.serializeProducts(run.products), products = ADRuns.serializeProducts(run.products),
sellAt = run.sellAtPercent, sellAt = run.sellAtPercent,
storeAbove = run.storeAbovePercent, storeAbove = run.storeAbovePercent,
@@ -392,6 +403,7 @@ function ADRuns.fromRecords(records)
sourceWayPointIds = sources, sourceWayPointIds = sources,
sourceBuildingIds = parseIds(record.sourceBuildings), sourceBuildingIds = parseIds(record.sourceBuildings),
targetBuildingIds = parseIds(record.targetBuildings), targetBuildingIds = parseIds(record.targetBuildings),
storeBuildingIds = parseIds(record.storeBuildings),
products = ADRuns.parseProducts(record.products), products = ADRuns.parseProducts(record.products),
sellAtPercent = clampRange(record.sellAt, ADRuns.DEFAULT_SELL_AT_PERCENT, ADRuns.MIN_SELL_AT_PERCENT, ADRuns.MAX_SELL_AT_PERCENT), 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), 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 return result
end 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 ---- -- ---- køretøjsnavne ----
ADRunsController.NAME_INTERVAL_MS = 2000 ADRunsController.NAME_INTERVAL_MS = 2000
+2
View File
@@ -42,6 +42,7 @@ function ADRunsStorage.writeRecords(xmlFile, records)
-- v3: bygninger (flow-tavlen) -- v3: bygninger (flow-tavlen)
xmlFile:setString(key .. "#sourceBuildings", record.sourceBuildings or "") xmlFile:setString(key .. "#sourceBuildings", record.sourceBuildings or "")
xmlFile:setString(key .. "#targetBuildings", record.targetBuildings or "") xmlFile:setString(key .. "#targetBuildings", record.targetBuildings or "")
xmlFile:setString(key .. "#storeBuildings", record.storeBuildings or "")
xmlFile:setString(key .. "#products", record.products or "") xmlFile:setString(key .. "#products", record.products or "")
xmlFile:setInt(key .. "#sellAt", record.sellAt or ADRuns.DEFAULT_SELL_AT_PERCENT) xmlFile:setInt(key .. "#sellAt", record.sellAt or ADRuns.DEFAULT_SELL_AT_PERCENT)
xmlFile:setInt(key .. "#storeAbove", record.storeAbove or ADRuns.DEFAULT_STORE_ABOVE_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"), sources = xmlFile:getString(key .. "#sources"),
sourceBuildings = xmlFile:getString(key .. "#sourceBuildings"), sourceBuildings = xmlFile:getString(key .. "#sourceBuildings"),
targetBuildings = xmlFile:getString(key .. "#targetBuildings"), targetBuildings = xmlFile:getString(key .. "#targetBuildings"),
storeBuildings = xmlFile:getString(key .. "#storeBuildings"),
products = xmlFile:getString(key .. "#products"), products = xmlFile:getString(key .. "#products"),
sellAt = xmlFile:getInt(key .. "#sellAt"), sellAt = xmlFile:getInt(key .. "#sellAt"),
storeAbove = xmlFile:getInt(key .. "#storeAbove"), storeAbove = xmlFile:getInt(key .. "#storeAbove"),
+15 -4
View File
@@ -122,22 +122,25 @@ function SmartPickupFrame:fillBuildingRows(adEnv, run, role)
return return
end end
local chosen = {} 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 for _, id in ipairs(ids or {}) do chosen[id] = true end
local count = 0 local count = 0
for _, building in ipairs(ADBuildings.list(adEnv, g_currentMission:getFarmId())) do 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) 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 if fits then
count = count + 1 count = count + 1
local row = self:addRow(self.ingredientPrefab, {kind = role == "load" and "sourceBuilding" or "targetBuilding", buildingId = building.id}) local row = self:addRow(self.ingredientPrefab, {kind = rowKind, buildingId = building.id})
row:getDescendantByName("title"):setText(self:describeBuilding(building, role)) 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) row:getDescendantByName("option"):setState(chosen[building.id] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end end
end end
-- valgte bygninger der ikke længere findes (solgt) -- valgte bygninger der ikke længere findes (solgt)
for _, id in ipairs(ids or {}) do for _, id in ipairs(ids or {}) do
if ADBuildings.find(adEnv, g_currentMission:getFarmId(), id) == nil then 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("title"):setText(string.format(" %s — %s", tostring(id), self.i18n:getText("spu_buildingGone")))
row:getDescendantByName("option"):setState(SmartPickupFrame.STATE_ON, false) row:getDescendantByName("option"):setState(SmartPickupFrame.STATE_ON, false)
end 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.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.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.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) self:fillOutboundStatusRows(adEnv, run)
else else
self:addTextRow(self.i18n:getText("spu_section_to")) 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 run == nil or data == nil then return end
if data.kind == "sourceBuilding" then if data.kind == "sourceBuilding" then
ADRunsController.store = ADRuns.toggleSourceBuilding(ADRunsController.store, run.id, data.buildingId) 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 else
ADRunsController.store = ADRuns.toggleTargetBuilding(ADRunsController.store, run.id, data.buildingId) ADRunsController.store = ADRuns.toggleTargetBuilding(ADRunsController.store, run.id, data.buildingId)
end end
+1 -1
View File
@@ -678,7 +678,7 @@ function SmartPickupFrame:onClickIngredient(state, element)
return self:onClickCollectRound(state) return self:onClickCollectRound(state)
elseif data ~= nil and data.kind == "direction" then elseif data ~= nil and data.kind == "direction" then
return self:onClickDirection(state) 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) return self:onClickBuilding(state, element)
end end
self:safe(function(frame) 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_use" text="forbrug %s/døgn"/>
<text name="spu_stat_prod" text="prod. %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_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_stat_rising" text="stiger"/>
<text name="spu_tab_factories" text="Fabrikker"/> <text name="spu_tab_factories" text="Fabrikker"/>
<text name="spu_tab_silos" text="Siloer"/> <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_use" text="use %s/day"/>
<text name="spu_stat_prod" text="prod. %s/day"/> <text name="spu_stat_prod" text="prod. %s/day"/>
<text name="spu_stat_total" text="Building total"/> <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_stat_rising" text="rising"/>
<text name="spu_tab_factories" text="Factories"/> <text name="spu_tab_factories" text="Factories"/>
<text name="spu_tab_silos" text="Silos"/> <text name="spu_tab_silos" text="Silos"/>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107"> <modDesc descVersion="107">
<author>masterdraco</author> <author>masterdraco</author>
<version>1.17.7.0</version> <version>1.18.0.0</version>
<title> <title>
<en>AD Smart Pickup</en> <en>AD Smart Pickup</en>
</title> </title>
+10 -1
View File
@@ -1,6 +1,6 @@
# AD Smart Pickup — brugermanual # AD Smart Pickup — brugermanual
Version 1.17.7 · Farming Simulator 25 · tillæg til AutoDrive Version 1.18.0 · Farming Simulator 25 · tillæg til AutoDrive
*English version: [USER_MANUAL.md](USER_MANUAL.md)* *English version: [USER_MANUAL.md](USER_MANUAL.md)*
@@ -298,6 +298,15 @@ Bulkvarer kræver en tipvogn eller tank. Pallevarer kræver en vogn med Universa
| **Kør til lager over** | Lager-varer køres væk, når kilden er fyldt over denne andel | 50–95 %, standard 80 % | | **Kør til lager over** | Lager-varer køres væk, når kilden er fyldt over denne andel | 50–95 %, standard 80 % |
| **Saml op på én tur** | Opsamlingsrunde, se nedenfor | Ja/Nej | | **Saml op på én tur** | Opsamlingsrunde, se nedenfor | Ja/Nej |
### Lager til: tvunget aflæsningssted
Under reglerne står afsnittet **Lager til** med gårdens siloer og egne fabrikker. Vælger du én eller
flere, kører lagerture kun dertil. Den første i listen, der tager varen, har plads og kan nås, får
læsset. Et eksempel er gylle, der kun må til biogasanlægget og aldrig til AIO-fabrikken. Kan ingen af
de valgte tage læsset, venter traktoren ved Wait, og loggen skriver hvorfor, fx *valgt lager: BGA500kw:
fuld*. Vælger du ingen, finder moden selv den nærmeste silo eller egne fabrik, der bruger varen.
Paller følger stadig reglen om nærmeste palleskur.
### Sådan vælges hver tur ### Sådan vælges hver tur
Før hver pålæsning, og hvert femte sekund mens traktoren holder, vurderer moden: Før hver pålæsning, og hvert femte sekund mens traktoren holder, vurderer moden:
+10 -1
View File
@@ -1,6 +1,6 @@
# AD Smart Pickup — user manual # AD Smart Pickup — user manual
Version 1.17.7 · Farming Simulator 25 · add-on for AutoDrive Version 1.18.0 · Farming Simulator 25 · add-on for AutoDrive
*Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)* *Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)*
@@ -302,6 +302,15 @@ Bulk goods need a tipper or tank. Pallet goods need a trailer with Universal Aut
| **Drive to storage above** | Store goods are moved when the source is filled above this share | 50–95 %, default 80 % | | **Drive to storage above** | Store goods are moved when the source is filled above this share | 50–95 %, default 80 % |
| **Collect in one trip** | Collection round, see below | Yes/No | | **Collect in one trip** | Collection round, see below | Yes/No |
### Store to: forced drop-off
Below the rules there is a **Store to** section listing the farm's silos and own factories. If you
pick one or more, storage trips only go there. The first one in the list that accepts the good, has
room and can be reached gets the load. An example is slurry that must only go to the biogas plant and
never to the AIO factory. If none of the chosen buildings can take the load, the tractor waits at
Wait, and the log says why, such as *valgt lager: BGA500kw: fuld*. If you pick none, the mod finds the
nearest silo or own factory that uses the good by itself. Pallets still follow the nearest pallet shed rule.
### How each trip is chosen ### How each trip is chosen
Before each loading, and every five seconds while the tractor waits, the mod checks: Before each loading, and every five seconds while the tractor waits, the mod checks:
+25
View File
@@ -44,5 +44,30 @@ check("LS2 fremmed anlæg aldrig", L.isStoreTarget({owningPlaceable = foreignBio
check("LS2 hjemmet selv aldrig", L.isStoreTarget({owningPlaceable = biogas}, SLURRY, {owningPlaceable = biogas}, true), false) check("LS2 hjemmet selv aldrig", L.isStoreTarget({owningPlaceable = biogas}, SLURRY, {owningPlaceable = biogas}, true), false)
g_currentMission = nil g_currentMission = nil
-- LS3: tvunget lagermål — kun de valgte markører, i rækkefølge; første der tager varen, har plads og kan nås
do
local stations = {
[101] = {accepts = {[SLURRY] = true}, free = 0}, -- BGA fuld
[102] = {accepts = {[SLURRY] = true}, free = 900000}, -- gylletank
[103] = {accepts = {[MILK] = true}, free = 900000}, -- tager ikke gylle
}
for _, st in pairs(stations) do
st.getIsFillTypeSupported = function(self, ft) return self.accepts[ft] == true end
st.getFreeCapacity = function(self) return self.free end
end
ADSmartPickup = {getFarmId = function() return 1 end, getUnloadStationAtWayPointId = function(_, id) return stations[id] end}
local adEnv = {ADGraphManager = {getMapMarkerByWayPointId = function(_, id) return {id = id, name = "m" .. id} end}}
local vehicle = {}
local all = function() return true end
check("LS3 fuld BGA springes over -> tank", L.findForcedDumpMarker(adEnv, vehicle, SLURRY, 20000, {101, 102}, all).id, 102)
stations[101].free = 500000
check("LS3 BGA med plads vælges først", L.findForcedDumpMarker(adEnv, vehicle, SLURRY, 20000, {101, 102}, all).id, 101)
local none, why = L.findForcedDumpMarker(adEnv, vehicle, SLURRY, 20000, {103}, all)
check("LS3 intet valgt mål tager varen -> nil", none, nil)
check("LS3 forklaring", why ~= nil and why:find("valgt", 1, true) ~= nil, true)
check("LS3 ikke nåelig -> næste", L.findForcedDumpMarker(adEnv, vehicle, SLURRY, 20000, {101, 102}, function(id) return id ~= 101 end).id, 102)
ADSmartPickup = nil
end
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1) os.exit(failures == 0 and 0 or 1)
+17
View File
@@ -582,5 +582,22 @@ do
sources[31], sources[32], products[31], products[32] = nil, nil, nil, nil sources[31], sources[32], products[31], products[32] = nil, nil, nil, nil
end end
-- O-forced: "Lager til" valgt -> lagerturen går til det valgte mål, ikke moden egen silo-valg
do
sources[41] = {kind = "silo", wayPointId = 41, station = {name = "gylleSilo"}, name = "Gyllesilo"}
products[41] = {{fillType = SUGAR, name = "SUGAR", title = "Sukker", available = 11000000, level = 11000000, capacity = 12000000, isPallet = false}}
local vF = newVehicle("Gyllevogn", 0, 0, 20000)
runs[vF] = outboundRun(79, {41}, {{fillType = "SUGAR", policy = "store"}})
runs[vF].forcedMarkers = {31}
ADRunsController.getStoreMarkers = function(_, run) return run.forcedMarkers or {} end
local forcedCalls = 0
ADLoadSwap.findForcedDumpMarker = function(_, _, _, _, ids) forcedCalls = forcedCalls + 1 return {id = ids[1], name = "BGA", markerIndex = 99} end
dumpMarker = mStore
ADOutbound.choosePickup(adEnv, vF)
check("O-forced valgt mål brugt", forcedCalls > 0, true)
check("O-forced tur-mål = BGA", ADOutbound.getTripFor(vF) and ADOutbound.getTripFor(vF).targetWayPointId, 31)
ADRunsController.getStoreMarkers, ADLoadSwap.findForcedDumpMarker = nil, nil
end
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1) os.exit(failures == 0 and 0 or 1)
+15
View File
@@ -307,5 +307,20 @@ do
check("RN ukendt køretøj", names.v9, nil) check("RN ukendt køretøj", names.v9, nil)
end end
-- RS: tvunget lagermål — storeBuildingIds (toggle, kopi, records roundtrip)
do
local store, run = ADRuns.createRun(ADRuns.newStore(), "Slurry", "outbound")
check("RS tom som standard", #run.storeBuildingIds, 0)
store = ADRuns.toggleStoreBuilding(store, run.id, "BGA500kw")
store = ADRuns.toggleStoreBuilding(store, run.id, "tank2")
check("RS to valgt", table.concat(ADRuns.getRun(store, run.id).storeBuildingIds, ","), "BGA500kw,tank2")
store = ADRuns.toggleStoreBuilding(store, run.id, "tank2")
check("RS fravalgt igen", table.concat(ADRuns.getRun(store, run.id).storeBuildingIds, ","), "BGA500kw")
local records = ADRuns.toRecords(store)
check("RS record", records[1].storeBuildings, "BGA500kw")
local back = ADRuns.fromRecords(records)
check("RS roundtrip", table.concat(ADRuns.getRun(back, run.id).storeBuildingIds, ","), "BGA500kw")
end
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1) os.exit(failures == 0 and 0 or 1)
+8
View File
@@ -700,6 +700,14 @@ local rowIn = ADRunsController.getFlowRow(adEnv, ADRuns.getRun(ADRunsController.
check("CF5 forsyning fra = alle siloer", rowIn.fromAllSilos, true) check("CF5 forsyning fra = alle siloer", rowIn.fromAllSilos, true)
check("CF5 forsyning til", table.concat(rowIn.toNames, ","), "Kostald 1,Sukkerfabrik") check("CF5 forsyning til", table.concat(rowIn.toNames, ","), "Kostald 1,Sukkerfabrik")
check("CF5 forsyning auto-varer", rowIn.productAuto, true) check("CF5 forsyning auto-varer", rowIn.productAuto, true)
-- SB: "Lager til" — valgte bygninger -> deres aflæsningsmarkører (i valgt rækkefølge; ukendte udelades)
do
local sbRun = ADRunsController.createRun("Gylle", "outbound")
ADRunsController.updateRun(sbRun.id, {storeBuildingIds = {"mill", "findes-ikke", "barn1"}})
check("SB markører", table.concat(ADRunsController.getStoreMarkers(adEnv, ADRuns.getRun(ADRunsController.store, sbRun.id)), ","), "77,94")
ADRunsController.updateRun(sbRun.id, {storeBuildingIds = {}})
check("SB tom = ingen tvang", #ADRunsController.getStoreMarkers(adEnv, ADRuns.getRun(ADRunsController.store, sbRun.id)), 0)
end
ADBuildings = nil; ADSources = nil; ADOutbound = nil ADBuildings = nil; ADSources = nil; ADOutbound = nil
-- RC-navne: køretøjer får AD-førernavn efter forløbet; ude af forløb -> modelnavnet igen -- RC-navne: køretøjer får AD-førernavn efter forløbet; ude af forløb -> modelnavnet igen