SCI Specifications: Chapter 3 - The Graphics subsystem

From SCI Wiki
Revision as of 06:50, 16 September 2020 by Rainer De Temple (talk | contribs) (→‎The SCI0 and SCI01 pic resource: fixed some errors and cleaned up a little)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

General stuff

The graphics in SCI are generated using four resource types:

  • Pic resources for background pictures
  • View resources for images
  • Font resources for drawing text
  • Cursor resources for displaying the mouse pointer

Those resources are drawn on three distinct maps:

  • The visual map, used for displaying the actual pictures the player sees
  • The priority map, which keeps information about how the depth of the screen
  • The control map, which contains special information

SCI Ports

Lars Skovlund

Version 1.0, 6. July 1999

Note that the observations made in this document are generally based on SCI version 0.000.572 (the one that comes with LSL3), but should be valid even for SCI01 and SCI1, as well. I know already about some differences in the port system from SCI0 to SCI1, but I feel we should have an interpreter running for SCI0 before dealing with SCI1.

This article discusses a key data structure in SCI graphics handling; this data structure is called a port, and it is involved in most graphics-related operations. The port is basically a graphics state record, storing things like pen color, current font, cursor position etc. Each port also has an origin and a size. The actual port data structure has remained absolutely unchanged from SCI0 up to the latest versions of SCI1.

The port can be viewed as a rectangle in which things are drawn. Every drawing operation (even KDrawPic) is executed relative to the origin coordinates of the current port (depending on the kernel function, other parameters in the port structure are used as well), such that coordinate (0, 0) in the "picture window" (such a thing really exists in SCI!) is not the top of the screen, but rather the leftmost point underneath the menu bar. The coordinate set (0,0) is called the local coordinates, and its physical position on the screen, (0, 10), is called the global coordinates. Kernel calls exist to ease conversion between the two coordinate systems, but they are, it appears, meant for event handlers to use, and not generally usable (I think they take a pointer to an Event object as a parameter).

At least three ports are created and managed automatically by the SCI interpreter. These are the "window manager" port, the menu port, and the picture port (which is actually a window, see later). The latter two should be fairly easy to understand. The menu bar is drawn in the menu port, and the current room is drawn in the picture port. What may be less obvious is that the window manager port is an "invisible" port, on which the window backgrounds are drawn, although the windows have a port themselves. If you are familiar with Windows™ programming, the term "client rectangle" may ring a bell here - SCI draws the window backgrounds, using values in the window manager port, while the window's own port controls what is drawn inside it. The window manager port covers the same bounding rectangle as the picture window, but it is transparent so it doesn't mess up the graphics.

I feel compelled to mention windows for a bit here, not in depth - they are the subject of a later article - but just to mention that the structure used to manage windows is just an extension of the port structure. Whenever an SCI system call needs a pointer to a port structure, a pointer to a window structure will do. This implicates that the SysWindow class (which implements windows) has no "port" property. Instead, its "window" property points to the extended port/window structure which can safely be passed to KSetPort. Not surprisingly, many of KNewWindow's arguments end up in the port part of the window structure.

An SCI program can't directly instantiate a port. If a program wants to access a specific part of the screen using ports, it has to instantiate a transparent window. In fact, SCI creates the picture window using RNewWindow, the same function that the kernel call KNewWindow ends up calling, asking for an untitled window with a transparent background - but more on that in a later article.

It must be stressed that ports are purely internal structures. Although a program can select different ports to draw in, the data structures themselves are absolutely off-limits to SCI code. KNewWindow fills a port structure with user-supplied data, but there is no way of changing that data, short of dis- posing the window and instantiating it again. The structure is frequently changed by SCI itself, though.

Only two kernel calls deal directly with ports:

KGetPort (see the Section called Kernel function 0x14: GetPort() in Chapter 5)
KSetPort (see the Section called Kernel function 0x15: SetPort() in Chapter 5)

These two functions are often used in pairs (also internally), like:

var temp;

temp=KGetPort();  /* Save the old port */
KSetPort(...);      /* Activate some other port */
..                            /* Draw some stuff */
KSetPort(temp);    /* Reactivate the old port */

The Cursor resource

This resource stores a simple bitmap describing the shape and texture of the mouse pointer. All information stored herein is little endian in byte order.

