/**
 *      This feature fixes a bug that can allow players to bypass server's
 *  friendly fire limitations and teamkill.
 *  Usual fixes apply friendly fire scale to suspicious damage themselves, which
 *  also disables some of the environmental damage.
 *  In order to avoid that, this fix allows server owner to define precisely
 *  to what damage types to apply the friendly fire scaling.
 *  It should be all damage types related to projectiles.
 *      Copyright 2019 - 2021 Anton Tarasenko
 *------------------------------------------------------------------------------
 * This file is part of Acedia.
 *
 * Acedia is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License, or
 * (at your option) any later version.
 *
 * Acedia is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Acedia.  If not, see <https://www.gnu.org/licenses/>.
 */
class FixFFHack_Feature extends Feature;

/**
 *      It's possible to bypass friendly fire damage scaling and always deal
 *  full damage to other players, if one were to either leave the server or
 *  spectate right after shooting a projectile. We use game rules to catch
 *  such occurrences and apply friendly fire scaling to weapons,
 *  specified by server admins.
 *      To specify required subset of weapons, one must first
 *  chose a general rule (scale by default / don't scale by default) and then,
 *  optionally, add exceptions to it.
 *      Choosing `scaleByDefault == true` as a general rule will make this fix
 *  behave in the similar way to `KFExplosiveFix` by mutant and will disable
 *  some environmental sources of damage on some maps. One can then add relevant
 *  damage classes as exceptions to fix that downside, but making an extensive
 *  list of such sources might prove problematic.
 *      On the other hand, setting `scaleByDefault == false` will allow to get
 *  rid of team-killing exploits by simply adding damage types of all
 *  projectile weapons, used on a server. This fix comes with such filled-in
 *  list of all vanilla projectile classes.
 */

//      Defines a general rule for choosing whether or not to apply
//  friendly fire scaling.
//  This can be overwritten by exceptions (`alwaysScale` or `neverScale`).
//  Enabling scaling by default without any exceptions in `neverScale` will
//  make this fix behave almost identically to Mutant's
//  'Explosives Fix Mutator'.
var private /*config*/ bool                         scaleByDefault;
//  Damage types, for which we should always reapply friendly fire scaling.
var private /*config*/ array< class<DamageType> >   alwaysScale;
//  Damage types, for which we should never reapply friendly fire scaling.
var private /*config*/ array< class<DamageType> >   neverScale;

protected function OnEnabled()
{
    _server.unreal.gameRules.OnNetDamage(self).connect = NetDamage;
}

protected function OnDisabled()
{
    _server.unreal.gameRules.OnNetDamage(self).Disconnect();
}

protected function SwapConfig(FeatureConfig config)
{
    local FixFFHack newConfig;
    newConfig = FixFFHack(config);
    if (newConfig == none) {
        return;
    }
    scaleByDefault  = newConfig.scaleByDefault;
    alwaysScale     = newConfig.alwaysScale;
    neverScale      = newConfig.neverScale;
}

function int NetDamage(
    int                 originalDamage,
    int                 damage,
    Pawn                injured,
    Pawn                instigator,
    Vector              hitLocation,
    out Vector          momentum,
    class<DamageType>   damageType)
{
    //  Something is very wrong and we can just bail on this damage
    if (damageType == none) {
        return 0;
    }
    //  We only check when suspicious instigators that aren't a world
    if (!damageType.default.bCausedByWorld && IsSuspicious(instigator))
    {
        if (ShouldScaleDamage(damageType))
        {
            //  Remove pushback to avoid environmental kills
            momentum = Vect(0.0, 0.0, 0.0);
            damage *= _server.unreal.GetKFGameType().friendlyFireScale;
        }
    }
    return damage;
}

private function bool IsSuspicious(Pawn instigator)
{
    //  Instigator vanished
    if (instigator == none) return true;

    //  Instigator already became spectator
    if (KFPawn(instigator) != none)
    {
        if (instigator.playerReplicationInfo != none) {
            return instigator.playerReplicationInfo.bOnlySpectator;
        }
        return true; // Replication info is gone => suspicious
    }
    return false;
}

//  Checks general rule and exception list
public final function bool ShouldScaleDamage(class<DamageType> damageType)
{
    local int                           i;
    local array< class<DamageType> >    exceptions;
    if (damageType == none) return false;

    if (scaleByDefault) {
        exceptions = neverScale;
    }
    else {
        exceptions = alwaysScale;
    }
    for (i = 0; i < exceptions.length; i += 1)
    {
        if (exceptions[i] == damageType) {
            return (!scaleByDefault);
        }
    }
    return scaleByDefault;
}

defaultproperties
{
    configClass = class'FixFFHack'
    scaleByDefault  = false
    //  Vanilla damage types for projectiles
    alwaysScale(0)  = class'KFMod.DamTypeCrossbuzzsawHeadShot'
    alwaysScale(1)  = class'KFMod.DamTypeCrossbuzzsaw'
    alwaysScale(2)  = class'KFMod.DamTypeFrag'
    alwaysScale(3)  = class'KFMod.DamTypePipeBomb'
    alwaysScale(4)  = class'KFMod.DamTypeM203Grenade'
    alwaysScale(5)  = class'KFMod.DamTypeM79Grenade'
    alwaysScale(6)  = class'KFMod.DamTypeM79GrenadeImpact'
    alwaysScale(7)  = class'KFMod.DamTypeM32Grenade'
    alwaysScale(8)  = class'KFMod.DamTypeLAW'
    alwaysScale(9)  = class'KFMod.DamTypeLawRocketImpact'
    alwaysScale(10) = class'KFMod.DamTypeFlameNade'
    alwaysScale(11) = class'KFMod.DamTypeFlareRevolver'
    alwaysScale(12) = class'KFMod.DamTypeFlareProjectileImpact'
    alwaysScale(13) = class'KFMod.DamTypeBurned'
    alwaysScale(14) = class'KFMod.DamTypeTrenchgun'
    alwaysScale(15) = class'KFMod.DamTypeHuskGun'
    alwaysScale(16) = class'KFMod.DamTypeCrossbow'
    alwaysScale(17) = class'KFMod.DamTypeCrossbowHeadShot'
    alwaysScale(18) = class'KFMod.DamTypeM99SniperRifle'
    alwaysScale(19) = class'KFMod.DamTypeM99HeadShot'
    alwaysScale(20) = class'KFMod.DamTypeShotgun'
    alwaysScale(21) = class'KFMod.DamTypeNailGun'
    alwaysScale(22) = class'KFMod.DamTypeDBShotgun'
    alwaysScale(23) = class'KFMod.DamTypeKSGShotgun'
    alwaysScale(24) = class'KFMod.DamTypeBenelli'
    alwaysScale(25) = class'KFMod.DamTypeSPGrenade'
    alwaysScale(26) = class'KFMod.DamTypeSPGrenadeImpact'
    alwaysScale(27) = class'KFMod.DamTypeSeekerSixRocket'
    alwaysScale(28) = class'KFMod.DamTypeSeekerRocketImpact'
    alwaysScale(29) = class'KFMod.DamTypeSealSquealExplosion'
    alwaysScale(30) = class'KFMod.DamTypeRocketImpact'
    alwaysScale(31) = class'KFMod.DamTypeBlowerThrower'
    alwaysScale(32) = class'KFMod.DamTypeSPShotgun'
    alwaysScale(33) = class'KFMod.DamTypeZEDGun'
    alwaysScale(34) = class'KFMod.DamTypeZEDGunMKII'
}