Making resilient missions

From FreeSpace Wiki
Revision as of 18:48, 25 April 2013 by TopAce (talk | contribs) (some wording changes, deletions and additions)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
Originally posted by TopAce here, the article was submitted to the FS Wiki with the following alterations: minor wording changes, wikification

As a mission designer and tester, I often run into bugs that prevent the mission from progressing from one stage to another, making the mission unbeatable due to a FREDding oversight. If caught in this "infinite loop," the player has no choice but to time-compress and hope that something will happen. Often, it's a false hope.

Opening the mission in FRED and looking at relevant mission events and arrival/departure cues usually reveals the mistake. Sometimes the FREDder simply forgot to modify a SEXP's argument, leaving Alpha 1 under an is-destroyed-delay. Sometimes the bug is caused by unanticipated player action, like destroying a cargo container that should be kept intact.

But the mission doesn't necessarily end up in an infinite loop if such mistakes are made. Sometimes two events occur at the same time that are not supposed to co-occur. For instance, enemy bombers arrive if the ship they're meant to attack is no longer in the area (destroyed, departed, or not yet arrived). Or: you get congratulated in the debriefing for completing a secondary objective after being sentenced for high treason. Clearly something is wrong here.

As conventional wisdom goes: "Expect the unexpected." This is an oxymoron, because if you expect something to happen, it's no longer unexpected. I figured I'd elaborate on this, by posting this guide on how to make your missions more resilient. Truth be told, I can't help you predict how your players will play your mission, but I have some advice to give on how to prevent your players from breaking it.

A metaphor

If mission instability were an illness and if I were a doctor, I'd suggest you spend your money on healthy lifestyle as opposed to buying a cure. Without metaphors, all I said was to consider mission stability simultaneously with placing ships and creating mission events. Implement the fix before without the mission. Take a look at your events, arrival/departure cues/ship flags and try to play your mission in your head. Is there a thinkable possibility that may prevent Stage 2 from beginning? What kind of possibility is this and how can one make sure that Stage 2 begins when it should? You cannot predict how your players will play your mission, but you can build your mission as to not allow the player to get into unforeseeable scenarios. Let me introduce two terms here which I will use in this essay (these terms are actually not used by the community but are nonetheless important to convey the message). A theoretical possibility and a thinkable possibility.

Theoretical and thinkable possibilities

Something is theoretically possible if the combination of SEXPs, ship attributes, and table data allow it to happen. Say, we have a wing of enemy bombers in the mission with bombs attacking a Deimos. It is theoretically possible for the Deimos to be destroyed, because bombs have the "huge" flag, which compensates for the Deimos's "big damage" flag. The Deimos doesn't have the ship-invulnerable flag to ensure its survival and the neither do the bombers to ensure their survival. Whether the bombers stand a chance or not depends on how many enemy bombers there are, which turrets reach them, how far away they arrive from the Deimos, etc.

These factors contribute to what degree it is thinkable to take the Deimos's destruction into consideration. If it is a sole wing of four Medusas arriving 3,000 meters away, unescorted, and your wingmen, in 9 out of 10 test runs, rip them to pieces before any could launch one bomb, then it is not thinkable to assume the Deimos will be destroyed. But what if we have a Deimos-vs-Deimos duel, with fighters and bombers aplenty on both sides? If the enemy Deimos arrives from behind, with its bombers immediately sprouting out bombs? Then the fight suddenly becomes losable, not just in the distant theoretical sense.

Simply determining how winnable this mission is won't do us much good if that's the whole of the mission. But what if another friendly corvette jumps in after the mission to relieve the battered-down Deimos? I could very well imagine a less experienced FREDder coming up with an arrival cue such as this:

$Arrival cue: ( is-destroyed-delay 0 "Enemy Corvette" )

Which does not take into consideration that the enemy corvette could be destroyed AFTER the friendly corvette has gone down. If this arrival cue (which in itself isn't problematic) is paired with a similarly simple event, like this:

$Formula: ( when 
   ( has-arrived-delay 1 "Friendly Corvette 2" ) 
   ( send-message 
      "Friendly Corvette" 
      "High" 
      "Youre relieved" 
   )
)
+Name: Youre relieved msg
+Repeat Count: 1
+Interval: 1

Which will send the "Good job protecting the Deimos. You're relieved." message even if the message's target has been destroyed.

The FREDder, in this case, assumes that as soon as the friendly corvette blows, the player will jump out. But if he relents, he will run into this oddity. If the success debrief requires the second corvette's arrival only, then the player will get congratulated, and chances are, you will also get the failure debrief. It is important to always assume that the player will not follow orders to the letter.

But how does this relate to the previously defined two terms, theoretical and thinkable occurrences? Simply put, if this battle is a cutscene that's always won, then the player won't run into this clash. The friendly corvette will always win and the second friendly corvette will never say "Good job defending the Deimos. You're relieved." at an inappropriate time. But if this is an interactive mission, the player may run into this bug. The harder the mission, the more likely.

There's a pretty wide penumbra between levels of thinkable outcomes. Consider Clash of the Titans II and the Colossus-Sathanas duel there. In the above categorization, it is theoretically possible for the Colossus to win. The Sathanas is not invulnerable and the Collie has beams, strong ones. But given that the Sath unleashes its full forward firepower at an already damaged Collie, I can't blame the mission's FREDder for not including a Colossus victory debriefing. To make things worse for the Colossus, it is even disabled, not being able to make maneuvers. Even if you armed yourself and your wingmen specifically to save the Collie, you wouldn't be able to dish out as much damage against the Sath's four(!) BFReds. Sorry, tables have been set up so that the juggernaut has very, very durable forward beams.

Clash of the Titans II is a rather extreme example, but it demonstrates my idea of an unthinkable, but theoretically possible, occurrence. A less extreme example would be the Ravana-Actium and Lysander encounter in The Great Hunt. The Ravana's beams are quite fragile, and the player and his wingmen aren't too far from the action. Had I FREDded that mission, I would have considered adding an alternate ending of the mission, where both corvettes survive. Or make the Ravana's beams invulnerable if the Ravana's first appearance was meant to be flashy.

Lastly, let's consider the Plato in Out of the Dark, Into the Night. If you engage and disrupt the Shaitan long enough, the Plato will reach the node, but it won't jump out. It will stay immobile, waiting to be destroyed so it could launch its escape pod, triggering a mission-critical directive/mission goal. I don't think I am the one and only player who could have saved the Plato had it not had to be destroyed.

Player behavior

Player interference is a big factor. I don't just mean that there are poor and outstanding players. Most of the time, when considering mission stability, the FREDder needs to account for his players acting strangely, getting into weird situations.

For example, consider a recon mission during which the player flies a Pegasus. There's Kappa 1, another Pegasus (your superior) in the background, giving advice, monitoring long-range hostile movements, etc. First, I want Kappa 1 to stay where he is, so I give it the play-dead orders, disable all player-issuable orders, and give it the ship-protect flag as well (which, one can argue, is redundant for a stealth fighter, but I'm just this kinda FREDder). When the mission ends, Kappa 1 jumps out. So far, so good; everything seems to be in order now. However, by accident, I realized there's one way to screw Kappa 1.

The mission, predictably, involves running away from a wing of fighters, and on one run-through I happened to be headed towards Kappa 1 during my escape. Though he was 20,000 m away still, I realized the player could just approach Kappa 1 and kill him, so I implemented a further safety mechanism: If you get 15,000 m close to Kappa 1, he will instruct you to follow your mission objectives. He does it again if you're 10,000 m close. At 5,000 m, he will abort the mission and jump out. Now, since I haven't used end-mission to end the mission in this case, there's a (thinkable) chance that the player will get back to his mission, so I had to make sure that no further messages come from Kappa 1 and the mission will not proceed to the second stage if Kappa 1 aborts the mission. Loosely speaking, nothing is supposed to happen after Kappa 1 jumps.

I noticed a possible flaw in my mission because I kept thinking how someone, if willingly wanted to, could break it. I may have realized this while in FRED, by just looking at the Ship Editor's Misc window, where you can set your ships invulnerable. After checking the Invulnerable option, I surely would have asked myself why I make Kappa 1 invulnerable if it could just jump out of someone got close to it. In this particular mission, this "someone" can only mean Alpha 1.

The player doesn't have to knowingly disobey his orders to break a mission. Sometimes he's unsure what tactic the mission will require and do something the FREDder didn't expect him to. In Casualties of War, there's a mission that involves taking down some sentry guns around a station in stage 1. Once it's done, the station deploys an Isis, which docks with the station. The docking triggers the second stage of the mission. On my first walkthrough I didn't know what I was supposed to do, so I destroyed the Isis, and the second stage of the mission never triggered. Lack of mission messages is partly responsible for this confusion, but had the FREDder simply made the Isis invulnerable, the second stage would have at least triggered. To use the aforementioned terms, even the theoretical possibility of breaking the mission is removed by making the Isis invulnerable.

Some specifics

Most of these safety fixes are best done before (not as the consequence of) testing. Below I will give some more specific tips.

Player Orders

Always get rid of all player-issuable orders for transports and above in size. You don't want the player to order an Elysium (docked with an Arcadia) to attack an enemy cruiser kilometers away, do you? Just imagine what it looks like that a transport carries with it the Arcadia towards the enemy cruiser.

Also consider removing issuable orders for all fighter and bomber wings that are supposed to act autonomously. You may have a wing of fighters with the sole purpose of escorting a freighter out. If you don't remove issuable orders, the player's C-3-(any number) order will affect this particular wing as well.

Guardians and Invulnerabilities

I guardian the engines on all friendly ships that must move or jump out. The AI may accidentally disable a ship in your convoy, which means it will never reach its destination unless you FRED a recovery op. In my book, all accidental engine disabling is a thinkable occurrence, so I defend against it unless confronted with an extreme scenario--like a Poseidon unloading cargo 50,000 meters away from all action.

Most of the time I don't see the need to guardian enemy ships like this. Let that poor enemy freighter drift in space if the player doesn't want to destroy it. If said enemy freighter is meant to escape, then guardian its engines and make it jump out immediately after the player gets within firing range.

I also guardian all warship turrets that play a major role in the mission. I don't know if the FREDder of Surrender, Belisarius! has made the Psamtik's beam indestructible. I bet he didn't. It surely isn't threatened by the Belisarius, but witty players may try to see what happens if the Psamtik's paper-thin beam turrets are destroyed. It's a thinkable occurrence.

Remember that you can switch guardians/invulnerabilities on/off via mission events. I temporarily make invulnerable all wingmen that are meant to deliver important lines at one point in a mission. Once the important messages are sent, make them vulnerable again.

destroyed-or-departed-delay

This is a very useful SEXP to make your missions more resilient. If paired with the "not" operator, it's actually a substitute for an is-ship-present.

I use this a bunch to determine if the given ship (usually wingmen) is in the area to send a message (or be talked to).

Here's an example:

$Formula: ( when 
   ( and 
      ( is-goal-true-delay "Deactivate" 9 ) 
      ( not 
         ( destroyed-or-departed-delay 
            0 
            "Protector" 
         )
      )
      ( string-equals 
         "@PlayerRole[Fighter]" 
         "Bomber" 
      )
      ( not 
         ( has-arrived-delay 0 "Larrik" ) 
      )
   )
   ( send-message 
      "#Washington" 
      "High" 
      "GTD - Time to attk Pr B" 
   )
)
+Name: Time to attk Prot
+Repeat Count: 1
+Interval: 1

Here, the Washington (your command ship) will advise you to attack the Protector (a Leviathan) if the following conditions are met:

  • The first primary objective has been fulfilled. Command doesn't want you to disregard your primary objectives just to take down a sole Levy.
  • The Protector is still present. If it has been destroyed or has departed, then there's no point in sending the message as there's nothing to talk about anymore. The mission involves 4 GTVA fighters, with selectable ship and loadout--the Herc II and Hornets being among them. It *is* a thinkable possibility that the player will attempt--and succeed--in destroying it.
  • The player is flying a bomber. If the player flew a fighter, Command would ask you to stay away from the Protector. (see below)
  • The Larrik hasn't arrived. The Larrik is a cruiser that arrives if you run out of time in the first stage of the mission. Command won't give you further orders if the mission has failed anyway.

Well, the above example has some other SEXPs in it, including "not" + has-arrived-delay, which is similar in purpose, it's just the inverse: It doesn't check if a given ship is still present, but rather if it is still NOT present.

Alternately, you can use...

num-ships-in-battle

Which, in one SEXP, covers that the given ships are destroyed, departed, or not arrived yet. I use this to determine if there are no hostiles in the area, listing all hostile participants of a given stage of the mission. Like this:

