Background Graphics
Also see Custom Graphics Engine Frequently Asked Questions
Each cell of the dungeon can have its own 'Skin'. A 'Skin' consists of zero to four graphics assocated with the 'Floor', 'Middle', 'Ceiling', and 'Walls' and four masks which describe what part of the graphics are to be displayed and where.. The Skin also can contain graphics for each of the possible Wall Decorations and for each of the possible Floor Decorations. The Skin number for each Cell is stored in the dungeon file. The definition of each Skin (the IDs of the four graphics, the four sets of masks, and the graphics and masks for each Decoration) is contained in the CSBgraphics.dat file. The graphics themselves are also stored in the CSBGraphics.dat file. The graphic 'masks' are also stored in CSBGraphics.dat.. All the graphics associated with backgrounds (including masks and skin definitions) are stored as a single 'Graphic Type' identified as 'Background Graphics' when using CSBGraphics.exe. Finally, the 'CSBGraphics.dat' contains 'code' for each skin that is executed and which controls the drawing of the floors, middle, ceiling, walls, and decorations.
The dungeon can contain up to 255 'Skins'. There can be up to 65535 graphics and masks for the floors, middle, ceiling, walls, and decorations combined. Each of the Floor, Middle, and Ceiling graphics is a single graphic that is (in the simplest case) the size of the entire area. The masks for each of these area selects the portion of the graphic to be displayed and designates the position in the viewport where it is to be displayed. Each Decoration is a single graphic and collection of masks that control what pieces of the graphic are to be displayed and where.
The Code has a separate function for each Cell position and Cell type. For example, there is a function for a Stone Cell two steps ahead and one to the right. There is another function for a teleporter two steps ahead and one to the right. There is another function for a teleporter one step aheand and one to the right.. Any one of these functions can do as it pleases. For example, the function for a Teleporter one step ahead and one step to the left can draw a Door two steps ahead and one to the right if it so pleases. But a person would have to be mad to do this. Are you there, Zyx? See Custom Graphics Code Explained
The cells are numbered from 0 to 20 starting at the farthest visible cell to the far left and working to the right and closer. The drawing functions are called in inccreasing numerical order. Here is a diagram with the party looking north (P = party):
--------------------------
| 0 | 4 | 6 | 5 | 1 |
| | | | | |
--------------------------
| 2 | 7 | 9 | 8 | 3 |
| | | | | |
--------------------------
| 10 | 12 | 14 | 13 | 11 |
| | | | | |
--------------------------
| 15 | 17 | 16 |
| | | |
----------------
| 18 | 20 | 19 |
| | P | |
----------------
The definition of the skins is stored as 'Background Graphic' with ID = 1.. Each skin is defined by a number of 16-bit values that designate the graphicID for the floors, middle, ceiling, walls, and decorations. Very simple. Skin 0 existes in the definitions but is never used because Skin 0 means to use no custom background. Skin 1 follows, then Skin 2, etc, each occupying 16 bytes. The first four 16-bit values represent the Floor Graphic ID, the Middle graphic ID, the Ceiling graphic ID, and the Wall graphic ID associated with that skin. The second four 16-bit values identify the mask to be used for Floor, Middle, Ceiling, and Walls. If any of the graphic IDs (or mask ID) is zero then no custom graphic is used for the corresponding portion of the viewport.. The remainder of the skin definition consists of a variable number of 16-bit values for the floor decorations and a variable number of 16-bit words for the wall decorations. The total structure looks like this:
struct SKINS
{
i16 number of skins = Sn
i16 offset of skin 0 ( not used - must be zero )
i16 offset of skin 1
...
i16 offset of skin Sn-1
SKINDEF skinDef [ Sn ] ;
};
struct SKINDEF
{
i16 floor graphic ID
i16 middle graphic ID
i16 ceiling graphic ID
i16 wall graphic ID
i16 floor mask ID
i16 middle mask ID
i16 ceiling mask ID
i16 wall mask ID
i16 code ID
i16 number of floor decorations = Fn
i16 floor decoration graphic ID [ Fn ]
i16 number of wall decorations = Wn
i16 wall decoration graphic ID [ Wn ]
};
The graphics themselves (other than the skin definition) can have any ID you choose although it is wasteful to use very large graphic numbers because it causes memory to be wasted at runtime. Also, at runtime you can generate 'Virtual' graphics such as mirrored floors, shifted, and scaled graphics
The graphics and the masks are in the Atari 'Bit-Plane' format in which four 16-bit words are used to represent 16 pixels. The first of the four words contains the least significant bit of each of the 16 pixels. The second 16-bit word contains the next least significant bits, etc. The most significant bit in each of the four words provide the four bits for the first pixel, etc. In this way the four words define 16 pixels. The mask need only contain one word for the 16 pixels because the masks for each of the four words are identical and can be constructed at runtime by replicating just one word. So, in the mask graphic 16 pixels are represented by one 16-bit word and in the 'picture' graphic 16 pixels are represented by four words.
The Mask graphic has the following format:
struct index
{
i32 number of masks = Mn
i32 maskOffset [ Mn ]
};
maskOffset is the byte offset within the file of the start of each of the masks used by the skin. If any of these is zero then any code using that mask will not be drawn. Each mask (pointed at by the index entry) is of the following format:
struct maskEntry
{
ui16 sourceX;
ui16 sourceY;
ui16 destinationX;
ui16 dstinationY;
ui16 width;
ui16 height;
ui16 pixels[width*height/16];
};
The 'sourceX', 'sourceY', 'destinationWidth', 'destinationHeight', 'width' and 'height' are all in units of one pixel. Therefore, 'sourceX' and 'width' must be multiples of 16; 'destinationX need not be a multiple of 16 although it is more efficient if it is. 'destinationX' and 'destinationY' are relative to the top left of the viewport. As a result of this, you can draw floor graphics on the ceiling or anywhere else in the viewport that you choose. Moreover, the graphics can be any size. So if you want to draw trees or columns you can do so.
The 'picture' graphic has the following format:
struct picture
{
i32 width; //In units of pixels. Multiple of 16;
i16 pixels[#pixels in graphic/4]
};
*******************************************************************************************************
*****
***** A DECORATION GRAPHIC ( WALL OR FLOOR ) LOOKS LIKE THIS
*****
*******************************************************************************************************
i16 number of decorationPictures = Pn
i16 offset of decorationP icture 0
i16 offset of decorationP icture 1
.....
i16 offset of decorationP icture Pn-1
i16 number of decorationLocations = Ln
i16 decorationLocation 0
i16 decorationLocation 1
...
i16 decorationLocation Ln-1
Here starts the actual bitmap data and mask data.
Each picture is an 'interleaved' bitmap with the mask interleaved in the pixel data.
struct decorationPicture
{
i8 width; // In units of pixels. Multiple of 16.
i8 height;
PICTUREGROUP pictureData[width/4 * height];
};
struct PICTUREGROUP
{
i16 mask;
i16 bitplanes[4];
};
struct decorationLocation
{
i8 destinationX;
i8 destinationY;
};
Notice that the pictures have no sourceX nor sourceY. The source of bitmaps all start at ( 0, 0 );
Like other graphics, the destinationX must be a multiple of 16 and the width must be a multiple of 16.