Game Development Community

Giving the player fire control over an AITurret

by Kirby Webber · in Torque Game Engine · 12/29/2005 (2:16 pm) · 13 replies

I just recently got Paul Dana's turret resource into 1.4 and working (YAY!).

After that, I went about getting a fully functional AITurret mounted to my vehicles (I'm working with hovers, but that's hardly relevant.)

So far, so good.

At this point, I'm looking for a way to allow the AITurret to pick it's own targets (as it already does), but allow the player to actually fire the turret via the mouse.

Additionally, I'll need to credit the player with any kills which are techically scored by the turret.

Any advice would be greatly appreciated.

~ Cheers.

#1
12/29/2005 (2:31 pm)
Make the vehicle 'aware' of the mounted turret by either checking for a mounted object or have a variable that gets set in the vehicle script that lets the vehicle 'know' the turret is there. Then, use a script function tthat gets called when a mouse trigger is clicked. I think it may be the Player::onTrigger function (there is some function that fires when any trigger comes in for the player). You will have to determine what triggers the mouse buttons cause. Use that to explicitly toggle the fire trigger on the turret. This is a script only solution. There may be other C++ ways of doing it, but this will be easy to tweak to get it working.
#2
12/29/2005 (2:40 pm)
Okay, I think I follow your logic here Frank, but how exactly would one go about making the vehicle "aware" of the mounted object in script?
#3
12/29/2005 (2:43 pm)
Incidentally, the fucntion that fires when the mouse button is pressed is "MouseFire()" in default.bind.cs

I had planned to use that function to wrench fire control back from the AI, but when it comes down to brass tacks...
#4
12/29/2005 (2:59 pm)
Okay, when you actually mount the object to the vehicle do this:
%vehicle.turret = new AITurret() // or whatever you do to create the turret

Now the reference to the turret is stored in that vehicle.

Anytime the vehicle gets a onTrigger event that it recognizes as belonging to the turret then do some on action on the turret:
%vehicle.turret.setImageTrigger(0,1) // something like that, I am not sure what the exact code is trigger an image on another object. You may even just have a 'fire' command for the turret object. You could also do supervisory control: select target, show target, etc.

Also, you do not need to use a high level function like mousefire. There should be a function that gets called on the player/vehicle object server side. Oh yeah, this code needs to be server side in the scripting.
Here is an example of using onTrigger:
// catch triggers for player control
function AITurretData::onTrigger(%this, %obj, %triggerNum, %val)
{
   // if dead stop firing
   if (%obj.getState() $= "Dead")
      return;

   //echo(%this.getClassName()@" "@%obj.getDatablock().getClassName()@" "@%triggerNum@" "@%val);
   
   if(%triggerNum == 0)
   {
      %obj.setImageTrigger(%this.weaponSlot[0],%val);
      %obj.setImageTrigger(%this.weaponSlot[1],%val);
   }
   
   //%obj.setImageTrigger(%this.weaponSlot[0],false);
   //%obj.setImageTrigger(%this.weaponSlot[1],false);
}

This code uses the AIPlayer object as a turret by mounting the AIPlayer onto a staticShape in mount point 1 so that the player can rotate. The AITurret I am using does not correspond to the one developed in C++. It is a script only AITurret based upon AIPlayer. I use this onTrigger function when I mount the AITurret as a Player so that the turret sees my firing. Notice that I check to see that %triggerNum is equal to some value (fire value is 0 for Player). So this code might work on the AITurret stuff you are using. Just see if this onTrigger function gets called by putting in an echo command:
function <Your Object Datablock Name>::onTrigger(%this, %obj, %triggerNum, %val)
{
   echo(%this.getClassName()@" "@%obj.getDatablock().getClassName()@" "@%triggerNum@" "@%val);
}

Noitice is the above code I am firing two weapons at the same time.
#5
12/29/2005 (5:31 pm)
Okay... I am slowly getting somewhere. I know this because the console output indicates I'm going down the right path.

I have defined my AITurret functions as AITurretData::FunctionName(%args){}

The function (provided with the resource) that handles weapon firing looks like this:

