#include <amxmodx>
#include <amxmisc>
#include <engine>
#include <cstrike>
#include <fakemeta_util>
#include <hamsandwich>
#include <fun>

#include <kz_wrbot/AmxxArch>
#include <kz_wrbot/curl>

#define PLUGIN "KZ_Bot"
#define VERSION "1.23"
#define AUTHOR "Flummi & MichaelKheel"

//#define DEV_DEBUG

new g_szMapName[32];
new Array:fPlayerAngle, Array:fPlayerKeys, Array:fPlayerVelo, Array:fPlayerOrigin;
new g_timer;
// Timer
new Float:timer_time[33], bool:timer_started[33], bool:IsPaused[33], Float:g_pausetime[33], bool:bot_finish_use[33];
new g_bot_start, g_bot_enable, g_bot_frame, wr_bot_id;
new SyncHudBotTimer;
new Trie:g_tButtons[2];
new url_sprite[64], url_sprite_xz[64];
new WR_TIME[130], WR_NAME[130], WR_COUNTRY[130], WR_SRC[5];
new Float:nExttHink = 0.009;
new timer_bot, timer_option;
new Float:flStartTime;

#define NUM_THREADS 256

#pragma dynamic 32767

#define PEV_PDATA_SAFE    2

#define OFFSET_TEAM            114
#define OFFSET_DEFUSE_PLANT    193
#define HAS_DEFUSE_KIT        (1<<16)
#define OFFSET_INTERNALMODEL    126

new bool:g_Demos = false;

new iXJWRs, iCCWRs, iKZRUWRs, iArchive;
new bool:bFoundDemo = false;
new iDemo_header_size;
new iArchiveName[256];
new iDemoName[256];
new iNavName[256];
new iFile;
new iParsedFile;
new g_demoExt[32] = "rar";

public plugin_precache() {
  get_mapname(g_szMapName, sizeof(g_szMapName) - 1);
  strtolower(g_szMapName);
  parsing_country("xj");

  new i;
  for(i = 0; i < sizeof(g_tButtons); i++)
    g_tButtons[i] = TrieCreate();

  new szStartTargets[][] = {
    "counter_start", "clockstartbutton", "firsttimerelay", "but_start",
    "counter_start_button", "multi_start", "timer_startbutton", "start_timer_emi", "gogogo"
  };

  for(i = 0; i < sizeof szStartTargets ; i++)
    TrieSetCell(g_tButtons[0], szStartTargets[i], i);

  new szFinishTargets[][] = {
    "counter_off", "clockstopbutton", "clockstop", "but_stop",
    "counter_stop_button", "multi_stop", "stop_counter", "m_counter_end_emi"
  };

  for(i = 0; i < sizeof szFinishTargets; i++)
    TrieSetCell(g_tButtons[1], szFinishTargets[i], i);

  new Ent = engfunc(EngFunc_CreateNamedEntity , engfunc(EngFunc_AllocString, "info_target"));
  set_pev(Ent, pev_classname, "BotThink");
  set_pev(Ent, pev_nextthink, get_gametime() + 0.01);
  register_forward(FM_Think, "fwd_Think", 1);
  fPlayerAngle  = ArrayCreate(2);
  fPlayerOrigin = ArrayCreate(3);
  fPlayerVelo   = ArrayCreate(3);
  fPlayerKeys   = ArrayCreate(1);
}

public plugin_init() {
  register_plugin(PLUGIN, VERSION, AUTHOR);

  timer_bot = register_cvar("timer_bot", "1");
  timer_option = register_cvar("timer_option", "1"); // 1 txt, 2 hud

  new kreedz_cfg[128], ConfigDir[64];
  get_configsdir(ConfigDir, 64);
  formatex(kreedz_cfg, 128, "%s/wrbot.cfg", ConfigDir);

  if(file_exists(kreedz_cfg)) {
    server_cmd("exec %s", kreedz_cfg);
    server_exec();
  }
  else
    server_print("[WR_BOT] Config file is not connected, please check.");

  if(get_pcvar_num(timer_bot) == 1) {
    new iTimerEnt = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString , "info_target"));
    set_pev(iTimerEnt, pev_classname, "kz_time_think");
    set_pev(iTimerEnt, pev_nextthink, get_gametime() + 1.0);
  }

  SyncHudBotTimer = CreateHudSyncObj();

  announce();
}

