diff --git a/plugin.yml b/plugin.yml index d1fa95e..0bf1aa4 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,5 +1,7 @@ +--- name: Boat main: onebone\boat\Main -version: "1.0" +version: 0.2.0-REVIVAL author: onebone -api: [1.12.0] +api: [3.0.0, 4.0.0] +... \ No newline at end of file diff --git a/src/onebone/boat/Main.php b/src/onebone/boat/Main.php index ca5947a..5ff57a8 100644 --- a/src/onebone/boat/Main.php +++ b/src/onebone/boat/Main.php @@ -2,89 +2,29 @@ namespace onebone\boat; -use pocketmine\plugin\PluginBase; -use pocketmine\event\Listener; -use pocketmine\inventory\BigShapelessRecipe; -use pocketmine\item\Item; -use pocketmine\entity\Entity; -use pocketmine\event\server\DataPacketReceiveEvent; -use pocketmine\network\protocol\InteractPacket; -use pocketmine\network\protocol\SetEntityLinkPacket; -use pocketmine\network\protocol\MovePlayerPacket; -use pocketmine\event\player\PlayerQuitEvent; - +use onebone\boat\entity\Boat as BoatEntity; use onebone\boat\item\Boat as BoatItem; -use onebone\boat\packet\PlayerInputPacket; -use onebone\boat\entity\Boat; - -class Main extends PluginBase implements Listener{ - private $riding = []; - - public function onEnable(){ - $this->getServer()->getPluginManager()->registerEvents($this, $this); - - Item::$list[333] = BoatItem::class; - Item::addCreativeItem(new Item(333)); - $this->getServer()->addRecipe((new BigShapelessRecipe(Item::get(333, 0, 1)))->addIngredient(Item::get(Item::WOODEN_PLANK, null, 5))->addIngredient(Item::get(Item::WOODEN_SHOVEL, null, 1))); - - Entity::registerEntity("\\onebone\\boat\\entity\\Boat", true); - - $this->getServer()->getNetwork()->registerPacket(0xae, PlayerInputPacket::class); - } - - public function onQuit(PlayerQuitEvent $event){ - if(isset($this->riding[$event->getPlayer()->getName()])){ - unset($this->riding[$event->getPlayer()->getName()]); - } - } - - public function onPacketReceived(DataPacketReceiveEvent $event){ - $packet = $event->getPacket(); - $player = $event->getPlayer(); - if($packet instanceof InteractPacket){ - $boat = $player->getLevel()->getEntity($packet->target); - if($boat instanceof Boat){ - if($packet->action === 1){ - $pk = new SetEntityLinkPacket(); - $pk->from = $boat->getId(); - $pk->to = $player->getId(); - $pk->type = 2; - - $this->getServer()->broadcastPacket($player->getLevel()->getPlayers(), $pk); - $pk = new SetEntityLinkPacket(); - $pk->from = $boat->getId(); - $pk->to = 0; - $pk->type = 2; - $player->dataPacket($pk); - - $this->riding[$player->getName()] = $packet->target; - }elseif($packet->action === 3){ - $pk = new SetEntityLinkPacket(); - $pk->from = $boat->getId(); - $pk->to = $player->getId(); - $pk->type = 3; - - $this->getServer()->broadcastPacket($player->getLevel()->getPlayers(), $pk); - $pk = new SetEntityLinkPacket(); - $pk->from = $boat->getId(); - $pk->to = 0; - $pk->type = 3; - $player->dataPacket($pk); +use onebone\boat\listener\EventListener; +use pocketmine\entity\Entity; +use pocketmine\inventory\ShapelessRecipe; +use pocketmine\item\Item; +use pocketmine\item\ItemFactory; +use pocketmine\plugin\PluginBase; - if(isset($this->riding[$event->getPlayer()->getName()])){ - unset($this->riding[$event->getPlayer()->getName()]); - } - } - } - }elseif($packet instanceof MovePlayerPacket){ - if(isset($this->riding[$player->getName()])){ - $boat = $player->getLevel()->getEntity($this->riding[$player->getName()]); - if($boat instanceof Boat){ - $boat->x = $packet->x; - $boat->y = $packet->y; - $boat->z = $packet->z; - } - } - } - } +class Main extends PluginBase{ + + public function onLoad(): void{ + Entity::registerEntity(BoatEntity::class, true); + ItemFactory::registerItem($item = new BoatItem(), true); + if (!Item::isCreativeItem($item)) + Item::addCreativeItem($item); + } + + public function onEnable() : void{ + $this->getServer()->getCraftingManager()->registerShapelessRecipe(new ShapelessRecipe([ + Item::get(Item::WOODEN_PLANKS, 0, 5), + Item::get(Item::WOODEN_SHOVEL, 0, 1) + ], [Item::get(Item::BOAT, 0, 1)])); + $this->getServer()->getPluginManager()->registerEvents(new EventListener(), $this); + } } diff --git a/src/onebone/boat/entity/Boat.php b/src/onebone/boat/entity/Boat.php index aec3a33..a8b1c70 100644 --- a/src/onebone/boat/entity/Boat.php +++ b/src/onebone/boat/entity/Boat.php @@ -2,64 +2,192 @@ namespace onebone\boat\entity; -use pocketmine\entity\Entity; -use pocketmine\network\protocol\AddEntityPacket; -use pocketmine\event\entity\EntityDeathEvent; +use onebone\boat\item\Boat as BoatItem; +use pocketmine\network\mcpe\protocol\AddActorPacket; use pocketmine\Player; +use pocketmine\Server; +use pocketmine\math\Vector3; +use pocketmine\block\Planks; +use pocketmine\entity\Entity; +use pocketmine\entity\Vehicle; +use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\entity\EntityDamageEvent; -use pocketmine\network\protocol\EntityEventPacket; -use pocketmine\item\Item; - -class Boat extends Entity{ - const NETWORK_ID = 90; - - public function spawnTo(Player $player){ - $pk = new AddEntityPacket(); - $pk->eid = $this->getId(); - $pk->type = self::NETWORK_ID; - $pk->x = $this->x; - $pk->y = $this->y; - $pk->z = $this->z; - $pk->speedX = 0; - $pk->speedY = 0; - $pk->speedZ = 0; - $pk->yaw = 0; - $pk->pitch = 0; - $pk->metadata = $this->dataProperties; - $player->dataPacket($pk); - - parent::spawnTo($player); - } - - public function attack($damage, EntityDamageEvent $source){ - parent::attack($damage, $source); - - if(!$source->isCancelled()){ - $pk = new EntityEventPacket(); - $pk->eid = $this->id; - $pk->event = EntityEventPacket::HURT_ANIMATION; - foreach($this->getLevel()->getPlayers() as $player){ - $player->dataPacket($pk); - } - } - } - - public function kill(){ - parent::kill(); +use pocketmine\event\entity\EntityRegainHealthEvent; +use pocketmine\network\mcpe\protocol\ActorEventPacket; +use pocketmine\network\mcpe\protocol\SetActorLinkPacket; +use pocketmine\network\mcpe\protocol\AnimatePacket; +use pocketmine\network\mcpe\protocol\types\EntityLink; + +class Boat extends Vehicle{ + + public const NETWORK_ID = self::BOAT; + + public const TAG_WOOD_ID = "WoodID"; + + public const ACTION_ROW_RIGHT = 128; + public const ACTION_ROW_LEFT = 129; + + public $height = 0.455; + + public $width = 1.4; + + /** @var float */ + public $gravity = 0.0; + /** @var float */ + public $drag = 0.1; + + public ?Entity $rider = null; + + + public function initEntity() : void{ + parent::initEntity(); + $woodId = $this->namedtag->getInt(self::TAG_WOOD_ID, Planks::OAK); + if($woodId > 5 || $woodId < 0){ + $woodId = Planks::OAK; + } + + $this->setWoodId($woodId); + $this->setMaxHealth(4); + $this->setGenericFlag(self::DATA_FLAG_STACKABLE, true); + } + + public function saveNBT() : void{ + parent::saveNBT(); + $this->namedtag->setInt(self::TAG_WOOD_ID, $this->getWoodId()); + } + + public function attack(EntityDamageEvent $source) : void{ + parent::attack($source); + + if(!$source->isCancelled()){ + $pk = new ActorEventPacket(); + $pk->entityRuntimeId = $this->id; + $pk->event = ActorEventPacket::HURT_ANIMATION; + Server::getInstance()->broadcastPacket($this->getViewers(), $pk); + } + } + + protected function sendSpawnPacket(Player $player) : void{ + $pk = new AddActorPacket(); + $pk->type = "minecraft:boat"; + $pk->entityRuntimeId = $this->getId(); + $pk->position = $this->getPosition(); + $pk->motion = $this->getMotion(); + $pk->attributes = $this->getAttributeMap()->getAll(); + $pk->metadata = $this->getDataPropertyManager()->getAll(); + if ($this->rider !== null) { + $pk->links[] = new EntityLink($this->getId(), $this->rider->getId(), EntityLink::TYPE_RIDER, true, true); + } + + $player->sendDataPacket($pk); + } + + public function kill() : void{ + parent::kill(); + + if($this->lastDamageCause instanceof EntityDamageByEntityEvent){ + $damager = $this->lastDamageCause->getDamager(); + if($damager instanceof Player and $damager->isCreative()){ + return; + } + } foreach($this->getDrops() as $item){ $this->getLevel()->dropItem($this, $item); } - } - - public function getDrops(){ - return [ - Item::get(333, 0, 1) - ]; - } - - public function getSaveId(){ - $class = new \ReflectionClass(static::class); - return $class->getShortName(); - } + } + + public function onUpdate(int $currentTick) : bool{ + if($this->closed){ + return false; + } + + if($this->getHealth() < $this->getMaxHealth() && $currentTick % 10 === 0){ + $this->heal(new EntityRegainHealthEvent($this, 1, EntityRegainHealthEvent::CAUSE_REGEN)); + } + + return parent::onUpdate($currentTick); + } + + public function getDrops() : array{ + return [ + new BoatItem($this->getWoodId()) + ]; + } + + public function getWoodId() : int{ + return $this->propertyManager->getInt(self::DATA_VARIANT); + } + + public function setWoodId(int $woodId) : void{ + $this->propertyManager->setInt(self::DATA_VARIANT, $woodId); + } + + public function canLink(Entity $rider) : bool{ + return $this->rider === null; + } + + public function link(Entity $rider) : bool{ + if($this->rider === null){ + $rider->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_RIDING, true); + $rider->getDataPropertyManager()->setVector3(Entity::DATA_RIDER_SEAT_POSITION, new Vector3(0, 1, 0)); + $rider->getDataPropertyManager()->setByte(self::DATA_RIDER_ROTATION_LOCKED, true); + $rider->getDataPropertyManager()->setFloat(self::DATA_RIDER_MAX_ROTATION, 90); + $rider->getDataPropertyManager()->setFloat(self::DATA_RIDER_MIN_ROTATION, -90); + + $pk = new SetActorLinkPacket(); + $pk->link = new EntityLink($this->getId(), $rider->getId(), EntityLink::TYPE_RIDER, true, true); + Server::getInstance()->broadcastPacket($this->getViewers(), $pk); + + $this->rider = $rider; + return true; + } + + return false; + } + + public function unlink(Entity $rider) : bool{ + if($this->rider === $rider){ + $rider->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_RIDING, false); + $rider->getDataPropertyManager()->setVector3(Entity::DATA_RIDER_SEAT_POSITION, new Vector3(0, 0, 0)); + $rider->getDataPropertyManager()->setByte(self::DATA_RIDER_ROTATION_LOCKED, false); + + $pk = new SetActorLinkPacket(); + $pk->link = new EntityLink($this->getId(), $rider->getId(), EntityLink::TYPE_REMOVE, true, true); + Server::getInstance()->broadcastPacket($this->getViewers(), $pk); + + $this->rider = null; + return true; + } + + return false; + } + + public function absoluteMove(Vector3 $pos, float $yaw = 0, float $pitch = 0) : void{ + $this->setComponents($pos->x, $pos->y, $pos->z); + $this->setRotation($yaw, $pitch); + $this->updateMovement(); + } + + public function handleAnimatePacket(AnimatePacket $packet) : void{ + if($this->rider !== null){ + switch($packet->action){ + case self::ACTION_ROW_RIGHT: + $this->propertyManager->setFloat(self::DATA_PADDLE_TIME_RIGHT, $packet->float); + break; + + case self::ACTION_ROW_LEFT: + $this->propertyManager->setFloat(self::DATA_PADDLE_TIME_LEFT, $packet->float); + break; + } + } + } + + public function getRider() : ?Entity{ + return $this->rider; + } + + public function isRider(Entity $rider) : bool{ + return $this->rider === $rider; + } } diff --git a/src/onebone/boat/item/Boat.php b/src/onebone/boat/item/Boat.php index 2b4c2ec..9ec737a 100644 --- a/src/onebone/boat/item/Boat.php +++ b/src/onebone/boat/item/Boat.php @@ -2,56 +2,42 @@ namespace onebone\boat\item; -use pocketmine\item\Item; -use pocketmine\level\Level; -use pocketmine\block\Block; +use onebone\boat\entity\Boat as BoatEntity; +use pocketmine\block\{ + Block, Planks +}; +use pocketmine\item\Boat as BoatItemPM; +use pocketmine\math\Vector3; use pocketmine\Player; -use pocketmine\nbt\tag\Compound; -use pocketmine\nbt\tag\Enum; -use pocketmine\nbt\tag\Double; -use pocketmine\nbt\tag\Float; -use onebone\boat\entity\Boat as BoatEntity; +class Boat extends BoatItemPM{ -class Boat extends Item{ - public function __construct($meta = 0, $count = 1){ - parent::__construct(333, $meta, $count, "Boat"); + public function __construct(int $meta = 0){ + parent::__construct($meta); + $this->name = $this->getVanillaName(); } - public function canBeActivated(){ - return true; - } - - public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){ - $realPos = $block->getSide($face); - - $boat = new BoatEntity($player->getLevel()->getChunk($realPos->getX() >> 4, $realPos->getZ() >> 4), new Compound("", [ - "Pos" => new Enum("Pos", [ - new Double("", $realPos->getX()), - new Double("", $realPos->getY()), - new Double("", $realPos->getZ()) - ]), - "Motion" => new Enum("Motion", [ - new Double("", 0), - new Double("", 0), - new Double("", 0) - ]), - "Rotation" => new Enum("Rotation", [ - new Float("", 0), - new Float("", 0) - ]), - ])); - $boat->spawnToAll(); + public function getVanillaName() : string{ + static $names = [ + Planks::OAK => "%item.boat.oak.name", + Planks::SPRUCE => "%item.boat.spruce.name", + Planks::BIRCH => "%item.boat.birch.name", + Planks::JUNGLE => "%item.boat.jungle.name", + Planks::ACACIA => "%item.boat.acacia.name", + Planks::DARK_OAK => "%item.boat.dark_oak.name", + ]; + return $names[$this->meta] ?? "Boat"; + } - $item = $player->getInventory()->getItemInHand(); - $count = $item->getCount(); - if(--$count <= 0){ - $player->getInventory()->setItemInHand(Item::get(Item::AIR)); - return; - } + public function onActivate(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : bool{ + if ($player->isSurvival()) { + $player->getInventory()->setItemInHand($this->pop()); + } - $item->setCount($count); - $player->getInventory()->setItemInHand($item); - return true; - } + $nbt = BoatEntity::createBaseNBT($blockClicked->getSide($face)->add(0.5, 0.5, 0.5)); + $nbt->setInt(BoatEntity::TAG_WOOD_ID, $this->meta); + $boat = new BoatEntity($player->getLevel(), $nbt); + $boat->spawnToAll(); + return true; + } } diff --git a/src/onebone/boat/listener/EventListener.php b/src/onebone/boat/listener/EventListener.php new file mode 100644 index 0000000..58df0ba --- /dev/null +++ b/src/onebone/boat/listener/EventListener.php @@ -0,0 +1,88 @@ +getPlayer(); + if (!$player->getDataFlag(Entity::DATA_FLAGS, Entity::DATA_FLAG_RIDING)) + return; + + foreach($player->getLevel()->getNearbyEntities($player->getBoundingBox()->expand(2, 2, 2), $player) as $key => $entity) { + if ($entity instanceof BoatEntity && $entity->unlink($player)) + return; + } + } + + /** @priority HIGHEST */ + public function onDataPacketReceiveEvent(DataPacketReceiveEvent $event) : void{ + $packet = $event->getPacket(); + $player = $event->getPlayer(); + if ($packet instanceof InventoryTransactionPacket) { + if ($packet->trData->getTypeId() !== InventoryTransactionPacket::TYPE_USE_ITEM_ON_ENTITY) + return; + + $entity = $player->getLevel()->getEntity($packet->trData->getEntityRuntimeId()); + if (!$entity instanceof BoatEntity) + return; + + if ($packet->trData->getActionType() !== UseItemOnEntityTransactionData::ACTION_INTERACT) + return; + + if($entity->canLink($player)){ + $entity->link($player); + } + + $event->setCancelled(); + } else if ($packet instanceof InteractPacket) { + $entity = $player->getLevel()->getEntity($packet->target); + if (!$entity instanceof BoatEntity) + return; + + if ($packet->action === InteractPacket::ACTION_LEAVE_VEHICLE && $entity->isRider($player)){ + $entity->unlink($player); + } + + $event->setCancelled(); + } else if ($packet instanceof MoveActorAbsolutePacket) { + $entity = $player->getLevel()->getEntity($packet->entityRuntimeId); + if ($entity instanceof BoatEntity && $entity->isRider($player)) { + $entity->absoluteMove($packet->position, $packet->xRot, $packet->zRot); + $event->setCancelled(); + } + } else if ($packet instanceof AnimatePacket) { + foreach ($player->getLevel()->getEntities() as $entity) { + if ($entity instanceof BoatEntity && $entity->isRider($player)){ + switch ($packet->action) { + case BoatEntity::ACTION_ROW_RIGHT: + case BoatEntity::ACTION_ROW_LEFT: + $entity->handleAnimatePacket($packet); + $event->setCancelled(); + break; + } + break; + } + } + } else if ($packet instanceof PlayerInputPacket or $packet instanceof SetActorMotionPacket) { + if (!$player->getDataFlag(Entity::DATA_FLAGS, Entity::DATA_FLAG_RIDING)) + return; + + $event->setCancelled(); + } + } +} diff --git a/src/onebone/boat/packet/PlayerInputPacket.php b/src/onebone/boat/packet/PlayerInputPacket.php deleted file mode 100644 index 7633c90..0000000 --- a/src/onebone/boat/packet/PlayerInputPacket.php +++ /dev/null @@ -1,47 +0,0 @@ -motX = $this->getFloat(); - $this->motY = $this->getFloat(); - $flags = $this->getByte(); - $this->jumping = (($flags & 0x80) > 0); - $this->sneaking = (($flags & 0x40) > 0); - } - - public function encode(){ - - } - -}