Jump to content

Learn GTA3script in Y minutes


Link2012

Recommended Posts

This is a finished serie of tutorials based off the Learn X in Y Minutes website, which quickly guides you thought programming language.

And that's exactly what is going to happen here, I'll quickly guide you thought the GTA3script language, which was the language used by Rockstar North to script the missions and other behaviors of the III-era games. As such, basic programming knowledge is assumed, be it with the Sanny Builder Language, or with any other programming language.

You can get the full tutorial directories here, so you can compile them yourself. In fact, I highly recommend reading those series from the editor with proper highlighting. You can get the compiler and editor extension here.

 

The serie is divided in three parts, where the first is obligatory and the other two are independent from each other.

Tip: Do notice how the important parts are all near comment blocks, so you should watch for those.

Edited by Link2012
Link to comment
Share on other sites

The Basics

 

// This is a comment line.

/*
    This is a multiline comment
    /* It may be nested
        /* as many times */
       as you want.
    */
*/

// This script is only meant as an way of understanding the basics of the GTA3script language.
// Don't try running it. It makes no logical sense, and is likely to crash the game.

///////////////////////
///// 1. Variables ////
///////////////////////

// Global variables are declared like this:
VAR_INT my_global_int other_global_int
VAR_FLOAT my_global_float
VAR_TEXT_LABEL my_global_text8      // GTASA only
VAR_TEXT_LABEL16 my_global_text16   // GTASA only

// You may use arrays as well (GTASA only)
VAR_INT my_global_array[32]
my_global_array[0] = 11
my_global_array[3] = 22

// To declare local variables you use LVAR_ instead of VAR_
// Local variables must be declared inside scopes
{
    LVAR_INT my_local   
}

// Naturally, you may use the same name between scopes
{
    LVAR_FLOAT my_local
}

// Do note branching from scope to scope is dangerous, it uses the same variable space as the other scope.
{
    scope1:
    LVAR_INT test_var 
    test_var = 99
    GOSUB scope2
    GOTO scope1
}
{
    scope2:
    LVAR_INT not_another_var
    // the value of not_another_var is likely to be 99 as it uses the same space as test_var
    not_another_var = 3
    RETURN
    // the value of test_var after returning to scope1 is likely 3.
    // do not rely on this behaviour though.
}

//////////////////////////
///// 2. Commands ////////
//////////////////////////

// Mostly everything is a command on GTA3script, including the variable declaration seen above.
VAR_INT player scplayer

// Commands gives instructions to the script engine. Such as to create the player.
// The CREATE_PLAYER command takes the player id, the coordinates to create the player at, and outputs the player reference into a variable.
// (http://www.gtamodding.com/wiki/0053)
CREATE_PLAYER 0 9.5 5.0 -100.0 player

// Most commands takes character references, as such we need the players character.
// Do note how the output values of commands are always the last parameters.
GET_PLAYER_CHAR player scplayer

// A very important command is WAIT (http://www.gtamodding.com/wiki/0001).
// The game is single-threaded, and as such it has to stop updating the game to process scripts, including this one.
// To signal the game we're done with the script on this tick, we should use WAIT.
WAIT 0    // stops execution and returns on the next tick.
WAIT 1000 // stops execution for at least 1000ms.

// Arguments of commands are usually of the following types:
DO_FADE 100 0        // integers
DO_FADE 100 FADE_OUT // constants, in this particular case produces the same result as above.
SET_TIME_SCALE 1.0   // floats
GOSUB scope2         // labels
PRINT_HELP M_FAIL    // text labels, you may think of this as strings in other languages.

// A interesting remark: The characters '(', ')' and ',' are treated as spaces, so those lines are valid:
PRINT_BIG (M_FAIL) 1000 1
PRINT_BIG(M_FAIL, 1000, 1)
PRINT_BIG M_FAIL, (1000), 1

// Another remark: **Everything** is case-insensitive, so:
print_big m_FaIL 1000 1 // is exactly the same as above 

// SAVE_STRING_TO_DEBUG_FILE is a special command, the **only** one which accepts a string literal.
SAVE_STRING_TO_DEBUG_FILE "The quick brown fox jumps over the lazy dog"
// Nevertheless, the string will be compiled into a uppercase string.

// Since variables and text labels may collide, to give a variable to a text label argument
// you should prefix the variable name with an $, like so:
VAR_TEXT_LABEL m_fail
m_fail = M_PASSD
PRINT_BIG $m_fail 2000 1    // Prints M_PASSD text label
PRINT_BIG m_fail 2000 1     // Prints M_FAIL text label

//////////////////////////
///// 3. Constants ///////
//////////////////////////

// Since GTASA you may define your own string constants.
CONST_INT   int_max 2147483647
CONST_FLOAT math_pi 3.1415927

PRINT_BIG M_FAIL int_max 1      // same as PRINT_BIG M_FAIL 2147483647 1
my_global_float = math_pi * 2.0 // same as my_global_float = 3.1415927 * 2.0

//////////////////////////
///// 4. Control-Flow ////
//////////////////////////

VAR_INT i j k
GENERATE_RANDOM_INT i
GENERATE_RANDOM_INT j

// If statement
IF i = 100
    k = 0
ELSE
    k = 1
ENDIF

// Logical disjunction
IF j < -100
OR i >= -100
    ++k
ENDIF

// Logical conjunction
IF IS_CHAR_ON_FOOT scplayer
AND NOT IS_CHAR_DEAD scplayer   // Logical negation
AND NOT k = 2
    SET_CHAR_COORDINATES scplayer 0.0 0.0 0.0
ENDIF

// Something to be aware of as well is that all conditions checks will happen no matter the result of the previous ones.
// So, if you're doing a conjunction and the first condition failed, the second one will run anyway!
// This also applies to disjunction. If the first condition is true, the second condition will run anyway.

// You cannot mix disjunction and conjuntion! The following is invalid:
// IF i = 0
// OR i = 1
// AND i = 2
//     NOP
// ENDIF

// Switch statement (since GTASA)
SWITCH k
    CASE 1
        // This runs if k = 1
        FREEZE_CHAR_POSITION scplayer TRUE
        BREAK
    CASE 2
    CASE 3
        // This runs if k = 2 or k = 3
        SET_CHAR_HEALTH scplayer 0
        BREAK
    DEFAULT // Optional
        // This runs if k is none of the above
        ADD_ARMOUR_TO_CHAR scplayer 100
        BREAK
