//==============================================================================
//  NicePack / NiceReplicationInfo
//==============================================================================
//  Manages sending messages from clients to server.
//==============================================================================
//  'Nice pack' source
//  Do whatever the fuck you want with it
//  Author: dkanus
//  E-mail: dkanus@gmail.com
//==============================================================================
class NiceReplicationInfo extends ReplicationInfo
    dependson(NiceBullet);
var NicePack Mut;
replication{
    reliable if(Role < ROLE_Authority)
       ServerDamagePawn, ServerDealDamage, ServerDealMeleeDamage,
       ServerUpdateHit, ServerExplode, ServerJunkieExtension,
       ServerStickProjectile, ServerHealTarget;
}
//  Makes server to spawn a sticked projectile.
simulated function ServerStickProjectile
(
    KFHumanPawn instigator,
    Actor base,
    name bone,
    Vector shift,
    Rotator direction,
    NiceBullet.ExplosionData expData
){
    class'NiceProjectileSpawner'.static.
       StickProjectile(instigator, base, bone, shift, direction, expData);
}
//  Returns scale value that determines how to scale explosion damage to
//      given victim.
//  Method assumes that a valid victim was passed.
simulated function float GetDamageScale(Actor victim, Vector explosionLocation,
                                       Vector victimPoint,
                                       float explRadius, float explExp){
    local Vector    dirToVictim;
    local float     scale;
    local float     distToVictim;
    local KFPawn    victimKFPawn;
    local KFMonster victimMonster;
    dirToVictim     = victimPoint - explosionLocation;
    distToVictim    = FMax(1.0, VSize(dirToVictim));
    scale = 1 - FMax(0.0, (distToVictim - victim.collisionRadius) / explRadius);
    if(scale <= 0)
       scale = 0;
    else
       scale = scale ** explExp;
    //  Try scaling for exposure level (only available to monsters and KFPawns)
    victimKFPawn    = KFPawn(victim);
    victimMonster   = KFMonster(victim);
    if(victimKFPawn != none && victimKFPawn.health <= 0)
       scale *= victimKFPawn.GetExposureTo(explosionLocation);
    else if(victimMonster != none && victimMonster.health <= 0)
       scale *= victimMonster.GetExposureTo(explosionLocation);
    return scale;
}
//  Returns scale values that determine how to scale explosion damage to
//      given victim.
//  There's two scale values due to how kf1 calculated explosion damage:
//      by scaling it according to distance to two different points
//      (location of the victim and a point 75% of collision height higher).
//  First scale will be the one with the highest number.
//  Method assumes that a valid victim was passed.
simulated function CalculateDamageScales(   out float scale1, out float scale2,
                                           Actor victim,
                                           Vector explosionLocation,
                                           float explRadius, float explExp){
    local Vector    victimPoint1, victimPoint2;
    local float     swap;
    victimPoint1    = victim.location;
    victimPoint2.z  += victim.CollisionHeight * 0.25;
    victimPoint2    = victim.location;
    victimPoint2.z  += victim.CollisionHeight * 0.75;
    scale1 = GetDamageScale(victim, explosionLocation, victimPoint1,
                           explRadius, explExp);
    scale2 = GetDamageScale(victim, explosionLocation, victimPoint2,
                           explRadius, explExp);
    if(scale1 < scale2){
       swap    = scale1;
       scale1  = scale2;
       scale2  = swap;
    }
}
//  Simulates an explosion on a server.
simulated function ServerExplode
(
    float explDamage,
    float explRadius,
    float explExp,
    class<NiceWeaponDamageType> explDmgType,
    float momentum,
    Vector explLocation,
    Pawn instigator,
    optional Actor explosionTarget,
    optional vector explosiveDirection,
    optional bool preventProximityHeadDamage
){
    local Actor         victim;
    local int           numKilled;
    local Vector        dirToVictim;
    local Vector        hitLocation;
    local float         scale1, scale2;
    local NiceMonster   niceVictim;
    if(Role < ROLE_Authority) return;
    foreach VisibleActors(class'Actor', victim, explRadius, explLocation){
        if(victim == none || victim == self)    continue;
        if(victim.role < ROLE_Authority)        continue;
        if(ExtendedZCollision(victim) != none)  continue;
        if(Trigger(victim) != none)             continue;

        niceVictim = NiceMonster(victim);
        dirToVictim = Normal(victim.location - explLocation);
        hitLocation = victim.location - 0.5 *
            (victim.collisionHeight + victim.collisionRadius) * dirToVictim;
        CalculateDamageScales(  scale1, scale2,
                                victim, explLocation, explRadius, explExp);
        // Deal head damage if explosion is close enough to the victim's head
        if (    niceVictim != none && !preventProximityHeadDamage
            &&  niceVictim.GetDistanceToHead(explLocation) <= explRadius * 0.1)
        {
            ServerDealDamage(   victim, explDamage, instigator,
                                hitLocation, 0.0 * dirToVictim,
                                explDmgType, 0.5);
        }
        // Deal main damage
        if(scale1 > 0 || scale2 > 0) {
            ServerDealDamage(
                victim,
                explDamage * FMax(scale1, scale2),
                instigator,
                hitLocation,
                FMax(scale1, scale2) * momentum * dirToVictim,
                explDmgType);
        }
        if(niceVictim != none) {
            if (niceVictim.health <= 0) {
                numKilled += 1;
            }
            else {
                niceVictim.concussionCountdown = 10.0;
            }
        }
    }
    if(numKilled >= 4)
       KFGameType(level.game).DramaticEvent(0.05);
    else if(numKilled >= 2)
       KFGameType(level.game).DramaticEvent(0.03);
}
simulated function ServerDamagePawn
(
    KFPawn injured,
    int damage,
    Pawn instigatedBy,
    Vector hitLocation,
    Vector momentum,
    Class<DamageType> damageType,
    int hitPoint
){
    local array<int> hitPoints;
    if(injured == none) return;
    hitPoints[0] = hitPoint;
    injured.ProcessLocationalDamage(damage, instigatedBy, hitLocation, momentum,
                                   damageType, hitPoints);
}
simulated function HandleNiceHealingMechanicsAndSkills
(
    NiceHumanPawn healer,
    NiceHumanPawn healed,
    float healPotency
){
    local bool                  hasZEDHeavenCanceller;
    local NicePlayerController  nicePlayer;
    if(healer == none || healed == none) return;
    nicePlayer = NicePlayerController(healer.controller);
    if(nicePlayer == none)
       return;
    if(class'NiceVeterancyTypes'.static.
       hasSkill(nicePlayer, class'NiceSkillCommandoAdrenalineShot')){
       healed.medicAdrenaliteTime =
           class'NiceSkillCommandoAdrenalineShot'.default.boostTime;
    }
    if(class'NiceVeterancyTypes'.static.
       hasSkill(nicePlayer, class'NiceSkillMedicSymbioticHealth')){
       healer.TakeHealing(
           healer,
           healer.healthMax *
               class'NiceSkillMedicSymbioticHealth'.default.selfBoost,
           healPotency,
           KFWeapon(healer.weapon));
    }
    hasZEDHeavenCanceller = class'NiceVeterancyTypes'.static.
       hasSkill(nicePlayer, class'NiceSkillCommandoZEDHeavenCanceller');
    if(nicePlayer.IsZedTimeActive() && hasZEDHeavenCanceller){
       healed.health = healed.healthMax;
       healed.bZedTimeInvincible = true;
    }
}