0x00 - 0x01

X coordinate of the mouse cursor hot spot as a 16 bit integer. This variable is not used in SCI0.

0x02 - 0x03

Y coordinate of the mouse cursor hot spot as a 16 bit integer. Only 0x03 is used in SCI0; here, if set, the hot spot is at (8,8), if not set, it is located at (0,0).

0x04 - 0x23

This is a list of 16 unsigned 16 bit integers constituting bitmasks for the mouse cursor's transparency map, with the MSB representing the leftmost pixel.

0x24 - 0x43

This is another list of 16 unsigned 16 bit integers. Each of them represents another bitmask, determining whether the mouse cursor pixel should be drawn in black (not set) or white (set).

To determine whether or not to draw a pixel, and, if it is to be drawn, in which color it should be drawn in, the corresponding bits of both bitmask lists mentioned above have to be examined. In the table below, A represents a bit from the first list, and B the corresponding bit from the lower list.

 

Color mapping for the SCI0 mouse pointer

AB Result
00 Transparent
01 Transparent
10 0x00 (Black)
11 0x0f (White)

 

Color mapping for the SCI1 mouse pointer

Since this method of doing things wastes one combination, the table was changed for SCI01 and SCI1:

AB Result
00 Transparent
01 0x0f (White)
10 0x00 (Black)
11 0x07 (Light Gray)

 

The SCI0 View Resource

In SCI0, Views are collections of images or sprites. Each View resource contains a number of groups, which, in turn, contain one or more images. Usually, those groups contain a number of consecutive animation frames. It appears to be customary to store related animations or images in a single frame. For example, the basic movements of all protagonists (four or eight animation cycles (depending on the game)) are stored inside of a single View resource. Please note that the byte order of the following data is always little endian.

The View Resource

0x00 - 0x01

The number of image groups available.

0x02 - 0x03

A bitmask containing the 'mirrored' flag for each of the groups, with the LSB containing the 'mirrored' flag for group 0.

0x04 - 0x07

- unknown -

0x08...

A list of indices pointing to the start of the cell list for each image group. The number of entries is equal to the number of cells as described in 0x00 -

0x01.

Cell List

0x00 - 0x01

The number of image cells available for this group.

0x02 - 0x03

- unknown -

0x04...

A list of 16 bit pointers indexing the start of the image cell structure for each image cell. The pointers are relative to the beginning of the resource data.

Image Cell

0x00 - 0x01

The horizontal (X) size of the image.

0x02 - 0x03

The vertical (Y) size of the image.

0x04

The x placement modifier. This signed value determines the number of pixels a view cell is moved to the right before it is drawn.

0x05

The y placement modifier. This signed value determines the number of pixels a view cell is moved downwards before it is drawn.

0x06

The color key, i.e. the color number used for transparency in this cell.

0x07...

A list of combined color/repeat count entries. Each byte contains a color entry (low nibble) and a repeat count (high nibble). If the color is equal to the color key from index 0x06, then no drawing should be performed, although [repeat] pixels still need to be skipped. It is not known whether this list is terminated; the FreeSCI drawing algorithm stops drawing as soon as the rectangle defined in the first two cell entries has been filled.

The SCI font resource

SCI font resources remained unchanged during the SCI revisions and were still used in SCI32. Their format is relatively straightforward and completely sufficient for any 8 or even 16 bit character table:

Table 3-1. The SCI font resource data structure

Offset Type Meaning
0 16 bit integer, little endian encoding Always zero (?)
2 16 bit integer, little endian encoding NUMCHAR: Number of characters
4 16 bit integer, little endian encoding HEIGHT: Number of pixel lines per text line
6 + NR * 2 16 bit integer, little endian encoding Absolute offset of the character #NR, where 0 <= NR < NUMCHAR
HEIGHT does not affect the height of a character, though- it only tells the interpreter how far to move downwards when displaying a line of text. The characters referenced to starting at offset 6 are encoded as follows:

Table 3-2. The SCI font resource character data structure

Offset Type Meaning
0 unsigned 8 bit integer character HEIGHT WIDTH
1 unsigned 8 bit integer character WIDTH HEIGHT
2... bitmask, size HEIGHT * round_up(WIDTH / 8) Bitmask for the character

