rich_is_bored@Posted: Sun Aug 08, 2004 6:16 pm :
Level Scripting Tutorial 3

Introduction

Welcome to the third and last of my scripting tutorials. Well, that's probably not entirely true. Maybe I'll figure something else out and it will be worth writing about.

Anyway, In this tutorial we will take the mechanical arm from the last tutorial as well as some new pieces and create a machine. It's a pretty simple machine and it isn't perfect. But I wasn't making eye candy to begin with.

Advanced: Creating a Machine

Just like in the previous tutorials I am streamlining the process. I am providing you will everything you will need.

The zip is linked to below.

FILE IS NO LONGER AVAILABLE

Planning Your Project

The first thing to do is to plan. You need to know what you're trying to accomplish and how to do it.

One thing that is necessary is an idea. So here is the point where I am going to trust you...

You see, I decided it would be easier to just show you it in action than draw out a bunch of diagrams. This means that everything needed including the script is included in this zip file. Of course in order to learn anything you're going to rename, move, or delete the script after you've seen what we are going to do.

So, this means your going to run the map and observe the machine running through its cycle. So...

Extract the zip to [Your Doom 3 Directory]\base. Fire up doom, run the map rich_scripting_tutorial_3. After it loads and starts up
you'll see these crazy robotic arms moving around and doing all kinds of weird nonsense. As you can see, I didn't do a good job of planning. Yeah, I'm not proud of it but it'll work for this tutorial.

As you can see, the robotic clasp picks up a canister and the welding gun will meet up with it and fire. Next the clasp will place the canister back and the sequence will repeat with the second canister.

Now you should have a pretty good idea what we will be trying to accomplish. But, before we start scripting away I need to give you a good idea how the parts come together to form arms and such.

*missing image*
http://www.doom3world.org/richard/grabarm.png

And here are all the parts of the clasp. From here on out we will just refer to this as the grab arm. You should be familiar with it already (Previous tutorial, Hint, Hint).

It is also a good idea to come up with a naming convention when we are working with several parts. This will make it easier to tell what is where. I am going to name these parts from the top down.

Here are the names for the grab arm components from top to bottom. We will also be forming a child parent tree. The items at the bottom will be parented to the items above it. Now take into consideration that the fingers will all be parented to the finger joint a.k.a. $GrabArm_Base3.

$GrabArm_Base1
$GrabArm_Swivel1
$GrabArm_Base2
$GrabArm_Swivel2
$GrabArm_Base3
$GrabArm_Finger1
$GrabArm_Finger2
$GrabArm_Finger3


*missing image*
http://www.doom3world.org/richard/sparkarm.png

It's the same deal here. I will name the parts from top to bottom. Again the items at the bottom will be child objects to the items above.

$SparkArm_Base1
$SparkArm_Swivel1
$SparkArm_Base2
$SparkArm_Swivel2
$SparkArm_Base3
$SparkArm_Gun1


We'll need a name for both canisters as well. We can name them from left to right to keep things simple. You all read from left to right don't you?

$Can1
$Can2


Nutz 'n' Bolts: Screwing around in the Map Editor

After you have planned... umm yeah, you'll fire up the editor and begin putting parts into place. It's not over yet though; you'll still need to run through the motions to figure out where everything goes and what you'll need to make it work.

Go ahead and load up rich_scripting_tutorial_3.map in the editor. You'll see that the parts are laid out in the editor similar to the diagram below.

*missing image*
http://www.doom3world.org/richard/layout.png

Of course in the editor you'll see several entities that are not displayed here. All of these are speakers and particle generators except for three. See if you can find them.

Hint: Take a look at the base of the grab arm (where it attaches to the machine) and to the left, directly above each canister.

Guess what these are for? Well, the grab arm needs to move to each canister and then back to its original position. These entities act as markers so the arm will know where to go. Basically they are three func_statics with the following names.

$Can_Pos1
$Can_Pos2
$Arm_Pos1


It's pretty straight forward. They're named from left to right again.

We aren't interested in the sounds and special effects yet. We need to get this puppy moving before we think about what sounds or special effects to use.

Playing God: Scripting it to Life

This would be the part where you delete, rename, move, or in other words, get rid of the script. Unless you wanna steal my work thief! :P

So, start up your favorite text editor editpad... What?! You haven't downloaded editpad yet?! Ah, forget it. Stick to whatever you're using. If you haven't changed yet you never will. Although I was thinking about working on a colored text filter specifically for doom 3 scripting. Oh well.

Alright. We are definitely going to need some comments in this code so we don't get all confused.

So start out with a simple script like this.

Code:
////////////////////////////////////////////////////
//
//   Setup binds, etc...
//
////////////////////////////////////////////////////
void setup_objects()
{

// SparkArm Binds

// GrabArm Binds

}

////////////////////////////////////////////////////
//
//   Part Movement...
//
////////////////////////////////////////////////////

////////////////////////////////////////////////////
//
//   MAIN
//
////////////////////////////////////////////////////

void main ()
{
   setup_objects ();
   sys.wait(3);
}


You should already know what's coming next. That's right. We need to setup our binds. Let's take care of the spark arm. You should know where this goes by now.

Hint: We are setting up objects right?

