Grenade Launcher

From Oldunreal-Wiki
Jump to navigation Jump to search

Okay, heres a simple and unimaginative grenade launcher that subclasses TournamentWeapon instead of a pre-existing weapon. I've tried to be as thorough as I can in describing whats going on in the major parts of how a weapon works. All the grenade launcher does is launch a single grenade in primary mode, and 3 in alt fire mode, and a lot of the code here is unnecessary, but I included it to hopefully help explain whats going on.


// ==========================================
// Grenade Launcher
// ==========================================
class GrnLauncher expands TournamentWeapon;

Any of the Play____() functions are animation functions and you should override them in your new weapons to play the right animations for your new mesh. In this case I'm using the Unreal Eightball mesh, so all I have to do is have it play those animations.

The PlayAnim() function plays animations (duh), using the name of the animation, rate of play, and finally tweentime. An easy way to make a weapon fire faster is to simply increase the rate at which the firing animation plays, since UT's weapons depend on the animation length as its timing. Tweening is a method Unreal uses to smooth out changes in animation keyframes, so if the weapon is already playing an animation, it will blend into the new Fire animation.


function PlayFiring()
{
  PlayAnim( 'Fire', 0.6, 0.05);
}
// The eightball mesh from Unreal doesn't have a specific AltFire
// animation, so I just have it play the normal Fire animation.
function PlayAltFiring()
{
  PlayFiring();
}

When the player presses the fire key/button, the Fire() function in PlayerPawn is called, which in turn calls the Fire() function of the currently selected weapon. Then the weapon tries to use some ammo, and then begins the firing process, either calling the TraceFire() function if bInstantHit is set to true (the sniper rifle and minigun are examples of this), or its calls the ProjectileFire() function which we use here.


function Fire( float Value )
{
  // AmmoType is a reference to our current Ammo, which is created
  // when we pick up the weapon.  The UseAmmo() function simply
  // returns true and subtracts if it has amount of ammo specified.
  if (AmmoType.UseAmmo(1))
  {
    // Then the weapon goes to the NormalFire state, which is a good
    // place to override functions that should be different from
    // the AltFiring mode, ie in this case the ProjectileFire()
    // function needs to be changed from each mode.
    GotoState('NormalFire');
    // The ShakeView() shakes the view of the player (duh), and
    // by tweaking these values you can add to the effect of a weapon.
    if ( PlayerPawn(Owner) != None )
      PlayerPawn(Owner).ShakeView(ShakeTime, ShakeMag, ShakeVert);
    bPointing=True;
    // Play the firing animation...
    PlayFiring();
    if ( !bRapidFire && (FiringSpeed > 0) )
      Pawn(Owner).PlayRecoil(FiringSpeed);
    // If bInstantHit is set to true, then call TraceFire(), otherwise
    // call the ProjectileFire() function.
    if ( bInstantHit )
      TraceFire(0.0);
    else
      ProjectileFire(ProjectileClass, ProjectileSpeed, bWarnTarget);
    if ( Owner.bHidden )
      CheckVisibility();
  }
}

States are a feature of Unrealscript that allow you to have different code executed within the same class, allowing you to easily add new functionality to your code without rewrite all of it. Weapons have 2 separate firing states, NormalFire and AltFiring. In our weapon we want the normal fire to shoot a single grenade, while altfire will shoot 3 of them, so really we only have to override the ProjectileFire() class in the AltFiring state, but I've overridden it in NormalFire too to explain how it works.


