// BUGS:
// 1. Clicking at the void space of actors list doesn't refresh actor's info
// 2. CullDistance can't be selected if moCheckBox under it was clicked before
// 3. Cam't select cullDistance with just typing it and licking hitbox (the single way now - to press enter)

// IDEAS
// 1. Allow to use projectile as a spec
// 2. Disable skybox?
// 3. Highlighting of the save button depends on editBox

class a_VisibilityHandler extends a_ActorBase
  config(HideMut);


// The most important array here
var() GUIList cachedActors;

var() Actor pickedActor;

// this variable is needed if bSelectActor is true
// since timer change bHidden all the time
// original bHidden have to be stored here
var() bool pickedActorOriginalBHidden; 

var() bool bShowDirectionToActor,
           bSelectActor,
           bProjCollidesWithHiddenActor,
           bDiscoveryMode,
           bShowBlockingVolumes,
           bShowZombieZoneVolumes,
           bShowZombieVolumes,
           bShowAnotherVolumes,
           bShowSpriteActors,
           bApplyCullDistanceForEngineActors,
           bHideTerrain,
           bHideCoronas,
           bDisableFog,
           bApplyCullDistanceForMeshActors;

var() config bool bHideEmitters,
                  bHideActorsWithMeshes,
                  bShowPresetsForCurrentMapOnly;

var() HideShopVolume navigationShop;
var() ShopVolume     originalShop;

var() GUIScrollText discoveryLog, presetInsides;

struct IntAndActor
{
  var Actor a;
  var int i;
};

var() array<IntAndActor> cachedEngineActors;    // cull distance
var() array<IntAndActor> cachedMeshActors;      // cull distance
var() array<NavigationPoint> cachedNavPoints;   // navigartion points

var() config int cullDistanceEngineActors;
var() config int cullDistanceMeshActors;

struct OriginalValues
{
  var Actor a;
  var bool bBlockActors;
  var bool bBlockZeroExtentTraces;
  var bool bCollideActors;
};

var() array<OriginalValues> changedActors;

struct ZoneInfoBackup
{
  var ZoneInfo zoneInfo;
  var int distanceFogStart;
  var int distanceFogEnd;
};

var() array<TerrainInfo.DecorationLayer> terrainBackup;
var() array<Actor> coronasBackup;
var() array<ZoneInfoBackup> fogBackup;

var string colorR, colorG, colorB, colorY, colorN;

var() GUIList presetsList;


simulated function postBeginPlay()
{
  super.postBeginPlay();

  colorR = chr(27)$chr(255)$chr(70)$chr(70);
  colorY = chr(27)$chr(255)$chr(255)$chr(70);
  colorG = chr(27)$chr(70)$chr(255)$chr(70);
  colorB = chr(27)$chr(70)$chr(70)$chr(255);
  colorN = chr(27)$chr(70)$chr(70)$chr(70);

  navigationShop = spawn(class'HideShopVolume');

  cachedActors = new(none) class'GUIList';
  discoveryLog = new(none) class'GUIScrollText';
  presetsList = new(none) class'GUIList';
  presetInsides = new(none) class'GUIScrollText';

  initPresetsList();
}


////////////////////////////////////////////////////////////
///                 WORKING WITH PRESETS
////////////////////////////////////////////////////////////

simulated function deletePreset(string presetName)
{
  local HideMenuPreset presetToDelete;
  local string actualPresetName, dev_null;

  if (presetName == "")
  {
    logMessage("You haven't type a single character of the preset's name.");
    return;
  }

  presetToDelete = new(none, presetName) class'HideMenuPreset';
  presetToDelete.clearConfig();

  initPresetsList();

  presetInsides.setContent("");
  presetInsides.newText = "";

  divide(presetName, "@", actualPresetName, dev_null);
  logMessage("Preset \""$actualPresetName$"\" was successfully deleted.");
}


simulated function bool savePreset(string presetName)
{
  local HideMenuPreset  presetToSave;
  local int             i;
  local string          presetDescription;

  if(presetName == "")
  {
    logMessage("You haven't type a single character of the preset's name.");
    return false;
  }

  if(inStr(presetName, " //") != -1)
    divide(presetName, " //", presetName, presetDescription);
  else
    divide(presetName, "//", presetName, presetDescription);

  if(inStr(presetName, " ") != -1 || inStr(presetName, "[") != -1 ||
     inStr(presetName, "]") != -1 || inStr(presetName, "@") != -1)
  {
    logMessage("Preset's name can't contain spaces and characters '@', '[', ']'.");
    return false;
  }

  presetToSave = new(none, presetName$"@"$getCurrentMapName()) class'HideMenuPreset';
  presetToSave.clearConfig();

  if(presetDescription != "")
    presetToSave.presetDescription = presetDescription;

  for (i = 0; i < cachedActors.elements.length; i++)
  {
    if(cachedActors.elements[i].extraData != none)
      presetToSave.uselessActors[presetToSave.uselessActors.length] = getActorName(actor(cachedActors.elements[i].extraData));
  }

  presetToSave.SaveConfig();

  initPresetsList();

  logMessage("Preset \""$presetName$"\" was successfully saved.");

  return true;
}


