Script v1_7_1_0
- AI
- Animals
- Collections
- Contracts
- Debug
- Economy
- Elements
- EnvironmentalScore
- Errors
- Events
- GUI
- Handtools
- Hud
- I3d
- Input
- Jobs
- Maps
- Materials
- Misc
- AbstractManager
- AdditionalFieldBuyInfo
- BaleManager
- ConnectionHoseManager
- CropSensorLinkageData
- ExtendedWeedControl
- FillTypeManager
- FruitTypeManager
- ManureSensorLinkageData
- ProductionChainManager
- SplitTypeManager
- SprayTypeManager
- TensionBeltManager
- Timer
- ToolTypeManager
- TreePlantManager
- Objects
- Parameters
- Placeables
- Placement
- Player
- Shop
- Sounds
- Specialization
- Specializations
- StateMachine
- Statistics
- Tasks
- Triggers
- Utils
- Vehicles
Engine v1_7_1_0
- AI
- Animation
- Camera
- Entity
- Fillplanes
- general
- General
- I3D
- Input
- Lighting
- Math
- Network
- Node
- NoteNode
- Overlays
- Particle System
- Physics
- Rendering
- Scenegraph
- Shape
- Sound
- Spline
- String
- Terrain Detail
- Text Rendering
- Tire Track
- VoiceChat
- XML
Foundation Reference
TreePlantManager
ParentAbstractManagerFunctions
- addClientTree
- addingSplitShape
- addTreeCutJoint
- canPlantTree
- cleanupDeletedTrees
- cutTreeTrunkCallback
- deleteTreesData
- getClientTree
- getTreeTypeDescFromIndex
- getTreeTypeDescFromName
- getTreeTypeFilename
- initDataStructures
- initialize
- loadDefaultTypes
- loadFromXMLFile
- loadMapData
- loadTreeNode
- loadTreeTrunk
- loadTreeTypes
- new
- plantTree
- readFromServerStream
- registerTreeType
- removeClientTree
- removingSplitShape
- saveToXMLFile
- setSplitShapeLeafScaleAndVariation
- unloadMapData
- updateTrees
- writeToClientStream
addClientTree
DescriptionDefinitionaddClientTree()Code
720 | function TreePlantManager:addClientTree(serverSplitShapeFileId, nodeId) |
721 | if self.treesData ~= nil then |
722 | self.treesData.clientTrees[serverSplitShapeFileId] = nodeId |
723 | end |
724 | end |
addingSplitShape
DescriptionDefinitionaddingSplitShape()Code
748 | function TreePlantManager:addingSplitShape(shape, oldShape, fromTree) |
749 | local state |
750 | local variation |
751 | |
752 | -- If a parent is provided, copy the info if we still actively update |
753 | if oldShape ~= nil and self.activeDecayingSplitShapes[oldShape] ~= nil then |
754 | state = self.activeDecayingSplitShapes[oldShape].state |
755 | variation = self.activeDecayingSplitShapes[oldShape].variation |
756 | elseif fromTree then |
757 | state = 1 |
758 | local x, y, z = getWorldTranslation(shape) |
759 | variation = x + y + z |
760 | else |
761 | state = 0 |
762 | variation = 80 |
763 | end |
764 | |
765 | -- With no children, the shape has no branches and we need to update nothing |
766 | -- And as cuts from this item cannot have branches either, we do not need to store |
767 | -- it for parent state either. |
768 | if state ~= nil and getNumOfChildren(shape) > 0 then |
769 | self.activeDecayingSplitShapes[shape] = {state=state, variation=variation} |
770 | |
771 | self:setSplitShapeLeafScaleAndVariation(shape, state, variation) |
772 | end |
773 | |
774 | g_messageCenter:publish(MessageType.TREE_SHAPE_CUT, oldShape, shape) |
775 | end |
addTreeCutJoint
DescriptionDefinitionaddTreeCutJoint()Code
482 | function TreePlantManager:addTreeCutJoint(jointIndex, shape, nx,ny,nz, maxAngle, maxLifetime) |
483 | local treesData = self.treesData |
484 | local lnx,lny,lnz = worldDirectionToLocal(shape, nx,ny,nz) |
485 | local joint = {jointIndex=jointIndex, shape=shape, nx=nx,ny=ny,nz=nz, lnx=lnx,lny=lny,lnz=lnz, maxCosAngle=math.cos(maxAngle), destroyTime=g_currentMission.time+maxLifetime} |
486 | treesData.treeCutJoints[joint] = joint |
487 | end |
canPlantTree
DescriptionDefinitioncanPlantTree()Code
178 | function TreePlantManager:canPlantTree() |
179 | local totalNumSplit, numSplit = getNumOfSplitShapes() |
180 | local numUnsplit = totalNumSplit - numSplit |
181 | return (numUnsplit + self.numTreesWithoutSplits) < TreePlantManager.MAX_NUM_OF_SPLITSHAPES |
182 | end |
cleanupDeletedTrees
DescriptionDefinitioncleanupDeletedTrees()Code
491 | function TreePlantManager:cleanupDeletedTrees() |
492 | local treesData = self.treesData |
493 | |
494 | local numGrowingTrees = #treesData.growingTrees |
495 | local i = 1 |
496 | while i<=numGrowingTrees do |
497 | local tree = treesData.growingTrees[i] |
498 | -- Check if the tree has been cut in the mean time |
499 | if getNumOfChildren(tree.node) == 0 then |
500 | -- The tree has been removed completely, remove from list |
501 | table.remove(treesData.growingTrees, i) |
502 | numGrowingTrees = numGrowingTrees-1 |
503 | delete(tree.node) |
504 | |
505 | if not tree.hasSplitShapes then |
506 | self.numTreesWithoutSplits = math.max(self.numTreesWithoutSplits - 1, 0) |
507 | treesData.numTreesWithoutSplits = math.max(treesData.numTreesWithoutSplits - 1, 0) |
508 | end |
509 | else |
510 | i = i+1 |
511 | end |
512 | end |
513 | local numSplitTrees = #treesData.splitTrees |
514 | local i = 1 |
515 | while i<=numSplitTrees do |
516 | local tree = treesData.splitTrees[i] |
517 | -- Check if the tree has been cut in the mean time |
518 | if getNumOfChildren(tree.node) == 0 then |
519 | -- The tree has been removed completely, remove from list |
520 | table.remove(treesData.splitTrees, i) |
521 | numSplitTrees = numSplitTrees-1 |
522 | delete(tree.node) |
523 | |
524 | if not tree.hasSplitShapes then |
525 | self.numTreesWithoutSplits = math.max(self.numTreesWithoutSplits - 1, 0) |
526 | treesData.numTreesWithoutSplits = math.max(treesData.numTreesWithoutSplits - 1, 0) |
527 | end |
528 | else |
529 | i = i+1 |
530 | end |
531 | end |
532 | end |
cutTreeTrunkCallback
DescriptionDefinitioncutTreeTrunkCallback()Code
302 | function TreePlantManager:cutTreeTrunkCallback(shape, isBelow, isAbove, minY, maxY, minZ, maxZ) |
303 | self:addingSplitShape(shape, self.shapeBeingCut) |
304 | table.insert(self.loadTreeTrunkData.parts, {shape=shape, isBelow=isBelow, isAbove=isAbove, minY=minY, maxY=maxY, minZ=minZ, maxZ=maxZ}) |
305 | end |
deleteTreesData
DescriptionDefinitiondeleteTreesData()Code
57 | function TreePlantManager:deleteTreesData() |
58 | if self.treesData ~= nil then |
59 | delete(self.treesData.rootNode) |
60 | self.numTreesWithoutSplits = math.max(self.numTreesWithoutSplits - self.treesData.numTreesWithoutSplits, 0) |
61 | self:initDataStructures() |
62 | end |
63 | end |
getClientTree
DescriptionDefinitiongetClientTree()Code
736 | function TreePlantManager:getClientTree(serverSplitShapeFileId) |
737 | if self.treesData ~= nil then |
738 | return self.treesData.clientTrees[serverSplitShapeFileId] |
739 | end |
740 | end |
getTreeTypeDescFromIndex
DescriptionDefinitiongetTreeTypeDescFromIndex()Code
701 | function TreePlantManager:getTreeTypeDescFromIndex(index) |
702 | if self.treeTypes ~= nil then |
703 | return self.treeTypes[index] |
704 | end |
705 | return nil |
706 | end |
getTreeTypeDescFromName
DescriptionDefinitiongetTreeTypeDescFromName()Code
710 | function TreePlantManager:getTreeTypeDescFromName(name) |
711 | if self.nameToTreeType ~= nil and name ~= nil then |
712 | name = name:upper() |
713 | return self.nameToTreeType[name] |
714 | end |
715 | return nil |
716 | end |
getTreeTypeFilename
DescriptionDefinitiongetTreeTypeFilename()Code
168 | function TreePlantManager:getTreeTypeFilename(treeTypeDesc, growthState) |
169 | if treeTypeDesc == nil then |
170 | return nil |
171 | end |
172 | |
173 | return treeTypeDesc.treeFilenames[math.min(growthState, #treeTypeDesc.treeFilenames)] |
174 | end |
initDataStructures
DescriptionDefinitioninitDataStructures()Code
27 | function TreePlantManager:initDataStructures() |
28 | self.treeTypes = {} |
29 | self.indexToTreeType = {} |
30 | self.nameToTreeType = {} |
31 | self.treeFileCache = {} |
32 | |
33 | self.numTreesWithoutSplits = 0 |
34 | |
35 | self.activeDecayingSplitShapes = {} |
36 | self.updateDecayDtGame = 0 |
37 | end |
initialize
DescriptionDefinitioninitialize()Code
41 | function TreePlantManager:initialize() |
42 | local rootNode = createTransformGroup("trees") |
43 | link(getRootNode(), rootNode) |
44 | |
45 | self.treesData = {} |
46 | self.treesData.rootNode = rootNode |
47 | self.treesData.growingTrees = {} |
48 | self.treesData.splitTrees = {} |
49 | self.treesData.clientTrees = {} |
50 | self.treesData.updateDtGame = 0 |
51 | self.treesData.treeCutJoints = {} |
52 | self.treesData.numTreesWithoutSplits = 0 |
53 | end |
loadDefaultTypes
DescriptionDefinitionloadDefaultTypes()Code
67 | function TreePlantManager:loadDefaultTypes(missionInfo, baseDirectory) |
68 | local xmlFile = loadXMLFile("treeTypes", "data/maps/maps_treeTypes.xml") |
69 | self:loadTreeTypes(xmlFile, missionInfo, baseDirectory, true) |
70 | delete(xmlFile) |
71 | end |
loadFromXMLFile
DescriptionDefinitionloadFromXMLFile()Code
536 | function TreePlantManager:loadFromXMLFile(xmlFilename) |
537 | if xmlFilename == nil then |
538 | return false |
539 | end |
540 | local xmlFile = loadXMLFile("treePlantXML", xmlFilename) |
541 | if xmlFile == 0 then |
542 | return false |
543 | end |
544 | |
545 | local i = 0 |
546 | while true do |
547 | |
548 | local key = string.format("treePlant.tree(%d)", i) |
549 | if not hasXMLProperty(xmlFile, key) then |
550 | break |
551 | end |
552 | |
553 | local x, y, z = string.getVector(getXMLString(xmlFile, key.."#position")) |
554 | local rx, ry, rz = string.getVector(getXMLString(xmlFile, key.."#rotation")) |
555 | |
556 | rx = math.rad(rx) |
557 | ry = math.rad(ry) |
558 | rz = math.rad(rz) |
559 | |
560 | local treeTypeName = getXMLString(xmlFile, key.."#treeType") |
561 | local treeType = self.nameToTreeType[treeTypeName] |
562 | |
563 | if x ~= nil and y ~= nil and z ~= nil and rx ~= nil and ry ~= nil and rz ~= nil and treeType ~= nil then |
564 | local growthState = Utils.getNoNil(getXMLFloat(xmlFile, key.."#growthState"), 0.0) |
565 | local isGrowing = Utils.getNoNil(getXMLBool(xmlFile, key.."#isGrowing"), true) |
566 | local growthStateI = getXMLInt(xmlFile, key.."#growthStateI") -- note: might be nil, plantTree will use default behaviour (calculate from float growthState) |
567 | local splitShapeFileId = getXMLInt(xmlFile, key.."#splitShapeFileId") -- note: might be nil if not available |
568 | self:plantTree(treeType.index, x,y,z, rx,ry,rz, growthState, growthStateI, isGrowing, splitShapeFileId) |
569 | end |
570 | |
571 | i = i + 1 |
572 | end |
573 | delete(xmlFile) |
574 | |
575 | return true |
576 | end |
loadMapData
DescriptionLoad data on map loadDefinition
loadMapData()Return Values
boolean | true | if loading was successful else false |
76 | function TreePlantManager:loadMapData(xmlFile, missionInfo, baseDirectory) |
77 | TreePlantManager:superClass().loadMapData(self) |
78 | |
79 | self:loadDefaultTypes(missionInfo, baseDirectory) |
80 | return XMLUtil.loadDataFromMapXML(xmlFile, "treeTypes", baseDirectory, self, self.loadTreeTypes, missionInfo, baseDirectory) |
81 | end |
loadTreeNode
DescriptionDefinitionloadTreeNode()Code
229 | function TreePlantManager:loadTreeNode(treeTypeDesc, x,y,z, rx,ry,rz, growthStateI, splitShapeLoadingFileId) |
230 | local treesData = self.treesData |
231 | |
232 | growthStateI = math.min(growthStateI, table.getn(treeTypeDesc.treeFilenames)) |
233 | local i3dFilename = treeTypeDesc.treeFilenames[growthStateI] |
234 | |
235 | if self.treeFileCache[i3dFilename] == nil then |
236 | -- make sure the i3d is loaded, so that the file id will not be used by the i3d clone source |
237 | setSplitShapesLoadingFileId(-1) |
238 | setSplitShapesNextFileId(true) |
239 | local node, requestId = g_i3DManager:loadSharedI3DFile(i3dFilename, false, false) |
240 | if node ~= 0 then |
241 | delete(node) |
242 | self.treeFileCache[i3dFilename] = requestId |
243 | end |
244 | end |
245 | |
246 | setSplitShapesLoadingFileId(Utils.getNoNil(splitShapeLoadingFileId, -1)) |
247 | local splitShapeFileId = setSplitShapesNextFileId() |
248 | |
249 | local treeId, requestId = g_i3DManager:loadSharedI3DFile(i3dFilename, false, false) |
250 | g_i3DManager:releaseSharedI3DFile(requestId) |
251 | |
252 | if treeId ~= 0 then |
253 | link(treesData.rootNode, treeId) |
254 | |
255 | setTranslation(treeId, x,y,z) |
256 | setRotation(treeId, rx,ry,rz) |
257 | -- Split shapes loaded from savegames/streams are placed at world space, so correct the position after we moved our node |
258 | local numChildren = getNumOfChildren(treeId) |
259 | for i=0, numChildren-1 do |
260 | local child = getChildAt(treeId, i) |
261 | if getIsSplitShapeSplit(child) then |
262 | setWorldRotation(child, getRotation(child)) |
263 | setWorldTranslation(child, getTranslation(child)) |
264 | end |
265 | end |
266 | |
267 | addToPhysics(treeId) |
268 | end |
269 | |
270 | local updateRange = 2 |
271 | g_densityMapHeightManager:setCollisionMapAreaDirty(x-updateRange, z-updateRange, x+updateRange, z+updateRange, true) |
272 | g_currentMission.aiSystem:setAreaDirty(x-updateRange, x+updateRange, z-updateRange, z+updateRange) |
273 | return treeId, splitShapeFileId |
274 | end |
loadTreeTrunk
DescriptionDefinitionloadTreeTrunk()Code
278 | function TreePlantManager:loadTreeTrunk(treeTypeDesc, x, y, z, dirX, dirY, dirZ, length, growthState, delimb) |
279 | local treeId, splitShapeFileId = g_treePlantManager:loadTreeNode(treeTypeDesc, x, y, z, 0,0,0, growthState) |
280 | |
281 | if treeId ~= 0 then |
282 | if getFileIdHasSplitShapes(splitShapeFileId) then |
283 | local tree = {} |
284 | tree.node = treeId |
285 | tree.growthState = growthState |
286 | tree.x, tree.y, tree.z = x,y,z |
287 | tree.rx, tree.ry, tree.rz = 0, 0, 0 |
288 | tree.treeType = treeTypeDesc.index |
289 | tree.splitShapeFileId = splitShapeFileId |
290 | tree.hasSplitShapes = getFileIdHasSplitShapes(splitShapeFileId) |
291 | table.insert(self.treesData.splitTrees, tree) |
292 | |
293 | self.loadTreeTrunkData = {framesLeft=2, shape=treeId+2, x=x, y=y, z=z, length=length, offset=0.5, dirX=dirX, dirY=dirY, dirZ=dirZ, delimb=delimb} |
294 | else |
295 | delete(treeId) |
296 | end |
297 | end |
298 | end |
loadTreeTypes
DescriptionDefinitionloadTreeTypes()Code
97 | function TreePlantManager:loadTreeTypes(xmlFile, missionInfo, baseDirectory, isBaseType) |
98 | local i = 0 |
99 | while true do |
100 | local key = string.format("map.treeTypes.treeType(%d)", i) |
101 | if not hasXMLProperty(xmlFile, key) then |
102 | break |
103 | end |
104 | |
105 | local name = getXMLString(xmlFile, key .. "#name") |
106 | local nameI18N = getXMLString(xmlFile, key .. "#nameI18N") |
107 | local growthTimeHours = getXMLFloat(xmlFile, key .. "#growthTimeHours") |
108 | |
109 | if name == nil or nameI18N == nil or growthTimeHours == nil then |
110 | print("Warning: A treetype needs valid values for 'name', 'nameI18N', 'growthTimeHours'. Problem found at '"..tostring(key).."'") |
111 | end |
112 | |
113 | local filenames = {} |
114 | local j = 0 |
115 | while true do |
116 | local stageKey = string.format("%s.stage(%d)", key, j) |
117 | if not hasXMLProperty(xmlFile, stageKey) then |
118 | break |
119 | end |
120 | local filename = getXMLString(xmlFile, stageKey .. "#filename") |
121 | if filename ~= nil then |
122 | local path = Utils.getFilename(filename, baseDirectory) |
123 | table.insert(filenames, path) |
124 | end |
125 | j = j + 1 |
126 | end |
127 | if #filenames == 0 then |
128 | print("Warning: A treetype needs valid 'stage#filename' entries. '"..tostring(key).."'") |
129 | end |
130 | |
131 | self:registerTreeType(name, nameI18N, filenames, growthTimeHours, isBaseType) |
132 | |
133 | i = i + 1 |
134 | end |
135 | |
136 | return true |
137 | end |
new
DescriptionDefinitionnew()Code
20 | function TreePlantManager.new(customMt) |
21 | local self = AbstractManager.new(customMt or TreePlantManager_mt) |
22 | return self |
23 | end |
plantTree
DescriptionDefinitionplantTree()Code
186 | function TreePlantManager:plantTree(treeType, x,y,z, rx,ry,rz, growthState, growthStateI, isGrowing, splitShapeFileId) |
187 | local treesData = self.treesData |
188 | local treeTypeDesc = self.indexToTreeType[treeType] |
189 | if treeTypeDesc ~= nil then |
190 | growthState = MathUtil.clamp(growthState, 0, 1) |
191 | if growthStateI == nil then |
192 | growthStateI = math.floor(growthState*(table.getn(treeTypeDesc.treeFilenames)-1))+1 |
193 | end |
194 | local treeId, splitShapeFileId = self:loadTreeNode(treeTypeDesc, x,y,z, rx,ry,rz, growthStateI, splitShapeFileId) |
195 | |
196 | local tree = {} |
197 | tree.node = treeId |
198 | isGrowing = Utils.getNoNil(isGrowing, true) |
199 | if table.getn(treeTypeDesc.treeFilenames) <= 1 then |
200 | tree.growthState = 1 |
201 | isGrowing = false |
202 | else |
203 | tree.growthState = growthState |
204 | end |
205 | tree.x, tree.y, tree.z = x,y,z |
206 | tree.rx, tree.ry, tree.rz = rx,ry,rz |
207 | tree.treeType = treeType |
208 | tree.splitShapeFileId = splitShapeFileId |
209 | tree.hasSplitShapes = getFileIdHasSplitShapes(splitShapeFileId) |
210 | if isGrowing then |
211 | tree.origSplitShape = getChildAt(treeId, 0) |
212 | table.insert(treesData.growingTrees, tree) |
213 | else |
214 | table.insert(treesData.splitTrees, tree) |
215 | end |
216 | if not tree.hasSplitShapes then |
217 | self.numTreesWithoutSplits = self.numTreesWithoutSplits + 1 |
218 | treesData.numTreesWithoutSplits = treesData.numTreesWithoutSplits + 1 |
219 | end |
220 | |
221 | g_server:broadcastEvent(TreePlantEvent.new(treeType, x,y,z, rx,ry,rz, growthState, splitShapeFileId, isGrowing)) |
222 | |
223 | return treeId |
224 | end |
225 | end |
readFromServerStream
DescriptionDefinitionreadFromServerStream()Code
637 | function TreePlantManager:readFromServerStream(streamId) |
638 | local treesData = self.treesData |
639 | |
640 | local numTrees = streamReadInt32(streamId) |
641 | for i=1, numTrees do |
642 | local treeType = streamReadInt32(streamId) |
643 | local x = streamReadFloat32(streamId) |
644 | local y = streamReadFloat32(streamId) |
645 | local z = streamReadFloat32(streamId) |
646 | local rx = streamReadFloat32(streamId) |
647 | local ry = streamReadFloat32(streamId) |
648 | local rz = streamReadFloat32(streamId) |
649 | local growthStateI = streamReadInt8(streamId) |
650 | local serverSplitShapeFileId = streamReadInt32(streamId) |
651 | |
652 | local treeTypeDesc = self.indexToTreeType[treeType] |
653 | if treeTypeDesc ~= nil then |
654 | local nodeId, splitShapeFileId = self:loadTreeNode(treeTypeDesc, x,y,z, rx,ry,rz, growthStateI, -1) |
655 | setSplitShapesFileIdMapping(splitShapeFileId, serverSplitShapeFileId) |
656 | treesData.clientTrees[serverSplitShapeFileId] = nodeId |
657 | end |
658 | end |
659 | end |
registerTreeType
DescriptionDefinitionregisterTreeType()Code
141 | function TreePlantManager:registerTreeType(name, nameI18N, treeFilenames, growthTimeHours, isBaseType) |
142 | name = string.upper(name) |
143 | |
144 | if isBaseType and self.nameToTreeType[name] ~= nil then |
145 | print("Warning: TreeType '"..tostring(name).."' already exists. Ignoring treeType!") |
146 | return nil |
147 | end |
148 | |
149 | local treeType = self.nameToTreeType[name] |
150 | if treeType == nil then |
151 | treeType = {} |
152 | treeType.name = name |
153 | treeType.nameI18N = nameI18N |
154 | treeType.index = #self.treeTypes + 1 |
155 | table.insert(self.treeTypes, treeType) |
156 | self.indexToTreeType[treeType.index] = treeType |
157 | self.nameToTreeType[name] = treeType |
158 | end |
159 | |
160 | treeType.treeFilenames = treeFilenames |
161 | treeType.growthTimeHours = growthTimeHours |
162 | |
163 | return treeType |
164 | end |
removeClientTree
DescriptionDefinitionremoveClientTree()Code
728 | function TreePlantManager:removeClientTree(serverSplitShapeFileId) |
729 | if self.treesData ~= nil then |
730 | self.treesData.clientTrees[serverSplitShapeFileId] = nil |
731 | end |
732 | end |
removingSplitShape
DescriptionRemove any known state about a split shapeDefinition
removingSplitShape()Code
780 | function TreePlantManager:removingSplitShape(shape) |
781 | -- At this point the shape does not exist anymore! |
782 | self.activeDecayingSplitShapes[shape] = nil |
783 | end |
saveToXMLFile
DescriptionDefinitionsaveToXMLFile()Code
580 | function TreePlantManager:saveToXMLFile(xmlFilename) |
581 | ---- save mappings to xml |
582 | local xmlFile = createXMLFile("treePlantXML", xmlFilename, "treePlant") |
583 | if xmlFile ~= nil then |
584 | self:cleanupDeletedTrees() |
585 | |
586 | local index = 0 |
587 | for _, tree in pairs(self.treesData.growingTrees) do |
588 | local treeTypeDesc = self:getTreeTypeDescFromIndex(tree.treeType) |
589 | local treeTypeName = treeTypeDesc.name |
590 | local isGrowing = (getChildAt(tree.node, 0) == tree.origSplitShape) |
591 | local growthStateI = math.floor( tree.growthState * (table.getn(treeTypeDesc.treeFilenames) - 1) ) + 1 |
592 | local splitShapeFileId = Utils.getNoNil(tree.splitShapeFileId, -1) |
593 | |
594 | local treeKey = string.format("treePlant.tree(%d)", index) |
595 | setXMLString(xmlFile, treeKey.."#treeType", treeTypeName) |
596 | setXMLString(xmlFile, treeKey.."#position", string.format("%.4f %.4f %.4f", tree.x, tree.y, tree.z)) |
597 | setXMLString(xmlFile, treeKey.."#rotation", string.format("%.4f %.4f %.4f", math.deg(tree.rx), math.deg(tree.ry), math.deg(tree.rz))) |
598 | setXMLFloat(xmlFile, treeKey.."#growthState", tree.growthState) |
599 | setXMLInt(xmlFile, treeKey.."#growthStateI", growthStateI) |
600 | setXMLBool(xmlFile, treeKey.."#isGrowing", isGrowing) |
601 | setXMLInt(xmlFile, treeKey.."#splitShapeFileId", splitShapeFileId) |
602 | |
603 | index = index + 1 |
604 | end |
605 | |
606 | for _, tree in pairs(self.treesData.splitTrees) do |
607 | local treeTypeDesc = self:getTreeTypeDescFromIndex(tree.treeType) |
608 | local treeTypeName = treeTypeDesc.name |
609 | local isGrowing = false |
610 | local growthStateI = math.floor( tree.growthState * (table.getn(treeTypeDesc.treeFilenames) - 1) ) + 1 |
611 | local splitShapeFileId = Utils.getNoNil(tree.splitShapeFileId, -1) |
612 | |
613 | -- Note: we also save growthStateI so that we don't have issues with precision and load a different i3d when loading the savegame |
614 | local treeKey = string.format("treePlant.tree(%d)", index) |
615 | setXMLString(xmlFile, treeKey.."#treeType", treeTypeName) |
616 | setXMLString(xmlFile, treeKey.."#position", string.format("%.4f %.4f %.4f", tree.x, tree.y, tree.z)) |
617 | setXMLString(xmlFile, treeKey.."#rotation", string.format("%.4f %.4f %.4f", math.deg(tree.rx), math.deg(tree.ry), math.deg(tree.rz))) |
618 | setXMLFloat(xmlFile, treeKey.."#growthState", tree.growthState) |
619 | setXMLInt(xmlFile, treeKey.."#growthStateI", growthStateI) |
620 | setXMLBool(xmlFile, treeKey.."#isGrowing", isGrowing) |
621 | setXMLInt(xmlFile, treeKey.."#splitShapeFileId", splitShapeFileId) |
622 | |
623 | index = index + 1 |
624 | end |
625 | |
626 | saveXMLFile(xmlFile) |
627 | delete(xmlFile) |
628 | |
629 | return true |
630 | end |
631 | |
632 | return false |
633 | end |
setSplitShapeLeafScaleAndVariation
DescriptionDefinitionsetSplitShapeLeafScaleAndVariation()Code
787 | function TreePlantManager:setSplitShapeLeafScaleAndVariation(shape, scale, variation) |
788 | -- Splitshape is a trunk, and possibly has attachments. (Engine removes attachments when needed) |
789 | I3DUtil.setShaderParameterRec(shape, "windSnowLeafScale", 0, 0, scale, variation) |
790 | end |
unloadMapData
DescriptionDefinitionunloadMapData()Code
85 | function TreePlantManager:unloadMapData() |
86 | for i3dFilename, requestId in pairs(self.treeFileCache) do |
87 | g_i3DManager:releaseSharedI3DFile(requestId) |
88 | self.treeFileCache[i3dFilename] = true |
89 | end |
90 | |
91 | self:deleteTreesData() |
92 | TreePlantManager:superClass().unloadMapData(self) |
93 | end |
updateTrees
DescriptionDefinitionupdateTrees()Code
309 | function TreePlantManager:updateTrees(dt, dtGame) |
310 | local treesData = self.treesData |
311 | treesData.updateDtGame = treesData.updateDtGame + dtGame |
312 | |
313 | -- update all 60 ingame minutes |
314 | if treesData.updateDtGame > 1000*60*60 then |
315 | self:cleanupDeletedTrees() |
316 | |
317 | local time = treesData.updateDtGame |
318 | local dtHours = time / (1000*60*60) * g_currentMission.environment.timeAdjustment |
319 | treesData.updateDtGame = 0 |
320 | local numGrowingTrees = #treesData.growingTrees |
321 | |
322 | local i = 1 |
323 | while i <= numGrowingTrees do |
324 | local tree = treesData.growingTrees[i] |
325 | |
326 | -- Check if the tree has been cut in the mean time |
327 | if getChildAt(tree.node, 0) ~= tree.origSplitShape then |
328 | -- The tree has been cut, it will not grow anymore |
329 | table.remove(treesData.growingTrees, i) |
330 | numGrowingTrees = numGrowingTrees - 1 |
331 | tree.origSplitShape = nil |
332 | table.insert(treesData.splitTrees, tree) |
333 | else |
334 | local treeTypeDesc = self.indexToTreeType[tree.treeType] |
335 | local numTreeFiles = table.getn(treeTypeDesc.treeFilenames) |
336 | local growthState = tree.growthState |
337 | -- TODO check for collisions |
338 | local oldGrowthStateI = math.floor(growthState * (numTreeFiles - 1)) + 1 |
339 | growthState = math.min(growthState + dtHours / treeTypeDesc.growthTimeHours, 1) |
340 | local growthStateI = math.floor(growthState * (numTreeFiles - 1)) + 1 |
341 | |
342 | tree.growthState = growthState |
343 | if oldGrowthStateI ~= growthStateI and treeTypeDesc.treeFilenames[oldGrowthStateI] ~= treeTypeDesc.treeFilenames[growthStateI] then |
344 | |
345 | -- Delete the old tree |
346 | delete(tree.node) |
347 | |
348 | if not tree.hasSplitShapes then |
349 | self.numTreesWithoutSplits = math.max(self.numTreesWithoutSplits - 1, 0) |
350 | treesData.numTreesWithoutSplits = math.max(treesData.numTreesWithoutSplits - 1, 0) |
351 | end |
352 | |
353 | -- Create the new tree |
354 | local treeId, splitShapeFileId = self:loadTreeNode(treeTypeDesc, tree.x, tree.y, tree.z, tree.rx, tree.ry, tree.rz, growthStateI, -1) |
355 | |
356 | g_server:broadcastEvent(TreeGrowEvent.new(tree.treeType, tree.x, tree.y, tree.z, tree.rx, tree.ry, tree.rz, tree.growthState, splitShapeFileId, tree.splitShapeFileId)) |
357 | |
358 | tree.origSplitShape = getChildAt(treeId, 0) |
359 | tree.splitShapeFileId = splitShapeFileId |
360 | tree.hasSplitShapes = getFileIdHasSplitShapes(splitShapeFileId) |
361 | tree.node = treeId |
362 | |
363 | -- update collision map |
364 | local range = 2.5 |
365 | local x, _, z = getWorldTranslation(treeId) |
366 | g_densityMapHeightManager:setCollisionMapAreaDirty(x-range, z-range, x+range, z+range, true) |
367 | g_currentMission.aiSystem:setAreaDirty(x-range, x+range, z-range, z+range) |
368 | |
369 | if not tree.hasSplitShapes then |
370 | self.numTreesWithoutSplits = self.numTreesWithoutSplits + 1 |
371 | treesData.numTreesWithoutSplits = treesData.numTreesWithoutSplits + 1 |
372 | end |
373 | end |
374 | |
375 | if growthStateI >= numTreeFiles then |
376 | -- Reached max grow level, can't grow anymore |
377 | table.remove(treesData.growingTrees, i) |
378 | numGrowingTrees = numGrowingTrees-1 |
379 | tree.origSplitShape = nil |
380 | table.insert(treesData.splitTrees, tree) |
381 | else |
382 | i = i+1 |
383 | end |
384 | end |
385 | end |
386 | end |
387 | |
388 | local curTime = g_currentMission.time |
389 | for joint in pairs(treesData.treeCutJoints) do |
390 | if joint.destroyTime <= curTime or not entityExists(joint.shape) then |
391 | removeJoint(joint.jointIndex) |
392 | treesData.treeCutJoints[joint] = nil |
393 | else |
394 | local x1,y1,z1 = localDirectionToWorld(joint.shape, joint.lnx, joint.lny, joint.lnz) |
395 | if x1*joint.nx + y1*joint.ny + z1*joint.nz < joint.maxCosAngle then |
396 | removeJoint(joint.jointIndex) |
397 | treesData.treeCutJoints[joint] = nil |
398 | end |
399 | end |
400 | end |
401 | |
402 | if self.loadTreeTrunkData ~= nil then |
403 | self.loadTreeTrunkData.framesLeft = self.loadTreeTrunkData.framesLeft - 1 |
404 | -- first cut and remove upper part of tree |
405 | if self.loadTreeTrunkData.framesLeft == 1 then |
406 | local nx,ny,nz = 0, 1, 0 |
407 | local yx,yy,yz = -1, 0, 0 |
408 | local x,y,z = self.loadTreeTrunkData.x+1, self.loadTreeTrunkData.y, self.loadTreeTrunkData.z-1 |
409 | |
410 | self.loadTreeTrunkData.parts = {} |
411 | |
412 | local shape = self.loadTreeTrunkData.shape |
413 | if shape ~= nil and shape ~= 0 then |
414 | self.shapeBeingCut = shape |
415 | splitShape(shape, x,y+self.loadTreeTrunkData.length+self.loadTreeTrunkData.offset,z, nx,ny,nz, yx,yy,yz, 4, 4, "cutTreeTrunkCallback", self) |
416 | self:removingSplitShape(shape) |
417 | for _, p in pairs(self.loadTreeTrunkData.parts) do |
418 | if p.isAbove then |
419 | delete(p.shape) |
420 | else |
421 | self.loadTreeTrunkData.shape = p.shape |
422 | end |
423 | end |
424 | end |
425 | |
426 | -- second cut lower part to get final length |
427 | elseif self.loadTreeTrunkData.framesLeft == 0 then |
428 | local nx,ny,nz = 0, 1, 0 |
429 | local yx,yy,yz = -1, 0, 0 |
430 | local x,y,z = self.loadTreeTrunkData.x+1, self.loadTreeTrunkData.y, self.loadTreeTrunkData.z-1 |
431 | |
432 | self.loadTreeTrunkData.parts = {} |
433 | local shape = self.loadTreeTrunkData.shape |
434 | if shape ~= nil and shape ~= 0 then |
435 | splitShape(shape, x,y+self.loadTreeTrunkData.offset,z, nx,ny,nz, yx,yy,yz, 4, 4, "cutTreeTrunkCallback", self) |
436 | local finalShape = nil |
437 | for _, p in pairs(self.loadTreeTrunkData.parts) do |
438 | if p.isBelow then |
439 | delete(p.shape) |
440 | else |
441 | finalShape = p.shape |
442 | end |
443 | end |
444 | -- set correct rotation of final chunk |
445 | if finalShape ~= nil then |
446 | if self.loadTreeTrunkData.delimb then |
447 | removeSplitShapeAttachments(finalShape, x,y+self.loadTreeTrunkData.offset,z, nx,ny,nz, yx,yy,yz, self.loadTreeTrunkData.length, 4, 4) |
448 | end |
449 | |
450 | removeFromPhysics(finalShape) |
451 | setDirection(finalShape, 0, -1, 0, self.loadTreeTrunkData.dirX, self.loadTreeTrunkData.dirY, self.loadTreeTrunkData.dirZ) |
452 | addToPhysics(finalShape) |
453 | else |
454 | Logging.error("Unable to cut tree trunk with length '%s'. Try using a different value", self.loadTreeTrunkData.length) |
455 | end |
456 | end |
457 | |
458 | self.loadTreeTrunkData = nil |
459 | end |
460 | end |
461 | |
462 | self.updateDecayDtGame = self.updateDecayDtGame + dtGame |
463 | if self.updateDecayDtGame > TreePlantManager.DECAY_INTERVAL then |
464 | -- Update seasonal state of active split shapes |
465 | for shape, data in pairs(self.activeDecayingSplitShapes) do |
466 | if not entityExists(shape) then |
467 | self.activeDecayingSplitShapes[shape] = nil |
468 | elseif data.state > 0 then |
469 | local newState = math.max(data.state - TreePlantManager.DECAY_DURATION_INV * self.updateDecayDtGame, 0) |
470 | |
471 | self:setSplitShapeLeafScaleAndVariation(shape, newState, data.variation) |
472 | self.activeDecayingSplitShapes[shape].state = newState |
473 | end |
474 | end |
475 | |
476 | self.updateDecayDtGame = 0 |
477 | end |
478 | end |
writeToClientStream
DescriptionDefinitionwriteToClientStream()Code
663 | function TreePlantManager:writeToClientStream(streamId) |
664 | local treesData = self.treesData |
665 | |
666 | self:cleanupDeletedTrees() |
667 | |
668 | local numTrees = #treesData.growingTrees + #treesData.splitTrees |
669 | |
670 | streamWriteInt32(streamId, numTrees) |
671 | for _, tree in pairs(treesData.growingTrees) do |
672 | streamWriteInt32(streamId, tree.treeType) |
673 | streamWriteFloat32(streamId, tree.x) |
674 | streamWriteFloat32(streamId, tree.y) |
675 | streamWriteFloat32(streamId, tree.z) |
676 | streamWriteFloat32(streamId, tree.rx) |
677 | streamWriteFloat32(streamId, tree.ry) |
678 | streamWriteFloat32(streamId, tree.rz) |
679 | local treeTypeDesc = self.indexToTreeType[tree.treeType] |
680 | local growthStateI = math.floor(tree.growthState*(table.getn(treeTypeDesc.treeFilenames)-1))+1 |
681 | streamWriteInt8(streamId, growthStateI) |
682 | streamWriteInt32(streamId, tree.splitShapeFileId) |
683 | end |
684 | for _, tree in pairs(treesData.splitTrees) do |
685 | streamWriteInt32(streamId, tree.treeType) |
686 | streamWriteFloat32(streamId, tree.x) |
687 | streamWriteFloat32(streamId, tree.y) |
688 | streamWriteFloat32(streamId, tree.z) |
689 | streamWriteFloat32(streamId, tree.rx) |
690 | streamWriteFloat32(streamId, tree.ry) |
691 | streamWriteFloat32(streamId, tree.rz) |
692 | local treeTypeDesc = self.indexToTreeType[tree.treeType] |
693 | local growthStateI = math.floor(tree.growthState*(table.getn(treeTypeDesc.treeFilenames)-1))+1 |
694 | streamWriteInt8(streamId, growthStateI) |
695 | streamWriteInt32(streamId, tree.splitShapeFileId) |
696 | end |
697 | end |