FAQ / issues
changelog
developers
history
contact
game-monitor.com

Forum support thread: forum.mtavc.com
Please ask your questions in this forum thread.

Complete script: script4.lua


To explain this tutorial you first have to watch the vid for better understanding the concept of Hay:
Video footage: scriptvideo4.wmv - Windows Media Video (640x480, 25fps)

What is hay?
Hay is a map where the goal is to climb all the way to the top of the 3D tower of ... hay bails. This 4x4x50 tower is not solid but is filled for 31.25% allowing players to climb between the hay-bails up. The bails are randomly chosen to move (when possible) to block players from climbing, pushing them off or to help players with the next jump. For the stress factor, each bail moves faster at the higher you get. So a bail at the ground takes 4 seconds to move. while at the top it only takes 2 second. This is more frustrating when you jump and the bail moves away before you land on it. For the fun factor we added rocks, a rock remains on his place until it's touched. Then it destroys every hay bail underneath and taking (mostly) you and others down to death.

Local variables

 
local options = {
        x = 4,
        y = 4,
        z = 49, -- +1
        b = 245,
        r = 4
}

Options defines width 'x' , length 'y', height 'z', bails, 'b', rocks 'r' to be used for the tower. The 'z' has a +1 because at the top we make a bail that never moves plus a nice pickup which others are not going to like

Furthermore we have:

 
-- Don't touch below!
local matrix = {} --3D Matrix containing info over position of bails
local objects = {} --List of objects
local moving = {} --List of currently Moving objects (We dont want to move already moving objects)
local leveltop = {} --List of highest level reached from each player
local textDisplay --Container for textDisplay
local textItem --Container for textItem
local xy_speed --The number of ms the bails will move faster horizontally with each level
local z_speed --The number of ms the bails will move faster vertically with each level
local root = getRootElement() -- Root element
local barrier_x --Anti-run-away barrier x location
local barrier_y --Anti-run-away barrier y location
local barrier_r --Anti-run-away barrier radius

Initialization
At initialization we do the following things:

  • Calculate static variables
  • Create the tower
  • Create text display for players
  • Start bail-shifting process
  • Spawn players

Static values
We have 5 static variables to calculate, you might think: why caclulate static variables? Well we provide options in the local variables to change shape, density and number of rocks.

 
--Calculate speed velocity
xy_speed = 2000 / (options.z + 1)
z_speed = 1500 / (options.z + 1)

As I said, the higher you get, the faster the bails move. The 'normal' moving time of 1 bail is 4 seconds over the x/y-axis and 3 seconds over the z-axis. The difference between x/y axis and z axis is because the hay-bails dimensions are exactly 4x4x3 units. At the top the moving time is the half of the 'normal' time. To calculate the reduction time per level we divide half of the 'normal' time (2000ms for x/y axis and 1500 ms for z-axis) over the number of levels (options.z + 1).

Furthermore we have some variables to calculate for the anti-run-away barrier:

 
--Calculate tower center and barrier radius
barrier_x = (options.x + 1) * -2
barrier_y = (options.y + 1) * -2
x = options.x * 2 + 4
y = options.y * 2 + 4
barrier_r = math.sqrt(x*x+y*y)

Here I have to make a note we place the tower in the x/y axis range of 0 til minus 3000. All x/y coordinates are negative. This is because we place the tower next to the farm in the middle of the map which has coordinate 0,0.

Creation of the tower.

As you could see in the local variables we use a 3D matrix for the tower. Lua doesn't know exactly a matrix type so we actually work with a table (X axis) of tables (Y axis) which contain a table (Z axis) of variables. Logic ey?

 
--Clean matrix
for x = 1,options.x do --For each X
        matrix[x] = {} --Initialize the Y table
        for y = 1,options.y do --For each Y
                matrix[x][y] = {} --Initialize the Z table
                for z = 1,options.z do --For each Z
                        matrix[x][y][z] = 0 --Fill in 0
                end
        end
end

The placement of hay bails is easy: For each, pick a random (1) not taken location (2) in the matrix, mark it as taken (3) and create the object at the position relative to that location. (4)
To make a hay bails look 'different' from each other we randomly rotate the hay bails in steps of 90 degree over the x/y-axis and 180 degree over the z-axis. (4)

--Place number of hay bails in matrix
local x,y,z
for count = 1,options.b do
repeat
x = randInt ( 1, options.x ) --Note 1
y = randInt ( 1, options.y )
z = randInt ( 1, options.z )
until (matrix[x][y][z] == 0) --Note 2
        matrix[x][y][z] = 1 --Note 3
        objects[count] = createObject ( 3374, x * -4, y * -4, z * 3, randInt ( 0, 3 ) * 90, randInt ( 0, 1 ) * 180 , randInt ( 0, 1 ) * 180 ) --Note 4
