Release 5.23.0 (#6561)

This commit is contained in:
Dylan T. 2024-12-05 15:51:13 +00:00 committed by GitHub
commit ea8f971287
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
106 changed files with 3508 additions and 1615 deletions

107
changelogs/5.23.md Normal file
View File

@ -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.

View File

@ -32,7 +32,7 @@
"ext-zlib": ">=1.2.11", "ext-zlib": ">=1.2.11",
"composer-runtime-api": "^2.0", "composer-runtime-api": "^2.0",
"adhocore/json-comment": "~1.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-block-upgrade-schema": "~5.0.0+bedrock-1.21.40",
"pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50", "pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50",
"pocketmine/bedrock-item-upgrade-schema": "~1.14.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/callback-validator": "^1.0.2",
"pocketmine/color": "^0.3.0", "pocketmine/color": "^0.3.0",
"pocketmine/errorhandler": "^0.7.0", "pocketmine/errorhandler": "^0.7.0",
"pocketmine/locale-data": "~2.19.0", "pocketmine/locale-data": "~2.22.0",
"pocketmine/log": "^0.4.0", "pocketmine/log": "^0.4.0",
"pocketmine/math": "~1.0.0", "pocketmine/math": "~1.0.0",
"pocketmine/nbt": "~1.0.0", "pocketmine/nbt": "~1.0.0",

119
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "26d10b9381ab4e19684ca0b7a5a11a42", "content-hash": "732102eca72dc1d29e7b67dfbce07653",
"packages": [ "packages": [
{ {
"name": "adhocore/json-comment", "name": "adhocore/json-comment",
@ -125,6 +125,57 @@
], ],
"time": "2023-11-29T23:19:16+00:00" "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", "name": "pocketmine/bedrock-block-upgrade-schema",
"version": "5.0.0", "version": "5.0.0",
@ -420,16 +471,16 @@
}, },
{ {
"name": "pocketmine/locale-data", "name": "pocketmine/locale-data",
"version": "2.19.6", "version": "2.22.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/Language.git", "url": "https://github.com/pmmp/Language.git",
"reference": "93e473e20e7f4515ecf45c5ef0f9155b9247a86e" "reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/93e473e20e7f4515ecf45c5ef0f9155b9247a86e", "url": "https://api.github.com/repos/pmmp/Language/zipball/aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
"reference": "93e473e20e7f4515ecf45c5ef0f9155b9247a86e", "reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
"shasum": "" "shasum": ""
}, },
"type": "library", "type": "library",
@ -437,9 +488,9 @@
"description": "Language resources used by PocketMine-MP", "description": "Language resources used by PocketMine-MP",
"support": { "support": {
"issues": "https://github.com/pmmp/Language/issues", "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", "name": "pocketmine/log",
@ -565,60 +616,6 @@
}, },
"time": "2023-07-14T13:01:49+00:00" "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", "name": "pocketmine/raklib",
"version": "1.1.1", "version": "1.1.1",

View File

@ -89,6 +89,8 @@ use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver; use pocketmine\promise\PromiseResolver;
use pocketmine\resourcepacks\ResourcePackManager; use pocketmine\resourcepacks\ResourcePackManager;
use pocketmine\scheduler\AsyncPool; use pocketmine\scheduler\AsyncPool;
use pocketmine\scheduler\TimingsCollectionTask;
use pocketmine\scheduler\TimingsControlTask;
use pocketmine\snooze\SleeperHandler; use pocketmine\snooze\SleeperHandler;
use pocketmine\stats\SendUsageTask; use pocketmine\stats\SendUsageTask;
use pocketmine\thread\log\AttachableThreadSafeLogger; use pocketmine\thread\log\AttachableThreadSafeLogger;
@ -894,7 +896,36 @@ class Server{
$poolSize = max(1, (int) $poolSize); $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 = 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; $netCompressionThreshold = -1;
if($this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256) >= 0){ if($this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256) >= 0){
@ -968,9 +999,6 @@ class Server{
))); )));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_license($this->getName()))); $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(); DefaultPermissions::registerCorePermissions();
$this->commandMap = new SimpleCommandMap($this); $this->commandMap = new SimpleCommandMap($this);

View File

@ -31,8 +31,8 @@ use function str_repeat;
final class VersionInfo{ final class VersionInfo{
public const NAME = "PocketMine-MP"; public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.22.1"; public const BASE_VERSION = "5.23.0";
public const IS_DEVELOPMENT_BUILD = true; public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_CHANNEL = "stable"; public const BUILD_CHANNEL = "stable";
/** /**

View File

@ -35,10 +35,10 @@ use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\BlockTransaction; use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\AnvilFallSound; use pocketmine\world\sound\AnvilFallSound;
use pocketmine\world\sound\Sound; use pocketmine\world\sound\Sound;
use function lcg_value;
use function round; use function round;
class Anvil extends Transparent implements Fallable{ class Anvil extends Transparent implements Fallable{
@ -97,7 +97,7 @@ class Anvil extends Transparent implements Fallable{
} }
public function onHitGround(FallingBlock $blockEntity) : bool{ 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){ if($this->damage !== self::VERY_DAMAGED){
$this->damage = $this->damage + 1; $this->damage = $this->damage + 1;
}else{ }else{

View File

@ -69,6 +69,10 @@ class Campfire extends Transparent{
private const UPDATE_INTERVAL_TICKS = 10; 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; protected CampfireInventory $inventory;
/** /**
@ -129,6 +133,10 @@ class Campfire extends Transparent{
return [AxisAlignedBB::one()->trim(Facing::UP, 9 / 16)]; 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{ public function getInventory() : CampfireInventory{
return $this->inventory; return $this->inventory;
} }

View File

@ -31,8 +31,8 @@ use pocketmine\event\entity\EntityTrampleFarmlandEvent;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB; use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\utils\Utils;
use function intdiv; use function intdiv;
use function lcg_value;
class Farmland extends Transparent{ class Farmland extends Transparent{
public const MAX_WETNESS = 7; public const MAX_WETNESS = 7;
@ -148,7 +148,7 @@ class Farmland extends Transparent{
} }
public function onEntityLand(Entity $entity) : ?float{ 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 = new EntityTrampleFarmlandEvent($entity, $this);
$ev->call(); $ev->call();
if(!$ev->isCancelled()){ if(!$ev->isCancelled()){

View File

@ -24,60 +24,20 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\MultiAnySupportTrait;
use pocketmine\block\utils\SupportType; use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Fertilizer; use pocketmine\item\Fertilizer;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB; use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\World; use pocketmine\world\World;
use function array_key_first;
use function count; use function count;
use function shuffle; use function shuffle;
class GlowLichen extends Transparent{ class GlowLichen extends Transparent{
use MultiAnySupportTrait;
/** @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;
}
public function getLightLevel() : int{ public function getLightLevel() : int{
return 7; return 7;
@ -102,39 +62,11 @@ class GlowLichen extends Transparent{
return true; 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 : []; * @return int[]
$availableFaces = $this->getAvailableFaces(); */
protected function getInitialPlaceFaces(Block $blockReplace) : array{
if(count($availableFaces) === 0){ return $blockReplace instanceof GlowLichen ? $blockReplace->faces : [];
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);
}
}
} }
private function getSpreadBlock(Block $replace, int $spreadFace) : ?Block{ private function getSpreadBlock(Block $replace, int $spreadFace) : ?Block{
@ -261,17 +193,4 @@ class GlowLichen extends Transparent{
public function getFlammability() : int{ public function getFlammability() : int{
return 100; 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;
}
} }

View File

@ -31,13 +31,13 @@ use pocketmine\item\Item;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\BlockTransaction; use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\ItemFrameAddItemSound; use pocketmine\world\sound\ItemFrameAddItemSound;
use pocketmine\world\sound\ItemFrameRemoveItemSound; use pocketmine\world\sound\ItemFrameRemoveItemSound;
use pocketmine\world\sound\ItemFrameRotateItemSound; use pocketmine\world\sound\ItemFrameRotateItemSound;
use function is_infinite; use function is_infinite;
use function is_nan; use function is_nan;
use function lcg_value;
class ItemFrame extends Flowable{ class ItemFrame extends Flowable{
use AnyFacingTrait; use AnyFacingTrait;
@ -154,7 +154,7 @@ class ItemFrame extends Flowable{
return false; return false;
} }
$world = $this->position->getWorld(); $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->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
$world->addSound($this->position, new ItemFrameRemoveItemSound()); $world->addSound($this->position, new ItemFrameRemoveItemSound());
} }
@ -185,7 +185,7 @@ class ItemFrame extends Flowable{
public function getDropsForCompatibleTool(Item $item) : array{ public function getDropsForCompatibleTool(Item $item) : array{
$drops = parent::getDropsForCompatibleTool($item); $drops = parent::getDropsForCompatibleTool($item);
if($this->framedItem !== null && lcg_value() <= $this->itemDropChance){ if($this->framedItem !== null && Utils::getRandomFloat() <= $this->itemDropChance){
$drops[] = clone $this->framedItem; $drops[] = clone $this->framedItem;
} }

View File

@ -33,9 +33,9 @@ use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB; use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\utils\Utils;
use pocketmine\world\sound\FizzSound; use pocketmine\world\sound\FizzSound;
use pocketmine\world\sound\Sound; use pocketmine\world\sound\Sound;
use function lcg_value;
abstract class Liquid extends Transparent{ abstract class Liquid extends Transparent{
public const MAX_DECAY = 7; public const MAX_DECAY = 7;
@ -368,7 +368,7 @@ abstract class Liquid extends Transparent{
protected function liquidCollide(Block $cause, Block $result) : bool{ protected function liquidCollide(Block $cause, Block $result) : bool{
if(BlockEventHelper::form($this, $result, $cause)){ 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; return true;
} }

View File

@ -39,7 +39,7 @@ class Magma extends Opaque{
} }
public function onEntityInside(Entity $entity) : 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); $ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1);
$entity->attack($ev); $entity->attack($ev);
} }

View File

@ -36,7 +36,9 @@ use pocketmine\world\Position;
class Sugarcane extends Flowable{ class Sugarcane extends Flowable{
use AgeableTrait; use AgeableTrait;
use StaticSupportTrait; use StaticSupportTrait {
onNearbyBlockChange as onSupportBlockChange;
}
public const MAX_AGE = 15; public const MAX_AGE = 15;
@ -97,7 +99,13 @@ class Sugarcane extends Flowable{
} }
public function onRandomTick() : void{ 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){ if($this->age === self::MAX_AGE){
$this->grow($this->position); $this->grow($this->position);
}else{ }else{
@ -123,4 +131,23 @@ class Sugarcane extends Flowable{
return false; 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();
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -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,
});
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -64,6 +64,7 @@ use pocketmine\command\defaults\TransferServerCommand;
use pocketmine\command\defaults\VanillaCommand; use pocketmine\command\defaults\VanillaCommand;
use pocketmine\command\defaults\VersionCommand; use pocketmine\command\defaults\VersionCommand;
use pocketmine\command\defaults\WhitelistCommand; use pocketmine\command\defaults\WhitelistCommand;
use pocketmine\command\defaults\XpCommand;
use pocketmine\command\utils\CommandStringHelper; use pocketmine\command\utils\CommandStringHelper;
use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\KnownTranslationFactory;
@ -132,7 +133,8 @@ class SimpleCommandMap implements CommandMap{
new TitleCommand(), new TitleCommand(),
new TransferServerCommand(), new TransferServerCommand(),
new VersionCommand(), new VersionCommand(),
new WhitelistCommand() new WhitelistCommand(),
new XpCommand(),
]); ]);
} }

View File

@ -26,28 +26,28 @@ namespace pocketmine\command\defaults;
use pocketmine\command\Command; use pocketmine\command\Command;
use pocketmine\command\CommandSender; use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames; use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\scheduler\BulkCurlTask; use pocketmine\scheduler\BulkCurlTask;
use pocketmine\scheduler\BulkCurlTaskOperation; use pocketmine\scheduler\BulkCurlTaskOperation;
use pocketmine\timings\TimingsHandler; use pocketmine\timings\TimingsHandler;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\InternetException; use pocketmine\utils\InternetException;
use pocketmine\utils\InternetRequestResult; use pocketmine\utils\InternetRequestResult;
use pocketmine\utils\Utils;
use pocketmine\YmlServerProperties; use pocketmine\YmlServerProperties;
use Symfony\Component\Filesystem\Path; use Symfony\Component\Filesystem\Path;
use function count; use function count;
use function fclose; use function fclose;
use function file_exists; use function file_exists;
use function fopen; use function fopen;
use function fseek;
use function fwrite; use function fwrite;
use function http_build_query; use function http_build_query;
use function implode;
use function is_array; use function is_array;
use function json_decode; use function json_decode;
use function mkdir; use function mkdir;
use function stream_get_contents;
use function strtolower; use function strtolower;
use const CURLOPT_AUTOREFERER; use const CURLOPT_AUTOREFERER;
use const CURLOPT_FOLLOWLOCATION; use const CURLOPT_FOLLOWLOCATION;
@ -101,10 +101,24 @@ class TimingsCommand extends VanillaCommand{
TimingsHandler::reload(); TimingsHandler::reload();
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset()); Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset());
}elseif($mode === "merged" || $mode === "report" || $paste){ }elseif($mode === "merged" || $mode === "report" || $paste){
$timings = ""; $timingsPromise = TimingsHandler::requestPrintTimings();
if($paste){ Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_collect());
$fileTimings = Utils::assumeNotFalse(fopen("php://temp", "r+b"), "Opening php://temp should never fail"); $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{ }else{
throw new InvalidCommandSyntaxException();
}
return true;
}
/**
* @param string[] $lines
* @phpstan-param list<string> $lines
*/
private function createReportFile(array $lines, CommandSender $sender) : void{
$index = 0; $index = 0;
$timingFolder = Path::join($sender->getServer()->getDataPath(), "timings"); $timingFolder = Path::join($sender->getServer()->getDataPath(), "timings");
@ -116,20 +130,24 @@ class TimingsCommand extends VanillaCommand{
$timings = Path::join($timingFolder, "timings" . (++$index) . ".txt"); $timings = Path::join($timingFolder, "timings" . (++$index) . ".txt");
} }
$fileTimings = fopen($timings, "a+b"); $fileTimings = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($timings, "a+b"));
}
$lines = TimingsHandler::printTimings();
foreach($lines as $line){ foreach($lines as $line){
fwrite($fileTimings, $line . PHP_EOL); fwrite($fileTimings, $line . PHP_EOL);
} }
fclose($fileTimings);
if($paste){ Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
fseek($fileTimings, 0); }
/**
* @param string[] $lines
* @phpstan-param list<string> $lines
*/
private function uploadReport(array $lines, CommandSender $sender) : void{
$data = [ $data = [
"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(), "browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
"data" => $content = stream_get_contents($fileTimings) "data" => implode("\n", $lines)
]; ];
fclose($fileTimings);
$host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io"); $host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");
@ -169,14 +187,5 @@ class TimingsCommand extends VanillaCommand{
} }
} }
)); ));
}else{
fclose($fileTimings);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
}
}else{
throw new InvalidCommandSyntaxException();
}
return true;
} }
} }

View File

@ -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;
}
}

View File

