This section describes how scripting works within U61. Reading this should help you a lot writing new scripts for U61. However, it assumes that you have some basic programming skills. Anyways, if you are not a programmer yourself, you might find out that hacking U61 scripts is not that hard, by having a look on the script tutorial I made.
Different categories of functions
When scripting within U61, one has to use functions. There's not really any object-oriented approach in the scripts I wrote and I do not really see where such a thing as OO whould make things easier for beginners.
So you'll basically have to use functions, functions and functions, divided in 3 categories:
These functions *must* be defined in every script you make. They are called by the C++ engine. A typical user callback is "user_block_shape" which is the function that defines the shape of blocks. It receives an integer and must create the block associated to this code. You may change this functions (it's even recommended!) but keep in mind that they should always do something in the same spirit than the genuine ones. I mean that a "user_block_move_left" function should not try to change the map radically. However, there are protections to avoid this, as we'll see later, but still keep in mind that those functions should do something that corresponds in some way to the name they have. For instance, replacing "user_block_rotate_left" by a function that changes a block in some weird way is ok, as long as the game stays playable.
These functions are available in every script you write. In fact, it's just like I added a library to standard lua fonctions, and you can use it. A typical system api function is "u61_map_add_score" which takes an integer and adds it to the current score. You can not modify these functions, as they are coded in C++. They are the only tool you have to read or modify a player's parameters.
User library. Well, of course you can create your own set of Lua functions. In fact, those functions will be called within user callbacks and will contain calls to system API functions or to other user functions which form what I call the user library. For instance, there is a "column_down" function in the classic.lua script, which moves a whole column down by one square. This function might interest you in another script, so it can be viewed as belonging to a library but it's very different from a system API function since you can modify it.
When a user callback function is called, it's executed within a context which is associated to a player. You will never find in a system API function a parameter like 'player_id'. Indeed all script functions in U61 are designed to operate on one single player. You can safely assume that there won't be any unwanted interaction between players.
But there's a big pitfall (which I believe is not so easy to avoid that it seems) : you can *not* store anything in a global lua value. Well of course you can do it within a function - but in this case what is the advantage over a local value??? - but you can be 100% sure that what you stored there won't be available next time the function is called. Worse, it could be overwritten by some other call to the same function but for another player. Basically, using global values to communicate between different players is a *very* bad idea and is bound to fail.
However, I tried to provide a decent API to store persistent values. So you may use "u61_map_set_global" or "u61_map_get_global" to store and retrieve integers. Everything you store in there will be backuped and restored each time a user callback is called. This way you may for instance put here a flag which means "this player has the szwing-szwang-bouloulou malediction on him and he can not move his blocks whenever they are yellow or pink". You could also store a custom counter and when this counter reaches 0 you just mess up everything in the map. So these things are possible but beware, you may never store anything as a global lua value, for this would mean your script would not fit for network play, which is in my opinion very sad.
Now, these functions may read or write info on 2 different "objects", which are the map and the block, these objects are described later.
And sometimes, U61 needs some functions *not* to modify one of these objects, are not to be able to get informations about it. This is very important. For instance, the "user_block_move_right" function must not change the map. But you might think: "why should one impose such a limitation?", for I have, indeed, decided that it would be so.
In fact, the issue is about performance. Indeed the "move_..." functions may be called very often, and they need to backup everything they might possibly change, because this way if the move is not possible we can perform a sort of "rollback". So authorizing those functions to modify the map would mean a backup of the map each time you move a square, and I decided that this could be too time consumming for a simple basic move.
So every user callback and every system API function has rights associated to it. This rights are:
- block read
- block write
- map read
- map write
Basically, when a user callback is called, all the functions it calls inherits the rights it has. For instance, within "user_block_rotate_right" which grants the "block_read" and "block_write" rights, you can call "u61_block_get_x" which requires the right "block_read", but you can not call "u61_map_set_score" which requires the "map_write" right. That's all.
As U61 is a block-based game, everything is about squares. The next section describes the block and map objects, which are both made out of squares: the block is a set of squares associated to positions and a map is an array of squares.
The most important attribute of a square is its color. Today, there are 8 square colors. Some might think this is a limitation but I had to choose a number and keep myself to it since it makes it really easier to make game logic and graphics work well together. Basically it's possible to make U61 work with 64 colors, but you'll need to draw a lot of new squares and hack the C++ code. Wether it's worth it is up to you, in any case the code is GPL'ed so you can modify it if you wish, but I think 8 colors is enough to have fun and is quick to visualize.
When I say "color" it does not mean there are blue, yellow, red squares. You might want to draw different squares which do not look the same but have the same color. Color is just a way to differenciate squares from a logical point, wether the players sees different colors depends on the artist.
Very important: the convetion in U61 is to assume that when the color of a square is negative, the square is in an "empty" or "transparent" state. For instance, if you want to define a tetramino you need to define a block with 4 squares with colors greater than 0, and 96 squares with a color of -1. In the same spirit, a map is by default initialized with squares having color -1.
The block object
Well, I said there was no OO programming and I talk about a "block object". OK, let's be clear, the "block object" is not an object from the programming point of view, but it's true that from a theorical point it's an object with fields (stored by the C++ engine) and methods (prefixed by "u61_block_"). However, things such as inheritance have no sense in U61's context, so forget about OO programming and just think about the block object as some data you can access trough a pre-defined API.
So this block object represents the "falling block". It is basically composed of squares. It can have from 1 to 100 squares, and can be of any shape. Each square has the following parameters:
- x : the x coordinate. The greater it is the more the square will be on the right.
- y : the y coordinate. The greater it is the more the sqare will be close to the bottom of the map.
- color : the color of the square. The color can range from 0 to 7. A negative value indicates that the square is disactivated.
The block also has its own x and y parameters. These parameters tell in fact the global position of the square within the map. Concretely, when a block square is drawn on the map, the coordinates used are (block_x+square_x,block_y+square_y). So for instance, to move a whole block down, one just has to increase its y value.
The block object also has some interesting functions such as "block_center", please check the script API to get a complete description of them.
The map object
The map object contains... everything but the block! Indeed, you need write access to the map object to send curses, to update your curse, and of course to modify the map.
One could categoruze the map attributes and methods like this:
- Square related functions, such as "u61_set_square_color". These are probably the most usefull functions since they allow you to change the aspect of the map. Basically, the map can be considered as an array of 10x25 squares, each square having a color (0-7 are regular colors and -1 means there's no square at all).
- Curse related functions, such as "u61_get_curse_y". With these you can move the special block associated to the next player's curse, you can send a curse to another player, or use an antidote.
- Global parameters handling. Score for instance can be accessed "u61_add_score".
- Custom values storage. If you write advanced scripts, you might wish to store some data and get it back later in another function. This is *not* possible with global Lua values, because of multiplayer issues, but some functions such as "u61_set_global" make it possible to store global numbers.