Game Development Community

dev|Pro Game Development Curriculum

EnergyRifle For Torque3D MIT

by Steve Acaster · 02/11/2015 (9:15 am) · 5 comments


How to make a weapon which uses energy and not ammunition. To keep things simple we will inherit a lot things from Lurker weapon like shapes, animations and audio.

Step 1
Create a new energyRifle.cs file in art/datablocks/weapons.
The important bits are that the ImageDatablock does NOT have "ammo" listed, or there will be a conflict with "usesEnergy".
"minEnergy" is the minimum amount of energy the player must have for it to fire.
"infiniteAmmo" should be set to "true" to prevent console spam from weaponImage::onFire attempting to decrease ammo which does not exist. Alternatively the onFIre script could be changed to "if ( !%this.infiniteAmmo || %this.usesEnergy )" but we will stick to using "infiniteAmmo" in the imageDatablock for this resource.
"stateEnergyDrain" in the onFire state drains %n energy per full second so divide by the timeOut state if you want exact amounts.

//audio, particles and projectile all comes from Lurker.cs

//--------------------------------------------------------------------------
// Weapon Item.  This is the item that exists in the world, i.e. when it's
// been dropped, thrown or is acting as re-spawnable item.  When the weapon
// is mounted onto a shape, the SoldierWeaponImage is used.
//-----------------------------------------------------------------------------
datablock ItemData(energyRifle)
{
   // Mission editor category
   category = "Weapon";

   // Hook into Item Weapon class hierarchy. The weapon namespace
   // provides common weapon handling functions in addition to hooks
   // into the inventory system.
   className = "Weapon";

   // Basic Item properties
   shapeFile = "art/shapes/weapons/Lurker/TP_Lurker.DAE";
   mass = 1;
   elasticity = 0.2;
   friction = 0.6;
   emap = true;
   PreviewImage = 'lurker.png';

   // Dynamic properties defined by the scripts
   pickUpName = "energyRifle";
   description = "energyRifle";
   image = energyRifleWeaponImage;
   reticle = "crossHair";
};

