diff --git a/examples/Mission_Trigger.txt b/examples/Mission_Trigger.txt new file mode 100644 index 00000000..436db073 --- /dev/null +++ b/examples/Mission_Trigger.txt @@ -0,0 +1,70 @@ +// CLEO5 example script +// Sanny Builder 4 +// mode: GTA SA (v1.0 - SBL) + +{$CLEO .cs} +{$USE CLEO+} + +const + Mission_Icon = 34 // RadarSprite.Ryder - https://library.sannybuilder.com/#/sa/script/enums/RadarSprite + Mission_Pos_X = 2468.5 + Mission_Pos_Y = -1688.0 + Mission_Pos_Z = 13.5 + Mission_File = "my_ryder_mission" // my_ryder_mission.cm +end + +script_name 'mis_trg' + +CleoBlip blipHandle = -1 +int markerActive = false +while true + wait 0 + + if or + is_on_mission // any mission is in progress + not is_player_playing $player1 // wasted or busted + then + if + blipHandle <> -1 + then + remove_cleo_blip blipHandle + blipHandle = -1 + end + + markerActive = true // in case any mission ends in this mission trigger + wait {time} 1000 // on mission + continue + else + if + blipHandle == -1 + then + blipHandle = add_cleo_blip {RadarSprite} Mission_Icon {pos} Mission_Pos_X Mission_Pos_Y {short} true {rgba} 255 255 255 255 + end + + if + locate_char_any_means_3d $scplayer {pos} Mission_Pos_X Mission_Pos_Y Mission_Pos_Z {radius} 50.0 50.0 50.0 {drawSphere} false // in proximity? + then + if + //locate_stopped_char_any_means_3d $scplayer {pos} Mission_Pos_X Mission_Pos_Y Mission_Pos_Z {radius} 2.0 2.0 2.0 {drawSphere} true + locate_stopped_char_on_foot_3d $scplayer {pos} Mission_Pos_X Mission_Pos_Y Mission_Pos_Z {radius} 2.0 2.0 2.0 {drawSphere} true + //locate_stopped_char_in_car_3d $scplayer {pos} Mission_Pos_X Mission_Pos_Y Mission_Pos_Z {radius} 2.0 2.0 2.0 {drawSphere} true + then + if and + markerActive == false + can_player_start_mission $player1 + then + markerActive = true + load_and_launch_custom_mission Mission_File + continue + end + else + markerActive = false + end + else + wait {time} 1000 // away from trigger + continue + end + end +end + +terminate_this_custom_script // not needed, but good to have diff --git a/examples/Skin_Selector.txt b/examples/Skin_Selector.txt new file mode 100644 index 00000000..9e5e00fd --- /dev/null +++ b/examples/Skin_Selector.txt @@ -0,0 +1,589 @@ +// CLEO5 example script +// Sanny Builder 4 +// mode: GTA SA (v1.0 - SBL) +// +// Script for applying custom models for player character. +// Put compiled script into ModLoader\Skin Selector\cleo directory. +// Copy model .dff and .txd files into ModLoader\Skin Selector\skin.img directory. +// Type "skinselect" cheat or use key combination Tab+Q+E to display models menu. + +{$CLEO .cs} + +script_name {name} "skin_sel" +int initialized = false +longString currModelName = "" + +while true + // model selected but not set? Happens after game is loaded from save + if and + not is_text_empty {string} currModelName + not is_char_model $scplayer {modelId} #SPECIAL10 + then + initialized = false + end + + if + initialized == false + then + if + not INITIALIZE() + then + terminate_this_script // some critical error occured + end + + if + not PLAYER_MODEL_SET(currModelName) + then + currModelName = "" + end + initialized = true + end + + wait {time} 100 + + // Rayosuke's Skin Loader key combination (Tab+Q+E) + if and + is_button_pressed {pad} PadId.Pad1 {buttonId} Button.LeftShoulder1 // Tab + is_button_pressed {pad} PadId.Pad1 {buttonId} Button.LeftShoulder2 // Q + is_button_pressed {pad} PadId.Pad1 {buttonId} Button.RightShoulder2 // E + then + repeat + wait {time} 0 + until not is_button_pressed {pad} PadId.Pad1 {buttonId} Button.LeftShoulder1 // Tab + + goto @_TRIGGER_MENU + end + + if + test_cheat {input} "skinselect" + then + :_TRIGGER_MENU + if or + not can_player_start_mission $player1 // wasted, not on ground etc. + is_char_in_any_car $scplayer + is_char_getting_in_to_a_car $scplayer + then + add_one_off_sound {xyz} 0.0 0.0 0.0 {soundId} ScriptSound.SoundRouletteNoCash // error sound + continue + end + + currModelName = MENU_SKIN(currModelName) + PLAYER_MODEL_SET(currModelName) + end +end + + +function INITIALIZE() : logical + // undo any hacks in case previous game session was interrupted (like alt+tab then starting new game) + SCREEN_OFFSET_SET(0.0, 0.0) + PLAYER_MODEL_REBUILD_ENABLE(true) + + // check if installed inside ModLoader + int scriptPath = allocate_memory {size} 260 + scriptPath = get_script_filename {address} -1 {fullPath} true + if + not is_text_in_text {text} scriptPath {subText} "modloader" {ignoreCase} true + then + debug_on + trace "Skin Selector: ~r~~h~Error!~s~ This mod must be placed inside ModLoader directory." + free_memory {address} scriptPath + return false + end + free_memory {address} scriptPath + + // activate saving of this script's state whenever player saves the game + save_this_custom_script + + // create skin files directory + if + not does_directory_exist {path} ".\..\skin.img" + then + if + not create_directory {path} ".\..\skin.img" + then + debug_on + trace "Skin Selector: ~r~~h~Error!~s~ Failed to create 'skin.img' directory." + end + end + + // patch game code to allow model names with up to 15 characters (originally 7) + write_memory {address} 0x0047EDB9 {size} 1 {value} 15 {vp} false + write_memory {address} 0x0047EDD2 {size} 4 {value} 15 {vp} false + + return true +end + + +function MENU_SKIN(modelNamePtr :int) : string + int iTmp, iTmp2, iTmp3 + int str = allocate_memory {size} 260 + + int result = false + longString selectedModel + string_format selectedModel = "%s" modelNamePtr + int modelUpdated = true // force update at start + + clear_char_tasks_immediately $scplayer + + // store current player pos + float oriPos[3] + oriPos[0], oriPos[1], oriPos[2] = get_char_coordinates $scplayer + float oriDir = get_char_heading $scplayer + int oriInterior = get_char_area_visible $scplayer + + // hide surroundings + freeze_char_position $scplayer {state} true + set_char_collision $scplayer {state} false + set_char_coordinates_dont_warp_gang_no_offset $scplayer {pos} oriPos[0] oriPos[1] 2000.0 + set_char_area_visible $scplayer {areaId} 30 + set_area_visible {areaId} 30 + set_extra_colours {color} 84 {fade} false // sky and lighting + + set_player_fire_button {playerId} $player1 {state} false + set_player_jump_button {playerId} $player1 {state} false + + set_time_scale {scale} 0.5 + + display_hud {state} false + display_radar {state} false + display_zone_names {state} false + restore_camera_jumpcut + set_camera_in_front_of_player + set_camera_zoom {zoom} 1 // medium distance + SCREEN_OFFSET_SET(0.35, 0.25) // offset camera target as half of the screen is occupied by menu + + // current selection + int currPage = 0 + int currRow = 0 + + // count available skin models + iTmp = allocate_memory {size} 32 + string_format iTmp = "%s.dff" modelNamePtr // full filename of current model + + int skinCount = 1 // Default is always present + int fileSearch + if + fileSearch, str = find_first_file {searchMask} ".\..\skin.img\*.dff" + then + repeat + if + is_text_equal {text} str {another} iTmp {ignoreCase} true // current selection found + then + currPage = skinCount / 10 + currRow = skinCount % 10 + end + + skinCount += 1 + until not str = find_next_file fileSearch + end + free_memory iTmp + + if + skinCount <= 1 + then + // show no files error message + add_one_off_sound {xyz} 0.0 0.0 0.0 {soundId} ScriptSound.SoundRouletteNoCash // error sound + + str = resolve_filepath {path} ".\..\script.img" + while true + wait 0 + + use_text_commands {state} true + set_text_scale {width} 0.45 {height} 1.8 + set_text_colour {rgb} 255 255 255 {alpha} 255 + set_text_edge {size} 1 {rgb} 0 0 0 {alpha} 230 + set_text_centre {state} true + set_text_centre_size {width} 640.0 + display_text_formatted {pos} 320.0 383.0 {format} "No DFF files found in directory~n~%s" {args} str + + if or + is_button_pressed {pad} PadId.Pad1 {buttonId} Button.Triangle // cancel + is_key_pressed {keyCode} KeyCode.Escape // exiting to main menu + not has_save_game_finished // save menu was requested + has_game_just_returned_from_frontend // main menu was displayed + is_button_pressed {pad} PadId.Pad1 {buttonId} Button.Cross // accept + then + break + end + end + else + add_one_off_sound {xyz} 0.0 0.0 0.0 {soundId} ScriptSound.SoundRouletteAddCash // accept sound + + // help box + if + skinCount > 10 + then + add_text_label {dynamicKey} 'SKS_HLP' {text} "Use ~UP~~DOWN~ to browse~n~~<~~>~Change page~n~~h~~k~~PED_SPRINT~~s~ Select~n~~h~~k~~VEHICLE_ENTER_EXIT~~s~ Quit" + else + add_text_label {dynamicKey} 'SKS_HLP' {text} "Use ~UP~~DOWN~ to browse~n~~h~~k~~PED_SPRINT~~s~ Select~n~~h~~k~~VEHICLE_ENTER_EXIT~~s~ Quit" + end + print_help_forever {key} 'SKS_HLP' + + // show the menu + add_text_label {dynamicKey} 'SKS_CAP' {text} "Skin Selector" + add_text_label {dynamicKey} 'SKS_NUL' {text} "_" // empty + int menuHandle = -1 + while true + :_MENU_SKIN_MENU_BEGIN + if + menuHandle <> -1 + then + delete_menu menuHandle + end + menuHandle = create_menu {header} 'SKS_CAP' {pos} 32.0 150.0 {width} 180.0 {numColumns} 1 {interactive} true {background} true {alignment} Align.Center + + // there was some other menu already present? + if + menuHandle > 0 // handle is actually index + then + goto @_MENU_SKIN_CLOSE // error, other (game's or mod) menu already displayed + end + + if + skinCount <= 10 // single page + then + set_menu_column menuHandle {column} 0 {title} 'DUMMY' {rows} 'SKS_NUL' 'SKS_00' 'SKS_01' 'SKS_02' 'SKS_03' 'SKS_04' 'SKS_05' 'SKS_06' 'SKS_07' 'SKS_08' 'SKS_09' 'SKS_NUL' + activate_menu_item menuHandle {row} 0 {state} false // disable prev page slot + activate_menu_item menuHandle {row} 11 {state} false // disable next page slot + else + // page text + iTmp = currPage + 1 // 1-based display + + iTmp2 = skinCount + iTmp2 /= 10 + iTmp2 += 1 // 1-based display + + string_format str = "Page %d / %d" {args} iTmp iTmp2 + add_text_label {dynamicKey} 'SKS_PG' {text} str + + set_menu_column menuHandle {column} 0 {title} 'SKS_PG' {rows} 'SKS_NUL' 'SKS_00' 'SKS_01' 'SKS_02' 'SKS_03' 'SKS_04' 'SKS_05' 'SKS_06' 'SKS_07' 'SKS_08' 'SKS_09' 'SKS_NUL' + end + + // generate row texts + if + not fileSearch, str = find_first_file {searchMask} ".\..\skin.img\*.dff" + then + goto @_MENU_SKIN_CLOSE // error, no files + end + + iTmp = 0 // index + iTmp2 = 0 // page + while true + switch iTmp + case 0 + string_format str = "Default" + + case 1 + fileSearch, str = find_first_file {searchMask} ".\..\skin.img\*.dff" + + default + if + not str = find_next_file fileSearch + then + string_format str = "_" // empty row + end + end + + if + iTmp2 == currPage + then + // cut of file type extension + if + is_text_suffix {text} str {suffix} ".dff" {ignoreCase} true + then + iTmp3 = str + while true + if + is_text_equal {text} iTmp3 {another} ".dff" {ignoreCase} true + then + write_memory {address} iTmp3 {size} 1 {value} 0x00 {vp} false // text terminator + break + end + + iTmp3 += 1 // go to next character + end + end + + iTmp3 = iTmp % 10 + shortString labelName + string_format labelName = "SKS_%02d" iTmp3 + add_text_label {dynamicKey} labelName {text} str + + // empty row + if + is_text_equal {text} str {another} "_" {ignoreCase} true + then + // empty row is selected? Update currRow and recreate menu + if + currRow >= iTmp3 + then + currRow = iTmp3 - 1 + goto @_MENU_SKIN_MENU_BEGIN + end + + // disable that menu option + iTmp3 += 1 // first row is used for page navigation + activate_menu_item menuHandle {row} iTmp3 {state} false + else + if and + modelUpdated == true + iTmp3 == currRow + then + if + is_text_equal {text} str {another} "default" {ignoreCase} true + then + string_format selectedModel = "" + else + string_format selectedModel = "%s" str + end + + PLAYER_MODEL_SET(str) + task_go_straight_to_coord {handle} $scplayer {pos} 0.0 10000.0 0.0 {speed} MoveState.Walk {time} -1 + modelUpdated = false + end + end + end + + iTmp += 1 + + iTmp3 = iTmp % 10 + if + iTmp3 == 0 + then + iTmp2 += 1 // next page + + if + iTmp2 > currPage + then + break // all labels done + end + end + end + + // set selected row + iTmp = currRow + 1 + set_active_menu_item menuHandle {row} iTmp + + // handle user input + while modelUpdated == false + wait {time} 0 + + iTmp = currRow + + currRow = get_menu_item_selected menuHandle + currRow -= 1 // first row is used for page navigation + + // row selection changed + if + currRow <> iTmp + then + modelUpdated = true + end + + if or + is_button_pressed {pad} PadId.Pad1 {buttonId} Button.Triangle // cancel + is_key_pressed {keyCode} KeyCode.Escape // exiting to main menu + not has_save_game_finished // save menu was requested + has_game_just_returned_from_frontend // main menu was displayed + then + goto @_MENU_SKIN_CLOSE + end + + if + is_button_pressed {pad} PadId.Pad1 {buttonId} Button.Cross // accept + then + result = true + goto @_MENU_SKIN_CLOSE + end + + // handle page switching + if + skinCount > 10 + then + iTmp = get_pad_state {pad} PadId.Pad1 {buttonId} Button.LeftStickX + + // next page + if or + currRow > 9 + iTmp > 64 // analog stick right + then + if + currRow > 9 + then + currRow = 0 // warp around + end + + currPage += 1 + + iTmp = skinCount / 10 // total page count + if + currPage > iTmp + then + currPage = 0 + end + + add_one_off_sound {xyz} 0.0 0.0 0.0 {soundId} ScriptSound.SoundPickupStandard + + // wait for button release + repeat + wait {time} 0 + iTmp = get_pad_state {pad} PadId.Pad1 {buttonId} Button.LeftStickX + abs_lvar_int iTmp + until iTmp < 64 + + modelUpdated = true + break + end + + // prev page + if or + currRow < 0 + iTmp < -64 // analog stick left + then + if + currRow < 0 + then + currRow = 9 // warp around + end + + currPage -= 1 + + if + currPage < 0 + then + currPage = skinCount / 10 // warp around + end + + add_one_off_sound {xyz} 0.0 0.0 0.0 {soundId} ScriptSound.SoundPickupStandard + + // wait for button release + repeat + wait {time} 0 + iTmp = get_pad_state {pad} PadId.Pad1 {buttonId} Button.LeftStickX + abs_lvar_int iTmp + until iTmp < 64 + + modelUpdated = true + break + end + end + end + end + :_MENU_SKIN_CLOSE + end + + // cleanup + free_memory {address} str + + freeze_char_position $scplayer {state} false + set_char_collision $scplayer {state} true + set_char_coordinates_dont_warp_gang_no_offset $scplayer {pos} oriPos[0] oriPos[1] oriPos[2] + set_char_heading $scplayer {heading} oriDir + set_char_area_visible $scplayer {areaId} oriInterior + set_area_visible {areaId} oriInterior + if + oriInterior == 0 + then + clear_extra_colours {withFade} false + end + + set_player_fire_button {playerId} $player1 {state} true + set_player_jump_button {playerId} $player1 {state} true + + delete_menu menuHandle + + clear_help + + set_time_scale {scale} 1.0 + + display_hud {state} true + display_radar {state} true + display_zone_names {state} true + restore_camera_jumpcut + SCREEN_OFFSET_SET(0.0, 0.0) + + if + result == false + then + string_format selectedModel = "%s" modelNamePtr + end + + return selectedModel +end + + +function PLAYER_MODEL_SET(modelName :string) : logical + int result = true + + int len = get_text_length {text} modelName + if + len > 15 + then + debug_on + trace "Skin Selector: ~r~~h~Error!~s~ Invalid '%s' skin. Model filename must have up to 15 characters." modelName + string_format modelName = "default" // fallback + result = false + end + + // check if file present before trying to load + if and + not is_text_empty {string} modelName + not is_text_equal {text} modelName {another} "default" {ignoreCase} true + then + int filePath = allocate_memory {size} 260 + string_format filePath = ".\..\skin.img\%s.dff" modelName + if + not does_file_exist {path} filePath + then + debug_on + trace "Skin Selector: ~r~~h~Error!~s~ File '%s' not found." modelName + string_format modelName = "default" // fallback + result = false + end + free_memory {address} filePath + end + + if or + is_text_empty {string} modelName + is_text_equal {text} modelName {another} "default" {ignoreCase} true + then + PLAYER_MODEL_REBUILD_ENABLE(true) + build_player_model $player1 + set_player_model $player1 {modelId} 0 + else + load_special_character {slotId} 10 {modelName} modelName + load_all_models_now + + PLAYER_MODEL_REBUILD_ENABLE(false) + set_player_model $player1 {modelId} #SPECIAL10 + unload_special_character {slotId} 10 + end + + return result +end + + +// enable/disable function for rebuilding player's model +// called to update model after fat or muscles stat was changed +// messes up bones structure in case if player uses models other than CJ +function PLAYER_MODEL_REBUILD_ENABLE(enable :int) + if + enable == true + then + write_memory 0x005A81E0 {size} 1 {value} 0x56 {vp} 1 // restore CClothes::ConstructPedModel() + else + write_memory 0x005A81E0 {size} 1 {value} 0xC3 {vp} 1 // disable CClothes::ConstructPedModel() + end +end + + +// set xy offset of screen position (GTA SA 1.0) +function SCREEN_OFFSET_SET(x :float, y: float) + int ptr = read_memory_with_offset {address} 0x00B6F028 {offset} 0x954 {size} 4 // TheCamera.rwCamera + if + ptr <> 0 + then + write_memory_with_offset {address} ptr {offset} 0x78 {size} 4 {value} x + write_memory_with_offset {address} ptr {offset} 0x7C {size} 4 {value} y + end +end diff --git a/examples/Train_Spawner.txt b/examples/Train_Spawner.txt index ae8c8a77..d269b033 100644 --- a/examples/Train_Spawner.txt +++ b/examples/Train_Spawner.txt @@ -1,19 +1,25 @@ // CLEO5 example script // Sanny Builder 4 // mode: GTA SA (v1.0 - SBL) +// +// Type "train" cheat to spawn train + {$CLEO .cs} script_name {name} "train_sp" -int trainType = -1 +// patch CVehicle.CanBeDriven() function to allow player entering trains placed on any track +write_memory {address} 0x006D5410 {size} 1 {value} 0xFF {vp} false +int trainType = -1 while true - wait 333 + wait 250 int area = get_area_visible if and test_cheat "train" + is_player_playing $player1 // not wasted or busted is_player_control_on $player1 area == 0 // overworld then