simulated function selectCurrentPreset()
{
  local string          presetName;
  local HideMenuPreset  currentPreset;
  local int             i;
  local bool            bFirstString; // thanks TWI for inspiring me to create my own bicycle

  // User selected nothing
  if (presetsList.calculateIndex(true) == -1)
  {
    presetInsides.setContent("");
    presetInsides.newText = "";
    return;
  }

  presetName = presetsList.elements[presetsList.calculateIndex(true)].ExtraStrData;

  currentPreset = new(none, presetName) class'HideMenuPreset';

  presetInsides.setContent("");
  presetInsides.newText = "";

  if (currentPreset.presetDescription != "")
  {
    presetInsides.setContent(currentPreset.presetDescription$"|-----------------|");
    bFirstString = false;
  }

  for (i = 0; i < currentPreset.uselessActors.length; i++)
  {
    if (bFirstString)
      presetInsides.setContent(currentPreset.uselessActors[i]$"|");
    else
      presetInsides.newText $= currentPreset.uselessActors[i]$"|";
  }
}


simulated function loadPreset(string presetName)
{
  local HideMenuPreset  currentPreset;
  local int             i;
  local Actor           a;
  local string          actorName;

  currentPreset = new(none, presetName) class'HideMenuPreset';

  if (currentPreset == none)
  {
    debugMessage("loadPreset(): Preset \""$presetName$"\" can't be loaded.");
    return;
  }

  foreach AllActors(class'actor', a)
  {
    if(!a.bHidden)
      if(a.mesh != none || a.staticMesh != none || a.drawType == DT_Particle)
        if((a.bNoDelete || a.bStatic) && !a.bDeleteMe)
        {
          actorName = getActorName(a);

          for(i = currentPreset.uselessActors.length - 1; i >= 0; i--)
            if(actorName == currentPreset.uselessActors[i])
            {
              hideActor(a);
              currentPreset.uselessActors.remove(i, 1);
              continue;
            }
        }
  }
}


simulated function string getCurrentMapName()
{
  local string mapName;
  local int i;

  mapName = class'KFGameType'.static.getCurrentMapName(getController().level);

  //in case of 255.255.225.228:8080/KF-WestLondon
  i = (inStr(mapName, "/") + 1);
  mapName = mid(mapName, i, len(mapName) - i);

  return mapName;
}


simulated function initPresetsList()
{
  local array<string> presetsNames;
  local int i;
  local string presetName, presetMap, currentMap;

  currentMap = getCurrentMapName();

  presetsNames = getPerObjectNames("HideMut", string(class'HideMenuPreset'.name));

  presetsList.clear();

  for (i = 0; i < presetsNames.length; i++)
  {
    divide(presetsNames[i], "@", presetName, presetMap);

    if (bShowPresetsForCurrentMapOnly)
    {
      if (caps(presetMap) == caps(currentMap))
        presetsList.add(presetName, , presetsNames[i]);
    }
    else
    {
      presetsList.add(presetName@colorN$"["$presetMap$"]", , presetsNames[i]);
    }
  }

  presetsList.index = -1;
  clearPresetInsides();
}


simulated function linkListsToMenu(GUIList newCachedActors, GUIList newPresetsList, GUIScrollText newDiscoveryLog, GUIScrollText newPresetInsides)
{
  local int i;

  if (cachedActors != newCachedActors)
  {
    for (i = 0; i < cachedActors.elements.length; i++)
    {
      newCachedActors.add(cachedActors.elements[i].item, , cachedActors.elements[i].extraStrData);
    }
    cachedActors.clear();
    cachedActors = newCachedActors;
  }

  if (presetsList != newPresetsList)
  {
    for (i = 0; i < presetsList.elements.length; i++)
    {
      newPresetsList.add(presetsList.elements[i].item, , presetsList.elements[i].extraStrData);
    }
    presetsList.clear();
    presetsList = newPresetsList;
  }

  if (discoveryLog != newDiscoveryLog)
  {
    newDiscoveryLog.setContent(discoveryLog.newText);
    discoveryLog.setContent("");
    discoveryLog = newDiscoveryLog;
  }

  if (presetInsides != newPresetInsides)
  {
    newPresetInsides.setContent(presetInsides.newText);
    presetInsides.setContent("");
    presetInsides = newPresetInsides;
  }
}


////////////////////////////////////////////////////////////
///                 HELPING FUNCTIONS
////////////////////////////////////////////////////////////

simulated function debugMessage(string e)
{
  if (discoveryLog.newText != "")
    discoveryLog.newText $= "|";
  discoveryLog.NewText $= stripColor(e);

  getController().myHUD.AddTextMessage(colorR$e, class'LocalMessage', getController().playerReplicationInfo);
}


simulated function logMessage(string e)
{
  if (discoveryLog.newText != "")
    discoveryLog.newText $= "|";
  discoveryLog.NewText $= stripColor(e);

  getController().myHUD.AddTextMessage(e, class'LocalMessage', getController().playerReplicationInfo);
}


function string stripColor(string s)
{
  local int p;

  p = inStr(s, chr(27));
  while (p >= 0)
  {
    s = left(s, p)$mid(s, p + 4);
    p = inStr(s, chr(27));
  }

  return s;
}


simulated function bool isEverythingAlright()
{
  if (KFPlayerController(owner) != none)
    return true;
  else
  {
    setOwner(KFPlayerController(level.getLocalPlayerController()));
    if (KFPlayerController(owner) != none)
      return true;
    else
      return false;
  }
}


simulated function KFPlayerController getController()
{
  return KFPlayerController(owner);
}


simulated function bool isCached(Actor a)
{
  local int i;

  for (i = 0; i < cachedActors.elements.length; i++)
    if (cachedActors.elements[i].extraData == a)
      return true;

  return false;
}


simulated function int findCachedActor(Actor a)
{
  local int i;

  for (i = 0; i < cachedActors.elements.length; i++)
    if (cachedActors.elements[i].extraData == a)
      return i;

  return -1;
}