public Ham_ButtonUse(id) {
  new Float:origin[3];
  pev(id, pev_origin, origin);
  new ent = -1;
  while((ent = find_ent_in_sphere(ent, origin, 100.0)) != 0) {
    new classname[32];
    pev(ent, pev_classname, classname, charsmax(classname));

    new Float:eorigin[3];
    get_brush_entity_origin(ent, eorigin);
    static Float:Distance[2];
    new szTarget[32];
    pev(ent, pev_target, szTarget, 31);

    if(TrieKeyExists(g_tButtons[0], szTarget)) {
      if(g_bot_start < 0)
        g_bot_start = 0;

      if(vector_distance(origin, eorigin) >= Distance[0]) {
        timer_time[id] = get_gametime();
        IsPaused[id] = false;
        timer_started[id] = true;
        bot_finish_use[id] = false;
      }
      Distance[0] = vector_distance(origin, eorigin);
    }
    if(TrieKeyExists(g_tButtons[1], szTarget)) {
      if(vector_distance(origin, eorigin) >= Distance[1]) {
        if(!bot_finish_use[id]) {
          if(timer_started[id])
            Start_Bot();
          timer_started[id] = false;
          bot_finish_use[id] = true;
        }
      }
      Distance[1] = vector_distance(origin, eorigin);
    }
  }
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TIMER ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public timer_task(iTimer) {
  new Dead[32], deadPlayers;
  get_players(Dead, deadPlayers, "bh");
  for(new i = 0; i < deadPlayers; i++) {
    new specmode = pev(Dead[i], pev_iuser1);
    if(specmode == 2 || specmode == 4) {
      new target = pev(Dead[i], pev_iuser2);
      if(is_user_alive(target)) {
        if(timer_started[target] && target == wr_bot_id) {
          new Float:kreedztime = get_gametime() - (IsPaused[target] ? get_gametime() - g_pausetime[target] : timer_time[target]);
          new imin = floatround(kreedztime / 60.0, floatround_floor);
          new isec = floatround(kreedztime - imin * 60, floatround_floor);
          new mili = floatround((kreedztime - (imin * 60 + isec)) * 100, floatround_floor);
          if(get_pcvar_num(timer_option) == 1) {
            client_print(Dead[i], print_center, "[ %02i:%02i.%02i | HP: Godmode | 10aa ]", imin, isec, mili, IsPaused[target] ? "| *Paused*" : "");
          }
          else if(get_pcvar_num(timer_option) == 2) {
            set_hudmessage(255, 255, 255, -1.0, 0.35, 0, 0.0, 1.0, 0.0, 0.0);
            ShowSyncHudMsg(Dead[i], SyncHudBotTimer, "[ %02i:%02i.%02i | HP: Godmode | 10aa ]", imin, isec, mili, IsPaused[target] ? "| *Paused*" : "");
          }
        }
        else if(!timer_started[target] && target == wr_bot_id) {
          client_print(Dead[i], print_center, "");
        }
      }
    }
  }

  entity_set_float(iTimer, EV_FL_nextthink, get_gametime() + 0.07)
}

public Pause() {
  if(!IsPaused[wr_bot_id]) {
    g_pausetime[wr_bot_id] = get_gametime() - timer_time[wr_bot_id];
    timer_time[wr_bot_id] = 0.0;
    IsPaused[wr_bot_id] = true;
    g_bot_enable = 2;
  }
  else {
    if(timer_started[wr_bot_id]) {
      timer_time[wr_bot_id] = get_gametime() - g_pausetime[wr_bot_id];
    }
    IsPaused[wr_bot_id] = false;
    g_bot_enable = 1;
  }
}

public fwd_Think(iEnt) {
  if(!pev_valid(iEnt))
    return(FMRES_IGNORED);

  static className[32];
  pev(iEnt, pev_classname, className, 31);

  if(equal(className, "DemThink")) {
    static bool:Finished;
    for(new i = 0; i < NUM_THREADS; i++) {
      if(ReadFrames(iFile)) {
        Finished = true;
        break;
      }
    }

    if(Finished) {
      set_pev(iEnt, pev_flags, pev(iEnt, pev_flags) | FL_KILLME);
      fclose(iFile);

      // rename file iDemoName -> path/iArchiveName.dem
      new sDatadir[128], sNewfile[128];
      get_localinfo("amxx_datadir", sDatadir, charsmax(sDatadir));
      format(sNewfile, charsmax(sNewfile), "%s/demos/%s/%s.dem", sDatadir, g_szMapName, iArchiveName);
      if(!rename_file(iDemoName, sNewfile, 1)) // relative
        delete_file(iDemoName);
        
      server_print("from: %s", iDemoName);
      server_print("  to: %s", sNewfile);

      LoadParsedInfo(iNavName);
    }
    else
      set_pev(iEnt, pev_nextthink, get_gametime() + 0.001);
  }
  if(equal(className, "NavThink")) {
    static bool:Finished;
    for(new i = 0; i < NUM_THREADS; i++) {
      if(!ReadParsed(iEnt)) {
        Finished = true;
        break;
      }
    }

    if(Finished) {
      set_pev(iEnt, pev_flags, pev(iEnt, pev_flags) | FL_KILLME);
      delete_file(iNavName);
      set_task(2.0, "StartCountDown");
    }
  }

  if(equal(className, "kz_time_think")) {
    timer_task(1);
    set_pev(iEnt, pev_nextthink, get_gametime() + 0.08);
  }

  if(equal(className, "BotThink")) {
    BotThink(wr_bot_id);
    set_pev(iEnt, pev_nextthink, get_gametime() + nExttHink);
  }

  return(FMRES_IGNORED);
}

public BotThink(id) {
  static Float:ViewOrigin[3], Float:ViewAngle[3], Float:ViewVelocity[3], ViewKeys;
  static Float:last_check, Float:game_time, nFrame;
  game_time = get_gametime();

  if(game_time - last_check > 1.0) {
    if(nFrame < 100)
      nExttHink = nExttHink - 0.0001;
    else if(nFrame > 100)
      nExttHink = nExttHink + 0.0001;

    nFrame = 0;
    last_check = game_time;
  }

  if(g_bot_enable == 1 && wr_bot_id) {
    g_bot_frame++;
    if(g_bot_frame < ArraySize(fPlayerAngle)) {
      ArrayGetArray(fPlayerOrigin, g_bot_frame, ViewOrigin);
      ArrayGetArray(fPlayerAngle, g_bot_frame, ViewAngle);
      ArrayGetArray(fPlayerVelo, g_bot_frame, ViewVelocity);
      ViewKeys = ArrayGetCell(fPlayerKeys, g_bot_frame);

      if(ViewKeys&IN_ALT1) ViewKeys|=IN_JUMP;
      if(ViewKeys&IN_RUN)  ViewKeys|=IN_DUCK;

      if(ViewKeys&IN_RIGHT) {
        engclient_cmd(id, "weapon_usp");
        ViewKeys&=~IN_RIGHT;
      }
      if(ViewKeys&IN_LEFT) {
        engclient_cmd(id, "weapon_knife");
        ViewKeys&=~IN_LEFT;
      }
      if(ViewKeys & IN_USE) {
        Ham_ButtonUse(id);
        ViewKeys &= ~IN_USE;
      }

      engfunc(EngFunc_RunPlayerMove, id, ViewAngle, ViewVelocity[0], ViewVelocity[1], 0.0, ViewKeys, 0, 10);
      set_pev(id, pev_v_angle, ViewAngle);
      ViewAngle[0] /= -3.0;
      set_pev(id, pev_velocity, ViewVelocity);
      set_pev(id, pev_angles, ViewAngle);
      set_pev(id, pev_origin, ViewOrigin);
      set_pev(id, pev_button, ViewKeys );

      if(pev(id, pev_gaitsequence) == 4 && ~pev(id, pev_flags) & FL_ONGROUND)
        set_pev(id, pev_gaitsequence, 6);

      if(nFrame == ArraySize(fPlayerAngle) - 1)
        Start_Bot();
    }
    else
      g_bot_frame = 0;
  }
  nFrame++;
}

Create_Bot() {
  new txt[64]
  formatex(txt, charsmax(txt), "[%s] %s %s (%s)", WR_SRC, WR_NAME, WR_TIME, WR_COUNTRY);
  new id = engfunc(EngFunc_CreateFakeClient, txt);
  if(pev_valid(id)) {
    set_user_info(id, "model", "gordon");
    set_user_info(id, "rate", "3500");
    set_user_info(id, "cl_updaterate", "30");
    set_user_info(id, "cl_cmdrate", "60");
    set_user_info(id, "cl_lw", "0");
    set_user_info(id, "cl_lc", "0");
    set_user_info(id, "cl_dlmax", "128");
    set_user_info(id, "cl_righthand", "0");
    set_user_info(id, "ah", "1");
    set_user_info(id, "dm", "0");
    set_user_info(id, "tracker", "0");
    set_user_info(id, "friends", "0");
    set_user_info(id, "*bot", "1");
    set_user_info(id, "_cl_autowepswitch", "1");
    set_user_info(id, "_vgui_menu", "0");
    set_user_info(id, "_vgui_menus", "0");

    static szRejectReason[128];
    dllfunc(DLLFunc_ClientConnect, id, "WR BOT", "127.0.0.1" , szRejectReason);
    if(!is_user_connected(id)) {
      server_print("Connection rejected: %s", szRejectReason);
      return 0;
    }

    dllfunc(DLLFunc_ClientPutInServer, id);
    set_pev(id, pev_spawnflags, pev(id, pev_spawnflags) | FL_FAKECLIENT);
    set_pev(id, pev_flags, pev(id, pev_flags) | FL_FAKECLIENT);

    cs_set_user_team(id, CS_TEAM_CT);
    cs_set_user_model(id, "sas");
    cs_set_user_bpammo(id, CSW_USP, 250);

    cs_user_spawn(id);
    give_item(id, "weapon_knife");
    give_item(id, "weapon_usp");
    set_user_godmode(id, 1);

    return id;
  }
  return 0;
}

public StartCountDown() {
  if(!wr_bot_id)
    wr_bot_id = Create_Bot();

  g_timer = 0;
  set_task(1.0, "Show");
}

public Show() {
  g_timer--;
  set_hudmessage(255, 255, 255, 0.05, 0.2, 0, 6.0, 1.0);

  if(g_timer && g_timer >= 0)
    set_task(1.0, "Show");
  else {
    g_bot_enable = 1;
    Start_Bot();
  }
}

Start_Bot() {
  g_bot_frame = g_bot_start;
  timer_started[wr_bot_id] = false;
}

public client_disconnected(id) {
  if(id == wr_bot_id) {
    timer_time[id] = 0.0;
    IsPaused[wr_bot_id] = false;
    timer_started[wr_bot_id] = false;
    g_bot_enable = 0;
    g_bot_frame = 0;
    wr_bot_id = 0;
  }
}

enum _:Consts {
  HEADER_SIZE         = 544,
  HEADER_SIGNATURE_CHECK_SIZE = 6,
  HEADER_SIGNATURE_SIZE       = 8,
  HEADER_MAPNAME_SIZE     = 260,
  HEADER_GAMEDIR_SIZE     = 260,

  MIN_DIR_ENTRY_COUNT     = 1,
  MAX_DIR_ENTRY_COUNT     = 1024,
  DIR_ENTRY_SIZE          = 92,
  DIR_ENTRY_DESCRIPTION_SIZE  = 64,

  MIN_FRAME_SIZE          = 12,
  FRAME_CONSOLE_COMMAND_SIZE  = 64,
  FRAME_CLIENT_DATA_SIZE      = 32,
  FRAME_EVENT_SIZE        = 84,
  FRAME_WEAPON_ANIM_SIZE      = 8,
  FRAME_SOUND_SIZE_1      = 8,
  FRAME_SOUND_SIZE_2      = 16,
  FRAME_DEMO_BUFFER_SIZE      = 4,
  FRAME_NETMSG_SIZE       = 468,
  FRAME_NETMSG_DEMOINFO_SIZE  = 436,
  FRAME_NETMSG_MOVEVARS_SIZE  = 32,
  FRAME_NETMSG_MIN_MESSAGE_LENGTH = 0,
  FRAME_NETMSG_MAX_MESSAGE_LENGTH = 65536
};

enum DemoHeader {
  netProtocol,
  demoProtocol,
  mapName[HEADER_MAPNAME_SIZE],
  gameDir[HEADER_GAMEDIR_SIZE],
  mapCRC,
  directoryOffset
};

enum DemoEntry {
  dirEntryCount,
  type,
  description[DIR_ENTRY_DESCRIPTION_SIZE],
  flags,
  CDTrack,
  trackTime,
  frameCount,
  offset,
  fileLength,
  frames,
  ubuttons
};

enum FrameHeader {
  Type,
  Float:Timestamp,
  Number
};

enum NetMsgFrame {
  Float:timestamp,
  Float:view[3],
  viewmodel
};

new iDemoEntry[DemoEntry];
new iDemoHeader[DemoHeader];
new iDemoFrame[FrameHeader];

public announce() {
  new datadir[128];
  new filename[128];
  get_localinfo("amxx_datadir", datadir, charsmax(datadir));
  format(filename, charsmax(datadir), "%s/list_xj.txt", datadir);
  delete_file(filename);
  iXJWRs = fopen(filename, "wb");
  new CURL:curl = curl_easy_init();
  if(curl) {
    //curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
    curl_easy_setopt(curl, CURLOPT_URL, "https://xtreme-jumps.eu/demos.txt");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, "write_xj");
    curl_easy_perform(curl, "complite_xj");
  }
}

