Gattaca1996

Guardian.cs (Part 1)

Sep 4th, 2026 (edited)
44
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 235.85 KB | Gaming | 0 0
  1. // ===== Guardian 1.7.10  PASTEBIN PART 1 of 2 =====
  2. // Free Pastebin limit is 512 KB. This is the first half (through #endregion _log_).
  3. // Join: paste PART 1, then paste PART 2 immediately below it. Delete these header lines (optional; they are comments).
  4. using Facepunch;
  5. using Facepunch.Math;
  6. using Network;
  7. using Newtonsoft.Json;
  8. using Oxide.Core;
  9. using Oxide.Core.Libraries;
  10. using Oxide.Core.Libraries.Covalence;
  11. using Oxide.Core.Plugins;
  12. using Rust;
  13. using System;
  14. using System.Collections.Generic;
  15. using System.Text;
  16. using System.Text.RegularExpressions;
  17. using UnityEngine;
  18.  
  19. namespace Oxide.Plugins
  20. {
  21.     [Info("Guardian", "WhiteDragon / Gattaca", "1.7.10")]
  22.     [Description("Protects the server from various annoyances, cheats, and macro attacks.")]
  23.     class Guardian : CovalencePlugin
  24.     {
  25.         [PluginReference] private Plugin Friends, PlaytimeTracker;
  26.  
  27.         private static Guardian _instance;
  28.  
  29.         #region _action_queue_
  30.  
  31.         private class ActionQueue
  32.         {
  33.             private Queue<Action> actions;
  34.  
  35.             public ActionQueue(float interval)
  36.             {
  37.                 actions = new Queue<Action>();
  38.  
  39.                 Timers.Add(interval, () => Scan());
  40.             }
  41.  
  42.             public void Clear() => actions.Clear();
  43.  
  44.             public void Enqueue(Action callback)
  45.             {
  46.                 if(callback != null)
  47.                 {
  48.                     actions.Enqueue(callback);
  49.                 }
  50.             }
  51.  
  52.             private void Scan()
  53.             {
  54.                 if(actions.Count > 0)
  55.                 {
  56.                     actions.Dequeue()?.Invoke();
  57.                 }
  58.             }
  59.         }
  60.  
  61.         #endregion _action_queue_
  62.  
  63.         #region _admin_
  64.  
  65.         private class Admin
  66.         {
  67.             public class Settings
  68.             {
  69.                 public bool Broadcast;
  70.                 public bool Bypass;
  71.  
  72.                 public Settings()
  73.                 {
  74.                     Broadcast = true;
  75.                     Bypass    = true;
  76.                 }
  77.             }
  78.         }
  79.  
  80.         #endregion _admin_
  81.  
  82.         #region _anticheat_
  83.  
  84.         private class AntiCheat
  85.         {
  86.             public class Settings
  87.             {
  88.                 public Aim.Settings        Aim;
  89.                 public FireRate.Settings   FireRate;
  90.                 public Gravity.Settings    Gravity;
  91.                 public MeleeRate.Settings  MeleeRate;
  92.                 public Recoil.Settings     Recoil;
  93.                 public Server.Settings     Server;
  94.                 public Stash.Settings      Stash;
  95.                 public Trajectory.Settings Trajectory;
  96.                 public WallHack.Settings   WallHack;
  97.  
  98.                 public Settings()
  99.                 {
  100.                     Aim        = new Aim.Settings();
  101.                     FireRate   = new FireRate.Settings();
  102.                     Gravity    = new Gravity.Settings();
  103.                     MeleeRate  = new MeleeRate.Settings();
  104.                     Recoil     = new Recoil.Settings();
  105.                     Server     = new Server.Settings();
  106.                     Stash      = new Stash.Settings();
  107.                     Trajectory = new Trajectory.Settings();
  108.                     WallHack   = new WallHack.Settings();
  109.                 }
  110.  
  111.                 public void Validate()
  112.                 {
  113.                     Configuration.Validate(ref Aim,        () => new Aim.Settings(), () => Aim.Validate());
  114.                     Configuration.Validate(ref FireRate,   () => new FireRate.Settings());
  115.                     Configuration.Validate(ref Gravity,    () => new Gravity.Settings());
  116.                     Configuration.Validate(ref MeleeRate,  () => new MeleeRate.Settings());
  117.                     Configuration.Validate(ref Recoil,     () => new Recoil.Settings());
  118.                     Configuration.Validate(ref Server,     () => new Server.Settings());
  119.                     Configuration.Validate(ref Stash,      () => new Stash.Settings());
  120.                     Configuration.Validate(ref Trajectory, () => new Trajectory.Settings());
  121.                     Configuration.Validate(ref WallHack,   () => new WallHack.Settings());
  122.                 }
  123.             }
  124.  
  125.             private const Key category = Key.AntiCheat;
  126.  
  127.             private const float epsilon = 9.5367431640625E-7f;
  128.  
  129.             public static void Configure()
  130.             {
  131.                 Aim.Configure();
  132.                 FireRate.Configure();
  133.                 Gravity.Configure();
  134.                 MeleeRate.Configure();
  135.                 Recoil.Configure();
  136.                 Server.Configure();
  137.                 Stash.Configure();
  138.                 Trajectory.Configure();
  139.                 WallHack.Configure();
  140.  
  141.                 Gravity.Scan();
  142.                 Server.Scan();
  143.             }
  144.  
  145.             public static void Unload()
  146.             {
  147.                 Aim.Unload();
  148.                 FireRate.Unload();
  149.                 Gravity.Unload();
  150.                 MeleeRate.Unload();
  151.                 Recoil.Unload();
  152.                 Server.Unload();
  153.                 Stash.Unload();
  154.                 Trajectory.Unload();
  155.                 WallHack.Unload();
  156.             }
  157.  
  158.             public class Aim
  159.             {
  160.                 public class Settings
  161.                 {
  162.                     public bool       Ban;
  163.                     public ulong      Cooldown;
  164.                     public bool       Enabled;
  165.                     public float      Sensitivity;
  166.                     public AimTrigger Trigger;
  167.                     public bool       Warn;
  168.  
  169.                     public Settings()
  170.                     {
  171.                         Ban         = false;
  172.                         Cooldown    = 300;
  173.                         Enabled     = true;
  174.                         Sensitivity = 0.5f;
  175.                         Trigger     = new AimTrigger();
  176.                         Warn        = true;
  177.                     }
  178.  
  179.                     public class AimTrigger
  180.                     {
  181.                         public bool Animal;
  182.                         public bool Bradley;
  183.                         public bool Helicopter;
  184.                         public bool NPC;
  185.                     }
  186.  
  187.                     public void Validate()
  188.                     {
  189.                         Configuration.Validate(ref Trigger, () => new AimTrigger());
  190.                     }
  191.                     public Violation.Settings Validate(ulong max)
  192.                     {
  193.                         Configuration.Clamp(ref Cooldown,     1ul,  max);
  194.                         Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  195.  
  196.                         return new Violation.Settings(Ban, Cooldown, Sensitivity, Warn);
  197.                     }
  198.                 }
  199.  
  200.                 private const Key type = Key.AntiCheatAim;
  201.  
  202.                 private static readonly Violation violation = new Violation(category);
  203.  
  204.                 private static float aim_distance;
  205.                 private static float headshot_scale;
  206.                 private static float hit_scale;
  207.                 private static float pvp_distance;
  208.  
  209.                 private static float sensitivity_lo;
  210.                 private static float sensitivity_hi;
  211.  
  212.                 private static float spin_angle;
  213.  
  214.                 private static float swing_angle;
  215.  
  216.                 private class History
  217.                 {
  218.                     public HitArea      boneArea;
  219.                     public Counter      headshot;
  220.                     public Counter      hit;
  221.                     public HashSet<int> hits;
  222.                     public Counter      repeat;
  223.  
  224.                     private static readonly Dictionary<ulong, History> histories = new Dictionary<ulong, History>();
  225.  
  226.                     private History()
  227.                     {
  228.                         boneArea = 0;
  229.                         headshot = new Counter();
  230.                         hit      = new Counter();
  231.                         hits     = new HashSet<int>();
  232.                         repeat   = new Counter();
  233.                     }
  234.  
  235.                     public static void Clear() => histories.Clear();
  236.  
  237.                     public static bool Contains(ulong userid) => histories.ContainsKey(userid);
  238.  
  239.                     public static History Get(ulong userid)
  240.                     {
  241.                         History history;
  242.  
  243.                         if(!histories.TryGetValue(userid, out history))
  244.                         {
  245.                             histories.Add(userid, history = new History());
  246.                         }
  247.  
  248.                         return history;
  249.                     }
  250.                 }
  251.  
  252.                 public static void Configure()
  253.                 {
  254.                     var settings = config.AntiCheat.Aim.Validate(900);
  255.  
  256.                     violation.Configure(settings, 2, 8, 900000);
  257.  
  258.                     aim_distance   =  6.00f + ( 6.0f * (1.0f - settings.Sensitivity));
  259.                     headshot_scale =  1.50f +                  settings.Sensitivity;
  260.                     hit_scale      =  0.25f +                  settings.Sensitivity;
  261.                     pvp_distance   = 12.00f + (12.0f * (1.0f - settings.Sensitivity));
  262.                     sensitivity_lo = -0.50f + ( 0.5f *         settings.Sensitivity);
  263.                     sensitivity_hi = -1.00f - ( 0.5f * (1.0f - settings.Sensitivity));
  264.                     spin_angle     = 35.00f + (35.0f * (1.0f - settings.Sensitivity));
  265.                     swing_angle    = 10.00f + (10.0f * (1.0f - settings.Sensitivity));
  266.                 }
  267.  
  268.                 public static void Trigger(BaseEntity entity, HitInfo info)
  269.                 {
  270.                     var player = info.InitiatorPlayer;
  271.  
  272.                     if(Permissions.Bypass.AntiCheat.Aim(player.userID))
  273.                     {
  274.                         return;
  275.                     }
  276.  
  277.                     var weapon = Weapon.Get(player.userID, info.ProjectileID);
  278.  
  279.                     if(weapon == null)
  280.                     {
  281.                         return;
  282.                     }
  283.  
  284.                     _instance.NextTick(() =>
  285.                     {
  286.                         var position = info.HitPositionWorld;
  287.  
  288.                         var distance = Vector3.Distance(position, weapon.Position);
  289.  
  290.                         bool can_trigger = false, hit_location = true, pvp = false;
  291.  
  292.                         string target;
  293.  
  294.                         switch(Entity.GetType(entity, out target))
  295.                         {
  296.                         case Entity.Type.Animal:
  297.                         case Entity.Type.Bear:
  298.                         case Entity.Type.Boar:
  299.                         case Entity.Type.Chicken:
  300.                         case Entity.Type.Stag:       can_trigger = config.AntiCheat.Aim.Trigger.Animal; break;
  301.                         case Entity.Type.Bradley:    can_trigger = config.AntiCheat.Aim.Trigger.Bradley; break;
  302.                         case Entity.Type.Helicopter: can_trigger = config.AntiCheat.Aim.Trigger.Helicopter; break;
  303.                         case Entity.Type.Bot:
  304.                         case Entity.Type.Murderer:
  305.                         case Entity.Type.NPC:
  306.                         case Entity.Type.Scientist:  can_trigger = config.AntiCheat.Aim.Trigger.NPC; break;
  307.                         case Entity.Type.Player:     can_trigger = pvp = true; break;
  308.                         default:
  309.                             hit_location = false; break;
  310.                         }
  311.  
  312.                         if(can_trigger)
  313.                         {
  314.                             can_trigger = Entity.Health.Changed(entity);
  315.                         }
  316.  
  317.                         var history = History.Get(player.userID);
  318.  
  319.                         if(hit_location)
  320.                         {
  321.                             if(info.boneArea == 0)
  322.                             {
  323.                                 history.repeat.Decrement();
  324.                             }
  325.                             else
  326.                             {
  327.                                 if(info.boneArea == history.boneArea)
  328.                                 {
  329.                                     history.repeat.Increment();
  330.                                 }
  331.                                 else
  332.                                 {
  333.                                     history.repeat.Decrement();
  334.                                 }
  335.                             }
  336.  
  337.                             history.boneArea = info.boneArea;
  338.  
  339.                             if(pvp && (distance > pvp_distance))
  340.                             {
  341.                                 if(history.boneArea == HitArea.Head)
  342.                                 {
  343.                                     history.headshot.Increment();
  344.                                 }
  345.                                 else
  346.                                 {
  347.                                     history.headshot.Decrement();
  348.                                 }
  349.                             }
  350.                         }
  351.  
  352.                         var range_modifier = (1.0f - (0.25f * history.repeat.Ratio())) * (1.0f - (0.025f * weapon.Speed));
  353.  
  354.                         var range_variance = 1.0f - (distance / (weapon.Range * range_modifier));
  355.  
  356.                         var angle = Vector3.Angle(weapon.AimAngle, info.ProjectileVelocity);
  357.  
  358.                         var angle_drop_off = 12.0f / (4.0f - (weapon.Range / weapon.Velocity));
  359.  
  360.                         var angle_variance = weapon.Accuracy - (angle / (angle_drop_off + weapon.AimCone));
  361.  
  362.                         var pvp_variance = weapon.Accuracy - (history.headshot.Ratio(headshot_scale) + history.hit.Ratio(hit_scale));
  363.  
  364.                         var deflection = false;
  365.  
  366.                         var violations = 0ul;
  367.  
  368.                         if(hit_location && (distance > aim_distance) && (can_trigger || (angle > spin_angle)))
  369.                         {
  370.                             history.hits.Add(info.ProjectileID);
  371.  
  372.                             if(range_variance < sensitivity_lo)
  373.                             {
  374.                                 if(violation.Trigger(player.userID))
  375.                                 {
  376.                                     ++violations;
  377.                                 }
  378.                                 else if((range_variance < sensitivity_hi) && violation.Trigger(player.userID))
  379.                                 {
  380.                                     ++violations;
  381.                                 }
  382.                             }
  383.  
  384.                             if(angle_variance < sensitivity_lo)
  385.                             {
  386.                                 BasePlayer.FiredProjectile projectile;
  387.  
  388.                                 if(player.firedProjectiles.TryGetValue(info.ProjectileID, out projectile))
  389.                                 {
  390.                                     deflection = (projectile.ricochets > 0);
  391.                                 }
  392.  
  393.                                 if(!deflection)
  394.                                 {
  395.                                     // Facepunch removed Pool.GetList<T>() / Pool.FreeList (CS0117).
  396.                                     // Current Facepunch.Pool API: Get<List<T>>() rents a pooled list,
  397.                                     // FreeUnmanaged<T>(ref list) returns it. Same Vis.Entities usage.
  398.                                     var deflectors = Pool.Get<List<BaseEntity>>();
  399.  
  400.                                     Vis.Entities(position, aim_distance, deflectors);
  401.  
  402.                                     var d_squared = distance * distance;
  403.  
  404.                                     foreach(var deflector in deflectors)
  405.                                     {
  406.                                         if(deflector.SqrDistance(weapon.Position) < d_squared)
  407.                                         {
  408.                                             deflection = true;
  409.  
  410.                                             break;
  411.                                         }
  412.                                     }
  413.  
  414.                                     Pool.FreeUnmanaged<BaseEntity>(ref deflectors);
  415.                                 }
  416.  
  417.                                 if(!deflection || (angle > spin_angle))
  418.                                 {
  419.                                     if(violation.Trigger(player.userID))
  420.                                     {
  421.                                         ++violations;
  422.                                     }
  423.                                     else if((angle_variance < sensitivity_hi) && violation.Trigger(player.userID))
  424.                                     {
  425.                                         ++violations;
  426.                                     }
  427.  
  428.                                     if((angle > spin_angle) && violation.Trigger(player.userID))
  429.                                     {
  430.                                         ++violations;
  431.                                     }
  432.                                 }
  433.                             }
  434.  
  435.                             if(pvp && (distance > pvp_distance) && (pvp_variance < sensitivity_lo))
  436.                             {
  437.                                 if(violation.Trigger(player.userID))
  438.                                 {
  439.                                     ++violations;
  440.                                 }
  441.                                 else if((pvp_variance < sensitivity_hi) && violation.Trigger(player.userID))
  442.                                 {
  443.                                     ++violations;
  444.                                 }
  445.                             }
  446.  
  447.                             if((violations > 2) && (angle <= spin_angle))
  448.                             {
  449.                                 violations = 2;
  450.                             }
  451.                         }
  452.  
  453.                         var bodypart = Text.BodyPart(info.boneArea);
  454.  
  455.                         if(config.Log.AntiCheat.Aim)
  456.                         {
  457.                             Log.Console(Key.LogAntiCheatAim, new Dictionary<string, string>
  458.                             {
  459.                                 { "angle_variance", angle_variance.ToString("F6") },
  460.                                 { "bodypart", bodypart },
  461.                                 { "distance", distance.ToString("F1") },
  462.                                 { "playerid", player.UserIDString },
  463.                                 { "playername", Text.Sanitize(player.displayName) },
  464.                                 { "pvp_variance", pvp_variance.ToString("F6") },
  465.                                 { "range_variance", range_variance.ToString("F6") },
  466.                                 { "speed", weapon.Speed.ToString("F1") },
  467.                                 { "target", target },
  468.                                 { "weapon", $"{weapon.Name}{(deflection ? " (deflection)" : string.Empty)}" }
  469.                             });
  470.                         }
  471.  
  472.                         Projectile.Log.SetAim(player.userID, info.ProjectileID, angle_variance, pvp_variance, range_variance, pvp, deflection);
  473.  
  474.                         Projectile.Log.SetVictim(player.userID, info.ProjectileID, target);
  475.  
  476.                         Projectile.Log.SetHit(player.userID, info.ProjectileID, distance, info.boneArea);
  477.  
  478.                         if(config.AntiCheat.Aim.Enabled && (violations > 0))
  479.                         {
  480.                             Projectile.Log.SetAimViolations(player.userID, info.ProjectileID, violations);
  481.  
  482.                             var hit_angle = angle.ToString("F1");
  483.                             var hit_distance = distance.ToString("F1");
  484.  
  485.                             violation.Trigger(player.userID, type, Text.GetPlain(Key.ViolationAim, new Dictionary<string, string>
  486.                             {
  487.                                 { "angle", hit_angle },
  488.                                 { "bodypart", bodypart },
  489.                                 { "distance", hit_distance },
  490.                                 { "target", target },
  491.                                 { "weapon", weapon.Name }
  492.                             }), violations, false, new Dictionary<string, string>
  493.                             {
  494.                                 { "headshot_percent", history.headshot.Percent().ToString() },
  495.                                 { "hip_fire", (weapon.Zoom == 1.0f).ToString() },
  496.                                 { "hit_angle", hit_angle },
  497.                                 { "hit_area", Text.BodyPart(info.boneArea, "en") },
  498.                                 { "hit_distance", hit_distance },
  499.                                 { "hit_percent", history.hit.Percent().ToString() },
  500.                                 { "movement_speed", weapon.Speed.ToString("F1") },
  501.                                 { "projectile_id", info.ProjectileID.ToString() },
  502.                                 { "ricochet", deflection.ToString() },
  503.                                 { "swing_angle", "0.0" },
  504.                                 { "violation_id", weapon.Fired.Ticks.ToString() },
  505.                                 { "weapon_ammo", weapon.AmmoName },
  506.                                 { "weapon_attachments", string.Join(", ", weapon.Attachments) },
  507.                                 { "weapon_type", weapon.ShortName }
  508.                             });
  509.                         }
  510.                     });
  511.                 }
  512.  
  513.                 public static void Unload()
  514.                 {
  515.                     History.Clear();
  516.  
  517.                     violation.Clear();
  518.                 }
  519.  
  520.                 public static void Update(Weapon weapon)
  521.                 {
  522.                     var player = weapon.Player;
  523.  
  524.                     if(!History.Contains(player.userID))
  525.                     {
  526.                         return;
  527.                     }
  528.  
  529.                     var history = History.Get(player.userID);
  530.  
  531.                     foreach(var entry in weapon.Projectiles)
  532.                     {
  533.                         if(history.hits.Contains(entry))
  534.                         {
  535.                             history.hits.Remove(entry);
  536.  
  537.                             history.hit.Increment();
  538.  
  539.                             var angle = weapon.Swing;
  540.  
  541.                             if(config.AntiCheat.Aim.Enabled && (angle > swing_angle) && Projectile.Log.GetAimPvp(player.userID, entry))
  542.                             {
  543.                                 var distance = Projectile.Log.GetHitDistance(player.userID, entry);
  544.                                 if(distance > pvp_distance)
  545.                                 {
  546.                                     if(violation.Trigger(player.userID))
  547.                                     {
  548.                                         Projectile.Log.SetAimViolations(player.userID, entry, 1ul, true);
  549.  
  550.                                         var hit_angle    = Projectile.Log.GetAimAngle(player.userID, entry).ToString("F1");
  551.                                         var hit_area     = Projectile.Log.GetHitLocation(player.userID, entry);
  552.                                         var hit_distance = distance.ToString("F1");
  553.                                         var swing_angle  = angle.ToString("F1");
  554.  
  555.                                         violation.Trigger(player.userID, type, Text.GetPlain(Key.ViolationAim, new Dictionary<string, string>
  556.                                         {
  557.                                             { "angle", $"{hit_angle}-{swing_angle}" },
  558.                                             { "bodypart", Text.BodyPart(hit_area) },
  559.                                             { "distance", hit_distance },
  560.                                             { "target", Projectile.Log.GetVictim(player.userID, entry) },
  561.                                             { "weapon", weapon.Name }
  562.                                         }), 1ul, false, new Dictionary<string, string>
  563.                                         {
  564.                                             { "headshot_percent", history.headshot.Percent().ToString() },
  565.                                             { "hip_fire", (weapon.Zoom == 1.0f).ToString() },
  566.                                             { "hit_angle", hit_angle },
  567.                                             { "hit_area", Text.BodyPart(hit_area, "en") },
  568.                                             { "hit_distance", hit_distance },
  569.                                             { "hit_percent", history.hit.Percent().ToString() },
  570.                                             { "movement_speed", weapon.Speed.ToString("F1") },
  571.                                             { "projectile_id", entry.ToString() },
  572.                                             { "ricochet", Projectile.Log.GetRicochet(player.userID, entry).ToString() },
  573.                                             { "swing_angle", swing_angle },
  574.                                             { "violation_id", weapon.Fired.Ticks.ToString() },
  575.                                             { "weapon_ammo", weapon.AmmoName },
  576.                                             { "weapon_attachments", string.Join(", ", weapon.Attachments) },
  577.                                             { "weapon_type", weapon.ShortName }
  578.                                         });
  579.                                     }
  580.                                 }
  581.                             }
  582.                         }
  583.                         else
  584.                         {
  585.                             history.hit.Decrement();
  586.                         }
  587.                     }
  588.                 }
  589.             }
  590.  
  591.             public class FireRate
  592.             {
  593.                 public class Settings
  594.                 {
  595.                     public bool  Ban;
  596.                     public ulong Cooldown;
  597.                     public bool  Enabled;
  598.                     public float Sensitivity;
  599.                     public bool  Warn;
  600.  
  601.                     public Settings()
  602.                     {
  603.                         Ban         = false;
  604.                         Cooldown    = 10;
  605.                         Enabled     = true;
  606.                         Sensitivity = 0.5f;
  607.                         Warn        = true;
  608.                     }
  609.  
  610.                     public Violation.Settings Validate(ulong max)
  611.                     {
  612.                         Configuration.Clamp(ref Cooldown,     1ul,  max);
  613.                         Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  614.  
  615.                         return new Violation.Settings(Ban, Cooldown, Sensitivity, Warn);
  616.                     }
  617.                 }
  618.  
  619.                 private const Key type = Key.AntiCheatFireRate;
  620.  
  621.                 private static readonly Violation violation = new Violation(category);
  622.  
  623.                 private static float sensitivity;
  624.  
  625.                 private class History
  626.                 {
  627.                     public int      entity;
  628.                     public DateTime fired;
  629.                     public float    repeat;
  630.  
  631.                     private static readonly Dictionary<ulong, History> histories = new Dictionary<ulong, History>();
  632.  
  633.                     private History()
  634.                     {
  635.                         entity = 0;
  636.                         fired  = DateTime.MinValue;
  637.                         repeat = 10.0f;
  638.                     }
  639.  
  640.                     public static void Clear() => histories.Clear();
  641.  
  642.                     public static History Get(ulong userid)
  643.                     {
  644.                         History history;
  645.  
  646.                         if(!histories.TryGetValue(userid, out history))
  647.                         {
  648.                             histories.Add(userid, history = new History());
  649.                         }
  650.  
  651.                         return history;
  652.                     }
  653.                 }
  654.  
  655.                 public static void Configure()
  656.                 {
  657.                     var settings = config.AntiCheat.FireRate.Validate(30);
  658.  
  659.                     violation.Configure(settings, 2, 8, 5000);
  660.  
  661.                     sensitivity = 0.3f + (0.3f * settings.Sensitivity);
  662.                 }
  663.  
  664.                 private static uint Percent(float min, float delay, float max) =>
  665.                     (uint)(100.0f * (max - delay) / (max - min));
  666.  
  667.                 public static void Trigger(BaseEntity entity, HitInfo info)
  668.                 {
  669.                     var player = info.InitiatorPlayer;
  670.  
  671.                     if(Permissions.Bypass.AntiCheat.FireRate(player.userID))
  672.                     {
  673.                         return;
  674.                     }
  675.  
  676.                     var weapon = Weapon.Get(player.userID, info.ProjectileID);
  677.  
  678.                     if(weapon == null)
  679.                     {
  680.                         return;
  681.                     }
  682.  
  683.                     _instance.NextTick(() =>
  684.                     {
  685.                         var current = entity.GetInstanceID();
  686.                         var history = History.Get(player.userID);
  687.  
  688.                         if((weapon.Repeat == history.repeat) && (current == history.entity))
  689.                         {
  690.                             var delay = (float)weapon.Fired.Subtract(history.fired).TotalSeconds;
  691.  
  692.                             var max = weapon.Repeat * sensitivity;
  693.                             var min = max * 0.1f;
  694.  
  695.                             if(config.AntiCheat.FireRate.Enabled && (min <= delay) && (delay <= max) && violation.Trigger(player.userID))
  696.                             {
  697.                                 Projectile.Log.SetFireRateViolations(player.userID, info.ProjectileID, 1);
  698.  
  699.                                 var attack_rate = delay.ToString("F3");
  700.  
  701.                                 violation.Trigger(player.userID, type, Text.GetPlain(Key.ViolationFireRate, new Dictionary<string, string>
  702.                                 {
  703.                                     { "delay", attack_rate },
  704.                                     { "weapon", weapon.Name }
  705.                                 }), false, new Dictionary<string, string>
  706.                                 {
  707.                                     { "attack_rate", attack_rate },
  708.                                     { "movement_speed", weapon.Speed.ToString("F1") },
  709.                                     { "projectile_id", info.ProjectileID.ToString() },
  710.                                     { "rate_percent", Percent(min, delay, max).ToString() },
  711.                                     { "violation_id", weapon.Fired.Ticks.ToString() },
  712.                                     { "weapon_ammo", weapon.AmmoName },
  713.                                     { "weapon_attachments", string.Join(", ", weapon.Attachments) },
  714.                                     { "weapon_type", weapon.ShortName }
  715.                                 });
  716.                             }
  717.                         }
  718.  
  719.                         history.entity = current;
  720.                         history.fired = weapon.Fired;
  721.                         history.repeat = weapon.Repeat;
  722.                     });
  723.                 }
  724.  
  725.                 public static void Unload()
  726.                 {
  727.                     History.Clear();
  728.  
  729.                     violation.Clear();
  730.                 }
  731.             }
  732.  
  733.             public class Gravity
  734.             {
  735.                 public class Settings
  736.                 {
  737.                     public bool  Ban;
  738.                     public ulong Cooldown;
  739.                     public bool  Enabled;
  740.                     public float Sensitivity;
  741.                     public bool  Warn;
  742.  
  743.                     public Settings()
  744.                     {
  745.                         Ban         = false;
  746.                         Cooldown    = 10;
  747.                         Enabled     = true;
  748.                         Sensitivity = 0.5f;
  749.                         Warn        = true;
  750.                     }
  751.  
  752.                     public Violation.Settings Validate(ulong max)
  753.                     {
  754.                         Configuration.Clamp(ref Cooldown,     1ul,  max);
  755.                         Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  756.  
  757.                         return new Violation.Settings(Ban, Cooldown, Sensitivity, Warn);
  758.                     }
  759.                 }
  760.  
  761.                 private const Key type = Key.AntiCheatGravity;
  762.  
  763.                 private static readonly Violation violation = new Violation(category);
  764.  
  765.                 private static float sensitivity_lo;
  766.                 private static float sensitivity_md;
  767.                 private static float sensitivity_hi;
  768.  
  769.                 private class History
  770.                 {
  771.                     private static readonly Dictionary<ulong, float> histories = new Dictionary<ulong, float>();
  772.  
  773.                     public static void Clear() => histories.Clear();
  774.  
  775.                     public static float Set(ulong userid, float new_value)
  776.                     {
  777.                         float old_value;
  778.  
  779.                         if(!histories.TryGetValue(userid, out old_value))
  780.                         {
  781.                             histories.Add(userid, new_value);
  782.  
  783.                             return float.MaxValue;
  784.                         }
  785.  
  786.                         histories[userid] = new_value;
  787.  
  788.                         return old_value;
  789.                     }
  790.                 }
  791.  
  792.                 public static void Configure()
  793.                 {
  794.                     var settings = config.AntiCheat.Gravity.Validate(30);
  795.  
  796.                     violation.Configure(settings, 2, 8, 500);
  797.  
  798.                     sensitivity_lo = 1.55f + 1.5f * (1.0f - settings.Sensitivity);
  799.                     sensitivity_md = sensitivity_lo * 2.0f;
  800.                     sensitivity_hi = sensitivity_md * 2.0f;
  801.                 }
  802.  
  803.                 private static bool Check(BasePlayer player)
  804.                 {
  805.                     var position = player.transform.position; position.y += 0.05f;
  806.  
  807.                     if((History.Set(player.userID, position.y) >= position.y) || player.HasParent())
  808.                     {
  809.                         return false;
  810.                     }
  811.                     else if(Physics.Raycast(position, Vector3.down, sensitivity_md))
  812.                     {
  813.                         return false;
  814.                     }
  815.                     else if(position.y <= (Map.Terrain.Height(position) + sensitivity_lo))
  816.                     {
  817.                         return false;
  818.                     }
  819.  
  820.                     bool flying = true;
  821.  
  822.                     // Facepunch removed Pool.GetList<T>() (CS0117). Use Pool.Get<List<T>>() instead.
  823.                     var entities = Pool.Get<List<BaseEntity>>();
  824.  
  825.                     Vis.Entities(position, sensitivity_hi, entities);
  826.  
  827.                     foreach(var entity in entities)
  828.                     {
  829.                         if(player.IsStandingOnEntity(entity, -1))
  830.                         {
  831.                             flying = false; break;
  832.                         }
  833.  
  834.                         switch(entity.ShortPrefabName)
  835.                         {
  836.                         case "cave_lift":
  837.                         case "elevator_lift":
  838.                         case "floor.ladder.hatch":
  839.                         case "floor.triangle.ladder.hatch":
  840.                         case "hopperoutput":
  841.                         case "miningquarry":
  842.                         case "miningquarry_static":
  843.                         case "watchtower.wood":
  844.  
  845.                             if(player.Distance(entity.bounds.ClosestPoint(position)) <= sensitivity_lo)
  846.                             {
  847.                                 flying = false;
  848.                             }
  849.  
  850.                             break;
  851.  
  852.                         default:
  853.  
  854.                             // Facepunch removed BaseHelicopterVehicle (CS0246). Replacement is
  855.                             // PlayerHelicopter (minicopter / scrap heli / attack heli). That type
  856.                             // now inherits BaseVehicle, so it MUST be tested first: the old
  857.                             // BaseHelicopterVehicle branch skipped the distance gate. If we left
  858.                             // it after `is BaseVehicle`, player helis would take the tighter
  859.                             // sensitivity_lo check and false-flag gravity near a heli.
  860.                             if((entity is PlayerHelicopter) || (entity is SupplyDrop) || (entity is TreeEntity))
  861.                             {
  862.                                 flying = false;
  863.                             }
  864.                             else if((entity is BaseVehicle) || (entity is BaseVehicleSeat))
  865.                             {
  866.                                 if(player.Distance(entity.bounds.ClosestPoint(position)) <= sensitivity_lo)
  867.                                 {
  868.                                     flying = false;
  869.                                 }
  870.                             }
  871.                             else if(entity.ShortPrefabName == "ladder.wooden.wall")
  872.                             {
  873.                                 if(player.Distance(entity.bounds.ClosestPoint(position)) <= sensitivity_md)
  874.                                 {
  875.                                     flying = false;
  876.                                 }
  877.                             }
  878.                             else
  879.                             {
  880.                                 var top = entity.bounds.max.y;
  881.  
  882.                                 if((position.y >= top) && ((position.y - top) <= sensitivity_lo))
  883.                                 {
  884.                                     var bot = new Vector3(position.x, position.y - sensitivity_lo, position.z);
  885.  
  886.                                     if(Physics.CheckCapsule(position, bot, 0.25f))
  887.                                     {
  888.                                         flying = false;
  889.                                     }
  890.                                 }
  891.                             }
  892.  
  893.                             break;
  894.                         }
  895.  
  896.                         if(!flying)
  897.                         {
  898.                             break;
  899.                         }
  900.                     }
  901.  
  902.                     if(flying && (entities.Count == 0) && Vis.AnyColliders(position, sensitivity_lo))
  903.                     {
  904.                         flying = false;
  905.                     }
  906.  
  907.                     // Pair of Get<List<T>> above. FreeList is gone; FreeUnmanaged<T> is the current return path.
  908.                     Pool.FreeUnmanaged<BaseEntity>(ref entities);
  909.  
  910.                     return flying;
  911.                 }
  912.  
  913.                 public static void Scan()
  914.                 {
  915.                     Timers.Add(1.0f, () =>
  916.                     {
  917.                         if(config.AntiCheat.Gravity.Enabled)
  918.                         {
  919.                             foreach(var player in BasePlayer.activePlayerList)
  920.                             {
  921.                                 if(User.ShouldIgnore(player) || User.CanFly(player))
  922.                                 {
  923.                                     continue;
  924.                                 }
  925.  
  926.                                 if(player.isMounted || player.HasParent() || player.IsSwimming() || player.IsOnGround())
  927.                                 {
  928.                                     continue;
  929.                                 }
  930.  
  931.                                 var position = player.transform.position;
  932.  
  933.                                 if(Map.Water.IsSurface(position) && !Map.Monument.IsNearby(position) && !Map.Entities.InRange(position))
  934.                                 {
  935.                                     if((History.Set(player.userID, position.y) <= position.y))
  936.                                     {
  937.                                         Trigger(player, position.y, true);
  938.                                     }
  939.                                 }
  940.                             }
  941.                         }
  942.                     });
  943.                 }
  944.  
  945.                 public static void Trigger(BasePlayer player, float amount, bool scanned = false)
  946.                 {
  947.                     if(Permissions.Bypass.AntiCheat.Gravity(player.userID))
  948.                     {
  949.                         return;
  950.                     }
  951.  
  952.                     if(config.Log.AntiCheat.Gravity)
  953.                     {
  954.                         var position = player.transform.position;
  955.  
  956.                         Log.Console(Key.LogAntiCheatGravity, new Dictionary<string, string>
  957.                         {
  958.                             { "amount", amount.ToString("F6") },
  959.                             { "playerid", player.UserIDString },
  960.                             { "playername", Text.Sanitize(player.displayName) },
  961.                             { "position", $"({(int)position.x},{(int)position.y},{(int)position.z})" }
  962.                         });
  963.                     }
  964.  
  965.                     if(config.AntiCheat.Gravity.Enabled && (scanned || Check(player)) && violation.Trigger(player.userID))
  966.                     {
  967.                         violation.Trigger(player.userID, type, Text.GetPlain(Key.ViolationGravity, new Dictionary<string, string>
  968.                         {
  969.                             { "amount", amount.ToString("F6") }
  970.                         }), false, new Dictionary<string, string>
  971.                         {
  972.                             { "elevation", player.transform.position.y.ToString("F1") },
  973.                             { "movement_speed", player.estimatedSpeed.ToString("F1") },
  974.                             { "violation_id", DateTime.UtcNow.Ticks.ToString() }
  975.                         });
  976.                     }
  977.                 }
  978.  
  979.                 public static void Unload()
  980.                 {
  981.                     History.Clear();
  982.  
  983.                     violation.Clear();
  984.                 }
  985.             }
  986.  
  987.             public class MeleeRate
  988.             {
  989.                 public class Settings
  990.                 {
  991.                     public bool  Ban;
  992.                     public ulong Cooldown;
  993.                     public bool  Enabled;
  994.                     public float Sensitivity;
  995.                     public bool  Warn;
  996.  
  997.                     public Settings()
  998.                     {
  999.                         Ban         = false;
  1000.                         Cooldown    = 300;
  1001.                         Enabled     = true;
  1002.                         Sensitivity = 0.5f;
  1003.                         Warn        = true;
  1004.                     }
  1005.  
  1006.                     public Violation.Settings Validate(ulong max)
  1007.                     {
  1008.                         Configuration.Clamp(ref Cooldown,     1ul,  max);
  1009.                         Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  1010.  
  1011.                         return new Violation.Settings(Ban, Cooldown, Sensitivity, Warn);
  1012.                     }
  1013.                 }
  1014.  
  1015.                 private const Key type = Key.AntiCheatMeleeRate;
  1016.  
  1017.                 private static readonly Violation violation = new Violation(category);
  1018.  
  1019.                 private static float sensitivity;
  1020.  
  1021.                 private class History
  1022.                 {
  1023.                     private static readonly Dictionary<ulong, DateTime> histories = new Dictionary<ulong, DateTime>();
  1024.  
  1025.                     public static void Clear() => histories.Clear();
  1026.  
  1027.                     public static float Set(ulong userid, DateTime new_value)
  1028.                     {
  1029.                         DateTime old_value;
  1030.  
  1031.                         if(!histories.TryGetValue(userid, out old_value))
  1032.                         {
  1033.                             histories.Add(userid, new_value);
  1034.  
  1035.                             return float.MaxValue;
  1036.                         }
  1037.  
  1038.                         histories[userid] = new_value;
  1039.  
  1040.                         return (float)new_value.Subtract(old_value).TotalSeconds;
  1041.                     }
  1042.                 }
  1043.  
  1044.                 public static void Configure()
  1045.                 {
  1046.                     var settings = config.AntiCheat.MeleeRate.Validate(900);
  1047.  
  1048.                     violation.Configure(settings, 2, 8, 15000);
  1049.  
  1050.                     sensitivity = 0.4f + (0.3f * settings.Sensitivity);
  1051.                 }
  1052.  
  1053.                 private static uint Percent(float delay, float max) =>
  1054.                     (uint)(100.0f * (max - delay) / max);
  1055.  
  1056.                 public static void Trigger(BasePlayer player, HitInfo info)
  1057.                 {
  1058.                     if(Permissions.Bypass.AntiCheat.MeleeRate(player.userID))
  1059.                     {
  1060.                         return;
  1061.                     }
  1062.  
  1063.                     BaseMelee melee = info?.Weapon?.GetComponent<BaseMelee>();
  1064.  
  1065.                     if(melee == null)
  1066.                     {
  1067.                         return;
  1068.                     }
  1069.  
  1070.                     var item = info.Weapon.GetItem();
  1071.  
  1072.                     if(item == null)
  1073.                     {
  1074.                         return;
  1075.                     }
  1076.  
  1077.                     var current = DateTime.UtcNow;
  1078.  
  1079.                     _instance.NextTick(() =>
  1080.                     {
  1081.                         var itemid = item.info.itemid;
  1082.  
  1083.                         var delay = History.Set(player.userID, current);
  1084.  
  1085.                         string target = Entity.GetName(info.HitEntity);
  1086.  
  1087.                         var weapon = item.info.displayName.translated ?? "null";
  1088.  
  1089.                         if(config.Log.AntiCheat.MeleeRate)
  1090.                         {
  1091.                             var delay_string = (delay >= (melee.repeatDelay * 2.0f)) ? Text.Get(Key.idle) : $"{delay:0.00}{Text.Get(Key.DurationSecondsUnit)}";
  1092.  
  1093.                             Log.Console(Key.LogAntiCheatMeleeRate, new Dictionary<string, string>
  1094.                             {
  1095.                                 { "delay", delay_string },
  1096.                                 { "playerid", player.UserIDString },
  1097.                                 { "playername", Text.Sanitize(player.displayName) },
  1098.                                 { "target", target },
  1099.                                 { "weapon", weapon }
  1100.                             });
  1101.                         }
  1102.  
  1103.                         var chainsaw   = (itemid == 1104520648);
  1104.                         var jackhammer = (itemid == 1488979457);
  1105.  
  1106.                         var max = melee.repeatDelay * sensitivity;
  1107.  
  1108.                         if((delay <= max) && !(jackhammer || chainsaw))
  1109.                         {
  1110.                             if(config.AntiCheat.MeleeRate.Enabled && violation.Trigger(player.userID))
  1111.                             {
  1112.                                 var attack_rate = delay.ToString("F3");
  1113.  
  1114.                                 violation.Trigger(player.userID, type, Text.GetPlain(Key.ViolationMeleeRate, new Dictionary<string, string>
  1115.                                 {
  1116.                                     { "delay", attack_rate},
  1117.                                     { "target", target },
  1118.                                     { "weapon", weapon }
  1119.                                 }), false, new Dictionary<string, string>
  1120.                                 {
  1121.                                     { "attack_rate", attack_rate },
  1122.                                     { "movement_speed", player.estimatedSpeed.ToString("F1") },
  1123.                                     { "rate_percent", Percent(delay, max).ToString() },
  1124.                                     { "violation_id", DateTime.UtcNow.Ticks.ToString() },
  1125.                                     { "weapon_type", item.info.shortname }
  1126.                                 });
  1127.                             }
  1128.                         }
  1129.                     });
  1130.                 }
  1131.  
  1132.                 public static void Unload()
  1133.                 {
  1134.                     History.Clear();
  1135.  
  1136.                     violation.Clear();
  1137.                 }
  1138.             }
  1139.  
  1140.             public class Recoil
  1141.             {
  1142.                 public class Settings
  1143.                 {
  1144.                     public bool  Ban;
  1145.                     public ulong Cooldown;
  1146.                     public bool  Enabled;
  1147.                     public float Sensitivity;
  1148.                     public bool  Warn;
  1149.  
  1150.                     public Settings()
  1151.                     {
  1152.                         Ban         = false;
  1153.                         Cooldown    = 10;
  1154.                         Enabled     = true;
  1155.                         Sensitivity = 0.5f;
  1156.                         Warn        = true;
  1157.                     }
  1158.  
  1159.                     public Violation.Settings Validate(ulong max)
  1160.                     {
  1161.                         Configuration.Clamp(ref Cooldown,     1ul,  max);
  1162.                         Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  1163.  
  1164.                         return new Violation.Settings(Ban, Cooldown, Sensitivity, Warn);
  1165.                     }
  1166.                 }
  1167.  
  1168.                 private const float latency_min = 0.075f;
  1169.                 private const float latency_max = 0.175f;
  1170.  
  1171.                 private const float recoil_max   = 9.765625e-4f;
  1172.                 private const float recoil_range = recoil_max - epsilon;
  1173.                 private const float recoil_scale = 100.0f / recoil_range;
  1174.  
  1175.                 private const float repeat_interval = 1.25f;
  1176.  
  1177.                 private const Key type = Key.AntiCheatRecoil;
  1178.  
  1179.                 private static readonly Violation violation = new Violation(category);
  1180.  
  1181.                 private static ulong reset_limit;
  1182.                 private static float sensitivity;
  1183.  
  1184.                 private class History
  1185.                 {
  1186.                     public Counter  count_r;
  1187.                     public Counter  count_x;
  1188.                     public Counter  count_y;
  1189.                     public DateTime fired;
  1190.                     public ulong    repeats;
  1191.  
  1192.                     private static readonly Dictionary<ulong, History> histories = new Dictionary<ulong, History>();
  1193.  
  1194.                     private History()
  1195.                     {
  1196.                         count_r = new Counter();
  1197.                         count_x = new Counter();
  1198.                         count_y = new Counter();
  1199.                         fired   = DateTime.MinValue;
  1200.                         repeats = 0;
  1201.                     }
  1202.  
  1203.                     public static void Clear() => histories.Clear();
  1204.  
  1205.                     public static History Get(BasePlayer player)
  1206.                     {
  1207.                         History history;
  1208.  
  1209.                         if(!histories.TryGetValue(player.userID, out history))
  1210.                         {
  1211.                             histories.Add(player.userID, history = new History());
  1212.                         }
  1213.  
  1214.                         return history;
  1215.                     }
  1216.                 }
  1217.  
  1218.                 public static void Configure()
  1219.                 {
  1220.                     var settings = config.AntiCheat.Recoil.Validate(30);
  1221.  
  1222.                     violation.Configure(settings, 2, 8, 1000);
  1223.  
  1224.                     reset_limit = settings.Count(12, 20);
  1225.  
  1226.                     sensitivity = 0.00001f + (0.00009f * settings.Sensitivity);
  1227.                 }
  1228.  
  1229.                 private static float Latency(BasePlayer player, Weapon weapon)
  1230.                 {
  1231.                     var connection = player?.net?.connection;
  1232.  
  1233.                     var latency = 0.0f;
  1234.  
  1235.                     if(connection != null)
  1236.                     {
  1237.                         latency = 0.001f * (Net.sv.GetAveragePing(connection) >> 1);
  1238.                     }
  1239.  
  1240.                     if(latency < weapon.Repeat)
  1241.                     {
  1242.                         latency = weapon.Repeat;
  1243.                     }
  1244.  
  1245.                     return Generic.Clamp(latency, latency_min, latency_max);
  1246.                 }
  1247.  
  1248.                 private static uint Percent(float value) =>
  1249.                     (uint)((recoil_range - Generic.Clamp(value, epsilon, recoil_max)) * recoil_scale);
  1250.  
  1251.                 public static void Trigger(BasePlayer player, Weapon weapon)
  1252.                 {
  1253.                     if(Permissions.Bypass.AntiCheat.Recoil(player.userID))
  1254.                     {
  1255.                         return;
  1256.                     }
  1257.  
  1258.                     if(weapon == null)
  1259.                     {
  1260.                         return;
  1261.                     }
  1262.  
  1263.                     _instance.timer.In(Latency(player, weapon), () =>
  1264.                     {
  1265.                         var current = player.eyes.HeadRay().direction;
  1266.  
  1267.                         weapon.SetSwing(Vector3.Angle(current, weapon.AimAngle));
  1268.  
  1269.                         var x = Math.Abs(current.x - weapon.AimAngle.x) / weapon.Yaw;
  1270.                         var y = Math.Abs(current.y - weapon.AimAngle.y) / weapon.Pitch;
  1271.  
  1272.                         var history = History.Get(player);
  1273.  
  1274.                         x *= 0.25f + history.count_x.Ratio(true);
  1275.                         y *= 0.25f + history.count_y.Ratio(true);
  1276.  
  1277.                         var delay  = weapon.Fired.Subtract(history.fired).TotalSeconds;
  1278.                         var repeat = delay <= (weapon.Repeat * repeat_interval);
  1279.                         var reset  = false;
  1280.                         var reset_full = false;
  1281.  
  1282.                         if(Math.Abs(current.y) > 0.85f)
  1283.                         {
  1284.                             reset = true; y = 1.0f;
  1285.                         }
  1286.                         else if((x < epsilon) && (y < epsilon))
  1287.                         {
  1288.                             if(reset = (history.repeats == 0) || (history.repeats >= (weapon.Automatic ? 1ul : 2ul)))
  1289.                             {
  1290.                                 reset_full = true;
  1291.                             }
  1292.                         }
  1293.  
  1294.                         var violations = 0ul;
  1295.  
  1296.                         if(reset_full && (history.repeats > 0))
  1297.                         {
  1298.                             history.count_r.Increment();
  1299.  
  1300.                             if(history.count_r.Total() >= reset_limit)
  1301.                             {
  1302.                                 if(violation.Trigger(player.userID))
  1303.                                 {
  1304.                                     ++violations;
  1305.                                 }
  1306.                             }
  1307.                         }
  1308.                         else
  1309.                         {
  1310.                             history.count_r.Decrement();
  1311.                         }
  1312.  
  1313.                         var can_trigger = false;
  1314.  
  1315.                         if(repeat)
  1316.                         {
  1317.                             ++history.repeats;
  1318.  
  1319.                             can_trigger = !(reset || weapon.Shell);
  1320.                         }
  1321.                         else
  1322.                         {
  1323.                             history.repeats = 0;
  1324.                         }
  1325.  
  1326.                         if(can_trigger)
  1327.                         {
  1328.                             if(x <= sensitivity)
  1329.                             {
  1330.                                 history.count_x.Increment();
  1331.  
  1332.                                 if(violation.Trigger(player.userID))
  1333.                                 {
  1334.                                     ++violations;
  1335.                                 }
  1336.                             }
  1337.  
  1338.                             if(y <= sensitivity)
  1339.                             {
  1340.                                 history.count_y.Increment();
  1341.  
  1342.                                 if(violation.Trigger(player.userID))
  1343.                                 {
  1344.                                     ++violations;
  1345.                                 }
  1346.                             }
  1347.                         }
  1348.                         else if(!reset)
  1349.                         {
  1350.                             history.count_x.Decrement();
  1351.                             history.count_y.Decrement();
  1352.                         }
  1353.  
  1354.                         history.fired = weapon.Fired;
  1355.  
  1356.                         var playername = Text.Sanitize(player.displayName);
  1357.  
  1358.                         if(config.Log.AntiCheat.Recoil)
  1359.                         {
  1360.                             Log.Console(Key.LogAntiCheatRecoil, new Dictionary<string, string>
  1361.                             {
  1362.                                 { "count_x", history.count_x.Total().ToString("D2") },
  1363.                                 { "count_y", history.count_y.Total().ToString("D2") },
  1364.                                 { "pitch", y.ToString("F6") },
  1365.                                 { "playerid", player.UserIDString },
  1366.                                 { "playername", playername },
  1367.                                 { "swing", weapon.Swing.ToString("F1") },
  1368.                                 { "weapon", $"{weapon.Name}[{history.repeats}{(reset ? $" (reset)" : string.Empty)}]" },
  1369.                                 { "yaw", x.ToString("F6") }
  1370.                             });
  1371.                         }
  1372.  
  1373.                         foreach(var projectileid in weapon.Projectiles)
  1374.                         {
  1375.                             Projectile.Log.SetAttacker(player.userID, projectileid, playername, player.userID);
  1376.  
  1377.                             Projectile.Log.SetRecoil(player.userID, projectileid, y, history.repeats, x, weapon.Swing);
  1378.  
  1379.                             Projectile.Log.SetWeapon(player.userID, projectileid, weapon.Speed, weapon.Name);
  1380.                         }
  1381.  
  1382.                         if(config.AntiCheat.Recoil.Enabled && (violations > 0))
  1383.                         {
  1384.                             foreach(var projectileid in weapon.Projectiles)
  1385.                             {
  1386.                                 Projectile.Log.SetRecoilViolations(player.userID, projectileid, violations);
  1387.                             }
  1388.  
  1389.                             violation.Trigger(player.userID, type, Text.GetPlain(Key.ViolationRecoil, new Dictionary<string, string>
  1390.                             {
  1391.                                 { "pitch", y.ToString("F6") },
  1392.                                 { "weapon", weapon.Name },
  1393.                                 { "yaw", x.ToString("F6") }
  1394.                             }), violations, false, new Dictionary<string, string>
  1395.                             {
  1396.                                 { "movement_speed", weapon.Speed.ToString("F1") },
  1397.                                 { "recoil_pitch", Percent(y).ToString() },
  1398.                                 { "recoil_repeats", history.repeats.ToString() },
  1399.                                 { "recoil_yaw", Percent(x).ToString() },
  1400.                                 { "violation_id", weapon.Fired.Ticks.ToString() },
  1401.                                 { "weapon_ammo", weapon.AmmoName },
  1402.                                 { "weapon_attachments", string.Join(", ", weapon.Attachments) },
  1403.                                 { "weapon_type", weapon.ShortName }
  1404.                             });
  1405.                         }
  1406.                     });
  1407.                 }
  1408.  
  1409.                 public static void Unload()
  1410.                 {
  1411.                     History.Clear();
  1412.  
  1413.                     violation.Clear();
  1414.                 }
  1415.             }
  1416.  
  1417.             public class Server
  1418.             {
  1419.                 public class Settings
  1420.                 {
  1421.                     public bool  Ban;
  1422.                     public ulong Cooldown;
  1423.                     public bool  Enabled;
  1424.                     public float Sensitivity;
  1425.                     public bool  Warn;
  1426.  
  1427.                     public Settings()
  1428.                     {
  1429.                         Ban         = false;
  1430.                         Cooldown    = 300;
  1431.                         Enabled     = true;
  1432.                         Sensitivity = 0.5f;
  1433.                         Warn        = true;
  1434.                     }
  1435.  
  1436.                     public Violation.Settings Validate(ulong max)
  1437.                     {
  1438.                         Configuration.Clamp(ref Cooldown,     1ul,  max);
  1439.                         Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  1440.  
  1441.                         return new Violation.Settings(Ban, Cooldown, Sensitivity, Warn);
  1442.                     }
  1443.                 }
  1444.  
  1445.                 private static Vector3 sensitivity = Vector3.zero;
  1446.  
  1447.                 private const Key type = Key.Server;
  1448.  
  1449.                 private static readonly Violation violation = new Violation(category);
  1450.  
  1451.                 public static void Configure()
  1452.                 {
  1453.                     var settings = config.AntiCheat.Server.Validate(900);
  1454.  
  1455.                     sensitivity = new Vector3(0.0f, 0.125f + 0.25f * (1.0f - config.AntiCheat.Server.Sensitivity), 0.0f);
  1456.  
  1457.                     violation.Configure(settings, 4, 16, 60000);
  1458.                 }
  1459.  
  1460.                 public static void Scan()
  1461.                 {
  1462.                     Timers.Add(1.0f, () =>
  1463.                     {
  1464.                         if(config.AntiCheat.Server.Enabled)
  1465.                         {
  1466.                             foreach(var player in BasePlayer.activePlayerList)
  1467.                             {
  1468.                                 if(User.ShouldIgnore(player) || User.CanFly(player))
  1469.                                 {
  1470.                                     continue;
  1471.                                 }
  1472.  
  1473.                                 if(player.isMounted || player.HasParent() || player.IsSwimming())
  1474.                                 {
  1475.                                     continue;
  1476.                                 }
  1477.  
  1478.                                 if(!player.IsFlying)
  1479.                                 {
  1480.                                     player.SendConsoleCommand("noclip");
  1481.                                     player.SendConsoleCommand("debugcamera");
  1482.                                     player.SendConsoleCommand("camspeed", "0");
  1483.                                 }
  1484.  
  1485.                                 var position = player.transform.position;
  1486.                                 var highspot = position + sensitivity;
  1487.  
  1488.                                 if(!Map.Cave.IsInside(highspot))
  1489.                                 {
  1490.                                     if(Map.Terrain.IsInside(highspot, false))
  1491.                                     {
  1492.                                         _instance.OnPlayerViolation(player, AntiHackType.InsideTerrain, position.y);
  1493.                                     }
  1494.                                     else if(Map.Rock.IsInside(highspot, false))
  1495.                                     {
  1496.                                         _instance.OnPlayerViolation(player, AntiHackType.NoClip, position.y);
  1497.                                     }
  1498.                                 }
  1499.  
  1500.                                 if(Map.Building.InFoundation(highspot))
  1501.                                 {
  1502.                                     _instance.OnPlayerViolation(player, AntiHackType.NoClip, position.y);
  1503.                                 }
  1504.                             }
  1505.                         }
  1506.                     });
  1507.                 }
  1508.  
  1509.                 public static void Trigger(BasePlayer player, AntiHackType ahtype, float amount)
  1510.                 {
  1511.                     if(Permissions.Bypass.AntiCheat.Server(player.userID))
  1512.                     {
  1513.                         return;
  1514.                     }
  1515.  
  1516.                     if(Math.Abs(amount) < epsilon)
  1517.                     {
  1518.                         return;
  1519.                     }
  1520.  
  1521.                     var position = player.transform.position;
  1522.  
  1523.                     if(config.Log.AntiCheat.Server)
  1524.                     {
  1525.                         var colliders = string.Empty;
  1526.  
  1527.                         if((ahtype == AntiHackType.InsideTerrain) || (ahtype == AntiHackType.NoClip))
  1528.                         {
  1529.                             colliders = Map.Collider.Info(position);
  1530.                         }
  1531.  
  1532.                         Log.Console(Key.LogAntiCheatServer, new Dictionary<string, string>
  1533.                         {
  1534.                             { "amount", amount.ToString("F6") },
  1535.                             { "colliders", colliders },
  1536.                             { "playerid", player.UserIDString },
  1537.                             { "playername", Text.Sanitize(player.displayName) },
  1538.                             { "position", $"({(int)position.x},{(int)position.y},{(int)position.z})" },
  1539.                             { "type", ahtype.ToString() }
  1540.                         });
  1541.                     }
  1542.  
  1543.                     if(violation.Trigger(player.userID))
  1544.                     {
  1545.                         var antihack_amount = amount.ToString("F6");
  1546.                         var antihack_type   = ahtype.ToString();
  1547.  
  1548.                         violation.Trigger(player.userID, type, $"{antihack_type}({antihack_amount})", false, new Dictionary<string, string>
  1549.                         {
  1550.                             { "antihack_amount", antihack_amount },
  1551.                             { "antihack_type", antihack_type },
  1552.                             { "movement_speed", player.estimatedSpeed.ToString("F1") },
  1553.                             { "violation_id", DateTime.UtcNow.Ticks.ToString() }
  1554.                         });
  1555.                     }
  1556.                 }
  1557.  
  1558.                 public static void Unload() => violation.Clear();
  1559.             }
  1560.  
  1561.             public class Stash
  1562.             {
  1563.                 public class Settings
  1564.                 {
  1565.                     public bool  Ban;
  1566.                     public ulong Cooldown;
  1567.                     public bool  Enabled;
  1568.                     public float Sensitivity;
  1569.                     public bool  Warn;
  1570.  
  1571.                     public Settings()
  1572.                     {
  1573.                         Ban         = false;
  1574.                         Cooldown    = 3600;
  1575.                         Enabled     = true;
  1576.                         Sensitivity = 0.5f;
  1577.                         Warn        = true;
  1578.                     }
  1579.  
  1580.                     public Violation.Settings Validate(ulong max)
  1581.                     {
  1582.                         Configuration.Clamp(ref Cooldown,     1ul,  max);
  1583.                         Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  1584.  
  1585.                         return new Violation.Settings(Ban, Cooldown, Sensitivity, Warn);
  1586.                     }
  1587.                 }
  1588.  
  1589.                 private const Key type = Key.AntiCheatStash;
  1590.  
  1591.                 private static readonly Violation violation = new Violation(category);
  1592.  
  1593.                 private class History
  1594.                 {
  1595.                     private static readonly Dictionary<ulong, List<Vector3>> histories = new Dictionary<ulong, List<Vector3>>();
  1596.  
  1597.                     public static void Clear() => histories.Clear();
  1598.  
  1599.                     public static float Set(ulong userid, Vector3 position)
  1600.                     {
  1601.                         List<Vector3> history;
  1602.  
  1603.                         if(!histories.TryGetValue(userid, out history))
  1604.                         {
  1605.                             histories.Add(userid, history = new List<Vector3> { position });
  1606.  
  1607.                             return 0.0f;
  1608.                         }
  1609.  
  1610.                         var minimum = float.MaxValue;
  1611.                         var nearest = Vector3.zero;
  1612.  
  1613.                         foreach(var found in history)
  1614.                         {
  1615.                             var delta = (found - position).sqrMagnitude;
  1616.  
  1617.                             if(delta < minimum)
  1618.                             {
  1619.                                 minimum = delta;
  1620.                                 nearest = found;
  1621.                             }
  1622.                         }
  1623.  
  1624.                         if(!Map.InRange2D(position, nearest, 3.0f))
  1625.                         {
  1626.                             history.Add(position);
  1627.                         }
  1628.  
  1629.                         return (float)Math.Sqrt(minimum);
  1630.                     }
  1631.                 }
  1632.  
  1633.                 private static float sensitivity;
  1634.  
  1635.                 public static void Configure()
  1636.                 {
  1637.                     var settings = config.AntiCheat.Stash.Validate(14400);
  1638.  
  1639.                     violation.Configure(settings, 1, 1, 3600000);
  1640.  
  1641.                     sensitivity = 3.0f + 18.0f * (1.0f - settings.Sensitivity);
  1642.                 }
  1643.  
  1644.                 public static void Subscribe()
  1645.                 {
  1646.                     if(config.AntiCheat.Stash.Enabled)
  1647.                     {
  1648.                         Hooks.Subscribe(nameof(CanSeeStash));
  1649.                     }
  1650.                     else
  1651.                     {
  1652.                         Hooks.Unsubscribe(nameof(CanSeeStash));
  1653.                     }
  1654.                 }
  1655.  
  1656.                 public static void Trigger(BasePlayer player, StashContainer stash)
  1657.                 {
  1658.                     if(Permissions.Bypass.AntiCheat.Stash(player.userID))
  1659.                     {
  1660.                         return;
  1661.                     }
  1662.  
  1663.                     if((player.userID == stash.OwnerID) || User.IsTeamMate(player, stash.OwnerID) || User.IsFriend(player, stash.OwnerID))
  1664.                     {
  1665.                         return;
  1666.                     }
  1667.  
  1668.                     _instance.NextTick(() =>
  1669.                     {
  1670.                         var position = stash.transform.position;
  1671.  
  1672.                         var grid = Map.Grid(position);
  1673.  
  1674.                         if(config.Log.AntiCheat.Stash)
  1675.                         {
  1676.                             Log.Console(Key.LogAntiCheatStash, new Dictionary<string, string>
  1677.                             {
  1678.                                 { "grid", grid },
  1679.                                 { "ownerid", stash.OwnerID.ToString() },
  1680.                                 { "playerid", player.UserIDString },
  1681.                                 { "playername", Text.Sanitize(player.displayName) },
  1682.                                 { "position", $"({(int)position.x},{(int)position.y},{(int)position.z})" }
  1683.                             });
  1684.                         }
  1685.  
  1686.                         float distance = History.Set(player.userID, position);
  1687.  
  1688.                         if(config.AntiCheat.Stash.Enabled && (distance >= sensitivity))
  1689.                         {
  1690.                             var triggered = violation.Trigger(player.userID);
  1691.  
  1692.                             if(triggered || config.AntiCheat.Stash.Warn)
  1693.                             {
  1694.                                 var details = Text.GetPlain(Key.ViolationStash, new Dictionary<string, string>
  1695.                                 {
  1696.                                     { "grid", grid },
  1697.                                     { "position", $"({(int)position.x},{(int)position.y},{(int)position.z})" }
  1698.                                 });
  1699.  
  1700.                                 var hook_details = new Dictionary<string, string>
  1701.                                 {
  1702.                                     { "stash_distance", distance.ToString("F1") },
  1703.                                     { "movement_speed", player.estimatedSpeed.ToString("F1") },
  1704.                                     { "violation_id", DateTime.UtcNow.Ticks.ToString() }
  1705.                                 };
  1706.  
  1707.                                 if(triggered)
  1708.                                 {
  1709.                                     violation.Trigger(player.userID, type, details, false, hook_details);
  1710.                                 }
  1711.                                 else
  1712.                                 {
  1713.                                     violation.Warning(player.userID, type, details, hook_details);
  1714.                                 }
  1715.                             }
  1716.                         }
  1717.                     });
  1718.                 }
  1719.  
  1720.                 public static void Unload()
  1721.                 {
  1722.                     History.Clear();
  1723.  
  1724.                     violation.Clear();
  1725.                 }
  1726.             }
  1727.  
  1728.             public class Trajectory
  1729.             {
  1730.                 public class Settings
  1731.                 {
  1732.                     public bool  Ban;
  1733.                     public ulong Cooldown;
  1734.                     public bool  Enabled;
  1735.                     public float Sensitivity;
  1736.                     public bool  Warn;
  1737.  
  1738.                     public Settings()
  1739.                     {
  1740.                         Ban         = false;
  1741.                         Cooldown    = 90;
  1742.                         Enabled     = true;
  1743.                         Sensitivity = 0.5f;
  1744.                         Warn        = true;
  1745.                     }
  1746.  
  1747.                     public Violation.Settings Validate(ulong max)
  1748.                     {
  1749.                         Configuration.Clamp(ref Cooldown,     1ul,  max);
  1750.                         Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  1751.  
  1752.                         return new Violation.Settings(Ban, Cooldown, Sensitivity, Warn);
  1753.                     }
  1754.                 }
  1755.  
  1756.                 private const Key type = Key.AntiCheatTrajectory;
  1757.  
  1758.                 private static readonly Violation violation = new Violation(category);
  1759.  
  1760.                 private static float sensitivity;
  1761.                 private static float sensitivity_lo;
  1762.                 private static float sensitivity_hi;
  1763.  
  1764.                 public static void Configure()
  1765.                 {
  1766.                     var settings = config.AntiCheat.Trajectory.Validate(300);
  1767.  
  1768.                     violation.Configure(settings, 2, 8, 1000);
  1769.  
  1770.                     sensitivity = 5.0f + (5.0f * (1.0f - settings.Sensitivity));
  1771.  
  1772.                     sensitivity_lo = 0.0625f + 0.125f * settings.Sensitivity;
  1773.                     sensitivity_hi = 0.5f * sensitivity_lo;
  1774.                 }
  1775.  
  1776.                 private static float Ratio(float a, float b) => (a + sensitivity) / (b + sensitivity);
  1777.  
  1778.                 private static uint Percent(float value) =>
  1779.                     (uint)(100.0f * (1.0f - value));
  1780.  
  1781.                 public static void Trigger(BaseCombatEntity entity, HitInfo info)
  1782.                 {
  1783.                     var player = info.InitiatorPlayer;
  1784.  
  1785.                     if(Permissions.Bypass.AntiCheat.Trajectory(player.userID))
  1786.                     {
  1787.                         return;
  1788.                     }
  1789.  
  1790.                     var weapon = Weapon.Get(player.userID, info.ProjectileID);
  1791.  
  1792.                     if(weapon == null)
  1793.                     {
  1794.                         return;
  1795.                     }
  1796.  
  1797.                     var d_actual = Vector3.Distance(entity.transform.position, weapon.Position);
  1798.  
  1799.                     var d_server = info.ProjectileDistance;
  1800.  
  1801.                     _instance.NextTick(() =>
  1802.                     {
  1803.                         var variance = (d_actual < d_server) ? Ratio(d_actual, d_server) : Ratio(d_server, d_actual);
  1804.  
  1805.                         if(float.IsNaN(variance) || float.IsInfinity(variance))
  1806.                         {
  1807.                             variance = 1.0f;
  1808.                         }
  1809.                         else
  1810.                         {
  1811.                             variance = Math.Abs(variance);
  1812.                         }
  1813.  
  1814.                         ulong violations = 0;
  1815.  
  1816.                         if(variance < sensitivity_lo)
  1817.                         {
  1818.                             if(violation.Trigger(player.userID))
  1819.                             {
  1820.                                 ++violations;
  1821.                             }
  1822.  
  1823.                             if((variance < sensitivity_hi) && violation.Trigger(player.userID))
  1824.                             {
  1825.                                 ++violations;
  1826.                             }
  1827.                         }
  1828.  
  1829.                         if(config.Log.AntiCheat.Trajectory)
  1830.                         {
  1831.                             Log.Console(Key.LogAntiCheatTrajectory, new Dictionary<string, string>
  1832.                             {
  1833.                                 { "distance", d_actual.ToString("F1") },
  1834.                                 { "playerid", player.UserIDString },
  1835.                                 { "playername", Text.Sanitize(player.displayName) },
  1836.                                 { "reported", d_server.ToString("F1") },
  1837.                                 { "weapon", $"{weapon.Name}[{Enum.GetName(typeof(DamageType), entity.lastDamage)}]" }
  1838.                             });
  1839.                         }
  1840.  
  1841.                         Projectile.Log.SetTrajectory(player.userID, info.ProjectileID, variance);
  1842.  
  1843.                         if(player.isMounted || User.HasParent<CargoShip>(player) || User.HasParent<HotAirBalloon>(player))
  1844.                         {
  1845.                             return;
  1846.                         }
  1847.  
  1848.                         if(config.AntiCheat.Trajectory.Enabled && (violations > 0))
  1849.                         {
  1850.                             Projectile.Log.SetTrajectoryViolations(player.userID, info.ProjectileID, violations);
  1851.  
  1852.                             var hit_distance = d_actual.ToString("F1");
  1853.                             var projectile_distance = d_server.ToString("F1");
  1854.  
  1855.                             violation.Trigger(player.userID, type, Text.GetPlain(Key.ViolationTrajectory, new Dictionary<string, string>
  1856.                             {
  1857.                                 { "distance", hit_distance },
  1858.                                 { "reported", projectile_distance },
  1859.                                 { "weapon", weapon.Name }
  1860.                             }), violations, false, new Dictionary<string, string>
  1861.                             {
  1862.                                 { "hit_distance", hit_distance },
  1863.                                 { "movement_speed", weapon.Speed.ToString("F1") },
  1864.                                 { "projectile_distance", projectile_distance },
  1865.                                 { "projectile_id", info.ProjectileID.ToString() },
  1866.                                 { "trajectory_percent", Percent(variance).ToString() },
  1867.                                 { "violation_id", weapon.Fired.Ticks.ToString() },
  1868.                                 { "weapon_ammo", weapon.AmmoName },
  1869.                                 { "weapon_attachments", string.Join(", ", weapon.Attachments) },
  1870.                                 { "weapon_type", weapon.ShortName }
  1871.                             });
  1872.                         }
  1873.                     });
  1874.                 }
  1875.  
  1876.                 public static void Unload() => violation.Clear();
  1877.             }
  1878.  
  1879.             public class WallHack
  1880.             {
  1881.                 public class Settings
  1882.                 {
  1883.                     public bool  Ban;
  1884.                     public ulong Cooldown;
  1885.                     public bool  Enabled;
  1886.                     public float Sensitivity;
  1887.                     public bool  Warn;
  1888.  
  1889.                     public Settings()
  1890.                     {
  1891.                         Ban         = false;
  1892.                         Cooldown    = 300;
  1893.                         Enabled     = true;
  1894.                         Sensitivity = 0.5f;
  1895.                         Warn        = true;
  1896.                     }
  1897.  
  1898.                     public Violation.Settings Validate(ulong max)
  1899.                     {
  1900.                         Configuration.Clamp(ref Cooldown,     1ul,  max);
  1901.                         Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  1902.  
  1903.                         return new Violation.Settings(Ban, Cooldown, Sensitivity, Warn);
  1904.                     }
  1905.                 }
  1906.  
  1907.                 private const Key type = Key.AntiCheatWallHack;
  1908.  
  1909.                 private static readonly Violation violation = new Violation(category);
  1910.  
  1911.                 public static void Configure()
  1912.                 {
  1913.                     var settings = config.AntiCheat.WallHack.Validate(900);
  1914.  
  1915.                     violation.Configure(settings, 2, 8, 15000);
  1916.                 }
  1917.  
  1918.                 public static void Trigger(BasePlayer player, HitInfo info)
  1919.                 {
  1920.                     if(Permissions.Bypass.AntiCheat.WallHack(player.userID))
  1921.                     {
  1922.                         return;
  1923.                     }
  1924.  
  1925.                     var victim = info.HitEntity as BasePlayer;
  1926.  
  1927.                     if((victim == null) || !victim.userID.IsSteamId())
  1928.                     {
  1929.                         return;
  1930.                     }
  1931.  
  1932.                     var weapon = Weapon.Get(player.userID, info.ProjectileID);
  1933.  
  1934.                     if((weapon == null) || weapon.Spread)
  1935.                     {
  1936.                         return;
  1937.                     }
  1938.  
  1939.                     _instance.NextTick(() =>
  1940.                     {
  1941.                         if(Physics.Linecast(info.PointStart, info.HitPositionWorld, Layers.Mask.Construction))
  1942.                         {
  1943.                             if(config.AntiCheat.WallHack.Enabled && violation.Trigger(player.userID))
  1944.                             {
  1945.                                 Projectile.Log.SetWallHackViolations(player.userID, info.ProjectileID, 1);
  1946.  
  1947.                                 violation.Trigger(player.userID, type, Text.GetPlain(Key.ViolationWallHack, new Dictionary<string, string>
  1948.                                 {
  1949.                                     { "target", Text.Sanitize(victim.displayName) },
  1950.                                     { "weapon", weapon.Name }
  1951.                                 }), false, new Dictionary<string, string>
  1952.                                 {
  1953.                                     { "hit_distance", Vector3.Distance(victim.transform.position, weapon.Position).ToString("F1") },
  1954.                                     { "movement_speed", weapon.Speed.ToString("F1") },
  1955.                                     { "projectile_id", info.ProjectileID.ToString() },
  1956.                                     { "violation_id", weapon.Fired.Ticks.ToString() },
  1957.                                     { "weapon_ammo", weapon.AmmoName },
  1958.                                     { "weapon_attachments", string.Join(", ", weapon.Attachments) },
  1959.                                     { "weapon_type", weapon.ShortName }
  1960.                                 });
  1961.                             }
  1962.                         }
  1963.                     });
  1964.                 }
  1965.  
  1966.                 public static void Unload() => violation.Clear();
  1967.             }
  1968.         }
  1969.  
  1970.         #endregion _anticheat_
  1971.  
  1972.         #region _antiflood_
  1973.  
  1974.         private class AntiFlood
  1975.         {
  1976.             public class Settings
  1977.             {
  1978.                 public Chat.Settings     Chat;
  1979.                 public Command.Settings  Command;
  1980.                 public ItemDrop.Settings ItemDrop;
  1981.  
  1982.                 public Settings()
  1983.                 {
  1984.                     Chat     = new Chat.Settings();
  1985.                     Command  = new Command.Settings();
  1986.                     ItemDrop = new ItemDrop.Settings();
  1987.                 }
  1988.  
  1989.                 public void Validate()
  1990.                 {
  1991.                     Configuration.Validate(ref Chat,     () => new Chat.Settings());
  1992.                     Configuration.Validate(ref Command,  () => new Command.Settings());
  1993.                     Configuration.Validate(ref ItemDrop, () => new ItemDrop.Settings());
  1994.                 }
  1995.             }
  1996.  
  1997.             private const Key category = Key.AntiFlood;
  1998.  
  1999.             public static void Configure()
  2000.             {
  2001.                 Chat.Configure();
  2002.                 Command.Configure();
  2003.                 ItemDrop.Configure();
  2004.             }
  2005.  
  2006.             public static void Unload()
  2007.             {
  2008.                 Chat.Unload();
  2009.                 Command.Unload();
  2010.                 ItemDrop.Unload();
  2011.             }
  2012.  
  2013.             public class Chat
  2014.             {
  2015.                 public class Settings
  2016.                 {
  2017.                     public bool  Ban;
  2018.                     public ulong Cooldown;
  2019.                     public bool  Enabled;
  2020.                     public float Sensitivity;
  2021.                     public bool  Warn;
  2022.  
  2023.                     public Settings()
  2024.                     {
  2025.                         Ban         = false;
  2026.                         Cooldown    = 30;
  2027.                         Enabled     = true;
  2028.                         Sensitivity = 0.5f;
  2029.                         Warn        = false;
  2030.                     }
  2031.  
  2032.                     public Violation.Settings Validate(ulong max)
  2033.                     {
  2034.                         Configuration.Clamp(ref Cooldown,     1ul,  max);
  2035.                         Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  2036.  
  2037.                         return new Violation.Settings(Ban, Cooldown, Sensitivity, Warn);
  2038.                     }
  2039.                 }
  2040.  
  2041.                 private const Key type = Key.AntiFloodChat;
  2042.  
  2043.                 private static readonly Violation violation = new Violation(category);
  2044.  
  2045.                 public static void Configure()
  2046.                 {
  2047.                     var settings = config.AntiFlood.Chat.Validate(3600);
  2048.  
  2049.                     violation.Configure(settings, 5, 15, 1000);
  2050.                 }
  2051.  
  2052.                 public static ulong Cooldown(ulong playerid)
  2053.                 {
  2054.                     return violation.Cooldown(playerid);
  2055.                 }
  2056.  
  2057.                 public static void Subscribe()
  2058.                 {
  2059.                     if(config.AntiFlood.Chat.Enabled)
  2060.                     {
  2061.                         Hooks.Subscribe(nameof(OnPlayerChat));
  2062.                     }
  2063.                     else
  2064.                     {
  2065.                         Hooks.Unsubscribe(nameof(OnPlayerChat));
  2066.                     }
  2067.                 }
  2068.  
  2069.                 public static bool Trigger(ulong playerid)
  2070.                 {
  2071.                     return violation.Trigger(playerid);
  2072.                 }
  2073.  
  2074.                 public static void Unload() => violation.Clear();
  2075.  
  2076.                 public static void Violation(BasePlayer player, string details)
  2077.                 {
  2078.                     if(config.AntiFlood.Chat.Enabled)
  2079.                     {
  2080.                         violation.Trigger(player.userID, type, details);
  2081.                     }
  2082.                 }
  2083.             }
  2084.  
  2085.             public class Command
  2086.             {
  2087.                 public class Settings
  2088.                 {
  2089.                     public bool  Ban;
  2090.                     public ulong Cooldown;
  2091.                     public bool  Enabled;
  2092.                     public float Sensitivity;
  2093.                     public bool  Warn;
  2094.  
  2095.                     public Settings()
  2096.                     {
  2097.                         Ban         = false;
  2098.                         Cooldown    = 30;
  2099.                         Enabled     = true;
  2100.                         Sensitivity = 0.5f;
  2101.                         Warn        = false;
  2102.                     }
  2103.  
  2104.                     public Violation.Settings Validate(ulong max)
  2105.                     {
  2106.                         Configuration.Clamp(ref Cooldown,     1ul,  max);
  2107.                         Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  2108.  
  2109.                         return new Violation.Settings(Ban, Cooldown, Sensitivity, Warn);
  2110.                     }
  2111.                 }
  2112.  
  2113.                 private const Key type = Key.AntiFloodCommand;
  2114.  
  2115.                 private static readonly Violation violation = new Violation(category);
  2116.  
  2117.                 public static void Configure()
  2118.                 {
  2119.                     var settings = config.AntiFlood.Command.Validate(60);
  2120.  
  2121.                     violation.Configure(settings, 5, 15, 125);
  2122.                 }
  2123.  
  2124.                 public static ulong Cooldown(ulong playerid)
  2125.                 {
  2126.                     return violation.Cooldown(playerid);
  2127.                 }
  2128.  
  2129.                 public static void Subscribe()
  2130.                 {
  2131.                     if(config.AntiFlood.Command.Enabled)
  2132.                     {
  2133.                         Hooks.Subscribe(nameof(OnServerCommand));
  2134.                     }
  2135.                     else
  2136.                     {
  2137.                         Hooks.Unsubscribe(nameof(OnServerCommand));
  2138.                     }
  2139.                 }
  2140.  
  2141.                 public static bool Trigger(ulong playerid)
  2142.                 {
  2143.                     return violation.Trigger(playerid);
  2144.                 }
  2145.  
  2146.                 public static void Unload() => violation.Clear();
  2147.  
  2148.                 public static void Violation(BasePlayer player, string details)
  2149.                 {
  2150.                     if(config.AntiFlood.Command.Enabled)
  2151.                     {
  2152.                         violation.Trigger(player.userID, type, details);
  2153.                     }
  2154.                 }
  2155.             }
  2156.  
  2157.             public class ItemDrop
  2158.             {
  2159.                 public class Settings
  2160.                 {
  2161.                     public bool  Ban;
  2162.                     public ulong Cooldown;
  2163.                     public bool  Enabled;
  2164.                     public float Sensitivity;
  2165.                     public bool  Warn;
  2166.  
  2167.                     public Settings()
  2168.                     {
  2169.                         Ban         = false;
  2170.                         Cooldown    = 300;
  2171.                         Enabled     = true;
  2172.                         Sensitivity = 0.5f;
  2173.                         Warn        = false;
  2174.                     }
  2175.  
  2176.                     public Violation.Settings Validate(ulong max)
  2177.                     {
  2178.                         Configuration.Clamp(ref Cooldown,     1ul,  max);
  2179.                         Configuration.Clamp(ref Sensitivity, 0.0f, 1.0f);
  2180.  
  2181.                         return new Violation.Settings(Ban, Cooldown, Sensitivity, Warn);
  2182.                     }
  2183.                 }
  2184.  
  2185.                 private const Key type = Key.AntiFloodItemDrop;
  2186.  
  2187.                 private static readonly Violation violation = new Violation(category);
  2188.  
  2189.                 private static readonly Dictionary<ulong, int> history = new Dictionary<ulong, int>();
  2190.  
  2191.                 public static void Configure()
  2192.                 {
  2193.                     var settings = config.AntiFlood.ItemDrop.Validate(900);
  2194.  
  2195.                     violation.Configure(settings, 3, 7, 30000);
  2196.                 }
  2197.  
  2198.                 public static ulong CoolDown(ulong playerid, int itemid)
  2199.                 {
  2200.                     history[playerid] = itemid;
  2201.  
  2202.                     return violation.Cooldown(playerid);
  2203.                 }
  2204.  
  2205.                 public static void Subscribe()
  2206.                 {
  2207.                     if(config.AntiFlood.ItemDrop.Enabled)
  2208.                     {
  2209.                         Hooks.Subscribe(nameof(CanCraft));
  2210.                         Hooks.Subscribe(nameof(OnItemDropped));
  2211.                     }
  2212.                     else
  2213.                     {
  2214.                         Hooks.Unsubscribe(nameof(CanCraft));
  2215.                         Hooks.Unsubscribe(nameof(OnItemDropped));
  2216.                     }
  2217.                 }
  2218.  
  2219.                 public static bool Trigger(ulong playerid, int itemid)
  2220.                 {
  2221.                     if(history.ContainsKey(playerid))
  2222.                     {
  2223.                         if(history[playerid] == itemid)
  2224.                         {
  2225.                             if(config.Log.AntiFlood.ItemDrop)
  2226.                             {
  2227.                                 var playername = Text.Sanitize(BasePlayer.FindByID(playerid)?.displayName ?? Text.Get(Key.unknown));
  2228.  
  2229.                                 var itemtext = ItemManager.FindItemDefinition(itemid)?.displayName;
  2230.  
  2231.                                 var itemname = itemtext?.translated ?? itemtext?.english ?? Text.Get(Key.unknown);
  2232.  
  2233.                                 Log.Console(Key.LogAntiSpamItemDrop, new Dictionary<string, string>
  2234.                                 {
  2235.                                     { "itemid", itemid.ToString() },
  2236.                                     { "itemname", itemname },
  2237.                                     { "playerid", playerid.ToString() },
  2238.                                     { "playername", playername }
  2239.                                 });
  2240.                             }
  2241.  
  2242.                             return config.AntiFlood.ItemDrop.Enabled && violation.Trigger(playerid);
  2243.                         }
  2244.                     }
  2245.  
  2246.                     return false;
  2247.                 }
  2248.  
  2249.                 public static void Unload()
  2250.                 {
  2251.                     history.Clear();
  2252.  
  2253.                     violation.Clear();
  2254.                 }
  2255.  
  2256.                 public static void Violation(BasePlayer player, string details)
  2257.                 {
  2258.                     violation.Trigger(player.userID, type, details);
  2259.                 }
  2260.             }
  2261.         }
  2262.  
  2263.         #endregion _antiflood_
  2264.  
  2265.         #region _api_
  2266.  
  2267.         public class API
  2268.         {
  2269.             public class Settings
  2270.             {
  2271.                 public string ApiKey;
  2272.                 public bool   Enabled;
  2273.  
  2274.                 public Settings()
  2275.                 {
  2276.                     ApiKey  = string.Empty;
  2277.                     Enabled = false;
  2278.                 }
  2279.  
  2280.                 public void Validate()
  2281.                 {
  2282.                     Configuration.Validate(ref ApiKey, () => { return string.Empty; });
  2283.                 }
  2284.             }
  2285.         }
  2286.  
  2287.         #endregion _api_
  2288.  
  2289.         #region _ban_
  2290.  
  2291.         public class Ban
  2292.         {
  2293.             public class Settings
  2294.             {
  2295.                 public bool    Inherit;
  2296.                 public bool    Teleport;
  2297.                 public BanTime Time;
  2298.  
  2299.                 public Settings()
  2300.                 {
  2301.                     Inherit  = true;
  2302.                     Teleport = false;
  2303.                     Time     = new BanTime();
  2304.                 }
  2305.  
  2306.                 public class BanTime
  2307.                 {
  2308.                     public bool  Enforce;
  2309.                     public bool  Multiply;
  2310.                     public ulong Seconds;
  2311.                 }
  2312.  
  2313.                 public void Validate()
  2314.                 {
  2315.                     Configuration.Validate(ref Time, () => new BanTime());
  2316.                 }
  2317.             }
  2318.         }
  2319.  
  2320.         #endregion _ban_
  2321.  
  2322.         #region _chat_
  2323.  
  2324.         private class Chat
  2325.         {
  2326.             public static void Admin(Key key, Dictionary<string, string> parameters = null)
  2327.             {
  2328.                 Log.Console(key, parameters);
  2329.  
  2330.                 if(config.Admin.Broadcast)
  2331.                 {
  2332.                     foreach(var player in BasePlayer.activePlayerList)
  2333.                     {
  2334.                         if(Permissions.Admin(player.userID))
  2335.                         {
  2336.                             Send(player, key, parameters);
  2337.                         }
  2338.                     }
  2339.                 }
  2340.             }
  2341.  
  2342.             public static void Broadcast(Key key, Dictionary<string, string> parameters = null)
  2343.             {
  2344.                 foreach(var player in BasePlayer.activePlayerList)
  2345.                 {
  2346.                     Send(player, key, parameters);
  2347.                 }
  2348.             }
  2349.  
  2350.             public static void Console(BasePlayer player, Key key, Dictionary<string, string> parameters = null)
  2351.             {
  2352.                 if((player == null) || !player.IsConnected)
  2353.                 {
  2354.                     return;
  2355.                 }
  2356.  
  2357.                 player.ConsoleMessage(Text.GetPlain(key, player, parameters));
  2358.             }
  2359.  
  2360.             public static void Reply(IPlayer iplayer, Key key, Dictionary<string, string> parameters = null)
  2361.             {
  2362.                 if((iplayer == null) || iplayer.IsServer)
  2363.                 {
  2364.                     Log.Console(key, parameters);
  2365.                 }
  2366.                 else
  2367.                 {
  2368.                     if(iplayer.LastCommand == CommandType.Console)
  2369.                     {
  2370.                         iplayer.Reply(Text.GetPlain(key, iplayer, parameters));
  2371.                     }
  2372.                     else
  2373.                     {
  2374.                         Send(iplayer.Object as BasePlayer, key, parameters);
  2375.                     }
  2376.                 }
  2377.             }
  2378.  
  2379.             public static void Send(BasePlayer player, Key key, Dictionary<string, string> parameters = null)
  2380.             {
  2381.                 if((player == null) || !player.IsConnected)
  2382.                 {
  2383.                     return;
  2384.                 }
  2385.  
  2386.                 var message = Text.Get(key, player, parameters);
  2387.  
  2388.                 player.SendConsoleCommand("chat.add", 0, 76561199125814167UL, message);
  2389.             }
  2390.         }
  2391.  
  2392.         #endregion _chat_
  2393.  
  2394.         #region _command_
  2395.  
  2396.         private void CommandReceive(IPlayer iplayer, string command, string[] args) => Command.Receive(iplayer, command, args);
  2397.  
  2398.         private class Command
  2399.         {
  2400.             private static readonly Dictionary<string, Info> commands = new Dictionary<string, Info>();
  2401.  
  2402.             private static List<Info> info;
  2403.  
  2404.             private class Info
  2405.             {
  2406.                 public Action<IPlayer, string, string[]> Action { get; protected set; }
  2407.                 public List<string>                      Aliases { get; protected set; }
  2408.                 public Key                               Title { get; protected set; }
  2409.  
  2410.                 public Info(Action<IPlayer, string, string[]> action, Key title, params string[] aliases)
  2411.                 {
  2412.                     Action  = action;
  2413.                     Aliases = new List<string>(aliases);
  2414.                     Title   = title;
  2415.  
  2416.                     foreach(var alias in aliases)
  2417.                     {
  2418.                         commands.Add(alias, this);
  2419.                     }
  2420.                 }
  2421.             }
  2422.  
  2423.             public static void Load()
  2424.             {
  2425.                 info = new List<Info>
  2426.                 {
  2427.                     new Info(Config,   Key.CommandConfigTitle,   "g.config", "guardian.config"),
  2428.                     new Info(Help,     Key.CommandHelpTitle,     "g.help", "guardian", "guardian.help"),
  2429.                     new Info(Ip,       Key.CommandIpTitle,       "g.ip", "guardian.ip"),
  2430.                     new Info(Log,      Key.CommandLogTitle,      "g.log", "guardian.log"),
  2431.                     new Info(Server,   Key.CommandServerTitle,   "g.server", "guardian.server"),
  2432.                     new Info(Teleport, Key.CommandTeleportTitle, "g.tp", "g.tpv", "guardian.tp", "guardian.tpv"),
  2433.                     new Info(Users,    Key.CommandUserTitle,     "g.user", "guardian.user"),
  2434.                     new Info(Vpn,      Key.CommandVpnTitle,      "g.vpn", "guardian.vpn")
  2435.                 };
  2436.  
  2437.                 foreach(var command in commands)
  2438.                 {
  2439.                     _instance.AddCovalenceCommand(command.Key, nameof(CommandReceive));
  2440.                 }
  2441.             }
  2442.  
  2443.             public static void Receive(IPlayer iplayer, string command, string[] args)
  2444.             {
  2445.                 Info entry;
  2446.  
  2447.                 if(commands.TryGetValue(command = command.ToLower(), out entry))
  2448.                 {
  2449.                     entry.Action(iplayer, command, args);
  2450.                 }
  2451.                 else
  2452.                 {
  2453.                     Chat.Reply(iplayer, Key.CommandUnknown, new Dictionary<string, string>
  2454.                     {
  2455.                         { "command", (iplayer.LastCommand == CommandType.Console) ? command : ("/" + command) }
  2456.                     });
  2457.                 }
  2458.             }
  2459.  
  2460.             public static void Unload()
  2461.             {
  2462.                 commands.Clear();
  2463.  
  2464.                 info.Clear();
  2465.                 info = null;
  2466.             }
  2467.  
  2468.             private static void Config(IPlayer iplayer, string command, string[] args)
  2469.             {
  2470.                 var command_replace = new Dictionary<string, string>
  2471.                 {
  2472.                     { "command", (iplayer.LastCommand == CommandType.Console) ? command : ("/" + command) }
  2473.                 };
  2474.  
  2475.                 BasePlayer player = iplayer.IsServer ? null : iplayer.Object as BasePlayer;
  2476.  
  2477.                 if((player != null) && !Permissions.Command.Config(player.userID))
  2478.                 {
  2479.                     Chat.Reply(iplayer, Key.CommandNoPermission);
  2480.  
  2481.                     return;
  2482.                 }
  2483.                 else if(args.Length == 0)
  2484.                 {
  2485.                     goto syntax_error;
  2486.                 }
  2487.  
  2488.                 var subsection = args[0].ToLower();
  2489.                 var subcommand = subsection.Split('.')[0];
  2490.  
  2491.                 bool success = false;
  2492.  
  2493.                 switch(subcommand)
  2494.                 {
  2495.                 case "admin":
  2496.                     switch(subsection)
  2497.                     {
  2498.                     case "admin.broadcast": success = Config(iplayer, args, subsection, ref config.Admin.Broadcast); break;
  2499.                     case "admin.bypass":    success = Config(iplayer, args, subsection, ref config.Admin.Bypass);    break;
  2500.                     }
  2501.  
  2502.                     if(!success) Chat.Reply(iplayer, Key.CommandConfigAdminSyntax, command_replace);
  2503.  
  2504.                     return;
  2505.  
  2506.                 case "anticheat":
  2507.                     switch(Config(subsection))
  2508.                     {
  2509.                     case "anticheat.aim":
  2510.                         switch(subsection)
  2511.                         {
  2512.                         case "anticheat.aim.ban":                success = Config(iplayer, args, subsection, ref config.AntiCheat.Aim.Ban,         AntiCheat.Aim.Configure); break;
  2513.                         case "anticheat.aim.cooldown":           success = Config(iplayer, args, subsection, ref config.AntiCheat.Aim.Cooldown,    AntiCheat.Aim.Configure); break;
  2514.                         case "anticheat.aim.enabled":            success = Config(iplayer, args, subsection, ref config.AntiCheat.Aim.Enabled);                              break;
  2515.                         case "anticheat.aim.sensitivity":        success = Config(iplayer, args, subsection, ref config.AntiCheat.Aim.Sensitivity, AntiCheat.Aim.Configure); break;
  2516.                         case "anticheat.aim.trigger.animal":     success = Config(iplayer, args, subsection, ref config.AntiCheat.Aim.Trigger.Animal);                       break;
  2517.                         case "anticheat.aim.trigger.bradley":    success = Config(iplayer, args, subsection, ref config.AntiCheat.Aim.Trigger.Bradley);                      break;
  2518.                         case "anticheat.aim.trigger.helicopter": success = Config(iplayer, args, subsection, ref config.AntiCheat.Aim.Trigger.Helicopter);                   break;
  2519.                         case "anticheat.aim.trigger.npc":        success = Config(iplayer, args, subsection, ref config.AntiCheat.Aim.Trigger.NPC);                          break;
  2520.                         case "anticheat.aim.warn":               success = Config(iplayer, args, subsection, ref config.AntiCheat.Aim.Warn,        AntiCheat.Aim.Configure); break;
  2521.                         }
  2522.  
  2523.                         if(!success) Chat.Reply(iplayer, Key.CommandConfigAntiCheatAimSyntax, command_replace);
  2524.  
  2525.                         return;
  2526.  
  2527.                     case "anticheat.firerate":
  2528.                         switch(subsection)
  2529.                         {
  2530.                         case "anticheat.firerate.ban":         success = Config(iplayer, args, subsection, ref config.AntiCheat.FireRate.Ban,         AntiCheat.FireRate.Configure); break;
  2531.                         case "anticheat.firerate.cooldown":    success = Config(iplayer, args, subsection, ref config.AntiCheat.FireRate.Cooldown,    AntiCheat.FireRate.Configure); break;
  2532.                         case "anticheat.firerate.enabled":     success = Config(iplayer, args, subsection, ref config.AntiCheat.FireRate.Enabled);                                   break;
  2533.                         case "anticheat.firerate.sensitivity": success = Config(iplayer, args, subsection, ref config.AntiCheat.FireRate.Sensitivity, AntiCheat.FireRate.Configure); break;
  2534.                         case "anticheat.firerate.warn":        success = Config(iplayer, args, subsection, ref config.AntiCheat.FireRate.Warn,        AntiCheat.FireRate.Configure); break;
  2535.                         }
  2536.  
  2537.                         if(!success) Chat.Reply(iplayer, Key.CommandConfigAntiCheatFireRateSyntax, command_replace);
  2538.  
  2539.                         return;
  2540.  
  2541.                     case "anticheat.gravity":
  2542.                         switch(subsection)
  2543.                         {
  2544.                         case "anticheat.gravity.ban":          success = Config(iplayer, args, subsection, ref config.AntiCheat.Gravity.Ban,         AntiCheat.Gravity.Configure); break;
  2545.                         case "anticheat.gravity.cooldown":     success = Config(iplayer, args, subsection, ref config.AntiCheat.Gravity.Cooldown,    AntiCheat.Gravity.Configure); break;
  2546.                         case "anticheat.gravity.enabled":      success = Config(iplayer, args, subsection, ref config.AntiCheat.Gravity.Enabled);                                  break;
  2547.                         case "anticheat.gravity.sensitivity":  success = Config(iplayer, args, subsection, ref config.AntiCheat.Gravity.Sensitivity, AntiCheat.Gravity.Configure); break;
  2548.                         case "anticheat.gravity.warn":         success = Config(iplayer, args, subsection, ref config.AntiCheat.Gravity.Warn,        AntiCheat.Gravity.Configure); break;
  2549.                         }
  2550.  
  2551.                         if(!success) Chat.Reply(iplayer, Key.CommandConfigAntiCheatGravitySyntax, command_replace);
  2552.  
  2553.                         return;
  2554.  
  2555.                     case "anticheat.meleerate":
  2556.                         switch(subsection)
  2557.                         {
  2558.                         case "anticheat.meleerate.ban":         success = Config(iplayer, args, subsection, ref config.AntiCheat.MeleeRate.Ban,         AntiCheat.MeleeRate.Configure); break;
  2559.                         case "anticheat.meleerate.cooldown":    success = Config(iplayer, args, subsection, ref config.AntiCheat.MeleeRate.Cooldown,    AntiCheat.MeleeRate.Configure); break;
  2560.                         case "anticheat.meleerate.enabled":     success = Config(iplayer, args, subsection, ref config.AntiCheat.MeleeRate.Enabled);                                    break;
  2561.                         case "anticheat.meleerate.sensitivity": success = Config(iplayer, args, subsection, ref config.AntiCheat.MeleeRate.Sensitivity, AntiCheat.MeleeRate.Configure); break;
  2562.                         case "anticheat.meleerate.warn":        success = Config(iplayer, args, subsection, ref config.AntiCheat.MeleeRate.Warn,        AntiCheat.MeleeRate.Configure); break;
  2563.                         }
  2564.  
  2565.                         if(!success) Chat.Reply(iplayer, Key.CommandConfigAntiCheatMeleeRateSyntax, command_replace);
  2566.  
  2567.                         return;
  2568.  
  2569.                     case "anticheat.recoil":
  2570.                         switch(subsection)
  2571.                         {
  2572.                         case "anticheat.recoil.ban":         success = Config(iplayer, args, subsection, ref config.AntiCheat.Recoil.Ban,         AntiCheat.Recoil.Configure); break;
  2573.                         case "anticheat.recoil.cooldown":    success = Config(iplayer, args, subsection, ref config.AntiCheat.Recoil.Cooldown,    AntiCheat.Recoil.Configure); break;
  2574.                         case "anticheat.recoil.enabled":     success = Config(iplayer, args, subsection, ref config.AntiCheat.Recoil.Enabled);                                 break;
  2575.                         case "anticheat.recoil.sensitivity": success = Config(iplayer, args, subsection, ref config.AntiCheat.Recoil.Sensitivity, AntiCheat.Recoil.Configure); break;
  2576.                         case "anticheat.recoil.warn":        success = Config(iplayer, args, subsection, ref config.AntiCheat.Recoil.Warn,        AntiCheat.Recoil.Configure); break;
  2577.                         }
  2578.  
  2579.                         if(!success) Chat.Reply(iplayer, Key.CommandConfigAntiCheatRecoilSyntax, command_replace);
  2580.  
  2581.                         return;
  2582.  
  2583.                     case "anticheat.server":
  2584.                         switch(subsection)
  2585.                         {
  2586.                         case "anticheat.server.ban":         success = Config(iplayer, args, subsection, ref config.AntiCheat.Server.Ban,         AntiCheat.Server.Configure); break;
  2587.                         case "anticheat.server.cooldown":    success = Config(iplayer, args, subsection, ref config.AntiCheat.Server.Cooldown,    AntiCheat.Server.Configure); break;
  2588.                         case "anticheat.server.enabled":     success = Config(iplayer, args, subsection, ref config.AntiCheat.Server.Enabled);                                 break;
  2589.                         case "anticheat.server.sensitivity": success = Config(iplayer, args, subsection, ref config.AntiCheat.Server.Sensitivity, AntiCheat.Server.Configure); break;
  2590.                         case "anticheat.server.warn":        success = Config(iplayer, args, subsection, ref config.AntiCheat.Server.Warn,        AntiCheat.Server.Configure); break;
  2591.                         }
  2592.  
  2593.                         if(!success) Chat.Reply(iplayer, Key.CommandConfigAntiCheatServerSyntax, command_replace);
  2594.  
  2595.                         return;
  2596.  
  2597.                     case "anticheat.stash":
  2598.                         switch(subsection)
  2599.                         {
  2600.                         case "anticheat.stash.ban":         success = Config(iplayer, args, subsection, ref config.AntiCheat.Stash.Ban,         AntiCheat.Stash.Configure); break;
  2601.                         case "anticheat.stash.cooldown":    success = Config(iplayer, args, subsection, ref config.AntiCheat.Stash.Cooldown,    AntiCheat.Stash.Configure); break;
  2602.                         case "anticheat.stash.enabled":     success = Config(iplayer, args, subsection, ref config.AntiCheat.Stash.Enabled,     AntiCheat.Stash.Subscribe); break;
  2603.                         case "anticheat.stash.sensitivity": success = Config(iplayer, args, subsection, ref config.AntiCheat.Stash.Sensitivity, AntiCheat.Stash.Configure); break;
  2604.                         case "anticheat.stash.warn":        success = Config(iplayer, args, subsection, ref config.AntiCheat.Stash.Warn,        AntiCheat.Stash.Configure); break;
  2605.                         }
  2606.  
  2607.                         if(!success) Chat.Reply(iplayer, Key.CommandConfigAntiCheatStashSyntax, command_replace);
  2608.  
  2609.                         return;
  2610.  
  2611.                     case "anticheat.trajectory":
  2612.                         switch(subsection)
  2613.                         {
  2614.                         case "anticheat.trajectory.ban":         success = Config(iplayer, args, subsection, ref config.AntiCheat.Trajectory.Ban,         AntiCheat.Trajectory.Configure); break;
  2615.                         case "anticheat.trajectory.cooldown":    success = Config(iplayer, args, subsection, ref config.AntiCheat.Trajectory.Cooldown,    AntiCheat.Trajectory.Configure); break;
  2616.                         case "anticheat.trajectory.enabled":     success = Config(iplayer, args, subsection, ref config.AntiCheat.Trajectory.Enabled);                                     break;
  2617.                         case "anticheat.trajectory.sensitivity": success = Config(iplayer, args, subsection, ref config.AntiCheat.Trajectory.Sensitivity, AntiCheat.Trajectory.Configure); break;
  2618.                         case "anticheat.trajectory.warn":        success = Config(iplayer, args, subsection, ref config.AntiCheat.Trajectory.Warn,        AntiCheat.Trajectory.Configure); break;
  2619.                         }
  2620.  
  2621.                         if(!success) Chat.Reply(iplayer, Key.CommandConfigAntiCheatTrajectorySyntax, command_replace);
  2622.  
  2623.                         return;
  2624.  
  2625.                     case "anticheat.wallhack":
  2626.                         switch(subsection)
  2627.                         {
  2628.                         case "anticheat.wallhack.ban":         success = Config(iplayer, args, subsection, ref config.AntiCheat.WallHack.Ban,         AntiCheat.WallHack.Configure); break;
  2629.                         case "anticheat.wallhack.cooldown":    success = Config(iplayer, args, subsection, ref config.AntiCheat.WallHack.Cooldown,    AntiCheat.WallHack.Configure); break;
  2630.                         case "anticheat.wallhack.enabled":     success = Config(iplayer, args, subsection, ref config.AntiCheat.WallHack.Enabled);                                   break;
  2631.                         case "anticheat.wallhack.sensitivity": success = Config(iplayer, args, subsection, ref config.AntiCheat.WallHack.Sensitivity, AntiCheat.WallHack.Configure); break;
  2632.                         case "anticheat.wallhack.warn":        success = Config(iplayer, args, subsection, ref config.AntiCheat.WallHack.Warn,        AntiCheat.WallHack.Configure); break;
  2633.                         }
  2634.  
  2635.                         if(!success) Chat.Reply(iplayer, Key.CommandConfigAntiCheatWallHackSyntax, command_replace);
  2636.  
  2637.                         return;
  2638.                     }
  2639.  
  2640.                     Chat.Reply(iplayer, Key.CommandConfigAntiCheatSyntax, command_replace);
  2641.  
  2642.                     return;
  2643.  
  2644.                 case "antiflood":
  2645.                     switch(Config(subsection))
  2646.                     {
  2647.                     case "antiflood.chat":
  2648.                         switch(subsection)
  2649.                         {
  2650.                         case "antiflood.chat.ban":         success = Config(iplayer, args, subsection, ref config.AntiFlood.Chat.Ban,         AntiFlood.Chat.Configure); break;
  2651.                         case "antiflood.chat.cooldown":    success = Config(iplayer, args, subsection, ref config.AntiFlood.Chat.Cooldown,    AntiFlood.Chat.Configure); break;
  2652.                         case "antiflood.chat.enabled":     success = Config(iplayer, args, subsection, ref config.AntiFlood.Chat.Enabled,     AntiFlood.Chat.Subscribe); break;
  2653.                         case "antiflood.chat.sensitivity": success = Config(iplayer, args, subsection, ref config.AntiFlood.Chat.Sensitivity, AntiFlood.Chat.Configure); break;
  2654.                         case "antiflood.chat.warn":        success = Config(iplayer, args, subsection, ref config.AntiFlood.Chat.Warn,        AntiFlood.Chat.Configure); break;
  2655.                         }
  2656.  
  2657.                         if(!success) Chat.Reply(iplayer, Key.CommandConfigAntiFloodChatSyntax, command_replace);
  2658.  
  2659.                         return;
  2660.  
  2661.                     case "antiflood.command":
  2662.                         switch(subsection)
  2663.                         {
  2664.                         case "antiflood.command.ban":         success = Config(iplayer, args, subsection, ref config.AntiFlood.Command.Ban,         AntiFlood.Command.Configure); break;
  2665.                         case "antiflood.command.cooldown":    success = Config(iplayer, args, subsection, ref config.AntiFlood.Command.Cooldown,    AntiFlood.Command.Configure); break;
  2666.                         case "antiflood.command.enabled":     success = Config(iplayer, args, subsection, ref config.AntiFlood.Command.Enabled,     AntiFlood.Command.Subscribe); break;
  2667.                         case "antiflood.command.sensitivity": success = Config(iplayer, args, subsection, ref config.AntiFlood.Command.Sensitivity, AntiFlood.Command.Configure); break;
  2668.                         case "antiflood.command.warn":        success = Config(iplayer, args, subsection, ref config.AntiFlood.Command.Warn,        AntiFlood.Command.Configure); break;
  2669.                         }
  2670.  
  2671.                         if(!success) Chat.Reply(iplayer, Key.CommandConfigAntiFloodCommandSyntax, command_replace);
  2672.  
  2673.                         return;
  2674.  
  2675.                     case "antiflood.itemdrop":
  2676.                         switch(subsection)
  2677.                         {
  2678.                         case "antiflood.itemdrop.ban":         success = Config(iplayer, args, subsection, ref config.AntiFlood.ItemDrop.Ban,         AntiFlood.ItemDrop.Configure); break;
  2679.                         case "antiflood.itemdrop.cooldown":    success = Config(iplayer, args, subsection, ref config.AntiFlood.ItemDrop.Cooldown,    AntiFlood.ItemDrop.Configure); break;
  2680.                         case "antiflood.itemdrop.enabled":     success = Config(iplayer, args, subsection, ref config.AntiFlood.ItemDrop.Enabled,     AntiFlood.ItemDrop.Subscribe); break;
  2681.                         case "antiflood.itemdrop.sensitivity": success = Config(iplayer, args, subsection, ref config.AntiFlood.ItemDrop.Sensitivity, AntiFlood.ItemDrop.Configure); break;
  2682.                         case "antiflood.itemdrop.warn":        success = Config(iplayer, args, subsection, ref config.AntiFlood.ItemDrop.Warn,        AntiFlood.ItemDrop.Configure); break;
  2683.                         }
  2684.  
  2685.                         if(!success) Chat.Reply(iplayer, Key.CommandConfigAntiFloodItemDropSyntax, command_replace);
  2686.  
  2687.                         return;
  2688.                     }
  2689.  
  2690.                     Chat.Reply(iplayer, Key.CommandConfigAntiFloodSyntax, command_replace);
  2691.  
  2692.                     return;
  2693.  
  2694.                 case "ban":
  2695.                     switch(subsection)
  2696.                     {
  2697.                     case "ban.inherit":       success = Config(iplayer, args, subsection, ref config.Ban.Inherit); break;
  2698.                     case "ban.teleport":      success = Config(iplayer, args, subsection, ref config.Ban.Teleport); break;
  2699.                     case "ban.time.enforce":  success = Config(iplayer, args, subsection, ref config.Ban.Time.Enforce); break;
  2700.                     case "ban.time.multiply": success = Config(iplayer, args, subsection, ref config.Ban.Time.Multiply); break;
  2701.                     case "ban.time.seconds":  success = Config(iplayer, args, subsection, ref config.Ban.Time.Seconds); break;
  2702.                     }
  2703.  
  2704.                     if(!success) Chat.Reply(iplayer, Key.CommandConfigBanSyntax, command_replace);
  2705.  
  2706.                     return;
  2707.  
  2708.                 case "cripple":
  2709.                     switch(subsection)
  2710.                     {
  2711.                     case "cripple.heal":    success = Config(iplayer, args, subsection, ref config.Cripple.Heal); break;
  2712.                     case "cripple.inherit": success = Config(iplayer, args, subsection, ref config.Cripple.Inherit); break;
  2713.                     }
  2714.  
  2715.                     if(!success) Chat.Reply(iplayer, Key.CommandConfigCrippleSyntax, command_replace);
  2716.  
  2717.                     return;
  2718.  
  2719.                 case "discord":
  2720.                     switch(subsection)
  2721.                     {
  2722.                     case "discord.enabled":                   success = Config(iplayer, args, subsection, ref config.Discord.Enabled, () => Discord.Subscribe()); break;
  2723.                     case "discord.webhook":                   success = Config(iplayer, args, subsection, ref config.Discord.WebHook); break;
  2724.                     case "discord.filters.anticheat.enabled": success = Config(iplayer, args, subsection, ref config.Discord.Filters.AntiCheat.Enabled); break;
  2725.                     case "discord.filters.anticheat.webhook": success = Config(iplayer, args, subsection, ref config.Discord.Filters.AntiCheat.WebHook); break;
  2726.                     case "discord.filters.antiflood.enabled": success = Config(iplayer, args, subsection, ref config.Discord.Filters.AntiFlood.Enabled); break;
  2727.                     case "discord.filters.antiflood.webhook": success = Config(iplayer, args, subsection, ref config.Discord.Filters.AntiFlood.WebHook); break;
  2728.                     case "discord.filters.ip.enabled":        success = Config(iplayer, args, subsection, ref config.Discord.Filters.IP.Enabled); break;
  2729.                     case "discord.filters.ip.webhook":        success = Config(iplayer, args, subsection, ref config.Discord.Filters.IP.WebHook); break;
  2730.                     case "discord.filters.steam.enabled":     success = Config(iplayer, args, subsection, ref config.Discord.Filters.Steam.Enabled); break;
  2731.                     case "discord.filters.steam.webhook":     success = Config(iplayer, args, subsection, ref config.Discord.Filters.Steam.WebHook); break;
  2732.                     case "discord.filters.vpn.enabled":       success = Config(iplayer, args, subsection, ref config.Discord.Filters.VPN.Enabled); break;
  2733.                     case "discord.filters.vpn.webhook":       success = Config(iplayer, args, subsection, ref config.Discord.Filters.VPN.WebHook); break;
  2734.                     }
  2735.  
  2736.                     if(!success) Chat.Reply(iplayer, Key.CommandConfigDiscordSyntax, command_replace);
  2737.  
  2738.                     return;
  2739.  
  2740.                 case "entity":
  2741.                     switch(subsection)
  2742.                     {
  2743.                     case "entity.damage.animal":     success = Config(iplayer, args, subsection, ref config.Entity.Damage.Animal,     () => config.Entity.Damage.Validate()); break;
  2744.                     case "entity.damage.bradley":    success = Config(iplayer, args, subsection, ref config.Entity.Damage.Bradley,    () => config.Entity.Damage.Validate()); break;
  2745.                     case "entity.damage.building":   success = Config(iplayer, args, subsection, ref config.Entity.Damage.Building,   () => config.Entity.Damage.Validate()); break;
  2746.                     case "entity.damage.entity":     success = Config(iplayer, args, subsection, ref config.Entity.Damage.Entity,     () => config.Entity.Damage.Validate()); break;
  2747.                     case "entity.damage.friend":     success = Config(iplayer, args, subsection, ref config.Entity.Damage.Friend,     () => config.Entity.Damage.Validate()); break;
  2748.                     case "entity.damage.helicopter": success = Config(iplayer, args, subsection, ref config.Entity.Damage.Helicopter, () => config.Entity.Damage.Validate()); break;
  2749.                     case "entity.damage.npc":        success = Config(iplayer, args, subsection, ref config.Entity.Damage.NPC,        () => config.Entity.Damage.Validate()); break;
  2750.                     case "entity.damage.player":     success = Config(iplayer, args, subsection, ref config.Entity.Damage.Player,     () => config.Entity.Damage.Validate()); break;
  2751.                     case "entity.damage.team":       success = Config(iplayer, args, subsection, ref config.Entity.Damage.Team,       () => config.Entity.Damage.Validate()); break;
  2752.                     case "entity.damage.trap":       success = Config(iplayer, args, subsection, ref config.Entity.Damage.Trap,       () => config.Entity.Damage.Validate()); break;
  2753.                     }
  2754.  
  2755.                     if(!success) Chat.Reply(iplayer, Key.CommandConfigEntitySyntax, command_replace);
  2756.  
  2757.                     return;
  2758.  
  2759.                 case "ip":
  2760.                     switch(subsection)
  2761.                     {
  2762.                     case "ip.filter.cooldown":   success = Config(iplayer, args, subsection, ref config.IP.Filter.Cooldown, IP.Configure); break;
  2763.                     case "ip.violation.ban":     success = Config(iplayer, args, subsection, ref config.IP.Violation.Ban); break;
  2764.                     case "ip.violation.enabled": success = Config(iplayer, args, subsection, ref config.IP.Violation.Enabled); break;
  2765.                     }
  2766.  
  2767.                     if(!success) Chat.Reply(iplayer, Key.CommandConfigIpSyntax, command_replace);
  2768.  
  2769.                     return;
  2770.  
  2771.                 case "log":
  2772.                     switch(subsection)
  2773.                     {
  2774.                     case "log.anticheat.aim":        success = Config(iplayer, args, subsection, ref config.Log.AntiCheat.Aim); break;
  2775.                     case "log.anticheat.gravity":    success = Config(iplayer, args, subsection, ref config.Log.AntiCheat.Gravity); break;
  2776.                     case "log.anticheat.meleerate":  success = Config(iplayer, args, subsection, ref config.Log.AntiCheat.MeleeRate); break;
  2777.                     case "log.anticheat.recoil":     success = Config(iplayer, args, subsection, ref config.Log.AntiCheat.Recoil); break;
  2778.                     case "log.anticheat.server":     success = Config(iplayer, args, subsection, ref config.Log.AntiCheat.Server); break;
  2779.                     case "log.anticheat.stash":      success = Config(iplayer, args, subsection, ref config.Log.AntiCheat.Stash); break;
  2780.                     case "log.anticheat.trajectory": success = Config(iplayer, args, subsection, ref config.Log.AntiCheat.Trajectory); break;
  2781.                     case "log.antiflood.itemdrop":   success = Config(iplayer, args, subsection, ref config.Log.AntiFlood.ItemDrop); break;
  2782.                     case "log.ip.filter":            success = Config(iplayer, args, subsection, ref config.Log.IP.Filter); break;
  2783.                     case "log.projectile.collapse":  success = Config(iplayer, args, subsection, ref config.Log.Projectile.Collapse); break;
  2784.                     case "log.projectile.verbose":   success = Config(iplayer, args, subsection, ref config.Log.Projectile.Verbose); break;
  2785.                     case "log.user.bypass":          success = Config(iplayer, args, subsection, ref config.Log.User.Bypass); break;
  2786.                     case "log.user.connect":         success = Config(iplayer, args, subsection, ref config.Log.User.Connect); break;
  2787.                     case "log.vpn.check":            success = Config(iplayer, args, subsection, ref config.Log.VPN.Check); break;
  2788.                     }
  2789.  
  2790.                     if(!success) Chat.Reply(iplayer, Key.CommandConfigLogSyntax, command_replace);
  2791.  
  2792.                     return;
  2793.  
  2794.                 case "save":
  2795.                     if((args.Length == 1) && (subsection == "save"))
  2796.                     {
  2797.                         Configuration.Save();
  2798.  
  2799.                         Chat.Reply(iplayer, Key.CommandConfig, new Dictionary<string, string>
  2800.                         {
  2801.                             { "action", Text.GetPlain(Key.Saved, iplayer) },
  2802.                             { "info", Text.GetPlain(Key.file, iplayer) }
  2803.                         });
  2804.  
  2805.                         return;
  2806.                     }
  2807.  
  2808.                     Chat.Reply(iplayer, Key.CommandConfigSaveSyntax, command_replace);
  2809.  
  2810.                     return;
  2811.  
  2812.                 case "steam":
  2813.                     switch(subsection)
  2814.                     {
  2815.                     case "steam.api.apikey":        success = Config(iplayer, args, subsection, ref config.Steam.API.ApiKey,        Steam.Configure); break;
  2816.                     case "steam.api.enabled":       success = Config(iplayer, args, subsection, ref config.Steam.API.Enabled,       Steam.Configure); break;
  2817.                     case "steam.ban.active":        success = Config(iplayer, args, subsection, ref config.Steam.Ban.Active,        Steam.Configure); break;
  2818.                     case "steam.ban.community":     success = Config(iplayer, args, subsection, ref config.Steam.Ban.Community,     Steam.Configure); break;
  2819.                     case "steam.ban.days":          success = Config(iplayer, args, subsection, ref config.Steam.Ban.Days,          Steam.Configure); break;
  2820.                     case "steam.ban.economy":       success = Config(iplayer, args, subsection, ref config.Steam.Ban.Economy,       Steam.Configure); break;
  2821.                     case "steam.ban.game":          success = Config(iplayer, args, subsection, ref config.Steam.Ban.Game,          Steam.Configure); break;
  2822.                     case "steam.ban.vac":           success = Config(iplayer, args, subsection, ref config.Steam.Ban.VAC,           Steam.Configure); break;
  2823.                     case "steam.game.count":        success = Config(iplayer, args, subsection, ref config.Steam.Game.Count,        Steam.Configure); break;
  2824.                     case "steam.game.hours":        success = Config(iplayer, args, subsection, ref config.Steam.Game.Hours,        Steam.Configure); break;
  2825.                     case "steam.profile.invalid":   success = Config(iplayer, args, subsection, ref config.Steam.Profile.Invalid,   Steam.Configure); break;
  2826.                     case "steam.profile.limited":   success = Config(iplayer, args, subsection, ref config.Steam.Profile.Limited,   Steam.Configure); break;
  2827.                     case "steam.profile.private":   success = Config(iplayer, args, subsection, ref config.Steam.Profile.Private,   Steam.Configure); break;
  2828.                     case "steam.share.family":      success = Config(iplayer, args, subsection, ref config.Steam.Share.Family,      Steam.Configure); break;
  2829.                     case "steam.violation.ban":     success = Config(iplayer, args, subsection, ref config.Steam.Violation.Ban); break;
  2830.                     case "steam.violation.enabled": success = Config(iplayer, args, subsection, ref config.Steam.Violation.Enabled); break;
  2831.                     case "steam.violation.warn":    success = Config(iplayer, args, subsection, ref config.Steam.Violation.Warn, Steam.Configure); break;
  2832.                     }
  2833.  
  2834.                     if(!success) Chat.Reply(iplayer, Key.CommandConfigSteamSyntax, command_replace);
  2835.  
  2836.                     return;
  2837.  
  2838.                 case "user":
  2839.                     switch(subsection)
  2840.                     {
  2841.                     case "user.bypass.dayssincelastban": success = Config(iplayer, args, subsection, ref config.User.Bypass.DaysSinceBan); break;
  2842.                     case "user.bypass.enabled":          success = Config(iplayer, args, subsection, ref config.User.Bypass.Enabled); break;
  2843.                     case "user.bypass.hoursplayed":      success = Config(iplayer, args, subsection, ref config.User.Bypass.HoursPlayed); break;
  2844.                     case "user.bypass.multiply":         success = Config(iplayer, args, subsection, ref config.User.Bypass.Multiply); break;
  2845.                     case "user.friend.damage":           success = Config(iplayer, args, subsection, ref config.User.Friend.Damage); break;
  2846.                     case "user.team.damage":             success = Config(iplayer, args, subsection, ref config.User.Team.Damage); break;
  2847.                     }
  2848.  
  2849.                     if(!success) Chat.Reply(iplayer, Key.CommandConfigUserSyntax, command_replace);
  2850.  
  2851.                     return;
  2852.                 case "violation":
  2853.                     switch(subsection)
  2854.                     {
  2855.                     case "violation.ban":         success = Config(iplayer, args, subsection, ref config.Violation.Ban,         Violation.Configure); break;
  2856.                     case "violation.cooldown":    success = Config(iplayer, args, subsection, ref config.Violation.Cooldown,    Violation.Configure); break;
  2857.                     case "violation.sensitivity": success = Config(iplayer, args, subsection, ref config.Violation.Sensitivity, Violation.Configure); break;
  2858.                     }
  2859.  
  2860.                     if(!success) Chat.Reply(iplayer, Key.CommandConfigViolationSyntax, command_replace);
  2861.  
  2862.                     return;
  2863.  
  2864.                 case "vpn":
  2865.                     switch(subsection)
  2866.                     {
  2867.                     case "vpn.api.getipintel.apikey":      success = Config(iplayer, args, subsection, ref config.VPN.API.GetIpIntel.ApiKey); break;
  2868.                     case "vpn.api.getipintel.enabled":     success = Config(iplayer, args, subsection, ref config.VPN.API.GetIpIntel.Enabled); break;
  2869.                     case "vpn.api.ipapi.enabled":          success = Config(iplayer, args, subsection, ref config.VPN.API.IpApi.Enabled); break;
  2870.                     case "vpn.api.iphub.apikey":           success = Config(iplayer, args, subsection, ref config.VPN.API.IpHub.ApiKey, VPN.API.IpHub.Configure); break;
  2871.                     case "vpn.api.iphub.enabled":          success = Config(iplayer, args, subsection, ref config.VPN.API.IpHub.Enabled); break;
  2872.                     case "vpn.api.ipqualityscore.apikey":  success = Config(iplayer, args, subsection, ref config.VPN.API.IpQualityScore.ApiKey); break;
  2873.                     case "vpn.api.ipqualityscore.enabled": success = Config(iplayer, args, subsection, ref config.VPN.API.IpQualityScore.Enabled); break;
  2874.                     case "vpn.cache.hours":                success = Config(iplayer, args, subsection, ref config.VPN.Cache.Hours); break;
  2875.                     case "vpn.check.enabled":              success = Config(iplayer, args, subsection, ref config.VPN.Check.Enabled); break;
  2876.                     case "vpn.check.strict":               success = Config(iplayer, args, subsection, ref config.VPN.Check.Strict); break;
  2877.                     case "vpn.violation.ban":              success = Config(iplayer, args, subsection, ref config.VPN.Violation.Ban); break;
  2878.                     case "vpn.violation.enabled":          success = Config(iplayer, args, subsection, ref config.VPN.Violation.Enabled); break;
  2879.                     case "vpn.violation.warn":             success = Config(iplayer, args, subsection, ref config.VPN.Violation.Warn, VPN.Configure); break;
  2880.                     }
  2881.  
  2882.                     if(!success) Chat.Reply(iplayer, Key.CommandConfigVpnSyntax, command_replace);
  2883.  
  2884.                     return;
  2885.                 }
  2886.  
  2887.             syntax_error:
  2888.                 Chat.Reply(iplayer, Key.CommandConfigSyntax, command_replace);
  2889.             }
  2890.             private static string Config(string subsection)
  2891.             {
  2892.                 var split = subsection.Split('.');
  2893.  
  2894.                 if(split.Length >= 2)
  2895.                 {
  2896.                     return split[0] + "." + split[1];
  2897.                 }
  2898.  
  2899.                 return subsection;
  2900.             }
  2901.             private static bool Config<T>(IPlayer iplayer, string[] args, string setting, ref T value, Action callback = null)
  2902.             {
  2903.                 bool changed = false, success = false;
  2904.  
  2905.                 if(args.Length == 1)
  2906.                 {
  2907.                     success = true;
  2908.                 }
  2909.                 else if(args.Length == 2)
  2910.                 {
  2911.                     if(typeof(T) == typeof(bool))
  2912.                     {
  2913.                         bool new_value;
  2914.  
  2915.                         if(bool.TryParse(args[1], out new_value))
  2916.                         {
  2917.                             if(!value.Equals(new_value))
  2918.                             {
  2919.                                 changed = true; value = (T)Convert.ChangeType(new_value, typeof(T));
  2920.                             }
  2921.  
  2922.                             success = true;
  2923.                         }
  2924.                     }
  2925.                     else if(typeof(T) == typeof(float))
  2926.                     {
  2927.                         float new_value;
  2928.  
  2929.                         if(float.TryParse(args[1], out new_value))
  2930.                         {
  2931.                             if(!value.Equals(new_value))
  2932.                             {
  2933.                                 changed = true; value = (T)Convert.ChangeType(new_value, typeof(T));
  2934.                             }
  2935.  
  2936.                             success = true;
  2937.                         }
  2938.                     }
  2939.                     else if(typeof(T) == typeof(string))
  2940.                     {
  2941.                         string new_value = args[1];
  2942.  
  2943.                         if(!value.Equals(new_value))
  2944.                         {
  2945.                             changed = true; value = (T)Convert.ChangeType(new_value, typeof(T));
  2946.                         }
  2947.  
  2948.                         success = true;
  2949.                     }
  2950.                     else if(typeof(T) == typeof(ulong))
  2951.                     {
  2952.                         ulong new_value;
  2953.  
  2954.                         if(ulong.TryParse(args[1], out new_value))
  2955.                         {
  2956.                             if(!value.Equals(new_value))
  2957.                             {
  2958.                                 changed = true; value = (T)Convert.ChangeType(new_value, typeof(T));
  2959.                             }
  2960.  
  2961.                             success = true;
  2962.                         }
  2963.                     }
  2964.                     else
  2965.                     {
  2966.                         throw new NotImplementedException($"{_instance.Name}.Command.Config<T>: No conversion case exists for type T={typeof(T).Name}.");
  2967.                     }
  2968.                 }
  2969.  
  2970.                 if(success)
  2971.                 {
  2972.                     if(changed)
  2973.                     {
  2974.                         Configuration.SetDirty();
  2975.  
  2976.                         callback?.Invoke();
  2977.                     }
  2978.  
  2979.                     Chat.Reply(iplayer, Key.CommandConfig, new Dictionary<string, string>
  2980.                     {
  2981.                         { "action", Text.GetPlain(changed ? Key.Changed : Key.Current, iplayer) },
  2982.                         { "info", $"{setting}={value}" }
  2983.                     });
  2984.                 }
  2985.  
  2986.                 return success;
  2987.             }
  2988.  
  2989.             private static void Help(IPlayer iplayer, string command, string[] args)
  2990.             {
  2991.                 BasePlayer player = iplayer.IsServer ? null : iplayer.Object as BasePlayer;
  2992.  
  2993.                 if((player != null) && !Permissions.Admin(player.userID))
  2994.                 {
  2995.                     Chat.Reply(iplayer, Key.CommandNoPermission);
  2996.  
  2997.                     return;
  2998.                 }
  2999.  
  3000.                 var console = iplayer.IsServer || (iplayer.LastCommand == CommandType.Console);
  3001.  
  3002.                 StringBuilder aliases = new StringBuilder(), entries = new StringBuilder();
  3003.  
  3004.                 foreach(var entry in info)
  3005.                 {
  3006.                     foreach(var alias in entry.Aliases)
  3007.                     {
  3008.                         aliases.Append(console ? "\n    " : "\n    /").Append(alias);
  3009.                     }
  3010.  
  3011.                     entries.Append(Text.Get(Key.CommandHelpEntry, iplayer, new Dictionary<string, string>
  3012.                     {
  3013.                         { "aliases", aliases.ToString() },
  3014.                         { "title", Text.GetPlain(entry.Title, iplayer) }
  3015.                     }));
  3016.  
  3017.                     aliases.Clear();
  3018.                 }
  3019.  
  3020.                 Chat.Reply(iplayer, Key.CommandHelp, new Dictionary<string, string>
  3021.                 {
  3022.                     { "entries", entries.ToString() },
  3023.                     { "name", _instance.Name },
  3024.                     { "version", Version.String }
  3025.                 });
  3026.             }
  3027.  
  3028.             private static void Ip(IPlayer iplayer, string command, string[] args)
  3029.             {
  3030.                 BasePlayer player = iplayer.IsServer ? null : iplayer.Object as BasePlayer;
  3031.  
  3032.                 if((player != null) && !Permissions.Command.Ip(player.userID))
  3033.                 {
  3034.                     Chat.Reply(iplayer, Key.CommandNoPermission);
  3035.  
  3036.                     return;
  3037.                 }
  3038.                 else if(args.Length == 0)
  3039.                 {
  3040.                     goto syntax_error;
  3041.                 }
  3042.  
  3043.                 switch(args[0].ToLower())
  3044.                 {
  3045.                 case "allow":
  3046.                     if(args.Length == 1)
  3047.                     {
  3048.                         Ip(iplayer, Key.Allowed, IP.GetAllows());
  3049.  
  3050.                         return;
  3051.                     }
  3052.                     else if(args.Length == 2)
  3053.                     {
  3054.                         var network = IP.Network(args[1]);
  3055.  
  3056.                         if(network?.Address != null)
  3057.                         {
  3058.                             Ip(iplayer, Key.Allowed, IP.GetAllows(network.Address));
  3059.  
  3060.                             return;
  3061.                         }
  3062.                     }
  3063.                     else if(args.Length == 3)
  3064.                     {
  3065.                         var network = IP.Network(args[2]);
  3066.  
  3067.                         if(network?.Address != null)
  3068.                         {
  3069.                             switch(args[1].ToLower())
  3070.                             {
  3071.                             case "add":
  3072.                                 Ip(iplayer, Key.Allowed, network, true);
  3073.  
  3074.                                 return;
  3075.  
  3076.                             case "remove":
  3077.                                 Ip(iplayer, Key.Allowed, network, false);
  3078.  
  3079.                                 return;
  3080.                             }
  3081.                         }
  3082.                     }
  3083.  
  3084.                     break;
  3085.  
  3086.                 case "block":
  3087.                     if((args.Length == 2) && IP.IsValid(args[1]))
  3088.                     {
  3089.                         IP.Block(args[1]);
  3090.  
  3091.                         Chat.Reply(iplayer, Key.CommandIp, new Dictionary<string, string>
  3092.                         {
  3093.                             { "action", Text.GetPlain(Key.Blocked, iplayer) },
  3094.                             { "info", args[1] }
  3095.                         });
  3096.  
  3097.                         return;
  3098.                     }
  3099.  
  3100.                     break;
  3101.  
  3102.                 case "bypass":
  3103.                     if((args.Length == 2) && IP.IsValid(args[1]))
  3104.                     {
  3105.                         IP.Bypass(args[1]);
  3106.  
  3107.                         Chat.Reply(iplayer, Key.CommandIp, new Dictionary<string, string>
  3108.                         {
  3109.                             { "action", Text.GetPlain(Key.Bypassed, iplayer) },
  3110.                             { "info", args[1] }
  3111.                         });
  3112.  
  3113.                         return;
  3114.                     }
  3115.  
  3116.                     break;
  3117.  
  3118.                 case "deny":
  3119.                     if(args.Length == 1)
  3120.                     {
  3121.                         Ip(iplayer, Key.Denied, IP.GetDenies());
  3122.  
  3123.                         return;
  3124.                     }
  3125.                     else if(args.Length == 2)
  3126.                     {
  3127.                         var network = IP.Network(args[1]);
  3128.  
  3129.                         if(network?.Address != null)
  3130.                         {
  3131.                             Ip(iplayer, Key.Denied, IP.GetDenies(network.Address));
  3132.  
  3133.                             return;
  3134.                         }
  3135.                     }
  3136.                     else if(args.Length == 3)
  3137.                     {
  3138.                         var network = IP.Network(args[2]);
  3139.  
  3140.                         if(network?.Address != null)
  3141.                         {
  3142.                             switch(args[1].ToLower())
  3143.                             {
  3144.                             case "add":
  3145.                                 Ip(iplayer, Key.Denied, network, true);
  3146.  
  3147.                                 return;
  3148.  
  3149.                             case "remove":
  3150.                                 Ip(iplayer, Key.Denied, network, false);
  3151.  
  3152.                                 return;
  3153.                             }
  3154.                         }
  3155.                     }
  3156.  
  3157.                     break;
  3158.  
  3159.                 case "save":
  3160.                     if(args.Length == 1)
  3161.                     {
  3162.                         IP.Save();
  3163.  
  3164.                         Chat.Reply(iplayer, Key.CommandIp, new Dictionary<string, string>
  3165.                         {
  3166.                             { "action", Text.GetPlain(Key.Saved, iplayer) },
  3167.                             { "info", Text.GetPlain(Key.data, iplayer) }
  3168.                         });
  3169.  
  3170.                         return;
  3171.                     }
  3172.  
  3173.                     break;
  3174.  
  3175.                 case "unblock":
  3176.                     if((args.Length == 2) && IP.IsValid(args[1]))
  3177.                     {
  3178.                         IP.Unblock(args[1]);
  3179.  
  3180.                         Chat.Reply(iplayer, Key.CommandIp, new Dictionary<string, string>
  3181.                         {
  3182.                             { "action", Text.GetPlain(Key.Unblocked, iplayer) },
  3183.                             { "info", args[1] }
  3184.                         });
  3185.  
  3186.                         return;
  3187.                     }
  3188.  
  3189.                     break;
  3190.                 }
  3191.  
  3192.             syntax_error:
  3193.                 Chat.Reply(iplayer, Key.CommandIpSyntax, new Dictionary<string, string>
  3194.                 {
  3195.                     { "command", (iplayer.LastCommand == CommandType.Console) ? command : ("/" + command) }
  3196.                 });
  3197.             }
  3198.             private static void Ip(IPlayer iplayer, Key key, IP.NetworkInfo network, bool add)
  3199.             {
  3200.                 bool success;
  3201.  
  3202.                 if(key == Key.Allowed)
  3203.                 {
  3204.                     if(success = IP.SetAllow(network, add))
  3205.                     {
  3206.                         IP.Bypass(network);
  3207.                     }
  3208.                 }
  3209.                 else
  3210.                 {
  3211.                     if(success = IP.SetDeny(network, add))
  3212.                     {
  3213.                         IP.Block(network);
  3214.                     }
  3215.                 }
  3216.  
  3217.                 if(success)
  3218.                 {
  3219.                     Chat.Reply(iplayer, Key.CommandIpEntry, new Dictionary<string, string>
  3220.                     {
  3221.                         { "action", Text.GetPlain(add ? Key.Added : Key.Removed, iplayer) },
  3222.                         { "entry", $"{network.Address}/{network.Bits}" }
  3223.                     });
  3224.                 }
  3225.                 else
  3226.                 {
  3227.                     Chat.Reply(iplayer, Key.CommandIpEntryFailed, new Dictionary<string, string>
  3228.                     {
  3229.                         { "action", Text.GetPlain(add ? Key.add : Key.remove, iplayer) },
  3230.                         { "entry", $"{network.Address}/{network.Bits}" }
  3231.                     });
  3232.                 }
  3233.             }
  3234.             private static void Ip(IPlayer iplayer, Key key, List<string> list)
  3235.             {
  3236.                 StringBuilder addresses = new StringBuilder();
  3237.  
  3238.                 if(list.Count == 0)
  3239.                 {
  3240.                     addresses.Append(Text.GetPlain(Key.empty, iplayer));
  3241.                 }
  3242.                 else
  3243.                 {
  3244.                     foreach(var address in list)
  3245.                     {
  3246.                         addresses.Append("\n    ").Append(address);
  3247.                     }
  3248.                 }
  3249.  
  3250.                 Chat.Reply(iplayer, Key.CommandIpList, new Dictionary<string, string>
  3251.                 {
  3252.                     { "addresses", addresses.ToString() },
  3253.                     { "type", Text.GetPlain(key, iplayer) }
  3254.                 });
  3255.             }
  3256.  
  3257.             private static void Log(IPlayer iplayer, string command, string[] args)
  3258.             {
  3259.                 BasePlayer player = iplayer.IsServer ? null : iplayer.Object as BasePlayer;
  3260.  
  3261.                 if((player != null) && !Permissions.Admin(player.userID))
  3262.                 {
  3263.                     Chat.Reply(iplayer, Key.CommandNoPermission);
  3264.  
  3265.                     return;
  3266.                 }
  3267.                 else if((args.Length == 0) || (args.Length > 2))
  3268.                 {
  3269.                     goto syntax_error;
  3270.                 }
  3271.  
  3272.                 var userids = User.Find(args[0]);
  3273.  
  3274.                 if(userids.Count == 0)
  3275.                 {
  3276.                     Chat.Reply(iplayer, Key.CommandUserNotFound);
  3277.  
  3278.                     return;
  3279.                 }
  3280.                 else if(userids.Count == 1)
  3281.                 {
  3282.                     int lines = 0;
  3283.  
  3284.                     if(args.Length == 2)
  3285.                     {
  3286.                         int.TryParse(args[1], out lines);
  3287.                     }
  3288.  
  3289.                     if((lines = Generic.Clamp(lines, 0, 64)) == 0)
  3290.                     {
  3291.                         lines = 20;
  3292.                     }
  3293.  
  3294.                     foreach(var userid in userids)
  3295.                     {
  3296.                         Projectile.Log.Get(iplayer, userid, lines);
  3297.  
  3298.                         return;
  3299.                     }
  3300.                 }
  3301.                 else
  3302.                 {
  3303.                     Users(iplayer, Key.CommandUserTooMany, userids);
  3304.  
  3305.                     return;
  3306.                 }
  3307.  
  3308.  
  3309.             syntax_error:
  3310.                 Chat.Reply(iplayer, Key.CommandLogSyntax, new Dictionary<string, string>
  3311.                 {
  3312.                     { "command", (iplayer.LastCommand == CommandType.Console) ? command : ("/" + command) }
  3313.                 });
  3314.             }
  3315.  
  3316.             private static void Server(IPlayer iplayer, string command, string[] args)
  3317.             {
  3318.                 BasePlayer player = iplayer.IsServer ? null : iplayer.Object as BasePlayer;
  3319.  
  3320.                 if((player != null) && !Permissions.Command.Server(player.userID))
  3321.                 {
  3322.                     Chat.Reply(iplayer, Key.CommandNoPermission);
  3323.  
  3324.                     return;
  3325.                 }
  3326.                 else if(args.Length != 1)
  3327.                 {
  3328.                     goto syntax_error;
  3329.                 }
  3330.  
  3331.                 switch(args[0].ToLower())
  3332.                 {
  3333.                 case "pardon": User.Pardon(iplayer); return;
  3334.                 case "unban": User.Unban(iplayer); return;
  3335.                 case "uncripple": User.Uncripple(iplayer); return;
  3336.                 }
  3337.  
  3338.             syntax_error:
  3339.                 Chat.Reply(iplayer, Key.CommandServerSyntax, new Dictionary<string, string>
  3340.                 {
  3341.                     { "command", (iplayer.LastCommand == CommandType.Console) ? command : ("/" + command) }
  3342.                 });
  3343.             }
  3344.  
  3345.             private static void Teleport(IPlayer iplayer, string command, string[] args)
  3346.             {
  3347.                 BasePlayer player = iplayer.IsServer ? null : iplayer.Object as BasePlayer;
  3348.  
  3349.                 if((player != null) && !Permissions.Command.Tp(player.userID))
  3350.                 {
  3351.                     Chat.Reply(iplayer, Key.CommandNoPermission);
  3352.  
  3353.                     return;
  3354.                 }
  3355.                 else if(args.Length != 1)
  3356.                 {
  3357.                     goto syntax_error;
  3358.                 }
  3359.  
  3360.                 var userids = User.Find(args[0]);
  3361.  
  3362.                 if(userids.Count == 0)
  3363.                 {
  3364.                     Chat.Reply(iplayer, Key.CommandUserNotFound);
  3365.  
  3366.                     return;
  3367.                 }
  3368.                 else if(userids.Count > 1)
  3369.                 {
  3370.                     Users(iplayer, Key.CommandUserTooMany, userids);
  3371.  
  3372.                     return;
  3373.                 }
  3374.  
  3375.                 var position = Vector3.zero;
  3376.  
  3377.                 if(command.EndsWith(".tp"))
  3378.                 {
  3379.                     foreach(var userid in userids)
  3380.                     {
  3381.                         position = User.GetLastSeenPosition(userid);
  3382.                     }
  3383.                 }
  3384.                 else
  3385.                 {
  3386.                     foreach(var userid in userids)
  3387.                     {
  3388.                         position = User.GetViolationPosition(userid);
  3389.                     }
  3390.                 }
  3391.  
  3392.                 var found = position != Vector3.zero;
  3393.  
  3394.                 var parameters = new Dictionary<string, string>
  3395.                 {
  3396.                     { "position", found ? $"({position.x:0.0} {position.y:0.0} {position.z:0.0})" : Text.GetPlain(Key.unknown, iplayer) }
  3397.                 };
  3398.  
  3399.                 if(command.EndsWith(".tp"))
  3400.                 {
  3401.                     Chat.Reply(iplayer, Key.CommandTeleport, parameters);
  3402.                 }
  3403.                 else
  3404.                 {
  3405.                     Chat.Reply(iplayer, Key.CommandTeleportViolation, parameters);
  3406.                 }
  3407.  
  3408.                 if(found && !iplayer.IsServer)
  3409.                 {
  3410.                     iplayer.Teleport(position.x, position.y, position.z);
  3411.                 }
  3412.  
  3413.                 return;
  3414.  
  3415.             syntax_error:
  3416.                 Chat.Reply(iplayer, Key.CommandTeleportSyntax, new Dictionary<string, string>
  3417.                 {
  3418.                     { "command", (iplayer.LastCommand == CommandType.Console) ? command : ("/" + command) }
  3419.                 });
  3420.             }
  3421.  
  3422.             private static void Users(IPlayer iplayer, string command, string[] args)
  3423.             {
  3424.                 BasePlayer player = iplayer.IsServer ? null : iplayer.Object as BasePlayer;
  3425.  
  3426.                 if((player != null) && !Permissions.Admin(player.userID))
  3427.                 {
  3428.                     Chat.Reply(iplayer, Key.CommandNoPermission);
  3429.  
  3430.                     return;
  3431.                 }
  3432.                 else if(args.Length == 0)
  3433.                 {
  3434.                     goto syntax_error;
  3435.                 }
  3436.  
  3437.                 var is_ip = IP.IsValid(args[0]);
  3438.  
  3439.                 var userids = User.Find(args[0]);
  3440.  
  3441.                 if(userids.Count == 0)
  3442.                 {
  3443.                     Chat.Reply(iplayer, Key.CommandUserNotFound);
  3444.  
  3445.                     return;
  3446.                 }
  3447.                 else if(userids.Count == 1)
  3448.                 {
  3449.                     if(args.Length == 1)
  3450.                     {
  3451.                         foreach(var userid in userids)
  3452.                         {
  3453.                             Chat.Reply(iplayer, Key.CommandUserInfo, new Dictionary<string, string>
  3454.                             {
  3455.                                 { "info", User.InfoText(userid, iplayer) }
  3456.                             });
  3457.  
  3458.                             return;
  3459.                         }
  3460.                     }
  3461.                 }
  3462.                 else if(args.Length == 1)
  3463.                 {
  3464.                     Users(iplayer, Key.CommandUserTooMany, userids);
  3465.  
  3466.                     return;
  3467.                 }
  3468.                 else if(!is_ip)
  3469.                 {
  3470.                     goto syntax_error;
  3471.                 }
  3472.  
  3473.                 ulong duration = 0; string duration_string = null, reason = null;
  3474.  
  3475.                 if(args.Length == 3)
  3476.                 {
  3477.                     if(!Text.ParseTime(args[2], out duration))
  3478.                     {
  3479.                         reason = string.IsNullOrEmpty(args[2]) ? null : args[2];
  3480.                     }
  3481.                 }
  3482.                 else if(args.Length == 4)
  3483.                 {
  3484.                     reason = string.IsNullOrEmpty(args[2]) ? null : args[2];
  3485.  
  3486.                     if(!Text.ParseTime(args[3], out duration))
  3487.                     {
  3488.                         goto syntax_error;
  3489.                     }
  3490.                 }
  3491.                 else if(args.Length > 4)
  3492.                 {
  3493.                     goto syntax_error;
  3494.                 }
  3495.  
  3496.                 if(duration == 0)
  3497.                 {
  3498.                     duration_string = Text.GetPlain(Key.permanently, iplayer);
  3499.                 }
  3500.                 else
  3501.                 {
  3502.                     duration_string = $"{Text.GetPlain(Key.For, iplayer)} {Text.Duration.Short(TimeSpan.FromSeconds(duration), iplayer)}";
  3503.                 }
  3504.  
  3505.                 var subcommand = args[1].ToLower();
  3506.  
  3507.                 if(subcommand.StartsWith("team"))
  3508.                 {
  3509.                     var dot = subcommand.IndexOf('.') + 1;
  3510.  
  3511.                     if(subcommand.Length > dot)
  3512.                     {
  3513.                         subcommand = subcommand.Substring(dot);
  3514.                     }
  3515.  
  3516.                     var team = new HashSet<ulong>();
  3517.  
  3518.                     foreach(var userid in userids)
  3519.                     {
  3520.                         foreach(var entry in User.Team(userid))
  3521.                         {
  3522.                             team.Add(entry);
  3523.                         }
  3524.                     }
  3525.  
  3526.                     foreach(var entry in team)
  3527.                     {
  3528.                         userids.Add(entry);
  3529.                     }
  3530.                 }
  3531.  
  3532.                 switch(subcommand)
  3533.                 {
  3534.                 case "ban":
  3535.                     foreach(var userid in userids)
  3536.                     {
  3537.                         User.Ban(userid, reason, duration, iplayer);
  3538.  
  3539.                         Chat.Reply(iplayer, Key.CommandUser, new Dictionary<string, string>
  3540.                         {
  3541.                             { "action", Text.GetPlain(Key.Banned, iplayer) },
  3542.                             { "duration", duration_string },
  3543.                             { "playername", Text.Sanitize(User.Name(userid)) },
  3544.                             { "playerid", userid.ToString() },
  3545.                             { "reason", reason }
  3546.                         });
  3547.                     }
  3548.  
  3549.                     return;
  3550.  
  3551.                 case "ban.reset":
  3552.                     if(args.Length != 2)
  3553.                     {
  3554.                         goto syntax_error;
  3555.                     }
  3556.  
  3557.                     foreach(var userid in userids)
  3558.                     {
  3559.                         User.BanReset(userid, iplayer);
  3560.  
  3561.                         Chat.Reply(iplayer, Key.CommandUserAction, new Dictionary<string, string>
  3562.                         {
  3563.                             { "action", Text.GetPlain(Key.BanReset, iplayer) },
  3564.                             { "playername", Text.Sanitize(User.Name(userid)) },
  3565.                             { "playerid", userid.ToString() }
  3566.                         });
  3567.                     }
  3568.  
  3569.                     return;
  3570.  
  3571.                 case "cripple":
  3572.                     foreach(var userid in userids)
  3573.                     {
  3574.                         User.Cripple(userid, reason, duration, iplayer);
  3575.  
  3576.                         Chat.Reply(iplayer, Key.CommandUser, new Dictionary<string, string>
  3577.                         {
  3578.                             { "action", Text.GetPlain(Key.Crippled, iplayer) },
  3579.                             { "duration", duration_string },
  3580.                             { "playername", Text.Sanitize(User.Name(userid)) },
  3581.                             { "playerid", userid.ToString() },
  3582.                             { "reason", reason }
  3583.                         });
  3584.                     }
  3585.  
  3586.                     return;
  3587.  
  3588.                 case "cripple.reset":
  3589.                     if(args.Length != 2)
  3590.                     {
  3591.                         goto syntax_error;
  3592.                     }
  3593.  
  3594.                     foreach(var userid in userids)
  3595.                     {
  3596.                         User.CrippleReset(userid, iplayer);
  3597.  
  3598.                         Chat.Reply(iplayer, Key.CommandUserAction, new Dictionary<string, string>
  3599.                         {
  3600.                             { "action", Text.GetPlain(Key.CrippleReset, iplayer) },
  3601.                             { "playername", Text.Sanitize(User.Name(userid)) },
  3602.                             { "playerid", userid.ToString() }
  3603.                         });
  3604.                     }
  3605.  
  3606.                     return;
  3607.  
  3608.                 case "kick":
  3609.                     if((args.Length > 3) || ((args.Length == 3) && (reason == null)))
  3610.                     {
  3611.                         goto syntax_error;
  3612.                     }
  3613.  
  3614.                     foreach(var userid in userids)
  3615.                     {
  3616.                         User.Kick(userid, reason, iplayer);
  3617.  
  3618.                         Chat.Reply(iplayer, Key.CommandUserKick, new Dictionary<string, string>
  3619.                         {
  3620.                             { "action", Text.GetPlain(Key.Kicked, iplayer) },
  3621.                             { "playername", Text.Sanitize(User.Name(userid)) },
  3622.                             { "playerid", userid.ToString() },
  3623.                             { "reason", reason }
  3624.                         });
  3625.                     }
  3626.  
  3627.                     return;
  3628.  
  3629.                 case "pardon":
  3630.                     if(args.Length != 2)
  3631.                     {
  3632.                         goto syntax_error;
  3633.                     }
  3634.  
  3635.                     foreach(var userid in userids)
  3636.                     {
  3637.                         User.Pardon(userid, iplayer);
  3638.  
  3639.                         Chat.Reply(iplayer, Key.CommandUserAction, new Dictionary<string, string>
  3640.                         {
  3641.                             { "action", Text.GetPlain(Key.Pardoned, iplayer) },
  3642.                             { "playername", Text.Sanitize(User.Name(userid)) },
  3643.                             { "playerid", userid.ToString() }
  3644.                         });
  3645.                     }
  3646.  
  3647.                     return;
  3648.  
  3649.                 case "team":
  3650.                     if(args.Length != 2)
  3651.                     {
  3652.                         goto syntax_error;
  3653.                     }
  3654.  
  3655.                     Users(iplayer, Key.CommandUserTeam, userids);
  3656.  
  3657.                     return;
  3658.  
  3659.                 case "unban":
  3660.                     if(args.Length != 2)
  3661.                     {
  3662.                         goto syntax_error;
  3663.                     }
  3664.  
  3665.                     foreach(var userid in userids)
  3666.                     {
  3667.                         User.Unban(userid, true, iplayer);
  3668.  
  3669.                         Chat.Reply(iplayer, Key.CommandUserAction, new Dictionary<string, string>
  3670.                         {
  3671.                             { "action", Text.GetPlain(Key.Unbanned, iplayer) },
  3672.                             { "playername", Text.Sanitize(User.Name(userid)) },
  3673.                             { "playerid", userid.ToString() }
  3674.                         });
  3675.                     }
  3676.  
  3677.                     return;
  3678.  
  3679.                 case "uncripple":
  3680.                     if(args.Length != 2)
  3681.                     {
  3682.                         goto syntax_error;
  3683.                     }
  3684.  
  3685.                     foreach(var userid in userids)
  3686.                     {
  3687.                         User.Uncripple(userid, true, iplayer);
  3688.  
  3689.                         Chat.Reply(iplayer, Key.CommandUserAction, new Dictionary<string, string>
  3690.                         {
  3691.                             { "action", Text.GetPlain(Key.Uncrippled, iplayer) },
  3692.                             { "playername", Text.Sanitize(User.Name(userid)) },
  3693.                             { "playerid", userid.ToString() }
  3694.                         });
  3695.                     }
  3696.  
  3697.                     return;
  3698.                 }
  3699.  
  3700.             syntax_error:
  3701.                 Chat.Reply(iplayer, Key.CommandUserSyntax, new Dictionary<string, string>
  3702.                 {
  3703.                     { "command", (iplayer.LastCommand == CommandType.Console) ? command : ("/" + command) }
  3704.                 });
  3705.             }
  3706.             private static void Users(IPlayer iplayer, Key key, HashSet<ulong> userids)
  3707.             {
  3708.                 var users = new StringBuilder();
  3709.  
  3710.                 foreach(var userid in userids)
  3711.                 {
  3712.                     users.Append("\n    ");
  3713.                     users.Append(userid.ToString());
  3714.                     users.Append(" - ");
  3715.                     users.Append(User.Name(userid));
  3716.                     users.Append(" (");
  3717.                     users.Append(User.StatusText(userid, iplayer));
  3718.                     users.Append(')');
  3719.                 }
  3720.  
  3721.                 Chat.Reply(iplayer, key, new Dictionary<string, string>
  3722.                 {
  3723.                     { "users", users.ToString() }
  3724.                 });
  3725.  
  3726.                 users.Clear();
  3727.             }
  3728.  
  3729.             private static void Vpn(IPlayer iplayer, string command, string[] args)
  3730.             {
  3731.                 BasePlayer player = iplayer.IsServer ? null : iplayer.Object as BasePlayer;
  3732.  
  3733.                 if((player != null) && !Permissions.Command.Vpn(player.userID))
  3734.                 {
  3735.                     Chat.Reply(iplayer, Key.CommandNoPermission);
  3736.  
  3737.                     return;
  3738.                 }
  3739.                 else if(args.Length != 2)
  3740.                 {
  3741.                     goto syntax_error;
  3742.                 }
  3743.  
  3744.                 var address = args[1];
  3745.  
  3746.                 if(!IP.IsValid(address))
  3747.                 {
  3748.                     goto syntax_error;
  3749.                 }
  3750.  
  3751.                 switch(args[0].ToLower())
  3752.                 {
  3753.                 case "bypass":
  3754.                         VPN.Bypass(address);
  3755.  
  3756.                         IP.Unblock(address);
  3757.  
  3758.                         Vpn(iplayer, Key.Bypassed, address);
  3759.  
  3760.                         return;
  3761.  
  3762.                 case "status":
  3763.                     Vpn(iplayer, Key.Current, address);
  3764.  
  3765.                     return;
  3766.  
  3767.                 case "unblock":
  3768.                         VPN.Unblock(address);
  3769.  
  3770.                         IP.Unblock(address);
  3771.  
  3772.                         Vpn(iplayer, Key.Unblocked, address);
  3773.  
  3774.                         return;
  3775.                 }
  3776.  
  3777.             syntax_error:
  3778.                 Chat.Reply(iplayer, Key.CommandVpnSyntax, new Dictionary<string, string>
  3779.                 {
  3780.                     { "command", (iplayer.LastCommand == CommandType.Console) ? command : ("/" + command) }
  3781.                 });
  3782.             }
  3783.             private static void Vpn(IPlayer iplayer, Key key, string address)
  3784.             {
  3785.                 var info = VPN.IsBlocked(address)  ? Text.GetPlain(Key.Blocked,   iplayer) :
  3786.                            VPN.IsBypassed(address) ? Text.GetPlain(Key.Bypassed,  iplayer) :
  3787.                                                      Text.GetPlain(Key.Unblocked, iplayer);
  3788.  
  3789.                 Chat.Reply(iplayer, Key.CommandVpn, new Dictionary<string, string>
  3790.                 {
  3791.                     { "action", Text.Get(key, iplayer) },
  3792.                     { "info", $"{info}({address})" }
  3793.                 });
  3794.             }
  3795.         }
  3796.  
  3797.         #endregion _command_
  3798.  
  3799.         #region _configuration_
  3800.  
  3801.         private static Configuration config;
  3802.  
  3803.         private class Configuration
  3804.         {
  3805.             public Admin.Settings     Admin;
  3806.             public AntiCheat.Settings AntiCheat;
  3807.             public AntiFlood.Settings AntiFlood;
  3808.             public Ban.Settings       Ban;
  3809.             public Cripple.Settings   Cripple;
  3810.             public Discord.Settings   Discord;
  3811.             public Entity.Settings    Entity;
  3812.             public IP.Settings        IP;
  3813.             public Log.Settings       Log;
  3814.             public Steam.Settings     Steam;
  3815.             public User.Settings      User;
  3816.             public Version.Settings   Version;
  3817.             public Violation.Settings Violation;
  3818.             public VPN.Settings       VPN;
  3819.  
  3820.             private static bool corrupt  = false;
  3821.             private static bool dirty    = false;
  3822.             private static bool upgraded = false;
  3823.  
  3824.             public static void Clamp<T>(ref T value, T min, T max) where T : IComparable<T>
  3825.             {
  3826.                 T clamped = Generic.Clamp(value, min, max);
  3827.  
  3828.                 if(!value.Equals(clamped))
  3829.                 {
  3830.                     dirty = true; value = clamped;
  3831.                 }
  3832.             }
  3833.  
  3834.             public static void Load()
  3835.             {
  3836.                 dirty = false;
  3837.  
  3838.                 try
  3839.                 {
  3840.                     config = _instance.Config.ReadObject<Configuration>();
  3841.  
  3842.                     config.Version.Compare(0, 0, 0);
  3843.                 }
  3844.                 catch(NullReferenceException)
  3845.                 {
  3846.                     Guardian.Log.Warning("Configuration: Created new configuration with default settings.");
  3847.  
  3848.                     dirty = true; config = new Configuration();
  3849.                 }
  3850.                 catch(JsonException e)
  3851.                 {
  3852.                     Guardian.Log.Error("Configuration: Using default settings. Delete the configuration file, or fix the following error, and reload; " + e.ToString());
  3853.  
  3854.                     corrupt = true; config = new Configuration();
  3855.                 }
  3856.  
  3857.                 Validate();
  3858.             }
  3859.  
  3860.             public static void Save()
  3861.             {
  3862.                 if(dirty && !corrupt)
  3863.                 {
  3864.                     dirty = false;
  3865.  
  3866.                     _instance.Config.WriteObject(config);
  3867.                 }
  3868.             }
  3869.  
  3870.             public static void SetDirty() => dirty = true;
  3871.  
  3872.             public static void SetUpgrade(bool upgrade = true) => upgraded = upgrade;
  3873.  
  3874.             public static void Unload()
  3875.             {
  3876.                 Save();
  3877.  
  3878.                 config = null;
  3879.             }
  3880.  
  3881.             public static bool Upgraded() => upgraded;
  3882.  
  3883.             public static void Validate<T>(ref T value, Func<T> initializer, Action validator = null)
  3884.             {
  3885.                 if(value == null)
  3886.                 {
  3887.                     dirty = true; value = initializer();
  3888.                 }
  3889.                 else
  3890.                 {
  3891.                     validator?.Invoke();
  3892.                 }
  3893.             }
  3894.  
  3895.             private static void Validate()
  3896.             {
  3897.                 Validate(ref config.Admin,     () => new Admin.Settings());
  3898.                 Validate(ref config.AntiCheat, () => new AntiCheat.Settings(), () => config.AntiCheat.Validate());
  3899.                 Validate(ref config.AntiFlood, () => new AntiFlood.Settings(), () => config.AntiFlood.Validate());
  3900.                 Validate(ref config.Ban,       () => new Ban.Settings(),       () => config.Ban.Validate());
  3901.                 Validate(ref config.Cripple,   () => new Cripple.Settings());
  3902.                 Validate(ref config.Discord,   () => new Discord.Settings(),   () => config.Discord.Validate());
  3903.                 Validate(ref config.Entity,    () => new Entity.Settings(),    () => config.Entity.Validate());
  3904.                 Validate(ref config.IP,        () => new IP.Settings(),        () => config.IP.Validate());
  3905.                 Validate(ref config.Log,       () => new Log.Settings(),       () => config.Log.Validate());
  3906.                 Validate(ref config.Steam,     () => new Steam.Settings(),     () => config.Steam.Validate());
  3907.                 Validate(ref config.User,      () => new User.Settings(),      () => config.User.Validate());
  3908.                 Validate(ref config.Version,   () => new Version.Settings());
  3909.                 Validate(ref config.Violation, () => new Violation.Settings());
  3910.                 Validate(ref config.VPN,       () => new VPN.Settings(),       () => config.VPN.Validate());
  3911.  
  3912.                 config.Version.Validate();
  3913.             }
  3914.         }
  3915.  
  3916.         #endregion _conifguration_
  3917.  
  3918.         #region _counter_
  3919.  
  3920.         private class Counter
  3921.         {
  3922.             private uint count;
  3923.             private uint delay;
  3924.  
  3925.             public Counter()
  3926.             {
  3927.                 count = delay = 0;
  3928.             }
  3929.  
  3930.             public void Decrement()
  3931.             {
  3932.                 if((delay >> 31) == 1)
  3933.                 {
  3934.                     --count;
  3935.                 }
  3936.  
  3937.                 delay <<= 1;
  3938.             }
  3939.  
  3940.             public void Increment()
  3941.             {
  3942.                 if((delay >> 31) == 0)
  3943.                 {
  3944.                     ++count;
  3945.                 }
  3946.  
  3947.                 delay = (delay << 1) + 1;
  3948.             }
  3949.  
  3950.             public uint Percent()
  3951.             {
  3952.                 return (100u * count) >> 5;
  3953.             }
  3954.  
  3955.             public float Ratio(bool inverse = false)
  3956.             {
  3957.                 return inverse ? (1.0f - (count * 0.03125f)) : (count * 0.03125f);
  3958.             }
  3959.  
  3960.             public float Ratio(float scale, bool inverse = false)
  3961.             {
  3962.                 return (inverse ? (1.0f - (count * 0.03125f)) : (count * 0.03125f)) * scale;
  3963.             }
  3964.  
  3965.             public ulong Total()
  3966.             {
  3967.                 return count;
  3968.             }
  3969.         }
  3970.  
  3971.         #endregion _counter_
  3972.  
  3973.         #region _cripple_
  3974.  
  3975.         public class Cripple
  3976.         {
  3977.             public class Settings
  3978.             {
  3979.                 public bool Heal;
  3980.                 public bool Inherit;
  3981.  
  3982.                 public Settings()
  3983.                 {
  3984.                     Heal = false;
  3985.                     Inherit = true;
  3986.                 }
  3987.             }
  3988.         }
  3989.  
  3990.         #endregion _cripple_
  3991.  
  3992.         #region _data_
  3993.  
  3994.         private class Data
  3995.         {
  3996.             private static DataFileSystem data = null;
  3997.             private static string         path = null;
  3998.  
  3999.             public static void Close()
  4000.             {
  4001.                 data = null;
  4002.                 path = null;
  4003.             }
  4004.  
  4005.             public static bool Exists(string name)
  4006.             {
  4007.                 return data.ExistsDatafile(name);
  4008.             }
  4009.  
  4010.             public static void Open()
  4011.             {
  4012.                 path = $"{Interface.Oxide.DataDirectory}\\{_instance.Name}";
  4013.  
  4014.                 data = new DataFileSystem(path);
  4015.             }
  4016.  
  4017.             public static T ReadObject<T>(string name)
  4018.             {
  4019.                 if(!string.IsNullOrEmpty(name) && data.ExistsDatafile(name))
  4020.                 {
  4021.                     try
  4022.                     {
  4023.                         return data.ReadObject<T>(name);
  4024.                     }
  4025.                     catch(JsonException)
  4026.                     {
  4027.                         Log.Warning($"Data: recreating corrupted file \'{path}\\{name}.json\'.");
  4028.                     }
  4029.                 }
  4030.  
  4031.                 return default(T);
  4032.             }
  4033.  
  4034.             public static void WriteObject<T>(string name, T value)
  4035.             {
  4036.                 data.WriteObject(name, value);
  4037.             }
  4038.         }
  4039.  
  4040.         #endregion _data_
  4041.  
  4042.         #region _data_file_
  4043.  
  4044.         private class DataFile<TKey, TValue>
  4045.         {
  4046.             private readonly Dictionary<TKey, TValue> data = new Dictionary<TKey, TValue>();
  4047.  
  4048.             private bool dirty;
  4049.  
  4050.             private string name;
  4051.  
  4052.             public DataFile(string name = null)
  4053.             {
  4054.                 dirty = false; this.name = name;
  4055.             }
  4056.  
  4057.             public TValue this[TKey key]
  4058.             {
  4059.                 get
  4060.                 {
  4061.                     return data[key];
  4062.                 }
  4063.  
  4064.                 set
  4065.                 {
  4066.                     data[key] = value;
  4067.  
  4068.                     SetDirty();
  4069.                 }
  4070.             }
  4071.  
  4072.             public void Add(TKey key, TValue value)
  4073.             {
  4074.                 if(!data.ContainsKey(key))
  4075.                 {
  4076.                     this[key] = value;
  4077.                 }
  4078.             }
  4079.  
  4080.             public bool Contains(TKey key)
  4081.             {
  4082.                 return data.ContainsKey(key);
  4083.             }
  4084.  
  4085.             public void Clear()
  4086.             {
  4087.                 data.Clear();
  4088.  
  4089.                 SetDirty();
  4090.             }
  4091.  
  4092.             public bool Exists()
  4093.             {
  4094.                 if(!string.IsNullOrEmpty(name))
  4095.                 {
  4096.                     return Data.Exists(name);
  4097.                 }
  4098.  
  4099.                 return false;
  4100.             }
  4101.  
  4102.             public void ForEach(Action<TKey, TValue> action)
  4103.             {
  4104.                 foreach(var entry in data)
  4105.                 {
  4106.                     action(entry.Key, entry.Value);
  4107.                 }
  4108.             }
  4109.  
  4110.             public TValue Get(TKey key, TValue default_value)
  4111.             {
  4112.                 TValue value;
  4113.  
  4114.                 if(data.TryGetValue(key, out value))
  4115.                 {
  4116.                     return value;
  4117.                 }
  4118.  
  4119.                 return default_value;
  4120.             }
  4121.  
  4122.             public bool IsDirty() => dirty;
  4123.  
  4124.             public bool IsEmpty() => data.IsEmpty();
  4125.  
  4126.             public void Load()
  4127.             {
  4128.                 if(string.IsNullOrEmpty(name))
  4129.                 {
  4130.                     return;
  4131.                 }
  4132.  
  4133.                 try
  4134.                 {
  4135.                     SetDirty(false);
  4136.  
  4137.                     var file = Data.ReadObject<Dictionary<TKey, TValue>>(name);
  4138.  
  4139.                     foreach(var entry in file)
  4140.                     {
  4141.                         data[entry.Key] = entry.Value;
  4142.                     }
  4143.                 }
  4144.                 catch
  4145.                 {
  4146.                     SetDirty();
  4147.                 }
  4148.  
  4149.                 Save();
  4150.             }
  4151.  
  4152.             public void Load(string name)
  4153.             {
  4154.                 SetName(name);
  4155.  
  4156.                 Load();
  4157.             }
  4158.  
  4159.             public void Remove(TKey key)
  4160.             {
  4161.                 if(data.ContainsKey(key))
  4162.                 {
  4163.                     data.Remove(key);
  4164.  
  4165.                     SetDirty();
  4166.                 }
  4167.             }
  4168.  
  4169.             public void Save()
  4170.             {
  4171.                 if(IsDirty())
  4172.                 {
  4173.                     if(!string.IsNullOrEmpty(name))
  4174.                     {
  4175.                         Data.WriteObject(name, data);
  4176.                     }
  4177.  
  4178.                     SetDirty(false);
  4179.                 }
  4180.             }
  4181.  
  4182.             public void Save(string name)
  4183.             {
  4184.                 SetName(name);
  4185.  
  4186.                 Save();
  4187.             }
  4188.  
  4189.             public void SetDirty(bool value = true) => dirty = value;
  4190.  
  4191.             public void SetName(string name)
  4192.             {
  4193.                 this.name = name;
  4194.  
  4195.                 SetDirty();
  4196.             }
  4197.  
  4198.             public void Unload()
  4199.             {
  4200.                 Save();
  4201.  
  4202.                 data.Clear();
  4203.             }
  4204.         }
  4205.  
  4206.         #endregion _data_file_
  4207.  
  4208.         #region _discord_
  4209.  
  4210.         private class Discord
  4211.         {
  4212.             public class Settings
  4213.             {
  4214.                 public bool           Enabled;
  4215.                 public string         WebHook;
  4216.                 public DiscordFilters Filters;
  4217.  
  4218.                 public Settings()
  4219.                 {
  4220.                     Enabled = false;
  4221.                     WebHook = string.Empty;
  4222.                     Filters = new DiscordFilters();
  4223.                 }
  4224.  
  4225.                 public class DiscordFilter
  4226.                 {
  4227.                     public bool   Enabled;
  4228.                     public string WebHook;
  4229.  
  4230.                     public DiscordFilter()
  4231.                     {
  4232.                         Enabled = true;
  4233.                         WebHook = string.Empty;
  4234.                     }
  4235.  
  4236.                     public string URL() => Enabled ? WebHook : null;
  4237.  
  4238.                     public void Validate()
  4239.                     {
  4240.                         Configuration.Validate(ref WebHook, () => { return string.Empty; });
  4241.                     }
  4242.                 }
  4243.  
  4244.                 public class DiscordFilters
  4245.                 {
  4246.                     public DiscordFilter AntiCheat;
  4247.                     public DiscordFilter AntiFlood;
  4248.                     public DiscordFilter IP;
  4249.                     public DiscordFilter Steam;
  4250.                     public DiscordFilter VPN;
  4251.  
  4252.                     public DiscordFilters()
  4253.                     {
  4254.                         AntiCheat = new DiscordFilter();
  4255.                         AntiFlood = new DiscordFilter();
  4256.                         IP        = new DiscordFilter();
  4257.                         Steam     = new DiscordFilter();
  4258.                         VPN       = new DiscordFilter();
  4259.                     }
  4260.  
  4261.                     public void Validate()
  4262.                     {
  4263.                         Configuration.Validate(ref AntiCheat, () => new DiscordFilter(), () => AntiCheat.Validate());
  4264.                         Configuration.Validate(ref AntiFlood, () => new DiscordFilter(), () => AntiFlood.Validate());
  4265.                         Configuration.Validate(ref IP,        () => new DiscordFilter(), () => IP.Validate());
  4266.                         Configuration.Validate(ref Steam,     () => new DiscordFilter(), () => Steam.Validate());
  4267.                         Configuration.Validate(ref VPN,       () => new DiscordFilter(), () => VPN.Validate());
  4268.                     }
  4269.                 }
  4270.  
  4271.                 public void Validate()
  4272.                 {
  4273.                     Configuration.Validate(ref WebHook, () => { return string.Empty; });
  4274.                     Configuration.Validate(ref Filters, () => new DiscordFilters(), () => Filters.Validate());
  4275.                 }
  4276.             }
  4277.  
  4278.             public static void Send(string category, Dictionary<string, object> message)
  4279.             {
  4280.                 if(!config.Discord.Enabled || (message == null))
  4281.                 {
  4282.                     return;
  4283.                 }
  4284.  
  4285.                 if((message.Count == 0) || !(message.ContainsKey("content") || message.ContainsKey("embeds")))
  4286.                 {
  4287.                     Log.Console(Key.LogDiscordMessage);
  4288.  
  4289.                     return;
  4290.                 }
  4291.  
  4292.                 var url = Select(category);
  4293.  
  4294.                 if(string.IsNullOrWhiteSpace(url))
  4295.                 {
  4296.                     if(url != null)
  4297.                     {
  4298.                         Log.Console(Key.LogDiscordConfig);
  4299.                     }
  4300.  
  4301.                     return;
  4302.                 }
  4303.  
  4304.                 WebHook.Send(url, "Discord", JsonConvert.SerializeObject(message));
  4305.             }
  4306.  
  4307.             private static string Select(string category)
  4308.             {
  4309.                 string url;
  4310.  
  4311.                 switch(Violation.Category(category))
  4312.                 {
  4313.                 case Key.AntiCheat: url = config.Discord.Filters.AntiCheat.URL(); break;
  4314.                 case Key.AntiFlood: url = config.Discord.Filters.AntiFlood.URL(); break;
  4315.                 case Key.IP:        url = config.Discord.Filters.IP.URL();        break;
  4316.                 case Key.Steam:     url = config.Discord.Filters.Steam.URL();     break;
  4317.                 case Key.VPN:       url = config.Discord.Filters.VPN.URL();       break;
  4318.                 default:            url = null;                                   break;
  4319.                 }
  4320.  
  4321.                 if((url != null) && string.IsNullOrWhiteSpace(url))
  4322.                 {
  4323.                     url = config.Discord.WebHook;
  4324.                 }
  4325.  
  4326.                 return url;
  4327.             }
  4328.  
  4329.             public static void Subscribe()
  4330.             {
  4331.                 if(config.Discord.Enabled)
  4332.                 {
  4333.                     Hooks.Subscribe(nameof(OnGuardianViolation));
  4334.                 }
  4335.                 else
  4336.                 {
  4337.                     Hooks.Unsubscribe(nameof(OnGuardianViolation));
  4338.                 }
  4339.             }
  4340.         }
  4341.  
  4342.         #endregion _discord_
  4343.  
  4344.         #region _entity_
  4345.  
  4346.         private class Entity
  4347.         {
  4348.             public class Settings
  4349.             {
  4350.                 public EntityDamage Damage;
  4351.  
  4352.                 public Settings()
  4353.                 {
  4354.                     Damage = new EntityDamage();
  4355.                 }
  4356.  
  4357.                 public class EntityDamage
  4358.                 {
  4359.                     public float Animal;
  4360.                     public float Bradley;
  4361.                     public float Building;
  4362.                     public float Entity;
  4363.                     public float Friend;
  4364.                     public float Helicopter;
  4365.                     public float NPC;
  4366.                     public float Player;
  4367.                     public float Team;
  4368.                     public float Trap;
  4369.  
  4370.                     public EntityDamage()
  4371.                     {
  4372.                         Animal     = 1.0f;
  4373.                         Bradley    = 1.0f;
  4374.                         Building   = 1.0f;
  4375.                         Entity     = 1.0f;
  4376.                         Friend     = 1.0f;
  4377.                         Helicopter = 1.0f;
  4378.                         NPC        = 1.0f;
  4379.                         Player     = 1.0f;
  4380.                         Team       = 1.0f;
  4381.                         Trap       = 1.0f;
  4382.                     }
  4383.  
  4384.                     public void Validate()
  4385.                     {
  4386.                         Configuration.Clamp(ref Animal,     0.0f, 100.0f);
  4387.                         Configuration.Clamp(ref Bradley,    0.0f, 100.0f);
  4388.                         Configuration.Clamp(ref Building,   0.0f, 100.0f);
  4389.                         Configuration.Clamp(ref Entity,     0.0f, 100.0f);
  4390.                         Configuration.Clamp(ref Friend,     0.0f, 100.0f);
  4391.                         Configuration.Clamp(ref Helicopter, 0.0f, 100.0f);
  4392.                         Configuration.Clamp(ref NPC,        0.0f, 100.0f);
  4393.                         Configuration.Clamp(ref Player,     0.0f, 100.0f);
  4394.                         Configuration.Clamp(ref Team,       0.0f, 100.0f);
  4395.                         Configuration.Clamp(ref Trap,       0.0f, 100.0f);
  4396.                     }
  4397.                 }
  4398.  
  4399.                 public void Validate()
  4400.                 {
  4401.                     Configuration.Validate(ref Damage, () => new EntityDamage(), () => Damage.Validate());
  4402.                 }
  4403.             }
  4404.  
  4405.             public enum Type
  4406.             {
  4407.                 Animal,
  4408.                 AutoTurret,
  4409.                 Bear,
  4410.                 Boar,
  4411.                 Bot,
  4412.                 Bradley,
  4413.                 Building,
  4414.                 Chicken,
  4415.                 Entity,
  4416.                 FlameTurret,
  4417.                 GunTrap,
  4418.                 Helicopter,
  4419.                 Murderer,
  4420.                 NPC,
  4421.                 Player,
  4422.                 SAMSite,
  4423.                 Scientist,
  4424.                 Sentry,
  4425.                 Stag,
  4426.                 TC,
  4427.                 NULL
  4428.             }
  4429.  
  4430.             public class Damage
  4431.             {
  4432.                 private static readonly DamageTypeList cleared = new DamageTypeList();
  4433.  
  4434.                 public static void Cancel(HitInfo info)
  4435.                 {
  4436.                     info.damageTypes = cleared;
  4437.                     info.HitMaterial = 0;
  4438.                     info.PointStart  = Vector3.zero;
  4439.                     info.HitEntity   = null;
  4440.                 }
  4441.  
  4442.                 public static object Scale(HitInfo info, BasePlayer attacker, BaseCombatEntity victim)
  4443.                 {
  4444.                     switch(Entity.GetType(info.HitEntity))
  4445.                     {
  4446.                     case Type.Animal:      return Scale(info, config.Entity.Damage.Animal);
  4447.                     case Type.AutoTurret:  return Scale(info, config.Entity.Damage.Trap);
  4448.                     case Type.Bear:        return Scale(info, config.Entity.Damage.Animal);
  4449.                     case Type.Boar:        return Scale(info, config.Entity.Damage.Animal);
  4450.                     case Type.Bot:         return Scale(info, config.Entity.Damage.NPC);
  4451.                     case Type.Bradley:     return Scale(info, config.Entity.Damage.Bradley);
  4452.                     case Type.Building:    return Scale(info, config.Entity.Damage.Building);
  4453.                     case Type.Chicken:     return Scale(info, config.Entity.Damage.Animal);
  4454.                     case Type.Entity:      return Scale(info, config.Entity.Damage.Entity);
  4455.                     case Type.FlameTurret: return Scale(info, config.Entity.Damage.Trap);
  4456.                     case Type.GunTrap:     return Scale(info, config.Entity.Damage.Trap);
  4457.                     case Type.Helicopter:  return Scale(info, config.Entity.Damage.Helicopter);
  4458.                     case Type.Murderer:    return Scale(info, config.Entity.Damage.NPC);
  4459.                     case Type.NPC:         return Scale(info, config.Entity.Damage.NPC);
  4460.                     case Type.Player:      return Scale(info, attacker, victim.ToPlayer());
  4461.                     case Type.SAMSite:     return Scale(info, config.Entity.Damage.Trap);
  4462.                     case Type.Scientist:   return Scale(info, config.Entity.Damage.NPC);
  4463.                     case Type.Sentry:      return Scale(info, config.Entity.Damage.Trap);
  4464.                     case Type.Stag:        return Scale(info, config.Entity.Damage.Animal);
  4465.                     case Type.TC:          return Scale(info, config.Entity.Damage.Building);
  4466.                     }
  4467.  
  4468.                     return null;
  4469.                 }
  4470.                 private static object Scale(HitInfo info, BasePlayer attacker, BasePlayer victim)
  4471.                 {
  4472.                     if(victim == attacker)
  4473.                     {
  4474.                         return null;
  4475.                     }
  4476.  
  4477.                     var scale = 1.0f;
  4478.  
  4479.                     if((config.Entity.Damage.Friend != 1.0f) && User.IsFriend(attacker, victim))
  4480.                     {
  4481.                         scale = Math.Min(scale, config.Entity.Damage.Friend);
  4482.                     }
  4483.  
  4484.                     if((config.Entity.Damage.Team != 1.0f) && User.IsTeamMate(attacker, victim))
  4485.                     {
  4486.                         scale = Math.Min(scale, config.Entity.Damage.Team);
  4487.                     }
  4488.  
  4489.                     return Scale(info, scale * config.Entity.Damage.Player);
  4490.                 }
  4491.                 private static object Scale(HitInfo info, float scale)
  4492.                 {
  4493.                     if(scale == 1.0f)
  4494.                     {
  4495.                         return null;
  4496.                     }
  4497.  
  4498.                     if(scale == 0.0f)
  4499.                     {
  4500.                         Cancel(info);
  4501.                     }
  4502.                     else
  4503.                     {
  4504.                         info.damageTypes.ScaleAll(scale);
  4505.                     }
  4506.  
  4507.                     return true;
  4508.                 }
  4509.             }
  4510.  
  4511.             public static BasePlayer GetAttacker(BaseCombatEntity entity, HitInfo info)
  4512.             {
  4513.                 var attacker = info?.Initiator ?? entity?.lastAttacker;
  4514.  
  4515.                 if(attacker is FireBall)
  4516.                 {
  4517.                     return Fire.Initiator(attacker as FireBall);
  4518.                 }
  4519.  
  4520.                 return attacker as BasePlayer;
  4521.             }
  4522.  
  4523.             public static string GetName(BaseEntity entity)
  4524.             {
  4525.                 return GetName(entity, GetType(entity));
  4526.             }
  4527.             private static string GetName(BaseEntity entity, Type type)
  4528.             {
  4529.                 switch(type)
  4530.                 {
  4531.                 case Type.Animal:      return $"{Text.GetPlain(Key.EntityAnimal)}({entity.GetType().Name})";
  4532.                 case Type.AutoTurret:  return $"{Text.GetPlain(Key.EntityTrap)}({Text.GetPlain(Key.EntityAutoTurret)})";
  4533.                 case Type.Bear:        return $"{Text.GetPlain(Key.EntityAnimal)}({Text.GetPlain(Key.EntityBear)})";
  4534.                 case Type.Boar:        return $"{Text.GetPlain(Key.EntityAnimal)}({Text.GetPlain(Key.EntityBoar)})";
  4535.                 case Type.Bot:         return $"{Text.GetPlain(Key.EntityNPC)}({Text.Sanitize((entity as BasePlayer).displayName)})";
  4536.                 case Type.Bradley:     return $"{Text.GetPlain(Key.EntityNPC)}({Text.GetPlain(Key.EntityBradley)})";
  4537.                 case Type.Building:    return $"{Text.GetPlain(Key.EntityBuilding)}({GetPrefabName(entity)})";
  4538.                 case Type.Chicken:     return $"{Text.GetPlain(Key.EntityAnimal)}({Text.GetPlain(Key.EntityChicken)})";
  4539.                 case Type.Entity:      return $"{Text.GetPlain(Key.Entity)}({GetPrefabName(entity)})";
  4540.                 case Type.FlameTurret: return $"{Text.GetPlain(Key.EntityTrap)}({Text.GetPlain(Key.EntityFlameTurret)})";
  4541.                 case Type.GunTrap:     return $"{Text.GetPlain(Key.EntityTrap)}({Text.GetPlain(Key.EntityGunTrap)})";
  4542.                 case Type.Helicopter:  return $"{Text.GetPlain(Key.EntityNPC)}({Text.GetPlain(Key.EntityHelicopter)})";
  4543.                 case Type.Murderer:    return $"{Text.GetPlain(Key.EntityNPC)}({Text.GetPlain(Key.EntityMurderer)})";
  4544.                 case Type.NPC:         return $"{Text.GetPlain(Key.EntityNPC)}({entity.GetType().Name})";
  4545.                 case Type.Player:      return $"{Text.GetPlain(Key.EntityPlayer)}({Text.Sanitize((entity as BasePlayer).displayName)})";
  4546.                 case Type.SAMSite:     return $"{Text.GetPlain(Key.EntityTrap)}({Text.GetPlain(Key.EntitySAMSite)})";
  4547.                 case Type.Scientist:   return $"{Text.GetPlain(Key.EntityNPC)}({Text.GetPlain(Key.EntityScientist)})";
  4548.                 case Type.Sentry:      return $"{Text.GetPlain(Key.EntityNPC)}({Text.GetPlain(Key.EntityAutoTurret)})";
  4549.                 case Type.Stag:        return $"{Text.GetPlain(Key.EntityAnimal)}({Text.GetPlain(Key.EntityStag)})";
  4550.                 case Type.TC:          return $"{Text.GetPlain(Key.EntityBuilding)}({Text.GetPlain(Key.EntityTC)})";
  4551.                 }
  4552.  
  4553.                 return $"{Text.GetPlain(Key.Entity)}({Text.GetPlain(Key.NULL)})";
  4554.             }
  4555.  
  4556.             private static string GetPrefabName(BaseEntity entity)
  4557.             {
  4558.                 if(string.IsNullOrEmpty(entity.ShortPrefabName))
  4559.                 {
  4560.                     return entity.GetType().Name;
  4561.                 }
  4562.  
  4563.                 return entity.ShortPrefabName.Split('.')[0];
  4564.             }
  4565.  
  4566.             public static Type GetType(BaseEntity entity, out string name)
  4567.             {
  4568.                 var type = GetType(entity);
  4569.  
  4570.                 name = GetName(entity, type);
  4571.  
  4572.                 return type;
  4573.             }
  4574.             public static Type GetType(BaseEntity entity)
  4575.             {
  4576.                 if(entity == null)
  4577.                 {
  4578.                     return Type.NULL;
  4579.                 }
  4580.  
  4581.                 switch(entity.GetType().Name)
  4582.                 {
  4583.                 case "AutoTurret":     return Type.AutoTurret;
  4584.                 case "BaseHelicopter": return Type.Helicopter;
  4585.                 case "BasePlayer":     return (entity as BasePlayer).userID.IsSteamId() ? Type.Player : Type.Bot;
  4586.                 case "Bear":           return Type.Bear;
  4587.                 case "Boar":           return Type.Boar;
  4588.                 case "BradleyAPC":     return Type.Bradley;
  4589.                 case "Chicken":        return Type.Chicken;
  4590.                 case "FlameTurret":    return Type.FlameTurret;
  4591.                 case "GunTrap":        return Type.GunTrap;
  4592.                 case "HTNPlayer":      return Type.Scientist;
  4593.                 case "NPCAutoTurret":  return Type.Sentry;
  4594.                 case "NPCMurderer":    return Type.Murderer;
  4595.                 case "SamSite":        return Type.SAMSite;
  4596.                 case "Scientist":      return Type.Scientist;
  4597.                 case "ScientistNPC":   return Type.Scientist;
  4598.                 case "Stag":           return Type.Stag;
  4599.                 }
  4600.  
  4601.                 if(entity is BasePlayer)
  4602.                 {
  4603.                     return Type.NPC;
  4604.                 }
  4605.                 else if(entity is BaseAnimalNPC)
  4606.                 {
  4607.                     return Type.Animal;
  4608.                 }
  4609.                 else if(entity is BuildingBlock)
  4610.                 {
  4611.                     return Type.Building;
  4612.                 }
  4613.                 else if(entity is BuildingPrivlidge)
  4614.                 {
  4615.                     return Type.TC;
  4616.                 }
  4617.  
  4618.                 return Type.Entity;
  4619.             }
  4620.  
  4621.             public class Health
  4622.             {
  4623.                 private static readonly Dictionary<int, float> health = new Dictionary<int, float>();
  4624.  
  4625.                 public static bool Changed(BaseEntity entity)
  4626.                 {
  4627.                     if(entity != null)
  4628.                     {
  4629.                         var instanceid = entity.GetInstanceID();
  4630.  
  4631.                         float current = entity.Health(), previous;
  4632.  
  4633.                         if(!health.TryGetValue(instanceid, out previous))
  4634.                         {
  4635.                             previous = float.MaxValue;
  4636.                         }
  4637.  
  4638.                         health[instanceid] = current;
  4639.  
  4640.                         return previous != current;
  4641.                     }
  4642.  
  4643.                     return false;
  4644.                 }
  4645.  
  4646.                 public static void Clear() => health.Clear();
  4647.                 public static void Clear(BaseEntity entity)
  4648.                 {
  4649.                     if(entity != null)
  4650.                     {
  4651.                         health.Remove(entity.GetInstanceID());
  4652.                     }
  4653.                 }
  4654.             }
  4655.  
  4656.             public static void Unload() => Health.Clear();
  4657.         }
  4658.  
  4659.         #endregion _entity_
  4660.  
  4661.         #region _fire_
  4662.  
  4663.         private class Fire
  4664.         {
  4665.             private static Dictionary<int, BasePlayer> fires = new Dictionary<int, BasePlayer>();
  4666.  
  4667.             public static void Ignite(FireBall fire, BasePlayer initiator)
  4668.             {
  4669.                 if((fire != null) && (initiator?.userID.IsSteamId() ?? false))
  4670.                 {
  4671.                     fires[fire.GetInstanceID()] = initiator;
  4672.                 }
  4673.             }
  4674.  
  4675.             public static BasePlayer Initiator(FireBall fire)
  4676.             {
  4677.                 if(fire != null)
  4678.                 {
  4679.                     BasePlayer initiator;
  4680.  
  4681.                     if(fires.TryGetValue(fire.GetInstanceID(), out initiator))
  4682.                     {
  4683.                         return initiator;
  4684.                     }
  4685.                 }
  4686.  
  4687.                 return null;
  4688.             }
  4689.  
  4690.             public static void Spread(FireBall fire, FireBall spread)
  4691.             {
  4692.                 if((fire != null) || (spread != null))
  4693.                 {
  4694.                     BasePlayer initiator;
  4695.  
  4696.                     if(fires.TryGetValue(fire.GetInstanceID(), out initiator))
  4697.                     {
  4698.                         fires.Add(spread.GetInstanceID(), initiator);
  4699.                     }
  4700.                 }
  4701.             }
  4702.  
  4703.             public static void Quench(FireBall fire)
  4704.             {
  4705.                 if(fire != null)
  4706.                 {
  4707.                     fires.Remove(fire.GetInstanceID());
  4708.                 }
  4709.             }
  4710.  
  4711.             public static void Unload() => fires.Clear();
  4712.         }
  4713.  
  4714.         #endregion _fire_
  4715.  
  4716.         #region _generic_
  4717.  
  4718.         private class Generic
  4719.         {
  4720.             public static T Clamp<T>(T value, T min, T max) where T : IComparable<T>
  4721.             {
  4722.                 if(value.CompareTo(min) < 0)
  4723.                 {
  4724.                     return min;
  4725.                 }
  4726.                 else if(value.CompareTo(max) > 0)
  4727.                 {
  4728.                     return max;
  4729.                 }
  4730.  
  4731.                 return value;
  4732.             }
  4733.         }
  4734.  
  4735.         #endregion _generic_
  4736.  
  4737.         #region _hooks_
  4738.  
  4739.         private new class Hooks
  4740.         {
  4741.             private static HashSet<string> subscribed;
  4742.             private static HashSet<string> unsubscribed;
  4743.  
  4744.             public class Base
  4745.             {
  4746.                 public static void Subscribe()
  4747.                 {
  4748.                     Hooks.Subscribe(nameof(OnUserBanned));
  4749.                     Hooks.Subscribe(nameof(OnUserUnbanned));
  4750.                 }
  4751.             }
  4752.  
  4753.             public class Core
  4754.             {
  4755.                 public static void Subscribe()
  4756.                 {
  4757.                     Hooks.Subscribe(nameof(CanBypassQueue));
  4758.                     Hooks.Subscribe(nameof(CanLootEntity));
  4759.                     Hooks.Subscribe(nameof(CanLootPlayer));
  4760.                     Hooks.Subscribe(nameof(CanUserLogin));
  4761.                     Hooks.Subscribe(nameof(OnEntityDeath));
  4762.                     Hooks.Subscribe(nameof(OnEntityKill));
  4763.                     Hooks.Subscribe(nameof(OnEntityTakeDamage));
  4764.                     Hooks.Subscribe(nameof(OnFireBallDamage));
  4765.                     Hooks.Subscribe(nameof(OnFireBallSpread));
  4766.                     Hooks.Subscribe(nameof(OnFlameExplosion));
  4767.                     Hooks.Subscribe(nameof(OnFlameThrowerBurn));
  4768.                     Hooks.Subscribe(nameof(OnGroupPermissionGranted));
  4769.                     Hooks.Subscribe(nameof(OnGroupPermissionRevoked));
  4770.                     Hooks.Subscribe(nameof(OnLootEntity));
  4771.                     Hooks.Subscribe(nameof(OnLootPlayer));
  4772.                     Hooks.Subscribe(nameof(OnPlayerAttack));
  4773.                     Hooks.Subscribe(nameof(OnPlayerConnected));
  4774.                     Hooks.Subscribe(nameof(OnPlayerDisconnected));
  4775.                     Hooks.Subscribe(nameof(OnPlayerViolation));
  4776.                     Hooks.Subscribe(nameof(OnRocketLaunched));
  4777.                     Hooks.Subscribe(nameof(OnUserPermissionGranted));
  4778.                     Hooks.Subscribe(nameof(OnUserPermissionRevoked));
  4779.                     Hooks.Subscribe(nameof(OnWeaponFired));
  4780.                 }
  4781.             }
  4782.  
  4783.             public class Dynamic
  4784.             {
  4785.                 public static void Subscribe()
  4786.                 {
  4787.                     AntiCheat.Stash.Subscribe();
  4788.                     AntiFlood.Chat.Subscribe();
  4789.                     AntiFlood.Command.Subscribe();
  4790.                     AntiFlood.ItemDrop.Subscribe();
  4791.                     Discord.Subscribe();
  4792.                 }
  4793.             }
  4794.  
  4795.             public static void Load()
  4796.             {
  4797.                 subscribed = new HashSet<string>
  4798.                 {
  4799.                     nameof(CanBypassQueue),
  4800.                     nameof(CanCraft),
  4801.                     nameof(CanLootEntity),
  4802.                     nameof(CanLootPlayer),
  4803.                     nameof(CanSeeStash),
  4804.                     nameof(CanUserLogin),
  4805.                     nameof(OnEntityDeath),
  4806.                     nameof(OnEntityKill),
  4807.                     nameof(OnEntityTakeDamage),
  4808.                     nameof(OnFireBallDamage),
  4809.                     nameof(OnFireBallSpread),
  4810.                     nameof(OnFlameExplosion),
  4811.                     nameof(OnFlameThrowerBurn),
  4812.                     nameof(OnGuardianViolation),
  4813.                     nameof(OnGroupPermissionGranted),
  4814.                     nameof(OnGroupPermissionRevoked),
  4815.                     nameof(OnItemDropped),
  4816.                     nameof(OnLootEntity),
  4817.                     nameof(OnLootPlayer),
  4818.                     nameof(OnPlayerAttack),
  4819.                     nameof(OnPlayerChat),
  4820.                     nameof(OnPlayerConnected),
  4821.                     nameof(OnPlayerDisconnected),
  4822.                     nameof(OnPlayerViolation),
  4823.                     nameof(OnRocketLaunched),
  4824.                     nameof(OnServerCommand),
  4825.                     nameof(OnUserBanned),
  4826.                     nameof(OnUserPermissionGranted),
  4827.                     nameof(OnUserPermissionRevoked),
  4828.                     nameof(OnUserUnbanned),
  4829.                     nameof(OnWeaponFired)
  4830.                 };
  4831.  
  4832.                 unsubscribed = new HashSet<string>();
  4833.  
  4834.                 Unsubscribe();
  4835.             }
  4836.  
  4837.             public static void Subscribe(string hook)
  4838.             {
  4839.                 if(unsubscribed.Contains(hook))
  4840.                 {
  4841.                     unsubscribed.Remove(hook);
  4842.  
  4843.                     _instance.Subscribe(hook);
  4844.  
  4845.                     subscribed.Add(hook);
  4846.                 }
  4847.             }
  4848.  
  4849.             public static void Unload()
  4850.             {
  4851.                 Unsubscribe();
  4852.  
  4853.                 subscribed = null;
  4854.  
  4855.                 unsubscribed.Clear();
  4856.                 unsubscribed = null;
  4857.             }
  4858.  
  4859.             private static void Unsubscribe()
  4860.             {
  4861.                 foreach(var hook in subscribed)
  4862.                 {
  4863.                     _instance.Unsubscribe(hook);
  4864.  
  4865.                     unsubscribed.Add(hook);
  4866.                 }
  4867.  
  4868.                 subscribed.Clear();
  4869.             }
  4870.             public static void Unsubscribe(string hook)
  4871.             {
  4872.                 if(subscribed.Contains(hook))
  4873.                 {
  4874.                     subscribed.Remove(hook);
  4875.  
  4876.                     _instance.Unsubscribe(hook);
  4877.  
  4878.                     unsubscribed.Add(hook);
  4879.                 }
  4880.             }
  4881.         }
  4882.  
  4883.         #region _hooks_lifecycle_
  4884.  
  4885.         private void Init()
  4886.         {
  4887.             _instance = this;
  4888.  
  4889.             Data.Open();
  4890.             Configuration.Load();
  4891.  
  4892.             Hooks.Load();
  4893.  
  4894.             Permissions.Load();
  4895.  
  4896.             Text.Load();
  4897.  
  4898.             IP.Load();
  4899.             User.Load();
  4900.  
  4901.             Projectile.Load();
  4902.             Steam.Load();
  4903.             Violation.Load();
  4904.             VPN.Load();
  4905.             Weapon.Load();
  4906.  
  4907.             AntiCheat.Configure();
  4908.             AntiFlood.Configure();
  4909.  
  4910.             Command.Load();
  4911.  
  4912.             Configuration.Save();
  4913.         }
  4914.  
  4915.         private void Loaded()
  4916.         {
  4917.             Hooks.Base.Subscribe();
  4918.         }
  4919.  
  4920.         protected override void LoadDefaultConfig() { }
  4921.  
  4922.         protected override void LoadDefaultMessages() { }
  4923.  
  4924.         private void OnServerInitialized()
  4925.         {
  4926.             Hooks.Core.Subscribe();
  4927.  
  4928.             Hooks.Dynamic.Subscribe();
  4929.  
  4930.             Map.Load();
  4931.  
  4932.             User.Update();
  4933.         }
  4934.  
  4935.         private void OnServerSave()
  4936.         {
  4937.             Configuration.Save();
  4938.  
  4939.             IP.Save();
  4940.             User.Save();
  4941.             VPN.Save();
  4942.         }
  4943.  
  4944.         private void Unload()
  4945.         {
  4946.             Hooks.Unload();
  4947.  
  4948.             Timers.Destroy();
  4949.  
  4950.             Map.Unload();
  4951.             Fire.Unload();
  4952.             Entity.Unload();
  4953.  
  4954.             Command.Unload();
  4955.  
  4956.             AntiFlood.Unload();
  4957.             AntiCheat.Unload();
  4958.  
  4959.             Weapon.Unload();
  4960.             VPN.Unload();
  4961.             Violation.Unload();
  4962.             Steam.Unload();
  4963.             Projectile.Unload();
  4964.  
  4965.             User.Unload();
  4966.             IP.Unload();
  4967.  
  4968.             Text.Unload();
  4969.  
  4970.             Permissions.Unload();
  4971.  
  4972.             Configuration.Unload();
  4973.             Data.Close();
  4974.  
  4975.             _instance = null;
  4976.         }
  4977.  
  4978.         #endregion _hooks_lifecycle_
  4979.  
  4980.         #region _hooks_other_
  4981.  
  4982.         private object CanBypassQueue(Connection connection)
  4983.         {
  4984.             if(Permissions.Ignore(connection.userid, true))
  4985.             {
  4986.                 return true;
  4987.             }
  4988.             else if(config.Admin.Bypass && Permissions.Admin(connection.userid, true))
  4989.             {
  4990.                 return true;
  4991.             }
  4992.  
  4993.             return null;
  4994.         }
  4995.  
  4996.         private object CanCraft(ItemCrafter crafter, ItemBlueprint bp, int amount)
  4997.         {
  4998.             if(!config.AntiFlood.ItemDrop.Enabled)
  4999.             {
  5000.                 return null;
  5001.             }
  5002.  
  5003.             var player = crafter.gameObject.GetComponent<BasePlayer>();
  5004.  
  5005.             if(User.ShouldIgnore(player))
  5006.             {
  5007.                 return null;
  5008.             }
  5009.  
  5010.             var cooldown = AntiFlood.ItemDrop.CoolDown(player.userID, bp.targetItem.itemid);
  5011.  
  5012.             if(cooldown == 0)
  5013.             {
  5014.                 return null;
  5015.             }
  5016.  
  5017.             Chat.Send(player, Key.Cooldown, new Dictionary<string, string>
  5018.             {
  5019.                 { "cooldown", cooldown.ToString() },
  5020.                 { "type", Text.Get(Key.Crafting) }
  5021.             });
  5022.  
  5023.             AntiFlood.ItemDrop.Violation(player, bp.targetItem.displayName.english);
  5024.  
  5025.             return false;
  5026.         }
  5027.  
  5028.         private object CanLootEntity(BasePlayer looter, DroppedItemContainer target) => User.CanLoot(looter, target.playerSteamID);
  5029.  
  5030.         private object CanLootEntity(BasePlayer looter, LootableCorpse target) => User.CanLoot(looter, target.playerSteamID);
  5031.  
  5032.         private object CanLootPlayer(BasePlayer target, BasePlayer looter) => User.CanLoot(looter, target.userID);
  5033.  
  5034.         private object CanSeeStash(BasePlayer player, StashContainer stash)
  5035.         {
  5036.             if(!config.AntiCheat.Stash.Enabled)
  5037.             {
  5038.                 return null;
  5039.             }
  5040.  
  5041.             if(User.ShouldIgnore(player))
  5042.             {
  5043.                 return null;
  5044.             }
  5045.  
  5046.             AntiCheat.Stash.Trigger(player, stash);
  5047.  
  5048.             return null;
  5049.         }
  5050.  
  5051.         private object CanUserLogin(string name, string id, string address)
  5052.         {
  5053.             return User.CanConnect(name, id, address);
  5054.         }
  5055.  
  5056.         private void OnEntityDeath(BaseCombatEntity entity, HitInfo info)
  5057.         {
  5058.             Entity.Health.Clear(entity);
  5059.  
  5060.             var victim = entity.ToPlayer();
  5061.  
  5062.             if(User.ShouldIgnore(victim))
  5063.             {
  5064.                 return;
  5065.             }
  5066.  
  5067.             var attacker = Entity.GetAttacker(entity, info);
  5068.  
  5069.             if(!User.ShouldIgnore(attacker))
  5070.             {
  5071.                 User.AssignAttacker(attacker, victim);
  5072.             }
  5073.  
  5074.             User.AssignVictim(victim);
  5075.         }
  5076.  
  5077.         void OnEntityKill(BaseNetworkable entity)
  5078.         {
  5079.             if(entity is FireBall)
  5080.             {
  5081.                 Fire.Quench(entity as FireBall);
  5082.             }
  5083.         }
  5084.  
  5085.         private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
  5086.         {
  5087.             var attacker = Entity.GetAttacker(entity, info);
  5088.  
  5089.             if(User.ShouldIgnore(attacker))
  5090.             {
  5091.                 return null;
  5092.             }
  5093.  
  5094.             if(info.IsProjectile())
  5095.             {
  5096.                 AntiCheat.Aim.Trigger(entity, info);
  5097.                 AntiCheat.FireRate.Trigger(entity, info);
  5098.                 AntiCheat.Trajectory.Trigger(entity, info);
  5099.             }
  5100.  
  5101.             if((attacker.net?.connection?.authLevel ?? 0) > 0)
  5102.             {
  5103.                 return null;
  5104.             }
  5105.  
  5106.             var victim = entity.ToPlayer();
  5107.  
  5108.             if(!User.ShouldIgnore(victim))
  5109.             {
  5110.                 User.AssignAttacker(attacker, victim);
  5111.  
  5112.                 if(User.IsCrippled(attacker.userID) && (victim != attacker))
  5113.                 {
  5114.                     if(victim.IsWounded())
  5115.                     {
  5116.                         victim.StopWounded();
  5117.                     }
  5118.  
  5119.                     Entity.Damage.Cancel(info);
  5120.  
  5121.                     if(config.Cripple.Heal)
  5122.                     {
  5123.                         _instance.NextFrame(() =>
  5124.                         {
  5125.                             victim.Heal(100.0f);
  5126.                         });
  5127.                     }
  5128.  
  5129.                     return true;
  5130.                 }
  5131.             }
  5132.  
  5133.             return Entity.Damage.Scale(info, attacker, entity);
  5134.         }
  5135.  
  5136.         private void OnFireBallDamage(FireBall fireball, BaseCombatEntity target, HitInfo info)
  5137.         {
  5138.             info.Initiator = fireball;
  5139.         }
  5140.  
  5141.         private void OnFireBallSpread(FireBall fireball, BaseEntity entity)
  5142.         {
  5143.             Fire.Spread(fireball, entity as FireBall);
  5144.         }
  5145.  
  5146.         private void OnFlameExplosion(FlameExplosive explosive, BaseEntity entity)
  5147.         {
  5148.             if(explosive.creatorEntity is BasePlayer)
  5149.             {
  5150.                 var fire = entity as FireBall;
  5151.  
  5152.                 var initiator = explosive.creatorEntity as BasePlayer;
  5153.  
  5154.                 if(initiator.userID.IsSteamId() && (fire != null))
  5155.                 {
  5156.                     Fire.Ignite(fire, initiator);
  5157.                 }
  5158.             }
  5159.         }
  5160.  
  5161.         private void OnFlameThrowerBurn(FlameThrower flamethrower, BaseEntity entity)
  5162.         {
  5163.             var initiator = flamethrower.GetOwnerPlayer();
  5164.  
  5165.             if(initiator != null)
  5166.             {
  5167.                 var fire = entity as FireBall;
  5168.  
  5169.                 if(initiator.userID.IsSteamId() && (fire != null))
  5170.                 {
  5171.                     Fire.Ignite(fire, initiator);
  5172.                 }
  5173.             }
  5174.         }
  5175.  
  5176.         private void OnGroupPermissionGranted(string name, string perm)
  5177.         {
  5178.             if(Permissions.HasPrefix(perm))
  5179.             {
  5180.                 foreach(var id in permission.GetUsersInGroup(name))
  5181.                 {
  5182.                     var player = BasePlayer.FindAwakeOrSleeping(id);
  5183.  
  5184.                     if(player != null)
  5185.                     {
  5186.                         Permissions.Update(player.userID);
  5187.                     }
  5188.                 }
  5189.             }
  5190.         }
  5191.  
  5192.         private void OnGroupPermissionRevoked(string name, string perm) => OnGroupPermissionGranted(name, perm);
  5193.  
  5194.         private void OnGuardianViolation(string playerid, Dictionary<string, string> details)
  5195.         {
  5196.             Discord.Send(details["category"], new Dictionary<string, object>
  5197.             {
  5198.                 {
  5199.                     "embeds", new List<object>
  5200.                     {
  5201.                         new Dictionary<string, object>
  5202.                         {
  5203.                             { "color", int.Parse(details["color"]) },
  5204.                             { "description",
  5205.                                 $"{details["actionicon"]} {details["action"]} [{details["playername"]}]" +
  5206.                                 $"(https://steamcommunity.com/profiles/{playerid}) {playerid}\n" +
  5207.                                 $"{details["categoryicon"]} {details["category"]} - {details["type"]}: {details["details"]}"
  5208.                             }
  5209.                         }
  5210.                     }
  5211.                 }
  5212.             });
  5213.         }
  5214.  
  5215.         private void OnItemDropped(Item item, BaseEntity entity)
  5216.         {
  5217.             if(!config.AntiFlood.ItemDrop.Enabled)
  5218.             {
  5219.                 return;
  5220.             }
  5221.  
  5222.             var player = item?.GetOwnerPlayer();
  5223.  
  5224.             if(User.ShouldIgnore(player))
  5225.             {
  5226.                 return;
  5227.             }
  5228.  
  5229.             if(AntiFlood.ItemDrop.Trigger(player.userID, item.info.itemid))
  5230.             {
  5231.                 entity?.Kill();
  5232.             }
  5233.         }
  5234.  
  5235.         private void OnLootEntity(BasePlayer looter, BaseEntity target)
  5236.         {
  5237.             if(target is LootableCorpse)
  5238.             {
  5239.                 User.OnLoot(looter, (target as LootableCorpse).playerSteamID);
  5240.             }
  5241.             else if(target is DroppedItemContainer)
  5242.             {
  5243.                 User.OnLoot(looter, (target as DroppedItemContainer).playerSteamID);
  5244.             }
  5245.         }
  5246.  
  5247.         private void OnLootPlayer(BasePlayer looter, BasePlayer target) => User.OnLoot(looter, target.userID);
  5248.  
  5249.         private object OnPlayerAttack(BasePlayer player, HitInfo info)
  5250.         {
  5251.             if(User.ShouldIgnore(player) || (info.Weapon == null))
  5252.             {
  5253.                 return null;
  5254.             }
  5255.  
  5256.             if(info.IsProjectile())
  5257.             {
  5258.                 AntiCheat.WallHack.Trigger(player, info);
  5259.             }
  5260.             else
  5261.             {
  5262.                 AntiCheat.MeleeRate.Trigger(player, info);
  5263.             }
  5264.  
  5265.  
  5266.             return null;
  5267.         }
  5268.  
  5269.         private object OnPlayerChat(BasePlayer player, string message, ConVar.Chat.ChatChannel channel)
  5270.         {
  5271.             if(!config.AntiFlood.Chat.Enabled)
  5272.             {
  5273.                 return null;
  5274.             }
  5275.  
  5276.             if(User.ShouldIgnore(player))
  5277.             {
  5278.                 return null;
  5279.             }
  5280.  
  5281.             if(!AntiFlood.Chat.Trigger(player.userID))
  5282.             {
  5283.                 return null;
  5284.             }
  5285.  
  5286.             Chat.Send(player, Key.Cooldown, new Dictionary<string, string>
  5287.             {
  5288.                 { "cooldown", AntiFlood.Chat.Cooldown(player.userID).ToString() },
  5289.                 { "type", Text.Get(Key.Chat) }
  5290.             });
  5291.  
  5292.             AntiFlood.Chat.Violation(player, Enum.GetName(typeof(ConVar.Chat.ChatChannel), channel));
  5293.  
  5294.             return true;
  5295.         }
  5296.  
  5297.         private void OnPlayerConnected(BasePlayer player)
  5298.         {
  5299.             User.OnConnected(player);
  5300.         }
  5301.  
  5302.         private void OnPlayerDisconnected(BasePlayer player)
  5303.         {
  5304.             User.OnDisconnected(player);
  5305.         }
  5306.  
  5307.         private object OnPlayerViolation(BasePlayer player, AntiHackType ahtype, float amount)
  5308.         {
  5309.             if(User.ShouldIgnore(player) || User.IsInactive(player))
  5310.             {
  5311.                 return true;
  5312.             }
  5313.  
  5314.             if(ahtype == AntiHackType.FlyHack)
  5315.             {
  5316.                 if(config.AntiCheat.Gravity.Enabled)
  5317.                 {
  5318.                     NextFrame(() => AntiCheat.Gravity.Trigger(player, amount));
  5319.  
  5320.                     return true;
  5321.                 }
  5322.             }
  5323.             else
  5324.             {
  5325.                 if(config.AntiCheat.Server.Enabled)
  5326.                 {
  5327.                     NextFrame(() => AntiCheat.Server.Trigger(player, ahtype, amount));
  5328.  
  5329.                     return true;
  5330.                 }
  5331.             }
  5332.  
  5333.             return null;
  5334.         }
  5335.  
  5336.         private void OnRocketLaunched(BasePlayer player, BaseEntity entity)
  5337.         {
  5338.             if(User.ShouldIgnore(player))
  5339.             {
  5340.                 return;
  5341.             }
  5342.  
  5343.             var projectile = player.GetActiveItem().GetHeldEntity() as BaseProjectile;
  5344.  
  5345.             if(projectile == null)
  5346.             {
  5347.                 return;
  5348.             }
  5349.  
  5350.             var time = UnityEngine.Time.realtimeSinceStartup;
  5351.  
  5352.             var ammo = projectile.primaryMagazine.ammoType;
  5353.  
  5354.             var item = projectile.GetItem();
  5355.  
  5356.             var itemmod = ammo.GetComponent<ItemModProjectile>();
  5357.  
  5358.             var projectiles = Pool.Get<ProtoBuf.ProjectileShoot>();
  5359.  
  5360.             projectiles.ammoType    = ammo.itemid;
  5361.             projectiles.projectiles = Pool.Get<List<ProtoBuf.ProjectileShoot.Projectile>>();
  5362.  
  5363.             if((item?.info.shortname ?? string.Empty) != "pistol.eoka")
  5364.             {
  5365.                 foreach(var fired in player.firedProjectiles)
  5366.                 {
  5367.                     if((fired.Value.weaponSource as BaseProjectile) != projectile)
  5368.                     {
  5369.                         continue;
  5370.                     }
  5371.  
  5372.                     if((time - fired.Value.firedTime) > 0.01)
  5373.                     {
  5374.                         continue;
  5375.                     }
  5376.  
  5377.                     var entry = Pool.Get<ProtoBuf.ProjectileShoot.Projectile>();
  5378.  
  5379.                     entry.projectileID = fired.Key;
  5380.                     entry.startPos = fired.Value.initialPosition;
  5381.                     entry.startVel = fired.Value.initialVelocity;
  5382.  
  5383.                     projectiles.projectiles.Add(entry);
  5384.                 }
  5385.             }
  5386.  
  5387.             OnWeaponFired(projectile, player, itemmod, projectiles);
  5388.  
  5389.             projectiles.ResetToPool();
  5390.         }
  5391.  
  5392.         private object OnServerCommand(ConsoleSystem.Arg arg)
  5393.         {
  5394.             if(!config.AntiFlood.Command.Enabled)
  5395.             {
  5396.                 return null;
  5397.             }
  5398.  
  5399.             var player = arg.Player();
  5400.  
  5401.             if(User.ShouldIgnore(player) || (arg.cmd.FullName == "craft.add"))
  5402.             {
  5403.                 return null;
  5404.             }
  5405.  
  5406.             if(!AntiFlood.Command.Trigger(player.userID))
  5407.             {
  5408.                 return null;
  5409.             }
  5410.  
  5411.             Chat.Send(player, Key.Cooldown, new Dictionary<string, string>
  5412.             {
  5413.                 { "cooldown", AntiFlood.Command.Cooldown(player.userID).ToString() },
  5414.                 { "type", Text.Get(Key.Command) }
  5415.             });
  5416.  
  5417.             AntiFlood.Command.Violation(player, arg.cmd.FullName);
  5418.  
  5419.             return true;
  5420.         }
  5421.  
  5422.         private void OnUserBanned(string playername, string playerid, string playerip, string reason)
  5423.         {
  5424.             ulong userid;
  5425.  
  5426.             if(ulong.TryParse(playerid, out userid))
  5427.             {
  5428.                 if((userid != 0) && userid.IsSteamId())
  5429.                 {
  5430.                     NextTick(() => User.OnBanned(userid, true));
  5431.                 }
  5432.             }
  5433.         }
  5434.  
  5435.         private void OnUserPermissionGranted(string id, string perm)
  5436.         {
  5437.             if(Permissions.HasPrefix(perm))
  5438.             {
  5439.                 var player = BasePlayer.FindAwakeOrSleeping(id);
  5440.  
  5441.                 if(player != null)
  5442.                 {
  5443.                     Permissions.Update(player.userID);
  5444.                 }
  5445.             }
  5446.         }
  5447.  
  5448.         private void OnUserPermissionRevoked(string id, string perm) => OnUserPermissionGranted(id, perm);
  5449.  
  5450.         private void OnUserUnbanned(string playername, string playerid, string playerip)
  5451.         {
  5452.             ulong userid;
  5453.  
  5454.             if(ulong.TryParse(playerid, out userid))
  5455.             {
  5456.                 if((userid != 0) && userid.IsSteamId())
  5457.                 {
  5458.                     NextTick(() => User.OnBanned(userid, false));
  5459.                 }
  5460.             }
  5461.         }
  5462.  
  5463.         private void OnWeaponFired(BaseProjectile projectile, BasePlayer player, ItemModProjectile itemmod, ProtoBuf.ProjectileShoot projectiles)
  5464.         {
  5465.             if(User.ShouldIgnore(player))
  5466.             {
  5467.                 return;
  5468.             }
  5469.  
  5470.              AntiCheat.Recoil.Trigger(player, Weapon.Get(projectile, player, projectiles));
  5471.         }
  5472.  
  5473.         #endregion _hooks_other_
  5474.  
  5475.         #endregion _hooks_
  5476.  
  5477.         #region _ip_
  5478.  
  5479.         private class IP
  5480.         {
  5481.             public class Settings
  5482.             {
  5483.                 public IpFilter    Filter;
  5484.                 public IpViolation Violation;
  5485.  
  5486.                 public Settings()
  5487.                 {
  5488.                     Filter    = new IpFilter();
  5489.                     Violation = new IpViolation();
  5490.                 }
  5491.  
  5492.                 public class IpFilter
  5493.                 {
  5494.                     public ulong Cooldown;
  5495.  
  5496.                     public IpFilter()
  5497.                     {
  5498.                         Cooldown = 30ul;
  5499.                     }
  5500.  
  5501.                     public void Validate()
  5502.                     {
  5503.                         Configuration.Clamp(ref Cooldown, 0ul, 60ul);
  5504.                     }
  5505.                 }
  5506.  
  5507.                 public class IpViolation
  5508.                 {
  5509.                     public bool Ban;
  5510.                     public bool Enabled;
  5511.  
  5512.                     public IpViolation()
  5513.                     {
  5514.                         Ban     = false;
  5515.                         Enabled = true;
  5516.                     }
  5517.                 }
  5518.  
  5519.                 public void Validate()
  5520.                 {
  5521.                     Configuration.Validate(ref Filter,    () => new IpFilter(), () => Filter.Validate());
  5522.                     Configuration.Validate(ref Violation, () => new IpViolation());
  5523.                 }
  5524.             }
  5525.  
  5526.             private static readonly DataFile<string, uint> allows = new DataFile<string, uint>("ip_allows");
  5527.             private static readonly DataFile<string, bool> blocks = new DataFile<string, bool>();
  5528.             private static readonly DataFile<string, uint> denies = new DataFile<string, uint>("ip_denies");
  5529.  
  5530.             private static readonly HashSet<ulong> empty = new HashSet<ulong>();
  5531.  
  5532.             private static readonly DataFile<string, DateTime> filter = new DataFile<string, DateTime>();
  5533.  
  5534.             private static TimeSpan filter_cooldown;
  5535.  
  5536.             private static readonly DataFile<string, HashSet<ulong>> users = new DataFile<string, HashSet<ulong>>("ip_users");
  5537.  
  5538.             private static readonly Violation violation = new Violation(Key.IP);
  5539.  
  5540.             public class NetworkInfo
  5541.             {
  5542.                 public string Address { get; protected set; }
  5543.                 public uint   Bits { get; protected set; }
  5544.  
  5545.                 public NetworkInfo(string address, uint bits)
  5546.                 {
  5547.                     Address = address;
  5548.                     Bits    = bits;
  5549.                 }
  5550.             }
  5551.  
  5552.             private static uint Bits(string address)
  5553.             {
  5554.                 uint bits = 0;
  5555.  
  5556.                 var octets = address.Split('.');
  5557.  
  5558.                 foreach(var octet in octets)
  5559.                 {
  5560.                     if(octet == "*")
  5561.                     {
  5562.                         return bits;
  5563.                     }
  5564.                     else
  5565.                     {
  5566.                         bits += 8;
  5567.                     }
  5568.                 }
  5569.  
  5570.                 return bits;
  5571.             }
  5572.  
  5573.             public static void Block(NetworkInfo network)
  5574.             {
  5575.                 foreach(var address in Match(network))
  5576.                 {
  5577.                     if(!IsAllowed(address))
  5578.                     {
  5579.                         Block(address);
  5580.                     }
  5581.                 }
  5582.             }
  5583.  
  5584.             public static void Block(string address, ulong userid = 0)
  5585.             {
  5586.                 blocks[address] = true;
  5587.  
  5588.                 if(userid != 0)
  5589.                 {
  5590.                     Violation(address, userid);
  5591.                 }
  5592.  
  5593.                 _instance.timer.In(5.0f, () =>
  5594.                 {
  5595.                     foreach(var entry in users[address])
  5596.                     {
  5597.                         if(!User.IsConnected(entry) || Permissions.Ignore(entry))
  5598.                         {
  5599.                             continue;
  5600.                         }
  5601.  
  5602.                         Violation(address, userid);
  5603.                     }
  5604.                 });
  5605.             }
  5606.  
  5607.             public static void Bypass(NetworkInfo network)
  5608.             {
  5609.                 foreach(var address in Match(network))
  5610.                 {
  5611.                     blocks[address] = false;
  5612.                 }
  5613.             }
  5614.  
  5615.             public static void Bypass(string address) => blocks[address] = false;
  5616.  
  5617.             private static uint Decimal(string address)
  5618.             {
  5619.                 uint result = 0;
  5620.  
  5621.                 if(!string.IsNullOrEmpty(address))
  5622.                 {
  5623.                     var octets = address.Split('.');
  5624.  
  5625.                     if(octets.Length <= 4)
  5626.                     {
  5627.                         foreach(var octet in octets)
  5628.                         {
  5629.                             uint value;
  5630.  
  5631.                             if(uint.TryParse(octet, out value) && (value <= 255))
  5632.                             {
  5633.                                 result = (result << 8) + value;
  5634.                             }
  5635.                             else
  5636.                             {
  5637.                                 return 0;
  5638.                             }
  5639.                         }
  5640.  
  5641.                         if(octets.Length < 4)
  5642.                         {
  5643.                             result <<= ((4 - octets.Length) << 3);
  5644.                         }
  5645.                     }
  5646.                 }
  5647.  
  5648.                 return result;
  5649.             }
  5650.  
  5651.             public static bool Cooldown(string address)
  5652.             {
  5653.                 if(config.IP.Filter.Cooldown == 0)
  5654.                 {
  5655.                     return false;
  5656.                 }
  5657.  
  5658.                 var current = DateTime.UtcNow;
  5659.                 var elapsed = current.Subtract(filter.Get(address, DateTime.MinValue));
  5660.  
  5661.                 if(elapsed >= filter_cooldown)
  5662.                 {
  5663.                     filter[address] = current;
  5664.  
  5665.                     return false;
  5666.                 }
  5667.  
  5668.                 return true;
  5669.             }
  5670.  
  5671.             public static void Configure()
  5672.             {
  5673.                 config.IP.Filter.Validate();
  5674.  
  5675.                 if(config.IP.Filter.Cooldown > 0)
  5676.                 {
  5677.                     filter_cooldown = TimeSpan.FromSeconds(config.IP.Filter.Cooldown);
  5678.                 }
  5679.                 else
  5680.                 {
  5681.                     filter_cooldown = TimeSpan.MinValue;
  5682.                 }
  5683.             }
  5684.  
  5685.             public static bool Filter(string address, ulong userid)
  5686.             {
  5687.                 if(blocks.Contains(address))
  5688.                 {
  5689.                     if(blocks[address])
  5690.                     {
  5691.                         if(config.Log.IP.Filter)
  5692.                         {
  5693.                             Log.Console(Key.LogIpFilter, new Dictionary<string, string>
  5694.                             {
  5695.                                 { "action", Text.Get(Key.Blocked) },
  5696.                                 { "address", address },
  5697.                             });
  5698.                         }
  5699.  
  5700.                         Block(address, userid);
  5701.  
  5702.                         return false;
  5703.                     }
  5704.                 }
  5705.                 else if(IsDenied(address))
  5706.                 {
  5707.                     if(!IsAllowed(address))
  5708.                     {
  5709.                         if(config.Log.IP.Filter)
  5710.                         {
  5711.                             Log.Console(Key.LogIpFilter, new Dictionary<string, string>
  5712.                             {
  5713.                                 { "action", Text.Get(Key.Denied) },
  5714.                                 { "address", address },
  5715.                             });
  5716.                         }
  5717.  
  5718.                         Block(address, userid);
  5719.  
  5720.                         return false;
  5721.                     }
  5722.                 }
  5723.  
  5724.                 if(config.Log.IP.Filter)
  5725.                 {
  5726.                     Log.Console(Key.LogIpFilter, new Dictionary<string, string>
  5727.                     {
  5728.                         { "action", Text.Get(Key.Allowed) },
  5729.                         { "address", address },
  5730.                     });
  5731.                 }
  5732.  
  5733.                 return true;
  5734.             }
  5735.  
  5736.             public static HashSet<ulong> Find(string address)
  5737.             {
  5738.                 if(users.Contains(address))
  5739.                 {
  5740.                     return users[address];
  5741.                 }
  5742.  
  5743.                 return empty;
  5744.             }
  5745.  
  5746.             private static List<string> Get(DataFile<string, uint> data, string address)
  5747.             {
  5748.                 bool all = (address == null);
  5749.  
  5750.                 uint ip = all ? 0 : Decimal(address);
  5751.  
  5752.                 var results = new List<string>();
  5753.  
  5754.                 data.ForEach((network, bits) =>
  5755.                 {
  5756.                     if(all || Match(ip, Decimal(network), bits))
  5757.                     {
  5758.                         results.Add($"{network}/{bits}");
  5759.                     }
  5760.                 });
  5761.  
  5762.                 return results;
  5763.             }
  5764.  
  5765.             public static List<string> GetAllows(string address = null) => Get(allows, address);
  5766.  
  5767.             public static List<string> GetDenies(string address = null) => Get(denies, address);
  5768.  
  5769.             public static bool IsAllowed(string address) => IsMatched(allows, address);
  5770.  
  5771.             public static bool IsBlocked(string address) => blocks.Get(address, false);
  5772.  
  5773.             public static bool IsDenied(string address) => IsMatched(denies, address);
  5774.  
  5775.             private static bool IsMatched(DataFile<string, uint> data, string address)
  5776.             {
  5777.                 var ip = Decimal(address);
  5778.  
  5779.                 bool match = false;
  5780.  
  5781.                 data.ForEach((network, bits) =>
  5782.                 {
  5783.                     if(!match)
  5784.                     {
  5785.                         match = Match(ip, Decimal(network), bits);
  5786.                     }
  5787.                 });
  5788.  
  5789.                 return match;
  5790.             }
  5791.  
  5792.             public static bool IsValid(string address, bool full = true, bool wildcard = false)
  5793.             {
  5794.                 if(string.IsNullOrEmpty(address))
  5795.                 {
  5796.                     return false;
  5797.                 }
  5798.  
  5799.                 var octets = address.Split('.');
  5800.  
  5801.                 if(full ? (octets.Length != 4) : (octets.Length > 4))
  5802.                 {
  5803.                     return false;
  5804.                 }
  5805.  
  5806.                 bool found_wildcard = false;
  5807.  
  5808.                 foreach(var octet in octets)
  5809.                 {
  5810.                     uint value;
  5811.  
  5812.                     if(octet == "*")
  5813.                     {
  5814.                         if(wildcard)
  5815.                         {
  5816.                             found_wildcard = true;
  5817.                         }
  5818.                         else
  5819.                         {
  5820.                             return false;
  5821.                         }
  5822.                     }
  5823.                     if(!uint.TryParse(octet, out value) || (value > 255) || found_wildcard)
  5824.                     {
  5825.                         return false;
  5826.                     }
  5827.                 }
  5828.  
  5829.                 return true;
  5830.             }
  5831.  
  5832.             public static void Load()
  5833.             {
  5834.                 violation.Configure(new Violation.Settings(true, 3600), 1, 1, 1);
  5835.  
  5836.                 allows.Load();
  5837.                 denies.Load();
  5838.  
  5839.                 users.Load();
  5840.  
  5841.                 Configure();
  5842.             }
  5843.  
  5844.             private static bool Match(uint ip, uint network, uint bits)
  5845.             {
  5846.                 bool match = false;
  5847.  
  5848.                 if(bits == 0)
  5849.                 {
  5850.                     match = true;
  5851.                 }
  5852.                 else
  5853.                 {
  5854.                     var mask = uint.MaxValue << (32 - (int)bits);
  5855.  
  5856.                     if((ip & mask) == (network & mask))
  5857.                     {
  5858.                         match = true;
  5859.                     }
  5860.                 }
  5861.  
  5862.                 return match;
  5863.             }
  5864.  
  5865.             private static List<string> Match(NetworkInfo network)
  5866.             {
  5867.                 var addresses = new List<string>();
  5868.  
  5869.                 uint network_address = Decimal(network.Address);
  5870.  
  5871.                 blocks.ForEach((address, blocked) =>
  5872.                 {
  5873.                     if(Match(Decimal(address), network_address, network.Bits))
  5874.                     {
  5875.                         addresses.Add(address);
  5876.                     }
  5877.                 });
  5878.  
  5879.                 return addresses;
  5880.             }
  5881.  
  5882.             public static NetworkInfo Network(string address)
  5883.             {
  5884.                 if(string.IsNullOrEmpty(address))
  5885.                 {
  5886.                     return null;
  5887.                 }
  5888.  
  5889.                 uint bits = uint.MaxValue;
  5890.  
  5891.                 var split = address.Split('/');
  5892.  
  5893.                 if((split.Length == 1) && IsValid(split[0], false, true))
  5894.                 {
  5895.                     bits = Bits(split[0]);
  5896.                 }
  5897.                 else if((split.Length == 2) && IsValid(split[0], false))
  5898.                 {
  5899.                     if(!uint.TryParse(split[1], out bits))
  5900.                     {
  5901.                         bits = uint.MaxValue;
  5902.                     }
  5903.                 }
  5904.  
  5905.                 if(bits <= 32)
  5906.                 {
  5907.                     var ip = Decimal(split[0].Replace('*', '0'));
  5908.  
  5909.                     var mask = uint.MaxValue << (32 - (int)bits);
  5910.  
  5911.                     return new NetworkInfo(ToString(ip & mask), bits);
  5912.                 }
  5913.  
  5914.                 return null;
  5915.             }
  5916.  
  5917.             public static string Parse(string address)
  5918.             {
  5919.                 if(string.IsNullOrEmpty(address))
  5920.                 {
  5921.                     return null;
  5922.                 }
  5923.  
  5924.                 int length = address.IndexOf(':');
  5925.  
  5926.                 if(length > 0)
  5927.                 {
  5928.                     return address.Substring(0, length);
  5929.                 }
  5930.  
  5931.                 if(IsValid(address))
  5932.                 {
  5933.                     return address;
  5934.                 }
  5935.  
  5936.                 return null;
  5937.             }
  5938.  
  5939.             public static void Save()
  5940.             {
  5941.                 allows.Save();
  5942.                 denies.Save();
  5943.  
  5944.                 users.Save();
  5945.             }
  5946.  
  5947.             private static bool Set(DataFile<string, uint> data, NetworkInfo network, bool add)
  5948.             {
  5949.                 var matches = Get(data, network.Address);
  5950.  
  5951.                 if(add)
  5952.                 {
  5953.                     if(matches.Count == 0)
  5954.                     {
  5955.                         data[network.Address] = network.Bits;
  5956.  
  5957.                         return true;
  5958.                     }
  5959.                 }
  5960.                 else
  5961.                 {
  5962.                     if(matches.Count > 0)
  5963.                     {
  5964.                         foreach(var match in matches)
  5965.                         {
  5966.                             data.Remove(match.Split('/')[0]);
  5967.                         }
  5968.  
  5969.                         return true;
  5970.                     }
  5971.                 }
  5972.  
  5973.                 return false;
  5974.             }
  5975.  
  5976.             public static bool SetAllow(NetworkInfo network, bool add) => Set(allows, network, add);
  5977.  
  5978.             public static bool SetDeny(NetworkInfo network, bool add) => Set(denies, network, add);
  5979.  
  5980.             public static string ToString(uint ip)
  5981.             {
  5982.                 StringBuilder result = new StringBuilder(16);
  5983.  
  5984.                 result.Append(((ip >> 24) & 255u).ToString()).Append('.');
  5985.                 result.Append(((ip >> 16) & 255u).ToString()).Append('.');
  5986.                 result.Append(((ip >>  8) & 255u).ToString()).Append('.');
  5987.                 result.Append(( ip        & 255u).ToString());
  5988.  
  5989.                 return result.ToString();
  5990.             }
  5991.  
  5992.             public static void Unblock(NetworkInfo network)
  5993.             {
  5994.                 foreach(var address in Match(network))
  5995.                 {
  5996.                     blocks.Remove(address);
  5997.                 }
  5998.             }
  5999.  
  6000.             public static void Unblock(string address) => blocks.Remove(address);
  6001.  
  6002.             public static void Unload()
  6003.             {
  6004.                 allows.Unload();
  6005.                 blocks.Unload();
  6006.                 denies.Unload();
  6007.  
  6008.                 filter.Clear();
  6009.  
  6010.                 users.Unload();
  6011.  
  6012.                 violation.Clear();
  6013.             }
  6014.  
  6015.             public static void Update(string address, ulong userid)
  6016.             {
  6017.                 if(!string.IsNullOrEmpty(address))
  6018.                 {
  6019.                     if(!users.Contains(address))
  6020.                     {
  6021.                         users.Add(address, new HashSet<ulong>());
  6022.                     }
  6023.  
  6024.                     if(users[address].Add(userid))
  6025.                     {
  6026.                         users.SetDirty();
  6027.                     }
  6028.                 }
  6029.             }
  6030.  
  6031.             private static void Violation(string address, ulong userid)
  6032.             {
  6033.                 if(config.IP.Violation.Enabled)
  6034.                 {
  6035.                     ulong violations = config.IP.Violation.Ban ? ulong.MaxValue : 1ul;
  6036.  
  6037.                     violation.Trigger(userid, Key.Blocked, address, violations, true);
  6038.                 }
  6039.                 else
  6040.                 {
  6041.                     if(User.IsConnected(userid))
  6042.                     {
  6043.                         User.Kick(userid, Text.GetPlain(Key.IpBlocked));
  6044.                     }
  6045.                 }
  6046.             }
  6047.         }
  6048.  
  6049.         #endregion _ip_
  6050.  
  6051.         #region _log_
  6052.  
  6053.         private new class Log
  6054.         {
  6055.             public class Settings
  6056.             {
  6057.                 public LogAntiCheat  AntiCheat;
  6058.                 public LogAntiFlood  AntiFlood;
  6059.                 public LogIp         IP;
  6060.                 public LogProjectile Projectile;
  6061.                 public LogUser       User;
  6062.                 public LogVpn        VPN;
  6063.  
  6064.                 public Settings()
  6065.                 {
  6066.                     AntiCheat  = new LogAntiCheat();
  6067.                     AntiFlood  = new LogAntiFlood();
  6068.                     IP         = new LogIp();
  6069.                     Projectile = new LogProjectile();
  6070.                     User       = new LogUser();
  6071.                     VPN        = new LogVpn();
  6072.                 }
  6073.  
  6074.                 public class LogAntiCheat
  6075.                 {
  6076.                     public bool Aim;
  6077.                     public bool Gravity;
  6078.                     public bool MeleeRate;
  6079.                     public bool Recoil;
  6080.                     public bool Server;
  6081.                     public bool Stash;
  6082.                     public bool Trajectory;
  6083.                 }
  6084.  
  6085.                 public class LogAntiFlood
  6086.                 {
  6087.                     public bool ItemDrop;
  6088.                 }
  6089.  
  6090.                 public class LogIp
  6091.                 {
  6092.                     public bool Filter;
  6093.  
  6094.                     public LogIp()
  6095.                     {
  6096.                         Filter = true;
  6097.                     }
  6098.                 }
  6099.  
  6100.                 public class LogProjectile
  6101.                 {
  6102.                     public bool Collapse;
  6103.                     public bool Verbose;
  6104.  
  6105.                     public LogProjectile()
  6106.                     {
  6107.                         Collapse = true;
  6108.                     }
  6109.                 }
  6110.  
  6111.                 public class LogUser
  6112.                 {
  6113.                     public bool Bypass;
  6114.                     public bool Connect;
  6115.  
  6116.                     public LogUser()
  6117.                     {
  6118.                         Connect = true;
  6119.                     }
  6120.                 }
  6121.  
  6122.                 public class LogVpn
  6123.                 {
  6124.                     public bool Check;
  6125.  
  6126.                     public LogVpn()
  6127.                     {
  6128.                         Check = true;
  6129.                     }
  6130.                 }
  6131.  
  6132.                 public void Validate()
  6133.                 {
  6134.                     Configuration.Validate(ref AntiCheat,  () => new LogAntiCheat());
  6135.                     Configuration.Validate(ref AntiFlood,  () => new LogAntiFlood());
  6136.                     Configuration.Validate(ref IP,         () => new LogIp());
  6137.                     Configuration.Validate(ref Projectile, () => new LogProjectile());
  6138.                     Configuration.Validate(ref User,       () => new LogUser());
  6139.                     Configuration.Validate(ref VPN,        () => new LogVpn());
  6140.                 }
  6141.             }
  6142.  
  6143.             public static void Console(Key key, Dictionary<string, string> parameters = null)
  6144.             {
  6145.                 _instance.Puts(Text.GetPlain(key, parameters));
  6146.             }
  6147.  
  6148.             public static void Error(string message)
  6149.             {
  6150.                 _instance.LogError(message);
  6151.             }
  6152.  
  6153.             public static void Warning(string message)
  6154.             {
  6155.                 _instance.LogWarning(message);
  6156.             }
  6157.         }
  6158.  
  6159.         #endregion _log_
  6160.  
Advertisement
Comments
  • User was banned
Add Comment
Please, Sign In to add comment