Difference between revisions of "0050"

From GTAMods Wiki
Jump to navigation Jump to search
(Improved description and added implementation section.)
 
(7 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{Icon|trilogy}} '''GOSUB'''
+
{{TocRight}}
<hr />
+
{{OpCode
<onlyinclude>{{#ifeq:{{{transcludesection|opcode}}}|opcode|
+
| games      = {{Icon|t}}
'''Description'''
+
| command    = GOSUB
: Gosub
+
| description = Gosub
'''Syntax'''
+
| syntax1    = 0050: gosub [''label'']
: 0050: gosub [''int'']
+
| syntax2    = gosub [''label'']
: gosub [''int'']
+
| p1t        = [''label'']
'''Parameter'''
+
| p1d        = The position in the script it will go to, usually identified by a [[label]]
: [''int'']
+
}}
:: The position in the script it will jump to; can also be a [[label]]
 
  
This opcode allows the code to jump to a line anywhere in the script. Between that line and a RETURN ([[0051]]), this secrion acts like a simple subroutine. The subroutine is independent of the [[thread]] that the GOSUB is located. When the code reaches a RETURN, it will jump back to where the GOSUB is located and continues from there. GOSUBs can help dramatically simply repetitive code, and can help make the code cleaner and easier to read for the user.
+
The ''gosub'' instruction is similar to the ''goto'' instruction, but it saves the current offset before the jump so that it can be returned to (with the ''[[0051|return]]'' instruction). The jumped-to offset is equivalent to the start of a subroutine. Execution of this subroutine ends when the ''return'' instruction is read, at which point execution continues from the instruction after the ''gosub''.
}}</onlyinclude>
+
 
 +
Subroutines allow for [[Wikipedia:Procedural programming|procedural programming]] in scripts, which can reduce code repetition, increase readability and reduce size. Subroutines may be called from within other subroutines (like a [[wikipedia:Matryoshka doll|Matryoshka doll]]), but nesting more than 6 subroutine calls will crash the game. Procedures are an important part of the structure of [[Create a mission|mission scripts]].
  
 
== Example ==
 
