news about forums links tools tutorials contact

Dispersion Pistol - author: crack_addict
The Dispersion Pistol is the default weapon used in Unreal DM, and probably the first weapon you will find in Unreal SP. It has two modes of fire, both projectile based. Primary fires a energy projectile based upon the current mode of the weapon (the Dispersion Pistol can be upgraded by another item). Secondary fires a more powerful projectile based upon how long the Pawn charges.
    //=================================================
    // DispersionPistol.
    //=================================================
    class DispersionPistol expands Weapon;
Whenever you come across something that begins with "#exec", that tells the compiler to import some file. Generally the things that are imported are models/meshes, sounds, and textures...anything that you find in Unreal that Unrealed doesn't create. You do not need to use any "#exec" lines if you are not importing any new models/meshes, or if you import textures and/or sounds inside of Unrealed.
For all weapons there are 3 different models/meshes that the Unreal package uses. Player view is the model that is shown to whoever is holding the weapon. Pickup view is the model that is shown when a weapon is sitting in a level waiting to be picked up. 3rd person is the model shown to other Players when they look at a Pawn using that particular weapon.
The player view model is held up close to the Player's body, so generally it is a fraction of the size that pickup and 3rd person versions, but all 3 of the models come from the same source. All of the following pre imports first the mesh, sets the origin of the model (for rotation etc.), scales the model to the appropriate size, identifies all of the animations of the model (SEQUENCE), and then imports a texture and applies it to the mesh.

    // player view version
    #exec MESH IMPORT MESH=DPistol ANIVFILE=MODELS\dgun_a.3D
      DATAFILE=MODELS\dgun_d.3D X=0 Y=0 Z=0

    #exec MESH ORIGIN MESH=DPistol X=0 Y=0 Z=0 YAW=-64 PITCH=0
    #exec MESHMAP SCALE MESHMAP=DPistol X=0.005 Y=0.005 Z=0.01
    #exec MESH SEQUENCE MESH=DPistol SEQ=All     STARTFRAME=0  NUMFRAMES=141
    #exec MESH SEQUENCE MESH=DPistol SEQ=Select1 STARTFRAME=0  NUMFRAMES=11
      GROUP=Select
    #exec MESH SEQUENCE MESH=DPistol SEQ=Shoot1  STARTFRAME=11 NUMFRAMES=3
    #exec MESH SEQUENCE MESH=DPistol SEQ=Idle1   STARTFRAME=14  NUMFRAMES=2
    #exec MESH SEQUENCE MESH=DPistol SEQ=Down1   STARTFRAME=16  NUMFRAMES=5
    #exec MESH SEQUENCE MESH=DPistol SEQ=PowerUp1 STARTFRAME=21  NUMFRAMES=4
    #exec MESH SEQUENCE MESH=DPistol SEQ=Still    STARTFRAME=25  NUMFRAMES=5
    #exec MESH SEQUENCE MESH=DPistol SEQ=Select2 STARTFRAME=30  NUMFRAMES=11
      GROUP=Select
    #exec MESH SEQUENCE MESH=DPistol SEQ=Shoot2  STARTFRAME=41 NUMFRAMES=3
    #exec MESH SEQUENCE MESH=DPistol SEQ=Idle2   STARTFRAME=44  NUMFRAMES=2
    #exec MESH SEQUENCE MESH=DPistol SEQ=Down2   STARTFRAME=46  NUMFRAMES=5
    #exec MESH SEQUENCE MESH=DPistol SEQ=PowerUp2 STARTFRAME=51  NUMFRAMES=9
    #exec MESH SEQUENCE MESH=DPistol SEQ=Select3 STARTFRAME=60  NUMFRAMES=11
      GROUP=Select
    #exec MESH SEQUENCE MESH=DPistol SEQ=Shoot3  STARTFRAME=71 NUMFRAMES=3
    #exec MESH SEQUENCE MESH=DPistol SEQ=Idle3   STARTFRAME=74  NUMFRAMES=2
    #exec MESH SEQUENCE MESH=DPistol SEQ=Down3   STARTFRAME=76  NUMFRAMES=5
    #exec MESH SEQUENCE MESH=DPistol SEQ=PowerUp3 STARTFRAME=81  NUMFRAMES=9
    #exec MESH SEQUENCE MESH=DPistol SEQ=Select4 STARTFRAME=90  NUMFRAMES=11
      GROUP=Select
    #exec MESH SEQUENCE MESH=DPistol SEQ=Shoot4  STARTFRAME=101 NUMFRAMES=3
    #exec MESH SEQUENCE MESH=DPistol SEQ=Idle4   STARTFRAME=104  NUMFRAMES=2
    #exec MESH SEQUENCE MESH=DPistol SEQ=Down4   STARTFRAME=106  NUMFRAMES=5
    #exec MESH SEQUENCE MESH=DPistol SEQ=PowerUp4 STARTFRAME=111  NUMFRAMES=9
    #exec MESH SEQUENCE MESH=DPistol SEQ=Select5 STARTFRAME=120 NUMFRAMES=11
      GROUP=Select
    #exec MESH SEQUENCE MESH=DPistol SEQ=Shoot5  STARTFRAME=131 NUMFRAMES=3
    #exec MESH SEQUENCE MESH=DPistol SEQ=Idle5   STARTFRAME=134 NUMFRAMES=2
    #exec MESH SEQUENCE MESH=DPistol SEQ=Down5   STARTFRAME=136  NUMFRAMES=5

    #exec TEXTURE IMPORT NAME=DPistol1 FILE=MODELS\dgun.PCX GROUP="Skins"
    #exec OBJ LOAD FILE=Textures\SmokeEffect2.utx PACKAGE=UNREALI.SEffect2
    #exec MESHMAP SETTEXTURE MESHMAP=DPistol NUM=1  TEXTURE=DPistol1
    #exec MESHMAP SETTEXTURE MESHMAP=DPistol NUM=0
      TEXTURE=Unreali.SEffect2.SmokeEffect2

    // pickup version
    #exec MESH IMPORT MESH=DPistolPick ANIVFILE=MODELS\dgunlo_a.3D
      DATAFILE=MODELS\dgunlo_d.3D X=0 Y=0 Z=0

    #exec MESH ORIGIN MESH=DPistolPick X=0 Y=0 Z=0 YAW=64
    #exec MESH SEQUENCE MESH=DPistolPick SEQ=All  STARTFRAME=0  NUMFRAMES=1
    #exec TEXTURE IMPORT NAME=DPistol1 FILE=MODELS\dgun.PCX GROUP="Skins"
    #exec MESHMAP SCALE MESHMAP=DPistolPick X=0.04 Y=0.04 Z=0.08
    #exec MESHMAP SETTEXTURE MESHMAP=DPistolPick NUM=1 TEXTURE=DPistol1

    // 3rd person perspective version
    #exec MESH IMPORT MESH=DPistol3rd ANIVFILE=MODELS\dgunlo_a.3D
      DATAFILE=MODELS\dgunlo_d.3D X=0 Y=0 Z=0

    #exec MESH ORIGIN MESH=DPistol3rd X=0 Y=-200 Z=-110 YAW=-64 ROLL=9
    #exec MESH SEQUENCE MESH=DPistol3rd SEQ=All  STARTFRAME=0  NUMFRAMES=6
    #exec MESH SEQUENCE MESH=DPistol3rd SEQ=Still  STARTFRAME=0  NUMFRAMES=1
    #exec MESH SEQUENCE MESH=DPistol3rd SEQ=Shoot1 STARTFRAME=0  NUMFRAMES=6
    #exec TEXTURE IMPORT NAME=DPistol1 FILE=MODELS\dgun.PCX GROUP="Skins"
    #exec MESHMAP SCALE MESHMAP=DPistol3rd X=0.025 Y=0.025 Z=0.05
    #exec MESHMAP SETTEXTURE MESHMAP=DPistol3rd NUM=1 TEXTURE=DPistol1

