/**
* 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;
}
if ($packet::NETWORK_ID === ProtocolInfo::BATCH_PACKET) {
/** @var BatchPacket $packet */
$this->server->getNetwork()->processBatch($packet, $this);
return;
}
$timings = Timings::getReceiveDataPacketTimings($packet);
$timings->startTiming();
$this->server->getPluginManager()->callEvent($ev = new DataPacketReceiveEvent($this, $packet));
if ($ev->isCancelled()) {
$timings->stopTiming();
return;
}
switch ($packet::NETWORK_ID) {
case ProtocolInfo::ITEM_FRAME_DROP_ITEM_PACKET:
$tile = $this->level->getTile($this->temporalVector->setComponents($packet->x, $packet->y, $packet->z));
if ($tile instanceof ItemFrame) {
if ($tile->getItem()->getId() !== Item::AIR) {
$this->getServer()->getPluginManager()->callEvent($ev = new ItemFrameDropItemEvent($this->getLevel()->getBlock($tile), $this, $tile->getItem(), $tile->getItemDropChance()));
if (!$ev->isCancelled()) {
if (mt_rand(0, 10) / 10 <= $tile->getItemDropChance()) {
$this->level->dropItem($tile, $tile->getItem());
}
$tile->setItem(Item::get(Item::AIR));
$tile->setItemRotation(0);
}
}
}
break;
case ProtocolInfo::PLAYER_INPUT_PACKET:
break;
case ProtocolInfo::LOGIN_PACKET:
if ($this->loggedIn) {
break;
}
$this->username = TextFormat::clean($packet->username);
$this->displayName = $this->username;
$this->iusername = strtolower($this->username);
#$this->setDataProperty(self::DATA_NAMETAG, self::DATA_TYPE_STRING, $this->username, false);
if ($this->server->getMaxPlayers() !== -1) {
if (count($this->server->getOnlinePlayers()) >= $this->server->getMaxPlayers() and $this->kick("disconnectionScreen.serverFull", false)) {
break;
}
}
if (!in_array($packet->protocol, ProtocolInfo::ACCEPT_PROTOCOL)) {
if ($packet->protocol < ProtocolInfo::CURRENT_PROTOCOL) {
$message = "disconnectionScreen.outdatedClient";
$pk = new PlayStatusPacket();
$pk->status = PlayStatusPacket::LOGIN_FAILED_CLIENT;
$this->directDataPacket($pk);
} else {
$message = "disconnectionScreen.outdatedServer";
$pk = new PlayStatusPacket();
$pk->status = PlayStatusPacket::LOGIN_FAILED_SERVER;
$this->directDataPacket($pk);
}
$this->close("", $message, false);
break;
}
$this->randomClientId = $packet->clientId;
$this->uuid = UUID::fromString($packet->clientUUID);
$this->rawUUID = $this->uuid->toBinary();
$this->identityPublicKey = $packet->identityPublicKey;
$this->chainData = $packet->chainData;
$valid = true;
$len = strlen($packet->username);
if ($len > 16 or $len < 3) {
$valid = false;
}
for ($i = 0; $i < $len and $valid; ++$i) {
$c = ord($packet->username[$i]);
if ($c >= ord("a") and $c <= ord("z") or $c >= ord("A") and $c <= ord("Z") or $c >= ord("0") and $c <= ord("9") or $c === ord("_")) {
continue;
}
$valid = false;
break;
}
if (!$valid or $this->iusername === "rcon" or $this->iusername === "console") {
$this->close("", "disconnectionScreen.invalidName");
break;
}
if (strlen($packet->skin) !== 64 * 32 * 4 and strlen($packet->skin) !== 64 * 64 * 4) {
$this->close("", "disconnectionScreen.invalidSkin");
break;
}
$this->setSkin($packet->skin, $packet->skinId);
$this->server->getPluginManager()->callEvent($ev = new PlayerPreLoginEvent($this, "Plugin reason"));
if ($ev->isCancelled()) {
$this->close("", $ev->getKickMessage());
break;
}
$this->onPlayerPreLogin();
break;
case ProtocolInfo::MOVE_PLAYER_PACKET:
/*
/** EntityLink ** /
if($this->getlinkType() === Entity::LINK_MASTER){
$this->getlinkedTarget()->followEntity($this);
}
*/
if ($this->teleportPosition !== null) {
break;
}
$newPos = new Vector3($packet->x, $packet->y - $this->getEyeHeight(), $packet->z);
if ($newPos->distanceSquared($this) < 0.01 and $packet->yaw % 360 === $this->yaw and $packet->pitch % 360 === $this->pitch) {
//player hasn't moved, just client spamming packets
break;
}
$revert = false;
if (!$this->isAlive() or $this->spawned !== true) {
$revert = true;
$this->forceMovement = new Vector3($this->x, $this->y, $this->z);
}
if ($this->forceMovement instanceof Vector3 and (($dist = $newPos->distanceSquared($this->forceMovement)) > 0.1 or $revert)) {
$this->sendPosition($this->forceMovement, $packet->yaw, $packet->pitch);
} else {
$packet->yaw %= 360;
$packet->pitch %= 360;
if ($packet->yaw < 0) {
$packet->yaw += 360;
}
$this->setRotation($packet->yaw, $packet->pitch);
$this->newPosition = $newPos;
}
$this->forceMovement = null;
break;
case ProtocolInfo::ADVENTURE_SETTINGS_PACKET:
//TODO: player abilities, check for other changes
if ($packet->isFlying and !$this->allowFlight) {
$this->kick("Flying is not enabled on this server");
break;
} else {
$this->server->getPluginManager()->callEvent($ev = new PlayerToggleFlightEvent($this, $packet->isFlying));
if ($ev->isCancelled()) {
$this->sendSettings();
} else {
$this->flying = $ev->isFlying();
}
break;
}
break;
case ProtocolInfo::MOB_EQUIPMENT_PACKET:
if ($this->spawned === false or !$this->isAlive()) {
break;
}
/**
* Handle hotbar slot remapping
* This is the only time and place when hotbar mapping should ever be changed.
* Changing hotbar slot mapping at will has been deprecated because it causes far too many
* issues with Windows 10 Edition Beta.
*/
$this->inventory->setHeldItemIndex($packet->selectedSlot, false, $packet->slot);
$this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false);
break;
case ProtocolInfo::USE_ITEM_PACKET:
if ($this->spawned === false or !$this->isAlive() or $this->blocked) {
break;
}
$blockVector = new Vector3($packet->x, $packet->y, $packet->z);
$this->craftingType = self::CRAFTING_SMALL;
if ($packet->face >= 0 and $packet->face <= 5) {
//Use Block, place
$this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false);
if (!$this->canInteract($blockVector->add(0.5, 0.5, 0.5), 13) or $this->isSpectator()) {
} elseif ($this->isCreative()) {
$item = $this->inventory->getItemInHand();
if ($this->level->useItemOn($blockVector, $item, $packet->face, $packet->fx, $packet->fy, $packet->fz, $this) === true) {
break;
}
} elseif (!$this->inventory->getItemInHand()->deepEquals($packet->item)) {
$this->inventory->sendHeldItem($this);
} else {
$item = $this->inventory->getItemInHand();
$oldItem = clone $item;
//TODO: Implement adventure mode checks
if ($this->level->useItemOn($blockVector, $item, $packet->face, $packet->fx, $packet->fy, $packet->fz, $this)) {
if (!$item->deepEquals($oldItem) or $item->getCount() !== $oldItem->getCount()) {
$this->inventory->setItemInHand($item);
$this->inventory->sendHeldItem($this->hasSpawned);
}
break;
}
}
if ($this->isOnline()) {
$this->inventory->sendHeldItem($this);
}
if ($blockVector->distanceSquared($this) > 10000) {
break;
}
$target = $this->level->getBlock($blockVector);
$block = $target->getSide($packet->face);
$this->level->sendBlocks([$this], [$target, $block], UpdateBlockPacket::FLAG_ALL_PRIORITY);
break;
} elseif ($packet->face === -1) {
$aimPos = new Vector3(-sin($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI), -sin($this->pitch / 180 * M_PI), cos($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI));
if ($this->isCreative()) {
$item = $this->inventory->getItemInHand();
} elseif (!$this->inventory->getItemInHand()->deepEquals($packet->item)) {
$this->inventory->sendHeldItem($this);
break;
} else {
$item = $this->inventory->getItemInHand();
}
$ev = new PlayerInteractEvent($this, $item, $aimPos, $packet->face, PlayerInteractEvent::RIGHT_CLICK_AIR);
$this->server->getPluginManager()->callEvent($ev);
if ($ev->isCancelled()) {
$this->inventory->sendHeldItem($this);
break;
}
/** New Launchable Class **/
if ($item instanceof Launchable) {
$item->Launch($this);
if ($this->isSurvival() || $this->isAdventure()) {
$item->setCount($item->getCount() - 1);
$this->inventory->setItemInHand($item->getCount() > 0 ? $item : Item::get(Item::AIR));
}
} elseif ($item->getId() === Item::FISHING_ROD) {
if ($this->getHook() !== null) {
if ($this->getHook()->closed) {
$this->setHook();
return;
}
}
if ($this->getHook() instanceof FishingHook) {
$this->server->getPluginManager()->callEvent($fish = new PlayerFishEvent($this, $item, $this->getHook()));
if (!$ev->isCancelled()) {
$damageRod = $this->getHook()->reelLine();
#$this->unlinkHookFromPlayer($this);
}
} else {
$f = 0.6;
$nbt = new CompoundTag("", ["Pos" => new ListTag("Pos", [new DoubleTag("", $this->x), new DoubleTag("", $this->y + $this->getEyeHeight()), new DoubleTag("", $this->z)]), "Motion" => new ListTag("Motion", [new DoubleTag("", -sin($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI)), new DoubleTag("", -sin($this->pitch / 180 * M_PI)), new DoubleTag("", cos($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI))]), "Rotation" => new ListTag("Rotation", [new FloatTag("", $this->yaw), new FloatTag("", $this->pitch)])]);
$fishingHook = new FishingHook($this->chunk, $nbt, $this);
$this->server->getPluginManager()->callEvent($fish = new EntityLaunchFishingRodEvent($this, $item, $fishingHook, $f));
if (!$ev->isCancelled()) {
$this->linkHookToPlayer($fishingHook);
$this->getHook()->setMotion($this->getHook()->getMotion()->multiply($f));
$this->getHook()->shootingEntity = $this;
$this->getHook()->spawnToAll();
}
}
}
$this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, true);
$this->startAction = $this->server->getTick();
}
break;
case ProtocolInfo::PLAYER_ACTION_PACKET:
if ($this->spawned === false or $this->blocked === true or !$this->isAlive() and $packet->action !== PlayerActionPacket::ACTION_RESPAWN and $packet->action !== PlayerActionPacket::ACTION_DIMENSION_CHANGE) {
break;
}
$packet->eid = $this->id;
$pos = new Vector3($packet->x, $packet->y, $packet->z);
switch ($packet->action) {
case PlayerActionPacket::ACTION_START_BREAK:
if ($this->lastBreak !== PHP_INT_MAX or $pos->distanceSquared($this) > 10000) {
break;
}
$target = $this->level->getBlock($pos);
$ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, $packet->face, $target->getId() === 0 ? PlayerInteractEvent::LEFT_CLICK_AIR : PlayerInteractEvent::LEFT_CLICK_BLOCK);
$this->getServer()->getPluginManager()->callEvent($ev);
if ($ev->isCancelled()) {
$this->inventory->sendHeldItem($this);
break;
}
$block = $target->getSide($packet->face);
if ($block->getId() === Block::FIRE) {
$this->level->setBlock($block, new Air());
break;
}
$this->lastBreak = microtime(true);
break;
case PlayerActionPacket::ACTION_ABORT_BREAK:
$this->lastBreak = PHP_INT_MAX;
break;
case PlayerActionPacket::ACTION_RELEASE_ITEM:
if ($this->startAction > -1 and $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION)) {
if ($this->inventory->getItemInHand()->getId() === Item::BOW) {
$bow = $this->inventory->getItemInHand();
if ($this->isSurvival() and !$this->inventory->contains(Item::get(Item::ARROW, null))) {
$this->inventory->sendContents($this);
break;
}
$arrow = false;
foreach ($this->inventory->getContents() as $item) {
if ($item->getId() == Item::ARROW) {
$arrow = $item;
}
}
if ($arrow === false and $this->isCreative()) {
$arrow = Item::get(Item::ARROW, 0, 1);
} elseif ($arrow === false) {
break;
}
$nbt = new CompoundTag("", ["Pos" => new ListTag("Pos", [new DoubleTag("", $this->x), new DoubleTag("", $this->y + $this->getEyeHeight()), new DoubleTag("", $this->z)]), "Motion" => new ListTag("Motion", [new DoubleTag("", -sin($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI)), new DoubleTag("", -sin($this->pitch / 180 * M_PI)), new DoubleTag("", cos($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI))]), "Rotation" => new ListTag("Rotation", [new FloatTag("", $this->yaw), new FloatTag("", $this->pitch)]), "Fire" => new ShortTag("Fire", $this->isOnFire() ? 45 * 60 : 0), "Potion" => new ShortTag("Potion", $arrow->getDamage())]);
$diff = $this->server->getTick() - $this->startAction;
$p = $diff / 20;
$f = min(($p ** 2 + $p * 2) / 3, 1) * 2;
$ev = new EntityShootBowEvent($this, $bow, Entity::createEntity("Arrow", $this->chunk, $nbt, $this, $f == 2 ? true : false), $f);
if ($f < 0.1 or $diff < 5) {
$ev->setCancelled();
}
$this->server->getPluginManager()->callEvent($ev);
if ($ev->isCancelled()) {
$ev->getProjectile()->kill();
$this->inventory->sendContents($this);
} else {
$ev->getProjectile()->setMotion($ev->getProjectile()->getMotion()->multiply($ev->getForce()));
if ($this->isSurvival()) {
$this->inventory->removeItem(Item::get(Item::ARROW, $arrow->getDamage(), 1));
$bow->setDamage($bow->getDamage() + 1);
if ($bow->getDamage() >= 385) {
$this->inventory->setItemInHand(Item::get(Item::AIR, 0, 0));
} else {
$this->inventory->setItemInHand($bow);
}
}
if ($ev->getProjectile() instanceof Projectile) {
$this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($ev->getProjectile()));
if ($projectileEv->isCancelled()) {
$ev->getProjectile()->kill();
} else {
$ev->getProjectile()->spawnToAll();
$this->level->addSound(new LaunchSound($this), $this->getViewers());
}
} else {
$ev->getProjectile()->spawnToAll();
}
}
}
} elseif ($this->inventory->getItemInHand()->getId() === Item::BUCKET and $this->inventory->getItemInHand()->getDamage() === 1) {
//Milk!
$this->server->getPluginManager()->callEvent($ev = new PlayerItemConsumeEvent($this, $this->inventory->getItemInHand()));
if ($ev->isCancelled()) {
$this->inventory->sendContents($this);
break;
}
$pk = new EntityEventPacket();
$pk->eid = $this->getId();
$pk->event = EntityEventPacket::USE_ITEM;
$this->dataPacket($pk);
Server::broadcastPacket($this->getViewers(), $pk);
if ($this->isSurvival()) {
$slot = $this->inventory->getItemInHand();
--$slot->count;
$this->inventory->setItemInHand($slot);
$this->inventory->addItem(Item::get(Item::BUCKET, 0, 1));
}
$this->removeAllEffects();
} else {
$this->inventory->sendContents($this);
}
break;
case PlayerActionPacket::ACTION_STOP_SLEEPING:
$this->stopSleep();
break;
case PlayerActionPacket::ACTION_RESPAWN:
if ($this->spawned === false or $this->isAlive() or !$this->isOnline()) {
break;
}
if ($this->server->isHardcore()) {
$this->setBanned(true);
break;
}
$this->craftingType = self::CRAFTING_SMALL;
$this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $this->getSpawn()));
$this->teleport($ev->getRespawnPosition());
$this->setSprinting(false);
$this->setMovementSpeed(self::DEFAULT_SPEED);
//because setSprinting(false) would decrease
$this->setSneaking(false);
$this->extinguish();
$this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, 400, false);
$this->deadTicks = 0;
$this->noDamageTicks = 60;
$this->setHealth($this->getMaxHealth());
$this->removeAllEffects();
$this->sendData($this);
$this->sendSettings();
$this->inventory->sendContents($this);
$this->inventory->sendArmorContents($this);
$this->blocked = false;
$this->spawnToAll();
$this->scheduleUpdate();
break;
case PlayerActionPacket::ACTION_START_SPRINT:
$ev = new PlayerToggleSprintEvent($this, true);
$this->server->getPluginManager()->callEvent($ev);
if ($ev->isCancelled()) {
$this->sendData($this);
} else {
$this->setSprinting(true);
}
break 2;
case PlayerActionPacket::ACTION_STOP_SPRINT:
$ev = new PlayerToggleSprintEvent($this, false);
$this->server->getPluginManager()->callEvent($ev);
if ($ev->isCancelled()) {
$this->sendData($this);
} else {
$this->setSprinting(false);
}
break 2;
case PlayerActionPacket::ACTION_START_SNEAK:
$ev = new PlayerToggleSneakEvent($this, true);
$this->server->getPluginManager()->callEvent($ev);
if ($ev->isCancelled()) {
$this->sendData($this);
} else {
$this->setSneaking(true);
}
break 2;
case PlayerActionPacket::ACTION_STOP_SNEAK:
$ev = new PlayerToggleSneakEvent($this, false);
$this->server->getPluginManager()->callEvent($ev);
if ($ev->isCancelled()) {
$this->sendData($this);
} else {
$this->setSneaking(false);
}
break 2;
case PlayerActionPacket::ACTION_JUMP:
break 2;
}
$this->startAction = -1;
$this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false);
break;
case ProtocolInfo::REMOVE_BLOCK_PACKET:
if ($this->spawned === false or $this->blocked === true or !$this->isAlive()) {
break;
}
$this->craftingType = self::CRAFTING_SMALL;
$vector = new Vector3($packet->x, $packet->y, $packet->z);
$item = $this->inventory->getItemInHand();
$oldItem = clone $item;
if ($this->canInteract($vector->add(0.5, 0.5, 0.5), $this->isCreative() ? 13 : 6) and $this->level->useBreakOn($vector, $item, $this, true)) {
if ($this->isSurvival()) {
if (!$item->equals($oldItem) or $item->getCount() !== $oldItem->getCount()) {
$this->inventory->setItemInHand($item);
$this->inventory->sendHeldItem($this);
}
$this->exhaust(0.025, PlayerExhaustEvent::CAUSE_MINING);
}
break;
}
$this->inventory->sendContents($this);
$target = $this->level->getBlock($vector);
$tile = $this->level->getTile($vector);
$this->level->sendBlocks([$this], [$target], UpdateBlockPacket::FLAG_ALL_PRIORITY);
$this->inventory->sendHeldItem($this);
if ($tile instanceof Spawnable) {
$tile->spawnTo($this);
}
break;
case ProtocolInfo::MOB_ARMOR_EQUIPMENT_PACKET:
//This packet is ignored. Armour changes are also sent by ContainerSetSlotPackets, and are handled there instead.
break;
case ProtocolInfo::INTERACT_PACKET:
if ($this->spawned === false or !$this->isAlive() or $this->blocked) {
break;
}
$this->craftingType = self::CRAFTING_SMALL;
$target = $this->level->getEntity($packet->target);
$cancelled = false;
/**
* EntityLink
* @todo move into switch
* @todo linkAPI+minecarts branch merge...
*/
if ($target !== null && $target->isVehicle()) {
if ($packet->action === InteractPacket::ACTION_RIGHT_CLICK) {
$this->linkEntity($target);
break;
} elseif ($packet->action === InteractPacket::ACTION_LEAVE_VEHICLE) {
if ($this->isLinked()) {
$this->unlinkEntity($this);
}
break;
}
}
/**
* @todo What is this. @XenialDan/@thebigsmileXD
* @todo move into switch
*/
if ($packet->action === InteractPacket::ACTION_RIGHT_CLICK && $target instanceof Entity) {
$this->getInventory()->getItemInHand()->useOnEntity($target, $this);
// this is beta. Should return false anyways
break;
}
/**
* New 'handler', please don't put anything above anymore
*/
switch ($packet->action) {
case InteractPacket::ACTION_LEFT_CLICK:
//Attack
if ($target instanceof Player and $this->server->getConfigBoolean("pvp", true) === false) {
$cancelled = true;
}
if ($target instanceof Entity and $this->getGamemode() !== Player::VIEW and $this->isAlive() and $target->isAlive()) {
if ($target instanceof DroppedItem or $target instanceof Arrow) {
$this->kick("Attempting to attack an invalid entity");
$this->server->getLogger()->warning($this->getServer()->getLanguage()->translateString("pocketmine.player.invalidEntity", [$this->getName()]));
break;
}
$item = $this->inventory->getItemInHand();
$damageTable = [Item::WOODEN_SWORD => 4, Item::GOLD_SWORD => 4, Item::STONE_SWORD => 5, Item::IRON_SWORD => 6, Item::DIAMOND_SWORD => 7, Item::WOODEN_AXE => 3, Item::GOLD_AXE => 3, Item::STONE_AXE => 3, Item::IRON_AXE => 5, Item::DIAMOND_AXE => 6, Item::WOODEN_PICKAXE => 2, Item::GOLD_PICKAXE => 2, Item::STONE_PICKAXE => 3, Item::IRON_PICKAXE => 4, Item::DIAMOND_PICKAXE => 5, Item::WOODEN_SHOVEL => 1, Item::GOLD_SHOVEL => 1, Item::STONE_SHOVEL => 2, Item::IRON_SHOVEL => 3, Item::DIAMOND_SHOVEL => 4];
$damage = [EntityDamageEvent::MODIFIER_BASE => $damageTable[$item->getId()] ?? 1];
if (!$this->canInteract($target, 8)) {
$cancelled = true;
} elseif ($target instanceof Player) {
if (($target->getGamemode() & 0x1) > 0) {
break;
} elseif ($this->server->getConfigBoolean("pvp") !== true or $this->server->getDifficulty() === 0) {
$cancelled = true;
}
$armorValues = [Item::LEATHER_CAP => 1, Item::LEATHER_TUNIC => 3, Item::LEATHER_PANTS => 2, Item::LEATHER_BOOTS => 1, Item::CHAIN_HELMET => 1, Item::CHAIN_CHESTPLATE => 5, Item::CHAIN_LEGGINGS => 4, Item::CHAIN_BOOTS => 1, Item::GOLD_HELMET => 1, Item::GOLD_CHESTPLATE => 5, Item::GOLD_LEGGINGS => 3, Item::GOLD_BOOTS => 1, Item::IRON_HELMET => 2, Item::IRON_CHESTPLATE => 6, Item::IRON_LEGGINGS => 5, Item::IRON_BOOTS => 2, Item::DIAMOND_HELMET => 3, Item::DIAMOND_CHESTPLATE => 8, Item::DIAMOND_LEGGINGS => 6, Item::DIAMOND_BOOTS => 3];
$points = 0;
foreach ($target->getInventory()->getArmorContents() as $index => $i) {
if (isset($armorValues[$i->getId()])) {
$points += $armorValues[$i->getId()];
}
}
$damage[EntityDamageEvent::MODIFIER_ARMOR] = -floor($damage[EntityDamageEvent::MODIFIER_BASE] * $points * 0.04);
}
$ev = new EntityDamageByEntityEvent($this, $target, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $damage);
if ($cancelled) {
$ev->setCancelled();
}
$target->attack($ev->getFinalDamage(), $ev);
if ($ev->isCancelled()) {
if ($item->isTool() and $this->isSurvival()) {
$this->inventory->sendContents($this);
}
break;
}
if ($this->isSurvival()) {
if ($item->isTool()) {
if ($item->useOn($target) and $item->getDamage() >= $item->getMaxDurability()) {
$this->inventory->setItemInHand(Item::get(Item::AIR, 0, 1));
} else {
$this->inventory->setItemInHand($item);
}
}
$this->exhaust(0.3, PlayerExhaustEvent::CAUSE_ATTACK);
}
}
break;
default:
break;
//TODO: handle other actions
}
break;
case ProtocolInfo::ANIMATE_PACKET:
if ($this->spawned === false or !$this->isAlive()) {
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();
Server::broadcastPacket($this->getViewers(), $pk);
break;
case ProtocolInfo::SET_HEALTH_PACKET:
//Not used
break;
case ProtocolInfo::ENTITY_EVENT_PACKET:
if ($this->spawned === false or $this->blocked === true or !$this->isAlive()) {
break;
}
$this->craftingType = self::CRAFTING_SMALL;
$this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false);
//TODO: check if this should be true
switch ($packet->event) {
case EntityEventPacket::USE_ITEM:
//Eating
$slot = $this->inventory->getItemInHand();
if ($slot->canBeConsumed()) {
$ev = new PlayerItemConsumeEvent($this, $slot);
if (!$slot->canBeConsumedBy($this)) {
$ev->setCancelled();
}
$this->server->getPluginManager()->callEvent($ev);
if (!$ev->isCancelled()) {
$slot->onConsume($this);
} else {
$this->inventory->sendContents($this);
}
}
break;
}
break;
case ProtocolInfo::DROP_ITEM_PACKET:
if ($this->spawned === false or $this->blocked === true or !$this->isAlive()) {
break;
}
if ($packet->item->getId() === Item::AIR) {
/**
* This is so stupid it's unreal.
* Windows 10 Edition Beta drops the contents of the crafting grid when the inventory closes - including air.
*/
break;
}
$this->getTransactionQueue()->addTransaction(new DropItemTransaction($packet->item));
break;
case ProtocolInfo::COMMAND_STEP_PACKET:
if ($this->spawned === false or !$this->isAlive()) {
break;
}
$this->craftingType = 0;
$commandText = $packet->command;
if ($packet->args !== null) {
foreach ($packet->args as $arg) {
//command ordering will be an issue
$commandText .= " " . $arg;
}
}
$this->server->getPluginManager()->callEvent($ev = new PlayerCommandPreprocessEvent($this, "/" . $commandText));
if ($ev->isCancelled()) {
break;
}
Timings::$playerCommandTimer->startTiming();
$this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1));
Timings::$playerCommandTimer->stopTiming();
break;
case ProtocolInfo::TEXT_PACKET:
if ($this->spawned === false or !$this->isAlive()) {
break;
}
$this->craftingType = self::CRAFTING_SMALL;
if ($packet->type === TextPacket::TYPE_CHAT) {
$packet->message = TextFormat::clean($packet->message, $this->removeFormat);
foreach (explode("\n", $packet->message) as $message) {
if (trim($message) != "" and strlen($message) <= 255 and $this->messageCounter-- > 0) {
$ev = new PlayerCommandPreprocessEvent($this, $message);
if (mb_strlen($ev->getMessage(), "UTF-8") > 320) {
$ev->setCancelled();
}
$this->server->getPluginManager()->callEvent($ev);
if ($ev->isCancelled()) {
break;
}
if (substr($ev->getMessage(), 0, 2) === "./") {
//Command (./ = fast hack for old plugins post 0.16)
Timings::$playerCommandTimer->startTiming();
$this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 2));
Timings::$playerCommandTimer->stopTiming();
} else {
$this->server->getPluginManager()->callEvent($ev = new PlayerChatEvent($this, $ev->getMessage()));
if (!$ev->isCancelled()) {
$this->server->broadcastMessage($this->getServer()->getLanguage()->translateString($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 = self::CRAFTING_SMALL;
if (isset($this->windowIndex[$packet->windowid])) {
$this->server->getPluginManager()->callEvent(new InventoryCloseEvent($this->windowIndex[$packet->windowid], $this));
$this->removeWindow($this->windowIndex[$packet->windowid]);
}
/**
* Drop anything still left in the crafting inventory
* This will usually never be needed since Windows 10 clients will send DropItemPackets
* which will cause this to happen anyway, but this is here for when transactions
* fail and items end up stuck in the crafting inventory.
*/
foreach ($this->getFloatingInventory()->getContents() as $item) {
$this->getTransactionQueue()->addTransaction(new DropItemTransaction($item));
}
break;
case ProtocolInfo::CRAFTING_EVENT_PACKET:
if ($this->spawned === false or !$this->isAlive()) {
break;
} elseif (!isset($this->windowIndex[$packet->windowId])) {
$this->inventory->sendContents($this);
$pk = new ContainerClosePacket();
$pk->windowid = $packet->windowId;
$this->dataPacket($pk);
break;
}
$recipe = $this->server->getCraftingManager()->getRecipe($packet->id);
if ($recipe === null or ($recipe instanceof BigShapelessRecipe or $recipe instanceof BigShapedRecipe) and $this->craftingType === 0) {
$this->inventory->sendContents($this);
break;
}
foreach ($packet->input as $i => $item) {
if ($item->getDamage() === -1 or $item->getDamage() === 0xffff) {
$item->setDamage(null);
}
if ($i < 9 and $item->getId() > 0) {
$item->setCount(1);
}
}
$canCraft = true;
if ($recipe instanceof ShapedRecipe) {
for ($x = 0; $x < 3 and $canCraft; ++$x) {
for ($y = 0; $y < 3; ++$y) {
$item = $packet->input[$y * 3 + $x];
if (!$item instanceof Item) {
continue;
}
$ingredient = $recipe->getIngredient($x, $y);
if ($item->getCount() > 0) {
if ($ingredient === null or !$ingredient->deepEquals($item, $ingredient->getDamage() !== null, $ingredient->getCompoundTag() !== null)) {
$canCraft = false;
break;
}
}
}
}
} elseif ($recipe instanceof ShapelessRecipe) {
$needed = $recipe->getIngredientList();
for ($x = 0; $x < 3 and $canCraft; ++$x) {
for ($y = 0; $y < 3; ++$y) {
$item = clone $packet->input[$y * 3 + $x];
foreach ($needed as $k => $n) {
if ($n->deepEquals($item, $n->getDamage() !== null, $n->getCompoundTag() !== null)) {
$remove = min($n->getCount(), $item->getCount());
$n->setCount($n->getCount() - $remove);
$item->setCount($item->getCount() - $remove);
if ($n->getCount() === 0) {
unset($needed[$k]);
}
}
}
if ($item->getCount() > 0) {
$canCraft = false;
break;
}
}
}
if (count($needed) > 0) {
$canCraft = false;
}
} else {
$canCraft = false;
}
/** @var Item[] $ingredients */
$ingredients = $packet->input;
$result = $packet->output[0];
if (!$canCraft or !$recipe->getResult()->deepEquals($result)) {
$this->server->getLogger()->debug("Unmatched recipe " . $recipe->getId() . " from player " . $this->getName() . ": expected " . $recipe->getResult() . ", got " . $result . ", using: " . implode(", ", $ingredients));
$this->inventory->sendContents($this);
break;
}
$used = array_fill(0, $this->inventory->getSize(), 0);
foreach ($ingredients as $ingredient) {
$slot = -1;
foreach ($this->inventory->getContents() as $index => $i) {
if ($ingredient->getId() !== 0 and $ingredient->deepEquals($i, $ingredient->getDamage() !== null) and $i->getCount() - $used[$index] >= 1) {
$slot = $index;
$used[$index]++;
break;
}
}
if ($ingredient->getId() !== 0 and $slot === -1) {
$canCraft = false;
break;
}
}
if (!$canCraft) {
$this->server->getLogger()->debug("Unmatched recipe " . $recipe->getId() . " from player " . $this->getName() . ": client does not have enough items, using: " . implode(", ", $ingredients));
$this->inventory->sendContents($this);
break;
}
$this->server->getPluginManager()->callEvent($ev = new CraftItemEvent($this, $ingredients, $recipe));
if ($ev->isCancelled()) {
$this->inventory->sendContents($this);
break;
}
foreach ($used as $slot => $count) {
if ($count === 0) {
continue;
}
$item = $this->inventory->getItem($slot);
if ($item->getCount() > $count) {
$newItem = clone $item;
$newItem->setCount($item->getCount() - $count);
} else {
$newItem = Item::get(Item::AIR, 0, 0);
}
$this->inventory->setItem($slot, $newItem);
}
$extraItem = $this->inventory->addItem($recipe->getResult());
if (count($extraItem) > 0) {
foreach ($extraItem as $item) {
$this->level->dropItem($this, $item);
}
}
switch ($recipe->getResult()->getId()) {
case Item::WORKBENCH:
$this->awardAchievement("buildWorkBench");
break;
case Item::WOODEN_PICKAXE:
$this->awardAchievement("buildPickaxe");
break;
case Item::FURNACE:
$this->awardAchievement("buildFurnace");
break;
case Item::WOODEN_HOE:
$this->awardAchievement("buildHoe");
break;
case Item::BREAD:
$this->awardAchievement("makeBread");
break;
case Item::CAKE:
//TODO: detect complex recipes like cake that leave remains
$this->awardAchievement("bakeCake");
$this->inventory->addItem(Item::get(Item::BUCKET, 0, 3));
break;
case Item::STONE_PICKAXE:
case Item::GOLD_PICKAXE:
case Item::IRON_PICKAXE:
case Item::DIAMOND_PICKAXE:
$this->awardAchievement("buildBetterPickaxe");
break;
case Item::WOODEN_SWORD:
$this->awardAchievement("buildSword");
break;
case Item::DIAMOND:
$this->awardAchievement("diamond");
break;
}
break;
case ProtocolInfo::CONTAINER_SET_SLOT_PACKET:
if ($this->spawned === false or $this->blocked === true or !$this->isAlive()) {
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, $packet->item);
} elseif ($packet->windowid === ContainerSetContentPacket::SPECIAL_ARMOR) {
//Our armor
if ($packet->slot >= 4) {
break;
}
$transaction = new BaseTransaction($this->inventory, $packet->slot + $this->inventory->getSize(), $packet->item);
} elseif (isset($this->windowIndex[$packet->windowid])) {
//Transaction for non-player-inventory window, such as anvil, chest, etc.
$inv = $this->windowIndex[$packet->windowid];
$achievements = [];
if ($inv instanceof FurnaceInventory and $inv->getItem($packet->slot)->getId() === Item::IRON_INGOT and $packet->slot === FurnaceInventory::RESULT) {
$achievements[] = "acquireIron";
} elseif ($inv instanceof EnchantInventory and $packet->item->hasEnchantments()) {
$inv->onEnchant($this, $inv->getItem($packet->slot), $packet->item);
}
$transaction = new BaseTransaction($inv, $packet->slot, $packet->item, $achievements);
} else {
//Client sent a transaction for a window which the server doesn't think they have open
break;
}
$this->getTransactionQueue()->addTransaction($transaction);
break;
case ProtocolInfo::BLOCK_ENTITY_DATA_PACKET:
if ($this->spawned === false or $this->blocked === true or !$this->isAlive()) {
break;
}
$this->craftingType = self::CRAFTING_SMALL;
$pos = new Vector3($packet->x, $packet->y, $packet->z);
if ($pos->distanceSquared($this) > 10000) {
break;
}
$t = $this->level->getTile($pos);
if ($t instanceof Sign) {
$nbt = new NBT(NBT::LITTLE_ENDIAN);
$nbt->read($packet->namedtag, false, true);
$nbt = $nbt->getData();
if ($nbt["id"] !== Tile::SIGN) {
$t->spawnTo($this);
} else {
$ev = new SignChangeEvent($t->getBlock(), $this, [TextFormat::clean($nbt["Text1"], $this->removeFormat), TextFormat::clean($nbt["Text2"], $this->removeFormat), TextFormat::clean($nbt["Text3"], $this->removeFormat), TextFormat::clean($nbt["Text4"], $this->removeFormat)]);
if (!isset($t->namedtag->Creator) or $t->namedtag["Creator"] !== $this->getRawUniqueId()) {
$ev->setCancelled();
/*}else{
foreach($ev->getLines() as $line){
if(mb_strlen(TextFormat::clean($line), "UTF-8") > 20){ // TODO: Add pixel width calculation like MiNET
$ev->setCancelled();
}
}*/
}
$this->server->getPluginManager()->callEvent($ev);
if (!$ev->isCancelled()) {
$t->setText($ev->getLine(0), $ev->getLine(1), $ev->getLine(2), $ev->getLine(3));
} else {
$t->spawnTo($this);
}
}
}
break;
case ProtocolInfo::REQUEST_CHUNK_RADIUS_PACKET:
if ($this->spawned) {
$this->viewDistance = $packet->radius ** 2;
}
$pk = new ChunkRadiusUpdatedPacket();
$pk->radius = $packet->radius;
$this->dataPacket($pk);
break;
case ProtocolInfo::SET_PLAYER_GAME_TYPE_PACKET:
if ($packet->gamemode !== $this->gamemode) {
if (!$this->hasPermission("pocketmine.command.gamemode")) {
$pk = new SetPlayerGameTypePacket();
$pk->gamemode = $this->gamemode & 0x1;
$this->dataPacket($pk);
$this->sendSettings();
break;
}
$this->setGamemode($packet->gamemode, true);
}
break;
default:
break;
}
$timings->stopTiming();
}