datablock ShapeBaseImageData(energyRifleWeaponImage)
{
   // Basic Item properties
   shapeFile = "art/shapes/weapons/Lurker/TP_Lurker.DAE";
   shapeFileFP = "art/shapes/weapons/Lurker/FP_Lurker.DAE";
   emap = true;

   imageAnimPrefix = "Rifle";
   imageAnimPrefixFP = "Rifle";

   // Specify mount point & offset for 3rd person, and eye offset
   // for first person rendering.
   mountPoint = 0;
   firstPerson = true;
   useEyeNode = true;
   animateOnServer = true;

   // When firing from a point offset from the eye, muzzle correction
   // will adjust the muzzle vector to point to the eye LOS point.
   // Since this weapon doesn't actually fire from the muzzle point,
   // we need to turn this off.
   correctMuzzleVector = true;

   // Add the WeaponImage namespace as a parent, WeaponImage namespace
   // provides some hooks into the inventory system.
   class = "WeaponImage";
   className = "WeaponImage";

   // Projectiles and Ammo.
   item = energyRifle;
   
   usesEnergy = true;
   minEnergy = 10;
   infiniteAmmo = true;//need this to prevent console spam from weaponImage::onFire due to no ammo

   projectile = BulletProjectile;
   projectileType = Projectile;
   projectileSpread = "0.0";

   altProjectile = GrenadeLauncherProjectile;
   altProjectileSpread = "0.02";

   casing = BulletShell;
   shellExitDir        = "1.0 0.3 1.0";
   shellExitOffset     = "0.15 -0.56 -0.1";
   shellExitVariance   = 15.0;
   shellVelocity       = 3.0;

   // Weapon lights up while firing
   lightType = "WeaponFireLight";
   lightColor = "0.992126 0.968504 0.700787 1";
   lightRadius = "4";
   lightDuration = "100";
   lightBrightness = 2;

   // Shake camera while firing.
   shakeCamera = false;
   camShakeFreq = "0 0 0";
   camShakeAmp = "0 0 0";

   // Images have a state system which controls how the animations
   // are run, which sounds are played, script callbacks, etc. This
   // state system is downloaded to the client so that clients can
   // predict state changes and animate accordingly.  The following
   // system supports basic ready->fire->reload transitions as
   // well as a no-ammo->dryfire idle state.

   useRemainderDT = true;

   // Initial start up state
   stateName[0]                     = "Preactivate";
   stateTransitionOnLoaded[0]       = "Activate";
   stateTransitionOnNoAmmo[0]       = "NoAmmo";

   // Activating the gun.  Called when the weapon is first
   // mounted and there is ammo.
   stateName[1]                     = "Activate";
   stateTransitionGeneric0In[1]     = "SprintEnter";
   stateTransitionOnTimeout[1]      = "Ready";
   stateTimeoutValue[1]             = 0.5;
   stateSequence[1]                 = "switch_in";
   stateSound[1]                    = LurkerSwitchinSound;

   // Ready to fire, just waiting for the trigger
   stateName[2]                     = "Ready";
   stateTransitionGeneric0In[2]     = "SprintEnter";
   stateTransitionOnMotion[2]       = "ReadyMotion";
   stateTransitionOnTimeout[2]      = "ReadyFidget";
   stateTimeoutValue[2]             = 10;
   stateWaitForTimeout[2]           = false;
   stateScaleAnimation[2]           = false;
   stateScaleAnimationFP[2]         = false;
   stateTransitionOnNoAmmo[2]       = "NoAmmo";
   stateTransitionOnTriggerDown[2]  = "Fire";
   stateSequence[2]                 = "idle";

   // Same as Ready state but plays a fidget sequence
   stateName[3]                     = "ReadyFidget";//yorks - this is the devil, really.
   stateTransitionGeneric0In[3]     = "SprintEnter";
   stateTransitionOnMotion[3]       = "ReadyMotion";
   stateTransitionOnTimeout[3]      = "Ready";
   stateTimeoutValue[3]             = 6;
   stateWaitForTimeout[3]           = false;
   stateTransitionOnNoAmmo[3]       = "NoAmmo";
   stateTransitionOnTriggerDown[3]  = "Fire";
   stateSequence[3]                 = "idle_fidget1";
   stateSound[3]                    = LurkerIdleSound;

   // Ready to fire with player moving
   stateName[4]                     = "ReadyMotion";
   stateTransitionGeneric0In[4]     = "SprintEnter";
   stateTransitionOnNoMotion[4]     = "Ready";
   stateWaitForTimeout[4]           = false;
   stateScaleAnimation[4]           = false;
   stateScaleAnimationFP[4]         = false;
   stateSequenceTransitionIn[4]     = true;
   stateSequenceTransitionOut[4]    = true;
   stateTransitionOnNoAmmo[4]       = "NoAmmo";
   stateTransitionOnTriggerDown[4]  = "Fire";
   stateSequence[4]                 = "run";

   // Fire the weapon. Calls the fire script which does
   // the actual work.
   stateName[5]                     = "Fire";
   stateTransitionGeneric0In[5]     = "SprintEnter";
   stateTransitionOnTimeout[5]      = "NewRound";
   stateTimeoutValue[5]             = 0.15;
   stateEnergyDrain[5]              = 50.0;//yorks drain per full second
   stateFire[5]                     = true;
   stateRecoil[5]                   = "";
   stateAllowImageChange[5]         = false;
   stateSequence[5]                 = "fire";
   stateScaleAnimation[5]           = false;
   stateSequenceNeverTransition[5]  = true;
   stateSequenceRandomFlash[5]      = true;        // use muzzle flash sequence
   stateScript[5]                   = "onFire";
   stateSound[5]                    = LurkerFireSoundList;
   stateEmitter[5]                  = GunFireSmokeEmitter;
   stateEmitterTime[5]              = 0.025;

   // Put another round into the chamber if one is available
   stateName[6]                     = "NewRound";
   stateTransitionGeneric0In[6]     = "SprintEnter";
   stateTransitionOnNoAmmo[6]       = "NoAmmo";
   stateTransitionOnTimeout[6]      = "Ready";
   stateWaitForTimeout[6]           = "0";
   stateTimeoutValue[6]             = 0.05;
   stateAllowImageChange[6]         = false;
   stateEjectShell[6]               = true;

   // No ammo in the weapon, just idle until something
   // shows up. Play the dry fire sound if the trigger is
   // pulled.
   stateName[7]                     = "NoAmmo";
   stateTransitionGeneric0In[7]     = "SprintEnter";
   stateTransitionOnMotion[7]       = "NoAmmoMotion";
   stateTransitionOnAmmo[7]         = "Ready";
   stateTimeoutValue[7]             = 0.1;   // Slight pause to allow script to run when trigger is still held down from Fire state
   stateSequence[7]                 = "idle";
   stateScaleAnimation[7]           = false;
   stateScaleAnimationFP[7]         = false;
   stateTransitionOnTriggerDown[7]  = "DryFire";
   
   stateName[8]                     = "NoAmmoMotion";
   stateTransitionGeneric0In[8]     = "SprintEnter";
   stateTransitionOnNoMotion[8]     = "NoAmmo";
   stateWaitForTimeout[8]           = false;
   stateScaleAnimation[8]           = false;
   stateScaleAnimationFP[8]         = false;
   stateSequenceTransitionIn[8]     = true;
   stateSequenceTransitionOut[8]    = true;
   stateTransitionOnTriggerDown[8]  = "DryFire";
   stateTransitionOnAmmo[8]         = "Ready";
   stateSequence[8]                 = "run";

   // No ammo dry fire
   stateName[9]                     = "DryFire";
   stateTransitionGeneric0In[9]     = "SprintEnter";
   stateTransitionOnAmmo[9]         = "Ready";
   stateWaitForTimeout[9]           = "0";
   stateTimeoutValue[9]             = 0.7;
   stateTransitionOnTimeout[9]      = "NoAmmo";
   stateScript[9]                   = "onDryFire";
   stateSound[9]                    = MachineGunDryFire;

   // Start Sprinting
   stateName[10]                    = "SprintEnter";
   stateTransitionGeneric0Out[10]   = "SprintExit";
   stateTransitionOnTimeout[10]     = "Sprinting";
   stateWaitForTimeout[10]          = false;
   stateTimeoutValue[10]            = 0.5;
   stateWaitForTimeout[10]          = false;
   stateScaleAnimation[10]          = false;
   stateScaleAnimationFP[10]        = false;
   stateSequenceTransitionIn[10]    = true;
   stateSequenceTransitionOut[10]   = true;
   stateAllowImageChange[10]        = false;
   stateSequence[10]                = "sprint";

   // Sprinting
   stateName[11]                    = "Sprinting";
   stateTransitionGeneric0Out[11]   = "SprintExit";
   stateWaitForTimeout[11]          = false;
   stateScaleAnimation[11]          = false;
   stateScaleAnimationFP[11]        = false;
   stateSequenceTransitionIn[11]    = true;
   stateSequenceTransitionOut[11]   = true;
   stateAllowImageChange[11]        = false;
   stateSequence[11]                = "sprint";
   
   // Stop Sprinting
   stateName[12]                    = "SprintExit";
   stateTransitionGeneric0In[12]    = "SprintEnter";
   stateTransitionOnTimeout[12]     = "Ready";
   stateWaitForTimeout[12]          = false;
   stateTimeoutValue[12]            = 0.5;
   stateSequenceTransitionIn[12]    = true;
   stateSequenceTransitionOut[12]   = true;
   stateAllowImageChange[12]        = false;
   stateSequence[12]                = "sprint";
};