ENDSWITCH

// While loop
WHILE NOT IS_CHAR_DEAD scplayer
AND NOT IS_CHAR_IN_MODEL scplayer CHEETAH
    // It is very important to use WAIT on long-running loops, otherwise the will do nothing
    // but keep running your script, thus freezing the game.
    WAIT 0
ENDWHILE

// Repeat loop (since GTAVC)
REPEAT 5 i
    PRINT_WITH_NUMBER (NUMBER) i 1000 TRUE
    WAIT 1000
ENDREPEAT
// Prints 0, then 1, then 2, then 3, then 4.
// Do note the REPEAT runs at least once, so (REPEAT 0 i) would, in fact, print 0.

// Additionally, there is IFNOT and WHILENOT, which negates every condition.
// However, those are only supported in GTA3 and were never used.
IFNOT IS_CHAR_HEALTH_GREATER scplayer 30
AND NOT IS_CHAR_IN_ANY_CAR scplayer
    // This runs if the player health **IS NOT** greater than 30, and if the char **IS** in a vehicle.
ENDIF

//////////////////////////
///// 5. Timers       ////
//////////////////////////

// Within every scope there are two timer local variables: timera and timerb
// Those timers are in miliseconds and are updated on every game update tick.
{
    timera = 0
    timerb = 10000
    WHILE timera < 3000
        WAIT 0
    ENDWHILE
    // this made the script wait for around 3000 miliseconds (similarly to WAIT 3000).
    // also, TIMERB equals around 13000 at this point.
}

//////////////////////////
///// 6. Arithmetic   ////
//////////////////////////

//VAR_INT i j k -- already declared before
VAR_FLOAT x y z

// You may perform assignment/arithmetic like this:
x = 1.0
y = x + 3.5
i += 90
i = i - k
k *= k
j /= 2
my_global_text8  = TEXT // maximum of 7 characters
my_global_text16 = TEXT // maximum of 15 characters

// But you cannot do more than binary operations, so the following is invalid:
// x = x + y + z

// And, you cannot mix different types in a expression, the following is also invalid:
// x = j * 2
// x = 2       correct would be   x = 2.0

// In addition to the usual operations we know (addition, substraction, multiplication and division), we also have
// timed addition and timed substraction.
//
// They are essentially a shortcut to the delta time constant of the current frame.
// You may learn more about delta time in the animation present in this video: https://www.youtube.com/watch?v=a-w7w8x_moE
// 
// You should usually use this when moving objects in a loop, making the movement frame-rate independent.
//
x = y +@ 3.0    // x = y + 3.0 * delta_time
y +=@ 3.0       // y += 3.0 * delta_time
z -=@ y         // z -= y * delta_time

// You may perform a cast from integer to float, or float to integer, by using the =# operator.
x =# i
i =# x

// You may also use the increment/decrement operators.
// Do note there's no difference between pre and post incrementing.
++i     // same as i += 1
i++     // same as i += 1
--i     // same as i -= 1
i--     // same as i -= 1

// And, finally, you can use the comparision operators in conditional contexts:
IF i = 0
OR i < 0
OR i > 0
OR i >= 0
OR i <= 0
    NOP
ENDIF

///////////////////////////////
///// 7. Entities          ////
///////////////////////////////

VAR_INT car char object1 object2 empty_var sanity

// The language also checks the type of the entity assigned to a variable, in such a way
// that you cannot use the variable in a different context from which it was inteded to be used at.

CREATE_CAR (CHEETAH) 0.0 0.0 0.0 car
CREATE_CHAR (PEDTYPE_CIVMALE MALE01) 0.0 0.0 0.0 char

// IS_CHAR_DEAD car        // will not compile
// IS_CAR_ENGINE_ON char   // will not compile
// char = car              // will not compile
empty_var = car            // compiles fine, empty_var is of type CAR now

// As such, if you intend to use an certain variables as a entity type before creating it,
// you need to include some unreachable code that assigns the entity type to the variable.
//
// There are two ways to do this:

// The first, by using a impossible condition:
sanity = 0
IF sanity = 1
    CREATE_OBJECT (BRIEFCASE) 0.0 0.0 0.0 object1
ENDIF

// The second, by using a GOTO before the creation code.
GOTO entity_main
CREATE_OBJECT (DRUGS) 0.0 0.0 0.0 object2

entity_main:
GOSUB actually_create_objects

WHILE NOT IS_OBJECT_ON_SCREEN object1
AND NOT IS_OBJECT_ON_SCREEN object2
    WAIT 0
ENDWHILE

EXPLODE_CAR car
EXPLODE_CHAR_HEAD char
GOTO entity_cleanup

// this code is after the actual object usage, but runs before it.
actually_create_objects:
    CREATE_OBJECT (BRIEFCASE) 0.0 0.0 0.0 object1
    CREATE_OBJECT (DRUGS) 0.0 0.0 0.0 object2
RETURN

entity_cleanup:

// After you're done with your entities, you should probably delete or mark them as no longer needed.
//
// Deleting removes the entity from the world immediately, invalidating all references to it.
// Marking as no longer needed gives the control of the entity back to the game logic instead of the script engine.

MARK_OBJECT_AS_NO_LONGER_NEEDED object1
DELETE_OBJECT object2

DELETE_CHAR char
MARK_CAR_AS_NO_LONGER_NEEDED car

///////////////////////////////
///// 8. Farewell          ////
///////////////////////////////

// Oh well, I think we are done here.
// To kill your script, use the TERMINATE_THIS_SCRIPT command.
TERMINATE_THIS_SCRIPT
Edited by LINK/2012
Fixed broken formatting
Link to comment
Share on other sites

Multifiles

 

This tutorial is very long, so if you are not interested in the way multifiles scripting work (i.e. main.scm and script.img), simply skip this and go to custom scripts.

 

main.sc

 

// This is a tutorial about multifile scripts.
// It assume you know the basics of the GTA3script language from the previous tutorial.

// This multifile is for GTA SA since it contains more features than GTA III/VC, thus
// we can explain more things than it would be possible by using III/VC as base.

// The code in this tutorial should not really be used in a real world multifile!
// This time, the code makes logical sense and **actually works**. But the variables are misplaced, which is bad for custom scripts.
// Please check the stripped multifiles in the other thread (which?) for an actual base multifile source code.