Here "#exec" is used to import in sounds for the weapon, one for charging, another for firing, and another for when a Pawn picks it up.

    #exec AUDIO IMPORT FILE="Sounds\dispersion\Powerup3.WAV" NAME="PowerUp3"
      GROUP="Dispersion"
    #exec AUDIO IMPORT FILE="Sounds\dispersion\DShot1.WAV" NAME="DispShot"
      GROUP="Dispersion"
    #exec AUDIO IMPORT FILE="Sounds\dispersion\Dpickup2.WAV" NAME="DispPickup"
      GROUP="Dispersion"

Here after all the "#exec" lines the global variables are defined. These variables stay alive as long as their is a Spawned copy of this particular class in the game, whereas the variables defined inside of functions later on are local, and die as soon as the function is finished. Powerlevel stores what level of upgrade this particular Dispersion Pistol is, WeaponPos is an extra vector that is used later on to keep track of the current position of the weapon. Count and ChargeSize are both used to calculate the amount of charge when the Pistol is fired in Secondary mode. cl1 and cl2 are instances of ChargeLight, that as far as I can tell aren't used for anything useful in this version of the pre. Amp is a copy used to determine/use the inventory Amplifier, if the Pawn has an active version. PowerUpSound is the power up sound (tough one eh?).

    var travel int PowerLevel;
    var vector WeaponPos;
    var float Count,ChargeSize;
    var ChargeLight cl1,cl2;
    var Pickup Amp;
    var Sound PowerUpSound;

