Repository: pmmp/PocketMine-MP (language: php)

Issue:
Anvil does not deal fall damage and does not sound when it hits the ground
### Issue description
If you try to throw an anvil at the head to kill or harm them, it does not do so, nor does the sound of hitting the ground sound

- Expected result: What were you expecting to happen? Hurts the player and makes the sound when it hits the ground
- Actual result: What actually happened? Nothing

### Steps to reproduce the issue
1. Throw an anvil from a height of more than 3 or 4 blocks
2. Nothing happens when it falls, that should not happen

### OS and versions
<!-- try the `version` command | LATEST IS NOT A VALID VERSION -->
* PocketMine-MP: 4.0.0-BETA1 (git hash: 210b9c7b75fe3183600e85f9f4a24ef125fcb1cf)
* PHP: 8.0.9
* Using JIT: not supported
* Server OS: linux
* Game version: Win10

### Plugins
<!--- use the `plugins` command and paste the output below -->
Plugins (2): BuilderTools v1.3.0-beta1, DevTools v1.15.0+dev

- If you remove all plugins, does the issue still occur? Yes
- If the issue is **not** reproducible without plugins:
  - Have you asked for help on our forums before creating an issue?
  - Can you provide sample, *minimal* reproducing code for the issue? If so, paste it in the bottom section

### Crashdump, backtrace or other files

Expected result video: https://streamable.com/l0ohoh
Actual result video: https://streamable.com/n2g9fy



Discussion / hints:
This is not currently implemented in any version of PM.
Renamed issue 

Fix patch:
diff --git a/src/block/Anvil.php b/src/block/Anvil.php
index 17814e2c903..edfb9491deb 100644
--- a/src/block/Anvil.php
+++ b/src/block/Anvil.php
@@ -107,6 +107,14 @@ public function onHitGround(FallingBlock $blockEntity) : bool{
 		return true;
 	}
 
+	public function getFallDamagePerBlock() : float{
+		return 2.0;
+	}
+
+	public function getMaxFallDamage() : float{
+		return 40.0;
+	}
+
 	public function getLandSound() : ?Sound{
 		return new AnvilFallSound();
 	}
diff --git a/src/block/utils/Fallable.php b/src/block/utils/Fallable.php
index 74f96ac8b65..8f0dbc8348d 100644
--- a/src/block/utils/Fallable.php
+++ b/src/block/utils/Fallable.php
@@ -42,6 +42,18 @@ public function tickFalling() : ?Block;
 	 */
 	public function onHitGround(FallingBlock $blockEntity) : bool;
 
+	/**
+	 * Returns the damage caused per fallen block. This is multiplied by the fall distance (and capped according to
+	 * {@link Fallable::getMaxFallDamage()}) to calculate the damage dealt to any entities who intersect with the block
+	 * when it hits the ground.
+	 */
+	public function getFallDamagePerBlock() : float;
+
+	/**
+	 * Returns the maximum damage the block can deal to an entity when it hits the ground.
+	 */
+	public function getMaxFallDamage() : float;
+
 	/**
 	 * Returns the sound that will be played when FallingBlock hits the ground.
 	 */
diff --git a/src/block/utils/FallableTrait.php b/src/block/utils/FallableTrait.php
index d13a7f794fb..f062792efad 100644
--- a/src/block/utils/FallableTrait.php
+++ b/src/block/utils/FallableTrait.php
@@ -64,6 +64,14 @@ public function onHitGround(FallingBlock $blockEntity) : bool{
 		return true;
 	}
 
+	public function getFallDamagePerBlock() : float{
+		return 0.0;
+	}
+
+	public function getMaxFallDamage() : float{
+		return 0.0;
+	}
+
 	public function getLandSound() : ?Sound{
 		return null;
 	}
diff --git a/src/entity/Living.php b/src/entity/Living.php
index b30ab7f87bb..29a8ceae8ff 100644
--- a/src/entity/Living.php
+++ b/src/entity/Living.php
@@ -73,6 +73,7 @@
 use function min;
 use function mt_getrandmax;
 use function mt_rand;
+use function round;
 use function sqrt;
 use const M_PI;
 
