AGI Specifications: Chapter 6 - Logic Resources

From AGI Wiki
Jump to navigationJump to search

Table of Contents


LOGIC Resources

Written by Lance Ewing

(Last updated: 27 January 1997)

 


6.1 Introduction

At the heart of Sierra's Adventure Game Interpreter is the LOGIC file. These files contain the code that makes up the game. Each room has a logic script that goes with it. This logic script governs what can take place in that room. Here is an example of what the programmer writes when a game is being created.

Example 0: KQ4. Room 7.


if (said( open, door))       [ must be close enough
{
   if (posn( ego, 86, 120, 106, 133))
   {
      if (!night)
      {
         if ( door.open)
         {
            print("The door is already open");
         }
         else
         {
            set( game.control);
            set.priority( ego, 11);
            start.update( door);
            end.of.loop( door, door.done);
         }
      }
      else
      {
         print("You can't -- it's locked");
      }
   }
   else
   {
      set( notCloseEnough);
   }
}

The logic script is not stored like this in the game files though. Instead each AGI command is stored as a bytecode and the resulting data doesn't look much like the above example at all. This document will try to explain each component of a logic script the way it is stored in the actual game data.

 


6.2 Command list and argument types

Written by Peter Kelly (Last updated: 3 March 1998).

This is a list of all AGI commands and their argument types. The function and name of some of these is not yet known. Check out AGIcommands.pas in section "Sample code" for a Delphi/Pascal unit containing this information.

 

Text commands

# Name No. arg1 arg2 arg3 arg4 arg5 arg6 arg7
01 equaln 2 var num          
02 equalv 2 var var          
03 lessn 2 var num          
04 lessv 2 var var          
05 greatern 2 var num          
06 greaterv 2 var var          
07 isset 1 flag            
08 issetv 1 var            
09 has 1 Iobj            
0A obj.in.room 2 Iobj var          
0B posn 5 Sobj num num num num  
0C controller 1 ctr            
0D have.key 0              
0E said - ...          
0F compare.strings 2 str str          
10 obj.in.box 5 Sobj num num num num    
11 center.posn 5 Sobj num num num num    
12 right.posn 5 Sobj num num num num    
 #  Name              No.  arg1 arg2 arg3 arg4 arg5 arg6 arg7
01 equaln              2   var  num
02 equalv              2   var  var
03 lessn               2   var  num
04 lessv               2   var  var
05 greatern            2   var  num
06 greaterv            2   var  var
07 isset               1   flag
08 issetv              1   var
09 has                 1   Iobj
0A obj.in.room         2   Iobj var
0B posn                5   Sobj num  num  num  num
0C controller          1   ctr
0D have.key            0
0E said                -  ...
0F compare.strings     2   str  str
10 obj.in.box          5   Sobj num  num  num  num
11 center.posn         5   Sobj num  num  num  num
12 right.posn          5   Sobj num  num  num  num

 

Action commands

# Name No. arg1 arg2 arg3 arg4 arg5 arg6 arg7
00 return 0              
01 increment 1 var            
02 decrement 1 var            
03 assignn 2 var num          
04 assignv 2 var var          
05 addn 2 var num          
06 addv 2 var var          
07 subn 2 var num          
08 subv 2 var var          
09 lindirectv 2 var var          
0A rindirect 2 var var          
0B lindirectn 2 var num          
0C set 1 flag            
0D reset 1 flag            
0E toggle 1 flag            
0F set.v 1 var            
10 reset.v 1 var            
11 toggle.v 1 var            
12 new.room 1 num            
13 new.room.v 1 var            
14 load.logics 1 num            
15 load.logics.v 1 var            
16 call 1 num            
17 call.v 1 var            
18 load.pic 1 var            
19 draw.pic 1 var            
1A show.pic 0              
1B discard.pic 1 var            
1C overlay.pic 1 var            
1D show.pri.screen 0              
1E load.view 1 num            
1F load.view.v 1 var            
20 discard.view 1 num            
21 animate.obj 1 Sobj            
22 unanimate.all 0              
23 draw 1 Sobj            
24 erase 1 Sobj            
25 position 3 Sobj num num        
26 position.v 3 Sobj var var        
27 get.posn 3 Sobj var var        
28 reposition 3 Sobj var var        
29 set.view 2 Sobj num          
02 set.view.v 2 Sobj var          
02 set.loop 2 Sobj num          
02 set.loop.v 2 Sobj var          
02 fix.loop 1 Sobj            
02 release.loop 1 Sobj            
02 set.cel 2 Sobj num          
1E set.cel.v 2 Sobj var          
1F last.cel 2 Sobj var          
20 current.cel 2 Sobj var          
21 current.loop 2 Sobj var          
22 current.view 2 Sobj var          
23 number.of.loops 2 Sobj var          
24 set.priority 2 Sobj num          
25 set.priority.v 2 Sobj var          
26 release.priority 1 Sobj            
27 get.priority 2 Sobj var          
03 stop.update 1 Sobj            
03 start.update 1 Sobj            
03 force.update 1 Sobj            
03 ignore.horizon 1 Sobj            
03 observe.horizon 1 Sobj            
03 set.horizon 1 num            
28 object.on.water 1 Sobj            
29 object.on.land 1 Sobj            
2A object.on.anything 1 Sobj          
2B ignore.objs 1 Sobj            
2C observe.objs 1 Sobj            
2D distance 3 Sobj Sobj          
2E stop.cycling 1 Sobj            
2F start.cycling 1 Sobj            
30 normal.cycle 1 Sobj            
31 end.of.loop 2 Sobj flag          
04 reverse.cycle 1 Sobj            
04 reverse.loop 2 Sobj flag          
04 cycle.time 2 Sobj var          
04 stop.motion 1 Sobj            
04 start.motion 1 Sobj            
04 step.size 2 Sobj var          
32 step.time 2 Sobj var          
33 move.obj 5 Sobj num          
34 move.obj.v 5 Sobj var          
35 follow.ego 3 Sobj num          
36 wander 1 Sobj            
37 normal.motion 1 Sobj            
38 set.dir 2 Sobj var          
39 get.dir 2 Sobj var          
3A ignore.blocks 1 Sobj            
3B observe.blocks 1 Sobj            
05 block 4 num num          
5B unblock 0              
05 get 1 Iobj            
05 get.v 1 var            
05 drop 1 Iobj            
05 put 2 Iobj            
3C put.v 2 var var          
3D get.room.v 2 var var          
3E load.sound 1 num            
3F sound 2 num flag        
64 stop.sound 0              
41 print 1 msg            
42 print.v 1 var            
43 display 3 num num          
44 display.v 3 var var          
45 clear.lines 3 num num          
6A text.screen 0              
6B graphics 0              
06 set.cursor.char 1 msg            
06 set.text.attribute 2 num num        
06 shake.screen 1 num            
06 configure.screen 3 num num          
70 status.line.on 0              
71 status.line.off 0              
48 set.string 2 strinmessa            
49 get.string 5 strinmessa            
4A word.to.string 2 word strin          
4B parse 1 strin            
4C get.num 2 messavar            
77 prevent.input 0              
78 accept.input 0              
4F set.key 3 num num          
07 add.to.pic 7 num num          
07 add.to.pic.v 7 var var          
7C status 0              
7D save.game 0              
7E restore.game 0              
7F init.disk 0              
80 restart.game 0              
51 show.obj 1 num            
52 random 3 num num          
83 program.control 0              
84 player.control 0              
55 obj.status.v 1 var            
56 quit 1 num            
87 show.mem 0              
88 pause 0              
89 echo.line 0              
8A cancel.line 0              
8B init.joy 0              
8C toggle.monitor 0              
8D version 0              
08 script.size 1 num            
08 set.game.id 1 messa            
5A log 1 messa            
91 set.scan.start 0              
92 reset.scan.start 0              
5D reposition.to 3 Sobj num          
5E reposition.to.v 3 Sobj var          
95 trace.on 0              
60 trace.info 3 num num          
61 print.at 4 messanum            
62 print.at.v 4 messavar            
63 discard.view.v 1 var            
09 clear.text.rect 5 num num          
9B set.upper.left 2 ??? ???          
09 set.menu 1 messa            
09 set.menu.item 2 messacntrl            
9E submit.menu 0              
09 enable.item 1 cntrl            
00 disable.item 1 cntrl            
A1 menu.input 0              
00 show.obj.v 1 var            
A3 open.dialogue 0              
A4 close.dialogue 0              
00 mul.n 2 var num          
00 mul.v 2 var var          
00 div.n 2 var num          
00 div.v 2 var var          
A9 close.window 0              
AA unknown170 1 ???            
AB unknown171 0              
AC unknown172 0              
AD unknown173 0              
AE unknown174 1 ???            
AF unknown175 1 ???            
B0 unknown176 0*            
B1 unknown177 1 ???            
B2 unknown178 0              
B3 unknown179 4 ??? ??? ??? ???      
B4 unknown180 2 ??? ???          
B5 unknown181 0              

* 1 arg for AGI version 3.002.086

 


6.3 LOGIC resource format

The header

The header of each logic script is seven bytes in length for games before 1988. After this date compression seems to have been introduced and the header was subsequently altered. This compression will be discussed at a later stage.

Byte  Meaning
----- -----------------------------------------------------------
 0-1  0x1234: signature for the start of a file in the VOL block
  2   Vol file number
 3-4  Length of the logic script
 5-6  Offset of logic code end and text begin
----- -----------------------------------------------------------

All text that can be printed to the screen from within a logic script is stored in an encrypted form at the end of the logic script.

Example 1: KQ1. Room 2.

 12 34    Signature
 01       vol.1
 5F 06    Length = 0x065F
 BA 02    Text start = 0x02BA

 

The LOGIC codes

The logic code section starts immediately after the header and continues until the start of the text section has been reached. There are three sets of codes used in a logic script. Most codes will have between one and seven arguments inclusive. This is discussed later on. The first set of codes is the AGI commands themselves and they have the range:

0x00 - 0xB5    AGI commands. (eg. animate.obj)

The value 181 (0xB5) may well be different for each game. Sierra will have added more commands to their set as they went along. The value above is for Manhunter 2 which is one of the last AGI games made. The second set of codes is as follows:

   FF      if
   FE      else (or goto)
   FD      not (!)
   FC      or (||)

At present these are the only high value codes encountered. The if and or codes are more like brackets, ie. the code will be at the start and the end of the section of codes that it refers to. The following example will illustrate this:

Example 2: KQ1. Room 2.

   FF      'if' conditions start.
   07      07 = isset
   05      05 = flag 5
   FF      'if' conditions close.

The above translates to:

if (isset(5))

which tests whether flag number 5 is set. The 0xFF effectively switches the interpreter into a condition checking mode which leads us to the next set of codes which I call the condition codes:

0x00 - 0x12    Condition codes.

The isset condition code was introduced in example 2 above. When the interpreter encounters a 0xFF it will then interpret the following code values as being in the condition code range until it encounters the next 0xFF which switches it back into normal AGI command mode. The two bytes immediately following the second 0xFF determine how many bytes this if statement lasts for before the if is ended. When the second 0xFF is encountered the interpreter, be it us or the machine, does three things:

  1. Reads in the following two bytes.
  2. Opens a bracket.
  3. Switches to AGI command mode.

Example 3: KQ1. Room 2.


FF 07 05 FF    if (isset(5))
84 00          {                        [ For 0x0084 bytes.
18 00              load.pic(0);
19 00              draw.pic(0);
1B 00              discard.pic(0);
...                ...
               }                        [ Closed. 0x0084 bytes counted.

Of course, the code inside the brackets is only executed if the if condition is met.

 

The else command and more on brackets

The else statement will always continue after an if bracket block. This next feature is important and has caused a number of hassles in the past. When an else statement follows an if, then the bracket distance given after the if statement will be three bytes longer (this is a consequence of the way the interpreter handles if and else codes which is discussed later).

Here's an example:


if (isset(231)) {                          FF 07 E7 FF 05 00
    printf("The door is already open.");   65 0F
}
else {                                     FE 11 00
    set(36);                               0C 24
    prevent.input();                       77
    start.update(5);                       3B 05
    assignn(152, 3);                       03 98 03
    cycle.time(5, 152);                    4C 05 98
    end.of.loop(5, 232);                   49 05 E8
    sound(70, 154);                        63 46 9A
}

Usually you would expect the bracket distance to be 0x0002 but in the above case it is clearly 0x0005 which illustrates the difference between a straight if statement and an if..else structure. The situation is the same for nested if..else structures.

The else statements themselves are a lot like if statements except that they're test condition is given after the 0xFE code but is instead the inverse of the condition given by the above if statement. Only the bracket distance is given after the 0xFE code and then the AGI command clock that the else statement encompasses.

 

Test conditions

Conditions can be one of the following types:

FF 07 05 FF                         One condition tested, ie. isset(5)
FF FD 07 05 FF                      One condition NOTed, ie. !isset(5)
FF 07 05 07 06 FF                   Multiple conditions, ANDed.
FF FC 07 05 07 06 FC FF             Multiple conditions ORed.
FF FC 07 06 07 06 FC FD 07 08 FF    Combination.

These conditions translate to:

if (isset(5))
if (!isset(5))
if (isset(5) && isset(6))
if (isset(5) || isset(6))
if ((isset(5) || isset(6)) && !isset(7))

If multiple boolean expressions are grouped together, then there respective values are ANDed together. If multiple boolean expressions are grouped together and then surrounded by a pair of 0xFC codes, then their values are ORed together.

The 0xFD code only applies to the following condition code whose boolean value it inverts.

 

Arguments

You may well be asking how the interpreter knows how many arguments each code has and what type of argument each argument is. This information is stored in a file called AGIDATA.OVL (MS-DOS version). Inside this file there is a table which contains four bytes for each AGI command and condition code. These four bytes are interpreted as follows:

Byte  Meaning
----- -----------------------------------------------------------
 0-1  Pointer to the machine code implementation contained in the
      file AGI
  2   Number of arguments
  3   The type of arguments
----- -----------------------------------------------------------

The type of arguments value is interpreted as follows:

Bit        7     6     5     4     3     2     1       0
command( arg1, arg2, arg3, arg4, arg5, arg6, arg7); (unknown)

If the bit is set, argument is interpreted as a variable; otherwise the argument is interpreted as a number. It is unknown what bit 0 does since no AGI command or AGI condition code has more than seven arguments.


Examples:

  • 0x80 Says that the commands first argument is a variable.
  • 0x60 Says that the second and third arguments are variable numbers.

 

The text section

The text section of a logic script contains all the strings that can be displayed by that logic script. These strings are encrypted by xoring every eleven bytes with the string "Avis Durgan".

Example 4: KQ1. Room 2.


if (said(look, alligators))
{
    print("The alligators are swimming in the moat.");
}

In the above example, the print statement is represented as:

65 08

The 0x08 is the number given to the string and corresponds to its position in the list of strings at the end of the logic script.


The format of the text section is as follows:

Byte  Meaning
----- -----------------------------------------------------------
  0   Number of messages
 1-2  Pointer to end of messages
 3-4  A list of offsets which point to each of the messages. The
      first offset naturally enough points to the start of the
      textual data
 ...
  ?   Start of the text data. From this point the messages are
      encrypted with Avis Durgan (in their unencrypted form, each
      message is separated by a 0x00 value)
----- -----------------------------------------------------------

 

Machine code implementation

The machine code for each AGI statement is found in the AGI file. This is the AGI interpreter itself. The data in the AGIDATA.OVL file is used to find the start of the implementation for an AGI statement. Below are a couple of examples:

Example 5: MH2. equaln.


;equaln   (eg.   if (work = 3)   )
0D71 AC            LODSB                       ;get variable number
0D72 32FF          XOR     BH,BH
0D74 8AD8          MOV     BL,AL
0D76 AC            LODSB                       ;get test number
0D77 3A870900      CMP     AL,[BX+0009]        ;test if var = number
0D7B B000          MOV     AL,00               ;return 0 if not equal
0D7D 7502          JNZ     0D81
0D7F FEC0          INC     AL                  ;return 1 if equal
0D81 C3            RET

Example 6: MH2. equalv.


;equalv  (eg.   if (work = maxwork)   )
0D82 AC            LODSB                       ;get first var number
0D83 32FF          XOR     BH,BH               ;clear bh
0D85 8AD8          MOV     BL,AL               ;BX = variable number
0D87 8AA70900      MOV     AH,[BX+0009]        ;get first var value
0D8B AC            LODSB                       ;get second var number
0D8C 8AD8          MOV     BL,AL
0D8E 32C0          XOR     AL,AL               ;return 0 if not equal
0D90 3AA70900      CMP     AH,[BX+0009]        ;compare variables
0D94 7502          JNZ     0D98
0D96 FEC0          INC     AL                  ;return 1 if equal
0D98 C3            RET

These two examples show the difference between how numbers and variables are dealt with. In the case of a variable, the variables number is used as an index into the table of variable values to get the value which is being tested. It appears that the variable table is at offset 0x0009 in the data segment.

 

How the interpreter handles LOGIC code

Now that you know a bit about what the actual code looks like once it has been converted into the LOGIC game data, we will now look at how these codes are interpreted by the interpreter. The following 8086 assembly language code is the actual code from the MS-DOS version of Manhunter: San Francisco. There are some calls to routines which aren't displayed. Take my word for it that they do what the comment says. For those of you who can't follow whats going on, I'll explain the interpretation in steps after the code block.


;Decoding a LOGIC file.
1E6C:2EF2 56            PUSH  SI
1E6C:2EF3 57            PUSH  DI
1E6C:2EF4 55            PUSH  BP
1E6C:2EF5 8BEC          MOV   BP,SP
1E6C:2EF7 83EC02        SUB   SP,+02
1E6C:2EFA 8B7608        MOV   SI,[BP+08]    ;SI -> start of LOGIC script.
1E6C:2EFD 8B7406        MOV   SI,[SI+06]    ;Skip first 6 bytes (header).
1E6C:2F00 AC            LODSB               ;Get next byte in LOGIC file.
1E6C:2F01 84C0          TEST  AL,AL         ;Is code a zero?
1E6C:2F03 7414          JZ    2F19          ;If so, jump to exit.
1E6C:2F05 3CFF          CMP   AL,FF         ;If an opening 'if' code is found
1E6C:2F07 7419          JZ    2F22          ;jump to 'if' handler.
1E6C:2F09 3CFE          CMP   AL,FE         ;If an 'else' has not been found
1E6C:2F0B 7505          JNZ   2F12          ;jump over else/branch.
1E6C:2F0D AD            LODSW               ;Get word (bracket distance)
1E6C:2F0E 03F0          ADD   SI,AX         ;Add to SI. Skip else code.
1E6C:2F10 EBEE          JMP   2F00          ;Go back to get next byte.
1E6C:2F12 E8A8D6        CALL  05BD          ;Execute AGI command.
1E6C:2F15 85F6          TEST  SI,SI         ;
1E6C:2F17 75E8          JNZ   2F01          ;Jump back to top.
1E6C:2F19 8BC6          MOV   AX,SI
1E6C:2F1B 83C402        ADD   SP,+02
1E6C:2F1E 5D            POP   BP
1E6C:2F1F 5F            POP   DI
1E6C:2F20 5E            POP   SI
1E6C:2F21 C3            RET

;Handler for 'if' statement.
;BH determines if its in an OR bracket (BH=1 means OR).
;BL determines the nature of the evalutation (BL=1 means NOT)
1E6C:2F22 33DB          XOR   BX,BX
1E6C:2F24 AC            LODSB               ;Get next byte
1E6C:2F25 3CFC          CMP   AL,FC         ;If less than 0xFC, then
1E6C:2F27 721C          JB    2F45          ;jump to normal processing.
1E6C:2F29 7508          JNZ   2F33          ;If greater, jump to 'if' close.
1E6C:2F2B 84FF          TEST  BH,BH         ;(Could BH be the evaluation reg?
1E6C:2F2D 7551          JNZ   2F80          ;or whether its the second FC?
1E6C:2F2F FEC7          INC   BH            ;
1E6C:2F31 EBF1          JMP   2F24          ;Go back to get next byte.

1E6C:2F33 3CFF          CMP   AL,FF         ;Is the code for an 'if' close?
1E6C:2F35 7505          JNZ   2F3C          ;If not, jump to 'not' test.
1E6C:2F37 83C602        ADD   SI,+02        ;
1E6C:2F3A EBC4          JMP   2F00          ;
1E6C:2F3C 3CFD          CMP   AL,FD         ;Is the code for a 'not'?
1E6C:2F3E 7505          JNZ   2F45          ;If not, jump to test command.
1E6C:2F40 80F301        XOR   BL,01         ;
1E6C:2F43 EBDF          JMP   2F24          ;Go back to get next byte.
1E6C:2F45 53            PUSH  BX            ;BX = test conditions??
1E6C:2F46 E8E8DD        CALL  0D31          ;Evaluate separate test command.
1E6C:2F49 5B            POP   BX            ;
1E6C:2F4A 32C3          XOR   AL,BL         ;Toggle the result for NOT.
1E6C:2F4C B300          MOV   BL,00         ;
1E6C:2F4E 7506          JNZ   2F56          ;If true jump to 2F56.
1E6C:2F50 84FF          TEST  BH,BH         ;If BH=0 then not in OR and
1E6C:2F52 742C          JZ    2F80          ;test is truely false.
1E6C:2F54 EBCE          JMP   2F24          ;Otherwise evaluate next OR.
1E6C:2F56 84FF          TEST  BH,BH         ;Are we in OR mode?
1E6C:2F58 7424          JZ    2F7E          ;If not, continue with testing.
1E6C:2F5A 32FF          XOR   BH,BH         ;If so, then we will skip the
1E6C:2F5C 32E4          XOR   AH,AH         ;rest of the tests in the OR
1E6C:2F5E AC            LODSB               ;bracket since the first is true.
1E6C:2F5F 3CFC          CMP   AL,FC         ;OR: Waiting for closing OR.
1E6C:2F61 741B          JZ    2F7E          ;If OR found, then continue testing.
1E6C:2F63 77F9          JA    2F5E          ;
1E6C:2F65 3C0E          CMP   AL,0E         ;If 'said' then goto said handler
1E6C:2F67 7507          JNZ   2F70          ;else goto normal handler
1E6C:2F69 AC            LODSB               ;Work out number of words in said
1E6C:2F6A D1E0          SHL   AX,1          ;and jump over them.
1E6C:2F6C 03F0          ADD   SI,AX         ;
1E6C:2F6E EBEE          JMP   2F5E          ;
1E6C:2F70 8BF8          MOV   DI,AX         ;Jumps over arguments.
1E6C:2F72 D1E7          SHL   DI,1          ;
1E6C:2F74 D1E7          SHL   DI,1          ;
1E6C:2F76 8A856407      MOV   AL,[DI+0764]  ;Load up the number of arguments
1E6C:2F7A 03F0          ADD   SI,AX         ;Add to the execution pointer
1E6C:2F7C EBE0          JMP   2F5E          ;
1E6C:2F7E EBA4          JMP   2F24

;Test is false.
;This routine basically skips over the rest of the codes until it finds the
;closing 0xFF at which point it will load the following two bytes and add
;them to the execution pointer SI.
1E6C:2F80 32FF          XOR   BH,BH
1E6C:2F82 32E4          XOR   AH,AH
1E6C:2F84 AC            LODSB               ;
1E6C:2F85 3CFF          CMP   AL,FF         ;If the closing 0XFF is found,
1E6C:2F87 741D          JZ    2FA6          ;jump 2FA6.
1E6C:2F89 3CFC          CMP   AL,FC         ;If greater than FC,
1E6C:2F8B 73F7          JNB   2F84          ;get next byte.
1E6C:2F8D 3C0E          CMP   AL,0E         ;If 'said' then goto said handler
1E6C:2F8F 7507          JNZ   2F98          ;else goto normal handler.
1E6C:2F91 AC            LODSB               ;Work out number of words in said
1E6C:2F92 D1E0          SHL   AX,1          ;and jump over them.
1E6C:2F94 03F0          ADD   SI,AX
1E6C:2F96 EBEC          JMP   2F84
1E6C:2F98 8AD8          MOV   BL,AL         ;Jump over arguments.
1E6C:2F9A D1E3          SHL   BX,1
1E6C:2F9C D1E3          SHL   BX,1
1E6C:2F9E 8A876407      MOV   AL,[BX+0764]  ;Load up the number of arguments.
1E6C:2FA2 03F0          ADD   SI,AX         ;Add to the execution pointer.
1E6C:2FA4 EBDE          JMP   2F84
1E6C:2FA6 AD            LODSW
1E6C:2FA7 03F0          ADD   SI,AX         ;Skip over if (includes 3 else byte
s)
1E6C:2FA9 E954FF        JMP   2F00

Situation 1. Okay, every LOGIC file starts in normal AGI command execution mode. In this routine, if the code is below 0xFC, then it is presumed to be an AGI command. It will then call the main command execution routine which will jump to the relevant routine for the specific command using the jump table stored in AGIDATA.OVL. The command is performed and it returns to the main execution routine where it loops back to the top and deals with the next code in the LOGIC file.

Situation 2. If the code is an 0xFF code, then if jumps to the if statement handler. In this routine is basically assesses whether the whole test condition evaluates to true or to false. It does this by treating each test separately and calling the relevant test command routines using the jump table in the AGIDATA.OVL file. Each test command routine will return a value in AL which says whether it is true or not. Depending on the NOTs and ORs, the whole expression is evaluated. If at any stage during the evaluation the routine decides that the expression will be false, it exits to another routine which skips the rest of the if statement and then adds the two byte word following the closing 0xFF code to the execution pointer. This usually has the affect of jumping over the if block of code. If the if handler gets to the ending 0xFF then it knows the expression is true simply because it hasn't exited out of the routine yet. At this stage it jumps over the two bytes following the closing 0xFF and then goes back to executing straight AGI commands.


Situation 3. If in the normal execution of AGI commands, the code 0xFE is encountered, a very simple action takes place. The two bytes which follow form a 16-bit twos complement value which is added to execution pointer. This is all it does. Previously we said that the 0xFE code stood for the else statement which is in actual fact correct for over 90% of the time, but the small number of other occurrences are best described as goto statements. If you're confused by this, the following example will probably explain things.

Example:


if (said( open, door)) {
    [ first block of AGI statements
}
else {
    [ second block of AGI statements
}

The above example is how the original coder would have written the AGI code. If we now look at the following example, it is not hard to see that it would achieve the same thing.


if (!said( open, door)) goto label1;
    [ first block of AGI statements
    goto label2:

label1:
    [ second block of AGI statements

label2:


This is exactly how all ifs and elses are implemented in the LOGIC code. The if statement is a conditional branch where the branch is taken if the condition is not met, while the else statement is a nonconditional jump. If a 0xFE code appears in the middle of some AGI code and wasn't actually originally coded as an else, then it was most likely a goto statement.

 

The said test command

The above assembly language code does raise a very important point. The said command can have a variable number of arguments. Its code is 0x0E, and the byte following this byte gives the number of two byte words that follow as parameters.

Examples:


if (said(marble))                          FF 0E 01 1E 01 FF
if (said( open, door))                     FF 0E 02 37 02 73 00 FF

In the above examples, the values 0x011E, 0x0237, and 0x0073 are just random word numbers that could stand for the words given.

 

Inner loops

At first I almost totally discarded the existence of loops in the AGI code because it seemed to me that execution of the LOGIC file continually looped. Loop code like "while", "do..while", and "for" statements wouldn't be needed because you could just use a variable to increment with each pass and an if statement to test the value of the variable and take action if it was withing the desired range.

Example:


if (greatern(30, 45) && lessn(30, 55)) {
    print("You're in the hot zone!);
    increment(30);
}

I have found evidence of this sort of thing taking place which means that they must loop over continuously. I don't know whether this is something that the interpreter does itself or whether it is part of the AGI code, e.g. at the end of one LOGIC file it calls another which then calls the first one again. With the existence of the conditional branching and unconditional branching nature of the if and else statement, it is easy to see that some of the structures such as "do..while" can infact be coded into LOGIC code.

Example:


FF FD 0D FF 03 00 FE F7 FF

do {
} while (!havekey);

The above translation is a simple one which is taken from SQ2. The value 0xFFF7 is the twos complement notation for -9 which is the exact branching value to take the execution back to the start of the if statement. If the above example had AGI code between the 0x00 and the 0xFE, then there would be code within the brackets of the "do..while" structure. I don't know whether the original AGI coders used these statements or used goto statements to achieve the same result.

 

New information on LOGIC interpretation

It has now come to light that logic.0 is run over and over again with each interpretation cycle. The other LOGICs that have been loaded will only get executed if logic.0 calls them directly or indirectly (i.e. LOGICs called from logic.0 can call other LOGICs and so on).

I have also become aware that code 0x00 can basically be thought of as the command return. If logic.0 calls another logic, the execution will return to LOGIC.0 when the 0x00 code is encountered.

It is also possible to set the entry point for a LOGIC file. The set.scan.start command makes the entry point of the LOGIC file being executed equal to the position of the command following set.scan.start. This means that the next time the LOGIC file is executed, execution begins at that point. The reset.scan.start command sets the entry point back to the start of the LOGIC.

 


6.4 Sample code

The following examples are available in the distribution package:

  • [[AGI:Examples/logic/logic.c">logic.c]] by Lance Ewing: loads LOGIC resources into LOGICFile structure
  • [[AGI:Examples/logic/logic.h">logic.h]] by Lance Ewing: header file for logic.c
  • [[AGI:Examples/files/AGIfiles.c">AGIfiles.c]] by Lance Ewing: routines to handle loading of resources
  • [[AGI:Examples/files/AGIfiles.h">AGIfiles.h]] by Lance Ewing: header file for AGIfiles.c
  • [[AGI:Examples/logic/AGIcommands.pas">AGIcommands.pas]] by Peter Kelly: Delphi/Pascal unit with a list of all commands and argument types

 

Table of Contents

< Previous: Chapter 5 - Resource FormatsNext: Chapter 7 - PICTURE Resources >