The Engine calls three functions whenever an Actor is created, PreBeginPlay(), BeginPlay(), and PostBeginPlay(). They are very useful for executing pre no matter what, every time an instance of this Actor is created. Here PostBeginPlay() is used to store what Projectile to fire for both Primary and Secondary Fire.

    function PostBeginPlay()
    {
      Super.PostBeginPlay();
      ProjectileClass = class'DispersionAmmo';
      AltProjectileClass = class'DispersionAmmo';
      ProjectileSpeed = class'DispersionAmmo'.Default.speed;
      AltProjectileSpeed = class'DispersionAmmo'.Default.speed;
    }

RateSelf() is an AI function used solely by Bots (at the moment) to determine what priority this weapon should take when compared to others in the Bot's inventory.

    function float RateSelf( out int bUseAltMode )
    {
      local float rating;

      if ( Pawn(Owner).bShootSpecial )
        return 1000;

      if ( Amp != None )
        rating = 3.1 * AIRating;
      else
        rating = AIRating;

      if ( AmmoType.AmmoAmount <=0 )
        return 0.05;
      if ( pawn(owner).enemy == none )
      {
        busealtmode = 0;
        return rating * (powerlevel + 1);
      }

      busealtmode = int( frand() < 0.3 );
      return rating * (powerlevel + 1);
    }

SuggestAttackStyle() determines whether the bot should go with primary or secondary fire.

    // return delta to combat style
    function float SuggestAttackStyle()
    {
      local float EnemyDist;
      local Inventory Inv;

      if ( !Pawn(Owner).bIsPlayer || (PowerLevel > 0) )
        return 0;

      return -0.3;
    }

HandlePickupQuery() is a great function that allows an inventory item to perform some action whenever its Owner picks up another item. The Dispersion Pistol uses it here to check if its Owner just picked up a Powerup, and if so it will 'upgrade' itself.

    function bool HandlePickupQuery( inventory Item )
    {
      if ( Item.IsA('WeaponPowerup') )
      {
        AmmoType.AddAmmo(AmmoType.MaxAmmo);
        Pawn(Owner).ClientMessage(Item.PickupMessage);
        Item.PlaySound (PickupSound);
        if ( PowerLevel<4 )
        {
          shakevert = default.shakevert + powerlevel;
          powerupsound = item.activatesound;
          if ( pawn(owner).weapon == self )
          {
            powerlevel++;
            gotostate('powerup');
          }
          else if ( (pawn(owner).weapon != self)
    && !pawn(owner).bneverswitchonpickup )
          {
            pawn(owner).weapon.putdown();
            pawn(owner).pendingweapon = self;
            gotostate('powerup', 'waiting');
          }
          else powerlevel++;
        }
        item.setrespawn();
        return true;
      }
      else
        return Super.handlepickupquery(item);
    }