== Example ==
The following example, using Sanny Builder, will force a change to the colors of the specified vehicles.
+
The following example using Sanny Builder spawns five SWAT members to kill the player at the Washington Mall in Vice City after pressing [[00E1|button 13]] (CAMERA key).
<source lang="scm">
+
{{Pre|class=sb-code|1=
// set constants
+
<span class="k">const</span>
const
+
TEMP_VAR = <span class="nv">0@</span>
PRIM_COLOR = 0@
+
SWAT_1 = <span class="nv">1@</span>
SECD_COLOR = 1@
+
SWAT_2 = <span class="nv">2@</span>
PLAYER_CAR = 2@
+
SWAT_3 = <span class="nv">3@</span>
end
+
SWAT_4 = <span class="nv">4@</span>
 +
SWAT_5 = <span class="nv">5@</span>
 +
HAS_BEEN_SPAWNED = <span class="nv">6@</span>
 +
<span class="k">end</span>
  
while true
+
<span class="k">var</span>
    wait 10
+
TEMP_VAR : integer
    if
+
SWAT_1 : integer
        00DD:   actor $PLAYER_ACTOR driving_vehicle_type #LINERUN
+
SWAT_2 : integer
    then
+
SWAT_3 : integer
        PRIM_COLOR = 0
+
SWAT_4 : integer
        SECD_COLOR = 1
+
SWAT_5 : integer
        gosub @SET_COLOR
+
<span class="k">end</span>
    end
 
    if
 
        00DD:   actor $PLAYER_ACTOR driving_vehicle_type #PEREN
 
    then
 
        PRIM_COLOR = 10
 
        SECD_COLOR = 11
 
        gosub @SET_COLOR
 
    end
 
    if
 
        00DD:   actor $PLAYER_ACTOR driving_vehicle_type #SENTINEL
 
    then
 
        PRIM_COLOR = 20
 
        SECD_COLOR = 21
 
        gosub @SET_COLOR
 
    end
 
end
 
  
:SET_COLOR
+
HAS_BEEN_SPAWNED = <span class="k">false</span>
03C0: PLAYER_CAR = actor $PLAYER_ACTOR car
+
<span class="k">while</span> <span class="k">true</span>
0229: set_car PLAYER_CAR color_to PRIM_COLOR SECD_COLOR
+
    <span class="k">wait</span> <span class="m">10</span>
return
+
    <span class="k">if</span>
</source>
+
        [[00E1]]:   player <span class="m">0</span> pressed_button <span class="m">13</span>
 +
    <span class="k">then</span>
 +
        <span class="k">while</span> [[00E1]]:   player <span class="m">0</span> pressed_button <span class="m">13</span>
 +
            <span class="k">wait</span> <span class="m">0</span>
 +
        <span class="k">end</span>
 +
        <span class="k">if</span>
 +
            HAS_BEEN_SPAWNED == <span class="k">true</span>
 +
        <span class="k">then</span>
 +
            009B: destroy_actor_instantly SWAT_1
 +
            009B: destroy_actor_instantly SWAT_2
 +
            009B: destroy_actor_instantly SWAT_3
 +
            009B: destroy_actor_instantly SWAT_4
 +
            009B: destroy_actor_instantly SWAT_5
 +
            [[01C2]]: mark_actor_as_no_longer_needed SWAT_1
 +
            [[01C2]]: mark_actor_as_no_longer_needed SWAT_2
 +
            [[01C2]]: mark_actor_as_no_longer_needed SWAT_3
 +
            [[01C2]]: mark_actor_as_no_longer_needed SWAT_4
 +
            [[01C2]]: mark_actor_as_no_longer_needed SWAT_5
 +
        <span class="k">end</span>
 +
        HAS_BEEN_SPAWNED = <span class="k">true</span>
 +
        [[0247]]: request_model <span class="nt">#COLT45</span>
 +
        [[0247]]: request_model <span class="nt">#SWAT</span>
 +
        [[038B]]: load_requested_models
 +
        [[009A]]: SWAT_1 = create_actor_pedtype <span class="m">4</span> model <span class="nt">#SWAT</span> at <span class="m">18.9999</span> -<span class="m">928.7729</span> <span class="m">15.0727</span>
 +
        TEMP_VAR = SWAT_1
 +
        <span class="k">gosub</span> <span class="nl">@KILL_PLAYER</span>
 +
        [[009A]]: SWAT_2 = create_actor_pedtype <span class="m">4</span> model <span class="nt">#SWAT</span> at -<span class="m">15.6727</span> -<span class="m">929.0634</span> <span class="m">15.066</span>
 +
        TEMP_VAR = SWAT_2
 +
        <span class="k">gosub</span> <span class="nl">@KILL_PLAYER</span>
 +
        [[009A]]: SWAT_3 = create_actor_pedtype <span class="m">4</span> model <span class="nt">#SWAT</span> at -<span class="m">10.2667</span> -<span class="m">937.9695</span> <span class="m">9.4077</span>
 +
        TEMP_VAR = SWAT_3
 +
        <span class="k">gosub</span> <span class="nl">@KILL_PLAYER</span>
 +
        [[009A]]: SWAT_4 = create_actor_pedtype <span class="m">4</span> model <span class="nt">#SWAT</span> at -<span class="m">9.9934</span> -<span class="m">939.7717</span> <span class="m">9.4492</span>
 +
        TEMP_VAR = SWAT_4
 +
        <span class="k">gosub</span> <span class="nl">@KILL_PLAYER</span>
 +
        [[009A]]: SWAT_5 = create_actor_pedtype <span class="m">4</span> model <span class="nt">#SWAT</span> at -<span class="m">15.225</span> -<span class="m">949.0593</span> <span class="m">15.072</span>
 +
        TEMP_VAR = SWAT_5
 +
        <span class="k">gosub</span> <span class="nl">@KILL_PLAYER</span>
 +
        [[0249]]: release_model <span class="nt">#COLT45</span>
 +
        [[0249]]: release_model <span class="nt">#SWAT</span>
 +
    <span class="k">end</span>
 +
<span class="k">end</span>
 +
 
 +
<span class="nl">:KILL_PLAYER</span>
 +
[[0291]]: set_actor TEMP_VAR heed_threats <span class="m">1</span>
 +
[[0243]]: set_actor TEMP_VAR ped_stats_to <span class="m">16</span>
 +
[[011A]]: set_actor TEMP_VAR search_threat <span class="m">1</span>
 +
[[01B2]]: give_actor TEMP_VAR weapon <span class="m">17</span> ammo <span class="m">999</span>
 +
01CA: actor TEMP_VAR kill_player <span class="nv">$PLAYER_CHAR</span>
 +
<span class="k">return</span>
 +
}}
  
 
== Tricks ==
 