simulated function string getActorName(Actor a)
{
  local string actorName, dev_null;

  divide(string(a), ".", dev_null, actorName);

  return actorName;
}


////////////////////////////////////////////////////////////
///                 HIDING SHIT
////////////////////////////////////////////////////////////

simulated function setCullDistanceForMeshActors(int i)
{
  cullDistanceMeshActors = i;
  applyCullDistanceForMeshActors();
}


simulated function applyCullDistanceForMeshActors()
{
  local int i;
  local Actor a;

  if (bApplyCullDistanceForMeshActors)
  {
    if (cachedMeshActors.length > 0)
    {
      if (cachedMeshActors[0].a.cullDistance == cullDistanceMeshActors)
        return; //this cull distance is already applied. No need to do it for 2nd time

      for (i = 0; i < cachedMeshActors.length; i++)
        cachedMeshActors[i].a.cullDistance = cullDistanceMeshActors;
    }
    else
    {
      foreach allActors(class'actor', a)
      {
        if (a.mesh != none || a.staticMesh != none)
        {
          if ((a.bNoDelete || a.bStatic) && !a.bDeleteMe)
          {
            cachedMeshActors.length = i + 1;
            cachedMeshActors[i].i = a.cullDistance;
            cachedMeshActors[i].a = a;
            a.cullDistance = cullDistanceMeshActors;
            i++;
          }
        }
      }
    }
  }
  else
  {
    for (i = 0; i < cachedMeshActors.length; i++)
    {
      cachedMeshActors[i].a.cullDistance = cachedMeshActors[i].i;
    }

    cachedMeshActors.remove(0, cachedMeshActors.length);
  }
}


simulated function disableFog()
{
  local ZoneInfo zi;
  local int i;

  if (bDisableFog)
  {
    if (fogBackup.length > 0)
      return;

    foreach allActors(class'ZoneInfo', zi)
    {
      fogBackup.length = i + 1;

      fogBackup[i].distanceFogStart = zi.distanceFogStart;
      fogBackup[i].distanceFogEnd = zi.distanceFogEnd;
      fogBackup[i].zoneInfo = zi;
      zi.distanceFogStart = 0;
      zi.distanceFogEnd = 100000;

      i++;
    }
  }
  else
  {
    for (i = 0; i < fogBackup.length; i++)
    {
      fogBackup[i].zoneInfo.distanceFogStart = fogBackup[i].distanceFogStart;
      fogBackup[i].zoneInfo.distanceFogEnd = fogBackup[i].distanceFogEnd;
    }
    fogBackup.remove(0, fogBackup.length);
  }
}


simulated function hideCoronas()
{
  local Actor a;
  local int i;

  if (bHideCoronas)
  {
    foreach allActors(class'Actor', a)
    {
      if (a.bCorona)
      {
        a.bCorona = false;
        coronasBackup[coronasBackup.length] = a;
      }
    }
  }
  else
  {
    for (i = 0; i < coronasBackup.length; i++)
    {
      coronasBackup[i].bCorona = true;
    }
    coronasBackup.remove(0, coronasBackup.length);
  }
}


simulated function hideTerrain()
{
  local TerrainInfo ti;
  local int i;

  forEach allActors(class'TerrainInfo', ti)
    break;

  if (ti == none)
  {
    logMessage("There's no terrain on this map.");
    return;
  }

  if (bHideTerrain)
  {
    terrainBackup.length = ti.decolayers.length;

    for (i = ti.decolayers.length - 1; i >= 0 ; i--)
    {
      terrainBackup[i] = ti.decolayers[i];
      ti.decolayers.remove(i, 1);
    }
  }
  else
  {
    ti.decolayers.length = terrainBackup.length;

    for (i = terrainBackup.length - 1; i >= 0 ; i--)
    {
      ti.decolayers[i] = terrainBackup[i];
      terrainBackup.remove(i, 1);
    }
  }

  //  terrainBackup
}


////////////////////////////////////////////////////////////
///                 ENGINE ACTORS
////////////////////////////////////////////////////////////

simulated function setCullDistanceForEngineActors(int i)
{
  cullDistanceEngineActors = i;
  applyCullDistanceForEngineActors();
}


simulated function applyCullDistanceForEngineActors()
{
  local int i;

  if (bApplyCullDistanceForEngineActors)
  {
    for (i = 0; i < cachedEngineActors.length; i++)
    {
      cachedEngineActors[i].a.cullDistance = cullDistanceEngineActors;
    }
  }
  else
  {
    for (i = 0; i < cachedEngineActors.length; i++)
    {
      cachedEngineActors[i].a.cullDistance = cachedEngineActors[i].i;
    }
  }
}


simulated function showBlockingVolumes()
{
  local BlockingVolume bv;
  local int i, j;

  if (bShowBlockingVolumes)
  {
    i = cachedEngineActors.length;
    forEach allActors(class'BlockingVolume', bv)
    {
      if (KFZombieZoneVolume(bv) != none)
        continue;

      if (bv.bHidden)
      {
        cachedEngineActors.length = i + 1;
        cachedEngineActors[i].i = bv.cullDistance;
        cachedEngineActors[i].a = bv;

        if (bApplyCullDistanceForEngineActors)
          bv.cullDistance = cullDistanceEngineActors;

        bv.bHidden = false;
        bv.resetStaticFilterState();

        j++;
        i++;
      }
    }
    logMessage(j@"BlockingVolumes were unhided.");
  }
  else
    for(i = cachedEngineActors.length - 1; i >= 0; i--)
      if(BlockingVolume(cachedEngineActors[i].a) != none)
      {
        if(KFZombieZoneVolume(cachedEngineActors[i].a) != none)
          continue;

        cachedEngineActors[i].a.cullDistance = cachedEngineActors[i].i;

        cachedEngineActors[i].a.bHidden = true;
        cachedEngineActors[i].a.resetStaticFilterState();

        cachedEngineActors.remove(i, 1);
      }
}


