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:
@@ -144,7 +144,17 @@ local function findSellQuote(adEnv, vehicle, product, sellMarkers, sellAtPercent
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function findStoreMarker(adEnv, vehicle, product, capacity, sources, litersOverride)
|
local function findStoreMarker(adEnv, vehicle, product, capacity, sources, litersOverride)
|
||||||
if product.isPallet or ADLoadSwap == nil or ADLoadSwap.findDumpMarker == nil then
|
local isReachable = function(markerId)
|
||||||
|
return ADWaitPool.hasRouteTo(adEnv, vehicle, markerId)
|
||||||
|
end
|
||||||
|
-- paller lagres i et palleskur (objektlager) — UAL læsser af dér
|
||||||
|
if product.isPallet then
|
||||||
|
if ADSources.findPalletStore == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return ADSources.findPalletStore(adEnv, vehicle, product.fillType, ADSources.getSourcePlaceables(sources), isReachable, ADSmartPickup.getFarmId(vehicle))
|
||||||
|
end
|
||||||
|
if ADLoadSwap == nil or ADLoadSwap.findDumpMarker == nil then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local available = litersOverride or 0
|
local available = litersOverride or 0
|
||||||
@@ -154,9 +164,6 @@ 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))
|
||||||
local isReachable = function(markerId)
|
|
||||||
return ADWaitPool.hasRouteTo(adEnv, vehicle, markerId)
|
|
||||||
end
|
|
||||||
local marker = ADLoadSwap.findDumpMarker(adEnv, vehicle, product.fillType, liters, nil, isReachable, ADSources.getSourcePlaceables(sources))
|
local marker = ADLoadSwap.findDumpMarker(adEnv, vehicle, product.fillType, liters, nil, isReachable, ADSources.getSourcePlaceables(sources))
|
||||||
if marker == nil then
|
if marker == nil then
|
||||||
return nil
|
return nil
|
||||||
@@ -210,6 +217,11 @@ function ADOutbound.buildPlanInput(adEnv, run, vehicle)
|
|||||||
local homeId = product.sources[1] ~= nil and product.sources[1].wayPointId or nil
|
local homeId = product.sources[1] ~= nil and product.sources[1].wayPointId or nil
|
||||||
local quote, unreachableName = findSellQuote(adEnv, vehicle, product, sellMarkers, run.sellAtPercent, homeId)
|
local quote, unreachableName = findSellQuote(adEnv, vehicle, product, sellMarkers, run.sellAtPercent, homeId)
|
||||||
product.quote = quote
|
product.quote = quote
|
||||||
|
-- gårdens eget behov (fabrikker/stalde) sælges ikke; kilderne selv tæller ikke som forbrugere
|
||||||
|
if ADSources.getFarmDemand ~= nil then
|
||||||
|
local demand = ADSources.getFarmDemand(adEnv, product.fillType, farmId, ADSources.getSourcePlaceables(sources))
|
||||||
|
product.reserve, product.reservedFor = demand.liters, demand.consumers
|
||||||
|
end
|
||||||
if quote == nil and unreachableName ~= nil then
|
if quote == nil and unreachableName ~= nil then
|
||||||
warnOnce(adEnv, vehicle, "sell:" .. product.name, string.format("'%s' kan ikke nå salgssted '%s' for %s – tjek vejnettet", tostring(vehicle:getName()), unreachableName, product.name))
|
warnOnce(adEnv, vehicle, "sell:" .. product.name, string.format("'%s' kan ikke nå salgssted '%s' for %s – tjek vejnettet", tostring(vehicle:getName()), unreachableName, product.name))
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
-- products = { { fillType, name, policy = "store"|"sell"|"sellElseStore", isPallet,
|
-- products = { { fillType, name, policy = "store"|"sell"|"sellElseStore", isPallet,
|
||||||
-- sources = { {wayPointId, available, level, capacity} ... }, -- kilder med varen
|
-- sources = { {wayPointId, available, level, capacity} ... }, -- kilder med varen
|
||||||
-- quote = {percent, price, wayPointId, stationName} | nil, -- bedste NÅELIGE salgssted
|
-- quote = {percent, price, wayPointId, stationName} | nil, -- bedste NÅELIGE salgssted
|
||||||
-- store = {wayPointId, name, free} | nil } ... } } -- silo med plads, nåelig
|
-- store = {wayPointId, name, free} | nil, -- silo/palleskur med plads, nåelig
|
||||||
|
-- reserve = liter | nil, reservedFor = {navne} | nil } ... } } -- gårdens eget behov (sælges ikke)
|
||||||
-- plan(input) -> trip | nil, reasons
|
-- plan(input) -> trip | nil, reasons
|
||||||
-- trip = {kind = "sell"|"store", fillTypes, names, sourceIds, targetWayPointId, targetName, percent}
|
-- trip = {kind = "sell"|"store", fillTypes, names, sourceIds, targetWayPointId, targetName, percent}
|
||||||
-- reasons = én linje pr. vare der ikke gav en tur ("SUGAR 78 % af top (kræver 90), lager 41 %")
|
-- reasons = én linje pr. vare der ikke gav en tur ("SUGAR 78 % af top (kræver 90), lager 41 %")
|
||||||
@@ -43,8 +44,11 @@ end
|
|||||||
function ADOutboundPlanner.evaluate(product, input)
|
function ADOutboundPlanner.evaluate(product, input)
|
||||||
local available, storagePercent = sumSources(product)
|
local available, storagePercent = sumSources(product)
|
||||||
local percent = product.quote ~= nil and product.quote.percent or nil
|
local percent = product.quote ~= nil and product.quote.percent or nil
|
||||||
|
local reserve = math.max(0, product.reserve or 0)
|
||||||
return {
|
return {
|
||||||
available = available,
|
available = available,
|
||||||
|
reserve = reserve,
|
||||||
|
sellable = math.max(0, available - reserve),
|
||||||
fullLoad = input.capacity ~= nil and input.capacity > 0 and available >= input.capacity,
|
fullLoad = input.capacity ~= nil and input.capacity > 0 and available >= input.capacity,
|
||||||
storagePercent = storagePercent,
|
storagePercent = storagePercent,
|
||||||
storageHigh = storagePercent >= (input.storeAbovePercent or 80),
|
storageHigh = storagePercent >= (input.storeAbovePercent or 80),
|
||||||
@@ -53,10 +57,33 @@ function ADOutboundPlanner.evaluate(product, input)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function formatLiters(liters)
|
||||||
|
if liters >= 1000000 then
|
||||||
|
return string.format("%.1fM", liters / 1000000)
|
||||||
|
elseif liters >= 1000 then
|
||||||
|
return string.format("%.1fk", liters / 1000)
|
||||||
|
end
|
||||||
|
return string.format("%d", liters)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Salg må ikke røre reserven: enten er intet reserveret, eller et helt læs kan tages uden om den
|
||||||
|
-- (AutoDrive læsser altid vognen fuld, så en delvis mængde kan ikke styres).
|
||||||
|
local function canSellWithoutReserve(e, input)
|
||||||
|
if e.reserve <= 0 then
|
||||||
|
return e.sellable >= ADOutboundPlanner.MIN_AVAILABLE_LITERS
|
||||||
|
end
|
||||||
|
local capacity = input.capacity or 0
|
||||||
|
return e.sellable >= ADOutboundPlanner.MIN_AVAILABLE_LITERS and (capacity <= 0 or e.sellable >= capacity)
|
||||||
|
end
|
||||||
|
|
||||||
local function reasonFor(product, e, input)
|
local function reasonFor(product, e, input)
|
||||||
if e.available < ADOutboundPlanner.MIN_AVAILABLE_LITERS then
|
if e.available < ADOutboundPlanner.MIN_AVAILABLE_LITERS then
|
||||||
return product.name .. ": intet på lager"
|
return product.name .. ": intet på lager"
|
||||||
end
|
end
|
||||||
|
if wantsSell(product.policy) and e.priceOk and product.quote ~= nil and not canSellWithoutReserve(e, input) then
|
||||||
|
local names = table.concat(product.reservedFor or {}, ", ")
|
||||||
|
return string.format("%s: %s reserveret til %s", product.name, formatLiters(e.reserve), names ~= "" and names or "gårdens eget forbrug")
|
||||||
|
end
|
||||||
local canStore = wantsStore(product.policy) and product.store ~= nil
|
local canStore = wantsStore(product.policy) and product.store ~= nil
|
||||||
if wantsSell(product.policy) and product.quote == nil and not canStore then
|
if wantsSell(product.policy) and product.quote == nil and not canStore then
|
||||||
return product.name .. ": intet nåeligt salgssted"
|
return product.name .. ": intet nåeligt salgssted"
|
||||||
@@ -79,7 +106,7 @@ local function collectCandidates(input)
|
|||||||
local e = ADOutboundPlanner.evaluate(product, input)
|
local e = ADOutboundPlanner.evaluate(product, input)
|
||||||
local added = false
|
local added = false
|
||||||
if e.available >= ADOutboundPlanner.MIN_AVAILABLE_LITERS then
|
if e.available >= ADOutboundPlanner.MIN_AVAILABLE_LITERS then
|
||||||
if wantsSell(product.policy) and e.priceOk and product.quote ~= nil then
|
if wantsSell(product.policy) and e.priceOk 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})
|
table.insert(sell, {product = product, e = e, kind = "sell", targetWayPointId = product.quote.wayPointId, targetName = product.quote.stationName, percent = e.percent})
|
||||||
added = true
|
added = true
|
||||||
elseif wantsStore(product.policy) and (e.fullLoad or e.storageHigh) and product.store ~= nil then
|
elseif wantsStore(product.policy) and (e.fullLoad or e.storageHigh) and product.store ~= nil then
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
-- AD Prices
|
-- AD Prices
|
||||||
-- Prisregel for udkørsel: hvor tæt er salgsstedets pris på årets toppris for varen?
|
-- Prisregel for udkørsel: hvor tæt er salgsstedets pris på årets toppris for varen?
|
||||||
-- Toppris = stationens grundpris lige nu × (årets højeste periodefaktor ÷ nuværende periodes
|
-- Grundlaget er spillets faste sæsonkurve (fillType.economy.factors[1..12], samme data som
|
||||||
-- faktor) — dvs. det Priser-menuen ville vise som årets bedste måned for netop den station.
|
-- prisgrafen i Priser-menuen; metode fra Distribution Redux): kurve-% = faktor nu ÷ årets højeste.
|
||||||
-- Effektiv pris (inkl. prisfald efter salg og stor efterspørgsel) ÷ toppris = pris-%;
|
-- Oven på det ganges stationens egen afvigelse (effektiv pris ÷ stationens grundpris nu), så
|
||||||
-- stor efterspørgsel giver > 100 % og opfylder dermed altid reglen.
|
-- prisfald efter salg og stor efterspørgsel tæller med — stor efterspørgsel giver > 100 %.
|
||||||
|
-- Sværhedsgraden skalerer begge priser ens og falder ud af regnestykket. Flade kurver
|
||||||
|
-- (top/bund under FLAT_THRESHOLD) regnes som 100 %, så varer uden sæson aldrig strander.
|
||||||
-- De rene funktioner øverst har ingen spil-API; getQuote er spil-adapteren.
|
-- De rene funktioner øverst har ingen spil-API; getQuote er spil-adapteren.
|
||||||
|
|
||||||
ADPrices = {}
|
ADPrices = {}
|
||||||
|
|
||||||
ADPrices.LOG_PREFIX = "ADSmartPickup: "
|
ADPrices.LOG_PREFIX = "ADSmartPickup: "
|
||||||
|
-- top/bund under dette = ingen reel sæson (Distribution Redux bruger samme grænse)
|
||||||
|
ADPrices.FLAT_THRESHOLD = 1.02
|
||||||
|
|
||||||
function ADPrices.maxFactor(factors)
|
function ADPrices.maxFactor(factors)
|
||||||
local best = nil
|
local best = nil
|
||||||
@@ -37,6 +41,29 @@ function ADPrices.percentOfTop(price, top)
|
|||||||
return math.floor(100 * price / top + 0.5)
|
return math.floor(100 * price / top + 0.5)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function ADPrices.minFactor(factors)
|
||||||
|
local best = nil
|
||||||
|
for _, factor in pairs(factors or {}) do
|
||||||
|
if type(factor) == "number" and (best == nil or factor < best) then
|
||||||
|
best = factor
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return best or 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Kurve-%: nuværende periodes faktor ÷ årets højeste, afrundet. 100 uden kurve, uden periode
|
||||||
|
-- eller når kurven er flad.
|
||||||
|
function ADPrices.curvePercent(factors, period)
|
||||||
|
if factors == nil or period == nil then
|
||||||
|
return 100
|
||||||
|
end
|
||||||
|
local now, max, min = tonumber(factors[period]), ADPrices.maxFactor(factors), ADPrices.minFactor(factors)
|
||||||
|
if now == nil or now <= 0 or max <= 0 or min <= 0 or (max / min) < ADPrices.FLAT_THRESHOLD then
|
||||||
|
return 100
|
||||||
|
end
|
||||||
|
return math.floor(100 * now / max + 0.5)
|
||||||
|
end
|
||||||
|
|
||||||
-- Rigtige salgssteder: SellingStation der sælger (fabrikkers aflæsningsstationer er
|
-- Rigtige salgssteder: SellingStation der sælger (fabrikkers aflæsningsstationer er
|
||||||
-- også SellingStation-objekter, men med isSellingPoint = false) og vises i Priser-menuen.
|
-- også SellingStation-objekter, men med isSellingPoint = false) og vises i Priser-menuen.
|
||||||
function ADPrices.isSellingStation(station)
|
function ADPrices.isSellingStation(station)
|
||||||
@@ -49,18 +76,8 @@ local function getCurrentPeriod()
|
|||||||
return environment ~= nil and environment.currentPeriod or nil
|
return environment ~= nil and environment.currentPeriod or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getDifficultyMultiplier()
|
|
||||||
local economy = g_currentMission ~= nil and g_currentMission.economyManager or nil
|
|
||||||
if economy ~= nil and economy.getPriceMultiplier ~= nil then
|
|
||||||
local ok, value = pcall(economy.getPriceMultiplier, economy)
|
|
||||||
if ok and type(value) == "number" then
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
-- {price, top, percent} for varen ved stationen; nil hvis stationen ikke kan prissætte den.
|
-- {price, top, percent} for varen ved stationen; nil hvis stationen ikke kan prissætte den.
|
||||||
|
-- percent = kurve-% × (effektiv ÷ stationens grundpris nu); top = den pris der svarer til 100 %.
|
||||||
function ADPrices.getQuote(station, fillType)
|
function ADPrices.getQuote(station, fillType)
|
||||||
if station == nil or station.getEffectiveFillTypePrice == nil then
|
if station == nil or station.getEffectiveFillTypePrice == nil then
|
||||||
return nil
|
return nil
|
||||||
@@ -71,14 +88,12 @@ function ADPrices.getQuote(station, fillType)
|
|||||||
end
|
end
|
||||||
local fillTypeDesc = g_fillTypeManager ~= nil and g_fillTypeManager.getFillTypeByIndex ~= nil and g_fillTypeManager:getFillTypeByIndex(fillType) or nil
|
local fillTypeDesc = g_fillTypeManager ~= nil and g_fillTypeManager.getFillTypeByIndex ~= nil and g_fillTypeManager:getFillTypeByIndex(fillType) or nil
|
||||||
local factors = fillTypeDesc ~= nil and fillTypeDesc.economy ~= nil and fillTypeDesc.economy.factors or nil
|
local factors = fillTypeDesc ~= nil and fillTypeDesc.economy ~= nil and fillTypeDesc.economy.factors or nil
|
||||||
local period = getCurrentPeriod()
|
local curve = ADPrices.curvePercent(factors, getCurrentPeriod())
|
||||||
local factorNow = factors ~= nil and period ~= nil and tonumber(factors[period]) or 1
|
|
||||||
local baseNow = station.fillTypePrices ~= nil and tonumber(station.fillTypePrices[fillType]) or nil
|
local baseNow = station.fillTypePrices ~= nil and tonumber(station.fillTypePrices[fillType]) or nil
|
||||||
if baseNow == nil or baseNow <= 0 then
|
local stationRatio = (baseNow ~= nil and baseNow > 0) and (effective / baseNow) or 1
|
||||||
baseNow = (fillTypeDesc ~= nil and tonumber(fillTypeDesc.pricePerLiter) or 0) * factorNow * getDifficultyMultiplier()
|
local percent = math.floor(curve * stationRatio + 0.5)
|
||||||
end
|
local top = percent > 0 and (effective * 100 / percent) or effective
|
||||||
local top = ADPrices.topPrice(baseNow, factorNow, ADPrices.maxFactor(factors))
|
return {price = effective, top = top, percent = percent}
|
||||||
return {price = effective, top = top, percent = ADPrices.percentOfTop(effective, top)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Højeste pris-%, ved lighed højeste pris. nil ved tom liste.
|
-- Højeste pris-%, ved lighed højeste pris. nil ved tom liste.
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ if ADSmartPickup.dir ~= nil and source ~= nil then
|
|||||||
source(ADSmartPickup.dir .. "gui/SmartPickupFrame.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupFrame.lua")
|
||||||
source(ADSmartPickup.dir .. "gui/SmartPickupSupplyTab.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupSupplyTab.lua")
|
||||||
source(ADSmartPickup.dir .. "gui/SmartPickupOutboundTab.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupOutboundTab.lua")
|
||||||
|
source(ADSmartPickup.dir .. "gui/SmartPickupStorageTab.lua")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Markøren er dér hvor trækkerens næse stopper; læssetriggeren ligger et
|
-- Markøren er dér hvor trækkerens næse stopper; læssetriggeren ligger et
|
||||||
|
|||||||
@@ -317,6 +317,281 @@ function ADSources.getSellMarkers(adEnv, farmId)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- ---- paller til lager: objektlager (palleskur) som mål ----
|
||||||
|
|
||||||
|
function ADSources.objectStorageAccepts(placeable, fillType)
|
||||||
|
local spec = placeable ~= nil and placeable.spec_objectStorage or nil
|
||||||
|
if spec == nil or spec.supportsPallets == false then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if placeable.getObjectStorageSupportsFillType ~= nil then
|
||||||
|
local ok, value = pcall(placeable.getObjectStorageSupportsFillType, placeable, fillType)
|
||||||
|
return ok and value == true
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ledige pladser (objekter); uendeligt når lageret ingen kapacitet opgiver.
|
||||||
|
function ADSources.objectStorageFreeSlots(placeable)
|
||||||
|
local spec = placeable ~= nil and placeable.spec_objectStorage or nil
|
||||||
|
if spec == nil then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local capacity = spec.capacity or 0
|
||||||
|
if capacity <= 0 then
|
||||||
|
return math.huge
|
||||||
|
end
|
||||||
|
local stored = spec.storedObjects ~= nil and #spec.storedObjects or (spec.numStoredObjects or 0)
|
||||||
|
return math.max(0, capacity - stored)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Nærmeste markør ved et objektlager der tager varen og har plads, med rute fra traktoren;
|
||||||
|
-- kilderne selv udelukkes. {wayPointId, name, free = pladser} eller nil.
|
||||||
|
function ADSources.findPalletStore(adEnv, vehicle, fillType, excludedPlaceables, isReachable, farmId)
|
||||||
|
local x, z = getNodePosition(vehicle.rootNode)
|
||||||
|
if x == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local candidates = {}
|
||||||
|
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
||||||
|
local source = ADSources.identify(adEnv, marker.id, farmId)
|
||||||
|
if source ~= nil and source.kind == "objectStorage" and not (excludedPlaceables ~= nil and excludedPlaceables[source.placeable])
|
||||||
|
and ADSources.objectStorageAccepts(source.placeable, fillType) and ADSources.objectStorageFreeSlots(source.placeable) >= 1 then
|
||||||
|
local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id)
|
||||||
|
table.insert(candidates, {marker = marker, source = source, distance = distance2(x, z, wayPoint.x, wayPoint.z)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(candidates, function(a, b) return a.distance < b.distance end)
|
||||||
|
for _, candidate in ipairs(candidates) do
|
||||||
|
if isReachable == nil or isReachable(candidate.marker.id) then
|
||||||
|
return {wayPointId = candidate.marker.id, name = tostring(candidate.marker.name), free = ADSources.objectStorageFreeSlots(candidate.source.placeable)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ---- reserve: hvad gårdens egne fabrikker og stalde skal bruge det næste døgn ----
|
||||||
|
|
||||||
|
ADSources.RESERVE_HOURS = 24
|
||||||
|
|
||||||
|
local function isOwnedBy(placeable, farmId)
|
||||||
|
if placeable.getOwnerFarmId ~= nil then
|
||||||
|
local ok, owner = pcall(placeable.getOwnerFarmId, placeable)
|
||||||
|
return ok and owner == farmId
|
||||||
|
end
|
||||||
|
return placeable.ownerFarmId == farmId
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isProductionWanted(productionPoint, production)
|
||||||
|
local isEnabled = false
|
||||||
|
if productionPoint.getIsProductionEnabled ~= nil then
|
||||||
|
isEnabled = productionPoint:getIsProductionEnabled(production.id) == true
|
||||||
|
else
|
||||||
|
for _, active in ipairs(productionPoint.activeProductions or {}) do
|
||||||
|
if active == production then isEnabled = true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local missingInputs = ProductionPoint ~= nil and ProductionPoint.PROD_STATUS ~= nil and ProductionPoint.PROD_STATUS.MISSING_INPUTS or nil
|
||||||
|
return isEnabled or (missingInputs ~= nil and production.status == missingInputs)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Fabrik: døgnforbrug af varen i ønskede produktioner minus det den har, højst fri plads.
|
||||||
|
local function getProductionReserve(placeable, fillType)
|
||||||
|
local productionPoint = getProductionPoint(placeable)
|
||||||
|
if productionPoint == nil or productionPoint.storage == nil then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local perMinute = 0
|
||||||
|
for _, production in ipairs(productionPoint.productions or {}) do
|
||||||
|
if isProductionWanted(productionPoint, production) then
|
||||||
|
for _, input in ipairs(production.inputs or {}) do
|
||||||
|
if input.type == fillType then
|
||||||
|
local cyclesPerMinute = production.cyclesPerMinute or ((production.cyclesPerHour or 0) / 60)
|
||||||
|
perMinute = perMinute + (input.amount or 0) * cyclesPerMinute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if perMinute <= 0 then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local storage = productionPoint.storage
|
||||||
|
local level = storage.getFillLevel ~= nil and (storage:getFillLevel(fillType) or 0) or ((storage.fillLevels or {})[fillType] or 0)
|
||||||
|
local free = storage.getFreeCapacity ~= nil and storage:getFreeCapacity(fillType) or nil
|
||||||
|
if free == nil then
|
||||||
|
local capacity = storage.getCapacity ~= nil and (storage:getCapacity(fillType) or 0) or 0
|
||||||
|
free = math.max(0, capacity - level)
|
||||||
|
end
|
||||||
|
local need = perMinute * 60 * ADSources.RESERVE_HOURS - level
|
||||||
|
return math.max(0, math.min(need, free))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Stald: foder/halm/vand-rate × timer minus beholdning, højst fri plads (spec-felter som
|
||||||
|
-- Distribution Redux læser dem); automatisk vandforsyning = intet behov.
|
||||||
|
local function getHusbandryReserve(placeable, fillType)
|
||||||
|
if placeable.getHusbandryIsFillTypeSupported == nil or placeable.getHusbandryFillLevel == nil then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local okSupported, supported = pcall(placeable.getHusbandryIsFillTypeSupported, placeable, fillType)
|
||||||
|
if not okSupported or supported ~= true then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local name = g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeNameByIndex(fillType) or ""
|
||||||
|
local rate, level = 0, placeable:getHusbandryFillLevel(fillType) or 0
|
||||||
|
local food, straw, water = placeable.spec_husbandryFood, placeable.spec_husbandryStraw, placeable.spec_husbandryWater
|
||||||
|
if name == "WATER" then
|
||||||
|
if water == nil or water.automaticWaterSupply then return 0 end
|
||||||
|
rate = water.litersPerHour or 0
|
||||||
|
elseif name == "STRAW" and straw ~= nil then
|
||||||
|
rate = straw.inputLitersPerHour or 0
|
||||||
|
elseif food ~= nil then
|
||||||
|
rate = food.litersPerHour or 0
|
||||||
|
level = 0
|
||||||
|
for _, value in pairs(food.fillLevels or {}) do level = level + (value or 0) end
|
||||||
|
end
|
||||||
|
if rate <= 0 then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local free = placeable.getHusbandryFreeCapacity ~= nil and (placeable:getHusbandryFreeCapacity(fillType) or 0) or math.huge
|
||||||
|
return math.max(0, math.min(rate * ADSources.RESERVE_HOURS - level, free))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- {liters, consumers = {navne}} — gårdens samlede behov for varen; udelukkede placeables (kilderne) tæller ikke.
|
||||||
|
function ADSources.getFarmDemand(adEnv, fillType, farmId, excludedPlaceables)
|
||||||
|
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
|
||||||
|
local total, consumers = 0, {}
|
||||||
|
for _, placeable in pairs(placeables) do
|
||||||
|
if isOwnedBy(placeable, farmId) and not (excludedPlaceables ~= nil and excludedPlaceables[placeable]) then
|
||||||
|
local reserve = getProductionReserve(placeable, fillType) + getHusbandryReserve(placeable, fillType)
|
||||||
|
if reserve > 0 then
|
||||||
|
total = total + reserve
|
||||||
|
table.insert(consumers, getPlaceableName(placeable, "?"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return {liters = total, consumers = consumers}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ---- inventar: alt gården har liggende, pr. bygning ----
|
||||||
|
|
||||||
|
local function addEntry(entries, fillType, level, capacity, extra)
|
||||||
|
local entry = newProduct(fillType, level, level, capacity, false)
|
||||||
|
entry.isOutput = extra ~= nil and extra.isOutput or false
|
||||||
|
entry.count = extra ~= nil and extra.count or nil
|
||||||
|
table.insert(entries, entry)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function factoryEntries(placeable)
|
||||||
|
local productionPoint = getProductionPoint(placeable)
|
||||||
|
local storage = productionPoint ~= nil and productionPoint.storage or nil
|
||||||
|
local entries = {}
|
||||||
|
if storage == nil then return entries end
|
||||||
|
local outputs = {}
|
||||||
|
for _, production in ipairs(productionPoint.productions or {}) do
|
||||||
|
for _, output in ipairs(production.outputs or {}) do outputs[output.type] = true end
|
||||||
|
end
|
||||||
|
for fillType, level in pairs(storage.fillLevels or {}) do
|
||||||
|
if (level or 0) > 0 then
|
||||||
|
local capacity = storage.getCapacity ~= nil and (storage:getCapacity(fillType) or 0) or 0
|
||||||
|
addEntry(entries, fillType, level, capacity, {isOutput = outputs[fillType] == true})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
local function siloEntries(placeable)
|
||||||
|
local entries, levels, capacities = {}, {}, {}
|
||||||
|
for _, storage in pairs(placeable.spec_silo.storages or {}) do
|
||||||
|
for fillType, level in pairs(storage.fillLevels or {}) do
|
||||||
|
if (level or 0) > 0 then
|
||||||
|
levels[fillType] = (levels[fillType] or 0) + level
|
||||||
|
capacities[fillType] = (capacities[fillType] or 0) + (storage.getCapacity ~= nil and (storage:getCapacity(fillType) or 0) or 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for fillType, level in pairs(levels) do addEntry(entries, fillType, level, capacities[fillType] or 0) end
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
local function objectStorageEntries(placeable)
|
||||||
|
local entries = {}
|
||||||
|
local spec = placeable.spec_objectStorage
|
||||||
|
for _, objectInfo in ipairs(spec.objectInfos or {}) do
|
||||||
|
local fillType, perObject = getObjectInfoContent(objectInfo)
|
||||||
|
local count = objectInfo.numObjects or 0
|
||||||
|
if fillType ~= nil and count > 0 then
|
||||||
|
addEntry(entries, fillType, count * perObject, spec.capacity or 0, {count = count})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
local function husbandryEntries(placeable)
|
||||||
|
local entries, seen = {}, {}
|
||||||
|
local food = placeable.spec_husbandryFood
|
||||||
|
if food ~= nil then
|
||||||
|
local total = 0
|
||||||
|
for _, level in pairs(food.fillLevels or {}) do total = total + (level or 0) end
|
||||||
|
for fillType, level in pairs(food.fillLevels or {}) do
|
||||||
|
if (level or 0) > 0 then
|
||||||
|
seen[fillType] = true
|
||||||
|
addEntry(entries, fillType, level, level + math.max(0, (food.capacity or 0) - total))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, name in ipairs({"STRAW", "WATER", "MILK", "LIQUIDMANURE", "MANURE"}) do
|
||||||
|
local fillType = g_fillTypeManager ~= nil and g_fillTypeManager.getFillTypeIndexByName ~= nil and g_fillTypeManager:getFillTypeIndexByName(name) or nil
|
||||||
|
if fillType == nil and g_fillTypeManager ~= nil then
|
||||||
|
-- test-fakes uden getFillTypeIndexByName: slå navnet op baglæns
|
||||||
|
for index = 1, 64 do
|
||||||
|
if g_fillTypeManager:getFillTypeNameByIndex(index) == name then fillType = index break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if fillType ~= nil and not seen[fillType] and placeable.getHusbandryIsFillTypeSupported ~= nil and placeable:getHusbandryIsFillTypeSupported(fillType) then
|
||||||
|
local level = placeable.getHusbandryFillLevel ~= nil and (placeable:getHusbandryFillLevel(fillType) or 0) or 0
|
||||||
|
if level > 0 then
|
||||||
|
local free = placeable.getHusbandryFreeCapacity ~= nil and (placeable:getHusbandryFreeCapacity(fillType) or 0) or 0
|
||||||
|
addEntry(entries, fillType, level, level + free)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
-- { {name, kind = "factory"|"silo"|"objectStorage"|"husbandry", entries = {{fillType, name, title, level, capacity, isOutput, count}}} }
|
||||||
|
-- kun gårdens bygninger med indhold; sorteret på slags, dernæst navn.
|
||||||
|
function ADSources.getFarmInventory(adEnv, farmId)
|
||||||
|
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
|
||||||
|
local result = {}
|
||||||
|
for _, placeable in pairs(placeables) do
|
||||||
|
if isOwnedBy(placeable, farmId) then
|
||||||
|
local kind, entries = nil, nil
|
||||||
|
if getProductionPoint(placeable) ~= nil then
|
||||||
|
kind, entries = "factory", factoryEntries(placeable)
|
||||||
|
elseif placeable.spec_silo ~= nil then
|
||||||
|
kind, entries = "silo", siloEntries(placeable)
|
||||||
|
elseif placeable.spec_objectStorage ~= nil then
|
||||||
|
kind, entries = "objectStorage", objectStorageEntries(placeable)
|
||||||
|
elseif placeable.getHusbandryFillLevel ~= nil then
|
||||||
|
kind, entries = "husbandry", husbandryEntries(placeable)
|
||||||
|
end
|
||||||
|
if entries ~= nil and #entries > 0 then
|
||||||
|
table.sort(entries, function(a, b)
|
||||||
|
if a.isOutput ~= b.isOutput then return a.isOutput end
|
||||||
|
return tostring(a.title) < tostring(b.title)
|
||||||
|
end)
|
||||||
|
table.insert(result, {name = getPlaceableName(placeable, "?"), kind = kind, entries = entries, placeable = placeable})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local order = {factory = 1, husbandry = 2, objectStorage = 3, silo = 4}
|
||||||
|
table.sort(result, function(a, b)
|
||||||
|
if order[a.kind] ~= order[b.kind] then return order[a.kind] < order[b.kind] end
|
||||||
|
return a.name < b.name
|
||||||
|
end)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
function ADSources.getSourcePlaceables(sources)
|
function ADSources.getSourcePlaceables(sources)
|
||||||
local set = {}
|
local set = {}
|
||||||
for _, source in ipairs(sources or {}) do
|
for _, source in ipairs(sources or {}) do
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ SmartPickupFrame._mt = Class(SmartPickupFrame, TabbedMenuFrameElement)
|
|||||||
SmartPickupFrame.REFRESH_INTERVAL_MS = 1000
|
SmartPickupFrame.REFRESH_INTERVAL_MS = 1000
|
||||||
SmartPickupFrame.LOG_PREFIX = "ADSmartPickup: "
|
SmartPickupFrame.LOG_PREFIX = "ADSmartPickup: "
|
||||||
SmartPickupFrame.STATE_OFF, SmartPickupFrame.STATE_ON = 1, 2
|
SmartPickupFrame.STATE_OFF, SmartPickupFrame.STATE_ON = 1, 2
|
||||||
SmartPickupFrame.TAB_OVERVIEW, SmartPickupFrame.TAB_SUPPLY, SmartPickupFrame.TAB_OUTBOUND = 1, 2, 3
|
SmartPickupFrame.TAB_OVERVIEW, SmartPickupFrame.TAB_SUPPLY, SmartPickupFrame.TAB_OUTBOUND, SmartPickupFrame.TAB_STORAGE = 1, 2, 3, 4
|
||||||
SmartPickupFrame.TAB_NAMES = {"spu_tab_overview", "spu_tab_supply", "spu_tab_outbound"}
|
SmartPickupFrame.TAB_NAMES = {"spu_tab_overview", "spu_tab_supply", "spu_tab_outbound", "spu_tab_storage"}
|
||||||
-- Hver fane viser kun runs af sin egen slags; Oversigt opretter forsynings-runs.
|
-- Hver fane viser kun runs af sin egen slags; Oversigt og Lagre opretter forsynings-runs.
|
||||||
SmartPickupFrame.TAB_KIND = {"supply", "supply", "outbound"}
|
SmartPickupFrame.TAB_KIND = {"supply", "supply", "outbound", "supply"}
|
||||||
SmartPickupFrame.POLICIES = {"off", "store", "sell", "sellElseStore"}
|
SmartPickupFrame.POLICIES = {"off", "store", "sell", "sellElseStore"}
|
||||||
|
|
||||||
function SmartPickupFrame.new(i18n, getAdEnv)
|
function SmartPickupFrame.new(i18n, getAdEnv)
|
||||||
@@ -296,6 +296,7 @@ function SmartPickupFrame:clearRows()
|
|||||||
self.needsRows = {}
|
self.needsRows = {}
|
||||||
self.overviewRows = {}
|
self.overviewRows = {}
|
||||||
self.productRows, self.outboundStatusRows = {}, {}
|
self.productRows, self.outboundStatusRows = {}, {}
|
||||||
|
self.storageRows = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
function SmartPickupFrame:addRow(prefab, data)
|
function SmartPickupFrame:addRow(prefab, data)
|
||||||
@@ -345,6 +346,17 @@ function SmartPickupFrame:rebuild()
|
|||||||
self:clearRows()
|
self:clearRows()
|
||||||
local isOverview = self.currentTab == SmartPickupFrame.TAB_OVERVIEW
|
local isOverview = self.currentTab == SmartPickupFrame.TAB_OVERVIEW
|
||||||
self:setStaticRowsVisible(self.currentTab)
|
self:setStaticRowsVisible(self.currentTab)
|
||||||
|
if self.currentTab == SmartPickupFrame.TAB_STORAGE then
|
||||||
|
if adEnv ~= nil then
|
||||||
|
self:fillStorageRows(adEnv)
|
||||||
|
end
|
||||||
|
self.statusRow = self:addTextRow("")
|
||||||
|
self:colorRows()
|
||||||
|
self.boxLayout:invalidateLayout()
|
||||||
|
self:restoreFocusIfLost()
|
||||||
|
self:refreshLive()
|
||||||
|
return
|
||||||
|
end
|
||||||
if isOverview then
|
if isOverview then
|
||||||
if adEnv ~= nil then
|
if adEnv ~= nil then
|
||||||
self:fillOverviewRows(adEnv)
|
self:fillOverviewRows(adEnv)
|
||||||
@@ -665,6 +677,7 @@ function SmartPickupFrame:refreshLive()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
self:refreshOverview(adEnv)
|
self:refreshOverview(adEnv)
|
||||||
|
self:refreshStorageRows(adEnv)
|
||||||
local isRunning = false
|
local isRunning = false
|
||||||
if run ~= nil then
|
if run ~= nil then
|
||||||
self:syncOptionsFromModel(adEnv, run)
|
self:syncOptionsFromModel(adEnv, run)
|
||||||
|
|||||||
@@ -43,11 +43,9 @@ function SmartPickupFrame:describeProduct(adEnv, product)
|
|||||||
return string.format(self.i18n:getText("spu_product_row"), tostring(product.title), SmartPickupFrame.formatCompact(product.available or 0), price)
|
return string.format(self.i18n:getText("spu_product_row"), tostring(product.title), SmartPickupFrame.formatCompact(product.available or 0), price)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Paller kan ikke lagres i en silo: kun Fra/Salg for pallevarer.
|
-- Paller lagres i et palleskur (objektlager) i stedet for en silo — samme politikker som bulk.
|
||||||
SmartPickupFrame.PALLET_POLICIES = {"off", "sell"}
|
|
||||||
|
|
||||||
function SmartPickupFrame.getPoliciesFor(product)
|
function SmartPickupFrame.getPoliciesFor(product)
|
||||||
return product.isPallet and SmartPickupFrame.PALLET_POLICIES or SmartPickupFrame.POLICIES
|
return SmartPickupFrame.POLICIES
|
||||||
end
|
end
|
||||||
|
|
||||||
function SmartPickupFrame.getPolicyIndex(policies, policy)
|
function SmartPickupFrame.getPolicyIndex(policies, policy)
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
-- Smart Pickup — fanen Lagre
|
||||||
|
-- Alt gården har liggende, pr. bygning: fabrikker (outputs først), stalde, palleskure, siloer.
|
||||||
|
-- Kun bygninger med indhold vises; teksterne opdateres hvert sekund uden genopbygning.
|
||||||
|
-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua).
|
||||||
|
|
||||||
|
-- "Sugar mill (fabrik): Sukker 150.0k/180.0k ▸ · Roer 356.8k/360.0k" (▸ = output)
|
||||||
|
function SmartPickupFrame:describeInventory(entry)
|
||||||
|
local parts = {}
|
||||||
|
for _, item in ipairs(entry.entries) do
|
||||||
|
local amount
|
||||||
|
if item.count ~= nil then
|
||||||
|
amount = string.format(self.i18n:getText("spu_storage_pallets"), item.count)
|
||||||
|
else
|
||||||
|
amount = string.format("%s/%s", SmartPickupFrame.formatCompact(item.level or 0), SmartPickupFrame.formatCompact(item.capacity or 0))
|
||||||
|
end
|
||||||
|
table.insert(parts, string.format("%s %s%s", tostring(item.title), amount, item.isOutput and " ▸" or ""))
|
||||||
|
end
|
||||||
|
local kindText = self.i18n:getText("spu_source_kind_" .. tostring(entry.kind))
|
||||||
|
return string.format("%s (%s): %s", tostring(entry.name), kindText, table.concat(parts, " · "))
|
||||||
|
end
|
||||||
|
|
||||||
|
function SmartPickupFrame:getInventory(adEnv)
|
||||||
|
if ADSources == nil or ADSources.getFarmInventory == nil then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
return ADSources.getFarmInventory(adEnv, g_currentMission:getFarmId())
|
||||||
|
end
|
||||||
|
|
||||||
|
function SmartPickupFrame:fillStorageRows(adEnv)
|
||||||
|
self.storageRows = {}
|
||||||
|
self:addTextRow(self.i18n:getText("spu_section_storage"))
|
||||||
|
local inventory = self:getInventory(adEnv)
|
||||||
|
for _, entry in ipairs(inventory) do
|
||||||
|
table.insert(self.storageRows, {placeable = entry.placeable, title = self:addTextRow(self:describeInventory(entry))})
|
||||||
|
end
|
||||||
|
if #inventory == 0 then
|
||||||
|
self:addTextRow(self.i18n:getText("spu_storage_empty"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function SmartPickupFrame:refreshStorageRows(adEnv)
|
||||||
|
if self.currentTab ~= SmartPickupFrame.TAB_STORAGE or #(self.storageRows or {}) == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local byPlaceable = {}
|
||||||
|
for _, entry in ipairs(self:getInventory(adEnv)) do
|
||||||
|
byPlaceable[entry.placeable] = entry
|
||||||
|
end
|
||||||
|
for _, row in ipairs(self.storageRows) do
|
||||||
|
local entry = byPlaceable[row.placeable]
|
||||||
|
if entry ~= nil then
|
||||||
|
row.title:setText(self:describeInventory(entry))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -60,6 +60,11 @@
|
|||||||
<text name="spu_canCarry" text="kan bære: %s"/>
|
<text name="spu_canCarry" text="kan bære: %s"/>
|
||||||
<text name="spu_tab_supply" text="Forsyning"/>
|
<text name="spu_tab_supply" text="Forsyning"/>
|
||||||
<text name="spu_tab_outbound" text="Udkørsel"/>
|
<text name="spu_tab_outbound" text="Udkørsel"/>
|
||||||
|
<text name="spu_tab_storage" text="Lagre"/>
|
||||||
|
<text name="spu_section_storage" text="— Gårdens beholdning (▸ = fabrikkens output) —"/>
|
||||||
|
<text name="spu_storage_pallets" text="%d paller"/>
|
||||||
|
<text name="spu_storage_empty" text="Ingen bygninger med indhold"/>
|
||||||
|
<text name="spu_source_kind_husbandry" text="stald"/>
|
||||||
<text name="spu_section_run" text="— Run —"/>
|
<text name="spu_section_run" text="— Run —"/>
|
||||||
<text name="spu_section_targets" text="— Mål og varer —"/>
|
<text name="spu_section_targets" text="— Mål og varer —"/>
|
||||||
<text name="spu_section_sources" text="— Kilder (fabrik, silo eller objektlager) —"/>
|
<text name="spu_section_sources" text="— Kilder (fabrik, silo eller objektlager) —"/>
|
||||||
@@ -72,7 +77,7 @@
|
|||||||
<text name="spu_policy_store" text="Lager"/>
|
<text name="spu_policy_store" text="Lager"/>
|
||||||
<text name="spu_policy_sell" text="Salg"/>
|
<text name="spu_policy_sell" text="Salg"/>
|
||||||
<text name="spu_policy_sellElseStore" text="Salg, ellers lager"/>
|
<text name="spu_policy_sellElseStore" text="Salg, ellers lager"/>
|
||||||
<text name="spu_policy_tooltip" text="Lager: kør til en silo når der er et fuldt læs eller lageret er højt. Salg: sælg når prisen er tæt på årets top. Salg, ellers lager: sælg ved god pris, ellers til silo når lageret er højt."/>
|
<text name="spu_policy_tooltip" text="Lager: kør til en silo (paller: et palleskur) når der er et fuldt læs eller lageret er højt. Salg: sælg når prisen er tæt på årets top — men aldrig det gårdens fabrikker og stalde skal bruge det næste døgn. Salg, ellers lager: sælg ved god pris, ellers til lager når lageret er højt."/>
|
||||||
<text name="spu_sellAt" text="Sælg fra (% af årets toppris)"/>
|
<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_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_storeAbove" text="Kør til lager over"/>
|
||||||
|
|||||||
@@ -60,6 +60,11 @@
|
|||||||
<text name="spu_canCarry" text="can carry: %s"/>
|
<text name="spu_canCarry" text="can carry: %s"/>
|
||||||
<text name="spu_tab_supply" text="Supply"/>
|
<text name="spu_tab_supply" text="Supply"/>
|
||||||
<text name="spu_tab_outbound" text="Outbound"/>
|
<text name="spu_tab_outbound" text="Outbound"/>
|
||||||
|
<text name="spu_tab_storage" text="Storage"/>
|
||||||
|
<text name="spu_section_storage" text="— Farm inventory (▸ = production output) —"/>
|
||||||
|
<text name="spu_storage_pallets" text="%d pallets"/>
|
||||||
|
<text name="spu_storage_empty" text="No buildings with contents"/>
|
||||||
|
<text name="spu_source_kind_husbandry" text="barn"/>
|
||||||
<text name="spu_section_run" text="— Run —"/>
|
<text name="spu_section_run" text="— Run —"/>
|
||||||
<text name="spu_section_targets" text="— Targets and goods —"/>
|
<text name="spu_section_targets" text="— Targets and goods —"/>
|
||||||
<text name="spu_section_sources" text="— Sources (factory, silo or object storage) —"/>
|
<text name="spu_section_sources" text="— Sources (factory, silo or object storage) —"/>
|
||||||
@@ -72,7 +77,7 @@
|
|||||||
<text name="spu_policy_store" text="Store"/>
|
<text name="spu_policy_store" text="Store"/>
|
||||||
<text name="spu_policy_sell" text="Sell"/>
|
<text name="spu_policy_sell" text="Sell"/>
|
||||||
<text name="spu_policy_sellElseStore" text="Sell, else store"/>
|
<text name="spu_policy_sellElseStore" text="Sell, else store"/>
|
||||||
<text name="spu_policy_tooltip" text="Store: drive to a silo when there is a full load or the storage is high. Sell: sell when the price is near the yearly top. Sell, else store: sell at a good price, otherwise to a silo when storage is high."/>
|
<text name="spu_policy_tooltip" text="Store: drive to a silo (pallets: a pallet shed) when there is a full load or the storage is high. Sell: sell when the price is near the yearly top — never what the farm's factories and barns need for the next day. Sell, else store: sell at a good price, otherwise to storage when storage is high."/>
|
||||||
<text name="spu_sellAt" text="Sell from (% of yearly top price)"/>
|
<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_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_storeAbove" text="Drive to storage above"/>
|
||||||
|
|||||||
@@ -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.12.0.0</version>
|
<version>1.12.1.0</version>
|
||||||
<title>
|
<title>
|
||||||
<en>AD Smart Pickup</en>
|
<en>AD Smart Pickup</en>
|
||||||
</title>
|
</title>
|
||||||
|
|||||||
@@ -183,6 +183,23 @@ tjek vejnettet`), samme tekst i AD's notifikations-historik, i log.txt (WARN) og
|
|||||||
Traktoren holder ved Wait og kører selv når vejen er koblet på. Advarslen gentages først når
|
Traktoren holder ved Wait og kører selv når vejen er koblet på. Advarslen gentages først når
|
||||||
traktor, station eller vare ændrer sig.
|
traktor, station eller vare ændrer sig.
|
||||||
|
|
||||||
|
**Prisregel (v1.12.1, metode fra Distribution Redux):** pris-% = nuværende periodes faktor ÷ årets
|
||||||
|
højeste faktor i spillets faste sæsonkurve (samme data som prisgrafen) × stationens egen afvigelse
|
||||||
|
(effektiv pris ÷ stationens grundpris nu). Sværhedsgraden falder ud; flade kurver (top/bund < 1,02)
|
||||||
|
regnes som 100 %; stor efterspørgsel giver > 100 %.
|
||||||
|
|
||||||
|
**Reserve (v1.12.1):** før salg fratrækkes det gårdens egne fabrikker og stalde skal bruge det næste
|
||||||
|
døgn (forbrug × 24 t minus det de allerede har, højst deres fri plads; fabrikker = ønskede
|
||||||
|
produktioners inputs, stalde = foder/halm/vand-rate). Der sælges kun når et helt læs kan tages uden
|
||||||
|
at røre reserven. Loggen/UI'et siger fx `SUGARBEET: 196.0k reserveret til TMR-fabrik`. Lager-politik
|
||||||
|
rører ikke reserven (varen bliver på gården).
|
||||||
|
|
||||||
|
**Paller til lager (v1.12.1):** Lager-mål for pallevarer = nærmeste objektlager (palleskur) der tager
|
||||||
|
varen og har plads; AutoDrive læsser UAL-vognen af dér.
|
||||||
|
|
||||||
|
**Fanen Lagre (v1.12.1):** gårdens beholdning pr. bygning (fabrikker med ▸ ved outputs, stalde,
|
||||||
|
palleskure som antal paller, siloer), opdateret hvert sekund.
|
||||||
|
|
||||||
**Opsætning:** Udkørsel-fanen → Nyt run → afkryds kilder → sæt politik pr. vare → Ja på traktorer
|
**Opsætning:** Udkørsel-fanen → Nyt run → afkryds kilder → sæt politik pr. vare → Ja på traktorer
|
||||||
→ Start. AD-tilstand pr. traktor: PickupAndDeliver, 1./2. markør = første kilde (pladsholder,
|
→ Start. AD-tilstand pr. traktor: PickupAndDeliver, 1./2. markør = første kilde (pladsholder,
|
||||||
sættes pr. tur), rotation "kun pålæsning". Runs gemmes i `ADSmartPickup_runs.xml` version 2;
|
sættes pr. tur), rotation "kun pålæsning". Runs gemmes i `ADSmartPickup_runs.xml` version 2;
|
||||||
|
|||||||
@@ -394,6 +394,25 @@ ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
|
|||||||
ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
|
ADOutbound.beforeLoadUpdate(adEnv, task16, 100)
|
||||||
check("O16 næste spawn via fyldniveau", sources[3].spawnCalls, 2)
|
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
|
-- O10: install wrapper: fejl i hook stopper ikke AD
|
||||||
local calls = {}
|
local calls = {}
|
||||||
adEnv.LoadAtDestinationTask.update = function(self, dt) calls.update = (calls.update or 0) + 1 end
|
adEnv.LoadAtDestinationTask.update = function(self, dt) calls.update = (calls.update or 0) + 1 end
|
||||||
|
|||||||
@@ -109,5 +109,26 @@ check("P12 storageHigh", e.storageHigh, true)
|
|||||||
check("P12 fullLoad", e.fullLoad, true)
|
check("P12 fullLoad", e.fullLoad, true)
|
||||||
check("P12 priceOk", e.priceOk, 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"))
|
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)
|
||||||
|
|||||||
+16
-4
@@ -42,17 +42,29 @@ local station = {
|
|||||||
}
|
}
|
||||||
local quote = ADPrices.getQuote(station, SUGAR)
|
local quote = ADPrices.getQuote(station, SUGAR)
|
||||||
check("P4 pris", quote.price, 470)
|
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 procent", quote.percent, 75)
|
||||||
check("P4 er salgssted", ADPrices.isSellingStation(station), true)
|
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 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)
|
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 bare = {getEffectiveFillTypePrice = function() return 470 end}
|
||||||
local quote2 = ADPrices.getQuote(bare, SUGAR)
|
local quote2 = ADPrices.getQuote(bare, SUGAR)
|
||||||
check("P5 top fra pricePerLiter", quote2.top, 500)
|
check("P5 procent = kurven alene", quote2.percent, 80)
|
||||||
check("P5 procent", quote2.percent, 94)
|
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)
|
check("P5 fejlende station -> nil", ADPrices.getQuote({getEffectiveFillTypePrice = function() error("x") end}, SUGAR), nil)
|
||||||
|
|
||||||
-- P6: bedste tilbud = højeste procent, dernæst pris
|
-- P6: bedste tilbud = højeste procent, dernæst pris
|
||||||
|
|||||||
@@ -199,5 +199,82 @@ check("S9 fabrik foretrækkes frem for objektlager i nærheden", ADSources.ident
|
|||||||
local excluded = ADSources.getSourcePlaceables({source1, source2})
|
local excluded = ADSources.getSourcePlaceables({source1, source2})
|
||||||
check("S8 udelukkede placeables", excluded[factory] and excluded[silo], true)
|
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"))
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user