class NicePlayerController extends ScrnPlayerController
    config(NiceUser)
    dependson(NicePack)
    dependson(NiceFire);

var globalconfig int ZedHPBarStyle;                    // 0 - big zeds, 1 - all, 2 - disabled
var globalconfig int nicePlayerInfoVersionNumber;
// These are values stored in a settings file
var globalconfig bool   bDebug;
var globalconfig bool   bShowHLMessages;
var globalconfig bool   bAltSwitchesModes;
var globalconfig bool   bUseServerReload;               // Should we reload on 'ReloadMeNow' call?
var globalconfig bool   bAdvReloadCheck;                // Should we try guessing if a given binding is supposed to cause reload?
// Reload canceling options
var globalconfig bool   bRelCancelByFire;
var globalconfig bool   bRelCancelBySwitching;
var globalconfig bool   bRelCancelByNades;
var globalconfig bool   bRelCancelByAiming;
// Weapon management
var globalconfig bool   bNiceWeaponManagement;
var globalconfig bool   bNiceWeaponManagementWasInitialized;
// Additional information displaying
var globalconfig bool   bDisplayCounters;
var globalconfig bool   bDisplayWeaponProgress;
// ScrN panel-related hack to fix crashes
var globalconfig bool   bShowScrnMenu;
struct PlayedWith{
    var string playerName;
    var string steamID;
};
var globalconfig int                maxPlayedWithRecords;
var globalconfig array<PlayedWith>  recentlyPlayedWithDatabase;
var globalconfig array<PlayedWith>  playedWithDatabase;
struct WeaponGroup{
    var bool    bCanBeRemoved;
    var string  groupName;
    // Parameters for automatically adding weapons to the groups
    var bool        bAutoAddWeapon;
    var int         autoPerk;
    var array<int>  autoInvGroups;
};
var globalconfig array<WeaponGroup> UserWeaponGroups;
struct WeaponRecord{
    var string          friendlyName;
    var array<string>   groupsNames;
    var class<KFWeapon> weaponClass;
};
var globalconfig array<WeaponRecord> UserWeaponRecords;
struct WeaponPreset{
    var bool                        bCanBeRemoved;
    var string                      presetName;
    var bool                        perkedFirst;
    var int                         reqPerk;
    var array< class<KFWeapon> >    reqWeapons;
    var int                         dumpSelector;
    var array<int>                  selectorsList;
    var array<string>               groupsList;
    var bool                        bUsesMouseWheel;
    var bool                        bMouseWheelLoops;
};
var WeaponPreset DefaultWeaponPreset;
var globalconfig array<WeaponPreset> UserWeaponPresets;
struct WeaponSelector{
    var int                         selectorNumber;
    var array< class<KFWeapon> >    weaponList;
};
var bool                    hasZeroSelector;
var bool                    bUsesMouseWheel;
var bool                    bMouseWheelLoops;
var array<WeaponSelector>   activeSelectors;
var const string WeapGroupMeleeName;
var const string WeapGroupNonMeleeName;
var const string WeapGroupPistolsName;
var const string WeapGroupGeneralName;
var const string WeapGroupToolsName;
var const string WeapPresetDefaultName;
var const string WeapPresetGunslingerName;
var globalconfig int    tracesPerTickLimit;
var int                 tracesThisTick;
var globalconfig int    effectsLimitSoft;
var globalconfig int    effectsLimitHard;
// We break second into 10 parts and count how much projectiles has spawned in each one of them
var int                 effectsSpawned[10];
var int                 currentEffectTimeWindow;
var float               nextEffectsResetTime;
var int                 wallHitsLeft;
var NiceStorageClient   storageClient;
// These are actually used variables
var bool    bSettingsLoaded;
var bool    bFlagDebug;
var bool    bFlagShowHLMessages;
var bool    bFlagAltSwitchesModes;
var bool    bFlagUseServerReload;
var bool    bFlagDisplayCounters;
var bool    bFlagDisplayWeaponProgress;
var NiceCollisionManager        localCollisionManager;
struct StuckBulletRecord{
    var NiceBullet  bullet;
    var float       registrationTime;
};
var array<StuckBulletRecord> stuckBulletsSet;
var NicePack NicePackMutator;
var string SteamID64;
var bool hasExpConverted;
var bool bOpenedInitTrader;
var float sirenScreamMod;
// Skills stuff
struct SkillChoices{
    var byte isAltChoice[5];
};
// Player's skill choices. 'pendingSkills' are skill choices that player wants to use next, 'currentSkills' are current skills choices
var SkillChoices pendingSkills[20];
var globalConfig SkillChoices currentSkills[20];
// Skills that someone has and that clients should be aware of
var array< class<NiceSkill> >   broadcastedSkills;
// A new, updated broadcast skill set that's being received from the server
var array< class<NiceSkill> >   broadcastQueue;
var NiceReplicationInfo niceRI;
var NiceRepInfoRemoteData remoteRI;
var NiceAbilityManager abilityManager;
var bool bJunkieExtFailed;
replication{
    reliable if(Role == ROLE_Authority)
       niceRI, remoteRI, SteamID64, bJunkieExtFailed, pendingSkills, bSettingsLoaded, bFlagAltSwitchesModes, bFlagUseServerReload,
           bFlagShowHLMessages, bFlagDebug, bFlagDisplayCounters, bFlagDisplayWeaponProgress,
           abilityManager;
    reliable if(Role < ROLE_Authority)
            sirenScreamMod;
    reliable if(Role == ROLE_Authority)
       ClientSetSkill, ClientLoadSettings, ClientSaveConfig, ClientSetKey, ClientUpdatePlayedWithDatabase, ClientReceiveSkill, ClientBroadcastEnded, ClientLog, ClientUpdatePawnMaxHealth, ClientSetNiceWeapManagement,
           ClientSpawnSirenBall, ClientRemoveSirenBall, ClientNailsExplosion, ClientStickGhostProjectile, ClientSetZedStun, ClientShowScrnMenu;
    unreliable if(Role == ROLE_Authority)
       ClientSpawnGhostProjectile, ClientPrint;
    reliable if(Role < ROLE_Authority)
       ServerSetSkill, ServerSetPendingSkill, ServerSetAltSwitchesModes, ServerSetUseServerReload,
           ServerSetHLMessages, ServerMarkSettingsLoaded, ServerStartleZeds, ServerSetDisplayCounters,
           ServerSetDisplayWeaponProgress, ActivateAbility;
}

// Called on server only!
function PostLogin()
{
    local NicePack.PlayerRecord record;
    local NiceGameType NiceGT;
    local ScrnCustomPRI ScrnPRI;

    Super.PostLogin();
    // Restore data
    NiceGT = NiceGameType(Level.Game);
    ScrnPRI = class'ScrnCustomPRI'.static.FindMe(PlayerReplicationInfo);
    if(ScrnPRI != none)
       SteamID64 = ScrnPRI.GetSteamID64();
    if(SteamID64 != "")
       NicePackMutator = class'NicePack'.static.Myself(Level);
    if(NicePackMutator != none){
       record = NicePackMutator.FindPlayerRecord(SteamID64);
       // Copy data from a record
       PlayerReplicationInfo.Kills = record.kills;
       PlayerReplicationInfo.Deaths = record.deaths;
       KFPlayerReplicationInfo(PlayerReplicationInfo).KillAssists = record.assists;
    }
    // Set pending skills to current skill's values
    ClientLoadSettings();
    // Try giving necessary dosh to the player
    if(NicePackMutator != none)
       NicePackMutator.GiveProgressiveDosh(self);
    // Update recently played with players records
    if(SteamID64 != "")
       UpdateRecentPlayers(PlayerReplicationInfo.PlayerName, SteamID64);
    // Spawn ability manager
    abilityManager = Spawn(class'NiceAbilityManager', self);
}

