U61 (v0.4.1) - Script basics




Introduction
============


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


  Overview
  --------

    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:

  User callbacks
  --------------

    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.

  System API
  ----------

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

    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.



Context handling
================


  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.



Access rights
=============


  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.



Squares
=======


  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.

