Difference between revisions of "0050"
(Improved description and added implementation section.) |
|||
(7 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
− | {{ | + | {{TocRight}} |
− | + | {{OpCode | |
− | + | | games = {{Icon|t}} | |
− | + | | command = GOSUB | |
− | + | | description = Gosub | |
− | + | | syntax1 = 0050: gosub [''label''] | |
− | + | | syntax2 = gosub [''label''] | |
− | + | | p1t = [''label''] | |
− | + | | p1d = 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 ''[[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''. | |
− | + | ||
+ | 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 | + | 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). |
− | < | + | {{Pre|class=sb-code|1= |
− | // | + | <span class="k">const</span> |
− | + | TEMP_VAR = <span class="nv">0@</span> | |
− | + | SWAT_1 = <span class="nv">1@</span> | |
− | + | SWAT_2 = <span class="nv">2@</span> | |
− | + | 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> | ||
− | + | <span class="k">var</span> | |
− | + | TEMP_VAR : integer | |
− | + | SWAT_1 : integer | |
− | + | SWAT_2 : integer | |
− | + | SWAT_3 : integer | |
− | + | SWAT_4 : integer | |
− | + | SWAT_5 : integer | |
− | + | <span class="k">end</span> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | end | ||
− | : | + | HAS_BEEN_SPAWNED = <span class="k">false</span> |
− | + | <span class="k">while</span> <span class="k">true</span> | |
− | + | <span class="k">wait</span> <span class="m">10</span> | |
− | return | + | <span class="k">if</span> |
− | </ | + | [[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 | + | 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. |
− | < | + | {{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> |
− | + | CAR_MODEL = <span class="nv">1@</span> | |
− | CAR_MODEL = | + | 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 < <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; | ||
− | + | // Move to the next frame in the call stack. | |
− | + | script->callStackPosition = script->callStackPosition + 1; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | jumpToOffset(dest, script); | |
− | |||
− | + | </syntaxhighlight> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | </ | ||
− | + | == Keywords == | |
+ | jump, goto, gosub, subroutine, label |
Latest revision as of 11:51, 24 August 2020
Contents |
- 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