The bitmap consists of HEIGHT lines of n bytes, where n equals the number of bytes required for storing WIDTH bits. Data is stored with the MSB first, in little-endian encoding (first byte describes the 8 leftmost pixels), where a pixel is drawn iff the bit it corresponds to is set.

The SCI0 and SCI01 pic resource

See also
Picture Resource


The pic (background picture) resource format used in SCI0 is rather complex in comparison to the other graphical resource formats. It is best described as a sequence of drawing operations on a set of four 320x200 canvases, three of which are later used in the game (visual, priority, and control), and one of which is used during the drawing process for auxiliary purposes[Note 1].

Note hereIn order to describe the process, we will first need to define a set of operations we base them on:

FUNCTION peek_input(): Byte; /* returns the byte pointed to by the input pointer */
FUNCTION get_input(): Byte; /* works like peek_input(), but also increminates the 
                            ** input pointer  */
FUNCTION skip_input(x): Byte; /* skips x input bytes */


Using these pre-defined functions, we will now define additional helper functions used for reading specifically encoded data tuples:

FUNCTION GetAbsCoordinates(): (Integer, Integer)
VAR
    x, y, coordinate_prefix : Integer;
BEGIN
    coordinate_prefix := get_input();
    x := get_input();
    y := get_input();
    x |= (coordinate_prefix & 0xf0) << 4;
    y |= (coordinate_prefix & 0x0f) << 8;

    RETURN (x,y)
END


FUNCTION GetRelCoordinates(x : Integer, y: Integer): (Integer, Integer)
VAR
    input : Integer;
BEGIN
    input := get_input();

    ''The below code for x is inaccurate. The left-most bit is used to indicate sign, so before doing the bitshift it is necessary to remove the left-most bit''
    IF (input & 0x80) THEN
            x -= (input >> 4) & 0x7; /* remove left-most (sign) bit */
    ELSE
        x += (input >> 4);
    FI

    IF (input & 0x08) THEN
        y -= (input & 0x7);
    ELSE
        y += (input & 0x7);
    FI

    RETURN (x,y)
END

We also need some data types based on EGACOLOR and PRIORITY, which can be thought of as integers:

TYPE Palette = ARRAY[0..39] of EGACOLOR[0..1]
TYPE Priority_Table = ARRAY[0..39] of PRIORITY

Palette default_palette =
     <(0,0), (1,1), (2,2), (3,3), (4,4), (5,5), (6,6), (7,7),
      (8,8), (9,9), (a,a), (b,b), (c,c), (d,d), (e,e), (8,8),
      (8,8), (0,1), (0,2), (0,3), (0,4), (0,5), (0,6), (8,8),
      (8,8), (f,9), (f,a), (f,b), (f,c), (f,d), (f,e), (f,f),
      (0,8), (9,1), (2,a), (3,b), (4,c), (5,d), (6,e), (8,8)>;

#define DRAW_ENABLE_VISUAL   1
#define DRAW_ENABLE_PRIORITY 2
#define DRAW_ENABLE_CONTROL  4

#define PATTERN_FLAG_RECTANGLE 0x10
#define PATTERN_FLAG_USE_PATTERN 0x20

And now for the actual algorithm:

FUNCTION DrawPic (cumulative, fill_in_black : Boolean; default_palette: Integer; visual_map, priority_map, control_map, aux_map : Map): Map^4
VAR
    palette : Array [0..3] of Palette;
    drawenable, priority, control, col1, col2, pattern_nr, pattern_code : Integer;
