Files
fs25-adsmartpickup/FS25_ADSmartPickup/adChainData.lua
T

215 lines
10 KiB
Lua

-- AD Chain Data (læse-adapter)
-- Omsætter spillets tilstand (gårdens fabrikker, stalde, afgrøder, lager og marker) til de rene data som
-- ADChainModel og ADCropPlan regner på. Varer og afgrøder er NAVNE; mængder er liter pr. måned (spillets
-- rå rater: én 24-timers dag pr. måned, som lagerfanen). Al spiladgang sker inde i funktionerne, så filen kan
-- dofile's uden spillet. Hver bygning/mark/afgrøde læses i pcall; en fejl logges én gang pr. nøgle.
ADChainData = {}
ADChainData.LOG_PREFIX = "ADSmartPickup: kæde: "
ADChainData.PERMANENT = {GRAPE = true, GRAPEB = true, OLIVE = true, POPLAR = true}
ADChainData.warned = {}
-- Varige afgrøder (vin, oliven, poppel) skiftes aldrig; mod-vine hedder også GRAPE…
function ADChainData.isPermanent(fruitName)
if type(fruitName) ~= "string" or fruitName == "" then return false end
return ADChainData.PERMANENT[fruitName] == true or fruitName:sub(1, 5) == "GRAPE"
end
function ADChainData.recipeKey(placeableUid, productionId)
return tostring(placeableUid) .. ":" .. tostring(productionId)
end
-- Samme omregning som lagerfanen (adSources addFactoryRates): cyklusser pr. 24-timers dag = pr. måned.
function ADChainData.cyclesPerMonth(production)
return (production.cyclesPerMinute or ((production.cyclesPerHour or 0) / 60)) * 1440
end
function ADChainData.warnOnce(key, fmt, ...)
if ADChainData.warned[key] then return end
ADChainData.warned[key] = true
if Logging ~= nil and Logging.warning ~= nil then Logging.warning(ADChainData.LOG_PREFIX .. fmt, ...) end
end
-- ---- hjælpere ----
-- Navn for et fillType-indeks; husker indeks og titel til GUI og vognvalg.
local function nameResolver(data)
return function(index)
if index == nil or g_fillTypeManager == nil then return nil end
local name = g_fillTypeManager:getFillTypeNameByIndex(index)
if name == nil then return nil end
if data.fillIndex[name] == nil then
data.fillIndex[name] = index
local desc = g_fillTypeManager.getFillTypeByIndex ~= nil and g_fillTypeManager:getFillTypeByIndex(index) or nil
data.fillTitles[name] = tostring(desc ~= nil and desc.title or name)
end
return name
end
end
-- {{type, amount}} -> {[navn] = mængde pr. cyklus}
local function amountsByName(list, nameOf)
local result = {}
for _, entry in ipairs(list or {}) do
local name = nameOf(entry.type)
if name ~= nil and (entry.amount or 0) > 0 then result[name] = (result[name] or 0) + entry.amount end
end
return result
end
local function placeableId(placeable)
if placeable.uniqueId ~= nil then return tostring(placeable.uniqueId) end
return ADBuildings ~= nil and ADBuildings.getId ~= nil and ADBuildings.getId(placeable) or nil
end
-- Spillerens eget navn (ADNames) før spillets; samme rækkefølge som adSources/adBuildings.
local function placeableName(placeable, fallback)
local own = ADNames ~= nil and ADNames.forPlaceable ~= nil and ADNames.forPlaceable(placeable) or nil
if own ~= nil then return own end
if placeable.getName ~= nil then
local ok, name = pcall(placeable.getName, placeable)
if ok and name ~= nil then return tostring(name) end
end
return fallback
end
local function isEnabled(point, production)
if point.getIsProductionEnabled ~= nil then return point:getIsProductionEnabled(production.id) == true end
for _, active in ipairs(point.activeProductions or {}) do
if active == production then return true end
end
return false
end
-- ---- læsning pr. del ----
-- En fabriks produktioner -> opskrifter + punkter (samles først, så en fejl midtvejs ikke efterlader halve).
local function readFactory(placeable, point, byChain, nameOf)
local uid = placeableId(placeable)
if uid == nil then return {}, {} end
local buildingName = placeableName(placeable, uid)
local recipes, points = {}, {}
for _, production in ipairs(point.productions or {}) do
local key = ADChainData.recipeKey(uid, production.id)
table.insert(recipes, {key = key, name = tostring(production.name or production.id), building = uid,
buildingName = buildingName, enabled = isEnabled(point, production), byChain = (byChain or {})[key] == true,
cyclesPerMonth = ADChainData.cyclesPerMonth(production),
inputs = amountsByName(production.inputs, nameOf), outputs = amountsByName(production.outputs, nameOf)})
points[key] = {point = point, productionId = production.id, placeable = placeable}
end
return recipes, points
end
local function readHerd(placeable, nameOf)
local uid = placeableId(placeable)
local outputs = {}
for index, perMonth in pairs(ADSources.getHusbandryOutputs(placeable) or {}) do
local name = nameOf(index)
if name ~= nil then outputs[name] = (outputs[name] or 0) + perMonth end
end
if uid == nil or next(outputs) == nil then return nil end
return {building = uid, buildingName = placeableName(placeable, uid), outputs = outputs}
end
local function readPlaceables(data, farmId, byChain, nameOf)
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
for index, placeable in pairs(placeables) do
local ok, err = pcall(function()
if not ADPrices.isOwnedBy(placeable, farmId) then return end
local point = placeable.spec_productionPoint ~= nil and placeable.spec_productionPoint.productionPoint or nil
if point ~= nil then
local recipes, points = readFactory(placeable, point, byChain, nameOf)
for _, recipe in ipairs(recipes) do table.insert(data.recipes, recipe) end
for key, entry in pairs(points) do data.points[key] = entry end
elseif placeable.spec_husbandry ~= nil or placeable.getHusbandryFillLevel ~= nil then
local herd = readHerd(placeable, nameOf)
if herd ~= nil then table.insert(data.husbandry, herd) end
end
end)
if not ok then
local key = tostring(placeable ~= nil and placeable.uniqueId or index)
ADChainData.warnOnce("placeable:" .. key, "bygning %s kunne ikke læses: %s", key, tostring(err))
end
end
table.sort(data.recipes, function(a, b) return a.key < b.key end)
end
local function readCrops(data, nameOf)
local fruits = g_fruitTypeManager ~= nil and g_fruitTypeManager.fruitTypes or {}
for _, fruit in ipairs(fruits) do
if fruit.fillType ~= nil and fruit.allowsSeeding ~= false and fruit.name ~= nil then
local ok, err = pcall(function()
local fillName = fruit.fillType.name or nameOf(fruit.fillType.index)
if fillName == nil then return end
if fruit.fillType.index ~= nil then nameOf(fruit.fillType.index) end
data.crops[fillName] = fruit.name
data.yields[fruit.name] = (tonumber(fruit.literPerSqm) or 0) * 10000
local quote = fruit.fillType.index ~= nil and ADSources.getMarketQuote(fruit.fillType.index) or nil
data.prices[fruit.name] = quote ~= nil and quote.price or 0
end)
if not ok then ADChainData.warnOnce("fruit:" .. tostring(fruit.name), "afgrøde %s kunne ikke læses: %s", tostring(fruit.name), tostring(err)) end
end
end
end
local function readStock(data, adEnv, farmId, nameOf)
local ok, err = pcall(function()
for index, liters in pairs(ADStorageStats.farmTotals(ADSources.getFarmInventory(adEnv, farmId))) do
local fruit = data.crops[nameOf(index) or ""]
if fruit ~= nil then data.stock[fruit] = (data.stock[fruit] or 0) + liters end
end
end)
if not ok then ADChainData.warnOnce("stock", "lager kunne ikke læses: %s", tostring(err)) end
end
-- Afgrøder gården selv bruger: input i en tændt produktion eller foder/halm i en stald.
local function readUsedFruits(data, adEnv, farmId, nameOf)
for _, recipe in ipairs(data.recipes) do
if recipe.enabled then
for name in pairs(recipe.inputs) do
if data.crops[name] ~= nil then data.usedFruits[data.crops[name]] = true end
end
end
end
local ok, err = pcall(function()
for index, rate in pairs(ADSources.getFarmRates(adEnv, farmId) or {}) do
local fruit = data.crops[nameOf(index) or ""]
if fruit ~= nil and (rate.consumption or 0) > 0 then data.usedFruits[fruit] = true end
end
end)
if not ok then ADChainData.warnOnce("rates", "staldforbrug kunne ikke læses: %s", tostring(err)) end
end
local function readFields(data)
for _, info in ipairs(ADFields.list() or {}) do
local ok, entry = pcall(function()
local field = ADFieldJobs.getField(info.id)
local plan = field.plan or {}
local fruit = info.growing and info.fruitName or field.lastFruit
return {id = info.id, areaHa = ADFields.polygonAreaHa(info.verts), fruit = fruit,
targetFruit = plan.targetFruit, targetSource = plan.targetSource, chainFree = plan.chainFree == true,
permanent = ADChainData.isPermanent(info.fruitName or field.lastFruit), excluded = info.kind == "mowable"}
end)
if ok then
table.insert(data.fields, entry)
else
ADChainData.warnOnce("field:" .. tostring(info.id), "mark %s kunne ikke læses: %s", tostring(info.id), tostring(entry))
end
end
end
-- ---- samlet ----
-- byChain = {[recipeKey] = true} for produktioner kæden selv har tændt (ikke rødder).
function ADChainData.collect(adEnv, farmId, byChain)
local data = {recipes = {}, husbandry = {}, crops = {}, stock = {}, fields = {}, yields = {}, prices = {},
usedFruits = {}, points = {}, fillTitles = {}, fillIndex = {}}
local nameOf = nameResolver(data)
readPlaceables(data, farmId, byChain, nameOf)
readCrops(data, nameOf)
readStock(data, adEnv, farmId, nameOf)
readUsedFruits(data, adEnv, farmId, nameOf)
readFields(data)
return data
end