| Home Page | Recent Changes

TheHealer/TUTHealerAltFire

TUTHealerAltFire - The Healer Part 6 of 9

In this part of the Healer tutorial, we will set up our Alt-Fire class. This is the beam that does damage to enemies. Before we get into the code, make sure that your file is set up correctly.

Filename: {Base Directory}/TheHealer/classes/TUTHealerAltFire.uc

If you've changed the name of the package folder or the filename itself, make sure you make those changes throughout the code we'll be working with.

Quick Link: This is the complete source code for people who want to skip ahead. If you want to copy/paste into your file, this is the page to do it on.

This is where more of the cool stuff happens. Setting up the Weapon, Pickup, Ammo, and Ammo Pickup classes was necessary, but let's face it – most aspects of those files came right from the Link Gun. Well, most of this comes from the Link Gun too, but here is where we change the core functionality of the weapon to make it our own. This is the class that defines the Damaging properties of our weapon.

Note: I just realized how screwy my uscript is... it didn't make the transition from WOTgreal to Notepad to Wiki very well. It still works, but it's not very pretty. I'll fix it up at a later time, since it's not too bad.

What you may notice here is that this class is a very close approximation of the Fire class. This is to be expected, since there are only 3 differences between the two.

  • The beam will damage instead of heal.
  • The beam is red, not green.
  • The beam only works on enemies, not friends.

Heading and Class Declaration

