Link2012 Posted December 22, 2016 Share Posted December 22, 2016 (edited) 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. The Basics Multifiles Custom Scripts Tip: Do notice how the important parts are all near comment blocks, so you should watch for those. Edited August 1, 2022 by Link2012 Blackbird88, fastman92, Deadstroke and 19 others 22 Link to comment Share on other sites More sharing options...
Link2012 Posted December 22, 2016 Author Share Posted December 22, 2016 (edited) 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 May 22, 2020 by LINK/2012 Fixed broken formatting Deadstroke, Silent, fabio3 and 6 others 9 Link to comment Share on other sites More sharing options...
Link2012 Posted December 22, 2016 Author Share Posted December 22, 2016 (edited) 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 May 22, 2020 by LINK/2012 Fixed broken formatting Deadstroke, RyanDri3957V, _Israel_ and 4 others 7 Link to comment Share on other sites More sharing options...
Link2012 Posted December 22, 2016 Author Share Posted December 22, 2016 (edited) 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 May 22, 2020 by LINK/2012 Fixed broken formatting thehambone, Silent, _Israel_ and 9 others 11 1 Link to comment Share on other sites More sharing options...
_Israel_ Posted December 22, 2016 Share Posted December 22, 2016 Awesome ! wwwwandrarijaz 1 Link to comment Share on other sites More sharing options...
Junior_Djjr Posted December 25, 2016 Share Posted December 25, 2016 Please guys, start creating your mods using this. This can't go unnoticed. wwwwandrarijaz 1 Link to comment Share on other sites More sharing options...
fastman92 Posted December 25, 2016 Share Posted December 25, 2016 (edited) 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 December 25, 2016 by fastman92 Link to comment Share on other sites More sharing options...
Link2012 Posted December 25, 2016 Author Share Posted December 25, 2016 (edited) 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 December 25, 2016 by LINK/2012 Link to comment Share on other sites More sharing options...
thehambone Posted December 25, 2016 Share Posted December 25, 2016 Fantastic! Great work! Meet the GTA 3 players who've spent a decade playing pass-the-pad to 100% the game - GamesRadar GTA:LCS Save Editor - Supports Android, iOS, PS2, and PSP saves! GTA3 Save Editor - Big update in the works! Link to comment Share on other sites More sharing options...
Silent Posted December 25, 2016 Share Posted December 25, 2016 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 The Hero, Junior_Djjr, RyanDri3957V and 2 others 5 Link to comment Share on other sites More sharing options...
goodidea82 Posted December 27, 2016 Share Posted December 27, 2016 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. Junior_Djjr 1 Link to comment Share on other sites More sharing options...
Junior_Djjr Posted December 27, 2016 Share Posted December 27, 2016 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 More sharing options...
Silent Posted December 27, 2016 Share Posted December 27, 2016 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 More sharing options...
Junior_Djjr Posted December 28, 2016 Share Posted December 28, 2016 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!"). thehambone, Concavax and RyanDri3957V 3 Link to comment Share on other sites More sharing options...
Link2012 Posted May 22, 2020 Author Share Posted May 22, 2020 Fixed the broken formatting caused by the (not so) recent changes the forum had. RyanDri3957V and Grinch_ 2 Link to comment Share on other sites More sharing options...
Mysterdogg Posted July 29, 2022 Share Posted July 29, 2022 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 More sharing options...
Link2012 Posted August 1, 2022 Author Share Posted August 1, 2022 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 More sharing options...
Mysterdogg Posted August 1, 2022 Share Posted August 1, 2022 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 More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now