Creating a Single Player Mission

Scripting Cineractives (5.14)

Intro Cineractives

We'll look at the first Boot Camp mission as an example for most of this section. Luckily, this mission contains all of its cineractive scripts in one file called "objectives_cineractives.cfg". Let's begin with how to even start a cineractive.

Starting the Cineractive Script
The first script heading looks like this:

  Action()
  {
    CreateVarInteger("@.charge", 0);
    Cineractive()
    {
      At(0)
      {

The "Cineractive" action is what tells the game to enter cineractive mode. From there, the game will then execute each "At" block with their proper timings.

Timing
The "At(0)" means that this event will happen at 0 seconds into the cineractive. Everything listed within its set of brackets will occur in that instant.

Disabling Gameplay
The first thing you'll usually want to do is disable normal gameplay conditions.

      At(0)
      {
        Unpause();
        DisableInput(1);
        DisableIFace(1);
        DisableShroud(1);

The first 4 lines after turn off various aspects of the game. "DisableInput(1);" disables the ability to use the interface or press any keybinds, "DisableIFace(1);" disables drawing the interface, and "DisableShroud(1);" disables the map shroud so that all units are always visible. At the end of the cineractive, these 3 things will automatically be turned back on. "Unpause();" ensures if the game was paused before the cineractive starts somehow, then it won't stay paused. Typically, this isn't needed for intro cineractives, but it's very important for cineractives that are triggered partway through gameplay, or when the game ends.

Fading
Next further down, you'll see the "Fade()" command being used:

        Fade()
        {
          Time(0, 3, 5);
          Direction("up");
        }

The Fade has 3 numbers listed in its "Time" field. In every Time field, there is a starting number that is implied. That number is the time at which the fade-out will begin, and it is always 0. The first number that appears is the time when the fade-out will end. In this case it is 0. So immediately when triggering the fade the screen will be black. The next number, 3, specifies when the fade-in will begin. And the last number, 5, specifies when the fade-in will end, returning us to an un-obscured view. The "Direction" field specifies whether we're going from light-to-dark-to-light or dark-to-light-to-dark. We will always be using the first method, so always keep the direction "up".

What this all means, is that when this fade is used, the screen will be all black right away. Then, 3 seconds later, the screen will being to fade in. Finally, 2 seconds later, the fade-in will be complete and the game view will be completely unobstructed. This is how you have an intro cutscene that slowly fades away from black, during which you can do many things, such as use voice overs or place text on the screen.

Printing Text
Does your mission have a name? You can show it to the player using the "Text()" command:

        Text()
        {
          Time(1, 3, 4);
          Direction("up");
          Text("#shell.campaign.boot.title");
          Font("Debriefing");
          Pos(0.5, 0.35);
        }
        Text()
        {
          Time(1, 3, 4);
          Direction("up");
          Text("#missions.bc_p.t01.description");
          Font("Movie");
          Pos(0.5, 0.42);
        }
      }

You should recognize the "Time" and "Direction" fields from Fade. They operate exactly the same here. From the "At" timing the "Text" command is used, the text will fade in over one second, hold for two seconds, then fade away over one second. "Text" is, of course, the text that is written. Here, it is using a localization key. That is, it will automatically output a given phrase based on the language the game is set to. If you simply want to use a phrase that already exists in the game, this is the way to go. A list of keys can be found here.

Next is "Font", which should be self explanitory, followed by "Pos". There are three basic fonts to choose from; "System" which is the smallest font used for pretty much everything, "Debriefing" which is the slightly larger text used in the main menu, and "Movie" which is the largest text used exclusively for cineractives. The position only matters if your map has a subtitle, or you need multiple lines of text. Otherwise, Pos can be excluded and the text will automatically be centered for you.

Playing Music
In Army Men RTS, the music does not play on its own. Instead, you must specify when it starts, and what playlist to use. Let's take a look:

      At(1)
      {
        Action()
        {
          Cmd("sound.player.setfixedvolume 1");
          Cmd("sound.player.loadtrack t01_intro.mp3");
          Cmd("sound.player.removeactive 1");
          Cmd("sound.player.setfixedvolume 0");
          Cmd("sound.player.addtrack amb_bootcamp.mp3");
          Cmd("sound.player.play");
        }
      }

Cineractives don't support any native commands for music, so you'll need to use the "Action()" block instead. This cineractive has a custom backing track that is meant to play throughout. Let's go through each command and see how this is pulled off.

  1. Cmd("sound.player.setfixedvolume 1"); - This command enforces the game volume to the given level, regardless of the player's settings. It accepts a decimal between 0 and 1 inclusive, so 1 means the track will play at maximum volume.
  2. Cmd("sound.player.loadtrack t01_intro.mp3"); - This command adds a given audio track to the playlist. It will also increment the playlist to the added song immediately. If you have a custom mix for your cineractive, this is how you would queue it.
  3. Cmd("sound.player.removeactive 1"); - This command will remove the current audio track from the playlist, but will not interrupt playback of it. This way, once your cineractive is over, the custom music will not play again once the playlist loops back around.
  4. Cmd("sound.player.setfixedvolume 0"); - This command releases control of the game volume. It does not take effect until the next track plays, so you don't have to worry about interrupting your custom audio track.
  5. Cmd("sound.player.addtrack amb_bootcamp.mp3"); - This command will add a given audio track to the playlist. However, it will queue it, rather than play immediately. If you have custom music for your mission, you can use this method.
  6. Cmd("sound.player.play"); - This will begin audio playback.

Let's say that you don't have any custom music for your mission. In that case, things are a lot simplier:

        Action()
        {
          Cmd("sound.player.clear");
          Cmd("exec music_common_list_2.cfg");
          Cmd("sound.player.play");
        }

As you can see, this requires only three commands. "sound.player.clear" will clear the playlist. It shouldn't be necessary, but better safe than sorry. "exec music_common_list_2.cfg" executes a given .cfg file. In this case, this is a presupplied file that adds all eight default music tracks in some order. Some tracks start slowly with a given mood, while others are more boisterous.

Triggering the Camera Path
Further down a bit, you'll see a script being executed, but let's save that for later. For now let's look at the camera path. Use the "SetBookmark" command, then specify the name of the camera path that you want.

      At(3)
      {
        SetBookmark()
        {
          Name("intro");
        }

This will start the camera path at 3 seconds into the cineractive. Note that the camera will be put at the beginning of the path, not 3 seconds into it.

Note that a camera path does not have to be the exact length of a shot. You can switch bookmarks at any time, or if your bookmark ends before your cineractive is finished, then the camera will simply remain at the ending point and hold it until the next instruction is given.

Letterboxing
Let's take a look at letterboxing. Letterboxing the is black bars at the top and bottom of the screen, meant to keep the framing identical across any resolution a player might be using.

        Letterbox()
        {
          Time(0, 20.5, 21.5);
          Direction("down");
        }
      }

Letterboxing uses the same "Time" and "Direction" fields as both Fade and Text. In this case, the letterbox doesn't need to fade in as it is triggered while the screen is still black. So the first number is usually 0. The second number is how long the letterbox will remain on screen, typically the length of the cutscene, and the third number is how long the letterbox will take to fade away.

Here's an easy way to calculate how long a letterbox should last for: First, write down the At time the letterbox is triggered. Then, once your cineractive is finished, write down the At time that you use the "EndCineractive" command. Now, subtract the At time of the letterbox from the At time of the "EndCineractive" command. This will result in the third number of the Time field. To get the second, simply subtract the number of seconds you want the letterbox to fade over from the third number.

So for example, let's say you trigger your letterbox 3 seconds into your cutscene, and your cutscene ends after 42 seconds. You would subtract 3 from 42 to get 39, then assuming you want the letterbox to go away over 1 second, subtract 1 to get 38. This would result in a letterbox command that looks something like this.

    Cineractive()
    {
      At(3)
      {
        Letterbox()
        {
          Time(0, 38, 39);
          Direction("down");
        }
      }
      At(42)
      {
        EndCineractive();
      }
    }

Using Actions
Console commands aren't the only things you can use with actions. Let's rewind and look back at that script we skipped over.

      At(2)
      {
        Action()
        {
          ExecuteScript("chinook1", "unit.move.spawntospline")
          {
            Op("%.type", "=", "army.unit.fakechinook");
            Op("%.tag", "=", "chinook1");
            Op("%.curve", "=", "chinook1");
            Op("%.useroll", "=", 1);
          }
        }
      }

You'll remember from Chapter 9: Scripting Attacks when we looked at using scripts how they should function. As you can see, we are executing a script just like how we used them for control the computer's attacks. As a matter of fact, any actions that you can do with a regular objective script can be performed in a cineractive this way. For example, scrolling down a little further, you can see this being used to trigger a game message as we went over in Chapter 6: Messages / Objectives.

      At(8)
      {
        Action()
        {
          GameMessage()
          {
            Message("msg_intro1");
          }
        }
      }

Triggering Scripts With Vars
However, there is one caveat to all this. That is, the cineractive that is currently running is being run from the team that called it. This means that, effectively, any actions you call on will be carried out by the team running the cineractive. Basically, who is the team that triggered the cineractive? Any actions taken will be as if that team triggered an objective. This means specifically any units you spawn will be assigned to that specific team, and any units you try to order around must belong to the team running the script. So let's say for example, the player is running the intro cutscene. You could spawn the player's units and move them around as such. But what if you want to move someone else's stuff? What if you wanted some Tan units to walk by? This is where what we learned in Chapter 7: Variables comes into play.

So far we've been looking exclusively at the intro cineractive for Boot Camp mission 1. Scroll down further to the "cineractive_group3" script heading. This is the cutscene that spawns some Tan grunts to come attack some Green grunts.

      At(0)
      {
        Fade()
        {
          Time(1, 2, 3);
          Direction("up");
        }
        Action()
        {
          Op("@.charge", "=", 1);
        }
      }

Here you can see the team var "charge" being set to 1. Now let's take a look at the Tan's objectives located in the file "objectives_tan.cfg".

CreateObjectType("objective_charge", "Objective")
{
  GameObj();
  ObjectiveObj()

  {
    Condition("VarConstInteger", "@Green.charge", "==", 1);

    Action()
    {
      ExecuteScript("3e1", "squad.move.spawntoregion")
      {
        Op("%.region.src", "=", "3e1");
        Op("%.region.dst", "=", "3e1go");
        Op("%.types", "=", "grunt");
        Op("%.formation", "=", "Basic");
        Op("%.direction", "=", 90);
      }

As you can see, the Tan are watching the Green team's "charge" var. When it is 1, then the Tan team is told to execute several scripts. Using this method, you can assign several objectives to any team you want, while retaining control over when they are executed during cineractives.

Blending from Cineractive to Gameplay
Once you've done and shown everything you need to, there is one more thing to do before ending the cineractive.

      At(23.5)
      {
        DefaultCamera(1)
        {
          Pan()
          {
            Region("start");
            TrackTerrain(0);
            Time(1);
          }
        }
      }

This is the "DefaultCamera" command. By default, it will simply reset the camera to how it was before the cineractive started. But you can use the settings of "Pan" to control how that is done, or even what location to reset to.

Under Pan, you see three settings. The "Region" fields specifies which region the camera should return to. Usually this is the player's spawn, but you can pan to any arbitrary region. While the camera is panning, it will consider the height of the terrain and adjust itself accordingly. You can disable this behavior by setting "TrackTerrain" to 0. This way, the camera will seamlessly travel regardless of what's inbetween the start and end positions. Lastly, the "Time" field should be self explanitory. Use it to specify how long, in seconds, the pan will take.

By the way, this would be a good time to use the "DisableInput(0);", "DisableIFace(0);", and "DisableShroud(0);" commands. This is automatically done for you once the cineractive ends, but doing them here looks the most natural as the camera fades back to regular gameplay.

Ending the Cineractive
The final step!

      At(24.5)
      {
        EndCineractive();
        Action()
        {
          NewObjective("objective_start_green");
        }
      }
    }

The "EndCineractive" will exit cineractive mode and return gameplay to normal. It will also re-enable the interface, shroud, and player's input if they haven't been by now.

Outro Cineractives

For most missions, just an intro cutscene is enough. You can use it to signify what the player needs to do, such as through voice over, or simply displaying a key area on screen. Otherwise, you might also want an outro scene. This could either be showing off the player's handiwork, or if the player has failed, can show how or why they failed.

For the most part, this cutscene is scripted in the same way as the intro one. The main differences will be with the fade-in, and what happens after the fade-out.

Starting the Cutscene
While we're still looking at the first Boot Camp mission, scroll down to the script heading "cineractive_group1" and find the "Fade" command being used.

      At(0)
      {
        Fade()
        {
          Time(1, 2, 3);
          Direction("up");
        }
        Action()
        {
          Cmd("sound.player.fade 1");
        }
      }

This time, the first "Time" number is 1. This will mean that once the fade is triggered, the screen will fade-out over the duration of 1 second. Then it proceeds as it would otherwise. The screen will remain black for 1 second, then fade-in over 1 more second. This means that you will fade to black, then fade back in over the course of 3 seconds total.

As for the Action command, that is executing a console command to fade-out the music along with the fade to black. Whether you want to do that is up to you, but it's a good idea if your cutscene has voice over that needs to be heard.

Ending the Cutscene
An outro cineractive needs to end in one of two ways, either the player wins or loses. Let's take a look at how this works. Scroll all the way down to the end of the script heading "cineractive_outro", which is conveniently at the end of the whole file. It will look something like this:

      At(13)
      {
        Fade()
        {
          Time(2, 5, 5);
          Direction("up");
        }
      }
      At(15.5)
      {
        Action()
        {
          Win();
        }
      }
    }

Here, the Fade looks a bit different. The first number is 2, meaning the screen will fade to black over 2 seconds. However, the next two numbers are both 5. This means the screen will remain black for 3 full seconds, then simply cut back to full view. We can use these 3 seconds to end the mission however we wish. In this case, we use the "Win();" action, which will of course make the team running the cineractive instantly obtain victory. If this is a cutscene after losing, then you should use "Lose();" instead. In either case, we trigger this command 2.5 seconds after we trigger the fade, giving the screen enough time to fade to black. You may also want to fade the music too, since it does not lower in volume during a fade on its own.

Remember! The team that is running the cineractive script is the team that is carrying out all of the actions within! If the player's own team isn't the one running the script, then the Win and Lose commands may not behave as expected. Also, you may notice that the Boot Camp missions have either "Campaign" or "Shell" located within their Win commands. In short, this tells the game how it should return to the main menu. If you're creating a custom mission, don't try to use these unless you know what you're doing, as it is very likely to crash the game.

Midway Cineractives

Starting the Cutscene
The final kind of cineractive you'll use is one that is triggered only partway through the mission. Most often, this is just used to signify the player when they've either found something important, or their win condition has changed. Like before, the only differences in scripting them is using a different fade-in and fade-out. Unfortunately, Boot Camp doesn't contain any good examples, so let's take a look at the midway cineractive of Mission 15. You can find it under the script heading "cineractive_mid" in the file "objective_cineractive.cfg". It is located at the very end.

    Cineractive()
    {
      At(0)
      {
        Fade()
        {
          Time(1, 2, 3);
          Direction("up");
        }
        Action()

        {
          Cmd("sound.player.fade 2");
        }
      }
      At(1)
      {
        DisableInput(1);
        Pause();
      }
      At(2)
      {
        DisableIFace(1);
        DisableShroud(1);
        Letterbox()
        {
          Time(0, 15.5, 15.5);
          Direction("down");
        }
        SetBookmark()
        {
          Name("mid");
        }
      }

Let's break this down step by step. First, we trigger a fade. The fade-out occurs over 1 second, holds for 1 second, then fades back in over 1 more second. This is 3 seconds total where the screen will fade to black, then fade back up again. The music is also faded away during this time. Next, we disable the player's ability to make inputs. For this cutscene specifically, we also manually pause the game. This is of course stop any activity that is happening while we play our cutscene. It is helpful for returning control to the player in the exact same game state when control was taken away. Just don't forget to unpause later. Lastly, after two seconds have passed and the fade begins to fade back up from black, we disable the interface and terrain shroud. We also setup a letterbox and start our camera path at this time.

Ending the Cutscene
Overall, this is pretty much the same beginning as an outro cutscene. But let's take a look at ending our cutscene now. Scroll down to the end of the file.

      At(16)
      {
        Fade()
        {
          Time(1, 2, 3);
          Direction("up");
        }
      }
      At(17.5)
      {
        DefaultCamera();
        DisableIFace(0);
        DisableInput(0);
        DisableShroud(0);
      }
      At(18)
      {
        Unpause();
      }
      At(19)
      {
        EndCineractive();
        Action()
        {
          Cmd("sound.player.play");
        }
      }
    }

First, we trigger another fade. This fade is identical to the fade that brought us into the cutscene, a slow fade to black then back again over 3 seconds. Then, a second and a half later, we renable everything we disabled from the player. We do this now rather than letting the "EndCineractive" command do this naturally because this way our interface and controls will also appear to fade in with the screen. Otherwise, it would all appear immediately as soon as the cineractive ends. Then, half a second later, we remember to unpause the game. We paused it when the cutscene started, remember? Lastly, we end the cineractive, along with restarting the music that we also killed when starting the script.

Notice that this time, the "DefaultCamera" command has no fields listed, nor is it set to 1. The difference is that this will return the camera to exactly how the player left it when the cineractive was triggered. In most cases, this is desired over forcing the camera to pan to some spot they weren't focused on before. It is up to your own discretion which is better to use.