end

And the same for rocks but then 'truly' random rotated:

 
--Place number of rocks in matrix
for count = 1,options.r do
repeat
x = randInt ( 1, options.x ) --Note 1
y = randInt ( 1, options.y )
z = randInt ( 1, options.z )
until (matrix[x][y][z] == 0) --Note 2
	matrix[x][y][z] = 1 --Note 3
	createObject ( 1305, x * -4, y * -4, z * 3, randInt ( 0, 359 ), randInt ( 0, 359 ), randInt ( 0, 359 ) ) --Note 4
end

After the creation we place the static bail (1) on top plus a surprise (2):

 
--Place top-hay bail + surprise
createObject ( 3374, barrier_x, barrier_y, options.z * 3 + 3 ) --Note 1
createPickup ( barrier_x, barrier_y, options.z * 3 + 6, 2, 38, 500 ) --Note 2

Text display creation
As you could see in the video on the left side there was a list of names of all players with there current level, the highest level reached and health left. This is done via text displays. With text displays you can add text messages all over the user screen. I don't wanna go deep in the creation of this right now but itbasically comes to this: 1. Create the item-container. 'textCreateDisplay ()' 2. Create items 'textCreateTextItem ( text, x, y, priority, red, green, blue, alpha, scale)' 3. Add the items to item-container 'textDisplayAddText ( display, item )' 4. Add players to the item-container so they see all those items on their screen 'textDisplayAddObserver ( display, player )'
This is also where we initialize the player's highest reached level which is 0 (5).

 
--Set up textdisplay
textDisplay = textCreateDisplay () --Note 1
textItem = textCreateTextItem ( "", 0.08, 0.20, 2, 255, 255, 255, 255, 1) --Note 2
textDisplayAddText ( textDisplay, textItem ) --Note 3
local players = getElementsByType( "player" )
for k,v in players do
	leveltop[v] = 0 --Note 5
	textDisplayAddObserver ( textDisplay, v ) --Note 4
end

As you also could see it updates every second that display this is done by setting up a timer at initialization to call a function every second:

 
setTimer ( "level", 1000, 0 )

Start bail shifting
The starting of the bailshifting is again a timer that calls a function every 100ms.

 
setTimer ( "move", 100, 0 )

