Compare commits

..

69 Commits

Author SHA1 Message Date
0d5704b156 Release 3.11.3 2020-01-04 16:46:55 +00:00
f355044626 resources: do not remove client-side resource packs when forcing resources
I wanted to make this into a dedicated option, but it had some side effects (see 4794ba236a).
I'll settle for just disabling this useless behaviour, since we have other ways to force vanilla resources anyway; setting this flag to always-false has no user-facing effects anyway.
2020-01-04 16:14:08 +00:00
4794ba236a Revert "resource packs: added new option remove_client_resources, fixed client packs being removed when forcing resource pack download"
This reverts commit 06ec8b8397.

unfortunately, this had some unanticipated side effects, thanks to
idiotic behaviour in the client ... when having optional downloads but
trying to force resources, the client chokes because it thinks the
server is forcing it to apply a pack that it doesn't have. Since
there's no way to detect when this problem occurs in the protocol, the
only option is to revert this.
2020-01-04 16:04:07 +00:00
6490a49c70 tests: drop lint.sh - no longer needed now that we have PHPStan 2020-01-04 15:23:53 +00:00
5cd7e11b29 Use specialized build script for Travis 2020-01-04 15:15:01 +00:00
08e3b8ffdc build: added specialized script to create a server phar
this is much easier to use than devtools, and allows us to make additional specializations for PM build that would otherwise just be a colossal pain in the ass.
2020-01-04 14:56:53 +00:00
9232f4509c TimingsHandler: don't bail on redundant attempts to stop non-running timers
while it would be nice to bail, providing the environment to allow bailing without breaking stuff requires some complex changes that would reduce performance when timings is not running. Considering the limited usefulness of bailing here anyway, and the fact that it just has to be prevented to not have side effects, it doesn't make a whole lot of sense right now.

closes #3261, closes #3269, closes #3254
2020-01-04 14:44:55 +00:00
cef77907c6 Timings: fixed grandparent timers not working correctly, closes #3229 2020-01-04 13:23:04 +00:00
06ec8b8397 resource packs: added new option remove_client_resources, fixed client packs being removed when forcing resource pack download 2020-01-04 13:16:47 +00:00
ee08286eca moved git hash detection code to its own unit 2020-01-02 21:01:31 +00:00
a83211f96a VersionInfo: add strict_types to stop CS tools screwing with it 2020-01-02 20:05:50 +00:00
0b3c4ee496 bootstrap: don't choke on paths that have spaces in them 2019-12-30 11:58:44 +00:00
54de518634 bootstrap: fixed incorrect git hash detection when cwd is not the repo root 2019-12-30 09:09:47 +00:00
a908197907 Effect: document that duration is expected in ticks 2019-12-29 19:03:32 +00:00
3e23a568ca phpstan: properly fixing FPs on constant comparisons 2019-12-29 14:25:44 +00:00
dadc5c1b87 3.11.3 is next 2019-12-29 13:11:30 +00:00
a37d740111 Release 3.11.2 2019-12-29 13:11:30 +00:00
2de0ec02ba phpstan 0.12.3 2019-12-28 17:22:08 +00:00
d83820477f TimingsHandler: throw on attempt to stop timer that is not running
I do not think that this bug is in effect in the core code, but I can't be sure.
2019-12-21 13:17:40 +00:00
8726604899 Merge #3251: Remove usages of empty() 2019-12-19 11:08:08 +00:00
9cbe378e8c Timezone: fix possible crash on CentOS 2019-12-18 11:23:24 +00:00
494660102e Replace empty() usages with count() 2019-12-18 11:23:24 +00:00
216138a37e PlayerAuthInputPacket: Fix assigning variable in static context 2019-12-17 22:04:24 +08:00
b08c38f8f9 build/make-release: remove redundant require_once
this is included by vendor/autoload.php since 3.7.x.
2019-12-17 11:47:07 +00:00
911b6feaf9 NetworkInventoryAction: remove dead code
the trading & beacon types need to be checked as well, but I don't have the tools to check that right now.
2019-12-17 11:17:40 +00:00
2cb6990698 Enchantment: don't throw exceptions on out of range IDs
this function is used for data deserialization, and data may have bad enchantment IDs in it.
2019-12-14 10:04:51 +00:00
f7d66613df CompletedUsingItemPacket: added missing #include, closes #3219 2019-12-13 19:50:40 +00:00
95c32d26df Explosion: fixed ray trace getting stuck on a block when it encounters an empty subchunk
this might have caused unexpected behaviour in large caves.
2019-12-13 18:07:59 +00:00
9e1f6a2486 protocol: updated particle IDs 2019-12-13 10:52:09 +00:00
76994f15ac phpstan: green on level 5 2019-12-12 18:27:26 +00:00
cf73d74bd0 format/anvil: fixed possible type violation on saving chunk 2019-12-12 18:24:21 +00:00
37a8d95464 world IO: fixed crashes when garbage data found in tile/entity NBT data 2019-12-12 18:21:23 +00:00
9a4b72add5 PlayerInventory: fix type violation when calling equipItem() for non-Player holder 2019-12-12 16:31:22 +00:00
919534d978 EnderChest: fixed crash when plugins overwrite tile classes with incompatible ones
relates to 47a959dace
2019-12-12 16:28:30 +00:00
cb598155a4 Server: add @return annotation to crashDump() to make phpstan happy
this is technically a bug in PHPStan, but it's easier to do this than report a bug.
2019-12-12 16:19:57 +00:00
00888fdc55 TranslationContainer::__construct() accepts float and int too (they can be casted to string) 2019-12-12 16:19:04 +00:00
77795ae3bc BaseLang::translateString() accepts float and int too (they can be casted to string) 2019-12-12 16:18:11 +00:00
f39fc7e525 CompressBatchedTask::__construct() accepts Player[] not string[] 2019-12-12 16:16:48 +00:00
77f7595e0e Location::__construct() accepts floats for x,y,z 2019-12-12 16:16:16 +00:00
e8d3a25028 Position::__construct() accepts floats for x,y,z 2019-12-12 16:15:49 +00:00
1370930ea9 Entity: remove redundant defaults from lastX lastY lastZ, remove nullability
these fields are never null because they are initialized in the constructor, and they are never written to with null nor ever expected to be null.
2019-12-12 16:14:30 +00:00
70c3008b7b phpstan: green on level 4 2019-12-12 13:00:57 +00:00
46930b98b7 Timings: add a dedicated field for checking initialization 2019-12-12 12:59:01 +00:00
92be8c8ec0 PopulationTask: don't assume anything about TLS return values
while these SHOULD be what we say they are, it's possible for something else to overwrite them with junk and make the server catch fire.
2019-12-12 12:19:54 +00:00
62069bc7af Level: fix return type content doc comment for getAdjacentChunks() 2019-12-12 12:18:35 +00:00
26230c1f9b Player: don't report never-played for a disconnected player
this should catch fire like everything else does.
2019-12-12 12:17:52 +00:00
a9fafbc5eb BanEntry: remove nullable return from parseDate()
this function never returns null
2019-12-12 11:59:41 +00:00
b8778cb791 LevelProvider: fix doc comment of ::generate()
this raised false positives on phpstan level 4
2019-12-12 11:59:12 +00:00
73c5fe5cf9 BaseInventory: correctly annotate content type of slots field
fixes warnings on phpstan level 4
2019-12-12 11:58:11 +00:00
b3cfa5a3a0 TimingsHandler: use weak comparison for stopTiming start check
this could be an int or float, and 0 !== 0.0.
2019-12-12 11:48:34 +00:00
6127a02a8b phpstan 0.12.2 2019-12-12 10:43:45 +00:00
dbca36e5e2 SkinImage: throw on wrong data length 2019-12-11 22:45:14 +00:00
1171bae691 login: read the correct key SkinAnimationData 2019-12-11 22:40:05 +00:00
494f8e8251 login: fixed missing base64_decode() for animation images 2019-12-11 22:39:40 +00:00
3c9af56e06 3.11.2 is next 2019-12-11 20:20:26 +00:00
40a2211a5a Release 3.11.1 2019-12-11 20:20:26 +00:00
0196aa21d7 updated BedrockData submodule, close #3222, close #3223 2019-12-11 20:19:17 +00:00
833f3e574b 3.11.1 is next 2019-12-11 18:35:03 +00:00
a386ff8ce7 Release 3.11.0 2019-12-11 18:35:03 +00:00
e6c3b0fc0d Changes for 1.14
there are no protocol changes in 1.14 that matter; the reason the protocol version has been bumped is because of new blocks, items, mobs and recipes.
2019-12-11 18:32:22 +00:00
9568364277 fix minor formatting issue in SkinAdapterSingleton 2019-12-09 21:19:15 +00:00
73d4ff6b52 more missed function imports 2019-12-09 21:16:35 +00:00
7e98aa1497 leveldb: remove usage of multi-import statement
this doesn't conform to the code style.
2019-12-09 20:28:02 +00:00
f00c69c513 added missing function imports 2019-12-09 20:13:02 +00:00
50a4c42f3f NetworkBinaryStream: fix misplaced import 2019-12-09 19:53:32 +00:00
6b61efcfc8 remove unused imports 2019-12-09 19:49:21 +00:00
1a99938e4b LegacySkinAdapter: clean up nonsensical code ordering 2019-12-09 18:26:20 +00:00
a4d68fb32b LegacySkinAdapter: fix some wacky cape handling logic
I added checks for length validity locally and ran into some interesting behaviour because of this code.
2019-12-09 18:16:46 +00:00
5682cc8d53 3.10.2 is next 2019-12-07 16:41:41 +00:00
75 changed files with 618 additions and 261 deletions