Code:
// SparkArm Binds

   $SparkArm_Gun1.bind($SparkArm_Base3);
   $SparkArm_Base3.bind($SparkArm_Swivel2);
   $SparkArm_Swivel2.bind($SparkArm_Base2);
   $SparkArm_Base2.bind($SparkArm_Swivel1);
   $SparkArm_Swivel1.bind($SparkArm_Base1);


And from here we setup our binds for the grab arm.

Code:
// GrabArm Binds

   $GrabArm_Finger1.bind($GrabArm_Base3);
   $GrabArm_Finger2.bind($GrabArm_Base3);
   $GrabArm_Finger3.bind($GrabArm_Base3);
   $GrabArm_Base3.bind($GrabArm_Swivel2);
   $GrabArm_Swivel2.bind($GrabArm_Base2);
   $GrabArm_Base2.bind($GrabArm_Swivel1);
   $GrabArm_Swivel1.bind($GrabArm_Base1);


Now, we are ready to move some stuff. Let's setup a motion for the grab arm first since it's the more difficult of the two.

We aren't interested in covering the entire motion at first. In fact, we will only write enough code to grasp the first canister and move it into position. We are breaking the cycle into each component. (Remember talking about that from the first tutorial?)

First we will give this motion a name. Hmmm?. GrabCanister sounds good. (Pretty self explanatory huh?)

So let's make a function like this.

Code:
void GrabCanister()
{
}


Now, I'll explain what needs to happen on each line and you type up the code.

Move GrabArm_Base1 to Can_Pos1.
Wait for GrabArm_Base1 to finish it's movement.
Rotate GrabArm_Finger1 once 45 degrees on the X axis.
Rotate GrabArm_Finger2 once 45 degrees on the X axis.
Rotate GrabArm_Finger3 once 45 degrees on the X axis.
Move Can1 up 30.
Wait for Can1 to finish it's movement.
Rotate GrabArm_Finger1 once -35 degrees on the X axis.
Rotate GrabArm_Finger2 once -35 degrees on the X axis.
Rotate GrabArm_Finger3 once -35 degrees on the X axis.
Wait for Finger1 to finish it's movement. All three fingers should finish at the same time.
Bind Can1 to GrabArm_Base3.
Rotate GrabArm_Base3 once 360 degrees on the Y axis.
Wait for GrabArm_Base3 to finish it's movement.
Rotate GrabArm_Base2 once 90 degrees on the Y axis.
Wait for GrabArm_Base2 to finish it's movement.
Rotate GrabArm_Swivel2 once 90 degrees on the X axis.
Wait for GrabArm_Swivel2 to finish it's movement.
Move GrabArm_Base1 to Arm_Pos1.
Rotate GrabArm_Base2 once -90 on the Y axis.
Wait for GrabArm_Base2 to finish it's movement.

Now here is the code below. Yeah you could've just cheated and copied and pasted from below. But that's really lame.

Code:
void GrabCanister()
{
   $GrabArm_Base1.moveTo($Can_Pos1);
   sys.waitFor($GrabArm_Base1);
   $GrabArm_Finger1.rotateOnce('45 0 0');
   $GrabArm_Finger2.rotateOnce('45 0 0');
   $GrabArm_Finger3.rotateOnce('45 0 0');
   Can1.move(UP, 30);
   sys.waitFor($Can1);
   $GrabArm_Finger1.rotateOnce('-35 0 0');
   $GrabArm_Finger2.rotateOnce('-35 0 0');
   $GrabArm_Finger3.rotateOnce('-35 0 0');
   sys.waitFor($GrabArm_Finger1);
   Can1.bind($GrabArm_Base3);
   $GrabArm_Base3.rotateOnce('0 360 0');
   sys.waitFor($GrabArm_Base3);
   $GrabArm_Base2.rotateOnce('0 90 0');
   sys.waitFor($GrabArm_Base2);
   $GrabArm_Swivel2.rotateOnce('90 0 0');
   sys.waitFor($GrabArm_Swivel2);
   $GrabArm_Base1.moveTo($Arm_Pos1);
   $GrabArm_Base2.rotateOnce('0 -90 0');
   sys.waitFor($GrabArm_Base2);
}


Now add this function to the main function like this.

Code:
void main ()
{
   setup_objects ();
   sys.wait(3);
   GrabCanister();
}


Save your script as rich_scripting_tutorial_3.script and fire up the map. You've got a
mechanical arm picking up a canister.

You know what is really interesting? When it picks up the other canister it pretty much runs through the same set of motions doesn't it? Wouldn't it save you a lot of trouble if you could just call the same function to pick up the other canister? But how?


This is where we bring parameters into the equation. You see, every function has a set of parentheses appended to the end of it. You can pass objects to the functions by placing the object's names within these parentheses. All you have to do is change the function slightly.

Let's see what objects will change in the GrabCanister function when I am picking up the second canister? Hmmm? Just two, Can1 and Can_Pos1. Everything else remains the same.

We will need to create alias names for the objects that change. These are called reference variables. We cannot use the names Can1, Can2, Can_Pos1, or Can_Pos2 in the function in order for this to work. We need to use these reference variables as aliases for the objects that are passed to the function instead. In the function we will use the names Can and CanPos.