Bail shifting function
The 'core' of hay. At first we pick an random number, which presents a bail, between 1 and the number of bails (1). If that bail not moving (2) we get the object (3) and pick a random move (4). Then we make a temporarily copy of the matrix (5), fill in all player locations (6), and calculate the position of the object in the matrix (7). If the chosen move is possible, (it doesn't move out of the matrix and is free (8), we mark the bail as moving (9). Then it calculates the movement speed with use of the reduction-time-per-level variables (10). If the bail is done moving we like to do some settings (11). After that we calculate the new position of the bail (12). To prevent bails to move into each other we mark the next location of the bail in the matrix as taken (13). Eventually we move the bail itself (14).

 
function move ()
local rand
repeat
rand = randInt ( 1, options.b ) --Note 1
until (moving[rand] ~= 1) --Note 2
	local object = objects[ rand ] --Note 3
	local move = randInt ( 0, 5 ) --Note 4
	local x,y,z
	local x2,y2,z2 = getElementPosition ( object )
	local free = {}
	copyTable(matrix,free) --Note 5
	getFree(free) --Note 6
	x = x2 / -4 --Note 7
	y = y2 / -4
	z = z2 / 3
	if (move == 0) and (x ~= 1) and (free[x-1][y][z] == 0) then --Note 8
		moving[rand] = 1 --Note 9
		local s = 4000 - xy_speed * z --Note 10
		setTimer ("done", s, 1, rand, x, y, z) --Note 11
		x = x - 1 --Note 12
		matrix[x][y][z] = 1 --Note 13
		moveObject ( object, s, x2 + 4, y2, z2, 0, 0, 0 ) --Note 14
	elseif (move == 1) and (x ~= options.x) and (free[x+1][y][z] == 0) then
		moving[rand] = 1
		local s = 4000 - xy_speed * z
		setTimer ("done", s, 1, rand, x, y, z)
		x = x + 1
		matrix[x][y][z] = 1
		moveObject ( object, s, x2 - 4, y2, z2, 0, 0, 0 )
	elseif (move == 2) and (y ~= 1) and (free[x][y-1][z] == 0) then
		moving[rand] = 1
		local s = 4000 - xy_speed * z
		setTimer ("done", s, 1, rand, x, y, z)
		y = y - 1
		matrix[x][y][z] = 1
		moveObject ( object, s, x2, y2 + 4, z2, 0, 0, 0 )
	elseif (move == 3) and (y ~= options.y) and (free[x][y+1][z] == 0) then
		moving[rand] = 1
		local s = 4000 - xy_speed * z
		setTimer ("done", s, 1, rand, x, y, z)
		y = y + 1
		matrix[x][y][z] = 1
		moveObject ( object, s, x2, y2 - 4, z2, 0, 0, 0 )
	elseif (move == 4) and (z ~= 1) and (free[x][y][z-1] == 0) then
		moving[rand] = 1
		local s = 3000 - z_speed * z
		setTimer ("done", s, 1, rand, x, y, z)
		z = z - 1
		matrix[x][y][z] = 1
		moveObject ( object, s, x2, y2, z2 - 3, 0, 0, 0 )
	elseif (move == 5) and (z ~= options.z) and (free[x][y][z+1] == 0) then
		moving[rand] = 1
		local s = 3000 - z_speed * z
		setTimer ("done", s, 1, rand, x, y, z)
		z = z + 1
		matrix[x][y][z] = 1
		moveObject ( object, s, x2, y2, z2 + 3, 0, 0, 0 )
	end
end

Text display update
This function gets called every second and updates the text-display with the users current level, highest reached level and health. At the top of the text-display we report the number of levels in the tower (1), after that we iterate through each player (2) and calculate there current floor (3). As this system gets called every second we can also do a barrier check here. If a player is passed the barrier it shows a msg and kills the player (4). (The number 700 in this code is part of the coordination the player gets send to when he's dead and is used here to save some caclulations) If he is dead, list him as being at a invalid level '-'. (5). If he is alive show the current level (6). Edit the item of the text-display and we're done. (7)

 
function level()
	local x,y,z
	local level
	local string = options.z + 1 .. " levels:\n" --Note 1
	local players = getElementsByType( "player" )
	for k,v in players do --Note 2
		x,y,z = getElementPosition( v )
		level = math.floor(z / 3 - 0.5) --Note 3
		if (getDistanceBetweenPoints2D(barrier_x,barrier_y,x,y) > barrier_r) and (isPlayerDead ( v ) == false) and (z ~= 700) then --Note (4)
			outputChatBox( "* Killed: Don't walk away.", v, 255, 100, 100 )
			killPlayer(v)
		end
		if (z == 700) then --Note 5
			string = string .. getClientName ( v ) .. " (-/" .. leveltop[v] .. ") 0%\n"
		else --Note 6
			if (level > leveltop[v]) then
				leveltop[v] = level
			end
			string = string .. getClientName ( v ) .. " (" .. level .. "/" .. leveltop[v] .. ") " .. math.floor(getPlayerHealth(v)) .. "%\n"
		end
	end
	textItemSetText ( textItem , string ) --Note 7
end

Other functions
At the top of the tower we placed a surprise. When a player picks it up (1) we make a notification that that player has reached the top (2) and destroy the surprise. (3)

 
addEventHandler( "onPickupHit", root, "onPickupHit")
function onPickupHit ( player ) --Note 1
	if (getPickupType ( source ) == 2) then
		outputChatBox( "* " .. getClientName ( player ) .. " made it to the top!", root, 255, 100, 100 ) --Note 2
		destroyElement( source ) --Note 3
	end
end

When a player joins we have to add that player to the text-display to let the player be able to see the text-display (1). Also we must initialize his highest reached level which is 0. (2)

 
addEventHandler( "onPlayerJoin", root, "onPlayerJoin")
function onPlayerJoin ( )
leveltop[source] = 0 --Note 1
textDisplayAddObserver ( textDisplay, source ) --Note 2
end

When a bail is done moving, free the previous place (1) and mark the bail as not-moving. (2)

 
function done ( id, x, y, z )
	moving[id] = 0 --Note 1
	matrix[x][y][z] = 0 --Note 2
end

This function marks positions in the matrix as taken according to player locations. It iterates through every player (1), calculates there position (2), performs a boundary check (3) and mark it in the matrix as taken (4)

 
function getFree ( src )
	local x,y,z
	local players = getElementsByType( "player" )
	for k,v in players do --Note 1
		x,y,z = getElementPosition( v )
		x = math.floor(x / -4 + 0.5) --Note 2
		y = math.floor(y / -4 + 0.5)
		z = math.floor(z / 3 + 0.5)
		if (x >= 1) and (x <options>= 1) and (y <options>= 1) and (z <= options.z) then --Note 3
			src[x][y][z] = 2 --Note 4
		end
	end
end

This functions copies tables.

 
function copyTable ( src, des )
	for k,v in pairs(src) do
		if (type(v) == "table") then
			des[k] = {}
			copyTable(src[k],des[k])
		else
			des[k] = v
		end
	end
end