
<?php
echo "PocketMine-MP plugin SAC v3.3.10
This file has been generated using ConsoleScript v1.12.9 at Mon, 21 May 2018 14:03:01 +0000
==== Signed by DarkWav as version 3.3.10 [ShadowX] ====
----------------
";

if(extension_loaded("phar")){
	$phar = new \Phar(__FILE__);
	foreach($phar->getMetadata() as $key => $value){
		echo ucfirst($key) . ": " . (is_array($value) ? implode(", ", $value) : $value) . "\n";
	}
}

__HALT_COMPILER(); ?>
             ]  a:9:{s:4:"name";s:3:"SAC";s:7:"version";s:6:"3.3.10";s:4:"main";s:15:"DarkWav\SAC\SAC";s:3:"api";a:5:{i:0;s:12:"3.0.0-ALPHA9";i:1;s:13:"3.0.0-ALPHA10";i:2;s:13:"3.0.0-ALPHA11";i:3;s:13:"3.0.0-ALPHA12";i:4;s:13:"3.0.0-ALPHA13";}s:6:"depend";s:0:"";s:11:"description";s:0:"";s:7:"authors";s:0:"";s:7:"website";s:0:"";s:12:"creationDate";i:1526911381;}
   plugin.yml  [  Q         resources/AntiForceGM.txt  [  !         resources/AntiForceOP.txt  [           resources/config.yml.  [.  آ      !   src/DarkWav/SAC/EventListener.php  [  ѧ         src/DarkWav/SAC/KickTask.php^  [^  fe         src/DarkWav/SAC/Observer.php  [           src/DarkWav/SAC/SAC.php  [  8      name: SAC
main: DarkWav\SAC\SAC
version: 3.3.10
load: STARTUP
enable: STARTUP
author: DarkWav
api: [3.0.0-ALPHA9,3.0.0-ALPHA10,3.0.0-ALPHA11,3.0.0-ALPHA12,3.0.0-ALPHA13]
commands:
      shadowanticheat:
           description: Shows ShadowAntiCheat version
           aliases: ["ShadowAntiCheat","AntiCheat","SAC"]
      sac:
           description: Shows ShadowAntiCheat version
           aliases: ["ShadowAntiCheat","AntiCheat","SAC"]
permissions:
    legitop:
        description: "A permission."
        default: false
    sac:
        description: "Allows to use all SAC features."
        default: false
        children:
            sac.admin:
                description: "Get notified when someone is hacking."
                default: op
            sac.fly:
                description: "A permission."
                default: false
            sac.glide:
                description: "A permission."
                default: false
            sac.speed:
                description: "A permission."
                default: false
            sac.noclip:
                description: "A permission."
                default: false
            sac.reach:
                description: "A permission."
                default: false
            sac.instantkill:
                description: "A permission."
                default: false
            sac.killaura:
                description: "A permission."
                default: false
            sac.regen:
                description: "A permission."
                default: false
            sac.forcegamemode:
                description: "A permission."
                default: false

#######################################################
# USE THIS AT YOUR OWN RISK. I GUARANTEE FOR NOTHING! #
#######################################################
This will teach you how to use AntiForceGM.

Step 1: Enabling in config:

        Set "ForceGameMode" to "true"

WITH ENABLING THIS YOU AGREE THAT THERE IS NO "NO FALSE POSITIVE GUARANTEE" FOR THIS!

Step 3: Now Restart your server.

Step 4: You are now protected against ForceGM!

Have Fun with this Tool!#######################################################
# USE THIS AT YOUR OWN RISK. I GUARANTEE FOR NOTHING! #
#######################################################
This will teach you how to use AntiForceOP.
Step 1: Give all your OPs the permission "legitop"

Step 2: Enabling in config:

        Set "ForceOP" to "true"

Step 3: Now Restart your server.

Step 4: You are now protected against ForceOP! Dont forget to give the permission "legitop" to your new OPs BEFORE OPing them!

Have fun with this tool!---
#||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||#
#ShadowAntiCheat main configuration file.
#Current version: 3.3.10
#What should be blocked?
#||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||#
#true = ENABLE
#false = DISABLE
#block = block Hack
#kick = Player kick
#||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||#
   Plugin-Version: 3.3.10
   Config-Version: 3.6.4
#Enables i am waching you message

   I-AM-WATCHING-YOU: false

#Which color should be used in SAC?
#Supported colors:
# 0 = BLACK
# 1 = DARK_BLUE
# 2 = DARK_GREEN
# 3 = DARK_AQUA
# 4 = DARK_RED
# 5 = DARK_PURPLE
# 6 = GOLD
# 7 = GRAY
# 8 = DARK_GRAY
# 9 = BLUE
# a = GREEN
# b = AQUA
# c = RED
# d = LIGHT_PURPLE
# e = YELLOW
# f = WHITE

   Color: 3

#Enable auto banning after an amount of kicks???
#0 = Disabled
#1 = Instant Ban

   Max-Hacking-Times: 5

#What commands should be executed then?
#Use %PLAYER% for the Players name.

   MaxHackingExceededCommands:

   - "ban %PLAYER%"

#Should SAC show a message when someone gets kicked?
#If yes, which message should be shown?

   KickPlayerMessageBool: true
   KickPlayerMessage: "[SAC] > %PLAYER% was kicked for hacking. I am always watching you."

#Should SAC show a message when someone gets banned?
#If yes, which message should be shown?

   BanPlayerMessageBool: true
   BanPlayerMessage: "[SAC] > %PLAYER% was BANNED for hacking. I am always watching you."


#Enable verbose output???
#Sends a mesage to all OPs if someone is hacking.

   Verbose: true

#-------------------Fly-------------------#
#Do you want to enable Fly check?
#This wont have any problems with falling or jumping,
#even if the player has JumpBoost 2.
#This has beem re-written in 3.1.0 and
#takes care of NoDownlGlide.

   Fly: true

#How many moves may a player be flying before detection?
#15 Should work fine. (Standard)
#NEVER set this lower than 10
#Increase if Fly causes false poitives.

   Fly-Threshold: 15

#Wich Punishment should take place if Fly is detected?

   Fly-Punishment: kick

#Wich Message Sould be shown if Fly is detected?

   Fly-Message: "[SAC] > Fly is not allowed!"

#Which Log Message Sould be shown if Fly is detected?

   Fly-LogMessage: "[SAC] > %PLAYER% is hacking Fly!"

#----------------KillAura-----------------#
#BETA, REPORT BUGS!
#Do you want to enable KillAura check?
#This also catches AimAura and KillAuraLegit.
#This will block anything that gives the player
#a sanificant gamplay advantage, not useless bots.
#TP-Aura is handled by Speed. This is purely behavoir
#based, it does not spawn any fake entities.

   KillAura: true

#Angle (3rd-person-pvp prevention)
#This will block hitting with impossible angles.
#(blocks hitting behind yourself)
#This is pretty basic and will not cause false
#positives or false alerts.
#This also communicates with Heuristics. (Even when disabled.)
#Do you want to enable Angle?

   Angle: true

#Advanced KillAura Heuristics And Detection
#This is an advanced algorithm to catch a lot more
#combat hacks. This is generally aggersive, but
#you can make it even more aggressive or rather
#permissive, but i recommend leaving it as it is.
#How strong should Heuristics be configured?
#
# 0 = DISABLE
# 1 = PERMISSIVE
# 2 = NORMAL
# 3 = AGGRESSIVE
#
# PREMISSIVE will cause close to zero false positives
# even on large servers, but detection is slow and
# bypasses are more likely.
#
# NORMAL will catch most KillAuras, with a very little
# chanche of both false positives and negatives.
# I would recommand this setting because it is the most
# efficent and has the most usability.
#
# AGGRESSIVE will catch nearly every KillAura very fast,
# even some triggerbots are detected. This configuration is
# very agressive and may cause false positives depending
# on the situation. If you set "KillAura-Threshold" to
# 45 it can be very effective. USE AT YOUR OWN RISK!
#
#I reccomand to use PERMISSIVE if you have a big server.
#For other servers, use NORMAL for a normal server , only
#use AGGRESSIVE if you have a smaller server or massive
#problems with KillAura hackers.

   Heuristics: 2

#DeepHeuristics (Triggerbot/Advanced Aura Detection)
#This catches combat hacks that actually aim 
#on a Player. Disabeling this could make the check
#pretty easy to bypass.
#Advanced behavorial analysis of a players combat.
#This is a component of Heuristics you can configure.
#This will NOT work if Heuristics are disabled!
#
# 0 = DISABLE
# 1 = PERMISSIVE
# 2 = NORMAL
# 3 = AGGRESSIVE
#
# PERMISSIVE and NORMAL will catch some Macros and helps the KillAura
# Check to perform better. Generally NORMAL catches more hacks whilst
# PERMISSIVE gives almost zero False Alarms, it is still your choice.
# Works best with Heuristics set to PREMISSIVE or NORMAL.
#
# AGGRESSIVE is pretty paranoid and will catch nearly every combat hack in existance,
# but should be used with high thresholds. False Alarms much more likely.
# Works best with Heuristics set to AGGRESSIVE. USE AT YOUR OWN RISK!
#
#I would recommend to use AGGRESSIVE if you have many hackers,
#for any other case use NORMAL.
#Only use PERMISSIVE if you get False Detections with NORMAL.

   DeepHeuristics: 2

#AimbotCatcher
#This is and old mechanisim to catch aimaura and aimbots in
#general. It is known for causing a lot of false alerts
#on windows 10 edition. it will work on MCPE but
#it is a risk to activate it. I would NOT recommend to
#enable this. USE AT YOUR OWN RISK!

   AimbotCatcher: false

#How many times must a player hack before detection?
#35 Should work fine. (Standard)
#NEVER set this lower than 25
#Optimal settings:
#For PREMISSIVE Heuristics: 25-30
#For   NORMAL   Heuristics: 35-40
#For AGGRESSIVE Heuristics: 45-50
#ALWAYS SET TO AT LEAST 40 IF USING AGGRESSIVE Heuristics!
#Increase if KillAura causes false poitives.

   KillAura-Threshold: 40

#Wich Message Sould be shown if KillAura is detected?

   KillAura-Message: "[SAC] > KillAura is not allowed!"
   KillAura-HEUR-Message: "[SAC] > KillAura is not allowed!"

#Which Log Message Sould be shown if KillAura is detected?

   KillAura-LogMessage: "[SAC] > %PLAYER% is is suspected for KillAura (VL = %AURAVL%, Type = Angle)!"
   KillAura-HEUR-LogMessage: "[SAC] > %PLAYER% is is suspected for KillAura (VL = %HEURVL%, Type = Heuristic)!"

#------------------Regen------------------#
#Do you want to enable Regen check?
#Stops INSANE regeneration.

   Regen: true

#Wich Punishment should take place if Regen is detected?

   Regen-Punishment: kick

#Wich Message Sould be shown if Regen is detected?

   Regen-Message: "[SAC] > Regen is not allowed!"

#Which Log Message Sould be shown if Regen is detected?

   Regen-LogMessage: "[SAC] > %PLAYER% is hacking Regen!"

#-----------------FastBow-----------------#
#Do you want to enable FastBow check?
#Prevent players from shooting arrows too fast.
#Works Well as of version 3.3.4.

   FastBow: true

#Wich Punishment should take place if FastBow is detected?

   FastBow-Punishment: kick

#Wich Message Sould be shown if FastBow is detected?

   FastBow-Message: "[SAC] > FastBow is not allowed!"

#Which Log Message Sould be shown if FastBow is detected?

   FastBow-LogMessage: "[SAC] > %PLAYER% is hacking FastBow!"

#-----------------Speed-------------------#
#Do you want to enable Speed check?
#This works with falling.
#This also catches most TP-Aura hacks.

   Speed: true

#Wich Punishment should take place?
#WARNING: setting this to block will
#EXTREMLY lower the combat protection!

   Speed-Punishment: kick

#How many times must a player be hacking before detection?
#5 Should work fine. (Standard)
#NEVER set this to 0

   Speed-Threshold: 5

#Wich Message Sould be shown if Speed is detected?

   Speed-Message: "[SAC] > Speed is not allowed!"

#Which Log Message Sould be shown if Speed is detected?

   Speed-LogMessage: "[SAC] > %PLAYER% is hacking Speed!"

#-----------------Reach-------------------#
#Do you want to enable Reach check?

   Reach: true

#How much Range may a player have to be legit?
#This wont affect killaura detection, it just blocks the hits.
#Maxrange will be the maximum range.
#Players are not kicked for that due to
#False Positives.
#It simply mitigates an exploit in the game.
#Standard: 4.0
#NEVER set this to lower than 1

   MaxRange:  4.0
   
#-----------------Glide-------------------#
#Do you want to enable Glide check?
#NoDownlGlide is handled by Fly.

   Glide: true

#Wich Punishment should take place if Glide is detected?

   Glide-Punishment: kick

#Wich Message Sould be shown if Glide is detected?

   Glide-Message: "[SAC] > Glide is not allowed!"

#Which Log Message Sould be shown if Glide is detected?

   Glide-LogMessage: "[SAC] > %PLAYER% is hacking Glide!"

#----------------ForceOP------------------#
#######################################################
# USE THIS AT YOUR OWN RISK. I GUARANTEE FOR NOTHING! #
# THIS IS OBSOLETE AS OF NOW. I CANT GUARANTEE IT     #
# CAN HELP YOU ANYTHING!                              #
#######################################################
#Do you want to enable advanced ForceOP check?
#READ AntiForceOP.txt BEFORE ENABLING!

   ForceOP: false

#Which Permission should ForceOP check?
#THIS NEEDS TO BE AN EXISTING PERMISSION
#WICH IS NOT DEFAULTLY GIVEN TO OPS!

   ForceOP-Permission: "legitop"

#-----------------NoClip------------------#
#Do you want to enable NoClip Detection?
#This prevents players form glitching through blocks.
#If this caueses bugs, please report it to the Author!
#Lag with stairs has been highly decreased with the update
#3.0.2. IT STILL MAY CAUSE GLICHES!

   NoClip: true

#Which Punishment should take place?
#I do NOT reccomand to kick players for NoClip due to glitches!

   NoClip-Punishment: kick

#How many times must a player be hacking before kick?
#i reccomand setting this to above 50 because of pocketmines
#bugs and glitches.
#NEVER set this to 0

   NoClip-Threshold: 50

#Which Message Should be shown if NoClip is detected?

   NoClip-Message: "[SAC] > NoClip is not allowed!"

#Which Log Message Sould be shown if NoClip is detected?

   NoClip-LogMessage: "[SAC] > %PLAYER% is suspected for NoClip (VL = %NOCLIPVL%)!"

#--------------ForceGameMode--------------#
#######################################################
# USE THIS AT YOUR OWN RISK. I GUARANTEE FOR NOTHING! #
# THIS IS OBSOLETE AS OF NOW. I CANT GUARANTEE IT     #
# CAN HELP YOU ANYTHING!                              #
#######################################################
#Do you want to enable ForceGameMode Detection?
#USE THIS WITH CAUTION!
#This WILL BLOCK ANY PLAYER THAT IS CHANGEING ITS GAMEMODE
#AND IS NOT OP ALSO IF DONE VIA ADMIN/PLUGIN!
#I WONT GUARANTEE FOR ANY FALSE POSITIVES WITH THAT.
#READ AntiForceGM.txt BEFORE ENABLING!

   FoceGameMode: false

#Which Message Should be shown if ForceGameMode is detected?

   ForceGameMode-Message: "[SAC] > ForceGameMode is not allowed!"

#Which Log Message Sould be shown if ForceGameMode is detected?

   ForceGameMode-LogMessage: "[SAC] > %PLAYER% is hacking ForceGameMode!"

#//////////////////////////////////////////////////////
#//                                                  //
#//     SAC by DarkWav.                              //
#//     Distributed under the GGPL License.          //
#//                                                  //
#//////////////////////////////////////////////////////
...
<?php

namespace DarkWav\SAC;

use pocketmine\plugin\PluginBase;
use pocketmine\command\ConsoleCommandSender;
use pocketmine\utils\TextFormat;
use pocketmine\event\Listener;
use pocketmine\utils\Config;
use pocketmine\permission\Permission;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\player\PlayerInteractEvent;
use pocketmine\event\entity\EntityRegainHealthEvent;
use pocketmine\event\entity\EntityTeleportEvent;
use pocketmine\event\entity\EntityShootBowEvent;
use pocketmine\event\entity\Effect;
use pocketmine\event\player\PlayerEvent;
use pocketmine\event\player\PlayerMoveEvent;
use pocketmine\event\player\PlayerJoinEvent;
use pocketmine\event\player\PlayerQuitEvent;
use pocketmine\event\player\PlayerRespawnEvent;
use pocketmine\event\player\PlayerDeathEvent;
use pocketmine\event\player\PlayerAnimationEvent;


use pocketmine\math\Vector3;
use pocketmine\event\player\PlayerGameModeChangeEvent;
use DarkWav\SAC\SAC;
use DarkWav\SAC\Observer;
use pocketmine\event\Cancellable;
use pocketmine\Player;

class EventListener implements Listener
{
  public $Main;
  public $Logger;
  public $Server;

  public function __construct(SAC $Main)
  {
    $this->Main   = $Main;
    $this->Logger = $Main->getServer()->getLogger();
    $this->Server = $Main->getServer();
  }

  public function onJoin(PlayerJoinEvent $event)
  {
    $player   = $event->getPlayer();
    $hash     = spl_object_hash($player);
    $name     = $player->getName();

    $oldhash  = null;
    $observer = null;
    
    foreach ($this->Main->PlayerObservers as $key=>$obs)
    {
      if ($obs->PlayerName == $name)
      {
        $oldhash  = $key;
        $observer = $obs;
        $observer->Player = $player;
      }
    }

    if ($oldhash != null)
    {
      unset($this->Main->PlayerObservers[$oldhash]);
      $this->Main->PlayerObservers[$hash] = $observer;
      $this->Main->PlayerObservers[$hash]->PlayerRejoin();
    }  
    else
    {
      $observer = new Observer($player, $this->Main);
      $this->Main->PlayerObservers[$hash] = $observer;
      $this->Main->PlayerObservers[$hash]->PlayerJoin();      
    }
  }
  
  public function onQuit(PlayerQuitEvent $event)
  {
    $player   = $event->getPlayer();
    $hash     = spl_object_hash($player);

    if (!empty($player) and !empty($hash) and array_key_exists($hash , $this->Main->PlayerObservers))
    {
      $observer = $this->Main->PlayerObservers[$hash];
      if (!empty($observer))
      {
        $observer->PlayerQuit();
      }   
      $this->Main->PlayerObservers[$hash]->Player = null;
    }
  }


  public function onMove(PlayerMoveEvent $event)
  {
    $player   = $event->getPlayer();
    $hash     = spl_object_hash($player);

    if (array_key_exists($hash , $this->Main->PlayerObservers))
    {    
      $this->Main->PlayerObservers[$hash]->OnMove($event);
      /*
      //THIS IS IN-DEV AND NOT USEABLE
      $this->Main->PlayerObservers[$hash]->getRealKnockBack($event);
      */
    }  
  }

  public function onPlayerAnimationEvent(PlayerAnimationEvent $event)
  {
    $player   = $event->getPlayer();
    $hash     = spl_object_hash($player);
    
    if($event->getAnimationType() == 1)
    {
      if (array_key_exists($hash , $this->Main->PlayerObservers))
      {    
        $this->Main->PlayerObservers[$hash]->OnSwing($event);
      }  
    }
  }

  public function onEntityRegainHealthEvent(EntityRegainHealthEvent $event)
  {
    if ($event->getRegainReason() != EntityDamageEvent::CAUSE_MAGIC and $event->getRegainReason() != EntityDamageEvent::CAUSE_CUSTOM)
    {
      $hash = spl_object_hash($event->getEntity());
      if (array_key_exists($hash , $this->Main->PlayerObservers))
      {
        $this->Main->PlayerObservers[$hash]->PlayerRegainHealth($event);
      }   
    }
  }

  public function onPlayerGameModeChangeEvent(PlayerGameModeChangeEvent $event)
  {
    $hash = spl_object_hash($event->getPlayer());
    if (array_key_exists($hash , $this->Main->PlayerObservers))
    {
      $this->Main->PlayerObservers[$hash]->OnPlayerGameModeChangeEvent($event);
    }  
  }

  public function onDamage(EntityDamageEvent $event)
  {
    $evname = $event->getEventName();
    if ($event instanceof EntityDamageByEntityEvent)
    {
      $ThisEntity = $event->getEntity();
      if($ThisEntity instanceof Player)
      {
        $hash = spl_object_hash($ThisEntity);
        if (array_key_exists($hash , $this->Main->PlayerObservers))
        {
          $this->Main->PlayerObservers[$hash]->PlayerWasDamaged($event);
        }
      }
      
      $ThisDamager = $event->getDamager();
      if($ThisDamager instanceof Player)
      {
        if ($event->getCause() == EntityDamageEvent::CAUSE_ENTITY_ATTACK)
        {
          $hash = spl_object_hash($ThisDamager);
          if (array_key_exists($hash , $this->Main->PlayerObservers))
          {
            $this->Main->PlayerObservers[$hash]->PlayerHasDamaged($event);
          }
        }
      }
    }
  }

  public function onEntityShootBowEvent(EntityShootBowEvent $event)
  {
    $ThisEntity = $event->getEntity();
    if($ThisEntity instanceof Player)
    {
      $hash = spl_object_hash($ThisEntity);
      if (array_key_exists($hash , $this->Main->PlayerObservers))
      {
        $this->Main->PlayerObservers[$hash]->PlayerShotArrow($event);
      }
    }  
  }
  
  public function onPlayerDeathEvent(PlayerDeathEvent $event)
  {
    $player   = $event->getPlayer();
    $hash     = spl_object_hash($player);

    if (array_key_exists($hash , $this->Main->PlayerObservers))
    {    
      $this->Main->PlayerObservers[$hash]->onDeath($event);
    }      
  }

  public function onPlayerRespawnEvent(PlayerRespawnEvent $event)
  {
    $player   = $event->getPlayer();
    $hash     = spl_object_hash($player);

    if (array_key_exists($hash , $this->Main->PlayerObservers))
    {    
      $this->Main->PlayerObservers[$hash]->onRespawn($event);
    }      
  }  
  
  public function onEntityTeleportEvent(EntityTeleportEvent $event)
  {
    $hash = spl_object_hash($event->getEntity());
    if (array_key_exists($hash , $this->Main->PlayerObservers))
    {
      $this->Main->PlayerObservers[$hash]->onTeleport($event);
    }   
  }
  
}

//////////////////////////////////////////////////////
//                                                  //
//     SAC by DarkWav.                              //
//     Distributed under the GGPL License.          //
//                                                  //
//////////////////////////////////////////////////////<?php
namespace DarkWav\SAC;

use pocketmine\scheduler\PluginTask;
use pocketmine\utils\TextFormat;
use pocketmine\command\ConsoleCommandSender;
use DarkWav\SAC\Observer;

class KickTask extends PluginTask
{

  public function __construct($plugin)
  {
    parent::__construct($plugin);
    $this->plugin = $plugin;
  }

  public function onRun(int $currentTick)
  {
    $cl = $this->plugin->getConfig()->get("Color");
    foreach($this->plugin->PlayersToKick as $key=>$obs)
    {
      $obs->PlayerBanCounter++;
      if ($obs->PlayerBanCounter > 0 and $obs->PlayerBanCounter == $this->plugin->getConfig()->get("Max-Hacking-Times"))
      {
        foreach($this->plugin->getConfig()->get("MaxHackingExceededCommands") as $command)
        {
          $send = $obs->ScanMessage($command);
          $this->plugin->getServer()->dispatchCommand(new ConsoleCommandSender(), $send);
          if($this->plugin->getConfig()->get("BanPlayerMessageBool"))
          {
            $bmsg = $this->plugin->getConfig()->get("BanPlayerMessage");
            $sbmsg = $obs->ScanMessage($bmsg);
            $this->plugin->getServer()->broadcastMessage(TextFormat::ESCAPE."$cl" . $sbmsg);
          }
        }
        $obs->PlayerBanCounter = 0;
      }
      if ($obs->Player != null && $obs->Player->isOnline())
      {
        $obs->Player->kick(TextFormat::ESCAPE."$cl" . $obs->KickMessage);
        if($this->plugin->getConfig()->get("KickPlayerMessageBool"))
        {
          $msg = $this->plugin->getConfig()->get("KickPlayerMessage");
          $smsg = $obs->ScanMessage($msg);
          $this->plugin->getServer()->broadcastMessage(TextFormat::ESCAPE."$cl" . $smsg);
        }
      }   
      unset ($this->plugin->PlayersToKick[$key]);
    }  
  }
  
}

//////////////////////////////////////////////////////
//                                                  //
//     SAC by DarkWav.                              //
//     Distributed under the GGPL License.          //
//                                                  //
//////////////////////////////////////////////////////<?php

namespace DarkWav\SAC;

use pocketmine\utils\TextFormat;
use pocketmine\math\Vector3;
use pocketmine\Player;
use pocketmine\block\BlockIds;
use pocketmine\block\Block;
use DarkWav\SAC\EventListener;
use pocketmine\entity\Effect;

class Observer
{
  public $Player;
  public $surroundings;

  public function __construct($player, SAC $SAC)
  {
    $this->Player                  = $player;
    $this->PlayerName              = $this->Player->getName();
    $this->Main                    = $SAC;
    $this->ClientID                = $player->getClientId();
    $this->Logger                  = $SAC->getServer()->getLogger();
    $this->Server                  = $SAC->getServer();
    $this->JoinCounter             = 0;
    $this->KickMessage             = "";

    $this->PlayerAirCounter        = 0;
    $this->PlayerSpeedCounter      = 0;
    $this->PlayerGlideCounter      = 0;
    $this->PlayerNoClipCounter     = 0;
    $this->PlayerReachCounter      = 0;
    $this->PlayerReachFirstTick    = -1;
    $this->PlayerHitFirstTick      = -1;
    $this->PlayerShootFirstTick      = -1;
    $this->PlayerHitCounter        = 0;
    $this->PlayerShootCounter        = 0;
    $this->PlayerKillAuraCounter   = 0;
    $this->PlayerKillAuraV2Counter = 0;
    $this->SpeedAMP                = 0;
    $this->LastAngle               = 0;
    
    //DO NOT RESET!
    $this->PlayerBanCounter    = 0;
    //^^^^^^^^^^^^^

    $this->prev_tick        = -1.0;
    $this->prev_health_tick = -1.0;
    
    $this->x_arr_size   = 7;
    $this->x_arr_idx    = 0;
    $this->x_time_array = array_fill(0, $this->x_arr_size, 0.0);
    $this->x_dist_array = array_fill(0, $this->x_arr_size, 0.0);
    $this->x_time_sum   = 0.0;
    $this->x_distance   = 0.0;
    $this->x_dist_sum   = 0.0;
    $this->x_speed      = 0.0;
    
    $this->y_arr_size   = 10;
    $this->y_arr_idx    = 0;
    $this->y_time_array = array_fill(0, $this->y_arr_size, 0.0);
    $this->y_dist_array = array_fill(0, $this->y_arr_size, 0.0);
    $this->y_time_sum   = 0.0;
    $this->y_distance   = 0.0;
    $this->y_dist_sum   = 0.0;
    $this->y_speed      = 0.0;
    
    $this->hs_arr_size   = 5;
    $this->hs_arr_idx    = 0;
    $this->hs_time_array = array_fill(0, $this->hs_arr_size, 0.5);
    $this->hs_time_sum   = 0.5 * (double)$this->hs_arr_size;
    $this->hs_hit_time   = 0.5;  
    
    $this->x_pos_old    = new Vector3(0.0, 0.0, 0.0);
    $this->x_pos_new    = new Vector3(0.0, 0.0, 0.0);
    $this->y_pos_old    = 0.0;
    $this->y_pos_new    = 0.0;
   
    $this->heal_counter = 0;
    $this->heal_time    = 0;    
    
    $this->surroundings     = array();
    $this->clipsurroundings = array();
   
    $this->LastDamageTick = 0;
    $this->LastIceTick    = 0;
    $this->LastMoveTick   = 0;
    $this->Colorized      = $this->GetConfigEntry("Color");
    
    if     ($this->GetConfigEntry("Heuristics") == 1)
    {
      $this->dist_thr1 = 3.75;
      $this->dist_thr2 = 3.5;
    }
    elseif ($this->GetConfigEntry("Heuristics") == 2)
    {
      $this->dist_thr1 = 3.625;
      $this->dist_thr2 = 3.25;
    }
    elseif ($this->GetConfigEntry("Heuristics") == 3)
    {
      $this->dist_thr1 = 3.5;
      $this->dist_thr2 = 3.0;
    }
    else
    {
      $this->dist_thr1 = 0.00;
      $this->dist_thr2 = 0.00;
    }
    if     ($this->GetConfigEntry("DeepHeuristics") == 1)
    {
      $this->dist_thr3 = 4.0;
      $this->accuracy_thr1 = 3.0;
    }
    if     ($this->GetConfigEntry("DeepHeuristics") == 2)
    {
      $this->dist_thr3 = 3.875;
      $this->accuracy_thr1 = 4.0;
    }
    elseif ($this->GetConfigEntry("DeepHeuristics") == 3)
    {
      $this->dist_thr3 = 3.75;
      $this->accuracy_thr1 = 5.0;
    }
    else
    {
      $this->dist_thr3 = 0.000;
      $this->accuracy_thr1 = 0.000;
    }
  }  
  
  public function ResetObserver()
  {
    $this->PlayerReachCounter      =  0;
    $this->PlayerReachFirstTick    = -1;
    $this->PlayerHitFirstTick      = -1;
    $this->PlayerHitCounter        =  0;
    $this->PlayerKillAuraCounter   =  0;
    $this->PlayerKillAuraV2Counter =  0;

    $this->ResetMovement();
  }

  
  public function ResetMovement()
  {
    $this->PlayerAirCounter      = 0;
    $this->PlayerSpeedCounter    = 0;
    $this->PlayerGlideCounter    = 0;
    $this->PlayerNoClipCounter   = 0;
    $this->LastMoveTick          = 0;

    $this->prev_tick     = -1.0;
    
    $this->x_arr_size   = 7;
    $this->x_arr_idx    = 0;
    $this->x_time_array = array_fill(0, $this->x_arr_size, 0.0);
    $this->x_dist_array = array_fill(0, $this->x_arr_size, 0.0);
    $this->x_time_sum   = 0.0;
    $this->x_distance   = 0.0;
    $this->x_dist_sum   = 0.0;
    $this->x_speed      = 0.0;
    
    $this->y_arr_size   = 10;
    $this->y_arr_idx    = 0;
    $this->y_time_array = array_fill(0, $this->y_arr_size, 0.0);
    $this->y_dist_array = array_fill(0, $this->y_arr_size, 0.0);
    $this->y_time_sum   = 0.0;
    $this->y_distance   = 0.0;
    $this->y_dist_sum   = 0.0;
    $this->y_speed      = 0.0;

    $this->x_pos_old    = new Vector3(0.0, 0.0, 0.0);
    $this->x_pos_new    = new Vector3(0.0, 0.0, 0.0);    
    $this->y_pos_old    = 0.0;
    $this->y_pos_new    = 0.0;     
    
    $this->hs_arr_size   = 5;
    $this->hs_arr_idx    = 0;
    $this->hs_time_array = array_fill(0, $this->hs_arr_size, 0.5);
    $this->hs_time_sum   = 0.5 * (double)$this->hs_arr_size;
    $this->hs_hit_time   = 0.5;  
  }

  public function SACIsOnGround($pp)
  {
    if     ( $this->AllBlocksAir()      ) return false;
    else                                  return $this->Player->IsOnGround();
  }

  public function ScanMessage($message)
  {
    $pos     = strpos(strtoupper($message), "%PLAYER%");
    $newmsg  = $message;
    $newmsg2 = $message;
    $newmsg3 = $message;
    $newmsg4 = $message;
    
    if ($pos !== false)
    {
      $newmsg = substr_replace($message, $this->PlayerName, $pos, 8);
    }
    $pos2    = strpos(strtoupper($newmsg), "%AURAVL%");
    if ($pos2 !== false)
    {
      $newmsg2 = substr_replace($newmsg, $this->PlayerKillAuraCounter, $pos2, 8);
    }    
    else
    {
      $newmsg2 = $newmsg;
    }
    $pos3    = strpos(strtoupper($newmsg2), "%HEURVL%");
    if ($pos3 !== false)
    {
      $newmsg3 = substr_replace($newmsg2, $this->PlayerKillAuraV2Counter, $pos3, 8);
    }    
    else
    {
      $newmsg3 = $newmsg2;
    }
    $pos4    = strpos(strtoupper($newmsg3), "%NOCLIPVL%");
    if ($pos4 !== false)
    {
      $newmsg4 = substr_replace($newmsg3, $this->PlayerNoClipCounter, $pos4, 10);
    }    
    else
    {
      $newmsg4 = $newmsg3;
    }
    return $newmsg4;
  }

  public function GetConfigEntry($cfgkey)
  {
    $msg = $this->Main->getConfig()->get($cfgkey);
    return $this->ScanMessage($msg);    
  }

  public function KickPlayer($reason)
  {
    if (!in_array($this, $this->Main->PlayersToKick))
    {
      // Add current Observer to the array of Observers whose players shall be kicked ASAP
      $this->KickMessage = $reason;
      $this->Main->PlayersToKick[] = $this;
    }
  }

  public function NotifyAdmins($message)
  {
    if($this->GetConfigEntry("Verbose"))
    {
      $newmsg = $this->ScanMessage($message);
    
      foreach ($this->Main->PlayerObservers as $observer)
      {
        $player = $observer->Player;
        if ($player != null and $this->Player->hasPermission("sac.admin"))
        {
          $player->sendMessage(TextFormat::ESCAPE."$this->Colorized" . $newmsg);
        }
      }
    }  
  }
  
  public function PlayerQuit()
  {
    if ($this->GetConfigEntry("I-AM-WATCHING-YOU"))
    {
      $this->Logger->debug(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > $this->PlayerName is no longer watched...");
    }
  }

  public function PlayerJoin()
  {
    $this->JoinCounter++;
    if ($this->GetConfigEntry("I-AM-WATCHING-YOU"))
    {
      $this->Player->sendMessage(TextFormat::ESCAPE."$this->Colorized"."[SAC] > $this->PlayerName, I am watching you ...");
    }
  }
  
  public function PlayerRejoin()
  {
    $this->JoinCounter++;
    if ($this->GetConfigEntry("I-AM-WATCHING-YOU"))
    {
      $this->Player->sendMessage(TextFormat::ESCAPE."$this->Colorized"."[SAC] > $this->PlayerName, I am still watching you ...");
      $this->Logger->debug      (TextFormat::ESCAPE."$this->Colorized"."[SAC] > $this->PlayerName joined this server $this->JoinCounter times since server start");
    }
  }

  public function AllBlocksAir()
  {
    $level       = $this->Player->getLevel();
    $posX        = $this->Player->getX();
    $posY        = $this->Player->getY();
    $posZ        = $this->Player->getZ();    

    for ($xidx = $posX-1; $xidx <= $posX+1; $xidx = $xidx + 1)
    {
      for ($zidx = $posZ-1; $zidx <= $posZ+1; $zidx = $zidx + 1)
      {
        for ($yidx = $posY-1; $yidx <= $posY; $yidx = $yidx + 1)
        {
          $pos   = new Vector3($xidx, $yidx, $zidx);
          $block = $level->getBlock($pos)->getId();
          if ($block != Block::AIR)
          {
            return false;
          }   
        }
      }
    }
    return true;
  }

  public function PlayerRegainHealth($event)
  {
    if($this->GetConfigEntry("Regen"))
    {
      if ($this->Player->hasPermission("sac.regen")) return;
      $Reason2 = $event->getRegainReason();
      $tick    = (double)$this->Server->getTick(); 
      $tps     = (double)$this->Server->getTicksPerSecond();

      if ($Reason2 != 2)  // Ignore CAUSE_MAGIC
      {
        $heal_amount = $event->getAmount();    
        if ($heal_amount > 3)
        {
          if ($this->GetConfigEntry("Regen-Punishment") == "kick")
          {
            $event->setCancelled(true);
            $this->ResetObserver();
            $message = $this->GetConfigEntry("Regen-LogMessage");
            $reason  = $this->GetConfigEntry("Regen-Message");
            $this->NotifyAdmins($message);
            $this->KickPlayer($reason);
            return;
          }
          if ($this->GetConfigEntry("Regen-Punishment") == "block")
          {
            $event->setCancelled(true);
            $message = $this->GetConfigEntry("Regen-LogMessage");
            $this->NotifyAdmins($message);
          }
        }
        $tick    = (double)$this->Server->getTick(); 
        $tps     = (double)$this->Server->getTicksPerSecond();
        if ($tps > 0.0 and $this->prev_health_tick != -1.0)
        {
          $tick_count  = (double)($tick - $this->prev_health_tick);  // server ticks since last health regain
          $delta_t     = (double)($tick_count) / (double)$tps;       // seconds since last health regain    
          if ($delta_t < 10)
          {
            $this->heal_counter = $this->heal_counter + $heal_amount;
            $this->heal_time = $this->heal_time + $delta_t;
            if ($this->heal_counter >= 5)
            {
              $heal_rate = (double)$this->heal_counter / (double)$this->heal_time;
              if ($heal_rate > 0.5)
              {
                if ($this->GetConfigEntry("Regen-Punishment") == "kick")
                {
                  $event->setCancelled(true);
                  $this->ResetObserver();
                  $message = $this->GetConfigEntry("Regen-LogMessage");
                  $reason  = $this->GetConfigEntry("Regen-Message");
                  $this->NotifyAdmins($message);
                  $this->KickPlayer($reason);
                  return;
                }
                if ($this->GetConfigEntry("Regen-Punishment") == "block")
                {
                  $event->setCancelled(true);
                  $message = $this->GetConfigEntry("Regen-LogMessage");
                  $this->NotifyAdmins($message);
                }
              }
              $this->heal_counter = 0;
              $this->heal_time    = 0;
            }
          }  
        }
        $this->prev_health_tick = $tick;
      }
    }
  }

  # -------------------------------------------------------------------------------------
  # OnMove: Player has made a move
  # -------------------------------------------------------------------------------------
  public function OnMove($event)
  {
    $this->LastMoveTick = (double)$this->Server->getTick();
    $this->CheckForceOP($event);
    if ($this->Player->getGameMode() == 1 or $this->Player->getGameMode() == 3) return;
    
    $this->GetSurroundingBlocks();
    $this->CheckSpeedFlyGlide($event);
    $this->CheckNoClip($event);
  }

  # -------------------------------------------------------------------------------------
  # CheckForceOP: Check if the player is a legit OP
  # -------------------------------------------------------------------------------------
  public function CheckForceOP($event)
  {
    if ($this->GetConfigEntry("ForceOP"))
    {
      if ($this->Player->isOp())
      {
        if (!$this->Player->hasPermission($this->GetConfigEntry("ForceOP-Permission")))
        {
          $event->setCancelled(true);
          $message = "[SAC] > %PLAYER% used ForceOP!";
          $reason = "[SAC] > ForceOP detected!";
          $this->NotifyAdmins($message);
          $this->KickPlayer($reason);
        }
      }
    }
  }

  public function GetSurroundingBlocks()
  {
    $level       = $this->Player->getLevel();

    $posX        = $this->Player->getX();
    $posY        = $this->Player->getY();
    $posZ        = $this->Player->getZ();    

    $pos1        = new Vector3($posX  , $posY, $posZ  );
    $pos2        = new Vector3($posX-1, $posY, $posZ  );
    $pos3        = new Vector3($posX-1, $posY, $posZ-1);
    $pos4        = new Vector3($posX  , $posY, $posZ-1);
    $pos5        = new Vector3($posX+1, $posY, $posZ  );
    $pos6        = new Vector3($posX+1, $posY, $posZ+1);
    $pos7        = new Vector3($posX  , $posY, $posZ+1);
    $pos8        = new Vector3($posX+1, $posY, $posZ-1);
    $pos9        = new Vector3($posX-1, $posY, $posZ+1);
    $pos10       = new Vector3($posX  , $posY-1, $posZ  );
    $pos11       = new Vector3($posX-1, $posY-1, $posZ  );
    $pos12       = new Vector3($posX-1, $posY-1, $posZ-1);
    $pos13       = new Vector3($posX  , $posY-1, $posZ-1);
    $pos14       = new Vector3($posX+1, $posY-1, $posZ  );
    $pos15       = new Vector3($posX+1, $posY-1, $posZ+1);
    $pos16       = new Vector3($posX  , $posY-1, $posZ+1);
    $pos17       = new Vector3($posX+1, $posY-1, $posZ-1);
    $pos18       = new Vector3($posX-1, $posY-1, $posZ+1);
    $pos19       = new Vector3($posX  , $posY+1, $posZ  );
    $pos20       = new Vector3($posX-1, $posY+1, $posZ  );
    $pos21       = new Vector3($posX-1, $posY+1, $posZ-1);
    $pos22       = new Vector3($posX  , $posY+1, $posZ-1);
    $pos23       = new Vector3($posX+1, $posY+1, $posZ  );
    $pos24       = new Vector3($posX+1, $posY+1, $posZ+1);
    $pos25       = new Vector3($posX  , $posY+1, $posZ+1);
    $pos26       = new Vector3($posX+1, $posY+1, $posZ-1);
    $pos27       = new Vector3($posX-1, $posY+1, $posZ+1);
    $pos28       = new Vector3($posX  , $posY+2, $posZ  );
    $pos29       = new Vector3($posX-1, $posY+2, $posZ  );
    $pos30       = new Vector3($posX-1, $posY+2, $posZ-1);
    $pos31       = new Vector3($posX  , $posY+2, $posZ-1);
    $pos32       = new Vector3($posX+1, $posY+2, $posZ  );
    $pos33       = new Vector3($posX+1, $posY+2, $posZ+1);
    $pos34       = new Vector3($posX  , $posY+2, $posZ+1);
    $pos35       = new Vector3($posX+1, $posY+2, $posZ-1);
    $pos36       = new Vector3($posX-1, $posY+2, $posZ+1);
    
    $bpos1       = $level->getBlock($pos1)->getId();
    $bpos2       = $level->getBlock($pos2)->getId();
    $bpos3       = $level->getBlock($pos3)->getId();
    $bpos4       = $level->getBlock($pos4)->getId();
    $bpos5       = $level->getBlock($pos5)->getId();
    $bpos6       = $level->getBlock($pos6)->getId();
    $bpos7       = $level->getBlock($pos7)->getId();
    $bpos8       = $level->getBlock($pos8)->getId();
    $bpos9       = $level->getBlock($pos9)->getId();
    $bpos10       = $level->getBlock($pos10)->getId();
    $bpos11       = $level->getBlock($pos11)->getId();
    $bpos12       = $level->getBlock($pos12)->getId();
    $bpos13       = $level->getBlock($pos13)->getId();
    $bpos14       = $level->getBlock($pos14)->getId();
    $bpos15       = $level->getBlock($pos15)->getId();
    $bpos16       = $level->getBlock($pos16)->getId();
    $bpos17       = $level->getBlock($pos17)->getId();
    $bpos18       = $level->getBlock($pos18)->getId();
    $bpos19       = $level->getBlock($pos19)->getId();
    $bpos20       = $level->getBlock($pos20)->getId();
    $bpos21       = $level->getBlock($pos21)->getId();
    $bpos22       = $level->getBlock($pos22)->getId();
    $bpos23       = $level->getBlock($pos23)->getId();
    $bpos24       = $level->getBlock($pos24)->getId();
    $bpos25       = $level->getBlock($pos25)->getId();
    $bpos26       = $level->getBlock($pos26)->getId();
    $bpos27       = $level->getBlock($pos27)->getId();
    $bpos28       = $level->getBlock($pos28)->getId();
    $bpos29       = $level->getBlock($pos29)->getId();
    $bpos30       = $level->getBlock($pos30)->getId();
    $bpos31       = $level->getBlock($pos31)->getId();
    $bpos32       = $level->getBlock($pos32)->getId();
    $bpos33       = $level->getBlock($pos33)->getId();
    $bpos34       = $level->getBlock($pos34)->getId();
    $bpos35       = $level->getBlock($pos35)->getId();
    $bpos36       = $level->getBlock($pos36)->getId();
    
    $this->surroundings = array ($bpos1, $bpos2, $bpos3, $bpos4, $bpos5, $bpos6, $bpos7, $bpos8, $bpos9);
    $this->clipsurroundings = array ($bpos1, $bpos2, $bpos3, $bpos4, $bpos5, $bpos6, $bpos7, $bpos8, $bpos9, $bpos10, $bpos11, $bpos12, $bpos13, $bpos14, $bpos15, $bpos16, $bpos17, $bpos18, $bpos19, $bpos20, $bpos21, $bpos22, $bpos23, $bpos24, $bpos25, $bpos26, $bpos27, $bpos28, $bpos29, $bpos30, $bpos31, $bpos32, $bpos33, $bpos34, $bpos35, $bpos36);    
  }
  
  # -------------------------------------------------------------------------------------
  # CheckSpeedFlyGlide: Check if player is flying, gliding or moving too fast
  # -------------------------------------------------------------------------------------
  public function CheckSpeedFlyGlide($event)
  {
    if ($this->Player->hasPermission("sac.fly")) return;
    if ($this->Player->getAllowFlight()) return;
    if ($this->GetConfigEntry("Speed") or $this->GetConfigEntry("Fly") or $this->GetConfigEntry("Glide"))
    {
      #Anti Speed, Fly and Glide
      $this->x_pos_old  = new Vector3($event->getFrom()->getX(), 0.0, $event->getFrom()->getZ());
      $this->x_pos_new  = new Vector3($event->getTo()->getX()  , 0.0, $event->getTo()->getZ()  );
      $this->x_distance = $this->x_pos_old->distance($this->x_pos_new);

      $this->y_pos_old  = $event->getFrom()->getY();
      $this->y_pos_new  = $event->getTo()->getY();  
      $this->y_distance = $this->y_pos_old - $this->y_pos_new;

      $tick = (double)$this->Server->getTick(); 
      $tps  = (double)$this->Server->getTicksPerSecond();

      if ($tps > 0.0 and $this->prev_tick != -1.0)
      {
        $tick_count = (double)($tick - $this->prev_tick);     // server ticks since last move 
        $delta_t    = (double)($tick_count) / (double)$tps;   // seconds since last move

        if ($delta_t < 2.0)  // "OnMove" message lag is less than 2 second to calculate a new moving speed
        {    
          $this->x_time_sum = $this->x_time_sum - $this->x_time_array[$this->x_arr_idx] + $delta_t;             // ringbuffer time     sum  (remove oldest, add new)
          $this->x_dist_sum = $this->x_dist_sum - $this->x_dist_array[$this->x_arr_idx] + $this->x_distance;    // ringbuffer distance sum  (remove oldest, add new) 
          $this->x_time_array[$this->x_arr_idx] = $delta_t;                                                     // overwrite oldest delta_t  with the new one
          $this->x_dist_array[$this->x_arr_idx] = $this->x_distance;                                            // overwrite oldest distance with the new one          
          $this->x_arr_idx++;                                                                                   // Update ringbuffer position
          if ($this->x_arr_idx >= $this->x_arr_size) $this->x_arr_idx = 0;          
          
          $this->y_time_sum = $this->y_time_sum - $this->y_time_array[$this->y_arr_idx] + $delta_t;             // ringbuffer time     sum  (remove oldest, add new)
          $this->y_dist_sum = $this->y_dist_sum - $this->y_dist_array[$this->y_arr_idx] + $this->y_distance;    // ringbuffer distance sum  (remove oldest, add new) 
          $this->y_time_array[$this->y_arr_idx] = $delta_t;                                                      // overwrite oldest delta_t  with the new one
          $this->y_dist_array[$this->y_arr_idx] = $this->y_distance;                                             // overwrite oldest distance with the new one          
          $this->y_arr_idx++;                                                                                    // Update ringbuffer position
          if ($this->y_arr_idx >= $this->y_arr_size) $this->y_arr_idx = 0;
        }

        // calculate speed: distance per time      
        if ($this->x_time_sum > 0) $this->x_speed = (double)$this->x_dist_sum / (double)$this->x_time_sum;
        else                       $this->x_speed = 0.0;
        
        // calculate speed: distance per time      
        if ($this->y_time_sum > 0) $this->y_speed = (double)$this->y_dist_sum / (double)$this->y_time_sum;
        else                       $this->y_speed = 0.0;
     
        if ($this->GetConfigEntry("Speed"))
        {
          if (!$this->Player->hasPermission("sac.speed"))
          {
            if(    !in_array(Block::ICE               , $this->clipsurroundings ) 
               and !in_array(Block::FROSTED_ICE       , $this->clipsurroundings )
               and !in_array(Block::PACKED_ICE        , $this->clipsurroundings )
               and !in_array(Block::RAIL              , $this->clipsurroundings )
               and !in_array(Block::POWERED_RAIL      , $this->clipsurroundings )
               and !in_array(Block::DETECTOR_RAIL     , $this->clipsurroundings )
               and !in_array(Block::ACTIVATOR_RAIL    , $this->clipsurroundings ))
            {
            # $this->Logger->info(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > $this->PlayerName failed InArray!"); 
            # Anti Speed
                if ($this->Player->hasEffect(Effect::SPEED))
                {
                  $this->SpeedAMP = $this->Player->getEffect(Effect::SPEED)->getAmplifier();
                  if ($this->SpeedAMP < 3) # Speed 1 and 2
                  {
                    if ($this->x_speed > 10)
                    {
                      if (($tick - $this->LastDamageTick) > 30 and ($tick - $this->LastIceTick) > 60 )  # deactivate 1.5 seconds after receiving damage and 3.0 seconds after being near ice.
                      {
                        $this->PlayerSpeedCounter += 10;
                      }   
                    }
                    else
                    {
                      if ($this->PlayerSpeedCounter > 0)
                      { 
                        $this->PlayerSpeedCounter--;
                      }
                    }
                  }
                  elseif ($this->SpeedAMP == 3 or $this->SpeedAMP == 4) # Speed 3 and 4
                  {
                    if ($this->x_speed > 11.5)
                    {
                      if (($tick - $this->LastDamageTick) > 30 and ($tick - $this->LastIceTick) > 60 )  # deactivate 1.5 seconds after receiving damage and 3.0 seconds after being near ice.
                      {
                        $this->PlayerSpeedCounter += 10;
                      }   
                    }
                    else
                    {
                      if ($this->PlayerSpeedCounter > 0)
                      { 
                        $this->PlayerSpeedCounter--;
                      }
                    }
                  }
                  elseif ($this->SpeedAMP > 4) #Speed 5 and higher
                  {
                     $this->PlayerSpeedCounter++; # do nothing
                     $this->PlayerSpeedCounter--; # do nothing
                  }
                  else
                  {
                    if ($this->PlayerSpeedCounter > 0)
                    { 
                      $this->PlayerSpeedCounter--;
                    }
                  }
                }
                elseif ($this->x_speed > 8.325)
                {
                  if (($tick - $this->LastDamageTick) > 30 and ($tick - $this->LastIceTick) > 60 )  # deactivate 1.5 seconds after receiving damage and 3.0 seconds after being near ice.
                  {
                    $this->PlayerSpeedCounter += 10;
                  }
                }
                else
                {
                  if ($this->PlayerSpeedCounter > 0)
                  { 
                    $this->PlayerSpeedCounter--;
                  }
                }
            }
            else
            {
                $this->LastIceTick = $this->Server->getTick();
            }
          }
          if ($this->PlayerSpeedCounter > $this->GetConfigEntry("Speed-Threshold") * 10)
          {
            if ($this->GetConfigEntry("Speed-Punishment") == "kick")
            {
              $event->setCancelled(true);
              $this->ResetObserver();
              $message = $this->GetConfigEntry("Speed-LogMessage");
              $reason  = $this->GetConfigEntry("Speed-Message");
              $this->NotifyAdmins($message);
              $this->KickPlayer($reason);
            }
            if ($this->GetConfigEntry("Speed-Punishment") == "block")
            {
              $event->setCancelled(true);
              $message = $this->GetConfigEntry("Speed-LogMessage");
              $this->NotifyAdmins($message);
              $this->PlayerSpeedCounter = ($this->GetConfigEntry("Speed-Threshold") * 10) - 10;
            }
          }  
        }
      }
      $this->prev_tick = $tick;
    }

    # No Fly, No Glide and Anti Speed
    if (!$this->SACIsOnGround($this->Player))
    {
      if(    !in_array(Block::WATER               , $this->clipsurroundings ) 
         and !in_array(Block::FLOWING_WATER       , $this->clipsurroundings )
         and !in_array(Block::STILL_WATER         , $this->clipsurroundings )
         and !in_array(Block::LAVA                , $this->clipsurroundings )
         and !in_array(Block::FLOWING_LAVA        , $this->clipsurroundings )
         and !in_array(Block::STILL_LAVA          , $this->clipsurroundings )
         and !in_array(Block::LADDER              , $this->clipsurroundings )
         and !in_array(Block::VINE                , $this->clipsurroundings )
         and !in_array(Block::COBWEB              , $this->clipsurroundings ))
      {
        if ($this->y_pos_old > $this->y_pos_new)
        {
          # Player moves down. Check Glide Hack
          if ($this->GetConfigEntry("Glide"))
          {
            if (!$this->Player->hasPermission("sac.glide"))
            {
              $this->PlayerGlideCounter++;
            }
          }
        }
        elseif ($this->y_pos_old <= $this->y_pos_new)
        {
          # Player moves up or horizontal
          if ($this->GetConfigEntry("Fly"))
          {
            $this->PlayerAirCounter++;
            if ($this->PlayerGlideCounter > 0)
            {
              $this->PlayerGlideCounter--;
            }
          }
        }
      }
    }
    else
    {
      $this->PlayerAirCounter   = 0;
      $this->PlayerGlideCounter = 0;
    }
    
    if ($this->PlayerGlideCounter > 25 and $this->y_speed < 20)
    {
      if ($this->GetConfigEntry("Glide-Punishment") == "kick")
      {
        $event->setCancelled(true);
        $this->ResetObserver();
        $message = $this->GetConfigEntry("Glide-LogMessage");
        $reason  = $this->GetConfigEntry("Glide-Message");
        $this->NotifyAdmins($message);
        $this->KickPlayer($reason);
      }
      if ($this->GetConfigEntry("Glide-Punishment") == "block")
      {
        $event->setCancelled(true);
        $message = $this->GetConfigEntry("Glide-LogMessage");
        $this->NotifyAdmins($message);
      }
    }
    
    if ($this->PlayerAirCounter > $this->GetConfigEntry("Fly-Threshold"))
    {
      if ($this->GetConfigEntry("Fly-Punishment") == "kick")
      {
        $event->setCancelled(true);
        $this->ResetObserver();
        $message = $this->GetConfigEntry("Fly-LogMessage");
        $reason  = $this->GetConfigEntry("Fly-Message");
        $this->NotifyAdmins($message);
        $this->KickPlayer($reason);
      }
      if ($this->GetConfigEntry("Fly-Punishment") == "block")
      {
        $event->setCancelled(true);
        $message = $this->GetConfigEntry("Fly-LogMessage");
        $this->NotifyAdmins($message);
      }
    }
  }

  public function CheckNoClip($event)
  {
    # No Clip
    if ($this->GetConfigEntry("NoClip"))
    {
      if ($this->Player->hasPermission("sac.noclip")) return;
      $level    = $this->Player->getLevel();
      $pos      = new Vector3($this->Player->getX(), $this->Player->getY(), $this->Player->getZ());
      $BlockID  = $level->getBlock($pos)->getId();
      $pos2     = new Vector3($this->Player->getX(), $this->Player->getY()+1, $this->Player->getZ());
      $BlockID2 = $level->getBlock($pos2)->getId();

      //ANTI-FALSE-POSITIVES
      if ((

      //BUILDING MATERIAL

         $BlockID == 1
      or $BlockID == 2
      or $BlockID == 3
      or $BlockID == 4
      or $BlockID == 5
      or $BlockID == 7
      or $BlockID == 17
      or $BlockID == 18
      or $BlockID == 20
      or $BlockID == 43
      or $BlockID == 45
      or $BlockID == 47
      or $BlockID == 48
      or $BlockID == 49
      or $BlockID == 79
      or $BlockID == 80
      or $BlockID == 87
      or $BlockID == 89
      or $BlockID == 97
      or $BlockID == 98
      or $BlockID == 110
      or $BlockID == 112
      or $BlockID == 121
      or $BlockID == 155
      or $BlockID == 157
      or $BlockID == 159
      or $BlockID == 161
      or $BlockID == 162
      or $BlockID == 170
      or $BlockID == 172
      or $BlockID == 174
      or $BlockID == 243

      //ORES (for Prison mines)

      or $BlockID == 14  //GOLD     (-)
      or $BlockID == 15  //IRON     (-)
      or $BlockID == 16  //COAL     (-)
      or $BlockID == 21  //LAPIS    (-)
      or $BlockID == 56  //DIAMOND  (-)
      or $BlockID == 73  //REDSTONE (DARK)
      or $BlockID == 73  //REDSTONE (GLOWING)
      or $BlockID == 129 //EMERALD  (-)
      )
      and
      (

      //BUILDING MATERIAL

         $BlockID2 == 1
      or $BlockID2 == 2
      or $BlockID2 == 3
      or $BlockID2 == 4
      or $BlockID2 == 5
      or $BlockID2 == 7
      or $BlockID2 == 17
      or $BlockID2 == 18
      or $BlockID2 == 20
      or $BlockID2 == 43
      or $BlockID2 == 45
      or $BlockID2 == 47
      or $BlockID2 == 48
      or $BlockID2 == 49
      or $BlockID2 == 79
      or $BlockID2 == 80
      or $BlockID2 == 87
      or $BlockID2 == 89
      or $BlockID2 == 97
      or $BlockID2 == 98
      or $BlockID2 == 110
      or $BlockID2 == 112
      or $BlockID2 == 121
      or $BlockID2 == 155
      or $BlockID2 == 157
      or $BlockID2 == 159
      or $BlockID2 == 161
      or $BlockID2 == 162
      or $BlockID2 == 170
      or $BlockID2 == 172
      or $BlockID2 == 174
      or $BlockID2 == 243

      //ORES (for Prison mines)

      or $BlockID2 == 14  //GOLD     (-)
      or $BlockID2 == 15  //IRON     (-)
      or $BlockID2 == 16  //COAL     (-)
      or $BlockID2 == 21  //LAPIS    (-)
      or $BlockID2 == 56  //DIAMOND  (-)
      or $BlockID2 == 73  //REDSTONE (DARK)
      or $BlockID2 == 73  //REDSTONE (GLOWING)
      or $BlockID2 == 129 //EMERALD  (-)
      ))
      {
        #$this->Logger->info(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > Not_In_Array, VL: $this->PlayerNoClipCounter");        
        if ($this->GetConfigEntry("NoClip-Punishment") == "kick")
        {
          $this->PlayerNoClipCounter += 10;
          $event->setCancelled(true);
          if ($this->PlayerNoClipCounter > $this->GetConfigEntry("NoClip-Threshold") * 10)
          {
            $reason = $this->GetConfigEntry("NoClip-Message");
            $this->ResetObserver();
            $this->KickPlayer($reason);
          }
          if ($this->PlayerNoClipCounter > $this->GetConfigEntry("NoClip-Threshold") * 5)
          {
            $message = $this->GetConfigEntry("NoClip-LogMessage");
            $this->NotifyAdmins($message);
          }
        }
        if ($this->GetConfigEntry("NoClip-Punishment") == "block")
        {
            $event->setCancelled(true);
            $message = $this->GetConfigEntry("NoClip-LogMessage");
            $this->NotifyAdmins($message);
        }
      }
      else
      {
        if($this->PlayerNoClipCounter > 0)
        {
          $this->PlayerNoClipCounter--;
        }
      }
    }    
  }  
  
  public function OnPlayerGameModeChangeEvent($event)
  {
    if ($this->GetConfigEntry("ForceGameMode"))
    {
      if ($this->Player->hasPermission("sac.forcegamemode")) return;
      if(!$event->getPlayer()->isOp())
      {
        $message = $this->GetConfigEntry("ForceGameMode-LogMessage");
        $this->NotifyAdmins($message);
        $reason  = $this->GetConfigEntry("ForceGameMode-Message");
        $this->KickPlayer($reason);
        $event->$event->setCancelled(true);
      }
      else
      {
        return;
      }
    }
    else
    {
      return;
    }
  }
  
  public function OnSwing($event)
  {
    #$this->Logger->info(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > Player Swing Event: $this->PlayerName!");
  }
  
  public function PlayerHasDamaged($event)
  {
    $damaged_entity             = $event->getEntity();
    $is_damaged_entity_a_player = $damaged_entity instanceof Player;
    $damaged_entity_position    = new Vector3($damaged_entity->getX(), $damaged_entity->getY(), $damaged_entity->getZ());
    $damaged_xz_entity_position = new Vector3($damaged_entity->getX(), 0                      , $damaged_entity->getZ());
    
    $damager                    = $this->Player;    
    $damager_position           = new Vector3($damager->getX()       , $damager->getY()       , $damager->getZ()       );
    $damager_xz_position        = new Vector3($damager->getX()       , 0                      , $damager->getZ()       );
    
    $damager_direction          = $damager->getDirectionVector();
    $damager_direction          = $damager_direction->normalize();
    
    $damager_xz_direction       = $damager->getDirectionVector();
    $damager_xz_direction->y    = 0;
    $damager_xz_direction       = $damager_xz_direction->normalize();
    
    $entity_xz_direction        = $damaged_xz_entity_position->subtract($damager_xz_position)->normalize();
    $entity_direction           = $damaged_entity_position->subtract($damager_position)->normalize();

    $distance_xz                = $damager_xz_position->distance($damaged_xz_entity_position); 
    $distance                   = $damager_position->distance($damaged_entity_position); 
    
    $dot_product_xz = $damager_xz_direction->dot($entity_xz_direction);
    $angle_xz       = rad2deg(acos($dot_product_xz));
    
    $dot_product    = $damager_direction->dot($entity_direction);
    $angle          = rad2deg(acos($dot_product));
    
    $tick_count = (double)$this->Server->getTick() - $this->LastMoveTick; 
    $tps        = (double)$this->Server->getTicksPerSecond();
    if ($tps != 0) $delta_t    = (double)($tick_count) / (double)$tps;
    else           $delta_t    = 0; 
    
    $orient = ($damager_xz_direction->x * $entity_xz_direction->z) - ($damager_xz_direction->z * $entity_xz_direction->x);
    if ($orient > 0)
    {
      $trueangle = $angle_xz;
    }
    else
    {
      $trueangle = -$angle_xz;
    } 
    $aimconsistency = abs($trueangle - $this->LastAngle);
    
    #$this->Logger->debug(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > Kill Aura Counter: $this->PlayerKillAuraCounter     V2: $this->PlayerKillAuraV2Counter  Speed: $this->x_speed");
    #$this->Logger->info(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > $this->PlayerName : consistency = $aimconsistency, VL= $this->PlayerKillAuraV2Counter");
    #$this->Logger->info(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > $this->PlayerName : Trueangle = $trueangle");
    if ($this->Player->getGameMode() == 1 or $this->Player->getGameMode() == 3) return;
    // Kill Aura
    if ($this->GetConfigEntry("KillAura"))
    {
      if (!$this->Player->hasPermission("sac.killaura"))
      {
        if ($is_damaged_entity_a_player)
        {
          $tick = (double)$this->Server->getTick(); 
          $tps  = (double)$this->Server->getTicksPerSecond();
          if ($this->PlayerHitFirstTick == -1)
          {
            $this->PlayerHitFirstTick = $tick;
          }        

          $tick_count = (double)($tick - $this->PlayerHitFirstTick);   // server ticks since last hit
          $delta_t    = (double)($tick_count) / (double)$tps;          // seconds since last hit

          $this->hs_time_sum = $this->hs_time_sum - $this->hs_time_array[$this->hs_arr_idx] + $delta_t;      // ringbuffer time sum  (remove oldest, add new)
          $this->hs_time_array[$this->hs_arr_idx] = $delta_t;                                                // overwrite oldest delta_t  with the new one
          $this->hs_arr_idx++;                                                                               // Update ringbuffer position
          if ($this->hs_arr_idx >= $this->hs_arr_size) $this->hs_arr_idx = 0;          
          $this->hs_hit_time = $this->hs_time_sum / $this->hs_arr_size;
          #$this->Logger->info(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > THD $this->PlayerName : hittime = $this->hs_hit_time");
        
          if ($this->hs_hit_time < 0.1)
          {
            $this->PlayerHitCounter += 2;
          }
          else
          {
            if($this->PlayerHitCounter > 0)
            {
              $this->PlayerHitCounter--;
            }
          }
          //Allow a maximum of 7 Unlegit hits, couter derceases x2 slower
          if($this->PlayerHitCounter > 15)
          {
            $event->setCancelled(true);
            $this->ResetObserver();
            $message = $this->GetConfigEntry("KillAura-LogMessage");
            $reason  = $this->GetConfigEntry("KillAura-Message");
            $this->NotifyAdmins($message);
            $this->KickPlayer($reason);
            return;
          }
          $this->PlayerHitFirstTick = $tick;
          if ($distance_xz >= 0.5)
          {
            # V2
            if ($this->dist_thr1 != 0.00)
            {
              if (($distance >= $this->dist_thr1) and 
                  ($delta_t  <  0.5             ) and
                  ($angle_xz >  22.5            ) and
                  (
                    (($this->x_speed > 1.5) and ($this->hs_hit_time < 0.625)) or ($this->x_speed > 4.625)
                  ))
              {
                $this->PlayerKillAuraV2Counter+=3;
              }
              elseif (($distance >= $this->dist_thr2) and 
                      ($delta_t  <  0.5             ) and
                      ($angle_xz >  45.0            ) and
                      (
                        (($this->x_speed > 1.5) and ($this->hs_hit_time < 0.625)) or ($this->x_speed > 4.125)
                      ))
              {
                $this->PlayerKillAuraV2Counter+=6;
              }
              elseif (($distance >= $this->dist_thr3) and 
                      ($delta_t  <  0.5             ) and
                      (
                       (($this->x_speed > 1.5) and ($this->hs_hit_time < 0.625)) or ($this->x_speed > 4.625)
                      ))
              {
                if ($this->dist_thr3 != 0.000)
                {
                  $this->PlayerKillAuraV2Counter+=3;
                }
              }
              elseif (($aimconsistency <= $this->accuracy_thr1) and
                      ($aimconsistency >= 0.625               ) and
                      ($delta_t  <  0.5                       ) and
                      (
                       ($this->x_speed > 4.0)
                      ))
              {
                if ($this->dist_thr3 != 0.000)
                {
                  $this->PlayerKillAuraV2Counter+=2;
                }
              }
              else
              {
                if ($this->PlayerKillAuraV2Counter > 0)
                {
                  $this->PlayerKillAuraV2Counter--;
                }
              }
            }
            
            if ($angle_xz > 45)
            {
              if ($this->GetConfigEntry("Angle"))
              {
                $event->setCancelled(true);
              }
              if ($angle_xz > 90)
              {
                if ($this->dist_thr1 != 0.00)
                {
                  $this->PlayerKillAuraV2Counter+=6;
                }
                if ($this->GetConfigEntry("Angle"))
                {
                  $this->PlayerKillAuraCounter+=9;
                }
              }
            }            
            if ($this->GetConfigEntry("AimbotCatcher"))
            {
              #$this->Logger->debug(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > counter V2: $this->PlayerKillAuraV2Counter");
              # V1
              if (($angle_xz < 1.0) and ($angle < 20) and ($aimconsistency > 0.625) and ($delta_t < 0.5) and ($this->x_speed > 6.0))
              {
                $this->PlayerKillAuraCounter+=2;
                $this->PlayerKillAuraV2Counter+=2;
              }
              if (($angle_xz >= 1.0) or ($angle >= 20) or ($delta_t > 2.0) or ($aimconsistency <= 0.625))
              {
                if ($this->PlayerKillAuraCounter > 0)
                {
                  if (!$angle_xz > 90)
                  {
                    $this->PlayerKillAuraCounter--;
                  }
                }   
              }      
              #$this->Logger->info(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > counter V1: $this->PlayerKillAuraCounter  V2: $this->PlayerKillAuraV2Counter distance: $distance_xz  deltat: $delta_t  speedx: $this->x_speed anglexz: $angle_xz");
            }
            #$this->Logger->info(TextFormat::ESCAPE."$this->Colorized" . "AAA[SAC] > counter V1: $this->PlayerKillAuraCounter  V2: $this->PlayerKillAuraV2Counter distance: $distance_xz  deltat: $delta_t  speedx: $this->x_speed anglexz: $angle_xz");
          }  
      
          if (($this->PlayerKillAuraCounter >= $this->GetConfigEntry("KillAura-Threshold")))
          {
            $event->setCancelled(true);
            $reason = $this->GetConfigEntry("KillAura-Message");
            $this->ResetObserver();
            $this->KickPlayer($reason);
          }        
          if (($this->PlayerKillAuraCounter > $this->GetConfigEntry("KillAura-Threshold") * 0.25))
          {
            $message = $this->GetConfigEntry("KillAura-LogMessage");
            $this->NotifyAdmins($message);
          }            
          if (($this->PlayerKillAuraV2Counter >= $this->GetConfigEntry("KillAura-Threshold")))
          {
            $event->setCancelled(true);
            $reason = $this->GetConfigEntry("KillAura-HEUR-Message");
            $this->ResetObserver();
            $this->KickPlayer($reason);
          }
          if (($this->PlayerKillAuraV2Counter > $this->GetConfigEntry("KillAura-Threshold") * 0.25))
          {
            $message = $this->GetConfigEntry("KillAura-HEUR-LogMessage");
            $this->NotifyAdmins($message);
          }                
        }
      }
    }

    //Reach Check
    if ($this->GetConfigEntry("Reach"))
    {
      if (!$this->Player->hasPermission("sac.reach"))
      {
        $reach_distance = $damager_position->distance($damaged_entity_position); 
        #$this->Logger->debug(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > Reach distance $this->PlayerName : $reach_distance");
      
        if ($reach_distance > $this->GetConfigEntry("MaxRange"))
        {
          $event->setCancelled(true);
        }
      }
      /*
      if ($reach_distance > $this->GetConfigEntry("KickRange"))
      {
        $this->PlayerReachCounter++;
        #$this->Logger->debug(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > $this->PlayerName  ReachCounter: $this->PlayerReachCounter");
        $tick = (double)$this->Server->getTick(); 
        $tps  = (double)$this->Server->getTicksPerSecond();
        
        if ($this->PlayerReachFirstTick == -1)
        {
          $this->PlayerReachFirstTick = $tick;
        }
        if ($this->PlayerReachCounter > 4 and $tps > 0)
        {
          $tick_count = (double)($tick - $this->PlayerReachFirstTick); // server ticks since last reach hack
          $delta_t    = (double)($tick_count) / (double)$tps;          // seconds since first reach hack
          
          if ($delta_t < 60)
          {
            if ($this->GetConfigEntry("Reach-Punishment") == "kick")
            {
              $event->setCancelled(true);
              $this->ResetObserver();
              $message = $this->GetConfigEntry("Reach-LogMessage");
              $reason  = $this->GetConfigEntry("Reach-Message");
              $this->NotifyAdmins($message);
              $this->KickPlayer($reason);
              return;
            }
            if ($this->GetConfigEntry("Reach-Punishment") == "block")
            {
              $event->setCancelled(true);
              $message = $this->GetConfigEntry("Reach-LogMessage");
              $this->NotifyAdmins($message);
            }
          }
          else
          {
            $this->PlayerReachFirstTick = $tick;
            $this->PlayerReachCounter   = 0;
          }
        }
      }
      */
    }
    $this->LastAngle = $trueangle;
  }


  public function PlayerWasDamaged($event)
  {
    if ($event->getDamage() >= 1)
    {
      $this->LastDamageTick = $this->Server->getTick();  // remember time of last damage
      $this->PlayerSpeedCounter -= 8;
      $this->PlayerAirCounter -= 2;
      $this->PlayerGlideCounter -= 2;
    }
  }
  

  public function PlayerShotArrow($event)
  { 
    $damager                    = $this->Player;    
    $damager_position           = new Vector3($damager->getX()       , $damager->getY()       , $damager->getZ()       );
    $damager_xz_position        = new Vector3($damager->getX()       , 0                      , $damager->getZ()       );
    $tick_count = (double)$this->Server->getTick() - $this->LastMoveTick; 
    $tps        = (double)$this->Server->getTicksPerSecond();
    if ($tps != 0) $delta_t    = (double)($tick_count) / (double)$tps;
    else           $delta_t    = 0; 
    
    if ($this->Player->getGameMode() == 1 or $this->Player->getGameMode() == 3) return;
    if ($this->GetConfigEntry("FastBow"))
    {
      $tick = (double)$this->Server->getTick(); 
      $tps  = (double)$this->Server->getTicksPerSecond();
      if ($this->PlayerShootFirstTick == -1)
      {
        $this->PlayerShootFirstTick = $tick;
      }        
      $tick_count = (double)($tick - $this->PlayerShootFirstTick);   // server ticks since last hit
      $delta_t    = (double)($tick_count) / (double)$tps;          // seconds since last hit

      $this->hs_time_sum = $this->hs_time_sum - $this->hs_time_array[$this->hs_arr_idx] + $delta_t;      // ringbuffer time sum  (remove oldest, add new)
      $this->hs_time_array[$this->hs_arr_idx] = $delta_t;                                                // overwrite oldest delta_t  with the new one
      $this->hs_arr_idx++;                                                                               // Update ringbuffer position
      if ($this->hs_arr_idx >= $this->hs_arr_size) $this->hs_arr_idx = 0;          
      $this->hs_hit_time = $this->hs_time_sum / $this->hs_arr_size;
      #$this->Logger->info(TextFormat::ESCAPE."$this->Colorized" . "[SAC] > $this->PlayerName : Shottime = $this->hs_hit_time");
    
      if ($this->hs_hit_time < 0.65)
      {
        if ($this->GetConfigEntry("FastBow-Punishment") == "kick")
        {
          $this->PlayerShootCounter += 3;
        }
        $event->setCancelled(true);
      }
      else
      {
        if($this->PlayerShootCounter > 0)
        {
          $this->PlayerShootCounter--;
        }
      }
      //Allow a maximum of 6 Unlegit shots, couter derceases x3 slower
      if ($this->GetConfigEntry("FastBow-Punishment") == "kick")
      {
        if($this->PlayerShootCounter > 20)
        {
          $event->setCancelled(true);
          $this->ResetObserver();
          $message = $this->GetConfigEntry("FastBow-LogMessage");
          $reason  = $this->GetConfigEntry("FastBow-Message");
          $this->NotifyAdmins($message);
          $this->KickPlayer($reason);
          return;
        }
      }
      $this->PlayerShootFirstTick = $tick;
    }
  }


  public function onDeath($event)
  {
    $this->ResetMovement();
    $this->LastDamageTick = $this->Server->getTick();  // remember time of last damage
  }  


  public function onRespawn($event)
  {
    $this->ResetMovement();
    $this->LastDamageTick = $this->Server->getTick();  // remember time of last damage
  }  
  

  public function onTeleport($event)
  {
    $this->ResetMovement();
    $this->LastDamageTick = $this->Server->getTick();  // remember time of last damage
  }
}

//////////////////////////////////////////////////////
//                                                  //
//     SAC by DarkWav.                              //
//     Distributed under the GGPL License.          //
//                                                  //
//////////////////////////////////////////////////////
<?php

namespace DarkWav\SAC;

use pocketmine\plugin\PluginBase;
use pocketmine\utils\TextFormat;
use pocketmine\command\CommandSender;
use pocketmine\command\Command;
use pocketmine\utils\Config;
use pocketmine\event\player\PlayerJoinEvent;
use pocketmine\Player;
use pocketmine\Plugin;
use pocketmine\plugin\PluginLoader;
use DarkWav\SAC\EventListener;
use DarkWav\SAC\Observer;
use DarkWav\SAC\KickTask;

class SAC extends PluginBase
{
  public $Config;
  public $Logger;
  public $cl;
  public $PlayerObservers = array();
  public $PlayersToKick   = array();

  public function onEnable()
  {
    $this->getServer()->getScheduler()->scheduleRepeatingTask(new KickTask($this), 1);
    @mkdir($this->getDataFolder());
    $this->saveDefaultConfig();
    $this->saveResource("AntiForceOP.txt");
    $this->saveResource("AntiForceGM.txt");
    $cl              = $this->getConfig()->get("Color");
  
    $Config = $this->getConfig();
    $Logger = $this->getServer()->getLogger();
    $Server = $this->getServer();
    
    $this->getServer()->getPluginManager()->registerEvents(new EventListener($this), $this);
    $Logger->info(TextFormat::ESCAPE."$cl" . "[SAC] > ShadowAntiCheat Activated"            );
    $Logger->info(TextFormat::ESCAPE."$cl" . "[SAC] > ShadowAntiCheat v3.3.10 [ShadowX]");
    $Logger->info(TextFormat::ESCAPE."$cl" . "[SAC] > Loading Modules");
    if($Config->get("ForceOP"    )) $Logger->info(TextFormat::ESCAPE."$cl"."[SAC] > Enabling AntiForceOP"    );
    if($Config->get("NoClip"     )) $Logger->info(TextFormat::ESCAPE."$cl"."[SAC] > Enabling AntiNoClip"     );
    if($Config->get("Fly"        )) $Logger->info(TextFormat::ESCAPE."$cl"."[SAC] > Enabling AntiFly"        );
    if($Config->get("Fly"        )) $Logger->info(TextFormat::ESCAPE."$cl"."[SAC] > Enabling AntiSpider"     );
    if($Config->get("Glide"      )) $Logger->info(TextFormat::ESCAPE."$cl"."[SAC] > Enabling AntiGlide"      );
    if($Config->get("KillAura"   )) $Logger->info(TextFormat::ESCAPE."$cl"."[SAC] > Enabling AntiKillAura"   );
    if($Config->get("Reach"      )) $Logger->info(TextFormat::ESCAPE."$cl"."[SAC] > Enabling AntiReach"      );
    if($Config->get("Speed"      )) $Logger->info(TextFormat::ESCAPE."$cl"."[SAC] > Enabling AntiSpeed"      );
    if($Config->get("FastBow"    )) $Logger->info(TextFormat::ESCAPE."$cl"."[SAC] > Enabling AntiFastBow"    );
    if($Config->get("Regen"      )) $Logger->info(TextFormat::ESCAPE."$cl"."[SAC] > Enabling AntiRegen"      );

    if($Config->get("Config-Version") !== "3.6.4")
    {
      $Logger->warning(TextFormat::ESCAPE."$cl"."[SAC] > Your Config is out of date!");
    }
    if($Config->get("Plugin-Version") !== "3.3.8" and $Config->get("Plugin-Version") !== "3.3.9" and $Config->get("Plugin-Version") !== "3.3.10")
    {
      $Logger->error(TextFormat::ESCAPE."$cl"."[SAC] > Your Config is incompatible with this plugin version, please update immediately!");
      $Server->shutdown();
    }

    foreach($Server->getOnlinePlayers() as $player)
    {
      $hash     = spl_object_hash($player);
      $name     = $player->getName();
      $oldhash  = null;
      $observer = null;
      
      foreach ($this->PlayerObservers as $key=>$obs)
      {
        if ($obs->PlayerName == $name)
        {
          $oldhash  = $key;
          $observer = $obs;
          $observer->Player = $player;
        }
      }
      if ($oldhash != null)
      {
        unset($this->PlayerObservers[$oldhash]);
        $this->PlayerObservers[$hash] = $observer;
        $this->PlayerObservers[$hash]->PlayerRejoin();
      }  
      else
      {
        $observer = new Observer($player, $this);
        $this->PlayerObservers[$hash] = $observer;
        $this->PlayerObservers[$hash]->PlayerJoin();      
      }      
    }  
  }

  public function onDisable()
  {
    $cl              = $this->getConfig()->get("Color");
    $Logger = $this->getServer()->getLogger();
    $Server = $this->getServer();

    $Logger->info(TextFormat::ESCAPE."$cl"."[SAC] > You are no longer protected from cheats!");
    $Logger->info(TextFormat::ESCAPE."$cl"."[SAC] > ShadowAntiCheat Deactivated");
    $Server->enablePlugin($this);
  }
    
  public function onCommand(CommandSender $sender, Command $command, string $label, array $args) : bool
  {
    $Logger = $this->getServer()->getLogger();
    $cl              = $this->getConfig()->get("Color");
    if ($this->getConfig()->get("ForceOP"))
    {
      if ($sender->isOp())
      {
        if (!$sender->hasPermission($this->getConfig()->get("ForceOP-Permission")))
        {
          if ($sender instanceof Player)
          {
            $sname = $sender->getName();
            $message  = "[SAC] > $sname used ForceOP!";
            $this->NotifyAdmins($message);
            $sender->getPlayer()->kick(TextFormat::ESCAPE."$cl"."[SAC] > ForceOP detected!");
          }
        }
      }
    }
    if ($command->getName() === "sac" or $command->getName() === "shadowanticheat")
    {
      $sender->sendMessage(TextFormat::ESCAPE."$cl"."[SAC] > ShadowAntiCheat v3.3.10 [ShadowX] (~DarkWav/Darku)");
    }
	return false;
  }
  
  public function NotifyAdmins($message)
  {
    $cl              = $this->getConfig()->get("Color");
    if($this->getConfig()->get("Verbose"))
    {
      foreach ($this->PlayerObservers as $observer)
      {
        $player = $observer->Player;
        if ($player != null and $player->hasPermission("sac.admin"))
        {
          $player->sendMessage(TextFormat::ESCAPE."$cl" . $message);
        }
      }
    }  
  }  
  
}

//////////////////////////////////////////////////////
//                                                  //
//     SAC by DarkWav.                              //
//     Distributed under the GGPL License.          //
//                                                  //
//////////////////////////////////////////////////////
lpYכrрw   GBMB