simulated function showZombieZoneVolumes()
{
  local KFZombieZoneVolume zzv;
  local int i, j;

  if (bShowZombieZoneVolumes)
  {
    i = cachedEngineActors.length;
    forEach allActors(class'KFZombieZoneVolume', zzv)
    {
      if(zzv.bHidden)
      {
        cachedEngineActors.length = i + 1;
        cachedEngineActors[i].i = zzv.cullDistance;
        cachedEngineActors[i].a = zzv;

        if(bApplyCullDistanceForEngineActors)
          zzv.cullDistance = cullDistanceEngineActors;

        zzv.bHidden = false;
        zzv.resetStaticFilterState();

        j++;
        i++;
      }
    }
    logMessage(j@"KFZombieZoneVolumes were unhided.");
  }
  else
    for(i = cachedEngineActors.length - 1; i >= 0; i--)
      if(KFZombieZoneVolume(cachedEngineActors[i].a) != none)
      {
        cachedEngineActors[i].a.cullDistance = cachedEngineActors[i].i;

        cachedEngineActors[i].a.bHidden = true;
        cachedEngineActors[i].a.resetStaticFilterState();

        cachedEngineActors.remove(i, 1);
      }
}


simulated function showZombieVolumes()
{
  local ZombieVolume zv;
  local int i, j;

  if (bShowZombieVolumes)
  {
    i = cachedEngineActors.length;
    forEach allActors(class'ZombieVolume', zv)
    {
      if (zv.bHidden)
      {
        cachedEngineActors.length = i + 1;
        cachedEngineActors[i].i = zv.cullDistance;
        cachedEngineActors[i].a = zv;

        if(bApplyCullDistanceForEngineActors)
          zv.cullDistance = cullDistanceEngineActors;

        zv.bHidden = false;
        zv.resetStaticFilterState();

        j++;
        i++;
      }
    }
    logMessage(j@"ZombieVolumes were unhided.");
  }
  else
    for(i = cachedEngineActors.length - 1; i >= 0; i--)
      if(ZombieVolume(cachedEngineActors[i].a) != none)
      {
        cachedEngineActors[i].a.cullDistance = cachedEngineActors[i].i;

        cachedEngineActors[i].a.bHidden = true;
        cachedEngineActors[i].a.resetStaticFilterState();

        cachedEngineActors.remove(i, 1);
      }
}


simulated function showAnotherVolumes()
{
  local Volume v;
  local int i, j;

  if(bShowAnotherVolumes)
  {
    i = cachedEngineActors.length;
    forEach allActors(class'Volume', v)
    {
      if(BlockingVolume(v) != none || ZombieVolume(v) != none)
        continue;

      if(v.bHidden)
      {
        cachedEngineActors.length = i + 1;
        cachedEngineActors[i].i = v.cullDistance;
        cachedEngineActors[i].a = v;

        if(bApplyCullDistanceForEngineActors)
          v.cullDistance = cullDistanceEngineActors;

        v.bHidden = false;
        v.resetStaticFilterState();

        j++;
        i++;
      }
    }
    logMessage(j@"another volumes were unhided.");
  }
  else
    for(i = cachedEngineActors.length - 1; i >= 0; i--)
      if(Volume(cachedEngineActors[i].a) != none)
      {
        if(BlockingVolume(cachedEngineActors[i].a) != none || ZombieVolume(cachedEngineActors[i].a) != none)
        continue;

        cachedEngineActors[i].a.cullDistance = cachedEngineActors[i].i;

        cachedEngineActors[i].a.bHidden = true;
        cachedEngineActors[i].a.resetStaticFilterState();

        cachedEngineActors.remove(i, 1);
      }
}


simulated function showSpriteActors()
{
  local Actor a;
  local int i, j;

  if(bShowSpriteActors)
  {
    i = cachedEngineActors.length;
    forEach allActors(class'Actor', a)
    {
      if(a.DrawType != DT_Sprite)
        continue;

      if(a.bHidden)
      {
        cachedEngineActors.length = i + 1;
        cachedEngineActors[i].i = a.cullDistance;
        cachedEngineActors[i].a = a;

        if(bApplyCullDistanceForEngineActors)
          a.cullDistance = cullDistanceEngineActors;

        a.bHidden = false;
        a.resetStaticFilterState();

        j++;
        i++;
      }
    }
    logMessage(j@"sprite actors were unhided.");
  }
  else
  {
    for (i = cachedEngineActors.length - 1; i >= 0; i--)
    {
      if (cachedEngineActors[i].a.DrawType == DT_Sprite)
      {
        cachedEngineActors[i].a.cullDistance = cachedEngineActors[i].i;
        cachedEngineActors[i].a.bHidden = true;
        cachedEngineActors[i].a.resetStaticFilterState();
        cachedEngineActors.remove(i, 1);
      }
    }
  }
}


simulated function showPathNodes(bool b)
{
  local NavigationPoint np;
  local int i, j;

  if (b)
  {
    forEach allActors(class'NavigationPoint', np)
    {
      if (np == none)
        continue;

      cachedNavPoints.length = i + 1;
      cachedNavPoints[i] = np;
      cachedNavPoints[i].bHidden = false;
      cachedNavPoints[i].resetStaticFilterState();

      j++;
      i++;
    }
    logMessage(j $ " pathnodes were unhided.");
  }
  else
  {
    for (i = cachedNavPoints.length - 1; i >= 0; i--)
    {
      cachedNavPoints[i].bHidden = true;
      cachedNavPoints[i].resetStaticFilterState();
      cachedNavPoints.remove(i, 1);
    }
  }
}