public write_xj(data[], size, nmemb) {
  new real_size = size * nmemb;
  for(new i = 0; i < nmemb; i++)
    if(i < nmemb)
      fwrite(iXJWRs, data[i], BLOCK_BYTE);
  return real_size;
}

public complite_xj(CURLcode:code, CURL:curl) {
  curl_easy_cleanup(curl);
  fclose(iXJWRs);

  new datadir[128];
  new filename[128];
  get_localinfo("amxx_datadir", datadir, charsmax(datadir));
  format(filename, charsmax(datadir), "%s/list_cc.txt", datadir);
  delete_file(filename);
  iCCWRs = fopen(filename, "wb");
  new CURL:curl = curl_easy_init();
  if(curl) {
    curl_easy_setopt(curl, CURLOPT_URL, "https://cosy-climbing.net/demoz.txt");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, "write_cc");
    curl_easy_perform(curl, "complite_cc");
  }
}

public complite_cc(CURLcode:code, CURL:curl) {
  curl_easy_cleanup(curl);
  fclose(iCCWRs);

  new datadir[128];
  new filename[128];
  get_localinfo("amxx_datadir", datadir, charsmax(datadir));
  format(filename, charsmax(datadir), "%s/list_kzru.txt", datadir);
  delete_file(filename);
  iKZRUWRs = fopen(filename, "wb");
  new CURL:curl = curl_easy_init();
  if(curl) {
    curl_easy_setopt(curl, CURLOPT_URL, "https://kz-rush.ru/demos.txt");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, "write_kzru");
    curl_easy_perform(curl, "complite_kzru");
  }
}

