Compare commits

..

90 Commits
3.5.9 ... 3.6.5

Author SHA1 Message Date
f2ff510597 Release 3.6.5 2019-03-10 11:49:06 +00:00
562179bdd6 Backport 58cafc853f: s/level/world (strings only)
we should look at doing this for code too, but for now I'm not planning to break everyone's plugins.
2019-03-10 11:35:46 +00:00
5c12bee874 Backport other part of 2bffd5cc1c: Add timer measurements for autosave 2019-03-10 11:20:51 +00:00
99606bbe23 beware possible API break 2019-03-09 19:33:46 +00:00
a1d50de12e OOF 2019-03-09 19:31:13 +00:00
4252c5914b Backport 93cd00ae8f: Remove dead settings from pocketmine.yml 2019-03-09 19:21:39 +00:00
0659d2fbef Backport 6bd43a8215: Firehose auto-tick-rate anti-feature, closes #2665 2019-03-09 19:20:53 +00:00
10612acace Partial backport of 2bffd5cc1c to 3.6 2019-03-09 19:10:09 +00:00
1d810f8aeb Backport c3e66e0adc to 3.6 2019-03-09 19:09:05 +00:00
414104851a LevelDB: Mark chunks as changed when upgraded from an older chunk format 2019-03-09 19:02:34 +00:00
c0bed03a2a Update PlayerRespawnEvent.php (#2797)
removed obsolete comment
2019-03-05 16:28:58 +00:00
d25c84acff Fixed RegionLoader corrupting location table when too-large chunks are discovered
this was making the location table point to an offset that did not yet exist, which caused the region header consistency check to discard the region as corrupted the next time it was loaded.
2019-03-05 12:09:27 +00:00
55994e08db RegionLoader: make some error messages more detailed 2019-03-05 11:18:02 +00:00
6f5d4d6b80 RegionLoader: fixed handling of invalid chunk coordinates 2019-03-05 10:52:36 +00:00
df1ef7fe0c Improve RegionLoader tests 2019-03-05 10:51:44 +00:00
20a25a69df Force emission of output buffer contents on crash 2019-03-04 12:01:30 +00:00
faca610594 NetworkBinaryStream: remove dead field 2019-03-03 12:38:03 +00:00
91603dc2d6 3.6.5 is next 2019-03-03 11:50:10 +00:00
af90e18b18 Release 3.6.4 2019-03-03 11:49:07 +00:00
ab5b4d112b BaseInventory: fixed items with userdata stacking with items without userdata 2019-03-03 11:12:36 +00:00
a30b1fb6d5 Inventory: Add failing test case for itemstack NBT duplication 2019-03-03 11:11:33 +00:00
20b4723728 Player: fixed held slot being out of sync after dying, closes #2788
it appears this premature optimization dates back to the days when PlayerHotbarPacket was not useless.
2019-02-27 09:26:56 +00:00
d1ced0ffc6 Player: fixed XP not dropping on death 2019-02-27 09:22:52 +00:00
2164dbae67 Fixed reloaded arrows not despawning, closes #2781 2019-02-26 19:58:21 +00:00
6c92a2e88b Ladder: be more strict about resetting fall distance
closes #2790
2019-02-26 19:54:57 +00:00
97deadc59f PackedIce: fixed dropping without silk touch, closes #2789 2019-02-26 19:48:18 +00:00
0c3b136a8d Player: fixed removeWindow() sometimes removing GUI / crashing clients 2019-02-24 12:14:19 +00:00
79b7e08e60 Silence NetworkStackLatencyPacket spam from dev builds 2019-02-23 11:03:50 +00:00
2540dacdd7 PluginManager: fixed suffix split handling 2019-02-23 10:51:06 +00:00
f1078e3909 3.6.4 is next 2019-02-22 18:07:48 +00:00
2f43b054de Release 3.6.3 2019-02-22 18:07:00 +00:00
23b5d64535 Merge branch '3.5' into 3.6 2019-02-22 18:03:51 +00:00
9afa0e5483 Release 3.5.13 2019-02-22 17:56:44 +00:00
4eaea54b0e TaskScheduler: fixed wrong typehints
These methods never return null.
2019-02-22 17:47:28 +00:00
6b51bf4a80 Merge branch '3.5' into 3.6 2019-02-18 13:11:16 +00:00
cba8d86c4f Fixed Fire Aspect not working on TNT 2019-02-18 13:11:08 +00:00
2e834c8f5c Merge branch '3.5' into 3.6 2019-02-17 16:10:47 -05:00
f9873e9108 ItemFrame: fixed hardness 2019-02-17 16:10:21 -05:00
074baf7e1c Merge branch '3.5' into 3.6 2019-02-17 17:08:15 +00:00
2e0dd574e0 Set default health attribute value when changing max health, closes #2771 2019-02-17 17:06:32 +00:00
e16d8e31af Merge branch '3.5' into 3.6 2019-02-17 11:33:46 +00:00
3c93a57397 Entity: add a hack to prevent client-side movement when entity is not moving
this fixes #2227.
2019-02-17 11:33:34 +00:00
e2e927b328 3.6.3 is next 2019-02-14 17:17:18 +00:00
a8dab25201 Release 3.6.2 2019-02-14 17:15:19 +00:00
3de2b7969e Merge branch '3.5' into 3.6 2019-02-14 17:14:59 +00:00
8f486ea65d 3.5.13 is next 2019-02-14 17:14:34 +00:00
6b971b1761 Release 3.5.12 2019-02-14 16:55:44 +00:00
6f36fa504b TextFormat: make clean() less confusing, deduplicate some regex 2019-02-14 16:52:39 +00:00
8e73842a93 Player: work around 1.9 command casing crash bug, closes #2761 2019-02-14 16:17:17 +00:00
e71e18fc88 Merge branch '3.5' into 3.6 2019-02-14 16:02:30 +00:00
e1bacb5c6d Human: fixed hunger underflow when value is a fraction, closes #2761 2019-02-14 16:01:25 +00:00
44697e784a Sign: add validity checks on text encoding, scrub invalid UTF-8 on load
this works around a bug where corrupted text on preexisting signs can mess up the client. This also prevents corrupted text getting onto signs in the future by having them scrubbed and validated before applying them.
2019-02-14 15:16:51 +00:00
65529ff2ce Command: add @throws CommandException 2019-02-14 15:09:41 +00:00
c346c45d42 Use regex for command argument parsing, fixes #2266
moral of the story: don't abuse functions for things they weren't designed for... lol
2019-02-14 15:07:58 +00:00
c433fad0a7 another /u modifier 2019-02-14 13:50:56 +00:00
8fad5a6e30 TextFormat: use mb_scrub() in clean()
this redacts invalid characters to prevent them appearing in places that might break the client.
2019-02-13 20:01:36 +00:00
7a6f279825 TextFormat: fixed clean() not being unicode-aware 2019-02-13 17:06:44 +00:00
10b72c895d Merge branch '3.5' into 3.6 2019-02-13 14:53:00 +00:00
d520928888 Fixed startup time measurement, closes #2713, closes #2750 2019-02-13 14:50:52 +00:00
27767e7ddb Verify player name command input, closes #2729, closes #2749 2019-02-13 14:50:43 +00:00
243c12de7c EffectCommand: fix bounds check, closes #2055 2019-02-13 14:37:40 +00:00
372545e47e Merge branch '3.5' into 3.6 2019-02-12 16:56:53 +00:00
8913b48700 "Implement" info-update and reserved6 2019-02-12 16:56:12 +00:00
6ee4a0e090 Implement invisible bedrock to fix placement issues
I don't know why this wasn't done a long time ago, but here it is.
2019-02-12 16:41:23 +00:00
8bd8da4bc6 Merge branch '3.5' into 3.6 2019-02-10 17:15:48 +00:00
9ba4144a71 Server: remove useless gc_collect_cycles()
the server is shutting down at this point so who cares if there are cycles...
2019-02-10 17:15:41 +00:00
9da7c6af27 new metadata properties 2019-02-10 13:59:21 +00:00
109312284c 3.6.2 is next 2019-02-08 16:41:10 +00:00
51934614bc Release 3.6.1 2019-02-08 16:40:38 +00:00
9e89f65094 Fixed handling for some new blocks which slipped through the frontline 2019-02-08 16:38:17 +00:00
9562711b84 Updated BedrockData submodule to 1.9.0 2019-02-08 15:32:57 +00:00
30b49e0d22 Merge branch '3.5' into 3.6 2019-02-08 15:32:20 +00:00
a975868fc3 3.5.12 is next 2019-02-08 13:52:31 +00:00
b38b932845 Release 3.5.11 2019-02-08 13:52:09 +00:00
43cb19ebca Updated bedrockData submodule to 1.8.0 2019-02-08 13:51:41 +00:00
769cc91543 ItemFactory: fix crash when getting negative item IDs
these are now treated the same as any unknown item, and are now not possible to place.
2019-02-08 13:51:41 +00:00
278f37d3e0 3.6.1 is next 2019-02-07 22:07:31 +00:00
02a6ca84a9 Releass 3.6.0 2019-02-07 22:06:33 +00:00
b8703d5dff Protocol changes for 1.9.0 2019-02-07 21:56:42 +00:00
37c2d78731 3.5.11 is next 2019-02-07 20:47:05 +00:00
b7663e5815 Release 3.5.10 2019-02-07 20:46:27 +00:00
1d0ffa06f8 TallGrass: fixed placement on dirt, closes #2552 2019-02-07 16:55:25 +00:00
768cfe3953 Updated composer dependencies 2019-02-07 16:47:16 +00:00
2822465f33 Updated submodules 2019-02-07 16:20:45 +00:00
5da48f429f Fixed some remaining one-line field declarations, added type docs 2019-02-05 14:03:27 +00:00
dbd0d04549 fix wrong doc comments in Thread & Worker 2019-02-05 13:58:18 +00:00
0f92ec6d2a Level: Record a debug message when chunks are loaded without loaders 2019-02-03 16:24:10 +00:00
791b4d8ef3 SplashPotion: measure distance from eye height instead of base
this fixes effect durations being off (mostly), closes #2650
there are still some minor differences, but this is closer matching than the previous version.
2019-02-03 11:32:47 +00:00
0b7ff6f2e7 Level: properly mark some functions as @internal
this ensures these functions won't appear in the documentation.
2019-02-01 14:33:06 +00:00
af092b01e1 3.5.10 is next 2019-01-31 18:47:57 +00:00
70 changed files with 941 additions and 283 deletions

View File

@ -27,7 +27,7 @@
"pocketmine/raklib": "^0.12.0",
"pocketmine/spl": "^0.3.0",
"pocketmine/binaryutils": "^0.1.0",
"pocketmine/nbt": "^0.2.1",
"pocketmine/nbt": "^0.2.6",
"pocketmine/math": "^0.2.0",
"pocketmine/snooze": "^0.1.0",
"daverandom/callback-validator": "dev-master",

26
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d3fb809caf4d5a5c99054f47f28ff271",
"content-hash": "2f5313e4ebd7b62c785cf683b27464b4",
"packages": [
{
"name": "adhocore/json-comment",
@ -160,16 +160,16 @@
},
{
"name": "pocketmine/nbt",
"version": "0.2.5",
"version": "0.2.6",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "0b290fa0f5b44835ebeea8146c9ac960cac833f5"
"reference": "92eaf84dd61f700d3ec02ebd01b606cb5b1590d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/0b290fa0f5b44835ebeea8146c9ac960cac833f5",
"reference": "0b290fa0f5b44835ebeea8146c9ac960cac833f5",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/92eaf84dd61f700d3ec02ebd01b606cb5b1590d4",
"reference": "92eaf84dd61f700d3ec02ebd01b606cb5b1590d4",
"shasum": ""
},
"require": {
@ -194,23 +194,23 @@
],
"description": "PHP library for working with Named Binary Tags",
"support": {
"source": "https://github.com/pmmp/NBT/tree/0.2.5",
"source": "https://github.com/pmmp/NBT/tree/0.2.6",
"issues": "https://github.com/pmmp/NBT/issues"
},
"time": "2019-01-07T17:28:16+00:00"
"time": "2019-02-07T16:28:11+00:00"
},
{
"name": "pocketmine/raklib",
"version": "0.12.1",
"version": "0.12.2",
"source": {
"type": "git",
"url": "https://github.com/pmmp/RakLib.git",
"reference": "334b469f2d0f070f17902d7ac584bea7c6149dc6"
"reference": "54a36d55eeba0a182e00439920ee2fcfa8c72bf3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/334b469f2d0f070f17902d7ac584bea7c6149dc6",
"reference": "334b469f2d0f070f17902d7ac584bea7c6149dc6",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/54a36d55eeba0a182e00439920ee2fcfa8c72bf3",
"reference": "54a36d55eeba0a182e00439920ee2fcfa8c72bf3",
"shasum": ""
},
"require": {
@ -235,10 +235,10 @@
],
"description": "A RakNet server implementation written in PHP",
"support": {
"source": "https://github.com/pmmp/RakLib/tree/0.12.1",
"source": "https://github.com/pmmp/RakLib/tree/0.12",
"issues": "https://github.com/pmmp/RakLib/issues"
},
"time": "2019-01-04T14:23:37+00:00"
"time": "2019-01-21T14:17:30+00:00"
},
{
"name": "pocketmine/snooze",

View File

@ -720,7 +720,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$data = new CommandData();
$data->commandName = $command->getName();
//TODO: commands containing uppercase letters in the name crash 1.9.0 client
$data->commandName = strtolower($command->getName());
$data->commandDescription = $this->server->getLanguage()->translateString($command->getDescription());
$data->flags = 0;
$data->permission = 0;
@ -3626,7 +3627,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
if($this->inventory !== null){
$this->inventory->setHeldItemIndex(0, false); //This is already handled when sending contents, don't send it twice
$this->inventory->setHeldItemIndex(0);
$this->inventory->clearAll();
}
if($this->armorInventory !== null){
@ -3634,6 +3635,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
//TODO: allow this number to be manipulated during PlayerDeathEvent
$this->level->dropExperience($this, $this->getXpDropAmount());
$this->setXpAndProgress(0, 0);
if($ev->getDeathMessage() != ""){
$this->server->broadcast($ev->getDeathMessage(), Server::BROADCAST_CHANNEL_USERS);
}
@ -3903,8 +3908,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
throw new \InvalidArgumentException("Cannot remove fixed window $id (" . get_class($inventory) . ") from " . $this->getName());
}
$inventory->close($this);
if($id !== null){
$inventory->close($this);
unset($this->windows[$hash], $this->windowIndex[$id], $this->permanentWindows[$id]);
}
}

View File

@ -37,7 +37,7 @@ namespace pocketmine {
use pocketmine\wizard\SetupWizard;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.5.9";
const BASE_VERSION = "3.6.5";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;
@ -177,7 +177,6 @@ namespace pocketmine {
ini_set("default_charset", "utf-8");
ini_set("memory_limit", '-1');
define('pocketmine\START_TIME', microtime(true));
define('pocketmine\RESOURCE_PATH', \pocketmine\PATH . 'src' . DIRECTORY_SEPARATOR . 'pocketmine' . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR);
@ -245,6 +244,8 @@ namespace pocketmine {
}
}
//TODO: move this to a Server field
define('pocketmine\START_TIME', microtime(true));
ThreadManager::init();
new Server($autoloader, $logger, \pocketmine\DATA, \pocketmine\PLUGIN_PATH);

View File

@ -122,9 +122,7 @@ use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function filemtime;
use function floor;
use function function_exists;
use function gc_collect_cycles;
use function get_class;
use function getmypid;
use function getopt;
@ -143,6 +141,7 @@ use function max;
use function microtime;
use function min;
use function mkdir;
use function ob_end_flush;
use function pcntl_signal;
use function pcntl_signal_dispatch;
use function preg_replace;
@ -295,15 +294,6 @@ class Server{
/** @var int */
public $networkCompressionLevel = 7;
/** @var bool */
private $autoTickRate = true;
/** @var int */
private $autoTickRateLimit = 20;
/** @var bool */
private $alwaysTickPlayers = false;
/** @var int */
private $baseTickRate = 1;
/** @var int */
private $autoSaveTicker = 0;
/** @var int */
@ -1056,7 +1046,7 @@ class Server{
*/
public function unloadLevel(Level $level, bool $forceUnload = false) : bool{
if($level === $this->getDefaultLevel() and !$forceUnload){
throw new \InvalidStateException("The default level cannot be unloaded while running, please switch levels.");
throw new \InvalidStateException("The default world cannot be unloaded while running, please switch worlds.");
}
return $level->unload($forceUnload);
@ -1082,7 +1072,7 @@ class Server{
*/
public function loadLevel(string $name) : bool{
if(trim($name) === ""){
throw new LevelException("Invalid empty level name");
throw new LevelException("Invalid empty world name");
}
if($this->isLevelLoaded($name)){
return true;
@ -1109,8 +1099,6 @@ class Server{
(new LevelLoadEvent($level))->call();
$level->setTickRate($this->baseTickRate);
return true;
}
@ -1142,7 +1130,7 @@ class Server{
if(($providerClass = LevelProviderManager::getProviderByName($this->getProperty("level-settings.default-format", "pmanvil"))) === null){
$providerClass = LevelProviderManager::getProviderByName("pmanvil");
if($providerClass === null){
throw new \InvalidStateException("Default level provider has not been registered");
throw new \InvalidStateException("Default world provider has not been registered");
}
}
@ -1154,8 +1142,6 @@ class Server{
$level = new Level($this, $name, new $providerClass($path));
$this->levels[$level->getId()] = $level;
$level->setTickRate($this->baseTickRate);
(new LevelInitEvent($level))->call();
(new LevelLoadEvent($level))->call();
@ -1593,11 +1579,6 @@ class Server{
}
$this->networkCompressionAsync = (bool) $this->getProperty("network.async-compression", true);
$this->autoTickRate = (bool) $this->getProperty("level-settings.auto-tick-rate", true);
$this->autoTickRateLimit = (int) $this->getProperty("level-settings.auto-tick-rate-limit", 20);
$this->alwaysTickPlayers = (bool) $this->getProperty("level-settings.always-tick-players", false);
$this->baseTickRate = (int) $this->getProperty("level-settings.base-tick-rate", 1);
$this->doTitleTick = ((bool) $this->getProperty("console.title-tick", true)) && Terminal::hasFormattingCodes();
@ -2038,7 +2019,7 @@ class Server{
}
public function reload(){
$this->logger->info("Saving levels...");
$this->logger->info("Saving worlds...");
foreach($this->levels as $level){
$level->save();
@ -2116,7 +2097,7 @@ class Server{
$player->close($player->getLeaveMessage(), $this->getProperty("settings.shutdown-message", "Server closed"));
}
$this->getLogger()->debug("Unloading all levels");
$this->getLogger()->debug("Unloading all worlds");
foreach($this->getLevels() as $level){
$this->unloadLevel($level, true);
}
@ -2148,9 +2129,6 @@ class Server{
$this->network->unregisterInterface($interface);
}
}
$this->getLogger()->debug("Collecting cycles");
gc_collect_cycles();
}catch(\Throwable $e){
$this->logger->logException($e);
$this->logger->emergency("Crashed while crashing, killing process");
@ -2221,6 +2199,7 @@ class Server{
* @param array|null $trace
*/
public function exceptionHandler(\Throwable $e, $trace = null){
while(@ob_end_flush()){}
global $lastError;
if($trace === null){
@ -2252,6 +2231,7 @@ class Server{
}
public function crashDump(){
while(@ob_end_flush()){}
if(!$this->isRunning){
return;
}
@ -2432,8 +2412,6 @@ class Server{
foreach($this->players as $p){
if(!$p->loggedIn and ($tickTime - $p->creationTime) >= 10){
$p->close("", "Login timeout");
}elseif($this->alwaysTickPlayers and $p->spawned){
$p->onUpdate($currentTick);
}
}
@ -2443,32 +2421,13 @@ class Server{
// Level unloaded during the tick of a level earlier in this loop, perhaps by plugin
continue;
}
if($level->getTickRate() > $this->baseTickRate and --$level->tickRateCounter > 0){
continue;
}
$levelTime = microtime(true);
$level->doTick($currentTick);
$tickMs = (microtime(true) - $levelTime) * 1000;
$level->tickRateTime = $tickMs;
if($this->autoTickRate){
if($tickMs < 50 and $level->getTickRate() > $this->baseTickRate){
$level->setTickRate($r = $level->getTickRate() - 1);
if($r > $this->baseTickRate){
$level->tickRateCounter = $level->getTickRate();
}
$this->getLogger()->debug("Raising level \"{$level->getName()}\" tick rate to {$level->getTickRate()} ticks");
}elseif($tickMs >= 50){
if($level->getTickRate() === $this->baseTickRate){
$level->setTickRate(max($this->baseTickRate + 1, min($this->autoTickRateLimit, (int) floor($tickMs / 50))));
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
}elseif(($tickMs / $level->getTickRate()) >= 50 and $level->getTickRate() < $this->autoTickRateLimit){
$level->setTickRate($level->getTickRate() + 1);
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
}
$level->tickRateCounter = $level->getTickRate();
}
if($tickMs >= 50){
$this->getLogger()->debug(sprintf("World \"%s\" took too long to tick: %gms (%g ticks)", $level->getName(), $tickMs, round($tickMs / 50, 2)));
}
}
}
@ -2621,7 +2580,10 @@ class Server{
if($this->autoSave and ++$this->autoSaveTicker >= $this->autoSaveTicks){
$this->autoSaveTicker = 0;
$this->getLogger()->debug("[Auto Save] Saving worlds...");
$start = microtime(true);
$this->doAutoSave();
$this->getLogger()->debug("[Auto Save] Save completed in " . round(microtime(true) - $start, 3) . "s");
}
if($this->sendUsageTicker > 0 and --$this->sendUsageTicker === 0){

View File

@ -28,7 +28,7 @@ namespace pocketmine;
*/
abstract class Thread extends \Thread{
/** @var \ClassLoader */
/** @var \ClassLoader|null */
protected $classLoader;
/** @var string|null */
protected $composerAutoloaderPath;

View File

@ -28,7 +28,7 @@ namespace pocketmine;
*/
abstract class Worker extends \Worker{
/** @var \ClassLoader */
/** @var \ClassLoader|null */
protected $classLoader;
/** @var string|null */
protected $composerAutoloaderPath;

View File

@ -171,7 +171,7 @@ class BlockFactory{
self::registerBlock(new Cake());
//TODO: REPEATER_BLOCK
//TODO: POWERED_REPEATER
//TODO: INVISIBLEBEDROCK
self::registerBlock(new InvisibleBedrock());
self::registerBlock(new Trapdoor());
//TODO: MONSTER_EGG
self::registerBlock(new StoneBricks());
@ -319,13 +319,13 @@ class BlockFactory{
self::registerBlock(new Stonecutter());
self::registerBlock(new GlowingObsidian());
self::registerBlock(new NetherReactor());
//TODO: INFO_UPDATE
//TODO: INFO_UPDATE2
self::registerBlock(new InfoUpdate(Block::INFO_UPDATE, 0, "update!"));
self::registerBlock(new InfoUpdate(Block::INFO_UPDATE2, 0, "ate!upd"));
//TODO: MOVINGBLOCK
//TODO: OBSERVER
//TODO: STRUCTURE_BLOCK
//TODO: RESERVED6
self::registerBlock(new Reserved6(Block::RESERVED6, 0, "reserved6"));
for($id = 0, $size = self::$fullList->getSize() >> 4; $id < $size; ++$id){
if(self::$fullList[$id << 4] === null){
@ -430,8 +430,13 @@ class BlockFactory{
public static function registerStaticRuntimeIdMappings() : void{
/** @var mixed[] $runtimeIdMap */
$runtimeIdMap = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "runtimeid_table.json"), true);
$legacyIdMap = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "legacy_id_map.json"), true);
foreach($runtimeIdMap as $k => $obj){
self::registerMapping($k, $obj["id"], $obj["data"]);
//this has to use the json offset to make sure the mapping is consistent with what we send over network, even though we aren't using all the entries
if(!isset($legacyIdMap[$obj["name"]])){
continue;
}
self::registerMapping($k, $legacyIdMap[$obj["name"]], $obj["data"]);
}
}

View File

@ -30,6 +30,18 @@ use pocketmine\math\Vector3;
class CobblestoneWall extends Transparent{
public const NONE_MOSSY_WALL = 0;
public const MOSSY_WALL = 1;
public const GRANITE_WALL = 2;
public const DIORITE_WALL = 3;
public const ANDESITE_WALL = 4;
public const SANDSTONE_WALL = 5;
public const BRICK_WALL = 6;
public const STONE_BRICK_WALL = 7;
public const MOSSY_STONE_BRICK_WALL = 8;
public const NETHER_BRICK_WALL = 9;
public const END_STONE_BRICK_WALL = 10;
public const PRISMARINE_WALL = 11;
public const RED_SANDSTONE_WALL = 12;
public const RED_NETHER_BRICK_WALL = 13;
protected $id = self::COBBLESTONE_WALL;
@ -50,11 +62,23 @@ class CobblestoneWall extends Transparent{
}
public function getName() : string{
if($this->meta === 0x01){
return "Mossy Cobblestone Wall";
}
return "Cobblestone Wall";
static $names = [
self::NONE_MOSSY_WALL => "Cobblestone",
self::MOSSY_WALL => "Mossy Cobblestone",
self::GRANITE_WALL => "Granite",
self::DIORITE_WALL => "Diorite",
self::ANDESITE_WALL => "Andesite",
self::SANDSTONE_WALL => "Sandstone",
self::BRICK_WALL => "Brick",
self::STONE_BRICK_WALL => "Stone Brick",
self::MOSSY_STONE_BRICK_WALL => "Mossy Stone Brick",
self::NETHER_BRICK_WALL => "Nether Brick",
self::END_STONE_BRICK_WALL => "End Stone Brick",
self::PRISMARINE_WALL => "Prismarine",
self::RED_SANDSTONE_WALL => "Red Sandstone",
self::RED_NETHER_BRICK_WALL => "Red Nether Brick"
];
return ($names[$this->getVariant()] ?? "Unknown") . " Wall";
}
protected function recalculateBoundingBox() : ?AxisAlignedBB{

View File

@ -0,0 +1,31 @@
<?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\block;
class InfoUpdate extends Solid{
public function getHardness() : float{
return 1;
}
}

View File

@ -0,0 +1,51 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
class InvisibleBedrock extends Transparent{
protected $id = self::INVISIBLE_BEDROCK;
public function __construct(){
}
public function getName() : string{
return "Invisible Bedrock";
}
public function getHardness() : float{
return -1;
}
public function getBlastResistance() : float{
return 18000000;
}
public function isBreakable(Item $item) : bool{
return false;
}
}

View File

@ -112,4 +112,8 @@ class ItemFrame extends Flowable{
public function isAffectedBySilkTouch() : bool{
return false;
}
public function getHardness() : float{
return 0.25;
}
}

View File

@ -58,8 +58,10 @@ class Ladder extends Transparent{
}
public function onEntityCollide(Entity $entity) : void{
$entity->resetFallDistance();
$entity->onGround = true;
if($entity->asVector3()->floor()->distanceSquared($this) < 1){ //entity coordinates must be inside block
$entity->resetFallDistance();
$entity->onGround = true;
}
}
protected function recalculateBoundingBox() : ?AxisAlignedBB{

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
class PackedIce extends Solid{
protected $id = self::PACKED_ICE;
@ -46,4 +48,8 @@ class PackedIce extends Solid{
public function getToolType() : int{
return BlockToolType::TYPE_PICKAXE;
}
public function getDropsForCompatibleTool(Item $item) : array{
return [];
}
}

View File

@ -0,0 +1,31 @@
<?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\block;
class Reserved6 extends Solid{
public function getHardness() : float{
return 0;
}
}

View File

@ -26,6 +26,12 @@ namespace pocketmine\block;
class StoneSlab2 extends StoneSlab{
public const TYPE_RED_SANDSTONE = 0;
public const TYPE_PURPUR = 1;
public const TYPE_PRISMARINE = 2;
public const TYPE_DARK_PRISMARINE = 3;
public const TYPE_PRISMARINE_BRICKS = 4;
public const TYPE_MOSSY_COBBLESTONE = 5;
public const TYPE_SMOOTH_SANDSTONE = 6;
public const TYPE_RED_NETHER_BRICK = 7;
protected $id = self::STONE_SLAB2;
@ -36,7 +42,13 @@ class StoneSlab2 extends StoneSlab{
public function getName() : string{
static $names = [
self::TYPE_RED_SANDSTONE => "Red Sandstone",
self::TYPE_PURPUR => "Purpur"
self::TYPE_PURPUR => "Purpur",
self::TYPE_PRISMARINE => "Prismarine",
self::TYPE_DARK_PRISMARINE => "Dark Prismarine",
self::TYPE_PRISMARINE_BRICKS => "Prismarine Bricks",
self::TYPE_MOSSY_COBBLESTONE => "Mossy Cobblestone",
self::TYPE_SMOOTH_SANDSTONE => "Smooth Sandstone",
self::TYPE_RED_NETHER_BRICK => "Red Nether Brick"
];
return (($this->meta & 0x08) > 0 ? "Upper " : "") . ($names[$this->getVariant()] ?? "") . " Slab";

View File

@ -25,6 +25,8 @@ namespace pocketmine\block;
use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Arrow;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\FlintSteel;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
@ -51,8 +53,10 @@ class TNT extends Solid{
}
public function onActivate(Item $item, Player $player = null) : bool{
if($item instanceof FlintSteel){
$item->applyDamage(1);
if($item instanceof FlintSteel or $item->hasEnchantment(Enchantment::FIRE_ASPECT)){
if($item instanceof Durable){
$item->applyDamage(1);
}
$this->ignite();
return true;
}

View File

@ -51,8 +51,8 @@ class TallGrass extends Flowable{
}
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
$down = $this->getSide(Vector3::SIDE_DOWN);
if($down->getId() === self::GRASS){
$down = $this->getSide(Vector3::SIDE_DOWN)->getId();
if($down === self::GRASS or $down === self::DIRT){
$this->getLevel()->setBlock($blockReplace, $this, true);
return true;

View File

@ -26,6 +26,7 @@ declare(strict_types=1);
*/
namespace pocketmine\command;
use pocketmine\command\utils\CommandException;
use pocketmine\lang\TextContainer;
use pocketmine\lang\TranslationContainer;
use pocketmine\permission\PermissionManager;
@ -92,6 +93,7 @@ abstract class Command{
* @param string[] $args
*
* @return mixed
* @throws CommandException
*/
abstract public function execute(CommandSender $sender, string $commandLabel, array $args);

View File

@ -66,13 +66,13 @@ use pocketmine\command\defaults\VersionCommand;
use pocketmine\command\defaults\WhitelistCommand;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\Server;
use function array_map;
use function array_shift;
use function count;
use function explode;
use function implode;
use function min;
use function str_getcsv;
use function preg_match_all;
use function stripslashes;
use function strpos;
use function strtolower;
use function trim;
@ -247,7 +247,16 @@ class SimpleCommandMap implements CommandMap{
}
public function dispatch(CommandSender $sender, string $commandLine) : bool{
$args = array_map("\stripslashes", str_getcsv($commandLine, " "));
$args = [];
preg_match_all('/"((?:\\\\.|[^\\\\"])*)"|(\S+)/u', $commandLine, $matches);
foreach($matches[0] as $k => $_){
for($i = 1; $i <= 2; ++$i){
if($matches[$i][$k] !== ""){
$args[$k] = stripslashes($matches[$i][$k]);
break;
}
}
}
$sentCommandLabel = "";
$target = $this->matchCommand($sentCommandLabel, $args);

View File

@ -53,6 +53,9 @@ class DeopCommand extends VanillaCommand{
}
$name = array_shift($args);
if(!Player::isValidUserName($name)){
throw new InvalidCommandSyntaxException();
}
$player = $sender->getServer()->getOfflinePlayer($name);
$player->setOp(false);

View File

@ -83,7 +83,7 @@ class EffectCommand extends VanillaCommand{
$amplification = 0;
if(count($args) >= 3){
if(($d = $this->getBoundedInt($sender, $args[2], 0, INT32_MAX)) === null){
if(($d = $this->getBoundedInt($sender, $args[2], 0, (int) (INT32_MAX / 20))) === null){
return false;
}
$duration = $d * 20; //ticks

View File

@ -53,6 +53,9 @@ class OpCommand extends VanillaCommand{
}
$name = array_shift($args);
if(!Player::isValidUserName($name)){
throw new InvalidCommandSyntaxException();
}
$player = $sender->getServer()->getOfflinePlayer($name);
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.op.success", [$player->getName()]));

View File

@ -26,6 +26,8 @@ namespace pocketmine\command\defaults;
use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\lang\TranslationContainer;
use function microtime;
use function round;
class SaveCommand extends VanillaCommand{
@ -43,7 +45,8 @@ class SaveCommand extends VanillaCommand{
return true;
}
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.save.start"));
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.save.start"));
$start = microtime(true);
foreach($sender->getServer()->getOnlinePlayers() as $player){
$player->save();
@ -53,7 +56,7 @@ class SaveCommand extends VanillaCommand{
$level->save(true);
}
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.save.success"));
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.save.success", [round(microtime(true) - $start, 3)]));
return true;
}

View File

@ -108,13 +108,11 @@ class StatusCommand extends VanillaCommand{
foreach($server->getLevels() as $level){
$levelName = $level->getFolderName() !== $level->getName() ? " (" . $level->getName() . ")" : "";
$timeColor = ($level->getTickRate() > 1 or $level->getTickRateTime() > 40) ? TextFormat::RED : TextFormat::YELLOW;
$tickRate = $level->getTickRate() > 1 ? " (tick rate " . $level->getTickRate() . ")" : "";
$timeColor = $level->getTickRateTime() > 40 ? TextFormat::RED : TextFormat::YELLOW;
$sender->sendMessage(TextFormat::GOLD . "World \"{$level->getFolderName()}\"$levelName: " .
TextFormat::RED . number_format(count($level->getChunks())) . TextFormat::GREEN . " chunks, " .
TextFormat::RED . number_format(count($level->getEntities())) . TextFormat::GREEN . " entities, " .
TextFormat::RED . number_format(count($level->getTiles())) . TextFormat::GREEN . " tiles. " .
"Time $timeColor" . round($level->getTickRateTime(), 2) . "ms" . $tickRate
TextFormat::RED . number_format(count($level->getEntities())) . TextFormat::GREEN . " entities. " .
"Time $timeColor" . round($level->getTickRateTime(), 2) . "ms"
);
}

View File

@ -27,6 +27,7 @@ use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\TranslationContainer;
use pocketmine\Player;
use pocketmine\utils\TextFormat;
use function count;
use function implode;
@ -94,6 +95,9 @@ class WhitelistCommand extends VanillaCommand{
if($this->badPerm($sender, strtolower($args[0]))){
return false;
}
if(!Player::isValidUserName($args[1])){
throw new InvalidCommandSyntaxException();
}
switch(strtolower($args[0])){
case "add":
$sender->getServer()->getOfflinePlayer($args[1])->setWhitelisted(true);

View File

@ -200,6 +200,11 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_FLAGS2 = 91; //long (extended data flags)
/* 92 (float) related to panda lying down
* 93 (float) related to panda lying down */
public const DATA_AREA_EFFECT_CLOUD_DURATION = 94; //int
public const DATA_AREA_EFFECT_CLOUD_SPAWN_TIME = 95; //int
public const DATA_AREA_EFFECT_CLOUD_RADIUS_PER_TICK = 96; //float, usually negative
public const DATA_AREA_EFFECT_CLOUD_RADIUS_CHANGE_ON_PICKUP = 97; //float
public const DATA_AREA_EFFECT_CLOUD_PICKUP_COUNT = 98; //int
public const DATA_FLAG_ONFIRE = 0;
@ -1164,6 +1169,9 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
protected function updateMovement(bool $teleport = false) : void{
//TODO: hack for client-side AI interference: prevent client sided movement when motion is 0
$this->setImmobile($this->motion->x == 0 and $this->motion->y == 0 and $this->motion->z == 0);
$diffPosition = ($this->x - $this->lastX) ** 2 + ($this->y - $this->lastY) ** 2 + ($this->z - $this->lastZ) ** 2;
$diffRotation = ($this->yaw - $this->lastYaw) ** 2 + ($this->pitch - $this->lastPitch) ** 2;
@ -1632,7 +1640,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
assert(abs($dx) <= 20 and abs($dy) <= 20 and abs($dz) <= 20, "Movement distance is excessive: dx=$dx, dy=$dy, dz=$dz");
$list = $this->level->getCollisionCubes($this, $this->level->getTickRate() > 1 ? $this->boundingBox->offsetCopy($dx, $dy, $dz) : $this->boundingBox->addCoord($dx, $dy, $dz), false);
//TODO: bad hack here will cause unexpected behaviour under heavy lag
$list = $this->level->getCollisionCubes($this, $this->level->getTickRateTime() > 50 ? $this->boundingBox->offsetCopy($dx, $dy, $dz) : $this->boundingBox->addCoord($dx, $dy, $dz), false);
foreach($list as $bb){
$dy = $bb->calculateYOffset($this->boundingBox, $dy);

View File

@ -301,7 +301,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
$food = $this->getFood();
if($food > 0){
$food--;
$this->setFood($food);
$this->setFood(max($food, 0));
}
}
}

View File

@ -152,7 +152,7 @@ abstract class Living extends Entity implements Damageable{
}
public function setMaxHealth(int $amount) : void{
$this->attributeMap->getAttribute(Attribute::HEALTH)->setMaxValue($amount);
$this->attributeMap->getAttribute(Attribute::HEALTH)->setMaxValue($amount)->setDefaultValue($amount);
}
public function getAbsorption() : float{

View File

@ -123,7 +123,7 @@ class Arrow extends Projectile{
$hasUpdate = parent::entityBaseTick($tickDiff);
if($this->isCollided){
if($this->blockHit !== null){
$this->collideTicks += $tickDiff;
if($this->collideTicks > 1200){
$this->flagForDespawn();

View File

@ -85,7 +85,7 @@ class SplashPotion extends Throwable{
if(!$this->willLinger()){
foreach($this->level->getNearbyEntities($this->boundingBox->expandedCopy(4.125, 2.125, 4.125), $this) as $entity){
if($entity instanceof Living and $entity->isAlive()){
$distanceSquared = $entity->distanceSquared($this);
$distanceSquared = $entity->add(0, $entity->getEyeHeight(), 0)->distanceSquared($this);
if($distanceSquared > 16){ //4 blocks
continue;
}

View File

@ -27,7 +27,7 @@ use pocketmine\level\Position;
use pocketmine\Player;
/**
* Called when a player is respawned (or first time spawned)
* Called when a player is respawned
*/
class PlayerRespawnEvent extends PlayerEvent{
/** @var Position */

View File

@ -253,11 +253,9 @@ abstract class BaseInventory implements Inventory{
public function canAddItem(Item $item) : bool{
$item = clone $item;
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasCompoundTag();
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$slot = $this->getItem($i);
if($item->equals($slot, $checkDamage, $checkTags)){
if($item->equals($slot)){
if(($diff = $slot->getMaxStackSize() - $slot->getCount()) > 0){
$item->setCount($item->getCount() - $diff);
}

View File

@ -216,7 +216,7 @@ class Item implements ItemIds, \JsonSerializable{
/**
* Sets the Item's NBT
*
* @param CompoundTag|string $tags
* @param CompoundTag|string|null $tags
*
* @return Item
*/
@ -224,7 +224,7 @@ class Item implements ItemIds, \JsonSerializable{
if($tags instanceof CompoundTag){
$this->setNamedTag($tags);
}else{
$this->tags = (string) $tags;
$this->tags = $tags === null ? "" : (string) $tags;
$this->cachedNBT = null;
}
@ -232,6 +232,9 @@ class Item implements ItemIds, \JsonSerializable{
}
/**
* @deprecated This method returns NBT serialized in a network-dependent format. Prefer use of getNamedTag() instead.
* @see Item::getNamedTag()
*
* Returns the serialized NBT of the Item
* @return string
*/

View File

@ -300,16 +300,16 @@ class ItemFactory{
/**
* Returns an instance of the Item with the specified id, meta, count and NBT.
*
* @param int $id
* @param int $meta
* @param int $count
* @param CompoundTag|string $tags
* @param int $id
* @param int $meta
* @param int $count
* @param CompoundTag|string|null $tags
*
* @return Item
* @throws \TypeError
*/
public static function get(int $id, int $meta = 0, int $count = 1, $tags = "") : Item{
if(!is_string($tags) and !($tags instanceof CompoundTag)){
public static function get(int $id, int $meta = 0, int $count = 1, $tags = null) : Item{
if(!is_string($tags) and !($tags instanceof CompoundTag) and $tags !== null){
throw new \TypeError("`tags` argument must be a string or CompoundTag instance, " . (is_object($tags) ? "instance of " . get_class($tags) : gettype($tags)) . " given");
}
@ -318,7 +318,7 @@ class ItemFactory{
$listed = self::$list[self::getListOffset($id)];
if($listed !== null){
$item = clone $listed;
}elseif($id < 256){ //intentionally includes negatives, for extended block IDs
}elseif($id >= 0 and $id < 256){ //intentionally excludes negatives because extended blocks aren't supported yet
/* Blocks must have a damage value 0-15, but items can have damage value -1 to indicate that they are
* crafting ingredients with any-damage. */
$item = new ItemBlock($id, $meta);

View File

@ -74,7 +74,7 @@ class Explosion{
*/
public function __construct(Position $center, float $size, $what = null){
if(!$center->isValid()){
throw new \InvalidArgumentException("Position does not have a valid level");
throw new \InvalidArgumentException("Position does not have a valid world");
}
$this->source = $center;
$this->level = $center->getLevel();

View File

@ -261,11 +261,12 @@ class Level implements ChunkManager, Metadatable{
/** @var LevelTimings */
public $timings;
/** @var int */
private $tickRate;
/** @var int */
public $tickRateTime = 0;
/** @var int */
/**
* @deprecated
* @var int
*/
public $tickRateCounter = 0;
/** @var bool */
@ -411,19 +412,26 @@ class Level implements ChunkManager, Metadatable{
$this->timings = new LevelTimings($this);
$this->temporalPosition = new Position(0, 0, 0, $this);
$this->temporalVector = new Vector3(0, 0, 0);
$this->tickRate = 1;
}
/**
* @deprecated
* @return int
*/
public function getTickRate() : int{
return $this->tickRate;
return 1;
}
public function getTickRateTime() : float{
return $this->tickRateTime;
}
/**
* @deprecated does nothing
* @param int $tickRate
*/
public function setTickRate(int $tickRate){
$this->tickRate = $tickRate;
}
public function registerGeneratorToWorker(int $worker) : void{
@ -466,7 +474,7 @@ class Level implements ChunkManager, Metadatable{
public function close(){
if($this->closed){
throw new \InvalidStateException("Tried to close a level which is already closed");
throw new \InvalidStateException("Tried to close a world which is already closed");
}
foreach($this->chunks as $chunk){
@ -568,7 +576,8 @@ class Level implements ChunkManager, Metadatable{
}
/**
* @internal DO NOT use this from plugins, it's for internal use only. Use Server->unloadLevel() instead.
* @internal
* @see Server::unloadLevel()
*
* Unloads the current level from memory safely
*
@ -579,7 +588,7 @@ class Level implements ChunkManager, Metadatable{
*/
public function unload(bool $force = false) : bool{
if($this->doingTick and !$force){
throw new \InvalidStateException("Cannot unload a level during level tick");
throw new \InvalidStateException("Cannot unload a world during world tick");
}
$ev = new LevelUnloadEvent($this);
@ -598,7 +607,7 @@ class Level implements ChunkManager, Metadatable{
$defaultLevel = $this->server->getDefaultLevel();
foreach($this->getPlayers() as $player){
if($this === $defaultLevel or $defaultLevel === null){
$player->close($player->getLeaveMessage(), "Forced default level unload");
$player->close($player->getLeaveMessage(), "Forced default world unload");
}elseif($defaultLevel instanceof Level){
$player->teleport($this->server->getDefaultLevel()->getSafeSpawn());
}
@ -746,8 +755,7 @@ class Level implements ChunkManager, Metadatable{
}
/**
* WARNING: Do not use this, it's only for internal use.
* Changes to this function won't be recorded on the version.
* @internal
*/
public function checkTime(){
if($this->stopTime){
@ -758,8 +766,7 @@ class Level implements ChunkManager, Metadatable{
}
/**
* WARNING: Do not use this, it's only for internal use.
* Changes to this function won't be recorded on the version.
* @internal
*
* @param Player ...$targets If empty, will send to all players in the level.
*/
@ -771,15 +778,14 @@ class Level implements ChunkManager, Metadatable{
}
/**
* WARNING: Do not use this, it's only for internal use.
* Changes to this function won't be recorded on the version.
* @internal
*
* @param int $currentTick
*
*/
public function doTick(int $currentTick){
if($this->closed){
throw new \InvalidStateException("Attempted to tick a Level which has been closed");
throw new \InvalidStateException("Attempted to tick a world which has been closed");
}
$this->timings->doTick->startTiming();
@ -2693,10 +2699,10 @@ class Level implements ChunkManager, Metadatable{
*/
public function addEntity(Entity $entity){
if($entity->isClosed()){
throw new \InvalidArgumentException("Attempted to add a garbage closed Entity to Level");
throw new \InvalidArgumentException("Attempted to add a garbage closed Entity to world");
}
if($entity->getLevel() !== $this){
throw new LevelException("Invalid Entity level");
throw new LevelException("Invalid Entity world");
}
if($entity instanceof Player){
@ -2714,7 +2720,7 @@ class Level implements ChunkManager, Metadatable{
*/
public function removeEntity(Entity $entity){
if($entity->getLevel() !== $this){
throw new LevelException("Invalid Entity level");
throw new LevelException("Invalid Entity world");
}
if($entity instanceof Player){
@ -2733,10 +2739,10 @@ class Level implements ChunkManager, Metadatable{
*/
public function addTile(Tile $tile){
if($tile->isClosed()){
throw new \InvalidArgumentException("Attempted to add a garbage closed Tile to Level");
throw new \InvalidArgumentException("Attempted to add a garbage closed Tile to world");
}
if($tile->getLevel() !== $this){
throw new LevelException("Invalid Tile level");
throw new LevelException("Invalid Tile world");
}
$chunkX = $tile->getFloorX() >> 4;
@ -2759,7 +2765,7 @@ class Level implements ChunkManager, Metadatable{
*/
public function removeTile(Tile $tile){
if($tile->getLevel() !== $this){
throw new LevelException("Invalid Tile level");
throw new LevelException("Invalid Tile world");
}
unset($this->tiles[$tile->getId()], $this->updateTiles[$tile->getId()]);
@ -2841,6 +2847,7 @@ class Level implements ChunkManager, Metadatable{
$loader->onChunkLoaded($chunk);
}
}else{
$this->server->getLogger()->debug("Newly loaded chunk $x $z has no loaders registered, will be unloaded at next available opportunity");
$this->unloadChunkRequest($x, $z);
}

View File

@ -64,7 +64,7 @@ class Position extends Vector3{
*/
public function getLevel(){
if($this->level !== null and $this->level->isClosed()){
MainLogger::getLogger()->debug("Position was holding a reference to an unloaded Level");
MainLogger::getLogger()->debug("Position was holding a reference to an unloaded world");
$this->level = null;
}
@ -82,7 +82,7 @@ class Position extends Vector3{
*/
public function setLevel(Level $level = null){
if($level !== null and $level->isClosed()){
throw new \InvalidArgumentException("Specified level has been unloaded and cannot be used");
throw new \InvalidArgumentException("Specified world has been unloaded and cannot be used");
}
$this->level = $level;

View File

@ -74,10 +74,10 @@ class ChunkRequestTask extends AsyncTask{
$batch->isEncoded = true;
$level->chunkRequestCallback($this->chunkX, $this->chunkZ, $batch);
}else{
$server->getLogger()->error("Chunk request for level #" . $this->levelId . ", x=" . $this->chunkX . ", z=" . $this->chunkZ . " doesn't have any result data");
$server->getLogger()->error("Chunk request for world #" . $this->levelId . ", x=" . $this->chunkX . ", z=" . $this->chunkZ . " doesn't have any result data");
}
}else{
$server->getLogger()->debug("Dropped chunk task due to level not loaded");
$server->getLogger()->debug("Dropped chunk task due to world not loaded");
}
}
}

View File

@ -311,6 +311,7 @@ class LevelDB extends BaseLevelProvider{
$lightPopulated = true;
$chunkVersion = ord($this->db->get($index . self::TAG_VERSION));
$hasBeenUpgraded = $chunkVersion < self::CURRENT_LEVEL_CHUNK_VERSION;
$binaryStream = new BinaryStream();
@ -326,6 +327,9 @@ class LevelDB extends BaseLevelProvider{
$binaryStream->setBuffer($data, 0);
$subChunkVersion = $binaryStream->getByte();
if($subChunkVersion < self::CURRENT_LEVEL_SUBCHUNK_VERSION){
$hasBeenUpgraded = true;
}
switch($subChunkVersion){
case 0:
@ -334,6 +338,7 @@ class LevelDB extends BaseLevelProvider{
if($chunkVersion < 4){
$blockSkyLight = $binaryStream->get(2048);
$blockLight = $binaryStream->get(2048);
$hasBeenUpgraded = true; //drop saved light
}else{
//Mojang didn't bother changing the subchunk version when they stopped saving sky light -_-
$blockSkyLight = "";
@ -453,6 +458,7 @@ class LevelDB extends BaseLevelProvider{
$chunk->setGenerated(true);
$chunk->setPopulated(true);
$chunk->setLightPopulated($lightPopulated);
$chunk->setChanged($hasBeenUpgraded); //trigger rewriting chunk to disk if it was converted from an older format
return $chunk;
}

View File

@ -124,9 +124,6 @@ class RegionLoader{
*/
public function readChunk(int $x, int $z) : ?string{
$index = self::getChunkOffset($x, $z);
if($index < 0 or $index >= 4096){
throw new \InvalidArgumentException("Invalid chunk position in region, expected x/z in range 0-31, got x=$x, z=$z");
}
$this->lastUsed = time();
@ -143,15 +140,13 @@ class RegionLoader{
if($length <= 0 or $length > self::MAX_SECTOR_LENGTH){ //Not yet generated / corrupted
if($length >= self::MAX_SECTOR_LENGTH){
$this->locationTable[$index][0] = ++$this->lastSector;
$this->locationTable[$index][1] = 1;
throw new CorruptedChunkException("Corrupted chunk header detected (sector count larger than max)");
throw new CorruptedChunkException("Corrupted chunk header detected (sector count $length larger than max " . self::MAX_SECTOR_LENGTH . ")");
}
return null;
}
if($length > ($this->locationTable[$index][1] << 12)){ //Invalid chunk, bigger than defined number of sectors
MainLogger::getLogger()->error("Corrupted bigger chunk detected (bigger than number of sectors given in header)");
MainLogger::getLogger()->error("Chunk x=$x,z=$z length mismatch (expected " . ($this->locationTable[$index][1] << 12) . " sectors, got $length sectors)");
$this->locationTable[$index][1] = $length >> 12;
$this->writeLocationIndex($index);
}
@ -169,10 +164,25 @@ class RegionLoader{
return substr($chunkData, 1);
}
/**
* @param int $x
* @param int $z
*
* @return bool
* @throws \InvalidArgumentException
*/
public function chunkExists(int $x, int $z) : bool{
return $this->isChunkGenerated(self::getChunkOffset($x, $z));
}
/**
* @param int $x
* @param int $z
* @param string $chunkData
*
* @throws ChunkException
* @throws \InvalidArgumentException
*/
public function writeChunk(int $x, int $z, string $chunkData){
$this->lastUsed = time();
@ -202,14 +212,40 @@ class RegionLoader{
}
}
/**
* @param int $x
* @param int $z
*
* @throws \InvalidArgumentException
*/
public function removeChunk(int $x, int $z){
$index = self::getChunkOffset($x, $z);
$this->locationTable[$index][0] = 0;
$this->locationTable[$index][1] = 0;
}
/**
* @param int $x
* @param int $z
*
* @return int
* @throws \InvalidArgumentException
*/
protected static function getChunkOffset(int $x, int $z) : int{
return $x + ($z << 5);
if($x < 0 or $x > 31 or $z < 0 or $z > 31){
throw new \InvalidArgumentException("Invalid chunk position in region, expected x/z in range 0-31, got x=$x, z=$z");
}
return $x | ($z << 5);
}
/**
* @param int $offset
* @param int &$x
* @param int &$z
*/
protected static function getChunkCoords(int $offset, ?int &$x, ?int &$z) : void{
$x = $offset & 0x1f;
$z = ($offset >> 5) & 0x1f;
}
/**
@ -237,18 +273,23 @@ class RegionLoader{
}
$data = unpack("N*", $headerRaw);
/** @var int[] $usedOffsets */
$usedOffsets = [];
for($i = 0; $i < 1024; ++$i){
$index = $data[$i + 1];
$offset = $index >> 8;
if($offset !== 0){
fseek($this->filePointer, $offset << 12);
self::getChunkCoords($i, $x, $z);
$fileOffset = $offset << 12;
fseek($this->filePointer, $fileOffset);
if(fgetc($this->filePointer) === false){ //Try and read from the location
throw new CorruptedRegionException("Region file location offset points to invalid location");
throw new CorruptedRegionException("Region file location offset x=$x,z=$z points to invalid file location $fileOffset");
}elseif(isset($usedOffsets[$offset])){
throw new CorruptedRegionException("Found two chunk offsets pointing to the same location");
self::getChunkCoords($usedOffsets[$offset], $existingX, $existingZ);
throw new CorruptedRegionException("Found two chunk offsets (chunk1: x=$existingX,z=$existingZ, chunk2: x=$x,z=$z) pointing to the file location $fileOffset");
}else{
$usedOffsets[$offset] = true;
$usedOffsets[$offset] = $i;
}
}

View File

@ -30,6 +30,8 @@ use pocketmine\entity\Entity;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\math\Vector3;
use pocketmine\nbt\NetworkLittleEndianNBTStream;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\CommandOriginData;
use pocketmine\network\mcpe\protocol\types\EntityLink;
use pocketmine\utils\BinaryStream;
@ -79,10 +81,17 @@ class NetworkBinaryStream extends BinaryStream{
$cnt = $auxValue & 0xff;
$nbtLen = $this->getLShort();
$nbt = "";
if($nbtLen > 0){
$nbt = $this->get($nbtLen);
/** @var CompoundTag|string $nbt */
$nbt = "";
if($nbtLen === 0xffff){
$c = $this->getByte();
if($c !== 1){
throw new \UnexpectedValueException("Unexpected NBT count $c");
}
$nbt = (new NetworkLittleEndianNBTStream())->read($this->buffer, false, $this->offset);
}elseif($nbtLen !== 0){
throw new \UnexpectedValueException("Unexpected fake NBT length $nbtLen");
}
//TODO
@ -110,25 +119,14 @@ class NetworkBinaryStream extends BinaryStream{
$auxValue = (($item->getDamage() & 0x7fff) << 8) | $item->getCount();
$this->putVarInt($auxValue);
$nbt = $item->getCompoundTag();
$nbtLen = strlen($nbt);
if($nbtLen > 32767){
/*
* TODO: Workaround bug in the protocol (overflow)
* Encoded tags larger than 32KB overflow the length field, so we can't send these over network.
* However, it's unreasonable to randomly throw this burden off onto users by crashing their servers, so the
* next best solution is to just not send the NBT. This is also not an ideal solution (books and the like
* with too-large tags won't work on the client side) but it's better than crashing the server or client due
* to a protocol bug. Mojang have confirmed this will be resolved by a future MCPE release, so we'll just
* work around this problem until then.
*/
$nbt = "";
$nbtLen = 0;
if($item->hasCompoundTag()){
$this->putLShort(0xffff);
$this->putByte(1); //TODO: some kind of count field? always 1 as of 1.9.0
$this->put((new NetworkLittleEndianNBTStream())->write($item->getNamedTag()));
}else{
$this->putLShort(0);
}
$this->putLShort($nbtLen);
$this->put($nbt);
$this->putVarInt(0); //CanPlaceOn entry count (TODO)
$this->putVarInt(0); //CanDestroy entry count (TODO)
}

View File

@ -25,7 +25,6 @@ namespace pocketmine\network\mcpe;
use pocketmine\network\mcpe\protocol\AddBehaviorTreePacket;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\AddHangingEntityPacket;
use pocketmine\network\mcpe\protocol\AddItemEntityPacket;
use pocketmine\network\mcpe\protocol\AddPaintingPacket;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
@ -72,6 +71,7 @@ use pocketmine\network\mcpe\protocol\LabTablePacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacketV1;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacketV2;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
@ -210,10 +210,6 @@ abstract class NetworkSession{
return false;
}
public function handleAddHangingEntity(AddHangingEntityPacket $packet) : bool{
return false;
}
public function handleTakeItemEntity(TakeItemEntityPacket $packet) : bool{
return false;
}
@ -622,7 +618,7 @@ abstract class NetworkSession{
return false;
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
public function handleLevelSoundEventPacketV2(LevelSoundEventPacketV2 $packet) : bool{
return false;
}
@ -634,4 +630,8 @@ abstract class NetworkSession{
return false;
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
return false;
}
}

View File

@ -51,6 +51,7 @@ use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NetworkStackLatencyPacket;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
@ -300,4 +301,8 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
return $this->player->handleLevelSoundEvent($packet);
}
public function handleNetworkStackLatency(NetworkStackLatencyPacket $packet) : bool{
return true; //TODO: implement this properly - this is here to silence debug spam from MCPE dev builds
}
}

View File

@ -28,19 +28,37 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\network\mcpe\NetworkSession;
class AddPaintingPacket extends AddHangingEntityPacket{
class AddPaintingPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::ADD_PAINTING_PACKET;
/** @var string */
public $title;
/** @var int */
public $entityRuntimeId;
/** @var int */
public $x;
/** @var int|null */
public $entityUniqueId = null;
/** @var int */
public $y;
/** @var int */
public $z;
/** @var int */
public $direction;
protected function decodePayload(){
parent::decodePayload();
$this->entityUniqueId = $this->getEntityUniqueId();
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->getBlockPosition($this->x, $this->y, $this->z);
$this->direction = $this->getVarInt();
$this->title = $this->getString();
}
protected function encodePayload(){
parent::encodePayload();
$this->putEntityUniqueId($this->entityUniqueId ?? $this->entityRuntimeId);
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putBlockPosition($this->x, $this->y, $this->z);
$this->putVarInt($this->direction);
$this->putString($this->title);
}

File diff suppressed because one or more lines are too long

View File

@ -299,7 +299,7 @@ class LevelSoundEventPacket extends DataPacket{
public $disableRelativeVolume = false;
protected function decodePayload(){
$this->sound = $this->getByte();
$this->sound = $this->getUnsignedVarInt();
$this->position = $this->getVector3();
$this->extraData = $this->getVarInt();
$this->entityType = $this->getString();
@ -308,7 +308,7 @@ class LevelSoundEventPacket extends DataPacket{
}
protected function encodePayload(){
$this->putByte($this->sound);
$this->putUnsignedVarInt($this->sound);
$this->putVector3($this->position);
$this->putVarInt($this->extraData);
$this->putString($this->entityType);

View File

@ -25,39 +25,47 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkSession;
class AddHangingEntityPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::ADD_HANGING_ENTITY_PACKET;
/**
* Useless leftover from a 1.9 refactor, does nothing
*/
class LevelSoundEventPacketV2 extends DataPacket{
public const NETWORK_ID = ProtocolInfo::LEVEL_SOUND_EVENT_PACKET_V2;
/** @var int|null */
public $entityUniqueId = null;
/** @var int */
public $entityRuntimeId;
public $sound;
/** @var Vector3 */
public $position;
/** @var int */
public $x;
/** @var int */
public $y;
/** @var int */
public $z;
/** @var int */
public $direction;
public $extraData = -1;
/** @var string */
public $entityType = ":"; //???
/** @var bool */
public $isBabyMob = false; //...
/** @var bool */
public $disableRelativeVolume = false;
protected function decodePayload(){
$this->entityUniqueId = $this->getEntityUniqueId();
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->getBlockPosition($this->x, $this->y, $this->z);
$this->direction = $this->getVarInt();
$this->sound = $this->getByte();
$this->position = $this->getVector3();
$this->extraData = $this->getVarInt();
$this->entityType = $this->getString();
$this->isBabyMob = $this->getBool();
$this->disableRelativeVolume = $this->getBool();
}
protected function encodePayload(){
$this->putEntityUniqueId($this->entityUniqueId ?? $this->entityRuntimeId);
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putBlockPosition($this->x, $this->y, $this->z);
$this->putVarInt($this->direction);
$this->putByte($this->sound);
$this->putVector3($this->position);
$this->putVarInt($this->extraData);
$this->putString($this->entityType);
$this->putBool($this->isBabyMob);
$this->putBool($this->disableRelativeVolume);
}
public function handle(NetworkSession $session) : bool{
return $session->handleAddHangingEntity($this);
return $session->handleLevelSoundEventPacketV2($this);
}
}

View File

@ -32,13 +32,17 @@ class NetworkStackLatencyPacket extends DataPacket{
/** @var int */
public $timestamp;
/** @var bool */
public $needResponse;
protected function decodePayload(){
$this->timestamp = $this->getLLong();
$this->needResponse = $this->getBool();
}
protected function encodePayload(){
$this->putLLong($this->timestamp);
$this->putBool($this->needResponse);
}
public function handle(NetworkSession $session) : bool{

View File

@ -48,7 +48,6 @@ class PacketPool{
static::registerPacket(new AddEntityPacket());
static::registerPacket(new RemoveEntityPacket());
static::registerPacket(new AddItemEntityPacket());
static::registerPacket(new AddHangingEntityPacket());
static::registerPacket(new TakeItemEntityPacket());
static::registerPacket(new MoveEntityAbsolutePacket());
static::registerPacket(new MovePlayerPacket());
@ -151,9 +150,10 @@ class PacketPool{
static::registerPacket(new ScriptCustomEventPacket());
static::registerPacket(new SpawnParticleEffectPacket());
static::registerPacket(new AvailableEntityIdentifiersPacket());
static::registerPacket(new LevelSoundEventPacket());
static::registerPacket(new LevelSoundEventPacketV2());
static::registerPacket(new NetworkChunkPublisherUpdatePacket());
static::registerPacket(new BiomeDefinitionListPacket());
static::registerPacket(new LevelSoundEventPacket());
static::registerPacket(new BatchPacket());
}

View File

@ -39,15 +39,15 @@ interface ProtocolInfo{
/**
* Actual Minecraft: PE protocol version
*/
public const CURRENT_PROTOCOL = 313;
public const CURRENT_PROTOCOL = 332;
/**
* Current Minecraft PE version reported by the server. This is usually the earliest currently supported version.
*/
public const MINECRAFT_VERSION = 'v1.8.0';
public const MINECRAFT_VERSION = 'v1.9.0';
/**
* Version number sent to clients in ping responses.
*/
public const MINECRAFT_VERSION_NETWORK = '1.8.0';
public const MINECRAFT_VERSION_NETWORK = '1.9.0';
public const LOGIN_PACKET = 0x01;
public const PLAY_STATUS_PACKET = 0x02;
@ -64,7 +64,7 @@ interface ProtocolInfo{
public const ADD_ENTITY_PACKET = 0x0d;
public const REMOVE_ENTITY_PACKET = 0x0e;
public const ADD_ITEM_ENTITY_PACKET = 0x0f;
public const ADD_HANGING_ENTITY_PACKET = 0x10;
public const TAKE_ITEM_ENTITY_PACKET = 0x11;
public const MOVE_ENTITY_ABSOLUTE_PACKET = 0x12;
public const MOVE_PLAYER_PACKET = 0x13;
@ -168,8 +168,9 @@ interface ProtocolInfo{
public const SCRIPT_CUSTOM_EVENT_PACKET = 0x75;
public const SPAWN_PARTICLE_EFFECT_PACKET = 0x76;
public const AVAILABLE_ENTITY_IDENTIFIERS_PACKET = 0x77;
public const LEVEL_SOUND_EVENT_PACKET = 0x78;
public const LEVEL_SOUND_EVENT_PACKET_V2 = 0x78;
public const NETWORK_CHUNK_PUBLISHER_UPDATE_PACKET = 0x79;
public const BIOME_DEFINITION_LIST_PACKET = 0x7a;
public const LEVEL_SOUND_EVENT_PACKET = 0x7b;
}

View File

@ -35,6 +35,8 @@ class ResourcePacksInfoPacket extends DataPacket{
/** @var bool */
public $mustAccept = false; //if true, forces client to use selected resource packs
/** @var bool */
public $hasScripts = false; //if true, causes disconnect for any platform that doesn't support scripts yet
/** @var ResourcePack[] */
public $behaviorPackEntries = [];
/** @var ResourcePack[] */
@ -42,6 +44,7 @@ class ResourcePacksInfoPacket extends DataPacket{
protected function decodePayload(){
$this->mustAccept = $this->getBool();
$this->hasScripts = $this->getBool();
$behaviorPackCount = $this->getLShort();
while($behaviorPackCount-- > 0){
$this->getString();
@ -50,6 +53,7 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->getString();
$this->getString();
$this->getString();
$this->getBool();
}
$resourcePackCount = $this->getLShort();
@ -60,12 +64,13 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->getString();
$this->getString();
$this->getString();
$this->getBool();
}
}
protected function encodePayload(){
$this->putBool($this->mustAccept);
$this->putBool($this->hasScripts);
$this->putLShort(count($this->behaviorPackEntries));
foreach($this->behaviorPackEntries as $entry){
$this->putString($entry->getPackId());
@ -74,6 +79,7 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->putString(""); //TODO: encryption key
$this->putString(""); //TODO: subpack name
$this->putString(""); //TODO: content identity
$this->putBool(false); //TODO: has scripts (?)
}
$this->putLShort(count($this->resourcePackEntries));
foreach($this->resourcePackEntries as $entry){
@ -83,6 +89,7 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->putString(""); //TODO: encryption key
$this->putString(""); //TODO: subpack name
$this->putString(""); //TODO: content identity
$this->putBool(false); //TODO: seems useless for resource packs
}
}

View File

@ -34,6 +34,8 @@ class SpawnParticleEffectPacket extends DataPacket{
/** @var int */
public $dimensionId = DimensionIds::OVERWORLD; //wtf mojang
/** @var int */
public $entityUniqueId = -1; //default none
/** @var Vector3 */
public $position;
/** @var string */
@ -41,12 +43,14 @@ class SpawnParticleEffectPacket extends DataPacket{
protected function decodePayload(){
$this->dimensionId = $this->getByte();
$this->entityUniqueId = $this->getEntityUniqueId();
$this->position = $this->getVector3();
$this->particleName = $this->getString();
}
protected function encodePayload(){
$this->putByte($this->dimensionId);
$this->putEntityUniqueId($this->entityUniqueId);
$this->putVector3($this->position);
$this->putString($this->particleName);
}

View File

@ -84,11 +84,15 @@ class StartGamePacket extends DataPacket{
/** @var float */
public $lightningLevel;
/** @var bool */
public $hasConfirmedPlatformLockedContent = false;
/** @var bool */
public $isMultiplayerGame = true;
/** @var bool */
public $hasLANBroadcast = true;
/** @var bool */
public $hasXboxLiveBroadcast = false;
/** @var int */
public $xboxLiveBroadcastMode = 0; //TODO: find values
/** @var int */
public $platformBroadcastMode = 0;
/** @var bool */
public $commandsEnabled;
/** @var bool */
@ -101,20 +105,12 @@ class StartGamePacket extends DataPacket{
public $hasBonusChestEnabled = false;
/** @var bool */
public $hasStartWithMapEnabled = false;
/** @var bool */
public $hasTrustPlayersEnabled = false;
/** @var int */
public $defaultPlayerPermission = PlayerPermissions::MEMBER; //TODO
/** @var int */
public $xboxLiveBroadcastMode = 0; //TODO: find values
/** @var int */
public $serverChunkTickRadius = 4; //TODO (leave as default for now)
/** @var bool */
public $hasPlatformBroadcast = false;
/** @var int */
public $platformBroadcastMode = 0;
/** @var bool */
public $xboxLiveBroadcastIntent = false;
/** @var bool */
public $hasLockedBehaviorPack = false;
/** @var bool */
@ -166,21 +162,18 @@ class StartGamePacket extends DataPacket{
$this->hasEduFeaturesEnabled = $this->getBool();
$this->rainLevel = $this->getLFloat();
$this->lightningLevel = $this->getLFloat();
$this->hasConfirmedPlatformLockedContent = $this->getBool();
$this->isMultiplayerGame = $this->getBool();
$this->hasLANBroadcast = $this->getBool();
$this->hasXboxLiveBroadcast = $this->getBool();
$this->xboxLiveBroadcastMode = $this->getVarInt();
$this->platformBroadcastMode = $this->getVarInt();
$this->commandsEnabled = $this->getBool();
$this->isTexturePacksRequired = $this->getBool();
$this->gameRules = $this->getGameRules();
$this->hasBonusChestEnabled = $this->getBool();
$this->hasStartWithMapEnabled = $this->getBool();
$this->hasTrustPlayersEnabled = $this->getBool();
$this->defaultPlayerPermission = $this->getVarInt();
$this->xboxLiveBroadcastMode = $this->getVarInt();
$this->serverChunkTickRadius = $this->getLInt();
$this->hasPlatformBroadcast = $this->getBool();
$this->platformBroadcastMode = $this->getVarInt();
$this->xboxLiveBroadcastIntent = $this->getBool();
$this->hasLockedBehaviorPack = $this->getBool();
$this->hasLockedResourcePack = $this->getBool();
$this->isFromLockedWorldTemplate = $this->getBool();
@ -228,21 +221,18 @@ class StartGamePacket extends DataPacket{
$this->putBool($this->hasEduFeaturesEnabled);
$this->putLFloat($this->rainLevel);
$this->putLFloat($this->lightningLevel);
$this->putBool($this->hasConfirmedPlatformLockedContent);
$this->putBool($this->isMultiplayerGame);
$this->putBool($this->hasLANBroadcast);
$this->putBool($this->hasXboxLiveBroadcast);
$this->putVarInt($this->xboxLiveBroadcastMode);
$this->putVarInt($this->platformBroadcastMode);
$this->putBool($this->commandsEnabled);
$this->putBool($this->isTexturePacksRequired);
$this->putGameRules($this->gameRules);
$this->putBool($this->hasBonusChestEnabled);
$this->putBool($this->hasStartWithMapEnabled);
$this->putBool($this->hasTrustPlayersEnabled);
$this->putVarInt($this->defaultPlayerPermission);
$this->putVarInt($this->xboxLiveBroadcastMode);
$this->putLInt($this->serverChunkTickRadius);
$this->putBool($this->hasPlatformBroadcast);
$this->putVarInt($this->platformBroadcastMode);
$this->putBool($this->xboxLiveBroadcastIntent);
$this->putBool($this->hasLockedBehaviorPack);
$this->putBool($this->hasLockedResourcePack);
$this->putBool($this->isFromLockedWorldTemplate);

View File

@ -41,6 +41,7 @@ class TextPacket extends DataPacket{
public const TYPE_SYSTEM = 6;
public const TYPE_WHISPER = 7;
public const TYPE_ANNOUNCEMENT = 8;
public const TYPE_JSON = 9;
/** @var int */
public $type;
@ -69,6 +70,7 @@ class TextPacket extends DataPacket{
case self::TYPE_RAW:
case self::TYPE_TIP:
case self::TYPE_SYSTEM:
case self::TYPE_JSON:
$this->message = $this->getString();
break;
@ -99,6 +101,7 @@ class TextPacket extends DataPacket{
case self::TYPE_RAW:
case self::TYPE_TIP:
case self::TYPE_SYSTEM:
case self::TYPE_JSON:
$this->putString($this->message);
break;

View File

@ -40,7 +40,18 @@ use function strlen;
use function substr;
class QueryHandler{
private $server, $lastToken, $token, $longData, $shortData, $timeout;
/** @var Server */
private $server;
/** @var string */
private $lastToken;
/** @var string */
private $token;
/** @var string */
private $longData;
/** @var string */
private $shortData;
/** @var float */
private $timeout;
public const HANDSHAKE = 9;
public const STATISTICS = 0;

View File

@ -384,14 +384,14 @@ class PluginManager{
*/
public function isCompatibleApi(string ...$versions) : bool{
$serverString = $this->server->getApiVersion();
$serverApi = array_pad(explode("-", $serverString), 2, "");
$serverApi = array_pad(explode("-", $serverString, 2), 2, "");
$serverNumbers = array_map("\intval", explode(".", $serverApi[0]));
foreach($versions as $version){
//Format: majorVersion.minorVersion.patch (3.0.0)
// or: majorVersion.minorVersion.patch-devBuild (3.0.0-alpha1)
if($version !== $serverString){
$pluginApi = array_pad(explode("-", $version), 2, ""); //0 = version, 1 = suffix (optional)
$pluginApi = array_pad(explode("-", $version, 2), 2, ""); //0 = version, 1 = suffix (optional)
if(strtoupper($pluginApi[1]) !== strtoupper($serverApi[1])){ //Different release phase (alpha vs. beta) or phase build (alpha.1 vs alpha.2)
continue;

View File

@ -0,0 +1,251 @@
{
"minecraft:air": 0,
"minecraft:stone": 1,
"minecraft:grass": 2,
"minecraft:dirt": 3,
"minecraft:cobblestone": 4,
"minecraft:planks": 5,
"minecraft:sapling": 6,
"minecraft:bedrock": 7,
"minecraft:flowing_water": 8,
"minecraft:water": 9,
"minecraft:flowing_lava": 10,
"minecraft:lava": 11,
"minecraft:sand": 12,
"minecraft:gravel": 13,
"minecraft:gold_ore": 14,
"minecraft:iron_ore": 15,
"minecraft:coal_ore": 16,
"minecraft:log": 17,
"minecraft:leaves": 18,
"minecraft:sponge": 19,
"minecraft:glass": 20,
"minecraft:lapis_ore": 21,
"minecraft:lapis_block": 22,
"minecraft:dispenser": 23,
"minecraft:sandstone": 24,
"minecraft:noteblock": 25,
"minecraft:bed": 26,
"minecraft:golden_rail": 27,
"minecraft:detector_rail": 28,
"minecraft:sticky_piston": 29,
"minecraft:web": 30,
"minecraft:tallgrass": 31,
"minecraft:deadbush": 32,
"minecraft:piston": 33,
"minecraft:pistonArmCollision": 34,
"minecraft:wool": 35,
"minecraft:element_0": 36,
"minecraft:yellow_flower": 37,
"minecraft:red_flower": 38,
"minecraft:brown_mushroom": 39,
"minecraft:red_mushroom": 40,
"minecraft:gold_block": 41,
"minecraft:iron_block": 42,
"minecraft:double_stone_slab": 43,
"minecraft:stone_slab": 44,
"minecraft:brick_block": 45,
"minecraft:tnt": 46,
"minecraft:bookshelf": 47,
"minecraft:mossy_cobblestone": 48,
"minecraft:obsidian": 49,
"minecraft:torch": 50,
"minecraft:fire": 51,
"minecraft:mob_spawner": 52,
"minecraft:oak_stairs": 53,
"minecraft:chest": 54,
"minecraft:redstone_wire": 55,
"minecraft:diamond_ore": 56,
"minecraft:diamond_block": 57,
"minecraft:crafting_table": 58,
"minecraft:wheat": 59,
"minecraft:farmland": 60,
"minecraft:furnace": 61,
"minecraft:lit_furnace": 62,
"minecraft:standing_sign": 63,
"minecraft:wooden_door": 64,
"minecraft:ladder": 65,
"minecraft:rail": 66,
"minecraft:stone_stairs": 67,
"minecraft:wall_sign": 68,
"minecraft:lever": 69,
"minecraft:stone_pressure_plate": 70,
"minecraft:iron_door": 71,
"minecraft:wooden_pressure_plate": 72,
"minecraft:redstone_ore": 73,
"minecraft:lit_redstone_ore": 74,
"minecraft:unlit_redstone_torch": 75,
"minecraft:redstone_torch": 76,
"minecraft:stone_button": 77,
"minecraft:snow_layer": 78,
"minecraft:ice": 79,
"minecraft:snow": 80,
"minecraft:cactus": 81,
"minecraft:clay": 82,
"minecraft:reeds": 83,
"minecraft:jukebox": 84,
"minecraft:fence": 85,
"minecraft:pumpkin": 86,
"minecraft:netherrack": 87,
"minecraft:soul_sand": 88,
"minecraft:glowstone": 89,
"minecraft:portal": 90,
"minecraft:lit_pumpkin": 91,
"minecraft:cake": 92,
"minecraft:unpowered_repeater": 93,
"minecraft:powered_repeater": 94,
"minecraft:invisibleBedrock": 95,
"minecraft:trapdoor": 96,
"minecraft:monster_egg": 97,
"minecraft:stonebrick": 98,
"minecraft:brown_mushroom_block": 99,
"minecraft:red_mushroom_block": 100,
"minecraft:iron_bars": 101,
"minecraft:glass_pane": 102,
"minecraft:melon_block": 103,
"minecraft:pumpkin_stem": 104,
"minecraft:melon_stem": 105,
"minecraft:vine": 106,
"minecraft:fence_gate": 107,
"minecraft:brick_stairs": 108,
"minecraft:stone_brick_stairs": 109,
"minecraft:mycelium": 110,
"minecraft:waterlily": 111,
"minecraft:nether_brick": 112,
"minecraft:nether_brick_fence": 113,
"minecraft:nether_brick_stairs": 114,
"minecraft:nether_wart": 115,
"minecraft:enchanting_table": 116,
"minecraft:brewing_stand": 117,
"minecraft:cauldron": 118,
"minecraft:end_portal": 119,
"minecraft:end_portal_frame": 120,
"minecraft:end_stone": 121,
"minecraft:dragon_egg": 122,
"minecraft:redstone_lamp": 123,
"minecraft:lit_redstone_lamp": 124,
"minecraft:dropper": 125,
"minecraft:activator_rail": 126,
"minecraft:cocoa": 127,
"minecraft:sandstone_stairs": 128,
"minecraft:emerald_ore": 129,
"minecraft:ender_chest": 130,
"minecraft:tripwire_hook": 131,
"minecraft:tripWire": 132,
"minecraft:emerald_block": 133,
"minecraft:spruce_stairs": 134,
"minecraft:birch_stairs": 135,
"minecraft:jungle_stairs": 136,
"minecraft:command_block": 137,
"minecraft:beacon": 138,
"minecraft:cobblestone_wall": 139,
"minecraft:flower_pot": 140,
"minecraft:carrots": 141,
"minecraft:potatoes": 142,
"minecraft:wooden_button": 143,
"minecraft:skull": 144,
"minecraft:anvil": 145,
"minecraft:trapped_chest": 146,
"minecraft:light_weighted_pressure_plate": 147,
"minecraft:heavy_weighted_pressure_plate": 148,
"minecraft:unpowered_comparator": 149,
"minecraft:powered_comparator": 150,
"minecraft:daylight_detector": 151,
"minecraft:redstone_block": 152,
"minecraft:quartz_ore": 153,
"minecraft:hopper": 154,
"minecraft:quartz_block": 155,
"minecraft:quartz_stairs": 156,
"minecraft:double_wooden_slab": 157,
"minecraft:wooden_slab": 158,
"minecraft:stained_hardened_clay": 159,
"minecraft:stained_glass_pane": 160,
"minecraft:leaves2": 161,
"minecraft:log2": 162,
"minecraft:acacia_stairs": 163,
"minecraft:dark_oak_stairs": 164,
"minecraft:slime": 165,
"minecraft:iron_trapdoor": 167,
"minecraft:prismarine": 168,
"minecraft:seaLantern": 169,
"minecraft:hay_block": 170,
"minecraft:carpet": 171,
"minecraft:hardened_clay": 172,
"minecraft:coal_block": 173,
"minecraft:packed_ice": 174,
"minecraft:double_plant": 175,
"minecraft:standing_banner": 176,
"minecraft:wall_banner": 177,
"minecraft:daylight_detector_inverted": 178,
"minecraft:red_sandstone": 179,
"minecraft:red_sandstone_stairs": 180,
"minecraft:double_stone_slab2": 181,
"minecraft:stone_slab2": 182,
"minecraft:spruce_fence_gate": 183,
"minecraft:birch_fence_gate": 184,
"minecraft:jungle_fence_gate": 185,
"minecraft:dark_oak_fence_gate": 186,
"minecraft:acacia_fence_gate": 187,
"minecraft:repeating_command_block": 188,
"minecraft:chain_command_block": 189,
"minecraft:hard_glass_pane": 190,
"minecraft:hard_stained_glass_pane": 191,
"minecraft:chemical_heat": 192,
"minecraft:spruce_door": 193,
"minecraft:birch_door": 194,
"minecraft:jungle_door": 195,
"minecraft:acacia_door": 196,
"minecraft:dark_oak_door": 197,
"minecraft:grass_path": 198,
"minecraft:frame": 199,
"minecraft:chorus_flower": 200,
"minecraft:purpur_block": 201,
"minecraft:colored_torch_rg": 202,
"minecraft:purpur_stairs": 203,
"minecraft:colored_torch_bp": 204,
"minecraft:undyed_shulker_box": 205,
"minecraft:end_bricks": 206,
"minecraft:frosted_ice": 207,
"minecraft:end_rod": 208,
"minecraft:end_gateway": 209,
"minecraft:magma": 213,
"minecraft:nether_wart_block": 214,
"minecraft:red_nether_brick": 215,
"minecraft:bone_block": 216,
"minecraft:shulker_box": 218,
"minecraft:purple_glazed_terracotta": 219,
"minecraft:white_glazed_terracotta": 220,
"minecraft:orange_glazed_terracotta": 221,
"minecraft:magenta_glazed_terracotta": 222,
"minecraft:light_blue_glazed_terracotta": 223,
"minecraft:yellow_glazed_terracotta": 224,
"minecraft:lime_glazed_terracotta": 225,
"minecraft:pink_glazed_terracotta": 226,
"minecraft:gray_glazed_terracotta": 227,
"minecraft:silver_glazed_terracotta": 228,
"minecraft:cyan_glazed_terracotta": 229,
"minecraft:blue_glazed_terracotta": 231,
"minecraft:brown_glazed_terracotta": 232,
"minecraft:green_glazed_terracotta": 233,
"minecraft:red_glazed_terracotta": 234,
"minecraft:black_glazed_terracotta": 235,
"minecraft:concrete": 236,
"minecraft:concretePowder": 237,
"minecraft:chemistry_table": 238,
"minecraft:underwater_torch": 239,
"minecraft:chorus_plant": 240,
"minecraft:stained_glass": 241,
"minecraft:podzol": 243,
"minecraft:beetroot": 244,
"minecraft:stonecutter": 245,
"minecraft:glowingobsidian": 246,
"minecraft:netherreactor": 247,
"minecraft:info_update": 248,
"minecraft:info_update2": 249,
"minecraft:movingBlock": 250,
"minecraft:observer": 251,
"minecraft:structure_block": 252,
"minecraft:hard_glass": 253,
"minecraft:hard_stained_glass": 254,
"minecraft:reserved6": 255
}

View File

@ -109,13 +109,6 @@ player:
level-settings:
#The default format that levels will use when created
default-format: pmanvil
#Automatically change levels tick rate to maintain 20 ticks per second
auto-tick-rate: true
auto-tick-rate-limit: 20
#Sets the base tick rate (1 = 20 ticks per second, 2 = 10 ticks per second, etc.)
base-tick-rate: 1
#Tick all players each tick even when other settings disallow this.
always-tick-players: false
chunk-sending:
#To change server normal render distance, change view-distance in server.properties.

File diff suppressed because one or more lines are too long

View File

@ -64,7 +64,7 @@ class TaskScheduler{
/**
* @param Task $task
*
* @return null|TaskHandler
* @return TaskHandler
*/
public function scheduleTask(Task $task){
return $this->addTask($task, -1, -1);
@ -74,7 +74,7 @@ class TaskScheduler{
* @param Task $task
* @param int $delay
*
* @return null|TaskHandler
* @return TaskHandler
*/
public function scheduleDelayedTask(Task $task, int $delay){
return $this->addTask($task, $delay, -1);
@ -84,7 +84,7 @@ class TaskScheduler{
* @param Task $task
* @param int $period
*
* @return null|TaskHandler
* @return TaskHandler
*/
public function scheduleRepeatingTask(Task $task, int $period){
return $this->addTask($task, -1, $period);
@ -95,7 +95,7 @@ class TaskScheduler{
* @param int $delay
* @param int $period
*
* @return null|TaskHandler
* @return TaskHandler
*/
public function scheduleDelayedRepeatingTask(Task $task, int $delay, int $period){
return $this->addTask($task, $delay, $period);
@ -139,7 +139,7 @@ class TaskScheduler{
* @param int $delay
* @param int $period
*
* @return null|TaskHandler
* @return TaskHandler
*
* @throws \InvalidStateException
*/

View File

@ -33,6 +33,8 @@ use function array_pad;
use function array_slice;
use function explode;
use function implode;
use function mb_check_encoding;
use function mb_scrub;
use function sprintf;
class Sign extends Spawnable{
@ -57,6 +59,9 @@ class Sign extends Spawnable{
}
}
}
$this->text = array_map(function(string $line) : string{
return mb_scrub($line, 'UTF-8');
}, $this->text);
}
protected function writeSaveData(CompoundTag $nbt) : void{
@ -79,16 +84,16 @@ class Sign extends Spawnable{
*/
public function setText(?string $line1 = "", ?string $line2 = "", ?string $line3 = "", ?string $line4 = "") : void{
if($line1 !== null){
$this->text[0] = $line1;
$this->setLine(0, $line1, false);
}
if($line2 !== null){
$this->text[1] = $line2;
$this->setLine(1, $line2, false);
}
if($line3 !== null){
$this->text[2] = $line3;
$this->setLine(2, $line3, false);
}
if($line4 !== null){
$this->text[3] = $line4;
$this->setLine(3, $line4, false);
}
$this->onChanged();
@ -103,6 +108,9 @@ class Sign extends Spawnable{
if($index < 0 or $index > 3){
throw new \InvalidArgumentException("Index must be in the range 0-3!");
}
if(!mb_check_encoding($line, 'UTF-8')){
throw new \InvalidArgumentException("Text must be valid UTF-8");
}
$this->text[$index] = $line;
if($update){

View File

@ -30,7 +30,13 @@ use function count;
class Color{
/** @var int */
protected $a, $r, $g, $b;
protected $a;
/** @var int */
protected $r;
/** @var int */
protected $g;
/** @var int */
protected $b;
public function __construct(int $r, int $g, int $b, int $a = 0xff){
$this->r = $r & 0xff;

View File

@ -25,6 +25,7 @@ namespace pocketmine\utils;
use function is_array;
use function json_encode;
use function mb_scrub;
use function preg_quote;
use function preg_replace;
use function preg_split;
@ -77,18 +78,19 @@ abstract class TextFormat{
}
/**
* Cleans the string from Minecraft codes and ANSI Escape Codes
* Cleans the string from Minecraft codes, ANSI Escape Codes and invalid UTF-8 characters
*
* @param string $string
* @param bool $removeFormat
*
* @return string
* @return string valid clean UTF-8
*/
public static function clean(string $string, bool $removeFormat = true) : string{
$string = mb_scrub($string, 'UTF-8');
if($removeFormat){
return str_replace(TextFormat::ESCAPE, "", preg_replace(["/" . TextFormat::ESCAPE . "[0-9a-fk-or]/", "/\x1b[\\(\\][[0-9;\\[\\(]+[Bm]/"], "", $string));
$string = str_replace(TextFormat::ESCAPE, "", preg_replace("/" . TextFormat::ESCAPE . "[0-9a-fk-or]/u", "", $string));
}
return str_replace("\x1b", "", preg_replace("/\x1b[\\(\\][[0-9;\\[\\(]+[Bm]/", "", $string));
return str_replace("\x1b", "", preg_replace("/\x1b[\\(\\][[0-9;\\[\\(]+[Bm]/u", "", $string));
}
/**

View File

@ -0,0 +1,59 @@
<?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\inventory;
use PHPUnit\Framework\TestCase;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
class BaseInventoryTest extends TestCase{
public static function setUpBeforeClass(){
ItemFactory::init();
}
public function testAddItemDifferentUserData() : void{
$inv = new class extends BaseInventory{
public function getDefaultSize() : int{
return 1;
}
public function getName() : string{
return "";
}
};
$item1 = ItemFactory::get(Item::ARROW, 0, 1);
$item2 = ItemFactory::get(Item::ARROW, 0, 1)->setCustomName("TEST");
$inv->addItem(clone $item1);
self::assertFalse($inv->canAddItem($item2), "Item WITHOUT userdata should not stack with item WITH userdata");
self::assertNotEmpty($inv->addItem($item2));
$inv->clearAll();
self::assertEmpty($inv->getContents());
$inv->addItem(clone $item2);
self::assertFalse($inv->canAddItem($item1), "Item WITH userdata should not stack with item WITHOUT userdata");
self::assertNotEmpty($inv->addItem($item1));
}
}

View File

@ -25,28 +25,94 @@ namespace pocketmine\level\format\io\region;
use PHPUnit\Framework\TestCase;
use pocketmine\level\format\ChunkException;
use function file_exists;
use function random_bytes;
use function str_repeat;
use function sys_get_temp_dir;
use function unlink;
class RegionLoaderTest extends TestCase{
public function testChunkTooBig() : void{
$r = new RegionLoader(sys_get_temp_dir() . '/chunk_too_big.testregion_' . bin2hex(random_bytes(4)), 0, 0);
$r->open();
/** @var string */
private $regionPath;
/** @var RegionLoader */
private $region;
public function setUp(){
$this->regionPath = sys_get_temp_dir() . '/test.testregion';
if(file_exists($this->regionPath)){
unlink($this->regionPath);
}
$this->region = new RegionLoader($this->regionPath, 0, 0);
$this->region->open();
}
public function tearDown(){
$this->region->close();
if(file_exists($this->regionPath)){
unlink($this->regionPath);
}
}
public function testChunkTooBig() : void{
$this->expectException(ChunkException::class);
$r->writeChunk(0, 0, str_repeat("a", 1044476));
$this->region->writeChunk(0, 0, str_repeat("a", 1044476));
}
public function testChunkMaxSize() : void{
$data = str_repeat("a", 1044475);
$path = sys_get_temp_dir() . '/chunk_just_fits.testregion_' . bin2hex(random_bytes(4));
$r = new RegionLoader($path, 0, 0);
$r->open();
$this->region->writeChunk(0, 0, $data);
$this->region->close();
$r->writeChunk(0, 0, $data);
$r->close();
$r = new RegionLoader($path, 0, 0);
$r = new RegionLoader($this->regionPath, 0, 0);
$r->open();
self::assertSame($data, $r->readChunk(0, 0));
}
public function outOfBoundsCoordsProvider() : \Generator{
yield [-1, -1];
yield [32, 32];
yield [-1, 32];
yield [32, -1];
}
/**
* @dataProvider outOfBoundsCoordsProvider
* @param int $x
* @param int $z
*
* @throws ChunkException
* @throws \InvalidArgumentException
*/
public function testWriteChunkOutOfBounds(int $x, int $z) : void{
$this->expectException(\InvalidArgumentException::class);
$this->region->writeChunk($x, $z, str_repeat("\x00", 1000));
}
public function testReadWriteChunkInBounds() : void{
$dat = random_bytes(1000);
for($x = 0; $x < 32; ++$x){
for($z = 0; $z < 32; ++$z){
$this->region->writeChunk($x, $z, $dat);
}
}
for($x = 0; $x < 32; ++$x){
for($z = 0; $z < 32; ++$z){
self::assertSame($dat, $this->region->readChunk($x, $z));
}
}
}
/**
* @dataProvider outOfBoundsCoordsProvider
* @param int $x
* @param int $z
*
* @throws \InvalidArgumentException
* @throws \pocketmine\level\format\io\exception\CorruptedChunkException
*/
public function testReadChunkOutOfBounds(int $x, int $z) : void{
$this->expectException(\InvalidArgumentException::class);
$this->region->readChunk($x, $z);
}
}