diff --git a/FS25_ADSmartPickup/adFieldJobs.lua b/FS25_ADSmartPickup/adFieldJobs.lua index 04dd800..462c70b 100644 --- a/FS25_ADSmartPickup/adFieldJobs.lua +++ b/FS25_ADSmartPickup/adFieldJobs.lua @@ -8,7 +8,6 @@ ADFieldJobs = {} ADFieldJobs.LOG_PREFIX = "ADSmartPickup: " ADFieldJobs.TICK_MS = 1000 -- én mark scannes hvert andet sekund; passive marker (manuelle, uden mål) vurderes i ti portioner -ADFieldJobs.SCAN_EVERY_TICKS = 2 ADFieldJobs.PASSIVE_SLICES = 10 -- tidsmåling: tager et tick mere end dette, skrives det i loggen (højst én gang pr. minut) ADFieldJobs.SLOW_TICK_MS = 3 @@ -269,9 +268,12 @@ local function stepField(adEnv, info, env) end end +-- Realtid i ms: netGetTime (bruges overalt i FS), ellers getTimeSec. local function clockMs() - local ok, seconds = pcall(getTimeSec) - return ok and type(seconds) == "number" and seconds * 1000 or nil + local ok, ms = pcall(function() return netGetTime() end) + if ok and type(ms) == "number" then return ms end + local okSec, seconds = pcall(function() return getTimeSec() end) + return okSec and type(seconds) == "number" and seconds * 1000 or nil end -- Et langsomt tick (> SLOW_TICK_MS) skrives i loggen med fordeling, højst én gang pr. minut. @@ -286,15 +288,17 @@ end function ADFieldJobs.tick(adEnv, dt) ADFieldJobs.now = ADFieldJobs.now + dt ADFieldJobs.timer = ADFieldJobs.timer + dt + if ADFieldJobs.loaded then + -- markscanningen kører et lille stykke HVERT frame (coroutine), aldrig en hel mark i ét frame + local okScan, errScan = pcall(ADFields.scanStep, adEnv, g_currentMission:getFarmId(), ADFieldJobs.now) + if not okScan then Logging.warning(ADFieldJobs.LOG_PREFIX .. "markscanning: %s", tostring(errScan)) end + end if ADFieldJobs.timer < ADFieldJobs.TICK_MS then return end ADFieldJobs.timer = 0 ADFieldJobs.tickCount = ADFieldJobs.tickCount + 1 if not ADFieldJobs.loaded then ADFieldJobs.load() end local t0 = clockMs() - if ADFieldJobs.tickCount % ADFieldJobs.SCAN_EVERY_TICKS == 0 then - ADFields.scanNext(adEnv, g_currentMission:getFarmId(), ADFieldJobs.now) - end - local t1 = clockMs() + local t1 = t0 for _, assignment in pairs(ADFieldJobs.state.assignments) do local ok, err = pcall(ADFieldWork.follow, adEnv, assignment) if not ok then diff --git a/FS25_ADSmartPickup/adFields.lua b/FS25_ADSmartPickup/adFields.lua index 80a9873..2333ab1 100644 --- a/FS25_ADSmartPickup/adFields.lua +++ b/FS25_ADSmartPickup/adFields.lua @@ -127,15 +127,6 @@ local function readWeed(x, z) 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 @@ -146,22 +137,6 @@ local function missionSettings() } 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 @@ -176,75 +151,6 @@ local function isOnField(x, z) return FSDensityMapUtil.getFieldDataAtWorldPosition(x, y, z) and true or false end --- Centroid + halvvejs mod op til 8 hjørner; kun punkter der stadig er mark (bygninger fjerner areal). -local function samplePoints(field) - local verts = {} - for _, node in ipairs(field.polygonPoints or {}) do - if type(node) == "number" then - local x, _, z = getWorldTranslation(node) - table.insert(verts, {x = x, z = z}) - end - end - local cx, cz = field.posX, field.posZ - if (cx == nil or cz == nil) and #verts > 0 then - cx, cz = 0, 0 - for _, v in ipairs(verts) do cx, cz = cx + v.x, cz + v.z end - cx, cz = cx / #verts, cz / #verts - end - if cx == nil then return {}, verts end - local candidates = {{x = cx, z = cz}} - local step = math.max(1, math.floor(#verts / 8)) - for index = 1, #verts, step do - table.insert(candidates, {x = (cx + verts[index].x) / 2, z = (cz + verts[index].z) / 2}) - end - local points = {} - for _, p in ipairs(candidates) do - local ok, onField = pcall(isOnField, p.x, p.z) - if ok and onField then table.insert(points, p) end - end - return points, verts -end - -local function fruitAt(points) - local votes, best, bestVotes = {}, nil, 0 - for _, p in ipairs(points) do - local ok, index = pcall(FSDensityMapUtil.getFruitTypeIndexAtWorldPos, p.x, p.z) - if ok and type(index) == "number" and index > 0 then - votes[index] = (votes[index] or 0) + 1 - if votes[index] > bestVotes then best, bestVotes = index, votes[index] end - end - end - return best -end - --- Andel af prøvefladen i høstvinduet (samme kald som Arbejdsseddel: any = true,true; klar = false,false). -local function readyRatio(fruitIndex, points) - local anySum, readySum = 0, 0 - for _, p in ipairs(points) do - local okA, _, anyPixels = pcall(FSDensityMapUtil.getFruitArea, fruitIndex, p.x - 2, p.z - 2, p.x + 2, p.z, p.x, p.z + 2, true, true) - if okA and anyPixels then anySum = anySum + anyPixels end - 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, 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) local best, bestDistance = nil, math.huge for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do @@ -262,87 +168,240 @@ local function findMarker(adEnv, fieldId, x, z) return nil, best ~= nil and bestDistance or nil end -local function scanField(adEnv, field, fieldId) - local points, verts = samplePoints(field) - local info = {id = fieldId, field = field, points = points, verts = verts, mowable = false, readyRatio = 0} - info.x, info.z = field.posX, field.posZ - if (info.x == nil or info.z == nil) and points[1] ~= nil then info.x, info.z = points[1].x, points[1].z end - info.fruitIndex = #points > 0 and fruitAt(points) or nil - local mowable = info.fruitIndex ~= nil and ADFields.getMowable()[info.fruitIndex] or nil - if info.fruitIndex ~= nil then - local fruit = g_fruitTypeManager:getFruitTypeByIndex(info.fruitIndex) - local fillType = fruit ~= nil and fruit.fillType or nil - info.cropTitle = fillType ~= nil and fillType.title or (fruit ~= nil and fruit.name) or "?" +-- ---- scanning som arbejdskø ---- +-- En marks scanning er en kø af små opgaver (ét prøvepunkt pr. opgave), og scanStep kører én opgave pr. +-- frame. In-game 26/9 gav en hel mark i ét frame et hak (flere opslag gennemgår alle kortets ~40 +-- afgrødetyper pr. punkt). Coroutines findes ikke i FS25 (Giants fjernede dem), derfor en kø. + +-- (eksplicit hale: # er udefineret på en tabel med huller, og udførte opgaver nulstilles) +local function push(scan, task) + scan.tail = (scan.tail or 0) + 1 + scan.queue[scan.tail] = task +end + +local function vote(votes, key) + votes[key] = (votes[key] or 0) + 1 +end + +local function winner(votes) + local best, bestVotes = nil, 0 + for key, count in pairs(votes) do + if count > bestVotes then best, bestVotes = key, count end end + return best +end + +local function newScan(adEnv, entry) + local field, fieldId = entry.field, entry.id + local scan = {id = fieldId, queue = {}, head = 1, tail = 0, points = {}, fruitVotes = {}, growthVotes = {}, + anySum = 0, readySum = 0, soil = {fert = {0, 0}, lime = {0, 0}, plowLevel = {0, 0}, weed = {0, 0}}} + local info = {id = fieldId, field = field, points = scan.points, verts = {}, mowable = false, readyRatio = 0} + scan.info = info + -- 1) polygon og kandidatpunkter (centroid + halvvejs mod op til 8 hjørner) + push(scan, function() + local verts = {} + for _, node in ipairs(field.polygonPoints or {}) do + if type(node) == "number" then + local x, _, z = getWorldTranslation(node) + table.insert(verts, {x = x, z = z}) + end + end + info.verts = verts + local cx, cz = field.posX, field.posZ + if (cx == nil or cz == nil) and #verts > 0 then + cx, cz = 0, 0 + for _, v in ipairs(verts) do cx, cz = cx + v.x, cz + v.z end + cx, cz = cx / #verts, cz / #verts + end + info.x, info.z = cx, cz + if cx == nil then return end + local candidates = {{x = cx, z = cz}} + local step = math.max(1, math.floor(#verts / 8)) + for index = 1, #verts, step do + table.insert(candidates, {x = (cx + verts[index].x) / 2, z = (cz + verts[index].z) / 2}) + end + -- 2) én opgave pr. kandidat: er punktet stadig mark? (bygninger fjerner areal) + for _, candidate in ipairs(candidates) do + push(scan, function() + local ok, onField = pcall(isOnField, candidate.x, candidate.z) + if ok and onField then table.insert(scan.points, candidate) end + end) + end + -- 3) afgrøde pr. punkt (efter alle kandidater) + push(scan, function() + for _, point in ipairs(scan.points) do + push(scan, function() + local ok, index = pcall(FSDensityMapUtil.getFruitTypeIndexAtWorldPos, point.x, point.z) + if ok and type(index) == "number" and index > 0 then vote(scan.fruitVotes, index) end + end) + end + push(scan, function() ADFields.scanPerFruit(scan) end) + end) + end) + return scan +end + +-- Når afgrøden kendes: klar-andel, vækststadie og jord pr. punkt (én opgave pr. punkt). +function ADFields.scanPerFruit(scan) + local info = scan.info + info.fruitIndex = winner(scan.fruitVotes) + local fruitIndex = info.fruitIndex + local settings = missionSettings() + scan.settings = settings + for _, point in ipairs(scan.points) do + push(scan, function() + local x, z = point.x, point.z + if fruitIndex ~= nil then + local okA, _, anyPixels = pcall(FSDensityMapUtil.getFruitArea, fruitIndex, x - 2, z - 2, x + 2, z, x, z + 2, true, true) + if okA and anyPixels then scan.anySum = scan.anySum + anyPixels end + local okR, _, readyPixels = pcall(FSDensityMapUtil.getFruitArea, fruitIndex, x - 2, z - 2, x + 2, z, x, z + 2, false, false) + if okR and readyPixels then scan.readySum = scan.readySum + readyPixels end + local okG, _, state = pcall(function() + local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 1, z) + return FSDensityMapUtil.getFieldDataAtWorldPosition(x, y, z) + end) + if okG and type(state) == "number" then vote(scan.growthVotes, state) end + end + end) + push(scan, function() + if type(FieldDensityMap) ~= "table" then return end + local x, z = point.x, point.z + local readings = { + fert = readDensity(FieldDensityMap.SPRAY_LEVEL, x, z), + lime = readDensity(FieldDensityMap.LIME_LEVEL, x, z), + plowLevel = readDensity(FieldDensityMap.PLOW_LEVEL, x, z), + weed = settings.weedsEnabled and readWeed(x, z) or nil, + } + for key, value in pairs(readings) do + if value ~= nil then + scan.soil[key][1] = scan.soil[key][1] + value + scan.soil[key][2] = scan.soil[key][2] + 1 + end + end + end) + end +end + +-- Samler resultatet når køen er tom. +local function finishScan(adEnv, scan) + local info = scan.info + local mowable = info.fruitIndex ~= nil and ADFields.getMowable()[info.fruitIndex] or nil if mowable ~= nil then info.mowable, info.windrow, info.hay = true, mowable.windrow, mowable.hay end local fruit = nil if info.fruitIndex ~= nil then - info.readyRatio, info.anyPixels = readyRatio(info.fruitIndex, points) + info.readyRatio = scan.anySum > 0 and scan.readySum / scan.anySum or 0 + info.anyPixels = scan.anySum 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 + local fillType = fruit ~= nil and fruit.fillType or nil + info.cropTitle = fillType ~= nil and fillType.title or (fruit ~= nil and fruit.name) or "?" end info.kind = ADFields.classify(info.fruitIndex, info.mowable, info.readyRatio) - info.phase = fruit ~= nil and ADFields.growthPhase(growthStateAt(points), fruit) or nil + info.phase = fruit ~= nil and ADFields.growthPhase(winner(scan.growthVotes), 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) - local previous = ADFields.cache[fieldId] + local settings = scan.settings or missionSettings() + for key, sum in pairs(scan.soil) do + info[key] = sum[2] > 0 and sum[1] / sum[2] or nil + end + 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.needs = ADFields.soilNeeds(info, settings) + if not settings.limeRequired then info.lime = nil end + local previous = ADFields.cache[info.id] if previous ~= nil and previous.markerCheckedAt ~= nil and ADFields.now - previous.markerCheckedAt < ADFields.MARKER_REFRESH_MS then info.markerId, info.markerName, info.markerDistance, info.markerCheckedAt = previous.markerId, previous.markerName, previous.markerDistance, previous.markerCheckedAt elseif info.x ~= nil then - local marker, distance = findMarker(adEnv, fieldId, info.x, info.z) + local marker, distance = findMarker(adEnv, info.id, info.x, info.z) info.markerId = marker ~= nil and marker.id or nil info.markerName = marker ~= nil and marker.name or nil info.markerDistance = distance info.markerCheckedAt = ADFields.now end - return info -end - -local function ownedFields(farmId) - local result = {} - local all = g_fieldManager ~= nil and (g_fieldManager:getFields() or g_fieldManager.fields) or {} - for _, field in pairs(all) do - local id = field.getId ~= nil and field:getId() or field.fieldId - if id ~= nil and fieldOwner(field) == farmId then - table.insert(result, {id = id, field = field}) - end - end - table.sort(result, function(a, b) return a.id < b.id end) - return result -end - --- Scanner næste mark (round-robin) og returnerer alle kendte. Kaldes hvert sekund af job-løkken. -function ADFields.scanNext(adEnv, farmId, now) - ADFields.now = now or ADFields.now - if ADFields.ownedAt == nil or ADFields.now - ADFields.ownedAt >= ADFields.OWNED_REFRESH_MS then - ADFields.ownedAt = ADFields.now - local owned = ownedFields(farmId) - local ids = {} - for _, entry in ipairs(owned) do ids[entry.id] = true end - for id in pairs(ADFields.cache) do - if not ids[id] then ADFields.cache[id] = nil end - end - ADFields.order = owned - ADFields.listCache = nil - end - local owned = ADFields.order - if #owned == 0 then return end - ADFields.scanIndex = ADFields.scanIndex % #owned + 1 - local entry = owned[ADFields.scanIndex] - local ok, info = pcall(scanField, adEnv, entry.field, entry.id) + ADFields.cache[info.id] = info ADFields.listCache = nil - if ok then - ADFields.cache[entry.id] = info - else - Logging.warning("ADSmartPickup: F%s kunne ikke scannes: %s", tostring(entry.id), tostring(info)) +end + +-- Marklisten (ejede marker) i portioner af 25 marker pr. frame. +local function newOwnedScan(farmId) + local all = {} + for _, field in pairs(g_fieldManager ~= nil and (g_fieldManager:getFields() or g_fieldManager.fields) or {}) do + table.insert(all, field) end + local scan = {owned = true, queue = {}, head = 1, tail = 0, result = {}} + for start = 1, #all, 25 do + push(scan, function() + for index = start, math.min(start + 24, #all) do + local field = all[index] + local id = field.getId ~= nil and field:getId() or field.fieldId + if id ~= nil and fieldOwner(field) == farmId then + table.insert(scan.result, {id = id, field = field}) + end + end + end) + end + return scan +end + +local function finishOwnedScan(scan) + table.sort(scan.result, function(a, b) return a.id < b.id end) + local ids = {} + for _, entry in ipairs(scan.result) do ids[entry.id] = true end + for id in pairs(ADFields.cache) do + if not ids[id] then ADFields.cache[id] = nil end + end + ADFields.order = scan.result + ADFields.listCache = nil +end + +-- Kaldes HVERT frame: kører én opgave af den aktuelle scanning. En ny mark påbegyndes højst hvert +-- SCAN_INTERVAL_MS; marklisten opdateres hvert OWNED_REFRESH_MS. +ADFields.SCAN_INTERVAL_MS = 2000 +ADFields.lastScanStart = nil + +function ADFields.scanStep(adEnv, farmId, now) + ADFields.now = now or ADFields.now + local scan = ADFields.activeScan + if scan == nil then + if ADFields.ownedAt == nil or ADFields.now - ADFields.ownedAt >= ADFields.OWNED_REFRESH_MS then + ADFields.ownedAt = ADFields.now + scan = newOwnedScan(farmId) + else + if ADFields.lastScanStart ~= nil and ADFields.now - ADFields.lastScanStart < ADFields.SCAN_INTERVAL_MS then return end + local owned = ADFields.order + if #owned == 0 then return end + ADFields.lastScanStart = ADFields.now + ADFields.scanIndex = ADFields.scanIndex % #owned + 1 + scan = newScan(adEnv, owned[ADFields.scanIndex]) + end + ADFields.activeScan = scan + end + local task = scan.queue[scan.head] + if task ~= nil then + scan.queue[scan.head] = nil + scan.head = scan.head + 1 + local ok, err = pcall(task) + if not ok then + Logging.warning("ADSmartPickup: F%s kunne ikke scannes: %s", tostring(scan.id or "-"), tostring(err)) + ADFields.activeScan = nil + end + return + end + -- køen er tom: saml resultatet + ADFields.activeScan = nil + local ok, err + if scan.owned then + ok, err = pcall(finishOwnedScan, scan) + else + ok, err = pcall(finishScan, adEnv, scan) + end + if not ok then Logging.warning("ADSmartPickup: F%s scanning kunne ikke afsluttes: %s", tostring(scan.id or "-"), tostring(err)) end end -- Samme liste indtil næste scanning (list() kaldes hvert sekund; ingen ny tabel hver gang). diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index a7fc787..0a3c87a 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.27.1.0 + 1.27.2.0 <en>AD Smart Pickup</en> diff --git a/docs/BRUGERMANUAL.md b/docs/BRUGERMANUAL.md index a267171..f71b401 100644 --- a/docs/BRUGERMANUAL.md +++ b/docs/BRUGERMANUAL.md @@ -1,6 +1,6 @@ # AD Smart Pickup — brugermanual -Version 1.27.1 · Farming Simulator 25 · tillæg til AutoDrive +Version 1.27.2 · Farming Simulator 25 · tillæg til AutoDrive *English version: [USER_MANUAL.md](USER_MANUAL.md)* diff --git a/docs/TESTPLAN-markarbejde.md b/docs/TESTPLAN-markarbejde.md index 5134a62..0e4fb43 100644 --- a/docs/TESTPLAN-markarbejde.md +++ b/docs/TESTPLAN-markarbejde.md @@ -1,4 +1,4 @@ -# Testplan — Markarbejde (fanen Marker), version 1.27.1.0 +# Testplan — Markarbejde (fanen Marker), version 1.27.2.0 Stor test-session. Kør testene i rækkefølge: de første bygger opsætningen, de senere bygger på den. Hver test har **Gør**, **Forvent** og **Log** (linjer i `log.txt`, der beviser at det virkede). Loglinjer @@ -11,7 +11,7 @@ samme rækkefølge. ## 0. Forberedelse -- [ ] **0.1 Version.** Log: `Available mod: … (Version: 1.27.1.0) FS25_ADSmartPickup`. +- [ ] **0.1 Version.** Log: `Available mod: … (Version: 1.27.2.0) FS25_ADSmartPickup`. - [ ] **0.2 Fanen Marker åbner.** ESC → Smart Pickup → fanen **Marker**. Forvent: "Marker indlæses …" i få sekunder, derefter én række pr. ejet mark (også korn-, roe- og bare marker, ikke kun græs). Afgrøde-kolonnen viser afgrøden eller "bar jord". diff --git a/docs/USER_MANUAL.md b/docs/USER_MANUAL.md index 4489692..0639f47 100644 --- a/docs/USER_MANUAL.md +++ b/docs/USER_MANUAL.md @@ -1,6 +1,6 @@ # AD Smart Pickup — user manual -Version 1.27.1 · Farming Simulator 25 · add-on for AutoDrive +Version 1.27.2 · Farming Simulator 25 · add-on for AutoDrive *Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)* diff --git a/tests/test_adFieldsScan.lua b/tests/test_adFieldsScan.lua new file mode 100644 index 0000000..febcd97 --- /dev/null +++ b/tests/test_adFieldsScan.lua @@ -0,0 +1,76 @@ +-- Kør: luajit tests/test_adFieldsScan.lua (fra repo-roden) +-- Markscanningen som arbejdskø: bliver færdig, giver rigtigt resultat, og intet frame laver mere end ét +-- prøvepunkts arbejde (in-game 26/9: en hel mark i ét frame gav hak). +local failures = 0 +local function check(name, actual, expected) + if actual == expected then print("OK " .. name) else + failures = failures + 1 + print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual))) + end +end +Logging = {warning = function(fmt, ...) print("WARN " .. string.format(fmt, ...)) end, info = function() end} + +-- falsk verden: én mark (F7) med hvede der er klar, polygon med 4 hjørner +local GRASS, WHEAT = 1, 3 +local nodes = {[101] = {0, 0}, [102] = {100, 0}, [103] = {100, 100}, [104] = {0, 100}} +function getWorldTranslation(node) local p = nodes[node]; return p[1], 0, p[2] end +function getTerrainHeightAtWorldPos() return 0 end +local calls = 0 +FSDensityMapUtil = { + getFieldDataAtWorldPosition = function() calls = calls + 1; return 1, 6 end, -- på mark, vækststadie 6 + getFruitTypeIndexAtWorldPos = function() calls = calls + 1; return WHEAT end, + getFruitArea = function(_, _, _, _, _, _, _, any) calls = calls + 1; return 0, any and 10 or 10 end, +} +FieldDensityMap = {SPRAY_LEVEL = 4, LIME_LEVEL = 5, PLOW_LEVEL = 6} +DensityCoordType = {POINT_VECTOR_VECTOR = 1} +DensityMapModifier = {new = function() return {setParallelogramWorldCoords = function() end, executeGet = function() calls = calls + 1; return 1, 1 end} end} +g_fruitTypeManager = { + getFruitTypeByIndex = function(_, index) return {name = index == WHEAT and "WHEAT" or "GRASS", fillType = {index = 50, title = "Hvede"}, minHarvestingGrowthState = 6, maxHarvestingGrowthState = 7, cutState = 9} end, + getConverterDataByName = function() return {} end, +} +g_fillTypeManager = {getConverterDataByName = function() return {} end, getFillTypeNameByIndex = function() return "X" end} +g_farmlandManager = {getFarmlandOwner = function() return 1 end} +local field = {getId = function() return 7 end, farmland = {id = 70}, polygonPoints = {101, 102, 103, 104}, posX = 50, posZ = 50} +g_fieldManager = {getFields = function() return {field} end} +g_currentMission = {terrainRootNode = 1, missionInfo = {}, fieldGroundSystem = { + getDensityMapData = function() return 1, 0, 2 end, getMaxValue = function() return 2 end}} +local adEnv = {ADGraphManager = { + getMapMarkers = function() return {{id = 900, name = "F7"}, {id = 901, name = "Silo"}} end, + getWayPointById = function(_, id) return {x = 40, z = 40} end, +}} + +dofile("FS25_ADSmartPickup/adFields.lua") + +local maxPerFrame, frames = 0, 0 +local now = 0 +for _ = 1, 200 do + calls = 0 + ADFields.scanStep(adEnv, 1, now) + if calls > maxPerFrame then maxPerFrame = calls end + frames = frames + 1 + now = now + 16 + if ADFields.getInfo(7) ~= nil then break end +end +-- markliste først, så marken (efter SCAN_INTERVAL) — kør videre til marken er scannet +for _ = 1, 400 do + if ADFields.getInfo(7) ~= nil then break end + calls = 0 + ADFields.scanStep(adEnv, 1, now) + if calls > maxPerFrame then maxPerFrame = calls end + now = now + 16 +end +local info = ADFields.getInfo(7) +check("SC1 marken er scannet", info ~= nil, true) +check("SC2 afgrøde", info and info.fruitName, "WHEAT") +check("SC3 art = høst", info and info.kind, "harvest") +check("SC4 klar-andel", info and info.readyRatio, 1) +check("SC5 fase klar", info and info.phase, "ready") +check("SC6 markør F7", info and info.markerId, 900) +check("SC7 prøvepunkter", info and #info.points, 5) +check("SC8 gødning læst", info and info.fert, 1) +check("SC9 højst ét punkts arbejde pr. frame (<= 4 opslag)", maxPerFrame <= 4, true) +print(" (flest opslag i ét frame: " .. maxPerFrame .. ")") +check("SC10 marklisten", #ADFields.list(), 1) + +print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) +os.exit(failures == 0 and 0 or 1)