public write_kzru(data[], size, nmemb) {
  new real_size = size * nmemb;
  for(new i = 0; i < nmemb; i++)
    if(i < nmemb)
      fwrite(iKZRUWRs, data[i], BLOCK_BYTE);
  return real_size;
}

public write_cc(data[], size, nmemb) {
  new real_size = size * nmemb;
  for(new i = 0; i < nmemb; i++)
    if(i < nmemb)
      fwrite(iCCWRs, data[i], BLOCK_BYTE);
  return real_size;
}

public complite_kzru(CURLcode:code, CURL:curl) {
  curl_easy_cleanup(curl);
  fclose(iKZRUWRs);
  OnDemosComplete(0);
}

public OnDemosComplete(Index) {
  new demoslist[128];
  get_localinfo("amxx_datadir", demoslist, charsmax(demoslist));
  format(demoslist, charsmax(demoslist), "%s/list_xj.txt", demoslist);
  new iDemosList  = fopen(demoslist, "rb");
  new ExplodedString[7][128];
  new Line[128];
  new MapName[64];
  get_mapname(MapName, 63);
  format(MapName, charsmax(MapName), "%s ", MapName);
  while(!feof(iDemosList)) {
    fgets(iDemosList, Line, charsmax(Line));
    ExplodeString(ExplodedString, 6, 127, Line, ' ');
    new parsedmap[128];
    parsedmap = ExplodedString[0];
    format(parsedmap, charsmax(parsedmap), "%s ", parsedmap);
    if(containi(parsedmap, MapName) > -1) {
      bFoundDemo = true;
      break;
    }
  }
  if(!bFoundDemo) {
    get_mapname(MapName, 63);
    format(MapName, charsmax(MapName), "%s[", MapName);
    fseek(iDemosList, 0, SEEK_SET);
    while(!feof(iDemosList)) {
      fgets(iDemosList, Line, charsmax(Line));
      ExplodeString(ExplodedString, 6, 127, Line, ' ');
      if(containi(ExplodedString[0], MapName) > -1) {
        bFoundDemo = true;
        break;
      }
    }
  }
  else {
    new Float:Date = str_to_float(ExplodedString[1]);
    new sWRTime[24];
    fnConvertTime(Date, sWRTime, charsmax(sWRTime));
    format(iArchiveName, charsmax(iArchiveName), "%s_%s_%s", ExplodedString[0], ExplodedString[2], sWRTime);
    StringTimer(Date, WR_TIME, sizeof(WR_TIME) - 1);
    WR_NAME = ExplodedString[2];
    WR_COUNTRY = ExplodedString[3];
    new iLink[512];
    format(iLink, charsmax(iLink), "http://files.xtreme-jumps.eu/demos/%s.rar", iArchiveName);
    new datadir[128];
    get_localinfo("amxx_datadir", datadir, charsmax(datadir));
    format(datadir, charsmax(datadir), "%s/%s.rar", datadir, iArchiveName);
    WR_SRC = "XJ";
    DownloadDemoArchive(iArchiveName, iLink);
  }
  if(!bFoundDemo)
    CheckCCList();
}

