class UltraAdmin extends Admin
  config(AdminPlus_v4);

var APWeaponList APWeaponList;
var xAdminUser CurAdmin;	// Currently logged admin (not thread safe)
var bool bSilent;
var bool bMaster;

var localized string MSG_PMSent;
var localized string MSG_PlayersNotFound;
var localized string MSG_AdminLogin;
var localized string MSG_AdminLoginS;
var localized string MSG_AdminLogout;
var localized string MSG_SetSpeed;
var localized string MSG_NotLobby;
var localized string MSG_ForceReady;
var localized string MSG_Lock;
var localized string MSG_UnLock;
var localized string MSG_DoorRestore;
var localized string MSG_DoorUnweld;
var localized string MSG_DoorIgnore;
var localized string MSG_DoorAttack;
var localized string MSG_DoorWeldOff;
var localized string MSG_DoorWeldOn;
var localized string MSG_Kill;

var float LastKillTime;
var transient int addedFakes;

//var string LogName;
//var string sFileFormat;
//var  FileLog  CurrentLog;
//var transient bool bInitiated;
//var float defaultSpawnRate;
//var float currentSpawnRate;

//TODO: 1. make work with silent login
//      2. remove "pca.admin =" part, and make it in pca's postBeginPlay
var PlayerControllerArray   pca;
var APCleanList             APCleanList;
var APRageList              APRageList;
var APPawnActions           APPawnActions;
var KFGameType              KFGT;

// N.B.
// level.game.accessControl -> Manager
//=========================================================================
function Created()
{
  super.Created();

  /* if(bInitiated)
    return;
  bInitiated = true; */

  KFGT = KFGameType(level.game);
  if(KFGT == none)
    log("Admin Plus: YOU FAILED FAGGOT! KFGameType is not found!");
  if(APCleanList == none)
    APCleanList = spawn(class'APCleanList',outer);
  if(APRageList == none)
    APRageList = spawn(class'APRageList');
  if(APPawnActions == none)
    APPawnActions = spawn(class'APPawnActions');
  /*   if(CurrentLog != none)
    return;
  CurrentLog = Spawn(class'FileLog');
  LogName = LogFilename();
  CurrentLog.OpenLog(LogName);
  log("AdminPlus: Log File is created!"); */
}

//=========================================================================
// handle safe destroy and servertravel
event Destroyed()
{
	DestroyMyObjects();
	super.Destroyed();
}

// remove all refs and wipe spawned actors
function DestroyMyObjects()
{
	if(pca != none)
	{
		pca.Destroy();
		pca = none;
  }
  if(APCleanList != none)
	{
		APCleanList.Destroy();
		APCleanList = none;
  }
  if(APRageList != none)
	{
		APRageList.Destroy();
		APRageList = none;
  }
  if(APPawnActions != none)
	{
		APPawnActions.Destroy();
		APPawnActions = none;
  }
}

//=========================================================================
// modify Login, Logout messages, add TempAdmin feature
function DoLogin(string Username, string Password)
{
  if(Manager.AdminLogin(outer, username, password))
  {
    bSilent = class'Helper'.static.CheckSilent(outer,self);
    if(!bSilent)
      BroadcastText(GetPlayerName(outer)$MSG_AdminLogin);
    else
      BroadcastText(GetPlayerName(outer)$MSG_AdminLoginS);
    bAdmin = true;
    pca = spawn(class'PlayerControllerArray', outer);
    pca.admin = KFPlayerController(outer);
    //CurrentLog.Logf(GetPlayerName(outer)$" logged in as Evil Admin.");
  }

  if(outer.playerReplicationInfo.bAdmin == true && !Manager.AdminLogin(outer, username, password))
  {
    Manager.AdminLogout(Outer);
    Manager.AdminLogin(outer, "ut2004", Manager.Users.Get(0).Password);
    bAdmin = true;
    pca = spawn(class'PlayerControllerArray', outer);
    pca.admin = KFPlayerController(outer);
    BroadcastText(GetPlayerName(outer)$" logged in as %rFake Admin.");
  }
}

function doLoginSilent(string username, string password)
{
  local bool bWasSilent;

  bWasSilent = Outer.PlayerReplicationInfo.bSilentAdmin;

  if (!bWasSilent && Manager.adminLoginSilent(outer, username, password))
  {
    // bAdmin = true;
    SendMessage(outer,"%wYou silently logged in as a server administrator. No one noticed :p");
    log("Admin Plus: "$GetPlayerName(outer)$" logged in silently.");
  }
}

function DoLogout()
{
  local bool bWasSilent;

  bWasSilent = Outer.PlayerReplicationInfo.bSilentAdmin;

  if(Manager.AdminLogout(outer))
  {
    pca.destroy();
    bAdmin = false;
    if(bWasSilent)
    {
      bWasSilent = false;
      SendMessage(outer,"%wYou silently gave up administrator abilities.");
      log("Admin Plus: "$GetPlayerName(outer)@ "logged in silently.",);
      return;
    }
    BroadcastText(GetPlayerName(outer)$MSG_AdminLogout);
  }
}

//=========================================================================
exec function SloMo(float Speed)
{
  Speed = fClamp(Speed, 0.1, 7.0);
  BroadcastText(GetPlayerName(outer)$MSG_SetSpeed$Speed);
  level.game.SetGameSpeed(Speed);
}

//=========================================================================
exec function SetGravity(float F)
{
  local PhysicsVolume  PV;
  local vector XYDir;
  local float ZDiff, Time;
  local JumpPad J;
  local NavigationPoint N;
  local name VolumeTag;

  foreach AllActors(class'PhysicsVolume', PV, VolumeTag)
  {
    PV.Gravity.Z = F;
    PV.NetUpdateTime = Level.TimeSeconds - 1;
    PV.bAlwaysRelevant = true;

    if ( PV.IsA('DefaultPhysicsVolume') )
      Level.DefaultGravity = PV.Gravity.Z;

    for ( N=Level.NavigationPointList; N!=none; N=N.NextNavigationPoint )
      if ( N.IsA('JumpPad') && PV.Encompasses( N ) )
      {
        J = JumpPad(N);
        if ( J != none )
        {
          XYDir = J.JumpTarget.Location - J.Location;
          ZDiff = XYDir.Z;
          Time = 2.5f * J.JumpZModifier * Sqrt(Abs(ZDiff/F));
          J.JumpVelocity = XYDir/Time; 
          J.JumpVelocity.Z = ZDiff/Time - 0.5f * F * Time;
        }
      }
  }

  BroadcastText(GetPlayerName(outer)$" changed game gravity to -%b "$F);
}

exec function SG(float gr)
{
  SetGravity(gr);
}

//=========================================================================
// make Target Invisible
exec function InvisOn(string target)
{
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(pca.nextPlayer())
        pca.getCurrentPlayer().pawn.bHidden = true;

      BroadcastText(GetPlayerName(outer)$" forced %b"$pca.getNames()$"%w to be %rinvisible");
    }
  }
  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

// make Target visible
exec function InvisOff(string target)
{
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(pca.nextPlayer())
        pca.getCurrentPlayer().pawn.bHidden = false;

      BroadcastText(GetPlayerName(outer)$" forced "$pca.getNames()$" to be %rvisible");
    }
  }
  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//=========================================================================
exec function Summon(string ClassName)
{
  local class<actor> NewClass;
  local vector SpawnLoc;

  NewClass = class<actor>(DynamicLoadObject(ClassName, class'Class'));
  if(NewClass != none)
  {
    if(pawn != none)
      SpawnLoc = pawn.Location;
    else
      SpawnLoc = Location;
      
    Spawn( NewClass,,,SpawnLoc + 72 * Vector(Rotation) + vect(0,0,1) * 15 );
  }
}

