mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-11 20:10:17 +00:00
Compare commits
6 Commits
feat/anvil
...
overloaded
Author | SHA1 | Date | |
---|---|---|---|
016614e714 | |||
9e7e76a609 | |||
b1e4e44f77 | |||
f4f31b654b | |||
799dfecdd9 | |||
1415e2492a |
8
.github/workflows/build-docker-image.yml
vendored
8
.github/workflows/build-docker-image.yml
vendored
@ -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
|
||||
|
4
.github/workflows/draft-release.yml
vendored
4
.github/workflows/draft-release.yml
vendored
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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{
|
||||
|
@ -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){
|
||||
|
@ -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.
|
||||
|
@ -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
50
composer.lock
generated
@ -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",
|
||||
|
@ -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
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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{
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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) . ")";
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 . ")";
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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{
|
||||
|
@ -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;
|
||||
|
@ -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{
|
||||
|
@ -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];
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)));
|
||||
|
@ -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)");
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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]);
|
||||
|
@ -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
|
||||
){}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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){
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 [];
|
||||
}
|
||||
}
|
@ -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 [];
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user