Karajorma's Advanced FRED2 Lessons
I remember saying to Cetanu after he posted his Advanced FRED tutorial that as soon as I had released a mission I'd make one of my own. Well it's rather embarrassing to have only been able to get around to it 4 years later but here goes.
Since Birth of a Legend from BtRL is about the only released mission I've made which is really complex I'll be using that as the example. I suppose I might cull a few things out of King of the Hill for another tutorial one day but that mission has much less advanced FREDding in it.
The original plan for the mission called for ships to be invisible over a certain distance away from the player. The distance would get smaller depending on the difficulty level you were playing on. This proved to be a complete nightmare as far as playability was concerned cause often enemy ships would wander off after your wingmen and you'd never be able to figure out where either of them were.
However once I had set up the idea of a decreasing distance within which things would happen I found it useful to keep it for other events in the mission. For some strange reason which was necessary at the time I took a very long and complicated method of setting up the distance in the mission. Here's how I should have done it.
Notice how the name of the variable starts with CONST. I frequently use this if I'm setting up a variable to be used as a constant in the rest of the mission. It's slightly naughty cause I'm actually altering the value of the constant but it's done at t=0 and it's never touched again. Why would you set up a variable as a constant I hear the non-programmers amongst you ask? Quite simply if I've got a value of 340 dotted throughout my mission the easiest way to change that later is with a search & replace in notepad. If instead I've used CONST-WeaponRange instead I only need to change the default value of the variable.
Avoiding problems with Gauntlet and Slingshot
One of the main ideas of the mission was that the two pilots Gauntlet and Slingshot would be on point duty but would run into Scar and quickly get out of their depth. The player was meant to be distracted in the meanwhile dealing with more Cylon Raiders. However there is always the chance that during the battle with the Raiders the player would stumble into Gauntlet and Slingshot and notice that they were quietly sitting there sending panicked messages. For this reason once the player is out of range of their wing both ships are transported way out of range of the player
Of course quite a lot had to be done to prevent them from further messing up the mission. Both ships are made stealthy and friendly stealthy, invisible (for good measure), are moved 1,000km away, given orders to play dead, given orders to remain silent and finally protected.
And I still suspect that one day I'll hear a bug report that one of the Cylon Raiders went after them. :D
At one point in the mission the remainder of the players wing is meant to remain behind and guard a mining vessel. The player will head off to investigate a crippled fighter and end up in a 1 on 1 combat with the infamous Cylon ace Scar. The problem was that some method was needed of checking that the player didn't decide to bring his wingmen along with him to help out. However if the player was smart enough to lead Scar back to his wingmen he shouldn't be penalised for this even if the AI later decided to follow Scar away from the mining ship it should have been guarding.
The first thing to do was to check if the player was ordering his wingmen to follow him. This was done using the little used Order SEXP. In an event before this one the reset-orders SEXP was used to make sure that only orders given after the wingmen were told to remain behind were counted. (Don't bother looking for Reset-Orders in 3.6.9. It was only added after it came out. CVS builds and 3.6.10 will have it).
A similar event was needed for Blue 2 and Blue Wing (Different events were used cause the messages were different for Blue 2 and Blue 3).
If any of those events were triggered then a nasty trap was set for the player in the form of an additional wing of ships which would go after the mining vessel. However the trap wouldn't automatically spring shut. The player had to actually be guilty of getting his wingmen to abandon their duty. If he relented and sent them back to guard the miners the trap might be avoided.
First thing that had to be checked was if the player had dragged his wingmen too far away from the ship they were supposed to be guarding.
Notice the reappearance of that CONST variable? As you can see the harder the difficulty level you play on the shorter the distance you have to drag the wingmen away before they trigger the arrival of the extra wing of Raiders. Similar SEXPs were used to check for the same thing with Blue 3 or Blue wing.
Once Scar had arrived the player could order his wingmen to abandon their post and attack him directly. So this needed to be checked for too
Again the distance from the mining vessel was also checked as the second part of that AND SEXP to check that the player had dragged his wingmen out of range.
Now the entire event could be used as the arrival cue for the extra enemy wing.
One problem with the mission was that there were two possible ways to encounter Scar. If you chose to disobey Gauntlet's orders you could follow her and encounter Scar together with the other Raiders in his wing. If you obeyed her orders you would encounter him later on in the mission on his own in a completely different area of space.
This did raise a problem. There had to be two ships in the mission with the same name. Fortunately there is an old trick to get around this. Freespace can't display certain characters that FRED can. Therefore a ship named Fractalÿ4 will appear in-game as Fractal 4 but will be treated by FRED as a completely different ship. This allowed me to give the illusion of having a ship appear to be a member of a wing which didn't actually appear during that particular run through the mission. (Note: There is actually a better way to do that in 3.6.10 builds. Anything after a # in a ship name is ignored. If I were building the mission again now I would have named that ship Fractal 4#Scar instead.)
Special music for Scar
A rather easy one this but Scar's appearance in the mission demanded the use of his own special theme tune from the show rather than the music that was being used for the rest of the mission. Use of the change-soundtrack a little earlier on in the mission allowed Scar to appear with his own distinctive theme and leave the players who knew the shows soundtrack in little doubt just who they was facing.
Banter presented a bit of a challenge. We had seven sets of mission banter, five of these were general chit-chat that could be played in any run through of the mission. The other two would only appear if the player had disobeyed orders in the previous mission and which one was played would be dependant on whether one of the player's wingmen (Chum) had survived the mission or not. Furthermore in order to improve the replayability of the mission I didn't want any particular set of dialogue to repeat until all the sets that it was possible to hear for a mission had already been played once. I also wanted the system to be flexible in case a problem forced me to pull one of the sets of banter. Lastly I wanted the five standard sets to play in random order or else they'd also be predictable.
As you can imagine this was quite a tall order. The answer obviously required the use of player persistent variables as this was the only way to record which sets of dialogue had already been played. However setting, updating and selecting between seven different persistent variables if I used one for each would be a nightmare. It would be nice if I could store all the information on which messages had played in a single variable. In C you could do this using bitwise operations but I couldn't see any way to make that work in FRED. However there was a way to do something similar.
Take the first seven prime numbers as representatives for the seven different sets of banter (2,3,5,7,11,13,17) and multiply them together and store the result (510510) as the initial value of a variable. This variable can be used to which messages have now been played. If the value of the variable is divisible by 17 it means that the message that corresponds to number 17 (i.e the 7th message) hasn't been played yet. When a message was played I'd simply divide the variable by the prime number that corresponded to it and it would be struck off the list. Once the variable reached a value of 1 I'd know that everything had been played and I could reset the value. And checking whether the number was exactly divisible by another number is simple. I'd just use the mod SEXP.
Sound complicated? It was. But compared with the monster list of events to do it any other way it seemed simple. So it was time to code it.
First I needed a reset event. This was simple. All it had to do was reset the variable to my magic number whenever the variable reached a value of 1.
The next event would deal with the mission banter if this was the first run though the mission and the player had saved Chum. The use of the previous event and goal SEXPs allowed me to check what had happened in the previous mission (A persistent variable would have worked just as well). The next step is to check if the MessagesLeft variable is divisible by 2 (meaning that the set of banter dealing with Chum's survival hasn't been played). If it hasn't then two variables are set. ChooseBanter tells the game whether to keep trying to pick a set to play and SelectedBanter holds the prime number corresponding to the banter set we picked.
Then there was a corresponding event for if Chum had been killed. This was basically the same as the above event except that SelectedBanter was set to 3 instead of 2 if he had died.
The 4th event was designed to be triggered if the player had obeyed his orders in the previous missions. If MessagesLeft was divisible by 2 it meant that the Chum Lived and Chum died banter sets were still in play. This event removed them if the player shouldn't be hearing them by dividing the value of MessagesLeft by 6 (I had to divide by 2*3 because I wanted both messages removed. I could have divided by 2 then 3 of course).
The 5th event did the real work. This was the one that would pick one of the five main sets of banter at random. The five remaining primes are entered into a random-multiple-of list so that one is picked. The game checks if that banter has been played and if it finds one that hasn't it sets the SelectedBanter value to that number.
To be honest now that I look at it I don't think the string-to-int is needed and I probably should have set ChooseBanter to NO but since it doesn't hurt, who cares?
The remaining events dealt with actually playing the banter and were basically the same apart from the messages sent. First they would check what value SelectedBanter was currently set to. If the number corresponded to that events send-message-list then it would trigger.
First thing it would do is divide MessagesLeft to indicate that message had now been played. Then it would send the messages. Finally it would set a value for the PersonaActivation variable. This was because at the start of the game all the wingmen were silenced to stop them butting in with standard chat if the player decided to tell them to form on his wing. PersonaActivation simply held the length of this set of banter so a later event could turn them back on at the right time.
The only remaining issue was with the Chum Lives/Chum Dies banter playing events. These two are obviously mutually exclusive so instead of dividing MessagesLeft by 2 or 3 they divided it by 6 to mark both messages as unplayable and remove them from the list at the same time.
Okay, those are some of the more complex issues I faced while making the mission. Hope this stirs some ideas amongst the FREDders on this board.
Well I did say I might do a part two based on King of the Hill and since I recently got a request for it, here goes. :) For those who haven't played it King of the Hill is a multiplayer TvT mission with a bit of a twist. Instead of winning by destroying the enemy forces the idea was to enter and hold an area of space for predetermined length of time (2 minutes) without dying. The first player to enter the area is King of the Hill. He remains the king until he either exits the area or is killed. At this point the player who has been in the area the longest becomes the new king.
Doing this required very heavy use of variables. I'm assuming that anyone reading this has already read the section in my FAQ on using variables and at least has an idea how to create and modify variables even if they don't understand exactly how or why to use them.
Setting up the constants
As I did in Birth of a Legend I've set up two variables to act as constants. In this case they are true constants because I never alter them during the mission.
CONST-KotH-Distance is the radius of the area that the player has to hold in order to win. In this case 1km. CONST-WinningCount is the amount of time the player needs to hold the hill in order to win. 120 seconds in this case.
By setting these constants and using them in the mission I left myself some wiggle room if I found during balancing that they were too big or too small. All I'd have to do is alter the constant and the rest of the mission would automatically update.
Who is the King?
The first problem that needs solving is telling who is going to be King. To do that I need to determine who has been in a certain area of space for the longest time. So I added a waypoint in the middle of the hill area and simply added to a variable every time the ship in question was close to it. The event has a high repeat count and a 1 second interval same as many of the events in this mission. This event checks Green 1. Obviously I needed a separate variable and event for each ship.
Then I needed a similar event resetting the counter if the ship left the hill area. This was basically the reverse of the previous event. Finally I needed an event to check who had been on the hill the longest. Again I needed one of these for each ship.
REQ-King-check is simply a boolean which tells me if I need to crown a new king. The CurrentKing variable however is a little more interesting. Due to the way FRED sanity checks everything on mission load this had to point at a ship on mission load. I solved this by adding a stealthed fighter miles off in the distance called A Ship. Both variables are string variables. I could have used a number variable for the first one (with 0 representing false and 1 representing true) but I've always preferred to use a proper Boolean hence the YES/NO options.
The upshot of this event is that if Green 1 has been on the hill longer than anyone else he'll become king.
Requesting A New King
If the player who was king exited the game or the hill someone else should become king. This was fairly simple.
Setting this variable to YES triggers another event which in turn resets the king.
The programmers amongst you will recognise this as a crude attempt at a function/method call in FRED. :)
I did things this way because when first designed there were several ways that a king could be reset and it made sense to have that in its own event rather than copying and pasting it into multiple places. Later on I trimmed out the other paths and was left with this rather strange piece of code as a result.
As you can see the event sets everything back to its default state except for REQ-King-check which as you saw above tells the game to select a new king. KingCount is a simple counter which is counting how long the king has been king. It's updated in the same way as counters for each ship were updated except that instead of testing green 1 etc it simply tests the distance CurrentKing is from the node.
Saying who is king
Next came a few events to notify the players who was winning. These ended up a little more complicated than they absolutely needed to be because rather than reporting how long someone has been king for in seconds it does it in second and minutes. (Someone sent me a suggestion that I should do this along with an edited mission but I'll be damned if I can remember who!).
First I only want updates every 10 seconds so we test whether KingCount is exactly divisible by 10 using the mod SEXP. We test the value of WinningShip because we don't want to send messages after someone has won.
Since I want to give a special warning when someone is one minute or 30 seconds from winning I test that's not true.
The action part of the event then sets the value of a couple of variables which will be used in the message. The message text is as follows
"$CurrentKing holds the hill and has been king of the hill for $KingMin minutes $KingSec seconds."
FS2 checks if there is a variable with the name following the $ symbol and replaces it with the value if there is. Since these messages will be played on the client machines as well as the host all variables used in messages had to be marked as network variables so that they were also updated on the client machines. If this wasn't done the messages would have outputted the default values for the variables in every message. Only variables that were used in messages were marked this way though. The game is updating several variables every second and it would completely lag everything if all the updates of those were sent out to the clients. Furthermore the clients wouldn't do anything with them anyway!
A similar event and message were used when the clock was still under 60 seconds for any ship (rather than "y holds the hill and has been king of the hill for 0 minutes x seconds").
Anyway, those were the more complicated problems I remember having in the mission.