Compare commits

..

6 Commits

Author SHA1 Message Date
016614e714 ... 2025-02-17 02:05:09 +00:00
9e7e76a609 Fix PHPStan 2025-02-17 02:01:52 +00:00
b1e4e44f77 Ensure overloaded members can't conflict with static ones and vice versa 2025-02-17 01:58:08 +00:00
f4f31b654b Generate parameter name 2025-02-17 01:56:13 +00:00
799dfecdd9 typo 2025-02-17 01:13:18 +00:00
1415e2492a First look at enum-overloaded registry members
this allows us to reference registry members with dynamic enum cases, which has a lot of potential applications.

this enables us to keep the blocks as unique types, but with some of the power of dynamic state properties.

this is a rough initial proof of concept; it will need to be improved for further integration.
2025-02-17 00:58:07 +00:00
60 changed files with 252 additions and 1564 deletions

View File

@ -53,7 +53,7 @@ jobs:
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
- name: Build image for tag
uses: docker/build-push-action@v6.15.0
uses: docker/build-push-action@v6.13.0
with:
push: true
context: ./pocketmine-mp
@ -66,7 +66,7 @@ jobs:
- name: Build image for major tag
if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v6.15.0
uses: docker/build-push-action@v6.13.0
with:
push: true
context: ./pocketmine-mp
@ -79,7 +79,7 @@ jobs:
- name: Build image for minor tag
if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v6.15.0
uses: docker/build-push-action@v6.13.0
with:
push: true
context: ./pocketmine-mp
@ -92,7 +92,7 @@ jobs:
- name: Build image for latest tag
if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v6.15.0
uses: docker/build-push-action@v6.13.0
with:
push: true
context: ./pocketmine-mp

View File

@ -165,7 +165,7 @@ jobs:
${{ github.workspace }}/core-permissions.rst
- name: Create draft release
uses: ncipollo/release-action@v1.16.0
uses: ncipollo/release-action@v1.15.0
id: create-draft
with:
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst
@ -182,8 +182,6 @@ jobs:
:information_source: Download the recommended PHP binary [here](${{ steps.php-binary-url.outputs.PHP_BINARY_URL }}).
:warning: Found a bug? Report it on our [issue tracker](${{ github.server_url }}/${{ github.repository }}/issues). **We can't fix bugs if you don't report them.**
- name: Post draft release URL on PR
if: github.event_name == 'pull_request_target'
uses: thollander/actions-comment-pull-request@v3

View File

@ -65,8 +65,6 @@ PocketMine-MP accepts community contributions! The following resources will be u
* [Building and running PocketMine-MP from source](BUILDING.md)
* [Contributing Guidelines](CONTRIBUTING.md)
New here? Check out [issues with the "Easy task" label](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22Easy%20task%22) for things you could work to familiarise yourself with the codebase.
## Donate
PocketMine-MP is free, but it requires a lot of time and effort from unpaid volunteers to develop. Donations enable us to keep delivering support for new versions and adding features your players love.

View File

