/*************************************************
 * Decompiled by Serious and Edited by SyndiShanX
 * Script: shared\weapons\_hacker_tool.gsc
*************************************************/

#using scripts\codescripts\struct;
#using scripts\shared\callbacks_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\killstreaks_shared;
#using scripts\shared\scoreevents_shared;
#using scripts\shared\system_shared;
#using scripts\shared\util_shared;
#using scripts\shared\weapons\_heatseekingmissile;
#using scripts\shared\weapons\_weaponobjects;
#namespace hacker_tool;

function init_shared() {
  level.weaponhackertool = getweapon("pda_hack");
  level.hackertoollostsightlimitms = 1000;
  level.hackertoollockonradius = 25;
  level.hackertoollockonfov = 65;
  level.hackertoolhacktimems = 0.4;
  level.equipmenthackertoolradius = 20;
  level.equipmenthackertooltimems = 100;
  level.carepackagehackertoolradius = 60;
  level.carepackagehackertooltimems = getgametypesetting("crateCaptureTime") * 500;
  level.carepackagefriendlyhackertooltimems = getgametypesetting("crateCaptureTime") * 2000;
  level.carepackageownerhackertooltimems = 250;
  level.vehiclehackertoolradius = 80;
  level.vehiclehackertooltimems = 5000;
  clientfield::register("toplayer", "hacker_tool", 1, 2, "int");
  callback::on_spawned(&on_player_spawned);
}

function on_player_spawned() {
  self endon("disconnect");
  self clearhackertarget(undefined, 0, 1);
  self thread watchhackertooluse();
  self thread watchhackertoolfired();
}

function clearhackertarget(weapon, successfulhack, spawned) {
  self notify("stop_lockon_sound");
  self notify("stop_locked_sound");
  self notify("clearhackertarget");
  self.stingerlocksound = undefined;
  self stoprumble("stinger_lock_rumble");
  self.hackertoollockstarttime = 0;
  self.hackertoollockstarted = 0;
  self.hackertoollockfinalized = 0;
  self.hackertoollocktimeelapsed = 0;
  if(isDefined(weapon)) {
    if(weapon.ishacktoolweapon) {
      self setweaponhackpercent(weapon, 0);
    }
    if(isDefined(self.hackertooltarget)) {
      heatseekingmissile::setfriendlyflags(weapon, self.hackertooltarget);
    }
  }
  if(successfulhack == 0) {
    if(spawned == 0) {
      if(isDefined(self.hackertooltarget)) {
        self playsoundtoplayer("evt_hacker_hack_lost", self);
      }
    }
    self clientfield::set_to_player("hacker_tool", 0);
    self stophackertoolsoundloop();
  }
  if(isDefined(self.hackertooltarget)) {
    heatseekingmissile::targetinghacking(self.hackertooltarget, 0);
  }
  self.hackertooltarget = undefined;
  self weaponlockfree();
  self weaponlocktargettooclose(0);
  self weaponlocknoclearance(0);
  self stoplocalsound(game["locking_on_sound"]);
  self stoplocalsound(game["locked_on_sound"]);
  self heatseekingmissile::destroylockoncanceledmessage();
}