Part 2
Remember to exec the file in art/datablockExec.cs.
//...
// Load the weapon datablocks
exec("./weapons/Lurker.cs");
exec("./weapons/Ryder.cs");
exec("./weapons/ProxMine.cs");
exec("./weapons/Turret.cs");
exec("./weapons/energyRifle.cs");//yorks - our new energyRifle after Lurker which we inherit from

exec("./teleporter.cs");
//...

Step 3
Stock Torque3D is set not to show the weapon icon on the player's HUD if there is no ammo, so we need to change that.
In scripts/server/weapon.cs edit the "WeaponImage::onMount" function, go right to the bottom and set a new condition if there is no ammo.
function WeaponImage::onMount(%this, %obj, %slot)
{
//...
      if (%obj.client !$= "" && !%obj.isAiControlled)
         %obj.client.RefreshWeaponHud( 1, %this.item.previewImage, %this.item.reticle, %this.item.zoomReticle, %currentAmmo );
   }
   else//yorks in start
   {
      //make sure weapon icon appears on the player HUD for energy weapons and non-ammo weapons
      if (%obj.client !$= "" && !%obj.isAiControlled)
         %obj.client.RefreshWeaponHud( 0, %this.item.previewImage, %this.item.reticle, %this.item.zoomReticle, 0 );
   }//yorks in end
}
[/code2]
Now the energyRifle's weapon icon will show on the HUD (we're using the lurker.png image) and there will be no ammo count.

[u][b]Part 4[/b][/u]
Give the player datablock the ability to have the weapon in it's inventory and set it to be the default weapon.
art/datablocks/player.cs edit the datablock for "PlayerData(DefaultPlayerData)"

[code2=ts]//...
   cameraMinDist = "0";
   DecalData = "PlayerFootprint";

   // Allowable Inventory Items
   mainWeapon = energyRifle;//yorks was - Ryder;
   
   maxInv[energyRifle] = 1;//yorks new

   maxInv[Lurker] = 1;
   maxInv[LurkerClip] = 20;

   maxInv[LurkerGrenadeLauncher] = 1;
   maxInv[LurkerGrenadeAmmo] = 20;
//...

Part 5
Let's equip the player with the energyRifle when they spawn.
scripts/server/gamecore.cs edit gameCore::loadout function.

