How To Script a Basic Tank What Tools Will You Need? Each of the following tools you'll need, and they're available from the MAD TA Download Page! 1. Scriptor V1.00 (do not use ANY other compiler). 2. Marcosoft's Lazy Scripter. 3. 3DO Builder. 4. HPI View/Hpi Pack or HPI Manager The next thing I want to say is that I won't answer any queries by email. If you have a problem, post it at the forum, that way other people with the same problem can find a solution :-) I have included a unit with this tutorial that I'd like to release at MAD TA some day. Everything is finished except the script. So... I challenge you to do it for me. For a bit of fun, the first person who sends me this unit with the script working flawlessly can have the pleasure of naming this weapon of war, and they will of course get the credit for scripting it (10 mins of fame - Gotta be worth it ;-) ). You'll find the unit in the Tutorial Challenge folder, and a pic along with side this file. Before I forget, you will need the MAD Stuff file from the top of our units section to see all of the textures correctly. The first thing to cover before you start scripting is object origins. These are little points about which an object will rotate. In 3DO Builder, you can see where an object origin lies by selecting the object, and looking for a blue dot. See it? Now, if you're scripting a tank, you have to make sure that some object origins are in the right place. Take a look at your turret. Where is the object origin? It should be at the center of the turret, preferably at the same height as the barrel(s). If it isn't here, then move the turret around, and use the "Set Current Position as Object Origin" function until you get it right (it won't take you long to see how this works). Once you have your origin in the right place, move your turret back to it's original place. It's important that you do NOT click on "Use Current Position as Object Origin" again, or you will have to start over. Now what about the barrels? If you want them to be able to turn up and down, the object origin has to be at the position where the barrel joins onto the turret. Imagine your barrel turning up and down around this point. Is it going to cause a problem? Or will it be fine. To help you decide, take a look at some of Cavedog's tank models (always refer to the old trusty when unsure) Now you're happy with your object origins (if you're not, don't worry, you'll soon find out if they're ok once the thing's scripted), we'll cover a few basic unwritten rules for scripting (say these every night aloud before going to sleep): Rule #1: Lazyness is happiness. If you can find an easy way around a problem that you know will work, take it! Rule #2: Don't trust anyone elses scripting! It doesn't matter who wrote it, there's no guarantee it's right. Rule #3: Never add or take away pieces of a model after scripting it without changing your "piece" line to match, or you'll find disfunctional pieces in TA, or crashes! Rule #4: If it doesn't compile, it doesn't work! Scriptor will give you an error message that may or may not be on the line where your mistake is. The mistake will ALWAYS be on or before the line in question. Rule #5: The script is read from top to bottom. When starting or calling scripts, the script being called must always be placed before the script calling it, or you will get an "unexpected identifier" error. Rule #6: It doesn't matter what a weapon fires, it's basic scripting is the same as any other. The main division in weapons is between turreted and non-turreted weapons. Rule #7: Script names are always followed by "( )", in these there might be additional info for Scriptor, but in most there's nothing but empty space between the brackets. Most of these rules you won't need for this tutorial, but they will be very useful to you in the future when you venture into some more complex stuff. Ok, familiar with the rules? No? Well, never mind, just don't forget they're there for future use! Let's get started on scripting the beast! We'll follow through in a step by step fashion to make this task easy. Don't worry about how your tank is put together, it'll work! 1. Start up Lazy Scripter. This is a program that follows Rule #1 :-). It will build a backbone for your script, saving you a LOT of time and energy doing a lot of boring work. Where it says "3DO Filename", clock on "Load", and direct the program to your Tank model. The next thing to do is sort out all the little details in this unit. Over on the right, you can see a number of "check" boxes. "Can move" is enabled, and so it "Compatible With Unit Viewer". Disable "Compatible With Unit Viewer". Unless your script is extremely complex, unit viewer will be able to handle it, so this line is unnecessary, and a waste of space. On the left you will find 2 more "check boxes", one marked can fire (which should be enabled), and another marked "Hide flares when not firing". If your model does not have visible flares (simply a point from which the weapon fires), disable "Hide Flares when not firing". Next, select where your weapons aim from. This is done by selecting the piece beside where it says "Select a piece to aim from" This should be prettty obvious since the program already gives a hint, but just incase - the "turret" should be the piece that turns around to face the heading of the enemy target. The aim from piece must be either your turret piece or a child object, and should have it's object origin as close to being in horizontal alignment with the barrels as possible. If your model has more than one turret, then you will need to enable the "Secondary turret" as well. 3 turrets require the "Tertiary Turret" enabled. The last thing you will need to do in this program is select a piece to fire from. One little thing that you'll have to get over is that Lazy Scripter doesn't allow for turrets with more than one barrel. But don't fret, we can fix that later. Just choose one of the firing pieces for the moment. Now click on "Create Script". Using your mouse, highlight all of the script, right click, and select "Copy" (copy and paste?). So we move on to the real scripting program... 2. You'll have something to fix in Scripter before you go into this program. Copy all of the files from the "include" subfolder into the main Scripter directory (you do this in Windows by the way, just incase you're thinking we've started the program up ;) ). 3. Start up Scriptor V1.00. Go to File and click "New". Paste all of the script you copied from Lazy Scripter into the new script, and save the file as the codename of your unit. Now you're ready to start! 4. Take a look at the header of your script, this is the first few lines, including the "piece", "static-var", "#include" and "#define" lines. There are a few things to learn about the header: A) The piece line should include every object that you have in 3DO Builder. It does at the moment, but if you change pieces in your model after scripting it, you'll need to make the changes in this line. The order of the objects is not important B) Whenever you need a static var, you have to place it's name into the static-var line so that Scriptor will recognise it. Don't worry about what a Static-var is yet, we'll be using them later, and I'll explain them then. C) The Include lines tell scriptor which of the .h files to place in the script, they include smokeunit, exptye, sfxtype, rockunit, hitweap, and stdtank. We'll pay a little attention to the Rockunit and Hitweap scripts because it is important to know when they are appropriate to use and when not. The Hitweap script causes the unit to rock whenever it is hit by a weapon. So, since we're building a tank we want this script to add extra effect. If we were scripting a kbot or aircraft, though, we wouldn't want them because it would look bad in TA. The Rockunit script causes the unit to rock when it fires a weapon. If your tank carries shells, we might expect this with the recoil, but if your tank carries a laser, it is unlikely to recoil, so delete the line! Again, for kbots and aircraft, this line is rarely used. D) The Define line instructs scriptor as to the value of certain variables. It can also be used to call piecenums (that's the scripting word for an object of your model, like "turret"). At the moment, you can see a line saying "#define SMOKEPIECE1 base" This line is telling scriptor that a variable talked about in the Smokeunit.h file is actually the base object. Easy, huh? You can have up to 4 smokepieces, if you want to add more, simply type #define SMOKEPIECE2 turrret" on the line below. Your first piece of scripting, and an absolute doddle. Just remember that define and include lines must be preceded by "#" so that Scriptor knows what you're talking about. 5. Ok, now you've gotten your head around the header, we'll move on to scripting the unit itself. You will be happy to know that the hardest part of this tutorial is already over :) We'll start off by visiting the all-important Create() script. So scroll down to find it, or use the search tool in Scriptor. The script starts with an opening bracket { and ends with a closing bracket }. A script may have other opening and closing brackets with the use of "If" and "While" commands, and we will see where this can occur later on. The create script is the first script called when a unit starts to build. You will see that it starts another script called "Smokeunit" (a part of your #include section). You will also need to set all static vars' initial values in here, and hide all flares and things you don't want to be seen. To hide a piece, all you have to do is type a line: hide "piecename" (excluding quotes) followed by a semicolon. eg hide flare1; hide flare2; Yes, it is that easy. So hide all your flares now. That's all you have to do in create for this script at the moment :-) Now, we'll move onto the the aim script. Scroll down until you find the AimPrimary(heading, pitch) script. The heading and pitch info tells TA that this script incorporates a direction to a target in both x and y axes. Don't worry about that, though. All you have to know is that it's there. The next thing you see is "Signal SIG_AIM1" followed by "set-signal-mask SIG_AIM1". This line makes sure that the weapon does not fire without turning to the correct heading when the enemy target is moving, so it's very important. You don't need to understand signals and masks yet, but they provide a method of killing an unwanted script (by "masking"). If the heading or pitch of a target changes, the Aim script is re-called, and the signal cancels out the old running script. Delete the line where it says "// Place aiming animation code here" because that's what you're going to do now! What we have to do is get our turret piece to turn towards the heading, and the "barrel" to turn toward the pitch, without the unit firing before the pieces have turned into their position. The speed at which our turret and barrel(s) should turn would depend on the type of weapon we're making, for example, a flak turret would want a turn rate of around 400, a missile tower 200, a medium tank 110, and a whopping great heavy tank (like Metron) less than 60. What we do is turn the turret to face the heading and the barrel(s) to face the pitch:. The spikey brackets are used around any figures given in all turn and spin commands. Move commands are different, using square brackets [ ]. It's just a convention, but Scriptor will hate you if you do it wrong. All statement lines are ended with a semicolon ; What's a statement, you ask? Well: "You are going to the shop" is a statement, because I am telling you that you're going to the shop. "Are you going to the shop?" is not a statement, it's a question! One other thing to note is that "turn to" actually means "Turn Around". I know it's not right, but that's the way it's done :P I am assuming that you know your x, y, and z axes (check them in 3DO Builder if you're not sure) turn turret to y-axis heading speed <120>; turn barrel1 to x-axis (0-pitch) speed <70>; turn barrel2 to x-axis (0-pitch) speed <70>; So now our turret is turning towards the heading ,and the barrels (there are 2 in my model) are turning toward the pitch. It is perfecly reasonablt to have your turret turning to the pitch. Simply because barrels are used by convention doesn't mean that's all you can use. One thing I forgot to mention is that we use (0-pitch). Don't worry about it, that's just the way it is. Now we have to stop our weapon from firing before it has turned to the correct position. For this we use a "wait" command, and it's done like this: wait-for-turn turret around y-axis; wait-for-turn barrel2 around x-axis; We have only waited for the second barrel to turn around the x-axis because if both barrels started from the same position at the same time, and at the same speed, it is reasonable to assume that both will arrive at the same time. The next line is already done for you, it says "start-script RestoreAfterDelay();" If you have a look around, you'll find this script, and see that it is again requesting for animations. We will tackle that next. The Return 1 line at the end of the AimPrimary is unusual, telling the script to re-start. Most scripts use return 0 (and it is the default line at the end of any script, even if you can't see it, unless otherwse specified). Now, your AimPrimary should look something like this: AimPrimary(heading, pitch) { signal SIG_AIM1; set-signal-mask SIG_AIM1; turn turret to y-axis heading speed <120>; turn barrel1 to x-axis (0-pitch) speed <70>; turn barreel2 to x-axis (0-pitch) speed <70>; wait-for-turn turret around y-axis; wait-for-turn barrel2 around x-axis; start-script RestoreAfterDelay(); return(TRUE); } Rightio! That's all done. The FirePrimry() script is next, so take a look for it. This script tells the unit what to do when the weapon is fired. There is already a default setup in there. But we can add to that. Again, don't worry if your turret has more than 1 barrel, just follow through, and we'll come to that. Currently the fire script has show/hide flare with a sleep command in between. If you don't have a visible flare (just a fire point), delete the 2 flare lines. Firstly, you might want a recoil on the barrel. Yes? This is pretty simple to do, and we use the MOVE command to do it. We want the barrel to move quickly backward, wait a little while, then slowly return, yes? So, here's how we do it, moving the barrel along the z-axis. Please note that if the barrel is aiming upward, and you move it along the z-axis, it will still move correctly, because the definition of the z-axis is built into the object itself. So, the barrel won't move through the top of the turret just because it's aiming upward. If you don't understand, don't worry. It will occur to you later. : FirePrimary() { Show flare1; move barrel1 to z-axis [-1.5] speed [200]; sleep 150; move barrel to z-axis [0.00] speed [2]; hide flare1; return 0; } The flare is shown, and the barrel starts moving at the SAME TIME because there are no sleep or wait-for commands in between. Similarly, the flare disappears, and the barrel starts to return at the same time. Sleep commands are calculated in milliseconds. So the script waits for 150 milliseconds before restoring the pieces. The Return 0 is particuarly important in the AimPrimary script, as the script can become troublesome if it is not present. It is good practice to include return 0 at the end of any scripting function, with the exception of the aim script. The amont you move the barrel depends on how big your unit is. I have just typed in [1.5] there, because it's a reasonable average figure. When you test your script, you might find it too bit or too small, but it's easy changed, right? If you have a 1 barrel script, well done! Your aim script is complete! If you have more than one barrel, then you will have to learn a little about Static Vars (even if you're finished your fire script, I recommend that you read this. They are a very important part of TA scripting, and you will use them a lot one day for all kinds of tasks). So what is a Static Var? A Static var is a named variable, it can be given any value when and where you choose. That's it! So where is it useful. Well, you can assign IF and while commands, like this IF (MyStaticVar = = 1) { blah blah blah.. } See the use? Now. I will explain the above statement in detail when we get to it, but for now, we need to create a static var for our function because we need to use an "if" command in order to get our barrels working... The great thing about static-vars is that you can call them almost any name (except ones that conflict with other functions like ARMORED, HEALTH etc. You will know if you've accidently bumped into one.) Go to your Static-var line, and add your name. It can be any name you choose, as long as you can remember it. By tradition, we call gun variables "gun1" for the primary, "gun2" for the secondary etc. But you don't have to. Now your static-var line should be like this (don't forget the comma in between!): static-var Restore_Delay, Gun1; Next, it's always good practice to give your static var a starting value. It doesn't matter what it's for. So, go to your create script, because this is the first script called when a unit starts building, and give it a value. You can put this anywhere in the script, but maybe best to keep the static vars together, so add the line below Restore_Delay = 2000; Gun1 = 1; That wan't hard, was it? The next bit isn't either. So, now we have to apply this to our FirePrimary script. This is where the "If" command comes in. There are a few conventions with If commands: 1. Your static var is placed between brackets within the IF command, showing Scriptor what you're talking about. Some commands can also be used within the IF statement, including AND, + - * / , OR. These won't be of any help to you yet, but in a later tutorial, you might see them cropping up. 2. When typing the IF command, and you have an equal to in the query, always put 2 equals like this = = (without the space) 3. Whatever you open, you close! Every IF command requires you to open a bracket, signifying the start of the command, put your closing bracket at the point where you wish the influence of the If command to stop. It's as simple as that! Now, since our Static Var starts as equal to 1, our IF command has to be able to handle that. What to do is include all of your original Fire script within the IF Statement. We can change the value of our Static Var command within the IF statement. The statement will still run through to the end, even if the variable is no longer equal to 1 after it starts. We will also see here an instance of where return 0 is used before the end of the script. FirePrimary { IF (Gun1 == 1) // there's 2" = " there! The value is kept within standard brackets. { // Here's your opening statement Show flare1; // notice how I have moved all of the info in the lines to be in vertical alignment Gun1 = 2; // with the opening/closing brackets? Makes the script much easier for the writer move barrel1 to z-axis [-1.5] speed [200]; // to read (unlike this statement which is messy)! :) sleep 150; move barrel to z-axis [0.00] speed [2]; hide flare1; return 0; // We want the script to finish here, because it has done all the work we want it to. } //And here's your closing statement! } Now, within the IF statement, we hanve changed the value of our static Var. Now... how can we use this? Well, if we call the script again, we know that Gun1 is going to be equal to 2, therefore the script will ignore all of the info within our IF query. So, we can either add our info for the second barrelbelow our IF query, or create another IF statement, and put the info in there. I am going to take the second option, because it makes the script more clear. Obviously our IF command will be IF the static Var is equal to 2. And in here, we will give all the info of the first IF command, except we will change it to apply to the second barrel: IF (Gun1 == 2) { Show flare2; Gun1 = 1; move barrel2 to z-axis [-1.5] speed [200]; sleep 150; move barre2 to z-axis [0.00] speed [2]; hide flare2; return 0; } See? Exactly the same except all the move commands and show flare are applied to barrel 2, and Gun2 becomes equal to 1 again :-) So, now your Fire script looks like this: FirePrimary() { IF (Gun1 == 1) { Show flare1; Gun1 = 2; move barrel1 to z-axis [-1.5] speed [200]; sleep 150; move barre1 to z-axis [0.00] speed [2]; hide flare1; return 0; } IF (Gun1 == 2) { Show flare2; Gun1 = 1; move barrel2 to z-axis [-1.5] speed [200]; sleep 150; move barre2 to z-axis [0.00] speed [2]; hide flare2; return 0; } } A nice piece of scripting, eh? If your turret has 3 barrels, you would obviously make Gun1 equal to 3 in the last statement, and create a third IF command, that would bring it back to 1. The next script to tackle is the mysterious QueryPrimary script. This script tells the unit where to fire from. If you have only 1 barrel, you don't need to do anything here, because you already set the value with Lazy Scripter. If you've got more than 1 barrel, then you will need another IF command, but you can use your old static var, since it already covers which barrel is firing. Currently your QueryPrimary() looks like this: QueryPrimary(piecenum) { piecenum = flare1; } But if you have more than one barrel, then obviously the weapon shouldn't be leaving from the same place every time. You've probably already figured how to do this (I hope I'm not turning you into a vegetable here). Just remember that you don't want to change the value of your static-var in this script: QueryPrimary(piecenum) { IF (Gun1 == 1) { piecenum = flare1; return 0; // because the script has now done all the work we want it to do - good practice! } IF (Gun1 == 2) { piecenum = flare2; return 0; } } Not hard, eh? It only gets easier from here! QueryPrimary is finished! Now, we want our turret and barrels to restore to their starting positions if the unit hasn't fired in a while. So, head over to the RestoreAfterDelay script. This is baby's stuff to you now. So far all you've got is : RestoreAfterDelay() { Sleep Restore_Delay; // add some stuff here!! return 0; } Now, all we have to do is tell our turret and barrels to turn back to "0" after sleeping for the Restore_Delay time. So, it's as simple as this: RestoreAfterDelay() { Sleep Restore_Delay; turn turret to y-axis <0.00> speed <80>; // Remember you can set speeds to whatever you like! turn barrel1 to x-axis <0.00> speed <50>; turn barrel2 to x-axis <0.00> speed <50>; return 0; // there are no wait-for commands here, because we don't want to wait for anything to } // happen! (It is a common mistake by newbies to add it, so don't!) Done :-) Your script is basically finished! All you have to do now is sort out a few cosmetic things... Go to your Killed script. You will see loads of lines of pieces exploding. There are 4 sections to the script, depending on "Severity" (= Health Percent lost, or would have been lost in fatal blow I think). For the largest part of your tank - the bit with all of the hull (which you've probably named base or body), we don't want it to fly around in the air if the tank is hit hard by an enemy weapon, right? Now at the moment, it will for the last 2 sections of this script (Explode piecenum type FALL | SMOKE | FIRE | EXPLODE_ON_HIT | BITMAP2; sends the piece flying around the screen emitting fire and smoke. But we don't want that. The biggest thing that could happen to our tank is for it to shatter, so... Delete the line exploding your body/base piece in the last 2 sections of the Killed Script, and add the following line instead: explode base type SHATTER | BITMAP4; The info pieces are separated by | It's on your keyboard at the bottom left - broken line above back-slash \ Got it? Right. That's it. Your script is done! Try compiling. If it compiles, you're in business, put it into your scripts folder for your unit, pack, and fire up TA. If not, then have a look to see where your error is. The chances are that you are missing a semicolon, or an end bracket. It's usually something silly like that ;) Hope you found this tutorial useful :-) M. Kenyon