function watchhackertoolfired() {
  self endon("disconnect");
  self endon("death");
  self endon("killhackermonitor");
  while(true) {
    self waittill("hacker_tool_fired", hackertooltarget, weapon);
    if(isDefined(hackertooltarget)) {
      if(isentityhackablecarepackage(hackertooltarget)) {
        scoreevents::givecratecapturemedal(hackertooltarget, self);
        hackertooltarget notify("captured", self, 1);
        if(isDefined(hackertooltarget.owner) && isPlayer(hackertooltarget.owner) && hackertooltarget.owner.team != self.team && isDefined(level.play_killstreak_hacked_dialog)) {
          hackertooltarget.owner[[level.play_killstreak_hacked_dialog]](hackertooltarget.killstreaktype, hackertooltarget.killstreakid, self);
        }
      } else {
        if(isentityhackableweaponobject(hackertooltarget) && isDefined(hackertooltarget.hackertrigger)) {
          hackertooltarget.hackertrigger notify("trigger", self, 1);
          hackertooltarget.previouslyhacked = 1;
          self.throwinggrenade = 0;
        } else {
          if(isDefined(hackertooltarget.killstreak_hackedcallback) && (!isDefined(hackertooltarget.killstreaktimedout) || hackertooltarget.killstreaktimedout == 0)) {
            if(hackertooltarget.killstreak_hackedprotection == 0) {
              if(isDefined(hackertooltarget.owner) && isPlayer(hackertooltarget.owner)) {
                if(isDefined(level.play_killstreak_hacked_dialog)) {
                  hackertooltarget.owner[[level.play_killstreak_hacked_dialog]](hackertooltarget.killstreaktype, hackertooltarget.killstreakid, self);
                }
              }
              self playsoundtoplayer("evt_hacker_fw_success", self);
              hackertooltarget notify("killstreak_hacked", self);
              hackertooltarget.previouslyhacked = 1;
              hackertooltarget[[hackertooltarget.killstreak_hackedcallback]](self);
              if(self util::has_blind_eye_perk_purchased_and_equipped() || self util::has_hacker_perk_purchased_and_equipped()) {
                self addplayerstat("hack_streak_with_blindeye_or_engineer", 1);
              }
            } else {
              if(isDefined(hackertooltarget.owner) && isPlayer(hackertooltarget.owner)) {
                if(isDefined(level.play_killstreak_firewall_hacked_dialog)) {
                  self.hackertooltarget.owner[[level.play_killstreak_firewall_hacked_dialog]](self.hackertooltarget.killstreaktype, self.hackertooltarget.killstreakid);
                }
              }
              self playsoundtoplayer("evt_hacker_ks_success", self);
              scoreevents::processscoreevent("hacked_killstreak_protection", self, hackertooltarget, level.weaponhackertool);
            }
            hackertooltarget.killstreak_hackedprotection = 0;
          } else {
            if(isDefined(hackertooltarget.classname) && hackertooltarget.classname == "grenade") {
              damage = 1;
            } else {
              if(isDefined(hackertooltarget.hackertooldamage)) {
                damage = hackertooltarget.hackertooldamage;
              } else {
                if(isDefined(hackertooltarget.maxhealth)) {
                  damage = hackertooltarget.maxhealth + 1;
                } else {
                  damage = 999999;
                }
              }
            }
            if(isDefined(hackertooltarget.numflares) && hackertooltarget.numflares > 0) {
              damage = 1;
              hackertooltarget.numflares--;
              hackertooltarget heatseekingmissile::missiletarget_playflarefx();
            }
            hackertooltarget dodamage(damage, self.origin, self, self, 0, "MOD_UNKNOWN", 0, weapon);
          }
        }
      }
      if(self util::is_item_purchased("pda_hack")) {
        self addplayerstat("hack_enemy_target", 1);
      }
      self addweaponstat(weapon, "used", 1);
    }
    clearhackertarget(weapon, 1, 0);
    self forceoffhandend();
    if(getdvarint("player_sustainAmmo") == 0) {
      clip_ammo = self getweaponammoclip(weapon);
      clip_ammo--;
      /
      #
      assert(clip_ammo >= 0);
      self setweaponammoclip(weapon, clip_ammo);
    }
    self killstreaks::switch_to_last_non_killstreak_weapon();
  }
}

function watchhackertooluse() {
  self endon("disconnect");
  self endon("death");
  for(;;) {
    self waittill("grenade_pullback", weapon);
    if(weapon.rootweapon == level.weaponhackertool) {
      wait(0.05);
      currentoffhand = self getcurrentoffhand();
      if(self isusingoffhand() && currentoffhand.rootweapon == level.weaponhackertool) {
        self thread hackertooltargetloop(weapon);
        self thread watchhackertoolend(weapon);
        self thread watchforgrenadefire(weapon);
        self thread watchhackertoolinterrupt(weapon);
      }
    }
  }
}