SetSwitchPriority() is a function used to automatically determine what weapon should come up when you either pick up a new weapon, or run out of ammo for the current weapon.

    function SetSwitchPriority(pawn Other)
    {
      local int i;
      local name MyType;

      if (PowerLevel == 0)
        MyType = 'DispersionPistol';
      else if (PowerLevel == 1)
        MyType = 'DispersionPower1';
      else if (PowerLevel == 2)
        MyType = 'DispersionPower2';
      else if (PowerLevel == 3)
        MyType = 'DispersionPower3';
      else if (PowerLevel == 4)
        MyType = 'DispersionPower4';
      else
        MyType = 'DispersionPower5';

      if ( PlayerPawn(Other) != None )
        for ( i=0; i<20; i++)
          if ( playerpawn(other).weaponpriority[i] == mytype )
          {
            autoswitchpriority = i;
            return;
          }
    }

    function becomepickup()
    {
      amp = none;
      super.becomepickup();
    }

PlayFiring() (and PlayAltFiring()) are basically the animation and sound control functions that are called whenever the weapon is fired. The Dispersion Pistol plays a firing sound, shakes it's Owner's view via ShakeView(), and then based upon the PowerLevel it plays a firing animation.

    function PlayFiring()
    {
      AmmoType.GoToState('Idle2');
      Owner.PlaySound(AltFireSound, SLOT_None, 1.8*Pawn(Owner).SoundDampening
        ,,,1.2);
      if ( PlayerPawn(Owner) != None )
        PlayerPawn(Owner).ShakeView(ShakeTime, ShakeMag, ShakeVert);
      if (PowerLevel==0)
        PlayAnim('Shoot1',0.4,0.2);
      else if (PowerLevel==1)
        PlayAnim('Shoot2',0.3,0.2);
      else if (PowerLevel==2)
        PlayAnim('Shoot3',0.2, 0.2);
      else if (PowerLevel==3)
        PlayAnim('Shoot4',0.1,0.2);
      else if (PowerLevel==4)
        PlayAnim('Shoot5',0.1,0.2);
    }

ProjectileFire() is the function used by all Projectile-based weapons to fire the appropriate projectile, in the appropriate direction, from the correct starting point.

    function Projectile ProjectileFire(class ProjClass, float ProjSpeed, bool bWarn)
    {
      local Vector Start, X,Y,Z;
      local DispersionAmmo da;
      local float Mult;

      Owner.MakeNoise(Pawn(Owner).SoundDampening);

If the Pistol's Owner has an Amplifier then set the damage multiplier equal to whatever Amp.UseCharge(80) returns.

      if (Amp!=None) Mult = Amp.UseCharge(80);
      else Mult=1.0;

GetAxes is a function that returns normals along the axis of a given rotator. Here is used to store the axes from the Pistol's Owner's ViewRotation (the direction the Owner is looking).

      GetAxes(Pawn(owner).ViewRotation,X,Y,Z);

Start is calculated by first using the Owner's Location, then adding ClacDrawOffset(), and then moving along the proper axis the amount determined by FireOffset specified in the Default Properties.

      Start = Owner.Location + CalcDrawOffset() + FireOffset.X * X +
    FireOffset.Y * Y + FireOffset.Z * Z;

AdjustedAim is essentially the direction that the projectile is to be fired. It is calculated here by a function in Pawn called AdjustAim, that essentially just uses the Owner's ViewRotation and adjusts it towards any targets if AutoAim is enabled.

      AdjustedAim = pawn(owner).AdjustAim(ProjSpeed, Start,
                             AimError, True, (3.5*FRand()-1<PowerLevel));

Here is where the actual sparkling projectile you see is created. Depending on the Powerlevel, different projectiles (with different properties/meshes) are created.

      if ( (PowerLevel == 0) || (AmmoType.AmmoAmount < 10) )
        da = dispersionammo(spawn(projclass,,, start,adjustedaim));
      else
      {
        if ( (powerlevel==1) && ammotype.useammo(2) )
          da = spawn(class'dammo2',,, start,adjustedaim);
        if ( (powerlevel==2) && ammotype.useammo(4) )
          da = spawn(class'dammo3',,, start,adjustedaim);
        if ( (powerlevel==3) && ammotype.useammo(5) )
          da = spawn(class'dammo4',,, start ,adjustedaim);
        if ( (powerlevel>=4) && AmmoType.UseAmmo(6) )
          da = Spawn(class'DAmmo5',,, Start,AdjustedAim);
      }

      if ( da != None )
      {
        if (Mult>1.0) da.InitSplash(FMin(da.damage * Mult, 100));
      }
    }