simulated function RemovePoisonAndBleed(NiceHumanPawn healed)
{
    local Inventory I;

    // log spam fix
    if (healed == none)
        return;

    // No poison and bleed
    if (healed.inventory == none)
        return;

    for (I = healed.inventory; I != none; I = I.inventory)
    {
       if (MeanPoisonInventory(I) != none || MeanBleedInventory(I) != none)
           I.Destroy();
    }
}

//  Tells server to heal given human pawn.
simulated function ServerHealTarget(
    NiceHumanPawn healed,
    float charPotency,
    Pawn instigator
) {
    local NiceHumanPawn healer;
    local PlayerController healedPC;
    local KFPlayerReplicationInfo KFPRI;
    local NiceMedicGun healingTool;
    local float healTotal;
    local float healPotency;

    if(instigator == none || healed == none)                    return;
    if(healed.health <= 0 || healed.health >= healed.healthMax) return;
    KFPRI = KFPlayerReplicationInfo(instigator.PlayerReplicationInfo);
    if(KFPRI == none || KFPRI.ClientVeteranSkill == none)       return;
    healer = NiceHumanPawn(instigator);
    if(healer == none)                                          return;

    healingTool = NiceMedicGun(healer.weapon);
    healPotency = KFPRI.ClientVeteranSkill.static.GetHealPotency(KFPRI);
    healTotal = charPotency * healPotency;
    healer.AlphaAmount = 255;
    if(healingTool != none) {
       healingTool.ClientSuccessfulHeal(healer, healed);
    }
    healedPC = PlayerController(healed.controller);
    if(healedPC != none) {
        healedPC.ClientMessage(
            "You've been healed by" @ healer.GetPlayerName(),
            'CriticalEvent');
    }
    if(healed.health >= healed.healthMax) {
       healed.GiveHealth(healTotal, healed.healthMax);
       return;
    }
    HandleNiceHealingMechanicsAndSkills(healer, healed, healPotency);
    if(healed.health < healed.healthMax) {
       healed.TakeHealing(healed, healTotal, healPotency, healingTool);
    }
    RemovePoisonAndBleed(healed);
}
simulated function HandleNiceDamageMechanicsAndSkills
(
    NiceMonster niceZed,
    out int damage,
    NiceHumanPawn nicePawn,
    out Vector hitLocation,
    out Vector momentum,
    class<NiceWeaponDamageType> damageType,
    out float headshotLevel,
    out float lockonTime
){
    local bool                  hasZEDFrenzy;
    local bool                  hasTranquilizer;
    local bool                  hasVorpalBlade;
    local NiceMonsterController zedController;
    local NicePlayerController  nicePlayer;
    if(niceZed == none)     return;
    if(nicePawn == none)    return;
    nicePlayer = NicePlayerController(nicePawn.controller);
    if(nicePlayer == none)
       return;
    //  Medic's skills
    if(class<NiceDamTypeMedicDart>(damageType) != none){
       hasTranquilizer = class'NiceVeterancyTypes'.static.
           hasSkill(nicePlayer, class'NiceSkillCommandoTranquilizer');
       hasZEDFrenzy = class'NiceVeterancyTypes'.static.
           hasSkill(nicePlayer, class'NiceSkillMedicZEDFrenzy');
       // Medic's frenzy
       if(hasZEDFrenzy && nicePlayer.IsZedTimeActive()){
           niceZed.madnessCountDown =
               class'NiceSkillMedicZEDFrenzy'.default.madnessTime;
           zedController = NiceMonsterController(niceZed.controller);
           if(zedController != none)
               zedController.FindNewEnemy();
       }
    }
    //  Zerker's skills
    if(class<niceDamageTypeVetBerserker>(DamageType) != none){
       hasVorpalBlade = class'NiceVeterancyTypes'.static.
           HasSkill(nicePlayer, class'NiceSkillZerkVorpalBlade');
       if(     hasVorpalBlade && headshotLevel > 0.0
           &&  !nicePawn.IsZedExtentionsRecorded(niceZed))
           damage *= class'NiceSkillZerkVorpalBlade'.default.damageBonus;
    }
}
simulated function UpdateMeleeInvincibility
(
    NiceMonster niceZed,
    int damage,
    NiceHumanPawn nicePawn,
    Vector hitLocation,
    Vector momentum,
    class<NiceWeaponDamageType> damageType,
    float headshotLevel,
    bool mainTarget
){
    local bool hasGunzerker;
    local bool allowedInvincibility;
    if(nicePawn == none)    return;
    if(niceZed == none)     return;
    allowedInvincibility = class'NiceVeterancyTypes'.static.
       GetVeterancy(nicePawn.PlayerReplicationInfo) == class'NiceVetBerserker';
    allowedInvincibility = allowedInvincibility || niceZed.headHealth <= 0;
    if(!allowedInvincibility)
       return;
    //  Handle non-melee cases (gunzerker-invincibility)
    hasGunzerker = class'NiceVeterancyTypes'.static.
       hasSkill(   NicePlayerController(nicePawn.controller),
                   class'NiceSkillZerkGunzerker');
    if(hasGunzerker && class<niceDamageTypeVetBerserker>(damageType) == none)
       nicePawn.TryExtendingInv(niceZed, false, headshotLevel > 0.0);
    //  Handle melee-cases
    if(mainTarget && class<niceDamageTypeVetBerserker>(damageType) != none)
       nicePawn.TryExtendingInv(niceZed, true, headshotLevel > 0.0);
    nicePawn.ApplyWeaponStats(nicePawn.weapon);
}
simulated function UpdateArdour(bool isKill, NicePlayerController nicePlayer){
    local bool          hasArdour;
    local NiceHumanPawn nicePawn;
    local float         cooldownChange;
    if(nicePlayer == none)                  return;
    if(nicePlayer.abilityManager == none)   return;
    nicePawn = NiceHumanPawn(nicePlayer.pawn);
    if(nicePawn == none)
        return;
    hasArdour = class'NiceVeterancyTypes'.static.
       hasSkill(   nicePlayer,
                   class'NiceSkillSharpshooterArdour');
    if(!hasArdour)
       return;
    cooldownChange =
        class'NiceSkillSharpshooterArdour'.default.
            headshotKillReduction[nicePawn.calibrationScore - 1];
    if(!isKill){
       cooldownChange *=
           class'NiceSkillSharpshooterArdour'.default.justHeadshotReduction;
    }
    nicePlayer.abilityManager.AddToCooldown(1, -cooldownChange);
}
//  Returns 'true' if before calling it zed was alive and had a head.
simulated function bool ServerDealDamageBase
(
    Actor other,
    int damage,
    Pawn instigatedBy,
    Vector hitLocation,
    Vector momentum,
    class<NiceWeaponDamageType> damageType,
    optional float headshotLevel,
    optional float lockonTime
){
    local NiceMonster           niceZed;
    local NiceHumanPawn         nicePawn;
    local NicePlayerController  nicePlayer;
    local bool                  zedWasAliveWithHead;
    if(other == none) return false;
    niceZed = NiceMonster(other);
    nicePawn = NiceHumanPawn(InstigatedBy);
    if(nicePawn != none)
       nicePlayer = NicePlayerController(nicePawn.Controller);
    if(niceZed == none || nicePlayer == none){
       other.TakeDamage(   damage, instigatedBy,
                           hitLocation, momentum, damageType);
       return false;
    }
    zedWasAliveWithHead = (niceZed.health > 0.0) && (niceZed.headHealth > 0.0);
    HandleNiceDamageMechanicsAndSkills( niceZed, damage, nicePawn,
                                       hitLocation, momentum, damageType,
                                       headshotLevel, lockonTime);
    niceZed.TakeDamageClient(   damage, instigatedBy, hitLocation, momentum,
                               damageType, headshotLevel, lockonTime);
    return zedWasAliveWithHead;
}
//  Tells server to damage given pawn.
simulated function ServerDealDamage
(
    Actor other,
    int damage,
    Pawn instigatedBy,
    Vector hitLocation,
    Vector momentum,
    class<NiceWeaponDamageType> damageType,
    optional float headshotLevel,
    optional float lockonTime
){
    local NiceMonster   niceZed;
    local bool          zedWasAliveWithHead;
    if(headshotLevel > 0)
       UpdateArdour(false, NicePlayerController(instigatedBy.controller));
    zedWasAliveWithHead = ServerDealDamageBase( other, damage, instigatedBy,
                                               hitLocation, momentum,
                                               damageType, headshotLevel,
                                               lockonTime);
    if(!zedWasAliveWithHead)
       return;
    niceZed = NiceMonster(other);
    if(     niceZed != none
       &&  (niceZed.health < 0 || niceZed.headHealth < 0))
       UpdateArdour(true, NicePlayerController(instigatedBy.controller));
    UpdateMeleeInvincibility(   niceZed, damage,
                               NiceHumanPawn(instigatedBy),
                               hitLocation, momentum, damageType,
                               headshotLevel, true);
}
//  Tells server to damage given pawn with melee.
//  Difference with 'ServerDealDamage' is that this function passes data about
//      whether our target was 'main' target of melee swing
//      or was hit by AOE effect.
simulated function ServerDealMeleeDamage
(
    Actor other,
    int damage,
    Pawn instigatedBy,
    Vector hitLocation,
    Vector momentum,
    class<NiceWeaponDamageType> damageType,
    bool mainTarget,
    optional float headshotLevel
){
    local bool zedWasAliveWithHead;
    zedWasAliveWithHead = ServerDealDamageBase( other, damage, instigatedBy,
                                               hitLocation, momentum,
                                               damageType, headshotLevel, 0.0);
    if(!zedWasAliveWithHead)
       return;
    UpdateMeleeInvincibility(   NiceMonster(other), damage,
                               NiceHumanPawn(instigatedBy),
                               hitLocation, momentum, damageType,
                               headshotLevel, mainTarget);
}
simulated function ServerUpdateHit
(
    Actor tpActor,
    Actor hitActor,
    Vector clientHitLoc,
    Vector hitNormal,
    optional Vector hitLocDiff
){
    local KFWeaponAttachment weapAttach;
    weapAttach = KFWeaponAttachment(tpActor);
    if(weapAttach != none)
       weapAttach.UpdateHit(hitActor, clientHitLoc + hitLocDiff, hitNormal);
}
simulated function ServerJunkieExtension(   NicePlayerController player,
                                           bool isHeadshot){
    local NiceGameType              niceGame;
    local class<NiceVeterancyTypes> niceVet;
    if(player == none || player.bJunkieExtFailed) return;
    niceGame = NiceGameType(player.Level.Game);
    if(niceGame == none || !niceGame.bZEDTimeActive)
       return;
    niceVet = class'NiceVeterancyTypes'.static.
       GetVeterancy(player.PlayerReplicationInfo);   
    if(niceVet == none)
       return;
    if(niceVet.static.hasSkill(player, class'NiceSkillSharpshooterZEDAdrenaline')){
       if(!isHeadshot)
           player.bJunkieExtFailed = true;
       else if(Mut != none)
           Mut.JunkieZedTimeExtend();
    }
}

defaultproperties
{
}
