diff --git a/src/pocketmine/MemoryManager.php b/src/pocketmine/MemoryManager.php index 69ca142b4..655fcb346 100644 --- a/src/pocketmine/MemoryManager.php +++ b/src/pocketmine/MemoryManager.php @@ -25,6 +25,7 @@ use pocketmine\event\server\LowMemoryEvent; use pocketmine\event\Timings; use pocketmine\scheduler\GarbageCollectionTask; use pocketmine\utils\Utils; +use pocketmine\utils\UUID; class MemoryManager{ @@ -231,7 +232,7 @@ class MemoryManager{ } $this->leakInfo[$identifier] = [ - "id" => $id = Utils::dataToUUID($identifier . ":" . $this->leakSeed++), + "id" => $id = md5($identifier . ":" . $this->leakSeed++), "class" => get_class($object), "hash" => $identifier ]; diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 497590fd0..81176227c 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -127,6 +127,7 @@ use pocketmine\tile\Spawnable; use pocketmine\tile\Tile; use pocketmine\utils\TextFormat; use pocketmine\utils\Utils; +use pocketmine\utils\UUID; use raklib\Binary; /** @@ -161,6 +162,8 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade protected $sendIndex = 0; + private $clientSecret; + /** @var Vector3 */ public $speed = null; @@ -182,7 +185,6 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade public $creationTime = 0; protected $randomClientId; - protected $uuid; protected $lastMovement = 0; /** @var Vector3 */ @@ -253,8 +255,8 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade return $this->randomClientId; } - public function getUniqueId(){ - return $this->uuid; + public function getClientSecret(){ + return $this->clientSecretId; } public function isBanned(){ @@ -352,7 +354,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade * @return bool */ public function canSee(Player $player){ - return !isset($this->hiddenPlayers[$player->getUniqueId()]); + return !isset($this->hiddenPlayers[$player->getRawUniqueId()]); } /** @@ -362,7 +364,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade if($player === $this){ return; } - $this->hiddenPlayers[$player->getUniqueId()] = $player; + $this->hiddenPlayers[$player->getRawUniqueId()] = $player; $player->despawnFrom($this); } @@ -373,7 +375,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade if($player === $this){ return; } - unset($this->hiddenPlayers[$player->getUniqueId()]); + unset($this->hiddenPlayers[$player->getRawUniqueId()]); if($player->isOnline()){ $player->spawnTo($this); } @@ -510,7 +512,8 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade $this->newPosition = new Vector3(0, 0, 0); $this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0); - $this->uuid = Utils::dataToUUID($ip, $port, $clientID); + $this->uuid = null; + $this->rawUUID = null; $this->creationTime = microtime(true); } @@ -560,12 +563,15 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade */ public function setDisplayName($name){ $this->displayName = $name; + if($this->spawned){ + $this->server->updatePlayerListData($this->getUniqueId(), $this->getId(), $this->getDisplayName(), $isSlim, $str); + } } public function setSkin($str, $isSlim = false){ parent::setSkin($str, $isSlim); if($this->spawned){ - $this->respawnToAll(); + $this->server->updatePlayerListData($this->getUniqueId(), $this->getId(), $this->getDisplayName(), $isSlim, $str); } } @@ -621,18 +627,6 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade $entity->despawnFrom($this); } } - //TODO HACK: removes tile entities that linger whenever you teleport - // to a different world - $pk = new UpdateBlockPacket(); - foreach($level->getChunkTiles($x, $z) as $tile){ - if($tile instanceof Spawnable){ - $pk->records[] = [$tile->x, $tile->z, $tile->y, 0, 0, UpdateBlockPacket::FLAG_NONE]; - } - } - if(count($pk->records)){ - $this->dataPacket($pk); - } - //---- unset($this->usedChunks[$index]); } @@ -1580,6 +1574,192 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade return ($dot1 - $dot) >= -$maxDiff; } + public function onPlayerPreLogin(){ + //TODO: implement auth + $this->tryAuthenticate(); + } + + public function tryAuthenticate(){ + //TODO: implement authentication after it is available + $this->authenticateCallback(true); + } + + public function authenticateCallback($valid){ + + //TODO add more stuff after authentication is available + if(!$valid){ + $this->close("", "disconnectionScreen.invalidSession"); + return; + } + + $this->processLogin(); + } + + protected function processLogin(){ + if(!$this->server->isWhitelisted(strtolower($this->getName()))){ + $this->close($this->getLeaveMessage(), "Server is white-listed"); + + return; + }elseif($this->server->getNameBans()->isBanned(strtolower($this->getName())) or $this->server->getIPBans()->isBanned($this->getAddress())){ + $this->close($this->getLeaveMessage(), "You are banned"); + + return; + } + + if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){ + $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this); + } + if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){ + $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this); + } + + foreach($this->server->getOnlinePlayers() as $p){ + if($p !== $this and strtolower($p->getName()) === strtolower($this->getName())){ + if($p->kick("logged in from another location") === false){ + $this->close($this->getLeaveMessage(), "Logged in from another location"); + return; + } + }elseif($p->loggedIn and $this->getUniqueId()->equals($p->getUniqueId())){ + if($p->kick("logged in from another location") === false){ + $this->close($this->getLeaveMessage(), "Logged in from another location"); + return; + } + } + } + + $nbt = $this->server->getOfflinePlayerData($this->username); + if(!isset($nbt->NameTag)){ + $nbt->NameTag = new String("NameTag", $this->username); + }else{ + $nbt["NameTag"] = $this->username; + } + $this->gamemode = $nbt["playerGameType"] & 0x03; + if($this->server->getForceGamemode()){ + $this->gamemode = $this->server->getGamemode(); + $nbt->playerGameType = new Int("playerGameType", $this->gamemode); + } + + $this->allowFlight = $this->isCreative(); + + + if(($level = $this->server->getLevelByName($nbt["Level"])) === null){ + $this->setLevel($this->server->getDefaultLevel()); + $nbt["Level"] = $this->level->getName(); + $nbt["Pos"][0] = $this->level->getSpawnLocation()->x; + $nbt["Pos"][1] = $this->level->getSpawnLocation()->y; + $nbt["Pos"][2] = $this->level->getSpawnLocation()->z; + }else{ + $this->setLevel($level); + } + + if(!($nbt instanceof Compound)){ + $this->close($this->getLeaveMessage(), "Invalid data"); + + return; + } + + $this->achievements = []; + + /** @var Byte $achievement */ + foreach($nbt->Achievements as $achievement){ + $this->achievements[$achievement->getName()] = $achievement->getValue() > 0 ? true : false; + } + + $nbt->lastPlayed = new Long("lastPlayed", floor(microtime(true) * 1000)); + if($this->server->getAutoSave()){ + $this->server->saveOfflinePlayerData($this->username, $nbt, true); + } + + parent::__construct($this->level->getChunk($nbt["Pos"][0] >> 4, $nbt["Pos"][2] >> 4, true), $nbt); + $this->loggedIn = true; + $this->server->addOnlinePlayer($this); + + $this->server->getPluginManager()->callEvent($ev = new PlayerLoginEvent($this, "Plugin reason")); + if($ev->isCancelled()){ + $this->close($this->getLeaveMessage(), $ev->getKickMessage()); + + return; + } + + if($this->isCreative()){ + $this->inventory->setHeldItemSlot(0); + }else{ + $this->inventory->setHeldItemSlot(0); + } + + $pk = new PlayStatusPacket(); + $pk->status = PlayStatusPacket::LOGIN_SUCCESS; + $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); + + if($this->spawnPosition === null and isset($this->namedtag->SpawnLevel) and ($level = $this->server->getLevelByName($this->namedtag["SpawnLevel"])) instanceof Level){ + $this->spawnPosition = new Position($this->namedtag["SpawnX"], $this->namedtag["SpawnY"], $this->namedtag["SpawnZ"], $level); + } + + $spawnPosition = $this->getSpawn(); + + $pk = new StartGamePacket(); + $pk->seed = -1; + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $pk->spawnX = (int) $spawnPosition->x; + $pk->spawnY = (int) $spawnPosition->y; + $pk->spawnZ = (int) $spawnPosition->z; + $pk->generator = 1; //0 old, 1 infinite, 2 flat + $pk->gamemode = $this->gamemode & 0x01; + $pk->eid = 0; //Always use EntityID as zero for the actual player + $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); + + $pk = new SetTimePacket(); + $pk->time = $this->level->getTime(); + $pk->started = $this->level->stopTime == false; + $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); + + $pk = new SetSpawnPositionPacket(); + $pk->x = (int) $spawnPosition->x; + $pk->y = (int) $spawnPosition->y; + $pk->z = (int) $spawnPosition->z; + $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); + + $pk = new SetHealthPacket(); + $pk->health = $this->getHealth(); + $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); + + $pk = new SetDifficultyPacket(); + $pk->difficulty = $this->server->getDifficulty(); + $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); + + $this->server->getLogger()->info($this->getServer()->getLanguage()->translateString("pocketmine.player.logIn", [ + TextFormat::AQUA . $this->username . TextFormat::WHITE, + $this->ip, + $this->port, + $this->id, + $this->level->getName(), + round($this->x, 4), + round($this->y, 4), + round($this->z, 4) + ])); + + if($this->isOp()){ + $this->setRemoveFormat(false); + } + + if($this->gamemode === Player::SPECTATOR){ + $pk = new ContainerSetContentPacket(); + $pk->windowid = ContainerSetContentPacket::SPECIAL_CREATIVE; + $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); + }else{ + $pk = new ContainerSetContentPacket(); + $pk->windowid = ContainerSetContentPacket::SPECIAL_CREATIVE; + $pk->slots = Item::getCreativeItems(); + $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); + } + + $this->forceMovement = $this->teleportPosition = $this->getPosition(); + + $this->server->onPlayerLogin($this); + } + /** * Handles a Minecraft packet * TODO: Separate all of this in handlers @@ -1624,7 +1804,9 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade $this->randomClientId = $packet->clientId; $this->loginData = ["clientId" => $packet->clientId, "loginData" => null]; - $this->uuid = Utils::dataToUUID($this->randomClientId, $this->iusername, $this->getAddress()); + $this->uuid = $packet->clientUUID; + $this->clientSecret = $packet->clientSecret; + $this->rawUUID = $this->uuid->toBinary(); if(count($this->server->getOnlinePlayers()) > $this->server->getMaxPlayers() and $this->kick("disconnectionScreen.serverFull", false)){ break; @@ -1687,164 +1869,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade break; } - if(!$this->server->isWhitelisted(strtolower($this->getName()))){ - $this->close($this->getLeaveMessage(), "Server is white-listed"); - - break; - }elseif($this->server->getNameBans()->isBanned(strtolower($this->getName())) or $this->server->getIPBans()->isBanned($this->getAddress())){ - $this->close($this->getLeaveMessage(), "You are banned"); - - break; - } - - if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){ - $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this); - } - if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){ - $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this); - } - - foreach($this->server->getOnlinePlayers() as $p){ - if($p !== $this and strtolower($p->getName()) === strtolower($this->getName())){ - if($p->kick("logged in from another location") === false){ - $this->close($this->getLeaveMessage(), "Logged in from another location"); - - $timings->stopTiming(); - return; - } - } - } - - $nbt = $this->server->getOfflinePlayerData($this->username); - if(!isset($nbt->NameTag)){ - $nbt->NameTag = new String("NameTag", $this->username); - }else{ - $nbt["NameTag"] = $this->username; - } - $this->gamemode = $nbt["playerGameType"] & 0x03; - if($this->server->getForceGamemode()){ - $this->gamemode = $this->server->getGamemode(); - $nbt->playerGameType = new Int("playerGameType", $this->gamemode); - } - - $this->allowFlight = $this->isCreative(); - - - if(($level = $this->server->getLevelByName($nbt["Level"])) === null){ - $this->setLevel($this->server->getDefaultLevel()); - $nbt["Level"] = $this->level->getName(); - $nbt["Pos"][0] = $this->level->getSpawnLocation()->x; - $nbt["Pos"][1] = $this->level->getSpawnLocation()->y; - $nbt["Pos"][2] = $this->level->getSpawnLocation()->z; - }else{ - $this->setLevel($level); - } - - if(!($nbt instanceof Compound)){ - $this->close($this->getLeaveMessage(), "Invalid data"); - - break; - } - - $this->achievements = []; - - /** @var Byte $achievement */ - foreach($nbt->Achievements as $achievement){ - $this->achievements[$achievement->getName()] = $achievement->getValue() > 0 ? true : false; - } - - $nbt->lastPlayed = new Long("lastPlayed", floor(microtime(true) * 1000)); - if($this->server->getAutoSave()){ - $this->server->saveOfflinePlayerData($this->username, $nbt, true); - } - - parent::__construct($this->level->getChunk($nbt["Pos"][0] >> 4, $nbt["Pos"][2] >> 4, true), $nbt); - $this->loggedIn = true; - - $this->server->getPluginManager()->callEvent($ev = new PlayerLoginEvent($this, "Plugin reason")); - if($ev->isCancelled()){ - $this->close($this->getLeaveMessage(), $ev->getKickMessage()); - - break; - } - - if($this->isCreative()){ - $this->inventory->setHeldItemSlot(0); - }else{ - $this->inventory->setHeldItemSlot(0); - } - - $pk = new PlayStatusPacket(); - $pk->status = PlayStatusPacket::LOGIN_SUCCESS; - $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); - - if($this->spawnPosition === null and isset($this->namedtag->SpawnLevel) and ($level = $this->server->getLevelByName($this->namedtag["SpawnLevel"])) instanceof Level){ - $this->spawnPosition = new Position($this->namedtag["SpawnX"], $this->namedtag["SpawnY"], $this->namedtag["SpawnZ"], $level); - } - - $spawnPosition = $this->getSpawn(); - - $pk = new StartGamePacket(); - $pk->seed = -1; - $pk->x = $this->x; - $pk->y = $this->y; - $pk->z = $this->z; - $pk->spawnX = (int) $spawnPosition->x; - $pk->spawnY = (int) $spawnPosition->y; - $pk->spawnZ = (int) $spawnPosition->z; - $pk->generator = 1; //0 old, 1 infinite, 2 flat - $pk->gamemode = $this->gamemode & 0x01; - $pk->eid = 0; //Always use EntityID as zero for the actual player - $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); - - $pk = new SetTimePacket(); - $pk->time = $this->level->getTime(); - $pk->started = $this->level->stopTime == false; - $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); - - $pk = new SetSpawnPositionPacket(); - $pk->x = (int) $spawnPosition->x; - $pk->y = (int) $spawnPosition->y; - $pk->z = (int) $spawnPosition->z; - $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); - - $pk = new SetHealthPacket(); - $pk->health = $this->getHealth(); - $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); - - $pk = new SetDifficultyPacket(); - $pk->difficulty = $this->server->getDifficulty(); - $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); - - $this->server->getLogger()->info($this->getServer()->getLanguage()->translateString("pocketmine.player.logIn", [ - TextFormat::AQUA . $this->username . TextFormat::WHITE, - $this->ip, - $this->port, - $this->id, - $this->level->getName(), - round($this->x, 4), - round($this->y, 4), - round($this->z, 4) - ])); - - if($this->isOp()){ - $this->setRemoveFormat(false); - } - - if($this->gamemode === Player::SPECTATOR){ - $pk = new ContainerSetContentPacket(); - $pk->windowid = ContainerSetContentPacket::SPECIAL_CREATIVE; - $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); - }else{ - $pk = new ContainerSetContentPacket(); - $pk->windowid = ContainerSetContentPacket::SPECIAL_CREATIVE; - $pk->slots = Item::getCreativeItems(); - $this->dataPacket($pk->setChannel(Network::CHANNEL_PRIORITY)); - } - - $this->forceMovement = $this->teleportPosition = $this->getPosition(); - - $this->server->onPlayerLogin($this); + $this->onPlayerPreLogin(); break; case ProtocolInfo::MOVE_PLAYER_PACKET: @@ -1873,7 +1898,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade } break; - case ProtocolInfo::PLAYER_EQUIPMENT_PACKET: + case ProtocolInfo::MOB_EQUIPMENT_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } @@ -2243,7 +2268,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade } break; - case ProtocolInfo::PLAYER_ARMOR_EQUIPMENT_PACKET: + case ProtocolInfo::MOB_ARMOR_EQUIPMENT_PACKET: break; case ProtocolInfo::INTERACT_PACKET: @@ -2527,136 +2552,138 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade } break; - case ProtocolInfo::CONTAINER_SET_CONTENT_PACKET: - if($packet->windowid === ContainerSetContentPacket::SPECIAL_CRAFTING){ - if(count($packet->slots) < 9){ - $this->inventory->sendContents($this); - break; + case ProtocolInfo::CRAFTING_EVENT_PACKET: + //TODO HACK + $this->server->getLogger()->warning("CRAFTING NOT YET IMPLEMENTED!"); + break; + if(count($packet->slots) < 9){ + $this->inventory->sendContents($this); + break; + } + + foreach($packet->slots as $i => $item){ + /** @var Item $item */ + if($item->getDamage() === -1 or $item->getDamage() === 0xffff){ + $item->setDamage(null); } - foreach($packet->slots as $i => $item){ - /** @var Item $item */ - if($item->getDamage() === -1 or $item->getDamage() === 0xffff){ - $item->setDamage(null); - } - - if($i < 9 and $item->getId() > 0){ - $item->setCount(1); - } - } - - $result = $packet->slots[9]; - - if($this->craftingType === 1 or $this->craftingType === 2){ - $recipe = new BigShapelessRecipe($result); - }else{ - $recipe = new ShapelessRecipe($result); - } - - /** @var Item[] $ingredients */ - $ingredients = []; - for($x = 0; $x < 3; ++$x){ - for($y = 0; $y < 3; ++$y){ - $item = $packet->slots[$x * 3 + $y]; - if($item->getCount() > 0 and $item->getId() > 0){ - //TODO shaped - $recipe->addIngredient($item); - $ingredients[$x * 3 + $y] = $item; - } - } - } - - if(!Server::getInstance()->getCraftingManager()->matchRecipe($recipe)){ - $this->server->getLogger()->debug("Unmatched recipe from player ". $this->getName() .": " . $recipe->getResult().", using: " . implode(", ", $recipe->getIngredientList())); - $this->inventory->sendContents($this); - break; - } - - $canCraft = true; - - $used = array_fill(0, $this->inventory->getSize(), 0); - - foreach($ingredients as $ingredient){ - $slot = -1; - $checkDamage = $ingredient->getDamage() === null ? false : true; - foreach($this->inventory->getContents() as $index => $i){ - if($ingredient->equals($i, $checkDamage) and ($i->getCount() - $used[$index]) >= 1){ - $slot = $index; - $used[$index]++; - break; - } - } - - if($slot === -1){ - $canCraft = false; - break; - } - } - - if(!$canCraft){ - $this->inventory->sendContents($this); - break; - } - - foreach($used as $slot => $count){ - if($count === 0){ - continue; - } - - $item = $this->inventory->getItem($slot); - - if($item->getCount() > $count){ - $newItem = clone $item; - $newItem->setCount($item->getCount() - $count); - }else{ - $newItem = Item::get(Item::AIR, 0, 0); - } - - $this->inventory->setItem($slot, $newItem); - } - - $extraItem = $this->inventory->addItem($recipe->getResult()); - if(count($extraItem) > 0){ - foreach($extraItem as $item){ - $this->level->dropItem($this, $item); - } - } - - switch($recipe->getResult()->getId()){ - case Item::WORKBENCH: - $this->awardAchievement("buildWorkBench"); - break; - case Item::WOODEN_PICKAXE: - $this->awardAchievement("buildPickaxe"); - break; - case Item::FURNACE: - $this->awardAchievement("buildFurnace"); - break; - case Item::WOODEN_HOE: - $this->awardAchievement("buildHoe"); - break; - case Item::BREAD: - $this->awardAchievement("makeBread"); - break; - case Item::CAKE: - //TODO: detect complex recipes like cake that leave remains - $this->awardAchievement("bakeCake"); - $this->inventory->addItem(Item::get(Item::BUCKET, 0, 3)); - break; - case Item::STONE_PICKAXE: - case Item::GOLD_PICKAXE: - case Item::IRON_PICKAXE: - case Item::DIAMOND_PICKAXE: - $this->awardAchievement("buildBetterPickaxe"); - break; - case Item::WOODEN_SWORD: - $this->awardAchievement("buildSword"); - break; - case Item::DIAMOND: - $this->awardAchievement("diamond"); - break; + if($i < 9 and $item->getId() > 0){ + $item->setCount(1); } } + + $result = $packet->slots[9]; + + if($this->craftingType === 1 or $this->craftingType === 2){ + $recipe = new BigShapelessRecipe($result); + }else{ + $recipe = new ShapelessRecipe($result); + } + + /** @var Item[] $ingredients */ + $ingredients = []; + for($x = 0; $x < 3; ++$x){ + for($y = 0; $y < 3; ++$y){ + $item = $packet->slots[$x * 3 + $y]; + if($item->getCount() > 0 and $item->getId() > 0){ + //TODO shaped + $recipe->addIngredient($item); + $ingredients[$x * 3 + $y] = $item; + } + } + } + + if(!Server::getInstance()->getCraftingManager()->matchRecipe($recipe)){ + $this->server->getLogger()->debug("Unmatched recipe from player ". $this->getName() .": " . $recipe->getResult().", using: " . implode(", ", $recipe->getIngredientList())); + $this->inventory->sendContents($this); + break; + } + + $canCraft = true; + + $used = array_fill(0, $this->inventory->getSize(), 0); + + foreach($ingredients as $ingredient){ + $slot = -1; + $checkDamage = $ingredient->getDamage() === null ? false : true; + foreach($this->inventory->getContents() as $index => $i){ + if($ingredient->equals($i, $checkDamage) and ($i->getCount() - $used[$index]) >= 1){ + $slot = $index; + $used[$index]++; + break; + } + } + + if($slot === -1){ + $canCraft = false; + break; + } + } + + if(!$canCraft){ + $this->inventory->sendContents($this); + break; + } + + foreach($used as $slot => $count){ + if($count === 0){ + continue; + } + + $item = $this->inventory->getItem($slot); + + if($item->getCount() > $count){ + $newItem = clone $item; + $newItem->setCount($item->getCount() - $count); + }else{ + $newItem = Item::get(Item::AIR, 0, 0); + } + + $this->inventory->setItem($slot, $newItem); + } + + $extraItem = $this->inventory->addItem($recipe->getResult()); + if(count($extraItem) > 0){ + foreach($extraItem as $item){ + $this->level->dropItem($this, $item); + } + } + + switch($recipe->getResult()->getId()){ + case Item::WORKBENCH: + $this->awardAchievement("buildWorkBench"); + break; + case Item::WOODEN_PICKAXE: + $this->awardAchievement("buildPickaxe"); + break; + case Item::FURNACE: + $this->awardAchievement("buildFurnace"); + break; + case Item::WOODEN_HOE: + $this->awardAchievement("buildHoe"); + break; + case Item::BREAD: + $this->awardAchievement("makeBread"); + break; + case Item::CAKE: + //TODO: detect complex recipes like cake that leave remains + $this->awardAchievement("bakeCake"); + $this->inventory->addItem(Item::get(Item::BUCKET, 0, 3)); + break; + case Item::STONE_PICKAXE: + case Item::GOLD_PICKAXE: + case Item::IRON_PICKAXE: + case Item::DIAMOND_PICKAXE: + $this->awardAchievement("buildBetterPickaxe"); + break; + case Item::WOODEN_SWORD: + $this->awardAchievement("buildSword"); + break; + case Item::DIAMOND: + $this->awardAchievement("diamond"); + break; + } + break; case ProtocolInfo::CONTAINER_SET_SLOT_PACKET: @@ -2761,7 +2788,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade TextFormat::clean($nbt["Text1"], $this->removeFormat), TextFormat::clean($nbt["Text2"], $this->removeFormat), TextFormat::clean($nbt["Text3"], $this->removeFormat), TextFormat::clean($nbt["Text4"], $this->removeFormat) ]); - if(!isset($t->namedtag->Creator) or $t->namedtag["Creator"] !== $this->getUniqueId()){ + if(!isset($t->namedtag->Creator) or $t->namedtag["Creator"] !== $this->getRawUniqueId()){ $ev->setCancelled(); }else{ foreach($ev->getLines() as $line){ @@ -2918,6 +2945,10 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade $this->interface->close($this, $notify ? $reason : ""); + if($this->loggedIn){ + $this->server->removeOnlinePlayer($this); + } + $this->loggedIn = false; if(isset($ev) and $this->username != "" and $this->spawned !== false and $ev->getQuitMessage() != ""){ @@ -3217,12 +3248,6 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade } } - if($this->chunk !== null and $this->spawned){ - //TODO HACK: Minecraft: PE does not like moving a player from old chunks. - //Player entities get stuck in unloaded chunks and the client does not accept position updates. - $this->sendPosition($this, null, null, MovePlayerPacket::MODE_RESET, Network::CHANNEL_MOVEMENT, $reload); - } - foreach($newChunk as $player){ $this->spawnTo($player); } diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index e5e05d4c1..ec4319f02 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -53,6 +53,8 @@ use pocketmine\event\TranslationContainer; use pocketmine\inventory\CraftingManager; use pocketmine\inventory\InventoryType; use pocketmine\inventory\Recipe; +use pocketmine\inventory\ShapedRecipe; +use pocketmine\inventory\ShapelessRecipe; use pocketmine\item\Item; use pocketmine\lang\BaseLang; use pocketmine\level\format\anvil\Anvil; @@ -80,7 +82,9 @@ use pocketmine\nbt\tag\String; use pocketmine\network\CompressBatchedTask; use pocketmine\network\Network; use pocketmine\network\protocol\BatchPacket; +use pocketmine\network\protocol\CraftingDataPacket; use pocketmine\network\protocol\DataPacket; +use pocketmine\network\protocol\PlayerListPacket; use pocketmine\network\query\QueryHandler; use pocketmine\network\RakLibInterface; use pocketmine\network\rcon\RCON; @@ -111,6 +115,7 @@ use pocketmine\utils\Terminal; use pocketmine\utils\TextFormat; use pocketmine\utils\TextWrapper; use pocketmine\utils\Utils; +use pocketmine\utils\UUID; use pocketmine\utils\VersionString; /** @@ -249,6 +254,9 @@ class Server{ /** @var Player[] */ private $players = []; + /** @var Player[] */ + private $playerList = []; + private $identifiers = []; /** @var Level[] */ @@ -705,7 +713,7 @@ class Server{ * @return Player[] */ public function getOnlinePlayers(){ - return $this->players; + return $this->playerList; } public function addRecipe(Recipe $recipe){ @@ -2311,8 +2319,11 @@ class Server{ public function onPlayerLogin(Player $player){ if($this->sendUsageTicker > 0){ - $this->uniquePlayers[$player->getUniqueId()] = $player->getUniqueId(); + $this->uniquePlayers[$player->getRawUniqueId()] = $player->getRawUniqueId(); } + + $this->sendFullPlayerListData($player); + $this->sendRecipeList($player); } public function addPlayer($identifier, Player $player){ @@ -2320,6 +2331,66 @@ class Server{ $this->identifiers[spl_object_hash($player)] = $identifier; } + public function addOnlinePlayer(Player $player){ + $this->playerList[$player->getRawUniqueId()] = $player; + + $this->updatePlayerListData($player->getUniqueId(), $player->getUniqueId(), $player->getDisplayName(), $player->isSkinSlim(), $player->getSkinData()); + } + + public function removeOnlinePlayer(Player $player){ + if(isset($this->playerList[$player->getRawUniqueId()])){ + unset($this->playerList[$player->getRawUniqueId()]); + + $pk = new PlayerListPacket(); + $pk->type = PlayerListPacket::TYPE_REMOVE; + $pk->entries[] = [$player->getUniqueId()]; + Server::broadcastPacket($this->playerList, $pk->setChannel(Network::CHANNEL_ENTITY_SPAWNING)); + } + } + + public function updatePlayerListData(UUID $uuid, $entityId, $name, $isSlim, $skinData, array $players = null){ + $pk = new PlayerListPacket(); + $pk->type = PlayerListPacket::TYPE_ADD; + $pk->entries[] = [$uuid, $entityId, $name, $isSlim, $skinData]; + Server::broadcastPacket($players === null ? $this->playerList : $players, $pk->setChannel(Network::CHANNEL_ENTITY_SPAWNING)); + } + + public function removePlayerListData(UUID $uuid, array $players = null){ + $pk = new PlayerListPacket(); + $pk->type = PlayerListPacket::TYPE_REMOVE; + $pk->entries[] = [$uuid]; + Server::broadcastPacket($players === null ? $this->playerList : $players, $pk->setChannel(Network::CHANNEL_ENTITY_SPAWNING)); + } + + public function sendFullPlayerListData(Player $p){ + $pk = new PlayerListPacket(); + $pk->type = PlayerListPacket::TYPE_ADD; + foreach($this->playerList as $player){ + $pk->entries[] = [$player->getUniqueId(), $player->getUniqueId(), $player->getDisplayName(), $player->isSkinSlim(), $player->getSkinData()]; + } + + $p->dataPacket($pk->setChannel(Network::CHANNEL_ENTITY_SPAWNING)); + } + + public function sendRecipeList(Player $p){ + $pk = new CraftingDataPacket(); + $pk->cleanRecipes = true; + + foreach($this->getCraftingManager()->getRecipes() as $recipe){ + if($recipe instanceof ShapedRecipe){ + $pk->addShapedRecipe($recipe); + }elseif($recipe instanceof ShapelessRecipe){ + $pk->addShapelessRecipe($recipe); + } + } + + foreach($this->getCraftingManager()->getFurnaceRecipes() as $recipe){ + $pk->addFurnaceRecipe($recipe); + } + + $p->dataPacket($pk->setChannel(Network::CHANNEL_WORLD_EVENTS)); + } + private function checkTickUpdates($currentTick, $tickTime){ foreach($this->players as $p){ if(!$p->loggedIn and ($tickTime - $p->creationTime) >= 10){ @@ -2370,7 +2441,7 @@ class Server{ public function doAutoSave(){ if($this->getAutoSave()){ Timings::$worldSaveTimer->startTiming(); - foreach($this->getOnlinePlayers() as $index => $player){ + foreach($this->players as $index => $player){ if($player->isOnline()){ $player->save(true); }elseif(!$player->isConnected()){ diff --git a/src/pocketmine/entity/Human.php b/src/pocketmine/entity/Human.php index 3827ed639..5389f4f2d 100644 --- a/src/pocketmine/entity/Human.php +++ b/src/pocketmine/entity/Human.php @@ -24,6 +24,7 @@ namespace pocketmine\entity; use pocketmine\inventory\InventoryHolder; use pocketmine\inventory\PlayerInventory; use pocketmine\item\Item as ItemItem; +use pocketmine\utils\UUID; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Byte; use pocketmine\nbt\tag\Compound; @@ -46,6 +47,11 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{ /** @var PlayerInventory */ protected $inventory; + + /** @var UUID */ + protected $uuid; + protected $rawUUID; + public $width = 0.6; public $length = 0.6; public $height = 1.8; @@ -62,6 +68,20 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{ return $this->isSlim; } + /** + * @return UUID|null + */ + public function getUniqueId(){ + return $this->uuid; + } + + /** + * @return string + */ + public function getRawUniqueId(){ + return $this->rawUUID; + } + /** * @param string $str * @param bool $isSlim @@ -94,6 +114,8 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{ if(isset($this->namedtag->Skin) and $this->namedtag->Skin instanceof Compound){ $this->setSkin($this->namedtag->Skin["Data"], $this->namedtag->Skin["Slim"] > 0); } + + $this->uuid = UUID::fromData($this->getId(), $this->getSkinData(), $this->getNameTag()); } if(isset($this->namedtag->Inventory) and $this->namedtag->Inventory instanceof Enum){ @@ -198,8 +220,13 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{ throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set"); } + + if(!($this instanceof Player)){ + $this->server->updatePlayerListData($this->getUniqueId(), $this->getId(), $this->getName(), $this->isSlim, $this->skin, $player); + } + $pk = new AddPlayerPacket(); - $pk->clientID = $this->getId(); + $pk->uuid = $this->getUniqueId(); $pk->username = $this->getName(); $pk->eid = $this->getId(); $pk->x = $this->x; @@ -219,13 +246,18 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{ $player->dataPacket($pk->setChannel(Network::CHANNEL_ENTITY_SPAWNING)); $this->inventory->sendArmorContents($player); + + if(!($this instanceof Player)){ + $this->server->removePlayerListData($this->getUniqueId(), $player); + } } } public function despawnFrom(Player $player){ if(isset($this->hasSpawned[$player->getLoaderId()])){ + $pk = new RemovePlayerPacket(); - $pk->eid = $this->getId(); + $pk->eid = $this->getUniqueId(); $pk->clientID = $this->getId(); $player->dataPacket($pk->setChannel(Network::CHANNEL_ENTITY_SPAWNING)); unset($this->hasSpawned[$player->getLoaderId()]); diff --git a/src/pocketmine/inventory/CraftingManager.php b/src/pocketmine/inventory/CraftingManager.php index 75d81bab8..7298c9547 100644 --- a/src/pocketmine/inventory/CraftingManager.php +++ b/src/pocketmine/inventory/CraftingManager.php @@ -26,6 +26,7 @@ use pocketmine\block\Stone; use pocketmine\block\Wood; use pocketmine\block\Wood2; use pocketmine\item\Item; +use pocketmine\utils\UUID; class CraftingManager{ @@ -38,6 +39,8 @@ class CraftingManager{ /** @var FurnaceRecipe[] */ public $furnaceRecipes = []; + private static $RECIPE_COUNT = 0; + public function __construct(){ $this->registerStonecutter(); @@ -519,6 +522,8 @@ class CraftingManager{ }elseif($recipe instanceof FurnaceRecipe){ $this->registerFurnaceRecipe($recipe); } + + $recipe->setId(UUID::fromData(++self::$RECIPE_COUNT, $recipe->getResult()->getId(), $recipe->getResult()->getDamage(), $recipe->getResult()->getCount(), $recipe->getResult()->getCompoundTag())); } } diff --git a/src/pocketmine/inventory/FurnaceRecipe.php b/src/pocketmine/inventory/FurnaceRecipe.php index dfefdca21..34b4dbf81 100644 --- a/src/pocketmine/inventory/FurnaceRecipe.php +++ b/src/pocketmine/inventory/FurnaceRecipe.php @@ -23,8 +23,12 @@ namespace pocketmine\inventory; use pocketmine\item\Item; use pocketmine\Server; +use pocketmine\utils\UUID; class FurnaceRecipe implements Recipe{ + + private $id = null; + /** @var Item */ private $output; @@ -40,6 +44,18 @@ class FurnaceRecipe implements Recipe{ $this->ingredient = clone $ingredient; } + public function getId(){ + return $this->id; + } + + public function setId(UUID $id){ + if($this->id !== null){ + throw new \InvalidStateException("Id is already set"); + } + + $this->id = $id; + } + /** * @param Item $item */ diff --git a/src/pocketmine/inventory/PlayerInventory.php b/src/pocketmine/inventory/PlayerInventory.php index 05b9f2a39..9348f6ec4 100644 --- a/src/pocketmine/inventory/PlayerInventory.php +++ b/src/pocketmine/inventory/PlayerInventory.php @@ -29,8 +29,8 @@ use pocketmine\item\Item; use pocketmine\network\Network; use pocketmine\network\protocol\ContainerSetContentPacket; use pocketmine\network\protocol\ContainerSetSlotPacket; -use pocketmine\network\protocol\PlayerArmorEquipmentPacket; -use pocketmine\network\protocol\PlayerEquipmentPacket; +use pocketmine\network\protocol\MobArmorEquipmentPacket; +use pocketmine\network\protocol\MobEquipmentPacket; use pocketmine\Player; use pocketmine\Server; @@ -126,7 +126,7 @@ class PlayerInventory extends BaseInventory{ public function sendHeldItem($target){ $item = $this->getItemInHand(); - $pk = new PlayerEquipmentPacket(); + $pk = new MobEquipmentPacket(); $pk->eid = ($target === $this->getHolder() ? 0 : $this->getHolder()->getId()); $pk->item = $item->getId(); $pk->meta = $item->getDamage(); @@ -315,7 +315,7 @@ class PlayerInventory extends BaseInventory{ } } - $pk = new PlayerArmorEquipmentPacket(); + $pk = new MobArmorEquipmentPacket(); $pk->eid = $this->getHolder()->getId(); $pk->slots = $slots; $pk->encode(); @@ -372,7 +372,7 @@ class PlayerInventory extends BaseInventory{ } } - $pk = new PlayerArmorEquipmentPacket(); + $pk = new MobArmorEquipmentPacket(); $pk->eid = $this->getHolder()->getId(); $pk->slots = $slots; $pk->encode(); diff --git a/src/pocketmine/inventory/Recipe.php b/src/pocketmine/inventory/Recipe.php index ae11e250b..bcac5ba75 100644 --- a/src/pocketmine/inventory/Recipe.php +++ b/src/pocketmine/inventory/Recipe.php @@ -21,6 +21,8 @@ namespace pocketmine\inventory; +use pocketmine\utils\UUID; + interface Recipe{ /** @@ -29,4 +31,9 @@ interface Recipe{ public function getResult(); public function registerToCraftingManager(); + + /** + * @return UUID + */ + public function getId(); } \ No newline at end of file diff --git a/src/pocketmine/inventory/ShapedRecipe.php b/src/pocketmine/inventory/ShapedRecipe.php index 6371a68d8..1dea57c79 100644 --- a/src/pocketmine/inventory/ShapedRecipe.php +++ b/src/pocketmine/inventory/ShapedRecipe.php @@ -23,11 +23,14 @@ namespace pocketmine\inventory; use pocketmine\item\Item; use pocketmine\Server; +use pocketmine\utils\UUID; class ShapedRecipe implements Recipe{ /** @var Item */ private $output; + private $id = null; + /** @var string[] */ private $rows = []; @@ -61,6 +64,22 @@ class ShapedRecipe implements Recipe{ $this->output = clone $result; } + public function getResult(){ + return $this->output; + } + + public function getId(){ + return $this->id; + } + + public function setId(UUID $id){ + if($this->id !== null){ + throw new \InvalidStateException("Id is already set"); + } + + $this->id = $id; + } + /** * @param string $key * @param Item $item diff --git a/src/pocketmine/inventory/ShapelessRecipe.php b/src/pocketmine/inventory/ShapelessRecipe.php index be87c8006..550411a2e 100644 --- a/src/pocketmine/inventory/ShapelessRecipe.php +++ b/src/pocketmine/inventory/ShapelessRecipe.php @@ -23,11 +23,14 @@ namespace pocketmine\inventory; use pocketmine\item\Item; use pocketmine\Server; +use pocketmine\utils\UUID; class ShapelessRecipe implements Recipe{ /** @var Item */ private $output; + private $id = null; + /** @var Item[] */ private $ingredients = []; @@ -35,6 +38,18 @@ class ShapelessRecipe implements Recipe{ $this->output = clone $result; } + public function getId(){ + return $this->id; + } + + public function setId(UUID $id){ + if($this->id !== null){ + throw new \InvalidStateException("Id is already set"); + } + + $this->id = $id; + } + public function getResult(){ return clone $this->output; } diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 410d0c7be..dbf5e89a3 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -1729,7 +1729,7 @@ class Level implements ChunkManager, Metadatable{ "Text4" => new String("Text4", "") ])); if($player !== null){ - $tile->namedtag->Creator = new String("Creator", $player->getUniqueId()); + $tile->namedtag->Creator = new String("Creator", $player->getRawUniqueId()); } } $item->setCount($item->getCount() - 1); diff --git a/src/pocketmine/network/Network.php b/src/pocketmine/network/Network.php index 06387e948..111cc6f85 100644 --- a/src/pocketmine/network/Network.php +++ b/src/pocketmine/network/Network.php @@ -36,6 +36,8 @@ use pocketmine\network\protocol\ContainerOpenPacket; use pocketmine\network\protocol\ContainerSetContentPacket; use pocketmine\network\protocol\ContainerSetDataPacket; use pocketmine\network\protocol\ContainerSetSlotPacket; +use pocketmine\network\protocol\CraftingDataPacket; +use pocketmine\network\protocol\CraftingEventPacket; use pocketmine\network\protocol\DataPacket; use pocketmine\network\protocol\DropItemPacket; use pocketmine\network\protocol\FullChunkDataPacket; @@ -55,8 +57,8 @@ use pocketmine\network\protocol\TextPacket; use pocketmine\network\protocol\MoveEntityPacket; use pocketmine\network\protocol\MovePlayerPacket; use pocketmine\network\protocol\PlayerActionPacket; -use pocketmine\network\protocol\PlayerArmorEquipmentPacket; -use pocketmine\network\protocol\PlayerEquipmentPacket; +use pocketmine\network\protocol\MobArmorEquipmentPacket; +use pocketmine\network\protocol\MobEquipmentPacket; use pocketmine\network\protocol\RemoveBlockPacket; use pocketmine\network\protocol\RemoveEntityPacket; use pocketmine\network\protocol\RemovePlayerPacket; @@ -72,6 +74,7 @@ use pocketmine\network\protocol\TakeItemEntityPacket; use pocketmine\network\protocol\TileEventPacket; use pocketmine\network\protocol\UpdateBlockPacket; use pocketmine\network\protocol\UseItemPacket; +use pocketmine\network\protocol\PlayerListPacket; use pocketmine\Player; use pocketmine\Server; use pocketmine\utils\Binary; @@ -296,6 +299,7 @@ class Network{ $this->registerPacket(ProtocolInfo::LOGIN_PACKET, LoginPacket::class); $this->registerPacket(ProtocolInfo::PLAY_STATUS_PACKET, PlayStatusPacket::class); $this->registerPacket(ProtocolInfo::DISCONNECT_PACKET, DisconnectPacket::class); + $this->registerPacket(ProtocolInfo::BATCH_PACKET, BatchPacket::class); $this->registerPacket(ProtocolInfo::TEXT_PACKET, TextPacket::class); $this->registerPacket(ProtocolInfo::SET_TIME_PACKET, SetTimePacket::class); $this->registerPacket(ProtocolInfo::START_GAME_PACKET, StartGamePacket::class); @@ -314,8 +318,8 @@ class Network{ $this->registerPacket(ProtocolInfo::LEVEL_EVENT_PACKET, LevelEventPacket::class); $this->registerPacket(ProtocolInfo::TILE_EVENT_PACKET, TileEventPacket::class); $this->registerPacket(ProtocolInfo::ENTITY_EVENT_PACKET, EntityEventPacket::class); - $this->registerPacket(ProtocolInfo::PLAYER_EQUIPMENT_PACKET, PlayerEquipmentPacket::class); - $this->registerPacket(ProtocolInfo::PLAYER_ARMOR_EQUIPMENT_PACKET, PlayerArmorEquipmentPacket::class); + $this->registerPacket(ProtocolInfo::MOB_EQUIPMENT_PACKET, MobEquipmentPacket::class); + $this->registerPacket(ProtocolInfo::MOB_ARMOR_EQUIPMENT_PACKET, MobArmorEquipmentPacket::class); $this->registerPacket(ProtocolInfo::INTERACT_PACKET, InteractPacket::class); $this->registerPacket(ProtocolInfo::USE_ITEM_PACKET, UseItemPacket::class); $this->registerPacket(ProtocolInfo::PLAYER_ACTION_PACKET, PlayerActionPacket::class); @@ -333,10 +337,12 @@ class Network{ $this->registerPacket(ProtocolInfo::CONTAINER_SET_SLOT_PACKET, ContainerSetSlotPacket::class); $this->registerPacket(ProtocolInfo::CONTAINER_SET_DATA_PACKET, ContainerSetDataPacket::class); $this->registerPacket(ProtocolInfo::CONTAINER_SET_CONTENT_PACKET, ContainerSetContentPacket::class); + $this->registerPacket(ProtocolInfo::CRAFTING_DATA_PACKET, CraftingDataPacket::class); + $this->registerPacket(ProtocolInfo::CRAFTING_EVENT_PACKET, CraftingEventPacket::class); $this->registerPacket(ProtocolInfo::ADVENTURE_SETTINGS_PACKET, AdventureSettingsPacket::class); $this->registerPacket(ProtocolInfo::TILE_ENTITY_DATA_PACKET, TileEntityDataPacket::class); $this->registerPacket(ProtocolInfo::FULL_CHUNK_DATA_PACKET, FullChunkDataPacket::class); $this->registerPacket(ProtocolInfo::SET_DIFFICULTY_PACKET, SetDifficultyPacket::class); - $this->registerPacket(ProtocolInfo::BATCH_PACKET, BatchPacket::class); + $this->registerPacket(ProtocolInfo::PLAYER_LIST_PACKET, PlayerListPacket::class); } } diff --git a/src/pocketmine/network/protocol/AddPlayerPacket.php b/src/pocketmine/network/protocol/AddPlayerPacket.php index 5e3154875..afa1eb833 100644 --- a/src/pocketmine/network/protocol/AddPlayerPacket.php +++ b/src/pocketmine/network/protocol/AddPlayerPacket.php @@ -31,7 +31,7 @@ use pocketmine\utils\Binary; class AddPlayerPacket extends DataPacket{ const NETWORK_ID = Info::ADD_PLAYER_PACKET; - public $clientID; + public $uuid; public $username; public $eid; public $x; @@ -43,19 +43,15 @@ class AddPlayerPacket extends DataPacket{ public $pitch; public $yaw; public $item; - public $meta; public $metadata; - public $slim = false; - public $skin = null; - public function decode(){ } public function encode(){ $this->reset(); - $this->putLong($this->clientID); + $this->putUUID($this->uuid); $this->putString($this->username); $this->putLong($this->eid); $this->putFloat($this->x); @@ -67,10 +63,8 @@ class AddPlayerPacket extends DataPacket{ $this->putFloat($this->yaw); $this->putFloat($this->yaw); //TODO headrot $this->putFloat($this->pitch); - $this->putShort($this->item); - $this->putShort($this->meta); - $this->putByte($this->slim ? 1 : 0); - $this->putString($this->skin); + $this->putSlot($this->item); + $meta = Binary::writeMetadata($this->metadata); $this->put($meta); } diff --git a/src/pocketmine/network/protocol/ContainerSetContentPacket.php b/src/pocketmine/network/protocol/ContainerSetContentPacket.php index 108008965..ebeabd20d 100644 --- a/src/pocketmine/network/protocol/ContainerSetContentPacket.php +++ b/src/pocketmine/network/protocol/ContainerSetContentPacket.php @@ -30,7 +30,6 @@ class ContainerSetContentPacket extends DataPacket{ const SPECIAL_INVENTORY = 0; const SPECIAL_ARMOR = 0x78; const SPECIAL_CREATIVE = 0x79; - const SPECIAL_CRAFTING = 0x7a; public $windowid; public $slots = []; diff --git a/src/pocketmine/network/protocol/CraftingDataPacket.php b/src/pocketmine/network/protocol/CraftingDataPacket.php new file mode 100644 index 000000000..1c1f7127d --- /dev/null +++ b/src/pocketmine/network/protocol/CraftingDataPacket.php @@ -0,0 +1,122 @@ + + + +use pocketmine\inventory\FurnaceRecipe; +use pocketmine\inventory\ShapedRecipe; +use pocketmine\inventory\ShapelessRecipe; +use pocketmine\utils\Binary; + +class CraftingDataPacket extends DataPacket{ + const NETWORK_ID = Info::CRAFTING_DATA_PACKET; + + const ENTRY_SHAPELESS = 0; + const ENTRY_SHAPED = 1; + const ENTRY_FURNACE = 2; + const ENTRY_FURNACE_DATA = 3; + const ENTRY_ENCHANT = 4; + + /** @var object[] */ + public $entries = []; + public $cleanRecipes = false; + + public function writeEntry($entry){ + if($entry instanceof ShapelessRecipe){ + $this->writeShapelessRecipe($entry); + }elseif($entry instanceof ShapedRecipe){ + $this->writeShapedRecipe($entry); + }elseif($entry instanceof FurnaceRecipe){ + $this->writeFurnaceRecipe($entry); + } + } + + private function writeShapelessRecipe(ShapelessRecipe $recipe){ + $this->putInt(CraftingDataPacket::ENTRY_SHAPELESS); + + $this->putInt($recipe->getIngredientCount()); + foreach($recipe->getIngredientList() as $item){ + $this->putSlot($item); + } + + $this->putInt(1); + $this->putSlot($recipe->getResult()); + } + + private function writeShapedRecipe(ShapedRecipe $recipe){ + $this->putInt(CraftingDataPacket::ENTRY_SHAPED); + } + + private function writeFurnaceRecipe(FurnaceRecipe $recipe){ + if($recipe->getInput()->getDamage() !== 0){ //Data recipe + $this->putInt(CraftingDataPacket::ENTRY_FURNACE_DATA); + $this->putInt(($recipe->getInput()->getId() << 16) | ($recipe->getInput()->getDamage())); + $this->putSlot($recipe->getResult()); + }else{ + $this->putInt(CraftingDataPacket::ENTRY_FURNACE); + $this->putInt($recipe->getInput()->getId()); + $this->putSlot($recipe->getResult()); + } + } + + private function writeEnchant(){ + $entry = Binary::writeInt(CraftingDataPacket::ENTRY_ENCHANT); + //TODO + } + + public function addShapelessRecipe(ShapelessRecipe $recipe){ + $this->entries[] = $recipe; + } + + public function addShapedRecipe(ShapedRecipe $recipe){ + $this->entries[] = $recipe; + } + + public function addFurnaceRecipe(FurnaceRecipe $recipe){ + $this->entries[] = $recipe; + } + + public function addEnchant(){ + //TODO + } + + public function clean(){ + $this->entries = []; + return parent::clean(); + } + + public function decode(){ + + } + + public function encode(){ + $this->reset(); + $this->putByte($this->type); + $this->putInt(count($this->entries)); + foreach($this->entries as $d){ + + } + } + +} diff --git a/src/pocketmine/network/protocol/CraftingEventPacket.php b/src/pocketmine/network/protocol/CraftingEventPacket.php new file mode 100644 index 000000000..eb3abc612 --- /dev/null +++ b/src/pocketmine/network/protocol/CraftingEventPacket.php @@ -0,0 +1,62 @@ + + + +class CraftingEventPacket extends DataPacket{ + const NETWORK_ID = Info::CRAFTING_EVENT_PACKET; + + public $windowId; + public $type; + public $id; + public $input = []; + public $output = []; + + public function clean(){ + $this->input = []; + $this->output = []; + return parent::clean(); + } + + public function decode(){ + $this->windowId = $this->getByte(); + $this->type = $this->getInt(); + $this->id = $this->getUUID(); + + $size = $this->getInt(); + for($i = 0; $i < $size and $i < 128; ++$i){ + $this->input[] = $this->getSlot(); + } + + $size = $this->getInt(); + for($i = 0; $i < $size and $i < 128; ++$i){ + $this->output[] = $this->getSlot(); + } + } + + public function encode(){ + + } + +} diff --git a/src/pocketmine/network/protocol/DataPacket.php b/src/pocketmine/network/protocol/DataPacket.php index 0077bfcb1..998063c1b 100644 --- a/src/pocketmine/network/protocol/DataPacket.php +++ b/src/pocketmine/network/protocol/DataPacket.php @@ -28,6 +28,7 @@ use pocketmine\utils\Binary; #endif use pocketmine\item\Item; +use pocketmine\utils\UUID; abstract class DataPacket extends \stdClass{ @@ -162,10 +163,18 @@ abstract class DataPacket extends \stdClass{ } } + protected function getUUID(){ + return UUID::fromBinary($this->get(16)); + } + + protected function putUUID(UUID $uuid){ + $this->put($uuid->toBinary()); + } + protected function getSlot(){ - $id = $this->getShort(false); + $id = $this->getShort(true); - if($id == 0xffff){ + if($id <= 0){ return Item::get(0, 0, 0); } @@ -191,7 +200,7 @@ abstract class DataPacket extends \stdClass{ protected function putSlot(Item $item){ if($item->getId() === 0){ - $this->putShort(0xffff); + $this->putShort(0); return; } diff --git a/src/pocketmine/network/protocol/EntityEventPacket.php b/src/pocketmine/network/protocol/EntityEventPacket.php index f41bc6589..53aba2365 100644 --- a/src/pocketmine/network/protocol/EntityEventPacket.php +++ b/src/pocketmine/network/protocol/EntityEventPacket.php @@ -43,6 +43,8 @@ class EntityEventPacket extends DataPacket{ const AMBIENT_SOUND = 16; const RESPAWN = 17; + //TODO add new events + public $eid; public $event; diff --git a/src/pocketmine/network/protocol/Info.php b/src/pocketmine/network/protocol/Info.php index 3a6a9c2d7..6da042f8a 100644 --- a/src/pocketmine/network/protocol/Info.php +++ b/src/pocketmine/network/protocol/Info.php @@ -30,7 +30,7 @@ interface Info{ /** * Actual Minecraft: PE protocol version */ - const CURRENT_PROTOCOL = 28; + const CURRENT_PROTOCOL = 30; const LOGIN_PACKET = 0x82; const PLAY_STATUS_PACKET = 0x83; @@ -56,8 +56,8 @@ interface Info{ const ENTITY_EVENT_PACKET = 0x97; const MOB_EFFECT_PACKET = 0x98; const UPDATE_ATTRIBUTES_PACKET = 0x99; - const PLAYER_EQUIPMENT_PACKET = 0x9a; - const PLAYER_ARMOR_EQUIPMENT_PACKET = 0x9b; + const MOB_EQUIPMENT_PACKET = 0x9a; + const MOB_ARMOR_EQUIPMENT_PACKET = 0x9b; const INTERACT_PACKET = 0x9c; const USE_ITEM_PACKET = 0x9d; const PLAYER_ACTION_PACKET = 0x9e; @@ -75,14 +75,17 @@ interface Info{ const CONTAINER_SET_SLOT_PACKET = 0xaa; const CONTAINER_SET_DATA_PACKET = 0xab; const CONTAINER_SET_CONTENT_PACKET = 0xac; - const ADVENTURE_SETTINGS_PACKET = 0xad; - const TILE_ENTITY_DATA_PACKET = 0xae; - //const PLAYER_INPUT_PACKET = 0xaf; - const FULL_CHUNK_DATA_PACKET = 0xb0; - const SET_DIFFICULTY_PACKET = 0xb1; - //const CHANGE_DIMENSION_PACKET = 0xb2; - //const SET_PLAYER_GAMETYPE_PACKET = 0xb3; - //const PLAYER_LIST_PACKET = 0xb4; + const CRAFTING_DATA_PACKET = 0xad; + const CRAFTING_EVENT_PACKET = 0xae; + const ADVENTURE_SETTINGS_PACKET = 0xaf; + const TILE_ENTITY_DATA_PACKET = 0xb0; + //const PLAYER_INPUT_PACKET = 0xb1; + const FULL_CHUNK_DATA_PACKET = 0xb2; + const SET_DIFFICULTY_PACKET = 0xb3; + //const CHANGE_DIMENSION_PACKET = 0xb4; + //const SET_PLAYER_GAMETYPE_PACKET = 0xb5; + const PLAYER_LIST_PACKET = 0xb6; + //const TELEMETRY_EVENT_PACKET = 0xb7; } diff --git a/src/pocketmine/network/protocol/LoginPacket.php b/src/pocketmine/network/protocol/LoginPacket.php index 8020387d6..3cc8eca94 100644 --- a/src/pocketmine/network/protocol/LoginPacket.php +++ b/src/pocketmine/network/protocol/LoginPacket.php @@ -32,6 +32,10 @@ class LoginPacket extends DataPacket{ public $protocol2; public $clientId; + public $clientUUID; + public $serverAddress; + public $clientSecret; + public $slim = false; public $skin = null; @@ -39,11 +43,15 @@ class LoginPacket extends DataPacket{ $this->username = $this->getString(); $this->protocol1 = $this->getInt(); $this->protocol2 = $this->getInt(); - if($this->protocol1 < 22){ //New fields! + if($this->protocol1 < Info::CURRENT_PROTOCOL){ //New fields! $this->setBuffer(null, 0); //Skip batch packet handling return; } $this->clientId = $this->getLong(); + $this->clientUUID = $this->getUUID(); + $this->serverAddress = $this->getString(); + $this->clientSecret = $this->getString(); + $this->slim = $this->getByte() > 0; $this->skin = $this->getString(); } diff --git a/src/pocketmine/network/protocol/PlayerArmorEquipmentPacket.php b/src/pocketmine/network/protocol/MobArmorEquipmentPacket.php similarity index 70% rename from src/pocketmine/network/protocol/PlayerArmorEquipmentPacket.php rename to src/pocketmine/network/protocol/MobArmorEquipmentPacket.php index 7df9f98b2..0fb13e577 100644 --- a/src/pocketmine/network/protocol/PlayerArmorEquipmentPacket.php +++ b/src/pocketmine/network/protocol/MobArmorEquipmentPacket.php @@ -24,27 +24,27 @@ namespace pocketmine\network\protocol; #include -class PlayerArmorEquipmentPacket extends DataPacket{ - const NETWORK_ID = Info::PLAYER_ARMOR_EQUIPMENT_PACKET; +class MobArmorEquipmentPacket extends DataPacket{ + const NETWORK_ID = Info::MOB_ARMOR_EQUIPMENT_PACKET; public $eid; public $slots = []; public function decode(){ $this->eid = $this->getLong(); - $this->slots[0] = $this->getByte(); - $this->slots[1] = $this->getByte(); - $this->slots[2] = $this->getByte(); - $this->slots[3] = $this->getByte(); + $this->slots[0] = $this->getSlot(); + $this->slots[1] = $this->getSlot(); + $this->slots[2] = $this->getSlot(); + $this->slots[3] = $this->getSlot(); } public function encode(){ $this->reset(); $this->putLong($this->eid); - $this->putByte($this->slots[0]); - $this->putByte($this->slots[1]); - $this->putByte($this->slots[2]); - $this->putByte($this->slots[3]); + $this->putSlot($this->slots[0]); + $this->putSlot($this->slots[1]); + $this->putSlot($this->slots[2]); + $this->putSlot($this->slots[3]); } } diff --git a/src/pocketmine/network/protocol/PlayerEquipmentPacket.php b/src/pocketmine/network/protocol/MobEquipmentPacket.php similarity index 82% rename from src/pocketmine/network/protocol/PlayerEquipmentPacket.php rename to src/pocketmine/network/protocol/MobEquipmentPacket.php index 32ab69160..fa897b06b 100644 --- a/src/pocketmine/network/protocol/PlayerEquipmentPacket.php +++ b/src/pocketmine/network/protocol/MobEquipmentPacket.php @@ -24,19 +24,17 @@ namespace pocketmine\network\protocol; #include -class PlayerEquipmentPacket extends DataPacket{ - const NETWORK_ID = Info::PLAYER_EQUIPMENT_PACKET; +class MobEquipmentPacket extends DataPacket{ + const NETWORK_ID = Info::MOB_EQUIPMENT_PACKET; public $eid; public $item; - public $meta; public $slot; public $selectedSlot; public function decode(){ $this->eid = $this->getLong(); - $this->item = $this->getShort(); - $this->meta = $this->getShort(); + $this->item = $this->getSlot(); $this->slot = $this->getByte(); $this->selectedSlot = $this->getByte(); } @@ -44,8 +42,7 @@ class PlayerEquipmentPacket extends DataPacket{ public function encode(){ $this->reset(); $this->putLong($this->eid); - $this->putShort($this->item); - $this->putShort($this->meta); + $this->putSlot($this->item); $this->putByte($this->slot); $this->putByte($this->selectedSlot); } diff --git a/src/pocketmine/network/protocol/PlayerListPacket.php b/src/pocketmine/network/protocol/PlayerListPacket.php new file mode 100644 index 000000000..bcb45acd6 --- /dev/null +++ b/src/pocketmine/network/protocol/PlayerListPacket.php @@ -0,0 +1,64 @@ + + + +class PlayerListPacket extends DataPacket{ + const NETWORK_ID = Info::PLAYER_LIST_PACKET; + + const TYPE_ADD = 0; + const TYPE_REMOVE = 1; + + //REMOVE: UUID, ADD: UUID, entity id, name, isSlim, skin + /** @var array[] */ + public $entries = []; + public $type; + + public function clean(){ + $this->entries = []; + return parent::clean(); + } + + public function decode(){ + + } + + public function encode(){ + $this->reset(); + $this->putByte($this->type); + $this->putInt(count($this->entries)); + foreach($this->entries as $d){ + if($this->type === self::TYPE_ADD){ + $this->putUUID($d[0]); + $this->putLong($d[1]); + $this->putString($d[2]); + $this->putByte($d[3] ? 1 : 0); + $this->putString($d[4]); + }else{ + $this->putUUID($d[0]); + } + } + } + +} diff --git a/src/pocketmine/network/protocol/RemovePlayerPacket.php b/src/pocketmine/network/protocol/RemovePlayerPacket.php index ef903f604..0e8bfa1cd 100644 --- a/src/pocketmine/network/protocol/RemovePlayerPacket.php +++ b/src/pocketmine/network/protocol/RemovePlayerPacket.php @@ -28,7 +28,7 @@ class RemovePlayerPacket extends DataPacket{ const NETWORK_ID = Info::REMOVE_PLAYER_PACKET; public $eid; - public $clientID; + public $clientId; public function decode(){ @@ -37,7 +37,7 @@ class RemovePlayerPacket extends DataPacket{ public function encode(){ $this->reset(); $this->putLong($this->eid); - $this->putLong($this->clientID); + $this->putUUID($this->clientId); } } diff --git a/src/pocketmine/scheduler/SendUsageTask.php b/src/pocketmine/scheduler/SendUsageTask.php index d303872aa..632d0563a 100644 --- a/src/pocketmine/scheduler/SendUsageTask.php +++ b/src/pocketmine/scheduler/SendUsageTask.php @@ -101,13 +101,13 @@ class SendUsageTask extends AsyncTask{ //This anonymizes the user ids so they cannot be reversed to the original foreach($playerList as $k => $v){ - $playerList[$k] = Utils::dataToUUID($v); + $playerList[$k] = md5($v); } $players = []; foreach($server->getOnlinePlayers() as $p){ if($p->isOnline()){ - $players[] = Utils::dataToUUID($p->getUniqueId()); + $players[] = md5($p->getUniqueId()->toBinary()); } } diff --git a/src/pocketmine/utils/UUID.php b/src/pocketmine/utils/UUID.php new file mode 100644 index 000000000..717aca96d --- /dev/null +++ b/src/pocketmine/utils/UUID.php @@ -0,0 +1,105 @@ +parts[0] = (int) $part1; + $this->parts[1] = (int) $part2; + $this->parts[2] = (int) $part3; + $this->parts[3] = (int) $part4; + + $this->version = $version === null ? ($this->parts[1] & 0xf000) >> 12 : (int) $version; + } + + public function getVersion(){ + return $this->version; + } + + public function equals(UUID $uuid){ + return $uuid->parts[0] === $this->parts[0] and $uuid->parts[1] === $this->parts[1] and $uuid->parts[2] === $this->parts[2] and $uuid->parts[3] === $this->parts[3]; + } + + /** + * Creates an UUID from an hexadecimal representation + * + * @param string $uuid + * @param int $version + * @return UUID + */ + public static function fromString($uuid, $version = null){ + return self::fromBinary(hex2bin(str_replace("-", "", trim($uuid))), $version); + } + + /** + * Creates an UUID from a binary representation + * + * @param string $uuid + * @param int $version + * @return UUID + */ + public static function fromBinary($uuid, $version = null){ + if(strlen($uuid) !== 16){ + throw new \InvalidArgumentException("Must have exactly 16 bytes"); + } + + return new UUID(Binary::readInt(substr($uuid, 0, 4)), Binary::readInt(substr($uuid, 4, 4)), Binary::readInt(substr($uuid, 8, 4)), Binary::readInt(substr($uuid, 12, 4)), $version); + } + + /** + * Creates an UUIDv3 from binary data or list of binary data + * + * @param string ...$data + * @return UUID + */ + public static function fromData(...$data){ + $hash = hash("md5", implode($data), true); + + return self::fromBinary($hash, 3); + } + + public static function fromRandom(){ + return self::fromData(Binary::writeInt(time()), Binary::writeShort(getmypid()), Binary::writeShort(getmyuid()), Binary::writeInt(mt_rand(-0x7fffffff, 0x7fffffff)), Binary::writeInt(mt_rand(-0x7fffffff, 0x7fffffff))); + } + + public function toBinary(){ + return Binary::writeInt($this->parts[0]) . Binary::writeInt($this->parts[1]) . Binary::writeInt($this->parts[2]) . Binary::writeInt($this->parts[3]); + } + + public function toString(){ + $hex = bin2hex(self::toBinary()); + + //xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx 8-4-4-12 + if($this->version !== null){ + return substr($hex, 0, 8) . "-" . substr($hex, 8, 4) . "-" . hexdec($this->version) . substr($hex, 13, 3) . "-8" . substr($hex, 17, 3) . "-" . substr($hex, 20, 12); + } + return substr($hex, 0, 8) . "-" . substr($hex, 8, 4) . "-" . substr($hex, 12, 4) . "-" . substr($hex, 16, 4) . "-" . substr($hex, 20, 12); + } + + public function __toString(){ + return $this->toString(); + } +} \ No newline at end of file diff --git a/src/pocketmine/utils/Utils.php b/src/pocketmine/utils/Utils.php index 8caa74358..b8f99cdff 100644 --- a/src/pocketmine/utils/Utils.php +++ b/src/pocketmine/utils/Utils.php @@ -49,14 +49,23 @@ class Utils{ } } + /** + * @deprecated + */ public static function randomUUID(){ return Utils::toUUID(Binary::writeInt(time()) . Binary::writeShort(getmypid()) . Binary::writeShort(getmyuid()) . Binary::writeInt(mt_rand(-0x7fffffff, 0x7fffffff)) . Binary::writeInt(mt_rand(-0x7fffffff, 0x7fffffff)), 2); } + /** + * @deprecated + */ public static function dataToUUID(...$params){ return Utils::toUUID(hash("md5", implode($params), true), 3); } + /** + * @deprecated + */ public static function toUUID($data, $version = 2, $fixed = "8"){ if(strlen($data) !== 16){ throw new \InvalidArgumentException("Data must be 16 bytes"); @@ -76,7 +85,7 @@ class Utils{ * * @param string $extra optional, additional data to identify the machine * - * @return string + * @return UUID */ public static function getMachineUniqueId($extra = ""){ if(self::$serverUniqueId !== null and $extra === ""){ @@ -127,7 +136,7 @@ class Utils{ $data .= $ext . ":" . phpversion($ext); } - $uuid = Utils::dataToUUID($machine, $data); + $uuid = UUID::fromData($machine, $data); if($extra === ""){ self::$serverUniqueId = $uuid;