rott/kf_sources/ScrnBalanceSrv/Classes/ScrnNailGunProjectile.uc
2026-07-14 20:27:09 +07:00

514 lines
18 KiB
Ucode

/* WARNING!
This projectile uses KFMonster's bZedUnderControl and NumZCDHits for own purposes!
It sohuldn't be an issue because those variables seem not be used by anything
bZedUnderControl - zed has been nailed and flying nail takes zed with it (can't be nailed more in this state)
NumZCDHits - number of nails zed is pinned with. Can be nailed more. Zed will be released when NumZCDHits reaches 0.
*/
class ScrnNailGunProjectile extends ScrnCustomShotgunBullet;
var String ImpactSoundRefs[6];
var byte Bounces;
var transient bool bBounced;
var bool bFinishedPenetrating;
var KFMonster MonsterHeadAttached;
var ProjectileBodyPart Giblet;
var KFMonster NailedMonster; //monster that currently is attached to nail
var transient KFMonster OldNailedMonster; //used for replication
var vector NailHitDelta; // distance between hit's and victim's locations
var transient vector NailingLocation; // location, where zed was hit by nail
var transient float NailedFlyDistance; // how far did zed flew away?
var transient bool bStillFlying; // NailedMonster is still flying (not pinned yet)
var transient ScrnAchievements.AchStrInfo ach_Nail100m, ach_NailToWall, ach_PushShiver, ach_ProNailer; //related achievements
var Pawn PendingDeadVictim;
var vector PendingHitSpot;
var float LifeSpanAfterHitWall; // for how long keep zeds nailed to a wall?
var(Movement) float VelocityModMass;
var(Movement) float VelocityModHealth;
replication
{
reliable if ( bNetInitial && Role == ROLE_Authority )
Bounces;
reliable if ( bNetDirty && Role == ROLE_Authority )
MonsterHeadAttached, bFinishedPenetrating,
PendingHitSpot, PendingDeadVictim,
NailedMonster, NailHitDelta;
}
static function PreloadAssets()
{
default.ImpactSounds[0] = sound(DynamicLoadObject(default.ImpactSoundRefs[0], class'Sound', true));
default.ImpactSounds[1] = sound(DynamicLoadObject(default.ImpactSoundRefs[1], class'Sound', true));
default.ImpactSounds[2] = sound(DynamicLoadObject(default.ImpactSoundRefs[2], class'Sound', true));
default.ImpactSounds[3] = sound(DynamicLoadObject(default.ImpactSoundRefs[3], class'Sound', true));
default.ImpactSounds[4] = sound(DynamicLoadObject(default.ImpactSoundRefs[4], class'Sound', true));
default.ImpactSounds[5] = sound(DynamicLoadObject(default.ImpactSoundRefs[5], class'Sound', true));
super.PreloadAssets();
}
static function bool UnloadAssets()
{
default.ImpactSounds[0] = none;
default.ImpactSounds[1] = none;
default.ImpactSounds[2] = none;
default.ImpactSounds[3] = none;
default.ImpactSounds[4] = none;
default.ImpactSounds[5] = none;
return super.UnloadAssets();
}
simulated function PostBeginPlay()
{
super(Projectile).PostBeginPlay();
Velocity = Speed * Vector(Rotation); // starts off slower so combo can be done closer
//SetTimer(0.4, false); //wut? There is no Timer function defined in parent classes
if ( Level.NetMode != NM_DedicatedServer ) {
if ( !PhysicsVolume.bWaterVolume )
{
Trail = Spawn(class'NailGunTracer',self);
Trail.Lifespan = Lifespan;
}
}
}
simulated function PostNetReceive()
{
local Coords boneCoords;
super.PostNetReceive();
//log ("Nail.PostNetReceive NailedMonster="$NailedMonster @ "NailHitDelta="$NailHitDelta, 'ScrnBalance');
if( Giblet == none && MonsterHeadAttached != none )
{
boneCoords = MonsterHeadAttached.GetBoneCoords( 'head' );
Giblet = Spawn( Class'ProjectileBodyPart',,, boneCoords.Origin, Rotator(boneCoords.XAxis) );
Giblet.SetStaticMesh(MonsterHeadAttached.DetachedHeadClass.default.StaticMesh);
Giblet.SetLocation(Location);
Giblet.SetPhysics( PHYS_None );
Giblet.SetBase(self);
Giblet.Lifespan = Lifespan;
}
if ( NailedMonster != OldNailedMonster ) {
if ( NailedMonster != none ) {
Mass = NailedMonster.Mass;
Bounces=0;
NailedMonster.SetBase(self);
NailedMonster.SetPhysics(PHYS_Flying); // fly away, baby ;)
NailedMonster.bZedUnderControl = true; //indicate that nail is taking zed with it
if ( Physics != PHYS_None )
SetPhysics(PHYS_Falling);
}
else {
Mass = default.Mass;
}
if ( OldNailedMonster != none) {
if ( OldNailedMonster.Physics == PHYS_Flying )
OldNailedMonster.SetPhysics(PHYS_Walking);
}
OldNailedMonster = NailedMonster;
}
}
// Code taken from Marco's HL2 Crossbow
simulated function NailDeadBodiesToWall()
{
local vector X,HL,HN;
// Attempt to pin body onto wall
X = vector(Rotation);
if( PendingDeadVictim!=None && PendingDeadVictim.Health<=0 && PendingDeadVictim.Physics==PHYS_KarmaRagdoll )
{
if( Trace(HL,HN,Location+X*1000.f,Location-X*10.f,False)!=None )
Spawn(Class'BodyAttacher',PendingDeadVictim,,PendingHitSpot).AttachEndPoint = HL-HN*4;
}
PendingDeadVictim = none;
}
function ReleaseMonster()
{
if ( NailedMonster == none )
return;
Mass = default.Mass;
if ( --NailedMonster.NumZCDHits <= 0 ) {
NailedMonster.bZedUnderControl = false;
NailedMonster.AirSpeed = NailedMonster.default.AirSpeed * NailedMonster.GroundSpeed / NailedMonster.default.GroundSpeed;
if ( NailedMonster.Base == self )
NailedMonster.SetBase(none);
//drop zed on the ground and let him walk away
if ( NailedMonster.Physics == PHYS_Flying )
NailedMonster.SetPhysics(PHYS_Walking);
}
// achievements
//PlayerController(Instigator.Controller).ClientMessage("NailedFlyDistance = " $ sqrt(NailedFlyDistance) $ " ("$ sqrt(NailedFlyDistance) / 50.0 $"m)");
if ( NailedFlyDistance >= 25000000.0 && ach_Nail100m.AchHandler != none )
ach_Nail100m.AchHandler.ProgressAchievement(ach_Nail100m.AchIndex, 1);
if ( NailedFlyDistance >= 62500.0 && ach_PushShiver.AchHandler != none && NailedMonster.IsA('ZombieShiver') )
ach_PushShiver.AchHandler.ProgressAchievement(ach_PushShiver.AchIndex, 1);
NailedMonster = none;
NetUpdateTime = Level.TimeSeconds - 1;
}
simulated function Tick(float DeltaTime)
{
local vector X,HL,HN, ZedNewLoc;
super.Tick(DeltaTime);
if ( Level.NetMode != NM_DedicatedServer && Physics != PHYS_None )
{
SetRotation(Rotator(Normal(Velocity)));
}
if ( NailedMonster != none ) {
// take naled zed with me
ZedNewLoc = Location - NailHitDelta;
X = vector(Rotation);
if ( NailedMonster.Health > 0 ) {
NailedMonster.Velocity = Velocity;
if ( Physics != PHYS_None ) {
bStillFlying = true;
if ( NailedMonster.bZedUnderControl ) {
if ( !NailedMonster.SetLocation(ZedNewLoc) )
ReleaseMonster();
}
}
else {
if ( bStillFlying ) {
bStillFlying = false;
if ( NailedMonster.bZedUnderControl ) {
NailedMonster.SetLocation(ZedNewLoc); // set final pinned location
NailedMonster.bZedUnderControl = false; //allow other nails to pin this zed
}
// nail stopped, so trace precise location where to pin the zed
// if( Trace(HL, HN, ZedNewLoc+X*50.0, ZedNewLoc-X*50.f, true) == none ) {
// ReleaseMonster();
// return;
// }
//NailedMonster.SetLocation(HL);
}
}
if ( Role == ROLE_Authority && NailedMonster != none ) {
NailedFlyDistance = fmax(NailedFlyDistance, VSizeSquared(NailedMonster.Location - NailingLocation));
if ( VSizeSquared(ZedNewLoc - NailedMonster.Location) > 2500 ) {
ReleaseMonster(); //too far from the nail - release zed
return;
}
if ( !bStillFlying && NailedMonster != none && ach_NailToWall.AchHandler != none ) {
ach_NailToWall.AchHandler.ProgressAchievement(ach_NailToWall.AchIndex, 1);
ach_NailToWall.AchHandler = none;
}
}
}
else if ( NailedMonster.Physics==PHYS_KarmaRagdoll ) {
// attach dead body to a wall
if( Trace(HL,HN,ZedNewLoc+X*1000.0, ZedNewLoc-X*10.0, False) != none )
Spawn(Class'BodyAttacher',NailedMonster,,PendingHitSpot).AttachEndPoint = HL-HN*4;
ReleaseMonster();
}
}
}
simulated function ProcessTouch (Actor Other, vector HitLocation)
{
local vector X;
local Vector TempHitLocation, HitNormal;
local array<int> HitPoints;
local KFPlayerReplicationInfo KFPRI;
local KFPawn HitPawn;
local Pawn Victim;
local KFMonster KFM;
local bool bWasDecapitated;
local Actor TempActor;
local float PerkedDamage;
ReleaseMonster(); //release currently nailed monster, when hitting another
if ( Other == none || Other == Instigator || Other.Base == Instigator || !Other.bBlockHitPointTraces )
return;
if( bFinishedPenetrating ) {
return;
}
//Test - don't pin ExtendedZCollision
// if ( ExtendedZCollision(Other) != none )
// Victim = Pawn(Other.Owner); // ExtendedZCollision is attached to KFMonster
// else if ( Pawn(Other) != none )
Victim = Pawn(Other);
X = Vector(Rotation);
KFM = KFMonster(Victim);
KFPRI = KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo);
// damage bonus also makes it fly faster
PerkedDamage = Damage;
if ( KFPRI != none && KFPRI.ClientVeteranSkill != none )
PerkedDamage = KFPRI.ClientVeteranSkill.Static.AddDamage(KFPRI, KFM, KFPawn(Instigator), Damage, MyDamageType);
if ( KFM != none ) {
bWasDecapitated = KFM.bDecapitated;
Velocity /= 1.0 + (KFM.Mass*VelocityModMass + KFM.Health*VelocityModHealth)/PerkedDamage; //heavy and healthy pawns slow down nails more
}
if( ROBulletWhipAttachment(Other) != none ) {
// we touched player's auxilary collision cylinder, not let's trace to the player himself
// Other.Base = KFPawn
if( Other.Base == none || Other.Base.bDeleteMe )
return;
Other = Instigator.HitPointTrace(TempHitLocation, HitNormal, HitLocation + (200 * X), HitPoints, HitLocation,, 1);
if( Other == none || HitPoints.Length == 0 )
return; // bullet didn't hit a pawn
HitPawn = KFPawn(Other);
if (Role == ROLE_Authority) {
if ( HitPawn != none && !HitPawn.bDeleteMe ) {
HitPawn.ProcessLocationalDamage(Damage, Instigator, TempHitLocation, MomentumTransfer * Normal(Velocity), MyDamageType,HitPoints);
}
}
}
else {
if ( Victim != none && Victim.IsHeadShot(HitLocation, X, 1.0) ) {
// HEADSHOT
Victim.TakeDamage(Damage * HeadShotDamageMult, Instigator, HitLocation, MomentumTransfer * Normal(Velocity), MyDamageType);
if( Role == ROLE_Authority && KFM != none && !bWasDecapitated ) {
if ( bBounced && KFM.bDecapitated )
ach_ProNailer.AchHandler.ProgressAchievement(ach_ProNailer.AchIndex, 1);
if( MonsterHeadAttached == none && KFM.Health < 0 ) {
MonsterHeadAttached = KFM;
if( Level.NetMode == NM_ListenServer || Level.NetMode == NM_StandAlone )
PostNetReceive();
}
}
}
else {
// BODYSHOT
Other.TakeDamage(Damage, Instigator, HitLocation, MomentumTransfer * Normal(Velocity), MyDamageType);
}
}
//Bounces=0; // don't bounce after hit somebody
// penetration damage reduction
if ( KFPRI != none && KFPRI.ClientVeteranSkill != none )
PenDamageReduction = KFPRI.ClientVeteranSkill.static.GetShotgunPenetrationDamageMulti(KFPRI,default.PenDamageReduction);
else
PenDamageReduction = default.PenDamageReduction;
// loose penetration damage after hitting specific zeds -- PooSH
if ( KFM != none)
PenDamageReduction *= ZedPenDamageReduction(KFM);
Damage *= PenDamageReduction; // Keep going, but lose effectiveness each time.
speed = VSize(Velocity);
// if we've struck through more than the max number of foes, destroy.
if ( Damage / default.Damage < (default.PenDamageReduction ** MaxPenetrations) + 0.0001
|| Speed < (default.Speed * 0.25))
{
ReleaseMonster();
bFinishedPenetrating = true;
SetPhysics(PHYS_Falling);
Velocity = PhysicsVolume.Gravity;
Bounces=0;
}
else if ( Role == ROLE_Authority && KFM != none
&& (bWasDecapitated || !KFM.bDecapitated) ) //don't nail zed on decapitating shot
{
PendingHitSpot = HitLocation;
if ( Victim.Health <= 0) {
PendingDeadVictim = KFM;
NetUpdateTime = Level.TimeSeconds - 1;
}
else if ( !KFM.bZedUnderControl && (bWasDecapitated ||
( // KFM.CollisionRadius < 27 && (!KFM.bUseExtendedCollision || KFM.ColRadius < 27) &&
KFM.Mass < 360 && KFM.Health < PerkedDamage * 10 && ZombieSiren(KFM) == none)) )
{
// can pin only small zeds (up to Clot) or wounded medium zeds (Gorefast, Siren)
// find a point where nail should go out of body after a penetration:
// go behind the actor and trace in reverse direction
TempActor = Trace(TempHitLocation, HitNormal,
HitLocation, HitLocation + X * 2.0 * (KFM.CollisionRadius + KFM.CollisionHeight),
true);
if ( TempActor == KFM || TempActor == KFM.MyExtCollision) {
NailingLocation = KFM.Location; // need for ach
if ( KFM.NumZCDHits <= 0 ) {
// not pinned yet
KFM.NumZCDHits = 1;
NailedMonster = KFM;
NailHitDelta = TempHitLocation - KFM.Location;
KFM.bZedUnderControl = true; // under our conrol
KFM.SetPhysics(PHYS_Flying); // fly away, baby ;)
KFM.AirSpeed = 0;
KFM.SetBase(self);
if ( Physics != PHYS_None )
SetPhysics(PHYS_Falling);
Mass = KFM.Mass;
Bounces=0;
NetUpdateTime = Level.TimeSeconds - 1;
}
else {
KFM.NumZCDHits++;
}
}
}
}
}
simulated function HitWall( vector HitNormal, actor Wall )
{
if ( !Wall.bStatic && !Wall.bWorldGeometry
&& ((Mover(Wall) == None) || Mover(Wall).bDamageTriggered) )
{
if ( Level.NetMode != NM_Client )
{
if ( Instigator == None || Instigator.Controller == None )
Wall.SetDelayedDamageInstigatorController( InstigatorController );
Wall.TakeDamage( Damage, instigator, Location, MomentumTransfer * Normal(Velocity), MyDamageType);
}
Destroy();
return;
}
SetRotation(rotator(Normal(Velocity)));
if (Bounces > 0)
{
if ( !Level.bDropDetail && (FRand() < 0.4) )
Playsound(ImpactSounds[Rand(6)]);
Velocity = 0.65 * (Velocity - 2.0*HitNormal*(Velocity dot HitNormal));
SetPhysics(PHYS_Falling);
--Bounces;
bBounced=True;
if ( !Level.bDropDetail && (Level.NetMode != NM_DedicatedServer))
{
Spawn(class'ROEffects.ROBulletHitMetalEffect',,,Location, rotator(hitnormal));
}
}
else
{
if (ImpactEffect != None && (Level.NetMode != NM_DedicatedServer))
{
Spawn(ImpactEffect,,, Location, rotator(-HitNormal));
}
SetPhysics(PHYS_None);
Bounces=0;
NailDeadBodiesToWall();
//NailAliveZedsToWall();
LifeSpan = LifeSpanAfterHitWall;
bBounce = false;
if (Trail != None)
{
Trail.mRegen=False;
Trail.SetPhysics(PHYS_None);
//Trail.mRegenRange[0] = 0.0;//trail.mRegenRange[0] * 0.6;
//Trail.mRegenRange[1] = 0.0;//trail.mRegenRange[1] * 0.6;
}
}
}
simulated function PhysicsVolumeChange( PhysicsVolume Volume )
{
if (Volume.bWaterVolume)
{
if ( Trail != None )
Trail.mRegen=False;
Velocity *= 0.65;
}
}
simulated function Landed( Vector HitNormal )
{
SetPhysics(PHYS_None);
Bounces=0;
LifeSpan = LifeSpanAfterHitWall;
}
simulated function Destroyed()
{
super.Destroyed();
if( Giblet != none )
{
Giblet.Destroy();
Giblet = none;
}
if( MonsterHeadAttached != none )
{
MonsterHeadAttached = none;
}
ReleaseMonster();
}
defaultproperties
{
ImpactSoundRefs(0)="ProjectileSounds.Bullets.Impact_Metal"
ImpactSoundRefs(1)="ProjectileSounds.Bullets.Impact_Metal"
ImpactSoundRefs(2)="ProjectileSounds.Bullets.Impact_Metal"
ImpactSoundRefs(3)="ProjectileSounds.Bullets.Impact_Metal"
ImpactSoundRefs(4)="ProjectileSounds.Bullets.Impact_Metal"
ImpactSoundRefs(5)="ProjectileSounds.Bullets.Impact_Metal"
Bounces=2
LifeSpanAfterHitWall=2.000000
VelocityModMass=0.005000
VelocityModHealth=0.120000
BigZedPenDmgReduction=0.000000
MediumZedPenDmgReduction=0.000000
StaticMeshRef="EffectsSM.Weapons.Vlad_9000_Nail"
PenDamageReduction=0.330000
Damage=40.000000
MyDamageType=Class'ScrnBalanceSrv.ScrnDamTypeNailGun'
ExplosionDecal=Class'KFMod.NailGunDecal'
bNetTemporary=False
bUpdateSimulatedPosition=True
LifeSpan=10.000000
bNetNotify=True
bBounce=True
}