@ -36,7 +36,7 @@ require dirname(__DIR__) . '/vendor/autoload.php';
*/
$options = [
"base_version" => VersionInfo::BASE_VERSION,
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION, limit: 2)[0],
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0],
"mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
"is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
"changelog_file_name" => function() : string{

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\build\update_registry_annotations;
use pocketmine\utils\OverloadedRegistryMember;
use pocketmine\utils\Utils;
use function basename;
use function class_exists;
@ -34,6 +35,7 @@ use function fwrite;
use function implode;
use function is_dir;
use function ksort;
use function lcfirst;
use function mb_strtoupper;
use function preg_match;
use function sprintf;
@ -47,11 +49,19 @@ if(count($argv) !== 2){
exit(1);
}
/**
* @phpstan-param \ReflectionClass<*> $class
*/
function makeTypehint(string $namespaceName, \ReflectionClass $class) : string{
return $class->getNamespaceName() === $namespaceName ? $class->getShortName() : '\\' . $class->getName();
}
/**
* @param object[] $members
* @phpstan-param array<string, object> $members
* @phpstan-param array<string, OverloadedRegistryMember> $overloadedMembers
*/
function generateMethodAnnotations(string $namespaceName, array $members) : string{
function generateMethodAnnotations(string $namespaceName, array $members, array $overloadedMembers) : string{
$selfName = basename(__FILE__);
$lines = ["/**"];
$lines[] = " * This doc-block is generated automatically, do not modify it manually.";
@ -69,14 +79,20 @@ function generateMethodAnnotations(string $namespaceName, array $members) : stri
}
if($reflect === false){
$typehint = "object";
}elseif($reflect->getNamespaceName() === $namespaceName){
$typehint = $reflect->getShortName();
}else{
$typehint = '\\' . $reflect->getName();
$typehint = makeTypehint($namespaceName, $reflect);
}
$accessor = mb_strtoupper($name);
$memberLines[$accessor] = sprintf($lineTmpl, $accessor, $typehint);
}
foreach(Utils::stringifyKeys($overloadedMembers) as $baseName => $member){
$accessor = mb_strtoupper($baseName);
$returnTypehint = makeTypehint($namespaceName, new \ReflectionClass($member->memberClass));
$enumReflect = new \ReflectionClass($member->enumClass);
$paramTypehint = makeTypehint($namespaceName, $enumReflect);
$memberLines[] = sprintf(" * @method static %s %s(%s \$%s)", $returnTypehint, $accessor, $paramTypehint, lcfirst($enumReflect->getShortName()));
}
ksort($memberLines, SORT_STRING);
foreach($memberLines as $line){
@ -107,7 +123,7 @@ function processFile(string $file) : void{
}
echo "Found registry in $file\n";
$replacement = generateMethodAnnotations($matches[1], $className::getAll());
$replacement = generateMethodAnnotations($matches[1], $className::getAll(), $className::getAllOverloaded());
$newContents = str_replace($docComment, $replacement, $contents);
if($newContents !== $contents){

View File

@ -36,17 +36,3 @@ It also allows creating new collapsible groups of items, and modifying or removi
- `BedrockDataFiles` now includes constants for folders at the top level of `BedrockData` as well as files.
- The structure of creative data in `BedrockData` was changed to accommodate item category and grouping information. `creativeitems.json` has been replaced by `creative/*.json`, which contain information about item grouping and also segregates item lists per category.
- New information was added to `required_item_list.json` in `BedrockData`, as the server is now required to send item component NBT data in some cases.
# 5.25.1
Released 26th February 2025.
## Fixes
- Fixed confusing exception message when a block-breaking tool has an efficiency value of zero.
- Fixed incorrect facing of doors since 1.21.60 (resulted in mismatched AABBs between client & server, rendering glitches etc.)
- Resource pack UUIDs are now validated on load. Previously, invalid UUIDs would be accepted, and potentially cause a server crash on player join.
# 5.25.2
Released 4th March 2025.
## Fixes
- Added limits to various `explode()` calls.

View File

@ -52,7 +52,7 @@
"symfony/filesystem": "~6.4.0"
},
"require-dev": {
"phpstan/phpstan": "2.1.6",
"phpstan/phpstan": "2.1.4",
"phpstan/phpstan-phpunit": "^2.0.0",
"phpstan/phpstan-strict-rules": "^2.0.0",
"phpunit/phpunit": "^10.5.24"

50
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "2a56fc6dee1dac2ade34d965aa49dc82",
"content-hash": "663122b8f03ef5ec6718a419923a0851",
"packages": [
{
"name": "adhocore/json-comment",
@ -67,16 +67,16 @@
},
{
"name": "brick/math",
"version": "0.12.2",
"version": "0.12.1",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "901eddb1e45a8e0f689302e40af871c181ecbe40"
"reference": "f510c0a40911935b77b86859eb5223d58d660df1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/901eddb1e45a8e0f689302e40af871c181ecbe40",
"reference": "901eddb1e45a8e0f689302e40af871c181ecbe40",
"url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1",
"reference": "f510c0a40911935b77b86859eb5223d58d660df1",
"shasum": ""
},
"require": {
@ -85,7 +85,7 @@
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^10.1",
"vimeo/psalm": "6.8.8"
"vimeo/psalm": "5.16.0"
},
"type": "library",
"autoload": {
@ -115,7 +115,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.12.2"
"source": "https://github.com/brick/math/tree/0.12.1"
},
"funding": [
{
@ -123,7 +123,7 @@
"type": "github"
}
],
"time": "2025-02-26T10:21:45+00:00"
"time": "2023-11-29T23:19:16+00:00"
},
{
"name": "netresearch/jsonmapper",
@ -1150,16 +1150,16 @@
"packages-dev": [
{
"name": "myclabs/deep-copy",
"version": "1.13.0",
"version": "1.12.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "024473a478be9df5fdaca2c793f2232fe788e414"
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414",
"reference": "024473a478be9df5fdaca2c793f2232fe788e414",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
"shasum": ""
},
"require": {
@ -1198,7 +1198,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.0"
"source": "https://github.com/myclabs/DeepCopy/tree/1.12.1"
},
"funding": [
{
@ -1206,7 +1206,7 @@
"type": "tidelift"
}
],
"time": "2025-02-12T12:17:51+00:00"
"time": "2024-11-08T17:47:46+00:00"
},
{
"name": "nikic/php-parser",
@ -1386,16 +1386,16 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.6",
"version": "2.1.4",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c"
"reference": "8f99e18eb775dbaf6460c95fa0b65312da9c746a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c",
"reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8f99e18eb775dbaf6460c95fa0b65312da9c746a",
"reference": "8f99e18eb775dbaf6460c95fa0b65312da9c746a",
"shasum": ""
},
"require": {
@ -1440,7 +1440,7 @@
"type": "github"
}
],
"time": "2025-02-19T15:46:42+00:00"
"time": "2025-02-10T08:25:21+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
@ -1864,16 +1864,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.45",
"version": "10.5.44",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "bd68a781d8e30348bc297449f5234b3458267ae8"
"reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8",
"reference": "bd68a781d8e30348bc297449f5234b3458267ae8",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1381c62769be4bb88fa4c5aec1366c7c66ca4f36",
"reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36",
"shasum": ""
},
"require": {
@ -1945,7 +1945,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.44"
},
"funding": [
{
@ -1961,7 +1961,7 @@
"type": "tidelift"
}
],
"time": "2025-02-06T16:08:12+00:00"
"time": "2025-01-31T07:00:38+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@ -11,10 +11,8 @@ includes:
rules:
- pocketmine\phpstan\rules\DeprecatedLegacyEnumAccessRule
- pocketmine\phpstan\rules\DisallowDynamicNewRule
- pocketmine\phpstan\rules\DisallowEnumComparisonRule
- pocketmine\phpstan\rules\DisallowForeachByReferenceRule
- pocketmine\phpstan\rules\ExplodeLimitRule
- pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule
# - pocketmine\phpstan\rules\ThreadedSupportedTypesRule

View File

@ -264,7 +264,7 @@ JIT_WARNING
$composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp');
if($composerGitHash !== null){
//we can't verify dependency versions if we were installed without using git
$currentGitHash = explode("-", VersionInfo::GIT_HASH(), 2)[0];
$currentGitHash = explode("-", VersionInfo::GIT_HASH())[0];
if($currentGitHash !== $composerGitHash){
critical_error("Composer dependencies and/or autoloader are out of sync.");
critical_error("- Current revision is $currentGitHash");

View File

@ -699,7 +699,7 @@ class Server{
public function removeOp(string $name) : void{
$lowercaseName = strtolower($name);
foreach(Utils::promoteKeys($this->operators->getAll()) as $operatorName => $_){
foreach($this->operators->getAll() as $operatorName => $_){
$operatorName = (string) $operatorName;
if($lowercaseName === strtolower($operatorName)){
$this->operators->remove($operatorName);

View File

@ -31,7 +31,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.25.3";
public const BASE_VERSION = "5.25.1";
public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable";

View File

@ -154,7 +154,7 @@ class BlockBreakInfo{
$efficiency = $item->getMiningEfficiency(($this->toolType & $item->getBlockToolType()) !== 0);
if($efficiency <= 0){
throw new \InvalidArgumentException(get_class($item) . " must have a positive mining efficiency, but got $efficiency");
throw new \InvalidArgumentException(get_class($item) . " has invalid mining efficiency: expected >= 0, got $efficiency");
}
$base /= $efficiency;

View File

@ -79,6 +79,10 @@ use function strtolower;
* @see build/generate-registry-annotations.php
* @generate-registry-docblock
*
* @method static Sapling SAPLING(\pocketmine\block\utils\SaplingType $saplingType)
* @method static Leaves LEAVES(\pocketmine\block\utils\LeavesType $leavesType)
* @method static FloorSign SIGN(\pocketmine\block\utils\WoodType $woodType)
* @method static WallSign WALL_SIGN(\pocketmine\block\utils\WoodType $woodType)
* @method static WoodenButton ACACIA_BUTTON()
* @method static WoodenDoor ACACIA_DOOR()
* @method static WoodenFence ACACIA_FENCE()
@ -1231,10 +1235,12 @@ final class VanillaBlocks{
$name = $saplingType->getDisplayName();
self::register(strtolower($saplingType->name) . "_sapling", fn(BID $id) => new Sapling($id, $name . " Sapling", $saplingTypeInfo, $saplingType));
}
self::registerOverloaded("sapling", SaplingType::class, Sapling::class);
foreach(LeavesType::cases() as $leavesType){
$name = $leavesType->getDisplayName();
self::register(strtolower($leavesType->name) . "_leaves", fn(BID $id) => new Leaves($id, $name . " Leaves", $leavesBreakInfo, $leavesType));
}
self::registerOverloaded("leaves", LeavesType::class, Leaves::class);
$sandstoneBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD));
self::register("red_sandstone_stairs", fn(BID $id) => new Stair($id, "Red Sandstone Stairs", $sandstoneBreakInfo));
@ -1370,22 +1376,12 @@ final class VanillaBlocks{
self::register($idName("pressure_plate"), fn(BID $id) => new WoodenPressurePlate($id, $name . " Pressure Plate", $woodenPressurePlateBreakInfo, $woodType, 20));
self::register($idName("trapdoor"), fn(BID $id) => new WoodenTrapdoor($id, $name . " Trapdoor", $woodenDoorBreakInfo, $woodType));
$signAsItem = match($woodType){
WoodType::OAK => VanillaItems::OAK_SIGN(...),
WoodType::SPRUCE => VanillaItems::SPRUCE_SIGN(...),
WoodType::BIRCH => VanillaItems::BIRCH_SIGN(...),
WoodType::JUNGLE => VanillaItems::JUNGLE_SIGN(...),
WoodType::ACACIA => VanillaItems::ACACIA_SIGN(...),
WoodType::DARK_OAK => VanillaItems::DARK_OAK_SIGN(...),
WoodType::MANGROVE => VanillaItems::MANGROVE_SIGN(...),
WoodType::CRIMSON => VanillaItems::CRIMSON_SIGN(...),
WoodType::WARPED => VanillaItems::WARPED_SIGN(...),
WoodType::CHERRY => VanillaItems::CHERRY_SIGN(...),
WoodType::PALE_OAK => VanillaItems::PALE_OAK_SIGN(...),
};
$signAsItem = fn() => VanillaItems::SIGN($woodType);
self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class);
self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class);
}
self::registerOverloaded("sign", WoodType::class, FloorSign::class);
self::registerOverloaded("wall_sign", WoodType::class, WallSign::class);
}
private static function registerMushroomBlocks() : void{

View File

@ -25,7 +25,6 @@ namespace pocketmine\block\inventory;
use pocketmine\inventory\SimpleInventory;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\item\Item;
use pocketmine\world\Position;
class AnvilInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
@ -38,12 +37,4 @@ class AnvilInventory extends SimpleInventory implements BlockInventory, Temporar
$this->holder = $holder;
parent::__construct(2);
}
public function getInput() : Item {
return $this->getItem(self::SLOT_INPUT);
}
public function getMaterial() : Item {
return $this->getItem(self::SLOT_MATERIAL);
}
}

View File

@ -62,10 +62,9 @@ class Sign extends Spawnable{
/**
* @return string[]
* @deprecated
*/
public static function fixTextBlob(string $blob) : array{
return array_slice(array_pad(explode("\n", $blob, limit: 5), 4, ""), 0, 4);
return array_slice(array_pad(explode("\n", $blob), 4, ""), 0, 4);
}
protected SignText $text;

View File

@ -1,66 +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\utils;
use pocketmine\crafting\AnvilCraftResult;
use pocketmine\item\Item;
use pocketmine\Server;
final class AnvilHelper{
private const COST_LIMIT = 39;
/**
* Attempts to calculate the result of an anvil operation.
*
* Returns null if the operation can't do anything.
*/
public static function calculateResult(Item $base, Item $material, ?string $customName, bool $isCreative) : ?AnvilCraftResult{
$recipe = Server::getInstance()->getCraftingManager()->matchAnvilRecipe($base, $material);
if($recipe === null){
return null;
}
$result = $recipe->getResultFor($base, $material);
if($result !== null){
$resultItem = $result->getOutput();
$xpCost = $result->getXpCost();
if(($customName === null || $customName === "") && $resultItem->hasCustomName()){
$xpCost++;
$resultItem->clearCustomName();
}elseif($customName !== null && $resultItem->getCustomName() !== $customName){
$xpCost++;
$resultItem->setCustomName($customName);
}
$result = new AnvilCraftResult($xpCost, $resultItem, $result->getSacrificeResult());
}
if($result === null || $result->getXpCost() <= 0 || ($result->getXpCost() > self::COST_LIMIT && !$isCreative)){
return null;
}
return $result;
}
}

View File

@ -79,7 +79,7 @@ class SignText{
* @throws \InvalidArgumentException if the text is not valid UTF-8
*/
public static function fromBlob(string $blob, ?Color $baseColor = null, bool $glowing = false) : SignText{
return new self(array_slice(array_pad(explode("\n", $blob, limit: self::LINE_COUNT + 1), self::LINE_COUNT, ""), 0, self::LINE_COUNT), $baseColor, $glowing);
return new self(array_slice(array_pad(explode("\n", $blob), self::LINE_COUNT, ""), 0, self::LINE_COUNT), $baseColor, $glowing);
}
/**

View File

@ -37,7 +37,6 @@ use function array_values;
use function explode;
use function implode;
use function str_replace;
use const PHP_INT_MAX;
abstract class Command{
@ -114,7 +113,7 @@ abstract class Command{
}
public function setPermission(?string $permission) : void{
$this->setPermissions($permission === null ? [] : explode(";", $permission, limit: PHP_INT_MAX));
$this->setPermissions($permission === null ? [] : explode(";", $permission));
}
public function testPermission(CommandSender $target, ?string $permission = null) : bool{

View File

@ -39,7 +39,6 @@ use function ksort;
use function min;
use function sort;
use function strtolower;
use const PHP_INT_MAX;
use const SORT_FLAG_CASE;
use const SORT_NATURAL;
@ -109,7 +108,7 @@ class HelpCommand extends VanillaCommand{
$usage = $cmd->getUsage();
$usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage;
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString, limit: PHP_INT_MAX)))
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString)))
->prefix(TextFormat::GOLD));
$aliases = $cmd->getAliases();

View File

@ -219,11 +219,7 @@ class ParticleCommand extends VanillaCommand{
break;
case "blockdust":
if($data !== null){
//to preserve the old unlimited explode behaviour, allow this to split into at most 5 parts
//this allows the 4th argument to be processed normally if given without forcing it to also consume
//any unexpected parts
//we probably ought to error in this case, but this will do for now
$d = explode("_", $data, limit: 5);
$d = explode("_", $data);
if(count($d) >= 3){
return new DustParticle(new Color(
((int) $d[0]) & 0xff,

View File

@ -62,7 +62,7 @@ class ConsoleCommandSender implements CommandSender{
$message = $this->getLanguage()->translate($message);
}
foreach(explode("\n", trim($message), limit: PHP_INT_MAX) as $line){
foreach(explode("\n", trim($message)) as $line){
Terminal::writeLine(TextFormat::GREEN . "Command output | " . TextFormat::addBase(TextFormat::WHITE, $line));
}
}

View File

@ -1,66 +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\crafting;
use pocketmine\item\Item;
/**
* This class is here to hold the result of an anvil crafting process.
*/
final class AnvilCraftResult{
/**
* @param Item|null $sacrificeResult If the given item is considered as null (count <= 0), the value will be set to null.
*/
public function __construct(
private int $xpCost,
private Item $output,
private ?Item $sacrificeResult
){
if($this->sacrificeResult !== null && $this->sacrificeResult->isNull()){
$this->sacrificeResult = null;
}
}
/**
* Represent the amount of experience points required to craft the output item.
*/
public function getXpCost() : int{
return $this->xpCost;
}
/**
* Represent the item given as output of the crafting process.
*/
public function getOutput() : Item{
return $this->output;
}
/**
* This result has to be null if the sacrifice slot need to be emptied.
* If not null, it represent the item that will be left in the sacrifice slot after the crafting process.
*/
public function getSacrificeResult() : ?Item{
return $this->sacrificeResult;
}
}

View File

@ -1,78 +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\crafting;
use pocketmine\item\Durable;
use pocketmine\item\ToolTier;
use pocketmine\item\VanillaArmorMaterials;
use pocketmine\item\VanillaItems;
use pocketmine\world\format\io\GlobalItemDataHandlers;
final class AnvilCraftingManagerDataFiller{
public static function fillData(CraftingManager $manager) : CraftingManager{
foreach([
[
VanillaItems::DIAMOND(),
[VanillaArmorMaterials::DIAMOND(), ToolTier::DIAMOND]
], [
VanillaItems::GOLD_INGOT(),
[VanillaArmorMaterials::GOLD(), ToolTier::GOLD]
], [
VanillaItems::IRON_INGOT(),
[VanillaArmorMaterials::IRON(), ToolTier::IRON]
], [
VanillaItems::NETHERITE_INGOT(),
[VanillaArmorMaterials::NETHERITE(), ToolTier::NETHERITE]
], [
VanillaItems::SCUTE(),
[VanillaArmorMaterials::TURTLE(), null]
], [
VanillaItems::LEATHER(),
[VanillaArmorMaterials::LEATHER(), null]
]
] as [$item, [$armorMaterial, $toolTier]]){
$manager->registerAnvilRecipe(new MaterialRepairRecipe(
new ArmorRecipeIngredient($armorMaterial),
new ExactRecipeIngredient($item)
));
if($toolTier !== null){
$manager->registerAnvilRecipe(new MaterialRepairRecipe(
new TieredToolRecipeIngredient($toolTier),
new ExactRecipeIngredient($item)
));
}
}
foreach(VanillaItems::getAll() as $item){
if($item instanceof Durable){
$itemId = GlobalItemDataHandlers::getSerializer()->serializeType($item)->getName();
$manager->registerAnvilRecipe(new ItemSelfCombineRecipe(
new MetaWildcardRecipeIngredient($itemId)
));
}
}
return $manager;
}
}

View File

@ -1,30 +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\crafting;
use pocketmine\item\Item;
interface AnvilRecipe{
public function getResultFor(Item $input, Item $material) : ?AnvilCraftResult;
}

View File

@ -1,50 +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\crafting;
use pocketmine\item\Armor;
use pocketmine\item\ArmorMaterial;
use pocketmine\item\Item;
use function spl_object_id;
class ArmorRecipeIngredient implements RecipeIngredient{
public function __construct(
private ArmorMaterial $material
){
}
public function getMaterial() : ArmorMaterial{ return $this->material; }
public function accepts(Item $item) : bool{
if($item->getCount() < 1){
return false;
}
return $item instanceof Armor && $item->getMaterial() === $this->material;
}
public function __toString() : string{
return "ArmorRecipeIngredient(ArmorMaterial@" . spl_object_id($this->material) . ")";
}
}

View File

@ -29,12 +29,8 @@ use pocketmine\nbt\TreeRoot;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\DestructorCallbackTrait;
use pocketmine\utils\ObjectSet;
use function array_shift;
use function count;
use function implode;
use function ksort;
use function spl_object_id;
use const SORT_STRING;
use function usort;
class CraftingManager{
use DestructorCallbackTrait;
@ -80,18 +76,6 @@ class CraftingManager{
*/
private array $brewingRecipeCache = [];
/**
* @var AnvilRecipe[]
* @phpstan-var list<AnvilRecipe>
*/
private array $anvilRecipes = [];
/**
* @var AnvilRecipe[][]
* @phpstan-var array<int, array<int, AnvilRecipe>>
*/
private array $anvilRecipeCache = [];
/** @phpstan-var ObjectSet<\Closure() : void> */
private ObjectSet $recipeRegisteredCallbacks;
@ -116,7 +100,6 @@ class CraftingManager{
/**
* Function used to arrange Shapeless Recipe ingredient lists into a consistent order.
* @deprecated
*/
public static function sort(Item $i1, Item $i2) : int{
//Use spaceship operator to compare each property, then try the next one if they are equivalent.
@ -125,30 +108,45 @@ class CraftingManager{
return $retval;
}
private static function hashOutput(Item $output) : string{
$write = new BinaryStream();
$write->putVarInt($output->getStateId());
$write->put((new LittleEndianNbtSerializer())->write(new TreeRoot($output->getNamedTag())));
/**
* @param Item[] $items
*
* @return Item[]
* @phpstan-return list<Item>
*/
private static function pack(array $items) : array{
$result = [];
return $write->getBuffer();
foreach($items as $item){
foreach($result as $otherItem){
if($item->canStackWith($otherItem)){
$otherItem->setCount($otherItem->getCount() + $item->getCount());
continue 2;
}
}
//No matching item found
$result[] = clone $item;
}
return $result;
}
/**
* @param Item[] $outputs
*/
private static function hashOutputs(array $outputs) : string{
if(count($outputs) === 1){
return self::hashOutput(array_shift($outputs));
}
$unique = [];
$outputs = self::pack($outputs);
usort($outputs, [self::class, "sort"]);
$result = new BinaryStream();
foreach($outputs as $o){
//count is not written because the outputs might be from multiple repetitions of a single recipe
//this reduces the accuracy of the hash, but it won't matter in most cases.
$hash = self::hashOutput($o);
$unique[$hash] = $hash;
$result->putVarInt($o->getStateId());
$result->put((new LittleEndianNbtSerializer())->write(new TreeRoot($o->getNamedTag())));
}
ksort($unique, SORT_STRING);
return implode("", $unique);
return $result->getBuffer();
}
/**
@ -199,14 +197,6 @@ class CraftingManager{
return $this->potionContainerChangeRecipes;
}
/**
* @return AnvilRecipe[][]
* @phpstan-return list<AnvilRecipe>
*/
public function getAnvilRecipes() : array{
return $this->anvilRecipes;
}
public function registerShapedRecipe(ShapedRecipe $recipe) : void{
$this->shapedRecipes[self::hashOutputs($recipe->getResults())][] = $recipe;
$this->craftingRecipeIndex[] = $recipe;
@ -241,14 +231,6 @@ class CraftingManager{
}
}
public function registerAnvilRecipe(AnvilRecipe $recipe) : void{
$this->anvilRecipes[] = $recipe;
foreach($this->recipeRegisteredCallbacks as $callback){
$callback();
}
}
/**
* @param Item[] $outputs
*/
@ -322,21 +304,4 @@ class CraftingManager{
return null;
}
public function matchAnvilRecipe(Item $input, Item $material) : ?AnvilRecipe{
$inputHash = $input->getStateId();
$materialHash = $material->getStateId();
$cached = $this->anvilRecipeCache[$inputHash][$materialHash] ?? null;
if($cached !== null){
return $cached;
}
foreach($this->anvilRecipes as $recipe){
if($recipe->getResultFor($input, $material) !== null){
return $this->anvilRecipeCache[$inputHash][$materialHash] = $recipe;
}
}
return null;
}
}

View File

@ -185,7 +185,6 @@ final class CraftingManagerFromDataHelper{
/**
* @param mixed[] $data
*
* @return object[]
*
* @phpstan-template TRecipeData of object
@ -211,7 +210,7 @@ final class CraftingManagerFromDataHelper{
$result = new CraftingManager();
foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath, 'shapeless_crafting.json'), ShapelessRecipeData::class) as $recipe){
$recipeType = match ($recipe->block) {
$recipeType = match($recipe->block){
"crafting_table" => ShapelessRecipeType::CRAFTING,
"stonecutter" => ShapelessRecipeType::STONECUTTER,
"smithing_table" => ShapelessRecipeType::SMITHING,
@ -272,7 +271,7 @@ final class CraftingManagerFromDataHelper{
));
}
foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath, 'smelting.json'), FurnaceRecipeData::class) as $recipe){
$furnaceType = match ($recipe->block) {
$furnaceType = match ($recipe->block){
"furnace" => FurnaceType::FURNACE,
"blast_furnace" => FurnaceType::BLAST_FURNACE,
"smoker" => FurnaceType::SMOKER,
@ -334,8 +333,6 @@ final class CraftingManagerFromDataHelper{
));
}
$result = AnvilCraftingManagerDataFiller::fillData($result);
//TODO: smithing
return $result;

View File

@ -1,123 +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\crafting;
use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\item\Durable;
use pocketmine\item\EnchantedBook;
use pocketmine\item\enchantment\AvailableEnchantmentRegistry;
use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\item\enchantment\Rarity;
use pocketmine\item\Item;
use function floor;
use function max;
use function min;
abstract class ItemCombineRecipe implements AnvilRecipe{
abstract protected function validate(Item $input, Item $material) : bool;
public function getResultFor(Item $input, Item $material) : ?AnvilCraftResult{
if($this->validate($input, $material)){
$result = (clone $input);
$xpCost = 0;
if($result instanceof Durable && $material instanceof Durable && $this->repair($result, $material)){
// The two items are compatible for repair
$xpCost = 2;
}
// combining enchantments
foreach($material->getEnchantments() as $instance){
$enchantment = $instance->getType();
$level = $instance->getLevel();
if(!AvailableEnchantmentRegistry::getInstance()->isAvailableForItem($enchantment, $input)){
continue;
}
if(($targetEnchantment = $input->getEnchantment($enchantment)) !== null){
// Enchant already present on the target item
$targetLevel = $targetEnchantment->getLevel();
$newLevel = ($targetLevel === $level ? $targetLevel + 1 : max($targetLevel, $level));
$level = min($newLevel, $enchantment->getMaxLevel());
$instance = new EnchantmentInstance($enchantment, $level);
}else{
// Check if the enchantment is compatible with the existing enchantments
foreach($input->getEnchantments() as $testedInstance){
$testedEnchantment = $testedInstance->getType();
if(!$testedEnchantment->isCompatibleWith($enchantment)){
$xpCost++;
//TODO: XP COST
continue 2;
}
}
}
$costAddition = match ($enchantment->getRarity()) {
Rarity::COMMON => 1,
Rarity::UNCOMMON => 2,
Rarity::RARE => 4,
Rarity::MYTHIC => 8,
default => throw new TransactionValidationException("Invalid rarity " . $enchantment->getRarity() . " found")
};
if($material instanceof EnchantedBook){
// Enchanted books are half as expensive to combine
$costAddition = max(1, $costAddition / 2);
}
$levelDifference = $instance->getLevel() - $input->getEnchantmentLevel($instance->getType());
$xpCost += (int) floor($costAddition * $levelDifference);
$result->addEnchantment($instance);
$xpCost += 2 ** $input->getAnvilRepairCost() - 1;
$xpCost += 2 ** $material->getAnvilRepairCost() - 1;
$result->setAnvilRepairCost(
max($result->getAnvilRepairCost(), $material->getAnvilRepairCost()) + 1
);
}
if($xpCost !== 0){
return new AnvilCraftResult($xpCost, $result, null);
}
}
return null;
}
private function repair(Durable $result, Durable $material) : bool{
$damage = $result->getDamage();
if($damage === 0){
return false;
}
$baseMaxDurability = $result->getMaxDurability();
$baseDurability = $baseMaxDurability - $damage;
$materialDurability = $material->getMaxDurability() - $material->getDamage();
$addDurability = (int) ($baseMaxDurability * 12 / 100);
$result->setDamage($baseMaxDurability - min(
$baseMaxDurability,
$baseDurability + $materialDurability + $addDurability
));
return true;
}
}

View File

@ -1,44 +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\crafting;
use pocketmine\item\Item;
/**
* Represent a recipe that repair an item with a material in an anvil.
*/
class ItemSelfCombineRecipe extends ItemCombineRecipe{
/**
* @param RecipeIngredient $target The item that will be concerned by the combinaison.
* The input and material have to be accepted by this ingredient to be able to combine them.
*/
public function __construct(
private RecipeIngredient $target
){
}
protected function validate(Item $input, Item $material) : bool{
return $this->target->accepts($input) && $this->target->accepts($material);
}
}

View File

@ -1,69 +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\crafting;
use pocketmine\item\Durable;
use pocketmine\item\Item;
use function ceil;
use function floor;
use function max;
use function min;
/**
* Represent a recipe that repair an item with a material in an anvil.
*/
class MaterialRepairRecipe implements AnvilRecipe{
public function __construct(
private RecipeIngredient $input,
private RecipeIngredient $material
){
}
public function getInput() : RecipeIngredient{
return $this->input;
}
public function getMaterial() : RecipeIngredient{
return $this->material;
}
public function getResultFor(Item $input, Item $material) : ?AnvilCraftResult{
if($this->input->accepts($input) && $this->material->accepts($material) && $input instanceof Durable){
$damage = $input->getDamage();
if($damage !== 0){
$quarter = min($damage, (int) floor($input->getMaxDurability() / 4));
$numberRepair = min($material->getCount(), (int) ceil($damage / $quarter));
$damage -= $quarter * $numberRepair;
return new AnvilCraftResult(
$numberRepair,
(clone $input)->setDamage(max(0, $damage)),
(clone $material)->setCount($material->getCount() - $numberRepair)
);
}
}
return null;
}
}

View File

@ -97,7 +97,6 @@ class ShapedRecipe implements CraftingRecipe{
$this->shape = $shape;
Utils::validateArrayValueType($ingredients, function(RecipeIngredient $_) : void{});
foreach(Utils::stringifyKeys($ingredients) as $char => $i){
if(!str_contains(implode($this->shape), $char)){
throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape");
@ -106,7 +105,6 @@ class ShapedRecipe implements CraftingRecipe{
$this->ingredientList[$char] = clone $i;
}
Utils::validateArrayValueType($results, function(Item $_) : void{});
$this->results = Utils::cloneObjectArray($results);
}

View File

@ -53,9 +53,7 @@ class ShapelessRecipe implements CraftingRecipe{
if(count($ingredients) > 9){
throw new \InvalidArgumentException("Shapeless recipes cannot have more than 9 ingredients");
}
Utils::validateArrayValueType($ingredients, function(RecipeIngredient $_) : void{});
$this->ingredients = $ingredients;
Utils::validateArrayValueType($results, function(Item $_) : void{});
$this->results = Utils::cloneObjectArray($results);
}

View File

@ -1,49 +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\crafting;
use pocketmine\item\Item;
use pocketmine\item\TieredTool;
use pocketmine\item\ToolTier;
class TieredToolRecipeIngredient implements RecipeIngredient{
public function __construct(
private ToolTier $tier
){
}
public function getTier() : ToolTier{ return $this->tier; }
public function accepts(Item $item) : bool{
if($item->getCount() < 1){
return false;
}
return $item instanceof TieredTool && $item->getTier() === $this->tier;
}
public function __toString() : string{
return "TieredToolRecipeIngredient(" . $this->tier->name . ")";
}
}

View File

@ -131,8 +131,7 @@ final class BlockStateDeserializerHelper{
//TODO: check if these need any special treatment to get the appropriate data to both halves of the door
return $block
->setTop($in->readBool(BlockStateNames::UPPER_BLOCK_BIT))
//a door facing "east" is actually facing north - thanks mojang
->setFacing(Facing::rotateY($in->readCardinalHorizontalFacing(), clockwise: false))
->setFacing($in->readCardinalHorizontalFacing())
->setHingeRight($in->readBool(BlockStateNames::DOOR_HINGE_BIT))
->setOpen($in->readBool(BlockStateNames::OPEN_BIT));
}

View File

@ -100,8 +100,7 @@ final class BlockStateSerializerHelper{
public static function encodeDoor(Door $block, Writer $out) : Writer{
return $out
->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop())
//a door facing north is encoded as "east"
->writeCardinalHorizontalFacing(Facing::rotateY($block->getFacing(), clockwise: true))
->writeCardinalHorizontalFacing($block->getFacing())
->writeBool(BlockStateNames::DOOR_HINGE_BIT, $block->isHingeRight())
->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen());
}

View File

@ -1,86 +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\event\player;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\item\Item;
use pocketmine\player\Player;
/**
* Called when a player uses an anvil (renaming, repairing, combining items).
* This event is called once per action even if multiple tasks are performed at once.
*/
class PlayerUseAnvilEvent extends PlayerEvent implements Cancellable{
use CancellableTrait;
public function __construct(
Player $player,
private Item $baseItem,
private ?Item $materialItem,
private Item $resultItem,
private ?string $customName,
private int $xpCost
){
$this->player = $player;
}
/**
* Returns the item that the player is using as the base item (left slot).
*/
public function getBaseItem() : Item{
return $this->baseItem;
}
/**
* Returns the item that the player is using as the material item (right slot), or null if there is no material item
* (e.g. when renaming an item).
*/
public function getMaterialItem() : ?Item{
return $this->materialItem;
}
/**
* Returns the item that the player will receive as a result of the anvil operation.
*/
public function getResultItem() : Item{
return $this->resultItem;
}
/**
* Returns the custom name that the player is setting on the item, or null if the player is not renaming the item.
*
* This value is defined when the base item is already renamed.
*/
public function getCustomName() : ?string{
return $this->customName;
}
/**
* Returns the amount of XP levels that the player will spend on this anvil operation.
*/
public function getXpCost() : int{
return $this->xpCost;
}
}

View File

@ -1,155 +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\inventory\transaction;
use pocketmine\block\Anvil;
use pocketmine\block\inventory\AnvilInventory;
use pocketmine\block\utils\AnvilHelper;
use pocketmine\block\VanillaBlocks;
use pocketmine\crafting\AnvilCraftResult;
use pocketmine\event\player\PlayerUseAnvilEvent;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\sound\AnvilBreakSound;
use pocketmine\world\sound\AnvilUseSound;
use function count;
use function mt_rand;
class AnvilTransaction extends InventoryTransaction{
private ?Item $baseItem = null;
private ?Item $materialItem = null;
public function __construct(
Player $source,
private readonly AnvilCraftResult $expectedResult,
private readonly ?string $customName
){
parent::__construct($source);
}
private function validateFiniteResources(int $xpSpent) : void{
$expectedXpCost = $this->expectedResult->getXpCost();
if($xpSpent !== $expectedXpCost){
throw new TransactionValidationException("Expected the amount of xp spent to be $expectedXpCost, but received $xpSpent");
}
$xpLevel = $this->source->getXpManager()->getXpLevel();
if($xpLevel < $expectedXpCost){
throw new TransactionValidationException("Player's XP level $xpLevel is less than the required XP level $expectedXpCost");
}
}
private function validateInputs(Item $base, Item $material, Item $expectedOutput) : ?int{
$calculAttempt = AnvilHelper::calculateResult($base, $material, $this->customName, $this->source->isCreative());
if($calculAttempt === null){
return null;
}
$result = $calculAttempt->getOutput();
if(!$result->equalsExact($expectedOutput)){
return null;
}
$this->baseItem = $base;
$this->materialItem = $material;
return $calculAttempt->getXpCost();
}
public function validate() : void{
if(count($this->actions) < 1){
throw new TransactionValidationException("Transaction must have at least one action to be executable");
}
/** @var Item[] $inputs */
$inputs = [];
/** @var Item[] $outputs */
$outputs = [];
$this->matchItems($outputs, $inputs);
if(($outputCount = count($outputs)) !== 1){
throw new TransactionValidationException("Expected 1 output item, but received $outputCount");
}
$outputItem = $outputs[0];
if(($inputCount = count($inputs)) < 1){
throw new TransactionValidationException("Expected at least 1 input item, but received $inputCount");
}
if($inputCount > 2){
throw new TransactionValidationException("Expected at most 2 input items, but received $inputCount");
}
if(count($inputs) < 2){
$xpCost = $this->validateInputs($inputs[0], VanillaItems::AIR(), $outputItem) ??
throw new TransactionValidationException("Inputs do not match expected result");
}else{
$xpCost = $this->validateInputs($inputs[0], $inputs[1], $outputItem) ??
$this->validateInputs($inputs[1], $inputs[0], $outputItem) ??
throw new TransactionValidationException("Inputs do not match expected result");
}
if($this->source->hasFiniteResources()){
$this->validateFiniteResources($xpCost);
}
}
public function execute() : void{
parent::execute();
if($this->source->hasFiniteResources()){
$this->source->getXpManager()->subtractXpLevels($this->expectedResult->getXpCost());
}
$inventory = $this->source->getCurrentWindow();
if($inventory instanceof AnvilInventory){
$world = $inventory->getHolder()->getWorld();
if(mt_rand(0, 12) === 0){
$anvilBlock = $world->getBlock($inventory->getHolder());
if($anvilBlock instanceof Anvil){
$newDamage = $anvilBlock->getDamage() + 1;
if($newDamage > Anvil::VERY_DAMAGED){
$newBlock = VanillaBlocks::AIR();
$world->addSound($inventory->getHolder(), new AnvilBreakSound());
}else{
$newBlock = $anvilBlock->setDamage($newDamage);
}
$world->setBlock($inventory->getHolder(), $newBlock);
}
}
$world->addSound($inventory->getHolder(), new AnvilUseSound());
}
}
protected function callExecuteEvent() : bool{
if($this->baseItem === null){
throw new AssumptionFailedError("Expected that baseItem are not null before executing the event");
}
$ev = new PlayerUseAnvilEvent($this->source, $this->baseItem, $this->materialItem, $this->expectedResult->getOutput(), $this->customName, $this->expectedResult->getXpCost());
$ev->call();
return !$ev->isCancelled();
}
}

View File

@ -69,7 +69,6 @@ class Item implements \JsonSerializable{
public const TAG_DISPLAY_NAME = "Name";
public const TAG_DISPLAY_LORE = "Lore";
public const TAG_REPAIR_COST = "RepairCost";
public const TAG_KEEP_ON_DEATH = "minecraft:keep_on_death";
@ -85,7 +84,6 @@ class Item implements \JsonSerializable{
protected string $customName = "";
/** @var string[] */
protected array $lore = [];
protected int $anvilRepairCost = 0;
/** TODO: this needs to die in a fire */
protected ?CompoundTag $blockEntityTag = null;
@ -284,30 +282,6 @@ class Item implements \JsonSerializable{
return $this;
}
/**
* Returns the anvil repair cost of the item.
* This value is used in anvil to determine the XP cost of repairing the item.
*
* In vanilla, this value is stored in the "RepairCost" tag.
*/
public function getAnvilRepairCost() : int{
return $this->anvilRepairCost;
}
/**
* Sets the anvil repair cost value of the item.
* This value is used in anvil to determine the XP cost of repairing the item.
* Higher cost means more XP is required to repair the item.
*
* In vanilla, this value is stored in the "RepairCost" tag.
*
* @return $this
*/
public function setAnvilRepairCost(int $cost) : self{
$this->anvilRepairCost = $cost;
return $this;
}
/**
* @throws NbtException
*/
@ -364,7 +338,6 @@ class Item implements \JsonSerializable{
}
$this->keepOnDeath = $tag->getByte(self::TAG_KEEP_ON_DEATH, 0) !== 0;
$this->anvilRepairCost = $tag->getInt(self::TAG_REPAIR_COST, 0);
}
protected function serializeCompoundTag(CompoundTag $tag) : void{
@ -433,12 +406,6 @@ class Item implements \JsonSerializable{
}else{
$tag->removeTag(self::TAG_KEEP_ON_DEATH);
}
if($this->anvilRepairCost > 0){
$tag->setInt(self::TAG_REPAIR_COST, $this->anvilRepairCost);
}else{
$tag->removeTag(self::TAG_REPAIR_COST);
}
}
public function getCount() : int{

View File

@ -111,8 +111,7 @@ final class LegacyStringToItemParser{
*/
public function parse(string $input) : Item{
$key = $this->reprocess($input);
//TODO: this should be limited to 2 parts, but 3 preserves old behaviour when given a string like 351:4:1
$b = explode(":", $key, limit: 3);
$b = explode(":", $key);
if(!isset($b[1])){
$meta = 0;

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\block\utils\RecordType;
use pocketmine\block\utils\WoodType;
use pocketmine\block\VanillaBlocks as Blocks;
use pocketmine\entity\Entity;
use pocketmine\entity\Location;
@ -47,6 +48,8 @@ use function strtolower;
* @see build/generate-registry-annotations.php
* @generate-registry-docblock
*
* @method static Boat BOAT(BoatType $boatType)
* @method static ItemBlockWallOrFloor SIGN(\pocketmine\block\utils\WoodType $woodType)
* @method static Boat ACACIA_BOAT()
* @method static ItemBlockWallOrFloor ACACIA_SIGN()
* @method static ItemBlock AIR()
@ -396,7 +399,6 @@ final class VanillaItems{
//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", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::ACACIA_SIGN(), Blocks::ACACIA_WALL_SIGN()));
self::register("amethyst_shard", fn(IID $id) => new Item($id, "Amethyst Shard"));
self::register("apple", fn(IID $id) => new Apple($id, "Apple"));
self::register("arrow", fn(IID $id) => new Arrow($id, "Arrow"));
@ -406,7 +408,6 @@ final class VanillaItems{
self::register("beetroot", fn(IID $id) => new Beetroot($id, "Beetroot"));
self::register("beetroot_seeds", fn(IID $id) => new BeetrootSeeds($id, "Beetroot Seeds"));
self::register("beetroot_soup", fn(IID $id) => new BeetrootSoup($id, "Beetroot Soup"));
self::register("birch_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::BIRCH_SIGN(), Blocks::BIRCH_WALL_SIGN()));
self::register("blaze_powder", fn(IID $id) => new Item($id, "Blaze Powder"));
self::register("blaze_rod", fn(IID $id) => new BlazeRod($id, "Blaze Rod"));
self::register("bleach", fn(IID $id) => new Item($id, "Bleach"));
@ -420,7 +421,6 @@ final class VanillaItems{
self::register("bucket", fn(IID $id) => new Bucket($id, "Bucket"));
self::register("carrot", fn(IID $id) => new Carrot($id, "Carrot"));
self::register("charcoal", fn(IID $id) => new Coal($id, "Charcoal"));
self::register("cherry_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::CHERRY_SIGN(), Blocks::CHERRY_WALL_SIGN()));
self::register("chemical_aluminium_oxide", fn(IID $id) => new Item($id, "Aluminium Oxide"));
self::register("chemical_ammonia", fn(IID $id) => new Item($id, "Ammonia"));
self::register("chemical_barium_sulphate", fn(IID $id) => new Item($id, "Barium Sulphate"));
@ -475,8 +475,6 @@ final class VanillaItems{
self::register("cookie", fn(IID $id) => new Cookie($id, "Cookie"));
self::register("copper_ingot", fn(IID $id) => new Item($id, "Copper Ingot"));
self::register("coral_fan", fn(IID $id) => new CoralFan($id));
self::register("crimson_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::CRIMSON_SIGN(), Blocks::CRIMSON_WALL_SIGN()));
self::register("dark_oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::DARK_OAK_SIGN(), Blocks::DARK_OAK_WALL_SIGN()));
self::register("diamond", fn(IID $id) => new Item($id, "Diamond"));
self::register("disc_fragment_5", fn(IID $id) => new Item($id, "Disc Fragment (5)"));
self::register("dragon_breath", fn(IID $id) => new Item($id, "Dragon's Breath"));
@ -516,12 +514,10 @@ final class VanillaItems{
self::register("ink_sac", fn(IID $id) => new Item($id, "Ink Sac"));
self::register("iron_ingot", fn(IID $id) => new Item($id, "Iron Ingot"));
self::register("iron_nugget", fn(IID $id) => new Item($id, "Iron Nugget"));
self::register("jungle_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::JUNGLE_SIGN(), Blocks::JUNGLE_WALL_SIGN()));
self::register("lapis_lazuli", fn(IID $id) => new Item($id, "Lapis Lazuli"));
self::register("lava_bucket", fn(IID $id) => new LiquidBucket($id, "Lava Bucket", Blocks::LAVA()));
self::register("leather", fn(IID $id) => new Item($id, "Leather"));
self::register("magma_cream", fn(IID $id) => new Item($id, "Magma Cream"));
self::register("mangrove_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::MANGROVE_SIGN(), Blocks::MANGROVE_WALL_SIGN()));
self::register("medicine", fn(IID $id) => new Medicine($id, "Medicine"));
self::register("melon", fn(IID $id) => new Melon($id, "Melon"));
self::register("melon_seeds", fn(IID $id) => new MelonSeeds($id, "Melon Seeds"));
@ -539,9 +535,7 @@ final class VanillaItems{
self::register("netherite_scrap", fn(IID $id) => new class($id, "Netherite Scrap") extends Item{
public function isFireProof() : bool{ return true; }
});
self::register("oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::OAK_SIGN(), Blocks::OAK_WALL_SIGN()));
self::register("painting", fn(IID $id) => new PaintingItem($id, "Painting"));
self::register("pale_oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::PALE_OAK_SIGN(), Blocks::PALE_OAK_WALL_SIGN()));
self::register("paper", fn(IID $id) => new Item($id, "Paper"));
self::register("phantom_membrane", fn(IID $id) => new Item($id, "Phantom Membrane"));
self::register("pitcher_pod", fn(IID $id) => new PitcherPod($id, "Pitcher Pod"));
@ -597,7 +591,6 @@ final class VanillaItems{
self::register("snowball", fn(IID $id) => new Snowball($id, "Snowball"));
self::register("spider_eye", fn(IID $id) => new SpiderEye($id, "Spider Eye"));
self::register("splash_potion", fn(IID $id) => new SplashPotion($id, "Splash Potion"));
self::register("spruce_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::SPRUCE_SIGN(), Blocks::SPRUCE_WALL_SIGN()));
self::register("spyglass", fn(IID $id) => new Spyglass($id, "Spyglass"));
self::register("steak", fn(IID $id) => new Steak($id, "Steak"));
self::register("stick", fn(IID $id) => new Stick($id, "Stick"));
@ -607,7 +600,6 @@ final class VanillaItems{
self::register("sweet_berries", fn(IID $id) => new SweetBerries($id, "Sweet Berries"));
self::register("torchflower_seeds", fn(IID $id) => new TorchflowerSeeds($id, "Torchflower Seeds"));
self::register("totem", fn(IID $id) => new Totem($id, "Totem of Undying"));
self::register("warped_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::WARPED_SIGN(), Blocks::WARPED_WALL_SIGN()));
self::register("water_bucket", fn(IID $id) => new LiquidBucket($id, "Water Bucket", Blocks::WATER()));
self::register("wheat", fn(IID $id) => new Item($id, "Wheat"));
self::register("wheat_seeds", fn(IID $id) => new WheatSeeds($id, "Wheat Seeds"));
@ -618,6 +610,12 @@ final class VanillaItems{
//boat type is static, because different types of wood may have different properties
self::register(strtolower($type->name) . "_boat", fn(IID $id) => new Boat($id, $type->getDisplayName() . " Boat", $type));
}
self::registerOverloaded("boat", BoatType::class, Boat::class);
foreach(WoodType::cases() as $woodType){
self::register("{$woodType->name}_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::SIGN($woodType), Blocks::WALL_SIGN($woodType)));
}
self::registerOverloaded("sign", WoodType::class, ItemBlockWallOrFloor::class);
}
private static function registerSpawnEggs() : void{

View File

@ -71,7 +71,7 @@ class Language{
foreach($files as $file){
try{
$code = explode(".", $file, limit: 2)[0];
$code = explode(".", $file)[0];
$strings = self::loadLang($path, $code);
if(isset($strings[KnownTranslationKeys::LANGUAGE_NAME])){
$result[$code] = $strings[KnownTranslationKeys::LANGUAGE_NAME];

View File

@ -72,11 +72,9 @@ final class JwtUtils{
* @throws JwtException
*/
public static function split(string $jwt) : array{
//limit of 4 allows us to detect too many parts without having to split the string up into a potentially large
//number of parts
$v = explode(".", $jwt, limit: 4);
$v = explode(".", $jwt);
if(count($v) !== 3){
throw new JwtException("Expected exactly 3 JWT parts delimited by a period");
throw new JwtException("Expected exactly 3 JWT parts, got " . count($v));
}
return [$v[0], $v[1], $v[2]]; //workaround phpstan bug
}

View File

@ -23,14 +23,11 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\handler;
use pocketmine\block\inventory\AnvilInventory;
use pocketmine\block\inventory\EnchantInventory;
use pocketmine\block\utils\AnvilHelper;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\CreateItemAction;
use pocketmine\inventory\transaction\action\DestroyItemAction;
use pocketmine\inventory\transaction\action\DropItemAction;
use pocketmine\inventory\transaction\AnvilTransaction;
use pocketmine\inventory\transaction\CraftingTransaction;
use pocketmine\inventory\transaction\EnchantingTransaction;
use pocketmine\inventory\transaction\InventoryTransaction;
@ -42,7 +39,6 @@ use pocketmine\network\mcpe\protocol\types\inventory\ContainerUIIds;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftingConsumeInputStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftingCreateSpecificResultStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftRecipeAutoStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftRecipeOptionalStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftRecipeStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CreativeCreateStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\DeprecatedCraftingResultsStackRequestAction;
@ -294,7 +290,7 @@ class ItemStackRequestExecutor{
* @throws ItemStackRequestProcessException
*/
private function assertDoingCrafting() : void{
if(!$this->specialTransaction instanceof CraftingTransaction && !$this->specialTransaction instanceof EnchantingTransaction && !$this->specialTransaction instanceof AnvilTransaction){
if(!$this->specialTransaction instanceof CraftingTransaction && !$this->specialTransaction instanceof EnchantingTransaction){
if($this->specialTransaction === null){
throw new ItemStackRequestProcessException("Expected CraftRecipe or CraftRecipeAuto action to precede this action");
}else{
@ -352,15 +348,6 @@ class ItemStackRequestExecutor{
}
}elseif($action instanceof CraftRecipeAutoStackRequestAction){
$this->beginCrafting($action->getRecipeId(), $action->getRepetitions());
}elseif($action instanceof CraftRecipeOptionalStackRequestAction){
$window = $this->player->getCurrentWindow();
if($window instanceof AnvilInventory){
$result = AnvilHelper::calculateResult($window->getInput(), $window->getMaterial(), $this->request->getFilterStrings()[0] ?? null, $this->player->isCreative());
if($result !== null){
$this->specialTransaction = new AnvilTransaction($this->player, $result, $this->request->getFilterStrings()[0] ?? null);
$this->setNextCreatedItem($result->getOutput());
}
}
}elseif($action instanceof CraftingConsumeInputStackRequestAction){
$this->assertDoingCrafting();
$this->removeItemFromSlot($action->getSource(), $action->getCount()); //output discarded - we allow CraftingTransaction to verify the balance

View File

@ -148,9 +148,7 @@ class BanEntry{
return null;
}
//we expect at most 5 parts, but accept 6 in case of an extra unexpected delimiter
//we don't want to include unexpected data into the ban reason
$parts = explode("|", trim($str), limit: 6);
$parts = explode("|", trim($str));
$entry = new BanEntry(trim(array_shift($parts)));
if(count($parts) > 0){
$entry->setCreated(self::parseDate(array_shift($parts)));

View File

@ -26,7 +26,6 @@ namespace pocketmine\resourcepacks;
use pocketmine\utils\Config;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use Ramsey\Uuid\Uuid;
use Symfony\Component\Filesystem\Path;
use function array_keys;
use function copy;
@ -104,14 +103,9 @@ class ResourcePackManager{
try{
$newPack = $this->loadPackFromPath(Path::join($this->path, $pack));
$index = strtolower($newPack->getPackId());
if(!Uuid::isValid($index)){
//TODO: we should use Uuid in ResourcePack interface directly but that would break BC
//for now we need to validate this here to make sure it doesn't cause crashes later on
throw new ResourcePackException("Invalid UUID ($index)");
}
$this->uuidList[$index] = $newPack;
$this->resourcePacks[] = $newPack;
$index = strtolower($newPack->getPackId());
$this->uuidList[$index] = $newPack;
$keyPath = Path::join($this->path, $pack . ".key");
if(file_exists($keyPath)){
@ -196,11 +190,6 @@ class ResourcePackManager{
$resourcePacks = [];
foreach($resourceStack as $pack){
$uuid = strtolower($pack->getPackId());
if(!Uuid::isValid($uuid)){
//TODO: we should use Uuid in ResourcePack interface directly but that would break BC
//for now we need to validate this here to make sure it doesn't cause crashes later on
throw new \InvalidArgumentException("Invalid resource pack UUID ($uuid)");
}
if(isset($uuidList[$uuid])){
throw new \InvalidArgumentException("Cannot load two resource pack with the same UUID ($uuid)");
}

View File

@ -54,7 +54,6 @@ use const CASE_LOWER;
use const JSON_BIGINT_AS_STRING;
use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;
use const PHP_INT_MAX;
use const YAML_UTF8_ENCODING;
/**
@ -340,7 +339,7 @@ class Config{
}
public function setNested(string $key, mixed $value) : void{
$vars = explode(".", $key, limit: PHP_INT_MAX);
$vars = explode(".", $key);
$base = array_shift($vars);
if(!isset($this->config[$base])){
@ -367,7 +366,7 @@ class Config{
return $this->nestedCache[$key];
}
$vars = explode(".", $key, limit: PHP_INT_MAX);
$vars = explode(".", $key);
$base = array_shift($vars);
if(isset($this->config[$base])){
$base = $this->config[$base];
@ -391,7 +390,7 @@ class Config{
$this->nestedCache = [];
$this->changed = true;
$vars = explode(".", $key, limit: PHP_INT_MAX);
$vars = explode(".", $key);
$currentNode = &$this->config;
while(count($vars) > 0){
@ -496,7 +495,7 @@ class Config{
*/
public static function parseList(string $content) : array{
$result = [];
foreach(explode("\n", trim(str_replace("\r\n", "\n", $content)), limit: PHP_INT_MAX) as $v){
foreach(explode("\n", trim(str_replace("\r\n", "\n", $content))) as $v){
$v = trim($v);
if($v === ""){
continue;

View File

@ -60,7 +60,6 @@ use const CURLOPT_RETURNTRANSFER;
use const CURLOPT_SSL_VERIFYHOST;
use const CURLOPT_SSL_VERIFYPEER;
use const CURLOPT_TIMEOUT_MS;
use const PHP_INT_MAX;
use const SOCK_DGRAM;
use const SOL_UDP;
@ -228,10 +227,9 @@ class Internet{
$rawHeaders = substr($raw, 0, $headerSize);
$body = substr($raw, $headerSize);
$headers = [];
//TODO: explore if we can set these limits lower
foreach(explode("\r\n\r\n", $rawHeaders, limit: PHP_INT_MAX) as $rawHeaderGroup){
foreach(explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup){
$headerGroup = [];
foreach(explode("\r\n", $rawHeaderGroup, limit: PHP_INT_MAX) as $line){
foreach(explode("\r\n", $rawHeaderGroup) as $line){
$nameValue = explode(":", $line, 2);
if(isset($nameValue[1])){
$headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]);

View File

@ -21,21 +21,19 @@
declare(strict_types=1);
namespace pocketmine\crafting;
namespace pocketmine\utils;
use pocketmine\item\Item;
final class OverloadedRegistryMember{
/**
* Represent a recipe that repair an item with a material in an anvil.
*/
class ItemDifferentCombineRecipe extends ItemCombineRecipe{
/**
* @phpstan-template TMember of object
* @phpstan-param class-string<covariant \UnitEnum> $enumClass
* @phpstan-param class-string<TMember> $memberClass
* @phpstan-param array<string, string> $enumToMemberMap
*/
public function __construct(
private RecipeIngredient $base,
private RecipeIngredient $material
){
}
protected function validate(Item $input, Item $material) : bool{
return $this->base->accepts($input) && $this->material->accepts($material);
}
public readonly string $enumClass,
public readonly string $memberClass,
public readonly array $enumToMemberMap
){}
}

View File

@ -24,7 +24,9 @@ declare(strict_types=1);
namespace pocketmine\utils;
use function array_map;
use function assert;
use function count;
use function get_class;
use function mb_strtoupper;
use function preg_match;
@ -42,6 +44,12 @@ trait RegistryTrait{
*/
private static $members = null;
/**
* @var OverloadedRegistryMember[]
* @phpstan-var array<string, OverloadedRegistryMember>
*/
private static $overloadedMembers = [];
private static function verifyName(string $name) : void{
if(preg_match('/^(?!\d)[A-Za-z\d_]+$/u', $name) === 0){
throw new \InvalidArgumentException("Invalid member name \"$name\", should only contain letters, numbers and underscores, and must not start with a number");
@ -59,12 +67,37 @@ trait RegistryTrait{
}
self::verifyName($name);
$upperName = mb_strtoupper($name);
if(isset(self::$members[$upperName])){
if(isset(self::$members[$upperName]) || isset(self::$overloadedMembers[$upperName])){
throw new \InvalidArgumentException("\"$upperName\" is already reserved");
}
self::$members[$upperName] = $member;
}
/**
* @phpstan-template TEnum of \UnitEnum
* @phpstan-param class-string<TEnum> $enumClass
* @phpstan-param class-string $returnClass
*/
private static function registerOverloaded(string $baseName, string $enumClass, string $returnClass) : void{
self::verifyName($baseName);
$upperName = mb_strtoupper($baseName);
if(isset(self::$members[$upperName]) || isset(self::$overloadedMembers[$upperName])){
throw new \InvalidArgumentException("\"$upperName\" is already reserved");
}
$enumToMemberMap = [];
foreach($enumClass::cases() as $case){
$memberName = mb_strtoupper($case->name . "_" . $baseName);
if(!isset(self::$members[$memberName])){
throw new \LogicException("\"$memberName\" needs to be registered to define overloaded member with enum $enumClass");
}
if(!self::$members[$memberName] instanceof $returnClass){
throw new \LogicException("\"$memberName\" doesn't satisfy the desired type $returnClass");
}
$enumToMemberMap[$case->name] = $memberName;
}
self::$overloadedMembers[$upperName] = new OverloadedRegistryMember($enumClass, $returnClass, $enumToMemberMap);
}
/**
* Inserts default entries into the registry.
*
@ -111,20 +144,36 @@ trait RegistryTrait{
* @return object
*/
public static function __callStatic($name, $arguments){
if(count($arguments) > 0){
throw new \ArgumentCountError("Expected exactly 0 arguments, " . count($arguments) . " passed");
}
if(count($arguments) === 0){
//fast path
if(self::$members !== null && isset(self::$members[$name])){
return self::preprocessMember(self::$members[$name]);
}
//fast path
if(self::$members !== null && isset(self::$members[$name])){
return self::preprocessMember(self::$members[$name]);
}
//fallback
try{
return self::_registryFromString($name);
}catch(\InvalidArgumentException $e){
throw new \Error($e->getMessage(), 0, $e);
}
}elseif(count($arguments) === 1 && $arguments[0] instanceof \UnitEnum){
$enum = $arguments[0];
self::checkInit();
//fallback
try{
return self::_registryFromString($name);
}catch(\InvalidArgumentException $e){
throw new \Error($e->getMessage(), 0, $e);
$overloadInfo = self::$overloadedMembers[$name] ?? self::$overloadedMembers[mb_strtoupper($name)] ?? null;
if($overloadInfo !== null){
if($enum::class !== $overloadInfo->enumClass){
throw new \Error("Wrong enum type for overloaded registry member " . self::class . "::" . mb_strtoupper($name) . "($overloadInfo->enumClass)");
}
$memberName = $overloadInfo->enumToMemberMap[$enum->name];
assert(self::$members !== null);
return self::preprocessMember(self::$members[$memberName]);
}
throw new \Error("No such overloaded registry member: " . self::class . "::" . mb_strtoupper($name) . "(" . get_class($enum) . ")");
}else{
throw new \LogicException("Incorrect arguments passed to overloaded registry member: " . self::class . "::" . mb_strtoupper($name));
}
}
@ -136,4 +185,12 @@ trait RegistryTrait{
self::checkInit();
return array_map(self::preprocessMember(...), self::$members ?? throw new AssumptionFailedError(self::class . "::checkInit() did not initialize self::\$members correctly"));
}
/**
* @return string[]
* @phpstan-return array<string, OverloadedRegistryMember>
*/
public static function getAllOverloaded() : array{
return self::$overloadedMembers;
}
}

View File

@ -369,7 +369,7 @@ final class Utils{
debug_zval_dump($value);
$contents = ob_get_contents();
if($contents === false) throw new AssumptionFailedError("ob_get_contents() should never return false here");
$ret = explode("\n", $contents, limit: 2);
$ret = explode("\n", $contents);
ob_end_clean();
if(preg_match('/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){
@ -587,7 +587,7 @@ final class Utils{
* @phpstan-param \Closure(TMemberType) : void $validator
*/
public static function validateArrayValueType(array $array, \Closure $validator) : void{
foreach(Utils::promoteKeys($array) as $k => $v){
foreach($array as $k => $v){
try{
$validator($v);
}catch(\TypeError $e){

View File

@ -26,12 +26,10 @@ namespace pocketmine\world\generator;
use pocketmine\data\bedrock\BiomeIds;
use pocketmine\item\LegacyStringToItemParser;
use pocketmine\item\LegacyStringToItemParserException;
use pocketmine\world\World;
use function array_map;
use function explode;
use function preg_match;
use function preg_match_all;
use const PHP_INT_MAX;
/**
* @internal
@ -72,7 +70,7 @@ final class FlatGeneratorOptions{
*/
public static function parseLayers(string $layers) : array{
$result = [];
$split = array_map('\trim', explode(',', $layers, limit: World::Y_MAX - World::Y_MIN));
$split = array_map('\trim', explode(',', $layers));
$y = 0;
$itemParser = LegacyStringToItemParser::getInstance();
foreach($split as $line){
@ -98,7 +96,7 @@ final class FlatGeneratorOptions{
* @throws InvalidGeneratorOptionsException
*/
public static function parsePreset(string $presetString) : self{
$preset = explode(";", $presetString, limit: 4);
$preset = explode(";", $presetString);
$blocks = $preset[1] ?? "";
$biomeId = (int) ($preset[2] ?? BiomeIds::PLAINS);
$optionsString = $preset[3] ?? "";
@ -111,10 +109,9 @@ final class FlatGeneratorOptions{
$params = true;
if($matches[3][$i] !== ""){
$params = [];
$p = explode(" ", $matches[3][$i], limit: PHP_INT_MAX);
$p = explode(" ", $matches[3][$i]);
foreach($p as $k){
//TODO: this should be limited to 2 parts, but 3 preserves old behaviour when given e.g. treecount=20=1
$k = explode("=", $k, limit: 3);
$k = explode("=", $k);
if(isset($k[1])){
$params[$k[0]] = $k[1];
}

View File

@ -18,12 +18,6 @@ parameters:
count: 1
path: ../../../src/Server.php
-
message: '#^Dynamic new is not allowed\.$#'
identifier: pocketmine.new.dynamic
count: 1
path: ../../../src/Server.php
-
message: '#^Method pocketmine\\Server\:\:getCommandAliases\(\) should return array\<string, list\<string\>\> but returns array\<string, array\<mixed\>\>\.$#'
identifier: return.type
@ -55,16 +49,16 @@ parameters:
path: ../../../src/VersionInfo.php
-
message: '#^Static property pocketmine\\VersionInfo\:\:\$gitHash \(string\|null\) does not accept mixed\.$#'
identifier: assign.propertyType
message: '#^Method pocketmine\\VersionInfo\:\:GIT_HASH\(\) should return string but returns mixed\.$#'
identifier: return.type
count: 1
path: ../../../src/VersionInfo.php
-
message: '#^Dynamic new is not allowed\.$#'
identifier: pocketmine.new.dynamic
message: '#^Static property pocketmine\\VersionInfo\:\:\$gitHash \(string\|null\) does not accept mixed\.$#'
identifier: assign.propertyType
count: 1
path: ../../../src/block/Block.php
path: ../../../src/VersionInfo.php
-
message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#'
@ -522,12 +516,6 @@ parameters:
count: 3
path: ../../../src/block/tile/Spawnable.php
-
message: '#^Dynamic new is not allowed\.$#'
identifier: pocketmine.new.dynamic
count: 1
path: ../../../src/block/tile/TileFactory.php
-
message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getPotentialLightAt\(\) expects int, float\|int given\.$#'
identifier: argument.type
@ -930,6 +918,24 @@ parameters:
count: 1
path: ../../../src/plugin/PluginDescription.php
-
message: '#^Parameter \#1 \$haystack of function stripos expects string, mixed given\.$#'
identifier: argument.type
count: 1
path: ../../../src/plugin/PluginDescription.php
-
message: '#^Parameter \#2 \$subject of function preg_match expects string, mixed given\.$#'
identifier: argument.type
count: 1
path: ../../../src/plugin/PluginDescription.php
-
message: '#^Parameter \#3 \$subject of function str_replace expects array\<string\>\|string, mixed given\.$#'
identifier: argument.type
count: 1
path: ../../../src/plugin/PluginDescription.php
-
message: '#^Property pocketmine\\plugin\\PluginDescription\:\:\$authors \(array\<string\>\) does not accept list\<mixed\>\.$#'
identifier: assign.propertyType
@ -955,13 +961,13 @@ parameters:
path: ../../../src/plugin/PluginManager.php
-
message: '#^Dynamic new is not allowed\.$#'
identifier: pocketmine.new.dynamic
message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getPackSize\(\) should return int but returns int\<0, max\>\|false\.$#'
identifier: return.type
count: 1
path: ../../../src/plugin/PluginManager.php
path: ../../../src/resourcepacks/ZippedResourcePack.php
-
message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getPackSize\(\) should return int but returns int\<0, max\>\|false\.$#'
message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getSha256\(\) should return string but returns string\|false\.$#'
identifier: return.type
count: 1
path: ../../../src/resourcepacks/ZippedResourcePack.php
@ -1003,7 +1009,7 @@ parameters:
path: ../../../src/utils/Config.php
-
message: '#^Parameter \#1 \$config of static method pocketmine\\utils\\Config\:\:writeProperties\(\) expects array\<int\|string, bool\|float\|int\|string\>, array\<mixed\> given\.$#'
message: '#^Parameter \#1 \$config of static method pocketmine\\utils\\Config\:\:writeProperties\(\) expects array\<int\|string, bool\|float\|int\|string\>, array\<int\|string, mixed\> given\.$#'
identifier: argument.type
count: 1
path: ../../../src/utils/Config.php
@ -1272,12 +1278,6 @@ parameters:
count: 1
path: ../../../src/world/format/io/region/RegionLoader.php
-
message: '#^Dynamic new is not allowed\.$#'
identifier: pocketmine.new.dynamic
count: 1
path: ../../../src/world/generator/GeneratorRegisterTask.php
-
message: '#^Method pocketmine\\world\\generator\\biome\\BiomeSelector\:\:pickBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#'
identifier: return.type

View File

@ -114,6 +114,12 @@ parameters:
count: 1
path: ../../../src/crash/CrashDump.php
-
message: '#^Call to function assert\(\) with false and ''unknown hit type'' will always evaluate to false\.$#'
identifier: function.impossibleType
count: 1
path: ../../../src/entity/projectile/Projectile.php
-
message: '#^Property pocketmine\\item\\WritableBookBase\:\:\$pages \(list\<pocketmine\\item\\WritableBookPage\>\) does not accept non\-empty\-array\<int, pocketmine\\item\\WritableBookPage\>\.$#'
identifier: assign.propertyType

View File

@ -1,55 +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\phpstan\rules;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\New_;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
/**
* @phpstan-implements Rule<New_>
*/
final class DisallowDynamicNewRule implements Rule{
public function getNodeType() : string{
return New_::class;
}
public function processNode(Node $node, Scope $scope) : array{
/** @var New_ $node */
if($node->class instanceof Expr){
return [
RuleErrorBuilder::message("Dynamic new is not allowed.")
->tip("For factories, use closures instead. Closures can implement custom logic, are statically analyzable, and don't restrict the constructor signature.")
->identifier("pocketmine.new.dynamic")
->build()
];
}
return [];
}
}

View File

@ -1,92 +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\phpstan\rules;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function count;
/**
* @phpstan-implements Rule<FuncCall>
*/
final class ExplodeLimitRule implements Rule{
private ReflectionProvider $reflectionProvider;
public function __construct(
ReflectionProvider $reflectionProvider
){
$this->reflectionProvider = $reflectionProvider;
}
public function getNodeType() : string{
return FuncCall::class;
}
public function processNode(Node $node, Scope $scope) : array{
if(!$node->name instanceof Name){
return [];
}
if(!$this->reflectionProvider->hasFunction($node->name, $scope)){
return [];
}
$functionReflection = $this->reflectionProvider->getFunction($node->name, $scope);
if($functionReflection->getName() !== 'explode'){
return [];
}
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
$scope,
$node->getArgs(),
$functionReflection->getVariants(),
$functionReflection->getNamedArgumentsVariants(),
);
$normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node);
if($normalizedFuncCall === null){
return [];
}
$count = count($normalizedFuncCall->getArgs());
if($count !== 3){
return [
RuleErrorBuilder::message('The $limit parameter of explode() must be set to prevent malicious client data wasting resources.')
->identifier("pocketmine.explode.limit")
->build()
];
}
return [];
}
}

View File

@ -1,194 +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\crafting;
use Generator;
use PHPUnit\Framework\TestCase;
use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function floor;
class AnvilCraftTest extends TestCase{
public static function materialRepairRecipeProvider() : Generator{
yield "No repair available" => [
VanillaItems::DIAMOND_PICKAXE(),
VanillaItems::DIAMOND(),
null
];
yield "Repair one damage" => [
VanillaItems::DIAMOND_PICKAXE()->setDamage(1),
VanillaItems::DIAMOND(),
new AnvilCraftResult(1, VanillaItems::DIAMOND_PICKAXE(), null)
];
yield "Repair one damage with more materials than expected" => [
VanillaItems::DIAMOND_PICKAXE()->setDamage(1),
VanillaItems::DIAMOND()->setCount(2),
new AnvilCraftResult(1, VanillaItems::DIAMOND_PICKAXE(), VanillaItems::DIAMOND())
];
$diamondPickaxeQuarter = (int) floor(VanillaItems::DIAMOND_PICKAXE()->getMaxDurability() / 4);
yield "Repair one quarter" => [
VanillaItems::DIAMOND_PICKAXE()->setDamage($diamondPickaxeQuarter),
VanillaItems::DIAMOND()->setCount(1),
new AnvilCraftResult(1, VanillaItems::DIAMOND_PICKAXE(), null)
];
yield "Repair one quarter plus 1" => [
VanillaItems::DIAMOND_PICKAXE()->setDamage($diamondPickaxeQuarter + 1),
VanillaItems::DIAMOND()->setCount(1),
new AnvilCraftResult(1, VanillaItems::DIAMOND_PICKAXE()->setDamage(1), null)
];
yield "Repair more than one quarter" => [
VanillaItems::DIAMOND_PICKAXE()->setDamage($diamondPickaxeQuarter * 2),
VanillaItems::DIAMOND()->setCount(2),
new AnvilCraftResult(2, VanillaItems::DIAMOND_PICKAXE(), null)
];
yield "Repair more than one quarter with more materials than expected" => [
VanillaItems::DIAMOND_PICKAXE()->setDamage($diamondPickaxeQuarter * 2),
VanillaItems::DIAMOND()->setCount(3),
new AnvilCraftResult(2, VanillaItems::DIAMOND_PICKAXE(), VanillaItems::DIAMOND()->setCount(1))
];
}
/**
* @dataProvider materialRepairRecipeProvider
*/
public function testMaterialRepairRecipe(Item $base, Item $material, ?AnvilCraftResult $expected) : void{
$recipe = new MaterialRepairRecipe(
new WildcardRecipeIngredient(),
new WildcardRecipeIngredient()
);
self::assertAnvilCraftResultEquals($expected, $recipe->getResultFor($base, $material));
}
public static function itemSelfCombineRecipeProvider() : Generator{
yield "Combine two identical items without damage and enchants" => [
VanillaItems::DIAMOND_PICKAXE(),
VanillaItems::DIAMOND_PICKAXE(),
null
];
yield "Enchant on base item and no enchants on material with no damage" => [
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
VanillaItems::DIAMOND_PICKAXE(),
null
];
yield "No enchant on base item and one enchant on material" => [
VanillaItems::DIAMOND_PICKAXE(),
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
new AnvilCraftResult(2, VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)), null)
];
yield "Combine two identical items with damage" => [
VanillaItems::DIAMOND_PICKAXE()->setDamage(10),
VanillaItems::DIAMOND_PICKAXE(),
new AnvilCraftResult(1, VanillaItems::DIAMOND_PICKAXE()->setDamage(0), null)
];
yield "Combine two identical items with damage for material" => [
VanillaItems::DIAMOND_PICKAXE(),
VanillaItems::DIAMOND_PICKAXE()->setDamage(10),
null
];
yield "Combine two identical items with different enchantments" => [
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 2)),
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
new AnvilCraftResult(2, VanillaItems::DIAMOND_PICKAXE()
->addEnchantment(new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 2))
->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
null)
];
yield "Combine two identical items with different enchantments with damage" => [
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 2))->setDamage(10),
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
new AnvilCraftResult(4, VanillaItems::DIAMOND_PICKAXE()
->addEnchantment(new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 2))
->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
null)
];
yield "Combine two identical items with different enchantments with damage for material" => [
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 2)),
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1))->setDamage(10),
new AnvilCraftResult(2, VanillaItems::DIAMOND_PICKAXE()
->addEnchantment(new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 2))
->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
null)
];
yield "Combine two identical items with same enchantment level" => [
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 1)),
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 1)),
new AnvilCraftResult(8, VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 2)), null)
];
yield "Combine two identical items with same enchantment level and damage" => [
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 1))->setDamage(10),
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 1)),
new AnvilCraftResult(10, VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 2))->setDamage(0), null)
];
yield "Combine two identical items with same enchantment level and damage for material" => [
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 1)),
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 1))->setDamage(10),
new AnvilCraftResult(8, VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 2)), null)
];
}
/**
* @dataProvider itemSelfCombineRecipeProvider
*/
public function testItemSelfCombineRecipe(Item $base, Item $combined, ?AnvilCraftResult $expected) : void{
$recipe = new ItemSelfCombineRecipe(new WildcardRecipeIngredient());
self::assertAnvilCraftResultEquals($expected, $recipe->getResultFor($base, $combined));
}
private static function assertAnvilCraftResultEquals(?AnvilCraftResult $expected, ?AnvilCraftResult $actual) : void{
if($expected === null){
self::assertNull($actual, "Recipe did not match expected result");
return;
}else{
self::assertNotNull($actual, "Recipe did not match expected result");
}
self::assertEquals($expected->getXpCost(), $actual->getXpCost(), "XP cost did not match expected result");
self::assertTrue($expected->getOutput()->equalsExact($actual->getOutput()), "Recipe output did not match expected result");
$sacrificeResult = $expected->getSacrificeResult();
if($sacrificeResult !== null){
$resultExpected = $actual->getSacrificeResult();
self::assertNotNull($resultExpected, "Recipe sacrifice result did not match expected result");
self::assertTrue($sacrificeResult->equalsExact($resultExpected), "Recipe sacrifice result did not match expected result");
}else{
self::assertNull($actual->getSacrificeResult(), "Recipe sacrifice result did not match expected result");
}
}
}

View File

@ -1,37 +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\crafting;
use pocketmine\item\Item;
final class WildcardRecipeIngredient implements RecipeIngredient{
public function __toString() : string{
return "WildcardRecipeIngredient()";
}
public function accepts(Item $item) : bool{
return true;
}
}

View File

@ -51,10 +51,8 @@ use function array_unique;
use function array_values;
use function count;
use function dirname;
use function fclose;
use function file_exists;
use function file_put_contents;
use function fopen;
use function fwrite;
use function get_class;
use function get_debug_type;
@ -887,44 +885,6 @@ function cmdUpdateAll(array $argv) : int{
return 0;
}
/**
* @param string[] $argv
*/
function cmdDumpTable(array $argv) : int{
$tableFile = $argv[2];
$outputFile = $argv[3];
$output = fopen($outputFile, 'wb');
if($output === false){
fwrite(STDERR, "Failed to open output file: $outputFile\n");
return 1;
}
$table = loadUpgradeTableFromFile($tableFile, reverse: false);
ksort($table, SORT_STRING);
foreach(Utils::stringifyKeys($table) as $oldName => $mappings){
fwrite($output, "---------- MAPPING LIST: $oldName ----------\n");
foreach($mappings as $mapping){
$oldNbt = $mapping->old->toVanillaNbt();
$oldNbt->setInt("version", $mapping->new->getVersion());
//intentionally not reused result of toVanillaNbt otherwise output wouldn't include version
fwrite($output, "OLD: " . $mapping->old->toVanillaNbt() . "\n");
if(!$oldNbt->equals($mapping->new->toVanillaNbt())){
fwrite($output, "NEW: " . $mapping->new->toVanillaNbt() . "\n");
}else{
fwrite($output, "NEW: version bump only (" . $mapping->new->getVersion() . ")\n");
}
fwrite($output, "-----\n");
}
}
fclose($output);
\GlobalLogger::get()->info("Table dump file $outputFile generated successfully.");
return 0;
}
/**
* @param string[] $argv
*/
@ -933,8 +893,7 @@ function main(array $argv) : int{
"generate" => [["palette upgrade table file", "schema output file"], cmdGenerate(...)],
"test" => [["palette upgrade table file", "schema output file"], cmdTest(...)],
"update" => [["schema input file", "old palette file", "updated schema output file"], cmdUpdate(...)],
"update-all" => [["schema folder", "path to BlockPaletteArchive"], cmdUpdateAll(...)],
"dump-table" => [["palette upgrade table file", "txt output file"], cmdDumpTable(...)]
"update-all" => [["schema folder", "path to BlockPaletteArchive"], cmdUpdateAll(...)]
];
$selected = $argv[1] ?? null;

View File

@ -624,7 +624,7 @@ function main(array $argv) : int{
}
foreach($packets as $lineNum => $line){
$parts = explode(':', $line, limit: 3);
$parts = explode(':', $line);
if(count($parts) !== 2){
fwrite(STDERR, 'Wrong packet format at line ' . ($lineNum + 1) . ', expected read:base64 or write:base64');
return 1;