meta data for this page
Vehicles/VehicleScripts/SnowCannon.lua
SnowCannon handles all vehicle-related features of a snow cannon. A single vehicle may contain more than just one snow cannon (e.g. a twin snow cannon, or maybe even some separately controllable nozzles).
25 SnowCannon = SnowCannon or {}; 26 SnowSystem.SNOW_HEIGHTSTEP = 2 / 255; 27 SnowCannon.CONTROL_RANGE = 5*5;
SnowCannon:load(dataTable)
First, we set up all functions required by this vehicle script. Functions with return values are not supported by VehicleManager:newFunction()
, therefore these are assigned directly.
Afterwards, we read some configuration for all the snow cannons that are available on this vehicle, and initialize them.
33 function SnowCannon:load(dataTable) 34 self.setIsCannonTurnedOn = VehicleManager:newFunction("setIsCannonTurnedOn"); 35 self.setSnowCannonGroupId = VehicleManager:newFunction("setSnowCannonGroupId"); 36 self.setCannonRotationX = VehicleManager:newFunction("setCannonRotationX"); 37 self.setCannonRotationY = VehicleManager:newFunction("setCannonRotationY"); 38 self.snowCannonRemoteCallback = VehicleManager:newFunction("snowCannonRemoteCallback") 39 self.snowCannonRemoteMove = VehicleManager:newFunction("snowCannonRemoteMove") 40 self.getSnowCannonRemoteButton = SnowCannon.getSnowCannonRemoteButton; 41 self.getMayTurnCannonOn = SnowCannon.getMayTurnCannonOn; 42 43 self.snowCannons = {}; 44 self.snowCannonGroupId = 0; -- to be overridden by Escape Menu GUI 45 46 for n, v in pairs(dataTable.snowCannons) do 47 -- note: rotation is controlled via separate scripts (as this function is shared e.g. with the blade) 48 -- we only handle starting/stopping, particle system and snow spawning 49 local spawnIndex = v.snowSpawnIndex or ""; 50 local propellerIndex = v.propellerIndex or ""; 51 52 if cannonIndex == "" then break else 53 local cannon = {}; 54 cannon.snowSpawnId = getChild(self.id, spawnIndex); 55 cannon.controlId = getChild(self.id, v.controlIndex or "") 56 cannon.cameraId = getChild(self.id, v.cameraIndex or ""); 57 58 if propellerIndex ~= "" then 59 cannon.propellerId = getChild(self.id, propellerIndex); 60 cannon.propellerMaxSpeed = (v.propellerSpeed or 300)/60 * 360; -- rpm (here converted to degrees per second) 61 cannon.propellerSpeed = 0; 62 end; 63 64 if v.rotX ~= nil then 65 cannon.rotXId = getChild(self.id, v.rotX.index); 66 cannon.rotXmin = v.rotX.min or 0; 67 cannon.rotXmax = v.rotX.max or 45; 68 cannon.rotXSpeed = v.rotX.speed or 15; 69 cannon.rotXAttach = v.rotX.attachValue; 70 71 local x,y,z = getRotation(cannon.rotXId); 72 cannon.rotX = x; 73 end; 74 if v.rotY ~= nil then 75 cannon.rotYId = getChild(self.id, v.rotY.index); 76 cannon.rotYmin = v.rotY.min; 77 cannon.rotYmax = v.rotY.max; 78 cannon.rotYSpeed = v.rotY.speed or 30; 79 cannon.rotYAttach = v.rotY.attachValue; 80 81 local x,y,z = getRotation(cannon.rotYId); 82 cannon.rotY = y; 83 end; 84 85 cannon.particleSystems = {}; 86 if v.particleSystems ~= nil then 87 for _, str in pairs(v.particleSystems) do 88 local particleId = getChild(self.id, str); 89 ParticleSystem.stop(particleId); 90 91 table.insert(cannon.particleSystems, particleId); 92 end; 93 end; 94 cannon.particleSystemsPlaying = false; 95 96 if v.idleSound ~= nil then 97 cannon.idleSoundId = Utils.loadBundleGameObject(self.bundleId, v.idleSound); 98 setParent(cannon.idleSoundId, cannon.snowSpawnId); 99 setPosition(cannon.idleSoundId, 0,0,0); 100 AudioSource.stop(cannon.idleSoundId); 101 end; 102 103 cannon.isTurnedOn = false; 104 cannon.inputKey = v.inputKey or "SnowCannon_TurnOnOff"; 105 cannon.startupDuration = v.startupDuration or 5; -- wait some time before snow is spawned 106 cannon.startupTimer = 0; 107 cannon.rayLength = v.rayLength or 15; 108 cannon.spreadFactor = v.spreadFactor or 1.4; 109 cannon.minSpreadRadius = v.minSpreadRadius or 7; 110 cannon.volumePerSecond = (v.volumePerHour or 5000)/3600; -- in m³ per hour 111 cannon.cumulatedVolume = 0; -- cumulated volume of snow spawned already 112 cannon.pricePerCubic = v.pricePerCubic or 1; -- should be a realistic value 113 114 self.snowCannons[n] = cannon; 115 end; 116 end; 117 118 SnowmakingManager:registerVehicle(self); 119 end;
SnowCannon:update(dt)
There are multiple things that have to be updated every frame:
- If the player is active (
g_scenario.player.isActive
), we need to check whether the player wants to switch the cannon on or off, or maybe wants to rotate the cannon. - If the cannon is turned on, it should start emitting snow after some time, i.e. we have to update some particle effects.
- The actual process of creating snow also has to be updated.
126 function SnowCannon:update(dt) 127 -- alright, first animate all cannons 128 local player = g_scenario.player; 129 130 for k, cannon in pairs(self.snowCannons) do 131 -- animate propeller in x axis 132 if cannon.propellerId ~= nil then 133 cannon.propellerSpeed = Utils.moveTowards(cannon.propellerSpeed, cannon.isTurnedOn and cannon.propellerMaxSpeed or 0, cannon.propellerMaxSpeed * dt); 134 if cannon.propellerSpeed ~= nil then 135 rotate(cannon.propellerId, 0,0, cannon.propellerSpeed * dt); 136 end; 137 end; 138 139 local px,py,pz = getWorldPosition(cannon.controlId); 140 local dx,dy,dz = px - player.x, py - player.y, pz - player.z; 141 142 if player.isActive and self.attacherMasterVehicle == nil and (dx*dx + dy*dy + dz*dz) < SnowCannon.CONTROL_RANGE then 143 -- check if the player wants to toggle on/off the cannon 144 local inputKey = InputMapper[cannon.inputKey]; 145 if InputMapper:getKeyDown(inputKey) then 146 self:setIsCannonTurnedOn(k, not cannon.isTurnedOn); 147 end; 148 149 -- key hint 150 g_GUI:addKeyHint(inputKey, l10n.get(cannon.isTurnedOn and "Input_SnowCannon_TurnOff" or "Input_SnowCannon_TurnOn")); 151 152 -- let the player turn the cannon 153 if cannon.rotYId ~= nil then 154 if InputMapper:getKey(InputMapper.SnowCannon_RotateLeft) then 155 self:setCannonRotationY(k, cannon.rotY - cannon.rotYSpeed * dt); 156 157 elseif InputMapper:getKey(InputMapper.SnowCannon_RotateRight) then 158 self:setCannonRotationY(k, cannon.rotY + cannon.rotYSpeed * dt); 159 end; 160 g_GUI:addDoubleKeyHint(InputMapper.SnowCannon_RotateLeft, InputMapper.SnowCannon_RotateRight, l10n.get("SnowCannon_RotateLeftRight")); 161 end; 162 163 if cannon.rotXId ~= nil then 164 if InputMapper:getKey(InputMapper.SnowCannon_RotateUp) then 165 self:setCannonRotationX(k, cannon.rotX + cannon.rotXSpeed * dt); 166 167 elseif InputMapper:getKey(InputMapper.SnowCannon_RotateDown) then 168 self:setCannonRotationX(k, cannon.rotX - cannon.rotXSpeed * dt); 169 end; 170 g_GUI:addDoubleKeyHint(InputMapper.SnowCannon_RotateUp, InputMapper.SnowCannon_RotateDown, l10n.get("SnowCannon_RotateUpDown")); 171 end; 172 end; 173 174 if cannon.isTurnedOn and not self:getMayTurnCannonOn(k) then 175 self:setIsCannonTurnedOn(k, false); 176 end; 177 178 if cannon.isTurnedOn then 179 cannon.startupTimer = cannon.startupTimer + dt; 180 181 if cannon.startupTimer >= cannon.startupDuration then 182 183 -- start playing the particle systems 184 if not cannon.particleSystemsPlaying then 185 for _, id in pairs(cannon.particleSystems) do 186 ParticleSystem.play(id); 187 end; 188 189 if cannon.idleSoundId ~= nil then 190 AudioSource.play(cannon.idleSoundId); 191 end; 192 end; 193 194 -- we're spawning some snow! 195 -- get the world position and direction of our cannon 196 local x,y,z = getWorldPosition(cannon.snowSpawnId); 197 local dx,dy,dz = Utils.transformDirection(cannon.snowSpawnId, 0,0,1); -- yeet snow in +Z direction 198 199 -- snow focuses on a fixed point that is located some metres (rayLength) in front of our snowSpawnId 200 local rayLength = cannon.rayLength; 201 x,y,z = x + rayLength*dx, y + rayLength*dy, z + rayLength*dz; 202 203 local terrainDelta = y - Utils.sampleTerrainHeight(x, z); 204 205 -- let's assume snow to spread in linear way: 206 -- if emitted e.g. 5 metres above terrain, it will spread in a radius of e.g. 5 metres (=> diametre 10 m!) 207 -- this can be customised using the cannon's spread factor 208 -- this is approximated using a slightly modified cone volume formula (V = 1/3 * r^2 * pi * h) 209 cannon.cumulatedVolume = cannon.cumulatedVolume + cannon.volumePerSecond * dt; 210 211 local spreadRadius = math.max(terrainDelta * cannon.spreadFactor, cannon.minSpreadRadius); 212 local deltaHeight = 0.7 * cannon.cumulatedVolume / (spreadRadius^1.6); 213 214 -- spawn if delta height is larger than 2x step size 215 if deltaHeight >= 10*SnowSystem.SNOW_HEIGHTSTEP then 216 local price = cannon.cumulatedVolume * cannon.pricePerCubic; 217 218 if g_scenario:canAffordExpense(price) then 219 -- pass this on to snow system 220 SnowSystem.addSnowCircular(x, deltaHeight, z, spreadRadius); 221 g_scenario:addSnowmakingExpense(price); 222 else 223 -- shut cannon off 224 self:setIsCannonTurnedOn(k, false); 225 end; 226 cannon.cumulatedVolume = 0; 227 228 end; 229 end; 230 end; 231 end; 232 end;
SnowCannon:saveToTable(tbl)
Saves all relevant variables.
236 function SnowCannon:saveToTable(tbl) 237 if tbl == nil then return end; 238 239 tbl.snowCannonGroupId = self.snowCannonGroupId; 240 tbl.snowCannons = {}; 241 242 for n, cannon in pairs(self.snowCannons) do 243 tbl.snowCannons[n] = { 244 isTurnedOn = cannon.isTurnedOn, 245 cumulatedVolume = cannon.cumulatedVolume, -- save this to avoid all cannons "eating" money at once 246 propellerSpeed = cannon.propellerSpeed, 247 startupTimer = cannon.startupTimer, 248 rotX = cannon.rotX, 249 rotY = cannon.rotY, 250 }; 251 end; 252 end;
SnowCannon:loadFromTable(tbl)
Restores all values from the savegame.
256 function SnowCannon:loadFromTable(tbl) 257 if tbl == nil then return end; 258 259 self.snowCannonGroupId = getNoNil(tbl.snowCannonGroupId, self.snowCannonGroupId); 260 261 if tbl.snowCannons == nil then return end; 262 263 for n, cannon in pairs(self.snowCannons) do 264 local savedCannon = tbl.snowCannons[n]; 265 266 if savedCannon ~= nil then 267 cannon.isTurnedOn = getNoNil(savedCannon.isTurnedOn, false); 268 cannon.cumulatedVolume = getNoNil(savedCannon.cumulatedVolume, cannon.cumulatedVolume); 269 cannon.propellerSpeed = getNoNil(savedCannon.propellerSpeed, cannon.propellerSpeed); 270 cannon.startupTimer = getNoNil(savedCannon.startupTimer, cannon.startupTimer); 271 272 if savedCannon.rotX ~= nil and cannon.rotXId ~= nil then 273 self:setCannonRotationX(n, savedCannon.rotX); 274 end; 275 if savedCannon.rotY ~= nil and cannon.rotYId ~= nil then 276 self:setCannonRotationY(n, savedCannon.rotY); 277 end; 278 end; 279 280 -- apply that (to make sure everything is alright) 281 self:setIsCannonTurnedOn(n, cannon.isTurnedOn, true); 282 end; 283 end;
SnowCannon:setIsCannonTurnedOn(cannonIndex, state)
Call this to turn the snow cannon number cannonIndex
(int, starting with 1) on or off, depending on state
(bool).
287 function SnowCannon:setIsCannonTurnedOn(cannonIndex, state) 288 -- don't allow the player to turn on the cannon if he doesn't have enough cash 289 if state and not g_scenario:canAffordExpense(0) then 290 state = false; 291 end; 292 293 if cannonIndex == nil then 294 -- apply this to all cannons 295 for k, _ in pairs(self.snowCannons) do 296 -- make sure k is not nil, as this would result in an infinite loop 297 if k ~= nil then 298 self:setIsCannonTurnedOn(k, state); 299 end; 300 end; 301 end; 302 303 local cannon = self.snowCannons[cannonIndex]; 304 if cannon == nil then return end; 305 306 cannon.isTurnedOn = state and self:getMayTurnCannonOn(cannonIndex); 307 308 if not state then 309 -- stop emitting particles 310 for _, id in pairs(cannon.particleSystems) do 311 ParticleSystem.stop(id); 312 end; 313 cannon.particleSystemsPlaying = false; 314 315 cannon.startupTimer = 0; 316 cannon.cumulatedVolume = 0; 317 318 if cannon.idleSoundId ~= nil then 319 AudioSource.stop(cannon.idleSoundId); 320 end; 321 end; 322 end;
SnowCannon:getMayTurnCannonOn(cannonIndex)
Returns whether the snow cannon may be turned on. This is always allowed unless the snow cannon is attached to another vehicle.
327 function SnowCannon:getMayTurnCannonOn(cannonIndex) 328 return self.attacherMasterVehicle == nil; 329 end;
SnowCannon:onAttach()
Rotate the snow cannon to a specific position if rotXAttach
or rotYAttach
are enabled in the cannon's configuration table.
These will not be required for most cannons.
335 function SnowCannon:onAttach() 336 for k, v in pairs(self.snowCannons) do 337 self:setIsCannonTurnedOn(k, false); 338 339 if v.rotXAttach ~= nil then 340 self:setCannonRotationX(k, v.rotXAttach); 341 end; 342 343 if v.rotYAttach ~= nil then 344 self:setCannonRotationY(k, v.rotYAttach); 345 end; 346 end; 347 end;
SnowCannon:setSnowCannonGroupId(groupId)
Makes the snow cannon join group number groupId
(int). 0 means the snow cannon does not belong to any group.
351 function SnowCannon:setSnowCannonGroupId(groupId) 352 self.snowCannonGroupId = math.max(groupId or 0, 0); 353 end;
SnowCannon:getSnowCannonRemoteButton()
Returns the button description that is displayed in the overview menu's snowmaking section (if this snow cannon is selected).
357 function SnowCannon:getSnowCannonRemoteButton() 358 if self.snowCannons == nil or self.snowCannons[1] == nil then 359 return "Invalid"; 360 end; 361 362 return l10n.get(self.snowCannons[1].isTurnedOn and "ui_esc_snowCannonRemote1" or "ui_esc_snowCannonRemote0"); 363 end;
SnowCannon:snowCannonRemoteCallback()
Is called every time the player clicks the start/stop button in the overview menu's snowmaking section. The button's label is specified by the return value of SnowCannon:getSnowCannonRemoteButton()
.
367 function SnowCannon:snowCannonRemoteCallback() 368 if self.snowCannons == nil or self.snowCannons[1] == nil then return end; 369 370 self:setIsCannonTurnedOn(1, not self.snowCannons[1].isTurnedOn); 371 end;
SnowCannon:snowCannonRemoteMove(deltaX, deltaY)
Allows to rotate the snow cannon via the overview menu's snowmaking section.
375 function SnowCannon:snowCannonRemoteMove(deltaX, deltaY) 376 local cannon = self.snowCannons[1]; 377 if cannon == nil then return end; 378 379 local dt = 1/30; 380 381 if deltaY ~= nil and deltaY ~= 0 then 382 self:setCannonRotationX(1, cannon.rotX + cannon.rotXSpeed * dt * deltaY); 383 end; 384 if deltaX ~= nil and deltaX ~= 0 then 385 self:setCannonRotationY(1, cannon.rotY + cannon.rotYSpeed * dt * deltaX); 386 end; 387 end;
SnowCannon:setCannonRotationX(cannonIndex, rotX)
Rotates the snow cannon along its X rotation axis.
391 function SnowCannon:setCannonRotationX(cannonIndex, rotX) 392 local cannon = self.snowCannons[cannonIndex]; 393 if cannon == nil then return end; 394 395 if cannon.rotXId ~= nil then 396 cannon.rotX = clamp(rotX, cannon.rotXmin, cannon.rotXmax); 397 setRotationX(cannon.rotXId, cannon.rotX); 398 end; 399 end;
SnowCannon:setCannonRotationY(cannonIndex, rotY)
Rotates the snow cannon along its Y rotation axis.
403 function SnowCannon:setCannonRotationY(cannonIndex, rotY) 404 local cannon = self.snowCannons[cannonIndex]; 405 if cannon == nil then return end; 406 407 if cannon.rotYId ~= nil then 408 cannon.rotY = clamp(rotY, cannon.rotYmin, cannon.rotYmax); 409 setRotationY(cannon.rotYId, cannon.rotY); 410 end; 411 end;
SnowCannon:destroy()
413 function SnowCannon:destroy() 414 SnowmakingManager:unregisterVehicle(self); 415 end;
Copyright
All contents of this page may be used for modding use for Winter Resort Simulator only. Any use exceeding this regulation is not permitted.
Copyright (C) HR Innoways, 2020. All Rights Reserved.