BSL:Tutorial/Scratch: Difference between revisions

From OniGalore
Jump to navigation Jump to search
(→‎Part 1 : Knowing your formats: moving Shell-Style page here; no point in being separate)
(rewrote the tutorial top to bottom; the script has been tweaked and simplified, and some odd advice on forking corrected)
(7 intermediate revisions by 2 users not shown)
Line 1: Line 1:
This page is a short tutorial on how to get started on simple scripts for those who have had no experience with scripting before.
This page is a short tutorial for those who have no experience with scripting on how to get started creating a new level script. It's recommended that you read the [[BSL:Introduction]] page first to get a grasp of BSL's syntax. Each significant command in this tutorial should have a link behind it the first time it's referenced; click that link to learn more about the command. For a complete list of commands, see [[BSL:Functions]] and [[BSL:Variables]].
It will teach you, step by step, how to write a simple script. By the end of this tutorial, you will be able to move on to more advanced scripts.
{{TOClimit|2}}
 
==The "main" function==
 
Open up your [[IGMD]] folder and look for '''EnvWarehouse/'''. Inside this folder you should see seven .bsl files. Using a plain-text editor (such as Notepad or Notepad++ in Windows, and TextEdit or BBEdit in macOS), open the file '''warehouse_main.bsl'''. It should look like this:
----
#
 
# warehouse_main.bsl
== Part 1 : Knowing your formats ==
#
 
The game will accept 2 types of syntax for scripts, and they are based on different styles of writing. Choose the one that suits you best.
var int my_save_point;
 
The types are :
 
'''Shell-Style''':
This is compiled much like some System-Shells. It is written in a "one-function-call-a-line" format, and is the easiest to begin with if you have had no coding experience, as the code is neatly displayed line after line. The downside of this it, that once you get into more complex scripts or scripts with many function calls, the file becomes very hard to read and very large. Also, an great tool, passing information through a command, cannot be done using Shell-Style.
 
Example:
 
func p2
{
ai2_doalarm muro1 11
fork follow
ai2_spawn tarmac_striker_3
ai2_spawn cruel_striker1
ai2_spawn Hangar1_A_Striker_3
ai2_spawn Start_Striker_5
ai2_spawn Roof2_Striker_1
ai2_spawn Hangar1_B_Striker_5
chr_teleport tarmac_striker_3 199
chr_teleport cruel_striker1 200
chr_teleport Hangar1_A_Striker_3 108
chr_teleport Start_Striker_5 109
chr_teleport Roof2_Striker_1 110
chr_teleport Hangar1_B_Striker_5 117
ai2_attack tarmac_striker_3 muro1
ai2_attack cruel_striker1 muro1
ai2_attack Hangar1_A_Striker_3 muro1
ai2_attack Start_Striker_5 muro1
ai2_attack Roof2_Striker_1 0
ai2_attack Hangar1_B_Striker_5 0
}
 
'''C-Style''':
This is compiled much like the popular coding style C. The syntax can be placed into multiple-function lines and functions are divided by a ''';''' . In advanced scripts, this style will allow you to condense your script and the parts will be easier to read/debug. It also allows you to utilize some of the more advanced features easily. The problem with this is, that starting off, it is harder to write if you have had no previous experience with coding.
 
Example: [[C-Style]]
 
Hybrid Style:
This is a combination between C-Style and Shell-Stlye. It keeps the easy-to-read format of Shell-Style, yet allows you to use the more advanced techniques of C-Style.
 
For the purpose of this article, we will use Shell-Style syntax for simplicity.
 
 
----
 
== Part 2: Starting Off ==
 
Now, I suggest reading the original script files to get a basic idea for how scripts are laid out. This will allow us to jump-start a bit.
 