== Tricks ==
There are tricks one can use that Rockstar had never employed in their scripts.
+
There are tricks one can use with GOSUB that Rockstar had never employed in their scripts.
 
=== Array ===
 
=== Array ===
 
:''See also [[VC Arrays]]''
 
:''See also [[VC Arrays]]''
The following example functions similarly to the code above but is slightly slower due to the while loop. As you can see it is more complicated but can be the preferred method if the array becomes huge.
+
The following example for Vice City allows you to display different messages depending on the vehicle you are currently in. The example is limited to the first few vehicles in the [[CARS (IDE Section)|CARS section]] but can be expanded to include all available vehicles in the game.
<source lang="scm">
+
{{Pre|class=sb-code|1=
// set constants
+
<span class="c1">// set constants</span>
const
+
<span class="k">const</span>
PLAYER_CAR = 0@
+
PLAYER_CAR = <span class="nv">0@</span>
COLOR_LABEL = 1@
+
CAR_MODEL = <span class="nv">1@</span>
CAR_MODEL = 2@
+
PRINT_ARRAY = <span class="nv">2@</span>
end
+
PRINT_ARRAY_ELEMENT = <span class="nv">3@</span>
 +
<span class="k">end</span>
 +
 
 +
<span class="k">var</span>
 +
CAR_MODEL : integer
 +
PRINT_ARRAY : integer
 +
PRINT_ARRAY_ELEMENT : integer
 +
<span class="k">end</span>
 +
 
 +
<span class="c1">// determine correct size for each array element</span>
 +
PRINT_ARRAY_ELEMENT = <span class="nl">@PRINT_NAME_ARRAY</span>
 +
PRINT_ARRAY_ELEMENT *= -<span class="m">1</span>
 +
PRINT_ARRAY_ELEMENT += <span class="nl">@PRINT_NAME_ITEM</span>
 +
<span class="k">while</span> <span class="k">true</span>
 +
    <span class="k">wait</span> <span class="m">10</span>
 +
    <span class="k">if</span>
 +
        00E0:  player <span class="m">0</span> in_any_car
 +
    <span class="k">then</span>
 +
        03C1: PLAYER_CAR = player <span class="m">0</span> car_no_save
 +
        0441: CAR_MODEL = car PLAYER_CAR model
 +
        <span class="k">if</span>
 +
            CAR_MODEL &lt; <span class="nt">#VOODOO</span>  <span class="c1">// for this example, limit to first few vehicles in [[CARS (IDE Section)|CARS section]]</span>
 +
        <span class="k">then</span>
 +
            CAR_MODEL -= <span class="nt">#LANDSTAL</span>  <span class="c1">// let index of first vehicle be 0</span>
 +
            CAR_MODEL *= PRINT_ARRAY_ELEMENT  <span class="c1">// get correct array offset</span>
 +
            PRINT_ARRAY = <span class="nl">@PRINT_NAME_ARRAY</span>  <span class="c1">// get position of beginning of array</span>
 +
            PRINT_ARRAY += CAR_MODEL  <span class="c1">// add array offset to position of beginning of array</span>
 +
            <span class="k">gosub</span> PRINT_ARRAY  <span class="c1">// gosub to print text</span>
 +
        <span class="k">end</span>
 +
    <span class="k">end</span>
 +
<span class="k">end</span>
 +
 
 +
<span class="nl">:PRINT_NAME_ARRAY</span>
 +
[[00BC]]: text_highpriority <span class="s1">'LANDSTK'</span> <span class="m">10</span> ms <span class="m">1</span>
 +
<span class="k">return</span>
 +
<span class="nl">:PRINT_NAME_ITEM</span>
 +
[[00BC]]: text_highpriority <span class="s1">'IDAHO'</span> <span class="m">10</span> ms <span class="m">1</span>
 +
<span class="k">return</span>
 +
[[00BC]]: text_highpriority <span class="s1">'STINGER'</span> <span class="m">10</span> ms <span class="m">1</span>
 +
<span class="k">return</span>
 +
[[00BC]]: text_highpriority <span class="s1">'LINERUN'</span> <span class="m">10</span> ms <span class="m">1</span>
 +
<span class="k">return</span>
 +
[[00BC]]: text_highpriority <span class="s1">'PEREN'</span> <span class="m">10</span> ms <span class="m">1</span>
 +
<span class="k">return</span>
 +
[[00BC]]: text_highpriority <span class="s1">'SENTINL'</span> <span class="m">10</span> ms <span class="m">1</span>
 +
<span class="k">return</span>
 +
[[00BC]]: text_highpriority <span class="s1">'RIO'</span> <span class="m">10</span> ms <span class="m">1</span>
 +
<span class="k">return</span>
 +
[[00BC]]: text_highpriority <span class="s1">'FIRETRK'</span> <span class="m">10</span> ms <span class="m">1</span>
 +
<span class="k">return</span>
 +
[[00BC]]: text_highpriority <span class="s1">'TRASHM'</span> <span class="m">10</span> ms <span class="m">1</span>
 +
<span class="k">return</span>
 +
[[00BC]]: text_highpriority <span class="s1">'STRETCH'</span> <span class="m">10</span> ms <span class="m">1</span>
 +
<span class="k">return</span>
 +
[[00BC]]: text_highpriority <span class="s1">'MANANA'</span> <span class="m">10</span> ms <span class="m">1</span>
 +
<span class="k">return</span>
 +
[[00BC]]: text_highpriority <span class="s1">'INFERNS'</span> <span class="m">10</span> ms <span class="m">1</span>
 +
<span class="k">return</span>
 +
}}
 +
 
 +
