feat(udkørsel): 'Sælg altid når kilden er over X %' (sælger uanset pris, bedste salgssted der kan nås); fix: flydende varer lagres ikke automatisk i en fabriks rist (gyllevogn holdt ved AIO) (v1.33.0.0)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {},
|
||||
}
|
||||
|
||||
@@ -240,6 +240,8 @@
|
||||
<text name="spu_sellAt" text="Sælg fra (% af årets toppris)"/>
|
||||
<text name="spu_sellAt_tooltip" text="Der køres til salg når salgsstedets pris er mindst denne andel af varens toppris i årets prisforløb — uanset mængde. Stor efterspørgsel tæller altid."/>
|
||||
<text name="spu_storeAbove" text="Kør til lager over"/>
|
||||
<text name="spu_sellAbove" text="Sælg altid når kilden er over"/>
|
||||
<text name="spu_sellAbove_off" text="Fra"/>
|
||||
<text name="spu_storeAbove_tooltip" text="Varer med Lager-politik køres til silo når kildens lager er over denne fyldning (eller der er et fuldt læs)."/>
|
||||
<text name="spu_collectRound" text="Saml op på én tur"/>
|
||||
<text name="spu_collectRound_tooltip" text="Tipvogn: samme vare fra flere kilder til vognen er fuld. Autoload-vogn: flere pallevarer med samme mål på én tur."/>
|
||||
@@ -434,6 +436,7 @@
|
||||
<text name="spu_guide_flow_sumGoods" text="%d varer"/>
|
||||
<text name="spu_guide_flow_sumSellAt" text="sælg fra %d %%"/>
|
||||
<text name="spu_guide_flow_sumStoreAbove" text="lager over %d %%"/>
|
||||
<text name="spu_guide_flow_sumSellAbove" text="sælg altid over %d %%"/>
|
||||
<text name="spu_guide_flow_sumRound" text="samlerunde"/>
|
||||
<text name="spu_guide_flow_sumLoops" text="%d ture"/>
|
||||
<text name="spu_guide_flow_sumLoopsInf" text="uendelige ture"/>
|
||||
|
||||
@@ -240,6 +240,8 @@
|
||||
<text name="spu_sellAt" text="Sell from (% of yearly top price)"/>
|
||||
<text name="spu_sellAt_tooltip" text="Drive to sell when the selling point pays at least this share of the good's top price in the yearly price curve — regardless of amount. Great demand always qualifies."/>
|
||||
<text name="spu_storeAbove" text="Drive to storage above"/>
|
||||
<text name="spu_sellAbove" text="Always sell when source is above"/>
|
||||
<text name="spu_sellAbove_off" text="Off"/>
|
||||
<text name="spu_storeAbove_tooltip" text="Goods with the Store policy are driven to a silo when the source storage is above this fill level (or a full load is available)."/>
|
||||
<text name="spu_collectRound" text="Collect in one trip"/>
|
||||
<text name="spu_collectRound_tooltip" text="Tipper: same good from several sources until full. Autoload trailer: several pallet goods with the same destination in one trip."/>
|
||||
@@ -434,6 +436,7 @@
|
||||
<text name="spu_guide_flow_sumGoods" text="%d goods"/>
|
||||
<text name="spu_guide_flow_sumSellAt" text="sell from %d %%"/>
|
||||
<text name="spu_guide_flow_sumStoreAbove" text="store above %d %%"/>
|
||||
<text name="spu_guide_flow_sumSellAbove" text="always sell above %d %%"/>
|
||||
<text name="spu_guide_flow_sumRound" text="collection round"/>
|
||||
<text name="spu_guide_flow_sumLoops" text="%d trips"/>
|
||||
<text name="spu_guide_flow_sumLoopsInf" text="endless trips"/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<modDesc descVersion="107">
|
||||
<author>masterdraco</author>
|
||||
<version>1.32.5.0</version>
|
||||
<version>1.33.0.0</version>
|
||||
<title>
|
||||
<en>AD Smart Pickup</en>
|
||||
</title>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user