@ -29,23 +29,21 @@ use pocketmine\utils\Process;
use function cli_set_process_title; use function cli_set_process_title;
use function count; use function count;
use function dirname; use function dirname;
use function feof;
use function fwrite; use function fwrite;
use function stream_socket_client; use function is_numeric;
use const PHP_EOL;
use const STDOUT;
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'; require dirname(__DIR__, 2) . '/vendor/autoload.php';
if(count($argv) !== 2){
die("Please provide a server to connect to");
}
@cli_set_process_title('PocketMine-MP Console Reader'); @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 */ /** @phpstan-var ThreadSafeArray<int, string> $channel */
$channel = new ThreadSafeArray(); $channel = new ThreadSafeArray();
@ -75,15 +73,15 @@ $thread = new class($channel) extends NativeThread{
}; };
$thread->start(NativeThread::INHERIT_NONE); $thread->start(NativeThread::INHERIT_NONE);
while(!feof($socket)){ while(true){
$line = $channel->synchronized(function() use ($channel) : ?string{ $line = $channel->synchronized(function() use ($channel) : ?string{
if(count($channel) === 0){ if(count($channel) === 0){
$channel->wait(1_000_000); $channel->wait(1_000_000);
} }
$line = $channel->shift(); return $channel->shift();
return $line;
}); });
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 //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 //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. //false even though the connection is actually broken. However, fwrite() will fail.

View File

@ -29,19 +29,16 @@ use Symfony\Component\Filesystem\Path;
use function base64_encode; use function base64_encode;
use function fgets; use function fgets;
use function fopen; use function fopen;
use function mt_rand;
use function preg_replace; use function preg_replace;
use function proc_close; use function proc_close;
use function proc_open; use function proc_open;
use function proc_terminate; use function proc_terminate;
use function rtrim;
use function sprintf; use function sprintf;
use function stream_select; 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 function trim;
use const PHP_BINARY; 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 * 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 @@ use const STREAM_SHUT_RDWR;
* communication. * communication.
*/ */
final class ConsoleReaderChildProcessDaemon{ final class ConsoleReaderChildProcessDaemon{
public const TOKEN_DELIMITER = ":";
public const TOKEN_HASH_ALGO = "xxh3";
private \PrefixedLogger $logger; private \PrefixedLogger $logger;
/** @var resource */ /** @var resource */
private $subprocess; private $subprocess;
/** @var resource */ /** @var resource */
private $socket; private $socket;
private int $commandTokenSeed;
public function __construct( public function __construct(
\Logger $logger \Logger $logger
){ ){
$this->logger = new \PrefixedLogger($logger, "Console Reader Daemon"); $this->logger = new \PrefixedLogger($logger, "Console Reader Daemon");
$this->commandTokenSeed = mt_rand();
$this->prepareSubprocess(); $this->prepareSubprocess();
} }
private function prepareSubprocess() : void{ 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 //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 :( //the path to avoid the problem. This is an abysmally shitty hack, but here we are :(
$sub = Utils::assumeNotFalse(proc_open( $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"), 2 => fopen("php://stderr", "w"),
], ],
$pipes $pipes
), "Something has gone horribly wrong"); ), "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->subprocess = $sub;
$this->socket = $client; $this->socket = $pipes[1];
} }
private function shutdownSubprocess() : void{ private function shutdownSubprocess() : void{
@ -104,7 +101,6 @@ final class ConsoleReaderChildProcessDaemon{
//the first place). //the first place).
proc_terminate($this->subprocess); proc_terminate($this->subprocess);
proc_close($this->subprocess); proc_close($this->subprocess);
stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
} }
public function readLine() : ?string{ public function readLine() : ?string{
@ -112,13 +108,27 @@ final class ConsoleReaderChildProcessDaemon{
$w = null; $w = null;
$e = null; $e = null;
if(stream_select($r, $w, $e, 0, 0) === 1){ if(stream_select($r, $w, $e, 0, 0) === 1){
$command = fgets($this->socket); $line = fgets($this->socket);
if($command === false){ if($line === false){
$this->logger->debug("Lost connection to subprocess, restarting (maybe the child process was killed from outside?)"); $this->logger->debug("Lost connection to subprocess, restarting (maybe the child process was killed from outside?)");
$this->shutdownSubprocess(); $this->shutdownSubprocess();
$this->prepareSubprocess(); $this->prepareSubprocess();
return null; 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("#\\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"); $command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");

View File

@ -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;
}
}

View File

@ -66,5 +66,7 @@ final class EnchantmentIdMap{
$this->register(EnchantmentIds::VANISHING, VanillaEnchantments::VANISHING()); $this->register(EnchantmentIds::VANISHING, VanillaEnchantments::VANISHING());
$this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK()); $this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK());
$this->register(EnchantmentIds::FROST_WALKER, VanillaEnchantments::FROST_WALKER());
} }
} }

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -31,6 +31,7 @@ use pocketmine\block\utils\DyeColor;
use pocketmine\block\VanillaBlocks as Blocks; use pocketmine\block\VanillaBlocks as Blocks;
use pocketmine\data\bedrock\CompoundTypeIds; use pocketmine\data\bedrock\CompoundTypeIds;
use pocketmine\data\bedrock\DyeColorIdMap; use pocketmine\data\bedrock\DyeColorIdMap;
use pocketmine\data\bedrock\GoatHornTypeIdMap;
use pocketmine\data\bedrock\item\ItemTypeNames as Ids; use pocketmine\data\bedrock\item\ItemTypeNames as Ids;
use pocketmine\data\bedrock\item\SavedItemData as Data; use pocketmine\data\bedrock\item\SavedItemData as Data;
use pocketmine\data\bedrock\MedicineTypeIdMap; use pocketmine\data\bedrock\MedicineTypeIdMap;
@ -38,6 +39,7 @@ use pocketmine\data\bedrock\PotionTypeIdMap;
use pocketmine\data\bedrock\SuspiciousStewTypeIdMap; use pocketmine\data\bedrock\SuspiciousStewTypeIdMap;
use pocketmine\item\Banner; use pocketmine\item\Banner;
use pocketmine\item\Dye; use pocketmine\item\Dye;
use pocketmine\item\GoatHorn;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\item\Medicine; use pocketmine\item\Medicine;
use pocketmine\item\Potion; use pocketmine\item\Potion;
@ -230,6 +232,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::EMERALD, Items::EMERALD()); $this->map1to1Item(Ids::EMERALD, Items::EMERALD());
$this->map1to1Item(Ids::ENCHANTED_BOOK, Items::ENCHANTED_BOOK()); $this->map1to1Item(Ids::ENCHANTED_BOOK, Items::ENCHANTED_BOOK());
$this->map1to1Item(Ids::ENCHANTED_GOLDEN_APPLE, Items::ENCHANTED_GOLDEN_APPLE()); $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::ENDER_PEARL, Items::ENDER_PEARL());
$this->map1to1Item(Ids::EXPERIENCE_BOTTLE, Items::EXPERIENCE_BOTTLE()); $this->map1to1Item(Ids::EXPERIENCE_BOTTLE, Items::EXPERIENCE_BOTTLE());
$this->map1to1Item(Ids::EYE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::EYE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE());
@ -263,6 +266,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::HONEY_BOTTLE, Items::HONEY_BOTTLE()); $this->map1to1Item(Ids::HONEY_BOTTLE, Items::HONEY_BOTTLE());
$this->map1to1Item(Ids::HONEYCOMB, Items::HONEYCOMB()); $this->map1to1Item(Ids::HONEYCOMB, Items::HONEYCOMB());
$this->map1to1Item(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE, Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE()); $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::INK_SAC, Items::INK_SAC());
$this->map1to1Item(Ids::IRON_AXE, Items::IRON_AXE()); $this->map1to1Item(Ids::IRON_AXE, Items::IRON_AXE());
$this->map1to1Item(Ids::IRON_BOOTS, Items::IRON_BOOTS()); $this->map1to1Item(Ids::IRON_BOOTS, Items::IRON_BOOTS());
@ -348,6 +352,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::RAW_COPPER, Items::RAW_COPPER()); $this->map1to1Item(Ids::RAW_COPPER, Items::RAW_COPPER());
$this->map1to1Item(Ids::RAW_GOLD, Items::RAW_GOLD()); $this->map1to1Item(Ids::RAW_GOLD, Items::RAW_GOLD());
$this->map1to1Item(Ids::RAW_IRON, Items::RAW_IRON()); $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::REDSTONE, Items::REDSTONE_DUST());
$this->map1to1Item(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH()); $this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH());
@ -483,6 +488,14 @@ final class ItemSerializerDeserializerRegistrar{
}, },
fn(Banner $item) => DyeColorIdMap::getInstance()->toInvertedId($item->getColor()) 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( $this->map1to1ItemWithMeta(
Ids::MEDICINE, Ids::MEDICINE,
Items::MEDICINE(), Items::MEDICINE(),

View File

@ -35,6 +35,7 @@ use pocketmine\event\entity\EntityMotionEvent;
use pocketmine\event\entity\EntityRegainHealthEvent; use pocketmine\event\entity\EntityRegainHealthEvent;
use pocketmine\event\entity\EntitySpawnEvent; use pocketmine\event\entity\EntitySpawnEvent;
use pocketmine\event\entity\EntityTeleportEvent; use pocketmine\event\entity\EntityTeleportEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB; use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector2; use pocketmine\math\Vector2;
@ -74,7 +75,6 @@ use function deg2rad;
use function floor; use function floor;
use function fmod; use function fmod;
use function get_class; use function get_class;
use function lcg_value;
use function sin; use function sin;
use function spl_object_id; use function spl_object_id;
use const M_PI_2; use const M_PI_2;
@ -909,7 +909,7 @@ abstract class Entity{
return false; return false;
} }
$force = lcg_value() * 0.2 + 0.1; $force = Utils::getRandomFloat() * 0.2 + 0.1;
$this->motion = match($direction){ $this->motion = match($direction){
Facing::WEST => $this->motion->withComponents(-$force, null, null), Facing::WEST => $this->motion->withComponents(-$force, null, null),
@ -1563,6 +1563,13 @@ abstract class Entity{
$this->hasSpawned = []; $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. * Flags the entity to be removed from the world on the next tick.
*/ */

View File

@ -32,6 +32,7 @@ use pocketmine\data\bedrock\PotionTypeIdMap;
use pocketmine\data\bedrock\PotionTypeIds; use pocketmine\data\bedrock\PotionTypeIds;
use pocketmine\data\SavedDataLoadingException; use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\EntityDataHelper as Helper; use pocketmine\entity\EntityDataHelper as Helper;
use pocketmine\entity\object\EndCrystal;
use pocketmine\entity\object\ExperienceOrb; use pocketmine\entity\object\ExperienceOrb;
use pocketmine\entity\object\FallingBlock; use pocketmine\entity\object\FallingBlock;
use pocketmine\entity\object\ItemEntity; use pocketmine\entity\object\ItemEntity;
@ -42,6 +43,7 @@ use pocketmine\entity\projectile\Arrow;
use pocketmine\entity\projectile\Egg; use pocketmine\entity\projectile\Egg;
use pocketmine\entity\projectile\EnderPearl; use pocketmine\entity\projectile\EnderPearl;
use pocketmine\entity\projectile\ExperienceBottle; use pocketmine\entity\projectile\ExperienceBottle;
use pocketmine\entity\projectile\IceBomb;
use pocketmine\entity\projectile\Snowball; use pocketmine\entity\projectile\Snowball;
use pocketmine\entity\projectile\SplashPotion; use pocketmine\entity\projectile\SplashPotion;
use pocketmine\item\Item; use pocketmine\item\Item;
@ -92,6 +94,10 @@ final class EntityFactory{
return new Egg(Helper::parseLocation($nbt, $world), null, $nbt); return new Egg(Helper::parseLocation($nbt, $world), null, $nbt);
}, ['Egg', 'minecraft:egg']); }, ['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{ $this->register(EnderPearl::class, function(World $world, CompoundTag $nbt) : EnderPearl{
return new EnderPearl(Helper::parseLocation($nbt, $world), null, $nbt); return new EnderPearl(Helper::parseLocation($nbt, $world), null, $nbt);
}, ['ThrownEnderpearl', 'minecraft:ender_pearl']); }, ['ThrownEnderpearl', 'minecraft:ender_pearl']);
@ -115,6 +121,10 @@ final class EntityFactory{
return new FallingBlock(Helper::parseLocation($nbt, $world), FallingBlock::parseBlockNBT(RuntimeBlockStateRegistry::getInstance(), $nbt), $nbt); return new FallingBlock(Helper::parseLocation($nbt, $world), FallingBlock::parseBlockNBT(RuntimeBlockStateRegistry::getInstance(), $nbt), $nbt);
}, ['FallingSand', 'minecraft:falling_block']); }, ['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{ $this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{
$itemTag = $nbt->getCompoundTag(ItemEntity::TAG_ITEM); $itemTag = $nbt->getCompoundTag(ItemEntity::TAG_ITEM);
if($itemTag === null){ if($itemTag === null){

View File

@ -34,6 +34,7 @@ interface FoodSource extends Consumable{
/** /**
* Returns whether a Human eating this FoodSource must have a non-full hunger bar. * 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; public function requiresHunger() : bool;
} }

View File

@ -68,6 +68,7 @@ use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket; use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\world\sound\TotemUseSound; use pocketmine\world\sound\TotemUseSound;
use pocketmine\world\World;
use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface; use Ramsey\Uuid\UuidInterface;
use function array_fill; use function array_fill;
@ -189,8 +190,16 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
return $this->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{ 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; return false;
} }

View File

@ -88,7 +88,8 @@ class HungerManager{
} }
/** /**
* 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{ public function isHungry() : bool{
return $this->getFood() < $this->getMaxFood(); return $this->getFood() < $this->getMaxFood();

View File

@ -25,6 +25,8 @@ namespace pocketmine\entity;
use pocketmine\block\Block; use pocketmine\block\Block;
use pocketmine\block\BlockTypeIds; use pocketmine\block\BlockTypeIds;
use pocketmine\block\VanillaBlocks;
use pocketmine\block\Water;
use pocketmine\data\bedrock\EffectIdMap; use pocketmine\data\bedrock\EffectIdMap;
use pocketmine\entity\animation\DeathAnimation; use pocketmine\entity\animation\DeathAnimation;
use pocketmine\entity\animation\HurtAnimation; use pocketmine\entity\animation\HurtAnimation;
@ -44,6 +46,7 @@ use pocketmine\item\Durable;
use pocketmine\item\enchantment\Enchantment; use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\math\VoxelRayTrace; use pocketmine\math\VoxelRayTrace;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
@ -58,18 +61,19 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\timings\Timings; use pocketmine\timings\Timings;
use pocketmine\utils\Binary; use pocketmine\utils\Binary;
use pocketmine\utils\Utils;
use pocketmine\world\sound\BurpSound; use pocketmine\world\sound\BurpSound;
use pocketmine\world\sound\EntityLandSound; use pocketmine\world\sound\EntityLandSound;
use pocketmine\world\sound\EntityLongFallSound; use pocketmine\world\sound\EntityLongFallSound;
use pocketmine\world\sound\EntityShortFallSound; use pocketmine\world\sound\EntityShortFallSound;
use pocketmine\world\sound\ItemBreakSound; use pocketmine\world\sound\ItemBreakSound;
use function abs;
use function array_shift; use function array_shift;
use function atan2; use function atan2;
use function ceil; use function ceil;
use function count; use function count;
use function floor; use function floor;
use function ksort; use function ksort;
use function lcg_value;
use function max; use function max;
use function min; use function min;
use function mt_getrandmax; use function mt_getrandmax;
@ -128,6 +132,8 @@ abstract class Living extends Entity{
protected bool $gliding = false; protected bool $gliding = false;
protected bool $swimming = false; protected bool $swimming = false;
private ?int $frostWalkerLevel = null;
protected function getInitialDragMultiplier() : float{ return 0.02; } protected function getInitialDragMultiplier() : float{ return 0.02; }
protected function getInitialGravity() : float{ return 0.08; } protected function getInitialGravity() : float{ return 0.08; }
@ -151,6 +157,14 @@ abstract class Living extends Entity{
$this->getViewers(), $this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobArmorChange($recipients, $this) 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(); $health = $this->getMaxHealth();
@ -490,7 +504,7 @@ abstract class Living extends Entity{
$helmet = $this->armorInventory->getHelmet(); $helmet = $this->armorInventory->getHelmet();
if($helmet instanceof Armor){ if($helmet instanceof Armor){
$finalDamage = $source->getFinalDamage(); $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); $this->armorInventory->setHelmet($helmet);
} }
} }
@ -687,6 +701,47 @@ abstract class Living extends Entity{
return $hasUpdate; 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. * Ticks the entity's air supply, consuming it when underwater and regenerating it when out of water.
*/ */
@ -697,7 +752,7 @@ abstract class Living extends Entity{
$this->setBreathing(false); $this->setBreathing(false);
if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 || if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 ||
lcg_value() <= (1 / ($respirationLevel + 1)) Utils::getRandomFloat() <= (1 / ($respirationLevel + 1))
){ ){
$ticks -= $tickDiff; $ticks -= $tickDiff;
if($ticks <= -20){ if($ticks <= -20){

View File

@ -26,6 +26,7 @@ namespace pocketmine\entity;
use pocketmine\entity\animation\SquidInkCloudAnimation; use pocketmine\entity\animation\SquidInkCloudAnimation;
use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems; use pocketmine\item\VanillaItems;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
@ -124,4 +125,8 @@ class Squid extends WaterAnimal{
VanillaItems::INK_SAC()->setCount(mt_rand(1, 3)) VanillaItems::INK_SAC()->setCount(mt_rand(1, 3))
]; ];
} }
public function getPickedItem() : ?Item{
return VanillaItems::SQUID_SPAWN_EGG();
}
} }

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\entity; namespace pocketmine\entity;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
@ -87,6 +89,10 @@ class Villager extends Living implements Ageable{
return $this->baby; return $this->baby;
} }
public function getPickedItem() : ?Item{
return VanillaItems::VILLAGER_SPAWN_EGG();
}
protected function syncNetworkData(EntityMetadataCollection $properties) : void{ protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties); parent::syncNetworkData($properties);
$properties->setGenericFlag(EntityMetadataFlags::BABY, $this->baby); $properties->setGenericFlag(EntityMetadataFlags::BABY, $this->baby);

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\entity; namespace pocketmine\entity;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems; use pocketmine\item\VanillaItems;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use function mt_rand; use function mt_rand;
@ -65,4 +66,8 @@ class Zombie extends Living{
//TODO: check for equipment and whether it's a baby //TODO: check for equipment and whether it's a baby
return 5; return 5;
} }
public function getPickedItem() : ?Item{
return VanillaItems::ZOMBIE_SPAWN_EGG();
}
} }

View File

@ -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()));
}
}

View File

@ -35,6 +35,7 @@ use pocketmine\entity\Location;
use pocketmine\event\entity\EntityBlockChangeEvent; use pocketmine\event\entity\EntityBlockChangeEvent;
use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
@ -194,6 +195,10 @@ class FallingBlock extends Entity{
return $nbt; return $nbt;
} }
public function getPickedItem() : ?Item{
return $this->block->asItem();
}
protected function syncNetworkData(EntityMetadataCollection $properties) : void{ protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties); parent::syncNetworkData($properties);

View File

@ -28,6 +28,7 @@ use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo; use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Location; use pocketmine\entity\Location;
use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems; use pocketmine\item\VanillaItems;
use pocketmine\math\AxisAlignedBB; use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing; use pocketmine\math\Facing;
@ -165,6 +166,10 @@ class Painting extends Entity{
)); ));
} }
public function getPickedItem() : ?Item{
return VanillaItems::PAINTING();
}
/** /**
* Returns the painting motive (which image is displayed on the painting) * Returns the painting motive (which image is displayed on the painting)
*/ */

View File

