Shoghi Cervantes 79cc6ca559 Multiple fixes
2014-06-25 02:03:58 +02:00

2225 lines
62 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
namespace pocketmine;
use pocketmine\block\Block;
use pocketmine\command\CommandSender;
use pocketmine\entity\DroppedItem;
use pocketmine\entity\Human;
use pocketmine\event\inventory\InventoryCloseEvent;
use pocketmine\event\inventory\InventoryPickupItemEvent;
use pocketmine\event\player\PlayerAchievementAwardedEvent;
use pocketmine\event\player\PlayerAnimationEvent;
use pocketmine\event\player\PlayerChatEvent;
use pocketmine\event\player\PlayerCommandPreprocessEvent;
use pocketmine\event\player\PlayerDropItemEvent;
use pocketmine\event\player\PlayerGameModeChangeEvent;
use pocketmine\event\player\PlayerItemConsumeEvent;
use pocketmine\event\player\PlayerJoinEvent;
use pocketmine\event\player\PlayerKickEvent;
use pocketmine\event\player\PlayerLoginEvent;
use pocketmine\event\player\PlayerPreLoginEvent;
use pocketmine\event\player\PlayerQuitEvent;
use pocketmine\event\player\PlayerRespawnEvent;
use pocketmine\event\server\DataPacketReceiveEvent;
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\inventory\BaseTransaction;
use pocketmine\inventory\BigShapelessRecipe;
use pocketmine\inventory\CraftingTransactionGroup;
use pocketmine\inventory\FurnaceInventory;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\InventoryHolder;
use pocketmine\inventory\SimpleTransactionGroup;
use pocketmine\inventory\StonecutterShapelessRecipe;
use pocketmine\item\Item;
use pocketmine\level\Level;
use pocketmine\level\Position;
use pocketmine\math\Vector3;
use pocketmine\metadata\MetadataValue;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\Byte;
use pocketmine\nbt\tag\Compound;
use pocketmine\nbt\tag\Int;
use pocketmine\nbt\tag\String;
use pocketmine\network\protocol\AdventureSettingsPacket;
use pocketmine\network\protocol\AnimatePacket;
use pocketmine\network\protocol\DataPacket;
use pocketmine\network\protocol\EntityEventPacket;
use pocketmine\network\protocol\FullChunkDataPacket;
use pocketmine\network\protocol\Info as ProtocolInfo;
use pocketmine\network\protocol\LoginStatusPacket;
use pocketmine\network\protocol\MessagePacket;
use pocketmine\network\protocol\MovePlayerPacket;
use pocketmine\network\protocol\ReadyPacket;
use pocketmine\network\protocol\SetSpawnPositionPacket;
use pocketmine\network\protocol\SetTimePacket;
use pocketmine\network\protocol\StartGamePacket;
use pocketmine\network\protocol\TakeItemEntityPacket;
use pocketmine\network\protocol\UnloadChunkPacket;
use pocketmine\network\protocol\UpdateBlockPacket;
use pocketmine\network\SourceInterface;
use pocketmine\permission\PermissibleBase;
use pocketmine\permission\PermissionAttachment;
use pocketmine\plugin\Plugin;
use pocketmine\scheduler\CallbackTask;
use pocketmine\tile\Sign;
use pocketmine\tile\Spawnable;
use pocketmine\tile\Tile;
use pocketmine\utils\ReversePriorityQueue;
use pocketmine\utils\TextFormat;
/**
* Main class that handles networking, recovery, and packet sending to the server part
*/
class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
const SURVIVAL = 0;
const CREATIVE = 1;
const ADVENTURE = 2;
const SPECTATOR = 3;
const VIEW = Player::SPECTATOR;
const MAX_QUEUE = 2048;
const SURVIVAL_SLOTS = 36;
const CREATIVE_SLOTS = 112;
/** @var SourceInterface */
protected $interface;
public $spawned = false;
public $loggedIn = false;
public $gamemode;
public $lastBreak;
protected $windowCnt = 2;
/** @var \SplObjectStorage<Inventory> */
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);
*/