When we call this modified form of the GrabCanister function it will look like this when we pass Can1 and Can_Pos1.

Code:
GrabCanister($Can_Pos1, $Can1);


Or when we pass Can2 and Can_Pos2 it will look like this.

Code:
GrabCanister($Can_Pos2, $Can2);


And when we declare our function we will now change the line to look like this.

Code:
void GrabCanister(entity CanPos, entity Can)
{


Notice that I defined the type of variables by using the keyword entity. This is because we are passing entity names to these reference variables.

And to modify the code of the function we will replace every Can1 with Can and every Can_Pos1 with CanPos. The code should look like this.

Code:
void GrabCanister(entity CanPos, entity Can)
{
   $GrabArm_Base1.moveTo(CanPos);
   sys.waitFor($GrabArm_Base1);
   $GrabArm_Finger1.rotateOnce('45 0 0');
   $GrabArm_Finger2.rotateOnce('45 0 0');
   $GrabArm_Finger3.rotateOnce('45 0 0');
   Can.move(UP, 30);
   sys.waitFor(Can);
   $GrabArm_Finger1.rotateOnce('-35 0 0');
   $GrabArm_Finger2.rotateOnce('-35 0 0');
   $GrabArm_Finger3.rotateOnce('-35 0 0');
   sys.waitFor($GrabArm_Finger1);
   Can.bind($GrabArm_Base3);
   $GrabArm_Base3.rotateOnce('0 360 0');
   sys.waitFor($GrabArm_Base3);
   $GrabArm_Base2.rotateOnce('0 90 0');
   sys.waitFor($GrabArm_Base2);
   $GrabArm_Swivel2.rotateOnce('90 0 0');
   sys.waitFor($GrabArm_Swivel2);
   $GrabArm_Base1.moveTo($Arm_Pos1);
   $GrabArm_Base2.rotateOnce('0 -90 0');
   sys.waitFor($GrabArm_Base2);
}


And now in the main function I will change the call to GrabCanister to look like this.

Code:
void main ()
{
   setup_objects ();
   sys.wait(3);
   GrabCanister($Can_Pos2, $Can2);
}


Save your script and fire up the map. Now we are looking at some impressive stuff. Here you are reusing the same code to move a completely different canister.So from here we can make a function to move the place the canister back. We'll call it simply PlaceCanister.

Basically we are doing what we did above backwards. So here is the code.

Code:
void PlaceCanister(entity CanPos, entity Can)
{
   $GrabArm_Base2.rotateOnce('0 90 0');
   $GrabArm_Base1.moveTo(CanPos);
   sys.waitFor($GrabArm_Base2);
   $GrabArm_Swivel2.rotateOnce('-90 0 0');
   sys.waitFor($GrabArm_Swivel2);
   $GrabArm_Base3.rotateOnce('0 -360 0');
   sys.waitFor($GrabArm_Base3);
   Can.unbind();
   $GrabArm_Finger1.rotateOnce('35 0 0');
   $GrabArm_Finger2.rotateOnce('35 0 0');
   $GrabArm_Finger3.rotateOnce('35 0 0');

   sys.waitFor($GrabArm_Finger1);
   Can.rotateTo('0 0 0');
   Can.move(DOWN, 30);
   sys.waitFor(Can);
   $GrabArm_Finger1.rotateOnce('-45 0 0');
   $GrabArm_Finger2.rotateOnce('-45 0 0');
   $GrabArm_Finger3.rotateOnce('-45 0 0');
   $GrabArm_Base1.moveTo($Arm_Pos1);
   $GrabArm_Base2.rotateTo('0 0 0');
   sys.waitFor($GrabArm_Base1);
}


There are two lines I do want you to take note of though. Notice in this function the unbind command. This is because I want to detach the canister from the mechanical arm rather than bind it. Also notice how I am not placing anything in the parentheses. This is because you can only bind an object to one other object at a time. So there is no need to tell the engine what to detach the canister from. There is only one possible object it could be detached from in the first place.

The second line to look at is this line...

Code:
$GrabArm_Base2.rotateTo('0 0 0');


The rotateTo command is used here to ensure that GrabArm_Base2 is positioned the same as it was when the map first loaded. Without this command the base of the arm would start bending in the wrong direction. Go ahead and comment it out and give it a test run if you're curious.

Now we throw a call to PlaceCanister immediately following the call to GrabCanister.

Code:
void main ()
{
   setup_objects ();
   sys.wait(3);
   GrabCanister($Can_Pos2, $Can2);
   PlaceCanister($Can_Pos2, $Can2);
}


Save the script and start up the map. It will now grab the second canister and place it back. Go ahead and add another call to both GrabCanister and PlaceCanister each pointing to the first canister instead. It should look like this.

Code:
void main ()
{
   setup_objects ();
   sys.wait(3);
   GrabCanister($Can_Pos2, $Can2);
   PlaceCanister($Can_Pos2, $Can2);
   GrabCanister($Can_Pos1, $Can1);
   PlaceCanister($Can_Pos1, $Can1);
}


Same deal. Save and start the map. Now it will grab the second canister, place it back, and then do the same with the first canister. Neat huh?

Now we just need to code a function to move the spark gun into position. We'll call this PosPiecesToFire. Why the crazy name? Well later we will call the spark effects function from within this function. So basically this function will position the pieces and then fire. Make sense now?

This function is pretty simple compared to the previous one. We won't be passing objects to it and its movement is simple. So here's your code.

Code:
void PosPiecesToFire ()
{
   $SparkArm_Swivel2.rotateOnce('-90 0 0');
   sys.waitFor($SparkArm_Swivel2);
   $SparkArm_Gun1.rotateOnce('-5 0 0');
   sys.waitFor($SparkArm_Gun1);
   $SparkArm_Base3.rotateOnce('0 120 0');
   sys.waitFor($SparkArm_Base3);
   $SparkArm_Base3.rotateOnce('0 120 0');
   sys.waitFor($SparkArm_Base3);
   $SparkArm_Base3.rotateOnce('0 120 0');
   $SparkArm_Gun1.rotateOnce('5 0 0');
   $SparkArm_Swivel2.rotateOnce('90 0 0');
   $SparkArm_Base3.rotateTo('0 0 0');
   sys.waitFor($SparkArm_Swivel2);
}


Now change the main function to look like this.

Code:
void main ()
{
   setup_objects ();
   sys.wait(3);
   GrabCanister($Can_Pos2, $Can2);
   PosPiecesToFire();
   PlaceCanister($Can_Pos2, $Can2);
   GrabCanister($Can_Pos1, $Can1);
   PosPiecesToFire();
   PlaceCanister($Can_Pos1, $Can1);
}


Again, save the script and start up the map. Now each arm does its own thing.

Let's change something real quick. I mean we don't want this to happen once and stop right?

We're going to create a function called MechCycle that will execute forever and a day. Basically we will cut the machine movement code from main and paste it.

Then we'll place that code in a while loop. It should look like this.

Code:
void MechCycle ()
{
   while (1) {
      GrabCanister($Can_Pos1, $Can1, $Can1_TubeSnd);
      PosPiecesToFire();
      PlaceCanister($Can_Pos1, $Can1, $Can1_TubeSnd);
      GrabCanister($Can_Pos2, $Can2, $Can2_TubeSnd);
      PosPiecesToFire();
      PlaceCanister($Can_Pos2, $Can2, $Can2_TubeSnd);
   }
}


Now, we just throw a call in main so it looks like this.

Code:
void main ()
{
   setup_objects ();
   sys.wait(3);
   MechCycle();
}


Save the script and run the map. Now the machine runs continuously.

Adding Bells, Whistles and a Light

Here is the fun part. Everything is already moving all you got to do is make it pretty. Well, I suppose it isn't going to look too pretty unless I went through the trouble of texturing my objects in the first place. Ahhh, whatever. I'm still learning modeling anyway.

Technically this part is so simple it really isn't worth documenting but hey, I'm trying to cover it all right?

Now, I'm no expert at sound type stuff and I tried really hard to find sounds that matched the action. But hey, in all honesty you should probably be creating new sounds for your work anyway. I decided I would need eight speakers. Two upward hydraulic pump sounds, one for each arm. Two downward hydraulic pump sounds again, one for each arm. Two steam type sounds for each canister being released. A spark gun sound and finally some form of ambient mechanical hum.

The ambient sound loops and the remaining sounds are all setup to sound when triggered by setting a key called s_waitfortrigger to 1. Here are the names of the sounds that need scripting.

$SparkArm_JointUp
$SparkArm_JointDown
$SparkArm_Snd1
$GrabArm_JointUp
$GrabArm_JointDown
$Can1_TubeSnd
$Can2_TubeSnd



Now because the spark arm doesn't move a significant amount the joint sounds don't need to be bound. Neither do the canister tube sounds. However, since the grab arm and the spark arm move a pretty good distance we'll bind their speakers to the moving entities.

Don't worry about the binds yet though, We'll be knocking out the sounds as well as lighting and special effects all at once.

Triggering a sound is pretty simple. Really all you have to do is perform a sys.trigger(insert speaker name here) whenever you want that particular sound to fire. There is no need to clutter your map with actual trigger entities.

Now for the spark gun. I created a func_fx, brought up the entities properties, set the fx key to fx/sparks.fx, and set the restart key to 1 so that I can trigger this effect multiple times.

Next I created a light and set the key startoff to 1. I set the shader to lights/roundfire.

Again, both the light and the func_fx are triggered using the command sys.trigger(insert light or func_fx name here).

Here is the name of the light and func_fx...

$SparkArm_Light1
$SparkArm_Fx1


So, change your binds to look like this.

Code:
void setup_objects()
{

// SparkArm Binds

   $SparkArm_Fx1.bind($SparkArm_Gun1);
   $SparkArm_Light1.bind($SparkArm_Gun1);
   $SparkArm_Snd1.bind($SparkArm_Gun1);
   $SparkArm_Gun1.bind($SparkArm_Base3);
   $SparkArm_Base3.bind($SparkArm_Swivel2);
   $SparkArm_Swivel2.bind($SparkArm_Base2);
   $SparkArm_Base2.bind($SparkArm_Swivel1);
   $SparkArm_Swivel1.bind($SparkArm_Base1);         

// GrabArm Binds

   $GrabArm_Finger1.bind($GrabArm_Base3);
   $GrabArm_Finger2.bind($GrabArm_Base3);
   $GrabArm_Finger3.bind($GrabArm_Base3);
   $GrabArm_Base3.bind($GrabArm_Swivel2);
   $GrabArm_Swivel2.bind($GrabArm_Base2);
   $GrabArm_Base2.bind($GrabArm_Swivel1);
   $GrabArm_Swivel1.bind($GrabArm_Base1);
   $GrabArm_JointUp.bind($GrabArm_Swivel2);
   $GrabArm_JointDown.bind($GrabArm_Swivel2);
}


Add the function FireSparkGun. Give it the following code.

Code:
void FireSparkGun ()
{
   sys.trigger($SparkArm_Fx1);
   sys.trigger($SparkArm_Snd1);
   sys.trigger($SparkArm_Light1);
   sys.wait(3);
   sys.trigger($SparkArm_Light1);
}


And add another entity reference to GrabCanister and PlaceCanister for the canister tube sound to be triggered. We'll call it CanTubeSnd.

Then it's just a matter of finding the right line to insert your sound triggers into. I'll save you the trouble. Here's the code.

Code:
void GrabCanister(entity CanPos, entity Can, entity CanTubeSnd)
{
   $GrabArm_Base1.moveTo(CanPos);
   sys.trigger($GrabArm_JointUp);
   sys.waitFor($GrabArm_Base1);
   $GrabArm_Finger1.rotateOnce('45 0 0');
   $GrabArm_Finger2.rotateOnce('45 0 0');
   $GrabArm_Finger3.rotateOnce('45 0 0');
   sys.trigger($GrabArm_JointUp);
   Can.move(UP, 30);
   sys.trigger(CanTubeSnd);
   sys.waitFor(Can);
   $GrabArm_Finger1.rotateOnce('-35 0 0');
   $GrabArm_Finger2.rotateOnce('-35 0 0');
   $GrabArm_Finger3.rotateOnce('-35 0 0');
   sys.trigger($GrabArm_JointDown);
   sys.waitFor($GrabArm_Finger1);
   Can.bind($GrabArm_Base3);
   $GrabArm_Base3.rotateOnce('0 360 0');
   sys.trigger($GrabArm_JointUp);
   sys.waitFor($GrabArm_Base3);
   $GrabArm_Base2.rotateOnce('0 90 0');
   sys.trigger($GrabArm_JointDown);
   sys.waitFor($GrabArm_Base2);
   $GrabArm_Swivel2.rotateOnce('90 0 0');
   sys.trigger($GrabArm_JointUp);
   sys.waitFor($GrabArm_Swivel2);
   $GrabArm_Base1.moveTo($Arm_Pos1);
   $GrabArm_Base2.rotateOnce('0 -90 0');
   sys.trigger($GrabArm_JointUp);
   sys.trigger($GrabArm_JointDown);
   sys.waitFor($GrabArm_Base2);
}

void PlaceCanister(entity CanPos, entity Can, entity CanTubeSnd)
{
   $GrabArm_Base2.rotateOnce('0 90 0');
   sys.trigger($GrabArm_JointUp);
   $GrabArm_Base1.moveTo(CanPos);
   sys.waitFor($GrabArm_Base2);
   $GrabArm_Swivel2.rotateOnce('-90 0 0');
   sys.trigger($GrabArm_JointDown);
   sys.waitFor($GrabArm_Swivel2);
   $GrabArm_Base3.rotateOnce('0 -360 0');
   sys.trigger($GrabArm_JointDown);
   sys.waitFor($GrabArm_Base3);
   Can.unbind();
   $GrabArm_Finger1.rotateOnce('35 0 0');
   $GrabArm_Finger2.rotateOnce('35 0 0');
   $GrabArm_Finger3.rotateOnce('35 0 0');
   sys.trigger($GrabArm_JointUp);
   sys.waitFor($GrabArm_Finger1);
   Can.rotateTo('0 0 0');
   Can.move(DOWN, 30);
   sys.trigger(CanTubeSnd);
   sys.waitFor(Can);
   $GrabArm_Finger1.rotateOnce('-45 0 0');
   $GrabArm_Finger2.rotateOnce('-45 0 0');
   $GrabArm_Finger3.rotateOnce('-45 0 0');
   $GrabArm_Base1.moveTo($Arm_Pos1);
   $GrabArm_Base2.rotateTo('0 0 0');
   sys.trigger($GrabArm_JointUp);
   sys.trigger($GrabArm_JointDown);
   sys.waitFor($GrabArm_Base1);
}


Next we'll modify PosPiecesToFire so it calls FireSparkGun and triggers its own speakers. Again here's the code.

Code:
void PosPiecesToFire ()
{
   $SparkArm_Swivel2.rotateOnce('-90 0 0');
   sys.trigger($SparkArm_JointUp);
   sys.waitFor($SparkArm_Swivel2);
   $SparkArm_Gun1.rotateOnce('-5 0 0');
   sys.trigger($SparkArm_JointUp);
   sys.waitFor($SparkArm_Gun1);
   FireSparkGun();
   $SparkArm_Base3.rotateOnce('0 120 0');
   sys.trigger($SparkArm_JointDown);
   sys.waitFor($SparkArm_Base3);
   FireSparkGun();
   $SparkArm_Base3.rotateOnce('0 120 0');
   sys.trigger($SparkArm_JointDown);
   sys.waitFor($SparkArm_Base3);
   FireSparkGun();
   $SparkArm_Base3.rotateOnce('0 120 0');
   sys.trigger($SparkArm_JointDown);
   $SparkArm_Gun1.rotateOnce('5 0 0');
   sys.trigger($SparkArm_JointDown);
   $SparkArm_Swivel2.rotateOnce('90 0 0');
   sys.trigger($SparkArm_JointDown);
   $SparkArm_Base3.rotateTo('0 0 0');
   sys.waitFor($SparkArm_Swivel2);
}


Finally, we'll modify MechCycle to compensate for the new reference variable CanTubeSnd. Here's the code.

Code:
void MechCycle ()
{
   while (1) {
      GrabCanister($Can_Pos1, $Can1, $Can1_TubeSnd);
      PosPiecesToFire();
      PlaceCanister($Can_Pos1, $Can1, $Can1_TubeSnd);
      GrabCanister($Can_Pos2, $Can2, $Can2_TubeSnd);
      PosPiecesToFire();
      PlaceCanister($Can_Pos2, $Can2, $Can2_TubeSnd);
   }
}


And just to make you have to scroll down for an hour here is your final script.

Man its long isn't it?

Code:
////////////////////////////////////////////////////
//
//   Setup binds and times etc...
//
////////////////////////////////////////////////////
void setup_objects()
{

// SparkArm Binds

   $SparkArm_Fx1.bind($SparkArm_Gun1);
   $SparkArm_Light1.bind($SparkArm_Gun1);
   $SparkArm_Snd1.bind($SparkArm_Gun1);
   $SparkArm_Gun1.bind($SparkArm_Base3);
   $SparkArm_Base3.bind($SparkArm_Swivel2);
   $SparkArm_Swivel2.bind($SparkArm_Base2);
   $SparkArm_Base2.bind($SparkArm_Swivel1);
   $SparkArm_Swivel1.bind($SparkArm_Base1);         

// GrabArm Binds

   $GrabArm_Finger1.bind($GrabArm_Base3);
   $GrabArm_Finger2.bind($GrabArm_Base3);
   $GrabArm_Finger3.bind($GrabArm_Base3);
   $GrabArm_Base3.bind($GrabArm_Swivel2);
   $GrabArm_Swivel2.bind($GrabArm_Base2);
   $GrabArm_Base2.bind($GrabArm_Swivel1);
   $GrabArm_Swivel1.bind($GrabArm_Base1);
   $GrabArm_JointUp.bind($GrabArm_Swivel2);
   $GrabArm_JointDown.bind($GrabArm_Swivel2);
}

////////////////////////////////////////////////////
//
//   Block movement...
//
////////////////////////////////////////////////////

void FireSparkGun ()
{
   sys.trigger($SparkArm_Fx1);
   sys.trigger($SparkArm_Snd1);
   sys.trigger($SparkArm_Light1);
   sys.wait(3);
   sys.trigger($SparkArm_Light1);
}

void GrabCanister(entity CanPos, entity Can, entity CanTubeSnd)
{
   $GrabArm_Base1.moveTo(CanPos);
   sys.trigger($GrabArm_JointUp);
   sys.waitFor($GrabArm_Base1);
   $GrabArm_Finger1.rotateOnce('45 0 0');
   $GrabArm_Finger2.rotateOnce('45 0 0');
   $GrabArm_Finger3.rotateOnce('45 0 0');
   sys.trigger($GrabArm_JointUp);
   Can.move(UP, 30);
   sys.trigger(CanTubeSnd);
   sys.waitFor(Can);
   $GrabArm_Finger1.rotateOnce('-35 0 0');
   $GrabArm_Finger2.rotateOnce('-35 0 0');
   $GrabArm_Finger3.rotateOnce('-35 0 0');
   sys.trigger($GrabArm_JointDown);
   sys.waitFor($GrabArm_Finger1);
   Can.bind($GrabArm_Base3);
   $GrabArm_Base3.rotateOnce('0 360 0');
   sys.trigger($GrabArm_JointUp);
   sys.waitFor($GrabArm_Base3);
   $GrabArm_Base2.rotateOnce('0 90 0');
   sys.trigger($GrabArm_JointDown);
   sys.waitFor($GrabArm_Base2);
   $GrabArm_Swivel2.rotateOnce('90 0 0');
   sys.trigger($GrabArm_JointUp);
   sys.waitFor($GrabArm_Swivel2);
   $GrabArm_Base1.moveTo($Arm_Pos1);
   $GrabArm_Base2.rotateOnce('0 -90 0');
   sys.trigger($GrabArm_JointUp);
   sys.trigger($GrabArm_JointDown);
   sys.waitFor($GrabArm_Base2);
}

void PlaceCanister(entity CanPos, entity Can, entity CanTubeSnd)
{
   $GrabArm_Base2.rotateOnce('0 90 0');
   sys.trigger($GrabArm_JointUp);
   $GrabArm_Base1.moveTo(CanPos);
   sys.waitFor($GrabArm_Base2);
   $GrabArm_Swivel2.rotateOnce('-90 0 0');
   sys.trigger($GrabArm_JointDown);
   sys.waitFor($GrabArm_Swivel2);
   $GrabArm_Base3.rotateOnce('0 -360 0');
   sys.trigger($GrabArm_JointDown);
   sys.waitFor($GrabArm_Base3);
   Can.unbind();
   $GrabArm_Finger1.rotateOnce('35 0 0');
   $GrabArm_Finger2.rotateOnce('35 0 0');
   $GrabArm_Finger3.rotateOnce('35 0 0');
   sys.trigger($GrabArm_JointUp);
   sys.waitFor($GrabArm_Finger1);
   Can.rotateTo('0 0 0');
   Can.move(DOWN, 30);
   sys.trigger(CanTubeSnd);
   sys.waitFor(Can);
   $GrabArm_Finger1.rotateOnce('-45 0 0');
   $GrabArm_Finger2.rotateOnce('-45 0 0');
   $GrabArm_Finger3.rotateOnce('-45 0 0');
   $GrabArm_Base1.moveTo($Arm_Pos1);
   $GrabArm_Base2.rotateTo('0 0 0');
   sys.trigger($GrabArm_JointUp);
   sys.trigger($GrabArm_JointDown);
   sys.waitFor($GrabArm_Base1);
}

void PosPiecesToFire ()
{
   $SparkArm_Swivel2.rotateOnce('-90 0 0');
   sys.trigger($SparkArm_JointUp);
   sys.waitFor($SparkArm_Swivel2);
   $SparkArm_Gun1.rotateOnce('-5 0 0');
   sys.trigger($SparkArm_JointUp);
   sys.waitFor($SparkArm_Gun1);
   FireSparkGun();
   $SparkArm_Base3.rotateOnce('0 120 0');
   sys.trigger($SparkArm_JointDown);
   sys.waitFor($SparkArm_Base3);
   FireSparkGun();
   $SparkArm_Base3.rotateOnce('0 120 0');
   sys.trigger($SparkArm_JointDown);
   sys.waitFor($SparkArm_Base3);
   FireSparkGun();
   $SparkArm_Base3.rotateOnce('0 120 0');
   sys.trigger($SparkArm_JointDown);
   $SparkArm_Gun1.rotateOnce('5 0 0');
   sys.trigger($SparkArm_JointDown);
   $SparkArm_Swivel2.rotateOnce('90 0 0');
   sys.trigger($SparkArm_JointDown);
   $SparkArm_Base3.rotateTo('0 0 0');
   sys.waitFor($SparkArm_Swivel2);
}

void MechCycle ()
{
   while (1) {
      GrabCanister($Can_Pos1, $Can1, $Can1_TubeSnd);
      PosPiecesToFire();
      PlaceCanister($Can_Pos1, $Can1, $Can1_TubeSnd);
      GrabCanister($Can_Pos2, $Can2, $Can2_TubeSnd);
      PosPiecesToFire();
      PlaceCanister($Can_Pos2, $Can2, $Can2_TubeSnd);
   }
}


////////////////////////////////////////////////////
//
//   MAIN
//
////////////////////////////////////////////////////

void main ()
{
   setup_objects ();
   sys.wait(3);
   MechCycle();
}


Now you save your script and run the map. Ta Da! Here is the exact same script you started with... Ahem.. I mean look! All that hard work paid off!

Conclusion

That pretty much sums up what I know of scripting as a whole. I'm hoping that some of you out there will be on my level soon and we can start tackling ideas together. If not, run over this tutorial a couple of times. You should get it.

Got any questions? Ask them.



iceheart@Posted: Mon Aug 09, 2004 1:26 am :
Very nice :), Shouldn't your models be casting shadows? Also they are completely black.