//=========================================================================
exec function AdvancedSummon(string ClassName,string target)
{
  local class<actor> NewClass;
  local vector SpawnLoc;

  NewClass = class<actor>(DynamicLoadObject(ClassName,class'Class'));
  if(NewClass == none)
  {
    SendMessage(outer,"%wCan't find class: %b"$className);
    return;
  }

  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(pca.nextPlayer())
      {
        SpawnLoc = pca.getCurrentPlayer().pawn.Location;
        Spawn( NewClass,,,SpawnLoc + 72 * Vector(Rotation) + vect(0,0,1) * 15 );
      }
    }

    else
    {
      while(pca.nextPlayer())
      {
        SpawnLoc = Location;
        Spawn( NewClass,,,SpawnLoc + 72 * Vector(Rotation) + vect(0,0,1) * 15 );
      }
    }

    BroadcastText(GetPlayerName(outer)$" summoned %r"$pca.getNames()$"!");
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//=========================================================================
exec function Teleport()
{
  local actor HitActor;
  local vector HitNormal, HitLocation;

  HitActor = Trace(HitLocation, HitNormal, ViewTarget.Location + 10000 * vector(Rotation),ViewTarget.Location, true);

  if(HitActor == none)
    HitLocation = ViewTarget.Location + 10000 * vector(Rotation);

  else
    HitLocation = HitLocation + ViewTarget.CollisionRadius * HitNormal;

  ViewTarget.SetLocation(HitLocation);
  ViewTarget.PlayTeleportEffect(false,true);
}

exec function tp()
{
  Teleport();
}