Fire() and AltFire() are one of the many functions called when a Pawn decides it wants to fire the weapon its using. This version of AltFire() just sets a boolean to true for AI purposes, calls CheckVisibility() which I'm not exactly sure does, and then goes to the AltFiring state.

    function AltFire( float Value )
    {
      bPointing=True;
      CheckVisibility();
      GoToState('AltFiring');
    }

The AltFiring state in the Dispersion Pistol doesn't actually fire the weapon, but instead increments a value while the AltFire is pushed and once the button is released it changes states to ShootLoad which takes care of firing the projectile.

    state AltFiring
    {
    ignores AltFire;

The Tick() function is a way to make rapid updates/changes to a class. It is called whenever possible (when enabled), and can be used for pretty much anything you can think of. Be wary of putting too much pre in the tick function, because it is called alot in a short amount of time, and can bring the game to a halt if it has to process too much. The Dispersion Pistol uses it to keep track of how long it has been charging, and limits how much you can charge.

      function Tick( float DeltaTime )
      {
        if ( Level.NetMode == NM_StandAlone )
        {
          PlayerViewOffset.X = WeaponPos.X + FRand()*ChargeSize*7;
          PlayerViewOffset.Y = WeaponPos.Y + FRand()*ChargeSize*7;
          PlayerViewOffset.Z = WeaponPos.Z + FRand()*ChargeSize*7;
        }
        ChargeSize += DeltaTime;
        if( (pawn(Owner).bAltFire==0)) GoToState('ShootLoad');
        Count += DeltaTime;
        if (Count > 0.3)
        {
          Count = 0.0;
          If (!AmmoType.UseAmmo(1)) GoToState('ShootLoad');
          AmmoType.GoToState('Idle2');
        }
      }

EndState() and BeginState() are two function that can be used to do something at the end and beginning of a state respectively. Dispersion Pistol uses BeginState() to initialize two variables, and uses EndState() to clean up.

      Function EndState()
      {
        PlayerviewOffset = WeaponPos;
        if (cl1!=None) cl1.Destroy();
        if (cl2!=None) cl2.Destroy();
      }

      function BeginState()
      {
        WeaponPos = PlayerviewOffset;
        ChargeSize=0.0;
      }

The Begin: and End: labels are nearly identical to BeginState() and EndState(), the only main difference being that BeginState() and EndState() are functions, and if you need to use a local variable (to find some object for example) you can only create variables inside functions. Otherwise Begin: and End: can be used for the same purposes.

    Begin:
      if (AmmoType.UseAmmo(1))
      {
        Owner.Playsound(Misc1Sound,SLOT_Misc, Pawn(Owner).SoundDampening*4.0);
        Count = 0.0;
        Sleep(2.0 + 0.6 * PowerLevel);
        GoToState('ShootLoad');
      }
      else GotoState('Idle');
    }

ShootLoad is used to fire off a projectile when the Dispersion has either reached its max charge, or the Owner decided to let go. Nothing really special in here, the BeginState() function is essentially ProjectileFire() except that it takes into account the ChargeSize variable, to increase damage and size appropriately.

    state ShootLoad
    {
      function Fire(float F) {}
      function AltFire(float F) {}

      function BeginState()
      {
        local DispersionAmmo d;
        local Vector Start, X,Y,Z;
        local float Mult;

        if (Amp!=None) Mult = Amp.UseCharge(ChargeSize*50+50);
        else Mult=1.0;

        Owner.MakeNoise(Pawn(Owner).SoundDampening);
        GetAxes(Pawn(owner).ViewRotation,X,Y,Z);
        InvCalcView();
        Start = Location + FireOffset.X * X + FireOffset.Y * Y
                + FireOffset.Z * Z;
        AdjustedAim = pawn(owner).AdjustAim(AltProjectileSpeed,
                           Start, AimError, True, True);
        d = DispersionAmmo(Spawn(AltProjectileClass,,, Start,AdjustedAim));
        d.bAltFire = True;
        d.DrawScale = 0.5 + ChargeSize*0.6;
        d.InitSplash(d.DrawScale * Mult * 1.1);
        Owner.PlaySound(AltFireSound, SLOT_Misc, 1.8*Pawn(Owner).SoundDampening);
        if ( PlayerPawn(Owner) != None )
          PlayerPawn(Owner).ShakeView(ShakeTime, ShakeMag*ChargeSize, ShakeVert);
        if (PowerLevel==0) PlayAnim('Shoot1',0.2, 0.05);
        else if (PowerLevel==1) PlayAnim('Shoot2',0.2, 0.05);
        else if (PowerLevel==2) PlayAnim('Shoot3',0.2, 0.05);
        else if (PowerLevel==3) PlayAnim('Shoot4',0.2, 0.05);
        else if (PowerLevel==4) PlayAnim('Shoot5',0.2, 0.05);
      }
    Begin:
      FinishAnim();
      Finish();
    }