// show zed paths:
//  Orange - Flying path, narrow.
//  Light Orange - Flying path, wide.
//  Light Purple - requires high jump (higher than normal jump capability).
//  Blue - narrow path.
//  Green - normal width.
//  White - wide path.
//  Pink - very wide path.
//  Yellow - forced path.
//  Purple - "advanced" path (requires "intelligence" to be used).
//  Red - "one-way" path (i.e. path going from node A->B, but B->A is proscribed).
final function ShowPaths(bool b)
{
  local KFPlayerController pc;
  local NavigationPoint np;
  local array<NavigationPoint> np_array;
  local ReachSpec Spec;
  local vector SpecColor;
  local int i, j;

  pc = getController();

  if (!b)
  {
    pc.ClearStayingDebugLines();
    return;
  }

  forEach allActors(class'NavigationPoint', np)
  {
    if (np == none)
      continue;

    np_array.length = i + 1;
    np_array[i] = np;

    i++;
  }

  for (i = np_array.length - 1; i >= 0; i--)
  {
    for (j = 0; j < np_array[i].PathList.Length; j++)
    {
      Spec = np_array[i].PathList[j];
      SpecColor = vect(0, 255, 0);
      // 260 = MAXVEHICLERADIUS 80 = MINCOMMONHEIGHT
      if (( Spec.CollisionRadius >= 260 ) && (Spec.CollisionHeight >= 80))
      {
        SpecColor = vect( 255,255,255 );
      }
      // Jump spec
      else if( ( Spec.reachFlags & 8 ) == Spec.reachFlags )
      {
        SpecColor = vect( 191, 165, 99 );
      }
      // Proscribed
      else if( ( Spec.reachFlags & 128 ) == Spec.reachFlags )
      {
        SpecColor = vect( 255, 0, 0 );
      }
      // Forced
      else if( ( Spec.reachFlags & 256 ) == Spec.reachFlags )
      {
        SpecColor = vect( 255, 255, 0 );
      }
      // Blue (narrow)
      else if( ( Spec.CollisionRadius < 72 || Spec.CollisionHeight < 80 ) )
      {
        SpecColor = vect( 0, 0, 255 );
      }

      pc.DrawStayingDebugLine(np_array[i].PathList[j].Start.Location, np_array[i].PathList[j].End.Location, SpecColor.X, SpecColor.Y, SpecColor.Z);
    }
  }
}


////////////////////////////////////////////////////////////
///                 RANDOM SHIT
////////////////////////////////////////////////////////////

simulated function sortCachedActors()
{
  local Object extraData;
  local string item;
  local int i, j;
  local string actorName1, actorName2, dev_null;

  if (cachedActors.elements.length <= 1)
    return;

  for (i = 0; i < cachedActors.elements.length - 1; i++)
    for (j = i + 1; j < cachedActors.elements.length; j++)
    {
      if(cachedActors.elements[i].extraData.class == cachedActors.elements[j].extraData.class)
      {
        divide(string(cachedActors.elements[i].extraData.class), ".", dev_null, actorName1);
        divide(string(cachedActors.elements[j].extraData.class), ".", dev_null, actorName2);
        if (int(repl(stripColor(cachedActors.elements[i].item), actorName1, "")) >
          int(repl(stripColor(cachedActors.elements[j].item), actorName2, "")))
        {
          extraData = cachedActors.elements[i].extraData;
          item = cachedActors.elements[i].item;

          cachedActors.elements[i].extraData = cachedActors.elements[j].extraData;
          cachedActors.elements[i].item = cachedActors.elements[j].item;

          cachedActors.elements[j].extraData = extraData;
          cachedActors.elements[j].item = item;
        }
      }
      else if (cachedActors.elements[i].item > cachedActors.elements[j].item)
      {
        extraData = cachedActors.elements[i].extraData;
        item = cachedActors.elements[i].item;

        cachedActors.elements[i].extraData = cachedActors.elements[j].extraData;
        cachedActors.elements[i].item = cachedActors.elements[j].item;

        cachedActors.elements[j].extraData = extraData;
        cachedActors.elements[j].item = item;
      }
    }
}


////////////////////////////////////////////////////////////
///          DISCOVERING MODE & PICKING ACTORS
////////////////////////////////////////////////////////////

simulated function pickActor()
{
  local proj_PickActor pap;
  local KFPlayerController pc;

  pc = getController();

  pap = pc.spawn(class'proj_PickActor', pc, , pc.pawn.location + pc.pawn.eyeHeight * vect(0, 0, 1), pc.rotation);
  pap.a_visibilityHandler = self;
}


