My program doesn't have bugs. It just develops random features.
Legacy:TheHealer/TUTHealerFire
Contents
TUTHealerFire - The Healer Part 5 of 9[edit]
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[edit]
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[edit]
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[edit]
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[edit]
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[edit]
Continue to Part 6, TheHealer/TUTHealerAltFire
The complete tutorial map:
- Legacy:TheHealer/TUTHealer – Our main weapon class.
- Filename: {Base Directory}/TheHealer/classes/TUTHealer.uc
- Legacy:TheHealer/TUTHealerPickup – Our weapon's pickup class, what you see on the ground.
- Filename: {Base Directory}/TheHealer/classes/TUTHealerPickup.uc
- Legacy:TheHealer/TUTHealerAmmo – Our weapon's ammo class.
- Filename: {Base Directory}/TheHealer/classes/TUTHealerAmmo.uc
- Legacy:TheHealer/TUTHealerAmmoPickup – The ammo's pickup class, what you see on the ground.
- Filename: {Base Directory}/TheHealer/classes/TUTHealerAmmoPickup.uc
- Legacy:TheHealer/TUTHealerFire – Our weapon's primary fire class.
- Filename: {Base Directory}/TheHealer/classes/TUTHealerFire.uc
- Legacy:TheHealer/TUTHealerAltFire – Our weapon's alternate (secondary) fire class.
- Filename: {Base Directory}/TheHealer/classes/TUTHealerAltFire.uc
- Legacy:TheHealer/TUTHealerBeamEffect – The Healer's Beam Effect
- Filename: {Base Directory}/TheHealer/classes/TUTHealerBeamEffect.uc
- Legacy:TheHealer/TUTHealerAttachment – Our weapon's attachment class.
- Filename: {Base Directory}/TheHealer/classes/TUTHealerAttachment.uc
- Legacy:TheHealer/TUT The End – Putting it all together.