40    public const DEFAULT_FX_INTERVAL_TICKS = 5;
 
   42    private int $fxTicker = 0;
 
   43    private float $breakSpeed;
 
   44    private float $breakProgress = 0;
 
   46    public function __construct(
 
   50        private int $targetedFace,
 
   51        private int $maxPlayerDistance,
 
   52        private int $fxTickInterval = self::DEFAULT_FX_INTERVAL_TICKS
 
   54        $this->breakSpeed = $this->calculateBreakProgressPerTick();
 
   55        if($this->breakSpeed > 0){
 
   56            $this->player->getWorld()->broadcastPacketToViewers(
 
   66    private function calculateBreakProgressPerTick() : 
float{
 
   67        if(!$this->block->getBreakInfo()->isBreakable()){
 
   70        $breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getInventory()->getItemInHand()) * 20;
 
   71        if(!$this->player->isOnGround() && !$this->player->isFlying()){
 
   72            $breakTimePerTick *= 5;
 
   74        if($this->player->isUnderwater() && !$this->player->getArmorInventory()->getHelmet()->hasEnchantment(VanillaEnchantments::AQUA_AFFINITY())){
 
   75            $breakTimePerTick *= 5;
 
   77        if($breakTimePerTick > 0){
 
   78            $progressPerTick = 1 / $breakTimePerTick;
 
   80            $haste = $this->player->getEffects()->get(VanillaEffects::HASTE());
 
   82                $hasteLevel = $haste->getEffectLevel();
 
   83                $progressPerTick *= (1 + 0.2 * $hasteLevel) * (1.2 ** $hasteLevel);
 
   86            $miningFatigue = $this->player->getEffects()->get(VanillaEffects::MINING_FATIGUE());
 
   87            if($miningFatigue !== 
null){
 
   88                $miningFatigueLevel = $miningFatigue->getEffectLevel();
 
   89                $progressPerTick *= 0.21 ** $miningFatigueLevel;
 
   92            return $progressPerTick;
 
   97    public function update() : 
bool{
 
   98        if($this->player->getPosition()->distanceSquared($this->blockPos->add(0.5, 0.5, 0.5)) > $this->maxPlayerDistance ** 2){
 
  102        $newBreakSpeed = $this->calculateBreakProgressPerTick();
 
  103        if(abs($newBreakSpeed - $this->breakSpeed) > 0.0001){
 
  104            $this->breakSpeed = $newBreakSpeed;
 
  105            $this->player->getWorld()->broadcastPacketToViewers(
 
  111        $this->breakProgress += $this->breakSpeed;
 
  113        if(($this->fxTicker++ % $this->fxTickInterval) === 0 && $this->breakProgress < 1){
 
  114            $this->player->getWorld()->addParticle($this->blockPos, 
new BlockPunchParticle($this->block, $this->targetedFace));
 
  115            $this->player->getWorld()->addSound($this->blockPos, 
new BlockPunchSound($this->block));
 
  116            $this->player->broadcastAnimation(
new ArmSwingAnimation($this->player), $this->player->getViewers());
 
  119        return $this->breakProgress < 1;
 
  122    public function getBlockPos() : 
Vector3{
 
  123        return $this->blockPos;
 
  126    public function getTargetedFace() : 
int{
 
  127        return $this->targetedFace;
 
  130    public function setTargetedFace(
int $face) : 
void{
 
  131        Facing::validate($face);
 
  132        $this->targetedFace = $face;
 
  135    public function getBreakSpeed() : 
float{
 
  136        return $this->breakSpeed;
 
  139    public function getBreakProgress() : 
float{
 
  140        return $this->breakProgress;
 
  143    public function __destruct(){
 
  144        if($this->player->getWorld()->isInLoadedTerrain($this->blockPos)){
 
  145            $this->player->getWorld()->broadcastPacketToViewers(