So where was all our snow this christmas? Well all of us down at QA had the exact same question. So jbeta helped us out with the next best thing...
MTA Snow! Yes that's right, its as lame as it sounds!
Basically its a script that creates artificial snow around players when they type in a command in the console. The script allows changing the radius, density, speed and size of your snow to give the desired effect.
Forum support thread: forum.mtavc.com
Please ask your questions in this forum thread.
Video footage: scriptvideo2.avi - XviD codec (640x480, 30fps)
Complete script: script2.lua
Preparation
First off, lets create a table,
snowflake = {} --This initializes the snowflake IDs table |
For more information about Lua and it's tables, I suggest you look up the official docs
and then we'll create some global variables:
flakeID = 1 --This sets the first flake ID as 1 updaterate = 27 --Positions' update rate in miliseconds height = 4 --Snowflakes starting position above the player's head snowing = false --This is a variable that tells the script whether it is snowing or not. |
Setting up our command
We learnt last week that you can use an addEventHandler function to attach your functions to MTA's events. This script however uses a command in the console to trigger the function. For this, there is a similar function called addCommandHandler:
addCommandHandler ( "snow", "snow_SnowCommand" ) --This assigns the snow_SnowCommand function to the "snow" command in console |
This attaches the command "snow" in console to the function "snow_SnowCommand", similar to the addEventHandler function.
function snow_SnowCommand ( player, commandname, radius, density, speed, size ) --You can pass radius, density, speed and flake size parameters in the command |
The addCommandHandler function automatically passes two variables to the function it is attached to. These are the "player" and "commandname" variables. Any extra arguments will be passed as extra variables.
For example, if a user was to type "snow 4 3 2 1" in console, then the command handler would pass:
the player who typed the command, "snow", 4, 3, 2, 1
corresponding to the function above.
The command is designed so that when a user types "snow" in console then snow is created.
if not snowing then --check the 'snowing' variable and check if it is true. If it is not then it is not snowing snowing = true --set the snowing variable to true snow_CreateSnow ( player, radius, density, speed, size ) --call the snow_CreateSnow function to create our snow! outputChatBox ( "Snow started by " .. getClientName ( player ) ) --announce that snowing was started. |
If it is not snowing already, initiate a snow_CreateSnow function. Also note the getClientName function. This basically retrieves the name of a specified player.
The command is also designed so that if snow is already falling then typing "snow" in console will stop the snow:
else snowing = false --set the snowing variable to false outputChatBox ( "Snow stopped." ) --announce the snowing has stopped end end |
Creating our snow
The script uses a snow_CreateSnow function. This isn't one of MTA's default functions, rather a custom function:
function snow_CreateSnow ( player, radius, density, speed, size ) |
The script creates snow around the player who initiated it. Therefore we need to retrieve the location of the player:
local px, py, pz = getElementPosition ( player ) |
Last week we saw setElementPosition. This function is used to get the position of the player rather than set it - and in this case they have been defined with the variables px, py, pz.
--If player is invalid or has disconnected, stop the function if not px or not py or not pz then snowing = false return end |
This part of the script checks whether the player has disconnected or quit. This is done by checking if the px, py or pz variables are invalid. If they are then it must be because the player does not exist in some way.
The next section of the script handles parameters that are not specified. For example, if a user just types "snow" without specifying radius, density, speed or size then they will be defaulted.
--If any parameter is not set, the script will give it a default value if not radius then radius = 20 else radius = tonumber ( radius ) end |
If radius does not exist, then default it to 20. If it does exist, convert it to a number from a string using lua's tonumber function.
if not density then density = 3 else density = math.ceil ( tonumber ( density ) ) end --density should be an integer (flakes/second) |
Similarly, if the density was not specified default it to 3. If it does exist, convert it to a number from a string, and use lua's math.ceil function to round the number up to a whole number.
if not speed then speed = 1.5 else speed = math.abs ( tonumber ( speed ) ) end --speed should only be positive (units/second) |
This makes use of lua's math.abs function to return an absolute or positive number.
For more information on both math.abs and math.ceil please see the official docs.
if not size then size = 0.15 else size = tonumber ( size ) end |
Lastly default the size if not specified.
Next, we use lua's while loops to create as many snowflakes as desired. It uses the density variable that was specified earlier to create each snowflake. It basically loops until counter > density.
local counter = 0 --We will repeat the flake creating process one time per flake while counter <= density do |
--For x and y, we will calculate a random position around the player, within the maximum radius circle. local angle = randInt ( 0, 359 ) local fx = px + math.cos ( angle ) * radius * randFloat () local fy = py + math.sin ( angle ) * radius * randFloat () --z is just the player's height, plus the height we set before. local fz = pz + height |
Here we use trigonometry to get a random position to place the snow flake. First we use Lua's randInt to gain a random integer between 0 and 359 - i.e. and angle. Next we use trigonometry on the angle to gain the x and y position, and multiply that by the radius specified earlier. This is then multiplied by a random float between 0 and 1 (randFloat) to get a random position within the radius.
Now that we have the position, let's actually create the snowflake
snowflake[flakeID] = createMarker ( fx, fy, fz, "corona", size, 255, 255, 255, 150 ) |
This uses the createMarker function. Last week we used this function with the type of a "cylinder" to create a cylinder marker. This type creates a "corona" marker, which is somewhat of a 'glowball'. It also adds this marker to the snowflake table so it can be retived later.
Setting up timers
--We calculate the time it takes for a flake to fall, in miliseconds local time = 1000 * ( height + 1 ) / speed --We tell the script to update the flake as many times as needed, and store the timer ID in a variable setTimer ( "snow_moveFlake", updaterate, math.floor ( time / updaterate ), flakeID, speed ) --We set a timer to destroy the flake when the time has passed setTimer ( "snow_destroyFlake", time, 1, flakeID ) |
Then a calculation is made to work out the time required - using time = distance / speed.
The setTimer function is used to call a function after a certain time.
In this case two timers are called. One to move the snowflake by calling a snow_moveFlake according to the rate at which the snowflake position is updated, and another destroy the snowflake after the time has elapsed.
--Repeat this function again in a second, if snow is still on if snowing then setTimer ( "snow_CreateSnow", 1000, 1, player, radius, density, speed, size ) end end |
Finally, we call the function to create snow again in one second, and end the function.
Moving and destroying our snowflakes
Next up is the snow_moveFlake function.
function snow_moveFlake ( flakeID, speed ) local fx, fy, fz = getElementPosition ( snowflake[flakeID] ) setElementPosition ( snowflake[flakeID], fx, fy, fz - ( updaterate / 1000 * speed ) ) end |
This retrieves the position of the snowflake by looking it up in the table. It then moves it downwards by calculating according to the speed and the updaterate. Since this is called multiple times according to the updaterate with the timer we set above, it appears to move.
Lastly, we have the snow_destroyFlake function, which destroys the snowflake when the time has elapsed. This function has been called on a timer earlier on.
function snow_destroyFlake ( flakeID ) destroyElement ( snowflake[flakeID] ) snowflake[flakeID] = nil end |
We use the same function as last week, destroyElement, to destroy the marker i.e the snowflake. Then we blank out the the snowflake in the table.
You think its all over? It is now. If you have any questions please feel free to head down to our forums.





