Gattaca1996

Guardian.cs (Part 2)

Sep 4th, 2026 (edited)
25
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 278.44 KB | Gaming | 0 0
  1. // ===== Guardian 1.7.10  PASTEBIN PART 2 of 2 =====
  2. // Continues from part 1 at #region _map_. Append this file under part 1.
  3. // Delete these header lines, then concatenate. Result must match Guardian.cs 1.7.10.
  4.  
  5.         #region _map_
  6.  
  7.         private class Map
  8.         {
  9.             public class Building
  10.             {
  11.                 public static bool HasPrivilege(DecayEntity entity) =>
  12.                     entity.GetBuildingPrivilege() != null;
  13.  
  14.                 public static bool InFoundation(Vector3 position)
  15.                 {
  16.                     try
  17.                     {
  18.                         RaycastHit hit;
  19.  
  20.                         Physics.queriesHitBackfaces = true;
  21.  
  22.                         if(Physics.Raycast(position, Vector3.up, out hit, 4.5f, Layers.Mask.Construction, QueryTriggerInteraction.Ignore))
  23.                         {
  24.                             var block = hit.GetEntity() as BuildingBlock;
  25.  
  26.                             if((block != null) && Hit.IsFoundation(hit))
  27.                             {
  28.                                 switch(block.grade)
  29.                                 {
  30.                                 case BuildingGrade.Enum.Stone:
  31.                                 case BuildingGrade.Enum.Metal:
  32.                                 case BuildingGrade.Enum.TopTier:
  33.                                     return true;
  34.                                 }
  35.                             }
  36.                         }
  37.                     }
  38.                     finally
  39.                     {
  40.                         Physics.queriesHitBackfaces = false;
  41.                     }
  42.  
  43.                     return false;
  44.                 }
  45.  
  46.                 public static bool IsNearby(Vector3 position, bool check_privilege = true)
  47.                 {
  48.                     // Facepunch removed Pool.GetList<T>() (CS0117). Use Pool.Get<List<T>>() instead.
  49.                     var blocks = Pool.Get<List<BuildingBlock>>();
  50.  
  51.                     Vis.Entities(position, 16.0f, blocks, Layers.Mask.Construction, QueryTriggerInteraction.Ignore);
  52.  
  53.                     bool nearby = false;
  54.  
  55.                     foreach(var block in blocks)
  56.                     {
  57.                         if(check_privilege)
  58.                         {
  59.                             if(HasPrivilege(block))
  60.                             {
  61.                                 nearby = true; break;
  62.                             }
  63.                         }
  64.                         else
  65.                         {
  66.                             nearby = true; break;
  67.                         }
  68.                     }
  69.  
  70.                     Pool.FreeUnmanaged<BuildingBlock>(ref blocks);
  71.  
  72.                     return nearby;
  73.                 }
  74.             }
  75.  
  76.             public class Cave
  77.             {
  78.                 public static bool IsInside(Vector3 position)
  79.                 {
  80.                     foreach(var hit in Physics.RaycastAll(position, Vector3.up, 125f, Layers.Mask.World))
  81.                     {
  82.                         if(Hit.IsCave(hit) || Hit.IsRock(hit))
  83.                         {
  84.                             return true;
  85.                         }
  86.                     }
  87.  
  88.                     foreach(var hit in Physics.RaycastAll(position, Vector3.down, 125f, Layers.Mask.World))
  89.                     {
  90.                         if(Hit.IsCave(hit))
  91.                         {
  92.                             return true;
  93.                         }
  94.                     }
  95.  
  96.                     return false;
  97.                 }
  98.             }
  99.  
  100.             public class Collider
  101.             {
  102.                 public static string Info(Vector3 position)
  103.                 {
  104.                     var colliders = new List<string>();
  105.  
  106.                     foreach(var hit in Physics.RaycastAll(position, Vector3.up, 50.0f, Layers.Mask.World, QueryTriggerInteraction.Ignore))
  107.                     {
  108.                         colliders.Add($"{hit.collider.name} ({hit.distance}m)");
  109.                     }
  110.  
  111.                     try
  112.                     {
  113.                         RaycastHit hit;
  114.  
  115.                         Physics.queriesHitBackfaces = true;
  116.  
  117.                         if(Physics.Raycast(new Vector3(position.x, position.y + 0.1f, position.z), Vector3.up, out hit, 4.5f, Layers.Mask.Construction, QueryTriggerInteraction.Ignore))
  118.                         {
  119.                             if(Hit.IsFoundation(hit))
  120.                             {
  121.                                 colliders.Add($"foundation ({hit.distance}m)");
  122.                             }
  123.                         }
  124.                     }
  125.                     finally
  126.                     {
  127.                         Physics.queriesHitBackfaces = false;
  128.                     }
  129.  
  130.                     var up = colliders.Count > 0 ? $"\nUp: {string.Join(", ", colliders)}" : string.Empty;
  131.  
  132.                     colliders.Clear();
  133.  
  134.                     foreach(var hit in Physics.RaycastAll(position, Vector3.down, 50.0f, Layers.Mask.World, QueryTriggerInteraction.Ignore))
  135.                     {
  136.                         colliders.Add($"{hit.collider.name} ({hit.distance}m)");
  137.                     }
  138.  
  139.                     var dn = colliders.Count > 0 ? $"\nDn: {string.Join(", ", colliders)}" : string.Empty;
  140.  
  141.                     return $"{up}{dn}";
  142.                 }
  143.             }
  144.  
  145.             public class Entities
  146.             {
  147.                 public static bool InRange(Vector3 position, float radius = 3.0f)
  148.                 {
  149.                     // Facepunch removed Pool.GetList<T>() / Pool.FreeList (CS0117).
  150.                     var entities = Pool.Get<List<DecayEntity>>();
  151.  
  152.                     Vis.Entities(position, radius, entities);
  153.  
  154.                     bool nearby = entities.Count > 0;
  155.  
  156.                     Pool.FreeUnmanaged<DecayEntity>(ref entities);
  157.  
  158.                     return nearby;
  159.                 }
  160.             }
  161.  
  162.             public static string Grid(Vector3 position)
  163.             {
  164.                 const double scale = 1.0 / 146.304;
  165.  
  166.                 float normal = World.Size >> 1;
  167.  
  168.                 if((Math.Abs(position.x) > normal) || (Math.Abs(position.z) > normal))
  169.                 {
  170.                     return string.Empty;
  171.                 }
  172.  
  173.                 int x = (int)((normal + position.x) * scale);
  174.                 int y = (int)((normal - position.z) * scale);
  175.  
  176.                 int r, q = Math.DivRem(x, 26, out r);
  177.  
  178.                 if(q > 0)
  179.                 {
  180.                     return $"{Convert.ToChar(64 + q)}{Convert.ToChar(65 + r)}{y}";
  181.                 }
  182.                 else
  183.                 {
  184.                     return $"{Convert.ToChar(65 + r)}{y}";
  185.                 }
  186.             }
  187.  
  188.             private class Hit
  189.             {
  190.                 private static string Collider(RaycastHit hit) =>
  191.                     hit.collider?.name.ToLower() ?? string.Empty;
  192.  
  193.                 public static bool IsBunker(RaycastHit hit) =>
  194.                     IsBunker(Collider(hit));
  195.                 public static bool IsBunker(string collider) =>
  196.                     collider.Contains("bunker.");
  197.  
  198.                 public static bool IsCave(RaycastHit hit) =>
  199.                     IsCave(Collider(hit));
  200.                 public static bool IsCave(string collider) =>
  201.                     collider.Contains("cave_");
  202.  
  203.                 public static bool IsCorridor(RaycastHit hit) =>
  204.                     IsCorridor(Collider(hit));
  205.                 public static bool IsCorridor(string collider) =>
  206.                     collider.Contains("corridor_");
  207.  
  208.                 public static bool IsDuct(RaycastHit hit) =>
  209.                     IsDuct(Collider(hit));
  210.                 public static bool IsDuct(string collider) =>
  211.                     collider.Contains("duct_");
  212.  
  213.                 public static bool IsFoundation(RaycastHit hit) =>
  214.                     IsFoundation(Collider(hit));
  215.                 public static bool IsFoundation(string collider) =>
  216.                     collider.Contains("foundation.");
  217.  
  218.                 public static bool IsMine(RaycastHit hit) =>
  219.                     IsMine(Collider(hit));
  220.                 public static bool IsMine(string collider) =>
  221.                     collider.Contains("mine_tnl_");
  222.  
  223.                 public static bool IsRoad(RaycastHit hit) =>
  224.                     IsRoad(Collider(hit));
  225.                 public static bool IsRoad(string collider) =>
  226.                     collider.Contains("road");
  227.  
  228.                 public static bool IsRock(RaycastHit hit) =>
  229.                     IsRock(Collider(hit));
  230.                 public static bool IsRock(string collider) =>
  231.                     collider.Contains("rock_");
  232.  
  233.                 public static bool IsStairwell(RaycastHit hit) =>
  234.                     IsStairwell(Collider(hit));
  235.                 public static bool IsStairwell(string collider) =>
  236.                     collider.Contains("stairwell_");
  237.  
  238.                 public static bool IsTunnel(RaycastHit hit) =>
  239.                     IsTunnel(Collider(hit));
  240.                 public static bool IsTunnel(string collider) =>
  241.                     collider.Contains("tunnel");
  242.  
  243.                 public static bool IsUnderground(RaycastHit hit) =>
  244.                     IsUnderground(Collider(hit));
  245.                 public static bool IsUnderground(string collider)
  246.                 {
  247.                     foreach(var partition in collider.Split('_', '.'))
  248.                     {
  249.                         switch(partition)
  250.                         {
  251.                         case "bunker":
  252.                         case "corridor":
  253.                         case "duct":
  254.                         case "mine":
  255.                         case "stairwell":
  256.                         case "tunnel":
  257.                             return true;
  258.                         }
  259.                     }
  260.  
  261.                     return false;
  262.                 }
  263.             }
  264.  
  265.             public static bool InRange(Vector3 a, Vector3 b, float distance) =>
  266.                 (a - b).sqrMagnitude <= distance * distance;
  267.             public static bool InRange2D(Vector3 a, Vector3 b, float distance) =>
  268.                 InRange(new Vector3(a.x, 0f, a.z), new Vector3(b.x, 0f, b.z), distance);
  269.  
  270.             public static void Load()
  271.             {
  272.                 _instance.timer.In(0.1f, () => Monument.Load());
  273.             }
  274.  
  275.             public class Monument
  276.             {
  277.                 private static SparseMap global = new SparseMap(4.0f);
  278.                 private static SparseMap tunnel = new SparseMap(4.0f);
  279.  
  280.                 public static bool IsNearby(Vector3 position) =>
  281.                     global.Test(position);
  282.  
  283.                 public static bool IsTunnel(Vector3 position) =>
  284.                     tunnel.Test(position);
  285.  
  286.                 public static void Load()
  287.                 {
  288.                     foreach(var monument in TerrainMeta.Path.Monuments)
  289.                     {
  290.                         var center = monument.transform.position;
  291.                         var radius = Range(monument);
  292.  
  293.                         global.Set(center, radius);
  294.  
  295.                         if(monument.name.Contains("entrance"))
  296.                         {
  297.                             tunnel.Set(center, radius);
  298.                         }
  299.                     }
  300.                 }
  301.  
  302.                 private static float Range(MonumentInfo monument)
  303.                 {
  304.                     var separator = monument.name.LastIndexOf('/');
  305.  
  306.                     switch((separator > 0) ? monument.name.Substring(separator + 1).Replace(".prefab", "") : monument.name)
  307.                     {
  308.                     case "airfield_1":              return 255f;
  309.                     case "bandit_town":             return 105f;
  310.                     case "cave_large_hard":
  311.                     case "cave_large_medium":
  312.                     case "cave_large_sewers_hard":
  313.                     case "cave_medium_easy":
  314.                     case "cave_medium_hard":
  315.                     case "cave_medium_medium":
  316.                     case "cave_small_easy":
  317.                     case "cave_small_hard":
  318.                     case "cave_small_medium":       return  75f;
  319.                     case "compound":                return 255f;
  320.                     case "entrance":                return  20f;
  321.                     case "excavator_1":             return 150f;
  322.                     case "fishing_village_a":
  323.                     case "fishing_village_b":
  324.                     case "fishing_village_c":       return  55f;
  325.                     case "gas_station_1":           return  60f;
  326.                     case "harbor_1":
  327.                     case "harbor_2":                return 135f;
  328.                     case "junkyard_1":              return 105f;
  329.                     case "launch_site_1":           return 245f;
  330.                     case "lighthouse":              return  50f;
  331.                     case "military_tunnel_1":       return 105f;
  332.                     case "mining_quarry_a":
  333.                     case "mining_quarry_b":
  334.                     case "mining_quarry_c":         return  30f;
  335.                     case "OilrigAI":                return 100f;
  336.                     case "OilrigAI2":               return 200f;
  337.                     case "power_sub_big_1":
  338.                     case "power_sub_big_2":         return  30f;
  339.                     case "power_sub_small_1":
  340.                     case "power_sub_small_2":       return  25f;
  341.                     case "powerplant_1":            return 145f;
  342.                     case "radtown_small_3":         return  95f;
  343.                     case "satellite_dish":          return  85f;
  344.                     case "sphere_tank":             return  75f;
  345.                     case "stables_a":
  346.                     case "stables_b":               return  80f;
  347.                     case "supermarket_1":           return  60f;
  348.                     case "swamp_a":
  349.                     case "swamp_b":                 return  30f;
  350.                     case "swamp_c":                 return  55f;
  351.                     case "trainyard_1":             return 145f;
  352.                     case "warehouse":               return  50f;
  353.                     case "water_treatment_plant_1": return 175f;
  354.                     case "water_well_a":
  355.                     case "water_well_b":
  356.                     case "water_well_c":
  357.                     case "water_well_d":
  358.                     case "water_well_e":            return  30f;
  359.                     }
  360.  
  361.                     return 50.0f;
  362.                 }
  363.  
  364.                 public static void Unload()
  365.                 {
  366.                     global.Clear();
  367.                     tunnel.Clear();
  368.                 }
  369.             }
  370.  
  371.             public class Position
  372.             {
  373.                 [Flags]
  374.                 public enum Check : ulong
  375.                 {
  376.                     None     =  0ul,
  377.                     Building =  1ul,
  378.                     Entities =  1ul << 1,
  379.                     Monument =  1ul << 2,
  380.                     Road     =  1ul << 3,
  381.                     Terrain  =  1ul << 4,
  382.                     Water    =  1ul << 5,
  383.                     All      = ~0ul
  384.                 }
  385.  
  386.                 private static bool HasCheck(Check checks, Check check) =>
  387.                     (checks & check) == check;
  388.  
  389.                 public static Vector3 Random(Check checks = Check.All)
  390.                 {
  391.                     var max = (float)(World.Size >> 1);
  392.                     var min = -max;
  393.  
  394.                     Vector3 position;
  395.  
  396.                     do
  397.                     {
  398.                         position = Surface(Random(min, max, min, max));
  399.  
  400.                         if(HasCheck(checks, Check.Terrain))
  401.                         {
  402.                             if(!Terrain.IsSurface(position))
  403.                             {
  404.                                 continue;
  405.                             }
  406.                         }
  407.                         else if(HasCheck(checks, Check.Water) && !Water.IsSurface(position))
  408.                         {
  409.                             continue;
  410.                         }
  411.  
  412.                         if(HasCheck(checks, Check.Monument) && Monument.IsNearby(position))
  413.                         {
  414.                             continue;
  415.                         }
  416.  
  417.                         if(HasCheck(checks, Check.Road) && Road.IsNearby(position))
  418.                         {
  419.                             continue;
  420.                         }
  421.  
  422.                         if(HasCheck(checks, Check.Building) && Building.IsNearby(position))
  423.                         {
  424.                             continue;
  425.                         }
  426.  
  427.                         if(HasCheck(checks, Check.Entities) && Entities.InRange(position))
  428.                         {
  429.                             continue;
  430.                         }
  431.                     }
  432.                     while(Rock.IsInside(position));
  433.  
  434.                     return position;
  435.                 }
  436.                 private static Vector3 Random(float min_x, float max_x, float min_z, float max_z) =>
  437.                     new Vector3(Core.Random.Range(min_x, max_x), 0.0f, Core.Random.Range(min_z, max_z));
  438.             }
  439.  
  440.             public class Road
  441.             {
  442.                 public static bool IsNearby(Vector3 position)
  443.                 {
  444.                     position = Terrain.Level(position, 0.5f);
  445.  
  446.                     foreach(var hit in Physics.RaycastAll(position, Vector3.down, 25.0f, Layers.Mask.World, QueryTriggerInteraction.Ignore))
  447.                     {
  448.                         if(Hit.IsRoad(hit))
  449.                         {
  450.                             return true;
  451.                         }
  452.                     }
  453.  
  454.                     return false;
  455.                 }
  456.             }
  457.  
  458.             public class Rock
  459.             {
  460.                 public static bool IsInside(Vector3 position, bool check_cave = true)
  461.                 {
  462.                     if(check_cave && Cave.IsInside(position))
  463.                     {
  464.                         return false;
  465.                     }
  466.  
  467.                     try
  468.                     {
  469.                         RaycastHit hit;
  470.  
  471.                         Physics.queriesHitBackfaces = true;
  472.  
  473.                         if(Physics.Raycast(position, Vector3.up, out hit, 25.0f, Layers.Mask.World, QueryTriggerInteraction.Ignore))
  474.                         {
  475.                             return Hit.IsRock(hit);
  476.                         }
  477.                     }
  478.                     finally
  479.                     {
  480.                         Physics.queriesHitBackfaces = false;
  481.                     }
  482.  
  483.                     return false;
  484.                 }
  485.             }
  486.  
  487.             public static Vector3 Surface(Vector3 position, float offset = 0.0f) =>
  488.                 Terrain.IsSurface(position) ? Terrain.Level(position, offset) : Water.Level(position, offset);
  489.  
  490.             public class Terrain
  491.             {
  492.                 public static float Height(Vector3 position) =>
  493.                     TerrainMeta.HeightMap.GetHeight(position);
  494.  
  495.                 public static bool IsInside(Vector3 position, bool check_cave = true)
  496.                 {
  497.                     var elevation = Height(position);
  498.  
  499.                     if((elevation - 1.0f) > position.y)
  500.                     {
  501.                         float c_x = position.x + 1.0f, f_x = position.x - 1.0f;
  502.                         float c_z = position.z + 1.0f, f_z = position.z - 1.0f;
  503.  
  504.                         elevation = Math.Min(elevation, Height(new Vector3(c_x, 0, c_z)));
  505.                         elevation = Math.Min(elevation, Height(new Vector3(c_x, 0, f_z)));
  506.                         elevation = Math.Min(elevation, Height(new Vector3(f_x, 0, c_z)));
  507.                         elevation = Math.Min(elevation, Height(new Vector3(f_x, 0, f_z)));
  508.  
  509.                         if((elevation - 1.0f) > position.y)
  510.                         {
  511.                             if(check_cave && Cave.IsInside(position))
  512.                             {
  513.                                 return false;
  514.                             }
  515.                             else if(Underground.IsInside(position))
  516.                             {
  517.                                 return false;
  518.                             }
  519.                             else if(Monument.IsNearby(position))
  520.                             {
  521.                                 return (elevation - 51.0f) > position.y;
  522.                             }
  523.  
  524.                             return true;
  525.                         }
  526.                     }
  527.  
  528.                     return false;
  529.                 }
  530.  
  531.                 public static bool IsSurface(Vector3 position) =>
  532.                     Height(position) > Water.Height(position);
  533.  
  534.                 public static Vector3 Level(Vector3 position, float offset = 0.0f) =>
  535.                     new Vector3(position.x, Height(position) + offset, position.z);
  536.             }
  537.  
  538.             public class Underground
  539.             {
  540.                 public static bool IsInside(Vector3 position)
  541.                 {
  542.                     if(Monument.IsTunnel(position))
  543.                     {
  544.                         return true;
  545.                     }
  546.  
  547.                     foreach(var hit in Physics.RaycastAll(position, Vector3.up, 25.0f, Layers.Mask.World, QueryTriggerInteraction.Ignore))
  548.                     {
  549.                         if(Hit.IsUnderground(hit))
  550.                         {
  551.                             return true;
  552.                         }
  553.                     }
  554.  
  555.                     foreach(var hit in Physics.RaycastAll(position, Vector3.down, 25.0f, Layers.Mask.World, QueryTriggerInteraction.Ignore))
  556.                     {
  557.                         if(Hit.IsUnderground(hit))
  558.                         {
  559.                             return true;
  560.                         }
  561.                     }
  562.  
  563.                     return false;
  564.                 }
  565.             }
  566.  
  567.             public static void Unload() => Monument.Unload();
  568.  
  569.             public class Water
  570.             {
  571.                 public static float Height(Vector3 position) =>
  572.                     TerrainMeta.WaterMap.GetHeight(position);
  573.  
  574.                 public static bool IsSurface(Vector3 position) =>
  575.                     Height(position) > Terrain.Height(position);
  576.  
  577.                 public static Vector3 Level(Vector3 position, float offset = 0.0f) =>
  578.                     new Vector3(position.x, Height(position) + offset, position.z);
  579.             }
  580.         }
  581.  
  582.         #endregion _map_
  583.  
  584.         #region _permissions_
  585.  
  586.         private class Permissions
  587.         {
  588.             private static string PERMISSION_ADMIN;
  589.             private static string PERMISSION_ALL;
  590.             private static string PERMISSION_IGNORE;
  591.             private static string PERMISSION_PREFIX;
  592.  
  593.             private static readonly HashSet<ulong> all      = new HashSet<ulong>();
  594.             private static readonly HashSet<ulong> admin    = new HashSet<ulong>();
  595.             private static readonly HashSet<ulong> ignore   = new HashSet<ulong>();
  596.  
  597.             public static bool Admin(ulong userid, bool forced = false) =>
  598.                 (forced ? HasPermission(userid, PERMISSION_ADMIN) : admin.Contains(userid)) || All(userid, forced);
  599.  
  600.             public static bool All(ulong userid, bool forced = false) =>
  601.                 (forced ? HasPermission(userid, PERMISSION_ALL) : all.Contains(userid));
  602.  
  603.             public class Bypass
  604.             {
  605.                 private static string PERMISSION_PREFIX;
  606.                 private static string PERMISSION_STEAM;
  607.                 private static string PERMISSION_VPN;
  608.  
  609.                 private static readonly HashSet<ulong> steam = new HashSet<ulong>();
  610.                 private static readonly HashSet<ulong> vpn   = new HashSet<ulong>();
  611.  
  612.                 public class AntiCheat
  613.                 {
  614.                     private static readonly HashSet<ulong> aim        = new HashSet<ulong>();
  615.                     private static readonly HashSet<ulong> firerate   = new HashSet<ulong>();
  616.                     private static readonly HashSet<ulong> gravity    = new HashSet<ulong>();
  617.                     private static readonly HashSet<ulong> meleerate  = new HashSet<ulong>();
  618.                     private static readonly HashSet<ulong> recoil     = new HashSet<ulong>();
  619.                     private static readonly HashSet<ulong> server     = new HashSet<ulong>();
  620.                     private static readonly HashSet<ulong> stash      = new HashSet<ulong>();
  621.                     private static readonly HashSet<ulong> trajectory = new HashSet<ulong>();
  622.                     private static readonly HashSet<ulong> wallhack   = new HashSet<ulong>();
  623.  
  624.                     private static string PERMISSION_AIM;
  625.                     private static string PERMISSION_FIRERATE;
  626.                     private static string PERMISSION_GRAVITY;
  627.                     private static string PERMISSION_MELEERATE;
  628.                     private static string PERMISSION_PREFIX;
  629.                     private static string PERMISSION_RECOIL;
  630.                     private static string PERMISSION_SERVER;
  631.                     private static string PERMISSION_STASH;
  632.                     private static string PERMISSION_TRAJECTORY;
  633.                     private static string PERMISSION_WALLHACK;
  634.  
  635.                     public static void Load()
  636.                     {
  637.                         PERMISSION_PREFIX = Bypass.PERMISSION_PREFIX + "anticheat.";
  638.  
  639.                         PERMISSION_AIM        = PERMISSION_PREFIX + "aim";
  640.                         PERMISSION_FIRERATE   = PERMISSION_PREFIX + "firerate";
  641.                         PERMISSION_GRAVITY    = PERMISSION_PREFIX + "gravity";
  642.                         PERMISSION_MELEERATE  = PERMISSION_PREFIX + "meleerate";
  643.                         PERMISSION_RECOIL     = PERMISSION_PREFIX + "recoil";
  644.                         PERMISSION_SERVER     = PERMISSION_PREFIX + "server";
  645.                         PERMISSION_STASH      = PERMISSION_PREFIX + "stash";
  646.                         PERMISSION_TRAJECTORY = PERMISSION_PREFIX + "trajectory";
  647.                         PERMISSION_WALLHACK   = PERMISSION_PREFIX + "wallhack";
  648.  
  649.                         _instance.permission.RegisterPermission(PERMISSION_AIM,        _instance);
  650.                         _instance.permission.RegisterPermission(PERMISSION_FIRERATE,   _instance);
  651.                         _instance.permission.RegisterPermission(PERMISSION_GRAVITY,    _instance);
  652.                         _instance.permission.RegisterPermission(PERMISSION_MELEERATE,  _instance);
  653.                         _instance.permission.RegisterPermission(PERMISSION_RECOIL,     _instance);
  654.                         _instance.permission.RegisterPermission(PERMISSION_SERVER,     _instance);
  655.                         _instance.permission.RegisterPermission(PERMISSION_STASH,      _instance);
  656.                         _instance.permission.RegisterPermission(PERMISSION_TRAJECTORY, _instance);
  657.                         _instance.permission.RegisterPermission(PERMISSION_WALLHACK,   _instance);
  658.                     }
  659.  
  660.                     public static bool Aim(ulong userid, bool forced = false) =>
  661.                        (forced ? HasPermission(userid, PERMISSION_AIM) : aim.Contains(userid)) || All(userid, forced);
  662.  
  663.                     public static bool FireRate(ulong userid, bool forced = false) =>
  664.                         (forced ? HasPermission(userid, PERMISSION_FIRERATE) : firerate.Contains(userid)) || All(userid, forced);
  665.  
  666.                     public static bool Gravity(ulong userid, bool forced = false) =>
  667.                         (forced ? HasPermission(userid, PERMISSION_GRAVITY) : gravity.Contains(userid)) || All(userid, forced);
  668.  
  669.                     public static bool MeleeRate(ulong userid, bool forced = false) =>
  670.                         (forced ? HasPermission(userid, PERMISSION_MELEERATE) : meleerate.Contains(userid)) || All(userid, forced);
  671.  
  672.                     public static bool Recoil(ulong userid, bool forced = false) =>
  673.                         (forced ? HasPermission(userid, PERMISSION_RECOIL) : recoil.Contains(userid)) || All(userid, forced);
  674.  
  675.                     public static void Reset(ulong userid)
  676.                     {
  677.                         aim.Remove(userid);
  678.                         firerate.Remove(userid);
  679.                         gravity.Remove(userid);
  680.                         meleerate.Remove(userid);
  681.                         recoil.Remove(userid);
  682.                         server.Remove(userid);
  683.                         stash.Remove(userid);
  684.                         trajectory.Remove(userid);
  685.                         wallhack.Remove(userid);
  686.                     }
  687.  
  688.                     public static bool Server(ulong userid, bool forced = false) =>
  689.                         (forced ? HasPermission(userid, PERMISSION_SERVER) : server.Contains(userid)) || All(userid, forced);
  690.  
  691.                     public static bool Stash(ulong userid, bool forced = false) =>
  692.                         (forced ? HasPermission(userid, PERMISSION_STASH) : stash.Contains(userid)) || All(userid, forced);
  693.  
  694.                     public static bool Trajectory(ulong userid, bool forced = false) =>
  695.                         (forced ? HasPermission(userid, PERMISSION_TRAJECTORY) : trajectory.Contains(userid)) || All(userid, forced);
  696.  
  697.                     public static void Unload()
  698.                     {
  699.                         PERMISSION_AIM = null;
  700.                         PERMISSION_FIRERATE = null;
  701.                         PERMISSION_GRAVITY = null;
  702.                         PERMISSION_MELEERATE = null;
  703.                         PERMISSION_PREFIX = null;
  704.                         PERMISSION_RECOIL = null;
  705.                         PERMISSION_SERVER = null;
  706.                         PERMISSION_STASH = null;
  707.                         PERMISSION_TRAJECTORY = null;
  708.                         PERMISSION_WALLHACK = null;
  709.  
  710.                         aim.Clear();
  711.                         firerate.Clear();
  712.                         gravity.Clear();
  713.                         meleerate.Clear();
  714.                         recoil.Clear();
  715.                         server.Clear();
  716.                         stash.Clear();
  717.                         trajectory.Clear();
  718.                         wallhack.Clear();
  719.                     }
  720.  
  721.                     public static void Update(ulong userid)
  722.                     {
  723.                         if(!userid.IsSteamId())
  724.                         {
  725.                             return;
  726.                         }
  727.  
  728.                         Permissions.Update(userid, PERMISSION_AIM,        aim);
  729.                         Permissions.Update(userid, PERMISSION_FIRERATE,   firerate);
  730.                         Permissions.Update(userid, PERMISSION_GRAVITY,    gravity);
  731.                         Permissions.Update(userid, PERMISSION_MELEERATE,  meleerate);
  732.                         Permissions.Update(userid, PERMISSION_RECOIL,     recoil);
  733.                         Permissions.Update(userid, PERMISSION_SERVER,     server);
  734.                         Permissions.Update(userid, PERMISSION_STASH,      stash);
  735.                         Permissions.Update(userid, PERMISSION_TRAJECTORY, trajectory);
  736.                         Permissions.Update(userid, PERMISSION_WALLHACK,   wallhack);
  737.                     }
  738.  
  739.                     public static bool WallHack(ulong userid, bool forced = false) =>
  740.                         (forced ? HasPermission(userid, PERMISSION_WALLHACK) : wallhack.Contains(userid)) || All(userid, forced);
  741.                 }
  742.  
  743.                 public static void Load()
  744.                 {
  745.                     PERMISSION_PREFIX = Permissions.PERMISSION_PREFIX + "bypass.";
  746.  
  747.                     PERMISSION_STEAM = PERMISSION_PREFIX + "steam";
  748.                     PERMISSION_VPN   = PERMISSION_PREFIX + "vpn";
  749.  
  750.                     _instance.permission.RegisterPermission(PERMISSION_STEAM, _instance);
  751.                     _instance.permission.RegisterPermission(PERMISSION_VPN,   _instance);
  752.  
  753.                     AntiCheat.Load();
  754.                 }
  755.  
  756.                 public static void Reset(ulong userid)
  757.                 {
  758.                     steam.Remove(userid);
  759.                     vpn.Remove(userid);
  760.  
  761.                     AntiCheat.Reset(userid);
  762.                 }
  763.  
  764.                 public static bool Steam(ulong userid, bool forced = false) =>
  765.                     (forced ? HasPermission(userid, PERMISSION_STEAM) : steam.Contains(userid)) || All(userid, forced);
  766.  
  767.                 public static void Unload()
  768.                 {
  769.                     PERMISSION_PREFIX = null;
  770.                     PERMISSION_STEAM  = null;
  771.                     PERMISSION_VPN    = null;
  772.  
  773.                     steam.Clear();
  774.                     vpn.Clear();
  775.  
  776.                     AntiCheat.Unload();
  777.                 }
  778.  
  779.                 public static void Update(ulong userid)
  780.                 {
  781.                     Permissions.Update(userid, PERMISSION_STEAM, vpn);
  782.                     Permissions.Update(userid, PERMISSION_VPN,   vpn);
  783.  
  784.                     AntiCheat.Update(userid);
  785.                 }
  786.  
  787.                 public static bool Vpn(ulong userid, bool forced = false) =>
  788.                     (forced ? HasPermission(userid, PERMISSION_VPN) : vpn.Contains(userid)) || All(userid, forced);
  789.             }
  790.  
  791.             public class Command
  792.             {
  793.                 private static readonly HashSet<ulong> config = new HashSet<ulong>();
  794.                 private static readonly HashSet<ulong> ip     = new HashSet<ulong>();
  795.                 private static readonly HashSet<ulong> server = new HashSet<ulong>();
  796.                 private static readonly HashSet<ulong> tp     = new HashSet<ulong>();
  797.                 private static readonly HashSet<ulong> vpn    = new HashSet<ulong>();
  798.  
  799.                 private static string PERMISSION_CONFIG;
  800.                 private static string PERMISSION_IP;
  801.                 private static string PERMISSION_PREFIX;
  802.                 private static string PERMISSION_SERVER;
  803.                 private static string PERMISSION_TP;
  804.                 private static string PERMISSION_VPN;
  805.  
  806.                 public static void Load()
  807.                 {
  808.                     PERMISSION_PREFIX = Permissions.PERMISSION_PREFIX + "command.";
  809.  
  810.                     PERMISSION_CONFIG = PERMISSION_PREFIX + "config";
  811.                     PERMISSION_IP     = PERMISSION_PREFIX + "ip";
  812.                     PERMISSION_SERVER = PERMISSION_PREFIX + "server";
  813.                     PERMISSION_TP     = PERMISSION_PREFIX + "teleport";
  814.                     PERMISSION_VPN    = PERMISSION_PREFIX + "vpn";
  815.  
  816.                     _instance.permission.RegisterPermission(PERMISSION_CONFIG, _instance);
  817.                     _instance.permission.RegisterPermission(PERMISSION_IP,     _instance);
  818.                     _instance.permission.RegisterPermission(PERMISSION_SERVER, _instance);
  819.                     _instance.permission.RegisterPermission(PERMISSION_TP,     _instance);
  820.                     _instance.permission.RegisterPermission(PERMISSION_VPN,    _instance);
  821.                 }
  822.  
  823.                 public static bool Config(ulong userid, bool forced = false) =>
  824.                    (forced ? HasPermission(userid, PERMISSION_CONFIG) : config.Contains(userid)) || All(userid, forced);
  825.  
  826.                 public static bool Ip(ulong userid, bool forced = false) =>
  827.                     (forced ? HasPermission(userid, PERMISSION_IP) : ip.Contains(userid)) || All(userid, forced);
  828.  
  829.                 public static void Reset(ulong userid)
  830.                 {
  831.                     config.Remove(userid);
  832.                     ip.Remove(userid);
  833.                     server.Remove(userid);
  834.                     tp.Remove(userid);
  835.                     vpn.Remove(userid);
  836.                 }
  837.  
  838.                 public static bool Server(ulong userid, bool forced = false) =>
  839.                     (forced ? HasPermission(userid, PERMISSION_SERVER) : server.Contains(userid)) || All(userid, forced);
  840.  
  841.                 public static bool Tp(ulong userid, bool forced = false) =>
  842.                     (forced ? HasPermission(userid, PERMISSION_SERVER) : server.Contains(userid)) || All(userid, forced);
  843.  
  844.                 public static void Unload()
  845.                 {
  846.                     PERMISSION_CONFIG = null;
  847.                     PERMISSION_IP     = null;
  848.                     PERMISSION_PREFIX = null;
  849.                     PERMISSION_SERVER = null;
  850.                     PERMISSION_TP     = null;
  851.                     PERMISSION_VPN    = null;
  852.  
  853.                     config.Clear();
  854.                     ip.Clear();
  855.                     server.Clear();
  856.                     tp.Clear();
  857.                     vpn.Clear();
  858.                 }
  859.  
  860.                 public static void Update(ulong userid)
  861.                 {
  862.                     Permissions.Update(userid, PERMISSION_CONFIG, config);
  863.                     Permissions.Update(userid, PERMISSION_IP,     ip);
  864.                     Permissions.Update(userid, PERMISSION_SERVER, server);
  865.                     Permissions.Update(userid, PERMISSION_TP,     tp);
  866.                     Permissions.Update(userid, PERMISSION_VPN,    vpn);
  867.                 }
  868.  
  869.                 public static bool Vpn(ulong userid, bool forced = false) =>
  870.                     (forced ? HasPermission(userid, PERMISSION_VPN) : vpn.Contains(userid)) || All(userid, forced);
  871.             }
  872.  
  873.             public static string[] Groups(ulong userid) =>
  874.                 _instance.permission.GetUserGroups(userid.ToString());
  875.  
  876.             private static bool HasPermission(ulong userid, string permission) =>
  877.                 _instance.permission.UserHasPermission(userid.ToString(), permission);
  878.  
  879.             public static bool HasPrefix(string permission) =>
  880.                 permission.StartsWith(PERMISSION_PREFIX);
  881.  
  882.             public static bool Ignore(ulong userid, bool forced = false) =>
  883.                 (forced ? HasPermission(userid, PERMISSION_IGNORE) : ignore.Contains(userid)) || All(userid, forced);
  884.  
  885.             public static void Load()
  886.             {
  887.                 PERMISSION_PREFIX = _instance.Name.ToLower() + ".";
  888.  
  889.                 PERMISSION_ALL      = PERMISSION_PREFIX + "all";
  890.                 PERMISSION_ADMIN    = PERMISSION_PREFIX + "admin";
  891.                 PERMISSION_IGNORE   = PERMISSION_PREFIX + "ignore";
  892.  
  893.                 _instance.permission.RegisterPermission(PERMISSION_ALL,    _instance);
  894.                 _instance.permission.RegisterPermission(PERMISSION_ADMIN,  _instance);
  895.                 _instance.permission.RegisterPermission(PERMISSION_IGNORE, _instance);
  896.  
  897.                 Bypass.Load();
  898.                 Command.Load();
  899.             }
  900.  
  901.             public static void Reset(ulong userid)
  902.             {
  903.                 all.Remove(userid);
  904.                 admin.Remove(userid);
  905.                 ignore.Remove(userid);
  906.  
  907.                 Bypass.Reset(userid);
  908.                 Command.Reset(userid);
  909.             }
  910.  
  911.             public static void Unload()
  912.             {
  913.                 PERMISSION_ALL      = null;
  914.                 PERMISSION_ADMIN    = null;
  915.                 PERMISSION_IGNORE   = null;
  916.                 PERMISSION_PREFIX   = null;
  917.  
  918.                 all.Clear();
  919.                 admin.Clear();
  920.                 ignore.Clear();
  921.  
  922.                 Bypass.Unload();
  923.                 Command.Unload();
  924.             }
  925.  
  926.             public static void Update(ulong userid)
  927.             {
  928.                 if(!userid.IsSteamId())
  929.                 {
  930.                     return;
  931.                 }
  932.  
  933.                 Update(userid, PERMISSION_ALL,    all);
  934.                 Update(userid, PERMISSION_ADMIN,  admin);
  935.                 Update(userid, PERMISSION_IGNORE, ignore);
  936.  
  937.                 Bypass.Update(userid);
  938.                 Command.Update(userid);
  939.             }
  940.  
  941.             private static bool Update(ulong userid, string permission, HashSet<ulong> cache) =>
  942.                 HasPermission(userid, permission) ? cache.Add(userid) : cache.Remove(userid);
  943.         }
  944.  
  945.         #endregion _permissions_
  946.  
  947.         #region _projectile_
  948.  
  949.         private class Projectile
  950.         {
  951.             public class Log
  952.             {
  953.                 private static readonly Queue<Entry>                              expired = new Queue<Entry>();
  954.                 private static readonly Dictionary<ulong, Queue<Entry>>           history = new Dictionary<ulong, Queue<Entry>>();
  955.                 private static readonly Dictionary<ulong, Dictionary<int, Entry>> pending = new Dictionary<ulong, Dictionary<int, Entry>>();
  956.                 private static readonly Queue<Request>                            request = new Queue<Request>();
  957.                 private static readonly Queue<Entry>                              reserve = new Queue<Entry>();
  958.  
  959.                 static readonly StringBuilder buffer = new StringBuilder();
  960.  
  961.                 private class Entry
  962.                 {
  963.                     public float    aim_angle;
  964.                     public float    aim_pvp;
  965.                     public float    aim_range;
  966.                     public ulong    aim_violations;
  967.                     public string   attacker;
  968.                     public ulong    attacker_id;
  969.                     public ulong    firerate_violations;
  970.                     public float    hit_distance;
  971.                     public HitArea  hit_location;
  972.                     public bool     pvp;
  973.                     public float    recoil_pitch;
  974.                     public ulong    recoil_repeats;
  975.                     public float    recoil_swing;
  976.                     public ulong    recoil_violations;
  977.                     public float    recoil_yaw;
  978.                     public bool     ricochet;
  979.                     public float    speed;
  980.                     public DateTime timestamp;
  981.                     public float    trajectory;
  982.                     public ulong    trajectory_violations;
  983.                     public string   victim;
  984.                     public bool     violations;
  985.                     public ulong    wallhack_violations;
  986.                     public string   weapon;
  987.  
  988.                     private static readonly Queue<Entry> pool = new Queue<Entry>();
  989.  
  990.                     private Entry(DateTime timestamp)
  991.                     {
  992.                         Default().Set(timestamp);
  993.                     }
  994.  
  995.                     private Entry Default()
  996.                     {
  997.                         aim_angle = 1;
  998.                         aim_pvp = 1;
  999.                         aim_range = 1;
  1000.                         aim_violations = 0;
  1001.                         attacker = null;
  1002.                         attacker_id = 0;
  1003.                         firerate_violations = 0;
  1004.                         hit_distance = 0;
  1005.                         hit_location = 0;
  1006.                         pvp = false;
  1007.                         recoil_pitch = 1;
  1008.                         recoil_repeats = 0;
  1009.                         recoil_swing = 0;
  1010.                         recoil_violations = 0;
  1011.                         recoil_yaw = 1;
  1012.                         speed = 0;
  1013.                         trajectory = 1;
  1014.                         trajectory_violations = 0;
  1015.                         victim = null;
  1016.                         violations = false;
  1017.                         wallhack_violations = 0;
  1018.                         weapon = null;
  1019.  
  1020.                         return this;
  1021.                     }
  1022.  
  1023.                     public string Format(bool collapse, bool id, bool time)
  1024.                     {
  1025.                         buffer.Clear();
  1026.  
  1027.                         if(time)
  1028.                         {
  1029.                             var delta = DateTime.UtcNow.Subtract(timestamp);
  1030.  
  1031.                             buffer.Append(((int)delta.TotalSeconds).ToString("D5")).Append('.').Append((delta.Milliseconds / 10).ToString("D2")).Append("s ");
  1032.                         }
  1033.  
  1034.                         buffer.Append("Recoil[").Append(recoil_repeats.ToString("D2"));
  1035.                         buffer.Append("](x=").Append(recoil_yaw.ToString("F6"));
  1036.                         buffer.Append(", y=").Append(recoil_pitch.ToString("F6"));
  1037.                         buffer.Append(", a=").Append(recoil_swing.ToString("F1"));
  1038.                         buffer.Append(')');
  1039.  
  1040.                         if(!(collapse && string.IsNullOrEmpty(victim)))
  1041.                         {
  1042.                             buffer.Append(" Trajectory(").Append(trajectory.ToString("F6")).Append(')');
  1043.  
  1044.                             buffer.Append(" Aim(a=").Append(aim_angle.ToString("F6")).Append(", p=").Append(aim_pvp.ToString("F6")).Append(", r=").Append(aim_range.ToString("F6")).Append(')');
  1045.                         }
  1046.  
  1047.                         if(id)
  1048.                         {
  1049.                             buffer.Append(' ').Append(attacker).Append('[').Append(attacker_id.ToString()).Append(']');
  1050.                         }
  1051.  
  1052.                         buffer.Append(" (").Append(speed.ToString("F1")).Append("m/s, ").Append(weapon).Append(')');
  1053.  
  1054.                         if(victim != null)
  1055.                         {
  1056.                             buffer.Append(": ").Append(victim).Append('[').Append(Text.BodyPart(hit_location)).Append(", ").Append(hit_distance.ToString("F1")).Append("m]");
  1057.                         }
  1058.  
  1059.                         if(violations)
  1060.                         {
  1061.                             buffer.Append(' ');
  1062.  
  1063.                             if(       aim_violations > 0) buffer.Append("[A]");
  1064.                             if(  firerate_violations > 0) buffer.Append("[F]");
  1065.                             if(    recoil_violations > 0) buffer.Append("[R]");
  1066.                             if(trajectory_violations > 0) buffer.Append("[T]");
  1067.                             if(  wallhack_violations > 0) buffer.Append("[W]");
  1068.                         }
  1069.  
  1070.                         return buffer.ToString();
  1071.                     }
  1072.  
  1073.                     public static Entry Get(DateTime timestamp) => (pool.Count > 0) ? pool.Dequeue().Set(timestamp) : new Entry(timestamp);
  1074.  
  1075.                     public void Release() => pool.Enqueue(Default());
  1076.  
  1077.                     private Entry Set(DateTime timestamp)
  1078.                     {
  1079.                         this.timestamp = timestamp;
  1080.  
  1081.                         return this;
  1082.                     }
  1083.  
  1084.                     public static void Unload() => pool.Clear();
  1085.                 }
  1086.  
  1087.                 private class Request
  1088.                 {
  1089.                     public IPlayer actor;
  1090.                     public int     lines;
  1091.                     public ulong   userid;
  1092.                 }
  1093.  
  1094.                 public static void Add(Weapon weapon)
  1095.                 {
  1096.                     Dictionary<int, Entry> _pending;
  1097.  
  1098.                     if(!pending.TryGetValue(weapon.Player.userID, out _pending))
  1099.                     {
  1100.                         pending.Add(weapon.Player.userID, _pending = new Dictionary<int, Entry>());
  1101.                     }
  1102.  
  1103.                     foreach(var projectileid in weapon.Projectiles)
  1104.                     {
  1105.                         try
  1106.                         {
  1107.                             _pending.Add(projectileid, Entry.Get(weapon.Fired));
  1108.                         }
  1109.                         catch(Exception e)
  1110.                         {
  1111.                             Guardian.Log.Warning($"Projectile.Log.Add({projectileid}): {weapon.Name}({e.Message})");
  1112.                         }
  1113.                     }
  1114.                 }
  1115.  
  1116.                 public static void Expire(Weapon weapon)
  1117.                 {
  1118.                     Dictionary<int, Entry> _pending;
  1119.  
  1120.                     if(pending.TryGetValue(weapon.Player.userID, out _pending))
  1121.                     {
  1122.                         foreach(var projectileid in weapon.Projectiles)
  1123.                         {
  1124.                             Entry entry;
  1125.  
  1126.                             if(_pending.TryGetValue(projectileid, out entry))
  1127.                             {
  1128.                                 _pending.Remove(projectileid);
  1129.  
  1130.                                 if(config.Log.Projectile.Verbose || entry.violations || (entry.victim != null) || (weapon.Projectiles.Count == 1))
  1131.                                 {
  1132.                                     reserve.Enqueue(entry);
  1133.                                 }
  1134.                                 else
  1135.                                 {
  1136.                                     expired.Enqueue(entry);
  1137.                                 }
  1138.                             }
  1139.                         }
  1140.  
  1141.                         while(expired.Count > 0)
  1142.                         {
  1143.                             expired.Dequeue().Release();
  1144.                         }
  1145.  
  1146.                         Queue<Entry> _history;
  1147.  
  1148.                         if(!history.TryGetValue(weapon.Player.userID, out _history))
  1149.                         {
  1150.                             history.Add(weapon.Player.userID, _history = new Queue<Entry>());
  1151.                         }
  1152.  
  1153.                         while(reserve.Count > 0)
  1154.                         {
  1155.                             if(_history.Count > 64)
  1156.                             {
  1157.                                 _history.Dequeue().Release();
  1158.                             }
  1159.  
  1160.                             _history.Enqueue(reserve.Dequeue());
  1161.                         }
  1162.                     }
  1163.                 }
  1164.  
  1165.                 public static void Get(IPlayer actor, ulong userid, int lines)
  1166.                 {
  1167.                     request.Enqueue(new Request { actor = actor, lines = lines, userid = userid });
  1168.                 }
  1169.  
  1170.                 public static float GetAimAngle(ulong userid, int id)
  1171.                 {
  1172.                     var aim_angle = 0.0f;
  1173.  
  1174.                     TrySetEntry(userid, id, entry =>
  1175.                     {
  1176.                         aim_angle = entry.aim_angle;
  1177.                     });
  1178.  
  1179.                     return aim_angle;
  1180.                 }
  1181.  
  1182.                 public static bool GetAimPvp(ulong userid, int id)
  1183.                 {
  1184.                     var pvp = false;
  1185.  
  1186.                     TrySetEntry(userid, id, entry =>
  1187.                     {
  1188.                         pvp = entry.pvp;
  1189.                     });
  1190.  
  1191.                     return pvp;
  1192.                 }
  1193.  
  1194.                 public static float GetHitDistance(ulong userid, int id)
  1195.                 {
  1196.                     var hit_distance = 0.0f;
  1197.  
  1198.                     TrySetEntry(userid, id, entry =>
  1199.                     {
  1200.                         hit_distance = entry.hit_distance;
  1201.                     });
  1202.  
  1203.                     return hit_distance;
  1204.                 }
  1205.  
  1206.                 public static HitArea GetHitLocation(ulong userid, int id)
  1207.                 {
  1208.                     var hit_location = (HitArea)0;
  1209.  
  1210.                     TrySetEntry(userid, id, entry =>
  1211.                     {
  1212.                         hit_location = entry.hit_location;
  1213.                     });
  1214.  
  1215.                     return hit_location;
  1216.                 }
  1217.  
  1218.                 public static bool GetRicochet(ulong userid, int id)
  1219.                 {
  1220.                     var ricochet = false;
  1221.  
  1222.                     TrySetEntry(userid, id, entry =>
  1223.                     {
  1224.                         ricochet = entry.ricochet;
  1225.                     });
  1226.  
  1227.                     return ricochet;
  1228.                 }
  1229.  
  1230.                 public static string GetVictim(ulong userid, int id)
  1231.                 {
  1232.                     var victim = string.Empty;
  1233.  
  1234.                     TrySetEntry(userid, id, entry =>
  1235.                     {
  1236.                         victim = entry.victim;
  1237.                     });
  1238.  
  1239.                     return victim;
  1240.                 }
  1241.  
  1242.                 public static void Send()
  1243.                 {
  1244.                     if(request.Count == 0)
  1245.                     {
  1246.                         return;
  1247.                     }
  1248.  
  1249.                     var send = request.Dequeue();
  1250.  
  1251.                     Queue<Entry> _history;
  1252.  
  1253.                     if(!history.TryGetValue(send.userid, out _history) || (_history.Count == 0))
  1254.                     {
  1255.                         Chat.Reply(send.actor, Key.CommandLogNoEntries, new Dictionary<string, string>
  1256.                         {
  1257.                             { "playerid", send.userid.ToString() },
  1258.                             { "playername", Text.Sanitize(User.Name(send.userid)) }
  1259.                         });
  1260.  
  1261.                         return;
  1262.                     }
  1263.  
  1264.                     if(send.actor.LastCommand == CommandType.Chat)
  1265.                     {
  1266.                         Chat.Reply(send.actor, Key.CommandLogSeeConsole);
  1267.  
  1268.                         send.actor.LastCommand = CommandType.Console;
  1269.                     }
  1270.  
  1271.                     Chat.Reply(send.actor, Key.CommandLogHeading, new Dictionary<string, string>
  1272.                     {
  1273.                         { "playerid", send.userid.ToString() },
  1274.                         { "playername", Text.Sanitize(User.Name(send.userid)) }
  1275.                     });
  1276.  
  1277.                     var skip = (_history.Count > send.lines) ? (_history.Count - send.lines) : 0;
  1278.  
  1279.                     foreach(var entry in _history)
  1280.                     {
  1281.                         if(skip > 0)
  1282.                         {
  1283.                             --skip; continue;
  1284.                         }
  1285.  
  1286.                         Chat.Reply(send.actor, Key.CommandLogLine, new Dictionary<string, string>
  1287.                         {
  1288.                             { "info", entry.Format(config.Log.Projectile.Collapse, false, true) }
  1289.                         });
  1290.                     }
  1291.                 }
  1292.  
  1293.                 public static void SetAim(ulong userid, int id, float aim_angle, float aim_pvp, float aim_range, bool pvp, bool ricochet)
  1294.                 {
  1295.                     TrySetEntry(userid, id, entry =>
  1296.                     {
  1297.                         entry.aim_angle = aim_angle;
  1298.                         entry.aim_pvp   = aim_pvp;
  1299.                         entry.aim_range = aim_range;
  1300.                         entry.pvp       = pvp;
  1301.                         entry.ricochet  = ricochet;
  1302.                     });
  1303.                 }
  1304.                 public static void SetAimViolations(ulong userid, int id, ulong aim_violations, bool add = false)
  1305.                 {
  1306.                     TrySetEntry(userid, id, entry =>
  1307.                     {
  1308.                         entry.aim_violations = add ? (aim_violations += aim_violations) : aim_violations;
  1309.                         entry.violations     = entry.violations || (entry.aim_violations > 0);
  1310.                     });
  1311.                 }
  1312.  
  1313.                 public static void SetAttacker(ulong userid, int id, string attacker, ulong attaker_id)
  1314.                 {
  1315.                     TrySetEntry(userid, id, entry =>
  1316.                     {
  1317.                         entry.attacker    = attacker;
  1318.                         entry.attacker_id = attaker_id;
  1319.                     });
  1320.                 }
  1321.  
  1322.                 public static void SetFireRateViolations(ulong userid, int id, ulong firerate_violations)
  1323.                 {
  1324.                     TrySetEntry(userid, id, entry =>
  1325.                     {
  1326.                         entry.violations = entry.violations || ((entry.firerate_violations = firerate_violations) > 0);
  1327.                     });
  1328.                 }
  1329.  
  1330.                 public static void SetHit(ulong userid, int id, float hit_distance, HitArea hit_location)
  1331.                 {
  1332.                     TrySetEntry(userid, id, entry =>
  1333.                     {
  1334.                         entry.hit_distance = hit_distance;
  1335.                         entry.hit_location = hit_location;
  1336.                     });
  1337.                 }
  1338.  
  1339.                 public static void SetRecoil(ulong userid, int id, float recoil_pitch, ulong recoil_repeats, float recoil_yaw, float recoil_swing)
  1340.                 {
  1341.                     TrySetEntry(userid, id, entry =>
  1342.                     {
  1343.                         entry.recoil_pitch   = recoil_pitch;
  1344.                         entry.recoil_repeats = recoil_repeats;
  1345.                         entry.recoil_swing   = recoil_swing;
  1346.                         entry.recoil_yaw     = recoil_yaw;
  1347.                     });
  1348.                 }
  1349.                 public static void SetRecoilViolations(ulong userid, int id, ulong recoil_violations)
  1350.                 {
  1351.                     TrySetEntry(userid, id, entry =>
  1352.                     {
  1353.                         entry.violations = entry.violations || ((entry.recoil_violations = recoil_violations) > 0);
  1354.                     });
  1355.                 }
  1356.  
  1357.                 public static void SetTrajectory(ulong userid, int id, float trajectory)
  1358.                 {
  1359.                     TrySetEntry(userid, id, entry =>
  1360.                     {
  1361.                         entry.trajectory = trajectory;
  1362.                     });
  1363.                 }
  1364.                 public static void SetTrajectoryViolations(ulong userid, int id, ulong trajectory_violations)
  1365.                 {
  1366.                     TrySetEntry(userid, id, entry =>
  1367.                     {
  1368.                         entry.violations = entry.violations || ((entry.trajectory_violations = trajectory_violations) > 0);
  1369.                     });
  1370.                 }
  1371.  
  1372.                 public static void SetVictim(ulong userid, int id, string victim)
  1373.                 {
  1374.                     TrySetEntry(userid, id, entry =>
  1375.                     {
  1376.                         entry.victim = victim;
  1377.                     });
  1378.                 }
  1379.  
  1380.                 public static void SetWallHackViolations(ulong userid, int id, ulong wallhack_violations)
  1381.                 {
  1382.                     TrySetEntry(userid, id, entry =>
  1383.                     {
  1384.                         entry.violations = entry.violations || ((entry.wallhack_violations = wallhack_violations) > 0);
  1385.                     });
  1386.                 }
  1387.  
  1388.                 public static void SetWeapon(ulong userid, int id, float speed, string weapon)
  1389.                 {
  1390.                     TrySetEntry(userid, id, entry =>
  1391.                     {
  1392.                         entry.speed  = speed;
  1393.                         entry.weapon = weapon;
  1394.                     });
  1395.                 }
  1396.  
  1397.                 private static void TrySetEntry(ulong userid, int id, Action<Entry> set)
  1398.                 {
  1399.                     Dictionary<int, Entry> _pending;
  1400.  
  1401.                     if(pending.TryGetValue(userid, out _pending))
  1402.                     {
  1403.                         Entry entry;
  1404.  
  1405.                         if(_pending.TryGetValue(id, out entry))
  1406.                         {
  1407.                             set(entry);
  1408.                         }
  1409.                     }
  1410.                 }
  1411.  
  1412.                 public static void Unload()
  1413.                 {
  1414.                     buffer.Clear();
  1415.  
  1416.                     expired.Clear();
  1417.                     history.Clear();
  1418.                     pending.Clear();
  1419.                     request.Clear();
  1420.                     reserve.Clear();
  1421.  
  1422.                     Entry.Unload();
  1423.                 }
  1424.             }
  1425.  
  1426.             private static readonly List<Weapon>                               expired = new List<Weapon>();
  1427.             private static readonly Dictionary<ulong, Dictionary<int, Weapon>> reverse = new Dictionary<ulong, Dictionary<int, Weapon>>();
  1428.             private static readonly Dictionary<ulong, Queue<Weapon>>           weapons = new Dictionary<ulong, Queue<Weapon>>();
  1429.  
  1430.             public static void Add(Weapon weapon)
  1431.             {
  1432.                 Queue<Weapon> _weapons;
  1433.  
  1434.                 if(!weapons.TryGetValue(weapon.Player.userID, out _weapons))
  1435.                 {
  1436.                     weapons.Add(weapon.Player.userID, _weapons = new Queue<Weapon>());
  1437.                 }
  1438.  
  1439.                 _weapons.Enqueue(weapon);
  1440.  
  1441.                 Dictionary<int, Weapon> _reverse;
  1442.  
  1443.                 if(!reverse.TryGetValue(weapon.Player.userID, out _reverse))
  1444.                 {
  1445.                     reverse.Add(weapon.Player.userID, _reverse = new Dictionary<int, Weapon>());
  1446.                 }
  1447.  
  1448.                 foreach(var projectileid in weapon.Projectiles)
  1449.                 {
  1450.                     _reverse[projectileid] = weapon;
  1451.                 }
  1452.  
  1453.                 Log.Add(weapon);
  1454.             }
  1455.  
  1456.             public static void Load()
  1457.             {
  1458.                 Timers.Add(0.1f, () =>
  1459.                 {
  1460.                     var time = DateTime.UtcNow;
  1461.  
  1462.                     foreach(var user in weapons)
  1463.                     {
  1464.                         var _weapons = user.Value;
  1465.  
  1466.                         while(_weapons.Count > 0)
  1467.                         {
  1468.                             if(time.Subtract(_weapons.Peek().Fired).TotalSeconds > 9.0)
  1469.                             {
  1470.                                 expired.Add(_weapons.Dequeue());
  1471.                             }
  1472.                             else
  1473.                             {
  1474.                                 break;
  1475.                             }
  1476.                         }
  1477.                     }
  1478.  
  1479.                     expired.Sort((x, y) => DateTime.Compare(x.Fired, y.Fired));
  1480.  
  1481.                     foreach(var weapon in expired)
  1482.                     {
  1483.                         AntiCheat.Aim.Update(weapon);
  1484.  
  1485.                         Dictionary<int, Weapon> _reverse;
  1486.  
  1487.                         if(reverse.TryGetValue(weapon.Player.userID, out _reverse))
  1488.                         {
  1489.                             foreach(var projectileid in weapon.Projectiles)
  1490.                             {
  1491.                                 _reverse.Remove(projectileid);
  1492.                             }
  1493.  
  1494.                             Log.Expire(weapon);
  1495.  
  1496.                             weapon.Release();
  1497.                         }
  1498.  
  1499.                     }
  1500.  
  1501.                     expired.Clear();
  1502.  
  1503.                     Log.Send();
  1504.                 });
  1505.             }
  1506.  
  1507.             public static void Unload()
  1508.             {
  1509.                 expired.Clear();
  1510.                 reverse.Clear();
  1511.                 weapons.Clear();
  1512.  
  1513.                 Log.Unload();
  1514.             }
  1515.  
  1516.             public static Weapon Weapon(ulong userid, int projectileid)
  1517.             {
  1518.                 Dictionary<int, Weapon> _reverse;
  1519.  
  1520.                 if(reverse.TryGetValue(userid, out _reverse))
  1521.                 {
  1522.                     Weapon weapon;
  1523.  
  1524.                     if(_reverse.TryGetValue(projectileid, out weapon))
  1525.                     {
  1526.                         return weapon;
  1527.                     }
  1528.                 }
  1529.  
  1530.                 return null;
  1531.             }
  1532.         }
  1533.  
  1534.         #endregion _projectile_
  1535.  
  1536.         #region _sparse_map_
  1537.  
  1538.         private class SparseMap
  1539.         {
  1540.             private Dictionary<Texel, Cell> map;
  1541.             private float map_scale;
  1542.  
  1543.             private struct Cell
  1544.             {
  1545.                 private ulong pixels;
  1546.  
  1547.                 public bool IsEmpty() =>
  1548.                     pixels == 0;
  1549.  
  1550.                 private static ulong Mask(Delta delta) =>
  1551.                     1ul << (((delta.Y + 4) << 3) + delta.X + 4);
  1552.  
  1553.                 public Cell Reset(Delta delta) =>
  1554.                     new Cell { pixels = pixels & ~Mask(delta) };
  1555.  
  1556.                 public Cell Set(Delta delta) =>
  1557.                     new Cell { pixels = pixels | Mask(delta) };
  1558.  
  1559.                 public bool Test(Delta delta) =>
  1560.                     (pixels & Mask(delta)) != 0;
  1561.             }
  1562.  
  1563.             private struct Delta
  1564.             {
  1565.                 public int X { get; private set; }
  1566.                 public int Y { get; private set; }
  1567.  
  1568.                 public Delta(Texel texel, Pixel pixel)
  1569.                 {
  1570.                     X = pixel.X - texel.X;
  1571.                     Y = pixel.Y - texel.Y;
  1572.                 }
  1573.             }
  1574.  
  1575.             private struct Pixel
  1576.             {
  1577.                 public int X { get; private set; }
  1578.                 public int Y { get; private set; }
  1579.  
  1580.                 public Pixel(int x, int y)
  1581.                 {
  1582.                     X = x;
  1583.                     Y = y;
  1584.                 }
  1585.  
  1586.                 public Pixel(Vector3 position, SparseMap map)
  1587.                 {
  1588.                     X = map.Scale(position.x);
  1589.                     Y = map.Scale(position.z);
  1590.                 }
  1591.             }
  1592.  
  1593.             private struct Texel
  1594.             {
  1595.                 public int X { get; private set; }
  1596.                 public int Y { get; private set; }
  1597.  
  1598.                 public Texel(Pixel pixel)
  1599.                 {
  1600.                     X = pixel.X & ~7;
  1601.                     Y = pixel.Y & ~7;
  1602.                 }
  1603.  
  1604.                 public override bool Equals(object o) =>
  1605.                     (o is Texel) ? (this == (Texel)o) : false;
  1606.  
  1607.                 public static bool operator ==(Texel a, Texel b) =>
  1608.                     (a.X == b.X) && (a.Y == b.Y);
  1609.                 public static bool operator !=(Texel a, Texel b) =>
  1610.                     (a.X != b.X) || (a.Y != b.Y);
  1611.  
  1612.                 public override int GetHashCode() =>
  1613.                     (((X * 1664525 + 1013904223) & 0x3fffffff) >> 3) ^ ((Y * 1103515245 + 12345) << 2);
  1614.             }
  1615.  
  1616.             public SparseMap(float scale)
  1617.             {
  1618.                 map = new Dictionary<Texel, Cell>();
  1619.  
  1620.                 SetScale(scale);
  1621.             }
  1622.  
  1623.             public void Clear() => map.Clear();
  1624.             public int Count() => map.Count;
  1625.  
  1626.             public void Reset(float scale = 0.0f)
  1627.             {
  1628.                 Clear();
  1629.  
  1630.                 if(scale != 0.0f)
  1631.                 {
  1632.                     SetScale(scale);
  1633.                 }
  1634.             }
  1635.             private void Reset(Pixel pixel)
  1636.             {
  1637.                 var texel = new Texel(pixel);
  1638.  
  1639.                 Cell value;
  1640.  
  1641.                 if(!map.TryGetValue(texel, out value))
  1642.                 {
  1643.                     return;
  1644.                 }
  1645.  
  1646.                 value = value.Reset(new Delta(texel, pixel));
  1647.  
  1648.                 if(value.IsEmpty())
  1649.                 {
  1650.                     map.Remove(texel);
  1651.                 }
  1652.                 else
  1653.                 {
  1654.                     map[texel] = value;
  1655.                 }
  1656.             }
  1657.             public void Reset(Vector3 position) =>
  1658.                 Reset(new Pixel(position, this));
  1659.  
  1660.             public float Scale() => map_scale;
  1661.             private int Scale(float n) =>
  1662.                 (int)(n * map_scale);
  1663.  
  1664.             private void Set(Pixel pixel)
  1665.             {
  1666.                 var texel = new Texel(pixel);
  1667.  
  1668.                 Cell value;
  1669.  
  1670.                 if(!map.TryGetValue(texel, out value))
  1671.                 {
  1672.                     value = new Cell();
  1673.                 }
  1674.  
  1675.                 map[texel] = value.Set(new Delta(texel, pixel));
  1676.             }
  1677.             public void Set(Vector3 position) =>
  1678.                 Set(new Pixel(position, this));
  1679.             public int Set(Vector3 origin, float range)
  1680.             {
  1681.                 var pixels = 0;
  1682.                 var center = new Pixel(origin, this);
  1683.  
  1684.                 var r = Scale(range);
  1685.  
  1686.                 if(r < 0)
  1687.                 {
  1688.                     return pixels;
  1689.                 }
  1690.  
  1691.                 var r_squared = r * r;
  1692.  
  1693.                 if(r_squared == 0)
  1694.                 {
  1695.                     if(range >= 0.25f)
  1696.                     {
  1697.                         Set(center); ++pixels;
  1698.                     }
  1699.  
  1700.                     return pixels;
  1701.                 }
  1702.  
  1703.                 for(int delta_x = -r; delta_x < r; ++delta_x)
  1704.                 {
  1705.                     int delta_y = (int)Math.Sqrt(r_squared - delta_x * delta_x);
  1706.  
  1707.                     int x = center.X + delta_x, limit_y = center.Y + delta_y;
  1708.  
  1709.                     for(int y = center.Y - delta_y; y < limit_y; ++y)
  1710.                     {
  1711.                         Set(new Pixel(x, y)); ++pixels;
  1712.                     }
  1713.                 }
  1714.  
  1715.                 return pixels;
  1716.             }
  1717.  
  1718.             private void SetScale(float scale)
  1719.             {
  1720.                 scale = Math.Abs(scale);
  1721.  
  1722.                 scale = (scale < 0.125f) ? 0.125f : scale;
  1723.  
  1724.                 map_scale = 1.0f / scale;
  1725.             }
  1726.  
  1727.             private bool Test(Pixel pixel)
  1728.             {
  1729.                 var texel = new Texel(pixel);
  1730.  
  1731.                 Cell value;
  1732.  
  1733.                 if(map.TryGetValue(texel, out value))
  1734.                 {
  1735.                     return value.Test(new Delta(texel, pixel));
  1736.                 }
  1737.  
  1738.                 return false;
  1739.             }
  1740.             public bool Test(Vector3 position) =>
  1741.                 Test(new Pixel(position, this));
  1742.         }
  1743.  
  1744.         #endregion _sparse_map_
  1745.  
  1746.         #region _steam_
  1747.  
  1748.         private class Steam
  1749.         {
  1750.             public class Settings
  1751.             {
  1752.                 public API.Settings   API;
  1753.                 public SteamBan       Ban;
  1754.                 public SteamGame      Game;
  1755.                 public SteamProfile   Profile;
  1756.                 public SteamShare     Share;
  1757.                 public SteamViolation Violation;
  1758.  
  1759.                 public Settings()
  1760.                 {
  1761.                     API       = new API.Settings();
  1762.                     Ban       = new SteamBan();
  1763.                     Game      = new SteamGame();
  1764.                     Profile   = new SteamProfile();
  1765.                     Share     = new SteamShare();
  1766.                     Violation = new SteamViolation();
  1767.                 }
  1768.  
  1769.                 public class SteamBan
  1770.                 {
  1771.                     public bool  Active;
  1772.                     public bool  Community;
  1773.                     public ulong Days;
  1774.                     public bool  Economy;
  1775.                     public ulong Game;
  1776.                     public ulong VAC;
  1777.                 }
  1778.                 public class SteamGame
  1779.                 {
  1780.                     public ulong Count;
  1781.                     public ulong Hours;
  1782.                 }
  1783.                 public class SteamProfile
  1784.                 {
  1785.                     public bool Invalid;
  1786.                     public bool Limited;
  1787.                     public bool Private;
  1788.                 }
  1789.                 public class SteamShare
  1790.                 {
  1791.                     public bool Family;
  1792.                 }
  1793.  
  1794.                 public class SteamViolation
  1795.                 {
  1796.                     public bool Ban;
  1797.                     public bool Enabled;
  1798.                     public bool Warn;
  1799.  
  1800.                     public SteamViolation()
  1801.                     {
  1802.                         Ban     = false;
  1803.                         Enabled = true;
  1804.                         Warn    = false;
  1805.                     }
  1806.                 }
  1807.  
  1808.                 public void Validate()
  1809.                 {
  1810.                     Configuration.Validate(ref API,       () => new API.Settings(), () => API.Validate());
  1811.                     Configuration.Validate(ref Ban,       () => new SteamBan());
  1812.                     Configuration.Validate(ref Game,      () => new SteamGame());
  1813.                     Configuration.Validate(ref Profile,   () => new SteamProfile());
  1814.                     Configuration.Validate(ref Share,     () => new SteamShare());
  1815.                     Configuration.Validate(ref Violation, () => new SteamViolation());
  1816.                 }
  1817.             }
  1818.  
  1819.             private static uint appid;
  1820.  
  1821.             private static ActionQueue checks;
  1822.  
  1823.             private static readonly Violation violation = new Violation(Key.Steam);
  1824.  
  1825.             public static void Check(ulong userid)
  1826.             {
  1827.                 if(!config.Steam.API.Enabled || Permissions.Bypass.Steam(userid))
  1828.                 {
  1829.                     return;
  1830.                 }
  1831.  
  1832.                 if(string.IsNullOrEmpty(config.Steam.API.ApiKey))
  1833.                 {
  1834.                     Log.Console(Key.LogSteamConfig, new Dictionary<string, string>
  1835.                     {
  1836.                         { "api", "API" },
  1837.                         { "link", $"https://steamcommunity.com/dev/apikey" }
  1838.                     });
  1839.  
  1840.                     return;
  1841.                 }
  1842.  
  1843.                 if(Ban.check)
  1844.                 {
  1845.                     checks.Enqueue(() => Ban.Check(userid));
  1846.                 }
  1847.             }
  1848.  
  1849.             public static void Configure()
  1850.             {
  1851.                 violation.Configure(new Violation.Settings(true, 3600, 0.5f, config.Steam.Violation.Warn), 1, 1, 1);
  1852.  
  1853.                 Game.check = Share.check = Profile.check = Summaries.check = Ban.check = false;
  1854.  
  1855.                 if(config.Steam.API.Enabled)
  1856.                 {
  1857.                     Game.check =
  1858.                         config.Steam.Game.Count > 0 ||
  1859.                         config.Steam.Game.Hours > 0;
  1860.  
  1861.                     Share.check =
  1862.                         Game.check ||
  1863.                         config.Steam.Share.Family;
  1864.  
  1865.                     Profile.check =
  1866.                         Share.check ||
  1867.                         config.Steam.Profile.Invalid ||
  1868.                         config.Steam.Profile.Limited;
  1869.  
  1870.                     Summaries.check =
  1871.                         Profile.check ||
  1872.                         config.Steam.Profile.Private;
  1873.  
  1874.                     Ban.check =
  1875.                         Summaries.check ||
  1876.                         config.Steam.Ban.Active ||
  1877.                         config.Steam.Ban.Community ||
  1878.                         config.Steam.Ban.Days > 0 ||
  1879.                         config.Steam.Ban.Economy ||
  1880.                         config.Steam.Ban.Game > 0 ||
  1881.                         config.Steam.Ban.VAC > 0;
  1882.                 }
  1883.             }
  1884.  
  1885.             public static void Load()
  1886.             {
  1887.                 appid = _instance.covalence.ClientAppId;
  1888.  
  1889.                 Configure();
  1890.  
  1891.                 checks = new ActionQueue(1.0f);
  1892.             }
  1893.  
  1894.             public static void Unload()
  1895.             {
  1896.                 checks.Clear();
  1897.                 checks = null;
  1898.  
  1899.                 violation.Clear();
  1900.             }
  1901.  
  1902.             private static void Violation(ulong userid, Key type, string details, bool ban = false)
  1903.             {
  1904.                 if(config.Steam.Violation.Enabled)
  1905.                 {
  1906.                     ulong violations = (config.Steam.Violation.Ban || ban) ? ulong.MaxValue : 1ul;
  1907.  
  1908.                     violation.Trigger(userid, type, details, violations, true);
  1909.                 }
  1910.                 else
  1911.                 {
  1912.                     if(User.IsConnected(userid))
  1913.                     {
  1914.                         User.Kick(userid, $"Steam: {type}");
  1915.                     }
  1916.                 }
  1917.             }
  1918.  
  1919.             private class Ban
  1920.             {
  1921.                 [JsonProperty("players")]
  1922.                 public Player[] Players;
  1923.  
  1924.                 public class Player
  1925.                 {
  1926.                     [JsonProperty("CommunityBanned")]
  1927.                     public bool CommunityBanned;
  1928.  
  1929.                     [JsonProperty("VACBanned")]
  1930.                     public bool VacBanned;
  1931.  
  1932.                     [JsonProperty("NumberOfVACBans")]
  1933.                     public ulong NumberOfVacBans;
  1934.  
  1935.                     [JsonProperty("DaysSinceLastBan")]
  1936.                     public ulong DaysSinceLastBan;
  1937.  
  1938.                     [JsonProperty("NumberOfGameBans")]
  1939.                     public ulong NumberOfGameBans;
  1940.  
  1941.                     [JsonProperty("EconomyBan")]
  1942.                     public string EconomyBan;
  1943.                 }
  1944.  
  1945.                 [JsonIgnore]
  1946.                 private static readonly string api = nameof(Ban);
  1947.  
  1948.                 [JsonIgnore]
  1949.                 public static bool check = false;
  1950.  
  1951.                 public static void Check(ulong userid)
  1952.                 {
  1953.                     var url = $"https://api.steampowered.com/ISteamUser/GetPlayerBans/v1/?key={config.Steam.API.ApiKey}&steamids={userid}";
  1954.  
  1955.                     _instance.webrequest.Enqueue(url, string.Empty, (code, reply) =>
  1956.                     {
  1957.                         if(code != 200 || string.IsNullOrEmpty(reply))
  1958.                         {
  1959.                             Log.Console(Key.LogSteam, new Dictionary<string, string>
  1960.                             {
  1961.                                 { "api", api },
  1962.                                 { "info", $"({code}: {reply})" },
  1963.                                 { "playerid", userid.ToString() },
  1964.                                 { "playername", Text.Sanitize(User.Name(userid)) },
  1965.                                 { "type", "HTTP" }
  1966.                             });
  1967.  
  1968.                             return;
  1969.                         }
  1970.  
  1971.                         try
  1972.                         {
  1973.                             var response = JsonConvert.DeserializeObject<Ban>(reply).Players[0];
  1974.  
  1975.                             var has_bans = (response.NumberOfGameBans + response.NumberOfVacBans) > 0;
  1976.  
  1977.                             if(config.Steam.Ban.Active && response.VacBanned)
  1978.                             {
  1979.                                 Violation(userid, Key.SteamBanActive, $"{true}");
  1980.                             }
  1981.                             else if(config.Steam.Ban.Community && response.CommunityBanned)
  1982.                             {
  1983.                                 Violation(userid, Key.SteamBanCommunity, $"{true}");
  1984.                             }
  1985.                             else if((config.Steam.Ban.Days > 0) && (config.Steam.Ban.Days > response.DaysSinceLastBan) && has_bans)
  1986.                             {
  1987.                                 Violation(userid, Key.SteamBanDays, $"{response.DaysSinceLastBan}");
  1988.                             }
  1989.                             else if(config.Steam.Ban.Economy && (response.EconomyBan == "banned"))
  1990.                             {
  1991.                                 Violation(userid, Key.SteamBanEconomy, $"{true}");
  1992.                             }
  1993.                             else if((config.Steam.Ban.Game > 0) && (config.Steam.Ban.Game <= response.NumberOfGameBans))
  1994.                             {
  1995.                                 Violation(userid, Key.SteamBanGame, $"{response.NumberOfGameBans}", true);
  1996.                             }
  1997.                             else if((config.Steam.Ban.VAC > 0) && (config.Steam.Ban.VAC <= response.NumberOfVacBans))
  1998.                             {
  1999.                                 Violation(userid, Key.SteamBanVAC, $"{response.NumberOfVacBans}", true);
  2000.                             }
  2001.                             else if(Summaries.check)
  2002.                             {
  2003.                                 Summaries.Check(userid);
  2004.                             }
  2005.                         }
  2006.                         catch
  2007.                         {
  2008.                             Log.Console(Key.LogSteam, new Dictionary<string, string>
  2009.                             {
  2010.                                 { "api", api },
  2011.                                 { "info", reply },
  2012.                                 { "playerid", userid.ToString() },
  2013.                                 { "playername", Text.Sanitize(User.Name(userid)) },
  2014.                                 { "type", "JSON" }
  2015.                             });
  2016.                         }
  2017.                     }, _instance);
  2018.                 }
  2019.             }
  2020.  
  2021.             private class Game
  2022.             {
  2023.                 [JsonProperty("response")]
  2024.                 public Content Response;
  2025.  
  2026.                 public class Content
  2027.                 {
  2028.                     [JsonProperty("game_count")]
  2029.                     public ulong GameCount;
  2030.  
  2031.                     [JsonProperty("games")]
  2032.                     public Game[] Games;
  2033.  
  2034.                     public class Game
  2035.                     {
  2036.                         [JsonProperty("appid")]
  2037.                         public uint AppId;
  2038.  
  2039.                         [JsonProperty("playtime_forever")]
  2040.                         public ulong PlaytimeForever;
  2041.                     }
  2042.                 }
  2043.  
  2044.                 [JsonIgnore]
  2045.                 private static readonly string api = nameof(Game);
  2046.  
  2047.                 [JsonIgnore]
  2048.                 public static bool check = false;
  2049.  
  2050.                 public static void Check(ulong userid, bool private_profile, bool is_sharing)
  2051.                 {
  2052.                     if(private_profile || is_sharing)
  2053.                     {
  2054.                         if(config.Steam.Game.Count > 0)
  2055.                         {
  2056.                             Violation(userid, Key.SteamGameCount, "(null)");
  2057.                         }
  2058.                         else if(config.Steam.Game.Hours > 0)
  2059.                         {
  2060.                             Violation(userid, Key.SteamGameHours, "(null)");
  2061.                         }
  2062.  
  2063.                         return;
  2064.                     }
  2065.  
  2066.                     var url = $"https://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key={config.Steam.API.ApiKey}&steamid={userid}";
  2067.  
  2068.                     _instance.webrequest.Enqueue(url, string.Empty, (code, reply) =>
  2069.                     {
  2070.                         if(code != 200 || string.IsNullOrEmpty(reply))
  2071.                         {
  2072.                             Log.Console(Key.LogSteam, new Dictionary<string, string>
  2073.                             {
  2074.                                 { "api", api },
  2075.                                 { "info", $"({code}: {reply})" },
  2076.                                 { "playerid", userid.ToString() },
  2077.                                 { "playername", Text.Sanitize(User.Name(userid)) },
  2078.                                 { "type", "HTTP" }
  2079.                             });
  2080.  
  2081.                             return;
  2082.                         }
  2083.  
  2084.                         try
  2085.                         {
  2086.                             var response = JsonConvert.DeserializeObject<Game>(reply).Response;
  2087.  
  2088.                             if((config.Steam.Game.Count > 0) && (config.Steam.Game.Count > response.GameCount))
  2089.                             {
  2090.                                 Violation(userid, Key.SteamGameCount, $"{response.GameCount}");
  2091.                             }
  2092.                             else if(config.Steam.Game.Hours > 0)
  2093.                             {
  2094.                                 foreach(var game in response.Games)
  2095.                                 {
  2096.                                     if(game.AppId == appid)
  2097.                                     {
  2098.                                         var hours_played = game.PlaytimeForever / 60ul;
  2099.  
  2100.                                         if(config.Steam.Game.Hours > hours_played)
  2101.                                         {
  2102.                                             Violation(userid, Key.SteamGameHours, $"{hours_played}");
  2103.  
  2104.                                             break;
  2105.                                         }
  2106.                                     }
  2107.                                 }
  2108.                             }
  2109.                         }
  2110.                         catch
  2111.                         {
  2112.                             Log.Console(Key.LogSteam, new Dictionary<string, string>
  2113.                             {
  2114.                                 { "api", api },
  2115.                                 { "info", reply },
  2116.                                 { "playerid", userid.ToString() },
  2117.                                 { "playername", Text.Sanitize(User.Name(userid)) },
  2118.                                 { "type", "JSON" }
  2119.                             });
  2120.                         }
  2121.                     }, _instance);
  2122.                 }
  2123.             }
  2124.  
  2125.             private class Profile
  2126.             {
  2127.                 private static readonly string api = nameof(Profile);
  2128.  
  2129.                 public static bool check = false;
  2130.  
  2131.                 public static void Check(ulong userid, bool private_profile)
  2132.                 {
  2133.                     var url = $"https://steamcommunity.com/profiles/{userid}/?xml=1";
  2134.  
  2135.                     _instance.webrequest.Enqueue(url, string.Empty, (code, reply) =>
  2136.                     {
  2137.                         if(code != 200 || string.IsNullOrEmpty(reply))
  2138.                         {
  2139.                             Log.Console(Key.LogSteam, new Dictionary<string, string>
  2140.                             {
  2141.                                 { "api", api },
  2142.                                 { "info", $"({code}: {reply})" },
  2143.                                 { "playerid", userid.ToString() },
  2144.                                 { "playername", Text.Sanitize(User.Name(userid)) },
  2145.                                 { "type", "HTTP" }
  2146.                             });
  2147.  
  2148.                             return;
  2149.                         }
  2150.  
  2151.                         var response = reply.ToLower();
  2152.  
  2153.                         var has_profile = !response.Contains("this user has not yet set up their steam community profile");
  2154.  
  2155.                         if(config.Steam.Profile.Invalid && !has_profile)
  2156.                         {
  2157.                             Violation(userid, Key.SteamProfileInvalid, $"{true}");
  2158.                         }
  2159.                         else if(config.Steam.Profile.Limited && has_profile && response.Contains("<islimitedaccount>1</islimitedaccount>"))
  2160.                         {
  2161.                             Violation(userid, Key.SteamProfileLimited, $"{true}");
  2162.                         }
  2163.                         else if(Share.check)
  2164.                         {
  2165.                             Share.Check(userid, private_profile);
  2166.                         }
  2167.                     }, _instance);
  2168.                 }
  2169.             }
  2170.  
  2171.             private class Share
  2172.             {
  2173.                 [JsonProperty("response")]
  2174.                 public Content Response;
  2175.  
  2176.                 public class Content
  2177.                 {
  2178.                     [JsonProperty("lender_steamid")]
  2179.                     public ulong LenderSteamId;
  2180.                 }
  2181.  
  2182.                 [JsonIgnore]
  2183.                 private static readonly string api = nameof(Share);
  2184.  
  2185.                 [JsonIgnore]
  2186.                 public static bool check = false;
  2187.  
  2188.                 public static void Check(ulong userid, bool private_profile)
  2189.                 {
  2190.                     var url = $"https://api.steampowered.com/IPlayerService/IsPlayingSharedGame/v0001/?key={config.Steam.API.ApiKey}&steamid={userid}&appid_playing={appid}";
  2191.  
  2192.                     _instance.webrequest.Enqueue(url, string.Empty, (code, reply) =>
  2193.                     {
  2194.                         if(code != 200 || string.IsNullOrEmpty(reply))
  2195.                         {
  2196.                             Log.Console(Key.LogSteam, new Dictionary<string, string>
  2197.                             {
  2198.                                 { "api", api },
  2199.                                 { "info", $"({code}: {reply})" },
  2200.                                 { "playerid", userid.ToString() },
  2201.                                 { "playername", Text.Sanitize(User.Name(userid)) },
  2202.                                 { "type", "HTTP" }
  2203.                             });
  2204.  
  2205.                             return;
  2206.                         }
  2207.  
  2208.                         try
  2209.                         {
  2210.                             var response = JsonConvert.DeserializeObject<Share>(reply).Response;
  2211.  
  2212.                             var is_sharing = (response.LenderSteamId > 0);
  2213.  
  2214.                             if(config.Steam.Share.Family && is_sharing)
  2215.                             {
  2216.                                 Violation(userid, Key.SteamShareFamily, $"{true}");
  2217.                             }
  2218.                             else if(Game.check)
  2219.                             {
  2220.                                 Game.Check(userid, private_profile, is_sharing);
  2221.                             }
  2222.                         }
  2223.                         catch
  2224.                         {
  2225.                             Log.Console(Key.LogSteam, new Dictionary<string, string>
  2226.                             {
  2227.                                 { "api", api },
  2228.                                 { "info", reply },
  2229.                                 { "playerid", userid.ToString() },
  2230.                                 { "playername", Text.Sanitize(User.Name(userid)) },
  2231.                                 { "type", "JSON" }
  2232.                             });
  2233.                         }
  2234.                     }, _instance);
  2235.                 }
  2236.             }
  2237.  
  2238.             private class Summaries
  2239.             {
  2240.                 [JsonProperty("response")]
  2241.                 public Content Response;
  2242.  
  2243.                 public class Content
  2244.                 {
  2245.                     [JsonProperty("players")]
  2246.                     public Player[] Players;
  2247.  
  2248.                     public class Player
  2249.                     {
  2250.                         [JsonProperty("communityvisibilitystate")]
  2251.                         public int CommunityVisibilityState;
  2252.                     }
  2253.                 }
  2254.  
  2255.                 [JsonIgnore]
  2256.                 private static readonly string api = nameof(Summaries);
  2257.  
  2258.                 [JsonIgnore]
  2259.                 public static bool check = false;
  2260.  
  2261.                 public static void Check(ulong userid)
  2262.                 {
  2263.                     var url = $"https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key={config.Steam.API.ApiKey}&steamids={userid}";
  2264.  
  2265.                     _instance.webrequest.Enqueue(url, string.Empty, (code, reply) =>
  2266.                     {
  2267.                         if(code != 200 || string.IsNullOrEmpty(reply))
  2268.                         {
  2269.                             Log.Console(Key.LogSteam, new Dictionary<string, string>
  2270.                             {
  2271.                                 { "api", api },
  2272.                                 { "info", $"({code}: {reply})" },
  2273.                                 { "playerid", userid.ToString() },
  2274.                                 { "playername", Text.Sanitize(User.Name(userid)) },
  2275.                                 { "type", "HTTP" }
  2276.                             });
  2277.  
  2278.                             return;
  2279.                         }
  2280.  
  2281.                         try
  2282.                         {
  2283.                             var response = JsonConvert.DeserializeObject<Summaries>(reply).Response.Players[0];
  2284.  
  2285.                             var private_profile = (response.CommunityVisibilityState < 3);
  2286.  
  2287.                             if(config.Steam.Profile.Private && private_profile)
  2288.                             {
  2289.                                 Violation(userid, Key.SteamProfilePrivate, $"{true}");
  2290.                             }
  2291.                             else if(Profile.check)
  2292.                             {
  2293.                                 Profile.Check(userid, private_profile);
  2294.                             }
  2295.                         }
  2296.                         catch
  2297.                         {
  2298.                             Log.Console(Key.LogSteam, new Dictionary<string, string>
  2299.                             {
  2300.                                 { "api", api },
  2301.                                 { "info", reply },
  2302.                                 { "playerid", userid.ToString() },
  2303.                                 { "playername", Text.Sanitize(User.Name(userid)) },
  2304.                                 { "type", "JSON" }
  2305.                             });
  2306.                         }
  2307.                     }, _instance);
  2308.                 }
  2309.             }
  2310.         }
  2311.  
  2312.         #endregion _steam_
  2313.  
  2314.         #region _text_
  2315.  
  2316.         private enum Key
  2317.         {
  2318.             add,
  2319.             Added,
  2320.             Address,
  2321.             ago,
  2322.             Allowed,
  2323.             AllUsers,
  2324.             and,
  2325.             AntiCheat,
  2326.             AntiCheatAim,
  2327.             AntiCheatFireRate,
  2328.             AntiCheatGravity,
  2329.             AntiCheatMeleeRate,
  2330.             AntiCheatRecoil,
  2331.             AntiCheatStash,
  2332.             AntiCheatTrajectory,
  2333.             AntiCheatWallHack,
  2334.             AntiFlood,
  2335.             AntiFloodChat,
  2336.             AntiFloodCommand,
  2337.             AntiFloodItemDrop,
  2338.             BanCount,
  2339.             BanInherited,
  2340.             Banned,
  2341.             banned,
  2342.             BanReason,
  2343.             BanReset,
  2344.             Blocked,
  2345.             BodyPart,
  2346.             BodyPartArm,
  2347.             BodyPartChest,
  2348.             BodyPartFoot,
  2349.             BodyPartHand,
  2350.             BodyPartHead,
  2351.             BodyPartLeg,
  2352.             BodyPartStomach,
  2353.             Bypassed,
  2354.             Changed,
  2355.             Chat,
  2356.             Command,
  2357.             CommandConfig,
  2358.             CommandConfigAdminSyntax,
  2359.             CommandConfigAntiCheatAimSyntax,
  2360.             CommandConfigAntiCheatFireRateSyntax,
  2361.             CommandConfigAntiCheatGravitySyntax,
  2362.             CommandConfigAntiCheatMeleeRateSyntax,
  2363.             CommandConfigAntiCheatRecoilSyntax,
  2364.             CommandConfigAntiCheatServerSyntax,
  2365.             CommandConfigAntiCheatStashSyntax,
  2366.             CommandConfigAntiCheatSyntax,
  2367.             CommandConfigAntiCheatTrajectorySyntax,
  2368.             CommandConfigAntiCheatWallHackSyntax,
  2369.             CommandConfigAntiFloodChatSyntax,
  2370.             CommandConfigAntiFloodCommandSyntax,
  2371.             CommandConfigAntiFloodItemDropSyntax,
  2372.             CommandConfigAntiFloodSyntax,
  2373.             CommandConfigBanSyntax,
  2374.             CommandConfigCrippleSyntax,
  2375.             CommandConfigDiscordSyntax,
  2376.             CommandConfigEntitySyntax,
  2377.             CommandConfigIpSyntax,
  2378.             CommandConfigLogSyntax,
  2379.             CommandConfigSaveSyntax,
  2380.             CommandConfigSteamSyntax,
  2381.             CommandConfigSyntax,
  2382.             CommandConfigTitle,
  2383.             CommandConfigUserSyntax,
  2384.             CommandConfigViolationSyntax,
  2385.             CommandConfigVpnSyntax,
  2386.             CommandHelp,
  2387.             CommandHelpEntry,
  2388.             CommandHelpTitle,
  2389.             CommandIp,
  2390.             CommandIpEntry,
  2391.             CommandIpEntryFailed,
  2392.             CommandIpList,
  2393.             CommandIpSyntax,
  2394.             CommandIpTitle,
  2395.             CommandLogHeading,
  2396.             CommandLogLine,
  2397.             CommandLogNoEntries,
  2398.             CommandLogSeeConsole,
  2399.             CommandLogSyntax,
  2400.             CommandLogTitle,
  2401.             CommandNoPermission,
  2402.             CommandServerSyntax,
  2403.             CommandServerTitle,
  2404.             CommandTeleport,
  2405.             CommandTeleportSyntax,
  2406.             CommandTeleportTitle,
  2407.             CommandTeleportViolation,
  2408.             CommandUnknown,
  2409.             CommandUser,
  2410.             CommandUserAction,
  2411.             CommandUserInfo,
  2412.             CommandUserKick,
  2413.             CommandUserNotFound,
  2414.             CommandUserSyntax,
  2415.             CommandUserTeam,
  2416.             CommandUserTitle,
  2417.             CommandUserTooMany,
  2418.             CommandVpn,
  2419.             CommandVpnSyntax,
  2420.             CommandVpnTitle,
  2421.             connected,
  2422.             CONSOLE,
  2423.             Cooldown,
  2424.             Crafting,
  2425.             CrippleCount,
  2426.             Crippled,
  2427.             crippled,
  2428.             CrippleReason,
  2429.             CrippleReset,
  2430.             Current,
  2431.             data,
  2432.             Denied,
  2433.             Detected,
  2434.             DurationDay,
  2435.             DurationDays,
  2436.             DurationHour,
  2437.             DurationHours,
  2438.             DurationMinute,
  2439.             DurationMinutes,
  2440.             DurationSecond,
  2441.             DurationSeconds,
  2442.             DurationSecondsUnit,
  2443.             empty,
  2444.             Entity,
  2445.             EntityAnimal,
  2446.             EntityAutoTurret,
  2447.             EntityBear,
  2448.             EntityBoar,
  2449.             EntityBradley,
  2450.             EntityBuilding,
  2451.             EntityChicken,
  2452.             EntityFlameTurret,
  2453.             EntityGunTrap,
  2454.             EntityHelicopter,
  2455.             EntityMurderer,
  2456.             EntityNPC,
  2457.             EntityPlayer,
  2458.             EntitySAMSite,
  2459.             EntityScientist,
  2460.             EntityStag,
  2461.             EntityTC,
  2462.             EntityTrap,
  2463.             file,
  2464.             For,
  2465.             idle,
  2466.             InvalidSteamId,
  2467.             IP,
  2468.             IpBlocked,
  2469.             IpCooldown,
  2470.             Kicked,
  2471.             LogAntiCheatAim,
  2472.             LogAntiCheatGravity,
  2473.             LogAntiCheatMeleeRate,
  2474.             LogAntiCheatRecoil,
  2475.             LogAntiCheatServer,
  2476.             LogAntiCheatStash,
  2477.             LogAntiCheatTrajectory,
  2478.             LogAntiSpamItemDrop,
  2479.             LogConnect,
  2480.             LogDiscordConfig,
  2481.             LogDiscordMessage,
  2482.             LogIpFilter,
  2483.             LogSteam,
  2484.             LogSteamConfig,
  2485.             LogUserBypass,
  2486.             LogVpnCheck,
  2487.             LogVpnCheckConfig,
  2488.             LogVpnCheckError,
  2489.             LogWebHook,
  2490.             Name,
  2491.             never,
  2492.             NoReasonGiven,
  2493.             NULL,
  2494.             offline,
  2495.             OnAntiCheatTriggered,
  2496.             online,
  2497.             OxideGroups,
  2498.             Pardoned,
  2499.             permanently,
  2500.             Played,
  2501.             remove,
  2502.             Removed,
  2503.             Saved,
  2504.             Server,
  2505.             SERVER,
  2506.             Status,
  2507.             Steam,
  2508.             SteamBanActive,
  2509.             SteamBanCommunity,
  2510.             SteamBanDays,
  2511.             SteamBanEconomy,
  2512.             SteamBanGame,
  2513.             SteamBanVAC,
  2514.             SteamGameCount,
  2515.             SteamGameHours,
  2516.             SteamID,
  2517.             SteamProfileInvalid,
  2518.             SteamProfileLimited,
  2519.             SteamProfilePrivate,
  2520.             SteamShareFamily,
  2521.             True,
  2522.             Unbanned,
  2523.             Unblocked,
  2524.             Uncrippled,
  2525.             unknown,
  2526.             UnknownKey,
  2527.             UserAction,
  2528.             UserBanTeleport,
  2529.             UserBanTeleported,
  2530.             UserConnectBanInherit,
  2531.             UserConnectIpBlocked,
  2532.             UserConnectSteamIdInvalid,
  2533.             UserInfoText,
  2534.             UserInfoTextBullet,
  2535.             UserInfoTextLabel,
  2536.             UserPardonProgress,
  2537.             Violation,
  2538.             ViolationAim,
  2539.             ViolationFireRate,
  2540.             ViolationGravity,
  2541.             ViolationMeleeRate,
  2542.             ViolationRecoil,
  2543.             ViolationStash,
  2544.             ViolationTrajectory,
  2545.             ViolationWallHack,
  2546.             VPN,
  2547.             VpnCache,
  2548.             VpnDetected,
  2549.             Warning
  2550.         }
  2551.  
  2552.         private class Text
  2553.         {
  2554.             private static readonly Dictionary<string, Dictionary<Key, string>> decorated = new Dictionary<string, Dictionary<Key, string>>();
  2555.             private static readonly Dictionary<string, Dictionary<Key, string>> unadorned = new Dictionary<string, Dictionary<Key, string>>();
  2556.  
  2557.             private static string server_language;
  2558.  
  2559.             private class RegEx
  2560.             {
  2561.                 public static Regex clean1;
  2562.                 public static Regex clean2;
  2563.                 public static Regex clean3;
  2564.                 public static Regex markup;
  2565.                 public static Regex spaces;
  2566.  
  2567.                 public static void Load()
  2568.                 {
  2569.                     clean1 = new Regex(@"\p{Cc}+|\p{Cs}+", RegexOptions.Compiled);
  2570.                     clean2 = new Regex(@"\{+|\}+",         RegexOptions.Compiled);
  2571.                     markup = new Regex(@"<[^>]+>",         RegexOptions.Compiled);
  2572.                     spaces = new Regex(@"\s{2,}",          RegexOptions.Compiled);
  2573.                 }
  2574.  
  2575.                 public static void Unload()
  2576.                 {
  2577.                     clean1 = clean2 = clean3 = markup = spaces = null;
  2578.                 }
  2579.             }
  2580.  
  2581.             public static string Actor(IPlayer actor) =>
  2582.                 Actor(actor, server_language);
  2583.             public static string Actor(IPlayer actor, BasePlayer player) =>
  2584.                 Actor(actor, Language(player.UserIDString));
  2585.             public static string Actor(IPlayer actor, IPlayer iplayer) =>
  2586.                 Actor(actor, iplayer.IsServer ? server_language : Language(iplayer.Id));
  2587.             public static string Actor(IPlayer actor, ulong userid) =>
  2588.                 Actor(actor, Language(userid.ToString()));
  2589.             public static string Actor(IPlayer actor, string language)
  2590.             {
  2591.                 if(actor == null)
  2592.                 {
  2593.                     return GetPlain(Key.SERVER, language);
  2594.                 }
  2595.                 else if(actor.IsServer)
  2596.                 {
  2597.                     return GetPlain(Key.CONSOLE, language);
  2598.                 }
  2599.                 else
  2600.                 {
  2601.                     return Sanitize(actor.Name);
  2602.                 }
  2603.             }
  2604.  
  2605.             public static string BodyPart(HitArea area) =>
  2606.                 BodyPart(area, server_language);
  2607.             public static string BodyPart(HitArea area, BasePlayer player) =>
  2608.                 BodyPart(area, Language(player.UserIDString));
  2609.             public static string BodyPart(HitArea area, IPlayer iplayer) =>
  2610.                 BodyPart(area, iplayer.IsServer ? server_language : Language(iplayer.Id));
  2611.             public static string BodyPart(HitArea area, ulong userid) =>
  2612.                 BodyPart(area, Language(userid.ToString()));
  2613.             public static string BodyPart(HitArea area, string language)
  2614.             {
  2615.                 switch(area)
  2616.                 {
  2617.                 case HitArea.Head:    return GetPlain(Key.BodyPartHead);
  2618.                 case HitArea.Chest:   return GetPlain(Key.BodyPartChest);
  2619.                 case HitArea.Stomach: return GetPlain(Key.BodyPartStomach);
  2620.                 case HitArea.Arm:     return GetPlain(Key.BodyPartArm);
  2621.                 case HitArea.Hand:    return GetPlain(Key.BodyPartHand);
  2622.                 case HitArea.Leg:     return GetPlain(Key.BodyPartLeg);
  2623.                 case HitArea.Foot:    return GetPlain(Key.BodyPartFoot);
  2624.                 }
  2625.  
  2626.                 return GetPlain(Key.BodyPart, language);
  2627.             }
  2628.  
  2629.             public class Duration
  2630.             {
  2631.                 public static string Hours(TimeSpan time) =>
  2632.                     Hours(time, server_language);
  2633.                 public static string Hours(TimeSpan time, BasePlayer player) =>
  2634.                     Hours(time, Language(player.UserIDString));
  2635.                 public static string Hours(TimeSpan time, IPlayer iplayer) =>
  2636.                     Hours(time, iplayer.IsServer ? server_language : Language(iplayer.Id));
  2637.                 public static string Hours(TimeSpan time, ulong userid) =>
  2638.                     Hours(time, Language(userid.ToString()));
  2639.                 public static string Hours(TimeSpan time, string language) =>
  2640.                     $"{time.Duration().TotalHours:0.00} {GetPlain(Key.DurationHours, language)}";
  2641.  
  2642.                 public static string Short(TimeSpan time) =>
  2643.                     Short(time, server_language);
  2644.                 public static string Short(TimeSpan time, BasePlayer player) =>
  2645.                     Short(time, Language(player.UserIDString));
  2646.                 public static string Short(TimeSpan time, IPlayer iplayer) =>
  2647.                     Short(time, iplayer.IsServer ? server_language : Language(iplayer.Id));
  2648.                 public static string Short(TimeSpan time, ulong userid) =>
  2649.                     Short(time, Language(userid.ToString()));
  2650.                 public static string Short(TimeSpan time, string language)
  2651.                 {
  2652.                     var duration = time.Duration();
  2653.  
  2654.                     if(duration.Days != 0)
  2655.                     {
  2656.                         return $"{duration.TotalDays:0.00} {GetPlain(Key.DurationDays, language)}";
  2657.                     }
  2658.                     else if(duration.Hours != 0)
  2659.                     {
  2660.                         return $"{duration.TotalHours:0.00} {GetPlain(Key.DurationHours, language)}";
  2661.                     }
  2662.                     else if(duration.Minutes != 0)
  2663.                     {
  2664.                         return $"{duration.TotalMinutes:0.00} {GetPlain(Key.DurationMinutes, language)}";
  2665.                     }
  2666.                     else
  2667.                     {
  2668.                         return $"{duration.TotalSeconds:0.00} {GetPlain(Key.DurationSeconds, language)}";
  2669.                     }
  2670.                 }
  2671.             }
  2672.  
  2673.             public static string Get(Key key, Dictionary<string, string> parameters = null) =>
  2674.                 Get(key, server_language, parameters);
  2675.             public static string Get(Key key, BasePlayer player, Dictionary<string, string> parameters = null) =>
  2676.                 Get(key, Language(player.UserIDString), parameters);
  2677.             public static string Get(Key key, IPlayer iplayer, Dictionary<string, string> parameters = null)
  2678.             {
  2679.                 if(iplayer.LastCommand == CommandType.Console)
  2680.                 {
  2681.                     return GetPlain(key, iplayer.IsServer ? server_language : Language(iplayer.Id), parameters);
  2682.                 }
  2683.                 else
  2684.                 {
  2685.                     return Get(key, iplayer.IsServer ? server_language : Language(iplayer.Id), parameters);
  2686.                 }
  2687.             }
  2688.             public static string Get(Key key, ulong userid, Dictionary<string, string> parameters = null) =>
  2689.                 Get(key, Language(userid.ToString()), parameters);
  2690.             public static string Get(Key key, string language, Dictionary<string, string> parameters = null) =>
  2691.                 Get(key, Messages(decorated, language), parameters);
  2692.             private static string Get(Key key, Dictionary<Key, string> cache, Dictionary<string, string> parameters)
  2693.             {
  2694.                 string message = null;
  2695.  
  2696.                 if(cache?.TryGetValue(key, out message) ?? false)
  2697.                 {
  2698.                     return Replace(message, parameters);
  2699.                 }
  2700.  
  2701.                 return Enum.GetName(typeof(Key), key);
  2702.             }
  2703.  
  2704.             public static string GetPlain(Key key, Dictionary<string, string> parameters = null) =>
  2705.                 GetPlain(key, server_language, parameters);
  2706.             public static string GetPlain(Key key, BasePlayer player, Dictionary<string, string> parameters = null) =>
  2707.                 GetPlain(key, Language(player.UserIDString), parameters);
  2708.             public static string GetPlain(Key key, IPlayer iplayer, Dictionary<string, string> parameters = null) =>
  2709.                 GetPlain(key, iplayer.IsServer ? server_language : Language(iplayer.Id), parameters);
  2710.             public static string GetPlain(Key key, ulong userid, Dictionary<string, string> parameters = null) =>
  2711.                 GetPlain(key, Language(userid.ToString()), parameters);
  2712.             public static string GetPlain(Key key, string language, Dictionary<string, string> parameters = null) =>
  2713.                 Get(key, Messages(unadorned, language), parameters);
  2714.  
  2715.             public static string Language(string userid = null)
  2716.             {
  2717.                 if(string.IsNullOrEmpty(userid))
  2718.                 {
  2719.                     return server_language;
  2720.                 }
  2721.  
  2722.                 var language = _instance.lang.GetLanguage(userid) ?? server_language;
  2723.  
  2724.                 return decorated.ContainsKey(language) ? language : server_language;
  2725.             }
  2726.  
  2727.             public static void Load()
  2728.             {
  2729.                 var languages = _instance.lang.GetLanguages(_instance);
  2730.  
  2731.                 if((languages.Length == 0) || Configuration.Upgraded())
  2732.                 {
  2733.                     RegisterMessages();
  2734.  
  2735.                     languages = _instance.lang.GetLanguages(_instance);
  2736.                 }
  2737.  
  2738.                 var requested = _instance.lang.GetServerLanguage();
  2739.  
  2740.                 if(!string.IsNullOrEmpty(requested) && languages.Contains(requested))
  2741.                 {
  2742.                     server_language = requested;
  2743.                 }
  2744.                 else
  2745.                 {
  2746.                     server_language = "en";
  2747.                 }
  2748.  
  2749.                 RegEx.Load();
  2750.  
  2751.                 foreach(var language in languages)
  2752.                 {
  2753.                     var m_decorated = decorated[language] = new Dictionary<Key, string>();
  2754.                     var m_unadorned = unadorned[language] = new Dictionary<Key, string>();
  2755.  
  2756.                     foreach(var entry in _instance.lang.GetMessages(language, _instance))
  2757.                     {
  2758.                         if(string.IsNullOrEmpty(entry.Key))
  2759.                         {
  2760.                             continue;
  2761.                         }
  2762.  
  2763.                         Key key;
  2764.  
  2765.                         if(Enum.TryParse(entry.Key, out key))
  2766.                         {
  2767.                             m_decorated.Add(key, entry.Value);
  2768.                             m_unadorned.Add(key, Strip(entry.Value));
  2769.                         }
  2770.                         else
  2771.                         {
  2772.                             Log.Console(Key.UnknownKey, new Dictionary<string, string>
  2773.                             {
  2774.                                 { "key", entry.Key },
  2775.                                 { "language", language }
  2776.                             });
  2777.                         }
  2778.                     }
  2779.                 }
  2780.             }
  2781.  
  2782.             private static Dictionary<Key, string> Messages(Dictionary<string, Dictionary<Key, string>> cache, string language)
  2783.             {
  2784.                 Dictionary<Key, string> messages;
  2785.  
  2786.                 if(!cache.TryGetValue(language, out messages))
  2787.                 {
  2788.                     if(!cache.TryGetValue(server_language, out messages))
  2789.                     {
  2790.                         return null;
  2791.                     }
  2792.                 }
  2793.  
  2794.                 return messages;
  2795.             }
  2796.  
  2797.             public static bool ParseTime(string message, out ulong seconds)
  2798.             {
  2799.                 seconds = 0;
  2800.  
  2801.                 if(string.IsNullOrEmpty(message))
  2802.                 {
  2803.                     return false;
  2804.                 }
  2805.  
  2806.                 ulong amount = 0; char units = 's';
  2807.  
  2808.                 for(int i = 0; i < message.Length; ++i)
  2809.                 {
  2810.                     var current = message[i];
  2811.  
  2812.                     if(char.IsDigit(current))
  2813.                     {
  2814.                         amount *= 10;
  2815.                         amount += (ulong)(current - '0');
  2816.                     }
  2817.                     else
  2818.                     {
  2819.                         if((i + 1) != message.Length)
  2820.                         {
  2821.                             return false;
  2822.                         }
  2823.  
  2824.                         units = char.ToLower(current);
  2825.                     }
  2826.                 }
  2827.  
  2828.                 switch(units)
  2829.                 {
  2830.                 case 'd': seconds = amount * 86400ul; return true;
  2831.                 case 'h': seconds = amount *  3600ul; return true;
  2832.                 case 'm': seconds = amount *    60ul; return true;
  2833.                 case 's': seconds = amount;           return true;
  2834.                 }
  2835.  
  2836.                 return false;
  2837.             }
  2838.  
  2839.             private static void RegisterMessages()
  2840.             {
  2841.                 _instance.lang.RegisterMessages(new Dictionary<string, string>(), _instance, "en");
  2842.                 _instance.lang.RegisterMessages(new Dictionary<string, string>
  2843.                 {
  2844.                     { nameof(Key.add), "add" },
  2845.                     { nameof(Key.Added), "Added" },
  2846.                     { nameof(Key.Address), "Address" },
  2847.                     { nameof(Key.ago), "ago" },
  2848.                     { nameof(Key.Allowed), "Allowed" },
  2849.                     { nameof(Key.AllUsers), "All Users" },
  2850.                     { nameof(Key.and), "and" },
  2851.                     { nameof(Key.AntiCheat), "AntiCheat" },
  2852.                     { nameof(Key.AntiCheatAim), "Aim" },
  2853.                     { nameof(Key.AntiCheatFireRate), "Fire Rate" },
  2854.                     { nameof(Key.AntiCheatGravity), "Gravity" },
  2855.                     { nameof(Key.AntiCheatMeleeRate), "Melee Rate" },
  2856.                     { nameof(Key.AntiCheatRecoil), "Recoil" },
  2857.                     { nameof(Key.AntiCheatStash), "Stash" },
  2858.                     { nameof(Key.AntiCheatTrajectory), "Trajectory" },
  2859.                     { nameof(Key.AntiCheatWallHack), "Wall Hack" },
  2860.                     { nameof(Key.AntiFlood), "AntiFlood" },
  2861.                     { nameof(Key.AntiFloodChat), "Chat" },
  2862.                     { nameof(Key.AntiFloodCommand), "Command" },
  2863.                     { nameof(Key.AntiFloodItemDrop), "Dropped Items" },
  2864.                     { nameof(Key.BanCount), "Ban Count" },
  2865.                     { nameof(Key.BanInherited), "Ban inherited" },
  2866.                     { nameof(Key.Banned), "Banned" },
  2867.                     { nameof(Key.banned), "banned" },
  2868.                     { nameof(Key.BanReason), "Ban Reason" },
  2869.                     { nameof(Key.BanReset), "Reset ban data for" },
  2870.                     { nameof(Key.Blocked), "Blocked" },
  2871.                     { nameof(Key.BodyPart), "Body" },
  2872.                     { nameof(Key.BodyPartArm), "Arm" },
  2873.                     { nameof(Key.BodyPartChest), "Chest" },
  2874.                     { nameof(Key.BodyPartFoot), "Foot" },
  2875.                     { nameof(Key.BodyPartHand), "Hand" },
  2876.                     { nameof(Key.BodyPartHead), "Head" },
  2877.                     { nameof(Key.BodyPartLeg), "Leg" },
  2878.                     { nameof(Key.BodyPartStomach), "Stomach" },
  2879.                     { nameof(Key.Bypassed), "Bypassed" },
  2880.                     { nameof(Key.Changed), "Changed" },
  2881.                     { nameof(Key.Chat), "Chat" },
  2882.                     { nameof(Key.Command), "Command" },
  2883.                     { nameof(Key.CommandConfig), "<size=12><color=#c0ffc0>{action} configuration: </color><color=#c0c0ff>{info}</color></size>" },
  2884.                     { nameof(Key.CommandConfigAdminSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} admin.broadcast [true|false]\n    {command} admin.bypass [true|false]</color></size>" },
  2885.                     { nameof(Key.CommandConfigAntiCheatAimSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} anticheat.aim.ban [true|false]\n    {command} anticheat.aim.cooldown [SECONDS]\n    {command} anticheat.aim.enabled [true|false]\n    {command} anticheat.aim.sensitivity [0.0-1.0]\n    {command} anticheat.aim.trigger.animal [true|false]\n    {command} anticheat.aim.trigger.bradley [true|false]\n    {command} anticheat.aim.trigger.helicopter [true|false]\n    {command} anticheat.aim.trigger.npc [true|false]\n    {command} anticheat.aim.warn [true|false]</color></size>" },
  2886.                     { nameof(Key.CommandConfigAntiCheatFireRateSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} anticheat.firerate.ban [true|false]\n    {command} anticheat.firerate.cooldown [SECONDS]\n    {command} anticheat.firerate.enabled [true|false]\n    {command} anticheat.firerate.sensitivity [0.0-1.0]\n    {command} anticheat.firerate.warn [true|false]</color></size>" },
  2887.                     { nameof(Key.CommandConfigAntiCheatGravitySyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} anticheat.gravity.ban [true|false]\n    {command} anticheat.gravity.cooldown [SECONDS]\n    {command} anticheat.gravity.enabled [true|false]\n    {command} anticheat.gravity.sensitivity [0.0-1.0]\n    {command} anticheat.gravity.warn [true|false]</color></size>" },
  2888.                     { nameof(Key.CommandConfigAntiCheatMeleeRateSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} anticheat.meleerate.ban [true|false]\n    {command} anticheat.meleerate.cooldown [SECONDS]\n    {command} anticheat.meleerate.enabled [true|false]\n    {command} anticheat.meleerate.sensitivity [0.0-1.0]\n    {command} anticheat.meleerate.warn [true|false]</color></size>" },
  2889.                     { nameof(Key.CommandConfigAntiCheatRecoilSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} anticheat.recoil.ban [true|false]\n    {command} anticheat.recoil.cooldown [SECONDS]\n    {command} anticheat.recoil.enabled [true|false]\n    {command} anticheat.recoil.sensitivity [0.0-1.0]\n    {command} anticheat.recoil.warn [true|false]</color></size>" },
  2890.                     { nameof(Key.CommandConfigAntiCheatServerSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} anticheat.server.ban [true|false]\n    {command} anticheat.server.cooldown [SECONDS]\n    {command} anticheat.server.enabled [true|false]\n    {command} anticheat.server.sensitivity [0.0-1.0]\n    {command} anticheat.server.warn [true|false]</color></size>" },
  2891.                     { nameof(Key.CommandConfigAntiCheatStashSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} anticheat.stash.ban [true|false]\n    {command} anticheat.stash.cooldown [SECONDS]\n    {command} anticheat.stash.enabled [true|false]\n    {command} anticheat.stash.sensitivity [0.0-1.0]\n    {command} anticheat.stash.warn [true|false]</color></size>" },
  2892.                     { nameof(Key.CommandConfigAntiCheatSyntax), "<size=12><color=#ffc0c0>Syntax:</color><color=#c0c0ff>\n    {command} anticheat.aim\n    {command} anticheat.firerate\n    {command} anticheat.gravity\n    {command} anticheat.meleerate\n    {command} anticheat.recoil\n    {command} anticheat.server\n    {command} anticheat.stash\n    {command} anticheat.trajetory\n    {command} anticheat.wallhack</color></size>" },
  2893.                     { nameof(Key.CommandConfigAntiCheatTrajectorySyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} anticheat.trajectory.ban [true|false]\n    {command} anticheat.trajectory.cooldown [SECONDS]\n    {command} anticheat.trajectory.enabled [true|false]\n    {command} anticheat.trajectory.sensitivity [0.0-1.0]\n    {command} anticheat.trajectory.warn [true|false]</color></size>" },
  2894.                     { nameof(Key.CommandConfigAntiCheatWallHackSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} anticheat.wallhack.ban [true|false]\n    {command} anticheat.wallhack.cooldown [SECONDS]\n    {command} anticheat.wallhack.enabled [true|false]\n    {command} anticheat.wallhack.sensitivity [0.0-1.0]\n    {command} anticheat.wallhack.warn [true|false]</color></size>" },
  2895.                     { nameof(Key.CommandConfigAntiFloodChatSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} antiflood.chat.ban [true|false]\n    {command} antiflood.chat.cooldown [SECONDS]\n    {command} antiflood.chat.enabled [true|false]\n    {command} antiflood.chat.sensitivity [0.0-1.0]\n    {command} antiflood.chat.warn [true|false]</color></size>" },
  2896.                     { nameof(Key.CommandConfigAntiFloodCommandSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} antiflood.command.ban [true|false]\n    {command} antiflood.command.cooldown [SECONDS]\n    {command} antiflood.command.enabled [true|false]\n    {command} antiflood.command.sensitivity [0.0-1.0]\n    {command} antiflood.command.warn [true|false]</color></size>" },
  2897.                     { nameof(Key.CommandConfigAntiFloodItemDropSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} antiflood.itemdrop.ban [true|false]\n    {command} antiflood.itemdrop.cooldown [SECONDS]\n    {command} antiflood.itemdrop.enabled [true|false]\n    {command} antiflood.itemdrop.sensitivity [0.0-1.0]\n    {command} antiflood.itemdrop.warn [true|false]</color></size>" },
  2898.                     { nameof(Key.CommandConfigAntiFloodSyntax), "<size=12><color=#ffc0c0>Syntax:</color><color=#c0c0ff>\n    {command} antiflood.chat\n    {command} antiflood.command\n    {command} antiflood.itemdrop</color></size>" },
  2899.                     { nameof(Key.CommandConfigBanSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} ban.inherit [true|false]\n    {command} ban.teleport [true|false]\n    {command} ban.time.enforce [true|false]\n    {command} ban.time.multiply [true|false]\n    {command} ban.time.seconds [AMOUNT[d|h|m|s]]</color></size>" },
  2900.                     { nameof(Key.CommandConfigCrippleSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} cripple.heal [true|false]\n    {command} cripple.inherit [true|false]</color></size>" },
  2901.                     { nameof(Key.CommandConfigDiscordSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} discord.enabled [true|false]\n    {command} discord.webhook [URL]\n    {command} discord.filters.anticheat.enabled [true|false]\n    {command} discord.filters.anticheat.webhook [URL]\n    {command} discord.filters.antiflood.enabled [true|false]\n    {command} discord.filters.antiflood.webhook [URL]\n    {command} discord.filters.ip.enabled [true|false]\n    {command} discord.filters.ip.webhook [URL]\n    {command} discord.filters.steam.enabled [true|false]\n    {command} discord.filters.steam.webhook [URL]\n    {command} discord.filters.vpn.enabled [true|false]\n    {command} discord.filters.vpn.webhook [URL]</color></size>" },
  2902.                     { nameof(Key.CommandConfigEntitySyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} entity.damage.animal [0.0-100.0]\n    {command} entity.damage.bradley [0.0-100.0]\n    {command} entity.damage.building [0.0-100.0]\n    {command} entity.damage.entity [0.0-100.0]\n    {command} entity.damage.friend [0.0-100.0]\n    {command} entity.damage.helicopter [0.0-100.0]\n    {command} entity.damage.npc [0.0-100.0]\n    {command} entity.damage.player [0.0-100.0]\n    {command} entity.damage.team [0.0-100.0]\n    {command} entity.damage.trap [0.0-100.0]</color></size>" },
  2903.                     { nameof(Key.CommandConfigIpSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} ip.filter.cooldown [SECONDS]\n    {command} ip.violation.ban [true|false]\n    {command} ip.violation.enabled [true|false]</color></size>" },
  2904.                     { nameof(Key.CommandConfigLogSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} log.anticheat.gravity [true|false]\n    {command} log.anticheat.meleerate [true|false]\n    {command} log.anticheat.projectile [true|false]\n    {command} log.anticheat.server [true|false]\n    {command} log.anticheat.stash [true|false]\n    {command} log.antiflood.itemdrop [true|false]\n    {command} log.ip.filter [true|false]\n    {command} log.projectile.collapse [true|false]\n    {command} log.projectile.verbose [true|false]\n    {command} log.user.connect [true|false]\n    {command} log.vpn.bypass [true|false]\n    {command} log.vpn.check [true|false]</color></size>" },
  2905.                     { nameof(Key.CommandConfigSaveSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} save</color></size>" },
  2906.                     { nameof(Key.CommandConfigSteamSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} steam.api.apikey [\"API-KEY\"]\n    {command} steam.api.enabled [true|false]\n    {command} steam.ban.active [true|false]\n    {command} steam.ban.community [true|false]\n    {command} steam.ban.days [DAYS]\n    {command} steam.ban.economy [true|false]\n    {command} steam.ban.game [NUMBER]\n    {command} steam.ban.vac [NUMBER]\n    {command} steam.game.count [NUMBER]\n    {command} steam.game.hours [HOURS]\n    {command} steam.profile.invalid [true|false]\n    {command} steam.profile.limited [true|false]\n    {command} steam.profile.private [true|false]\n    {command} steam.share.family [true|false]\n    {command} steam.violation.ban [true|false]\n    {command} steam.violation.enabled [true|false]\n    {command} steam.violation.warn [true|false]</color></size>" },
  2907.                     { nameof(Key.CommandConfigSyntax), "<size=12><color=#ffc0c0>Syntax:</color><color=#c0c0ff>\n    {command} admin\n    {command} anticheat\n    {command} antiflood\n    {command} ban\n    {command} cripple\n    {command} discord\n    {command} entity\n    {command} ip\n    {command} log\n    {command} save\n    {command} steam\n    {command} user\n    {command} violation\n    {command} vpn</color></size>" },
  2908.                     { nameof(Key.CommandConfigTitle), "Configuration Settings" },
  2909.                     { nameof(Key.CommandConfigUserSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} user.bypass.dayssincelastban [DAYS]\n    {command} user.bypass.enabled [true|false]\n    {command} user.bypass.multiply [true|false]\n    {command} user.bypass.hoursplayed [HOURS]</color></size>" },
  2910.                     { nameof(Key.CommandConfigViolationSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} violation.ban [true|false]\n    {command} violation.cooldown [SECONDS]\n    {command} violation.enabled [true|false]\n    {command} violation.sensitivity [0.0-1.0]</size>" },
  2911.                     { nameof(Key.CommandConfigVpnSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} vpn.api.getipintel.apikey [\"API-KEY\"]\n    {command} vpn.api.getipintel.enabled [true|false]\n    {command} vpn.api.ipapi.enabled [true|false]\n    {command} vpn.api.iphub.apikey [\"API-KEY\"]\n    {command} vpn.api.iphub.enabled [true|false]\n    {command} vpn.api.ipqualityscore.apikey [\"API-KEY\"]\n    {command} vpn.api.ipqualityscore.enabled [true|false]\n    {command} vpn.cache.hours [HOURS]\n    {command} vpn.check.enabled [true|false]\n    {command} vpn.check.strict [true|false]\n    {command} vpn.violation.ban [true|false]\n    {command} vpn.violation.enabled [true|false]\n    {command} vpn.violation.warn [true|false]</color></size>" },
  2912.                     { nameof(Key.CommandHelp), "<size=12><color=#ffffc0>{name} v{version} - Help</color>{entries}</size>" },
  2913.                     { nameof(Key.CommandHelpEntry), "<color=#c0ffc0>\n{title}:</color><color=#c0c0ff>{aliases}</color>" },
  2914.                     { nameof(Key.CommandHelpTitle), "Help Information" },
  2915.                     { nameof(Key.CommandIp), "<size=12><color=#c0ffc0>{action} IP: </color><color=#c0c0ff>{info}</color></size>" },
  2916.                     { nameof(Key.CommandIpEntry), "<size=12><color=#c0ffc0>{action} IP entry: </color><color=#c0c0ff>{entry}</color></size>" },
  2917.                     { nameof(Key.CommandIpEntryFailed), "<size=12><color=#ffc0c0>Failed to {action} IP entry: </color><color=#c0c0ff>{entry}</color></size>" },
  2918.                     { nameof(Key.CommandIpList), "<size=12><color=#c0ffc0>{type} IP entries: </color><color=#c0c0ff>{addresses}</color></size>" },
  2919.                     { nameof(Key.CommandIpSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} allow\n    {command} allow IP[/BITS]\n    {command} allow add IP[/BITS]\n    {command} allow remove IP[/BITS]\n    {command} block IP\n    {command} bypass IP\n    {command} deny\n    {command} deny IP[/BITS]\n    {command} deny add IP[/BITS]\n    {command} deny remove IP[/BITS]\n    {command} save\n    {command} unblock IP</color></size>" },
  2920.                     { nameof(Key.CommandIpTitle), "IP Filtering" },
  2921.                     { nameof(Key.CommandLogHeading), "<size=12><color=#c0ffc0>Projectile Log for {playername}[{playerid}]:</color></size>" },
  2922.                     { nameof(Key.CommandLogLine), "<size=12><color=#c0c0c0>{info}</color></size>" },
  2923.                     { nameof(Key.CommandLogNoEntries), "<size=12><color=#ffffc0>No log data available for {playername}[{playerid}].</color></size>" },
  2924.                     { nameof(Key.CommandLogSeeConsole), "<size=12><color=#c0ffc0>Check the console (F1) for results.</color></size>" },
  2925.                     { nameof(Key.CommandLogSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} IP|NAME|STEAMID [LINES]</color></size>" },
  2926.                     { nameof(Key.CommandLogTitle), "Projectile Log" },
  2927.                     { nameof(Key.CommandNoPermission), "<color=#ffc0c0><size=12>You do not have permission to use this command.</size></color>" },
  2928.                     { nameof(Key.CommandServerSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} pardon\n    {command} unban\n    {command} uncripple</color></size>" },
  2929.                     { nameof(Key.CommandServerTitle), "Server Administration" },
  2930.                     { nameof(Key.CommandTeleport), "<size=12><color=#c0ffc0>Last seen location: </color><color=#c0c0ff>{position}</color></size>" },
  2931.                     { nameof(Key.CommandTeleportSyntax), "<size=12><color=#ffc0c0>Syntax:</color><color=#c0c0ff>\n    {command} IP|NAME|STEAMID</color></size>" },
  2932.                     { nameof(Key.CommandTeleportTitle), "Teleportation" },
  2933.                     { nameof(Key.CommandTeleportViolation), "<size=12><color=#c0ffc0>Violation location: </color><color=#c0c0ff>{position}</color></size>" },
  2934.                     { nameof(Key.CommandUnknown), "<size=12><color=#ffc0c0>Command unknown: </color><color=#c0c0ff>{command}</color></size>" },
  2935.                     { nameof(Key.CommandUser), "<size=12><color=#c0ffc0>{action} {playername}[{playerid}] {duration}: </color><color=#c0c0ff>{reason}</color></size>" },
  2936.                     { nameof(Key.CommandUserAction), "<size=12><color=#c0ffc0>{action} {playername}[{playerid}]</color></size>" },
  2937.                     { nameof(Key.CommandUserInfo), "<size=12><color=#c0ffc0>User information:</color><color=#c0c0ff>{info}</color></size>" },
  2938.                     { nameof(Key.CommandUserKick), "<size=12><color=#c0ffc0>{action} {playername}[{playerid}]: </color><color=#c0c0ff>{reason}</color></size>" },
  2939.                     { nameof(Key.CommandUserNotFound), "<size=12><color=#ffc0c0>User not found.</color></size>" },
  2940.                     { nameof(Key.CommandUserSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} IP|NAME|STEAMID [team]\n    {command} IP|NAME|STEAMID [team.]ban [\"REASON\"] [TIME[d|h|m|s]]\n    {command} IP|NAME|STEAMID [team.]ban.reset\n    {command} IP|NAME|STEAMID [team.]cripple [\"REASON\"] [TIME[d|h|m|s]]\n    {command} IP|NAME|STEAMID [team.]cripple.reset\n    {command} IP|NAME|STEAMID [team.]kick [\"REASON\"]\n    {command} IP|NAME|STEAMID [team.]pardon\n    {command} IP|NAME|STEAMID [team.]unban\n    {command} IP|NAME|STEAMID [team.]uncripple</color></size>" },
  2941.                     { nameof(Key.CommandUserTeam), "<size=12><color=#c0ffc0>Team members:</color><color=#c0c0ff>{users}</color></size>" },
  2942.                     { nameof(Key.CommandUserTitle), "User Administration" },
  2943.                     { nameof(Key.CommandUserTooMany), "<size=12><color=#ffc0c0>Found multiple users:</color><color=#c0c0ff>{users}</color></size>" },
  2944.                     { nameof(Key.CommandVpn), "<size=12><color=#c0ffc0>{action} VPN check: </color><color=#c0c0ff>{info}</color></size>" },
  2945.                     { nameof(Key.CommandVpnSyntax), "<size=12><color=#ffc0c0>Syntax [optional]:</color><color=#c0c0ff>\n    {command} bypass IP\n    {command} status IP\n    {command} unblock IP</color></size>" },
  2946.                     { nameof(Key.CommandVpnTitle), "VPN Detection" },
  2947.                     { nameof(Key.connected), "connected" },
  2948.                     { nameof(Key.CONSOLE), "CONSOLE" },
  2949.                     { nameof(Key.Cooldown), "<color=#ffc0c0><size=12>{type} cooldown: {cooldown}s</size></color>" },
  2950.                     { nameof(Key.Crafting), "Crafting" },
  2951.                     { nameof(Key.CrippleCount), "Cripple Count" },
  2952.                     { nameof(Key.Crippled), "Crippled" },
  2953.                     { nameof(Key.crippled), "crippled" },
  2954.                     { nameof(Key.CrippleReason), "Cripple Reason" },
  2955.                     { nameof(Key.CrippleReset), "Reset cripple data for" },
  2956.                     { nameof(Key.Current), "Current" },
  2957.                     { nameof(Key.data), "data" },
  2958.                     { nameof(Key.Denied), "Denied" },
  2959.                     { nameof(Key.Detected), "Detected" },
  2960.                     { nameof(Key.DurationDay), "day" },
  2961.                     { nameof(Key.DurationDays), "days" },
  2962.                     { nameof(Key.DurationHour), "hour" },
  2963.                     { nameof(Key.DurationHours), "hours" },
  2964.                     { nameof(Key.DurationMinute), "minute" },
  2965.                     { nameof(Key.DurationMinutes), "minutes" },
  2966.                     { nameof(Key.DurationSecond), "second" },
  2967.                     { nameof(Key.DurationSeconds), "seconds" },
  2968.                     { nameof(Key.DurationSecondsUnit), "s" },
  2969.                     { nameof(Key.empty), "empty" },
  2970.                     { nameof(Key.Entity), "Entity" },
  2971.                     { nameof(Key.EntityAnimal), "Animal" },
  2972.                     { nameof(Key.EntityAutoTurret), "Auto Turret" },
  2973.                     { nameof(Key.EntityBear), "Bear" },
  2974.                     { nameof(Key.EntityBoar), "Boar" },
  2975.                     { nameof(Key.EntityBradley), "Bradley APC" },
  2976.                     { nameof(Key.EntityBuilding), "Building" },
  2977.                     { nameof(Key.EntityChicken), "Chicken" },
  2978.                     { nameof(Key.EntityFlameTurret), "Flame Turret" },
  2979.                     { nameof(Key.EntityGunTrap), "Shotgun Trap" },
  2980.                     { nameof(Key.EntityHelicopter), "Helicopter" },
  2981.                     { nameof(Key.EntityMurderer), "Murderer" },
  2982.                     { nameof(Key.EntityNPC), "NPC" },
  2983.                     { nameof(Key.EntityPlayer), "Player" },
  2984.                     { nameof(Key.EntitySAMSite), "SAM Site" },
  2985.                     { nameof(Key.EntityScientist), "Scientist" },
  2986.                     { nameof(Key.EntityStag), "Stag" },
  2987.                     { nameof(Key.EntityTC), "Tool Cupboard" },
  2988.                     { nameof(Key.EntityTrap), "Trap" },
  2989.                     { nameof(Key.file), "file" },
  2990.                     { nameof(Key.For), "for" },
  2991.                     { nameof(Key.idle), "idle" },
  2992.                     { nameof(Key.InvalidSteamId), "Invalid SteamID" },
  2993.                     { nameof(Key.IP), "IP" },
  2994.                     { nameof(Key.IpBlocked), "<color=#ffc000><size=12>IP: Blocked</size></color>" },
  2995.                     { nameof(Key.IpCooldown), "<color=#ffc000><size=12>IP: Cooldown</size></color>" },
  2996.                     { nameof(Key.Kicked), "Kicked" },
  2997.                     { nameof(Key.LogAntiCheatAim), "<color=#ffc000><size=12>Hit info for {playername}[{playerid}]: {target} ({bodypart}; {distance}m; r={range_variance}; a={angle_variance}; p={pvp_variance}; {speed}m/s) {weapon}</size></color>" },
  2998.                     { nameof(Key.LogAntiCheatGravity), "<color=#ffc000><size=12>Gravity info for {playername}[{playerid}]: {amount} {position}</size></color>" },
  2999.                     { nameof(Key.LogAntiCheatMeleeRate), "<color=#ffc000><size=12>Melee info for {playername}[{playerid}]: {weapon} ({delay}) - {target}</size></color>" },
  3000.                     { nameof(Key.LogAntiCheatRecoil), "<color=#ffc000><size=12>Recoil [y: {yaw}({count_x})][p: {pitch}({count_y})][a: {swing}] {playername}[{playerid}] - {weapon}</size></color>" },
  3001.                     { nameof(Key.LogAntiCheatServer), "<color=#ffc000><size=12>Server AntiHack violation for {playername}[{playerid}]: {type}({amount}) {position}{colliders}</size></color>" },
  3002.                     { nameof(Key.LogAntiCheatStash), "<color=#ffc000><size=12>Stash discovery info for {playername}[{playerid}]: at {grid}{position} - owner={ownerid}</size></color>" },
  3003.                     { nameof(Key.LogAntiCheatTrajectory), "<color=#ffc000><size=12>Trajectory [t: {trajectory}] {playername}[{playerid}]: {weapon}({distance}m - {reported}m)</size></color>" },
  3004.                     { nameof(Key.LogAntiSpamItemDrop), "<color=#ffc000><size=12>Item drop by player {playername}[{playerid}] - {itemname}[{itemid}]</size></color>" },
  3005.                     { nameof(Key.LogConnect), "<color=#ffc000><size=12>{action} connection for {playername}[{playerid}]: {type}({reason})</size></color>" },
  3006.                     { nameof(Key.LogDiscordConfig), "<color=#ffc0c0><size=12>Discord: WebHook URL is not configured!</size></color>" },
  3007.                     { nameof(Key.LogDiscordMessage), "<color=#ffc0c0><size=12>Discord: Message requires at least one \'content\' or one \'embed\' field!</size></color>" },
  3008.                     { nameof(Key.LogIpFilter), "<color=#ffc000><size=12>IP Filter: {action} connection from {address}</size></color>" },
  3009.                     { nameof(Key.LogSteam), "<color=#ffc0c0><size=12>Steam Check[{api}]: {type} error for {playername}[{playerid}] - {info}</size></color>" },
  3010.                     { nameof(Key.LogSteamConfig), "<color=#ffc0c0><size=12>Steam Check[{api}]: APIKEY missing! Get one from here: {link}</size></color>" },
  3011.                     { nameof(Key.LogUserBypass), "<color=#ffc000><size=12>User Bypass: {playername}[{playerid}](address={address}; banned {bantime}; played {playtime}) - {status}</size></color>" },
  3012.                     { nameof(Key.LogVpnCheck), "<color=#ffc000><size=12>VPN Check[{api}]: {address}({info})</size></color>" },
  3013.                     { nameof(Key.LogVpnCheckConfig), "<color=#ffc0c0><size=12>VPN Check[{api}]: APIKEY missing! Get one from here: {link}</size></color>" },
  3014.                     { nameof(Key.LogVpnCheckError), "<color=#ffc0c0><size=12>VPN Check[{api}]: {type} error for {address} - {info}</size></color>" },
  3015.                     { nameof(Key.LogWebHook), "<color=#ffc0c0><size=12>WebHook({category}): {type} error - {info}</size></color>" },
  3016.                     { nameof(Key.Name), "Name" },
  3017.                     { nameof(Key.never), "never" },
  3018.                     { nameof(Key.NoReasonGiven), "no reason given" },
  3019.                     { nameof(Key.NULL), "null" },
  3020.                     { nameof(Key.offline), "offline" },
  3021.                     { nameof(Key.OnAntiCheatTriggered), "{action} for {category} - {type}" },
  3022.                     { nameof(Key.online), "online" },
  3023.                     { nameof(Key.OxideGroups), "Oxide Groups" },
  3024.                     { nameof(Key.Pardoned), "Pardoned" },
  3025.                     { nameof(Key.permanently), "permanently" },
  3026.                     { nameof(Key.Played), "Played" },
  3027.                     { nameof(Key.remove), "remove" },
  3028.                     { nameof(Key.Removed), "Removed" },
  3029.                     { nameof(Key.Saved), "Saved" },
  3030.                     { nameof(Key.Server), "Server" },
  3031.                     { nameof(Key.SERVER), "SERVER" },
  3032.                     { nameof(Key.Status), "Status" },
  3033.                     { nameof(Key.Steam), "Steam" },
  3034.                     { nameof(Key.SteamBanActive), "VAC Banned" },
  3035.                     { nameof(Key.SteamBanCommunity), "Community Banned" },
  3036.                     { nameof(Key.SteamBanDays), "Days Since Ban" },
  3037.                     { nameof(Key.SteamBanEconomy), "Economy Banned" },
  3038.                     { nameof(Key.SteamBanGame), "Game Bans" },
  3039.                     { nameof(Key.SteamBanVAC), "VAC Bans" },
  3040.                     { nameof(Key.SteamGameCount), "Game Count" },
  3041.                     { nameof(Key.SteamGameHours), "Hours Played" },
  3042.                     { nameof(Key.SteamID), "SteamID" },
  3043.                     { nameof(Key.SteamProfileInvalid), "Invalid Profile" },
  3044.                     { nameof(Key.SteamProfileLimited), "Limited Profile" },
  3045.                     { nameof(Key.SteamProfilePrivate), "Private Profile" },
  3046.                     { nameof(Key.SteamShareFamily), "Family Share" },
  3047.                     { nameof(Key.True), "True" },
  3048.                     { nameof(Key.Unbanned), "Unbanned" },
  3049.                     { nameof(Key.Unblocked), "Unblocked" },
  3050.                     { nameof(Key.Uncrippled), "Uncrippled" },
  3051.                     { nameof(Key.unknown), "unknown" },
  3052.                     { nameof(Key.UnknownKey), "<color=#ffc0c0><size=12>Text error: language={language}]; key={key}</size></color>" },
  3053.                     { nameof(Key.UserAction), "<color=#ffc000><size=12>{actor}: {action} {playername}[{playerid}]</size></color>" },
  3054.                     { nameof(Key.UserBanTeleport), "<color=#c0ffc0><size=12>A banned player was teleported to your location!</size></color>" },
  3055.                     { nameof(Key.UserBanTeleported), "<color=#c0ffc0><size=12>Teleported {playername}[{playerid}] to {victim}.</size></color>" },
  3056.                     { nameof(Key.UserConnectBanInherit), "<color=#ffff00><size=12>You are banned from this server (Inherited: {reason})</size></color>" },
  3057.                     { nameof(Key.UserConnectSteamIdInvalid), "<color=#ffff00><size=12>Connection denied (SteamID: Invalid)</size></color>" },
  3058.                     { nameof(Key.UserConnectIpBlocked), "<color=#ffff00><size=12>Connection denied (IP: Blocked)</size></color>" },
  3059.                     { nameof(Key.UserInfoText), "<color=#c0c0ff><size=12>{info}</size></color>" },
  3060.                     { nameof(Key.UserInfoTextBullet), "<color=#c0c0ff><size=12>\n    - {info}</size></color>" },
  3061.                     { nameof(Key.UserInfoTextLabel), "<color=#c0c0ff><size=12>\n    {label}: {info}</size></color>" },
  3062.                     { nameof(Key.UserPardonProgress), "<size=12><color=#ffc000>Pardoned users: </color><color=#ffffc0>{count}/{total}</color></size>" },
  3063.                     { nameof(Key.Violation), "<color=#ffffc0><size=12>{action} {playername}[{playerid}] for {category}({type}): {details}</size></color>" },
  3064.                     { nameof(Key.ViolationAim), "<color=#ffffc0><size=12>{target}, {weapon}, {bodypart}, {distance}m, {angle}deg</size></color>" },
  3065.                     { nameof(Key.ViolationFireRate), "<color=#ffffc0><size=12>{weapon}, {delay}s</size></color>" },
  3066.                     { nameof(Key.ViolationGravity), "<color=#ffffc0><size=12>{amount}m</size></color>" },
  3067.                     { nameof(Key.ViolationMeleeRate), "<color=#ffffc0><size=12>{weapon}, {delay}s, {target}</size></color>" },
  3068.                     { nameof(Key.ViolationRecoil), "<color=#ffffc0><size=12>{weapon}, yaw={yaw}, pitch={pitch}</size></color>" },
  3069.                     { nameof(Key.ViolationStash), "<color=#ffffc0><size=12>{grid}{position}</size></color>" },
  3070.                     { nameof(Key.ViolationTrajectory), "<color=#ffffc0><size=12>{weapon}, {distance}m, {reported}m</size></color>" },
  3071.                     { nameof(Key.ViolationWallHack), "<color=#ffffc0><size=12>{weapon}, {target}</size></color>" },
  3072.                     { nameof(Key.VPN), "VPN" },
  3073.                     { nameof(Key.VpnCache), "VPN Cache" },
  3074.                     { nameof(Key.VpnDetected), "<color=#ffc000><size=12>VPN: Detected</size></color>" },
  3075.                     { nameof(Key.Warning), "Warning" }
  3076.                 }, _instance, "en");
  3077.             }
  3078.  
  3079.             private static string Replace(string message, Dictionary<string, string> parameters = null)
  3080.             {
  3081.                 if((message != null) && (parameters != null))
  3082.                 {
  3083.                     foreach(var entry in parameters)
  3084.                     {
  3085.                         message = message.Replace('{' + entry.Key + '}', entry.Value);
  3086.                     }
  3087.                 }
  3088.  
  3089.                 return message;
  3090.             }
  3091.  
  3092.             private static string Strip(string message)
  3093.             {
  3094.                 if(string.IsNullOrEmpty(message))
  3095.                 {
  3096.                     return string.Empty;
  3097.                 }
  3098.  
  3099.                 return RegEx.markup.Replace(message, (match) =>
  3100.                 {
  3101.                     var tag = match.Value.ToLower();
  3102.  
  3103.                     switch(tag)
  3104.                     {
  3105.                     case "<b>":
  3106.                     case "<i>":
  3107.                     case "</b>":
  3108.                     case "</color>":
  3109.                     case "</i>":
  3110.                     case "</size>":
  3111.                         return string.Empty;
  3112.                     }
  3113.  
  3114.                     if(tag.StartsWith("<color=") || tag.StartsWith("<size="))
  3115.                     {
  3116.                         return string.Empty;
  3117.                     }
  3118.  
  3119.                     return match.Value;
  3120.                 });
  3121.             }
  3122.  
  3123.             public static string Sanitize(string message)
  3124.             {
  3125.                 if(string.IsNullOrEmpty(message))
  3126.                 {
  3127.                     return string.Empty;
  3128.                 }
  3129.  
  3130.                 message = RegEx.clean1.Replace(message, " ");
  3131.                 message = RegEx.clean2.Replace(message, " ");
  3132.  
  3133.                 return Trim(message);
  3134.             }
  3135.  
  3136.             private static string Trim(string message)
  3137.             {
  3138.                 if(string.IsNullOrEmpty(message))
  3139.                 {
  3140.                     return string.Empty;
  3141.                 }
  3142.  
  3143.                 return RegEx.spaces.Replace(message, " ").Trim();
  3144.             }
  3145.  
  3146.             public static void Unload()
  3147.             {
  3148.                 RegEx.Unload();
  3149.  
  3150.                 foreach(var language in decorated)
  3151.                 {
  3152.                     language.Value.Clear();
  3153.                 }
  3154.  
  3155.                 decorated.Clear();
  3156.  
  3157.                 foreach(var language in unadorned)
  3158.                 {
  3159.                     language.Value.Clear();
  3160.                 }
  3161.  
  3162.                 unadorned.Clear();
  3163.  
  3164.                 server_language = null;
  3165.             }
  3166.         }
  3167.  
  3168.         #endregion _text_
  3169.  
  3170.         #region _timers_
  3171.  
  3172.         private class Timers
  3173.         {
  3174.             private static readonly List<Timer> timers = new List<Timer>();
  3175.  
  3176.             public static void Add(float interval, Action callback)
  3177.             {
  3178.                 timers.Add(_instance.timer.Every(interval, callback));
  3179.             }
  3180.  
  3181.             public static void Destroy()
  3182.             {
  3183.                 foreach(var timer in timers)
  3184.                 {
  3185.                     timer.Destroy();
  3186.                 }
  3187.  
  3188.                 timers.Clear();
  3189.             }
  3190.         }
  3191.  
  3192.         #endregion _timers_
  3193.  
  3194.         #region _user_
  3195.  
  3196.         private class User
  3197.         {
  3198.             public class Settings
  3199.             {
  3200.                 public UserBypass Bypass;
  3201.                 public UserFriend Friend;
  3202.                 public UserTeam   Team;
  3203.  
  3204.                 public Settings()
  3205.                 {
  3206.                     Bypass = new UserBypass();
  3207.                     Friend = new UserFriend();
  3208.                     Team   = new UserTeam();
  3209.                 }
  3210.  
  3211.                 public class UserBypass
  3212.                 {
  3213.                     public ulong DaysSinceBan;
  3214.                     public bool  Enabled;
  3215.                     public ulong HoursPlayed;
  3216.                     public bool  Multiply;
  3217.  
  3218.                     public UserBypass()
  3219.                     {
  3220.                         DaysSinceBan = 10ul;
  3221.                         HoursPlayed  =  6ul;
  3222.                     }
  3223.  
  3224.                     public void Validate()
  3225.                     {
  3226.                         Configuration.Clamp(ref DaysSinceBan, 1ul,  365ul);
  3227.                         Configuration.Clamp(ref HoursPlayed,  1ul, 8760ul);
  3228.                     }
  3229.                 }
  3230.  
  3231.                 public class UserFriend
  3232.                 {
  3233.                     public bool Damage;
  3234.                 }
  3235.  
  3236.                 public class UserTeam
  3237.                 {
  3238.                     public bool Damage;
  3239.                 }
  3240.  
  3241.                 public void Validate()
  3242.                 {
  3243.                     Configuration.Validate(ref Bypass, () => new UserBypass(), () => Bypass.Validate());
  3244.                     Configuration.Validate(ref Friend, () => new UserFriend());
  3245.                     Configuration.Validate(ref Team,   () => new UserTeam());
  3246.                 }
  3247.             }
  3248.  
  3249.             private class Info
  3250.             {
  3251.                 public ulong           userid;
  3252.                 public string          name;
  3253.                 public HashSet<string> names;
  3254.                 public string          address;
  3255.                 public HashSet<string> addresses;
  3256.  
  3257.                 public DateTime time_connected;
  3258.                 public DateTime time_disconnected;
  3259.                 public TimeSpan time_played;
  3260.  
  3261.                 public ulong    ban_count;
  3262.                 public string   ban_reason;
  3263.                 public DateTime ban_time;
  3264.                 public DateTime ban_timer;
  3265.                 public bool     is_banned;
  3266.  
  3267.                 public ulong    cripple_count;
  3268.                 public string   cripple_reason;
  3269.                 public DateTime cripple_time;
  3270.                 public DateTime cripple_timer;
  3271.                 public bool     is_crippled;
  3272.  
  3273.                 public Vector3 l_position;
  3274.                 public Vector3 v_position;
  3275.  
  3276.                 [JsonIgnore] public DateTime     access_time;
  3277.                 [JsonIgnore] public ulong        attacked;
  3278.                 [JsonIgnore] public ulong        attacker;
  3279.                 [JsonIgnore] public bool         dirty;
  3280.                 [JsonIgnore] public BasePlayer   player;
  3281.                 [JsonIgnore] public Stack<ulong> teleport;
  3282.                 [JsonIgnore] public List<ulong>  victims;
  3283.  
  3284.                 public Info()
  3285.                 {
  3286.                     userid = 0;
  3287.  
  3288.                     name      = string.Empty;
  3289.                     names     = new HashSet<string>();
  3290.                     address   = string.Empty;
  3291.                     addresses = new HashSet<string>();
  3292.  
  3293.                     time_connected    = DateTime.MaxValue;
  3294.                     time_disconnected = DateTime.MinValue;
  3295.                     time_played       = TimeSpan.Zero;
  3296.  
  3297.                     ban_count  = 0;
  3298.                     ban_reason = string.Empty;
  3299.                     ban_time   = DateTime.MaxValue;
  3300.                     ban_timer  = DateTime.MinValue;
  3301.                     is_banned  = false;
  3302.  
  3303.                     cripple_count  = 0;
  3304.                     cripple_reason = string.Empty;
  3305.                     cripple_time   = DateTime.MaxValue;
  3306.                     cripple_timer  = DateTime.MinValue;
  3307.                     is_crippled    = false;
  3308.  
  3309.                     l_position = Vector3.zero;
  3310.                     v_position = Vector3.zero;
  3311.  
  3312.                     attacked = 0;
  3313.                     attacker = 0;
  3314.                     victims  = new List<ulong>();
  3315.  
  3316.                     dirty = true;
  3317.                 }
  3318.             }
  3319.  
  3320.             private static readonly DataFile<string, HashSet<ulong>> names = new DataFile<string, HashSet<ulong>>("user_names");
  3321.  
  3322.             private static ActionQueue teleport;
  3323.  
  3324.             private static readonly Dictionary<ulong, Info> users = new Dictionary<ulong, Info>();
  3325.  
  3326.             private static void Action(IPlayer actor, Key action, Info user = null)
  3327.             {
  3328.                 var parameters = new Dictionary<string, string>
  3329.                 {
  3330.                     { "action", Text.GetPlain(action) },
  3331.                     { "actor", Text.Actor(actor) },
  3332.                     { "playerid", user?.userid.ToString() ?? "*" },
  3333.                     { "playername", Text.Sanitize(user?.name ?? Text.GetPlain(Key.AllUsers)) }
  3334.                 };
  3335.  
  3336.                 Log.Console(Key.UserAction, parameters);
  3337.  
  3338.                 if(config.Admin.Broadcast)
  3339.                 {
  3340.                     foreach(var player in BasePlayer.activePlayerList)
  3341.                     {
  3342.                         if(Permissions.Admin(player.userID))
  3343.                         {
  3344.                             parameters["action"]     = Text.GetPlain(action, player);
  3345.                             parameters["actor"]      = Text.Actor(actor, player);
  3346.                             parameters["playername"] = Text.Sanitize(user?.name ?? Text.GetPlain(Key.AllUsers, player));
  3347.  
  3348.                             Chat.Send(player, Key.UserAction, parameters);
  3349.                         }
  3350.                     }
  3351.                 }
  3352.             }
  3353.  
  3354.             public static string Address(ulong userid)
  3355.             {
  3356.                 return Load(userid).address;
  3357.             }
  3358.  
  3359.             public static void AssignAttacker(BasePlayer attacker, BasePlayer victim)
  3360.             {
  3361.                 if((attacker == null) || attacker.IsAdmin || victim.IsAdmin || !victim.IsConnected)
  3362.                 {
  3363.                     return;
  3364.                 }
  3365.  
  3366.                 if((attacker == victim) || (victim.lastDamage == DamageType.Bleeding))
  3367.                 {
  3368.                     return;
  3369.                 }
  3370.  
  3371.                 if(IsTeamMate(attacker, victim) || IsFriend(attacker, victim.userID))
  3372.                 {
  3373.                     return;
  3374.                 }
  3375.  
  3376.                 Load(attacker.userID).attacked = victim.userID;
  3377.  
  3378.                 Load(victim.userID).attacker = attacker.userID;
  3379.             }
  3380.  
  3381.             public static void AssignVictim(BasePlayer victim)
  3382.             {
  3383.                 var user = Load(victim.userID);
  3384.  
  3385.                 if(victim.IsConnected && (user.attacker != 0))
  3386.                 {
  3387.                     var attacker = Load(user.attacker);
  3388.  
  3389.                     attacker.victims.Remove(victim.userID);
  3390.                     attacker.victims.Add(victim.userID);
  3391.                 }
  3392.  
  3393.                 user.attacker = 0;
  3394.             }
  3395.             private static void AssignVictims(Info user)
  3396.             {
  3397.                 user.teleport = new Stack<ulong>();
  3398.  
  3399.                 if((user.attacked != 0) && !user.victims.Contains(user.attacked))
  3400.                 {
  3401.                     user.teleport.Push(user.attacked);
  3402.  
  3403.                     user.attacked = 0;
  3404.                 }
  3405.  
  3406.                 foreach(var victim in user.victims)
  3407.                 {
  3408.                     user.teleport.Push(victim);
  3409.                 }
  3410.  
  3411.                 user.victims.Clear();
  3412.             }
  3413.  
  3414.             public static void Ban(ulong userid, string reason = null, ulong seconds = 0, IPlayer actor = null)
  3415.             {
  3416.                 var user = Load(userid);
  3417.  
  3418.                 if(user.is_banned)
  3419.                 {
  3420.                     if(actor == null)
  3421.                     {
  3422.                         return;
  3423.                     }
  3424.  
  3425.                     user.is_banned = false;
  3426.  
  3427.                     _instance.server.Command("unban", user.userid.ToString());
  3428.                 }
  3429.                 else
  3430.                 {
  3431.                     user.ban_count++;
  3432.                 }
  3433.  
  3434.                 _instance.NextTick(() =>
  3435.                 {
  3436.                     user.ban_reason = string.IsNullOrEmpty(reason) ? Text.GetPlain(Key.NoReasonGiven) : reason;
  3437.                     user.ban_time   = DateTime.UtcNow;
  3438.                     user.ban_timer  = (seconds == 0) ? DateTime.MaxValue : user.ban_time.AddSeconds(seconds);
  3439.                     user.is_banned  = true;
  3440.  
  3441.                     user.dirty = true;
  3442.  
  3443.                     Action(actor, Key.Banned, user);
  3444.  
  3445.                     _instance.server.Command("banid", user.userid.ToString(), user.name, user.ban_reason);
  3446.                 });
  3447.             }
  3448.  
  3449.             public static ulong BanCount(ulong userid)
  3450.             {
  3451.                 return Load(userid).ban_count;
  3452.             }
  3453.  
  3454.             private static void BanInherit(ulong userid, string name, string address, Info copy)
  3455.             {
  3456.                 var user = Load(userid);
  3457.  
  3458.                 Update(user, name, address);
  3459.  
  3460.                 user.ban_count  = copy.ban_count;
  3461.                 user.ban_reason = copy.ban_reason;
  3462.                 user.ban_time   = copy.ban_time;
  3463.                 user.ban_timer  = copy.ban_timer;
  3464.                 user.is_banned  = copy.is_banned;
  3465.  
  3466.                 user.time_connected    = DateTime.UtcNow;
  3467.                 user.time_disconnected = user.time_connected;
  3468.  
  3469.                 user.dirty = true;
  3470.  
  3471.                 Action(null, Key.Banned, user);
  3472.  
  3473.                 _instance.server.Command("banid", user.userid.ToString(), user.name, user.ban_reason);
  3474.             }
  3475.  
  3476.             public static void BanReset(ulong userid, IPlayer actor = null)
  3477.             {
  3478.                 var user = Load(userid);
  3479.  
  3480.                 var was_banned = user.is_banned;
  3481.  
  3482.                 user.ban_count  = 0;
  3483.                 user.ban_reason = string.Empty;
  3484.                 user.ban_time   = DateTime.MaxValue;
  3485.                 user.ban_timer  = DateTime.MinValue;
  3486.                 user.is_banned  = false;
  3487.  
  3488.                 user.dirty = true;
  3489.  
  3490.                 Action(actor, Key.BanReset, user);
  3491.  
  3492.                 if(was_banned)
  3493.                 {
  3494.                     _instance.server.Command("unban", user.userid.ToString());
  3495.                 }
  3496.             }
  3497.  
  3498.             private static void BanTeleport(Info user, ulong count = 0)
  3499.             {
  3500.                 Info victim = null;
  3501.  
  3502.                 while((victim == null) && (user.teleport.Count > 0))
  3503.                 {
  3504.                     victim = Load(user.teleport.Peek());
  3505.  
  3506.                     if(!(victim.player?.IsConnected ?? false))
  3507.                     {
  3508.                         count = 0; victim = null;
  3509.  
  3510.                         user.teleport.Pop();
  3511.                     }
  3512.                 }
  3513.  
  3514.                 if(victim == null)
  3515.                 {
  3516.                     return;
  3517.                 }
  3518.  
  3519.                 user.access_time = DateTime.UtcNow;
  3520.  
  3521.                 if(victim.player.IsSleeping() || victim.player.IsDead() || victim.player.HasParent())
  3522.                 {
  3523.                     if(count < 10)
  3524.                     {
  3525.                         teleport.Enqueue(() => BanTeleport(user, count + 1));
  3526.                     }
  3527.                     else
  3528.                     {
  3529.                         user.teleport.Pop();
  3530.  
  3531.                         if(user.teleport.Count > 0)
  3532.                         {
  3533.                             teleport.Enqueue(() => BanTeleport(user));
  3534.                         }
  3535.                     }
  3536.  
  3537.                     return;
  3538.                 }
  3539.  
  3540.                 var attacker = BasePlayer.FindAwakeOrSleeping(user.userid.ToString());
  3541.  
  3542.                 if(attacker == null)
  3543.                 {
  3544.                     return;
  3545.                 }
  3546.  
  3547.                 var position = attacker.transform.position; position.y += 0.1f;
  3548.  
  3549.                 RaycastHit hit;
  3550.  
  3551.                 if(Physics.Raycast(position, Vector3.down, out hit))
  3552.                 {
  3553.                     position.y -= hit.distance;
  3554.                 }
  3555.  
  3556.                 attacker.MovePosition(position);
  3557.  
  3558.                 // Facepunch removed BasePlayer.ClientRPCPlayer (CS1061). Current RPC path is
  3559.                 // ClientRPC(RpcTarget.Player(rpcName, connectionPlayer), args...). Matches
  3560.                 // vanilla VehicleSpawner.NudgePlayersInRadius ForcePositionTo usage.
  3561.                 // Old: attacker.ClientRPCPlayer(null, attacker, "ForcePositionTo", position);
  3562.                 attacker.ClientRPC(RpcTarget.Player("ForcePositionTo", attacker), position);
  3563.  
  3564.                 attacker.SendNetworkUpdateImmediate();
  3565.  
  3566.                 Chat.Admin(Key.UserBanTeleported, new Dictionary<string, string>
  3567.                 {
  3568.                     { "playerid", user.userid.ToString() },
  3569.                     { "playername", Text.Sanitize(user.name) },
  3570.                     { "victim", Text.Sanitize(victim.name) }
  3571.                 });
  3572.  
  3573.                 Chat.Send(victim.player, Key.UserBanTeleport);
  3574.             }
  3575.  
  3576.             public static DateTime BanTime(ulong userid)
  3577.             {
  3578.                 return Load(userid).ban_time;
  3579.             }
  3580.  
  3581.             private static void BanTimeEnforce(Info user)
  3582.             {
  3583.                 if(!config.Ban.Time.Enforce)
  3584.                 {
  3585.                     return;
  3586.                 }
  3587.  
  3588.                 var seconds = 0ul;
  3589.  
  3590.                 if(config.Ban.Time.Seconds > 0)
  3591.                 {
  3592.                     seconds = config.Ban.Time.Seconds;
  3593.  
  3594.                     if(config.Ban.Time.Multiply)
  3595.                     {
  3596.                         seconds *= user.ban_count;
  3597.                     }
  3598.                 }
  3599.  
  3600.                 if(seconds == 0)
  3601.                 {
  3602.                     user.ban_timer = DateTime.MaxValue;
  3603.                 }
  3604.                 else
  3605.                 {
  3606.                     user.ban_timer = user.ban_time.AddSeconds(seconds);
  3607.                 }
  3608.  
  3609.                 user.dirty = true;
  3610.             }
  3611.  
  3612.             private static bool CanBypass(Info user)
  3613.             {
  3614.                 if(Permissions.Ignore(user.userid) || (config.Admin.Bypass && Permissions.Admin(user.userid)))
  3615.                 {
  3616.                     return true;
  3617.                 }
  3618.                 else if(!config.User.Bypass.Enabled)
  3619.                 {
  3620.                     return false;
  3621.                 }
  3622.  
  3623.                 bool never_banned = true;
  3624.  
  3625.                 var days_since_banned = 0.0;
  3626.  
  3627.                 var date_current = DateTime.UtcNow;
  3628.  
  3629.                 var time_banned = TimeSpan.MaxValue;
  3630.  
  3631.                 if(user.ban_time < date_current)
  3632.                 {
  3633.                     time_banned = date_current.Subtract(user.ban_time);
  3634.  
  3635.                     days_since_banned = time_banned.TotalDays;
  3636.  
  3637.                     never_banned = false;
  3638.                 }
  3639.  
  3640.                 var time_played = TimePlayed(user);
  3641.  
  3642.                 var hours_played = time_played.TotalHours;
  3643.  
  3644.                 bool bypass = false;
  3645.  
  3646.                 var days_banned_minimum  = config.User.Bypass.DaysSinceBan;
  3647.                 var hours_played_minimum = config.User.Bypass.HoursPlayed;
  3648.  
  3649.                 if(config.User.Bypass.Multiply)
  3650.                 {
  3651.                     var multiplier = user.ban_count + 1;
  3652.  
  3653.                     days_banned_minimum  *= multiplier;
  3654.                     hours_played_minimum *= multiplier;
  3655.                 }
  3656.  
  3657.                 if(hours_played >= hours_played_minimum)
  3658.                 {
  3659.                     bypass = never_banned || (days_since_banned >= days_banned_minimum);
  3660.                 }
  3661.  
  3662.                 if(config.Log.User.Bypass)
  3663.                 {
  3664.                     Log.Console(Key.LogUserBypass, new Dictionary<string, string>
  3665.                     {
  3666.                         { "address", user.address },
  3667.                         { "bantime", never_banned ? Text.Get(Key.never) : $"{Text.Duration.Short(time_banned)} {Text.Get(Key.ago)}" },
  3668.                         { "playerid", user.userid.ToString() },
  3669.                         { "playername", Text.Sanitize(user.name) },
  3670.                         { "playtime", Text.Duration.Hours(time_played) },
  3671.                         { "status", bypass ? Text.Get(Key.Allowed) : Text.Get(Key.Denied) }
  3672.                     });
  3673.                 }
  3674.  
  3675.                 return bypass;
  3676.             }
  3677.  
  3678.             public static object CanConnect(string name, string id, string address)
  3679.             {
  3680.                 if(IP.Cooldown(address))
  3681.                 {
  3682.                     return Text.GetPlain(Key.IpCooldown);
  3683.                 }
  3684.  
  3685.                 ulong userid;
  3686.  
  3687.                 if(!id.StartsWith("7656119") || !ulong.TryParse(id, out userid))
  3688.                 {
  3689.                     if(config.Log.User.Connect)
  3690.                     {
  3691.                         Log.Console(Key.LogConnect, new Dictionary<string, string>
  3692.                         {
  3693.                             { "action", Text.GetPlain(Key.Denied) },
  3694.                             { "playerid", id },
  3695.                             { "playername", Text.Sanitize(name) },
  3696.                             { "reason", address },
  3697.                             { "type", Text.GetPlain(Key.InvalidSteamId) }
  3698.                         });
  3699.                     }
  3700.  
  3701.                     return Text.GetPlain(Key.UserConnectSteamIdInvalid, Text.Language(id));
  3702.                 }
  3703.  
  3704.                 if(Permissions.Ignore(userid, true))
  3705.                 {
  3706.                     return null;
  3707.                 }
  3708.  
  3709.                 else if(Permissions.Admin(userid, true) && config.Admin.Bypass)
  3710.                 {
  3711.                     return null;
  3712.                 }
  3713.  
  3714.                 if(!IP.Filter(address, userid))
  3715.                 {
  3716.                     if(config.Log.User.Connect)
  3717.                     {
  3718.                         Log.Console(Key.LogConnect, new Dictionary<string, string>
  3719.                         {
  3720.                             { "action", Text.GetPlain(Key.Denied) },
  3721.                             { "playerid", id },
  3722.                             { "playername", Text.Sanitize(name) },
  3723.                             { "reason", address },
  3724.                             { "type", Text.GetPlain(Key.Blocked) }
  3725.                         });
  3726.                     }
  3727.  
  3728.                     return Text.GetPlain(Key.UserConnectIpBlocked, userid);
  3729.                 }
  3730.  
  3731.                 var cripple_inherit = config.Cripple.Inherit && !IsCrippled(userid);
  3732.  
  3733.                 if(config.Ban.Inherit || cripple_inherit)
  3734.                 {
  3735.                     var userids = IP.Find(address);
  3736.  
  3737.                     if(cripple_inherit)
  3738.                     {
  3739.                         foreach(var entry in userids)
  3740.                         {
  3741.                             var user = Load(entry);
  3742.  
  3743.                             if(user.is_crippled)
  3744.                             {
  3745.                                 _instance.NextTick(() => CrippleInherit(userid, name, address, user));
  3746.  
  3747.                                 break;
  3748.                             }
  3749.                         }
  3750.                     }
  3751.  
  3752.                     if(config.Ban.Inherit)
  3753.                     {
  3754.                         foreach(var entry in userids)
  3755.                         {
  3756.                             var user = Load(entry);
  3757.  
  3758.                             if(user.is_banned)
  3759.                             {
  3760.                                 if(config.Log.User.Connect)
  3761.                                 {
  3762.                                     Log.Console(Key.LogConnect, new Dictionary<string, string>
  3763.                                     {
  3764.                                         { "action", Text.GetPlain(Key.Denied) },
  3765.                                         { "playerid", id },
  3766.                                         { "playername", Text.Sanitize(name) },
  3767.                                         { "reason", user.ban_reason },
  3768.                                         { "type", Text.GetPlain(Key.BanInherited) }
  3769.                                     });
  3770.                                 }
  3771.  
  3772.                                 _instance.NextTick(() => BanInherit(userid, name, address, user));
  3773.  
  3774.                                 return Text.GetPlain(Key.UserConnectBanInherit, userid, new Dictionary<string, string>
  3775.                                 {
  3776.                                     { "reason", user.ban_reason }
  3777.                                 });
  3778.                             }
  3779.                         }
  3780.                     }
  3781.                 }
  3782.  
  3783.                 return null;
  3784.             }
  3785.  
  3786.             public static bool CanFly(BasePlayer player) =>
  3787.                 HasAuthLevel(player) || HasAdminFlag(player) || HasDeveloperFlag(player);
  3788.  
  3789.             private static bool CanLoot(ulong userid, ulong targetid)
  3790.             {
  3791.                 if(userid == targetid)
  3792.                 {
  3793.                     return true;
  3794.                 }
  3795.  
  3796.                 if(IsCrippled(userid))
  3797.                 {
  3798.                     if(Permissions.Ignore(userid) || Permissions.Admin(userid))
  3799.                     {
  3800.                         return true;
  3801.                     }
  3802.  
  3803.                     return false;
  3804.                 }
  3805.  
  3806.                 return true;
  3807.             }
  3808.             public static object CanLoot(BasePlayer player, ulong targetid)
  3809.             {
  3810.                 if(ShouldIgnore(player) || CanLoot(player.userID, targetid))
  3811.                 {
  3812.                     return null;
  3813.                 }
  3814.  
  3815.                 return false;
  3816.             }
  3817.  
  3818.             public static void Cripple(ulong userid, string reason = null, ulong seconds = 0, IPlayer actor = null)
  3819.             {
  3820.                 var user = Load(userid);
  3821.  
  3822.                 if(!user.is_crippled)
  3823.                 {
  3824.                     user.cripple_count++;
  3825.                 }
  3826.  
  3827.                 user.cripple_reason = string.IsNullOrEmpty(reason) ? Text.GetPlain(Key.NoReasonGiven) : reason;
  3828.                 user.cripple_time   = DateTime.UtcNow;
  3829.                 user.cripple_timer  = (seconds == 0) ? DateTime.MaxValue : user.cripple_time.AddSeconds(seconds);
  3830.  
  3831.                 user.is_crippled = true;
  3832.  
  3833.                 user.dirty = true;
  3834.  
  3835.                 Action(actor, Key.Crippled, user);
  3836.             }
  3837.  
  3838.             private static void CrippleInherit(ulong userid, string name, string address, Info copy)
  3839.             {
  3840.                 var user = Load(userid);
  3841.  
  3842.                 Update(user, name, address);
  3843.  
  3844.                 user.cripple_count  = copy.cripple_count;
  3845.                 user.cripple_reason = copy.cripple_reason;
  3846.                 user.cripple_time   = copy.cripple_time;
  3847.                 user.cripple_timer  = copy.cripple_timer;
  3848.                 user.is_crippled    = copy.is_crippled;
  3849.  
  3850.                 user.dirty = true;
  3851.  
  3852.                 Action(null, Key.Crippled, user);
  3853.             }
  3854.  
  3855.             public static void CrippleReset(ulong userid, IPlayer actor = null)
  3856.             {
  3857.                 var user = Load(userid);
  3858.  
  3859.                 user.cripple_count  = 0;
  3860.                 user.cripple_reason = string.Empty;
  3861.                 user.cripple_time   = DateTime.MaxValue;
  3862.                 user.cripple_timer  = DateTime.MinValue;
  3863.                 user.is_crippled    = false;
  3864.  
  3865.                 user.dirty = true;
  3866.  
  3867.                 Action(actor, Key.CrippleReset, user);
  3868.             }
  3869.  
  3870.             public static bool Exists(ulong userid) =>
  3871.                 users.ContainsKey(userid) || Data.Exists($"Users/{userid}");
  3872.  
  3873.             public static HashSet<ulong> Find(string text)
  3874.             {
  3875.                 var found = new HashSet<ulong>();
  3876.  
  3877.                 if(IP.IsValid(text))
  3878.                 {
  3879.                     foreach(var userid in IP.Find(text))
  3880.                     {
  3881.                         found.Add(userid);
  3882.                     }
  3883.                 }
  3884.                 else if(text.IsSteamId())
  3885.                 {
  3886.                     ulong userid;
  3887.  
  3888.                     if(ulong.TryParse(text, out userid) && Exists(userid))
  3889.                     {
  3890.                         found.Add(userid);
  3891.                     }
  3892.                 }
  3893.                 else
  3894.                 {
  3895.                     var key = text.Sanitize();
  3896.  
  3897.                     if(names.Contains(key))
  3898.                     {
  3899.                         foreach(var userid in names[key])
  3900.                         {
  3901.                             found.Add(userid);
  3902.                         }
  3903.                     }
  3904.                     else
  3905.                     {
  3906.                         var search = key.ToLower();
  3907.  
  3908.                         names.ForEach((name, userids) =>
  3909.                         {
  3910.                             if(name.ToLower().Contains(search))
  3911.                             {
  3912.                                 foreach(var userid in userids)
  3913.                                 {
  3914.                                     found.Add(userid);
  3915.                                 }
  3916.                             }
  3917.                         });
  3918.                     }
  3919.                 }
  3920.  
  3921.                 return found;
  3922.             }
  3923.  
  3924.             public static Vector3 GetLastSeenPosition(ulong userid)
  3925.             {
  3926.                 var player = BasePlayer.FindAwakeOrSleeping(userid.ToString());
  3927.  
  3928.                 if(player != null)
  3929.                 {
  3930.                     return player.transform.position;
  3931.                 }
  3932.  
  3933.                 return Load(userid).l_position;
  3934.             }
  3935.  
  3936.             public static Vector3 GetViolationPosition(ulong userid) =>
  3937.                 Load(userid).v_position;
  3938.  
  3939.             public static bool HasAuthLevel(BasePlayer player) =>
  3940.                 (player?.net?.connection?.authLevel ?? 0) > 0;
  3941.  
  3942.             public static bool HasAdminFlag(BasePlayer player) =>
  3943.                 player.HasPlayerFlag(BasePlayer.PlayerFlags.IsAdmin);
  3944.  
  3945.             private static bool HasConnected(Info user) =>
  3946.                 user.time_connected != DateTime.MaxValue;
  3947.  
  3948.             public static bool HasDeveloperFlag(BasePlayer player) =>
  3949.                 player.HasPlayerFlag(BasePlayer.PlayerFlags.IsDeveloper);
  3950.  
  3951.             public static bool HasParent<T>(BasePlayer player) =>
  3952.                 player.GetParentEntity() is T;
  3953.  
  3954.             public static string InfoText(ulong userid) =>
  3955.                 InfoText(userid, Text.Language());
  3956.             public static string InfoText(ulong userid, BasePlayer player) =>
  3957.                 InfoText(userid, Text.Language(player.UserIDString));
  3958.             public static string InfoText(ulong userid, IPlayer iplayer) =>
  3959.                 InfoText(userid, Text.Language(iplayer.IsServer ? null : iplayer.Id));
  3960.             public static string InfoText(ulong userid, ulong playerid) =>
  3961.                 InfoText(userid, Text.Language(playerid.ToString()));
  3962.             public static string InfoText(ulong userid, string language)
  3963.             {
  3964.                 if(!Exists(userid))
  3965.                 {
  3966.                     return Text.GetPlain(Key.unknown, language);
  3967.                 }
  3968.  
  3969.                 var user = Load(userid);
  3970.  
  3971.                 var info = new StringBuilder();
  3972.  
  3973.                 var parameters = new Dictionary<string, string>();
  3974.  
  3975.                 parameters["label"] = Text.GetPlain(Key.SteamID, language);
  3976.                 parameters["info"] = user.userid.ToString();
  3977.                 info.Append(Text.GetPlain(Key.UserInfoTextLabel, language, parameters));
  3978.  
  3979.                 parameters["label"] = Text.GetPlain(Key.Name, language);
  3980.                 parameters["info"] = Text.Sanitize(user.name);
  3981.                 info.Append(Text.GetPlain(Key.UserInfoTextLabel, language, parameters));
  3982.                 if(user.names.Count > 1)
  3983.                 {
  3984.                     foreach(var name in user.names)
  3985.                     {
  3986.                         if(name != user.name)
  3987.                         {
  3988.                             parameters["info"] = Text.Sanitize(name);
  3989.                             info.Append(Text.GetPlain(Key.UserInfoTextBullet, language, parameters));
  3990.                         }
  3991.                     }
  3992.                 }
  3993.  
  3994.                 parameters["label"] = Text.GetPlain(Key.Played, language);
  3995.                 parameters["info"] = Text.Duration.Hours(TimePlayed(user), language);
  3996.                 info.Append(Text.GetPlain(Key.UserInfoTextLabel, language, parameters));
  3997.  
  3998.                 parameters["label"] = Text.GetPlain(Key.Status, language);
  3999.                 parameters["info"] = StatusText(user, language);
  4000.                 info.Append(Text.GetPlain(Key.UserInfoTextLabel, language, parameters));
  4001.  
  4002.                 if(user.ban_count > 0)
  4003.                 {
  4004.                     parameters["label"] = Text.GetPlain(Key.BanCount, language);
  4005.                     parameters["info"] = user.ban_count.ToString();
  4006.                     info.Append(Text.GetPlain(Key.UserInfoTextLabel, language, parameters));
  4007.  
  4008.                     if(user.is_banned)
  4009.                     {
  4010.                         parameters["label"] = Text.GetPlain(Key.BanReason, language);
  4011.                         parameters["info"] = user.ban_reason;
  4012.                         info.Append(Text.GetPlain(Key.UserInfoTextLabel, language, parameters));
  4013.                     }
  4014.                 }
  4015.  
  4016.                 if(user.cripple_count > 0)
  4017.                 {
  4018.                     parameters["label"] = Text.GetPlain(Key.CrippleCount, language);
  4019.                     parameters["info"] = user.cripple_count.ToString();
  4020.                     info.Append(Text.GetPlain(Key.UserInfoTextLabel, language, parameters));
  4021.  
  4022.                     if(user.is_crippled)
  4023.                     {
  4024.                         parameters["label"] = Text.GetPlain(Key.CrippleReason, language);
  4025.                         parameters["info"] = user.cripple_reason;
  4026.                         info.Append(Text.GetPlain(Key.UserInfoTextLabel, language, parameters));
  4027.                     }
  4028.                 }
  4029.  
  4030.                 parameters["label"] = Text.GetPlain(Key.Address, language);
  4031.                 parameters["info"] = user.address;
  4032.                 info.Append(Text.GetPlain(Key.UserInfoTextLabel, language, parameters));
  4033.                 if(user.addresses.Count > 1)
  4034.                 {
  4035.                     foreach(var address in user.addresses)
  4036.                     {
  4037.                         if(address != user.address)
  4038.                         {
  4039.                             parameters["info"] = address;
  4040.                             info.Append(Text.GetPlain(Key.UserInfoTextBullet, language, parameters));
  4041.                         }
  4042.                     }
  4043.                 }
  4044.  
  4045.                 parameters["label"] = Text.GetPlain(Key.OxideGroups, language);
  4046.                 parameters["info"] = string.Join(", ", Permissions.Groups(user.userid));
  4047.                 info.Append(Text.GetPlain(Key.UserInfoTextLabel, language, parameters));
  4048.  
  4049.                 parameters["info"] = info.ToString();
  4050.                 return Text.GetPlain(Key.UserInfoText, language, parameters);
  4051.             }
  4052.  
  4053.             public static bool IsBanned(ulong userid) =>
  4054.                 Load(userid).is_banned;
  4055.  
  4056.             private static bool IsConnected(Info user) =>
  4057.                 user.player?.IsConnected ?? false;
  4058.             public static bool IsConnected(ulong userid)
  4059.             {
  4060.                 Info user;
  4061.  
  4062.                 if(users.TryGetValue(userid, out user))
  4063.                 {
  4064.                     return IsConnected(user);
  4065.                 }
  4066.  
  4067.                 return false;
  4068.             }
  4069.  
  4070.             public static bool IsCrippled(ulong userid) =>
  4071.                 Load(userid).is_crippled;
  4072.  
  4073.             public static bool IsFriend(BasePlayer a, BasePlayer b) =>
  4074.                 IsFriend(a, b.userID);
  4075.             public static bool IsFriend(BasePlayer player, ulong userid) =>
  4076.                 (_instance?.Friends?.Call<bool>("IsFriend", player.userID, userid) ?? false);
  4077.  
  4078.             public static bool IsInactive(BasePlayer player) =>
  4079.                 player.IsDead() || player.IsSleeping() || !player.IsConnected;
  4080.  
  4081.             private static bool IsStale(Info user) =>
  4082.                 !(HasConnected(user) && (user.time_connected > SaveRestore.SaveCreatedTime.ToUniversalTime()));
  4083.  
  4084.             public static bool IsTeamMate(BasePlayer a, BasePlayer b)
  4085.                 => (a.currentTeam != 0) && (a.currentTeam == b.currentTeam);
  4086.             public static bool IsTeamMate(BasePlayer player, ulong userid)
  4087.             {
  4088.                 var team = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
  4089.  
  4090.                 return team?.members?.Contains(userid) ?? false;
  4091.             }
  4092.  
  4093.             public static void Kick(ulong userid, string reason = null, IPlayer actor = null)
  4094.             {
  4095.                 var user = Load(userid);
  4096.  
  4097.                 if(IsConnected(user))
  4098.                 {
  4099.                     reason = string.IsNullOrEmpty(reason) ? Text.GetPlain(Key.NoReasonGiven) : reason;
  4100.  
  4101.                     if(actor != null)
  4102.                     {
  4103.                         Action(actor, Key.Kicked, user);
  4104.                     }
  4105.  
  4106.                     _instance.server.Command("kick", user.userid.ToString(), reason);
  4107.                 }
  4108.             }
  4109.  
  4110.             public static void Load()
  4111.             {
  4112.                 names.Load();
  4113.  
  4114.                 teleport = new ActionQueue(6.0f);
  4115.  
  4116.                 Timers.Add(6.0f, () => Scan());
  4117.             }
  4118.             private static Info Load(ulong userid)
  4119.             {
  4120.                 Info user;
  4121.  
  4122.                 if(!userid.IsSteamId())
  4123.                 {
  4124.                     throw new ArgumentException("Parameter must be a valid Steam ID", nameof(userid));
  4125.                 }
  4126.                 else if(users.ContainsKey(userid))
  4127.                 {
  4128.                     user = users[userid];
  4129.                 }
  4130.                 else
  4131.                 {
  4132.                     try
  4133.                     {
  4134.                         user = Data.ReadObject<Info>($"Users/{userid}");
  4135.  
  4136.                         if((user?.userid ?? 0) == 0)
  4137.                         {
  4138.                             throw new Exception();
  4139.                         }
  4140.                     }
  4141.                     catch
  4142.                     {
  4143.                         user = new Info
  4144.                         {
  4145.                             dirty  = true,
  4146.                             userid = userid
  4147.                         };
  4148.                     }
  4149.  
  4150.                     if(user.name == null)
  4151.                     {
  4152.                         user.dirty = true; user.name = string.Empty;
  4153.                     }
  4154.  
  4155.                     if(user.names == null)
  4156.                     {
  4157.                         user.dirty = true; user.names = new HashSet<string>();
  4158.                     }
  4159.  
  4160.                     if(user.address == null)
  4161.                     {
  4162.                         user.dirty = true; user.address = string.Empty;
  4163.                     }
  4164.  
  4165.                     if(user.addresses == null)
  4166.                     {
  4167.                         user.dirty = true; user.addresses = new HashSet<string>();
  4168.                     }
  4169.  
  4170.                     if(user.ban_reason == null)
  4171.                     {
  4172.                         user.dirty = true; user.ban_reason = string.Empty;
  4173.                     }
  4174.  
  4175.                     if(user.cripple_reason == null)
  4176.                     {
  4177.                         user.dirty = true; user.cripple_reason = string.Empty;
  4178.                     }
  4179.  
  4180.                     if(IsStale(user))
  4181.                     {
  4182.                         SetLastSeenPosition(user, Vector3.zero);
  4183.                         SetViolationPosition(user, Vector3.zero);
  4184.                     }
  4185.  
  4186.                     users.Add(userid, user);
  4187.  
  4188.                     Save(user);
  4189.                 }
  4190.  
  4191.                 user.access_time = DateTime.UtcNow;
  4192.  
  4193.                 return user;
  4194.             }
  4195.  
  4196.             public static string Name(ulong userid) =>
  4197.                 Load(userid).name;
  4198.  
  4199.             public static void OnBanned(ulong userid, bool banned)
  4200.             {
  4201.                 var user = Load(userid);
  4202.  
  4203.                 if(banned)
  4204.                 {
  4205.                     if(!user.is_banned)
  4206.                     {
  4207.                         var serveruser = ServerUsers.Get(userid);
  4208.  
  4209.                         user.ban_count++;
  4210.                         user.ban_reason = string.IsNullOrEmpty(serveruser.notes) ? Text.GetPlain(Key.NoReasonGiven) : serveruser.notes;
  4211.                         user.ban_time = DateTime.UtcNow;
  4212.                         user.is_banned = true;
  4213.  
  4214.                         if(serveruser.expiry < 0)
  4215.                         {
  4216.                             user.ban_timer = DateTime.MaxValue;
  4217.                         }
  4218.                         else
  4219.                         {
  4220.                             user.ban_timer = user.ban_time.AddSeconds(serveruser.expiry - Epoch.Current);
  4221.                         }
  4222.  
  4223.                         user.dirty = true;
  4224.                     }
  4225.  
  4226.                     BanTimeEnforce(user);
  4227.  
  4228.                     if(config.Ban.Teleport)
  4229.                     {
  4230.                         AssignVictims(user);
  4231.  
  4232.                         if(user.teleport.Count > 0)
  4233.                         {
  4234.                             _instance.timer.In(6.0f, () => BanTeleport(user));
  4235.                         }
  4236.                     }
  4237.                 }
  4238.                 else
  4239.                 {
  4240.                     if(user.is_banned && config.Ban.Time.Enforce)
  4241.                     {
  4242.                         if((user.ban_timer != DateTime.MaxValue) && (user.ban_timer > DateTime.UtcNow))
  4243.                         {
  4244.                             _instance.server.Command("banid", user.userid.ToString(), user.name, user.ban_reason);
  4245.  
  4246.                             return;
  4247.                         }
  4248.                     }
  4249.  
  4250.                     if(user.ban_count == 0)
  4251.                     {
  4252.                         user.ban_time = DateTime.MaxValue;
  4253.                     }
  4254.  
  4255.                     user.ban_reason = string.Empty;
  4256.                     user.ban_timer  = DateTime.MinValue;
  4257.                     user.is_banned  = false;
  4258.  
  4259.                     user.dirty = true;
  4260.                 }
  4261.             }
  4262.  
  4263.             public static void OnConnected(BasePlayer player)
  4264.             {
  4265.                 var user = Load(player.userID);
  4266.  
  4267.                 var connected = TimeSpan.FromSeconds(user.player?.net?.connection?.GetSecondsConnected() ?? 0.0f);
  4268.  
  4269.                 user.time_connected = DateTime.UtcNow.Subtract(connected);
  4270.  
  4271.                 if(user.is_banned)
  4272.                 {
  4273.                     user.is_banned = false;
  4274.  
  4275.                     user.ban_reason = string.Empty;
  4276.                     user.ban_timer  = DateTime.MinValue;
  4277.                 }
  4278.  
  4279.                 if(_instance.PlaytimeTracker)
  4280.                 {
  4281.                     var result = _instance.PlaytimeTracker.Call("GetPlayTime", player.UserIDString);
  4282.  
  4283.                     if(result != null)
  4284.                     {
  4285.                         var time_played = TimeSpan.FromSeconds(Convert.ToDouble(result));
  4286.  
  4287.                         if(time_played > user.time_played)
  4288.                         {
  4289.                             user.time_played = time_played;
  4290.                         }
  4291.                     }
  4292.                 }
  4293.  
  4294.                 user.player = player;
  4295.  
  4296.                 Update(user, player.displayName, IP.Parse(player?.Connection?.ipaddress));
  4297.  
  4298.                 user.dirty = true;
  4299.  
  4300.                 Permissions.Update(user.userid);
  4301.  
  4302.                 if(!CanBypass(user))
  4303.                 {
  4304.                     Steam.Check(user.userid);
  4305.  
  4306.                     VPN.Check(user.address, user.userid);
  4307.                 }
  4308.             }
  4309.  
  4310.             public static void OnDisconnected(BasePlayer player)
  4311.             {
  4312.                 var user = Load(player.userID);
  4313.  
  4314.                 user.time_disconnected = DateTime.UtcNow;
  4315.  
  4316.                 if(user.time_disconnected > user.time_connected)
  4317.                 {
  4318.                     user.time_played += user.time_disconnected.Subtract(user.time_connected);
  4319.                 }
  4320.  
  4321.                 user.player = null;
  4322.  
  4323.                 SetLastSeenPosition(user, player.transform.position);
  4324.  
  4325.                 user.dirty = true;
  4326.  
  4327.                 Permissions.Reset(user.userid);
  4328.             }
  4329.  
  4330.             public static void OnLoot(BasePlayer player, ulong targetid)
  4331.             {
  4332.                 if(ShouldIgnore(player))
  4333.                 {
  4334.                     return;
  4335.                 }
  4336.  
  4337.                 if(!CanLoot(player.userID, targetid))
  4338.                 {
  4339.                     _instance.NextFrame(() => player.EndLooting());
  4340.                 }
  4341.             }
  4342.  
  4343.             public static void Pardon(ulong userid, IPlayer actor = null) =>
  4344.                 Pardon(Load(userid), actor);
  4345.             public static void Pardon(IPlayer actor)
  4346.             {
  4347.                 var found = new HashSet<ulong>();
  4348.  
  4349.                 names.ForEach((name, userids) =>
  4350.                 {
  4351.                     found.UnionWith(userids);
  4352.                 });
  4353.  
  4354.                 var userid = new Queue<ulong>(found);
  4355.  
  4356.                 Pardon(userid, actor);
  4357.             }
  4358.             private static void Pardon(Queue<ulong> userid, IPlayer actor, int total = 0)
  4359.             {
  4360.                 if(total == 0)
  4361.                 {
  4362.                     total = userid.Count;
  4363.                 }
  4364.  
  4365.                 if(userid.Count > 0)
  4366.                 {
  4367.                     var user = Load(userid.Dequeue());
  4368.  
  4369.                     Pardon(user, actor, false);
  4370.  
  4371.                     Save(user);
  4372.  
  4373.                     var count = total - userid.Count;
  4374.  
  4375.                     if((count % ((total <= 100) ? 10 : 100)) == 0)
  4376.                     {
  4377.                         Chat.Reply(actor, Key.UserPardonProgress, new Dictionary<string, string>
  4378.                         {
  4379.                             { "count", count.ToString() },
  4380.                             { "total", total.ToString() }
  4381.                         });
  4382.                     }
  4383.  
  4384.                     _instance.NextTick(() => Pardon(userid, actor, total));
  4385.                 }
  4386.                 else
  4387.                 {
  4388.                     if(total > 0)
  4389.                     {
  4390.                         Chat.Reply(actor, Key.UserPardonProgress, new Dictionary<string, string>
  4391.                         {
  4392.                             { "count", total.ToString() },
  4393.                             { "total", total.ToString() }
  4394.                         });
  4395.                     }
  4396.  
  4397.                     Action(actor, Key.Pardoned);
  4398.                 }
  4399.             }
  4400.             private static void Pardon(Info user, IPlayer actor, bool broadcast = true)
  4401.             {
  4402.                 if(user.is_banned)
  4403.                 {
  4404.                     if(user.ban_count > 1)
  4405.                     {
  4406.                         user.dirty = true; user.ban_count = 1;
  4407.  
  4408.                         BanTimeEnforce(user);
  4409.                     }
  4410.                 }
  4411.                 else
  4412.                 {
  4413.                     if(user.ban_count != 0)
  4414.                     {
  4415.                         user.dirty = true; user.ban_count = 0;
  4416.                     }
  4417.  
  4418.                     if(user.ban_time != DateTime.MaxValue)
  4419.                     {
  4420.                         user.dirty = true; user.ban_time = DateTime.MaxValue;
  4421.                     }
  4422.  
  4423.                     if(user.ban_timer != DateTime.MinValue)
  4424.                     {
  4425.                         user.dirty = true; user.ban_timer = DateTime.MinValue;
  4426.                     }
  4427.                 }
  4428.  
  4429.                 if(user.is_crippled)
  4430.                 {
  4431.                     if(user.cripple_count > 1)
  4432.                     {
  4433.                         user.dirty = true; user.cripple_count = 1;
  4434.                     }
  4435.                 }
  4436.                 else
  4437.                 {
  4438.                     if(user.cripple_count != 0)
  4439.                     {
  4440.                         user.dirty = true; user.cripple_count = 0;
  4441.                     }
  4442.  
  4443.                     if(user.cripple_time != DateTime.MaxValue)
  4444.                     {
  4445.                         user.dirty = true; user.cripple_time = DateTime.MaxValue;
  4446.                     }
  4447.  
  4448.                     if(user.cripple_timer != DateTime.MinValue)
  4449.                     {
  4450.                         user.dirty = true; user.cripple_timer = DateTime.MinValue;
  4451.                     }
  4452.                 }
  4453.  
  4454.                 if(broadcast)
  4455.                 {
  4456.                     Action(actor, Key.Pardoned, user);
  4457.                 }
  4458.             }
  4459.  
  4460.             public static void Save()
  4461.             {
  4462.                 var current = DateTime.UtcNow;
  4463.  
  4464.                 var expired = TimeSpan.FromMinutes(30.0);
  4465.  
  4466.                 var remove = new List<ulong>();
  4467.  
  4468.                 foreach(var entry in users)
  4469.                 {
  4470.                     Save(entry.Value);
  4471.  
  4472.                     if(current.Subtract(entry.Value.access_time) >= expired)
  4473.                     {
  4474.                         remove.Add(entry.Key);
  4475.                     }
  4476.                 }
  4477.  
  4478.                 foreach(var entry in remove)
  4479.                 {
  4480.                     users.Remove(entry);
  4481.                 }
  4482.  
  4483.                 names.Save();
  4484.             }
  4485.             private static void Save(Info user)
  4486.             {
  4487.                 if(user.dirty)
  4488.                 {
  4489.                     user.dirty = false;
  4490.  
  4491.                     Data.WriteObject($"Users/{user.userid}", user);
  4492.                 }
  4493.             }
  4494.  
  4495.             private static void Scan()
  4496.             {
  4497.                 var current = DateTime.UtcNow;
  4498.  
  4499.                 foreach(var entry in users)
  4500.                 {
  4501.                     var user = entry.Value;
  4502.  
  4503.                     if(user.is_banned && (user.ban_timer != DateTime.MaxValue))
  4504.                     {
  4505.                         if(user.ban_timer > current)
  4506.                         {
  4507.                             user.access_time = current;
  4508.                         }
  4509.                         else
  4510.                         {
  4511.                             Unban(user.userid);
  4512.                         }
  4513.                     }
  4514.                     else if(user.is_crippled && (user.cripple_timer != DateTime.MaxValue))
  4515.                     {
  4516.                         if(user.cripple_timer > current)
  4517.                         {
  4518.                             user.access_time = current;
  4519.                         }
  4520.                         else
  4521.                         {
  4522.                             Uncripple(user.userid);
  4523.                         }
  4524.                     }
  4525.                 }
  4526.             }
  4527.  
  4528.             private static void SetLastSeenPosition(Info user, Vector3 position)
  4529.             {
  4530.                 if(user.l_position != position)
  4531.                 {
  4532.                     user.l_position = position;
  4533.  
  4534.                     user.dirty = true;
  4535.                 }
  4536.             }
  4537.  
  4538.             private static void SetViolationPosition(Info user, Vector3 position)
  4539.             {
  4540.                 if(user.v_position != position)
  4541.                 {
  4542.                     user.v_position = position;
  4543.  
  4544.                     user.dirty = true;
  4545.                 }
  4546.             }
  4547.             public static void SetViolationPosition(ulong userid)
  4548.             {
  4549.                 Info user;
  4550.  
  4551.                 if(users.TryGetValue(userid, out user))
  4552.                 {
  4553.                     if(user.player != null)
  4554.                     {
  4555.                         SetViolationPosition(user, user.player.transform.position);
  4556.                     }
  4557.                 }
  4558.             }
  4559.  
  4560.             public static bool ShouldIgnore(BasePlayer player) =>
  4561.                 (player == null) || !player.userID.IsSteamId() || Permissions.Ignore(player.userID);
  4562.  
  4563.             public static string StatusText(ulong userid) =>
  4564.                 StatusText(userid, Text.Language());
  4565.             public static string StatusText(ulong userid, BasePlayer player) =>
  4566.                 StatusText(userid, Text.Language(player.UserIDString));
  4567.             public static string StatusText(ulong userid, IPlayer iplayer) =>
  4568.                 StatusText(userid, Text.Language(iplayer.IsServer ? null : iplayer.Id));
  4569.             public static string StatusText(ulong userid, ulong playerid) =>
  4570.                 StatusText(userid, Text.Language(playerid.ToString()));
  4571.             public static string StatusText(ulong userid, string language) =>
  4572.                 Exists(userid) ? StatusText(Load(userid), language) : Text.GetPlain(Key.unknown, language);
  4573.             private static string StatusText(Info user, string language)
  4574.             {
  4575.                 StringBuilder status = new StringBuilder();
  4576.  
  4577.                 TimeSpan duration;
  4578.  
  4579.                 if(user.is_banned)
  4580.                 {
  4581.                     status.Append(Text.GetPlain(Key.banned, language));
  4582.                     status.Append(' ');
  4583.  
  4584.                     if(TimeSpan.MaxValue == (duration = TimeBanned(user)))
  4585.                     {
  4586.                         status.Append(Text.GetPlain(Key.permanently, language));
  4587.                     }
  4588.                     else
  4589.                     {
  4590.                         status.Append(Text.Duration.Short(duration, language));
  4591.                     }
  4592.                 }
  4593.                 else if(user.is_crippled)
  4594.                 {
  4595.                     status.Append(Text.GetPlain(Key.crippled, language));
  4596.                     status.Append(' ');
  4597.  
  4598.                     if(TimeSpan.MaxValue == (duration = TimeCrippled(user)))
  4599.                     {
  4600.                         status.Append(Text.GetPlain(Key.permanently, language));
  4601.                     }
  4602.                     else
  4603.                     {
  4604.                         status.Append(Text.Duration.Short(duration, language));
  4605.                     }
  4606.                 }
  4607.                 else if(user.player?.IsConnected ?? false)
  4608.                 {
  4609.                     status.Append(Text.GetPlain(Key.online, language));
  4610.                     status.Append(' ');
  4611.  
  4612.                     if(TimeSpan.MaxValue == (duration = TimeOnline(user)))
  4613.                     {
  4614.                         status.Append(Text.GetPlain(Key.never, language));
  4615.                     }
  4616.                     else
  4617.                     {
  4618.                         status.Append(Text.Duration.Short(duration, language));
  4619.                     }
  4620.                 }
  4621.                 else
  4622.                 {
  4623.                     status.Append(Text.GetPlain(Key.offline, language));
  4624.                     status.Append(' ');
  4625.  
  4626.                     if(TimeSpan.MaxValue == (duration = TimeOffline(user)))
  4627.                     {
  4628.                         status.Append(Text.GetPlain(Key.never, language));
  4629.                     }
  4630.                     else
  4631.                     {
  4632.                         status.Append(Text.Duration.Short(duration, language));
  4633.                     }
  4634.                 }
  4635.  
  4636.                 return status.ToString();
  4637.             }
  4638.  
  4639.             public static List<ulong> Team(ulong userid)
  4640.             {
  4641.                 ulong teamid = 0;
  4642.  
  4643.                 var player = Load(userid).player;
  4644.  
  4645.                 if(player != null)
  4646.                 {
  4647.                     teamid = player.currentTeam;
  4648.                 }
  4649.                 else
  4650.                 {
  4651.                     foreach(var team in RelationshipManager.ServerInstance.teams)
  4652.                     {
  4653.                         if(team.Value.members.Contains(userid))
  4654.                         {
  4655.                             teamid = team.Key;
  4656.  
  4657.                             break;
  4658.                         }
  4659.                     }
  4660.                 }
  4661.  
  4662.                 if(teamid != 0)
  4663.                 {
  4664.                     return new List<ulong>(RelationshipManager.ServerInstance.teams[teamid].members);
  4665.                 }
  4666.  
  4667.                 return new List<ulong> { userid };
  4668.             }
  4669.  
  4670.             public static TimeSpan TimeBanned(ulong userid) =>
  4671.                 TimeRemaining(Load(userid).ban_timer);
  4672.             private static TimeSpan TimeBanned(Info user) =>
  4673.                 TimeRemaining(user.ban_timer);
  4674.             public static TimeSpan TimeCrippled(ulong userid) =>
  4675.                 TimeRemaining(Load(userid).cripple_timer);
  4676.             private static TimeSpan TimeCrippled(Info user) =>
  4677.                 TimeRemaining(user.cripple_timer);
  4678.             private static TimeSpan TimeRemaining(DateTime time)
  4679.             {
  4680.                 if(time == DateTime.MaxValue)
  4681.                 {
  4682.                     return TimeSpan.MaxValue;
  4683.                 }
  4684.  
  4685.                 var current = DateTime.UtcNow;
  4686.  
  4687.                 if(time > current)
  4688.                 {
  4689.                     return time.Subtract(current);
  4690.                 }
  4691.  
  4692.                 return TimeSpan.Zero;
  4693.             }
  4694.  
  4695.             public static TimeSpan TimeOffline(ulong userid) =>
  4696.                 TimeSpent(Load(userid).time_disconnected);
  4697.             private static TimeSpan TimeOffline(Info user) =>
  4698.                 TimeSpent(user.time_disconnected);
  4699.             public static TimeSpan TimeOnline(ulong userid) =>
  4700.                 TimeSpent(Load(userid).time_connected);
  4701.             private static TimeSpan TimeOnline(Info user) =>
  4702.                 TimeSpent(user.time_connected);
  4703.             private static TimeSpan TimeSpent(DateTime time)
  4704.             {
  4705.                 if((time == DateTime.MaxValue) || (time == DateTime.MinValue))
  4706.                 {
  4707.                     return TimeSpan.MaxValue;
  4708.                 }
  4709.  
  4710.                 var current = DateTime.UtcNow;
  4711.  
  4712.                 if(time < current)
  4713.                 {
  4714.                     return current.Subtract(time);
  4715.                 }
  4716.  
  4717.                 return TimeSpan.Zero;
  4718.             }
  4719.  
  4720.             public static TimeSpan TimePlayed(ulong userid) =>
  4721.                 TimePlayed(Load(userid));
  4722.             private static TimeSpan TimePlayed(Info user)
  4723.             {
  4724.                 var time_played = user.time_played;
  4725.  
  4726.                 if(user.time_disconnected > user.time_connected)
  4727.                 {
  4728.                     time_played += user.time_disconnected.Subtract(user.time_connected);
  4729.                 }
  4730.                 else if(user.player?.IsConnected ?? false)
  4731.                 {
  4732.                     time_played += DateTime.UtcNow.Subtract(user.time_connected);
  4733.                 }
  4734.  
  4735.                 return time_played;
  4736.             }
  4737.  
  4738.             public static void Unban(ulong userid, bool manual = false, IPlayer actor = null) =>
  4739.                 Unban(Load(userid), manual, actor);
  4740.             public static void Unban(IPlayer actor)
  4741.             {
  4742.                 foreach(var entry in users)
  4743.                 {
  4744.                     var user = entry.Value;
  4745.  
  4746.                     if(user.is_banned)
  4747.                     {
  4748.                         Unban(user, false, actor, false);
  4749.                     }
  4750.                 }
  4751.  
  4752.                 Action(actor, Key.Unbanned);
  4753.             }
  4754.             private static void Unban(Info user, bool manual, IPlayer actor, bool broadcast = true)
  4755.             {
  4756.                 if(user.is_banned)
  4757.                 {
  4758.                     if(manual)
  4759.                     {
  4760.                         user.ban_count -= (user.ban_count > 0) ? 1ul : 0ul;
  4761.                     }
  4762.  
  4763.                     user.is_banned = false;
  4764.  
  4765.                     user.dirty = true;
  4766.  
  4767.                     if(broadcast)
  4768.                     {
  4769.                         Action(actor, Key.Unbanned, user);
  4770.                     }
  4771.  
  4772.                     _instance.server.Command("unban", user.userid.ToString());
  4773.                 }
  4774.             }
  4775.  
  4776.             public static void Uncripple(ulong userid, bool manual = false, IPlayer actor = null) =>
  4777.                 Uncripple(Load(userid), manual, actor);
  4778.             public static void Uncripple(IPlayer actor)
  4779.             {
  4780.                 foreach(var entry in users)
  4781.                 {
  4782.                     var user = entry.Value;
  4783.  
  4784.                     if(user.is_crippled)
  4785.                     {
  4786.                         Uncripple(user, false, actor, false);
  4787.                     }
  4788.                 }
  4789.  
  4790.                 Action(actor, Key.Uncrippled);
  4791.             }
  4792.             private static void Uncripple(Info user, bool manual, IPlayer actor, bool broadcast = true)
  4793.             {
  4794.                 if(user.is_crippled)
  4795.                 {
  4796.                     if(manual)
  4797.                     {
  4798.                         user.cripple_count -= (user.cripple_count > 0) ? 1ul : 0ul;
  4799.                     }
  4800.  
  4801.                     user.cripple_reason = string.Empty;
  4802.                     user.cripple_timer = DateTime.MinValue;
  4803.  
  4804.                     user.is_crippled = false;
  4805.  
  4806.                     user.dirty = true;
  4807.  
  4808.                     if(broadcast)
  4809.                     {
  4810.                         Action(actor, Key.Uncrippled, user);
  4811.                     }
  4812.                 }
  4813.             }
  4814.  
  4815.             public static void Unload()
  4816.             {
  4817.                 foreach(var player in BasePlayer.activePlayerList)
  4818.                 {
  4819.                     if(player.IsConnected)
  4820.                     {
  4821.                         OnDisconnected(player);
  4822.                     }
  4823.                 }
  4824.  
  4825.                 foreach(var player in BasePlayer.sleepingPlayerList)
  4826.                 {
  4827.                     if(player.IsConnected)
  4828.                     {
  4829.                         OnDisconnected(player);
  4830.                     }
  4831.                 }
  4832.  
  4833.                 Save();
  4834.  
  4835.                 names.Unload();
  4836.                 users.Clear();
  4837.  
  4838.                 teleport.Clear();
  4839.                 teleport = null;
  4840.             }
  4841.  
  4842.             public static void Update()
  4843.             {
  4844.                 _instance.NextTick(() =>
  4845.                 {
  4846.                     foreach(var player in BasePlayer.activePlayerList)
  4847.                     {
  4848.                         if(player.IsConnected)
  4849.                         {
  4850.                             OnConnected(player);
  4851.                         }
  4852.                     }
  4853.  
  4854.                     foreach(var player in BasePlayer.sleepingPlayerList)
  4855.                     {
  4856.                         if(player.IsConnected)
  4857.                         {
  4858.                             OnConnected(player);
  4859.                         }
  4860.                     }
  4861.  
  4862.                     foreach(var serveruser in ServerUsers.GetAll(ServerUsers.UserGroup.Banned))
  4863.                     {
  4864.                         OnBanned(serveruser.steamid, true);
  4865.                     }
  4866.                 });
  4867.             }
  4868.             private static void Update(Info user, string name, string address)
  4869.             {
  4870.                 if(user.name != name)
  4871.                 {
  4872.                     user.dirty = true; user.name = name;
  4873.                 }
  4874.  
  4875.                 if(user.names.Add(name))
  4876.                 {
  4877.                     user.dirty = true;
  4878.                 }
  4879.  
  4880.                 if(!names.Contains(name))
  4881.                 {
  4882.                     names.Add(name, new HashSet<ulong>());
  4883.                 }
  4884.  
  4885.                 if(names[name].Add(user.userid))
  4886.                 {
  4887.                     names.SetDirty();
  4888.                 }
  4889.  
  4890.                 if(!string.IsNullOrEmpty(address = address ?? user.address))
  4891.                 {
  4892.                     if(user.address != address)
  4893.                     {
  4894.                         user.dirty = true; user.address = address;
  4895.                     }
  4896.  
  4897.                     if(user.addresses.Add(address))
  4898.                     {
  4899.                         user.dirty = true;
  4900.                     }
  4901.  
  4902.                     IP.Update(address, user.userid);
  4903.                 }
  4904.             }
  4905.         }
  4906.  
  4907.         #endregion _user_
  4908.  
  4909.         #region _version_
  4910.  
  4911.         private new class Version
  4912.         {
  4913.             public class Settings
  4914.             {
  4915.                 public int Major;
  4916.                 public int Minor;
  4917.                 public int Patch;
  4918.  
  4919.                 public Settings()
  4920.                 {
  4921.                     Major = Minor = Patch = 0;
  4922.                 }
  4923.  
  4924.                 public int Compare(int major, int minor, int patch)
  4925.                 {
  4926.                     return
  4927.                         (Major != major) ? (Major - major) :
  4928.                         (Minor != minor) ? (Minor - minor) :
  4929.                         (Patch != patch) ? (Patch - patch) : 0;
  4930.                 }
  4931.  
  4932.                 public void Validate()
  4933.                 {
  4934.                     var current = (_instance as CovalencePlugin).Version;
  4935.  
  4936.                     if(Compare(current.Major, current.Minor, current.Patch) < 0)
  4937.                     {
  4938.                         Configuration.SetDirty();
  4939.  
  4940.                         Major = current.Major;
  4941.                         Minor = current.Minor;
  4942.                         Patch = current.Patch;
  4943.  
  4944.                         Configuration.SetUpgrade();
  4945.                     }
  4946.                     else
  4947.                     {
  4948.                         Configuration.SetUpgrade(false);
  4949.                     }
  4950.  
  4951.                     String = $"{Major}.{Minor}.{Patch}";
  4952.                 }
  4953.             }
  4954.  
  4955.             public static string String { get; protected set; }
  4956.         }
  4957.  
  4958.         #endregion _version_
  4959.  
  4960.         #region _violation_
  4961.  
  4962.         private class Violation
  4963.         {
  4964.             public class Settings
  4965.             {
  4966.                 public bool  Ban;
  4967.                 public ulong Cooldown;
  4968.                 public float Sensitivity;
  4969.                 public bool  Warn;
  4970.  
  4971.                 public Settings(bool ban = true, ulong cooldown = 7200, float sensitivity = 0.5f, bool warn = false)
  4972.                 {
  4973.                     Ban         = ban;
  4974.                     Cooldown    = cooldown;
  4975.                     Sensitivity = sensitivity;
  4976.                     Warn        = warn;
  4977.                 }
  4978.  
  4979.                 public ulong Count(ulong min, ulong max, bool squared = false)
  4980.                 {
  4981.                     if(squared)
  4982.                     {
  4983.                         return min + (ulong)((max - min) * (1.0f - Sensitivity * Sensitivity));
  4984.                     }
  4985.                     else
  4986.                     {
  4987.                         return min + (ulong)((max - min) * (1.0f - Sensitivity));
  4988.                     }
  4989.                 }
  4990.  
  4991.                 public Settings Validate(ulong max)
  4992.                 {
  4993.                     Configuration.Clamp(ref Cooldown,     1ul,  max);
  4994.                     Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  4995.  
  4996.                     return this;
  4997.                 }
  4998.             }
  4999.  
  5000.             private bool     ban;
  5001.             private Key      category;
  5002.             private ulong    cooldown;
  5003.             private ulong    count;
  5004.             private TimeSpan rate;
  5005.             private bool     warn;
  5006.  
  5007.             private Dictionary<ulong, History> histories;
  5008.  
  5009.             private static readonly Dictionary<string, Key> categories = new Dictionary<string, Key>();
  5010.  
  5011.             private static ActionQueue triggers;
  5012.  
  5013.             private static readonly Violation violation = new Violation(Key.NULL);
  5014.  
  5015.             private class History
  5016.             {
  5017.                 public DateTime cooldown;
  5018.                 public ulong    count;
  5019.                 public DateTime time;
  5020.                 public DateTime warned;
  5021.                 public ulong    warnings;
  5022.  
  5023.                 public History()
  5024.                 {
  5025.                     cooldown = DateTime.MinValue;
  5026.                     count    = 0;
  5027.                     time     = DateTime.MinValue;
  5028.                     warned   = DateTime.MinValue;
  5029.                     warnings = 0;
  5030.                 }
  5031.             }
  5032.  
  5033.             public Violation(Key category)
  5034.             {
  5035.                 this.category = category;
  5036.             }
  5037.  
  5038.             private void Broadcast(ulong userid, Key action, Key type, string details, Dictionary<string, string> hook_details)
  5039.             {
  5040.                 if((action == Key.Warning) && IsFlooding(userid))
  5041.                 {
  5042.                     return;
  5043.                 }
  5044.  
  5045.                 var actionname   = Text.GetPlain(action);
  5046.                 var categoryname = Text.GetPlain(category);
  5047.                 var playerid     = userid.ToString();
  5048.                 var playername   = Text.Sanitize(User.Name(userid));
  5049.                 var typename     = Text.GetPlain(type);
  5050.  
  5051.                 var parameters = new Dictionary<string, string>
  5052.                 {
  5053.                     { "action", actionname },
  5054.                     { "category", categoryname },
  5055.                     { "details", details },
  5056.                     { "playerid", playerid },
  5057.                     { "playername", playername },
  5058.                     { "type", typename }
  5059.                 };
  5060.  
  5061.                 Chat.Admin(Key.Violation, parameters);
  5062.  
  5063.                 switch(category)
  5064.                 {
  5065.                 case Key.AntiCheat: parameters["color"] = 0xff6060.ToString(); parameters["categoryicon"] = ":shield:"; break;
  5066.                 case Key.AntiFlood: parameters["color"] = 0xffff60.ToString(); parameters["categoryicon"] = ":stopwatch:"; break;
  5067.                 case Key.IP:        parameters["color"] = 0x00c0ff.ToString(); parameters["categoryicon"] = ":signal_strength:"; break;
  5068.                 case Key.Steam:     parameters["color"] = 0xc0c0ff.ToString(); parameters["categoryicon"] = ":gear:"; break;
  5069.                 case Key.VPN:       parameters["color"] = 0x00c0ff.ToString(); parameters["categoryicon"] = ":signal_strength:"; break;
  5070.                 default:            parameters["color"] = 0x60ff60.ToString(); parameters["categoryicon"] = ":question:"; break;
  5071.                 }
  5072.  
  5073.                 switch(action)
  5074.                 {
  5075.                 case Key.Banned:  parameters["actionicon"] = ":no_entry:"; break;
  5076.                 case Key.Kicked:  parameters["actionicon"] = ":x:"; break;
  5077.                 case Key.Warning: parameters["actionicon"] = ":warning:"; break;
  5078.                 }
  5079.  
  5080.                 Interface.CallHook("OnGuardianViolation", playerid, parameters);
  5081.  
  5082.                 if(category == Key.AntiCheat)
  5083.                 {
  5084.                     Interface.CallHook("OnGuardian" + Enum.GetName(typeof(Key), type), playerid, hook_details);
  5085.                 }
  5086.             }
  5087.  
  5088.             public static Key Category(string category)
  5089.             {
  5090.                 Key key;
  5091.  
  5092.                 if(string.IsNullOrEmpty(category) || !categories.TryGetValue(category, out key))
  5093.                 {
  5094.                     key = Key.NULL;
  5095.                 }
  5096.  
  5097.                 return key;
  5098.             }
  5099.  
  5100.             public static void Configure()
  5101.             {
  5102.                 var settings = config.Violation.Validate(86400);
  5103.  
  5104.                 violation.ban      = settings.Ban;
  5105.                 violation.cooldown = settings.Cooldown;
  5106.                 violation.count    = settings.Count(2, 6);
  5107.                 violation.rate     = TimeSpan.FromHours(1);
  5108.                 violation.warn     = settings.Warn;
  5109.  
  5110.                 violation.histories = new Dictionary<ulong, History>();
  5111.             }
  5112.  
  5113.             public void Configure(Settings settings, ulong trigger_min, ulong trigger_max, ulong trigger_rate)
  5114.             {
  5115.                 ban      = settings.Ban;
  5116.                 cooldown = settings.Cooldown;
  5117.                 count    = settings.Count(trigger_min, trigger_max);
  5118.                 rate     = TimeSpan.FromMilliseconds(trigger_rate);
  5119.                 warn     = settings.Warn;
  5120.  
  5121.                 histories = new Dictionary<ulong, History>();
  5122.             }
  5123.  
  5124.             public ulong Cooldown(ulong userid)
  5125.             {
  5126.                 var history = Get(userid);
  5127.  
  5128.                 var time = DateTime.UtcNow;
  5129.  
  5130.                 if(history.cooldown > time)
  5131.                 {
  5132.                     return (ulong)history.cooldown.Subtract(time).TotalSeconds + 1ul;
  5133.                 }
  5134.  
  5135.                 return 0;
  5136.             }
  5137.  
  5138.             public void Clear()
  5139.             {
  5140.                 histories.Clear();
  5141.  
  5142.                 histories = null;
  5143.             }
  5144.  
  5145.             private History Get(ulong userid)
  5146.             {
  5147.                 History history;
  5148.  
  5149.                 if(!histories.TryGetValue(userid, out history))
  5150.                 {
  5151.                     histories.Add(userid, history = new History());
  5152.                 }
  5153.  
  5154.                 return history;
  5155.             }
  5156.  
  5157.             private bool IsFlooding(ulong userid)
  5158.             {
  5159.                 var current = DateTime.UtcNow;
  5160.                 var history = Get(userid);
  5161.  
  5162.                 if(history.warnings > 6)
  5163.                 {
  5164.                     if(current < history.warned)
  5165.                     {
  5166.                         return true;
  5167.                     }
  5168.                     else
  5169.                     {
  5170.                         history.warnings = 0;
  5171.                     }
  5172.                 }
  5173.                 else
  5174.                 {
  5175.                     history.warnings++;
  5176.                 }
  5177.  
  5178.                 history.warned = current.AddSeconds(60.0);
  5179.  
  5180.                 return false;
  5181.             }
  5182.  
  5183.             public static void Load()
  5184.             {
  5185.                 Configure();
  5186.  
  5187.                 categories.Add(Text.GetPlain(Key.AntiCheat), Key.AntiCheat);
  5188.                 categories.Add(Text.GetPlain(Key.AntiFlood), Key.AntiFlood);
  5189.                 categories.Add(Text.GetPlain(Key.IP),        Key.IP);
  5190.                 categories.Add(Text.GetPlain(Key.Steam),     Key.Steam);
  5191.                 categories.Add(Text.GetPlain(Key.VPN),       Key.VPN);
  5192.  
  5193.                 triggers = new ActionQueue(1.0f);
  5194.             }
  5195.  
  5196.             private void Reduce(ulong userid)
  5197.             {
  5198.                 Get(userid).count >>= 1;
  5199.             }
  5200.  
  5201.             private void Reset(ulong userid)
  5202.             {
  5203.                 histories.Remove(userid);
  5204.             }
  5205.  
  5206.             public bool Trigger(ulong userid)
  5207.             {
  5208.                 var history = Get(userid);
  5209.  
  5210.                 var time = DateTime.UtcNow;
  5211.  
  5212.                 var sent = history.time; history.time = time;
  5213.  
  5214.                 var elapsed = time.Subtract(sent);
  5215.  
  5216.                 if(elapsed <= rate)
  5217.                 {
  5218.                     if(++history.count >= count)
  5219.                     {
  5220.                         if(history.cooldown < time)
  5221.                         {
  5222.                             history.cooldown = time.AddSeconds(cooldown);
  5223.                         }
  5224.  
  5225.                         return true;
  5226.                     }
  5227.                 }
  5228.                 else
  5229.                 {
  5230.                     if(elapsed.TotalSeconds < cooldown)
  5231.                     {
  5232.                         history.count -= (history.count > 0ul) ? 1ul : 0ul;
  5233.                     }
  5234.                     else
  5235.                     {
  5236.                         history.count = 0;
  5237.                     }
  5238.                 }
  5239.  
  5240.                 if(history.cooldown > time)
  5241.                 {
  5242.                     return true;
  5243.                 }
  5244.  
  5245.                 return false;
  5246.             }
  5247.  
  5248.             public void Trigger(ulong userid, Key type, string details, bool kick = false, Dictionary<string, string> hook_details = null)
  5249.             {
  5250.                 Trigger(userid, type, details, 1ul, kick, hook_details);
  5251.             }
  5252.             public void Trigger(ulong userid, Key type, string details, ulong violations, bool kick = false, Dictionary<string, string> hook_details = null)
  5253.             {
  5254.                 User.SetViolationPosition(userid);
  5255.  
  5256.                 triggers.Enqueue(() => Triggered(userid, type, details, violations, kick, hook_details));
  5257.             }
  5258.             private void Triggered(ulong userid, Key type, string details, ulong violations, bool kick, Dictionary<string, string> hook_details)
  5259.             {
  5260.                 if(User.IsBanned(userid))
  5261.                 {
  5262.                     return;
  5263.                 }
  5264.  
  5265.                 Key action;
  5266.  
  5267.                 if(config.Admin.Bypass && Permissions.Admin(userid))
  5268.                 {
  5269.                     Reduce(userid);
  5270.  
  5271.                     action = Key.Warning;
  5272.                 }
  5273.                 else
  5274.                 {
  5275.                     bool triggered = false;
  5276.  
  5277.                     if(violation.ban && ban)
  5278.                     {
  5279.                         while(violations-- > 0)
  5280.                         {
  5281.                             if(triggered = violation.Trigger(userid))
  5282.                             {
  5283.                                 break;
  5284.                             }
  5285.                         }
  5286.                     }
  5287.  
  5288.                     if(triggered)
  5289.                     {
  5290.                         Reset(userid);
  5291.  
  5292.                         action = Key.Banned;
  5293.  
  5294.                         User.Ban(userid, $"{Text.GetPlain(category)}: {Text.GetPlain(type)}");
  5295.  
  5296.                         violation.Reset(userid);
  5297.                     }
  5298.                     else
  5299.                     {
  5300.                         if(!kick && (ban || (!ban && warn) || violation.warn))
  5301.                         {
  5302.                             action = Key.Warning;
  5303.                         }
  5304.                         else if(!User.IsConnected(userid))
  5305.                         {
  5306.                             return;
  5307.                         }
  5308.                         else
  5309.                         {
  5310.                             action = Key.Kicked;
  5311.  
  5312.                             User.Kick(userid, $"{Text.GetPlain(category)}: {Text.GetPlain(type)}");
  5313.                         }
  5314.  
  5315.                         Reduce(userid);
  5316.                     }
  5317.                 }
  5318.  
  5319.                 Broadcast(userid, action, type, details, hook_details);
  5320.             }
  5321.  
  5322.             public static void Unload()
  5323.             {
  5324.                 categories.Clear();
  5325.  
  5326.                 triggers.Clear();
  5327.                 triggers = null;
  5328.  
  5329.                 violation.Clear();
  5330.             }
  5331.  
  5332.             public void Warning(ulong userid, Key type, string details, Dictionary<string, string> hook_details = null)
  5333.             {
  5334.                 triggers.Enqueue(() => Broadcast(userid, Key.Warning, type, details, hook_details));
  5335.             }
  5336.  
  5337.             public void Zero(ulong userid)
  5338.             {
  5339.                 var history = Get(userid);
  5340.  
  5341.                 history.cooldown = DateTime.MinValue;
  5342.                 history.count    = 0;
  5343.                 history.time     = DateTime.MinValue;
  5344.             }
  5345.         }
  5346.  
  5347.         #endregion _violation_
  5348.  
  5349.         #region _vpn_
  5350.  
  5351.         private class VPN
  5352.         {
  5353.             public class Settings
  5354.             {
  5355.                 public VpnApi       API;
  5356.                 public VpnCache     Cache;
  5357.                 public VpnCheck     Check;
  5358.                 public VpnViolation Violation;
  5359.  
  5360.                 public Settings()
  5361.                 {
  5362.                     API       = new VpnApi();
  5363.                     Cache     = new VpnCache();
  5364.                     Check     = new VpnCheck();
  5365.                     Violation = new VpnViolation();
  5366.                 }
  5367.  
  5368.                 public class VpnApi
  5369.                 {
  5370.                     public Guardian.API.Settings GetIpIntel;
  5371.                     public Guardian.API.Settings IpApi;
  5372.                     public Guardian.API.Settings IpHub;
  5373.                     public Guardian.API.Settings IpQualityScore;
  5374.  
  5375.                     public VpnApi()
  5376.                     {
  5377.                         GetIpIntel     = new Guardian.API.Settings();
  5378.                         IpApi          = new Guardian.API.Settings();
  5379.                         IpHub          = new Guardian.API.Settings();
  5380.                         IpQualityScore = new Guardian.API.Settings();
  5381.                     }
  5382.  
  5383.                     public void Validate()
  5384.                     {
  5385.                         Configuration.Validate(ref GetIpIntel,     () => new Guardian.API.Settings(), () => GetIpIntel.Validate());
  5386.                         Configuration.Validate(ref IpApi,          () => new Guardian.API.Settings(), () => IpApi.Validate());
  5387.                         Configuration.Validate(ref IpHub,          () => new Guardian.API.Settings(), () => IpHub.Validate());
  5388.                         Configuration.Validate(ref IpQualityScore, () => new Guardian.API.Settings(), () => IpQualityScore.Validate());
  5389.                     }
  5390.                 }
  5391.  
  5392.                 public class VpnCache
  5393.                 {
  5394.                     public ulong Hours;
  5395.  
  5396.                     public VpnCache()
  5397.                     {
  5398.                         Hours = 72ul;
  5399.                     }
  5400.                 }
  5401.  
  5402.                 public class VpnCheck
  5403.                 {
  5404.                     public bool Enabled;
  5405.                     public bool Strict;
  5406.                 }
  5407.  
  5408.                 public class VpnViolation
  5409.                 {
  5410.                     public bool Ban;
  5411.                     public bool Enabled;
  5412.                     public bool Warn;
  5413.  
  5414.                     public VpnViolation()
  5415.                     {
  5416.                         Ban     = false;
  5417.                         Enabled = true;
  5418.                         Warn    = false;
  5419.                     }
  5420.                 }
  5421.  
  5422.                 public void Validate()
  5423.                 {
  5424.                     Configuration.Validate(ref API,       () => new VpnApi(), () => API.Validate());
  5425.                     Configuration.Validate(ref Cache,     () => new VpnCache());
  5426.                     Configuration.Validate(ref Check,     () => new VpnCheck());
  5427.                     Configuration.Validate(ref Violation, () => new VpnViolation());
  5428.                 }
  5429.             }
  5430.  
  5431.             private static ActionQueue checks;
  5432.  
  5433.             private static readonly Violation violation = new Violation(Key.VPN);
  5434.  
  5435.             public class API
  5436.             {
  5437.                 public class GetIpIntel
  5438.                 {
  5439.                     [JsonProperty("message")]
  5440.                     public string Message { get; set; }
  5441.                     [JsonProperty("result")]
  5442.                     public float Result { get; set; }
  5443.                     [JsonProperty("status")]
  5444.                     public string Status { get; set; }
  5445.  
  5446.                     private const string api = "getipintel.net";
  5447.  
  5448.                     public static void Check(string address, ulong userid)
  5449.                     {
  5450.                         if(string.IsNullOrEmpty(config.VPN.API.GetIpIntel.ApiKey))
  5451.                         {
  5452.                             Log.Console(Key.LogVpnCheckConfig, new Dictionary<string, string>
  5453.                             {
  5454.                                 { "api", api },
  5455.                                 { "link", $"http://getipintel.net/" }
  5456.                             });
  5457.  
  5458.                             return;
  5459.                         }
  5460.  
  5461.                         var url = $"http://check.getipintel.net/check.php?ip={address}&contact={config.VPN.API.GetIpIntel.ApiKey}&format=json";
  5462.  
  5463.                         _instance.webrequest.Enqueue(url, string.Empty, (code, reply) =>
  5464.                         {
  5465.                             if(code != 200 || string.IsNullOrEmpty(reply))
  5466.                             {
  5467.                                 Log.Console(Key.LogVpnCheckError, new Dictionary<string, string>
  5468.                                 {
  5469.                                     { "address", address },
  5470.                                     { "api", api },
  5471.                                     { "info", $"({code}: {reply})" },
  5472.                                     { "type", "HTTP" }
  5473.                                 });
  5474.  
  5475.                                 return;
  5476.                             }
  5477.  
  5478.                             try
  5479.                             {
  5480.                                 var response = JsonConvert.DeserializeObject<GetIpIntel>(reply);
  5481.  
  5482.                                 if(string.IsNullOrEmpty(response.Status) || response.Status != "success")
  5483.                                 {
  5484.                                     Log.Console(Key.LogVpnCheckError, new Dictionary<string, string>
  5485.                                     {
  5486.                                         { "address", address },
  5487.                                         { "api", api },
  5488.                                         { "info", response.Message },
  5489.                                         { "type", "STATUS" }
  5490.                                     });
  5491.  
  5492.                                     return;
  5493.                                 }
  5494.  
  5495.                                 if(config.Log.VPN.Check)
  5496.                                 {
  5497.                                     Log.Console(Key.LogVpnCheck, new Dictionary<string, string>
  5498.                                     {
  5499.                                         { "address", address },
  5500.                                         { "api", api },
  5501.                                         { "info", $"result={response.Result}" },
  5502.                                     });
  5503.                                 }
  5504.  
  5505.                                 if(response.Result > 0.99)
  5506.                                 {
  5507.                                     Violation(address, userid, api);
  5508.                                 }
  5509.                                 else if(config.VPN.Check.Strict && (response.Result > 0.95))
  5510.                                 {
  5511.                                     Violation(address, userid, api);
  5512.                                 }
  5513.                             }
  5514.                             catch
  5515.                             {
  5516.                                 Log.Console(Key.LogVpnCheckError, new Dictionary<string, string>
  5517.                                 {
  5518.                                     { "address", address },
  5519.                                     { "api", api },
  5520.                                     { "info", reply },
  5521.                                     { "type", "JSON" }
  5522.                                 });
  5523.                             }
  5524.                         }, _instance);
  5525.                     }
  5526.                 }
  5527.  
  5528.                 public class IpApi
  5529.                 {
  5530.                     [JsonProperty("hosting")]
  5531.                     public bool Hosting { get; set; }
  5532.                     [JsonProperty("proxy")]
  5533.                     public bool Proxy { get; set; }
  5534.                     [JsonProperty("status")]
  5535.                     public string Status { get; set; }
  5536.  
  5537.                     private const string api = "ip-api.com";
  5538.  
  5539.                     public static void Check(string address, ulong userid)
  5540.                     {
  5541.                         var url = $"http://ip-api.com/json/{address}?fields=status,proxy,hosting";
  5542.  
  5543.                         _instance.webrequest.Enqueue(url, string.Empty, (code, reply) =>
  5544.                         {
  5545.                             if(code != 200 || string.IsNullOrEmpty(reply))
  5546.                             {
  5547.                                 Log.Console(Key.LogVpnCheckError, new Dictionary<string, string>
  5548.                                 {
  5549.                                     { "address", address },
  5550.                                     { "api", api },
  5551.                                     { "info", $"({code}: {reply})" },
  5552.                                     { "type", "HTTP" }
  5553.                                 });
  5554.  
  5555.                                 return;
  5556.                             }
  5557.  
  5558.                             try
  5559.                             {
  5560.                                 var response = JsonConvert.DeserializeObject<IpApi>(reply);
  5561.  
  5562.                                 if(string.IsNullOrEmpty(response.Status) || response.Status != "success")
  5563.                                 {
  5564.                                     Log.Console(Key.LogVpnCheckError, new Dictionary<string, string>
  5565.                                     {
  5566.                                         { "address", address },
  5567.                                         { "api", api },
  5568.                                         { "info", response.Status },
  5569.                                         { "type", "STATUS" }
  5570.                                     });
  5571.  
  5572.                                     return;
  5573.                                 }
  5574.  
  5575.                                 if(config.Log.VPN.Check)
  5576.                                 {
  5577.                                     Log.Console(Key.LogVpnCheck, new Dictionary<string, string>
  5578.                                     {
  5579.                                         { "address", address },
  5580.                                         { "api", api },
  5581.                                         { "info", $"hosting={response.Hosting}; proxy={response.Proxy}" },
  5582.                                     });
  5583.                                 }
  5584.  
  5585.                                 if(response.Proxy)
  5586.                                 {
  5587.                                     Violation(address, userid, api);
  5588.                                 }
  5589.                                 else if(config.VPN.Check.Strict && response.Hosting)
  5590.                                 {
  5591.                                     Violation(address, userid, api);
  5592.                                 }
  5593.                             }
  5594.                             catch
  5595.                             {
  5596.                                 Log.Console(Key.LogVpnCheckError, new Dictionary<string, string>
  5597.                                 {
  5598.                                     { "address", address },
  5599.                                     { "api", api },
  5600.                                     { "info", reply },
  5601.                                     { "type", "JSON" }
  5602.                                 });
  5603.                             }
  5604.                         }, _instance);
  5605.                     }
  5606.                 }
  5607.  
  5608.                 public class IpHub
  5609.                 {
  5610.                     [JsonProperty("block")]
  5611.                     public int Block { get; set; }
  5612.  
  5613.                     [JsonIgnore]
  5614.                     private static readonly Dictionary<string, string> headers = new Dictionary<string, string>();
  5615.  
  5616.                     private const string api = "iphub.info";
  5617.  
  5618.                     public static void Check(string address, ulong userid)
  5619.                     {
  5620.                         if(string.IsNullOrEmpty(config.VPN.API.IpHub.ApiKey))
  5621.                         {
  5622.                             Log.Console(Key.LogVpnCheckConfig, new Dictionary<string, string>
  5623.                             {
  5624.                                 { "api", api },
  5625.                                 { "link", $"http://iphub.info/" }
  5626.                             });
  5627.  
  5628.                             return;
  5629.                         }
  5630.  
  5631.                         var url = $"http://v2.api.iphub.info/ip/{address}";
  5632.  
  5633.                         _instance.webrequest.Enqueue(url, string.Empty, (code, reply) =>
  5634.                         {
  5635.                             if(code != 200 || string.IsNullOrEmpty(reply))
  5636.                             {
  5637.                                 Log.Console(Key.LogVpnCheckError, new Dictionary<string, string>
  5638.                                 {
  5639.                                     { "address", address },
  5640.                                     { "api", api },
  5641.                                     { "info", $"({code}: {reply})" },
  5642.                                     { "type", "HTTP" }
  5643.                                 });
  5644.  
  5645.                                 return;
  5646.                             }
  5647.  
  5648.                             try
  5649.                             {
  5650.                                 var response = JsonConvert.DeserializeObject<IpHub>(reply);
  5651.  
  5652.                                 if(config.Log.VPN.Check)
  5653.                                 {
  5654.                                     Log.Console(Key.LogVpnCheck, new Dictionary<string, string>
  5655.                                     {
  5656.                                     { "address", address },
  5657.                                     { "api", api },
  5658.                                     { "info", $"block={response.Block}" },
  5659.                                     });
  5660.                                 }
  5661.  
  5662.                                 if(response.Block == 1)
  5663.                                 {
  5664.                                     Violation(address, userid, api);
  5665.                                 }
  5666.                                 else if(config.VPN.Check.Strict && (response.Block == 2))
  5667.                                 {
  5668.                                     Violation(address, userid, api);
  5669.                                 }
  5670.                             }
  5671.                             catch
  5672.                             {
  5673.                                 Log.Console(Key.LogVpnCheckError, new Dictionary<string, string>
  5674.                                 {
  5675.                                     { "address", address },
  5676.                                     { "api", api },
  5677.                                     { "info", reply },
  5678.                                     { "type", "JSON" }
  5679.                                 });
  5680.                             }
  5681.                         }, _instance, RequestMethod.GET, headers);
  5682.                     }
  5683.  
  5684.                     public static void Configure()
  5685.                     {
  5686.                         headers["X-Key"] = config.VPN.API.IpHub.ApiKey;
  5687.                     }
  5688.  
  5689.                     public static void Unload() => headers.Clear();
  5690.                 }
  5691.  
  5692.                 public class IpQualityScore
  5693.                 {
  5694.                     [JsonProperty("fraud_score")]
  5695.                     public int FraudScore { get; set; }
  5696.                     [JsonProperty("message")]
  5697.                     public string Message { get; set; }
  5698.                     [JsonProperty("proxy")]
  5699.                     public bool Proxy { get; set; }
  5700.                     [JsonProperty("recent_abuse")]
  5701.                     public bool RecentAbuse { get; set; }
  5702.                     [JsonProperty("success")]
  5703.                     public bool Success { get; set; }
  5704.                     [JsonProperty("vpn")]
  5705.                     public bool VPN { get; set; }
  5706.  
  5707.                     private const string api = "ipqualityscore.com";
  5708.  
  5709.                     public static void Check(string address, ulong userid)
  5710.                     {
  5711.                         if(string.IsNullOrEmpty(config.VPN.API.IpQualityScore.ApiKey))
  5712.                         {
  5713.                             Log.Console(Key.LogVpnCheckConfig, new Dictionary<string, string>
  5714.                             {
  5715.                                 { "api", api },
  5716.                                 { "link", $"http://ipqualityscore.com/" }
  5717.                             });
  5718.  
  5719.                             return;
  5720.                         }
  5721.  
  5722.                         var url = $"https://ipqualityscore.com/api/json/ip/{config.VPN.API.IpQualityScore.ApiKey}/{address}?allow_public_access_points=true&lighter_penalties=true&mobile=true&strictness=1";
  5723.  
  5724.                         _instance.webrequest.Enqueue(url, string.Empty, (code, reply) =>
  5725.                         {
  5726.                             if(code != 200 || string.IsNullOrEmpty(reply))
  5727.                             {
  5728.                                 Log.Console(Key.LogVpnCheckError, new Dictionary<string, string>
  5729.                                 {
  5730.                                 { "address", address },
  5731.                                 { "api", api },
  5732.                                 { "info", $"({code}: {reply})" },
  5733.                                 { "type", "HTTP" }
  5734.                                 });
  5735.  
  5736.                                 return;
  5737.                             }
  5738.  
  5739.                             try
  5740.                             {
  5741.                                 var response = JsonConvert.DeserializeObject<IpQualityScore>(reply);
  5742.  
  5743.                                 if(!response.Success)
  5744.                                 {
  5745.                                     Log.Console(Key.LogVpnCheckError, new Dictionary<string, string>
  5746.                                     {
  5747.                                     { "address", address },
  5748.                                     { "api", api },
  5749.                                     { "info", response.Message },
  5750.                                     { "type", "STATUS" }
  5751.                                     });
  5752.  
  5753.                                     return;
  5754.                                 }
  5755.  
  5756.                                 if(config.Log.VPN.Check)
  5757.                                 {
  5758.                                     Log.Console(Key.LogVpnCheck, new Dictionary<string, string>
  5759.                                     {
  5760.                                     { "address", address },
  5761.                                     { "api", api },
  5762.                                     { "info", $"fraud_score={response.FraudScore}; proxy={response.Proxy}; recent_abuse={response.RecentAbuse}; vpn={response.VPN}" },
  5763.                                     });
  5764.                                 }
  5765.  
  5766.                                 if(response.VPN || (response.FraudScore >= 85))
  5767.                                 {
  5768.                                     Violation(address, userid, api);
  5769.                                 }
  5770.                                 else if(config.VPN.Check.Strict && (response.Proxy || response.RecentAbuse || (response.FraudScore >= 75)))
  5771.                                 {
  5772.                                     Violation(address, userid, api);
  5773.                                 }
  5774.                             }
  5775.                             catch
  5776.                             {
  5777.                                 Log.Console(Key.LogVpnCheckError, new Dictionary<string, string>
  5778.                                 {
  5779.                                 { "address", address },
  5780.                                 { "api", api },
  5781.                                 { "info", reply },
  5782.                                 { "type", "JSON" }
  5783.                                 });
  5784.                             }
  5785.                         }, _instance);
  5786.                     }
  5787.                 }
  5788.  
  5789.                 public static void Configure() => IpHub.Configure();
  5790.  
  5791.                 public static void Unload() => IpHub.Unload();
  5792.             }
  5793.  
  5794.             private class Cache
  5795.             {
  5796.                 private static readonly DataFile<string, DateTime> blocks = new DataFile<string, DateTime>("vpn_blocks");
  5797.                 private static readonly DataFile<string, DateTime> bypass = new DataFile<string, DateTime>("vpn_bypass");
  5798.  
  5799.                 public static void Block(string address)
  5800.                 {
  5801.                     bypass.Remove(address);
  5802.  
  5803.                     blocks[address] = DateTime.UtcNow;
  5804.                 }
  5805.  
  5806.                 public static void Bypass(string address, ulong _reserved = 0)
  5807.                 {
  5808.                     blocks.Remove(address);
  5809.  
  5810.                     bypass[address] = DateTime.UtcNow;
  5811.                 }
  5812.  
  5813.                 public static bool IsBlocked(string address) => IsCached(blocks, address);
  5814.  
  5815.                 public static bool IsBypassed(string address) => IsCached(bypass, address);
  5816.  
  5817.                 private static bool IsCached(DataFile<string, DateTime> cache, string address)
  5818.                 {
  5819.                     if(cache.Contains(address))
  5820.                     {
  5821.                         if(IsExpired(cache[address], DateTime.UtcNow))
  5822.                         {
  5823.                             cache.Remove(address);
  5824.                         }
  5825.                         else
  5826.                         {
  5827.                             return true;
  5828.                         }
  5829.                     }
  5830.  
  5831.                     return false;
  5832.                 }
  5833.  
  5834.                 private static bool IsExpired(DateTime timestamp, DateTime check)
  5835.                 {
  5836.                     if(config.VPN.Cache.Hours == 0)
  5837.                     {
  5838.                         return false;
  5839.                     }
  5840.  
  5841.                     if(check == DateTime.MinValue)
  5842.                     {
  5843.                         check = DateTime.UtcNow;
  5844.                     }
  5845.  
  5846.                     return check >= timestamp.AddHours(config.VPN.Cache.Hours);
  5847.                 }
  5848.  
  5849.                 public static void Load()
  5850.                 {
  5851.                     blocks.Load();
  5852.                     bypass.Load();
  5853.  
  5854.                     Update();
  5855.                 }
  5856.  
  5857.                 public static void Save()
  5858.                 {
  5859.                     Update();
  5860.  
  5861.                     blocks.Save();
  5862.                     bypass.Save();
  5863.                 }
  5864.  
  5865.                 public static void Unblock(string address) => blocks.Remove(address);
  5866.  
  5867.                 public static void Unload()
  5868.                 {
  5869.                     Update();
  5870.  
  5871.                     blocks.Unload();
  5872.                     bypass.Unload();
  5873.                 }
  5874.  
  5875.                 private static void Update()
  5876.                 {
  5877.                     var current = DateTime.UtcNow;
  5878.  
  5879.                     Update(blocks, current);
  5880.                     Update(bypass, current);
  5881.                 }
  5882.                 private static void Update(DataFile<string, DateTime> cache, DateTime current)
  5883.                 {
  5884.                     List<string> expired = new List<string>();
  5885.  
  5886.                     cache.ForEach((address, timestamp) =>
  5887.                     {
  5888.                         if(IsExpired(timestamp, current))
  5889.                         {
  5890.                             expired.Add(address);
  5891.                         }
  5892.                     });
  5893.  
  5894.                     foreach(var address in expired)
  5895.                     {
  5896.                         cache.Remove(address);
  5897.                     }
  5898.                 }
  5899.             }
  5900.  
  5901.             public static void Bypass(string address) => Cache.Bypass(address);
  5902.  
  5903.             public static void Check(string address, ulong userid)
  5904.             {
  5905.                 if(!config.VPN.Check.Enabled || Permissions.Bypass.Vpn(userid) || string.IsNullOrEmpty(address))
  5906.                 {
  5907.                     return;
  5908.                 }
  5909.                 else if(Cache.IsBypassed(address))
  5910.                 {
  5911.                     return;
  5912.                 }
  5913.                 else if(Cache.IsBlocked(address))
  5914.                 {
  5915.                     Violation(address, userid, Text.GetPlain(Key.VpnCache));
  5916.                 }
  5917.  
  5918.                 checks.Enqueue(() => Check(address, userid, 6.0f));
  5919.             }
  5920.             private static void Check(string address, ulong userid, float delay)
  5921.             {
  5922.                 if(config.VPN.API.GetIpIntel.Enabled)
  5923.                 {
  5924.                     Check(ref delay, address, userid, API.GetIpIntel.Check);
  5925.                 }
  5926.  
  5927.                 if(config.VPN.API.IpApi.Enabled)
  5928.                 {
  5929.                     Check(ref delay, address, userid, API.IpApi.Check);
  5930.                 }
  5931.  
  5932.                 if(config.VPN.API.IpHub.Enabled)
  5933.                 {
  5934.                     Check(ref delay, address, userid, API.IpHub.Check);
  5935.                 }
  5936.  
  5937.                 if(config.VPN.API.IpQualityScore.Enabled)
  5938.                 {
  5939.                     Check(ref delay, address, userid, API.IpQualityScore.Check);
  5940.                 }
  5941.  
  5942.                 Check(ref delay, address, userid, Cache.Bypass);
  5943.             }
  5944.             private static void Check(ref float delay, string address, ulong userid, Action<string, ulong> callback)
  5945.             {
  5946.                 _instance.timer.In(delay, () =>
  5947.                 {
  5948.                     if(!Cache.IsBlocked(address))
  5949.                     {
  5950.                         callback(address, userid);
  5951.                     }
  5952.                 });
  5953.  
  5954.                 delay += 6.0f;
  5955.             }
  5956.  
  5957.             public static void Configure()
  5958.             {
  5959.                 violation.Configure(new Violation.Settings(true, 3600, 0.5f, config.VPN.Violation.Warn), 1, 1, 1);
  5960.  
  5961.                 API.Configure();
  5962.             }
  5963.  
  5964.             public static bool IsBlocked(string address) => Cache.IsBlocked(address);
  5965.  
  5966.             public static bool IsBypassed(string address) => Cache.IsBypassed(address);
  5967.  
  5968.             public static void Load()
  5969.             {
  5970.                 Configure();
  5971.  
  5972.                 Cache.Load();
  5973.  
  5974.                 checks = new ActionQueue(6.0f);
  5975.             }
  5976.  
  5977.             public static void Save() => Cache.Save();
  5978.  
  5979.             public static void Unblock(string address) => Cache.Unblock(address);
  5980.  
  5981.             public static void Unload()
  5982.             {
  5983.                 checks.Clear();
  5984.                 checks = null;
  5985.  
  5986.                 violation.Clear();
  5987.  
  5988.                 API.Unload();
  5989.                 Cache.Unload();
  5990.             }
  5991.  
  5992.             private static void Violation(string address, ulong userid, string api)
  5993.             {
  5994.                 Cache.Block(address);
  5995.  
  5996.                 if(!(config.VPN.Violation.Enabled && config.VPN.Violation.Warn))
  5997.                 {
  5998.                     IP.Block(address);
  5999.                 }
  6000.  
  6001.                 foreach(var entry in IP.Find(address))
  6002.                 {
  6003.                     if(!User.IsConnected(entry) || Permissions.Bypass.Vpn(entry))
  6004.                     {
  6005.                         continue;
  6006.                     }
  6007.  
  6008.                     if(config.VPN.Violation.Enabled)
  6009.                     {
  6010.                         ulong violations = config.VPN.Violation.Ban ? ulong.MaxValue : 1ul;
  6011.  
  6012.                         violation.Trigger(entry, Key.Detected, $"{api}, {address}", violations, true);
  6013.                     }
  6014.                     else
  6015.                     {
  6016.                         User.Kick(entry, Text.GetPlain(Key.VpnDetected));
  6017.                     }
  6018.                 }
  6019.             }
  6020.         }
  6021.  
  6022.         #endregion _vpn_
  6023.  
  6024.         #region _weapon_
  6025.  
  6026.         private class Weapon
  6027.         {
  6028.             public float        Accuracy { get; protected set; }
  6029.             public Vector3      AimAngle { get; protected set; }
  6030.             public float        AimCone { get; protected set; }
  6031.             public float        AimSway { get; protected set; }
  6032.             public string       AmmoName { get; protected set; }
  6033.             public List<string> Attachments { get; protected set; }
  6034.             public bool         Automatic { get; protected set; }
  6035.             public DateTime     Fired { get; protected set; }
  6036.             public string       Name { get; protected set; }
  6037.             public float        Pitch { get; protected set; }
  6038.             public BasePlayer   Player { get; protected set; }
  6039.             public Vector3      Position { get; protected set; }
  6040.             public List<int>    Projectiles { get; protected set; }
  6041.             public float        Range { get; protected set; }
  6042.             public float        Repeat { get; protected set; }
  6043.             public bool         Shell { get; protected set; }
  6044.             public string       ShortName { get; protected set; }
  6045.             public float        Speed { get; protected set; }
  6046.             public bool         Spread { get; protected set; }
  6047.             public float        Swing { get; protected set; }
  6048.             public float        Velocity { get; protected set; }
  6049.             public float        Yaw { get; protected set; }
  6050.             public float        Zoom { get; protected set; }
  6051.  
  6052.             private static readonly Queue<Weapon> pool = new Queue<Weapon>();
  6053.  
  6054.             // Ammo ID's
  6055.             private static readonly int ArrowBone = ItemManager.FindItemDefinition("arrow.bone").itemid;
  6056.             private static readonly int ArrowFire = ItemManager.FindItemDefinition("arrow.fire").itemid;
  6057.             private static readonly int ArrowHV = ItemManager.FindItemDefinition("arrow.hv").itemid;
  6058.             private static readonly int ArrowWooden = ItemManager.FindItemDefinition("arrow.wooden").itemid;
  6059.             private static readonly int GrenadeHE = ItemManager.FindItemDefinition("ammo.grenadelauncher.he").itemid;
  6060.             private static readonly int GrenadeShotgun = ItemManager.FindItemDefinition("ammo.grenadelauncher.buckshot").itemid;
  6061.             private static readonly int GrenadeSmoke = ItemManager.FindItemDefinition("ammo.grenadelauncher.smoke").itemid;
  6062.             private static readonly int NailgunNails = ItemManager.FindItemDefinition("ammo.nailgun.nails").itemid;
  6063.             private static readonly int PistolBullet = ItemManager.FindItemDefinition("ammo.pistol").itemid;
  6064.             private static readonly int PistolHV = ItemManager.FindItemDefinition("ammo.pistol.hv").itemid;
  6065.             private static readonly int PistolIncendiary = ItemManager.FindItemDefinition("ammo.pistol.fire").itemid;
  6066.             private static readonly int RifleAmmo = ItemManager.FindItemDefinition("ammo.rifle").itemid;
  6067.             private static readonly int RifleExplosive = ItemManager.FindItemDefinition("ammo.rifle.explosive").itemid;
  6068.             private static readonly int RifleHV = ItemManager.FindItemDefinition("ammo.rifle.hv").itemid;
  6069.             private static readonly int RifleIncendiary = ItemManager.FindItemDefinition("ammo.rifle.incendiary").itemid;
  6070.             private static readonly int Rocket = ItemManager.FindItemDefinition("ammo.rocket.basic").itemid;
  6071.             private static readonly int RocketHV = ItemManager.FindItemDefinition("ammo.rocket.hv").itemid;
  6072.             private static readonly int RocketIncendiary = ItemManager.FindItemDefinition("ammo.rocket.fire").itemid;
  6073.             private static readonly int ShellBuckshot = ItemManager.FindItemDefinition("ammo.shotgun").itemid;
  6074.             private static readonly int ShellHandmade = ItemManager.FindItemDefinition("ammo.handmade.shell").itemid;
  6075.             private static readonly int ShellIncendiary = ItemManager.FindItemDefinition("ammo.shotgun.fire").itemid;
  6076.             private static readonly int ShellSlug = ItemManager.FindItemDefinition("ammo.shotgun.slug").itemid;
  6077.  
  6078.             // Weapon ID's
  6079.             private static readonly int AssaultRifle = ItemManager.FindItemDefinition("rifle.ak").itemid;
  6080.             private static readonly int BoltActionRifle = ItemManager.FindItemDefinition("rifle.bolt").itemid;
  6081.             private static readonly int CompoundBow = ItemManager.FindItemDefinition("bow.compound").itemid;
  6082.             private static readonly int Crossbow = ItemManager.FindItemDefinition("crossbow").itemid;
  6083.             private static readonly int CustomSMG = ItemManager.FindItemDefinition("smg.2").itemid;
  6084.             private static readonly int DoubleBarrelShotgun = ItemManager.FindItemDefinition("shotgun.double").itemid;
  6085.             private static readonly int EokaPistol = ItemManager.FindItemDefinition("pistol.eoka").itemid;
  6086.             private static readonly int HuntingBow = ItemManager.FindItemDefinition("bow.hunting").itemid;
  6087.             private static readonly int L96Rifle = ItemManager.FindItemDefinition("rifle.l96").itemid;
  6088.             private static readonly int LR300AssaultRifle = ItemManager.FindItemDefinition("rifle.lr300").itemid;
  6089.             private static readonly int M249 = ItemManager.FindItemDefinition("lmg.m249").itemid;
  6090.             private static readonly int M39Rifle = ItemManager.FindItemDefinition("rifle.m39").itemid;
  6091.             private static readonly int M92Pistol = ItemManager.FindItemDefinition("pistol.m92").itemid;
  6092.             private static readonly int MP5A4 = ItemManager.FindItemDefinition("smg.mp5").itemid;
  6093.             private static readonly int MultipleGrenadeLauncher = ItemManager.FindItemDefinition("multiplegrenadelauncher").itemid;
  6094.             private static readonly int Nailgun = ItemManager.FindItemDefinition("pistol.nailgun").itemid;
  6095.             private static readonly int PumpShotgun = ItemManager.FindItemDefinition("shotgun.pump").itemid;
  6096.             private static readonly int PythonRevolver = ItemManager.FindItemDefinition("pistol.python").itemid;
  6097.             private static readonly int Revolver = ItemManager.FindItemDefinition("pistol.revolver").itemid;
  6098.             private static readonly int RocketLauncher = ItemManager.FindItemDefinition("rocket.launcher").itemid;
  6099.             private static readonly int SemiAutomaticPistol = ItemManager.FindItemDefinition("pistol.semiauto").itemid;
  6100.             private static readonly int SemiAutomaticRifle = ItemManager.FindItemDefinition("rifle.semiauto").itemid;
  6101.             private static readonly int Spas12Shotgun = ItemManager.FindItemDefinition("shotgun.spas12").itemid;
  6102.             private static readonly int Thompson = ItemManager.FindItemDefinition("smg.thompson").itemid;
  6103.             private static readonly int WaterpipeShotgun = ItemManager.FindItemDefinition("shotgun.waterpipe").itemid;
  6104.  
  6105.             private class Ammo
  6106.             {
  6107.                 public float AimCone { get; set; }
  6108.                 public float Range { get; set; }
  6109.                 public float Velocity { get; set; }
  6110.  
  6111.                 public Ammo(float aimcone, float range, float velocity)
  6112.                 {
  6113.                     AimCone = aimcone; Range = range; Velocity = velocity;
  6114.                 }
  6115.             }
  6116.  
  6117.             private class Info
  6118.             {
  6119.                 public float                 Accuracy { get; set; }
  6120.                 public Dictionary<int, Ammo> Ammo { get; set; }
  6121.                 public bool                  Automatic { get; set; }
  6122.                 public string                Name { get; set; }
  6123.                 public Recoil                Recoil { get; set; }
  6124.                 public float                 Repeat { get; set; }
  6125.             }
  6126.  
  6127.             private class Recoil
  6128.             {
  6129.                 public float Pitch { get; set; }
  6130.                 public float Yaw { get; set; }
  6131.  
  6132.                 public Recoil(float pitch, float yaw)
  6133.                 {
  6134.                     Pitch = pitch; Yaw = yaw;
  6135.                 }
  6136.             }
  6137.  
  6138.             private static Dictionary<int, Info> weapons;
  6139.  
  6140.             private Weapon()
  6141.             {
  6142.                 Attachments = new List<string>();
  6143.                 Projectiles = new List<int>();
  6144.             }
  6145.  
  6146.             private static Weapon Get()
  6147.             {
  6148.                 if(pool.Count > 0)
  6149.                 {
  6150.                     return pool.Dequeue();
  6151.                 }
  6152.                 else
  6153.                 {
  6154.                     return new Weapon();
  6155.                 }
  6156.             }
  6157.             public static Weapon Get(ulong userid, int projectileid) => Projectile.Weapon(userid, projectileid);
  6158.             public static Weapon Get(BaseProjectile weapon_fired, BasePlayer player, ProtoBuf.ProjectileShoot fired = null)
  6159.             {
  6160.                 var item = weapon_fired.GetItem();
  6161.  
  6162.                 Info info;
  6163.  
  6164.                 if(!weapons.TryGetValue(item.info.itemid, out info))
  6165.                 {
  6166.                     return null;
  6167.                 }
  6168.  
  6169.                 var ammo = weapon_fired.primaryMagazine.ammoType;
  6170.  
  6171.                 Ammo ammoinfo;
  6172.  
  6173.                 if(!info.Ammo.TryGetValue(ammo.itemid, out ammoinfo))
  6174.                 {
  6175.                     return null;
  6176.                 }
  6177.  
  6178.                 var accuracy = info.Accuracy;
  6179.  
  6180.                 var spread = (ammo.itemid == ShellBuckshot) || (ammo.itemid == ShellHandmade);
  6181.  
  6182.                 var position = weapon_fired.MuzzlePoint?.transform?.position ?? weapon_fired.transform?.position ?? player.transform.position;
  6183.  
  6184.                 var weapon = Get();
  6185.  
  6186.                 weapon.Accuracy   = 1.0f;
  6187.                 weapon.AimAngle   = player.eyes.HeadRay().direction;
  6188.                 weapon.AimCone    = ammoinfo.AimCone;
  6189.                 weapon.AimSway    = 1.0f;
  6190.                 weapon.AmmoName   = ammo.shortname;
  6191.                 weapon.Automatic  = info.Automatic;
  6192.                 weapon.Fired      = DateTime.UtcNow;
  6193.                 weapon.Name       = info.Name;
  6194.                 weapon.Pitch      = info.Recoil.Pitch * (info.Automatic ? 0.25f : 0.5f);
  6195.                 weapon.Player     = player;
  6196.                 weapon.Position   = position;
  6197.                 weapon.Range      = ammoinfo.Range;
  6198.                 weapon.Repeat     = info.Repeat;
  6199.                 weapon.Shell      = spread || (ammo.itemid == ShellIncendiary) || (ammo.itemid == ShellSlug);
  6200.                 weapon.ShortName  = item.info.shortname;
  6201.                 weapon.Speed      = player.estimatedSpeed;
  6202.                 weapon.Spread     = spread || (ammo.itemid == GrenadeShotgun);
  6203.                 weapon.Swing      = 0.0f;
  6204.                 weapon.Velocity   = ammoinfo.Velocity;
  6205.                 weapon.Yaw        = info.Recoil.Yaw * (info.Automatic ? 0.125f : 0.25f); ;
  6206.                 weapon.Zoom       = 1.0f;
  6207.  
  6208.                 var aimsway = 1.0f;
  6209.  
  6210.                 foreach(var entry in player.inventory.containerWear.itemList)
  6211.                 {
  6212.                     if(entry.info.itemid == -1108136649) // Tactical Gloves
  6213.                     {
  6214.                         aimsway = 0.2f;
  6215.  
  6216.                         break;
  6217.                     }
  6218.                 }
  6219.  
  6220.                 var aiming = player.IsAiming;
  6221.  
  6222.                 var ducked = player.IsDucked();
  6223.  
  6224.                 var recoil = ducked ? 0.9f : 1.0f;
  6225.  
  6226.                 if(item?.contents?.itemList != null)
  6227.                 {
  6228.                     bool muzzle_brake = false;
  6229.  
  6230.                     foreach(var mod in item.contents.itemList)
  6231.                     {
  6232.                         switch(mod.info.shortname)
  6233.                         {
  6234.                         case "weapon.mod.8x.scope":
  6235.                             accuracy *= 0.85f;
  6236.                             recoil -= 0.2f;
  6237.                             weapon.AimCone *= 0.7f;
  6238.                             weapon.Zoom = aiming ? 16.0f : 1.0f;
  6239.                             break;
  6240.                         case "weapon.mod.small.scope":
  6241.                             accuracy *= 0.85f;
  6242.                             recoil -= 0.2f;
  6243.                             weapon.AimCone *= 0.7f;
  6244.                             weapon.Zoom = aiming ? 8.0f : 1.0f;
  6245.                             break;
  6246.                         case "weapon.mod.holosight":
  6247.                             accuracy *= 0.3f;
  6248.                             weapon.AimCone *= 0.3f;
  6249.                             weapon.Zoom = aiming ? 2.0f : 1.0f;
  6250.                             break;
  6251.                         case "weapon.mod.muzzleboost":
  6252.                             weapon.Range *= 0.9f;
  6253.                             weapon.Repeat *= 0.9f;
  6254.                             weapon.Velocity *= 0.9f;
  6255.                             break;
  6256.                         case "weapon.mod.muzzlebrake":
  6257.                             accuracy *= 1.38f;
  6258.                             muzzle_brake = true;
  6259.                             recoil -= 0.5f;
  6260.                             break;
  6261.                         case "weapon.mod.silencer":
  6262.                             accuracy *= 0.67f;
  6263.                             aimsway -= 0.2f;
  6264.                             recoil -= 0.2f;
  6265.                             weapon.AimCone *= 0.7f;
  6266.                             break;
  6267.                         case "weapon.mod.simplesight":
  6268.                             weapon.Zoom = aiming ? 0.5f : 1.0f;
  6269.                             break;
  6270.                         case "weapon.mod.lasersight":
  6271.                             accuracy *= 0.56f;
  6272.                             aimsway -= 0.9f;
  6273.                             weapon.AimCone *= aiming ? 0.8f : 0.6f;
  6274.                             break;
  6275.                         }
  6276.  
  6277.                         weapon.Attachments.Add(mod.info.shortname);
  6278.                     }
  6279.  
  6280.                     if(muzzle_brake)
  6281.                     {
  6282.                         weapon.AimCone += aiming ? 0.5f : 2.0f;
  6283.                     }
  6284.                 }
  6285.  
  6286.                 weapon.Accuracy -= accuracy;
  6287.                 weapon.AimSway  *= (aimsway > 0.0f) ? aimsway : 0.0f;
  6288.                 weapon.Pitch    *= recoil;
  6289.                 weapon.Yaw      *= recoil;
  6290.  
  6291.                 if((fired?.projectiles?.Count ?? 0) > 0)
  6292.                 {
  6293.                     foreach(var projectile in fired.projectiles)
  6294.                     {
  6295.                         weapon.Projectiles.Add(projectile.projectileID);
  6296.                     }
  6297.  
  6298.                     Projectile.Add(weapon);
  6299.                 }
  6300.  
  6301.                 return weapon;
  6302.             }
  6303.  
  6304.             public static bool IsValid(Item item) => weapons.ContainsKey(item?.info?.itemid ?? 0);
  6305.  
  6306.             public static void Load()
  6307.             {
  6308.                 weapons = new Dictionary<int, Info>
  6309.                 {
  6310.                     [AssaultRifle] = new Info
  6311.                     {
  6312.                         Accuracy = 0.04f,
  6313.                         Ammo = new Dictionary<int, Ammo>
  6314.                         {
  6315.                             { RifleAmmo,       new Ammo(0.2f, 187.5f, 375.0f) },
  6316.                             { RifleExplosive,  new Ammo(0.5f, 112.5f, 225.0f) },
  6317.                             { RifleHV,         new Ammo(0.2f, 225.0f, 450.0f) },
  6318.                             { RifleIncendiary, new Ammo(0.2f, 112.5f, 225.0f) }
  6319.                         },
  6320.                         Automatic = true,
  6321.                         Name = "Assault Rifle",
  6322.                         Recoil = new Recoil(13.000000f, 5.000000f),
  6323.                         Repeat = 0.1333f
  6324.                     },
  6325.                     [BoltActionRifle] = new Info
  6326.                     {
  6327.                         Accuracy = 0.02f,
  6328.                         Ammo = new Dictionary<int, Ammo>
  6329.                         {
  6330.                             { RifleAmmo,       new Ammo(0.0f, 328.0f, 656.0f) },
  6331.                             { RifleExplosive,  new Ammo(0.5f, 197.0f, 394.0f) },
  6332.                             { RifleHV,         new Ammo(0.0f, 394.0f, 788.0f) },
  6333.                             { RifleIncendiary, new Ammo(0.0f, 197.0f, 394.0f) }
  6334.                         },
  6335.                         Automatic = false,
  6336.                         Name = "Bolt Action Rifle",
  6337.                         Recoil = new Recoil(0.500000f, 4.000000f),
  6338.                         Repeat = 1.7f
  6339.                     },
  6340.                     [CompoundBow] = new Info
  6341.                     {
  6342.                         Accuracy = 0.00f,
  6343.                         Ammo = new Dictionary<int, Ammo>
  6344.                         {
  6345.                             { ArrowBone,   new Ammo(0.2f, 270.0f,  90.0f) },
  6346.                             { ArrowFire,   new Ammo(0.2f, 240.0f,  80.0f) },
  6347.                             { ArrowHV,     new Ammo(0.2f, 480.0f, 160.0f) },
  6348.                             { ArrowWooden, new Ammo(0.2f, 300.0f, 100.0f) }
  6349.                         },
  6350.                         Automatic = false,
  6351.                         Name = "Compound Bow",
  6352.                         Recoil = new Recoil(1.500000f, 3.000000f),
  6353.                         Repeat = 1.25f
  6354.                     },
  6355.                     [Crossbow] = new Info
  6356.                     {
  6357.                         Accuracy = 0.03f,
  6358.                         Ammo = new Dictionary<int, Ammo>
  6359.                         {
  6360.                             { ArrowBone,   new Ammo(1.0f, 202.5f,  67.5f) },
  6361.                             { ArrowFire,   new Ammo(1.0f, 180.0f,  60.0f) },
  6362.                             { ArrowHV,     new Ammo(1.0f, 360.0f, 120.0f) },
  6363.                             { ArrowWooden, new Ammo(1.0f, 225.0f,  75.0f) }
  6364.                         },
  6365.                         Automatic = false,
  6366.                         Name = "Crossbow",
  6367.                         Recoil = new Recoil(1.500000f, 3.000000f),
  6368.                         Repeat = 1.0f
  6369.                     },
  6370.                     [CustomSMG] = new Info
  6371.                     {
  6372.                         Accuracy = 0.03f,
  6373.                         Ammo = new Dictionary<int, Ammo>
  6374.                         {
  6375.                             { PistolBullet,     new Ammo(0.5f, 200.0f, 240.0f) },
  6376.                             { PistolHV,         new Ammo(0.5f, 266.7f, 320.0f) },
  6377.                             { PistolIncendiary, new Ammo(0.5f, 150.0f, 180.0f) }
  6378.                         },
  6379.                         Automatic = true,
  6380.                         Name = "Custom SMG",
  6381.                         Recoil = new Recoil(6.500000f, 5.7500000f),
  6382.                         Repeat = 0.1f
  6383.                     },
  6384.                     [DoubleBarrelShotgun] = new Info
  6385.                     {
  6386.                         Accuracy = 0.15f,
  6387.                         Ammo = new Dictionary<int, Ammo>
  6388.                         {
  6389.                             { ShellBuckshot,   new Ammo(12.5f, 45.0f, 225.0f) },
  6390.                             { ShellHandmade,   new Ammo(12.5f, 30.0f, 100.0f) },
  6391.                             { ShellIncendiary, new Ammo(12.5f, 25.0f, 100.0f) },
  6392.                             { ShellSlug,       new Ammo( 0.5f, 60.0f, 225.0f) }
  6393.                         },
  6394.                         Automatic = false,
  6395.                         Name = "Double Barrel Shotgun",
  6396.                         Recoil = new Recoil(2.500000f, 3.500000f),
  6397.                         Repeat = 0.5f
  6398.                     },
  6399.                     [EokaPistol] = new Info
  6400.                     {
  6401.                         Accuracy = 0.17f,
  6402.                         Ammo = new Dictionary<int, Ammo>
  6403.                         {
  6404.                             { ShellBuckshot,   new Ammo(14.0f, 45.0f, 225.0f) },
  6405.                             { ShellHandmade,   new Ammo(14.0f, 30.0f, 100.0f) },
  6406.                             { ShellIncendiary, new Ammo(14.0f, 25.0f, 100.0f) },
  6407.                             { ShellSlug,       new Ammo( 2.0f, 60.0f, 225.0f) }
  6408.                         },
  6409.                         Automatic = false,
  6410.                         Name = "Eoka Pistol",
  6411.                         Recoil = new Recoil(15.00000f, 10.00000f),
  6412.                         Repeat = 1.5f
  6413.                     },
  6414.                     [HuntingBow] = new Info
  6415.                     {
  6416.                         Accuracy = 0.02f,
  6417.                         Ammo = new Dictionary<int, Ammo>
  6418.                         {
  6419.                             { ArrowBone,   new Ammo(1.0f, 135.0f, 45.0f) },
  6420.                             { ArrowFire,   new Ammo(1.0f, 120.0f, 40.0f) },
  6421.                             { ArrowHV,     new Ammo(1.0f, 240.0f, 80.0f) },
  6422.                             { ArrowWooden, new Ammo(1.0f, 150.0f, 50.0f) }
  6423.                         },
  6424.                         Automatic = false,
  6425.                         Name = "Hunting Bow",
  6426.                         Recoil = new Recoil(1.500000f, 3.000000f),
  6427.                         Repeat = 1.25f
  6428.                     },
  6429.                     [L96Rifle] = new Info
  6430.                     {
  6431.                         Accuracy = 0.02f,
  6432.                         Ammo = new Dictionary<int, Ammo>
  6433.                         {
  6434.                             { RifleAmmo,       new Ammo(0.0f, 562.5f, 1125.0f) },
  6435.                             { RifleExplosive,  new Ammo(0.5f, 337.5f, 675.0f) },
  6436.                             { RifleHV,         new Ammo(0.0f, 675.0f, 1350.0f) },
  6437.                             { RifleIncendiary, new Ammo(0.0f, 337.5f, 675.0f) }
  6438.                         },
  6439.                         Automatic = false,
  6440.                         Name = "L96 Rifle",
  6441.                         Recoil = new Recoil(0.250000f, 2.000000f),
  6442.                         Repeat = 2.6f
  6443.                     },
  6444.                     [LR300AssaultRifle] = new Info
  6445.                     {
  6446.                         Accuracy = 0.04f,
  6447.                         Ammo = new Dictionary<int, Ammo>
  6448.                         {
  6449.                             { RifleAmmo,       new Ammo(0.2f, 187.5f, 375.0f) },
  6450.                             { RifleExplosive,  new Ammo(0.7f, 112.5f, 225.0f) },
  6451.                             { RifleHV,         new Ammo(0.2f, 225.0f, 450.0f) },
  6452.                             { RifleIncendiary, new Ammo(0.2f, 112.5f, 225.0f) }
  6453.                         },
  6454.                         Automatic = true,
  6455.                         Name = "LR-300 Assault Rifle",
  6456.                         Recoil = new Recoil(4.750000f, 3.000000f),
  6457.                         Repeat = 0.12f
  6458.                     },
  6459.                     [M249] = new Info
  6460.                     {
  6461.                         Accuracy = 0.07f,
  6462.                         Ammo = new Dictionary<int, Ammo>
  6463.                         {
  6464.                             { RifleAmmo,       new Ammo(0.2f, 243.75f, 487.5f) },
  6465.                             { RifleExplosive,  new Ammo(0.7f, 146.25f, 292.5f) },
  6466.                             { RifleHV,         new Ammo(0.2f, 292.50f, 585.0f) },
  6467.                             { RifleIncendiary, new Ammo(0.2f, 146.25f, 292.5f) }
  6468.                         },
  6469.                         Automatic = true,
  6470.                         Name = "M249",
  6471.                         Recoil = new Recoil(0.500000f, 1.000000f),
  6472.                         Repeat = 0.12f
  6473.                     },
  6474.                     [M39Rifle] = new Info
  6475.                     {
  6476.                         Accuracy = 0.04f,
  6477.                         Ammo = new Dictionary<int, Ammo>
  6478.                         {
  6479.                             { RifleAmmo,       new Ammo(0.1f, 234.375f, 468.75f) },
  6480.                             { RifleExplosive,  new Ammo(0.6f, 140.625f, 281.25f) },
  6481.                             { RifleHV,         new Ammo(0.1f, 281.250f, 562.50f) },
  6482.                             { RifleIncendiary, new Ammo(0.1f, 140.625f, 281.25f) }
  6483.                         },
  6484.                         Automatic = false,
  6485.                         Name = "M39 Rifle",
  6486.                         Recoil = new Recoil(1.000000f, 1.500000f),
  6487.                         Repeat = 0.2f
  6488.                     },
  6489.                     [M92Pistol] = new Info
  6490.                     {
  6491.                         Accuracy = 0.04f,
  6492.                         Ammo = new Dictionary<int, Ammo>
  6493.                         {
  6494.                             { PistolBullet,     new Ammo(1.0f,  90.0f, 300.0f) },
  6495.                             { PistolHV,         new Ammo(1.0f, 120.0f, 400.0f) },
  6496.                             { PistolIncendiary, new Ammo(1.0f,  67.5f, 225.0f) }
  6497.                         },
  6498.                         Automatic = false,
  6499.                         Name = "M92 Pistol",
  6500.                         Recoil = new Recoil(0.500000f, 1.000000f),
  6501.                         Repeat = 0.15f
  6502.                     },
  6503.                     [MP5A4] = new Info
  6504.                     {
  6505.                         Accuracy = 0.05f,
  6506.                         Ammo = new Dictionary<int, Ammo>
  6507.                         {
  6508.                             { PistolBullet,     new Ammo(0.5f, 200.0f, 240.0f) },
  6509.                             { PistolHV,         new Ammo(0.5f, 266.7f, 320.0f) },
  6510.                             { PistolIncendiary, new Ammo(0.5f, 150.0f, 180.0f) }
  6511.                         },
  6512.                         Automatic = true,
  6513.                         Name = "MP5A4",
  6514.                         Recoil = new Recoil(4.000000f, 3.625000f),
  6515.                         Repeat = 0.1f
  6516.                     },
  6517.                     [MultipleGrenadeLauncher] = new Info
  6518.                     {
  6519.                         Accuracy = 0.04f,
  6520.                         Ammo = new Dictionary<int, Ammo>
  6521.                         {
  6522.                             { GrenadeHE,      new Ammo( 2.25f, 100.0f, 100.0f) },
  6523.                             { GrenadeShotgun, new Ammo(17.25f, 225.0f, 225.0f) },
  6524.                             { GrenadeSmoke,   new Ammo( 2.25f, 100.0f, 100.0f) }
  6525.                         },
  6526.                         Automatic = false,
  6527.                         Name = "Multiple Grenade Launcher",
  6528.                         Recoil = new Recoil(2.500000f, 2.500000f),
  6529.                         Repeat = 0.4f
  6530.                     },
  6531.                     [Nailgun] = new Info
  6532.                     {
  6533.                         Accuracy = 0.04f,
  6534.                         Ammo = new Dictionary<int, Ammo>
  6535.                         {
  6536.                             { NailgunNails, new Ammo(0.75f, 25.0f, 50.0f) }
  6537.                         },
  6538.                         Automatic = false,
  6539.                         Name = "Nailgun",
  6540.                         Recoil = new Recoil(1.500000f, 1.000000f),
  6541.                         Repeat = 0.15f
  6542.                     },
  6543.                     [PumpShotgun] = new Info
  6544.                     {
  6545.                         Accuracy = 0.14f,
  6546.                         Ammo = new Dictionary<int, Ammo>
  6547.                         {
  6548.                             { ShellBuckshot,   new Ammo(12.0f, 101.25f, 225.0f) },
  6549.                             { ShellHandmade,   new Ammo(12.0f,  60.00f, 100.0f) },
  6550.                             { ShellIncendiary, new Ammo(12.0f,  45.00f, 100.0f) },
  6551.                             { ShellSlug,       new Ammo( 0.0f, 135.00f, 225.0f) }
  6552.                         },
  6553.                         Automatic = false,
  6554.                         Name = "Pump Shotgun",
  6555.                         Recoil = new Recoil(2.000000f, 2.000000f),
  6556.                         Repeat = 1.1f
  6557.                     },
  6558.                     [PythonRevolver] = new Info
  6559.                     {
  6560.                         Accuracy = 0.06f,
  6561.                         Ammo = new Dictionary<int, Ammo>
  6562.                         {
  6563.                             { PistolBullet,     new Ammo(0.5f, 72.0f, 300.0f) },
  6564.                             { PistolHV,         new Ammo(0.5f, 96.0f, 400.0f) },
  6565.                             { PistolIncendiary, new Ammo(0.5f, 54.0f, 225.0f) }
  6566.                         },
  6567.                         Automatic = false,
  6568.                         Name = "Python Revolver",
  6569.                         Recoil = new Recoil(0.500000f, 2.000000f),
  6570.                         Repeat = 0.15f
  6571.                     },
  6572.                     [Revolver] = new Info
  6573.                     {
  6574.                         Accuracy = 0.05f,
  6575.                         Ammo = new Dictionary<int, Ammo>
  6576.                         {
  6577.                             { PistolBullet,     new Ammo(0.75f, 54.0f, 300.0f) },
  6578.                             { PistolHV,         new Ammo(0.75f, 72.0f, 400.0f) },
  6579.                             { PistolIncendiary, new Ammo(0.75f, 40.5f, 225.0f) }
  6580.                         },
  6581.                         Automatic = false,
  6582.                         Name = "Revolver",
  6583.                         Recoil = new Recoil(1.500000f, 1.000000f),
  6584.                         Repeat = 0.175f
  6585.                     },
  6586.                     [RocketLauncher] = new Info
  6587.                     {
  6588.                         Accuracy = 0.04f,
  6589.                         Ammo = new Dictionary<int, Ammo>
  6590.                         {
  6591.                             { Rocket,           new Ammo(2.25f, 250.0f, 18.0f) },
  6592.                             { RocketHV,         new Ammo(2.25f, 400.0f, 40.0f) },
  6593.                             { RocketIncendiary, new Ammo(2.25f, 250.0f, 18.0f) }
  6594.                         },
  6595.                         Automatic = false,
  6596.                         Name = "Rocket Launcher",
  6597.                         Recoil = new Recoil(2.500000f, 2.500000f),
  6598.                         Repeat = 2.0f
  6599.                     },
  6600.                     [SemiAutomaticPistol] = new Info
  6601.                     {
  6602.                         Accuracy = 0.04f,
  6603.                         Ammo = new Dictionary<int, Ammo>
  6604.                         {
  6605.                             { PistolBullet,     new Ammo(0.75f, 54.0f, 300.0f) },
  6606.                             { PistolHV,         new Ammo(0.75f, 72.0f, 400.0f) },
  6607.                             { PistolIncendiary, new Ammo(0.75f, 40.5f, 225.0f) }
  6608.                         },
  6609.                         Automatic = false,
  6610.                         Name = "Semi-Automatic Pistol",
  6611.                         Recoil = new Recoil(1.000000f, 2.000000f),
  6612.                         Repeat = 0.15f
  6613.                     },
  6614.                     [SemiAutomaticRifle] = new Info
  6615.                     {
  6616.                         Accuracy = 0.04f,
  6617.                         Ammo = new Dictionary<int, Ammo>
  6618.                         {
  6619.                             { RifleAmmo,       new Ammo(0.25f, 187.5f, 375.0f) },
  6620.                             { RifleExplosive,  new Ammo(0.75f, 112.5f, 225.0f) },
  6621.                             { RifleHV,         new Ammo(0.25f, 225.0f, 450.0f) },
  6622.                             { RifleIncendiary, new Ammo(0.25f, 112.5f, 225.0f) }
  6623.                         },
  6624.                         Automatic = false,
  6625.                         Name = "Semi-Automatic Rifle",
  6626.                         Recoil = new Recoil(0.500000f, 1.000000f),
  6627.                         Repeat = 0.175f
  6628.                     },
  6629.                     [Spas12Shotgun] = new Info
  6630.                     {
  6631.                         Accuracy = 0.14f,
  6632.                         Ammo = new Dictionary<int, Ammo>
  6633.                         {
  6634.                             { ShellBuckshot,   new Ammo(12.0f, 101.25f, 225.0f) },
  6635.                             { ShellHandmade,   new Ammo(12.0f,  60.00f, 100.0f) },
  6636.                             { ShellIncendiary, new Ammo(12.0f,  45.00f, 100.0f) },
  6637.                             { ShellSlug,       new Ammo( 0.0f, 135.00f, 225.0f) }
  6638.                         },
  6639.                         Automatic = false,
  6640.                         Name = "Spas-12 Shotgun",
  6641.                         Recoil = new Recoil(2.000000f, 2.000000f),
  6642.                         Repeat = 0.25f
  6643.                     },
  6644.                     [Thompson] = new Info
  6645.                     {
  6646.                         Accuracy = 0.03f,
  6647.                         Ammo = new Dictionary<int, Ammo>
  6648.                         {
  6649.                             { PistolBullet,     new Ammo(0.5f, 250.0f, 300.0f) },
  6650.                             { PistolHV,         new Ammo(0.5f, 333.3f, 400.0f) },
  6651.                             { PistolIncendiary, new Ammo(0.5f, 187.5f, 225.0f) }
  6652.                         },
  6653.                         Automatic = true,
  6654.                         Name = "Thompson",
  6655.                         Recoil = new Recoil(6.500000f, 5.750000f),
  6656.                         Repeat = 0.13f
  6657.                     },
  6658.                     [WaterpipeShotgun] = new Info
  6659.                     {
  6660.                         Accuracy = 0.15f,
  6661.                         Ammo = new Dictionary<int, Ammo>
  6662.                         {
  6663.                             { ShellBuckshot,   new Ammo(13.0f, 45.0f, 225.0f) },
  6664.                             { ShellHandmade,   new Ammo(13.0f, 30.0f, 100.0f) },
  6665.                             { ShellIncendiary, new Ammo(13.0f, 25.0f, 100.0f) },
  6666.                             { ShellSlug,       new Ammo( 1.0f, 60.0f, 225.0f) }
  6667.                         },
  6668.                         Automatic = false,
  6669.                         Name = "Waterpipe Shotgun",
  6670.                         Recoil = new Recoil(2.000000f, 2.000000f),
  6671.                         Repeat = 2.0f
  6672.                     }
  6673.                 };
  6674.             }
  6675.  
  6676.             public void Release()
  6677.             {
  6678.                 Attachments.Clear();
  6679.                 Projectiles.Clear();
  6680.  
  6681.                 pool.Enqueue(this);
  6682.             }
  6683.  
  6684.             public void SetSwing(float amount)
  6685.             {
  6686.                 Swing = amount;
  6687.             }
  6688.  
  6689.             public static void Unload()
  6690.             {
  6691.                 pool.Clear();
  6692.  
  6693.                 weapons.Clear();
  6694.                 weapons = null;
  6695.             }
  6696.         }
  6697.  
  6698.         #endregion _weapon_
  6699.  
  6700.         #region _webhook_
  6701.  
  6702.         private class WebHook
  6703.         {
  6704.             public static void Send(string url, string category, string message)
  6705.             {
  6706.                 if(string.IsNullOrEmpty(url) || string.IsNullOrEmpty(message))
  6707.                 {
  6708.                     return;
  6709.                 }
  6710.  
  6711.                 if(string.IsNullOrWhiteSpace(category))
  6712.                 {
  6713.                     category = Text.GetPlain(Key.unknown);
  6714.                 }
  6715.  
  6716.                 _instance.webrequest.Enqueue(url, message, (code, reply) =>
  6717.                 {
  6718.                     if((code < 200) || (204 < code))
  6719.                     {
  6720.                         Log.Console(Key.LogWebHook, new Dictionary<string, string>
  6721.                         {
  6722.                             { "category", category },
  6723.                             { "info", $"({code}: {reply})" },
  6724.                             { "type", "http" }
  6725.                         });
  6726.                     }
  6727.                 }, _instance, RequestMethod.POST, new Dictionary<string, string> { { "Content-Type", "application/json" } });
  6728.             }
  6729.         }
  6730.  
  6731.         #endregion _webhook_
  6732.     }
  6733. }
  6734.  
Add Comment
Please, Sign In to add comment