simulated function discoveryMode()
{
  local Actor a;
  local int i;

  if (bDiscoveryMode)
  {
    if (changedActors.length > 0)
    {
      debugMessage("discoveryMode(): seems like function was called  2 times in a row"@
        "with bDiscoveryMode=true");
      return;
    }

    forEach getController().allActors(class'Actor', a)
    {
      if (a.mesh != none || a.staticMesh != none)
      {
        if ((a.bNoDelete || a.bStatic) && !a.bDeleteMe)
        {
          changedActors.length = i + 1;

          changedActors[i].a = a;
          changedActors[i].bBlockActors = a.bBlockActors;
          changedActors[i].bBlockZeroExtentTraces = a.bBlockZeroExtentTraces;
          changedActors[i].bCollideActors = a.bCollideActors;

          if (!a.bHidden || bProjCollidesWithHiddenActor)
          {
            a.bBlockZeroExtentTraces = true;
            a.setCollision(true, true);
          }
          else
          {
            a.bBlockZeroExtentTraces = false;
            a.setCollision(false, false);
          }

          i++;
        }
      }
    }
    logMessage("Discovery mode is enabled.");
  }
  else
  {
    if (changedActors.length == 0)
    {
      debugMessage("discoveryMode(): seems like function was called 2 times in a row"@
        "with bDiscoveryMode=false");
      return;
    }
    for (i = 0; i < changedActors.length; i++)
    {
      changedActors[i].a.bBlockZeroExtentTraces = changedActors[i].bBlockZeroExtentTraces;
      changedActors[i].a.setCollision(changedActors[i].bCollideActors, changedActors[i].bBlockActors);
    }
    changedActors.remove(0, changedActors.length);
    logMessage("Discovery mode is disabled.");
  }
}


simulated function bool isActorChanged(Actor a)
{
  local int i;

  for (i = 0; i < changedActors.length; i++)
    if (changedActors[i].a == a)
      return true;

  return false;
}


simulated function discoveryModeShowActor(Actor a)
{
  if (!bDiscoveryMode)
    return;

  if (!isActorChanged(a))
    return;

  a.bBlockZeroExtentTraces = true;
  a.setCollision(true, true);
}


simulated function discoveryModeHideActor(Actor a)
{
  if (!bDiscoveryMode)
    return;

  if (bProjCollidesWithHiddenActor)
    return;

  if (!isActorChanged(a))
    return;

  a.bBlockZeroExtentTraces = false;
  a.setCollision(false, false);
}


simulated function discoveryModeChangeCollision()
{
  local int i;

  if (bProjCollidesWithHiddenActor)
  {
    for (i = 0; i < changedActors.length; i++)
    {
      if (changedActors[i].a.bHidden)
      {
        changedActors[i].a.bBlockZeroExtentTraces = true;
        changedActors[i].a.setCollision(true, true);
      }
    }
  }
  else
  {
    for (i = 0; i < changedActors.length; i++)
    {
      if (changedActors[i].a.bHidden)
      {
        changedActors[i].a.bBlockZeroExtentTraces = false;
        changedActors[i].a.setCollision(false, false);
      }
    }
  }
}


simulated function clearLog()
{
  discoveryLog.setContent("");
  discoveryLog.newText = "";
}


simulated function clearPresetInsides()
{
  presetInsides.setContent("");
  presetInsides.newText = "";
}


////////////////////////////////////////////////////////////
///        SHOW ACTOR'S LOCATION & SELECT ACTOR SHIT
////////////////////////////////////////////////////////////

// changes gri.currentShop to special shop volume
// which has the same location as "pickedActor"
// so the trader arrow show location of "pickedActor"
simulated function showDirectionToActor()
{
  local shopvolume currentShop;
  local HUDKillingFloor KFHUD;

  currentShop = KFGameReplicationInfo(getController().gameReplicationInfo).currentShop;
  KFHUD = HUDKillingFloor(getController().myHud);

  if (!bShowDirectionToActor)
  {
    if (currentShop == navigationShop)
    {
      currentShop = originalShop;
      KFHUD.traderString = "Trader";
    }
    return;
  }

  if (pickedActor == none)
  {
    debugMessage("showDirectionToActor(): pickedActor == none");
    return;
  }

  // "if" to avoid sudden adding "navigationShop" to "originalShop"
  if (HideShopVolume(currentShop) == none)
    originalShop = currentShop;

  navigationShop.setLocation(pickedActor.location);
  KFHUD.traderString = getActorName(pickedActor);
  currentShop = navigationShop;
}


// "pickedActor" starts to change its "bHidden" value every second
simulated function selectActor()
{
  if (bSelectActor)
  {
    pickedActorOriginalBHidden = pickedActor.bHidden;
    setTimer(1.0, true);
  }
  else
  {
    setTimer(0, false);
    pickedActor.bHidden = pickedActorOriginalBHidden;

    if (pickedActor.bStatic)
      pickedActor.resetStaticFilterState();
  }
}


simulated function Timer()
{
  local shopvolume currentShop;

  if (bSelectActor)
  {
    pickedActor.bHidden = !pickedActor.bHidden;

    if (pickedActor.bStatic)
    pickedActor.resetStaticFilterState();
  }

  if (bShowDirectionToActor)
  {
    currentShop = KFGameReplicationInfo(getController().gameReplicationInfo).currentShop;

    if (currentShop != navigationShop)
    {
      originalShop = currentShop;
      currentShop = navigationShop;
    }
  }
}


////////////////////////////////////////////////////////////
///                 USEFUL STUFF
////////////////////////////////////////////////////////////

simulated function removeActorFromList(Actor a)
{
  local int index;

  if (a == none)
  {
    debugMessage("removeActorFromList(): There's no input actor.");
    return;
  }

  index = findCachedActor(a);

  if (index == -1)
  {
    debugMessage("removeActorFromList():"@getActorName(a)@
      "wasn't found in cached actors list. That's weird.");
    return;
  }

  if (a == pickedActor && bSelectActor)
  {
    pickedActorOriginalBHidden = false;
    bSelectActor = false;
    selectActor();
  }

  if (a.bHidden)
  {
    a.bHidden = false;
    if (a.bStatic)
      a.resetStaticFilterState();

    discoveryModeShowActor(a);
  }

  cachedActors.remove(index);
}