//
// What in the world is a multifile after all?
//
// It's a big compiled script containing multiple source scripts. Such big script is used by the game to
// conduct missions, submissions and other behaviours, as we'll see later on.
//
// This is what we previously knew simply as 'the SCM file'.
//

VAR_INT player scplayer player_group
VAR_INT flag_player_on_mission

// Each script in a multifile should have a unique name.
// This unique name may be used later on in a TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME to kill the script.
SCRIPT_NAME main

// We'll learn more about this command later.
SET_DEATHARREST_STATE OFF

// The following commands should be used exactly as follow, with the first parameter as 0.
// The compiler will, during the compilation process, replace the zeros with the correct values found during the compilation process.
SET_TOTAL_NUMBER_OF_MISSIONS 0
SET_PROGRESS_TOTAL 0
SET_MISSION_RESPECT_TOTAL 0
// In III/VC there is additionally (SET_COLLECTABLE1_TOTAL 0).

// Creating the player is one of the fundamental tasks of a multifile.
CREATE_PLAYER 0 0.0 0.0 5.0 player

// A multifile must as well tell the game logic which variable to lookup when testing
// whether the player is in a mission.
flag_player_on_mission = 0
DECLARE_MISSION_FLAG flag_player_on_mission

// The multifile also needs to give the player some initial clothes.
// Tip: Try removing those lines and running the game :)
GIVE_PLAYER_CLOTHES_OUTSIDE_SHOP player VEST VEST 0
GIVE_PLAYER_CLOTHES_OUTSIDE_SHOP player PLAYER_FACE HEAD 1 
GIVE_PLAYER_CLOTHES_OUTSIDE_SHOP player JEANSDENIM JEANS 2 
GIVE_PLAYER_CLOTHES_OUTSIDE_SHOP player SNEAKERBINCBLK SNEAKER 3 
BUILD_PLAYER_MODEL player

// And respawning points.
// Tip: Try removing those lines and dying / getting arrested.
ADD_HOSPITAL_RESTART (0.0 0.0 5.0) (0.0) 0 
ADD_POLICE_RESTART (0.0 0.0 5.0) (0.0) 0

// The game starts faded out, so let's fade in.
// Tip: Try a new game, then a new game again, without the following lines.
SET_PLAYER_CONTROL player ON
DO_FADE 0 FADE_IN

// This is the absolutely minimal initialization a multifile should do. The game works now.
// The following variable inits are also useful:
GET_PLAYER_CHAR player scplayer
GET_PLAYER_GROUP player player_group

// Now let's learn the true meaning of **multi**.

// Rockstar usually puts much of the initialization in a mission script.
//
// You see, all the code in a multifile is loaded into memory, except for missions and streamed scripts.
// So, this is not only convenient, but it saves memory.
//
// Do note two mission scripts **cannot** run at the same time, that's why we should
// keep track of flag_player_on_mission.
LOAD_AND_LAUNCH_MISSION initial.sc
WAIT 0 // wait for the initialization script to run/finish

// Let's start some scripts!
//
// Those scripts will run cooperatively, when one WAITs, the other gets
// to run, as we explained already in the basics tutorial. 
START_NEW_SCRIPT psave
START_NEW_SCRIPT money 5000 8000 3.0 // you may send values to be received in the script label local variables

// Now let's start some subscripts.
//
// Despite the command name, this does not create missions per se.
// Just think of this as a version of START_NEW_SCRIPT but within a new file.
LAUNCH_MISSION import.sc
LAUNCH_MISSION other_scripts.sc

// III additionally have GOSUB_FILE which does the same as GOSUB, but including a file as well.
// GOSUB_FILE label file.sc

// Now for a very interesting script type introduced in GTASA: streamed scripts.
//
// Streamed scripts are scripts which are streamed into memory only when neeeded,
// and there can be multiple of those running cooperatively at the same time.
//
// This has the memory benefit of missions with the coding freedom of normal scripts.
//
// To use streamed scripts we should, first of all, assign streamed scripts to string constants:
REGISTER_STREAMED_SCRIPT CARMOD1 carmod1.sc
REGISTER_STREAMED_SCRIPT FOOD_VENDOR food_vendor.sc

// Streamed scripts can be streamed in manually or by a script trigger.
// We'll see how to stream it manually at the main_loop, but for now let's focus on triggers.
//
// The triggers may be registered using:
//   + REGISTER_SCRIPT_BRAIN_FOR_CODE_USE
//   + REGISTER_ATTRACTOR_SCRIPT_BRAIN_FOR_CODE_USE
//   + ALLOCATE_STREAMED_SCRIPT_TO_RANDOM_PED
//   + ALLOCATE_STREAMED_SCRIPT_TO_OBJECT
//
// Each of those triggers are very delicate and requires a tutorial by themselves, which I'm
// not up to write, and gets out of the scope of this tutorial, which is quick learning.
//
// Instead, let's pick ALLOCATE_STREAMED_SCRIPT_TO_OBJECT and play with it.
//
ALLOCATE_STREAMED_SCRIPT_TO_OBJECT FOOD_VENDOR CHILLIDOGCART 100 70.0 -1
ALLOCATE_STREAMED_SCRIPT_TO_OBJECT FOOD_VENDOR NOODLECART_PROP 100 70.0 -1
// So, we just told the script engine we want to run FOOD_VENDOR once we're at a 70.0 radius
// of the the CHILLIDOGCART or NOODLECART_PROP objects.

// Unfortunately, objects spawned by script (CREATE_OBJECT) do not trigger streamed scripts, so we'll
// make a teleporter to that have a cart spawned by level files.
// Tip: Look away from the carts if the vendor character is not spawned.
START_NEW_SCRIPT cart_teleport

main_loop:
WAIT 250

// This is basically how you'd stream a streamed script manually:
IF IS_GARAGE_OPEN (BODLAWN)
OR IS_GARAGE_OPEN (MODLAST)
OR IS_GARAGE_OPEN (MDSSFSE)
OR IS_GARAGE_OPEN (MDS1SFS)
OR IS_GARAGE_OPEN (VECMOD)
    GET_NUMBER_OF_INSTANCES_OF_STREAMED_SCRIPT CARMOD1 num_carmod_instances
    IF num_carmod_instances = 0
        STREAM_SCRIPT CARMOD1
        IF HAS_STREAMED_SCRIPT_LOADED CARMOD1
            START_NEW_STREAMED_SCRIPT CARMOD1   // Tip: You can send values just like START_NEW_SCRIPT
        ENDIF
    ENDIF