state NormalFire
{
  // ProjectileFire() is responsible for creating a new projectile in
  // the proper location, facing the right direction.
  function Projectile ProjectileFire(class<projectile> ProjClass,
                                 float ProjSpeed, bool bWarn)
  {
    local Vector Start, X,Y,Z;
    local Pawn PawnOwner;
    PawnOwner = Pawn(Owner);
    Owner.MakeNoise(PawnOwner.SoundDampening);
    // GetAxes() will get the 3d axes based upon the given rotation,
    // so in this case it gets X (the forward-back), Y (the left-right),
    // and Z (the up-down) axes to use in calculating the start spot.
    GetAxes(PawnOwner.ViewRotation,X,Y,Z);
    // Start is calculated by taking the weapon owner's location, adding
    // the value generated by the CalcDrawOffset() function and then
    // adding the various values stored in FireOffset to the appropriate
    // axes.  FireOffset.X * X basically means move the point over
    // however much is stored in FireOffset.X in the direction X is pointing.
    // CalcDrawOffset(), which is defined in Inventory, calculates the
    //  EyeHeight of the owner, plus the bob of the weapon.
    Start = Owner.Location + CalcDrawOffset() + FireOffset.X * X + 
                FireOffset.Y * Y + FireOffset.Z * Z; 
    // AdjustedAim is the direction the projectile will be facing and is
    // generated by a function in Pawn called AdjustAim().  AdjustAim()
    // basically takes the direction the weapon's owner is facing and
    // then adds in auto-aim, aimerror, etc.  You could also try:
    //   AdjustedAim = 100000 * X;
    // and it would work just the same, since that would be pointing to
    // a far away point along the X axis, which is straight ahead, but
    // it is a good idea to stick with AdjustAim().
    AdjustedAim = PawnOwner.AdjustAim(ProjSpeed, Start, AimError, True, bWarn); 
    // And finally create the projectile and return it.
    return Spawn(ProjClass,,, Start,AdjustedAim); 
  }
  // These are here so that you can't fire while the weapon is already
  // firing.  Otherwise the timing of the firing would be off, since
  // it would no longer have to wait for the animation to finish.
  function Fire(float F) 
  {
  }
  function AltFire(float F) 
  {
  }
Begin:
  // FinishAnim() is a latent function that pauses execution of the code
  // until the current animation being played finishes.  The Unreal/UT
  // weapon code depends on this wait for timing, so if you remove this
  // call for some reason it will throw off the timing of your weapon.
  // Try removing it from the minigun sometime for an interesting 
  // effect. :)
  FinishAnim();
  // Finish(), defined in Weapon simply checks to see if the weapon is
  // out of ammo and switches to a new weapon if it is, and checks to see
  // if the player still has the button down, and if so fires again, otherwise
  // it goes to the Idle state to play some idling animations and wait
  // to fire again.
  Finish();
}
state AltFiring
{
  // This is the same version as in the NormalFire except that I've
  // altered it slightly to produce 2 more grenades, instead of just
  // the one.
  function Projectile ProjectileFire(class<projectile> ProjClass,
                                 float ProjSpeed, bool bWarn)
  {
    local Vector Start, X,Y,Z;
    local Pawn PawnOwner;
    local rotator newAdjustedAim;
    PawnOwner = Pawn(Owner);
    Owner.MakeNoise(PawnOwner.SoundDampening);
    GetAxes(PawnOwner.ViewRotation,X,Y,Z);
    Start = Owner.Location + CalcDrawOffset() + FireOffset.X * X +
                         FireOffset.Y * Y + FireOffset.Z * Z; 
    AdjustedAim = PawnOwner.AdjustAim(ProjSpeed, Start, AimError, True, bWarn);
    // For the hell of it, lets spawn the grenades at a slight angle
    // to either side of the original.
    newAdjustedAim = AdjustedAim;
    newAdjustedAim.Yaw += 4096; // 65535/16 ~= 23 degrees
    // Then go ahead and create a grenade pointing at the new direction.    
    Spawn(ProjClass,,, Start, newAdjustedAim);
    // Lets do the next one, on the other side and create it.
    newAdjustedAim.Yaw -= 8192; // 4096*2 = 46 degrees...the other direction
    Spawn(ProjClass,,, Start, newAdjustedAim);
    // And finally the original one...
    return Spawn(ProjClass,,, Start,AdjustedAim); 
  }
  function Fire(float F) 
  {
  }
  function AltFire(float F) 
  {
  }
Begin:
  FinishAnim();
  Finish();
}

With a new weapon you must specify an Ammo, otherwise the weapon won't work (Fire() depends on it). In this case I just used the rocket ammo from UT, since I didn't have a new mesh or anything. If you're using UnrealEd the defaultproperties will be hidden to you, but you can edit them by right-clicking on the class and selecting Edit Default Properties. If you use a text editor + ucc you can just add the AmmoName=Class'Botpack.RocketPack' line like I did down below. Any variable can have its default value set here, and some important ones to remember for weapons are AmmoName, ProjectileClass if its a projectile weapon, or bInstantHit for hitscan weapons. Check out the Weapon class for more info on the rest of the ones listed here.


defaultproperties
{
     AmmoName=Class'Botpack.RocketPack'
     PickupAmmoCount=6
     ProjectileClass=Class'UnrealShare.Grenade'
     AltProjectileClass=Class'UnrealShare.Grenade'
     shakemag=350.000000
     shaketime=0.200000
     shakevert=7.500000
     AIRating=0.700000
     RefireRate=0.250000
     AltRefireRate=0.250000
     AltFireSound=Sound'UnrealShare.Eightball.EightAltFire'
     SelectSound=Sound'UnrealShare.Eightball.Selecting'
     DeathMessage="%o was smacked down multiple times by %k's %w."
     AutoSwitchPriority=5
     InventoryGroup=5
     PickupMessage="You got the grenade launcher"
     ItemName="GrenadeLauncher"
     PlayerViewOffset=(X=1.900000,Z=-1.890000)
     PlayerViewMesh=LodMesh'UnrealShare.EightB'
     BobDamping=0.985000
     PickupViewMesh=LodMesh'UnrealShare.EightPick'
     ThirdPersonMesh=LodMesh'UnrealShare.8Ball3rd'
     StatusIcon=Texture'Botpack.Icons.Use8ball'
     PickupSound=Sound'UnrealShare.Pickups.WeaponPickup'
     Mesh=LodMesh'UnrealShare.EightPick'
     bNoSmooth=False
     CollisionHeight=10.000000
     bInstantHit=false
     bAltInstantHit=false
}