public CheckCCList() {
  new demoslist[128];
  get_localinfo("amxx_datadir", demoslist, charsmax(demoslist));
  format(demoslist, charsmax(demoslist), "%s/list_cc.txt", demoslist);
  new iDemosList = fopen(demoslist, "rb");
  new ExplodedString[7][128];
  new Line[128];
  new MapName[64];
  get_mapname(MapName, 63);
  format(MapName, charsmax(MapName), "%s ", MapName);
  while(!feof(iDemosList)) {
    fgets(iDemosList, Line, charsmax(Line));
    ExplodeString(ExplodedString, 6, 127, Line, ' ');
    new parsedmap[128];
    parsedmap = ExplodedString[0];
    format(parsedmap, charsmax(parsedmap), "%s ", parsedmap);
    if(containi(parsedmap, MapName) > -1) {
      bFoundDemo = true;
      break;
    }
  }
  if(!bFoundDemo) {
    get_mapname(MapName, 63);
    format(MapName, charsmax(MapName), "%s[", MapName);
    fseek(iDemosList, 0, SEEK_SET);
    while(!feof(iDemosList)) {
      fgets(iDemosList, Line, charsmax(Line));
      ExplodeString(ExplodedString, 6, 127, Line, ' ');
      if(containi(ExplodedString[0], MapName) > -1) {
        bFoundDemo = true;
        break;
      }
    }
  }
  else {
    new Float:Date = str_to_float(ExplodedString[1]);
    new sWRTime[24];
    fnConvertTime(Date, sWRTime, charsmax(sWRTime));
    format(iArchiveName, charsmax(iArchiveName), "%s_%s_%s", ExplodedString[0], ExplodedString[2], sWRTime);
    StringTimer(Date, WR_TIME, sizeof(WR_TIME) - 1);
    WR_NAME = ExplodedString[2];
    WR_COUNTRY = ExplodedString[3];
    new iLink[512];
    format(iLink, charsmax(iLink), "https://cosy-climbing.net/files/demos/%s.rar", iArchiveName);
    new datadir[128];
    get_localinfo("amxx_datadir", datadir, charsmax(datadir));
    format(datadir, charsmax(datadir), "%s/%s.rar", datadir, iArchiveName);
    WR_SRC = "CC";
    DownloadDemoArchive(iArchiveName, iLink);
  }
  if(!bFoundDemo)
    CheckKZRUList();
}

public CheckKZRUList() {
  new demoslist[128];
  get_localinfo("amxx_datadir", demoslist, charsmax(demoslist));
  format(demoslist, charsmax(demoslist), "%s/list_kzru.txt", demoslist);
  new iDemosList = fopen(demoslist, "rb");
  new ExplodedString[7][128];
  new Line[128];
  new MapName[64];
  get_mapname(MapName, 63);
  format(MapName, charsmax(MapName), "%s ", MapName);
  while(!feof(iDemosList)) {
    fgets(iDemosList, Line, charsmax(Line));
    ExplodeString(ExplodedString, 6, 127, Line, ' ');
    new parsedmap[128];
    parsedmap = ExplodedString[0];
    format(parsedmap, charsmax(parsedmap), "%s ", parsedmap);
    if(containi(parsedmap, MapName) > -1) {
      bFoundDemo = true;
      break;
    }
  }
  if(!bFoundDemo) {
    get_mapname(MapName, 63);
    format(MapName, charsmax(MapName), "%s[", MapName);
    fseek(iDemosList, 0, SEEK_SET);
    while(!feof(iDemosList)) {
      fgets(iDemosList, Line, charsmax(Line));
      ExplodeString(ExplodedString, 6, 127, Line, ' ');
      if(containi(ExplodedString[0], MapName) > -1) {
        bFoundDemo = true;
        break;
      }
    }
  }
  else {
    new Float:Date = str_to_float(ExplodedString[1]);
    new sWRTime[24];
    fnConvertTime(Date, sWRTime, charsmax(sWRTime));
    format(iArchiveName, charsmax(iArchiveName), "%s_%s_%s", ExplodedString[0], ExplodedString[2], sWRTime);
    StringTimer(Date, WR_TIME, sizeof(WR_TIME) - 1);
    WR_NAME = ExplodedString[2];
    WR_COUNTRY = ExplodedString[3];
    new iLink[512];
    format(iLink, charsmax(iLink), "https://kz-rush.ru/xr_public/demos/maps/cs16/%s.zip", iArchiveName);
    new datadir[128];
    get_localinfo("amxx_datadir", datadir, charsmax(datadir));
    format(datadir, charsmax(datadir), "%s/%s.zip", datadir, iArchiveName);
    WR_SRC = "KZRU";
    g_demoExt = "zip";
    DownloadDemoArchive(iArchiveName, iLink);
  }
}