ELSE
    MARK_STREAMED_SCRIPT_AS_NO_LONGER_NEEDED CARMOD1
ENDIF

GOTO main_loop

// Let's declare a few more variables.
// Usually this should be at the very top of the script, but to avoid poluting the 'minimal multifile' sample,
// we'll declare them here.
VAR_INT mistery_blip flag_mistery1_passed flag_mistery2_passed 
VAR_FLOAT mistery_xpos mistery_ypos mistery_zpos
VAR_INT num_carmod_instances dogcart1

// This is a simple script that gives player money when he's in a small sphere near the spawning point.
// Pretty straightforward, no comments needed.
{
    money:
    SCRIPT_NAME money

    // Remember the values we passed to START_NEW_SCRIPT?
    LVAR_INT amount wait_time // 5000 and 8000 will be received here.
    LVAR_FLOAT unused_but_example // 3.0 will be received here

    money_loop:
    WAIT 0
    IF IS_PLAYER_PLAYING player
        IF IS_CHAR_IN_AREA_ON_FOOT_3D scplayer (-8.0, 0.0 4.0) (-7.0 2.0 0.0) TRUE // Tip: Try FALSE
            ADD_SCORE player amount
            PLAY_MISSION_PASSED_TUNE 1
            WAIT wait_time
        ENDIF
    ENDIF
    GOTO money_loop
}


// This is a simple save game script.
// After spawning, turn around and look for a save pickup and play with it.
// Again, no comments needed.
{
    psave:
    SCRIPT_NAME psave

    LVAR_INT save_pickup pickup_created
    pickup_created = 0

    // Remeber the entities tutorial? Try compiling without those lines:
    IF pickup_created = 1
        CREATE_PICKUP 0 0 .0 .0 .0 save_pickup
    ENDIF

    psave_loop:
    WAIT 0
    IF flag_player_on_mission = 0
        GOSUB psave_create_pickup
        IF IS_PLAYER_PLAYING player
        AND HAS_PICKUP_BEEN_COLLECTED save_pickup
            GOSUB psave_do_save
            GOSUB psave_destroy_pickup
            GOSUB psave_create_pickup
            GOSUB psave_respawn_player
        ENDIF
    ELSE
        GOSUB psave_destroy_pickup
        WAIT 1000
    ENDIF
    GOTO psave_loop

    psave_do_save:
        SET_PLAYER_CONTROL player OFF
        ACTIVATE_SAVE_MENU
        WHILE NOT HAS_SAVE_GAME_FINISHED
            WAIT 0
        ENDWHILE
        SET_FADING_COLOUR 0 0 0
        DO_FADE 1000 FADE_OUT
        SET_PLAYER_CONTROL player OFF
    RETURN

    psave_respawn_player:
        IF IS_PLAYER_PLAYING player
            CLEAR_AREA (15.0 0.0 4.0) (1.0) TRUE
            SET_CHAR_COORDINATES scplayer (15.0 0.0 4.0)
        ENDIF
        WAIT 0
        DO_FADE 1000 FADE_IN
        RESTORE_CAMERA_JUMPCUT
        SET_CAMERA_BEHIND_PLAYER
        WAIT 500
        IF IS_PLAYER_PLAYING player
            SET_PLAYER_CONTROL player ON
        ENDIF
    RETURN

    psave_create_pickup:
        IF pickup_created = 0
            pickup_created = 1
            CREATE_PICKUP PICKUPSAVE PICKUP_ONCE 10.0 0.0 3.0 save_pickup
        ENDIF
    RETURN

    psave_destroy_pickup:
        IF pickup_created = 1
            pickup_created = 0
            REMOVE_PICKUP save_pickup
        ENDIF
    RETURN
}

{
    mistery1_loop:
    // Well, yeah, we may refrain from using SCRIPT_NAME, but in such case the script
    // cannot be terminated by another script.
    WAIT 250

    IF flag_mistery1_passed = 1
        TERMINATE_THIS_SCRIPT
    ENDIF

    IF IS_PLAYER_PLAYING player
        IF flag_player_on_mission = 0
            IF LOCATE_CHAR_ON_FOOT_3D scplayer (mistery_xpos mistery_ypos mistery_zpos) (1.2 1.2 2.0) FALSE
                IF CAN_PLAYER_START_MISSION player
                    flag_player_on_mission = 1
                    PRINT_BIG (SMOKE_4) 1000 2
                    GOSUB fade_for_mission
                    LOAD_AND_LAUNCH_MISSION mistery1.sc
                ENDIF
            ENDIF
        ENDIF
    ENDIF
    GOTO mistery1_loop
}

{
    mistery2_loop:
    // So, since San Andreas they started grouping mission loops for each contact in a single loop.
    // So we would have only mistery_loop. But let's build up on START_NEW_SCRIPT examples.
    WAIT 250

    IF flag_mistery2_passed = 1
        TERMINATE_THIS_SCRIPT
    ENDIF

    IF IS_PLAYER_PLAYING player
        IF flag_player_on_mission = 0
            IF LOCATE_CHAR_ON_FOOT_3D scplayer (mistery_xpos mistery_ypos mistery_zpos) (1.2 1.2 2.0) FALSE
                IF CAN_PLAYER_START_MISSION player
                    flag_player_on_mission = 1
                    PRINT_BIG (BCES4_2) 1000 2
                    GOSUB fade_for_mission
                    LOAD_AND_LAUNCH_MISSION mistery2.sc
                ENDIF
            ENDIF
        ENDIF
    ENDIF
    GOTO mistery2_loop
}

fade_for_mission:
    IF IS_PLAYER_PLAYING player
        SET_PLAYER_CONTROL player OFF // Tip: turning player control off makes the player safe
                                      // (i.e. cannot die or get arrested)
        SET_FADING_COLOUR 0 0 0
        DO_FADE 500 FADE_OUT
        WHILE GET_FADING_STATUS
            WAIT 0
        ENDWHILE
        CLEAR_PRINTS
        CLEAR_HELP
        CLEAR_CHAR_TASKS scplayer
    ENDIF