View File

@ -36,7 +36,6 @@ use function system;
use const pocketmine\BASE_VERSION;
use const STDIN;
require_once dirname(__DIR__) . '/src/pocketmine/VersionInfo.php';
require_once dirname(__DIR__) . '/vendor/autoload.php';
if(isset($argv[1])){

147
build/server-phar.php Normal file
View File

@ -0,0 +1,147 @@
<?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\build\server_phar;
use pocketmine\utils\Git;
require dirname(__DIR__) . '/vendor/autoload.php';
/**
* @param string[] $strings
* @param string|null $delim
*
* @return string[]
*/
function preg_quote_array(array $strings, string $delim = null) : array{
return array_map(function(string $str) use ($delim) : string{ return preg_quote($str, $delim); }, $strings);
}
/**
* @param string $pharPath
* @param string $basePath
* @param string[] $includedPaths
* @param array $metadata
* @param string $stub
* @param int $signatureAlgo
* @param int|null $compression
*
* @return \Generator|string[]
*/
function buildPhar(string $pharPath, string $basePath, array $includedPaths, array $metadata, string $stub, int $signatureAlgo = \Phar::SHA1, ?int $compression = null){
$basePath = rtrim(str_replace("/", DIRECTORY_SEPARATOR, $basePath), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$includedPaths = array_map(function($path){
return rtrim(str_replace("/", DIRECTORY_SEPARATOR, $path), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
}, $includedPaths);
yield "Creating output file $pharPath";
if(file_exists($pharPath)){
yield "Phar file already exists, overwriting...";
try{
\Phar::unlinkArchive($pharPath);
}catch(\PharException $e){
//unlinkArchive() doesn't like dodgy phars
unlink($pharPath);
}
}
yield "Adding files...";
$start = microtime(true);
$phar = new \Phar($pharPath);
$phar->setMetadata($metadata);
$phar->setStub($stub);
$phar->setSignatureAlgorithm($signatureAlgo);
$phar->startBuffering();
//If paths contain any of these, they will be excluded
$excludedSubstrings = preg_quote_array([
realpath($pharPath), //don't add the phar to itself
], '/');
$folderPatterns = preg_quote_array([
DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR,
DIRECTORY_SEPARATOR . '.' //"Hidden" files, git dirs etc
], '/');
//Only exclude these within the basedir, otherwise the project won't get built if it itself is in a directory that matches these patterns
$basePattern = preg_quote(rtrim($basePath, DIRECTORY_SEPARATOR), '/');
foreach($folderPatterns as $p){
$excludedSubstrings[] = $basePattern . '.*' . $p;
}
$regex = sprintf('/^(?!.*(%s))^%s(%s).*/i',
implode('|', $excludedSubstrings), //String may not contain any of these substrings
preg_quote($basePath, '/'), //String must start with this path...
implode('|', preg_quote_array($includedPaths, '/')) //... and must be followed by one of these relative paths, if any were specified. If none, this will produce a null capturing group which will allow anything.
);
$directory = new \RecursiveDirectoryIterator($basePath, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::CURRENT_AS_PATHNAME); //can't use fileinfo because of symlinks
$iterator = new \RecursiveIteratorIterator($directory);
$regexIterator = new \RegexIterator($iterator, $regex);
$count = count($phar->buildFromIterator($regexIterator, $basePath));
yield "Added $count files";
if($compression !== null){
yield "Checking for compressible files...";
foreach($phar as $file => $finfo){
/** @var \PharFileInfo $finfo */
if($finfo->getSize() > (1024 * 512)){
yield "Compressing " . $finfo->getFilename();
$finfo->compress($compression);
}
}
}
$phar->stopBuffering();
yield "Done in " . round(microtime(true) - $start, 3) . "s";
}
function main() : void{
if(ini_get("phar.readonly") == 1){
echo "Set phar.readonly to 0 with -dphar.readonly=0" . PHP_EOL;
exit(1);
}
$opts = getopt("", ["out:"]);
$gitHash = Git::getRepositoryStatePretty(dirname(__DIR__));
echo "Git hash detected as $gitHash" . PHP_EOL;
foreach(buildPhar(
$opts["out"] ?? getcwd() . DIRECTORY_SEPARATOR . "PocketMine-MP.phar",
dirname(__DIR__) . DIRECTORY_SEPARATOR,
[
'src',
'vendor'
],
[
'git' => $gitHash
],
'<?php require("phar://" . __FILE__ . "/src/pocketmine/PocketMine.php"); __HALT_COMPILER();'
) as $line){
echo $line . PHP_EOL;
}
}
if(!defined('pocketmine\_PHPSTAN_ANALYSIS')){
main();
}

45
changelogs/3.11.md Normal file
View File

@ -0,0 +1,45 @@
**For Minecraft: Bedrock Edition 1.14.0**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
Plugin developers should **only** update their required API to this version if you need the changes in this build.
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
# 3.11.0
- Added support for Minecraft: Bedrock Edition 1.14.0
- Removed compatibility with 1.13.0
# 3.11.1
- Fixed blocks with incorrect properties when placed or interacted with.
# 3.11.2
## Core
- PHPStan 0.12.3 with level 5 is now used for automated static analysis.
- Fixed a possible crash when plugins override the `EnderChest` tile class with something incompatible.
- Fixed disconnected players being considered as never played.
- Fixed enchantments with IDs outside the range 0-255 in item NBT crashing the server.
- Fixed particles rendering incorrectly.
- Timings handlers are no longer able to underflow; they now throw exceptions when attempting to be stopped more times than they were started.
- Fixed explosion rays getting stuck in empty subchunks (possible incorrect behaviour in large caves).
- Fixed bad tile/entity NBT data being propagated from world providers in some cases.
- Fixed a possible crash when detecting timezone on CentOS.
- Fixed many cases of incorrectly documented types in the API found by PHPStan.
- Generation tasks no longer assume that generator instances stored in TLS are always valid, fixing a possible crash.
## Protocol
- Fixed skin animation image corruption in LoginPacket handling caused by incorrect data handling.
- Fixed skin animation extra data not being decoded from LoginPacket.
- `SkinImage` now throws `InvalidArgumentException` if it receives an unexpected amount of bytes for the given image heigh/width.
- Fixed broken code in `PlayerAuthInputPacket::create()`.
- Removed some dead constants from `NetworkInventoryAction`.
# 3.11.3
- Fixed some PHPStan false-positives in release builds.
- Git hash is now correctly detected for source builds when the working directory is not the repository root.
- Added a specialized build script `build/server-phar.php` for creating server phars.
- Fixed timings crashing the server.
- Timings chains now work correctly.
- Fixed some minor timing errors in chained timings.
- Forcing resource packs no longer causes removal of client-sided resource packs. If this behaviour is desired, use a vanilla resource pack at the bottom of your resource stack (as was necessary for non-forced packs).
- Added documentation to the API to clarify that effect durations are in ticks.

View File

@ -4,14 +4,20 @@ includes:
- tests/phpstan/configs/optional-leveldb.neon
- tests/phpstan/configs/phpstan-bugs.neon
- tests/phpstan/configs/pthreads-bugs.neon
- tests/phpstan/configs/runtime-type-checks.neon
parameters:
level: 3
level: 5
autoload_files:
- tests/phpstan/bootstrap.php
- src/pocketmine/PocketMine.php
- build/server-phar.php
paths:
- src
- build/server-phar.php
dynamicConstantNames:
- pocketmine\IS_DEVELOPMENT_BUILD
- pocketmine\DEBUG
reportUnmatchedIgnoredErrors: false #no other way to silence platform-specific non-warnings
ignoreErrors:
-

View File

@ -464,11 +464,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function getFirstPlayed(){
return $this->namedtag instanceof CompoundTag ? $this->namedtag->getLong("firstPlayed", 0, true) : null;
return $this->namedtag->getLong("firstPlayed", 0, true);
}
public function getLastPlayed(){
return $this->namedtag instanceof CompoundTag ? $this->namedtag->getLong("lastPlayed", 0, true) : null;
return $this->namedtag->getLong("lastPlayed", 0, true);
}
public function hasPlayedBefore() : bool{
@ -741,7 +741,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$data->overloads[0][0] = $parameter;
$aliases = $command->getAliases();
if(!empty($aliases)){
if(count($aliases) > 0){
if(!in_array($data->commandName, $aliases, true)){
//work around a client bug which makes the original name not show when aliases are used
$aliases[] = $data->commandName;
@ -1208,7 +1208,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$this->loadQueue = $newOrder;
if(!empty($this->loadQueue) or !empty($unloadChunks)){
if(count($this->loadQueue) > 0 or count($unloadChunks) > 0){
$pk = new NetworkChunkPublisherUpdatePacket();
$pk->x = $this->getFloorX();
$pk->y = $this->getFloorY();
@ -1921,7 +1921,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$animations = [];
foreach($packet->clientData["AnimatedImageData"] as $animation){
$animations[] = new SkinAnimation(new SkinImage($animation["ImageHeight"], $animation["ImageWidth"], $animation["Image"]), $animation["Type"], $animation["Frames"]);
$animations[] = new SkinAnimation(new SkinImage($animation["ImageHeight"], $animation["ImageWidth"], base64_decode($animation["Image"], true)), $animation["Type"], $animation["Frames"]);
}
$skinData = new SkinData(
@ -1931,7 +1931,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$animations,
new SkinImage($packet->clientData["CapeImageHeight"], $packet->clientData["CapeImageWidth"], base64_decode($packet->clientData["CapeData"] ?? "")),
base64_decode($packet->clientData["SkinGeometryData"] ?? ""),
base64_decode($packet->clientData["AnimationData"] ?? ""),
base64_decode($packet->clientData["SkinAnimationData"] ?? ""),
$packet->clientData["PremiumSkin"] ?? false,
$packet->clientData["PersonaSkin"] ?? false,
$packet->clientData["CapeOnClassicSkin"] ?? false,
@ -2118,7 +2118,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$pk = new ResourcePackStackPacket();
$manager = $this->server->getResourcePackManager();
$pk->resourcePackStack = $manager->getResourceStack();
$pk->mustAccept = $manager->resourcePacksRequired();
//we don't force here, because it doesn't have user-facing effects
//but it does have an annoying side-effect when true: it makes
//the client remove its own non-server-supplied resource packs.
$pk->mustAccept = false;
$this->dataPacket($pk);
break;
case ResourcePackClientResponsePacket::STATUS_COMPLETED:

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine {
use pocketmine\utils\Git;
use pocketmine\utils\MainLogger;
use pocketmine\utils\ServerKiller;
use pocketmine\utils\Terminal;
@ -140,7 +141,7 @@ namespace pocketmine {
}
function server(){
if(!empty($messages = check_platform_dependencies())){
if(count($messages = check_platform_dependencies()) > 0){
echo PHP_EOL;
$binary = version_compare(PHP_VERSION, "5.4") >= 0 ? PHP_BINARY : "unknown";
critical_error("Selected PHP binary ($binary) does not satisfy some requirements.");
@ -186,12 +187,7 @@ namespace pocketmine {
$gitHash = str_repeat("00", 20);
if(\Phar::running(true) === ""){
if(Utils::execute("git rev-parse HEAD", $out) === 0 and $out !== false and strlen($out = trim($out)) === 40){
$gitHash = trim($out);
if(Utils::execute("git diff --quiet") === 1 or Utils::execute("git diff --cached --quiet") === 1){ //Locally-modified
$gitHash .= "-dirty";
}
}
$gitHash = Git::getRepositoryStatePretty(\pocketmine\PATH);
}else{
$phar = new \Phar(\Phar::running(false));
$meta = $phar->getMetadata();

View File

@ -114,6 +114,7 @@ use function asort;
use function assert;
use function base64_encode;
use function class_exists;
use function cli_set_process_title;
use function count;
use function define;
use function explode;
@ -128,7 +129,6 @@ use function getmypid;
use function getopt;
use function gettype;
use function implode;
use function ini_get;
use function ini_set;
use function is_array;
use function is_bool;
@ -1212,9 +1212,9 @@ class Server{
}
$path = $this->getDataPath() . "worlds/" . $name . "/";
if(!($this->getLevelByName($name) instanceof Level)){
return is_dir($path) and !empty(array_filter(scandir($path, SCANDIR_SORT_NONE), function($v){
return is_dir($path) and count(array_filter(scandir($path, SCANDIR_SORT_NONE), function($v){
return $v !== ".." and $v !== ".";
}));
})) > 0;
}
return true;
@ -1928,14 +1928,14 @@ class Server{
* @param bool $immediate
*/
public function batchPackets(array $players, array $packets, bool $forceSync = false, bool $immediate = false){
if(empty($packets)){
if(count($packets) === 0){
throw new \InvalidArgumentException("Cannot send empty batch");
}
Timings::$playerNetworkTimer->startTiming();
$targets = array_filter($players, function(Player $player) : bool{ return $player->isConnected(); });
if(!empty($targets)){
if(count($targets) > 0){
$pk = new BatchPacket();
foreach($packets as $p){
@ -2246,6 +2246,9 @@ class Server{
$this->crashDump();
}
/**
* @return void
*/
public function crashDump(){
while(@ob_end_flush()){}
if(!$this->isRunning){

View File

@ -19,6 +19,8 @@
*
*/
declare(strict_types=1);
namespace pocketmine;
// composer autoload doesn't use require_once and also pthreads can inherit things
@ -30,6 +32,6 @@ const _VERSION_INFO_INCLUDED = true;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.10.1";
const BASE_VERSION = "3.11.3";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;

View File

@ -40,6 +40,7 @@ use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
use pocketmine\Player;
use pocketmine\plugin\Plugin;
use function array_merge;
use function count;
use function get_class;
use const PHP_INT_MAX;
@ -727,7 +728,7 @@ class Block extends Position implements BlockIds, Metadatable{
*/
public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{
$bbs = $this->getCollisionBoxes();
if(empty($bbs)){
if(count($bbs) === 0){
return null;
}

View File

@ -84,6 +84,9 @@ class EnderChest extends Chest{
$enderChest = $t;
}else{
$enderChest = Tile::createTile(Tile::ENDER_CHEST, $this->getLevel(), TileEnderChest::createNBT($this));
if(!($enderChest instanceof TileEnderChest)){
return true;
}
}
if(!$this->getSide(Vector3::SIDE_UP)->isTransparent()){

View File

@ -25,6 +25,7 @@ namespace pocketmine\block;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use function count;
abstract class Fence extends Transparent{
@ -85,7 +86,7 @@ abstract class Fence extends Transparent{
);
}
if(empty($bbs)){
if(count($bbs) === 0){
//centre post AABB (only needed if not connected on any axis - other BBs overlapping will do this if any connections are made)
return [
new AxisAlignedBB(

View File

@ -25,6 +25,7 @@ namespace pocketmine\block;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use function count;
abstract class Thin extends Transparent{
@ -77,7 +78,7 @@ abstract class Thin extends Transparent{
);
}
if(empty($bbs)){
if(count($bbs) === 0){
//centre post AABB (only needed if not connected on any axis - other BBs overlapping will do this if any connections are made)
return [
new AxisAlignedBB(

View File

@ -328,12 +328,12 @@ class SimpleCommandMap implements CommandMap{
}
}
if(!empty($recursive)){
if(count($recursive) > 0){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.recursive", [$alias, implode(", ", $recursive)]));
continue;
}
if(!empty($bad)){
if(count($bad) > 0){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.notFound", [$alias, implode(", ", $bad)]));
continue;
}

View File

@ -195,7 +195,8 @@ class Effect{
}
/**
* Returns the default duration this effect will apply for if a duration is not specified.
* Returns the default duration (in ticks) this effect will apply for if a duration is not specified.
*
* @return int
*/
public function getDefaultDuration() : int{

View File

@ -75,6 +75,8 @@ class EffectInstance{
}
/**
* Returns the number of ticks remaining until the effect expires.
*
* @return int
*/
public function getDuration() : int{
@ -82,6 +84,8 @@ class EffectInstance{
}
/**
* Sets the number of ticks remaining until the effect expires.
*
* @param int $duration
*
* @throws \InvalidArgumentException

View File

@ -463,12 +463,12 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
/** @var Block[]|null */
protected $blocksAround = null;
/** @var float|null */
public $lastX = null;
/** @var float|null */
public $lastY = null;
/** @var float|null */
public $lastZ = null;
/** @var float */
public $lastX;
/** @var float */
public $lastY;
/** @var float */
public $lastZ;
/** @var Vector3 */
protected $motion;
@ -1102,7 +1102,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->justCreated = false;
$changedProperties = $this->propertyManager->getDirty();
if(!empty($changedProperties)){
if(count($changedProperties) > 0){
$this->sendData($this->hasSpawned, $changedProperties);
$this->propertyManager->clearDirtyProperties();
}

View File

@ -61,6 +61,7 @@ use function array_merge;
use function array_rand;
use function array_values;
use function ceil;
use function count;
use function in_array;
use function max;
use function min;
@ -554,7 +555,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
}
}
if(!empty($equipment)){
if(count($equipment) > 0){
$repairItem = $equipment[$k = array_rand($equipment)];
if($repairItem->getDamage() > 0){
$repairAmount = min($repairItem->getDamage(), $xpValue * 2);

View File

@ -263,7 +263,7 @@ abstract class Living extends Entity implements Damageable{
* @return bool
*/
public function hasEffects() : bool{
return !empty($this->effects);
return count($this->effects) > 0;
}
/**
@ -332,7 +332,7 @@ abstract class Living extends Entity implements Damageable{
}
}
if(!empty($colors)){
if(count($colors) > 0){
$this->propertyManager->setInt(Entity::DATA_POTION_COLOR, Color::mix(...$colors)->toARGB());
$this->propertyManager->setByte(Entity::DATA_POTION_AMBIENT, $ambient ? 1 : 0);
}else{
@ -713,7 +713,7 @@ abstract class Living extends Entity implements Damageable{
}
}
return !empty($this->effects);
return count($this->effects) > 0;
}
/**
@ -893,7 +893,7 @@ abstract class Living extends Entity implements Damageable{
*/
public function getTargetBlock(int $maxDistance, array $transparent = []) : ?Block{
$line = $this->getLineOfSight($maxDistance, 1, $transparent);
if(!empty($line)){
if(count($line) > 0){
return array_shift($line);
}

View File

@ -34,7 +34,6 @@ use pocketmine\level\Position;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\IntTag;
use function abs;
use function floor;
use function get_class;
class FallingBlock extends Entity{

View File

@ -34,6 +34,7 @@ use pocketmine\item\Potion;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\utils\Color;
use function count;
use function round;
use function sqrt;
@ -63,7 +64,7 @@ class SplashPotion extends Throwable{
$effects = $this->getPotionEffects();
$hasEffects = true;
if(empty($effects)){
if(count($effects) === 0){
$colors = [
new Color(0x38, 0x5d, 0xc6) //Default colour for splash water bottle and similar with no effects.
];

View File

@ -46,7 +46,7 @@ abstract class BaseInventory implements Inventory{
protected $name;
/** @var string */
protected $title;
/** @var \SplFixedArray|Item[] */
/** @var \SplFixedArray|(Item|null)[] */
protected $slots;
/** @var Player[] */
protected $viewers = [];

View File

@ -66,19 +66,23 @@ class PlayerInventory extends BaseInventory{
* @return bool if the equipment change was successful, false if not.
*/
public function equipItem(int $hotbarSlot) : bool{
$holder = $this->getHolder();
if(!$this->isHotbarSlot($hotbarSlot)){
$this->sendContents($this->getHolder());
if($holder instanceof Player){
$this->sendContents($holder);
}
return false;
}
$ev = new PlayerItemHeldEvent($this->getHolder(), $this->getItem($hotbarSlot), $hotbarSlot);
$ev->call();
if($holder instanceof Player){
$ev = new PlayerItemHeldEvent($holder, $this->getItem($hotbarSlot), $hotbarSlot);
$ev->call();
if($ev->isCancelled()){
$this->sendHeldItem($this->getHolder());
return false;
if($ev->isCancelled()){
$this->sendHeldItem($holder);
return false;
}
}
$this->setHeldItemIndex($hotbarSlot, false);
return true;

View File

@ -135,6 +135,6 @@ class ShapelessRecipe implements CraftingRecipe{
return false; //failed to match the needed item to a given item
}
return empty($input); //crafting grid should be empty apart from the given ingredient stacks
return count($input) === 0; //crafting grid should be empty apart from the given ingredient stacks
}
}

View File

@ -68,14 +68,14 @@ class CraftingTransaction extends InventoryTransaction{
* @throws TransactionValidationException
*/
protected function matchRecipeItems(array $txItems, array $recipeItems, bool $wildcards, int $iterations = 0) : int{
if(empty($recipeItems)){
if(count($recipeItems) === 0){
throw new TransactionValidationException("No recipe items given");
}
if(empty($txItems)){
if(count($txItems) === 0){
throw new TransactionValidationException("No transaction items given");
}
while(!empty($recipeItems)){
while(count($recipeItems) > 0){
/** @var Item $recipeItem */
$recipeItem = array_pop($recipeItems);
$needCount = $recipeItem->getCount();
@ -114,7 +114,7 @@ class CraftingTransaction extends InventoryTransaction{
if($iterations < 1){
throw new TransactionValidationException("Tried to craft zero times");
}
if(!empty($txItems)){
if(count($txItems) > 0){
//all items should be destroyed in this process
throw new TransactionValidationException("Expected 0 ingredients left over, have " . count($txItems));
}

View File

@ -237,13 +237,13 @@ class InventoryTransaction{
* @return null|Item
*/
protected function findResultItem(Item $needOrigin, array $possibleActions) : ?Item{
assert(!empty($possibleActions));
assert(count($possibleActions) > 0);
foreach($possibleActions as $i => $action){
if($action->getSourceItem()->equalsExact($needOrigin)){
$newList = $possibleActions;
unset($newList[$i]);
if(empty($newList)){
if(count($newList) === 0){
return $action->getTargetItem();
}
$result = $this->findResultItem($action->getTargetItem(), $newList);

View File

@ -31,6 +31,7 @@ use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\Player;
use function array_rand;
use function count;
class PaintingItem extends Item{
public function __construct(int $meta = 0){
@ -66,7 +67,7 @@ class PaintingItem extends Item{
}
}
if(empty($motives)){ //No space available
if(count($motives) === 0){ //No space available
return false;
}

View File

@ -155,6 +155,9 @@ class Enchantment{
* @return Enchantment|null
*/
public static function getEnchantment(int $id) : ?Enchantment{
if($id < 0 or $id >= self::$enchantments->getSize()){
return null;
}
return self::$enchantments[$id] ?? null;
}

View File

@ -115,9 +115,9 @@ class BaseLang{
}
/**
* @param string $str
* @param string[] $params
* @param string|null $onlyPrefix
* @param string $str
* @param (float|int|string)[] $params
* @param string|null $onlyPrefix
*
* @return string
*/

View File

@ -31,8 +31,8 @@ class TranslationContainer extends TextContainer{
protected $params = [];
/**
* @param string $text
* @param string[] $params
* @param string $text
* @param (float|int|string)[] $params
*/
public function __construct(string $text, array $params = []){
parent::__construct($text);

View File

@ -123,6 +123,10 @@ class Explosion{
$vBlock->y = $pointerY >= $y ? $y : $y - 1;
$vBlock->z = $pointerZ >= $z ? $z : $z - 1;
$pointerX += $vector->x;
$pointerY += $vector->y;
$pointerZ += $vector->z;
if(!$this->subChunkHandler->moveTo($vBlock->x, $vBlock->y, $vBlock->z)){
continue;
}
@ -137,10 +141,6 @@ class Explosion{
}
}
}
$pointerX += $vector->x;
$pointerY += $vector->y;
$pointerZ += $vector->z;
}
}
}

View File

@ -503,7 +503,7 @@ class Level implements ChunkManager, Metadatable{
if(!is_array($pk)){
$pk = [$pk];
}
if(!empty($pk)){
if(count($pk) > 0){
if($players === null){
foreach($pk as $e){
$this->broadcastPacketToViewers($sound, $e);
@ -519,7 +519,7 @@ class Level implements ChunkManager, Metadatable{
if(!is_array($pk)){
$pk = [$pk];
}
if(!empty($pk)){
if(count($pk) > 0){
if($players === null){
foreach($pk as $e){
$this->broadcastPacketToViewers($particle, $e);
@ -881,7 +881,7 @@ class Level implements ChunkManager, Metadatable{
if(count($this->changedBlocks) > 0){
if(count($this->players) > 0){
foreach($this->changedBlocks as $index => $blocks){
if(empty($blocks)){ //blocks can be set normally and then later re-set with direct send
if(count($blocks) === 0){ //blocks can be set normally and then later re-set with direct send
continue;
}
unset($this->chunkCache[$index]);
@ -909,8 +909,8 @@ class Level implements ChunkManager, Metadatable{
$this->checkSleep();
}
if(!empty($this->globalPackets)){
if(!empty($this->players)){
if(count($this->globalPackets) > 0){
if(count($this->players) > 0){
$this->server->batchPackets($this->players, $this->globalPackets);
}
$this->globalPackets = [];
@ -1845,7 +1845,7 @@ class Level implements ChunkManager, Metadatable{
$item->onDestroyBlock($target);
if(!empty($drops)){
if(count($drops) > 0){
$dropPos = $target->add(0.5, 0.5, 0.5);
foreach($drops as $drop){
if(!$drop->isNull()){
@ -1949,7 +1949,7 @@ class Level implements ChunkManager, Metadatable{
if($hand->isSolid()){
foreach($hand->getCollisionBoxes() as $collisionBox){
if(!empty($this->getCollidingEntities($collisionBox))){
if(count($this->getCollidingEntities($collisionBox)) > 0){
return false; //Entity in block
}
@ -2442,7 +2442,7 @@ class Level implements ChunkManager, Metadatable{
* @param int $x
* @param int $z
*
* @return Chunk[]
* @return (Chunk|null)[]
*/
public function getAdjacentChunks(int $x, int $z) : array{
$result = [];

View File

@ -33,12 +33,12 @@ class Location extends Position{
public $pitch;
/**
* @param int $x
* @param int $y
* @param int $z
* @param float $yaw
* @param float $pitch
* @param Level $level
* @param float|int $x
* @param float|int $y
* @param float|int $z
* @param float $yaw
* @param float $pitch
* @param Level $level
*/
public function __construct($x = 0, $y = 0, $z = 0, $yaw = 0.0, $pitch = 0.0, Level $level = null){
$this->yaw = $yaw;

View File

@ -33,10 +33,10 @@ class Position extends Vector3{
public $level = null;
/**
* @param int $x
* @param int $y
* @param int $z
* @param Level $level
* @param float|int $x
* @param float|int $y
* @param float|int $z
* @param Level $level
*/
public function __construct($x = 0, $y = 0, $z = 0, Level $level = null){
parent::__construct($x, $y, $z);

View File

@ -71,7 +71,7 @@ interface LevelProvider{
* @param string $name
* @param int $seed
* @param string $generator
* @param array[] $options
* @param array $options
*/
public static function generate(string $path, string $name, int $seed, string $generator, array $options = []);

View File

@ -26,6 +26,7 @@ namespace pocketmine\level\format\io\leveldb;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\io\BaseLevelProvider;
use pocketmine\level\format\io\ChunkUtils;
use pocketmine\level\format\io\exception\CorruptedChunkException;
use pocketmine\level\format\io\exception\UnsupportedChunkFormatException;
use pocketmine\level\format\SubChunk;
use pocketmine\level\generator\Flat;
@ -33,12 +34,18 @@ use pocketmine\level\generator\GeneratorManager;
use pocketmine\level\Level;
use pocketmine\level\LevelException;
use pocketmine\nbt\LittleEndianNBTStream;
use pocketmine\nbt\tag\{ByteTag, CompoundTag, FloatTag, IntTag, LongTag, StringTag};
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\LongTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\utils\Binary;
use pocketmine\utils\BinaryStream;
use function array_values;
use function chr;
use function count;
use function defined;
use function explode;
use function extension_loaded;
@ -409,24 +416,27 @@ class LevelDB extends BaseLevelProvider{
/** @var CompoundTag[] $entities */
$entities = [];
if(($entityData = $this->db->get($index . self::TAG_ENTITY)) !== false and $entityData !== ""){
$entities = $nbt->read($entityData, true);
if(!is_array($entities)){
$entities = [$entities];
}
}
/** @var CompoundTag $entityNBT */
foreach($entities as $entityNBT){
if($entityNBT->hasTag("id", IntTag::class)){
$entityNBT->setInt("id", $entityNBT->getInt("id") & 0xff); //remove type flags - TODO: use these instead of removing them)
$entityTags = $nbt->read($entityData, true);
foreach((is_array($entityTags) ? $entityTags : [$entityTags]) as $entityTag){
if(!($entityTag instanceof CompoundTag)){
throw new CorruptedChunkException("Entity root tag should be TAG_Compound");
}
if($entityTag->hasTag("id", IntTag::class)){
$entityTag->setInt("id", $entityTag->getInt("id") & 0xff); //remove type flags - TODO: use these instead of removing them)
}
$entities[] = $entityTag;
}
}
/** @var CompoundTag[] $tiles */
$tiles = [];
if(($tileData = $this->db->get($index . self::TAG_BLOCK_ENTITY)) !== false and $tileData !== ""){
$tiles = $nbt->read($tileData, true);
if(!is_array($tiles)){
$tiles = [$tiles];
$tileTags = $nbt->read($tileData, true);
foreach((is_array($tileTags) ? $tileTags : [$tileTags]) as $tileTag){
if(!($tileTag instanceof CompoundTag)){
throw new CorruptedChunkException("Tile root tag should be TAG_Compound");
}
$tiles[] = $tileTag;
}
}
@ -510,7 +520,7 @@ class LevelDB extends BaseLevelProvider{
* @param string $index
*/
private function writeTags(array $targets, string $index){
if(!empty($targets)){
if(count($targets) > 0){
$nbt = new LittleEndianNBTStream();
$this->db->put($index, $nbt->write($targets));
}else{

View File

@ -51,7 +51,7 @@ class Anvil extends McRegion{
$subChunks = [];
foreach($chunk->getSubChunks() as $y => $subChunk){
if($subChunk->isEmpty()){
if(!($subChunk instanceof SubChunk) or $subChunk->isEmpty()){
continue;
}
@ -122,8 +122,8 @@ class Anvil extends McRegion{
$chunk->getInt("xPos"),
$chunk->getInt("zPos"),
$subChunks,
$chunk->hasTag("Entities", ListTag::class) ? $chunk->getListTag("Entities")->getValue() : [],
$chunk->hasTag("TileEntities", ListTag::class) ? $chunk->getListTag("TileEntities")->getValue() : [],
$chunk->hasTag("Entities", ListTag::class) ? self::getCompoundList("Entities", $chunk->getListTag("Entities")) : [],
$chunk->hasTag("TileEntities", ListTag::class) ? self::getCompoundList("TileEntities", $chunk->getListTag("TileEntities")) : [],
$biomeIds,
$chunk->getIntArray("HeightMap", [])
);

View File

@ -194,8 +194,8 @@ class McRegion extends BaseLevelProvider{
$chunk->getInt("xPos"),
$chunk->getInt("zPos"),
$subChunks,
$chunk->hasTag("Entities", ListTag::class) ? $chunk->getListTag("Entities")->getValue() : [],
$chunk->hasTag("TileEntities", ListTag::class) ? $chunk->getListTag("TileEntities")->getValue() : [],
$chunk->hasTag("Entities", ListTag::class) ? self::getCompoundList("Entities", $chunk->getListTag("Entities")) : [],
$chunk->hasTag("TileEntities", ListTag::class) ? self::getCompoundList("TileEntities", $chunk->getListTag("TileEntities")) : [],
$biomeIds,
$heightMap
);
@ -205,6 +205,31 @@ class McRegion extends BaseLevelProvider{
return $result;
}
/**
* @param string $context
* @param ListTag $list
*
* @return CompoundTag[]
* @throws CorruptedChunkException
*/
protected static function getCompoundList(string $context, ListTag $list) : array{
if($list->count() === 0){ //empty lists might have wrong types, we don't care
return [];
}
if($list->getTagType() !== NBT::TAG_Compound){
throw new CorruptedChunkException("Expected TAG_List<TAG_Compound> for '$context'");
}
$result = [];
foreach($list as $tag){
if(!($tag instanceof CompoundTag)){
//this should never happen, but it's still possible due to lack of native type safety
throw new CorruptedChunkException("Expected TAG_List<TAG_Compound> for '$context'");
}
$result[] = $tag;
}
return $result;
}
public static function getProviderName() : string{
return "mcregion";
}

View File

@ -56,11 +56,9 @@ class PopulationTask extends AsyncTask{
}
public function onRun(){
/** @var SimpleChunkManager $manager */
$manager = $this->getFromThreadStore("generation.level{$this->levelId}.manager");
/** @var Generator $generator */
$generator = $this->getFromThreadStore("generation.level{$this->levelId}.generator");
if($manager === null or $generator === null){
if(!($manager instanceof SimpleChunkManager) or !($generator instanceof Generator)){
$this->state = false;
return;
}

View File

@ -29,7 +29,7 @@ use pocketmine\network\mcpe\protocol\DataPacket;
abstract class Particle extends Vector3{
public const TYPE_BUBBLE = 1;
//2 same as 1
public const TYPE_BUBBLE_MANUAL = 2;
public const TYPE_CRITICAL = 3;
public const TYPE_BLOCK_FORCE_FIELD = 4;
public const TYPE_SMOKE = 5;
@ -40,56 +40,61 @@ abstract class Particle extends Vector3{
public const TYPE_LARGE_SMOKE = 10;
public const TYPE_REDSTONE = 11;
public const TYPE_RISING_RED_DUST = 12;
//62 same as 12
public const TYPE_ITEM_BREAK = 13;
public const TYPE_SNOWBALL_POOF = 14;
public const TYPE_HUGE_EXPLODE = 15;
//60 same as 15
public const TYPE_HUGE_EXPLODE_SEED = 16;
public const TYPE_MOB_FLAME = 17;
public const TYPE_HEART = 18;
public const TYPE_TERRAIN = 19;
public const TYPE_SUSPENDED_TOWN = 20, TYPE_TOWN_AURA = 20;
//61 same as 20
public const TYPE_PORTAL = 21;
//22 same as 21
public const TYPE_SPLASH = 23, TYPE_WATER_SPLASH = 23;
//24 same as 23
public const TYPE_WATER_SPLASH_MANUAL = 24;
public const TYPE_WATER_WAKE = 25;
public const TYPE_DRIP_WATER = 26;
public const TYPE_DRIP_LAVA = 27;
public const TYPE_FALLING_DUST = 28, TYPE_DUST = 28;
public const TYPE_MOB_SPELL = 29;
public const TYPE_MOB_SPELL_AMBIENT = 30;
public const TYPE_MOB_SPELL_INSTANTANEOUS = 31;
public const TYPE_INK = 32;
public const TYPE_SLIME = 33;
public const TYPE_RAIN_SPLASH = 34;
public const TYPE_VILLAGER_ANGRY = 35;
//59 same as 35
public const TYPE_VILLAGER_HAPPY = 36;
public const TYPE_ENCHANTMENT_TABLE = 37;
public const TYPE_TRACKING_EMITTER = 38;
public const TYPE_NOTE = 39;
public const TYPE_WITCH_SPELL = 40;
public const TYPE_CARROT = 41;
//42 unknown
public const TYPE_END_ROD = 43;
//58 same as 43
public const TYPE_DRAGONS_BREATH = 44;
public const TYPE_SPIT = 45;
public const TYPE_TOTEM = 46;
public const TYPE_FOOD = 47;
public const TYPE_FIREWORKS_STARTER = 48;
public const TYPE_FIREWORKS_SPARK = 49;
public const TYPE_FIREWORKS_OVERLAY = 50;
public const TYPE_BALLOON_GAS = 51;
public const TYPE_COLORED_FLAME = 52;
public const TYPE_SPARKLER = 53;
public const TYPE_CONDUIT = 54;
public const TYPE_BUBBLE_COLUMN_UP = 55;
public const TYPE_BUBBLE_COLUMN_DOWN = 56;
public const TYPE_SNEEZE = 57;
public const TYPE_DRIP_HONEY = 28;
public const TYPE_FALLING_DUST = 29, TYPE_DUST = 29;
public const TYPE_MOB_SPELL = 30;
public const TYPE_MOB_SPELL_AMBIENT = 31;
public const TYPE_MOB_SPELL_INSTANTANEOUS = 32;
public const TYPE_INK = 33;
public const TYPE_SLIME = 34;
public const TYPE_RAIN_SPLASH = 35;
public const TYPE_VILLAGER_ANGRY = 36;
public const TYPE_VILLAGER_HAPPY = 37;
public const TYPE_ENCHANTMENT_TABLE = 38;
public const TYPE_TRACKING_EMITTER = 39;
public const TYPE_NOTE = 40;
public const TYPE_WITCH_SPELL = 41;
public const TYPE_CARROT = 42;
public const TYPE_MOB_APPEARANCE = 43;
public const TYPE_END_ROD = 44;
public const TYPE_DRAGONS_BREATH = 45;
public const TYPE_SPIT = 46;
public const TYPE_TOTEM = 47;
public const TYPE_FOOD = 48;
public const TYPE_FIREWORKS_STARTER = 49;
public const TYPE_FIREWORKS_SPARK = 50;
public const TYPE_FIREWORKS_OVERLAY = 51;
public const TYPE_BALLOON_GAS = 52;
public const TYPE_COLORED_FLAME = 53;
public const TYPE_SPARKLER = 54;
public const TYPE_CONDUIT = 55;
public const TYPE_BUBBLE_COLUMN_UP = 56;
public const TYPE_BUBBLE_COLUMN_DOWN = 57;
public const TYPE_SNEEZE = 58;
public const TYPE_SHULKER_BULLET = 59;
public const TYPE_BLEACH = 60;
public const TYPE_DRAGON_DESTROY_BLOCK = 61;
public const TYPE_MYCELIUM_DUST = 62;
public const TYPE_FALLING_RED_DUST = 63;
public const TYPE_CAMPFIRE_SMOKE = 64;
public const TYPE_TALL_CAMPFIRE_SMOKE = 65;
public const TYPE_DRAGON_BREATH_FIRE = 66;
public const TYPE_DRAGON_BREATH_TRAIL = 67;
/**
* @return DataPacket|DataPacket[]

View File

@ -35,7 +35,7 @@ class CompressBatchedTask extends AsyncTask{
/**
* @param BatchPacket $batch
* @param string[] $targets
* @param Player[] $targets
*/
public function __construct(BatchPacket $batch, array $targets){
$this->data = $batch->payload;

View File

@ -38,9 +38,9 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\network\mcpe\protocol\types\CommandOriginData;
use pocketmine\network\mcpe\protocol\types\EntityLink;
use pocketmine\network\mcpe\protocol\types\SkinAnimation;
use pocketmine\network\mcpe\protocol\types\SkinData;
use pocketmine\network\mcpe\protocol\types\SkinImage;
use pocketmine\network\mcpe\protocol\types\SkinAnimation;
use pocketmine\network\mcpe\protocol\types\StructureSettings;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\UUID;

View File

@ -67,7 +67,6 @@ use pocketmine\network\mcpe\protocol\ShowCreditsPacket;
use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket;
use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\network\mcpe\protocol\types\SkinAdapterSingleton;
use pocketmine\network\mcpe\protocol\types\SkinData;
use pocketmine\Player;
use pocketmine\Server;
use pocketmine\timings\Timings;

View File

@ -37,6 +37,7 @@ use function openssl_verify;
use function ord;
use function str_split;
use function strlen;
use function strtr;
use function time;
use function wordwrap;
use const OPENSSL_ALGO_SHA384;

View File

@ -26,7 +26,6 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
use function base64_decode;
use function file_get_contents;
class AvailableActorIdentifiersPacket extends DataPacket{

View File

@ -31,6 +31,7 @@ use pocketmine\network\mcpe\protocol\types\CommandEnum;
use pocketmine\network\mcpe\protocol\types\CommandEnumConstraint;
use pocketmine\network\mcpe\protocol\types\CommandParameter;
use pocketmine\utils\BinaryDataException;
use function array_search;
use function count;
use function dechex;

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class CompletedUsingItemPacket extends DataPacket{

View File

@ -85,7 +85,7 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
$result->inputMode = $inputMode;
$result->playMode = $playMode;
if($vrGazeDirection !== null){
$this->vrGazeDirection = $vrGazeDirection->asVector3();
$result->vrGazeDirection = $vrGazeDirection->asVector3();
}
return $result;
}

View File

@ -27,7 +27,6 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\SkinData;
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
use function count;

View File

@ -39,15 +39,15 @@ interface ProtocolInfo{
/**
* Actual Minecraft: PE protocol version
*/
public const CURRENT_PROTOCOL = 388;
public const CURRENT_PROTOCOL = 389;
/**
* Current Minecraft PE version reported by the server. This is usually the earliest currently supported version.
*/
public const MINECRAFT_VERSION = 'v1.13.0';
public const MINECRAFT_VERSION = 'v1.14.0';
/**
* Version number sent to clients in ping responses.
*/
public const MINECRAFT_VERSION_NETWORK = '1.13.0';
public const MINECRAFT_VERSION_NETWORK = '1.14.0';
public const LOGIN_PACKET = 0x01;
public const PLAY_STATUS_PACKET = 0x02;

View File

@ -28,7 +28,6 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\network\mcpe\NetworkSession;
use function strlen;
class ResourcePackChunkDataPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::RESOURCE_PACK_CHUNK_DATA_PACKET;

View File

@ -28,9 +28,7 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\math\Vector3;
use pocketmine\nbt\NetworkLittleEndianNBTStream;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\NetworkBinaryStream;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;

View File

@ -27,14 +27,16 @@ use pocketmine\entity\Skin;
use function is_array;
use function is_string;
use function json_decode;
use function json_encode;
use function random_bytes;
use function str_repeat;
class LegacySkinAdapter implements SkinAdapter{
public function toSkinData(Skin $skin) : SkinData{
$capeData = new SkinImage(32, 64, $skin->getCapeData());
if($skin->getCapeData() === ""){
$capeData = new SkinImage(0, 0, $skin->getCapeData());
}
$capeData = $skin->getCapeData();
$capeImage = $capeData === "" ? new SkinImage(0, 0, "") : new SkinImage(32, 64, $capeData);
$geometryName = $skin->getGeometryName();
if($geometryName === ""){
$geometryName = "geometry.humanoid.custom";
@ -43,13 +45,18 @@ class LegacySkinAdapter implements SkinAdapter{
$skin->getSkinId(),
json_encode(["geometry" => ["default" => $geometryName]]),
SkinImage::fromLegacy($skin->getSkinData()), [],
$capeData,
$capeImage,
$skin->getGeometryData()
);
}
public function fromSkinData(SkinData $data) : Skin{
$capeData = $data->getCapeImage()->getData();
if($data->isPersona()){
return new Skin("Standard_Custom", str_repeat(random_bytes(3) . "\xff", 2048));
}
$capeData = $data->isPersonaCapeOnClassic() ? "" : $data->getCapeImage()->getData();
$geometryName = "";
$resourcePatch = json_decode($data->getResourcePatch(), true);
if(is_array($resourcePatch["geometry"]) && is_string($resourcePatch["geometry"]["default"])){
@ -57,11 +64,7 @@ class LegacySkinAdapter implements SkinAdapter{
}else{
//TODO: Kick for invalid skin
}
if($data->isPersona()){
return new Skin("Standard_Custom", str_repeat(random_bytes(3) . "\xff", 2048));
}elseif($data->isPersonaCapeOnClassic()){
$capeData = "";
}
return new Skin($data->getSkinId(), $data->getSkinImage()->getData(), $capeData, $geometryName, $data->getGeometryData());
}
}

View File

@ -48,18 +48,12 @@ class NetworkInventoryAction{
*
* Expect these to change in the future.
*/
public const SOURCE_TYPE_CRAFTING_ADD_INGREDIENT = -2;
public const SOURCE_TYPE_CRAFTING_REMOVE_INGREDIENT = -3;
public const SOURCE_TYPE_CRAFTING_RESULT = -4;
public const SOURCE_TYPE_CRAFTING_USE_INGREDIENT = -5;
public const SOURCE_TYPE_ANVIL_INPUT = -10;
public const SOURCE_TYPE_ANVIL_MATERIAL = -11;
public const SOURCE_TYPE_ANVIL_RESULT = -12;
public const SOURCE_TYPE_ANVIL_OUTPUT = -13;
public const SOURCE_TYPE_ENCHANT_INPUT = -15;
public const SOURCE_TYPE_ENCHANT_MATERIAL = -16;
public const SOURCE_TYPE_ENCHANT_OUTPUT = -17;
public const SOURCE_TYPE_TRADING_INPUT_1 = -20;
@ -69,9 +63,6 @@ class NetworkInventoryAction{
public const SOURCE_TYPE_BEACON = -24;
/** Any client-side window dropping its contents when the player closes it */
public const SOURCE_TYPE_CONTAINER_DROP_CONTENTS = -100;
public const ACTION_MAGIC_SLOT_CREATIVE_DELETE_ITEM = 0;
public const ACTION_MAGIC_SLOT_CREATIVE_CREATE_ITEM = 1;
@ -214,10 +205,6 @@ class NetworkInventoryAction{
case self::SOURCE_TODO:
//These types need special handling.
switch($this->windowId){
case self::SOURCE_TYPE_CRAFTING_ADD_INGREDIENT:
case self::SOURCE_TYPE_CRAFTING_REMOVE_INGREDIENT:
case self::SOURCE_TYPE_CONTAINER_DROP_CONTENTS: //TODO: this type applies to all fake windows, not just crafting
return new SlotChangeAction($player->getCraftingGrid(), $this->inventorySlot, $this->oldItem, $this->newItem);
case self::SOURCE_TYPE_CRAFTING_RESULT:
case self::SOURCE_TYPE_CRAFTING_USE_INGREDIENT:
return null;

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types;
use pocketmine\entity\Skin;
use pocketmine\utils\UUID;
class PlayerListEntry{

View File

@ -28,8 +28,6 @@ use pocketmine\nbt\NBT;
use pocketmine\nbt\NetworkLittleEndianNBTStream;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\utils\BinaryDataException;
use function file_get_contents;
use function getmypid;
use function json_decode;

View File

@ -25,7 +25,7 @@ namespace pocketmine\network\mcpe\protocol\types;
/**
* Accessor for SkinAdapter
*/
*/
class SkinAdapterSingleton{
/** @var SkinAdapter|null */
private static $skinAdapter = null;

View File

@ -22,6 +22,7 @@
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types;
use function strlen;
class SkinImage{
@ -33,6 +34,9 @@ class SkinImage{
private $data;
public function __construct(int $height, int $width, string $data){
if(($expected = $height * $width * 4) !== ($actual = strlen($data))){
throw new \InvalidArgumentException("Data should be exactly $expected bytes, got $actual bytes");
}
$this->height = $height;
$this->width = $width;
$this->data = $data;

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\permission;
use function array_shift;
use function count;
use function explode;
use function implode;
use function strlen;
@ -136,10 +137,10 @@ class BanEntry{
/**
* @param string $date
*
* @return \DateTime|null
* @return \DateTime
* @throws \RuntimeException
*/
private static function parseDate(string $date) : ?\DateTime{
private static function parseDate(string $date) : \DateTime{
$datetime = \DateTime::createFromFormat(self::$format, $date);
if(!($datetime instanceof \DateTime)){
throw new \RuntimeException("Error parsing date for BanEntry: " . implode(", ", \DateTime::getLastErrors()["errors"]));
@ -161,17 +162,17 @@ class BanEntry{
$str = explode("|", trim($str));
$entry = new BanEntry(trim(array_shift($str)));
do{
if(empty($str)){
if(count($str) === 0){
break;
}
$entry->setCreated(self::parseDate(array_shift($str)));
if(empty($str)){
if(count($str) === 0){
break;
}
$entry->setSource(trim(array_shift($str)));
if(empty($str)){
if(count($str) === 0){
break;
}
@ -179,7 +180,7 @@ class BanEntry{
if($expire !== "" and strtolower($expire) !== "forever"){
$entry->setExpires(self::parseDate($expire));
}
if(empty($str)){
if(count($str) === 0){
break;
}

View File

@ -168,7 +168,7 @@ class PermissionManager{
public function unsubscribeFromAllPermissions(Permissible $permissible) : void{
foreach($this->permSubs as $permission => &$subs){
unset($subs[spl_object_hash($permissible)]);
if(empty($subs)){
if(count($subs) === 0){
unset($this->permSubs[$permission]);
}
}

View File

@ -38,6 +38,7 @@ use function strlen;
use function strtoupper;
use function substr;
use function version_compare;
use function yaml_parse;
class PluginDescription{
private $map;

View File

@ -40,6 +40,7 @@ use pocketmine\timings\TimingsHandler;
use pocketmine\utils\Utils;
use function array_intersect;
use function array_map;
use function array_merge;
use function array_pad;
use function class_exists;
use function count;

View File

@ -34,6 +34,7 @@ use function filesize;
use function fopen;
use function fread;
use function fseek;
use function gettype;
use function hash_file;
use function implode;

View File

@ -38,6 +38,7 @@ use pocketmine\level\Level;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\ContainerSetDataPacket;
use function ceil;
use function count;
use function max;
class Furnace extends Spawnable implements InventoryHolder, Container, Nameable{
@ -232,7 +233,7 @@ class Furnace extends Spawnable implements InventoryHolder, Container, Nameable{
$packets[] = $pk;
}
if(!empty($packets)){
if(count($packets) > 0){
foreach($this->getInventory()->getViewers() as $player){
$windowId = $player->getWindowId($this->getInventory());
if($windowId > 0){

View File

@ -31,6 +31,8 @@ use pocketmine\tile\Tile;
use function dechex;
abstract class Timings{
/** @var bool */
private static $initialized = false;
/** @var TimingsHandler */
public static $fullTickTimer;
@ -104,9 +106,10 @@ abstract class Timings{
public static $pluginTaskTimingMap = [];
public static function init(){
if(self::$serverTickTimer instanceof TimingsHandler){
if(self::$initialized){
return;
}
self::$initialized = true;
self::$fullTickTimer = new TimingsHandler("Full Server Tick");
self::$serverTickTimer = new TimingsHandler("** Full Server Tick", self::$fullTickTimer);

View File

@ -158,29 +158,45 @@ class TimingsHandler{
}
public function startTiming(){
if(self::$enabled and ++$this->timingDepth === 1){
$this->start = microtime(true);
if($this->parent !== null and ++$this->parent->timingDepth === 1){
$this->parent->start = $this->start;
if(self::$enabled){
$this->internalStartTiming(microtime(true));
}
}
private function internalStartTiming(float $now) : void{
if(++$this->timingDepth === 1){
$this->start = $now;
if($this->parent !== null){
$this->parent->internalStartTiming($now);
}
}
}
public function stopTiming(){
if(self::$enabled){
if(--$this->timingDepth !== 0 or $this->start === 0){
return;
}
$this->internalStopTiming(microtime(true));
}
}
$diff = microtime(true) - $this->start;
$this->totalTime += $diff;
$this->curTickTotal += $diff;
++$this->curCount;
++$this->count;
$this->start = 0;
if($this->parent !== null){
$this->parent->stopTiming();
}
private function internalStopTiming(float $now) : void{
if($this->timingDepth === 0){
//TODO: it would be nice to bail here, but since we'd have to track timing depth across resets
//and enable/disable, it would have a performance impact. Therefore, considering the limited
//usefulness of bailing here anyway, we don't currently bother.
return;
}
if(--$this->timingDepth !== 0 or $this->start == 0){
return;
}
$diff = $now - $this->start;
$this->totalTime += $diff;
$this->curTickTotal += $diff;
++$this->curCount;
++$this->count;
$this->start = 0;
if($this->parent !== null){
$this->parent->internalStopTiming($now);
}
}

View File

@ -420,7 +420,7 @@ class Config{
while(count($vars) > 0){
$nodeName = array_shift($vars);
if(isset($currentNode[$nodeName])){
if(empty($vars)){ //final node
if(count($vars) === 0){ //final node
unset($currentNode[$nodeName]);
}elseif(is_array($currentNode[$nodeName])){
$currentNode =& $currentNode[$nodeName];

View File

@ -0,0 +1,70 @@
<?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\utils;
use function str_repeat;
use function strlen;
use function trim;
final class Git{
private function __construct(){
//NOOP
}
/**
* Returns the git hash of the currently checked out head of the given repository, or null on failure.
*
* @param string $dir
* @param bool &$dirty Output, set to whether the repo has local changes
*
* @return string|null
*/
public static function getRepositoryState(string $dir, bool &$dirty) : ?string{
if(Utils::execute("git -C \"$dir\" rev-parse HEAD", $out) === 0 and $out !== false and strlen($out = trim($out)) === 40){
if(Utils::execute("git -C \"$dir\" diff --quiet") === 1 or Utils::execute("git -C \"$dir\" diff --cached --quiet") === 1){ //Locally-modified
$dirty = true;
}
return $out;
}
return null;
}
/**
* Infallible, returns a string representing git state, or a string of zeros on failure.
* If the repo is dirty, a "-dirty" suffix is added.
*
* @param string $dir
*
* @return string
*/
public static function getRepositoryStatePretty(string $dir) : string{
$dirty = false;
$detectedHash = self::getRepositoryState($dir, $dirty);
if($detectedHash !== null){
return $detectedHash . ($dirty ? "-dirty" : "");
}
return str_repeat("00", 20);
}
}

View File

@ -33,6 +33,7 @@ use function implode;
use function ini_get;
use function ini_set;
use function is_link;
use function is_string;
use function json_decode;
use function parse_ini_file;
use function preg_match;
@ -149,7 +150,7 @@ abstract class Timezone{
// RHEL / CentOS
if(file_exists('/etc/sysconfig/clock')){
$data = parse_ini_file('/etc/sysconfig/clock');
if(!empty($data['ZONE'])){
if(isset($data['ZONE']) and is_string($data['ZONE'])){
return trim($data['ZONE']);
}
}

View File

@ -58,6 +58,7 @@ use function is_object;
use function is_readable;
use function is_string;
use function json_decode;
use function ltrim;
use function memory_get_usage;
use function ob_end_clean;
use function ob_get_contents;
@ -72,6 +73,7 @@ use function preg_match_all;
use function preg_replace;
use function proc_close;
use function proc_open;
use function rtrim;
use function sha1;
use function spl_object_hash;
use function str_pad;
@ -82,6 +84,7 @@ use function stripos;
use function strlen;
use function strpos;
use function strtolower;
use function strtr;
use function substr;
use function sys_get_temp_dir;
use function trim;

View File

@ -33,6 +33,7 @@ use pocketmine\utils\Config;
use pocketmine\utils\Internet;
use pocketmine\utils\InternetException;
use function base64_encode;
use function count;
use function fgets;
use function random_bytes;
use function sleep;
@ -59,7 +60,7 @@ class SetupWizard{
$this->message(\pocketmine\NAME . " set-up wizard");
$langs = BaseLang::getLanguageList();
if(empty($langs)){
if(count($langs) === 0){
$this->error("No language files found, please use provided builds or clone the repository recursively.");
return false;
}

View File

@ -1,35 +0,0 @@
#!/bin/bash
PHP_BINARY="php"
DIR=""
FIND="find"
while getopts "p:d:f:" OPTION 2> /dev/null; do
case ${OPTION} in
p)
PHP_BINARY="$OPTARG"
;;
d)
DIR="$OPTARG"
;;
f)
FIND="$OPTARG"
;;
esac
done
if [ "$DIR" == "" ]; then
echo No directory specified
exit 1
fi
echo Running PHP lint scans on \"$DIR\"...
OUTPUT=`$FIND "$DIR" -name "*.php" -print0 | xargs -0 -n1 -P4 "$PHP_BINARY" -l`
if [ $? -ne 0 ]; then
echo $OUTPUT | grep -v "No syntax errors"
exit 1
fi
echo Lint scan completed successfully.

View File

@ -27,7 +27,28 @@ parameters:
path: ../../../src/pocketmine/MemoryManager.php
-
message: "#^Array \\(array\\) does not accept key int\\.$#"
message: "#^Comparison operation \"\\>\\=\" between 0 and 2 is always false\\.$#"
count: 1
path: ../../../src/pocketmine/plugin/PluginDescription.php
path: ../../../src/pocketmine/block/Liquid.php
-
#adjacentSources comparison FP
message: "#^If condition is always false\\.$#"
count: 1
path: ../../../src/pocketmine/block/Liquid.php
-
#$class::NETWORK_ID false positive
message: "#^Strict comparison using \\!\\=\\= between \\-1 and \\-1 will always evaluate to false\\.$#"
count: 1
path: ../../../src/pocketmine/entity/Entity.php
-
message: "#^Call to function assert\\(\\) with false and 'unknown hit type' will always evaluate to false\\.$#"
count: 1
path: ../../../src/pocketmine/entity/projectile/Projectile.php
-
message: "#^Strict comparison using \\=\\=\\= between int\\<1, max\\> and 0 will always evaluate to false\\.$#"
count: 1
path: ../../../src/pocketmine/utils/Config.php

View File

@ -0,0 +1,31 @@
parameters:
ignoreErrors:
-
#::add() / ::remove() thread parameter
message: "#^If condition is always true\\.$#"
count: 2
path: ../../../src/pocketmine/ThreadManager.php
-
#::get() tags parameter
message: "#^If condition is always false\\.$#"
count: 1
path: ../../../src/pocketmine/item/ItemFactory.php
-
#::get() tags parameter
message: "#^Strict comparison using \\!\\=\\= between null and null will always evaluate to false\\.$#"
count: 1
path: ../../../src/pocketmine/item/ItemFactory.php
-
#::get() tags parameter
message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#"
count: 1
path: ../../../src/pocketmine/item/ItemFactory.php
-
#->sendBlocks() blocks parameter
message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#"
count: 2
path: ../../../src/pocketmine/level/Level.php

View File

@ -14,14 +14,7 @@ while getopts "p:t:" OPTION 2> /dev/null; do
esac
done
./tests/lint.sh -p "$PHP_BINARY" -d ./src/pocketmine
if [ $? -ne 0 ]; then
echo Lint scan failed!
exit 1
fi
[ ! -f phpstan.phar ] && echo "Downloading PHPStan..." && curl -sSLO https://github.com/phpstan/phpstan/releases/download/0.12.0/phpstan.phar
[ ! -f phpstan.phar ] && echo "Downloading PHPStan..." && curl -sSLO https://github.com/phpstan/phpstan/releases/download/0.12.3/phpstan.phar
"$PHP_BINARY" phpstan.phar analyze --no-progress --memory-limit=2G || exit 1
echo "PHPStan scan succeeded"
@ -39,7 +32,7 @@ cd tests/plugins/PocketMine-DevTools
"$PHP_BINARY" -dphar.readonly=0 ./src/DevTools/ConsoleScript.php --make ./ --relative ./ --out ../../../DevTools.phar
cd ../../..
"$PHP_BINARY" -dphar.readonly=0 DevTools.phar --make src,vendor --relative ./ --entry src/pocketmine/PocketMine.php --out PocketMine-MP.phar
"$PHP_BINARY" -dphar.readonly=0 ./build/server-phar.php ./PocketMine-MP.phar
if [ -f PocketMine-MP.phar ]; then
echo Server phar created successfully.
else