T3D/Tutorials/SimpleFPSTutorial/Part5

From TDN

Simple FPS Tutorial for Torque3D



Back to Part Four: custom scripts for sharing the paricles, FX and audio


Part FIVE



Custom Weapon Server Scripts:

Torque3D comes with a basic script function that supplies all the necessary functionality for combining weapons, items, ammo and images into a working system, "game/scripts/server/weapon.cs". But there is also the opportunity to create scripts and functions that will override this and be unique to an individual weapon (eg: change it's accuracy or add special functions/attacks). We're going to create new server script files for our 2 custom weapons to make certain that they work better with AiPlayers/bots.

The first issue with the stock script of "game/scripts/server/weapon.cs" is that it's expecting a human player as a "client", eg: multiplayer on another computer from the server computer which is running the game - so it doesn't check for AiPlayers on the same computer when updating the weaponHUD display for the player.

Create a new file "game/scripts/server/semiauto.cs" and copy and paste the following functions from "game/scripts/server/weapon.cs":

WeaponImage::onMount
WeaponImage::onUnmount
WeaponImage::onFire
Ammo::onInventory

Now change "WeaponImage" to "semiautoImage" and "Ammo" to "semiautoAmmo". These will now override the stock functions for the semiauto weapon, whilst preserving the original functions intact for other uses.

First, let's fix the weaponHUD display so that AiPlayers don't interfere with it. Anywhere you see the line that starts as "%obj.client.RefreshWeaponHud..." you need to filter out AiPlayers. This is in "onMount", "onUnmount" and "onInventory". Right above it add:

if(%obj.getClassName() !$="AIPlayer")//yorks added

I'm using the "yorks added" comment to tag these changes so I know that I did them and can keep track of my changes and additions. Yorks isn't a word you tend to see in script and so it's easy to find in an automated search. Use whatever tag you like, but remember that developers leave these tags too, often with their name, and if your name is the same as a developer's name tag, an automated search will pick this up, so it's always better to use a unique tag/codeword for search. (just a little hint from finding out that "Steve" brought up multiple developer tags when I was modding CoD back in the day)

Whilst keeping an eye on the amount of bullets in an inventory is a game mechanic fro the Player, it's not always for an AiPlayer, and in this Simple FPS Tutorial the AiPlayers do not want to run out of bullets. So in the "onFire" function, change:

   if ( !%this.infiniteAmmo )
      %obj.decInventory(%this.ammo, 1);


To read:

   if ( %obj.getClassName() !$="AIPlayer" )//yorks new -- bots get infinite bullets
       %obj.decInventory(%this.ammo, 1);


The reason that we're not using "infiniteAmmo" is that it makes the weapon not decrease ammo even if the player has picked it up.

Your finished "semiauto.cs" should now look something like this:

function semiautoImage::onMount(%this, %obj, %slot)
{
   // Images assume a false ammo state on load.  We need to
   // set the state according to the current inventory.
   if(%this.ammo !$= "")
   {
      if (%obj.getInventory(%this.ammo))
      {
         %obj.setImageAmmo(%slot, true);
         %currentAmmo = %obj.getInventory(%this.ammo);
      }
      else
         %currentAmmo = 0;
		 
		if(%obj.getClassName() !$="AIPlayer")//yorks added
			%obj.client.RefreshWeaponHud(%currentAmmo, %this.item.previewImage, %this.item.reticle);
   }
}

function semiautoImage::onUnmount(%this, %obj, %slot)
{
	if(%obj.getClassName() !$="AIPlayer")//yorks added
		%obj.client.RefreshWeaponHud(0, "", "");
}

function semiautoImage::onFire(%this, %obj, %slot)
{
   //echo("\c4WeaponImage::onFire( "@%this.getName()@", "@%obj.client.nameBase@", "@%slot@" )");

   // Decrement inventory ammo. The image's ammo state is updated
   // automatically by the ammo inventory hooks.
   if ( %obj.getClassName() !$="AIPlayer" )//yorks new -- bots get infinite bullets
      %obj.decInventory(%this.ammo, 1);

   if (%this.projectileSpread)
   {
      // We'll need to "skew" this projectile a little bit.  We start by
      // getting the straight ahead aiming point of the gun
      %vec = %obj.getMuzzleVector(%slot);

      // Then we'll create a spread matrix by randomly generating x, y, and z
      // points in a circle
      for(%i = 0; %i < 3; %i++)
         %matrix = %matrix @ (getRandom() - 0.5) * 2 * 3.1415926 * %this.projectileSpread @ " ";
      %mat = MatrixCreateFromEuler(%matrix);

      // Which we'll use to alter the projectile's initial vector with
      %muzzleVector = MatrixMulVector(%mat, %vec);
   }
   else
   {
      // Weapon projectile doesn't have a spread factor so we fire it using
      // the straight ahead aiming point of the gun
      %muzzleVector = %obj.getMuzzleVector(%slot);
   }

   // Get the player's velocity, we'll then add it to that of the projectile
   %objectVelocity = %obj.getVelocity();
   %muzzleVelocity = VectorAdd(
      VectorScale(%muzzleVector, %this.projectile.muzzleVelocity),
      VectorScale(%objectVelocity, %this.projectile.velInheritFactor));

   // Create the projectile object
   %p = new (%this.projectileType)()
   {
      dataBlock = %this.projectile;
      initialVelocity = %muzzleVelocity;
      initialPosition = %obj.getMuzzlePoint(%slot);
      sourceObject = %obj;
      sourceSlot = %slot;
      client = %obj.client;
   };
   MissionCleanup.add(%p);
   return %p;
}

function semiautoAmmo::onInventory(%this, %obj, %amount)
{
   // The ammo inventory state has changed, we need to update any
   // mounted images using this ammo to reflect the new state.
   for (%i = 0; %i < 8; %i++)
   {
      if ((%image = %obj.getMountedImage(%i)) > 0)
         if (isObject(%image.ammo) && %image.ammo.getId() == %this.getId())
         {
            %obj.setImageAmmo(%i, %amount != 0);
            %currentAmmo = %obj.getInventory(%this);
			
			if(%obj.getClassName() !$="AIPlayer")//yorks added
				%obj.client.setAmmoAmountHud(%currentAmmo);
         }
   }
}


Now save the file and copy it, renaming the copy "fullauto.cs". Open that file up and replace ever instance of "semiauto" to "fullauto". Save and now both weapon scripts are fixed for AiPlayers.

You still need to tell the engine to use them. So in "game/scripts/server/ScriptExec.cs" list them at the bottom.

//custom scripts
exec("./semiauto.cs");
exec("./fullauto.cs");


Bullets make holes, so our weapons need a decal to place on objects that they hit. In the ProjectileData of our 2 weapons (art) script files, you may have noticed: decal = bulletHoleDecal; We're going to make that bulletHoleDecal. In "Simple_FPS_Tutorial/game/art/decals" open up "managedDecalData.cs" and create a new entry. Basically we'll copy a stock datablock for the mark left by an exploding rocket and then set the scale very small ; 0.2 here.

//yorks addition
datablock DecalData(bulletholeDecal)
{
   Material = "DECAL_RocketEXP";
   size = "0.2";
   lifeSpan = "50000";
   randomize = "1";
   texRows = "2";
   texCols = "2";
   clippingAngle = "60";
};


Save, and everytime you shoot an object or the ground, a decal bullet hole will appear. That's it for the weapons themselves - but we still have to tell the player that they can use them by setting them as allowable inventory items.




Part Six: Custom Player Scripts