BEGIN
    palette := (default_palette × 4);
    drawenable := DRAW_ENABLE_VISUAL | DRAW_ENABLE_PRIORITY
    control := priority := 0;
    col1 := col2 := 0;
    pattern_nr := 0;
    pattern_code := 0;

    IF (!cumulative) THEN BEGIN
        visual_map := (0xf × 320 × 200);
        map control := map priority := map aux := (0 × 320 × 200);
    END

    FOREVER DO BEGIN

        opcode := get_input();

        COND opcode:
            0xf0 → /* PIC_OP_SET_COLOR */
                code := get_input();
                (col1, col2) := palette[default_palette + (code / 40)][code % 40];
                drawenable |= DRAW_ENABLE_VISUAL;

            0xf1 → /* PIC_OP_DISABLE_VISUAL */
                drawenable &= ~DRAW_ENABLE_VISUAL;

            0xf2 → /* PIC_OP_SET_PRIORITY */
                code := get_input();
                priority := code & 0xf;
                drawenable |= DRAW_ENABLE_PRIORITY;

            0xf3 → /* PIC_OP_DISABLE_PRIORITY */
                drawenable &= ~DRAW_ENABLE_PRIORITY;

            0xf4 → /* PIC_OP_RELATIVE_PATTERNS */
                IF (pattern_code & PATTERN_FLAG_USE_PATTERN) THEN
                    pattern_nr := (get_input() >> 1) & 0x7f
                FI

                (x,y) := GetAbsCoordinates();

                DrawPattern(x, y, col1, col2, priority, control, drawenable,
                         pattern_code & PATTERN_FLAG_USE_PATTERN,
                         pattern_size, pattern_nr, pattern_code & PATTERN_FLAG_RECTANGLE);

                WHILE (peek_input() < 0xf0) DO BEGIN
                    IF (pattern_code & PATTERN_FLAG_USE_PATTERN) THEN
                        pattern_nr := (get_input() >> 1) & 0x7f
                    FI
                    (x,y) =  GetRelCoordinates(x,y);
                    DrawPattern(x, y, col1, col2, priority, control, drawenable,
                             pattern_code & PATTERN_FLAG_USE_PATTERN,
                             pattern_size, pattern_nr, pattern_code & PATTERN_FLAG_RECTANGLE);
                END

            0xf5 → /* PIC_OP_RELATIVE_MEDIUM_LINES */
                (oldx, oldy) := GetAbsCoordinates();
                WHILE (peek_input() < 0xf0) DO BEGIN
                    temp := get_input();
                    IF (temp & 0x80) THEN
                        y := oldy - (temp & 0x7f)
                    ELSE
                        y := oldy + temp
                    FI
                    x = oldx + get_input();
                     DitherLine(oldx, oldy, x, y, col1, col2, priority, control, drawenable);
                    (oldx, oldy) := (x, y);
                END

            0xf6 → /* PIC_OP_RELATIVE_LONG_LINES */
                (oldx, oldy) :=  GetAbsCoordinates()
                WHILE (peek_input() < 0xf0) DO BEGIN
                    (x, y) := GetAbsCoordinates();
                    DitherLine(oldx, oldy, x, y, col1, col2, priority, control, drawenable);
                    (oldx, oldy) := (x, y);
                END

            0xf7 → /* PIC_OP_RELATIVE_SHORT_LINES */
                (oldx, oldy) =  GetAbsCoordinates()
                WHILE (peek_input() < 0xf0) DO BEGIN
                    (x, y) := GetRelCoordinates(oldx, oldy);
                    DitherLine(oldx, oldy, x, y, col1, col2, priority, control, drawenable);
                    (oldx, oldy) := (x, y);
                END

            0xf8 → /* PIC_OP_FILL */
                IF (fill_in_black) THEN
                    (oldc1, oldc2) := (c1, c2);
                FI

                WHILE (peek_input() < 0xf0) DO BEGIN
                    (x, y) := GetAbsCoordinates();
                    DitherFill(x, y, col1, col2, priority, control, drawenable);
                END

                IF (fill_in_black) THEN
                    (c1, c2) := (oldc1, oldc2);
                FI

            0xf9 → /* PIC_OP_SET_PATTERN */
                pattern_code := get_input() & 0x37;
                pattern_size := pattern_code & 0x7;

            0xfa → /* PIC_OP_ABSOLUTE_PATTERNS */
                WHILE (peek_input() < 0xf0) DO
                    IF (pattern_code & PATTERN_FLAG_USE_PATTERN)
                        pattern_nr := (get_input() >> 1) & 0x7f
                    FI
                    (x, y) := GetAbsCoordinates();
                    DrawPattern(x, y, col1, col2, priority, control, drawenable,
                             pattern_code & PATTERN_FLAG_USE_PATTERN,
                             pattern_size, pattern_nr, pattern_code & PATTERN_FLAG_RECTANGLE);
                    END

            0xfb → /* PIC_OP_SET_CONTROL */
                control := get_input() & 0x0f;
                drawenable |= DRAW_ENABLE_CONTROL;

            0xfc → /* PIC_OP_DISABLE_CONTROL */
                drawenable &= ~DRAW_ENABLE_CONTROL;

            0xfd → /* PIC_OP_RELATIVE_MEDIUM_PATTERNS */
                IF (pattern_code & PATTERN_FLAG_USE_PATTERN) THEN
                    pattern_nr := (get_input() >> 1) & 0x7f;
                FI

                (oldx, oldy) := GetAbsCoordinates();

                DrawPattern(x, y, col1, col2, priority, control, drawenable,
                         pattern_code & PATTERN_FLAG_USE_PATTERN,
                         pattern_size, pattern_nr, pattern_code & PATTERN_FLAG_RECTANGLE);

                WHILE (peek_input() < 0xf0) DO BEGIN
                    IF (pattern_code & PATTERN_FLAG_USE_PATTERN) THEN
                        pattern_nr := (get_input() >> 1) & 0x7f;
                    FI
            
                    temp := get_input();
                    IF (temp & 0x80)
                        y := oldy - (temp & 0x7f)
                    ELSE
                        y := oldy + temp
                    FI
                    x := oldx + get_input();
                    DrawPattern(x, y, col1, col2, priority, control, drawenable,
                             pattern_code & PATTERN_FLAG_USE_PATTERN,
                             pattern_size, pattern_nr, pattern_code & PATTERN_FLAG_RECTANGLE);
                END

            0xfd → /* PIC_OP_OPX */
                COND get_input():
                    0x00 → /* PIC_OPX_SET_PALETTE_ENTRY */
                        WHILE peek_input() < 0xf0 DO BEGIN
                            index := get_input();
                            color := get_input();
                            palette[index / 40][color % 40] := color;
                        END

                    0x01 → /* PIC_OPX_SET_PALETTE */
                        palette_number := get_input();
                        FOR i := 0 TO 39 DO
                            palette[palette_number][i] := get_input();
                        OD

                    0x02 → /* PIC_OPX_MONO0 */
                        skip_input(41);

                    0x03 → /* PIC_OPX_MONO1 */
                        skip_input(1);

                    0x04 → /* PIC_OPX_MONO2 */
                    0x05 → /* PIC_OPX_MONO3 */
                        skip_input(1);

                    0x06 → /* PIC_OPX_MONO4 */
                    0x07 → /* PIC_OPX_EMBEDDED_VIEW */ /* SCI01 operation */
                    0x08 → /* PIC_OPX_SET_PRIORITY_TABLE */ /* SCI01 operation */

            0xff → return (visual, control, priority, aux);
        END OF COND
      END