// fire turret gun and setup to fire again fireRate seconds later
function AITurretData::fireGun(%this,%turret)
{

   // fire gun
   %turret.fire(0);

   // setup next fire time
   %this.setupNextFire(%turret,%this.fireRate,%this.fireRateVariance);
}

In my HoverVehicle script, I add the turret like so:

%turret = new AITurret(Blaster)
   {
       datablock = blasterTurret;
   };
   // mount turret at whatever mount point
   %obj.mountObject(%turret,9);
   %obj.mountedTurret = %turret;

The, as an experiment, I tried a simple experiment to access the fireGun() function like so:

function HoverVehicleData::onTrigger(%this, %obj, %turret, %val)
{
         Blaster.AITurretData.fireGun(Blaster);
}

Now, obviously (to anyone who understands what's going on... I sure don't =P) nothing happens when you press the trigger, although here's my console output:

... Unable to find object: '' attempting to call function 'fireGun'


What this tells me is that, in my HoverVehicle::OnTrigger() function, Blaster (the turrets name) is recognized as a valid AITurret type or it wouldn't even get to the "attempting to call function fireGun()" would it?

If I'm correct, it's really a matter of syntax then, isn't it?

What would be the proper way to address this specific turret in a way that fireGun() expects?

[edit]

For clarity - I DID try handing %turret adn %obj to the fireGun() function, but the results are vertiably the same.
#6
12/29/2005 (5:39 pm)
Nevermind... I tried the whole "Blaster.AITurretData.Firegun()" with some nonsense in place of "Blaster" and it's the same thing.

This should be so simple, WTH am I missing? =\
#7
12/29/2005 (6:56 pm)
Okay, you are making good progress. Your on trigger is doing something at least. Now:
Blaster.AITurretData.fireGun(Blaster);
Is not going to do anything because it does not reference any instance of the objects you are using. Try something along these lines:
// make sure you understand the onTrigger parameters
function HoverVehicleData::onTrigger(%this, %obj, %triggerNum, %val)
{
   // check type of trigger, and that it has a positive value
   if(%triggerNum == 0 && %val)
   {
      %obj.mountedTurret.fireGun( %obj.mountedTurret);
    }
}
Also, put in the echo function to see what triggers cause what. The triggers are NOT just for firing a weapon! There are triggers for other things that will call that function. Like jumping for instance.
#8
12/29/2005 (7:37 pm)
Well... this shows some serious progress for me.

Console now says:
base.AB/server/scripts/banshee.cs (233): Unknown command fireGun.
  Object Blaster(1496) AITurret -> Turret -> ShapeBase -> GameBase -> SceneObject -> NetObject -> SimObject

Which seems to be a step in the right direction. Obviously, the engine recognized that the entity I'm trying to throw a trigger on was "blaster".

If I may, since you've been so helpful with your time - "teach a man to fish" for a moment.

One of the things I've never quite grasped about Torque script is the passing in of arguments to scriptd functions.

In traditional programming languages, if you do not explicitly hand arguments into a function, those arguments are effectively 'Null'.

In Torquescript it seems that you can grab valid arguments out of 'thin air'. What I believe is actually transpiring is that, as an example, the %obj variable used to instantiate the vehicle in the onAdd() function is being associated with that calss instance, therefore when that variable is called from a hoverVehicleData function, it knows what value to use based upon it's association with that particular instance? *cough*namespcae?*cough*

Am I somewhere on the right track?

If so, the question would then be, how do I associate a function call from one (namespace?) class instance to another instance of an entirely different class?

At any rate, I am extremely grateful for your help thus far Frank.

=)
#9
12/29/2005 (7:46 pm)
AHA!

I change the AITurretData::fireGun() to AITurret::fireGun() and voila!

Now to tweak and whatnot!

Thanks a million for you help Frank - though if you could answer the question above, it'd be a GREAT help.

~Cheers!
#10
12/29/2005 (8:42 pm)
You are correct, when you use the format:

%myObject.someMethod();

to call a member method, it looks at the namespace of %myObject, and calls the method someMethod() within that namespace. For example:

AITurret::fireGun()
{
// do something here
}

%newTurret = new AITurret() {
datablock = myTurretData;
}

%newTurret.fireGun();