public DownloadDemoArchive(iArchiveName[], iLink[]) {
  new datadir[128];
  new filename[128];
  get_localinfo("amxx_datadir", datadir, charsmax(datadir));
  
  new demodir[128], demodirfile[128];
  format(demodir, charsmax(demodir), "%s/demos", datadir);
  format(demodirfile, charsmax(demodirfile), "%s/%s", demodir, g_szMapName);

  if(!dir_exists(demodir))
    mkdir(demodir);
  if(!dir_exists(demodirfile))
    mkdir(demodirfile);

  format(filename, charsmax(datadir), "%s/%s.%s", datadir, iArchiveName, g_demoExt);
  iArchive = fopen(filename, "wb");
  new CURL:curl = curl_easy_init();
  if(curl) {
    curl_easy_setopt(curl, CURLOPT_URL, iLink);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, "write_archive");
    curl_easy_perform(curl, "complite_archive");
  }
}

public write_archive(data[], size, nmemb) {
  new real_size = size * nmemb;
  for(new i = 0; i < nmemb; i++)
    if(i < nmemb)
    fwrite(iArchive, data[i], BLOCK_BYTE);
  return real_size;
}

public complite_archive(CURLcode:code, CURL:curl) {
  curl_easy_cleanup(curl);
  fclose(iArchive);
  OnArchiveComplete();
}

public OnArchiveComplete() {
  new datadir[128], RARArchive[128];

  get_localinfo("amxx_datadir", datadir, charsmax(datadir));
  format(RARArchive, charsmax(RARArchive), "%s/%s.%s", datadir, iArchiveName, g_demoExt);

  server_print(" src: %s", WR_SRC);
  server_print(" rar: %s", RARArchive);

  AA_Unarchive(RARArchive, datadir, "@OnComplete", 0);
}
@OnComplete(id, iError) {
  new datadir[128];
  get_localinfo("amxx_datadir", datadir, charsmax(datadir));
  format(datadir, charsmax(datadir), "%s/%s.%s", datadir, iArchiveName, g_demoExt);
  delete_file(datadir);

  if(iError == AA_NO_ERROR) {
    new szNavName[256], demodir1[128], demodir2[128];
    get_localinfo("amxx_datadir", datadir, charsmax(datadir));
    format(szNavName, sizeof(szNavName), "%s", datadir, iArchiveName);
    format(demodir1, sizeof(demodir1), "%s/demos", szNavName);
    format(demodir2, sizeof(demodir2), "%s/demos/%s", szNavName, g_szMapName);

    if(!dir_exists(demodir1))
      mkdir(demodir1);
    if(!dir_exists(demodir2))
      mkdir(demodir2);

    format(iNavName, sizeof(iNavName), "%s/%s.nav", datadir, iArchiveName);
    format(iDemoName, sizeof(iDemoName), "%s/%s.dem", datadir, iArchiveName);
    if(!file_exists(iNavName)) {
      iFile = fopen(iDemoName, "rb");
      if(iFile) {
        iParsedFile = fopen(iNavName, "w");
        ReadHeaderX();
      }
    }
    else
      LoadParsedInfo(iNavName);

    flStartTime = get_gametime();
  }
  else
    server_print("Failed to unpack. Error code: %d", iError);
}

public fnConvertTime(Float:time, convert_time[], len) {
  new sTemp[24];
  new Float:fSeconds = time, iMinutes;

  iMinutes = floatround(fSeconds / 60.0, floatround_floor);
  fSeconds -= iMinutes * 60.0;
  new intpart = floatround(fSeconds, floatround_floor);
  new Float:decpart = (fSeconds - intpart) * 100.0;
  intpart = floatround(decpart);

  formatex(sTemp, charsmax(sTemp), "%02i%02.0f.%02d", iMinutes, fSeconds, intpart);
  formatex(convert_time, len, sTemp);
  return PLUGIN_HANDLED;
}

public LoadParsedInfo(szNavName[]) {
  iFile = fopen(szNavName, "rb");
  new Ent = engfunc(EngFunc_CreateNamedEntity , engfunc(EngFunc_AllocString, "info_target"));
  set_pev(Ent, pev_classname, "NavThink");
  set_pev(Ent, pev_nextthink, get_gametime() + 0.01);
}

public ReadHeaderX() {
  if(IsValidDemoFile(iFile)) {
    ReadHeader(iFile);
    new Ent = engfunc(EngFunc_CreateNamedEntity , engfunc(EngFunc_AllocString, "info_target"));
    set_pev(Ent, pev_classname, "DemThink");
    set_pev(Ent, pev_nextthink, get_gametime() + 0.01);
  }
  else
    server_print("demo is not valid!");
}

public bool:IsValidDemoFile(file) {
  fseek(file, 0, SEEK_END);
  iDemo_header_size = ftell(file);

  if(iDemo_header_size < HEADER_SIZE)
    return false;

  fseek(file, 0, SEEK_SET);
  new signature[HEADER_SIGNATURE_CHECK_SIZE];

  fread_blocks(file, signature, sizeof(signature), BLOCK_CHAR);

  if(!contain("HLDEMO", signature))
    return false;

  return true;
}