END

This algorithm uses three auxiliary algorithms, DrawPattern, DitherLine, and DitherFill, which are sketched below. All of these functions are supposed to take the four maps as implicit parameters.

PROCEDURE DrawPattern(x, y, col1, col2, priority, control, drawenable : Integer;  solid : Boolean ;  pattern_size, pattern_nr : Integer; rectangle : Boolean)

Alters (x,y) so that 0 <= (x - pattern_size), 319 >= (x + pattern_size), 189 >= (y + pattern_size) and 0 <= (y - pattern_size), then draws a rectangle or a circle filled with col1, col2, priority, control, as determined by drawenable.

If rectangle is not set, it will draw a rectangle, otherwise a circle of size pattern_size. pattern_nr is used to specify the start index in the random bit table (256 random bits)

PROCEDURE DitherLine(x, y, xend, yend, color1, color2, priority, control, drawenable : Integer)

Draws a dithered line between (x, y+10) and (xend, yend+10). If the appropriate drawenable flags are set, it draws 'priority' to the priority map, 'control' to the control map, and 'color1' and 'color2' (alternating) to the visual map. The auxiliary map is bitwise-or'd with the drawenable flag while this is done.

PROCEDURE DitherFill(x, y, col0, col1, priority, control, drawenable : Integer)

Fills all layers for which drawenable is set with the appropriate content. Diagonal filling is not allowed. Boundaries are determined as follows:

<x<0, x>319, y<10, y>199 are hard boundaries. We now determine the 'boundary map' bound_map and the allowed color legal_color. If bound_map[coordinates] = legal_color, then the pixel may be filled.

IF (drawenable & DRAW_ENABLE_VISUAL)
    bound_map = visual;
    legal_color = 0xf;