@@ -429,6 +430,10 @@ public function applyDamageModifiers(EntityDamageEvent $source) : void{
 		$source->setModifier(-$source->getFinalDamage() * min(ceil(min($totalEpf, 25) * (mt_rand(50, 100) / 100)), 20) * 0.04, EntityDamageEvent::MODIFIER_ARMOR_ENCHANTMENTS);
 
 		$source->setModifier(-min($this->getAbsorption(), $source->getFinalDamage()), EntityDamageEvent::MODIFIER_ABSORPTION);
+
+		if($cause === EntityDamageEvent::CAUSE_FALLING_BLOCK && $this->armorInventory->getHelmet() instanceof Armor){
+			$source->setModifier(-($source->getFinalDamage() / 4), EntityDamageEvent::MODIFIER_ARMOR_HELMET);
+		}
 	}
 
 	/**
@@ -460,6 +465,15 @@ protected function applyPostDamageEffects(EntityDamageEvent $source) : void{
 			if($damage > 0){
 				$attacker->attack(new EntityDamageByEntityEvent($this, $attacker, EntityDamageEvent::CAUSE_MAGIC, $damage));
 			}
+
+			if($source->getModifier(EntityDamageEvent::MODIFIER_ARMOR_HELMET) < 0){
+				$helmet = $this->armorInventory->getHelmet();
+				if($helmet instanceof Armor){
+					$finalDamage = $source->getFinalDamage();
+					$this->damageItem($helmet, (int) round($finalDamage * 4 + lcg_value() * $finalDamage * 2));
+					$this->armorInventory->setHelmet($helmet);
+				}
+			}
 		}
 	}
 
diff --git a/src/entity/object/FallingBlock.php b/src/entity/object/FallingBlock.php
index 301d38b645d..0371ec9501d 100644
--- a/src/entity/object/FallingBlock.php
+++ b/src/entity/object/FallingBlock.php
@@ -30,8 +30,10 @@
 use pocketmine\data\SavedDataLoadingException;
 use pocketmine\entity\Entity;
 use pocketmine\entity\EntitySizeInfo;
+use pocketmine\entity\Living;
 use pocketmine\entity\Location;
 use pocketmine\event\entity\EntityBlockChangeEvent;
+use pocketmine\event\entity\EntityDamageByEntityEvent;
 use pocketmine\event\entity\EntityDamageEvent;
 use pocketmine\math\Vector3;
 use pocketmine\nbt\tag\ByteTag;
@@ -44,6 +46,8 @@
 use pocketmine\world\format\io\GlobalBlockStateHandlers;
 use pocketmine\world\sound\BlockBreakSound;
 use function abs;
+use function min;
+use function round;
 
 class FallingBlock extends Entity{
 	private const TAG_FALLING_BLOCK = "FallingBlock"; //TAG_Compound
@@ -156,8 +160,20 @@ protected function entityBaseTick(int $tickDiff = 1) : bool{
 	}
 
 	protected function onHitGround() : ?float{
-		if($this->block instanceof Fallable && !$this->block->onHitGround($this)){
-			$this->flagForDespawn();
+		if($this->block instanceof Fallable){
+			$damagePerBlock = $this->block->getFallDamagePerBlock();
+			if($damagePerBlock > 0 && ($fallenBlocks = round($this->fallDistance) - 1) > 0){
+				$damage = min($fallenBlocks * $damagePerBlock, $this->block->getMaxFallDamage());
+				foreach($this->getWorld()->getCollidingEntities($this->getBoundingBox()) as $entity){
+					if($entity instanceof Living){
+						$ev = new EntityDamageByEntityEvent($this, $entity, EntityDamageEvent::CAUSE_FALLING_BLOCK, $damage);
+						$entity->attack($ev);
+					}
+				}
+			}
+			if(!$this->block->onHitGround($this)){
+				$this->flagForDespawn();
+			}
 		}
 		return null;
 	}
diff --git a/src/event/entity/EntityDamageEvent.php b/src/event/entity/EntityDamageEvent.php
index edb44f1f713..7c3e2aaa40c 100644
--- a/src/event/entity/EntityDamageEvent.php
+++ b/src/event/entity/EntityDamageEvent.php
@@ -46,6 +46,7 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{
 	public const MODIFIER_TOTEM = 8;
 	public const MODIFIER_WEAPON_ENCHANTMENTS = 9;
 	public const MODIFIER_PREVIOUS_DAMAGE_COOLDOWN = 10;
+	public const MODIFIER_ARMOR_HELMET = 11;
 
 	public const CAUSE_CONTACT = 0;
 	public const CAUSE_ENTITY_ATTACK = 1;
@@ -63,6 +64,7 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{
 	public const CAUSE_MAGIC = 13;
 	public const CAUSE_CUSTOM = 14;
 	public const CAUSE_STARVATION = 15;
+	public const CAUSE_FALLING_BLOCK = 16;
 
 	private float $baseDamage;
 	private float $originalBase;
diff --git a/src/event/player/PlayerDeathEvent.php b/src/event/player/PlayerDeathEvent.php
index dd7c21eb298..4a1632cfc5e 100644
--- a/src/event/player/PlayerDeathEvent.php
+++ b/src/event/player/PlayerDeathEvent.php
@@ -25,6 +25,7 @@
 
 use pocketmine\block\BlockTypeIds;
 use pocketmine\entity\Living;
+use pocketmine\entity\object\FallingBlock;
 use pocketmine\event\entity\EntityDamageByBlockEvent;
 use pocketmine\event\entity\EntityDamageByEntityEvent;
 use pocketmine\event\entity\EntityDamageEvent;
@@ -155,6 +156,19 @@ public static function deriveMessage(string $name, ?EntityDamageEvent $deathCaus
 			case EntityDamageEvent::CAUSE_MAGIC:
 				return KnownTranslationFactory::death_attack_magic($name);
 
+			case EntityDamageEvent::CAUSE_FALLING_BLOCK:
+				if($deathCause instanceof EntityDamageByEntityEvent){
+					$e = $deathCause->getDamager();
+					if($e instanceof FallingBlock){
+						if($e->getBlock()->getTypeId() === BlockTypeIds::ANVIL){
+							return KnownTranslationFactory::death_attack_anvil($name);
+						}else{
+							return KnownTranslationFactory::death_attack_fallingBlock($name);
+						}
+					}
+				}
+				break;
+
 			case EntityDamageEvent::CAUSE_CUSTOM:
 				break;
 


Test patch: