Series Tutorial Introduction :
This tutorial will explain the inner workings of series files, as well as the other items associated with series - the missions, and the file game\cdlayout.iff. It will also briefly mention the file menu\splashfn.iff, which is used in relation to splash screens. Although you need to edit the EXE in order to specify the name of the series file you want the game to use, this is not covered here - refer to the EXE editing tutorial instead.
Missions will only be discussed very briefly, and only from the point of view of series files. Also, parts of the series file itself will be given only a general description - namely, the parts that deal with room files, which are a subject for a different tutorial. At the bottom of this tutorial, there is a link to a WCP Pascal version of WCP's original series file - it is a good idea to examine it closely after reading the tutorial. A note about terminology - for clarity's same, whenever I refer to a 'function', this means a set of commands joined together in a function structure like this: function <name>; begin <commands> end;
On the other hand, 'command' means just that - a one-line WCP command. All (known) WCP commands are listed inside lib\wcp.pas. While these two terms are generally used interchangeably, it is a good idea to distinguish between one and the other because their names are frequently very similar.
CDLAYOUT First, game\cdlayout.iff. This file is a sort of database of all the MIS files that the game uses (apart from the sim missions). If a mission is not in here, it cannot be used in the series. The structure of the file is very simple. It contains the form LOUT, which in turn contains one VERI chunk, and one LIST chunk. The LIST chunk contains the data about the missions (six lines for each mission), while the VERI chunk contains just one line of data. This line contains the number of missions in the LIST chunk. I am not sure, but I do not believe the game actually checks the VERI chunk, so this entry probably does not make any difference.
Here is a sample mission data entry inside the LIST chunk, with a basic description next to each entry:
// unknown enemy intro long 0 //mission number - you'll refer to this in the series file long 1 //CD number - do not change, unless you intend to use more than one :) cstring "intro.mis" //mission file to be used cstring "silence" //music file to be used in mission long 0 //number of mission name string, from language\ msnames.eng long 15 //unknown - 15 seems to work, so...
That is all there is to be done for cdlayout.iff. The series file The next, and obviously most important stage is the series file itself. You can call it whatever you like, just remember to edit the EXE appropriately (see EXE editing tutorial for details). In this section, I will be using examples drawn from the WCP series file. However, because of differences between the language originally used to build the series file, and Thomas Bruckner's WCP Pascal, what you will see here is not the original WCP series file, but rather a reconstruction of this file in WCP Pascal. Although I have checked this reconstruction quite thoroughly, it is possible that there are some mistakes in it as far as the mission branches go; nonetheless, the code itself works properly.
The file begins with all the usual stuff that appears in one form or another in most WCP Pascal files:
series prophecy; // the name of the SRS file after compilation
#SetOutputPath("$wcp$\mission\"); #include wcp;
The next stage is to add all the constants, and in particular, a constant for each mission. For example:
const //Series B MISSION_B1 = 0, MISSION_B2 = 1, MISSION_B3A = 2, MISSION_B3B = 3, MISSION_B4A = 4, MISSION_B4B = 5, SFX_RandomPavo_ENG_09 = 209, SFX_RandomPavo_ENG_10 = 211;
Why bother with constants for every mission? It is simply easier later on refer to missions using a constant rather than trying to remember what mission number 56 in cdlayout.iff was. Similarly, it's easier to declare constants for sound effects than trying to remember what each of WCP/SO's 233 sound effects (plus whatever sounds you add) is. As a general rule, use constants as much as possible, in any situation possible. A series (or indeed, a mission) with declared constants is no different in functionality to one which uses numbers directly, but the code is much more understandable.
Next are the variables. The difference between a variable and a constant is (obviously) that the value of a variable can change during execution of the code, while a constant is, uh, constant. The WCP series file uses many, many variables. A variable can be declared with a particular initial value, but this is not necessary. Here is just two or three to give you an idea of what they are used for:
var g_B1_WON = 0, g_B2_WON = 0, g_B3a_WON = 0, g_C1_ClickedOnDallas, g_ready_door_open, g_Attaboy = 0;
Of the above, perhaps the most significant variables are those first three - g_B1_WON and the like. These sort of variables are used as a means of communication between the series and the individual missions. Having declared the variable in the series file, you then declare it inside the relevant mission file, using the "external" form of declaration:
external g_B1_WON;
Having thus declared it, the mission can now alter this variable. Here is an example of how this is done in one of the Unknown Enemy missions:
if (m_Failed) then begin SF_ObjectiveSetFailure(mobj_locate_fralthi); g_UE_1_Won := false; SF_SetMissionFailure; end;
The above code is called at the player's home navpoint after the player returns from the mission, if the mission's internal variable "m_Failed" is true (ie., equal to 1). Once triggered, the code declares the mission's objective (the constant "mobj_locate_fralthi") as failed, and then sets the value of the external variable "g_UE_1_Won" as false (ie., equal to 0). On the other hand, had the "m_Failed" variable been false, the "g_UE_1_Won" variable would have been set to true.
Having gotten the hang of modifying these variables, let us now take a look at how they are used in the series file. The most important part of the series file is the "MCP" function. This function controls the campaign's flow; it plays certain movies (but not all - most movies in WCP are triggered by the missions), triggers the "get callsign" screen, the end credits, and the individual missions. Here are a few extracts from the WCP series file, with comments included directly in the code:
function MCP; begin // We begin by playing the intro movie SYS_PlayMovie(10); // And then we get the callsign MCP_GetCallsign;
//***************************** // Series B //***************************** // Despite its misleading name, Series B is the first series of WCP. MCP_RunMission(MISSION_B1); MCP_RunMission(MISSION_B2);
// The first mission branch. If the "g_B2_WON" variable is true, // that is, if the player fulfilled B2's win conditions, mission B3A // is played. If this is not the case, we go to B3B if (g_B2_WON) then MCP_RunMission(MISSION_B3A) else MCP_RunMission(MISSION_B3B);
We now skip a bit and go to Series C. There is an interesting type of branch there. Mission C1 sets two external variables - "g_C1_ENGINES_DAMAGED" and "g_C1_BRIDGE_DAMAGED" depending on the state of the Midway at the end of the mission. These variables are then used to determine what mission is played next, showing you just how endless the possibilities are for mission branch triggers. If you really wanted to, you could have a mission branch triggered if the player has exactly 95% damage and had killed an alien ship named Fred during the mission. Take a look:
//***************************** //SERIES C //***************************** MCP_RunMission(MISSION_C1); // Run c2a if completely successful // Run c2b if bridge damaged // Run c2c if engines damaged // End of game if both the engines and the bridge are damaged // (this is handled internally by mission C1) if ((g_C1_BRIDGE_DAMAGED <> 1) and (g_C1_ENGINES_DAMAGED <> 1)) then MCP_RunMission(MISSION_C2A); if (g_C1_BRIDGE_DAMAGED) then MCP_RunMission(MISSION_C2B); if (g_C1_ENGINES_DAMAGED) then MCP_RunMission(MISSION_C2C);
Now we will skip a big chunk of the MCP function, and go to Series H. We want to take a look at WCP's fighter-selection missions there. In particular, we will be interested in "MISSION_H1_choos". Take a careful look at this section of the campaign, and then I will explain what is going on.
//***************************** //SERIES H //***************************** MCP_RunMission(MISSION_H1_choos); // Allow the player to choose between H1y and H1z, // based on the ship he chooses. MCP_ShipSelector (ShipId_C_vampire, ShipId_C_devastator); if (MCP_GetSelectedShip = ShipId_C_vampire) then // Player chose Vampire MCP_RunMission(MISSION_H1Y) else // Player chose Devastator MCP_RunMission(MISSION_H1Z);
So, what is this MISSION_H1_choos? If you remember WCP, the first mission in Series H was the Vampire/Devastator mission... yet, the series contains the additional MISSION_H1_choos. This mission is very unusual - it contains no spaceflight section. It's only gameflow. Let's take a quick trip to the mission files to figure out what that means.
Gameflow, being the opposite of spaceflight, is the part of the game when you are onboard the Midway and can choose, for example, to go to the Rec Room. This gameflow element is triggered by the MS_RunGameflow command inside individual missions, usually prior to the MS_RunSpaceflight command (which launches the spaceflight section). This is all done inside the mission's MAIN function. For example, like this:
function MAIN; begin MS_RunGameflow(0); // gameflow MS_RunBriefing; // pre-mission ICIS-style briefing MS_RunSpaceflight(0); // spaceflight MS_RunDebriefing; // post-mission debriefing end;
A MAIN function does not need to contain MS_RunGameflow or MS_RunBriefing:
function MAIN; begin MS_RunSpaceflight(0); end;
In this case, following the end of the previous mission, the player would find himself in the cockpit. However, since he would not have the opportunity to move around the Midway, he would not have the opportunity to save the game; nor would the game be able to autosave - autosaving is only done in missions with gameflow.
The MAIN function can contain any of the commands that begin with "MS_". Those, however, will not be covered inside this tutorial. And what about H1_choos? Well, its MAIN function contains this:
function MAIN; SYS_PlayMovie (1560);
//everyone gets promoted to Wolf Pack MS_ChangePilotSquadron (Pilot_Player_1, SN_wolf_pack); MS_ChangePilotSquadron (Pilot_Player_2, SN_wolf_pack); MS_ChangePilotSquadron (Pilot_Player_3, SN_wolf_pack); MS_ChangePilotSquadron (Pilot_Player_4, SN_wolf_pack); MS_ChangePilotSquadron (Pilot_Player_5, SN_wolf_pack); MS_ChangePilotSquadron (Pilot_Player_6, SN_wolf_pack); MS_ChangePilotSquadron (Pilot_Player_7, SN_wolf_pack); MS_ChangePilotSquadron (Pilot_Player_8, SN_wolf_pack);
MS_ChangePilotSquadron (Pilot_Maestro, SN_wolf_pack); MS_ChangePilotSquadron (Pilot_Stiletto, SN_wolf_pack); MS_ChangePilotSquadron (Pilot_Zero, SN_wolf_pack);
//gameflow MS_RunGameflow(0);
//briefing start SYS_PlayMovie(2220); end;
Note the MS_ChangePilotSquadron command, by the way. The MAIN function plays movie 1560, then promotes the player, Maestro, Stiletto and Zero to WolfPack... but it promotes eight players! Is this perhaps evidence that WCP had been supposed to have a cooperative multiplayer campaign mode?
Anyway... after the promotions, the function runs the gameflow, giving the player an opportunity to autosave. Then, when the player is ready to proceed, he goes to the briefing room, and watches briefing movie 2220. And then the mission ends.
So, what is the point of Mission H1_choos? Originally, I thought that the choice of ship was done inside this gameflow-only mission. As you can see above however, this is not the case - the ship selector is triggered after H1_choos is finished, but before either of the two fighter-based missions are started. So, H1_choos does not influence the fighter choice, and it doesn't contain a spaceflight section. At first glance, it seems that the series would work exactly the same if this "mission" didn't exist at all - those movies and promotions could be placed in the next mission, after all.
The reason for H1_choos' existence becomes more obvious if we study how the MCP_ShipSelector command works. This command presents the player with a choice of two fighters. The choice is then memorised and triggered in the very next mission. The choice can also be checked using the command MCP_GetSelectedShip, and used to trigger a different mission depending on what fighter the player is flying.
The problem with this is that MCP_ShipSelector is only a single command. It's not a mission, so it cannot have a gameflow element. Therefore, you can't autosave prior to making the choice. What's more, once the choice is made, the only way to choose again is to complete the mission that came just before the choice. Thus, MCP_ShipSelector used by itself only works fine if you have no need to restart. What happens if you choose a ship, go out on the mission, die and then try to restart? There are two options:
1. If this mission has a gameflow element (ie., after choosing the ship, you go back to roaming the Midway, Cerberus, or wherever else you are), you will restart the mission WITHOUT making the choice again. So, when you play the mission the second time, instead of flying your chosen fighter, you will use whatever ship the mission assigns the player by default. Obviously, this is not an acceptable option.
2. On the other hand, if this mission has no gameflow element (after choosing the ship, you DON'T roam the Midway, you go straight into space or into the pre-mission briefing/movie/whatever), restarting will take you back to the start of the previous mission. You will be able to make the choice, but first you will need to complete the previous mission again. This is also not an acceptable option, unless... the previous mission has no spaceflight. It's dead easy to complete a mission when you don't need to get into the cockpit to do it, right? :)
So, that's the reason for the gameflow-only H1_choos. The mission is there to give you an opportunity to save prior to going into H1y or H1z, both of which contain an ICIS briefing and spaceflight, but no gameflow. An additional bonus is that in the History mission list, we do not see separate name entries for H1y and H1z - we only see a name for H1_choos. This suits us just fine, since the idea is to make the player think that H1y and H1z happen at the same time (and therefore, only one can be chosen).
The above also explains what happens during WCP's only real double mission - Defang The Beast. These are of ourse two missions. Like the H1_choos and H1y/H1z combination above, the first mission of Defang The Beast contains gameflow, while the second doesn't. The difference is that in this case both missions also contain spaceflight. So, upon dying in the second mission of the Defang The Beast duet, you have to go back to the start of the first one...
One more thing to note before we move on - MCP_GetSelectedShip. In the series H code we saw earlier, the player actually played a different mission depending on what ship he chose. This is not actually necessary - you could give the player the option of playing the same mission in two different ships. The series H code would then look like this:
//***************************** //SERIES H //***************************** MCP_RunMission(MISSION_H1_choos); // Allow the player to choose the ship, // then go into mission H1 regardless of the choice. MCP_ShipSelector (ShipId_C_vampire, ShipId_C_devastator); MCP_RunMission(MISSION_H1);
Of course, in this case you could make the series code even simpler - you could get rid of H1_choos and MCP_ShipSelector, and instead add MS_ShipSelector to H1's MAIN function, since MS_ShipSelector is identical in effects to MCP_ShipSelector. The downside of this would be that the player's wingmen (and the rest of the mission, obviously) would be unaffected by the fighter choice (there is no MS_GetSelectedShip). You'd be flying a Vampire for example, while they'd be flying the Devastators assigned to them by the mission code. Using separate missions depending on the player's choice gives you much more control.
We are just about finished with the MCP function. The last thing left to do is to explain the several "MCP_" commands which were not used in the examples above, but which can be very important for us.
MCP_EscapeMenu - this command triggers the options menu (usually triggered by the Escape key). It's really only useful at the end of the campaign, since at other times the player can trigger the menu by himself. MCP_Exit - very self-explanatory. This command exits the game. Useful at the end of the campaign. Can also be used in the middle of a campaign, if you want the player to exit the game after the loss of a particular mission (and after seeing some sort of losing scene, of course). MCP_ShowSplashScreen(num, sec) - this command shows a splash screen. The first variable (num) determines the number of the splash screen (drawn from menu\splashfn.iff, which will be explained in a moment), while the second determines the length of time in seconds that the screen will be displayed if the player doesn't skip it manually. MCP_WinningEndgame - shows the winning end game. In WCP, this is the bit where the credits scroll by on one half of the screen while the other half plays a mission file called wendgame.mis. In SO, it's identical to WCP's losing endgame, with credits scrolling by on top of an image (called menu\credbitm.iff).
Splash Screens Before we go on to the rest of the series file, let us take a quick look at the structure of the file menu\splashfn.iff:
IFF "SPLASHFN.IFF" { FORM "SPSN" { CHUNK "FILN" { cstring "splash_one" cstring "splash_two" } } }
...It's as simple as that. The FILN chunk contains a list of all the splash screen filenames. These images are stored inside the menu directory, and all have the extension IFF. For example, "splash_one" is actually menu\splash_one.iff. The first variable of the MCP_ShowSplashScreen command mentioned above is nothing more than a reference to a line number inside splashfn.iff. These numbers begin with 0. So, for example, MCP_ShowSplashScreen(0, 30) would display menu\splash_one.iff for 30 seconds. The Room-related Commands The main section of the series file is usually followed by the room-related functions (though of course, you could place these anywhere in the series file, as long as they're outside of the MAIN function). These functions basically tell the game how to react to the player's interaction with the room that he's in, between the missions. They use the set of commands that start with "GF_".
The name of the function for each 'hotspot' in the room is set in the room file itself (room files will be discussed in a separate tutorial). Each hotspot should, but doesn't need to, have references to three different functions, activated under different circumstances (mouse click on the hotspot, mouse moving over the hotspot, and mouse moving away from the hotspot). Note that while not every hotspot needs to have all three functions in the room file, all the functions specified in the room file MUST be present and accounted for in the series file.
As an example, we will take a look at WCP's "rec.rom" room file. This file contains a hotspot which refers to a function called G_RunTrainingSimulator (no prizes for guessing what this hotspot does, folks). So, in WCP's series file, we find the corresponding function...
function G_RunTrainingSimulator; begin GF_RunTrainingSimulator; end;
Pretty simple, isn't it? All it does is run one of WCP's "GF_" commands, which we'll go over in a moment. First, however, let's take a look at another function from WCP's series file. Although there's no limit to how complex room-related functions could get, WCP's room-related functions generally do not get more complex than this one:
function G_Simulator_Hover; begin if (GF_GetRoomVar(12)) then begin SYS_PlaySFX (SFX_SimDoorOpen); GF_SetRoomVar(12,0); end;
GF_SetRoomVar(13,0); if (GF_HotspotCurrentFrame > 8) then GF_SetRoomVar (13, 1); end;
What's going on above? This is the function which is activated when the player moves his mouse over the simulator hotspot (as opposed to moving away from the simulator hotspot - that's handled by G_Simulator_Fidget).
This function, in spite of its apparent complexity, in fact does almost nothing. First, it checks if the room variable number 12 is true (the GF_GetRoomVar(x) function, explained in further detail below). This variable is set true by the G_Simulator_Fidget function - in other words, if the player has just moved the mouse away from the simulator hotspot, variable number 12 is true (of course, you could use any other variable for this purpose). So, assuming that this is the case, the Hover function does two things - it plays the sound effect of the sim door opening, and sets the room variable 12 to false, so that if the player continues to hover over the sim, it won't happen again.
Regardless of whether the room variable 12 is true or not, the function then goes on to set the room variable number 13 to false (this variable is another link to the G_Simulator_Fidget function, which plays the sim closing sound effect if 13 is true). This is followed by one more IF statement, which checks if the sim hotspot's animation frame is greater than 8. If this is the case, it sets the room variable number 13 to true.
It is interesting that the function would set 13 to false, only to immediately set it back to true depending on how far the hotspot's animation has proceeded. The idea behind this is to ensire that if the player moves the mouse over the simulator hotspot for only an instant, the game will know that it shouldn't play the sim closing sound effect when the G_Simulator_Fidget function goes into effect. For clarity's sake, here's G_Simulator_Fidget. No explanations of what it does, since it is essentially the reverse version of G_Simulator_Hover.
function G_Simulator_Fidget; begin GF_SetRoomVar(12, 0); if (GF_HotspotCurrentFrame = 0) then GF_SetRoomVar(12, 1);
if (GF_GetRoomVar(13)) then begin SYS_PlaySFX (SFX_SimDoorClose); GF_SetRoomVar (13, 0); end; end;
The WCP series file contains many other hotspot functions, but it would be pointless to examine them right now. You may want to take a look at them later, however. For now, let's quickly go over all the "GF_" commands to see what these things can do.
GF_Exit - this command exits the gameflow, taking the player into whatever mission is up next. GF_ExitGame - exits the game, plain and simple. GF_GetRoomVar(variable) - this command checks the value of a specific room variable. It is used exactly the same way as any "SF_Get" commands. GF_HotspotCurrentFrame - checks the number of the frame currently being displayed by the active hotspot. Used the same as GF_GetRoomVar above. GF_HotspotDisable - I have not used this command yet. Presumably, however, it disables the active hotspot until further notice. GF_HotspotEnable - Haven't used this one either, but it's probably the 'further notice' to the command above ;). GF_LoadRoom(num) - This command switches the player to a different room. Note that the rooms are referred to not by name but by number, starting from 0. In order to use this command at all, you must declare at least two rooms in the current mission - the first one will become room number 0, the second will be 1, and so on. If you try to switch to a room which has not been declared in the current mission, the game will crash. To declare rooms inside a mission, use the "#SetRooms" command. GF_QuickLoad - This command allows the player to load a previously-saved game. GF_QuickSave - This command allows the player to save the game. GF_RunBriefingComm(series, comm) - This is the command used during the Hhrass Relay Station mission to play Dekker's briefing. Essentially, the command simply plays a special comm with a fancy computer-terminal background around it. GF_RunMainTerminal - This command allows the player to access the history terminal. GF_RunKillboard - If you can't guess the meaning of this one, why are you reading this? :) GF_RunObjectViewer - Runs the object viewer. GF_RunStellarMap - Runs the stellar map. Or at least it would, had the stellar map been implemented. As it is, the command leads you to a useless place-holder screen. GF_RunTrainingSimulator - Hmmm... GF_SetRoomVar(variable, TrueorFalse) - This command allows you to change the value assigned to a particular room variable. Presumably, you can assign any value to it; however, I have not seen any function where it would be worthwhile to assign a value other than true (1) or false (0).
This essentially concludes the room-related functions, which are the last 'compulsory' element of any series file. It is worth noting however that you can include other functions in the series file - these functions can then be accessed in your missions, provided you use the "#include" command.
And that's the end of the series tutorial. It is my hope that this tutorial covers everything that's needed for series files. However, keep in mind that I wrote this tutorial over a fairly lengthy period - as I write this, I can only vaguely remember the beginning of this file. Thus, it is quite possible that I skipped something or made a mistake somewhere. It is therefore a very good idea to take a close look at the WCP series file.
WCP series file
SO series file
Tutorial by Quarto