function watchhackertoolinterrupt(weapon) {
  self endon("disconnect");
  self endon("hacker_tool_fired");
  self endon("death");
  self endon("weapon_change");
  self endon("grenade_fire");
  while(true) {
    level waittill("use_interrupt", interrupttarget);
    if(self.hackertooltarget == interrupttarget) {
      clearhackertarget(weapon, 0, 0);
    }
    wait(0.05);
  }
}

function watchhackertoolend(weapon) {
  self endon("disconnect");
  self endon("hacker_tool_fired");
  msg = self util::waittill_any_return("weapon_change", "death", "hacker_tool_fired", "disconnect");
  clearhackertarget(weapon, 0, 0);
  self clientfield::set_to_player("hacker_tool", 0);
  self stophackertoolsoundloop();
}

function watchforgrenadefire(weapon) {
  self endon("disconnect");
  self endon("hacker_tool_fired");
  self endon("weapon_change");
  self endon("death");
  while(true) {
    self waittill("grenade_fire", grenade_instance, grenade_weapon, respawnfromhack);
    if(isDefined(respawnfromhack) && respawnfromhack) {
      continue;
    }
    clearhackertarget(grenade_weapon, 0, 0);
    clip_ammo = self getweaponammoclip(grenade_weapon);
    clip_max_ammo = grenade_weapon.clipsize;
    if(clip_ammo < clip_max_ammo) {
      clip_ammo++;
    }
    self setweaponammoclip(grenade_weapon, clip_ammo);
    break;
  }
}

function playhackertoolsoundloop() {
  if(!isDefined(self.hacker_sound_ent) || (isDefined(self.hacker_alreadyhacked) && self.hacker_alreadyhacked == 1)) {
    self playLoopSound("evt_hacker_device_loop");
    self.hacker_sound_ent = 1;
    self.hacker_alreadyhacked = 0;
  }
}

function stophackertoolsoundloop() {
  self stoploopsound(0.5);
  self.hacker_sound_ent = undefined;
  self.hacker_alreadyhacked = undefined;
}

