public function onUpdate($currentTick) { if ($this->closed !== false) { return false; } $this->lastUpdate = $currentTick; $this->timings->startTiming(); $hasUpdate = true; //parent::onUpdate($currentTick); if ($this->isAlive()) { $expectedPos = new Vector3($this->x + $this->motionX, $this->y + $this->motionY, $this->z + $this->motionZ); $expBlock0 = $this->getLevel()->getBlock($expectedPos->add(0, -1, 0)->round()); $expBlock1 = $this->getLevel()->getBlock($expectedPos->add(0, 0, 0)->round()); if ($expBlock0->getId() == 0) { $this->motionY -= $this->gravity; //重力计算 $this->motionX = 0; $this->motionZ = 0; } else { $this->motionY = 0; } if ($expBlock1->getId() != 0) { $this->motionY += 0.1; } $this->move($this->motionX, $this->motionY, $this->motionZ); if ($this->isFreeMoving) { $this->motionX = 0; $this->motionZ = 0; $this->isFreeMoving = false; } /*$friction = 1 - $this->drag; $this->motionX *= $friction; $this->motionY *= 1 - $this->drag; $this->motionZ *= $friction;*/ $f = sqrt($this->motionX ** 2 + $this->motionZ ** 2); $this->yaw = -atan2($this->motionX, $this->motionZ) * 180 / M_PI; //视角计算 //$this->pitch = (-atan2($f, $this->motionY) * 180 / M_PI); $this->updateMovement(); } $this->timings->stopTiming(); return $hasUpdate or !$this->onGround or abs($this->motionX) > 1.0E-5 or abs($this->motionY) > 1.0E-5 or abs($this->motionZ) > 1.0E-5; }
public function onUpdate($currentTick) { if ($this->closed == true || $this->dead == true || ($target = $this->target) == null || $target->dead || $this->player instanceof Player && !$this->player->spawned) { if (!$this->closed) { parent::close(); } $this->dead = true; return false; } else { $dis = sqrt(pow($dZ = $target->z - $this->z, 2) + pow($dX = $target->x - $this->x, 2)); if ($this->isHead) { $bb = clone $this->getBoundingBox(); $this->timings->startTiming(); $tickDiff = max(1, $currentTick - $this->lastUpdate); $this->lastUpdate = $currentTick; $hasUpdate = $this->entityBaseTick($tickDiff); if ($this->getLevel()->getFolderName() !== $target->getLevel()->getFolderName()) { $this->teleport($target); } $onGround = count($this->level->getCollisionBlocks($bb->offset(0, -$this->gravity, 0))) > 0; $x = cos($at2 = atan2($dZ, $dX)) * $this->speed; $z = sin($at2) * $this->speed; $y = 0; $bb->offset(0, $this->gravity, 0); $isJump = count($this->level->getCollisionBlocks($bb->grow(0, 0, 0)->offset($x, 1, $z))) <= 0; if (count($this->level->getCollisionBlocks($bb->grow(0, 0, 0)->offset(0, 0, $z))) > 0) { $z = 0; if ($isJump) { $y = $this->gravity; } } if (count($this->level->getCollisionBlocks($bb->grow(0, 0, 0)->offset($x, 0, 0))) > 0) { $x = 0; if ($isJump) { $y = $this->gravity; } } if (!$this->isFallow || $dis < $this->distance) { $x = 0; $z = 0; } if ($this->isFallow && $dis > 20) { $this->updateMove($target); if ($target instanceof Player) { $target->sendMessage("[MobPet] 같이가요 "); } return true; } elseif (!$isJump && $target->y > $this->y - ($target instanceof Player ? 0.5 : 0)) { if ($this->jumpTick <= 0) { $this->jumpTick = 40; } elseif ($this->jumpTick > 36) { $y = $this->gravity; } } if ($this->jumpTick > 0) { $this->jumpTick--; } if (($n = floor($this->y) - $this->y) < $this->gravity && $n > 0) { $y = -$n; } if ($y == 0 && !$onGround) { $y = -$this->gravity; } $block = $this->level->getBlock($this->add($vec = new Vector3($x, $y, $z))); if ($block->hasEntityCollision()) { $block->addVelocityToEntity($this, $vec2 = $vec->add(0, $this->gravity, 0)); $vec->x = ($vec->x + $vec2->x / 2) / 5; $vec->y = $vec->y + $vec2->y / 2; $vec->z = ($vec->z + $vec2->z / 2) / 5; } if (count($this->level->getCollisionBlocks($bb->offset(0, -0.01, 0))) <= 0) { $y -= 0.01; } $this->updateMove($vec->add(new Vector3(($this->boundingBox->minX + $this->boundingBox->maxX - $this->drag) / 2, ($this->boundingBox->minY + $this->boundingBox->maxY) / 2, ($this->boundingBox->minZ + $this->boundingBox->maxZ - $this->drag) / 2))); $this->onGround = $onGround; } else { $x = cos($at2 = atan2($dZ, $dX)) * $this->distance; $z = sin($at2) * $this->distance; $this->updateMove($target->add(-$x, 0, -$z)); } } return true; }
public function updateMove(int $tickDiff) { if (!$this->isMovement()) { return null; } if ($this->isKnockback()) { $this->move($this->motionX * $tickDiff, $this->motionY, $this->motionZ * $tickDiff); $this->motionY -= 0.15 * $tickDiff; $this->updateMovement(); return null; } $before = $this->baseTarget; $this->checkTarget(); if ($this->baseTarget instanceof Creature or $before !== $this->baseTarget) { $x = $this->baseTarget->x - $this->x; $y = $this->baseTarget->y - $this->y; $z = $this->baseTarget->z - $this->z; if ($this->stayTime > 0 || $x ** 2 + $z ** 2 < 0.7) { $this->motionX = 0; $this->motionZ = 0; } else { $diff = abs($x) + abs($z); $this->motionX = $this->getSpeed() * 0.15 * ($x / $diff); $this->motionZ = $this->getSpeed() * 0.15 * ($z / $diff); } $this->yaw = -atan2($this->motionX, $this->motionZ) * 180 / M_PI; $this->pitch = $y == 0 ? 0 : rad2deg(-atan2($y, sqrt($x ** 2 + $z ** 2))); } $target = $this->mainTarget != null ? $this->mainTarget : $this->baseTarget; if ($this->stayTime > 0) { $this->stayTime -= 1; } else { $isJump = false; $dx = $this->motionX * $tickDiff; $dy = $this->motionY * $tickDiff; $dz = $this->motionZ * $tickDiff; $be = new Vector2($this->x + $dx, $this->z + $dz); $this->move($dx, $dy, $dz); $af = new Vector2($this->x, $this->z); if ($be->x != $af->x || $be->y != $af->y) { $x = 0; $z = 0; if ($be->x - $af->x != 0) { $x += $be->x - $af->x > 0 ? 1 : -1; } if ($be->y - $af->y != 0) { $z += $be->y - $af->y > 0 ? 1 : -1; } $vec = new Vector3(Math::floorFloat($be->x), (int) $this->y, Math::floorFloat($be->y)); $block = $this->level->getBlock($vec->add($x, 0, $z)); $block2 = $this->level->getBlock($vec->add($x, 1, $z)); if (!$block->canPassThrough()) { $bb = $block2->getBoundingBox(); if ($this->motionY > -$this->gravity * 4 && ($block2->canPassThrough() || ($bb == null || $bb != null && $bb->maxY - $this->y <= 1))) { $isJump = true; if ($this->motionY >= 0.3) { $this->motionY += $this->gravity; } else { $this->motionY = 0.3; } } elseif ($this->level->getBlock($vec)->getId() == Item::LADDER) { $isJump = true; $this->motionY = 0.15; } } if (!$isJump) { $this->moveTime -= 90 * $tickDiff; } } if ($this->onGround && !$isJump) { $this->motionY = 0; } elseif (!$isJump) { if ($this->motionY > -$this->gravity * 4) { $this->motionY = -$this->gravity * 4; } else { $this->motionY -= $this->gravity; } } } $this->updateMovement(); return $target; }
/** * 高密集型发包计时器 * - 发送数据包 * - 计算自由落体相关数据 * 循环间隔:1 tick */ public function ZombieRandomWalk() { foreach ($this->AIHolder->getServer()->getLevels() as $level) { foreach ($level->getEntities() as $zo) { if ($zo instanceof Zombie) { if (isset($this->AIHolder->pigzombie[$zo->getId()])) { $zom =& $this->AIHolder->pigzombie[$zo->getId()]; if ($zom['canAttack'] != 0) { $zom['canAttack'] -= 1; } $pos = $zo->getLocation(); //echo ($zom['IsChasing']."\n"); //真正的自由落体 by boybook if ($zom['drop'] !== false) { $olddrop = $zom['drop']; $zom['drop'] += 0.5; $drop = $zom['drop']; //echo $drop."\n"; $dropy = $zo->getY() - ($olddrop * 0.05 + 0.0125); $posd0 = new Vector3(floor($zo->getX()), floor($dropy), floor($zo->getZ())); $posd = new Vector3($zo->getX(), $dropy, $zo->getZ()); if ($this->AIHolder->whatBlock($zo->getLevel(), $posd0) == "air") { $zo->setPosition($posd->add(0, -1, 0)); //下降 } else { for ($i = 1; $i <= $drop; $i++) { $posd0->y++; if ($this->AIHolder->whatBlock($zo->getLevel(), $posd0) != "block") { $posd->y = $posd0->y; $zo->setPosition($posd->add(0, -1, 0)); //下降完成 $h = $zom['drop'] * $zom['drop'] / 20; $damage = $h - 3; //echo($h . ": " . $damage . "\n"); if ($damage > 0) { //$zo->attack($damage, EntityDamageEvent::CAUSE_FALL); $zo->setHealth($zo->getHealth() - $damage); } $zom['drop'] = false; break; } } } } else { $drop = 0; } if ($zom['IsChasing'] !== false) { if (!$zom['knockBack']) { //echo $zy; $zom['up'] = 0; if ($this->AIHolder->whatBlock($level, $pos) == "water") { $zom['swim'] += 1; if ($zom['swim'] >= 20) { $zom['swim'] = 0; } } else { $zom['swim'] = 0; } //echo("目标:".$zo->getY()." "); //echo("原先:".$zom['oldv3']->y."\n"); if (abs($zo->getY() - $zom['oldv3']->y) == 1 and $zom['canjump'] === true) { //var_dump("跳"); $zom['canjump'] = false; $zom['jump'] = 0.5; } else { if ($zom['jump'] > 0.01) { $zom['jump'] -= 0.1; } else { $zom['jump'] = 0.01; } } $pk3 = new SetEntityMotionPacket(); $pk3->entities = [[$zo->getID(), $zom['xxx'] / 10, -$zom['swim'] / 100 + $zom['jump'] - $drop, $zom['zzz'] / 10]]; foreach ($zo->getViewers() as $pl) { $pl->dataPacket($pk3); } $p = $this->AIHolder->getServer()->getPlayer($zom['IsChasing']); if ($p instanceof Player) { if ($p->distance($pos) <= 1.3) { //僵尸的火焰点燃人类 if ($zo->fireTicks > 0) { $p->setOnFire(1); } } if ($p->distance($pos) <= 1.5) { if ($zom['canAttack'] == 0) { $zom['canAttack'] = 20; //@$p->knockBack($zo, 0, $zom['xxx'] / 10, $zom['zzz'] / 10); if ($p->isSurvival()) { $zoDamage = $this->AIHolder->getZombieDamage($zo->getHealth()); $damage = $this->AIHolder->getPlayerDamage($p, $zoDamage); //echo $zoDamage."-".$damage."\n"; $p->attack($damage, new EntityDamageByEntityEvent($zo, $p, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $damage)); } } } } } } else { if ($zom['up'] == 1) { if ($zom['yup'] <= 10) { $pk3 = new SetEntityMotionPacket(); $pk3->entities = [[$zo->getID(), $zom['motionx'] / 10, $zom['motiony'] / 10, $zom['motionz'] / 10]]; foreach ($zo->getViewers() as $pl) { $pl->dataPacket($pk3); } } else { $pk3 = new SetEntityMotionPacket(); $pk3->entities = [[$zo->getID(), $zom['motionx'] / 10 - $zom['motiony'] / 10, $zom['motionz'] / 10]]; foreach ($zo->getViewers() as $pl) { $pl->dataPacket($pk3); } } } else { $pk3 = new SetEntityMotionPacket(); $pk3->entities = [[$zo->getID(), $zom['motionx'] / 10, -$zom['motiony'] / 10, $zom['motionz'] / 10]]; foreach ($zo->getViewers() as $pl) { $pl->dataPacket($pk3); } } } } } } } }
/** * 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) { $block = $this->level->getBlock($tile); $this->server->getPluginManager()->callEvent($ev = new BlockBreakEvent($this, $block, $this->getInventory()->getItemInHand(), true)); if (!$ev->isCancelled()) { $item = $tile->getItem(); $this->server->getPluginManager()->callEvent($ev = new ItemFrameDropItemEvent($this, $block, $tile, $item)); if (!$ev->isCancelled()) { if ($item->getId() !== Item::AIR) { if (mt_rand(0, 10) / 10 < $tile->getItemDropChance()) { $this->level->dropItem($tile, $item); } $tile->setItem(Item::get(Item::AIR)); $tile->setItemRotation(0); } } else { $tile->spawnTo($this); } } else { $tile->spawnTo($this); } } break; case ProtocolInfo::REQUEST_CHUNK_RADIUS_PACKET: /*if($this->spawned){ $this->viewDistance = $packet->radius ** 2; }*/ $pk = new ChunkRadiusUpdatedPacket(); $pk->radius = $this->server->chunkRadius != -1 ? $this->server->chunkRadius : $packet->radius; $this->dataPacket($pk); break; case ProtocolInfo::PLAYER_INPUT_PACKET: break; case ProtocolInfo::LOGIN_PACKET: if ($this->loggedIn) { break; } $pk = new PlayStatusPacket(); $pk->status = PlayStatusPacket::LOGIN_SUCCESS; $this->dataPacket($pk); $this->username = TextFormat::clean($packet->username); $this->displayName = $this->username; $this->setNameTag($this->username); $this->iusername = strtolower($this->username); $this->protocol = $packet->protocol; if ($this->server->getConfigBoolean("online-mode", false) && $packet->identityPublicKey === null) { $this->kick("disconnectionScreen.notAuthenticated", false); break; } if (count($this->server->getOnlinePlayers()) >= $this->server->getMaxPlayers() and $this->kick("disconnectionScreen.serverFull", false)) { break; } if (!in_array($packet->protocol, ProtocolInfo::ACCEPTED_PROTOCOLS)) { 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(); $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 * 64 * 4 and strlen($packet->skin) != 64 * 32 * 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; } if ($this->isConnected()) { $this->onPlayerPreLogin(); } break; case ProtocolInfo::MOVE_PLAYER_PACKET: if ($this->linkedEntity instanceof Entity) { $entity = $this->linkedEntity; if ($entity instanceof Boat) { $entity->setPosition($this->temporalVector->setComponents($packet->x, $packet->y - 0.3, $packet->z)); } /*if($entity instanceof Minecart){ $entity->isFreeMoving = true; $entity->motionX = -sin($packet->yaw / 180 * M_PI); $entity->motionZ = cos($packet->yaw / 180 * M_PI); }*/ } $newPos = new Vector3($packet->x, $packet->y - $this->getEyeHeight(), $packet->z); $revert = false; if (!$this->isAlive() or $this->spawned !== true) { $revert = true; $this->forceMovement = new Vector3($this->x, $this->y, $this->z); } if ($this->teleportPosition !== null or $this->forceMovement instanceof Vector3 and (($dist = $newPos->distanceSquared($this->forceMovement)) > 0.1 or $revert)) { if ($this->forceMovement instanceof Vector3) { $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::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; 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; } } $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 === 0xff) { if ($this->isSpectator()) { break; } $aimPos = (new Vector3($packet->x / 32768, $packet->y / 32768, $packet->z / 32768))->normalize(); 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; } if ($item->getId() === Item::FISHING_ROD) { if ($this->isFishing()) { $this->server->getPluginManager()->callEvent($ev = new PlayerUseFishingRodEvent($this, PlayerUseFishingRodEvent::ACTION_STOP_FISHING)); } else { $this->server->getPluginManager()->callEvent($ev = new PlayerUseFishingRodEvent($this, PlayerUseFishingRodEvent::ACTION_START_FISHING)); } if (!$ev->isCancelled()) { if ($this->isFishing()) { $this->setFishingHook(); } else { $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)])]); $f = 0.6; $this->fishingHook = new FishingHook($this->chunk, $nbt, $this); $this->fishingHook->setMotion($this->fishingHook->getMotion()->multiply($f)); $this->fishingHook->spawnToAll(); } } } elseif ($item->getId() === Item::SNOWBALL) { $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)])]); $f = 1.5; $snowball = Entity::createEntity("Snowball", $this->chunk, $nbt, $this); $snowball->setMotion($snowball->getMotion()->multiply($f)); if ($this->isSurvival()) { $item->setCount($item->getCount() - 1); $this->inventory->setItemInHand($item->getCount() > 0 ? $item : Item::get(Item::AIR)); } if ($snowball instanceof Projectile) { $this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($snowball)); if ($projectileEv->isCancelled()) { $snowball->kill(); } else { $snowball->spawnToAll(); $this->level->addSound(new LaunchSound($this), $this->getViewers()); } } else { $snowball->spawnToAll(); } } elseif ($item->getId() === Item::EGG) { $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)])]); $f = 1.5; $egg = Entity::createEntity("Egg", $this->chunk, $nbt, $this); $egg->setMotion($egg->getMotion()->multiply($f)); if ($this->isSurvival()) { $item->setCount($item->getCount() - 1); $this->inventory->setItemInHand($item->getCount() > 0 ? $item : Item::get(Item::AIR)); } if ($egg instanceof Projectile) { $this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($egg)); if ($projectileEv->isCancelled()) { $egg->kill(); } else { $egg->spawnToAll(); $this->level->addSound(new LaunchSound($this), $this->getViewers()); } } else { $egg->spawnToAll(); } } elseif ($item->getId() == Item::ENCHANTING_BOTTLE) { $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)])]); $f = 1.1; $thrownExpBottle = new ThrownExpBottle($this->chunk, $nbt, $this); $thrownExpBottle->setMotion($thrownExpBottle->getMotion()->multiply($f)); if ($this->isSurvival()) { $item->setCount($item->getCount() - 1); $this->inventory->setItemInHand($item->getCount() > 0 ? $item : Item::get(Item::AIR)); } if ($thrownExpBottle instanceof Projectile) { $this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($thrownExpBottle)); if ($projectileEv->isCancelled()) { $thrownExpBottle->kill(); } else { $thrownExpBottle->spawnToAll(); $this->level->addSound(new LaunchSound($this), $this->getViewers()); } } else { $thrownExpBottle->spawnToAll(); } } elseif ($item->getId() == Item::SPLASH_POTION and $this->server->allowSplashPotion) { $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)]), "PotionId" => new ShortTag("PotionId", $item->getDamage())]); $f = 1.1; $thrownPotion = new ThrownPotion($this->chunk, $nbt, $this); $thrownPotion->setMotion($thrownPotion->getMotion()->multiply($f)); if ($this->isSurvival()) { $item->setCount($item->getCount() - 1); $this->inventory->setItemInHand($item->getCount() > 0 ? $item : Item::get(Item::AIR)); } if ($thrownPotion instanceof Projectile) { $this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($thrownPotion)); if ($projectileEv->isCancelled()) { $thrownPotion->kill(); } else { $thrownPotion->spawnToAll(); $this->level->addSound(new LaunchSound($this), $this->getViewers()); } } else { $thrownPotion->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_SPAWN_SAME_DIMENSION and $packet->action !== PlayerActionPacket::ACTION_SPAWN_OVERWORLD) { break; } $packet->eid = $this->id; $pos = new Vector3($packet->x, $packet->y, $packet->z); switch ($packet->action) { case PlayerActionPacket::ACTION_START_BREAK: //Fixes fire breaking in creative. if ($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()) { $side = $target->getSide($packet->face); if ($side instanceof Fire) { $side->getLevel()->setBlock($side, new Air()); } $this->lastBreak = microtime(true); } else { $this->inventory->sendHeldItem($this); } 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; //$pk; $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_SPAWN_SAME_DIMENSION: case PlayerActionPacket::ACTION_SPAWN_OVERWORLD: if ($this->isAlive() or !$this->isOnline()) { break; } if ($this->server->isHardcore()) { $this->setBanned(true); break; } $this->craftingType = self::CRAFTING_SMALL; if ($this->server->netherEnabled) { if ($this->level == $this->server->netherLevel) { $this->teleport($pos = $this->server->getDefaultLevel()->getSafeSpawn()); } } $this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $this->getSpawn())); $this->teleport($ev->getRespawnPosition()); $this->setSprinting(false); $this->setSneaking(false); $this->extinguish(); $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, 300); $this->deadTicks = 0; $this->noDamageTicks = 60; $this->removeAllEffects(); $this->setHealth($this->getMaxHealth()); $this->setFood(20); $this->starvationTick = 0; $this->foodTick = 0; $this->foodUsageTime = 0; $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, $this->server->destroyBlockParticle)) { 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; if ($target instanceof Player and $this->server->getConfigBoolean("pvp", true) === false) { $cancelled = true; } if ($target instanceof Boat or $target instanceof Minecart and $target->getType() == Minecart::TYPE_NORMAL) { if ($packet->action === InteractPacket::ACTION_RIGHT_CLICK) { $this->linkEntity($target); } elseif ($packet->action === InteractPacket::ACTION_LEFT_CLICK) { if ($this->linkedEntity == $target) { $target->setLinked(0, $this); } $target->close(); } elseif ($packet->action === InteractPacket::ACTION_LEAVE_VEHICLE) { $this->setLinked(0, $target); } return; } if ($packet->action === InteractPacket::ACTION_RIGHT_CLICK) { if ($target instanceof Animal and $this->getInventory()->getItemInHand()) { //TODO: Feed } break; } 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(); $damage = [EntityDamageEvent::MODIFIER_BASE => $item->getModifyAttackDamage($target)]; 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; } } $ev = new EntityDamageByEntityEvent($this, $target, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $damage, 0.4 + $item->getEnchantmentLevel(Enchantment::TYPE_WEAPON_KNOCKBACK) * 0.15); if ($cancelled) { $ev->setCancelled(); } if ($target->attack($ev->getFinalDamage(), $ev) === true) { $fireAspectL = $item->getEnchantmentLevel(Enchantment::TYPE_WEAPON_FIRE_ASPECT); if ($fireAspectL > 0) { $fireEv = new EntityCombustByEntityEvent($this, $target, $fireAspectL * 4, $ev->getFireProtectL()); Server::getInstance()->getPluginManager()->callEvent($fireEv); if (!$fireEv->isCancelled()) { $target->setOnFire($fireEv->getDuration()); } } //Thorns if ($this->isSurvival()) { $ev->createThornsDamage(); if ($ev->getThornsDamage() > 0) { $thornsEvent = new EntityDamageByEntityEvent($target, $this, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $ev->getThornsDamage(), 0); if (!$thornsEvent->isCancelled()) { if ($this->attack($thornsEvent->getFinalDamage(), $thornsEvent) === true) { $thornsEvent->useArmors(); $ev->setThornsArmorUse(); } } } } $ev->useArmors(); } 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; 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; } if ($this->isCreative() and $this->server->limitedCreative) { break; } $this->getTransactionQueue()->addTransaction(new DropItemTransaction($packet->item)); 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, 1) === "/") { //Command Timings::$playerCommandTimer->startTiming(); $this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1)); 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; } /** * For some annoying reason, anvils send window ID 255 when crafting with them instead of the _actual_ anvil window ID * The result of this is anvils immediately closing when used. This is highly unusual, especially since the * container set slot packets send the correct window ID, but... eh */ /*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 ($this->craftingType === self::CRAFTING_ANVIL) { $anvilInventory = $this->windowIndex[$packet->windowId] ?? null; if ($anvilInventory === null) { foreach ($this->windowIndex as $window) { if ($window instanceof AnvilInventory) { $anvilInventory = $window; break; } } if ($anvilInventory === null) { //If it's _still_ null, then the player doesn't have a valid anvil window, cannot proceed. $this->getServer()->getLogger()->debug("Couldn't find an anvil window for " . $this->getName() . ", exiting"); $this->inventory->sendContents($this); break; } } if ($recipe === null) { //Item renamed if (!$anvilInventory->onRename($this, $packet->output[0])) { $this->getServer()->getLogger()->debug($this->getName() . " failed to rename an item in an anvil"); $this->inventory->sendContents($this); } } else { //TODO: Anvil crafting recipes } break; } elseif (($recipe instanceof BigShapelessRecipe or $recipe instanceof BigShapedRecipe) and $this->craftingType === 0) { $this->server->getLogger()->debug("Received big crafting recipe from " . $this->getName() . " with no crafting table open"); $this->inventory->sendContents($this); break; } elseif ($recipe === null) { $this->server->getLogger()->debug("Null (unknown) crafting recipe received from " . $this->getName() . " for " . $packet->output[0]); $this->inventory->sendContents($this); break; } /** @var Item $item */ foreach ($packet->input as $i => $item) { if ($item->getDamage() === -1 or $item->getDamage() === 0xffff) { $item->setDamage(null); } if ($i < 9 and $item->getId() > 0) { //TODO: Get rid of this hack. $item->setCount(1); } } $canCraft = true; if (count($packet->input) === 0) { /* If the packet "input" field is empty this needs to be handled differently. * "input" is used to tell the server what items to remove from the client's inventory * Because crafting takes the materials in the crafting grid, nothing needs to be taken from the inventory * Instead, we take the materials from the crafting inventory * To know what materials we need to take, we have to guess the crafting recipe used based on the * output item and the materials stored in the crafting items * The reason we have to guess is because Win10 sometimes sends a different recipe UUID * say, if you put the wood for a door in the right hand side of the crafting grid instead of the left * it will send the recipe UUID for a wooden pressure plate. Unknown currently whether this is a client * bug or if there is something wrong with the way the server handles recipes. * TODO: Remove recipe correction and fix desktop crafting recipes properly. * In fact, TODO: Rewrite crafting entirely. */ $possibleRecipes = $this->server->getCraftingManager()->getRecipesByResult($packet->output[0]); if (!$packet->output[0]->deepEquals($recipe->getResult())) { $this->server->getLogger()->debug("Mismatched desktop recipe received from player " . $this->getName() . ", expected " . $recipe->getResult() . ", got " . $packet->output[0]); } $recipe = null; foreach ($possibleRecipes as $r) { /* Check the ingredient list and see if it matches the ingredients we've put into the crafting grid * As soon as we find a recipe that we have all the ingredients for, take it and run with it. */ //Make a copy of the floating inventory that we can make changes to. $floatingInventory = clone $this->floatingInventory; $ingredients = $r->getIngredientList(); //Check we have all the necessary ingredients. foreach ($ingredients as $ingredient) { if (!$floatingInventory->contains($ingredient)) { //We're short on ingredients, try the next recipe $canCraft = false; break; } //This will only be reached if we have the item to take away. $floatingInventory->removeItem($ingredient); } if ($canCraft) { //Found a recipe that works, take it and run with it. $recipe = $r; break; } } if ($recipe !== null) { $this->server->getPluginManager()->callEvent($ev = new CraftItemEvent($this, $ingredients, $recipe)); if ($ev->isCancelled()) { $this->inventory->sendContents($this); break; } $this->floatingInventory = $floatingInventory; //Set player crafting inv to the idea one created in this process $this->floatingInventory->addItem(clone $recipe->getResult()); //Add the result to our picture of the crafting inventory } else { $this->server->getLogger()->debug("Unmatched desktop crafting recipe " . $packet->id . " from player " . $this->getName()); $this->inventory->sendContents($this); break; } } else { if ($recipe instanceof ShapedRecipe) { for ($x = 0; $x < 3 and $canCraft; ++$x) { for ($y = 0; $y < 3; ++$y) { $item = $packet->input[$y * 3 + $x]; $ingredient = $recipe->getIngredient($x, $y); if ($item->getCount() > 0 and $item->getId() > 0) { if ($ingredient == null) { $canCraft = false; break; } if ($ingredient->getId() != 0 and !$ingredient->deepEquals($item, $ingredient->getDamage() !== null, $ingredient->getCompoundTag() !== null)) { $canCraft = false; break; } } elseif ($ingredient !== null and $item->getId() !== 0) { $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; } //Nasty hack. TODO: Get rid $canCraft = true; //0.13.1大量物品本地配方出现问题,无法解决,使用极端(唯一)方法修复. /** @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 and !$this->isCreative()) { //Could not add all the items to our inventory (not enough space) 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); $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(); } $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; default: break; } $timings->stopTiming(); }
/** * 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(); }
/** * 高密集型发包计时器 * - 发送数据包 * - 计算自由落体相关数据 * 循环间隔:1 tick */ public function CreeperRandomWalk() { foreach ($this->AIHolder->getServer()->getLevels() as $level) { foreach ($level->getEntities() as $zo) { if ($zo instanceof Creeper) { if (isset($this->AIHolder->Creeper[$zo->getId()])) { $zom =& $this->AIHolder->Creeper[$zo->getId()]; if ($zom['canAttack'] != 0) { $zom['canAttack'] -= 1; } $pos = $zo->getLocation(); //echo ($zom['IsChasing']."\n"); if ($zom['boom'] == false) { //真正的自由落体 by boybook if ($zom['drop'] !== false) { $olddrop = $zom['drop']; $zom['drop'] += 0.5; $drop = $zom['drop']; //echo $drop."\n"; $dropy = $zo->getY() - ($olddrop * 0.05 + 0.0125); $posd0 = new Vector3(floor($zo->getX()), floor($dropy), floor($zo->getZ())); $posd = new Vector3($zo->getX(), $dropy, $zo->getZ()); if ($this->AIHolder->whatBlock($zo->getLevel(), $posd0) == "air") { $zo->setPosition($posd->add(0, 1, 0)); //下降 } else { for ($i = 1; $i <= $drop; $i++) { $posd0->y++; if ($this->AIHolder->whatBlock($zo->getLevel(), $posd0) != "block") { $posd->y = $posd0->y; //$zo->setPosition($posd); //下降完成 $h = $zom['drop'] * $zom['drop'] / 20; $damage = $h - 3; //echo($h . ": " . $damage . "\n"); if ($damage > 0) { $zo->setHealth($zo->getHealth() - $damage); } $zom['drop'] = false; break; } } } } else { $drop = 0; } if ($zom['IsChasing'] !== false) { //var_dump($zom['IsChasing']); if (!$zom['knockBack']) { //echo $zy; $zom['up'] = 0; if ($this->AIHolder->whatBlock($level, $pos) == "water") { $zom['swim'] += 1; if ($zom['swim'] >= 20) { $zom['swim'] = 0; } } else { $zom['swim'] = 0; } //echo("目标:".$zo->getY()." "); //echo("原先:".$zom['oldv3']->y."\n"); if (abs($zo->getY() - $zom['oldv3']->y) == 1 and $zom['canjump'] === true) { //var_dump("跳"); $zom['canjump'] = false; $zom['jump'] = 0.5; } else { if ($zom['jump'] > 0.01) { $zom['jump'] -= 0.1; } else { $zom['jump'] = 0.01; } } $pk3 = new SetEntityMotionPacket(); $pk3->entities = [[$zo->getID(), $zom['xxx'] / 10, -$zom['swim'] / 100 + $zom['jump'] - $drop, $zom['zzz'] / 10]]; foreach ($zo->getViewers() as $pl) { $pl->dataPacket($pk3); } $p = $this->AIHolder->getServer()->getPlayer($zom['IsChasing']); if ($p instanceof Player) { if ($p->distance($pos) <= 1.3) { //苦力怕的火焰点燃人类 // if ($zo->fireTicks > 0) { // $p->setOnFire(5); // } } if ($p->distance($pos) <= 2) { //boom $zom['boom'] = 5; } if ($p->distance($pos) <= 1.5) { if ($zom['canAttack'] == 0) { $zom['canAttack'] = 20; @$p->knockBack($zo, 0, $zom['xxx'] / 10, $zom['zzz'] / 10); if ($p->isSurvival()) { $damage = 0; //echo $zoDamage."-".$damage."\n"; //$p->attack($damage); } } } } } } else { //echo "."; $pk3 = new SetEntityMotionPacket(); $pk3->entities = [[$zo->getID(), $zom['motionx'] / 10, 0, $zom['motionz'] / 10]]; foreach ($zo->getViewers() as $pl) { $pl->dataPacket($pk3); } } } else { $zom['boom'] = $zom['boom'] - 1; if ($zom['boom'] <= 0) { unset($this->AIHolder->Creeper[$zo->getId()]); $level->getEntity($zo->getId())->close(); $e = new Explosion(new Position($zo->getX(), $zo->getY(), $zo->getZ(), $level), 4); if ($this->AIHolder->getServer()->aiConfig["creeperexplode"]) { $e->explode(); } else { $e->explodeB(); } } } } } } } }
public function SkeletonRandomWalk() { foreach ($this->plugin->getServer()->getLevels() as $level) { foreach ($level->getEntities() as $zo) { if ($zo instanceof Skeleton) { if (isset($this->plugin->Skeleton[$zo->getId()])) { $zom =& $this->plugin->Skeleton[$zo->getId()]; if ($zom['canAttack'] != 0) { $zom['canAttack'] -= 1; } $pos = $zo->getLocation(); //echo ($zom['IsChasing']."\n"); //真正的自由落体 by boybook if ($zom['drop'] !== false) { $olddrop = $zom['drop']; $zom['drop'] += 0.5; $drop = $zom['drop']; //echo $drop."\n"; $dropy = $zo->getY() - ($olddrop * 0.05 + 0.0125); $posd0 = new Vector3(floor($zo->getX()), floor($dropy), floor($zo->getZ())); $posd = new Vector3($zo->getX(), $dropy, $zo->getZ()); if ($this->plugin->whatBlock($zo->getLevel(), $posd0) == "air") { $zo->setPosition($posd->add(0, 1, 0)); //下降 } else { for ($i = 1; $i <= $drop; $i++) { $posd0->y++; if ($this->plugin->whatBlock($zo->getLevel(), $posd0) != "block") { $posd->y = $posd0->y; $zo->setPosition($posd->add(0, 1, 0)); //下降完成 $h = $zom['drop'] * $zom['drop'] / 20; $damage = $h - 3; //echo($h . ": " . $damage . "\n"); if ($damage > 0) { //$zo->attack($damage, EntityDamageEvent::CAUSE_FALL); $zo->setHealth($zo->getHealth() - $damage); } $zom['drop'] = false; break; } } } } else { $drop = 0; } if ($zom['IsChasing'] !== false) { if (!$zom['knockBack']) { //echo $zy; $zom['up'] = 0; if ($this->plugin->whatBlock($level, $pos) == "water") { $zom['swim'] += 1; if ($zom['swim'] >= 20) { $zom['swim'] = 0; } } else { $zom['swim'] = 0; } //echo("目标:".$zo->getY()." "); //echo("原先:".$zom['oldv3']->y."\n"); if (abs($zo->getY() - $zom['oldv3']->y) == 1 and $zom['canjump'] === true) { //var_dump("跳"); $zom['canjump'] = false; $zom['jump'] = 0.5; } else { if ($zom['jump'] > 0.01) { $zom['jump'] -= 0.1; } else { $zom['jump'] = 0.01; } } $pk3 = new SetEntityMotionPacket(); $pk3->entities = [[$zo->getID(), $zom['xxx'] / 10, -$zom['swim'] / 100 + $zom['jump'] - $drop, $zom['zzz'] / 10]]; foreach ($zo->getViewers() as $pl) { $pl->dataPacket($pk3); } $p = $this->plugin->getServer()->getPlayer($zom['IsChasing']); if ($p instanceof Player) { $zom['shoot'] = $zom['shoot'] - 1; if ($zom['shoot'] <= 0) { //boom $zom['shoot'] = 20; $v3 = new Vector3($zo->getX(), $zo->getY() + 2, $zo->getZ()); $chunk = $level->getChunk($v3->x >> 4, $v3->z >> 4, true); $posnn = new Vector3($zo->getX(), $p->getY(), $zo->getZ()); $my = $p->getY() - $zo->getY(); $d = $p->distance($posnn); $pitch = $this->getmypitch($my, $d); $nbt2 = new CompoundTag("", ["Pos" => new EnumTag("Pos", [new DoubleTag("", $zo->getX()), new DoubleTag("", $zo->getY()), new DoubleTag("", $zo->getZ())]), "Motion" => new EnumTag("Motion", [new DoubleTag("", -\sin($zom['yaw']) * \cos($pitch / 180 * M_PI)), new DoubleTag("", -\sin($pitch / 180 * M_PI)), new DoubleTag("", \cos($zom['yaw'] / 180 * M_PI) * \cos($pitch / 180 * M_PI))]), "Rotation" => new EnumTag("Rotation", [new FloatTag("", $zom['yaw']), new FloatTag("", $pitch)])]); $f = 1.5; //$ev = new EntityShootBowEvent($this, $bow, Entity::createEntity("Arrow", $this->chunk, $nbt, $this), $f); $ev = new EntityShootBowEvent($zo, new ITEM(262, 0), Entity::createEntity("Arrow", $chunk, $nbt2, $zo), $f); } } } } else { if ($zom['up'] == 1) { if ($zom['yup'] <= 10) { $pk3 = new SetEntityMotionPacket(); $pk3->entities = [[$zo->getID(), $zom['motionx'] / 10, $zom['motiony'] / 10, $zom['motionz'] / 10]]; foreach ($zo->getViewers() as $pl) { $pl->dataPacket($pk3); } } else { $pk3 = new SetEntityMotionPacket(); $pk3->entities = [[$zo->getID(), $zom['motionx'] / 10 - $zom['motiony'] / 10, $zom['motionz'] / 10]]; foreach ($zo->getViewers() as $pl) { $pl->dataPacket($pk3); } } } else { $pk3 = new SetEntityMotionPacket(); $pk3->entities = [[$zo->getID(), $zom['motionx'] / 10, -$zom['motiony'] / 10, $zom['motionz'] / 10]]; foreach ($zo->getViewers() as $pl) { $pl->dataPacket($pk3); } } } } } } } }
/** * 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) { $block = $this->level->getBlock($tile); $this->server->getPluginManager()->callEvent($ev = new BlockBreakEvent($this, $block, $this->getInventory()->getItemInHand(), true)); if (!$ev->isCancelled()) { $item = $tile->getItem(); $this->server->getPluginManager()->callEvent($ev = new ItemFrameDropItemEvent($this, $block, $tile, $item)); if (!$ev->isCancelled()) { if ($item->getId() !== Item::AIR) { if (mt_rand(0, 10) / 10 < $tile->getItemDropChance()) { $this->level->dropItem($tile, $item); } $tile->setItem(Item::get(Item::AIR)); $tile->setItemRotation(0); } } else { $tile->spawnTo($this); } } else { $tile->spawnTo($this); } } break; case ProtocolInfo::REQUEST_CHUNK_RADIUS_PACKET: /*if($this->spawned){ $this->viewDistance = $packet->radius ** 2; }*/ $pk = new ChunkRadiusUpdatedPacket(); $pk->radius = $this->server->chunkRadius != -1 ? $this->server->chunkRadius : $packet->radius; $this->dataPacket($pk); break; case ProtocolInfo::PLAYER_INPUT_PACKET: break; case ProtocolInfo::LOGIN_PACKET: if ($this->loggedIn) { break; } $pk = new PlayStatusPacket(); $pk->status = PlayStatusPacket::LOGIN_SUCCESS; $this->dataPacket($pk); $this->username = TextFormat::clean($packet->username); $this->displayName = $this->username; $this->setNameTag($this->username); $this->iusername = strtolower($this->username); $this->protocol = $packet->protocol; if (count($this->server->getOnlinePlayers()) >= $this->server->getMaxPlayers() and $this->kick("disconnectionScreen.serverFull", false)) { break; } if (!in_array($packet->protocol, ProtocolInfo::ACCEPTED_PROTOCOLS)) { 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->loginData = ["clientId" => $packet->clientId, "loginData" => null]; $this->uuid = UUID::fromString($packet->clientUUID); $this->rawUUID = $this->uuid->toBinary(); $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 * 64 * 4 and strlen($packet->skin) != 64 * 32 * 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: if ($this->linkedEntity instanceof Entity) { $entity = $this->linkedEntity; if ($entity instanceof Boat) { $entity->setPosition($this->temporalVector->setComponents($packet->x, $packet->y - 0.3, $packet->z)); } /*if($entity instanceof Minecart){ $entity->isFreeMoving = true; $entity->motionX = -sin($packet->yaw / 180 * M_PI); $entity->motionZ = cos($packet->yaw / 180 * M_PI); }*/ } $newPos = new Vector3($packet->x, $packet->y - $this->getEyeHeight(), $packet->z); $revert = false; if (!$this->isAlive() or $this->spawned !== true) { $revert = true; $this->forceMovement = new Vector3($this->x, $this->y, $this->z); } if ($this->teleportPosition !== null or $this->forceMovement instanceof Vector3 and (($dist = $newPos->distanceSquared($this->forceMovement)) > 0.1 or $revert)) { if ($this->forceMovement instanceof Vector3) { $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::MOB_EQUIPMENT_PACKET: if ($this->spawned === false or !$this->isAlive()) { 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 } /** @var Item $item */ $item = null; if ($this->isCreative()) { //Creative mode match $item = $packet->item; $slot = Item::getCreativeItemIndex($item); } else { $item = $this->inventory->getItem($packet->slot); $slot = $packet->slot; } if ($packet->slot === -1) { //Air if ($this->isCreative()) { $found = false; for ($i = 0; $i < $this->inventory->getHotbarSize(); ++$i) { if ($this->inventory->getHotbarSlotIndex($i) === -1) { $this->inventory->setHeldItemIndex($i); $found = true; break; } } if (!$found) { //couldn't find a empty slot (error) $this->inventory->sendContents($this); break; } } else { if ($packet->selectedSlot >= 0 and $packet->selectedSlot < 9) { $this->inventory->setHeldItemIndex($packet->selectedSlot, false); $this->inventory->setHeldItemSlot($packet->slot); $this->inventory->sendHeldItem($this->getViewers()); } else { $this->inventory->sendContents($this); break; } } } elseif ($item === null or $slot === -1 or !$item->deepEquals($packet->item)) { // packet error or not implemented $this->inventory->sendContents($this); break; } elseif ($this->isCreative()) { $this->inventory->setHeldItemIndex($packet->selectedSlot, false); $this->inventory->setItem($packet->selectedSlot, $item); $this->inventory->setHeldItemSlot($packet->selectedSlot); $this->inventory->sendHeldItem($this->getViewers()); } else { if ($packet->selectedSlot >= 0 and $packet->selectedSlot < $this->inventory->getHotbarSize()) { $this->inventory->setHeldItemIndex($packet->selectedSlot, false); $this->inventory->setHeldItemSlot($slot); $this->inventory->sendHeldItem($this->getViewers()); } else { $this->inventory->sendContents($this); break; } } $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 = 0; 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; 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; } } $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 === 0xff) { if ($this->isSpectator()) { break; } $aimPos = (new Vector3($packet->x / 32768, $packet->y / 32768, $packet->z / 32768))->normalize(); 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; } if ($item->getId() === Item::FISHING_ROD) { if ($this->isFishing()) { $this->server->getPluginManager()->callEvent($ev = new PlayerUseFishingRodEvent($this, PlayerUseFishingRodEvent::ACTION_STOP_FISHING)); } else { $this->server->getPluginManager()->callEvent($ev = new PlayerUseFishingRodEvent($this, PlayerUseFishingRodEvent::ACTION_START_FISHING)); } if (!$ev->isCancelled()) { if ($this->isFishing()) { $this->setFishingHook(); } else { $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)])]); $f = 0.6; $this->fishingHook = new FishingHook($this->chunk, $nbt, $this); $this->fishingHook->setMotion($this->fishingHook->getMotion()->multiply($f)); $this->fishingHook->spawnToAll(); } } } elseif ($item->getId() === Item::SNOWBALL) { $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)])]); $f = 1.5; $snowball = Entity::createEntity("Snowball", $this->chunk, $nbt, $this); $snowball->setMotion($snowball->getMotion()->multiply($f)); if ($this->isSurvival()) { $item->setCount($item->getCount() - 1); $this->inventory->setItemInHand($item->getCount() > 0 ? $item : Item::get(Item::AIR)); } if ($snowball instanceof Projectile) { $this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($snowball)); if ($projectileEv->isCancelled()) { $snowball->kill(); } else { $snowball->spawnToAll(); $this->level->addSound(new LaunchSound($this), $this->getViewers()); } } else { $snowball->spawnToAll(); } } elseif ($item->getId() === Item::EGG) { $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)])]); $f = 1.5; $egg = Entity::createEntity("Egg", $this->chunk, $nbt, $this); $egg->setMotion($egg->getMotion()->multiply($f)); if ($this->isSurvival()) { $item->setCount($item->getCount() - 1); $this->inventory->setItemInHand($item->getCount() > 0 ? $item : Item::get(Item::AIR)); } if ($egg instanceof Projectile) { $this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($egg)); if ($projectileEv->isCancelled()) { $egg->kill(); } else { $egg->spawnToAll(); $this->level->addSound(new LaunchSound($this), $this->getViewers()); } } else { $egg->spawnToAll(); } } elseif ($item->getId() == Item::ENCHANTING_BOTTLE) { $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)])]); $f = 1.1; $thrownExpBottle = new ThrownExpBottle($this->chunk, $nbt, $this); $thrownExpBottle->setMotion($thrownExpBottle->getMotion()->multiply($f)); if ($this->isSurvival()) { $item->setCount($item->getCount() - 1); $this->inventory->setItemInHand($item->getCount() > 0 ? $item : Item::get(Item::AIR)); } if ($thrownExpBottle instanceof Projectile) { $this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($thrownExpBottle)); if ($projectileEv->isCancelled()) { $thrownExpBottle->kill(); } else { $thrownExpBottle->spawnToAll(); $this->level->addSound(new LaunchSound($this), $this->getViewers()); } } else { $thrownExpBottle->spawnToAll(); } } elseif ($item->getId() == Item::SPLASH_POTION and $this->server->allowSplashPotion) { $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)]), "PotionId" => new ShortTag("PotionId", $item->getDamage())]); $f = 1.1; $thrownPotion = new ThrownPotion($this->chunk, $nbt, $this); $thrownPotion->setMotion($thrownPotion->getMotion()->multiply($f)); if ($this->isSurvival()) { $item->setCount($item->getCount() - 1); $this->inventory->setItemInHand($item->getCount() > 0 ? $item : Item::get(Item::AIR)); } if ($thrownPotion instanceof Projectile) { $this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($thrownPotion)); if ($projectileEv->isCancelled()) { $thrownPotion->kill(); } else { $thrownPotion->spawnToAll(); $this->level->addSound(new LaunchSound($this), $this->getViewers()); } } else { $thrownPotion->spawnToAll(); } } $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, true); $this->startAction = $this->server->getTick(); } break; case ProtocolInfo::PLAYER_ACTION_PACKET: //$this->consumeHeldItem(); if ($this->spawned === false or $this->blocked === true or !$this->isAlive() and $packet->action !== PlayerActionPacket::ACTION_SPAWN_SAME_DIMENSION and $packet->action !== PlayerActionPacket::ACTION_SPAWN_OVERWORLD) { break; } $packet->eid = $this->id; $pos = new Vector3($packet->x, $packet->y, $packet->z); switch ($packet->action) { case PlayerActionPacket::ACTION_START_BREAK: //Fixes fire breaking in creative. if ($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()) { $side = $target->getSide($packet->face); if ($side instanceof Fire) { $side->getLevel()->setBlock($side, new Air()); } $this->lastBreak = microtime(true); } else { $this->inventory->sendHeldItem($this); } 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; //$pk; $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_SPAWN_SAME_DIMENSION: case PlayerActionPacket::ACTION_SPAWN_OVERWORLD: if ($this->isAlive() or !$this->isOnline()) { break; } if ($this->server->isHardcore()) { $this->setBanned(true); break; } $this->craftingType = 0; if ($this->server->netherEnabled) { if ($this->level == $this->server->netherLevel) { $this->teleport($pos = $this->server->getDefaultLevel()->getSafeSpawn()); } } $this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $this->getSpawn())); $this->teleport($ev->getRespawnPosition()); $this->setSprinting(false); $this->setSneaking(false); $this->extinguish(); $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, 300); $this->deadTicks = 0; $this->noDamageTicks = 60; $this->removeAllEffects(); $this->setHealth($this->getMaxHealth()); $this->setFood(20); if ($this->server->expEnabled) { $this->updateExperience(); } $this->starvationTick = 0; $this->foodTick = 0; $this->foodUsageTime = 0; $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; 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; 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; 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; } if ($packet->action !== PlayerActionPacket::ACTION_JUMP) { $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 = 0; $vector = new Vector3($packet->x, $packet->y, $packet->z); if ($this->isCreative()) { $item = $this->inventory->getItemInHand(); } else { $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, $this->server->destroyBlockParticle)) { if ($this->isSurvival()) { if (!$item->equals($oldItem) or $item->getCount() !== $oldItem->getCount()) { $this->inventory->setItemInHand($item); $this->inventory->sendHeldItem($this); } } 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: break; case ProtocolInfo::INTERACT_PACKET: if ($this->spawned === false or !$this->isAlive() or $this->blocked) { break; } $this->craftingType = 0; $target = $this->level->getEntity($packet->target); $cancelled = false; if ($target instanceof Player and $this->server->getConfigBoolean("pvp", true) === false) { $cancelled = true; } if ($target instanceof Boat or $target instanceof Minecart and $target->getType() == Minecart::TYPE_NORMAL) { if ($packet->action === InteractPacket::ACTION_RIGHT_CLICK) { $this->linkEntity($target); } elseif ($packet->action === InteractPacket::ACTION_LEFT_CLICK) { if ($this->linkedEntity == $target) { $target->setLinked(0, $this); } $target->close(); } elseif ($packet->action === InteractPacket::ACTION_LEAVE_VEHICLE) { $this->setLinked(0, $target); } return; } if ($packet->action === InteractPacket::ACTION_RIGHT_CLICK) { if ($target instanceof Animal and $this->getInventory()->getItemInHand()) { //TODO: Feed } break; } 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(); $damage = [EntityDamageEvent::MODIFIER_BASE => $item->getModifyAttackDamage($target)]; 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; } } $ev = new EntityDamageByEntityEvent($this, $target, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $damage, 0.4 + $item->getEnchantmentLevel(Enchantment::TYPE_WEAPON_KNOCKBACK) * 0.15); if ($cancelled) { $ev->setCancelled(); } if ($target->attack($ev->getFinalDamage(), $ev) === true) { $fireAspectL = $item->getEnchantmentLevel(Enchantment::TYPE_WEAPON_FIRE_ASPECT); if ($fireAspectL > 0) { $fireEv = new EntityCombustByEntityEvent($this, $target, $fireAspectL * 4, $ev->getFireProtectL()); Server::getInstance()->getPluginManager()->callEvent($fireEv); if (!$fireEv->isCancelled()) { $target->setOnFire($fireEv->getDuration()); } } //Thorns if ($this->isSurvival()) { $ev->createThornsDamage(); if ($ev->getThornsDamage() > 0) { $thornsEvent = new EntityDamageByEntityEvent($target, $this, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $ev->getThornsDamage(), 0); if (!$thornsEvent->isCancelled()) { if ($this->attack($thornsEvent->getFinalDamage(), $thornsEvent) === true) { $thornsEvent->useArmors(); $ev->setThornsArmorUse(); } } } } $ev->useArmors(); } if ($ev->isCancelled()) { if ($item->isTool() and $this->isSurvival()) { $this->inventory->sendContents($this); } break; } if ($item->isTool() and $this->isSurvival()) { if ($item->useOn($target) and $item->getDamage() >= $item->getMaxDurability()) { $this->inventory->setItemInHand(Item::get(Item::AIR, 0, 1)); } else { $this->inventory->setItemInHand($item); } } } 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 = 0; $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false); //TODO: check if this should be true switch ($packet->event) { case 9: //Eating $this->consumeHeldItem(); break; } break; case ProtocolInfo::DROP_ITEM_PACKET: if ($this->spawned === false or $this->blocked === true or !$this->isAlive()) { break; } if (!$this->inventory->contains($packet->item) or $this->isCreative() and $this->server->limitedCreative) { $this->inventory->sendContents($this); break; } $slot = $this->inventory->first($packet->item); if ($slot == -1) { $this->inventory->sendContents($this); break; } $dropItem = $this->inventory->getItem($slot); $ev = new PlayerDropItemEvent($this, $dropItem); $this->server->getPluginManager()->callEvent($ev); if ($ev->isCancelled()) { $this->inventory->sendSlot($slot, $this); break; } $this->inventory->remove($dropItem); //$this->inventory->setItemInHand(Item::get(Item::AIR, 0, 1)); $motion = $this->getDirectionVector()->multiply(0.4); $this->level->dropItem($this->add(0, 1.3, 0), $dropItem, $motion, 40); $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false); break; case ProtocolInfo::TEXT_PACKET: if ($this->spawned === false or !$this->isAlive()) { break; } $this->craftingType = 0; 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, 1) === "/") { //Command Timings::$playerCommandTimer->startTiming(); $this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1)); 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 = 0; $this->currentTransaction = null; if (isset($this->windowIndex[$packet->windowid])) { if ($this->windowIndex[$packet->windowid] instanceof AnvilInventory) { $this->anvilItem = null; } $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::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; } /** @var Item $item */ 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]; $ingredient = $recipe->getIngredient($x, $y); if ($item->getCount() > 0 and $item->getId() > 0) { if ($ingredient == null) { $canCraft = false; break; } if ($ingredient->getId() != 0 and !$ingredient->deepEquals($item, $ingredient->getDamage() !== null, $ingredient->getCompoundTag() !== null)) { $canCraft = false; break; } } elseif ($ingredient !== null and $item->getId() !== 0) { $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 */ $canCraft = true; //0.13.1大量物品本地配方出现问题,无法解决,使用极端(唯一)方法修复. $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; } if ($this->isCreative()) { if (Item::getCreativeItemIndex($packet->item) !== -1) { $this->inventory->setItem($packet->slot, $packet->item); $this->inventory->setHotbarSlotIndex($packet->slot, $packet->slot); //links $hotbar[$packet->slot] to $slots[$packet->slot] } } $transaction = new BaseTransaction($this->inventory, $packet->slot, $this->inventory->getItem($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(), $this->inventory->getArmorItem($packet->slot), $packet->item); } elseif (isset($this->windowIndex[$packet->windowid])) { $this->craftingType = 0; $inv = $this->windowIndex[$packet->windowid]; /** @var $packet \pocketmine\network\protocol\ContainerSetSlotPacket */ if ($inv instanceof EnchantInventory and $packet->item->hasEnchantments()) { $inv->onEnchant($this, $inv->getItem($packet->slot), $packet->item); } if ($inv instanceof AnvilInventory) { if ($packet->slot == 2) { if ($packet->item->getId() != Item::AIR) { $this->anvilItem = $packet->item; } elseif ($this->anvilItem != null) { if (!$inv->onRename($this->anvilItem, $this)) { break; //maybe cheating! } $this->anvilItem = null; } } } $transaction = new BaseTransaction($inv, $packet->slot, $inv->getItem($packet->slot), $packet->item); } else { break; } if ($transaction->getSourceItem()->deepEquals($transaction->getTargetItem()) 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) - 8) { if ($this->currentTransaction !== null) { foreach ($this->currentTransaction->getInventories() as $inventory) { if ($inventory instanceof PlayerInventory) { $inventory->sendArmorContents($this); } $inventory->sendContents($this); } } $this->currentTransaction = new SimpleTransactionGroup($this); } $this->currentTransaction->addTransaction($transaction); if ($this->currentTransaction->canExecute()) { $achievements = []; foreach ($this->currentTransaction->getTransactions() as $ts) { $inv = $ts->getInventory(); if ($inv instanceof FurnaceInventory) { if ($ts->getSlot() === 2) { switch ($inv->getResult()->getId()) { case Item::IRON_INGOT: $achievements[] = "acquireIron"; break; } } } } if ($this->currentTransaction->execute()) { foreach ($achievements as $a) { $this->awardAchievement($a); } } $this->currentTransaction = null; } break; case ProtocolInfo::BLOCK_ENTITY_DATA_PACKET: if ($this->spawned === false or $this->blocked === true or !$this->isAlive()) { break; } $this->craftingType = 0; $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); $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(); } $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; default: break; } $timings->stopTiming(); }
/** * Creates fizzing sound and smoke. Used when lava flows over block or mixes with water. * * @param Vector3 $pos */ protected function triggerLavaMixEffects(Vector3 $pos) { $this->getLevel()->addSound(new FizzSound($pos->add(0.5, 0.5, 0.5), 2.5 + mt_rand(0, 1000) / 1000 * 0.8)); for ($i = 0; $i < 8; ++$i) { $this->getLevel()->addParticle(new SmokeParticle($pos->add(mt_rand(0, 80) / 100, 0.5, mt_rand(0, 80) / 100))); } }
/** * 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::LOGIN_PACKET: if ($this->loggedIn) { break; } $this->username = TextFormat::clean($packet->username); $this->displayName = $this->username; $this->setNameTag($this->username); $this->iusername = strtolower($this->username); if (count($this->server->getOnlinePlayers()) >= $this->server->getMaxPlayers() and $this->kick("disconnectionScreen.serverFull", false)) { break; } if ($packet->protocol1 !== ProtocolInfo::CURRENT_PROTOCOL) { if ($packet->protocol1 < 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->loginData = ["clientId" => $packet->clientId, "loginData" => null]; $this->uuid = $packet->clientUUID; $this->rawUUID = $this->uuid->toBinary(); $this->clientSecret = $packet->clientSecret; $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->skinname, $packet->oldclient, $packet->slim, $packet->transparent); $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: $newPos = new Vector3($packet->x, $packet->y - $this->getEyeHeight(), $packet->z); $revert = false; if (!$this->isAlive() or $this->spawned !== true) { $revert = true; $this->forceMovement = new Vector3($this->x, $this->y, $this->z); } if ($this->teleportPosition !== null or $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::MOB_EQUIPMENT_PACKET: if ($this->spawned === false or !$this->isAlive()) { 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 } /** @var Item $item */ $item = null; if ($this->isCreative()) { //Creative mode match $item = $packet->item; $slot = Item::getCreativeItemIndex($item); } else { $item = $this->inventory->getItem($packet->slot); $slot = $packet->slot; } if ($packet->slot === -1) { //Air if ($this->isCreative()) { $found = false; for ($i = 0; $i < $this->inventory->getHotbarSize(); ++$i) { if ($this->inventory->getHotbarSlotIndex($i) === -1) { $this->inventory->setHeldItemIndex($i); $found = true; break; } } if (!$found) { //couldn't find a empty slot (error) $this->inventory->sendContents($this); break; } } else { if ($packet->selectedSlot >= 0 and $packet->selectedSlot < 9) { $this->inventory->setHeldItemIndex($packet->selectedSlot); $this->inventory->setHeldItemSlot($packet->slot); } else { $this->inventory->sendContents($this); break; } } } elseif ($item === null or $slot === -1 or !$item->deepEquals($packet->item)) { // packet error or not implemented $this->inventory->sendContents($this); break; } elseif ($this->isCreative()) { $this->inventory->setHeldItemIndex($packet->selectedSlot); $this->inventory->setItem($packet->selectedSlot, $item); $this->inventory->setHeldItemSlot($packet->selectedSlot); } else { if ($packet->selectedSlot >= 0 and $packet->selectedSlot < $this->inventory->getHotbarSize()) { $this->inventory->setHeldItemIndex($packet->selectedSlot); $this->inventory->setHeldItemSlot($slot); } else { $this->inventory->sendContents($this); break; } } $this->inventory->sendHeldItem($this->hasSpawned); $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 = 0; 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; } } $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 === 0xff) { $aimPos = (new Vector3($packet->x / 32768, $packet->y / 32768, $packet->z / 32768))->normalize(); 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; } if ($item->getId() === Item::SNOWBALL) { $nbt = new Compound("", ["Pos" => new Enum("Pos", [new Double("", $this->x), new Double("", $this->y + $this->getEyeHeight()), new Double("", $this->z)]), "Motion" => new Enum("Motion", [new Double("", $aimPos->x), new Double("", $aimPos->y), new Double("", $aimPos->z)]), "Rotation" => new Enum("Rotation", [new Float("", $this->yaw), new Float("", $this->pitch)])]); $f = 1.5; $snowball = Entity::createEntity("Snowball", $this->chunk, $nbt, $this); $snowball->setMotion($snowball->getMotion()->multiply($f)); if ($this->isSurvival()) { $item->setCount($item->getCount() - 1); $this->inventory->setItemInHand($item->getCount() > 0 ? $item : Item::get(Item::AIR)); } if ($snowball instanceof Projectile) { $this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($snowball)); if ($projectileEv->isCancelled()) { $snowball->kill(); } else { $snowball->spawnToAll(); $this->level->addSound(new LaunchSound($this), $this->getViewers()); } } else { $snowball->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; } $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, 0, 1))) { $this->inventory->sendContents($this); break; } $nbt = new Compound("", ["Pos" => new Enum("Pos", [new Double("", $this->x), new Double("", $this->y + $this->getEyeHeight()), new Double("", $this->z)]), "Motion" => new Enum("Motion", [new Double("", -sin($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI)), new Double("", -sin($this->pitch / 180 * M_PI)), new Double("", cos($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI))]), "Rotation" => new Enum("Rotation", [new Float("", $this->yaw), new Float("", $this->pitch)]), "Fire" => new Short("Fire", $this->isOnFire() ? 45 * 60 : 0)]); $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, 0, 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 = 0; $this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $this->getSpawn())); $this->teleport($ev->getRespawnPosition()); $this->setSprinting(false); $this->setSneaking(false); $this->extinguish(); $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, 300); $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; 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; 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; 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; } $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 = 0; $vector = new Vector3($packet->x, $packet->y, $packet->z); if ($this->isCreative()) { $item = $this->inventory->getItemInHand(); } else { $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)) { if ($this->isSurvival()) { if (!$item->deepEquals($oldItem) or $item->getCount() !== $oldItem->getCount()) { $this->inventory->setItemInHand($item); $this->inventory->sendHeldItem($this->hasSpawned); } } 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: break; case ProtocolInfo::INTERACT_PACKET: if ($this->spawned === false or !$this->isAlive() or $this->blocked) { break; } $this->craftingType = 0; $target = $this->level->getEntity($packet->target); $cancelled = false; 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 => isset($damageTable[$item->getId()]) ? $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 ($item->isTool() and $this->isSurvival()) { if ($item->useOn($target) and $item->getDamage() >= $item->getMaxDurability()) { $this->inventory->setItemInHand(Item::get(Item::AIR, 0, 1)); } else { $this->inventory->setItemInHand($item); } } } 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 = 0; $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false); //TODO: check if this should be true switch ($packet->event) { case 9: //Eating $items = [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::COOKED_SALMON => 6, Item::RAW_FISH => 2, Item::RAW_SALMON => 2, Item::CLOWN_FISH => 1, Item::PUFFER_FISH => 1]; $slot = $this->inventory->getItemInHand(); if ($this->getHealth() < $this->getMaxHealth() 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 = $this->getId(); $pk->event = EntityEventPacket::USE_ITEM; $this->dataPacket($pk); Server::broadcastPacket($this->getViewers(), $pk); $amount = $items[$slot->getId()]; $ev = new EntityRegainHealthEvent($this, $amount, EntityRegainHealthEvent::CAUSE_EATING); $this->heal($ev->getAmount(), $ev); --$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)); } elseif ($slot->getId() === Item::PUFFER_FISH) { //Pufferfish //$this->addEffect(Effect::getEffect(Effect::HUNGER)->setAmplifier(2)->setDuration(15 * 20)); $this->addEffect(Effect::getEffect(Effect::NAUSEA)->setAmplifier(1)->setDuration(15 * 20)); $this->addEffect(Effect::getEffect(Effect::POISON)->setAmplifier(3)->setDuration(60 * 20)); } } break; } break; case ProtocolInfo::DROP_ITEM_PACKET: if ($this->spawned === false or $this->blocked === true or !$this->isAlive()) { break; } $item = $this->inventory->getItemInHand(); $ev = new PlayerDropItemEvent($this, $item); $this->server->getPluginManager()->callEvent($ev); if ($ev->isCancelled()) { $this->inventory->sendContents($this); break; } $this->inventory->setItemInHand(Item::get(Item::AIR, 0, 1)); $motion = $this->getDirectionVector()->multiply(0.4); $this->level->dropItem($this->add(0, 1.3, 0), $item, $motion, 40); $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false); break; case ProtocolInfo::TEXT_PACKET: if ($this->spawned === false or !$this->isAlive()) { break; } $this->craftingType = 0; 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, 1) === "/") { //Command Timings::$playerCommandTimer->startTiming(); $this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1)); 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 = 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::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]; $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, $i->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; } if ($this->isCreative()) { if (Item::getCreativeItemIndex($packet->item) !== -1) { $this->inventory->setItem($packet->slot, $packet->item); $this->inventory->setHotbarSlotIndex($packet->slot, $packet->slot); //links $hotbar[$packet->slot] to $slots[$packet->slot] } } $transaction = new BaseTransaction($this->inventory, $packet->slot, $this->inventory->getItem($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(), $this->inventory->getArmorItem($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()->deepEquals($transaction->getTargetItem()) 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) - 8) { if ($this->currentTransaction !== null) { foreach ($this->currentTransaction->getInventories() as $inventory) { if ($inventory instanceof PlayerInventory) { $inventory->sendArmorContents($this); } $inventory->sendContents($this); } } $this->currentTransaction = new SimpleTransactionGroup($this); } $this->currentTransaction->addTransaction($transaction); if ($this->currentTransaction->canExecute()) { $achievements = []; foreach ($this->currentTransaction->getTransactions() as $ts) { $inv = $ts->getInventory(); if ($inv instanceof FurnaceInventory) { if ($ts->getSlot() === 2) { switch ($inv->getResult()->getId()) { case Item::IRON_INGOT: $achievements[] = "acquireIron"; break; } } } } if ($this->currentTransaction->execute()) { foreach ($achievements as $a) { $this->awardAchievement($a); } } $this->currentTransaction = null; } break; case ProtocolInfo::BLOCK_ENTITY_DATA_PACKET: if ($this->spawned === false or $this->blocked === true or !$this->isAlive()) { break; } $this->craftingType = 0; $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); $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($line, "UTF-8") > 16) { $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; default: break; } $timings->stopTiming(); }