mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-06-03 18:42:37 +00:00
Merge branch 'minor-next' into stable
This commit is contained in:
commit
ea339355bb
168
build/server-phar-stub.php
Normal file
168
build/server-phar-stub.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\server_phar_stub;
|
||||
|
||||
use function clearstatcache;
|
||||
use function copy;
|
||||
use function fclose;
|
||||
use function fflush;
|
||||
use function flock;
|
||||
use function fopen;
|
||||
use function fwrite;
|
||||
use function getmypid;
|
||||
use function hrtime;
|
||||
use function is_dir;
|
||||
use function is_file;
|
||||
use function mkdir;
|
||||
use function number_format;
|
||||
use function str_replace;
|
||||
use function stream_get_contents;
|
||||
use function sys_get_temp_dir;
|
||||
use function tempnam;
|
||||
use function unlink;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
use const LOCK_EX;
|
||||
use const LOCK_NB;
|
||||
use const LOCK_UN;
|
||||
|
||||
/**
|
||||
* Finds the appropriate tmp directory to store the decompressed phar cache, accounting for potential file name
|
||||
* collisions.
|
||||
*/
|
||||
function preparePharCacheDirectory() : string{
|
||||
clearstatcache();
|
||||
|
||||
$i = 0;
|
||||
do{
|
||||
$tmpPath = sys_get_temp_dir() . '/PocketMine-MP-phar-cache.' . $i;
|
||||
$i++;
|
||||
}while(is_file($tmpPath));
|
||||
if(!@mkdir($tmpPath) && !is_dir($tmpPath)){
|
||||
throw new \RuntimeException("Failed to create temporary directory $tmpPath. Please ensure the disk has enough space and that the current user has permission to write to this location.");
|
||||
}
|
||||
|
||||
return $tmpPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes caches left behind by previous server instances.
|
||||
* This ensures that the tmp directory doesn't get flooded by servers crashing in restart loops.
|
||||
*/
|
||||
function cleanupPharCache(string $tmpPath) : void{
|
||||
clearstatcache();
|
||||
|
||||
/** @var string[] $matches */
|
||||
foreach(new \RegexIterator(
|
||||
new \FilesystemIterator(
|
||||
$tmpPath,
|
||||
\FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS
|
||||
),
|
||||
'/(.+)\.lock$/',
|
||||
\RegexIterator::GET_MATCH
|
||||
) as $matches){
|
||||
$lockFilePath = $matches[0];
|
||||
$baseTmpPath = $matches[1];
|
||||
|
||||
$file = @fopen($lockFilePath, "rb");
|
||||
if($file === false){
|
||||
//another process probably deleted the lock file already
|
||||
continue;
|
||||
}
|
||||
|
||||
if(flock($file, LOCK_EX | LOCK_NB)){
|
||||
//this tmpfile is no longer in use
|
||||
flock($file, LOCK_UN);
|
||||
fclose($file);
|
||||
|
||||
unlink($lockFilePath);
|
||||
unlink($baseTmpPath . ".tar");
|
||||
unlink($baseTmpPath);
|
||||
echo "Deleted stale phar cache at $baseTmpPath\n";
|
||||
}else{
|
||||
$pid = stream_get_contents($file);
|
||||
fclose($file);
|
||||
|
||||
echo "Phar cache at $baseTmpPath is still in use by PID $pid\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertPharToTar(string $tmpName, string $pharPath) : string{
|
||||
$tmpPharPath = $tmpName . ".phar";
|
||||
copy($pharPath, $tmpPharPath);
|
||||
|
||||
$phar = new \Phar($tmpPharPath);
|
||||
//phar requires phar.readonly=0, and zip doesn't support disabling compression - tar is the only viable option
|
||||
//we don't need phar anyway since we don't need to directly execute the file, only require files from inside it
|
||||
$phar->convertToData(\Phar::TAR, \Phar::NONE);
|
||||
unset($phar);
|
||||
\Phar::unlinkArchive($tmpPharPath);
|
||||
|
||||
return $tmpName . ".tar";
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks a phar tmp cache to prevent it from being deleted by other server instances.
|
||||
* This code looks similar to Filesystem::createLockFile(), but we can't use that because it's inside the compressed
|
||||
* phar.
|
||||
*/
|
||||
function lockPharCache(string $lockFilePath) : void{
|
||||
//this static variable will keep the file(s) locked until the process ends
|
||||
static $lockFiles = [];
|
||||
|
||||
$lockFile = fopen($lockFilePath, "wb");
|
||||
if($lockFile === false){
|
||||
throw new \RuntimeException("Failed to open temporary file");
|
||||
}
|
||||
flock($lockFile, LOCK_EX); //this tells other server instances not to delete this cache file
|
||||
fwrite($lockFile, (string) getmypid()); //maybe useful for debugging
|
||||
fflush($lockFile);
|
||||
$lockFiles[$lockFilePath] = $lockFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a decompressed .tar of PocketMine-MP.phar in the system temp directory for loading code from.
|
||||
*
|
||||
* @return string path to the temporary decompressed phar (actually a .tar)
|
||||
*/
|
||||
function preparePharCache(string $tmpPath, string $pharPath) : string{
|
||||
clearstatcache();
|
||||
|
||||
$tmpName = tempnam($tmpPath, "PMMP");
|
||||
if($tmpName === false){
|
||||
throw new \RuntimeException("Failed to create temporary file");
|
||||
}
|
||||
|
||||
lockPharCache($tmpName . ".lock");
|
||||
return convertPharToTar($tmpName, $pharPath);
|
||||
}
|
||||
|
||||
$tmpDir = preparePharCacheDirectory();
|
||||
cleanupPharCache($tmpDir);
|
||||
echo "Preparing PocketMine-MP.phar decompressed cache...\n";
|
||||
$start = hrtime(true);
|
||||
$cacheName = preparePharCache($tmpDir, __FILE__);
|
||||
echo "Cache ready at $cacheName in " . number_format((hrtime(true) - $start) / 1e9, 2) . "s\n";
|
||||
|
||||
require 'phar://' . str_replace(DIRECTORY_SEPARATOR, '/', $cacheName) . '/src/PocketMine.php';
|
@ -23,7 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\build\server_phar;
|
||||
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Git;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function dirname;
|
||||
@ -169,21 +171,7 @@ function main() : void{
|
||||
'git' => $gitHash,
|
||||
'build' => $build
|
||||
],
|
||||
<<<'STUB'
|
||||
<?php
|
||||
|
||||
$tmpDir = sys_get_temp_dir();
|
||||
if(!is_readable($tmpDir) or !is_writable($tmpDir)){
|
||||
echo "ERROR: tmpdir $tmpDir is not accessible." . PHP_EOL;
|
||||
echo "Check that the directory exists, and that the current user has read/write permissions for it." . PHP_EOL;
|
||||
echo "Alternatively, set 'sys_temp_dir' to a different directory in your php.ini file." . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require("phar://" . __FILE__ . "/src/PocketMine.php");
|
||||
__HALT_COMPILER();
|
||||
STUB
|
||||
,
|
||||
Filesystem::fileGetContents(Path::join(__DIR__, 'server-phar-stub.php')) . "\n__HALT_COMPILER();",
|
||||
\Phar::SHA1,
|
||||
\Phar::GZ
|
||||
) as $line){
|
||||
|
@ -22,7 +22,7 @@
|
||||
"ext-openssl": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-phar": "*",
|
||||
"ext-pmmpthread": "^6.0.7",
|
||||
"ext-pmmpthread": "^6.1.0",
|
||||
"ext-reflection": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-sockets": "*",
|
||||
@ -33,20 +33,20 @@
|
||||
"composer-runtime-api": "^2.0",
|
||||
"adhocore/json-comment": "~1.2.0",
|
||||
"pocketmine/netresearch-jsonmapper": "~v4.4.999",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~3.6.0+bedrock-1.20.70",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~4.0.0+bedrock-1.20.70",
|
||||
"pocketmine/bedrock-data": "~2.9.0+bedrock-1.20.70",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.8.0+bedrock-1.20.70",
|
||||
"pocketmine/bedrock-protocol": "~29.0.0+bedrock-1.20.70",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/color": "^0.3.0",
|
||||
"pocketmine/errorhandler": "^0.6.0",
|
||||
"pocketmine/errorhandler": "^0.7.0",
|
||||
"pocketmine/locale-data": "~2.19.0",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/math": "~1.0.0",
|
||||
"pocketmine/nbt": "~1.0.0",
|
||||
"pocketmine/raklib": "^0.15.0",
|
||||
"pocketmine/raklib-ipc": "^0.2.0",
|
||||
"pocketmine/raklib": "~1.1.0",
|
||||
"pocketmine/raklib-ipc": "~1.0.0",
|
||||
"pocketmine/snooze": "^0.5.0",
|
||||
"ramsey/uuid": "~4.7.0",
|
||||
"symfony/filesystem": "~6.4.0"
|
||||
|
80
composer.lock
generated
80
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": "f0a5391180046806c7263b50a8913ddd",
|
||||
"content-hash": "40f8971303dc2060ae4e28e6fc84bdfc",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -122,16 +122,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-block-upgrade-schema",
|
||||
"version": "3.6.0",
|
||||
"version": "4.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
|
||||
"reference": "1496e275db5148cb96bdaa998115e5e31a5c1e4d"
|
||||
"reference": "ebd768e5b202cae59b0a7057982e3a2f40ba1954"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/1496e275db5148cb96bdaa998115e5e31a5c1e4d",
|
||||
"reference": "1496e275db5148cb96bdaa998115e5e31a5c1e4d",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/ebd768e5b202cae59b0a7057982e3a2f40ba1954",
|
||||
"reference": "ebd768e5b202cae59b0a7057982e3a2f40ba1954",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -142,9 +142,9 @@
|
||||
"description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues",
|
||||
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/3.6.0"
|
||||
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/4.0.0"
|
||||
},
|
||||
"time": "2024-02-28T19:25:25+00:00"
|
||||
"time": "2024-04-05T16:02:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-data",
|
||||
@ -376,25 +376,25 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/errorhandler",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/ErrorHandler.git",
|
||||
"reference": "dae214a04348b911e8219ebf125ff1c5589cc878"
|
||||
"reference": "cae94884368a74ece5294b9ff7fef18732dcd921"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/ErrorHandler/zipball/dae214a04348b911e8219ebf125ff1c5589cc878",
|
||||
"reference": "dae214a04348b911e8219ebf125ff1c5589cc878",
|
||||
"url": "https://api.github.com/repos/pmmp/ErrorHandler/zipball/cae94884368a74ece5294b9ff7fef18732dcd921",
|
||||
"reference": "cae94884368a74ece5294b9ff7fef18732dcd921",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "0.12.99",
|
||||
"phpstan/phpstan-strict-rules": "^0.12.2",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
"phpstan/phpstan": "~1.10.3",
|
||||
"phpstan/phpstan-strict-rules": "^1.0",
|
||||
"phpunit/phpunit": "^9.5 || ^10.0 || ^11.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@ -409,9 +409,9 @@
|
||||
"description": "Utilities to handle nasty PHP E_* errors in a usable way",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/ErrorHandler/issues",
|
||||
"source": "https://github.com/pmmp/ErrorHandler/tree/0.6.0"
|
||||
"source": "https://github.com/pmmp/ErrorHandler/tree/0.7.0"
|
||||
},
|
||||
"time": "2022-01-08T21:05:46+00:00"
|
||||
"time": "2024-04-02T18:29:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/locale-data",
|
||||
@ -616,28 +616,28 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/raklib",
|
||||
"version": "0.15.1",
|
||||
"version": "1.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/RakLib.git",
|
||||
"reference": "79b7b4d1d7516dc6e322514453645ad9452b20ca"
|
||||
"reference": "be2783be516bf6e2872ff5c81fb9048596617b97"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/79b7b4d1d7516dc6e322514453645ad9452b20ca",
|
||||
"reference": "79b7b4d1d7516dc6e322514453645ad9452b20ca",
|
||||
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/be2783be516bf6e2872ff5c81fb9048596617b97",
|
||||
"reference": "be2783be516bf6e2872ff5c81fb9048596617b97",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-sockets": "*",
|
||||
"php": "^8.0",
|
||||
"php": "^8.1",
|
||||
"php-64bit": "*",
|
||||
"php-ipv6": "*",
|
||||
"pocketmine/binaryutils": "^0.2.0",
|
||||
"pocketmine/log": "^0.3.0 || ^0.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.9.17",
|
||||
"phpstan/phpstan": "1.10.1",
|
||||
"phpstan/phpstan-strict-rules": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
@ -653,32 +653,32 @@
|
||||
"description": "A RakNet server implementation written in PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/RakLib/issues",
|
||||
"source": "https://github.com/pmmp/RakLib/tree/0.15.1"
|
||||
"source": "https://github.com/pmmp/RakLib/tree/1.1.1"
|
||||
},
|
||||
"time": "2023-03-07T15:10:34+00:00"
|
||||
"time": "2024-03-04T14:02:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/raklib-ipc",
|
||||
"version": "0.2.0",
|
||||
"version": "1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/RakLibIpc.git",
|
||||
"reference": "26ed56fa9db06e4ca6e8920c0ede2e01e219bb9c"
|
||||
"reference": "ce632ef2c6743e71eddb5dc329c49af6555f90bc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/RakLibIpc/zipball/26ed56fa9db06e4ca6e8920c0ede2e01e219bb9c",
|
||||
"reference": "26ed56fa9db06e4ca6e8920c0ede2e01e219bb9c",
|
||||
"url": "https://api.github.com/repos/pmmp/RakLibIpc/zipball/ce632ef2c6743e71eddb5dc329c49af6555f90bc",
|
||||
"reference": "ce632ef2c6743e71eddb5dc329c49af6555f90bc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"php-64bit": "*",
|
||||
"pocketmine/binaryutils": "^0.2.0",
|
||||
"pocketmine/raklib": "^0.15.0"
|
||||
"pocketmine/raklib": "^0.15.0 || ^1.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.9.17",
|
||||
"phpstan/phpstan": "1.10.1",
|
||||
"phpstan/phpstan-strict-rules": "^1.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
@ -694,9 +694,9 @@
|
||||
"description": "Channel-based protocols for inter-thread/inter-process communication with RakLib",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/RakLibIpc/issues",
|
||||
"source": "https://github.com/pmmp/RakLibIpc/tree/0.2.0"
|
||||
"source": "https://github.com/pmmp/RakLibIpc/tree/1.0.1"
|
||||
},
|
||||
"time": "2023-02-13T13:40:40+00:00"
|
||||
"time": "2024-03-01T15:55:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/snooze",
|
||||
@ -2335,16 +2335,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/environment",
|
||||
"version": "6.0.1",
|
||||
"version": "6.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/environment.git",
|
||||
"reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951"
|
||||
"reference": "8074dbcd93529b357029f5cc5058fd3e43666984"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/43c751b41d74f96cbbd4e07b7aec9675651e2951",
|
||||
"reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984",
|
||||
"reference": "8074dbcd93529b357029f5cc5058fd3e43666984",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2359,7 +2359,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "6.0-dev"
|
||||
"dev-main": "6.1-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -2387,7 +2387,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/environment/issues",
|
||||
"security": "https://github.com/sebastianbergmann/environment/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/environment/tree/6.0.1"
|
||||
"source": "https://github.com/sebastianbergmann/environment/tree/6.1.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -2395,7 +2395,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-04-11T05:39:26+00:00"
|
||||
"time": "2024-03-23T08:47:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/exporter",
|
||||
@ -2953,7 +2953,7 @@
|
||||
"ext-openssl": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-phar": "*",
|
||||
"ext-pmmpthread": "^6.0.7",
|
||||
"ext-pmmpthread": "^6.1.0",
|
||||
"ext-reflection": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-sockets": "*",
|
||||
|
@ -47,4 +47,6 @@ final class BootstrapOptions{
|
||||
public const DATA = "data";
|
||||
/** Shows basic server version information and exits */
|
||||
public const VERSION = "version";
|
||||
/** Disables writing logs to server.log */
|
||||
public const NO_LOG_FILE = "no-log-file";
|
||||
}
|
||||
|
@ -317,7 +317,7 @@ JIT_WARNING
|
||||
//Logger has a dependency on timezone
|
||||
Timezone::init();
|
||||
|
||||
$opts = getopt("", [BootstrapOptions::NO_WIZARD, BootstrapOptions::ENABLE_ANSI, BootstrapOptions::DISABLE_ANSI]);
|
||||
$opts = getopt("", [BootstrapOptions::NO_WIZARD, BootstrapOptions::ENABLE_ANSI, BootstrapOptions::DISABLE_ANSI, BootstrapOptions::NO_LOG_FILE]);
|
||||
if(isset($opts[BootstrapOptions::ENABLE_ANSI])){
|
||||
Terminal::init(true);
|
||||
}elseif(isset($opts[BootstrapOptions::DISABLE_ANSI])){
|
||||
@ -325,8 +325,13 @@ JIT_WARNING
|
||||
}else{
|
||||
Terminal::init();
|
||||
}
|
||||
$logFile = isset($opts[BootstrapOptions::NO_LOG_FILE]) ? null : Path::join($dataPath, "server.log");
|
||||
|
||||
$logger = new MainLogger($logFile, Terminal::hasFormattingCodes(), "Server", new \DateTimeZone(Timezone::get()), false, Path::join($dataPath, "log_archive"));
|
||||
if($logFile === null){
|
||||
$logger->notice("Logging to file disabled. Ensure logs are collected by other means (e.g. Docker logs).");
|
||||
}
|
||||
|
||||
$logger = new MainLogger(Path::join($dataPath, "server.log"), Terminal::hasFormattingCodes(), "Server", new \DateTimeZone(Timezone::get()));
|
||||
\GlobalLogger::set($logger);
|
||||
|
||||
emit_performance_warnings($logger);
|
||||
|
@ -63,7 +63,8 @@ final class VersionInfo{
|
||||
if(\Phar::running(true) === ""){
|
||||
$gitHash = Git::getRepositoryStatePretty(\pocketmine\PATH);
|
||||
}else{
|
||||
$phar = new \Phar(\Phar::running(false));
|
||||
$pharPath = \Phar::running(false);
|
||||
$phar = \Phar::isValidPharFilename($pharPath) ? new \Phar($pharPath) : new \PharData($pharPath);
|
||||
$meta = $phar->getMetadata();
|
||||
if(isset($meta["git"])){
|
||||
$gitHash = $meta["git"];
|
||||
@ -82,7 +83,8 @@ final class VersionInfo{
|
||||
if(self::$buildNumber === null){
|
||||
self::$buildNumber = 0;
|
||||
if(\Phar::running(true) !== ""){
|
||||
$phar = new \Phar(\Phar::running(false));
|
||||
$pharPath = \Phar::running(false);
|
||||
$phar = \Phar::isValidPharFilename($pharPath) ? new \Phar($pharPath) : new \PharData($pharPath);
|
||||
$meta = $phar->getMetadata();
|
||||
if(is_array($meta) && isset($meta["build"]) && is_int($meta["build"])){
|
||||
self::$buildNumber = $meta["build"];
|
||||
|
@ -36,6 +36,7 @@ use pocketmine\item\VanillaItems;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\sound\SweetBerriesPickSound;
|
||||
use function mt_rand;
|
||||
|
||||
class SweetBerryBush extends Flowable{
|
||||
@ -81,6 +82,7 @@ class SweetBerryBush extends Flowable{
|
||||
}elseif(($dropAmount = $this->getBerryDropAmount()) > 0){
|
||||
$world->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES));
|
||||
$world->dropItem($this->position, $this->asItem()->setCount($dropAmount));
|
||||
$world->addSound($this->position, new SweetBerriesPickSound());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -82,6 +82,7 @@ enum BannerPatternType{
|
||||
case DIAGONAL_UP_LEFT;
|
||||
case DIAGONAL_UP_RIGHT;
|
||||
case FLOWER;
|
||||
case GLOBE;
|
||||
case GRADIENT;
|
||||
case GRADIENT_UP;
|
||||
case HALF_HORIZONTAL;
|
||||
@ -89,6 +90,7 @@ enum BannerPatternType{
|
||||
case HALF_VERTICAL;
|
||||
case HALF_VERTICAL_RIGHT;
|
||||
case MOJANG;
|
||||
case PIGLIN;
|
||||
case RHOMBUS;
|
||||
case SKULL;
|
||||
case SMALL_STRIPES;
|
||||
|
@ -63,6 +63,12 @@ use function strpos;
|
||||
use function substr;
|
||||
use function zend_version;
|
||||
use function zlib_encode;
|
||||
use const E_COMPILE_ERROR;
|
||||
use const E_CORE_ERROR;
|
||||
use const E_ERROR;
|
||||
use const E_PARSE;
|
||||
use const E_RECOVERABLE_ERROR;
|
||||
use const E_USER_ERROR;
|
||||
use const FILE_IGNORE_NEW_LINES;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
use const JSON_UNESCAPED_SLASHES;
|
||||
@ -85,6 +91,9 @@ class CrashDump{
|
||||
public const PLUGIN_INVOLVEMENT_DIRECT = "direct";
|
||||
public const PLUGIN_INVOLVEMENT_INDIRECT = "indirect";
|
||||
|
||||
public const FATAL_ERROR_MASK =
|
||||
E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR;
|
||||
|
||||
private CrashDumpData $data;
|
||||
private string $encodedData;
|
||||
|
||||
@ -186,7 +195,7 @@ class CrashDump{
|
||||
$error = $lastExceptionError;
|
||||
}else{
|
||||
$error = error_get_last();
|
||||
if($error === null){
|
||||
if($error === null || ($error["type"] & self::FATAL_ERROR_MASK) === 0){
|
||||
throw new \RuntimeException("Crash error information missing - did something use exit()?");
|
||||
}
|
||||
$error["trace"] = Utils::printableTrace(Utils::currentTrace(3)); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump
|
||||
|
@ -56,6 +56,7 @@ final class BannerPatternTypeIdMap{
|
||||
BannerPatternType::DIAGONAL_UP_LEFT => "ld",
|
||||
BannerPatternType::DIAGONAL_UP_RIGHT => "rud",
|
||||
BannerPatternType::FLOWER => "flo",
|
||||
BannerPatternType::GLOBE => "glb",
|
||||
BannerPatternType::GRADIENT => "gra",
|
||||
BannerPatternType::GRADIENT_UP => "gru",
|
||||
BannerPatternType::HALF_HORIZONTAL => "hh",
|
||||
@ -63,6 +64,7 @@ final class BannerPatternTypeIdMap{
|
||||
BannerPatternType::HALF_VERTICAL => "vh",
|
||||
BannerPatternType::HALF_VERTICAL_RIGHT => "vhr",
|
||||
BannerPatternType::MOJANG => "moj",
|
||||
BannerPatternType::PIGLIN => "pig",
|
||||
BannerPatternType::RHOMBUS => "mr",
|
||||
BannerPatternType::SKULL => "sku",
|
||||
BannerPatternType::SMALL_STRIPES => "ss",
|
||||
|
@ -38,20 +38,24 @@ use pocketmine\nbt\tag\ByteTag;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\nbt\tag\Tag;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_keys;
|
||||
use function count;
|
||||
use function get_class;
|
||||
use function implode;
|
||||
|
||||
final class BlockStateReader{
|
||||
|
||||
/**
|
||||
* @var true[]
|
||||
* @phpstan-var array<string, true>
|
||||
* @var Tag[]
|
||||
* @phpstan-var array<string, Tag>
|
||||
*/
|
||||
private array $usedStates = [];
|
||||
private array $unusedStates;
|
||||
|
||||
public function __construct(
|
||||
private BlockStateData $data
|
||||
){}
|
||||
){
|
||||
$this->unusedStates = $this->data->getStates();
|
||||
}
|
||||
|
||||
public function missingOrWrongTypeException(string $name, ?Tag $tag) : BlockStateDeserializeException{
|
||||
return new BlockStateDeserializeException("Property \"$name\" " . ($tag !== null ? "has unexpected type " . get_class($tag) : "is missing"));
|
||||
@ -66,7 +70,7 @@ final class BlockStateReader{
|
||||
|
||||
/** @throws BlockStateDeserializeException */
|
||||
public function readBool(string $name) : bool{
|
||||
$this->usedStates[$name] = true;
|
||||
unset($this->unusedStates[$name]);
|
||||
$tag = $this->data->getState($name);
|
||||
if($tag instanceof ByteTag){
|
||||
switch($tag->getValue()){
|
||||
@ -80,7 +84,7 @@ final class BlockStateReader{
|
||||
|
||||
/** @throws BlockStateDeserializeException */
|
||||
public function readInt(string $name) : int{
|
||||
$this->usedStates[$name] = true;
|
||||
unset($this->unusedStates[$name]);
|
||||
$tag = $this->data->getState($name);
|
||||
if($tag instanceof IntTag){
|
||||
return $tag->getValue();
|
||||
@ -99,7 +103,7 @@ final class BlockStateReader{
|
||||
|
||||
/** @throws BlockStateDeserializeException */
|
||||
public function readString(string $name) : string{
|
||||
$this->usedStates[$name] = true;
|
||||
unset($this->unusedStates[$name]);
|
||||
//TODO: only allow a specific set of values (strings are primarily used for enums)
|
||||
$tag = $this->data->getState($name);
|
||||
if($tag instanceof StringTag){
|
||||
@ -346,7 +350,7 @@ final class BlockStateReader{
|
||||
*/
|
||||
public function ignored(string $name) : void{
|
||||
if($this->data->getState($name) !== null){
|
||||
$this->usedStates[$name] = true;
|
||||
unset($this->unusedStates[$name]);
|
||||
}else{
|
||||
throw $this->missingOrWrongTypeException($name, null);
|
||||
}
|
||||
@ -363,10 +367,8 @@ final class BlockStateReader{
|
||||
* @throws BlockStateDeserializeException
|
||||
*/
|
||||
public function checkUnreadProperties() : void{
|
||||
foreach(Utils::stringifyKeys($this->data->getStates()) as $name => $tag){
|
||||
if(!isset($this->usedStates[$name])){
|
||||
throw new BlockStateDeserializeException("Unread property \"$name\"");
|
||||
}
|
||||
if(count($this->unusedStates) > 0){
|
||||
throw new BlockStateDeserializeException("Unread properties: " . implode(", ", array_keys($this->unusedStates)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,20 +64,24 @@ final class BlockStateUpgradeSchema{
|
||||
*/
|
||||
public array $remappedStates = [];
|
||||
|
||||
public readonly int $versionId;
|
||||
|
||||
public function __construct(
|
||||
public int $maxVersionMajor,
|
||||
public int $maxVersionMinor,
|
||||
public int $maxVersionPatch,
|
||||
public int $maxVersionRevision,
|
||||
public readonly int $maxVersionMajor,
|
||||
public readonly int $maxVersionMinor,
|
||||
public readonly int $maxVersionPatch,
|
||||
public readonly int $maxVersionRevision,
|
||||
private int $schemaId
|
||||
){}
|
||||
){
|
||||
$this->versionId = ($this->maxVersionMajor << 24) | ($this->maxVersionMinor << 16) | ($this->maxVersionPatch << 8) | $this->maxVersionRevision;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This is defined by Mojang, and therefore cannot be relied on. Use getSchemaId() instead for
|
||||
* internal version management.
|
||||
*/
|
||||
public function getVersionId() : int{
|
||||
return ($this->maxVersionMajor << 24) | ($this->maxVersionMinor << 16) | ($this->maxVersionPatch << 8) | $this->maxVersionRevision;
|
||||
return $this->versionId;
|
||||
}
|
||||
|
||||
public function getSchemaId() : int{ return $this->schemaId; }
|
||||
|
@ -23,17 +23,28 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\data\bedrock\block\upgrade;
|
||||
|
||||
use function ksort;
|
||||
use const SORT_STRING;
|
||||
|
||||
final class BlockStateUpgradeSchemaFlattenedName{
|
||||
|
||||
/**
|
||||
* @param string[] $flattenedValueRemaps
|
||||
* @phpstan-param array<string, string> $flattenedValueRemaps
|
||||
*/
|
||||
public function __construct(
|
||||
public string $prefix,
|
||||
public string $flattenedProperty,
|
||||
public string $suffix
|
||||
){}
|
||||
public string $suffix,
|
||||
public array $flattenedValueRemaps
|
||||
){
|
||||
ksort($this->flattenedValueRemaps, SORT_STRING);
|
||||
}
|
||||
|
||||
public function equals(self $that) : bool{
|
||||
return $this->prefix === $that->prefix &&
|
||||
$this->flattenedProperty === $that->flattenedProperty &&
|
||||
$this->suffix === $that->suffix;
|
||||
$this->suffix === $that->suffix &&
|
||||
$this->flattenedValueRemaps === $that->flattenedValueRemaps;
|
||||
}
|
||||
}
|
||||
|
@ -166,7 +166,8 @@ final class BlockStateUpgradeSchemaUtils{
|
||||
$remap->newName ?? new BlockStateUpgradeSchemaFlattenedName(
|
||||
$remap->newFlattenedName->prefix,
|
||||
$remap->newFlattenedName->flattenedProperty,
|
||||
$remap->newFlattenedName->suffix
|
||||
$remap->newFlattenedName->suffix,
|
||||
$remap->newFlattenedName->flattenedValueRemaps ?? [],
|
||||
),
|
||||
array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->newState ?? []),
|
||||
$remap->copiedState ?? []
|
||||
@ -301,7 +302,8 @@ final class BlockStateUpgradeSchemaUtils{
|
||||
new BlockStateUpgradeSchemaModelFlattenedName(
|
||||
$remap->newName->prefix,
|
||||
$remap->newName->flattenedProperty,
|
||||
$remap->newName->suffix
|
||||
$remap->newName->suffix,
|
||||
$remap->newName->flattenedValueRemaps
|
||||
),
|
||||
array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->newState),
|
||||
$remap->copiedState
|
||||
|
@ -35,9 +35,14 @@ use function sprintf;
|
||||
use const SORT_NUMERIC;
|
||||
|
||||
final class BlockStateUpgrader{
|
||||
/** @var BlockStateUpgradeSchema[] */
|
||||
/**
|
||||
* @var BlockStateUpgradeSchema[][] versionId => [schemaId => schema]
|
||||
* @phpstan-var array<int, array<int, BlockStateUpgradeSchema>>
|
||||
*/
|
||||
private array $upgradeSchemas = [];
|
||||
|
||||
private int $outputVersion = 0;
|
||||
|
||||
/**
|
||||
* @param BlockStateUpgradeSchema[] $upgradeSchemas
|
||||
* @phpstan-param array<int, BlockStateUpgradeSchema> $upgradeSchemas
|
||||
@ -50,87 +55,116 @@ final class BlockStateUpgrader{
|
||||
|
||||
public function addSchema(BlockStateUpgradeSchema $schema) : void{
|
||||
$schemaId = $schema->getSchemaId();
|
||||
if(isset($this->upgradeSchemas[$schemaId])){
|
||||
throw new \InvalidArgumentException("Cannot add two schemas with the same schema ID");
|
||||
$versionId = $schema->getVersionId();
|
||||
if(isset($this->upgradeSchemas[$versionId][$schemaId])){
|
||||
throw new \InvalidArgumentException("Cannot add two schemas with the same schema ID and version ID");
|
||||
}
|
||||
$this->upgradeSchemas[$schemaId] = $schema;
|
||||
|
||||
//schema ID tells us the order when multiple schemas use the same version ID
|
||||
$this->upgradeSchemas[$versionId][$schemaId] = $schema;
|
||||
|
||||
ksort($this->upgradeSchemas, SORT_NUMERIC);
|
||||
ksort($this->upgradeSchemas[$versionId], SORT_NUMERIC);
|
||||
|
||||
$this->outputVersion = max($this->outputVersion, $schema->getVersionId());
|
||||
}
|
||||
|
||||
public function upgrade(BlockStateData $blockStateData) : BlockStateData{
|
||||
$version = $blockStateData->getVersion();
|
||||
$highestVersion = $version;
|
||||
foreach($this->upgradeSchemas as $schema){
|
||||
$resultVersion = $schema->getVersionId();
|
||||
$highestVersion = max($highestVersion, $resultVersion);
|
||||
if($version > $resultVersion){
|
||||
//even if this is actually the same version, we have to apply it anyway because mojang are dumb and
|
||||
//didn't always bump the blockstate version when changing it :(
|
||||
foreach($this->upgradeSchemas as $resultVersion => $schemaList){
|
||||
/*
|
||||
* Sometimes Mojang made changes without bumping the version ID.
|
||||
* A notable example is 0131_1.18.20.27_beta_to_1.18.30.json, which renamed a bunch of blockIDs.
|
||||
* When this happens, all the schemas must be applied even if the version is the same, because the input
|
||||
* version doesn't tell us which of the schemas have already been applied.
|
||||
* If there's only one schema for a version (the norm), we can safely assume it's already been applied if
|
||||
* the version is the same, and skip over it.
|
||||
*/
|
||||
if($version > $resultVersion || (count($schemaList) === 1 && $version === $resultVersion)){
|
||||
continue;
|
||||
}
|
||||
$oldName = $blockStateData->getName();
|
||||
$oldState = $blockStateData->getStates();
|
||||
if(isset($schema->remappedStates[$oldName])){
|
||||
foreach($schema->remappedStates[$oldName] as $remap){
|
||||
if(count($remap->oldState) > count($oldState)){
|
||||
//match criteria has more requirements than we have state properties
|
||||
continue; //try next state
|
||||
}
|
||||
foreach(Utils::stringifyKeys($remap->oldState) as $k => $v){
|
||||
if(!isset($oldState[$k]) || !$oldState[$k]->equals($v)){
|
||||
continue 2; //try next state
|
||||
}
|
||||
}
|
||||
|
||||
if(is_string($remap->newName)){
|
||||
$newName = $remap->newName;
|
||||
}else{
|
||||
$flattenedValue = $oldState[$remap->newName->flattenedProperty] ?? null;
|
||||
if($flattenedValue instanceof StringTag){
|
||||
$newName = sprintf("%s%s%s", $remap->newName->prefix, $flattenedValue->getValue(), $remap->newName->suffix);
|
||||
unset($oldState[$remap->newName->flattenedProperty]);
|
||||
}else{
|
||||
//flattened property is not a TAG_String, so this transformation is not applicable
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$newState = $remap->newState;
|
||||
foreach($remap->copiedState as $stateName){
|
||||
if(isset($oldState[$stateName])){
|
||||
$newState[$stateName] = $oldState[$stateName];
|
||||
}
|
||||
}
|
||||
|
||||
$blockStateData = new BlockStateData($newName, $newState, $resultVersion);
|
||||
continue 2; //try next schema
|
||||
}
|
||||
}
|
||||
$newName = $schema->renamedIds[$oldName] ?? null;
|
||||
|
||||
$stateChanges = 0;
|
||||
$states = $blockStateData->getStates();
|
||||
|
||||
$states = $this->applyPropertyAdded($schema, $oldName, $states, $stateChanges);
|
||||
$states = $this->applyPropertyRemoved($schema, $oldName, $states, $stateChanges);
|
||||
$states = $this->applyPropertyRenamedOrValueChanged($schema, $oldName, $states, $stateChanges);
|
||||
$states = $this->applyPropertyValueChanged($schema, $oldName, $states, $stateChanges);
|
||||
|
||||
if($newName !== null || $stateChanges > 0){
|
||||
$blockStateData = new BlockStateData($newName ?? $oldName, $states, $resultVersion);
|
||||
//don't break out; we may need to further upgrade the state
|
||||
foreach($schemaList as $schema){
|
||||
$blockStateData = $this->applySchema($schema, $blockStateData);
|
||||
}
|
||||
}
|
||||
|
||||
if($highestVersion > $version){
|
||||
if($this->outputVersion > $version){
|
||||
//always update the version number of the blockstate, even if it didn't change - this is needed for
|
||||
//external tools
|
||||
$blockStateData = new BlockStateData($blockStateData->getName(), $blockStateData->getStates(), $highestVersion);
|
||||
$blockStateData = new BlockStateData($blockStateData->getName(), $blockStateData->getStates(), $this->outputVersion);
|
||||
}
|
||||
return $blockStateData;
|
||||
}
|
||||
|
||||
private function applySchema(BlockStateUpgradeSchema $schema, BlockStateData $blockStateData) : BlockStateData{
|
||||
$newStateData = $this->applyStateRemapped($schema, $blockStateData);
|
||||
if($newStateData !== null){
|
||||
return $newStateData;
|
||||
}
|
||||
|
||||
$oldName = $blockStateData->getName();
|
||||
$newName = $schema->renamedIds[$oldName] ?? null;
|
||||
|
||||
$stateChanges = 0;
|
||||
$states = $blockStateData->getStates();
|
||||
|
||||
$states = $this->applyPropertyAdded($schema, $oldName, $states, $stateChanges);
|
||||
$states = $this->applyPropertyRemoved($schema, $oldName, $states, $stateChanges);
|
||||
$states = $this->applyPropertyRenamedOrValueChanged($schema, $oldName, $states, $stateChanges);
|
||||
$states = $this->applyPropertyValueChanged($schema, $oldName, $states, $stateChanges);
|
||||
|
||||
if($newName !== null || $stateChanges > 0){
|
||||
return new BlockStateData($newName ?? $oldName, $states, $schema->getVersionId());
|
||||
}
|
||||
|
||||
return $blockStateData;
|
||||
}
|
||||
|
||||
private function applyStateRemapped(BlockStateUpgradeSchema $schema, BlockStateData $blockStateData) : ?BlockStateData{
|
||||
$oldName = $blockStateData->getName();
|
||||
$oldState = $blockStateData->getStates();
|
||||
|
||||
if(isset($schema->remappedStates[$oldName])){
|
||||
foreach($schema->remappedStates[$oldName] as $remap){
|
||||
if(count($remap->oldState) > count($oldState)){
|
||||
//match criteria has more requirements than we have state properties
|
||||
continue; //try next state
|
||||
}
|
||||
foreach(Utils::stringifyKeys($remap->oldState) as $k => $v){
|
||||
if(!isset($oldState[$k]) || !$oldState[$k]->equals($v)){
|
||||
continue 2; //try next state
|
||||
}
|
||||
}
|
||||
|
||||
if(is_string($remap->newName)){
|
||||
$newName = $remap->newName;
|
||||
}else{
|
||||
$flattenedValue = $oldState[$remap->newName->flattenedProperty] ?? null;
|
||||
if($flattenedValue instanceof StringTag){
|
||||
$embedValue = $remap->newName->flattenedValueRemaps[$flattenedValue->getValue()] ?? $flattenedValue->getValue();
|
||||
$newName = sprintf("%s%s%s", $remap->newName->prefix, $embedValue, $remap->newName->suffix);
|
||||
unset($oldState[$remap->newName->flattenedProperty]);
|
||||
}else{
|
||||
//flattened property is not a TAG_String, so this transformation is not applicable
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$newState = $remap->newState;
|
||||
foreach($remap->copiedState as $stateName){
|
||||
if(isset($oldState[$stateName])){
|
||||
$newState[$stateName] = $oldState[$stateName];
|
||||
}
|
||||
}
|
||||
|
||||
return new BlockStateData($newName, $newState, $schema->getVersionId());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag[] $states
|
||||
* @phpstan-param array<string, Tag> $states
|
||||
|
@ -23,7 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\data\bedrock\block\upgrade\model;
|
||||
|
||||
final class BlockStateUpgradeSchemaModelFlattenedName{
|
||||
use function count;
|
||||
|
||||
final class BlockStateUpgradeSchemaModelFlattenedName implements \JsonSerializable{
|
||||
|
||||
/** @required */
|
||||
public string $prefix;
|
||||
@ -31,10 +33,31 @@ final class BlockStateUpgradeSchemaModelFlattenedName{
|
||||
public string $flattenedProperty;
|
||||
/** @required */
|
||||
public string $suffix;
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var array<string, string>
|
||||
*/
|
||||
public array $flattenedValueRemaps;
|
||||
|
||||
public function __construct(string $prefix, string $flattenedProperty, string $suffix){
|
||||
/**
|
||||
* @param string[] $flattenedValueRemaps
|
||||
* @phpstan-param array<string, string> $flattenedValueRemaps
|
||||
*/
|
||||
public function __construct(string $prefix, string $flattenedProperty, string $suffix, array $flattenedValueRemaps){
|
||||
$this->prefix = $prefix;
|
||||
$this->flattenedProperty = $flattenedProperty;
|
||||
$this->suffix = $suffix;
|
||||
$this->flattenedValueRemaps = $flattenedValueRemaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize() : array{
|
||||
$result = (array) $this;
|
||||
if(count($this->flattenedValueRemaps) === 0){
|
||||
unset($result["flattenedValueRemaps"]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +149,24 @@ abstract class Living extends Entity{
|
||||
$this->getViewers(),
|
||||
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobArmorChange($recipients, $this)
|
||||
)));
|
||||
$playArmorSound = function(Item $newItem, Item $oldItem) : void{
|
||||
if(!$newItem->isNull() && $newItem instanceof Armor && !$newItem->equalsExact($oldItem)){
|
||||
$equipSound = $newItem->getMaterial()->getEquipSound();
|
||||
if($equipSound !== null){
|
||||
$this->broadcastSound($equipSound);
|
||||
}
|
||||
}
|
||||
};
|
||||
$this->armorInventory->getListeners()->add(new CallbackInventoryListener(
|
||||
function(Inventory $inventory, int $slot, Item $oldItem) use ($playArmorSound) : void{
|
||||
$playArmorSound($inventory->getItem($slot), $oldItem);
|
||||
},
|
||||
function(Inventory $inventory, array $oldContents) use ($playArmorSound) : void{
|
||||
foreach($oldContents as $slot => $oldItem){
|
||||
$playArmorSound($inventory->getItem($slot), $oldItem);
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
$health = $this->getMaxHealth();
|
||||
|
||||
|
106
src/event/player/PlayerResourcePackOfferEvent.php
Normal file
106
src/event/player/PlayerResourcePackOfferEvent.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\player;
|
||||
|
||||
use pocketmine\event\Event;
|
||||
use pocketmine\player\PlayerInfo;
|
||||
use pocketmine\resourcepacks\ResourcePack;
|
||||
use function array_unshift;
|
||||
|
||||
/**
|
||||
* Called after a player authenticates and is being offered resource packs to download.
|
||||
*
|
||||
* This event should be used to decide which resource packs to offer the player and whether to require the player to
|
||||
* download the packs before they can join the server.
|
||||
*/
|
||||
class PlayerResourcePackOfferEvent extends Event{
|
||||
/**
|
||||
* @param ResourcePack[] $resourcePacks
|
||||
* @param string[] $encryptionKeys pack UUID => key, leave unset for any packs that are not encrypted
|
||||
*
|
||||
* @phpstan-param list<ResourcePack> $resourcePacks
|
||||
* @phpstan-param array<string, string> $encryptionKeys
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly PlayerInfo $playerInfo,
|
||||
private array $resourcePacks,
|
||||
private array $encryptionKeys,
|
||||
private bool $mustAccept
|
||||
){}
|
||||
|
||||
public function getPlayerInfo() : PlayerInfo{
|
||||
return $this->playerInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a resource pack to the top of the stack.
|
||||
* The resources in this pack will be applied over the top of any existing packs.
|
||||
*/
|
||||
public function addResourcePack(ResourcePack $entry, ?string $encryptionKey = null) : void{
|
||||
array_unshift($this->resourcePacks, $entry);
|
||||
if($encryptionKey !== null){
|
||||
$this->encryptionKeys[$entry->getPackId()] = $encryptionKey;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resource packs to offer. Packs are applied from the highest key to the lowest, with each pack
|
||||
* overwriting any resources from the previous pack. This means that the pack at index 0 gets the final say on which
|
||||
* resources are used.
|
||||
*
|
||||
* @param ResourcePack[] $resourcePacks
|
||||
* @param string[] $encryptionKeys pack UUID => key, leave unset for any packs that are not encrypted
|
||||
*
|
||||
* @phpstan-param list<ResourcePack> $resourcePacks
|
||||
* @phpstan-param array<string, string> $encryptionKeys
|
||||
*/
|
||||
public function setResourcePacks(array $resourcePacks, array $encryptionKeys) : void{
|
||||
$this->resourcePacks = $resourcePacks;
|
||||
$this->encryptionKeys = $encryptionKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResourcePack[]
|
||||
* @phpstan-return list<ResourcePack>
|
||||
*/
|
||||
public function getResourcePacks() : array{
|
||||
return $this->resourcePacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @phpstan-return array<string, string>
|
||||
*/
|
||||
public function getEncryptionKeys() : array{
|
||||
return $this->encryptionKeys;
|
||||
}
|
||||
|
||||
public function setMustAccept(bool $mustAccept) : void{
|
||||
$this->mustAccept = $mustAccept;
|
||||
}
|
||||
|
||||
public function mustAccept() : bool{
|
||||
return $this->mustAccept;
|
||||
}
|
||||
}
|
@ -23,10 +23,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\item;
|
||||
|
||||
use pocketmine\world\sound\Sound;
|
||||
|
||||
class ArmorMaterial{
|
||||
|
||||
public function __construct(
|
||||
private readonly int $enchantability
|
||||
private readonly int $enchantability,
|
||||
private readonly ?Sound $equipSound = null
|
||||
){
|
||||
}
|
||||
|
||||
@ -39,4 +42,11 @@ class ArmorMaterial{
|
||||
public function getEnchantability() : int{
|
||||
return $this->enchantability;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sound that plays when equipping the armor
|
||||
*/
|
||||
public function getEquipSound() : ?Sound{
|
||||
return $this->equipSound;
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,13 @@ declare(strict_types=1);
|
||||
namespace pocketmine\item;
|
||||
|
||||
use pocketmine\utils\RegistryTrait;
|
||||
use pocketmine\world\sound\ArmorEquipChainSound;
|
||||
use pocketmine\world\sound\ArmorEquipDiamondSound;
|
||||
use pocketmine\world\sound\ArmorEquipGenericSound;
|
||||
use pocketmine\world\sound\ArmorEquipGoldSound;
|
||||
use pocketmine\world\sound\ArmorEquipIronSound;
|
||||
use pocketmine\world\sound\ArmorEquipLeatherSound;
|
||||
use pocketmine\world\sound\ArmorEquipNetheriteSound;
|
||||
|
||||
/**
|
||||
* This doc-block is generated automatically, do not modify it manually.
|
||||
@ -62,12 +69,12 @@ final class VanillaArmorMaterials{
|
||||
}
|
||||
|
||||
protected static function setup() : void{
|
||||
self::register("leather", new ArmorMaterial(15));
|
||||
self::register("chainmail", new ArmorMaterial(12));
|
||||
self::register("iron", new ArmorMaterial(9));
|
||||
self::register("turtle", new ArmorMaterial(9));
|
||||
self::register("gold", new ArmorMaterial(25));
|
||||
self::register("diamond", new ArmorMaterial(10));
|
||||
self::register("netherite", new ArmorMaterial(15));
|
||||
self::register("leather", new ArmorMaterial(15, new ArmorEquipLeatherSound()));
|
||||
self::register("chainmail", new ArmorMaterial(12, new ArmorEquipChainSound()));
|
||||
self::register("iron", new ArmorMaterial(9, new ArmorEquipIronSound()));
|
||||
self::register("turtle", new ArmorMaterial(9, new ArmorEquipGenericSound()));
|
||||
self::register("gold", new ArmorMaterial(25, new ArmorEquipGoldSound()));
|
||||
self::register("diamond", new ArmorMaterial(10, new ArmorEquipDiamondSound()));
|
||||
self::register("netherite", new ArmorMaterial(15, new ArmorEquipNetheriteSound()));
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\network\mcpe;
|
||||
|
||||
use pocketmine\entity\effect\EffectInstance;
|
||||
use pocketmine\event\player\PlayerDuplicateLoginEvent;
|
||||
use pocketmine\event\player\PlayerResourcePackOfferEvent;
|
||||
use pocketmine\event\server\DataPacketDecodeEvent;
|
||||
use pocketmine\event\server\DataPacketReceiveEvent;
|
||||
use pocketmine\event\server\DataPacketSendEvent;
|
||||
@ -100,6 +101,8 @@ use pocketmine\player\Player;
|
||||
use pocketmine\player\PlayerInfo;
|
||||
use pocketmine\player\UsedChunkStatus;
|
||||
use pocketmine\player\XboxLivePlayerInfo;
|
||||
use pocketmine\promise\Promise;
|
||||
use pocketmine\promise\PromiseResolver;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
@ -158,15 +161,24 @@ class NetworkSession{
|
||||
|
||||
/** @var string[] */
|
||||
private array $sendBuffer = [];
|
||||
|
||||
/**
|
||||
* @var \SplQueue|CompressBatchPromise[]|string[]
|
||||
* @phpstan-var \SplQueue<CompressBatchPromise|string>
|
||||
* @var PromiseResolver[]
|
||||
* @phpstan-var list<PromiseResolver<true>>
|
||||
*/
|
||||
private array $sendBufferAckPromises = [];
|
||||
|
||||
/** @phpstan-var \SplQueue<array{CompressBatchPromise|string, list<PromiseResolver<true>>}> */
|
||||
private \SplQueue $compressedQueue;
|
||||
private bool $forceAsyncCompression = true;
|
||||
private bool $enableCompression = false; //disabled until handshake completed
|
||||
|
||||
private int $nextAckReceiptId = 0;
|
||||
/**
|
||||
* @var PromiseResolver[][]
|
||||
* @phpstan-var array<int, list<PromiseResolver<true>>>
|
||||
*/
|
||||
private array $ackPromisesByReceiptId = [];
|
||||
|
||||
private ?InventoryManager $invManager = null;
|
||||
|
||||
/**
|
||||
@ -464,7 +476,23 @@ class NetworkSession{
|
||||
}
|
||||
}
|
||||
|
||||
public function sendDataPacket(ClientboundPacket $packet, bool $immediate = false) : bool{
|
||||
public function handleAckReceipt(int $receiptId) : void{
|
||||
if(!$this->connected){
|
||||
return;
|
||||
}
|
||||
if(isset($this->ackPromisesByReceiptId[$receiptId])){
|
||||
$promises = $this->ackPromisesByReceiptId[$receiptId];
|
||||
unset($this->ackPromisesByReceiptId[$receiptId]);
|
||||
foreach($promises as $promise){
|
||||
$promise->resolve(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param PromiseResolver<true>|null $ackReceiptResolver
|
||||
*/
|
||||
private function sendDataPacketInternal(ClientboundPacket $packet, bool $immediate, ?PromiseResolver $ackReceiptResolver) : bool{
|
||||
if(!$this->connected){
|
||||
return false;
|
||||
}
|
||||
@ -487,6 +515,9 @@ class NetworkSession{
|
||||
$packets = [$packet];
|
||||
}
|
||||
|
||||
if($ackReceiptResolver !== null){
|
||||
$this->sendBufferAckPromises[] = $ackReceiptResolver;
|
||||
}
|
||||
foreach($packets as $evPacket){
|
||||
$this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder(), $evPacket));
|
||||
}
|
||||
@ -500,6 +531,23 @@ class NetworkSession{
|
||||
}
|
||||
}
|
||||
|
||||
public function sendDataPacket(ClientboundPacket $packet, bool $immediate = false) : bool{
|
||||
return $this->sendDataPacketInternal($packet, $immediate, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-return Promise<true>
|
||||
*/
|
||||
public function sendDataPacketWithReceipt(ClientboundPacket $packet, bool $immediate = false) : Promise{
|
||||
$resolver = new PromiseResolver();
|
||||
|
||||
if(!$this->sendDataPacketInternal($packet, $immediate, $resolver)){
|
||||
$resolver->reject();
|
||||
}
|
||||
|
||||
return $resolver->getPromise();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -541,7 +589,9 @@ class NetworkSession{
|
||||
$batch = $stream->getBuffer();
|
||||
}
|
||||
$this->sendBuffer = [];
|
||||
$this->queueCompressedNoBufferFlush($batch, $immediate);
|
||||
$ackPromises = $this->sendBufferAckPromises;
|
||||
$this->sendBufferAckPromises = [];
|
||||
$this->queueCompressedNoBufferFlush($batch, $immediate, $ackPromises);
|
||||
}finally{
|
||||
Timings::$playerNetworkSend->stopTiming();
|
||||
}
|
||||
@ -568,22 +618,27 @@ class NetworkSession{
|
||||
}
|
||||
}
|
||||
|
||||
private function queueCompressedNoBufferFlush(CompressBatchPromise|string $batch, bool $immediate = false) : void{
|
||||
/**
|
||||
* @param PromiseResolver[] $ackPromises
|
||||
*
|
||||
* @phpstan-param list<PromiseResolver<true>> $ackPromises
|
||||
*/
|
||||
private function queueCompressedNoBufferFlush(CompressBatchPromise|string $batch, bool $immediate = false, array $ackPromises = []) : void{
|
||||
Timings::$playerNetworkSend->startTiming();
|
||||
try{
|
||||
if(is_string($batch)){
|
||||
if($immediate){
|
||||
//Skips all queues
|
||||
$this->sendEncoded($batch, true);
|
||||
$this->sendEncoded($batch, true, $ackPromises);
|
||||
}else{
|
||||
$this->compressedQueue->enqueue($batch);
|
||||
$this->compressedQueue->enqueue([$batch, $ackPromises]);
|
||||
$this->flushCompressedQueue();
|
||||
}
|
||||
}elseif($immediate){
|
||||
//Skips all queues
|
||||
$this->sendEncoded($batch->getResult(), true);
|
||||
$this->sendEncoded($batch->getResult(), true, $ackPromises);
|
||||
}else{
|
||||
$this->compressedQueue->enqueue($batch);
|
||||
$this->compressedQueue->enqueue([$batch, $ackPromises]);
|
||||
$batch->onResolve(function() : void{
|
||||
if($this->connected){
|
||||
$this->flushCompressedQueue();
|
||||
@ -600,14 +655,14 @@ class NetworkSession{
|
||||
try{
|
||||
while(!$this->compressedQueue->isEmpty()){
|
||||
/** @var CompressBatchPromise|string $current */
|
||||
$current = $this->compressedQueue->bottom();
|
||||
[$current, $ackPromises] = $this->compressedQueue->bottom();
|
||||
if(is_string($current)){
|
||||
$this->compressedQueue->dequeue();
|
||||
$this->sendEncoded($current);
|
||||
$this->sendEncoded($current, false, $ackPromises);
|
||||
|
||||
}elseif($current->hasResult()){
|
||||
$this->compressedQueue->dequeue();
|
||||
$this->sendEncoded($current->getResult());
|
||||
$this->sendEncoded($current->getResult(), false, $ackPromises);
|
||||
|
||||
}else{
|
||||
//can't send any more queued until this one is ready
|
||||
@ -619,13 +674,24 @@ class NetworkSession{
|
||||
}
|
||||
}
|
||||
|
||||
private function sendEncoded(string $payload, bool $immediate = false) : void{
|
||||
/**
|
||||
* @param PromiseResolver[] $ackPromises
|
||||
* @phpstan-param list<PromiseResolver<true>> $ackPromises
|
||||
*/
|
||||
private function sendEncoded(string $payload, bool $immediate, array $ackPromises) : void{
|
||||
if($this->cipher !== null){
|
||||
Timings::$playerNetworkSendEncrypt->startTiming();
|
||||
$payload = $this->cipher->encrypt($payload);
|
||||
Timings::$playerNetworkSendEncrypt->stopTiming();
|
||||
}
|
||||
$this->sender->send($payload, $immediate);
|
||||
|
||||
if(count($ackPromises) > 0){
|
||||
$ackReceiptId = $this->nextAckReceiptId++;
|
||||
$this->ackPromisesByReceiptId[$ackReceiptId] = $ackPromises;
|
||||
}else{
|
||||
$ackReceiptId = null;
|
||||
}
|
||||
$this->sender->send($payload, $immediate, $ackReceiptId);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -645,6 +711,19 @@ class NetworkSession{
|
||||
$this->setHandler(null);
|
||||
$this->connected = false;
|
||||
|
||||
$ackPromisesByReceiptId = $this->ackPromisesByReceiptId;
|
||||
$this->ackPromisesByReceiptId = [];
|
||||
foreach($ackPromisesByReceiptId as $resolvers){
|
||||
foreach($resolvers as $resolver){
|
||||
$resolver->reject();
|
||||
}
|
||||
}
|
||||
$sendBufferAckPromises = $this->sendBufferAckPromises;
|
||||
$this->sendBufferAckPromises = [];
|
||||
foreach($sendBufferAckPromises as $resolver){
|
||||
$resolver->reject();
|
||||
}
|
||||
|
||||
$this->logger->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_network_session_close($reason)));
|
||||
}
|
||||
}
|
||||
@ -840,7 +919,19 @@ class NetworkSession{
|
||||
$this->sendDataPacket(PlayStatusPacket::create(PlayStatusPacket::LOGIN_SUCCESS));
|
||||
|
||||
$this->logger->debug("Initiating resource packs phase");
|
||||
$this->setHandler(new ResourcePacksPacketHandler($this, $this->server->getResourcePackManager(), function() : void{
|
||||
|
||||
$packManager = $this->server->getResourcePackManager();
|
||||
$resourcePacks = $packManager->getResourceStack();
|
||||
$keys = [];
|
||||
foreach($resourcePacks as $resourcePack){
|
||||
$key = $packManager->getPackEncryptionKey($resourcePack->getPackId());
|
||||
if($key !== null){
|
||||
$keys[$resourcePack->getPackId()] = $key;
|
||||
}
|
||||
}
|
||||
$event = new PlayerResourcePackOfferEvent($this->info, $resourcePacks, $keys, $packManager->resourcePacksRequired());
|
||||
$event->call();
|
||||
$this->setHandler(new ResourcePacksPacketHandler($this, $event->getResourcePacks(), $event->getEncryptionKeys(), $event->mustAccept(), function() : void{
|
||||
$this->createPlayer();
|
||||
}));
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ interface PacketSender{
|
||||
/**
|
||||
* Pushes a packet into the channel to be processed.
|
||||
*/
|
||||
public function send(string $payload, bool $immediate) : void;
|
||||
public function send(string $payload, bool $immediate, ?int $receiptId) : void;
|
||||
|
||||
/**
|
||||
* Closes the channel, terminating the connection.
|
||||
|
@ -38,7 +38,7 @@ final class ZlibCompressor implements Compressor{
|
||||
|
||||
public const DEFAULT_LEVEL = 7;
|
||||
public const DEFAULT_THRESHOLD = 256;
|
||||
public const DEFAULT_MAX_DECOMPRESSION_SIZE = 2 * 1024 * 1024;
|
||||
public const DEFAULT_MAX_DECOMPRESSION_SIZE = 8 * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* @see SingletonTrait::make()
|
||||
|
@ -37,12 +37,13 @@ use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackInfoEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackStackEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackType;
|
||||
use pocketmine\resourcepacks\ResourcePack;
|
||||
use pocketmine\resourcepacks\ResourcePackManager;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function ceil;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
@ -50,40 +51,72 @@ use function substr;
|
||||
* packs to the client.
|
||||
*/
|
||||
class ResourcePacksPacketHandler extends PacketHandler{
|
||||
private const PACK_CHUNK_SIZE = 128 * 1024; //128KB
|
||||
private const PACK_CHUNK_SIZE = 256 * 1024; //256KB
|
||||
|
||||
/**
|
||||
* Larger values allow downloading more chunks at the same time, increasing download speed, but the client may choke
|
||||
* and cause the download speed to drop (due to ACKs taking too long to arrive).
|
||||
*/
|
||||
private const MAX_CONCURRENT_CHUNK_REQUESTS = 1;
|
||||
|
||||
/**
|
||||
* @var ResourcePack[]
|
||||
* @phpstan-var array<string, ResourcePack>
|
||||
*/
|
||||
private array $resourcePacksById = [];
|
||||
|
||||
/** @var bool[][] uuid => [chunk index => hasSent] */
|
||||
private array $downloadedChunks = [];
|
||||
|
||||
/** @phpstan-var \SplQueue<array{ResourcePack, int}> */
|
||||
private \SplQueue $requestQueue;
|
||||
|
||||
private int $activeRequests = 0;
|
||||
|
||||
/**
|
||||
* @phpstan-param \Closure() : void $completionCallback
|
||||
* @param ResourcePack[] $resourcePackStack
|
||||
* @param string[] $encryptionKeys pack UUID => key, leave unset for any packs that are not encrypted
|
||||
*
|
||||
* @phpstan-param list<ResourcePack> $resourcePackStack
|
||||
* @phpstan-param array<string, string> $encryptionKeys
|
||||
* @phpstan-param \Closure() : void $completionCallback
|
||||
*/
|
||||
public function __construct(
|
||||
private NetworkSession $session,
|
||||
private ResourcePackManager $resourcePackManager,
|
||||
private array $resourcePackStack,
|
||||
private array $encryptionKeys,
|
||||
private bool $mustAccept,
|
||||
private \Closure $completionCallback
|
||||
){}
|
||||
){
|
||||
$this->requestQueue = new \SplQueue();
|
||||
foreach($resourcePackStack as $pack){
|
||||
$this->resourcePacksById[$pack->getPackId()] = $pack;
|
||||
}
|
||||
}
|
||||
|
||||
private function getPackById(string $id) : ?ResourcePack{
|
||||
return $this->resourcePacksById[strtolower($id)] ?? null;
|
||||
}
|
||||
|
||||
public function setUp() : void{
|
||||
$resourcePackEntries = array_map(function(ResourcePack $pack) : ResourcePackInfoEntry{
|
||||
//TODO: more stuff
|
||||
$encryptionKey = $this->resourcePackManager->getPackEncryptionKey($pack->getPackId());
|
||||
|
||||
return new ResourcePackInfoEntry(
|
||||
$pack->getPackId(),
|
||||
$pack->getPackVersion(),
|
||||
$pack->getPackSize(),
|
||||
$encryptionKey ?? "",
|
||||
$this->encryptionKeys[$pack->getPackId()] ?? "",
|
||||
"",
|
||||
$pack->getPackId(),
|
||||
false
|
||||
);
|
||||
}, $this->resourcePackManager->getResourceStack());
|
||||
}, $this->resourcePackStack);
|
||||
//TODO: support forcing server packs
|
||||
$this->session->sendDataPacket(ResourcePacksInfoPacket::create(
|
||||
resourcePackEntries: $resourcePackEntries,
|
||||
behaviorPackEntries: [],
|
||||
mustAccept: $this->resourcePackManager->resourcePacksRequired(),
|
||||
mustAccept: $this->mustAccept,
|
||||
hasAddons: false,
|
||||
hasScripts: false,
|
||||
forceServerPacks: false,
|
||||
@ -112,11 +145,11 @@ class ResourcePacksPacketHandler extends PacketHandler{
|
||||
if($splitPos !== false){
|
||||
$uuid = substr($uuid, 0, $splitPos);
|
||||
}
|
||||
$pack = $this->resourcePackManager->getPackById($uuid);
|
||||
$pack = $this->getPackById($uuid);
|
||||
|
||||
if(!($pack instanceof ResourcePack)){
|
||||
//Client requested a resource pack but we don't have it available on the server
|
||||
$this->disconnectWithError("Unknown pack $uuid requested, available packs: " . implode(", ", $this->resourcePackManager->getPackIdList()));
|
||||
$this->disconnectWithError("Unknown pack $uuid requested, available packs: " . implode(", ", array_keys($this->resourcePacksById)));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -136,7 +169,7 @@ class ResourcePacksPacketHandler extends PacketHandler{
|
||||
case ResourcePackClientResponsePacket::STATUS_HAVE_ALL_PACKS:
|
||||
$stack = array_map(static function(ResourcePack $pack) : ResourcePackStackEntry{
|
||||
return new ResourcePackStackEntry($pack->getPackId(), $pack->getPackVersion(), ""); //TODO: subpacks
|
||||
}, $this->resourcePackManager->getResourceStack());
|
||||
}, $this->resourcePackStack);
|
||||
|
||||
//we support chemistry blocks by default, the client should already have this installed
|
||||
$stack[] = new ResourcePackStackEntry("0fba4063-dba1-4281-9b89-ff9390653530", "1.0.0", "");
|
||||
@ -159,9 +192,9 @@ class ResourcePacksPacketHandler extends PacketHandler{
|
||||
}
|
||||
|
||||
public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $packet) : bool{
|
||||
$pack = $this->resourcePackManager->getPackById($packet->packId);
|
||||
$pack = $this->getPackById($packet->packId);
|
||||
if(!($pack instanceof ResourcePack)){
|
||||
$this->disconnectWithError("Invalid request for chunk $packet->chunkIndex of unknown pack $packet->packId, available packs: " . implode(", ", $this->resourcePackManager->getPackIdList()));
|
||||
$this->disconnectWithError("Invalid request for chunk $packet->chunkIndex of unknown pack $packet->packId, available packs: " . implode(", ", array_keys($this->resourcePacksById)));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -184,8 +217,37 @@ class ResourcePacksPacketHandler extends PacketHandler{
|
||||
$this->downloadedChunks[$packId][$packet->chunkIndex] = true;
|
||||
}
|
||||
|
||||
$this->session->sendDataPacket(ResourcePackChunkDataPacket::create($packId, $packet->chunkIndex, $offset, $pack->getPackChunk($offset, self::PACK_CHUNK_SIZE)));
|
||||
$this->requestQueue->enqueue([$pack, $packet->chunkIndex]);
|
||||
$this->processChunkRequestQueue();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function processChunkRequestQueue() : void{
|
||||
if($this->activeRequests >= self::MAX_CONCURRENT_CHUNK_REQUESTS || $this->requestQueue->isEmpty()){
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @var ResourcePack $pack
|
||||
* @var int $chunkIndex
|
||||
*/
|
||||
[$pack, $chunkIndex] = $this->requestQueue->dequeue();
|
||||
|
||||
$packId = $pack->getPackId();
|
||||
$offset = $chunkIndex * self::PACK_CHUNK_SIZE;
|
||||
$chunkData = $pack->getPackChunk($offset, self::PACK_CHUNK_SIZE);
|
||||
$this->activeRequests++;
|
||||
$this->session
|
||||
->sendDataPacketWithReceipt(ResourcePackChunkDataPacket::create($packId, $chunkIndex, $offset, $chunkData))
|
||||
->onCompletion(
|
||||
function() : void{
|
||||
$this->activeRequests--;
|
||||
$this->processChunkRequestQueue();
|
||||
},
|
||||
function() : void{
|
||||
//this may have been rejected because of a disconnection - this will do nothing in that case
|
||||
$this->disconnectWithError("Plugin interrupted sending of resource packs");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -252,7 +252,9 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
|
||||
}
|
||||
|
||||
public function onPacketAck(int $sessionId, int $identifierACK) : void{
|
||||
|
||||
if(isset($this->sessions[$sessionId])){
|
||||
$this->sessions[$sessionId]->handleAckReceipt($identifierACK);
|
||||
}
|
||||
}
|
||||
|
||||
public function setName(string $name) : void{
|
||||
@ -289,12 +291,13 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
|
||||
$this->network->getBandwidthTracker()->add($bytesSentDiff, $bytesReceivedDiff);
|
||||
}
|
||||
|
||||
public function putPacket(int $sessionId, string $payload, bool $immediate = true) : void{
|
||||
public function putPacket(int $sessionId, string $payload, bool $immediate = true, ?int $receiptId = null) : void{
|
||||
if(isset($this->sessions[$sessionId])){
|
||||
$pk = new EncapsulatedPacket();
|
||||
$pk->buffer = self::MCPE_RAKNET_PACKET_ID . $payload;
|
||||
$pk->reliability = PacketReliability::RELIABLE_ORDERED;
|
||||
$pk->orderChannel = 0;
|
||||
$pk->identifierACK = $receiptId;
|
||||
|
||||
$this->interface->sendEncapsulated($sessionId, $pk, $immediate);
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ class RakLibPacketSender implements PacketSender{
|
||||
private RakLibInterface $handler
|
||||
){}
|
||||
|
||||
public function send(string $payload, bool $immediate) : void{
|
||||
public function send(string $payload, bool $immediate, ?int $receiptId) : void{
|
||||
if(!$this->closed){
|
||||
$this->handler->putPacket($this->sessionId, $payload, $immediate);
|
||||
$this->handler->putPacket($this->sessionId, $payload, $immediate, $receiptId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,8 @@ class RakLibServer extends Thread{
|
||||
new SimpleProtocolAcceptor($this->protocolVersion),
|
||||
new UserToRakLibThreadMessageReceiver(new PthreadsChannelReader($this->mainToThreadBuffer)),
|
||||
new RakLibToUserThreadMessageSender(new SnoozeAwarePthreadsChannelWriter($this->threadToMainBuffer, $this->sleeperEntry->createNotifier())),
|
||||
new ExceptionTraceCleaner($this->mainPath)
|
||||
new ExceptionTraceCleaner($this->mainPath),
|
||||
recvMaxSplitParts: 512
|
||||
);
|
||||
$this->synchronized(function() : void{
|
||||
$this->ready = true;
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\thread;
|
||||
|
||||
use pmmp\thread\Thread as NativeThread;
|
||||
use pmmp\thread\ThreadSafeArray;
|
||||
use pocketmine\crash\CrashDump;
|
||||
use pocketmine\errorhandler\ErrorToExceptionHandler;
|
||||
use pocketmine\Server;
|
||||
use function error_get_last;
|
||||
@ -150,7 +151,7 @@ trait CommonThreadPartsTrait{
|
||||
$this->synchronized(function() : void{
|
||||
if($this->isTerminated() && $this->crashInfo === null){
|
||||
$last = error_get_last();
|
||||
if($last !== null){
|
||||
if($last !== null && ($last["type"] & CrashDump::FATAL_ERROR_MASK) !== 0){
|
||||
//fatal error
|
||||
$crashInfo = ThreadCrashInfo::fromLastErrorInfo($last, $this->getThreadName());
|
||||
}else{
|
||||
|
@ -39,12 +39,12 @@ class MainLogger extends AttachableThreadSafeLogger implements \BufferedLogger{
|
||||
private bool $useFormattingCodes = false;
|
||||
private string $mainThreadName;
|
||||
private string $timezone;
|
||||
private MainLoggerThread $logWriterThread;
|
||||
private ?MainLoggerThread $logWriterThread = null;
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function __construct(string $logFile, bool $useFormattingCodes, string $mainThreadName, \DateTimeZone $timezone, bool $logDebug = false){
|
||||
public function __construct(?string $logFile, bool $useFormattingCodes, string $mainThreadName, \DateTimeZone $timezone, bool $logDebug = false, ?string $logArchiveDir = null){
|
||||
parent::__construct();
|
||||
$this->logDebug = $logDebug;
|
||||
|
||||
@ -52,8 +52,10 @@ class MainLogger extends AttachableThreadSafeLogger implements \BufferedLogger{
|
||||
$this->mainThreadName = $mainThreadName;
|
||||
$this->timezone = $timezone->getName();
|
||||
|
||||
$this->logWriterThread = new MainLoggerThread($logFile);
|
||||
$this->logWriterThread->start(NativeThread::INHERIT_NONE);
|
||||
if($logFile !== null){
|
||||
$this->logWriterThread = new MainLoggerThread($logFile, $logArchiveDir);
|
||||
$this->logWriterThread->start(NativeThread::INHERIT_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,10 +168,12 @@ class MainLogger extends AttachableThreadSafeLogger implements \BufferedLogger{
|
||||
}
|
||||
|
||||
public function shutdownLogWriterThread() : void{
|
||||
if(NativeThread::getCurrentThreadId() === $this->logWriterThread->getCreatorId()){
|
||||
$this->logWriterThread->shutdown();
|
||||
}else{
|
||||
throw new \LogicException("Only the creator thread can shutdown the logger thread");
|
||||
if($this->logWriterThread !== null){
|
||||
if(NativeThread::getCurrentThreadId() === $this->logWriterThread->getCreatorId()){
|
||||
$this->logWriterThread->shutdown();
|
||||
}else{
|
||||
throw new \LogicException("Only the creator thread can shutdown the logger thread");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +197,9 @@ class MainLogger extends AttachableThreadSafeLogger implements \BufferedLogger{
|
||||
|
||||
$this->synchronized(function() use ($message, $level, $time) : void{
|
||||
Terminal::writeLine($message);
|
||||
$this->logWriterThread->write($time->format("Y-m-d") . " " . TextFormat::clean($message) . PHP_EOL);
|
||||
if($this->logWriterThread !== null){
|
||||
$this->logWriterThread->write($time->format("Y-m-d") . " " . TextFormat::clean($message) . PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ThreadSafeLoggerAttachment $attachment
|
||||
@ -205,11 +211,11 @@ class MainLogger extends AttachableThreadSafeLogger implements \BufferedLogger{
|
||||
}
|
||||
|
||||
public function syncFlushBuffer() : void{
|
||||
$this->logWriterThread->syncFlushBuffer();
|
||||
$this->logWriterThread?->syncFlushBuffer();
|
||||
}
|
||||
|
||||
public function __destruct(){
|
||||
if(!$this->logWriterThread->isJoined() && NativeThread::getCurrentThreadId() === $this->logWriterThread->getCreatorId()){
|
||||
if($this->logWriterThread !== null && !$this->logWriterThread->isJoined() && NativeThread::getCurrentThreadId() === $this->logWriterThread->getCreatorId()){
|
||||
$this->shutdownLogWriterThread();
|
||||
}
|
||||
}
|
||||
|
@ -25,23 +25,42 @@ namespace pocketmine\utils;
|
||||
|
||||
use pmmp\thread\Thread;
|
||||
use pmmp\thread\ThreadSafeArray;
|
||||
use function clearstatcache;
|
||||
use function date;
|
||||
use function fclose;
|
||||
use function file_exists;
|
||||
use function fopen;
|
||||
use function fstat;
|
||||
use function fwrite;
|
||||
use function is_dir;
|
||||
use function is_file;
|
||||
use function is_resource;
|
||||
use function mkdir;
|
||||
use function pathinfo;
|
||||
use function rename;
|
||||
use function strlen;
|
||||
use function touch;
|
||||
use const PATHINFO_EXTENSION;
|
||||
use const PATHINFO_FILENAME;
|
||||
|
||||
final class MainLoggerThread extends Thread{
|
||||
|
||||
/** @phpstan-var ThreadSafeArray<int, string> */
|
||||
private ThreadSafeArray $buffer;
|
||||
private bool $syncFlush = false;
|
||||
private bool $shutdown = false;
|
||||
|
||||
public function __construct(
|
||||
private string $logFile
|
||||
private string $logFile,
|
||||
private ?string $archiveDir,
|
||||
private readonly int $maxFileSize = 32 * 1024 * 1024 //32 MB
|
||||
){
|
||||
$this->buffer = new ThreadSafeArray();
|
||||
touch($this->logFile);
|
||||
if($this->archiveDir !== null && !@mkdir($this->archiveDir) && !is_dir($this->archiveDir)){
|
||||
throw new \RuntimeException("Unable to create archive directory: " . (
|
||||
is_file($this->archiveDir) ? "it already exists and is not a directory" : "permission denied"));
|
||||
}
|
||||
}
|
||||
|
||||
public function write(string $line) : void{
|
||||
@ -71,12 +90,64 @@ final class MainLoggerThread extends Thread{
|
||||
$this->join();
|
||||
}
|
||||
|
||||
/** @return resource */
|
||||
private function openLogFile(string $file, int &$size){
|
||||
$logResource = fopen($file, "ab");
|
||||
if(!is_resource($logResource)){
|
||||
throw new \RuntimeException("Couldn't open log file");
|
||||
}
|
||||
$stat = fstat($logResource);
|
||||
if($stat === false){
|
||||
throw new AssumptionFailedError("fstat() should not fail here");
|
||||
}
|
||||
$size = $stat['size'];
|
||||
return $logResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $logResource
|
||||
* @return resource
|
||||
*/
|
||||
private function archiveLogFile($logResource, int &$size, string $archiveDir){
|
||||
fclose($logResource);
|
||||
|
||||
clearstatcache();
|
||||
|
||||
$i = 0;
|
||||
$date = date("Y-m-d\TH.i.s");
|
||||
$baseName = pathinfo($this->logFile, PATHINFO_FILENAME);
|
||||
$extension = pathinfo($this->logFile, PATHINFO_EXTENSION);
|
||||
do{
|
||||
//this shouldn't be necessary, but in case the user messes with the system time for some reason ...
|
||||
$fileName = "$baseName.$date.$i.$extension";
|
||||
$out = $this->archiveDir . "/" . $fileName;
|
||||
$i++;
|
||||
}while(file_exists($out));
|
||||
|
||||
//the user may have externally deleted the whole directory - make sure it exists before we do anything
|
||||
@mkdir($archiveDir);
|
||||
rename($this->logFile, $out);
|
||||
|
||||
$logResource = $this->openLogFile($this->logFile, $size);
|
||||
fwrite($logResource, "--- Starting new log file - old log file archived as $fileName ---\n");
|
||||
|
||||
return $logResource;
|
||||
}
|
||||
|
||||
private function logFileReadyToArchive(int $size) : bool{
|
||||
return $size >= $this->maxFileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $logResource
|
||||
*/
|
||||
private function writeLogStream($logResource) : void{
|
||||
private function writeLogStream(&$logResource, int &$size, ?string $archiveDir) : void{
|
||||
while(($chunk = $this->buffer->shift()) !== null){
|
||||
fwrite($logResource, $chunk);
|
||||
$size += strlen($chunk);
|
||||
if($archiveDir !== null && $this->logFileReadyToArchive($size)){
|
||||
$logResource = $this->archiveLogFile($logResource, $size, $archiveDir);
|
||||
}
|
||||
}
|
||||
|
||||
$this->synchronized(function() : void{
|
||||
@ -88,13 +159,15 @@ final class MainLoggerThread extends Thread{
|
||||
}
|
||||
|
||||
public function run() : void{
|
||||
$logResource = fopen($this->logFile, "ab");
|
||||
if(!is_resource($logResource)){
|
||||
throw new \RuntimeException("Couldn't open log file");
|
||||
$size = 0;
|
||||
$logResource = $this->openLogFile($this->logFile, $size);
|
||||
$archiveDir = $this->archiveDir;
|
||||
if($archiveDir !== null && $this->logFileReadyToArchive($size)){
|
||||
$logResource = $this->archiveLogFile($logResource, $size, $archiveDir);
|
||||
}
|
||||
|
||||
while(!$this->shutdown){
|
||||
$this->writeLogStream($logResource);
|
||||
$this->writeLogStream($logResource, $size, $archiveDir);
|
||||
$this->synchronized(function() : void{
|
||||
if(!$this->shutdown && !$this->syncFlush){
|
||||
$this->wait();
|
||||
@ -102,7 +175,7 @@ final class MainLoggerThread extends Thread{
|
||||
});
|
||||
}
|
||||
|
||||
$this->writeLogStream($logResource);
|
||||
$this->writeLogStream($logResource, $size, $archiveDir);
|
||||
|
||||
fclose($logResource);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\utils;
|
||||
|
||||
use pocketmine\thread\ThreadManager;
|
||||
use pocketmine\thread\Thread;
|
||||
use function count;
|
||||
use function exec;
|
||||
use function fclose;
|
||||
@ -122,7 +122,7 @@ final class Process{
|
||||
|
||||
//TODO: more OS
|
||||
|
||||
return count(ThreadManager::getInstance()->getAll()) + 2; //MainLogger + Main Thread
|
||||
return Thread::getRunningCount() + 1; //pmmpthread doesn't count the main thread
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,6 +114,13 @@ trait RegistryTrait{
|
||||
if(count($arguments) > 0){
|
||||
throw new \ArgumentCountError("Expected exactly 0 arguments, " . count($arguments) . " passed");
|
||||
}
|
||||
|
||||
//fast path
|
||||
if(self::$members !== null && isset(self::$members[$name])){
|
||||
return self::preprocessMember(self::$members[$name]);
|
||||
}
|
||||
|
||||
//fallback
|
||||
try{
|
||||
return self::_registryFromString($name);
|
||||
}catch(\InvalidArgumentException $e){
|
||||
|
@ -31,7 +31,9 @@ use pocketmine\world\format\io\exception\CorruptedWorldException;
|
||||
use pocketmine\world\format\io\exception\UnsupportedWorldFormatException;
|
||||
use pocketmine\world\format\PalettedBlockArray;
|
||||
use pocketmine\world\WorldException;
|
||||
use function count;
|
||||
use function file_exists;
|
||||
use function implode;
|
||||
|
||||
abstract class BaseWorldProvider implements WorldProvider{
|
||||
protected WorldData $worldData;
|
||||
@ -62,27 +64,35 @@ abstract class BaseWorldProvider implements WorldProvider{
|
||||
*/
|
||||
abstract protected function loadLevelData() : WorldData;
|
||||
|
||||
private function translatePalette(PalettedBlockArray $blockArray) : PalettedBlockArray{
|
||||
private function translatePalette(PalettedBlockArray $blockArray, \Logger $logger) : PalettedBlockArray{
|
||||
$palette = $blockArray->getPalette();
|
||||
|
||||
$newPalette = [];
|
||||
$blockDecodeErrors = [];
|
||||
foreach($palette as $k => $legacyIdMeta){
|
||||
//TODO: remember data for unknown states so we can implement them later
|
||||
$id = $legacyIdMeta >> 4;
|
||||
$meta = $legacyIdMeta & 0xf;
|
||||
try{
|
||||
$newStateData = $this->blockDataUpgrader->upgradeIntIdMeta($legacyIdMeta >> 4, $legacyIdMeta & 0xf);
|
||||
$newStateData = $this->blockDataUpgrader->upgradeIntIdMeta($id, $meta);
|
||||
}catch(BlockStateDeserializeException $e){
|
||||
$blockDecodeErrors[] = "Palette offset $k / Failed to upgrade legacy ID/meta $id:$meta: " . $e->getMessage();
|
||||
$newStateData = GlobalBlockStateHandlers::getUnknownBlockStateData();
|
||||
}
|
||||
|
||||
try{
|
||||
$newPalette[$k] = $this->blockStateDeserializer->deserialize($newStateData);
|
||||
}catch(BlockStateDeserializeException){
|
||||
//TODO: this needs to be logged
|
||||
//TODO: maybe we can remember unknown states for later saving instead of discarding them and destroying maps...
|
||||
}catch(BlockStateDeserializeException $e){
|
||||
//this should never happen anyway - if the upgrader returned an invalid state, we have bigger problems
|
||||
$blockDecodeErrors[] = "Palette offset $k / Failed to deserialize upgraded state $id:$meta: " . $e->getMessage();
|
||||
$newPalette[$k] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData());
|
||||
}
|
||||
}
|
||||
|
||||
if(count($blockDecodeErrors) > 0){
|
||||
$logger->error("Errors decoding/upgrading blocks:\n - " . implode("\n - ", $blockDecodeErrors));
|
||||
}
|
||||
|
||||
//TODO: this is sub-optimal since it reallocates the offset table multiple times
|
||||
return PalettedBlockArray::fromData(
|
||||
$blockArray->getBitsPerBlock(),
|
||||
@ -91,16 +101,16 @@ abstract class BaseWorldProvider implements WorldProvider{
|
||||
);
|
||||
}
|
||||
|
||||
protected function palettizeLegacySubChunkXZY(string $idArray, string $metaArray) : PalettedBlockArray{
|
||||
return $this->translatePalette(SubChunkConverter::convertSubChunkXZY($idArray, $metaArray));
|
||||
protected function palettizeLegacySubChunkXZY(string $idArray, string $metaArray, \Logger $logger) : PalettedBlockArray{
|
||||
return $this->translatePalette(SubChunkConverter::convertSubChunkXZY($idArray, $metaArray), $logger);
|
||||
}
|
||||
|
||||
protected function palettizeLegacySubChunkYZX(string $idArray, string $metaArray) : PalettedBlockArray{
|
||||
return $this->translatePalette(SubChunkConverter::convertSubChunkYZX($idArray, $metaArray));
|
||||
protected function palettizeLegacySubChunkYZX(string $idArray, string $metaArray, \Logger $logger) : PalettedBlockArray{
|
||||
return $this->translatePalette(SubChunkConverter::convertSubChunkYZX($idArray, $metaArray), $logger);
|
||||
}
|
||||
|
||||
protected function palettizeLegacySubChunkFromColumn(string $idArray, string $metaArray, int $yOffset) : PalettedBlockArray{
|
||||
return $this->translatePalette(SubChunkConverter::convertSubChunkFromLegacyColumn($idArray, $metaArray, $yOffset));
|
||||
protected function palettizeLegacySubChunkFromColumn(string $idArray, string $metaArray, int $yOffset, \Logger $logger) : PalettedBlockArray{
|
||||
return $this->translatePalette(SubChunkConverter::convertSubChunkFromLegacyColumn($idArray, $metaArray, $yOffset), $logger);
|
||||
}
|
||||
|
||||
public function getPath() : string{
|
||||
|
@ -26,6 +26,7 @@ namespace pocketmine\world\format\io\leveldb;
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\data\bedrock\BiomeIds;
|
||||
use pocketmine\data\bedrock\block\BlockStateDeserializeException;
|
||||
use pocketmine\data\bedrock\block\convert\UnsupportedBlockStateException;
|
||||
use pocketmine\nbt\LittleEndianNbtSerializer;
|
||||
use pocketmine\nbt\NBT;
|
||||
use pocketmine\nbt\NbtDataException;
|
||||
@ -58,6 +59,7 @@ use function count;
|
||||
use function defined;
|
||||
use function extension_loaded;
|
||||
use function file_exists;
|
||||
use function implode;
|
||||
use function is_dir;
|
||||
use function mkdir;
|
||||
use function ord;
|
||||
@ -184,6 +186,8 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
$paletteSize = $stream->getLInt();
|
||||
}
|
||||
|
||||
$blockDecodeErrors = [];
|
||||
|
||||
for($i = 0; $i < $paletteSize; ++$i){
|
||||
try{
|
||||
$offset = $stream->getOffset();
|
||||
@ -199,18 +203,25 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
$blockStateData = $this->blockDataUpgrader->upgradeBlockStateNbt($blockStateNbt);
|
||||
}catch(BlockStateDeserializeException $e){
|
||||
//while not ideal, this is not a fatal error
|
||||
$logger->error("Failed to upgrade blockstate: " . $e->getMessage() . " offset $i in palette, blockstate NBT: " . $blockStateNbt->toString());
|
||||
$blockDecodeErrors[] = "Palette offset $i / Upgrade error: " . $e->getMessage() . ", NBT: " . $blockStateNbt->toString();
|
||||
$palette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData());
|
||||
continue;
|
||||
}
|
||||
try{
|
||||
$palette[] = $this->blockStateDeserializer->deserialize($blockStateData);
|
||||
}catch(UnsupportedBlockStateException $e){
|
||||
$blockDecodeErrors[] = "Palette offset $i / " . $e->getMessage();
|
||||
$palette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData());
|
||||
}catch(BlockStateDeserializeException $e){
|
||||
$logger->error("Failed to deserialize blockstate: " . $e->getMessage() . " offset $i in palette, blockstate NBT: " . $blockStateNbt->toString());
|
||||
$blockDecodeErrors[] = "Palette offset $i / Deserialize error: " . $e->getMessage() . ", NBT: " . $blockStateNbt->toString();
|
||||
$palette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData());
|
||||
}
|
||||
}
|
||||
|
||||
if(count($blockDecodeErrors) > 0){
|
||||
$logger->error("Errors decoding blocks:\n - " . implode("\n - ", $blockDecodeErrors));
|
||||
}
|
||||
|
||||
//TODO: exceptions
|
||||
return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette);
|
||||
}
|
||||
@ -443,7 +454,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
|
||||
$subChunks = [];
|
||||
for($yy = 0; $yy < 8; ++$yy){
|
||||
$storages = [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $yy)];
|
||||
$storages = [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $yy, new \PrefixedLogger($logger, "Subchunk y=$yy"))];
|
||||
if(isset($convertedLegacyExtraData[$yy])){
|
||||
$storages[] = $convertedLegacyExtraData[$yy];
|
||||
}
|
||||
@ -482,7 +493,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
}
|
||||
}
|
||||
|
||||
$storages = [$this->palettizeLegacySubChunkXZY($blocks, $blockData)];
|
||||
$storages = [$this->palettizeLegacySubChunkXZY($blocks, $blockData, $logger)];
|
||||
if($convertedLegacyExtraData !== null){
|
||||
$storages[] = $convertedLegacyExtraData;
|
||||
}
|
||||
|
@ -31,10 +31,11 @@ use pocketmine\world\format\SubChunk;
|
||||
class Anvil extends RegionWorldProvider{
|
||||
use LegacyAnvilChunkTrait;
|
||||
|
||||
protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d) : SubChunk{
|
||||
protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d, \Logger $logger) : SubChunk{
|
||||
return new SubChunk(Block::EMPTY_STATE_ID, [$this->palettizeLegacySubChunkYZX(
|
||||
self::readFixedSizeByteArray($subChunk, "Blocks", 4096),
|
||||
self::readFixedSizeByteArray($subChunk, "Data", 2048)
|
||||
self::readFixedSizeByteArray($subChunk, "Data", 2048),
|
||||
$logger
|
||||
)], $biomes3d);
|
||||
//ignore legacy light information
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ trait LegacyAnvilChunkTrait{
|
||||
/**
|
||||
* @throws CorruptedChunkException
|
||||
*/
|
||||
protected function deserializeChunk(string $data) : ?LoadedChunkData{
|
||||
protected function deserializeChunk(string $data, \Logger $logger) : ?LoadedChunkData{
|
||||
$decompressed = @zlib_decode($data);
|
||||
if($decompressed === false){
|
||||
throw new CorruptedChunkException("Failed to decompress chunk NBT");
|
||||
@ -90,7 +90,8 @@ trait LegacyAnvilChunkTrait{
|
||||
$subChunksTag = $chunk->getListTag("Sections") ?? [];
|
||||
foreach($subChunksTag as $subChunk){
|
||||
if($subChunk instanceof CompoundTag){
|
||||
$subChunks[$subChunk->getByte("Y")] = $this->deserializeSubChunk($subChunk, clone $biomes3d);
|
||||
$y = $subChunk->getByte("Y");
|
||||
$subChunks[$y] = $this->deserializeSubChunk($subChunk, clone $biomes3d, new \PrefixedLogger($logger, "Subchunk y=$y"));
|
||||
}
|
||||
}
|
||||
for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){
|
||||
@ -111,6 +112,6 @@ trait LegacyAnvilChunkTrait{
|
||||
);
|
||||
}
|
||||
|
||||
abstract protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d) : SubChunk;
|
||||
abstract protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d, \Logger $logger) : SubChunk;
|
||||
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class McRegion extends RegionWorldProvider{
|
||||
/**
|
||||
* @throws CorruptedChunkException
|
||||
*/
|
||||
protected function deserializeChunk(string $data) : ?LoadedChunkData{
|
||||
protected function deserializeChunk(string $data, \Logger $logger) : ?LoadedChunkData{
|
||||
$decompressed = @zlib_decode($data);
|
||||
if($decompressed === false){
|
||||
throw new CorruptedChunkException("Failed to decompress chunk NBT");
|
||||
@ -90,7 +90,12 @@ class McRegion extends RegionWorldProvider{
|
||||
$fullData = self::readFixedSizeByteArray($chunk, "Data", 16384);
|
||||
|
||||
for($y = 0; $y < 8; ++$y){
|
||||
$subChunks[$y] = new SubChunk(Block::EMPTY_STATE_ID, [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $y)], clone $biomes3d);
|
||||
$subChunks[$y] = new SubChunk(Block::EMPTY_STATE_ID, [$this->palettizeLegacySubChunkFromColumn(
|
||||
$fullIds,
|
||||
$fullData,
|
||||
$y,
|
||||
new \PrefixedLogger($logger, "Subchunk y=$y"),
|
||||
)], clone $biomes3d);
|
||||
}
|
||||
for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){
|
||||
if(!isset($subChunks[$y])){
|
||||
|
@ -35,10 +35,11 @@ use pocketmine\world\format\SubChunk;
|
||||
class PMAnvil extends RegionWorldProvider{
|
||||
use LegacyAnvilChunkTrait;
|
||||
|
||||
protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d) : SubChunk{
|
||||
protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d, \Logger $logger) : SubChunk{
|
||||
return new SubChunk(Block::EMPTY_STATE_ID, [$this->palettizeLegacySubChunkXZY(
|
||||
self::readFixedSizeByteArray($subChunk, "Blocks", 4096),
|
||||
self::readFixedSizeByteArray($subChunk, "Data", 2048)
|
||||
self::readFixedSizeByteArray($subChunk, "Data", 2048),
|
||||
$logger
|
||||
)], $biomes3d);
|
||||
}
|
||||
|
||||
|
@ -147,7 +147,7 @@ abstract class RegionWorldProvider extends BaseWorldProvider{
|
||||
/**
|
||||
* @throws CorruptedChunkException
|
||||
*/
|
||||
abstract protected function deserializeChunk(string $data) : ?LoadedChunkData;
|
||||
abstract protected function deserializeChunk(string $data, \Logger $logger) : ?LoadedChunkData;
|
||||
|
||||
/**
|
||||
* @return CompoundTag[]
|
||||
@ -200,7 +200,7 @@ abstract class RegionWorldProvider extends BaseWorldProvider{
|
||||
|
||||
$chunkData = $this->loadRegion($regionX, $regionZ)->readChunk($chunkX & 0x1f, $chunkZ & 0x1f);
|
||||
if($chunkData !== null){
|
||||
return $this->deserializeChunk($chunkData);
|
||||
return $this->deserializeChunk($chunkData, new \PrefixedLogger($this->logger, "Loading chunk x=$chunkX z=$chunkZ"));
|
||||
}
|
||||
|
||||
return null;
|
||||
|
35
src/world/sound/ArmorEquipChainSound.php
Normal file
35
src/world/sound/ArmorEquipChainSound.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class ArmorEquipChainSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::ARMOR_EQUIP_CHAIN, $pos, false)];
|
||||
}
|
||||
}
|
35
src/world/sound/ArmorEquipDiamondSound.php
Normal file
35
src/world/sound/ArmorEquipDiamondSound.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class ArmorEquipDiamondSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::ARMOR_EQUIP_DIAMOND, $pos, false)];
|
||||
}
|
||||
}
|
35
src/world/sound/ArmorEquipGenericSound.php
Normal file
35
src/world/sound/ArmorEquipGenericSound.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class ArmorEquipGenericSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::ARMOR_EQUIP_GENERIC, $pos, false)];
|
||||
}
|
||||
}
|
35
src/world/sound/ArmorEquipGoldSound.php
Normal file
35
src/world/sound/ArmorEquipGoldSound.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class ArmorEquipGoldSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::ARMOR_EQUIP_GOLD, $pos, false)];
|
||||
}
|
||||
}
|
35
src/world/sound/ArmorEquipIronSound.php
Normal file
35
src/world/sound/ArmorEquipIronSound.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class ArmorEquipIronSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::ARMOR_EQUIP_IRON, $pos, false)];
|
||||
}
|
||||
}
|
35
src/world/sound/ArmorEquipLeatherSound.php
Normal file
35
src/world/sound/ArmorEquipLeatherSound.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class ArmorEquipLeatherSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::ARMOR_EQUIP_LEATHER, $pos, false)];
|
||||
}
|
||||
}
|
35
src/world/sound/ArmorEquipNetheriteSound.php
Normal file
35
src/world/sound/ArmorEquipNetheriteSound.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class ArmorEquipNetheriteSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::ARMOR_EQUIP_NETHERITE, $pos, false)];
|
||||
}
|
||||
}
|
35
src/world/sound/SweetBerriesPickSound.php
Normal file
35
src/world/sound/SweetBerriesPickSound.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class SweetBerriesPickSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BLOCK_SWEET_BERRY_BUSH_PICK, $pos, false)];
|
||||
}
|
||||
}
|
@ -650,6 +650,11 @@ parameters:
|
||||
count: 2
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$playerInfo of class pocketmine\\\\event\\\\player\\\\PlayerResourcePackOfferEvent constructor expects pocketmine\\\\player\\\\PlayerInfo, pocketmine\\\\player\\\\PlayerInfo\\|null given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$target of method pocketmine\\\\command\\\\Command\\:\\:testPermissionSilent\\(\\) expects pocketmine\\\\command\\\\CommandSender, pocketmine\\\\player\\\\Player\\|null given\\.$#"
|
||||
count: 1
|
||||
|
@ -24,13 +24,16 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function asort;
|
||||
use function file_get_contents;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
use function json_decode;
|
||||
use function log;
|
||||
use function print_r;
|
||||
use const SORT_STRING;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
|
||||
class BlockTest extends TestCase{
|
||||
|
||||
@ -91,34 +94,71 @@ class BlockTest extends TestCase{
|
||||
}
|
||||
}
|
||||
|
||||
public function testConsistency() : void{
|
||||
$list = json_decode(file_get_contents(__DIR__ . '/block_factory_consistency_check.json'), true);
|
||||
if(!is_array($list)){
|
||||
throw new \pocketmine\utils\AssumptionFailedError("Old table should be array{knownStates: array<string, string>, stateDataBits: int}");
|
||||
/**
|
||||
* @return int[]
|
||||
* @phpstan-return array<string, int>
|
||||
*/
|
||||
public static function computeConsistencyCheckTable(RuntimeBlockStateRegistry $blockStateRegistry) : array{
|
||||
$newTable = [];
|
||||
|
||||
$idNameLookup = [];
|
||||
//if we ever split up block registration into multiple registries (e.g. separating chemistry blocks),
|
||||
//we'll need to ensure those additional registries are also included here
|
||||
foreach(Utils::stringifyKeys(VanillaBlocks::getAll()) as $name => $blockType){
|
||||
$id = $blockType->getTypeId();
|
||||
if(isset($idNameLookup[$id])){
|
||||
throw new AssumptionFailedError("TypeID $name collides with " . $idNameLookup[$id]);
|
||||
}
|
||||
$idNameLookup[$id] = $name;
|
||||
}
|
||||
$knownStates = [];
|
||||
/**
|
||||
* @var string $name
|
||||
* @var int[] $stateIds
|
||||
*/
|
||||
foreach($list["knownStates"] as $name => $stateIds){
|
||||
foreach($stateIds as $stateId){
|
||||
$knownStates[$stateId] = $name;
|
||||
|
||||
foreach($blockStateRegistry->getAllKnownStates() as $index => $block){
|
||||
if($index !== $block->getStateId()){
|
||||
throw new AssumptionFailedError("State index should always match state ID");
|
||||
}
|
||||
$idName = $idNameLookup[$block->getTypeId()];
|
||||
$newTable[$idName] = ($newTable[$idName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return $newTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param array<string, int> $actual
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function computeConsistencyCheckDiff(string $expectedFile, array $actual) : array{
|
||||
$expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 2, JSON_THROW_ON_ERROR);
|
||||
if(!is_array($expected)){
|
||||
throw new AssumptionFailedError("Old table should be array<string, int>");
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
foreach($expected as $typeName => $numStates){
|
||||
if(!is_string($typeName) || !is_int($numStates)){
|
||||
throw new AssumptionFailedError("Old table should be array<string, int>");
|
||||
}
|
||||
if(!isset($actual[$typeName])){
|
||||
$errors[] = "Removed block type $typeName ($numStates permutations)";
|
||||
}elseif($actual[$typeName] !== $numStates){
|
||||
$errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actual[$typeName];
|
||||
}
|
||||
}
|
||||
foreach(Utils::stringifyKeys($actual) as $typeName => $numStates){
|
||||
if(!isset($expected[$typeName])){
|
||||
$errors[] = "Added block type $typeName (" . $actual[$typeName] . " permutations)";
|
||||
}
|
||||
}
|
||||
$oldStateDataSize = $list["stateDataBits"];
|
||||
self::assertSame($oldStateDataSize, Block::INTERNAL_STATE_DATA_BITS, "Changed number of state data bits - consistency check probably need regenerating");
|
||||
|
||||
$states = $this->blockFactory->getAllKnownStates();
|
||||
foreach($states as $stateId => $state){
|
||||
self::assertArrayHasKey($stateId, $knownStates, "New block state $stateId (" . print_r($state, true) . ") - consistency check may need regenerating");
|
||||
self::assertSame($knownStates[$stateId], $state->getName());
|
||||
}
|
||||
asort($knownStates, SORT_STRING);
|
||||
foreach($knownStates as $k => $name){
|
||||
self::assertArrayHasKey($k, $states, "Missing previously-known block state $k " . ($k >> Block::INTERNAL_STATE_DATA_BITS) . ":" . ($k & Block::INTERNAL_STATE_DATA_MASK) . " ($name)");
|
||||
self::assertSame($name, $states[$k]->getName());
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function testConsistency() : void{
|
||||
$newTable = self::computeConsistencyCheckTable($this->blockFactory);
|
||||
$errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable);
|
||||
|
||||
self::assertEmpty($errors, "Block factory consistency check failed:\n" . implode("\n", $errors));
|
||||
}
|
||||
|
||||
public function testEmptyStateId() : void{
|
||||
|
File diff suppressed because one or more lines are too long
@ -21,86 +21,31 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockTest;
|
||||
use pocketmine\block\RuntimeBlockStateRegistry;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
require dirname(__DIR__, 3) . '/vendor/autoload.php';
|
||||
|
||||
/* This script needs to be re-run after any intentional blockfactory change (adding or removing a block state). */
|
||||
|
||||
$factory = new RuntimeBlockStateRegistry();
|
||||
$remaps = [];
|
||||
$new = [];
|
||||
foreach(RuntimeBlockStateRegistry::getInstance()->getAllKnownStates() as $index => $block){
|
||||
if($index !== $block->getStateId()){
|
||||
throw new AssumptionFailedError("State index should always match state ID");
|
||||
}
|
||||
$new[$index] = $block->getName();
|
||||
}
|
||||
$newTable = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance());
|
||||
|
||||
$oldTablePath = __DIR__ . '/block_factory_consistency_check.json';
|
||||
if(file_exists($oldTablePath)){
|
||||
$oldTable = json_decode(file_get_contents($oldTablePath), true);
|
||||
if(!is_array($oldTable)){
|
||||
throw new AssumptionFailedError("Old table should be array{knownStates: array<string, string>, stateDataBits: int}");
|
||||
}
|
||||
$old = [];
|
||||
/**
|
||||
* @var string $name
|
||||
* @var int[] $stateIds
|
||||
*/
|
||||
foreach($oldTable["knownStates"] as $name => $stateIds){
|
||||
foreach($stateIds as $stateId){
|
||||
$old[$stateId] = $name;
|
||||
}
|
||||
}
|
||||
$oldStateDataSize = $oldTable["stateDataBits"];
|
||||
$oldStateDataMask = ~(~0 << $oldStateDataSize);
|
||||
$errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable);
|
||||
|
||||
if($oldStateDataSize !== Block::INTERNAL_STATE_DATA_BITS){
|
||||
echo "State data bits changed from $oldStateDataSize to " . Block::INTERNAL_STATE_DATA_BITS . "\n";
|
||||
}
|
||||
|
||||
foreach($old as $k => $name){
|
||||
[$oldId, $oldStateData] = [$k >> $oldStateDataSize, $k & $oldStateDataMask];
|
||||
$reconstructedK = ($oldId << Block::INTERNAL_STATE_DATA_BITS) | $oldStateData;
|
||||
if(!isset($new[$reconstructedK])){
|
||||
echo "Removed state for $name ($oldId:$oldStateData)\n";
|
||||
}
|
||||
}
|
||||
foreach($new as $k => $name){
|
||||
[$newId, $newStateData] = [$k >> Block::INTERNAL_STATE_DATA_BITS, $k & Block::INTERNAL_STATE_DATA_MASK];
|
||||
if($newStateData > $oldStateDataMask){
|
||||
echo "Added state for $name ($newId, $newStateData)\n";
|
||||
}else{
|
||||
$reconstructedK = ($newId << $oldStateDataSize) | $newStateData;
|
||||
if(!isset($old[$reconstructedK])){
|
||||
echo "Added state for $name ($newId:$newStateData)\n";
|
||||
}elseif($old[$reconstructedK] !== $name){
|
||||
echo "Name changed ($newId:$newStateData) " . $old[$reconstructedK] . " -> " . $name . "\n";
|
||||
}
|
||||
if(count($errors) > 0){
|
||||
echo count($errors) . " changes detected:\n";
|
||||
foreach($errors as $error){
|
||||
echo $error . "\n";
|
||||
}
|
||||
}else{
|
||||
echo "No changes detected\n";
|
||||
}
|
||||
}else{
|
||||
echo "WARNING: Unable to calculate diff, no previous consistency check file found\n";
|
||||
}
|
||||
|
||||
$newTable = [];
|
||||
foreach($new as $stateId => $name){
|
||||
$newTable[$name][] = $stateId;
|
||||
}
|
||||
ksort($newTable, SORT_STRING);
|
||||
foreach(Utils::stringifyKeys($newTable) as $name => $stateIds){
|
||||
sort($stateIds, SORT_NUMERIC);
|
||||
$newTable[$name] = $stateIds;
|
||||
}
|
||||
|
||||
file_put_contents(__DIR__ . '/block_factory_consistency_check.json', json_encode(
|
||||
[
|
||||
"knownStates" => $newTable,
|
||||
"stateDataBits" => Block::INTERNAL_STATE_DATA_BITS
|
||||
],
|
||||
JSON_THROW_ON_ERROR
|
||||
));
|
||||
file_put_contents($oldTablePath, json_encode($newTable, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
|
||||
|
@ -47,11 +47,11 @@ class BlockStateUpgraderTest extends TestCase{
|
||||
}
|
||||
|
||||
private function getNewSchema() : BlockStateUpgradeSchema{
|
||||
return $this->getNewSchemaVersion(PHP_INT_MAX);
|
||||
return $this->getNewSchemaVersion(PHP_INT_MAX, 0);
|
||||
}
|
||||
|
||||
private function getNewSchemaVersion(int $versionId) : BlockStateUpgradeSchema{
|
||||
$schema = new BlockStateUpgradeSchema(($versionId >> 24) & 0xff, ($versionId >> 16) & 0xff, ($versionId >> 8) & 0xff, $versionId & 0xff, 0);
|
||||
private function getNewSchemaVersion(int $versionId, int $schemaId) : BlockStateUpgradeSchema{
|
||||
$schema = new BlockStateUpgradeSchema(($versionId >> 24) & 0xff, ($versionId >> 16) & 0xff, ($versionId >> 8) & 0xff, $versionId & 0xff, $schemaId);
|
||||
$this->upgrader->addSchema($schema);
|
||||
return $schema;
|
||||
}
|
||||
@ -211,20 +211,23 @@ class BlockStateUpgraderTest extends TestCase{
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-return \Generator<int, array{int, int, bool}, void, void>
|
||||
* @phpstan-return \Generator<int, array{int, int, bool, int}, void, void>
|
||||
*/
|
||||
public static function upgraderVersionCompatibilityProvider() : \Generator{
|
||||
yield [0x1_00_00_00, 0x1_00_00_00, true]; //Same version: must be altered - this may be a backwards-compatible change that Mojang didn't bother to bump for
|
||||
yield [0x1_00_01_00, 0x1_00_00_00, true]; //Schema newer than block: must be altered
|
||||
yield [0x1_00_00_00, 0x1_00_01_00, false]; //Block newer than schema: block must NOT be altered
|
||||
yield [0x1_00_00_00, 0x1_00_00_00, true, 2]; //Same version, multiple schemas targeting version - must be altered, we don't know which schemas are applicable
|
||||
yield [0x1_00_00_00, 0x1_00_00_00, false, 1]; //Same version, one schema targeting version - do not change
|
||||
yield [0x1_00_01_00, 0x1_00_00_00, true, 1]; //Schema newer than block: must be altered
|
||||
yield [0x1_00_00_00, 0x1_00_01_00, false, 1]; //Block newer than schema: block must NOT be altered
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider upgraderVersionCompatibilityProvider
|
||||
*/
|
||||
public function testUpgraderVersionCompatibility(int $schemaVersion, int $stateVersion, bool $shouldChange) : void{
|
||||
$schema = $this->getNewSchemaVersion($schemaVersion);
|
||||
$schema->renamedIds[self::TEST_BLOCK] = self::TEST_BLOCK_2;
|
||||
public function testUpgraderVersionCompatibility(int $schemaVersion, int $stateVersion, bool $shouldChange, int $schemaCount) : void{
|
||||
for($i = 0; $i < $schemaCount; $i++){
|
||||
$schema = $this->getNewSchemaVersion($schemaVersion, $i);
|
||||
$schema->renamedIds[self::TEST_BLOCK] = self::TEST_BLOCK_2;
|
||||
}
|
||||
|
||||
$getStateData = fn() => new BlockStateData(
|
||||
self::TEST_BLOCK,
|
||||
|
@ -32,8 +32,6 @@ use pocketmine\utils\MainLogger;
|
||||
use function define;
|
||||
use function dirname;
|
||||
use function microtime;
|
||||
use function sys_get_temp_dir;
|
||||
use function tempnam;
|
||||
use function usleep;
|
||||
|
||||
class AsyncPoolTest extends TestCase{
|
||||
@ -45,13 +43,12 @@ class AsyncPoolTest extends TestCase{
|
||||
|
||||
public function setUp() : void{
|
||||
@define('pocketmine\\COMPOSER_AUTOLOADER_PATH', dirname(__DIR__, 3) . '/vendor/autoload.php');
|
||||
$this->mainLogger = new MainLogger(tempnam(sys_get_temp_dir(), "pmlog"), false, "Main", new \DateTimeZone('UTC'));
|
||||
$this->mainLogger = new MainLogger(null, false, "Main", new \DateTimeZone('UTC'));
|
||||
$this->pool = new AsyncPool(2, 1024, new ThreadSafeClassLoader(), $this->mainLogger, new SleeperHandler());
|
||||
}
|
||||
|
||||
public function tearDown() : void{
|
||||
$this->pool->shutdown();
|
||||
$this->mainLogger->shutdownLogWriterThread();
|
||||
}
|
||||
|
||||
public function testTaskLeak() : void{
|
||||
|
@ -38,18 +38,23 @@ use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_key_first;
|
||||
use function array_key_last;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_shift;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function explode;
|
||||
use function file_put_contents;
|
||||
use function fwrite;
|
||||
use function implode;
|
||||
use function json_encode;
|
||||
use function ksort;
|
||||
use function min;
|
||||
use function sort;
|
||||
use function strlen;
|
||||
use function strrev;
|
||||
use function substr;
|
||||
use function usort;
|
||||
use const JSON_PRETTY_PRINT;
|
||||
use const SORT_STRING;
|
||||
@ -275,6 +280,77 @@ function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgra
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $strings
|
||||
*/
|
||||
function findCommonPrefix(array $strings) : string{
|
||||
sort($strings, SORT_STRING);
|
||||
|
||||
$first = $strings[array_key_first($strings)];
|
||||
$last = $strings[array_key_last($strings)];
|
||||
|
||||
$maxLength = min(strlen($first), strlen($last));
|
||||
for($i = 0; $i < $maxLength; ++$i){
|
||||
if($first[$i] !== $last[$i]){
|
||||
return substr($first, 0, $i);
|
||||
}
|
||||
}
|
||||
|
||||
return substr($first, 0, $maxLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $strings
|
||||
*/
|
||||
function findCommonSuffix(array $strings) : string{
|
||||
$reversed = array_map(strrev(...), $strings);
|
||||
|
||||
return strrev(findCommonPrefix($reversed));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[][][] $candidateFlattenedValues
|
||||
* @phpstan-param array<string, array<string, array<string, string>>> $candidateFlattenedValues
|
||||
*
|
||||
* @return BlockStateUpgradeSchemaFlattenedName[][]
|
||||
* @phpstan-return array<string, array<string, BlockStateUpgradeSchemaFlattenedName>>
|
||||
*/
|
||||
function buildFlattenPropertyRules(array $candidateFlattenedValues) : array{
|
||||
$flattenPropertyRules = [];
|
||||
foreach(Utils::stringifyKeys($candidateFlattenedValues) as $propertyName => $filters){
|
||||
foreach(Utils::stringifyKeys($filters) as $filter => $valueToId){
|
||||
$ids = array_values($valueToId);
|
||||
|
||||
//TODO: this is a bit too enthusiastic. For example, when flattening the old "stone", it will see that
|
||||
//"granite", "andesite", "stone" etc all have "e" as a common suffix, which works, but looks a bit daft.
|
||||
//This also causes more remaps to be generated than necessary, since some of the values are already
|
||||
//contained in the new ID.
|
||||
$idPrefix = findCommonPrefix($ids);
|
||||
$idSuffix = findCommonSuffix($ids);
|
||||
if(strlen($idSuffix) < 2){
|
||||
$idSuffix = "";
|
||||
}
|
||||
|
||||
$valueMap = [];
|
||||
foreach(Utils::stringifyKeys($valueToId) as $value => $newId){
|
||||
$newValue = substr($newId, strlen($idPrefix), $idSuffix !== "" ? -strlen($idSuffix) : null);
|
||||
if($newValue !== $value){
|
||||
$valueMap[$value] = $newValue;
|
||||
}
|
||||
}
|
||||
|
||||
$flattenPropertyRules[$propertyName][$filter] = new BlockStateUpgradeSchemaFlattenedName(
|
||||
$idPrefix,
|
||||
$propertyName,
|
||||
$idSuffix,
|
||||
$valueMap
|
||||
);
|
||||
}
|
||||
}
|
||||
ksort($flattenPropertyRules, SORT_STRING);
|
||||
return $flattenPropertyRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to compress a list of remapped states by looking at which state properties were consistently unchanged.
|
||||
* This significantly reduces the output size during flattening when the flattened block has many permutations
|
||||
@ -327,9 +403,9 @@ function processRemappedStates(array $upgradeTable) : array{
|
||||
$unchangedStatesByNewName[$newName] = $unchangedStates;
|
||||
}
|
||||
|
||||
$flattenedProperties = [];
|
||||
$notFlattenedProperties = [];
|
||||
$notFlattenedPropertyValues = [];
|
||||
|
||||
$candidateFlattenedValues = [];
|
||||
foreach($upgradeTable as $pair){
|
||||
foreach(Utils::stringifyKeys($pair->old->getStates()) as $propertyName => $propertyValue){
|
||||
if(isset($notFlattenedProperties[$propertyName])){
|
||||
@ -344,37 +420,41 @@ function processRemappedStates(array $upgradeTable) : array{
|
||||
$notFlattenedProperties[$propertyName] = true;
|
||||
continue;
|
||||
}
|
||||
$parts = explode($rawValue, $pair->new->getName(), 2);
|
||||
if(count($parts) !== 2){
|
||||
//the new name does not contain the property value, but it may still be able to be flattened in other cases
|
||||
$notFlattenedPropertyValues[$propertyName][$rawValue] = $rawValue;
|
||||
continue;
|
||||
}
|
||||
[$prefix, $suffix] = $parts;
|
||||
|
||||
$filter = $pair->old->getStates();
|
||||
foreach($unchangedStatesByNewName[$pair->new->getName()] as $unchangedPropertyName){
|
||||
unset($filter[$unchangedPropertyName]);
|
||||
}
|
||||
unset($filter[$propertyName]);
|
||||
|
||||
$rawFilter = encodeOrderedProperties($filter);
|
||||
$flattenRule = new BlockStateUpgradeSchemaFlattenedName(
|
||||
prefix: $prefix,
|
||||
flattenedProperty: $propertyName,
|
||||
suffix: $suffix
|
||||
);
|
||||
if(!isset($flattenedProperties[$propertyName][$rawFilter])){
|
||||
$flattenedProperties[$propertyName][$rawFilter] = $flattenRule;
|
||||
}elseif(!$flattenRule->equals($flattenedProperties[$propertyName][$rawFilter])){
|
||||
$notFlattenedProperties[$propertyName] = true;
|
||||
if(isset($candidateFlattenedValues[$propertyName][$rawFilter])){
|
||||
$valuesToIds = $candidateFlattenedValues[$propertyName][$rawFilter];
|
||||
$existingNewId = $valuesToIds[$rawValue] ?? null;
|
||||
if($existingNewId !== null && $existingNewId !== $pair->new->getName()){
|
||||
//this old value is associated with multiple new IDs - bad candidate for flattening
|
||||
$notFlattenedProperties[$propertyName] = true;
|
||||
continue;
|
||||
}
|
||||
foreach(Utils::stringifyKeys($valuesToIds) as $otherRawValue => $otherNewId){
|
||||
if($otherRawValue === $rawValue){
|
||||
continue;
|
||||
}
|
||||
if($otherNewId === $pair->new->getName()){
|
||||
//this old value maps to the same new ID as another old value - bad candidate for flattening
|
||||
$notFlattenedProperties[$propertyName] = true;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
$candidateFlattenedValues[$propertyName][$rawFilter][$rawValue] = $pair->new->getName();
|
||||
}
|
||||
}
|
||||
foreach(Utils::stringifyKeys($notFlattenedProperties) as $propertyName => $_){
|
||||
unset($flattenedProperties[$propertyName]);
|
||||
unset($candidateFlattenedValues[$propertyName]);
|
||||
}
|
||||
|
||||
ksort($flattenedProperties, SORT_STRING);
|
||||
$flattenedProperties = buildFlattenPropertyRules($candidateFlattenedValues);
|
||||
$flattenProperty = array_key_first($flattenedProperties);
|
||||
|
||||
$list = [];
|
||||
@ -393,19 +473,15 @@ function processRemappedStates(array $upgradeTable) : array{
|
||||
}
|
||||
ksort($cleanedOldState);
|
||||
ksort($cleanedNewState);
|
||||
$flattened = false;
|
||||
if($flattenProperty !== null){
|
||||
$flattenedValue = $cleanedOldState[$flattenProperty] ?? null;
|
||||
if(!$flattenedValue instanceof StringTag){
|
||||
throw new AssumptionFailedError("This should always be a TAG_String");
|
||||
}
|
||||
if(!isset($notFlattenedPropertyValues[$flattenProperty][$flattenedValue->getValue()])){
|
||||
unset($cleanedOldState[$flattenProperty]);
|
||||
$flattened = true;
|
||||
throw new AssumptionFailedError("This should always be a TAG_String ($newName $flattenProperty)");
|
||||
}
|
||||
unset($cleanedOldState[$flattenProperty]);
|
||||
}
|
||||
$rawOldState = encodeOrderedProperties($cleanedOldState);
|
||||
$newNameRule = $flattenProperty !== null && $flattened ?
|
||||
$newNameRule = $flattenProperty !== null ?
|
||||
$flattenedProperties[$flattenProperty][$rawOldState] ?? throw new AssumptionFailedError("This should always be set") :
|
||||
$newName;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user