simulated function showActor(Actor a)
{
  local int index;
  local string actorName;

  if (a == none)
  {
    debugMessage("showActor(): There's no input actor.");
    return;
  }

  if (a == pickedActor && bSelectActor)
  {
    pickedActorOriginalBHidden = false;
    bSelectActor = false;
    selectActor();
  }

  if (a.bHidden)
  {
    a.bHidden = false;
    if (a.bStatic)
      a.resetStaticFilterState();

    discoveryModeShowActor(a);
  }

  actorName = colorB$getActorName(a);

  index = findCachedActor(a);

  if (index > -1)
  {
    cachedActors.elements[index].item = actorName;
    cachedActors.setIndex(index);
    cachedActors.selectedItems[0] = index;
    return;
  }

  cachedActors.add(actorName, a);
  cachedActors.setIndex(cachedActors.elements.length - 1);
  cachedActors.selectedItems[0] = cachedActors.elements.length - 1;
}


simulated function hideActor(Actor a)
{
  local int index;
  local string actorName;

  if (a == none)
  {
    debugMessage("hideActor(): There's no input actor.");
    return;
  }

  if (a == pickedActor && bSelectActor)
  {
    pickedActorOriginalBHidden = true;
    bSelectActor = false;
    selectActor();
  }

  if (!a.bHidden)
  {
    a.bHidden = true;
    if (a.bStatic)
      a.resetStaticFilterState();

    discoveryModeHideActor(a);
  }

  actorName = colorG$getActorName(a);

  index = findCachedActor(a);

  if (index > -1)
  {
    cachedActors.elements[index].item = actorName;
    cachedActors.setIndex(index);
    cachedActors.selectedItems[0] = index;
    return;
  }

  cachedActors.add(actorName, a);
  cachedActors.setIndex(cachedActors.elements.length - 1);
  cachedActors.selectedItems[0] = cachedActors.elements.length - 1;
}


simulated function bool selectActorFromList()
{
  local int selectedSctringNumber;
  local Actor a;

  selectedSctringNumber = cachedActors.calculateIndex(true);

  //  if(selectedSctringNumber > cachedActors.elements.length)
  if (selectedSctringNumber == -1)
    return false;

  a = Actor(cachedActors.elements[selectedSctringNumber].extraData);
  if (a == none)
  {
    debugMessage("selectActorFromList(): For some reason it turned out that"@
      StripColor(cachedActors.elements[selectedSctringNumber].item)@"== none."@
        "The item was removed from the list");
    cachedActors.remove(selectedSctringNumber);
    return false;
  }

  // this whole shit just to change actors without pain
  // so old actor will be reseted to the old state
  // and new actor will recieve attention of selectActor() and showDirectionToActor()
  if (pickedActor != a)
  {
    if (bSelectActor)
    {
      bSelectActor = false;
      selectActor();
      pickedActor = a;
      bSelectActor = true;
      selectActor();
    }
    else
    {
      pickedActor = a;
    }
    showDirectionToActor();
  }

  return true;
}


simulated function pickActorWithProjectile(Actor a)
{
  local int index, i;
  local bool bBlockActorsActual, bBlockZeroExtentTracesActual, bCollideActorsActual;

  if (pickedActor != a || !bSelectActor)
  {
    if (bSelectActor)
    {
      bSelectActor = false;
      selectActor();
    }

    pickedActor = a;
    bSelectActor = true;
    selectActor();
  }
  showDirectionToActor();

  index = findCachedActor(a);

  if (index > -1)
  {
    cachedActors.setIndex(index);
    cachedActors.selectedItems[0] = index;
  }

  if (bDiscoveryMode)
  {
    if (isActorChanged(a))
      for (i = 0; i < changedActors.length; i++)
        if (changedActors[i].a == a)
        {
          bBlockActorsActual = changedActors[i].bBlockActors;
          bBlockZeroExtentTracesActual = changedActors[i].bBlockZeroExtentTraces;
          bCollideActorsActual = changedActors[i].bCollideActors;
        }
  }
  else
  {
    bBlockActorsActual = a.bBlockActors;
    bBlockZeroExtentTracesActual = a.bBlockZeroExtentTraces;
    bCollideActorsActual = a.bCollideActors;
  }

  if (bBlockActorsActual && bCollideActorsActual)
    logMessage(getActorName(a)@colorR$"[blocks everything]");
  else if (bBlockZeroExtentTracesActual && bCollideActorsActual)
    logMessage(getActorName(a)@colorY$"[blocks projectiles]");
  else
    logMessage(getActorName(a)@colorG$"[blocks nothing]");
}


simulated function removeAllActorsFromList()
{
  local int i, j;
  local Actor a;

  for (i = cachedActors.elements.length - 1; i >= 0 ; i--)
  {
    a = Actor(cachedActors.elements[i].extraData);
    if (a != none)
    {
      if (a == pickedActor && bSelectActor)
      {
        pickedActorOriginalBHidden = false;
        bSelectActor = false;
        selectActor();
      }

      if (a.bHidden)
      {
        a.bHidden = false;
        if (a.bStatic)
          a.resetStaticFilterState();

        discoveryModeShowActor(a);
      }

      cachedActors.remove(i);
      j++;
    }
    else
    {
      debugMessage("removeAllUnhidedActorsFromList(): For some reason it turned out that"@
        StripColor(cachedActors.elements[i].item)@"== none."@
        "The item was removed from the list");
      cachedActors.remove(i);
    }
  }

  if (j > 0)
    logMessage("[Remove all actors]:"@j@"actors were removed from the list.");
}


