diff --git a/FS25_ADSmartPickup/adCropPlan.lua b/FS25_ADSmartPickup/adCropPlan.lua new file mode 100644 index 0000000..fc81616 --- /dev/null +++ b/FS25_ADSmartPickup/adCropPlan.lua @@ -0,0 +1,76 @@ +-- AD Crop Plan (ren logik) +-- Mangel pr. afgrøde over 12 måneder (lager + forventet høst mod kædens behov) og valg af marker der skal skifte +-- målafgrøde: "fri til kæden" -> auto, ellers forslag. Brugerens egen målafgrøde ændres aldrig automatisk. + +ADCropPlan = {} +ADCropPlan.HORIZON_MONTHS = 12 +ADCropPlan.MIN_SHARE = 0.10 +ADCropPlan.MIN_HA = 0.5 + +function ADCropPlan.plannedFruit(field) + if field.targetFruit ~= nil and field.targetFruit ~= "" then return field.targetFruit end + return field.fruit +end + +function ADCropPlan.shortages(demand, stock, fields, yields) + local list = {} + for fruit, perMonth in pairs(demand or {}) do + local perHa = (yields or {})[fruit] or 0 + if perHa > 0 and (perMonth or 0) > 0 then + local need = perMonth * ADCropPlan.HORIZON_MONTHS + local supply = (stock or {})[fruit] or 0 + for _, field in ipairs(fields or {}) do + if ADCropPlan.plannedFruit(field) == fruit then supply = supply + (field.areaHa or 0) * perHa end + end + local missing = need - supply + local missingHa = missing / perHa + if missing > need * ADCropPlan.MIN_SHARE and missingHa > ADCropPlan.MIN_HA then + table.insert(list, {fruit = fruit, needLiters = need, supplyLiters = supply, missingLiters = missing, missingHa = missingHa}) + end + end + end + table.sort(list, function(a, b) + if a.missingHa ~= b.missingHa then return a.missingHa > b.missingHa end + return a.fruit < b.fruit + end) + return list +end + +local function isCandidate(field, used, dismissedFruit, fruit) + if field.permanent or field.excluded then return false end + local planned = ADCropPlan.plannedFruit(field) + if planned ~= nil and planned ~= "" and used[planned] then return false end + return dismissedFruit ~= fruit +end + +function ADCropPlan.choose(shortages, fields, usedFruits, yields, prices, dismissed) + local actions, taken = {}, {} + local used = usedFruits or {} + for _, shortage in ipairs(shortages or {}) do + local remaining = shortage.missingHa + local candidates = {} + for _, field in ipairs(fields or {}) do + if not taken[field.id] and isCandidate(field, used, (dismissed or {})[field.id], shortage.fruit) then + local planned = ADCropPlan.plannedFruit(field) or "" + local value = ((yields or {})[planned] or 0) * ((prices or {})[planned] or 0) + table.insert(candidates, {field = field, value = value}) + end + end + table.sort(candidates, function(a, b) + if a.value ~= b.value then return a.value < b.value end + local da, db = math.abs((a.field.areaHa or 0) - remaining), math.abs((b.field.areaHa or 0) - remaining) + if da ~= db then return da < db end + return a.field.id < b.field.id + end) + for _, candidate in ipairs(candidates) do + if remaining <= 0 then break end + local field = candidate.field + local auto = field.chainFree == true and field.targetSource ~= "user" + table.insert(actions, {fieldId = field.id, from = ADCropPlan.plannedFruit(field) or "", to = shortage.fruit, + hectares = field.areaHa or 0, reason = shortage.fruit, mode = auto and "auto" or "suggest"}) + taken[field.id] = true + remaining = remaining - (field.areaHa or 0) + end + end + return actions +end diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 8a16652..664eb7a 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -56,6 +56,7 @@ +