rich_is_bored@Posted: Mon Aug 09, 2004 2:39 am :
If they are completely black then that means the material shader isn't working. And if the material shader isn't working that explains why there are no shadows.

Did you extract the zip to the base folder?

When the files extract they should go in the following locations...

[your doom 3 directory]\base\maps\

rich_scripting_tutorial_3.cm
rich_scripting_tutorial_3.map
rich_scripting_tutorial_3.proc
rich_scripting_tutorial_3.script

[your doom 3 directory]\base\materials\

rich_scripting_tutorials.mtr

[your doom 3 directory]\base\models\

rich_scripting_tutorials_arm_base.lwo
rich_scripting_tutorials_arm_shaft.lwo
rich_scripting_tutorials_canister.lwo
rich_scripting_tutorials_finger.lwo
rich_scripting_tutorials_finger_joint.lwo
rich_scripting_tutorials_grey.tga
rich_scripting_tutorials_welder.lwo

If the tga or mtr are in the wrong location that would explain the problem.

You didn't mention anything beside graphical issues so I assume everything else is in the correct location.

If anyone else can confirm that it works, doesn't work when you extract it to the base folder I'd appriciate it.

It works on my end but that doesn't really help because I made the damn thing. :D



iceheart@Posted: Mon Aug 09, 2004 4:10 pm :
Nope, everything is where it's supposed to be here, unzipped into the base folder, I double checked all the indvidual subfolders and it looks proper. The only thing I can think of is that I have unpacked all my pak files as well in order to explore the file structure, so that might create some conflicts. And yes, everything else (map, script) works fine.



