329 | function PlaceableHusbandryFence:createHusbandryFence(fence, poleRoot, panelRoot, panelLength) |
330 | |
331 | local numPoleVariations = 0 |
332 | if poleRoot ~= nil then |
333 | numPoleVariations = getNumOfChildren(poleRoot) |
334 | end |
335 | local numPanelVariations = 0 |
336 | if panelRoot ~= nil then |
337 | numPanelVariations = getNumOfChildren(panelRoot) |
338 | end |
339 | |
340 | for k, node in ipairs(fence.nodes) do |
341 | local sx, sy, sz = localToLocal(node, fence.rootNode, 0, 0, 0) |
342 | local posX, posY, posZ = sx, sy, sz |
343 | local rotY = 0 |
344 | local nextNode = fence.nodes[k+1] |
345 | if nextNode ~= nil then |
346 | local ex, _, ez = localToLocal(nextNode, fence.rootNode, 0, 0, 0) |
347 | local distance = MathUtil.vector2Length(ex-sx, ez-sz) |
348 | local dirX, dirZ = MathUtil.vector2Normalize(ex-sx, ez-sz) |
349 | rotY = math.atan2(dirX, dirZ) + math.pi |
350 | |
351 | local usedNumPanels = math.floor(distance / panelLength) |
352 | if distance % panelLength > (panelLength * 0.5) then |
353 | usedNumPanels = usedNumPanels + 1 |
354 | end |
355 | |
356 | local usedPanelLength = distance / usedNumPanels |
357 | |
358 | while distance > 0.001 do |
359 | local part = {} |
360 | |
361 | local pole |
362 | if poleRoot ~= nil and (#fence.parts > 0 or fence.hasStartPole) then |
363 | pole = clone(getChildAt(poleRoot, math.random(0, numPoleVariations-1)), false, false, false) |
364 | else |
365 | pole = createTransformGroup("startPole") |
366 | end |
367 | part.pole = pole |
368 | link(fence.rootNode, pole) |
369 | setRotation(pole, 0, rotY, 0) |
370 | setTranslation(pole, posX, posY, posZ) |
371 | |
372 | if panelRoot ~= nil then |
373 | local panel = clone(getChildAt(panelRoot, math.random(0, numPanelVariations-1)), false, false, false) |
374 | link(fence.rootNode, panel) |
375 | setTranslation(panel, posX, posY, posZ) |
376 | setDirection(panel, dirX, 0, dirZ, 0, 1, 0) |
377 | setScale(panel, 1, 1, usedPanelLength / panelLength) |
378 | part.collision = getChildAt(panel, 0) |
379 | part.visual = getChildAt(panel, 1) |
380 | part.panel = panel |
381 | end |
382 | |
383 | posX = posX + dirX * usedPanelLength |
384 | posZ = posZ + dirZ * usedPanelLength |
385 | distance = distance - usedPanelLength |
386 | part.length = usedPanelLength |
387 | |
388 | table.insert(fence.parts, part) |
389 | end |
390 | else |
391 | local pole |
392 | if poleRoot ~= nil and fence.hasEndPole then |
393 | pole = clone(getChildAt(poleRoot, math.random(0, numPoleVariations-1)), false, false, false) |
394 | else |
395 | pole = createTransformGroup("endPole") |
396 | end |
397 | |
398 | link(fence.rootNode, pole) |
399 | setRotation(pole, 0, rotY, 0) |
400 | setTranslation(pole, posX, posY, posZ) |
401 | table.insert(fence.parts, {pole=pole}) |
402 | end |
403 | end |
404 | |
405 | self:updateHusbandryFence(fence, false) |
406 | end |
172 | function PlaceableHusbandryFence:onFenceI3DLoaded(i3dNode, failedReason, args) |
173 | local fence = args.fence |
174 | local fenceXmlFile = args.fenceXmlFile |
175 | local loadingTask = args.loadingTask |
176 | |
177 | if i3dNode ~= 0 then |
178 | local components = {} |
179 | local i3dMappings = {} |
180 | I3DUtil.loadI3DComponents(i3dNode, components) |
181 | I3DUtil.loadI3DMapping(fenceXmlFile, i3dNode, components, i3dMappings) |
182 | |
183 | local polesRootNode = I3DUtil.indexToObject(components, fenceXmlFile:getString("placeable.fence.poles#node"), i3dMappings) |
184 | local panelsRootNode = I3DUtil.indexToObject(components, fenceXmlFile:getString("placeable.fence.panels#node"), i3dMappings) |
185 | local panelLength = fenceXmlFile:getFloat("placable.fence.panels#length", 2) |
186 | fence.maxAngle = math.rad(fenceXmlFile:getFloat("placeable.fence#maxVerticalAngle", 45)) |
187 | |
188 | self:createHusbandryFence(fence, polesRootNode, panelsRootNode, panelLength) |
189 | |
190 | delete(i3dNode) |
191 | end |
192 | |
193 | fenceXmlFile:delete() |
194 | |
195 | self:finishLoadingTask(loadingTask) |
196 | end |
200 | function PlaceableHusbandryFence:onGateI3DLoaded(i3dNode, failedReason, args) |
201 | local gate = args.gate |
202 | local gateXmlFile = args.gateXmlFile |
203 | local loadingTask = args.loadingTask |
204 | |
205 | if i3dNode ~= 0 then |
206 | local components = {} |
207 | local i3dMappings = {} |
208 | I3DUtil.loadI3DComponents(i3dNode, components) |
209 | I3DUtil.loadI3DMapping(gateXmlFile, i3dNode, components, i3dMappings) |
210 | |
211 | local gateKey = string.format("placeable.fence.gate(%d)", gate.gateIndex-1) |
212 | |
213 | local gateRootNode = I3DUtil.indexToObject(components, gateXmlFile:getString(gateKey .. "#node"), i3dMappings) |
214 | |
215 | if gateRootNode ~= nil then |
216 | |
217 | local animatedObject = AnimatedObject.new(self.isServer, self.isClient) |
218 | animatedObject:setOwnerFarmId(self:getOwnerFarmId(), false) |
219 | |
220 | local saveId = "test" --string.format("AnimatedObject_%s_gate_%d_%d_%d_%d", self.configFileName, segment.x1, segment.z1, segment.x2, segment.x2) |
221 | local builder = animatedObject:builder(gate.filename, saveId) |
222 | |
223 | gateXmlFile:iterate(gateKey .. ".door", function(_, doorKey) |
224 | local doorNode = I3DUtil.indexToObject(gateRootNode, gateXmlFile:getString(doorKey .. "#node"), i3dMappings) |
225 | if doorNode ~= nil then |
226 | local rotation = gateXmlFile:getValue(doorKey .. "#openRotation", nil, true) |
227 | local translation = gateXmlFile:getValue(doorKey .. "#openTranslation", nil, true) |
228 | |
229 | builder:addSimplePart(doorNode, rotation, translation) |
230 | end |
231 | end) |
232 | |
233 | local duration = gateXmlFile:getValue(gateKey .. "#openDuration") * 1000 |
234 | builder:setDuration(duration) |
235 | |
236 | local triggerNode = I3DUtil.indexToObject(gateRootNode, gateXmlFile:getString(gateKey .. "#triggerNode"), i3dMappings) |
237 | builder:setTrigger(triggerNode) |
238 | |
239 | builder:setSounds(gateXmlFile.handle, gateKey ..".sounds", gateRootNode) |
240 | |
241 | local openText = gateXmlFile:getString(gateKey .. "#openText", "action_openGate") |
242 | local closeText = gateXmlFile:getString(gateKey .. "#closeText", "action_closeGate") |
243 | builder:setActions("ACTIVATE_HANDTOOL", openText, nil, closeText) |
244 | |
245 | if builder:build() then |
246 | animatedObject:register(true) |
247 | gate.animatedObject = animatedObject |
248 | else |
249 | animatedObject:delete() |
250 | end |
251 | |
252 | link(gate.node, gateRootNode) |
253 | end |
254 | |
255 | delete(i3dNode) |
256 | end |
257 | |
258 | gateXmlFile:delete() |
259 | |
260 | self:finishLoadingTask(loadingTask) |
261 | end |
65 | function PlaceableHusbandryFence:onLoad(savegame) |
66 | local spec = self.spec_husbandryFence |
67 | spec.fences = {} |
68 | spec.canBePlaced = true |
69 | |
70 | self.xmlFile:iterate("placeable.husbandry.fences.fence", function(_, key) |
71 | local filename = self.xmlFile:getValue(key .. "#filename") |
72 | if filename == nil then |
73 | Logging.xmlWarning(self.xmlFile, "Missing segment filename for '%s'", key) |
74 | return |
75 | end |
76 | |
77 | local fence = {} |
78 | fence.filename = Utils.getFilename(filename, self.baseDirectory) |
79 | fence.nodes = {} |
80 | fence.parts = {} |
81 | fence.rootNode = createTransformGroup("fence") |
82 | link(self.rootNode, fence.rootNode) |
83 | |
84 | fence.hasStartPole = self.xmlFile:getValue(key .. "#hasStartPole", true) |
85 | fence.hasEndPole = self.xmlFile:getValue(key .. "#hasEndPole", true) |
86 | |
87 | self.xmlFile:iterate(key .. ".node", function(_, segmentKey) |
88 | local node = self.xmlFile:getValue(segmentKey .. "#node", nil, self.components, self.i3dMappings) |
89 | |
90 | if node == nil then |
91 | Logging.xmlWarning(self.xmlFile, "Missing fence node for '%s'", segmentKey) |
92 | return false |
93 | end |
94 | |
95 | table.insert(fence.nodes, node) |
96 | |
97 | return true |
98 | end) |
99 | |
100 | if #fence.nodes > 0 then |
101 | local fenceXmlFile = XMLFile.load("fence", fence.filename) |
102 | if fenceXmlFile == nil then |
103 | Logging.xmlWarning(self.xmlFile, "Could not load fence xml file for '%s'", key) |
104 | return |
105 | end |
106 | |
107 | local fenceFilename = fenceXmlFile:getString("placeable.base.filename") |
108 | if fenceFilename == nil then |
109 | Logging.xmlWarning(fenceXmlFile, "Missing fence filename.") |
110 | return |
111 | end |
112 | |
113 | fence.i3dFilename = Utils.getFilename(fenceFilename, self.baseDirectory) |
114 | |
115 | local loadingTask = self:createLoadingTask() |
116 | local arguments = { |
117 | fence = fence, |
118 | fenceXmlFile = fenceXmlFile, |
119 | loadingTask = loadingTask |
120 | } |
121 | fence.sharedLoadRequestId = g_i3DManager:loadSharedI3DFileAsync(fence.i3dFilename, false, false, self.onFenceI3DLoaded, self, arguments) |
122 | end |
123 | |
124 | table.insert(spec.fences, fence) |
125 | end) |
126 | |
127 | spec.gates = {} |
128 | self.xmlFile:iterate("placeable.husbandry.fences.gate", function(_, key) |
129 | local filename = self.xmlFile:getValue(key .. "#filename") |
130 | if filename == nil then |
131 | Logging.xmlWarning(self.xmlFile, "Missing gate filename for '%s'", key) |
132 | return |
133 | end |
134 | |
135 | local gate = {} |
136 | gate.filename = Utils.getFilename(filename, self.baseDirectory) |
137 | gate.node = self.xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings) |
138 | if gate.node == nil then |
139 | Logging.xmlWarning(self.xmlFile, "No gate node defined for '%s'", key) |
140 | return |
141 | end |
142 | gate.gateIndex = self.xmlFile:getValue(key .. "#gateIndex") or 1 |
143 | |
144 | local gateXmlFile = XMLFile.load("gate", gate.filename, Placeable.xmlSchema) |
145 | if gateXmlFile == nil then |
146 | Logging.xmlWarning(self.xmlFile, "Could not load gate xml file for '%s'", key) |
147 | return |
148 | end |
149 | |
150 | local gateFilename = gateXmlFile:getString("placeable.base.filename") |
151 | if gateFilename == nil then |
152 | Logging.xmlWarning(gateXmlFile, "Missing gate filename.") |
153 | return |
154 | end |
155 | |
156 | gate.i3dFilename = Utils.getFilename(gateFilename, self.baseDirectory) |
157 | |
158 | local loadingTask = self:createLoadingTask() |
159 | local arguments = { |
160 | gate = gate, |
161 | gateXmlFile = gateXmlFile, |
162 | loadingTask = loadingTask |
163 | } |
164 | gate.sharedLoadRequestId = g_i3DManager:loadSharedI3DFileAsync(gate.i3dFilename, false, false, self.onGateI3DLoaded, self, arguments) |
165 | |
166 | table.insert(spec.gates, gate) |
167 | end) |
168 | end |
50 | function PlaceableHusbandryFence.registerXMLPaths(schema, basePath) |
51 | schema:setXMLSpecializationType("Husbandry") |
52 | basePath = basePath .. ".husbandry.fences" |
53 | schema:register(XMLValueType.STRING, basePath .. ".fence(?)#filename", "Fence filename") |
54 | schema:register(XMLValueType.BOOL, basePath .. ".fence(?)#hasStartPole", "Has start pole") |
55 | schema:register(XMLValueType.BOOL, basePath .. ".fence(?)#hasEndPole", "Has end pole") |
56 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".fence(?).node(?)#node", "Fence node") |
57 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".gate(?)#node", "Gate node") |
58 | schema:register(XMLValueType.STRING, basePath .. ".gate(?)#filename", "Gate filename") |
59 | schema:register(XMLValueType.INT, basePath .. ".gate(?)#gateIndex", "Gate index") |
60 | schema:setXMLSpecializationType() |
61 | end |
410 | function PlaceableHusbandryFence:updateHusbandryFence(fence, updateCollision) |
411 | local success = true |
412 | for index, part in ipairs(fence.parts) do |
413 | local pole = part.pole |
414 | local x, y, z = getWorldTranslation(pole) |
415 | y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, y, z) |
416 | setWorldTranslation(pole, x, y, z) |
417 | |
418 | local panel = part.panel |
419 | if panel ~= nil then |
420 | setWorldTranslation(panel, x, y, z) |
421 | end |
422 | |
423 | local lastPart = fence.parts[index - 1] |
424 | if lastPart ~= nil then |
425 | if lastPart.panel ~= nil then |
426 | local _, yDif, zDif = localToLocal(pole, lastPart.pole, 0, 0, 0) |
427 | local angle = math.atan2(math.abs(yDif), math.abs(zDif)) |
428 | |
429 | if angle > fence.maxAngle then |
430 | success = false |
431 | end |
432 | |
433 | I3DUtil.setShaderParameterRec(lastPart.visual, "yOffset", yDif, 0, 0, 0, false, nil) |
434 | |
435 | if updateCollision then |
436 | x, y, z = getTranslation(lastPart.collision) |
437 | local xDir, yDir, zDir = 0, yDif, lastPart.length |
438 | local length = MathUtil.vector3Length(xDir, yDir, zDir) |
439 | xDir, yDir, zDir = MathUtil.vector3Normalize(xDir, yDir, zDir) |
440 | |
441 | local offset = (length-lastPart.length) * 0.5 |
442 | x = x + xDir*offset |
443 | y = y + yDir*offset |
444 | z = z + zDir*offset |
445 | |
446 | setDirection(lastPart.collision, xDir, yDir, zDir, 0, 1, 0) |
447 | setTranslation(lastPart.collision, x, y, z) |
448 | end |
449 | end |
450 | end |
451 | end |
452 | |
453 | return success |
454 | end |