class ScrnM79MGrenadeProjectile extends ScrnM79GrenadeProjectile; #exec OBJ LOAD FILE=KF_GrenadeSnd.uax #exec OBJ LOAD FILE=Inf_WeaponsTwo.uax #exec OBJ LOAD FILE=KF_LAWSnd.uax var bool bHealing; // nade is currently healing var transient int TotalHeals; // The total number of times this nade has healed var() int MaxHeals; // The total number of times this nade will heal until its done healing var() float HealTimer; // How often to do healing var transient int HealedHP; //total amount of HP restored to players var transient array HealedPlayers; var transient KFWeapon InstigatorWeapon; var Emitter GreenCloud; var class GreenCloudClass; var Sound HealingSound; var Rotator IntitialRotationAdjustment; // used to rotate nade in a proper direction var() float DampenFactor, DampenFactorParallel; replication { reliable if ( Role==ROLE_Authority && (bNetInitial || bNetDirty) ) bHealing; } simulated function PostNetReceive() { if (Role == ROLE_Authority) return; // just to be sure // sync states with server based on flags if( bHidden ) { if ( !bDisintegrated ) { GoToState('Disintegrating'); } } else if ( bHealing ) { if ( !bHasExploded ) { GoToState('Healing'); } } else if ( bDud ) { if ( !bOutOfPropellant ) { GoToState('Dropping'); } } } //overrided to disable smoke trail simulated function PostBeginPlay() { super.PostBeginPlay(); if ( SmokeTrail != none ) SmokeTrail.SetRelativeRotation(rot(32768,0,0)); if ( Instigator != none) InstigatorWeapon = KFWeapon(Instigator.Weapon); } simulated function PostNetBeginPlay() { if ( Role < ROLE_Authority ) { PostNetReceive(); } } function Timer() { } simulated function Tick( float DeltaTime ) { SetRotation(IntitialRotationAdjustment + Rotator(Normal(Velocity))); if ( bHealing && !bHasExploded && Role < ROLE_Authority ) { GoToState('Healing'); } } simulated function Disintegrate(vector HitLocation, vector HitNormal) { if ( !bDisintegrated ) GoToState('Disintegrating'); } simulated function Explode(vector HitLocation, vector HitNormal) { if ( !bHasExploded ) GotoState('Healing'); } function BlowUp(vector HitLocation) { MakeNoise(1.0); } simulated function Destroyed() { StopFX(); Super(Projectile).Destroyed(); } function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, optional int HitIndex) { if( damageType == class'SirenScreamDamage' ) { Disintegrate(HitLocation, vect(0,0,1)); } } simulated function ProcessTouch( actor Other, vector HitLocation ) { // Don't let it hit this player, or blow up on another player if ( Other == none || Other == Instigator || Other.Base == Instigator ) return; // Don't collide with bullet whip attachments if( KFBulletWhipAttachment(Other) != none ) return; Other.TakeDamage( ImpactDamage, Instigator, HitLocation, Normal(Velocity), ImpactDamageType ); // more realistic interactions with karma objects. if (Other.IsA('NetKActor')) { KAddImpulse(Velocity,HitLocation); return; } Speed = 0; Velocity = vect(0,0,0); SetPhysics(PHYS_Falling); GotoState('Dropping'); } simulated function HitWall( vector HitNormal, actor Wall ) { local Vector VNorm; if ( ROBulletWhipAttachment(Wall) != none ) return; // don't collide with this shit, cuz it is on server side only and screws clients // Reflect off Wall w/damping VNorm = (Velocity dot HitNormal) * HitNormal; Velocity = -VNorm * DampenFactor + (Velocity - VNorm) * DampenFactorParallel; Speed = VSize(Velocity); ImpactDamage *= DampenFactor; SetPhysics(PHYS_Falling); GotoState('FallingDown'); } simulated function Landed( vector HitNormal ) { GotoState('Healing'); } function HealRadius(float HealAmount, float HealRadius, vector HealLocation) { local KFHumanPawn Victim; local int i; // Healing local KFPlayerReplicationInfo KFPRI; local KFSteamStatsAndAchievements Stats; local int MedicReward, TotalEarnedDosh; local float HealSum; // for modifying based on perks local float HealPotency; if ( bHurtEntry ) return; bHurtEntry = true; HealPotency = 1.0; // raise it half a meter to be sure it doesn't stuck inside a floor like bugged pipes HealLocation.Z = HealLocation.Z + 25; if ( Instigator != none ) KFPRI = KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo); if ( KFPRI != none ) { Stats = KFSteamStatsAndAchievements(KFPRI.SteamStatsAndAchievements); if ( KFPRI.ClientVeteranSkill != none ) HealPotency = KFPRI.ClientVeteranSkill.Static.GetHealPotency(KFPRI); } foreach CollidingActors(class'KFHumanPawn', Victim, HealRadius, HealLocation) { if( Victim.Health <= 0 || Victim.Health >= Victim.HealthMax ) continue; MedicReward = HealAmount * HealPotency; HealSum = MedicReward; if ( (Victim.Health + Victim.healthToGive + MedicReward) > Victim.HealthMax ) MedicReward = max(0, Victim.HealthMax - (Victim.Health + Victim.healthToGive)); //used to set different health restore rate if ( ScrnHumanPawn(Victim) != none ) ScrnHumanPawn(Victim).TakeHealing(ScrnHumanPawn(Instigator), HealSum, HealPotency, InstigatorWeapon); else Victim.GiveHealth(HealSum, Victim.HealthMax); // calculate total amount of health and unique player count HealedHP += MedicReward; i = 0; while ( i < HealedPlayers.Length && HealedPlayers[i] != Victim ) { i++; } if ( i == HealedPlayers.Length ) { HealedPlayers[i] = Victim; } if ( KFPRI != None ) { if ( MedicReward > 0 && Stats != none ) { Stats.AddDamageHealed(MedicReward); } // Give the medic reward money as a percentage of how much of the person's health they healed MedicReward = int((FMin(float(MedicReward),Victim.HealthMax)/Victim.HealthMax) * 60); if ( class'ScrnBalance'.default.Mut.bMedicRewardFromTeam && Victim.PlayerReplicationInfo != none && Victim.PlayerReplicationInfo.Team != none ) { // give money from team wallet if ( Victim.PlayerReplicationInfo.Team.Score >= MedicReward ) { Victim.PlayerReplicationInfo.Team.Score -= MedicReward; KFPRI.Score += MedicReward; TotalEarnedDosh += MedicReward; } } else { KFPRI.Score += MedicReward; TotalEarnedDosh += MedicReward; } } } if ( TotalEarnedDosh > 0 && KFHumanPawn(Instigator) != none ) { KFHumanPawn(Instigator).AlphaAmount = 255; } bHurtEntry = false; } function SuccessfulHealAchievements() { if ( HealedPlayers.Length >= 6 && Instigator != none && Instigator.PlayerReplicationInfo != none && SRStatsBase(Instigator.PlayerReplicationInfo.SteamStatsAndAchievements) != none ) { class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID( SRStatsBase(Instigator.PlayerReplicationInfo.SteamStatsAndAchievements).Rep, 'ExplosionLove', 1); } } function SuccessfulHealMessage() { if ( ScrnM79M(InstigatorWeapon) != none ) ScrnM79M(InstigatorWeapon).ClientSuccessfulHeal(HealedPlayers.length, HealedHP); } simulated function StopFX() { AmbientSound = none; if ( SmokeTrail != None ) SmokeTrail.Kill(); if ( GreenCloud != none ) GreenCloud.Kill(); } auto simulated state Flying { simulated function EndState() { bOutOfPropellant = true; bDud = true; } } simulated state FallingDown { simulated function BeginState() { SetPhysics(PHYS_Falling); } } simulated state Dropping extends FallingDown { ignores HitWall, ProcessTouch, Tick; } simulated state Healing { ignores HitWall, ProcessTouch, Landed, Explode; simulated function BeginState() { local rotator R; // turn on actor property replication to set the correct position of the healing grenade // actually it is ugly - nade teleports half a meter away from target due to lag. Rolling back... // bSkipActorPropertyReplication = false; // bUpdateSimulatedPosition = true; bHealing = true; bHasExploded = true; Speed = 0; Velocity = Vect(0,0,0); SetPhysics(PHYS_None); // land on the ground, keep yaw R = IntitialRotationAdjustment; R.Yaw = Rotation.Yaw; SetRotation(R); BlowUp(Location); PlaySound(ExplosionSound,,TransientSoundVolume); AmbientSound = HealingSound; if ( Level.NetMode != NM_DedicatedServer ) { GreenCloud = Spawn(GreenCloudClass,self,, Location, rotator(vect(0,0,1))); GreenCloud.SetBase(self); Spawn(ExplosionDecal,self,,Location, rotator(vect(0,0,-1))); } if ( SmokeTrail != none ) { SmokeTrail.SetRelativeRotation(Rotation * -1.0); } LifeSpan = MaxHeals * HealTimer + 2; SetTimer(HealTimer, true); Timer(); if( Role == ROLE_Authority ) { Disable('Tick'); NetUpdateTime = Level.TimeSeconds - 1; // update now } } simulated function EndState() { bHealing = false; StopFX(); SetTimer(0, false); if ( HealedHP > 0 ) { SuccessfulHealAchievements(); SuccessfulHealMessage(); } } simulated function Timer() { HealRadius(Damage, DamageRadius, Location); if (--MaxHeals <= 0) { GoToState('Disintegrating'); } } simulated function Tick( float DeltaTime ) { if ( bHidden ) { GoToState('Disintegrating'); } } } simulated state Disintegrating { ignores HitWall, ProcessTouch, Landed, Explode, Disintegrate, TakeDamage; simulated function BeginState() { bHidden = true; bDisintegrated = true; PlaySound(DisintegrateSound,,2.0); if ( EffectIsRelevant(Location,false) ) { Spawn(Class'KFMod.SirenNadeDeflect',,, Location, rotator(vect(0,0,1))); } StopFX(); if( Role == ROLE_Authority ) { // replicate disintegration to clients SetTimer(0.2, false); NetUpdateTime = Level.TimeSeconds - 1; } else { Destroy(); } } function Timer() { Destroy(); } } defaultproperties { MaxHeals=20 HealTimer=0.500000 GreenCloudClass=Class'ScrnBalanceSrv.ScrnNadeHealing' HealingSound=Sound'Inf_WeaponsTwo.smokegrenade.smoke_loop' IntitialRotationAdjustment=(Pitch=-10010,Roll=16384) DampenFactor=0.250000 DampenFactorParallel=0.250000 SmokeTrailClass=Class'ScrnBalanceSrv.ScrnMedicNadeTrail' ArmDistSquared=0.000000 ImpactDamageType=Class'ScrnBalanceSrv.ScrnDamTypeMedicGrenadeImpact' StraightFlightTime=1.000000 StaticMeshRef="KF_pickups5_Trip.nades.MedicNade_Pickup" ExplosionSoundRef="KF_GrenadeSnd.NadeBase.MedicNade_Explode" Speed=2000.000000 MaxSpeed=2500.000000 Damage=4.000000 DamageRadius=225.000000 MyDamageType=Class'KFMod.DamTypeMedicNade' ExplosionDecal=Class'ScrnBalanceSrv.ScrnMedicNadeDecal' bAcceptsProjectors=True bNetTemporary=False bSkipActorPropertyReplication=True bUpdateSimulatedPosition=False LifeSpan=15.000000 DrawScale=2.000000 PrePivot=(Z=-1.500000) SoundVolume=150 SoundRadius=100.000000 TransientSoundRadius=200.000000 CollisionRadius=10.000000 CollisionHeight=1.000000 }