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

Issue:
unable to cancel item activation without cancelling block activation
<!--- tell us what you want -->
## Description
We need some way to fine-tune the actions that result when when right clicking on a block using an item.


<!--- explain why you want this and why it's a good idea -->
## Justification
It's not possible to prevent the use of an item by clicking on a block without also preventing the clicked block reacting to click.

This is problematic in arenas where we need to prevent the use of items like spawn eggs and paintings, but we still want to allow the use of doors and trapdoors.

<!--- (optional) describe alternative methods you've explored to achieve your goal -->
## Alternative methods
Providing the functionality that I mentioned, disabling painting and spawn egg, without disabling doors and trapdoors, can currently only be done by hardcoding a list of blacklisted items or whitelisted blocks.
Cake isn't always eatable in creative
### Issue description

In mojang's default minecraft, we can eat cake anytime in creative, but in pmmp we not.

### Steps to reproduce the issue
1. Eat cake in creative when you are full.

### OS and versions
<!-- try the `version` command | LATEST IS NOT A VALID VERSION -->
* PocketMine-MP: 5.5.0
* PHP: 8.2.10 (cli) (built: Sep  4 2023 17:37:11) (ZTS Visual C++ 2019 x64)
* Using JIT: probably no
* Server OS:win10
* Game version: Win10

### Plugins
DevTools v1.17.1, EasyEdit v3.0.1, TGCapes v2.1.1, server v0.0.1

- If you remove all plugins, does the issue still occur? probably yes

### Crashdump, backtrace or other files
This problem doesn't occur any crash.



Discussion / hints:
Currently, PlayerInteractEvent has two jobs:
- Handling the "clicking" of blocks: placing (or touching / interacting) and breaking.
- Handling the interaction between items and blocks.

Maybe, that should be split into two events, where one would be for the "clicking" and which cancels both block placing and breaking (where we also have an inconsistency with PlayerInteractEvent, how you mentioned on discord (cancelling PlayerInteractEvent cancels block placing, but not block breaking)), while the second would handle the interaction itself, maybe in the way of how Spigot deals with this, so we don't need to create two events for both interactions (would both most likely use the same parameters): 
- `PlayerInteractEvent::cancelBlockInteraction()` to disable block interaction (`Block::onActivate()`)
- `PlayerInteractEvent::cancelItemInteraction()` to disable item interaction (`Item::onActivate()`) 
- `PlayerInteractEvent::cancel()` to disable both.

But, I guess, that would be a bc break since the event behaviour of PlayerInteractEvent would change and I don't know if you would like it that near the release of PM4.
4.1 would be better.
hmh, expected something like that
Just curious, would the approach be suitable, or should that be discussed later on again?


Fix patch:
diff --git a/changelogs/5.23.md b/changelogs/5.23.md
new file mode 100644
index 00000000000..f5b08593d7f
--- /dev/null
+++ b/changelogs/5.23.md
@@ -0,0 +1,107 @@
+# 5.23.0
+Released 5th December 2024.
+
+This is a minor feature release, including new gameplay features, internals improvements, API additions and
+deprecations, and improvements to timings.
+
+**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
+Do not update plugin minimum API versions unless you need new features added in this release.
+
+**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
+Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
+
+## General
+- `/timings` now supports collecting timings from async task workers. These new timings will be shown alongside `Full Server Tick` timings, but will not be counted in total load.
+- Added `/xp` command.
+- `start.sh` will now emit warnings when the server process exits with an unusual exit code. This helps to detect unexpected segmentation faults and other kinds of native errors.
+
+## Gameplay
+- Added the following new items:
+  - End Crystal
+  - Goat Horn (all variants)
+  - Ice Bomb (from Education Edition)
+  - Recovery Compass
+- Added the following enchantments:
+  - Frost Walker
+- Sugarcane now self-destructs when there is no water adjacent to the base block.
+- Added basic support for middle-clicking on entities to get their spawn eggs.
+- Added sounds when drinking potions.
+- Eating food is now allowed in creative mode and in peaceful difficulty.
+
+## API
+### `pocketmine\block`
+- Extracted `MultiAnyFacingTrait` and `MultiAnySupportTrait` from `GlowLichen` to enable reuse in other blocks.
+- The following API methods have been deprecated:
+  - `Campfire->getInventory()` - this was added by mistake and can't be well-supported given the way that blocks work
+
+### `pocketmine\command`
+- The following classes have been added:
+  - `ClosureCommand` - allows registering a closure to execute a command
+
+### `pocketmine\event`
+- Added APIs to `PlayerInteractEvent` to allow toggling item and block interactions.
+  - This allows various customisations, such as allowing interactions when sneaking, selectively disabling item or block reactions, etc.
+  - If both item and block interactions are disabled, the event is **not** cancelled (blocks can still be placed).
+  - The following API methods have been added:
+    - `public PlayerInteractEvent->setUseBlock(bool $useBlock) : void`
+    - `public PlayerInteractEvent->setUseItem(bool $useItem) : void`
+    - `public PlayerInteractEvent->useBlock() : bool` - returns whether the block can respond to the interaction (toggling levers, opening/closing doors, etc).
+    - `public PlayerInteractEvent->useItem() : bool` - returns whether the item can respond to the interaction (spawn eggs, flint & steel, etc).
+- The following new classes have been added:
+  - `player\PlayerEntityPickEvent` - called when a player middle-clicks on an entity
+
+### `pocketmine\inventory\transaction`
+- The following API methods have been deprecated:
+  - `InventoryAction->onAddToTransaction()`
+
+### `pocketmine\permission`
+- The following API methods have been deprecated:
+  - `PermissionManager->getPermissionSubscriptions()`
+  - `PermissionManager->subscribeToPermission()`
+  - `PermissionManager->unsubscribeFromAllPermissions()`
+  - `PermissionManager->unsubscribeFromPermission()`
+
+### `pocketmine\plugin`
+- The following classes have been deprecated:
+  - `DiskResourceProvider`
+  - `ResourceProvider`
+
+### `pocketmine\promise`
+- `Promise::all()` now accepts zero promises. This will return an already-resolved promise with an empty array.
+
+### `pocketmine\scheduler`
+- Added PHPStan generic types to `TaskHandler` and related APIs in `TaskScheduler` and `Task`.
+- The following API methods have been deprecated
+  - `AsyncTask->publishProgress()`
+  - `AsyncTask->onProgressUpdate()`
+
+### `pocketmine\timings`
+- Timings can now notify other code when timings are enabled/disabled, reloaded, or collected.
+  - The intent of this is to facilitate timings usage on other threads, and have the results collected into a single timings report.
+  - Timings cannot directly control timings on other threads, so these callbacks allow plugins to use custom mechanisms to toggle, reset and collect timings.
+  - PocketMine-MP currently uses this to collect timings from async task workers. More internal threads may be supported in the future.
+- The following API methods have been added:
+  - `public static TimingsHandler::getCollectCallbacks() : ObjectSet<\Closure() : list<Promise<list<string>>>` - callbacks for (asynchronously) collecting timings (typically from other threads). The returned promises should be resolved with the result of `TimingsHandler::printCurrentThreadRecords()`.
+  - `public static TimingsHandler::getReloadCallbacks() : ObjectSet<\Closure() : void>` - callbacks called when timings are reset
+  - `public static TimingsHandler::getToggleCallbacks() : ObjectSet<\Closure(bool $enable) : void>` - callbacks called when timings are enabled/disabled
+  - `public static TimingsHandler::requestPrintTimings() : Promise<list<string>>` - asynchronously collects timing results from all threads and assembles them into a single report
+- The following API methods have been deprecated:
+  - `TimingsHandler::printTimings()` - this function cannot support async timings collection. Use `TimingsHandler::requestPrintTimings()` instead.
+  - `Timings::getAsyncTaskErrorTimings()` - internal method that is no longer needed
+- The following constants have been deprecated:
+  - `Timings::GROUP_BREAKDOWN` - no longer used
+
+### `pocketmine\utils`
+- The following API methods have been added:
+  - `public static Utils::getRandomFloat() : float` - returns a random float between 0 and 1. Drop-in replacement for `lcg_value()` in PHP 8.4.
+
+## Internals
+- Blocks are now always synced with the client during a right-click-block interaction. This clears mispredictions on the client in case the new `PlayerInteractEvent` flags were customized by plugins.
+- `VanillaBlocks` and `VanillaItems` now use reflection to lookup TypeId constants by registration name, instead of requiring TypeIds to be manually specified.
+  - While this is obviously a hack, it prevents incorrect constants from being used when adding new blocks, and guarantees that the names of constants in `BlockTypeIds` and `ItemTypeIds` will match their corresponding entries in `VanillaBlocks` and `VanillaItems` respectively.
+  - It also significantly improves readability of `VanillaBlocks` and `VanillaItems`, as well as eliminating ugly code like `WoodLikeBlockIdHelper`.
+  - In PM6, the team is exploring options to redesign `VanillaBlocks` and `VanillaItems` to eliminate the need for defining separate TypeIds entirely.
+- `ConsoleReader` now uses socket support in `proc_open()` to transmit IPC messages to the server process. Previously, a temporary socket server was used, which was unreliable in some conditions.
+- Event handler tests have been converted to PHPUnit tests by mocking `Server` and `Plugin` instances. Previously, these required integration tests for these dependencies.
+- Fixed various deprecation warnings in PHP 8.4.
+- `netresearch/jsonmapper` is now used at `5.0.0`. The PMMP fork of this library has been removed, as it is no longer needed.
diff --git a/composer.json b/composer.json
index 4a6ad5daf2d..e2ae641cac0 100644
--- a/composer.json
+++ b/composer.json
@@ -32,7 +32,7 @@
       "ext-zlib": ">=1.2.11",
       "composer-runtime-api": "^2.0",
       "adhocore/json-comment": "~1.2.0",
-      "pocketmine/netresearch-jsonmapper": "~v4.4.999",
+      "netresearch/jsonmapper": "~v5.0.0",
       "pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40",
       "pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50",
       "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
@@ -41,7 +41,7 @@
       "pocketmine/callback-validator": "^1.0.2",
       "pocketmine/color": "^0.3.0",
       "pocketmine/errorhandler": "^0.7.0",
-      "pocketmine/locale-data": "~2.19.0",
+      "pocketmine/locale-data": "~2.22.0",
       "pocketmine/log": "^0.4.0",
       "pocketmine/math": "~1.0.0",
       "pocketmine/nbt": "~1.0.0",
diff --git a/composer.lock b/composer.lock
index 89e7b3af434..1cf1415888b 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "26d10b9381ab4e19684ca0b7a5a11a42",
+    "content-hash": "732102eca72dc1d29e7b67dfbce07653",
     "packages": [
         {
             "name": "adhocore/json-comment",
@@ -125,6 +125,57 @@
             ],
             "time": "2023-11-29T23:19:16+00:00"
         },
+        {
+            "name": "netresearch/jsonmapper",
+            "version": "v5.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/cweiske/jsonmapper.git",
+                "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c",
+                "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-pcre": "*",
+                "ext-reflection": "*",
+                "ext-spl": "*",
+                "php": ">=7.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0",
+                "squizlabs/php_codesniffer": "~3.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "JsonMapper": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "OSL-3.0"
+            ],
+            "authors": [
+                {
+                    "name": "Christian Weiske",
+                    "email": "cweiske@cweiske.de",
+                    "homepage": "http://github.com/cweiske/jsonmapper/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Map nested JSON structures onto PHP classes",
+            "support": {
+                "email": "cweiske@cweiske.de",
+                "issues": "https://github.com/cweiske/jsonmapper/issues",
+                "source": "https://github.com/cweiske/jsonmapper/tree/v5.0.0"
+            },
+            "time": "2024-09-08T10:20:00+00:00"
+        },
         {
             "name": "pocketmine/bedrock-block-upgrade-schema",
             "version": "5.0.0",
@@ -420,16 +471,16 @@
         },
         {
             "name": "pocketmine/locale-data",
-            "version": "2.19.6",
+            "version": "2.22.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/pmmp/Language.git",
-                "reference": "93e473e20e7f4515ecf45c5ef0f9155b9247a86e"
+                "reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/pmmp/Language/zipball/93e473e20e7f4515ecf45c5ef0f9155b9247a86e",
-                "reference": "93e473e20e7f4515ecf45c5ef0f9155b9247a86e",
+                "url": "https://api.github.com/repos/pmmp/Language/zipball/aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
+                "reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
                 "shasum": ""
             },
             "type": "library",
@@ -437,9 +488,9 @@
             "description": "Language resources used by PocketMine-MP",
             "support": {
                 "issues": "https://github.com/pmmp/Language/issues",
-                "source": "https://github.com/pmmp/Language/tree/2.19.6"
+                "source": "https://github.com/pmmp/Language/tree/2.22.0"
             },
-            "time": "2023-08-08T16:53:23+00:00"
+            "time": "2024-11-16T13:28:01+00:00"
         },
         {
             "name": "pocketmine/log",
@@ -565,60 +616,6 @@
             },
             "time": "2023-07-14T13:01:49+00:00"
         },
-        {
-            "name": "pocketmine/netresearch-jsonmapper",
-            "version": "v4.4.999",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/pmmp/netresearch-jsonmapper.git",
-                "reference": "9a6610033d56e358e86a3e4fd5f87063c7318833"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/pmmp/netresearch-jsonmapper/zipball/9a6610033d56e358e86a3e4fd5f87063c7318833",
-                "reference": "9a6610033d56e358e86a3e4fd5f87063c7318833",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "ext-pcre": "*",
-                "ext-reflection": "*",
-                "ext-spl": "*",
-                "php": ">=7.1"
-            },
-            "replace": {
-                "netresearch/jsonmapper": "~4.2.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0",
-                "squizlabs/php_codesniffer": "~3.5"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-0": {
-                    "JsonMapper": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "OSL-3.0"
-            ],
-            "authors": [
-                {
-                    "name": "Christian Weiske",
-                    "email": "cweiske@cweiske.de",
-                    "homepage": "http://github.com/cweiske/jsonmapper/",
-                    "role": "Developer"
-                }
-            ],
-            "description": "Fork of netresearch/jsonmapper with security fixes needed by pocketmine/pocketmine-mp",
-            "support": {
-                "email": "cweiske@cweiske.de",
-                "issues": "https://github.com/cweiske/jsonmapper/issues",
-                "source": "https://github.com/pmmp/netresearch-jsonmapper/tree/v4.4.999"
-            },
-            "time": "2024-02-23T13:17:01+00:00"
-        },
         {
             "name": "pocketmine/raklib",
             "version": "1.1.1",
diff --git a/src/Server.php b/src/Server.php
index 074088068b9..9003a7f9cfa 100644
--- a/src/Server.php
+++ b/src/Server.php
@@ -89,6 +89,8 @@
 use pocketmine\promise\PromiseResolver;
 use pocketmine\resourcepacks\ResourcePackManager;
 use pocketmine\scheduler\AsyncPool;
+use pocketmine\scheduler\TimingsCollectionTask;
+use pocketmine\scheduler\TimingsControlTask;
 use pocketmine\snooze\SleeperHandler;
 use pocketmine\stats\SendUsageTask;
 use pocketmine\thread\log\AttachableThreadSafeLogger;
@@ -894,7 +896,36 @@ public function __construct(
 				$poolSize = max(1, (int) $poolSize);
 			}
 
+			TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false));
+			$this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND);
+
 			$this->asyncPool = new AsyncPool($poolSize, max(-1, $this->configGroup->getPropertyInt(Yml::MEMORY_ASYNC_WORKER_HARD_LIMIT, 256)), $this->autoloader, $this->logger, $this->tickSleeper);
+			$this->asyncPool->addWorkerStartHook(function(int $i) : void{
+				if(TimingsHandler::isEnabled()){
+					$this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled(true), $i);
+				}
+			});
+			TimingsHandler::getToggleCallbacks()->add(function(bool $enable) : void{
+				foreach($this->asyncPool->getRunningWorkers() as $workerId){
+					$this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled($enable), $workerId);
+				}
+			});
+			TimingsHandler::getReloadCallbacks()->add(function() : void{
+				foreach($this->asyncPool->getRunningWorkers() as $workerId){
+					$this->asyncPool->submitTaskToWorker(TimingsControlTask::reload(), $workerId);
+				}
+			});
+			TimingsHandler::getCollectCallbacks()->add(function() : array{
+				$promises = [];
+				foreach($this->asyncPool->getRunningWorkers() as $workerId){
+					$resolver = new PromiseResolver();
+					$this->asyncPool->submitTaskToWorker(new TimingsCollectionTask($resolver), $workerId);
+
+					$promises[] = $resolver->getPromise();
+				}
+
+				return $promises;
+			});
 
 			$netCompressionThreshold = -1;
 			if($this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256) >= 0){
@@ -968,9 +999,6 @@ public function __construct(
 			)));
 			$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
 
-			TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false));
-			$this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND);
-
 			DefaultPermissions::registerCorePermissions();
 
 			$this->commandMap = new SimpleCommandMap($this);
diff --git a/src/VersionInfo.php b/src/VersionInfo.php
index 9c1fdf2302f..90c1a7df4fd 100644
--- a/src/VersionInfo.php
+++ b/src/VersionInfo.php
@@ -31,8 +31,8 @@
 
 final class VersionInfo{
 	public const NAME = "PocketMine-MP";
-	public const BASE_VERSION = "5.22.1";
-	public const IS_DEVELOPMENT_BUILD = true;
+	public const BASE_VERSION = "5.23.0";
+	public const IS_DEVELOPMENT_BUILD = false;
 	public const BUILD_CHANNEL = "stable";
 
 	/**
diff --git a/src/block/Anvil.php b/src/block/Anvil.php
index 4b4afef6153..0a1a4707005 100644
--- a/src/block/Anvil.php
+++ b/src/block/Anvil.php
@@ -35,10 +35,10 @@
 use pocketmine\math\Facing;
 use pocketmine\math\Vector3;
 use pocketmine\player\Player;
+use pocketmine\utils\Utils;
 use pocketmine\world\BlockTransaction;
 use pocketmine\world\sound\AnvilFallSound;
 use pocketmine\world\sound\Sound;
-use function lcg_value;
 use function round;
 
 class Anvil extends Transparent implements Fallable{
@@ -97,7 +97,7 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
 	}
 
 	public function onHitGround(FallingBlock $blockEntity) : bool{
-		if(lcg_value() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){
+		if(Utils::getRandomFloat() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){
 			if($this->damage !== self::VERY_DAMAGED){
 				$this->damage = $this->damage + 1;
 			}else{
diff --git a/src/block/Campfire.php b/src/block/Campfire.php
index ce759ee87f7..9f4c42a9ccc 100644
--- a/src/block/Campfire.php
+++ b/src/block/Campfire.php
@@ -69,6 +69,10 @@ class Campfire extends Transparent{
 
 	private const UPDATE_INTERVAL_TICKS = 10;
 
+	/**
+	 * @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block
+	 * has never been set in the world.
+	 */
 	protected CampfireInventory $inventory;
 
 	/**
@@ -129,6 +133,10 @@ protected function recalculateCollisionBoxes() : array{
 		return [AxisAlignedBB::one()->trim(Facing::UP, 9 / 16)];
 	}
 
+	/**
+	 * @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block
+	 * has never been set in the world.
+	 */
 	public function getInventory() : CampfireInventory{
 		return $this->inventory;
 	}
diff --git a/src/block/Farmland.php b/src/block/Farmland.php
index a17a220f0b5..b7a2500a8b5 100644
--- a/src/block/Farmland.php
+++ b/src/block/Farmland.php
@@ -31,8 +31,8 @@
 use pocketmine\item\Item;
 use pocketmine\math\AxisAlignedBB;
 use pocketmine\math\Facing;
+use pocketmine\utils\Utils;
 use function intdiv;
-use function lcg_value;
 
 class Farmland extends Transparent{
 	public const MAX_WETNESS = 7;
@@ -148,7 +148,7 @@ public function onRandomTick() : void{
 	}
 
 	public function onEntityLand(Entity $entity) : ?float{
-		if($entity instanceof Living && lcg_value() < $entity->getFallDistance() - 0.5){
+		if($entity instanceof Living && Utils::getRandomFloat() < $entity->getFallDistance() - 0.5){
 			$ev = new EntityTrampleFarmlandEvent($entity, $this);
 			$ev->call();
 			if(!$ev->isCancelled()){
diff --git a/src/block/GlowLichen.php b/src/block/GlowLichen.php
index de66ccad769..d30e2539582 100644
--- a/src/block/GlowLichen.php
+++ b/src/block/GlowLichen.php
@@ -24,60 +24,20 @@
 namespace pocketmine\block;
 
 use pocketmine\block\utils\BlockEventHelper;
+use pocketmine\block\utils\MultiAnySupportTrait;
 use pocketmine\block\utils\SupportType;
-use pocketmine\data\runtime\RuntimeDataDescriber;
 use pocketmine\item\Fertilizer;
 use pocketmine\item\Item;
 use pocketmine\math\AxisAlignedBB;
 use pocketmine\math\Facing;
 use pocketmine\math\Vector3;
 use pocketmine\player\Player;
-use pocketmine\world\BlockTransaction;
 use pocketmine\world\World;
-use function array_key_first;
 use function count;
 use function shuffle;
 
 class GlowLichen extends Transparent{
-
-	/** @var int[] */
-	protected array $faces = [];
-
-	protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
-		$w->facingFlags($this->faces);
-	}
-
-	/** @return int[] */
-	public function getFaces() : array{ return $this->faces; }
-
-	public function hasFace(int $face) : bool{
-		return isset($this->faces[$face]);
-	}
-
-	/**
-	 * @param int[] $faces
-	 * @return $this
-	 */
-	public function setFaces(array $faces) : self{
-		$uniqueFaces = [];
-		foreach($faces as $face){
-			Facing::validate($face);
-			$uniqueFaces[$face] = $face;
-		}
-		$this->faces = $uniqueFaces;
-		return $this;
-	}
-
-	/** @return $this */
-	public function setFace(int $face, bool $value) : self{
-		Facing::validate($face);
-		if($value){
-			$this->faces[$face] = $face;
-		}else{
-			unset($this->faces[$face]);
-		}
-		return $this;
-	}
+	use MultiAnySupportTrait;
 
 	public function getLightLevel() : int{
 		return 7;
@@ -102,39 +62,11 @@ public function canBeReplaced() : bool{
 		return true;
 	}
 
-	public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
-		$this->faces = $blockReplace instanceof GlowLichen ? $blockReplace->faces : [];
-		$availableFaces = $this->getAvailableFaces();
-
-		if(count($availableFaces) === 0){
-			return false;
-		}
-
-		$opposite = Facing::opposite($face);
-		$placedFace = isset($availableFaces[$opposite]) ? $opposite : array_key_first($availableFaces);
-		$this->faces[$placedFace] = $placedFace;
-
-		return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
-	}
-
-	public function onNearbyBlockChange() : void{
-		$changed = false;
-
-		foreach($this->faces as $face){
-			if($this->getAdjacentSupportType($face) !== SupportType::FULL){
-				unset($this->faces[$face]);
-				$changed = true;
-			}
-		}
-
-		if($changed){
-			$world = $this->position->getWorld();
-			if(count($this->faces) === 0){
-				$world->useBreakOn($this->position);
-			}else{
-				$world->setBlock($this->position, $this);
-			}
-		}
+	/**
+	 * @return int[]
+	 */
+	protected function getInitialPlaceFaces(Block $blockReplace) : array{
+		return $blockReplace instanceof GlowLichen ? $blockReplace->faces : [];
 	}
 
 	private function getSpreadBlock(Block $replace, int $spreadFace) : ?Block{
@@ -261,17 +193,4 @@ public function getFlameEncouragement() : int{
 	public function getFlammability() : int{
 		return 100;
 	}
-
-	/**
-	 * @return array<int, int> $faces
-	 */
-	private function getAvailableFaces() : array{
-		$faces = [];
-		foreach(Facing::ALL as $face){
-			if(!$this->hasFace($face) && $this->getAdjacentSupportType($face) === SupportType::FULL){
-				$faces[$face] = $face;
-			}
-		}
-		return $faces;
-	}
 }
diff --git a/src/block/ItemFrame.php b/src/block/ItemFrame.php
index b5b6093c497..c03806a3b55 100644
--- a/src/block/ItemFrame.php
+++ b/src/block/ItemFrame.php
@@ -31,13 +31,13 @@
 use pocketmine\math\Facing;
 use pocketmine\math\Vector3;
 use pocketmine\player\Player;
+use pocketmine\utils\Utils;
 use pocketmine\world\BlockTransaction;
 use pocketmine\world\sound\ItemFrameAddItemSound;
 use pocketmine\world\sound\ItemFrameRemoveItemSound;
 use pocketmine\world\sound\ItemFrameRotateItemSound;
 use function is_infinite;
 use function is_nan;
-use function lcg_value;
 
 class ItemFrame extends Flowable{
 	use AnyFacingTrait;
@@ -154,7 +154,7 @@ public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
 			return false;
 		}
 		$world = $this->position->getWorld();
-		if(lcg_value() <= $this->itemDropChance){
+		if(Utils::getRandomFloat() <= $this->itemDropChance){
 			$world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
 			$world->addSound($this->position, new ItemFrameRemoveItemSound());
 		}
@@ -185,7 +185,7 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
 
 	public function getDropsForCompatibleTool(Item $item) : array{
 		$drops = parent::getDropsForCompatibleTool($item);
-		if($this->framedItem !== null && lcg_value() <= $this->itemDropChance){
+		if($this->framedItem !== null && Utils::getRandomFloat() <= $this->itemDropChance){
 			$drops[] = clone $this->framedItem;
 		}
 
diff --git a/src/block/Liquid.php b/src/block/Liquid.php
index 6404cf9081f..a37019d650d 100644
--- a/src/block/Liquid.php
+++ b/src/block/Liquid.php
@@ -33,9 +33,9 @@
 use pocketmine\math\AxisAlignedBB;
 use pocketmine\math\Facing;
 use pocketmine\math\Vector3;
+use pocketmine\utils\Utils;
 use pocketmine\world\sound\FizzSound;
 use pocketmine\world\sound\Sound;
-use function lcg_value;
 
 abstract class Liquid extends Transparent{
 	public const MAX_DECAY = 7;
@@ -368,7 +368,7 @@ protected function checkForHarden() : bool{
 
 	protected function liquidCollide(Block $cause, Block $result) : bool{
 		if(BlockEventHelper::form($this, $result, $cause)){
-			$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
+			$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.8));
 		}
 		return true;
 	}
diff --git a/src/block/Magma.php b/src/block/Magma.php
index d2f309325d4..7b3fa5229eb 100644
--- a/src/block/Magma.php
+++ b/src/block/Magma.php
@@ -39,7 +39,7 @@ public function hasEntityCollision() : bool{
 	}
 
 	public function onEntityInside(Entity $entity) : bool{
-		if($entity instanceof Living && !$entity->isSneaking()){
+		if($entity instanceof Living && !$entity->isSneaking() && $entity->getFrostWalkerLevel() === 0){
 			$ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1);
 			$entity->attack($ev);
 		}
diff --git a/src/block/Sugarcane.php b/src/block/Sugarcane.php
index 97b4aee9c19..2da2dc9b971 100644
--- a/src/block/Sugarcane.php
+++ b/src/block/Sugarcane.php
@@ -36,7 +36,9 @@
 
 class Sugarcane extends Flowable{
 	use AgeableTrait;
-	use StaticSupportTrait;
+	use StaticSupportTrait {
+		onNearbyBlockChange as onSupportBlockChange;
+	}
 
 	public const MAX_AGE = 15;
 
@@ -97,7 +99,13 @@ public function ticksRandomly() : bool{
 	}
 
 	public function onRandomTick() : void{
-		if(!$this->getSide(Facing::DOWN)->hasSameTypeId($this)){
+		$down = $this->getSide(Facing::DOWN);
+		if(!$down->hasSameTypeId($this)){
+			if(!$this->hasNearbyWater($down)){
+				$this->position->getWorld()->useBreakOn($this->position, createParticles: true);
+				return;
+			}
+
 			if($this->age === self::MAX_AGE){
 				$this->grow($this->position);
 			}else{
@@ -123,4 +131,23 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
 
 		return false;
 	}
+
+	private function hasNearbyWater(Block $down) : bool{
+		foreach($down->getHorizontalSides() as $sideBlock){
+			$blockId = $sideBlock->getTypeId();
+			if($blockId === BlockTypeIds::WATER || $blockId === BlockTypeIds::FROSTED_ICE){
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public function onNearbyBlockChange() : void{
+		$down = $this->getSide(Facing::DOWN);
+		if(!$down->hasSameTypeId($this) && !$this->hasNearbyWater($down)){
+			$this->position->getWorld()->useBreakOn($this->position, createParticles: true);
+		}else{
+			$this->onSupportBlockChange();
+		}
+	}
 }
diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php
index 60540dfb832..54cf90a0c3b 100644
--- a/src/block/VanillaBlocks.php
+++ b/src/block/VanillaBlocks.php
@@ -26,7 +26,6 @@
 use pocketmine\block\BlockBreakInfo as BreakInfo;
 use pocketmine\block\BlockIdentifier as BID;
 use pocketmine\block\BlockToolType as ToolType;
-use pocketmine\block\BlockTypeIds as Ids;
 use pocketmine\block\BlockTypeInfo as Info;
 use pocketmine\block\BlockTypeTags as Tags;
 use pocketmine\block\tile\Banner as TileBanner;
@@ -56,6 +55,7 @@
 use pocketmine\block\tile\Note as TileNote;
 use pocketmine\block\tile\ShulkerBox as TileShulkerBox;
 use pocketmine\block\tile\Smoker as TileSmoker;
+use pocketmine\block\tile\Tile;
 use pocketmine\block\utils\AmethystTrait;
 use pocketmine\block\utils\LeavesType;
 use pocketmine\block\utils\SaplingType;
@@ -64,9 +64,12 @@
 use pocketmine\item\enchantment\ItemEnchantmentTags as EnchantmentTags;
 use pocketmine\item\Item;
 use pocketmine\item\ToolTier;
+use pocketmine\item\VanillaItems;
 use pocketmine\math\Facing;
 use pocketmine\utils\CloningRegistryTrait;
+use function is_int;
 use function mb_strtolower;
+use function mb_strtoupper;
 use function strtolower;
 
 /**
@@ -793,8 +796,28 @@ private function __construct(){
 		//NOOP
 	}
 
-	protected static function register(string $name, Block $block) : void{
+	/**
+	 * @phpstan-template TBlock of Block
+	 * @phpstan-param \Closure(BID) : TBlock $createBlock
+	 * @phpstan-param class-string<covariant Tile> $tileClass
+	 * @phpstan-return TBlock
+	 */
+	protected static function register(string $name, \Closure $createBlock, ?string $tileClass = null) : Block{
+		//this sketchy hack allows us to avoid manually writing the constants inline
+		//since type IDs are generated from this class anyway, I'm OK with this hack
+		//nonetheless, we should try to get rid of it in a future major version (e.g by using string type IDs)
+		$reflect = new \ReflectionClass(BlockTypeIds::class);
+		$typeId = $reflect->getConstant(mb_strtoupper($name));
+		if(!is_int($typeId)){
+			//this allows registering new stuff without adding new type ID constants
+			//this reduces the number of mandatory steps to test new features in local development
+			\GlobalLogger::get()->error(self::class . ": No constant type ID found for $name, generating a new one");
+			$typeId = BlockTypeIds::newId();
+		}
+		$block = $createBlock(new BID($typeId, $tileClass));
 		self::_registryRegister($name, $block);
+
+		return $block;
 	}
 
 	/**
@@ -809,11 +832,12 @@ public static function getAll() : array{
 	}
 
 	protected static function setup() : void{
+		self::register("air", fn(BID $id) => new Air($id, "Air", new Info(BreakInfo::indestructible(-1.0))));
+
 		$railBreakInfo = new Info(new BreakInfo(0.7));
-		self::register("activator_rail", new ActivatorRail(new BID(Ids::ACTIVATOR_RAIL), "Activator Rail", $railBreakInfo));
-		self::register("air", new Air(new BID(Ids::AIR), "Air", new Info(BreakInfo::indestructible(-1.0))));
-		self::register("anvil", new Anvil(new BID(Ids::ANVIL), "Anvil", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 6000.0))));
-		self::register("bamboo", new Bamboo(new BID(Ids::BAMBOO), "Bamboo", new Info(new class(2.0 /* 1.0 in PC */, ToolType::AXE) extends BreakInfo{
+		self::register("activator_rail", fn(BID $id) => new ActivatorRail($id, "Activator Rail", $railBreakInfo));
+		self::register("anvil", fn(BID $id) => new Anvil($id, "Anvil", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 6000.0))));
+		self::register("bamboo", fn(BID $id) => new Bamboo($id, "Bamboo", new Info(new class(2.0 /* 1.0 in PC */, ToolType::AXE) extends BreakInfo{
 			public function getBreakTime(Item $item) : float{
 				if($item->getBlockToolType() === ToolType::SWORD){
 					return 0.0;
@@ -821,242 +845,242 @@ public function getBreakTime(Item $item) : float{
 				return parent::getBreakTime($item);
 			}
 		}, [Tags::POTTABLE_PLANTS])));
-		self::register("bamboo_sapling", new BambooSapling(new BID(Ids::BAMBOO_SAPLING), "Bamboo Sapling", new Info(BreakInfo::instant())));
+		self::register("bamboo_sapling", fn(BID $id) => new BambooSapling($id, "Bamboo Sapling", new Info(BreakInfo::instant())));
 
 		$bannerBreakInfo = new Info(BreakInfo::axe(1.0));
-		self::register("banner", new FloorBanner(new BID(Ids::BANNER, TileBanner::class), "Banner", $bannerBreakInfo));
-		self::register("wall_banner", new WallBanner(new BID(Ids::WALL_BANNER, TileBanner::class), "Wall Banner", $bannerBreakInfo));
-		self::register("barrel", new Barrel(new BID(Ids::BARREL, TileBarrel::class), "Barrel", new Info(BreakInfo::axe(2.5))));
-		self::register("barrier", new Transparent(new BID(Ids::BARRIER), "Barrier", new Info(BreakInfo::indestructible())));
-		self::register("beacon", new Beacon(new BID(Ids::BEACON, TileBeacon::class), "Beacon", new Info(new BreakInfo(3.0))));
-		self::register("bed", new Bed(new BID(Ids::BED, TileBed::class), "Bed Block", new Info(new BreakInfo(0.2))));
-		self::register("bedrock", new Bedrock(new BID(Ids::BEDROCK), "Bedrock", new Info(BreakInfo::indestructible())));
-
-		self::register("beetroots", new Beetroot(new BID(Ids::BEETROOTS), "Beetroot Block", new Info(BreakInfo::instant())));
-		self::register("bell", new Bell(new BID(Ids::BELL, TileBell::class), "Bell", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))));
-		self::register("blue_ice", new BlueIce(new BID(Ids::BLUE_ICE), "Blue Ice", new Info(BreakInfo::pickaxe(2.8))));
-		self::register("bone_block", new BoneBlock(new BID(Ids::BONE_BLOCK), "Bone Block", new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD))));
-		self::register("bookshelf", new Bookshelf(new BID(Ids::BOOKSHELF), "Bookshelf", new Info(BreakInfo::axe(1.5))));
-		self::register("chiseled_bookshelf", new ChiseledBookshelf(new BID(Ids::CHISELED_BOOKSHELF, TileChiseledBookshelf::class), "Chiseled Bookshelf", new Info(BreakInfo::axe(1.5))));
-		self::register("brewing_stand", new BrewingStand(new BID(Ids::BREWING_STAND, TileBrewingStand::class), "Brewing Stand", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))));
+		self::register("banner", fn(BID $id) => new FloorBanner($id, "Banner", $bannerBreakInfo), TileBanner::class);
+		self::register("wall_banner", fn(BID $id) => new WallBanner($id, "Wall Banner", $bannerBreakInfo), TileBanner::class);
+		self::register("barrel", fn(BID $id) => new Barrel($id, "Barrel", new Info(BreakInfo::axe(2.5))), TileBarrel::class);
+		self::register("barrier", fn(BID $id) => new Transparent($id, "Barrier", new Info(BreakInfo::indestructible())));
+		self::register("beacon", fn(BID $id) => new Beacon($id, "Beacon", new Info(new BreakInfo(3.0))), TileBeacon::class);
+		self::register("bed", fn(BID $id) => new Bed($id, "Bed Block", new Info(new BreakInfo(0.2))), TileBed::class);
+		self::register("bedrock", fn(BID $id) => new Bedrock($id, "Bedrock", new Info(BreakInfo::indestructible())));
+
+		self::register("beetroots", fn(BID $id) => new Beetroot($id, "Beetroot Block", new Info(BreakInfo::instant())));
+		self::register("bell", fn(BID $id) => new Bell($id, "Bell", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))), TileBell::class);
+		self::register("blue_ice", fn(BID $id) => new BlueIce($id, "Blue Ice", new Info(BreakInfo::pickaxe(2.8))));
+		self::register("bone_block", fn(BID $id) => new BoneBlock($id, "Bone Block", new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD))));
+		self::register("bookshelf", fn(BID $id) => new Bookshelf($id, "Bookshelf", new Info(BreakInfo::axe(1.5))));
+		self::register("chiseled_bookshelf", fn(BID $id) => new ChiseledBookshelf($id, "Chiseled Bookshelf", new Info(BreakInfo::axe(1.5))), TileChiseledBookshelf::class);
+		self::register("brewing_stand", fn(BID $id) => new BrewingStand($id, "Brewing Stand", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))), TileBrewingStand::class);
 
 		$bricksBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
-		self::register("brick_stairs", new Stair(new BID(Ids::BRICK_STAIRS), "Brick Stairs", $bricksBreakInfo));
-		self::register("bricks", new Opaque(new BID(Ids::BRICKS), "Bricks", $bricksBreakInfo));
+		self::register("brick_stairs", fn(BID $id) => new Stair($id, "Brick Stairs", $bricksBreakInfo));
+		self::register("bricks", fn(BID $id) => new Opaque($id, "Bricks", $bricksBreakInfo));
 
-		self::register("brown_mushroom", new BrownMushroom(new BID(Ids::BROWN_MUSHROOM), "Brown Mushroom", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
-		self::register("cactus", new Cactus(new BID(Ids::CACTUS), "Cactus", new Info(new BreakInfo(0.4), [Tags::POTTABLE_PLANTS])));
-		self::register("cake", new Cake(new BID(Ids::CAKE), "Cake", new Info(new BreakInfo(0.5))));
+		self::register("brown_mushroom", fn(BID $id) => new BrownMushroom($id, "Brown Mushroom", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
+		self::register("cactus", fn(BID $id) => new Cactus($id, "Cactus", new Info(new BreakInfo(0.4), [Tags::POTTABLE_PLANTS])));
+		self::register("cake", fn(BID $id) => new Cake($id, "Cake", new Info(new BreakInfo(0.5))));
 
 		$campfireBreakInfo = new Info(BreakInfo::axe(2.0));
-		self::register("campfire", new Campfire(new BID(Ids::CAMPFIRE, TileCampfire::class), "Campfire", $campfireBreakInfo));
-		self::register("soul_campfire", new SoulCampfire(new BID(Ids::SOUL_CAMPFIRE, TileCampfire::class), "Soul Campfire", $campfireBreakInfo));
+		self::register("campfire", fn(BID $id) => new Campfire($id, "Campfire", $campfireBreakInfo), TileCampfire::class);
+		self::register("soul_campfire", fn(BID $id) => new SoulCampfire($id, "Soul Campfire", $campfireBreakInfo), TileCampfire::class);
 
-		self::register("carrots", new Carrot(new BID(Ids::CARROTS), "Carrot Block", new Info(BreakInfo::instant())));
+		self::register("carrots", fn(BID $id) => new Carrot($id, "Carrot Block", new Info(BreakInfo::instant())));
 
 		$chestBreakInfo = new Info(BreakInfo::axe(2.5));
-		self::register("chest", new Chest(new BID(Ids::CHEST, TileChest::class), "Chest", $chestBreakInfo));
-		self::register("clay", new Clay(new BID(Ids::CLAY), "Clay Block", new Info(BreakInfo::shovel(0.6))));
-		self::register("coal", new Coal(new BID(Ids::COAL), "Coal Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 30.0))));
+		self::register("chest", fn(BID $id) => new Chest($id, "Chest", $chestBreakInfo), TileChest::class);
+		self::register("clay", fn(BID $id) => new Clay($id, "Clay Block", new Info(BreakInfo::shovel(0.6))));
+		self::register("coal", fn(BID $id) => new Coal($id, "Coal Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 30.0))));
 
 		$cobblestoneBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
-		self::register("cobblestone", $cobblestone = new Opaque(new BID(Ids::COBBLESTONE), "Cobblestone", $cobblestoneBreakInfo));
-		self::register("mossy_cobblestone", new Opaque(new BID(Ids::MOSSY_COBBLESTONE), "Mossy Cobblestone", $cobblestoneBreakInfo));
-		self::register("cobblestone_stairs", new Stair(new BID(Ids::COBBLESTONE_STAIRS), "Cobblestone Stairs", $cobblestoneBreakInfo));
-		self::register("mossy_cobblestone_stairs", new Stair(new BID(Ids::MOSSY_COBBLESTONE_STAIRS), "Mossy Cobblestone Stairs", $cobblestoneBreakInfo));
-
-		self::register("cobweb", new Cobweb(new BID(Ids::COBWEB), "Cobweb", new Info(new BreakInfo(4.0, ToolType::SWORD | ToolType::SHEARS, 1))));
-		self::register("cocoa_pod", new CocoaBlock(new BID(Ids::COCOA_POD), "Cocoa Block", new Info(BreakInfo::axe(0.2, null, 15.0))));
-		self::register("coral_block", new CoralBlock(new BID(Ids::CORAL_BLOCK), "Coral Block", new Info(BreakInfo::pickaxe(7.0, ToolTier::WOOD))));
-		self::register("daylight_sensor", new DaylightSensor(new BID(Ids::DAYLIGHT_SENSOR, TileDaylightSensor::class), "Daylight Sensor", new Info(BreakInfo::axe(0.2))));
-		self::register("dead_bush", new DeadBush(new BID(Ids::DEAD_BUSH), "Dead Bush", new Info(BreakInfo::instant(ToolType::SHEARS, 1), [Tags::POTTABLE_PLANTS])));
-		self::register("detector_rail", new DetectorRail(new BID(Ids::DETECTOR_RAIL), "Detector Rail", $railBreakInfo));
-
-		self::register("diamond", new Opaque(new BID(Ids::DIAMOND), "Diamond Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::IRON, 30.0))));
-		self::register("dirt", new Dirt(new BID(Ids::DIRT), "Dirt", new Info(BreakInfo::shovel(0.5), [Tags::DIRT])));
-		self::register("sunflower", new DoublePlant(new BID(Ids::SUNFLOWER), "Sunflower", new Info(BreakInfo::instant())));
-		self::register("lilac", new DoublePlant(new BID(Ids::LILAC), "Lilac", new Info(BreakInfo::instant())));
-		self::register("rose_bush", new DoublePlant(new BID(Ids::ROSE_BUSH), "Rose Bush", new Info(BreakInfo::instant())));
-		self::register("peony", new DoublePlant(new BID(Ids::PEONY), "Peony", new Info(BreakInfo::instant())));
-		self::register("pink_petals", new PinkPetals(new BID(Ids::PINK_PETALS), "Pink Petals", new Info(BreakInfo::instant())));
-		self::register("double_tallgrass", new DoubleTallGrass(new BID(Ids::DOUBLE_TALLGRASS), "Double Tallgrass", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
-		self::register("large_fern", new DoubleTallGrass(new BID(Ids::LARGE_FERN), "Large Fern", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
-		self::register("pitcher_plant", new DoublePlant(new BID(Ids::PITCHER_PLANT), "Pitcher Plant", new Info(BreakInfo::instant())));
-		self::register("pitcher_crop", new PitcherCrop(new BID(Ids::PITCHER_CROP), "Pitcher Crop", new Info(BreakInfo::instant())));
-		self::register("double_pitcher_crop", new DoublePitcherCrop(new BID(Ids::DOUBLE_PITCHER_CROP), "Double Pitcher Crop", new Info(BreakInfo::instant())));
-		self::register("dragon_egg", new DragonEgg(new BID(Ids::DRAGON_EGG), "Dragon Egg", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD))));
-		self::register("dried_kelp", new DriedKelp(new BID(Ids::DRIED_KELP), "Dried Kelp Block", new Info(new BreakInfo(0.5, ToolType::NONE, 0, 12.5))));
-		self::register("emerald", new Opaque(new BID(Ids::EMERALD), "Emerald Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::IRON, 30.0))));
-		self::register("enchanting_table", new EnchantingTable(new BID(Ids::ENCHANTING_TABLE, TileEnchantingTable::class), "Enchanting Table", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 6000.0))));
-		self::register("end_portal_frame", new EndPortalFrame(new BID(Ids::END_PORTAL_FRAME), "End Portal Frame", new Info(BreakInfo::indestructible())));
-		self::register("end_rod", new EndRod(new BID(Ids::END_ROD), "End Rod", new Info(BreakInfo::instant())));
-		self::register("end_stone", new Opaque(new BID(Ids::END_STONE), "End Stone", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 45.0))));
+		$cobblestone = self::register("cobblestone", fn(BID $id) => new Opaque($id, "Cobblestone", $cobblestoneBreakInfo));
+		self::register("mossy_cobblestone", fn(BID $id) => new Opaque($id, "Mossy Cobblestone", $cobblestoneBreakInfo));
+		self::register("cobblestone_stairs", fn(BID $id) => new Stair($id, "Cobblestone Stairs", $cobblestoneBreakInfo));
+		self::register("mossy_cobblestone_stairs", fn(BID $id) => new Stair($id, "Mossy Cobblestone Stairs", $cobblestoneBreakInfo));
+
+		self::register("cobweb", fn(BID $id) => new Cobweb($id, "Cobweb", new Info(new BreakInfo(4.0, ToolType::SWORD | ToolType::SHEARS, 1))));
+		self::register("cocoa_pod", fn(BID $id) => new CocoaBlock($id, "Cocoa Block", new Info(BreakInfo::axe(0.2, null, 15.0))));
+		self::register("coral_block", fn(BID $id) => new CoralBlock($id, "Coral Block", new Info(BreakInfo::pickaxe(7.0, ToolTier::WOOD))));
+		self::register("daylight_sensor", fn(BID $id) => new DaylightSensor($id, "Daylight Sensor", new Info(BreakInfo::axe(0.2))), TileDaylightSensor::class);
+		self::register("dead_bush", fn(BID $id) => new DeadBush($id, "Dead Bush", new Info(BreakInfo::instant(ToolType::SHEARS, 1), [Tags::POTTABLE_PLANTS])));
+		self::register("detector_rail", fn(BID $id) => new DetectorRail($id, "Detector Rail", $railBreakInfo));
+
+		self::register("diamond", fn(BID $id) => new Opaque($id, "Diamond Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::IRON, 30.0))));
+		self::register("dirt", fn(BID $id) => new Dirt($id, "Dirt", new Info(BreakInfo::shovel(0.5), [Tags::DIRT])));
+		self::register("sunflower", fn(BID $id) => new DoublePlant($id, "Sunflower", new Info(BreakInfo::instant())));
+		self::register("lilac", fn(BID $id) => new DoublePlant($id, "Lilac", new Info(BreakInfo::instant())));
+		self::register("rose_bush", fn(BID $id) => new DoublePlant($id, "Rose Bush", new Info(BreakInfo::instant())));
+		self::register("peony", fn(BID $id) => new DoublePlant($id, "Peony", new Info(BreakInfo::instant())));
+		self::register("pink_petals", fn(BID $id) => new PinkPetals($id, "Pink Petals", new Info(BreakInfo::instant())));
+		self::register("double_tallgrass", fn(BID $id) => new DoubleTallGrass($id, "Double Tallgrass", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
+		self::register("large_fern", fn(BID $id) => new DoubleTallGrass($id, "Large Fern", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
+		self::register("pitcher_plant", fn(BID $id) => new DoublePlant($id, "Pitcher Plant", new Info(BreakInfo::instant())));
+		self::register("pitcher_crop", fn(BID $id) => new PitcherCrop($id, "Pitcher Crop", new Info(BreakInfo::instant())));
+		self::register("double_pitcher_crop", fn(BID $id) => new DoublePitcherCrop($id, "Double Pitcher Crop", new Info(BreakInfo::instant())));
+		self::register("dragon_egg", fn(BID $id) => new DragonEgg($id, "Dragon Egg", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD))));
+		self::register("dried_kelp", fn(BID $id) => new DriedKelp($id, "Dried Kelp Block", new Info(new BreakInfo(0.5, ToolType::NONE, 0, 12.5))));
+		self::register("emerald", fn(BID $id) => new Opaque($id, "Emerald Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::IRON, 30.0))));
+		self::register("enchanting_table", fn(BID $id) => new EnchantingTable($id, "Enchanting Table", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 6000.0))), TileEnchantingTable::class);
+		self::register("end_portal_frame", fn(BID $id) => new EndPortalFrame($id, "End Portal Frame", new Info(BreakInfo::indestructible())));
+		self::register("end_rod", fn(BID $id) => new EndRod($id, "End Rod", new Info(BreakInfo::instant())));
+		self::register("end_stone", fn(BID $id) => new Opaque($id, "End Stone", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 45.0))));
 
 		$endBrickBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD, 4.0));
-		self::register("end_stone_bricks", new Opaque(new BID(Ids::END_STONE_BRICKS), "End Stone Bricks", $endBrickBreakInfo));
-		self::register("end_stone_brick_stairs", new Stair(new BID(Ids::END_STONE_BRICK_STAIRS), "End Stone Brick Stairs", $endBrickBreakInfo));
+		self::register("end_stone_bricks", fn(BID $id) => new Opaque($id, "End Stone Bricks", $endBrickBreakInfo));
+		self::register("end_stone_brick_stairs", fn(BID $id) => new Stair($id, "End Stone Brick Stairs", $endBrickBreakInfo));
 
-		self::register("ender_chest", new EnderChest(new BID(Ids::ENDER_CHEST, TileEnderChest::class), "Ender Chest", new Info(BreakInfo::pickaxe(22.5, ToolTier::WOOD, 3000.0))));
-		self::register("farmland", new Farmland(new BID(Ids::FARMLAND), "Farmland", new Info(BreakInfo::shovel(0.6), [Tags::DIRT])));
-		self::register("fire", new Fire(new BID(Ids::FIRE), "Fire Block", new Info(BreakInfo::instant(), [Tags::FIRE])));
+		self::register("ender_chest", fn(BID $id) => new EnderChest($id, "Ender Chest", new Info(BreakInfo::pickaxe(22.5, ToolTier::WOOD, 3000.0))), TileEnderChest::class);
+		self::register("farmland", fn(BID $id) => new Farmland($id, "Farmland", new Info(BreakInfo::shovel(0.6), [Tags::DIRT])));
+		self::register("fire", fn(BID $id) => new Fire($id, "Fire Block", new Info(BreakInfo::instant(), [Tags::FIRE])));
 
 		$flowerTypeInfo = new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS]);
-		self::register("dandelion", new Flower(new BID(Ids::DANDELION), "Dandelion", $flowerTypeInfo));
-		self::register("poppy", new Flower(new BID(Ids::POPPY), "Poppy", $flowerTypeInfo));
-		self::register("allium", new Flower(new BID(Ids::ALLIUM), "Allium", $flowerTypeInfo));
-		self::register("azure_bluet", new Flower(new BID(Ids::AZURE_BLUET), "Azure Bluet", $flowerTypeInfo));
-		self::register("blue_orchid", new Flower(new BID(Ids::BLUE_ORCHID), "Blue Orchid", $flowerTypeInfo));
-		self::register("cornflower", new Flower(new BID(Ids::CORNFLOWER), "Cornflower", $flowerTypeInfo));
-		self::register("lily_of_the_valley", new Flower(new BID(Ids::LILY_OF_THE_VALLEY), "Lily of the Valley", $flowerTypeInfo));
-		self::register("orange_tulip", new Flower(new BID(Ids::ORANGE_TULIP), "Orange Tulip", $flowerTypeInfo));
-		self::register("oxeye_daisy", new Flower(new BID(Ids::OXEYE_DAISY), "Oxeye Daisy", $flowerTypeInfo));
-		self::register("pink_tulip", new Flower(new BID(Ids::PINK_TULIP), "Pink Tulip", $flowerTypeInfo));
-		self::register("red_tulip", new Flower(new BID(Ids::RED_TULIP), "Red Tulip", $flowerTypeInfo));
-		self::register("white_tulip", new Flower(new BID(Ids::WHITE_TULIP), "White Tulip", $flowerTypeInfo));
-		self::register("torchflower", new Flower(new BID(Ids::TORCHFLOWER), "Torchflower", $flowerTypeInfo));
-		self::register("torchflower_crop", new TorchflowerCrop(new BID(Ids::TORCHFLOWER_CROP), "Torchflower Crop", new Info(BreakInfo::instant())));
-		self::register("flower_pot", new FlowerPot(new BID(Ids::FLOWER_POT, TileFlowerPot::class), "Flower Pot", new Info(BreakInfo::instant())));
-		self::register("frosted_ice", new FrostedIce(new BID(Ids::FROSTED_ICE), "Frosted Ice", new Info(BreakInfo::pickaxe(2.5))));
-		self::register("furnace", new Furnace(new BID(Ids::FURNACE, TileNormalFurnace::class), "Furnace", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::FURNACE));
-		self::register("blast_furnace", new Furnace(new BID(Ids::BLAST_FURNACE, TileBlastFurnace::class), "Blast Furnace", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::BLAST_FURNACE));
-		self::register("smoker", new Furnace(new BID(Ids::SMOKER, TileSmoker::class), "Smoker", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::SMOKER));
+		self::register("dandelion", fn(BID $id) => new Flower($id, "Dandelion", $flowerTypeInfo));
+		self::register("poppy", fn(BID $id) => new Flower($id, "Poppy", $flowerTypeInfo));
+		self::register("allium", fn(BID $id) => new Flower($id, "Allium", $flowerTypeInfo));
+		self::register("azure_bluet", fn(BID $id) => new Flower($id, "Azure Bluet", $flowerTypeInfo));
+		self::register("blue_orchid", fn(BID $id) => new Flower($id, "Blue Orchid", $flowerTypeInfo));
+		self::register("cornflower", fn(BID $id) => new Flower($id, "Cornflower", $flowerTypeInfo));
+		self::register("lily_of_the_valley", fn(BID $id) => new Flower($id, "Lily of the Valley", $flowerTypeInfo));
+		self::register("orange_tulip", fn(BID $id) => new Flower($id, "Orange Tulip", $flowerTypeInfo));
+		self::register("oxeye_daisy", fn(BID $id) => new Flower($id, "Oxeye Daisy", $flowerTypeInfo));
+		self::register("pink_tulip", fn(BID $id) => new Flower($id, "Pink Tulip", $flowerTypeInfo));
+		self::register("red_tulip", fn(BID $id) => new Flower($id, "Red Tulip", $flowerTypeInfo));
+		self::register("white_tulip", fn(BID $id) => new Flower($id, "White Tulip", $flowerTypeInfo));
+		self::register("torchflower", fn(BID $id) => new Flower($id, "Torchflower", $flowerTypeInfo));
+		self::register("torchflower_crop", fn(BID $id) => new TorchflowerCrop($id, "Torchflower Crop", new Info(BreakInfo::instant())));
+		self::register("flower_pot", fn(BID $id) => new FlowerPot($id, "Flower Pot", new Info(BreakInfo::instant())), TileFlowerPot::class);
+		self::register("frosted_ice", fn(BID $id) => new FrostedIce($id, "Frosted Ice", new Info(BreakInfo::pickaxe(2.5))));
+		self::register("furnace", fn(BID $id) => new Furnace($id, "Furnace", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::FURNACE), TileNormalFurnace::class);
+		self::register("blast_furnace", fn(BID $id) => new Furnace($id, "Blast Furnace", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::BLAST_FURNACE), TileBlastFurnace::class);
+		self::register("smoker", fn(BID $id) => new Furnace($id, "Smoker", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::SMOKER), TileSmoker::class);
 
 		$glassBreakInfo = new Info(new BreakInfo(0.3));
-		self::register("glass", new Glass(new BID(Ids::GLASS), "Glass", $glassBreakInfo));
-		self::register("glass_pane", new GlassPane(new BID(Ids::GLASS_PANE), "Glass Pane", $glassBreakInfo));
-		self::register("glowing_obsidian", new GlowingObsidian(new BID(Ids::GLOWING_OBSIDIAN), "Glowing Obsidian", new Info(BreakInfo::pickaxe(10.0, ToolTier::DIAMOND, 50.0))));
-		self::register("glowstone", new Glowstone(new BID(Ids::GLOWSTONE), "Glowstone", new Info(BreakInfo::pickaxe(0.3))));
-		self::register("glow_lichen", new GlowLichen(new BID(Ids::GLOW_LICHEN), "Glow Lichen", new Info(BreakInfo::axe(0.2, null, 0.2))));
-		self::register("gold", new Opaque(new BID(Ids::GOLD), "Gold Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::IRON, 30.0))));
+		self::register("glass", fn(BID $id) => new Glass($id, "Glass", $glassBreakInfo));
+		self::register("glass_pane", fn(BID $id) => new GlassPane($id, "Glass Pane", $glassBreakInfo));
+		self::register("glowing_obsidian", fn(BID $id) => new GlowingObsidian($id, "Glowing Obsidian", new Info(BreakInfo::pickaxe(10.0, ToolTier::DIAMOND, 50.0))));
+		self::register("glowstone", fn(BID $id) => new Glowstone($id, "Glowstone", new Info(BreakInfo::pickaxe(0.3))));
+		self::register("glow_lichen", fn(BID $id) => new GlowLichen($id, "Glow Lichen", new Info(BreakInfo::axe(0.2, null, 0.2))));
+		self::register("gold", fn(BID $id) => new Opaque($id, "Gold Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::IRON, 30.0))));
 
 		$grassBreakInfo = BreakInfo::shovel(0.6);
-		self::register("grass", new Grass(new BID(Ids::GRASS), "Grass", new Info($grassBreakInfo, [Tags::DIRT])));
-		self::register("grass_path", new GrassPath(new BID(Ids::GRASS_PATH), "Grass Path", new Info($grassBreakInfo)));
-		self::register("gravel", new Gravel(new BID(Ids::GRAVEL), "Gravel", new Info(BreakInfo::shovel(0.6))));
+		self::register("grass", fn(BID $id) => new Grass($id, "Grass", new Info($grassBreakInfo, [Tags::DIRT])));
+		self::register("grass_path", fn(BID $id) => new GrassPath($id, "Grass Path", new Info($grassBreakInfo)));
+		self::register("gravel", fn(BID $id) => new Gravel($id, "Gravel", new Info(BreakInfo::shovel(0.6))));
 
 		$hardenedClayBreakInfo = new Info(BreakInfo::pickaxe(1.25, ToolTier::WOOD, 21.0));
-		self::register("hardened_clay", new HardenedClay(new BID(Ids::HARDENED_CLAY), "Hardened Clay", $hardenedClayBreakInfo));
+		self::register("hardened_clay", fn(BID $id) => new HardenedClay($id, "Hardened Clay", $hardenedClayBreakInfo));
 
 		$hardenedGlassBreakInfo = new Info(new BreakInfo(10.0));
-		self::register("hardened_glass", new HardenedGlass(new BID(Ids::HARDENED_GLASS), "Hardened Glass", $hardenedGlassBreakInfo));
-		self::register("hardened_glass_pane", new HardenedGlassPane(new BID(Ids::HARDENED_GLASS_PANE), "Hardened Glass Pane", $hardenedGlassBreakInfo));
-		self::register("hay_bale", new HayBale(new BID(Ids::HAY_BALE), "Hay Bale", new Info(new BreakInfo(0.5))));
-		self::register("hopper", new Hopper(new BID(Ids::HOPPER, TileHopper::class), "Hopper", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 15.0))));
-		self::register("ice", new Ice(new BID(Ids::ICE), "Ice", new Info(BreakInfo::pickaxe(0.5))));
+		self::register("hardened_glass", fn(BID $id) => new HardenedGlass($id, "Hardened Glass", $hardenedGlassBreakInfo));
+		self::register("hardened_glass_pane", fn(BID $id) => new HardenedGlassPane($id, "Hardened Glass Pane", $hardenedGlassBreakInfo));
+		self::register("hay_bale", fn(BID $id) => new HayBale($id, "Hay Bale", new Info(new BreakInfo(0.5))));
+		self::register("hopper", fn(BID $id) => new Hopper($id, "Hopper", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 15.0))), TileHopper::class);
+		self::register("ice", fn(BID $id) => new Ice($id, "Ice", new Info(BreakInfo::pickaxe(0.5))));
 
 		$updateBlockBreakInfo = new Info(new BreakInfo(1.0));
-		self::register("info_update", new Opaque(new BID(Ids::INFO_UPDATE), "update!", $updateBlockBreakInfo));
-		self::register("info_update2", new Opaque(new BID(Ids::INFO_UPDATE2), "ate!upd", $updateBlockBreakInfo));
-		self::register("invisible_bedrock", new Transparent(new BID(Ids::INVISIBLE_BEDROCK), "Invisible Bedrock", new Info(BreakInfo::indestructible())));
+		self::register("info_update", fn(BID $id) => new Opaque($id, "update!", $updateBlockBreakInfo));
+		self::register("info_update2", fn(BID $id) => new Opaque($id, "ate!upd", $updateBlockBreakInfo));
+		self::register("invisible_bedrock", fn(BID $id) => new Transparent($id, "Invisible Bedrock", new Info(BreakInfo::indestructible())));
 
 		$ironBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::STONE, 30.0));
-		self::register("iron", new Opaque(new BID(Ids::IRON), "Iron Block", $ironBreakInfo));
-		self::register("iron_bars", new Thin(new BID(Ids::IRON_BARS), "Iron Bars", $ironBreakInfo));
+		self::register("iron", fn(BID $id) => new Opaque($id, "Iron Block", $ironBreakInfo));
+		self::register("iron_bars", fn(BID $id) => new Thin($id, "Iron Bars", $ironBreakInfo));
 		$ironDoorBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 25.0));
-		self::register("iron_door", new Door(new BID(Ids::IRON_DOOR), "Iron Door", $ironDoorBreakInfo));
-		self::register("iron_trapdoor", new Trapdoor(new BID(Ids::IRON_TRAPDOOR), "Iron Trapdoor", $ironDoorBreakInfo));
+		self::register("iron_door", fn(BID $id) => new Door($id, "Iron Door", $ironDoorBreakInfo));
+		self::register("iron_trapdoor", fn(BID $id) => new Trapdoor($id, "Iron Trapdoor", $ironDoorBreakInfo));
 
 		$itemFrameInfo = new Info(new BreakInfo(0.25));
-		self::register("item_frame", new ItemFrame(new BID(Ids::ITEM_FRAME, TileItemFrame::class), "Item Frame", $itemFrameInfo));
-		self::register("glowing_item_frame", new ItemFrame(new BID(Ids::GLOWING_ITEM_FRAME, TileGlowingItemFrame::class), "Glow Item Frame", $itemFrameInfo));
+		self::register("item_frame", fn(BID $id) => new ItemFrame($id, "Item Frame", $itemFrameInfo), TileItemFrame::class);
+		self::register("glowing_item_frame", fn(BID $id) => new ItemFrame($id, "Glow Item Frame", $itemFrameInfo), TileGlowingItemFrame::class);
 
-		self::register("jukebox", new Jukebox(new BID(Ids::JUKEBOX, TileJukebox::class), "Jukebox", new Info(BreakInfo::axe(0.8)))); //TODO: in PC the hardness is 2.0, not 0.8, unsure if this is a MCPE bug or not
-		self::register("ladder", new Ladder(new BID(Ids::LADDER), "Ladder", new Info(BreakInfo::axe(0.4))));
+		self::register("jukebox", fn(BID $id) => new Jukebox($id, "Jukebox", new Info(BreakInfo::axe(0.8))), TileJukebox::class); //TODO: in PC the hardness is 2.0, not 0.8, unsure if this is a MCPE bug or not
+		self::register("ladder", fn(BID $id) => new Ladder($id, "Ladder", new Info(BreakInfo::axe(0.4))));
 
 		$lanternBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD));
-		self::register("lantern", new Lantern(new BID(Ids::LANTERN), "Lantern", $lanternBreakInfo, 15));
-		self::register("soul_lantern", new Lantern(new BID(Ids::SOUL_LANTERN), "Soul Lantern", $lanternBreakInfo, 10));
-
-		self::register("lapis_lazuli", new Opaque(new BID(Ids::LAPIS_LAZULI), "Lapis Lazuli Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE))));
-		self::register("lava", new Lava(new BID(Ids::LAVA), "Lava", new Info(BreakInfo::indestructible(500.0))));
-		self::register("lectern", new Lectern(new BID(Ids::LECTERN, TileLectern::class), "Lectern", new Info(BreakInfo::axe(2.0))));
-		self::register("lever", new Lever(new BID(Ids::LEVER), "Lever", new Info(new BreakInfo(0.5))));
-		self::register("magma", new Magma(new BID(Ids::MAGMA), "Magma Block", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))));
-		self::register("melon", new Melon(new BID(Ids::MELON), "Melon Block", new Info(BreakInfo::axe(1.0))));
-		self::register("melon_stem", new MelonStem(new BID(Ids::MELON_STEM), "Melon Stem", new Info(BreakInfo::instant())));
-		self::register("monster_spawner", new MonsterSpawner(new BID(Ids::MONSTER_SPAWNER, TileMonsterSpawner::class), "Monster Spawner", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))));
-		self::register("mycelium", new Mycelium(new BID(Ids::MYCELIUM), "Mycelium", new Info(BreakInfo::shovel(0.6), [Tags::DIRT])));
+		self::register("lantern", fn(BID $id) => new Lantern($id, "Lantern", $lanternBreakInfo, 15));
+		self::register("soul_lantern", fn(BID $id) => new Lantern($id, "Soul Lantern", $lanternBreakInfo, 10));
+
+		self::register("lapis_lazuli", fn(BID $id) => new Opaque($id, "Lapis Lazuli Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE))));
+		self::register("lava", fn(BID $id) => new Lava($id, "Lava", new Info(BreakInfo::indestructible(500.0))));
+		self::register("lectern", fn(BID $id) => new Lectern($id, "Lectern", new Info(BreakInfo::axe(2.0))), TileLectern::class);
+		self::register("lever", fn(BID $id) => new Lever($id, "Lever", new Info(new BreakInfo(0.5))));
+		self::register("magma", fn(BID $id) => new Magma($id, "Magma Block", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))));
+		self::register("melon", fn(BID $id) => new Melon($id, "Melon Block", new Info(BreakInfo::axe(1.0))));
+		self::register("melon_stem", fn(BID $id) => new MelonStem($id, "Melon Stem", new Info(BreakInfo::instant())));
+		self::register("monster_spawner", fn(BID $id) => new MonsterSpawner($id, "Monster Spawner", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))), TileMonsterSpawner::class);
+		self::register("mycelium", fn(BID $id) => new Mycelium($id, "Mycelium", new Info(BreakInfo::shovel(0.6), [Tags::DIRT])));
 
 		$netherBrickBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
-		self::register("nether_bricks", new Opaque(new BID(Ids::NETHER_BRICKS), "Nether Bricks", $netherBrickBreakInfo));
-		self::register("red_nether_bricks", new Opaque(new BID(Ids::RED_NETHER_BRICKS), "Red Nether Bricks", $netherBrickBreakInfo));
-		self::register("nether_brick_fence", new Fence(new BID(Ids::NETHER_BRICK_FENCE), "Nether Brick Fence", $netherBrickBreakInfo));
-		self::register("nether_brick_stairs", new Stair(new BID(Ids::NETHER_BRICK_STAIRS), "Nether Brick Stairs", $netherBrickBreakInfo));
-		self::register("red_nether_brick_stairs", new Stair(new BID(Ids::RED_NETHER_BRICK_STAIRS), "Red Nether Brick Stairs", $netherBrickBreakInfo));
-		self::register("chiseled_nether_bricks", new Opaque(new BID(Ids::CHISELED_NETHER_BRICKS), "Chiseled Nether Bricks", $netherBrickBreakInfo));
-		self::register("cracked_nether_bricks", new Opaque(new BID(Ids::CRACKED_NETHER_BRICKS), "Cracked Nether Bricks", $netherBrickBreakInfo));
-
-		self::register("nether_portal", new NetherPortal(new BID(Ids::NETHER_PORTAL), "Nether Portal", new Info(BreakInfo::indestructible(0.0))));
-		self::register("nether_reactor_core", new NetherReactor(new BID(Ids::NETHER_REACTOR_CORE), "Nether Reactor Core", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD))));
-		self::register("nether_wart_block", new Opaque(new BID(Ids::NETHER_WART_BLOCK), "Nether Wart Block", new Info(new BreakInfo(1.0, ToolType::HOE))));
-		self::register("nether_wart", new NetherWartPlant(new BID(Ids::NETHER_WART), "Nether Wart", new Info(BreakInfo::instant())));
-		self::register("netherrack", new Netherrack(new BID(Ids::NETHERRACK), "Netherrack", new Info(BreakInfo::pickaxe(0.4, ToolTier::WOOD))));
-		self::register("note_block", new Note(new BID(Ids::NOTE_BLOCK, TileNote::class), "Note Block", new Info(BreakInfo::axe(0.8))));
-		self::register("obsidian", new Opaque(new BID(Ids::OBSIDIAN), "Obsidian", new Info(BreakInfo::pickaxe(35.0 /* 50 in PC */,  ToolTier::DIAMOND, 6000.0))));
-		self::register("packed_ice", new PackedIce(new BID(Ids::PACKED_ICE), "Packed Ice", new Info(BreakInfo::pickaxe(0.5))));
-		self::register("podzol", new Podzol(new BID(Ids::PODZOL), "Podzol", new Info(BreakInfo::shovel(0.5), [Tags::DIRT])));
-		self::register("potatoes", new Potato(new BID(Ids::POTATOES), "Potato Block", new Info(BreakInfo::instant())));
-		self::register("powered_rail", new PoweredRail(new BID(Ids::POWERED_RAIL), "Powered Rail", $railBreakInfo));
+		self::register("nether_bricks", fn(BID $id) => new Opaque($id, "Nether Bricks", $netherBrickBreakInfo));
+		self::register("red_nether_bricks", fn(BID $id) => new Opaque($id, "Red Nether Bricks", $netherBrickBreakInfo));
+		self::register("nether_brick_fence", fn(BID $id) => new Fence($id, "Nether Brick Fence", $netherBrickBreakInfo));
+		self::register("nether_brick_stairs", fn(BID $id) => new Stair($id, "Nether Brick Stairs", $netherBrickBreakInfo));
+		self::register("red_nether_brick_stairs", fn(BID $id) => new Stair($id, "Red Nether Brick Stairs", $netherBrickBreakInfo));
+		self::register("chiseled_nether_bricks", fn(BID $id) => new Opaque($id, "Chiseled Nether Bricks", $netherBrickBreakInfo));
+		self::register("cracked_nether_bricks", fn(BID $id) => new Opaque($id, "Cracked Nether Bricks", $netherBrickBreakInfo));
+
+		self::register("nether_portal", fn(BID $id) => new NetherPortal($id, "Nether Portal", new Info(BreakInfo::indestructible(0.0))));
+		self::register("nether_reactor_core", fn(BID $id) => new NetherReactor($id, "Nether Reactor Core", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD))));
+		self::register("nether_wart_block", fn(BID $id) => new Opaque($id, "Nether Wart Block", new Info(new BreakInfo(1.0, ToolType::HOE))));
+		self::register("nether_wart", fn(BID $id) => new NetherWartPlant($id, "Nether Wart", new Info(BreakInfo::instant())));
+		self::register("netherrack", fn(BID $id) => new Netherrack($id, "Netherrack", new Info(BreakInfo::pickaxe(0.4, ToolTier::WOOD))));
+		self::register("note_block", fn(BID $id) => new Note($id, "Note Block", new Info(BreakInfo::axe(0.8))), TileNote::class);
+		self::register("obsidian", fn(BID $id) => new Opaque($id, "Obsidian", new Info(BreakInfo::pickaxe(35.0 /* 50 in PC */,  ToolTier::DIAMOND, 6000.0))));
+		self::register("packed_ice", fn(BID $id) => new PackedIce($id, "Packed Ice", new Info(BreakInfo::pickaxe(0.5))));
+		self::register("podzol", fn(BID $id) => new Podzol($id, "Podzol", new Info(BreakInfo::shovel(0.5), [Tags::DIRT])));
+		self::register("potatoes", fn(BID $id) => new Potato($id, "Potato Block", new Info(BreakInfo::instant())));
+		self::register("powered_rail", fn(BID $id) => new PoweredRail($id, "Powered Rail", $railBreakInfo));
 
 		$prismarineBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
-		self::register("prismarine", new Opaque(new BID(Ids::PRISMARINE), "Prismarine", $prismarineBreakInfo));
-		self::register("dark_prismarine", new Opaque(new BID(Ids::DARK_PRISMARINE), "Dark Prismarine", $prismarineBreakInfo));
-		self::register("prismarine_bricks", new Opaque(new BID(Ids::PRISMARINE_BRICKS), "Prismarine Bricks", $prismarineBreakInfo));
-		self::register("prismarine_bricks_stairs", new Stair(new BID(Ids::PRISMARINE_BRICKS_STAIRS), "Prismarine Bricks Stairs", $prismarineBreakInfo));
-		self::register("dark_prismarine_stairs", new Stair(new BID(Ids::DARK_PRISMARINE_STAIRS), "Dark Prismarine Stairs", $prismarineBreakInfo));
-		self::register("prismarine_stairs", new Stair(new BID(Ids::PRISMARINE_STAIRS), "Prismarine Stairs", $prismarineBreakInfo));
+		self::register("prismarine", fn(BID $id) => new Opaque($id, "Prismarine", $prismarineBreakInfo));
+		self::register("dark_prismarine", fn(BID $id) => new Opaque($id, "Dark Prismarine", $prismarineBreakInfo));
+		self::register("prismarine_bricks", fn(BID $id) => new Opaque($id, "Prismarine Bricks", $prismarineBreakInfo));
+		self::register("prismarine_bricks_stairs", fn(BID $id) => new Stair($id, "Prismarine Bricks Stairs", $prismarineBreakInfo));
+		self::register("dark_prismarine_stairs", fn(BID $id) => new Stair($id, "Dark Prismarine Stairs", $prismarineBreakInfo));
+		self::register("prismarine_stairs", fn(BID $id) => new Stair($id, "Prismarine Stairs", $prismarineBreakInfo));
 
 		$pumpkinBreakInfo = new Info(BreakInfo::axe(1.0));
-		self::register("pumpkin", new Pumpkin(new BID(Ids::PUMPKIN), "Pumpkin", $pumpkinBreakInfo));
-		self::register("carved_pumpkin", new CarvedPumpkin(new BID(Ids::CARVED_PUMPKIN), "Carved Pumpkin", new Info(BreakInfo::axe(1.0), enchantmentTags: [EnchantmentTags::MASK])));
-		self::register("lit_pumpkin", new LitPumpkin(new BID(Ids::LIT_PUMPKIN), "Jack o'Lantern", $pumpkinBreakInfo));
+		self::register("pumpkin", fn(BID $id) => new Pumpkin($id, "Pumpkin", $pumpkinBreakInfo));
+		self::register("carved_pumpkin", fn(BID $id) => new CarvedPumpkin($id, "Carved Pumpkin", new Info(BreakInfo::axe(1.0), enchantmentTags: [EnchantmentTags::MASK])));
+		self::register("lit_pumpkin", fn(BID $id) => new LitPumpkin($id, "Jack o'Lantern", $pumpkinBreakInfo));
 
-		self::register("pumpkin_stem", new PumpkinStem(new BID(Ids::PUMPKIN_STEM), "Pumpkin Stem", new Info(BreakInfo::instant())));
+		self::register("pumpkin_stem", fn(BID $id) => new PumpkinStem($id, "Pumpkin Stem", new Info(BreakInfo::instant())));
 
 		$purpurBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
-		self::register("purpur", new Opaque(new BID(Ids::PURPUR), "Purpur Block", $purpurBreakInfo));
-		self::register("purpur_pillar", new SimplePillar(new BID(Ids::PURPUR_PILLAR), "Purpur Pillar", $purpurBreakInfo));
-		self::register("purpur_stairs", new Stair(new BID(Ids::PURPUR_STAIRS), "Purpur Stairs", $purpurBreakInfo));
+		self::register("purpur", fn(BID $id) => new Opaque($id, "Purpur Block", $purpurBreakInfo));
+		self::register("purpur_pillar", fn(BID $id) => new SimplePillar($id, "Purpur Pillar", $purpurBreakInfo));
+		self::register("purpur_stairs", fn(BID $id) => new Stair($id, "Purpur Stairs", $purpurBreakInfo));
 
 		$quartzBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD));
-		self::register("quartz", new Opaque(new BID(Ids::QUARTZ), "Quartz Block", $quartzBreakInfo));
-		self::register("chiseled_quartz", new SimplePillar(new BID(Ids::CHISELED_QUARTZ), "Chiseled Quartz Block", $quartzBreakInfo));
-		self::register("quartz_pillar", new SimplePillar(new BID(Ids::QUARTZ_PILLAR), "Quartz Pillar", $quartzBreakInfo));
-		self::register("smooth_quartz", new Opaque(new BID(Ids::SMOOTH_QUARTZ), "Smooth Quartz Block", $quartzBreakInfo));
-		self::register("quartz_bricks", new Opaque(new BID(Ids::QUARTZ_BRICKS), "Quartz Bricks", $quartzBreakInfo));
-
-		self::register("quartz_stairs", new Stair(new BID(Ids::QUARTZ_STAIRS), "Quartz Stairs", $quartzBreakInfo));
-		self::register("smooth_quartz_stairs", new Stair(new BID(Ids::SMOOTH_QUARTZ_STAIRS), "Smooth Quartz Stairs", $quartzBreakInfo));
-
-		self::register("rail", new Rail(new BID(Ids::RAIL), "Rail", $railBreakInfo));
-		self::register("red_mushroom", new RedMushroom(new BID(Ids::RED_MUSHROOM), "Red Mushroom", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
-		self::register("redstone", new Redstone(new BID(Ids::REDSTONE), "Redstone Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 30.0))));
-		self::register("redstone_comparator", new RedstoneComparator(new BID(Ids::REDSTONE_COMPARATOR, TileComparator::class), "Redstone Comparator", new Info(BreakInfo::instant())));
-		self::register("redstone_lamp", new RedstoneLamp(new BID(Ids::REDSTONE_LAMP), "Redstone Lamp", new Info(new BreakInfo(0.3))));
-		self::register("redstone_repeater", new RedstoneRepeater(new BID(Ids::REDSTONE_REPEATER), "Redstone Repeater", new Info(BreakInfo::instant())));
-		self::register("redstone_torch", new RedstoneTorch(new BID(Ids::REDSTONE_TORCH), "Redstone Torch", new Info(BreakInfo::instant())));
-		self::register("redstone_wire", new RedstoneWire(new BID(Ids::REDSTONE_WIRE), "Redstone", new Info(BreakInfo::instant())));
-		self::register("reserved6", new Reserved6(new BID(Ids::RESERVED6), "reserved6", new Info(BreakInfo::instant())));
+		self::register("quartz", fn(BID $id) => new Opaque($id, "Quartz Block", $quartzBreakInfo));
+		self::register("chiseled_quartz", fn(BID $id) => new SimplePillar($id, "Chiseled Quartz Block", $quartzBreakInfo));
+		self::register("quartz_pillar", fn(BID $id) => new SimplePillar($id, "Quartz Pillar", $quartzBreakInfo));
+		self::register("smooth_quartz", fn(BID $id) => new Opaque($id, "Smooth Quartz Block", $quartzBreakInfo));
+		self::register("quartz_bricks", fn(BID $id) => new Opaque($id, "Quartz Bricks", $quartzBreakInfo));
+
+		self::register("quartz_stairs", fn(BID $id) => new Stair($id, "Quartz Stairs", $quartzBreakInfo));
+		self::register("smooth_quartz_stairs", fn(BID $id) => new Stair($id, "Smooth Quartz Stairs", $quartzBreakInfo));
+
+		self::register("rail", fn(BID $id) => new Rail($id, "Rail", $railBreakInfo));
+		self::register("red_mushroom", fn(BID $id) => new RedMushroom($id, "Red Mushroom", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
+		self::register("redstone", fn(BID $id) => new Redstone($id, "Redstone Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 30.0))));
+		self::register("redstone_comparator", fn(BID $id) => new RedstoneComparator($id, "Redstone Comparator", new Info(BreakInfo::instant())), TileComparator::class);
+		self::register("redstone_lamp", fn(BID $id) => new RedstoneLamp($id, "Redstone Lamp", new Info(new BreakInfo(0.3))));
+		self::register("redstone_repeater", fn(BID $id) => new RedstoneRepeater($id, "Redstone Repeater", new Info(BreakInfo::instant())));
+		self::register("redstone_torch", fn(BID $id) => new RedstoneTorch($id, "Redstone Torch", new Info(BreakInfo::instant())));
+		self::register("redstone_wire", fn(BID $id) => new RedstoneWire($id, "Redstone", new Info(BreakInfo::instant())));
+		self::register("reserved6", fn(BID $id) => new Reserved6($id, "reserved6", new Info(BreakInfo::instant())));
 
 		$sandTypeInfo = new Info(BreakInfo::shovel(0.5), [Tags::SAND]);
-		self::register("sand", new Sand(new BID(Ids::SAND), "Sand", $sandTypeInfo));
-		self::register("red_sand", new Sand(new BID(Ids::RED_SAND), "Red Sand", $sandTypeInfo));
-
-		self::register("sea_lantern", new SeaLantern(new BID(Ids::SEA_LANTERN), "Sea Lantern", new Info(new BreakInfo(0.3))));
-		self::register("sea_pickle", new SeaPickle(new BID(Ids::SEA_PICKLE), "Sea Pickle", new Info(BreakInfo::instant())));
-		self::register("mob_head", new MobHead(new BID(Ids::MOB_HEAD, TileMobHead::class), "Mob Head", new Info(new BreakInfo(1.0), enchantmentTags: [EnchantmentTags::MASK])));
-		self::register("slime", new Slime(new BID(Ids::SLIME), "Slime Block", new Info(BreakInfo::instant())));
-		self::register("snow", new Snow(new BID(Ids::SNOW), "Snow Block", new Info(BreakInfo::shovel(0.2, ToolTier::WOOD))));
-		self::register("snow_layer", new SnowLayer(new BID(Ids::SNOW_LAYER), "Snow Layer", new Info(BreakInfo::shovel(0.1, ToolTier::WOOD))));
-		self::register("soul_sand", new SoulSand(new BID(Ids::SOUL_SAND), "Soul Sand", new Info(BreakInfo::shovel(0.5))));
-		self::register("sponge", new Sponge(new BID(Ids::SPONGE), "Sponge", new Info(new BreakInfo(0.6, ToolType::HOE))));
+		self::register("sand", fn(BID $id) => new Sand($id, "Sand", $sandTypeInfo));
+		self::register("red_sand", fn(BID $id) => new Sand($id, "Red Sand", $sandTypeInfo));
+
+		self::register("sea_lantern", fn(BID $id) => new SeaLantern($id, "Sea Lantern", new Info(new BreakInfo(0.3))));
+		self::register("sea_pickle", fn(BID $id) => new SeaPickle($id, "Sea Pickle", new Info(BreakInfo::instant())));
+		self::register("mob_head", fn(BID $id) => new MobHead($id, "Mob Head", new Info(new BreakInfo(1.0), enchantmentTags: [EnchantmentTags::MASK])), TileMobHead::class);
+		self::register("slime", fn(BID $id) => new Slime($id, "Slime Block", new Info(BreakInfo::instant())));
+		self::register("snow", fn(BID $id) => new Snow($id, "Snow Block", new Info(BreakInfo::shovel(0.2, ToolTier::WOOD))));
+		self::register("snow_layer", fn(BID $id) => new SnowLayer($id, "Snow Layer", new Info(BreakInfo::shovel(0.1, ToolTier::WOOD))));
+		self::register("soul_sand", fn(BID $id) => new SoulSand($id, "Soul Sand", new Info(BreakInfo::shovel(0.5))));
+		self::register("sponge", fn(BID $id) => new Sponge($id, "Sponge", new Info(new BreakInfo(0.6, ToolType::HOE))));
 		$shulkerBoxBreakInfo = new Info(BreakInfo::pickaxe(2));
-		self::register("shulker_box", new ShulkerBox(new BID(Ids::SHULKER_BOX, TileShulkerBox::class), "Shulker Box", $shulkerBoxBreakInfo));
+		self::register("shulker_box", fn(BID $id) => new ShulkerBox($id, "Shulker Box", $shulkerBoxBreakInfo), TileShulkerBox::class);
 
 		$stoneBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
-		self::register(
+		$stone = self::register(
 			"stone",
-			$stone = new class(new BID(Ids::STONE), "Stone", $stoneBreakInfo) extends Opaque{
+			fn(BID $id) => new class($id, "Stone", $stoneBreakInfo) extends Opaque{
 				public function getDropsForCompatibleTool(Item $item) : array{
 					return [VanillaBlocks::COBBLESTONE()->asItem()];
 				}
@@ -1066,110 +1090,110 @@ public function isAffectedBySilkTouch() : bool{
 				}
 			}
 		);
-		self::register("andesite", new Opaque(new BID(Ids::ANDESITE), "Andesite", $stoneBreakInfo));
-		self::register("diorite", new Opaque(new BID(Ids::DIORITE), "Diorite", $stoneBreakInfo));
-		self::register("granite", new Opaque(new BID(Ids::GRANITE), "Granite", $stoneBreakInfo));
-		self::register("polished_andesite", new Opaque(new BID(Ids::POLISHED_ANDESITE), "Polished Andesite", $stoneBreakInfo));
-		self::register("polished_diorite", new Opaque(new BID(Ids::POLISHED_DIORITE), "Polished Diorite", $stoneBreakInfo));
-		self::register("polished_granite", new Opaque(new BID(Ids::POLISHED_GRANITE), "Polished Granite", $stoneBreakInfo));
-
-		self::register("stone_bricks", $stoneBrick = new Opaque(new BID(Ids::STONE_BRICKS), "Stone Bricks", $stoneBreakInfo));
-		self::register("mossy_stone_bricks", $mossyStoneBrick = new Opaque(new BID(Ids::MOSSY_STONE_BRICKS), "Mossy Stone Bricks", $stoneBreakInfo));
-		self::register("cracked_stone_bricks", $crackedStoneBrick = new Opaque(new BID(Ids::CRACKED_STONE_BRICKS), "Cracked Stone Bricks", $stoneBreakInfo));
-		self::register("chiseled_stone_bricks", $chiseledStoneBrick = new Opaque(new BID(Ids::CHISELED_STONE_BRICKS), "Chiseled Stone Bricks", $stoneBreakInfo));
+		self::register("andesite", fn(BID $id) => new Opaque($id, "Andesite", $stoneBreakInfo));
+		self::register("diorite", fn(BID $id) => new Opaque($id, "Diorite", $stoneBreakInfo));
+		self::register("granite", fn(BID $id) => new Opaque($id, "Granite", $stoneBreakInfo));
+		self::register("polished_andesite", fn(BID $id) => new Opaque($id, "Polished Andesite", $stoneBreakInfo));
+		self::register("polished_diorite", fn(BID $id) => new Opaque($id, "Polished Diorite", $stoneBreakInfo));
+		self::register("polished_granite", fn(BID $id) => new Opaque($id, "Polished Granite", $stoneBreakInfo));
+
+		$stoneBrick = self::register("stone_bricks", fn(BID $id) => new Opaque($id, "Stone Bricks", $stoneBreakInfo));
+		$mossyStoneBrick = self::register("mossy_stone_bricks", fn(BID $id) => new Opaque($id, "Mossy Stone Bricks", $stoneBreakInfo));
+		$crackedStoneBrick = self::register("cracked_stone_bricks", fn(BID $id) => new Opaque($id, "Cracked Stone Bricks", $stoneBreakInfo));
+		$chiseledStoneBrick = self::register("chiseled_stone_bricks", fn(BID $id) => new Opaque($id, "Chiseled Stone Bricks", $stoneBreakInfo));
 
 		$infestedStoneBreakInfo = new Info(BreakInfo::pickaxe(0.75));
-		self::register("infested_stone", new InfestedStone(new BID(Ids::INFESTED_STONE), "Infested Stone", $infestedStoneBreakInfo, $stone));
-		self::register("infested_stone_brick", new InfestedStone(new BID(Ids::INFESTED_STONE_BRICK), "Infested Stone Brick", $infestedStoneBreakInfo, $stoneBrick));
-		self::register("infested_cobblestone", new InfestedStone(new BID(Ids::INFESTED_COBBLESTONE), "Infested Cobblestone", $infestedStoneBreakInfo, $cobblestone));
-		self::register("infested_mossy_stone_brick", new InfestedStone(new BID(Ids::INFESTED_MOSSY_STONE_BRICK), "Infested Mossy Stone Brick", $infestedStoneBreakInfo, $mossyStoneBrick));
-		self::register("infested_cracked_stone_brick", new InfestedStone(new BID(Ids::INFESTED_CRACKED_STONE_BRICK), "Infested Cracked Stone Brick", $infestedStoneBreakInfo, $crackedStoneBrick));
-		self::register("infested_chiseled_stone_brick", new InfestedStone(new BID(Ids::INFESTED_CHISELED_STONE_BRICK), "Infested Chiseled Stone Brick", $infestedStoneBreakInfo, $chiseledStoneBrick));
-
-		self::register("stone_stairs", new Stair(new BID(Ids::STONE_STAIRS), "Stone Stairs", $stoneBreakInfo));
-		self::register("smooth_stone", new Opaque(new BID(Ids::SMOOTH_STONE), "Smooth Stone", $stoneBreakInfo));
-		self::register("andesite_stairs", new Stair(new BID(Ids::ANDESITE_STAIRS), "Andesite Stairs", $stoneBreakInfo));
-		self::register("diorite_stairs", new Stair(new BID(Ids::DIORITE_STAIRS), "Diorite Stairs", $stoneBreakInfo));
-		self::register("granite_stairs", new Stair(new BID(Ids::GRANITE_STAIRS), "Granite Stairs", $stoneBreakInfo));
-		self::register("polished_andesite_stairs", new Stair(new BID(Ids::POLISHED_ANDESITE_STAIRS), "Polished Andesite Stairs", $stoneBreakInfo));
-		self::register("polished_diorite_stairs", new Stair(new BID(Ids::POLISHED_DIORITE_STAIRS), "Polished Diorite Stairs", $stoneBreakInfo));
-		self::register("polished_granite_stairs", new Stair(new BID(Ids::POLISHED_GRANITE_STAIRS), "Polished Granite Stairs", $stoneBreakInfo));
-		self::register("stone_brick_stairs", new Stair(new BID(Ids::STONE_BRICK_STAIRS), "Stone Brick Stairs", $stoneBreakInfo));
-		self::register("mossy_stone_brick_stairs", new Stair(new BID(Ids::MOSSY_STONE_BRICK_STAIRS), "Mossy Stone Brick Stairs", $stoneBreakInfo));
-		self::register("stone_button", new StoneButton(new BID(Ids::STONE_BUTTON), "Stone Button", new Info(BreakInfo::pickaxe(0.5))));
-		self::register("stonecutter", new Stonecutter(new BID(Ids::STONECUTTER), "Stonecutter", new Info(BreakInfo::pickaxe(3.5))));
-		self::register("stone_pressure_plate", new StonePressurePlate(new BID(Ids::STONE_PRESSURE_PLATE), "Stone Pressure Plate", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))));
+		self::register("infested_stone", fn(BID $id) => new InfestedStone($id, "Infested Stone", $infestedStoneBreakInfo, $stone));
+		self::register("infested_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Stone Brick", $infestedStoneBreakInfo, $stoneBrick));
+		self::register("infested_cobblestone", fn(BID $id) => new InfestedStone($id, "Infested Cobblestone", $infestedStoneBreakInfo, $cobblestone));
+		self::register("infested_mossy_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Mossy Stone Brick", $infestedStoneBreakInfo, $mossyStoneBrick));
+		self::register("infested_cracked_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Cracked Stone Brick", $infestedStoneBreakInfo, $crackedStoneBrick));
+		self::register("infested_chiseled_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Chiseled Stone Brick", $infestedStoneBreakInfo, $chiseledStoneBrick));
+
+		self::register("stone_stairs", fn(BID $id) => new Stair($id, "Stone Stairs", $stoneBreakInfo));
+		self::register("smooth_stone", fn(BID $id) => new Opaque($id, "Smooth Stone", $stoneBreakInfo));
+		self::register("andesite_stairs", fn(BID $id) => new Stair($id, "Andesite Stairs", $stoneBreakInfo));
+		self::register("diorite_stairs", fn(BID $id) => new Stair($id, "Diorite Stairs", $stoneBreakInfo));
+		self::register("granite_stairs", fn(BID $id) => new Stair($id, "Granite Stairs", $stoneBreakInfo));
+		self::register("polished_andesite_stairs", fn(BID $id) => new Stair($id, "Polished Andesite Stairs", $stoneBreakInfo));
+		self::register("polished_diorite_stairs", fn(BID $id) => new Stair($id, "Polished Diorite Stairs", $stoneBreakInfo));
+		self::register("polished_granite_stairs", fn(BID $id) => new Stair($id, "Polished Granite Stairs", $stoneBreakInfo));
+		self::register("stone_brick_stairs", fn(BID $id) => new Stair($id, "Stone Brick Stairs", $stoneBreakInfo));
+		self::register("mossy_stone_brick_stairs", fn(BID $id) => new Stair($id, "Mossy Stone Brick Stairs", $stoneBreakInfo));
+		self::register("stone_button", fn(BID $id) => new StoneButton($id, "Stone Button", new Info(BreakInfo::pickaxe(0.5))));
+		self::register("stonecutter", fn(BID $id) => new Stonecutter($id, "Stonecutter", new Info(BreakInfo::pickaxe(3.5))));
+		self::register("stone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, "Stone Pressure Plate", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))));
 
 		//TODO: in the future this won't be the same for all the types
 		$stoneSlabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
 
-		self::register("brick_slab", new Slab(new BID(Ids::BRICK_SLAB), "Brick", $stoneSlabBreakInfo));
-		self::register("cobblestone_slab", new Slab(new BID(Ids::COBBLESTONE_SLAB), "Cobblestone", $stoneSlabBreakInfo));
-		self::register("fake_wooden_slab", new Slab(new BID(Ids::FAKE_WOODEN_SLAB), "Fake Wooden", $stoneSlabBreakInfo));
-		self::register("nether_brick_slab", new Slab(new BID(Ids::NETHER_BRICK_SLAB), "Nether Brick", $stoneSlabBreakInfo));
-		self::register("quartz_slab", new Slab(new BID(Ids::QUARTZ_SLAB), "Quartz", $stoneSlabBreakInfo));
-		self::register("sandstone_slab", new Slab(new BID(Ids::SANDSTONE_SLAB), "Sandstone", $stoneSlabBreakInfo));
-		self::register("smooth_stone_slab", new Slab(new BID(Ids::SMOOTH_STONE_SLAB), "Smooth Stone", $stoneSlabBreakInfo));
-		self::register("stone_brick_slab", new Slab(new BID(Ids::STONE_BRICK_SLAB), "Stone Brick", $stoneSlabBreakInfo));
-		self::register("dark_prismarine_slab", new Slab(new BID(Ids::DARK_PRISMARINE_SLAB), "Dark Prismarine", $stoneSlabBreakInfo));
-		self::register("mossy_cobblestone_slab", new Slab(new BID(Ids::MOSSY_COBBLESTONE_SLAB), "Mossy Cobblestone", $stoneSlabBreakInfo));
-		self::register("prismarine_slab", new Slab(new BID(Ids::PRISMARINE_SLAB), "Prismarine", $stoneSlabBreakInfo));
-		self::register("prismarine_bricks_slab", new Slab(new BID(Ids::PRISMARINE_BRICKS_SLAB), "Prismarine Bricks", $stoneSlabBreakInfo));
-		self::register("purpur_slab", new Slab(new BID(Ids::PURPUR_SLAB), "Purpur", $stoneSlabBreakInfo));
-		self::register("red_nether_brick_slab", new Slab(new BID(Ids::RED_NETHER_BRICK_SLAB), "Red Nether Brick", $stoneSlabBreakInfo));
-		self::register("red_sandstone_slab", new Slab(new BID(Ids::RED_SANDSTONE_SLAB), "Red Sandstone", $stoneSlabBreakInfo));
-		self::register("smooth_sandstone_slab", new Slab(new BID(Ids::SMOOTH_SANDSTONE_SLAB), "Smooth Sandstone", $stoneSlabBreakInfo));
-		self::register("andesite_slab", new Slab(new BID(Ids::ANDESITE_SLAB), "Andesite", $stoneSlabBreakInfo));
-		self::register("diorite_slab", new Slab(new BID(Ids::DIORITE_SLAB), "Diorite", $stoneSlabBreakInfo));
-		self::register("end_stone_brick_slab", new Slab(new BID(Ids::END_STONE_BRICK_SLAB), "End Stone Brick", $stoneSlabBreakInfo));
-		self::register("granite_slab", new Slab(new BID(Ids::GRANITE_SLAB), "Granite", $stoneSlabBreakInfo));
-		self::register("polished_andesite_slab", new Slab(new BID(Ids::POLISHED_ANDESITE_SLAB), "Polished Andesite", $stoneSlabBreakInfo));
-		self::register("polished_diorite_slab", new Slab(new BID(Ids::POLISHED_DIORITE_SLAB), "Polished Diorite", $stoneSlabBreakInfo));
-		self::register("polished_granite_slab", new Slab(new BID(Ids::POLISHED_GRANITE_SLAB), "Polished Granite", $stoneSlabBreakInfo));
-		self::register("smooth_red_sandstone_slab", new Slab(new BID(Ids::SMOOTH_RED_SANDSTONE_SLAB), "Smooth Red Sandstone", $stoneSlabBreakInfo));
-		self::register("cut_red_sandstone_slab", new Slab(new BID(Ids::CUT_RED_SANDSTONE_SLAB), "Cut Red Sandstone", $stoneSlabBreakInfo));
-		self::register("cut_sandstone_slab", new Slab(new BID(Ids::CUT_SANDSTONE_SLAB), "Cut Sandstone", $stoneSlabBreakInfo));
-		self::register("mossy_stone_brick_slab", new Slab(new BID(Ids::MOSSY_STONE_BRICK_SLAB), "Mossy Stone Brick", $stoneSlabBreakInfo));
-		self::register("smooth_quartz_slab", new Slab(new BID(Ids::SMOOTH_QUARTZ_SLAB), "Smooth Quartz", $stoneSlabBreakInfo));
-		self::register("stone_slab", new Slab(new BID(Ids::STONE_SLAB), "Stone", $stoneSlabBreakInfo));
-
-		self::register("legacy_stonecutter", new Opaque(new BID(Ids::LEGACY_STONECUTTER), "Legacy Stonecutter", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD))));
-		self::register("sugarcane", new Sugarcane(new BID(Ids::SUGARCANE), "Sugarcane", new Info(BreakInfo::instant())));
-		self::register("sweet_berry_bush", new SweetBerryBush(new BID(Ids::SWEET_BERRY_BUSH), "Sweet Berry Bush", new Info(BreakInfo::instant())));
-		self::register("tnt", new TNT(new BID(Ids::TNT), "TNT", new Info(BreakInfo::instant())));
-		self::register("fern", new TallGrass(new BID(Ids::FERN), "Fern", new Info(BreakInfo::instant(ToolType::SHEARS, 1), [Tags::POTTABLE_PLANTS])));
-		self::register("tall_grass", new TallGrass(new BID(Ids::TALL_GRASS), "Tall Grass", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
-
-		self::register("blue_torch", new Torch(new BID(Ids::BLUE_TORCH), "Blue Torch", new Info(BreakInfo::instant())));
-		self::register("purple_torch", new Torch(new BID(Ids::PURPLE_TORCH), "Purple Torch", new Info(BreakInfo::instant())));
-		self::register("red_torch", new Torch(new BID(Ids::RED_TORCH), "Red Torch", new Info(BreakInfo::instant())));
-		self::register("green_torch", new Torch(new BID(Ids::GREEN_TORCH), "Green Torch", new Info(BreakInfo::instant())));
-		self::register("torch", new Torch(new BID(Ids::TORCH), "Torch", new Info(BreakInfo::instant())));
-
-		self::register("trapped_chest", new TrappedChest(new BID(Ids::TRAPPED_CHEST, TileChest::class), "Trapped Chest", $chestBreakInfo));
-		self::register("tripwire", new Tripwire(new BID(Ids::TRIPWIRE), "Tripwire", new Info(BreakInfo::instant())));
-		self::register("tripwire_hook", new TripwireHook(new BID(Ids::TRIPWIRE_HOOK), "Tripwire Hook", new Info(BreakInfo::instant())));
-		self::register("underwater_torch", new UnderwaterTorch(new BID(Ids::UNDERWATER_TORCH), "Underwater Torch", new Info(BreakInfo::instant())));
-		self::register("vines", new Vine(new BID(Ids::VINES), "Vines", new Info(BreakInfo::axe(0.2))));
-		self::register("water", new Water(new BID(Ids::WATER), "Water", new Info(BreakInfo::indestructible(500.0))));
-		self::register("lily_pad", new WaterLily(new BID(Ids::LILY_PAD), "Lily Pad", new Info(BreakInfo::instant())));
+		self::register("brick_slab", fn(BID $id) => new Slab($id, "Brick", $stoneSlabBreakInfo));
+		self::register("cobblestone_slab", fn(BID $id) => new Slab($id, "Cobblestone", $stoneSlabBreakInfo));
+		self::register("fake_wooden_slab", fn(BID $id) => new Slab($id, "Fake Wooden", $stoneSlabBreakInfo));
+		self::register("nether_brick_slab", fn(BID $id) => new Slab($id, "Nether Brick", $stoneSlabBreakInfo));
+		self::register("quartz_slab", fn(BID $id) => new Slab($id, "Quartz", $stoneSlabBreakInfo));
+		self::register("sandstone_slab", fn(BID $id) => new Slab($id, "Sandstone", $stoneSlabBreakInfo));
+		self::register("smooth_stone_slab", fn(BID $id) => new Slab($id, "Smooth Stone", $stoneSlabBreakInfo));
+		self::register("stone_brick_slab", fn(BID $id) => new Slab($id, "Stone Brick", $stoneSlabBreakInfo));
+		self::register("dark_prismarine_slab", fn(BID $id) => new Slab($id, "Dark Prismarine", $stoneSlabBreakInfo));
+		self::register("mossy_cobblestone_slab", fn(BID $id) => new Slab($id, "Mossy Cobblestone", $stoneSlabBreakInfo));
+		self::register("prismarine_slab", fn(BID $id) => new Slab($id, "Prismarine", $stoneSlabBreakInfo));
+		self::register("prismarine_bricks_slab", fn(BID $id) => new Slab($id, "Prismarine Bricks", $stoneSlabBreakInfo));
+		self::register("purpur_slab", fn(BID $id) => new Slab($id, "Purpur", $stoneSlabBreakInfo));
+		self::register("red_nether_brick_slab", fn(BID $id) => new Slab($id, "Red Nether Brick", $stoneSlabBreakInfo));
+		self::register("red_sandstone_slab", fn(BID $id) => new Slab($id, "Red Sandstone", $stoneSlabBreakInfo));
+		self::register("smooth_sandstone_slab", fn(BID $id) => new Slab($id, "Smooth Sandstone", $stoneSlabBreakInfo));
+		self::register("andesite_slab", fn(BID $id) => new Slab($id, "Andesite", $stoneSlabBreakInfo));
+		self::register("diorite_slab", fn(BID $id) => new Slab($id, "Diorite", $stoneSlabBreakInfo));
+		self::register("end_stone_brick_slab", fn(BID $id) => new Slab($id, "End Stone Brick", $stoneSlabBreakInfo));
+		self::register("granite_slab", fn(BID $id) => new Slab($id, "Granite", $stoneSlabBreakInfo));
+		self::register("polished_andesite_slab", fn(BID $id) => new Slab($id, "Polished Andesite", $stoneSlabBreakInfo));
+		self::register("polished_diorite_slab", fn(BID $id) => new Slab($id, "Polished Diorite", $stoneSlabBreakInfo));
+		self::register("polished_granite_slab", fn(BID $id) => new Slab($id, "Polished Granite", $stoneSlabBreakInfo));
+		self::register("smooth_red_sandstone_slab", fn(BID $id) => new Slab($id, "Smooth Red Sandstone", $stoneSlabBreakInfo));
+		self::register("cut_red_sandstone_slab", fn(BID $id) => new Slab($id, "Cut Red Sandstone", $stoneSlabBreakInfo));
+		self::register("cut_sandstone_slab", fn(BID $id) => new Slab($id, "Cut Sandstone", $stoneSlabBreakInfo));
+		self::register("mossy_stone_brick_slab", fn(BID $id) => new Slab($id, "Mossy Stone Brick", $stoneSlabBreakInfo));
+		self::register("smooth_quartz_slab", fn(BID $id) => new Slab($id, "Smooth Quartz", $stoneSlabBreakInfo));
+		self::register("stone_slab", fn(BID $id) => new Slab($id, "Stone", $stoneSlabBreakInfo));
+
+		self::register("legacy_stonecutter", fn(BID $id) => new Opaque($id, "Legacy Stonecutter", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD))));
+		self::register("sugarcane", fn(BID $id) => new Sugarcane($id, "Sugarcane", new Info(BreakInfo::instant())));
+		self::register("sweet_berry_bush", fn(BID $id) => new SweetBerryBush($id, "Sweet Berry Bush", new Info(BreakInfo::instant())));
+		self::register("tnt", fn(BID $id) => new TNT($id, "TNT", new Info(BreakInfo::instant())));
+		self::register("fern", fn(BID $id) => new TallGrass($id, "Fern", new Info(BreakInfo::instant(ToolType::SHEARS, 1), [Tags::POTTABLE_PLANTS])));
+		self::register("tall_grass", fn(BID $id) => new TallGrass($id, "Tall Grass", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
+
+		self::register("blue_torch", fn(BID $id) => new Torch($id, "Blue Torch", new Info(BreakInfo::instant())));
+		self::register("purple_torch", fn(BID $id) => new Torch($id, "Purple Torch", new Info(BreakInfo::instant())));
+		self::register("red_torch", fn(BID $id) => new Torch($id, "Red Torch", new Info(BreakInfo::instant())));
+		self::register("green_torch", fn(BID $id) => new Torch($id, "Green Torch", new Info(BreakInfo::instant())));
+		self::register("torch", fn(BID $id) => new Torch($id, "Torch", new Info(BreakInfo::instant())));
+
+		self::register("trapped_chest", fn(BID $id) => new TrappedChest($id, "Trapped Chest", $chestBreakInfo), TileChest::class);
+		self::register("tripwire", fn(BID $id) => new Tripwire($id, "Tripwire", new Info(BreakInfo::instant())));
+		self::register("tripwire_hook", fn(BID $id) => new TripwireHook($id, "Tripwire Hook", new Info(BreakInfo::instant())));
+		self::register("underwater_torch", fn(BID $id) => new UnderwaterTorch($id, "Underwater Torch", new Info(BreakInfo::instant())));
+		self::register("vines", fn(BID $id) => new Vine($id, "Vines", new Info(BreakInfo::axe(0.2))));
+		self::register("water", fn(BID $id) => new Water($id, "Water", new Info(BreakInfo::indestructible(500.0))));
+		self::register("lily_pad", fn(BID $id) => new WaterLily($id, "Lily Pad", new Info(BreakInfo::instant())));
 
 		$weightedPressurePlateBreakInfo = new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD));
-		self::register("weighted_pressure_plate_heavy", new WeightedPressurePlateHeavy(
-			new BID(Ids::WEIGHTED_PRESSURE_PLATE_HEAVY),
+		self::register("weighted_pressure_plate_heavy", fn(BID $id) => new WeightedPressurePlateHeavy(
+			$id,
 			"Weighted Pressure Plate Heavy",
 			$weightedPressurePlateBreakInfo,
 			deactivationDelayTicks: 10,
 			signalStrengthFactor: 0.1
 		));
-		self::register("weighted_pressure_plate_light", new WeightedPressurePlateLight(
-			new BID(Ids::WEIGHTED_PRESSURE_PLATE_LIGHT),
+		self::register("weighted_pressure_plate_light", fn(BID $id) => new WeightedPressurePlateLight(
+			$id,
 			"Weighted Pressure Plate Light",
 			$weightedPressurePlateBreakInfo,
 			deactivationDelayTicks: 10,
 			signalStrengthFactor: 1.0
 		));
-		self::register("wheat", new Wheat(new BID(Ids::WHEAT), "Wheat Block", new Info(BreakInfo::instant())));
+		self::register("wheat", fn(BID $id) => new Wheat($id, "Wheat Block", new Info(BreakInfo::instant())));
 
 		$leavesBreakInfo = new Info(new class(0.2, ToolType::HOE) extends BreakInfo{
 			public function getBreakTime(Item $item) : float{
@@ -1183,39 +1207,39 @@ public function getBreakTime(Item $item) : float{
 
 		foreach(SaplingType::cases() as $saplingType){
 			$name = $saplingType->getDisplayName();
-			self::register(strtolower($saplingType->name) . "_sapling", new Sapling(WoodLikeBlockIdHelper::getSaplingIdentifier($saplingType), $name . " Sapling", $saplingTypeInfo, $saplingType));
+			self::register(strtolower($saplingType->name) . "_sapling", fn(BID $id) => new Sapling($id, $name . " Sapling", $saplingTypeInfo, $saplingType));
 		}
 		foreach(LeavesType::cases() as $leavesType){
 			$name = $leavesType->getDisplayName();
-			self::register(strtolower($leavesType->name) . "_leaves", new Leaves(WoodLikeBlockIdHelper::getLeavesIdentifier($leavesType), $name . " Leaves", $leavesBreakInfo, $leavesType));
+			self::register(strtolower($leavesType->name) . "_leaves", fn(BID $id) => new Leaves($id, $name . " Leaves", $leavesBreakInfo, $leavesType));
 		}
 
 		$sandstoneBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD));
-		self::register("red_sandstone_stairs", new Stair(new BID(Ids::RED_SANDSTONE_STAIRS), "Red Sandstone Stairs", $sandstoneBreakInfo));
-		self::register("smooth_red_sandstone_stairs", new Stair(new BID(Ids::SMOOTH_RED_SANDSTONE_STAIRS), "Smooth Red Sandstone Stairs", $sandstoneBreakInfo));
-		self::register("red_sandstone", new Opaque(new BID(Ids::RED_SANDSTONE), "Red Sandstone", $sandstoneBreakInfo));
-		self::register("chiseled_red_sandstone", new Opaque(new BID(Ids::CHISELED_RED_SANDSTONE), "Chiseled Red Sandstone", $sandstoneBreakInfo));
-		self::register("cut_red_sandstone", new Opaque(new BID(Ids::CUT_RED_SANDSTONE), "Cut Red Sandstone", $sandstoneBreakInfo));
-		self::register("smooth_red_sandstone", new Opaque(new BID(Ids::SMOOTH_RED_SANDSTONE), "Smooth Red Sandstone", $sandstoneBreakInfo));
-
-		self::register("sandstone_stairs", new Stair(new BID(Ids::SANDSTONE_STAIRS), "Sandstone Stairs", $sandstoneBreakInfo));
-		self::register("smooth_sandstone_stairs", new Stair(new BID(Ids::SMOOTH_SANDSTONE_STAIRS), "Smooth Sandstone Stairs", $sandstoneBreakInfo));
-		self::register("sandstone", new Opaque(new BID(Ids::SANDSTONE), "Sandstone", $sandstoneBreakInfo));
-		self::register("chiseled_sandstone", new Opaque(new BID(Ids::CHISELED_SANDSTONE), "Chiseled Sandstone", $sandstoneBreakInfo));
-		self::register("cut_sandstone", new Opaque(new BID(Ids::CUT_SANDSTONE), "Cut Sandstone", $sandstoneBreakInfo));
-		self::register("smooth_sandstone", new Opaque(new BID(Ids::SMOOTH_SANDSTONE), "Smooth Sandstone", $sandstoneBreakInfo));
-
-		self::register("glazed_terracotta", new GlazedTerracotta(new BID(Ids::GLAZED_TERRACOTTA), "Glazed Terracotta", new Info(BreakInfo::pickaxe(1.4, ToolTier::WOOD))));
-		self::register("dyed_shulker_box", new DyedShulkerBox(new BID(Ids::DYED_SHULKER_BOX, TileShulkerBox::class), "Dyed Shulker Box", $shulkerBoxBreakInfo));
-		self::register("stained_glass", new StainedGlass(new BID(Ids::STAINED_GLASS), "Stained Glass", $glassBreakInfo));
-		self::register("stained_glass_pane", new StainedGlassPane(new BID(Ids::STAINED_GLASS_PANE), "Stained Glass Pane", $glassBreakInfo));
-		self::register("stained_clay", new StainedHardenedClay(new BID(Ids::STAINED_CLAY), "Stained Clay", $hardenedClayBreakInfo));
-		self::register("stained_hardened_glass", new StainedHardenedGlass(new BID(Ids::STAINED_HARDENED_GLASS), "Stained Hardened Glass", $hardenedGlassBreakInfo));
-		self::register("stained_hardened_glass_pane", new StainedHardenedGlassPane(new BID(Ids::STAINED_HARDENED_GLASS_PANE), "Stained Hardened Glass Pane", $hardenedGlassBreakInfo));
-		self::register("carpet", new Carpet(new BID(Ids::CARPET), "Carpet", new Info(new BreakInfo(0.1))));
-		self::register("concrete", new Concrete(new BID(Ids::CONCRETE), "Concrete", new Info(BreakInfo::pickaxe(1.8, ToolTier::WOOD))));
-		self::register("concrete_powder", new ConcretePowder(new BID(Ids::CONCRETE_POWDER), "Concrete Powder", new Info(BreakInfo::shovel(0.5))));
-		self::register("wool", new Wool(new BID(Ids::WOOL), "Wool", new Info(new class(0.8, ToolType::SHEARS) extends BreakInfo{
+		self::register("red_sandstone_stairs", fn(BID $id) => new Stair($id, "Red Sandstone Stairs", $sandstoneBreakInfo));
+		self::register("smooth_red_sandstone_stairs", fn(BID $id) => new Stair($id, "Smooth Red Sandstone Stairs", $sandstoneBreakInfo));
+		self::register("red_sandstone", fn(BID $id) => new Opaque($id, "Red Sandstone", $sandstoneBreakInfo));
+		self::register("chiseled_red_sandstone", fn(BID $id) => new Opaque($id, "Chiseled Red Sandstone", $sandstoneBreakInfo));
+		self::register("cut_red_sandstone", fn(BID $id) => new Opaque($id, "Cut Red Sandstone", $sandstoneBreakInfo));
+		self::register("smooth_red_sandstone", fn(BID $id) => new Opaque($id, "Smooth Red Sandstone", $sandstoneBreakInfo));
+
+		self::register("sandstone_stairs", fn(BID $id) => new Stair($id, "Sandstone Stairs", $sandstoneBreakInfo));
+		self::register("smooth_sandstone_stairs", fn(BID $id) => new Stair($id, "Smooth Sandstone Stairs", $sandstoneBreakInfo));
+		self::register("sandstone", fn(BID $id) => new Opaque($id, "Sandstone", $sandstoneBreakInfo));
+		self::register("chiseled_sandstone", fn(BID $id) => new Opaque($id, "Chiseled Sandstone", $sandstoneBreakInfo));
+		self::register("cut_sandstone", fn(BID $id) => new Opaque($id, "Cut Sandstone", $sandstoneBreakInfo));
+		self::register("smooth_sandstone", fn(BID $id) => new Opaque($id, "Smooth Sandstone", $sandstoneBreakInfo));
+
+		self::register("glazed_terracotta", fn(BID $id) => new GlazedTerracotta($id, "Glazed Terracotta", new Info(BreakInfo::pickaxe(1.4, ToolTier::WOOD))));
+		self::register("dyed_shulker_box", fn(BID $id) => new DyedShulkerBox($id, "Dyed Shulker Box", $shulkerBoxBreakInfo), TileShulkerBox::class);
+		self::register("stained_glass", fn(BID $id) => new StainedGlass($id, "Stained Glass", $glassBreakInfo));
+		self::register("stained_glass_pane", fn(BID $id) => new StainedGlassPane($id, "Stained Glass Pane", $glassBreakInfo));
+		self::register("stained_clay", fn(BID $id) => new StainedHardenedClay($id, "Stained Clay", $hardenedClayBreakInfo));
+		self::register("stained_hardened_glass", fn(BID $id) => new StainedHardenedGlass($id, "Stained Hardened Glass", $hardenedGlassBreakInfo));
+		self::register("stained_hardened_glass_pane", fn(BID $id) => new StainedHardenedGlassPane($id, "Stained Hardened Glass Pane", $hardenedGlassBreakInfo));
+		self::register("carpet", fn(BID $id) => new Carpet($id, "Carpet", new Info(new BreakInfo(0.1))));
+		self::register("concrete", fn(BID $id) => new Concrete($id, "Concrete", new Info(BreakInfo::pickaxe(1.8, ToolTier::WOOD))));
+		self::register("concrete_powder", fn(BID $id) => new ConcretePowder($id, "Concrete Powder", new Info(BreakInfo::shovel(0.5))));
+		self::register("wool", fn(BID $id) => new Wool($id, "Wool", new Info(new class(0.8, ToolType::SHEARS) extends BreakInfo{
 			public function getBreakTime(Item $item) : float{
 				$time = parent::getBreakTime($item);
 				if($item->getBlockToolType() === ToolType::SHEARS){
@@ -1228,54 +1252,54 @@ public function getBreakTime(Item $item) : float{
 
 		//TODO: in the future these won't all have the same hardness; they only do now because of the old metadata crap
 		$wallBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
-		self::register("cobblestone_wall", new Wall(new BID(Ids::COBBLESTONE_WALL), "Cobblestone Wall", $wallBreakInfo));
-		self::register("andesite_wall", new Wall(new BID(Ids::ANDESITE_WALL), "Andesite Wall", $wallBreakInfo));
-		self::register("brick_wall", new Wall(new BID(Ids::BRICK_WALL), "Brick Wall", $wallBreakInfo));
-		self::register("diorite_wall", new Wall(new BID(Ids::DIORITE_WALL), "Diorite Wall", $wallBreakInfo));
-		self::register("end_stone_brick_wall", new Wall(new BID(Ids::END_STONE_BRICK_WALL), "End Stone Brick Wall", $wallBreakInfo));
-		self::register("granite_wall", new Wall(new BID(Ids::GRANITE_WALL), "Granite Wall", $wallBreakInfo));
-		self::register("mossy_stone_brick_wall", new Wall(new BID(Ids::MOSSY_STONE_BRICK_WALL), "Mossy Stone Brick Wall", $wallBreakInfo));
-		self::register("mossy_cobblestone_wall", new Wall(new BID(Ids::MOSSY_COBBLESTONE_WALL), "Mossy Cobblestone Wall", $wallBreakInfo));
-		self::register("nether_brick_wall", new Wall(new BID(Ids::NETHER_BRICK_WALL), "Nether Brick Wall", $wallBreakInfo));
-		self::register("prismarine_wall", new Wall(new BID(Ids::PRISMARINE_WALL), "Prismarine Wall", $wallBreakInfo));
-		self::register("red_nether_brick_wall", new Wall(new BID(Ids::RED_NETHER_BRICK_WALL), "Red Nether Brick Wall", $wallBreakInfo));
-		self::register("red_sandstone_wall", new Wall(new BID(Ids::RED_SANDSTONE_WALL), "Red Sandstone Wall", $wallBreakInfo));
-		self::register("sandstone_wall", new Wall(new BID(Ids::SANDSTONE_WALL), "Sandstone Wall", $wallBreakInfo));
-		self::register("stone_brick_wall", new Wall(new BID(Ids::STONE_BRICK_WALL), "Stone Brick Wall", $wallBreakInfo));
+		self::register("cobblestone_wall", fn(BID $id) => new Wall($id, "Cobblestone Wall", $wallBreakInfo));
+		self::register("andesite_wall", fn(BID $id) => new Wall($id, "Andesite Wall", $wallBreakInfo));
+		self::register("brick_wall", fn(BID $id) => new Wall($id, "Brick Wall", $wallBreakInfo));
+		self::register("diorite_wall", fn(BID $id) => new Wall($id, "Diorite Wall", $wallBreakInfo));
+		self::register("end_stone_brick_wall", fn(BID $id) => new Wall($id, "End Stone Brick Wall", $wallBreakInfo));
+		self::register("granite_wall", fn(BID $id) => new Wall($id, "Granite Wall", $wallBreakInfo));
+		self::register("mossy_stone_brick_wall", fn(BID $id) => new Wall($id, "Mossy Stone Brick Wall", $wallBreakInfo));
+		self::register("mossy_cobblestone_wall", fn(BID $id) => new Wall($id, "Mossy Cobblestone Wall", $wallBreakInfo));
+		self::register("nether_brick_wall", fn(BID $id) => new Wall($id, "Nether Brick Wall", $wallBreakInfo));
+		self::register("prismarine_wall", fn(BID $id) => new Wall($id, "Prismarine Wall", $wallBreakInfo));
+		self::register("red_nether_brick_wall", fn(BID $id) => new Wall($id, "Red Nether Brick Wall", $wallBreakInfo));
+		self::register("red_sandstone_wall", fn(BID $id) => new Wall($id, "Red Sandstone Wall", $wallBreakInfo));
+		self::register("sandstone_wall", fn(BID $id) => new Wall($id, "Sandstone Wall", $wallBreakInfo));
+		self::register("stone_brick_wall", fn(BID $id) => new Wall($id, "Stone Brick Wall", $wallBreakInfo));
 
 		self::registerElements();
 
 		$chemistryTableBreakInfo = new Info(BreakInfo::pickaxe(2.5, ToolTier::WOOD));
-		self::register("compound_creator", new ChemistryTable(new BID(Ids::COMPOUND_CREATOR), "Compound Creator", $chemistryTableBreakInfo));
-		self::register("element_constructor", new ChemistryTable(new BID(Ids::ELEMENT_CONSTRUCTOR), "Element Constructor", $chemistryTableBreakInfo));
-		self::register("lab_table", new ChemistryTable(new BID(Ids::LAB_TABLE), "Lab Table", $chemistryTableBreakInfo));
-		self::register("material_reducer", new ChemistryTable(new BID(Ids::MATERIAL_REDUCER), "Material Reducer", $chemistryTableBreakInfo));
+		self::register("compound_creator", fn(BID $id) => new ChemistryTable($id, "Compound Creator", $chemistryTableBreakInfo));
+		self::register("element_constructor", fn(BID $id) => new ChemistryTable($id, "Element Constructor", $chemistryTableBreakInfo));
+		self::register("lab_table", fn(BID $id) => new ChemistryTable($id, "Lab Table", $chemistryTableBreakInfo));
+		self::register("material_reducer", fn(BID $id) => new ChemistryTable($id, "Material Reducer", $chemistryTableBreakInfo));
 
-		self::register("chemical_heat", new ChemicalHeat(new BID(Ids::CHEMICAL_HEAT), "Heat Block", $chemistryTableBreakInfo));
+		self::register("chemical_heat", fn(BID $id) => new ChemicalHeat($id, "Heat Block", $chemistryTableBreakInfo));
 
 		self::registerMushroomBlocks();
 
-		self::register("coral", new Coral(
-			new BID(Ids::CORAL),
+		self::register("coral", fn(BID $id) => new Coral(
+			$id,
 			"Coral",
 			new Info(BreakInfo::instant()),
 		));
-		self::register("coral_fan", new FloorCoralFan(
-			new BID(Ids::CORAL_FAN),
+		self::register("coral_fan", fn(BID $id) => new FloorCoralFan(
+			$id,
 			"Coral Fan",
 			new Info(BreakInfo::instant()),
 		));
-		self::register("wall_coral_fan", new WallCoralFan(
-			new BID(Ids::WALL_CORAL_FAN),
+		self::register("wall_coral_fan", fn(BID $id) => new WallCoralFan(
+			$id,
 			"Wall Coral Fan",
 			new Info(BreakInfo::instant()),
 		));
 
-		self::register("mangrove_roots", new MangroveRoots(new BID(Ids::MANGROVE_ROOTS), "Mangrove Roots", new Info(BreakInfo::axe(0.7))));
-		self::register("muddy_mangrove_roots", new SimplePillar(new BID(Ids::MUDDY_MANGROVE_ROOTS), "Muddy Mangrove Roots", new Info(BreakInfo::shovel(0.7), [Tags::MUD])));
-		self::register("froglight", new Froglight(new BID(Ids::FROGLIGHT), "Froglight", new Info(new BreakInfo(0.3))));
-		self::register("sculk", new Sculk(new BID(Ids::SCULK), "Sculk", new Info(new BreakInfo(0.6, ToolType::HOE))));
-		self::register("reinforced_deepslate", new class(new BID(Ids::REINFORCED_DEEPSLATE), "Reinforced Deepslate", new Info(new BreakInfo(55.0, ToolType::NONE, 0, 3600.0))) extends Opaque{
+		self::register("mangrove_roots", fn(BID $id) => new MangroveRoots($id, "Mangrove Roots", new Info(BreakInfo::axe(0.7))));
+		self::register("muddy_mangrove_roots", fn(BID $id) => new SimplePillar($id, "Muddy Mangrove Roots", new Info(BreakInfo::shovel(0.7), [Tags::MUD])));
+		self::register("froglight", fn(BID $id) => new Froglight($id, "Froglight", new Info(new BreakInfo(0.3))));
+		self::register("sculk", fn(BID $id) => new Sculk($id, "Sculk", new Info(new BreakInfo(0.6, ToolType::HOE))));
+		self::register("reinforced_deepslate", fn(BID $id) => new class($id, "Reinforced Deepslate", new Info(new BreakInfo(55.0, ToolType::NONE, 0, 3600.0))) extends Opaque{
 			public function getDropsForCompatibleTool(Item $item) : array{
 				return [];
 			}
@@ -1308,300 +1332,311 @@ private static function registerWoodenBlocks() : void{
 			$name = $woodType->getDisplayName();
 			$idName = fn(string $suffix) => strtolower($woodType->name) . "_" . $suffix;
 
-			self::register($idName(mb_strtolower($woodType->getStandardLogSuffix() ?? "log", 'US-ASCII')), new Wood(WoodLikeBlockIdHelper::getLogIdentifier($woodType), $name . " " . ($woodType->getStandardLogSuffix() ?? "Log"), $logBreakInfo, $woodType));
-			self::register($idName(mb_strtolower($woodType->getAllSidedLogSuffix() ?? "wood", 'US-ASCII')), new Wood(WoodLikeBlockIdHelper::getAllSidedLogIdentifier($woodType), $name . " " . ($woodType->getAllSidedLogSuffix() ?? "Wood"), $logBreakInfo, $woodType));
-
-			self::register($idName("planks"), new Planks(WoodLikeBlockIdHelper::getPlanksIdentifier($woodType), $name . " Planks", $planksBreakInfo, $woodType));
-			self::register($idName("fence"), new WoodenFence(WoodLikeBlockIdHelper::getFenceIdentifier($woodType), $name . " Fence", $planksBreakInfo, $woodType));
-			self::register($idName("slab"), new WoodenSlab(WoodLikeBlockIdHelper::getSlabIdentifier($woodType), $name, $planksBreakInfo, $woodType));
-
-			self::register($idName("fence_gate"), new FenceGate(WoodLikeBlockIdHelper::getFenceGateIdentifier($woodType), $name . " Fence Gate", $planksBreakInfo, $woodType));
-			self::register($idName("stairs"), new WoodenStairs(WoodLikeBlockIdHelper::getStairsIdentifier($woodType), $name . " Stairs", $planksBreakInfo, $woodType));
-			self::register($idName("door"), new WoodenDoor(WoodLikeBlockIdHelper::getDoorIdentifier($woodType), $name . " Door", $woodenDoorBreakInfo, $woodType));
-
-			self::register($idName("button"), new WoodenButton(WoodLikeBlockIdHelper::getButtonIdentifier($woodType), $name . " Button", $woodenButtonBreakInfo, $woodType));
-			self::register($idName("pressure_plate"), new WoodenPressurePlate(WoodLikeBlockIdHelper::getPressurePlateIdentifier($woodType), $name . " Pressure Plate", $woodenPressurePlateBreakInfo, $woodType, 20));
-			self::register($idName("trapdoor"), new WoodenTrapdoor(WoodLikeBlockIdHelper::getTrapdoorIdentifier($woodType), $name . " Trapdoor", $woodenDoorBreakInfo, $woodType));
-
-			[$floorSignId, $wallSignId, $signAsItem] = WoodLikeBlockIdHelper::getSignInfo($woodType);
-			self::register($idName("sign"), new FloorSign($floorSignId, $name . " Sign", $signBreakInfo, $woodType, $signAsItem));
-			self::register($idName("wall_sign"), new WallSign($wallSignId, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem));
+			self::register($idName(mb_strtolower($woodType->getStandardLogSuffix() ?? "log", 'US-ASCII')), fn(BID $id) => new Wood($id, $name . " " . ($woodType->getStandardLogSuffix() ?? "Log"), $logBreakInfo, $woodType));
+			self::register($idName(mb_strtolower($woodType->getAllSidedLogSuffix() ?? "wood", 'US-ASCII')), fn(BID $id) => new Wood($id, $name . " " . ($woodType->getAllSidedLogSuffix() ?? "Wood"), $logBreakInfo, $woodType));
+
+			self::register($idName("planks"), fn(BID $id) => new Planks($id, $name . " Planks", $planksBreakInfo, $woodType));
+			self::register($idName("fence"), fn(BID $id) => new WoodenFence($id, $name . " Fence", $planksBreakInfo, $woodType));
+			self::register($idName("slab"), fn(BID $id) => new WoodenSlab($id, $name, $planksBreakInfo, $woodType));
+
+			self::register($idName("fence_gate"), fn(BID $id) => new FenceGate($id, $name . " Fence Gate", $planksBreakInfo, $woodType));
+			self::register($idName("stairs"), fn(BID $id) => new WoodenStairs($id, $name . " Stairs", $planksBreakInfo, $woodType));
+			self::register($idName("door"), fn(BID $id) => new WoodenDoor($id, $name . " Door", $woodenDoorBreakInfo, $woodType));
+
+			self::register($idName("button"), fn(BID $id) => new WoodenButton($id, $name . " Button", $woodenButtonBreakInfo, $woodType));
+			self::register($idName("pressure_plate"), fn(BID $id) => new WoodenPressurePlate($id, $name . " Pressure Plate", $woodenPressurePlateBreakInfo, $woodType, 20));
+			self::register($idName("trapdoor"), fn(BID $id) => new WoodenTrapdoor($id, $name . " Trapdoor", $woodenDoorBreakInfo, $woodType));
+
+			$signAsItem = match($woodType){
+				WoodType::OAK => VanillaItems::OAK_SIGN(...),
+				WoodType::SPRUCE => VanillaItems::SPRUCE_SIGN(...),
+				WoodType::BIRCH => VanillaItems::BIRCH_SIGN(...),
+				WoodType::JUNGLE => VanillaItems::JUNGLE_SIGN(...),
+				WoodType::ACACIA => VanillaItems::ACACIA_SIGN(...),
+				WoodType::DARK_OAK => VanillaItems::DARK_OAK_SIGN(...),
+				WoodType::MANGROVE => VanillaItems::MANGROVE_SIGN(...),
+				WoodType::CRIMSON => VanillaItems::CRIMSON_SIGN(...),
+				WoodType::WARPED => VanillaItems::WARPED_SIGN(...),
+				WoodType::CHERRY => VanillaItems::CHERRY_SIGN(...),
+			};
+			self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem));
+			self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem));
 		}
 	}
 
 	private static function registerMushroomBlocks() : void{
 		$mushroomBlockBreakInfo = new Info(BreakInfo::axe(0.2));
 
-		self::register("brown_mushroom_block", new BrownMushroomBlock(new BID(Ids::BROWN_MUSHROOM_BLOCK), "Brown Mushroom Block", $mushroomBlockBreakInfo));
-		self::register("red_mushroom_block", new RedMushroomBlock(new BID(Ids::RED_MUSHROOM_BLOCK), "Red Mushroom Block", $mushroomBlockBreakInfo));
+		self::register("brown_mushroom_block", fn(BID $id) => new BrownMushroomBlock($id, "Brown Mushroom Block", $mushroomBlockBreakInfo));
+		self::register("red_mushroom_block", fn(BID $id) => new RedMushroomBlock($id, "Red Mushroom Block", $mushroomBlockBreakInfo));
 
 		//finally, the stems
-		self::register("mushroom_stem", new MushroomStem(new BID(Ids::MUSHROOM_STEM), "Mushroom Stem", $mushroomBlockBreakInfo));
-		self::register("all_sided_mushroom_stem", new MushroomStem(new BID(Ids::ALL_SIDED_MUSHROOM_STEM), "All Sided Mushroom Stem", $mushroomBlockBreakInfo));
+		self::register("mushroom_stem", fn(BID $id) => new MushroomStem($id, "Mushroom Stem", $mushroomBlockBreakInfo));
+		self::register("all_sided_mushroom_stem", fn(BID $id) => new MushroomStem($id, "All Sided Mushroom Stem", $mushroomBlockBreakInfo));
 	}
 
 	private static function registerElements() : void{
 		$instaBreak = new Info(BreakInfo::instant());
-		self::register("element_zero", new Opaque(new BID(Ids::ELEMENT_ZERO), "???", $instaBreak));
-
-		$register = fn(string $name, int $id, string $displayName, string $symbol, int $atomicWeight, int $group) =>
-			self::register("element_$name", new Element(new BID($id), $displayName, $instaBreak, $symbol, $atomicWeight, $group));
-
-		$register("hydrogen", Ids::ELEMENT_HYDROGEN, "Hydrogen", "h", 1, 5);
-		$register("helium", Ids::ELEMENT_HELIUM, "Helium", "he", 2, 7);
-		$register("lithium", Ids::ELEMENT_LITHIUM, "Lithium", "li", 3, 0);
-		$register("beryllium", Ids::ELEMENT_BERYLLIUM, "Beryllium", "be", 4, 1);
-		$register("boron", Ids::ELEMENT_BORON, "Boron", "b", 5, 4);
-		$register("carbon", Ids::ELEMENT_CARBON, "Carbon", "c", 6, 5);
-		$register("nitrogen", Ids::ELEMENT_NITROGEN, "Nitrogen", "n", 7, 5);
-		$register("oxygen", Ids::ELEMENT_OXYGEN, "Oxygen", "o", 8, 5);
-		$register("fluorine", Ids::ELEMENT_FLUORINE, "Fluorine", "f", 9, 6);
-		$register("neon", Ids::ELEMENT_NEON, "Neon", "ne", 10, 7);
-		$register("sodium", Ids::ELEMENT_SODIUM, "Sodium", "na", 11, 0);
-		$register("magnesium", Ids::ELEMENT_MAGNESIUM, "Magnesium", "mg", 12, 1);
-		$register("aluminum", Ids::ELEMENT_ALUMINUM, "Aluminum", "al", 13, 3);
-		$register("silicon", Ids::ELEMENT_SILICON, "Silicon", "si", 14, 4);
-		$register("phosphorus", Ids::ELEMENT_PHOSPHORUS, "Phosphorus", "p", 15, 5);
-		$register("sulfur", Ids::ELEMENT_SULFUR, "Sulfur", "s", 16, 5);
-		$register("chlorine", Ids::ELEMENT_CHLORINE, "Chlorine", "cl", 17, 6);
-		$register("argon", Ids::ELEMENT_ARGON, "Argon", "ar", 18, 7);
-		$register("potassium", Ids::ELEMENT_POTASSIUM, "Potassium", "k", 19, 0);
-		$register("calcium", Ids::ELEMENT_CALCIUM, "Calcium", "ca", 20, 1);
-		$register("scandium", Ids::ELEMENT_SCANDIUM, "Scandium", "sc", 21, 2);
-		$register("titanium", Ids::ELEMENT_TITANIUM, "Titanium", "ti", 22, 2);
-		$register("vanadium", Ids::ELEMENT_VANADIUM, "Vanadium", "v", 23, 2);
-		$register("chromium", Ids::ELEMENT_CHROMIUM, "Chromium", "cr", 24, 2);
-		$register("manganese", Ids::ELEMENT_MANGANESE, "Manganese", "mn", 25, 2);
-		$register("iron", Ids::ELEMENT_IRON, "Iron", "fe", 26, 2);
-		$register("cobalt", Ids::ELEMENT_COBALT, "Cobalt", "co", 27, 2);
-		$register("nickel", Ids::ELEMENT_NICKEL, "Nickel", "ni", 28, 2);
-		$register("copper", Ids::ELEMENT_COPPER, "Copper", "cu", 29, 2);
-		$register("zinc", Ids::ELEMENT_ZINC, "Zinc", "zn", 30, 2);
-		$register("gallium", Ids::ELEMENT_GALLIUM, "Gallium", "ga", 31, 3);
-		$register("germanium", Ids::ELEMENT_GERMANIUM, "Germanium", "ge", 32, 4);
-		$register("arsenic", Ids::ELEMENT_ARSENIC, "Arsenic", "as", 33, 4);
-		$register("selenium", Ids::ELEMENT_SELENIUM, "Selenium", "se", 34, 5);
-		$register("bromine", Ids::ELEMENT_BROMINE, "Bromine", "br", 35, 6);
-		$register("krypton", Ids::ELEMENT_KRYPTON, "Krypton", "kr", 36, 7);
-		$register("rubidium", Ids::ELEMENT_RUBIDIUM, "Rubidium", "rb", 37, 0);
-		$register("strontium", Ids::ELEMENT_STRONTIUM, "Strontium", "sr", 38, 1);
-		$register("yttrium", Ids::ELEMENT_YTTRIUM, "Yttrium", "y", 39, 2);
-		$register("zirconium", Ids::ELEMENT_ZIRCONIUM, "Zirconium", "zr", 40, 2);
-		$register("niobium", Ids::ELEMENT_NIOBIUM, "Niobium", "nb", 41, 2);
-		$register("molybdenum", Ids::ELEMENT_MOLYBDENUM, "Molybdenum", "mo", 42, 2);
-		$register("technetium", Ids::ELEMENT_TECHNETIUM, "Technetium", "tc", 43, 2);
-		$register("ruthenium", Ids::ELEMENT_RUTHENIUM, "Ruthenium", "ru", 44, 2);
-		$register("rhodium", Ids::ELEMENT_RHODIUM, "Rhodium", "rh", 45, 2);
-		$register("palladium", Ids::ELEMENT_PALLADIUM, "Palladium", "pd", 46, 2);
-		$register("silver", Ids::ELEMENT_SILVER, "Silver", "ag", 47, 2);
-		$register("cadmium", Ids::ELEMENT_CADMIUM, "Cadmium", "cd", 48, 2);
-		$register("indium", Ids::ELEMENT_INDIUM, "Indium", "in", 49, 3);
-		$register("tin", Ids::ELEMENT_TIN, "Tin", "sn", 50, 3);
-		$register("antimony", Ids::ELEMENT_ANTIMONY, "Antimony", "sb", 51, 4);
-		$register("tellurium", Ids::ELEMENT_TELLURIUM, "Tellurium", "te", 52, 4);
-		$register("iodine", Ids::ELEMENT_IODINE, "Iodine", "i", 53, 6);
-		$register("xenon", Ids::ELEMENT_XENON, "Xenon", "xe", 54, 7);
-		$register("cesium", Ids::ELEMENT_CESIUM, "Cesium", "cs", 55, 0);
-		$register("barium", Ids::ELEMENT_BARIUM, "Barium", "ba", 56, 1);
-		$register("lanthanum", Ids::ELEMENT_LANTHANUM, "Lanthanum", "la", 57, 8);
-		$register("cerium", Ids::ELEMENT_CERIUM, "Cerium", "ce", 58, 8);
-		$register("praseodymium", Ids::ELEMENT_PRASEODYMIUM, "Praseodymium", "pr", 59, 8);
-		$register("neodymium", Ids::ELEMENT_NEODYMIUM, "Neodymium", "nd", 60, 8);
-		$register("promethium", Ids::ELEMENT_PROMETHIUM, "Promethium", "pm", 61, 8);
-		$register("samarium", Ids::ELEMENT_SAMARIUM, "Samarium", "sm", 62, 8);
-		$register("europium", Ids::ELEMENT_EUROPIUM, "Europium", "eu", 63, 8);
-		$register("gadolinium", Ids::ELEMENT_GADOLINIUM, "Gadolinium", "gd", 64, 8);
-		$register("terbium", Ids::ELEMENT_TERBIUM, "Terbium", "tb", 65, 8);
-		$register("dysprosium", Ids::ELEMENT_DYSPROSIUM, "Dysprosium", "dy", 66, 8);
-		$register("holmium", Ids::ELEMENT_HOLMIUM, "Holmium", "ho", 67, 8);
-		$register("erbium", Ids::ELEMENT_ERBIUM, "Erbium", "er", 68, 8);
-		$register("thulium", Ids::ELEMENT_THULIUM, "Thulium", "tm", 69, 8);
-		$register("ytterbium", Ids::ELEMENT_YTTERBIUM, "Ytterbium", "yb", 70, 8);
-		$register("lutetium", Ids::ELEMENT_LUTETIUM, "Lutetium", "lu", 71, 8);
-		$register("hafnium", Ids::ELEMENT_HAFNIUM, "Hafnium", "hf", 72, 2);
-		$register("tantalum", Ids::ELEMENT_TANTALUM, "Tantalum", "ta", 73, 2);
-		$register("tungsten", Ids::ELEMENT_TUNGSTEN, "Tungsten", "w", 74, 2);
-		$register("rhenium", Ids::ELEMENT_RHENIUM, "Rhenium", "re", 75, 2);
-		$register("osmium", Ids::ELEMENT_OSMIUM, "Osmium", "os", 76, 2);
-		$register("iridium", Ids::ELEMENT_IRIDIUM, "Iridium", "ir", 77, 2);
-		$register("platinum", Ids::ELEMENT_PLATINUM, "Platinum", "pt", 78, 2);
-		$register("gold", Ids::ELEMENT_GOLD, "Gold", "au", 79, 2);
-		$register("mercury", Ids::ELEMENT_MERCURY, "Mercury", "hg", 80, 2);
-		$register("thallium", Ids::ELEMENT_THALLIUM, "Thallium", "tl", 81, 3);
-		$register("lead", Ids::ELEMENT_LEAD, "Lead", "pb", 82, 3);
-		$register("bismuth", Ids::ELEMENT_BISMUTH, "Bismuth", "bi", 83, 3);
-		$register("polonium", Ids::ELEMENT_POLONIUM, "Polonium", "po", 84, 4);
-		$register("astatine", Ids::ELEMENT_ASTATINE, "Astatine", "at", 85, 6);
-		$register("radon", Ids::ELEMENT_RADON, "Radon", "rn", 86, 7);
-		$register("francium", Ids::ELEMENT_FRANCIUM, "Francium", "fr", 87, 0);
-		$register("radium", Ids::ELEMENT_RADIUM, "Radium", "ra", 88, 1);
-		$register("actinium", Ids::ELEMENT_ACTINIUM, "Actinium", "ac", 89, 9);
-		$register("thorium", Ids::ELEMENT_THORIUM, "Thorium", "th", 90, 9);
-		$register("protactinium", Ids::ELEMENT_PROTACTINIUM, "Protactinium", "pa", 91, 9);
-		$register("uranium", Ids::ELEMENT_URANIUM, "Uranium", "u", 92, 9);
-		$register("neptunium", Ids::ELEMENT_NEPTUNIUM, "Neptunium", "np", 93, 9);
-		$register("plutonium", Ids::ELEMENT_PLUTONIUM, "Plutonium", "pu", 94, 9);
-		$register("americium", Ids::ELEMENT_AMERICIUM, "Americium", "am", 95, 9);
-		$register("curium", Ids::ELEMENT_CURIUM, "Curium", "cm", 96, 9);
-		$register("berkelium", Ids::ELEMENT_BERKELIUM, "Berkelium", "bk", 97, 9);
-		$register("californium", Ids::ELEMENT_CALIFORNIUM, "Californium", "cf", 98, 9);
-		$register("einsteinium", Ids::ELEMENT_EINSTEINIUM, "Einsteinium", "es", 99, 9);
-		$register("fermium", Ids::ELEMENT_FERMIUM, "Fermium", "fm", 100, 9);
-		$register("mendelevium", Ids::ELEMENT_MENDELEVIUM, "Mendelevium", "md", 101, 9);
-		$register("nobelium", Ids::ELEMENT_NOBELIUM, "Nobelium", "no", 102, 9);
-		$register("lawrencium", Ids::ELEMENT_LAWRENCIUM, "Lawrencium", "lr", 103, 9);
-		$register("rutherfordium", Ids::ELEMENT_RUTHERFORDIUM, "Rutherfordium", "rf", 104, 2);
-		$register("dubnium", Ids::ELEMENT_DUBNIUM, "Dubnium", "db", 105, 2);
-		$register("seaborgium", Ids::ELEMENT_SEABORGIUM, "Seaborgium", "sg", 106, 2);
-		$register("bohrium", Ids::ELEMENT_BOHRIUM, "Bohrium", "bh", 107, 2);
-		$register("hassium", Ids::ELEMENT_HASSIUM, "Hassium", "hs", 108, 2);
-		$register("meitnerium", Ids::ELEMENT_MEITNERIUM, "Meitnerium", "mt", 109, 2);
-		$register("darmstadtium", Ids::ELEMENT_DARMSTADTIUM, "Darmstadtium", "ds", 110, 2);
-		$register("roentgenium", Ids::ELEMENT_ROENTGENIUM, "Roentgenium", "rg", 111, 2);
-		$register("copernicium", Ids::ELEMENT_COPERNICIUM, "Copernicium", "cn", 112, 2);
-		$register("nihonium", Ids::ELEMENT_NIHONIUM, "Nihonium", "nh", 113, 3);
-		$register("flerovium", Ids::ELEMENT_FLEROVIUM, "Flerovium", "fl", 114, 3);
-		$register("moscovium", Ids::ELEMENT_MOSCOVIUM, "Moscovium", "mc", 115, 3);
-		$register("livermorium", Ids::ELEMENT_LIVERMORIUM, "Livermorium", "lv", 116, 3);
-		$register("tennessine", Ids::ELEMENT_TENNESSINE, "Tennessine", "ts", 117, 6);
-		$register("oganesson", Ids::ELEMENT_OGANESSON, "Oganesson", "og", 118, 7);
+		self::register("element_zero", fn(BID $id) => new Opaque($id, "???", $instaBreak));
+
+		$register = fn(string $name, string $displayName, string $symbol, int $atomicWeight, int $group) =>
+			self::register("element_$name", fn(BID $id) => new Element($id, $displayName, $instaBreak, $symbol, $atomicWeight, $group));
+
+		$register("hydrogen", "Hydrogen", "h", 1, 5);
+		$register("helium", "Helium", "he", 2, 7);
+		$register("lithium", "Lithium", "li", 3, 0);
+		$register("beryllium", "Beryllium", "be", 4, 1);
+		$register("boron", "Boron", "b", 5, 4);
+		$register("carbon", "Carbon", "c", 6, 5);
+		$register("nitrogen", "Nitrogen", "n", 7, 5);
+		$register("oxygen", "Oxygen", "o", 8, 5);
+		$register("fluorine", "Fluorine", "f", 9, 6);
+		$register("neon", "Neon", "ne", 10, 7);
+		$register("sodium", "Sodium", "na", 11, 0);
+		$register("magnesium", "Magnesium", "mg", 12, 1);
+		$register("aluminum", "Aluminum", "al", 13, 3);
+		$register("silicon", "Silicon", "si", 14, 4);
+		$register("phosphorus", "Phosphorus", "p", 15, 5);
+		$register("sulfur", "Sulfur", "s", 16, 5);
+		$register("chlorine", "Chlorine", "cl", 17, 6);
+		$register("argon", "Argon", "ar", 18, 7);
+		$register("potassium", "Potassium", "k", 19, 0);
+		$register("calcium", "Calcium", "ca", 20, 1);
+		$register("scandium", "Scandium", "sc", 21, 2);
+		$register("titanium", "Titanium", "ti", 22, 2);
+		$register("vanadium", "Vanadium", "v", 23, 2);
+		$register("chromium", "Chromium", "cr", 24, 2);
+		$register("manganese", "Manganese", "mn", 25, 2);
+		$register("iron", "Iron", "fe", 26, 2);
+		$register("cobalt", "Cobalt", "co", 27, 2);
+		$register("nickel", "Nickel", "ni", 28, 2);
+		$register("copper", "Copper", "cu", 29, 2);
+		$register("zinc", "Zinc", "zn", 30, 2);
+		$register("gallium", "Gallium", "ga", 31, 3);
+		$register("germanium", "Germanium", "ge", 32, 4);
+		$register("arsenic", "Arsenic", "as", 33, 4);
+		$register("selenium", "Selenium", "se", 34, 5);
+		$register("bromine", "Bromine", "br", 35, 6);
+		$register("krypton", "Krypton", "kr", 36, 7);
+		$register("rubidium", "Rubidium", "rb", 37, 0);
+		$register("strontium", "Strontium", "sr", 38, 1);
+		$register("yttrium", "Yttrium", "y", 39, 2);
+		$register("zirconium", "Zirconium", "zr", 40, 2);
+		$register("niobium", "Niobium", "nb", 41, 2);
+		$register("molybdenum", "Molybdenum", "mo", 42, 2);
+		$register("technetium", "Technetium", "tc", 43, 2);
+		$register("ruthenium", "Ruthenium", "ru", 44, 2);
+		$register("rhodium", "Rhodium", "rh", 45, 2);
+		$register("palladium", "Palladium", "pd", 46, 2);
+		$register("silver", "Silver", "ag", 47, 2);
+		$register("cadmium", "Cadmium", "cd", 48, 2);
+		$register("indium", "Indium", "in", 49, 3);
+		$register("tin", "Tin", "sn", 50, 3);
+		$register("antimony", "Antimony", "sb", 51, 4);
+		$register("tellurium", "Tellurium", "te", 52, 4);
+		$register("iodine", "Iodine", "i", 53, 6);
+		$register("xenon", "Xenon", "xe", 54, 7);
+		$register("cesium", "Cesium", "cs", 55, 0);
+		$register("barium", "Barium", "ba", 56, 1);
+		$register("lanthanum", "Lanthanum", "la", 57, 8);
+		$register("cerium", "Cerium", "ce", 58, 8);
+		$register("praseodymium", "Praseodymium", "pr", 59, 8);
+		$register("neodymium", "Neodymium", "nd", 60, 8);
+		$register("promethium", "Promethium", "pm", 61, 8);
+		$register("samarium", "Samarium", "sm", 62, 8);
+		$register("europium", "Europium", "eu", 63, 8);
+		$register("gadolinium", "Gadolinium", "gd", 64, 8);
+		$register("terbium", "Terbium", "tb", 65, 8);
+		$register("dysprosium", "Dysprosium", "dy", 66, 8);
+		$register("holmium", "Holmium", "ho", 67, 8);
+		$register("erbium", "Erbium", "er", 68, 8);
+		$register("thulium", "Thulium", "tm", 69, 8);
+		$register("ytterbium", "Ytterbium", "yb", 70, 8);
+		$register("lutetium", "Lutetium", "lu", 71, 8);
+		$register("hafnium", "Hafnium", "hf", 72, 2);
+		$register("tantalum", "Tantalum", "ta", 73, 2);
+		$register("tungsten", "Tungsten", "w", 74, 2);
+		$register("rhenium", "Rhenium", "re", 75, 2);
+		$register("osmium", "Osmium", "os", 76, 2);
+		$register("iridium", "Iridium", "ir", 77, 2);
+		$register("platinum", "Platinum", "pt", 78, 2);
+		$register("gold", "Gold", "au", 79, 2);
+		$register("mercury", "Mercury", "hg", 80, 2);
+		$register("thallium", "Thallium", "tl", 81, 3);
+		$register("lead", "Lead", "pb", 82, 3);
+		$register("bismuth", "Bismuth", "bi", 83, 3);
+		$register("polonium", "Polonium", "po", 84, 4);
+		$register("astatine", "Astatine", "at", 85, 6);
+		$register("radon", "Radon", "rn", 86, 7);
+		$register("francium", "Francium", "fr", 87, 0);
+		$register("radium", "Radium", "ra", 88, 1);
+		$register("actinium", "Actinium", "ac", 89, 9);
+		$register("thorium", "Thorium", "th", 90, 9);
+		$register("protactinium", "Protactinium", "pa", 91, 9);
+		$register("uranium", "Uranium", "u", 92, 9);
+		$register("neptunium", "Neptunium", "np", 93, 9);
+		$register("plutonium", "Plutonium", "pu", 94, 9);
+		$register("americium", "Americium", "am", 95, 9);
+		$register("curium", "Curium", "cm", 96, 9);
+		$register("berkelium", "Berkelium", "bk", 97, 9);
+		$register("californium", "Californium", "cf", 98, 9);
+		$register("einsteinium", "Einsteinium", "es", 99, 9);
+		$register("fermium", "Fermium", "fm", 100, 9);
+		$register("mendelevium", "Mendelevium", "md", 101, 9);
+		$register("nobelium", "Nobelium", "no", 102, 9);
+		$register("lawrencium", "Lawrencium", "lr", 103, 9);
+		$register("rutherfordium", "Rutherfordium", "rf", 104, 2);
+		$register("dubnium", "Dubnium", "db", 105, 2);
+		$register("seaborgium", "Seaborgium", "sg", 106, 2);
+		$register("bohrium", "Bohrium", "bh", 107, 2);
+		$register("hassium", "Hassium", "hs", 108, 2);
+		$register("meitnerium", "Meitnerium", "mt", 109, 2);
+		$register("darmstadtium", "Darmstadtium", "ds", 110, 2);
+		$register("roentgenium", "Roentgenium", "rg", 111, 2);
+		$register("copernicium", "Copernicium", "cn", 112, 2);
+		$register("nihonium", "Nihonium", "nh", 113, 3);
+		$register("flerovium", "Flerovium", "fl", 114, 3);
+		$register("moscovium", "Moscovium", "mc", 115, 3);
+		$register("livermorium", "Livermorium", "lv", 116, 3);
+		$register("tennessine", "Tennessine", "ts", 117, 6);
+		$register("oganesson", "Oganesson", "og", 118, 7);
 	}
 
 	private static function registerOres() : void{
 		$stoneOreBreakInfo = fn(ToolTier $toolTier) => new Info(BreakInfo::pickaxe(3.0, $toolTier));
-		self::register("coal_ore", new CoalOre(new BID(Ids::COAL_ORE), "Coal Ore", $stoneOreBreakInfo(ToolTier::WOOD)));
-		self::register("copper_ore", new CopperOre(new BID(Ids::COPPER_ORE), "Copper Ore", $stoneOreBreakInfo(ToolTier::STONE)));
-		self::register("diamond_ore", new DiamondOre(new BID(Ids::DIAMOND_ORE), "Diamond Ore", $stoneOreBreakInfo(ToolTier::IRON)));
-		self::register("emerald_ore", new EmeraldOre(new BID(Ids::EMERALD_ORE), "Emerald Ore", $stoneOreBreakInfo(ToolTier::IRON)));
-		self::register("gold_ore", new GoldOre(new BID(Ids::GOLD_ORE), "Gold Ore", $stoneOreBreakInfo(ToolTier::IRON)));
-		self::register("iron_ore", new IronOre(new BID(Ids::IRON_ORE), "Iron Ore", $stoneOreBreakInfo(ToolTier::STONE)));
-		self::register("lapis_lazuli_ore", new LapisOre(new BID(Ids::LAPIS_LAZULI_ORE), "Lapis Lazuli Ore", $stoneOreBreakInfo(ToolTier::STONE)));
-		self::register("redstone_ore", new RedstoneOre(new BID(Ids::REDSTONE_ORE), "Redstone Ore", $stoneOreBreakInfo(ToolTier::IRON)));
+		self::register("coal_ore", fn(BID $id) => new CoalOre($id, "Coal Ore", $stoneOreBreakInfo(ToolTier::WOOD)));
+		self::register("copper_ore", fn(BID $id) => new CopperOre($id, "Copper Ore", $stoneOreBreakInfo(ToolTier::STONE)));
+		self::register("diamond_ore", fn(BID $id) => new DiamondOre($id, "Diamond Ore", $stoneOreBreakInfo(ToolTier::IRON)));
+		self::register("emerald_ore", fn(BID $id) => new EmeraldOre($id, "Emerald Ore", $stoneOreBreakInfo(ToolTier::IRON)));
+		self::register("gold_ore", fn(BID $id) => new GoldOre($id, "Gold Ore", $stoneOreBreakInfo(ToolTier::IRON)));
+		self::register("iron_ore", fn(BID $id) => new IronOre($id, "Iron Ore", $stoneOreBreakInfo(ToolTier::STONE)));
+		self::register("lapis_lazuli_ore", fn(BID $id) => new LapisOre($id, "Lapis Lazuli Ore", $stoneOreBreakInfo(ToolTier::STONE)));
+		self::register("redstone_ore", fn(BID $id) => new RedstoneOre($id, "Redstone Ore", $stoneOreBreakInfo(ToolTier::IRON)));
 
 		$deepslateOreBreakInfo = fn(ToolTier $toolTier) => new Info(BreakInfo::pickaxe(4.5, $toolTier));
-		self::register("deepslate_coal_ore", new CoalOre(new BID(Ids::DEEPSLATE_COAL_ORE), "Deepslate Coal Ore", $deepslateOreBreakInfo(ToolTier::WOOD)));
-		self::register("deepslate_copper_ore", new CopperOre(new BID(Ids::DEEPSLATE_COPPER_ORE), "Deepslate Copper Ore", $deepslateOreBreakInfo(ToolTier::STONE)));
-		self::register("deepslate_diamond_ore", new DiamondOre(new BID(Ids::DEEPSLATE_DIAMOND_ORE), "Deepslate Diamond Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
-		self::register("deepslate_emerald_ore", new EmeraldOre(new BID(Ids::DEEPSLATE_EMERALD_ORE), "Deepslate Emerald Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
-		self::register("deepslate_gold_ore", new GoldOre(new BID(Ids::DEEPSLATE_GOLD_ORE), "Deepslate Gold Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
-		self::register("deepslate_iron_ore", new IronOre(new BID(Ids::DEEPSLATE_IRON_ORE), "Deepslate Iron Ore", $deepslateOreBreakInfo(ToolTier::STONE)));
-		self::register("deepslate_lapis_lazuli_ore", new LapisOre(new BID(Ids::DEEPSLATE_LAPIS_LAZULI_ORE), "Deepslate Lapis Lazuli Ore", $deepslateOreBreakInfo(ToolTier::STONE)));
-		self::register("deepslate_redstone_ore", new RedstoneOre(new BID(Ids::DEEPSLATE_REDSTONE_ORE), "Deepslate Redstone Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
+		self::register("deepslate_coal_ore", fn(BID $id) => new CoalOre($id, "Deepslate Coal Ore", $deepslateOreBreakInfo(ToolTier::WOOD)));
+		self::register("deepslate_copper_ore", fn(BID $id) => new CopperOre($id, "Deepslate Copper Ore", $deepslateOreBreakInfo(ToolTier::STONE)));
+		self::register("deepslate_diamond_ore", fn(BID $id) => new DiamondOre($id, "Deepslate Diamond Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
+		self::register("deepslate_emerald_ore", fn(BID $id) => new EmeraldOre($id, "Deepslate Emerald Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
+		self::register("deepslate_gold_ore", fn(BID $id) => new GoldOre($id, "Deepslate Gold Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
+		self::register("deepslate_iron_ore", fn(BID $id) => new IronOre($id, "Deepslate Iron Ore", $deepslateOreBreakInfo(ToolTier::STONE)));
+		self::register("deepslate_lapis_lazuli_ore", fn(BID $id) => new LapisOre($id, "Deepslate Lapis Lazuli Ore", $deepslateOreBreakInfo(ToolTier::STONE)));
+		self::register("deepslate_redstone_ore", fn(BID $id) => new RedstoneOre($id, "Deepslate Redstone Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
 
 		$netherrackOreBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD));
-		self::register("nether_quartz_ore", new NetherQuartzOre(new BID(Ids::NETHER_QUARTZ_ORE), "Nether Quartz Ore", $netherrackOreBreakInfo));
-		self::register("nether_gold_ore", new NetherGoldOre(new BID(Ids::NETHER_GOLD_ORE), "Nether Gold Ore", $netherrackOreBreakInfo));
+		self::register("nether_quartz_ore", fn(BID $id) => new NetherQuartzOre($id, "Nether Quartz Ore", $netherrackOreBreakInfo));
+		self::register("nether_gold_ore", fn(BID $id) => new NetherGoldOre($id, "Nether Gold Ore", $netherrackOreBreakInfo));
 	}
 
 	private static function registerCraftingTables() : void{
 		//TODO: this is the same for all wooden crafting blocks
 		$craftingBlockBreakInfo = new Info(BreakInfo::axe(2.5));
-		self::register("cartography_table", new CartographyTable(new BID(Ids::CARTOGRAPHY_TABLE), "Cartography Table", $craftingBlockBreakInfo));
-		self::register("crafting_table", new CraftingTable(new BID(Ids::CRAFTING_TABLE), "Crafting Table", $craftingBlockBreakInfo));
-		self::register("fletching_table", new FletchingTable(new BID(Ids::FLETCHING_TABLE), "Fletching Table", $craftingBlockBreakInfo));
-		self::register("loom", new Loom(new BID(Ids::LOOM), "Loom", $craftingBlockBreakInfo));
-		self::register("smithing_table", new SmithingTable(new BID(Ids::SMITHING_TABLE), "Smithing Table", $craftingBlockBreakInfo));
+		self::register("cartography_table", fn(BID $id) => new CartographyTable($id, "Cartography Table", $craftingBlockBreakInfo));
+		self::register("crafting_table", fn(BID $id) => new CraftingTable($id, "Crafting Table", $craftingBlockBreakInfo));
+		self::register("fletching_table", fn(BID $id) => new FletchingTable($id, "Fletching Table", $craftingBlockBreakInfo));
+		self::register("loom", fn(BID $id) => new Loom($id, "Loom", $craftingBlockBreakInfo));
+		self::register("smithing_table", fn(BID $id) => new SmithingTable($id, "Smithing Table", $craftingBlockBreakInfo));
 	}
 
 	private static function registerChorusBlocks() : void{
 		$chorusBlockBreakInfo = new Info(BreakInfo::axe(0.4));
-		self::register("chorus_plant", new ChorusPlant(new BID(Ids::CHORUS_PLANT), "Chorus Plant", $chorusBlockBreakInfo));
-		self::register("chorus_flower", new ChorusFlower(new BID(Ids::CHORUS_FLOWER), "Chorus Flower", $chorusBlockBreakInfo));
+		self::register("chorus_plant", fn(BID $id) => new ChorusPlant($id, "Chorus Plant", $chorusBlockBreakInfo));
+		self::register("chorus_flower", fn(BID $id) => new ChorusFlower($id, "Chorus Flower", $chorusBlockBreakInfo));
 	}
 
 	private static function registerBlocksR13() : void{
-		self::register("light", new Light(new BID(Ids::LIGHT), "Light Block", new Info(BreakInfo::indestructible())));
-		self::register("wither_rose", new WitherRose(new BID(Ids::WITHER_ROSE), "Wither Rose", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
+		self::register("light", fn(BID $id) => new Light($id, "Light Block", new Info(BreakInfo::indestructible())));
+		self::register("wither_rose", fn(BID $id) => new WitherRose($id, "Wither Rose", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
 	}
 
 	private static function registerBlocksR14() : void{
-		self::register("honeycomb", new Opaque(new BID(Ids::HONEYCOMB), "Honeycomb Block", new Info(new BreakInfo(0.6))));
+		self::register("honeycomb", fn(BID $id) => new Opaque($id, "Honeycomb Block", new Info(new BreakInfo(0.6))));
 	}
 
 	private static function registerBlocksR16() : void{
 		//for some reason, slabs have weird hardness like the legacy ones
 		$slabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
 
-		self::register("ancient_debris", new class(new BID(Ids::ANCIENT_DEBRIS), "Ancient Debris", new Info(BreakInfo::pickaxe(30, ToolTier::DIAMOND, 3600.0))) extends Opaque{
+		self::register("ancient_debris", fn(BID $id) => new class($id, "Ancient Debris", new Info(BreakInfo::pickaxe(30, ToolTier::DIAMOND, 3600.0))) extends Opaque{
 			public function isFireProofAsItem() : bool{ return true; }
 		});
 		$netheriteBreakInfo = new Info(BreakInfo::pickaxe(50, ToolTier::DIAMOND, 3600.0));
-		self::register("netherite", new class(new BID(Ids::NETHERITE), "Netherite Block", $netheriteBreakInfo) extends Opaque{
+		self::register("netherite", fn(BID $id) => new class($id, "Netherite Block", $netheriteBreakInfo) extends Opaque{
 			public function isFireProofAsItem() : bool{ return true; }
 		});
 
 		$basaltBreakInfo = new Info(BreakInfo::pickaxe(1.25, ToolTier::WOOD, 21.0));
-		self::register("basalt", new SimplePillar(new BID(Ids::BASALT), "Basalt", $basaltBreakInfo));
-		self::register("polished_basalt", new SimplePillar(new BID(Ids::POLISHED_BASALT), "Polished Basalt", $basaltBreakInfo));
-		self::register("smooth_basalt", new Opaque(new BID(Ids::SMOOTH_BASALT), "Smooth Basalt", $basaltBreakInfo));
+		self::register("basalt", fn(BID $id) => new SimplePillar($id, "Basalt", $basaltBreakInfo));
+		self::register("polished_basalt", fn(BID $id) => new SimplePillar($id, "Polished Basalt", $basaltBreakInfo));
+		self::register("smooth_basalt", fn(BID $id) => new Opaque($id, "Smooth Basalt", $basaltBreakInfo));
 
 		$blackstoneBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
-		self::register("blackstone", new Opaque(new BID(Ids::BLACKSTONE), "Blackstone", $blackstoneBreakInfo));
-		self::register("blackstone_slab", new Slab(new BID(Ids::BLACKSTONE_SLAB), "Blackstone", $slabBreakInfo));
-		self::register("blackstone_stairs", new Stair(new BID(Ids::BLACKSTONE_STAIRS), "Blackstone Stairs", $blackstoneBreakInfo));
-		self::register("blackstone_wall", new Wall(new BID(Ids::BLACKSTONE_WALL), "Blackstone Wall", $blackstoneBreakInfo));
+		self::register("blackstone", fn(BID $id) => new Opaque($id, "Blackstone", $blackstoneBreakInfo));
+		self::register("blackstone_slab", fn(BID $id) => new Slab($id, "Blackstone", $slabBreakInfo));
+		self::register("blackstone_stairs", fn(BID $id) => new Stair($id, "Blackstone Stairs", $blackstoneBreakInfo));
+		self::register("blackstone_wall", fn(BID $id) => new Wall($id, "Blackstone Wall", $blackstoneBreakInfo));
 
-		self::register("gilded_blackstone", new GildedBlackstone(new BID(Ids::GILDED_BLACKSTONE), "Gilded Blackstone", $blackstoneBreakInfo));
+		self::register("gilded_blackstone", fn(BID $id) => new GildedBlackstone($id, "Gilded Blackstone", $blackstoneBreakInfo));
 
 		//TODO: polished blackstone ought to have 2.0 hardness (as per java) but it's 1.5 in Bedrock (probably parity bug)
 		$prefix = fn(string $thing) => "Polished Blackstone" . ($thing !== "" ? " $thing" : "");
-		self::register("polished_blackstone", new Opaque(new BID(Ids::POLISHED_BLACKSTONE), $prefix(""), $blackstoneBreakInfo));
-		self::register("polished_blackstone_button", new StoneButton(new BID(Ids::POLISHED_BLACKSTONE_BUTTON), $prefix("Button"), new Info(BreakInfo::pickaxe(0.5))));
-		self::register("polished_blackstone_pressure_plate", new StonePressurePlate(new BID(Ids::POLISHED_BLACKSTONE_PRESSURE_PLATE), $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD)), 20));
-		self::register("polished_blackstone_slab", new Slab(new BID(Ids::POLISHED_BLACKSTONE_SLAB), $prefix(""), $slabBreakInfo));
-		self::register("polished_blackstone_stairs", new Stair(new BID(Ids::POLISHED_BLACKSTONE_STAIRS), $prefix("Stairs"), $blackstoneBreakInfo));
-		self::register("polished_blackstone_wall", new Wall(new BID(Ids::POLISHED_BLACKSTONE_WALL), $prefix("Wall"), $blackstoneBreakInfo));
-		self::register("chiseled_polished_blackstone", new Opaque(new BID(Ids::CHISELED_POLISHED_BLACKSTONE), "Chiseled Polished Blackstone", $blackstoneBreakInfo));
+		self::register("polished_blackstone", fn(BID $id) => new Opaque($id, $prefix(""), $blackstoneBreakInfo));
+		self::register("polished_blackstone_button", fn(BID $id) => new StoneButton($id, $prefix("Button"), new Info(BreakInfo::pickaxe(0.5))));
+		self::register("polished_blackstone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD)), 20));
+		self::register("polished_blackstone_slab", fn(BID $id) => new Slab($id, $prefix(""), $slabBreakInfo));
+		self::register("polished_blackstone_stairs", fn(BID $id) => new Stair($id, $prefix("Stairs"), $blackstoneBreakInfo));
+		self::register("polished_blackstone_wall", fn(BID $id) => new Wall($id, $prefix("Wall"), $blackstoneBreakInfo));
+		self::register("chiseled_polished_blackstone", fn(BID $id) => new Opaque($id, "Chiseled Polished Blackstone", $blackstoneBreakInfo));
 
 		$prefix = fn(string $thing) => "Polished Blackstone Brick" . ($thing !== "" ? " $thing" : "");
-		self::register("polished_blackstone_bricks", new Opaque(new BID(Ids::POLISHED_BLACKSTONE_BRICKS), "Polished Blackstone Bricks", $blackstoneBreakInfo));
-		self::register("polished_blackstone_brick_slab", new Slab(new BID(Ids::POLISHED_BLACKSTONE_BRICK_SLAB), "Polished Blackstone Brick", $slabBreakInfo));
-		self::register("polished_blackstone_brick_stairs", new Stair(new BID(Ids::POLISHED_BLACKSTONE_BRICK_STAIRS), $prefix("Stairs"), $blackstoneBreakInfo));
-		self::register("polished_blackstone_brick_wall", new Wall(new BID(Ids::POLISHED_BLACKSTONE_BRICK_WALL), $prefix("Wall"), $blackstoneBreakInfo));
-		self::register("cracked_polished_blackstone_bricks", new Opaque(new BID(Ids::CRACKED_POLISHED_BLACKSTONE_BRICKS), "Cracked Polished Blackstone Bricks", $blackstoneBreakInfo));
+		self::register("polished_blackstone_bricks", fn(BID $id) => new Opaque($id, "Polished Blackstone Bricks", $blackstoneBreakInfo));
+		self::register("polished_blackstone_brick_slab", fn(BID $id) => new Slab($id, "Polished Blackstone Brick", $slabBreakInfo));
+		self::register("polished_blackstone_brick_stairs", fn(BID $id) => new Stair($id, $prefix("Stairs"), $blackstoneBreakInfo));
+		self::register("polished_blackstone_brick_wall", fn(BID $id) => new Wall($id, $prefix("Wall"), $blackstoneBreakInfo));
+		self::register("cracked_polished_blackstone_bricks", fn(BID $id) => new Opaque($id, "Cracked Polished Blackstone Bricks", $blackstoneBreakInfo));
 
-		self::register("soul_torch", new Torch(new BID(Ids::SOUL_TORCH), "Soul Torch", new Info(BreakInfo::instant())));
-		self::register("soul_fire", new SoulFire(new BID(Ids::SOUL_FIRE), "Soul Fire", new Info(BreakInfo::instant(), [Tags::FIRE])));
+		self::register("soul_torch", fn(BID $id) => new Torch($id, "Soul Torch", new Info(BreakInfo::instant())));
+		self::register("soul_fire", fn(BID $id) => new SoulFire($id, "Soul Fire", new Info(BreakInfo::instant(), [Tags::FIRE])));
 
 		//TODO: soul soul ought to have 0.5 hardness (as per java) but it's 1.0 in Bedrock (probably parity bug)
-		self::register("soul_soil", new Opaque(new BID(Ids::SOUL_SOIL), "Soul Soil", new Info(BreakInfo::shovel(1.0))));
+		self::register("soul_soil", fn(BID $id) => new Opaque($id, "Soul Soil", new Info(BreakInfo::shovel(1.0))));
 
-		self::register("shroomlight", new class(new BID(Ids::SHROOMLIGHT), "Shroomlight", new Info(new BreakInfo(1.0, ToolType::HOE))) extends Opaque{
+		self::register("shroomlight", fn(BID $id) => new class($id, "Shroomlight", new Info(new BreakInfo(1.0, ToolType::HOE))) extends Opaque{
 			public function getLightLevel() : int{ return 15; }
 		});
 
-		self::register("warped_wart_block", new Opaque(new BID(Ids::WARPED_WART_BLOCK), "Warped Wart Block", new Info(new BreakInfo(1.0, ToolType::HOE))));
-		self::register("crying_obsidian", new class(new BID(Ids::CRYING_OBSIDIAN), "Crying Obsidian", new Info(BreakInfo::pickaxe(35.0 /* 50 in Java */, ToolTier::DIAMOND, 6000.0))) extends Opaque{
+		self::register("warped_wart_block", fn(BID $id) => new Opaque($id, "Warped Wart Block", new Info(new BreakInfo(1.0, ToolType::HOE))));
+		self::register("crying_obsidian", fn(BID $id) => new class($id, "Crying Obsidian", new Info(BreakInfo::pickaxe(35.0 /* 50 in Java */, ToolTier::DIAMOND, 6000.0))) extends Opaque{
 			public function getLightLevel() : int{ return 10;}
 		});
 
-		self::register("twisting_vines", new NetherVines(new BID(Ids::TWISTING_VINES), "Twisting Vines", new Info(BreakInfo::instant()), Facing::UP));
-		self::register("weeping_vines", new NetherVines(new BID(Ids::WEEPING_VINES), "Weeping Vines", new Info(BreakInfo::instant()), Facing::DOWN));
+		self::register("twisting_vines", fn(BID $id) => new NetherVines($id, "Twisting Vines", new Info(BreakInfo::instant()), Facing::UP));
+		self::register("weeping_vines", fn(BID $id) => new NetherVines($id, "Weeping Vines", new Info(BreakInfo::instant()), Facing::DOWN));
 
 		$netherRootsInfo = new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS]);
-		self::register("crimson_roots", new NetherRoots(new BID(Ids::CRIMSON_ROOTS), "Crimson Roots", $netherRootsInfo));
-		self::register("warped_roots", new NetherRoots(new BID(Ids::WARPED_ROOTS), "Warped Roots", $netherRootsInfo));
+		self::register("crimson_roots", fn(BID $id) => new NetherRoots($id, "Crimson Roots", $netherRootsInfo));
+		self::register("warped_roots", fn(BID $id) => new NetherRoots($id, "Warped Roots", $netherRootsInfo));
 
-		self::register("chain", new Chain(new BID(Ids::CHAIN), "Chain", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))));
+		self::register("chain", fn(BID $id) => new Chain($id, "Chain", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))));
 	}
 
 	private static function registerBlocksR17() : void{
 		//in java this can be acquired using any tool - seems to be a parity issue in bedrock
 		$amethystInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD));
-		self::register("amethyst", new class(new BID(Ids::AMETHYST), "Amethyst", $amethystInfo) extends Opaque{
+		self::register("amethyst", fn(BID $id) => new class($id, "Amethyst", $amethystInfo) extends Opaque{
 			use AmethystTrait;
 		});
-		self::register("budding_amethyst", new BuddingAmethyst(new BID(Ids::BUDDING_AMETHYST), "Budding Amethyst", $amethystInfo));
-		self::register("amethyst_cluster", new AmethystCluster(new BID(Ids::AMETHYST_CLUSTER), "Amethyst Cluster", $amethystInfo));
+		self::register("budding_amethyst", fn(BID $id) => new BuddingAmethyst($id, "Budding Amethyst", $amethystInfo));
+		self::register("amethyst_cluster", fn(BID $id) => new AmethystCluster($id, "Amethyst Cluster", $amethystInfo));
 
-		self::register("calcite", new Opaque(new BID(Ids::CALCITE), "Calcite", new Info(BreakInfo::pickaxe(0.75, ToolTier::WOOD))));
+		self::register("calcite", fn(BID $id) => new Opaque($id, "Calcite", new Info(BreakInfo::pickaxe(0.75, ToolTier::WOOD))));
 
-		self::register("raw_copper", new Opaque(new BID(Ids::RAW_COPPER), "Raw Copper Block", new Info(BreakInfo::pickaxe(5, ToolTier::STONE, 30.0))));
-		self::register("raw_gold", new Opaque(new BID(Ids::RAW_GOLD), "Raw Gold Block", new Info(BreakInfo::pickaxe(5, ToolTier::IRON, 30.0))));
-		self::register("raw_iron", new Opaque(new BID(Ids::RAW_IRON), "Raw Iron Block", new Info(BreakInfo::pickaxe(5, ToolTier::STONE, 30.0))));
+		self::register("raw_copper", fn(BID $id) => new Opaque($id, "Raw Copper Block", new Info(BreakInfo::pickaxe(5, ToolTier::STONE, 30.0))));
+		self::register("raw_gold", fn(BID $id) => new Opaque($id, "Raw Gold Block", new Info(BreakInfo::pickaxe(5, ToolTier::IRON, 30.0))));
+		self::register("raw_iron", fn(BID $id) => new Opaque($id, "Raw Iron Block", new Info(BreakInfo::pickaxe(5, ToolTier::STONE, 30.0))));
 
 		$deepslateBreakInfo = new Info(BreakInfo::pickaxe(3, ToolTier::WOOD, 18.0));
-		self::register("deepslate", new class(new BID(Ids::DEEPSLATE), "Deepslate", $deepslateBreakInfo) extends SimplePillar{
+		self::register("deepslate", fn(BID $id) => new class($id, "Deepslate", $deepslateBreakInfo) extends SimplePillar{
 			public function getDropsForCompatibleTool(Item $item) : array{
 				return [VanillaBlocks::COBBLED_DEEPSLATE()->asItem()];
 			}
@@ -1612,113 +1647,113 @@ public function isAffectedBySilkTouch() : bool{
 		});
 
 		//TODO: parity issue here - in Java this has a hardness of 3.0, but in bedrock it's 3.5
-		self::register("chiseled_deepslate", new Opaque(new BID(Ids::CHISELED_DEEPSLATE), "Chiseled Deepslate", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0))));
+		self::register("chiseled_deepslate", fn(BID $id) => new Opaque($id, "Chiseled Deepslate", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0))));
 
 		$deepslateBrickBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0));
-		self::register("deepslate_bricks", new Opaque(new BID(Ids::DEEPSLATE_BRICKS), "Deepslate Bricks", $deepslateBrickBreakInfo));
-		self::register("deepslate_brick_slab", new Slab(new BID(Ids::DEEPSLATE_BRICK_SLAB), "Deepslate Brick", $deepslateBrickBreakInfo));
-		self::register("deepslate_brick_stairs", new Stair(new BID(Ids::DEEPSLATE_BRICK_STAIRS), "Deepslate Brick Stairs", $deepslateBrickBreakInfo));
-		self::register("deepslate_brick_wall", new Wall(new BID(Ids::DEEPSLATE_BRICK_WALL), "Deepslate Brick Wall", $deepslateBrickBreakInfo));
-		self::register("cracked_deepslate_bricks", new Opaque(new BID(Ids::CRACKED_DEEPSLATE_BRICKS), "Cracked Deepslate Bricks", $deepslateBrickBreakInfo));
+		self::register("deepslate_bricks", fn(BID $id) => new Opaque($id, "Deepslate Bricks", $deepslateBrickBreakInfo));
+		self::register("deepslate_brick_slab", fn(BID $id) => new Slab($id, "Deepslate Brick", $deepslateBrickBreakInfo));
+		self::register("deepslate_brick_stairs", fn(BID $id) => new Stair($id, "Deepslate Brick Stairs", $deepslateBrickBreakInfo));
+		self::register("deepslate_brick_wall", fn(BID $id) => new Wall($id, "Deepslate Brick Wall", $deepslateBrickBreakInfo));
+		self::register("cracked_deepslate_bricks", fn(BID $id) => new Opaque($id, "Cracked Deepslate Bricks", $deepslateBrickBreakInfo));
 
 		$deepslateTilesBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0));
-		self::register("deepslate_tiles", new Opaque(new BID(Ids::DEEPSLATE_TILES), "Deepslate Tiles", $deepslateTilesBreakInfo));
-		self::register("deepslate_tile_slab", new Slab(new BID(Ids::DEEPSLATE_TILE_SLAB), "Deepslate Tile", $deepslateTilesBreakInfo));
-		self::register("deepslate_tile_stairs", new Stair(new BID(Ids::DEEPSLATE_TILE_STAIRS), "Deepslate Tile Stairs", $deepslateTilesBreakInfo));
-		self::register("deepslate_tile_wall", new Wall(new BID(Ids::DEEPSLATE_TILE_WALL), "Deepslate Tile Wall", $deepslateTilesBreakInfo));
-		self::register("cracked_deepslate_tiles", new Opaque(new BID(Ids::CRACKED_DEEPSLATE_TILES), "Cracked Deepslate Tiles", $deepslateTilesBreakInfo));
+		self::register("deepslate_tiles", fn(BID $id) => new Opaque($id, "Deepslate Tiles", $deepslateTilesBreakInfo));
+		self::register("deepslate_tile_slab", fn(BID $id) => new Slab($id, "Deepslate Tile", $deepslateTilesBreakInfo));
+		self::register("deepslate_tile_stairs", fn(BID $id) => new Stair($id, "Deepslate Tile Stairs", $deepslateTilesBreakInfo));
+		self::register("deepslate_tile_wall", fn(BID $id) => new Wall($id, "Deepslate Tile Wall", $deepslateTilesBreakInfo));
+		self::register("cracked_deepslate_tiles", fn(BID $id) => new Opaque($id, "Cracked Deepslate Tiles", $deepslateTilesBreakInfo));
 
 		$cobbledDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0));
-		self::register("cobbled_deepslate", new Opaque(new BID(Ids::COBBLED_DEEPSLATE), "Cobbled Deepslate", $cobbledDeepslateBreakInfo));
-		self::register("cobbled_deepslate_slab", new Slab(new BID(Ids::COBBLED_DEEPSLATE_SLAB), "Cobbled Deepslate", $cobbledDeepslateBreakInfo));
-		self::register("cobbled_deepslate_stairs", new Stair(new BID(Ids::COBBLED_DEEPSLATE_STAIRS), "Cobbled Deepslate Stairs", $cobbledDeepslateBreakInfo));
-		self::register("cobbled_deepslate_wall", new Wall(new BID(Ids::COBBLED_DEEPSLATE_WALL), "Cobbled Deepslate Wall", $cobbledDeepslateBreakInfo));
+		self::register("cobbled_deepslate", fn(BID $id) => new Opaque($id, "Cobbled Deepslate", $cobbledDeepslateBreakInfo));
+		self::register("cobbled_deepslate_slab", fn(BID $id) => new Slab($id, "Cobbled Deepslate", $cobbledDeepslateBreakInfo));
+		self::register("cobbled_deepslate_stairs", fn(BID $id) => new Stair($id, "Cobbled Deepslate Stairs", $cobbledDeepslateBreakInfo));
+		self::register("cobbled_deepslate_wall", fn(BID $id) => new Wall($id, "Cobbled Deepslate Wall", $cobbledDeepslateBreakInfo));
 
 		$polishedDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0));
-		self::register("polished_deepslate", new Opaque(new BID(Ids::POLISHED_DEEPSLATE), "Polished Deepslate", $polishedDeepslateBreakInfo));
-		self::register("polished_deepslate_slab", new Slab(new BID(Ids::POLISHED_DEEPSLATE_SLAB), "Polished Deepslate", $polishedDeepslateBreakInfo));
-		self::register("polished_deepslate_stairs", new Stair(new BID(Ids::POLISHED_DEEPSLATE_STAIRS), "Polished Deepslate Stairs", $polishedDeepslateBreakInfo));
-		self::register("polished_deepslate_wall", new Wall(new BID(Ids::POLISHED_DEEPSLATE_WALL), "Polished Deepslate Wall", $polishedDeepslateBreakInfo));
+		self::register("polished_deepslate", fn(BID $id) => new Opaque($id, "Polished Deepslate", $polishedDeepslateBreakInfo));
+		self::register("polished_deepslate_slab", fn(BID $id) => new Slab($id, "Polished Deepslate", $polishedDeepslateBreakInfo));
+		self::register("polished_deepslate_stairs", fn(BID $id) => new Stair($id, "Polished Deepslate Stairs", $polishedDeepslateBreakInfo));
+		self::register("polished_deepslate_wall", fn(BID $id) => new Wall($id, "Polished Deepslate Wall", $polishedDeepslateBreakInfo));
 
-		self::register("tinted_glass", new TintedGlass(new BID(Ids::TINTED_GLASS), "Tinted Glass", new Info(new BreakInfo(0.3))));
+		self::register("tinted_glass", fn(BID $id) => new TintedGlass($id, "Tinted Glass", new Info(new BreakInfo(0.3))));
 
 		//blast resistance should be 30 if we were matched with java :(
 		$copperBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 18.0));
-		self::register("lightning_rod", new LightningRod(new BID(Ids::LIGHTNING_ROD), "Lightning Rod", $copperBreakInfo));
+		self::register("lightning_rod", fn(BID $id) => new LightningRod($id, "Lightning Rod", $copperBreakInfo));
 
-		self::register("copper", new Copper(new BID(Ids::COPPER), "Copper Block", $copperBreakInfo));
-		self::register("chiseled_copper", new Copper(new BID(Ids::CHISELED_COPPER), "Chiseled Copper", $copperBreakInfo));
-		self::register("copper_grate", new CopperGrate(new BID(Ids::COPPER_GRATE), "Copper Grate", $copperBreakInfo));
-		self::register("cut_copper", new Copper(new BID(Ids::CUT_COPPER), "Cut Copper Block", $copperBreakInfo));
-		self::register("cut_copper_slab", new CopperSlab(new BID(Ids::CUT_COPPER_SLAB), "Cut Copper Slab", $copperBreakInfo));
-		self::register("cut_copper_stairs", new CopperStairs(new BID(Ids::CUT_COPPER_STAIRS), "Cut Copper Stairs", $copperBreakInfo));
-		self::register("copper_bulb", new CopperBulb(new BID(Ids::COPPER_BULB), "Copper Bulb", $copperBreakInfo));
+		self::register("copper", fn(BID $id) => new Copper($id, "Copper Block", $copperBreakInfo));
+		self::register("chiseled_copper", fn(BID $id) => new Copper($id, "Chiseled Copper", $copperBreakInfo));
+		self::register("copper_grate", fn(BID $id) => new CopperGrate($id, "Copper Grate", $copperBreakInfo));
+		self::register("cut_copper", fn(BID $id) => new Copper($id, "Cut Copper Block", $copperBreakInfo));
+		self::register("cut_copper_slab", fn(BID $id) => new CopperSlab($id, "Cut Copper Slab", $copperBreakInfo));
+		self::register("cut_copper_stairs", fn(BID $id) => new CopperStairs($id, "Cut Copper Stairs", $copperBreakInfo));
+		self::register("copper_bulb", fn(BID $id) => new CopperBulb($id, "Copper Bulb", $copperBreakInfo));
 
 		$copperDoorBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 30.0));
-		self::register("copper_door", new CopperDoor(new BID(Ids::COPPER_DOOR), "Copper Door", $copperDoorBreakInfo));
-		self::register("copper_trapdoor", new CopperTrapdoor(new BID(Ids::COPPER_TRAPDOOR), "Copper Trapdoor", $copperDoorBreakInfo));
+		self::register("copper_door", fn(BID $id) => new CopperDoor($id, "Copper Door", $copperDoorBreakInfo));
+		self::register("copper_trapdoor", fn(BID $id) => new CopperTrapdoor($id, "Copper Trapdoor", $copperDoorBreakInfo));
 
 		$candleBreakInfo = new Info(new BreakInfo(0.1));
-		self::register("candle", new Candle(new BID(Ids::CANDLE), "Candle", $candleBreakInfo));
-		self::register("dyed_candle", new DyedCandle(new BID(Ids::DYED_CANDLE), "Dyed Candle", $candleBreakInfo));
+		self::register("candle", fn(BID $id) => new Candle($id, "Candle", $candleBreakInfo));
+		self::register("dyed_candle", fn(BID $id) => new DyedCandle($id, "Dyed Candle", $candleBreakInfo));
 
 		//TODO: duplicated break info :(
 		$cakeBreakInfo = new Info(new BreakInfo(0.5));
-		self::register("cake_with_candle", new CakeWithCandle(new BID(Ids::CAKE_WITH_CANDLE), "Cake With Candle", $cakeBreakInfo));
-		self::register("cake_with_dyed_candle", new CakeWithDyedCandle(new BID(Ids::CAKE_WITH_DYED_CANDLE), "Cake With Dyed Candle", $cakeBreakInfo));
+		self::register("cake_with_candle", fn(BID $id) => new CakeWithCandle($id, "Cake With Candle", $cakeBreakInfo));
+		self::register("cake_with_dyed_candle", fn(BID $id) => new CakeWithDyedCandle($id, "Cake With Dyed Candle", $cakeBreakInfo));
 
-		self::register("hanging_roots", new HangingRoots(new BID(Ids::HANGING_ROOTS), "Hanging Roots", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
+		self::register("hanging_roots", fn(BID $id) => new HangingRoots($id, "Hanging Roots", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
 
-		self::register("cave_vines", new CaveVines(new BID(Ids::CAVE_VINES), "Cave Vines", new Info(BreakInfo::instant())));
+		self::register("cave_vines", fn(BID $id) => new CaveVines($id, "Cave Vines", new Info(BreakInfo::instant())));
 
-		self::register("small_dripleaf", new SmallDripleaf(new BID(Ids::SMALL_DRIPLEAF), "Small Dripleaf", new Info(BreakInfo::instant(ToolType::SHEARS, toolHarvestLevel: 1))));
-		self::register("big_dripleaf_head", new BigDripleafHead(new BID(Ids::BIG_DRIPLEAF_HEAD), "Big Dripleaf", new Info(BreakInfo::instant())));
-		self::register("big_dripleaf_stem", new BigDripleafStem(new BID(Ids::BIG_DRIPLEAF_STEM), "Big Dripleaf Stem", new Info(BreakInfo::instant())));
+		self::register("small_dripleaf", fn(BID $id) => new SmallDripleaf($id, "Small Dripleaf", new Info(BreakInfo::instant(ToolType::SHEARS, toolHarvestLevel: 1))));
+		self::register("big_dripleaf_head", fn(BID $id) => new BigDripleafHead($id, "Big Dripleaf", new Info(BreakInfo::instant())));
+		self::register("big_dripleaf_stem", fn(BID $id) => new BigDripleafStem($id, "Big Dripleaf Stem", new Info(BreakInfo::instant())));
 	}
 
 	private static function registerBlocksR18() : void{
-		self::register("spore_blossom", new SporeBlossom(new BID(Ids::SPORE_BLOSSOM), "Spore Blossom", new Info(BreakInfo::instant())));
+		self::register("spore_blossom", fn(BID $id) => new SporeBlossom($id, "Spore Blossom", new Info(BreakInfo::instant())));
 	}
 
 	private static function registerMudBlocks() : void{
-		self::register("mud", new Opaque(new BID(Ids::MUD), "Mud", new Info(BreakInfo::shovel(0.5), [Tags::MUD])));
-		self::register("packed_mud", new Opaque(new BID(Ids::PACKED_MUD), "Packed Mud", new Info(BreakInfo::pickaxe(1.0, null, 15.0))));
+		self::register("mud", fn(BID $id) => new Opaque($id, "Mud", new Info(BreakInfo::shovel(0.5), [Tags::MUD])));
+		self::register("packed_mud", fn(BID $id) => new Opaque($id, "Packed Mud", new Info(BreakInfo::pickaxe(1.0, null, 15.0))));
 
 		$mudBricksBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
 
-		self::register("mud_bricks", new Opaque(new BID(Ids::MUD_BRICKS), "Mud Bricks", $mudBricksBreakInfo));
-		self::register("mud_brick_slab", new Slab(new BID(Ids::MUD_BRICK_SLAB), "Mud Brick", $mudBricksBreakInfo));
-		self::register("mud_brick_stairs", new Stair(new BID(Ids::MUD_BRICK_STAIRS), "Mud Brick Stairs", $mudBricksBreakInfo));
-		self::register("mud_brick_wall", new Wall(new BID(Ids::MUD_BRICK_WALL), "Mud Brick Wall", $mudBricksBreakInfo));
+		self::register("mud_bricks", fn(BID $id) => new Opaque($id, "Mud Bricks", $mudBricksBreakInfo));
+		self::register("mud_brick_slab", fn(BID $id) => new Slab($id, "Mud Brick", $mudBricksBreakInfo));
+		self::register("mud_brick_stairs", fn(BID $id) => new Stair($id, "Mud Brick Stairs", $mudBricksBreakInfo));
+		self::register("mud_brick_wall", fn(BID $id) => new Wall($id, "Mud Brick Wall", $mudBricksBreakInfo));
 	}
 
 	private static function registerTuffBlocks() : void{
 		$tuffBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
 
-		self::register("tuff", new Opaque(new BID(Ids::TUFF), "Tuff", $tuffBreakInfo));
-		self::register("tuff_slab", new Slab(new BID(Ids::TUFF_SLAB), "Tuff", $tuffBreakInfo));
-		self::register("tuff_stairs", new Stair(new BID(Ids::TUFF_STAIRS), "Tuff Stairs", $tuffBreakInfo));
-		self::register("tuff_wall", new Wall(new BID(Ids::TUFF_WALL), "Tuff Wall", $tuffBreakInfo));
-		self::register("chiseled_tuff", new Opaque(new BID(Ids::CHISELED_TUFF), "Chiseled Tuff", $tuffBreakInfo));
-
-		self::register("tuff_bricks", new Opaque(new BID(Ids::TUFF_BRICKS), "Tuff Bricks", $tuffBreakInfo));
-		self::register("tuff_brick_slab", new Slab(new BID(Ids::TUFF_BRICK_SLAB), "Tuff Brick", $tuffBreakInfo));
-		self::register("tuff_brick_stairs", new Stair(new BID(Ids::TUFF_BRICK_STAIRS), "Tuff Brick Stairs", $tuffBreakInfo));
-		self::register("tuff_brick_wall", new Wall(new BID(Ids::TUFF_BRICK_WALL), "Tuff Brick Wall", $tuffBreakInfo));
-		self::register("chiseled_tuff_bricks", new Opaque(new BID(Ids::CHISELED_TUFF_BRICKS), "Chiseled Tuff Bricks", $tuffBreakInfo));
-
-		self::register("polished_tuff", new Opaque(new BID(Ids::POLISHED_TUFF), "Polished Tuff", $tuffBreakInfo));
-		self::register("polished_tuff_slab", new Slab(new BID(Ids::POLISHED_TUFF_SLAB), "Polished Tuff", $tuffBreakInfo));
-		self::register("polished_tuff_stairs", new Stair(new BID(Ids::POLISHED_TUFF_STAIRS), "Polished Tuff Stairs", $tuffBreakInfo));
-		self::register("polished_tuff_wall", new Wall(new BID(Ids::POLISHED_TUFF_WALL), "Polished Tuff Wall", $tuffBreakInfo));
+		self::register("tuff", fn(BID $id) => new Opaque($id, "Tuff", $tuffBreakInfo));
+		self::register("tuff_slab", fn(BID $id) => new Slab($id, "Tuff", $tuffBreakInfo));
+		self::register("tuff_stairs", fn(BID $id) => new Stair($id, "Tuff Stairs", $tuffBreakInfo));
+		self::register("tuff_wall", fn(BID $id) => new Wall($id, "Tuff Wall", $tuffBreakInfo));
+		self::register("chiseled_tuff", fn(BID $id) => new Opaque($id, "Chiseled Tuff", $tuffBreakInfo));
+
+		self::register("tuff_bricks", fn(BID $id) => new Opaque($id, "Tuff Bricks", $tuffBreakInfo));
+		self::register("tuff_brick_slab", fn(BID $id) => new Slab($id, "Tuff Brick", $tuffBreakInfo));
+		self::register("tuff_brick_stairs", fn(BID $id) => new Stair($id, "Tuff Brick Stairs", $tuffBreakInfo));
+		self::register("tuff_brick_wall", fn(BID $id) => new Wall($id, "Tuff Brick Wall", $tuffBreakInfo));
+		self::register("chiseled_tuff_bricks", fn(BID $id) => new Opaque($id, "Chiseled Tuff Bricks", $tuffBreakInfo));
+
+		self::register("polished_tuff", fn(BID $id) => new Opaque($id, "Polished Tuff", $tuffBreakInfo));
+		self::register("polished_tuff_slab", fn(BID $id) => new Slab($id, "Polished Tuff", $tuffBreakInfo));
+		self::register("polished_tuff_stairs", fn(BID $id) => new Stair($id, "Polished Tuff Stairs", $tuffBreakInfo));
+		self::register("polished_tuff_wall", fn(BID $id) => new Wall($id, "Polished Tuff Wall", $tuffBreakInfo));
 	}
 
 	private static function registerCauldronBlocks() : void{
 		$cauldronBreakInfo = new Info(BreakInfo::pickaxe(2, ToolTier::WOOD));
 
-		self::register("cauldron", new Cauldron(new BID(Ids::CAULDRON, TileCauldron::class), "Cauldron", $cauldronBreakInfo));
-		self::register("water_cauldron", new WaterCauldron(new BID(Ids::WATER_CAULDRON, TileCauldron::class), "Water Cauldron", $cauldronBreakInfo));
-		self::register("lava_cauldron", new LavaCauldron(new BID(Ids::LAVA_CAULDRON, TileCauldron::class), "Lava Cauldron", $cauldronBreakInfo));
-		self::register("potion_cauldron", new PotionCauldron(new BID(Ids::POTION_CAULDRON, TileCauldron::class), "Potion Cauldron", $cauldronBreakInfo));
+		self::register("cauldron", fn(BID $id) => new Cauldron($id, "Cauldron", $cauldronBreakInfo), TileCauldron::class);
+		self::register("water_cauldron", fn(BID $id) => new WaterCauldron($id, "Water Cauldron", $cauldronBreakInfo), TileCauldron::class);
+		self::register("lava_cauldron", fn(BID $id) => new LavaCauldron($id, "Lava Cauldron", $cauldronBreakInfo), TileCauldron::class);
+		self::register("potion_cauldron", fn(BID $id) => new PotionCauldron($id, "Potion Cauldron", $cauldronBreakInfo), TileCauldron::class);
 	}
 }
diff --git a/src/block/WoodLikeBlockIdHelper.php b/src/block/WoodLikeBlockIdHelper.php
deleted file mode 100644
index 88fdff3a6ec..00000000000
--- a/src/block/WoodLikeBlockIdHelper.php
+++ /dev/null
@@ -1,263 +0,0 @@
-<?php
-
-/*
- *
- *  ____            _        _   __  __ _                  __  __ ____
- * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
- * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
- * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
- * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * @author PocketMine Team
- * @link http://www.pocketmine.net/
- *
- *
- */
-
-declare(strict_types=1);
-
-namespace pocketmine\block;
-
-use pocketmine\block\BlockIdentifier as BID;
-use pocketmine\block\BlockTypeIds as Ids;
-use pocketmine\block\tile\Sign as TileSign;
-use pocketmine\block\utils\LeavesType;
-use pocketmine\block\utils\SaplingType;
-use pocketmine\block\utils\WoodType;
-use pocketmine\item\VanillaItems;
-
-/**
- * All wood-like blocks have different IDs for different wood types.
- *
- * We can't make these dynamic, because some types of wood have different type properties (e.g. crimson and warped planks
- * are not flammable, but all other planks are).
- *
- * In the future, it's entirely within the realm of reason that the other types of wood may differ in other ways, such
- * as flammability, hardness, required tool tier, etc.
- * Therefore, to stay on the safe side of Mojang, wood-like blocks have static types. This does unfortunately generate
- * a lot of ugly code.
- *
- * @internal
- */
-final class WoodLikeBlockIdHelper{
-
-	public static function getPlanksIdentifier(WoodType $type) : BID{
-		return new BID(match($type){
-			WoodType::OAK => Ids::OAK_PLANKS,
-			WoodType::SPRUCE => Ids::SPRUCE_PLANKS,
-			WoodType::BIRCH => Ids::BIRCH_PLANKS,
-			WoodType::JUNGLE => Ids::JUNGLE_PLANKS,
-			WoodType::ACACIA => Ids::ACACIA_PLANKS,
-			WoodType::DARK_OAK => Ids::DARK_OAK_PLANKS,
-			WoodType::MANGROVE => Ids::MANGROVE_PLANKS,
-			WoodType::CRIMSON => Ids::CRIMSON_PLANKS,
-			WoodType::WARPED => Ids::WARPED_PLANKS,
-			WoodType::CHERRY => Ids::CHERRY_PLANKS,
-		});
-	}
-
-	public static function getFenceIdentifier(WoodType $type) : BID{
-		return new BID(match($type){
-			WoodType::OAK => Ids::OAK_FENCE,
-			WoodType::SPRUCE => Ids::SPRUCE_FENCE,
-			WoodType::BIRCH => Ids::BIRCH_FENCE,
-			WoodType::JUNGLE => Ids::JUNGLE_FENCE,
-			WoodType::ACACIA => Ids::ACACIA_FENCE,
-			WoodType::DARK_OAK => Ids::DARK_OAK_FENCE,
-			WoodType::MANGROVE => Ids::MANGROVE_FENCE,
-			WoodType::CRIMSON => Ids::CRIMSON_FENCE,
-			WoodType::WARPED => Ids::WARPED_FENCE,
-			WoodType::CHERRY => Ids::CHERRY_FENCE,
-		});
-	}
-
-	public static function getSlabIdentifier(WoodType $type) : BID{
-		return new BID(match($type){
-			WoodType::OAK => Ids::OAK_SLAB,
-			WoodType::SPRUCE => Ids::SPRUCE_SLAB,
-			WoodType::BIRCH => Ids::BIRCH_SLAB,
-			WoodType::JUNGLE => Ids::JUNGLE_SLAB,
-			WoodType::ACACIA => Ids::ACACIA_SLAB,
-			WoodType::DARK_OAK => Ids::DARK_OAK_SLAB,
-			WoodType::MANGROVE => Ids::MANGROVE_SLAB,
-			WoodType::CRIMSON => Ids::CRIMSON_SLAB,
-			WoodType::WARPED => Ids::WARPED_SLAB,
-			WoodType::CHERRY => Ids::CHERRY_SLAB,
-		});
-	}
-
-	public static function getLogIdentifier(WoodType $treeType) : BID{
-		return new BID(match($treeType){
-			WoodType::OAK => Ids::OAK_LOG,
-			WoodType::SPRUCE => Ids::SPRUCE_LOG,
-			WoodType::BIRCH => Ids::BIRCH_LOG,
-			WoodType::JUNGLE => Ids::JUNGLE_LOG,
-			WoodType::ACACIA => Ids::ACACIA_LOG,
-			WoodType::DARK_OAK => Ids::DARK_OAK_LOG,
-			WoodType::MANGROVE => Ids::MANGROVE_LOG,
-			WoodType::CRIMSON => Ids::CRIMSON_STEM,
-			WoodType::WARPED => Ids::WARPED_STEM,
-			WoodType::CHERRY => Ids::CHERRY_LOG,
-		});
-	}
-
-	public static function getAllSidedLogIdentifier(WoodType $treeType) : BID{
-		return new BID(match($treeType){
-			WoodType::OAK => Ids::OAK_WOOD,
-			WoodType::SPRUCE => Ids::SPRUCE_WOOD,
-			WoodType::BIRCH => Ids::BIRCH_WOOD,
-			WoodType::JUNGLE => Ids::JUNGLE_WOOD,
-			WoodType::ACACIA => Ids::ACACIA_WOOD,
-			WoodType::DARK_OAK => Ids::DARK_OAK_WOOD,
-			WoodType::MANGROVE => Ids::MANGROVE_WOOD,
-			WoodType::CRIMSON => Ids::CRIMSON_HYPHAE,
-			WoodType::WARPED => Ids::WARPED_HYPHAE,
-			WoodType::CHERRY => Ids::CHERRY_WOOD,
-		});
-	}
-
-	public static function getLeavesIdentifier(LeavesType $leavesType) : BID{
-		return new BID(match($leavesType){
-			LeavesType::OAK => Ids::OAK_LEAVES,
-			LeavesType::SPRUCE => Ids::SPRUCE_LEAVES,
-			LeavesType::BIRCH => Ids::BIRCH_LEAVES,
-			LeavesType::JUNGLE => Ids::JUNGLE_LEAVES,
-			LeavesType::ACACIA => Ids::ACACIA_LEAVES,
-			LeavesType::DARK_OAK => Ids::DARK_OAK_LEAVES,
-			LeavesType::MANGROVE => Ids::MANGROVE_LEAVES,
-			LeavesType::AZALEA => Ids::AZALEA_LEAVES,
-			LeavesType::FLOWERING_AZALEA => Ids::FLOWERING_AZALEA_LEAVES,
-			LeavesType::CHERRY => Ids::CHERRY_LEAVES,
-		});
-	}
-
-	public static function getSaplingIdentifier(SaplingType $treeType) : BID{
-		return new BID(match($treeType){
-			SaplingType::OAK => Ids::OAK_SAPLING,
-			SaplingType::SPRUCE => Ids::SPRUCE_SAPLING,
-			SaplingType::BIRCH => Ids::BIRCH_SAPLING,
-			SaplingType::JUNGLE => Ids::JUNGLE_SAPLING,
-			SaplingType::ACACIA => Ids::ACACIA_SAPLING,
-			SaplingType::DARK_OAK => Ids::DARK_OAK_SAPLING,
-		});
-	}
-
-	/**
-	 * @return BID[]|\Closure[]
-	 * @phpstan-return array{BID, BID, \Closure() : \pocketmine\item\Item}
-	 */
-	public static function getSignInfo(WoodType $treeType) : array{
-		$make = fn(int $floorId, int $wallId, \Closure $getItem) => [
-			new BID($floorId, TileSign::class),
-			new BID($wallId, TileSign::class),
-			$getItem
-		];
-		return match($treeType){
-			WoodType::OAK => $make(Ids::OAK_SIGN, Ids::OAK_WALL_SIGN, fn() => VanillaItems::OAK_SIGN()),
-			WoodType::SPRUCE => $make(Ids::SPRUCE_SIGN, Ids::SPRUCE_WALL_SIGN, fn() => VanillaItems::SPRUCE_SIGN()),
-			WoodType::BIRCH => $make(Ids::BIRCH_SIGN, Ids::BIRCH_WALL_SIGN, fn() => VanillaItems::BIRCH_SIGN()),
-			WoodType::JUNGLE => $make(Ids::JUNGLE_SIGN, Ids::JUNGLE_WALL_SIGN, fn() => VanillaItems::JUNGLE_SIGN()),
-			WoodType::ACACIA => $make(Ids::ACACIA_SIGN, Ids::ACACIA_WALL_SIGN, fn() => VanillaItems::ACACIA_SIGN()),
-			WoodType::DARK_OAK => $make(Ids::DARK_OAK_SIGN, Ids::DARK_OAK_WALL_SIGN, fn() => VanillaItems::DARK_OAK_SIGN()),
-			WoodType::MANGROVE => $make(Ids::MANGROVE_SIGN, Ids::MANGROVE_WALL_SIGN, fn() => VanillaItems::MANGROVE_SIGN()),
-			WoodType::CRIMSON => $make(Ids::CRIMSON_SIGN, Ids::CRIMSON_WALL_SIGN, fn() => VanillaItems::CRIMSON_SIGN()),
-			WoodType::WARPED => $make(Ids::WARPED_SIGN, Ids::WARPED_WALL_SIGN, fn() => VanillaItems::WARPED_SIGN()),
-			WoodType::CHERRY => $make(Ids::CHERRY_SIGN, Ids::CHERRY_WALL_SIGN, fn() => VanillaItems::CHERRY_SIGN()),
-		};
-	}
-
-	public static function getTrapdoorIdentifier(WoodType $treeType) : BID{
-		return new BID(match($treeType){
-			WoodType::OAK => Ids::OAK_TRAPDOOR,
-			WoodType::SPRUCE => Ids::SPRUCE_TRAPDOOR,
-			WoodType::BIRCH => Ids::BIRCH_TRAPDOOR,
-			WoodType::JUNGLE => Ids::JUNGLE_TRAPDOOR,
-			WoodType::ACACIA => Ids::ACACIA_TRAPDOOR,
-			WoodType::DARK_OAK => Ids::DARK_OAK_TRAPDOOR,
-			WoodType::MANGROVE => Ids::MANGROVE_TRAPDOOR,
-			WoodType::CRIMSON => Ids::CRIMSON_TRAPDOOR,
-			WoodType::WARPED => Ids::WARPED_TRAPDOOR,
-			WoodType::CHERRY => Ids::CHERRY_TRAPDOOR,
-		});
-	}
-
-	public static function getButtonIdentifier(WoodType $treeType) : BID{
-		return new BID(match($treeType){
-			WoodType::OAK => Ids::OAK_BUTTON,
-			WoodType::SPRUCE => Ids::SPRUCE_BUTTON,
-			WoodType::BIRCH => Ids::BIRCH_BUTTON,
-			WoodType::JUNGLE => Ids::JUNGLE_BUTTON,
-			WoodType::ACACIA => Ids::ACACIA_BUTTON,
-			WoodType::DARK_OAK => Ids::DARK_OAK_BUTTON,
-			WoodType::MANGROVE => Ids::MANGROVE_BUTTON,
-			WoodType::CRIMSON => Ids::CRIMSON_BUTTON,
-			WoodType::WARPED => Ids::WARPED_BUTTON,
-			WoodType::CHERRY => Ids::CHERRY_BUTTON,
-		});
-	}
-
-	public static function getPressurePlateIdentifier(WoodType $treeType) : BID{
-		return new BID(match($treeType){
-			WoodType::OAK => Ids::OAK_PRESSURE_PLATE,
-			WoodType::SPRUCE => Ids::SPRUCE_PRESSURE_PLATE,
-			WoodType::BIRCH => Ids::BIRCH_PRESSURE_PLATE,
-			WoodType::JUNGLE => Ids::JUNGLE_PRESSURE_PLATE,
-			WoodType::ACACIA => Ids::ACACIA_PRESSURE_PLATE,
-			WoodType::DARK_OAK => Ids::DARK_OAK_PRESSURE_PLATE,
-			WoodType::MANGROVE => Ids::MANGROVE_PRESSURE_PLATE,
-			WoodType::CRIMSON => Ids::CRIMSON_PRESSURE_PLATE,
-			WoodType::WARPED => Ids::WARPED_PRESSURE_PLATE,
-			WoodType::CHERRY => Ids::CHERRY_PRESSURE_PLATE,
-		});
-	}
-
-	public static function getDoorIdentifier(WoodType $treeType) : BID{
-		return new BID(match($treeType){
-			WoodType::OAK => Ids::OAK_DOOR,
-			WoodType::SPRUCE => Ids::SPRUCE_DOOR,
-			WoodType::BIRCH => Ids::BIRCH_DOOR,
-			WoodType::JUNGLE => Ids::JUNGLE_DOOR,
-			WoodType::ACACIA => Ids::ACACIA_DOOR,
-			WoodType::DARK_OAK => Ids::DARK_OAK_DOOR,
-			WoodType::MANGROVE => Ids::MANGROVE_DOOR,
-			WoodType::CRIMSON => Ids::CRIMSON_DOOR,
-			WoodType::WARPED => Ids::WARPED_DOOR,
-			WoodType::CHERRY => Ids::CHERRY_DOOR,
-		});
-	}
-
-	public static function getFenceGateIdentifier(WoodType $treeType) : BID{
-		return new BID(match($treeType){
-			WoodType::OAK => Ids::OAK_FENCE_GATE,
-			WoodType::SPRUCE => Ids::SPRUCE_FENCE_GATE,
-			WoodType::BIRCH => Ids::BIRCH_FENCE_GATE,
-			WoodType::JUNGLE => Ids::JUNGLE_FENCE_GATE,
-			WoodType::ACACIA => Ids::ACACIA_FENCE_GATE,
-			WoodType::DARK_OAK => Ids::DARK_OAK_FENCE_GATE,
-			WoodType::MANGROVE => Ids::MANGROVE_FENCE_GATE,
-			WoodType::CRIMSON => Ids::CRIMSON_FENCE_GATE,
-			WoodType::WARPED => Ids::WARPED_FENCE_GATE,
-			WoodType::CHERRY => Ids::CHERRY_FENCE_GATE,
-		});
-	}
-
-	public static function getStairsIdentifier(WoodType $treeType) : BID{
-		return new BID(match($treeType){
-			WoodType::OAK => Ids::OAK_STAIRS,
-			WoodType::SPRUCE => Ids::SPRUCE_STAIRS,
-			WoodType::BIRCH => Ids::BIRCH_STAIRS,
-			WoodType::JUNGLE => Ids::JUNGLE_STAIRS,
-			WoodType::ACACIA => Ids::ACACIA_STAIRS,
-			WoodType::DARK_OAK => Ids::DARK_OAK_STAIRS,
-			WoodType::MANGROVE => Ids::MANGROVE_STAIRS,
-			WoodType::CRIMSON => Ids::CRIMSON_STAIRS,
-			WoodType::WARPED => Ids::WARPED_STAIRS,
-			WoodType::CHERRY => Ids::CHERRY_STAIRS,
-		});
-	}
-}
diff --git a/src/block/utils/MultiAnyFacingTrait.php b/src/block/utils/MultiAnyFacingTrait.php
new file mode 100644
index 00000000000..66f26d98009
--- /dev/null
+++ b/src/block/utils/MultiAnyFacingTrait.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\block\utils;
+
+use pocketmine\data\runtime\RuntimeDataDescriber;
+use pocketmine\math\Facing;
+
+/**
+ * Used by blocks that can have multiple target faces in the area of one solid block, such as covering three sides of a corner.
+ */
+trait MultiAnyFacingTrait{
+
+	/** @var int[] */
+	protected array $faces = [];
+
+	protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
+		$w->facingFlags($this->faces);
+	}
+
+	/** @return int[] */
+	public function getFaces() : array{ return $this->faces; }
+
+	public function hasFace(int $face) : bool{
+		return isset($this->faces[$face]);
+	}
+
+	/**
+	 * @param int[] $faces
+	 * @return $this
+	 */
+	public function setFaces(array $faces) : self{
+		$uniqueFaces = [];
+		foreach($faces as $face){
+			Facing::validate($face);
+			$uniqueFaces[$face] = $face;
+		}
+		$this->faces = $uniqueFaces;
+		return $this;
+	}
+
+	/** @return $this */
+	public function setFace(int $face, bool $value) : self{
+		Facing::validate($face);
+		if($value){
+			$this->faces[$face] = $face;
+		}else{
+			unset($this->faces[$face]);
+		}
+		return $this;
+	}
+}
diff --git a/src/block/utils/MultiAnySupportTrait.php b/src/block/utils/MultiAnySupportTrait.php
new file mode 100644
index 00000000000..ae1da7befe1
--- /dev/null
+++ b/src/block/utils/MultiAnySupportTrait.php
@@ -0,0 +1,96 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\block\utils;
+
+use pocketmine\block\Block;
+use pocketmine\item\Item;
+use pocketmine\math\Facing;
+use pocketmine\math\Vector3;
+use pocketmine\player\Player;
+use pocketmine\world\BlockTransaction;
+use function array_key_first;
+use function count;
+
+/**
+ * Used by blocks that have multiple support requirements in the area of one solid block, such as covering three sides of a corner.
+ * Prevents placement if support isn't available and automatically destroys a block side if it's support is removed.
+ */
+trait MultiAnySupportTrait{
+	use MultiAnyFacingTrait;
+
+	/**
+	 * Returns a list of faces that block should already have when placed.
+	 *
+	 * @return int[]
+	 */
+	abstract protected function getInitialPlaceFaces(Block $blockReplace) : array;
+
+	public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
+		$this->faces = $this->getInitialPlaceFaces($blockReplace);
+		$availableFaces = $this->getAvailableFaces();
+
+		if(count($availableFaces) === 0){
+			return false;
+		}
+
+		$opposite = Facing::opposite($face);
+		$placedFace = isset($availableFaces[$opposite]) ? $opposite : array_key_first($availableFaces);
+		$this->faces[$placedFace] = $placedFace;
+
+		return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
+	}
+
+	public function onNearbyBlockChange() : void{
+		$changed = false;
+
+		foreach($this->faces as $face){
+			if($this->getAdjacentSupportType($face) !== SupportType::FULL){
+				unset($this->faces[$face]);
+				$changed = true;
+			}
+		}
+
+		if($changed){
+			$world = $this->position->getWorld();
+			if(count($this->faces) === 0){
+				$world->useBreakOn($this->position);
+			}else{
+				$world->setBlock($this->position, $this);
+			}
+		}
+	}
+
+	/**
+	 * @return array<int, int> $faces
+	 */
+	private function getAvailableFaces() : array{
+		$faces = [];
+		foreach(Facing::ALL as $face){
+			if(!$this->hasFace($face) && $this->getAdjacentSupportType($face) === SupportType::FULL){
+				$faces[$face] = $face;
+			}
+		}
+		return $faces;
+	}
+}
diff --git a/src/command/ClosureCommand.php b/src/command/ClosureCommand.php
new file mode 100644
index 00000000000..289c82853ec
--- /dev/null
+++ b/src/command/ClosureCommand.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\command;
+
+use pocketmine\lang\Translatable;
+use pocketmine\utils\Utils;
+
+/**
+ * @phpstan-type Execute \Closure(CommandSender $sender, Command $command, string $commandLabel, list<string> $args) : mixed
+ */
+final class ClosureCommand extends Command{
+	/** @phpstan-var Execute */
+	private \Closure $execute;
+
+	/**
+	 * @param string[] $permissions
+	 * @phpstan-param Execute $execute
+	 */
+	public function __construct(
+		string $name,
+		\Closure $execute,
+		array $permissions,
+		Translatable|string $description = "",
+		Translatable|string|null $usageMessage = null,
+		array $aliases = []
+	){
+		Utils::validateCallableSignature(
+			fn(CommandSender $sender, Command $command, string $commandLabel, array $args) : mixed => 1,
+			$execute,
+		);
+		$this->execute = $execute;
+		parent::__construct($name, $description, $usageMessage, $aliases);
+		$this->setPermissions($permissions);
+	}
+
+	public function execute(CommandSender $sender, string $commandLabel, array $args){
+		return ($this->execute)($sender, $this, $commandLabel, $args);
+	}
+}
diff --git a/src/command/SimpleCommandMap.php b/src/command/SimpleCommandMap.php
index 06367994a04..e0d8e6565e8 100644
--- a/src/command/SimpleCommandMap.php
+++ b/src/command/SimpleCommandMap.php
@@ -64,6 +64,7 @@
 use pocketmine\command\defaults\VanillaCommand;
 use pocketmine\command\defaults\VersionCommand;
 use pocketmine\command\defaults\WhitelistCommand;
+use pocketmine\command\defaults\XpCommand;
 use pocketmine\command\utils\CommandStringHelper;
 use pocketmine\command\utils\InvalidCommandSyntaxException;
 use pocketmine\lang\KnownTranslationFactory;
@@ -132,7 +133,8 @@ private function setDefaultCommands() : void{
 			new TitleCommand(),
 			new TransferServerCommand(),
 			new VersionCommand(),
-			new WhitelistCommand()
+			new WhitelistCommand(),
+			new XpCommand(),
 		]);
 	}
 
diff --git a/src/command/defaults/TimingsCommand.php b/src/command/defaults/TimingsCommand.php
index 3c0701ea423..ec26f3efe09 100644
--- a/src/command/defaults/TimingsCommand.php
+++ b/src/command/defaults/TimingsCommand.php
@@ -26,28 +26,28 @@
 use pocketmine\command\Command;
 use pocketmine\command\CommandSender;
 use pocketmine\command\utils\InvalidCommandSyntaxException;
+use pocketmine\errorhandler\ErrorToExceptionHandler;
 use pocketmine\lang\KnownTranslationFactory;
 use pocketmine\permission\DefaultPermissionNames;
 use pocketmine\player\Player;
 use pocketmine\scheduler\BulkCurlTask;
 use pocketmine\scheduler\BulkCurlTaskOperation;
 use pocketmine\timings\TimingsHandler;
+use pocketmine\utils\AssumptionFailedError;
 use pocketmine\utils\InternetException;
 use pocketmine\utils\InternetRequestResult;
-use pocketmine\utils\Utils;
 use pocketmine\YmlServerProperties;
 use Symfony\Component\Filesystem\Path;
 use function count;
 use function fclose;
 use function file_exists;
 use function fopen;
-use function fseek;
 use function fwrite;
 use function http_build_query;
+use function implode;
 use function is_array;
 use function json_decode;
 use function mkdir;
-use function stream_get_contents;
 use function strtolower;
 use const CURLOPT_AUTOREFERER;
 use const CURLOPT_FOLLOWLOCATION;
@@ -101,82 +101,91 @@ public function execute(CommandSender $sender, string $commandLabel, array $args
 			TimingsHandler::reload();
 			Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset());
 		}elseif($mode === "merged" || $mode === "report" || $paste){
-			$timings = "";
-			if($paste){
-				$fileTimings = Utils::assumeNotFalse(fopen("php://temp", "r+b"), "Opening php://temp should never fail");
-			}else{
-				$index = 0;
-				$timingFolder = Path::join($sender->getServer()->getDataPath(), "timings");
-
-				if(!file_exists($timingFolder)){
-					mkdir($timingFolder, 0777);
-				}
-				$timings = Path::join($timingFolder, "timings.txt");
-				while(file_exists($timings)){
-					$timings = Path::join($timingFolder, "timings" . (++$index) . ".txt");
-				}
-
-				$fileTimings = fopen($timings, "a+b");
-			}
-			$lines = TimingsHandler::printTimings();
-			foreach($lines as $line){
-				fwrite($fileTimings, $line . PHP_EOL);
-			}
-
-			if($paste){
-				fseek($fileTimings, 0);
-				$data = [
-					"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
-					"data" => $content = stream_get_contents($fileTimings)
-				];
-				fclose($fileTimings);
-
-				$host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");
-
-				$sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask(
-					[new BulkCurlTaskOperation(
-						"https://$host?upload=true",
-						10,
-						[],
-						[
-							CURLOPT_HTTPHEADER => [
-								"User-Agent: $agent",
-								"Content-Type: application/x-www-form-urlencoded"
-							],
-							CURLOPT_POST => true,
-							CURLOPT_POSTFIELDS => http_build_query($data),
-							CURLOPT_AUTOREFERER => false,
-							CURLOPT_FOLLOWLOCATION => false
-						]
-					)],
-					function(array $results) use ($sender, $host) : void{
-						/** @phpstan-var array<InternetRequestResult|InternetException> $results */
-						if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
-							return;
-						}
-						$result = $results[0];
-						if($result instanceof InternetException){
-							$sender->getServer()->getLogger()->logException($result);
-							return;
-						}
-						$response = json_decode($result->getBody(), true);
-						if(is_array($response) && isset($response["id"])){
-							Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
-								"https://" . $host . "/?id=" . $response["id"]));
-						}else{
-							$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
-							Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
-						}
-					}
-				));
-			}else{
-				fclose($fileTimings);
-				Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
-			}
+			$timingsPromise = TimingsHandler::requestPrintTimings();
+			Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_collect());
+			$timingsPromise->onCompletion(
+				fn(array $lines) => $paste ? $this->uploadReport($lines, $sender) : $this->createReportFile($lines, $sender),
+				fn() => throw new AssumptionFailedError("This promise is not expected to be rejected")
+			);
 		}else{
 			throw new InvalidCommandSyntaxException();
 		}
 
 		return true;
 	}
+
+	/**
+	 * @param string[] $lines
+	 * @phpstan-param list<string> $lines
+	 */
+	private function createReportFile(array $lines, CommandSender $sender) : void{
+		$index = 0;
+		$timingFolder = Path::join($sender->getServer()->getDataPath(), "timings");
+
+		if(!file_exists($timingFolder)){
+			mkdir($timingFolder, 0777);
+		}
+		$timings = Path::join($timingFolder, "timings.txt");
+		while(file_exists($timings)){
+			$timings = Path::join($timingFolder, "timings" . (++$index) . ".txt");
+		}
+
+		$fileTimings = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($timings, "a+b"));
+		foreach($lines as $line){
+			fwrite($fileTimings, $line . PHP_EOL);
+		}
+		fclose($fileTimings);
+
+		Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
+	}
+
+	/**
+	 * @param string[] $lines
+	 * @phpstan-param list<string> $lines
+	 */
+	private function uploadReport(array $lines, CommandSender $sender) : void{
+		$data = [
+			"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
+			"data" => implode("\n", $lines)
+		];
+
+		$host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");
+
+		$sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask(
+			[new BulkCurlTaskOperation(
+				"https://$host?upload=true",
+				10,
+				[],
+				[
+					CURLOPT_HTTPHEADER => [
+						"User-Agent: $agent",
+						"Content-Type: application/x-www-form-urlencoded"
+					],
+					CURLOPT_POST => true,
+					CURLOPT_POSTFIELDS => http_build_query($data),
+					CURLOPT_AUTOREFERER => false,
+					CURLOPT_FOLLOWLOCATION => false
+				]
+			)],
+			function(array $results) use ($sender, $host) : void{
+				/** @phpstan-var array<InternetRequestResult|InternetException> $results */
+				if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
+					return;
+				}
+				$result = $results[0];
+				if($result instanceof InternetException){
+					$sender->getServer()->getLogger()->logException($result);
+					return;
+				}
+				$response = json_decode($result->getBody(), true);
+				if(is_array($response) && isset($response["id"])){
+					Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
+						"https://" . $host . "/?id=" . $response["id"]));
+				}else{
+					$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
+					Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
+				}
+			}
+		));
+	}
 }
diff --git a/src/command/defaults/XpCommand.php b/src/command/defaults/XpCommand.php
new file mode 100644
index 00000000000..cb035236574
--- /dev/null
+++ b/src/command/defaults/XpCommand.php
@@ -0,0 +1,89 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\command\defaults;
+
+use pocketmine\command\CommandSender;
+use pocketmine\command\utils\InvalidCommandSyntaxException;
+use pocketmine\entity\Attribute;
+use pocketmine\lang\KnownTranslationFactory;
+use pocketmine\permission\DefaultPermissionNames;
+use pocketmine\utils\AssumptionFailedError;
+use pocketmine\utils\Limits;
+use pocketmine\utils\TextFormat;
+use function abs;
+use function count;
+use function str_ends_with;
+use function substr;
+
+class XpCommand extends VanillaCommand{
+
+	public function __construct(){
+		parent::__construct(
+			"xp",
+			KnownTranslationFactory::pocketmine_command_xp_description(),
+			KnownTranslationFactory::pocketmine_command_xp_usage()
+		);
+		$this->setPermissions([
+			DefaultPermissionNames::COMMAND_XP_SELF,
+			DefaultPermissionNames::COMMAND_XP_OTHER
+		]);
+	}
+
+	public function execute(CommandSender $sender, string $commandLabel, array $args){
+		if(count($args) < 1){
+			throw new InvalidCommandSyntaxException();
+		}
+
+		$player = $this->fetchPermittedPlayerTarget($sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_XP_SELF, DefaultPermissionNames::COMMAND_XP_OTHER);
+		if($player === null){
+			return true;
+		}
+
+		$xpManager = $player->getXpManager();
+		if(str_ends_with($args[0], "L")){
+			$xpLevelAttr = $player->getAttributeMap()->get(Attribute::EXPERIENCE_LEVEL) ?? throw new AssumptionFailedError();
+			$maxXpLevel = (int) $xpLevelAttr->getMaxValue();
+			$currentXpLevel = $xpManager->getXpLevel();
+			$xpLevels = $this->getInteger($sender, substr($args[0], 0, -1), -$currentXpLevel, $maxXpLevel - $currentXpLevel);
+			if($xpLevels >= 0){
+				$xpManager->addXpLevels($xpLevels, false);
+				$sender->sendMessage(KnownTranslationFactory::commands_xp_success_levels((string) $xpLevels, $player->getName()));
+			}else{
+				$xpLevels = abs($xpLevels);
+				$xpManager->subtractXpLevels($xpLevels);
+				$sender->sendMessage(KnownTranslationFactory::commands_xp_success_negative_levels((string) $xpLevels, $player->getName()));
+			}
+		}else{
+			$xp = $this->getInteger($sender, $args[0], max: Limits::INT32_MAX);
+			if($xp < 0){
+				$sender->sendMessage(KnownTranslationFactory::commands_xp_failure_widthdrawXp()->prefix(TextFormat::RED));
+			}else{
+				$xpManager->addXp($xp, false);
+				$sender->sendMessage(KnownTranslationFactory::commands_xp_success((string) $xp, $player->getName()));
+			}
+		}
+
+		return true;
+	}
+}
diff --git a/src/console/ConsoleReaderChildProcess.php b/src/console/ConsoleReaderChildProcess.php
index 0d5a30fdcc0..c0c4f0048f1 100644
--- a/src/console/ConsoleReaderChildProcess.php
+++ b/src/console/ConsoleReaderChildProcess.php
@@ -29,23 +29,21 @@
 use function cli_set_process_title;
 use function count;
 use function dirname;
-use function feof;
 use function fwrite;
-use function stream_socket_client;
+use function is_numeric;
+use const PHP_EOL;
+use const STDOUT;
 
-require dirname(__DIR__, 2) . '/vendor/autoload.php';
-
-if(count($argv) !== 2){
-	die("Please provide a server to connect to");
+if(count($argv) !== 2 || !is_numeric($argv[1])){
+	echo "Usage: " . $argv[0] . " <command token seed>" . PHP_EOL;
+	exit(1);
 }
 
+$commandTokenSeed = (int) $argv[1];
+
+require dirname(__DIR__, 2) . '/vendor/autoload.php';
+
 @cli_set_process_title('PocketMine-MP Console Reader');
-$errCode = null;
-$errMessage = null;
-$socket = stream_socket_client($argv[1], $errCode, $errMessage, 15.0);
-if($socket === false){
-	throw new \RuntimeException("Failed to connect to server process ($errCode): $errMessage");
-}
 
 /** @phpstan-var ThreadSafeArray<int, string> $channel */
 $channel = new ThreadSafeArray();
@@ -75,15 +73,15 @@ public function run() : void{
 };
 
 $thread->start(NativeThread::INHERIT_NONE);
-while(!feof($socket)){
+while(true){
 	$line = $channel->synchronized(function() use ($channel) : ?string{
 		if(count($channel) === 0){
 			$channel->wait(1_000_000);
 		}
-		$line = $channel->shift();
-		return $line;
+		return $channel->shift();
 	});
-	if(@fwrite($socket, ($line ?? "") . "\n") === false){
+	$message = $line !== null ? ConsoleReaderChildProcessUtils::createMessage($line, $commandTokenSeed) : "";
+	if(@fwrite(STDOUT, $message . "\n") === false){
 		//Always send even if there's no line, to check if the parent is alive
 		//If the parent process was terminated forcibly, it won't close the connection properly, so feof() will return
 		//false even though the connection is actually broken. However, fwrite() will fail.
diff --git a/src/console/ConsoleReaderChildProcessDaemon.php b/src/console/ConsoleReaderChildProcessDaemon.php
index 138559f06e9..f7300b7a520 100644
--- a/src/console/ConsoleReaderChildProcessDaemon.php
+++ b/src/console/ConsoleReaderChildProcessDaemon.php
@@ -29,19 +29,16 @@
 use function base64_encode;
 use function fgets;
 use function fopen;
+use function mt_rand;
 use function preg_replace;
 use function proc_close;
 use function proc_open;
 use function proc_terminate;
+use function rtrim;
 use function sprintf;
 use function stream_select;
-use function stream_socket_accept;
-use function stream_socket_get_name;
-use function stream_socket_server;
-use function stream_socket_shutdown;
 use function trim;
 use const PHP_BINARY;
-use const STREAM_SHUT_RDWR;
 
 /**
  * This pile of shit exists because PHP on Windows is broken, and can't handle stream_select() on stdin or pipes
@@ -58,44 +55,44 @@
  * communication.
  */
 final class ConsoleReaderChildProcessDaemon{
+	public const TOKEN_DELIMITER = ":";
+	public const TOKEN_HASH_ALGO = "xxh3";
+
 	private \PrefixedLogger $logger;
 	/** @var resource */
 	private $subprocess;
 	/** @var resource */
 	private $socket;
+	private int $commandTokenSeed;
 
 	public function __construct(
 		\Logger $logger
 	){
 		$this->logger = new \PrefixedLogger($logger, "Console Reader Daemon");
+		$this->commandTokenSeed = mt_rand();
 		$this->prepareSubprocess();
 	}
 
 	private function prepareSubprocess() : void{
-		$server = stream_socket_server("tcp://127.0.0.1:0");
-		if($server === false){
-			throw new \RuntimeException("Failed to open console reader socket server");
-		}
-		$address = Utils::assumeNotFalse(stream_socket_get_name($server, false), "stream_socket_get_name() shouldn't return false here");
-
 		//Windows sucks, and likes to corrupt UTF-8 file paths when they travel to the subprocess, so we base64 encode
 		//the path to avoid the problem. This is an abysmally shitty hack, but here we are :(
 		$sub = Utils::assumeNotFalse(proc_open(
-			[PHP_BINARY, '-dopcache.enable_cli=0', '-r', sprintf('require base64_decode("%s", true);', base64_encode(Path::join(__DIR__, 'ConsoleReaderChildProcess.php'))), $address],
 			[
+				PHP_BINARY,
+				'-dopcache.enable_cli=0',
+				'-r',
+				sprintf('require base64_decode("%s", true);', base64_encode(Path::join(__DIR__, 'ConsoleReaderChildProcess.php'))),
+				(string) $this->commandTokenSeed
+			],
+			[
+				1 => ['socket'],
 				2 => fopen("php://stderr", "w"),
 			],
 			$pipes
 		), "Something has gone horribly wrong");
 
-		$client = stream_socket_accept($server, 15);
-		if($client === false){
-			throw new AssumptionFailedError("stream_socket_accept() returned false");
-		}
-		stream_socket_shutdown($server, STREAM_SHUT_RDWR);
-
 		$this->subprocess = $sub;
-		$this->socket = $client;
+		$this->socket = $pipes[1];
 	}
 
 	private function shutdownSubprocess() : void{
@@ -104,7 +101,6 @@ private function shutdownSubprocess() : void{
 		//the first place).
 		proc_terminate($this->subprocess);
 		proc_close($this->subprocess);
-		stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
 	}
 
 	public function readLine() : ?string{
@@ -112,13 +108,27 @@ public function readLine() : ?string{
 		$w = null;
 		$e = null;
 		if(stream_select($r, $w, $e, 0, 0) === 1){
-			$command = fgets($this->socket);
-			if($command === false){
+			$line = fgets($this->socket);
+			if($line === false){
 				$this->logger->debug("Lost connection to subprocess, restarting (maybe the child process was killed from outside?)");
 				$this->shutdownSubprocess();
 				$this->prepareSubprocess();
 				return null;
 			}
+			$line = rtrim($line, "\n");
+
+			if($line === ""){
+				//keepalive
+				return null;
+			}
+
+			$command = ConsoleReaderChildProcessUtils::parseMessage($line, $this->commandTokenSeed);
+			if($command === null){
+				//this is not a command - it may be some kind of error output from the subprocess
+				//write it directly to the console
+				$this->logger->warning("Unexpected output from child process: $line");
+				return null;
+			}
 
 			$command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
 			$command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
diff --git a/src/console/ConsoleReaderChildProcessUtils.php b/src/console/ConsoleReaderChildProcessUtils.php
new file mode 100644
index 00000000000..661e9b0f7bb
--- /dev/null
+++ b/src/console/ConsoleReaderChildProcessUtils.php
@@ -0,0 +1,71 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\console;
+
+use function hash;
+use function strlen;
+use function strrpos;
+use function substr;
+
+final class ConsoleReaderChildProcessUtils{
+	public const TOKEN_DELIMITER = ":";
+	public const TOKEN_HASH_ALGO = "xxh3";
+
+	private function __construct(){
+
+	}
+
+	/**
+	 * Creates an IPC message to transmit a user's input command to the parent process.
+	 *
+	 * Unfortunately we can't currently provide IPC pipes other than stdout/stderr to subprocesses on Windows, so this
+	 * adds a hash of the user input (with a counter as salt) to prevent unintended process output (like error messages)
+	 * from being treated as user input.
+	 */
+	public static function createMessage(string $line, int &$counter) : string{
+		$token = hash(self::TOKEN_HASH_ALGO, $line, options: ['seed' => $counter]);
+		$counter++;
+		return $line . self::TOKEN_DELIMITER . $token;
+	}
+
+	/**
+	 * Extracts a command from an IPC message from the console reader subprocess.
+	 * Returns the user's input command, or null if this isn't a user input.
+	 */
+	public static function parseMessage(string $message, int &$counter) : ?string{
+		$delimiterPos = strrpos($message, self::TOKEN_DELIMITER);
+		if($delimiterPos !== false){
+			$left = substr($message, 0, $delimiterPos);
+			$right = substr($message, $delimiterPos + strlen(self::TOKEN_DELIMITER));
+			$expectedToken = hash(self::TOKEN_HASH_ALGO, $left, options: ['seed' => $counter]);
+
+			if($expectedToken === $right){
+				$counter++;
+				return $left;
+			}
+		}
+
+		return null;
+	}
+}
diff --git a/src/data/bedrock/EnchantmentIdMap.php b/src/data/bedrock/EnchantmentIdMap.php
index e3d652b19ce..90a10dc20a9 100644
--- a/src/data/bedrock/EnchantmentIdMap.php
+++ b/src/data/bedrock/EnchantmentIdMap.php
@@ -66,5 +66,7 @@ private function __construct(){
 		$this->register(EnchantmentIds::VANISHING, VanillaEnchantments::VANISHING());
 
 		$this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK());
+
+		$this->register(EnchantmentIds::FROST_WALKER, VanillaEnchantments::FROST_WALKER());
 	}
 }
diff --git a/src/data/bedrock/GoatHornTypeIdMap.php b/src/data/bedrock/GoatHornTypeIdMap.php
new file mode 100644
index 00000000000..0510a09cee7
--- /dev/null
+++ b/src/data/bedrock/GoatHornTypeIdMap.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\data\bedrock;
+
+use pocketmine\item\GoatHornType;
+use pocketmine\utils\SingletonTrait;
+
+final class GoatHornTypeIdMap{
+	use SingletonTrait;
+	/** @phpstan-use IntSaveIdMapTrait<GoatHornType> */
+	use IntSaveIdMapTrait;
+
+	private function __construct(){
+		foreach(GoatHornType::cases() as $case){
+			$this->register(match($case){
+				GoatHornType::PONDER => GoatHornTypeIds::PONDER,
+				GoatHornType::SING => GoatHornTypeIds::SING,
+				GoatHornType::SEEK => GoatHornTypeIds::SEEK,
+				GoatHornType::FEEL => GoatHornTypeIds::FEEL,
+				GoatHornType::ADMIRE => GoatHornTypeIds::ADMIRE,
+				GoatHornType::CALL => GoatHornTypeIds::CALL,
+				GoatHornType::YEARN => GoatHornTypeIds::YEARN,
+				GoatHornType::DREAM => GoatHornTypeIds::DREAM
+			}, $case);
+		}
+	}
+}
diff --git a/src/data/bedrock/GoatHornTypeIds.php b/src/data/bedrock/GoatHornTypeIds.php
new file mode 100644
index 00000000000..048d246fe0a
--- /dev/null
+++ b/src/data/bedrock/GoatHornTypeIds.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\data\bedrock;
+
+final class GoatHornTypeIds{
+	public const PONDER = 0;
+	public const SING = 1;
+	public const SEEK = 2;
+	public const FEEL = 3;
+	public const ADMIRE = 4;
+	public const CALL = 5;
+	public const YEARN = 6;
+	public const DREAM = 7;
+}
diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php
index 86f6621df80..df1db421102 100644
--- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php
+++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php
@@ -31,6 +31,7 @@
 use pocketmine\block\VanillaBlocks as Blocks;
 use pocketmine\data\bedrock\CompoundTypeIds;
 use pocketmine\data\bedrock\DyeColorIdMap;
+use pocketmine\data\bedrock\GoatHornTypeIdMap;
 use pocketmine\data\bedrock\item\ItemTypeNames as Ids;
 use pocketmine\data\bedrock\item\SavedItemData as Data;
 use pocketmine\data\bedrock\MedicineTypeIdMap;
@@ -38,6 +39,7 @@
 use pocketmine\data\bedrock\SuspiciousStewTypeIdMap;
 use pocketmine\item\Banner;
 use pocketmine\item\Dye;
+use pocketmine\item\GoatHorn;
 use pocketmine\item\Item;
 use pocketmine\item\Medicine;
 use pocketmine\item\Potion;
@@ -230,6 +232,7 @@ private function register1to1ItemMappings() : void{
 		$this->map1to1Item(Ids::EMERALD, Items::EMERALD());
 		$this->map1to1Item(Ids::ENCHANTED_BOOK, Items::ENCHANTED_BOOK());
 		$this->map1to1Item(Ids::ENCHANTED_GOLDEN_APPLE, Items::ENCHANTED_GOLDEN_APPLE());
+		$this->map1to1Item(Ids::END_CRYSTAL, Items::END_CRYSTAL());
 		$this->map1to1Item(Ids::ENDER_PEARL, Items::ENDER_PEARL());
 		$this->map1to1Item(Ids::EXPERIENCE_BOTTLE, Items::EXPERIENCE_BOTTLE());
 		$this->map1to1Item(Ids::EYE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE());
@@ -263,6 +266,7 @@ private function register1to1ItemMappings() : void{
 		$this->map1to1Item(Ids::HONEY_BOTTLE, Items::HONEY_BOTTLE());
 		$this->map1to1Item(Ids::HONEYCOMB, Items::HONEYCOMB());
 		$this->map1to1Item(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE, Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE());
+		$this->map1to1Item(Ids::ICE_BOMB, Items::ICE_BOMB());
 		$this->map1to1Item(Ids::INK_SAC, Items::INK_SAC());
 		$this->map1to1Item(Ids::IRON_AXE, Items::IRON_AXE());
 		$this->map1to1Item(Ids::IRON_BOOTS, Items::IRON_BOOTS());
@@ -348,6 +352,7 @@ private function register1to1ItemMappings() : void{
 		$this->map1to1Item(Ids::RAW_COPPER, Items::RAW_COPPER());
 		$this->map1to1Item(Ids::RAW_GOLD, Items::RAW_GOLD());
 		$this->map1to1Item(Ids::RAW_IRON, Items::RAW_IRON());
+		$this->map1to1Item(Ids::RECOVERY_COMPASS, Items::RECOVERY_COMPASS());
 		$this->map1to1Item(Ids::REDSTONE, Items::REDSTONE_DUST());
 		$this->map1to1Item(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());
 		$this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH());
@@ -483,6 +488,14 @@ function(Banner $item, int $meta) : void{
 			},
 			fn(Banner $item) => DyeColorIdMap::getInstance()->toInvertedId($item->getColor())
 		);
+		$this->map1to1ItemWithMeta(
+			Ids::GOAT_HORN,
+			Items::GOAT_HORN(),
+			function(GoatHorn $item, int $meta) : void{
+				$item->setHornType(GoatHornTypeIdMap::getInstance()->fromId($meta) ?? throw new ItemTypeDeserializeException("Unknown goat horn type ID $meta"));
+			},
+			fn(GoatHorn $item) => GoatHornTypeIdMap::getInstance()->toId($item->getHornType())
+		);
 		$this->map1to1ItemWithMeta(
 			Ids::MEDICINE,
 			Items::MEDICINE(),
diff --git a/src/entity/Entity.php b/src/entity/Entity.php
index 3d791ebca63..9bd0de9eace 100644
--- a/src/entity/Entity.php
+++ b/src/entity/Entity.php
@@ -35,6 +35,7 @@
 use pocketmine\event\entity\EntityRegainHealthEvent;
 use pocketmine\event\entity\EntitySpawnEvent;
 use pocketmine\event\entity\EntityTeleportEvent;
+use pocketmine\item\Item;
 use pocketmine\math\AxisAlignedBB;
 use pocketmine\math\Facing;
 use pocketmine\math\Vector2;
@@ -74,7 +75,6 @@
 use function floor;
 use function fmod;
 use function get_class;
-use function lcg_value;
 use function sin;
 use function spl_object_id;
 use const M_PI_2;
@@ -909,7 +909,7 @@ protected function checkObstruction(float $x, float $y, float $z) : bool{
 				return false;
 			}
 
-			$force = lcg_value() * 0.2 + 0.1;
+			$force = Utils::getRandomFloat() * 0.2 + 0.1;
 
 			$this->motion = match($direction){
 				Facing::WEST => $this->motion->withComponents(-$force, null, null),
@@ -1563,6 +1563,13 @@ public function despawnFromAll() : void{
 		$this->hasSpawned = [];
 	}
 
+	/**
+	 * Returns the item that players will equip when middle-clicking on this entity.
+	 */
+	public function getPickedItem() : ?Item{
+		return null;
+	}
+
 	/**
 	 * Flags the entity to be removed from the world on the next tick.
 	 */
diff --git a/src/entity/EntityFactory.php b/src/entity/EntityFactory.php
index d8d189cffc1..03d9c03e6be 100644
--- a/src/entity/EntityFactory.php
+++ b/src/entity/EntityFactory.php
@@ -32,6 +32,7 @@
 use pocketmine\data\bedrock\PotionTypeIds;
 use pocketmine\data\SavedDataLoadingException;
 use pocketmine\entity\EntityDataHelper as Helper;
+use pocketmine\entity\object\EndCrystal;
 use pocketmine\entity\object\ExperienceOrb;
 use pocketmine\entity\object\FallingBlock;
 use pocketmine\entity\object\ItemEntity;
@@ -42,6 +43,7 @@
 use pocketmine\entity\projectile\Egg;
 use pocketmine\entity\projectile\EnderPearl;
 use pocketmine\entity\projectile\ExperienceBottle;
+use pocketmine\entity\projectile\IceBomb;
 use pocketmine\entity\projectile\Snowball;
 use pocketmine\entity\projectile\SplashPotion;
 use pocketmine\item\Item;
@@ -92,6 +94,10 @@ public function __construct(){
 			return new Egg(Helper::parseLocation($nbt, $world), null, $nbt);
 		}, ['Egg', 'minecraft:egg']);
 
+		$this->register(EndCrystal::class, function(World $world, CompoundTag $nbt) : EndCrystal{
+			return new EndCrystal(Helper::parseLocation($nbt, $world), $nbt);
+		}, ['EnderCrystal', 'minecraft:ender_crystal']);
+
 		$this->register(EnderPearl::class, function(World $world, CompoundTag $nbt) : EnderPearl{
 			return new EnderPearl(Helper::parseLocation($nbt, $world), null, $nbt);
 		}, ['ThrownEnderpearl', 'minecraft:ender_pearl']);
@@ -115,6 +121,10 @@ public function __construct(){
 			return new FallingBlock(Helper::parseLocation($nbt, $world), FallingBlock::parseBlockNBT(RuntimeBlockStateRegistry::getInstance(), $nbt), $nbt);
 		}, ['FallingSand', 'minecraft:falling_block']);
 
+		$this->register(IceBomb::class, function(World $world, CompoundTag $nbt) : IceBomb{
+			return new IceBomb(Helper::parseLocation($nbt, $world), null, $nbt);
+		}, ['minecraft:ice_bomb']);
+
 		$this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{
 			$itemTag = $nbt->getCompoundTag(ItemEntity::TAG_ITEM);
 			if($itemTag === null){
diff --git a/src/entity/FoodSource.php b/src/entity/FoodSource.php
index 98478b4a1da..028c7678382 100644
--- a/src/entity/FoodSource.php
+++ b/src/entity/FoodSource.php
@@ -34,6 +34,7 @@ public function getSaturationRestore() : float;
 
 	/**
 	 * Returns whether a Human eating this FoodSource must have a non-full hunger bar.
+	 * This is ignored in creative mode and in peaceful difficulty.
 	 */
 	public function requiresHunger() : bool;
 }
diff --git a/src/entity/Human.php b/src/entity/Human.php
index f2c4c7a7405..833196651c7 100644
--- a/src/entity/Human.php
+++ b/src/entity/Human.php
@@ -68,6 +68,7 @@
 use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket;
 use pocketmine\player\Player;
 use pocketmine\world\sound\TotemUseSound;
+use pocketmine\world\World;
 use Ramsey\Uuid\Uuid;
 use Ramsey\Uuid\UuidInterface;
 use function array_fill;
@@ -189,8 +190,16 @@ public function getHungerManager() : HungerManager{
 		return $this->hungerManager;
 	}
 
+	/**
+	 * Returns whether the Human can eat food. This may return a different result than {@link HungerManager::isHungry()},
+	 * as HungerManager only handles the hunger bar.
+	 */
+	public function canEat() : bool{
+		return $this->hungerManager->isHungry() || $this->getWorld()->getDifficulty() === World::DIFFICULTY_PEACEFUL;
+	}
+
 	public function consumeObject(Consumable $consumable) : bool{
-		if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->hungerManager->isHungry()){
+		if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->canEat()){
 			return false;
 		}
 
diff --git a/src/entity/HungerManager.php b/src/entity/HungerManager.php
index a3185589187..7e3b40e745f 100644
--- a/src/entity/HungerManager.php
+++ b/src/entity/HungerManager.php
@@ -88,7 +88,8 @@ public function addFood(float $amount) : void{
 	}
 
 	/**
-	 * Returns whether this Human may consume objects requiring hunger.
+	 * Returns whether the food level is below the maximum.
+	 * This doesn't decide if the entity can eat food. Use {@link Human::canEat()} for that.
 	 */
 	public function isHungry() : bool{
 		return $this->getFood() < $this->getMaxFood();
diff --git a/src/entity/Living.php b/src/entity/Living.php
index 81f46424f17..852344784a6 100644
--- a/src/entity/Living.php
+++ b/src/entity/Living.php
@@ -25,6 +25,8 @@
 
 use pocketmine\block\Block;
 use pocketmine\block\BlockTypeIds;
+use pocketmine\block\VanillaBlocks;
+use pocketmine\block\Water;
 use pocketmine\data\bedrock\EffectIdMap;
 use pocketmine\entity\animation\DeathAnimation;
 use pocketmine\entity\animation\HurtAnimation;
@@ -44,6 +46,7 @@
 use pocketmine\item\enchantment\Enchantment;
 use pocketmine\item\enchantment\VanillaEnchantments;
 use pocketmine\item\Item;
+use pocketmine\math\AxisAlignedBB;
 use pocketmine\math\Vector3;
 use pocketmine\math\VoxelRayTrace;
 use pocketmine\nbt\tag\CompoundTag;
@@ -58,18 +61,19 @@
 use pocketmine\player\Player;
 use pocketmine\timings\Timings;
 use pocketmine\utils\Binary;
+use pocketmine\utils\Utils;
 use pocketmine\world\sound\BurpSound;
 use pocketmine\world\sound\EntityLandSound;
 use pocketmine\world\sound\EntityLongFallSound;
 use pocketmine\world\sound\EntityShortFallSound;
 use pocketmine\world\sound\ItemBreakSound;
+use function abs;
 use function array_shift;
 use function atan2;
 use function ceil;
 use function count;
 use function floor;
 use function ksort;
-use function lcg_value;
 use function max;
 use function min;
 use function mt_getrandmax;
@@ -128,6 +132,8 @@ abstract class Living extends Entity{
 	protected bool $gliding = false;
 	protected bool $swimming = false;
 
+	private ?int $frostWalkerLevel = null;
+
 	protected function getInitialDragMultiplier() : float{ return 0.02; }
 
 	protected function getInitialGravity() : float{ return 0.08; }
@@ -151,6 +157,14 @@ protected function initEntity(CompoundTag $nbt) : void{
 			$this->getViewers(),
 			fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobArmorChange($recipients, $this)
 		)));
+		$this->armorInventory->getListeners()->add(new CallbackInventoryListener(
+			onSlotChange: function(Inventory $inventory, int $slot) : void{
+				if($slot === ArmorInventory::SLOT_FEET){
+					$this->frostWalkerLevel = null;
+				}
+			},
+			onContentChange: function() : void{ $this->frostWalkerLevel = null; }
+		));
 
 		$health = $this->getMaxHealth();
 
@@ -490,7 +504,7 @@ protected function applyPostDamageEffects(EntityDamageEvent $source) : void{
 				$helmet = $this->armorInventory->getHelmet();
 				if($helmet instanceof Armor){
 					$finalDamage = $source->getFinalDamage();
-					$this->damageItem($helmet, (int) round($finalDamage * 4 + lcg_value() * $finalDamage * 2));
+					$this->damageItem($helmet, (int) round($finalDamage * 4 + Utils::getRandomFloat() * $finalDamage * 2));
 					$this->armorInventory->setHelmet($helmet);
 				}
 			}
@@ -687,6 +701,47 @@ protected function entityBaseTick(int $tickDiff = 1) : bool{
 		return $hasUpdate;
 	}
 
+	protected function move(float $dx, float $dy, float $dz) : void{
+		$oldX = $this->location->x;
+		$oldZ = $this->location->z;
+
+		parent::move($dx, $dy, $dz);
+
+		$frostWalkerLevel = $this->getFrostWalkerLevel();
+		if($frostWalkerLevel > 0 && (abs($this->location->x - $oldX) > self::MOTION_THRESHOLD || abs($this->location->z - $oldZ) > self::MOTION_THRESHOLD)){
+			$this->applyFrostWalker($frostWalkerLevel);
+		}
+	}
+
+	protected function applyFrostWalker(int $level) : void{
+		$radius = $level + 2;
+		$world = $this->getWorld();
+
+		$baseX = $this->location->getFloorX();
+		$y = $this->location->getFloorY() - 1;
+		$baseZ = $this->location->getFloorZ();
+
+		$frostedIce = VanillaBlocks::FROSTED_ICE();
+		for($x = $baseX - $radius; $x <= $baseX + $radius; $x++){
+			for($z = $baseZ - $radius; $z <= $baseZ + $radius; $z++){
+				$block = $world->getBlockAt($x, $y, $z);
+				if(
+					!$block instanceof Water ||
+					!$block->isSource() ||
+					$world->getBlockAt($x, $y + 1, $z)->getTypeId() !== BlockTypeIds::AIR ||
+					count($world->getNearbyEntities(AxisAlignedBB::one()->offset($x, $y, $z))) !== 0
+				){
+					continue;
+				}
+				$world->setBlockAt($x, $y, $z, $frostedIce);
+			}
+		}
+	}
+
+	public function getFrostWalkerLevel() : int{
+		return $this->frostWalkerLevel ??= $this->armorInventory->getBoots()->getEnchantmentLevel(VanillaEnchantments::FROST_WALKER());
+	}
+
 	/**
 	 * Ticks the entity's air supply, consuming it when underwater and regenerating it when out of water.
 	 */
@@ -697,7 +752,7 @@ protected function doAirSupplyTick(int $tickDiff) : bool{
 			$this->setBreathing(false);
 
 			if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 ||
-				lcg_value() <= (1 / ($respirationLevel + 1))
+				Utils::getRandomFloat() <= (1 / ($respirationLevel + 1))
 			){
 				$ticks -= $tickDiff;
 				if($ticks <= -20){
diff --git a/src/entity/Squid.php b/src/entity/Squid.php
index 75c50061b98..a0f3fef48b9 100644
--- a/src/entity/Squid.php
+++ b/src/entity/Squid.php
@@ -26,6 +26,7 @@
 use pocketmine\entity\animation\SquidInkCloudAnimation;
 use pocketmine\event\entity\EntityDamageByEntityEvent;
 use pocketmine\event\entity\EntityDamageEvent;
+use pocketmine\item\Item;
 use pocketmine\item\VanillaItems;
 use pocketmine\math\Vector3;
 use pocketmine\nbt\tag\CompoundTag;
@@ -124,4 +125,8 @@ public function getDrops() : array{
 			VanillaItems::INK_SAC()->setCount(mt_rand(1, 3))
 		];
 	}
+
+	public function getPickedItem() : ?Item{
+		return VanillaItems::SQUID_SPAWN_EGG();
+	}
 }
diff --git a/src/entity/Villager.php b/src/entity/Villager.php
index 376401a5df2..fee65b01437 100644
--- a/src/entity/Villager.php
+++ b/src/entity/Villager.php
@@ -23,6 +23,8 @@
 
 namespace pocketmine\entity;
 
+use pocketmine\item\Item;
+use pocketmine\item\VanillaItems;
 use pocketmine\nbt\tag\CompoundTag;
 use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
 use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
@@ -87,6 +89,10 @@ public function isBaby() : bool{
 		return $this->baby;
 	}
 
+	public function getPickedItem() : ?Item{
+		return VanillaItems::VILLAGER_SPAWN_EGG();
+	}
+
 	protected function syncNetworkData(EntityMetadataCollection $properties) : void{
 		parent::syncNetworkData($properties);
 		$properties->setGenericFlag(EntityMetadataFlags::BABY, $this->baby);
diff --git a/src/entity/Zombie.php b/src/entity/Zombie.php
index 18fc2207e7f..159a2dd2586 100644
--- a/src/entity/Zombie.php
+++ b/src/entity/Zombie.php
@@ -23,6 +23,7 @@
 
 namespace pocketmine\entity;
 
+use pocketmine\item\Item;
 use pocketmine\item\VanillaItems;
 use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
 use function mt_rand;
@@ -65,4 +66,8 @@ public function getXpDropAmount() : int{
 		//TODO: check for equipment and whether it's a baby
 		return 5;
 	}
+
+	public function getPickedItem() : ?Item{
+		return VanillaItems::ZOMBIE_SPAWN_EGG();
+	}
 }
diff --git a/src/entity/object/EndCrystal.php b/src/entity/object/EndCrystal.php
new file mode 100644
index 00000000000..afaeb676938
--- /dev/null
+++ b/src/entity/object/EndCrystal.php
@@ -0,0 +1,146 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\entity\object;
+
+use pocketmine\entity\Entity;
+use pocketmine\entity\EntitySizeInfo;
+use pocketmine\entity\Explosive;
+use pocketmine\event\entity\EntityDamageEvent;
+use pocketmine\event\entity\EntityPreExplodeEvent;
+use pocketmine\item\Item;
+use pocketmine\item\VanillaItems;
+use pocketmine\math\Vector3;
+use pocketmine\nbt\tag\CompoundTag;
+use pocketmine\nbt\tag\IntTag;
+use pocketmine\network\mcpe\protocol\types\BlockPosition;
+use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
+use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
+use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
+use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
+use pocketmine\world\Explosion;
+
+class EndCrystal extends Entity implements Explosive{
+	private const TAG_SHOWBASE = "ShowBottom"; //TAG_Byte
+
+	private const TAG_BLOCKTARGET_X = "BlockTargetX"; //TAG_Int
+	private const TAG_BLOCKTARGET_Y = "BlockTargetY"; //TAG_Int
+	private const TAG_BLOCKTARGET_Z = "BlockTargetZ"; //TAG_Int
+
+	public static function getNetworkTypeId() : string{ return EntityIds::ENDER_CRYSTAL; }
+
+	protected bool $showBase = false;
+	protected ?Vector3 $beamTarget = null;
+
+	protected function getInitialSizeInfo() : EntitySizeInfo{ return new EntitySizeInfo(2.0, 2.0); }
+
+	protected function getInitialDragMultiplier() : float{ return 1.0; }
+
+	protected function getInitialGravity() : float{ return 0.0; }
+
+	public function isFireProof() : bool{
+		return true;
+	}
+
+	public function getPickedItem() : ?Item{
+		return VanillaItems::END_CRYSTAL();
+	}
+
+	public function showBase() : bool{
+		return $this->showBase;
+	}
+
+	public function setShowBase(bool $showBase) : void{
+		$this->showBase = $showBase;
+		$this->networkPropertiesDirty = true;
+	}
+
+	public function getBeamTarget() : ?Vector3{
+		return $this->beamTarget;
+	}
+
+	public function setBeamTarget(?Vector3 $beamTarget) : void{
+		$this->beamTarget = $beamTarget;
+		$this->networkPropertiesDirty = true;
+	}
+
+	public function attack(EntityDamageEvent $source) : void{
+		parent::attack($source);
+		if(
+			$source->getCause() !== EntityDamageEvent::CAUSE_VOID &&
+			!$this->isFlaggedForDespawn() &&
+			!$source->isCancelled()
+		){
+			$this->flagForDespawn();
+			$this->explode();
+		}
+	}
+
+	protected function initEntity(CompoundTag $nbt) : void{
+		parent::initEntity($nbt);
+
+		$this->setMaxHealth(1);
+		$this->setHealth(1);
+
+		$this->setShowBase($nbt->getByte(self::TAG_SHOWBASE, 0) === 1);
+
+		if(
+			($beamXTag = $nbt->getTag(self::TAG_BLOCKTARGET_X)) instanceof IntTag &&
+			($beamYTag = $nbt->getTag(self::TAG_BLOCKTARGET_Y)) instanceof IntTag &&
+			($beamZTag = $nbt->getTag(self::TAG_BLOCKTARGET_Z)) instanceof IntTag
+		){
+			$this->setBeamTarget(new Vector3($beamXTag->getValue(), $beamYTag->getValue(), $beamZTag->getValue()));
+		}
+	}
+
+	public function saveNBT() : CompoundTag{
+		$nbt = parent::saveNBT();
+
+		$nbt->setByte(self::TAG_SHOWBASE, $this->showBase ? 1 : 0);
+		if($this->beamTarget !== null){
+			$nbt->setInt(self::TAG_BLOCKTARGET_X, $this->beamTarget->getFloorX());
+			$nbt->setInt(self::TAG_BLOCKTARGET_Y, $this->beamTarget->getFloorY());
+			$nbt->setInt(self::TAG_BLOCKTARGET_Z, $this->beamTarget->getFloorZ());
+		}
+		return $nbt;
+	}
+
+	public function explode() : void{
+		$ev = new EntityPreExplodeEvent($this, 6);
+		$ev->call();
+		if(!$ev->isCancelled()){
+			$explosion = new Explosion($this->getPosition(), $ev->getRadius(), $this);
+			if($ev->isBlockBreaking()){
+				$explosion->explodeA();
+			}
+			$explosion->explodeB();
+		}
+	}
+
+	protected function syncNetworkData(EntityMetadataCollection $properties) : void{
+		parent::syncNetworkData($properties);
+
+		$properties->setGenericFlag(EntityMetadataFlags::SHOWBASE, $this->showBase);
+		$properties->setBlockPos(EntityMetadataProperties::BLOCK_TARGET, BlockPosition::fromVector3($this->beamTarget ?? Vector3::zero()));
+	}
+}
diff --git a/src/entity/object/FallingBlock.php b/src/entity/object/FallingBlock.php
index 9d8af893470..66d4049f88a 100644
--- a/src/entity/object/FallingBlock.php
+++ b/src/entity/object/FallingBlock.php
@@ -35,6 +35,7 @@
 use pocketmine\event\entity\EntityBlockChangeEvent;
 use pocketmine\event\entity\EntityDamageByEntityEvent;
 use pocketmine\event\entity\EntityDamageEvent;
+use pocketmine\item\Item;
 use pocketmine\math\Vector3;
 use pocketmine\nbt\tag\ByteTag;
 use pocketmine\nbt\tag\CompoundTag;
@@ -194,6 +195,10 @@ public function saveNBT() : CompoundTag{
 		return $nbt;
 	}
 
+	public function getPickedItem() : ?Item{
+		return $this->block->asItem();
+	}
+
 	protected function syncNetworkData(EntityMetadataCollection $properties) : void{
 		parent::syncNetworkData($properties);
 
diff --git a/src/entity/object/Painting.php b/src/entity/object/Painting.php
index f6449883c7d..641d040510d 100644
--- a/src/entity/object/Painting.php
+++ b/src/entity/object/Painting.php
@@ -28,6 +28,7 @@
 use pocketmine\entity\EntitySizeInfo;
 use pocketmine\entity\Location;
 use pocketmine\event\entity\EntityDamageByEntityEvent;
+use pocketmine\item\Item;
 use pocketmine\item\VanillaItems;
 use pocketmine\math\AxisAlignedBB;
 use pocketmine\math\Facing;
@@ -165,6 +166,10 @@ protected function sendSpawnPacket(Player $player) : void{
 		));
 	}
 
+	public function getPickedItem() : ?Item{
+		return VanillaItems::PAINTING();
+	}
+
 	/**
 	 * Returns the painting motive (which image is displayed on the painting)
 	 */
diff --git a/src/entity/object/PrimedTNT.php b/src/entity/object/PrimedTNT.php
index ec621adfb80..af3c9792280 100644
--- a/src/entity/object/PrimedTNT.php
+++ b/src/entity/object/PrimedTNT.php
@@ -23,11 +23,13 @@
 
 namespace pocketmine\entity\object;
 
+use pocketmine\block\VanillaBlocks;
 use pocketmine\entity\Entity;
 use pocketmine\entity\EntitySizeInfo;
 use pocketmine\entity\Explosive;
 use pocketmine\event\entity\EntityDamageEvent;
 use pocketmine\event\entity\EntityPreExplodeEvent;
+use pocketmine\item\Item;
 use pocketmine\math\Vector3;
 use pocketmine\nbt\tag\CompoundTag;
 use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
@@ -127,6 +129,10 @@ public function explode() : void{
 		}
 	}
 
+	public function getPickedItem() : ?Item{
+		return VanillaBlocks::TNT()->setWorksUnderwater($this->worksUnderwater)->asItem();
+	}
+
 	protected function syncNetworkData(EntityMetadataCollection $properties) : void{
 		parent::syncNetworkData($properties);
 
diff --git a/src/entity/projectile/IceBomb.php b/src/entity/projectile/IceBomb.php
new file mode 100644
index 00000000000..69a5d46a2c1
--- /dev/null
+++ b/src/entity/projectile/IceBomb.php
@@ -0,0 +1,86 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\entity\projectile;
+
+use pocketmine\block\Block;
+use pocketmine\block\BlockTypeIds;
+use pocketmine\block\VanillaBlocks;
+use pocketmine\event\entity\ProjectileHitEvent;
+use pocketmine\item\VanillaItems;
+use pocketmine\math\AxisAlignedBB;
+use pocketmine\math\RayTraceResult;
+use pocketmine\math\Vector3;
+use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
+use pocketmine\world\particle\ItemBreakParticle;
+use pocketmine\world\sound\IceBombHitSound;
+
+class IceBomb extends Throwable{
+	public static function getNetworkTypeId() : string{ return EntityIds::ICE_BOMB; }
+
+	public function getResultDamage() : int{
+		return -1;
+	}
+
+	protected function calculateInterceptWithBlock(Block $block, Vector3 $start, Vector3 $end) : ?RayTraceResult{
+		if($block->getTypeId() === BlockTypeIds::WATER){
+			$pos = $block->getPosition();
+
+			return AxisAlignedBB::one()->offset($pos->x, $pos->y, $pos->z)->calculateIntercept($start, $end);
+		}
+
+		return parent::calculateInterceptWithBlock($block, $start, $end);
+	}
+
+	protected function onHit(ProjectileHitEvent $event) : void{
+		$world = $this->getWorld();
+		$pos = $this->location;
+
+		$world->addSound($pos, new IceBombHitSound());
+		$itemBreakParticle = new ItemBreakParticle(VanillaItems::ICE_BOMB());
+		for($i = 0; $i < 6; ++$i){
+			$world->addParticle($pos, $itemBreakParticle);
+		}
+	}
+
+	protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{
+		parent::onHitBlock($blockHit, $hitResult);
+
+		$pos = $blockHit->getPosition();
+		$world = $pos->getWorld();
+		$posX = $pos->getFloorX();
+		$posY = $pos->getFloorY();
+		$posZ = $pos->getFloorZ();
+
+		$ice = VanillaBlocks::ICE();
+		for($x = $posX - 1; $x <= $posX + 1; $x++){
+			for($y = $posY - 1; $y <= $posY + 1; $y++){
+				for($z = $posZ - 1; $z <= $posZ + 1; $z++){
+					if($world->getBlockAt($x, $y, $z)->getTypeId() === BlockTypeIds::WATER){
+						$world->setBlockAt($x, $y, $z, $ice);
+					}
+				}
+			}
+		}
+	}
+}
diff --git a/src/entity/projectile/Projectile.php b/src/entity/projectile/Projectile.php
index 55950b6a6d1..0abc274b5dd 100644
--- a/src/entity/projectile/Projectile.php
+++ b/src/entity/projectile/Projectile.php
@@ -28,6 +28,7 @@
 use pocketmine\entity\Entity;
 use pocketmine\entity\Living;
 use pocketmine\entity\Location;
+use pocketmine\entity\object\EndCrystal;
 use pocketmine\event\entity\EntityCombustByEntityEvent;
 use pocketmine\event\entity\EntityDamageByChildEntityEvent;
 use pocketmine\event\entity\EntityDamageByEntityEvent;
@@ -96,7 +97,7 @@ protected function initEntity(CompoundTag $nbt) : void{
 	}
 
 	public function canCollideWith(Entity $entity) : bool{
-		return $entity instanceof Living && !$this->onGround;
+		return ($entity instanceof Living || $entity instanceof EndCrystal) && !$this->onGround;
 	}
 
 	public function canBeCollidedWith() : bool{
diff --git a/src/event/player/PlayerEntityPickEvent.php b/src/event/player/PlayerEntityPickEvent.php
new file mode 100644
index 00000000000..3c742d6c489
--- /dev/null
+++ b/src/event/player/PlayerEntityPickEvent.php
@@ -0,0 +1,53 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\event\player;
+
+use pocketmine\entity\Entity;
+use pocketmine\event\Cancellable;
+use pocketmine\event\CancellableTrait;
+use pocketmine\item\Item;
+use pocketmine\player\Player;
+
+/**
+ * Called when a player middle-clicks on an entity to get an item in creative mode.
+ */
+class PlayerEntityPickEvent extends PlayerEvent implements Cancellable{
+	use CancellableTrait;
+
+	public function __construct(
+		Player $player,
+		private Entity $entityClicked,
+		private Item $resultItem
+	){
+		$this->player = $player;
+	}
+
+	public function getEntity() : Entity{
+		return $this->entityClicked;
+	}
+
+	public function getResultItem() : Item{
+		return $this->resultItem;
+	}
+}
diff --git a/src/event/player/PlayerInteractEvent.php b/src/event/player/PlayerInteractEvent.php
index 6119d5e99cb..46daf70813d 100644
--- a/src/event/player/PlayerInteractEvent.php
+++ b/src/event/player/PlayerInteractEvent.php
@@ -42,6 +42,9 @@ class PlayerInteractEvent extends PlayerEvent implements Cancellable{
 
 	protected Vector3 $touchVector;
 
+	protected bool $useItem = true;
+	protected bool $useBlock = true;
+
 	public function __construct(
 		Player $player,
 		protected Item $item,
@@ -73,4 +76,28 @@ public function getTouchVector() : Vector3{
 	public function getFace() : int{
 		return $this->blockFace;
 	}
+
+	/**
+	 * Returns whether the item may react to the interaction. If disabled, items such as spawn eggs will not activate.
+	 * This does NOT prevent blocks from being placed - it makes the item behave as if the player is sneaking.
+	 */
+	public function useItem() : bool{ return $this->useItem; }
+
+	/**
+	 * Sets whether the used item may react to the interaction. If false, items such as spawn eggs will not activate.
+	 * This does NOT prevent blocks from being placed - it makes the item behave as if the player is sneaking.
+	 */
+	public function setUseItem(bool $useItem) : void{ $this->useItem = $useItem; }
+
+	/**
+	 * Returns whether the block may react to the interaction. If false, doors, fence gates and trapdoors will not
+	 * respond, containers will not open, etc.
+	 */
+	public function useBlock() : bool{ return $this->useBlock; }
+
+	/**
+	 * Sets whether the block may react to the interaction. If false, doors, fence gates and trapdoors will not
+	 * respond, containers will not open, etc.
+	 */
+	public function setUseBlock(bool $useBlock) : void{ $this->useBlock = $useBlock; }
 }
diff --git a/src/inventory/transaction/InventoryTransaction.php b/src/inventory/transaction/InventoryTransaction.php
index b3465a8dfb1..47290e40151 100644
--- a/src/inventory/transaction/InventoryTransaction.php
+++ b/src/inventory/transaction/InventoryTransaction.php
@@ -111,6 +111,9 @@ public function addAction(InventoryAction $action) : void{
 		if(!isset($this->actions[$hash = spl_object_id($action)])){
 			$this->actions[$hash] = $action;
 			$action->onAddToTransaction($this);
+			if($action instanceof SlotChangeAction && !isset($this->inventories[$inventoryId = spl_object_id($action->getInventory())])){
+				$this->inventories[$inventoryId] = $action->getInventory();
+			}
 		}else{
 			throw new \InvalidArgumentException("Tried to add the same action to a transaction twice");
 		}
@@ -129,16 +132,6 @@ private function shuffleActions() : void{
 		$this->actions = $actions;
 	}
 
-	/**
-	 * @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions
-	 * involving inventories.
-	 */
-	public function addInventory(Inventory $inventory) : void{
-		if(!isset($this->inventories[$hash = spl_object_id($inventory)])){
-			$this->inventories[$hash] = $inventory;
-		}
-	}
-
 	/**
 	 * @param Item[] $needItems
 	 * @param Item[] $haveItems
diff --git a/src/inventory/transaction/action/InventoryAction.php b/src/inventory/transaction/action/InventoryAction.php
index fff3d22b809..2f0db083c9f 100644
--- a/src/inventory/transaction/action/InventoryAction.php
+++ b/src/inventory/transaction/action/InventoryAction.php
@@ -60,6 +60,7 @@ abstract public function validate(Player $source) : void;
 
 	/**
 	 * Called when the action is added to the specified InventoryTransaction.
+	 * @deprecated
 	 */
 	public function onAddToTransaction(InventoryTransaction $transaction) : void{
 
diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php
index 68c3dba1b32..3c9b8e5cf79 100644
--- a/src/inventory/transaction/action/SlotChangeAction.php
+++ b/src/inventory/transaction/action/SlotChangeAction.php
@@ -25,7 +25,6 @@
 
 use pocketmine\inventory\Inventory;
 use pocketmine\inventory\SlotValidatedInventory;
-use pocketmine\inventory\transaction\InventoryTransaction;
 use pocketmine\inventory\transaction\TransactionValidationException;
 use pocketmine\item\Item;
 use pocketmine\player\Player;
@@ -85,13 +84,6 @@ public function validate(Player $source) : void{
 		}
 	}
 
-	/**
-	 * Adds this action's target inventory to the transaction's inventory list.
-	 */
-	public function onAddToTransaction(InventoryTransaction $transaction) : void{
-		$transaction->addInventory($this->inventory);
-	}
-
 	/**
 	 * Sets the item into the target inventory.
 	 */
diff --git a/src/item/Armor.php b/src/item/Armor.php
index 417c57f75ca..63a8003adc9 100644
--- a/src/item/Armor.php
+++ b/src/item/Armor.php
@@ -33,7 +33,7 @@
 use pocketmine\nbt\tag\IntTag;
 use pocketmine\player\Player;
 use pocketmine\utils\Binary;
-use function lcg_value;
+use pocketmine\utils\Utils;
 use function mt_rand;
 
 class Armor extends Durable{
@@ -129,7 +129,7 @@ protected function getUnbreakingDamageReduction(int $amount) : int{
 
 			$chance = 1 / ($unbreakingLevel + 1);
 			for($i = 0; $i < $amount; ++$i){
-				if(mt_rand(1, 100) > 60 && lcg_value() > $chance){ //unbreaking only applies to armor 40% of the time at best
+				if(mt_rand(1, 100) > 60 && Utils::getRandomFloat() > $chance){ //unbreaking only applies to armor 40% of the time at best
 					$negated++;
 				}
 			}
diff --git a/src/item/Durable.php b/src/item/Durable.php
index f110f6ea517..069a01202ed 100644
--- a/src/item/Durable.php
+++ b/src/item/Durable.php
@@ -25,7 +25,7 @@
 
 use pocketmine\item\enchantment\VanillaEnchantments;
 use pocketmine\nbt\tag\CompoundTag;
-use function lcg_value;
+use pocketmine\utils\Utils;
 use function min;
 
 abstract class Durable extends Item{
@@ -87,7 +87,7 @@ protected function getUnbreakingDamageReduction(int $amount) : int{
 
 			$chance = 1 / ($unbreakingLevel + 1);
 			for($i = 0; $i < $amount; ++$i){
-				if(lcg_value() > $chance){
+				if(Utils::getRandomFloat() > $chance){
 					$negated++;
 				}
 			}
diff --git a/src/item/EndCrystal.php b/src/item/EndCrystal.php
new file mode 100644
index 00000000000..320d657e6df
--- /dev/null
+++ b/src/item/EndCrystal.php
@@ -0,0 +1,59 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\item;
+
+use pocketmine\block\Block;
+use pocketmine\block\BlockTypeIds;
+use pocketmine\entity\Location;
+use pocketmine\entity\object\EndCrystal as EntityEndCrystal;
+use pocketmine\math\AxisAlignedBB;
+use pocketmine\math\Facing;
+use pocketmine\math\Vector3;
+use pocketmine\player\Player;
+use function count;
+
+class EndCrystal extends Item{
+
+	public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{
+		if($blockClicked->getTypeId() === BlockTypeIds::OBSIDIAN || $blockClicked->getTypeId() === BlockTypeIds::BEDROCK){
+			$pos = $blockClicked->getPosition();
+			$world = $pos->getWorld();
+			$bb = AxisAlignedBB::one()
+				->offset($pos->getX(), $pos->getY(), $pos->getZ())
+				->extend(Facing::UP, 1);
+			if(
+				count($world->getNearbyEntities($bb)) === 0 &&
+				$blockClicked->getSide(Facing::UP)->getTypeId() === BlockTypeIds::AIR &&
+				$blockClicked->getSide(Facing::UP, 2)->getTypeId() === BlockTypeIds::AIR
+			){
+				$crystal = new EntityEndCrystal(Location::fromObject($pos->add(0.5, 1, 0.5), $world));
+				$crystal->spawnToAll();
+
+				$this->pop();
+				return ItemUseResult::SUCCESS;
+			}
+		}
+		return ItemUseResult::NONE;
+	}
+}
diff --git a/src/item/Food.php b/src/item/Food.php
index 1950c4b14ae..d01ce9e18d8 100644
--- a/src/item/Food.php
+++ b/src/item/Food.php
@@ -44,6 +44,6 @@ public function onConsume(Living $consumer) : void{
 	}
 
 	public function canStartUsingItem(Player $player) : bool{
-		return !$this->requiresHunger() || $player->getHungerManager()->isHungry();
+		return !$this->requiresHunger() || $player->canEat();
 	}
 }
diff --git a/src/item/GoatHorn.php b/src/item/GoatHorn.php
new file mode 100644
index 00000000000..088701e3986
--- /dev/null
+++ b/src/item/GoatHorn.php
@@ -0,0 +1,71 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\item;
+
+use pocketmine\data\runtime\RuntimeDataDescriber;
+use pocketmine\math\Vector3;
+use pocketmine\player\Player;
+use pocketmine\world\sound\GoatHornSound;
+
+class GoatHorn extends Item implements Releasable{
+
+	private GoatHornType $goatHornType = GoatHornType::PONDER;
+
+	protected function describeState(RuntimeDataDescriber $w) : void{
+		$w->enum($this->goatHornType);
+	}
+
+	public function getHornType() : GoatHornType{ return $this->goatHornType; }
+
+	/**
+	 * @return $this
+	 */
+	public function setHornType(GoatHornType $type) : self{
+		$this->goatHornType = $type;
+		return $this;
+	}
+
+	public function getMaxStackSize() : int{
+		return 1;
+	}
+
+	public function getCooldownTicks() : int{
+		return 140;
+	}
+
+	public function getCooldownTag() : ?string{
+		return ItemCooldownTags::GOAT_HORN;
+	}
+
+	public function canStartUsingItem(Player $player) : bool{
+		return true;
+	}
+
+	public function onClickAir(Player $player, Vector3 $directionVector, array &$returnedItems) : ItemUseResult{
+		$position = $player->getPosition();
+		$position->getWorld()->addSound($position, new GoatHornSound($this->goatHornType));
+
+		return ItemUseResult::SUCCESS;
+	}
+}
diff --git a/src/item/GoatHornType.php b/src/item/GoatHornType.php
new file mode 100644
index 00000000000..6c0c3b2f7fc
--- /dev/null
+++ b/src/item/GoatHornType.php
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\item;
+
+enum GoatHornType{
+
+	case PONDER;
+	case SING;
+	case SEEK;
+	case FEEL;
+	case ADMIRE;
+	case CALL;
+	case YEARN;
+	case DREAM;
+}
diff --git a/src/item/IceBomb.php b/src/item/IceBomb.php
new file mode 100644
index 00000000000..fbc9f24a2f6
--- /dev/null
+++ b/src/item/IceBomb.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\item;
+
+use pocketmine\entity\Location;
+use pocketmine\entity\projectile\IceBomb as IceBombEntity;
+use pocketmine\entity\projectile\Throwable;
+use pocketmine\player\Player;
+
+class IceBomb extends ProjectileItem{
+
+	public function getMaxStackSize() : int{
+		return 16;
+	}
+
+	protected function createEntity(Location $location, Player $thrower) : Throwable{
+		return new IceBombEntity($location, $thrower);
+	}
+
+	public function getThrowForce() : float{
+		return 1.5;
+	}
+
+	public function getCooldownTicks() : int{
+		return 10;
+	}
+}
diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php
index 66eebed744c..c93c23e817e 100644
--- a/src/item/ItemTypeIds.php
+++ b/src/item/ItemTypeIds.php
@@ -324,8 +324,12 @@ private function __construct(){
 	public const SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = 20285;
 	public const PITCHER_POD = 20286;
 	public const NAME_TAG = 20287;
+	public const GOAT_HORN = 20288;
+	public const END_CRYSTAL = 20289;
+	public const ICE_BOMB = 20290;
+	public const RECOVERY_COMPASS = 20291;
 
-	public const FIRST_UNUSED_ITEM_ID = 20288;
+	public const FIRST_UNUSED_ITEM_ID = 20292;
 
 	private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;
 
diff --git a/src/item/Potion.php b/src/item/Potion.php
index 41b0f634a88..192a257841f 100644
--- a/src/item/Potion.php
+++ b/src/item/Potion.php
@@ -26,6 +26,7 @@
 use pocketmine\data\runtime\RuntimeDataDescriber;
 use pocketmine\entity\Living;
 use pocketmine\player\Player;
+use pocketmine\world\sound\BottleEmptySound;
 
 class Potion extends Item implements ConsumableItem{
 
@@ -50,7 +51,7 @@ public function getMaxStackSize() : int{
 	}
 
 	public function onConsume(Living $consumer) : void{
-
+		$consumer->broadcastSound(new BottleEmptySound());
 	}
 
 	public function getAdditionalEffects() : array{
diff --git a/src/item/RottenFlesh.php b/src/item/RottenFlesh.php
index 4cecc67fcd3..2ea3ee9559c 100644
--- a/src/item/RottenFlesh.php
+++ b/src/item/RottenFlesh.php
@@ -25,7 +25,7 @@
 
 use pocketmine\entity\effect\EffectInstance;
 use pocketmine\entity\effect\VanillaEffects;
-use function lcg_value;
+use pocketmine\utils\Utils;
 
 class RottenFlesh extends Food{
 
@@ -38,7 +38,7 @@ public function getSaturationRestore() : float{
 	}
 
 	public function getAdditionalEffects() : array{
-		if(lcg_value() <= 0.8){
+		if(Utils::getRandomFloat() <= 0.8){
 			return [
 				new EffectInstance(VanillaEffects::HUNGER(), 600)
 			];
diff --git a/src/item/SpawnEgg.php b/src/item/SpawnEgg.php
index 51dcceebd2a..ab4f0e14983 100644
--- a/src/item/SpawnEgg.php
+++ b/src/item/SpawnEgg.php
@@ -27,15 +27,15 @@
 use pocketmine\entity\Entity;
 use pocketmine\math\Vector3;
 use pocketmine\player\Player;
+use pocketmine\utils\Utils;
 use pocketmine\world\World;
-use function lcg_value;
 
 abstract class SpawnEgg extends Item{
 
 	abstract protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity;
 
 	public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{
-		$entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), lcg_value() * 360, 0);
+		$entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), Utils::getRandomFloat() * 360, 0);
 
 		if($this->hasCustomName()){
 			$entity->setNameTag($this->getCustomName());
diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php
index ee0f1f5c520..09c93d5d925 100644
--- a/src/item/StringToItemParser.php
+++ b/src/item/StringToItemParser.php
@@ -1184,6 +1184,13 @@ private static function registerDynamicItems(self $result) : void{
 
 			$result->register($prefix("dye"), fn() => Items::DYE()->setColor($color));
 		}
+
+		foreach(GoatHornType::cases() as $goatHornType){
+			$prefix = fn(string $name) => strtolower($goatHornType->name) . "_" . $name;
+
+			$result->register($prefix("goat_horn"), fn() => Items::GOAT_HORN()->setHornType($goatHornType));
+		}
+
 		foreach(SuspiciousStewType::cases() as $suspiciousStewType){
 			$prefix = fn(string $name) => strtolower($suspiciousStewType->name) . "_" . $name;
 
@@ -1323,6 +1330,7 @@ private static function registerItems(self $result) : void{
 		$result->register("enchanted_book", fn() => Items::ENCHANTED_BOOK());
 		$result->register("enchanted_golden_apple", fn() => Items::ENCHANTED_GOLDEN_APPLE());
 		$result->register("enchanting_bottle", fn() => Items::EXPERIENCE_BOTTLE());
+		$result->register("end_crystal", fn() => Items::END_CRYSTAL());
 		$result->register("ender_pearl", fn() => Items::ENDER_PEARL());
 		$result->register("experience_bottle", fn() => Items::EXPERIENCE_BOTTLE());
 		$result->register("eye_armor_trim_smithing_template", fn() => Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE());
@@ -1341,6 +1349,7 @@ private static function registerItems(self $result) : void{
 		$result->register("glow_berries", fn() => Items::GLOW_BERRIES());
 		$result->register("glow_ink_sac", fn() => Items::GLOW_INK_SAC());
 		$result->register("glowstone_dust", fn() => Items::GLOWSTONE_DUST());
+		$result->register("goat_horn", fn() => Items::GOAT_HORN());
 		$result->register("gold_axe", fn() => Items::GOLDEN_AXE());
 		$result->register("gold_boots", fn() => Items::GOLDEN_BOOTS());
 		$result->register("gold_chestplate", fn() => Items::GOLDEN_CHESTPLATE());
@@ -1369,6 +1378,7 @@ private static function registerItems(self $result) : void{
 		$result->register("honey_bottle", fn() => Items::HONEY_BOTTLE());
 		$result->register("host_armor_trim_smithing_template", fn() => Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE());
 		$result->register("honeycomb", fn() => Items::HONEYCOMB());
+		$result->register("ice_bomb", fn() => Items::ICE_BOMB());
 		$result->register("ink_sac", fn() => Items::INK_SAC());
 		$result->register("iron_axe", fn() => Items::IRON_AXE());
 		$result->register("iron_boots", fn() => Items::IRON_BOOTS());
@@ -1471,6 +1481,7 @@ private static function registerItems(self $result) : void{
 		$result->register("record_strad", fn() => Items::RECORD_STRAD());
 		$result->register("record_wait", fn() => Items::RECORD_WAIT());
 		$result->register("record_ward", fn() => Items::RECORD_WARD());
+		$result->register("recovery_compass", fn() => Items::RECOVERY_COMPASS());
 		$result->register("redstone", fn() => Items::REDSTONE_DUST());
 		$result->register("redstone_dust", fn() => Items::REDSTONE_DUST());
 		$result->register("rib_armor_trim_smithing_template", fn() => Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());
diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php
index 5115ee48a8b..6768ed8f07e 100644
--- a/src/item/VanillaItems.php
+++ b/src/item/VanillaItems.php
@@ -33,11 +33,12 @@
 use pocketmine\inventory\ArmorInventory;
 use pocketmine\item\enchantment\ItemEnchantmentTags as EnchantmentTags;
 use pocketmine\item\ItemIdentifier as IID;
-use pocketmine\item\ItemTypeIds as Ids;
 use pocketmine\item\VanillaArmorMaterials as ArmorMaterials;
 use pocketmine\math\Vector3;
 use pocketmine\utils\CloningRegistryTrait;
 use pocketmine\world\World;
+use function is_int;
+use function mb_strtoupper;
 use function strtolower;
 
 /**
@@ -157,6 +158,7 @@
  * @method static EnchantedBook ENCHANTED_BOOK()
  * @method static GoldenAppleEnchanted ENCHANTED_GOLDEN_APPLE()
  * @method static EnderPearl ENDER_PEARL()
+ * @method static EndCrystal END_CRYSTAL()
  * @method static ExperienceBottle EXPERIENCE_BOTTLE()
  * @method static Item EYE_ARMOR_TRIM_SMITHING_TEMPLATE()
  * @method static Item FEATHER()
@@ -171,6 +173,7 @@
  * @method static Item GLOWSTONE_DUST()
  * @method static GlowBerries GLOW_BERRIES()
  * @method static Item GLOW_INK_SAC()
+ * @method static GoatHorn GOAT_HORN()
  * @method static GoldenApple GOLDEN_APPLE()
  * @method static Axe GOLDEN_AXE()
  * @method static Armor GOLDEN_BOOTS()
@@ -189,6 +192,7 @@
  * @method static Item HONEYCOMB()
  * @method static HoneyBottle HONEY_BOTTLE()
  * @method static Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE()
+ * @method static IceBomb ICE_BOMB()
  * @method static Item INK_SAC()
  * @method static Axe IRON_AXE()
  * @method static Armor IRON_BOOTS()
@@ -280,6 +284,7 @@
  * @method static Record RECORD_STRAD()
  * @method static Record RECORD_WAIT()
  * @method static Record RECORD_WARD()
+ * @method static Item RECOVERY_COMPASS()
  * @method static Redstone REDSTONE_DUST()
  * @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE()
  * @method static RottenFlesh ROTTEN_FLESH()
@@ -339,8 +344,29 @@ private function __construct(){
 		//NOOP
 	}
 
-	protected static function register(string $name, Item $item) : void{
+	/**
+	 * @phpstan-template TItem of Item
+	 * @phpstan-param \Closure(IID) : TItem $createItem
+	 * @phpstan-return TItem
+	 */
+	protected static function register(string $name, \Closure $createItem) : Item{
+		//this sketchy hack allows us to avoid manually writing the constants inline
+		//since type IDs are generated from this class anyway, I'm OK with this hack
+		//nonetheless, we should try to get rid of it in a future major version (e.g by using string type IDs)
+		$reflect = new \ReflectionClass(ItemTypeIds::class);
+		$typeId = $reflect->getConstant(mb_strtoupper($name));
+		if(!is_int($typeId)){
+			//this allows registering new stuff without adding new type ID constants
+			//this reduces the number of mandatory steps to test new features in local development
+			\GlobalLogger::get()->error(self::class . ": No constant type ID found for $name, generating a new one");
+			$typeId = ItemTypeIds::newId();
+		}
+
+		$item = $createItem(new IID($typeId));
+
 		self::_registryRegister($name, $item);
+
+		return $item;
 	}
 
 	/**
@@ -360,242 +386,240 @@ protected static function setup() : void{
 		self::registerTierToolItems();
 		self::registerSmithingTemplates();
 
-		self::register("air", Blocks::AIR()->asItem()->setCount(0));
+		//this doesn't use the regular register() because it doesn't have an item typeID
+		//in the future we'll probably want to dissociate this from the air block and make a proper null item
+		self::_registryRegister("air", Blocks::AIR()->asItem()->setCount(0));
 
-		self::register("acacia_sign", new ItemBlockWallOrFloor(new IID(Ids::ACACIA_SIGN), Blocks::ACACIA_SIGN(), Blocks::ACACIA_WALL_SIGN()));
-		self::register("amethyst_shard", new Item(new IID(Ids::AMETHYST_SHARD), "Amethyst Shard"));
-		self::register("apple", new Apple(new IID(Ids::APPLE), "Apple"));
-		self::register("arrow", new Arrow(new IID(Ids::ARROW), "Arrow"));
-		self::register("baked_potato", new BakedPotato(new IID(Ids::BAKED_POTATO), "Baked Potato"));
-		self::register("bamboo", new Bamboo(new IID(Ids::BAMBOO), "Bamboo"));
-		self::register("banner", new Banner(new IID(Ids::BANNER), Blocks::BANNER(), Blocks::WALL_BANNER()));
-		self::register("beetroot", new Beetroot(new IID(Ids::BEETROOT), "Beetroot"));
-		self::register("beetroot_seeds", new BeetrootSeeds(new IID(Ids::BEETROOT_SEEDS), "Beetroot Seeds"));
-		self::register("beetroot_soup", new BeetrootSoup(new IID(Ids::BEETROOT_SOUP), "Beetroot Soup"));
-		self::register("birch_sign", new ItemBlockWallOrFloor(new IID(Ids::BIRCH_SIGN), Blocks::BIRCH_SIGN(), Blocks::BIRCH_WALL_SIGN()));
-		self::register("blaze_powder", new Item(new IID(Ids::BLAZE_POWDER), "Blaze Powder"));
-		self::register("blaze_rod", new BlazeRod(new IID(Ids::BLAZE_ROD), "Blaze Rod"));
-		self::register("bleach", new Item(new IID(Ids::BLEACH), "Bleach"));
-		self::register("bone", new Item(new IID(Ids::BONE), "Bone"));
-		self::register("bone_meal", new Fertilizer(new IID(Ids::BONE_MEAL), "Bone Meal"));
-		self::register("book", new Book(new IID(Ids::BOOK), "Book", [EnchantmentTags::ALL]));
-		self::register("bow", new Bow(new IID(Ids::BOW), "Bow", [EnchantmentTags::BOW]));
-		self::register("bowl", new Bowl(new IID(Ids::BOWL), "Bowl"));
-		self::register("bread", new Bread(new IID(Ids::BREAD), "Bread"));
-		self::register("brick", new Item(new IID(Ids::BRICK), "Brick"));
-		self::register("bucket", new Bucket(new IID(Ids::BUCKET), "Bucket"));
-		self::register("carrot", new Carrot(new IID(Ids::CARROT), "Carrot"));
-		self::register("charcoal", new Coal(new IID(Ids::CHARCOAL), "Charcoal"));
-		self::register("cherry_sign", new ItemBlockWallOrFloor(new IID(Ids::CHERRY_SIGN), Blocks::CHERRY_SIGN(), Blocks::CHERRY_WALL_SIGN()));
-		self::register("chemical_aluminium_oxide", new Item(new IID(Ids::CHEMICAL_ALUMINIUM_OXIDE), "Aluminium Oxide"));
-		self::register("chemical_ammonia", new Item(new IID(Ids::CHEMICAL_AMMONIA), "Ammonia"));
-		self::register("chemical_barium_sulphate", new Item(new IID(Ids::CHEMICAL_BARIUM_SULPHATE), "Barium Sulphate"));
-		self::register("chemical_benzene", new Item(new IID(Ids::CHEMICAL_BENZENE), "Benzene"));
-		self::register("chemical_boron_trioxide", new Item(new IID(Ids::CHEMICAL_BORON_TRIOXIDE), "Boron Trioxide"));
-		self::register("chemical_calcium_bromide", new Item(new IID(Ids::CHEMICAL_CALCIUM_BROMIDE), "Calcium Bromide"));
-		self::register("chemical_calcium_chloride", new Item(new IID(Ids::CHEMICAL_CALCIUM_CHLORIDE), "Calcium Chloride"));
-		self::register("chemical_cerium_chloride", new Item(new IID(Ids::CHEMICAL_CERIUM_CHLORIDE), "Cerium Chloride"));
-		self::register("chemical_charcoal", new Item(new IID(Ids::CHEMICAL_CHARCOAL), "Charcoal"));
-		self::register("chemical_crude_oil", new Item(new IID(Ids::CHEMICAL_CRUDE_OIL), "Crude Oil"));
-		self::register("chemical_glue", new Item(new IID(Ids::CHEMICAL_GLUE), "Glue"));
-		self::register("chemical_hydrogen_peroxide", new Item(new IID(Ids::CHEMICAL_HYDROGEN_PEROXIDE), "Hydrogen Peroxide"));
-		self::register("chemical_hypochlorite", new Item(new IID(Ids::CHEMICAL_HYPOCHLORITE), "Hypochlorite"));
-		self::register("chemical_ink", new Item(new IID(Ids::CHEMICAL_INK), "Ink"));
-		self::register("chemical_iron_sulphide", new Item(new IID(Ids::CHEMICAL_IRON_SULPHIDE), "Iron Sulphide"));
-		self::register("chemical_latex", new Item(new IID(Ids::CHEMICAL_LATEX), "Latex"));
-		self::register("chemical_lithium_hydride", new Item(new IID(Ids::CHEMICAL_LITHIUM_HYDRIDE), "Lithium Hydride"));
-		self::register("chemical_luminol", new Item(new IID(Ids::CHEMICAL_LUMINOL), "Luminol"));
-		self::register("chemical_magnesium_nitrate", new Item(new IID(Ids::CHEMICAL_MAGNESIUM_NITRATE), "Magnesium Nitrate"));
-		self::register("chemical_magnesium_oxide", new Item(new IID(Ids::CHEMICAL_MAGNESIUM_OXIDE), "Magnesium Oxide"));
-		self::register("chemical_magnesium_salts", new Item(new IID(Ids::CHEMICAL_MAGNESIUM_SALTS), "Magnesium Salts"));
-		self::register("chemical_mercuric_chloride", new Item(new IID(Ids::CHEMICAL_MERCURIC_CHLORIDE), "Mercuric Chloride"));
-		self::register("chemical_polyethylene", new Item(new IID(Ids::CHEMICAL_POLYETHYLENE), "Polyethylene"));
-		self::register("chemical_potassium_chloride", new Item(new IID(Ids::CHEMICAL_POTASSIUM_CHLORIDE), "Potassium Chloride"));
-		self::register("chemical_potassium_iodide", new Item(new IID(Ids::CHEMICAL_POTASSIUM_IODIDE), "Potassium Iodide"));
-		self::register("chemical_rubbish", new Item(new IID(Ids::CHEMICAL_RUBBISH), "Rubbish"));
-		self::register("chemical_salt", new Item(new IID(Ids::CHEMICAL_SALT), "Salt"));
-		self::register("chemical_soap", new Item(new IID(Ids::CHEMICAL_SOAP), "Soap"));
-		self::register("chemical_sodium_acetate", new Item(new IID(Ids::CHEMICAL_SODIUM_ACETATE), "Sodium Acetate"));
-		self::register("chemical_sodium_fluoride", new Item(new IID(Ids::CHEMICAL_SODIUM_FLUORIDE), "Sodium Fluoride"));
-		self::register("chemical_sodium_hydride", new Item(new IID(Ids::CHEMICAL_SODIUM_HYDRIDE), "Sodium Hydride"));
-		self::register("chemical_sodium_hydroxide", new Item(new IID(Ids::CHEMICAL_SODIUM_HYDROXIDE), "Sodium Hydroxide"));
-		self::register("chemical_sodium_hypochlorite", new Item(new IID(Ids::CHEMICAL_SODIUM_HYPOCHLORITE), "Sodium Hypochlorite"));
-		self::register("chemical_sodium_oxide", new Item(new IID(Ids::CHEMICAL_SODIUM_OXIDE), "Sodium Oxide"));
-		self::register("chemical_sugar", new Item(new IID(Ids::CHEMICAL_SUGAR), "Sugar"));
-		self::register("chemical_sulphate", new Item(new IID(Ids::CHEMICAL_SULPHATE), "Sulphate"));
-		self::register("chemical_tungsten_chloride", new Item(new IID(Ids::CHEMICAL_TUNGSTEN_CHLORIDE), "Tungsten Chloride"));
-		self::register("chemical_water", new Item(new IID(Ids::CHEMICAL_WATER), "Water"));
-		self::register("chorus_fruit", new ChorusFruit(new IID(Ids::CHORUS_FRUIT), "Chorus Fruit"));
-		self::register("clay", new Item(new IID(Ids::CLAY), "Clay"));
-		self::register("clock", new Clock(new IID(Ids::CLOCK), "Clock"));
-		self::register("clownfish", new Clownfish(new IID(Ids::CLOWNFISH), "Clownfish"));
-		self::register("coal", new Coal(new IID(Ids::COAL), "Coal"));
-		self::register("cocoa_beans", new CocoaBeans(new IID(Ids::COCOA_BEANS), "Cocoa Beans"));
-		self::register("compass", new Compass(new IID(Ids::COMPASS), "Compass", [EnchantmentTags::COMPASS]));
-		self::register("cooked_chicken", new CookedChicken(new IID(Ids::COOKED_CHICKEN), "Cooked Chicken"));
-		self::register("cooked_fish", new CookedFish(new IID(Ids::COOKED_FISH), "Cooked Fish"));
-		self::register("cooked_mutton", new CookedMutton(new IID(Ids::COOKED_MUTTON), "Cooked Mutton"));
-		self::register("cooked_porkchop", new CookedPorkchop(new IID(Ids::COOKED_PORKCHOP), "Cooked Porkchop"));
-		self::register("cooked_rabbit", new CookedRabbit(new IID(Ids::COOKED_RABBIT), "Cooked Rabbit"));
-		self::register("cooked_salmon", new CookedSalmon(new IID(Ids::COOKED_SALMON), "Cooked Salmon"));
-		self::register("cookie", new Cookie(new IID(Ids::COOKIE), "Cookie"));
-		self::register("copper_ingot", new Item(new IID(Ids::COPPER_INGOT), "Copper Ingot"));
-		self::register("coral_fan", new CoralFan(new IID(Ids::CORAL_FAN)));
-		self::register("crimson_sign", new ItemBlockWallOrFloor(new IID(Ids::CRIMSON_SIGN), Blocks::CRIMSON_SIGN(), Blocks::CRIMSON_WALL_SIGN()));
-		self::register("dark_oak_sign", new ItemBlockWallOrFloor(new IID(Ids::DARK_OAK_SIGN), Blocks::DARK_OAK_SIGN(), Blocks::DARK_OAK_WALL_SIGN()));
-		self::register("diamond", new Item(new IID(Ids::DIAMOND), "Diamond"));
-		self::register("disc_fragment_5", new Item(new IID(Ids::DISC_FRAGMENT_5), "Disc Fragment (5)"));
-		self::register("dragon_breath", new Item(new IID(Ids::DRAGON_BREATH), "Dragon's Breath"));
-		self::register("dried_kelp", new DriedKelp(new IID(Ids::DRIED_KELP), "Dried Kelp"));
+		self::register("acacia_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::ACACIA_SIGN(), Blocks::ACACIA_WALL_SIGN()));
+		self::register("amethyst_shard", fn(IID $id) => new Item($id, "Amethyst Shard"));
+		self::register("apple", fn(IID $id) => new Apple($id, "Apple"));
+		self::register("arrow", fn(IID $id) => new Arrow($id, "Arrow"));
+		self::register("baked_potato", fn(IID $id) => new BakedPotato($id, "Baked Potato"));
+		self::register("bamboo", fn(IID $id) => new Bamboo($id, "Bamboo"));
+		self::register("banner", fn(IID $id) => new Banner($id, Blocks::BANNER(), Blocks::WALL_BANNER()));
+		self::register("beetroot", fn(IID $id) => new Beetroot($id, "Beetroot"));
+		self::register("beetroot_seeds", fn(IID $id) => new BeetrootSeeds($id, "Beetroot Seeds"));
+		self::register("beetroot_soup", fn(IID $id) => new BeetrootSoup($id, "Beetroot Soup"));
+		self::register("birch_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::BIRCH_SIGN(), Blocks::BIRCH_WALL_SIGN()));
+		self::register("blaze_powder", fn(IID $id) => new Item($id, "Blaze Powder"));
+		self::register("blaze_rod", fn(IID $id) => new BlazeRod($id, "Blaze Rod"));
+		self::register("bleach", fn(IID $id) => new Item($id, "Bleach"));
+		self::register("bone", fn(IID $id) => new Item($id, "Bone"));
+		self::register("bone_meal", fn(IID $id) => new Fertilizer($id, "Bone Meal"));
+		self::register("book", fn(IID $id) => new Book($id, "Book", [EnchantmentTags::ALL]));
+		self::register("bow", fn(IID $id) => new Bow($id, "Bow", [EnchantmentTags::BOW]));
+		self::register("bowl", fn(IID $id) => new Bowl($id, "Bowl"));
+		self::register("bread", fn(IID $id) => new Bread($id, "Bread"));
+		self::register("brick", fn(IID $id) => new Item($id, "Brick"));
+		self::register("bucket", fn(IID $id) => new Bucket($id, "Bucket"));
+		self::register("carrot", fn(IID $id) => new Carrot($id, "Carrot"));
+		self::register("charcoal", fn(IID $id) => new Coal($id, "Charcoal"));
+		self::register("cherry_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::CHERRY_SIGN(), Blocks::CHERRY_WALL_SIGN()));
+		self::register("chemical_aluminium_oxide", fn(IID $id) => new Item($id, "Aluminium Oxide"));
+		self::register("chemical_ammonia", fn(IID $id) => new Item($id, "Ammonia"));
+		self::register("chemical_barium_sulphate", fn(IID $id) => new Item($id, "Barium Sulphate"));
+		self::register("chemical_benzene", fn(IID $id) => new Item($id, "Benzene"));
+		self::register("chemical_boron_trioxide", fn(IID $id) => new Item($id, "Boron Trioxide"));
+		self::register("chemical_calcium_bromide", fn(IID $id) => new Item($id, "Calcium Bromide"));
+		self::register("chemical_calcium_chloride", fn(IID $id) => new Item($id, "Calcium Chloride"));
+		self::register("chemical_cerium_chloride", fn(IID $id) => new Item($id, "Cerium Chloride"));
+		self::register("chemical_charcoal", fn(IID $id) => new Item($id, "Charcoal"));
+		self::register("chemical_crude_oil", fn(IID $id) => new Item($id, "Crude Oil"));
+		self::register("chemical_glue", fn(IID $id) => new Item($id, "Glue"));
+		self::register("chemical_hydrogen_peroxide", fn(IID $id) => new Item($id, "Hydrogen Peroxide"));
+		self::register("chemical_hypochlorite", fn(IID $id) => new Item($id, "Hypochlorite"));
+		self::register("chemical_ink", fn(IID $id) => new Item($id, "Ink"));
+		self::register("chemical_iron_sulphide", fn(IID $id) => new Item($id, "Iron Sulphide"));
+		self::register("chemical_latex", fn(IID $id) => new Item($id, "Latex"));
+		self::register("chemical_lithium_hydride", fn(IID $id) => new Item($id, "Lithium Hydride"));
+		self::register("chemical_luminol", fn(IID $id) => new Item($id, "Luminol"));
+		self::register("chemical_magnesium_nitrate", fn(IID $id) => new Item($id, "Magnesium Nitrate"));
+		self::register("chemical_magnesium_oxide", fn(IID $id) => new Item($id, "Magnesium Oxide"));
+		self::register("chemical_magnesium_salts", fn(IID $id) => new Item($id, "Magnesium Salts"));
+		self::register("chemical_mercuric_chloride", fn(IID $id) => new Item($id, "Mercuric Chloride"));
+		self::register("chemical_polyethylene", fn(IID $id) => new Item($id, "Polyethylene"));
+		self::register("chemical_potassium_chloride", fn(IID $id) => new Item($id, "Potassium Chloride"));
+		self::register("chemical_potassium_iodide", fn(IID $id) => new Item($id, "Potassium Iodide"));
+		self::register("chemical_rubbish", fn(IID $id) => new Item($id, "Rubbish"));
+		self::register("chemical_salt", fn(IID $id) => new Item($id, "Salt"));
+		self::register("chemical_soap", fn(IID $id) => new Item($id, "Soap"));
+		self::register("chemical_sodium_acetate", fn(IID $id) => new Item($id, "Sodium Acetate"));
+		self::register("chemical_sodium_fluoride", fn(IID $id) => new Item($id, "Sodium Fluoride"));
+		self::register("chemical_sodium_hydride", fn(IID $id) => new Item($id, "Sodium Hydride"));
+		self::register("chemical_sodium_hydroxide", fn(IID $id) => new Item($id, "Sodium Hydroxide"));
+		self::register("chemical_sodium_hypochlorite", fn(IID $id) => new Item($id, "Sodium Hypochlorite"));
+		self::register("chemical_sodium_oxide", fn(IID $id) => new Item($id, "Sodium Oxide"));
+		self::register("chemical_sugar", fn(IID $id) => new Item($id, "Sugar"));
+		self::register("chemical_sulphate", fn(IID $id) => new Item($id, "Sulphate"));
+		self::register("chemical_tungsten_chloride", fn(IID $id) => new Item($id, "Tungsten Chloride"));
+		self::register("chemical_water", fn(IID $id) => new Item($id, "Water"));
+		self::register("chorus_fruit", fn(IID $id) => new ChorusFruit($id, "Chorus Fruit"));
+		self::register("clay", fn(IID $id) => new Item($id, "Clay"));
+		self::register("clock", fn(IID $id) => new Clock($id, "Clock"));
+		self::register("clownfish", fn(IID $id) => new Clownfish($id, "Clownfish"));
+		self::register("coal", fn(IID $id) => new Coal($id, "Coal"));
+		self::register("cocoa_beans", fn(IID $id) => new CocoaBeans($id, "Cocoa Beans"));
+		self::register("compass", fn(IID $id) => new Compass($id, "Compass", [EnchantmentTags::COMPASS]));
+		self::register("cooked_chicken", fn(IID $id) => new CookedChicken($id, "Cooked Chicken"));
+		self::register("cooked_fish", fn(IID $id) => new CookedFish($id, "Cooked Fish"));
+		self::register("cooked_mutton", fn(IID $id) => new CookedMutton($id, "Cooked Mutton"));
+		self::register("cooked_porkchop", fn(IID $id) => new CookedPorkchop($id, "Cooked Porkchop"));
+		self::register("cooked_rabbit", fn(IID $id) => new CookedRabbit($id, "Cooked Rabbit"));
+		self::register("cooked_salmon", fn(IID $id) => new CookedSalmon($id, "Cooked Salmon"));
+		self::register("cookie", fn(IID $id) => new Cookie($id, "Cookie"));
+		self::register("copper_ingot", fn(IID $id) => new Item($id, "Copper Ingot"));
+		self::register("coral_fan", fn(IID $id) => new CoralFan($id));
+		self::register("crimson_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::CRIMSON_SIGN(), Blocks::CRIMSON_WALL_SIGN()));
+		self::register("dark_oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::DARK_OAK_SIGN(), Blocks::DARK_OAK_WALL_SIGN()));
+		self::register("diamond", fn(IID $id) => new Item($id, "Diamond"));
+		self::register("disc_fragment_5", fn(IID $id) => new Item($id, "Disc Fragment (5)"));
+		self::register("dragon_breath", fn(IID $id) => new Item($id, "Dragon's Breath"));
+		self::register("dried_kelp", fn(IID $id) => new DriedKelp($id, "Dried Kelp"));
 		//TODO: add interface to dye-colour objects
-		self::register("dye", new Dye(new IID(Ids::DYE), "Dye"));
-		self::register("echo_shard", new Item(new IID(Ids::ECHO_SHARD), "Echo Shard"));
-		self::register("egg", new Egg(new IID(Ids::EGG), "Egg"));
-		self::register("emerald", new Item(new IID(Ids::EMERALD), "Emerald"));
-		self::register("enchanted_book", new EnchantedBook(new IID(Ids::ENCHANTED_BOOK), "Enchanted Book", [EnchantmentTags::ALL]));
-		self::register("enchanted_golden_apple", new GoldenAppleEnchanted(new IID(Ids::ENCHANTED_GOLDEN_APPLE), "Enchanted Golden Apple"));
-		self::register("ender_pearl", new EnderPearl(new IID(Ids::ENDER_PEARL), "Ender Pearl"));
-		self::register("experience_bottle", new ExperienceBottle(new IID(Ids::EXPERIENCE_BOTTLE), "Bottle o' Enchanting"));
-		self::register("feather", new Item(new IID(Ids::FEATHER), "Feather"));
-		self::register("fermented_spider_eye", new Item(new IID(Ids::FERMENTED_SPIDER_EYE), "Fermented Spider Eye"));
-		self::register("fire_charge", new FireCharge(new IID(Ids::FIRE_CHARGE), "Fire Charge"));
-		self::register("fishing_rod", new FishingRod(new IID(Ids::FISHING_ROD), "Fishing Rod", [EnchantmentTags::FISHING_ROD]));
-		self::register("flint", new Item(new IID(Ids::FLINT), "Flint"));
-		self::register("flint_and_steel", new FlintSteel(new IID(Ids::FLINT_AND_STEEL), "Flint and Steel", [EnchantmentTags::FLINT_AND_STEEL]));
-		self::register("ghast_tear", new Item(new IID(Ids::GHAST_TEAR), "Ghast Tear"));
-		self::register("glass_bottle", new GlassBottle(new IID(Ids::GLASS_BOTTLE), "Glass Bottle"));
-		self::register("glistering_melon", new Item(new IID(Ids::GLISTERING_MELON), "Glistering Melon"));
-		self::register("glow_berries", new GlowBerries(new IID(Ids::GLOW_BERRIES), "Glow Berries"));
-		self::register("glow_ink_sac", new Item(new IID(Ids::GLOW_INK_SAC), "Glow Ink Sac"));
-		self::register("glowstone_dust", new Item(new IID(Ids::GLOWSTONE_DUST), "Glowstone Dust"));
-		self::register("gold_ingot", new Item(new IID(Ids::GOLD_INGOT), "Gold Ingot"));
-		self::register("gold_nugget", new Item(new IID(Ids::GOLD_NUGGET), "Gold Nugget"));
-		self::register("golden_apple", new GoldenApple(new IID(Ids::GOLDEN_APPLE), "Golden Apple"));
-		self::register("golden_carrot", new GoldenCarrot(new IID(Ids::GOLDEN_CARROT), "Golden Carrot"));
-		self::register("gunpowder", new Item(new IID(Ids::GUNPOWDER), "Gunpowder"));
-		self::register("heart_of_the_sea", new Item(new IID(Ids::HEART_OF_THE_SEA), "Heart of the Sea"));
-		self::register("honey_bottle", new HoneyBottle(new IID(Ids::HONEY_BOTTLE), "Honey Bottle"));
-		self::register("honeycomb", new Item(new IID(Ids::HONEYCOMB), "Honeycomb"));
-		self::register("ink_sac", new Item(new IID(Ids::INK_SAC), "Ink Sac"));
-		self::register("iron_ingot", new Item(new IID(Ids::IRON_INGOT), "Iron Ingot"));
-		self::register("iron_nugget", new Item(new IID(Ids::IRON_NUGGET), "Iron Nugget"));
-		self::register("jungle_sign", new ItemBlockWallOrFloor(new IID(Ids::JUNGLE_SIGN), Blocks::JUNGLE_SIGN(), Blocks::JUNGLE_WALL_SIGN()));
-		self::register("lapis_lazuli", new Item(new IID(Ids::LAPIS_LAZULI), "Lapis Lazuli"));
-		self::register("lava_bucket", new LiquidBucket(new IID(Ids::LAVA_BUCKET), "Lava Bucket", Blocks::LAVA()));
-		self::register("leather", new Item(new IID(Ids::LEATHER), "Leather"));
-		self::register("magma_cream", new Item(new IID(Ids::MAGMA_CREAM), "Magma Cream"));
-		self::register("mangrove_sign", new ItemBlockWallOrFloor(new IID(Ids::MANGROVE_SIGN), Blocks::MANGROVE_SIGN(), Blocks::MANGROVE_WALL_SIGN()));
-		self::register("medicine", new Medicine(new IID(Ids::MEDICINE), "Medicine"));
-		self::register("melon", new Melon(new IID(Ids::MELON), "Melon"));
-		self::register("melon_seeds", new MelonSeeds(new IID(Ids::MELON_SEEDS), "Melon Seeds"));
-		self::register("milk_bucket", new MilkBucket(new IID(Ids::MILK_BUCKET), "Milk Bucket"));
-		self::register("minecart", new Minecart(new IID(Ids::MINECART), "Minecart"));
-		self::register("mushroom_stew", new MushroomStew(new IID(Ids::MUSHROOM_STEW), "Mushroom Stew"));
-		self::register("name_tag", new NameTag(new IID(Ids::NAME_TAG), "Name Tag"));
-		self::register("nautilus_shell", new Item(new IID(Ids::NAUTILUS_SHELL), "Nautilus Shell"));
-		self::register("nether_brick", new Item(new IID(Ids::NETHER_BRICK), "Nether Brick"));
-		self::register("nether_quartz", new Item(new IID(Ids::NETHER_QUARTZ), "Nether Quartz"));
-		self::register("nether_star", new Item(new IID(Ids::NETHER_STAR), "Nether Star"));
-		self::register("netherite_ingot", new class(new IID(Ids::NETHERITE_INGOT), "Netherite Ingot") extends Item{
+		self::register("dye", fn(IID $id) => new Dye($id, "Dye"));
+		self::register("echo_shard", fn(IID $id) => new Item($id, "Echo Shard"));
+		self::register("egg", fn(IID $id) => new Egg($id, "Egg"));
+		self::register("emerald", fn(IID $id) => new Item($id, "Emerald"));
+		self::register("enchanted_book", fn(IID $id) => new EnchantedBook($id, "Enchanted Book", [EnchantmentTags::ALL]));
+		self::register("enchanted_golden_apple", fn(IID $id) => new GoldenAppleEnchanted($id, "Enchanted Golden Apple"));
+		self::register("end_crystal", fn(IID $id) => new EndCrystal($id, "End Crystal"));
+		self::register("ender_pearl", fn(IID $id) => new EnderPearl($id, "Ender Pearl"));
+		self::register("experience_bottle", fn(IID $id) => new ExperienceBottle($id, "Bottle o' Enchanting"));
+		self::register("feather", fn(IID $id) => new Item($id, "Feather"));
+		self::register("fermented_spider_eye", fn(IID $id) => new Item($id, "Fermented Spider Eye"));
+		self::register("fire_charge", fn(IID $id) => new FireCharge($id, "Fire Charge"));
+		self::register("fishing_rod", fn(IID $id) => new FishingRod($id, "Fishing Rod", [EnchantmentTags::FISHING_ROD]));
+		self::register("flint", fn(IID $id) => new Item($id, "Flint"));
+		self::register("flint_and_steel", fn(IID $id) => new FlintSteel($id, "Flint and Steel", [EnchantmentTags::FLINT_AND_STEEL]));
+		self::register("ghast_tear", fn(IID $id) => new Item($id, "Ghast Tear"));
+		self::register("glass_bottle", fn(IID $id) => new GlassBottle($id, "Glass Bottle"));
+		self::register("glistering_melon", fn(IID $id) => new Item($id, "Glistering Melon"));
+		self::register("glow_berries", fn(IID $id) => new GlowBerries($id, "Glow Berries"));
+		self::register("glow_ink_sac", fn(IID $id) => new Item($id, "Glow Ink Sac"));
+		self::register("glowstone_dust", fn(IID $id) => new Item($id, "Glowstone Dust"));
+		self::register("goat_horn", fn(IID $id) => new GoatHorn($id, "Goat Horn"));
+		self::register("gold_ingot", fn(IID $id) => new Item($id, "Gold Ingot"));
+		self::register("gold_nugget", fn(IID $id) => new Item($id, "Gold Nugget"));
+		self::register("golden_apple", fn(IID $id) => new GoldenApple($id, "Golden Apple"));
+		self::register("golden_carrot", fn(IID $id) => new GoldenCarrot($id, "Golden Carrot"));
+		self::register("gunpowder", fn(IID $id) => new Item($id, "Gunpowder"));
+		self::register("heart_of_the_sea", fn(IID $id) => new Item($id, "Heart of the Sea"));
+		self::register("honey_bottle", fn(IID $id) => new HoneyBottle($id, "Honey Bottle"));
+		self::register("honeycomb", fn(IID $id) => new Item($id, "Honeycomb"));
+		self::register("ice_bomb", fn(IID $id) => new IceBomb($id, "Ice Bomb"));
+		self::register("ink_sac", fn(IID $id) => new Item($id, "Ink Sac"));
+		self::register("iron_ingot", fn(IID $id) => new Item($id, "Iron Ingot"));
+		self::register("iron_nugget", fn(IID $id) => new Item($id, "Iron Nugget"));
+		self::register("jungle_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::JUNGLE_SIGN(), Blocks::JUNGLE_WALL_SIGN()));
+		self::register("lapis_lazuli", fn(IID $id) => new Item($id, "Lapis Lazuli"));
+		self::register("lava_bucket", fn(IID $id) => new LiquidBucket($id, "Lava Bucket", Blocks::LAVA()));
+		self::register("leather", fn(IID $id) => new Item($id, "Leather"));
+		self::register("magma_cream", fn(IID $id) => new Item($id, "Magma Cream"));
+		self::register("mangrove_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::MANGROVE_SIGN(), Blocks::MANGROVE_WALL_SIGN()));
+		self::register("medicine", fn(IID $id) => new Medicine($id, "Medicine"));
+		self::register("melon", fn(IID $id) => new Melon($id, "Melon"));
+		self::register("melon_seeds", fn(IID $id) => new MelonSeeds($id, "Melon Seeds"));
+		self::register("milk_bucket", fn(IID $id) => new MilkBucket($id, "Milk Bucket"));
+		self::register("minecart", fn(IID $id) => new Minecart($id, "Minecart"));
+		self::register("mushroom_stew", fn(IID $id) => new MushroomStew($id, "Mushroom Stew"));
+		self::register("name_tag", fn(IID $id) => new NameTag($id, "Name Tag"));
+		self::register("nautilus_shell", fn(IID $id) => new Item($id, "Nautilus Shell"));
+		self::register("nether_brick", fn(IID $id) => new Item($id, "Nether Brick"));
+		self::register("nether_quartz", fn(IID $id) => new Item($id, "Nether Quartz"));
+		self::register("nether_star", fn(IID $id) => new Item($id, "Nether Star"));
+		self::register("netherite_ingot", fn(IID $id) => new class($id, "Netherite Ingot") extends Item{
 			public function isFireProof() : bool{ return true; }
 		});
-		self::register("netherite_scrap", new class(new IID(Ids::NETHERITE_SCRAP), "Netherite Scrap") extends Item{
+		self::register("netherite_scrap", fn(IID $id) => new class($id, "Netherite Scrap") extends Item{
 			public function isFireProof() : bool{ return true; }
 		});
-		self::register("oak_sign", new ItemBlockWallOrFloor(new IID(Ids::OAK_SIGN), Blocks::OAK_SIGN(), Blocks::OAK_WALL_SIGN()));
-		self::register("painting", new PaintingItem(new IID(Ids::PAINTING), "Painting"));
-		self::register("paper", new Item(new IID(Ids::PAPER), "Paper"));
-		self::register("phantom_membrane", new Item(new IID(Ids::PHANTOM_MEMBRANE), "Phantom Membrane"));
-		self::register("pitcher_pod", new PitcherPod(new IID(Ids::PITCHER_POD), "Pitcher Pod"));
-		self::register("poisonous_potato", new PoisonousPotato(new IID(Ids::POISONOUS_POTATO), "Poisonous Potato"));
-		self::register("popped_chorus_fruit", new Item(new IID(Ids::POPPED_CHORUS_FRUIT), "Popped Chorus Fruit"));
-		self::register("potato", new Potato(new IID(Ids::POTATO), "Potato"));
-		self::register("potion", new Potion(new IID(Ids::POTION), "Potion"));
-		self::register("prismarine_crystals", new Item(new IID(Ids::PRISMARINE_CRYSTALS), "Prismarine Crystals"));
-		self::register("prismarine_shard", new Item(new IID(Ids::PRISMARINE_SHARD), "Prismarine Shard"));
-		self::register("pufferfish", new Pufferfish(new IID(Ids::PUFFERFISH), "Pufferfish"));
-		self::register("pumpkin_pie", new PumpkinPie(new IID(Ids::PUMPKIN_PIE), "Pumpkin Pie"));
-		self::register("pumpkin_seeds", new PumpkinSeeds(new IID(Ids::PUMPKIN_SEEDS), "Pumpkin Seeds"));
-		self::register("rabbit_foot", new Item(new IID(Ids::RABBIT_FOOT), "Rabbit's Foot"));
-		self::register("rabbit_hide", new Item(new IID(Ids::RABBIT_HIDE), "Rabbit Hide"));
-		self::register("rabbit_stew", new RabbitStew(new IID(Ids::RABBIT_STEW), "Rabbit Stew"));
-		self::register("raw_beef", new RawBeef(new IID(Ids::RAW_BEEF), "Raw Beef"));
-		self::register("raw_chicken", new RawChicken(new IID(Ids::RAW_CHICKEN), "Raw Chicken"));
-		self::register("raw_copper", new Item(new IID(Ids::RAW_COPPER), "Raw Copper"));
-		self::register("raw_fish", new RawFish(new IID(Ids::RAW_FISH), "Raw Fish"));
-		self::register("raw_gold", new Item(new IID(Ids::RAW_GOLD), "Raw Gold"));
-		self::register("raw_iron", new Item(new IID(Ids::RAW_IRON), "Raw Iron"));
-		self::register("raw_mutton", new RawMutton(new IID(Ids::RAW_MUTTON), "Raw Mutton"));
-		self::register("raw_porkchop", new RawPorkchop(new IID(Ids::RAW_PORKCHOP), "Raw Porkchop"));
-		self::register("raw_rabbit", new RawRabbit(new IID(Ids::RAW_RABBIT), "Raw Rabbit"));
-		self::register("raw_salmon", new RawSalmon(new IID(Ids::RAW_SALMON), "Raw Salmon"));
-		self::register("record_11", new Record(new IID(Ids::RECORD_11), RecordType::DISK_11, "Record 11"));
-		self::register("record_13", new Record(new IID(Ids::RECORD_13), RecordType::DISK_13, "Record 13"));
-		self::register("record_5", new Record(new IID(Ids::RECORD_5), RecordType::DISK_5, "Record 5"));
-		self::register("record_blocks", new Record(new IID(Ids::RECORD_BLOCKS), RecordType::DISK_BLOCKS, "Record Blocks"));
-		self::register("record_cat", new Record(new IID(Ids::RECORD_CAT), RecordType::DISK_CAT, "Record Cat"));
-		self::register("record_chirp", new Record(new IID(Ids::RECORD_CHIRP), RecordType::DISK_CHIRP, "Record Chirp"));
-		self::register("record_far", new Record(new IID(Ids::RECORD_FAR), RecordType::DISK_FAR, "Record Far"));
-		self::register("record_mall", new Record(new IID(Ids::RECORD_MALL), RecordType::DISK_MALL, "Record Mall"));
-		self::register("record_mellohi", new Record(new IID(Ids::RECORD_MELLOHI), RecordType::DISK_MELLOHI, "Record Mellohi"));
-		self::register("record_otherside", new Record(new IID(Ids::RECORD_OTHERSIDE), RecordType::DISK_OTHERSIDE, "Record Otherside"));
-		self::register("record_pigstep", new Record(new IID(Ids::RECORD_PIGSTEP), RecordType::DISK_PIGSTEP, "Record Pigstep"));
-		self::register("record_stal", new Record(new IID(Ids::RECORD_STAL), RecordType::DISK_STAL, "Record Stal"));
-		self::register("record_strad", new Record(new IID(Ids::RECORD_STRAD), RecordType::DISK_STRAD, "Record Strad"));
-		self::register("record_wait", new Record(new IID(Ids::RECORD_WAIT), RecordType::DISK_WAIT, "Record Wait"));
-		self::register("record_ward", new Record(new IID(Ids::RECORD_WARD), RecordType::DISK_WARD, "Record Ward"));
-		self::register("redstone_dust", new Redstone(new IID(Ids::REDSTONE_DUST), "Redstone"));
-		self::register("rotten_flesh", new RottenFlesh(new IID(Ids::ROTTEN_FLESH), "Rotten Flesh"));
-		self::register("scute", new Item(new IID(Ids::SCUTE), "Scute"));
-		self::register("shears", new Shears(new IID(Ids::SHEARS), "Shears", [EnchantmentTags::SHEARS]));
-		self::register("shulker_shell", new Item(new IID(Ids::SHULKER_SHELL), "Shulker Shell"));
-		self::register("slimeball", new Item(new IID(Ids::SLIMEBALL), "Slimeball"));
-		self::register("snowball", new Snowball(new IID(Ids::SNOWBALL), "Snowball"));
-		self::register("spider_eye", new SpiderEye(new IID(Ids::SPIDER_EYE), "Spider Eye"));
-		self::register("splash_potion", new SplashPotion(new IID(Ids::SPLASH_POTION), "Splash Potion"));
-		self::register("spruce_sign", new ItemBlockWallOrFloor(new IID(Ids::SPRUCE_SIGN), Blocks::SPRUCE_SIGN(), Blocks::SPRUCE_WALL_SIGN()));
-		self::register("spyglass", new Spyglass(new IID(Ids::SPYGLASS), "Spyglass"));
-		self::register("steak", new Steak(new IID(Ids::STEAK), "Steak"));
-		self::register("stick", new Stick(new IID(Ids::STICK), "Stick"));
-		self::register("string", new StringItem(new IID(Ids::STRING), "String"));
-		self::register("sugar", new Item(new IID(Ids::SUGAR), "Sugar"));
-		self::register("suspicious_stew", new SuspiciousStew(new IID(Ids::SUSPICIOUS_STEW), "Suspicious Stew"));
-		self::register("sweet_berries", new SweetBerries(new IID(Ids::SWEET_BERRIES), "Sweet Berries"));
-		self::register("torchflower_seeds", new TorchflowerSeeds(new IID(Ids::TORCHFLOWER_SEEDS), "Torchflower Seeds"));
-		self::register("totem", new Totem(new IID(Ids::TOTEM), "Totem of Undying"));
-		self::register("warped_sign", new ItemBlockWallOrFloor(new IID(Ids::WARPED_SIGN), Blocks::WARPED_SIGN(), Blocks::WARPED_WALL_SIGN()));
-		self::register("water_bucket", new LiquidBucket(new IID(Ids::WATER_BUCKET), "Water Bucket", Blocks::WATER()));
-		self::register("wheat", new Item(new IID(Ids::WHEAT), "Wheat"));
-		self::register("wheat_seeds", new WheatSeeds(new IID(Ids::WHEAT_SEEDS), "Wheat Seeds"));
-		self::register("writable_book", new WritableBook(new IID(Ids::WRITABLE_BOOK), "Book & Quill"));
-		self::register("written_book", new WrittenBook(new IID(Ids::WRITTEN_BOOK), "Written Book"));
+		self::register("oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::OAK_SIGN(), Blocks::OAK_WALL_SIGN()));
+		self::register("painting", fn(IID $id) => new PaintingItem($id, "Painting"));
+		self::register("paper", fn(IID $id) => new Item($id, "Paper"));
+		self::register("phantom_membrane", fn(IID $id) => new Item($id, "Phantom Membrane"));
+		self::register("pitcher_pod", fn(IID $id) => new PitcherPod($id, "Pitcher Pod"));
+		self::register("poisonous_potato", fn(IID $id) => new PoisonousPotato($id, "Poisonous Potato"));
+		self::register("popped_chorus_fruit", fn(IID $id) => new Item($id, "Popped Chorus Fruit"));
+		self::register("potato", fn(IID $id) => new Potato($id, "Potato"));
+		self::register("potion", fn(IID $id) => new Potion($id, "Potion"));
+		self::register("prismarine_crystals", fn(IID $id) => new Item($id, "Prismarine Crystals"));
+		self::register("prismarine_shard", fn(IID $id) => new Item($id, "Prismarine Shard"));
+		self::register("pufferfish", fn(IID $id) => new Pufferfish($id, "Pufferfish"));
+		self::register("pumpkin_pie", fn(IID $id) => new PumpkinPie($id, "Pumpkin Pie"));
+		self::register("pumpkin_seeds", fn(IID $id) => new PumpkinSeeds($id, "Pumpkin Seeds"));
+		self::register("rabbit_foot", fn(IID $id) => new Item($id, "Rabbit's Foot"));
+		self::register("rabbit_hide", fn(IID $id) => new Item($id, "Rabbit Hide"));
+		self::register("rabbit_stew", fn(IID $id) => new RabbitStew($id, "Rabbit Stew"));
+		self::register("raw_beef", fn(IID $id) => new RawBeef($id, "Raw Beef"));
+		self::register("raw_chicken", fn(IID $id) => new RawChicken($id, "Raw Chicken"));
+		self::register("raw_copper", fn(IID $id) => new Item($id, "Raw Copper"));
+		self::register("raw_fish", fn(IID $id) => new RawFish($id, "Raw Fish"));
+		self::register("raw_gold", fn(IID $id) => new Item($id, "Raw Gold"));
+		self::register("raw_iron", fn(IID $id) => new Item($id, "Raw Iron"));
+		self::register("raw_mutton", fn(IID $id) => new RawMutton($id, "Raw Mutton"));
+		self::register("raw_porkchop", fn(IID $id) => new RawPorkchop($id, "Raw Porkchop"));
+		self::register("raw_rabbit", fn(IID $id) => new RawRabbit($id, "Raw Rabbit"));
+		self::register("raw_salmon", fn(IID $id) => new RawSalmon($id, "Raw Salmon"));
+		self::register("record_11", fn(IID $id) => new Record($id, RecordType::DISK_11, "Record 11"));
+		self::register("record_13", fn(IID $id) => new Record($id, RecordType::DISK_13, "Record 13"));
+		self::register("record_5", fn(IID $id) => new Record($id, RecordType::DISK_5, "Record 5"));
+		self::register("record_blocks", fn(IID $id) => new Record($id, RecordType::DISK_BLOCKS, "Record Blocks"));
+		self::register("record_cat", fn(IID $id) => new Record($id, RecordType::DISK_CAT, "Record Cat"));
+		self::register("record_chirp", fn(IID $id) => new Record($id, RecordType::DISK_CHIRP, "Record Chirp"));
+		self::register("record_far", fn(IID $id) => new Record($id, RecordType::DISK_FAR, "Record Far"));
+		self::register("record_mall", fn(IID $id) => new Record($id, RecordType::DISK_MALL, "Record Mall"));
+		self::register("record_mellohi", fn(IID $id) => new Record($id, RecordType::DISK_MELLOHI, "Record Mellohi"));
+		self::register("record_otherside", fn(IID $id) => new Record($id, RecordType::DISK_OTHERSIDE, "Record Otherside"));
+		self::register("record_pigstep", fn(IID $id) => new Record($id, RecordType::DISK_PIGSTEP, "Record Pigstep"));
+		self::register("record_stal", fn(IID $id) => new Record($id, RecordType::DISK_STAL, "Record Stal"));
+		self::register("record_strad", fn(IID $id) => new Record($id, RecordType::DISK_STRAD, "Record Strad"));
+		self::register("record_wait", fn(IID $id) => new Record($id, RecordType::DISK_WAIT, "Record Wait"));
+		self::register("record_ward", fn(IID $id) => new Record($id, RecordType::DISK_WARD, "Record Ward"));
+		self::register("recovery_compass", fn(IID $id) => new Item($id, "Recovery Compass"));
+		self::register("redstone_dust", fn(IID $id) => new Redstone($id, "Redstone"));
+		self::register("rotten_flesh", fn(IID $id) => new RottenFlesh($id, "Rotten Flesh"));
+		self::register("scute", fn(IID $id) => new Item($id, "Scute"));
+		self::register("shears", fn(IID $id) => new Shears($id, "Shears", [EnchantmentTags::SHEARS]));
+		self::register("shulker_shell", fn(IID $id) => new Item($id, "Shulker Shell"));
+		self::register("slimeball", fn(IID $id) => new Item($id, "Slimeball"));
+		self::register("snowball", fn(IID $id) => new Snowball($id, "Snowball"));
+		self::register("spider_eye", fn(IID $id) => new SpiderEye($id, "Spider Eye"));
+		self::register("splash_potion", fn(IID $id) => new SplashPotion($id, "Splash Potion"));
+		self::register("spruce_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::SPRUCE_SIGN(), Blocks::SPRUCE_WALL_SIGN()));
+		self::register("spyglass", fn(IID $id) => new Spyglass($id, "Spyglass"));
+		self::register("steak", fn(IID $id) => new Steak($id, "Steak"));
+		self::register("stick", fn(IID $id) => new Stick($id, "Stick"));
+		self::register("string", fn(IID $id) => new StringItem($id, "String"));
+		self::register("sugar", fn(IID $id) => new Item($id, "Sugar"));
+		self::register("suspicious_stew", fn(IID $id) => new SuspiciousStew($id, "Suspicious Stew"));
+		self::register("sweet_berries", fn(IID $id) => new SweetBerries($id, "Sweet Berries"));
+		self::register("torchflower_seeds", fn(IID $id) => new TorchflowerSeeds($id, "Torchflower Seeds"));
+		self::register("totem", fn(IID $id) => new Totem($id, "Totem of Undying"));
+		self::register("warped_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::WARPED_SIGN(), Blocks::WARPED_WALL_SIGN()));
+		self::register("water_bucket", fn(IID $id) => new LiquidBucket($id, "Water Bucket", Blocks::WATER()));
+		self::register("wheat", fn(IID $id) => new Item($id, "Wheat"));
+		self::register("wheat_seeds", fn(IID $id) => new WheatSeeds($id, "Wheat Seeds"));
+		self::register("writable_book", fn(IID $id) => new WritableBook($id, "Book & Quill"));
+		self::register("written_book", fn(IID $id) => new WrittenBook($id, "Written Book"));
 
 		foreach(BoatType::cases() as $type){
 			//boat type is static, because different types of wood may have different properties
-			self::register(strtolower($type->name) . "_boat", new Boat(new IID(match($type){
-				BoatType::OAK => Ids::OAK_BOAT,
-				BoatType::SPRUCE => Ids::SPRUCE_BOAT,
-				BoatType::BIRCH => Ids::BIRCH_BOAT,
-				BoatType::JUNGLE => Ids::JUNGLE_BOAT,
-				BoatType::ACACIA => Ids::ACACIA_BOAT,
-				BoatType::DARK_OAK => Ids::DARK_OAK_BOAT,
-				BoatType::MANGROVE => Ids::MANGROVE_BOAT,
-			}), $type->getDisplayName() . " Boat", $type));
+			self::register(strtolower($type->name) . "_boat", fn(IID $id) => new Boat($id, $type->getDisplayName() . " Boat", $type));
 		}
 	}
 
 	private static function registerSpawnEggs() : void{
-		self::register("zombie_spawn_egg", new class(new IID(Ids::ZOMBIE_SPAWN_EGG), "Zombie Spawn Egg") extends SpawnEgg{
+		self::register("zombie_spawn_egg", fn(IID $id) => new class($id, "Zombie Spawn Egg") extends SpawnEgg{
 			protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{
 				return new Zombie(Location::fromObject($pos, $world, $yaw, $pitch));
 			}
 		});
-		self::register("squid_spawn_egg", new class(new IID(Ids::SQUID_SPAWN_EGG), "Squid Spawn Egg") extends SpawnEgg{
+		self::register("squid_spawn_egg", fn(IID $id) => new class($id, "Squid Spawn Egg") extends SpawnEgg{
 			protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{
 				return new Squid(Location::fromObject($pos, $world, $yaw, $pitch));
 			}
 		});
-		self::register("villager_spawn_egg", new class(new IID(Ids::VILLAGER_SPAWN_EGG), "Villager Spawn Egg") extends SpawnEgg{
+		self::register("villager_spawn_egg", fn(IID $id) => new class($id, "Villager Spawn Egg") extends SpawnEgg{
 			protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{
 				return new Villager(Location::fromObject($pos, $world, $yaw, $pitch));
 			}
@@ -603,87 +627,87 @@ protected function createEntity(World $world, Vector3 $pos, float $yaw, float $p
 	}
 
 	private static function registerTierToolItems() : void{
-		self::register("diamond_axe", new Axe(new IID(Ids::DIAMOND_AXE), "Diamond Axe", ToolTier::DIAMOND, [EnchantmentTags::AXE]));
-		self::register("golden_axe", new Axe(new IID(Ids::GOLDEN_AXE), "Golden Axe", ToolTier::GOLD, [EnchantmentTags::AXE]));
-		self::register("iron_axe", new Axe(new IID(Ids::IRON_AXE), "Iron Axe", ToolTier::IRON, [EnchantmentTags::AXE]));
-		self::register("netherite_axe", new Axe(new IID(Ids::NETHERITE_AXE), "Netherite Axe", ToolTier::NETHERITE, [EnchantmentTags::AXE]));
-		self::register("stone_axe", new Axe(new IID(Ids::STONE_AXE), "Stone Axe", ToolTier::STONE, [EnchantmentTags::AXE]));
-		self::register("wooden_axe", new Axe(new IID(Ids::WOODEN_AXE), "Wooden Axe", ToolTier::WOOD, [EnchantmentTags::AXE]));
-		self::register("diamond_hoe", new Hoe(new IID(Ids::DIAMOND_HOE), "Diamond Hoe", ToolTier::DIAMOND, [EnchantmentTags::HOE]));
-		self::register("golden_hoe", new Hoe(new IID(Ids::GOLDEN_HOE), "Golden Hoe", ToolTier::GOLD, [EnchantmentTags::HOE]));
-		self::register("iron_hoe", new Hoe(new IID(Ids::IRON_HOE), "Iron Hoe", ToolTier::IRON, [EnchantmentTags::HOE]));
-		self::register("netherite_hoe", new Hoe(new IID(Ids::NETHERITE_HOE), "Netherite Hoe", ToolTier::NETHERITE, [EnchantmentTags::HOE]));
-		self::register("stone_hoe", new Hoe(new IID(Ids::STONE_HOE), "Stone Hoe", ToolTier::STONE, [EnchantmentTags::HOE]));
-		self::register("wooden_hoe", new Hoe(new IID(Ids::WOODEN_HOE), "Wooden Hoe", ToolTier::WOOD, [EnchantmentTags::HOE]));
-		self::register("diamond_pickaxe", new Pickaxe(new IID(Ids::DIAMOND_PICKAXE), "Diamond Pickaxe", ToolTier::DIAMOND, [EnchantmentTags::PICKAXE]));
-		self::register("golden_pickaxe", new Pickaxe(new IID(Ids::GOLDEN_PICKAXE), "Golden Pickaxe", ToolTier::GOLD, [EnchantmentTags::PICKAXE]));
-		self::register("iron_pickaxe", new Pickaxe(new IID(Ids::IRON_PICKAXE), "Iron Pickaxe", ToolTier::IRON, [EnchantmentTags::PICKAXE]));
-		self::register("netherite_pickaxe", new Pickaxe(new IID(Ids::NETHERITE_PICKAXE), "Netherite Pickaxe", ToolTier::NETHERITE, [EnchantmentTags::PICKAXE]));
-		self::register("stone_pickaxe", new Pickaxe(new IID(Ids::STONE_PICKAXE), "Stone Pickaxe", ToolTier::STONE, [EnchantmentTags::PICKAXE]));
-		self::register("wooden_pickaxe", new Pickaxe(new IID(Ids::WOODEN_PICKAXE), "Wooden Pickaxe", ToolTier::WOOD, [EnchantmentTags::PICKAXE]));
-		self::register("diamond_shovel", new Shovel(new IID(Ids::DIAMOND_SHOVEL), "Diamond Shovel", ToolTier::DIAMOND, [EnchantmentTags::SHOVEL]));
-		self::register("golden_shovel", new Shovel(new IID(Ids::GOLDEN_SHOVEL), "Golden Shovel", ToolTier::GOLD, [EnchantmentTags::SHOVEL]));
-		self::register("iron_shovel", new Shovel(new IID(Ids::IRON_SHOVEL), "Iron Shovel", ToolTier::IRON, [EnchantmentTags::SHOVEL]));
-		self::register("netherite_shovel", new Shovel(new IID(Ids::NETHERITE_SHOVEL), "Netherite Shovel", ToolTier::NETHERITE, [EnchantmentTags::SHOVEL]));
-		self::register("stone_shovel", new Shovel(new IID(Ids::STONE_SHOVEL), "Stone Shovel", ToolTier::STONE, [EnchantmentTags::SHOVEL]));
-		self::register("wooden_shovel", new Shovel(new IID(Ids::WOODEN_SHOVEL), "Wooden Shovel", ToolTier::WOOD, [EnchantmentTags::SHOVEL]));
-		self::register("diamond_sword", new Sword(new IID(Ids::DIAMOND_SWORD), "Diamond Sword", ToolTier::DIAMOND, [EnchantmentTags::SWORD]));
-		self::register("golden_sword", new Sword(new IID(Ids::GOLDEN_SWORD), "Golden Sword", ToolTier::GOLD, [EnchantmentTags::SWORD]));
-		self::register("iron_sword", new Sword(new IID(Ids::IRON_SWORD), "Iron Sword", ToolTier::IRON, [EnchantmentTags::SWORD]));
-		self::register("netherite_sword", new Sword(new IID(Ids::NETHERITE_SWORD), "Netherite Sword", ToolTier::NETHERITE, [EnchantmentTags::SWORD]));
-		self::register("stone_sword", new Sword(new IID(Ids::STONE_SWORD), "Stone Sword", ToolTier::STONE, [EnchantmentTags::SWORD]));
-		self::register("wooden_sword", new Sword(new IID(Ids::WOODEN_SWORD), "Wooden Sword", ToolTier::WOOD, [EnchantmentTags::SWORD]));
+		self::register("diamond_axe", fn(IID $id) => new Axe($id, "Diamond Axe", ToolTier::DIAMOND, [EnchantmentTags::AXE]));
+		self::register("golden_axe", fn(IID $id) => new Axe($id, "Golden Axe", ToolTier::GOLD, [EnchantmentTags::AXE]));
+		self::register("iron_axe", fn(IID $id) => new Axe($id, "Iron Axe", ToolTier::IRON, [EnchantmentTags::AXE]));
+		self::register("netherite_axe", fn(IID $id) => new Axe($id, "Netherite Axe", ToolTier::NETHERITE, [EnchantmentTags::AXE]));
+		self::register("stone_axe", fn(IID $id) => new Axe($id, "Stone Axe", ToolTier::STONE, [EnchantmentTags::AXE]));
+		self::register("wooden_axe", fn(IID $id) => new Axe($id, "Wooden Axe", ToolTier::WOOD, [EnchantmentTags::AXE]));
+		self::register("diamond_hoe", fn(IID $id) => new Hoe($id, "Diamond Hoe", ToolTier::DIAMOND, [EnchantmentTags::HOE]));
+		self::register("golden_hoe", fn(IID $id) => new Hoe($id, "Golden Hoe", ToolTier::GOLD, [EnchantmentTags::HOE]));
+		self::register("iron_hoe", fn(IID $id) => new Hoe($id, "Iron Hoe", ToolTier::IRON, [EnchantmentTags::HOE]));
+		self::register("netherite_hoe", fn(IID $id) => new Hoe($id, "Netherite Hoe", ToolTier::NETHERITE, [EnchantmentTags::HOE]));
+		self::register("stone_hoe", fn(IID $id) => new Hoe($id, "Stone Hoe", ToolTier::STONE, [EnchantmentTags::HOE]));
+		self::register("wooden_hoe", fn(IID $id) => new Hoe($id, "Wooden Hoe", ToolTier::WOOD, [EnchantmentTags::HOE]));
+		self::register("diamond_pickaxe", fn(IID $id) => new Pickaxe($id, "Diamond Pickaxe", ToolTier::DIAMOND, [EnchantmentTags::PICKAXE]));
+		self::register("golden_pickaxe", fn(IID $id) => new Pickaxe($id, "Golden Pickaxe", ToolTier::GOLD, [EnchantmentTags::PICKAXE]));
+		self::register("iron_pickaxe", fn(IID $id) => new Pickaxe($id, "Iron Pickaxe", ToolTier::IRON, [EnchantmentTags::PICKAXE]));
+		self::register("netherite_pickaxe", fn(IID $id) => new Pickaxe($id, "Netherite Pickaxe", ToolTier::NETHERITE, [EnchantmentTags::PICKAXE]));
+		self::register("stone_pickaxe", fn(IID $id) => new Pickaxe($id, "Stone Pickaxe", ToolTier::STONE, [EnchantmentTags::PICKAXE]));
+		self::register("wooden_pickaxe", fn(IID $id) => new Pickaxe($id, "Wooden Pickaxe", ToolTier::WOOD, [EnchantmentTags::PICKAXE]));
+		self::register("diamond_shovel", fn(IID $id) => new Shovel($id, "Diamond Shovel", ToolTier::DIAMOND, [EnchantmentTags::SHOVEL]));
+		self::register("golden_shovel", fn(IID $id) => new Shovel($id, "Golden Shovel", ToolTier::GOLD, [EnchantmentTags::SHOVEL]));
+		self::register("iron_shovel", fn(IID $id) => new Shovel($id, "Iron Shovel", ToolTier::IRON, [EnchantmentTags::SHOVEL]));
+		self::register("netherite_shovel", fn(IID $id) => new Shovel($id, "Netherite Shovel", ToolTier::NETHERITE, [EnchantmentTags::SHOVEL]));
+		self::register("stone_shovel", fn(IID $id) => new Shovel($id, "Stone Shovel", ToolTier::STONE, [EnchantmentTags::SHOVEL]));
+		self::register("wooden_shovel", fn(IID $id) => new Shovel($id, "Wooden Shovel", ToolTier::WOOD, [EnchantmentTags::SHOVEL]));
+		self::register("diamond_sword", fn(IID $id) => new Sword($id, "Diamond Sword", ToolTier::DIAMOND, [EnchantmentTags::SWORD]));
+		self::register("golden_sword", fn(IID $id) => new Sword($id, "Golden Sword", ToolTier::GOLD, [EnchantmentTags::SWORD]));
+		self::register("iron_sword", fn(IID $id) => new Sword($id, "Iron Sword", ToolTier::IRON, [EnchantmentTags::SWORD]));
+		self::register("netherite_sword", fn(IID $id) => new Sword($id, "Netherite Sword", ToolTier::NETHERITE, [EnchantmentTags::SWORD]));
+		self::register("stone_sword", fn(IID $id) => new Sword($id, "Stone Sword", ToolTier::STONE, [EnchantmentTags::SWORD]));
+		self::register("wooden_sword", fn(IID $id) => new Sword($id, "Wooden Sword", ToolTier::WOOD, [EnchantmentTags::SWORD]));
 	}
 
 	private static function registerArmorItems() : void{
-		self::register("chainmail_boots", new Armor(new IID(Ids::CHAINMAIL_BOOTS), "Chainmail Boots", new ArmorTypeInfo(1, 196, ArmorInventory::SLOT_FEET, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::BOOTS]));
-		self::register("diamond_boots", new Armor(new IID(Ids::DIAMOND_BOOTS), "Diamond Boots", new ArmorTypeInfo(3, 430, ArmorInventory::SLOT_FEET, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::BOOTS]));
-		self::register("golden_boots", new Armor(new IID(Ids::GOLDEN_BOOTS), "Golden Boots", new ArmorTypeInfo(1, 92, ArmorInventory::SLOT_FEET, material: ArmorMaterials::GOLD()), [EnchantmentTags::BOOTS]));
-		self::register("iron_boots", new Armor(new IID(Ids::IRON_BOOTS), "Iron Boots", new ArmorTypeInfo(2, 196, ArmorInventory::SLOT_FEET, material: ArmorMaterials::IRON()), [EnchantmentTags::BOOTS]));
-		self::register("leather_boots", new Armor(new IID(Ids::LEATHER_BOOTS), "Leather Boots", new ArmorTypeInfo(1, 66, ArmorInventory::SLOT_FEET, material: ArmorMaterials::LEATHER()), [EnchantmentTags::BOOTS]));
-		self::register("netherite_boots", new Armor(new IID(Ids::NETHERITE_BOOTS), "Netherite Boots", new ArmorTypeInfo(3, 482, ArmorInventory::SLOT_FEET, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::BOOTS]));
+		self::register("chainmail_boots", fn(IID $id) => new Armor($id, "Chainmail Boots", new ArmorTypeInfo(1, 196, ArmorInventory::SLOT_FEET, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::BOOTS]));
+		self::register("diamond_boots", fn(IID $id) => new Armor($id, "Diamond Boots", new ArmorTypeInfo(3, 430, ArmorInventory::SLOT_FEET, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::BOOTS]));
+		self::register("golden_boots", fn(IID $id) => new Armor($id, "Golden Boots", new ArmorTypeInfo(1, 92, ArmorInventory::SLOT_FEET, material: ArmorMaterials::GOLD()), [EnchantmentTags::BOOTS]));
+		self::register("iron_boots", fn(IID $id) => new Armor($id, "Iron Boots", new ArmorTypeInfo(2, 196, ArmorInventory::SLOT_FEET, material: ArmorMaterials::IRON()), [EnchantmentTags::BOOTS]));
+		self::register("leather_boots", fn(IID $id) => new Armor($id, "Leather Boots", new ArmorTypeInfo(1, 66, ArmorInventory::SLOT_FEET, material: ArmorMaterials::LEATHER()), [EnchantmentTags::BOOTS]));
+		self::register("netherite_boots", fn(IID $id) => new Armor($id, "Netherite Boots", new ArmorTypeInfo(3, 482, ArmorInventory::SLOT_FEET, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::BOOTS]));
 
-		self::register("chainmail_chestplate", new Armor(new IID(Ids::CHAINMAIL_CHESTPLATE), "Chainmail Chestplate", new ArmorTypeInfo(5, 241, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::CHESTPLATE]));
-		self::register("diamond_chestplate", new Armor(new IID(Ids::DIAMOND_CHESTPLATE), "Diamond Chestplate", new ArmorTypeInfo(8, 529, ArmorInventory::SLOT_CHEST, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::CHESTPLATE]));
-		self::register("golden_chestplate", new Armor(new IID(Ids::GOLDEN_CHESTPLATE), "Golden Chestplate", new ArmorTypeInfo(5, 113, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::GOLD()), [EnchantmentTags::CHESTPLATE]));
-		self::register("iron_chestplate", new Armor(new IID(Ids::IRON_CHESTPLATE), "Iron Chestplate", new ArmorTypeInfo(6, 241, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::IRON()), [EnchantmentTags::CHESTPLATE]));
-		self::register("leather_tunic", new Armor(new IID(Ids::LEATHER_TUNIC), "Leather Tunic", new ArmorTypeInfo(3, 81, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::LEATHER()), [EnchantmentTags::CHESTPLATE]));
-		self::register("netherite_chestplate", new Armor(new IID(Ids::NETHERITE_CHESTPLATE), "Netherite Chestplate", new ArmorTypeInfo(8, 593, ArmorInventory::SLOT_CHEST, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::CHESTPLATE]));
+		self::register("chainmail_chestplate", fn(IID $id) => new Armor($id, "Chainmail Chestplate", new ArmorTypeInfo(5, 241, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::CHESTPLATE]));
+		self::register("diamond_chestplate", fn(IID $id) => new Armor($id, "Diamond Chestplate", new ArmorTypeInfo(8, 529, ArmorInventory::SLOT_CHEST, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::CHESTPLATE]));
+		self::register("golden_chestplate", fn(IID $id) => new Armor($id, "Golden Chestplate", new ArmorTypeInfo(5, 113, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::GOLD()), [EnchantmentTags::CHESTPLATE]));
+		self::register("iron_chestplate", fn(IID $id) => new Armor($id, "Iron Chestplate", new ArmorTypeInfo(6, 241, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::IRON()), [EnchantmentTags::CHESTPLATE]));
+		self::register("leather_tunic", fn(IID $id) => new Armor($id, "Leather Tunic", new ArmorTypeInfo(3, 81, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::LEATHER()), [EnchantmentTags::CHESTPLATE]));
+		self::register("netherite_chestplate", fn(IID $id) => new Armor($id, "Netherite Chestplate", new ArmorTypeInfo(8, 593, ArmorInventory::SLOT_CHEST, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::CHESTPLATE]));
 
-		self::register("chainmail_helmet", new Armor(new IID(Ids::CHAINMAIL_HELMET), "Chainmail Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::HELMET]));
-		self::register("diamond_helmet", new Armor(new IID(Ids::DIAMOND_HELMET), "Diamond Helmet", new ArmorTypeInfo(3, 364, ArmorInventory::SLOT_HEAD, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::HELMET]));
-		self::register("golden_helmet", new Armor(new IID(Ids::GOLDEN_HELMET), "Golden Helmet", new ArmorTypeInfo(2, 78, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::GOLD()), [EnchantmentTags::HELMET]));
-		self::register("iron_helmet", new Armor(new IID(Ids::IRON_HELMET), "Iron Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::IRON()), [EnchantmentTags::HELMET]));
-		self::register("leather_cap", new Armor(new IID(Ids::LEATHER_CAP), "Leather Cap", new ArmorTypeInfo(1, 56, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::LEATHER()), [EnchantmentTags::HELMET]));
-		self::register("netherite_helmet", new Armor(new IID(Ids::NETHERITE_HELMET), "Netherite Helmet", new ArmorTypeInfo(3, 408, ArmorInventory::SLOT_HEAD, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::HELMET]));
-		self::register("turtle_helmet", new TurtleHelmet(new IID(Ids::TURTLE_HELMET), "Turtle Shell", new ArmorTypeInfo(2, 276, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::TURTLE()), [EnchantmentTags::HELMET]));
+		self::register("chainmail_helmet", fn(IID $id) => new Armor($id, "Chainmail Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::HELMET]));
+		self::register("diamond_helmet", fn(IID $id) => new Armor($id, "Diamond Helmet", new ArmorTypeInfo(3, 364, ArmorInventory::SLOT_HEAD, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::HELMET]));
+		self::register("golden_helmet", fn(IID $id) => new Armor($id, "Golden Helmet", new ArmorTypeInfo(2, 78, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::GOLD()), [EnchantmentTags::HELMET]));
+		self::register("iron_helmet", fn(IID $id) => new Armor($id, "Iron Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::IRON()), [EnchantmentTags::HELMET]));
+		self::register("leather_cap", fn(IID $id) => new Armor($id, "Leather Cap", new ArmorTypeInfo(1, 56, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::LEATHER()), [EnchantmentTags::HELMET]));
+		self::register("netherite_helmet", fn(IID $id) => new Armor($id, "Netherite Helmet", new ArmorTypeInfo(3, 408, ArmorInventory::SLOT_HEAD, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::HELMET]));
+		self::register("turtle_helmet", fn(IID $id) => new TurtleHelmet($id, "Turtle Shell", new ArmorTypeInfo(2, 276, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::TURTLE()), [EnchantmentTags::HELMET]));
 
-		self::register("chainmail_leggings", new Armor(new IID(Ids::CHAINMAIL_LEGGINGS), "Chainmail Leggings", new ArmorTypeInfo(4, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::LEGGINGS]));
-		self::register("diamond_leggings", new Armor(new IID(Ids::DIAMOND_LEGGINGS), "Diamond Leggings", new ArmorTypeInfo(6, 496, ArmorInventory::SLOT_LEGS, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::LEGGINGS]));
-		self::register("golden_leggings", new Armor(new IID(Ids::GOLDEN_LEGGINGS), "Golden Leggings", new ArmorTypeInfo(3, 106, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::GOLD()), [EnchantmentTags::LEGGINGS]));
-		self::register("iron_leggings", new Armor(new IID(Ids::IRON_LEGGINGS), "Iron Leggings", new ArmorTypeInfo(5, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::IRON()), [EnchantmentTags::LEGGINGS]));
-		self::register("leather_pants", new Armor(new IID(Ids::LEATHER_PANTS), "Leather Pants", new ArmorTypeInfo(2, 76, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::LEATHER()), [EnchantmentTags::LEGGINGS]));
-		self::register("netherite_leggings", new Armor(new IID(Ids::NETHERITE_LEGGINGS), "Netherite Leggings", new ArmorTypeInfo(6, 556, ArmorInventory::SLOT_LEGS, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::LEGGINGS]));
+		self::register("chainmail_leggings", fn(IID $id) => new Armor($id, "Chainmail Leggings", new ArmorTypeInfo(4, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::LEGGINGS]));
+		self::register("diamond_leggings", fn(IID $id) => new Armor($id, "Diamond Leggings", new ArmorTypeInfo(6, 496, ArmorInventory::SLOT_LEGS, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::LEGGINGS]));
+		self::register("golden_leggings", fn(IID $id) => new Armor($id, "Golden Leggings", new ArmorTypeInfo(3, 106, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::GOLD()), [EnchantmentTags::LEGGINGS]));
+		self::register("iron_leggings", fn(IID $id) => new Armor($id, "Iron Leggings", new ArmorTypeInfo(5, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::IRON()), [EnchantmentTags::LEGGINGS]));
+		self::register("leather_pants", fn(IID $id) => new Armor($id, "Leather Pants", new ArmorTypeInfo(2, 76, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::LEATHER()), [EnchantmentTags::LEGGINGS]));
+		self::register("netherite_leggings", fn(IID $id) => new Armor($id, "Netherite Leggings", new ArmorTypeInfo(6, 556, ArmorInventory::SLOT_LEGS, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::LEGGINGS]));
 	}
 
 	private static function registerSmithingTemplates() : void{
-		self::register("netherite_upgrade_smithing_template", new Item(new IID(Ids::NETHERITE_UPGRADE_SMITHING_TEMPLATE), "Netherite Upgrade Smithing Template"));
-		self::register("coast_armor_trim_smithing_template", new Item(new IID(Ids::COAST_ARMOR_TRIM_SMITHING_TEMPLATE), "Coast Armor Trim Smithing Template"));
-		self::register("dune_armor_trim_smithing_template", new Item(new IID(Ids::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE), "Dune Armor Trim Smithing Template"));
-		self::register("eye_armor_trim_smithing_template", new Item(new IID(Ids::EYE_ARMOR_TRIM_SMITHING_TEMPLATE), "Eye Armor Trim Smithing Template"));
-		self::register("host_armor_trim_smithing_template", new Item(new IID(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE), "Host Armor Trim Smithing Template"));
-		self::register("raiser_armor_trim_smithing_template", new Item(new IID(Ids::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE), "Raiser Armor Trim Smithing Template"));
-		self::register("rib_armor_trim_smithing_template", new Item(new IID(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE), "Rib Armor Trim Smithing Template"));
-		self::register("sentry_armor_trim_smithing_template", new Item(new IID(Ids::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE), "Sentry Armor Trim Smithing Template"));
-		self::register("shaper_armor_trim_smithing_template", new Item(new IID(Ids::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE), "Shaper Armor Trim Smithing Template"));
-		self::register("silence_armor_trim_smithing_template", new Item(new IID(Ids::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE), "Silence Armor Trim Smithing Template"));
-		self::register("snout_armor_trim_smithing_template", new Item(new IID(Ids::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE), "Snout Armor Trim Smithing Template"));
-		self::register("spire_armor_trim_smithing_template", new Item(new IID(Ids::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE), "Spire Armor Trim Smithing Template"));
-		self::register("tide_armor_trim_smithing_template", new Item(new IID(Ids::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE), "Tide Armor Trim Smithing Template"));
-		self::register("vex_armor_trim_smithing_template", new Item(new IID(Ids::VEX_ARMOR_TRIM_SMITHING_TEMPLATE), "Vex Armor Trim Smithing Template"));
-		self::register("ward_armor_trim_smithing_template", new Item(new IID(Ids::WARD_ARMOR_TRIM_SMITHING_TEMPLATE), "Ward Armor Trim Smithing Template"));
-		self::register("wayfinder_armor_trim_smithing_template", new Item(new IID(Ids::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE), "Wayfinder Armor Trim Smithing Template"));
-		self::register("wild_armor_trim_smithing_template", new Item(new IID(Ids::WILD_ARMOR_TRIM_SMITHING_TEMPLATE), "Wild Armor Trim Smithing Template"));
+		self::register("netherite_upgrade_smithing_template", fn(IID $id) => new Item($id, "Netherite Upgrade Smithing Template"));
+		self::register("coast_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Coast Armor Trim Smithing Template"));
+		self::register("dune_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Dune Armor Trim Smithing Template"));
+		self::register("eye_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Eye Armor Trim Smithing Template"));
+		self::register("host_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Host Armor Trim Smithing Template"));
+		self::register("raiser_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Raiser Armor Trim Smithing Template"));
+		self::register("rib_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Rib Armor Trim Smithing Template"));
+		self::register("sentry_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Sentry Armor Trim Smithing Template"));
+		self::register("shaper_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Shaper Armor Trim Smithing Template"));
+		self::register("silence_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Silence Armor Trim Smithing Template"));
+		self::register("snout_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Snout Armor Trim Smithing Template"));
+		self::register("spire_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Spire Armor Trim Smithing Template"));
+		self::register("tide_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Tide Armor Trim Smithing Template"));
+		self::register("vex_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Vex Armor Trim Smithing Template"));
+		self::register("ward_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Ward Armor Trim Smithing Template"));
+		self::register("wayfinder_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Wayfinder Armor Trim Smithing Template"));
+		self::register("wild_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Wild Armor Trim Smithing Template"));
 	}
 
 }
diff --git a/src/item/enchantment/AvailableEnchantmentRegistry.php b/src/item/enchantment/AvailableEnchantmentRegistry.php
index cae94c666f5..eed7bff5273 100644
--- a/src/item/enchantment/AvailableEnchantmentRegistry.php
+++ b/src/item/enchantment/AvailableEnchantmentRegistry.php
@@ -57,6 +57,7 @@ private function __construct(){
 		$this->register(Enchantments::THORNS(), [Tags::CHESTPLATE], [Tags::HELMET, Tags::LEGGINGS, Tags::BOOTS]);
 		$this->register(Enchantments::RESPIRATION(), [Tags::HELMET], []);
 		$this->register(Enchantments::AQUA_AFFINITY(), [Tags::HELMET], []);
+		$this->register(Enchantments::FROST_WALKER(), [/* no primary items */], [Tags::BOOTS]);
 		$this->register(Enchantments::SHARPNESS(), [Tags::SWORD, Tags::AXE], []);
 		$this->register(Enchantments::KNOCKBACK(), [Tags::SWORD], []);
 		$this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []);
diff --git a/src/item/enchantment/StringToEnchantmentParser.php b/src/item/enchantment/StringToEnchantmentParser.php
index 47a750ff27d..b6763e49125 100644
--- a/src/item/enchantment/StringToEnchantmentParser.php
+++ b/src/item/enchantment/StringToEnchantmentParser.php
@@ -44,6 +44,7 @@ private static function make() : self{
 		$result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION());
 		$result->register("flame", fn() => VanillaEnchantments::FLAME());
 		$result->register("fortune", fn() => VanillaEnchantments::FORTUNE());
+		$result->register("frost_walker", fn() => VanillaEnchantments::FROST_WALKER());
 		$result->register("infinity", fn() => VanillaEnchantments::INFINITY());
 		$result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK());
 		$result->register("mending", fn() => VanillaEnchantments::MENDING());
diff --git a/src/item/enchantment/VanillaEnchantments.php b/src/item/enchantment/VanillaEnchantments.php
index 19ce3971633..1132dc9c662 100644
--- a/src/item/enchantment/VanillaEnchantments.php
+++ b/src/item/enchantment/VanillaEnchantments.php
@@ -41,6 +41,7 @@
  * @method static ProtectionEnchantment FIRE_PROTECTION()
  * @method static Enchantment FLAME()
  * @method static Enchantment FORTUNE()
+ * @method static Enchantment FROST_WALKER()
  * @method static Enchantment INFINITY()
  * @method static KnockbackEnchantment KNOCKBACK()
  * @method static Enchantment MENDING()
@@ -145,6 +146,16 @@ protected static function setup() : void{
 			fn(int $level) : int => 10 * $level,
 			30
 		));
+
+		self::register("FROST_WALKER", new Enchantment(
+			KnownTranslationFactory::enchantment_frostwalker(),
+			Rarity::RARE,
+			0,
+			0,
+			2,
+			fn(int $level) : int => 10 * $level,
+			15
+		));
 		self::register("AQUA_AFFINITY", new Enchantment(
 			KnownTranslationFactory::enchantment_waterWorker(),
 			Rarity::RARE,
diff --git a/src/lang/KnownTranslationFactory.php b/src/lang/KnownTranslationFactory.php
index ea8c2952e5e..f0b70ffd1a0 100644
--- a/src/lang/KnownTranslationFactory.php
+++ b/src/lang/KnownTranslationFactory.php
@@ -603,6 +603,31 @@ public static function commands_whitelist_usage() : Translatable{
 		return new Translatable(KnownTranslationKeys::COMMANDS_WHITELIST_USAGE, []);
 	}
 
+	public static function commands_xp_failure_widthdrawXp() : Translatable{
+		return new Translatable(KnownTranslationKeys::COMMANDS_XP_FAILURE_WIDTHDRAWXP, []);
+	}
+
+	public static function commands_xp_success(Translatable|string $param0, Translatable|string $param1) : Translatable{
+		return new Translatable(KnownTranslationKeys::COMMANDS_XP_SUCCESS, [
+			0 => $param0,
+			1 => $param1,
+		]);
+	}
+
+	public static function commands_xp_success_levels(Translatable|string $param0, Translatable|string $param1) : Translatable{
+		return new Translatable(KnownTranslationKeys::COMMANDS_XP_SUCCESS_LEVELS, [
+			0 => $param0,
+			1 => $param1,
+		]);
+	}
+
+	public static function commands_xp_success_negative_levels(Translatable|string $param0, Translatable|string $param1) : Translatable{
+		return new Translatable(KnownTranslationKeys::COMMANDS_XP_SUCCESS_NEGATIVE_LEVELS, [
+			0 => $param0,
+			1 => $param1,
+		]);
+	}
+
 	public static function death_attack_anvil(Translatable|string $param0) : Translatable{
 		return new Translatable(KnownTranslationKeys::DEATH_ATTACK_ANVIL, [
 			0 => $param0,
@@ -667,6 +692,12 @@ public static function death_attack_fireworks(Translatable|string $param0) : Tra
 		]);
 	}
 
+	public static function death_attack_flyIntoWall(Translatable|string $param0) : Translatable{
+		return new Translatable(KnownTranslationKeys::DEATH_ATTACK_FLYINTOWALL, [
+			0 => $param0,
+		]);
+	}
+
 	public static function death_attack_generic(Translatable|string $param0) : Translatable{
 		return new Translatable(KnownTranslationKeys::DEATH_ATTACK_GENERIC, [
 			0 => $param0,
@@ -1025,6 +1056,14 @@ public static function item_record_chirp_desc() : Translatable{
 		return new Translatable(KnownTranslationKeys::ITEM_RECORD_CHIRP_DESC, []);
 	}
 
+	public static function item_record_creator_desc() : Translatable{
+		return new Translatable(KnownTranslationKeys::ITEM_RECORD_CREATOR_DESC, []);
+	}
+
+	public static function item_record_creator_music_box_desc() : Translatable{
+		return new Translatable(KnownTranslationKeys::ITEM_RECORD_CREATOR_MUSIC_BOX_DESC, []);
+	}
+
 	public static function item_record_far_desc() : Translatable{
 		return new Translatable(KnownTranslationKeys::ITEM_RECORD_FAR_DESC, []);
 	}
@@ -1045,6 +1084,14 @@ public static function item_record_pigstep_desc() : Translatable{
 		return new Translatable(KnownTranslationKeys::ITEM_RECORD_PIGSTEP_DESC, []);
 	}
 
+	public static function item_record_precipice_desc() : Translatable{
+		return new Translatable(KnownTranslationKeys::ITEM_RECORD_PRECIPICE_DESC, []);
+	}
+
+	public static function item_record_relic_desc() : Translatable{
+		return new Translatable(KnownTranslationKeys::ITEM_RECORD_RELIC_DESC, []);
+	}
+
 	public static function item_record_stal_desc() : Translatable{
 		return new Translatable(KnownTranslationKeys::ITEM_RECORD_STAL_DESC, []);
 	}
@@ -1394,6 +1441,10 @@ public static function pocketmine_command_timings_alreadyEnabled() : Translatabl
 		return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED, []);
 	}
 
+	public static function pocketmine_command_timings_collect() : Translatable{
+		return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_COLLECT, []);
+	}
+
 	public static function pocketmine_command_timings_description() : Translatable{
 		return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_DESCRIPTION, []);
 	}
@@ -1536,6 +1587,14 @@ public static function pocketmine_command_whitelist_description() : Translatable
 		return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_WHITELIST_DESCRIPTION, []);
 	}
 
+	public static function pocketmine_command_xp_description() : Translatable{
+		return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_XP_DESCRIPTION, []);
+	}
+
+	public static function pocketmine_command_xp_usage() : Translatable{
+		return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_XP_USAGE, []);
+	}
+
 	public static function pocketmine_crash_archive(Translatable|string $param0, Translatable|string $param1) : Translatable{
 		return new Translatable(KnownTranslationKeys::POCKETMINE_CRASH_ARCHIVE, [
 			0 => $param0,
@@ -2056,6 +2115,14 @@ public static function pocketmine_permission_command_whitelist_remove() : Transl
 		return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_REMOVE, []);
 	}
 
+	public static function pocketmine_permission_command_xp_other() : Translatable{
+		return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_XP_OTHER, []);
+	}
+
+	public static function pocketmine_permission_command_xp_self() : Translatable{
+		return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_XP_SELF, []);
+	}
+
 	public static function pocketmine_permission_group_console() : Translatable{
 		return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_GROUP_CONSOLE, []);
 	}
diff --git a/src/lang/KnownTranslationKeys.php b/src/lang/KnownTranslationKeys.php
index c8345273060..1c1fef20f5b 100644
--- a/src/lang/KnownTranslationKeys.php
+++ b/src/lang/KnownTranslationKeys.php
@@ -137,6 +137,10 @@ final class KnownTranslationKeys{
 	public const COMMANDS_WHITELIST_REMOVE_SUCCESS = "commands.whitelist.remove.success";
 	public const COMMANDS_WHITELIST_REMOVE_USAGE = "commands.whitelist.remove.usage";
 	public const COMMANDS_WHITELIST_USAGE = "commands.whitelist.usage";
+	public const COMMANDS_XP_FAILURE_WIDTHDRAWXP = "commands.xp.failure.widthdrawXp";
+	public const COMMANDS_XP_SUCCESS = "commands.xp.success";
+	public const COMMANDS_XP_SUCCESS_LEVELS = "commands.xp.success.levels";
+	public const COMMANDS_XP_SUCCESS_NEGATIVE_LEVELS = "commands.xp.success.negative.levels";
 	public const DEATH_ATTACK_ANVIL = "death.attack.anvil";
 	public const DEATH_ATTACK_ARROW = "death.attack.arrow";
 	public const DEATH_ATTACK_ARROW_ITEM = "death.attack.arrow.item";
@@ -147,6 +151,7 @@ final class KnownTranslationKeys{
 	public const DEATH_ATTACK_FALL = "death.attack.fall";
 	public const DEATH_ATTACK_FALLINGBLOCK = "death.attack.fallingBlock";
 	public const DEATH_ATTACK_FIREWORKS = "death.attack.fireworks";
+	public const DEATH_ATTACK_FLYINTOWALL = "death.attack.flyIntoWall";
 	public const DEATH_ATTACK_GENERIC = "death.attack.generic";
 	public const DEATH_ATTACK_INFIRE = "death.attack.inFire";
 	public const DEATH_ATTACK_INWALL = "death.attack.inWall";
@@ -227,11 +232,15 @@ final class KnownTranslationKeys{
 	public const ITEM_RECORD_BLOCKS_DESC = "item.record_blocks.desc";
 	public const ITEM_RECORD_CAT_DESC = "item.record_cat.desc";
 	public const ITEM_RECORD_CHIRP_DESC = "item.record_chirp.desc";
+	public const ITEM_RECORD_CREATOR_DESC = "item.record_creator.desc";
+	public const ITEM_RECORD_CREATOR_MUSIC_BOX_DESC = "item.record_creator_music_box.desc";
 	public const ITEM_RECORD_FAR_DESC = "item.record_far.desc";
 	public const ITEM_RECORD_MALL_DESC = "item.record_mall.desc";
 	public const ITEM_RECORD_MELLOHI_DESC = "item.record_mellohi.desc";
 	public const ITEM_RECORD_OTHERSIDE_DESC = "item.record_otherside.desc";
 	public const ITEM_RECORD_PIGSTEP_DESC = "item.record_pigstep.desc";
+	public const ITEM_RECORD_PRECIPICE_DESC = "item.record_precipice.desc";
+	public const ITEM_RECORD_RELIC_DESC = "item.record_relic.desc";
 	public const ITEM_RECORD_STAL_DESC = "item.record_stal.desc";
 	public const ITEM_RECORD_STRAD_DESC = "item.record_strad.desc";
 	public const ITEM_RECORD_WAIT_DESC = "item.record_wait.desc";
@@ -306,6 +315,7 @@ final class KnownTranslationKeys{
 	public const POCKETMINE_COMMAND_TIME_DESCRIPTION = "pocketmine.command.time.description";
 	public const POCKETMINE_COMMAND_TIME_USAGE = "pocketmine.command.time.usage";
 	public const POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED = "pocketmine.command.timings.alreadyEnabled";
+	public const POCKETMINE_COMMAND_TIMINGS_COLLECT = "pocketmine.command.timings.collect";
 	public const POCKETMINE_COMMAND_TIMINGS_DESCRIPTION = "pocketmine.command.timings.description";
 	public const POCKETMINE_COMMAND_TIMINGS_DISABLE = "pocketmine.command.timings.disable";
 	public const POCKETMINE_COMMAND_TIMINGS_ENABLE = "pocketmine.command.timings.enable";
@@ -336,6 +346,8 @@ final class KnownTranslationKeys{
 	public const POCKETMINE_COMMAND_VERSION_SERVERSOFTWAREVERSION = "pocketmine.command.version.serverSoftwareVersion";
 	public const POCKETMINE_COMMAND_VERSION_USAGE = "pocketmine.command.version.usage";
 	public const POCKETMINE_COMMAND_WHITELIST_DESCRIPTION = "pocketmine.command.whitelist.description";
+	public const POCKETMINE_COMMAND_XP_DESCRIPTION = "pocketmine.command.xp.description";
+	public const POCKETMINE_COMMAND_XP_USAGE = "pocketmine.command.xp.usage";
 	public const POCKETMINE_CRASH_ARCHIVE = "pocketmine.crash.archive";
 	public const POCKETMINE_CRASH_CREATE = "pocketmine.crash.create";
 	public const POCKETMINE_CRASH_ERROR = "pocketmine.crash.error";
@@ -449,6 +461,8 @@ final class KnownTranslationKeys{
 	public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_LIST = "pocketmine.permission.command.whitelist.list";
 	public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_RELOAD = "pocketmine.permission.command.whitelist.reload";
 	public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_REMOVE = "pocketmine.permission.command.whitelist.remove";
+	public const POCKETMINE_PERMISSION_COMMAND_XP_OTHER = "pocketmine.permission.command.xp.other";
+	public const POCKETMINE_PERMISSION_COMMAND_XP_SELF = "pocketmine.permission.command.xp.self";
 	public const POCKETMINE_PERMISSION_GROUP_CONSOLE = "pocketmine.permission.group.console";
 	public const POCKETMINE_PERMISSION_GROUP_OPERATOR = "pocketmine.permission.group.operator";
 	public const POCKETMINE_PERMISSION_GROUP_USER = "pocketmine.permission.group.user";
diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php
index 8a42371e694..88b4ba1a053 100644
--- a/src/network/mcpe/handler/InGamePacketHandler.php
+++ b/src/network/mcpe/handler/InGamePacketHandler.php
@@ -497,15 +497,18 @@ private function handleUseItemTransaction(UseItemTransactionData $data) : bool{
 
 				$blockPos = $data->getBlockPosition();
 				$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
-				if(!$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos)){
-					$this->onFailedBlockAction($vBlockPos, $data->getFace());
-				}
+				$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos);
+				//always sync this in case plugins caused a different result than the client expected
+				//we *could* try to enhance detection of plugin-altered behaviour, but this would require propagating
+				//more information up the stack. For now I think this is good enough.
+				//if only the client would tell us what blocks it thinks changed...
+				$this->syncBlocksNearby($vBlockPos, $data->getFace());
 				return true;
 			case UseItemTransactionData::ACTION_BREAK_BLOCK:
 				$blockPos = $data->getBlockPosition();
 				$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
 				if(!$this->player->breakBlock($vBlockPos)){
-					$this->onFailedBlockAction($vBlockPos, null);
+					$this->syncBlocksNearby($vBlockPos, null);
 				}
 				return true;
 			case UseItemTransactionData::ACTION_CLICK_AIR:
@@ -533,9 +536,9 @@ private static function validateFacing(int $facing) : void{
 	}
 
 	/**
-	 * Internal function used to execute rollbacks when an action fails on a block.
+	 * Syncs blocks nearby to ensure that the client and server agree on the world's blocks after a block interaction.
 	 */
-	private function onFailedBlockAction(Vector3 $blockPos, ?int $face) : void{
+	private function syncBlocksNearby(Vector3 $blockPos, ?int $face) : void{
 		if($blockPos->distanceSquared($this->player->getLocation()) < 10000){
 			$blocks = $blockPos->sidesArray();
 			if($face !== null){
@@ -672,7 +675,7 @@ public function handleBlockPickRequest(BlockPickRequestPacket $packet) : bool{
 	}
 
 	public function handleActorPickRequest(ActorPickRequestPacket $packet) : bool{
-		return false; //TODO
+		return $this->player->pickEntity($packet->actorUniqueId);
 	}
 
 	public function handlePlayerAction(PlayerActionPacket $packet) : bool{
@@ -686,7 +689,7 @@ private function handlePlayerActionFromData(int $action, BlockPosition $blockPos
 			case PlayerAction::START_BREAK:
 				self::validateFacing($face);
 				if(!$this->player->attackBlock($pos, $face)){
-					$this->onFailedBlockAction($pos, $face);
+					$this->syncBlocksNearby($pos, $face);
 				}
 
 				break;
@@ -1002,7 +1005,7 @@ public function handleLecternUpdate(LecternUpdatePacket $packet) : bool{
 		$lectern = $world->getBlockAt($pos->getX(), $pos->getY(), $pos->getZ());
 		if($lectern instanceof Lectern && $this->player->canInteract($lectern->getPosition(), 15)){
 			if(!$lectern->onPageTurn($packet->page)){
-				$this->onFailedBlockAction($lectern->getPosition(), null);
+				$this->syncBlocksNearby($lectern->getPosition(), null);
 			}
 			return true;
 		}
diff --git a/src/permission/DefaultPermissionNames.php b/src/permission/DefaultPermissionNames.php
index fab532e2869..a916e813c05 100644
--- a/src/permission/DefaultPermissionNames.php
+++ b/src/permission/DefaultPermissionNames.php
@@ -84,6 +84,8 @@ final class DefaultPermissionNames{
 	public const COMMAND_WHITELIST_LIST = "pocketmine.command.whitelist.list";
 	public const COMMAND_WHITELIST_RELOAD = "pocketmine.command.whitelist.reload";
 	public const COMMAND_WHITELIST_REMOVE = "pocketmine.command.whitelist.remove";
+	public const COMMAND_XP_OTHER = "pocketmine.command.xp.other";
+	public const COMMAND_XP_SELF = "pocketmine.command.xp.self";
 	public const GROUP_CONSOLE = "pocketmine.group.console";
 	public const GROUP_OPERATOR = "pocketmine.group.operator";
 	public const GROUP_USER = "pocketmine.group.user";
diff --git a/src/permission/DefaultPermissions.php b/src/permission/DefaultPermissions.php
index c72765af6bc..1030e9ff8cc 100644
--- a/src/permission/DefaultPermissions.php
+++ b/src/permission/DefaultPermissions.php
@@ -112,5 +112,7 @@ public static function registerCorePermissions() : void{
 		self::registerPermission(new Permission(Names::COMMAND_WHITELIST_LIST, l10n::pocketmine_permission_command_whitelist_list()), [$operatorRoot]);
 		self::registerPermission(new Permission(Names::COMMAND_WHITELIST_RELOAD, l10n::pocketmine_permission_command_whitelist_reload()), [$operatorRoot]);
 		self::registerPermission(new Permission(Names::COMMAND_WHITELIST_REMOVE, l10n::pocketmine_permission_command_whitelist_remove()), [$operatorRoot]);
+		self::registerPermission(new Permission(Names::COMMAND_XP_OTHER, l10n::pocketmine_permission_command_xp_other()), [$operatorRoot]);
+		self::registerPermission(new Permission(Names::COMMAND_XP_SELF, l10n::pocketmine_permission_command_xp_self()), [$operatorRoot]);
 	}
 }
diff --git a/src/permission/PermissionManager.php b/src/permission/PermissionManager.php
index f2b02e8e5ea..c9e37f5e946 100644
--- a/src/permission/PermissionManager.php
+++ b/src/permission/PermissionManager.php
@@ -23,6 +23,7 @@
 
 namespace pocketmine\permission;
 
+use pocketmine\Server;
 use pocketmine\utils\Utils;
 use function count;
 use function spl_object_id;
@@ -71,6 +72,10 @@ public function removePermission(Permission|string $permission) : void{
 		}
 	}
 
+	/**
+	 * @deprecated Superseded by server chat broadcast channels
+	 * @see Server::subscribeToBroadcastChannel()
+	 */
 	public function subscribeToPermission(string $permission, PermissibleInternal $permissible) : void{
 		if(!isset($this->permSubs[$permission])){
 			$this->permSubs[$permission] = [];
@@ -78,6 +83,10 @@ public function subscribeToPermission(string $permission, PermissibleInternal $p
 		$this->permSubs[$permission][spl_object_id($permissible)] = $permissible;
 	}
 
+	/**
+	 * @deprecated Superseded by server chat broadcast channels
+	 * @see Server::unsubscribeFromBroadcastChannel()
+	 */
 	public function unsubscribeFromPermission(string $permission, PermissibleInternal $permissible) : void{
 		if(isset($this->permSubs[$permission][spl_object_id($permissible)])){
 			if(count($this->permSubs[$permission]) === 1){
@@ -88,6 +97,10 @@ public function unsubscribeFromPermission(string $permission, PermissibleInterna
 		}
 	}
 
+	/**
+	 * @deprecated Superseded by server chat broadcast channels
+	 * @see Server::unsubscribeFromAllBroadcastChannels()
+	 */
 	public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) : void{
 		foreach(Utils::promoteKeys($this->permSubs) as $permission => $subs){
 			if(count($subs) === 1 && isset($subs[spl_object_id($permissible)])){
@@ -99,6 +112,8 @@ public function unsubscribeFromAllPermissions(PermissibleInternal $permissible)
 	}
 
 	/**
+	 * @deprecated Superseded by server chat broadcast channels
+	 * @see Server::getBroadcastChannelSubscribers()
 	 * @return PermissibleInternal[]
 	 */
 	public function getPermissionSubscriptions(string $permission) : array{
diff --git a/src/player/Player.php b/src/player/Player.php
index d203fe6a949..bf911a2ff26 100644
--- a/src/player/Player.php
+++ b/src/player/Player.php
@@ -57,6 +57,7 @@
 use pocketmine\event\player\PlayerDropItemEvent;
 use pocketmine\event\player\PlayerEmoteEvent;
 use pocketmine\event\player\PlayerEntityInteractEvent;
+use pocketmine\event\player\PlayerEntityPickEvent;
 use pocketmine\event\player\PlayerExhaustEvent;
 use pocketmine\event\player\PlayerGameModeChangeEvent;
 use pocketmine\event\player\PlayerInteractEvent;
@@ -109,6 +110,7 @@
 use pocketmine\network\mcpe\protocol\MovePlayerPacket;
 use pocketmine\network\mcpe\protocol\SetActorMotionPacket;
 use pocketmine\network\mcpe\protocol\types\BlockPosition;
+use pocketmine\network\mcpe\protocol\types\DimensionIds;
 use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
 use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
 use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
@@ -190,6 +192,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
 	private const TAG_SPAWN_X = "SpawnX"; //TAG_Int
 	private const TAG_SPAWN_Y = "SpawnY"; //TAG_Int
 	private const TAG_SPAWN_Z = "SpawnZ"; //TAG_Int
+	private const TAG_DEATH_WORLD = "DeathLevel"; //TAG_String
+	private const TAG_DEATH_X = "DeathPositionX"; //TAG_Int
+	private const TAG_DEATH_Y = "DeathPositionY"; //TAG_Int
+	private const TAG_DEATH_Z = "DeathPositionZ"; //TAG_Int
 	public const TAG_LEVEL = "Level"; //TAG_String
 	public const TAG_LAST_KNOWN_XUID = "LastKnownXUID"; //TAG_String
 
@@ -272,6 +278,8 @@ public static function isValidUserName(?string $name) : bool{
 
 	private bool $respawnLocked = false;
 
+	private ?Position $deathPosition = null;
+
 	//TODO: Abilities
 	protected bool $autoJump = true;
 	protected bool $allowFlight = false;
@@ -390,6 +398,9 @@ function() : void{
 		if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_SPAWN_WORLD, ""))) instanceof World){
 			$this->spawnPosition = new Position($nbt->getInt(self::TAG_SPAWN_X), $nbt->getInt(self::TAG_SPAWN_Y), $nbt->getInt(self::TAG_SPAWN_Z), $world);
 		}
+		if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_DEATH_WORLD, ""))) instanceof World){
+			$this->deathPosition = new Position($nbt->getInt(self::TAG_DEATH_X), $nbt->getInt(self::TAG_DEATH_Y), $nbt->getInt(self::TAG_DEATH_Z), $world);
+		}
 	}
 
 	public function getLeaveMessage() : Translatable|string{
@@ -1031,6 +1042,30 @@ public function doChunkRequests() : void{
 		}
 	}
 
+	public function getDeathPosition() : ?Position{
+		if($this->deathPosition !== null && !$this->deathPosition->isValid()){
+			$this->deathPosition = null;
+		}
+		return $this->deathPosition;
+	}
+
+	/**
+	 * @param Vector3|Position|null $pos
+	 */
+	public function setDeathPosition(?Vector3 $pos) : void{
+		if($pos !== null){
+			if($pos instanceof Position && $pos->world !== null){
+				$world = $pos->world;
+			}else{
+				$world = $this->getWorld();
+			}
+			$this->deathPosition = new Position($pos->x, $pos->y, $pos->z, $world);
+		}else{
+			$this->deathPosition = null;
+		}
+		$this->networkPropertiesDirty = true;
+	}
+
 	/**
 	 * @return Position
 	 */
@@ -1470,6 +1505,10 @@ public function onUpdate(int $currentTick) : bool{
 		return true;
 	}
 
+	public function canEat() : bool{
+		return $this->isCreative() || parent::canEat();
+	}
+
 	public function canBreathe() : bool{
 		return $this->isCreative() || parent::canBreathe();
 	}
@@ -1709,29 +1748,58 @@ public function pickBlock(Vector3 $pos, bool $addTileNBT) : bool{
 		$ev->call();
 
 		if(!$ev->isCancelled()){
-			if($existingSlot !== -1){
-				if($existingSlot < $this->inventory->getHotbarSize()){
-					$this->inventory->setHeldItemIndex($existingSlot);
-				}else{
-					$this->inventory->swap($this->inventory->getHeldItemIndex(), $existingSlot);
-				}
-			}else{
-				$firstEmpty = $this->inventory->firstEmpty();
-				if($firstEmpty === -1){ //full inventory
-					$this->inventory->setItemInHand($item);
-				}elseif($firstEmpty < $this->inventory->getHotbarSize()){
-					$this->inventory->setItem($firstEmpty, $item);
-					$this->inventory->setHeldItemIndex($firstEmpty);
-				}else{
-					$this->inventory->swap($this->inventory->getHeldItemIndex(), $firstEmpty);
-					$this->inventory->setItemInHand($item);
-				}
-			}
+			$this->equipOrAddPickedItem($existingSlot, $item);
+		}
+
+		return true;
+	}
+
+	public function pickEntity(int $entityId) : bool{
+		$entity = $this->getWorld()->getEntity($entityId);
+		if($entity === null){
+			return true;
+		}
+
+		$item = $entity->getPickedItem();
+		if($item === null){
+			return true;
+		}
+
+		$ev = new PlayerEntityPickEvent($this, $entity, $item);
+		$existingSlot = $this->inventory->first($item);
+		if($existingSlot === -1 && ($this->hasFiniteResources() || $this->isSpectator())){
+			$ev->cancel();
+		}
+		$ev->call();
+
+		if(!$ev->isCancelled()){
+			$this->equipOrAddPickedItem($existingSlot, $item);
 		}
 
 		return true;
 	}
 
+	private function equipOrAddPickedItem(int $existingSlot, Item $item) : void{
+		if($existingSlot !== -1){
+			if($existingSlot < $this->inventory->getHotbarSize()){
+				$this->inventory->setHeldItemIndex($existingSlot);
+			}else{
+				$this->inventory->swap($this->inventory->getHeldItemIndex(), $existingSlot);
+			}
+		}else{
+			$firstEmpty = $this->inventory->firstEmpty();
+			if($firstEmpty === -1){ //full inventory
+				$this->inventory->setItemInHand($item);
+			}elseif($firstEmpty < $this->inventory->getHotbarSize()){
+				$this->inventory->setItem($firstEmpty, $item);
+				$this->inventory->setHeldItemIndex($firstEmpty);
+			}else{
+				$this->inventory->swap($this->inventory->getHeldItemIndex(), $firstEmpty);
+				$this->inventory->setItemInHand($item);
+			}
+		}
+	}
+
 	/**
 	 * Performs a left-click (attack) action on the block.
 	 *
@@ -2302,6 +2370,7 @@ protected function destroyCycles() : void{
 		unset($this->cursorInventory);
 		unset($this->craftingGrid);
 		$this->spawnPosition = null;
+		$this->deathPosition = null;
 		$this->blockBreakHandler = null;
 		parent::destroyCycles();
 	}
@@ -2343,6 +2412,13 @@ public function getSaveData() : CompoundTag{
 			$nbt->setInt(self::TAG_SPAWN_Z, $spawn->getFloorZ());
 		}
 
+		if($this->deathPosition !== null && $this->deathPosition->isValid()){
+			$nbt->setString(self::TAG_DEATH_WORLD, $this->deathPosition->getWorld()->getFolderName());
+			$nbt->setInt(self::TAG_DEATH_X, $this->deathPosition->getFloorX());
+			$nbt->setInt(self::TAG_DEATH_Y, $this->deathPosition->getFloorY());
+			$nbt->setInt(self::TAG_DEATH_Z, $this->deathPosition->getFloorZ());
+		}
+
 		$nbt->setInt(self::TAG_GAME_MODE, GameModeIdMap::getInstance()->toId($this->gamemode));
 		$nbt->setLong(self::TAG_FIRST_PLAYED, $this->firstPlayed);
 		$nbt->setLong(self::TAG_LAST_PLAYED, (int) floor(microtime(true) * 1000));
@@ -2362,6 +2438,8 @@ protected function onDeath() : void{
 		//main inventory and drops the rest on the ground.
 		$this->removeCurrentWindow();
 
+		$this->setDeathPosition($this->getPosition());
+
 		$ev = new PlayerDeathEvent($this, $this->getDrops(), $this->getXpDropAmount(), null);
 		$ev->call();
 
@@ -2490,6 +2568,17 @@ protected function syncNetworkData(EntityMetadataCollection $properties) : void{
 
 		$properties->setPlayerFlag(PlayerMetadataFlags::SLEEP, $this->sleeping !== null);
 		$properties->setBlockPos(EntityMetadataProperties::PLAYER_BED_POSITION, $this->sleeping !== null ? BlockPosition::fromVector3($this->sleeping) : new BlockPosition(0, 0, 0));
+
+		if($this->deathPosition !== null && $this->deathPosition->world === $this->location->world){
+			$properties->setBlockPos(EntityMetadataProperties::PLAYER_DEATH_POSITION, BlockPosition::fromVector3($this->deathPosition));
+			//TODO: this should be updated when dimensions are implemented
+			$properties->setInt(EntityMetadataProperties::PLAYER_DEATH_DIMENSION, DimensionIds::OVERWORLD);
+			$properties->setByte(EntityMetadataProperties::PLAYER_HAS_DIED, 1);
+		}else{
+			$properties->setBlockPos(EntityMetadataProperties::PLAYER_DEATH_POSITION, new BlockPosition(0, 0, 0));
+			$properties->setInt(EntityMetadataProperties::PLAYER_DEATH_DIMENSION, DimensionIds::OVERWORLD);
+			$properties->setByte(EntityMetadataProperties::PLAYER_HAS_DIED, 0);
+		}
 	}
 
 	public function sendData(?array $targets, ?array $data = null) : void{
diff --git a/src/plugin/DiskResourceProvider.php b/src/plugin/DiskResourceProvider.php
index efdc9cd1724..9649f565f13 100644
--- a/src/plugin/DiskResourceProvider.php
+++ b/src/plugin/DiskResourceProvider.php
@@ -36,6 +36,8 @@
 /**
  * Provides resources from the given plugin directory on disk. The path may be prefixed with a specific access protocol
  * to enable special types of access.
+ *
+ * @deprecated
  */
 class DiskResourceProvider implements ResourceProvider{
 	private string $file;
diff --git a/src/plugin/ResourceProvider.php b/src/plugin/ResourceProvider.php
index 3594d7eee88..5ff8db882a8 100644
--- a/src/plugin/ResourceProvider.php
+++ b/src/plugin/ResourceProvider.php
@@ -23,6 +23,9 @@
 
 namespace pocketmine\plugin;
 
+/**
+ * @deprecated
+ */
 interface ResourceProvider{
 	/**
 	 * Gets an embedded resource on the plugin file.
diff --git a/src/promise/Promise.php b/src/promise/Promise.php
index cd8fcc4f1c9..c09037158bf 100644
--- a/src/promise/Promise.php
+++ b/src/promise/Promise.php
@@ -70,16 +70,17 @@ public function isResolved() : bool{
 	 *
 	 * @phpstan-template TPromiseValue
 	 * @phpstan-template TKey of array-key
-	 * @phpstan-param non-empty-array<TKey, Promise<TPromiseValue>> $promises
+	 * @phpstan-param array<TKey, Promise<TPromiseValue>> $promises
 	 *
 	 * @phpstan-return Promise<array<TKey, TPromiseValue>>
 	 */
 	public static function all(array $promises) : Promise{
-		if(count($promises) === 0){
-			throw new \InvalidArgumentException("At least one promise must be provided");
-		}
 		/** @phpstan-var PromiseResolver<array<TKey, TPromiseValue>> $resolver */
 		$resolver = new PromiseResolver();
+		if(count($promises) === 0){
+			$resolver->resolve([]);
+			return $resolver->getPromise();
+		}
 		$values = [];
 		$toResolve = count($promises);
 		$continue = true;
diff --git a/src/scheduler/AsyncTask.php b/src/scheduler/AsyncTask.php
index b0b64347adc..1175b6fc4f9 100644
--- a/src/scheduler/AsyncTask.php
+++ b/src/scheduler/AsyncTask.php
@@ -27,6 +27,7 @@
 use pmmp\thread\ThreadSafe;
 use pmmp\thread\ThreadSafeArray;
 use pocketmine\thread\NonThreadSafeValue;
+use pocketmine\timings\Timings;
 use function array_key_exists;
 use function igbinary_serialize;
 use function igbinary_unserialize;
@@ -67,7 +68,10 @@ abstract class AsyncTask extends Runnable{
 	 */
 	private static array $threadLocalStorage = [];
 
-	/** @phpstan-var ThreadSafeArray<int, string>|null */
+	/**
+	 * @phpstan-var ThreadSafeArray<int, string>|null
+	 * @deprecated
+	 */
 	private ?ThreadSafeArray $progressUpdates = null;
 
 	private ThreadSafe|string|int|bool|null|float $result = null;
@@ -78,7 +82,14 @@ abstract class AsyncTask extends Runnable{
 	public function run() : void{
 		$this->result = null;
 
-		$this->onRun();
+		$timings = Timings::getAsyncTaskRunTimings($this);
+		$timings->startTiming();
+
+		try{
+			$this->onRun();
+		}finally{
+			$timings->stopTiming();
+		}
 
 		$this->finished = true;
 		AsyncWorker::getNotifier()->wakeupSleeper();
@@ -153,6 +164,8 @@ public function onCompletion() : void{
 	}
 
 	/**
+	 * @deprecated
+	 *
 	 * Call this method from {@link AsyncTask::onRun} (AsyncTask execution thread) to schedule a call to
 	 * {@link AsyncTask::onProgressUpdate} from the main thread with the given progress parameter.
 	 *
@@ -167,6 +180,7 @@ public function publishProgress(mixed $progress) : void{
 	}
 
 	/**
+	 * @deprecated
 	 * @internal Only call from AsyncPool.php on the main thread
 	 */
 	public function checkProgressUpdates() : void{
@@ -179,6 +193,8 @@ public function checkProgressUpdates() : void{
 	}
 
 	/**
+	 * @deprecated
+	 *
 	 * Called from the main thread after {@link AsyncTask::publishProgress} is called.
 	 * All {@link AsyncTask::publishProgress} calls should result in {@link AsyncTask::onProgressUpdate} calls before
 	 * {@link AsyncTask::onCompletion} is called.
diff --git a/src/scheduler/Task.php b/src/scheduler/Task.php
index bde405a637d..db42b477ad0 100644
--- a/src/scheduler/Task.php
+++ b/src/scheduler/Task.php
@@ -26,8 +26,12 @@
 use pocketmine\utils\Utils;
 
 abstract class Task{
+	/** @phpstan-var TaskHandler<static>|null  */
 	private ?TaskHandler $taskHandler = null;
 
+	/**
+	 * @phpstan-return TaskHandler<static>|null
+	 */
 	final public function getHandler() : ?TaskHandler{
 		return $this->taskHandler;
 	}
@@ -36,6 +40,9 @@ public function getName() : string{
 		return Utils::getNiceClassName($this);
 	}
 
+	/**
+	 * @phpstan-param TaskHandler<static>|null $taskHandler
+	 */
 	final public function setHandler(?TaskHandler $taskHandler) : void{
 		if($this->taskHandler === null || $taskHandler === null){
 			$this->taskHandler = $taskHandler;
diff --git a/src/scheduler/TaskHandler.php b/src/scheduler/TaskHandler.php
index b90c992d920..985344b6498 100644
--- a/src/scheduler/TaskHandler.php
+++ b/src/scheduler/TaskHandler.php
@@ -26,6 +26,9 @@
 use pocketmine\timings\Timings;
 use pocketmine\timings\TimingsHandler;
 
+/**
+ * @template TTask of Task
+ */
 class TaskHandler{
 	protected int $nextRun;
 
@@ -36,6 +39,9 @@ class TaskHandler{
 	private string $taskName;
 	private string $ownerName;
 
+	/**
+	 * @phpstan-param TTask $task
+	 */
 	public function __construct(
 		protected Task $task,
 		protected int $delay = -1,
@@ -66,6 +72,9 @@ public function setNextRun(int $ticks) : void{
 		$this->nextRun = $ticks;
 	}
 
+	/**
+	 * @phpstan-return TTask
+	 */
 	public function getTask() : Task{
 		return $this->task;
 	}
diff --git a/src/scheduler/TaskScheduler.php b/src/scheduler/TaskScheduler.php
index f2b7c484678..3e2b089c6c5 100644
--- a/src/scheduler/TaskScheduler.php
+++ b/src/scheduler/TaskScheduler.php
@@ -33,12 +33,12 @@
 class TaskScheduler{
 	private bool $enabled = true;
 
-	/** @phpstan-var ReversePriorityQueue<int, TaskHandler> */
+	/** @phpstan-var ReversePriorityQueue<int, TaskHandler<covariant Task>> */
 	protected ReversePriorityQueue $queue;
 
 	/**
 	 * @var ObjectSet|TaskHandler[]
-	 * @phpstan-var ObjectSet<TaskHandler>
+	 * @phpstan-var ObjectSet<TaskHandler<covariant Task>>
 	 */
 	protected ObjectSet $tasks;
 
@@ -51,18 +51,42 @@ public function __construct(
 		$this->tasks = new ObjectSet();
 	}
 
+	/**
+	 * @phpstan-template TTask of Task
+	 * @phpstan-param TTask $task
+	 *
+	 * @phpstan-return TaskHandler<TTask>
+	 */
 	public function scheduleTask(Task $task) : TaskHandler{
 		return $this->addTask($task, -1, -1);
 	}
 
+	/**
+	 * @phpstan-template TTask of Task
+	 * @phpstan-param TTask $task
+	 *
+	 * @phpstan-return TaskHandler<TTask>
+	 */
 	public function scheduleDelayedTask(Task $task, int $delay) : TaskHandler{
 		return $this->addTask($task, $delay, -1);
 	}
 
+	/**
+	 * @phpstan-template TTask of Task
+	 * @phpstan-param TTask $task
+	 *
+	 * @phpstan-return TaskHandler<TTask>
+	 */
 	public function scheduleRepeatingTask(Task $task, int $period) : TaskHandler{
 		return $this->addTask($task, -1, $period);
 	}
 
+	/**
+	 * @phpstan-template TTask of Task
+	 * @phpstan-param TTask $task
+	 *
+	 * @phpstan-return TaskHandler<TTask>
+	 */
 	public function scheduleDelayedRepeatingTask(Task $task, int $delay, int $period) : TaskHandler{
 		return $this->addTask($task, $delay, $period);
 	}
@@ -77,10 +101,19 @@ public function cancelAllTasks() : void{
 		}
 	}
 
+	/**
+	 * @phpstan-param TaskHandler<covariant Task> $task
+	 */
 	public function isQueued(TaskHandler $task) : bool{
 		return $this->tasks->contains($task);
 	}
 
+	/**
+	 * @phpstan-template TTask of Task
+	 * @phpstan-param TTask $task
+	 *
+	 * @phpstan-return TaskHandler<TTask>
+	 */
 	private function addTask(Task $task, int $delay, int $period) : TaskHandler{
 		if(!$this->enabled){
 			throw new \LogicException("Tried to schedule task to disabled scheduler");
@@ -99,6 +132,11 @@ private function addTask(Task $task, int $delay, int $period) : TaskHandler{
 		return $this->handle(new TaskHandler($task, $delay, $period, $this->owner));
 	}
 
+	/**
+	 * @phpstan-template TTask of Task
+	 * @phpstan-param TaskHandler<TTask> $handler
+	 * @phpstan-return TaskHandler<TTask>
+	 */
 	private function handle(TaskHandler $handler) : TaskHandler{
 		if($handler->isDelayed()){
 			$nextRun = $this->currentTick + $handler->getDelay();
@@ -128,7 +166,7 @@ public function mainThreadHeartbeat(int $currentTick) : void{
 		}
 		$this->currentTick = $currentTick;
 		while($this->isReady($this->currentTick)){
-			/** @var TaskHandler $task */
+			/** @phpstan-var TaskHandler<covariant Task> $task */
 			$task = $this->queue->extract();
 			if($task->isCancelled()){
 				$this->tasks->remove($task);
diff --git a/src/scheduler/TimingsCollectionTask.php b/src/scheduler/TimingsCollectionTask.php
new file mode 100644
index 00000000000..25cd41166c2
--- /dev/null
+++ b/src/scheduler/TimingsCollectionTask.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\scheduler;
+
+use pocketmine\promise\PromiseResolver;
+use pocketmine\timings\TimingsHandler;
+
+/**
+ * @phpstan-type Resolver PromiseResolver<list<string>>
+ */
+final class TimingsCollectionTask extends AsyncTask{
+	private const TLS_KEY_RESOLVER = "resolver";
+
+	/**
+	 * @phpstan-param PromiseResolver<list<string>> $promiseResolver
+	 */
+	public function __construct(PromiseResolver $promiseResolver){
+		$this->storeLocal(self::TLS_KEY_RESOLVER, $promiseResolver);
+	}
+
+	public function onRun() : void{
+		$this->setResult(TimingsHandler::printCurrentThreadRecords());
+	}
+
+	public function onCompletion() : void{
+		/**
+		 * @var string[] $result
+		 * @phpstan-var list<string> $result
+		 */
+		$result = $this->getResult();
+		/**
+		 * @var PromiseResolver $promiseResolver
+		 * @phpstan-var PromiseResolver<list<string>> $promiseResolver
+		 */
+		$promiseResolver = $this->fetchLocal(self::TLS_KEY_RESOLVER);
+
+		$promiseResolver->resolve($result);
+	}
+}
diff --git a/src/scheduler/TimingsControlTask.php b/src/scheduler/TimingsControlTask.php
new file mode 100644
index 00000000000..51e906e6bcb
--- /dev/null
+++ b/src/scheduler/TimingsControlTask.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\scheduler;
+
+use pocketmine\timings\TimingsHandler;
+
+final class TimingsControlTask extends AsyncTask{
+
+	private const ENABLE = 1;
+	private const DISABLE = 2;
+	private const RELOAD = 3;
+
+	private function __construct(
+		private int $operation
+	){}
+
+	public static function setEnabled(bool $enable) : self{
+		return new self($enable ? self::ENABLE : self::DISABLE);
+	}
+
+	public static function reload() : self{
+		return new self(self::RELOAD);
+	}
+
+	public function onRun() : void{
+		if($this->operation === self::ENABLE){
+			TimingsHandler::setEnabled(true);
+			\GlobalLogger::get()->debug("Enabled timings");
+		}elseif($this->operation === self::DISABLE){
+			TimingsHandler::setEnabled(false);
+			\GlobalLogger::get()->debug("Disabled timings");
+		}elseif($this->operation === self::RELOAD){
+			TimingsHandler::reload();
+			\GlobalLogger::get()->debug("Reset timings");
+		}else{
+			throw new \InvalidArgumentException("Invalid operation $this->operation");
+		}
+	}
+}
diff --git a/src/timings/Timings.php b/src/timings/Timings.php
index 563af69bff6..e717000233f 100644
--- a/src/timings/Timings.php
+++ b/src/timings/Timings.php
@@ -30,12 +30,14 @@
 use pocketmine\network\mcpe\protocol\ServerboundPacket;
 use pocketmine\player\Player;
 use pocketmine\scheduler\AsyncTask;
+use pocketmine\scheduler\Task;
 use pocketmine\scheduler\TaskHandler;
 use function get_class;
 use function str_starts_with;
 
 abstract class Timings{
 	public const GROUP_MINECRAFT = "Minecraft";
+	/** @deprecated No longer used */
 	public const GROUP_BREAKDOWN = "Minecraft - Breakdown";
 
 	private static bool $initialized = false;
@@ -123,11 +125,16 @@ abstract class Timings{
 
 	/** @var TimingsHandler[] */
 	private static array $asyncTaskProgressUpdate = [];
+
 	/** @var TimingsHandler[] */
 	private static array $asyncTaskCompletion = [];
 	/** @var TimingsHandler[] */
 	private static array $asyncTaskError = [];
 
+	private static TimingsHandler $asyncTaskWorkers;
+	/** @var TimingsHandler[] */
+	private static array $asyncTaskRun = [];
+
 	public static function init() : void{
 		if(self::$initialized){
 			return;
@@ -187,11 +194,17 @@ public static function init() : void{
 		self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync);
 		self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error Handlers", self::$schedulerAsync);
 
+		self::$asyncTaskWorkers = new TimingsHandler("Async Task Workers");
+
 		self::$playerCommand = new TimingsHandler("Player Command");
 		self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache");
 
 	}
 
+	/**
+	 * @template TTask of Task
+	 * @phpstan-param TaskHandler<TTask> $task
+	 */
 	public static function getScheduledTaskTimings(TaskHandler $task, int $period) : TimingsHandler{
 		self::init();
 		$name = "Task: " . $task->getTaskName();
@@ -339,6 +352,9 @@ public static function getAsyncTaskCompletionTimings(AsyncTask $task, string $gr
 		return self::$asyncTaskCompletion[$taskClass];
 	}
 
+	/**
+	 * @deprecated No longer used
+	 */
 	public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
 		$taskClass = $task::class;
 		if(!isset(self::$asyncTaskError[$taskClass])){
@@ -352,4 +368,18 @@ public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group =
 
 		return self::$asyncTaskError[$taskClass];
 	}
+
+	public static function getAsyncTaskRunTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
+		$taskClass = $task::class;
+		if(!isset(self::$asyncTaskRun[$taskClass])){
+			self::init();
+			self::$asyncTaskRun[$taskClass] = new TimingsHandler(
+				"AsyncTask - " . self::shortenCoreClassName($taskClass, "pocketmine\\") . " - Run",
+				self::$asyncTaskWorkers,
+				$group
+			);
+		}
+
+		return self::$asyncTaskRun[$taskClass];
+	}
 }
diff --git a/src/timings/TimingsHandler.php b/src/timings/TimingsHandler.php
index 574dd6d2b88..95f7dbacc6d 100644
--- a/src/timings/TimingsHandler.php
+++ b/src/timings/TimingsHandler.php
@@ -23,20 +23,66 @@
 
 namespace pocketmine\timings;
 
+use pmmp\thread\Thread as NativeThread;
+use pocketmine\promise\Promise;
+use pocketmine\promise\PromiseResolver;
 use pocketmine\Server;
+use pocketmine\utils\ObjectSet;
 use pocketmine\utils\Utils;
+use function array_merge;
+use function array_push;
 use function hrtime;
 use function implode;
 use function spl_object_id;
 
+/**
+ * @phpstan-type CollectPromise Promise<list<string>>
+ */
 class TimingsHandler{
-	private const FORMAT_VERSION = 2; //peak timings fix
+	private const FORMAT_VERSION = 3; //thread timings collection
 
 	private static bool $enabled = false;
 	private static int $timingStart = 0;
 
-	/** @return string[] */
-	public static function printTimings() : array{
+	/** @phpstan-var ObjectSet<\Closure(bool $enable) : void> */
+	private static ?ObjectSet $toggleCallbacks = null;
+	/** @phpstan-var ObjectSet<\Closure() : void> */
+	private static ?ObjectSet $reloadCallbacks = null;
+	/** @phpstan-var ObjectSet<\Closure() : list<CollectPromise>> */
+	private static ?ObjectSet $collectCallbacks = null;
+
+	/**
+	 * @phpstan-template T of object
+	 * @phpstan-param ?ObjectSet<T> $where
+	 * @phpstan-param-out ObjectSet<T> $where
+	 * @phpstan-return ObjectSet<T>
+	 */
+	private static function lazyGetSet(?ObjectSet &$where) : ObjectSet{
+		//workaround for phpstan bug - allows us to ignore 1 error instead of 6 without suppressing other errors
+		return $where ??= new ObjectSet();
+	}
+
+	/**
+	 * @phpstan-return ObjectSet<\Closure(bool $enable) : void>
+	 */
+	public static function getToggleCallbacks() : ObjectSet{ return self::lazyGetSet(self::$toggleCallbacks); }
+
+	/**
+	 * @phpstan-return ObjectSet<\Closure() : void>
+	 */
+	public static function getReloadCallbacks() : ObjectSet{ return self::lazyGetSet(self::$reloadCallbacks); }
+
+	/**
+	 * @phpstan-return ObjectSet<\Closure() : list<CollectPromise>>
+	 */
+	public static function getCollectCallbacks() : ObjectSet{ return self::lazyGetSet(self::$collectCallbacks); }
+
+	/**
+	 * @return string[]
+	 * @phpstan-return list<string>
+	 */
+	public static function printCurrentThreadRecords() : array{
+		$threadId = NativeThread::getCurrentThread()?->getThreadId();
 		$groups = [];
 
 		foreach(TimingsRecord::getAll() as $timings){
@@ -49,7 +95,7 @@ public static function printTimings() : array{
 
 			$avg = $time / $count;
 
-			$group = $timings->getGroup();
+			$group = $timings->getGroup() . ($threadId !== null ? " ThreadId: $threadId" : "");
 			$groups[$group][] = implode(" ", [
 				$timings->getName(),
 				"Time: $time",
@@ -72,6 +118,15 @@ public static function printTimings() : array{
 			}
 		}
 
+		return $result;
+	}
+
+	/**
+	 * @return string[]
+	 */
+	private static function printFooter() : array{
+		$result = [];
+
 		$result[] = "# Version " . Server::getInstance()->getVersion();
 		$result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion();
 
@@ -79,29 +134,95 @@ public static function printTimings() : array{
 
 		$sampleTime = hrtime(true) - self::$timingStart;
 		$result[] = "Sample time $sampleTime (" . ($sampleTime / 1000000000) . "s)";
+
 		return $result;
 	}
 
+	/**
+	 * @deprecated This only collects timings from the main thread. Collecting timings from all threads is an async
+	 * operation, so it can't be done synchronously.
+	 *
+	 * @return string[]
+	 */
+	public static function printTimings() : array{
+		$records = self::printCurrentThreadRecords();
+		$footer = self::printFooter();
+
+		return [...$records, ...$footer];
+	}
+
+	/**
+	 * Collects timings asynchronously, allowing timings from multiple threads to be aggregated into a single report.
+	 *
+	 * NOTE: You need to add a callback to collectCallbacks if you want to include timings from other threads. They
+	 * won't be automatically collected if you don't, since the main thread has no way to access them.
+	 *
+	 * This is an asynchronous operation, and the result is returned as a promise.
+	 * The caller must add a callback to the returned promise to get the complete timings report.
+	 *
+	 * @phpstan-return Promise<list<string>>
+	 */
+	public static function requestPrintTimings() : Promise{
+		$thisThreadRecords = self::printCurrentThreadRecords();
+
+		$otherThreadRecordPromises = [];
+		if(self::$collectCallbacks !== null){
+			foreach(self::$collectCallbacks as $callback){
+				$callbackPromises = $callback();
+				array_push($otherThreadRecordPromises, ...$callbackPromises);
+			}
+		}
+
+		$resolver = new PromiseResolver();
+		Promise::all($otherThreadRecordPromises)->onCompletion(
+			function(array $promisedRecords) use ($resolver, $thisThreadRecords) : void{
+				$resolver->resolve([...$thisThreadRecords, ...array_merge(...$promisedRecords), ...self::printFooter()]);
+			},
+			function() : void{
+				throw new \AssertionError("This promise is not expected to be rejected");
+			}
+		);
+
+		return $resolver->getPromise();
+	}
+
 	public static function isEnabled() : bool{
 		return self::$enabled;
 	}
 
 	public static function setEnabled(bool $enable = true) : void{
+		if($enable === self::$enabled){
+			return;
+		}
 		self::$enabled = $enable;
-		self::reload();
+		self::internalReload();
+		if(self::$toggleCallbacks !== null){
+			foreach(self::$toggleCallbacks as $callback){
+				$callback($enable);
+			}
+		}
 	}
 
 	public static function getStartTime() : float{
 		return self::$timingStart;
 	}
 
-	public static function reload() : void{
+	private static function internalReload() : void{
 		TimingsRecord::reset();
 		if(self::$enabled){
 			self::$timingStart = hrtime(true);
 		}
 	}
 
+	public static function reload() : void{
+		self::internalReload();
+		if(self::$reloadCallbacks !== null){
+			foreach(self::$reloadCallbacks as $callback){
+				$callback();
+			}
+		}
+	}
+
 	public static function tick(bool $measure = true) : void{
 		if(self::$enabled){
 			TimingsRecord::tick($measure);
diff --git a/src/utils/Utils.php b/src/utils/Utils.php
index 38f523ad4db..c8be174d6a1 100644
--- a/src/utils/Utils.php
+++ b/src/utils/Utils.php
@@ -67,6 +67,8 @@
 use function is_object;
 use function is_string;
 use function mb_check_encoding;
+use function mt_getrandmax;
+use function mt_rand;
 use function ob_end_clean;
 use function ob_get_contents;
 use function ob_start;
@@ -688,4 +690,12 @@ function_exists('opcache_get_status') &&
 		//jit not available
 		return null;
 	}
+
+	/**
+	 * Returns a random float between 0.0 and 1.0
+	 * Drop-in replacement for lcg_value()
+	 */
+	public static function getRandomFloat() : float{
+		return mt_rand() / mt_getrandmax();
+	}
 }
diff --git a/src/world/World.php b/src/world/World.php
index 187f7ab8b89..ff65377c094 100644
--- a/src/world/World.php
+++ b/src/world/World.php
@@ -83,6 +83,7 @@
 use pocketmine\utils\AssumptionFailedError;
 use pocketmine\utils\Limits;
 use pocketmine\utils\ReversePriorityQueue;
+use pocketmine\utils\Utils;
 use pocketmine\world\biome\Biome;
 use pocketmine\world\biome\BiomeRegistry;
 use pocketmine\world\format\Chunk;
@@ -120,7 +121,6 @@
 use function gettype;
 use function is_a;
 use function is_object;
-use function lcg_value;
 use function max;
 use function microtime;
 use function min;
@@ -1998,10 +1998,10 @@ public function dropItem(Vector3 $source, Item $item, ?Vector3 $motion = null, i
 			return null;
 		}
 
-		$itemEntity = new ItemEntity(Location::fromObject($source, $this, lcg_value() * 360, 0), $item);
+		$itemEntity = new ItemEntity(Location::fromObject($source, $this, Utils::getRandomFloat() * 360, 0), $item);
 
 		$itemEntity->setPickupDelay($delay);
-		$itemEntity->setMotion($motion ?? new Vector3(lcg_value() * 0.2 - 0.1, 0.2, lcg_value() * 0.2 - 0.1));
+		$itemEntity->setMotion($motion ?? new Vector3(Utils::getRandomFloat() * 0.2 - 0.1, 0.2, Utils::getRandomFloat() * 0.2 - 0.1));
 		$itemEntity->spawnToAll();
 
 		return $itemEntity;
@@ -2018,9 +2018,9 @@ public function dropExperience(Vector3 $pos, int $amount) : array{
 		$orbs = [];
 
 		foreach(ExperienceOrb::splitIntoOrbSizes($amount) as $split){
-			$orb = new ExperienceOrb(Location::fromObject($pos, $this, lcg_value() * 360, 0), $split);
+			$orb = new ExperienceOrb(Location::fromObject($pos, $this, Utils::getRandomFloat() * 360, 0), $split);
 
-			$orb->setMotion(new Vector3((lcg_value() * 0.2 - 0.1) * 2, lcg_value() * 0.4, (lcg_value() * 0.2 - 0.1) * 2));
+			$orb->setMotion(new Vector3((Utils::getRandomFloat() * 0.2 - 0.1) * 2, Utils::getRandomFloat() * 0.4, (Utils::getRandomFloat() * 0.2 - 0.1) * 2));
 			$orb->spawnToAll();
 
 			$orbs[] = $orb;
@@ -2173,19 +2173,25 @@ public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $cli
 
 		if($player !== null){
 			$ev = new PlayerInteractEvent($player, $item, $blockClicked, $clickVector, $face, PlayerInteractEvent::RIGHT_CLICK_BLOCK);
+			if($player->isSneaking()){
+				$ev->setUseItem(false);
+				$ev->setUseBlock($item->isNull()); //opening doors is still possible when sneaking if using an empty hand
+			}
 			if($player->isSpectator()){
 				$ev->cancel(); //set it to cancelled so plugins can bypass this
 			}
 
 			$ev->call();
 			if(!$ev->isCancelled()){
-				if((!$player->isSneaking() || $item->isNull()) && $blockClicked->onInteract($item, $face, $clickVector, $player, $returnedItems)){
+				if($ev->useBlock() && $blockClicked->onInteract($item, $face, $clickVector, $player, $returnedItems)){
 					return true;
 				}
 
-				$result = $item->onInteractBlock($player, $blockReplace, $blockClicked, $face, $clickVector, $returnedItems);
-				if($result !== ItemUseResult::NONE){
-					return $result === ItemUseResult::SUCCESS;
+				if($ev->useItem()){
+					$result = $item->onInteractBlock($player, $blockReplace, $blockClicked, $face, $clickVector, $returnedItems);
+					if($result !== ItemUseResult::NONE){
+						return $result === ItemUseResult::SUCCESS;
+					}
 				}
 			}else{
 				return false;
diff --git a/src/world/sound/BottleEmptySound.php b/src/world/sound/BottleEmptySound.php
new file mode 100644
index 00000000000..bb2a0be2ad1
--- /dev/null
+++ b/src/world/sound/BottleEmptySound.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\world\sound;
+
+use pocketmine\math\Vector3;
+use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
+use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
+
+class BottleEmptySound implements Sound{
+
+	public function encode(Vector3 $pos) : array{
+		return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BOTTLE_EMPTY, $pos, false)];
+	}
+}
diff --git a/src/world/sound/GoatHornSound.php b/src/world/sound/GoatHornSound.php
new file mode 100644
index 00000000000..3987db3daee
--- /dev/null
+++ b/src/world/sound/GoatHornSound.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\world\sound;
+
+use pocketmine\item\GoatHornType;
+use pocketmine\math\Vector3;
+use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
+use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
+
+class GoatHornSound implements Sound{
+	public function __construct(private GoatHornType $goatHornType){}
+
+	public function encode(Vector3 $pos) : array{
+		return [LevelSoundEventPacket::nonActorSound(match($this->goatHornType){
+			GoatHornType::PONDER => LevelSoundEvent::HORN_CALL0,
+			GoatHornType::SING => LevelSoundEvent::HORN_CALL1,
+			GoatHornType::SEEK => LevelSoundEvent::HORN_CALL2,
+			GoatHornType::FEEL => LevelSoundEvent::HORN_CALL3,
+			GoatHornType::ADMIRE => LevelSoundEvent::HORN_CALL4,
+			GoatHornType::CALL => LevelSoundEvent::HORN_CALL5,
+			GoatHornType::YEARN => LevelSoundEvent::HORN_CALL6,
+			GoatHornType::DREAM => LevelSoundEvent::HORN_CALL7
+		}, $pos, false)];
+	}
+}
diff --git a/src/world/sound/IceBombHitSound.php b/src/world/sound/IceBombHitSound.php
new file mode 100644
index 00000000000..7dcdeebf676
--- /dev/null
+++ b/src/world/sound/IceBombHitSound.php
@@ -0,0 +1,34 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\world\sound;
+
+use pocketmine\math\Vector3;
+use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
+use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
+
+final class IceBombHitSound implements Sound{
+	public function encode(Vector3 $pos) : array{
+		return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::ICEBOMB_HIT, $pos, false)];
+	}
+}
diff --git a/start.sh b/start.sh
index 4d47871143f..8f458b4e6d8 100755
--- a/start.sh
+++ b/start.sh
@@ -44,6 +44,27 @@ fi
 
 LOOPS=0
 
+handle_exit_code() {
+	local exitcode=$1
+	if [ "$exitcode" -eq 134 ] || [ "$exitcode" -eq 139 ]; then #SIGABRT/SIGSEGV
+		echo ""
+		echo "ERROR: The server process was killed due to a critical error (code $exitcode) which could indicate a problem with PHP."
+		echo "Updating your PHP binary is recommended."
+		echo "If this keeps happening, please open a bug report."
+		echo ""
+	elif [ "$exitcode" -eq 143 ]; then #SIGKILL, maybe user intervention
+		echo ""
+		echo "WARNING: Server was forcibly killed!"
+		echo "If you didn't kill the server manually, this probably means the server used too much memory and was killed by the system's OOM Killer."
+		echo "Please ensure your system has enough available RAM."
+		echo ""
+	elif [ "$exitcode" -ne 0 ] && [ "$exitcode" -ne 137 ]; then #normal exit / SIGTERM
+		echo ""
+		echo "WARNING: Server did not shut down correctly! (code $exitcode)"
+		echo ""
+	fi
+}
+
 set +e
 
 if [ "$DO_LOOP" == "yes" ]; then
@@ -52,11 +73,15 @@ if [ "$DO_LOOP" == "yes" ]; then
 			echo "Restarted $LOOPS times"
 		fi
 		"$PHP_BINARY" "$POCKETMINE_FILE" "$@"
+		handle_exit_code $?
 		echo "To escape the loop, press CTRL+C now. Otherwise, wait 5 seconds for the server to restart."
 		echo ""
 		sleep 5
 		((LOOPS++))
 	done
 else
-	exec "$PHP_BINARY" "$POCKETMINE_FILE" "$@"
+	"$PHP_BINARY" "$POCKETMINE_FILE" "$@"
+	exitcode=$?
+	handle_exit_code $exitcode
+	exit $exitcode
 fi


Test patch:
diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon
index 54028f0dd12..f15dc9d53e7 100644
--- a/tests/phpstan/configs/actual-problems.neon
+++ b/tests/phpstan/configs/actual-problems.neon
@@ -801,7 +801,7 @@ parameters:
 			path: ../../../src/scheduler/BulkCurlTask.php
 
 		-
-			message: "#^Cannot call method getNextRun\\(\\) on array\\<string, int\\|pocketmine\\\\scheduler\\\\TaskHandler\\>\\|int\\|pocketmine\\\\scheduler\\\\TaskHandler\\.$#"
+			message: "#^Cannot call method getNextRun\\(\\) on array\\<string, int\\|pocketmine\\\\scheduler\\\\TaskHandler\\<covariant pocketmine\\\\scheduler\\\\Task\\>\\>\\|int\\|pocketmine\\\\scheduler\\\\TaskHandler\\<covariant pocketmine\\\\scheduler\\\\Task\\>\\.$#"
 			count: 1
 			path: ../../../src/scheduler/TaskScheduler.php
 
diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon
index 02c94d2d68e..ea5e1c62a70 100644
--- a/tests/phpstan/configs/phpstan-bugs.neon
+++ b/tests/phpstan/configs/phpstan-bugs.neon
@@ -20,6 +20,56 @@ parameters:
 			count: 1
 			path: ../../../src/block/DoubleTallGrass.php
 
+		-
+			message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:ACACIA_SIGN\\(\\)\\.$#"
+			count: 1
+			path: ../../../src/block/VanillaBlocks.php
+
+		-
+			message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:BIRCH_SIGN\\(\\)\\.$#"
+			count: 1
+			path: ../../../src/block/VanillaBlocks.php
+
+		-
+			message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:CHERRY_SIGN\\(\\)\\.$#"
+			count: 1
+			path: ../../../src/block/VanillaBlocks.php
+
+		-
+			message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:CRIMSON_SIGN\\(\\)\\.$#"
+			count: 1
+			path: ../../../src/block/VanillaBlocks.php
+
+		-
+			message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:DARK_OAK_SIGN\\(\\)\\.$#"
+			count: 1
+			path: ../../../src/block/VanillaBlocks.php
+
+		-
+			message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:JUNGLE_SIGN\\(\\)\\.$#"
+			count: 1
+			path: ../../../src/block/VanillaBlocks.php
+
+		-
+			message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:MANGROVE_SIGN\\(\\)\\.$#"
+			count: 1
+			path: ../../../src/block/VanillaBlocks.php
+
+		-
+			message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:OAK_SIGN\\(\\)\\.$#"
+			count: 1
+			path: ../../../src/block/VanillaBlocks.php
+
+		-
+			message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:SPRUCE_SIGN\\(\\)\\.$#"
+			count: 1
+			path: ../../../src/block/VanillaBlocks.php
+
+		-
+			message: "#^Creating callable from a non\\-native static method pocketmine\\\\item\\\\VanillaItems\\:\\:WARPED_SIGN\\(\\)\\.$#"
+			count: 1
+			path: ../../../src/block/VanillaBlocks.php
+
 		-
 			message: "#^Call to function assert\\(\\) with false and 'unknown hit type' will always evaluate to false\\.$#"
 			count: 1
@@ -40,6 +90,11 @@ parameters:
 			count: 1
 			path: ../../../src/plugin/PluginManager.php
 
+		-
+			message: "#^Method pocketmine\\\\timings\\\\TimingsHandler\\:\\:lazyGetSet\\(\\) should return pocketmine\\\\utils\\\\ObjectSet\\<T of object\\> but returns pocketmine\\\\utils\\\\ObjectSet\\<object\\>\\.$#"
+			count: 1
+			path: ../../../src/timings/TimingsHandler.php
+
 		-
 			message: "#^Casting to int something that's already int\\.$#"
 			count: 1
diff --git a/tests/phpunit/block/BlockTypeIdsTest.php b/tests/phpunit/block/BlockTypeIdsTest.php
index 32c8d490295..ce21a89ab68 100644
--- a/tests/phpunit/block/BlockTypeIdsTest.php
+++ b/tests/phpunit/block/BlockTypeIdsTest.php
@@ -51,6 +51,7 @@ public function testVanillaBlocksParity() : void{
 		foreach(Utils::stringifyKeys(VanillaBlocks::getAll()) as $name => $block){
 			$expected = $block->getTypeId();
 			$actual = $reflect->getConstant($name);
+			self::assertNotFalse($actual, "VanillaBlocks::$name() does not have a BlockTypeIds constant");
 			self::assertSame($expected, $actual, "VanillaBlocks::$name() does not match BlockTypeIds::$name");
 		}
 	}
diff --git a/tests/phpunit/console/ConsoleReaderChildProcessUtilsTest.php b/tests/phpunit/console/ConsoleReaderChildProcessUtilsTest.php
new file mode 100644
index 00000000000..31ae2e27a4e
--- /dev/null
+++ b/tests/phpunit/console/ConsoleReaderChildProcessUtilsTest.php
@@ -0,0 +1,92 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\console;
+
+use PHPUnit\Framework\TestCase;
+use function mt_rand;
+use function str_repeat;
+
+final class ConsoleReaderChildProcessUtilsTest extends TestCase{
+
+	/**
+	 * @phpstan-return \Generator<int, array{string}, void, void>
+	 */
+	public static function commandStringProvider() : \Generator{
+		yield ["stop"];
+		yield ["pocketmine:status"];
+		yield [str_repeat("s", 1000)];
+		yield ["time set day"];
+		yield ["give \"Steve\" golden_apple"];
+	}
+
+	/**
+	 * @dataProvider commandStringProvider
+	 */
+	public function testCreateParseSymmetry(string $input) : void{
+		$counterCreate = $counterParse = mt_rand();
+		$message = ConsoleReaderChildProcessUtils::createMessage($input, $counterCreate);
+		$parsedInput = ConsoleReaderChildProcessUtils::parseMessage($message, $counterParse);
+
+		self::assertSame($input, $parsedInput);
+	}
+
+	public function testCreateMessage() : void{
+		$counter = 0;
+
+		ConsoleReaderChildProcessUtils::createMessage("", $counter);
+		self::assertSame(1, $counter, "createMessage should always bump the counter");
+	}
+
+	/**
+	 * @phpstan-return \Generator<int, array{string, bool}, void, void>
+	 */
+	public static function parseMessageProvider() : \Generator{
+		$counter = 0;
+		yield [ConsoleReaderChildProcessUtils::createMessage("", $counter), true];
+
+		yield ["", false]; //keepalive message, doesn't bump counter
+
+		$counter = 1;
+		yield [ConsoleReaderChildProcessUtils::createMessage("", $counter), false]; //mismatched counter
+
+		$counter = 0;
+		yield ["a" . ConsoleReaderChildProcessUtils::TOKEN_DELIMITER . "b", false]; //message with delimiter but not a valid IPC message
+	}
+
+	/**
+	 * @dataProvider parseMessageProvider
+	 */
+	public static function testParseMessage(string $message, bool $valid) : void{
+		$counter = $oldCounter = 0;
+
+		$input = ConsoleReaderChildProcessUtils::parseMessage($message, $counter);
+		if(!$valid){
+			self::assertNull($input, "Result should be null on invalid message");
+			self::assertSame($oldCounter, $counter, "Counter shouldn't be bumped on invalid message");
+		}else{
+			self::assertNotNull($input, "This was a valid message, expected a result");
+			self::assertSame($oldCounter + 1, $counter, "Counter should be bumped on valid message parse");
+		}
+	}
+}
diff --git a/tests/phpunit/event/EventTest.php b/tests/phpunit/event/EventTest.php
new file mode 100644
index 00000000000..7410cc3bb02
--- /dev/null
+++ b/tests/phpunit/event/EventTest.php
@@ -0,0 +1,79 @@
+<?php
+
+/*
+ *
+ *  ____            _        _   __  __ _                  __  __ ____
+ * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
+ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
+ * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
+ * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * @author PocketMine Team
+ * @link http://www.pocketmine.net/
+ *
+ *
+ */
+
+declare(strict_types=1);
+
+namespace pocketmine\event;
+
+use PHPUnit\Framework\TestCase;
+use pocketmine\event\fixtures\TestChildEvent;
+use pocketmine\event\fixtures\TestGrandchildEvent;
+use pocketmine\event\fixtures\TestParentEvent;
+use pocketmine\plugin\Plugin;
+use pocketmine\plugin\PluginManager;
+use pocketmine\Server;
+
+final class EventTest extends TestCase{
+
+	private Plugin $mockPlugin;
+	private PluginManager $pluginManager;
+
+	protected function setUp() : void{
+		HandlerListManager::global()->unregisterAll();
+
+		//TODO: this is a really bad hack and could break any time if PluginManager decides to access its Server field
+		//we really need to make it possible to register events without a Plugin or Server context
+		$mockServer = $this->createMock(Server::class);
+		$this->mockPlugin = self::createStub(Plugin::class);
+		$this->mockPlugin->method('isEnabled')->willReturn(true);
+
+		$this->pluginManager = new PluginManager($mockServer, null);
+	}
+
+	public static function tearDownAfterClass() : void{
+		HandlerListManager::global()->unregisterAll();
+	}
+
+	public function testHandlerInheritance() : void{
+		$expectedOrder = [
+			TestGrandchildEvent::class,
+			TestChildEvent::class,
+			TestParentEvent::class
+		];
+		$actualOrder = [];
+
+		foreach($expectedOrder as $class){
+			$this->pluginManager->registerEvent(
+				$class,
+				function(TestParentEvent $event) use (&$actualOrder, $class) : void{
+					$actualOrder[] = $class;
+				},
+				EventPriority::NORMAL,
+				$this->mockPlugin
+			);
+		}
+
+		$event = new TestGrandchildEvent();
+		$event->call();
+
+		self::assertSame($expectedOrder, $actualOrder, "Expected event handlers to be called from most specific to least specific");
+	}
+}
diff --git a/tests/phpunit/event/HandlerListManagerTest.php b/tests/phpunit/event/HandlerListManagerTest.php
index edff36639dd..c61043dab88 100644
--- a/tests/phpunit/event/HandlerListManagerTest.php
+++ b/tests/phpunit/event/HandlerListManagerTest.php
@@ -24,6 +24,12 @@
 namespace pocketmine\event;
 
 use PHPUnit\Framework\TestCase;
+use pocketmine\event\fixtures\TestAbstractAllowHandleEvent;
+use pocketmine\event\fixtures\TestAbstractEvent;
+use pocketmine\event\fixtures\TestConcreteEvent;
+use pocketmine\event\fixtures\TestConcreteExtendsAbstractEvent;
+use pocketmine\event\fixtures\TestConcreteExtendsAllowHandleEvent;
+use pocketmine\event\fixtures\TestConcreteExtendsConcreteEvent;
 
 class HandlerListManagerTest extends TestCase{
 
diff --git a/tests/phpunit/event/TestAbstractAllowHandleEvent.php b/tests/phpunit/event/fixtures/TestAbstractAllowHandleEvent.php
similarity index 92%
rename from tests/phpunit/event/TestAbstractAllowHandleEvent.php
rename to tests/phpunit/event/fixtures/TestAbstractAllowHandleEvent.php
index 1bac06bbb0e..3831698098e 100644
--- a/tests/phpunit/event/TestAbstractAllowHandleEvent.php
+++ b/tests/phpunit/event/fixtures/TestAbstractAllowHandleEvent.php
@@ -21,7 +21,9 @@
 
 declare(strict_types=1);
 
-namespace pocketmine\event;
+namespace pocketmine\event\fixtures;
+
+use pocketmine\event\Event;
 
 /**
  * @allowHandle
diff --git a/tests/phpunit/event/TestAbstractEvent.php b/tests/phpunit/event/fixtures/TestAbstractEvent.php
similarity index 92%
rename from tests/phpunit/event/TestAbstractEvent.php
rename to tests/phpunit/event/fixtures/TestAbstractEvent.php
index 92a95363e30..b48d8f5269a 100644
--- a/tests/phpunit/event/TestAbstractEvent.php
+++ b/tests/phpunit/event/fixtures/TestAbstractEvent.php
@@ -21,7 +21,9 @@
 
 declare(strict_types=1);
 
-namespace pocketmine\event;
+namespace pocketmine\event\fixtures;
+
+use pocketmine\event\Event;
 
 abstract class TestAbstractEvent extends Event{
 
diff --git a/tests/plugins/TesterPlugin/src/event/ChildEvent.php b/tests/phpunit/event/fixtures/TestChildEvent.php
similarity index 89%
rename from tests/plugins/TesterPlugin/src/event/ChildEvent.php
rename to tests/phpunit/event/fixtures/TestChildEvent.php
index b71d2627fce..569a2c069ab 100644
--- a/tests/plugins/TesterPlugin/src/event/ChildEvent.php
+++ b/tests/phpunit/event/fixtures/TestChildEvent.php
@@ -21,8 +21,8 @@
 
 declare(strict_types=1);
 
-namespace pmmp\TesterPlugin\event;
+namespace pocketmine\event\fixtures;
 
-class ChildEvent extends ParentEvent{
+class TestChildEvent extends TestParentEvent{
 
 }
diff --git a/tests/phpunit/event/TestConcreteEvent.php b/tests/phpunit/event/fixtures/TestConcreteEvent.php
similarity index 92%
rename from tests/phpunit/event/TestConcreteEvent.php
rename to tests/phpunit/event/fixtures/TestConcreteEvent.php
index 8b159df91a6..cf744eb2ced 100644
--- a/tests/phpunit/event/TestConcreteEvent.php
+++ b/tests/phpunit/event/fixtures/TestConcreteEvent.php
@@ -21,7 +21,9 @@
 
 declare(strict_types=1);
 
-namespace pocketmine\event;
+namespace pocketmine\event\fixtures;
+
+use pocketmine\event\Event;
 
 class TestConcreteEvent extends Event{
 
diff --git a/tests/phpunit/event/TestConcreteExtendsAbstractEvent.php b/tests/phpunit/event/fixtures/TestConcreteExtendsAbstractEvent.php
similarity index 95%
rename from tests/phpunit/event/TestConcreteExtendsAbstractEvent.php
rename to tests/phpunit/event/fixtures/TestConcreteExtendsAbstractEvent.php
index 3f0fa572fef..54ec3dbb128 100644
--- a/tests/phpunit/event/TestConcreteExtendsAbstractEvent.php
+++ b/tests/phpunit/event/fixtures/TestConcreteExtendsAbstractEvent.php
@@ -21,7 +21,7 @@
 
 declare(strict_types=1);
 
-namespace pocketmine\event;
+namespace pocketmine\event\fixtures;
 
 class TestConcreteExtendsAbstractEvent extends TestAbstractEvent{
 
diff --git a/tests/phpunit/event/TestConcreteExtendsAllowHandleEvent.php b/tests/phpunit/event/fixtures/TestConcreteExtendsAllowHandleEvent.php
similarity index 95%
rename from tests/phpunit/event/TestConcreteExtendsAllowHandleEvent.php
rename to tests/phpunit/event/fixtures/TestConcreteExtendsAllowHandleEvent.php
index e4146512116..362ce693b50 100644
--- a/tests/phpunit/event/TestConcreteExtendsAllowHandleEvent.php
+++ b/tests/phpunit/event/fixtures/TestConcreteExtendsAllowHandleEvent.php
@@ -21,7 +21,7 @@
 
 declare(strict_types=1);
 
-namespace pocketmine\event;
+namespace pocketmine\event\fixtures;
 
 class TestConcreteExtendsAllowHandleEvent extends TestAbstractAllowHandleEvent{
 
diff --git a/tests/phpunit/event/TestConcreteExtendsConcreteEvent.php b/tests/phpunit/event/fixtures/TestConcreteExtendsConcreteEvent.php
similarity index 95%
rename from tests/phpunit/event/TestConcreteExtendsConcreteEvent.php
rename to tests/phpunit/event/fixtures/TestConcreteExtendsConcreteEvent.php
index cc955893515..3fd3a6cf0e0 100644
--- a/tests/phpunit/event/TestConcreteExtendsConcreteEvent.php
+++ b/tests/phpunit/event/fixtures/TestConcreteExtendsConcreteEvent.php
@@ -21,7 +21,7 @@
 
 declare(strict_types=1);
 
-namespace pocketmine\event;
+namespace pocketmine\event\fixtures;
 
 class TestConcreteExtendsConcreteEvent extends TestConcreteEvent{
 
diff --git a/tests/plugins/TesterPlugin/src/event/ParentEvent.php b/tests/phpunit/event/fixtures/TestGrandchildEvent.php
similarity index 89%
rename from tests/plugins/TesterPlugin/src/event/ParentEvent.php
rename to tests/phpunit/event/fixtures/TestGrandchildEvent.php
index 68f7df6300e..bfe50f9f3d4 100644
--- a/tests/plugins/TesterPlugin/src/event/ParentEvent.php
+++ b/tests/phpunit/event/fixtures/TestGrandchildEvent.php
@@ -21,8 +21,8 @@
 
 declare(strict_types=1);
 
-namespace pmmp\TesterPlugin\event;
+namespace pocketmine\event\fixtures;
 
-class ParentEvent extends \pocketmine\event\Event{
+class TestGrandchildEvent extends TestChildEvent{
 
 }
diff --git a/tests/plugins/TesterPlugin/src/event/GrandchildEvent.php b/tests/phpunit/event/fixtures/TestParentEvent.php
similarity index 87%
rename from tests/plugins/TesterPlugin/src/event/GrandchildEvent.php
rename to tests/phpunit/event/fixtures/TestParentEvent.php
index 40c37c56794..c204422722b 100644
--- a/tests/plugins/TesterPlugin/src/event/GrandchildEvent.php
+++ b/tests/phpunit/event/fixtures/TestParentEvent.php
@@ -21,8 +21,10 @@
 
 declare(strict_types=1);
 
-namespace pmmp\TesterPlugin\event;
+namespace pocketmine\event\fixtures;
 
-class GrandchildEvent extends ChildEvent{
+use pocketmine\event\Event;
+
+class TestParentEvent extends Event{
 
 }
diff --git a/tests/phpunit/item/ItemTypeIdsTest.php b/tests/phpunit/item/ItemTypeIdsTest.php
index 7ac8485fccd..7336780b33f 100644
--- a/tests/phpunit/item/ItemTypeIdsTest.php
+++ b/tests/phpunit/item/ItemTypeIdsTest.php
@@ -54,6 +54,7 @@ public function testVanillaItemsParity() : void{
 			}
 			$expected = $item->getTypeId();
 			$actual = $reflect->getConstant($name);
+			self::assertNotFalse($actual, "VanillaItems::$name() does not have an ItemTypeIds constant");
 			self::assertSame($expected, $actual, "VanillaItems::$name() type ID does not match ItemTypeIds::$name");
 		}
 	}
diff --git a/tests/plugins/TesterPlugin/src/EventHandlerInheritanceTest.php b/tests/plugins/TesterPlugin/src/EventHandlerInheritanceTest.php
deleted file mode 100644
index efe20f8d8f9..00000000000
--- a/tests/plugins/TesterPlugin/src/EventHandlerInheritanceTest.php
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-
-/*
- *
- *  ____            _        _   __  __ _                  __  __ ____
- * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
- * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
- * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
- * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * @author PocketMine Team
- * @link http://www.pocketmine.net/
- *
- *
- */
-
-declare(strict_types=1);
-
-namespace pmmp\TesterPlugin;
-
-use pmmp\TesterPlugin\event\ChildEvent;
-use pmmp\TesterPlugin\event\GrandchildEvent;
-use pmmp\TesterPlugin\event\ParentEvent;
-use pocketmine\event\EventPriority;
-use function implode;
-
-final class EventHandlerInheritanceTest extends Test{
-
-	private const EXPECTED_ORDER = [
-		GrandchildEvent::class,
-		ChildEvent::class,
-		ParentEvent::class
-	];
-
-	/** @var string[] */
-	private array $callOrder = [];
-
-	public function getName() : string{
-		return "Event Handler Inheritance Test";
-	}
-
-	public function getDescription() : string{
-		return "Tests that child events are correctly passed to parent event handlers";
-	}
-
-	public function run() : void{
-		$plugin = $this->getPlugin();
-		$plugin->getServer()->getPluginManager()->registerEvent(
-			ParentEvent::class,
-			function(ParentEvent $event) : void{
-				$this->callOrder[] = ParentEvent::class;
-			},
-			EventPriority::NORMAL,
-			$plugin
-		);
-		$plugin->getServer()->getPluginManager()->registerEvent(
-			ChildEvent::class,
-			function(ChildEvent $event) : void{
-				$this->callOrder[] = ChildEvent::class;
-			},
-			EventPriority::NORMAL,
-			$plugin
-		);
-		$plugin->getServer()->getPluginManager()->registerEvent(
-			GrandchildEvent::class,
-			function(GrandchildEvent $event) : void{
-				$this->callOrder[] = GrandchildEvent::class;
-			},
-			EventPriority::NORMAL,
-			$plugin
-		);
-
-		$event = new GrandchildEvent();
-		$event->call();
-
-		if($this->callOrder === self::EXPECTED_ORDER){
-			$this->setResult(Test::RESULT_OK);
-		}else{
-			$plugin->getLogger()->error("Expected order: " . implode(", ", self::EXPECTED_ORDER) . ", got: " . implode(", ", $this->callOrder));
-			$this->setResult(Test::RESULT_FAILED);
-		}
-	}
-}
diff --git a/tests/plugins/TesterPlugin/src/Main.php b/tests/plugins/TesterPlugin/src/Main.php
index 26d3441f4a6..08b59dbacb6 100644
--- a/tests/plugins/TesterPlugin/src/Main.php
+++ b/tests/plugins/TesterPlugin/src/Main.php
@@ -57,7 +57,7 @@ public function onEnable() : void{
 		}), 10);
 
 		$this->waitingTests = [
-			new EventHandlerInheritanceTest($this),
+			//Add test objects here
 		];
 	}