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:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user