function GameCore::loadOut(%game, %player)
{
   //echo (%game @"\c4 -> "@ %game.class @" -> GameCore::loadOut");

   %player.clearWeaponCycle();
   
   %player.setInventory(energyRifle, 1);//yorks new
   %player.addToWeaponCycle(energyRifle);//yorks new
   
   %player.setInventory(Ryder, 1);
   %player.setInventory(RyderClip, %player.maxInventory(RyderClip));
//...

Part 6
Finally we need to set the player's energy to the HUD so they can see what is going on. Open the GUI Editor and add a "GuiHealthBarHud" and a "GuiHealthTextHud". Remember to set both of these to display Energy or else they will show health. As the "minEnergy" for shooting is 10 in the imageDatablock, set the bar's "pulseThreshold" to 0.1 (10%) and the text's "warnThreshold" to 10.
Here is what my art/gui/playGui.gui looks like:
new GuiHealthBarHud() {
      fillColor = "0 0 0 0.65";
      frameColor = "0 0 1 1";
      damageFillColor = "0 1 1 1";
      pulseRate = "2";
      pulseThreshold = "0.1";
      showFill = "1";
      showFrame = "1";
      displayEnergy = "1";
      position = "202 697";
      extent = "32 64";
      minExtent = "8 2";
      horizSizing = "right";
      vertSizing = "top";
      profile = "GuiDefaultProfile";
      visible = "1";
      active = "1";
      tooltipProfile = "GuiToolTipProfile";
      hovertime = "1000";
      isContainer = "0";
      canSave = "1";
      canSaveDynamicFields = "0";
   };
   new GuiHealthTextHud() {
      fillColor = "0 0 0 0.65";
      frameColor = "0 0 0 1";
      textColor = "0 1 1 1";
      warningColor = "1 0 0 1";
      showFill = "1";
      showFrame = "1";
      showTrueValue = "0";
      showEnergy = "1";
      warnThreshold = "10";
      pulseThreshold = "5";
      pulseRate = "2";
      position = "239 693";
      extent = "72 72";
      minExtent = "8 2";
      horizSizing = "right";
      vertSizing = "top";
      profile = "GuiBigTextProfile";
      visible = "1";
      active = "1";
      tooltipProfile = "GuiToolTipProfile";
      hovertime = "1000";
      isContainer = "0";
      canSave = "1";
      canSaveDynamicFields = "0";
   };

Part 7
Turns out there is a crash if a non-ammo using weapon is the default weapon loaded at runtime and then changes to another weapon.
Crash does not occur if the starting weapon uses ammo ... so we need to get around that.

scripts/client/client.cs client command "RefreshWeaponHUD" function needs changing

function clientCmdRefreshWeaponHUD(%amount, %preview, %ret, %zoomRet, %amountInClips)
{
   if (!%amount)
   {
      //AmmoAmount.setVisible(false);//yorks bad!
      //causes a crash if this weapon is the default and you change to another ammo using weapon
      AmmoAmount.setText("");
   }
   else
   {
      AmmoAmount.setVisible(true);
      AmmoAmount.setText("Ammo: " @ %amount @ "/" @ %amountInClips);
   }
//...
And hopefully that's that and it all works fine.

#1
02/11/2015 (12:03 pm)
Nice resource Steve, thanks.
#2
02/11/2015 (6:07 pm)
Nice, now we have to add ribbons to the bullets and we have a blaster rifle!
#3
02/12/2015 (9:14 am)
Added a Part 7 to the end of the resource because of some daft crash.

Found a crash caused by RefreshWeaponHUD not displaying the ammoAmount if a non-ammo weapon is the first one loaded at startup and then the player switches to an ammo-using weapon. No crash if first weapon loaded is an ammo-user. Makes no sense but hey, we're all used to that. :P

function clientCmdRefreshWeaponHUD(%amount, %preview, %ret, %zoomRet, %amountInClips)
{
   if (!%amount)
   {
      //AmmoAmount.setVisible(false);//yorks bad!
      //causes a crash if this weapon is the default and you change to an other weapon
      AmmoAmount.setText("");
   }
   else
   {
      AmmoAmount.setVisible(true);
      AmmoAmount.setText("Ammo: " @ %amount @ "/" @ %amountInClips);
   }
//...
#4
02/19/2015 (4:40 pm)
great stuff, would love to see more such helpfully resources.

Thanks Steve!
#5
04/12/2015 (11:33 am)
Steve I'm wondering how you could convert this to vehicle weapons, where the vehicle generates energy and the weapon draws on available energy pool.
As this could be ideal for energy weapons, though wonder it there'd be issues if said vehcile had a combination of energy, missiles and/or ballistic ammo weapons?

Have you looked into this already?