From 4bd9b5fe8ac017272563d9fb11b6457dc3c8b3e1 Mon Sep 17 00:00:00 2001 From: HLLMR <147449512+HLLMR@users.noreply.github.com> Date: Sun, 1 Nov 2026 07:04:23 -0600 Subject: [PATCH] Detect coffee plantation rows in the vine scanner The coffee plantation of the Beans & Alpacas DLC registers its plants in the vine system but has no spec_vine, so getVineNodesOnField stopped with "attempt to index nil with 'vineSegments'" and vine detection never finished. getVineSegmentsAndWidth() returns the spec_vine data as before, and for the coffee plantation builds the same layout once per scan from its fence segments and getOrchardRowSpacing(). Placeables without usable row data are skipped with a debug message. Fixes #1346 Co-Authored-By: Claude Opus 5.5 --- scripts/field/VineScanner.lua | 68 +++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/scripts/field/VineScanner.lua b/scripts/field/VineScanner.lua index 77750972a..f8c7cdcc4 100644 --- a/scripts/field/VineScanner.lua +++ b/scripts/field/VineScanner.lua @@ -11,6 +11,7 @@ end function VineScanner:setup() self.vineSystem = g_currentMission.vineSystem + self.orchardRows = {} end function VineScanner:foundVines() @@ -43,6 +44,7 @@ end ---@return table|nil closest vine node ---@return Placeable|nil closest placeable function VineScanner:getVineNodesOnField(vertices, tx, tz, isCustomField) + self.orchardRows = {} if vertices == nil or next(vertices) == nil or tx == nil or tz == nil then self.lines = nil self.width = nil @@ -61,14 +63,17 @@ function VineScanner:getVineNodesOnField(vertices, tx, tz, isCustomField) for node, placeable in pairs(self.vineSystem.nodes) do x, _, z = getWorldTranslation(node) if CpMathUtil.isPointInPolygon(vertices, x, z) then - segment = self:getVineSegmentIxForNode(node, placeable.spec_vine.vineSegments) - table.insert(vineSegments, segment) - dist = MathUtil.vector2Length(x-tx, z-tz) - if dist < closestDist then - closestSegment = self:getVineSegmentIxForNode(node, placeable.spec_vine.vineSegments) - closestPlaceable = placeable - closestNode = node - closestDist = dist + local placeableSegments = self:getVineSegmentsAndWidth(placeable) + segment = placeableSegments and self:getVineSegmentIxForNode(node, placeableSegments) + if segment then + table.insert(vineSegments, segment) + dist = MathUtil.vector2Length(x-tx, z-tz) + if dist < closestDist then + closestSegment = segment + closestPlaceable = placeable + closestNode = node + closestDist = dist + end end end end @@ -147,7 +152,8 @@ function VineScanner:findVineNodesInField(vertices, tx, tz, isCustomField) self.lines = newLines - self.width = closestPlaceable.spec_vine.width + local _, width = self:getVineSegmentsAndWidth(closestPlaceable) + self.width = width self:debug("Found %d number of lines for a width of %.2f", #self.lines, self.width) @@ -189,7 +195,7 @@ end ---@return table right lines function VineScanner:separateIntoColumns(lines, closestNode) local placeable = self.vineSystem.nodes[closestNode] - local width = placeable.spec_vine.width + local _, width = self:getVineSegmentsAndWidth(placeable) local columnsLeft = {} local columnsRight = {} --- Left columns @@ -407,6 +413,48 @@ function VineScanner:getLines() return self.lines end +--- Gets the vine segments and the row spacing of a placeable in the vine system. +--- The coffee plantation of the Beans & Alpacas DLC has no spec_vine, its fence segments +--- hold the vine nodes and the spacing comes from getOrchardRowSpacing(). +--- These are converted into the spec_vine layout once per scan. +---@param placeable table +---@return table|nil vine segments +---@return number|nil row spacing +function VineScanner:getVineSegmentsAndWidth(placeable) + if placeable.spec_vine then + return placeable.spec_vine.vineSegments, placeable.spec_vine.width + end + self.orchardRows = self.orchardRows or {} + local rows = self.orchardRows[placeable] + if rows == nil then + rows = {} + self.orchardRows[placeable] = rows + local fence = placeable.getFence and placeable:getFence() + local width = placeable.getOrchardRowSpacing and placeable:getOrchardRowSpacing() + if fence == nil or type(fence.segments) ~= "table" or type(width) ~= "number" or width <= 0 then + self:debug("Placeable of type %s in the vine system has no vine rows, ignoring it.", tostring(placeable.typeName)) + return + end + local vineSegments = {} + for _, fenceSegment in ipairs(fence.segments) do + if fenceSegment.vineNodes and fenceSegment.getStartPos and fenceSegment.getEndPos then + local x1, _, z1 = fenceSegment:getStartPos() + local x2, _, z2 = fenceSegment:getEndPos() + if x1 and z1 and x2 and z2 then + local nodes = {} + for node, _ in pairs(fenceSegment.vineNodes) do + table.insert(nodes, {node = node}) + end + vineSegments[{x1 = x1, z1 = z1, x2 = x2, z2 = z2}] = nodes + end + end + end + rows.vineSegments = vineSegments + rows.width = width + end + return rows.vineSegments, rows.width +end + --- Gets a segment for a vine node. function VineScanner:getVineSegmentIxForNode(node, vineSegments) for segment, nodes in pairs(vineSegments) do