== Implementation ==
 +
When the game executes a ''gosub'' instruction, it saves the current script offset to the call stack before changing the offset to the destination (specified by the argument). When the next ''return'' is executed, this process is reversed: the game retrieves the previous offset from the call stack and jumps to that, and the current stack frame is discarded.
 +
 
 +
<syntaxhighlight lang="cpp">
 +
/* Note: This is not real code and will not compile. */
 +
/* Based on decompilation of SA v2.0 */
 +
 
 +
void jumpToOffset(int offset, Script *script) {
 +
    // If the offset is negative, jump relative to the start of the script.
 +
    if (offset < 0) {
 +
        script->readPointer = script->startOffset - offset;
 +
        return;
 +
    }
 +
 
 +
    // Positive offsets are relative to the start of the main script.
 +
    &script->readPointer = &mainScriptData + offset;
 +
}
 +
 
 +
/* ... */
 +
 
 +
readScriptArguments(1, script);
 +
int dest = arguments[0];
 +
 
 +
// Set the call stack position to the current location so we can return later.
 +
(&script->callStack)[script->callStackPosition] = script->readPointer;
  
while true
+
// Move to the next frame in the call stack.
    wait 10
+
script->callStackPosition = script->callStackPosition + 1;
    COLOR_LABEL = @SET_COLOR  // gets the position of label SET_COLOR
 
    CAR_MODEL = #LINERUN  // so that this code works for III/VC/SA
 
    while #SENTINEL >= CAR_MODEL  // while CAR_MODEL is LINERUN, PEREN, or SENTINEL
 
        wait 0
 
        if
 
            00DD:  actor $PLAYER_ACTOR driving_vehicle_type CAR_MODEL
 
        then
 
            03C0: PLAYER_CAR = actor $PLAYER_ACTOR car
 
            gosub COLOR_LABEL  // gosub to set color
 
        end
 
        COLOR_LABEL += 11  // add 11 bytes to go to the next set color
 
        // 29 02 03 00 00 04 xx 04 xx 51 00, 11 bytes, is equivalent to
 
        // 0229: PLAYER_CAR xx xx 0051: return
 
        CAR_MODEL += 1 // increment car model ide number
 
  
    end
