feat(marker): marker — art (slå/høst/bar), vækstfase, jordniveauer (gødning/kalk/pløj/ukrudt) og spillets indstillinger

This commit is contained in:
2026-09-25 23:53:20 +02:00
parent 9be60d4b1c
commit 793352c4c4
2 changed files with 174 additions and 2 deletions
+142 -2
View File
@@ -33,6 +33,42 @@ function ADFields.buildMowable(mowerConverter, tedderConverter, fillTypeName)
return result return result
end end
-- Markens art: slåafgrøde, høstafgrøde eller bar (ingen afgrøde). Vokser = afgrøde der ikke er klar endnu.
function ADFields.classify(fruitIndex, isMowable, readyRatio)
if fruitIndex == nil then return "bare", false end
local kind = isMowable and "mowable" or "harvest"
return kind, (readyRatio or 0) < 0.9
end
-- Vækststadie -> fase ud fra afgrødens grænser (FS25-felter hedder minHarvestingGrowthState eller
-- minHarvestGrowthState afhængigt af version).
function ADFields.growthPhase(state, fruit)
if state == nil or fruit == nil then return nil end
if state == 0 then return "none" end
if fruit.cutState ~= nil and fruit.cutState ~= 0 and state == fruit.cutState then return "cut" end
if fruit.witheredState ~= nil and state == fruit.witheredState then return "withered" end
local minReady = fruit.minHarvestingGrowthState or fruit.minHarvestGrowthState
local maxReady = fruit.maxHarvestingGrowthState or fruit.maxHarvestGrowthState or minReady
if minReady ~= nil and state >= minReady and state <= (maxReady or minReady) then return "ready" end
if minReady ~= nil and state < minReady then return "growing" end
return "cut"
end
-- Vokser marken? Fasen vinder; ukendt fase -> der er voksende afgrøde (getFruitArea) og den er ikke klar.
function ADFields.isGrowing(phase, anyPixels, readyRatio)
if phase ~= nil then return phase == "growing" end
return (anyPixels or 0) > 0 and (readyRatio or 0) < 0.9
end
-- Jordens behov (kalk/pløjning) ud fra gennemsnitsniveauer og spillets indstillinger (kalk/pløjning kan
-- være slået fra i savegamet). Ukendt niveau = intet behov.
function ADFields.soilNeeds(levels, settings)
return {
lime = settings.limeRequired and levels.lime ~= nil and levels.lime < 1 or false,
plow = settings.plowingRequired and levels.plowLevel ~= nil and levels.plowLevel < 1 or false,
}
end
function ADFields.getMowable() function ADFields.getMowable()
if ADFields.mowable ~= nil then return ADFields.mowable end if ADFields.mowable ~= nil then return ADFields.mowable end
local ok, result = pcall(function() local ok, result = pcall(function()
@@ -47,6 +83,80 @@ function ADFields.getMowable()
return ADFields.mowable return ADFields.mowable
end end
-- ---- density-maps (samme læsning som AI-Bonden/Arbejdsseddel, bevist i spillet) ----
ADFields.densityCache = {}
local function readDensity(channel, x, z)
if channel == nil or g_currentMission == nil or g_currentMission.fieldGroundSystem == nil then return nil end
local cached = ADFields.densityCache[channel]
if cached == nil then
local ok, mapId, first, num = pcall(function() return g_currentMission.fieldGroundSystem:getDensityMapData(channel) end)
if not ok or mapId == nil then ADFields.densityCache[channel] = false; return nil end
cached = {modifier = DensityMapModifier.new(mapId, first, num, g_currentMission.terrainRootNode)}
ADFields.densityCache[channel] = cached
elseif cached == false then
return nil
end
local value
pcall(function()
cached.modifier:setParallelogramWorldCoords(x - 0.5, z - 0.5, 1, 0, 0, 1, DensityCoordType.POINT_VECTOR_VECTOR)
local sum, area = cached.modifier:executeGet()
if area ~= nil and area > 0 then value = sum / area end
end)
return value
end
local function readWeed(x, z)
local weedSystem = g_currentMission ~= nil and g_currentMission.weedSystem or nil
if weedSystem == nil or weedSystem.densityMap == nil then return nil end
if ADFields.weedModifier == nil then
local ok, modifier = pcall(DensityMapModifier.new, weedSystem.densityMap, weedSystem.firstChannel or 0, weedSystem.numChannels or 4, g_currentMission.terrainRootNode)
ADFields.weedModifier = ok and modifier or false
end
if ADFields.weedModifier == false then return nil end
local value
pcall(function()
ADFields.weedModifier:setParallelogramWorldCoords(x - 0.5, z - 0.5, 1, 0, 0, 1, DensityCoordType.POINT_VECTOR_VECTOR)
local sum, area = ADFields.weedModifier:executeGet()
if area ~= nil and area > 0 then value = sum / area end
end)
return value
end
local function average(points, reader, channel)
local sum, count = 0, 0
for _, p in ipairs(points) do
local value = reader(channel, p.x, p.z)
if value ~= nil then sum, count = sum + value, count + 1 end
end
return count > 0 and sum / count or nil
end
local function missionSettings()
local info = g_currentMission ~= nil and g_currentMission.missionInfo or {}
local function flag(value) return value == nil or value == true end
return {
limeRequired = flag(info.limeRequired),
plowingRequired = flag(info.plowingRequiredEnabled),
weedsEnabled = flag(info.weedsEnabled),
}
end
-- Jord og pleje: gødning (og max), kalk, pløjning, ukrudt — gennemsnit over prøvepunkterne.
local function readSoil(info)
if type(FieldDensityMap) ~= "table" or #info.points == 0 then return end
local settings = missionSettings()
info.fert = average(info.points, readDensity, FieldDensityMap.SPRAY_LEVEL)
local okMax, maxValue = pcall(function() return g_currentMission.fieldGroundSystem:getMaxValue(FieldDensityMap.SPRAY_LEVEL) end)
info.fertMax = okMax and type(maxValue) == "number" and maxValue or 2
info.lime = average(info.points, readDensity, FieldDensityMap.LIME_LEVEL)
info.plowLevel = average(info.points, readDensity, FieldDensityMap.PLOW_LEVEL)
if settings.weedsEnabled then
info.weed = average(info.points, function(_, x, z) return readWeed(x, z) end, nil)
end
info.needs = ADFields.soilNeeds(info, settings)
if not settings.limeRequired then info.lime = nil end
end
local function fieldOwner(field) local function fieldOwner(field)
local farmland = field.farmland or (field.getFarmland ~= nil and field:getFarmland()) or nil local farmland = field.farmland or (field.getFarmland ~= nil and field:getFarmland()) or nil
if farmland == nil then return nil end if farmland == nil then return nil end
@@ -111,7 +221,23 @@ local function readyRatio(fruitIndex, points)
local okR, _, readyPixels = pcall(FSDensityMapUtil.getFruitArea, fruitIndex, p.x - 2, p.z - 2, p.x + 2, p.z, p.x, p.z + 2, false, false) local okR, _, readyPixels = pcall(FSDensityMapUtil.getFruitArea, fruitIndex, p.x - 2, p.z - 2, p.x + 2, p.z, p.x, p.z + 2, false, false)
if okR and readyPixels then readySum = readySum + readyPixels end if okR and readyPixels then readySum = readySum + readyPixels end
end end
return anySum > 0 and readySum / anySum or 0 return anySum > 0 and readySum / anySum or 0, anySum
end
-- Vækststadiet på prøvepunkterne (flertal): getFieldDataAtWorldPosition's anden returværdi.
local function growthStateAt(points)
local votes, best, bestVotes = {}, nil, 0
for _, p in ipairs(points) do
local ok, _, state = pcall(function()
local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, p.x, 1, p.z)
return FSDensityMapUtil.getFieldDataAtWorldPosition(p.x, y, p.z)
end)
if ok and type(state) == "number" then
votes[state] = (votes[state] or 0) + 1
if votes[state] > bestVotes then best, bestVotes = state, votes[state] end
end
end
return best
end end
local function findMarker(adEnv, fieldId, x, z) local function findMarker(adEnv, fieldId, x, z)
@@ -145,8 +271,22 @@ local function scanField(adEnv, field, fieldId)
end end
if mowable ~= nil then if mowable ~= nil then
info.mowable, info.windrow, info.hay = true, mowable.windrow, mowable.hay info.mowable, info.windrow, info.hay = true, mowable.windrow, mowable.hay
info.readyRatio = readyRatio(info.fruitIndex, points)
end end
local fruit = nil
if info.fruitIndex ~= nil then
info.readyRatio, info.anyPixels = readyRatio(info.fruitIndex, points)
fruit = g_fruitTypeManager:getFruitTypeByIndex(info.fruitIndex)
info.fruitName = fruit ~= nil and fruit.name or nil
info.harvestFillType = fruit ~= nil and fruit.fillType ~= nil and fruit.fillType.index or nil
end
info.kind = ADFields.classify(info.fruitIndex, info.mowable, info.readyRatio)
info.phase = fruit ~= nil and ADFields.growthPhase(growthStateAt(points), fruit) or nil
info.growing = info.fruitIndex ~= nil and ADFields.isGrowing(info.phase, info.anyPixels, info.readyRatio)
-- høstet stub eller visnet afgrøde er i praksis en bar mark (klargøring/såning)
if info.kind == "harvest" and (info.phase == "cut" or info.phase == "withered" or info.phase == "none") then
info.kind = "bare"
end
readSoil(info)
if info.x ~= nil then if info.x ~= nil then
local marker, distance = findMarker(adEnv, fieldId, info.x, info.z) local marker, distance = findMarker(adEnv, fieldId, info.x, info.z)
info.markerId = marker ~= nil and marker.id or nil info.markerId = marker ~= nil and marker.id or nil
+32
View File
@@ -29,5 +29,37 @@ check("F3 rug (halm) ikke", mowable[2], nil)
check("F3 kløver", mowable[3].hay, 31) check("F3 kløver", mowable[3].hay, 31)
check("F3 bønnehalm -> halm ikke", mowable[4], nil) check("F3 bønnehalm -> halm ikke", mowable[4], nil)
-- F4: markens art og om den vokser
local kind, growing = F.classify(3, false, 0.4)
check("F4 høstafgrøde", kind, "harvest"); check("F4 vokser", growing, true)
check("F4 klar høst vokser ikke", select(2, F.classify(3, false, 0.95)), false)
check("F4 slåafgrøde", (F.classify(1, true, 0.2)), "mowable")
check("F4 ingen afgrøde = bar", (F.classify(nil, false, 0)), "bare")
check("F4 bar vokser ikke", select(2, F.classify(nil, false, 0)), false)
-- F5: jordens behov ud fra gennemsnit og spillets indstillinger
local needs = F.soilNeeds({lime = 0.2, plowLevel = 0}, {limeRequired = true, plowingRequired = true})
check("F5 kalk", needs.lime, true); check("F5 pløj", needs.plow, true)
local off = F.soilNeeds({lime = 0, plowLevel = 0}, {limeRequired = false, plowingRequired = false})
check("F5 kalk slået fra i spillet", off.lime, false); check("F5 pløjning slået fra", off.plow, false)
check("F5 kalket jord", F.soilNeeds({lime = 2, plowLevel = 1}, {limeRequired = true, plowingRequired = true}).lime, false)
check("F5 ukendt niveau = intet behov", F.soilNeeds({}, {limeRequired = true, plowingRequired = true}).plow, false)
-- F6: vækststadie -> fase ud fra afgrødens egne grænser
local wheat = {minHarvestingGrowthState = 6, maxHarvestingGrowthState = 7, cutState = 9, witheredState = 8}
check("F6 intet", F.growthPhase(0, wheat), "none")
check("F6 spirer/vokser", F.growthPhase(3, wheat), "growing")
check("F6 klar", F.growthPhase(6, wheat), "ready")
check("F6 visnet", F.growthPhase(8, wheat), "withered")
check("F6 høstet stub", F.growthPhase(9, wheat), "cut")
check("F6 ukendt stadie", F.growthPhase(nil, wheat), nil)
check("F6 FS25-navne (minHarvestGrowthState)", F.growthPhase(6, {minHarvestGrowthState = 5, maxHarvestGrowthState = 6, cutState = 8}), "ready")
-- F7: vokser = fase growing; ukendt fase -> voksende pixels og ikke klar
check("F7 fase vinder", F.isGrowing("cut", 100, 0.1), false)
check("F7 fase vokser", F.isGrowing("growing", 0, 0), true)
check("F7 reserve: voksende pixels", F.isGrowing(nil, 50, 0.2), true)
check("F7 reserve: ingen pixels (stub)", F.isGrowing(nil, 0, 0), false)
check("F7 reserve: klar", F.isGrowing(nil, 50, 0.95), false)
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)