simulated function ClientPostLogin(){
    local int i, j, k;
    local bool bEntryExists;
    local array<PlayedWith> newPlayedWithData;
    j = 0;
    for(i = 0;i < maxPlayedWithRecords;i ++){
       if(j < recentlyPlayedWithDatabase.Length){
           newPlayedWithData[newPlayedWithData.Length] = recentlyPlayedWithDatabase[j ++];
       }
       else
           break;
    }
    if(i < maxPlayedWithRecords){
       j = 0;
       for(i = newPlayedWithData.Length - 1;i < maxPlayedWithRecords;i ++){
           bEntryExists = false;
           for(k = 0;k < newPlayedWithData.Length;k ++)
               if(newPlayedWithData[k].steamID == playedWithDatabase[j].steamID){
                   bEntryExists = true;
                   break;
               }
           if(j < playedWithDatabase.Length && !bEntryExists)
               newPlayedWithData[newPlayedWithData.Length] = playedWithDatabase[j ++];
           else
               break;
       }
    }
    recentlyPlayedWithDatabase.Length = 0;
    playedWithDatabase = newPlayedWithData;
    UpdateDefaultWeaponSettings();
    //  Create sync node
    storageClient = new class'NiceStorageClient';
    storageClient.events.static.AddAdapter(class'NiceRemoteDataAdapter', level);
    // Init collisions
    if(Role < ROLE_Authority)
       localCollisionManager = Spawn(class'NiceCollisionManager');
    // Update ScrN menu setting
    ClientShowScrnMenu(bShowScrnMenu);
    // Change HUD parameters
    class'ScrnHUD'.default.EnemyHealthBarHeight = 4.0;
}
function ShowLobbyMenu(){
    Super.ShowLobbyMenu();
}
function TryActivatePendingSkills(){
    local int i, j;
    for(i = 0;i < 20;i ++)
       for(j = 0;j < 5;j ++)
           ServerSetSkill(i, j, pendingSkills[i].isAltChoice[j]);
}
simulated function ServerSetAltSwitchesModes(bool bSwitches){
    bFlagAltSwitchesModes = bSwitches;
}
simulated function ServerSetUseServerReload(bool bUseServer){
    bFlagUseServerReload = bUseServer;
}
simulated function ServerSetHLMessages(bool bDoMessages){
    bFlagShowHLMessages = bDoMessages;
}
simulated function ServerSetDebug(bool bDoDebug){
    bFlagDebug = bDoDebug;
}
simulated function ServerSetDisplayCounters(bool bDoDisplay){
    bFlagDisplayCounters = bDoDisplay;
}
simulated function ServerSetDisplayWeaponProgress(bool bDoDisplay){
    bFlagDisplayWeaponProgress = bDoDisplay;
}
simulated function ServerMarkSettingsLoaded(){
    bSettingsLoaded = true;
}
simulated function ClientSaveConfig(){
    if(bSettingsLoaded){
       bDebug                          = bFlagDebug;
       bShowHLMessages                 = bFlagShowHLMessages;
       bAltSwitchesModes               = bFlagAltSwitchesModes;
       bUseServerReload                = bFlagUseServerReload;
       bDisplayCounters                = bFlagDisplayCounters;
       bDisplayWeaponProgress          = bFlagDisplayWeaponProgress;
       SaveConfig();
    }
}
simulated function ClientLoadSettings(){
    local int i, j;
    ServerSetDebug(bDebug);
    ServerSetHLMessages(bShowHLMessages);
    ServerSetAltSwitchesModes(bAltSwitchesModes);
    ServerSetUseServerReload(bUseServerReload);
    ServerSetDisplayCounters(bDisplayCounters);
    ServerSetDisplayWeaponProgress(bDisplayWeaponProgress);
    if(Role < ROLE_Authority){
       for(i = 0;i < 20;i ++)
           for(j = 0;j < 5;j ++)
               ServerSetSkill(i, j, currentSkills[i].isAltChoice[j], true);
    }
    // If reliable replicated functions really are replicated in order, that should do the trick
    ServerMarkSettingsLoaded();
}
// Changes skill locally, without any replication
simulated function SetSkill(int perkIndex, int skillIndex, byte newValue){
    if(perkIndex < 0 || skillIndex < 0 || perkIndex >= 20 || skillIndex >= 5)
       return;
    if(newValue > 0)
       newValue = 1;
    else
       newValue = 0;
    currentSkills[perkIndex].isAltChoice[skillIndex] = newValue;
}
// Changes skill choice on client side
simulated function ClientSetSkill(int perkIndex, int skillIndex, byte newValue){
    local byte oldValue;
    oldValue = currentSkills[perkIndex].isAltChoice[skillIndex];
    SetSkill(perkIndex, skillIndex, newValue);
    if(oldValue != newValue)
       TriggerSelectEventOnSkillChange(perkIndex, skillIndex, newValue);
}
// Changes pending skill choice on client side
simulated function ServerSetPendingSkill(int perkIndex, int skillIndex, byte newValue){
    if(perkIndex < 0 || skillIndex < 0 || perkIndex >= 20 || skillIndex >= 5)
       return;
    if(newValue > 0)
       newValue = 1;
    else
       newValue = 0;
    pendingSkills[perkIndex].isAltChoice[skillIndex] = newValue;
}
// Calls skill change events
simulated function TriggerSelectEventOnSkillChange(int perkIndex, int skillIndex, byte newValue){
    local class<NiceVeterancyTypes> niceVet;
    niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(PlayerReplicationInfo);
    if(niceVet == none || niceVet.default.PerkIndex != perkIndex)
       return;
    if(newValue == 1){
       niceVet.default.SkillGroupA[skillIndex].static.SkillDeSelected(self);
       niceVet.default.SkillGroupB[skillIndex].static.SkillSelected(self);
    }
    else{
       niceVet.default.SkillGroupB[skillIndex].static.SkillDeSelected(self);
       niceVet.default.SkillGroupA[skillIndex].static.SkillSelected(self);
    }
}
// Calls skill change events
simulated function TriggerSelectEventOnPerkChange(class<NiceVeterancyTypes> oldVet, class<NiceVeterancyTypes> newVet){
    local int i;
    for(i = 0;i < 5;i ++){
       // Deselect old skill
       if( oldVet != none && currentSkills[oldVet.default.PerkIndex].isAltChoice[i] == 0
           && oldVet.default.SkillGroupA[i] != none)
           oldVet.default.SkillGroupA[i].static.SkillDeSelected(self);
       if( oldVet != none && currentSkills[oldVet.default.PerkIndex].isAltChoice[i] == 1
           && oldVet.default.SkillGroupB[i] != none)
           oldVet.default.SkillGroupB[i].static.SkillDeSelected(self);
       // Select new skill
       if( newVet != none && currentSkills[newVet.default.PerkIndex].isAltChoice[i] == 0
           && newVet.default.SkillGroupA[i] != none)
           newVet.default.SkillGroupA[i].static.SkillSelected(self);
       if( newVet != none && currentSkills[newVet.default.PerkIndex].isAltChoice[i] == 1
           && newVet.default.SkillGroupB[i] != none)
           newVet.default.SkillGroupB[i].static.SkillSelected(self);
    }
}
// Changes (if possible) skill choice on server side and calls 'ClientSetSkill' to also alter value on the client side
// Always calls 'ServerSetPendingSkill' for a change of a pending skill array
simulated function ServerSetSkill(int perkIndex, int skillIndex, byte newValue, optional bool bForce){
    local byte oldValue;
    if(bForce || niceRI.Mut.CanChangeSkill(Self)){
       oldValue = currentSkills[perkIndex].isAltChoice[skillIndex];
       SetSkill(perkIndex, skillIndex, newValue);
       ClientSetSkill(perkIndex, skillIndex, newValue);
       if(oldValue != newValue)
           TriggerSelectEventOnSkillChange(perkIndex, skillIndex, newValue);
    }
    ServerSetPendingSkill(perkIndex, skillIndex, newValue);
}
simulated function ClientReceiveSkill(class<NiceSkill> newSkill){
    broadcastQueue[broadcastQueue.Length] = newSkill;
}
simulated function ClientBroadcastEnded(){
    broadcastedSkills = broadcastQueue;
    broadcastQueue.Length = 0;
}
simulated function array< class<NiceSkill> > GetActiveBroadcastSkills(){
    local int i;
    local int currentLevel;
    local SkillChoices choices;
    local class<NiceVeterancyTypes> niceVet;
    local array< class<NiceSkill> > broadcastSkills;
    niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(PlayerReplicationInfo);
    currentLevel = class'NiceVeterancyTypes'.static.GetClientVeteranSkillLevel(KFPlayerReplicationInfo(PlayerReplicationInfo));
    if(niceVet != none){
       choices = currentSkills[niceVet.default.PerkIndex];
       for(i = 0;i < 5 && i < currentLevel;i ++){
           if(niceVet.default.SkillGroupA[i].default.bBroadcast && choices.isAltChoice[i] == 0)
               broadcastSkills[broadcastSkills.Length] = niceVet.default.SkillGroupA[i];
           if(niceVet.default.SkillGroupB[i].default.bBroadcast && choices.isAltChoice[i] > 0)
               broadcastSkills[broadcastSkills.Length] = niceVet.default.SkillGroupB[i];
       }
    }
    return broadcastSkills;
}
simulated function ClientSetKey(int key){
    Pawn.Weapon.InventoryGroup = key;
}
// Remove shaking for sharpshooter with a right skill
function bool ShouldShake(){
    local class<NiceVeterancyTypes> niceVet;
    niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(PlayerReplicationInfo);
    if(niceVet != none && niceVet.static.hasSkill(self, class'NiceSkillEnforcerUnstoppable'))
       return false;
    return true;
}
event SetAmbientShake(float FalloffStartTime, float    FalloffTime, vector    OffsetMag, float OffsetFreq, rotator RotMag, float RotFreq){
    if(ShouldShake())
       Super.SetAmbientShake(FalloffStartTime, FalloffTime, OffsetMag, OffsetFreq, RotMag, RotFreq);
}
function ShakeView(vector shRotMag, vector shRotRate, float shRotTime, vector shOffsetMag, vector shOffsetRate, float shOffsetTime){
    if(ShouldShake())
       Super.ShakeView(shRotMag, shRotRate, shRotTime, shOffsetMag, shOffsetRate, shOffsetTime);
}
simulated event Destroyed(){
    local NicePack.PlayerRecord record;
    if(NicePackMutator != none && SteamID64 != ""){
       record = NicePackMutator.FindPlayerRecord(SteamID64);
       // Copy data from a record
       record.kills = PlayerReplicationInfo.Kills;
       record.deaths = PlayerReplicationInfo.Deaths;
       record.assists = KFPlayerReplicationInfo(PlayerReplicationInfo).KillAssists;
       NicePackMutator.UpdatePlayerRecord(record);
    }
    Super.Destroyed();
}
// Screw that
simulated function ClientWeaponDestroyed(class<Weapon> WClass){}
// This event is generated when new pawn spawns
function PawnSpawned()
{
    local bool bFoundExp;
    local float convertedExp;
    local ClientPerkRepLink R;
    local SRCustomProgress exp;
    local NicePack.PlayerRecord record;
    //local NiceHumanPawn nicePawn;
    // Make sure our health is at it's top
    Pawn.Health = Pawn.HealthMax;
    // Make a record about spawning
    record = NicePackMutator.FindPlayerRecord(SteamID64);
    record.bHasSpawned = true;
    NicePackMutator.UpdatePlayerRecord(record);
    // Exp conversion
    if(!hasExpConverted){
       R = SRStatsBase(SteamStatsAndAchievements).Rep;
       if(R == none)
           return;
       hasExpConverted = true;
       // Field medic
       convertedExp = R.RDamageHealedStat * class'NicePack'.default.vetFieldMedicExpCost;
       if(R.GetCustomValueInt(Class'NiceVetFieldMedicExp') == 0 && convertedExp > 0){
           bFoundExp = false;
           for(exp = R.CustomLink;exp != none;exp = exp.NextLink)
               if(exp.Class.Name == 'NiceVetFieldMedicExp'){
                   bFoundExp = true;
                   break;
               }
           if(bFoundExp)
               exp.IncrementProgress(convertedExp);
       }
       // Support
       convertedExp = R.RShotgunDamageStat * class'NicePack'.default.vetSupportDamageExpCost;
       if(R.GetCustomValueInt(Class'NiceVetSupportExp') == 0 && convertedExp > 0){
           bFoundExp = false;
           for(exp = R.CustomLink;exp != none;exp = exp.NextLink)
               if(exp.Class.Name == 'NiceVetSupportExp'){
                   bFoundExp = true;
                   break;
               }
           if(bFoundExp)
               exp.IncrementProgress(convertedExp);
       }
       // Commando
       convertedExp = R.RBullpupDamageStat * class'NicePack'.default.vetCommandoDamageExpCost;
       if(R.GetCustomValueInt(Class'NiceVetCommandoExp') == 0 && convertedExp > 0){
           bFoundExp = false;
           for(exp = R.CustomLink;exp != none;exp = exp.NextLink)
               if(exp.Class.Name == 'NiceVetCommandoExp'){
                   bFoundExp = true;
                   break;
               }
           if(bFoundExp)
               exp.IncrementProgress(convertedExp);
       }
       // Sharpshooter
       convertedExp = R.RHeadshotKillsStat * class'NicePack'.default.vetSharpHeadshotExpCost;
       if(R.GetCustomValueInt(Class'NiceVetSharpshooterExp') == 0 && convertedExp > 0){
           bFoundExp = false;
           for(exp = R.CustomLink;exp != none;exp = exp.NextLink)
               if(exp.Class.Name == 'NiceVetSharpshooterExp'){
                   bFoundExp = true;
                   break;
               }
           if(bFoundExp)
               exp.IncrementProgress(convertedExp);
       }
       // Demolition
       convertedExp = R.RExplosivesDamageStat * class'NicePack'.default.vetDemoDamageExpCost;
       if(R.GetCustomValueInt(Class'NiceVetDemolitionsExp') == 0 && convertedExp > 0){
           bFoundExp = false;
           for(exp = R.CustomLink;exp != none;exp = exp.NextLink)
               if(exp.Class.Name == 'NiceVetDemolitionsExp'){
                   bFoundExp = true;
                   break;
               }
           if(bFoundExp)
               exp.IncrementProgress(convertedExp);
       }
       // Berserker
       convertedExp = R.RMeleeDamageStat * class'NicePack'.default.vetZerkDamageExpCost;
       if(R.GetCustomValueInt(Class'NiceVetBerserkerExp') == 0 && convertedExp > 0){
           bFoundExp = false;
           for(exp = R.CustomLink;exp != none;exp = exp.NextLink)
               if(exp.Class.Name == 'NiceVetBerserkerExp'){
                   bFoundExp = true;
                   break;
               }
           if(bFoundExp)
               exp.IncrementProgress(convertedExp);
       }
    }
    // Give necessary dosh to the player
    if(NicePackMutator != none)
       NicePackMutator.GiveProgressiveDosh(self);
    // Update veterancy (possibly not needed)
    /*nicePawn = NiceHumanPawn(Pawn);
    if(nicePawn != none)
       nicePawn.VeterancyChanged();*/
}
exec function ForceSetName(coerce string S){
    ChangeName(S);
    UpdateURL("Name", S, true);
}
exec function OpenTrader(){
    if(KFHumanPawn(Pawn) != none){
       ShowBuyMenu("Initial trader", KFHumanPawn(Pawn).MaxCarryWeight);
       bOpenedInitTrader = true;
    }
}
function UpdateRecentPlayers(string playerName, string steamID){
    local Controller P;
    local NicePlayerController nicePlayer;
    for(P = Level.ControllerList; P != none; P = P.nextController){
       nicePlayer = NicePlayerController(P);
       if(nicePlayer != none && nicePlayer.SteamID64 != steamID){
           nicePlayer.ClientUpdatePlayedWithDatabase(playerName, steamID);
           if(nicePlayer.SteamID64 != ""){
               Self.ClientUpdatePlayedWithDatabase(nicePlayer.PlayerReplicationInfo.PlayerName, nicePlayer.SteamID64);
           }
       }
    }
}
simulated function ClientUpdatePlayedWithDatabase(string playerName, string steamID){
    local int i;
    local bool bFound;
    local PlayedWith newRecord;
    if(SteamID64 != "" && SteamID64 != steamID){
       // Prepare new record
       newRecord.playerName = playerName;
       newRecord.steamID = steamID;
       // Update recently played with players
       bFound = false;
       for(i = 0;i < recentlyPlayedWithDatabase.Length;i ++)
           if(recentlyPlayedWithDatabase[i].steamID == steamID){
               recentlyPlayedWithDatabase[i] = newRecord;
               bFound = true;
               break;
           }
       if(!bFound)
           recentlyPlayedWithDatabase[recentlyPlayedWithDatabase.Length] = newRecord;
       // Update older record
       for(i = 0;i < playedWithDatabase.Length;i ++)
           if(playedWithDatabase[i].steamID == steamID){
               playedWithDatabase.Remove(i, 1);
               break;
           }
    }
}
simulated function bool IsZedTimeActive(){
    local KFGameType KFGT;
    if(Role < ROLE_Authority)
       return bZedTimeActive;
    else{
       KFGT = KFGameType(Level.Game);
       if(KFGT != none)
           return KFGT.bZEDTimeActive;
    }
    return false;
}
function BecomeActivePlayer(){
    if(Role < ROLE_Authority)
       return;
    if(!Level.Game.AllowBecomeActivePlayer(self))
       return;
    super.BecomeActivePlayer();
    if(NicePackMutator != none)
       NicePackMutator.GiveProgressiveDosh(self);
}
function ServerStartleZeds(float dist, float duration){
    local Vector pawnLoc;
    local Controller contr;
    local NiceMonsterController niceZed;
    if(Pawn != none)
       pawnLoc = Pawn.Location;
    else
       return;
    for(contr = Level.ControllerList; contr != none; contr = contr.nextController){
       niceZed = NiceMonsterController(contr);
       if(niceZed != none && niceZed.Pawn != none && VSize(pawnLoc - niceZed.Pawn.Location) <= dist)
           niceZed.StartleFor(Pawn, duration);
    }
}