simulated function removeAllUnhidedActorsFromList()
{
  local int i, j;
  local Actor a;

  for (i = cachedActors.elements.length - 1; i >= 0 ; i--)
  {
    a = Actor(cachedActors.elements[i].extraData);
    if (a != none)
    {
      if (a == pickedActor)
        if(bSelectActor)
        {
          bSelectActor = false;
          selectActor();
        }

      if (!a.bHidden)
      {
        cachedActors.remove(i);
        j++;
      }
    }
    else
    {
      debugMessage("removeAllUnhidedActorsFromList(): For some reason it turned out that"@
        StripColor(cachedActors.elements[i].item)@"== none."@
        "The item was removed from the list");
      cachedActors.remove(i);
    }
  }

  if (j > 0)
    logMessage("[Remove all unhided actors]:"@j@"actors were removed from the list.");
}


simulated function showAllActorsInList()
{
  local int i, j;
  local string actorName;
  local Actor a;

  for (i = cachedActors.elements.length - 1; i >= 0 ; i--)
  {
    a = Actor(cachedActors.elements[i].extraData);
    if (a != none)
    {
      if(a == pickedActor)
        if(bSelectActor)
        {
          bSelectActor = false;
          selectActor();
        }

      if(a.bHidden)
      {
        a.bHidden = false;
        if(a.bStatic)
          a.resetStaticFilterState();

        discoveryModeShowActor(a);

        actorName = colorB$getActorName(a);

        cachedActors.elements[i].item = actorName;

        j++;
      }
    }
    else
    {
      debugMessage("showAllActorsInList(): For some reason it turned out that"@
        StripColor(cachedActors.elements[i].item)@"== none."@
        "The item was removed from the list");
      cachedActors.remove(i);
    }
  }

  if(j > 0)
    logMessage("[Show all]:"@j@"actors were unhided.");
}


simulated function hideAllActorsInList()
{
  local int i, j;
  local string actorName;
  local Actor a;

  for(i = cachedActors.elements.length - 1; i >= 0 ; i--)
  {
    a = Actor(cachedActors.elements[i].extraData);
    if(a != none)
    {
      if(a == pickedActor)
        if(bSelectActor)
        {
          bSelectActor = false;
          selectActor();
        }

      if(!a.bHidden)
      {
        a.bHidden = true;
        if(a.bStatic)
          a.resetStaticFilterState();

        discoveryModeHideActor(a);

        actorName = colorG$getActorName(a);

        cachedActors.elements[i].item = actorName;

        j++;
      }
    }
    else
    {
      debugMessage("hideAllActorsInList(): For some reason it turned out that"@
        StripColor(cachedActors.elements[i].item)@"== none."@
        "The item was removed from the list");
      cachedActors.remove(i);
    }
  }
  if(j > 0)
    logMessage("[Hida all]:"@j@"actors were hided.");
}


simulated function doTheMagic()
{
  local Actor a;
  local string actorName, dev_null;
  local bool bCheckIfCached;
  local int j;

  if (!isEverythingAlright())
  {
    debugMessage("doTheMagic(): Can't find player controller");
    return;
  }

  if (cachedActors.elements.length > 0)
    bCheckIfCached = true;

  forEach getController().allActors(class'Actor', a)
  {
    if (bHideActorsWithMeshes)
    {
      if (a.mesh != none || a.staticMesh != none)
      {
        if (!a.bHidden)
          if((a.bNoDelete || a.bStatic) && !a.bDeleteMe) //actors which will be deleted soon are not allowed
            if(!((a.bBlockActors || a.bBlockZeroExtentTraces) && a.bCollideActors))
              if(Pickup(a) == none)
              {
                if(bCheckIfCached)
                  if(isCached(a))
                    continue;

                a.bHidden = true;
                if(a.bStatic)
                    a.resetStaticFilterState();

                divide(string(a), ".", dev_null, actorName);
                actorName = colorG$actorName;

                cachedActors.add(actorName, a);

                j++;
                continue;
              }
      }
    }

    if (bHideEmitters)
    {
      if (a.drawType == DT_Particle)
      {
        if (!a.bHidden)
        {
          if((a.bNoDelete || a.bStatic) && !a.bDeleteMe)
          {
            if (bCheckIfCached)
              if (isCached(a))
                continue;

            a.bHidden = true;
            if (a.bStatic)
              a.resetStaticFilterState();

            divide(string(a), ".", dev_null, actorName);
            actorName = colorG$actorName;

            cachedActors.add(actorName, a);

            j++;
          }
        }
      }
    }
  }

  if (j > 0)
    logMessage("[Magic button]:"@j@"actors were hided.");
}


function Termination()
{
  // showpaths cleanup
  getController().ClearStayingDebugLines();

  cachedActors.clear();
  cachedActors = none;

  pickedActor = none;
  navigationShop = none;
  originalShop = none;

  presetsList.clear();
  presetsList = none;

  discoveryLog.newText = "";
  discoveryLog.setContent("");
  discoveryLog = none;

  presetInsides.newText = "";
  presetInsides.setContent("");
  presetInsides = none;

  cachedEngineActors.length = 0;
  cachedMeshActors.length = 0;
  cachedNavPoints.length = 0;
  changedActors.length = 0;
  terrainBackup.length = 0;
  coronasBackup.length = 0;
  fogBackup.length = 0;

  setTimer(0.0, false);
  super.Termination();
}

defaultproperties
{
     bHideEmitters=True
     bHideActorsWithMeshes=True
     bShowPresetsForCurrentMapOnly=True
}
