SCI/Specifications/SCI in action/Views and animation in SCI

From ScummVM :: Wiki
Jump to navigation Jump to search

Views and animation in SCI

Original document by Lars Skovlund with notes by Christoph Reichenbach

This chapter deals with a rather complex subject within SCI. The subsystem described here is one of the "bad boys" in SCI, since it calls functions in user space, as well as changing the value of various selectors. This document is not necessarily complete. There are several things I have not covered - because they are better off in a separate document, or simply because I haven't yet figured that part out. IOW, this stuff is incomplete. Things may change.

After drawing a pic on the screen (which is DrawPic's job, that doesn't surprise now, does it?), some views have to be added to it. There are two ways of doing this; the AddToPic and the Animate call. While AddToPic is used for static views, Animate lets each animated view in the cast list perform an "animation cycle".

An animation cycle is done entirely in SCI code (with the aid of some kernel calls). It involves two other objects; the mover and the cycler. The mover is responsible for controlling the motion of an actor towards a specific point, while the cycler changes the image of the actor, making him appear to walk, for instance.

The behavior of a view is controlled by its signal property. This property contains a bitfield which describes a lot of animation-related stuff. The bits can be roughly divided into two groups; the script and interpreter bits (I had called them Option and State bits at first, but that is not entirely accurate). The first group allows the script to influence the drawing pro- cess somewhat, the other are used internally by the interpreter. The two groups overlap a bit, though.

Table 6-1. SCI and FreeSCI signal bits

Bit # Name FreeSCI constant Meaning
0   _K_VIEW_SIG_FLAG_STOP_UPDATE A view updating process has ended
1   _K_VIEW_SIG_FLAG_UPDATED The view object is being updated
2 noUpd _K_VIEW_SIG_FLAG_NO_UPDATE Don't actually draw the view
3   _K_VIEW_SIG_FLAG_HIDDEN The view is hidden from sight. Often, if an actor is

supposed to enter and exit a room (such as the guards in the plazas in QfG2), this bit is used. When he's supposed to enter the room, bit 3 in his signal is cleared. When he leaves, bit 3 is set, but his SCI object is not deleted.

4 fixPriOn _K_VIEW_SIG_FLAG_FIX_PRI_ON if this bit is set, the priority of the view never changes

