From 954dd25c510882cf5c676d34acb8889400fa5f73 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Sat, 26 Sep 2026 21:37:20 +0200 Subject: [PATCH] =?UTF-8?q?feat(udk=C3=B8rsel):=20'S=C3=A6lg=20altid=20n?= =?UTF-8?q?=C3=A5r=20kilden=20er=20over=20X=20%'=20(s=C3=A6lger=20uanset?= =?UTF-8?q?=20pris,=20bedste=20salgssted=20der=20kan=20n=C3=A5s);=20fix:?= =?UTF-8?q?=20flydende=20varer=20lagres=20ikke=20automatisk=20i=20en=20fab?= =?UTF-8?q?riks=20rist=20(gyllevogn=20holdt=20ved=20AIO)=20(v1.33.0.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adFlowGuide.lua | 17 ++++++++++++++--- FS25_ADSmartPickup/adLoadSwap.lua | 13 +++++++++++++ FS25_ADSmartPickup/adOutbound.lua | 11 ++++++++++- FS25_ADSmartPickup/adOutboundPlanner.lua | 4 +++- FS25_ADSmartPickup/adRuns.lua | 8 ++++++++ FS25_ADSmartPickup/adRunsStorage.lua | 2 ++ FS25_ADSmartPickup/l10n/l10n_da.xml | 3 +++ FS25_ADSmartPickup/l10n/l10n_en.xml | 3 +++ FS25_ADSmartPickup/modDesc.xml | 2 +- tests/test_adLoadSwap.lua | 6 ++++++ tests/test_adOutboundPlanner.lua | 11 +++++++++++ 11 files changed, 74 insertions(+), 6 deletions(-) diff --git a/FS25_ADSmartPickup/adFlowGuide.lua b/FS25_ADSmartPickup/adFlowGuide.lua index 1348d4e..6c6e354 100644 --- a/FS25_ADSmartPickup/adFlowGuide.lua +++ b/FS25_ADSmartPickup/adFlowGuide.lua @@ -65,7 +65,7 @@ end local RUN_FIELDS = { "id", "name", "kind", "sourceBuildingIds", "targetBuildingIds", "storeBuildingIds", "products", - "ingredientMode", "ingredients", "sellAtPercent", "storeAbovePercent", "collectRound", "loops", + "ingredientMode", "ingredients", "sellAtPercent", "storeAbovePercent", "sellAbovePercent", "collectRound", "loops", "returnBelowPercent", "waitPoolGroup", "vehicleIds", } @@ -79,6 +79,7 @@ local function draftFrom(run, isNew) draft.ingredientMode = draft.ingredientMode == "manual" and "manual" or "auto" draft.sellAtPercent = draft.sellAtPercent or ADRuns.DEFAULT_SELL_AT_PERCENT draft.storeAbovePercent = draft.storeAbovePercent or ADRuns.DEFAULT_STORE_ABOVE_PERCENT + draft.sellAbovePercent = draft.sellAbovePercent or ADRuns.DEFAULT_SELL_ABOVE_PERCENT draft.returnBelowPercent = draft.returnBelowPercent or ADRuns.DEFAULT_RETURN_BELOW_PERCENT draft.loops = draft.loops or 0 draft.waitPoolGroup = draft.waitPoolGroup or "" @@ -126,7 +127,7 @@ end -- ---- valg ---- local SET_VALUES = { - kind = true, ingredientMode = true, sellAtPercent = true, storeAbovePercent = true, loops = true, + kind = true, ingredientMode = true, sellAtPercent = true, storeAbovePercent = true, sellAbovePercent = true, loops = true, returnBelowPercent = true, waitPoolGroup = true, } local BOOL_VALUES = {showAllBuildings = true, storeAuto = true, collectRound = true} @@ -185,6 +186,13 @@ local function percentOptions(from, to, step) return options end +-- "Sælg altid over": Fra, 50-100 % +local function sellAboveOptions() + local options = {{label = L("spu_sellAbove_off"), value = 0}} + for _, option in ipairs(percentOptions(50, ADRuns.MAX_SELL_ABOVE_PERCENT, 5)) do table.insert(options, option) end + return options +end + local function chosenSet(list) local set = {} for _, value in ipairs(list or {}) do set[value] = true end @@ -272,6 +280,7 @@ local function rowsRules(guide) return { choice("sellAtPercent", L("spu_sellAt"), percentOptions(ADRuns.MIN_SELL_AT_PERCENT, ADRuns.MAX_SELL_AT_PERCENT, 5), draft.sellAtPercent), choice("storeAbovePercent", L("spu_storeAbove"), percentOptions(ADRuns.MIN_STORE_ABOVE_PERCENT, ADRuns.MAX_STORE_ABOVE_PERCENT, 5), draft.storeAbovePercent), + choice("sellAbovePercent", L("spu_sellAbove"), sellAboveOptions(), draft.sellAbovePercent), toggle("collectRound", L("spu_collectRound"), draft.collectRound), } end @@ -388,6 +397,7 @@ local SUMMARY = { local draft = guide.draft if isOut(guide) then local parts = {L("spu_guide_flow_sumSellAt", {draft.sellAtPercent}), L("spu_guide_flow_sumStoreAbove", {draft.storeAbovePercent})} + if (draft.sellAbovePercent or 0) > 0 then table.insert(parts, L("spu_guide_flow_sumSellAbove", {draft.sellAbovePercent})) end if draft.collectRound then table.insert(parts, L("spu_guide_flow_sumRound")) end return parts end @@ -429,7 +439,8 @@ function ADFlowGuide.result(guide) targetBuildingIds = ADGuide.deepCopy(draft.targetBuildingIds), storeBuildingIds = storeIds, products = ADGuide.deepCopy(draft.products), ingredientMode = draft.ingredientMode, ingredients = ADGuide.deepCopy(draft.ingredients), sellAtPercent = draft.sellAtPercent, - storeAbovePercent = draft.storeAbovePercent, collectRound = draft.collectRound, loops = draft.loops, + storeAbovePercent = draft.storeAbovePercent, sellAbovePercent = draft.sellAbovePercent or 0, + collectRound = draft.collectRound, loops = draft.loops, returnBelowPercent = draft.returnBelowPercent, waitPoolGroup = draft.waitPoolGroup or "", } if draft.name ~= nil and draft.name:match("%S") then changes.name = draft.name end diff --git a/FS25_ADSmartPickup/adLoadSwap.lua b/FS25_ADSmartPickup/adLoadSwap.lua index f8f6799..9f7394d 100644 --- a/FS25_ADSmartPickup/adLoadSwap.lua +++ b/FS25_ADSmartPickup/adLoadSwap.lua @@ -97,6 +97,19 @@ function ADLoadSwap.rankDumpCandidates(candidates, avoidMarkers) return ranked end +-- Flydende varer lægges kun i tanke/siloer, aldrig automatisk i en fabriks aflæsning: fabrikkerne har ofte en +-- rist med præcis aflæsning, som en gyllevogns udløb bag på ikke rammer (in-game 26/9: 1 mio. l gylle holdt +-- ved AIO'ens rist og spærrede vejen). Vælger brugeren selv fabrikken som lager, gælder valget. +ADLoadSwap.LIQUIDS = {LIQUIDMANURE = true, DIGESTATE = true, MILK = true, BUFFALOMILK = true, GOATMILK = true, + SHEEPMILK = true, WATER = true, LIQUIDFERTILIZER = true, HERBICIDE = true, METHANE = true, DIESEL = true} +function ADLoadSwap.isLiquidName(name) + return name ~= nil and ADLoadSwap.LIQUIDS[string.upper(tostring(name))] == true +end +function ADLoadSwap.isLiquid(fillType) + local name = g_fillTypeManager ~= nil and g_fillTypeManager.getFillTypeNameByIndex ~= nil and g_fillTypeManager:getFillTypeNameByIndex(fillType) or nil + return ADLoadSwap.isLiquidName(name) +end + function ADLoadSwap.findDumpMarker(adEnv, vehicle, fillType, rest, homeWayPointId, isReachable, excludedPlaceables, avoidMarkers, allowFactories) if vehicle.rootNode == nil then return nil, "køretøjet har ingen position" diff --git a/FS25_ADSmartPickup/adOutbound.lua b/FS25_ADSmartPickup/adOutbound.lua index bb3d72a..15cf4b6 100644 --- a/FS25_ADSmartPickup/adOutbound.lua +++ b/FS25_ADSmartPickup/adOutbound.lua @@ -186,6 +186,12 @@ local function findSellQuote(adEnv, vehicle, product, sellMarkers, sellAtPercent if unreachableName ~= nil then return nil, unreachableName end + -- ingen pris over grænsen: det bedste salgssted der kan nås (bruges af "sælg altid over X %") + for _, quote in ipairs(quotes) do + if homeId == nil or ADWaitPool.hasRouteBothWays(adEnv, vehicle, quote.wayPointId, homeId) then + return quote, nil + end + end return quotes[1], nil end @@ -251,7 +257,9 @@ local function findStoreMarker(adEnv, vehicle, product, capacity, sources, liter 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) + -- flydende varer (gylle, digestat, mælk ...) kun i tanke/siloer — ikke automatisk i en fabriks rist + local allowFactories = not (ADLoadSwap.isLiquid ~= nil and ADLoadSwap.isLiquid(product.fillType)) + local marker = ADLoadSwap.findDumpMarker(adEnv, vehicle, product.fillType, liters, nil, isReachable, ADSources.getSourcePlaceables(sources), avoid, allowFactories) if marker == nil then return nil end @@ -356,6 +364,7 @@ function ADOutbound.buildPlanInput(adEnv, run, vehicle) hasAutoload = hasAutoload, sellAtPercent = run.sellAtPercent, storeAbovePercent = run.storeAbovePercent, + sellAbovePercent = run.sellAbovePercent, collectRound = run.collectRound == true, products = products, } diff --git a/FS25_ADSmartPickup/adOutboundPlanner.lua b/FS25_ADSmartPickup/adOutboundPlanner.lua index 8b5b744..1a5c18e 100644 --- a/FS25_ADSmartPickup/adOutboundPlanner.lua +++ b/FS25_ADSmartPickup/adOutboundPlanner.lua @@ -68,6 +68,8 @@ function ADOutboundPlanner.evaluate(product, input) storageHigh = storagePercent >= (input.storeAbovePercent or 80), percent = percent, priceOk = percent ~= nil and percent >= (input.sellAtPercent or 90), + -- "sælg altid over X %": kilden er så fuld, at der sælges uanset pris + overflow = (input.sellAbovePercent or 0) > 0 and storagePercent >= input.sellAbovePercent, } end @@ -123,7 +125,7 @@ local function collectCandidates(input) local e = ADOutboundPlanner.evaluate(product, input) local added = false if e.available >= ADOutboundPlanner.MIN_AVAILABLE_LITERS then - if wantsSell(product.policy) and e.priceOk and product.quote ~= nil and canSellWithoutReserve(e, input) then + if wantsSell(product.policy) and (e.priceOk or e.overflow) and product.quote ~= nil and canSellWithoutReserve(e, input) then table.insert(sell, {product = product, e = e, kind = "sell", targetWayPointId = product.quote.wayPointId, targetName = product.quote.stationName, percent = e.percent}) added = true elseif wantsStore(product.policy) and (e.fullLoad or e.storageHigh or (input.feedMode and e.available >= ADOutboundPlanner.FEED_MIN_LITERS)) and product.store ~= nil then diff --git a/FS25_ADSmartPickup/adRuns.lua b/FS25_ADSmartPickup/adRuns.lua index 0007b24..752ff9c 100644 --- a/FS25_ADSmartPickup/adRuns.lua +++ b/FS25_ADSmartPickup/adRuns.lua @@ -17,6 +17,9 @@ ADRuns.KINDS = {supply = true, outbound = true} ADRuns.POLICIES = {store = true, sell = true, sellElseStore = true} ADRuns.DEFAULT_SELL_AT_PERCENT, ADRuns.MIN_SELL_AT_PERCENT, ADRuns.MAX_SELL_AT_PERCENT = 90, 50, 100 ADRuns.DEFAULT_STORE_ABOVE_PERCENT, ADRuns.MIN_STORE_ABOVE_PERCENT, ADRuns.MAX_STORE_ABOVE_PERCENT = 80, 50, 95 +-- "Sælg altid over X %": kildens lager over grænsen sælges uanset pris (0 = fra). Så hober f.eks. gylle +-- ved BGA'en sig ikke op, når prisen er lav. +ADRuns.DEFAULT_SELL_ABOVE_PERCENT, ADRuns.MIN_SELL_ABOVE_PERCENT, ADRuns.MAX_SELL_ABOVE_PERCENT = 0, 0, 100 local function copyList(list) local copy = {} @@ -51,6 +54,7 @@ local function copyRun(run) products = copyProducts(run.products), sellAtPercent = run.sellAtPercent, storeAbovePercent = run.storeAbovePercent, + sellAbovePercent = run.sellAbovePercent, collectRound = run.collectRound, targetWayPointId = run.targetWayPointId, targetWayPointIds = copyList(run.targetWayPointIds), @@ -98,6 +102,7 @@ function ADRuns.createRun(store, name, kind) products = {}, sellAtPercent = ADRuns.DEFAULT_SELL_AT_PERCENT, storeAbovePercent = ADRuns.DEFAULT_STORE_ABOVE_PERCENT, + sellAbovePercent = ADRuns.DEFAULT_SELL_ABOVE_PERCENT, collectRound = false, targetWayPointId = nil, targetWayPointIds = {}, @@ -164,6 +169,7 @@ function ADRuns.updateRun(store, runId, changes) 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 + if changes.sellAbovePercent ~= nil then run.sellAbovePercent = clampRange(changes.sellAbovePercent, ADRuns.DEFAULT_SELL_ABOVE_PERCENT, ADRuns.MIN_SELL_ABOVE_PERCENT, ADRuns.MAX_SELL_ABOVE_PERCENT) end if changes.collectRound ~= nil then run.collectRound = changes.collectRound == true end return new end @@ -364,6 +370,7 @@ function ADRuns.toRecords(store) products = ADRuns.serializeProducts(run.products), sellAt = run.sellAtPercent, storeAbove = run.storeAbovePercent, + sellAbove = run.sellAbovePercent or 0, collectRound = run.collectRound and 1 or 0, } end @@ -407,6 +414,7 @@ function ADRuns.fromRecords(records) 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), + sellAbovePercent = clampRange(record.sellAbove, ADRuns.DEFAULT_SELL_ABOVE_PERCENT, ADRuns.MIN_SELL_ABOVE_PERCENT, ADRuns.MAX_SELL_ABOVE_PERCENT), collectRound = tonumber(record.collectRound) == 1, targetWayPointId = targets[1], targetWayPointIds = targets, diff --git a/FS25_ADSmartPickup/adRunsStorage.lua b/FS25_ADSmartPickup/adRunsStorage.lua index 3242057..22b7523 100644 --- a/FS25_ADSmartPickup/adRunsStorage.lua +++ b/FS25_ADSmartPickup/adRunsStorage.lua @@ -46,6 +46,7 @@ function ADRunsStorage.writeRecords(xmlFile, records) 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) + xmlFile:setInt(key .. "#sellAbove", record.sellAbove or 0) xmlFile:setInt(key .. "#collectRound", record.collectRound or 0) for vehicleIndex, vehicleId in ipairs(record.vehicleIds) do xmlFile:setString(string.format("%s.vehicle(%d)#id", key, vehicleIndex - 1), vehicleId) @@ -74,6 +75,7 @@ function ADRunsStorage.readRecords(xmlFile) products = xmlFile:getString(key .. "#products"), sellAt = xmlFile:getInt(key .. "#sellAt"), storeAbove = xmlFile:getInt(key .. "#storeAbove"), + sellAbove = xmlFile:getInt(key .. "#sellAbove"), collectRound = xmlFile:getInt(key .. "#collectRound"), vehicleIds = {}, } diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml index 184d7ee..2a7e67c 100644 --- a/FS25_ADSmartPickup/l10n/l10n_da.xml +++ b/FS25_ADSmartPickup/l10n/l10n_da.xml @@ -240,6 +240,8 @@ + + @@ -434,6 +436,7 @@ + diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml index efe959d..d8018dc 100644 --- a/FS25_ADSmartPickup/l10n/l10n_en.xml +++ b/FS25_ADSmartPickup/l10n/l10n_en.xml @@ -240,6 +240,8 @@ + + @@ -434,6 +436,7 @@ + diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 3db94ea..18e998d 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.32.5.0 + 1.33.0.0 <en>AD Smart Pickup</en> diff --git a/tests/test_adLoadSwap.lua b/tests/test_adLoadSwap.lua index ee25b0f..13f813c 100644 --- a/tests/test_adLoadSwap.lua +++ b/tests/test_adLoadSwap.lua @@ -69,5 +69,11 @@ do ADSmartPickup = nil end +-- LQ: flydende varer lægges ikke automatisk i en fabrik (AIO-risten, in-game 26/9) +check("LQ gylle er flydende", ADLoadSwap.isLiquidName("LIQUIDMANURE"), true) +check("LQ digestat er flydende", ADLoadSwap.isLiquidName("digestate"), true) +check("LQ hvede er ikke flydende", ADLoadSwap.isLiquidName("WHEAT"), false) +check("LQ ukendt", ADLoadSwap.isLiquidName(nil), false) + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1) diff --git a/tests/test_adOutboundPlanner.lua b/tests/test_adOutboundPlanner.lua index 60a9779..fa4dbf7 100644 --- a/tests/test_adOutboundPlanner.lua +++ b/tests/test_adOutboundPlanner.lua @@ -173,5 +173,16 @@ blocked.storeReason = "Bale & Pallet Storage: ingen vej til 'Chicken Storage Unl local _, reasons17 = P.plan(input({blocked})) check("P17 præcis grund i status", reasons17[1], "EGG: Bale & Pallet Storage: ingen vej til 'Chicken Storage Unload'") +-- SA: "sælg altid over X %" — kilden over grænsen sælges selvom prisen er lav (gylle ved BGA'en, 26/9) +local lowQuote = {percent = 40, price = 0.5, top = 1.25, wayPointId = 77, stationName = "Liquid Buy"} +local fullSlurry = {{wayPointId = 5, available = 900000, level = 900000, capacity = 1000000}} +local lowSlurry = {{wayPointId = 5, available = 300000, level = 300000, capacity = 1000000}} +local saOff = P.plan(input({product("LIQUIDMANURE", 40, "sell", fullSlurry, lowQuote, nil)})) +check("SA fra: lav pris -> ikke salg", saOff ~= nil and saOff.kind or "intet", "intet") +local saOn = P.plan(input({product("LIQUIDMANURE", 40, "sell", fullSlurry, lowQuote, nil)}, {sellAbovePercent = 80})) +check("SA 90 % fyldt over 80 %: sælg trods lav pris", saOn ~= nil and saOn.kind, "sell") +local saLow = P.plan(input({product("LIQUIDMANURE", 40, "sell", lowSlurry, lowQuote, nil)}, {sellAbovePercent = 80})) +check("SA 30 % fyldt: ikke salg", saLow ~= nil and saLow.kind or "intet", "intet") + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)