I love the smell of UnrealEd crashing in the morning. – tarquin

Legacy:TheHealer/TUTHealerFire

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to: navigation, search

TUTHealerFire - The Healer Part 5 of 9

In this part of the Healer tutorial, we will set up our Fire class – the primary fire mode, or the beam that will Heal your teammates. Before we get into the code, make sure that your file is set up correctly.

Filename: {Base Directory}/TheHealer/classes/TUTHealerFire.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 all 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.

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 : TUTHealerFire
// class type : Weapon Fire
// description: The Healer's Primary Fire mode
// author     : HSDanClark
//------------------------------------------------------------------------------
// TODO       :
//
//------------------------------------------------------------------------------
class TUTHealerFire extends WeaponFire;
  • Our pickup extends (or is a child of, so-to-speak) the WeaponFire class.

Exec Directives and Variable Declarations

No Exec directives.

const NoDamage = 0;
 
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 TUTHealerBeamEffect LB;
    local Pawn P;
 
    if (!bIsFiring)
    {
	bInitAimError = true;
        return;
    }
    TUTHealer = TUTHealer(Weapon);
 
 
    if (FlashEmitter != None)
    {
        // set muzzle flash color
        FlashEmitter.Skins[0] = Texture'XEffectMat.link_muz_green';
 
        // 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);
 
                if ( !Other.bWorldGeometry )
                {
                    P = Pawn(Other);
 
                    if (Other.Physics == PHYS_Falling || Other.Physics == PHYS_Flying
                        || Other.Physics == PHYS_Swimming)
                        MT = DeathMatch(Level.Game).LinkLockDownFactor * MomentumTransfer;
                    else
                        MT = 0.0;
 
                    // This is the key, this is where we give health instead of taking damage.
                    P.GiveHealth(3,199);
 
                    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'TUTHealerBeamEffect',Instigator);
 
	if (Beam != None)
	{
            Beam.LinkColor = 0;
            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;
}
function StartBerserk()
{
//    Damage = default.Damage * 1.33;
//    Damage = default.Damage * 1.33;
}
 
function StopBerserk()
{
//    Damage = default.Damage;
//    Damage = default.Damage;
}

These two functions define what happens when you use the Beserk combo. They have been commented out from the original Link Gun code, but left in as an example. Our Healer will not be affected by the Beserk adrenaline combo.

Default Properties

defaultproperties
{
    NoAmmoSound=Sound'WeaponSounds.P1Reload5'
 
    AmmoClass=class'TUTHealerAmmo'
    AmmoPerFire=1
    DamageType=class'DamTypeLinkShaft'
    Damage=-15
    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.

The Next Step

Continue to Part 6, TheHealer/TUTHealerAltFire

The complete tutorial map:

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

Discussion