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:
- /TUTHealer – Our main weapon class.
- Filename: {Base Directory}/TheHealer/classes/TUTHealer.uc
- /TUTHealerPickup – Our weapon's pickup class, what you see on the ground.
- Filename: {Base Directory}/TheHealer/classes/TUTHealerPickup.uc
- /TUTHealerAmmo – Our weapon's ammo class.
- Filename: {Base Directory}/TheHealer/classes/TUTHealerAmmo.uc
- /TUTHealerAmmoPickup – The ammo's pickup class, what you see on the ground.
- Filename: {Base Directory}/TheHealer/classes/TUTHealerAmmoPickup.uc
- /TUTHealerFire – Our weapon's primary fire class.
- Filename: {Base Directory}/TheHealer/classes/TUTHealerFire.uc
- /TUTHealerAltFire – Our weapon's alternate (secondary) fire class.
- Filename: {Base Directory}/TheHealer/classes/TUTHealerAltFire.uc
- /TUTHealerBeamEffect? – The Healer's Beam Effect
- Filename: {Base Directory}/TheHealer/classes/TUTHealerBeamEffect.uc
- /TUTHealerAttachment? – Our weapon's attachment class.
- Filename: {Base Directory}/TheHealer/classes/TUTHealerAttachment.uc
- [/TUT The End]? – Putting it all together.