mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-09 19:24:12 +00:00
Compare commits
33 Commits
4.0.0-BETA
...
4.0.0-BETA
Author | SHA1 | Date | |
---|---|---|---|
002feacf8e | |||
b8523f7a18 | |||
640e88009b | |||
6cd272c9e1 | |||
566c57bcd3 | |||
3c754b079c | |||
dbf9a33160 | |||
07b1cff306 | |||
0989c77037 | |||
5107d0df4e | |||
579ef63663 | |||
8abc952c74 | |||
4c3a5fdd73 | |||
54f287feb6 | |||
84f8b3eb2d | |||
15fca84f3b | |||
c60144210f | |||
8ac999cbd4 | |||
4f8501ff34 | |||
2405e45b35 | |||
e0b07ff308 | |||
729f831b8f | |||
0356716e8e | |||
5c81b04813 | |||
1ebb206762 | |||
29e2d92098 | |||
ef82a2cd79 | |||
87031627bf | |||
f066199971 | |||
a0e9eec652 | |||
fa6a432d58 | |||
102277c636 | |||
f50f26d52e |
2
.github/workflows/draft-release.yml
vendored
2
.github/workflows/draft-release.yml
vendored
@ -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 }}
|
||||
|
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -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
3
.gitmodules
vendored
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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
49
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": "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
@ -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');
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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"));
|
84
src/crash/CrashDumpData.php
Normal file
84
src/crash/CrashDumpData.php
Normal 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;
|
||||
}
|
||||
}
|
46
src/crash/CrashDumpDataGeneral.php
Normal file
46
src/crash/CrashDumpDataGeneral.php
Normal 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,
|
||||
){}
|
||||
}
|
45
src/crash/CrashDumpDataPluginEntry.php
Normal file
45
src/crash/CrashDumpDataPluginEntry.php
Normal 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,
|
||||
){}
|
||||
}
|
@ -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");
|
||||
|
@ -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();
|
||||
|
||||
|
51
src/event/player/PlayerEmoteEvent.php
Normal file
51
src/event/player/PlayerEmoteEvent.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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){
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
171
tools/simulate-chunk-selector.php
Normal file
171
tools/simulate-chunk-selector.php
Normal 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);
|
Reference in New Issue
Block a user