RETURN

main/import.sc

 

// main/import.sc
//--------------------
// Subscript files must be specialized with the MISSION_START / MISSION_END directives.
// But once again, remember, this is not a mission per se!
MISSION_START

SCRIPT_NAME import

// We'll learn more about this command later.
// But it's most likely you want to turn off this on subscripts.
SET_DEATHARREST_STATE OFF

// I'll stop here because whatever you would use START_NEW_SCRIPT for, you can also use subscript for.
// Here are some of the uses of subscripts in the trilogy: Unique Stunt Jumps, Rampages, Car Import Submissions...

MISSION_END     // Terminates the script.

main/other_scripts.sc

 

// main/other_scripts.sc
MISSION_START
SET_DEATHARREST_STATE OFF
MISSION_END

// Another use for subscripts is including labels in another file,
// like labels for GOSUBs or for START_NEW_SCRIPTs.

{
some_random_script:
TERMINATE_THIS_SCRIPT
// you can use START_NEW_SCRIPT some_random_script from anywhere now.
}

some_random_gosub:
RETURN
// you can use GOSUB some_random_gosub from anywhere now.

{
    cart_teleport:
    WAIT 0
    IF IS_PLAYER_PLAYING player
        IF IS_CHAR_IN_AREA_ON_FOOT_3D scplayer (-15.0, 5.0 4.0) (-14.0 2.0 0.0) TRUE
            REQUEST_COLLISION -2157.6257 -425.5779
            LOAD_ALL_MODELS_NOW
            SET_CHAR_COORDINATES scplayer -2157.6257 -425.5779 -100.0
            ADD_SCORE player 100 // some money to buy food
        ENDIF
    ENDIF
    GOTO cart_teleport
}

main/initial.sc

 

// main/initial.sc
//---------------------
// Initialization mission, as explained already in main.sc!
MISSION_START
SCRIPT_NAME initial

flag_mistery1_passed = 0
flag_mistery2_passed = 0
mistery_xpos = 0.0
mistery_ypos = 4.0
mistery_zpos = 4.0

START_NEW_SCRIPT mistery1_loop
ADD_SPRITE_BLIP_FOR_CONTACT_POINT (mistery_xpos mistery_ypos mistery_zpos) RADAR_SPRITE_MYSTERY mistery_blip

MISSION_END

main/mistery1.sc

 

// main/mistery1.sc
// 
// This is a mission script. It has the following basic structure:
MISSION_START
GOSUB mission_start_mistery1
IF HAS_DEATHARREST_BEEN_EXECUTED 
    GOSUB mission_mistery1_failed
ENDIF
GOSUB mission_cleanup_mistery1
MISSION_END

//
// Here, in mission scripts, is where the the deatharrest state shines (remember SET_DEATHARREST_STATE?).
// The deatharrest state is the scripting engine constantly checking if the player is dead or arrested,
// if so, the engine will make the script return from all its GOSUBs immediately!
//
// Do you see the relationship in the basic mission structure? Nice, eh?
//
// Another interesting features of mission scripts are the mission cleanup list and the cleanup gosub.
// The cleanup gosub serves as a disposal method for all the resources used by the mission.
// The cleanup list is a script engine internal list that keeps track of some, but not all, of the entities created
// by the mission, in such a way that once the mission is ended, the control of those entities are given back to the
// game logic (just like by calling MARK_THING_AS_NO_LONGER_NEEDED). The most notable entities tracked by the cleanup
// list are chars and cars.
//
// It reminds a bit of RAII. Request your stuff on mission startup and cleanup them on the cleanup sub.
//

// Let's write a simple 'GO FROM POINT A TO POINT B' mission.

{

// Variables for mission
// For III/VC you'll probably declare globals, but SA has an abundant amount of local variables in a mission.
LVAR_INT mistery1_delivery_car mistery1_delivery_blip
LVAR_FLOAT x y z

mission_start_mistery1:

flag_player_on_mission = 1
REGISTER_MISSION_GIVEN
WAIT 0 // let other scripts know a mission has been started

SCRIPT_NAME mister1

LOAD_MISSION_TEXT (BCESAR4)
REQUEST_MODEL ZR350
LOAD_ALL_MODELS_NOW // Tip: This freezes the game, use only while faded out (which we are).
                    // Otherwise use HAS_MODEL_LOADED in a while loop.

GET_CHAR_COORDINATES scplayer (x y z)
CREATE_CAR ZR350 (x y z) mistery1_delivery_car
WARP_CHAR_INTO_CAR scplayer mistery1_delivery_car

ADD_BLIP_FOR_COORD (40.0 0.0 4.0) mistery1_delivery_blip 

DO_FADE 1000 FADE_IN
WHILE GET_FADING_STATUS
    WAIT 0
ENDWHILE

PRINT_NOW (BCE5W10) 5000 1
SET_PLAYER_CONTROL player ON

WHILE NOT LOCATE_CHAR_ANY_MEANS_3D scplayer (40.0 0.0 4.0) (5.0 5.0 3.0) TRUE
    WAIT 0
    IF IS_CAR_DEAD mistery1_delivery_car
        PRINT_NOW (BCE5@24) 5000 1
        GOTO mission_mistery1_failed
    ENDIF
ENDWHILE
GOTO mission_mistery1_passed

mission_mistery1_failed:
PRINT_BIG (M_FAIL) 5000 1
RETURN

mission_mistery1_passed:
PRINT_WITH_NUMBER_BIG (M_PASSS 25000) 5000 1 // MISSION PASSED!~n~~w~$~1~~n~~w~RESPECT +
ADD_SCORE player (25000)
                                            // Remember the counters at the top of main.sc?
REGISTER_MISSION_PASSED	(SMOKE_4)           // This builds up to the SET_TOTAL_NUMBER_OF_MISSIONS counter
PLAYER_MADE_PROGRESS 1                      // This builds up to the SET_PROGRESS_TOTAL counter. 
AWARD_PLAYER_MISSION_RESPECT 3              // This builds up to the SET_MISSION_RESPECT_TOTAL counter.
CLEAR_WANTED_LEVEL player
PLAY_MISSION_PASSED_TUNE 1
flag_mistery1_passed = 1
START_NEW_SCRIPT mistery2_loop
RETURN

mission_cleanup_mistery1:
MARK_MODEL_AS_NO_LONGER_NEEDED ZR350
//MARK_CAR_AS_NO_LONGER_NEEDED mistery1_delivery_car  // Not necessary, mission cleanup list does that for us!
REMOVE_BLIP mistery1_delivery_blip
flag_player_on_mission = 0
MISSION_HAS_FINISHED // runs the mission cleanup list
RETURN

}