iceheart@Posted: Mon Aug 09, 2004 4:41 pm :
Your .mtr file looks a little odd, pointing to stuff like /tutorials/Canister and stuff like that, but altering it using my extremely limited understanding of mtr files to this:

Code:
models/rich_scripting_tutorials_canister
{
   diffusemap    models/rich_scripting_tutorials_grey.tga
}


etc.

Gives absolutely no difference (still black, no shadows)



rich_is_bored@Posted: Mon Aug 09, 2004 6:18 pm :
Ah! It's because you unpaked your pk4s. When you do that it causes a conflict with new content.

It's best if you want to look at the files within the PK4s that you extract them to a separate directory.



iceheart@Posted: Mon Aug 09, 2004 6:31 pm :
What, exactly is it that conflicts with what here?



rich_is_bored@Posted: Mon Aug 09, 2004 6:43 pm :
In other words, when you extracted Doom 3's pk4s into your base directory you disabled your ability to load custom content. When Doom 3 loads it sees all the files you extracted in their appropriate directories and begins to load them as a mod, even though it's just the game's vanilla content.

Obviously if the map and models from the tutorial load but they don't display a texture then doom 3's being rather quirky as to what will work and what won't.

What I will tell you is that it works without the pk4s extracted and I don't plan on unpacking my pk4's and testing my tutorials with them just so you can "have it your way". To put it frankly, this ain't Burger King.



