diff --git a/FS25_ADSmartPickup/adFields.lua b/FS25_ADSmartPickup/adFields.lua index dff1bc1..c1588c2 100644 --- a/FS25_ADSmartPickup/adFields.lua +++ b/FS25_ADSmartPickup/adFields.lua @@ -33,6 +33,42 @@ function ADFields.buildMowable(mowerConverter, tedderConverter, fillTypeName) return result 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() if ADFields.mowable ~= nil then return ADFields.mowable end local ok, result = pcall(function() @@ -47,6 +83,80 @@ function ADFields.getMowable() return ADFields.mowable 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 farmland = field.farmland or (field.getFarmland ~= nil and field:getFarmland()) or nil 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) if okR and readyPixels then readySum = readySum + readyPixels 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 local function findMarker(adEnv, fieldId, x, z) @@ -145,8 +271,22 @@ local function scanField(adEnv, field, fieldId) end if mowable ~= nil then info.mowable, info.windrow, info.hay = true, mowable.windrow, mowable.hay - info.readyRatio = readyRatio(info.fruitIndex, points) 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 local marker, distance = findMarker(adEnv, fieldId, info.x, info.z) info.markerId = marker ~= nil and marker.id or nil diff --git a/tests/test_adFields.lua b/tests/test_adFields.lua index 0338292..d67cbf2 100644 --- a/tests/test_adFields.lua +++ b/tests/test_adFields.lua @@ -29,5 +29,37 @@ check("F3 rug (halm) ikke", mowable[2], nil) check("F3 kløver", mowable[3].hay, 31) 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")) os.exit(failures == 0 and 0 or 1)