Compare commits

..

2 Commits

Author SHA1 Message Date
9946ea9e8a get rid of the old hack 2025-05-28 23:07:02 +01:00
14b70c04ad NetworkSession: add support for discarding repeated packets before they're decoded
this dramatically reduces the server workload dealing with spammy packets like right-click interactions trigger.
It also solves the problem of players getting kicked for right-clicking for too long.
2025-05-28 23:00:46 +01:00
26 changed files with 218 additions and 243 deletions

View File

@ -53,7 +53,7 @@ jobs:
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
- name: Build image for tag
uses: docker/build-push-action@v6.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

View File

@ -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
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "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",

View File

@ -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)){

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
"",

View File

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

View File

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

View File

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