+
jumpToOffset(dest, script);
end
 
  
:SET_COLOR
+
</syntaxhighlight>
0229: set_car PLAYER_CAR color_to 0 1
 
return
 
0229: set_car PLAYER_CAR color_to 10 11
 
return                           
 
0229: set_car PLAYER_CAR color_to 20 21
 
return
 
</source>
 
  
[[Category:OpCodes]]
+
== Keywords ==
 +
jump, goto, gosub, subroutine, label

Latest revision as of 11:51, 24 August 2020

GTA III Vice City San Andreas GOSUB


Description
Gosub
Syntax
0050: gosub [label]
gosub [label]
Parameter
[label]
The position in the script it will go to, usually identified by a label

The gosub instruction is similar to the goto instruction, but it saves the current offset before the jump so that it can be returned to (with the return instruction). The jumped-to offset is equivalent to the start of a subroutine. Execution of this subroutine ends when the return instruction is read, at which point execution continues from the instruction after the gosub.

Subroutines allow for procedural programming in scripts, which can reduce code repetition, increase readability and reduce size. Subroutines may be called from within other subroutines (like a Matryoshka doll), but nesting more than 6 subroutine calls will crash the game. Procedures are an important part of the structure of mission scripts.

Example

The following example using Sanny Builder spawns five SWAT members to kill the player at the Washington Mall in Vice City after pressing button 13 (CAMERA key).

const
TEMP_VAR = 0@
SWAT_1 = 1@
SWAT_2 = 2@
SWAT_3 = 3@
SWAT_4 = 4@
SWAT_5 = 5@
HAS_BEEN_SPAWNED = 6@
end

var
TEMP_VAR : integer
SWAT_1 : integer
SWAT_2 : integer
SWAT_3 : integer
SWAT_4 : integer
SWAT_5 : integer
end

HAS_BEEN_SPAWNED = false
while true
    wait 10
    if
        00E1:   player 0 pressed_button 13
    then
        while 00E1:   player 0 pressed_button 13
            wait 0
        end
        if
            HAS_BEEN_SPAWNED == true
        then
            009B: destroy_actor_instantly SWAT_1
            009B: destroy_actor_instantly SWAT_2
            009B: destroy_actor_instantly SWAT_3
            009B: destroy_actor_instantly SWAT_4
            009B: destroy_actor_instantly SWAT_5
            01C2: mark_actor_as_no_longer_needed SWAT_1
            01C2: mark_actor_as_no_longer_needed SWAT_2
            01C2: mark_actor_as_no_longer_needed SWAT_3
            01C2: mark_actor_as_no_longer_needed SWAT_4
            01C2: mark_actor_as_no_longer_needed SWAT_5
        end
        HAS_BEEN_SPAWNED = true
        0247: request_model #COLT45
        0247: request_model #SWAT
        038B: load_requested_models
        009A: SWAT_1 = create_actor_pedtype 4 model #SWAT at 18.9999 -928.7729 15.0727
        TEMP_VAR = SWAT_1
        gosub @KILL_PLAYER
        009A: SWAT_2 = create_actor_pedtype 4 model #SWAT at -15.6727 -929.0634 15.066
        TEMP_VAR = SWAT_2
        gosub @KILL_PLAYER
        009A: SWAT_3 = create_actor_pedtype 4 model #SWAT at -10.2667 -937.9695 9.4077
        TEMP_VAR = SWAT_3
        gosub @KILL_PLAYER
        009A: SWAT_4 = create_actor_pedtype 4 model #SWAT at -9.9934 -939.7717 9.4492
        TEMP_VAR = SWAT_4
        gosub @KILL_PLAYER
        009A: SWAT_5 = create_actor_pedtype 4 model #SWAT at -15.225 -949.0593 15.072
        TEMP_VAR = SWAT_5
        gosub @KILL_PLAYER
        0249: release_model #COLT45
        0249: release_model #SWAT
    end