public ReadHeader(file) {
  fseek(file, HEADER_SIGNATURE_SIZE, SEEK_SET);
  fread(file, iDemoHeader[demoProtocol], BLOCK_INT);
  fread(file, iDemoHeader[netProtocol], BLOCK_INT);
  fread_blocks(file, iDemoHeader[mapName], HEADER_MAPNAME_SIZE, BLOCK_CHAR);
  fread_blocks(file, iDemoHeader[gameDir], HEADER_GAMEDIR_SIZE, BLOCK_CHAR);
  fread(file, iDemoHeader[mapCRC], BLOCK_INT);
  fread(file, iDemoHeader[directoryOffset], BLOCK_INT);
  fseek(file, iDemoHeader[directoryOffset], SEEK_SET);

  fread(file, iDemoEntry[dirEntryCount], BLOCK_INT);
  for(new i = 0; i < iDemoEntry[dirEntryCount]; i++) {
    fread(file, iDemoEntry[type], BLOCK_INT);
    fread_blocks(file, iDemoEntry[description], DIR_ENTRY_DESCRIPTION_SIZE, BLOCK_CHAR);
    fread(file, iDemoEntry[flags], BLOCK_INT);
    fread(file, iDemoEntry[CDTrack], BLOCK_INT);
    fread(file, iDemoEntry[trackTime], BLOCK_INT);
    fread(file, iDemoEntry[frameCount], BLOCK_INT);
    fread(file, iDemoEntry[offset], BLOCK_INT);
    fread(file, iDemoEntry[fileLength], BLOCK_INT);
  }

  fseek(file, iDemoEntry[offset], SEEK_SET);
}

public ReadParsed(iEnt) {
  if(iFile) {
    new szLineData[512];
    static sExplodedLine[11][150];
    if(!feof(iFile)) {
      fseek(iFile, 0, SEEK_CUR);
      new iSeek = ftell(iFile);
      fseek(iFile, 0, SEEK_END);
      fseek(iFile, iSeek, SEEK_SET);
      fgets(iFile, szLineData, charsmax(szLineData));
      ExplodeString(sExplodedLine, 10, 50, szLineData, '|');
      if(equal(sExplodedLine[1], "ASD")) {
        new Keys = str_to_num(sExplodedLine[2]);
        new Float:Angles[3];
        Angles[0] = str_to_float(sExplodedLine[3]);
        Angles[1] = str_to_float(sExplodedLine[4]);
        Angles[2] = 0.0;
        new Float:Origin[3];
        Origin[0] = str_to_float(sExplodedLine[5]);
        Origin[1] = str_to_float(sExplodedLine[6]);
        Origin[2] = str_to_float(sExplodedLine[7]);
        new Float:velocity[3];
        velocity[0] = str_to_float(sExplodedLine[8]);
        velocity[1] = str_to_float(sExplodedLine[9]);
        velocity[2] = 0.0;

        ArrayPushArray(fPlayerAngle, Angles);
        ArrayPushArray(fPlayerOrigin, Origin);
        ArrayPushArray(fPlayerVelo, velocity);
        ArrayPushCell(fPlayerKeys, Keys);
      }
      set_pev(iEnt, pev_nextthink, get_gametime() + 0.0001);
      return true;
    }
    else {
      server_print("Finished loading demo in %f sec.", get_gametime() - flStartTime);
      return false;
    }
  }
  return false;
}
public ReadFrames(file) {
  fseek(file, 0, SEEK_CUR);
  new iSeek = ftell(file);
  fseek(file, 0, SEEK_END);
  fseek(iFile, iSeek, SEEK_SET);
  static sum;

  if(!feof(file)) {
    new FrameType = ReadFrameHeader(file);
    new breakme;
    switch(FrameType) {
      case 0: { }
      case 1: {
        new Float:Origin[3], Float:ViewAngles[3], Float:velocity[3], iAsd[1024];
        fseek(file, 4, SEEK_CUR);
        for(new i = 0; i < 3; ++i)
          fseek(file, 4, SEEK_CUR);
        for(new i = 0; i < 3; ++i)
          fread(file, _:ViewAngles[i], BLOCK_INT);
        fseek(file, 64, SEEK_CUR);
        for(new i = 0; i < 3; ++i)
          fread(file, _:velocity[i], BLOCK_INT);
        for(new i = 0; i < 3; ++i)
          fread(file, _:Origin[i], BLOCK_INT);
        fseek(file, 124, SEEK_CUR);
        for(new i = 0; i < 3; ++i)
          fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 2, SEEK_CUR);
        fread(file, iDemoEntry[ubuttons], BLOCK_SHORT);
        format(iAsd, charsmax(iAsd), "%d|ASD|%d|%.4f|%.4f|%.3f|%.3f|%f|%.3f|%.3f|%.3f^n",sum, iDemoEntry[ubuttons], ViewAngles[0], ViewAngles[1], Origin[0],Origin[1],Origin[2], velocity[0], velocity[1], velocity[2]);
        fputs(iParsedFile, iAsd);
        fseek(file, 196, SEEK_CUR);
        new length;
        fread(file, length, BLOCK_INT);
        fseek(file, length, SEEK_CUR);
      }
      case 2: { }
      case 3: {
        new ConsoleCmd[FRAME_CONSOLE_COMMAND_SIZE];
        fread_blocks(file, ConsoleCmd, FRAME_CONSOLE_COMMAND_SIZE, BLOCK_CHAR);
      }
      case 4: {
        sum++;
        for(new i = 0; i < 3; ++i)
          fseek(file, 4, SEEK_CUR);
        for(new i = 0; i < 3; ++i)
          fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
      }
      case 5: {
        breakme = 2;
      }
      case 6: {
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        for(new i = 0; i < 3; ++i)
          fseek(file, 4, SEEK_CUR);
        for(new i = 0; i < 3; ++i)
          fseek(file, 4, SEEK_CUR);
        for(new i = 0; i < 3; ++i)
          fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
        fseek(file, 4, SEEK_CUR);
      }
      case 7: {
        fseek(file, 8, SEEK_CUR);
      }
      case 8: {
        fseek(file, 4, SEEK_CUR);
        new length;
        fread(file, length, BLOCK_INT);
        new msg[128];
        fread_blocks(file, msg, length, BLOCK_CHAR);
        fseek(file, 16, SEEK_CUR);
      }
      case 9: {
        new length = 0;
        fread(file, length, BLOCK_INT);
        new buffer[4];
        fread_blocks(file, buffer, length, BLOCK_BYTE);
      }
      default: {
        breakme = 2;
      }
    }
    if(breakme == 2)
      return true;
  }
  return false;
}

