mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-11 03:58:06 +00:00
Compare commits
2 Commits
translatab
...
repeated-p
Author | SHA1 | Date | |
---|---|---|---|
9946ea9e8a | |||
14b70c04ad |
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.18.0
|
||||
uses: docker/build-push-action@v6.16.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.18.0
|
||||
uses: docker/build-push-action@v6.16.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.18.0
|
||||
uses: docker/build-push-action@v6.16.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.18.0
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
|
@ -36,7 +36,7 @@
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60",
|
||||
"pocketmine/bedrock-data": "~5.0.0+bedrock-1.21.80",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
|
||||
"pocketmine/bedrock-protocol": "~38.1.0+bedrock-1.21.80",
|
||||
"pocketmine/bedrock-protocol": "~38.0.0+bedrock-1.21.80",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/color": "^0.3.0",
|
||||
@ -45,14 +45,14 @@
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/math": "~1.0.0",
|
||||
"pocketmine/nbt": "~1.1.0",
|
||||
"pocketmine/raklib": "~1.2.0",
|
||||
"pocketmine/raklib": "~1.1.2",
|
||||
"pocketmine/raklib-ipc": "~1.0.0",
|
||||
"pocketmine/snooze": "^0.5.0",
|
||||
"ramsey/uuid": "~4.8.0",
|
||||
"ramsey/uuid": "~4.7.0",
|
||||
"symfony/filesystem": "~6.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "2.1.17",
|
||||
"phpstan/phpstan": "2.1.16",
|
||||
"phpstan/phpstan-phpunit": "^2.0.0",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.0",
|
||||
"phpunit/phpunit": "^10.5.24"
|
||||
|
117
composer.lock
generated
117
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": "7c3052613e98e566d8b00ae3c9119057",
|
||||
"content-hash": "b106b34fbd6c8abdfd45931bcb18bb69",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -67,16 +67,16 @@
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
"version": "0.13.1",
|
||||
"version": "0.12.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/brick/math.git",
|
||||
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04"
|
||||
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04",
|
||||
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04",
|
||||
"url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba",
|
||||
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -115,7 +115,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/brick/math/issues",
|
||||
"source": "https://github.com/brick/math/tree/0.13.1"
|
||||
"source": "https://github.com/brick/math/tree/0.12.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -123,7 +123,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-29T13:50:30+00:00"
|
||||
"time": "2025-02-28T13:11:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "netresearch/jsonmapper",
|
||||
@ -256,16 +256,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-protocol",
|
||||
"version": "38.1.0+bedrock-1.21.80",
|
||||
"version": "38.0.1+bedrock-1.21.80",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockProtocol.git",
|
||||
"reference": "a1fa215563517050045309bb779a67f75843b867"
|
||||
"reference": "0c1c13e970a2e1ded1609d0b442b4fcfd24cd21f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/a1fa215563517050045309bb779a67f75843b867",
|
||||
"reference": "a1fa215563517050045309bb779a67f75843b867",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/0c1c13e970a2e1ded1609d0b442b4fcfd24cd21f",
|
||||
"reference": "0c1c13e970a2e1ded1609d0b442b4fcfd24cd21f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -296,9 +296,9 @@
|
||||
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/38.1.0+bedrock-1.21.80"
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/38.0.1+bedrock-1.21.80"
|
||||
},
|
||||
"time": "2025-05-28T22:19:59+00:00"
|
||||
"time": "2025-05-17T11:56:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
@ -618,16 +618,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/raklib",
|
||||
"version": "1.2.0",
|
||||
"version": "1.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/RakLib.git",
|
||||
"reference": "a28d05216d34dbd00e8aed827a58df6b4c11510b"
|
||||
"reference": "4145a31cd812fe8931c3c9c691fcd2ded2f47e7f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/a28d05216d34dbd00e8aed827a58df6b4c11510b",
|
||||
"reference": "a28d05216d34dbd00e8aed827a58df6b4c11510b",
|
||||
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/4145a31cd812fe8931c3c9c691fcd2ded2f47e7f",
|
||||
"reference": "4145a31cd812fe8931c3c9c691fcd2ded2f47e7f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -655,9 +655,9 @@
|
||||
"description": "A RakNet server implementation written in PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/RakLib/issues",
|
||||
"source": "https://github.com/pmmp/RakLib/tree/1.2.0"
|
||||
"source": "https://github.com/pmmp/RakLib/tree/1.1.2"
|
||||
},
|
||||
"time": "2025-06-08T17:36:06+00:00"
|
||||
"time": "2025-04-06T03:38:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/raklib-ipc",
|
||||
@ -818,20 +818,20 @@
|
||||
},
|
||||
{
|
||||
"name": "ramsey/uuid",
|
||||
"version": "4.8.1",
|
||||
"version": "4.7.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ramsey/uuid.git",
|
||||
"reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28"
|
||||
"reference": "91039bc1faa45ba123c4328958e620d382ec7088"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28",
|
||||
"reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088",
|
||||
"reference": "91039bc1faa45ba123c4328958e620d382ec7088",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13",
|
||||
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12",
|
||||
"ext-json": "*",
|
||||
"php": "^8.0",
|
||||
"ramsey/collection": "^1.2 || ^2.0"
|
||||
@ -840,23 +840,26 @@
|
||||
"rhumsaa/uuid": "self.version"
|
||||
},
|
||||
"require-dev": {
|
||||
"captainhook/captainhook": "^5.25",
|
||||
"captainhook/captainhook": "^5.10",
|
||||
"captainhook/plugin-composer": "^5.3",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
|
||||
"ergebnis/composer-normalize": "^2.47",
|
||||
"mockery/mockery": "^1.6",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
|
||||
"doctrine/annotations": "^1.8",
|
||||
"ergebnis/composer-normalize": "^2.15",
|
||||
"mockery/mockery": "^1.3",
|
||||
"paragonie/random-lib": "^2",
|
||||
"php-mock/php-mock": "^2.6",
|
||||
"php-mock/php-mock-mockery": "^1.5",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.4.0",
|
||||
"phpbench/phpbench": "^1.2.14",
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpstan/phpstan-mockery": "^2.0",
|
||||
"phpstan/phpstan-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"slevomat/coding-standard": "^8.18",
|
||||
"squizlabs/php_codesniffer": "^3.13"
|
||||
"php-mock/php-mock": "^2.2",
|
||||
"php-mock/php-mock-mockery": "^1.3",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.1",
|
||||
"phpbench/phpbench": "^1.0",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan": "^1.8",
|
||||
"phpstan/phpstan-mockery": "^1.1",
|
||||
"phpstan/phpstan-phpunit": "^1.1",
|
||||
"phpunit/phpunit": "^8.5 || ^9",
|
||||
"ramsey/composer-repl": "^1.4",
|
||||
"slevomat/coding-standard": "^8.4",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"vimeo/psalm": "^4.9"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
|
||||
@ -891,9 +894,19 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/ramsey/uuid/issues",
|
||||
"source": "https://github.com/ramsey/uuid/tree/4.8.1"
|
||||
"source": "https://github.com/ramsey/uuid/tree/4.7.6"
|
||||
},
|
||||
"time": "2025-06-01T06:28:46+00:00"
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/ramsey",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/ramsey/uuid",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-04-27T21:32:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
@ -1025,16 +1038,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v5.5.0",
|
||||
"version": "v5.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9"
|
||||
"reference": "447a020a1f875a434d62f2a401f53b82a396e494"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9",
|
||||
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494",
|
||||
"reference": "447a020a1f875a434d62f2a401f53b82a396e494",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1077,9 +1090,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0"
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0"
|
||||
},
|
||||
"time": "2025-05-31T08:24:38+00:00"
|
||||
"time": "2024-12-30T11:07:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phar-io/manifest",
|
||||
@ -1201,16 +1214,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.17",
|
||||
"version": "2.1.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "89b5ef665716fa2a52ecd2633f21007a6a349053"
|
||||
"reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053",
|
||||
"reference": "89b5ef665716fa2a52ecd2633f21007a6a349053",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9",
|
||||
"reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1255,7 +1268,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-05-21T20:55:28+00:00"
|
||||
"time": "2025-05-16T09:40:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-phpunit",
|
||||
|
@ -1618,7 +1618,7 @@ class Server{
|
||||
if(!is_dir($crashFolder)){
|
||||
mkdir($crashFolder);
|
||||
}
|
||||
$crashDumpPath = Path::join($crashFolder, date("Y-m-d_H.i.s_T", (int) $dump->getData()->time) . ".log");
|
||||
$crashDumpPath = Path::join($crashFolder, date("D_M_j-H.i.s-T_Y", (int) $dump->getData()->time) . ".log");
|
||||
|
||||
$fp = @fopen($crashDumpPath, "wb");
|
||||
if(!is_resource($fp)){
|
||||
|
@ -123,7 +123,7 @@ abstract class Command{
|
||||
}
|
||||
|
||||
if($this->permissionMessage === null){
|
||||
$target->sendMessage(KnownTranslationFactory::pocketmine_command_error_permission($this->name)->baseTextFormat(TextFormat::RED));
|
||||
$target->sendMessage(KnownTranslationFactory::pocketmine_command_error_permission($this->name)->prefix(TextFormat::RED));
|
||||
}elseif($this->permissionMessage !== ""){
|
||||
$target->sendMessage(str_replace("<permission>", $permission ?? implode(";", $this->permission), $this->permissionMessage));
|
||||
}
|
||||
@ -237,7 +237,7 @@ abstract class Command{
|
||||
public static function broadcastCommandMessage(CommandSender $source, Translatable|string $message, bool $sendToSource = true) : void{
|
||||
$users = $source->getServer()->getBroadcastChannelSubscribers(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
|
||||
$result = KnownTranslationFactory::chat_type_admin($source->getName(), $message);
|
||||
$colored = $result->baseTextFormat(TextFormat::GRAY . TextFormat::ITALIC);
|
||||
$colored = $result->prefix(TextFormat::GRAY . TextFormat::ITALIC);
|
||||
|
||||
if($sendToSource){
|
||||
$source->sendMessage($message);
|
||||
|
@ -107,7 +107,7 @@ class FormattedCommandAlias extends Command{
|
||||
$timings->stopTiming();
|
||||
}
|
||||
}else{
|
||||
$sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_notFound($commandLabel, "/help")->baseTextFormat(TextFormat::RED)));
|
||||
$sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_notFound($commandLabel, "/help")->prefix(TextFormat::RED)));
|
||||
|
||||
//to match the behaviour of SimpleCommandMap::dispatch()
|
||||
//this shouldn't normally happen, but might happen if the command was unregistered or modified after
|
||||
|
@ -226,7 +226,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
return true;
|
||||
}
|
||||
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($sentCommandLabel ?? "", "/help")->baseTextFormat(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($sentCommandLabel ?? "", "/help")->prefix(TextFormat::RED));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ class ClearCommand extends VanillaCommand{
|
||||
}
|
||||
}catch(LegacyStringToItemParserException $e){
|
||||
//vanilla checks this at argument parsing layer, can't come up with a better alternative
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->baseTextFormat(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->prefix(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -90,7 +90,7 @@ class ClearCommand extends VanillaCommand{
|
||||
if($count > 0){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_clear_testing($target->getName(), (string) $count));
|
||||
}else{
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->baseTextFormat(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->prefix(TextFormat::RED));
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -132,7 +132,7 @@ class ClearCommand extends VanillaCommand{
|
||||
if($clearedCount > 0){
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_clear_success($target->getName(), (string) $clearedCount));
|
||||
}else{
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->baseTextFormat(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->prefix(TextFormat::RED));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -68,7 +68,7 @@ class EffectCommand extends VanillaCommand{
|
||||
|
||||
$effect = StringToEffectParser::getInstance()->parse($args[1]);
|
||||
if($effect === null){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_effect_notFound($args[1])->baseTextFormat(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_effect_notFound($args[1])->prefix(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ class GiveCommand extends VanillaCommand{
|
||||
try{
|
||||
$item = StringToItemParser::getInstance()->parse($args[1]) ?? LegacyStringToItemParser::getInstance()->parse($args[1]);
|
||||
}catch(LegacyStringToItemParserException $e){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->baseTextFormat(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->prefix(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ class GiveCommand extends VanillaCommand{
|
||||
$player->getInventory()->addItem($item);
|
||||
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_give_success(
|
||||
$item->getName() . TextFormat::RESET . " (" . $args[1] . ")",
|
||||
$item->getName() . " (" . $args[1] . ")",
|
||||
(string) $item->getCount(),
|
||||
$player->getName()
|
||||
));
|
||||
|
@ -105,22 +105,22 @@ class HelpCommand extends VanillaCommand{
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getLabel())
|
||||
->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------"));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString)
|
||||
->baseTextFormat(TextFormat::GOLD));
|
||||
->prefix(TextFormat::GOLD));
|
||||
|
||||
$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)))
|
||||
->baseTextFormat(TextFormat::GOLD));
|
||||
->prefix(TextFormat::GOLD));
|
||||
|
||||
$aliases = $cmd->getAliases();
|
||||
sort($aliases, SORT_NATURAL);
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::RESET . implode(", ", $aliases))
|
||||
->baseTextFormat(TextFormat::GOLD));
|
||||
->prefix(TextFormat::GOLD));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($commandName, "/help")->baseTextFormat(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($commandName, "/help")->prefix(TextFormat::RED));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ class ParticleCommand extends VanillaCommand{
|
||||
$particle = $this->getParticle($name, $data);
|
||||
|
||||
if($particle === null){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_particle_notFound($name)->baseTextFormat(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_particle_notFound($name)->prefix(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ class SayCommand extends VanillaCommand{
|
||||
$sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_announcement(
|
||||
$sender instanceof Player ? $sender->getDisplayName() : ($sender instanceof ConsoleCommandSender ? "Server" : $sender->getName()),
|
||||
implode(" ", $args)
|
||||
)->baseTextFormat(TextFormat::LIGHT_PURPLE));
|
||||
)->prefix(TextFormat::LIGHT_PURPLE));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -60,9 +60,9 @@ class TellCommand extends VanillaCommand{
|
||||
|
||||
if($player instanceof Player){
|
||||
$message = implode(" ", $args);
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_message_display_outgoing($player->getDisplayName(), $message)->baseTextFormat(TextFormat::GRAY . TextFormat::ITALIC));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_message_display_outgoing($player->getDisplayName(), $message)->prefix(TextFormat::GRAY . TextFormat::ITALIC));
|
||||
$name = $sender instanceof Player ? $sender->getDisplayName() : $sender->getName();
|
||||
$player->sendMessage(KnownTranslationFactory::commands_message_display_incoming($name, $message)->baseTextFormat(TextFormat::GRAY . TextFormat::ITALIC));
|
||||
$player->sendMessage(KnownTranslationFactory::commands_message_display_incoming($name, $message)->prefix(TextFormat::GRAY . TextFormat::ITALIC));
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_message_display_outgoing($player->getDisplayName(), $message), false);
|
||||
}else{
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound());
|
||||
|
@ -99,11 +99,11 @@ abstract class VanillaCommand extends Command{
|
||||
|
||||
$v = (int) $input;
|
||||
if($v > $max){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooBig($input, (string) $max)->baseTextFormat(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooBig($input, (string) $max)->prefix(TextFormat::RED));
|
||||
return null;
|
||||
}
|
||||
if($v < $min){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooSmall($input, (string) $min)->baseTextFormat(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooSmall($input, (string) $min)->prefix(TextFormat::RED));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -256,7 +256,7 @@ abstract class BaseInventory implements Inventory, SlotValidatedInventory{
|
||||
$slotItem->setCount($slotItem->getCount() + $amount);
|
||||
$this->setItem($i, $slotItem);
|
||||
if($newItem->getCount() <= 0){
|
||||
return $newItem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -270,7 +270,7 @@ abstract class BaseInventory implements Inventory, SlotValidatedInventory{
|
||||
$slotItem->setCount($amount);
|
||||
$this->setItem($slotIndex, $slotItem);
|
||||
if($newItem->getCount() <= 0){
|
||||
return $newItem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\lang;
|
||||
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_filter;
|
||||
@ -142,26 +141,18 @@ class Language{
|
||||
/**
|
||||
* @param (float|int|string|Translatable)[] $params
|
||||
*/
|
||||
public function translateString(string $str, array $params = [], ?string $onlyPrefix = null, string $baseFormat = "") : string{
|
||||
if($onlyPrefix !== null && !str_starts_with($str, $onlyPrefix)){
|
||||
//plain key for client-side translation
|
||||
//% is added here if we add base format since this will turn into an embedded key
|
||||
return $baseFormat !== "" ? TextFormat::addBase($baseFormat, "%" . $str) : $str;
|
||||
}
|
||||
$baseText = $this->internalGet($str);
|
||||
if($baseText === null){ //key not found, embedded inside format string with %, or doesn't match prefix
|
||||
public function translateString(string $str, array $params = [], ?string $onlyPrefix = null) : string{
|
||||
$baseText = ($onlyPrefix === null || str_starts_with($str, $onlyPrefix)) ? $this->internalGet($str) : null;
|
||||
if($baseText === null){ //key not found, embedded inside format string, or doesn't match prefix
|
||||
$baseText = $this->parseTranslation($str, $onlyPrefix);
|
||||
}
|
||||
|
||||
foreach(Utils::promoteKeys($params) as $i => $p){
|
||||
$replacement = $p instanceof Translatable ? $this->translate($p) : (string) $p;
|
||||
if($baseFormat !== ""){
|
||||
$replacement = TextFormat::addBase($baseFormat, $replacement) . TextFormat::RESET;
|
||||
}
|
||||
$baseText = str_replace("{%$i}", $replacement, $baseText);
|
||||
}
|
||||
|
||||
return $baseFormat !== "" ? TextFormat::addBase($baseFormat, $baseText) : $baseText;
|
||||
return $baseText;
|
||||
}
|
||||
|
||||
public function translate(Translatable $c) : string{
|
||||
@ -170,17 +161,12 @@ class Language{
|
||||
$baseText = $this->parseTranslation($c->getText());
|
||||
}
|
||||
|
||||
$baseFormat = $c->getBaseFormat();
|
||||
|
||||
foreach(Utils::promoteKeys($c->getParameters()) as $i => $p){
|
||||
$replacement = $p instanceof Translatable ? $this->translate($p) : $p;
|
||||
if($baseFormat !== ""){
|
||||
$replacement = TextFormat::addBase($baseFormat, $replacement) . TextFormat::RESET;
|
||||
}
|
||||
$baseText = str_replace("{%$i}", $replacement, $baseText);
|
||||
}
|
||||
|
||||
return $baseFormat !== "" ? TextFormat::addBase($baseFormat, $baseText) : $baseText;
|
||||
return $baseText;
|
||||
}
|
||||
|
||||
protected function internalGet(string $id) : ?string{
|
||||
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\lang;
|
||||
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
final class Translatable{
|
||||
@ -35,8 +34,7 @@ final class Translatable{
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $text,
|
||||
array $params = [],
|
||||
private string $baseFormat = ""
|
||||
array $params = []
|
||||
){
|
||||
foreach(Utils::promoteKeys($params) as $k => $param){
|
||||
if(!($param instanceof Translatable)){
|
||||
@ -62,8 +60,6 @@ final class Translatable{
|
||||
return $this->params[$i] ?? null;
|
||||
}
|
||||
|
||||
public function getBaseFormat() : string{ return $this->baseFormat; }
|
||||
|
||||
public function format(string $before, string $after) : self{
|
||||
return new self("$before%$this->text$after", $this->params);
|
||||
}
|
||||
@ -75,12 +71,4 @@ final class Translatable{
|
||||
public function postfix(string $postfix) : self{
|
||||
return new self("%$this->text" . $postfix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base format to be applied to the translation result by {@link TextFormat::addBase()}.
|
||||
* Any existing base format is overwritten.
|
||||
*/
|
||||
public function baseTextFormat(string $baseFormat) : self{
|
||||
return new self($this->text, $this->params, $baseFormat);
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,6 @@ use pocketmine\inventory\transaction\action\SlotChangeAction;
|
||||
use pocketmine\inventory\transaction\InventoryTransaction;
|
||||
use pocketmine\item\enchantment\EnchantingOption;
|
||||
use pocketmine\item\enchantment\EnchantmentInstance;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\network\mcpe\cache\CreativeInventoryCache;
|
||||
use pocketmine\network\mcpe\protocol\ClientboundPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
|
||||
@ -229,25 +228,17 @@ class InventoryManager{
|
||||
return null;
|
||||
}
|
||||
|
||||
private function addPredictedSlotChangeInternal(Inventory $inventory, int $slot, ItemStack $item) : void{
|
||||
private function addPredictedSlotChange(Inventory $inventory, int $slot, ItemStack $item) : void{
|
||||
$this->inventories[spl_object_id($inventory)]->predictions[$slot] = $item;
|
||||
}
|
||||
|
||||
public function addPredictedSlotChange(Inventory $inventory, int $slot, Item $item) : void{
|
||||
$typeConverter = $this->session->getTypeConverter();
|
||||
$itemStack = $typeConverter->coreItemStackToNet($item);
|
||||
$this->addPredictedSlotChangeInternal($inventory, $slot, $itemStack);
|
||||
}
|
||||
|
||||
public function addTransactionPredictedSlotChanges(InventoryTransaction $tx) : void{
|
||||
$typeConverter = $this->session->getTypeConverter();
|
||||
foreach($tx->getActions() as $action){
|
||||
if($action instanceof SlotChangeAction){
|
||||
//TODO: ItemStackRequestExecutor can probably build these predictions with much lower overhead
|
||||
$this->addPredictedSlotChange(
|
||||
$action->getInventory(),
|
||||
$action->getSlot(),
|
||||
$action->getTargetItem()
|
||||
);
|
||||
$itemStack = $typeConverter->coreItemStackToNet($action->getTargetItem());
|
||||
$this->addPredictedSlotChange($action->getInventory(), $action->getSlot(), $itemStack);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -276,7 +267,7 @@ class InventoryManager{
|
||||
}
|
||||
|
||||
[$inventory, $slot] = $info;
|
||||
$this->addPredictedSlotChangeInternal($inventory, $slot, $action->newItem->getItemStack());
|
||||
$this->addPredictedSlotChange($inventory, $slot, $action->newItem->getItemStack());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,6 +58,7 @@ use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientboundCloseFormPacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientboundPacket;
|
||||
use pocketmine\network\mcpe\protocol\DisconnectPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
|
||||
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
|
||||
use pocketmine\network\mcpe\protocol\NetworkChunkPublisherUpdatePacket;
|
||||
@ -109,6 +110,7 @@ use pocketmine\promise\PromiseResolver;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\BinaryDataException;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
@ -194,6 +196,17 @@ class NetworkSession{
|
||||
*/
|
||||
private ObjectSet $disposeHooks;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var array<int, string>
|
||||
*/
|
||||
private array $repeatedPacketFilters = [];
|
||||
/**
|
||||
* @var int[]
|
||||
* @phpstan-var array<int, int>
|
||||
*/
|
||||
private array $repeatedPacketFilterStats = [];
|
||||
|
||||
public function __construct(
|
||||
private Server $server,
|
||||
private NetworkSessionManager $manager,
|
||||
@ -221,6 +234,8 @@ class NetworkSession{
|
||||
$this->onSessionStartSuccess(...)
|
||||
));
|
||||
|
||||
$this->addRepeatedPacketFilter(InventoryTransactionPacket::NETWORK_ID);
|
||||
|
||||
$this->manager->add($this);
|
||||
$this->logger->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_network_session_open()));
|
||||
}
|
||||
@ -350,6 +365,44 @@ class NetworkSession{
|
||||
}
|
||||
}
|
||||
|
||||
public function addRepeatedPacketFilter(int $packetId) : void{
|
||||
$this->repeatedPacketFilters[$packetId] = "";
|
||||
$this->repeatedPacketFilterStats[$packetId] = 0;
|
||||
}
|
||||
|
||||
public function removeRepeatedPacketFilter(int $packetId) : void{
|
||||
unset($this->repeatedPacketFilters[$packetId]);
|
||||
unset($this->repeatedPacketFilterStats[$packetId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stats for repeated packet filters, indexed by packet ID.
|
||||
* The value is the number of times a packet was dropped due to being repeated.
|
||||
*
|
||||
* @return int[]
|
||||
* @phpstan-return array<int, int>
|
||||
*/
|
||||
public function getRepeatedPacketFilterStats() : array{
|
||||
return $this->repeatedPacketFilterStats;
|
||||
}
|
||||
|
||||
private function checkRepeatedPacketFilter(string $buffer) : bool{
|
||||
//TODO: would be great if we didn't repeat reading the ID inside PacketPool
|
||||
$dummy = 0;
|
||||
$packetId = Binary::readUnsignedVarInt($buffer, $dummy);
|
||||
|
||||
if(isset($this->repeatedPacketFilters[$packetId])){
|
||||
if($buffer === $this->repeatedPacketFilters[$packetId]){
|
||||
$this->repeatedPacketFilterStats[$packetId]++;
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->repeatedPacketFilters[$packetId] = $buffer;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PacketHandlingException
|
||||
*/
|
||||
@ -403,6 +456,10 @@ class NetworkSession{
|
||||
try{
|
||||
$stream = new BinaryStream($decompressed);
|
||||
foreach(PacketBatch::decodeRaw($stream) as $buffer){
|
||||
if($this->checkRepeatedPacketFilter($buffer)){
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->gamePacketLimiter->decrement();
|
||||
$packet = $this->packetPool->getPacket($buffer);
|
||||
if($packet === null){
|
||||
@ -767,7 +824,7 @@ class NetworkSession{
|
||||
$errorId = implode("-", str_split(bin2hex(random_bytes(6)), 4));
|
||||
|
||||
$this->disconnect(
|
||||
reason: KnownTranslationFactory::pocketmine_disconnect_error($reason, $errorId)->baseTextFormat(TextFormat::RED),
|
||||
reason: KnownTranslationFactory::pocketmine_disconnect_error($reason, $errorId)->prefix(TextFormat::RED),
|
||||
disconnectScreenMessage: KnownTranslationFactory::pocketmine_disconnect_error($disconnectScreenMessage ?? $reason, $errorId),
|
||||
);
|
||||
}
|
||||
@ -1130,12 +1187,8 @@ class NetworkSession{
|
||||
public function prepareClientTranslatableMessage(Translatable $message) : array{
|
||||
//we can't send nested translations to the client, so make sure they are always pre-translated by the server
|
||||
$language = $this->player->getLanguage();
|
||||
$baseFormat = $message->getBaseFormat();
|
||||
$parameters = array_map(function(string|Translatable $p) use ($baseFormat, $language){
|
||||
$string = $p instanceof Translatable ? $language->translate($p) : $p;
|
||||
return $baseFormat !== "" ? TextFormat::addBase($baseFormat, $string) . TextFormat::RESET : $string;
|
||||
}, $message->getParameters());
|
||||
return [$language->translateString($message->getText(), $parameters, "pocketmine.", $baseFormat), $parameters];
|
||||
$parameters = array_map(fn(string|Translatable $p) => $p instanceof Translatable ? $language->translate($p) : $p, $message->getParameters());
|
||||
return [$language->translateString($message->getText(), $parameters, "pocketmine."), $parameters];
|
||||
}
|
||||
|
||||
public function onChatMessage(Translatable|string $message) : void{
|
||||
|
@ -116,7 +116,6 @@ use function is_nan;
|
||||
use function json_decode;
|
||||
use function max;
|
||||
use function mb_strlen;
|
||||
use function microtime;
|
||||
use function sprintf;
|
||||
use function str_starts_with;
|
||||
use function strlen;
|
||||
@ -128,16 +127,11 @@ use const JSON_THROW_ON_ERROR;
|
||||
class InGamePacketHandler extends PacketHandler{
|
||||
private const MAX_FORM_RESPONSE_DEPTH = 2; //modal/simple will be 1, custom forms 2 - they will never contain anything other than string|int|float|bool|null
|
||||
|
||||
protected float $lastRightClickTime = 0.0;
|
||||
protected ?UseItemTransactionData $lastRightClickData = null;
|
||||
|
||||
protected ?Vector3 $lastPlayerAuthInputPosition = null;
|
||||
protected ?float $lastPlayerAuthInputYaw = null;
|
||||
protected ?float $lastPlayerAuthInputPitch = null;
|
||||
protected ?BitSet $lastPlayerAuthInputFlags = null;
|
||||
|
||||
protected ?BlockPosition $lastBlockAttacked = null;
|
||||
|
||||
public bool $forceMoveSync = false;
|
||||
|
||||
protected ?string $lastRequestedFullSkinId = null;
|
||||
@ -213,7 +207,7 @@ class InGamePacketHandler extends PacketHandler{
|
||||
}
|
||||
|
||||
$inputFlags = $packet->getInputFlags();
|
||||
if($this->lastPlayerAuthInputFlags === null || !$inputFlags->equals($this->lastPlayerAuthInputFlags)){
|
||||
if($inputFlags !== $this->lastPlayerAuthInputFlags){
|
||||
$this->lastPlayerAuthInputFlags = $inputFlags;
|
||||
|
||||
$sneaking = $inputFlags->get(PlayerAuthInputFlags::SNEAKING);
|
||||
@ -250,28 +244,6 @@ class InGamePacketHandler extends PacketHandler{
|
||||
|
||||
$packetHandled = true;
|
||||
|
||||
$useItemTransaction = $packet->getItemInteractionData();
|
||||
if($useItemTransaction !== null){
|
||||
if(count($useItemTransaction->getTransactionData()->getActions()) > 100){
|
||||
throw new PacketHandlingException("Too many actions in item use transaction");
|
||||
}
|
||||
|
||||
$this->inventoryManager->setCurrentItemStackRequestId($useItemTransaction->getRequestId());
|
||||
$this->inventoryManager->addRawPredictedSlotChanges($useItemTransaction->getTransactionData()->getActions());
|
||||
if(!$this->handleUseItemTransaction($useItemTransaction->getTransactionData())){
|
||||
$packetHandled = false;
|
||||
$this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")");
|
||||
}else{
|
||||
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
|
||||
}
|
||||
$this->inventoryManager->setCurrentItemStackRequestId(null);
|
||||
}
|
||||
|
||||
$itemStackRequest = $packet->getItemStackRequest();
|
||||
$itemStackResponseBuilder = $itemStackRequest !== null ? $this->handleSingleItemStackRequest($itemStackRequest) : null;
|
||||
|
||||
//itemstack request or transaction may set predictions for the outcome of these actions, so these need to be
|
||||
//processed last
|
||||
$blockActions = $packet->getBlockActions();
|
||||
if($blockActions !== null){
|
||||
if(count($blockActions) > 100){
|
||||
@ -292,9 +264,27 @@ class InGamePacketHandler extends PacketHandler{
|
||||
}
|
||||
}
|
||||
|
||||
$useItemTransaction = $packet->getItemInteractionData();
|
||||
if($useItemTransaction !== null){
|
||||
if(count($useItemTransaction->getTransactionData()->getActions()) > 100){
|
||||
throw new PacketHandlingException("Too many actions in item use transaction");
|
||||
}
|
||||
|
||||
$this->inventoryManager->setCurrentItemStackRequestId($useItemTransaction->getRequestId());
|
||||
$this->inventoryManager->addRawPredictedSlotChanges($useItemTransaction->getTransactionData()->getActions());
|
||||
if(!$this->handleUseItemTransaction($useItemTransaction->getTransactionData())){
|
||||
$packetHandled = false;
|
||||
$this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")");
|
||||
}else{
|
||||
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
|
||||
}
|
||||
$this->inventoryManager->setCurrentItemStackRequestId(null);
|
||||
}
|
||||
|
||||
$itemStackRequest = $packet->getItemStackRequest();
|
||||
if($itemStackRequest !== null){
|
||||
$itemStackResponse = $itemStackResponseBuilder?->build() ?? new ItemStackResponse(ItemStackResponse::RESULT_ERROR, $itemStackRequest->getRequestId());
|
||||
$this->session->sendDataPacket(ItemStackResponsePacket::create([$itemStackResponse]));
|
||||
$result = $this->handleSingleItemStackRequest($itemStackRequest);
|
||||
$this->session->sendDataPacket(ItemStackResponsePacket::create([$result]));
|
||||
}
|
||||
|
||||
return $packetHandled;
|
||||
@ -477,33 +467,24 @@ class InGamePacketHandler extends PacketHandler{
|
||||
|
||||
switch($data->getActionType()){
|
||||
case UseItemTransactionData::ACTION_CLICK_BLOCK:
|
||||
//TODO: start hack for client spam bug
|
||||
$clickPos = $data->getClickPosition();
|
||||
$spamBug = ($this->lastRightClickData !== null &&
|
||||
microtime(true) - $this->lastRightClickTime < 0.1 && //100ms
|
||||
$this->lastRightClickData->getPlayerPosition()->distanceSquared($data->getPlayerPosition()) < 0.00001 &&
|
||||
$this->lastRightClickData->getBlockPosition()->equals($data->getBlockPosition()) &&
|
||||
$this->lastRightClickData->getClickPosition()->distanceSquared($clickPos) < 0.00001 //signature spam bug has 0 distance, but allow some error
|
||||
);
|
||||
//get rid of continued spam if the player clicks and holds right-click
|
||||
$this->lastRightClickData = $data;
|
||||
$this->lastRightClickTime = microtime(true);
|
||||
if($spamBug){
|
||||
return true;
|
||||
}
|
||||
//TODO: end hack for client spam bug
|
||||
|
||||
self::validateFacing($data->getFace());
|
||||
|
||||
$blockPos = $data->getBlockPosition();
|
||||
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
|
||||
$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos);
|
||||
$this->player->interactBlock($vBlockPos, $data->getFace(), $data->getClickPosition());
|
||||
//always sync this in case plugins caused a different result than the client expected
|
||||
//we *could* try to enhance detection of plugin-altered behaviour, but this would require propagating
|
||||
//more information up the stack. For now I think this is good enough.
|
||||
//if only the client would tell us what blocks it thinks changed...
|
||||
$this->syncBlocksNearby($vBlockPos, $data->getFace());
|
||||
return true;
|
||||
case UseItemTransactionData::ACTION_BREAK_BLOCK:
|
||||
$blockPos = $data->getBlockPosition();
|
||||
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
|
||||
if(!$this->player->breakBlock($vBlockPos)){
|
||||
$this->syncBlocksNearby($vBlockPos, null);
|
||||
}
|
||||
return true;
|
||||
case UseItemTransactionData::ACTION_CLICK_AIR:
|
||||
if($this->player->isUsingItem()){
|
||||
if(!$this->player->consumeHeldItem()){
|
||||
@ -579,7 +560,7 @@ class InGamePacketHandler extends PacketHandler{
|
||||
return false;
|
||||
}
|
||||
|
||||
private function handleSingleItemStackRequest(ItemStackRequest $request) : ?ItemStackResponseBuilder{
|
||||
private function handleSingleItemStackRequest(ItemStackRequest $request) : ItemStackResponse{
|
||||
if(count($request->getActions()) > 60){
|
||||
//recipe book auto crafting can affect all slots of the inventory when consuming inputs or producing outputs
|
||||
//this means there could be as many as 50 CraftingConsumeInput actions or Place (taking the result) actions
|
||||
@ -596,11 +577,7 @@ class InGamePacketHandler extends PacketHandler{
|
||||
$executor = new ItemStackRequestExecutor($this->player, $this->inventoryManager, $request);
|
||||
try{
|
||||
$transaction = $executor->generateInventoryTransaction();
|
||||
if($transaction !== null){
|
||||
$result = $this->executeInventoryTransaction($transaction, $request->getRequestId());
|
||||
}else{
|
||||
$result = true; //predictions only, just send responses
|
||||
}
|
||||
$result = $this->executeInventoryTransaction($transaction, $request->getRequestId());
|
||||
}catch(ItemStackRequestProcessException $e){
|
||||
$result = false;
|
||||
$this->session->getLogger()->debug("ItemStackRequest #" . $request->getRequestId() . " failed: " . $e->getMessage());
|
||||
@ -608,7 +585,10 @@ class InGamePacketHandler extends PacketHandler{
|
||||
$this->inventoryManager->requestSyncAll();
|
||||
}
|
||||
|
||||
return $result ? $executor->getItemStackResponseBuilder() : null;
|
||||
if(!$result){
|
||||
return new ItemStackResponse(ItemStackResponse::RESULT_ERROR, $request->getRequestId());
|
||||
}
|
||||
return $executor->buildItemStackResponse();
|
||||
}
|
||||
|
||||
public function handleItemStackRequest(ItemStackRequestPacket $packet) : bool{
|
||||
@ -618,7 +598,7 @@ class InGamePacketHandler extends PacketHandler{
|
||||
throw new PacketHandlingException("Too many requests in ItemStackRequestPacket");
|
||||
}
|
||||
foreach($packet->getRequests() as $request){
|
||||
$responses[] = $this->handleSingleItemStackRequest($request)?->build() ?? new ItemStackResponse(ItemStackResponse::RESULT_ERROR, $request->getRequestId());
|
||||
$responses[] = $this->handleSingleItemStackRequest($request);
|
||||
}
|
||||
|
||||
$this->session->sendDataPacket(ItemStackResponsePacket::create($responses));
|
||||
@ -681,27 +661,16 @@ class InGamePacketHandler extends PacketHandler{
|
||||
|
||||
switch($action){
|
||||
case PlayerAction::START_BREAK:
|
||||
case PlayerAction::CONTINUE_DESTROY_BLOCK: //destroy the next block while holding down left click
|
||||
self::validateFacing($face);
|
||||
if($this->lastBlockAttacked !== null && $blockPosition->equals($this->lastBlockAttacked)){
|
||||
//the client will send CONTINUE_DESTROY_BLOCK for the currently targeted block directly before it
|
||||
//sends PREDICT_DESTROY_BLOCK, but also when it starts to break the block
|
||||
//this seems like a bug in the client and would cause spurious left-click events if we allowed it to
|
||||
//be delivered to the player
|
||||
$this->session->getLogger()->debug("Ignoring PlayerAction $action on $pos because we were already destroying this block");
|
||||
break;
|
||||
}
|
||||
if(!$this->player->attackBlock($pos, $face)){
|
||||
$this->syncBlocksNearby($pos, $face);
|
||||
}
|
||||
$this->lastBlockAttacked = $blockPosition;
|
||||
|
||||
break;
|
||||
|
||||
case PlayerAction::ABORT_BREAK:
|
||||
case PlayerAction::STOP_BREAK:
|
||||
$this->player->stopBreakBlock($pos);
|
||||
$this->lastBlockAttacked = null;
|
||||
break;
|
||||
case PlayerAction::START_SLEEPING:
|
||||
//unused
|
||||
@ -712,17 +681,11 @@ class InGamePacketHandler extends PacketHandler{
|
||||
case PlayerAction::CRACK_BREAK:
|
||||
self::validateFacing($face);
|
||||
$this->player->continueBreakBlock($pos, $face);
|
||||
$this->lastBlockAttacked = $blockPosition;
|
||||
break;
|
||||
case PlayerAction::INTERACT_BLOCK: //TODO: ignored (for now)
|
||||
break;
|
||||
case PlayerAction::CREATIVE_PLAYER_DESTROY_BLOCK:
|
||||
//TODO: do we need to handle this?
|
||||
case PlayerAction::PREDICT_DESTROY_BLOCK:
|
||||
if(!$this->player->breakBlock($pos)){
|
||||
$this->syncBlocksNearby($pos, $face);
|
||||
}
|
||||
$this->lastBlockAttacked = null;
|
||||
break;
|
||||
case PlayerAction::START_ITEM_USE_ON:
|
||||
case PlayerAction::STOP_ITEM_USE_ON:
|
||||
|
@ -33,11 +33,9 @@ use pocketmine\inventory\transaction\EnchantingTransaction;
|
||||
use pocketmine\inventory\transaction\InventoryTransaction;
|
||||
use pocketmine\inventory\transaction\TransactionBuilder;
|
||||
use pocketmine\inventory\transaction\TransactionBuilderInventory;
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\network\mcpe\InventoryManager;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ContainerUIIds;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\FullContainerName;
|
||||
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;
|
||||
@ -49,7 +47,6 @@ use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\DropStackReque
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequest;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequestAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequestSlotInfo;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\MineBlockStackRequestAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\PlaceStackRequestAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\SwapStackRequestAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\TakeStackRequestAction;
|
||||
@ -365,16 +362,6 @@ class ItemStackRequestExecutor{
|
||||
$this->setNextCreatedItem($nextResultItem);
|
||||
}elseif($action instanceof DeprecatedCraftingResultsStackRequestAction){
|
||||
//no obvious use
|
||||
}elseif($action instanceof MineBlockStackRequestAction){
|
||||
$slot = $action->getHotbarSlot();
|
||||
$this->requestSlotInfos[] = new ItemStackRequestSlotInfo(new FullContainerName(ContainerUIIds::HOTBAR), $slot, $action->getStackId());
|
||||
$inventory = $this->player->getInventory();
|
||||
$usedItem = $inventory->slotExists($slot) ? $inventory->getItem($slot) : null;
|
||||
$predictedDamage = $action->getPredictedDurability();
|
||||
if($usedItem instanceof Durable && $predictedDamage >= 0 && $predictedDamage <= $usedItem->getMaxDurability()){
|
||||
$usedItem->setDamage($predictedDamage);
|
||||
$this->inventoryManager->addPredictedSlotChange($inventory, $slot, $usedItem);
|
||||
}
|
||||
}else{
|
||||
throw new ItemStackRequestProcessException("Unhandled item stack request action");
|
||||
}
|
||||
@ -383,7 +370,7 @@ class ItemStackRequestExecutor{
|
||||
/**
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
public function generateInventoryTransaction() : ?InventoryTransaction{
|
||||
public function generateInventoryTransaction() : InventoryTransaction{
|
||||
foreach(Utils::promoteKeys($this->request->getActions()) as $k => $action){
|
||||
try{
|
||||
$this->processItemStackRequestAction($action);
|
||||
@ -393,9 +380,6 @@ class ItemStackRequestExecutor{
|
||||
}
|
||||
$this->setNextCreatedItem(null);
|
||||
$inventoryActions = $this->builder->generateActions();
|
||||
if(count($inventoryActions) === 0){
|
||||
return null;
|
||||
}
|
||||
|
||||
$transaction = $this->specialTransaction ?? new InventoryTransaction($this->player);
|
||||
foreach($inventoryActions as $action){
|
||||
@ -405,16 +389,12 @@ class ItemStackRequestExecutor{
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
public function getItemStackResponseBuilder() : ItemStackResponseBuilder{
|
||||
public function buildItemStackResponse() : ItemStackResponse{
|
||||
$builder = new ItemStackResponseBuilder($this->request->getRequestId(), $this->inventoryManager);
|
||||
foreach($this->requestSlotInfos as $requestInfo){
|
||||
$builder->addSlot($requestInfo->getContainerName()->getContainerId(), $requestInfo->getSlotId());
|
||||
}
|
||||
|
||||
return $builder;
|
||||
}
|
||||
|
||||
public function buildItemStackResponse() : ItemStackResponse{
|
||||
return $this->getItemStackResponseBuilder()->build();
|
||||
return $builder->build();
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ class PreSpawnPacketHandler extends PacketHandler{
|
||||
$this->server->getMotd(),
|
||||
"",
|
||||
false,
|
||||
new PlayerMovementSettings(ServerAuthMovementMode::SERVER_AUTHORITATIVE_V3, 0, true),
|
||||
new PlayerMovementSettings(ServerAuthMovementMode::SERVER_AUTHORITATIVE_V2, 0, false),
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
|
@ -411,7 +411,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
|
||||
public function getLeaveMessage() : Translatable|string{
|
||||
if($this->spawned){
|
||||
return KnownTranslationFactory::multiplayer_player_left($this->getDisplayName())->baseTextFormat(TextFormat::YELLOW);
|
||||
return KnownTranslationFactory::multiplayer_player_left($this->getDisplayName())->prefix(TextFormat::YELLOW);
|
||||
}
|
||||
|
||||
return "";
|
||||
@ -946,7 +946,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
});
|
||||
|
||||
$ev = new PlayerJoinEvent($this,
|
||||
KnownTranslationFactory::multiplayer_player_joined($this->getDisplayName())->baseTextFormat(TextFormat::YELLOW)
|
||||
KnownTranslationFactory::multiplayer_player_joined($this->getDisplayName())->prefix(TextFormat::YELLOW)
|
||||
);
|
||||
$ev->call();
|
||||
if($ev->getJoinMessage() !== ""){
|
||||
@ -1644,10 +1644,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
$newReplica = clone $oldHeldItem;
|
||||
$newReplica->setCount($newHeldItem->getCount());
|
||||
if($newReplica instanceof Durable && $newHeldItem instanceof Durable){
|
||||
$newDamage = $newHeldItem->getDamage();
|
||||
if($newDamage >= 0 && $newDamage <= $newReplica->getMaxDurability()){
|
||||
$newReplica->setDamage($newDamage);
|
||||
}
|
||||
$newReplica->setDamage($newHeldItem->getDamage());
|
||||
}
|
||||
$damagedOrDeducted = $newReplica->equalsExact($newHeldItem);
|
||||
|
||||
|
@ -190,10 +190,8 @@ abstract class TextFormat{
|
||||
* - Base format "§c" (red) + "Hello" (no format) = "§r§cHello"
|
||||
* - Base format "§c" + "Hello §rWorld" = "§r§cHello §r§cWorld"
|
||||
*
|
||||
* Note: Adding base formatting to the output string a second time won't override conflicting formatting from the
|
||||
* earlier call (e.g. adding base format BLUE to a string which already has YELLOW base formatting will
|
||||
* still result in yellow text after any RESET code). However, complementary codes (e.g. italic, bold) will combine
|
||||
* with the existing codes (e.g. adding ITALIC to a string with base format YELLOW will give yellow & italic text).
|
||||
* Note: Adding base formatting to the output string a second time will result in a combination of formats from both
|
||||
* calls. This is not by design, but simply a consequence of the way the function is implemented.
|
||||
*/
|
||||
public static function addBase(string $baseFormat, string $string) : string{
|
||||
$baseFormatParts = self::tokenize($baseFormat);
|
||||
|
@ -702,6 +702,12 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/inventory/transaction/InventoryTransaction.php
|
||||
|
||||
-
|
||||
message: '#^Cannot cast mixed to int\.$#'
|
||||
identifier: cast.int
|
||||
count: 2
|
||||
path: ../../../src/item/Item.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$buffer of method pocketmine\\nbt\\BaseNbtSerializer\:\:read\(\) expects string, mixed given\.$#'
|
||||
identifier: argument.type
|
||||
|
Reference in New Issue
Block a user