229 lines
11 KiB
Lua
229 lines
11 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
|
|
|
|
-- En læsefejl i dette kald: data.partial = true (også når advarslen allerede er logget), så kæden ikke
|
|
-- slukker eller stopper noget på et ufuldstændigt billede af gården.
|
|
local function failed(data, key, fmt, ...)
|
|
data.partial = true
|
|
ADChainData.warnOnce(key, fmt, ...)
|
|
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)
|
|
failed(data, "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 failed(data, "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 failed(data, "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 failed(data, "rates", "staldforbrug kunne ikke læses: %s", tostring(err)) end
|
|
end
|
|
|
|
-- Markscanningen har ikke nået alle marker endnu (efter indlæsning ~én mark pr. 2 s): marklisten er ufuldstændig,
|
|
-- så udbud undervurderes -> fieldsIncomplete (kæden rører ingen marker) og partial (intet slukkes/stoppes).
|
|
local function readFields(data)
|
|
if ADFields.isScanComplete ~= nil and not ADFields.isScanComplete() then
|
|
data.partial, data.fieldsIncomplete = true, true
|
|
end
|
|
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
|
|
failed(data, "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).
|
|
-- data.partial = true når noget ikke kunne læses i dette kald; data.fieldsIncomplete = true mens markscanningen
|
|
-- ikke har nået alle marker.
|
|
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
|