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