iceheart@Posted: Mon Aug 09, 2004 6:45 pm :
Hey I'm being curious here, not hostile :)



rich_is_bored@Posted: Mon Aug 09, 2004 6:47 pm :
No hostility intended. I'm just telling it like it is. :)



iceheart@Posted: Mon Aug 09, 2004 6:54 pm :
I was asking that because everything I know about the engine and pak files (from older id engines) says that unpacked files override the pak files, but your files do not replace any existing files and so there should be no difference if the files are in paks or in folders, so I was just wondering where exactly the conflict occurs (as in, doom3 will not load the mtr file for some reason, or whatever other thing is causing this behaviour?).



rich_is_bored@Posted: Mon Aug 09, 2004 7:06 pm :
I'm not sure why it doesn't work. Technically, it should. But the only way to find the actual code reponsible for the problem is to read every script, def, and otherwise uncompiled bit in the game to narrow the problem down.

My hands are full updating definitions, revising tutorials, ect... I simply don't have the time.

All I can do is make educated guesses and since I know on my end that the PK4s are not extracted and they are on yours, it's a relativley safe assumption that the extracted PK4s are your problem.

If you could just work with me by cleaning out your base directory and trying the tutorial that way, I'd honestly appriciate it. If it still doesn't work then I'll have a reason to troubleshoot the issue further.



