Script callbacks reference
Home | Introduction | Installation | Quick start | Troubleshooting | Themes | Script tutorial | Script basics | Script callbacks reference | Script API reference | Known bugs | Frequently Asked Questions

Script callbacks reference


Introduction

User callbacks are lua functions which are called from the C++ engine. U61 requires all these functions to be declared in a script, even if they do nothing. In these user callbacks, you may use functions from the U61 system API, but be carefull, you can not use any system API functions in any user callback, since there are some access right constraints.


user_do_curse

Declaration

user_do_curse(num,sent)

Description

This function can be called int 2 cases:

Basically, one could separate the curses into 2 categories:

You might wonder why I did not simply create 2 categories of curses, and let the C++ engine handle all this "send to victim" business. Well, I liked the idea of letting the scripter control what's happening. For instance, you may create a special curse which sends severa curses (all the ones you currently have for instance) to your victim. Basically, what I described with the "sent" parameter determining if one has to send the curse or not is only a suggestion. You may use the system the way you want, for instance, it's possible to create a special curse which forces your victim to send a curse to its own victim...

Of course one of the big strenghs of U61 is that the "user_do_curse" and "u61_send_curse" work pretty well, even in a slow networked environment - at least I hope so 8-) -.

Example

The following example is a very simplified version of "user_do_curse". This function can indeed grow very big and it's a wise decision to make it call smaller functions. But still this purpose of this example is to remain simple. It has the 2 main types of curses:

ID_CURSE_PLUS=3
ID_CURSE_MINUS=4

function user_do_curse(num,sent)
    if (num==ID_CURSE_MINUS) then
        if (sent==0) then
            u61_send_curse(num)
        else
            u61_add_score(-1000)
        end
    elseif (num==ID_CURSE_PLUS) then
        u61_add_score(1000)
    end    
end

user_do_shape

Declaration

user_do_shape(num)

Description

This function shapes the block before it appears on the screen. You should not include any random behavior in this function. In fact it is a bad idea to include random behaviors in U61 in general, but I mention it here for it might be very tempting to ignore the "num" parameter and do a random number generator oneself. But this is bound to fail in a network game for you can not garantee the remote players will see the right shape. In fact the "num" parameter "is" what U61 sents to other players, so it's only by using it that you can garantee game consistency.

A typical "user_do_shape" only needs to call the "u61_add_item" function. Indeed this function allows you to add squares to a block and this is what you generally need to do since at the very start of the function the block is empty, and you wish to end with a fully defined block. You should add least one square in evrey block, or the game might freeze.

Of course you can make the shape generation depend on a curse flag or something, but I feel that these kind of things should be done in the "u61_new_shape" instead. For instance, if you want to block the shape 0 for a while, it's IMHO better not to generate a 0 output in "u61_new_shape" than modify temporarly the creation of shape 0 in "u61_do_shape".

One important thing is to know that after "user_do_shape" has been executed, U61 always executes the "u61_center" function, which might alter the coordinates you have set. So you should not try to recognize a block later in the script by asking the coordinates of his squares, unless you are sure that "u61_center" can not alter them, or if you know in what way they have been changed. You may wonder why I chose this behavior, for it can seem anoying. Well, I just personnally estimate that an automatic "u61_center" is a comfortable thing to have, and it avoids badly centered blocks.

Example

This set of functions generates:

function user_do_shape(num)
    if num==0 then
        triangle(0)
    elseif num==1 then
        square(2)
    else
        circle(5)
    end
end

function triangle(color)
    u61_add_item(0,0,color)
    u61_add_item(0,1,color)
    u61_add_item(1,1,color)
    u61_add_item(0,2,color)
    u61_add_item(2,2,color)
    u61_add_item(0,3,color)
    u61_add_item(1,3,color)
    u61_add_item(2,3,color)
    u61_add_item(3,3,color)
end

function square(color)
    u61_add_item(0,0,color)
    u61_add_item(1,0,color)
    u61_add_item(2,0,color)
    u61_add_item(3,0,color)
    u61_add_item(0,1,color)
    u61_add_item(3,1,color)
    u61_add_item(0,2,color)
    u61_add_item(3,2,color)
    u61_add_item(0,3,color)   
    u61_add_item(1,3,color)   
    u61_add_item(2,3,color)   
    u61_add_item(3,3,color)   
end

