798 | function WoodHarvester:findSplitShapesInRange(yOffset, skipCutAnimation) |
799 | local spec = self.spec_woodHarvester |
800 | if spec.attachedSplitShape == nil and spec.cutNode ~= nil then |
801 | local x,y,z = localToWorld(spec.cutNode, yOffset or 0, 0, 0) |
802 | local nx,ny,nz = localDirectionToWorld(spec.cutNode, 1,0,0) |
803 | local yx,yy,yz = localDirectionToWorld(spec.cutNode, 0,1,0) |
804 | |
805 | --#debug local zx,zy,zz = localDirectionToWorld(spec.cutNode, 0,0,1) |
806 | --#debug DebugUtil.drawDebugNode(spec.cutNode, "", false) |
807 | --#debug DebugUtil.drawDebugAreaRectangle(x,y,z, x+zx*spec.cutSizeZ,y+zy*spec.cutSizeZ,z+zz*spec.cutSizeZ, x+yx*spec.cutSizeY,y+yy*spec.cutSizeY,z+yz*spec.cutSizeY, false, 1,0,0) |
808 | |
809 | if spec.curSplitShape == nil and (spec.cutReleasedComponentJoint == nil or spec.cutReleasedComponentJointRotLimitX == 0) then |
810 | local shape, minY, maxY, minZ, maxZ = findSplitShape(x,y,z, nx,ny,nz, yx,yy,yz, spec.cutSizeY, spec.cutSizeZ) |
811 | |
812 | if shape ~= 0 then |
813 | local splitType = g_splitTypeManager:getSplitTypeByIndex(getSplitType(shape)) |
814 | if splitType == nil or not splitType.allowsWoodHarvester then |
815 | spec.warnInvalidTree = true |
816 | else |
817 | if self:getCanSplitShapeBeAccessed(x, z, shape) then |
818 | local treeDx,treeDy,treeDz = localDirectionToWorld(shape, 0,1,0) -- wood harvester trees always grow in the y direction |
819 | local cosTreeAngle = MathUtil.dotProduct(nx,ny,nz, treeDx,treeDy,treeDz) |
820 | |
821 | -- Only allow cutting if the cut header is approximately parallel to the tree (15° offset) |
822 | if math.acos(cosTreeAngle) <= 0.2617 then |
823 | local radius = math.max(maxY-minY, maxZ-minZ)*0.5 * cosTreeAngle |
824 | |
825 | --#debug local x1, y1, z1 = localToWorld(spec.cutNode, yOffset or 0, minY, minZ) |
826 | --#debug local x2, y2, z2 = localToWorld(spec.cutNode, yOffset or 0, minY, maxZ) |
827 | --#debug local x3, y3, z3 = localToWorld(spec.cutNode, yOffset or 0, maxY, minZ) |
828 | --#debug Utils.renderTextAtWorldPosition((x1+x3) / 2, (y1+y3) / 2, (z1+z3) / 2, string.format("diam: %.1f/%.1f", math.deg(radius*2), math.deg(spec.cutMaxRadius*2)), getCorrectTextSize(0.012), 0) |
829 | --#debug DebugUtil.drawDebugAreaRectangle(x1, y1, z1, x2, y2, z2, x3, y3, z3, false, 0,1,0) |
830 | |
831 | if radius > spec.cutMaxRadius then |
832 | spec.warnInvalidTreeRadius = true |
833 | |
834 | -- check one meter higher if the tree would fit and then display different warning |
835 | x, y, z = localToWorld(spec.cutNode, yOffset or 0 + 1, 0, 0) |
836 | shape, minY, maxY, minZ, maxZ = findSplitShape(x,y,z, nx,ny,nz, yx,yy,yz, spec.cutSizeY, spec.cutSizeZ) |
837 | if shape ~= 0 then |
838 | radius = math.max(maxY-minY, maxZ-minZ)*0.5 * cosTreeAngle |
839 | |
840 | --#debug x1, y1, z1 = localToWorld(spec.cutNode, yOffset or 0 + 1, minY, minZ) |
841 | --#debug x2, y2, z2 = localToWorld(spec.cutNode, yOffset or 0 + 1, minY, maxZ) |
842 | --#debug x3, y3, z3 = localToWorld(spec.cutNode, yOffset or 0 + 1, maxY, minZ) |
843 | --#debug Utils.renderTextAtWorldPosition((x1+x3) / 2, (y1+y3) / 2, (z1+z3) / 2, string.format("diam: %.1f/%.1f", math.deg(radius*2), math.deg(spec.cutMaxRadius*2)), getCorrectTextSize(0.012), 0) |
844 | --#debug DebugUtil.drawDebugAreaRectangle(x1, y1, z1, x2, y2, z2, x3, y3, z3, false, 0,1,0) |
845 | |
846 | if radius <= spec.cutMaxRadius then |
847 | spec.warnInvalidTreeRadius = false |
848 | spec.warnInvalidTreePosition = true |
849 | end |
850 | end |
851 | else |
852 | self:setLastTreeDiameter(math.max(maxY-minY, maxZ-minZ)) |
853 | spec.curSplitShape = shape |
854 | |
855 | if skipCutAnimation then |
856 | self:setAnimationTime(spec.cutAnimation.name, 1, true) |
857 | spec.cutTimer = 0 |
858 | end |
859 | end |
860 | end |
861 | else |
862 | spec.warnTreeNotOwned = true |
863 | end |
864 | end |
865 | end |
866 | end |
867 | end |
868 | end |
25 | function WoodHarvester.initSpecialization() |
26 | local schema = Vehicle.xmlSchema |
27 | schema:setXMLSpecializationType("WoodHarvester") |
28 | |
29 | schema:register(XMLValueType.NODE_INDEX, "vehicle.woodHarvester.cutNode#node", "Cut node") |
30 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.cutNode#maxRadius", "Max. radius", 1) |
31 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.cutNode#sizeY", "Size Y", 1) |
32 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.cutNode#sizeZ", "Size Z", 1) |
33 | schema:register(XMLValueType.NODE_INDEX, "vehicle.woodHarvester.cutNode#attachNode", "Attach node") |
34 | schema:register(XMLValueType.NODE_INDEX, "vehicle.woodHarvester.cutNode#attachReferenceNode", "Attach reference node") |
35 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.cutNode#attachMoveSpeed", "Attach move speed", 3) |
36 | schema:register(XMLValueType.INT, "vehicle.woodHarvester.cutNode#releasedComponentJointIndex", "Released component joint") |
37 | schema:register(XMLValueType.ANGLE, "vehicle.woodHarvester.cutNode#releasedComponentJointRotLimitXSpeed", "Released component joint rot limit X speed", 100) |
38 | schema:register(XMLValueType.INT, "vehicle.woodHarvester.cutNode#releasedComponentJoint2Index", "Released component joint 2") |
39 | |
40 | schema:register(XMLValueType.NODE_INDEX, "vehicle.woodHarvester.delimbNode#node", "Delimb node") |
41 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.delimbNode#sizeX", "Delimb size X", 0.1) |
42 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.delimbNode#sizeY", "Delimb size Y", 1) |
43 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.delimbNode#sizeZ", "Delimb size Z", 1) |
44 | schema:register(XMLValueType.BOOL, "vehicle.woodHarvester.delimbNode#delimbOnCut", "Delimb on cut", false) |
45 | |
46 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.cutLengths#min", "Min. cut length", 1) |
47 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.cutLengths#max", "Max. cut length", 5) |
48 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.cutLengths#step", "Cut length steps", 0.5) |
49 | |
50 | EffectManager.registerEffectXMLPaths(schema, "vehicle.woodHarvester.cutEffects") |
51 | EffectManager.registerEffectXMLPaths(schema, "vehicle.woodHarvester.delimbEffects") |
52 | AnimationManager.registerAnimationNodesXMLPaths(schema, "vehicle.woodHarvester.forwardingNodes") |
53 | |
54 | SoundManager.registerSampleXMLPaths(schema, "vehicle.woodHarvester.sounds", "cut") |
55 | SoundManager.registerSampleXMLPaths(schema, "vehicle.woodHarvester.sounds", "delimb") |
56 | |
57 | schema:register(XMLValueType.STRING, "vehicle.woodHarvester.cutAnimation#name", "Cut animation name") |
58 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.cutAnimation#speedScale", "Cut animation speed scale") |
59 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.cutAnimation#cutTime", "Cut animation cut time") |
60 | |
61 | schema:register(XMLValueType.STRING, "vehicle.woodHarvester.grabAnimation#name", "Grab animation name") |
62 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.grabAnimation#speedScale", "Grab animation speed scale") |
63 | |
64 | schema:register(XMLValueType.NODE_INDEX, "vehicle.woodHarvester.treeSizeMeasure#node", "Tree size measure node") |
65 | schema:register(XMLValueType.FLOAT, "vehicle.woodHarvester.treeSizeMeasure#rotMaxRadius", "Max. tree size as reference for grab animation", 1) |
66 | |
67 | Dashboard.registerDashboardXMLPaths(schema, "vehicle.woodHarvester.dashboards", "cutLength | curCutLength | diameter") |
68 | |
69 | schema:setXMLSpecializationType() |
70 | |
71 | local schemaSavegame = Vehicle.xmlSchemaSavegame |
72 | schemaSavegame:register(XMLValueType.FLOAT, "vehicles.vehicle(?).woodHarvester#currentCutLength", "Current cut length", "Min. length") |
73 | schemaSavegame:register(XMLValueType.BOOL, "vehicles.vehicle(?).woodHarvester#isTurnedOn", "Harvester is turned on", false) |
74 | schemaSavegame:register(XMLValueType.VECTOR_4, "vehicles.vehicle(?).woodHarvester#lastTreeSize", "Last dimensions of tree to cutNode") |
75 | schemaSavegame:register(XMLValueType.VECTOR_3, "vehicles.vehicle(?).woodHarvester#lastTreeJointPos", "Last tree joint position in local space of splitShape") |
76 | schemaSavegame:register(XMLValueType.BOOL, "vehicles.vehicle(?).woodHarvester#hasAttachedSplitShape", "Has split shape attached", false) |
77 | end |
126 | function WoodHarvester:onLoad(savegame) |
127 | local spec = self.spec_woodHarvester |
128 | |
129 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.woodHarvester.delimbSound", "vehicle.woodHarvester.sounds.delimb") --FS17 to FS19 |
130 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.woodHarvester.cutSound", "vehicle.woodHarvester.sounds.cut") --FS17 to FS19 |
131 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.woodHarvester.treeSizeMeasure#index", "vehicle.woodHarvester.treeSizeMeasure#node") --FS17 to FS19 |
132 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.woodHarvester.forwardingWheels.wheel(0)", "vehicle.woodHarvester.forwardingNodes.animationNode") --FS17 to FS19 |
133 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.woodHarvester.cutParticleSystems", "vehicle.woodHarvester.cutEffects") --FS17 to FS19 |
134 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.woodHarvester.delimbParticleSystems", "vehicle.woodHarvester.delimbEffects") --FS17 to FS19 |
135 | |
136 | spec.curSplitShape = nil |
137 | spec.attachedSplitShape = nil |
138 | spec.hasAttachedSplitShape = false |
139 | spec.isAttachedSplitShapeMoving = false |
140 | spec.attachedSplitShapeX = 0 |
141 | spec.attachedSplitShapeY = 0 |
142 | spec.attachedSplitShapeZ = 0 |
143 | spec.attachedSplitShapeTargetY = 0 |
144 | spec.attachedSplitShapeLastCutY = 0 |
145 | spec.attachedSplitShapeStartY = 0 |
146 | spec.cutTimer = -1 |
147 | |
148 | spec.lastTreeSize = nil |
149 | spec.lastTreeJointPos = nil |
150 | spec.loadedSplitShapeFromSavegame = false |
151 | |
152 | spec.cutNode = self.xmlFile:getValue("vehicle.woodHarvester.cutNode#node", nil, self.components, self.i3dMappings) |
153 | spec.cutMaxRadius = self.xmlFile:getValue("vehicle.woodHarvester.cutNode#maxRadius", 1) |
154 | spec.cutSizeY = self.xmlFile:getValue("vehicle.woodHarvester.cutNode#sizeY", 1) |
155 | spec.cutSizeZ = self.xmlFile:getValue("vehicle.woodHarvester.cutNode#sizeZ", 1) |
156 | spec.cutAttachNode = self.xmlFile:getValue("vehicle.woodHarvester.cutNode#attachNode", nil, self.components, self.i3dMappings) |
157 | spec.cutAttachReferenceNode = self.xmlFile:getValue("vehicle.woodHarvester.cutNode#attachReferenceNode", nil, self.components, self.i3dMappings) |
158 | spec.cutAttachMoveSpeed = self.xmlFile:getValue("vehicle.woodHarvester.cutNode#attachMoveSpeed", 3) * 0.001 |
159 | local cutReleasedComponentJointIndex = self.xmlFile:getValue("vehicle.woodHarvester.cutNode#releasedComponentJointIndex") |
160 | if cutReleasedComponentJointIndex ~= nil then |
161 | spec.cutReleasedComponentJoint = self.componentJoints[cutReleasedComponentJointIndex] |
162 | spec.cutReleasedComponentJointRotLimitX = 0 |
163 | spec.cutReleasedComponentJointRotLimitXSpeed = self.xmlFile:getValue("vehicle.woodHarvester.cutNode#releasedComponentJointRotLimitXSpeed", 100) * 0.001 |
164 | end |
165 | local cutReleasedComponentJoint2Index = self.xmlFile:getValue("vehicle.woodHarvester.cutNode#releasedComponentJoint2Index") |
166 | if cutReleasedComponentJoint2Index ~= nil then |
167 | spec.cutReleasedComponentJoint2 = self.componentJoints[cutReleasedComponentJoint2Index] |
168 | spec.cutReleasedComponentJoint2RotLimitX = 0 |
169 | spec.cutReleasedComponentJoint2RotLimitXSpeed = self.xmlFile:getValue("vehicle.woodHarvester.cutNode#releasedComponentJointRotLimitXSpeed", 100) * 0.001 |
170 | end |
171 | |
172 | if spec.cutAttachReferenceNode ~= nil and spec.cutAttachNode ~= nil then |
173 | spec.cutAttachHelperNode = createTransformGroup("helper") |
174 | link(spec.cutAttachReferenceNode, spec.cutAttachHelperNode) |
175 | setTranslation(spec.cutAttachHelperNode, 0,0,0) |
176 | setRotation(spec.cutAttachHelperNode, 0,0,0) |
177 | end |
178 | |
179 | spec.delimbNode = self.xmlFile:getValue("vehicle.woodHarvester.delimbNode#node", nil, self.components, self.i3dMappings) |
180 | spec.delimbSizeX = self.xmlFile:getValue("vehicle.woodHarvester.delimbNode#sizeX", 0.1) |
181 | spec.delimbSizeY = self.xmlFile:getValue("vehicle.woodHarvester.delimbNode#sizeY", 1) |
182 | spec.delimbSizeZ = self.xmlFile:getValue("vehicle.woodHarvester.delimbNode#sizeZ", 1) |
183 | spec.delimbOnCut = self.xmlFile:getValue("vehicle.woodHarvester.delimbNode#delimbOnCut", false) |
184 | |
185 | spec.cutLengthMin = self.xmlFile:getValue("vehicle.woodHarvester.cutLengths#min", 1) |
186 | spec.cutLengthMax = self.xmlFile:getValue("vehicle.woodHarvester.cutLengths#max", 5) |
187 | spec.cutLengthStep = self.xmlFile:getValue("vehicle.woodHarvester.cutLengths#step", 0.5) |
188 | |
189 | if self.isClient then |
190 | spec.cutEffects = g_effectManager:loadEffect(self.xmlFile, "vehicle.woodHarvester.cutEffects", self.components, self, self.i3dMappings) |
191 | spec.delimbEffects = g_effectManager:loadEffect(self.xmlFile, "vehicle.woodHarvester.delimbEffects", self.components, self, self.i3dMappings) |
192 | spec.forwardingNodes = g_animationManager:loadAnimations(self.xmlFile, "vehicle.woodHarvester.forwardingNodes", self.components, self, self.i3dMappings) |
193 | |
194 | spec.samples = {} |
195 | spec.samples.cut = g_soundManager:loadSampleFromXML(self.xmlFile, "vehicle.woodHarvester.sounds", "cut", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self) |
196 | spec.samples.delimb = g_soundManager:loadSampleFromXML(self.xmlFile, "vehicle.woodHarvester.sounds", "delimb", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self) |
197 | spec.isCutSamplePlaying = false |
198 | spec.isDelimbSamplePlaying = false |
199 | end |
200 | |
201 | spec.cutAnimation = {} |
202 | spec.cutAnimation.name = self.xmlFile:getValue("vehicle.woodHarvester.cutAnimation#name") |
203 | spec.cutAnimation.speedScale = self.xmlFile:getValue("vehicle.woodHarvester.cutAnimation#speedScale", 1) |
204 | spec.cutAnimation.cutTime = self.xmlFile:getValue("vehicle.woodHarvester.cutAnimation#cutTime", 1) |
205 | |
206 | spec.grabAnimation = {} |
207 | spec.grabAnimation.name = self.xmlFile:getValue("vehicle.woodHarvester.grabAnimation#name") |
208 | spec.grabAnimation.speedScale = self.xmlFile:getValue("vehicle.woodHarvester.grabAnimation#speedScale", 1) |
209 | |
210 | spec.treeSizeMeasure = {} |
211 | spec.treeSizeMeasure.node = self.xmlFile:getValue("vehicle.woodHarvester.treeSizeMeasure#node", nil, self.components, self.i3dMappings) |
212 | spec.treeSizeMeasure.rotMaxRadius = self.xmlFile:getValue("vehicle.woodHarvester.treeSizeMeasure#rotMaxRadius", 1) |
213 | |
214 | spec.warnInvalidTree = false |
215 | spec.warnInvalidTreeRadius = false |
216 | spec.warnInvalidTreePosition = false |
217 | spec.warnTreeNotOwned = false |
218 | |
219 | spec.currentCutLength = spec.cutLengthMin |
220 | spec.lastDiameter = 0 |
221 | |
222 | spec.texts = {} |
223 | spec.texts.actionChangeCutLength = g_i18n:getText("action_woodHarvesterChangeCutLength") |
224 | spec.texts.actionCut = g_i18n:getText("action_woodHarvesterCut") |
225 | spec.texts.warningFoldingTreeMounted = g_i18n:getText("warning_foldingTreeMounted") |
226 | spec.texts.warningTreeTooThick = g_i18n:getText("warning_treeTooThick") |
227 | spec.texts.warningTreeTooThickAtPosition = g_i18n:getText("warning_treeTooThickAtPosition") |
228 | spec.texts.warningTreeTypeNotSupported = g_i18n:getText("warning_treeTypeNotSupported") |
229 | spec.texts.warningYouDontHaveAccessToThisLand = g_i18n:getText("warning_youAreNotAllowedToCutThisTree") |
230 | |
231 | if self.loadDashboardsFromXML ~= nil then |
232 | self:loadDashboardsFromXML(self.xmlFile, "vehicle.woodHarvester.dashboards", {valueTypeToLoad = "cutLength", |
233 | valueObject = spec, |
234 | valueFunc = function() |
235 | return spec.currentCutLength * 100 |
236 | end}) |
237 | |
238 | self:loadDashboardsFromXML(self.xmlFile, "vehicle.woodHarvester.dashboards", {valueTypeToLoad = "curCutLength", |
239 | valueObject = spec, |
240 | valueFunc = function() |
241 | return math.abs(spec.currentCutLength - (spec.attachedSplitShapeTargetY - spec.attachedSplitShapeY)) * 100 |
242 | end}) |
243 | |
244 | self:loadDashboardsFromXML(self.xmlFile, "vehicle.woodHarvester.dashboards", {valueTypeToLoad = "diameter", |
245 | valueObject = spec, |
246 | valueFunc = function() |
247 | return spec.lastDiameter * 1000 |
248 | end}) |
249 | end |
250 | end |
359 | function WoodHarvester:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
360 | local spec = self.spec_woodHarvester |
361 | |
362 | -- Verify that the split shapes still exist (possible that someone has cut them) |
363 | if self.isServer then |
364 | local lostShape = false |
365 | if spec.attachedSplitShape ~= nil then |
366 | if not entityExists(spec.attachedSplitShape) then |
367 | spec.attachedSplitShape = nil |
368 | spec.attachedSplitShapeJointIndex = nil |
369 | spec.isAttachedSplitShapeMoving = false |
370 | spec.cutTimer = -1 |
371 | lostShape = true |
372 | end |
373 | elseif spec.curSplitShape ~= nil then |
374 | if not entityExists(spec.curSplitShape) then |
375 | spec.curSplitShape = nil |
376 | lostShape = true |
377 | end |
378 | end |
379 | if lostShape then |
380 | SpecializationUtil.raiseEvent(self, "onCutTree", 0, false) |
381 | if g_server ~= nil then |
382 | g_server:broadcastEvent(WoodHarvesterOnCutTreeEvent.new(self, 0), nil, nil, self) |
383 | end |
384 | end |
385 | end |
386 | |
387 | if self.isServer and (spec.attachedSplitShape ~= nil or spec.curSplitShape ~= nil) then |
388 | if spec.cutTimer > 0 then |
389 | if spec.cutAnimation.name ~= nil then |
390 | if self:getAnimationTime(spec.cutAnimation.name) > spec.cutAnimation.cutTime then |
391 | spec.cutTimer = 0 |
392 | end |
393 | else |
394 | spec.cutTimer = math.max(spec.cutTimer - dt, 0) |
395 | end |
396 | end |
397 | local readyToCut = spec.cutTimer == 0 |
398 | |
399 | -- cut |
400 | if readyToCut then |
401 | spec.cutTimer = -1 |
402 | |
403 | local x,y,z = getWorldTranslation(spec.cutNode) |
404 | local nx,ny,nz = localDirectionToWorld(spec.cutNode, 1,0,0) |
405 | local yx,yy,yz = localDirectionToWorld(spec.cutNode, 0,1,0) |
406 | local newTreeCut = false |
407 | |
408 | local currentSplitShape |
409 | if spec.attachedSplitShapeJointIndex ~= nil then |
410 | removeJoint(spec.attachedSplitShapeJointIndex) |
411 | spec.attachedSplitShapeJointIndex = nil |
412 | currentSplitShape = spec.attachedSplitShape |
413 | spec.attachedSplitShape = nil |
414 | else |
415 | currentSplitShape = spec.curSplitShape |
416 | spec.curSplitShape = nil |
417 | newTreeCut = true |
418 | end |
419 | |
420 | -- remember split type name for later (achievement) |
421 | local splitTypeName = "" |
422 | local splitType = g_splitTypeManager:getSplitTypeByIndex(getSplitType(currentSplitShape)) |
423 | if splitType ~= nil then |
424 | splitTypeName = splitType.name |
425 | end |
426 | |
427 | if spec.delimbOnCut then |
428 | local xD,yD,zD = getWorldTranslation(spec.delimbNode) |
429 | local nxD,nyD,nzD = localDirectionToWorld(spec.delimbNode, 1,0,0) |
430 | local yxD,yyD,yzD = localDirectionToWorld(spec.delimbNode, 0,1,0) |
431 | local vx,vy,vz = x-xD,y-yD,z-zD |
432 | local sizeX = MathUtil.vector3Length(vx,vy,vz) |
433 | removeSplitShapeAttachments(currentSplitShape, xD+vx*0.5,yD+vy*0.5,zD+vz*0.5, nxD,nyD,nzD, yxD,yyD,yzD, sizeX*0.7+spec.delimbSizeX, spec.delimbSizeY, spec.delimbSizeZ) |
434 | end |
435 | |
436 | spec.attachedSplitShape = nil |
437 | spec.curSplitShape = nil |
438 | spec.prevSplitShape = currentSplitShape |
439 | |
440 | if not spec.loadedSplitShapeFromSavegame then |
441 | g_currentMission:removeKnownSplitShape(currentSplitShape) |
442 | self.shapeBeingCut = currentSplitShape |
443 | self.shapeBeingCutIsTree = getRigidBodyType(currentSplitShape) == RigidBodyType.STATIC |
444 | self.shapeBeingCutIsNew = newTreeCut |
445 | splitShape(currentSplitShape, x,y,z, nx,ny,nz, yx,yy,yz, spec.cutSizeY, spec.cutSizeZ, "woodHarvesterSplitShapeCallback", self) |
446 | g_treePlantManager:removingSplitShape(currentSplitShape) |
447 | else |
448 | self:woodHarvesterSplitShapeCallback(currentSplitShape, false, true, unpack(spec.lastTreeSize)) |
449 | end |
450 | |
451 | if spec.attachedSplitShape == nil then |
452 | SpecializationUtil.raiseEvent(self, "onCutTree", 0, false) |
453 | if g_server ~= nil then |
454 | g_server:broadcastEvent(WoodHarvesterOnCutTreeEvent.new(self, 0), nil, nil, self) |
455 | end |
456 | else |
457 | if spec.delimbOnCut then |
458 | local xD,yD,zD = getWorldTranslation(spec.delimbNode) |
459 | local nxD,nyD,nzD = localDirectionToWorld(spec.delimbNode, 1,0,0) |
460 | local yxD,yyD,yzD = localDirectionToWorld(spec.delimbNode, 0,1,0) |
461 | local vx,vy,vz = x-xD,y-yD,z-zD |
462 | local sizeX = MathUtil.vector3Length(vx,vy,vz) |
463 | removeSplitShapeAttachments(spec.attachedSplitShape, xD+vx*3,yD+vy*3,zD+vz*3, nxD,nyD,nzD, yxD,yyD,yzD, sizeX*3+spec.delimbSizeX, spec.delimbSizeY, spec.delimbSizeZ) |
464 | end |
465 | end |
466 | |
467 | if newTreeCut then |
468 | local stats = g_currentMission:farmStats(self:getActiveFarm()) |
469 | |
470 | -- increase tree cut counter for achievements |
471 | local cutTreeCount = stats:updateStats("cutTreeCount", 1) |
472 | |
473 | g_achievementManager:tryUnlock("CutTreeFirst", cutTreeCount) |
474 | g_achievementManager:tryUnlock("CutTree", cutTreeCount) |
475 | |
476 | -- update the types of trees cut so far (achievement) |
477 | if splitTypeName ~= "" then |
478 | stats:updateTreeTypesCut(splitTypeName) |
479 | end |
480 | end |
481 | end |
482 | |
483 | -- delimb |
484 | if spec.attachedSplitShape ~= nil and spec.isAttachedSplitShapeMoving then |
485 | if spec.delimbNode ~= nil then |
486 | local x,y,z = getWorldTranslation(spec.delimbNode) |
487 | local nx,ny,nz = localDirectionToWorld(spec.delimbNode, 1,0,0) |
488 | local yx,yy,yz = localDirectionToWorld(spec.delimbNode, 0,1,0) |
489 | |
490 | removeSplitShapeAttachments(spec.attachedSplitShape, x,y,z, nx,ny,nz, yx,yy,yz, spec.delimbSizeX, spec.delimbSizeY, spec.delimbSizeZ) |
491 | end |
492 | |
493 | if spec.cutNode ~= nil and spec.attachedSplitShapeJointIndex ~= nil then |
494 | local x,y,z = getWorldTranslation(spec.cutAttachReferenceNode) |
495 | local nx,ny,nz = localDirectionToWorld(spec.cutAttachReferenceNode, 0,1,0) |
496 | local _, lengthRem = getSplitShapePlaneExtents(spec.attachedSplitShape, x,y,z, nx,ny,nz) |
497 | |
498 | if lengthRem == nil or lengthRem <= 0.1 then |
499 | |
500 | -- end of tree |
501 | removeJoint(spec.attachedSplitShapeJointIndex) |
502 | spec.attachedSplitShapeJointIndex = nil |
503 | spec.attachedSplitShape = nil |
504 | |
505 | self:onDelimbTree(false) |
506 | if g_server ~= nil then |
507 | g_server:broadcastEvent(WoodHarvesterOnDelimbTreeEvent.new(self, false), nil, nil, self) |
508 | end |
509 | |
510 | SpecializationUtil.raiseEvent(self, "onCutTree", 0, false) |
511 | if g_server ~= nil then |
512 | g_server:broadcastEvent(WoodHarvesterOnCutTreeEvent.new(self, 0), nil, nil, self) |
513 | end |
514 | else |
515 | spec.attachedSplitShapeY = spec.attachedSplitShapeY + spec.cutAttachMoveSpeed*dt |
516 | |
517 | if spec.attachedSplitShapeY >= spec.attachedSplitShapeTargetY then |
518 | spec.attachedSplitShapeY = spec.attachedSplitShapeTargetY |
519 | self:onDelimbTree(false) |
520 | if g_server ~= nil then |
521 | g_server:broadcastEvent(WoodHarvesterOnDelimbTreeEvent.new(self, false), nil, nil, self) |
522 | end |
523 | end |
524 | if spec.attachedSplitShapeJointIndex ~= nil then |
525 | x,y,z = localToWorld(spec.cutNode, 0.3,0,0) |
526 | nx,ny,nz = localDirectionToWorld(spec.cutNode, 1,0,0) |
527 | local yx,yy,yz = localDirectionToWorld(spec.cutNode, 0,1,0) |
528 | local shape, minY, maxY, minZ, maxZ = findSplitShape(x,y,z, nx,ny,nz, yx,yy,yz, spec.cutSizeY, spec.cutSizeZ) |
529 | if shape == spec.attachedSplitShape then |
530 | local treeCenterX,treeCenterY,treeCenterZ = localToWorld(spec.cutNode, 0, (minY+maxY)*0.5, (minZ+maxZ)*0.5) |
531 | spec.attachedSplitShapeX, _, spec.attachedSplitShapeZ = worldToLocal(spec.attachedSplitShape, treeCenterX,treeCenterY,treeCenterZ) |
532 | self:setLastTreeDiameter((maxY-minY + maxZ-minZ)*0.5) |
533 | end |
534 | x,y,z = localToWorld(spec.attachedSplitShape, spec.attachedSplitShapeX, spec.attachedSplitShapeY, spec.attachedSplitShapeZ) |
535 | setJointPosition(spec.attachedSplitShapeJointIndex, 1, x,y,z) |
536 | end |
537 | end |
538 | end |
539 | end |
540 | end |
541 | |
542 | -- effect and sound for cut and delimb |
543 | if self.isClient then |
544 | -- cut |
545 | if spec.cutAnimation.name ~= nil then |
546 | if self:getIsAnimationPlaying(spec.cutAnimation.name) and self:getAnimationTime(spec.cutAnimation.name) < spec.cutAnimation.cutTime then |
547 | if not spec.isCutSamplePlaying then |
548 | g_soundManager:playSample(spec.samples.cut) |
549 | spec.isCutSamplePlaying = true |
550 | end |
551 | g_effectManager:setFillType(spec.cutEffects, FillType.WOODCHIPS) |
552 | g_effectManager:startEffects(spec.cutEffects) |
553 | else |
554 | if spec.isCutSamplePlaying then |
555 | g_soundManager:stopSample(spec.samples.cut) |
556 | spec.isCutSamplePlaying = false |
557 | end |
558 | g_effectManager:stopEffects(spec.cutEffects) |
559 | end |
560 | end |
561 | |
562 | -- delimb |
563 | if spec.isAttachedSplitShapeMoving then |
564 | if not spec.isDelimbSamplePlaying then |
565 | g_soundManager:playSample(spec.samples.delimb) |
566 | spec.isDelimbSamplePlaying = true |
567 | end |
568 | g_effectManager:setFillType(spec.delimbEffects, FillType.WOODCHIPS) |
569 | g_effectManager:startEffects(spec.delimbEffects) |
570 | g_animationManager:startAnimations(spec.forwardingNodes) |
571 | else |
572 | if spec.isDelimbSamplePlaying then |
573 | g_soundManager:stopSample(spec.samples.delimb) |
574 | spec.isDelimbSamplePlaying = false |
575 | end |
576 | g_effectManager:stopEffects(spec.delimbEffects) |
577 | g_animationManager:stopAnimations(spec.forwardingNodes) |
578 | end |
579 | end |
580 | end |
584 | function WoodHarvester:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
585 | local spec = self.spec_woodHarvester |
586 | |
587 | spec.warnInvalidTree = false |
588 | spec.warnInvalidTreeRadius = false |
589 | spec.warnInvalidTreePosition = false |
590 | spec.warnTreeNotOwned = false |
591 | |
592 | if self:getIsTurnedOn() then |
593 | if spec.attachedSplitShape == nil and spec.cutNode ~= nil then |
594 | local x,y,z = getWorldTranslation(spec.cutNode) |
595 | local nx,ny,nz = localDirectionToWorld(spec.cutNode, 1,0,0) |
596 | local yx,yy,yz = localDirectionToWorld(spec.cutNode, 0,1,0) |
597 | |
598 | self:findSplitShapesInRange() |
599 | |
600 | if spec.curSplitShape ~= nil then |
601 | local minY,maxY, minZ,maxZ = testSplitShape(spec.curSplitShape, x,y,z, nx,ny,nz, yx,yy,yz, spec.cutSizeY, spec.cutSizeZ) |
602 | if minY == nil then |
603 | spec.curSplitShape = nil |
604 | else |
605 | -- check if cut would be below y=0 (tree CoSy) |
606 | local cutTooLow = false |
607 | local _ |
608 | _,y,_ = localToLocal(spec.cutNode, spec.curSplitShape, 0,minY,minZ) |
609 | cutTooLow = cutTooLow or y < 0.01 |
610 | _,y,_ = localToLocal(spec.cutNode, spec.curSplitShape, 0,minY,maxZ) |
611 | cutTooLow = cutTooLow or y < 0.01 |
612 | _,y,_ = localToLocal(spec.cutNode, spec.curSplitShape, 0,maxY,minZ) |
613 | cutTooLow = cutTooLow or y < 0.01 |
614 | _,y,_ = localToLocal(spec.cutNode, spec.curSplitShape, 0,maxY,maxZ) |
615 | cutTooLow = cutTooLow or y < 0.01 |
616 | if cutTooLow then |
617 | spec.curSplitShape = nil |
618 | end |
619 | end |
620 | end |
621 | |
622 | if spec.curSplitShape == nil and spec.cutTimer > -1 then |
623 | SpecializationUtil.raiseEvent(self, "onCutTree", 0, false) |
624 | if g_server ~= nil then |
625 | g_server:broadcastEvent(WoodHarvesterOnCutTreeEvent.new(self, 0), nil, nil, self) |
626 | end |
627 | end |
628 | |
629 | end |
630 | end |
631 | |
632 | if self.isServer then |
633 | if spec.attachedSplitShape == nil then |
634 | if spec.cutReleasedComponentJoint ~= nil and spec.cutReleasedComponentJointRotLimitX ~= 0 then |
635 | spec.cutReleasedComponentJointRotLimitX = math.max(0, spec.cutReleasedComponentJointRotLimitX - spec.cutReleasedComponentJointRotLimitXSpeed*dt) |
636 | setJointRotationLimit(spec.cutReleasedComponentJoint.jointIndex, 0, true, 0, spec.cutReleasedComponentJointRotLimitX) |
637 | end |
638 | if spec.cutReleasedComponentJoint2 ~= nil and spec.cutReleasedComponentJoint2RotLimitX ~= 0 then |
639 | spec.cutReleasedComponentJoint2RotLimitX = math.max(spec.cutReleasedComponentJoint2RotLimitX-spec.cutReleasedComponentJoint2RotLimitXSpeed*dt, 0) |
640 | setJointRotationLimit(spec.cutReleasedComponentJoint2.jointIndex, 0, true, -spec.cutReleasedComponentJoint2RotLimitX, spec.cutReleasedComponentJoint2RotLimitX) |
641 | end |
642 | end |
643 | end |
644 | |
645 | if self.isServer then |
646 | if self.playDelayedGrabAnimationTime ~= nil then |
647 | if self.playDelayedGrabAnimationTime < g_currentMission.time then |
648 | self.playDelayedGrabAnimationTime = nil |
649 | if self:getAnimationTime(spec.grabAnimation.name) > 0 then |
650 | if spec.grabAnimation.name ~= nil and spec.attachedSplitShape == nil then |
651 | if spec.grabAnimation.speedScale > 0 then |
652 | self:setAnimationStopTime(spec.grabAnimation.name, 0) |
653 | else |
654 | self:setAnimationStopTime(spec.grabAnimation.name, 1) |
655 | end |
656 | self:playAnimation(spec.grabAnimation.name, -spec.grabAnimation.speedScale, self:getAnimationTime(spec.grabAnimation.name), false) |
657 | end |
658 | end |
659 | end |
660 | end |
661 | end |
662 | |
663 | if self.isClient then |
664 | local actionEvent = spec.actionEvents[InputAction.IMPLEMENT_EXTRA2] |
665 | if actionEvent ~= nil then |
666 | local showAction = false |
667 | if spec.hasAttachedSplitShape then |
668 | if not spec.isAttachedSplitShapeMoving and self:getAnimationTime(spec.cutAnimation.name) == 1 then |
669 | showAction = true |
670 | end |
671 | elseif spec.curSplitShape ~= nil then |
672 | showAction = true |
673 | end |
674 | |
675 | g_inputBinding:setActionEventActive(actionEvent.actionEventId, showAction) |
676 | end |
677 | |
678 | actionEvent = spec.actionEvents[InputAction.IMPLEMENT_EXTRA3] |
679 | if actionEvent ~= nil then |
680 | g_inputBinding:setActionEventActive(actionEvent.actionEventId, not spec.isAttachedSplitShapeMoving) |
681 | if not spec.isAttachedSplitShapeMoving then |
682 | g_inputBinding:setActionEventText(actionEvent.actionEventId, string.format(spec.texts.actionChangeCutLength, string.format("%.1f", spec.currentCutLength))) |
683 | end |
684 | end |
685 | |
686 | end |
687 | end |
948 | function WoodHarvester:woodHarvesterSplitShapeCallback(shape, isBelow, isAbove, minY, maxY, minZ, maxZ) |
949 | local spec = self.spec_woodHarvester |
950 | |
951 | g_currentMission:addKnownSplitShape(shape) |
952 | g_treePlantManager:addingSplitShape(shape, self.shapeBeingCut, self.shapeBeingCutIsTree) |
953 | |
954 | if spec.attachedSplitShape == nil and isAbove and not isBelow and spec.cutAttachNode ~= nil and spec.cutAttachReferenceNode ~= nil then |
955 | spec.attachedSplitShape = shape |
956 | spec.lastTreeSize = {minY, maxY, minZ, maxZ} |
957 | |
958 | -- Current tree center (mid of cut area) |
959 | local treeCenterX, treeCenterY, treeCenterZ = localToWorld(spec.cutNode, 0, (minY+maxY)*0.5, (minZ+maxZ)*0.5) |
960 | |
961 | if spec.loadedSplitShapeFromSavegame then |
962 | if spec.lastTreeJointPos ~= nil then |
963 | treeCenterX, treeCenterY, treeCenterZ = localToWorld(shape, unpack(spec.lastTreeJointPos)) |
964 | end |
965 | |
966 | spec.loadedSplitShapeFromSavegame = false |
967 | end |
968 | spec.lastTreeJointPos = {worldToLocal(shape, treeCenterX, treeCenterY, treeCenterZ)} |
969 | |
970 | -- Target tree center (half tree size in front of the reference node) |
971 | local x,y,z = localToWorld(spec.cutAttachReferenceNode, 0, 0, (maxZ-minZ)*0.5) |
972 | |
973 | local dx,dy,dz = localDirectionToWorld(shape, 0,0,1) |
974 | |
975 | local upx,upy,upz = localDirectionToWorld(spec.cutAttachReferenceNode, 0,1,0) |
976 | local sideX,sideY,sizeZ = MathUtil.crossProduct(upx,upy,upz, dx,dy,dz) |
977 | dx,dy,dz = MathUtil.crossProduct(sideX,sideY,sizeZ, upx,upy,upz) -- Note: we want the up axis to be exact, thus orthogonalize the direction here |
978 | I3DUtil.setWorldDirection(spec.cutAttachHelperNode, dx,dy,dz, upx,upy,upz, 2) |
979 | |
980 | local constr = JointConstructor.new() |
981 | constr:setActors(spec.cutAttachNode, shape) |
982 | -- Note: we assume that the direction of the tree is equal to the y axis |
983 | constr:setJointTransforms(spec.cutAttachHelperNode, shape) |
984 | constr:setJointWorldPositions(x,y,z, treeCenterX,treeCenterY,treeCenterZ) |
985 | |
986 | constr:setRotationLimit(0, 0, 0) |
987 | constr:setRotationLimit(1, 0, 0) |
988 | constr:setRotationLimit(2, 0, 0) |
989 | |
990 | constr:setEnableCollision(false) |
991 | |
992 | spec.attachedSplitShapeJointIndex = constr:finalize() |
993 | |
994 | if spec.cutReleasedComponentJoint ~= nil then |
995 | spec.cutReleasedComponentJointRotLimitX = math.pi*0.9 |
996 | if spec.cutReleasedComponentJoint.jointIndex ~= 0 then |
997 | setJointRotationLimit(spec.cutReleasedComponentJoint.jointIndex, 0, true, 0, spec.cutReleasedComponentJointRotLimitX) |
998 | end |
999 | end |
1000 | if spec.cutReleasedComponentJoint2 ~= nil then |
1001 | spec.cutReleasedComponentJoint2RotLimitX = math.pi*0.9 |
1002 | if spec.cutReleasedComponentJoint2.jointIndex ~= 0 then |
1003 | setJointRotationLimit(spec.cutReleasedComponentJoint2.jointIndex, 0, true, -spec.cutReleasedComponentJoint2RotLimitX, spec.cutReleasedComponentJoint2RotLimitX) |
1004 | end |
1005 | end |
1006 | |
1007 | spec.attachedSplitShapeX, spec.attachedSplitShapeY, spec.attachedSplitShapeZ = worldToLocal(shape, treeCenterX,treeCenterY,treeCenterZ) |
1008 | spec.attachedSplitShapeLastCutY = spec.attachedSplitShapeY |
1009 | spec.attachedSplitShapeStartY = spec.attachedSplitShapeY |
1010 | spec.attachedSplitShapeTargetY = spec.attachedSplitShapeY |
1011 | |
1012 | local radius = ((maxY - minY) + (maxZ - minZ)) / 4 |
1013 | SpecializationUtil.raiseEvent(self, "onCutTree", radius, self.shapeBeingCutIsNew) |
1014 | if g_server ~= nil then |
1015 | g_server:broadcastEvent(WoodHarvesterOnCutTreeEvent.new(self, radius), nil, nil, self) |
1016 | end |
1017 | end |
1018 | end |