function hackertooltargetloop(weapon) {
  self endon("disconnect");
  self endon("death");
  self endon("weapon_change");
  self endon("grenade_fire");
  self clientfield::set_to_player("hacker_tool", 1);
  self playhackertoolsoundloop();
  while(true) {
    wait(0.05);
    wait(0.05);
    if(self.hackertoollockfinalized) {
      if(!self isvalidhackertooltarget(self.hackertooltarget, weapon, 0)) {
        self clearhackertarget(weapon, 0, 0);
        continue;
      }
      passed = self hackersoftsighttest(weapon);
      if(!passed) {
        continue;
      }
      self clientfield::set_to_player("hacker_tool", 0);
      self stophackertoolsoundloop();
      heatseekingmissile::targetinghacking(self.hackertooltarget, 0);
      heatseekingmissile::setfriendlyflags(weapon, self.hackertooltarget);
      thread heatseekingmissile::looplocallocksound(game["locked_on_sound"], 0.75);
      self notify("hacker_tool_fired", self.hackertooltarget, weapon);
      return;
    }
    if(self.hackertoollockstarted) {
      if(!self isvalidhackertooltarget(self.hackertooltarget, weapon, 0)) {
        self clearhackertarget(weapon, 0, 0);
        continue;
      }
      lockontime = self getlockontime(self.hackertooltarget, weapon);
      if(lockontime == 0) {
        self clearhackertarget(weapon, 0, 0);
        continue;
      }
      if(self.hackertoollocktimeelapsed == 0) {
        self playlocalsound("evt_hacker_hacking");
        if(isDefined(self.hackertooltarget.owner) && isPlayer(self.hackertooltarget.owner)) {
          if(isDefined(self.hackertooltarget.killstreak_hackedcallback) && (!isDefined(self.hackertooltarget.killstreaktimedout) || self.hackertooltarget.killstreaktimedout == 0)) {
            if(self.hackertooltarget.killstreak_hackedprotection == 0) {
              if(isDefined(level.play_killstreak_being_hacked_dialog)) {
                self.hackertooltarget.owner[[level.play_killstreak_being_hacked_dialog]](self.hackertooltarget.killstreaktype, self.hackertooltarget.killstreakid);
              }
            } else if(isDefined(level.play_killstreak_firewall_being_hacked_dialog)) {
              self.hackertooltarget.owner[[level.play_killstreak_firewall_being_hacked_dialog]](self.hackertooltarget.killstreaktype, self.hackertooltarget.killstreakid);
            }
          }
        }
      }
      self weaponlockstart(self.hackertooltarget);
      self playhackertoolsoundloop();
      if(isDefined(self.hackertooltarget.killstreak_hackedprotection) && self.hackertooltarget.killstreak_hackedprotection == 1) {
        self clientfield::set_to_player("hacker_tool", 3);
      } else {
        self clientfield::set_to_player("hacker_tool", 2);
      }
      heatseekingmissile::targetinghacking(self.hackertooltarget, 1);
      heatseekingmissile::setfriendlyflags(weapon, self.hackertooltarget);
      passed = self hackersoftsighttest(weapon);
      if(!passed) {
        continue;
      }
      if(self.hackertoollostsightlinetime == 0) {
        self.hackertoollocktimeelapsed = self.hackertoollocktimeelapsed + (0.1 * hackingtimescale(self.hackertooltarget));
        hackpercentage = (self.hackertoollocktimeelapsed / lockontime) * 100;
        self setweaponhackpercent(weapon, hackpercentage);
        heatseekingmissile::setfriendlyflags(weapon, self.hackertooltarget);
      } else {
        self.hackertoollocktimeelapsed = self.hackertoollocktimeelapsed - (0.1 * hackingtimenolineofsightscale(self.hackertooltarget));
        if(self.hackertoollocktimeelapsed < 0) {
          self.hackertoollocktimeelapsed = 0;
          self clearhackertarget(weapon, 0, 0);
          continue;
        }
        hackpercentage = (self.hackertoollocktimeelapsed / lockontime) * 100;
        self setweaponhackpercent(weapon, hackpercentage);
        heatseekingmissile::setfriendlyflags(weapon, self.hackertooltarget);
      }
      if(self.hackertoollocktimeelapsed < lockontime) {
        continue;
      }
      assert(isDefined(self.hackertooltarget));
      self notify("stop_lockon_sound");
      self.hackertoollockfinalized = 1;
      self weaponlockfinalize(self.hackertooltarget);
      continue;
    }
    if(self isempjammed()) {
      self heatseekingmissile::destroylockoncanceledmessage();
      continue;
    }
    besttarget = self getbesthackertooltarget(weapon);
    if(!isDefined(besttarget)) {
      self stophackertoolsoundloop();
      self heatseekingmissile::destroylockoncanceledmessage();
      continue;
    }
    if(!self heatseekingmissile::locksighttest(besttarget)) {
      self stophackertoolsoundloop();
      self heatseekingmissile::destroylockoncanceledmessage();
      continue;
    }
    if(self heatseekingmissile::locksighttest(besttarget) && isDefined(besttarget.lockondelay) && besttarget.lockondelay) {
      self stophackertoolsoundloop();
      self heatseekingmissile::displaylockoncanceledmessage();
      continue;
    }
    self heatseekingmissile::destroylockoncanceledmessage();
    if(isentitypreviouslyhacked(besttarget)) {
      if(!isDefined(self.hacker_sound_ent) || (isDefined(self.hacker_alreadyhacked) && self.hacker_alreadyhacked == 0)) {
        self.hacker_sound_ent = 1;
        self.hacker_alreadyhacked = 1;
        self playLoopSound("evt_hacker_unhackable_loop");
      }
      continue;
    } else {
      self stophackertoolsoundloop();
    }
    heatseekingmissile::initlockfield(besttarget);
    self.hackertooltarget = besttarget;
    self thread watchtargetentityupdate(besttarget);
    self.hackertoollockstarttime = gettime();
    self.hackertoollockstarted = 1;
    self.hackertoollostsightlinetime = 0;
    self.hackertoollocktimeelapsed = 0;
    self setweaponhackpercent(weapon, 0);
    if(isDefined(self.hackertooltarget)) {
      heatseekingmissile::setfriendlyflags(weapon, self.hackertooltarget);
    }
  }
}