public ReadFrameHeader(file) {
  fread(file, iDemoFrame[Type], BLOCK_BYTE);
  fread(file, _:iDemoFrame[Timestamp], BLOCK_INT);
  fread(file, iDemoFrame[Number], BLOCK_INT);

  return(iDemoFrame[Type]);
}

public ExplodeString(p_szOutput[][], p_nMax, p_nSize, p_szInput[], p_szDelimiter) {
  new nIdx = 0, l = strlen(p_szInput);
  new nLen = (1 + copyc(p_szOutput[nIdx], p_nSize, p_szInput, p_szDelimiter));
  while((nLen < l) && (++nIdx < p_nMax))
    nLen += (1 + copyc(p_szOutput[nIdx], p_nSize, p_szInput[nLen], p_szDelimiter));
  return(nIdx);
}

public parsing_country(data[]) {
  new demoslist[128];
  get_localinfo("amxx_datadir", demoslist, charsmax(demoslist));
  if(equali(data, "xj"))
    format(demoslist, charsmax(demoslist), "%s/list_xj.txt", demoslist);
  else if(equali(data, "cc"))
    format(demoslist, charsmax(demoslist), "%s/list_cc.txt", demoslist);

  new iDemosList = fopen(demoslist, "rb");
  new ExplodedString[7][128];
  new Line[128];
  new MapName[64];
  get_mapname(MapName, 63);
  format(MapName, charsmax(MapName), "%s ", MapName);
  while(!feof(iDemosList)) {
    fgets(iDemosList, Line, charsmax(Line));
    ExplodeString(ExplodedString, 6, 127, Line, ' ');
    new parsedmap[128];
    parsedmap = ExplodedString[0];
    format(parsedmap, charsmax(parsedmap), "%s ", parsedmap);
    if(containi(parsedmap, MapName) > -1) {
      g_Demos = true;
      break;
    }
  }
  if(!g_Demos) {
    get_mapname(MapName, 63);
    format(MapName, charsmax(MapName), "%s[", MapName);
    fseek(iDemosList, 0, SEEK_SET);
    while(!feof(iDemosList)) {
      fgets(iDemosList, Line, charsmax(Line));
      ExplodeString(ExplodedString, 6, 127, Line, ' ');
      if(containi(ExplodedString[0], MapName) > -1) {
        g_Demos = true;
        break;
      }
    }
  }
  else {
    new FLAG[10];
    formatex(FLAG, charsmax(FLAG), "%s", ExplodedString[3]);
    trim(FLAG);
    if(equali(FLAG, ""))
      FLAG = "xz";
    if(equali(FLAG, "n-"))
      FLAG = "xz";

    formatex(url_sprite, charsmax(url_sprite), "sprites/wrbot/%s.spr", FLAG);
    formatex(url_sprite_xz, charsmax(url_sprite_xz), "sprites/wrbot/xz.spr");
    if(file_exists(url_sprite))
      precache_model(url_sprite);
    else if(file_exists(url_sprite_xz))
      precache_model(url_sprite_xz);
    else
      return;
  }
  if(!g_Demos && equali(data, "xj"))
    parsing_country("cc");
}

public client_connect(id) {
  if(is_user_bot(id))
    return;
}

stock StringTimer(const Float:flRealTime, szOutPut[], const iSizeOutPut) {
  static Float:flTime, iMinutes, iSeconds, iMiliSeconds, Float:iMili;
  new string[12];

  flTime = flRealTime;

  if(flTime < 0.0)
    flTime = 0.0;

  iMinutes = floatround(flTime / 60, floatround_floor);
  iSeconds = floatround(flTime - (iMinutes * 60), floatround_floor);
  iMili = floatfract(flRealTime);
  formatex(string, 11, "%.02f", iMili >= 0 ? iMili + 0.005 : iMili - 0.005);
  iMiliSeconds = floatround(str_to_float(string) * 100, floatround_floor);

  formatex(szOutPut, iSizeOutPut, "%02d:%02d.%02d", iMinutes, iSeconds, iMiliSeconds);
}