/*simulated function ClientExitZedTime(){
    super.ClientExitZedTime();
}*/
simulated function ClientLog(String logStr){
    if(bFlagDebug)
       Log("NiceDebug:"$logStr);
}

function ServerUse()
{
    local NiceHumanPawn myPawn;

    myPawn = NiceHumanPawn(Pawn);
    if (myPawn == none)
    {
       super.ServerUse();
       return;
    }

    // Handle initial shop / medic drugs
    if (NicePackMutator != none && NicePackMutator.bIsPreGame && NicePackMutator.bInitialTrader)
    {
       if (VSize(Pawn.Velocity) <= 0.0)
       {
           ShowBuyMenu("Initial trader", myPawn.MaxCarryWeight);
           bOpenedInitTrader = true;
       }
    }

    // call this anyways, so we can use doors!
    super.ServerUse();
}

simulated function ClientUpdatePawnMaxHealth(NiceHumanPawn updatePawn, int newHealthMax){
    updatePawn.HealthMax = newHealthMax;
}
simulated function int FindWeaponGroup(string groupName){
    local int i;
    for(i = 0;i < UserWeaponGroups.Length;i ++)
       if(UserWeaponGroups[i].groupName ~= groupName)
           return i;
    return -1;
}
simulated function int AddWeaponGroup(string groupName){
    local int groupIndex;
    local WeaponGroup newWeapGroup;
    groupIndex = FindWeaponGroup(groupName);
    if(groupIndex < 0){
       newWeapGroup.bCanBeRemoved = true;
       newWeapGroup.groupName = groupName;
       newWeapGroup.autoPerk = -1;
       UserWeaponGroups[UserWeaponGroups.Length] = newWeapGroup;
       return UserWeaponGroups.Length - 1;
    }
    return groupIndex;
}
simulated function RemoveWeaponGroup(string groupName){
    local int i, j;
    local int groupIndex;
    groupIndex = FindWeaponGroup(groupName);
    if(groupIndex >= 0 && UserWeaponGroups[groupIndex].bCanBeRemoved)
       UserWeaponGroups.Remove(groupIndex, 1);
    // Remove group name from weapons' records
    for(i = 0;i < UserWeaponRecords.Length;i ++)
       for(j = 0;j < UserWeaponRecords[i].groupsNames.Length;j ++)
           if(UserWeaponRecords[i].groupsNames[j] ~= groupName){
               UserWeaponRecords[i].groupsNames.Remove(j, 1);
               break;
           }
}
// Looks through 'UserWeaponRecords' and return index of given weapon if it's placed in given group
// Passing empty string "" will force function to search for weapon in all groups
simulated function int FindWeaponInGroup(class<KFWeapon> weaponClass, string groupName){
    local int i, j;
    local bool anyRecord;
    anyRecord = (groupName == "");
    for(i = 0;i < UserWeaponRecords.Length;i ++)
       if(weaponClass == UserWeaponRecords[i].weaponClass){
           if(anyRecord)
               return i;
           for(j = 0;j < UserWeaponRecords[i].groupsNames.Length;j ++)
               if(groupName ~= UserWeaponRecords[i].groupsNames[j])
                   return i;
           break;
       }
    return -1;
}
// Returns 'true' iff weapon belongs to at least one group from 'groupsArray'
simulated function bool IsWeaponInGroups(class<KFWeapon> weaponClass, array<WeaponGroup> groupsArray){
    local int i, j;
    local int weaponIndex;
    weaponIndex = FindWeaponInGroup(weaponClass, "");
    for(i = 0;i < UserWeaponRecords[weaponIndex].groupsNames.Length;i ++)
       for(j = 0;j < groupsArray.Length;j ++)
           if(UserWeaponRecords[weaponIndex].groupsNames[i] ~= groupsArray[j].groupName)
               return true;
    return false;
}
// Returns weapons from 'input' array that are included in given group; removes them from the 'input' array
simulated function array< class<KFWeapon> > FilterWeaponsByGroup(out array< class<KFWeapon> > input, string groupName){
    local int i;
    local int weaponIndex;
    local array< class<KFWeapon> > output;
    i = 0;
    while(i < input.Length){
       weaponIndex = FindWeaponInGroup(input[i], groupName);
       if(weaponIndex < 0)
           i ++;
       else{
           output[output.Length] = input[i];
           input.Remove(i, 1);
       }
    }
    return output;
}
// Adds weapon record if this weapon class wasn't already recorded
// Returns index of the weapon after operation
simulated function int AddWeaponRecord(class<KFWeapon> weaponClass){
    local int i;
    local int weaponIndex;
    local WeaponRecord newWeapRecord;
    if(WeaponClass == none)
       return -1;
    weaponIndex = FindWeaponInGroup(weaponClass, "");
    if(weaponIndex < 0){
       newWeapRecord.weaponClass = weaponClass;
       for(i = 0;i < UserWeaponRecords.Length;i ++)
           if( UserWeaponRecords[i].weaponClass.default.InventoryGroup > weaponClass.default.InventoryGroup
               || (UserWeaponRecords[i].weaponClass.default.InventoryGroup == weaponClass.default.InventoryGroup && UserWeaponRecords[i].weaponClass.default.GroupOffset > weaponClass.default.GroupOffset) ){
               UserWeaponRecords.Insert(i, 1);
               UserWeaponRecords[i] = newWeapRecord;
               return i;
           }
       weaponIndex = UserWeaponRecords.Length;
       UserWeaponRecords[weaponIndex] = newWeapRecord;
       return weaponIndex;
    }
    return weaponIndex;
}
simulated function bool AddWeaponToGroup(class<KFWeapon> weaponClass, string groupName){
    local int i;
    local int weaponIndex;
    // Exit if there's no such group
    if(FindWeaponGroup(groupName) < 0)
       return false;
    weaponIndex = AddWeaponRecord(weaponClass);
    for(i = 0;i < UserWeaponRecords[weaponIndex].groupsNames.Length;i ++)
       if(UserWeaponRecords[weaponIndex].groupsNames[i] ~= groupName)
           return true;
    UserWeaponRecords[weaponIndex].groupsNames[UserWeaponRecords[weaponIndex].groupsNames.Length] = groupName;
    return true;
}
// Automatically adds weapon class to groups that requested it
simulated function AutoAddWeaponToGroups(class<KFWeapon> weaponClass){
    local int   i, j;
    local int   weaponIndex;
    local int   weaponPerkIndex;
    local bool  groupContains, requirementsMatch;
    // Indices of groups that don't contain 'weaponClass', but have auto addition enabled
    local array<int> autoGroups;
    weaponIndex = AddWeaponRecord(weaponClass);
    // Fill 'autoGroups' array
    for(i = 0;i < UserWeaponGroups.Length;i ++){
       // If that's not an auto add group - skip it
       if(!UserWeaponGroups[i].bAutoAddWeapon)
           continue;
       // Does this group already contain this weapon class?
       groupContains = false;
       for(j = 0;j < UserWeaponRecords[weaponIndex].groupsNames.Length;j ++)
           if(UserWeaponGroups[i].groupName ~= UserWeaponRecords[weaponIndex].groupsNames[j]){
               groupContains = true;
               break;
           }
       // If it doesn't - add it
       if(!groupContains)
           autoGroups[autoGroups.Length] = i;
    }
    // Add this weapon to new auto groups with suitable requirement
    for(i = 0;i < autoGroups.Length;i ++){
       requirementsMatch = false;
       // Let's just assume that there's always a pickup class, otherwise - wth?!
       // Match perk
       weaponPerkIndex = -1;
       if(class<KFWeaponPickup>(weaponClass.default.PickupClass) != none)
           weaponPerkIndex = class<KFWeaponPickup>(weaponClass.default.PickupClass).default.CorrespondingPerkIndex;
       requirementsMatch = (UserWeaponGroups[autoGroups[i]].autoPerk < 0 || UserWeaponGroups[autoGroups[i]].autoPerk == weaponPerkIndex);
       // Match InventoryGroup
       if(requirementsMatch && UserWeaponGroups[autoGroups[i]].autoInvGroups.Length > 0){
           // We reset flag here, but will set it to true if matching inventory group is found
           requirementsMatch = false;
           for(j = 0;j < UserWeaponGroups[autoGroups[i]].autoInvGroups.Length;j ++)
               if(UserWeaponGroups[autoGroups[i]].autoInvGroups[j] == weaponClass.default.InventoryGroup){
                   requirementsMatch = true;
                   break;
               }
       }
       // Add matched groups
       if(requirementsMatch)
           UserWeaponRecords[weaponIndex].groupsNames[UserWeaponRecords[weaponIndex].groupsNames.Length] = UserWeaponGroups[autoGroups[i]].groupName;
    }
}
simulated function int FindWeaponPreset(string presetName){
    local int i;
    for(i = 0;i < UserWeaponPresets.Length;i ++)
       if(UserWeaponPresets[i].presetName ~= presetName)
           return i;
    return -1;
}
simulated function int AddWeaponPreset(string presetName){
    local int presetIndex;
    local WeaponPreset newWeapPreset;
    presetIndex = FindWeaponPreset(presetName);
    if(presetIndex < 0){
       newWeapPreset.bCanBeRemoved = true;
       newWeapPreset.presetName = presetName;
       UserWeaponPresets[UserWeaponPresets.Length] = newWeapPreset;
       return UserWeaponPresets.Length - 1;
    }
    return presetIndex;
}
simulated function RemoveWeaponPreset(string presetName){
    local int presetIndex;
    presetIndex = FindWeaponPreset(presetName);
    if(presetIndex >= 0 && UserWeaponPresets[presetIndex].bCanBeRemoved)
       UserWeaponPresets.Remove(presetIndex, 1);
}
simulated function bool IsSubsetOf(array< class<KFWeapon> > subset, array< class<KFWeapon> > overset){
    local int i, j;
    local bool elementIncluded;
    for(i = 0;i < subset.Length;i ++){
       elementIncluded = false;
       for(j = 0;j < overset.Length;j ++)
           if(subset[i] == overset[j]){
               elementIncluded = true;
               break;
           }
       if(!elementIncluded)
           return false;
    }
    return true;
}
simulated function WeaponPreset GetCurrentPreset(int perk, array< class<KFWeapon> > currentInventory){
    local int i;
    local WeaponPreset loopPreset;
    for(i = 0;i < UserWeaponPresets.Length;i ++){
       loopPreset = UserWeaponPresets[i];
       if(loopPreset.reqPerk >= 0 && loopPreset.reqPerk != perk)
           continue;
       if(!IsSubsetOf(loopPreset.reqWeapons, currentInventory))
           continue;
       return loopPreset;
    }
    return DefaultWeaponPreset;
}
simulated function array<WeaponGroup> GetPresetGroups(WeaponPreset preset){
    local int i, j;
    local int groupIndex;
    local bool bRepeatingGroup;
    local array<WeaponGroup> result;
    for(i = 0;i < preset.groupsList.Length;i ++){
       bRepeatingGroup = false;
       for(j = 0; j < result.Length;j ++)
           if(preset.groupsList[i] ~= result[j].groupName){
               bRepeatingGroup = true;
               break;
           }
       if(!bRepeatingGroup){
           groupIndex = FindWeaponGroup(preset.groupsList[i]);
           if(groupIndex >= 0)
               result[result.Length] = UserWeaponGroups[groupIndex];
       }
    }
    return result;
}
simulated function UpdateDefaultWeaponSettings(){
    local int index;
    if(!bNiceWeaponManagementWasInitialized){
       UserWeaponGroups.Length = 0;
       UserWeaponPresets.Length = 0;
       UserWeaponRecords.Length = 0;
       AddWeaponPreset(WeapPresetGunslingerName);
       index = FindWeaponPreset(WeapPresetGunslingerName);
       if(index >= 0){
           UserWeaponPresets[index].bCanBeRemoved = true;
           UserWeaponPresets[index].presetName = WeapPresetGunslingerName;
           UserWeaponPresets[index].perkedFirst = false;
           UserWeaponPresets[index].reqPerk = 8;
           UserWeaponPresets[index].dumpSelector = -1;
           UserWeaponPresets[index].selectorsList[0] = 1;
           UserWeaponPresets[index].groupsList[0] = WeapGroupMeleeName;
           UserWeaponPresets[index].selectorsList[1] = 2;
           UserWeaponPresets[index].groupsList[1] = WeapGroupNonMeleeName;
           UserWeaponPresets[index].selectorsList[2] = 3;
           UserWeaponPresets[index].groupsList[2] = WeapGroupNonMeleeName;
           UserWeaponPresets[index].selectorsList[3] = 4;
           UserWeaponPresets[index].groupsList[3] = WeapGroupNonMeleeName;
           UserWeaponPresets[index].selectorsList[4] = 5;
           UserWeaponPresets[index].groupsList[4] = WeapGroupToolsName;
           bNiceWeaponManagementWasInitialized = true;
       }
    }
    index = AddWeaponGroup(WeapGroupMeleeName);
    UserWeaponGroups[index].bCanBeRemoved = false;
    UserWeaponGroups[index].bAutoAddWeapon = true;
    UserWeaponGroups[index].autoInvGroups[0] = 1;
    index = AddWeaponGroup(WeapGroupNonMeleeName);
    UserWeaponGroups[index].bCanBeRemoved = false;
    UserWeaponGroups[index].bAutoAddWeapon = true;
    UserWeaponGroups[index].autoInvGroups[0] = 2;
    UserWeaponGroups[index].autoInvGroups[1] = 3;
    UserWeaponGroups[index].autoInvGroups[2] = 4;
    index = AddWeaponGroup(WeapGroupPistolsName);
    UserWeaponGroups[index].bCanBeRemoved = false;
    UserWeaponGroups[index].bAutoAddWeapon = true;
    UserWeaponGroups[index].autoInvGroups[0] = 2;
    index = AddWeaponGroup(WeapGroupGeneralName);
    UserWeaponGroups[index].bCanBeRemoved = false;
    UserWeaponGroups[index].bAutoAddWeapon = true;
    UserWeaponGroups[index].autoInvGroups[0] = 3;
    UserWeaponGroups[index].autoInvGroups[1] = 4;
    index = AddWeaponGroup(WeapGroupToolsName);
    UserWeaponGroups[index].bCanBeRemoved = false;
    UserWeaponGroups[index].bAutoAddWeapon = true;
    UserWeaponGroups[index].autoInvGroups[0] = 5;
    DefaultWeaponPreset.bCanBeRemoved = false;
    DefaultWeaponPreset.reqPerk = -1;
    DefaultWeaponPreset.reqWeapons.Length = 0;
    DefaultWeaponPreset.dumpSelector = -1;
    DefaultWeaponPreset.presetName = WeapPresetDefaultName;
    DefaultWeaponPreset.selectorsList[0] = 1;
    DefaultWeaponPreset.groupsList[0] = WeapGroupMeleeName;
    DefaultWeaponPreset.selectorsList[1] = 2;
    DefaultWeaponPreset.groupsList[1] = WeapGroupPistolsName;
    DefaultWeaponPreset.selectorsList[2] = 3;
    DefaultWeaponPreset.groupsList[2] = WeapGroupGeneralName;
    DefaultWeaponPreset.selectorsList[3] = 4;
    DefaultWeaponPreset.groupsList[3] = WeapGroupGeneralName;
    DefaultWeaponPreset.selectorsList[4] = 5;
    DefaultWeaponPreset.groupsList[4] = WeapGroupToolsName;
}
simulated function array< class<KFWeapon> > SortWeaponArray(array< class<KFWeapon> > input, int perkIndex){
    local int i, j;
    local int weaponPerkIndex;
    local array< class<KFWeapon> > output, perked;
    for(i = 0;i < UserWeaponRecords.Length;i ++)
       for(j = 0;j < input.Length;j ++)
           if(UserWeaponRecords[i].weaponClass == input[j]){
               if(class<KFWeaponPickup>(input[j].default.PickupClass) != none)
                   weaponPerkIndex = class<KFWeaponPickup>(input[j].default.PickupClass).default.CorrespondingPerkIndex;
               if(perkIndex == weaponPerkIndex)
                   perked[perked.Length] = input[j];
               else
                   output[output.Length] = input[j];
               input.Remove(j, 1);
               break;
           }
    for(i = 0;i < perked.Length;i ++)
       output[output.Length] = perked[i];
    return output;
}
simulated function array< class<KFWeapon> > AddDumpSelector(int dumpSelectorNum, array<WeaponGroup> activeGroups, array< class<KFWeapon> > currentWeapons, array< class<KFWeapon> > emptyWeapons){
    local int i;
    local WeaponSelector tempSelector;
    i = 0;
    tempSelector.selectorNumber = dumpSelectorNum;
    // Add empty weapons to the bottom
    for(i = 0;i < emptyWeapons.Length;i ++)
       tempSelector.weaponList[tempSelector.weaponList.Length] = emptyWeapons[i];
    // Add weapons not in current active groups
    while(i < currentWeapons.Length)
       if(!IsWeaponInGroups(currentWeapons[i], activeGroups)){
           tempSelector.weaponList[tempSelector.weaponList.Length] = currentWeapons[i];
           currentWeapons.Remove(i, 1);
       }
       else
           i ++;
    activeSelectors[activeSelectors.Length] = tempSelector;
    return currentWeapons;
}
simulated function AddRegularSelectors(WeaponPreset currentPreset, array<WeaponGroup> activeGroups, array< class<KFWeapon> > currentWeapons){
    local int i, j, k;
    local int remWeaps;
    local int groupWeaponsAmount, groupSelectorsAmount;
    local WeaponSelector tempSelector;
    local array<WeaponSelector> groupSelectors;
    local array< class<KFWeapon> > groupWeapons;
    // Build selectors for each group
    hasZeroSelector = false;
    tempSelector.weaponList.Length = 0;
    for(i = 0;i < activeGroups.Length;i ++){
       groupWeapons = FilterWeaponsByGroup(currentWeapons, activeGroups[i].groupName);
       // Add empty selectors
       groupSelectors.Length = 0;
       for(j = 0;j < currentPreset.groupsList.Length && j < currentPreset.selectorsList.Length;j ++){
           if(currentPreset.groupsList[j] ~= activeGroups[i].groupName){
               tempSelector.selectorNumber = currentPreset.selectorsList[j];
               groupSelectors[groupSelectors.Length] = tempSelector;
           }
       }
       // Distribute weapons by selectors
       groupWeaponsAmount = groupWeapons.Length;
       groupSelectorsAmount = groupSelectors.Length;
       if(groupWeaponsAmount == groupSelectorsAmount)
           for(j = 0;j < groupSelectorsAmount;j ++)
               groupSelectors[j].weaponList[0] = groupWeapons[j];
       else if(groupWeaponsAmount < groupSelectorsAmount && groupWeaponsAmount > 0)
           for(j = 0;j < groupSelectorsAmount;j ++)
               groupSelectors[j].weaponList[0] = groupWeapons[Min(j, groupWeaponsAmount - 1)];
       else{
           remWeaps = groupWeaponsAmount % groupSelectorsAmount;
           // Load the uneven part
           for(j = 0;j < remWeaps;j ++)
               groupSelectors[j].weaponList[0] = groupWeapons[j];
           // Load everything else
           k = 0;
           for(j = remWeaps;j < groupWeaponsAmount;j ++){
               groupSelectors[k].weaponList[groupSelectors[k].weaponList.Length] = groupWeapons[j];
               k ++;
               k = k % groupSelectorsAmount;
           }
       }
       // Add selectors to main group and check if there's non-empty 0-selector
       for(j = 0;j < groupSelectorsAmount;j ++){
           activeSelectors[activeSelectors.Length] = groupSelectors[j];
           if(groupSelectors[j].selectorNumber == 0 && groupSelectors[j].weaponList.Length > 0)
               hasZeroSelector = true;
       }
    }
}
simulated function UpdateSelectors(){
    local int i;
    // Variables for finding veterancy and current weapon list
    local Inventory Inv;
    local NiceHumanPawn nicePawn;
    local KFPlayerReplicationInfo KFPRI;
    local class<ScrnVeterancyTypes> scrnVet;
    // Veterancy and current weapon list
    local int currPerk;
    local array< class<KFWeapon> > emptyWeapons;
    local array< class<KFWeapon> > currentWeapons;
    // Variables directly necessary to sort weapons by selectors
    local bool bSortByPerk;
    local int dumpSelector;
    local WeaponPreset currentPreset;
    local array<WeaponGroup> activeGroups;
    // Find current veterancy index
    nicePawn = NiceHumanPawn(Pawn);
    KFPRI = KFPlayerReplicationInfo(PlayerReplicationInfo);
    if(nicePawn == none || KFPRI == none || KFPRI.ClientVeteranSkill == none)
       return;
    scrnVet = class<ScrnVeterancyTypes>(KFPRI.ClientVeteranSkill);
    if(scrnVet != none)
       currPerk = scrnVet.default.PerkIndex;
    else
       currPerk = -1;
    // Build weapons list
    for(Inv = nicePawn.Inventory;Inv != none;Inv = Inv.Inventory)
       if(class<KFWeapon>(Inv.class) != none && class<Frag>(Inv.class) == none){
           //if(KFWeapon(Inv).HasAmmo())
           currentWeapons[currentWeapons.Length] = class<KFWeapon>(Inv.class);
           //else
           //    emptyWeapons[emptyWeapons.Length] = class<KFWeapon>(Inv.class);
           // Add weapon to required groups before using it
           AutoAddWeaponToGroups(class<KFWeapon>(Inv.class));
       }
    // Find active groups and read setting from the preset
    currentPreset = GetCurrentPreset(currPerk, currentWeapons);
    activeGroups = GetPresetGroups(currentPreset);
    bUsesMouseWheel = currentPreset.bUsesMouseWheel;
    bMouseWheelLoops = currentPreset.bMouseWheelLoops;
    if(currentPreset.presetName ~= WeapPresetDefaultName)
       bSortByPerk = bPrioritizePerkedWeapons;
    else
       bSortByPerk = currentPreset.perkedFirst;
    if(bSortByPerk)
       currentWeapons = SortWeaponArray(currentWeapons, currPerk);
    else
       currentWeapons = SortWeaponArray(currentWeapons, -1);
    // Verify that dump selector exists and isn't used for something else
    dumpSelector = currentPreset.dumpSelector;
    if(dumpSelector >= 0){
       for(i = 0;i < currentPreset.selectorsList.Length;i ++)
           if(dumpSelector == currentPreset.selectorsList[i]){
               dumpSelector = -1;
               break;
           }
    }
    //////// Selectors building
    activeSelectors.Length = 0;
    // Add dump selector
    if(dumpSelector >= 0)
       currentWeapons = AddDumpSelector(dumpSelector, activeGroups, currentWeapons, emptyWeapons);
    AddRegularSelectors(currentPreset, activeGroups, currentWeapons);
}
simulated function ClientShowScrnMenu(bool bDoShow){
    class'NiceInvasionLoginMenu'.default.bShowScrnMenu = bDoShow;
    bShowScrnMenu = bDoShow;
}
simulated function ClientSetNiceWeapManagement(bool bDoManage){
    if(bDoManage && !bNiceWeaponManagement)
       UpdateSelectors();
    bNiceWeaponManagement = bDoManage;
}
simulated function ScrollSelector(byte F, bool bLoop, optional bool bReverse){
    local int i;
    local bool bFoundWeapon;
    local bool endOfSelector;
    local bool bAllowToStartOver;
    local int selectorIndex;
    // Find selector's index
    selectorIndex = -1;
    for(i = 0;i < activeSelectors.Length;i ++)
       if(activeSelectors[i].selectorNumber == F){
           selectorIndex = i;
           break;
       }
    // Do nothing in case of missing/empty selector or missing pawn
    if(selectorIndex == -1 || Pawn == none || activeSelectors[selectorIndex].weaponList.Length <= 0)
       return;
    // Find current weapon's place in this selector
    if(bReverse)
       i = 0;
    else
       i = activeSelectors[selectorIndex].weaponList.Length - 1;
    bFoundWeapon = false;
    endOfSelector = false;
    while(!endOfSelector){
       if(Pawn.Weapon == none || activeSelectors[selectorIndex].weaponList[i] == Pawn.Weapon.class){
           bFoundWeapon = (activeSelectors[selectorIndex].weaponList[i] == Pawn.Weapon.class);
           break;
       }
       if(bReverse){
           i ++;
           endOfSelector = i >= activeSelectors[selectorIndex].weaponList.Length;
       }
       else{
           i --;
           endOfSelector = i < 0;
       }
    }
    // If weapon isn't from this selector, or is placed at it's end (and looping is allowed) - begin from the start
    bAllowToStartOver = (!bFoundWeapon || bLoop);
    if(bReverse){
       if(i < activeSelectors[selectorIndex].weaponList.Length - 1)
           GetWeapon(activeSelectors[selectorIndex].weaponList[i + 1]);
       else if(bAllowToStartOver)
           GetWeapon(activeSelectors[selectorIndex].weaponList[0]);
    }
    else{
       if(i > 0)
           GetWeapon(activeSelectors[selectorIndex].weaponList[i - 1]);
       else if(bAllowToStartOver)
           GetWeapon(activeSelectors[selectorIndex].weaponList[activeSelectors[selectorIndex].weaponList.Length - 1]);
    }
}
simulated function ClientSetZedStun(NiceMonster zed, bool bStun, float duration){
    if(zed == none)
       return;
    if(bStun)
       zed.StunCountDown = duration;
    else
       zed.StunCountDown = 0.0;
    zed.StunRefreshClient(bStun);
}
exec function SwitchWeapon(byte F){
    if(!bNiceWeaponManagement)
       super.SwitchWeapon(F);
    else{
       UpdateSelectors();
       ScrollSelector(F, true);
    }
}
exec function GetWeapon(class<Weapon> NewWeaponClass){
    local Inventory Inv;
    local int Count;
    if((Pawn == none) || (Pawn.Inventory == none) || (NewWeaponClass == none))
       return;
    if((Pawn.Weapon != none) && (Pawn.Weapon.Class == NewWeaponClass) && (Pawn.PendingWeapon == none)){
       Pawn.Weapon.Reselect();
       return;
    }
    if(Pawn.PendingWeapon != none && Pawn.PendingWeapon.bForceSwitch)
       return;
    for(Inv = Pawn.Inventory;Inv != none;Inv = Inv.Inventory){
       if(Inv.Class == NewWeaponClass){
           Pawn.PendingWeapon = Weapon(Inv);
           if(Pawn.Weapon != none)
               Pawn.Weapon.PutDown();
           else
               ChangedWeapon();
           return;
       }
       Count ++;
       if(Count > 1000)
           return;
    }
}
function ServerSetViewTarget(Actor NewViewTarget){
    local bool bWasSpec;
    if(!IsInState('Spectating'))
       return;
    bWasSpec = !bBehindView && ViewTarget != Pawn && ViewTarget != self;
    SetViewTarget(NewViewTarget);
    ViewTargetChanged();
    ClientSetViewTarget(NewViewTarget);
    if(ViewTarget == self || bWasSpec)
       bBehindView = false;
    else
       bBehindView = true;
    ClientSetBehindView(bBehindView);
}
function ViewTargetChanged(){
    local Controller C;
    local ScrnHumanPawn ScrnVT;
    if(Role < ROLE_Authority)
       return;
    ScrnVT = ScrnHumanPawn(OldViewTarget);
    if(ScrnVT != none && ScrnVT != ViewTarget){
       for(C = Level.ControllerList;C != none;C = C.NextController){
           if(C.Pawn != ScrnVT && PlayerController(C) != none && PlayerController(C).ViewTarget == ScrnVT)
               break;
       }
       ScrnVT.bViewTarget = (C != none);
    }
    ScrnVT = ScrnHumanPawn(ViewTarget);
    if (ScrnVT != none)
           ScrnVT.bViewTarget = true; // tell pawn that we are watching him
    OldViewTarget = ViewTarget;
}
// Reloaded to add nice single/dual classes
function LoadDualWieldables(){
    local ClientPerkRepLink CPRL;
    local class<NiceWeaponPickup> WP;
    local class<NiceSingle> W;
    local int i;
    
    CPRL = class'ClientPerkRepLink'.Static.FindStats(self);
    if(CPRL == none || CPRL.ShopInventory.Length == 0)
       return;
    for(i = 0;i < CPRL.ShopInventory.Length;i ++){
       WP = class<NiceWeaponPickup>(CPRL.ShopInventory[i].PC);
       if(WP == none)
           continue;
       W = class<NiceSingle>(WP.default.InventoryType);
       if(W != none && W.default.DualClass != none)
           AddDualWieldable(W, W.default.DualClass);
    }
    super.LoadDualWieldables();
}
// If player only has one pistol out of two possible, then return 'false'
// Because he's got the right one and new one is the left one; completely different stuff
function bool IsInInventory(class<Pickup> PickupToCheck, bool bCheckForEquivalent, bool bCheckForVariant){
    local bool bResult;
    local Inventory CurInv;
    local NiceSingle singlePistol;
    bResult = super.IsInInventory(PickupToCheck, bCheckForEquivalent, bCheckForVariant);
    if(!bResult || class<NiceSinglePickup>(PickupToCheck) == none)
       return bResult;
    for(CurInv = Pawn.Inventory; CurInv != none; CurInv = CurInv.Inventory)
       if(CurInv.default.PickupClass == PickupToCheck){
           singlePistol = NiceSingle(CurInv);
           if(singlePistol != none && !singlePistol.bIsDual)
               return false;
           else
               break;
       }
    return bResult;
}
state Spectating{
    exec function Use(){
       local vector HitLocation, HitNormal, TraceEnd, TraceStart;
       local rotator R;
       local Actor A;

       PlayerCalcView(A, TraceStart, R);
       TraceEnd = TraceStart + 1000 * Vector(R);
       A = Trace(HitLocation, HitNormal, TraceEnd, TraceStart, true);
       if(Pawn(A) != none)
           ServerSetViewTarget(A);
    }
}
simulated function ClientSpawnGhostProjectile(Vector Start, int pitch, int yaw, int roll, NiceFire.ShotType shotParams, NiceFire.FireModeContext fireContext, bool bForceComplexTraj){
    local Rotator projectileDir;
    projectileDir.Pitch = pitch;
    projectileDir.Yaw   = yaw;
    projectileDir.Roll  = roll;
    class'NiceProjectileSpawner'.static.SpawnProjectile(Start, projectileDir, shotParams, fireContext, true, bForceComplexTraj);
}
simulated function ClientStickGhostProjectile(KFHumanPawn instigator, Actor base, name bone, Vector shift, Rotator rot,
    NiceBullet.ExplosionData expData, int stuckID){
    class'NiceProjectileSpawner'.static.SpawnStuckProjectile(instigator, base, bone, shift, rot, expData, true,
       stuckID);
}
simulated function SpawnSirenBall(NiceZombieSiren siren){
    if(NicePackMutator == none || siren == none)
       return;
    ClientSpawnSirenBall(siren, NicePackMutator.nextSirenScreamID);
    siren.currentScreamID = NicePackMutator.nextSirenScreamID;
    NicePackMutator.nextSirenScreamID ++;
}
simulated function ClientSpawnSirenBall(NiceZombieSiren siren, int ID){
    if(localCollisionManager == none || siren == none || siren.screamTimings.Length <= 0)
       return;
    localCollisionManager.AddSphereCollision(ID, siren.ScreamRadius * 0.75, siren, Level.TimeSeconds + siren.screamLength * (siren.screamTimings[siren.screamTimings.Length-1] - siren.screamTimings[0]));
}
simulated function ClientRemoveSirenBall(int ID){
    if(localCollisionManager == none)
       return;
    localCollisionManager.RemoveSphereCollision(ID);
}
simulated function ClientNailsExplosion(int amount, Vector start, NiceFire.ShotType shotParams,
    NiceFire.FireModeContext fireContext, optional bool bIsGhost){
    local int i;
    for(i = 0;i < amount;i ++)
       class'NiceProjectileSpawner'.static.SpawnProjectile(start, RotRand(true), shotParams, fireContext, bIsGhost);
}
simulated function AddEffect(){
    effectsSpawned[currentEffectTimeWindow] ++;
}
simulated function bool CanSpawnEffect(bool bIsGhost){
    local int i;
    local int totalEffects;
    local bool surpSoftLimit, surpHardLimit, surpQuota;
    for(i = 0;i < 10;i ++)
       totalEffects += effectsSpawned[i];
    surpQuota       = (0.1 * effectsSpawned[currentEffectTimeWindow]) >= effectsLimitHard;
    surpSoftLimit   = totalEffects > effectsLimitSoft;
    surpHardLimit   = totalEffects > effectsLimitHard;
    if(bIsGhost && (surpQuota || surpSoftLimit))
       return false;
    if(surpHardLimit)
       return false;
    return true;
}
simulated function RegisterStuckBullet(NiceBullet bullet){
    local StuckBulletRecord newRecord;
    if(bullet == none)
       return;
    newRecord.bullet = bullet;
    newRecord.registrationTime = Level.TimeSeconds;
    stuckBulletsSet[stuckBulletsSet.Length] = newRecord;
}
simulated function ExplodeStuckBullet(int id){
    local int i;
    local array<StuckBulletRecord> newSet;
    for(i = 0;i < stuckBulletsSet.Length;i ++)
       if(stuckBulletsSet[i].bullet.stuckID == id)
           stuckBulletsSet[i].bullet.DoExplode(stuckBulletsSet[i].bullet.location,
               stuckBulletsSet[i].bullet.movementDirection);
       else
           newSet[newSet.Length] = stuckBulletsSet[i];
    stuckBulletsSet = newSet;
}
simulated function FreeOldStuckBullets(){
    local int i;
    local array<StuckBulletRecord> newSet;
    if(stuckBulletsSet.Length <= 0)
       return;
    for(i = 0;i < stuckBulletsSet.Length;i ++)
       if( stuckBulletsSet[i].bullet != none &&
           (!stuckBulletsSet[i].bullet.bGhost || stuckBulletsSet[i].registrationTime + 60.0 <= Level.TimeSeconds))
           newSet[newSet.Length] = stuckBulletsSet[i];
       else if(stuckBulletsSet[i].bullet != none)
           stuckBulletsSet[i].bullet.KillBullet();
    stuckBulletsSet = newSet;
}
// Dualies functions
exec function SwitchDualies(){
    local NiceSingle singlePistol;
    local NiceDualies dualPistols;
    if(Pawn != none){
       singlePistol = NiceSingle(Pawn.Weapon);
       dualPistols = NiceDualies(Pawn.Weapon);
    }
    if(singlePistol != none)
       singlePistol.ServerSwitchToDual();
    else if(dualPistols != none)
       dualPistols.ServerSwitchToSingle();
}
exec function SwitchToOtherSingle(){
    local NiceSingle singlePistol;
    if(Pawn != none)
       singlePistol = NiceSingle(Pawn.Weapon);
    if(singlePistol != none)
       singlePistol.ServerSwitchToOtherSingle();
}
exec function FireLeftGun(){
    local NiceDualies dualPistols;
    if(Pawn != none)
       dualPistols = NiceDualies(Pawn.Weapon);
    if(dualPistols != none)
       dualPistols.FireGivenGun(true);
}
exec function FireRightGun(){
    local NiceDualies dualPistols;
    if(Pawn != none)
       dualPistols = NiceDualies(Pawn.Weapon);
    if(dualPistols != none)
       dualPistols.FireGivenGun(false);
}
exec function ActivateAbility(int abilityIndex){
    if(abilityIndex < 0)
       return;
    if(abilityIndex >= abilityManager.currentAbilitiesAmount)
       return;
    switch(abilityManager.currentAbilities[abilityIndex].myState){
    case ASTATE_READY:
       abilityManager.SetAbilityState(abilityIndex, ASTATE_ACTIVE);
       break;
    case ASTATE_ACTIVE:
       abilityManager.SetAbilityState(abilityIndex, ASTATE_READY);
       break;
    }
}
simulated function ClientPrint(){
    if(storageClient == none) return;
    storageClient.Print(self);
}

