/* 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
}