// IMPORTANT NOTICE:
// Don't START_NEW_SCRIPT into a label inside a mission script.
// Remember, the mission scripts do not persist in memory!

main/mistery2.sc

 

// main/mistery2.sc
MISSION_START
GOSUB mission_start_mistery2
IF HAS_DEATHARREST_BEEN_EXECUTED 
    GOSUB mission_mistery2_failed
ENDIF
GOSUB mission_cleanup_mistery2
MISSION_END

// One last mission, let's see a feature introduced in GTASA: SKIP_CUTSCENE.

{

mission_start_mistery2:

flag_player_on_mission = 1
REGISTER_MISSION_GIVEN
WAIT 0

SCRIPT_NAME mister2

SWITCH_WIDESCREEN TRUE

// Everthing enclosed in a SKIP_CUTSCENE_START...SKIP_CUTSCENE_END may be skipped if the player
// presses the skip key (usually ENTER). This makes it easy to work with cutscenes.
SKIP_CUTSCENE_START
    SET_FIXED_CAMERA_POSITION (0.0 -10.0 20.0) (0.0 0.0 0.0)
    POINT_CAMERA_AT_CHAR scplayer FIXED JUMP_CUT

    DO_FADE 1000 FADE_IN
    WHILE GET_FADING_STATUS
        WAIT 0
    ENDWHILE

    PRINT_NOW (GYMHELP) 10000 1
    WAIT 5000

    SET_FIXED_CAMERA_POSITION (10.0 0.0 20.0) (0.0 0.0 0.0)
    POINT_CAMERA_AT_CHAR scplayer FIXED JUMP_CUT
    PRINT_NOW (AMUHLP) 10000 1
    WAIT 5000

    SET_FIXED_CAMERA_POSITION (0.0 20.0 20.0) (0.0 0.0 0.0)
    POINT_CAMERA_AT_CHAR scplayer FIXED JUMP_CUT
    PRINT_NOW (BURG_10) 10000 1
    WAIT 5000
SKIP_CUTSCENE_END

CLEAR_PRINTS
DO_FADE 500 FADE_OUT
WHILE GET_FADING_STATUS
    WAIT 0
ENDWHILE

SWITCH_WIDESCREEN FALSE
RESTORE_CAMERA_JUMPCUT

DO_FADE 500 FADE_IN
WHILE GET_FADING_STATUS
    WAIT 0
ENDWHILE

SET_PLAYER_CONTROL player ON
GOTO mission_mistery2_passed

mission_mistery2_failed:
PRINT_BIG (M_FAIL) 5000 1
RETURN

mission_mistery2_passed:
PRINT_WITH_NUMBER_BIG (M_PASSS 100000) 5000 1
ADD_SCORE player (100000)
REGISTER_MISSION_PASSED	(BCES4_2)
PLAYER_MADE_PROGRESS 1
AWARD_PLAYER_MISSION_RESPECT 10
CLEAR_WANTED_LEVEL player
PLAY_MISSION_PASSED_TUNE 2
REMOVE_BLIP mistery_blip
flag_mistery2_passed = 1
RETURN

mission_cleanup_mistery2:
flag_player_on_mission = 0
MISSION_HAS_FINISHED
RETURN

}

main/streams/carmod1.sc

 

// main/stream/carmod1.sc
//----------------------------
// Streamed scripts are enclosed by the SCRIPT_START...SCRIPT_END directive.
// A scope must appear right after it as well.
SCRIPT_START
{
// Now we would code the tunning garage script, which is very complicated,
// so let's just imagine it's here :)
}
SCRIPT_END

main/streams/food_vendor.sc

 