exec simulated function DoConnect(string S){
    local bool varb;
    if(storageClient == none) return;
    varb = storageClient.ConnectData(S);
}

exec simulated function DoCreate(string S){
    if(storageClient == none) return;
    storageClient.CreateData(S, NSP_HIGH);
}

exec simulated function DoesExist(string S){
    if(storageClient == none) return;
    storageClient.DoesDataExistOnServer(S);
}

exec simulated function DoGetAccess(string S){
    if(storageClient == none) return;
    storageClient.RequestWriteAccess(S);
}

exec simulated function DoFree(string S){
    if(storageClient == none) return;
    storageClient.GiveupWriteAccess(S);
}

exec simulated function DoSet(string dataName, string varName, int varValue){
    if(storageClient == none) return;
    storageClient.GetData(dataName).SetInt(varName, varValue);
}

exec simulated function Siren(float value)
{
    sirenScreamMod = value;
}


// copy-cat to fix MyTrader accessed none log spam
function ClientLocationalVoiceMessage(PlayerReplicationInfo Sender,
                                      PlayerReplicationInfo Recipient,
                                      name MessageType, byte MessageID,
                                      optional Pawn SenderPawn, optional vector SenderLocation)
{
    local VoicePack Voice;
    local ShopVolume Shop;

    if (Sender == none || Sender.VoiceType == none || Player.Console == none || Level.NetMode == NM_DedicatedServer)
    {
        return;
    }

    Voice = Spawn(Sender.VoiceType, self);
    if (KFVoicePack(Voice) != none)
    {
        if (MessageType == 'TRADER')
        {
            if (Pawn != none && MessageID >= 4)
            {
                foreach Pawn.TouchingActors(class'ShopVolume', Shop)
                {
                    // fix this
                    if (Shop.MyTrader != none)
                        SenderLocation = Shop.MyTrader.Location;
                    else
                        SenderLocation = Shop.Location;

                    // Only play the 30 Seconds remaining messages come across as Locational Speech if we're in the Shop
                    if (MessageID == 4)
                    {
                        return;
                    }
                    else if (MessageID == 5)
                    {
                        MessageID = 999;
                    }

                    break;
                }
            }

            // Only play the 10 Seconds remaining message if we are in the Shop
            // and only play the 30 seconds remaning message if we haven't been to the Shop
            if (MessageID == 5 || (MessageID == 4 && bHasHeardTraderWelcomeMessage))
            {
                return;
            }
            else if (MessageID == 999)
            {
                MessageID = 5;
            }

            // Store the fact that we've heard the Trader's Welcome message on the client
            if (MessageID == 7)
            {
                bHasHeardTraderWelcomeMessage = true;
            }
            // If we're hearing the Shop's Closed Message, reset the Trader's Welcome message flag
            else if (MessageID == 6)
            {
                bHasHeardTraderWelcomeMessage = false;
            }

            KFVoicePack(Voice).ClientInitializeLocational(Sender, Recipient, MessageType, MessageID, SenderPawn, SenderLocation);

            if (MessageID > 6 /*&& bBuyMenuIsOpen*/)
            {
                // TODO: Show KFVoicePack(Voice).GetClientParsedMessage() in the Buy Menu
            }
            else if (KFVoicePack(Voice).GetClientParsedMessage() != "")
            {
                // Radio commands print to Text
                TeamMessage(Sender, KFVoicePack(Voice).GetClientParsedMessage(), 'Trader');
            }
        }
        else
        {
            KFVoicePack(Voice).ClientInitializeLocational(Sender, Recipient, MessageType, MessageID, SenderPawn, SenderLocation);

            if (KFVoicePack(Voice).GetClientParsedMessage() != "")
            {
                TeamMessage(Sender, KFVoicePack(Voice).GetClientParsedMessage(), 'Voice');
            }
        }
    }
    else if (Voice != none)
    {
        Voice.ClientInitialize(Sender, Recipient, MessageType, MessageID);
    }
}


defaultproperties
{
    MidGameMenuClass="NicePack.NiceInvasionLoginMenu"
     nicePlayerInfoVersionNumber=1
     bAltSwitchesModes=True
     bAdvReloadCheck=True
     bRelCancelByFire=True
     bRelCancelBySwitching=True
     bRelCancelByNades=True
     bRelCancelByAiming=True
     bNiceWeaponManagement=True
     bDisplayCounters=True
     bDisplayWeaponProgress=True
     bShowScrnMenu=True
     maxPlayedWithRecords=100
     WeapGroupMeleeName="Melee"
     WeapGroupNonMeleeName="NonMelee"
     WeapGroupPistolsName="Pistols"
     WeapGroupGeneralName="General"
     WeapGroupToolsName="Tools"
     WeapPresetDefaultName="Default"
     WeapPresetGunslingerName="Gunslinger"
     tracesPerTickLimit=1000
     effectsLimitSoft=100
     effectsLimitHard=200
     sirenScreamMod=1.000000
     LobbyMenuClassString="NicePack.NiceLobbyMenu"
     PawnClass=class'NiceHumanPawn'
}