(if it isn't, the interpreter recalculates the priority automagically).

5   _K_VIEW_SIG_FLAG_ALWAYS_UPDATE  
6   _K_VIEW_SIG_FLAG_FORCE_UPDATE  
7   _K_VIEW_SIG_FLAG_REMOVE The view should be removed from the screen (an interpreter

bit - its corresponding script bit is bit 3). If bit 3 isn't set as well, the view reappears on the next frame.

8   _K_VIEW_SIG_FLAG_FROZEN Deactivates the mover object of the view (it is "frozen" -

the view can still turn, however).

9 isExtra _K_VIEW_SIG_FLAG_IS_EXTRA  
10   _K_VIEW_SIG_FLAG_HIT_OBSTACLE The view hit an obstacle on the last animation cycle
11   _K_VIEW_SIG_FLAG_DOESNT_TURN Meaningful for actors only. Means that the actor does not

turn, even though he is walking the "wrong way".

12   _K_VIEW_SIG_FLAG_NO_CYCLER The view cycler is disabled. This makes the view float

instead of walk.

13 ignoreHorizon _K_VIEW_SIG_FLAG_IGNORE_HORIZON  
14 ignrAct _K_VIEW_SIG_FLAG_IGNORE_ACTOR Actors can walk in the rectangle occupied by the view. The

behavior of this bit is odd, and best expressed by example. The Guild Master in QfG1 has his bit 14 set. This means that ego (or someone else) can walk all the way to his chair (try sneaking in on him from behind). If we clear this bit, we can't sneak in on him.

15   _K_VIEW_SIG_FLAG_DISPOSE_ME The view should be disposed
[1]   _K_VIEW_SIG_FLAG_FREESCI_PRIVATE Used as an intermediate result by the interpreter; marks

views that are going to have their nsRect/lsRect regions redrawn (for the test in the main draw algorithm's step 17.1., below)

[1]   _K_VIEW_SIG_FLAG_FRESCI_STOPUPD View has been 'stopupdated'. This flag is set whenever the

view has the STOP_UPDATE bit set, and cleared as soon as it moves again. Stopupdated views are collided against differently than normal views.


The unlisted bits are probably all interpreter bits. They don't seem to have an effect when set. Many bits seem to be involved in the decision whether to display a view or not. I have not completely figured this out.[2]

Animate (see the Section called Kernel function 0x0b: Animate([DblList], [word]) in Chapter 5) can be called in two ways:

Animate(DblList cast, bool cycle)
Animate()

If the second syntax is used, the two parameters are assumed to be zero.

The cast list is just a list of the views to draw. Animate creates a backup of this list for updating purposes. However, this backup cast list isn't just a normal copy. The interpreter copies some selectors from the view (view, loop, cel, nsRect) and places them in a special data structure. This indicates to me that there is a possibility that the view objects may be deleted even though an update is anticipated.

The general pseudocode for Animate goes as follows:

0. Backup PicNotValid: PicNotValid' := PicNotValid
1. If we don't have a new cast:
    1.1. if PicNotValid is set:
        1.1.1. Redraw picture with opening animation
    1.2. exit
2. For each view in the cast list:
    2.1. If view is not frozen:
	2.1.1. call view::doit(), performing an animation cycle
3. Prepare a list of y coordinates by traversing the cast list
4. For each view in the cast list:
    4.1. If the view resource view::view has not been loaded yet:
	4.1.1. Load view.nr, where nr=view::view
5. For each view in the cast list:
    5.1. If view::loop is invalid, set view::loop := 0
    5.2. If view::cel is invalid, set view::cel := 0
6. Sort the cast list, first by y, then by z
7. For each view in the cast list: Update view::nsRect (SetNowSeen())
8. For each view in the cast list: Unless the views' priority is fixed, recalculate it
9. For each view in the cast list:
    9.1. If NO_UPDATE is set for the view:
	9.1.1. If the following holds:
            9.1.1.1.     (VIEW_UPDATED || FORCE_UPDATE)
            9.1.1.2.  || (!(VIEW_UPDATED || FORCE_UPDATE) && !IS_HIDDEN && REMOVE_VIEW)
            9.1.1.3.  || (!(VIEW_UPDATED || FORCE_UPDATE) && !IS_HIDDEN && !REMOVE_VIEW && ALWAYS_UPDATE)
            9.1.1.4.  || (!(VIEW_UPDATED || FORCE_UPDATE) && IS_HIDDEN && ALWAYS_UPDATE)
	    9.1.1.5. then increase PicNotValid by one.
	9.1.2. Clear the STOP_UPDATE flag
    9.2. otherwise:
	9.2.1. If (STOP_UPDATE and !ALWAYS_UPDATE) or (!STOP_UPDATE and ALWAYS_UPDATE)
            9.2.1.1. Increase PicNotValid by one
	9.2.2. Clear the FORCE_UPDATE flag
10. If PicNotValid is now greater than zero, call the sub-algorithm described separately
11. For each view: If NO_UPDATE, IS_HIDDEN and ALWAYS_UPDATE are not set:
    11.1. [12] Save the area covered by the view's nsRect, store the handle in view::underBits
    11.2. [13] Draw the view object
    11.3. [14] If the view IS_HIDDEN, clear the REMOVE_VIEW bit (don't need to hide it twice)
    11.4. [15] Insert the view into the backup cast list
16. If PicNotValid', our copy of the initial value of PicNotValid, is non-zero:
    16.1. Refresh entire screen with opening animation
    16.2. PicNotValid := 0
17. For each view in the cast list:
    17.1. [18] If the view was changed in step 10 and neither REMOVE_VIEW nor NO_UPDATE is set:
        17.1.1. [19] Redraw the nsRect and lsRect areas
        17.1.2. [20] Copy the nsRect to the lsRect
        17.1.3. [21] If IS_HIDDEN, set REMOVE_VIEW as well
22. For each view in the reverse cast list:
    22.1. [23] If neither NO_UPDATE nor REMOVE_VIEW is set:
        22.1.1. Restore the underbits
        22.1.2. Clear the underbits
    22.2. [24] if DISPOSE_ME is set, call view::dispose to dispose it

With the sub-algorithm being:

1. For each view from the cast list:
    1.1. [2] If NO_UPDATE is set:
        1.1.1. [3] If REMOVE_VIEW is set:
            1.1.1.1. If PicNotValid is 0, restore the area covered by view::underBits
            1.1.1.2. Free view::underBits
        1.1.2. [4] Clear FORCE_UPDATE
        1.1.3. [5] If VIEW_UPDATED is set: Clear VIEW_UPDATED and NO_UPDATE
    1.2. otherwise (if NO_UPDATE is not set):
        1.2.1. Clear STOP_UPDATE
        1.2.2. Set NO_UPDATE
6. For each view from the cast list:
    6.1. [7] Draw the view
    6.2. [8] If ALWAYS_UPDATE, clear STOP_UPDATE, VIEW_UPDATED, NO_UPDATE, FORCE_UPDATE
    6.3. [9] Clip the nsRect against the boundaries of the "natural" priority band of the view
    6.4. [10] If IGNORE_ACTOR is clear, fill the area found in 6.3. with 0xf on the control map
11. For each view from the view list:
    11.1. if NO_UPDATE is set:
        11.1.1. [12] If IS_HIDDEN, then set REMOVE_VIEW, otherwise:
            11.1.1.1. clear REMOVE_VIEW
            11.1.1.2. [13] Save the area covered by the nsRect in the underBits
14. For each view from the cast list:
    14.1. If NO_UPDATE is set and IS_HIDDEN is clear:
        14.1.1. [15] Draw the view

Note that the ReAnimate subfunction (0x0D) of the Graph kernel call redraws parts of the maps using the cast list created by Animate, whereas the ShowBits call (0x0C) copies parts of the active map to the physical screen.

Notes

  1. 1.0 1.1 This flag is used internally in FreeSCI; it can't be found in the view objects, only in their copies in the dynview widget list.
  2. The bit names I have written come from some debug information I got from QfG2 - type "suck blue frog" then Ctrl-W to save the cast list!