feat: v1.12.1.0 — prisregel via sæsonkurven, reserve til gårdens eget forbrug, paller til palleskur, fanen Lagre (idéer fra Distribution Redux)

This commit is contained in:
2026-09-23 10:34:41 +02:00
parent 43483bda76
commit 2d6e6dd7bf
16 changed files with 595 additions and 43 deletions
+19
View File
@@ -394,6 +394,25 @@ ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
check("O16 næste spawn via fyldniveau", sources[3].spawnCalls, 2)
-- O17 (reserve + paller til lager): buildPlanInput henter reserve og palle-lager fra ADSources
local demandCalls = {}
ADSources.getFarmDemand = function(_, ft, farmId, excluded) table.insert(demandCalls, ft) return {liters = ft == SUGAR and 4000 or 0, consumers = {"TMR-fabrik"}} end
local palletStoreMarker = nil
ADSources.findPalletStore = function(_, vehicle, ft, excluded, isReachable, farmId) return palletStoreMarker end
local v17 = newVehicle("Reserve", 0, 0, 20000)
runs[v17] = outboundRun(17, {1}, {{fillType = "SUGAR", policy = "sell"}})
local input17 = ADOutbound.buildPlanInput(adEnv, runs[v17], v17)
check("O17 reserve på salgsvare", input17.products[1].reserve, 4000)
check("O17 reserveret til", input17.products[1].reservedFor[1], "TMR-fabrik")
local v18 = newVehicle("PalletStore", 100, 0, 8000, true)
runs[v18] = outboundRun(18, {3}, {{fillType = "STRAW_BASKET", policy = "store"}})
palletStoreMarker = {wayPointId = 40, name = "Palleskur", free = 100}
sources[3].stored[BASKET] = 4
products[3][1].available, products[3][1].level, products[3][1].capacity = 4000, 3900, 4000
local input18 = ADOutbound.buildPlanInput(adEnv, runs[v18], v18)
check("O18 palle-lager som store-mål", input18.products[1].store ~= nil and input18.products[1].store.wayPointId, 40)
check("O18 reserve spørges ikke for lager-politik", demandCalls[#demandCalls] ~= BASKET, true)
-- O10: install wrapper: fejl i hook stopper ikke AD
local calls = {}
adEnv.LoadAtDestinationTask.update = function(self, dt) calls.update = (calls.update or 0) + 1 end
+21
View File
@@ -109,5 +109,26 @@ check("P12 storageHigh", e.storageHigh, true)
check("P12 fullLoad", e.fullLoad, true)
check("P12 priceOk", e.priceOk, true)
-- P13 (reserve): det gårdens fabrikker/stalde skal bruge, sælges ikke
local BEET = 10
local withReserve = product("SUGARBEET", BEET, "sell", {{wayPointId = 3, available = 268000, level = 268000, capacity = 12000000}}, quoteS, nil)
withReserve.reserve, withReserve.reservedFor = 196000, {"TMR-fabrik"}
local trip13 = P.plan(input({withReserve}))
check("P13 fuldt læs uden om reserven -> salg", trip13 ~= nil and trip13.kind, "sell")
withReserve.reserve = 260000 -- kun 8.000 til overs: mindre end et læs
local trip13b, reasons13b = P.plan(input({withReserve}))
check("P13 reserven ville blive rørt -> ingen tur", trip13b, nil)
check("P13 begrundelse", reasons13b[1], "SUGARBEET: 260.0k reserveret til TMR-fabrik")
withReserve.reserve = 300000 -- alt reserveret
check("P13 alt reserveret -> ingen tur", P.plan(input({withReserve})), nil)
withReserve.reserve = 0
check("P13 reserve 0 -> salg uanset mængde", P.plan(input({withReserve})).kind, "sell")
-- lager-politik ignorerer reserven (varen bliver på gården)
local storeReserve = product("SUGARBEET", BEET, "store", {{wayPointId = 3, available = 268000, level = 11000000, capacity = 12000000}}, nil, storeSilo)
storeReserve.reserve = 300000
check("P13 lager rører ikke reserven", P.plan(input({storeReserve})).kind, "store")
local e13 = P.evaluate(withReserve, input({}, {capacity = 20000}))
check("P13 evaluate sellable", e13.sellable, 268000)
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)
+16 -4
View File
@@ -42,17 +42,29 @@ local station = {
}
local quote = ADPrices.getQuote(station, SUGAR)
check("P4 pris", quote.price, 470)
check("P4 top", quote.top, 625)
check("P4 top (pris ved 100 %)", math.floor(quote.top + 0.5), 627)
check("P4 procent", quote.percent, 75)
check("P4 er salgssted", ADPrices.isSellingStation(station), true)
check("P4 skjult = ikke salgssted", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = true, hideFromPricesMenu = true}), false)
check("P4 fabrik-station = ikke salgssted", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = false}), false)
-- P5: uden stationens grundpris: pricePerLiter × faktor nu × sværhedsgrad
-- P5: uden stationens grundpris: kun kurven tæller (faktor nu ÷ faktor max)
local bare = {getEffectiveFillTypePrice = function() return 470 end}
local quote2 = ADPrices.getQuote(bare, SUGAR)
check("P5 top fra pricePerLiter", quote2.top, 500)
check("P5 procent", quote2.percent, 94)
check("P5 procent = kurven alene", quote2.percent, 80)
check("P5 top = effektiv ÷ kurve", math.floor(quote2.top + 0.5), 588)
-- P7: kurven er grundlaget (Distribution Redux-metoden): faktor nu ÷ årets max, uafhængigt af sværhedsgrad
check("P7 curvePercent", ADPrices.curvePercent({1.0, 1.25, 0.9}, 1), 80)
check("P7 curvePercent ved top", ADPrices.curvePercent({1.0, 1.25, 0.9}, 2), 100)
check("P7 flad kurve = 100", ADPrices.curvePercent({1.0, 1.01, 1.0}, 3), 100)
check("P7 ingen kurve = 100", ADPrices.curvePercent(nil, 1), 100)
check("P7 ukendt periode = 100", ADPrices.curvePercent({1.0, 1.25}, nil), 100)
-- P8: stor efterspørgsel ganger stationens egen faktor på: 500 grundpris, 700 effektiv, kurve 80 % -> 112
local demandStation = {fillTypePrices = {[SUGAR] = 500}, getEffectiveFillTypePrice = function() return 700 end}
check("P8 stor efterspørgsel > 100", ADPrices.getQuote(demandStation, SUGAR).percent, 112)
-- P9: sværhedsgraden påvirker ikke procenten (begge priser skaleres ens)
local hardStation = {fillTypePrices = {[SUGAR] = 250}, getEffectiveFillTypePrice = function() return 235 end}
check("P9 uafhængig af sværhedsgrad", ADPrices.getQuote(hardStation, SUGAR).percent, 75)
check("P5 fejlende station -> nil", ADPrices.getQuote({getEffectiveFillTypePrice = function() error("x") end}, SUGAR), nil)
-- P6: bedste tilbud = højeste procent, dernæst pris
+77
View File
@@ -199,5 +199,82 @@ check("S9 fabrik foretrækkes frem for objektlager i nærheden", ADSources.ident
local excluded = ADSources.getSourcePlaceables({source1, source2})
check("S8 udelukkede placeables", excluded[factory] and excluded[silo], true)
-- S10 (paller til lager): objektlager med plads der tager varen; nærmeste nåelige fra traktoren
objectStorage.getObjectStorageSupportsFillType = function(self, ft) return ft == BASKET or ft == FORAGE end
check("S10 tager kurve", ADSources.objectStorageAccepts(objectStorage, BASKET), true)
check("S10 tager ikke sukker", ADSources.objectStorageAccepts(objectStorage, SUGAR), false)
check("S10 fri pladser", ADSources.objectStorageFreeSlots(objectStorage), 3990)
local fullShed = {rootNode = {x = 700, z = 700}, spec_objectStorage = {capacity = 10, numStoredObjects = 10, objectInfos = {}}, getName = function() return "Fuldt skur" end}
table.insert(g_currentMission.placeableSystem.placeables, fullShed)
addMarker(8, "Fuldt skur", 700, 690)
check("S10 fuldt skur 0 pladser", ADSources.objectStorageFreeSlots(fullShed), 0)
local truck = {rootNode = {x = 650, z = 650}}
local store = ADSources.findPalletStore(adEnv, truck, BASKET, {}, function() return true end, 1)
check("S10 nærmeste med plads = Objektlager (skuret er fuldt)", store.wayPointId, 3)
check("S10 navn", store.name, "Objektlager")
check("S10 udelukket kilde -> nil", ADSources.findPalletStore(adEnv, truck, BASKET, {[objectStorage] = true}, function() return true end, 1), nil)
check("S10 uopnåelig -> nil", ADSources.findPalletStore(adEnv, truck, BASKET, {}, function() return false end, 1), nil)
check("S10 sukker: intet skur tager det", ADSources.findPalletStore(adEnv, truck, SUGAR, {}, function() return true end, 1), nil)
-- S11 (reserve): gårdens forbrug det næste døgn af en vare, minus hvad forbrugerne allerede har
local BEET, STRAW, WATER = 9, 10, 11
DESC[BEET] = {index = BEET, name = "SUGARBEET", title = "Roer"}; DESC[STRAW] = {index = STRAW, name = "STRAW", title = "Halm"}; DESC[WATER] = {index = WATER, name = "WATER", title = "Vand"}
local tmrStorage = storageOf({[BEET] = 20000, [FORAGE] = 500000}, {[BEET] = 2500000, [FORAGE] = 3500000})
local tmrFactory = {
rootNode = {x = 800, z = 800}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "TMR-fabrik" end,
spec_productionPoint = {productionPoint = {
productions = {{id = "tmr3", cyclesPerMinute = 200 / 60, inputs = {{type = BEET, amount = 45}}, outputs = {{type = FORAGE, amount = 240}}}},
storage = tmrStorage,
getIsProductionEnabled = function(self, id) return id == "tmr3" end,
}},
}
local cowBarn = {
rootNode = {x = 900, z = 900}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Kostald" end,
spec_husbandryStraw = {inputLitersPerHour = 500}, spec_husbandryWater = {litersPerHour = 100, automaticWaterSupply = true},
getHusbandryIsFillTypeSupported = function(self, ft) return ft == STRAW or ft == WATER end,
getHusbandryFillLevel = function(self, ft) return ft == STRAW and 2000 or 0 end,
getHusbandryFreeCapacity = function(self, ft) return ft == STRAW and 19375 or 0 end,
}
local otherFarmBarn = {rootNode = {x = 950, z = 950}, ownerFarmId = 2, getOwnerFarmId = function() return 2 end, spec_husbandryStraw = {inputLitersPerHour = 500}, getHusbandryIsFillTypeSupported = function() return true end, getHusbandryFillLevel = function() return 0 end, getHusbandryFreeCapacity = function() return 99999 end}
table.insert(g_currentMission.placeableSystem.placeables, tmrFactory)
table.insert(g_currentMission.placeableSystem.placeables, cowBarn)
table.insert(g_currentMission.placeableSystem.placeables, otherFarmBarn)
local demand = ADSources.getFarmDemand(adEnv, BEET, 1, {})
-- 45 l × (200/60) pr. min × 1440 min = 216.000 l/døgn; har 20.000 -> 196.000
check("S11 roer: døgnforbrug minus beholdning", demand.liters, 196000)
check("S11 forbruger-navn", demand.consumers[1], "TMR-fabrik")
local strawDemand = ADSources.getFarmDemand(adEnv, STRAW, 1, {})
-- 500 l/t × 24 = 12.000; har 2.000 -> 10.000 (under fri plads 19.375)
check("S11 halm: stald", strawDemand.liters, 10000)
check("S11 anden farm tæller ikke", #strawDemand.consumers, 1)
check("S11 automatisk vand = intet behov", ADSources.getFarmDemand(adEnv, WATER, 1, {}).liters, 0)
check("S11 output er ikke behov", ADSources.getFarmDemand(adEnv, FORAGE, 1, {}).liters, 0)
check("S11 udelukket forbruger", ADSources.getFarmDemand(adEnv, BEET, 1, {[tmrFactory] = true}).liters, 0)
tmrStorage.fillLevels[BEET] = 300000
check("S11 nok på lager -> 0", ADSources.getFarmDemand(adEnv, BEET, 1, {}).liters, 0)
tmrStorage.fillLevels[BEET] = 20000
tmrFactory.spec_productionPoint.productionPoint.getIsProductionEnabled = function() return false end
check("S11 slukket produktion -> 0", ADSources.getFarmDemand(adEnv, BEET, 1, {}).liters, 0)
-- S12 (inventar): alt gården har liggende, pr. bygning, kun bygninger med indhold
factory.ownerFarmId = 1; silo.ownerFarmId = 1; objectStorage.ownerFarmId = 1
local inventory = ADSources.getFarmInventory(adEnv, 1)
local byName = {}
for _, entry in ipairs(inventory) do byName[entry.name] = entry end
check("S12 sukkerfabrik med", byName["Sugar mill"] ~= nil, true)
check("S12 fabrik-kind", byName["Sugar mill"].kind, "factory")
check("S12 fabrik sukker", byName["Sugar mill"].entries[1].level, 150000)
check("S12 fabrik sukker er output", byName["Sugar mill"].entries[1].isOutput, true)
local beetEntry
for _, e in ipairs(byName["TMR-fabrik"].entries) do if e.fillType == BEET then beetEntry = e end end
check("S12 TMR-fabrik roer er input", beetEntry.isOutput, false)
check("S12 outputs først", byName["TMR-fabrik"].entries[1].isOutput, true)
check("S12 silo", byName["Farma"].entries[1].fillType, FORAGE)
check("S12 objektlager kurve som antal", byName["Objektlager"].entries[1].count, 8)
check("S12 stald halm", byName["Kostald"].entries[1].level, 2000)
check("S12 stald kapacitet = niveau + fri", byName["Kostald"].entries[1].capacity, 21375)
check("S12 tomt skur udeladt", byName["Fuldt skur"], nil)
check("S12 anden farm udeladt", #inventory, 5)
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)