function watchtargetentityupdate(besttarget) {
  self endon("death");
  self endon("disconnect");
  self notify("watchtargetentityupdate");
  self endon("watchtargetentityupdate");
  self endon("clearhackertarget");
  besttarget endon("death");
  besttarget waittill("hackertool_update_ent", newentity);
  heatseekingmissile::initlockfield(newentity);
  self.hackertooltarget = newentity;
}

function getbesthackertooltarget(weapon) {
  targetsvalid = [];
  targetsall = arraycombine(target_getarray(), level.missileentities, 0, 0);
  targetsall = arraycombine(targetsall, level.hackertooltargets, 0, 0);
  for(idx = 0; idx < targetsall.size; idx++) {
    target_ent = targetsall[idx];
    if(!isDefined(target_ent) || !isDefined(target_ent.owner)) {
      continue;
    }
    if(getdvarstring("") == "") {
      if(self iswithinhackertoolreticle(targetsall[idx], weapon)) {
        targetsvalid[targetsvalid.size] = targetsall[idx];
      }
      continue;
    }
    if(level.teambased || level.use_team_based_logic_for_locking_on === 1) {
      if(isentityhackablecarepackage(target_ent)) {
        if(self cantargetentity(target_ent, weapon)) {
          targetsvalid[targetsvalid.size] = target_ent;
        }
      } else {
        if(isDefined(target_ent.team)) {
          if(target_ent.team != self.team) {
            if(self cantargetentity(target_ent, weapon)) {
              targetsvalid[targetsvalid.size] = target_ent;
            }
          }
        } else if(isDefined(target_ent.owner.team)) {
          if(target_ent.owner.team != self.team) {
            if(self cantargetentity(target_ent, weapon)) {
              targetsvalid[targetsvalid.size] = target_ent;
            }
          }
        }
      }
      continue;
    }
    if(self iswithinhackertoolreticle(target_ent, weapon)) {
      if(isentityhackablecarepackage(target_ent)) {
        if(self cantargetentity(target_ent, weapon)) {
          targetsvalid[targetsvalid.size] = target_ent;
        }
        continue;
      }
      if(isDefined(target_ent.owner) && self != target_ent.owner) {
        if(self cantargetentity(target_ent, weapon)) {
          targetsvalid[targetsvalid.size] = target_ent;
        }
      }
    }
  }
  chosenent = undefined;
  if(targetsvalid.size != 0) {
    chosenent = targetsvalid[0];
  }
  return chosenent;
}

function cantargetentity(target, weapon) {
  if(!self iswithinhackertoolreticle(target, weapon)) {
    return false;
  }
  if(!isvalidhackertooltarget(target, weapon, 1)) {
    return false;
  }
  return true;
}

function iswithinhackertoolreticle(target, weapon) {
  radiusinner = gethackertoolinnerradius(target);
  radiusouter = gethackertoolouterradius(target);
  if(target_scaleminmaxradius(target, self, level.hackertoollockonfov, radiusinner, radiusouter) > 0) {
    return 1;
  }
  return target_boundingisunderreticle(self, target, weapon.lockonmaxrange);
}

function hackingtimescale(target) {
  hackratio = 1;
  radiusinner = gethackertoolinnerradius(target);
  radiusouter = gethackertoolouterradius(target);
  if(radiusinner != radiusouter) {
    scale = target_scaleminmaxradius(target, self, level.hackertoollockonfov, radiusinner, radiusouter);
    scale = ((scale * scale) * scale) * scale;
    hacktime = lerpfloat(gethackoutertime(target), gethacktime(target), scale);
    hackertooldebugtext = getdvarint("", 0);
    if(hackertooldebugtext) {
      print3d(target.origin, (((("" + scale) + "") + radiusinner) + "") + radiusouter, (0, 0, 0), 1, hackertooldebugtext, 2);
    }
    assert(hacktime > 0);
    hackratio = gethacktime(target) / hacktime;
    if(!isDefined(hackratio)) {
      hackratio = 1;
    }
  }
  return hackratio;
}

