*/ protected $windows; /** @var Inventory[] */ protected $windowIndex = []; protected $sendIndex = 0; public $blocked = true; public $achievements = []; public $lastCorrect; /** @var SimpleTransactionGroup */ protected $currentTransaction = null; public $craftingType = 0; //0 = 2x2 crafting, 1 = 3x3 crafting, 2 = stonecutter protected $isCrafting = false; public $loginData = []; protected $lastMovement = 0; protected $forceMovement = false; protected $connected = true; protected $clientID; protected $ip; protected $removeFormat = true; protected $port; protected $username; protected $iusername; protected $displayName; protected $startAction = false; protected $sleeping = false; public $usedChunks = []; protected $loadQueue = []; protected $chunkACK = []; /** @var \pocketmine\scheduler\TaskHandler */ protected $chunkLoadTask; /** @var Player[] */ protected $hiddenPlayers = []; private $viewDistance; private $spawnPosition; private $inAction = false; private $needACK = []; /** * @var \pocketmine\scheduler\TaskHandler[] */ protected $tasks = []; /** @var PermissibleBase */ private $perm = null; public function isBanned(){ return $this->server->getNameBans()->isBanned(strtolower($this->getName())); } public function setBanned($value){ if($value === true){ $this->server->getNameBans()->addBan($this->getName(), null, null, null); }else{ $this->server->getNameBans()->remove($this->getName()); } } public function isWhitelisted(){ return $this->server->isWhitelisted(strtolower($this->getName())); } public function setWhitelisted($value){ if($value === true){ $this->server->addWhitelist(strtolower($this->getName())); }else{ $this->server->removeWhitelist(strtolower($this->getName())); } } public function getPlayer(){ return $this; } public function getFirstPlayed(){ return $this->namedtag instanceof Compound ? $this->namedtag["firstPlayed"] : null; } public function getLastPlayed(){ return $this->namedtag instanceof Compound ? $this->namedtag["lastPlayed"] : null; } public function hasPlayedBefore(){ return $this->namedtag instanceof Compound; } protected function initEntity(){ parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ if($this->spawned === true and $player->getLevel() === $this->getLevel() and $player->canSee($this)){ parent::spawnTo($player); } } /** * @param Player $player */ public function despawnFrom(Player $player){ if($this->spawned === true){ parent::despawnFrom($player); } } /** * @return Server */ public function getServer(){ return $this->server; } /** * @return bool */ public function getRemoveFormat(){ return $this->removeFormat; } /** * @param bool $remove */ public function setRemoveFormat($remove = true){ $this->removeFormat = (bool) $remove; } /** * @param Player $player * * @return bool */ public function canSee(Player $player){ return !isset($this->hiddenPlayers[$player->getName()]); } /** * @param Player $player */ public function hidePlayer(Player $player){ if($player === $this){ return; } $this->hiddenPlayers[$player->getName()] = $player; $player->despawnFrom($this); } /** * @param Player $player */ public function showPlayer(Player $player){ if($player === $this){ return; } unset($this->hiddenPlayers[$player->getName()]); $player->spawnTo($this); } /** * @return bool */ public function isOnline(){ return $this->connected === true and $this->loggedIn === true; } /** * @return bool */ public function isOp(){ return $this->server->isOp($this->getName()); } /** * @param bool $value */ public function setOp($value){ if($value === $this->isOp()){ return; } if($value === true){ $this->server->addOp($this->getName()); }else{ $this->server->removeOp($this->getName()); } $this->recalculatePermissions(); } /** * @param permission\Permission|string $name * * @return bool */ public function isPermissionSet($name){ return $this->perm->isPermissionSet($name); } /** * @param permission\Permission|string $name * * @return bool */ public function hasPermission($name){ return $this->perm->hasPermission($name); } /** * @param Plugin $plugin * @param string $name * @param bool $value * * @return permission\PermissionAttachment */ public function addAttachment(Plugin $plugin, $name = null, $value = null){ return $this->perm->addAttachment($plugin, $name, $value); } /** * @param PermissionAttachment $attachment */ public function removeAttachment(PermissionAttachment $attachment){ $this->perm->removeAttachment($attachment); } public function recalculatePermissions(){ $this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this); $this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this); $this->perm->recalculatePermissions(); 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); } } /** * @return permission\PermissionAttachmentInfo[] */ public function getEffectivePermissions(){ return $this->perm->getEffectivePermissions(); } /** * @param SourceInterface $interface * @param integer $clientID * @param string $ip * @param integer $port */ public function __construct(SourceInterface $interface, $clientID, $ip, $port){ $this->interface = $interface; $this->windows = new \SplObjectStorage(); $this->perm = new PermissibleBase($this); $this->namedtag = new Compound(); $this->server = Server::getInstance(); $this->lastBreak = microtime(true); $this->clientID = $clientID; $this->ip = $ip; $this->port = $port; $this->spawnPosition = $this->server->getDefaultLevel()->getSafeSpawn(); $this->timeout = microtime(true) + 20; $this->gamemode = $this->server->getGamemode(); $this->setLevel($this->server->getDefaultLevel(), true); $this->viewDistance = $this->server->getViewDistance(); $this->server->getLogger()->debug("New Session started with " . $ip . ":" . $port . ", Client ID " . $this->clientID); } /** * @param string $achievementId */ public function removeAchievement($achievementId){ if($this->hasAchievement($achievementId)){ $this->achievements[$achievementId] = false; } } /** * @param string $achievementId * * @return bool */ public function hasAchievement($achievementId){ if(!isset(Achievement::$list[$achievementId]) or !isset($this->achievements)){ $this->achievements = []; return false; } if(!isset($this->achievements[$achievementId]) or $this->achievements[$achievementId] == false){ return false; } return true; } /** * @return bool */ public function isConnected(){ return $this->connected === true; } /** * Gets the "friendly" name to display of this player to use in the chat. * * @return string */ public function getDisplayName(){ return $this->displayName; } /** * @param string $name */ public function setDisplayName($name){ $this->displayName = $name; } /** * @return string */ public function getNameTag(){ return $this->nameTag; } /** * @param string $name */ public function setNameTag($name){ $this->nameTag = $name; $this->despawnFromAll(); if($this->spawned === true){ $this->spawnToAll(); } } /** * Gets the player IP address * * @return string */ public function getAddress(){ return $this->ip; } /** * @return int */ public function getPort(){ return $this->port; } /** * @return bool */ public function isSleeping(){ return $this->sleeping instanceof Vector3; } public function unloadChunk($x, $z){ $index = Level::chunkHash($x, $z); if(isset($this->usedChunks[$index])){ foreach($this->getLevel()->getChunkEntities($x, $z) as $entity){ if($entity !== $this){ $entity->despawnFrom($this); } } $pk = new UnloadChunkPacket(); $pk->chunkX = $x; $pk->chunkZ = $z; $this->dataPacket($pk); unset($this->usedChunks[$index]); } unset($this->loadQueue[$index]); $this->orderChunks(); } /** * @return Position */ public function getSpawn(){ return $this->spawnPosition; } /** * @param int $identifier * * @return bool */ public function checkACK($identifier){ return !isset($this->needACK[$identifier]); } public function handleACK($identifier){ unset($this->needACK[$identifier]); if(isset($this->chunkACK[$identifier])){ $index = $this->chunkACK[$identifier]; unset($this->chunkACK[$identifier]); if(isset($this->usedChunks[$index])){ $this->usedChunks[$index][0] = true; $X = null; $Z = null; Level::getXZ($index, $X, $Z); foreach($this->getLevel()->getChunkEntities($X, $Z) as $entity){ if($entity !== $this){ $entity->spawnTo($this); } } } } } /** * Sends, if available, the next ordered chunk to the client * * WARNING: Do not use this, it's only for internal use. * Changes to this function won't be recorded on the version. * */ public function sendNextChunk(){ if($this->connected === false or !isset($this->chunkLoadTask)){ return; } if(count($this->loadQueue) === 0){ $this->chunkLoadTask->setNextRun($this->chunkLoadTask->getNextRun() + 30); }else{ $count = 0; $limit = (int) $this->server->getProperty("chunk-sending.per-tick", 1); foreach($this->loadQueue as $index => $distance){ if($count >= $limit){ break; } ++$count; $X = null; $Z = null; Level::getXZ($index, $X, $Z); if(!$this->getLevel()->isChunkPopulated($X, $Z)){ $this->chunkLoadTask->setNextRun($this->chunkLoadTask->getNextRun() + 30); return; } unset($this->loadQueue[$index]); $this->usedChunks[$index] = [false, 0]; $this->getLevel()->useChunk($X, $Z, $this); $pk = new FullChunkDataPacket; $pk->chunkX = $X; $pk->chunkZ = $Z; $pk->data = $this->getLevel()->getNetworkChunk($X, $Z, 0xff); $cnt = $this->dataPacket($pk, true); if($cnt === false or $cnt === true){ return; } $this->chunkACK[$cnt] = $index; } } if(count($this->usedChunks) >= 56 and $this->spawned === false){ $spawned = 0; foreach($this->usedChunks as $d){ if($d[0] === true){ $spawned++; } } if($spawned < 56){ return; } //TODO //$this->heal($this->data->get("health"), "spawn", true); $this->spawned = true; $this->sendSettings(); $this->inventory->sendContents($this); $this->inventory->sendArmorContents($this); $this->blocked = false; $pk = new SetTimePacket; $pk->time = $this->getLevel()->getTime(); $pk->started = $this->getLevel()->stopTime == false; $this->dataPacket($pk); $pos = new Position($this->x, $this->y, $this->z, $this->getLevel()); $pos = $this->getLevel()->getSafeSpawn($pos); $this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $pos)); $this->teleport($ev->getRespawnPosition()); //Hack to have the correct amount of slots when changing gamemode $pk = new StartGamePacket; $pk->seed = $this->getLevel()->getSeed(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->spawnX = (int) $this->spawnPosition->x; $pk->spawnY = (int) $this->spawnPosition->y; $pk->spawnZ = (int) $this->spawnPosition->z; $pk->generator = 1; $pk->gamemode = $this->gamemode & 0x01; $pk->eid = 0; $this->dataPacket($pk); $this->spawnToAll(); $this->server->getPluginManager()->callEvent($ev = new PlayerJoinEvent($this, $this->getName() . " joined the game")); if(strlen(trim($ev->getJoinMessage())) > 0){ $this->server->broadcastMessage($ev->getJoinMessage()); } if($this->server->getUpdater()->hasUpdate() and $this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){ $this->server->getUpdater()->showPlayerUpdate($this); } } } /** * * WARNING: Do not use this, it's only for internal use. * Changes to this function won't be recorded on the version. * * @return bool */ public function orderChunks(){ if($this->connected === false){ return false; } $newOrder = []; $lastChunk = $this->usedChunks; $centerX = $this->x >> 4; $centerZ = $this->z >> 4; $startX = $centerX - $this->viewDistance; $startZ = $centerZ - $this->viewDistance; $finalX = $centerX + $this->viewDistance; $finalZ = $centerZ + $this->viewDistance; $generateQueue = new ReversePriorityQueue(); for($X = $startX; $X <= $finalX; ++$X){ for($Z = $startZ; $Z <= $finalZ; ++$Z){ $distance = abs($X - $centerX) + abs($Z - $centerZ); $index = Level::chunkHash($X, $Z); if(!isset($this->usedChunks[$index])){ if($this->getLevel()->isChunkPopulated($X, $Z)){ $newOrder[$index] = $distance; }else{ $generateQueue->insert([$X, $Z], $distance); } } unset($lastChunk[$index]); } } asort($newOrder); $this->loadQueue = $newOrder; $i = 0; while(count($this->loadQueue) < 3 and $generateQueue->count() > 0 and $i < 16){ $d = $generateQueue->extract(); $this->getLevel()->generateChunk($d[0], $d[1]); ++$i; } foreach($lastChunk as $index => $Yndex){ $X = null; $Z = null; Level::getXZ($index, $X, $Z); foreach($this->getLevel()->getChunkEntities($X, $Z) as $entity){ if($entity !== $this){ $entity->despawnFrom($this); } } $pk = new UnloadChunkPacket(); $pk->chunkX = $X; $pk->chunkZ = $Z; $this->dataPacket($pk); unset($this->usedChunks[$index]); } } /** * Sends an ordered DataPacket to the send buffer * * @param DataPacket $packet * @param bool $needACK * * @return int|bool */ public function dataPacket(DataPacket $packet, $needACK = false){ if($this->connected === false){ return false; } $this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet)); if($ev->isCancelled()){ return false; } $identifier = $this->interface->putPacket($this, $packet, $needACK, false); if($needACK and $identifier !== null){ $this->needACK[$identifier] = false; return $identifier; } return true; } /** * @param DataPacket $packet * @param bool $needACK * * @return bool|int */ public function directDataPacket(DataPacket $packet, $needACK = false){ if($this->connected === false){ return false; } $this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet)); if($ev->isCancelled()){ return false; } $identifier = $this->interface->putPacket($this, $packet, $needACK, true); if($needACK and $identifier !== null){ $this->needACK[$identifier] = false; return $identifier; } return true; } /** * @param Vector3 $pos * * @return boolean */ public function sleepOn(Vector3 $pos){ foreach($this->getLevel()->getPlayers() as $p){ if($p->sleeping instanceof Vector3){ if($pos->distance($p->sleeping) <= 0.1){ return false; } } } $this->sleeping = $pos; $this->teleport(new Position($pos->x + 0.5, $pos->y + 1, $pos->z + 0.5, $this->getLevel())); /*if($this->entity instanceof Entity){ $this->updateMetadata(); }*/ $this->setSpawn($pos); $this->server->getScheduler()->scheduleDelayedTask(new CallbackTask(array($this, "checkSleep")), 60); return true; } /** * Sets the spawnpoint of the player (and the compass direction) to a Vector3, or set it on another world with a Position object * * @param Vector3|Position $pos */ public function setSpawn(Vector3 $pos){ if(!($pos instanceof Position)){ $level = $this->getLevel(); }else{ $level = $pos->getLevel(); } $this->spawnPosition = new Position($pos->x, $pos->y, $pos->z, $level); $pk = new SetSpawnPositionPacket; $pk->x = (int) $this->spawnPosition->x; $pk->y = (int) $this->spawnPosition->y; $pk->z = (int) $this->spawnPosition->z; $this->dataPacket($pk); } public function stopSleep(){ $this->sleeping = false; //if($this->entity instanceof Entity){ //$this->entity->updateMetadata(); //} } /** * WARNING: Do not use this, it's only for internal use. * Changes to this function won't be recorded on the version. */ public function checkSleep(){ if($this->sleeping !== false){ //TODO: Move to Level /*if($this->server->api->time->getPhase($this->getLevel()) === "night"){ foreach($this->getLevel()->getPlayers() as $p){ if($p->sleeping === false){ return; } } $this->server->api->time->set("day", $this->getLevel()); foreach($this->getLevel()->getPlayers() as $p){ $p->stopSleep(); } }*/ } return; } /*public function eventHandler($data, $event){ switch($event){ //TODO, obsolete case "tile.update": if($data->getLevel() === $this->getLevel()){ if($data instanceof Furnace){ foreach($this->windows as $id => $w){ if($w === $data){ $pk = new ContainerSetDataPacket; $pk->windowid = $id; $pk->property = 0; //Smelting $pk->value = floor($data->namedtag->CookTime); $this->dataPacket($pk); $pk = new ContainerSetDataPacket; $pk->windowid = $id; $pk->property = 1; //Fire icon $pk->value = $data->namedtag->BurnTicks; $this->dataPacket($pk); } } } } break; case "entity.metadata": if($data->getID() === $this->id){ $eid = 0; }else{ $eid = $data->getID(); } if($data->getLevel() === $this->getLevel()){ $pk = new SetEntityDataPacket; $pk->eid = $eid; $pk->metadata = $data->getDamage(); $this->dataPacket($pk); } break; } }*/ /** * @param string $achievementId * * @return bool */ public function awardAchievement($achievementId){ if(isset(Achievement::$list[$achievementId]) and !$this->hasAchievement($achievementId)){ foreach(Achievement::$list[$achievementId]["requires"] as $requerimentId){ if(!$this->hasAchievement($requerimentId)){ return false; } } $this->server->getPluginManager()->callEvent($ev = new PlayerAchievementAwardedEvent($this, $achievementId)); if(!$ev->isCancelled()){ $this->achievements[$achievementId] = true; Achievement::broadcast($this, $achievementId); return true; }else{ return false; } } return false; } /** * @return int */ public function getGamemode(){ return $this->gamemode; } /** * Sets the gamemode, and if needed, kicks the player * TODO: Check if Mojang adds the ability to change gamemode without kicking players * * @param int $gm * * @return bool */ public function setGamemode($gm){ if($gm < 0 or $gm > 3 or $this->gamemode === $gm){ return false; } $this->server->getPluginManager()->callEvent($ev = new PlayerGameModeChangeEvent($this, (int) $gm)); if($ev->isCancelled()){ return false; } if(($this->gamemode & 0x01) === ($gm & 0x01)){ $this->gamemode = $gm; $this->sendMessage("Your gamemode has been changed to " . Server::getGamemodeString($this->getGamemode()) . ".\n"); }else{ $this->gamemode = $gm; $this->sendMessage("Your gamemode has been changed to " . Server::getGamemodeString($this->getGamemode()) . ".\n"); $this->inventory->clearAll(); $this->inventory->sendContents($this->getViewers()); $this->inventory->sendHeldItem($this->hasSpawned); } $this->namedtag->playerGameType = new Int("playerGameType", $this->gamemode); $pk = new StartGamePacket; $pk->seed = $this->getLevel()->getSeed(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->spawnX = (int) $this->spawnPosition->x; $pk->spawnY = (int) $this->spawnPosition->y; $pk->spawnZ = (int) $this->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); $this->sendSettings(); return true; } /** * Sends all the option flags * * WARNING: Do not use this, it's only for internal use. * Changes to this function won't be recorded on the version. * * @param bool $nametags */ public function sendSettings($nametags = true){ /* bit mask | flag name 0x00000001 world_inmutable 0x00000002 - 0x00000004 - 0x00000008 - (autojump) 0x00000010 - 0x00000020 nametags_visible 0x00000040 ? 0x00000080 ? 0x00000100 ? 0x00000200 ? 0x00000400 ? 0x00000800 ? 0x00001000 ? 0x00002000 ? 0x00004000 ? 0x00008000 ? 0x00010000 ? 0x00020000 ? 0x00040000 ? 0x00080000 ? 0x00100000 ? 0x00200000 ? 0x00400000 ? 0x00800000 ? 0x01000000 ? 0x02000000 ? 0x04000000 ? 0x08000000 ? 0x10000000 ? 0x20000000 ? 0x40000000 ? 0x80000000 ? */ $flags = 0; if(($this->gamemode & 0x02) === 0x02){ $flags |= 0x01; //Do not allow placing/breaking blocks, adventure mode } if($nametags !== false){ $flags |= 0x20; //Show Nametags } $pk = new AdventureSettingsPacket; $pk->flags = $flags; $this->dataPacket($pk); } protected function getCreativeBlock(Item $item){ foreach(Block::$creative as $i => $d){ if($d[0] === $item->getID() and $d[1] === $item->getDamage()){ return $i; } } return -1; } public function onUpdate(){ if($this->spawned === false){ return true; } $hasUpdate = $this->entityBaseTick(); foreach($this->getLevel()->getNearbyEntities($this->boundingBox->expand(1, 1, 1), $this) as $entity){ if($entity instanceof DroppedItem){ if($entity->dead !== true and $entity->getPickupDelay() <= 0){ $item = $entity->getItem(); if($item instanceof Item){ if(($this->gamemode & 0x01) === 0 and !$this->inventory->canAddItem($item)){ continue; } $this->server->getPluginManager()->callEvent($ev = new InventoryPickupItemEvent($this->inventory, $item)); if($ev->isCancelled()){ continue; } switch($item->getID()){ case Item::WOOD: $this->awardAchievement("mineWood"); break; case Item::DIAMOND: $this->awardAchievement("diamond"); break; } $pk = new TakeItemEntityPacket; $pk->eid = 0; $pk->target = $entity->getID(); $this->dataPacket($pk); $pk = new TakeItemEntityPacket; $pk->eid = $this->getID(); $pk->target = $entity->getID(); $this->server->broadcastPacket($entity->getViewers(), $pk); $this->inventory->addItem(clone $item); $entity->kill(); } } } } } /** * Handles a Minecraft packet * TODO: Separate all of this in handlers * * WARNING: Do not use this, it's only for internal use. * Changes to this function won't be recorded on the version. * * @param DataPacket $packet */ public function handleDataPacket(DataPacket $packet){ if($this->connected === false){ return; } $this->server->getPluginManager()->callEvent($ev = new DataPacketReceiveEvent($this, $packet)); if($ev->isCancelled()){ return; } switch($packet->pid()){ case ProtocolInfo::LOGIN_PACKET: if($this->loggedIn === true){ break; } $this->username = TextFormat::clean($packet->username); $this->displayName = $this->username; $this->nameTag = $this->username; $this->iusername = strtolower($this->username); $this->loginData = array("clientId" => $packet->clientId, "loginData" => $packet->loginData); if(count($this->server->getOnlinePlayers()) > $this->server->getMaxPlayers()){ if($this->kick("server full") === true){ return; } } if($packet->protocol1 !== ProtocolInfo::CURRENT_PROTOCOL){ if($packet->protocol1 < ProtocolInfo::CURRENT_PROTOCOL){ $pk = new LoginStatusPacket; $pk->status = 1; $this->dataPacket($pk); }else{ $pk = new LoginStatusPacket; $pk->status = 2; $this->dataPacket($pk); } $this->close("", "Incorrect protocol #" . $packet->protocol1, false); return; } if(preg_match('#^[a-zA-Z0-9_]{3,16}$#', $packet->username) == 0 or $this->username === "" or $this->iusername === "rcon" or $this->iusername === "console" or strlen($packet->username) > 16 or strlen($packet->username) < 3){ $this->close("", "Bad username"); return; } $this->server->getPluginManager()->callEvent($ev = new PlayerPreLoginEvent($this, "Plugin reason")); if($ev->isCancelled()){ $this->close($ev->getKickMessage(), "Plugin reason"); return; } if(!$this->server->isWhitelisted(strtolower($this->getName()))){ $this->close($this->username . " has left the game", "Server is white-listed"); return; }elseif($this->server->getNameBans()->isBanned(strtolower($this->getName())) or $this->server->getIPBans()->isBanned($this->getAddress())){ $this->close($this->username . " has left the game", "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->getName() . " has left the game", "already logged in"); return; }else{ break; } } } $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); } if(($level = $this->server->getLevelByName($nbt["Level"])) === null){ $this->setLevel($this->server->getDefaultLevel(), true); $nbt["Level"] = $this->getLevel()->getName(); $nbt["Pos"][0] = $this->getLevel()->getSpawn()->x; $nbt["Pos"][1] = $this->getLevel()->getSpawn()->y; $nbt["Pos"][2] = $this->getLevel()->getSpawn()->z; }else{ $this->setLevel($level, true); } if(!($nbt instanceof Compound)){ $this->close($this->username . " has left the game", "invalid data"); return; } $this->achievements = []; /** @var Byte $achievement */ foreach($nbt->Achievements as $achievement){ $this->achievements[$achievement->getName()] = $achievement->getValue() > 0 ? true : false; } $nbt["lastPlayed"] = floor(microtime(true) * 1000); $this->server->saveOfflinePlayerData($this->username, $nbt); parent::__construct($this->getLevel()->getChunkAt($nbt["Pos"][0], $nbt["Pos"][2], true), $nbt); $this->loggedIn = true; if(($this->gamemode & 0x01) === 0x01){ $this->inventory->setHeldItemSlot(0); $this->inventory->setItemInHand(Item::get(Item::STONE, 0, 1)); } $this->server->getPluginManager()->callEvent($ev = new PlayerLoginEvent($this, "Plugin reason")); if($ev->isCancelled()){ $this->close($ev->getKickMessage(), "Plugin reason"); return; } $pk = new LoginStatusPacket; $pk->status = 0; $this->directDataPacket($pk); if(($level = $this->server->getLevelByName($this->namedtag["SpawnLevel"])) instanceof Level){ $this->spawnPosition = new Position($this->namedtag["SpawnX"], $this->namedtag["SpawnY"], $this->namedtag["SpawnZ"], $level); } $pk = new StartGamePacket; $pk->seed = $this->getLevel()->getSeed(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->spawnX = (int) $this->spawnPosition->x; $pk->spawnY = (int) $this->spawnPosition->y; $pk->spawnZ = (int) $this->spawnPosition->z; $pk->generator = 1; //0 old, 1 infinite, 2 flat $pk->gamemode = 0; //Hack to have the correct amount of slots on gamemode change $pk->eid = 0; //Always use EntityID as zero for the actual player $this->directDataPacket($pk); $pk = new SetTimePacket(); $pk->time = $this->getLevel()->getTime(); $this->directDataPacket($pk); $this->server->getLogger()->info(TextFormat::AQUA . $this->username . TextFormat::WHITE . "[/" . $this->ip . ":" . $this->port . "] logged in with entity id " . $this->id . " at (" . $this->getLevel()->getName() . ", " . round($this->x, 4) . ", " . round($this->y, 4) . ", " . round($this->z, 4) . ")"); $this->orderChunks(); $this->tasks[] = $this->server->getScheduler()->scheduleDelayedRepeatingTask(new CallbackTask(array($this, "orderChunks")), 10, 40); $this->sendNextChunk(); $this->tasks[] = $this->chunkLoadTask = $this->server->getScheduler()->scheduleRepeatingTask(new CallbackTask(array($this, "sendNextChunk")), 1); $pk = new ReadyPacket(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $this->dataPacket($pk); break; case ProtocolInfo::READY_PACKET: //TODO: check if($this->loggedIn === false){ break; } switch($packet->status){ case 1: //Spawn!! break; case 2: //Chunk loaded? break; } break; case ProtocolInfo::ROTATE_HEAD_PACKET: if($this->spawned === false){ break; } $this->setRotation($packet->yaw, $this->pitch); break; case ProtocolInfo::MOVE_PLAYER_PACKET: if($this->spawned === false){ break; } $newPos = new Vector3($packet->x, $packet->y, $packet->z); if($this->forceMovement instanceof Vector3){ if($this->forceMovement->distance($newPos) <= 0.7){ $this->forceMovement = false; }else{ $this->setPosition($this->forceMovement); } } /*$speed = $this->entity->getSpeedMeasure(); if($this->blocked === true or ($this->server->api->getProperty("allow-flight") !== true and (($speed > 9 and ($this->gamemode & 0x01) === 0x00) or $speed > 20 or $this->entity->distance($newPos) > 7)) or $this->server->api->handle("player.move", $this->entity) === false){ if($this->lastCorrect instanceof Vector3){ $this->teleport($this->lastCorrect, $this->entity->yaw, $this->entity->pitch, false); } if($this->blocked !== true){ $this->server->getLogger()->warning($this->username." moved too quickly!"); } }else{*/ if(!$this->setPositionAndRotation($newPos, $packet->yaw, $packet->pitch)){ $pk = new MovePlayerPacket(); $pk->eid = 0; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->bodyYaw = $this->yaw; $pk->pitch = $this->pitch; $pk->yaw = $this->yaw; $this->directDataPacket($pk); } //} break; case ProtocolInfo::PLAYER_EQUIPMENT_PACKET: if($this->spawned === false){ break; } if($packet->slot === 0x28 or $packet->slot === 0 or $packet->slot === 255){ //0 for 0.8.0 compatibility $packet->slot = -1; //Air }else{ $packet->slot -= 9; //Get real block slot } if(($this->gamemode & 0x01) === 1){ //Creative mode match $item = Item::get($packet->item, $packet->meta, 1); $packet->slot = $this->getCreativeBlock($item); }else{ $item = $this->inventory->getItem($packet->slot); } if(!isset($item) or $packet->slot === -1 or $item->getID() !== $packet->item or $item->getDamage() !== $packet->meta){ $this->inventory->sendContents($this); break; }elseif(($this->gamemode & 0x01) === Player::CREATIVE){ $item = Item::get( Block::$creative[$packet->slot][0], Block::$creative[$packet->slot][1], 1 ); $this->inventory->setHeldItemSlot(0); $this->inventory->setItemInHand($item); }else{ $this->inventory->setHeldItemSlot($packet->slot); } $this->inventory->sendHeldItem($this->hasSpawned); if($this->inAction === true){ $this->inAction = false; //$this->entity->updateMetadata(); } break; case ProtocolInfo::REQUEST_CHUNK_PACKET: break; case ProtocolInfo::USE_ITEM_PACKET: $blockVector = new Vector3($packet->x, $packet->y, $packet->z); $this->craftingType = 0; if(($this->spawned === false or $this->blocked === true) and $packet->face >= 0 and $packet->face <= 5){ $target = $this->getLevel()->getBlock($blockVector); $block = $target->getSide($packet->face); $pk = new UpdateBlockPacket; $pk->x = $target->x; $pk->y = $target->y; $pk->z = $target->z; $pk->block = $target->getID(); $pk->meta = $target->getDamage(); $this->dataPacket($pk); $pk = new UpdateBlockPacket; $pk->x = $block->x; $pk->y = $block->y; $pk->z = $block->z; $pk->block = $block->getID(); $pk->meta = $block->getDamage(); $this->dataPacket($pk); break; } $packet->eid = $this->id; if($packet->face >= 0 and $packet->face <= 5){ //Use Block, place if($this->inAction === true){ $this->inAction = false; //$this->entity->updateMetadata(); } if($blockVector->distance($this) > 10){ }elseif(($this->gamemode & 0x01) === 1){ $item = $this->inventory->getItemInHand();; if($this->getLevel()->useItemOn($blockVector, $item, $packet->face, $packet->fx, $packet->fy, $packet->fz, $this) === true){ break; } }elseif($this->inventory->getItemInHand()->getID() !== $packet->item or ($this->inventory->getItemInHand()->isTool() === false and $this->inventory->getItemInHand()->getDamage() !== $packet->meta)){ $this->inventory->sendHeldItem($this); }else{ $item = clone $this->inventory->getItemInHand(); //TODO: Implement adventure mode checks if($this->getLevel()->useItemOn($blockVector, $item, $packet->face, $packet->fx, $packet->fy, $packet->fz, $this) === true){ $this->inventory->setItemInHand($item); $this->inventory->sendHeldItem($this->hasSpawned); break; } } $target = $this->getLevel()->getBlock($blockVector); $block = $target->getSide($packet->face); $pk = new UpdateBlockPacket; $pk->x = $target->x; $pk->y = $target->y; $pk->z = $target->z; $pk->block = $target->getID(); $pk->meta = $target->getDamage(); $this->dataPacket($pk); $pk = new UpdateBlockPacket; $pk->x = $block->x; $pk->y = $block->y; $pk->z = $block->z; $pk->block = $block->getID(); $pk->meta = $block->getDamage(); $this->dataPacket($pk); break; }elseif($packet->face === 0xff){ //TODO: add event $this->inAction = true; $this->startAction = microtime(true); //$this->updateMetadata(); } break; case ProtocolInfo::PLAYER_ACTION_PACKET: if($this->spawned === false or $this->blocked === true){ break; } $this->craftingType = 0; $packet->eid = $this->id; switch($packet->action){ /*case 5: //Shot arrow if($this->entity->inAction === true){ if($this->getSlot($this->getCurrentEquipment())->getID() === BOW){ if($this->startAction !== false){ $time = microtime(true) - $this->startAction; $d = array( "x" => $this->entity->x, "y" => $this->entity->y + 1.6, "z" => $this->entity->z, ); $e = $this->server->api->entity->add($this->getLevel(), ENTITY_OBJECT, OBJECT_ARROW, $d); $e->yaw = $this->entity->yaw; $e->pitch = $this->entity->pitch; $rotation = ($this->entity->yaw - 90) % 360; if($rotation < 0){ $rotation = (360 + $rotation); } $rotation = ($rotation + 180); if($rotation >= 360){ $rotation = ($rotation - 360); } $X = 1; $Z = 1; $overturn = false; if(0 <= $rotation and $rotation < 90){ }elseif(90 <= $rotation and $rotation < 180){ $rotation -= 90; $X = (-1); $overturn = true; }elseif(180 <= $rotation and $rotation < 270){ $rotation -= 180; $X = (-1); $Z = (-1); }elseif(270 <= $rotation and $rotation < 360){ $rotation -= 270; $Z = (-1); $overturn = true; } $rad = deg2rad($rotation); $pitch = (-($this->entity->pitch)); $speed = 80; $speedY = (sin(deg2rad($pitch)) * $speed); $speedXZ = (cos(deg2rad($pitch)) * $speed); if($overturn){ $speedX = (sin($rad) * $speedXZ * $X); $speedZ = (cos($rad) * $speedXZ * $Z); } else{ $speedX = (cos($rad) * $speedXZ * $X); $speedZ = (sin($rad) * $speedXZ * $Z); } $e->speedX = $speedX; $e->speedZ = $speedZ; $e->speedY = $speedY; $e->spawnToAll(); } } } $this->startAction = false; $this->entity->inAction = false; $this->entity->updateMetadata(); break; */ case 6: //get out of the bed $this->stopSleep(); break; } break; case ProtocolInfo::REMOVE_BLOCK_PACKET: if($this->spawned === false or $this->blocked === true){ break; } $this->craftingType = 0; $vector = new Vector3($packet->x, $packet->y, $packet->z); if(($this->gamemode & 0x01) === 1){ $item = $this->inventory->getItemInHand(); }else{ $item = clone $this->inventory->getItemInHand(); } if($this->getLevel()->useBreakOn($vector, $item, $this) === true){ if(($this->gamemode & 0x01) === 0){ $this->inventory->setItemInHand($item); $this->inventory->sendHeldItem($this->hasSpawned); } break; } $target = $this->getLevel()->getBlock($vector); $tile = $this->getLevel()->getTile($vector); $pk = new UpdateBlockPacket; $pk->x = $target->x; $pk->y = $target->y; $pk->z = $target->z; $pk->block = $target->getID(); $pk->meta = $target->getDamage(); $this->dataPacket($pk); //TODO: priority if($tile instanceof Spawnable){ $tile->spawnTo($this); } break; case ProtocolInfo::PLAYER_ARMOR_EQUIPMENT_PACKET: if($this->spawned === false or $this->blocked === true){ break; } for($i = 0; $i < 4; ++$i){ $s = $packet->slots[$i]; if($s === 0 or $s === 255){ $s = Item::get(Item::AIR, 0, 0); }else{ $s = Item::get($s + 256, 0, 1); } $slot = $this->inventory->getArmorItem($i); if($slot->getID() !== Item::AIR and $s->getID() === Item::AIR){ $this->inventory->setArmorItem($i, Item::get(Item::AIR, 0, 0)); }elseif($s->getID() !== Item::AIR and $slot->getID() === Item::AIR and ($sl = $this->inventory->first($s)) !== -1){ if($this->inventory->setArmorItem($i, $this->inventory->getItem($sl)) === false){ $this->inventory->sendContents($this); }else{ $this->inventory->setItem($sl, Item::get(Item::AIR, 0, 0)); } }elseif($s->getID() !== Item::AIR and $slot->getID() !== Item::AIR and ($slot->getID() !== $s->getID() or $slot->getDamage() !== $s->getDamage()) and ($sl = $this->inventory->first($s)) !== -1){ if($this->inventory->setArmorItem($i, $this->inventory->getItem($sl)) === false){ $this->inventory->sendContents($this); }else{ $this->inventory->setItem($sl, $slot); } } } if($this->inAction === true){ $this->inAction = false; //$this->entity->updateMetadata(); } break; /*case ProtocolInfo::INTERACT_PACKET: if($this->spawned === false){ break; } $this->craftingType = 0; $packet->eid = $this->id; $data = []; $data["target"] = $packet->target; $data["eid"] = $packet->eid; $data["action"] = $packet->action; $target = Entity::get($packet->target); if($target instanceof Entity and $this->gamemode !== VIEW and $this->blocked === false and ($target instanceof Entity) and $this->entity->distance($target) <= 8){ $data["targetentity"] = $target; $data["entity"] = $this->entity; if($target instanceof Player and ($this->server->api->getProperty("pvp") == false or $this->server->difficulty <= 0 or ($target->player->gamemode & 0x01) === 0x01)){ break; }elseif($this->server->handle("player.interact", $data) !== false){ $slot = $this->getSlot($this->getCurrentEquipment()); switch($slot->getID()){ case WOODEN_SWORD: case GOLD_SWORD: $damage = 4; break; case STONE_SWORD: $damage = 5; break; case IRON_SWORD: $damage = 6; break; case DIAMOND_SWORD: $damage = 7; break; case WOODEN_AXE: case GOLD_AXE: $damage = 3; break; case STONE_AXE: $damage = 4; break; case IRON_AXE: $damage = 5; break; case DIAMOND_AXE: $damage = 6; break; case WOODEN_PICKAXE: case GOLD_PICKAXE: $damage = 2; break; case STONE_PICKAXE: $damage = 3; break; case IRON_PICKAXE: $damage = 4; break; case DIAMOND_PICKAXE: $damage = 5; break; case WOODEN_SHOVEL: case GOLD_SHOVEL: $damage = 1; break; case STONE_SHOVEL: $damage = 2; break; case IRON_SHOVEL: $damage = 3; break; case DIAMOND_SHOVEL: $damage = 4; break; default: $damage = 1;//$this->server->difficulty; } $target->harm($damage, $this->id); if($slot->isTool() === true and ($this->gamemode & 0x01) === 0){ if($slot->useOn($target) and $slot->getDamage() >= $slot->getMaxDurability()){ $this->setSlot($this->getCurrentEquipment(), new Item(AIR, 0, 0)); } } } } break;*/ case ProtocolInfo::ANIMATE_PACKET: if($this->spawned === false){ break; } $this->server->getPluginManager()->callEvent($ev = new PlayerAnimationEvent($this, $packet->action)); if($ev->isCancelled()){ break; } $pk = new AnimatePacket(); $pk->eid = $this->getID(); $pk->action = $ev->getAnimationType(); $this->server->broadcastPacket($this->getViewers(), $pk); break; case ProtocolInfo::RESPAWN_PACKET: if($this->spawned === false or $this->dead === false){ break; } $this->craftingType = 0; $this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $this->spawnPosition)); $this->teleport($ev->getRespawnPosition()); //$this->entity->fire = 0; //$this->entity->air = 300; //$this->entity->setHealth(20, "respawn", true); //$this->entity->updateMetadata(); $this->sendSettings(); $this->inventory->sendContents($this); $this->inventory->sendArmorContents($this); $this->blocked = false; break; case ProtocolInfo::SET_HEALTH_PACKET: //Not used break; case ProtocolInfo::ENTITY_EVENT_PACKET: if($this->spawned === false or $this->blocked === true){ break; } $this->craftingType = 0; if($this->inAction === true){ $this->inAction = false; //$this->updateMetadata(); } switch($packet->event){ case 9: //Eating $items = array( Item::APPLE => 4, Item::MUSHROOM_STEW => 10, Item::BEETROOT_SOUP => 10, Item::BREAD => 5, Item::RAW_PORKCHOP => 3, Item::COOKED_PORKCHOP => 8, Item::RAW_BEEF => 3, Item::STEAK => 8, Item::COOKED_CHICKEN => 6, Item::RAW_CHICKEN => 2, Item::MELON_SLICE => 2, Item::GOLDEN_APPLE => 10, Item::PUMPKIN_PIE => 8, Item::CARROT => 4, Item::POTATO => 1, Item::BAKED_POTATO => 6, //Item::COOKIE => 2, //Item::COOKED_FISH => 5, //Item::RAW_FISH => 2, ); $slot = $this->inventory->getItemInHand(); if($this->getHealth() < 20 and isset($items[$slot->getID()])){ $this->server->getPluginManager()->callEvent($ev = new PlayerItemConsumeEvent($this, $slot)); if($ev->isCancelled()){ $this->inventory->sendContents($this); break; } $pk = new EntityEventPacket(); $pk->eid = 0; $pk->event = 9; $this->dataPacket($pk); $pk->eid = $this->getID(); $this->server->broadcastPacket($this->getViewers(), $pk); $this->heal($items[$slot->getID()], "eating"); --$slot->count; $this->inventory->setItemInHand($slot); if($slot->getID() === Item::MUSHROOM_STEW or $slot->getID() === Item::BEETROOT_SOUP){ $this->inventory->addItem(Item::get(Item::BOWL, 0, 1)); } } break; } break; case ProtocolInfo::DROP_ITEM_PACKET: if($this->spawned === false or $this->blocked === true){ break; } $packet->eid = $this->id; $item = $this->inventory->getItemInHand(); $ev = new PlayerDropItemEvent($this, $item); if($this->blocked === true){ $ev->setCancelled(true); } $this->server->getPluginManager()->callEvent($ev); if($ev->isCancelled()){ $this->inventory->sendContents($this); break; } $this->inventory->setItemInHand(Item::get(Item::AIR, 0, 0)); $motion = $this->getDirectionVector()->multiply(10); $this->getLevel()->dropItem($this->add(0, 1, 0), $item, $motion); if($this->inAction === true){ $this->inAction = false; //$this->updateMetadata(); } break; case ProtocolInfo::MESSAGE_PACKET: if($this->spawned === false){ break; } $this->craftingType = 0; $packet->message = TextFormat::clean($packet->message); if(trim($packet->message) != "" and strlen($packet->message) <= 255){ $message = $packet->message; $this->server->getPluginManager()->callEvent($ev = new PlayerCommandPreprocessEvent($this, $message)); if($ev->isCancelled()){ break; } if(substr($ev->getMessage(), 0, 1) === "/"){ //Command $this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1)); }else{ $this->server->getPluginManager()->callEvent($ev = new PlayerChatEvent($this, $ev->getMessage())); if(!$ev->isCancelled()){ $this->server->broadcastMessage(sprintf($ev->getFormat(), $ev->getPlayer()->getDisplayName(), $ev->getMessage()), $ev->getRecipients()); } } } break; case ProtocolInfo::CONTAINER_CLOSE_PACKET: if($this->spawned === false or $packet->windowid === 0){ break; } $this->craftingType = 0; $this->currentTransaction = null; if(isset($this->windowIndex[$packet->windowid])){ $this->server->getPluginManager()->callEvent(new InventoryCloseEvent($this->windowIndex[$packet->windowid], $this)); $this->removeWindow($this->windowIndex[$packet->windowid]); }else{ unset($this->windowIndex[$packet->windowid]); } break; case ProtocolInfo::CONTAINER_SET_SLOT_PACKET: if($this->spawned === false or $this->blocked === true){ break; } if($packet->slot < 0){ break; } if($packet->windowid === 0){ //Our inventory if($packet->slot > $this->inventory->getSize()){ break; } $transaction = new BaseTransaction($this->inventory, $packet->slot, $this->inventory->getItem($packet->slot), $packet->item); }elseif(isset($this->windowIndex[$packet->windowid])){ $this->craftingType = 0; $inv = $this->windowIndex[$packet->windowid]; $transaction = new BaseTransaction($inv, $packet->slot, $inv->getItem($packet->slot), $packet->item); }else{ break; } if($transaction->getSourceItem()->equals($transaction->getTargetItem(), true) and $transaction->getTargetItem()->getCount() === $transaction->getSourceItem()->getCount()){ //No changes! //No changes, just a local inventory update sent by the server break; } if($this->currentTransaction === null or $this->currentTransaction->getCreationTime() < (microtime(true) - 0.4)){ if($this->currentTransaction instanceof SimpleTransactionGroup){ foreach($this->currentTransaction->getInventories() as $inventory){ $inventory->sendContents($inventory->getViewers()); } } $this->currentTransaction = new SimpleTransactionGroup($this); } $this->currentTransaction->addTransaction($transaction); if($this->currentTransaction->canExecute()){ if(!$this->currentTransaction->execute()){ $this->currentTransaction = null; break; } foreach($this->currentTransaction->getTransactions() as $ts){ $inv = $ts->getInventory(); if($inv instanceof FurnaceInventory){ if($ts->getSlot() === 2){ switch($inv->getResult()){ case Item::IRON_INGOT: $this->awardAchievement("acquireIron"); break; } } } } $this->currentTransaction = null; }elseif($packet->windowid == 0){ //Try crafting $craftingGroup = new CraftingTransactionGroup($this->currentTransaction); if($craftingGroup->canExecute()){ //We can craft! $recipe = $craftingGroup->getMatchingRecipe(); if($recipe instanceof BigShapelessRecipe and $this->craftingType !== 1){ break; }elseif($recipe instanceof StonecutterShapelessRecipe and $this->craftingType !== 2){ break; } if($craftingGroup->execute()){ switch($craftingGroup->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 remainings $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; } } $this->currentTransaction = null; } } break; case ProtocolInfo::SEND_INVENTORY_PACKET: //TODO, Mojang, enable this ยด^_^` if($this->spawned === false){ break; } break; case ProtocolInfo::ENTITY_DATA_PACKET: if($this->spawned === false or $this->blocked === true){ break; } $this->craftingType = 0; $t = $this->getLevel()->getTile(new Vector3($packet->x, $packet->y, $packet->z)); if($t instanceof Sign){ if(!isset($t->namedtag->Creator) or $t->namedtag["Creator"] !== $this->username){ $t->spawnTo($this); }else{ $nbt = new NBT(NBT::LITTLE_ENDIAN); $nbt->read($packet->namedtag); $nbt = $nbt->getData(); if($nbt["id"] !== Tile::SIGN){ $t->spawnTo($this); }else{ $t->setText($nbt["Text1"], $nbt["Text2"], $nbt["Text3"], $nbt["Text4"]); } } } break; default: $this->server->getLogger()->debug("Unhandled " . $packet->pid() . " data packet for " . $this->username . " (" . $this->clientID . "): " . print_r($packet, true)); break; } } /** * Kicks a player from the server * * @param string $reason * * @return bool */ public function kick($reason = ""){ $this->server->getPluginManager()->callEvent($ev = new PlayerKickEvent($this, $reason, "Kicked player " . $this->username . "." . ($reason !== "" ? " With reason: $reason" : ""))); if(!$ev->isCancelled()){ $this->sendMessage("You have been kicked. " . ($reason !== "" ? " Reason: $reason" : "") . "\n"); $this->close($ev->getQuitMessage(), $reason); return true; } return false; } /** * Sends a direct chat message to a player * * @param string $message */ public function sendMessage($message){ $mes = explode("\n", $message); foreach($mes as $m){ if(preg_match_all('#@([@A-Za-z_]{1,})#', $m, $matches, PREG_OFFSET_CAPTURE) > 0){ $offsetshift = 0; foreach($matches[1] as $selector){ if($selector[0]{0} === "@"){ //Escape! $m = substr_replace($m, $selector[0], $selector[1] + $offsetshift - 1, strlen($selector[0]) + 1); --$offsetshift; continue; } switch(strtolower($selector[0])){ case "player": case "username": $m = substr_replace($m, $this->username, $selector[1] + $offsetshift - 1, strlen($selector[0]) + 1); $offsetshift += strlen($selector[0]) - strlen($this->username) + 1; break; } } } if($m !== ""){ $pk = new MessagePacket; $pk->source = ""; //Do not use this ;) $pk->message = $this->removeFormat === false ? $m : TextFormat::clean($m); $this->dataPacket($pk); } } } /** * @param string $message Message to be broadcasted * @param string $reason Reason showed in console */ public function close($message = "", $reason = "generic reason"){ if($this->connected === true){ if($this->username != ""){ $this->server->getPluginManager()->callEvent($ev = new PlayerQuitEvent($this, $message)); if($this->loggedIn === true){ parent::close(); $this->save(); } } $this->connected = false; $this->interface->close($this, $reason); $this->server->removePlayer($this); $this->getLevel()->freeAllChunks($this); $this->loggedIn = false; foreach($this->tasks as $task){ $task->cancel(); } $this->tasks = []; if(isset($ev) and $this->username != "" and $this->spawned !== false and $ev->getQuitMessage() != ""){ $this->server->broadcastMessage($ev->getQuitMessage()); } $this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this); $this->spawned = false; $this->server->getLogger()->info(TextFormat::AQUA . $this->username . TextFormat::WHITE . "[/" . $this->ip . ":" . $this->port . "] logged out due to " . $reason); $this->windows = new \SplObjectStorage(); $this->windowIndex = []; $this->usedChunks = []; $this->loadQueue = []; unset($this->buffer); } } /** * Handles player data saving */ public function save(){ parent::saveNBT(); $this->namedtag["Level"] = $this->getLevel()->getName(); $this->namedtag["SpawnLevel"] = $this->getLevel()->getName(); $this->namedtag["SpawnX"] = (int) $this->spawnPosition->x; $this->namedtag["SpawnY"] = (int) $this->spawnPosition->y; $this->namedtag["SpawnZ"] = (int) $this->spawnPosition->z; foreach($this->achievements as $achievement => $status){ $this->namedtag->Achievements[$achievement] = new Byte($achievement, $status === true ? 1 : 0); } $this->namedtag["playerGameType"] = $this->gamemode; $this->namedtag["lastPlayed"] = floor(microtime(true) * 1000); //$this->data->set("health", $this->getHealth()); if($this->username != "" and $this->isOnline() and $this->namedtag instanceof Compound){ $this->server->saveOfflinePlayerData($this->username, $this->namedtag); } } /** * Gets the username * * @return string */ public function getName(){ return $this->username; } /** * @param Inventory $inventory * * @return int */ public function getWindowId(Inventory $inventory){ if($this->windows->contains($inventory)){ return $this->windows[$inventory]; } return -1; } /** * Returns the created/existing window id * * @param Inventory $inventory * @param int $forceId * * @return int */ public function addWindow(Inventory $inventory, $forceId = null){ if($this->windows->contains($inventory)){ return $this->windows[$inventory]; } if($forceId === null){ $this->windowCnt = $cnt = max(2, ++$this->windowCnt % 99); }else{ $cnt = (int) $forceId; } $this->windowIndex[$cnt] = $inventory; $this->windows->attach($inventory, $cnt); if($inventory->open($this)){ return $cnt; }else{ $this->removeWindow($inventory); return -1; } } public function removeWindow(Inventory $inventory){ $inventory->close($this); if($this->windows->contains($inventory)){ $id = $this->windows[$inventory]; $this->windows->detach($this->windowIndex[$id]); unset($this->windowIndex[$id]); } } public function setMetadata($metadataKey, MetadataValue $metadataValue){ $this->server->getPlayerMetadata()->setMetadata($this, $metadataKey, $metadataValue); } public function getMetadata($metadataKey){ return $this->server->getPlayerMetadata()->getMetadata($this, $metadataKey); } public function hasMetadata($metadataKey){ return $this->server->getPlayerMetadata()->hasMetadata($this, $metadataKey); } public function removeMetadata($metadataKey, Plugin $plugin){ $this->server->getPlayerMetadata()->removeMetadata($this, $metadataKey, $plugin); } } /* * TODO death reasons if(is_numeric($data["cause"])){ $e = Entity::get($data["cause"]); if($e instanceof Entity){ switch($e->class){ case ENTITY_PLAYER: $message = " was killed by " . $e->name; break; default: $message = " was killed"; break; } } }else{ switch($data["cause"]){ case "cactus": $message = " was pricked to death"; break; case "lava": $message = " tried to swim in lava"; break; case "fire": $message = " went up in flames"; break; case "burning": $message = " burned to death"; break; case "suffocation": $message = " suffocated in a wall"; break; case "water": $message = " drowned"; break; case "void": $message = " fell out of the world"; break; case "fall": $message = " hit the ground too hard"; break; case "explosion": $message = " blew up"; break; default: $message = " died"; break; } } Player::broadcastMessage($data["player"]->getName() . $message); */