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(