function hackingtimenolineofsightscale(target) {
  hackratio = 1;
  if(isDefined(target.killstreakhacklostlineofsighttimems) && target.killstreakhacklostlineofsighttimems > 0) {
    /
    #
    assert(target.killstreakhacklostlineofsighttimems > 0);
    hackratio = 1000 / target.killstreakhacklostlineofsighttimems;
  }
  return hackratio;
}

function isentityhackableweaponobject(entity) {
  if(isDefined(entity.classname) && entity.classname == "grenade") {
    if(isDefined(entity.weapon)) {
      watcher = weaponobjects::getweaponobjectwatcherbyweapon(entity.weapon);
      if(isDefined(watcher)) {
        if(watcher.hackable) {
          /
          #
          assert(isDefined(watcher.hackertoolradius));
          assert(isDefined(watcher.hackertooltimems));
          return true;
        }
      }
    }
  }
  return false;
}

function getweaponobjecthackerradius(entity) {
  /
  #
  assert(isDefined(entity.classname));
  assert(isDefined(entity.weapon));
  watcher = weaponobjects::getweaponobjectwatcherbyweapon(entity.weapon);
  /
  #
  assert(watcher.hackable);
  assert(isDefined(watcher.hackertoolradius));
  return watcher.hackertoolradius;
}

function getweaponobjecthacktimems(entity) {
  /
  #
  assert(isDefined(entity.classname));
  assert(isDefined(entity.weapon));
  watcher = weaponobjects::getweaponobjectwatcherbyweapon(entity.weapon);
  /
  #
  assert(watcher.hackable);
  assert(isDefined(watcher.hackertooltimems));
  return watcher.hackertooltimems;
}

function isentityhackablecarepackage(entity) {
  if(isDefined(entity.model)) {
    return entity.model == "wpn_t7_care_package_world";
  }
  return 0;
}

function isvalidhackertooltarget(ent, weapon, allowhacked) {
  if(!isDefined(ent)) {
    return false;
  }
  if(self util::isusingremote()) {
    return false;
  }
  if(self isempjammed()) {
    return false;
  }
  if(!(target_istarget(ent) || (isDefined(ent.allowhackingaftercloak) && ent.allowhackingaftercloak == 1)) && !isentityhackableweaponobject(ent) && !isinarray(level.hackertooltargets, ent)) {
    return false;
  }
  if(isentityhackableweaponobject(ent)) {
    if(distancesquared(self.origin, ent.origin) > (weapon.lockonmaxrange * weapon.lockonmaxrange)) {
      return false;
    }
  }
  if(allowhacked == 0 && isentitypreviouslyhacked(ent)) {
    return false;
  }
  return true;
}

function isentitypreviouslyhacked(entity) {
  if(isDefined(entity.previouslyhacked) && entity.previouslyhacked) {
    return true;
  }
  return false;
}

function hackersoftsighttest(weapon) {
  passed = 1;
  lockontime = 0;
  if(isDefined(self.hackertooltarget)) {
    lockontime = self getlockontime(self.hackertooltarget, weapon);
  }
  if(lockontime == 0 || self isempjammed()) {
    self clearhackertarget(weapon, 0, 0);
    passed = 0;
  } else {
    if(iswithinhackertoolreticle(self.hackertooltarget, weapon) && self heatseekingmissile::locksighttest(self.hackertooltarget)) {
      self.hackertoollostsightlinetime = 0;
    } else {
      if(self.hackertoollostsightlinetime == 0) {
        self.hackertoollostsightlinetime = gettime();
      }
      timepassed = gettime() - self.hackertoollostsightlinetime;
      lostlineofsighttimelimitmsec = level.hackertoollostsightlimitms;
      if(isDefined(self.hackertooltarget.killstreakhacklostlineofsightlimitms)) {
        lostlineofsighttimelimitmsec = self.hackertooltarget.killstreakhacklostlineofsightlimitms;
      }
      if(timepassed >= lostlineofsighttimelimitmsec) {
        self clearhackertarget(weapon, 0, 0);
        passed = 0;
      }
    }
  }
  return passed;
}

