MAP - OpenMMO/openMMO GitHub Wiki
##Généralités
Les fichiers .map contiennent la description des mondes virtuels sur lesquels vous jouez. Ces mondes sont des grilles de 3072x3072 cases, pavées avec des dalles de motifs différents (eau, sable, terre, herbe, rocher, graviers, pavage etc.), et décorés ci et là avec des sprites (éléments décoratifs qui s'affichent sur plus d'une case, tels que arbres, murs, rochers, montagnes, barrières etc.)
Ces mondes sont au nombre de cinq (à l'heure actuelle) :
-
Le fichier worldmap.map décrit le monde principal : les trois îles d'Althéa, plus quelques îles supplémentaires et certains intérieurs de bâtiments. Les collisions relatives à ce monde sont représentées dans les fichiers WDA par la carte de collisions nommée "Realms of Oblivion".
-
Le fichier dungeonmap.map décrit l'intérieur des donjons, tours, cryptes et bâtiments en dur, ainsi que l'île de Makrsh P'Tangh. Les collisions relatives à ce monde sont représentées dans les fichiers WDA par la carte de collisions nommée "Dungeons".
-
Le fichier cavernmap.map décrit l'intérieur des grottes et des cavernes, ainsi que le Royaume de l'Oracle. Les collisions relatives à ce monde sont représentées dans les fichiers WDA par la carte de collisions nommée "Caverns".
-
Le fichier underworld.map décrit dans sa partie supérieure six îles non utilisées dans le jeu d'origine, placées là à l'intention des techniciens des serveurs T4C qui désirent créer de toutes pièces des îles supplémentaires pour leurs joueurs, ainsi que dans sa partie inférieure, des grottes, des cavernes et des intérieurs de donjon pour accompagner celles-ci. Les collisions relatives à ce monde sont représentées dans les fichiers WDA par la carte de collisions nommée "Addon World".
-
Le fichier leoworld.map, apparu avec la version 1.50, décrit encore d'autres zones non utilisées dans le jeu d'origine, placées là à l'intention des techniciens des serveurs T4C qui désirent créer de toutes pièces des îles supplémentaires pour leurs joueurs. Les collisions relatives à ce monde sont représentées dans les fichiers WDA par la carte de collisions nommée "LeoWorld".
A noter qu'à partir de la version 1.60 de T4C, le nom de ces fichiers a été préfixé par v2_ et leur format interne diffère très légèrement (changement d'une constante).
##Format d'un fichier .map
Un fichier .map représente une grille de 3072x3072 positions. Cette grille est subdivisée en 24x24 blocs de 128x128 positions dans le fichier. Chacun de ces blocs est collé à la suite de l'autre, de gauche à droite et de haut en bas, mais les adresses de départ de chacun de ces blocs sont données en début de fichier, séquentiellement, sur quatre octets.
L'index de la première position du premier bloc correspond à la position 0,0 en jeu.
L'identifiant du dallage ou du sprite relatif à chacune de ces positions est décrit sur deux octets. Ces identifiants peuvent varier de 0x0000 à 0x0fff (0 à 4095 décimal) pour les versions de T4C jusqu'à la version 1.50, et de 0x0000 à 0x1fff (0 à 8191 décimal) pour les versions de T4C à partir de la version 1.60.
La compression de chacun de ces blocs est assurée par une version simplifiée de l'algorithme RLE.
###Format canonique d'un fichier .map :
[ 4 octets ][ 4 octets ][...][ 4 octets ][ N octets ][ N octets ][...][ N octets ]
[Adr bloc 1][Adr bloc 2][...][Adr bloc 576][Données Bloc 1][Données Bloc 2][...][Données Bloc 576]
###Algorithme de lecture des blocs
La principale difficulté d'une fonction de lecture des blocs compressés réside dans la manière de calculer la position d'une ligne de données de 128 dalles dans le buffer de l'image décompressée. Ce calcul est le suivant :
adresse = (N° bloc ''modulo'' nb blocs en largeur) * largeur bloc * taille d'un élément
+ ((N° bloc / nb blocs en hauteur) * hauteur map * taille d'un élément * hauteur bloc)
+ N° ligne * hauteur map * taille d'un élément
Soit :
adresse = (N° du bloc % 24) * 128 * 2 // X de départ du bloc à partir de la gauche de l'image
+ ((N° du bloc / 24) * 3072 * 2 * 128) // Y de départ du bloc à partir du haut de l'image
+ N° de la ligne * 3072 * 2; // Y de départ de la ligne à partir du haut du bloc
On écrira donc la fonction comme suit :
void Map_Load (unsigned char *raw_data, unsigned char *image_data, unsigned short rle_value)
{
// this function deflates a raw map file data from raw_data into image_data, using the RLE
// trigger value specified in rle_value
int block_number;
int offset;
int line_number;
int pixel_address;
unsigned char *tmp_unpack; // mallocated
// allocate space for an unpacked block of data (128x128 array, 2 bytes per slot)
tmp_unpack = (unsigned char *) malloc (128 * 128 * sizeof (short));
// for each block...
for (block_number = 0; block_number < 24 * 24; block_number++)
{
// read the block start address from the beginning of the map file
offset = *(unsigned int *) &raw_data[block_number * 4];
// uncompress this block using Vircom's pseudo-RLE algorithm
RLE_UncompressBlock (&raw_data[offset], (unsigned short *) tmp_unpack, rle_value);
// for each line in this block...
for (line_number = 0; line_number < 128; line_number++)
{
// compute line's first final pixel location in the full image array
pixel_address = (block_number % 24) * 128 * 2
+ ((block_number / 24) * 3072 * 2 * 128)
+ line_number * 3072 * 2;
// and copy the line data at this location
memcpy ((char *) &image_data[pixel_address], (char *) &tmp_unpack[line_number * 128 * 2], 128 * 2);
}
}
// map is read, decompressed and now accessible in pt_image array
free (tmp_unpack); // we no longer need the tmp_unpack memory space, so release it
return; // finished
}
##Algorithme pseudo-RLE utilisé par Vircom
-
Pour les fichiers .map jusqu'à la version 1.50, la valeur rle_value au-dessus de laquelle l'ID de la dalle sera interprétée comme un début de séquence compressée en RLE est 0x1000.
-
Pour les fichiers .map à partir de la version 1.60, la valeur rle_value au-dessus de laquelle l'ID de la dalle sera interprétée comme un début de séquence compressée en RLE est 0x2000.
L'ID de départ des dalles commence, semble-t-il, à 0x354 (852 décimal). Cette valeur sera donc ajoutée à toutes les ID décompressées.
###Format canonique d'un bloc compressé en RLE avec l'algorithme Vircom
Voici un exemple de séquence compressée :
[2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets]
[ 0x0001 ][ 0x1009 ][ 0x0002 ][ 0x0003 ][ 0x0001 ][ 0x1004 ][ 0x0003 ][ 0x0005 ]
Ce bloc se lit : 0x0001 ; 9 fois 0x0002 ; 0x0003 ; 0x0001 ; 4 fois 0x0003 ; 0x0005.
Voici donc la même séquence, décompressée :
[2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets][2 octets]
[ 0x0001 ][ 0x0002 ][ 0x0002 ][ 0x0002 ][ 0x0002 ][ 0x0002 ][ 0x0002 ][ 0x0002 ][ 0x0002 ][ 0x0002 ][ 0x0003 ][ 0x0001 ][ 0x0003 ][ 0x0003 ][ 0x0003 ][ 0x0003 ][ 0x0005 ]
On remarque que les spécifications standard du format RLE, telles que la parité obligatoire des séquences répétées ou les caractères de contrôle, ne sont pas respectées.
###Implémentation
La fonction suivante en C décompresse un bloc de données de 128x128 positions compressées avec l'algorithme pseudo-RLE de Vircom.
void RLE_UncompressBlock (unsigned char *packed_data, unsigned short *unpacked_data, unsigned short rle_value)
{
// this function deflates the map data corresponding to a 128x128 pixels size block from
// packed_data into unpacked_data using the RLE trigger value specified in rle_value.
int val;
int val2;
int i;
unsigned short *start_offset;
start_offset = unpacked_data; // remember block start address
// as long as we haven't deflated 128 * 128 = 16384 positions, i.e the full block...
do
{
val = *(unsigned short *) packed_data; // get the current value we're reading (2 bytes)
packed_data += 2; // advance in packed data
// is it below the RLE trigger value ?
if (val < rle_value)
{
*unpacked_data = 0x354 + val * 4; // if so, that's uncompressed data, so just copy it
unpacked_data++; // advance two bytes in deflated data
}
else
{
// else, this is a RLE compressed sequence size. Get the repeated value (2 bytes)
val2 = *(unsigned short *) packed_data;
packed_data += 2; // advance two bytes in packed data
// for each word of repeated RLE data...
for (i = 0; i < val - rle_value; i++)
{
*unpacked_data = 0x354 + val2 * 4; // duplicate it in the deflated buffer
unpacked_data++; // and advance two bytes in deflated data
}
}
}
while (unpacked_data - start_offset < 128 * 128);
return; // finished, block is deflated
}