// main/stream/food_vendor.sc
//----------------------------------
// For the good of the nation the food vendor script from the original multifile has been decompiled
// and improved a bit, there's nothing very special to learn here except for the fact the triggerer
// is passed to the first local variable of the streamed script.
//----------------------------------
SCRIPT_START
{

LVAR_INT prop_cart      // The object which triggered this script is passed to the first variable.
LVAR_INT script_state
LVAR_INT vendor
LVAR_INT next_state_time

LVAR_INT game_timer temp_health
LVAR_FLOAT temp_x temp_y temp_z temp_heading
LVAR_FLOAT area_lower_x area_lower_y area_lower_z
LVAR_FLOAT area_upper_x area_upper_y area_upper_z

SCRIPT_NAME fodvend

script_state = 0
IF script_state = 1
    CREATE_OBJECT_NO_OFFSET ICESCART_PROP 0.0 0.0 0.0 prop_cart
ENDIF

food_vendor_loop:
WAIT 0
GET_GAME_TIMER game_timer
IF DOES_OBJECT_EXIST prop_cart
    IF IS_OBJECT_WITHIN_BRAIN_ACTIVATION_RANGE prop_cart
        IF IS_PLAYER_PLAYING player
            IF NOT HAS_OBJECT_BEEN_UPROOTED prop_cart

                IF script_state = 0 // still needs to create vendor
                    GET_OFFSET_FROM_OBJECT_IN_WORLD_COORDS prop_cart -1.0 0.0 -1.0 temp_x temp_y temp_z
                    area_lower_x = temp_x - 0.5
                    area_lower_y = temp_y - 0.5
                    area_lower_z = temp_z
                    area_upper_x = temp_x + 0.5
                    area_upper_y = temp_y + 0.5
                    area_upper_z = temp_z + 2.0
                    IF NOT IS_AREA_OCCUPIED (area_lower_x area_lower_y area_lower_z) (area_upper_x area_upper_y area_upper_z) FALSE FALSE TRUE FALSE FALSE
                        IF NOT IS_POINT_ON_SCREEN temp_x temp_y temp_z 1.0
                            REQUEST_MODEL BMOCHIL
                            IF HAS_MODEL_LOADED BMOCHIL
                                CREATE_CHAR (PEDTYPE_CIVMALE BMOCHIL) (temp_x temp_y temp_z) vendor
                                GET_OFFSET_FROM_OBJECT_IN_WORLD_COORDS prop_cart (1.0 0.0 0.0) (area_lower_x area_lower_y area_lower_z)
                                area_lower_x = area_lower_x - temp_x
                                area_lower_y = area_lower_y - temp_y
                                GET_HEADING_FROM_VECTOR_2D (area_lower_x area_lower_y) temp_heading
                                SET_CHAR_HEADING vendor temp_heading
                                ++script_state
                            ENDIF
                        ENDIF
                    ELSE
                        GOTO terminate_vendor_script
                    ENDIF
                ENDIF

                IF script_state = 1 // may buy food
                    IF LOCATE_CHAR_ON_FOOT_OBJECT_2D scplayer prop_cart 8.0 8.0 FALSE
                        REQUEST_ANIMATION VENDING
                        IF HAS_ANIMATION_LOADED VENDING
                            IF IS_SCORE_GREATER player 0
                                GET_OFFSET_FROM_OBJECT_IN_WORLD_COORDS prop_cart (1.0 0.0 0.0) (temp_x temp_y temp_z)
                                IF LOCATE_CHAR_ON_FOOT_3D scplayer (temp_x temp_y temp_z) (0.6 0.6 1.0) TRUE
                                    GET_CHAR_HEALTH scplayer temp_health
                                    temp_health += 50
                                    SET_CHAR_HEALTH scplayer temp_health
                                    TASK_PLAY_ANIM_SECONDARY scplayer VEND_EAT1_P VENDING 4.0 FALSE FALSE FALSE FALSE -1
                                    INCREMENT_INT_STAT 245 10
                                    next_state_time = game_timer + 3000
                                    ADD_SCORE player -1
                                    ++script_state
                                ENDIF
                            ENDIF
                        ENDIF
                    ENDIF
                ENDIF

                IF script_state = 2 // waiting for animation to end
                    IF game_timer > next_state_time
                        next_state_time = game_timer + 27000
                        ++script_state
                    ENDIF
                ENDIF

                IF script_state = 3 // waiting for next buy time and player get out of cart activation sphere
                    IF game_timer > next_state_time
                        GET_OFFSET_FROM_OBJECT_IN_WORLD_COORDS prop_cart 1.0 0.0 0.0 temp_x temp_y temp_z
                        IF NOT LOCATE_CHAR_ON_FOOT_3D scplayer temp_x temp_y temp_z 0.6 0.6 1.0 FALSE
                            script_state = 1
                        ENDIF
                    ENDIF
                ENDIF

            ELSE // HAS_OBJECT_BEEN_UPROOTED
                IF script_state > 0
                    IF IS_PLAYER_PLAYING player
                        IF NOT IS_CHAR_DEAD vendor
                            TASK_KILL_CHAR_ON_FOOT_TIMED vendor scplayer 10000
                        ENDIF
                    ENDIF
                ENDIF
                GOTO terminate_vendor_script
            ENDIF
        ELSE
            GOSUB mark_vendor_as_no_longer_needed
            MARK_MODEL_AS_NO_LONGER_NEEDED BMOCHIL
        ENDIF
    ELSE
        GOTO terminate_vendor_script
    ENDIF
ELSE
    GOTO terminate_vendor_script
ENDIF
GOTO food_vendor_loop

terminate_vendor_script:
    GOSUB mark_vendor_as_no_longer_needed
    MARK_MODEL_AS_NO_LONGER_NEEDED BMOCHIL
TERMINATE_THIS_SCRIPT

mark_vendor_as_no_longer_needed:
    IF script_state > 0
        MARK_CHAR_AS_NO_LONGER_NEEDED vendor
        REMOVE_ANIMATION VENDING
        script_state = 0
    ENDIF
RETURN

}
SCRIPT_END
Edited by LINK/2012
Fixed broken formatting
Link to comment
Share on other sites

Custom Scripts

move_around.sc

 

// So, all we've learnt so far is everything Rockstar North's language offers.
//
// But, as curious as we modders are, we discovered ways to inject custom scripts into the game,
// and even further, add more script commands.
//
// This is what we call custom scripts (or cleo scripts). Those scripts are independent of
// the multifile and are run when they are put in the CLEO directory (http://cleo.li/).
//
// Most CLEO additions are useful commands, which you can learn by yourself (if you didn't already).
// But, some of these additions requires languages semantics, and that's what we'll focus on here.
//
//----------------------------------------------------------
//
// Custom scripts are enclosed by SCRIPT_START and SCRIPT_END, much like streamed scripts.
SCRIPT_START
{

LVAR_INT scplayer
LVAR_FLOAT player_x player_y player_z

// Tip: The player reference which CREATE_PLAYER gives (see multifile guide) is simply
// the player index, which is always 0 since there's no two-player mode on PC.
GET_PLAYER_CHAR 0 scplayer

main_loop:
WAIT 0
IF IS_PLAYER_PLAYING 0
    GET_CHAR_COORDINATES scplayer player_x player_y player_z

    // CLEO_CALL calls a label procedure. This is similar to functions in any other language.
    // Unlike GOSUB, this truly creates a new local variable scope, and can receive and output values.
    CLEO_CALL get_random_coords 0 (player_x player_y player_z 50.0) (player_x player_y player_z)
    //                          ^ This parameter should always be 0.
    //                            This was previously the amount of input variables, but now we employ
    //                            automatic detection of this value, much like progress counters in multifiles.

    SET_CHAR_COORDINATES scplayer (player_x player_y -100.0)

    // Most CLEO commands needs to accept string literals, which GTA3script do not have*,
    // as such, when CLEO support is enabled, you are given the power to use string literals within
    // strings and text label parameters. Unlike the *SAVE_STRING_TO_DEBUG_FILE literal, the string literals
    // in CLEO commands are compiled as-is, with no case conversion whatsover.
    //
    // I don't recommend using string literals in any other command other than CLEO commands.
    //
    PRINT_FORMATTED_NOW "Your current coordinate is %f %f %f" 5000 (player_x player_y player_z)
    WAIT 200
ENDIF
GOTO main_loop 

}
SCRIPT_END