end

:KILL_PLAYER
0291: set_actor TEMP_VAR heed_threats 1
0243: set_actor TEMP_VAR ped_stats_to 16
011A: set_actor TEMP_VAR search_threat 1
01B2: give_actor TEMP_VAR weapon 17 ammo 999
01CA: actor TEMP_VAR kill_player $PLAYER_CHAR
return

Tricks

There are tricks one can use with GOSUB that Rockstar had never employed in their scripts.

Array

See also VC Arrays

The following example for Vice City allows you to display different messages depending on the vehicle you are currently in. The example is limited to the first few vehicles in the CARS section but can be expanded to include all available vehicles in the game.

// set constants
const
PLAYER_CAR = 0@
CAR_MODEL = 1@
PRINT_ARRAY = 2@
PRINT_ARRAY_ELEMENT = 3@
end

var
CAR_MODEL : integer
PRINT_ARRAY : integer
PRINT_ARRAY_ELEMENT : integer
end

// determine correct size for each array element
PRINT_ARRAY_ELEMENT = @PRINT_NAME_ARRAY
PRINT_ARRAY_ELEMENT *= -1
PRINT_ARRAY_ELEMENT += @PRINT_NAME_ITEM
while true
    wait 10
    if
        00E0:   player 0 in_any_car
    then
        03C1: PLAYER_CAR = player 0 car_no_save
        0441: CAR_MODEL = car PLAYER_CAR model
        if
            CAR_MODEL < #VOODOO  // for this example, limit to first few vehicles in CARS section
        then
            CAR_MODEL -= #LANDSTAL  // let index of first vehicle be 0
            CAR_MODEL *= PRINT_ARRAY_ELEMENT  // get correct array offset
            PRINT_ARRAY = @PRINT_NAME_ARRAY  // get position of beginning of array
            PRINT_ARRAY += CAR_MODEL  // add array offset to position of beginning of array
            gosub PRINT_ARRAY  // gosub to print text
        end
    end
end

:PRINT_NAME_ARRAY
00BC: text_highpriority 'LANDSTK' 10 ms 1
return
:PRINT_NAME_ITEM
00BC: text_highpriority 'IDAHO' 10 ms 1
return
00BC: text_highpriority 'STINGER' 10 ms 1
return
00BC: text_highpriority 'LINERUN' 10 ms 1
return
00BC: text_highpriority 'PEREN' 10 ms 1
return
00BC: text_highpriority 'SENTINL' 10 ms 1
return
00BC: text_highpriority 'RIO' 10 ms 1
return
00BC: text_highpriority 'FIRETRK' 10 ms 1
return
00BC: text_highpriority 'TRASHM' 10 ms 1
return
00BC: text_highpriority 'STRETCH' 10 ms 1
return
00BC: text_highpriority 'MANANA' 10 ms 1
return
00BC: text_highpriority 'INFERNS' 10 ms 1
return

Implementation

When the game executes a gosub instruction, it saves the current script offset to the call stack before changing the offset to the destination (specified by the argument). When the next return is executed, this process is reversed: the game retrieves the previous offset from the call stack and jumps to that, and the current stack frame is discarded.

/* Note: This is not real code and will not compile. */
/* Based on decompilation of SA v2.0 */

void jumpToOffset(int offset, Script *script) {
    // If the offset is negative, jump relative to the start of the script.
    if (offset < 0) {
        script->readPointer = script->startOffset - offset;
        return;
    }

    // Positive offsets are relative to the start of the main script.
    &script->readPointer = &mainScriptData + offset;
}

/* ... */

readScriptArguments(1, script);
int dest = arguments[0];

// Set the call stack position to the current location so we can return later.
(&script->callStack)[script->callStackPosition] = script->readPointer;

// Move to the next frame in the call stack.
script->callStackPosition = script->callStackPosition + 1;

jumpToOffset(dest, script);

Keywords

jump, goto, gosub, subroutine, label