Tutorial - Mission Infrastructure
Mission Infrastructure and Checkpoints
As missions get longer and complicated, we hit two problems that can crop up. One, playtesting longer missions takes longer. You and your testers aren't going to have a fun time repeatedly testing a 45 minute mission. Bugs past the 30 minute mark are probably going to get missed and we're going to get mission fatigue that will hit the latter parts of the mission when the FREDder is tired of the mission and gets lazy.
Likewise your players aren't going to have a fun time dying at the 44 minute mark and have to redo it all. Thankfully we have a solution to this now. Checkpoints.
Blue Planet 2: War in Heaven used checkpoints in a few missions to break up the long missions into something much more manageable. The main issue was it took up A LOT of sexp variables. 1 for each ship's position on each axis, 1 for health, and more if we need to keep track of subsystems and weapons. We can easily hit the sexp limit if we have too many ships and missions that need this.
Thankfully, Admiral MS made a checkpoint script that saves all this info into a text file that gets saved in the player's FreeSpace directory. It takes all the pain and tedium out of saving and retrieving the info. But that's only half the battle. To properly use checkpoints, we should have a good mission infrastructure.
I'm going to talk about how I plan and implement this kind stuff. Don't need to follow this word for word, this is a way to give you an idea on how to approach the daunting task of adding checkpoints to missions.
What We Need
The first thing we will need is 1: the checkpoint save script, and 2: a neat intuitive way to let the player choose if they want to resume from a checkpoint. You could go and use key-pressed + a training message, but I'm going to use my PromptBox script, because its awesome. So read up on those scripts and install them, because I will be using them!
When I plan out a mission, I plan out the whole mission, then start dividing it up into chunks. An intro cutscene is one stage. Fighting the first wave of enemies is the next. Then after killing the fighters, a capital ship arrives. That's another stage. Then after that new capital ship runs away, we start another new stage.
I'm going to create a semi-simple FreeSpace mission to show you how I make it, and why I do certain things.
First lets come up with a mission and plan out how we intend to divide up the mission.
Sample Mission Outline
Mission Name: Dead Drop
Setting: Contested System. Before FS2 begins. The NTF and GTVA are still on even ground with each other.
Background: Through the NTF intelligence, the NTF has managed to gather sensitive military intel on the GTVA. The intel has been dead dropped on a civilian owned gas hauler. The NTF has hired a pirate band to capture the hauler. The pirates get a large sum of money, the transport and its cargo in exchange for an "important item" on the ship. The GTVA is completely unaware of the intel being stolen.
However the pirates bungled the attack and the hauler managed to get a full distress report off to the GTVA. The NTF reacts to either capture the hauler, or destroy it.
Stage 1: Intro cutscene (skippable). A quick look at the gas hauler, docked to a Pirate transport.
Stage 2: Orders are to engage the pirates. Force is small and inexperienced. After the fighters are destroyed, a docked pirate transport tries to flee.
Stage 3: After the pirates are dispatched, an NTF Aeolus cruiser arrives. The cruiser tries to claim jurisdiction in the matter. _They_ will rescue this gas hauler, even though the danger has already passed. GTVA calls their bluff and sends a wing of Medusa and a Fenris to show the Aeolus out. After a brief fight, the Aeolus retreats.
Stage 4: GTVA transport arrives to repair the damage on the hauler so it can leave.
*CHECKPOINT GOES HERE!*
Stage 5: Hauler's engines are repaired. NTF Deimos class corvette arrives. Orders the transport to surrender. GTVA responds by sending a Sobek. The NTF announce that the hauler is now considered a military target and will destroy it. (The NTF aren't getting that dead drop, so its better to cover your tracks!)
The Deimos is forced to engage the Sobek, allowing the the hauler begin a retreat. The Player's wings must escort until it can jump away. Another NTF Aeolus arrives, jamming the nav signal the transport is trying to use. You must destroy the Aeolus' nav system so the hauler can connect to the nav network. Also destroying those main beam cannons would help.
Stage 6: Hauler makes it out, NTF leave. GTVA forces leave soon after.
Building Mission Infrastructure
So there's a certain method to the madness to easily divide this mission into parts. I've already made the mission in question and I'm not going to go through step-by-step how to build it. Hopefully this won't be too complicated to grasp, its just a lot of planning and keeping to a set plan. To make it easier, I'm going to use... COLORS!
But first, here's our happy mission space. Lots of fun ships. Some FS2 named, some named from dark reaches of pain. Let's take a look at our events.
So, you'll notice there are a bunch of events that are named "Stage X - Blah blah blah". Those are the stages we set up in our mission design doc. To help you out, I've even color coded the stages! Our ultimate goal is to keep relevant events tied to things within their own stage. This will let us go in and out of stages without worrying about it effecting anything else by accident.
Here we see an example with the NTC Vanguard's role in the mission. When Stage 3 - Fight NTF goes true, it sets up the Cue Vanguard event. The Vanguard arrives and it "Has some words". After "words have been", two other events are set to happen. The Vanguard's beams are set to be freed, and the Sif and Delta Wing are cued to arrive. And as we peer down the chain from there, we can see the top half of something that says "Vanguard Runs". So simple, right? Nothing new here!
This is the basic idea of how I keep my mission infrastructure simple and clean. I could have easily used is-event-true-delay "Vanguard Runs" to trigger Stage 4. But instead when “Vanguard Runs” happens, I modify a variable called !ddStage to become 4. And then Stage 4 begins when !ddStage is 4. We don't want the stages to be directly linked to each other, an indirect link with a "stage director" variable is what we're after. But wait, that seems needlessly complicated.
Also why is that variable have a ! at the front? It puts it at the top of the variable list so easy finding. When you get really brave with 60+ variables, marking important variables makes life easier.
THIS is why! I'm going to get a little ahead of myself here, but when the mission starts, our stage variable is 0. If we don't detect any save data, or if there is saved data and we want to start from the beginning, we continue by setting our stage variable to 1. But if there is saved data and we want to skip ahead, then we just set our stage variable to 5. All of those stage starting events that are waiting for the stage variable to be 1, 2, 3 or 4 aren't going to happen. So with all of our events within those stages aren't going to happen either.
And even if we did go through the mission like normal, well, it all just appears to be normal anyway. Aha, but Axem, you could have just triggered Stage 5 with having "Go to NTCv Fight" OR "Time for Stage 5". Yes, I could have. But we're not done with our stage variable yet.
So let's pretend we're going through the mission like normal. The GTC Sif is normally cued to arrive in stage 3. So, "Sif + Delta Cue" eventually leads to "Vanguard Runs." And "Vanguard Runs" sets the stage variable to 4. How hold on... "Sif Arrival Skipped"... When the stage variable is greater than 3, oh yes that's true, its 4 right now. And not and-in-sequence, stage is 3, then 4. Well it DID go 3, then 4. So this event... it doesn't apply to us. (Alternatively, you could go when the stage variable is greater than 3 and Sif has not arrived yet, I feel this way is a little more robust and generic for any situation.)
Now let's pretend we're skipping to stage 5 right off the hop. Well, our stage variable is above 3, and since we've gone from 0 to 5, the not and-in-sequence is true! So the Sif's arrival info is set so it has no warp effect, and its cued to arrive right away, and in the next event "Place Skipped Sif" we place it down somewhere in the mission. Warping out covers a lot of distance and it would have travelled quite a bit in stage 3 anyway. But we're not toooo concerned with where it is, so I wasn't picky with where I put it in the event.
Of course you should be a little cautious of how ships arrive. It's normal we set messages from a ship to announce that they've arrived. That of course is done with the has-arrived-delay sexp... but in our example, that would trigger no matter which method we had the Sif arrive. So I've done the easiest thing and just chained its arriving message to the original cue. ("Sif + Delta Cue" -> "Sif + Delta Arrives")
So that's how we attend to getting arrival cues of ships, how do we deal with ships that shouldn't be there anymore? All sorts of ways. Setting up a similar thing like we did up above, change departure-info and force ships to leave or... ship-vanish. Ship-vanish is easy. Just remember that a docked ship will vanish anything docked with it!
You can see from those the same line of thinking. If we're above stage 2, the pirates should be gone anyway, so we can vanish them.
Now what's really cool about this is you can, as a mission designer, skip to any part of the mission you want. Just set the initial value of the stage variable. Want skip to the Vanguard arriving? Make !ddStage's initial value be 3. Want to start from after the initial cutscene, make it 2. With the less than and greater thans, the mission knows how to add and remove ships to set up the stage without a problem. This makes beta testing long missions so much easier.
Now For The Actual Checkpoint
So with that basic theory under our belts, we come to the real fun. Checkpoints. They are like, super easy. I mean look.
How easy is that?!
The checkpoint save triggers when the transport docks. The script just needs script-eval saveship('SHIPNAME') and it'll add it to a save file (Remember those single quotes!). And when we trigger an event where the player chose to load a checkpoint, we use script-eval loadship('SHIPNAME'). You'll notice there's also a 3 after 'Alpha 1'. That extra optional number loads some extra parameters to the ships. 1 (which is the default) just loads ship class, weapons, health, etc. Basic stuff. 2 loads that plus weapon/shield/afterburner energy. And 3 loads all of that plus position and orientation. For our fighters, we load them with 3. For our capships, we just load them with the default. The Sif would try to restart her waypoint path (even if she was about to finish it) and her placement isn't too critical to the mission. Omega 1 doesn't need it because her "Skipped Arrival" event will set-docked it to the gas hauler, so its position changes anyway.
You'll also notice, there is no sign of our stage variable here. For good reason. We don't want to reload save data if we played it normally. And its not actually part of a relevant stage event. It's sort of a backstage worker who has a single job. So its okay that he's tied to an event outside his scope. (Its best to keep this kind of thing to a minimum anyway).
Now how do we let the player know they can select save data? Training messages, let them call a reinforcement buoy, or... my incredibly awesome prompt box script! So the first thing we should do is use the saveexist() function to find out if there's any saved data. How the function works is if there's a sexp variable called "saveexist" and we call the saveexist() function, the saveexist variable will go to 1. And with that info we just set up the prompt box to ask if the player wants to skip ahead or not. (Note, there needs to be a 1ms delay for the prompt box to work on mission start. Its just a weird quirk.)
And we just continue from there. See the thread for more info about how the prompt box works. Otherwise you can use key-press or other user input methods.
And once we're all done with the mission, just use script-eval deleteshipsavefile() and the save file goes bye bye.
There's a whole bunch of stuff that I sort of skimmed over. I highly encourage you to look through the mission file and the documentation that Admiral MS made for his checkpoint script.
SO. To recap this long and wordy post.
- Use the ship save/load script for easy checkpointing
- Dividing your mission into independent parts makes checkpointing 1000% easier. It also makes playtesting longer missions easier. It's extra work, but its extra work that saves you work later.
- Save ship data with saveship('SHIPNAME') (Single quotes required!)
- Load ship data with loadship('SHIPNAME',#) (1, 2, or 3 depending on how much data you want to load)
- Delete saved data with deleteshipsavefile()