function circle(color)
    u61_add_item(1,0,color)
    u61_add_item(2,0,color)
    u61_add_item(0,1,color)
    u61_add_item(3,1,color)
    u61_add_item(0,2,color)
    u61_add_item(3,2,color)
    u61_add_item(1,3,color)
    u61_add_item(2,3,color)
end

user_get_curse_name

Declaration

user_get_curse_name(num)

Description

This function has a very simple goal: provide the user with a name for each curse. This name will be disaplyed in the status zone, under his map. Of course the game will run correctly if this functions returns a blank string, bug it will certainly not help the players.

The names should not be too long. At the time I write these lines, there's a limit of 10 characters, so if you exceed this limit the name will be truncated. And be carefull if your name uses lots of "W" and "M", for these letters are usually wider then plain "I"s.

Example

The following sample defines 2 curses names. A default name ("surprise") has been defined in case the curse is unknown, which should, it's true, never arrive if the code is consistent.

function user_get_curse_name(num)
    local name

    name="surprise"

    if (num==ID_CURSE_MALEDICTION_OF_GOOLOO) then
        name="gooloo"
    elseif (num==ID_CURSE_YOULLDIE) then
        name="you'll die"
    end

    return name
end

user_land

Declaration

user_land()

Description

This function is called when it's not possible to move the block down any more. It's role is to modify the shape of the block before it is merged with the map. Most of the time, this function will do nothing, but you may wish - in some cases - to change the shape of the block just as it lands.

Let's take an example: you might consider that your blocks are not "solid" blocks, ie they break themselves into parts as they land. This is a very common trick in block-based games, and that's why it's available in U61.

You could also wish to send a curse as the block lands, or modify the map. This is not allowed because of performances issues, which lead me not to give the "map_write" right to this function. Indeed this function is very important for the "anticipation" mode. In this mode, the user can view in real-time where the block will land. This feature is achieved by calling the "user_move_down" function until "user_block_land" is required. And I have decided that such operations (as they are called very very often) should not require a full backup of the map, which can be quite CPU-consuming since the map structure can be quite big. It isn't that big now but I expect it to grow as I will add features to U61. So basically, just remember that there's a good reason not to modify the map in this function. However, it does not mean you have no callback at all when a block lands. Indeed, it is possible to set up a callback on a pattern match by using "user_match_pattern". This should - I hope - be enough in most cases.

Example

This example shows a function that makes blocks move up of one row as they land. This should make them hang in the air as they land. I can't figure out in what kind of context it could be usefull, but it's only a theorical example:

function user_land()
    u61_set_block_y(u61_get_block_y()-1)
end

user_match_pattern

Declaration

user_match_pattern(match_count)

Description

This is a fundamental function in U61. It is responsible for saying which squares should explode when a block lands. It is called after the block has landed, ie "user_land" has already been called when "user_match_pattern" is called. It is also responsible for incrementing the score of the player.

You'll notice that this function has no access rights on the block. This is because there's no more block when this function is called. Indeed, all the squares of the falled block are merged with the map. This means that if there were 10 squares in the map and 3 squares in the block, when this function is called you get an "all in one"map with 13 squares. By default, the block squares are put "as is" on the map, but if you want a more accurate control, you may tweak the "user_land" function.

The pattern to be matched can be anything, based on the position and the color of the map squares. You may search a completed horizontal line (as in the "classic") script, some sort of alignments, a shape, a sequence of colors, well, anything! I believe the "user_match_pattern" function is what makes a given set of rule realy different from the others.

