594 | function PlaceableVine:getSectionFactor(node, i, startX, startY, startZ, currentX, currentY, currentZ) |
595 | local spec = self.spec_vine |
596 | |
597 | local factor = 0 |
598 | local sectionStart = spec.sectionLength * (i-1) |
599 | local sectionEnd = spec.sectionLength * i |
600 | |
601 | -- convert to local space |
602 | local _, _, localStartZ = worldToLocal(node, startX, startY, startZ) |
603 | local _, _, localCurrentZ = worldToLocal(node, currentX, currentY, currentZ) |
604 | |
605 | if localStartZ > localCurrentZ then |
606 | localStartZ, localCurrentZ = localCurrentZ, localStartZ |
607 | end |
608 | |
609 | if localStartZ < sectionEnd and localCurrentZ > sectionStart then |
610 | local checkStart = math.max(localStartZ, sectionStart) |
611 | local checkEnd = math.min(localCurrentZ, sectionEnd) |
612 | |
613 | local checkDistance = math.abs(checkStart - checkEnd) |
614 | factor = checkDistance / spec.sectionLength |
615 | end |
616 | |
617 | return factor |
618 | end |
513 | function PlaceableVine:harvestVine(node, startX, startY, startZ, currentX, currentY, currentZ, callback, target) |
514 | if not self.isServer then |
515 | return 0 |
516 | end |
517 | |
518 | local spec = self.spec_vine |
519 | local data = spec.nodes[node] |
520 | if data == nil then |
521 | return 0 |
522 | end |
523 | |
524 | local needsUpdate = false |
525 | for i=1, spec.numSections do |
526 | local factor = self:getSectionFactor(node, i, startX, startY, startZ, currentX, currentY, currentZ) |
527 | |
528 | if factor > spec.thresholdFactor then |
529 | local section = data.sections[i] |
530 | |
531 | local area, totalArea, weedFactor, sprayFactor, plowFactor = FSDensityMapUtil.updateVineCutArea(spec.fruitType.index, section[1], section[2], section[3], section[4], section[5], section[6]) |
532 | |
533 | if area > 0 then |
534 | callback(target, self, area, totalArea, weedFactor, sprayFactor, plowFactor, spec.sectionLength) |
535 | |
536 | needsUpdate = true |
537 | end |
538 | end |
539 | end |
540 | |
541 | if needsUpdate then |
542 | self:updateVineNode(node, false) |
543 | end |
544 | end |
224 | function PlaceableVine:onCreateSegmentPanel(isPreview, segment, panel, poleIndex, dy, pole) |
225 | local spec = self.spec_vine |
226 | |
227 | if not getAllowFoliageShadows() then |
228 | local function disableShadows(node) |
229 | if getHasClassId(node, ClassIds.SHAPE) then |
230 | setShapeCastShadowmap(node, false) |
231 | end |
232 | end |
233 | |
234 | I3DUtil.interateRecursively(panel, disableShadows) |
235 | if pole ~= nil then |
236 | I3DUtil.interateRecursively(pole, disableShadows) |
237 | end |
238 | end |
239 | |
240 | if not isPreview then |
241 | local node = getChildAt(panel, 0) |
242 | |
243 | local data = {node=node, poleIndex=poleIndex} |
244 | spec.nodes[node] = data |
245 | table.insert(spec.vineSegments[segment], data) |
246 | |
247 | local x, _, z = getWorldTranslation(panel) |
248 | local dirX, _, dirZ = localDirectionToWorld(panel, 0, 0, 1) |
249 | dirX, dirZ = MathUtil.vector2Normalize(dirX, dirZ) |
250 | |
251 | local normX, _, normZ = MathUtil.crossProduct(dirX, 0, dirZ, 0, 1, 0) |
252 | normX, normZ = MathUtil.vector2Normalize(normX, normZ) |
253 | local sizeHalfX = spec.width * 0.5 |
254 | |
255 | data.growthStates = {} |
256 | for nodeIndex, foliageStates in pairs(spec.growthStates) do |
257 | local stateNode = I3DUtil.indexToObject(panel, nodeIndex) |
258 | if stateNode == nil then |
259 | Logging.warning("Failed to get vine panel growth state node ('%s')", nodeIndex) |
260 | return |
261 | end |
262 | |
263 | data.growthStates[stateNode] = {foliageStates=foliageStates, value=0, sectionStates={}} |
264 | setVisibility(stateNode, false) |
265 | |
266 | -- check if node has custom lod1 with children |
267 | for i=0, getNumOfChildren(stateNode)-1 do |
268 | local lodNode = getChildAt(stateNode, i) |
269 | if getNumOfChildren(lodNode) > 1 then |
270 | for j=0, getNumOfChildren(lodNode)-1 do |
271 | local lodChildNode = getChildAt(lodNode, j) |
272 | local lodX, lodY, lodZ = getTranslation(lodChildNode) |
273 | local distanceFactor = lodZ / spec.length |
274 | |
275 | lodY = lodY + distanceFactor * -dy |
276 | setTranslation(lodChildNode, lodX, lodY, lodZ) |
277 | |
278 | if spec.numLODOffsets > 0 and getHasShaderParameter(lodChildNode, "uvOffset") then |
279 | local uvOffset = math.random(0, spec.numLODOffsets-1) * (1 / spec.numLODOffsets) |
280 | setShaderParameter(lodChildNode, "uvOffset", uvOffset, 0, 0, 0, false) |
281 | end |
282 | end |
283 | end |
284 | end |
285 | end |
286 | |
287 | local maxState = 1 |
288 | local maxPixels = 0 |
289 | data.sections = {} |
290 | -- data.sectionDebugAreas = {} |
291 | data.growthValues = {} |
292 | |
293 | local sectionLength = spec.sectionLength |
294 | |
295 | for i=1, spec.numSections do |
296 | local startX = x + dirX * sectionLength*(i-1) + normX * -sizeHalfX |
297 | local startZ = z + dirZ * sectionLength*(i-1) + normZ * -sizeHalfX |
298 | local widthX = startX + normX * spec.width |
299 | local widthZ = startZ + normZ * spec.width |
300 | local heightX = startX + dirX * sectionLength |
301 | local heightZ = startZ + dirZ * sectionLength |
302 | |
303 | data.sections[i] = {startX, startZ, widthX, widthZ, heightX, heightZ} |
304 | |
305 | -- local debugArea = Debug2DArea.new(true, false, {0, 0.7 * (i / spec.numSections), 0, 0.1}) |
306 | -- debugArea:createWithPositions(startX, nil, startZ, widthX, nil, widthZ, heightX, nil, heightZ) |
307 | -- data.sectionDebugAreas[i] = debugArea |
308 | -- g_debugManager:addPermanentElement(debugArea) |
309 | |
310 | data.growthValues[i] = {totalArea=0, values={}} |
311 | for stateNode, growthStateData in pairs(data.growthStates) do |
312 | for _, foliageData in ipairs(growthStateData.foliageStates) do |
313 | data.growthValues[i].values[foliageData.state] = 0 |
314 | end |
315 | end |
316 | |
317 | data.growthValues[i].totalArea = FSDensityMapUtil.updateVineAreaValues(spec.fruitType.index, startX, startZ, widthX, widthZ, heightX, heightZ, data.growthValues[i].values) |
318 | |
319 | for state, value in pairs(data.growthValues[i].values) do |
320 | if value > maxPixels then |
321 | maxPixels = value |
322 | maxState = state |
323 | end |
324 | end |
325 | end |
326 | |
327 | if not self.isLoadingFromSavegameXML then |
328 | for i=1, spec.numSections do |
329 | local section = data.sections[i] |
330 | FSDensityMapUtil.createVineArea(spec.fruitType.index, section[1], section[2], section[3], section[4], section[5], section[6], maxState) |
331 | end |
332 | end |
333 | |
334 | g_currentMission.vineSystem:addElement(self, node, spec.width, spec.length) |
335 | self:updateVineNode(node, false) |
336 | else |
337 | local states = getChildAt(panel, 1) |
338 | for i=0, getNumOfChildren(states)-1 do |
339 | setVisibility(getChildAt(states, i), i == spec.previewNodeIndex) |
340 | end |
341 | end |
342 | end |
85 | function PlaceableVine:onLoad(savegame) |
86 | local spec = self.spec_vine |
87 | local xmlFile = self.xmlFile |
88 | |
89 | local fruitTypeName = xmlFile:getValue("placeable.vine#fruitType") |
90 | if fruitTypeName == nil then |
91 | Logging.xmlWarning(xmlFile, "Missing fruit type name") |
92 | return |
93 | end |
94 | |
95 | local fruitType = g_fruitTypeManager:getFruitTypeByName(fruitTypeName) |
96 | if fruitType == nil then |
97 | Logging.xmlWarning(xmlFile, "Fruit type '%s' not defined", fruitTypeName) |
98 | return |
99 | end |
100 | |
101 | spec.fruitType = fruitType |
102 | spec.length = xmlFile:getValue("placeable.vine#length", 1) |
103 | spec.width = xmlFile:getValue("placeable.vine#width", 1) |
104 | spec.thresholdFactor = xmlFile:getValue("placeable.vine#thresholdFactor", 0.5) |
105 | spec.numLODOffsets = xmlFile:getValue("placeable.vine#numLODOffsets", 0) |
106 | spec.numSections = math.max(xmlFile:getValue("placeable.vine#numSections", 1), 1) |
107 | spec.sectionLength = spec.length / spec.numSections |
108 | spec.vineSegments = {} |
109 | spec.nodes = {} |
110 | spec.growthStates = {} |
111 | |
112 | xmlFile:iterate("placeable.vine.growthStates.growthState", function(_, key) |
113 | local nodeIndex = xmlFile:getValue(key .. "#nodeIndex") |
114 | if nodeIndex == nil then |
115 | Logging.xmlWarning(xmlFile, "Missing growth state nodeIndex for '%s'", key) |
116 | return |
117 | end |
118 | |
119 | local foliageStates = {} |
120 | |
121 | xmlFile:iterate(key .. ".foliage", function(_, foliageKey) |
122 | local state = xmlFile:getValue(foliageKey .. "#state") |
123 | if state == nil then |
124 | Logging.xmlWarning(xmlFile, "Missing foliage state for '%s'", foliageKey) |
125 | return |
126 | end |
127 | |
128 | if state < 0 and state > (fruitType.numStateChannels^2-1) then |
129 | Logging.xmlWarning(xmlFile, "Invalid foliage state for '%s'", foliageKey) |
130 | return |
131 | end |
132 | |
133 | local sectionState = xmlFile:getValue(foliageKey .. "#sectionState") |
134 | if sectionState == nil then |
135 | Logging.xmlWarning(xmlFile, "Missing foliage sectionState for '%s'", foliageKey) |
136 | return |
137 | end |
138 | |
139 | table.insert(foliageStates, {state=state, sectionState=sectionState}) |
140 | end) |
141 | |
142 | if #foliageStates == 0 then |
143 | Logging.xmlWarning(xmlFile, "Missing foliage states for growthstate '%s'", key) |
144 | return |
145 | end |
146 | |
147 | spec.growthStates[nodeIndex] = foliageStates |
148 | end) |
149 | |
150 | spec.previewNodeIndex = xmlFile:getValue("placeable.vine.growthStates#previewNodeIndex", 0) |
151 | |
152 | spec.resetStates = {} |
153 | xmlFile:iterate("placeable.vine.resetStates.resetState", function(_, key) |
154 | local state = xmlFile:getValue(key .. "#state") |
155 | if state == nil then |
156 | Logging.xmlWarning(xmlFile, "Missing reset state for '%s'", key) |
157 | return |
158 | end |
159 | local targetState = xmlFile:getValue(key .. "#targetState") |
160 | if targetState == nil then |
161 | Logging.xmlWarning(xmlFile, "Missing reset target state for '%s'", key) |
162 | return |
163 | end |
164 | local threshold = xmlFile:getValue(key .. "#threshold") |
165 | if threshold == nil then |
166 | Logging.xmlWarning(xmlFile, "Missing reset state threshold for '%s'", key) |
167 | return |
168 | end |
169 | |
170 | local resetState = {} |
171 | resetState.state = state |
172 | resetState.targetState = targetState |
173 | resetState.values = {} |
174 | resetState.values[state] = 0 |
175 | resetState.threshold = threshold |
176 | |
177 | table.insert(spec.resetStates, resetState) |
178 | end) |
179 | end |
56 | function PlaceableVine.registerXMLPaths(schema, basePath) |
57 | schema:setXMLSpecializationType("Vine") |
58 | schema:register(XMLValueType.STRING, basePath .. ".vine#fruitType", "Vine fruit type") |
59 | schema:register(XMLValueType.FLOAT, basePath .. ".vine#width", "Vine width") |
60 | schema:register(XMLValueType.FLOAT, basePath .. ".vine#length", "Vine length") |
61 | schema:register(XMLValueType.FLOAT, basePath .. ".vine#thresholdFactor", "Section work threshold factor") |
62 | schema:register(XMLValueType.INT, basePath .. ".vine#numLODOffsets", "Vine num lod offsets") |
63 | schema:register(XMLValueType.INT, basePath .. ".vine#numSections", "Vine num sub sections") |
64 | schema:register(XMLValueType.INT, basePath .. ".vine.growthStates#previewNodeIndex", "Node index of preview node") |
65 | schema:register(XMLValueType.STRING, basePath .. ".vine.growthStates.growthState(?)#nodeIndex", "Growthstate node index. Relative to panel rootnode") |
66 | schema:register(XMLValueType.INT, basePath .. ".vine.growthStates.growthState(?).foliage(?)#state", "Growthstate") |
67 | schema:register(XMLValueType.INT, basePath .. ".vine.growthStates.growthState(?).foliage(?)#sectionState", "SectionState") |
68 | schema:register(XMLValueType.INT, basePath .. ".vine.resetStates.resetState(?)#state", "Reset state") |
69 | schema:register(XMLValueType.INT, basePath .. ".vine.resetStates.resetState(?)#targetState", "Reset target state") |
70 | schema:register(XMLValueType.FLOAT, basePath .. ".vine.resetStates.resetState(?)#threshold", "Threshold to apply reset") |
71 | schema:setXMLSpecializationType() |
72 | end |
418 | function PlaceableVine:updateVineNode(node, isGrowing) |
419 | local spec = self.spec_vine |
420 | |
421 | local data = spec.nodes[node] |
422 | if data == nil then |
423 | return |
424 | end |
425 | |
426 | if not entityExists(node) then |
427 | return |
428 | end |
429 | |
430 | if isGrowing then |
431 | -- check if an area needs to be reset |
432 | -- e.g. only a small part of was prepared for new growing |
433 | -- in that case we need to reset the whole are to make sure that vine is regrowing completly |
434 | |
435 | local startX, startZ, widthX, widthZ, heightX, heightZ = self:getVineAreaByNode(node) |
436 | |
437 | for _, resetState in ipairs(spec.resetStates) do |
438 | local totalArea = FSDensityMapUtil.updateVineAreaValues(spec.fruitType.index, startX, startZ, widthX, widthZ, heightX, heightZ, resetState.values) |
439 | |
440 | local factor = resetState.values[resetState.state] / totalArea |
441 | if self.isServer and factor < 1 and factor > resetState.threshold then |
442 | FSDensityMapUtil.resetVineArea(spec.fruitType.index, startX, startZ, widthX, widthZ, heightX, heightZ, resetState.targetState) |
443 | |
444 | break |
445 | end |
446 | end |
447 | end |
448 | |
449 | data.totalArea = 0 |
450 | if data.growthValues == nil then |
451 | data.growthValues = {} |
452 | end |
453 | |
454 | for i=1, spec.numSections do |
455 | local section = data.sections[i] |
456 | if data.growthValues[i] == nil then |
457 | data.growthValues[i] = {totalArea=0, values={}} |
458 | for stateNode, growthStateData in pairs(data.growthStates) do |
459 | for _, foliageData in ipairs(growthStateData.foliageStates) do |
460 | data.growthValues[i].values[foliageData.state] = 0 |
461 | end |
462 | end |
463 | end |
464 | |
465 | data.growthValues[i].totalArea = FSDensityMapUtil.updateVineAreaValues(spec.fruitType.index, section[1], section[2], section[3], section[4], section[5], section[6], data.growthValues[i].values) |
466 | end |
467 | |
468 | self:updateVineVisuals(data) |
469 | end |
473 | function PlaceableVine:updateVineVisuals(data) |
474 | local spec = self.spec_vine |
475 | local maxValue = 0 |
476 | local growthStateNode = nil |
477 | |
478 | for stateNode, growthStateData in pairs(data.growthStates) do |
479 | setVisibility(stateNode, false) |
480 | growthStateData.value = 0 |
481 | for i=1, spec.numSections do |
482 | local maxSectionValue = 0 |
483 | growthStateData.sectionStates[i] = 1 |
484 | |
485 | for _, foliageState in ipairs(growthStateData.foliageStates) do |
486 | local value = data.growthValues[i].values[foliageState.state] |
487 | growthStateData.value = growthStateData.value + value |
488 | |
489 | if value >= maxSectionValue then |
490 | growthStateData.sectionStates[i] = foliageState.sectionState |
491 | maxSectionValue = value |
492 | end |
493 | end |
494 | end |
495 | |
496 | if growthStateData.value > maxValue then |
497 | if growthStateNode ~= nil then |
498 | setVisibility(growthStateNode, false) |
499 | end |
500 | |
501 | maxValue = growthStateData.value |
502 | setVisibility(stateNode, true) |
503 | growthStateNode = stateNode |
504 | |
505 | local sectionStates = growthStateData.sectionStates |
506 | I3DUtil.setShaderParameterRec(stateNode, "hideSectionStates", sectionStates[1], sectionStates[2], sectionStates[3], spec.sectionLength, false, nil) |
507 | end |
508 | end |
509 | end |