PlayIdleAnim() is an animation function that is called to play an idle animation, and because the Dispersion Pistol happens to have more than one idle animation, PlayIdleAnim() plays the proper animation depending on the PowerLevel.

    function PlayIdleAnim()
    {
      if (PowerLevel==0) LoopAnim('Idle1',0.04,0.2);
      else if (PowerLevel==1) LoopAnim('Idle2',0.04,0.2);
      else if (PowerLevel==2) LoopAnim('Idle3',0.04,0.2);
      else if (PowerLevel==3) LoopAnim('Idle4',0.04,0.2);
      else if (PowerLevel==4) LoopAnim('Idle5',0.04,0.2);
    }

The PowerUp state is active when the weapon is powering up after picking up a weaponpowerup (tough one eh?), and can be used to play animations, sounds, modify variables or anything else. The Dispersion Pistol needs this to play the proper animation (remember those cool things popping out of your gun?), and modify the ammo.

    state PowerUp
    {
    ignores fire, altfire;

      function BringUp()
      {
        bWeaponUp = false;
        PlaySelect();
        GotoState('Powerup', 'Raising');
      }

      function bool PutDown()
      {
        bChangeWeapon = true;
        return True;
      }

      function BeginState()
      {
        bChangeWeapon = false;
      }

    Raising:
      FinishAnim();
      PowerLevel++;
    Begin:
      if (PowerLevel<5)
      {
        ammotype.maxammo += 10;
        ammotype.addammo(10);
        if ( powerlevel < 5 )
          owner.playsound(powerupsound, slot_none, pawn(owner).sounddampening);
        if (powerlevel==1)
          playanim('powerup1',0.1, 0.05);
        else if (powerlevel==2)
          playanim('powerup2',0.1, 0.05);
        else if (powerlevel==3)
          playanim('powerup3',0.1, 0.05);
        else if (powerlevel==4)
          playanim('powerup4',0.1, 0.05);
        finishanim();
        if ( bchangeweapon )
          gotostate('downweapon');
        finish();
      }
    waiting:
    }

All Tween* functions are animation related functions that are used to make the transition between different animations. It is important to note that TweenAnim is not the same as PlayAnim. You should use PlayAnim when there isn't currently an animation already playing, and use TweenAnim when one is playing, otherwise you could end up with noticeable "jumps" from one animation to the other.

    function TweenDown()
    {
      if ( GetAnimGroup(AnimSequence) == 'Select' )
        TweenAnim( AnimSequence, AnimFrame * 0.4 );
      else
      {
        if (PowerLevel==0) PlayAnim('Down1', 1.0, 0.05);
        else if (PowerLevel==1) PlayAnim('Down2', 1.0, 0.05);
        else if (PowerLevel==2) PlayAnim('Down3', 1.0, 0.05);
        else if (PowerLevel==3) PlayAnim('Down4', 1.0, 0.05);
        else if (PowerLevel==4) PlayAnim('Down5', 1.0, 0.05);
      }
    }

    function TweenSelect()
    {
      TweenAnim('Select1',0.001);
    }

    function PlaySelect()
    {
      Owner.PlaySound(SelectSound, SLOT_None, Pawn(Owner).SoundDampening);
      if (PowerLevel==0) PlayAnim('Select1',0.5,0.0);
      else if (PowerLevel==1) PlayAnim('Select2',0.5,0.0);
      else if (PowerLevel==2) PlayAnim('Select3',0.5,0.0);
      else if (PowerLevel==3) PlayAnim('Select4',0.5,0.0);
      else if (PowerLevel==4) PlayAnim('Select5',0.5,0.0);
    }