mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-12 20:35:11 +00:00
Compare commits
60 Commits
Author | SHA1 | Date | |
---|---|---|---|
f1c071ce7f | |||
e2f46a4358 | |||
4c08a05fae | |||
b473ffdedc | |||
60dddcd12a | |||
93c26a0b0c | |||
545ec9c881 | |||
d5a1961e6b | |||
6bc79149c3 | |||
e018311e73 | |||
71d02e5870 | |||
d312aef1ac | |||
200de3fe84 | |||
f560a6efea | |||
7ecd7fd13f | |||
b893645a81 | |||
b19b3134ad | |||
243f86b0a0 | |||
9156cbc269 | |||
43fe6a1934 | |||
342a74ffcb | |||
2183bf875c | |||
8cc2a4ce5d | |||
f61e14e341 | |||
7b24fbc8db | |||
c4f3426bae | |||
046c39b02e | |||
5c3eed40b3 | |||
3e5237b6e0 | |||
d9a867016c | |||
a50a863ab7 | |||
1b03168b88 | |||
6b9fee05d6 | |||
45a18ffe1e | |||
f0182c9996 | |||
2d88058710 | |||
ab48d85c35 | |||
d9b7a28747 | |||
31ceafa111 | |||
694d7d4e20 | |||
2da2fdd6d4 | |||
c1c56f29bb | |||
9b820a0849 | |||
3128449033 | |||
a60154e0b7 | |||
4cbbf2e91c | |||
b0624aff9f | |||
4835537886 | |||
925da62afa | |||
d1ee9eb960 | |||
5e68858ebf | |||
d749f19c73 | |||
58067b2ad1 | |||
af5637e050 | |||
a524b0e447 | |||
88a5e92c20 | |||
b876ae4ef8 | |||
c5cd813b76 | |||
bc2dff3f51 | |||
839d5eab7b |
@ -3515,11 +3515,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
/**
|
||||
* Handles player data saving
|
||||
*
|
||||
* @param bool $async
|
||||
*
|
||||
* @throws \InvalidStateException if the player is closed
|
||||
*/
|
||||
public function save(bool $async = false){
|
||||
public function save(){
|
||||
if($this->closed){
|
||||
throw new \InvalidStateException("Tried to save closed player");
|
||||
}
|
||||
@ -3556,7 +3554,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->namedtag->setLong("lastPlayed", (int) floor(microtime(true) * 1000));
|
||||
|
||||
if($this->username != "" and $this->namedtag instanceof CompoundTag){
|
||||
$this->server->saveOfflinePlayerData($this->username, $this->namedtag, $async);
|
||||
$this->server->saveOfflinePlayerData($this->username, $this->namedtag);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ namespace pocketmine {
|
||||
use pocketmine\wizard\SetupWizard;
|
||||
|
||||
const NAME = "PocketMine-MP";
|
||||
const BASE_VERSION = "3.2.6";
|
||||
const BASE_VERSION = "3.3.3";
|
||||
const IS_DEVELOPMENT_BUILD = false;
|
||||
const BUILD_NUMBER = 0;
|
||||
|
||||
|
@ -91,7 +91,6 @@ use pocketmine\plugin\PluginManager;
|
||||
use pocketmine\plugin\ScriptPluginLoader;
|
||||
use pocketmine\resourcepacks\ResourcePackManager;
|
||||
use pocketmine\scheduler\AsyncPool;
|
||||
use pocketmine\scheduler\FileWriteTask;
|
||||
use pocketmine\scheduler\SendUsageTask;
|
||||
use pocketmine\snooze\SleeperHandler;
|
||||
use pocketmine\snooze\SleeperNotifier;
|
||||
@ -805,9 +804,8 @@ class Server{
|
||||
/**
|
||||
* @param string $name
|
||||
* @param CompoundTag $nbtTag
|
||||
* @param bool $async
|
||||
*/
|
||||
public function saveOfflinePlayerData(string $name, CompoundTag $nbtTag, bool $async = false){
|
||||
public function saveOfflinePlayerData(string $name, CompoundTag $nbtTag){
|
||||
$ev = new PlayerDataSaveEvent($nbtTag, $name);
|
||||
$ev->setCancelled(!$this->shouldSavePlayerData());
|
||||
|
||||
@ -816,11 +814,7 @@ class Server{
|
||||
if(!$ev->isCancelled()){
|
||||
$nbt = new BigEndianNBTStream();
|
||||
try{
|
||||
if($async){
|
||||
$this->asyncPool->submitTask(new FileWriteTask($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData())));
|
||||
}else{
|
||||
file_put_contents($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData()));
|
||||
}
|
||||
file_put_contents($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData()));
|
||||
}catch(\Throwable $e){
|
||||
$this->logger->critical($this->getLanguage()->translateString("pocketmine.data.saveError", [$name, $e->getMessage()]));
|
||||
$this->logger->logException($e);
|
||||
@ -1449,6 +1443,7 @@ class Server{
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error2"));
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error3"));
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error4", ["settings.enable-dev-builds"]));
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error5", ["https://github.com/pmmp/PocketMine-MP/releases"]));
|
||||
$this->forceShutdown();
|
||||
return;
|
||||
}
|
||||
@ -2246,6 +2241,12 @@ class Server{
|
||||
|
||||
$this->forceShutdown();
|
||||
$this->isRunning = false;
|
||||
|
||||
//Force minimum uptime to be >= 120 seconds, to reduce the impact of spammy crash loops
|
||||
$spacing = ((int) \pocketmine\START_TIME) - time() + 120;
|
||||
if($spacing > 0){
|
||||
sleep($spacing);
|
||||
}
|
||||
@Utils::kill(getmypid());
|
||||
exit(1);
|
||||
}
|
||||
@ -2318,7 +2319,7 @@ class Server{
|
||||
$pk = new PlayerListPacket();
|
||||
$pk->type = PlayerListPacket::TYPE_ADD;
|
||||
|
||||
$pk->entries[] = PlayerListEntry::createAdditionEntry($uuid, $entityId, $name, "", 0, $skin, $xboxUserId);
|
||||
$pk->entries[] = PlayerListEntry::createAdditionEntry($uuid, $entityId, $name, $skin, $xboxUserId);
|
||||
|
||||
$this->broadcastPacket($players ?? $this->playerList, $pk);
|
||||
}
|
||||
@ -2341,7 +2342,7 @@ class Server{
|
||||
$pk = new PlayerListPacket();
|
||||
$pk->type = PlayerListPacket::TYPE_ADD;
|
||||
foreach($this->playerList as $player){
|
||||
$pk->entries[] = PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), "", 0, $player->getSkin(), $player->getXuid());
|
||||
$pk->entries[] = PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), $player->getSkin(), $player->getXuid());
|
||||
}
|
||||
|
||||
$p->dataPacket($pk);
|
||||
@ -2397,7 +2398,7 @@ class Server{
|
||||
Timings::$worldSaveTimer->startTiming();
|
||||
foreach($this->players as $index => $player){
|
||||
if($player->spawned){
|
||||
$player->save(true);
|
||||
$player->save();
|
||||
}elseif(!$player->isConnected()){
|
||||
$this->removePlayer($player);
|
||||
}
|
||||
|
@ -206,34 +206,38 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
public const DATA_FLAG_INTERESTED = 26;
|
||||
public const DATA_FLAG_CHARGED = 27;
|
||||
public const DATA_FLAG_TAMED = 28;
|
||||
public const DATA_FLAG_LEASHED = 29;
|
||||
public const DATA_FLAG_SHEARED = 30;
|
||||
public const DATA_FLAG_GLIDING = 31;
|
||||
public const DATA_FLAG_ELDER = 32;
|
||||
public const DATA_FLAG_MOVING = 33;
|
||||
public const DATA_FLAG_BREATHING = 34;
|
||||
public const DATA_FLAG_CHESTED = 35;
|
||||
public const DATA_FLAG_STACKABLE = 36;
|
||||
public const DATA_FLAG_SHOWBASE = 37;
|
||||
public const DATA_FLAG_REARING = 38;
|
||||
public const DATA_FLAG_VIBRATING = 39;
|
||||
public const DATA_FLAG_IDLING = 40;
|
||||
public const DATA_FLAG_EVOKER_SPELL = 41;
|
||||
public const DATA_FLAG_CHARGE_ATTACK = 42;
|
||||
public const DATA_FLAG_WASD_CONTROLLED = 43;
|
||||
public const DATA_FLAG_CAN_POWER_JUMP = 44;
|
||||
public const DATA_FLAG_LINGER = 45;
|
||||
public const DATA_FLAG_HAS_COLLISION = 46;
|
||||
public const DATA_FLAG_AFFECTED_BY_GRAVITY = 47;
|
||||
public const DATA_FLAG_FIRE_IMMUNE = 48;
|
||||
public const DATA_FLAG_DANCING = 49;
|
||||
public const DATA_FLAG_ENCHANTED = 50;
|
||||
public const DATA_FLAG_SHOW_TRIDENT_ROPE = 51; // tridents show an animated rope when enchanted with loyalty after they are thrown and return to their owner. To be combined with DATA_OWNER_EID
|
||||
public const DATA_FLAG_CONTAINER_PRIVATE = 52; //inventory is private, doesn't drop contents when killed if true
|
||||
//53 TransformationComponent
|
||||
public const DATA_FLAG_SPIN_ATTACK = 54;
|
||||
public const DATA_FLAG_SWIMMING = 55;
|
||||
public const DATA_FLAG_BRIBED = 56; //dolphins have this set when they go to find treasure for the player
|
||||
public const DATA_FLAG_ORPHANED = 29;
|
||||
public const DATA_FLAG_LEASHED = 30;
|
||||
public const DATA_FLAG_SHEARED = 31;
|
||||
public const DATA_FLAG_GLIDING = 32;
|
||||
public const DATA_FLAG_ELDER = 33;
|
||||
public const DATA_FLAG_MOVING = 34;
|
||||
public const DATA_FLAG_BREATHING = 35;
|
||||
public const DATA_FLAG_CHESTED = 36;
|
||||
public const DATA_FLAG_STACKABLE = 37;
|
||||
public const DATA_FLAG_SHOWBASE = 38;
|
||||
public const DATA_FLAG_REARING = 39;
|
||||
public const DATA_FLAG_VIBRATING = 40;
|
||||
public const DATA_FLAG_IDLING = 41;
|
||||
public const DATA_FLAG_EVOKER_SPELL = 42;
|
||||
public const DATA_FLAG_CHARGE_ATTACK = 43;
|
||||
public const DATA_FLAG_WASD_CONTROLLED = 44;
|
||||
public const DATA_FLAG_CAN_POWER_JUMP = 45;
|
||||
public const DATA_FLAG_LINGER = 46;
|
||||
public const DATA_FLAG_HAS_COLLISION = 47;
|
||||
public const DATA_FLAG_AFFECTED_BY_GRAVITY = 48;
|
||||
public const DATA_FLAG_FIRE_IMMUNE = 49;
|
||||
public const DATA_FLAG_DANCING = 50;
|
||||
public const DATA_FLAG_ENCHANTED = 51;
|
||||
public const DATA_FLAG_SHOW_TRIDENT_ROPE = 52; // tridents show an animated rope when enchanted with loyalty after they are thrown and return to their owner. To be combined with DATA_OWNER_EID
|
||||
public const DATA_FLAG_CONTAINER_PRIVATE = 53; //inventory is private, doesn't drop contents when killed if true
|
||||
//54 TransformationComponent
|
||||
public const DATA_FLAG_SPIN_ATTACK = 55;
|
||||
public const DATA_FLAG_SWIMMING = 56;
|
||||
public const DATA_FLAG_BRIBED = 57; //dolphins have this set when they go to find treasure for the player
|
||||
public const DATA_FLAG_PREGNANT = 58;
|
||||
public const DATA_FLAG_LAYING_EGG = 59;
|
||||
//60 ??
|
||||
|
||||
public static $entityCount = 1;
|
||||
/** @var Entity[] */
|
||||
|
@ -841,7 +841,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
/* we don't use Server->updatePlayerListData() because that uses batches, which could cause race conditions in async compression mode */
|
||||
$pk = new PlayerListPacket();
|
||||
$pk->type = PlayerListPacket::TYPE_ADD;
|
||||
$pk->entries = [PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), $this->getName(), 0, $this->skin)];
|
||||
$pk->entries = [PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), $this->skin)];
|
||||
$player->dataPacket($pk);
|
||||
}
|
||||
|
||||
|
@ -655,11 +655,11 @@ abstract class Living extends Entity implements Damageable{
|
||||
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
if($this->doEffectsTick($tickDiff)){
|
||||
$hasUpdate = true;
|
||||
}
|
||||
|
||||
if($this->isAlive()){
|
||||
if($this->doEffectsTick($tickDiff)){
|
||||
$hasUpdate = true;
|
||||
}
|
||||
|
||||
if($this->isInsideOfSolid()){
|
||||
$hasUpdate = true;
|
||||
$ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_SUFFOCATION, 1);
|
||||
|
@ -208,7 +208,7 @@ class ItemEntity extends Entity{
|
||||
$item = $this->getItem();
|
||||
$playerInventory = $player->getInventory();
|
||||
|
||||
if(!($item instanceof Item) or ($player->isSurvival() and !$playerInventory->canAddItem($item))){
|
||||
if($player->isSurvival() and !$playerInventory->canAddItem($item)){
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -37,67 +37,68 @@ class PaintingItem extends Item{
|
||||
}
|
||||
|
||||
public function onActivate(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : bool{
|
||||
if(!$blockClicked->isTransparent() and $face > 1 and !$blockReplace->isSolid()){
|
||||
/** @var PaintingMotive[] $motives */
|
||||
$motives = [];
|
||||
if($face === Vector3::SIDE_DOWN or $face === Vector3::SIDE_UP){
|
||||
return false;
|
||||
}
|
||||
|
||||
$totalDimension = 0;
|
||||
foreach(PaintingMotive::getAll() as $motive){
|
||||
$currentTotalDimension = $motive->getHeight() + $motive->getWidth();
|
||||
if($currentTotalDimension < $totalDimension){
|
||||
continue;
|
||||
$motives = [];
|
||||
|
||||
$totalDimension = 0;
|
||||
foreach(PaintingMotive::getAll() as $motive){
|
||||
$currentTotalDimension = $motive->getHeight() + $motive->getWidth();
|
||||
if($currentTotalDimension < $totalDimension){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(Painting::canFit($player->level, $blockReplace, $face, true, $motive)){
|
||||
if($currentTotalDimension > $totalDimension){
|
||||
$totalDimension = $currentTotalDimension;
|
||||
/*
|
||||
* This drops all motive possibilities smaller than this
|
||||
* We use the total of height + width to allow equal chance of horizontal/vertical paintings
|
||||
* when there is an L-shape of space available.
|
||||
*/
|
||||
$motives = [];
|
||||
}
|
||||
|
||||
if(Painting::canFit($player->level, $blockReplace, $face, true, $motive)){
|
||||
if($currentTotalDimension > $totalDimension){
|
||||
$totalDimension = $currentTotalDimension;
|
||||
/*
|
||||
* This drops all motive possibilities smaller than this
|
||||
* We use the total of height + width to allow equal chance of horizontal/vertical paintings
|
||||
* when there is an L-shape of space available.
|
||||
*/
|
||||
$motives = [];
|
||||
}
|
||||
|
||||
$motives[] = $motive;
|
||||
}
|
||||
$motives[] = $motive;
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($motives)){ //No space available
|
||||
return false;
|
||||
}
|
||||
if(empty($motives)){ //No space available
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var PaintingMotive $motive */
|
||||
$motive = $motives[array_rand($motives)];
|
||||
/** @var PaintingMotive $motive */
|
||||
$motive = $motives[array_rand($motives)];
|
||||
|
||||
static $directions = [
|
||||
Vector3::SIDE_SOUTH => 0,
|
||||
Vector3::SIDE_WEST => 1,
|
||||
Vector3::SIDE_NORTH => 2,
|
||||
Vector3::SIDE_EAST => 3
|
||||
];
|
||||
static $directions = [
|
||||
Vector3::SIDE_SOUTH => 0,
|
||||
Vector3::SIDE_WEST => 1,
|
||||
Vector3::SIDE_NORTH => 2,
|
||||
Vector3::SIDE_EAST => 3
|
||||
];
|
||||
|
||||
$direction = $directions[$face] ?? -1;
|
||||
if($direction === -1){
|
||||
return false;
|
||||
}
|
||||
$direction = $directions[$face] ?? -1;
|
||||
if($direction === -1){
|
||||
return false;
|
||||
}
|
||||
|
||||
$nbt = Entity::createBaseNBT($blockReplace, null, $direction * 90, 0);
|
||||
$nbt->setByte("Direction", $direction);
|
||||
$nbt->setString("Motive", $motive->getName());
|
||||
$nbt->setInt("TileX", $blockClicked->getFloorX());
|
||||
$nbt->setInt("TileY", $blockClicked->getFloorY());
|
||||
$nbt->setInt("TileZ", $blockClicked->getFloorZ());
|
||||
$nbt = Entity::createBaseNBT($blockReplace, null, $direction * 90, 0);
|
||||
$nbt->setByte("Direction", $direction);
|
||||
$nbt->setString("Motive", $motive->getName());
|
||||
$nbt->setInt("TileX", $blockClicked->getFloorX());
|
||||
$nbt->setInt("TileY", $blockClicked->getFloorY());
|
||||
$nbt->setInt("TileZ", $blockClicked->getFloorZ());
|
||||
|
||||
$entity = Entity::createEntity("Painting", $blockReplace->getLevel(), $nbt);
|
||||
$entity = Entity::createEntity("Painting", $blockReplace->getLevel(), $nbt);
|
||||
|
||||
if($entity instanceof Entity){
|
||||
--$this->count;
|
||||
$entity->spawnToAll();
|
||||
if($entity instanceof Entity){
|
||||
--$this->count;
|
||||
$entity->spawnToAll();
|
||||
|
||||
$player->getLevel()->broadcastLevelEvent($blockReplace->add(0.5, 0.5, 0.5), LevelEventPacket::EVENT_SOUND_ITEMFRAME_PLACE); //item frame and painting have the same sound
|
||||
return true;
|
||||
}
|
||||
$player->getLevel()->broadcastLevelEvent($blockReplace->add(0.5, 0.5, 0.5), LevelEventPacket::EVENT_SOUND_ITEMFRAME_PLACE); //item frame and painting have the same sound
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
Submodule src/pocketmine/lang/locale updated: 0cd3f8ef30...1d525a7c1c
@ -2979,6 +2979,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->unloadChunkRequest($X, $Z, true);
|
||||
}
|
||||
}
|
||||
$chunk->collectGarbage();
|
||||
}
|
||||
|
||||
$this->provider->doGarbageCollection();
|
||||
|
@ -836,18 +836,13 @@ class Chunk{
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of empty subchunks
|
||||
* Disposes of empty subchunks and frees data where possible
|
||||
*/
|
||||
public function pruneEmptySubChunks(){
|
||||
public function collectGarbage() : void{
|
||||
foreach($this->subChunks as $y => $subChunk){
|
||||
if($subChunk instanceof EmptySubChunk){
|
||||
continue;
|
||||
}elseif($subChunk->isEmpty()){ //normal subchunk full of air, remove it and replace it with an empty stub
|
||||
if(!($subChunk instanceof EmptySubChunk) and $subChunk->isEmpty()){ //normal subchunk full of air, remove it and replace it with an empty stub
|
||||
$this->subChunks[$y] = $this->emptySubChunk;
|
||||
}else{
|
||||
continue; //do not set changed
|
||||
}
|
||||
$this->hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -887,20 +882,31 @@ class Chunk{
|
||||
$stream = new BinaryStream();
|
||||
$stream->putInt($this->x);
|
||||
$stream->putInt($this->z);
|
||||
$count = 0;
|
||||
$subChunks = "";
|
||||
foreach($this->subChunks as $y => $subChunk){
|
||||
if($subChunk instanceof EmptySubChunk){
|
||||
continue;
|
||||
$stream->putByte(($this->lightPopulated ? 4 : 0) | ($this->terrainPopulated ? 2 : 0) | ($this->terrainGenerated ? 1 : 0));
|
||||
if($this->terrainGenerated){
|
||||
//subchunks
|
||||
$count = 0;
|
||||
$subChunks = "";
|
||||
foreach($this->subChunks as $y => $subChunk){
|
||||
if($subChunk instanceof EmptySubChunk){
|
||||
continue;
|
||||
}
|
||||
++$count;
|
||||
$subChunks .= chr($y) . $subChunk->getBlockIdArray() . $subChunk->getBlockDataArray();
|
||||
if($this->lightPopulated){
|
||||
$subChunks .= $subChunk->getBlockSkyLightArray() . $subChunk->getBlockLightArray();
|
||||
}
|
||||
}
|
||||
$stream->putByte($count);
|
||||
$stream->put($subChunks);
|
||||
|
||||
//biomes
|
||||
$stream->put($this->biomeIds);
|
||||
if($this->lightPopulated){
|
||||
$stream->put(pack("v*", ...$this->heightMap));
|
||||
}
|
||||
++$count;
|
||||
$subChunks .= chr($y) . $subChunk->fastSerialize();
|
||||
}
|
||||
$stream->putByte($count);
|
||||
$stream->put($subChunks);
|
||||
$stream->put(pack("v*", ...$this->heightMap) .
|
||||
$this->biomeIds .
|
||||
chr(($this->lightPopulated ? 4 : 0) | ($this->terrainPopulated ? 2 : 0) | ($this->terrainGenerated ? 1 : 0)));
|
||||
|
||||
return $stream->getBuffer();
|
||||
}
|
||||
|
||||
@ -916,19 +922,36 @@ class Chunk{
|
||||
|
||||
$x = $stream->getInt();
|
||||
$z = $stream->getInt();
|
||||
$flags = $stream->getByte();
|
||||
$lightPopulated = (bool) ($flags & 4);
|
||||
$terrainPopulated = (bool) ($flags & 2);
|
||||
$terrainGenerated = (bool) ($flags & 1);
|
||||
|
||||
$subChunks = [];
|
||||
$count = $stream->getByte();
|
||||
for($y = 0; $y < $count; ++$y){
|
||||
$subChunks[$stream->getByte()] = SubChunk::fastDeserialize($stream->get(10240));
|
||||
$biomeIds = "";
|
||||
$heightMap = [];
|
||||
if($terrainGenerated){
|
||||
$count = $stream->getByte();
|
||||
for($y = 0; $y < $count; ++$y){
|
||||
$subChunks[$stream->getByte()] = new SubChunk(
|
||||
$stream->get(4096), //blockids
|
||||
$stream->get(2048), //blockdata
|
||||
$lightPopulated ? $stream->get(2048) : "", //skylight
|
||||
$lightPopulated ? $stream->get(2048) : "" //blocklight
|
||||
);
|
||||
}
|
||||
|
||||
$biomeIds = $stream->get(256);
|
||||
if($lightPopulated){
|
||||
$heightMap = array_values(unpack("v*", $stream->get(512)));
|
||||
}
|
||||
}
|
||||
$heightMap = array_values(unpack("v*", $stream->get(512)));
|
||||
$biomeIds = $stream->get(256);
|
||||
|
||||
$chunk = new Chunk($x, $z, $subChunks, [], [], $biomeIds, $heightMap);
|
||||
$flags = $stream->getByte();
|
||||
$chunk->lightPopulated = (bool) ($flags & 4);
|
||||
$chunk->terrainPopulated = (bool) ($flags & 2);
|
||||
$chunk->terrainGenerated = (bool) ($flags & 1);
|
||||
$chunk->setGenerated($terrainGenerated);
|
||||
$chunk->setPopulated($terrainPopulated);
|
||||
$chunk->setLightPopulated($lightPopulated);
|
||||
|
||||
return $chunk;
|
||||
}
|
||||
}
|
||||
|
@ -126,8 +126,4 @@ class EmptySubChunk implements SubChunkInterface{
|
||||
public function networkSerialize() : string{
|
||||
return "\x00" . str_repeat("\x00", 6144);
|
||||
}
|
||||
|
||||
public function fastSerialize() : string{
|
||||
throw new \BadMethodCallException("Should not try to serialize empty subchunks");
|
||||
}
|
||||
}
|
||||
|
@ -106,13 +106,14 @@ class SubChunk implements SubChunkInterface{
|
||||
|
||||
if($data !== null){
|
||||
$i >>= 1;
|
||||
$byte = ord($this->data{$i});
|
||||
$oldPair = ord($this->data{$i});
|
||||
if(($y & 1) === 0){
|
||||
$this->data{$i} = chr(($byte & 0xf0) | ($data & 0x0f));
|
||||
$newPair = ($oldPair & 0xf0) | ($data & 0x0f);
|
||||
}else{
|
||||
$this->data{$i} = chr((($data & 0x0f) << 4) | ($byte & 0x0f));
|
||||
$newPair = (($data & 0x0f) << 4) | ($oldPair & 0x0f);
|
||||
}
|
||||
if($this->data{$i} !== $byte){
|
||||
if($newPair !== $oldPair){
|
||||
$this->data{$i} = chr($newPair);
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
@ -222,23 +223,6 @@ class SubChunk implements SubChunkInterface{
|
||||
return "\x00" . $this->ids . $this->data;
|
||||
}
|
||||
|
||||
public function fastSerialize() : string{
|
||||
return
|
||||
$this->ids .
|
||||
$this->data .
|
||||
$this->skyLight .
|
||||
$this->blockLight;
|
||||
}
|
||||
|
||||
public static function fastDeserialize(string $data) : SubChunk{
|
||||
return new SubChunk(
|
||||
substr($data, 0, 4096), //ids
|
||||
substr($data, 4096, 2048), //data
|
||||
substr($data, 6144, 2048), //sky light
|
||||
substr($data, 8192, 2048) //block light
|
||||
);
|
||||
}
|
||||
|
||||
public function __debugInfo(){
|
||||
return [];
|
||||
}
|
||||
|
@ -202,9 +202,4 @@ interface SubChunkInterface{
|
||||
* @return string
|
||||
*/
|
||||
public function networkSerialize() : string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function fastSerialize() : string;
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ use pocketmine\network\mcpe\protocol\BatchPacket;
|
||||
use pocketmine\network\mcpe\protocol\FullChunkDataPacket;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\tile\Spawnable;
|
||||
|
||||
class ChunkRequestTask extends AsyncTask{
|
||||
|
||||
@ -39,36 +38,22 @@ class ChunkRequestTask extends AsyncTask{
|
||||
protected $chunkX;
|
||||
protected $chunkZ;
|
||||
|
||||
protected $tiles;
|
||||
|
||||
protected $compressionLevel;
|
||||
|
||||
public function __construct(Level $level, int $chunkX, int $chunkZ, Chunk $chunk){
|
||||
$this->levelId = $level->getId();
|
||||
$this->compressionLevel = $level->getServer()->networkCompressionLevel;
|
||||
|
||||
$this->chunk = $chunk->fastSerialize();
|
||||
$this->chunk = $chunk->networkSerialize();
|
||||
$this->chunkX = $chunkX;
|
||||
$this->chunkZ = $chunkZ;
|
||||
|
||||
//TODO: serialize tiles with chunks
|
||||
$tiles = "";
|
||||
foreach($chunk->getTiles() as $tile){
|
||||
if($tile instanceof Spawnable){
|
||||
$tiles .= $tile->getSerializedSpawnCompound();
|
||||
}
|
||||
}
|
||||
|
||||
$this->tiles = $tiles;
|
||||
}
|
||||
|
||||
public function onRun(){
|
||||
$chunk = Chunk::fastDeserialize($this->chunk);
|
||||
|
||||
$pk = new FullChunkDataPacket();
|
||||
$pk->chunkX = $this->chunkX;
|
||||
$pk->chunkZ = $this->chunkZ;
|
||||
$pk->data = $chunk->networkSerialize() . $this->tiles;
|
||||
$pk->data = $this->chunk;
|
||||
|
||||
$batch = new BatchPacket();
|
||||
$batch->addPacket($pk);
|
||||
|
@ -95,7 +95,7 @@ class FloatingTextParticle extends Particle{
|
||||
|
||||
$add = new PlayerListPacket();
|
||||
$add->type = PlayerListPacket::TYPE_ADD;
|
||||
$add->entries = [PlayerListEntry::createAdditionEntry($uuid, $this->entityId, $name, $name, 0, new Skin("Standard_Custom", str_repeat("\x00", 8192)))];
|
||||
$add->entries = [PlayerListEntry::createAdditionEntry($uuid, $this->entityId, $name, new Skin("Standard_Custom", str_repeat("\x00", 8192)))];
|
||||
$p[] = $add;
|
||||
|
||||
$pk = new AddPlayerPacket();
|
||||
|
@ -109,7 +109,12 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
$this->putVarInt($auxValue);
|
||||
|
||||
$nbt = $item->getCompoundTag();
|
||||
$this->putLShort(strlen($nbt));
|
||||
$nbtLen = strlen($nbt);
|
||||
if($nbtLen > 32767){
|
||||
throw new \InvalidArgumentException("NBT encoded length must be < 32768, got $nbtLen bytes");
|
||||
}
|
||||
|
||||
$this->putLShort($nbtLen);
|
||||
$this->put($nbt);
|
||||
|
||||
$this->putVarInt(0); //CanPlaceOn entry count (TODO)
|
||||
|
@ -101,6 +101,7 @@ use pocketmine\network\mcpe\protocol\ResourcePackStackPacket;
|
||||
use pocketmine\network\mcpe\protocol\ResourcePacksInfoPacket;
|
||||
use pocketmine\network\mcpe\protocol\RespawnPacket;
|
||||
use pocketmine\network\mcpe\protocol\RiderJumpPacket;
|
||||
use pocketmine\network\mcpe\protocol\ScriptCustomEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\ServerSettingsResponsePacket;
|
||||
use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket;
|
||||
@ -603,4 +604,8 @@ abstract class NetworkSession{
|
||||
public function handleNetworkStackLatency(NetworkStackLatencyPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleScriptCustomEvent(ScriptCustomEventPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -38,10 +38,6 @@ class AddPlayerPacket extends DataPacket{
|
||||
public $uuid;
|
||||
/** @var string */
|
||||
public $username;
|
||||
/** @var string */
|
||||
public $thirdPartyName = "";
|
||||
/** @var int */
|
||||
public $platform = 0;
|
||||
/** @var int|null */
|
||||
public $entityUniqueId = null; //TODO
|
||||
/** @var int */
|
||||
@ -81,8 +77,6 @@ class AddPlayerPacket extends DataPacket{
|
||||
protected function decodePayload(){
|
||||
$this->uuid = $this->getUUID();
|
||||
$this->username = $this->getString();
|
||||
$this->thirdPartyName = $this->getString();
|
||||
$this->platform = $this->getVarInt();
|
||||
$this->entityUniqueId = $this->getEntityUniqueId();
|
||||
$this->entityRuntimeId = $this->getEntityRuntimeId();
|
||||
$this->platformChatId = $this->getString();
|
||||
@ -113,8 +107,6 @@ class AddPlayerPacket extends DataPacket{
|
||||
protected function encodePayload(){
|
||||
$this->putUUID($this->uuid);
|
||||
$this->putString($this->username);
|
||||
$this->putString($this->thirdPartyName);
|
||||
$this->putVarInt($this->platform);
|
||||
$this->putEntityUniqueId($this->entityUniqueId ?? $this->entityRuntimeId);
|
||||
$this->putEntityRuntimeId($this->entityRuntimeId);
|
||||
$this->putString($this->platformChatId);
|
||||
|
@ -134,4 +134,12 @@ abstract class DataPacket extends NetworkBinaryStream{
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function __get($name){
|
||||
throw new \Error("Cannot read non-existing field \"$name\"");
|
||||
}
|
||||
|
||||
public function __set($name, $value){
|
||||
throw new \Error("Cannot write non-existing field \"$name\"");
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,9 @@ class EventPacket extends DataPacket{
|
||||
public const TYPE_BOSS_KILLED = 7;
|
||||
public const TYPE_AGENT_COMMAND = 8;
|
||||
public const TYPE_AGENT_CREATED = 9;
|
||||
public const TYPE_PATTERN_REMOVED = 10; //???
|
||||
public const TYPE_COMMANED_EXECUTED = 11;
|
||||
public const TYPE_FISH_BUCKETED = 12;
|
||||
|
||||
/** @var int */
|
||||
public $playerRuntimeId;
|
||||
|
@ -146,6 +146,7 @@ class PacketPool{
|
||||
static::registerPacket(new SetLocalPlayerAsInitializedPacket());
|
||||
static::registerPacket(new UpdateSoftEnumPacket());
|
||||
static::registerPacket(new NetworkStackLatencyPacket());
|
||||
static::registerPacket(new ScriptCustomEventPacket());
|
||||
|
||||
static::registerPacket(new BatchPacket());
|
||||
}
|
||||
|
@ -56,8 +56,6 @@ class PlayerListPacket extends DataPacket{
|
||||
$entry->uuid = $this->getUUID();
|
||||
$entry->entityUniqueId = $this->getEntityUniqueId();
|
||||
$entry->username = $this->getString();
|
||||
$entry->thirdPartyName = $this->getString();
|
||||
$entry->platform = $this->getVarInt();
|
||||
|
||||
$skinId = $this->getString();
|
||||
$skinData = $this->getString();
|
||||
@ -90,8 +88,6 @@ class PlayerListPacket extends DataPacket{
|
||||
$this->putUUID($entry->uuid);
|
||||
$this->putEntityUniqueId($entry->entityUniqueId);
|
||||
$this->putString($entry->username);
|
||||
$this->putString($entry->thirdPartyName);
|
||||
$this->putVarInt($entry->platform);
|
||||
$this->putString($entry->skin->getSkinId());
|
||||
$this->putString($entry->skin->getSkinData());
|
||||
$this->putString($entry->skin->getCapeData());
|
||||
|
@ -39,15 +39,15 @@ interface ProtocolInfo{
|
||||
/**
|
||||
* Actual Minecraft: PE protocol version
|
||||
*/
|
||||
public const CURRENT_PROTOCOL = 282;
|
||||
public const CURRENT_PROTOCOL = 291;
|
||||
/**
|
||||
* Current Minecraft PE version reported by the server. This is usually the earliest currently supported version.
|
||||
*/
|
||||
public const MINECRAFT_VERSION = 'v1.6.0';
|
||||
public const MINECRAFT_VERSION = 'v1.7.0';
|
||||
/**
|
||||
* Version number sent to clients in ping responses.
|
||||
*/
|
||||
public const MINECRAFT_VERSION_NETWORK = '1.6.0';
|
||||
public const MINECRAFT_VERSION_NETWORK = '1.7.0';
|
||||
|
||||
public const LOGIN_PACKET = 0x01;
|
||||
public const PLAY_STATUS_PACKET = 0x02;
|
||||
@ -164,5 +164,6 @@ interface ProtocolInfo{
|
||||
public const SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET = 0x71;
|
||||
public const UPDATE_SOFT_ENUM_PACKET = 0x72;
|
||||
public const NETWORK_STACK_LATENCY_PACKET = 0x73;
|
||||
public const SCRIPT_CUSTOM_EVENT_PACKET = 0x75;
|
||||
|
||||
}
|
||||
|
@ -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\network\mcpe\protocol;
|
||||
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
|
||||
class ScriptCustomEventPacket extends DataPacket{
|
||||
public const NETWORK_ID = ProtocolInfo::SCRIPT_CUSTOM_EVENT_PACKET;
|
||||
|
||||
/** @var string */
|
||||
public $eventName;
|
||||
/** @var string json data */
|
||||
public $eventData;
|
||||
|
||||
protected function decodePayload(){
|
||||
$this->eventName = $this->getString();
|
||||
$this->eventData = $this->getString();
|
||||
}
|
||||
|
||||
protected function encodePayload(){
|
||||
$this->putString($this->eventName);
|
||||
$this->putString($this->eventData);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $session) : bool{
|
||||
return $session->handleScriptCustomEvent($this);
|
||||
}
|
||||
}
|
@ -31,8 +31,8 @@ use pocketmine\network\mcpe\protocol\types\ScorePacketEntry;
|
||||
class SetScorePacket extends DataPacket{
|
||||
public const NETWORK_ID = ProtocolInfo::SET_SCORE_PACKET;
|
||||
|
||||
public const TYPE_MODIFY_SCORE = 0;
|
||||
public const TYPE_RESET_SCORE = 1;
|
||||
public const TYPE_CHANGE = 0;
|
||||
public const TYPE_REMOVE = 1;
|
||||
|
||||
/** @var int */
|
||||
public $type;
|
||||
@ -43,9 +43,23 @@ class SetScorePacket extends DataPacket{
|
||||
$this->type = $this->getByte();
|
||||
for($i = 0, $i2 = $this->getUnsignedVarInt(); $i < $i2; ++$i){
|
||||
$entry = new ScorePacketEntry();
|
||||
$entry->uuid = $this->getUUID();
|
||||
$entry->scoreboardId = $this->getVarLong();
|
||||
$entry->objectiveName = $this->getString();
|
||||
$entry->score = $this->getLInt();
|
||||
if($this->type !== self::TYPE_REMOVE){
|
||||
$entry->type = $this->getByte();
|
||||
switch($entry->type){
|
||||
case ScorePacketEntry::TYPE_PLAYER:
|
||||
case ScorePacketEntry::TYPE_ENTITY:
|
||||
$entry->entityUniqueId = $this->getEntityUniqueId();
|
||||
break;
|
||||
case ScorePacketEntry::TYPE_FAKE_PLAYER:
|
||||
$entry->customName = $this->getString();
|
||||
break;
|
||||
default:
|
||||
throw new \UnexpectedValueException("Unknown entry type $entry->type");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,9 +67,23 @@ class SetScorePacket extends DataPacket{
|
||||
$this->putByte($this->type);
|
||||
$this->putUnsignedVarInt(count($this->entries));
|
||||
foreach($this->entries as $entry){
|
||||
$this->putUUID($entry->uuid);
|
||||
$this->putVarLong($entry->scoreboardId);
|
||||
$this->putString($entry->objectiveName);
|
||||
$this->putLInt($entry->score);
|
||||
if($this->type !== self::TYPE_REMOVE){
|
||||
$this->putByte($entry->type);
|
||||
switch($entry->type){
|
||||
case ScorePacketEntry::TYPE_PLAYER:
|
||||
case ScorePacketEntry::TYPE_ENTITY:
|
||||
$this->putEntityUniqueId($entry->entityUniqueId);
|
||||
break;
|
||||
case ScorePacketEntry::TYPE_FAKE_PLAYER:
|
||||
$this->putString($entry->customName);
|
||||
break;
|
||||
default:
|
||||
throw new \UnexpectedValueException("Unknown entry type $entry->type");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ class SetScoreboardIdentityPacket extends DataPacket{
|
||||
$entry = new ScoreboardIdentityPacketEntry();
|
||||
$entry->scoreboardId = $this->getVarLong();
|
||||
if($this->type === self::TYPE_REGISTER_IDENTITY){
|
||||
$entry->uuid = $this->getUUID();
|
||||
$entry->entityUniqueId = $this->getEntityUniqueId();
|
||||
}
|
||||
|
||||
$this->entries[] = $entry;
|
||||
@ -58,7 +58,7 @@ class SetScoreboardIdentityPacket extends DataPacket{
|
||||
foreach($this->entries as $entry){
|
||||
$this->putVarLong($entry->scoreboardId);
|
||||
if($this->type === self::TYPE_REGISTER_IDENTITY){
|
||||
$this->putUUID($entry->uuid);
|
||||
$this->putEntityUniqueId($entry->entityUniqueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +116,8 @@ class StartGamePacket extends DataPacket{
|
||||
public $hasLockedResourcePack = false;
|
||||
/** @var bool */
|
||||
public $isFromLockedWorldTemplate = false;
|
||||
/** @var bool */
|
||||
public $useMsaGamertagsOnly = false;
|
||||
|
||||
/** @var string */
|
||||
public $levelId = ""; //base64 string, usually the same as world folder name in vanilla
|
||||
@ -173,6 +175,7 @@ class StartGamePacket extends DataPacket{
|
||||
$this->hasLockedBehaviorPack = $this->getBool();
|
||||
$this->hasLockedResourcePack = $this->getBool();
|
||||
$this->isFromLockedWorldTemplate = $this->getBool();
|
||||
$this->useMsaGamertagsOnly = $this->getBool();
|
||||
|
||||
$this->levelId = $this->getString();
|
||||
$this->worldName = $this->getString();
|
||||
@ -232,6 +235,7 @@ class StartGamePacket extends DataPacket{
|
||||
$this->putBool($this->hasLockedBehaviorPack);
|
||||
$this->putBool($this->hasLockedResourcePack);
|
||||
$this->putBool($this->isFromLockedWorldTemplate);
|
||||
$this->putBool($this->useMsaGamertagsOnly);
|
||||
|
||||
$this->putString($this->levelId);
|
||||
$this->putString($this->worldName);
|
||||
|
@ -48,10 +48,6 @@ class TextPacket extends DataPacket{
|
||||
/** @var string */
|
||||
public $sourceName;
|
||||
/** @var string */
|
||||
public $sourceThirdPartyName = "";
|
||||
/** @var int */
|
||||
public $sourcePlatform = 0;
|
||||
/** @var string */
|
||||
public $message;
|
||||
/** @var string[] */
|
||||
public $parameters = [];
|
||||
@ -69,8 +65,6 @@ class TextPacket extends DataPacket{
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
case self::TYPE_ANNOUNCEMENT:
|
||||
$this->sourceName = $this->getString();
|
||||
$this->sourceThirdPartyName = $this->getString();
|
||||
$this->sourcePlatform = $this->getVarInt();
|
||||
case self::TYPE_RAW:
|
||||
case self::TYPE_TIP:
|
||||
case self::TYPE_SYSTEM:
|
||||
@ -101,8 +95,6 @@ class TextPacket extends DataPacket{
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
case self::TYPE_ANNOUNCEMENT:
|
||||
$this->putString($this->sourceName);
|
||||
$this->putString($this->sourceThirdPartyName);
|
||||
$this->putVarInt($this->sourcePlatform);
|
||||
case self::TYPE_RAW:
|
||||
case self::TYPE_TIP:
|
||||
case self::TYPE_SYSTEM:
|
||||
|
@ -34,10 +34,6 @@ class PlayerListEntry{
|
||||
public $entityUniqueId;
|
||||
/** @var string */
|
||||
public $username;
|
||||
/** @var string */
|
||||
public $thirdPartyName = "";
|
||||
/** @var int */
|
||||
public $platform = 0;
|
||||
/** @var Skin */
|
||||
public $skin;
|
||||
/** @var string */
|
||||
@ -52,13 +48,11 @@ class PlayerListEntry{
|
||||
return $entry;
|
||||
}
|
||||
|
||||
public static function createAdditionEntry(UUID $uuid, int $entityUniqueId, string $username, string $thirdPartyName, int $platform, Skin $skin, string $xboxUserId = "", string $platformChatId = "") : PlayerListEntry{
|
||||
public static function createAdditionEntry(UUID $uuid, int $entityUniqueId, string $username, Skin $skin, string $xboxUserId = "", string $platformChatId = "") : PlayerListEntry{
|
||||
$entry = new PlayerListEntry();
|
||||
$entry->uuid = $uuid;
|
||||
$entry->entityUniqueId = $entityUniqueId;
|
||||
$entry->username = $username;
|
||||
$entry->thirdPartyName = $thirdPartyName;
|
||||
$entry->platform = $platform;
|
||||
$entry->skin = $skin;
|
||||
$entry->xboxUserId = $xboxUserId;
|
||||
$entry->platformChatId = $platformChatId;
|
||||
|
@ -23,13 +23,23 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\protocol\types;
|
||||
|
||||
use pocketmine\utils\UUID;
|
||||
|
||||
class ScorePacketEntry{
|
||||
/** @var UUID */
|
||||
public $uuid;
|
||||
public const TYPE_PLAYER = 1;
|
||||
public const TYPE_ENTITY = 2;
|
||||
public const TYPE_FAKE_PLAYER = 3;
|
||||
|
||||
/** @var int */
|
||||
public $scoreboardId;
|
||||
/** @var string */
|
||||
public $objectiveName;
|
||||
/** @var int */
|
||||
public $score;
|
||||
|
||||
/** @var int */
|
||||
public $type;
|
||||
|
||||
/** @var int|null (if type entity or player) */
|
||||
public $entityUniqueId;
|
||||
/** @var string|null (if type fake player) */
|
||||
public $customName;
|
||||
}
|
||||
|
@ -23,12 +23,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\protocol\types;
|
||||
|
||||
use pocketmine\utils\UUID;
|
||||
|
||||
class ScoreboardIdentityPacketEntry{
|
||||
/** @var int */
|
||||
public $scoreboardId;
|
||||
/** @var UUID|null */
|
||||
public $uuid;
|
||||
/** @var int|null */
|
||||
public $entityUniqueId;
|
||||
|
||||
}
|
||||
|
@ -199,122 +199,140 @@ class PluginManager{
|
||||
* @return Plugin[]
|
||||
*/
|
||||
public function loadPlugins(string $directory, array $newLoaders = null){
|
||||
if(!is_dir($directory)){
|
||||
return [];
|
||||
}
|
||||
|
||||
if(is_dir($directory)){
|
||||
$plugins = [];
|
||||
$loadedPlugins = [];
|
||||
$dependencies = [];
|
||||
$softDependencies = [];
|
||||
if(is_array($newLoaders)){
|
||||
$loaders = [];
|
||||
foreach($newLoaders as $key){
|
||||
if(isset($this->fileAssociations[$key])){
|
||||
$loaders[$key] = $this->fileAssociations[$key];
|
||||
}
|
||||
$plugins = [];
|
||||
$loadedPlugins = [];
|
||||
$dependencies = [];
|
||||
$softDependencies = [];
|
||||
if(is_array($newLoaders)){
|
||||
$loaders = [];
|
||||
foreach($newLoaders as $key){
|
||||
if(isset($this->fileAssociations[$key])){
|
||||
$loaders[$key] = $this->fileAssociations[$key];
|
||||
}
|
||||
}else{
|
||||
$loaders = $this->fileAssociations;
|
||||
}
|
||||
foreach($loaders as $loader){
|
||||
foreach(new \DirectoryIterator($directory) as $file){
|
||||
if($file === "." or $file === ".."){
|
||||
}else{
|
||||
$loaders = $this->fileAssociations;
|
||||
}
|
||||
foreach($loaders as $loader){
|
||||
foreach(new \DirectoryIterator($directory) as $file){
|
||||
if($file === "." or $file === ".."){
|
||||
continue;
|
||||
}
|
||||
$file = $directory . $file;
|
||||
if(!$loader->canLoadPlugin($file)){
|
||||
continue;
|
||||
}
|
||||
try{
|
||||
$description = $loader->getPluginDescription($file);
|
||||
if($description === null){
|
||||
continue;
|
||||
}
|
||||
$file = $directory . $file;
|
||||
if(!$loader->canLoadPlugin($file)){
|
||||
|
||||
$name = $description->getName();
|
||||
if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.restrictedName"]));
|
||||
continue;
|
||||
}elseif(strpos($name, " ") !== false){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.plugin.spacesDiscouraged", [$name]));
|
||||
}
|
||||
|
||||
if(isset($plugins[$name]) or $this->getPlugin($name) instanceof Plugin){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.duplicateError", [$name]));
|
||||
continue;
|
||||
}
|
||||
try{
|
||||
$description = $loader->getPluginDescription($file);
|
||||
if($description instanceof PluginDescription){
|
||||
$name = $description->getName();
|
||||
if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.restrictedName"]));
|
||||
continue;
|
||||
}elseif(strpos($name, " ") !== false){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.plugin.spacesDiscouraged", [$name]));
|
||||
}
|
||||
|
||||
if(isset($plugins[$name]) or $this->getPlugin($name) instanceof Plugin){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.duplicateError", [$name]));
|
||||
continue;
|
||||
}
|
||||
if(!$this->isCompatibleApi(...$description->getCompatibleApis())){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
|
||||
$name,
|
||||
$this->server->getLanguage()->translateString("%pocketmine.plugin.incompatibleAPI", [implode(", ", $description->getCompatibleApis())])
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!$this->isCompatibleApi(...$description->getCompatibleApis())){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
|
||||
$name,
|
||||
$this->server->getLanguage()->translateString("%pocketmine.plugin.incompatibleAPI", [implode(", ", $description->getCompatibleApis())])
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(count($pluginMcpeProtocols = $description->getCompatibleMcpeProtocols()) > 0){
|
||||
$serverMcpeProtocols = [ProtocolInfo::CURRENT_PROTOCOL];
|
||||
if(count(array_intersect($pluginMcpeProtocols, $serverMcpeProtocols)) === 0){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
|
||||
$name,
|
||||
$this->server->getLanguage()->translateString("%pocketmine.plugin.incompatibleProtocol", [implode(", ", $pluginMcpeProtocols)])
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$plugins[$name] = $file;
|
||||
|
||||
$softDependencies[$name] = $description->getSoftDepend();
|
||||
$dependencies[$name] = $description->getDepend();
|
||||
|
||||
foreach($description->getLoadBefore() as $before){
|
||||
if(isset($softDependencies[$before])){
|
||||
$softDependencies[$before][] = $name;
|
||||
}else{
|
||||
$softDependencies[$before] = [$name];
|
||||
}
|
||||
}
|
||||
if(count($pluginMcpeProtocols = $description->getCompatibleMcpeProtocols()) > 0){
|
||||
$serverMcpeProtocols = [ProtocolInfo::CURRENT_PROTOCOL];
|
||||
if(count(array_intersect($pluginMcpeProtocols, $serverMcpeProtocols)) === 0){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
|
||||
$name,
|
||||
$this->server->getLanguage()->translateString("%pocketmine.plugin.incompatibleProtocol", [implode(", ", $pluginMcpeProtocols)])
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
}catch(\Throwable $e){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.fileError", [$file, $directory, $e->getMessage()]));
|
||||
$this->server->getLogger()->logException($e);
|
||||
}
|
||||
|
||||
$plugins[$name] = $file;
|
||||
|
||||
$softDependencies[$name] = $description->getSoftDepend();
|
||||
$dependencies[$name] = $description->getDepend();
|
||||
|
||||
foreach($description->getLoadBefore() as $before){
|
||||
if(isset($softDependencies[$before])){
|
||||
$softDependencies[$before][] = $name;
|
||||
}else{
|
||||
$softDependencies[$before] = [$name];
|
||||
}
|
||||
}
|
||||
}catch(\Throwable $e){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.fileError", [$file, $directory, $e->getMessage()]));
|
||||
$this->server->getLogger()->logException($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
while(count($plugins) > 0){
|
||||
$missingDependency = true;
|
||||
foreach($plugins as $name => $file){
|
||||
if(isset($dependencies[$name])){
|
||||
foreach($dependencies[$name] as $key => $dependency){
|
||||
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
|
||||
unset($dependencies[$name][$key]);
|
||||
}elseif(!isset($plugins[$dependency])){
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
|
||||
$name,
|
||||
$this->server->getLanguage()->translateString("%pocketmine.plugin.unknownDependency", [$dependency])
|
||||
]));
|
||||
unset($plugins[$name]);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if(count($dependencies[$name]) === 0){
|
||||
unset($dependencies[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($softDependencies[$name])){
|
||||
foreach($softDependencies[$name] as $key => $dependency){
|
||||
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
|
||||
unset($softDependencies[$name][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if(count($softDependencies[$name]) === 0){
|
||||
unset($softDependencies[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){
|
||||
unset($plugins[$name]);
|
||||
$missingDependency = false;
|
||||
if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
|
||||
$loadedPlugins[$name] = $plugin;
|
||||
}else{
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.genericLoadError", [$name]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
while(count($plugins) > 0){
|
||||
$missingDependency = true;
|
||||
if($missingDependency){
|
||||
foreach($plugins as $name => $file){
|
||||
if(isset($dependencies[$name])){
|
||||
foreach($dependencies[$name] as $key => $dependency){
|
||||
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
|
||||
unset($dependencies[$name][$key]);
|
||||
}elseif(!isset($plugins[$dependency])){
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
|
||||
$name,
|
||||
$this->server->getLanguage()->translateString("%pocketmine.plugin.unknownDependency", [$dependency])
|
||||
]));
|
||||
unset($plugins[$name]);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if(count($dependencies[$name]) === 0){
|
||||
unset($dependencies[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($softDependencies[$name])){
|
||||
foreach($softDependencies[$name] as $key => $dependency){
|
||||
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
|
||||
unset($softDependencies[$name][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if(count($softDependencies[$name]) === 0){
|
||||
unset($softDependencies[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){
|
||||
if(!isset($dependencies[$name])){
|
||||
unset($softDependencies[$name]);
|
||||
unset($plugins[$name]);
|
||||
$missingDependency = false;
|
||||
if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
|
||||
@ -325,34 +343,17 @@ class PluginManager{
|
||||
}
|
||||
}
|
||||
|
||||
//No plugins loaded :(
|
||||
if($missingDependency){
|
||||
foreach($plugins as $name => $file){
|
||||
if(!isset($dependencies[$name])){
|
||||
unset($softDependencies[$name]);
|
||||
unset($plugins[$name]);
|
||||
$missingDependency = false;
|
||||
if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
|
||||
$loadedPlugins[$name] = $plugin;
|
||||
}else{
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.genericLoadError", [$name]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//No plugins loaded :(
|
||||
if($missingDependency){
|
||||
foreach($plugins as $name => $file){
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.circularDependency"]));
|
||||
}
|
||||
$plugins = [];
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.circularDependency"]));
|
||||
}
|
||||
$plugins = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $loadedPlugins;
|
||||
}else{
|
||||
return [];
|
||||
}
|
||||
|
||||
return $loadedPlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -740,6 +741,24 @@ class PluginManager{
|
||||
continue;
|
||||
}
|
||||
|
||||
$parameters = $method->getParameters();
|
||||
if(count($parameters) !== 1){
|
||||
continue;
|
||||
}
|
||||
try{
|
||||
$eventClass = $parameters[0]->getClass();
|
||||
}catch(\ReflectionException $e){ //class doesn't exist
|
||||
if(isset($tags["softDepend"]) && !isset($this->plugins[$tags["softDepend"]])){
|
||||
$this->server->getLogger()->debug("Not registering @softDepend listener " . get_class($listener) . "::" . $method->getName() . "(" . $parameters[0]->getType()->getName() . ") because plugin \"" . $tags["softDepend"] . "\" not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
if($eventClass === null or !$eventClass->isSubclassOf(Event::class)){
|
||||
continue;
|
||||
}
|
||||
|
||||
try{
|
||||
$priority = isset($tags["priority"]) ? EventPriority::fromString($tags["priority"]) : EventPriority::NORMAL;
|
||||
}catch(\InvalidArgumentException $e){
|
||||
@ -761,21 +780,7 @@ class PluginManager{
|
||||
}
|
||||
}
|
||||
|
||||
$parameters = $method->getParameters();
|
||||
try{
|
||||
$isHandler = count($parameters) === 1 && $parameters[0]->getClass() instanceof \ReflectionClass && is_subclass_of($parameters[0]->getClass()->getName(), Event::class);
|
||||
}catch(\ReflectionException $e){
|
||||
if(isset($tags["softDepend"]) && !isset($this->plugins[$tags["softDepend"]])){
|
||||
$this->server->getLogger()->debug("Not registering @softDepend listener " . get_class($listener) . "::" . $method->getName() . "(" . $parameters[0]->getType()->getName() . ") because plugin \"" . $tags["softDepend"] . "\" not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
if($isHandler){
|
||||
$class = $parameters[0]->getClass()->getName();
|
||||
$this->registerEvent($class, $listener, $priority, new MethodEventExecutor($method->getName()), $plugin, $ignoreCancelled);
|
||||
}
|
||||
$this->registerEvent($eventClass->getName(), $listener, $priority, new MethodEventExecutor($method->getName()), $plugin, $ignoreCancelled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,10 +44,6 @@ class FileWriteTask extends AsyncTask{
|
||||
}
|
||||
|
||||
public function onRun(){
|
||||
try{
|
||||
file_put_contents($this->path, $this->contents, $this->flags);
|
||||
}catch(\Throwable $e){
|
||||
|
||||
}
|
||||
file_put_contents($this->path, $this->contents, $this->flags);
|
||||
}
|
||||
}
|
||||
|
@ -147,13 +147,9 @@ class SendUsageTask extends AsyncTask{
|
||||
}
|
||||
|
||||
public function onRun(){
|
||||
try{
|
||||
Internet::postURL($this->endpoint, $this->data, 5, [
|
||||
"Content-Type: application/json",
|
||||
"Content-Length: " . strlen($this->data)
|
||||
]);
|
||||
}catch(\Throwable $e){
|
||||
|
||||
}
|
||||
Internet::postURL($this->endpoint, $this->data, 5, [
|
||||
"Content-Type: application/json",
|
||||
"Content-Length: " . strlen($this->data)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\scheduler;
|
||||
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
abstract class Task{
|
||||
|
||||
/** @var TaskHandler */
|
||||
@ -46,6 +48,10 @@ abstract class Task{
|
||||
return -1;
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return Utils::getNiceClassName($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TaskHandler|null $taskHandler
|
||||
*/
|
||||
|
@ -66,7 +66,7 @@ class TaskHandler{
|
||||
$this->taskId = $taskId;
|
||||
$this->delay = $delay;
|
||||
$this->period = $period;
|
||||
$this->taskName = get_class($task);
|
||||
$this->taskName = $task->getName();
|
||||
$this->ownerName = $ownerName ?? "Unknown";
|
||||
$this->timings = Timings::getScheduledTaskTimings($this, $period);
|
||||
$this->task->setHandler($this);
|
||||
@ -135,10 +135,6 @@ class TaskHandler{
|
||||
return $this->period;
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: Do not use this, it's only for internal use.
|
||||
* Changes to this function won't be recorded on the version.
|
||||
*/
|
||||
public function cancel(){
|
||||
try{
|
||||
if(!$this->isCancelled()){
|
||||
|
@ -74,8 +74,13 @@ class Chest extends Spawnable implements InventoryHolder, Container, Nameable{
|
||||
$this->inventory->removeAllViewers(true);
|
||||
|
||||
if($this->doubleInventory !== null){
|
||||
$this->doubleInventory->removeAllViewers(true);
|
||||
$this->doubleInventory->invalidate();
|
||||
if($this->isPaired() and $this->level->isChunkLoaded($this->pairX >> 4, $this->pairZ >> 4)){
|
||||
$this->doubleInventory->removeAllViewers(true);
|
||||
$this->doubleInventory->invalidate();
|
||||
if(($pair = $this->getPair()) !== null){
|
||||
$pair->doubleInventory = null;
|
||||
}
|
||||
}
|
||||
$this->doubleInventory = null;
|
||||
}
|
||||
|
||||
@ -117,9 +122,9 @@ class Chest extends Spawnable implements InventoryHolder, Container, Nameable{
|
||||
$this->doubleInventory = $pair->doubleInventory;
|
||||
}else{
|
||||
if(($pair->x + ($pair->z << 15)) > ($this->x + ($this->z << 15))){ //Order them correctly
|
||||
$this->doubleInventory = new DoubleChestInventory($pair, $this);
|
||||
$this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($pair, $this);
|
||||
}else{
|
||||
$this->doubleInventory = new DoubleChestInventory($this, $pair);
|
||||
$this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($this, $pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,23 @@ class Utils{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a readable identifier for the class of the given object. Sanitizes class names for anonymous classes.
|
||||
*
|
||||
* @param object $obj
|
||||
*
|
||||
* @return string
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public static function getNiceClassName(object $obj) : string{
|
||||
$reflect = new \ReflectionClass($obj);
|
||||
if($reflect->isAnonymous()){
|
||||
return "anonymous@" . self::cleanPath($reflect->getFileName()) . "#L" . $reflect->getStartLine();
|
||||
}
|
||||
|
||||
return $reflect->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this machine / server instance unique ID
|
||||
* Returns a hash, the first 32 characters (or 16 if raw)
|
||||
|
11
start.cmd
11
start.cmd
@ -12,13 +12,10 @@ if exist bin\php\php.exe (
|
||||
if exist PocketMine-MP.phar (
|
||||
set POCKETMINE_FILE=PocketMine-MP.phar
|
||||
) else (
|
||||
if exist src\pocketmine\PocketMine.php (
|
||||
set POCKETMINE_FILE=src\pocketmine\PocketMine.php
|
||||
) else (
|
||||
echo Couldn't find a valid PocketMine-MP installation
|
||||
pause
|
||||
exit 1
|
||||
)
|
||||
echo PocketMine-MP.phar not found
|
||||
echo Downloads can be found at https://github.com/pmmp/PocketMine-MP/releases
|
||||
pause
|
||||
exit 1
|
||||
)
|
||||
|
||||
if exist bin\mintty.exe (
|
||||
|
@ -18,10 +18,9 @@ if($php -ne ""){
|
||||
if($file -eq ""){
|
||||
if(Test-Path "PocketMine-MP.phar"){
|
||||
$file = "PocketMine-MP.phar"
|
||||
}elseif(Test-Path "src\pocketmine\PocketMine.php"){
|
||||
$file = "src\pocketmine\PocketMine.php"
|
||||
}else{
|
||||
echo "Couldn't find a valid PocketMine-MP installation"
|
||||
echo "PocketMine-MP.phar not found"
|
||||
echo "Downloads can be found at https://github.com/pmmp/PocketMine-MP/releases"
|
||||
pause
|
||||
exit 1
|
||||
}
|
||||
|
5
start.sh
5
start.sh
@ -36,10 +36,9 @@ fi
|
||||
if [ "$POCKETMINE_FILE" == "" ]; then
|
||||
if [ -f ./PocketMine-MP.phar ]; then
|
||||
POCKETMINE_FILE="./PocketMine-MP.phar"
|
||||
elif [ -f ./src/pocketmine/PocketMine.php ]; then
|
||||
POCKETMINE_FILE="./src/pocketmine/PocketMine.php"
|
||||
else
|
||||
echo "Couldn't find a valid PocketMine-MP installation"
|
||||
echo "PocketMine-MP.phar not found"
|
||||
echo "Downloads can be found at https://github.com/pmmp/PocketMine-MP/releases"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
@ -43,6 +43,15 @@ class ItemTest extends TestCase{
|
||||
self::assertTrue($item->equals($item2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that same items without NBT are considered equal
|
||||
*/
|
||||
public function testItemEqualsNoNbt() : void{
|
||||
$item1 = ItemFactory::get(Item::DIAMOND_SWORD);
|
||||
$item2 = clone $item1;
|
||||
self::assertTrue($item1->equals($item2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that blocks are considered to be valid registered items
|
||||
*/
|
||||
|
Submodule tests/preprocessor updated: ea28e32720...a6ec3e1271
Reference in New Issue
Block a user