diff --git a/FS25_ADSmartPickup/adFieldSiloConfig.lua b/FS25_ADSmartPickup/adFieldSiloConfig.lua index 158dcf7..30dfab9 100644 --- a/FS25_ADSmartPickup/adFieldSiloConfig.lua +++ b/FS25_ADSmartPickup/adFieldSiloConfig.lua @@ -7,11 +7,15 @@ ADFieldSiloConfig.MAX_VEHICLES = 10 ADFieldSiloConfig.DEFAULT_MAX_VEHICLES = 2 ADFieldSiloConfig.MIN_LITERS_CHOICES = {5000, 10000, 25000, 50000} ADFieldSiloConfig.DEFAULT_MIN_LITERS = 10000 +-- foderreserve: staldenes forbrug i så mange døgn bliver på gården, før overskud køres til fabrik +ADFieldSiloConfig.RESERVE_DAYS_CHOICES = {3, 7, 14, 30} +ADFieldSiloConfig.DEFAULT_RESERVE_DAYS = 7 local function copy(cfg) local ids = {} for index, id in ipairs(cfg.storeIds or {}) do ids[index] = id end - return {enabled = cfg.enabled == true, storeIds = ids, maxVehicles = cfg.maxVehicles, minLiters = cfg.minLiters} + return {enabled = cfg.enabled == true, storeIds = ids, maxVehicles = cfg.maxVehicles, minLiters = cfg.minLiters, + toProduction = cfg.toProduction == true, reserveDays = cfg.reserveDays} end local function clampVehicles(value) @@ -19,19 +23,28 @@ local function clampVehicles(value) return math.max(1, math.min(ADFieldSiloConfig.MAX_VEHICLES, math.floor(value))) end -local function snapLiters(value) +local function snap(value, choices, default) value = tonumber(value) - if value == nil then return ADFieldSiloConfig.DEFAULT_MIN_LITERS end + if value == nil then return default end local best, bestDiff = nil, nil - for _, choice in ipairs(ADFieldSiloConfig.MIN_LITERS_CHOICES) do + for _, choice in ipairs(choices) do local diff = math.abs(choice - value) if bestDiff == nil or diff < bestDiff then best, bestDiff = choice, diff end end return best end +local function snapLiters(value) + return snap(value, ADFieldSiloConfig.MIN_LITERS_CHOICES, ADFieldSiloConfig.DEFAULT_MIN_LITERS) +end + +local function snapDays(value) + return snap(value, ADFieldSiloConfig.RESERVE_DAYS_CHOICES, ADFieldSiloConfig.DEFAULT_RESERVE_DAYS) +end + function ADFieldSiloConfig.new() - return {enabled = false, storeIds = {}, maxVehicles = ADFieldSiloConfig.DEFAULT_MAX_VEHICLES, minLiters = ADFieldSiloConfig.DEFAULT_MIN_LITERS} + return {enabled = false, storeIds = {}, maxVehicles = ADFieldSiloConfig.DEFAULT_MAX_VEHICLES, minLiters = ADFieldSiloConfig.DEFAULT_MIN_LITERS, + toProduction = false, reserveDays = ADFieldSiloConfig.DEFAULT_RESERVE_DAYS} end function ADFieldSiloConfig.with(cfg, changes) @@ -39,6 +52,8 @@ function ADFieldSiloConfig.with(cfg, changes) if changes.enabled ~= nil then new.enabled = changes.enabled == true end if changes.maxVehicles ~= nil then new.maxVehicles = clampVehicles(changes.maxVehicles) end if changes.minLiters ~= nil then new.minLiters = snapLiters(changes.minLiters) end + if changes.toProduction ~= nil then new.toProduction = changes.toProduction == true end + if changes.reserveDays ~= nil then new.reserveDays = snapDays(changes.reserveDays) end return new end @@ -62,7 +77,8 @@ end function ADFieldSiloConfig.toRecord(cfg) return {enabled = cfg.enabled and 1 or 0, stores = table.concat(cfg.storeIds or {}, " "), - maxVehicles = cfg.maxVehicles, minLiters = cfg.minLiters} + maxVehicles = cfg.maxVehicles, minLiters = cfg.minLiters, toProduction = cfg.toProduction and 1 or 0, + reserveDays = cfg.reserveDays} end function ADFieldSiloConfig.fromRecord(record) @@ -72,5 +88,7 @@ function ADFieldSiloConfig.fromRecord(record) cfg.enabled = tonumber(record.enabled) == 1 cfg.maxVehicles = clampVehicles(record.maxVehicles) cfg.minLiters = snapLiters(record.minLiters) + cfg.toProduction = tonumber(record.toProduction) == 1 + cfg.reserveDays = snapDays(record.reserveDays) return cfg end diff --git a/FS25_ADSmartPickup/adFieldSiloPlanner.lua b/FS25_ADSmartPickup/adFieldSiloPlanner.lua index 4cc5b74..8825692 100644 --- a/FS25_ADSmartPickup/adFieldSiloPlanner.lua +++ b/FS25_ADSmartPickup/adFieldSiloPlanner.lua @@ -1,6 +1,6 @@ -- AD Field Silo Planner -- Ren planlægning for marksilo-flowet: hvilke (silo, vare) skal hjem, hvor mange lånte vogne sendes, og til --- hvilket gårdlager. Ingen spil-kald; input muteres ikke. +-- hvilket gårdlager eller (med "kør til produktion") hvilken fabrik. Ingen spil-kald; input muteres ikke. ADFieldSiloPlanner = {} @@ -24,12 +24,16 @@ local function anyStoreTakes(stores, fillType, minLiters, reserved) return false end --- Nærmeste lager med plads til hele turen; ellers det med mest plads (over tærsklen). -local function chooseStore(stores, silo, fillType, liters, minLiters, reserved) +local function isProduction(store) + return store ~= nil and store.kind == "production" +end + +-- Nærmeste lager af den ønskede slags (fabrik/gård) med plads til hele turen; ellers det med mest plads. +local function chooseStore(stores, silo, fillType, liters, minLiters, reserved, production) local best, bestD, most, mostFree = nil, nil, nil, nil for _, store in ipairs(stores or {}) do local free = freeIn(store, fillType, reserved) - if store.markerId ~= nil and free >= minLiters then + if store.markerId ~= nil and free >= minLiters and isProduction(store) == production then local d = dist2(store, silo) if free >= liters and (bestD == nil or d < bestD) then best, bestD = store, d end if mostFree == nil or free > mostFree then most, mostFree = store, free end @@ -55,19 +59,34 @@ local function nearestVehicle(vehicles, used, silo, fillType) return best end +-- Foder først: mangler gården stadig foderreserve for varen (reserve − beholdning − læs på vej hjem), køres til +-- gårdlager; ellers til fabrik. Findes kun den ene slags, bruges den. +local function pickStore(input, farmIncoming, task, liters, minLiters, reserved) + local fillType = task.fillType + local short = ((input.feedReserve or {})[fillType] or 0) - ((input.farmStock or {})[fillType] or 0) - (farmIncoming[fillType] or 0) + local wantProduction = short <= 0 + return chooseStore(input.stores, task.silo, fillType, liters, minLiters, reserved, wantProduction) + or chooseStore(input.stores, task.silo, fillType, liters, minLiters, reserved, not wantProduction) +end + function ADFieldSiloPlanner.plan(input) local minLiters = input.minLiters or 10000 - local reserved, onWay = {}, {} + local storesById = {} + for _, store in ipairs(input.stores or {}) do storesById[store.id] = store end + local reserved, onWay, farmIncoming = {}, {}, {} for _, loan in ipairs(input.loans or {}) do local k = key(loan.siloId, loan.fillType) onWay[k] = (onWay[k] or 0) + (loan.liters or 0) + if loan.storeId ~= nil and not isProduction(storesById[loan.storeId]) then + farmIncoming[loan.fillType] = (farmIncoming[loan.fillType] or 0) + (loan.liters or 0) + end -- læs på vej til et lager optager pladsen, til de er aflæsset (også i senere runder) if loan.storeId ~= nil then local reserveKey = loan.storeId .. ":" .. tostring(loan.fillType) reserved[reserveKey] = (reserved[reserveKey] or 0) + (loan.liters or 0) end end - local status, tasks = {}, {} + local status, tasks, statusByKey = {}, {}, {} for _, silo in ipairs(input.silos or {}) do for fillType, liters in pairs(silo.stock or {}) do if (liters or 0) >= minLiters then @@ -85,6 +104,7 @@ function ADFieldSiloPlanner.plan(input) if rest >= minLiters then table.insert(tasks, {silo = silo, fillType = fillType, rest = rest}) end end table.insert(status, entry) + statusByKey[k] = entry end end end @@ -104,8 +124,13 @@ function ADFieldSiloPlanner.plan(input) local vehicle = nearestVehicle(input.vehicles, used, task.silo, task.fillType) if vehicle == nil then break end local liters = math.min(vehicle.capacity, task.rest) - local store = chooseStore(input.stores, task.silo, task.fillType, liters, minLiters, reserved) + local store = pickStore(input, farmIncoming, task, liters, minLiters, reserved) if store == nil then break end + if isProduction(store) then + statusByKey[key(task.silo.id, task.fillType)].toProduction = true + else + farmIncoming[task.fillType] = (farmIncoming[task.fillType] or 0) + liters + end used[vehicle.id] = true local reserveKey = store.id .. ":" .. tostring(task.fillType) reserved[reserveKey] = (reserved[reserveKey] or 0) + liters diff --git a/FS25_ADSmartPickup/adFieldSilos.lua b/FS25_ADSmartPickup/adFieldSilos.lua index 215902f..4659ba4 100644 --- a/FS25_ADSmartPickup/adFieldSilos.lua +++ b/FS25_ADSmartPickup/adFieldSilos.lua @@ -1,5 +1,6 @@ -- AD Field Silos -- Marksilo-flowet i spillet: samler marksiloer, gårdlagre og ledige vogne, lader ADFieldSiloPlanner vælge, +-- med "kør til produktion" går overskud ud over gårdens foderreserve til gårdens fabrikker; -- låner vogne (AutoDrive "Hent og aflever", én tur, parkering bagefter) og frigiver dem igen. -- Lån ligger kun i hukommelsen; opsætningen gemmes i ADSmartPickup_fieldsilos.xml. @@ -142,6 +143,53 @@ local function nearStore(stores, x, z) return false end +-- Fabrikkens input-varer (dem aflæsningen tager imod) med fri plads i dens lager. +local function factoryFree(placeable) + local spec = placeable ~= nil and placeable.spec_productionPoint or nil + local point = spec ~= nil and spec.productionPoint or nil + local storage = point ~= nil and point.storage or nil + if storage == nil or storage.getFreeCapacity == nil then return {} end + local inputs = {} + for fillType in pairs(point.inputFillTypeIds or {}) do inputs[fillType] = true end + for _, production in ipairs(point.productions or {}) do + for _, input in ipairs(production.inputs or {}) do inputs[input.type] = true end + end + local free = {} + for fillType in pairs(inputs) do free[fillType] = storage:getFreeCapacity(fillType) or 0 end + return free +end + +-- "Kør til produktion": gårdens fabrikker med aflæsningsmarkør bliver mål ved siden af gårdlagrene. +local function addFactories(stores, buildings) + for _, building in ipairs(buildings) do + local markerId = building.kind == "factory" and firstMarker(building, "unload") or nil + if markerId ~= nil then + local x, z = position(building.placeable ~= nil and building.placeable.rootNode or nil) + table.insert(stores, {id = building.id, name = building.name, kind = "production", markerId = markerId, + x = x, z = z, free = factoryFree(building.placeable)}) + end + end +end + +-- Foderreserve (staldenes døgnforbrug × døgn) og gårdlagrenes beholdning pr. vare. +function ADFieldSilos.collectFeed(stores, byId, fid, reserveDays) + local reserve, stock = {}, {} + local rates = ADSources ~= nil and ADSources.getFarmRates ~= nil and ADSources.getFarmRates(ADFieldSilos.adEnv, fid, true) or {} + for fillType, rate in pairs(rates) do reserve[fillType] = (rate.consumption or 0) * reserveDays end + local counted = {} -- Farma-siloer i samme klynge deler lager: tæl hvert lager én gang + for _, store in ipairs(stores) do + local building = byId[store.id] + local spec = store.kind ~= "production" and building ~= nil and building.placeable ~= nil and building.placeable.spec_silo or nil + for _, storage in pairs(spec ~= nil and spec.storages or {}) do + if not counted[storage] then + counted[storage] = true + for fillType, level in pairs(storage.fillLevels or {}) do stock[fillType] = (stock[fillType] or 0) + (level or 0) end + end + end + end + return reserve, stock +end + -- Gårdlagre fra opsætningen + marksiloer (alle markers marksiloer, uden dubletter og uden gårdlagre). function ADFieldSilos.collectBuildings(adEnv) local fid = farmId() @@ -159,6 +207,7 @@ function ADFieldSilos.collectBuildings(adEnv) x = x, z = z, free = siloFree(building.placeable)}) end end + if ADFieldSilos.config.toProduction then addFactories(stores, ADBuildings.list(adEnv, fid)) end local silos, seen = {}, runBuildingIds() for id in pairs(farmCluster(allSilos, storeIds)) do seen[id] = true end for _, info in ipairs(ADFields.list()) do @@ -175,7 +224,7 @@ function ADFieldSilos.collectBuildings(adEnv) end end end - return silos, stores + return silos, stores, byId end local function trailerInfo(adEnv, vehicle) @@ -335,10 +384,18 @@ end function ADFieldSilos.planNow(adEnv) local cfg = ADFieldSilos.config - local silos, stores = ADFieldSilos.collectBuildings(adEnv) + local silos, stores, byId = ADFieldSilos.collectBuildings(adEnv) local vehicles, vehiclesById = ADFieldSilos.collectVehicles(adEnv) + local feedReserve, farmStock = nil, nil + if cfg.toProduction then + local ok, reserve, stock = pcall(ADFieldSilos.collectFeed, stores, byId, farmId(), cfg.reserveDays) + if ok then feedReserve, farmStock = reserve, stock + else Logging.warning(ADFieldSilos.LOG_PREFIX .. "foderreserve kunne ikke beregnes: %s", tostring(reserve)) end + end local plan = ADFieldSiloPlanner.plan({silos = silos, stores = stores, loans = loanList(), vehicles = vehicles, - paused = currentPaused(), maxVehicles = cfg.maxVehicles, minLiters = cfg.minLiters}) + paused = currentPaused(), maxVehicles = cfg.maxVehicles, minLiters = cfg.minLiters, + feedReserve = feedReserve, farmStock = farmStock}) + ADFieldSilos.lastFeed = {reserve = feedReserve, stock = farmStock} ADFieldSilos.lastPlan = plan local paused = ADFieldJobs ~= nil and ADFieldJobs.state ~= nil and ADFieldJobs.state.paused if not cfg.enabled or paused then return plan end @@ -395,6 +452,8 @@ function ADFieldSilos.save() xmlFile:setString(ADFieldSilos.ROOT .. "#stores", record.stores) xmlFile:setInt(ADFieldSilos.ROOT .. "#maxVehicles", record.maxVehicles) xmlFile:setInt(ADFieldSilos.ROOT .. "#minLiters", record.minLiters) + xmlFile:setInt(ADFieldSilos.ROOT .. "#toProduction", record.toProduction) + xmlFile:setInt(ADFieldSilos.ROOT .. "#reserveDays", record.reserveDays) xmlFile:save() xmlFile:delete() return true @@ -416,6 +475,8 @@ function ADFieldSilos.load() stores = xmlFile:getString(ADFieldSilos.ROOT .. "#stores"), maxVehicles = xmlFile:getInt(ADFieldSilos.ROOT .. "#maxVehicles"), minLiters = xmlFile:getInt(ADFieldSilos.ROOT .. "#minLiters"), + toProduction = xmlFile:getInt(ADFieldSilos.ROOT .. "#toProduction"), + reserveDays = xmlFile:getInt(ADFieldSilos.ROOT .. "#reserveDays"), }) xmlFile:delete() end diff --git a/FS25_ADSmartPickup/adSources.lua b/FS25_ADSmartPickup/adSources.lua index 96c25fa..8cf9476 100644 --- a/FS25_ADSmartPickup/adSources.lua +++ b/FS25_ADSmartPickup/adSources.lua @@ -942,16 +942,17 @@ local function addAnimalProduction(rates, placeable) end -- {[fillType] = {consumption, production}} i liter pr. døgn for gårdens fabrikker og stalde. -function ADSources.getFarmRates(adEnv, farmId) +-- feedOnly: kun staldenes forbrug (foder, halm, vand) — marksilo-flowets foderreserve. +function ADSources.getFarmRates(adEnv, farmId, feedOnly) local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {} local rates = {} for _, placeable in pairs(placeables) do if isOwnedBy(placeable, farmId) then if getProductionPoint(placeable) ~= nil then - addFactoryRates(rates, placeable) + if not feedOnly then addFactoryRates(rates, placeable) end elseif placeable.getHusbandryFillLevel ~= nil then addHusbandryRates(rates, placeable) - pcall(addAnimalProduction, rates, placeable) + if not feedOnly then pcall(addAnimalProduction, rates, placeable) end end end end diff --git a/FS25_ADSmartPickup/gui/SmartPickupFieldSiloTab.lua b/FS25_ADSmartPickup/gui/SmartPickupFieldSiloTab.lua index 3e4bd6a..ca2f990 100644 --- a/FS25_ADSmartPickup/gui/SmartPickupFieldSiloTab.lua +++ b/FS25_ADSmartPickup/gui/SmartPickupFieldSiloTab.lua @@ -1,8 +1,9 @@ -- Smart Pickup — fanen Marksiloer --- Opsætning (til/fra, maks. vogne, mindste mængde, gårdlagre) og status for marksilo-flowet. +-- Opsætning (til/fra, maks. vogne, mindste mængde, kør til produktion + foderreserve, gårdlagre) og status for marksilo-flowet. -- Klik går til onClickFieldSiloRow; reglerne ligger i ADFieldSilos/ADFieldSiloPlanner. -SmartPickupFrame.FIELDSILO_ROW_KINDS = {fsEnabled = true, fsMaxVehicles = true, fsMinLiters = true, fsStore = true} +SmartPickupFrame.FIELDSILO_ROW_KINDS = {fsEnabled = true, fsMaxVehicles = true, fsMinLiters = true, fsStore = true, + fsToProduction = true, fsReserveDays = true} local function numberTexts(from, to) local texts = {} @@ -16,6 +17,12 @@ local function literTexts() return texts end +local function dayTexts(i18n) + local texts = {} + for _, days in ipairs(ADFieldSiloConfig.RESERVE_DAYS_CHOICES) do table.insert(texts, string.format(i18n:getText("spu_fs_days"), days)) end + return texts +end + local function indexOf(list, value) for index, entry in ipairs(list) do if entry == value then return index end end return 1 @@ -39,6 +46,10 @@ function SmartPickupFrame:fillFieldSiloRows(adEnv) self:addToggleRow("fsEnabled", i18n:getText("spu_fs_enabled"), onOff, cfg.enabled) self:addFieldSiloChoiceRow("fsMaxVehicles", i18n:getText("spu_fs_maxVehicles"), numberTexts(1, ADFieldSiloConfig.MAX_VEHICLES), cfg.maxVehicles) self:addFieldSiloChoiceRow("fsMinLiters", i18n:getText("spu_fs_minLiters"), literTexts(), indexOf(ADFieldSiloConfig.MIN_LITERS_CHOICES, cfg.minLiters)) + self:addToggleRow("fsToProduction", i18n:getText("spu_fs_toProduction"), onOff, cfg.toProduction) + if cfg.toProduction then + self:addFieldSiloChoiceRow("fsReserveDays", i18n:getText("spu_fs_reserveDays"), dayTexts(i18n), indexOf(ADFieldSiloConfig.RESERVE_DAYS_CHOICES, cfg.reserveDays)) + end local plan = ADFieldSilos.lastPlan local waiting = 0 @@ -61,6 +72,7 @@ function SmartPickupFrame:fillFieldSiloRows(adEnv) local title = desc ~= nil and (desc.title or desc.name) or tostring(entry.fillType) local state = i18n:getText("spu_fs_state_" .. entry.state) if entry.state == "noFarmStore" then state = string.format(state, title) end + if entry.toProduction then state = state .. " " .. i18n:getText("spu_fs_toFactory") end self:addCellRow(self.fieldPrefab, {tostring(entry.siloName), title, string.format("%d", math.floor(entry.liters)), string.format("%d", math.floor(entry.onWay)), state, ""}) end @@ -85,6 +97,10 @@ function SmartPickupFrame:onClickFieldSiloRow(state, data) cfg = ADFieldSiloConfig.with(cfg, {maxVehicles = state}) elseif data.kind == "fsMinLiters" then cfg = ADFieldSiloConfig.with(cfg, {minLiters = ADFieldSiloConfig.MIN_LITERS_CHOICES[state]}) + elseif data.kind == "fsToProduction" then + cfg = ADFieldSiloConfig.with(cfg, {toProduction = state == SmartPickupFrame.STATE_ON}) + elseif data.kind == "fsReserveDays" then + cfg = ADFieldSiloConfig.with(cfg, {reserveDays = ADFieldSiloConfig.RESERVE_DAYS_CHOICES[state]}) elseif data.kind == "fsStore" then cfg = ADFieldSiloConfig.toggleStore(cfg, data.buildingId) end diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml index ed7c20a..09a66a8 100644 --- a/FS25_ADSmartPickup/l10n/l10n_da.xml +++ b/FS25_ADSmartPickup/l10n/l10n_da.xml @@ -497,5 +497,9 @@ + + + + diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml index fa754be..30ccc00 100644 --- a/FS25_ADSmartPickup/l10n/l10n_en.xml +++ b/FS25_ADSmartPickup/l10n/l10n_en.xml @@ -497,5 +497,9 @@ + + + + diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 956117b..d4bbede 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.43.1.0 + 1.44.0.0 <en>AD Smart Pickup</en> diff --git a/tests/test_adFieldSiloConfig.lua b/tests/test_adFieldSiloConfig.lua index 4958667..1a1c0fe 100644 --- a/tests/test_adFieldSiloConfig.lua +++ b/tests/test_adFieldSiloConfig.lua @@ -29,5 +29,18 @@ check("record: x", back.maxVehicles, 4) check("record: tærskel", back.minLiters, 25000) check("tom record -> standard", C.fromRecord(nil).maxVehicles, 2) check("record uden tal -> standard tærskel", C.fromRecord({enabled = 1}).minLiters, 10000) +-- kør til produktion + foderreserve +check("standard: ikke til produktion", cfg.toProduction, false) +check("standard foderreserve 7 døgn", cfg.reserveDays, 7) +check("reserve snappes (10 -> 7)", C.with(cfg, {reserveDays = 10}).reserveDays, 7) +check("reserve 40 -> 30", C.with(cfg, {reserveDays = 40}).reserveDays, 30) +local prod = C.with(C.toggleStore(cfg, "a"), {toProduction = true, reserveDays = 14}) +check("with bevarer lagre", C.hasStore(prod, "a"), true) +check("with muterer ikke (produktion)", cfg.toProduction, false) +local prodBack = C.fromRecord(C.toRecord(prod)) +check("record: til produktion", prodBack.toProduction, true) +check("record: foderreserve", prodBack.reserveDays, 14) +check("gammel record uden felter -> standard", C.fromRecord({enabled = 1}).reserveDays, 7) +check("toggleStore bevarer produktion", C.toggleStore(prod, "b").toProduction, true) print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1) diff --git a/tests/test_adFieldSiloPlanner.lua b/tests/test_adFieldSiloPlanner.lua index 9440ba6..8a5effa 100644 --- a/tests/test_adFieldSiloPlanner.lua +++ b/tests/test_adFieldSiloPlanner.lua @@ -84,5 +84,32 @@ r = P.plan(base({stores = {store("s", 400, {[WHEAT] = 30000}), store("t", 900, { loans = {{siloId = "A", fillType = WHEAT, liters = 30000, storeId = "s"}}, vehicles = {vehicle("v1", 400, 50000)}})) check("I1 næste vogn til det andet lager", r.dispatch[1] ~= nil and r.dispatch[1].storeId, "t") +-- kør til produktion: foder først, overskud til fabrik +local function factory(id, x, free) return {id = id, kind = "production", markerId = id .. "m", x = x, z = 0, free = free} end +local farmAndFactory = {store("farm", 5000, {[WHEAT] = 1000000}), factory("mill", 100, {[WHEAT] = 1000000})} +r = P.plan(base({stores = farmAndFactory, silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {vehicle("v1", 400, 50000)}, + feedReserve = {[WHEAT] = 100000}, farmStock = {[WHEAT] = 20000}})) +check("P1 reserve ikke dækket -> gårdlager selv om fabrik er nærmere", r.dispatch[1].storeId, "farm") +r = P.plan(base({stores = farmAndFactory, silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {vehicle("v1", 400, 50000)}, + feedReserve = {[WHEAT] = 100000}, farmStock = {[WHEAT] = 150000}})) +check("P2 reserve dækket -> fabrik", r.dispatch[1].storeId, "mill") +r = P.plan(base({stores = farmAndFactory, silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {vehicle("v1", 400, 50000)}})) +check("P3 ikke foder (ingen reserve) -> fabrik", r.dispatch[1].storeId, "mill") +r = P.plan(base({stores = farmAndFactory, silos = {silo("A", 500, {[WHEAT] = 100000})}, vehicles = {vehicle("v1", 400, 50000), vehicle("v2", 400, 50000)}, + feedReserve = {[WHEAT] = 100000}, farmStock = {[WHEAT] = 70000}})) +check("P4 første læs dækker resten af reserven -> gård", r.dispatch[1].storeId, "farm") +check("P4 andet læs er overskud -> fabrik", r.dispatch[2].storeId, "mill") +r = P.plan(base({stores = farmAndFactory, silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {vehicle("v1", 400, 50000)}, + feedReserve = {[WHEAT] = 100000}, farmStock = {[WHEAT] = 60000}, loans = {{siloId = "B", fillType = WHEAT, liters = 50000, storeId = "farm"}}})) +check("P5 læs på vej til gården tæller med i reserven -> fabrik", r.dispatch[1] ~= nil and r.dispatch[1].storeId, "mill") +r = P.plan(base({stores = {store("farm", 5000, {[WHEAT] = 1000000}), factory("mill", 100, {[CANOLA] = 1000000})}, silos = {silo("A", 500, {[WHEAT] = 90000})}, + vehicles = {vehicle("v1", 400, 50000)}, farmStock = {[WHEAT] = 999999}})) +check("P6 ingen fabrik tager varen -> gårdlager", r.dispatch[1].storeId, "farm") +r = P.plan(base({stores = {store("farm", 5000, {[CANOLA] = 1000000}), factory("mill", 100, {[WHEAT] = 1000000})}, silos = {silo("A", 500, {[WHEAT] = 90000})}, + vehicles = {vehicle("v1", 400, 50000)}, feedReserve = {[WHEAT] = 100000}})) +check("P7 reserve mangler men ingen gård tager varen -> fabrik", r.dispatch[1].storeId, "mill") +check("P7 status ok (ikke noFarmStore)", r.status[1].state, "ok") +check("P7 status viser destination", r.status[1].toProduction, true) + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)