TorqueScript knows that since you declared %newTurret (note that this object handle is of local scope, so as soon as you leave this scope you will lose the variable %newTurret) of type AITurret, then when you execute the last line, it searches the AITurret:: namespace for the method.

The reason it worked when you changed the namespace from AITurretData::fireGun() to AITurret::fireGun() is because %obj, when passed into the method is the objectID of the vehicle being driven.

%obj.mountedTurret is the objectID of the turrent object. Alternatively, you could have called it as such:

%obj.mountedTurret.getDataBlock().fireGun(...);

and then the namespace AITurretData would have been used (assuming that was in fact the datablock for your turret.

The only other "magic" variable that floats around is the first parameter sent to a script method...commonly called %this. The engine automatically prepends the objectID of the calling object when it's namespace function is executed as the first parameter--we do not do this ourselves, the engine does it for us, but it's available to us if we desire to use it.

For example, in this case, in your ::fireGun() method, you have two params: %this, and %turret, and you are sending the objectID itself as the user parameter %turret as so:

%obj.mountedTurret.fireGun( %obj.mountedTurret );

--this is actually redundant, because the receiving parameters are %this, and %turret--which are the exact same object. You could just as simply used:

AITurret::fireGun(%this)
{
    // fire gun   
    %this.fire(0);
   // setup next fire time
   %this.setupNextFire(%this,%this.fireRate,%this.fireRateVariance);
}

and your call would look like:

%obj.mountedTurret.fireGun();
#11
12/30/2005 (8:03 am)
Hey Stephen, I really appreciate the clarification... that brigns a great deal of Torque script "out of the fog" for me - so to speak.

In the end, I realized that despite the fact that the weapon was mounted to an AITurret, I could access the trigger of the weaponImage directly like so:

function HoverVehicleData::onTrigger(%this, %obj, %triggernum, %val)
{
   // check type of trigger, and that it has a positive value
   if(%triggerNum == 0 && %val)
   {
      %obj.mountedTurret.setImageTrigger(0, true);
   }
   else
      %obj.mountedTurret.setImageTrigger(0, false);
}
This allows me to focus only on the behavior of the weapon image and not so much on the specifics of the turret datablock with regards to the weaponImage behavior... which is where think Frank was trying to direct me in the first place (I can be pretty thick sometimes).

At any rate, this discussion has proven educational on a number of levels for me! Thanks to you both.

~Off to "catch some fish" =)
#12
12/30/2005 (1:00 pm)
Yes, it is kind of wierd the whole datablock versus object thing. The call does come in on the HoverVehicleData, but it references the object through %obj. I assume the reason it is called on the datablock is because you will get the datablock and through %this and the object through %obj by default.

There are problems with your code as is:
function HoverVehicleData::onTrigger(%this, %obj, %triggernum, %val)
{
   // check type of trigger, and that it has a positive value
   // I structured it this way to work with your function
   if(%triggerNum == 0 && %val) 
   {
      %obj.mountedTurret.setImageTrigger(0, true);
   }
   else  
       // putting this in the else will cause it to stop firing on other non-firing events like jump
      %obj.mountedTurret.setImageTrigger(0, false); 
}

Here are the fixes for you:
function HoverVehicleData::onTrigger(%this, %obj, %triggernum, %val)
{
   // check type of trigger
   if(%triggerNum == 0)
   {
      // Use the %val on the trigger image to determine true or false,
      // if you print out the values you will see it gets 1 (true) and 0 (false) on press and release.
      %obj.mountedTurret.setImageTrigger(0, %val);
   }
   else if(%triggerNum == <somenumber>)  // note <somenumber> is not code
   {
       // Also, if you add your own triggers later for different things you can handle them by number here.   
       // Like fire missiles or something.
    }
}

Watch out for magic numbers like the zero used for firing you may want to setup some lookup values if there aren't already some defined like this:
// inside the hovervehicledata block
...
FireTriggerNum = 0;
...

Then in your function:
// check type of trigger
   if(%triggerNum == %this.FireTriggerNum)
   {

That way if the numbers will be descriptive and can be changed in one location.

Have fun.
#13
12/30/2005 (1:23 pm)
Hey Franks. Thanks agan! =)