//============================================================================= // SealSquealProjectile //============================================================================= // Projectile class for the seal squeal harpoon bomb launcher //============================================================================= // Killing Floor Source // Copyright (C) 2013 Tripwire Interactive LLC // - John "Ramm-Jaeger" Gibson //============================================================================= class SealSquealProjectile extends ROBallisticProjectile; #exec OBJ LOAD FILE=WeaponSounds.uax #exec OBJ LOAD FILE=ProjectileSounds.uax // camera shakes // var() vector ShakeRotMag; // how far to rot view var() vector ShakeRotRate; // how fast to rot view var() float ShakeRotTime; // how much time to rot the instigator's view var() vector ShakeOffsetMag; // max view offset vertically var() vector ShakeOffsetRate; // how fast to offset view vertically var() float ShakeOffsetTime; // how much time to offset view var() vector RotMag; // how far to rot view var() vector RotRate; // how fast to rot view var() float RotTime; // how much time to rot the instigator's view var() vector OffsetMag; // max view offset vertically var() vector OffsetRate; // how fast to offset view vertically var() float OffsetTime; // how much time to offset view var xEmitter Trail; var vector Dir; var() sound ExplosionSound; // The sound of the rocket exploding var bool bDisintegrated; // This nade has been disintegrated by a siren scream. var() sound DisintegrateSound; // The sound of this projectile disintegrating var bool bStuck; // This rocket has stuck in something var class ImpactDamageType; // Damagetype of this rocket hitting something, but not exploding var int ImpactDamage; // How much damage to do if this rocket impacts something without exploding var bool bHasExploded; var string StaticMeshRef; var string ExplosionSoundRef; var string DisintegrateSoundRef; var class SmokeTrailEmitterClass;// Emitter class for the smoke trail/fuze sparks var Emitter SmokeTrail; var class ExplosionEmitterClass;// Emitter class for the explosion var sound ImpactPawnSound; // Sound made when projectile hits a pawn var float ExplodeTimer; // How long this bomb will wait to explode var() string AmbientSoundRef; var string ImpactSoundRef; var string ImpactPawnSoundRef; var bool bAttachedToHead; // True if this projectile is stuck in a pawn's head replication { reliable if(Role == ROLE_Authority) bStuck; } static function PreloadAssets() { default.AmbientSound = sound(DynamicLoadObject(default.AmbientSoundRef, class'Sound', true)); default.ExplosionSound = sound(DynamicLoadObject(default.ExplosionSoundRef, class'Sound', true)); default.DisintegrateSound = sound(DynamicLoadObject(default.DisintegrateSoundRef, class'Sound', true)); default.ImpactSound = sound(DynamicLoadObject(default.ImpactSoundRef, class'Sound', true)); default.ImpactPawnSound = sound(DynamicLoadObject(default.ImpactPawnSoundRef, class'Sound', true)); UpdateDefaultStaticMesh(StaticMesh(DynamicLoadObject(default.StaticMeshRef, class'StaticMesh', true))); } static function bool UnloadAssets() { default.ExplosionSound = none; default.DisintegrateSound = none; default.ImpactSound = none; default.ImpactPawnSound = none; default.AmbientSound = none; UpdateDefaultStaticMesh(none); return true; } simulated function PostNetReceive() { if( bHidden && !bDisintegrated ) { Disintegrate(Location, vect(0,0,1)); } } function Timer() { if( !bHidden ) { Explode(Location, vect(0,0,1)); } else { Destroy(); } } function ShakeView() { local Controller C; local PlayerController PC; local float Dist, Scale; for ( C=Level.ControllerList; C!=None; C=C.NextController ) { PC = PlayerController(C); if ( PC != None && PC.ViewTarget != None ) { Dist = VSize(Location - PC.ViewTarget.Location); if ( Dist < DamageRadius * 2.0) { if (Dist < DamageRadius) Scale = 1.0; else Scale = (DamageRadius*2.0 - Dist) / (DamageRadius); C.ShakeView(ShakeRotMag*Scale, ShakeRotRate, ShakeRotTime, ShakeOffsetMag*Scale, ShakeOffsetRate, ShakeOffsetTime); } } } } simulated function HitWall(vector HitNormal, actor Wall) { if( Instigator != none ) { OrigLoc = Instigator.Location; } if(Level.NetMode != NM_DedicatedServer) { Spawn(class'ROBulletHitEffect',,, Location, rotator(-HitNormal)); } Stick(Wall, Location); } // Make this blow up when the pawn its attached to dies function HandleBasePawnDestroyed() { if( !bHasExploded ) { Explode(Location, vect(0,0,1)); } } simulated function Explode(vector HitLocation, vector HitNormal) { local Controller C; local PlayerController LocalPlayer; local KFMonster KFM; // Don't explode twice if(bHasExploded) { return; } bHasExploded = True; PlaySound(ExplosionSound,,2.0); if ( EffectIsRelevant(Location,false) ) { Spawn(ExplosionEmitterClass,,,HitLocation + HitNormal*20,rotator(HitNormal)); Spawn(ExplosionDecal,self,,HitLocation, rotator(-HitNormal)); } KFM = KFMonster(Base); if( KFM != none && Role == ROLE_Authority ) { KFM.NumHarpoonsAttached--; if( KFM.NumHarpoonsAttached <= 0 ) { KFM.bHarpoonStunned = false; } } BlowUp(HitLocation); Destroy(); // Shake nearby players screens LocalPlayer = Level.GetLocalPlayerController(); if ( (LocalPlayer != None) && (VSize(Location - LocalPlayer.ViewTarget.Location) < DamageRadius) ) LocalPlayer.ShakeView(RotMag, RotRate, RotTime, OffsetMag, OffsetRate, OffsetTime); for ( C=Level.ControllerList; C!=None; C=C.NextController ) if ( (PlayerController(C) != None) && (C != LocalPlayer) && (VSize(Location - PlayerController(C).ViewTarget.Location) < DamageRadius) ) C.ShakeView(RotMag, RotRate, RotTime, OffsetMag, OffsetRate, OffsetTime); } // Make the projectile distintegrate, instead of explode simulated function Disintegrate(vector HitLocation, vector HitNormal) { local KFMonster KFM; bDisintegrated = true; bHidden = true; if( Role == ROLE_Authority ) { SetTimer(0.1, false); NetUpdateTime = Level.TimeSeconds - 1; KFM = KFMonster(Base); if( KFM != none && Role == ROLE_Authority ) { KFM.NumHarpoonsAttached--; if( KFM.NumHarpoonsAttached <= 0 ) { KFM.bHarpoonStunned = false; } } } PlaySound(DisintegrateSound,,2.0); if ( EffectIsRelevant(Location,false) ) { Spawn(Class'KFMod.SirenNadeDeflect',,, HitLocation, rotator(vect(0,0,1))); } } simulated function PostBeginPlay() { BCInverse = 1 / BallisticCoefficient; if( Level.NetMode!=NM_DedicatedServer && (Level.NetMode!=NM_Client || Physics==PHYS_Projectile) ) { if ( !PhysicsVolume.bWaterVolume ) { Trail = Spawn(class'NailGunTracer',self); Trail.Lifespan = Lifespan; SmokeTrail = Spawn(SmokeTrailEmitterClass,self); } } OrigLoc = Location; if( !bStuck ) { Dir = vector(Rotation); Velocity = speed * Dir; } if (PhysicsVolume.bWaterVolume) { Velocity=0.6*Velocity; } super(Projectile).PostBeginPlay(); SetTimer(ExplodeTimer, false); } 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)); } else { Explode(HitLocation, vect(0,0,1)); } } simulated function Destroyed() { if (Trail !=None) { Trail.mRegen = False; } if ( SmokeTrail != none ) { SmokeTrail.Kill(); SmokeTrail.SetPhysics(PHYS_None); } if( !bHasExploded && !bHidden ) Explode(Location,vect(0,0,1)); if( bHidden && !bDisintegrated ) Disintegrate(Location,vect(0,0,1)); Super.Destroyed(); } // Stick this explosive to the wall or zed it hit simulated function Stick(actor HitActor, vector HitLocation) { local name NearestBone; local Pawn HitPawn; local float Dist; local vector HitDirection; local KFMonster KFM; HitDirection = Normal(Velocity); if( Velocity == vect(0,0,0) ) { HitDirection = Vector(Rotation); } SetRotation(Rotator(HitDirection)); bStuck=true; Velocity = vect(0,0,0); SetPhysics(PHYS_None); if( HitActor.IsA('ExtendedZCollision') && HitActor.Base != none && Pawn(HitActor.Base) != none ) { HitPawn = Pawn(HitActor.Base); } else { HitPawn = Pawn(HitActor); } if (HitPawn != none) { NearestBone = HitPawn.GetClosestBone(HitLocation, HitDirection, Dist); HitPawn.AttachToBone(self,NearestBone); // Flag if we are stick in a pawn's head if( NearestBone == HitPawn.HeadBone ) { bAttachedToHead = true; } KFM = KFMonster(HitPawn); if( KFM != none && Role == ROLE_Authority ) { //KFMonster(HitPawn).SetZapped(99999999,Instigator,true); if( KFM.bHarpoonToBodyStuns || KFM.bHarpoonToHeadStuns && bAttachedToHead ) { KFM.bHarpoonStunned = true; } KFM.NumHarpoonsAttached++; } // TODO: Improve the math here so its angle more closely matches the angle it stuck in at SetRelativeRotation(Rotator(HitDirection >> HitPawn.GetBoneRotation( NearestBone, 0 ))); } else { SetBase(HitActor); } if( Trail!=None ) { Trail.mRegen = False; } if ( Level.NetMode != NM_DedicatedServer ) { if( HitPawn != none ) { PlaySound(ImpactPawnSound, SLOT_Misc ); } else { PlaySound(ImpactSound, SLOT_Misc ); } } // Make light radius smaller once it impacts LightRadius=2.0; } /* HurtRadius() Hurt locally authoritative actors within the radius. */ simulated function HurtRadius( float DamageAmount, float DamageRadius, class DamageType, float Momentum, vector HitLocation ) { local actor Victims; local float damageScale, dist; local vector dirs; local int NumKilled; local KFMonster KFMonsterVictim; local Pawn P; local KFPawn KFP; local array CheckedPawns; local int i; local bool bAlreadyChecked; if ( bHurtEntry ) return; bHurtEntry = true; foreach CollidingActors (class 'Actor', Victims, DamageRadius, HitLocation) { // don't let blast damage affect fluid - VisibleCollisingActors doesn't really work for them - jag if( (Victims != self) && (Hurtwall != Victims) && (Victims.Role == ROLE_Authority) && !Victims.IsA('FluidSurfaceInfo') && ExtendedZCollision(Victims)==None ) { dirs = Victims.Location - HitLocation; dist = FMax(1,VSize(dirs)); dirs = dirs/dist; damageScale = 1 - FMax(0,(dist - Victims.CollisionRadius)/DamageRadius); if ( Instigator == None || Instigator.Controller == None ) Victims.SetDelayedDamageInstigatorController( InstigatorController ); if ( Victims == LastTouched ) LastTouched = None; P = Pawn(Victims); if( P != none ) { for (i = 0; i < CheckedPawns.Length; i++) { if (CheckedPawns[i] == P) { bAlreadyChecked = true; break; } } if( bAlreadyChecked ) { bAlreadyChecked = false; P = none; continue; } KFMonsterVictim = KFMonster(Victims); if( KFMonsterVictim != none && KFMonsterVictim.Health <= 0 ) { KFMonsterVictim = none; } KFP = KFPawn(Victims); if( KFMonsterVictim != none ) { // Double damage if attached to head if( bAttachedToHead && KFMonsterVictim == Base ) { damageScale *= 4.0; } else { damageScale *= KFMonsterVictim.GetExposureTo(HitLocation); } } else if( KFP != none ) { // Double damage if attached to head if( bAttachedToHead && KFP == Base ) { damageScale *= 2.0; } else { damageScale *= KFP.GetExposureTo(HitLocation); } } CheckedPawns[CheckedPawns.Length] = P; if ( damageScale <= 0) { P = none; continue; } else { //Victims = P; P = none; } } Victims.TakeDamage ( damageScale * DamageAmount, Instigator, Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dirs, (damageScale * Momentum * dirs), DamageType ); if (Vehicle(Victims) != None && Vehicle(Victims).Health > 0) Vehicle(Victims).DriverRadiusDamage(DamageAmount, DamageRadius, InstigatorController, DamageType, Momentum, HitLocation); if( Role == ROLE_Authority && KFMonsterVictim != none && KFMonsterVictim.Health <= 0 ) { NumKilled++; } } } if ( (LastTouched != None) && (LastTouched != self) && (LastTouched.Role == ROLE_Authority) && !LastTouched.IsA('FluidSurfaceInfo') ) { Victims = LastTouched; LastTouched = None; dirs = Victims.Location - HitLocation; dist = FMax(1,VSize(dirs)); dirs = dirs/dist; damageScale = FMax(Victims.CollisionRadius/(Victims.CollisionRadius + Victims.CollisionHeight),1 - FMax(0,(dist - Victims.CollisionRadius)/DamageRadius)); if ( Instigator == None || Instigator.Controller == None ) Victims.SetDelayedDamageInstigatorController(InstigatorController); Victims.TakeDamage ( damageScale * DamageAmount, Instigator, Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dirs, (damageScale * Momentum * dirs), DamageType ); if (Vehicle(Victims) != None && Vehicle(Victims).Health > 0) Vehicle(Victims).DriverRadiusDamage(DamageAmount, DamageRadius, InstigatorController, DamageType, Momentum, HitLocation); } if( Role == ROLE_Authority ) { if( NumKilled >= 4 ) { KFGameType(Level.Game).DramaticEvent(0.05); } else if( NumKilled >= 2 ) { KFGameType(Level.Game).DramaticEvent(0.03); } } bHurtEntry = false; } 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 || bStuck) return; // Don't collide with bullet whip attachments if( KFBulletWhipAttachment(Other) != none ) { return; } // Don't allow hits on poeple on the same team if( KFHumanPawn(Other) != none && Instigator != none && KFHumanPawn(Other).PlayerReplicationInfo.Team.TeamIndex == Instigator.PlayerReplicationInfo.Team.TeamIndex ) { return; } // Use the instigator's location if it exists. This fixes issues with // the original location of the projectile being really far away from // the real Origloc due to it taking a couple of milliseconds to // replicate the location to the client and the first replicated location has // already moved quite a bit. if( Instigator != none ) { OrigLoc = Instigator.Location; } Stick(Other, HitLocation); } simulated function Tick( float DeltaTime ) { if( !bStuck ) { SetRotation(Rotator(Normal(Velocity))); } super.Tick(DeltaTime); } simulated function Landed( vector HitNormal ) { SetPhysics(PHYS_None); } defaultproperties { ShakeRotMag=(X=600.000000,Y=600.000000,Z=600.000000) ShakeRotRate=(X=12500.000000,Y=12500.000000,Z=12500.000000) ShakeRotTime=6.000000 ShakeOffsetMag=(X=5.000000,Y=10.000000,Z=5.000000) ShakeOffsetRate=(X=300.000000,Y=300.000000,Z=300.000000) ShakeOffsetTime=3.500000 RotMag=(X=700.000000,Y=700.000000,Z=700.000000) RotRate=(X=12500.000000,Y=12500.000000,Z=12500.000000) RotTime=6.000000 OffsetMag=(X=5.000000,Y=10.000000,Z=7.000000) OffsetRate=(X=300.000000,Y=300.000000,Z=300.000000) OffsetTime=3.500000 Speed=3000.000000 MaxSpeed=3000.000000 Damage=350 DamageRadius=400 MomentumTransfer=75000.000000 MyDamageType=Class'KFMod.DamTypeSealSquealExplosion' ImpactSoundRef="KF_FY_SealSquealSND.WEP_Harpoon_Hit_Wall" ImpactPawnSoundRef="KF_FY_SealSquealSND.WEP_Harpoon_Hit_Flesh" ImpactDamageType=Class'KFMod.DamTypeRocketImpact' ImpactDamage=200 ExplosionDecal=Class'KFMod.KFScorchMark' AmbientSoundRef="KF_IJC_HalloweenSnd.KF_FlarePistol_Projectile_Loop" LightType=LT_Steady LightBrightness=128.0 LightRadius=8.000000 LightHue=21 LightSaturation=64 LightCone=16 bDynamicLight=True DrawType=DT_StaticMesh StaticMeshRef="KF_IJC_Halloween_Weps2.Harpoon_Projectile" LifeSpan=10.000000 DrawScale=1.000000 bUnlit=False ForceRadius=300.000000 ForceScale=10.000000 ExplosionSoundRef="KF_FY_SealSquealSND.WEP_Harpoon_Explode" bTrueBallistics=false AmbientSound=none SoundVolume=255 SoundRadius=250 AmbientVolumeScale=2.0 TransientSoundVolume=2.0 TransientSoundRadius=500 bFullVolume=false DisintegrateSoundRef="Inf_Weapons.faust_explode_distant02" bNetNotify=true bNetTemporary=false bBlockHitPointTraces=false SmokeTrailEmitterClass=Class'KFMod.SealSquealFuseEmitter' ExplosionEmitterClass=Class'KFmod.SealSquealExplosionEmitter' Physics = PHYS_Projectile ExplodeTimer=2.5 }