function registerwithhackertool(radius, hacktimems) {
  self endon("death");
  if(isDefined(radius)) {
    self.hackertoolradius = radius;
  } else {
    self.hackertoolradius = level.hackertoollockonradius;
  }
  if(isDefined(hacktimems)) {
    self.hackertooltimems = hacktimems;
  } else {
    self.hackertooltimems = level.hackertoolhacktimems;
  }
  self thread watchhackableentitydeath();
  level.hackertooltargets[level.hackertooltargets.size] = self;
}

function watchhackableentitydeath() {
  self waittill("death");
  arrayremovevalue(level.hackertooltargets, self);
}

function gethackertoolinnerradius(target) {
  radius = level.hackertoollockonradius;
  if(isentityhackablecarepackage(target)) {
    /
    #
    assert(isDefined(target.hackertoolradius));
    radius = target.hackertoolradius;
  } else {
    if(isentityhackableweaponobject(target)) {
      radius = getweaponobjecthackerradius(target);
    } else {
      if(isDefined(target.hackertoolinnerradius)) {
        radius = target.hackertoolinnerradius;
      } else if(isDefined(target.hackertoolradius)) {
        radius = target.hackertoolradius;
      }
    }
  }
  return radius;
}

function gethackertoolouterradius(target) {
  radius = level.hackertoollockonradius;
  if(isentityhackablecarepackage(target)) {
    /
    #
    assert(isDefined(target.hackertoolradius));
    radius = target.hackertoolradius;
  } else {
    if(isentityhackableweaponobject(target)) {
      radius = getweaponobjecthackerradius(target);
    } else {
      if(isDefined(target.hackertoolouterradius)) {
        radius = target.hackertoolouterradius;
      } else if(isDefined(target.hackertoolradius)) {
        radius = target.hackertoolradius;
      }
    }
  }
  return radius;
}

function gethacktime(target) {
  time = 500;
  if(isentityhackablecarepackage(target)) {
    /
    #
    assert(isDefined(target.hackertooltimems));
    if(isDefined(target.owner) && target.owner == self) {
      time = level.carepackageownerhackertooltimems;
    } else {
      if(isDefined(target.owner) && target.owner.team == self.team) {
        time = level.carepackagefriendlyhackertooltimems;
      } else {
        time = level.carepackagehackertooltimems;
      }
    }
  } else {
    if(isentityhackableweaponobject(target)) {
      time = getweaponobjecthacktimems(target);
    } else {
      if(isDefined(target.hackertoolinnertimems)) {
        time = target.hackertoolinnertimems;
      } else {
        time = level.vehiclehackertooltimems;
      }
    }
  }
  return time;
}

function gethackoutertime(target) {
  time = 500;
  if(isentityhackablecarepackage(target)) {
    /
    #
    assert(isDefined(target.hackertooltimems));
    if(isDefined(target.owner) && target.owner == self) {
      time = level.carepackageownerhackertooltimems;
    } else {
      if(isDefined(target.owner) && target.owner.team == self.team) {
        time = level.carepackagefriendlyhackertooltimems;
      } else {
        time = level.carepackagehackertooltimems;
      }
    }
  } else {
    if(isentityhackableweaponobject(target)) {
      time = getweaponobjecthacktimems(target);
    } else {
      if(isDefined(target.hackertooloutertimems)) {
        time = target.hackertooloutertimems;
      } else {
        time = level.vehiclehackertooltimems;
      }
    }
  }
  return time;
}

function getlockontime(target, weapon) {
  locklengthms = self gethacktime(self.hackertooltarget);
  if(locklengthms == 0) {
    return 0;
  }
  lockonspeed = weapon.lockonspeed;
  if(lockonspeed <= 0) {
    lockonspeed = 1000;
  }
  return locklengthms / lockonspeed;
}

function tunables() {
  while(true) {
    level.hackertoollostsightlimitms = getdvarint("", 1000);
    level.hackertoollockonradius = getdvarfloat("", 20);
    level.hackertoollockonfov = getdvarint("", 65);
    wait(1);
  }
}