Once the pattern is matched, it is a good idea to make the squares which should disappear explode. Technically, you could remove them right away (I've not tried it still), but I just think it's more user friendly for the player to see the block explode. The common method is to try and find a pattern, and when the first pattern is found, stop the search and make the founded pattern explode. Then, the Lua function is exited, and the game keeps on going. It's take a little time for squares to explode. While there's at least one square exploding on the map, U61 does not make a new block appear. Instead, it waits for the explosion to end, and when the explosion is ended, it runs the "user_match_pattern" function again. This way, it's possible to have cascading effects, and the player can understand why 12 of his squares disappeared while a pattern is usually made of 3 blocks.

This leads to a very important point: the "match_count" parameter. This parameter is equal to 0 when "user_match_pattern" is called for the first time. Then logically, "user_match_pattern" should make some squares explode (if needed of course) and let the squares alone while they blow up. When the explosion is finished, "user_match_pattern" is called again, but this time "match_count" is 1. Next time it will be 2, etc... When there's no more pattern to match, then U61's engine gives the player a new block, and resets the internal value corresponding to "match_count". This is a usefull feature for it allows an accurate control of the score, for instance, you might make something that gives:

Example

The following example finds any horizontal line which is filled with squares, or has only one square missing. It's very close from the pattern match used in the "classic" script. Note that the code does not lie directly in user_match_pattern but in an external function which is more general and can be used in another set of rules if needed.

function user_match_pattern(match_count)
    return match_holed_line(match_count,1)
end

function match_holed_line(match_count,max_hole)
    local x
    local y
    local width
    local height
    local holes
    local lines
    
    width=u61_get_width()
    height=u61_get_height()

    lines=0
    y=0
    while y<height and lines==0 do
        holes=0
        x=0
        while x<width do
            if u61_get_square_color(x,y)<0 then
                holes=holes+1
            end
            if not (u61_is_square_exploding(x,y)==0) then
                holes=holes+1
            end
            x=x+1
        end
        if holes<max_hole then 
            delete_line(y)
            lines=lines+1
        end
        y=y+1
    end    

    if lines>0 then
        u61_add_score((match_count+1)*1000)    
        if (match_count>=3) then
            u61_add_antidote()
        end
    end

    return lines
end

user_move_down

Declaration

user_move_down()

Description

This function looks like "user_move_left" and "user_move_right" at first sight, but it is quite different. Indeed the "user_move_down" function must imperatively move the block down in some way. The reason is that this function is used in the following cases:

Please note that in this function you do not need to check wether the operation you want to perform is possible or not. As with other "user_move_..." or "user_rotate_..." functions, the C++ engine automatically checks if something is wrong, and if there's a conflict it rollbacks the whole operation.

Example

The following function moves the block down, and increments the value of a global value at the same time:

MOVE_DOWN_COUNTER=123

function user_move_down()
    u61_set_block_y(u61_get_block_y()+1)
    u61_set_global(MOVE_DOWN_COUNTER,u61_get_global(MOVE_DOWN_COUNTER)+1)
end

user_move_left

Declaration

user_move_left()

Description

This function can generally be called in 2 cases:

Apart from this last point the "user_move_left" function is very similar to "user_rotate_left". One could certainly imagine scripts vhere "user_move_left" would not translate the block on the left. And even if in your script "user_move_left" perfor;s a left translation of the block, it is possible in this function to test if a given curse is active and change the function's behavior. This is what the "goofy" curse (in U61 0.2.2) does: it inverts the effects of "user_move_left" and "user_move_right", which makes the game a little... harder to control!

It's important to note that substracting 1 to the x coordinate of each square of the block won't make the block move to the left. Indeed after each call to "user_move_left" U61 centers the block (similar to a call to u61_center), so the only way to make a block translate is to use the "u61_set_block_x" function (or any associated function).

Example

This example function makes the block move left and down at the same time:

function user_move_left()
    u61_set_block_x(u61_get_block_x()-1)
    u61_set_block_y(u61_get_block_y()+1)
end

user_move_right

Declaration

user_move_right()

Description

Similar to "user_move_left" but associated to the "Move right" key.

Like with the "user_rotate_..." functions, "user_move_right" does not have to be the exact contrary of "user_move_left". You are just free to imagine the weirdest behaviors.

Example

In this example, the block is shifted of 2 blocks right:

function user_move_right()
    u61_set_block_x(u61_get_block_x()+2)
end

user_new_curse

Declaration

user_new_curse(num)

Description

This function is very similar to "user_new_shape". It's goal is to control the nature of the next curse which will be given to the player. Its only purpose is to return an integer which will be interpreted by "user_do_curse" later.

This function is called long before the player actually matches a pattern. Indeed, it is required to do so since the name of the next curse must be displayed in the player's information board/zone.

Of course, this "next curse" generation can be influenced by curses which affect the current player. For instance, you might decide that when a player is victim of the "THOU_SHALL_DIE" curse, then he can only get poor curses which have almost no effects on the opponents, and leave him in a hopeless position, bound to lose very soon. This is not fair practice but it's theorically possible 8-P

Example

The following example returns a curse code between 0 and 9 if the "ID_ZOUBIDA_MALEDICTION" curse is activated, and a code between 0 an 19 if not.

ID_ZOUBIDA_MALEDICTION=5

function user_new_curse(num)
    if u61_get_curse_age(ID_ZOUBIDA_MALEDICTION)<0 then
        num=mod(num,20)
    else
        num=mod(num,10)
    end

    return num
end

user_new_shape

Declaration

user_new_shape(num)

Description

This function is usefull for you to control the codes used for shape generation. Basically, a set of scripts might require the shape number to be between 0 and 6. So in this case you will return a value between 0 and 6. The "num" argument is here to provide a randomly generated number. You are free to use this value or not, but keep in mind that if you don't use it, it will be too late in the "user_do_shape" function to use some random numbers. You can consider this function as an intermediate between the random function and the function that generates the shape. Indeed the number it returns is used by the C++ engine to generate an internal event which will be sent to all remote players.

Tweaking this function can be very interesting if you want for instance to set up a mode where the player only receives blocks with shapes 34 an 125.

Example

This function returns a shape code between 0 and 6. More precisely, it returns:

user_new_shape(num)
    local result

    num=mod(num,100)

    if num>=60 then
        result=0
    elseif num>=50 then
        result=1
    elseif num>=30 then
        result=2
    elseif num>=20 then
        result=3
    elseif num>=10 then
        result=4
    elseif num>=5 then
        result=5
    else
        result=6
    end

    return result
end

user_rotate_left

Declaration

user_rotate_left()

Description

This function is called when the user presses the "Rotate left" key (what key exactly it is depends his key settings).

It may or may not perform a geometrical rotation. This is the case with the "classic.lua" script, but if you play with the "vertical.lua" script, you'll find out that rotation is in fact a "color shift". Basically, rotation should be any player launched function that alters your block but is not a plain translation.

If after executing the code of "user_rotate_left" there's a confict between a block square and a map square, I mean if they have the same absolute position, then the operation is automatically cancelled. This is one of the reasons you do not get "map_write" access in this function, indeed, the rollback of a map, especially the act of making a copy of a whole map in case the operation fails would in my opinion be exagerately time consuming, and I like the idea that "user_rotate_left" only affects the block.

This function is granted a "map_read" access, therefore you check if a curse is activated and modify the behavior of "user_rotate_left" dynamically.

Example

In order to show that "user_rotate_left" does not need to be a geometrical correction, this sample function performs a left/right flip of the block:

function user_rotate_left()
    local size
    local i
  
    size=u61_get_nb_items()
    i=0
    while i<size do
        u61_set_item_x(i,-u61_get_item_x(i))
        i=i+1
    end
end

user_rotate_right

Declaration

user_rotate_right()

Description

Exactly the same than "user_rotate_left". It's important to note that "user_rotate_left" does not need to be the contraryof "user_rotate_right". You may for instance decide that for a given script the "rotate left" and "rotate right" keys will correspond to 2 "special actions" which are by no way linked to each other. In fact the reason these functions are call "user_rotate_..." for is that in most scripts this name makes sense, and it would be IMHO a waste of time to redefine for each key and each script what label should be associated to keys.

Example

The following function is a function that increments the color of all the squares of the block:

function user_rotate_right()
    local size
    local i
  
    size=u61_get_nb_items()
    i=0
    while i<size do
        u61_set_item_color(i,mod(u61_get_item_color(i)+1,8))
        i=i+1
    end
end

user_square_blown_up

Declaration

user_square_blown_up(x,y)

Description

This function is called when a square has exploded. Explosions are usually started after a pattern match (see "user_match_pattern"), and then the C++ engine handles the explosion by himself, without calling any script. This takes a little time - during which the game keeps running - and when the explosion is finished, after say approximately 1/2 second, "user_square_blown_up" is called.

If the square that exploded had the same location that the current curse, then the "user_do_curse" function is launched. You do not have to do anything about this, it's just done automatically by the C++ engine.

Note that a single call to "user_match_pattern" can cause several squares to explode, so there will be several calls to "user_square_blown_up" - one per square to be precise -.

It's the responsibility of "user_square_blown_up" to change the map's structure after the square's explosion. If for instance the blown up square is to be replaced by the squares which are above it, one has to shift "manually" the squares down. Of course, you can decide that in some circumstances, the end of the explosion should cause the start of a new explosion elsewhere...

When this function is called, the square is still present on the map, and it still has its color. I mean that you can make the difference between a blue and a red blown up squares. This can be usefull if you want to set up cascading chains of explosions. Such effects can also be obtained by tweaking "user_match_pattern", but you may prefer to place your code in "user_square_blown_up", especially if your chain of explosion really depends on where the previous explosion was. The important thing to remember is that when there are no exploding squares and no pattern to match left, then the player gets a new block.

Example

The following function shifts down all the squares that are above the blown up square, and changes their color to that of the disappeared square. Note that this function moves the "curse" square if needed.

function user_square_blown_up(x,y)
    shift_column_down_and_color_it(x,y)
end

function shift_column_down_and_color_it(x_col,y_bottom)
    local y
    local color

    color=u61_get_square_color(x_col,y_bottom)
    y=y_bottom
    while u61_get_square_color(x_col,y-1)>=0 and y>0 do
        u61_set_square_color(x_col,y,color)
        y=y-1
    end
    u61_set_square_color(x_col,y,-1)

    if u61_get_curse_x()==x_col and u61_get_curse_y()<y_bottom then
        u61_set_curse_y(u61_get_curse_y()+1)
    end
end

user_time_callback_1

Declaration

user_time_callback_1()

Description

This function is called once per second. It has all rights on the map and block, so it's possible to do anything with it. By default, it increments the score of the player by 1 point, since the longer you play, the better you are...

Another typical use of this function would be to check if a curse is active and execute a special function if the curse is set on.

Example

In this example, if the "ID_BAD_LUCK" curse is active, then the player's score is not incremented any more.

ID_BAD_LUCK=13

function user_time_callback_1()
    if (u61_get_curse_age(ID_BAD_LUCK)<0) then
        u61_add_score(1)
    end    
end

user_time_callback_10

Declaration

user_time_callback_10()

Description

This function is called 10 times per second. It has all rights on the map and block, so it's possible to do anything with it.

This is a very good place to code most of the curse effects. Indeed, doing something 10 times per second makes things look fast and smooth enough in many cases, and it's not *too* time consuming (at least not as much as placing the code in user_time_callback_100).

Example

In this example, if the "ID_SHIFT_LEFT" curse is active, then the block is moved to the left.

ID_SHIFT_LEFT=33

function user_time_callback_10()
    if (u61_get_curse_age(ID_SHIFT_LEFT)<0) then
        u61_set_block_x(u61_get_block_x()-1)
    end    
end

user_time_callback_100

Declaration

user_time_callback_100()

Description

This function is called 100 times per second. It has all rights on the map and block, so it's possible to do anything with it.

Please take care not to put any complex code here, since it's executed very often. For instance, if there are 5 players in a game, it will be called 500 times per second, so if it takes 1 millisecond to be executed, then this function will eat up 50% of the CPU, so there won't be much left for other functions (which include all other Lua functions, C++ core engine, other tasks ran by the OS....). Basically, this function is here so that people who want a very accurate control of what's happening have this control, but in most cases one should try to use user_time_callback_10 or user_time_callback_1.

Example

In this example, if the "ID_SHIFT_RIGHT" curse is active, then the block is moved to the right. One could have - as in most cases - used the user_time_callback_10 function instead, only the block would have moved slower.

ID_SHIFT_RIGHT=66

function user_time_callback_100()
    if (u61_get_curse_age(ID_SHIFT_RIGHT)<0) then
        u61_set_block_x(u61_get_block_x()+1)
    end    
end

user_use_antidote

Declaration

user_use_antidote()

Description

This function is called when the player presses the key associated to the "use antidote" function. It is a user callback since there can not be a generic function for this. Indeed, what has to be done for such an action really depends on how all the curses have been coded.

Here are a few examples of what one could code in this function:

So as there are so many possibilities, I think it's a good thing to let the scripter code whatever he wants for this function. In fact, the one thing to remember about antidotes is that they appear in the status box of the player, and it's supposed to help the player.

Example

In this example, the antidote cures the oldest curse which affects the player, except for the "ID_ROTTEN_CURSE" curse. This makes the "rotten" curse an uncancellable curse, from an antidote point of view. Of course you can imagine that there are other ways to cure it, such as matching a special pattern for instance.

ID_ROTTEN_CURSE=66

function user_use_antidote()
    local oldest

    oldest=u61_get_oldest_curse(0)
    if oldest~=ID_ROTTEN_CURSE then
        u61_cancel_curse(oldest)
    end
end

This documentation is also available on: www.ufoot.org.
Contact author: ufoot@ufoot.org