32 | function Chainsaw:load(xmlFilename, player) |
33 | if not Chainsaw:superClass().load(self, xmlFilename, player) then |
34 | return false |
35 | end |
36 | |
37 | local xmlFile = loadXMLFile("TempXML", xmlFilename) |
38 | |
39 | self.rotationZ = 0 |
40 | self.rotationSpeedZ = 0.003 |
41 | self.cutSizeY = 1.1 |
42 | self.cutSizeZ = 1 |
43 | self.isCutting = false |
44 | self.waitingForResetAfterCut = false |
45 | self.cutNode = getChildAt(self.rootNode, 0) |
46 | self.graphicsNode = getChildAt(self.cutNode, 0) |
47 | self.chainNode = getChildAt(self.graphicsNode, 0) |
48 | self.psNode = getChildAt(self.graphicsNode, 1) |
49 | |
50 | self.pricePerSecond = Utils.getNoNil(getXMLFloat(xmlFile, "handTool.chainsaw.pricePerMinute"), 50) / 1000 |
51 | |
52 | if self.isClient then |
53 | self.particleSystems = {} |
54 | |
55 | local i = 0 |
56 | while true do |
57 | local keyPS = string.format("handTool.chainsaw.particleSystems.emitterShape(%d)", i) |
58 | if not hasXMLProperty(xmlFile, keyPS) then |
59 | break |
60 | end |
61 | local emitterShape = Utils.indexToObject(self.rootNode, getXMLString(xmlFile, keyPS.."#node")) |
62 | local particleType = getXMLString(xmlFile, keyPS.."#particleType") |
63 | if emitterShape ~= nil then |
64 | local fillType = FillUtil.FILLTYPE_WOODCHIPS |
65 | local particleSystem = MaterialUtil.getParticleSystem(fillType, particleType) |
66 | if particleSystem ~= nil then |
67 | table.insert(self.particleSystems, ParticleUtil.copyParticleSystem(xmlFile, keyPS, particleSystem, emitterShape)) |
68 | end |
69 | end |
70 | i = i + 1 |
71 | end |
72 | |
73 | if #self.particleSystems == 0 then |
74 | self.particleSystems = nil |
75 | end |
76 | |
77 | self.handNode = Utils.getNoNil(Utils.indexToObject(self.rootNode, getXMLString(xmlFile, "handTool.chainsaw.handNode#index")), self.rootNode) |
78 | self.handNodeRotation = Utils.getRadiansFromString(Utils.getNoNil(getXMLString(xmlFile, "handTool.chainsaw.handNode#rotation"), "0 0 0"), 3) |
79 | |
80 | self.equipmentUVs = Utils.getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, "handTool.chainsaw.equipment#uvs"), "0 0"), 2) |
81 | |
82 | self.chain = Utils.loadScrollers(self.rootNode, xmlFile, "handTool.chainsaw.chain", {}, false) |
83 | table.getn(self.chain) |
84 | |
85 | self.sampleStart = SoundUtil.loadSample(xmlFile, {}, "handTool.chainsaw.startSound", nil, self.baseDirectory) |
86 | self.sampleIdle = SoundUtil.loadSample(xmlFile, {}, "handTool.chainsaw.idleSound", nil, self.baseDirectory) |
87 | self.sampleWorkUp = SoundUtil.loadSample(xmlFile, {}, "handTool.chainsaw.workUpSound", nil, self.baseDirectory) |
88 | self.sampleWork = SoundUtil.loadSample(xmlFile, {}, "handTool.chainsaw.workSound", nil, self.baseDirectory) |
89 | self.sampleWorkDown = SoundUtil.loadSample(xmlFile, {}, "handTool.chainsaw.workDownSound", nil, self.baseDirectory) |
90 | self.sampleStop = SoundUtil.loadSample(xmlFile, {}, "handTool.chainsaw.stopSound", nil, self.baseDirectory) |
91 | self.sampleQuickTap = SoundUtil.loadSample(xmlFile, {}, "handTool.chainsaw.quickTapSound", nil, self.baseDirectory) |
92 | |
93 | self.soundIdlePitchMax = Utils.getNoNil(getXMLFloat(xmlFile, "handTool.chainsaw.idleSound#pitchMax"), 2.0) |
94 | |
95 | self.samplesBranch = {} |
96 | local i=0 |
97 | while true do |
98 | if not hasXMLProperty(xmlFile, string.format("handTool.chainsaw.branchSounds.branchSound(%d)#file",i)) then |
99 | break |
100 | end |
101 | local sampleBranch = SoundUtil.loadSample(xmlFile, {}, string.format("handTool.chainsaw.branchSounds.branchSound(%d)",i), nil, self.baseDirectory) |
102 | table.insert(self.samplesBranch, sampleBranch) |
103 | i = i + 1 |
104 | end |
105 | self.samplesBranchCount = i |
106 | self.samplesBranchActiveTimer = 0 |
107 | |
108 | local filename = getXMLString(xmlFile, "handTool.chainsaw.ringSelector#file") |
109 | if filename ~= nil then |
110 | local i3dNode = Utils.loadSharedI3DFile(filename, self.baseDirectory, false, false, false) |
111 | if i3dNode ~= 0 then |
112 | self.ringSelectorFilename = filename |
113 | self.ringSelector = getChildAt(i3dNode, 0) |
114 | self.ringSelectorScaleOffset = Utils.getNoNil(getXMLFloat(xmlFile, "handTool.chainsaw.ringSelector#scaleOffset"), 0.3) |
115 | setVisibility(self.ringSelector, false) |
116 | link(self.cutNode, self.ringSelector) |
117 | delete(i3dNode) |
118 | end |
119 | end |
120 | end |
121 | |
122 | self.needGloves = true |
123 | self.needHelmet = true |
124 | self.lastWorkTime = 0 |
125 | self.maxWorkTime = 300 |
126 | |
127 | self.speedFactor = 0 |
128 | |
129 | self.offsetY = 0 |
130 | self.moveSpeedY = 0.0001 |
131 | self.speedFactor = 0 |
132 | |
133 | self.startDuration = self.sampleStart.duration * 0.5 -- during the first half of the start sample the idle sample is faded in |
134 | self.startTime = 0 |
135 | |
136 | self.quickTapDuration = 0 |
137 | self.quickTapDurationTime = 250 -- mouse/button presses shorter than this will play the quick tap sound |
138 | self.lastQuickTapTime = 0 |
139 | self.quickTapMinDelay = 250 -- quick tap sound won't play again until after this amount of time |
140 | |
141 | self.workUpStartTime = 0 |
142 | self.workUpDuration = self.sampleWorkUp.duration - 20 -- shorter duration for transition between WorkUp & Work |
143 | self.workUpPlayed = false |
144 | self.workPlaying = false |
145 | |
146 | self.isCutting = false |
147 | self.isHorizontalCut = false |
148 | |
149 | self.currentHandNode = nil |
150 | |
151 | delete(xmlFile) |
152 | |
153 | return true |
154 | end |
182 | function Chainsaw:update(dt, allowInput) |
183 | Chainsaw:superClass().update(self, dt, allowInput) |
184 | |
185 | Utils.updateScrollers(self.chain, dt*self.speedFactor, true) |
186 | |
187 | if self.isServer then |
188 | local price = self.pricePerSecond * (dt / 1000) |
189 | g_currentMission.missionStats:updateStats("expenses", price) |
190 | g_currentMission:addSharedMoney(-price, "vehicleRunningCost") |
191 | end |
192 | |
193 | self.shouldDelimb = false |
194 | |
195 | local lockPlayerInput = false |
196 | |
197 | if allowInput then |
198 | local isCutting = false |
199 | |
200 | if not SoundUtil.isSamplePlaying(self.sampleStart, 1.5*dt) then |
201 | SoundUtil.playSample(self.sampleIdle, 0, 0, nil) |
202 | SoundUtil.playSample(self.sampleWork, 0, 0, 0) |
203 | SoundUtil.setSampleVolume(self.sampleIdle, self.sampleIdle.volume) |
204 | else |
205 | local idleVolume = Utils.clamp((g_currentMission.time - self.startTime) / self.startDuration, 0, self.sampleIdle.volume) |
206 | SoundUtil.setSampleVolume(self.sampleIdle, idleVolume) -- idle sound fades in during start sound |
207 | end |
208 | |
209 | setRotation(self.graphicsNode, math.rad(math.random(-1, 1))*0.1, math.rad(math.random(-1, 1))*0.1, math.rad(-180)) |
210 | |
211 | if self.curSplitShape == nil then |
212 | local input = InputBinding.getDigitalInputAxis(InputBinding.AXIS_ROTATE_HANDTOOL) |
213 | if InputBinding.isAxisZero(input) then |
214 | input = InputBinding.getAnalogInputAxis(InputBinding.AXIS_ROTATE_HANDTOOL) |
215 | end |
216 | lockPlayerInput = input ~= 0 |
217 | |
218 | if input ~= 0 then |
219 | self.rotationZ = Utils.clamp(self.rotationZ + self.rotationSpeedZ*input*dt, -0.1, 1.57) |
220 | setRotation(self.rootNode, 0, 0, self.rotationZ) |
221 | self.player:setIKDirty() |
222 | end |
223 | |
224 | self.offsetY = math.max(self.offsetY - 5*self.moveSpeedY*dt, 0) |
225 | setTranslation(self.graphicsNode, 0, self.offsetY, 0) |
226 | if self.offsetY ~= 0 then |
227 | self.player:setIKDirty() |
228 | end |
229 | end |
230 | |
231 | local shape = 0 |
232 | if not self.waitingForResetAfterCut then |
233 | local x,y,z = getWorldTranslation(self.cutNode) |
234 | local nx,ny,nz = localDirectionToWorld(self.cutNode, 1,0,0) |
235 | local yx,yy,yz = localDirectionToWorld(self.cutNode, 0,1,0) |
236 | if self.curSplitShape ~= nil or self.offsetY == 0 then |
237 | local minY,maxY, minZ,maxZ |
238 | if self.curSplitShape == nil or not entityExists(self.curSplitShape) then |
239 | self.curSplitShape = nil |
240 | shape, minY,maxY, minZ,maxZ = findSplitShape(x,y,z, nx,ny,nz, yx,yy,yz, self.cutSizeY, self.cutSizeZ) |
241 | |
242 | if shape ~= nil and shape ~= 0 then |
243 | local cutTooLow = false |
244 | local _,y1,_ = localToLocal(self.cutNode, shape, 0,minY,minZ) |
245 | local _,y3,_ = localToLocal(self.cutNode, shape, 0,maxY,minZ) |
246 | local _,y4,_ = localToLocal(self.cutNode, shape, 0,maxY,maxZ) |
247 | cutTooLow = y1 < 0.01 or y1 < 0.01 or y3 < 0.03 or y4 < 0.01 |
248 | if not cutTooLow then |
249 | local x1,y1,z1 = localToWorld(self.cutNode, 0,minY,minZ) |
250 | local x2,y2,z2 = localToWorld(self.cutNode, 0,minY,maxZ) |
251 | local x3,y3,z3 = localToWorld(self.cutNode, 0,maxY,minZ) |
252 | local x4,y4,z4 = localToWorld(self.cutNode, 0,maxY,maxZ) |
253 | local h1 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x1,y1,z1) |
254 | local h2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x2,y2,z2) |
255 | local h3 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x3,y3,z3) |
256 | local h4 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x4,y4,z4) |
257 | cutTooLow = h1 < 0.01 or h2 < 0.01 or h3 < 0.03 or h4 < 0.01 |
258 | end |
259 | if cutTooLow then |
260 | self.player.walkingIsLocked = false |
261 | self.curSplitShape = nil |
262 | shape, minY,maxY, minZ,maxZ = 0, nil,nil, nil,nil |
263 | end |
264 | end |
265 | end |
266 | |
267 | self.curSplitShapeMinY = minY |
268 | self.curSplitShapeMaxY = maxY |
269 | self.curSplitShapeMinZ = minZ |
270 | self.curSplitShapeMaxZ = maxZ |
271 | end |
272 | end |
273 | |
274 | if self.ringSelector ~= nil then |
275 | setVisibility(self.ringSelector, (self.curSplitShapeMinY ~= nil or self.curSplitShape ~= nil) and g_woodCuttingMarkerEnabled) |
276 | if g_woodCuttingMarkerEnabled then |
277 | self:updateRingSelector() |
278 | end |
279 | end |
280 | |
281 | if InputBinding.isPressed(InputBinding.ACTIVATE_HANDTOOL) then |
282 | self.quickTapDuration = self.quickTapDuration + dt |
283 | |
284 | self.speedFactor = math.min(self.speedFactor + dt/self.maxWorkTime, 1) |
285 | |
286 | if self.quickTapDuration > self.quickTapDurationTime then |
287 | if not self.workUpPlayed then |
288 | self.workUpStartTime = g_currentMission.time |
289 | SoundUtil.playSample(self.sampleWorkUp, 1, 0, nil) |
290 | self.workUpPlayed = true |
291 | end |
292 | |
293 | self.lastWorkTime = math.min(self.lastWorkTime+dt, self.maxWorkTime) |
294 | |
295 | if g_currentMission.time - self.workUpStartTime >= self.workUpDuration then |
296 | if not SoundUtil.isSamplePlaying(self.sampleWorkUp, 1.5*dt) -- TEST THIS |
297 | and not self.workPlaying then |
298 | SoundUtil.setSampleVolume(self.sampleWork, 1) |
299 | self.workPlaying = true |
300 | end |
301 | end |
302 | end |
303 | |
304 | if not self.waitingForResetAfterCut then |
305 | self.shouldDelimb = true |
306 | |
307 | local x,y,z = getWorldTranslation(self.cutNode) |
308 | local nx,ny,nz = localDirectionToWorld(self.cutNode, 1,0,0) |
309 | local yx,yy,yz = localDirectionToWorld(self.cutNode, 0,1,0) |
310 | |
311 | if self.curSplitShape ~= nil or self.offsetY == 0 then |
312 | if self.curSplitShape ~= nil and entityExists(self.curSplitShape) then |
313 | lockPlayerInput = true |
314 | local minY,maxY, minZ,maxZ = testSplitShape(self.curSplitShape, x,y,z, nx,ny,nz, yx,yy,yz, self.cutSizeY, self.cutSizeZ) |
315 | if minY == nil then |
316 | -- cancel cutting if shape can't be cut anymore from current position |
317 | self.player.walkingIsLocked = false |
318 | self.curSplitShape = nil |
319 | SoundUtil.setSamplePitch(self.sampleWork, 1) |
320 | else |
321 | local cutTooLow = false |
322 | local _,y1,_ = localToLocal(self.cutNode, self.curSplitShape, 0,minY,minZ) |
323 | local _,y3,_ = localToLocal(self.cutNode, self.curSplitShape, 0,maxY,minZ) |
324 | local _,y4,_ = localToLocal(self.cutNode, self.curSplitShape, 0,maxY,maxZ) |
325 | cutTooLow = y1 < 0.01 or y1 < 0.01 or y3 < 0.03 or y4 < 0.01 |
326 | if not cutTooLow then |
327 | local x1,y1,z1 = localToWorld(self.cutNode, 0,minY,minZ) |
328 | local x2,y2,z2 = localToWorld(self.cutNode, 0,minY,maxZ) |
329 | local x3,y3,z3 = localToWorld(self.cutNode, 0,maxY,minZ) |
330 | local x4,y4,z4 = localToWorld(self.cutNode, 0,maxY,maxZ) |
331 | local h1 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x1,y1,z1) |
332 | local h2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x2,y2,z2) |
333 | local h3 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x3,y3,z3) |
334 | local h4 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x4,y4,z4) |
335 | cutTooLow = h1 < 0.01 or h2 < 0.01 or h3 < 0.03 or h4 < 0.01 |
336 | end |
337 | if cutTooLow then |
338 | self.player.walkingIsLocked = false |
339 | self.curSplitShape = nil |
340 | end |
341 | end |
342 | self.curSplitShapeMinY = minY |
343 | self.curSplitShapeMaxY = maxY |
344 | self.curSplitShapeMinZ = minZ |
345 | self.curSplitShapeMaxZ = maxZ |
346 | else |
347 | if shape ~= 0 then |
348 | self.player.walkingIsLocked = true |
349 | self.curSplitShape = shape |
350 | end |
351 | end |
352 | |
353 | self:updateRingSelector() |
354 | |
355 | if self.curSplitShape ~= nil then |
356 | isCutting = true |
357 | self.offsetY = self.offsetY + self.moveSpeedY*dt |
358 | setTranslation(self.graphicsNode, 0, self.offsetY, 0) |
359 | self.player:setIKDirty() |
360 | |
361 | if self.offsetY > self.curSplitShapeMinY then |
362 | self.lastWorkTime = math.min(self.lastWorkTime, self.maxWorkTime*0.7) |
363 | SoundUtil.setSamplePitch(self.sampleWork, 0.96) |
364 | end |
365 | |
366 | if self.offsetY > self.curSplitShapeMaxY then |
367 | if g_currentMission:getIsServer() then |
368 | ChainsawUtil.cutSplitShape(self.curSplitShape, x,y,z, nx,ny,nz, yx,yy,yz, self.cutSizeY, self.cutSizeZ) |
369 | else |
370 | g_client:getServerConnection():sendEvent(ChainsawCutEvent:new(self.curSplitShape, x,y,z, nx,ny,nz, yx,yy,yz, self.cutSizeY, self.cutSizeZ)) |
371 | end |
372 | self.player.walkingIsLocked = false |
373 | self.waitingForResetAfterCut = true |
374 | self.curSplitShape = nil |
375 | self.curSplitShapeMinY = nil |
376 | SoundUtil.setSamplePitch(self.sampleWork, 1) |
377 | end |
378 | end |
379 | end |
380 | end |
381 | else |
382 | self.speedFactor = math.max(self.speedFactor - dt/self.maxWorkTime, 0) |
383 | self.waitingForResetAfterCut = false |
384 | self.player.walkingIsLocked = false |
385 | self.curSplitShape = nil |
386 | self.curSplitShapeMinY = nil |
387 | self.lastWorkTime = math.max(self.lastWorkTime - dt, 0) |
388 | |
389 | if self.quickTapDuration > 0 then |
390 | if self.quickTapDuration < self.quickTapDurationTime then |
391 | if g_currentMission.time - self.lastQuickTapTime > self.quickTapMinDelay then |
392 | SoundUtil.playSample(self.sampleQuickTap, 1, 0, nil) |
393 | self.lastQuickTapTime = g_currentMission.time |
394 | end |
395 | else |
396 | SoundUtil.stopSample(self.sampleWorkUp, true) |
397 | SoundUtil.setSampleVolume(self.sampleWork, 0) |
398 | self.workPlaying = false |
399 | SoundUtil.setSamplePitch(self.sampleWork, 1) |
400 | SoundUtil.playSample(self.sampleWorkDown, 1, 0, nil) |
401 | end |
402 | end |
403 | |
404 | self.workUpPlayed = false |
405 | self.quickTapDuration = 0 |
406 | end |
407 | |
408 | self.player:lockInput(lockPlayerInput) |
409 | |
410 | if self.particleSystems ~= nil then |
411 | local active = false |
412 | if (self.samplesBranchActiveTimer > g_currentMission.time) or (self.curSplitShapeMinY ~= nil and self.curSplitShapeMaxY ~= nil and self.offsetY > self.curSplitShapeMinY and self.offsetY < self.curSplitShapeMaxY) then |
413 | active = true |
414 | end |
415 | for _, ps in pairs(self.particleSystems) do |
416 | ParticleUtil.setEmittingState(ps, active) |
417 | end |
418 | end |
419 | |
420 | local idlePitch = Utils.lerp(self.sampleIdle.pitchOffset, self.soundIdlePitchMax, Utils.clamp(self.lastWorkTime/self.maxWorkTime, 0, 1)) |
421 | SoundUtil.setSamplePitch(self.sampleIdle, idlePitch) |
422 | |
423 | self:setCutting(isCutting, self.rotationZ > 0.7) |
424 | end |
425 | end |