Compare commits

..

33 Commits

Author SHA1 Message Date
002feacf8e Release 4.0.0-BETA11 2021-11-06 01:16:58 +00:00
b8523f7a18 Player: fix the fix which just degraded performance
if a chunk was requested for generation, count++ and count(activeRequests)++, which means that we would only get to submit half as many generation requests as we're allowed to.
Calculate the limit at the start and remember it instead.
2021-11-06 01:00:35 +00:00
640e88009b Player: fixed a mistake in generation rate limit
we don't want to allow sending further chunks when we haven't generated near ones, because we won't be able to see them anyway, and we might end up not needing them.
This now fully matches the results of PM3.
2021-11-06 00:57:37 +00:00
6cd272c9e1 Update transient composer dependencies 2021-11-06 00:55:13 +00:00
566c57bcd3 we no longer need submodules for these jobs 2021-11-06 00:35:39 +00:00
3c754b079c Move resources/locale to Composer dependency
all remaining submodules are now non-essential to running a server.
They are also versioned and updates can be done automatically using 'composer update'.

Finally, we can also put an end to the issue of translations being rendered incorrectly or being missing due to outdated submodules.
2021-11-06 00:32:58 +00:00
dbf9a33160 ChunkSelector: Improve algorithm to send chunks in proper circles, instead of squares
this ensures that the edge of loaded terain is always the same distance
away in any direction. This also means that when flying parallel to X or
Z axes, you now have about 12% more chunks directly in front of you,
instead of to your left and right, which gives the impression that
chunks are loading faster (they aren't, they are just being ordered in a
more sensible way).
2021-11-06 00:15:04 +00:00
07b1cff306 Bonemeal is no longer consumed when cancelling plant growth events (#4551) 2021-11-05 16:15:55 +00:00
0989c77037 Player: check that horizontal distance travelled is > 0 before adding exhaustion, or triggering a new chunk order
closes #4548
2021-11-05 15:46:55 +00:00
5107d0df4e Player: cap maximum number of active generation requests
this fixes the horrible spotty chunk loading seen in https://twitter.com/dktapps/status/1456397007946461190?s=20.
In practice, this made chunks invisible on teleport for several tens of seconds after teleporting. Having a larger chunks-per-tick with large render distance compounded to worsen the problem.
It wasn't really noticeable on small render distances, but very obvious on large ones with fast chunk sending and slow generation.

This also fixes #4187 (at least to the extent that it works on PM3, anyway).
2021-11-05 15:09:42 +00:00
579ef63663 EntityDataHelper: accept FloatTag for vector3 as well as Double
MCPE uses Float for entity positions.
2021-11-04 20:46:34 +00:00
8abc952c74 simulate-chunk-selector: do not reallocate colours on every frame 2021-11-04 19:38:40 +00:00
4c3a5fdd73 Clean PHPStan baselines from 1.0.2 2021-11-04 19:28:52 +00:00
54f287feb6 Merge branch 'stable' 2021-11-04 19:27:41 +00:00
84f8b3eb2d Move CrashDump to pocketmine\crash namespace 2021-11-04 19:23:45 +00:00
15fca84f3b remove some PHPStan error patterns 2021-11-04 19:22:49 +00:00
c60144210f Regenerate PHPStan bugs baseline 2021-11-04 19:18:29 +00:00
8ac999cbd4 Use object models for crashdump generation 2021-11-04 16:55:04 +00:00
4f8501ff34 Added a tool to visualise behaviour of ChunkSelector
I actually intended to write a tool for debugging generation, but it turns out this, as an intermediary step, is also useful and a whole bunch of fun to play with.
2021-11-04 14:14:55 +00:00
2405e45b35 Player: mark as not using item when held item slot is changed
closes #4538
2021-11-03 21:26:20 +00:00
e0b07ff308 Human: do not add more XP if totalXp limit was already reached
this matches the vanilla behaviour. For some reason it doesn't consider levels (so you can have a level higher or lower than this without actually having that amount of XP), but this matches Java behaviour as of 1.10.

fixes #4543
2021-11-03 20:45:55 +00:00
729f831b8f PHPStan 1.0.2 2021-11-03 20:26:32 +00:00
0356716e8e PopulationTask: do not expose internal fields as public
this code dates back to pthreads v2, when visibility on Threaded object fields meant different things (wtf, krakjoe??)
2021-11-03 15:23:43 +00:00
5c81b04813 PopulationTask: use typed properties 2021-11-03 15:22:49 +00:00
1ebb206762 World: fixed yet another edge case in drainPopulationRequestQueue() leading to assertion failure
really had to go fucking nuclear on it :(
2021-11-03 14:58:58 +00:00
29e2d92098 Bump phpstan/phpstan from 1.0.0 to 1.0.1 (#4541)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Commits](https://github.com/phpstan/phpstan/compare/1.0.0...1.0.1)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-03 11:27:42 +00:00
ef82a2cd79 Fixed PlayerEmoteEvent::setEmoteId() being useless 2021-11-02 23:10:26 +00:00
87031627bf Do not call PlayerEmoteEvent if rate limit was reached 2021-11-02 23:09:43 +00:00
f066199971 Implement emote support (#4523) 2021-11-02 23:04:55 +00:00
a0e9eec652 4.0.0-BETA11 is next 2021-11-02 19:18:20 +00:00
fa6a432d58 Release 4.0.0-BETA10 2021-11-02 19:18:19 +00:00
102277c636 draft-release: fixed BedrockData JSON minification 2021-11-02 19:16:36 +00:00
f50f26d52e 4.0.0-BETA10 is next 2021-11-02 19:14:15 +00:00
32 changed files with 679 additions and 252 deletions

View File

@ -42,7 +42,7 @@ jobs:
sed -i "s/const BUILD_NUMBER = 0/const BUILD_NUMBER = ${BUILD_NUMBER}/" src/VersionInfo.php
- name: Minify BedrockData JSON files
run: php resources/vanilla/.minify_json.php
run: php vendor/pocketmine/bedrock-data/.minify_json.php
- name: Build PocketMine-MP.phar
run: php -dphar.readonly=0 build/server-phar.php --git ${{ github.sha }}

View File

@ -91,8 +91,6 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Restore PHP build cache
id: php-build-cache
@ -195,8 +193,6 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Restore PHP build cache
id: php-build-cache

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "resources/locale"]
path = resources/locale
url = https://github.com/pmmp/Language.git
[submodule "tests/plugins/DevTools"]
path = tests/plugins/DevTools
url = https://github.com/pmmp/DevTools.git

View File

@ -172,7 +172,7 @@ HEADER;
echo "Done generating KnownTranslationFactory.\n";
}
$lang = parse_ini_file(Path::join(\pocketmine\RESOURCE_PATH, "locale", "eng.ini"), false, INI_SCANNER_RAW);
$lang = parse_ini_file(Path::join(\pocketmine\LOCALE_DATA_PATH, "eng.ini"), false, INI_SCANNER_RAW);
if($lang === false){
fwrite(STDERR, "Missing language files!\n");
exit(1);

View File

@ -1601,3 +1601,36 @@ Released 2nd November 2021.
- Fixed arrows getting added to creative players' inventories when picked up.
- Fixed players re-requesting the same ungenerated chunks multiple times before they were sent.
- Fixed commands not working in some cases after using some control sequences on the console.
# 4.0.0-BETA10
Released 2nd November 2021.
## Fixes
- Fixed an issue with BedrockData JSON minification which broke the release build of 4.0.0-BETA9.
# 4.0.0-BETA11
Released 6th November 2021.
## General
- `resources/locale` submodule has been removed. Language files are now included via Composer dependency [`pocketmine/locale-data`](https://packagist.org/packages/pocketmine/locale-data).
- This means it's now possible to run a server from git sources without cloning submodules :)
- All remaining submodules (DevTools, build/php) are non-essential for building and running a server.
- Added a tool `tools/simulate-chunk-sending.php` to visualise the behaviour of `ChunkSelector`.
## Fixes
- Fixed server crash on saving when player XP has reached int32 max (XP is now capped, similar to Java Edition).
- Fixed another edge case in chunk generation that led to assertion failures.
- Fixed server crash when finding a list of `TAG_Float` for entity positions instead of `TAG_Double`.
- Fixed fast eating when picking up items.
- Fixed terrain being invisible for a long time when teleporting into ungenerated terrain.
- Fixed weird chunk loading when teleporting into ungenerated terrain (sometimes farther chunks would render before closer ones, leaving holes in the map temporarily).
- Fixed players re-requesting chunks when turning their heads or jumping.
- Fixed bonemeal sometimes being consumed even when cancelling `BlockGrowEvent` and `StructureGrowEvent`.
## API
### Event
- Added `PlayerEmoteEvent`.
### Gameplay
- Chunks are now sent in proper circles. This improves the experience when flying quickly parallel to X or Z axis, since now more chunks in front of the player will load sooner.
- Added support for emotes.

View File

@ -41,6 +41,7 @@
"pocketmine/classloader": "^0.2.0",
"pocketmine/color": "^0.2.0",
"pocketmine/errorhandler": "^0.3.0",
"pocketmine/locale-data": "^1.0.3",
"pocketmine/log": "^0.4.0",
"pocketmine/log-pthreads": "^0.4.0",
"pocketmine/math": "^0.4.0",
@ -52,7 +53,7 @@
"webmozart/path-util": "^2.3"
},
"require-dev": {
"phpstan/phpstan": "1.0.0",
"phpstan/phpstan": "1.0.2",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.2"

49
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3fa50836a0e8560fe59ba9e73cc50c44",
"content-hash": "e36db7fb94bd79034dcc599c3029a621",
"packages": [
{
"name": "adhocore/json-comment",
@ -531,6 +531,29 @@
},
"time": "2021-02-12T18:56:22+00:00"
},
{
"name": "pocketmine/locale-data",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Language.git",
"reference": "7342b4eb593036c739e7f0c0ed95299ada69ff19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/7342b4eb593036c739e7f0c0ed95299ada69ff19",
"reference": "7342b4eb593036c739e7f0c0ed95299ada69ff19",
"shasum": ""
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"description": "Language resources used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/Language/issues",
"source": "https://github.com/pmmp/Language/tree/1.0.3"
},
"time": "2021-11-06T00:27:03+00:00"
},
{
"name": "pocketmine/log",
"version": "0.4.0",
@ -1480,16 +1503,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.13.0",
"version": "v4.13.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "50953a2691a922aa1769461637869a0a2faa3f53"
"reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53",
"reference": "50953a2691a922aa1769461637869a0a2faa3f53",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/63a79e8daa781cac14e5195e63ed8ae231dd10fd",
"reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd",
"shasum": ""
},
"require": {
@ -1530,9 +1553,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.13.1"
},
"time": "2021-09-20T12:20:58+00:00"
"time": "2021-11-03T20:52:16+00:00"
},
{
"name": "phar-io/manifest",
@ -1874,16 +1897,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.0.0",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "0d13a99513182e521271d46bde8f28caa4f84d97"
"reference": "e9e2a501102ba0b126b2f63a7f0a3b151056fe91"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d13a99513182e521271d46bde8f28caa4f84d97",
"reference": "0d13a99513182e521271d46bde8f28caa4f84d97",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e9e2a501102ba0b126b2f63a7f0a3b151056fe91",
"reference": "e9e2a501102ba0b126b2f63a7f0a3b151056fe91",
"shasum": ""
},
"require": {
@ -1914,7 +1937,7 @@
"description": "PHPStan - PHP Static Analysis Tool",
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/1.0.0"
"source": "https://github.com/phpstan/phpstan/tree/1.0.2"
},
"funding": [
{
@ -1934,7 +1957,7 @@
"type": "tidelift"
}
],
"time": "2021-11-01T06:38:20+00:00"
"time": "2021-11-03T16:09:51+00:00"
},
{
"name": "phpstan/phpstan-phpunit",

Submodule resources/locale deleted from f9076e4a6e

View File

@ -36,4 +36,5 @@ define('pocketmine\_CORE_CONSTANTS_INCLUDED', true);
define('pocketmine\PATH', dirname(__DIR__) . '/');
define('pocketmine\RESOURCE_PATH', dirname(__DIR__) . '/resources/');
define('pocketmine\BEDROCK_DATA_PATH', dirname(__DIR__) . '/vendor/pocketmine/bedrock-data/');
define('pocketmine\LOCALE_DATA_PATH', dirname(__DIR__) . '/vendor/pocketmine/locale-data/');
define('pocketmine\COMPOSER_AUTOLOADER_PATH', dirname(__DIR__) . '/vendor/autoload.php');

View File

@ -34,6 +34,7 @@ use pocketmine\console\ConsoleCommandSender;
use pocketmine\console\ConsoleReaderThread;
use pocketmine\crafting\CraftingManager;
use pocketmine\crafting\CraftingManagerFromDataHelper;
use pocketmine\crash\CrashDump;
use pocketmine\data\java\GameModeIdMap;
use pocketmine\entity\EntityDataHelper;
use pocketmine\entity\Location;
@ -1504,8 +1505,8 @@ class Server{
}
@touch($stamp); //update file timestamp
$plugin = $dump->getData()["plugin"];
if(is_string($plugin)){
$plugin = $dump->getData()->plugin;
if($plugin !== ""){
$p = $this->pluginManager->getPlugin($plugin);
if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){
$this->logger->debug("Not sending crashdump due to caused by non-phar plugin");
@ -1513,7 +1514,7 @@ class Server{
}
}
if($dump->getData()["error"]["type"] === \ParseError::class){
if($dump->getData()->error["type"] === \ParseError::class){
$report = false;
}

View File

@ -29,7 +29,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "4.0.0-BETA9";
public const BASE_VERSION = "4.0.0-BETA11";
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_NUMBER = 0;
public const BUILD_CHANNEL = "beta";

View File

@ -80,10 +80,9 @@ abstract class Crops extends Flowable{
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
$item->pop();
}
$item->pop();
return true;
}

View File

@ -76,9 +76,7 @@ class Sapling extends Flowable{
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer){
$this->grow($player);
if($item instanceof Fertilizer && $this->grow($player)){
$item->pop();
return true;
@ -108,19 +106,20 @@ class Sapling extends Flowable{
}
}
private function grow(?Player $player) : void{
private function grow(?Player $player) : bool{
$random = new Random(mt_rand());
$tree = TreeFactory::get($random, $this->treeType);
$transaction = $tree?->getBlockTransaction($this->position->getWorld(), $this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ(), $random);
if($transaction === null){
return;
return false;
}
$ev = new StructureGrowEvent($this, $transaction, $player);
$ev->call();
if(!$ev->isCancelled()){
$transaction->apply();
return $transaction->apply();
}
return false;
}
public function getFuelTime() : int{

View File

@ -48,7 +48,8 @@ class Sugarcane extends Flowable{
return 0b1111;
}
private function grow() : void{
private function grow() : bool{
$grew = false;
for($y = 1; $y < 3; ++$y){
if(!$this->position->getWorld()->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
break;
@ -61,12 +62,14 @@ class Sugarcane extends Flowable{
break;
}
$this->position->getWorld()->setBlock($b->position, $ev->getNewState());
$grew = true;
}else{
break;
}
}
$this->age = 0;
$this->position->getWorld()->setBlock($this->position, $this);
return $grew;
}
public function getAge() : int{ return $this->age; }
@ -82,12 +85,10 @@ class Sugarcane extends Flowable{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer){
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
$this->grow();
if(!$this->getSide(Facing::DOWN)->isSameType($this) && $this->grow()){
$item->pop();
}
$item->pop();
return true;
}

View File

@ -99,9 +99,9 @@ class SweetBerryBush extends Flowable{
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
$item->pop();
}
$item->pop();
}elseif(($dropAmount = $this->getBerryDropAmount()) > 0){
$this->position->getWorld()->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES));
$this->position->getWorld()->dropItem($this->position, $this->asItem()->setCount($dropAmount));

View File

@ -21,16 +21,18 @@
declare(strict_types=1);
namespace pocketmine;
namespace pocketmine\crash;
use Composer\InstalledVersions;
use pocketmine\errorhandler\ErrorTypeToStringMap;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\plugin\PluginBase;
use pocketmine\plugin\PluginManager;
use pocketmine\Server;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use pocketmine\VersionInfo;
use Webmozart\PathUtil\Path;
use function base64_encode;
use function date;
@ -69,6 +71,7 @@ use const FILE_IGNORE_NEW_LINES;
use const JSON_UNESCAPED_SLASHES;
use const PHP_EOL;
use const PHP_OS;
use const PHP_VERSION;
use const SORT_STRING;
class CrashDump{
@ -91,13 +94,9 @@ class CrashDump{
private $fp;
/** @var float */
private $time;
/**
* @var mixed[]
* @phpstan-var array<string, mixed>
*/
private $data = [];
private CrashDumpData $data;
/** @var string */
private $encodedData = "";
private $encodedData;
/** @var string */
private $path;
@ -118,9 +117,10 @@ class CrashDump{
throw new \RuntimeException("Could not create Crash Dump");
}
$this->fp = $fp;
$this->data["format_version"] = self::FORMAT_VERSION;
$this->data["time"] = $this->time;
$this->data["uptime"] = $this->time - $this->server->getStartTime();
$this->data = new CrashDumpData();
$this->data->format_version = self::FORMAT_VERSION;
$this->data->time = $this->time;
$this->data->uptime = $this->time - $this->server->getStartTime();
$this->addLine($this->server->getName() . " Crash Dump " . date("D M j H:i:s T Y", (int) $this->time));
$this->addLine();
$this->baseCrash();
@ -142,11 +142,7 @@ class CrashDump{
return $this->encodedData;
}
/**
* @return mixed[]
* @phpstan-return array<string, mixed>
*/
public function getData() : array{
public function getData() : CrashDumpData{
return $this->data;
}
@ -173,22 +169,21 @@ class CrashDump{
$plugins = $this->pluginManager->getPlugins();
$this->addLine();
$this->addLine("Loaded plugins:");
$this->data["plugins"] = [];
ksort($plugins, SORT_STRING);
foreach($plugins as $p){
$d = $p->getDescription();
$this->data["plugins"][$d->getName()] = [
"name" => $d->getName(),
"version" => $d->getVersion(),
"authors" => $d->getAuthors(),
"api" => $d->getCompatibleApis(),
"enabled" => $p->isEnabled(),
"depends" => $d->getDepend(),
"softDepends" => $d->getSoftDepend(),
"main" => $d->getMain(),
"load" => mb_strtoupper($d->getOrder()->name()),
"website" => $d->getWebsite()
];
$this->data->plugins[$d->getName()] = new CrashDumpDataPluginEntry(
name: $d->getName(),
version: $d->getVersion(),
authors: $d->getAuthors(),
api: $d->getCompatibleApis(),
enabled: $p->isEnabled(),
depends: $d->getDepend(),
softDepends: $d->getSoftDepend(),
main: $d->getMain(),
load: mb_strtoupper($d->getOrder()->name()),
website: $d->getWebsite()
);
$this->addLine($d->getName() . " " . $d->getVersion() . " by " . implode(", ", $d->getAuthors()) . " for API(s) " . implode(", ", $d->getCompatibleApis()));
}
}
@ -198,32 +193,26 @@ class CrashDump{
global $argv;
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-settings", true)){
$this->data["parameters"] = (array) $argv;
$this->data->parameters = (array) $argv;
if(($serverDotProperties = @file_get_contents(Path::join($this->server->getDataPath(), "server.properties"))) !== false){
$this->data["server.properties"] = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $serverDotProperties);
}else{
$this->data["server.properties"] = $serverDotProperties;
$this->data->serverDotProperties = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $serverDotProperties) ?? throw new AssumptionFailedError("Pattern is valid");
}
if(($pocketmineDotYml = @file_get_contents(Path::join($this->server->getDataPath(), "pocketmine.yml"))) !== false){
$this->data["pocketmine.yml"] = $pocketmineDotYml;
}else{
$this->data["pocketmine.yml"] = "";
$this->data->pocketmineDotYml = $pocketmineDotYml;
}
}else{
$this->data["pocketmine.yml"] = "";
$this->data["server.properties"] = "";
$this->data["parameters"] = [];
}
$extensions = [];
foreach(get_loaded_extensions() as $ext){
$extensions[$ext] = phpversion($ext);
$version = phpversion($ext);
if($version === false) throw new AssumptionFailedError();
$extensions[$ext] = $version;
}
$this->data["extensions"] = $extensions;
$this->data->extensions = $extensions;
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-phpinfo", true)){
ob_start();
phpinfo();
$this->data["phpinfo"] = ob_get_contents();
$this->data->phpinfo = ob_get_contents(); // @phpstan-ignore-line
ob_end_clean();
}
}
@ -255,18 +244,18 @@ class CrashDump{
if(isset($lastError["trace"])){
$lastError["trace"] = Utils::printableTrace($lastError["trace"]);
}
$this->data["lastError"] = $lastError;
$this->data->lastError = $lastError;
}
$this->data["error"] = $error;
unset($this->data["error"]["fullFile"]);
unset($this->data["error"]["trace"]);
$this->data->error = $error;
unset($this->data->error["fullFile"]);
unset($this->data->error["trace"]);
$this->addLine("Error: " . $error["message"]);
$this->addLine("File: " . $error["file"]);
$this->addLine("Line: " . $error["line"]);
$this->addLine("Type: " . $error["type"]);
$this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_NONE;
$this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_NONE;
if(!$this->determinePluginFromFile($error["fullFile"], true)){ //fatal errors won't leave any stack trace
foreach($error["trace"] as $frame){
if(!isset($frame["file"])){
@ -280,21 +269,20 @@ class CrashDump{
$this->addLine();
$this->addLine("Code:");
$this->data["code"] = [];
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-code", true) and file_exists($error["fullFile"])){
$file = @file($error["fullFile"], FILE_IGNORE_NEW_LINES);
if($file !== false){
for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10 and isset($file[$l]); ++$l){
$this->addLine("[" . ($l + 1) . "] " . $file[$l]);
$this->data["code"][$l + 1] = $file[$l];
$this->data->code[$l + 1] = $file[$l];
}
}
}
$this->addLine();
$this->addLine("Backtrace:");
foreach(($this->data["trace"] = Utils::printableTrace($error["trace"])) as $line){
foreach(($this->data->trace = Utils::printableTrace($error["trace"])) as $line){
$this->addLine($line);
}
$this->addLine();
@ -306,10 +294,10 @@ class CrashDump{
$this->addLine();
if($crashFrame){
$this->addLine("THIS CRASH WAS CAUSED BY A PLUGIN");
$this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_DIRECT;
$this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_DIRECT;
}else{
$this->addLine("A PLUGIN WAS INVOLVED IN THIS CRASH");
$this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_INDIRECT;
$this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_INDIRECT;
}
if(file_exists($filePath)){
@ -319,7 +307,7 @@ class CrashDump{
foreach($this->server->getPluginManager()->getPlugins() as $plugin){
$filePath = Filesystem::cleanPath($file->getValue($plugin));
if(strpos($frameCleanPath, $filePath) === 0){
$this->data["plugin"] = $plugin->getName();
$this->data->plugin = $plugin->getName();
$this->addLine("BAD PLUGIN: " . $plugin->getDescription()->getFullName());
break;
}
@ -341,19 +329,20 @@ class CrashDump{
);
}
$this->data["general"] = [];
$this->data["general"]["name"] = $this->server->getName();
$this->data["general"]["base_version"] = VersionInfo::BASE_VERSION;
$this->data["general"]["build"] = VersionInfo::BUILD_NUMBER;
$this->data["general"]["is_dev"] = VersionInfo::IS_DEVELOPMENT_BUILD;
$this->data["general"]["protocol"] = ProtocolInfo::CURRENT_PROTOCOL;
$this->data["general"]["git"] = VersionInfo::GIT_HASH();
$this->data["general"]["uname"] = php_uname("a");
$this->data["general"]["php"] = phpversion();
$this->data["general"]["zend"] = zend_version();
$this->data["general"]["php_os"] = PHP_OS;
$this->data["general"]["os"] = Utils::getOS();
$this->data["general"]["composer_libraries"] = $composerLibraries;
$this->data->general = new CrashDumpDataGeneral(
name: $this->server->getName(),
base_version: VersionInfo::BASE_VERSION,
build: VersionInfo::BUILD_NUMBER,
is_dev: VersionInfo::IS_DEVELOPMENT_BUILD,
protocol: ProtocolInfo::CURRENT_PROTOCOL,
git: VersionInfo::GIT_HASH(),
uname: php_uname("a"),
php: PHP_VERSION,
zend: zend_version(),
php_os: PHP_OS,
os: Utils::getOS(),
composer_libraries: $composerLibraries,
);
$this->addLine($this->server->getName() . " version: " . $version->getFullVersion(true) . " [Protocol " . ProtocolInfo::CURRENT_PROTOCOL . "]");
$this->addLine("Git commit: " . VersionInfo::GIT_HASH());
$this->addLine("uname -a: " . php_uname("a"));

View File

@ -0,0 +1,84 @@
<?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\crash;
final class CrashDumpData implements \JsonSerializable{
public int $format_version;
public float $time;
public float $uptime;
/** @var mixed[] */
public array $lastError = [];
/** @var mixed[] */
public array $error;
public string $plugin_involvement;
public string $plugin = "";
/** @var string[] */
public array $code = [];
/** @var string[] */
public array $trace;
/**
* @var CrashDumpDataPluginEntry[]
* @phpstan-var array<string, CrashDumpDataPluginEntry>
*/
public array $plugins = [];
/** @var string[] */
public array $parameters = [];
public string $serverDotProperties = "";
public string $pocketmineDotYml = "";
/**
* @var string[]
* @phpstan-var array<string, string>
*/
public array $extensions = [];
public string $phpinfo = "";
public CrashDumpDataGeneral $general;
/**
* @return mixed[]
*/
public function jsonSerialize() : array{
$result = (array) $this;
unset($result["serverDotProperties"]);
unset($result["pocketmineDotYml"]);
$result["pocketmine.yml"] = $this->pocketmineDotYml;
$result["server.properties"] = $this->serverDotProperties;
return $result;
}
}

View File

@ -0,0 +1,46 @@
<?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\crash;
final class CrashDumpDataGeneral{
/**
* @param string[] $composer_libraries
* @phpstan-param array<string, string> $composer_libraries
*/
public function __construct(
public string $name,
public string $base_version,
public int $build,
public bool $is_dev,
public int $protocol,
public string $git,
public string $uname,
public string $php,
public string $zend,
public string $php_os,
public string $os,
public array $composer_libraries,
){}
}

View File

@ -0,0 +1,45 @@
<?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\crash;
final class CrashDumpDataPluginEntry{
/**
* @param string[] $authors
* @param string[] $api
* @param string[] $depends
* @param string[] $softDepends
*/
public function __construct(
public string $name,
public string $version,
public array $authors,
public array $api,
public bool $enabled,
public array $depends,
public array $softDepends,
public string $main,
public string $load,
public string $website,
){}
}

View File

@ -59,10 +59,10 @@ final class EntityDataHelper{
if($pos === null and $optional){
return new Vector3(0, 0, 0);
}
if(!($pos instanceof ListTag) or $pos->getTagType() !== NBT::TAG_Double){
throw new \UnexpectedValueException("'$tagName' should be a List<Double>");
if(!($pos instanceof ListTag) or ($pos->getTagType() !== NBT::TAG_Double && $pos->getTagType() !== NBT::TAG_Float)){
throw new \UnexpectedValueException("'$tagName' should be a List<Double> or List<Float>");
}
/** @var DoubleTag[] $values */
/** @var DoubleTag[]|FloatTag[] $values */
$values = $pos->getValue();
if(count($values) !== 3){
throw new \UnexpectedValueException("Expected exactly 3 entries in '$tagName' tag");

View File

@ -152,6 +152,7 @@ class ExperienceManager{
* @param bool $playSound Whether to play level-up and XP gained sounds.
*/
public function addXp(int $amount, bool $playSound = true) : bool{
$amount = min($amount, Limits::INT32_MAX - $this->totalXp);
$oldLevel = $this->getXpLevel();
$oldTotal = $this->getCurrentTotalXp();

View File

@ -0,0 +1,51 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\player\Player;
/**
* Called when a player uses an emote.
*/
class PlayerEmoteEvent extends PlayerEvent implements Cancellable{
use CancellableTrait;
public function __construct(
Player $player,
private string $emoteId
){
$this->player = $player;
}
public function getEmoteId() : string{
return $this->emoteId;
}
public function setEmoteId(string $emoteId) : void{
$this->emoteId = $emoteId;
}
}

View File

@ -52,7 +52,7 @@ class Language{
*/
public static function getLanguageList(string $path = "") : array{
if($path === ""){
$path = Path::join(\pocketmine\RESOURCE_PATH, "locale");
$path = \pocketmine\LOCALE_DATA_PATH;
}
if(is_dir($path)){
@ -101,7 +101,7 @@ class Language{
$this->langName = strtolower($lang);
if($path === null){
$path = Path::join(\pocketmine\RESOURCE_PATH, "locale");
$path = \pocketmine\LOCALE_DATA_PATH;
}
$this->lang = self::loadLang($path, $this->langName);

View File

@ -62,6 +62,7 @@ use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\DisconnectPacket;
use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
@ -1043,6 +1044,10 @@ class NetworkSession{
$this->sendDataPacket(SetTitlePacket::setAnimationTimes($fadeIn, $stay, $fadeOut));
}
public function onEmote(Player $from, string $emoteId) : void{
$this->sendDataPacket(EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER));
}
public function tick() : void{
if($this->info === null){
if(time() >= $this->connectTime + 10){

View File

@ -59,6 +59,7 @@ use pocketmine\network\mcpe\protocol\CommandRequestPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\network\mcpe\protocol\CraftingEventPacket;
use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\InteractPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
@ -890,4 +891,9 @@ class InGamePacketHandler extends PacketHandler{
*/
return true;
}
public function handleEmote(EmotePacket $packet) : bool{
$this->player->emote($packet->getEmoteId());
return true;
}
}

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\player;
use pocketmine\world\World;
use const M_SQRT2;
//TODO: turn this into an interface?
final class ChunkSelector{
@ -33,34 +34,44 @@ final class ChunkSelector{
* @phpstan-return \Generator<int, int, void, void>
*/
public function selectChunks(int $radius, int $centerX, int $centerZ) : \Generator{
$radiusSquared = $radius ** 2;
for($subRadius = 0; $subRadius < $radius; $subRadius++){
$subRadiusSquared = $subRadius ** 2;
$nextSubRadiusSquared = ($subRadius + 1) ** 2;
$minX = (int) ($subRadius / M_SQRT2);
for($x = 0; $x < $radius; ++$x){
for($z = 0; $z <= $x; ++$z){
if(($x ** 2 + $z ** 2) > $radiusSquared){
break; //skip to next band
}
$lastZ = 0;
//If the chunk is in the radius, others at the same offsets in different quadrants are also guaranteed to be.
for($x = $subRadius; $x >= $minX; --$x){
for($z = $lastZ; $z <= $x; ++$z){
$distanceSquared = ($x ** 2 + $z ** 2);
if($distanceSquared < $subRadiusSquared){
continue;
}elseif($distanceSquared >= $nextSubRadiusSquared){
break; //skip to next X
}
/* Top right quadrant */
yield World::chunkHash($centerX + $x, $centerZ + $z);
/* Top left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ + $z);
/* Bottom right quadrant */
yield World::chunkHash($centerX + $x, $centerZ - $z - 1);
/* Bottom left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ - $z - 1);
$lastZ = $z;
//If the chunk is in the radius, others at the same offsets in different quadrants are also guaranteed to be.
if($x !== $z){
/* Top right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ + $x);
/* Top left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ + $x);
/* Bottom right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ - $x - 1);
/* Bottom left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ - $x - 1);
/* Top right quadrant */
yield World::chunkHash($centerX + $x, $centerZ + $z);
/* Top left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ + $z);
/* Bottom right quadrant */
yield World::chunkHash($centerX + $x, $centerZ - $z - 1);
/* Bottom left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ - $z - 1);
if($x !== $z){
/* Top right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ + $x);
/* Top left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ + $x);
/* Bottom right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ - $x - 1);
/* Bottom left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ - $x - 1);
}
}
}
}

View File

@ -53,6 +53,7 @@ use pocketmine\event\player\PlayerChatEvent;
use pocketmine\event\player\PlayerCommandPreprocessEvent;
use pocketmine\event\player\PlayerDeathEvent;
use pocketmine\event\player\PlayerDisplayNameChangeEvent;
use pocketmine\event\player\PlayerEmoteEvent;
use pocketmine\event\player\PlayerEntityInteractEvent;
use pocketmine\event\player\PlayerExhaustEvent;
use pocketmine\event\player\PlayerGameModeChangeEvent;
@ -72,6 +73,7 @@ use pocketmine\event\player\PlayerToggleSprintEvent;
use pocketmine\event\player\PlayerTransferEvent;
use pocketmine\form\Form;
use pocketmine\form\FormValidationException;
use pocketmine\inventory\CallbackInventoryListener;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\PlayerCursorInventory;
use pocketmine\inventory\transaction\action\DropItemAction;
@ -193,6 +195,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* @phpstan-var array<int, UsedChunkStatus>
*/
protected array $usedChunks = [];
/**
* @var true[]
* @phpstan-var array<int, true>
*/
private array $activeChunkGenerationRequests = [];
/**
* @var true[] chunkHash => dummy
* @phpstan-var array<int, true>
@ -234,6 +241,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
/** @var int[] ID => ticks map */
protected array $usedItemsCooldown = [];
private int $lastEmoteTick = 0;
protected int $formIdCounter = 0;
/** @var Form[] */
protected array $forms = [];
@ -288,6 +297,17 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
parent::initEntity($nbt);
$this->addDefaultWindows();
$this->inventory->getListeners()->add(new CallbackInventoryListener(
function(Inventory $unused, int $slot) : void{
if($slot === $this->inventory->getHeldItemIndex()){
$this->setUsingItem(false);
}
},
function() : void{
$this->setUsingItem(false);
}
));
$this->firstPlayed = $nbt->getLong("firstPlayed", $now = (int) (microtime(true) * 1000));
$this->lastPlayed = $nbt->getLong("lastPlayed", $now);
@ -628,6 +648,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
$this->getNetworkSession()->stopUsingChunk($x, $z);
unset($this->usedChunks[$index]);
unset($this->activeChunkGenerationRequests[$index]);
}
$world->unregisterChunkLoader($this->chunkLoader, $x, $z);
$world->unregisterChunkListener($this, $x, $z);
@ -664,8 +685,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$count = 0;
$world = $this->getWorld();
$limit = $this->chunksPerTick - count($this->activeChunkGenerationRequests);
foreach($this->loadQueue as $index => $distance){
if($count >= $this->chunksPerTick){
if($count >= $limit){
break;
}
@ -677,6 +700,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
++$count;
$this->usedChunks[$index] = UsedChunkStatus::REQUESTED_GENERATION();
$this->activeChunkGenerationRequests[$index] = true;
unset($this->loadQueue[$index]);
$this->getWorld()->registerChunkLoader($this->chunkLoader, $X, $Z, true);
$this->getWorld()->registerChunkListener($this, $X, $Z);
@ -692,6 +716,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
//multiple callbacks for this player. In that case, only the first one matters.
return;
}
unset($this->activeChunkGenerationRequests[$index]);
$this->usedChunks[$index] = UsedChunkStatus::REQUESTED_SENDING();
$this->getNetworkSession()->startUsingChunk($X, $Z, function() use ($X, $Z, $index) : void{
@ -1163,16 +1188,18 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->lastLocation = $to;
$this->broadcastMovement();
$distance = sqrt((($from->x - $to->x) ** 2) + (($from->z - $to->z) ** 2));
//TODO: check swimming (adds 0.015 exhaustion in MCPE)
if($this->isSprinting()){
$this->hungerManager->exhaust(0.1 * $distance, PlayerExhaustEvent::CAUSE_SPRINTING);
}else{
$this->hungerManager->exhaust(0.01 * $distance, PlayerExhaustEvent::CAUSE_WALKING);
}
$horizontalDistanceTravelled = sqrt((($from->x - $to->x) ** 2) + (($from->z - $to->z) ** 2));
if($horizontalDistanceTravelled > 0){
//TODO: check swimming (adds 0.015 exhaustion in MCPE)
if($this->isSprinting()){
$this->hungerManager->exhaust(0.1 * $horizontalDistanceTravelled, PlayerExhaustEvent::CAUSE_SPRINTING);
}else{
$this->hungerManager->exhaust(0.01 * $horizontalDistanceTravelled, PlayerExhaustEvent::CAUSE_WALKING);
}
if($this->nextChunkOrderRun > 20){
$this->nextChunkOrderRun = 20;
if($this->nextChunkOrderRun > 20){
$this->nextChunkOrderRun = 20;
}
}
}
@ -1727,6 +1754,21 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return true;
}
public function emote(string $emoteId) : void{
$currentTick = $this->server->getTick();
if($currentTick - $this->lastEmoteTick > 5){
$this->lastEmoteTick = $currentTick;
$event = new PlayerEmoteEvent($this, $emoteId);
$event->call();
if(!$event->isCancelled()){
$emoteId = $event->getEmoteId();
foreach($this->getViewers() as $player){
$player->getNetworkSession()->onEmote($this, $emoteId);
}
}
}
}
/**
* Drops an item on the ground in front of the player.
*/

View File

@ -263,6 +263,12 @@ class World implements ChunkManager{
* @phpstan-var \SplQueue<int>
*/
private \SplQueue $chunkPopulationRequestQueue;
/**
* @var true[] chunkHash => dummy
* @phpstan-var array<int, true>
*/
private array $chunkPopulationRequestQueueIndex = [];
/** @var bool[] */
private $generatorRegisteredWorkers = [];
@ -2733,12 +2739,19 @@ class World implements ChunkManager{
}
}
private function addChunkHashToPopulationRequestQueue(int $chunkHash) : void{
if(!isset($this->chunkPopulationRequestQueueIndex[$chunkHash])){
$this->chunkPopulationRequestQueue->enqueue($chunkHash);
$this->chunkPopulationRequestQueueIndex[$chunkHash] = true;
}
}
/**
* @phpstan-return Promise<Chunk>
*/
private function enqueuePopulationRequest(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : Promise{
$chunkHash = World::chunkHash($chunkX, $chunkZ);
$this->chunkPopulationRequestQueue->enqueue($chunkHash);
$this->addChunkHashToPopulationRequestQueue($chunkHash);
$resolver = $this->chunkPopulationRequestMap[$chunkHash] = new PromiseResolver();
if($associatedChunkLoader === null){
$temporaryLoader = new class implements ChunkLoader{};
@ -2753,17 +2766,9 @@ class World implements ChunkManager{
private function drainPopulationRequestQueue() : void{
$failed = [];
$seen = [];
while(count($this->activeChunkPopulationTasks) < $this->maxConcurrentChunkPopulationTasks && !$this->chunkPopulationRequestQueue->isEmpty()){
$nextChunkHash = $this->chunkPopulationRequestQueue->dequeue();
if(isset($seen[$nextChunkHash])){
//We may have previously requested this, decided we didn't want it, and then decided we did want it
//again, all before the generation request got executed. In that case, the chunk hash may appear in the
//queue multiple times (it can't be quickly removed from the queue when the request is cancelled, so we
//leave it in the queue).
continue;
}
$seen[$nextChunkHash] = true;
unset($this->chunkPopulationRequestQueueIndex[$nextChunkHash]);
World::getXZ($nextChunkHash, $nextChunkX, $nextChunkZ);
if(isset($this->chunkPopulationRequestMap[$nextChunkHash])){
assert(!isset($this->activeChunkPopulationTasks[$nextChunkHash]), "Population for chunk $nextChunkX $nextChunkZ already running");
@ -2779,7 +2784,7 @@ class World implements ChunkManager{
//these requests failed even though they weren't rate limited; we can't directly re-add them to the back of the
//queue because it would result in an infinite loop
foreach($failed as $hash){
$this->chunkPopulationRequestQueue->enqueue($hash);
$this->addChunkHashToPopulationRequestQueue($hash);
}
}
@ -2978,7 +2983,7 @@ class World implements ChunkManager{
//request failed, stick it back on the queue
//we didn't resolve the promise or touch it in any way, so any fake chunk loaders are still valid and
//don't need to be added a second time.
$this->chunkPopulationRequestQueue->enqueue($index);
$this->addChunkHashToPopulationRequestQueue($index);
}
$this->drainPopulationRequestQueue();

View File

@ -41,15 +41,11 @@ use function igbinary_unserialize;
class PopulationTask extends AsyncTask{
private const TLS_KEY_ON_COMPLETION = "onCompletion";
/** @var int */
public $worldId;
/** @var int */
private $chunkX;
/** @var int */
private $chunkZ;
private int $worldId;
private int $chunkX;
private int $chunkZ;
/** @var string|null */
public $chunk;
private ?string $chunk;
private string $adjacentChunks;

View File

@ -20,21 +20,6 @@ parameters:
count: 1
path: ../../../build/server-phar.php
-
message: "#^Cannot access offset \\(float\\|int\\) on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Cannot access offset string on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Parameter \\#1 \\$path of static method pocketmine\\\\utils\\\\Filesystem\\:\\:cleanPath\\(\\) expects string, mixed given\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Parameter \\#1 \\$stream of function fclose expects resource, resource\\|false given\\.$#"
count: 1
@ -70,11 +55,6 @@ parameters:
count: 3
path: ../../../src/PocketMine.php
-
message: "#^Cannot access offset 'type' on mixed\\.$#"
count: 1
path: ../../../src/Server.php
-
message: "#^Cannot cast mixed to string\\.$#"
count: 1
@ -545,6 +525,11 @@ parameters:
count: 1
path: ../../../src/crafting/CraftingManagerFromDataHelper.php
-
message: "#^Parameter \\#1 \\$path of static method pocketmine\\\\utils\\\\Filesystem\\:\\:cleanPath\\(\\) expects string, mixed given\\.$#"
count: 1
path: ../../../src/crash/CrashDump.php
-
message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#"
count: 1

View File

@ -1,60 +1,5 @@
parameters:
ignoreErrors:
-
message: "#^Cannot access offset 'base_version' on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Cannot access offset 'build' on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Cannot access offset 'composer_libraries' on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Cannot access offset 'git' on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Cannot access offset 'is_dev' on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Cannot access offset 'os' on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Cannot access offset 'php' on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Cannot access offset 'php_os' on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Cannot access offset 'protocol' on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Cannot access offset 'uname' on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Cannot access offset 'zend' on mixed\\.$#"
count: 1
path: ../../../src/CrashDump.php
-
message: "#^Instanceof between pocketmine\\\\block\\\\utils\\\\BannerPatternLayer and pocketmine\\\\block\\\\utils\\\\BannerPatternLayer will always evaluate to true\\.$#"
count: 1
@ -75,21 +20,16 @@ parameters:
count: 1
path: ../../../src/crafting/CraftingManager.php
-
message: "#^Property pocketmine\\\\crash\\\\CrashDumpData\\:\\:\\$extensions \\(array\\<string, string\\>\\) does not accept array\\<int\\|string, string\\>\\.$#"
count: 1
path: ../../../src/crash/CrashDump.php
-
message: "#^Call to function assert\\(\\) with false and 'unknown hit type' will always evaluate to false\\.$#"
count: 1
path: ../../../src/entity/projectile/Projectile.php
-
message: "#^Parameter \\#1 \\$array of function array_values expects array, array given\\.$#"
count: 1
path: ../../../src/plugin/PluginDescription.php
-
message: "#^Parameter \\#2 \\$array of function array_map expects array, array given\\.$#"
count: 3
path: ../../../src/plugin/PluginDescription.php
-
message: "#^Dead catch \\- RuntimeException is never thrown in the try block\\.$#"
count: 1

View File

@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace pocketmine\tools\simulate_chunk_selector;
use pocketmine\player\ChunkSelector;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\format\Chunk;
use pocketmine\world\World;
use Webmozart\PathUtil\Path;
use function assert;
use function count;
use function dirname;
use function fwrite;
use function getopt;
use function imagearc;
use function imagecolorallocate;
use function imagecreatetruecolor;
use function imagefill;
use function imagefilledrectangle;
use function imagepng;
use function imagerectangle;
use function imagesavealpha;
use function is_dir;
use function is_string;
use function mkdir;
use function realpath;
use function scandir;
use function str_pad;
use function strval;
use const SCANDIR_SORT_NONE;
use const STDERR;
use const STR_PAD_LEFT;
require dirname(__DIR__) . '/vendor/autoload.php';
function newImage(int $scale, int $radius) : \GdImage{
$image = imagecreatetruecolor($scale * $radius * 2, $scale * $radius * 2);
if($image === false){
throw new AssumptionFailedError();
}
imagesavealpha($image, true);
$black = imagecolorallocate($image, 0, 0, 0);
if($black === false){
throw new AssumptionFailedError();
}
imagefill($image, 0, 0, $black);
return $image;
}
function render(int $radius, int $baseX, int $baseZ, int $chunksPerStep, int $scale, \GdImage $image, int $chunkColor, int $offsetX, int $offsetZ, string $outputFolder) : void{
echo "Render start: radius $radius, chunks per step $chunksPerStep\n";
$iterator = (new ChunkSelector())->selectChunks($radius, $baseX, $baseZ);
$middleOffsetX = $scale * ($radius + $offsetX);
$middleOffsetZ = $scale * ($radius + $offsetZ);
$black = imagecolorallocate($image, 0, 0, 0);
$yellow = imagecolorallocate($image, 255, 255, 51);
$red = imagecolorallocate($image, 255, 0, 0);
if($black === false || $yellow === false || $red === false) throw new AssumptionFailedError();
$frame = 0;
$seen = [];
while($iterator->valid()){
$frame++;
for($i = 0; $i < $chunksPerStep; ++$i){
$chunkHash = $iterator->current();
if(!isset($seen[$chunkHash])){
$color = $chunkColor;
$seen[$chunkHash] = true;
}else{
$color = $yellow;
}
World::getXZ($chunkHash, $chunkX, $chunkZ);
$imageX = $middleOffsetX + (($chunkX - $baseX) * $scale);
$imageZ = $middleOffsetZ + (($chunkZ - $baseZ) * $scale);
imagefilledrectangle($image, $imageX, $imageZ, $imageX + $scale, $imageZ + $scale, $color);
imagerectangle($image, $imageX, $imageZ, $imageX + $scale, $imageZ + $scale, $black);
$iterator->next();
if(!$iterator->valid()){
break;
}
}
imagearc($image, $middleOffsetX, $middleOffsetZ, $radius * $scale * 2, $radius * $scale * 2, 0, 360, $red);
imagepng($image, Path::join($outputFolder, "frame" . str_pad(strval($frame), 5, "0", STR_PAD_LEFT) . ".png"));
echo "\rRendered step $frame";
}
echo "\n";
}
$radius = null;
$baseX = 10000 >> Chunk::COORD_BIT_SIZE;
$baseZ = 10000 >> Chunk::COORD_BIT_SIZE;
$nChunksPerStep = 32;
$scale = 10;
if(count(getopt("", ["help"])) !== 0){
echo "Required parameters:\n";
echo "--output=path/to/dir: Output folder to put the generated images into (will attempt to create if it doesn't exist)\n";
echo "--radius=N: Radius of chunks to render (default $radius)\n";
echo "\n";
echo "Optional parameters:\n";
echo "--baseX=N: Base X coordinate to use for simulation (default $baseX\n";
echo "--baseZ=N: Base Z coordinate to use for simulation (default $baseZ)\n";
echo "--scale=N: Height/width of square of pixels to use for each chunk (default $scale)\n";
echo "--chunksPerStep=N: Number of chunks to process in each frame (default $nChunksPerStep)\n";
exit(0);
}
foreach(getopt("", ["radius:", "baseX:", "baseZ:", "scale:", "chunksPerStep:"]) as $name => $value){
if(!is_string($value) || (string) ((int) $value) !== $value){
fwrite(STDERR, "Value for --$name must be an integer\n");
exit(1);
}
$value = (int) $value;
match($name){
"radius" => ($radius = $value),
"baseX" => ($baseX = $value),
"baseZ" => ($baseZ = $value),
"scale" => ($scale = $value),
"chunksPerStep" => ($nChunksPerStep = $value),
default => throw new AssumptionFailedError("getopt() returned unknown option")
};
}
if($radius === null){
fwrite(STDERR, "Please specify a radius using --radius\n");
exit(1);
}
$outputDirectory = null;
foreach(getopt("", ["output:"]) as $name => $value){
assert($name === "output");
if(!is_string($value)){
fwrite(STDERR, "Value for --$name must be a string\n");
exit(1);
}
if(!@mkdir($value) && !is_dir($value)){
fwrite(STDERR, "Output directory $value could not be created\n");
exit(1);
}
$files = scandir($value, SCANDIR_SORT_NONE);
if($files !== false && count($files) > 2){ //always returns . and ..
fwrite(STDERR, "Output directory $value is not empty\n");
exit(1);
}
$realPath = realpath($value);
if($realPath === false){
throw new AssumptionFailedError();
}
$outputDirectory = $realPath;
}
if($outputDirectory === null){
fwrite(STDERR, "Please specify an output directory using --output\n");
exit(1);
}
$image = newImage($scale, $radius);
$black = imagecolorallocate($image, 0, 0, 0);
$green = imagecolorallocate($image, 0, 220, 0);
if($black === false || $green === false){
throw new AssumptionFailedError();
}
render($radius, $baseX, $baseZ, $nChunksPerStep, $scale, $image, $green, 0, 0, $outputDirectory);