In order to keep our code reader-friendly (always a good thing if you need someone's help!), we will add a standard comment block to the top of the file. This will describe what's going on in the file.

After the comment block, we declare the class. Make sure you name the class with the same name as the filename, or bad things happen.

//------------------------------------------------------------------------------
// class name : TUTHealerAltFire
// class type : Weapon Fire
// description: The Healer's Secondary Fire mode
// author     : HSDanClark
//------------------------------------------------------------------------------
// TODO       :
//
//------------------------------------------------------------------------------
class TUTHealerAltFire extends WeaponFire;
  • Our pickup extends (or is a child of, so-to-speak) the WeaponFire class.

Exec Directives and Variable Declarations

No Exec directives.

var TUTHealerBeamEffect Beam;
var Sound MakeLinkSound;
var float UpTime;
var Pawn LockedPawn;
var float LinkBreakTime;
var() float LinkBreakDelay;
var float LinkScale[6];

var string MakeLinkForce;

var() class<DamageType> DamageType;
var() int Damage;
var() float MomentumTransfer;

var() float TraceRange;
var() float LinkFlexibility;

var bool bDoHit;
var()   bool bFeedbackDeath;
var bool bInitAimError;
var bool bLinkFeedbackPlaying;
var bool bStartFire;

var rotator DesiredAimError, CurrentAimError;

Functions

simulated function DestroyEffects()
{
    Super.DestroyEffects();
    if ( Level.NetMode != NM_Client )
    {
        if (Beam != None)
            Beam.Destroy();
    }
}

The next function, ModeTick, is where the beam damage and such is defined. Pay close attention to what's going on in here.

simulated function ModeTick(float dt)
{
    local Vector StartTrace, EndTrace, X,Y,Z;
    local Vector HitLocation, HitNormal, EndEffect;
    local Actor Other;
    local Rotator Aim;
    local TUTHealer TUTHealer;
    local float MT, Step;
    local bot B;
    local bool bShouldStop;
    local int AdjustedDamage;
    local TUTHealerBeamEffect LB;

    if (!bIsFiring)
    {
    bInitAimError = true;
        return;
    }
    TUTHealer = TUTHealer(Weapon);


    if (FlashEmitter != None)
    {
        // set muzzle flash color
        FlashEmitter.Skins[0] = Texture'XEffectMat.link_muz_red';

        // adjust muzzle flash size to link size
    FlashEmitter.mSizeRange[0] = FlashEmitter.default.mSizeRange[0] * 1;
    FlashEmitter.mSizeRange[1] = FlashEmitter.mSizeRange[0];
    }

    if ( TUTHealer.Ammo[ThisModeNum].AmmoAmount >= AmmoPerFire && ((UpTime > 0.0) || (Instigator.Role < ROLE_Authority)) )
    {
        UpTime -= dt;
        TUTHealer.GetViewAxes(X,Y,Z);

        // the to-hit trace always starts right in front of the eye
        StartTrace = Instigator.Location + Instigator.EyePosition() + X*Instigator.CollisionRadius;

        TraceRange = default.TraceRange;

        if ( Instigator.Role < ROLE_Authority )
        {
        if ( Beam == None )
        foreach DynamicActors(class'TUTHealerBeamEffect', LB )
            if ( !LB.bDeleteMe && (LB.Instigator != None) && (LB.Instigator == Instigator) )
            {
            Beam = LB;
            break;
            }
            if ( Beam != None )
            LockedPawn = Beam.LinkedPawn;
    }
        if ( LockedPawn != None )
        TraceRange *= 1.5;


    if ( LockedPawn != None )
    {
        EndTrace = LockedPawn.Location + LockedPawn.EyeHeight*Vect(0,0,0.5); // beam ends at approx gun height
    }

        if ( LockedPawn == None )
        {
            if ( Bot(Instigator.Controller) != None )
            {
            if ( bInitAimError )
        {
            CurrentAimError = AdjustAim(StartTrace, AimError);
            bInitAimError = false;
        }
        else
        {
            BoundError();
            CurrentAimError.Yaw = CurrentAimError.Yaw + Instigator.Rotation.Yaw;
        }
        // smooth aim error changes
        Step = 7500.0 * dt;
        if ( DesiredAimError.Yaw ClockWiseFrom CurrentAimError.Yaw )
        {
            CurrentAimError.Yaw += Step;
            if ( !(DesiredAimError.Yaw ClockWiseFrom CurrentAimError.Yaw) )
            {
                CurrentAimError.Yaw = DesiredAimError.Yaw;
            DesiredAimError = AdjustAim(StartTrace, AimError);
            }
        }
        else
        {
            CurrentAimError.Yaw -= Step;
            if ( DesiredAimError.Yaw ClockWiseFrom CurrentAimError.Yaw )
            {
                CurrentAimError.Yaw = DesiredAimError.Yaw;
            DesiredAimError = AdjustAim(StartTrace, AimError);
            }
        }
        CurrentAimError.Yaw = CurrentAimError.Yaw - Instigator.Rotation.Yaw;
        if ( BoundError() )
            DesiredAimError = AdjustAim(StartTrace, AimError);
            CurrentAimError.Yaw = CurrentAimError.Yaw + Instigator.Rotation.Yaw;
        Aim = Rotator(Instigator.Controller.Target.Location - StartTrace);

        Aim.Yaw = CurrentAimError.Yaw;

        // save difference
        CurrentAimError.Yaw = CurrentAimError.Yaw - Instigator.Rotation.Yaw;
            }
        else
            Aim = AdjustAim(StartTrace, AimError);

            X = Vector(Aim);
            EndTrace = StartTrace + TraceRange * X;
        }

        Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
        if (Other != None && Other != Instigator)
        EndEffect = HitLocation;
        else
        EndEffect = EndTrace;
    if (Beam != None)
            Beam.EndEffect = EndEffect;

    if ( Instigator.Role < ROLE_Authority )
            return;

        if (Other != None && Other != Instigator)
        {
            // beam is updated every frame, but damage is only done based on the firing rate
            if (bDoHit)
            {
                if (Beam != None)
            Beam.bLockedOn = false;

                    Instigator.MakeNoise(1.0);

                    AdjustedDamage = Damage;

                    if ( !Other.bWorldGeometry )
                    {
                        if (Level.Game.bTeamGame && Pawn(Other) != None && (Pawn(Other).PlayerReplicationInfo != None)
                    && Pawn(Other).PlayerReplicationInfo.Team == Instigator.PlayerReplicationInfo.Team)
                            AdjustedDamage = 0; // so even if friendly fire is on you can't hurt teammates

                        if (Other.Physics == PHYS_Falling || Other.Physics == PHYS_Flying || Other.Physics == PHYS_Swimming)
                            MT = DeathMatch(Level.Game).LinkLockDownFactor * MomentumTransfer;
                        else
                            MT = 0.0;

                        Other.TakeDamage(AdjustedDamage, Instigator, HitLocation, MT*X, DamageType);

                        if (Beam != None)
                            Beam.bLockedOn = true;
                    }
            }

        }

        if ( bShouldStop )
        B.StopFiring();
        else
    {
        // beam effect is created and destroyed when firing starts and stops

            if ( (Beam == None) && bIsFiring )
            Beam = Spawn(class'NCHealerBeamEffect',Instigator);

        if (Beam != None)
        {
            Beam.LinkColor = 1;
        Beam.LinkedPawn = LockedPawn;
        Beam.bHitSomething = (Other != None);
        Beam.EndEffect = EndEffect;
        }
    }
    }
    else
    {
        StopFiring();
    }

    bStartFire = false;
    bDoHit = false;
}

Yikes. Well, that's the most important bit right in there. I will add more comments in that code later, but let's press on.

function bool BoundError()
{
    CurrentAimError.Yaw = CurrentAimError.Yaw & 65535;
    if ( CurrentAimError.Yaw > 2048 )
    {
        if ( CurrentAimError.Yaw < 32768 )
    {
        CurrentAimError.Yaw = 2048;
        return true;
    }
    else if ( CurrentAimError.Yaw < 63487 )
    {
        CurrentAimError.Yaw = 63487;
        return true;
    }
    }
    return false;
}

function DoFireEffect()
{
    bDoHit = true;
    UpTime = FireRate+0.1;
}
function PlayFiring()
{
    if (Weapon.Ammo[ThisModeNum].AmmoAmount >= AmmoPerFire)
    ClientPlayForceFeedback("BLinkGunBeam1");
    Super.PlayFiring();
}

function StopFiring()
{
    if (Beam != None)
    {
        Beam.Destroy();
        Beam = None;
    }

    bStartFire = true;
    bFeedbackDeath = false;
    StopForceFeedback("BLinkGunBeam1");
}

The two functions above control the starting and stopping of the firing process. As you can see, the PlayFiring() function checks to make sure you have enough ammo, and if you do, will start the force feedback feature should the player have it enabled. The StopFiring() function does the opposite, Destroy()ing the beam you've created, setting it to none, then stopping the force feedback feature.

simulated function vector GetFireStart(vector X, vector Y, vector Z)
{
    return Instigator.Location + Instigator.EyePosition() + X*Instigator.CollisionRadius;
}

Default Properties

defaultproperties
{
    NoAmmoSound=Sound'WeaponSounds.P1Reload5'

    AmmoClass=class'TUTHealerAmmo'
    AmmoPerFire=1
    DamageType=class'DamTypeLinkShaft'
    Damage=10
    MomentumTransfer=20000.0
    bPawnRapidFireAnim=true

    FireAnim=AltFire
    FireEndAnim=None

    MakeLinkSound=Sound'WeaponSounds.LinkGun.LinkActivated'
    MakeLinkForce="LinkActivated"

    FlashEmitterClass=class'xEffects.LinkMuzFlashBeam1st'

    TraceRange=1100      // range of everything
    LinkFlexibility=0.64 // determines how easy it is to maintain a link.
                         // 1=must aim directly at linkee, 0=linkee can be 90 degrees to either side of you

    LinkBreakDelay=0.5   // link will stay established for this long extra when blocked (so you don't have to worry about every last tree getting in the way)

    FireRate=0.2

    BotRefireRate=0.99
    WarnTargetPct=+0.2

    ShakeOffsetMag=(X=0.0,Y=1.0,Z=1.0)
    ShakeOffsetRate=(X=1000.0,Y=1000.0,Z=1000.0)
    ShakeOffsetTime=3
    ShakeRotMag=(X=0.0,Y=0.0,Z=60.0)
    ShakeRotRate=(X=0.0,Y=0.0,Z=4000.0)
    ShakeRotTime=6

    bInitAimError=true

    LinkScale(0)=0.0
    LinkScale(1)=0.5
    LinkScale(2)=0.9
    LinkScale(3)=1.2
    LinkScale(4)=1.4
    LinkScale(5)=1.5
}

Now, these default properties are going to be a little strange. This code is not optimized for our Healer weapon. In fact, most of the properties are in some way linked (get it? linked?) to the alt-fire of the Link Gun. They have been left in for now, though I intend to optimize the code to remove all unnecessary traits of the Link Gun at a later time. For now, this works.

Also notice that the default value for Damage has changed from the Fire class, but the AmmoClass hasn't – that's because both the Fire and AltFire use the same ammo – just like the Rocket Launcher or Minigun.

The Next Step

Continue to Part 7, TheHealer/TUTHealerBeamEffect?

The complete tutorial map:

  1. /TUTHealer – Our main weapon class.
    1. Filename: {Base Directory}/TheHealer/classes/TUTHealer.uc
  2. /TUTHealerPickup – Our weapon's pickup class, what you see on the ground.
    1. Filename: {Base Directory}/TheHealer/classes/TUTHealerPickup.uc
  3. /TUTHealerAmmo – Our weapon's ammo class.
    1. Filename: {Base Directory}/TheHealer/classes/TUTHealerAmmo.uc
  4. /TUTHealerAmmoPickup – The ammo's pickup class, what you see on the ground.
    1. Filename: {Base Directory}/TheHealer/classes/TUTHealerAmmoPickup.uc
  5. /TUTHealerFire – Our weapon's primary fire class.
    1. Filename: {Base Directory}/TheHealer/classes/TUTHealerFire.uc
  6. /TUTHealerAltFire – Our weapon's alternate (secondary) fire class.
    1. Filename: {Base Directory}/TheHealer/classes/TUTHealerAltFire.uc
  7. /TUTHealerBeamEffect? – The Healer's Beam Effect
    1. Filename: {Base Directory}/TheHealer/classes/TUTHealerBeamEffect.uc
  8. /TUTHealerAttachment? – Our weapon's attachment class.
    1. Filename: {Base Directory}/TheHealer/classes/TUTHealerAttachment.uc
  9. [/TUT The End]? – Putting it all together.

Discussion

The Unreal Engine Documentation Site

Wiki Community

Topic Categories

Recent Changes

Offline Wiki

Unreal Engine

Console Commands

Terminology

FAQs

Help Desk

Mapping Topics

Mapping Lessons

UnrealEd Interface

UnrealScript Topics

UnrealScript Lessons

Making Mods

Class Tree

Modeling Topics

Chongqing Page

Log In