iceheart@Posted: Mon Aug 09, 2004 7:10 pm :
I think I did that just now, but there's no change, I'm going to try reinstalling the whole thing and try it on a clean install.



rich_is_bored@Posted: Mon Aug 09, 2004 7:17 pm :
You edited my mtr file. Did you ever change it back?

The reason it points to tutorials/canister is because that is the name I assigned the surface in Lightwave.

And no offense but, when you say you think you cleaned out your base directory did you actually do it or just think about it? :lol:



iceheart@Posted: Mon Aug 09, 2004 7:21 pm :
Oops :)

Well anyway I'll have a clean install to test it on in a couple of minutes, after that I'm going to go the old fashioned way of having one clean install and one editing install :).



iceheart@Posted: Mon Aug 09, 2004 7:23 pm :
When I said that I though I had cleaned it out I meant that I had the pak files in the base and I thought I had deleted all the individual files that came out of them, but I wasn't sure, hence I am reinstalling.



iceheart@Posted: Mon Aug 09, 2004 7:50 pm :
Done reinstalling, on a completely clean D3 install it works (I think, it has shadows and is solid grey, with the "fingers" solid white).



rich_is_bored@Posted: Mon Aug 09, 2004 8:16 pm :
That's it. Thanks.

You were getting me all worked up for nothing. :)



iceheart@Posted: Mon Aug 09, 2004 8:20 pm :
Hey while we're on the subject of scripting, I was wondering if you'd need any help populating this list: http://www.doom3world.org/index.php?milkme=list_scriptarg, I would be happy to help, I have a little bit of programming experience along with considerable experience in sinscript, morpheus script along with the STEF2 scripting system, which are all "predecessors" in a sense of the D3 script, so I think I could be useful in making some sense out of it.



rich_is_bored@Posted: Mon Aug 09, 2004 8:24 pm :
Yes please! I would really appriciate the help.

Just add anything you can in posts here...

http://www.doom3world.org/phpbb2/viewtopic.php?t=2562

... and I'll edit them in.

Feel free to post comments in the database on the main page aswell.