$Formula: ( when 
   ( and 
      ( is-event-true-delay 
         "Mercutio stage orders" 
         0 
      )
      ( = 
         ( num-ships-in-battle 
            "Mercutio#One" 
            "Mercutio#Two" 
            "Gemini" 
            "Leo" 
         )
         0 
      )
      ( not 
         ( is-destroyed-delay 0 "Washington" ) 
      )
   )
   ( add-goal 
      "Alpha" 
      ( ai-guard "Washington" 60 ) 
   )
   ( add-goal 
      "Beta" 
      ( ai-guard "Washington" 60 ) 
   )
   ( add-goal 
      "Delta" 
      ( ai-guard "Washington" 60 ) 
   )
)
+Name: Mercutio stage clear
+Repeat Count: 1
+Interval: 1

Which triggers if the second stage of the mission (called the "Mercutio stage" here) is won.

Had the is-event-true-delay "Mercutio stage orders" not been there, the event would trigger on startup, as none of the enemy ships listed under num-ships-in-battle is present on startup.

Furthermore, it wouldn't make sense for this event to trigger if the Washington is destroyed in stage 2, hence the "not" + is-destroyed-delay bit.

is-event-true, is-goal-true

Combined with "not," certain ships/events can be restricted to only appear in/after a certain stage of the mission. Here's an example event that demonstrates this:

$Formula: ( when 
   ( and 
      ( has-time-elapsed @TimeLimit[144] ) 
      ( not 
         ( is-goal-true-delay "Deactivate" 0 ) 
      )
   )
   ( do-nothing ) 
)
+Name: Timelimit hit
+Repeat Count: 1
+Interval: 1

This event comes true if the player doesn't complete the "Deactivate" mission goal within a given timeframe (144 seconds by default). The Larrik, the cruiser whose arrival forces Command to abandon the mission, arrives when this event comes true. As soon as the "Deactivate" mission goal is accomplished, the mission proceeds to the next stage. For later ships to arrive, the "Timelimit hit" event must not be true. It will also disable all nonautomated wingman messages, like this:

$Formula: ( when 
   ( and 
      ( is-event-true-delay 
         "Protector approaching" 
         2 
      )
      ( string-equals 
         "@PlayerRole[Fighter]" 
         "Fighter" 
      )
      ( not 
         ( destroyed-or-departed-delay 
            0 
            "Protector" 
         )
      )
      ( not 
         ( has-arrived-delay 0 "Larrik" ) 
      )
   )
   ( send-message-list 
      "#Washington" 
      "Medium" 
      "GTD - Protector approaching" 
      1750 
   )
)
+Name: F - Protector appr msg
+Repeat Count: 1
+Interval: 1

...which is meant to warn the player that the Protector has fired up its engines and is approaching. I don't think Command would bother with stating this observation after the mission has been aborted.

Closing Remarks

I'm a careful kind of FREDder. I'd rather implement more restrictions in my missions than see it broken. I'd rather see my messages not appear at all than see them appear when they are incongruous. I'm quite sure some of the examples/suggestions may seem frivolous to some of you. However, I have a good reason to make my missions like so. Just imagine that 100 players play your mission after its release. Supposing none of them ever restart the mission again, that means (on a theoretical level) 100 different kinds of player experience. They don't fly the same fighter with the same weapons. Some like issuing wingman orders, some don't. Some play on Very Easy, some on Insane. You can't take all possibilities into account, even if you tested the same mission more than 100 times.

It's not just math, but also psychology. These 100 people don't play the mission with the same tactic. Some are underachievers who just want to pass the mission, some are maximalists who will strive to complete all the mission objectives, and they keep experimenting with tactics/loadouts until they manage to beat all mission goals and get the best possible medal. The latter will more likely run into an unexpected scenario, due to his unorthodox gameplay style.

I haven't realized the extent two players can differ until I played the co-op Shivan gauntlet with someone. I picked the usual Perseus + Prom S combo, he chose a Boanerges. I didn't understand why anyone in his right mind would pick a bomber for a mission such as the gauntlet. He did it because of the Trebuchets. He fell back while I "tanked" for him in my Persie. His kill records were really good. I wouldn't have figured a bomber with Trebs would fare so well. After this, I always thoroughly consider whether to allow bombers and/or Trebuchets in my missions.

I used the illness metaphor to describe design flaws. Who, then, is the FREDder himself? Is he the doctor? I don't think this metaphor fits, because there are no doctors if there are no illnesses, and FREDding isn't just about making sure the missions won't break. Instead, the FREDder is an architect. An architect who not only must build a bridge that won't collapse, but also must make sure it will fulfill its intended uses. This is the bridge between the designer and the player.