//============================================================================= // KFExplosion_HRG_Vampire_BloodBall //============================================================================= // Used by projectiles and kismet to spawn an explosion //============================================================================= // Killing Floor 2 // Copyright (C) 2020 Tripwire Interactive LLC //============================================================================= class KFExplosion_HRG_Vampire_BloodBall extends KFExplosionActorLingering; var() class HealingDamageType; var() float HealingAmount; var AkEvent SmokeLoopStartEvent; var AkEvent SmokeLoopStopEvent; var KFPerk CachedInstigatorPerk; var float ChargePercentage; var float DamageByChargePercentage; var float fMinAmmoutHealing; var float fMaxAmmoutHealing; var float fAltMinAmmoutHealing; var float fAltMaxAmmoutHealing; var bool bHealsInstigator; var bool bHealsDifferentAmmoutToInstigator; var float ImpactDecalSizeMax; var float ImpactDecalSizeMin; simulated function SpawnExplosionParticleSystem(ParticleSystem Template) { local ParticleSystemComponent PSC; local vector vec; // If the template is none, grab the default if( !ExplosionTemplate.bAllowPerMaterialFX && Template == none ) { Template = KFGameExplosion(ExplosionTemplate).ExplosionEffects.DefaultImpactEffect.ParticleTemplate; } // Use custom pool PSC = WorldInfo.MyEmitterPool.SpawnEmitter(Template, Location, rotator(ExplosionTemplate.HitNormal), None); //ChargePercentage vec.X = ChargePercentage; vec.Y = ChargePercentage; vec.Z = ChargePercentage; PSC.SetVectorParameter( name("BlobCharge"), vec); PSC.SetFloatParameter( name("MineFxControlParam"), ChargePercentage); } /* * @param Direction For bDirectionalExplosion=true explosions, this is the forward direction of the blast. * Overridden to add the ability to spawn fragments from the explosion **/ simulated function Explode(GameExplosion NewExplosionTemplate, optional vector Direction) { local KFPawn KFP; super.Explode(NewExplosionTemplate, Direction); if( Instigator != none ) { KFP = KFPawn(Instigator); if( KFP != none ) { CachedInstigatorPerk = KFP.GetPerk(); } } if (Role == Role_Authority) { //DelayedExplosionDamage(); SetTimer(Interval, false, nameof(DelayedExplosionDamage), self); } } /** * Deal damage or heal players */ protected simulated function AffectsPawn(Pawn Victim, float DamageScale) { local KFPawn_Human HumanVictim; local KFPawn_Monster MonsterVictim; local KFProj_MedicGrenade OwnerProjectile; local bool bCanRepairArmor; local Box BBox; local vector BBoxCenter; local Actor HitActor; local bool bDamageBlocked; local float AmountToHeal; if( Victim != none && Victim.IsAliveAndWell() ) { MonsterVictim = KFPawn_Monster(Victim); if( MonsterVictim != none ) { if( bWasFadedOut|| bDeleteMe || bPendingDelete ) { return; } Victim.GetComponentsBoundingBox(BBox); BBoxCenter = (BBox.Min + BBox.Max) * 0.5f; HitActor = TraceExplosive(BBoxCenter, Location + vect(0, 0, 20)); bDamageBlocked = (HitActor != None && HitActor != Victim); if(bDamageBlocked && HitActor.IsA('KFDoorActor')) { bDamageBlocked = false; } if( !bDamageBlocked ) { Victim.TakeRadiusDamage(InstigatorController, ExplosionTemplate.Damage * DamageScale, ExplosionTemplate.DamageRadius, ExplosionTemplate.MyDamageType, ExplosionTemplate.MomentumTransferScale, Location, bDoFullDamage, (Owner != None) ? Owner : self, ExplosionTemplate.DamageFalloffExponent); } } else { Victim.GetComponentsBoundingBox(BBox); BBoxCenter = (BBox.Min + BBox.Max) * 0.5f; HitActor = TraceExplosive(BBoxCenter, Location + vect(0, 0, 20)); bDamageBlocked = (HitActor != None && HitActor != Victim); if(bDamageBlocked && HitActor.IsA('KFDoorActor')) { bDamageBlocked = false; } if(!bDamageBlocked) { if(!bHealsInstigator && Victim == Instigator) { return; } HumanVictim = KFPawn_Human(Victim); if( HumanVictim != none && HumanVictim.GetExposureTo(Location) > 0 ) { OwnerProjectile = KFProj_MedicGrenade(Owner); if( OwnerProjectile != none ) { bCanRepairArmor = OwnerProjectile.HealedPawns.Find( HumanVictim ) == INDEX_NONE; } if(bHealsDifferentAmmoutToInstigator && bHealsInstigator && Victim == Instigator) { AmountToHeal = FMax(fAltMinAmmoutHealing, Lerp(fAltMinAmmoutHealing, fAltMaxAmmoutHealing, DamageByChargePercentage)); HumanVictim.HealDamage( AmountToHeal, InstigatorController, HealingDamageType, bCanRepairArmor); } else { AmountToHeal = FMax(fMinAmmoutHealing, Lerp(fMinAmmoutHealing, fMaxAmmoutHealing, DamageByChargePercentage)); HumanVictim.HealDamage( AmountToHeal, InstigatorController, HealingDamageType, bCanRepairArmor); } if( bCanRepairArmor ) { OwnerProjectile.HealedPawns.AddItem( HumanVictim ); } } } } } } simulated protected function UpdateExplosionTemplateWithPerMaterialFX(PhysicalMaterial PhysMaterial) { super.UpdateExplosionTemplateWithPerMaterialFX(PhysMaterial); // Set a default impact effect if there isn't a physical material if( PhysMaterial == none ) { MyImpactEffect = KFGameExplosion(ExplosionTemplate).ExplosionEffects.DefaultImpactEffect; } else if( WorldInfo.MyImpactEffectManager != none ) // none on dedicated server { `ImpactEffectManager.GetImpactEffect(PhysMaterial, MyImpactEffect,KFGameExplosion(ExplosionTemplate).ExplosionEffects); } if( MyImpactEffect.ParticleTemplate != none ) { ExplosionTemplate.ExplosionSound = MyImpactEffect.Sound; } } //Copied from KFExplosionActor -> SpawnExplosionDecal //Added support to variable DecalSize depending on blood ball charge. simulated function SpawnExplosionDecal() { local MaterialInterface MI; local MaterialInstanceTimeVarying MITV_Decal; local int DecalMaterialsLength; local float DecalSize, DecalThickness; local KFGameExplosion KFExplosionTemplate; if( WorldInfo.bDropDetail ) { return; } // If the template is none, grab the default if( !ExplosionTemplate.bAllowPerMaterialFX ) { KFExplosionTemplate = KFGameExplosion(ExplosionTemplate); if( KFExplosionTemplate == none || KFExplosionTemplate.ExplosionEffects == none ) { return; } MyImpactEffect = KFExplosionTemplate.ExplosionEffects.DefaultImpactEffect; } // if we have a decal to spawn on impact DecalMaterialsLength = MyImpactEffect.DecalMaterials.length; if( DecalMaterialsLength > 0 ) { MI = MyImpactEffect.DecalMaterials[Rand(DecalMaterialsLength)]; if( MI != None ) { DecalSize = Lerp(ImpactDecalSizeMin, ImpactDecalSizeMax, ChargePercentage); //Always extend decal thickness for explosions DecalThickness = DecalSize * 2.f; if( MaterialInstanceTimeVarying(MI) != none ) { MITV_Decal = new(self) class'MaterialInstanceTimeVarying'; MITV_Decal.SetParent( MI ); WorldInfo.ExplosionDecalManager.SpawnDecal(MITV_Decal, ExplosionTemplate.HitLocation, rotator(-ExplosionTemplate.HitNormal), DecalSize, DecalSize, DecalThickness, FALSE,(MyImpactEffect.bNoDecalRotation) ? 0.f : (FRand() * 360.0) ); //here we need to see if we are an MITV and then set the burn out times to occur MITV_Decal.SetScalarStartTime( MyImpactEffect.DecalDissolveParamName, MyImpactEffect.DecalDuration ); } else { WorldInfo.ExplosionDecalManager.SpawnDecal( MI, ExplosionTemplate.HitLocation, rotator(-ExplosionTemplate.HitNormal), DecalSize, DecalSize, DecalThickness, true, (MyImpactEffect.bNoDecalRotation) ? 0.f : (FRand() * 360.0),,,,,,, MyImpactEffect.DecalDuration ); } } } } DefaultProperties { HealingDamageType=class'KFDT_Healing' HealingAmount=0; fMinAmmoutHealing=12; //10; fMaxAmmoutHealing=60; //50; ImpactDecalSizeMax=425.f ImpactDecalSizeMin=178.f Interval=0 MaxTime=0.0 FadeOutTime=0.0 bExplodeMoreThanOnce=false bDoFullDamage=false bOnlyDamagePawns=true bSkipLineCheckForPawns=true LoopStartEvent=none LoopStopEvent=none //EXPERIMENTAL FEATURES FOR DESIGN bHealsInstigator = false; bHealsDifferentAmmoutToInstigator = false; fAltMinAmmoutHealing=1; fAltMaxAmmoutHealing=5; }