@ -23,11 +23,13 @@ declare(strict_types=1);
namespace pocketmine\entity\object; namespace pocketmine\entity\object;
use pocketmine\block\VanillaBlocks;
use pocketmine\entity\Entity; use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo; use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Explosive; use pocketmine\entity\Explosive;
use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\entity\EntityPreExplodeEvent; use pocketmine\event\entity\EntityPreExplodeEvent;
use pocketmine\item\Item;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
@ -127,6 +129,10 @@ class PrimedTNT extends Entity implements Explosive{
} }
} }
public function getPickedItem() : ?Item{
return VanillaBlocks::TNT()->setWorksUnderwater($this->worksUnderwater)->asItem();
}
protected function syncNetworkData(EntityMetadataCollection $properties) : void{ protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties); parent::syncNetworkData($properties);

View File

@ -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);
}
}
}
}
}
}

View File

@ -28,6 +28,7 @@ use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\Entity; use pocketmine\entity\Entity;
use pocketmine\entity\Living; use pocketmine\entity\Living;
use pocketmine\entity\Location; use pocketmine\entity\Location;
use pocketmine\entity\object\EndCrystal;
use pocketmine\event\entity\EntityCombustByEntityEvent; use pocketmine\event\entity\EntityCombustByEntityEvent;
use pocketmine\event\entity\EntityDamageByChildEntityEvent; use pocketmine\event\entity\EntityDamageByChildEntityEvent;
use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\entity\EntityDamageByEntityEvent;
@ -96,7 +97,7 @@ abstract class Projectile extends Entity{
} }
public function canCollideWith(Entity $entity) : bool{ 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{ public function canBeCollidedWith() : bool{

View File

@ -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;
}
}

View File

@ -42,6 +42,9 @@ class PlayerInteractEvent extends PlayerEvent implements Cancellable{
protected Vector3 $touchVector; protected Vector3 $touchVector;
protected bool $useItem = true;
protected bool $useBlock = true;
public function __construct( public function __construct(
Player $player, Player $player,
protected Item $item, protected Item $item,
@ -73,4 +76,28 @@ class PlayerInteractEvent extends PlayerEvent implements Cancellable{
public function getFace() : int{ public function getFace() : int{
return $this->blockFace; 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; }
} }

View File

@ -111,6 +111,9 @@ class InventoryTransaction{
if(!isset($this->actions[$hash = spl_object_id($action)])){ if(!isset($this->actions[$hash = spl_object_id($action)])){
$this->actions[$hash] = $action; $this->actions[$hash] = $action;
$action->onAddToTransaction($this); $action->onAddToTransaction($this);
if($action instanceof SlotChangeAction && !isset($this->inventories[$inventoryId = spl_object_id($action->getInventory())])){
$this->inventories[$inventoryId] = $action->getInventory();
}
}else{ }else{
throw new \InvalidArgumentException("Tried to add the same action to a transaction twice"); throw new \InvalidArgumentException("Tried to add the same action to a transaction twice");
} }
@ -129,16 +132,6 @@ class InventoryTransaction{
$this->actions = $actions; $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[] $needItems
* @param Item[] $haveItems * @param Item[] $haveItems

View File

@ -60,6 +60,7 @@ abstract class InventoryAction{
/** /**
* Called when the action is added to the specified InventoryTransaction. * Called when the action is added to the specified InventoryTransaction.
* @deprecated
*/ */
public function onAddToTransaction(InventoryTransaction $transaction) : void{ public function onAddToTransaction(InventoryTransaction $transaction) : void{

View File

@ -25,7 +25,6 @@ namespace pocketmine\inventory\transaction\action;
use pocketmine\inventory\Inventory; use pocketmine\inventory\Inventory;
use pocketmine\inventory\SlotValidatedInventory; use pocketmine\inventory\SlotValidatedInventory;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\player\Player; use pocketmine\player\Player;
@ -85,13 +84,6 @@ class SlotChangeAction extends InventoryAction{
} }
} }
/**
* 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. * Sets the item into the target inventory.
*/ */

View File

@ -33,7 +33,7 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\IntTag;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\Binary; use pocketmine\utils\Binary;
use function lcg_value; use pocketmine\utils\Utils;
use function mt_rand; use function mt_rand;
class Armor extends Durable{ class Armor extends Durable{
@ -129,7 +129,7 @@ class Armor extends Durable{
$chance = 1 / ($unbreakingLevel + 1); $chance = 1 / ($unbreakingLevel + 1);
for($i = 0; $i < $amount; ++$i){ 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++; $negated++;
} }
} }

View File

@ -25,7 +25,7 @@ namespace pocketmine\item;
use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use function lcg_value; use pocketmine\utils\Utils;
use function min; use function min;
abstract class Durable extends Item{ abstract class Durable extends Item{
@ -87,7 +87,7 @@ abstract class Durable extends Item{
$chance = 1 / ($unbreakingLevel + 1); $chance = 1 / ($unbreakingLevel + 1);
for($i = 0; $i < $amount; ++$i){ for($i = 0; $i < $amount; ++$i){
if(lcg_value() > $chance){ if(Utils::getRandomFloat() > $chance){
$negated++; $negated++;
} }
} }

59
src/item/EndCrystal.php Normal file
View File

@ -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;
}
}

View File

@ -44,6 +44,6 @@ abstract class Food extends Item implements FoodSourceItem{
} }
public function canStartUsingItem(Player $player) : bool{ public function canStartUsingItem(Player $player) : bool{
return !$this->requiresHunger() || $player->getHungerManager()->isHungry(); return !$this->requiresHunger() || $player->canEat();
} }
} }

71
src/item/GoatHorn.php Normal file
View File

@ -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;
}
}

36
src/item/GoatHornType.php Normal file
View File

@ -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;
}

48
src/item/IceBomb.php Normal file
View File

@ -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;
}
}

View File

