Home | Introduction | Installation | Troubleshooting | 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)

- Arguments: a code previously returned by "user_new_curse", a boolean saying if the curse is local or comes from another player.
- Return values: none
- Rights: block_read, block_write, map_read, map_write

### Description

This function can be called int 2 cases:

- When a square explosion has ended, and this square's location was the location of the current curse. If you are playing with the default theme, it means that the square with the blinking "?" has exploded.
- When another player has called the "u61_send_curse" function, and the current player was his default victim.

Basically, one could separate the curses into 2 categories:

- Those who affect the player which launches them. They usually have a positive effect. For instance, it can be a goodie which slows your blocks down so it's easier for you to play. These curses are executed directly by "user_do_curse", the parameter "sent" being set to 0 (false).
- Those who affect the launcher's victim. They usually have a negative effect. For instance, it can be a nasty curse which speeds up the blocks, so that they become uncontrollable. These curses are also executed by "user_do_curse" (with "sent" set to 0), but one has to call "u61_send_curse" manually to send the curse to the other player (the victim). This other player will receive the curse, but this time the "sent" parameter will be 1 (true). So the "sent" parameter is fundamental since it allows you to identify if the curse is to be sent to the victim or to be executed.

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" is a goodie which is executed locally and increases the player's score.
- "ID_CURSE_MINUS" is a nasty curse which reduces the score of the player's victim.

    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)

- Arguments: a number previously given by user_new_shape
- Return values: none
- Rights: block_read, block_write, map_read

### 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:

- A triangle with color 0 if num is 0
- A square with color 2 if num is 1
- A circle with color 5 if num is 2

    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)

- Arguments: a code previously returned by "user_new_curse"
- Return values: the name of the curse, which should be shown to the player
- Rights: map_read

### 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()

- Arguments: none
- Return values: none
- Rights: block_read, block_write, map_read

### 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)

- Arguments: the number of times "user_match_pattern" has already been called during last matching session.
- Return values: 1 if some matching shapes and/or colors have been found, 0 otherwise.
- Rights: map_read, map_write

### 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:

- 100 points if there's only one pattern matched
- 300 points if there are two pattern matched
- 700 points if there are three pattern matched
- etc...

### 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()

- Arguments: none
- Return values: none
- Rights: block_read, block_write, map_read

### 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:

- The user presses the "move down" key (that's to say he presses the key which he has specified in his settings to be the "move down" key).
- A certain amount of time has ellapsed, and U61 decides to make the block move down. How long this delay is depends on the global speed of the game, which can be set in the game options, and generally increases while the game is running. And this is why it is important that one of the actions performed by "user_move_down" is actually to shift the block down. Indeed, one of U61 basic rules - the ones you can not change within the script -, is that "the block falls, and when it lands on other blocks, it freezes". If "user_move_down" does not move the block down, you could get a situation where your block never freezes and you never get any new block. Basically, a new block is sent to the user when a call to "user_move_down" has created a conflict between the block's squares and the map's squares. It is only in this case that the block will be freezed and that "user_land" and "user_match_pattern" will be called.
- The user presses the "drop" key, in this case "user_move_down" is called as often as possible until the block is stuck on the map. Again, if you ever make a "user_move_down" that does not lead to an incoherent position after a certain amount of calls, this function will loop for ever...

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()

- Arguments: none
- Return values: none
- Rights: block_read, block_write, map_read

### Description

This function can generally be called in 2 cases:

- The user has pressed the "Move left" key. (which key it is physcically depends on the user's settings). This is the most common case, and you had certainly thought of it.
- The "Rotate left" or "Rotate right" key has been pressed, and the "user_rotate_..." corresponding callback has put the block in such a state that there's a conflict between a square of the block and a square of the map. Therefore U61 automatically simulates a "Move left" or "Move right" key press, and then retries to execute the "user_rotate_...". This is an interesting feature since it enables the user to rotate his blocks even if the block is stuck to a wall.

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()

- Arguments: none
- Return values: none
- Rights: block_read, block_write, map_read

### 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)

- Arguments: a random value generated by the C++ engine
- Return values: the code of the curse that will be triggered if the current curse block explodes
- Rights: block_read, map_read

### 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)

- Arguments: a random number between 0 and 1 000 000 000
- Return values: the code that should be use for the next call to "user_do_shape"
- Rights: block_read, map_read

### 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:

- 40% of 0
- 20% of 1
- 10% of 2,3,4
- 5% of 5,6

    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()

- Arguments: none
- Return values: none
- Rights: block_read, block_write, map_read

### 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()

- Arguments: none
- Return values: none
- Rights: block_read, block_write, map_read

### 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)

- Arguments: coordinates of the square which blew up
- Return values: none
- Rights: map_read, map_write

### 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

### Declaration

    user_time_callback()

- Arguments: none
- Return values: none
- Rights: block_read, block_write, map_read, map_write

### Description

This function is called about 10 times/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. But it's wise not to code something too complex and/or time consuming in this function, since it's called very often.

### 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()
        if (u61_get_curse_age(ID_BAD_LUCK)<0) then
            u61_add_score(1)
        end
    end

## user_use_antidote

### Declaration

    user_use_antidote()

- Arguments: none
- Return values: none
- Rights: block_read, block_write, map_read, map_write

### 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:

- Remove the oldest curse. That's an obvious solution, when you use an antidote, it cures your oldest illness.
- Remove the curses in a special order, based on the severity of the curse. This way one could imagine that an antidote always cures the most annoying curse.
- Change the effect of curses. One could imagine special curses which the antidote can't really kill, but can make them less annoying, as if the desease were half cured.
- Invert the effect of the antidote. Indeed, a nice curse would be to put the player in such a state that the use of an antidote has the opposite effect of what he expects.

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()
        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