//=========================================================================
exec function GiveItem(coerce string ItemName, coerce string target)
{
  local pawn player;
  local array<string> Items;
  local int i;
  //local string ItemOnly;

  bSilent = class'Helper'.static.CheckSilent(outer,self);

  split(ItemName, ",", Items);

  //ItemOnly = Repl(ItemName,Left(ItemName,Instr(ItemName,".")+1),"");

  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(pca.nextPlayer())
      {
        player = pca.getCurrentPlayer().pawn;

        for(i = Items.length - 1; i >= 0 ; i--)
        {
          //skip void text
          if(Items[i] == "")
            continue;
          player.GiveWeapon(Items[i]);
          player.PlayTeleportEffect(true, true);
          AllAmmo(pca.getCurrentPlayer());

          if (KFGT.bWaveInProgress && !bSilent)
          {
            pca.getCurrentPlayer().playerReplicationInfo.Kills = 0;
            KFPlayerReplicationInfo(pca.getCurrentPlayer().PlayerReplicationInfo).KillAssists = 0;
          }
          
          //added so cheating fags don't get away with refilling their ammo during the wave
          if (KFGT.bWaveInProgress) 
            BroadcastText(GetPlayerName(outer)$" gifted %r"$Items[i]$"%w to "$pca.getNames());
          else
            SendMessage(pca.getCurrentPlayer(),"%wYou have been given the gift of: %r"$Items[i]);
          
          log("Admin Plus: "$GetPlayerName(outer)@ "gifted " $Items[i]$ "to " $pca.getNames()$"!");
        }
      }      
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

exec function GI(coerce string ItemName,coerce string target)
{
  GiveItem(ItemName, target);
}

//=========================================================================
exec function giveInv(string className, string target)
{
  local Inventory newInv, tempInv;
  local class<Inventory> newInvClass;
  local string itemName;

  newInvClass = class<Inventory>(dynamicLoadObject(className, class'Class'));
  if(newInvClass == none)
  {
    SendMessage(outer, "%wCan't find class: %b"$className);
    return;
  }

  if(pca.findPlayers(target)) // 'findPlayers' returns true if at least 1 player was found
  {
    if(pca.applyFilter("hasPawn")) // applies filter to found players, return same as above (also filter can be "isInGame" and "isSpectator")
    {
      while(pca.nextPlayer()) //player1 (true) -> player2 (true) -> player3 (true) -> none (false)
      {
        newInv = pca.getCurrentPlayer().spawn(newInvClass, pca.getCurrentPlayer());

        if(itemName == "")
          itemName = newInv.getHumanReadableName();

        if(newInv != none)
        {
          for(tempInv = pca.getCurrentPlayer().pawn.inventory; tempInv != none; tempInv = tempInv.inventory)
            if(tempInv.class == newInv.class)
            {
              SendMessage(outer, pca.getCurrentPlayerName()$"%w already has this item");
              newInv.destroy();
              continue;
            }

          if(newInv == none)
            continue;

          newInv.giveTo(pca.getCurrentPlayer().pawn);
          SendMessage(outer, "%b"$newInv.class$"%w was given to %b"$pca.getCurrentPlayerName());
          SendMessage(pca.getCurrentPlayer(), "%b"$itemName$"%w was given to you.");
        }
        else
          SendMessage(outer, "%b"$className$"%w was found, but can't be spawned for some reason");
      }

      if(pca.isPluralForm())
        BroadcastText(pca.getNames()$"%w were given \""$itemName$"\".");
      else
        BroadcastText(pca.getNames()$"%w was given \""$itemName$"\".");
    }
    else
      SendMessage(outer, "%wNo players were found");
  }
  else
    SendMessage(outer, "%wNo players were found");

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//=========================================================================
exec function Loaded(string target, int listName)
{
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(pca.nextPlayer())
      {
        AllWeapons(pca.getCurrentPlayer(),listName);
        AllAmmo(pca.getCurrentPlayer());
        pca.getCurrentPlayer().pawn.PlayTeleportEffect(true, true);
      }

      BroadcastText(GetPlayerName(outer)$" loaded "$pca.getNames()$" with tons of weapons!");
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//=========================================================================
function AllAmmo(PlayerController pc)
{
  local Inventory Inv;
  local KFHumanPawn PlayerPawn;
  local KFAmmunition AmmoToUpdate;
  local KFWeapon WeaponToFill;
  local KFPlayerReplicationInfo KFPRI;
  local class<KFVeterancyTypes> PlayerVeterancy;

  PlayerPawn = KFHumanPawn(PC.Pawn);
  if(PlayerPawn == none)
    return;

  KFPRI = KFPlayerReplicationInfo(PlayerPawn.PlayerReplicationInfo);
  if(KFPRI != none)
    PlayerVeterancy = KFPRI.ClientVeteranSkill;

  for(Inv = PlayerPawn.Inventory; Inv != none; Inv = Inv.Inventory)
  {
    WeaponToFill = KFWeapon(Inv);
    if(WeaponToFill != none)
      WeaponToFill.MagAmmoRemaining = WeaponToFill.MagCapacity;
    AmmoToUpdate = KFAmmunition(Inv);
    if(AmmoToUpdate != none && AmmoToUpdate.AmmoAmount < AmmoToUpdate.MaxAmmo)
    {
      if(PlayerVeterancy != none)
      {
        AmmoToUpdate.MaxAmmo = AmmoToUpdate.default.MaxAmmo;
        AmmoToUpdate.MaxAmmo = float(AmmoToUpdate.MaxAmmo) * PlayerVeterancy.static.AddExtraAmmoFor(KFPRI, AmmoToUpdate.class);
      }

      AmmoToUpdate.AmmoAmount = AmmoToUpdate.MaxAmmo;
    }
  }
  return;
}

function AllWeapons(PlayerController pc, coerce string listName)
{
  local KFHumanPawn PlayerPawn;
  local int i;

  PlayerPawn = KFHumanPawn(PC.Pawn);
  if(PlayerPawn == none)
    return;

  APWeaponList = new(none, listName) class'APWeaponList';

  //give default weapon set, just in case it's fucked up
  for(i = 0; i < APWeaponList.DefaultEquip.length; i++)
  {
    PlayerPawn.GiveWeapon(APWeaponList.DefaultEquip[i]);
  }

  if(APWeaponList.Weapons.length==0)
  {
    log("APWeaponList "$listName$" not found!!");
    return;
  }

  for (i = 0; i < APWeaponList.Weapons.length; i++)
  {
    PlayerPawn.GiveWeapon(APWeaponList.Weapons[i]);
  }
  APWeaponList = none;
}

//=========================================================================
exec function Ammo(string target)
{
  bSilent = class'Helper'.static.CheckSilent(outer,self);
  
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(pca.nextPlayer())
        AllAmmo(pca.getCurrentPlayer());

        if (KFGT.bWaveInProgress && !bSilent)
        {
          pca.getCurrentPlayer().playerReplicationInfo.Kills = 0;
          KFPlayerReplicationInfo(pca.getCurrentPlayer().PlayerReplicationInfo).KillAssists = 0;
        }        
      BroadcastText(GetPlayerName(outer)$" refilled ammo for %b"$pca.getNames());
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//=========================================================================
// let's keep this, just in case
exec function CauseEvent(name EventName)
{
  TriggerEvent(EventName, pawn, pawn);
}

//=========================================================================
//Door related stuff. Lets you to unweld all doors in map or restore them.
exec function Door(string srtState)
{
  local KFDoorMover KFDM;

  switch(srtState)
  {
    case "Unweld":
      foreach DynamicActors(class'KFDoorMover', KFDM)
        KFDM.MyTrigger.UnWeld(KFDM.WeldStrength,false,pawn);
      BroadcastText(GetPlayerName(outer)$MSG_DoorUnweld);
      break;
    case "Ignore":
      foreach DynamicActors(class'KFDoorMover', KFDM)
        KFDM.bZombiesIgnore=true;
      BroadcastText(GetPlayerName(outer)$MSG_DoorIgnore);
        break;
    case "Attack":
      foreach DynamicActors(class'KFDoorMover', KFDM)
        KFDM.bZombiesIgnore=false;
      BroadcastText(GetPlayerName(outer)$MSG_DoorAttack);
        break;
    case "WeldOff":
      foreach DynamicActors(class'KFDoorMover', KFDM)
        KFDM.bNoSeal=true;
      BroadcastText(GetPlayerName(outer)$MSG_DoorWeldOff);
        break;
    case "WeldOn":
      foreach DynamicActors(class'KFDoorMover', KFDM)
        KFDM.bNoSeal=false;
      BroadcastText(GetPlayerName(outer)$MSG_DoorWeldOn);
        break;
    default:
      foreach DynamicActors(class'KFDoorMover', KFDM)
        KFDM.RespawnDoor();
      BroadcastText(GetPlayerName(outer)$MSG_DoorRestore);
  }
}

exec function ZV(string s, optional int i)
{
  local ZombieVolume ZV;

  switch(s)
  {
    case "MDTP":
      i = Clamp(i,1,9999);
	    foreach AllActors(Class'ZombieVolume', ZV)
        ZV.MinDistanceToPlayer = i;
      BroadcastText(GetPlayerName(outer)$"%w set MDTP to - %b" $ i);
      break;
    case "Plain":
      foreach AllActors(Class'ZombieVolume', ZV)
        if (i == 1) //pseudo-bool, idk
          ZV.bAllowPlainSightSpawns=true;
        else
          ZV.bAllowPlainSightSpawns=false;
      if (i == 1)
        BroadcastText(GetPlayerName(outer)$" %renabled %wplain sight spawning!");
      else 
        BroadcastText(GetPlayerName(outer)$" %rdisabled %wplain sight spawning!");
     break;
    case "Debug":
      foreach AllActors(Class'ZombieVolume', ZV)
        if (i == 1) //pseudo-bool, idk
          ZV.bDebugZombieSpawning=true;
        else
          ZV.bDebugZombieSpawning=false;
      if (i == 1)
        BroadcastText(GetPlayerName(outer)$" %benabled %wspawn debugging!");
      else
        BroadcastText(GetPlayerName(outer)$" %rdisabled %wspawn debugging!");
     break;
  }
}

//============================= WAVE RELATED ===============================
// skips waves
exec function Skip()
{
  //we are killing zeds in case of some fags spawn them with commands
  MowZeds();

  if(!KFGT.bWaveInProgress && KFGT.waveNum <= KFGT.finalWave)
  {
     KFGT.waveCountDown = 1;
     if(InvasionGameReplicationInfo(KFGT.GameReplicationInfo) != None)
				InvasionGameReplicationInfo(KFGT.GameReplicationInfo).waveNumber = KFGT.waveNum;
     BroadcastText(GetPlayerName(outer)$" has started %bWave" @ KFGT.waveNum + 1);
    return;
  }

  else if(KFGT.bWaveInProgress && (KFGT.TotalMaxMonsters > 0 || KFGT.NumMonsters > 0))
  {
    ResetZeds();
    BroadcastText(GetPlayerName(outer)$" has ended %bWave"@ KFGT.waveNum + 1);
  }
}

// Sets wave number.
exec function Wave(int WaveNum)
{
  WaveNum = clamp(WaveNum,0,KFGT.finalWave+1);
  if(InvasionGameReplicationInfo(KFGT.GameReplicationInfo) != None)
		InvasionGameReplicationInfo(KFGT.GameReplicationInfo).waveNumber = WaveNum-1;

  MowZeds();
  if(KFGT.bWaveInProgress)
  {
    KFGT.WaveNum = WaveNum-2;
    ResetZeds();
  }
  else
    KFGT.WaveNum = WaveNum-1;

  BroadcastText(GetPlayerName(outer)$" switched to %bWave" @WaveNum);
}

function ResetZeds()
{
  KFGT.numMonsters = 0;
  KFGT.totalMaxMonsters = 0;
}

//=============================================
// Huge thanks to dkanus <3
exec function Kill()
{
  if(Level.TimeSeconds < LastKillTime)
  {
    return;
  }
  LastKillTime = Level.TimeSeconds + 1.5f;
  MowZeds();
  BroadcastText(GetPlayerName(outer)$MSG_Kill);
}

function MowZeds()
{
  local KFMonster M;
  local array<KFMonster> Monsters;
  local int i;

  foreach DynamicActors(class 'KFMonster', M)
  {
    if(M.Health > 0 && !M.bDeleteMe)
      Monsters[Monsters.length] = M;
  }

  for(i=0; i<Monsters.length; ++i)
  {
    if(Monsters[i] == none)
      continue;
    Monsters[i].Suicide();
  }
    // Monsters[i].Died(Monsters[i].Controller, class'DamageType', Monsters[i].Location);
}

//=========================================================================
/* exec function SpawnRate(float rate)
{
  local KFGameType GT;
  local float temp;
  
  GT = KFGameType(level.game);

  if (GT == none)
    return;
  else{
    GT.KFLRules.WaveSpawnPeriod = rate;
    BroadcastText("Evil admin changed spawn rate changed to"@String(rate) @ ", default" @String(DefSpawnRate()));
  }
  return;
}

function float DefSpawnRate()
{
  local KFGameType GT;
  local float OriginalWaveSpawnPeriod;
  
  GT = KFGameType(level.game);  
  GT.KFLRules.WaveSpawnPeriod = OriginalWaveSpawnPeriod;
  return OriginalWaveSpawnPeriod;
} */

//=========================================================================
function int RealPlayers()
{
  local Controller c;
  local int realPlayersCount;

  for(c = Level.ControllerList; c != none; c = c.NextController)
  {
    if(c.IsA('PlayerController') && c.PlayerReplicationInfo != none && !c.PlayerReplicationInfo.bOnlySpectator)
    {
      realPlayersCount++;
    }
  }

  return realPlayersCount;
}

exec function Faked(int numFaked)
{
  addedFakes = Clamp(numFaked, 0, 99);

  if(RealPlayers() > 0)
    KFGT.NumPlayers = RealPlayers() + addedFakes;
  else
    KFGT.NumPlayers = addedFakes;

  BroadcastText(GetPlayerName(outer)$" added %b"$addedFakes$"%w fakes!");
}

//================================================
// lock the team - shrink player slots to the current players value.
exec function Lock(string srtState)
{
  switch(srtState)
  {
    case "off":
      KFGT.MaxPlayers = 6;
      BroadcastText(GetPlayerName(outer)$MSG_UnLock$KFGT.NumPlayers$"/"$KFGT.MaxPlayers);
      break;
    default:
      KFGT.MaxPlayers = RealPlayers() + addedFakes;
      BroadcastText(GetPlayerName(outer)$MSG_Lock$KFGT.NumPlayers$"/"$KFGT.MaxPlayers);
  }
}

//=========================================================================
//similar to level cleanup in testmaps
exec function Clean(string trash)
{
  if(APCleanList != none)
    APCleanList.Clean(outer,self,trash);
}

//================================================
// change player name
exec function ChangeName(string target,string NewName)
{
  if(pca.findPlayers(target))
  {
    while(pca.nextPlayer())
      pca.getCurrentPlayer().playerReplicationInfo.PlayerName = NewName;

    BroadcastText(GetPlayerName(outer)$" changed %b"$pca.getNames()$"'s name to %b"$NewName);
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

exec function CN(string target,string NewName)
{
  ChangeName(target,NewName);
}

//================================================
//Slap that guys bothering you and do 20 point of damage from him
exec function Slap(string target, int SlapDamage)
{
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      SlapDamage  = Clamp(SlapDamage, 0, 500);

      while(pca.nextPlayer())
      {
        pca.getCurrentPlayer().pawn.TakeDamage(SlapDamage,pca.getCurrentPlayer().pawn,Vect(100000,100000,100000),Vect(100000,100000,100000),class'DamageType');
        pca.getCurrentPlayer().pawn.PlayTeleportEffect(true, true);
      }

      BroadcastText(GetPlayerName(outer)$" slapped %b"$pca.getNames()$" like a bitch!");
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//================================================
//Huge thanks to [MLG]N@GIBAT0R2004 for respawn functions and help <3
exec function respawn(string target, optional bool bRespawnSpectator)
{
  if( KFGT.IsInState('PendingMatch'))
  //if(KFGT.IsInState('MatchOver') || KFGT.IsInState('PendingMatch'))
  {
    SendMessage(outer,"%wMatch is over / not started. Cant execute.");
    return;
  }

  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("all"))
    {
      while(pca.nextPlayer())
      {
        if(bRespawnSpectator) // && pca.getCurrentPlayer().PlayerReplicationInfo.bOnlySpectator)
          respawnPlayer(pca.getCurrentPlayer());
        if(!bRespawnSpectator && !pca.getCurrentPlayer().PlayerReplicationInfo.bOnlySpectator)
          respawnPlayer(pca.getCurrentPlayer());
      }
    }
  }
}

//================================================
function respawnPlayer(PlayerController pc)
{
  local NavigationPoint startSpot;
  local int teamNum;
  local class<pawn> DefaultPlayerClass;

  if(pc.pawn != none)
    return;

  //protection in case a+ is used in solo game (delete it?)
  if(level.game.bRestartLevel && level.netMode != NM_DedicatedServer && level.netMode != NM_ListenServer)
    return;

  if(pc.isInState('Spectating'))
    pc.becomeActivePlayer();

  if(pc.isInState('Dead') ||
  pc.isInState('GameEnded') ||
  pc.isInState('RoundEnded') ||
  pc.isInState('PlayerWaiting'))
    pc.gotoState('WaitingForPawn');

  pc.setViewTarget(pc);
  pc.clientSetBehindView(false);
  pc.bBehindView = false;

  if ((pc.playerReplicationInfo == none) || (pc.playerReplicationInfo.Team == none))
    teamNum = 255;
  else
    teamNum = pc.playerReplicationInfo.team.teamIndex;

  startSpot = level.game.findPlayerStart(pc, teamNum);
  if(startSpot == none)
  {
    log("Admin Plus: Respawn attemt failed. Player start not found.");
    return;
  }

  if(pc.previousPawnClass != none && pc.pawnClass != pc.previousPawnClass)
    level.game.baseMutator.playerChangedClass(pc);

  if(pc.pawnClass != none )
    pc.pawn = level.game.spawn(pc.pawnClass, , , startSpot.location, startSpot.rotation);

  if(pc.pawn == none)
  {
    defaultPlayerClass = level.game.getDefaultPlayerClass(pc);
    pc.pawn = level.game.spawn(defaultPlayerClass, , , startSpot.location, startSpot.rotation);
  }

  if(pc.pawn == none )
  {
    log("Admin Plus: Couldn't spawn player of type"@pc.pawnClass@"at"@startSpot);
    pc.gotoState('Dead');
    pc.clientGotoState('Dead','Begin');
    return;
  }

  pc.timeMargin = -0.1;
  pc.pawn.anchor = startSpot;
  pc.pawn.laststartSpot = PlayerStart(startSpot);
  pc.pawn.lastStartTime = level.timeSeconds;
  pc.previousPawnClass = pc.pawn.class;

  pc.possess(pc.pawn);
  pc.pawnClass = pc.pawn.Class;

  pc.pawn.playTeleportEffect(true, true);
  pc.clientSetRotation(pc.pawn.rotation);
  pc.ClientSetViewTarget(pc.Pawn);
  level.game.addDefaultInventory(pc.pawn);
  level.game.triggerEvent(startSpot.event, startSpot, pc.pawn);

  KFHumanPawn(pc.pawn).veterancyChanged();

  if(KFGT != none)
    if(KFGT.bTradingDoorsOpen)
      pc.pawn.bBlockActors = false;

  pc.playerReplicationInfo.bOutOfLives = false;

  //remove kills to show how filthy are respawned players
  if (KFGT.bWaveInProgress && !bSilent)
  {
    pc.playerReplicationInfo.Kills = 0;
    KFPlayerReplicationInfo(PC.PlayerReplicationInfo).KillAssists = 0;
  }

  BroadcastText("%b"$GetPlayerName(outer)$"%w respawned %b"$pc.playerReplicationInfo.playerName);
  log("Admin Plus: "$GetPlayerName(outer)$" respawned"@GetPlayerName(pc));
}

//=========================================================================
exec function getInfo()
{
  local Controller c;

  for(c = level.controllerList; c != none; c = c.nextController)
    if(c.isA('PlayerController'))
    {
      SendMessage(outer, GetPlayerName(PlayerController(c))$": current state %r"$PlayerController(c).GetStateName());
    }
}

exec function findPlayers(string players, string filter)
{
  local int i;
  local bool bTemp, bTemp2;

  bTemp = pca.findPlayers(players);

  BroadcastText("%w============================================");
  BroadcastText("%wFollowing string was used for query: %b"$players);
  BroadcastText("%w----BEFORE FILTER----");
  BroadcastText("%wAt least 1 player found: %b"$bTemp);
  BroadcastText("%wReturn names: %b"$pca.pcArrayNames);
  BroadcastText("%wIs it plural form: %b"$pca.isPluralForm());
  BroadcastText("%wPlayers in array: %b"$pca.playerControllersArray.length);
  while(pca.nextPlayer())
  {
    BroadcastText(i@"%w, Player:%b"@pca.getCurrentPlayer());
    i++;
  }

  i = 0;
  bTemp2 = pca.applyFilter(filter);
  BroadcastText("%w----AFTER FILTER----[ %b"$filter$"%w ]");
  BroadcastText("%wAt least 1 player found: %b"$bTemp2);
  BroadcastText("%wReturn names: %b"$pca.pcArrayNames);
  BroadcastText("%wIs it plural form: %b"$pca.isPluralForm());
  BroadcastText("%wPlayers in array: %b"$pca.playerControllersArray.length);
  while(pca.nextPlayer())
  {
    BroadcastText(i@"%w, Player:%b"@pca.getCurrentPlayer());
    i++;
  }
  BroadcastText("%w============================================");
}

//=========================================================================
// spawn zeds without classname copy-paste
exec function Zed(string szedname, optional string eventType)
{
  local class<KFMonster> newZed;
  local vector spawnLocation;
  
  // print help list for this cmd
  if(szedname == "" && eventType == "")
  {
    class'Helper'.static.TellAbout(outer, Self,"MsgZeds");
    return;
  }

  newZed = class'APZedList'.static.getZedClass(szedname, eventType);

  if(newZed != none)
  {
    if (pawn != none)
      spawnLocation = pawn.location;
    else
      spawnLocation = location;
    spawn(newZed, , , spawnLocation + 72 * Vector(rotation) + vect(0,0,1) * 15);
  }
}

//=========================================================================
// Change a Player's dosh amount.
exec function Dosh(string target, int Dosh)
{
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("isInGame"))
    {
      while(pca.nextPlayer())
        pca.getCurrentPlayer().playerReplicationInfo.Score = Dosh;

      BroadcastText(GetPlayerName(outer)$" changed "$pca.getNames()$"'s dosh to - %b"@Dosh);
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

exec function ResetDosh(string target)
{
  Dosh(target, 0);
}

//=========================================================================
// Put Target In Walk Mode
exec function Walk(string target)
{
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(APPawnActions != none && pca.nextPlayer())
      {
        APPawnActions.EditPawn(pca.getCurrentPlayer().pawn,"");
      }
      BroadcastText(GetPlayerName(outer)$" forced "$pca.getNames()$" to %gWalk");
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//=========================================================================
//Put Target In Ghost Mode
exec function Ghost(string target)
{
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(APPawnActions != none && pca.nextPlayer())
      {
        APPawnActions.EditPawn(pca.getCurrentPlayer().pawn,"ghost");
      }
      BroadcastText(GetPlayerName(outer)$" forced "$pca.getNames()$" to %gNoClip");
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//=========================================================================
//Put Target In Fly Mode
exec function Fly(string target)
{
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(APPawnActions != none && pca.nextPlayer())
      {
        APPawnActions.EditPawn(pca.getCurrentPlayer().pawn,"fly");
      }
      BroadcastText(GetPlayerName(outer)$" forced %b"$pca.getNames()$"%w to %gFly");
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//================================================
//Put Target In Spider Mode
exec function Spider(string target)
{
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(APPawnActions != none && pca.nextPlayer())
      {
        APPawnActions.EditPawn(pca.getCurrentPlayer().pawn,"spider");
      }
      BroadcastText(GetPlayerName(outer)$" forced "$pca.getNames()$" to move like a %gSpider");
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//=========================================================================
//Put Target In God Mode
exec function God(string target)
{
  bSilent = class'Helper'.static.CheckSilent(outer,self);
  
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("isInGame"))
    {
      while(pca.nextPlayer())
      {
        pca.getCurrentPlayer().bGodMode = true;
        //boost kills to a ludicrious amount to contiminate fags' fabricated kills
        //e.g fags using zerk + godemode + slomo 10. yeah, go fuck yourself Kaio
        if (!bSilent)
        {
          pca.getCurrentPlayer().playerReplicationInfo.Kills = 9999;
          KFPlayerReplicationInfo(pca.getCurrentPlayer().PlayerReplicationInfo).KillAssists = 9999;
        }
      }
      BroadcastText(GetPlayerName(outer)$" %renabled %ggod%w mode for "$pca.getNames());
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//disable God Mode
exec function GodOff(string target)
{
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("isInGame"))
    {
      while(pca.nextPlayer())
      {
        if(pca.getCurrentPlayer().bGodMode)
          pca.getCurrentPlayer().bGodMode = false;
        //reset kills if fags decide to play legit
        if (!bSilent)
        {
          pca.getCurrentPlayer().playerReplicationInfo.Kills = 0;
          KFPlayerReplicationInfo(pca.getCurrentPlayer().PlayerReplicationInfo).KillAssists = 0;
        }
      }

      BroadcastText(GetPlayerName(outer)$"%r disabled %ggod%w mode for "$pca.getNames());
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//Kill and asssists command
//This shit is too powerful and only I and little BitchC- should be able to use it if at all
//Useful for when retards accidently reset other players' kills
exec function Score(string target, int Kills, int Assists)
{
  bMaster = class'Helper'.static.CheckMaster(outer,self);

  if (!bMaster)
    return;
    
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("isInGame"))
    {
      while(pca.nextPlayer())
      {
        if (Kills < 1000000)
          pca.getCurrentPlayer().playerReplicationInfo.Kills = Kills;
        if (Assists < 1000000)
          KFPlayerReplicationInfo(pca.getCurrentPlayer().PlayerReplicationInfo).KillAssists = Assists;
      }
      
      BroadcastText(GetPlayerName(outer)$" changed "$pca.getNames()$"'s Kills to - %b"@Kills);
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//=========================================================================
//send Private Messages to players
exec function PrivMessage(string target, string Msg)
{
  if(pca.findPlayers(target))
  {
    while(pca.nextPlayer())
      SendMessage(pca.getCurrentPlayer(),"%wPrivate Message from "$GetPlayerName(outer)$": "$Msg);

    SendMessage(outer,MSG_PMSent$pca.getNames());
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

exec function PM (string target, string Msg)
{
  PrivMessage(target,Msg);
}

//=========================================================================
// change Player's Size
exec function PlayerSize(string target,float nPlayerSize)
{
  local pawn player;

  nPlayerSize = FClamp(nPlayerSize, 0.1, 5.0);

  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(pca.nextPlayer())
      {
        player = pca.getCurrentPlayer().pawn;
        // set default scale before any other stuff
        player.SetDrawScale(nPlayerSize);
        player.SetCollisionSize(20.0 * nPlayerSize, 50.0 * nPlayerSize);
        player.BaseEyeHeight = 44.0 * nPlayerSize;
        player.EyeHeight = 44.0 * nPlayerSize;
        player.CrouchHeight = 34.0 * nPlayerSize;
        player.PrePivot.Z += (player.CollisionHeight - 50)*1.22;
        player.NetUpdateTime = Level.TimeSeconds - 1;
        player.PlayTeleportEffect(true, true);
      }

      BroadcastText(GetPlayerName(outer)$" changed "$pca.getNames()$"'s body size to %b"$nPlayerSize);
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

exec function PS(string target,float nPlayerSize)
{
  PlayerSize(target,nPlayerSize);
}

//=========================================================================
//Change Player's Head Size
exec function HeadSize(string target,float size)
{
  local pawn player;

  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(pca.nextPlayer())
      {
        player = pca.getCurrentPlayer().pawn;

        player.headscale = size;
        player.PlayTeleportEffect(true, true);
      }

      BroadcastText(GetPlayerName(outer)$" changed "$pca.getNames()$"'s head size to %b"$size);
    }
  }

  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

exec function HS(string target,float nHeadSize)
{
  HeadSize(target,nHeadSize);
}

//=========================================================================
// change maximum amount of zeds that can be spawned at once
exec function MaxZeds(int numZeds)
{
  local int i;
  local KFMonster Monster;

  switch(numZeds)
  {
    case 0:
      foreach DynamicActors(class 'KFMonster', Monster)
      {
        if(Monster.Health > 0)
          i++;
      }
      BroadcastText("%wCurrent spawned zeds - %b"$i$"%w, current MaxZeds -%b" @KFGT.MaxZombiesOnce);
      break;
    default:
      KFGT.MaxZombiesOnce = Clamp(numZeds, 32, 300);
      BroadcastText(GetPlayerName(outer)$" changed MaxZeds to %b"$KFGT.MaxZombiesOnce);
  }  
}

exec function MZ(int numZeds)
{
  MaxZeds(numZeds);
}

//=========================================================================
// change trader time. Consider this as a better pause
exec function Trader(int Time, optional bool bsave)
{
  switch(Time)
  {
    case 0:
      SendMessage(outer,"%wCurrent Trader time - %b"$KFGT.TimeBetweenWaves);
      break;
    default:
      Time = Clamp(Time, 10, 600);
      KFGT.WaveCountDown = Time;
      if(bsave)
        KFGT.TimeBetweenWaves = Time;
      BroadcastText(GetPlayerName(outer)$" changed Trader time to - %b"$Time$"%w [Saved - %r"$bsave$"%w]");
  }
}

//=========================================================================
// force players to press ready
exec function Ready()
{
  if(!KFGT.IsInState('PendingMatch'))
  {
    SendMessage(outer,MSG_NotLobby);
    return;
  }

  if(pca.findPlayers("all"))
    if(pca.applyFilter("isInGame"))
    {
      while(pca.nextPlayer())
        pca.getCurrentPlayer().PlayerReplicationInfo.bReadyToPlay = true;

      BroadcastText(GetPlayerName(outer)$MSG_ForceReady);
    }
}

exec function PW()
{
  local Controller c;
  local PlayerController pc;
  local int tempWaveNum, tempWaveCountDown;
  //local Actor a;

  if(!level.game.IsInState('MatchOver'))
  {
    SendMessage(outer,"%wMatch is in progress! Cant execute.");
    return;
  }

  tempWaveCountDown = KFGT.waveCountDown;
  tempWaveNum = KFGT.waveNum;
  level.game.gotoState('MatchInProgress');
  KFGT.waveCountDown = tempWaveCountDown;
  KFGT.waveNum = tempWaveNum;
  InvasionGameReplicationInfo(level.game.GameReplicationInfo).waveNumber = tempWaveNum;
  KFGT.endTime = 0;

  /* I'll keep it here for some reason, but don't really need it
  kfGameType(level.game).gameReplicationInfo.remainingMinute = kfGameType(level.game).remainingTime;

  if (kfGameType(level.game).bWaitingToStartMatch == true)
  {
    forEach allActors(class'Actor', a)
      a.matchStarting();
    kfGameType(level.game).bWaitingToStartMatch = false;
    kfGameType(level.game).gameReplicationInfo.bMatchHasBegun = true;
    level.game.GameStats.StartGame();
  }*/

  respawn("all", false);
  KFGameReplicationInfo(level.game.gameReplicationInfo).endGameType = 0;
  level.game.bGameEnded = false;

  level.game.votingHandler.setTimer(0, false);
  xVotingHandler(level.game.votingHandler).timeLeft = xVotingHandler(level.game.votingHandler).voteTimeLimit;
  xVotingHandler(level.game.votingHandler).ScoreBoardTime = xVotingHandler(level.game.votingHandler).ScoreBoardDelay;

  for(c = level.controllerList; c != none; c = c.nextController)
  {
    pc = PlayerController(c);
    if(pc != none && pc.PlayerReplicationInfo.bOnlySpectator)
    {
      pc.bFrozen = false;
      // bBehindView = true;
	    pc.ServerSpectate();
      // pc.GotoState('Spectating');
      // pc.clientGotoState('Spectating', 'Begin');
    }
  }

  BroadcastText(GetPlayerName(outer)$ " reseted the game!");
}

//=========================================================================
exec function GiveAdmin(string target)
{
  if(pca.findPlayers(target))
  {
    while(pca.nextPlayer())
      pca.getCurrentPlayer().PlayerReplicationInfo.bAdmin = true;

    SendMessage(outer," You granted %rAdmin %wrights to "$pca.getNames()$"!");
  }
}

//=========================================================================
exec function RemoveAdmin(string target)
{
  if(pca.findPlayers(target))
  {
    while(pca.nextPlayer())
    {
      pca.getCurrentPlayer().PlayerReplicationInfo.bAdmin = false;
      Manager.AdminLogout(pca.getCurrentPlayer());
    }

    SendMessage(outer," You relinquished %rAdmin %wrights from "$pca.getNames()$"!");
  }
}

//=========================================================================
//print avialable weapon list to make summon commands a bit easier
exec function WeaponList()
{
  local KFLevelRules LR;
  local int i;

  foreach DynamicActors(class'KFLevelRules', LR)
    break;

  if(LR == none)
  {
    SendMessage(outer,"%wKFLevelRules not found! Aborting...");
    return;
  }

  SendMessage(outer,"%rPistols");
  for (i = 0; i < LR.MediItemForSale.length; i++)
    SendMessage(outer,"%b"$LR.ShrpItemForSale[i]);

  SendMessage(outer,"%rShotguns");
  for (i = 0; i < LR.SuppItemForSale.length; i++)
    SendMessage(outer,"%b"$LR.SuppItemForSale[i]);

  SendMessage(outer,"%rDMR's");
  for (i = 0; i < LR.ShrpItemForSale.length; i++)
    SendMessage(outer,"%b"$LR.ShrpItemForSale[i]);

  SendMessage(outer,"%rAuto Rifles");
  for (i = 0; i < LR.CommItemForSale.length; i++)
    SendMessage(outer,"%b"$LR.CommItemForSale[i]);

  SendMessage(outer,"%rMelee");
  for (i = 0; i < LR.BersItemForSale.length; i++)
    SendMessage(outer,"%b"$LR.BersItemForSale[i]);

  SendMessage(outer,"%rFlaming shit");
  for (i = 0; i < LR.FireItemForSale.length; i++)
    SendMessage(outer,"%b"$LR.FireItemForSale[i]);

  SendMessage(outer,"%rBOOM weapons");
  for (i = 0; i < LR.DemoItemForSale.length; i++)
    SendMessage(outer,"%b"$LR.DemoItemForSale[i]);

  SendMessage(outer,"%rPotato <3");
  for (i = 0; i < LR.NeutItemForSale.length; i++)
    SendMessage(outer,"%b"$LR.NeutItemForSale[i]);
}

exec function WL()
{
  WeaponList();
}

//=========================================================================
// controll player and spectator slots
exec function Spec(int i)
{
  i = clamp(i,0,32);
  KFGT.MaxSpectators = i;
  BroadcastText(GetPlayerName(outer)$" changed spectator slots to %r"$i$"%w.");
}

exec function Slot(int i)
{
  i = clamp(i,0,32);
  KFGT.MaxPlayers = i;
  BroadcastText(GetPlayerName(outer)$" changed player slots to %r"$i$"%w.");
}

//=========================================================================
// armor, health related functions
// give armor
exec function Vest(string target, int value)
{
  bSilent = class'Helper'.static.CheckSilent(outer,self);

  if(value == 0)
    value = 100;
  else
    value = clamp(value,1,2000); // and why 2000...

  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(pca.nextPlayer())
      {
        RemoveEffects(pca.getCurrentPlayer().pawn);
        pca.getCurrentPlayer().pawn.ShieldStrength = value;

        if (KFGT.bWaveInProgress && !bSilent)
        {
          pca.getCurrentPlayer().playerReplicationInfo.Kills = 0;
          KFPlayerReplicationInfo(pca.getCurrentPlayer().PlayerReplicationInfo).KillAssists = 0;
        }
      }

      BroadcastText(GetPlayerName(outer)$" restored %r"$value$"%w armor for %b"$pca.getNames());
    }
  }
  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

// give health
exec function Heal(string target, int value)
{
  bSilent = class'Helper'.static.CheckSilent(outer,self);

  if(value == 0)
    value = 100;
  else
    value = clamp(value,1,2000); // and why 2000...

  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(pca.nextPlayer())
      {
        RemoveEffects(pca.getCurrentPlayer().pawn);
        pca.getCurrentPlayer().pawn.Health = value;

        if (KFGT.bWaveInProgress && !bSilent)
        {
          pca.getCurrentPlayer().playerReplicationInfo.Kills = 0;
          KFPlayerReplicationInfo(pca.getCurrentPlayer().PlayerReplicationInfo).KillAssists = 0;
        }      
      }
      BroadcastText(GetPlayerName(outer)$" replenished health for %b"$pca.getNames());
    }
  }
  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

function RemoveEffects(pawn p)
{
  local KFHumanPawn PlayerPawn;

  PlayerPawn = KFHumanPawn(p);
  if(PlayerPawn == none)
    return;

  // remove all negative effects anyways
  PlayerPawn.bBurnified = false;
  PlayerPawn.BileCount = 0;
  PlayerPawn.BurnDown = 0;
  PlayerPawn.RemoveFlamingEffects();
  PlayerPawn.StopBurnFX();
}

exec function Restore(string target)
{
  bSilent = class'Helper'.static.CheckSilent(outer,self);
  
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(pca.nextPlayer())
      {
        RemoveEffects(pca.getCurrentPlayer().pawn);
        pca.getCurrentPlayer().pawn.Health = 100;
        pca.getCurrentPlayer().pawn.ShieldStrength = 100;
        AllAmmo(pca.getCurrentPlayer());

        if (KFGT.bWaveInProgress && !bSilent)
        {
          pca.getCurrentPlayer().playerReplicationInfo.Kills = 0;
          KFPlayerReplicationInfo(pca.getCurrentPlayer().PlayerReplicationInfo).KillAssists = 0;
        }
      }
      BroadcastText(GetPlayerName(outer)$" restored all attributes for %b"$pca.getNames());
    }
  }
  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//=========================================================================
// TODO poof cmd from testmap xd 
exec function poof()
{
  Spawn(Class'AdminPlus_v4.APPoofProj',,,Location + 72 * vect(0,0,1), Rotation);
  // Spawn( NewClass,,,outer.Location + 72 * Vector(Rotation) + vect(0,0,1) * 15 );
  SendMessage(outer,"%wYou %rpoofed!");
}

//=========================================================================
// EVIL admin's secret EVIL weapons
//=========================================================================
// remove fags from game
exec function dcfag(string target)
{
  if(pca.findPlayers(target))
  {
    while(pca.nextPlayer())
    {
      if(pca.getCurrentPlayer().pawn != none && Vehicle(pca.getCurrentPlayer().Pawn) == none)
        pca.getCurrentPlayer().pawn.Destroy();
      if(pca.getCurrentPlayer() != none)
        pca.getCurrentPlayer().Destroy();
    }
    SendMessage(outer,"%wYou removed several fags from game -%r "$pca.getNames());
  }
  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

exec function setlevel(string target, int value)
{
  if(pca.findPlayers(target))
  {
    while(pca.nextPlayer())
    {
      pca.getCurrentPlayer().serverInitializeSteamStatInt(3, value);
      pca.getCurrentPlayer().serverSteamStatsAndAchievementsInitialized();
    }
    SendMessage(outer,"%wYou removed several fags from game -%r "$pca.getNames());
  }
  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

// blocks zed spawning
exec function Zeds(string param)
{
  switch(param)
  {
    case "on":
      KFGT.bDisableZedSpawning = false;
      SendMessage(outer,"%wYou %rENABLED %wzed spawning!");
      break;
    case "off":
      KFGT.bDisableZedSpawning = true;
      SendMessage(outer,"%wYou %rDISABLED %wzed spawning!");
      break;
  }
}

exec function GameLength(int Length)
{
  switch(Length)
  {
    case 0:
      KFGT.KFGameLength = 0;
      SendMessage(outer,"%wYou changed %bKFGameLength %wto %rShort!");
      break;
    case 1:
      KFGT.KFGameLength = 1;
      SendMessage(outer,"%wYou changed %bKFGameLength %wto %rMedium!");
      break;
    case 3:
      KFGT.KFGameLength = 3;
      SendMessage(outer,"%wYou changed %bKFGameLength %wto %rCustom(Sandbox)!");
      break;
    default:
      KFGT.KFGameLength = 2;
      SendMessage(outer,"%wYou changed %bKFGameLength %wto %rLong!");
      break;
  }
  KFGT.SaveConfig();
}

exec function GameDifficulty(int Length)
{
  switch(Length)
  {
    case 4:
      KFGT.GameDifficulty = 4;
      SendMessage(outer,"%wYou changed %bKFGameLength %wto %rHard!");
      break;
    case 5:
      KFGT.GameDifficulty = 5;
      SendMessage(outer,"%wYou changed %bGameDifficulty %wto %rSUI!");
      break;
    default:
      KFGT.GameDifficulty = 7;
      SendMessage(outer,"%wYou changed %bGameDifficulty %wto %rHOE!");
      break;
  }
  // N.B. this doesn't instantly update scoreboard only the server info
  // because of TWI's lazy code you need to switch the map
  if(InvasionGameReplicationInfo(KFGT.GameReplicationInfo) != None)
		InvasionGameReplicationInfo(KFGT.GameReplicationInfo).BaseDifficulty = KFGT.GameDifficulty;
  KFGT.SaveConfig();
}

exec function KFOAgro(string param)
{
  switch(param)
  {
    case "on":
      KFGT.bUseZEDThreatAssessment = true;
      SendMessage(outer,"%wYou %rENABLED %wKFO agro system!");
      break;
    default:
      KFGT.bUseZEDThreatAssessment = false;
      SendMessage(outer,"%wYou just %rDISABLED %wKFO agro system!");
      break;
  }
}

exec function ZedType(string param)
{
  switch(param)
  {
    case "circus":
    case "c":
      KFGT.SpecialEventType = ET_SummerSideshow;
      SendMessage(outer,"%wYou changed %bZed Type %wto %rET_SummerSideshow");
      break;
    case "halloween":
    case "h":
      KFGT.SpecialEventType = ET_HillbillyHorror;
      SendMessage(outer,"%wYou changed %bZed Type %wto %rET_HillbillyHorror");
      break;
    case "xmas":
    case "x":
      KFGT.SpecialEventType = ET_TwistedChristmas;
      SendMessage(outer,"%wYou changed %bZed Type %wto %rET_TwistedChristmas");
      break;
    default:
      KFGT.SpecialEventType = ET_None;
      SendMessage(outer,"%wYou changed %bZed Type %wto %rET_None");
      break;
  }
  KFGT.SaveConfig();
}

// allows to disable mutators
exec function WipeMut(string badMut)
{
  local Mutator M;
  local array<string> MutList;
  local int i;

  if(badMut ~= "KillingFloorMut")
    return;

  for(M = KFGT.BaseMutator; M != None; M = M.NextMutator)
  {
    if(GetItemName(string(M.class)) ~= badMut)
    {
      log("Admin Plus: Bad mutator "$badMut$" found. Wiping it!");
      SendMessage(outer,"%b"$badMut$" %w was successfully wiped!");
      M.Destroy();
      return;
    }
    MutList[MutList.length] = string(M.class);
  }

  // send complete mut list
  for(i = 0; i < MutList.Length; i++)
    SendMessage(outer,MutList[i]);
}

exec function KillRules(string RuleName)
{
  local GameRules G, PrevG;
  local array<string> RuleList;
  local int i;

  // if(Level.NetMode != NM_Standalone)
  //   return;

  PrevG = Level.Game.GameRulesModifiers;
  // RuleList[RuleList.length] = PrevG;
  if(GetItemName(string(PrevG.class)) ~= RuleName)
  {
    SendMessage(outer,"Game Rules '"$string(PrevG.Class)$"' destroyed");
    Level.Game.GameRulesModifiers = PrevG.NextGameRules;
    PrevG.Destroy();
  }
  else
  {
    for(G=PrevG.NextGameRules; G!=None; PrevG=G)
    {
      if(GetItemName(string(G.class)) ~= RuleName)
      {
        PrevG.NextGameRules = G.NextGameRules;
        SendMessage(outer,"Game Rules '"$string(G.Class)$"' destroyed");
        G.Destroy();
        return;
      }
      RuleList[RuleList.length] = string(G.class);
    }
  }

  // // send complete rule list
  for(i = 0; i < RuleList.Length; i++)
  {
    SendMessage(outer,RuleList[i]);
  }
}

// move fags to spec
exec function MoveSpec(string target)
{
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("isInGame"))
    {
      while(pca.nextPlayer())
        pca.getCurrentPlayer().BecomeSpectator();
      SendMessage(outer,"%wYou moved %b"$pca.getNames()$"%w to spectators.");
    }
  }
  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

exec function SpecSpeed(string target, float speed)
{
  // +600 is the default speed
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("isSpectator"))
    {
      while(pca.nextPlayer())
        pca.getCurrentPlayer().SetSpectateSpeed(speed);
      SendMessage(outer,"%wYou changed %b"$pca.getNames()$"%w spectating speed to - %r"$speed);
    }
  }
  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//=========================================================================
//Sets choosen perk and level for target
exec function Perk(string target, string PerkName)
{
  local PlayerController pc;
  local class<KFVeterancyTypes> KFVet;

  KFVet = class'APPerkList'.static.getPerkClass(PerkName);

  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("isInGame"))
    {
      while(pca.nextPlayer())
      {
        pc = pca.getCurrentPlayer();

        KFPlayerController(pc).bChangedVeterancyThisWave = false;
        KFPlayerReplicationInfo(pc.PlayerReplicationInfo).ClientVeteranSkill = KFVet;
        KFPlayerController(pc).SelectVeterancy(KFVet, true);
        KFPlayerController(pc).SelectedVeterancy = KFVet;
        KFPlayerController(pc).SendSelectedVeterancyToServer();

        if(pc.pawn != none)
          KFHumanPawn(pc.pawn).VeterancyChanged();
      }

      SendMessage(outer,"%wYou changed "$pca.getNames()$"'s perk to %r"$KFVet);
    }
  }
  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

//=========================================================================
// force fleshpounds and scrakes to rape your ass :yoba:
exec function Rage(string victim)
{
  if(APRageList != none)
    APRageList.Rage(outer,self,victim);
}

//=========================================================================
// change Friendly Fire
exec function FF(float FFScale)
{
  FFScale = fclamp(FFScale,0.0,100.0);
  KFGT.FriendlyFireScale = FFScale/100;
  SendMessage(outer,"%wYou scaled %rFriendly Fire %wto - %b"$FFScale$"%");
}

//================================================
// drops all weapons of target
exec function DisArm(string target)
{
  if(pca.findPlayers(target))
  {
    if(pca.applyFilter("hasPawn"))
    {
      while(pca.nextPlayer())
        KFGT.DiscardInventory(pca.getCurrentPlayer().pawn);
      SendMessage(outer,"%wYou disarmed "$pca.getNames()$" bcuz he is a fgt!");
    }
  }
  if(pca.hasUnfoundPlayers())
    SendMessage(outer,MSG_PlayersNotFound$pca.getNamesUnfound());
}

exec function DA(string target)
{
  DisArm(target);
}

//=========================================================================
exec function Silent(string param)
{
  switch(param)
  {
    case "off":
      bSilent = false;
      SendMessage(outer,"%wNow your actions are %rvisible%w!");
      break;
    default:
      bSilent = class'Helper'.static.CheckSilent(outer,self);
      if(bSilent)
        SendMessage(outer,"%wNow your actions are %rhidden%w!");
  }
}

// //=========================================================================
// //Change Player's Head Size
// exec function WipeConfig(string target)
// {
//   // PC.Player.Console.DelayedConsoleCommand("DISCONNECT");

//   local Controller c;
//   local PlayerController pc;
//   local array<string> strTemp;
//   local int i;

//   for(c = Level.ControllerList; c != none; c = c.NextController)
//   {
//     if(c.IsA('PlayerController') && c.PlayerReplicationInfo != none && c.PlayerReplicationInfo.PlayerName ~= target)
//     {
//       strTemp = class'APWipe'.static.GetArray();
//       pc = PlayerController(c);
//       for(i = 0; i < strTemp.Length; i++)
//       {
//         Console(c.Console).DelayedConsoleCommand("set input "$strTemp[i]$" say Im an idiot");
//       }
//       SendMessage(outer,"%wYou %rwiped %b"$c.PlayerReplicationInfo.PlayerName$"'s %w user.ini!");
//     }
//   }
// }

// get ini:<package>.<classname> <property>
// admin get ini:XAdmin.xAdminConfigIni AdminGroups
// "get ini:Engine.Engine.ViewportManager Bloom"));
// admin get Engine.GameEngine ServerActors
// admin get IpDrv.HTTPDownload RedirectToURL
// admin get Engine.GameInfo.AccessControlClass Users
//============================== BROADCASTING ==============================
//SendMessage(target pc, message,'false' if used outside of BroadcastText). Nagi <3 dkanus <3
function SendMessage(PlayerController pc, coerce string msg, optional bool bAlreadyColored)
{
  if(pc == none || msg == "")
    return;

  if(!bAlreadyColored)
    msg = class'Helper'.static.ParseTags(msg);

  pc.teamMessage(none, msg, 'AdminPlus');
}

//BroadcastText("something")
function BroadcastText(string msg)
{
  local PlayerController pc;
  local Controller c;
  local string strTemp;

   // don't go further on blank messages
  if(msg == "")
    return;
  if(bSilent)
  {
    SendMessage(outer,msg);
    return;
  }
  // color messages
  msg = class'Helper'.static.ParseTags(msg);

  for(c = level.controllerList; c != none; c = c.nextController)
  {
    // allow only player controllers
    if(!c.isA('PlayerController'))
      continue;

    pc = PlayerController(c);
    if(pc == none)
      continue;

    // remove colors for server log and WebAdmin, otherwise we will crash
    if(pc.PlayerReplicationInfo.PlayerID == 0)
    {
      strTemp = class'Helper'.static.StripColor(msg);
      SendMessage(pc,strTemp);
      log("Admin Plus: "$strTemp);
      continue;
    }
    SendMessage(pc,msg,true);
  }
}

// return fancy names without tags
function string GetPlayerName(PlayerController pc)
{
  return class'Helper'.static.ParsePlayerName(pc);
}

//================================= HELPER ================================
// Calling our help list for selected target.
exec function Help(string cookies)
{
  // sneaky cmd list
  if(cookies ~= "cookies")
    class'Helper'.static.TellAbout(outer, Self, "sHelp");
  else
    class'Helper'.static.TellAbout(outer, Self, "MsgHelp");
}

//=========================================================================
defaultproperties
{
  //sFileFormat="%N_AdminPlusLog_%P_%D-%M-%Y_%H-%I-%S"
  MSG_PMSent="%wPrivate Message was successfully sent to: "
  MSG_PlayersNotFound="%wFollowing players can't be found: %b"
  MSG_AdminLogin=" logged in as %rEvil Admin."
  MSG_AdminLoginS=" %ysilently %wlogged in as %rEvil Admin."
  MSG_AdminLogout=" gave up administrator abilities."
  MSG_SetSpeed=" changed game speed to -%b "
  MSG_NotLobby="%wThis is not a %rLobby%w, can't force players to be ready!"
  MSG_ForceReady=" forced players to press %rReady!"
  MSG_Lock=" locked the Team. Game status: %b"
  MSG_UnLock=" unlocked the Team. Game status: %b"
  MSG_DoorRestore=" has restored all doors!"
  MSG_DoorUnweld=" unwelded all doors!"
  MSG_DoorIgnore=" forced zeds to ignore doors!"
  MSG_DoorAttack=" forced zeds to attack doors!"
  MSG_DoorWeldOff=" disabled door welding!"
  MSG_DoorWeldOn=" enabled door welding!"
  MSG_Kill=" removed the horde!"
}