@ -324,8 +324,12 @@ final class ItemTypeIds{
public const SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = 20285; public const SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = 20285;
public const PITCHER_POD = 20286; public const PITCHER_POD = 20286;
public const NAME_TAG = 20287; 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; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;

View File

@ -26,6 +26,7 @@ namespace pocketmine\item;
use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Living; use pocketmine\entity\Living;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\world\sound\BottleEmptySound;
class Potion extends Item implements ConsumableItem{ class Potion extends Item implements ConsumableItem{
@ -50,7 +51,7 @@ class Potion extends Item implements ConsumableItem{
} }
public function onConsume(Living $consumer) : void{ public function onConsume(Living $consumer) : void{
$consumer->broadcastSound(new BottleEmptySound());
} }
public function getAdditionalEffects() : array{ public function getAdditionalEffects() : array{

View File

@ -25,7 +25,7 @@ namespace pocketmine\item;
use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects; use pocketmine\entity\effect\VanillaEffects;
use function lcg_value; use pocketmine\utils\Utils;
class RottenFlesh extends Food{ class RottenFlesh extends Food{
@ -38,7 +38,7 @@ class RottenFlesh extends Food{
} }
public function getAdditionalEffects() : array{ public function getAdditionalEffects() : array{
if(lcg_value() <= 0.8){ if(Utils::getRandomFloat() <= 0.8){
return [ return [
new EffectInstance(VanillaEffects::HUNGER(), 600) new EffectInstance(VanillaEffects::HUNGER(), 600)
]; ];

View File

@ -27,15 +27,15 @@ use pocketmine\block\Block;
use pocketmine\entity\Entity; use pocketmine\entity\Entity;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\World; use pocketmine\world\World;
use function lcg_value;
abstract class SpawnEgg extends Item{ abstract class SpawnEgg extends Item{
abstract protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity; 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{ 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()){ if($this->hasCustomName()){
$entity->setNameTag($this->getCustomName()); $entity->setNameTag($this->getCustomName());

View File

@ -1184,6 +1184,13 @@ final class StringToItemParser extends StringToTParser{
$result->register($prefix("dye"), fn() => Items::DYE()->setColor($color)); $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){ foreach(SuspiciousStewType::cases() as $suspiciousStewType){
$prefix = fn(string $name) => strtolower($suspiciousStewType->name) . "_" . $name; $prefix = fn(string $name) => strtolower($suspiciousStewType->name) . "_" . $name;
@ -1323,6 +1330,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("enchanted_book", fn() => Items::ENCHANTED_BOOK()); $result->register("enchanted_book", fn() => Items::ENCHANTED_BOOK());
$result->register("enchanted_golden_apple", fn() => Items::ENCHANTED_GOLDEN_APPLE()); $result->register("enchanted_golden_apple", fn() => Items::ENCHANTED_GOLDEN_APPLE());
$result->register("enchanting_bottle", fn() => Items::EXPERIENCE_BOTTLE()); $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("ender_pearl", fn() => Items::ENDER_PEARL());
$result->register("experience_bottle", fn() => Items::EXPERIENCE_BOTTLE()); $result->register("experience_bottle", fn() => Items::EXPERIENCE_BOTTLE());
$result->register("eye_armor_trim_smithing_template", fn() => Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("eye_armor_trim_smithing_template", fn() => Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE());
@ -1341,6 +1349,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("glow_berries", fn() => Items::GLOW_BERRIES()); $result->register("glow_berries", fn() => Items::GLOW_BERRIES());
$result->register("glow_ink_sac", fn() => Items::GLOW_INK_SAC()); $result->register("glow_ink_sac", fn() => Items::GLOW_INK_SAC());
$result->register("glowstone_dust", fn() => Items::GLOWSTONE_DUST()); $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_axe", fn() => Items::GOLDEN_AXE());
$result->register("gold_boots", fn() => Items::GOLDEN_BOOTS()); $result->register("gold_boots", fn() => Items::GOLDEN_BOOTS());
$result->register("gold_chestplate", fn() => Items::GOLDEN_CHESTPLATE()); $result->register("gold_chestplate", fn() => Items::GOLDEN_CHESTPLATE());
@ -1369,6 +1378,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("honey_bottle", fn() => Items::HONEY_BOTTLE()); $result->register("honey_bottle", fn() => Items::HONEY_BOTTLE());
$result->register("host_armor_trim_smithing_template", fn() => Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("host_armor_trim_smithing_template", fn() => Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("honeycomb", fn() => Items::HONEYCOMB()); $result->register("honeycomb", fn() => Items::HONEYCOMB());
$result->register("ice_bomb", fn() => Items::ICE_BOMB());
$result->register("ink_sac", fn() => Items::INK_SAC()); $result->register("ink_sac", fn() => Items::INK_SAC());
$result->register("iron_axe", fn() => Items::IRON_AXE()); $result->register("iron_axe", fn() => Items::IRON_AXE());
$result->register("iron_boots", fn() => Items::IRON_BOOTS()); $result->register("iron_boots", fn() => Items::IRON_BOOTS());
@ -1471,6 +1481,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("record_strad", fn() => Items::RECORD_STRAD()); $result->register("record_strad", fn() => Items::RECORD_STRAD());
$result->register("record_wait", fn() => Items::RECORD_WAIT()); $result->register("record_wait", fn() => Items::RECORD_WAIT());
$result->register("record_ward", fn() => Items::RECORD_WARD()); $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", fn() => Items::REDSTONE_DUST());
$result->register("redstone_dust", 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()); $result->register("rib_armor_trim_smithing_template", fn() => Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());

View File

@ -33,11 +33,12 @@ use pocketmine\entity\Zombie;
use pocketmine\inventory\ArmorInventory; use pocketmine\inventory\ArmorInventory;
use pocketmine\item\enchantment\ItemEnchantmentTags as EnchantmentTags; use pocketmine\item\enchantment\ItemEnchantmentTags as EnchantmentTags;
use pocketmine\item\ItemIdentifier as IID; use pocketmine\item\ItemIdentifier as IID;
use pocketmine\item\ItemTypeIds as Ids;
use pocketmine\item\VanillaArmorMaterials as ArmorMaterials; use pocketmine\item\VanillaArmorMaterials as ArmorMaterials;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\utils\CloningRegistryTrait; use pocketmine\utils\CloningRegistryTrait;
use pocketmine\world\World; use pocketmine\world\World;
use function is_int;
use function mb_strtoupper;
use function strtolower; use function strtolower;
/** /**
@ -157,6 +158,7 @@ use function strtolower;
* @method static EnchantedBook ENCHANTED_BOOK() * @method static EnchantedBook ENCHANTED_BOOK()
* @method static GoldenAppleEnchanted ENCHANTED_GOLDEN_APPLE() * @method static GoldenAppleEnchanted ENCHANTED_GOLDEN_APPLE()
* @method static EnderPearl ENDER_PEARL() * @method static EnderPearl ENDER_PEARL()
* @method static EndCrystal END_CRYSTAL()
* @method static ExperienceBottle EXPERIENCE_BOTTLE() * @method static ExperienceBottle EXPERIENCE_BOTTLE()
* @method static Item EYE_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Item EYE_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Item FEATHER() * @method static Item FEATHER()
@ -171,6 +173,7 @@ use function strtolower;
* @method static Item GLOWSTONE_DUST() * @method static Item GLOWSTONE_DUST()
* @method static GlowBerries GLOW_BERRIES() * @method static GlowBerries GLOW_BERRIES()
* @method static Item GLOW_INK_SAC() * @method static Item GLOW_INK_SAC()
* @method static GoatHorn GOAT_HORN()
* @method static GoldenApple GOLDEN_APPLE() * @method static GoldenApple GOLDEN_APPLE()
* @method static Axe GOLDEN_AXE() * @method static Axe GOLDEN_AXE()
* @method static Armor GOLDEN_BOOTS() * @method static Armor GOLDEN_BOOTS()
@ -189,6 +192,7 @@ use function strtolower;
* @method static Item HONEYCOMB() * @method static Item HONEYCOMB()
* @method static HoneyBottle HONEY_BOTTLE() * @method static HoneyBottle HONEY_BOTTLE()
* @method static Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static IceBomb ICE_BOMB()
* @method static Item INK_SAC() * @method static Item INK_SAC()
* @method static Axe IRON_AXE() * @method static Axe IRON_AXE()
* @method static Armor IRON_BOOTS() * @method static Armor IRON_BOOTS()
@ -280,6 +284,7 @@ use function strtolower;
* @method static Record RECORD_STRAD() * @method static Record RECORD_STRAD()
* @method static Record RECORD_WAIT() * @method static Record RECORD_WAIT()
* @method static Record RECORD_WARD() * @method static Record RECORD_WARD()
* @method static Item RECOVERY_COMPASS()
* @method static Redstone REDSTONE_DUST() * @method static Redstone REDSTONE_DUST()
* @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static RottenFlesh ROTTEN_FLESH() * @method static RottenFlesh ROTTEN_FLESH()
@ -339,8 +344,29 @@ final class VanillaItems{
//NOOP //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); self::_registryRegister($name, $item);
return $item;
} }
/** /**
@ -360,242 +386,240 @@ final class VanillaItems{
self::registerTierToolItems(); self::registerTierToolItems();
self::registerSmithingTemplates(); 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("acacia_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::ACACIA_SIGN(), Blocks::ACACIA_WALL_SIGN()));
self::register("amethyst_shard", new Item(new IID(Ids::AMETHYST_SHARD), "Amethyst Shard")); self::register("amethyst_shard", fn(IID $id) => new Item($id, "Amethyst Shard"));
self::register("apple", new Apple(new IID(Ids::APPLE), "Apple")); self::register("apple", fn(IID $id) => new Apple($id, "Apple"));
self::register("arrow", new Arrow(new IID(Ids::ARROW), "Arrow")); self::register("arrow", fn(IID $id) => new Arrow($id, "Arrow"));
self::register("baked_potato", new BakedPotato(new IID(Ids::BAKED_POTATO), "Baked Potato")); self::register("baked_potato", fn(IID $id) => new BakedPotato($id, "Baked Potato"));
self::register("bamboo", new Bamboo(new IID(Ids::BAMBOO), "Bamboo")); self::register("bamboo", fn(IID $id) => new Bamboo($id, "Bamboo"));
self::register("banner", new Banner(new IID(Ids::BANNER), Blocks::BANNER(), Blocks::WALL_BANNER())); self::register("banner", fn(IID $id) => new Banner($id, Blocks::BANNER(), Blocks::WALL_BANNER()));
self::register("beetroot", new Beetroot(new IID(Ids::BEETROOT), "Beetroot")); self::register("beetroot", fn(IID $id) => new Beetroot($id, "Beetroot"));
self::register("beetroot_seeds", new BeetrootSeeds(new IID(Ids::BEETROOT_SEEDS), "Beetroot Seeds")); self::register("beetroot_seeds", fn(IID $id) => new BeetrootSeeds($id, "Beetroot Seeds"));
self::register("beetroot_soup", new BeetrootSoup(new IID(Ids::BEETROOT_SOUP), "Beetroot Soup")); self::register("beetroot_soup", fn(IID $id) => new BeetrootSoup($id, "Beetroot Soup"));
self::register("birch_sign", new ItemBlockWallOrFloor(new IID(Ids::BIRCH_SIGN), Blocks::BIRCH_SIGN(), Blocks::BIRCH_WALL_SIGN())); self::register("birch_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::BIRCH_SIGN(), Blocks::BIRCH_WALL_SIGN()));
self::register("blaze_powder", new Item(new IID(Ids::BLAZE_POWDER), "Blaze Powder")); self::register("blaze_powder", fn(IID $id) => new Item($id, "Blaze Powder"));
self::register("blaze_rod", new BlazeRod(new IID(Ids::BLAZE_ROD), "Blaze Rod")); self::register("blaze_rod", fn(IID $id) => new BlazeRod($id, "Blaze Rod"));
self::register("bleach", new Item(new IID(Ids::BLEACH), "Bleach")); self::register("bleach", fn(IID $id) => new Item($id, "Bleach"));
self::register("bone", new Item(new IID(Ids::BONE), "Bone")); self::register("bone", fn(IID $id) => new Item($id, "Bone"));
self::register("bone_meal", new Fertilizer(new IID(Ids::BONE_MEAL), "Bone Meal")); self::register("bone_meal", fn(IID $id) => new Fertilizer($id, "Bone Meal"));
self::register("book", new Book(new IID(Ids::BOOK), "Book", [EnchantmentTags::ALL])); self::register("book", fn(IID $id) => new Book($id, "Book", [EnchantmentTags::ALL]));
self::register("bow", new Bow(new IID(Ids::BOW), "Bow", [EnchantmentTags::BOW])); self::register("bow", fn(IID $id) => new Bow($id, "Bow", [EnchantmentTags::BOW]));
self::register("bowl", new Bowl(new IID(Ids::BOWL), "Bowl")); self::register("bowl", fn(IID $id) => new Bowl($id, "Bowl"));
self::register("bread", new Bread(new IID(Ids::BREAD), "Bread")); self::register("bread", fn(IID $id) => new Bread($id, "Bread"));
self::register("brick", new Item(new IID(Ids::BRICK), "Brick")); self::register("brick", fn(IID $id) => new Item($id, "Brick"));
self::register("bucket", new Bucket(new IID(Ids::BUCKET), "Bucket")); self::register("bucket", fn(IID $id) => new Bucket($id, "Bucket"));
self::register("carrot", new Carrot(new IID(Ids::CARROT), "Carrot")); self::register("carrot", fn(IID $id) => new Carrot($id, "Carrot"));
self::register("charcoal", new Coal(new IID(Ids::CHARCOAL), "Charcoal")); self::register("charcoal", fn(IID $id) => new Coal($id, "Charcoal"));
self::register("cherry_sign", new ItemBlockWallOrFloor(new IID(Ids::CHERRY_SIGN), Blocks::CHERRY_SIGN(), Blocks::CHERRY_WALL_SIGN())); self::register("cherry_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, 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_aluminium_oxide", fn(IID $id) => new Item($id, "Aluminium Oxide"));
self::register("chemical_ammonia", new Item(new IID(Ids::CHEMICAL_AMMONIA), "Ammonia")); self::register("chemical_ammonia", fn(IID $id) => new Item($id, "Ammonia"));
self::register("chemical_barium_sulphate", new Item(new IID(Ids::CHEMICAL_BARIUM_SULPHATE), "Barium Sulphate")); self::register("chemical_barium_sulphate", fn(IID $id) => new Item($id, "Barium Sulphate"));
self::register("chemical_benzene", new Item(new IID(Ids::CHEMICAL_BENZENE), "Benzene")); self::register("chemical_benzene", fn(IID $id) => new Item($id, "Benzene"));
self::register("chemical_boron_trioxide", new Item(new IID(Ids::CHEMICAL_BORON_TRIOXIDE), "Boron Trioxide")); self::register("chemical_boron_trioxide", fn(IID $id) => new Item($id, "Boron Trioxide"));
self::register("chemical_calcium_bromide", new Item(new IID(Ids::CHEMICAL_CALCIUM_BROMIDE), "Calcium Bromide")); self::register("chemical_calcium_bromide", fn(IID $id) => new Item($id, "Calcium Bromide"));
self::register("chemical_calcium_chloride", new Item(new IID(Ids::CHEMICAL_CALCIUM_CHLORIDE), "Calcium Chloride")); self::register("chemical_calcium_chloride", fn(IID $id) => new Item($id, "Calcium Chloride"));
self::register("chemical_cerium_chloride", new Item(new IID(Ids::CHEMICAL_CERIUM_CHLORIDE), "Cerium Chloride")); self::register("chemical_cerium_chloride", fn(IID $id) => new Item($id, "Cerium Chloride"));
self::register("chemical_charcoal", new Item(new IID(Ids::CHEMICAL_CHARCOAL), "Charcoal")); self::register("chemical_charcoal", fn(IID $id) => new Item($id, "Charcoal"));
self::register("chemical_crude_oil", new Item(new IID(Ids::CHEMICAL_CRUDE_OIL), "Crude Oil")); self::register("chemical_crude_oil", fn(IID $id) => new Item($id, "Crude Oil"));
self::register("chemical_glue", new Item(new IID(Ids::CHEMICAL_GLUE), "Glue")); self::register("chemical_glue", fn(IID $id) => new Item($id, "Glue"));
self::register("chemical_hydrogen_peroxide", new Item(new IID(Ids::CHEMICAL_HYDROGEN_PEROXIDE), "Hydrogen Peroxide")); self::register("chemical_hydrogen_peroxide", fn(IID $id) => new Item($id, "Hydrogen Peroxide"));
self::register("chemical_hypochlorite", new Item(new IID(Ids::CHEMICAL_HYPOCHLORITE), "Hypochlorite")); self::register("chemical_hypochlorite", fn(IID $id) => new Item($id, "Hypochlorite"));
self::register("chemical_ink", new Item(new IID(Ids::CHEMICAL_INK), "Ink")); self::register("chemical_ink", fn(IID $id) => new Item($id, "Ink"));
self::register("chemical_iron_sulphide", new Item(new IID(Ids::CHEMICAL_IRON_SULPHIDE), "Iron Sulphide")); self::register("chemical_iron_sulphide", fn(IID $id) => new Item($id, "Iron Sulphide"));
self::register("chemical_latex", new Item(new IID(Ids::CHEMICAL_LATEX), "Latex")); self::register("chemical_latex", fn(IID $id) => new Item($id, "Latex"));
self::register("chemical_lithium_hydride", new Item(new IID(Ids::CHEMICAL_LITHIUM_HYDRIDE), "Lithium Hydride")); self::register("chemical_lithium_hydride", fn(IID $id) => new Item($id, "Lithium Hydride"));
self::register("chemical_luminol", new Item(new IID(Ids::CHEMICAL_LUMINOL), "Luminol")); self::register("chemical_luminol", fn(IID $id) => new Item($id, "Luminol"));
self::register("chemical_magnesium_nitrate", new Item(new IID(Ids::CHEMICAL_MAGNESIUM_NITRATE), "Magnesium Nitrate")); self::register("chemical_magnesium_nitrate", fn(IID $id) => new Item($id, "Magnesium Nitrate"));
self::register("chemical_magnesium_oxide", new Item(new IID(Ids::CHEMICAL_MAGNESIUM_OXIDE), "Magnesium Oxide")); self::register("chemical_magnesium_oxide", fn(IID $id) => new Item($id, "Magnesium Oxide"));
self::register("chemical_magnesium_salts", new Item(new IID(Ids::CHEMICAL_MAGNESIUM_SALTS), "Magnesium Salts")); self::register("chemical_magnesium_salts", fn(IID $id) => new Item($id, "Magnesium Salts"));
self::register("chemical_mercuric_chloride", new Item(new IID(Ids::CHEMICAL_MERCURIC_CHLORIDE), "Mercuric Chloride")); self::register("chemical_mercuric_chloride", fn(IID $id) => new Item($id, "Mercuric Chloride"));
self::register("chemical_polyethylene", new Item(new IID(Ids::CHEMICAL_POLYETHYLENE), "Polyethylene")); self::register("chemical_polyethylene", fn(IID $id) => new Item($id, "Polyethylene"));
self::register("chemical_potassium_chloride", new Item(new IID(Ids::CHEMICAL_POTASSIUM_CHLORIDE), "Potassium Chloride")); self::register("chemical_potassium_chloride", fn(IID $id) => new Item($id, "Potassium Chloride"));
self::register("chemical_potassium_iodide", new Item(new IID(Ids::CHEMICAL_POTASSIUM_IODIDE), "Potassium Iodide")); self::register("chemical_potassium_iodide", fn(IID $id) => new Item($id, "Potassium Iodide"));
self::register("chemical_rubbish", new Item(new IID(Ids::CHEMICAL_RUBBISH), "Rubbish")); self::register("chemical_rubbish", fn(IID $id) => new Item($id, "Rubbish"));
self::register("chemical_salt", new Item(new IID(Ids::CHEMICAL_SALT), "Salt")); self::register("chemical_salt", fn(IID $id) => new Item($id, "Salt"));
self::register("chemical_soap", new Item(new IID(Ids::CHEMICAL_SOAP), "Soap")); self::register("chemical_soap", fn(IID $id) => new Item($id, "Soap"));
self::register("chemical_sodium_acetate", new Item(new IID(Ids::CHEMICAL_SODIUM_ACETATE), "Sodium Acetate")); self::register("chemical_sodium_acetate", fn(IID $id) => new Item($id, "Sodium Acetate"));
self::register("chemical_sodium_fluoride", new Item(new IID(Ids::CHEMICAL_SODIUM_FLUORIDE), "Sodium Fluoride")); self::register("chemical_sodium_fluoride", fn(IID $id) => new Item($id, "Sodium Fluoride"));
self::register("chemical_sodium_hydride", new Item(new IID(Ids::CHEMICAL_SODIUM_HYDRIDE), "Sodium Hydride")); self::register("chemical_sodium_hydride", fn(IID $id) => new Item($id, "Sodium Hydride"));
self::register("chemical_sodium_hydroxide", new Item(new IID(Ids::CHEMICAL_SODIUM_HYDROXIDE), "Sodium Hydroxide")); self::register("chemical_sodium_hydroxide", fn(IID $id) => new Item($id, "Sodium Hydroxide"));
self::register("chemical_sodium_hypochlorite", new Item(new IID(Ids::CHEMICAL_SODIUM_HYPOCHLORITE), "Sodium Hypochlorite")); self::register("chemical_sodium_hypochlorite", fn(IID $id) => new Item($id, "Sodium Hypochlorite"));
self::register("chemical_sodium_oxide", new Item(new IID(Ids::CHEMICAL_SODIUM_OXIDE), "Sodium Oxide")); self::register("chemical_sodium_oxide", fn(IID $id) => new Item($id, "Sodium Oxide"));
self::register("chemical_sugar", new Item(new IID(Ids::CHEMICAL_SUGAR), "Sugar")); self::register("chemical_sugar", fn(IID $id) => new Item($id, "Sugar"));
self::register("chemical_sulphate", new Item(new IID(Ids::CHEMICAL_SULPHATE), "Sulphate")); self::register("chemical_sulphate", fn(IID $id) => new Item($id, "Sulphate"));
self::register("chemical_tungsten_chloride", new Item(new IID(Ids::CHEMICAL_TUNGSTEN_CHLORIDE), "Tungsten Chloride")); self::register("chemical_tungsten_chloride", fn(IID $id) => new Item($id, "Tungsten Chloride"));
self::register("chemical_water", new Item(new IID(Ids::CHEMICAL_WATER), "Water")); self::register("chemical_water", fn(IID $id) => new Item($id, "Water"));
self::register("chorus_fruit", new ChorusFruit(new IID(Ids::CHORUS_FRUIT), "Chorus Fruit")); self::register("chorus_fruit", fn(IID $id) => new ChorusFruit($id, "Chorus Fruit"));
self::register("clay", new Item(new IID(Ids::CLAY), "Clay")); self::register("clay", fn(IID $id) => new Item($id, "Clay"));
self::register("clock", new Clock(new IID(Ids::CLOCK), "Clock")); self::register("clock", fn(IID $id) => new Clock($id, "Clock"));
self::register("clownfish", new Clownfish(new IID(Ids::CLOWNFISH), "Clownfish")); self::register("clownfish", fn(IID $id) => new Clownfish($id, "Clownfish"));
self::register("coal", new Coal(new IID(Ids::COAL), "Coal")); self::register("coal", fn(IID $id) => new Coal($id, "Coal"));
self::register("cocoa_beans", new CocoaBeans(new IID(Ids::COCOA_BEANS), "Cocoa Beans")); self::register("cocoa_beans", fn(IID $id) => new CocoaBeans($id, "Cocoa Beans"));
self::register("compass", new Compass(new IID(Ids::COMPASS), "Compass", [EnchantmentTags::COMPASS])); self::register("compass", fn(IID $id) => new Compass($id, "Compass", [EnchantmentTags::COMPASS]));
self::register("cooked_chicken", new CookedChicken(new IID(Ids::COOKED_CHICKEN), "Cooked Chicken")); self::register("cooked_chicken", fn(IID $id) => new CookedChicken($id, "Cooked Chicken"));
self::register("cooked_fish", new CookedFish(new IID(Ids::COOKED_FISH), "Cooked Fish")); self::register("cooked_fish", fn(IID $id) => new CookedFish($id, "Cooked Fish"));
self::register("cooked_mutton", new CookedMutton(new IID(Ids::COOKED_MUTTON), "Cooked Mutton")); self::register("cooked_mutton", fn(IID $id) => new CookedMutton($id, "Cooked Mutton"));
self::register("cooked_porkchop", new CookedPorkchop(new IID(Ids::COOKED_PORKCHOP), "Cooked Porkchop")); self::register("cooked_porkchop", fn(IID $id) => new CookedPorkchop($id, "Cooked Porkchop"));
self::register("cooked_rabbit", new CookedRabbit(new IID(Ids::COOKED_RABBIT), "Cooked Rabbit")); self::register("cooked_rabbit", fn(IID $id) => new CookedRabbit($id, "Cooked Rabbit"));
self::register("cooked_salmon", new CookedSalmon(new IID(Ids::COOKED_SALMON), "Cooked Salmon")); self::register("cooked_salmon", fn(IID $id) => new CookedSalmon($id, "Cooked Salmon"));
self::register("cookie", new Cookie(new IID(Ids::COOKIE), "Cookie")); self::register("cookie", fn(IID $id) => new Cookie($id, "Cookie"));
self::register("copper_ingot", new Item(new IID(Ids::COPPER_INGOT), "Copper Ingot")); self::register("copper_ingot", fn(IID $id) => new Item($id, "Copper Ingot"));
self::register("coral_fan", new CoralFan(new IID(Ids::CORAL_FAN))); self::register("coral_fan", fn(IID $id) => new CoralFan($id));
self::register("crimson_sign", new ItemBlockWallOrFloor(new IID(Ids::CRIMSON_SIGN), Blocks::CRIMSON_SIGN(), Blocks::CRIMSON_WALL_SIGN())); self::register("crimson_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, 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("dark_oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::DARK_OAK_SIGN(), Blocks::DARK_OAK_WALL_SIGN()));
self::register("diamond", new Item(new IID(Ids::DIAMOND), "Diamond")); self::register("diamond", fn(IID $id) => new Item($id, "Diamond"));
self::register("disc_fragment_5", new Item(new IID(Ids::DISC_FRAGMENT_5), "Disc Fragment (5)")); self::register("disc_fragment_5", fn(IID $id) => new Item($id, "Disc Fragment (5)"));
self::register("dragon_breath", new Item(new IID(Ids::DRAGON_BREATH), "Dragon's Breath")); self::register("dragon_breath", fn(IID $id) => new Item($id, "Dragon's Breath"));
self::register("dried_kelp", new DriedKelp(new IID(Ids::DRIED_KELP), "Dried Kelp")); self::register("dried_kelp", fn(IID $id) => new DriedKelp($id, "Dried Kelp"));
//TODO: add interface to dye-colour objects //TODO: add interface to dye-colour objects
self::register("dye", new Dye(new IID(Ids::DYE), "Dye")); self::register("dye", fn(IID $id) => new Dye($id, "Dye"));
self::register("echo_shard", new Item(new IID(Ids::ECHO_SHARD), "Echo Shard")); self::register("echo_shard", fn(IID $id) => new Item($id, "Echo Shard"));
self::register("egg", new Egg(new IID(Ids::EGG), "Egg")); self::register("egg", fn(IID $id) => new Egg($id, "Egg"));
self::register("emerald", new Item(new IID(Ids::EMERALD), "Emerald")); self::register("emerald", fn(IID $id) => new Item($id, "Emerald"));
self::register("enchanted_book", new EnchantedBook(new IID(Ids::ENCHANTED_BOOK), "Enchanted Book", [EnchantmentTags::ALL])); self::register("enchanted_book", fn(IID $id) => new EnchantedBook($id, "Enchanted Book", [EnchantmentTags::ALL]));
self::register("enchanted_golden_apple", new GoldenAppleEnchanted(new IID(Ids::ENCHANTED_GOLDEN_APPLE), "Enchanted Golden Apple")); self::register("enchanted_golden_apple", fn(IID $id) => new GoldenAppleEnchanted($id, "Enchanted Golden Apple"));
self::register("ender_pearl", new EnderPearl(new IID(Ids::ENDER_PEARL), "Ender Pearl")); self::register("end_crystal", fn(IID $id) => new EndCrystal($id, "End Crystal"));
self::register("experience_bottle", new ExperienceBottle(new IID(Ids::EXPERIENCE_BOTTLE), "Bottle o' Enchanting")); self::register("ender_pearl", fn(IID $id) => new EnderPearl($id, "Ender Pearl"));
self::register("feather", new Item(new IID(Ids::FEATHER), "Feather")); self::register("experience_bottle", fn(IID $id) => new ExperienceBottle($id, "Bottle o' Enchanting"));
self::register("fermented_spider_eye", new Item(new IID(Ids::FERMENTED_SPIDER_EYE), "Fermented Spider Eye")); self::register("feather", fn(IID $id) => new Item($id, "Feather"));
self::register("fire_charge", new FireCharge(new IID(Ids::FIRE_CHARGE), "Fire Charge")); self::register("fermented_spider_eye", fn(IID $id) => new Item($id, "Fermented Spider Eye"));
self::register("fishing_rod", new FishingRod(new IID(Ids::FISHING_ROD), "Fishing Rod", [EnchantmentTags::FISHING_ROD])); self::register("fire_charge", fn(IID $id) => new FireCharge($id, "Fire Charge"));
self::register("flint", new Item(new IID(Ids::FLINT), "Flint")); self::register("fishing_rod", fn(IID $id) => new FishingRod($id, "Fishing Rod", [EnchantmentTags::FISHING_ROD]));
self::register("flint_and_steel", new FlintSteel(new IID(Ids::FLINT_AND_STEEL), "Flint and Steel", [EnchantmentTags::FLINT_AND_STEEL])); self::register("flint", fn(IID $id) => new Item($id, "Flint"));
self::register("ghast_tear", new Item(new IID(Ids::GHAST_TEAR), "Ghast Tear")); self::register("flint_and_steel", fn(IID $id) => new FlintSteel($id, "Flint and Steel", [EnchantmentTags::FLINT_AND_STEEL]));
self::register("glass_bottle", new GlassBottle(new IID(Ids::GLASS_BOTTLE), "Glass Bottle")); self::register("ghast_tear", fn(IID $id) => new Item($id, "Ghast Tear"));
self::register("glistering_melon", new Item(new IID(Ids::GLISTERING_MELON), "Glistering Melon")); self::register("glass_bottle", fn(IID $id) => new GlassBottle($id, "Glass Bottle"));
self::register("glow_berries", new GlowBerries(new IID(Ids::GLOW_BERRIES), "Glow Berries")); self::register("glistering_melon", fn(IID $id) => new Item($id, "Glistering Melon"));
self::register("glow_ink_sac", new Item(new IID(Ids::GLOW_INK_SAC), "Glow Ink Sac")); self::register("glow_berries", fn(IID $id) => new GlowBerries($id, "Glow Berries"));
self::register("glowstone_dust", new Item(new IID(Ids::GLOWSTONE_DUST), "Glowstone Dust")); self::register("glow_ink_sac", fn(IID $id) => new Item($id, "Glow Ink Sac"));
self::register("gold_ingot", new Item(new IID(Ids::GOLD_INGOT), "Gold Ingot")); self::register("glowstone_dust", fn(IID $id) => new Item($id, "Glowstone Dust"));
self::register("gold_nugget", new Item(new IID(Ids::GOLD_NUGGET), "Gold Nugget")); self::register("goat_horn", fn(IID $id) => new GoatHorn($id, "Goat Horn"));
self::register("golden_apple", new GoldenApple(new IID(Ids::GOLDEN_APPLE), "Golden Apple")); self::register("gold_ingot", fn(IID $id) => new Item($id, "Gold Ingot"));
self::register("golden_carrot", new GoldenCarrot(new IID(Ids::GOLDEN_CARROT), "Golden Carrot")); self::register("gold_nugget", fn(IID $id) => new Item($id, "Gold Nugget"));
self::register("gunpowder", new Item(new IID(Ids::GUNPOWDER), "Gunpowder")); self::register("golden_apple", fn(IID $id) => new GoldenApple($id, "Golden Apple"));
self::register("heart_of_the_sea", new Item(new IID(Ids::HEART_OF_THE_SEA), "Heart of the Sea")); self::register("golden_carrot", fn(IID $id) => new GoldenCarrot($id, "Golden Carrot"));
self::register("honey_bottle", new HoneyBottle(new IID(Ids::HONEY_BOTTLE), "Honey Bottle")); self::register("gunpowder", fn(IID $id) => new Item($id, "Gunpowder"));
self::register("honeycomb", new Item(new IID(Ids::HONEYCOMB), "Honeycomb")); self::register("heart_of_the_sea", fn(IID $id) => new Item($id, "Heart of the Sea"));
self::register("ink_sac", new Item(new IID(Ids::INK_SAC), "Ink Sac")); self::register("honey_bottle", fn(IID $id) => new HoneyBottle($id, "Honey Bottle"));
self::register("iron_ingot", new Item(new IID(Ids::IRON_INGOT), "Iron Ingot")); self::register("honeycomb", fn(IID $id) => new Item($id, "Honeycomb"));
self::register("iron_nugget", new Item(new IID(Ids::IRON_NUGGET), "Iron Nugget")); self::register("ice_bomb", fn(IID $id) => new IceBomb($id, "Ice Bomb"));
self::register("jungle_sign", new ItemBlockWallOrFloor(new IID(Ids::JUNGLE_SIGN), Blocks::JUNGLE_SIGN(), Blocks::JUNGLE_WALL_SIGN())); self::register("ink_sac", fn(IID $id) => new Item($id, "Ink Sac"));
self::register("lapis_lazuli", new Item(new IID(Ids::LAPIS_LAZULI), "Lapis Lazuli")); self::register("iron_ingot", fn(IID $id) => new Item($id, "Iron Ingot"));
self::register("lava_bucket", new LiquidBucket(new IID(Ids::LAVA_BUCKET), "Lava Bucket", Blocks::LAVA())); self::register("iron_nugget", fn(IID $id) => new Item($id, "Iron Nugget"));
self::register("leather", new Item(new IID(Ids::LEATHER), "Leather")); self::register("jungle_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::JUNGLE_SIGN(), Blocks::JUNGLE_WALL_SIGN()));
self::register("magma_cream", new Item(new IID(Ids::MAGMA_CREAM), "Magma Cream")); self::register("lapis_lazuli", fn(IID $id) => new Item($id, "Lapis Lazuli"));
self::register("mangrove_sign", new ItemBlockWallOrFloor(new IID(Ids::MANGROVE_SIGN), Blocks::MANGROVE_SIGN(), Blocks::MANGROVE_WALL_SIGN())); self::register("lava_bucket", fn(IID $id) => new LiquidBucket($id, "Lava Bucket", Blocks::LAVA()));
self::register("medicine", new Medicine(new IID(Ids::MEDICINE), "Medicine")); self::register("leather", fn(IID $id) => new Item($id, "Leather"));
self::register("melon", new Melon(new IID(Ids::MELON), "Melon")); self::register("magma_cream", fn(IID $id) => new Item($id, "Magma Cream"));
self::register("melon_seeds", new MelonSeeds(new IID(Ids::MELON_SEEDS), "Melon Seeds")); self::register("mangrove_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::MANGROVE_SIGN(), Blocks::MANGROVE_WALL_SIGN()));
self::register("milk_bucket", new MilkBucket(new IID(Ids::MILK_BUCKET), "Milk Bucket")); self::register("medicine", fn(IID $id) => new Medicine($id, "Medicine"));
self::register("minecart", new Minecart(new IID(Ids::MINECART), "Minecart")); self::register("melon", fn(IID $id) => new Melon($id, "Melon"));
self::register("mushroom_stew", new MushroomStew(new IID(Ids::MUSHROOM_STEW), "Mushroom Stew")); self::register("melon_seeds", fn(IID $id) => new MelonSeeds($id, "Melon Seeds"));
self::register("name_tag", new NameTag(new IID(Ids::NAME_TAG), "Name Tag")); self::register("milk_bucket", fn(IID $id) => new MilkBucket($id, "Milk Bucket"));
self::register("nautilus_shell", new Item(new IID(Ids::NAUTILUS_SHELL), "Nautilus Shell")); self::register("minecart", fn(IID $id) => new Minecart($id, "Minecart"));
self::register("nether_brick", new Item(new IID(Ids::NETHER_BRICK), "Nether Brick")); self::register("mushroom_stew", fn(IID $id) => new MushroomStew($id, "Mushroom Stew"));
self::register("nether_quartz", new Item(new IID(Ids::NETHER_QUARTZ), "Nether Quartz")); self::register("name_tag", fn(IID $id) => new NameTag($id, "Name Tag"));
self::register("nether_star", new Item(new IID(Ids::NETHER_STAR), "Nether Star")); self::register("nautilus_shell", fn(IID $id) => new Item($id, "Nautilus Shell"));
self::register("netherite_ingot", new class(new IID(Ids::NETHERITE_INGOT), "Netherite Ingot") extends Item{ 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; } 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; } 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("oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::OAK_SIGN(), Blocks::OAK_WALL_SIGN()));
self::register("painting", new PaintingItem(new IID(Ids::PAINTING), "Painting")); self::register("painting", fn(IID $id) => new PaintingItem($id, "Painting"));
self::register("paper", new Item(new IID(Ids::PAPER), "Paper")); self::register("paper", fn(IID $id) => new Item($id, "Paper"));
self::register("phantom_membrane", new Item(new IID(Ids::PHANTOM_MEMBRANE), "Phantom Membrane")); self::register("phantom_membrane", fn(IID $id) => new Item($id, "Phantom Membrane"));
self::register("pitcher_pod", new PitcherPod(new IID(Ids::PITCHER_POD), "Pitcher Pod")); self::register("pitcher_pod", fn(IID $id) => new PitcherPod($id, "Pitcher Pod"));
self::register("poisonous_potato", new PoisonousPotato(new IID(Ids::POISONOUS_POTATO), "Poisonous Potato")); self::register("poisonous_potato", fn(IID $id) => new PoisonousPotato($id, "Poisonous Potato"));
self::register("popped_chorus_fruit", new Item(new IID(Ids::POPPED_CHORUS_FRUIT), "Popped Chorus Fruit")); self::register("popped_chorus_fruit", fn(IID $id) => new Item($id, "Popped Chorus Fruit"));
self::register("potato", new Potato(new IID(Ids::POTATO), "Potato")); self::register("potato", fn(IID $id) => new Potato($id, "Potato"));
self::register("potion", new Potion(new IID(Ids::POTION), "Potion")); self::register("potion", fn(IID $id) => new Potion($id, "Potion"));
self::register("prismarine_crystals", new Item(new IID(Ids::PRISMARINE_CRYSTALS), "Prismarine Crystals")); self::register("prismarine_crystals", fn(IID $id) => new Item($id, "Prismarine Crystals"));
self::register("prismarine_shard", new Item(new IID(Ids::PRISMARINE_SHARD), "Prismarine Shard")); self::register("prismarine_shard", fn(IID $id) => new Item($id, "Prismarine Shard"));
self::register("pufferfish", new Pufferfish(new IID(Ids::PUFFERFISH), "Pufferfish")); self::register("pufferfish", fn(IID $id) => new Pufferfish($id, "Pufferfish"));
self::register("pumpkin_pie", new PumpkinPie(new IID(Ids::PUMPKIN_PIE), "Pumpkin Pie")); self::register("pumpkin_pie", fn(IID $id) => new PumpkinPie($id, "Pumpkin Pie"));
self::register("pumpkin_seeds", new PumpkinSeeds(new IID(Ids::PUMPKIN_SEEDS), "Pumpkin Seeds")); self::register("pumpkin_seeds", fn(IID $id) => new PumpkinSeeds($id, "Pumpkin Seeds"));
self::register("rabbit_foot", new Item(new IID(Ids::RABBIT_FOOT), "Rabbit's Foot")); self::register("rabbit_foot", fn(IID $id) => new Item($id, "Rabbit's Foot"));
self::register("rabbit_hide", new Item(new IID(Ids::RABBIT_HIDE), "Rabbit Hide")); self::register("rabbit_hide", fn(IID $id) => new Item($id, "Rabbit Hide"));
self::register("rabbit_stew", new RabbitStew(new IID(Ids::RABBIT_STEW), "Rabbit Stew")); self::register("rabbit_stew", fn(IID $id) => new RabbitStew($id, "Rabbit Stew"));
self::register("raw_beef", new RawBeef(new IID(Ids::RAW_BEEF), "Raw Beef")); self::register("raw_beef", fn(IID $id) => new RawBeef($id, "Raw Beef"));
self::register("raw_chicken", new RawChicken(new IID(Ids::RAW_CHICKEN), "Raw Chicken")); self::register("raw_chicken", fn(IID $id) => new RawChicken($id, "Raw Chicken"));
self::register("raw_copper", new Item(new IID(Ids::RAW_COPPER), "Raw Copper")); self::register("raw_copper", fn(IID $id) => new Item($id, "Raw Copper"));
self::register("raw_fish", new RawFish(new IID(Ids::RAW_FISH), "Raw Fish")); self::register("raw_fish", fn(IID $id) => new RawFish($id, "Raw Fish"));
self::register("raw_gold", new Item(new IID(Ids::RAW_GOLD), "Raw Gold")); self::register("raw_gold", fn(IID $id) => new Item($id, "Raw Gold"));
self::register("raw_iron", new Item(new IID(Ids::RAW_IRON), "Raw Iron")); self::register("raw_iron", fn(IID $id) => new Item($id, "Raw Iron"));
self::register("raw_mutton", new RawMutton(new IID(Ids::RAW_MUTTON), "Raw Mutton")); self::register("raw_mutton", fn(IID $id) => new RawMutton($id, "Raw Mutton"));
self::register("raw_porkchop", new RawPorkchop(new IID(Ids::RAW_PORKCHOP), "Raw Porkchop")); self::register("raw_porkchop", fn(IID $id) => new RawPorkchop($id, "Raw Porkchop"));
self::register("raw_rabbit", new RawRabbit(new IID(Ids::RAW_RABBIT), "Raw Rabbit")); self::register("raw_rabbit", fn(IID $id) => new RawRabbit($id, "Raw Rabbit"));
self::register("raw_salmon", new RawSalmon(new IID(Ids::RAW_SALMON), "Raw Salmon")); self::register("raw_salmon", fn(IID $id) => new RawSalmon($id, "Raw Salmon"));
self::register("record_11", new Record(new IID(Ids::RECORD_11), RecordType::DISK_11, "Record 11")); self::register("record_11", fn(IID $id) => new Record($id, RecordType::DISK_11, "Record 11"));
self::register("record_13", new Record(new IID(Ids::RECORD_13), RecordType::DISK_13, "Record 13")); self::register("record_13", fn(IID $id) => new Record($id, RecordType::DISK_13, "Record 13"));
self::register("record_5", new Record(new IID(Ids::RECORD_5), RecordType::DISK_5, "Record 5")); self::register("record_5", fn(IID $id) => new Record($id, RecordType::DISK_5, "Record 5"));
self::register("record_blocks", new Record(new IID(Ids::RECORD_BLOCKS), RecordType::DISK_BLOCKS, "Record Blocks")); self::register("record_blocks", fn(IID $id) => new Record($id, RecordType::DISK_BLOCKS, "Record Blocks"));
self::register("record_cat", new Record(new IID(Ids::RECORD_CAT), RecordType::DISK_CAT, "Record Cat")); self::register("record_cat", fn(IID $id) => new Record($id, RecordType::DISK_CAT, "Record Cat"));
self::register("record_chirp", new Record(new IID(Ids::RECORD_CHIRP), RecordType::DISK_CHIRP, "Record Chirp")); self::register("record_chirp", fn(IID $id) => new Record($id, RecordType::DISK_CHIRP, "Record Chirp"));
self::register("record_far", new Record(new IID(Ids::RECORD_FAR), RecordType::DISK_FAR, "Record Far")); self::register("record_far", fn(IID $id) => new Record($id, RecordType::DISK_FAR, "Record Far"));
self::register("record_mall", new Record(new IID(Ids::RECORD_MALL), RecordType::DISK_MALL, "Record Mall")); self::register("record_mall", fn(IID $id) => new Record($id, RecordType::DISK_MALL, "Record Mall"));
self::register("record_mellohi", new Record(new IID(Ids::RECORD_MELLOHI), RecordType::DISK_MELLOHI, "Record Mellohi")); self::register("record_mellohi", fn(IID $id) => new Record($id, RecordType::DISK_MELLOHI, "Record Mellohi"));
self::register("record_otherside", new Record(new IID(Ids::RECORD_OTHERSIDE), RecordType::DISK_OTHERSIDE, "Record Otherside")); self::register("record_otherside", fn(IID $id) => new Record($id, RecordType::DISK_OTHERSIDE, "Record Otherside"));
self::register("record_pigstep", new Record(new IID(Ids::RECORD_PIGSTEP), RecordType::DISK_PIGSTEP, "Record Pigstep")); self::register("record_pigstep", fn(IID $id) => new Record($id, RecordType::DISK_PIGSTEP, "Record Pigstep"));
self::register("record_stal", new Record(new IID(Ids::RECORD_STAL), RecordType::DISK_STAL, "Record Stal")); self::register("record_stal", fn(IID $id) => new Record($id, RecordType::DISK_STAL, "Record Stal"));
self::register("record_strad", new Record(new IID(Ids::RECORD_STRAD), RecordType::DISK_STRAD, "Record Strad")); self::register("record_strad", fn(IID $id) => new Record($id, RecordType::DISK_STRAD, "Record Strad"));
self::register("record_wait", new Record(new IID(Ids::RECORD_WAIT), RecordType::DISK_WAIT, "Record Wait")); self::register("record_wait", fn(IID $id) => new Record($id, RecordType::DISK_WAIT, "Record Wait"));
self::register("record_ward", new Record(new IID(Ids::RECORD_WARD), RecordType::DISK_WARD, "Record Ward")); self::register("record_ward", fn(IID $id) => new Record($id, RecordType::DISK_WARD, "Record Ward"));
self::register("redstone_dust", new Redstone(new IID(Ids::REDSTONE_DUST), "Redstone")); self::register("recovery_compass", fn(IID $id) => new Item($id, "Recovery Compass"));
self::register("rotten_flesh", new RottenFlesh(new IID(Ids::ROTTEN_FLESH), "Rotten Flesh")); self::register("redstone_dust", fn(IID $id) => new Redstone($id, "Redstone"));
self::register("scute", new Item(new IID(Ids::SCUTE), "Scute")); self::register("rotten_flesh", fn(IID $id) => new RottenFlesh($id, "Rotten Flesh"));
self::register("shears", new Shears(new IID(Ids::SHEARS), "Shears", [EnchantmentTags::SHEARS])); self::register("scute", fn(IID $id) => new Item($id, "Scute"));
self::register("shulker_shell", new Item(new IID(Ids::SHULKER_SHELL), "Shulker Shell")); self::register("shears", fn(IID $id) => new Shears($id, "Shears", [EnchantmentTags::SHEARS]));
self::register("slimeball", new Item(new IID(Ids::SLIMEBALL), "Slimeball")); self::register("shulker_shell", fn(IID $id) => new Item($id, "Shulker Shell"));
self::register("snowball", new Snowball(new IID(Ids::SNOWBALL), "Snowball")); self::register("slimeball", fn(IID $id) => new Item($id, "Slimeball"));
self::register("spider_eye", new SpiderEye(new IID(Ids::SPIDER_EYE), "Spider Eye")); self::register("snowball", fn(IID $id) => new Snowball($id, "Snowball"));
self::register("splash_potion", new SplashPotion(new IID(Ids::SPLASH_POTION), "Splash Potion")); self::register("spider_eye", fn(IID $id) => new SpiderEye($id, "Spider Eye"));
self::register("spruce_sign", new ItemBlockWallOrFloor(new IID(Ids::SPRUCE_SIGN), Blocks::SPRUCE_SIGN(), Blocks::SPRUCE_WALL_SIGN())); self::register("splash_potion", fn(IID $id) => new SplashPotion($id, "Splash Potion"));
self::register("spyglass", new Spyglass(new IID(Ids::SPYGLASS), "Spyglass")); self::register("spruce_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::SPRUCE_SIGN(), Blocks::SPRUCE_WALL_SIGN()));
self::register("steak", new Steak(new IID(Ids::STEAK), "Steak")); self::register("spyglass", fn(IID $id) => new Spyglass($id, "Spyglass"));
self::register("stick", new Stick(new IID(Ids::STICK), "Stick")); self::register("steak", fn(IID $id) => new Steak($id, "Steak"));
self::register("string", new StringItem(new IID(Ids::STRING), "String")); self::register("stick", fn(IID $id) => new Stick($id, "Stick"));
self::register("sugar", new Item(new IID(Ids::SUGAR), "Sugar")); self::register("string", fn(IID $id) => new StringItem($id, "String"));
self::register("suspicious_stew", new SuspiciousStew(new IID(Ids::SUSPICIOUS_STEW), "Suspicious Stew")); self::register("sugar", fn(IID $id) => new Item($id, "Sugar"));
self::register("sweet_berries", new SweetBerries(new IID(Ids::SWEET_BERRIES), "Sweet Berries")); self::register("suspicious_stew", fn(IID $id) => new SuspiciousStew($id, "Suspicious Stew"));
self::register("torchflower_seeds", new TorchflowerSeeds(new IID(Ids::TORCHFLOWER_SEEDS), "Torchflower Seeds")); self::register("sweet_berries", fn(IID $id) => new SweetBerries($id, "Sweet Berries"));
self::register("totem", new Totem(new IID(Ids::TOTEM), "Totem of Undying")); self::register("torchflower_seeds", fn(IID $id) => new TorchflowerSeeds($id, "Torchflower Seeds"));
self::register("warped_sign", new ItemBlockWallOrFloor(new IID(Ids::WARPED_SIGN), Blocks::WARPED_SIGN(), Blocks::WARPED_WALL_SIGN())); self::register("totem", fn(IID $id) => new Totem($id, "Totem of Undying"));
self::register("water_bucket", new LiquidBucket(new IID(Ids::WATER_BUCKET), "Water Bucket", Blocks::WATER())); self::register("warped_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::WARPED_SIGN(), Blocks::WARPED_WALL_SIGN()));
self::register("wheat", new Item(new IID(Ids::WHEAT), "Wheat")); self::register("water_bucket", fn(IID $id) => new LiquidBucket($id, "Water Bucket", Blocks::WATER()));
self::register("wheat_seeds", new WheatSeeds(new IID(Ids::WHEAT_SEEDS), "Wheat Seeds")); self::register("wheat", fn(IID $id) => new Item($id, "Wheat"));
self::register("writable_book", new WritableBook(new IID(Ids::WRITABLE_BOOK), "Book & Quill")); self::register("wheat_seeds", fn(IID $id) => new WheatSeeds($id, "Wheat Seeds"));
self::register("written_book", new WrittenBook(new IID(Ids::WRITTEN_BOOK), "Written Book")); 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){ foreach(BoatType::cases() as $type){
//boat type is static, because different types of wood may have different properties //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){ self::register(strtolower($type->name) . "_boat", fn(IID $id) => new Boat($id, $type->getDisplayName() . " Boat", $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));
} }
} }
private static function registerSpawnEggs() : void{ 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{ protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{
return new Zombie(Location::fromObject($pos, $world, $yaw, $pitch)); 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{ protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{
return new Squid(Location::fromObject($pos, $world, $yaw, $pitch)); 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{ protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{
return new Villager(Location::fromObject($pos, $world, $yaw, $pitch)); return new Villager(Location::fromObject($pos, $world, $yaw, $pitch));
} }
@ -603,87 +627,87 @@ final class VanillaItems{
} }
private static function registerTierToolItems() : void{ private static function registerTierToolItems() : void{
self::register("diamond_axe", new Axe(new IID(Ids::DIAMOND_AXE), "Diamond Axe", ToolTier::DIAMOND, [EnchantmentTags::AXE])); self::register("diamond_axe", fn(IID $id) => new Axe($id, "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("golden_axe", fn(IID $id) => new Axe($id, "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("iron_axe", fn(IID $id) => new Axe($id, "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("netherite_axe", fn(IID $id) => new Axe($id, "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("stone_axe", fn(IID $id) => new Axe($id, "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("wooden_axe", fn(IID $id) => new Axe($id, "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("diamond_hoe", fn(IID $id) => new Hoe($id, "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("golden_hoe", fn(IID $id) => new Hoe($id, "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("iron_hoe", fn(IID $id) => new Hoe($id, "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("netherite_hoe", fn(IID $id) => new Hoe($id, "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("stone_hoe", fn(IID $id) => new Hoe($id, "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("wooden_hoe", fn(IID $id) => new Hoe($id, "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("diamond_pickaxe", fn(IID $id) => new Pickaxe($id, "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("golden_pickaxe", fn(IID $id) => new Pickaxe($id, "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("iron_pickaxe", fn(IID $id) => new Pickaxe($id, "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("netherite_pickaxe", fn(IID $id) => new Pickaxe($id, "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("stone_pickaxe", fn(IID $id) => new Pickaxe($id, "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("wooden_pickaxe", fn(IID $id) => new Pickaxe($id, "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("diamond_shovel", fn(IID $id) => new Shovel($id, "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("golden_shovel", fn(IID $id) => new Shovel($id, "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("iron_shovel", fn(IID $id) => new Shovel($id, "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("netherite_shovel", fn(IID $id) => new Shovel($id, "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("stone_shovel", fn(IID $id) => new Shovel($id, "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("wooden_shovel", fn(IID $id) => new Shovel($id, "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("diamond_sword", fn(IID $id) => new Sword($id, "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("golden_sword", fn(IID $id) => new Sword($id, "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("iron_sword", fn(IID $id) => new Sword($id, "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("netherite_sword", fn(IID $id) => new Sword($id, "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("stone_sword", fn(IID $id) => new Sword($id, "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("wooden_sword", fn(IID $id) => new Sword($id, "Wooden Sword", ToolTier::WOOD, [EnchantmentTags::SWORD]));
} }
private static function registerArmorItems() : void{ 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("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", new Armor(new IID(Ids::DIAMOND_BOOTS), "Diamond Boots", new ArmorTypeInfo(3, 430, ArmorInventory::SLOT_FEET, 2, material: ArmorMaterials::DIAMOND()), [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", new Armor(new IID(Ids::GOLDEN_BOOTS), "Golden Boots", new ArmorTypeInfo(1, 92, ArmorInventory::SLOT_FEET, material: ArmorMaterials::GOLD()), [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", new Armor(new IID(Ids::IRON_BOOTS), "Iron Boots", new ArmorTypeInfo(2, 196, ArmorInventory::SLOT_FEET, material: ArmorMaterials::IRON()), [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", new Armor(new IID(Ids::LEATHER_BOOTS), "Leather Boots", new ArmorTypeInfo(1, 66, ArmorInventory::SLOT_FEET, material: ArmorMaterials::LEATHER()), [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", 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("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("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", new Armor(new IID(Ids::DIAMOND_CHESTPLATE), "Diamond Chestplate", new ArmorTypeInfo(8, 529, ArmorInventory::SLOT_CHEST, 2, material: ArmorMaterials::DIAMOND()), [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", new Armor(new IID(Ids::GOLDEN_CHESTPLATE), "Golden Chestplate", new ArmorTypeInfo(5, 113, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::GOLD()), [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", new Armor(new IID(Ids::IRON_CHESTPLATE), "Iron Chestplate", new ArmorTypeInfo(6, 241, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::IRON()), [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", new Armor(new IID(Ids::LEATHER_TUNIC), "Leather Tunic", new ArmorTypeInfo(3, 81, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::LEATHER()), [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", 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("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("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", new Armor(new IID(Ids::DIAMOND_HELMET), "Diamond Helmet", new ArmorTypeInfo(3, 364, ArmorInventory::SLOT_HEAD, 2, material: ArmorMaterials::DIAMOND()), [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", new Armor(new IID(Ids::GOLDEN_HELMET), "Golden Helmet", new ArmorTypeInfo(2, 78, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::GOLD()), [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", new Armor(new IID(Ids::IRON_HELMET), "Iron Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::IRON()), [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", new Armor(new IID(Ids::LEATHER_CAP), "Leather Cap", new ArmorTypeInfo(1, 56, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::LEATHER()), [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", 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("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", new TurtleHelmet(new IID(Ids::TURTLE_HELMET), "Turtle Shell", new ArmorTypeInfo(2, 276, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::TURTLE()), [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("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", new Armor(new IID(Ids::DIAMOND_LEGGINGS), "Diamond Leggings", new ArmorTypeInfo(6, 496, ArmorInventory::SLOT_LEGS, 2, material: ArmorMaterials::DIAMOND()), [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", new Armor(new IID(Ids::GOLDEN_LEGGINGS), "Golden Leggings", new ArmorTypeInfo(3, 106, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::GOLD()), [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", new Armor(new IID(Ids::IRON_LEGGINGS), "Iron Leggings", new ArmorTypeInfo(5, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::IRON()), [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", new Armor(new IID(Ids::LEATHER_PANTS), "Leather Pants", new ArmorTypeInfo(2, 76, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::LEATHER()), [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", 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("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{ 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("netherite_upgrade_smithing_template", fn(IID $id) => new Item($id, "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("coast_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("dune_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("eye_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("host_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("raiser_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("rib_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("sentry_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("shaper_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("silence_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("snout_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("spire_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("tide_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("vex_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("ward_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("wayfinder_armor_trim_smithing_template", fn(IID $id) => new Item($id, "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("wild_armor_trim_smithing_template", fn(IID $id) => new Item($id, "Wild Armor Trim Smithing Template"));
} }
} }

View File

@ -57,6 +57,7 @@ final class AvailableEnchantmentRegistry{
$this->register(Enchantments::THORNS(), [Tags::CHESTPLATE], [Tags::HELMET, Tags::LEGGINGS, Tags::BOOTS]); $this->register(Enchantments::THORNS(), [Tags::CHESTPLATE], [Tags::HELMET, Tags::LEGGINGS, Tags::BOOTS]);
$this->register(Enchantments::RESPIRATION(), [Tags::HELMET], []); $this->register(Enchantments::RESPIRATION(), [Tags::HELMET], []);
$this->register(Enchantments::AQUA_AFFINITY(), [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::SHARPNESS(), [Tags::SWORD, Tags::AXE], []);
$this->register(Enchantments::KNOCKBACK(), [Tags::SWORD], []); $this->register(Enchantments::KNOCKBACK(), [Tags::SWORD], []);
$this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []); $this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []);

View File

@ -44,6 +44,7 @@ final class StringToEnchantmentParser extends StringToTParser{
$result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION()); $result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION());
$result->register("flame", fn() => VanillaEnchantments::FLAME()); $result->register("flame", fn() => VanillaEnchantments::FLAME());
$result->register("fortune", fn() => VanillaEnchantments::FORTUNE()); $result->register("fortune", fn() => VanillaEnchantments::FORTUNE());
$result->register("frost_walker", fn() => VanillaEnchantments::FROST_WALKER());
$result->register("infinity", fn() => VanillaEnchantments::INFINITY()); $result->register("infinity", fn() => VanillaEnchantments::INFINITY());
$result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK()); $result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK());
$result->register("mending", fn() => VanillaEnchantments::MENDING()); $result->register("mending", fn() => VanillaEnchantments::MENDING());

View File

@ -41,6 +41,7 @@ use pocketmine\utils\RegistryTrait;
* @method static ProtectionEnchantment FIRE_PROTECTION() * @method static ProtectionEnchantment FIRE_PROTECTION()
* @method static Enchantment FLAME() * @method static Enchantment FLAME()
* @method static Enchantment FORTUNE() * @method static Enchantment FORTUNE()
* @method static Enchantment FROST_WALKER()
* @method static Enchantment INFINITY() * @method static Enchantment INFINITY()
* @method static KnockbackEnchantment KNOCKBACK() * @method static KnockbackEnchantment KNOCKBACK()
* @method static Enchantment MENDING() * @method static Enchantment MENDING()
@ -145,6 +146,16 @@ final class VanillaEnchantments{
fn(int $level) : int => 10 * $level, fn(int $level) : int => 10 * $level,
30 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( self::register("AQUA_AFFINITY", new Enchantment(
KnownTranslationFactory::enchantment_waterWorker(), KnownTranslationFactory::enchantment_waterWorker(),
Rarity::RARE, Rarity::RARE,

View File

@ -603,6 +603,31 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::COMMANDS_WHITELIST_USAGE, []); 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{ public static function death_attack_anvil(Translatable|string $param0) : Translatable{
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_ANVIL, [ return new Translatable(KnownTranslationKeys::DEATH_ATTACK_ANVIL, [
0 => $param0, 0 => $param0,
@ -667,6 +692,12 @@ final class KnownTranslationFactory{
]); ]);
} }
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{ public static function death_attack_generic(Translatable|string $param0) : Translatable{
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_GENERIC, [ return new Translatable(KnownTranslationKeys::DEATH_ATTACK_GENERIC, [
0 => $param0, 0 => $param0,
@ -1025,6 +1056,14 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::ITEM_RECORD_CHIRP_DESC, []); 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{ public static function item_record_far_desc() : Translatable{
return new Translatable(KnownTranslationKeys::ITEM_RECORD_FAR_DESC, []); return new Translatable(KnownTranslationKeys::ITEM_RECORD_FAR_DESC, []);
} }
@ -1045,6 +1084,14 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::ITEM_RECORD_PIGSTEP_DESC, []); 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{ public static function item_record_stal_desc() : Translatable{
return new Translatable(KnownTranslationKeys::ITEM_RECORD_STAL_DESC, []); return new Translatable(KnownTranslationKeys::ITEM_RECORD_STAL_DESC, []);
} }
@ -1394,6 +1441,10 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED, []); 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{ public static function pocketmine_command_timings_description() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_DESCRIPTION, []); return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_DESCRIPTION, []);
} }
@ -1536,6 +1587,14 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_WHITELIST_DESCRIPTION, []); 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{ public static function pocketmine_crash_archive(Translatable|string $param0, Translatable|string $param1) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_CRASH_ARCHIVE, [ return new Translatable(KnownTranslationKeys::POCKETMINE_CRASH_ARCHIVE, [
0 => $param0, 0 => $param0,
@ -2056,6 +2115,14 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_REMOVE, []); 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{ public static function pocketmine_permission_group_console() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_GROUP_CONSOLE, []); return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_GROUP_CONSOLE, []);
} }

View File

@ -137,6 +137,10 @@ final class KnownTranslationKeys{
public const COMMANDS_WHITELIST_REMOVE_SUCCESS = "commands.whitelist.remove.success"; public const COMMANDS_WHITELIST_REMOVE_SUCCESS = "commands.whitelist.remove.success";
public const COMMANDS_WHITELIST_REMOVE_USAGE = "commands.whitelist.remove.usage"; public const COMMANDS_WHITELIST_REMOVE_USAGE = "commands.whitelist.remove.usage";
public const COMMANDS_WHITELIST_USAGE = "commands.whitelist.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_ANVIL = "death.attack.anvil";
public const DEATH_ATTACK_ARROW = "death.attack.arrow"; public const DEATH_ATTACK_ARROW = "death.attack.arrow";
public const DEATH_ATTACK_ARROW_ITEM = "death.attack.arrow.item"; 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_FALL = "death.attack.fall";
public const DEATH_ATTACK_FALLINGBLOCK = "death.attack.fallingBlock"; public const DEATH_ATTACK_FALLINGBLOCK = "death.attack.fallingBlock";
public const DEATH_ATTACK_FIREWORKS = "death.attack.fireworks"; 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_GENERIC = "death.attack.generic";
public const DEATH_ATTACK_INFIRE = "death.attack.inFire"; public const DEATH_ATTACK_INFIRE = "death.attack.inFire";
public const DEATH_ATTACK_INWALL = "death.attack.inWall"; 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_BLOCKS_DESC = "item.record_blocks.desc";
public const ITEM_RECORD_CAT_DESC = "item.record_cat.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_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_FAR_DESC = "item.record_far.desc";
public const ITEM_RECORD_MALL_DESC = "item.record_mall.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_MELLOHI_DESC = "item.record_mellohi.desc";
public const ITEM_RECORD_OTHERSIDE_DESC = "item.record_otherside.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_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_STAL_DESC = "item.record_stal.desc";
public const ITEM_RECORD_STRAD_DESC = "item.record_strad.desc"; public const ITEM_RECORD_STRAD_DESC = "item.record_strad.desc";
public const ITEM_RECORD_WAIT_DESC = "item.record_wait.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_DESCRIPTION = "pocketmine.command.time.description";
public const POCKETMINE_COMMAND_TIME_USAGE = "pocketmine.command.time.usage"; 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_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_DESCRIPTION = "pocketmine.command.timings.description";
public const POCKETMINE_COMMAND_TIMINGS_DISABLE = "pocketmine.command.timings.disable"; public const POCKETMINE_COMMAND_TIMINGS_DISABLE = "pocketmine.command.timings.disable";
public const POCKETMINE_COMMAND_TIMINGS_ENABLE = "pocketmine.command.timings.enable"; 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_SERVERSOFTWAREVERSION = "pocketmine.command.version.serverSoftwareVersion";
public const POCKETMINE_COMMAND_VERSION_USAGE = "pocketmine.command.version.usage"; public const POCKETMINE_COMMAND_VERSION_USAGE = "pocketmine.command.version.usage";
public const POCKETMINE_COMMAND_WHITELIST_DESCRIPTION = "pocketmine.command.whitelist.description"; 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_ARCHIVE = "pocketmine.crash.archive";
public const POCKETMINE_CRASH_CREATE = "pocketmine.crash.create"; public const POCKETMINE_CRASH_CREATE = "pocketmine.crash.create";
public const POCKETMINE_CRASH_ERROR = "pocketmine.crash.error"; 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_LIST = "pocketmine.permission.command.whitelist.list";
public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_RELOAD = "pocketmine.permission.command.whitelist.reload"; 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_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_CONSOLE = "pocketmine.permission.group.console";
public const POCKETMINE_PERMISSION_GROUP_OPERATOR = "pocketmine.permission.group.operator"; public const POCKETMINE_PERMISSION_GROUP_OPERATOR = "pocketmine.permission.group.operator";
public const POCKETMINE_PERMISSION_GROUP_USER = "pocketmine.permission.group.user"; public const POCKETMINE_PERMISSION_GROUP_USER = "pocketmine.permission.group.user";

View File

@ -497,15 +497,18 @@ class InGamePacketHandler extends PacketHandler{
$blockPos = $data->getBlockPosition(); $blockPos = $data->getBlockPosition();
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ()); $vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
if(!$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos)){ $this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos);
$this->onFailedBlockAction($vBlockPos, $data->getFace()); //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; return true;
case UseItemTransactionData::ACTION_BREAK_BLOCK: case UseItemTransactionData::ACTION_BREAK_BLOCK:
$blockPos = $data->getBlockPosition(); $blockPos = $data->getBlockPosition();
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ()); $vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
if(!$this->player->breakBlock($vBlockPos)){ if(!$this->player->breakBlock($vBlockPos)){
$this->onFailedBlockAction($vBlockPos, null); $this->syncBlocksNearby($vBlockPos, null);
} }
return true; return true;
case UseItemTransactionData::ACTION_CLICK_AIR: case UseItemTransactionData::ACTION_CLICK_AIR:
@ -533,9 +536,9 @@ class InGamePacketHandler extends PacketHandler{
} }
/** /**
* 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){ if($blockPos->distanceSquared($this->player->getLocation()) < 10000){
$blocks = $blockPos->sidesArray(); $blocks = $blockPos->sidesArray();
if($face !== null){ if($face !== null){
@ -672,7 +675,7 @@ class InGamePacketHandler extends PacketHandler{
} }
public function handleActorPickRequest(ActorPickRequestPacket $packet) : bool{ public function handleActorPickRequest(ActorPickRequestPacket $packet) : bool{
return false; //TODO return $this->player->pickEntity($packet->actorUniqueId);
} }
public function handlePlayerAction(PlayerActionPacket $packet) : bool{ public function handlePlayerAction(PlayerActionPacket $packet) : bool{
@ -686,7 +689,7 @@ class InGamePacketHandler extends PacketHandler{
case PlayerAction::START_BREAK: case PlayerAction::START_BREAK:
self::validateFacing($face); self::validateFacing($face);
if(!$this->player->attackBlock($pos, $face)){ if(!$this->player->attackBlock($pos, $face)){
$this->onFailedBlockAction($pos, $face); $this->syncBlocksNearby($pos, $face);
} }
break; break;
@ -1002,7 +1005,7 @@ class InGamePacketHandler extends PacketHandler{
$lectern = $world->getBlockAt($pos->getX(), $pos->getY(), $pos->getZ()); $lectern = $world->getBlockAt($pos->getX(), $pos->getY(), $pos->getZ());
if($lectern instanceof Lectern && $this->player->canInteract($lectern->getPosition(), 15)){ if($lectern instanceof Lectern && $this->player->canInteract($lectern->getPosition(), 15)){
if(!$lectern->onPageTurn($packet->page)){ if(!$lectern->onPageTurn($packet->page)){
$this->onFailedBlockAction($lectern->getPosition(), null); $this->syncBlocksNearby($lectern->getPosition(), null);
} }
return true; return true;
} }

View File

@ -84,6 +84,8 @@ final class DefaultPermissionNames{
public const COMMAND_WHITELIST_LIST = "pocketmine.command.whitelist.list"; public const COMMAND_WHITELIST_LIST = "pocketmine.command.whitelist.list";
public const COMMAND_WHITELIST_RELOAD = "pocketmine.command.whitelist.reload"; public const COMMAND_WHITELIST_RELOAD = "pocketmine.command.whitelist.reload";
public const COMMAND_WHITELIST_REMOVE = "pocketmine.command.whitelist.remove"; 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_CONSOLE = "pocketmine.group.console";
public const GROUP_OPERATOR = "pocketmine.group.operator"; public const GROUP_OPERATOR = "pocketmine.group.operator";
public const GROUP_USER = "pocketmine.group.user"; public const GROUP_USER = "pocketmine.group.user";

View File

@ -112,5 +112,7 @@ abstract class DefaultPermissions{
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_LIST, l10n::pocketmine_permission_command_whitelist_list()), [$operatorRoot]); 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_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_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]);
} }
} }

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\permission; namespace pocketmine\permission;
use pocketmine\Server;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
use function count; use function count;
use function spl_object_id; use function spl_object_id;
@ -71,6 +72,10 @@ class PermissionManager{
} }
} }
/**
* @deprecated Superseded by server chat broadcast channels
* @see Server::subscribeToBroadcastChannel()
*/
public function subscribeToPermission(string $permission, PermissibleInternal $permissible) : void{ public function subscribeToPermission(string $permission, PermissibleInternal $permissible) : void{
if(!isset($this->permSubs[$permission])){ if(!isset($this->permSubs[$permission])){
$this->permSubs[$permission] = []; $this->permSubs[$permission] = [];
@ -78,6 +83,10 @@ class PermissionManager{
$this->permSubs[$permission][spl_object_id($permissible)] = $permissible; $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{ public function unsubscribeFromPermission(string $permission, PermissibleInternal $permissible) : void{
if(isset($this->permSubs[$permission][spl_object_id($permissible)])){ if(isset($this->permSubs[$permission][spl_object_id($permissible)])){
if(count($this->permSubs[$permission]) === 1){ if(count($this->permSubs[$permission]) === 1){
@ -88,6 +97,10 @@ class PermissionManager{
} }
} }
/**
* @deprecated Superseded by server chat broadcast channels
* @see Server::unsubscribeFromAllBroadcastChannels()
*/
public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) : void{ public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) : void{
foreach(Utils::promoteKeys($this->permSubs) as $permission => $subs){ foreach(Utils::promoteKeys($this->permSubs) as $permission => $subs){
if(count($subs) === 1 && isset($subs[spl_object_id($permissible)])){ if(count($subs) === 1 && isset($subs[spl_object_id($permissible)])){
@ -99,6 +112,8 @@ class PermissionManager{
} }
/** /**
* @deprecated Superseded by server chat broadcast channels
* @see Server::getBroadcastChannelSubscribers()
* @return PermissibleInternal[] * @return PermissibleInternal[]
*/ */
public function getPermissionSubscriptions(string $permission) : array{ public function getPermissionSubscriptions(string $permission) : array{

View File

@ -57,6 +57,7 @@ use pocketmine\event\player\PlayerDisplayNameChangeEvent;
use pocketmine\event\player\PlayerDropItemEvent; use pocketmine\event\player\PlayerDropItemEvent;
use pocketmine\event\player\PlayerEmoteEvent; use pocketmine\event\player\PlayerEmoteEvent;
use pocketmine\event\player\PlayerEntityInteractEvent; use pocketmine\event\player\PlayerEntityInteractEvent;
use pocketmine\event\player\PlayerEntityPickEvent;
use pocketmine\event\player\PlayerExhaustEvent; use pocketmine\event\player\PlayerExhaustEvent;
use pocketmine\event\player\PlayerGameModeChangeEvent; use pocketmine\event\player\PlayerGameModeChangeEvent;
use pocketmine\event\player\PlayerInteractEvent; use pocketmine\event\player\PlayerInteractEvent;
@ -109,6 +110,7 @@ use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket; use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\SetActorMotionPacket; use pocketmine\network\mcpe\protocol\SetActorMotionPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition; 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\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; 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_X = "SpawnX"; //TAG_Int
private const TAG_SPAWN_Y = "SpawnY"; //TAG_Int private const TAG_SPAWN_Y = "SpawnY"; //TAG_Int
private const TAG_SPAWN_Z = "SpawnZ"; //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_LEVEL = "Level"; //TAG_String
public const TAG_LAST_KNOWN_XUID = "LastKnownXUID"; //TAG_String public const TAG_LAST_KNOWN_XUID = "LastKnownXUID"; //TAG_String
@ -272,6 +278,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
private bool $respawnLocked = false; private bool $respawnLocked = false;
private ?Position $deathPosition = null;
//TODO: Abilities //TODO: Abilities
protected bool $autoJump = true; protected bool $autoJump = true;
protected bool $allowFlight = false; protected bool $allowFlight = false;
@ -390,6 +398,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_SPAWN_WORLD, ""))) instanceof World){ 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); $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{ public function getLeaveMessage() : Translatable|string{
@ -1031,6 +1042,30 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
} }
} }
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 * @return Position
*/ */
@ -1470,6 +1505,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return true; return true;
} }
public function canEat() : bool{
return $this->isCreative() || parent::canEat();
}
public function canBreathe() : bool{ public function canBreathe() : bool{
return $this->isCreative() || parent::canBreathe(); return $this->isCreative() || parent::canBreathe();
} }
@ -1709,6 +1748,38 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$ev->call(); $ev->call();
if(!$ev->isCancelled()){ if(!$ev->isCancelled()){
$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 !== -1){
if($existingSlot < $this->inventory->getHotbarSize()){ if($existingSlot < $this->inventory->getHotbarSize()){
$this->inventory->setHeldItemIndex($existingSlot); $this->inventory->setHeldItemIndex($existingSlot);
@ -1729,9 +1800,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
} }
} }
return true;
}
/** /**
* Performs a left-click (attack) action on the block. * Performs a left-click (attack) action on the block.
* *
@ -2302,6 +2370,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
unset($this->cursorInventory); unset($this->cursorInventory);
unset($this->craftingGrid); unset($this->craftingGrid);
$this->spawnPosition = null; $this->spawnPosition = null;
$this->deathPosition = null;
$this->blockBreakHandler = null; $this->blockBreakHandler = null;
parent::destroyCycles(); parent::destroyCycles();
} }
@ -2343,6 +2412,13 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$nbt->setInt(self::TAG_SPAWN_Z, $spawn->getFloorZ()); $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->setInt(self::TAG_GAME_MODE, GameModeIdMap::getInstance()->toId($this->gamemode));
$nbt->setLong(self::TAG_FIRST_PLAYED, $this->firstPlayed); $nbt->setLong(self::TAG_FIRST_PLAYED, $this->firstPlayed);
$nbt->setLong(self::TAG_LAST_PLAYED, (int) floor(microtime(true) * 1000)); $nbt->setLong(self::TAG_LAST_PLAYED, (int) floor(microtime(true) * 1000));
@ -2362,6 +2438,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
//main inventory and drops the rest on the ground. //main inventory and drops the rest on the ground.
$this->removeCurrentWindow(); $this->removeCurrentWindow();
$this->setDeathPosition($this->getPosition());
$ev = new PlayerDeathEvent($this, $this->getDrops(), $this->getXpDropAmount(), null); $ev = new PlayerDeathEvent($this, $this->getDrops(), $this->getXpDropAmount(), null);
$ev->call(); $ev->call();
@ -2490,6 +2568,17 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$properties->setPlayerFlag(PlayerMetadataFlags::SLEEP, $this->sleeping !== null); $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)); $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{ public function sendData(?array $targets, ?array $data = null) : void{

View File

@ -36,6 +36,8 @@ use const DIRECTORY_SEPARATOR;
/** /**
* Provides resources from the given plugin directory on disk. The path may be prefixed with a specific access protocol * 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. * to enable special types of access.
*
* @deprecated
*/ */
class DiskResourceProvider implements ResourceProvider{ class DiskResourceProvider implements ResourceProvider{
private string $file; private string $file;

View File

@ -23,6 +23,9 @@ declare(strict_types=1);
namespace pocketmine\plugin; namespace pocketmine\plugin;
/**
* @deprecated
*/
interface ResourceProvider{ interface ResourceProvider{
/** /**
* Gets an embedded resource on the plugin file. * Gets an embedded resource on the plugin file.

View File

@ -70,16 +70,17 @@ final class Promise{
* *
* @phpstan-template TPromiseValue * @phpstan-template TPromiseValue
* @phpstan-template TKey of array-key * @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>> * @phpstan-return Promise<array<TKey, TPromiseValue>>
*/ */
public static function all(array $promises) : Promise{ 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 */ /** @phpstan-var PromiseResolver<array<TKey, TPromiseValue>> $resolver */
$resolver = new PromiseResolver(); $resolver = new PromiseResolver();
if(count($promises) === 0){
$resolver->resolve([]);
return $resolver->getPromise();
}
$values = []; $values = [];
$toResolve = count($promises); $toResolve = count($promises);
$continue = true; $continue = true;

View File

@ -27,6 +27,7 @@ use pmmp\thread\Runnable;
use pmmp\thread\ThreadSafe; use pmmp\thread\ThreadSafe;
use pmmp\thread\ThreadSafeArray; use pmmp\thread\ThreadSafeArray;
use pocketmine\thread\NonThreadSafeValue; use pocketmine\thread\NonThreadSafeValue;
use pocketmine\timings\Timings;
use function array_key_exists; use function array_key_exists;
use function igbinary_serialize; use function igbinary_serialize;
use function igbinary_unserialize; use function igbinary_unserialize;
@ -67,7 +68,10 @@ abstract class AsyncTask extends Runnable{
*/ */
private static array $threadLocalStorage = []; private static array $threadLocalStorage = [];
/** @phpstan-var ThreadSafeArray<int, string>|null */ /**
* @phpstan-var ThreadSafeArray<int, string>|null
* @deprecated
*/
private ?ThreadSafeArray $progressUpdates = null; private ?ThreadSafeArray $progressUpdates = null;
private ThreadSafe|string|int|bool|null|float $result = null; private ThreadSafe|string|int|bool|null|float $result = null;
@ -78,7 +82,14 @@ abstract class AsyncTask extends Runnable{
public function run() : void{ public function run() : void{
$this->result = null; $this->result = null;
$timings = Timings::getAsyncTaskRunTimings($this);
$timings->startTiming();
try{
$this->onRun(); $this->onRun();
}finally{
$timings->stopTiming();
}
$this->finished = true; $this->finished = true;
AsyncWorker::getNotifier()->wakeupSleeper(); AsyncWorker::getNotifier()->wakeupSleeper();
@ -153,6 +164,8 @@ abstract class AsyncTask extends Runnable{
} }
/** /**
* @deprecated
*
* Call this method from {@link AsyncTask::onRun} (AsyncTask execution thread) to schedule a call to * 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. * {@link AsyncTask::onProgressUpdate} from the main thread with the given progress parameter.
* *
@ -167,6 +180,7 @@ abstract class AsyncTask extends Runnable{
} }
/** /**
* @deprecated
* @internal Only call from AsyncPool.php on the main thread * @internal Only call from AsyncPool.php on the main thread
*/ */
public function checkProgressUpdates() : void{ public function checkProgressUpdates() : void{
@ -179,6 +193,8 @@ abstract class AsyncTask extends Runnable{
} }
/** /**
* @deprecated
*
* Called from the main thread after {@link AsyncTask::publishProgress} is called. * Called from the main thread after {@link AsyncTask::publishProgress} is called.
* All {@link AsyncTask::publishProgress} calls should result in {@link AsyncTask::onProgressUpdate} calls before * All {@link AsyncTask::publishProgress} calls should result in {@link AsyncTask::onProgressUpdate} calls before
* {@link AsyncTask::onCompletion} is called. * {@link AsyncTask::onCompletion} is called.

View File

@ -26,8 +26,12 @@ namespace pocketmine\scheduler;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
abstract class Task{ abstract class Task{
/** @phpstan-var TaskHandler<static>|null */
private ?TaskHandler $taskHandler = null; private ?TaskHandler $taskHandler = null;
/**
* @phpstan-return TaskHandler<static>|null
*/
final public function getHandler() : ?TaskHandler{ final public function getHandler() : ?TaskHandler{
return $this->taskHandler; return $this->taskHandler;
} }
@ -36,6 +40,9 @@ abstract class Task{
return Utils::getNiceClassName($this); return Utils::getNiceClassName($this);
} }
/**
* @phpstan-param TaskHandler<static>|null $taskHandler
*/
final public function setHandler(?TaskHandler $taskHandler) : void{ final public function setHandler(?TaskHandler $taskHandler) : void{
if($this->taskHandler === null || $taskHandler === null){ if($this->taskHandler === null || $taskHandler === null){
$this->taskHandler = $taskHandler; $this->taskHandler = $taskHandler;

View File

@ -26,6 +26,9 @@ namespace pocketmine\scheduler;
use pocketmine\timings\Timings; use pocketmine\timings\Timings;
use pocketmine\timings\TimingsHandler; use pocketmine\timings\TimingsHandler;
/**
* @template TTask of Task
*/
class TaskHandler{ class TaskHandler{
protected int $nextRun; protected int $nextRun;
@ -36,6 +39,9 @@ class TaskHandler{
private string $taskName; private string $taskName;
private string $ownerName; private string $ownerName;
/**
* @phpstan-param TTask $task
*/
public function __construct( public function __construct(
protected Task $task, protected Task $task,
protected int $delay = -1, protected int $delay = -1,
@ -66,6 +72,9 @@ class TaskHandler{
$this->nextRun = $ticks; $this->nextRun = $ticks;
} }
/**
* @phpstan-return TTask
*/
public function getTask() : Task{ public function getTask() : Task{
return $this->task; return $this->task;
} }

View File

@ -33,12 +33,12 @@ use pocketmine\utils\ReversePriorityQueue;
class TaskScheduler{ class TaskScheduler{
private bool $enabled = true; private bool $enabled = true;
/** @phpstan-var ReversePriorityQueue<int, TaskHandler> */ /** @phpstan-var ReversePriorityQueue<int, TaskHandler<covariant Task>> */
protected ReversePriorityQueue $queue; protected ReversePriorityQueue $queue;
/** /**
* @var ObjectSet|TaskHandler[] * @var ObjectSet|TaskHandler[]
* @phpstan-var ObjectSet<TaskHandler> * @phpstan-var ObjectSet<TaskHandler<covariant Task>>
*/ */
protected ObjectSet $tasks; protected ObjectSet $tasks;
@ -51,18 +51,42 @@ class TaskScheduler{
$this->tasks = new ObjectSet(); $this->tasks = new ObjectSet();
} }
/**
* @phpstan-template TTask of Task
* @phpstan-param TTask $task
*
* @phpstan-return TaskHandler<TTask>
*/
public function scheduleTask(Task $task) : TaskHandler{ public function scheduleTask(Task $task) : TaskHandler{
return $this->addTask($task, -1, -1); 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{ public function scheduleDelayedTask(Task $task, int $delay) : TaskHandler{
return $this->addTask($task, $delay, -1); 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{ public function scheduleRepeatingTask(Task $task, int $period) : TaskHandler{
return $this->addTask($task, -1, $period); 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{ public function scheduleDelayedRepeatingTask(Task $task, int $delay, int $period) : TaskHandler{
return $this->addTask($task, $delay, $period); return $this->addTask($task, $delay, $period);
} }
@ -77,10 +101,19 @@ class TaskScheduler{
} }
} }
/**
* @phpstan-param TaskHandler<covariant Task> $task
*/
public function isQueued(TaskHandler $task) : bool{ public function isQueued(TaskHandler $task) : bool{
return $this->tasks->contains($task); 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{ private function addTask(Task $task, int $delay, int $period) : TaskHandler{
if(!$this->enabled){ if(!$this->enabled){
throw new \LogicException("Tried to schedule task to disabled scheduler"); throw new \LogicException("Tried to schedule task to disabled scheduler");
@ -99,6 +132,11 @@ class TaskScheduler{
return $this->handle(new TaskHandler($task, $delay, $period, $this->owner)); 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{ private function handle(TaskHandler $handler) : TaskHandler{
if($handler->isDelayed()){ if($handler->isDelayed()){
$nextRun = $this->currentTick + $handler->getDelay(); $nextRun = $this->currentTick + $handler->getDelay();
@ -128,7 +166,7 @@ class TaskScheduler{
} }
$this->currentTick = $currentTick; $this->currentTick = $currentTick;
while($this->isReady($this->currentTick)){ while($this->isReady($this->currentTick)){
/** @var TaskHandler $task */ /** @phpstan-var TaskHandler<covariant Task> $task */
$task = $this->queue->extract(); $task = $this->queue->extract();
if($task->isCancelled()){ if($task->isCancelled()){
$this->tasks->remove($task); $this->tasks->remove($task);

View File

@ -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);
}
}

View File

@ -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");
}
}
}

View File

@ -30,12 +30,14 @@ use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\ServerboundPacket; use pocketmine\network\mcpe\protocol\ServerboundPacket;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\scheduler\AsyncTask; use pocketmine\scheduler\AsyncTask;
use pocketmine\scheduler\Task;
use pocketmine\scheduler\TaskHandler; use pocketmine\scheduler\TaskHandler;
use function get_class; use function get_class;
use function str_starts_with; use function str_starts_with;
abstract class Timings{ abstract class Timings{
public const GROUP_MINECRAFT = "Minecraft"; public const GROUP_MINECRAFT = "Minecraft";
/** @deprecated No longer used */
public const GROUP_BREAKDOWN = "Minecraft - Breakdown"; public const GROUP_BREAKDOWN = "Minecraft - Breakdown";
private static bool $initialized = false; private static bool $initialized = false;
@ -123,11 +125,16 @@ abstract class Timings{
/** @var TimingsHandler[] */ /** @var TimingsHandler[] */
private static array $asyncTaskProgressUpdate = []; private static array $asyncTaskProgressUpdate = [];
/** @var TimingsHandler[] */ /** @var TimingsHandler[] */
private static array $asyncTaskCompletion = []; private static array $asyncTaskCompletion = [];
/** @var TimingsHandler[] */ /** @var TimingsHandler[] */
private static array $asyncTaskError = []; private static array $asyncTaskError = [];
private static TimingsHandler $asyncTaskWorkers;
/** @var TimingsHandler[] */
private static array $asyncTaskRun = [];
public static function init() : void{ public static function init() : void{
if(self::$initialized){ if(self::$initialized){
return; return;
@ -187,11 +194,17 @@ abstract class Timings{
self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync); self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync);
self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error 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::$playerCommand = new TimingsHandler("Player Command");
self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache"); 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{ public static function getScheduledTaskTimings(TaskHandler $task, int $period) : TimingsHandler{
self::init(); self::init();
$name = "Task: " . $task->getTaskName(); $name = "Task: " . $task->getTaskName();
@ -339,6 +352,9 @@ abstract class Timings{
return self::$asyncTaskCompletion[$taskClass]; return self::$asyncTaskCompletion[$taskClass];
} }
/**
* @deprecated No longer used
*/
public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{ public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
$taskClass = $task::class; $taskClass = $task::class;
if(!isset(self::$asyncTaskError[$taskClass])){ if(!isset(self::$asyncTaskError[$taskClass])){
@ -352,4 +368,18 @@ abstract class Timings{
return self::$asyncTaskError[$taskClass]; 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];
}
} }

View File

@ -23,20 +23,66 @@ declare(strict_types=1);
namespace pocketmine\timings; namespace pocketmine\timings;
use pmmp\thread\Thread as NativeThread;
use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver;
use pocketmine\Server; use pocketmine\Server;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
use function array_merge;
use function array_push;
use function hrtime; use function hrtime;
use function implode; use function implode;
use function spl_object_id; use function spl_object_id;
/**
* @phpstan-type CollectPromise Promise<list<string>>
*/
class TimingsHandler{ 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 bool $enabled = false;
private static int $timingStart = 0; private static int $timingStart = 0;
/** @return string[] */ /** @phpstan-var ObjectSet<\Closure(bool $enable) : void> */
public static function printTimings() : array{ 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 = []; $groups = [];
foreach(TimingsRecord::getAll() as $timings){ foreach(TimingsRecord::getAll() as $timings){
@ -49,7 +95,7 @@ class TimingsHandler{
$avg = $time / $count; $avg = $time / $count;
$group = $timings->getGroup(); $group = $timings->getGroup() . ($threadId !== null ? " ThreadId: $threadId" : "");
$groups[$group][] = implode(" ", [ $groups[$group][] = implode(" ", [
$timings->getName(), $timings->getName(),
"Time: $time", "Time: $time",
@ -72,6 +118,15 @@ class TimingsHandler{
} }
} }
return $result;
}
/**
* @return string[]
*/
private static function printFooter() : array{
$result = [];
$result[] = "# Version " . Server::getInstance()->getVersion(); $result[] = "# Version " . Server::getInstance()->getVersion();
$result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion(); $result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion();
@ -79,29 +134,95 @@ class TimingsHandler{
$sampleTime = hrtime(true) - self::$timingStart; $sampleTime = hrtime(true) - self::$timingStart;
$result[] = "Sample time $sampleTime (" . ($sampleTime / 1000000000) . "s)"; $result[] = "Sample time $sampleTime (" . ($sampleTime / 1000000000) . "s)";
return $result; 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{ public static function isEnabled() : bool{
return self::$enabled; return self::$enabled;
} }
public static function setEnabled(bool $enable = true) : void{ public static function setEnabled(bool $enable = true) : void{
if($enable === self::$enabled){
return;
}
self::$enabled = $enable; self::$enabled = $enable;
self::reload(); self::internalReload();
if(self::$toggleCallbacks !== null){
foreach(self::$toggleCallbacks as $callback){
$callback($enable);
}
}
} }
public static function getStartTime() : float{ public static function getStartTime() : float{
return self::$timingStart; return self::$timingStart;
} }
public static function reload() : void{ private static function internalReload() : void{
TimingsRecord::reset(); TimingsRecord::reset();
if(self::$enabled){ if(self::$enabled){
self::$timingStart = hrtime(true); 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{ public static function tick(bool $measure = true) : void{
if(self::$enabled){ if(self::$enabled){
TimingsRecord::tick($measure); TimingsRecord::tick($measure);

View File

@ -67,6 +67,8 @@ use function is_nan;
use function is_object; use function is_object;
use function is_string; use function is_string;
use function mb_check_encoding; use function mb_check_encoding;
use function mt_getrandmax;
use function mt_rand;
use function ob_end_clean; use function ob_end_clean;
use function ob_get_contents; use function ob_get_contents;
use function ob_start; use function ob_start;
@ -688,4 +690,12 @@ final class Utils{
//jit not available //jit not available
return null; 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();
}
} }

View File

@ -83,6 +83,7 @@ use pocketmine\ServerConfigGroup;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Limits; use pocketmine\utils\Limits;
use pocketmine\utils\ReversePriorityQueue; use pocketmine\utils\ReversePriorityQueue;
use pocketmine\utils\Utils;
use pocketmine\world\biome\Biome; use pocketmine\world\biome\Biome;
use pocketmine\world\biome\BiomeRegistry; use pocketmine\world\biome\BiomeRegistry;
use pocketmine\world\format\Chunk; use pocketmine\world\format\Chunk;
@ -120,7 +121,6 @@ use function get_class;
use function gettype; use function gettype;
use function is_a; use function is_a;
use function is_object; use function is_object;
use function lcg_value;
use function max; use function max;
use function microtime; use function microtime;
use function min; use function min;
@ -1998,10 +1998,10 @@ class World implements ChunkManager{
return null; 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->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(); $itemEntity->spawnToAll();
return $itemEntity; return $itemEntity;
@ -2018,9 +2018,9 @@ class World implements ChunkManager{
$orbs = []; $orbs = [];
foreach(ExperienceOrb::splitIntoOrbSizes($amount) as $split){ 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(); $orb->spawnToAll();
$orbs[] = $orb; $orbs[] = $orb;
@ -2173,20 +2173,26 @@ class World implements ChunkManager{
if($player !== null){ if($player !== null){
$ev = new PlayerInteractEvent($player, $item, $blockClicked, $clickVector, $face, PlayerInteractEvent::RIGHT_CLICK_BLOCK); $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()){ if($player->isSpectator()){
$ev->cancel(); //set it to cancelled so plugins can bypass this $ev->cancel(); //set it to cancelled so plugins can bypass this
} }
$ev->call(); $ev->call();
if(!$ev->isCancelled()){ 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; return true;
} }
if($ev->useItem()){
$result = $item->onInteractBlock($player, $blockReplace, $blockClicked, $face, $clickVector, $returnedItems); $result = $item->onInteractBlock($player, $blockReplace, $blockClicked, $face, $clickVector, $returnedItems);
if($result !== ItemUseResult::NONE){ if($result !== ItemUseResult::NONE){
return $result === ItemUseResult::SUCCESS; return $result === ItemUseResult::SUCCESS;
} }
}
}else{ }else{
return false; return false;
} }

View File

@ -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)];
}
}

View File

@ -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)];
}
}

View File

@ -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)];
}
}

View File

@ -44,6 +44,27 @@ fi
LOOPS=0 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 set +e
if [ "$DO_LOOP" == "yes" ]; then if [ "$DO_LOOP" == "yes" ]; then
@ -52,11 +73,15 @@ if [ "$DO_LOOP" == "yes" ]; then
echo "Restarted $LOOPS times" echo "Restarted $LOOPS times"
fi fi
"$PHP_BINARY" "$POCKETMINE_FILE" "$@" "$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 "To escape the loop, press CTRL+C now. Otherwise, wait 5 seconds for the server to restart."
echo "" echo ""
sleep 5 sleep 5
((LOOPS++)) ((LOOPS++))
done done
else else
exec "$PHP_BINARY" "$POCKETMINE_FILE" "$@" "$PHP_BINARY" "$POCKETMINE_FILE" "$@"
exitcode=$?
handle_exit_code $exitcode
exit $exitcode
fi fi

View File

@ -801,7 +801,7 @@ parameters:
path: ../../../src/scheduler/BulkCurlTask.php 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 count: 1
path: ../../../src/scheduler/TaskScheduler.php path: ../../../src/scheduler/TaskScheduler.php

View File

@ -20,6 +20,56 @@ parameters:
count: 1 count: 1
path: ../../../src/block/DoubleTallGrass.php 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\\.$#" message: "#^Call to function assert\\(\\) with false and 'unknown hit type' will always evaluate to false\\.$#"
count: 1 count: 1
@ -40,6 +90,11 @@ parameters:
count: 1 count: 1
path: ../../../src/plugin/PluginManager.php 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\\.$#" message: "#^Casting to int something that's already int\\.$#"
count: 1 count: 1

View File

@ -51,6 +51,7 @@ class BlockTypeIdsTest extends TestCase{
foreach(Utils::stringifyKeys(VanillaBlocks::getAll()) as $name => $block){ foreach(Utils::stringifyKeys(VanillaBlocks::getAll()) as $name => $block){
$expected = $block->getTypeId(); $expected = $block->getTypeId();
$actual = $reflect->getConstant($name); $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"); self::assertSame($expected, $actual, "VanillaBlocks::$name() does not match BlockTypeIds::$name");
} }
} }

View File

@ -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");
}
}
}

View File

@ -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");
}
}

View File

@ -24,6 +24,12 @@ declare(strict_types=1);
namespace pocketmine\event; namespace pocketmine\event;
use PHPUnit\Framework\TestCase; 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{ class HandlerListManagerTest extends TestCase{

View File

@ -21,7 +21,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace pocketmine\event; namespace pocketmine\event\fixtures;
use pocketmine\event\Event;
/** /**
* @allowHandle * @allowHandle

View File

@ -21,7 +21,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace pocketmine\event; namespace pocketmine\event\fixtures;
use pocketmine\event\Event;
abstract class TestAbstractEvent extends Event{ abstract class TestAbstractEvent extends Event{

View File

@ -21,8 +21,8 @@
declare(strict_types=1); declare(strict_types=1);
namespace pmmp\TesterPlugin\event; namespace pocketmine\event\fixtures;
class ChildEvent extends ParentEvent{ class TestChildEvent extends TestParentEvent{
} }

View File

@ -21,7 +21,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace pocketmine\event; namespace pocketmine\event\fixtures;
use pocketmine\event\Event;
class TestConcreteEvent extends Event{ class TestConcreteEvent extends Event{

View File

@ -21,7 +21,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace pocketmine\event; namespace pocketmine\event\fixtures;
class TestConcreteExtendsAbstractEvent extends TestAbstractEvent{ class TestConcreteExtendsAbstractEvent extends TestAbstractEvent{

View File

@ -21,7 +21,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace pocketmine\event; namespace pocketmine\event\fixtures;
class TestConcreteExtendsAllowHandleEvent extends TestAbstractAllowHandleEvent{ class TestConcreteExtendsAllowHandleEvent extends TestAbstractAllowHandleEvent{

Some files were not shown because too many files have changed in this diff Show More