Instantly share code, notes, and snippets.

Embed
What would you like to do?
Third Gen Scripting "Totally not a crap guide"
DISCLAIMER: Yes you dont need a begin but teaching people to follow it stops them getting lost in bungie scripts so deal with it.
DISCLAIMER: Halo CE scripters eat yer hearts out and ree some more.
DISCLAIMER: Do not trust the assembly decompiler, what it spits out is not the full story. It crashes if a single exprssion is invalid for any reason.
SUPER DUPER DISCLAIMER: I don't claim to know everything. What I know has been based on a few weeks during 2017 of nonstop face smashing keyboard until I worked it out.
Define the things:
`Type 8 Group` = These do exactly what you think, they group expressions together and are able to RETURN data based on their `Value Type`
`Type 9 Function` = Uses the `Opcode` to do cool stuff!
`Opcode` = Our desired function for an expression. -->I will be useing MS23 opcodes from Halo Online.<--
`NEI` = Next Expression Index, this has multiple names but we will use how assembly has them named.
`Salt` = The typical overwatch player. Fucking useless for what we are doing. But seriously we dont need to bother with it.
`Data` = This can be many things, from expression indexes to string table offsets. Lots 'o' stuff. Getting this wrong can be a massive pain.
(NOTE: Data can differ between engine versions, eg float32 data for a function may become an int8 in halo online. Check your plugins.)
`Argument` = Many functions require expressions to use as argument data. How can physics_set_gravity set the gravity without a value!
LINKS:
MS23 Opcodes straight from tagtool
https://docs.google.com/spreadsheets/d/1AUmVEytkVmwd0tU2WlnRZfra9zcZlPlOmi1-FQLmDxY/
Snipestyle's Halo Reach research doc (Papa bless)
https://docs.google.com/document/d/1pTGzMOSNCZcf2OiQauBFok1Gx-1El1UKJZx0FzXkyN8/
BEGIN HERE:
All scripting is done by editing the "Scrips" & "Script Expressions" blocks in the relevent scenario tag. You can use assembly to preview blam scripts but not edit them. That is why is guide exists as it is not a trival task to the unfamiliar, but it wont take long to get your head around it as a whole. The definitions above are going to be important to understand how to manuall edit and create scripts so please have a good read. Have the relevent opcode sheets ready for the version of engine that you are scriping for. These can be found in your assembly directory and in tagtool's source. If you preview a script with invalid expressions, depending on your build, assembly will either crash or not display anything. You will need a hexeditor for some of the more advanced scripting, I would recomend Hex Workshop for specifically this but HxD or any other editor will work just fine. Dont use the Notepad++ hex editor plugin. Its garbage and cannot handle the file sizes you will be dealing with.
This guide covers using assembly to do this, but tagtool works fine too.
This applies to: Halo 3, Halo 3 ODST, Halo Reach, Halo 4 and all builds of Halo Online.
Theory behind manual scripting:
Do not follow this 1 to 1 in real time. Just read and understand, use it as a guide when you are ready.
We can actually skip the `begin` but we are going to learn how to read/write them as it teaches you a few things you will want to know.
We create a new script by adding a new script block in scnr. Name it what you would like, does not matter. Set its return type to void, we are returning no data but
you will be returning data on more advanced scripts and treating scripts as if they were functions to be used in other scripts. (This is really cool btw)
Set the inital expression to an empty expression block, expand the block if needed. A few hundread is a good idea.
DO NOT OVERWITE EXISTING EXPRESSIONS. You will regret it, unless you are like me and can butcher some 3 or 4 scripts into one because im lazy. Dont do this. :)
The first expression will type of `8(Group)`, a return type of void, an NEI of `65535` and no `DATA`. We will also set the value type as void, the reason for this is that the first group expression in your script is responsible for the value returned by the entire script. We are not returning any data so void is the correct choicece. You will need to follow the an expressions type 8's `data` to find the next sibling expression if you are hunting through one of bungies scripts.
So we currently have:
(script static void myScript
() <--- we only have one expression, a group.
)
So we have a group on its own, with no `DATA` to follow. Following data for ANY group expression will equal the next expression. So set the data of our group to our next expression with an opcode of `dec:0 ,"begin"` with an NEI of 65535. We now have:
(script static void myScript
(begin <--- Our function, inside our group. Gigg.
)
)
This is assuming the begin expression has an NEI of 65535, it doesnt in your script but assume this just for this part and thus would be the end of our script at this point. Right, so we have a group and a begin. Our next expression after this MUST be a expression with a type of `8` and an NEI of `65535` (This will be clear as to why later). We would now have this:
(script static void myScript
(begin
() <--- Our group with an NEI of 65535 that has NO DATA.
)
)
So we set our group expression's data to our first expression-function that will be on this line, ill use `dec:158,"physics_set_gravity"` with an NEI of 65535. This opcode DOES have an argument; it requires a `real` as a value. We will do this later but for now we have the following:
(script static void myScript
(begin
(physics_set_gravity) <--- Our group, with our function inside of it. Giggity.
)
)
Cool beans, we got our function but according to our OPCODES sheet it does require an argument, this is where things can get complicated if multiple arguments are require and is why you MUST understand how group expressions work. For example if we wanted to use a function to return somthing for another function, we would use a group with its value type set to what we want to return. Confused? Dont worry about it for now. Lets give our function a argument.
(script static void myScript
(begin
(physics_set_gravity 0.5) <--- Half the gravity with a real.
)
)
How would do this? Have a quick think.
"physics_set_gravity" requires a real value based on our opcode sheet.
Set the NEI to our next expression, our expression will be: Type 9, an opcode of (dec:4,real), value type set to real, NEI of 65535 and set our data to 0.5 (Dont worry about where to write the 0.5 real data yet we will cover that later). We dont need a group as we are returning a value from a function because our opcode and value type being set to real, meaning the engine will read the data and provide that data to our function. Fantastic, if we run this script it will half the gravity for the entire map.
So... say we wanted to add more stuff to our script? what would we change?
This is where people get confused: blam script uses nesting, so our expressions are also nested in a similar way. We actually need to go back to the expression responsible
for the group that contains the entire line in this script. The group we made before our physics_set_gravity function is responsible for telling the script engine what the next line is. We set the NEI for that group expressions to 65535 for a reason, we did not need any extra lines but now we want a new line to work on.
(script static void myScript
(begin
(physics_set_gravity 0.5)
) ^
) ^
^<--- We will change the NEI for this group expression to our next group expression.
As you can read above, this sounds a tad confusing. We change the NEI for the group expression responsible for that line to our next expression that has a type of 8, value type of void and an NEI of 65535. We would now have:
(script static void myScript
(begin
(physics_set_gravity 0.5)
() <--- Note the empty group, we have a new line!
)
)
We can now set the DATA to our new group expression to another expression with a type of 9 and from here on out the sky is the limit. You can do this as many times as you need. Adding new lines, giving that line a function and giving that function and argument. So just to recap:
[1] (script static void myScript
[2,3] (begin
[4,5,6,7] (physics_set_gravity 0.5)
(sleep 300)
(physics_set_gravity 0.2)
...
... <--- And so our script goes on until complete
...
)
)
1. Set our scripts inital expression index to our first group expression, with a return type of void.
2. Create our scripts first group expression, with an NEI of 65535, value type of void as the script will return nothing and set its data to the next expression.
3. Create our begin function and set its NEI to our next lines group.
4. Create our group for the physics_set_gravity line, set nei 65535 until we finished the rest of this line. Set DATA to our next expression.
5. Create the function expression for physics_set_gravity, set nei to the next exprssion.
6. Create the last expression for this line, a real, set the relevent infomation for this expression.
7. Backtrack to 4. and set the NEI to our next group expression for out new line.
@. Thats it, Rinse repeat. When the script is finished set the last new line group expression NEI to 65535.
Expression data, how to:
[Unfinished]
Returning objects for a function, understanding group expressions for returning data. Nesting, Nesting, 1 2 3.
I have a trigger vollume. Inside this vollume I want any objects to be teleported out of it to a specific place when this script is ran.
I only want one of the potentially many objects inside teleported. We fulfill this in the following manor:
3. 2. 1. 4.
(object_teleport (list_get (volume_return_objects "myPtsdTriggerVollume") 0) "MyCutsceneFlag")
^ ^ ^ ^ ^ ^
^ ^ ^<---- Group, value type of object_list ---->^ ^ ^
^ ^ ^ ^
^ ^<-------- This group has a value type of object ---------^ ^
^ ^
^<---------- This group is for our entire line of expressions, value type of void ---------->^
`object_teleport` requires the following arguments: object, a cutscene flag (An index based on the "cut scene flags" tagblock in scnr)
1. The trigger vollume to check
2. volume_return_objects returns an object_list based on the specified trigger vollume.
3. list_get will get an object from an object_list based on 4.short number that is 0, meaning the first object in the list
We want only the first `object` that in the `object_list` that `volume_return_objects` provides us
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment