Een echte missie scripten (GTA San Andreas)
Hoofdpagina > Modding > Modding Tutorial List > Een echte missie scripten
Inleiding
In deze nogal lange tutorial gaan we uitleggen hoe je een echte missie maakt, zoals Rockstar ze ook bouwt. Dus niet als een simpele thread, zoals in de voorgaande tutorials.
Om te beginnen zullen we de structuur en de bijzonderheden op een rijtje zetten, daarna zullen we templates geven voor de verschillende delen, en deze uitleggen, en tenslotte zullen we een missie laten zien die op basis van deze templates zijn gemaakt.
Structuur
Missies zoals ze door rockstar zijn gemaakt, en zoals we ze nu ook gaan maken, wijken qua gedrag af van normale threads die je normaal gebruikt. Ten eerste wordt de code voor een missie pas vanaf de schijf in het geheugen geladen, wanneer de missie gestart wordt. Dit heeft tot gevolg dat er slechts 1 missie tegelijk actief kan zijn. Een ander heel groot verschil is de manier hoe de code zich gedraagt waneer de speler BUSTED of WASTED is. In een normale thread moet je hier steeds op checken (is_payer_defined), maar tijdens een missie hoeft dat niet. Als de speler tijdens een missie WASTED of BUSTED wordt, voert de SCM-engine, namelijk op dat moment automatisch een "return"-opcode uit. Je hoeft dus tijdens de missie zelf niet steeds te controleren of de speler nog leeft, maar je moet wel het standaard framework van een Rockstar missie gebruiken, om die automatische "return"-opcode op te vangen. Dit framework zal verderop uitgelegd wordt bij de missie-template.
Om een missie te maken heb je twee delen nodig:
- De missie zelf
- Een zogenaamde "sniffer"-thread, die de startvoorwaarden van een missie checked en controleert of de speler de start van een missie triggert ( door b.v. op een bepaalde plek in een marker te lopen).
Voor beide delen zullen we een template geven, die je voor je eigen project moet invullen en evt. uitbreiden.
Sniffer-thread template
De sniffer-thread wordt gestart, zodra de missies gedaan kunnen worden. Dit kan direct vanaf de start van het spel zijn, of op het moment dat je een andere missie gehaalt hebt. Deze template bevat de code voor een sniffer-thread voor twee missies, maar dit kun je vrij eenvoudig uitbreiden tot veel meer missies die na elkaar gedaan kunnen worden.
// $TUT_MISSIONS_PASSED wordt vooraf op 0 gezet. // bij het "PASS"-en van een missie , moet je $TUT_MISSIONS_PASSED ophogen. :TUT_MISSIONS_SNIFFER thread "TUT_SNIFF" $MARKER_SWEET_HOUSE = Marker.CreateIconAndSphere(42, <<xcoord>>, <<ycoord>>, <<zcoord>>) repeat wait $DEFAULT_WAIT_TIME if and player.Defined($PLAYER_CHAR) $ONMISSION == 0 then if and 00FF: actor $PLAYER_ACTOR sphere 0 in_sphere <<xcoord>>, <<ycoord>>, <<zcoord>> radius 1.0 1.0 2.0 on_foot Player.Controllable($PLAYER_CHAR) $TUT_MISSIONS_PASSED == 0 then $ONMISSION = 1 00BA: show_text_styled GXT 'INTRO_1' time 1000 style 2 start_mission 1 end if and 00FF: actor $PLAYER_ACTOR sphere 0 in_sphere <<xcoord>>, <<ycoord>>, <<zcoord>> radius 1.0 1.0 2.0 on_foot Player.Controllable($PLAYER_CHAR) $TUT_MISSIONS_PASSED == 1 then $ONMISSION = 1 00BA: show_text_styled GXT 'INTRO_2' time 1000 style 2 start_mission 2 end end until $TUT_MISSIONS_PASSED == 2 marker.Disable($MARKER_SWEET_HOUSE) end_thread
Globaal gesproken, zet deze thread een marker op de goede plek waar de missions gestart kunnen worden en wacht dan in een lus tot de speler in de marker loopt, of dat beide missies gepassed zijn. Zolang $TUT_MISSIONS_PASSED op 0 staat, is er nog geen van de twee tutorial missies gepassed, en zal de eerste missie gestart worden, als de speler in de marker loopt (de eerste tak van de lus). Nadat de speler de eerste missie gehaald heeft, (waarbij de $TUT_MISSIONS_PASSED is opgehoogd naar 1), zal de snifferthread opnieuw wachten tot de speler in de marker loopt (de tweede tak van de lus). Na het succesvol afronden van de tweede missie, zal de $TUT_MISSIONS_PASSED op 2 staan, waarna de lus wordt verlaten, de marker wordt opgeruimd, en de thread wordt beëindigd.
Mission template
//-------------Mission 1--------------- // Mission wrapper :TUT_MISSION_1 thread 'TUT 1' gosub @TUT_MISSION_1_MAIN if wasted_or_busted then gosub @TUT_MISSION_1_FAIL end gosub @TUT_MISSION_1_CLEANUP end_thread //------------------------------------- :TUT_MISSION_1_MAIN //------------------------------------- :TUT_MISSION_1_PASSED Player.Money($PLAYER_CHAR) += 10000 01E3: show_text_1number_styled GXT 'M_PASS' number 10000 time 5000 style 1 // MISSION PASSED!~n~~w~$~1~ // This subroutine is executed when the mission is passed. // Give the rewards, and make new missions available if needed. return //------------------------------------- :TUT_MISSION_1_FAIL 00BA: show_text_styled GXT 'M_FAIL' time 5000 style 1 // ~r~MISSION FAILED! // This subroutine is executed when the mission fails. return //------------------------------------- :TUT_MISSION_1_CLEANUP $ONMISSION = 0 // This subroutine is always executed at the end of the mission, regardless the outcome. // This is the place to unload models etc. mission_cleanup return
Het eerste stukje, van deze code is de missie-wrapper, die de verschillende delen van de missie aanroept met behulp van "gosub", om zo de automatische "return" bij het WASTED of BUSTED van de speler ook opvangt. Als eerste roept hij het hoofdgedeelte van de missie aan. Als die terug komt, kunnen er twee dingen zijn gebeurt:
- Een automatische "return, omdat de speler WASTED of BUSTED is. In dat geval wordt de mission failed nog aangeroepen. en daarna de "cleanup" routine, die alles opruimt.
- De missie is PASSED of FAILED om een andere reden. In dat geval in de mission_passed of mission_failed routine al aangeroepen, en hoeft alleen de "cleanup" nog gedaan te worden.
In het complete voorbeeld hieronder zul je zien, hoe je vanuit het hoofddeel, de mission_passed en mission_failed routines aanroept bij bepaalde gebeurtenissen.
Voorbeeld missie
Op basis van bovenstaande templates hebben we hieronder een simpele missie gemaakt. Naast de templates uit deze tutorials, worden ook verschillende dingen uit voorgaande tutorials gebruikt.
De bedoeling van deze missie, die je kunt starten, voor het huis van Sweet, is om een andere actor dood te schieten. Er staan echter twee actors, een mannetje en een vrouwtje. Het is de bedoeling om het vrouwtjes dood te schieten, dan is de missie passed. Als je echter het mannetje doodschiet, dan is de missie failed. Als de speler WASTED of BUSTED is, voor dat het vrouwtje is doodgeschoten, is de missie ook failed.
DEFINE MISSIONS 2 DEFINE MISSION 0 AT @INITIAL DEFINE MISSION 1 AT @TUT_MISSION_1 DEFINE EXTERNAL_SCRIPTS 0 // Use -1 in order not to compile AAA script //DEFINE SCRIPT {NAME} AT {LABEL} @ DEFINE UNKNOWN_EMPTY_SEGMENT 0 DEFINE UNKNOWN_THREADS_MEMORY 0 {$VERSION 3.0.0000} var $PLAYER_CHAR: Player end // var 03A4: name_thread 'MAIN' 01F0: set_max_wanted_level_to 6 0111: toggle_wasted_busted_check 0 00C0: set_current_time_hours_to 8 minutes_to 0 04E4: unknown_refresh_game_renderer_at 2488.56 -1666.84 03CB: set_rendering_origin_at 2488.56 -1666.84 13.38 0053: $PLAYER_CHAR = create_player #NULL at 2488.56 -1666.84 13.38 01F5: $PLAYER_ACTOR = create_player_actor $PLAYER_CHAR 07AF: $PLAYER_GROUP = player $PLAYER_CHAR group 0373: set_camera_directly_behind_player 01B6: set_weather 0 0001: wait 0 ms 087B: set_player $PLAYER_CHAR clothes_texture "PLAYER_FACE" model "HEAD" body_part 1 087B: set_player $PLAYER_CHAR clothes_texture "JEANSDENIM" model "JEANS" body_part 2 087B: set_player $PLAYER_CHAR clothes_texture "SNEAKERBINCBLK" model "SNEAKER" body_part 3 087B: set_player $PLAYER_CHAR clothes_texture "VEST" model "VEST" body_part 0 070D: rebuild_player $PLAYER_CHAR 01B4: toggle_player $PLAYER_CHAR can_move 1 016A: fade 1 time 0 04BB: select_interior 0 0629: change_integer_stat 181 to 4 016C: restart_if_wasted_at 2027.77 -1420.52 15.99 angle 137.0 town_number 0 016D: restart_if_busted_at 1550.68 -1675.49 14.51 angle 90.0 town_number 0 0180: set_on_mission_flag_to $ONMISSION // Note: your missions have to use the variable defined here 0004: $DEFAULT_WAIT_TIME = 250 03E6: remove_text_box 0417: start_mission 0 // Initial wait 0 // put your create_thread commands here 00D7: create_thread @TUT_MISSIONS_SNIFFER :MAIN_LOOP 0001: wait $DEFAULT_WAIT_TIME ms 0002: jump @MAIN_LOOP // put your mods (threads) here :TUT_MISSIONS_SNIFFER thread "TUT_SNIFF" $MARKER_SWEET_HOUSE = Marker.CreateIconAndSphere(42, $X_SWEET_HOUSE, $Y_SWEET_HOUSE, $Z_SWEET_HOUSE) repeat wait $DEFAULT_WAIT_TIME if and player.Defined($PLAYER_CHAR) $ONMISSION == 0 then if and 00FF: actor $PLAYER_ACTOR sphere 0 in_sphere $X_SWEET_HOUSE $Y_SWEET_HOUSE $Z_SWEET_HOUSE radius 1.0 1.0 2.0 on_foot Player.Controllable($PLAYER_CHAR) $TUT_MISSIONS_PASSED == 0 then $ONMISSION = 1 00BA: show_text_styled GXT 'INTRO_1' time 1000 style 2 // Big Smoke start_mission 1 end end until $TUT_MISSIONS_PASSED == 1 marker.Disable($MARKER_SWEET_HOUSE) end_thread //-------------Mission 0--------------- :INITIAL $TUT_MISSIONS_PASSED = 0 $ONMISSION = 0 $X_SWEET_HOUSE = 2515.07 $Y_SWEET_HOUSE = -1673.98 $Z_SWEET_HOUSE = 12.71 0629: change_integer_stat 225 to 999 end_thread //-------------Mission 1--------------- // Mission wrapper :TUT_MISSION_1 thread 'TUT 1' gosub @TUT_MISSION_1_MAIN if wasted_or_busted then gosub @TUT_MISSION_1_FAIL end gosub @TUT_MISSION_1_CLEANUP end_thread //------------------------------------- :TUT_MISSION_1_MAIN //Load models model.Load(#m4) model.Load(#BFYST) model.Load(#BMYST) model.Load(#TAMPA) 038B: load_requested_models :TUT_MISSION_1_LOAD wait 0 ms if and model.Available(#m4) model.Available(#BMYST) model.Available(#BFYST) model.Available(#TAMPA) 004D: jump_if_false @TUT_MISSION_1_LOAD 01B2: give_actor $PLAYER_ACTOR weapon 31 ammo 10000 // Load the weapon model before using this 01B9: set_actor $PLAYER_ACTOR armed_weapon_to 31 0674: set_car_model #TAMPA numberplate "PATRICK_" $car_num = car.Create(#TAMPA,2488.56, -1666.84, 13.38) $victim = Actor.Create(CIVFEMALE, #BFYST, 2488.56, -1656.84, 13.38) $innocent = Actor.Create(CIVMALE, #BMYST, 2490.56, -1656.84, 13.38) 0187: $victim_marker = create_marker_above_actor $victim repeat wait 0 ms if actor.Dead($innocent) then jump @TUT_MISSION_1_FAIL end until actor.Dead($victim) :TUT_MISSION_1_PASSED 0394: play_music 1 $TUT_MISSIONS_PASSED += 1 Player.Money($PLAYER_CHAR) += 10000 01E3: show_text_1number_styled GXT 'M_PASS' number 10000 time 5000 style 1 // MISSION PASSED!~n~~w~$~1~ return //------------------------------------- :TUT_MISSION_1_FAIL 00BA: show_text_styled GXT 'M_FAIL' time 5000 style 1 // ~r~MISSION FAILED! // Hier kun je alles kwijt wat moet gebeuren als de missie gefaald is. Bv. bepaalde dingen weer terugdraaien 0555: remove_weapon 31 from_actor $PLAYER_ACTOR return //------------------------------------- :TUT_MISSION_1_CLEANUP $ONMISSION = 0 // Hier komt alle code om dingen op te ruimen die voor de missie zijn geladen // En om speciale settings voor deze missie terug te zetten naar normaal Marker.Disable($victim_marker) 010D: set_player $PLAYER_CHAR wanted_level_to 0 model.Destroy(#BFYST) model.Destroy(#BMYST) model.Destroy(#M4) mission_cleanup return //-------------External script 0--------------- // put your external scripts here
Het eerste deel van de code, is een standaard stuk dat iedereen wel zal kennen van de stripped SCM file. Het enige dat daaraan is toegevoegd, is het starten van de snifferthread:
00D7: create_thread @TUT_MISSIONS_SNIFFER
En ten slotte laten we de main-thread rondjes draaien in :MAIN_LOOP
Daarna volgt de sniffer-thread. Gebaseerd op de template, maar beperkt tot maar 1 missie.
Je ziet dat we twee missies hebben gedefineerd, de eerste (Missie 0) gebruiken we net als rockstar voor het initialiseren van allerlei dingen. Hier zou je ook car_generators, en b.v. wapen pickups kunnen plaatsen. Wij zetten hier de $TUT_MISSIONS_PASSED op 0, en plaatsen de locatie van de mission trigger in 3 variabelen, zodat we ze makkelijk kunnen gebruiken op verschillende plekken.
De tweede missie (Missie1) is volgens de template opgezet. In het main deel worden eerst de modellen geladen, actors gespawned, wapens toegekend en een auto gespawned, zoals we in voorgaande tutorials al gezien hebben. Daarna wacht de code in een lus, totdat de $VICTIM wordt doodgeschoten, in dat geval loopt de code door in de mission_passed routine en is de missie dus afgelopen. Tijdens de lus wordt ook gechecked of het onschuldige mannetje nog leeft. Is dat niet meer het geval, dan springen we naar de mission_failed routine, en is de missie ook afgelopen.
In de mission passed routine, zetten we een boodschap op het scherm, geven we de speler het beloning-bedrag en hogen de $TUT_MISSIONS_PASSED op.
In de mission failed routine zetten we de boodschap op het scherm, en pakken de speler het wapen af dat hij gekregen heeft.
In de cleanup routine, ruimen we alle gebruikte modellen op en zetten het wanted-level terug op 0.