Before you get started with scripting, you'll need to understand how scripts work in UE2 (Unreal Engine 2), and how they interact with the game in real-time. As a pre-face before we get started, you should know a few things.
First off, scripting is NOT coding. Scripting is a trade-off for taking a lot of time to code something, so with that being said, you should expect to run into a few un-expected issues. You can't really avoid them, due to the nature of scripts, so you need to be ready to expect them. Debugging scripts is a common practice, in order to determine why your script isn't behaving in the way you've intended it to behave. As such, it's a good practice to do everything in the most simple way possible, and to make your scripts as readable as possible, in order for you to understand what's happening to the best of your ability.
Second off, if you want to get serious about modding this game, scripting is a must-learn! While you can get away with not coding for your projects, you can't really do that with scripting. Without learning how to script, you'll lack a lot of basic controls that you'll need as a proper mod developer. So, with that being said, let's get into the principles!
A script is an array of actions that are fired in order from first to last. When the list of actions is run through, the script is officially terminated, meaning it is now gone until the entire script is re-ran again. Under typical conditions, a script will start running the moment any map is loaded, or the moment the script is loaded into the game.
There are two types of scripts that you should learn:
AIScript - This scripting language can be used to make your own custom AI, or more commonly, it can be used for very intuitive script creations that can allow for the simple creation of custom logic for any map, without having to program! This language is the most powerful and the most simple.
CutScript - This scripting language was developed by KnowWonder, and is the language used for cutscenes you've likely already seen shown in-game many times. Cutscenes allow you to take your map to a whole new level, allowing you to create cinematic experiences with ease! This language is very good at cutscene creation, but does have a few odd exceptions, and isn't easy to write without the use of a cutscene creator.
Out of these two scripting languages, while AIScript is the easiest to learn, and the most intuitive, it does not mean you should slack on learning CutScript. However, don't worry about the order in which you'll learn these, because my personal suggestion would be to learn AIScript first in every case. The reason I recommend AIScript before CutScript, despite the fact the game never uses AIScript, is because it more-easily sets the foundation for how all scripting languages in UE2 typically behave, and it makes it a lot easier to understand how CutScript behaves once you understand how AIScript behaves.
AIScript follows a certain set of rules that you must fully understand before you can begin scripting. Here are the core rules:
The formatting for this language is as follows: Begin->Actions->Terminate. Begin is called when the script is initialized (loaded, or ran), Actions is the list of actions provided in the script, and once the final action is run, the script will Terminate. As explained in the "Core Principles" section, once the script runs Terminate, the script will have to be fully re-loaded in order to be run again. For AIScripts specifically, a script typically can't be re-loaded, however, it can be looped, making the script never-ending.
AIScripts are pre-defined inside a map's data file under typical circumstances, meaning that you cannot make an AIScript in real-time. You will have to modify the map's data file in order to change it.
Each action in the script can have 2 special types assigned to them. The first type is whether it is Latent or not. A Latent action is an action that makes the entire script Wait, until a condition is satisfied for its action. The second type is whether it is Conditional or not. If an action is Conditional, then it can return true or false. If it returns true, then the script will continue to the next action. If it returns false, it skips down the Actions list until it finds an action called ACTION_EndSection. The amount of ACTION_EndSection's in any AIScript must be equal to the amount of conditional actions. It's important to mention that while a Latent action will have a condition for when it will be considered finished, this should not be confused with a Conditional action, and in fact, an action can technically be both Latent and Conditional.
An AIScript is either Running, Waiting or Terminated. When a map first loads, if an AIScript is present, it will default to Running, and so the script will run before the map fully loads. It's recommended to make the first action in any script a latent action, so that the script is automatically put in the Waiting state, until the map is finished loading.
This language is natively single-threaded, meaning that if the script is waiting at any point, nothing else can occur within it. Despite this, nothing stops you from linking up AI scripts. Through this, this language can be considered multi-threaded.
An AIScript can either possess itself or another pawn. If you possess another pawn, then you will override all of their logic with the script's logic.
This language supports loops (only possible with delay/waiting), conditions, randomness, and custom logic.
Data cannot be stored in the script itself, however, if you use MPak, we can store data locally and globally, which can be interpreted by the script as data stored in the script.
In order to create your first AIScript, you'll need to place it into a map. Since we're just starting out, let's place down a ScriptedTrigger. You can find and place down a ScriptedTrigger from the tree on the right.
(Keypoint->AIScript->ScriptedTrigger)
Once you have that selected, place it into the level like you would with any other actor. However, unlike most triggers, keep in mind that this ScriptedTrigger actor is not actually a trigger. This means that you can't collide with it in any way, and as such, you should not treat it like a normal trigger. You can place these anywhere in a level.
Once placed, access its properties and look inside the AIScript property tab. As you can see, there is a variable called Actions. This variable is an array variable that will contain every action we need in our script. Ignore every other variable in this ScriptedTrigger for the time being. So, let's focus on the Actions variable for now.
Click on Actions, then click the Add button shown on the same line as the Actions variable. This will add a new action into the list of Actions. Once you've done this, click the new dropdown for Action 0, then click the down arrow to the left of the New button. As explained previously, we should use a latent action, so that the script will Wait until we need it to Run, which is good for almost every case. So, let's select the action ACTION_WaitForEvent. Once selected, click the New button as shown on the right. This will create a physical action for our Actions list. This action takes one variable, which is ExternalEvent. Give it the event name that you want to use to run this AIScript. When the event is to fired by anything, the script will begin Running like it was originally.
So, what have we accomplished? Now, when the map is first loaded, this script will automatically Begin, which is expected. Since it began, it iterates through the full list of Actions. Since there's only 1 action, once Action 0 is finished, the script will Terminate. So, once an event is fired to ExternalEvent (which satisfies the wait condition set by Action 0), the action will complete, and since there are no more Actions in the list, the script Terminates itself. While the script in its current state will not do anything notable, it is prepared so that we can start making our script.
Before we get started here, you may want to setup a way to run your current AIScript without having to have much of a hassle. I would recommend placing down a Trigger that fires an event to the name of the ExternalEvent seen in the ACTION_WaitForEvent. By doing this, you successfully make a Trigger able to run your script. If you want your script to be easily looped during this phase of script development, add the action ACTION_GotoAction and make it go to Action 0. By telling it to go to Action 0, you will effectively make an infinite loop, which will allow you to run the same script an infinite amount of times in your map. ACTION_GotoAction will be very commonly used in your future scripts more than likely, so make sure you understand how it works.
Now that you've seen how to create an action, let's run through some basic knowledge you'll need to know when developing a script.
You can Insert a new action above an action by clicking on an action in your Actions list and clicking the Insert button that appears on the right.
When clicking on an action, a Clear button will appear on the right. If you click this, it will delete the physical action you created, but will not remove the action number. If you want to delete the entire action, including the action number, click on the Delete button that appears on the right.
If you click on the Actions array, an Empty button will appear on the right. If you click this, it will empty the entire array of Actions, deleting the script.
When an action is created, the text that shows on the same line as the action number can be copied and pasted to a different action number. This text contains a pointer to your original action, so when you copy and paste it, you essentially duplicate your action. If you were to modify a duplicated action, then the change will appear on both the original action and the duplicated action. Additionally, you can cut and paste this text to a different action number in order to move the action to a different spot in the list.
Now, as mentioned before, let's run through how to possess a pawn with an AIScript. First off, go back to the actor list for placing actors, and select the ScriptedSequence keypoint (this is seen very close to where you originally found the ScriptedTrigger at). Place this down in the level, then assign it a unique tag. Then, head over to any pawn, and go into the AI property tab for the pawn. In there, you will find a variable named AIScriptTag. Set this to equal the unique tag of the ScriptedSequence you placed down in the level. If done correctly, you should now see a dark blue line tracing toward the ScriptedSequence you placed in the level, while the pawn is selected. If you see this, this means you've successfully linked up an AIScript with a pawn. From this point, simply make the script you want to control the pawn from within the ScriptedSequence you placed down. Simple as that! It is worth mentioning that certain actions will not work properly without possessing a pawn.
With that being said, you have now officially made your first AIScript! But, there's so much more to learn! Now that you understand how AIScripts work, take a look at the documentation made for each Action to learn how you can use them. From here, you can begin to learn how to make your own AIScripts.
Just like AIScript, CutScript follows a certain set of rules that you must fully understand before you can begin scripting. Here are the core rules:
The formatting for this language is as follows: Begin->Sequence->Pawn->Actions->Terminate. Begin is called when the script is initialized (loaded, or ran), Sequence is a list of Actions that will be ran, Pawn is the pawn to affect, Actions is the list of actions provided in the script (which is relative to the Sequence), and once every action has concluded in every Sequence, the script will Terminate. As explained in the "Core Principles" section, once the script runs Terminate, the script will have to be fully re-loaded in order to be run again.
CutScripts are pre-defined inside the game's Cutscenes folder directory (seen in the System folder, meaning that you cannot make a CutScript in real-time. You will have to modify the CutScript file in order to change it.
Each action in the script is either considered Non-Latent or Latent. Non-Latent is what most actions are, and they are actions that have no delay. A Latent action is an action that makes the current Sequence Wait, until a condition is satisfied for its action.
An CutScript is either Running, Waiting or Terminated. When a map first loads, it's important to remember that the script isn't considered active until an event is fired to the script that activates the cutscene. In a way, you can consider the script to be Waiting, but in reality, it simply hasn't been activated.
This language is natively multi-threaded, meaning that different parts of the script can run on its own times. This allows you to create complex and elaborate scripts, but is also prone to failure more easily, since it makes it much easier to accidentally forget about a hanging Sequence (a Sequence which cannot properly end, as it got stuck on an action unintentionally). This Sequence hanging error is a very common issue to run into when developing in CutScript.
A CutScript typically possesses another pawn, but can also have a Sequence be designated to itself. Each Sequence requires what's referred to as a PawnTag. A PawnTag should equal the tag of the pawn you want to possess. However, there are two special types of Sequences that use a special PawnTag. PawnTag=Main can be used to possess the cutscene itself, and PawnTag=Exit is a special Sequence that is not used until the cutscene, and it uses special Actions for it. When you possess another pawn, you will override all of their logic with the script's logic. The most common PawnTags are as follows: Main, Camera, Shrek, OnExit.
A CutScript is split into 3 different sections:
Properties
Sequences
OnExit
Properties is a section that dictates whether or not a custom Property is enabled. A Property is something you optionally declare, and no inputs are provided to it. A Property will typically be a simple boolean, or a flag, and they aren't very important to CutScript creation.
Next are Sequences. Sequences are typically where most script logic is handled. A CutScript is not limited in the amount of Sequences it can have, and for each Sequence, you will have a new list of Actions. Each Sequence requires a PawnTag to be specified. There are two special types of Sequences that use a special PawnTag. PawnTag=Main can be used to possess the cutscene itself, and PawnTag=OnExit is a special Sequence that is not used until the cutscene ends, and it uses special Actions for it. Both special Sequences are optional. If you want to possess a certain pawn, make the PawnTag equal the tag of the pawn you want to possess. The only other notable PawnTag you should know of, while it's not considered a special type, is Camera, which will take control of the current player's camera. Taking control of the camera (or the current player) is required for the cinematic bars to appear, and for the player to lose all control of their player.
When a CutScript is first ran, all Sequences are initialized, meaning all of their scripts begin at the same time (provided it was able to find the pawn with the tag specified). The pacing of the CutScript will depend on how well you setup the script, but your goal is essentially to have the CutScript end by having every Sequence in the CutScript end at the exact same time. Accidentally giving the current player back control while a Sequence is still running is called a Sequence Hang, and you need to avoid this at all cost. While action types haven't been discussed yet, you'll be doing what's referred to as Conducting. As the conductor of the CutScript, you'll be in charge of queuing Sequences to run their Actions, or to not run their Actions. And as the conductor, you'll need to insure that all Sequences end at the same time. If you properly conduct the CutScript, then you will never have to worry about Sequence Hanging, meaning you won't need to worry about the game potentially getting stuck from a cutscene. The way a conductor conducts a CutScript is through the two actions Cue and WaitForCue. These actions take a unique name after it, and through it, you can effectively make sure that every Sequence ends at the same time. Cue essentially goes through every Sequence and satisfies the condition of any WaitForCue that has the same name as the Cue that was ran.
And lastly, there's the OnExit section of the CutScript. This special Sequence is ran with custom logic only after all Sequences have successfully ended. When you read up on the CutScript documentation page, you will notice there's a section specifically for OnExit. This is because OnExit uses a special group of action types that no other Sequence can use. OnExit can be helpful, depending on the circumstances, but you typically won't use OnExit for much, other than automatically saving the game once the cutscene ends, or switching the control of the current player to a different pawn.
To begin creating a CutScript, you need to make a new CutScript in the Cutscenes folder, seen in the System folder, which is in the game's directory. You can do this by creating a new Text Document, then naming it something unique and ending it with a .int. It's important that the script file's file extension must be a .int, because if it is not, then the game will be unable to load your CutScript file. If you cannot see file extension names, be sure to enable that in the File Explorer on your Windows operating system. Once you've created the CutScript file, open it up with your text editor of choice. I personally recommend NotePad++, as it comes with a lot of useful tools to help you script more efficiently.
Important notice: don't try to learn how to make CutScripts by replicating how KnowWonder made their CutScripts. Basically every single CutScript they developed is heavily flawed in some way or another. They very commonly left in Sequence Hangs in particular. Although, as a fun little personal challenge, see if you can try to figure out what's wrong any of them and/or what's over-complicated about them!
Here is an example that shows what the general formatting for a CutScript should look like:
[CutScene]
line_0=NoBypass
[Sequence_0]
PawnTag=Main
line_0=ACTION 0
line_1=ACTION 1
line_2=ACTION 2
line_3=ACTION 3
[Sequence_1]
PawnTag=Shrek
line_0=ACTION 0
line_1=ACTION 1
[Sequence_2]
PawnTag=Camera
line_0=ACTION 0
line_1=ACTION 1
line_2=ACTION 2
[Sequence_3]
PawnTag=OnExit
line_0=SPECIALACTION 0
And here is an example of a functional CutScript:
[CutScene]
line_0=NoBypass
[Sequence_0]
PawnTag=Shrek
line_0=WaitForCue CutEnd
[Sequence_1]
PawnTag=Donkey
line_0=WalkTo Point1
line_1=TurnTo Shrek
line_2=WaitForCue CamDone
line_3=Cue CutEnd
[Sequence_2]
PawnTag=Camera
line_0=FlyTo Donkey Time=2.00 *
line_1=TargetFlyTo Point1 Time=2.00
line_2=Cue CamDone
line_3=WaitForCue CutEnd
[Sequence_3]
PawnTag=OnExit
line_0=Donkey SwitchControlToMe
With that being said, this should give you a general idea of how CutScript should be written. Let's go over each part, and explain what it does.
[CutScene]
line_0=NoBypass
This is the first section of the script, and it is the Properties. As explained before, this entire section of the script is optional. In this example, we pass NoBypass as a Property to use. If we wanted to add an additional Property, you'd simply add a new line. Each line must be manually numbered in order, starting from 0, and must start with line_. That means the next line would look like this: line_1=PROPERTY. If a line is not in order, then CutScript will ignore all future lines, until it either reaches a new section, or it reaches the end of the current Sequence. Since we're not in a Sequence, it would simply not read the Property, if we were to make this mistake.
[Sequence_0]
PawnTag=Main
line_0=ACTION 0
line_1=ACTION 1
line_2=ACTION 2
line_3=ACTION 3
This begins the Sequence declaration section. Here, the first Sequence is declared for the CutScript. Similar to line declarations, each Sequence name must also be numbered in order, starting from 0, and must start with Sequence_. However, unlike lines, each Sequence must be encased in brackets ( [] ). After the Sequence is declared, a PawnTag is declared. This Sequence uses the special Sequence Main in order to possess itself. This Sequence has 4 example actions typed, so it would execute every action in order, just like with AIScript. Once the last action is concluded within this Sequence, the Sequence will be Terminated.
[Sequence_1]
PawnTag=Shrek
line_0=ACTION 0
line_1=ACTION 1
[Sequence_2]
PawnTag=Camera
line_0=ACTION 0
line_1=ACTION 1
line_2=ACTION 2
Here, 2 more Sequences are declared. 1 will possess a pawn with the tag Shrek, and 2 will possess a pawn with the tag Camera. As explained previously, each Sequence is going to run through their list of Actions until they run out of Actions, in which case, the Sequence will be Terminated.
[Sequence_3]
PawnTag=OnExit
line_0=SPECIALACTION 0
The final section has been reached, which is the OnExit declaration section. As previously explained, this special Sequence is only executed once all Sequences have finished. Aside from it being a special Sequence with special Actions, nothing else is particularly notable with this, and the CutScript example ends there.
[CutScene]
line_0=NoBypass
[Sequence_0]
PawnTag=Shrek
line_0=WaitForCue CutEnd
[Sequence_1]
PawnTag=Donkey
line_0=WalkTo Point1
line_1=TurnTo Shrek
line_2=WaitForCue CamDone
line_3=Cue CutEnd
[Sequence_2]
PawnTag=Camera
line_0=FlyTo Donkey Time=2.00 *
line_1=TargetFlyTo Point1 Time=2.00
line_2=Cue CamDone
line_3=WaitForCue CutEnd
[Sequence_3]
PawnTag=OnExit
line_0=Donkey SwitchControlToMe
Here is the full example CutScript that would actually function in the game. Let's do a run-through of what exactly happens in this CutScript:
First, a Property is assigned to the cutscene, which is NoBypass. Then, 3 Sequences are initialized, that being Sequences 0, 1, and 2. Sequence 3 doesn't get initialized since it's a special Sequence with an exception to getting initialized.
Sequence 0, which is possessing Shrek, who is the current player in this example, gets put in a Waiting state; it's going to wait until the Cue CutEnd is run by another Sequence. Because Shrek is the current player, the current player is going to lose their control until Sequence 0 is Terminated. This also means that the cinematic black bars are going to appear on the top and bottom of the screen during this cutscene.
Sequence 2 possesses the Camera, then runs 2 Actions. FlyTo and TargetFlyTo are mainly exclusive to the camera specifically, and essentially make the camera fly to a certain point, and look toward a certain point. Take note that these 2 Actions have additional inputs provided, such as Time=2.00 and *. This is how optional variables can be changed for each individual action. FlyTo is normally Latent, but in this case, it's not, due to the *. However, TargetFlyTo is considered Latent in this case, so Sequence 2 is going to wait for 2 seconds before it can move onto the next action. After it runs these 2 Actions, it runs a Cue for CamDone, which in this script's case, lets the other Sequences know that the camera has finished moving. This is done deliberately to prevent a Sequence Hang that could potentially happen, due to how Sequence 1 works, which I'll touch on in such a second. After this, the Sequence gets put in a Waiting state due to the WaitForCue it calls, just like with Sequence 0.
Finally, let's look at Sequence 1. Sequence 1 possesses Donkey, then runs a Latent action, WalkTo. Once Donkey successfully walks to the actor with the tag Point1, the next action is run in the Sequence, which is also Latent. Once Donkey turns to the actor with the tag Shrek, the next action is run. Now this Sequence must wait until the Cue CamDone is run by another Sequence. Since we know that Sequence 2 will be queuing this, let's assume that this Cue was called, which would allow us to continue to the final action in Sequence 1. The final action is Cue CutEnd, and then, the Sequence Terminates itself. Due to every Sequence having to wait for the Cue CutEnd, this successfully finishes every Sequence at the same time, thereby preventing a Sequence Hang.
Once everything is said-and-done, Sequence 3 is run, since the cutscene has finished. It runs a special action, which in this particular case, switches the current player's control from Shrek to Donkey. And with that being said, the CutScript has now officially been Terminated, as all Sequences have been finished!
Now that you understand the traditional method of creating a CutScript, you should be made aware that a cutscene creator actually does exist, meaning you don't need to manually type out every script! Master_64 has created an official cutscene creator (or CutScript creator) from within MPak, and it simplifies the process so much for making CutScripts that I would suggest that you strictly use that. Of course, this does require the use of a library of code to do, but since MPak does a lot more than just add in a cutscene creator, I think you should heavily consider it. Check out the wiki for it to learn more!
Now why should you use a mod to make cutscenes instead of using the traditional method? Well, here's a list of reasons why:
Making the script becomes about the same difficulty as learning AIScript with the cutscene creator, instead of it being much harder
You no longer have to manually type out every script, and you don't have to remember every single optional variable for every action
The CutScript can be made inside of the map's data file directly
All cutscenes made with this creator will be compatible with any custom map
Installing a custom map no longer requires adding the CutScript files manually
With that being said, I would suggest to go ahead and install MPak for both the game and the level editor. The installation is very simple, and the power it will give you in terms of modding is completely unmatched in comparison with the original game.
Once you've installed MPak, go into a level, then place down MCutsceneCreator (Info->MInfo->MCutsceneCreator). In the MCutsceneCreator property tab, you'll see a variable called CCActions. Like AIScript, you'll be making a list of Actions, but unlike AIScript, eact CCAction is considered an entire CutScript, and each Sequence in the CutScript will act very similarly to a list of Actions done in AIScript. From this point, given the knowledge of the previous section for CutScript, you can begin making your own CutScripts from within the level editor, and as long as you use the same logic as with AIScripts, then you should be good to go! Make sure to take a look at the CutScript and MCutsceneCreator documentation to learn the full potential of CutScript.
So, now that you've read up (and hopefully tested yourself) on how scripting works, I wish you good luck in terms of applying this knowledge to your own scripts! Once you master the art of scripting, it's going to be safe to say you can do nearly anything you want with mapping/modding! And if you can merge the power of both scripts together...then you'll have something truly beautiful. Have fun exploring the world of scripting, and figuring out how every action works!