ELSIF (drawenable & DRAW_ENABLE_PRIORITY)
    bound_map = priority;
    legal_color = 0;
ELSIF (drawenable & DRAW_ENABLE_CONTROL)
    bound_map = control;
    legal_color = 0;
ELSE
    return;
FI

Windows, Dialogs and Controls

by Lars Skovlund

Version 1.0, 7. July 1999

I am going to start by mentioning the menus. It has nothing to do with the material I deal with in this essay. They use different kernel calls, and such things as port management are handled internally by the kernel routines. The SCI program just sets up a menu structure using the kernel calls. Since they are irrelevant to the subject of this essay, I will not spend more time on them.

The Rect structure is important (also to ports) since it is the basis for passing a screen position to the interpreter. It looks like this:

typedef struct
{
  short top, left, bottom, right;
}

It will be seen from this that rectangle coordinates in SCI are not normally represented in the usual (x,y,width,height) fashion. So pay close attention to this structure! Also, it is not passed as a pointer, but rather as the four values in order. This is particularly true of SCI objects, where the property names nsTop etc. actually form a Rect structure which can be used directly by the interpreter.

Windows are created using the KNewWindow kernel function. Each window has six attributes which are passed from the script to the kernel function:

Bounding rectangle
Title
Type
Priority
Foreground color
Background color

Of these, the type and priority are the most interesting, because they decide the appearance of the window. The type is a bit field:

bit 0 - transparency
bit 1 - window does _not_ have a frame
bit 2 - the window has a title
bit 3-6 - unused
bit 7 - see below

Bit 0 specifies a transparent window. KNewWindow does not save the image behind the created window - it stays on the screen until the pic is redrawn, so windows with this style definitely can't be used as message boxes. It does have some special uses, though. If this bit is not set, KNewWindow draws a rectangle in the specified background color using the bounding rectangle coordinates (using the WM port). When this bit is set,

Bit 1 specifies a window without a frame. The frame is the black shading you can see in the corner of a message box.

Bit 2 tells KNewWindow to draw a grey title bar with a title printed in white. In the version I have used for this essay, it is not possible to change the title bar colors. Note that the bounding rectangle is always specified as if the window had no title bar. If this bit is set, ten pixels are reserved above the coordinates specified. Although this bit is set, the Title parameter may still be NULL. If this is the case, an empty title bar is drawn.

Bit 7 has a special meaning; it is used only in window type 0x81, and is not tested in any other way. When this style is chosen, KNewWindow does not draw anything at all. It is the caller's responsibility to draw a window frame on the WM port. CB1 uses this style for its ornate windows, and draws the frame manually.

The picture window which I mentioned in the last article is created using style 3 (that is, TRANSPARENT | NOFRAME). The normal message box styles used in LSL3 are 0 and 4.

I have not been able to investigate the priority property yet, so the fol- lowing is based on suppositions. It is only used when drawing transparent windows. In this case, if priority is not -1 (which means not used), the window is drawn onto the priority map (with the specified priority value) as well as the screen.

There is a class called SysWindow which is just a simple wrapper around the following two kernel calls. Try breaking on SysWindow::open, then type c to inspect the current object. You can change all the parameters to KNewWindow (the Rect is split in its fields, to nsTop, nsLeft etc.)

To create a window structure, use KNewWindow (see the Section called Kernel function 0x13: NewWindow(Rect, HeapPtr, word, word, word, word) in Chapter 5); to remove it again, apply KDisposeWindow (see the Section called Kernel function 0x16: DisposeWindow(HeapPtr Window) in Chapter 5) on it.

So how do we put stuff inside these windows? That question is a little complicated to answer, because it is really a shared effort between the interpreter and the object hierarchy, and this is one case where the interpreter actually interacts with the objects itself. I will start by explaining the classes involved.

All control types are descendants of a common class (I do not know its name, since it appears to have an invalid name property). Among other things, this common class contains a type number and a state. The type number is the only thing that distinguishes the control types from each other inside the interpreter - if a wrong type is set, the interpreter might try to change a non-existent property.

The type numbers are laid out as follows:

  1. - Button control
  2. - Text control
  3. - Edit control
  4. - Icon control
  5. - not used
  6. - Selector control (as in the Save and Restore boxes)

The gauge "controls" are not really controls. I don't know how they work (yet).

Each control also has a state value. These are laid out as follows:

bit 0 - selectable. If this bit is set, the control can be selected using the Tab key. Except for the text and icon controls, all controls are selectable.
bit 1 - unknown. Always set, except for the text and icon controls
bit 2 - disabled. When this bit is set, a button is grayed out. No other control types are affected.
bit 3 - selected. When this bit is set, a frame is drawn around the control.

Note that state 3 is by far the most common. With that explained, I'll move on to the kernel functions. There are three functions associated with controls - KDrawControl (see the Section called Kernel function 0x17: DrawControl(HeapPtr) in Chapter 5), KHiliteControl (see the Section called Kernel function 0x18: HiliteControl(HeapPtr) in Chapter 5) and KEditControl (see the Section called Kernel function 0x19: EditControl(HeapPtr) in Chapter 5). Note that there is a KOnControl kernel call which is entirely unrelated to window management.

The dialogs are implemented using not one, but two classes - Dialog and Window. While the Window class maintains the window (It is derived from SysWindow), the Dialog class is just a list of controls. It is derived from the List class, but has extended functionality to tell its members to redraw etc. There is a special function, located in script 255, which allows scripts to push information about the dialog on the stack instead of creating the Dialog object manually.

Note that the internal debugger uses the same window calls as the SCI script. That is why the screen messes up if you step through drawing code - the debugger has activated the Debug window port, and "forgets" to switch back while stepping across instructions. Thus, all graphics commands are redirected to the debug window port. Not a pretty sight.

Pictures and movement control

By Lars Skovlund

Version 1.0, 24. July 1999

A pic in SCI consists of three layers (called maps - they are unrelated to the map resources found in SCI1 games). The visual map, used for the picture which appears on the user's screen. The priority map which tells the interpreter which things go in front of which in the three-dimensional room. Without the priority map, a room would just be a flat, painted surface. The control map decides where game characters (called actors) can walk and where special events occur. These special events are triggered by a game character walking on a particular spot. Where the visual map is almost always very complex and using dithered fills etc., the latter two consist of large areas of solid color.

Many functions which need to access these maps do so by using a bit-field. The bits are laid out as follows (but don't set more than one at a time!)

bit 0 - Visual
bit 1 - Priority
bit 2 - Control

It is important to understand that, although being represented as colors on the screen, a priority/control "color" should be considered a number. The colors map to values according to the standard EGA color values.

Every animated object in SCI has a priority. As the object moves, its pri- ority changes according to the so-called priority bands, explained next (it is, however, possible for a script to lock the priority of a view). The picture window is divided vertically into 16 priority bands. The priority of an animated object is determined by the position of its "base rectangle" in one of these bands. Things are drawn in order of ascending priority, so objects with priority 15 are guaranteed to be in front of everything else. The default priority mapping gives priority 0 a fairly large space, the 42 topmost rows (including the menu bar which AFAIK is 10) in the picture. All other priority bands have the same size. A script can choose to alter this mapping, specifying the amount of space to assign to priority 0, and the number of the last row to include in the mapping calculation.

In most rooms, it is desirable to limit actor movement, confining the actor to a specific part of the screen. In other cases, special events are triggered by movement into a specific screen area. On some occasions, even room switches are implemented using control polygons. While the meaning of priorities is determined by the kernel, the meaning of control values is entirely up to the script. It is more or less a standard, however, that actors can't walk on white control areas.

As the control map is not consulted by the interpreter itself (except in a few cases), scripts need a way to do so. That way is called OnControl, and it is a kernel call. Supplied with a point or a rectangle, it returns a bit mask containing the control values of all the pixels in the desired region. If a specific control value is encountered, it is used as a bit number, and that bit is set in the output mask.

This bit mask system is also used in another place, namely the illegalBits selector of the Act (actor) class. The illegalBits selector determines in which areas the actor may not walk.

The OnControl() system call is explained in the Section called Kernel function 0x52: OnControl(word, Point | Rect) in Chapter 5.

Notes
  1. Due to the vector graphics nature of these drawing operations, they are inherently more scaleable than pixmaps.

 

< Previous: Chapter 2 - Resource filesNext: Chapter 4 - The Sound subsystem >