Open up your IGMD and look for the following folder: EnvWarehouse.
Open it up, and we'll take a look at how it it laid out. There should be several files in here. Open using NOTEPAD, the one titled '''warehouse_main.bsl'''. It should look like this:
  func void main(void)
  func void main(void)
  {
  {
Line 70: Line 20:
     level_start
     level_start
  }
  }
This is an example of a '''main''' file. It contains the information of how the level starts.


Ignore the contents of the script for now, and notice that the contents of the function <tt>main</tt> are enclosed by a set of brackets ('''{''' and '''}''').
This is an example of a level's '''main''' script. It contains the main() function, which is always where the level starts. The script file can technically be called anything, but there must be a function called "main" in one of the level's script files. Oni will always look for and start with this function when a level loads.


All functions must be enclosed in this set of brackets or it will not be executed.
==Creating a script==
Close warehouse_main.bsl if you still have it open, and rename the containing folder "EnvWarehouse-original". Create a new folder called "EnvWarehouse" and create a new plain-text file inside it called "tutorial.bsl".


Close the file, and rename the folder "EnvWarehouse" "EnvWarehouse_original". Then, create a folder called "EnvWarehouse" and create a script file inside and name it "Tutorial". We will be using this file to show various examples of scripts and finally, write a small script of your own.
First we're just going to place you in the final room of TCTF Training where you practice firing weapons. The room will be empty since no enemies have been spawned by the script. In case you're wondering how you could be in TCTF Training when this is a script inside EnvWarehouse/, be aware that {{C0}} is actually part of the same level data files as {{C1}} and only appears to be separate in the Load Game menu (this is the only time Oni combines two levels into one set of files).


Are you editing the file tutorial.bsl? Good. First, declare the "main" function for the level by typing "<tt>func void main(void)</tt>" at the very beginning of the file. Inside of curly braces, type this one command: <tt>[[chr_teleport]] 0 7045</tt>. This function call will teleport Konoko (her ID is always 0) to flag 7045, which is located in the final training room.


----
The file's contents should look like this:
func void main(void)
{
    chr_teleport 0 7045
}


== Part 3: Basic Commands ==
Save the file, open up Oni and click "New Game" (you can also click "Load Game" and choose "TCTF Training" or any save point for "Syndicate Warehouse" and get the same result). You should be standing on the desk in the final training room. Now to start populating the level!


This is just a small section explaining the commands that will be used in this tutorial. For a complete list of commands, see [http://ssg.oni2.net/commands.htm ssg's site] for more information.
==Adding NPCs (AIs)==
Before we begin, notice that there are two types of AIs: AIs created from character resources ([[CHAR]]) and AIs created at runtime. AIs stored in CHARs are spawned by the function call <tt>[[ai2_spawn]] ''name''</tt>, where "name" is the name of the AI. AIs can be created at runtime by the function call <tt>[[chr_create]] ''ai_number'' start</tt>. These AIs do not have combat logic ([[MELE]]) and thus will not attack you physically, but can still use weapons. Below we will only work with CHAR AIs since they are already set up and ready to use.


ai2_spawn (name) - creates and starts an AI from a character object<br>
Now we are going to add 2 enemy AIs to our script that we started in the last section. Open it up, and let's add some enemies for Konoko to fight.
chr_teleport (name) (flag) - teleports a character to a flag<br>
chr_givepowerup (name) (powerup) (amount) - gives a character a powerup<br>
.
.
.


To recap, it should look like this:
func void main(void)
{
    chr_teleport 0 7045
}


----
After the call to <tt>chr_teleport</tt> add the following two commands on separate lines: <tt>ai2_spawn Top_Striker_1</tt> and <tt>ai2_spawn Top_Comguy_1</tt>. These commands tell the game to spawn two characters already named in the game data, Top_Striker_1 and Top_Comguy_1.


== Part 4: First Script ==
Since these characters do not spawn in the final room they must be teleported there. This time we'll use the <tt>chr_teleport</tt> command to teleport the 2 AIs, and we'll use their names to identify them instead of numeric IDs. Teleport them to the flags "7008" and "7009" respectively. When you're done, main() should now look like this:


This section will teach you how to write a very basic script (fighting script). It will start you off in the Final Training Room, where you practiced firing weapons in the original game. With this script, the room will appear very empty.
func void main(void)
{
    chr_teleport 0 7045
    ai2_spawn Top_Striker_1
    ai2_spawn Top_Comguy_1
    chr_teleport Top_Striker_1 7008
    chr_teleport Top_Comguy_1 7009
}


Let's open up the "Tutorial.bsl" file and start writing the script.
The script will now teleport you into the final training room and face you off against two [[Striker]]s. Try it out.


First, declare it the "main" portion of the script by typing "<tt>func void main(void)</tt> at the very beginning of the file. This tells the game which function it is. The "main" function is the function that the game looks at first to determine what is to be executed.
This fight too easy? In the next section we will spice up the fight. P.S.: You'll notice that one of the two Strikers is called a [[Comguy]]. There's no guarantee that Bungie West labeled their AIs correctly. Trust no one.


Below that line, type '''{'''. This tells the game that the function has started.
==Giving characters weapons==
Giving characters weapons is very easy: simply use the command <tt>[[chr_giveweapon]]</tt>. See [[BSL:Inventory management|HERE]] for the list of weapon names. Let's go with <tt>w3_phr</tt>, the plasma rifle. Give the two Strikers this weapon by adding the commands <tt>chr_giveweapon Top_Striker_1 w3_phr</tt> and <tt>chr_giveweapon Top_Comguy_1 w3_phr</tt> to the main() function. Try out the fight again.


Add a space, and then type <tt>chr_teleport 0 7010</tt>. This function teleports Konoko (her id is always 0) to the flag 7010. It is located in the final training room.
==Spawning weapons on the ground==
Spawning weapons is done with the command <tt>[[weapon_spawn]]</tt>. Change your previous <tt>chr_giveweapon</tt> commands into two <tt>[[weapon_spawn]]</tt> commands in order to spawn plasma rifles at flags 7008 and 7009. You should end up with the commands <tt>weapon_spawn w3_phr 7008</tt> and <tt>weapon_spawn w3_phr 7009</tt>. This will place plasma rifles at each Striker's feet. Each spawned weapon comes with a full clip. An AI will often pick up a weapon with ammo or a weapon which they have ammo for. Try out the fight. You may need to coax the AIs to run past the weapons in order for them to pick them up.


Finish the function "main" by typing '''}''' on the next line.
==Preventing scripting overload==
Before we continue, we have a boring matter of some housekeeping to attend to. There are trigger volumes in this room which can fire events during training: one is for supplying the player with fresh ammo if they run out and one is for monitoring whether the glass targets are shot out. Normally the level's scripting will respond to and then disable these "TV"s, but because we wiped out that level scripting, the TVs will continually fire until they overwhelm the scripting system. This is marked by the message "FATAL SCRIPTING ERROR: exceeded maximum scripting parameter base size of 32768" which is visible when [[Developer Mode]] is active. If you have Dev Mode enabled (through the [[Daodan DLL]]), press F1 to bring up the Comlink screen and type 'x' or "thedayismine" to enable Dev Mode, then play the level for about half a minute to see this message appear. This overload doesn't matter for this script yet because it runs all its commands at level start, but for a normal level script which plays out over time, it would disable all commands issued after the overload occurs. To fix this, call <tt>[[trigvolume_enable]] tv74 0</tt> and <tt>trigvolume_enable tv75 0</tt>.


It should look like this:
==Giving characters powerups==
Giving characters powerups is much like giving characters weapons. Use the command <tt>[[chr_givepowerup]]</tt> to give a character a powerup.


<tt>func void main(void)<br>
The powerups you can give a character are:
{<br>
* <tt>shield</tt>: Gives a force shield
chr_teleport 0 7010<br>
* <tt>invis</tt>: Gives a phase cloak
}</tt>
* <tt>ammo</tt>: Gives a ballistic ammo clip
* <tt>cell</tt>: Gives an energy cell


Use this command to grant an energy cell to each Striker. You should end up with the commands <tt>chr_givepowerup Top_Striker_1 cell 1</tt> and <tt>chr_givepowerup Top_Comguy_1 cell 1</tt>. This will allow them to reload their weapons once. To review, your script should now say:
func void main(void)
{
  chr_teleport 0 7045
  ai2_spawn Top_Striker_1
  ai2_spawn Top_Comguy_1
  chr_teleport Top_Striker_1 7008
  chr_teleport Top_Comguy_1 7009
  weapon_spawn w3_phr 7008
  weapon_spawn w3_phr 7009
  trigvolume_enable tv74 0
  trigvolume_enable tv75 0
  chr_givepowerup Top_Striker_1 cell 1
  chr_givepowerup Top_Comguy_1 cell 1
}


Save the file, load up Oni, and click "New Game". You should be standing in the center of the final combat training room.
==Spawning powerups on the ground==
Spawning powerups is easy with the command <tt>[[powerup_spawn]]</tt>. Add the commands <tt>powerup_spawn invis 7008</tt> and <tt>powerup_spawn hypo 7009</tt>. This command will spawn a phase cloak and a hypo at the Strikers' feet, but since AIs will not pick up powerups, these are for you only.


You are now on your way!
==Adding an ally==
Adding extra characters to the script can be done in the same way you added the Strikers. You can see all the AI names for this level [http://ssg.oni2.net/subfold/charas/l00_01.htm here]. Look up the name for Griffin in this level (he's available because he appears in Chapter 1's opening cutscene) and spawn him at flag 7007. You should end up with <tt>ai2_spawn griffin</tt> and <tt>chr_teleport griffin 7007</tt>. Your boss will now assist you in the fight against the armed Strikers.


==Making an enemy==
Look back at ssg's table to see what teams the AIs are on. Most of the time, adding enemies simply involve spawning and teleporting the AI to your location, and as long as its team is "Syndicate" it will be an enemy. If you want to make an enemy from someone who would normally be on an allied team, use the <tt>[[chr_changeteam]]</tt> command to change their team to Syndicate like this:


----
chr_changeteam griffin Syndicate


== Part 5: Adding NPCs (AI) ==
The same principle would work in reverse if you spawned an AI who's on team Syndicate and changed their team to "TCTF" or "Konoko" with this command.


Before we begin, notice that there are two types of AI. AI created from character objects, and AI created from memory. AI created from character objects are created by the function call <tt>ai2_spawn (name)</tt>, where (name) is the name of the AI. AI created from memory are created by the function call <tt>chr_create (ai_number) start,</tt>. These AI do not have combat logic, and thus will not attack you physically but can still use weapons.
==Overpower==
The fight is becoming too slanted against Konoko; let's give her a helping hand. We can use the <tt>[[chr_set_health]]</tt> command to set a character's health points (HP). Note that each character has a base health that is unique to them. For example, Konoko's base HP is 200 while a green Striker's might be only 50. Keep this in mind when setting characters' health. Setting it over their base HP will work like using a hypo at full health. A character in an "overhealth" state will lose 1 HP every 10 ticks (1/6 of a second), whether they're the player character or an AI. However only Konoko and Muro get stronger and glow while they have overhealth. Let's set Konoko's health to 400 so she enters overpower mode: <tt>chr_set_health 0 400</tt>.


Now, we are going to add 2 enemy AI to our script that we wrote in the last section. Open it up, and lets add some enemies for Konoko to fight.
Let's recap. If you've added everything above, you should have:
 
  func void main(void)
To recap, it should look like this :
{
  func void main(void){
    chr_teleport 0 7045
    chr_teleport 0 7010
    ai2_spawn Top_Striker_1
    ai2_spawn Top_Comguy_1
    chr_teleport Top_Striker_1 7008
    chr_teleport Top_Comguy_1 7009
    weapon_spawn w3_phr 7008
    weapon_spawn w3_phr 7009
    trigvolume_enable tv74 0
    trigvolume_enable tv75 0
    chr_givepowerup Top_Striker_1 cell 1
    chr_givepowerup Top_Comguy_1 cell 1
    powerup_spawn invis 7008
    powerup_spawn hypo 7009
    ai2_spawn griffin
    chr_teleport griffin 7007
    chr_changeteam griffin Syndicate
    chr_set_health 0 400
  }
  }
After the function call <tt>chr_teleport 0 7010</tt>, add the following 2 commands on separate lines: <tt>ai2_spawn Top_Striker_1</tt> and <tt>ai2_spawn Top_Comguy_1</tt>. These two commands tell the game to create 2 characters: '''Top_Striker_1''', and '''Top_Comguy_1'''.


Since that these characters do not spawn in the room, they must be teleported there. Using the <tt>chr_teleport</tt> command used earlier to teleport yourself, modify it to teleport the 2 AI using their names instead of "0". Teleport them to the flags "7008" and "7009" respectively. Refer to the example above or the list of function calls above to write this in.
==Delays==
To wait for a fixed amount of time before executing a command, use <tt>[[sleep]]</tt>, which takes an argument in ticks (1/60 of a second). Add this to wait for 5 seconds before spawning a training drone:


It should now look like this :
sleep 300
  func void main(void) {
  ai2_spawn dum_train_block
    chr_teleport 0 7010
chr_teleport dum_train_block 7010
    ai2_spawn Top_Striker_1
    ai2_spawn Top_Comguy_1
    chr_teleport Top_Striker_1 7008
    chr_teleport Top_Comguy_1 7009
}
This script will now teleport you into the final training room and face you off against 2 Strikers.


This fight too easy? In the next section, we will learn how to spice up the fight, and let you develop your own script.
You can also wait for a character's health to reach a certain target before proceeding. Change your <tt>sleep</tt> command to this <tt>[[chr_wait_health]]</tt> command in order to wait for the first Striker to die before spawning the drone:


chr_wait_health Top_Comguy_1 0
ai2_spawn dum_train_block
chr_teleport dum_train_block 7010


----
==Separate functions==
It's bad practice to put all your level scripting code in main(). Using multiple functions is not only clearer but allows you to run different code in parallel. Create a new function called "start_fight" and move all the code from main() into start_fight(). Then call start_fight() from main(). It should look like this:


== Part 6: Customization==
func void main(void)
{
    start_fight
}
func start_fight
{
    ...
}


Here, you will be able to split off and customize your script.
…with "..." being whatever you had in main() before.
Each subsection will represent a way to customize the above script.


===Weapons and Powerups===
==Forking==
These are the easiest things to add into a battle.
Now that you get the basic idea of functions, we can use multiple functions to run code in parallel. Change tutorial.bsl to read:


Weapons can be spawned on the ground or given to characters.
  func void main(void)
 
{
====Giving Characters Weapons====
    start_fight
 
    fork spawn_dummy
Giving characters weapons is very easy. Simply use the command <tt>chr_giveweapon</tt> to give a character a weapon.
    sound_music_start mus_ot 0.75
 
    fork kill_music
[[BSL:Inventory|Here]] are the list of the weapon codes. For the purpose of this example, we will use <tt>w3_phr</tt>, the plasma rifle. This command will give the two strikers plasma rifles.
}
 
Add the commands <tt>chr_giveweapon Top_Striker w3_phr</tt> and <tt>chr_giveweapon Top_Comguy w3_phr</tt> on separate lines beneath what you have already. Be sure to add it ''BEFORE'' the '''}''' or it will not execute.
func start_fight
 
{
====Spawning Weapons on the ground====
    chr_teleport 0 7045
 
   
Spawning weapons is simple. Use the command <tt>weapon_spawn</tt> to spawn a weapon.
    ai2_spawn Top_Striker_1
 
    ai2_spawn Top_Comguy_1
Add the commands <tt>weapon_spawn w3_phr 7008</tt> and <tt>weapon_spawn w3_phr 7009</tt> on separate lines beneath what you have already. Be sure to add it ''BEFORE'' the '''}''' or it will not execute. This command will spawn 2 plasma rifles, one under each striker's feet. AI will automatically pick up weapons with ammo or empty weapons they have ammo for.
   
 
    chr_teleport Top_Striker_1 7008
====Giving Characters Powerups====
    chr_teleport Top_Comguy_1 7009
 
Giving characters powerups is much like giving characters weapons. Use the command <tt>chr_givepowerup</tt> to give a character a powerup.
 
The powerups you can give a character are :
* shield : Gives a force shield
* invis : Gives a phase cloak
* ammo : Gives a ballistic ammo
* cell : Gives an energy cell.
 
For the purpose of this example, we will give the characters invisibilities.
 
Add the commands <tt>chr_givepowerup Top_Striker invis -1</tt> and <tt>chr_givepowerup Top_Comguy invis -1</tt> on separate lines beneath what you have already. Be sure to add it ''BEFORE'' the '''}''' or it will not execute. This command will make the strikers permanently invisible. To make then visible after 30 seconds, erase "-1".
 
====Spawning Powerups on the Ground====
 
Spawning powerups are simple. Use the command <tt>powerup_spawn</tt> to spawn a powerup.
 
Add the commands <tt>powerup_spawn invis 7008</tt> and <tt>powerup_spawn invis 7009</tt> on separate lines beneath what you have already. Be sure to add it ''BEFORE'' the '''}''' or it will not execute. This command will spawn 2 phase cloaks beneath the striker's feet. Since AI will not pick up powerups, this is for you only.
 
===Extra Characters===
 
Adding extra characters to the script is easy. Just follow the steps for adding the first AI and follow the procedure, look for AI names [[BSL:AI names|here]].
(Wiki page not here yet. Use [http://ssg.oni2.net/subfold/charas/charas.htm ssg's tables] instead.)  
 
====Adding Enemies====
 
Look at the chart for what teams the AI are on. Most of the time, adding enemies simply involve spawning and teleporting the AI. As long as the team in "Syndicate", it will be an enemy. If you want to add an emeny from an allied team, use the <tt>chr_changeteam</tt> command to changet their team to "Syndicate" like this:
 
<tt>chr_changeteam griffin Syndicate</tt>
 
====Adding Allies====
 
Look at the chart for what teams the AI are on. As long as the team is "TCTF" or "Konoko", it will be an ally for you in this script. If you want to add an ally from the enemy team, use the <tt>chr_changeteam</tt> command to changet their team to "TCTF" or "Konoko" like this:
 
<tt>chr_changeteam WH_Striker_D TCTF</tt>
 
===Overpower===
 
Use the <tt>chr_set_health</tt> command to set a character's health. Note that each character has a base health that may not be the same. For example, konoko's base health is 200, while a brown striker's is only 50. Keep this in mind when setting character's health. Setting it over their base health will send them into overpower - like using a hypo at full health. The health will drain at 1 every second even for AI.
*Attention! Attention! If ''that'' was true, Konoko's overpower would last 200 seconds : over 3 minutes. Which it doesn't.
*Then is it 1 point per frame = 200 frames ?
This example sets konoko's health to 400 - overpower.
  chr_set_health 0 400
 
 
This following example shows everything listed above added.
  func void main(void) {
    chr_teleport 0 7010
    powerup_spawn hypo 7010
   
   
    ai2_spawn Top_Striker_1
    weapon_spawn w3_phr 7008
    ai2_spawn Top_Comguy_1
    weapon_spawn w3_phr 7009
   
   
    chr_teleport Top_Striker_1 7008
    trigvolume_enable tv74 0
    chr_teleport Top_Comguy_1 7009
    trigvolume_enable tv75 0
   
   
    chr_givepowerup Top_Striker_1 invis -1
    chr_givepowerup Top_Striker_1 cell 1
    chr_givepowerup Top_Comguy_1 invis -1
    chr_givepowerup Top_Comguy_1 cell 1
    powerup_spawn invis 7007
    powerup_spawn invis 7009
   
   
    chr_giveweapon Top_Striker_1 w7_scc
    powerup_spawn invis 7008
    chr_giveweapon Top_Comguy_1 w3_phr
    powerup_spawn hypo 7009
    chr_givepowerup Top_Striker_1 ammo 15
    chr_givepowerup Top_Comguy_1 cell 30
   
   
    weapon_spawn w2_sap 7045
    ai2_spawn griffin
    powerup_spawn ammo 7008
    chr_teleport griffin 7007
    chr_changeteam griffin Syndicate
   
   
    ai2_spawn OutroTCTF01
    chr_set_health 0 400
    chr_teleport OutroTCTF01 7007
}
   
   
    ai2_spawn WH_Thug_A
func spawn_dummy
    chr_changeteam WH_Thug_A TCTF
{
    chr_teleport WH_Thug_A 7007
    chr_wait_health Top_Comguy_1 0
    ai2_spawn dum_train_block
    chr_teleport dum_train_block 7010
}
   
   
    ai2_spawn griffin
func kill_music
    chr_changeteam griffin Syndicate
{
    chr_teleport griffin 7008
    chr_wait_health Top_Striker_1 0
    chr_set_health griffin 3900
    chr_wait_health Top_Comguy_1 0
    chr_wait_health dum_train_block 0
    chr_wait_health griffin 0
    sound_music_volume mus_ot 0 5.0
  }
  }


First main() calls start_fight(), which runs in its entirety before returning control to main(), and then main() forks spawn_dummy(), which waits until one Striker is dead before spawning the training dummy.


----
So far there's nothing different from how the script ran before, but note the third line in main(), which calls [[sound_music_start]]. When you play the level, the music begins immediately. If we had called spawn_dummy() without "fork", we would be stuck waiting for the dummy to spawn before control returned to main() and it could play the music.
 
== Part 7: Delay ==
 
<tt>sleep</tt><br>
<tt>chr_wait_health</tt>
 
To wait for some time before executing a command, use <tt>sleep</tt>. To wait for approx 5 seconds before spawning an extra drone, go like this:
 
<tt>sleep 300<br>
ai2_spawn dum_train_block<br>
chr_teleport dum_train_block 7010<br>
</tt>
 
To wait for a striker to die and then spawn some drones, go like this:
 
<tt>chr_wait_health Top_Striker 0<br>
ai2_spawn dum_train_block<br>
chr_teleport dum_train_block 7010<br></tt>
 
 
== Part 8: Forking and Separate Functions==
 
This is the part where we start moving commands out of "main". Create a new function called "logic" like this : type "func logic" two lines below the '''}''' of "main". Type '''{''' and copy out everything inside "main" into "logic". Type <tt>fork logic</tt> in main. It should look like this :
 
<tt>func void main(void)<br>
{<br>
fork logic<br>
}<br>
<br>
func logic<br>
{<br>
...<br>
}</tt>
 
... being whatever you had in "main" before. Add <tt>chr_wait_health griffin 0</tt> at the end of "logic", then type <tt>fork logic2</tt>. Create the function "logic2". In "logic2" you can write whatever you want that you have learned before. For the purpose of this example, '''...''' will represent whatever you had, '''..''' will represent whatever you want, and typed text will represent thing that must be included.
 
<tt>func void main(void)<br>
{<br>
fork logic<br>
}<br>
<br>
func logic<br>
{<br>
...<br>
chr_wait_health griffin 0<br>
fork logic2<br>
}<br>
<br>
func logic2 {<br>
..<br>
}</tt>
 
===Extra: Multiple Forking===
 
More than one function can be forked in the same function. Look at ths example :
 
<tt>func void main(void)<br>
{<br>
fork logicA<br>
fork logicB<br>
}<br>
 
func logicA {<br>
..<br>
}<br>
 
func logicB {<br>
..<br>}</tt>
 
Both "logicA" and "logicB" are executed at the same time. If <tt>chr_wait_health griffin 0</tt> were to be removed, likewise, both "logic" and "logic2" are executed at the same time.
 
== Part 9: Variables and Save Points ==
 
Ever wondered why there is no folder for basic training? The truth is, it is actually part of the Warehouse level and is managed by save points.
 
This is the beginning of the "level_start" which is forked from "main" in the original level scripts.
 
<tt>func void level_start(string ai_name)<br>
{<br>
<br>
<br>
    if (save_point eq 0)<br>
    {    <br>
        ...<br>
    }<br>
<br>
    if (save_point eq 1)<br>
    {<br>
        ...<br>
    }<br>
<br>
}</tt>
 
Using forking, you can link save points to functions. Like so :
 
<tt>func void main(void)<br>
{<br>
<br>
    if (save_point eq 0)<br>
    {    <br>
        fork logic<br>
    }<br>
<br>
    if (save_point eq 1)<br>
    {<br>
        fork logic2<br>
    }<br>
<br>
}</tt>
 
When save point 0 is loaded (TCTF Training), the function "logic" is executed. If save point 1 is loaded (Syndicate Warehouse), "logic2" is executed. Note that in Warehouse, the save point number in the script is one higher than in the level load list, because "TCTF Training" is SP0.


After that, we fork one more function, kill_music(), which waits for all four characters to be defeated before fading out the music. We could have simply called this function without "fork" since it's the last line of code in main(), but forking it is good practice so that more code can be added to main() in the future without needing to remember to add "fork" to this line.


==Save points==
As a final note, remember how TCTF Training and Syndicate Warehouse are the same level, just separated by save point? If you look back at the original main() function that was in warehouse_main.bsl, its last line called the function level_start(). You'll find this function in warehouse_level_scripts.bsl. This function runs whenever a save point from either Training or Warehouse is loaded.


func void level_start(string ai_name)
{
    if (save_point eq 0)
    {
      ...
    }
    if (save_point eq 1)
    {
      ...
    }
    ...
}


<tt>[[save_point]]</tt> is a built-in variable that can be read but not set, and which tells you the save point that the player loaded, so you can set up your level accordingly (clicking on the name of the level in Load Game registers as save point 0). To record the player reaching a save point in your level, you call <tt>[[save_game]] ''n''</tt>, where 'n' is 1-10.


                                                              - Your_Mom
[[Category:Modding tutorials]]

Revision as of 02:14, 6 May 2023

This page is a short tutorial for those who have no experience with scripting on how to get started creating a new level script. It's recommended that you read the BSL:Introduction page first to get a grasp of BSL's syntax. Each significant command in this tutorial should have a link behind it the first time it's referenced; click that link to learn more about the command. For a complete list of commands, see BSL:Functions and BSL:Variables.

The "main" function

Open up your IGMD folder and look for EnvWarehouse/. Inside this folder you should see seven .bsl files. Using a plain-text editor (such as Notepad or Notepad++ in Windows, and TextEdit or BBEdit in macOS), open the file warehouse_main.bsl. It should look like this:

#
# warehouse_main.bsl
#

var int my_save_point;

func void main(void)
{
    env_show 2010 0
    gl_fog_blue=.15
    gl_fog_red=.15
    gl_fog_green=.15
    gl_fog_start=.99
    gs_farclipplane_set 5000
    obj_create 20 20
    level_start
}

This is an example of a level's main script. It contains the main() function, which is always where the level starts. The script file can technically be called anything, but there must be a function called "main" in one of the level's script files. Oni will always look for and start with this function when a level loads.

Creating a script

Close warehouse_main.bsl if you still have it open, and rename the containing folder "EnvWarehouse-original". Create a new folder called "EnvWarehouse" and create a new plain-text file inside it called "tutorial.bsl".

First we're just going to place you in the final room of TCTF Training where you practice firing weapons. The room will be empty since no enemies have been spawned by the script. In case you're wondering how you could be in TCTF Training when this is a script inside EnvWarehouse/, be aware that CHAPTER 00 . COMBAT TRAINING is actually part of the same level data files as CHAPTER 01 . TRIAL RUN and only appears to be separate in the Load Game menu (this is the only time Oni combines two levels into one set of files).

Are you editing the file tutorial.bsl? Good. First, declare the "main" function for the level by typing "func void main(void)" at the very beginning of the file. Inside of curly braces, type this one command: chr_teleport 0 7045. This function call will teleport Konoko (her ID is always 0) to flag 7045, which is located in the final training room.

The file's contents should look like this:

func void main(void)
{
   chr_teleport 0 7045
}

Save the file, open up Oni and click "New Game" (you can also click "Load Game" and choose "TCTF Training" or any save point for "Syndicate Warehouse" and get the same result). You should be standing on the desk in the final training room. Now to start populating the level!

Adding NPCs (AIs)

Before we begin, notice that there are two types of AIs: AIs created from character resources (CHAR) and AIs created at runtime. AIs stored in CHARs are spawned by the function call ai2_spawn name, where "name" is the name of the AI. AIs can be created at runtime by the function call chr_create ai_number start. These AIs do not have combat logic (MELE) and thus will not attack you physically, but can still use weapons. Below we will only work with CHAR AIs since they are already set up and ready to use.

Now we are going to add 2 enemy AIs to our script that we started in the last section. Open it up, and let's add some enemies for Konoko to fight.

To recap, it should look like this:

func void main(void)
{
    chr_teleport 0 7045
}

After the call to chr_teleport add the following two commands on separate lines: ai2_spawn Top_Striker_1 and ai2_spawn Top_Comguy_1. These commands tell the game to spawn two characters already named in the game data, Top_Striker_1 and Top_Comguy_1.

Since these characters do not spawn in the final room they must be teleported there. This time we'll use the chr_teleport command to teleport the 2 AIs, and we'll use their names to identify them instead of numeric IDs. Teleport them to the flags "7008" and "7009" respectively. When you're done, main() should now look like this:

func void main(void)
{
   chr_teleport 0 7045

   ai2_spawn Top_Striker_1
   ai2_spawn Top_Comguy_1

   chr_teleport Top_Striker_1 7008
   chr_teleport Top_Comguy_1 7009
}

The script will now teleport you into the final training room and face you off against two Strikers. Try it out.

This fight too easy? In the next section we will spice up the fight. P.S.: You'll notice that one of the two Strikers is called a Comguy. There's no guarantee that Bungie West labeled their AIs correctly. Trust no one.

Giving characters weapons

Giving characters weapons is very easy: simply use the command chr_giveweapon. See HERE for the list of weapon names. Let's go with w3_phr, the plasma rifle. Give the two Strikers this weapon by adding the commands chr_giveweapon Top_Striker_1 w3_phr and chr_giveweapon Top_Comguy_1 w3_phr to the main() function. Try out the fight again.

Spawning weapons on the ground

Spawning weapons is done with the command weapon_spawn. Change your previous chr_giveweapon commands into two weapon_spawn commands in order to spawn plasma rifles at flags 7008 and 7009. You should end up with the commands weapon_spawn w3_phr 7008 and weapon_spawn w3_phr 7009. This will place plasma rifles at each Striker's feet. Each spawned weapon comes with a full clip. An AI will often pick up a weapon with ammo or a weapon which they have ammo for. Try out the fight. You may need to coax the AIs to run past the weapons in order for them to pick them up.

Preventing scripting overload

Before we continue, we have a boring matter of some housekeeping to attend to. There are trigger volumes in this room which can fire events during training: one is for supplying the player with fresh ammo if they run out and one is for monitoring whether the glass targets are shot out. Normally the level's scripting will respond to and then disable these "TV"s, but because we wiped out that level scripting, the TVs will continually fire until they overwhelm the scripting system. This is marked by the message "FATAL SCRIPTING ERROR: exceeded maximum scripting parameter base size of 32768" which is visible when Developer Mode is active. If you have Dev Mode enabled (through the Daodan DLL), press F1 to bring up the Comlink screen and type 'x' or "thedayismine" to enable Dev Mode, then play the level for about half a minute to see this message appear. This overload doesn't matter for this script yet because it runs all its commands at level start, but for a normal level script which plays out over time, it would disable all commands issued after the overload occurs. To fix this, call trigvolume_enable tv74 0 and trigvolume_enable tv75 0.

Giving characters powerups

Giving characters powerups is much like giving characters weapons. Use the command chr_givepowerup to give a character a powerup.

The powerups you can give a character are:

  • shield: Gives a force shield
  • invis: Gives a phase cloak
  • ammo: Gives a ballistic ammo clip
  • cell: Gives an energy cell

Use this command to grant an energy cell to each Striker. You should end up with the commands chr_givepowerup Top_Striker_1 cell 1 and chr_givepowerup Top_Comguy_1 cell 1. This will allow them to reload their weapons once. To review, your script should now say:

func void main(void)
{
  chr_teleport 0 7045

  ai2_spawn Top_Striker_1
  ai2_spawn Top_Comguy_1

  chr_teleport Top_Striker_1 7008
  chr_teleport Top_Comguy_1 7009

  weapon_spawn w3_phr 7008
  weapon_spawn w3_phr 7009

  trigvolume_enable tv74 0
  trigvolume_enable tv75 0

  chr_givepowerup Top_Striker_1 cell 1
  chr_givepowerup Top_Comguy_1 cell 1
}

Spawning powerups on the ground

Spawning powerups is easy with the command powerup_spawn. Add the commands powerup_spawn invis 7008 and powerup_spawn hypo 7009. This command will spawn a phase cloak and a hypo at the Strikers' feet, but since AIs will not pick up powerups, these are for you only.

Adding an ally

Adding extra characters to the script can be done in the same way you added the Strikers. You can see all the AI names for this level here. Look up the name for Griffin in this level (he's available because he appears in Chapter 1's opening cutscene) and spawn him at flag 7007. You should end up with ai2_spawn griffin and chr_teleport griffin 7007. Your boss will now assist you in the fight against the armed Strikers.

Making an enemy

Look back at ssg's table to see what teams the AIs are on. Most of the time, adding enemies simply involve spawning and teleporting the AI to your location, and as long as its team is "Syndicate" it will be an enemy. If you want to make an enemy from someone who would normally be on an allied team, use the chr_changeteam command to change their team to Syndicate like this:

chr_changeteam griffin Syndicate

The same principle would work in reverse if you spawned an AI who's on team Syndicate and changed their team to "TCTF" or "Konoko" with this command.

Overpower

The fight is becoming too slanted against Konoko; let's give her a helping hand. We can use the chr_set_health command to set a character's health points (HP). Note that each character has a base health that is unique to them. For example, Konoko's base HP is 200 while a green Striker's might be only 50. Keep this in mind when setting characters' health. Setting it over their base HP will work like using a hypo at full health. A character in an "overhealth" state will lose 1 HP every 10 ticks (1/6 of a second), whether they're the player character or an AI. However only Konoko and Muro get stronger and glow while they have overhealth. Let's set Konoko's health to 400 so she enters overpower mode: chr_set_health 0 400.

Let's recap. If you've added everything above, you should have:

func void main(void)
{
   chr_teleport 0 7045

   ai2_spawn Top_Striker_1
   ai2_spawn Top_Comguy_1

   chr_teleport Top_Striker_1 7008
   chr_teleport Top_Comguy_1 7009

   weapon_spawn w3_phr 7008
   weapon_spawn w3_phr 7009

   trigvolume_enable tv74 0
   trigvolume_enable tv75 0

   chr_givepowerup Top_Striker_1 cell 1
   chr_givepowerup Top_Comguy_1 cell 1

   powerup_spawn invis 7008
   powerup_spawn hypo 7009

   ai2_spawn griffin
   chr_teleport griffin 7007
   chr_changeteam griffin Syndicate

   chr_set_health 0 400
}

Delays

To wait for a fixed amount of time before executing a command, use sleep, which takes an argument in ticks (1/60 of a second). Add this to wait for 5 seconds before spawning a training drone:

sleep 300
ai2_spawn dum_train_block
chr_teleport dum_train_block 7010

You can also wait for a character's health to reach a certain target before proceeding. Change your sleep command to this chr_wait_health command in order to wait for the first Striker to die before spawning the drone:

chr_wait_health Top_Comguy_1 0
ai2_spawn dum_train_block
chr_teleport dum_train_block 7010

Separate functions

It's bad practice to put all your level scripting code in main(). Using multiple functions is not only clearer but allows you to run different code in parallel. Create a new function called "start_fight" and move all the code from main() into start_fight(). Then call start_fight() from main(). It should look like this:

func void main(void)
{
   start_fight
}

func start_fight
{
   ...
}

…with "..." being whatever you had in main() before.

Forking

Now that you get the basic idea of functions, we can use multiple functions to run code in parallel. Change tutorial.bsl to read:

func void main(void)
{
   start_fight
   fork spawn_dummy
   sound_music_start mus_ot 0.75
   fork kill_music
}

func start_fight
{
   chr_teleport 0 7045

   ai2_spawn Top_Striker_1
   ai2_spawn Top_Comguy_1

   chr_teleport Top_Striker_1 7008
   chr_teleport Top_Comguy_1 7009

   weapon_spawn w3_phr 7008
   weapon_spawn w3_phr 7009

   trigvolume_enable tv74 0
   trigvolume_enable tv75 0

   chr_givepowerup Top_Striker_1 cell 1
   chr_givepowerup Top_Comguy_1 cell 1

   powerup_spawn invis 7008
   powerup_spawn hypo 7009

   ai2_spawn griffin
   chr_teleport griffin 7007
   chr_changeteam griffin Syndicate

   chr_set_health 0 400
}

func spawn_dummy
{
   chr_wait_health Top_Comguy_1 0
   ai2_spawn dum_train_block
   chr_teleport dum_train_block 7010
}

func kill_music
{
   chr_wait_health Top_Striker_1 0
   chr_wait_health Top_Comguy_1 0
   chr_wait_health dum_train_block 0
   chr_wait_health griffin 0
   sound_music_volume mus_ot 0 5.0
}

First main() calls start_fight(), which runs in its entirety before returning control to main(), and then main() forks spawn_dummy(), which waits until one Striker is dead before spawning the training dummy.

So far there's nothing different from how the script ran before, but note the third line in main(), which calls sound_music_start. When you play the level, the music begins immediately. If we had called spawn_dummy() without "fork", we would be stuck waiting for the dummy to spawn before control returned to main() and it could play the music.

After that, we fork one more function, kill_music(), which waits for all four characters to be defeated before fading out the music. We could have simply called this function without "fork" since it's the last line of code in main(), but forking it is good practice so that more code can be added to main() in the future without needing to remember to add "fork" to this line.

Save points

As a final note, remember how TCTF Training and Syndicate Warehouse are the same level, just separated by save point? If you look back at the original main() function that was in warehouse_main.bsl, its last line called the function level_start(). You'll find this function in warehouse_level_scripts.bsl. This function runs whenever a save point from either Training or Warehouse is loaded.

func void level_start(string ai_name)
{
   if (save_point eq 0)
   {
      ...
   }

   if (save_point eq 1)
   {
      ...
   }

   ...
}

save_point is a built-in variable that can be read but not set, and which tells you the save point that the player loaded, so you can set up your level accordingly (clicking on the name of the level in Load Game registers as save point 0). To record the player reaching a save point in your level, you call save_game n, where 'n' is 1-10.