{
// So this is the body of a CLEO procedure.
get_random_coords:
LVAR_FLOAT x y z radius     // The inputs will be passed to those variables,
                            // pretty much like with multifile's START_NEW_SCRIPT.

LVAR_FLOAT lower_x lower_y lower_z
LVAR_FLOAT upper_x upper_y upper_z

lower_x = x - radius
lower_y = y - radius
lower_z = z - radius

upper_x = x + radius
upper_y = y + radius
upper_z = z + radius

GENERATE_RANDOM_FLOAT_IN_RANGE (lower_x upper_x) x
GENERATE_RANDOM_FLOAT_IN_RANGE (lower_y upper_y) y
GENERATE_RANDOM_FLOAT_IN_RANGE (lower_z upper_z) z

CLEO_RETURN 0 x y z // and those are going to be the outputs of the procedure.
//          ^ should always be 0, for the same reason as explained in CLEO_CALL.
}

{
// I couldn't find any use for conditional procedures on this sample, but let's explain those anyway:
is_x_equal_to_y:
LVAR_INT x y
IF x = y
    IS_PC_VERSION       // gives true
ELSE
    IS_AUSTRALIAN_GAME  // gives false
ENDIF
CLEO_RETURN 0
//
// This procedure can then be used like
// IF CLEO_CALL is_x_equal_to_y 0 x y 
//     NOP
// ENDIF
//
// **Note 1:** You cannot put the CLEO_CALL in a AND/OR context, it doesn't accumulate the boolean results properly.
// **Note 2:** You also cannot use NOT CLEO_CALL to negate the its boolean result.
}

// Hexadecimal integer literals aren't present in the original language and were introduced
// because of CLEO:
WRITE_MEMORY 0xDEADC0DE 0 0 FALSE

// We also introduced DUMP...ENDDUMP, which is the equivalent of Sanny Builder's HEX...END.
DUMP
    00 FF 00 7F
ENDDUMP

// Another addition was REQUIRE. This is very much like LAUNCH_MISSION or GOSUB_FILE:
// It includes a new script file into the compilation process. But unlike the former commands,
// this does not spawn a cooperative script.
REQUIRE example.sc

// Final Note:
// To kill a custom script you shall use TERMINATE_THIS_CUSTOM_SCRIPT and TERMINATE_ALL_CUSTOM_SCRIPTS_WITH_THIS_NAME
// instead of TERMINATE_THIS_SCRIPT and TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME.

move_around/example.sc

 

// move_around/example.sc

// Whatever you want to code here.
// Usually you would fill those required files with procedures.
Edited by LINK/2012
Fixed broken formatting
Link to comment
Share on other sites

Please guys, start creating your mods using this. This can't go unnoticed. :)

  • Like 1
Link to comment
Share on other sites

It might be too late.
There must be a really good reason, why someone would use this instead of Sanny Builder.

When Sanny Builder already the job just fine.

 

Also, I've got a question.

How can I access the variables like:

0@(2@,30i)

1@(2@,30i)

2@(2@,30i)

3@(2@,30i)

4@(2@,30i)

5@(2@,30i)

6@(2@,30i)

7@(2@,30i)

 

2@ is correctly set up to point into script memory.

.....

Which is what I'm doing in my complicated scripts.

Edited by fastman92
Link to comment
Share on other sites

How can I access the variables like:

0@(2@,30i)

1@(2@,30i)

2@(2@,30i)

3@(2@,30i)

4@(2@,30i)

5@(2@,30i)

6@(2@,30i)

7@(2@,30i)

 

2@ is correctly set up to point into script memory.

You cannot, the correct approach is to relax the local variable limitation in the script engine which we'll be working on in the future. This is more or less a high-level language, as such this kind of trickery does not fit. Edited by LINK/2012
Link to comment
Share on other sites

There must be a really good reason, why someone would use this instead of Sanny Builder.

When Sanny Builder already the job just fine.

sannylang is joke of a scripting language compared to gta3script though*.

 

*Not meant to be disrespectful, we all realize sannylang is mirroring what we knew about SCM back in the day so naturally it's nothing close to real gta3script

Link to comment
Share on other sites

It should be mentioned in big letters that after a "wait" statement, the existence and the state of every used object must be checked and that any global variable may have been modified and must be checked. In particular one should take care when calling subroutines which have "wait" statements inside, because one does not see the "wait" statement when calling the subroutine. There are hundreds of cleo scripts which do not follow this rule and then cause random crashes of the game.

  • Like 1
Link to comment
Share on other sites

There are hundreds of cleo scripts which do not follow this rule and then cause random crashes of the game.

Hundreds? You were kind :)

This is really a common problem.

 

Maybe just a note "After WAIT the game was processed, and all used global vars and objects must be checked (e.g. If your object still exists or was deleted by game etc)"

Link to comment
Share on other sites

Entities you create yourself will not be deleted by the game though unless you explicitly allow it to.

Link to comment
Share on other sites

Entities you create yourself will not be deleted by the game though unless you explicitly allow it to.

Yep, because of this we used the word "used" and not "created".

 

For example, when you pick up some entity from world it can be deleted at any time and beginners never think about it; or think, but these guys use a lot of unnecessary WAIT at any time, and such entities ends up being deleted by the game, so crashes when you try to use it again.

 

This is actually very common, even Active Dashboard / Steering there was this error in the penultimate version.

 

Another common mistake is to mark the vehicle / ped as "no longer necessary", unnecessarily, so doing missions failed or even crashes too (related to the above problem). But this is a learning problem by looking at poor third party codes ("Oh, he used it, I think I should use it too!").

  • Like 3
Link to comment
Share on other sites

  • 3 years later...

Fixed the broken formatting caused by the (not so) recent changes the forum had.

  • Like 2
Link to comment
Share on other sites

  • 2 years later...
On 12/21/2016 at 11:35 PM, Link2012 said:

compiler and editor extension

Mind taking a lot at this? the link doesn't properly work 😥

Link to comment
Share on other sites

On 7/29/2022 at 1:05 PM, Mysterdogg said:

Mind taking a lot at this? the link doesn't properly work 😥

Thanks for the heads up. Fixed the links.

Link to comment
Share on other sites

14 hours ago, Link2012 said:

Thanks for the heads up. Fixed the links.

I meant look 😅 but yeah, thanks to you!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • 1 User Currently Viewing
    0 members, 0 Anonymous, 1 Guest

×
×
  • Create New...

Important Information

By using GTAForums.com, you agree to our Terms of Use and Privacy Policy.