Merge 'minor-next' into 'major-next'

Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11865975725
This commit is contained in:
pmmp-restrictedactions-bot[bot]
2024-11-16 01:26:32 +00:00
14 changed files with 218 additions and 34 deletions

View File

@@ -35,6 +35,7 @@ use pocketmine\event\entity\EntityMotionEvent;
use pocketmine\event\entity\EntityRegainHealthEvent;
use pocketmine\event\entity\EntitySpawnEvent;
use pocketmine\event\entity\EntityTeleportEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector2;
@@ -1560,6 +1561,13 @@ abstract class Entity{
$this->hasSpawned = [];
}
/**
* Returns the item that players will equip when middle-clicking on this entity.
*/
public function getPickedItem() : ?Item{
return null;
}
/**
* Flags the entity to be removed from the world on the next tick.
*/

View File

@@ -26,6 +26,7 @@ namespace pocketmine\entity;
use pocketmine\entity\animation\SquidInkCloudAnimation;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
@@ -124,4 +125,8 @@ class Squid extends WaterAnimal{
VanillaItems::INK_SAC()->setCount(mt_rand(1, 3))
];
}
public function getPickedItem() : ?Item{
return VanillaItems::SQUID_SPAWN_EGG();
}
}

View File

@@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\entity;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
@@ -87,6 +89,10 @@ class Villager extends Living implements Ageable{
return $this->baby;
}
public function getPickedItem() : ?Item{
return VanillaItems::VILLAGER_SPAWN_EGG();
}
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);
$properties->setGenericFlag(EntityMetadataFlags::BABY, $this->baby);

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\entity;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use function mt_rand;
@@ -65,4 +66,8 @@ class Zombie extends Living{
//TODO: check for equipment and whether it's a baby
return 5;
}
public function getPickedItem() : ?Item{
return VanillaItems::ZOMBIE_SPAWN_EGG();
}
}

View File

@@ -35,6 +35,7 @@ use pocketmine\entity\Location;
use pocketmine\event\entity\EntityBlockChangeEvent;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
@@ -194,6 +195,10 @@ class FallingBlock extends Entity{
return $nbt;
}
public function getPickedItem() : ?Item{
return $this->block->asItem();
}
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);

View File

@@ -28,6 +28,7 @@ use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Location;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
@@ -165,6 +166,10 @@ class Painting extends Entity{
));
}
public function getPickedItem() : ?Item{
return VanillaItems::PAINTING();
}
/**
* Returns the painting motive (which image is displayed on the painting)
*/

View File

@@ -23,11 +23,13 @@ declare(strict_types=1);
namespace pocketmine\entity\object;
use pocketmine\block\VanillaBlocks;
use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Explosive;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\entity\EntityPreExplodeEvent;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
@@ -127,6 +129,10 @@ class PrimedTNT extends Entity implements Explosive{
}
}
public function getPickedItem() : ?Item{
return VanillaBlocks::TNT()->setWorksUnderwater($this->worksUnderwater)->asItem();
}
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);

View File

@@ -0,0 +1,53 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\item\Item;
use pocketmine\player\Player;
/**
* Called when a player middle-clicks on an entity to get an item in creative mode.
*/
class PlayerEntityPickEvent extends PlayerEvent implements Cancellable{
use CancellableTrait;
public function __construct(
Player $player,
private Entity $entityClicked,
private Item $resultItem
){
$this->player = $player;
}
public function getEntity() : Entity{
return $this->entityClicked;
}
public function getResultItem() : Item{
return $this->resultItem;
}
}

View File

@@ -42,6 +42,9 @@ class PlayerInteractEvent extends PlayerEvent implements Cancellable{
protected Vector3 $touchVector;
protected bool $useItem = true;
protected bool $useBlock = true;
public function __construct(
Player $player,
protected Item $item,
@@ -73,4 +76,28 @@ class PlayerInteractEvent extends PlayerEvent implements Cancellable{
public function getFace() : int{
return $this->blockFace;
}
/**
* Returns whether the item may react to the interaction. If disabled, items such as spawn eggs will not activate.
* This does NOT prevent blocks from being placed - it makes the item behave as if the player is sneaking.
*/
public function useItem() : bool{ return $this->useItem; }
/**
* Sets whether the used item may react to the interaction. If false, items such as spawn eggs will not activate.
* This does NOT prevent blocks from being placed - it makes the item behave as if the player is sneaking.
*/
public function setUseItem(bool $useItem) : void{ $this->useItem = $useItem; }
/**
* Returns whether the block may react to the interaction. If false, doors, fence gates and trapdoors will not
* respond, containers will not open, etc.
*/
public function useBlock() : bool{ return $this->useBlock; }
/**
* Sets whether the block may react to the interaction. If false, doors, fence gates and trapdoors will not
* respond, containers will not open, etc.
*/
public function setUseBlock(bool $useBlock) : void{ $this->useBlock = $useBlock; }
}

View File

@@ -493,15 +493,18 @@ class InGamePacketHandler extends PacketHandler{
$blockPos = $data->getBlockPosition();
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
if(!$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos)){
$this->onFailedBlockAction($vBlockPos, $data->getFace());
}
$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos);
//always sync this in case plugins caused a different result than the client expected
//we *could* try to enhance detection of plugin-altered behaviour, but this would require propagating
//more information up the stack. For now I think this is good enough.
//if only the client would tell us what blocks it thinks changed...
$this->syncBlocksNearby($vBlockPos, $data->getFace());
return true;
case UseItemTransactionData::ACTION_BREAK_BLOCK:
$blockPos = $data->getBlockPosition();
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
if(!$this->player->breakBlock($vBlockPos)){
$this->onFailedBlockAction($vBlockPos, null);
$this->syncBlocksNearby($vBlockPos, null);
}
return true;
case UseItemTransactionData::ACTION_CLICK_AIR:
@@ -529,9 +532,9 @@ class InGamePacketHandler extends PacketHandler{
}
/**
* Internal function used to execute rollbacks when an action fails on a block.
* Syncs blocks nearby to ensure that the client and server agree on the world's blocks after a block interaction.
*/
private function onFailedBlockAction(Vector3 $blockPos, ?int $face) : void{
private function syncBlocksNearby(Vector3 $blockPos, ?int $face) : void{
if($blockPos->distanceSquared($this->player->getLocation()) < 10000){
$blocks = $blockPos->sidesArray();
if($face !== null){
@@ -668,7 +671,7 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleActorPickRequest(ActorPickRequestPacket $packet) : bool{
return false; //TODO
return $this->player->pickEntity($packet->actorUniqueId);
}
public function handlePlayerAction(PlayerActionPacket $packet) : bool{
@@ -682,7 +685,7 @@ class InGamePacketHandler extends PacketHandler{
case PlayerAction::START_BREAK:
self::validateFacing($face);
if(!$this->player->attackBlock($pos, $face)){
$this->onFailedBlockAction($pos, $face);
$this->syncBlocksNearby($pos, $face);
}
break;
@@ -998,7 +1001,7 @@ class InGamePacketHandler extends PacketHandler{
$lectern = $world->getBlockAt($pos->getX(), $pos->getY(), $pos->getZ());
if($lectern instanceof Lectern && $this->player->canInteract($lectern->getPosition(), 15)){
if(!$lectern->onPageTurn($packet->page)){
$this->onFailedBlockAction($lectern->getPosition(), null);
$this->syncBlocksNearby($lectern->getPosition(), null);
}
return true;
}

View File

@@ -58,6 +58,7 @@ use pocketmine\event\player\PlayerDisplayNameChangeEvent;
use pocketmine\event\player\PlayerDropItemEvent;
use pocketmine\event\player\PlayerEmoteEvent;
use pocketmine\event\player\PlayerEntityInteractEvent;
use pocketmine\event\player\PlayerEntityPickEvent;
use pocketmine\event\player\PlayerExhaustEvent;
use pocketmine\event\player\PlayerGameModeChangeEvent;
use pocketmine\event\player\PlayerInteractEvent;
@@ -1714,29 +1715,58 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$ev->call();
if(!$ev->isCancelled()){
if($existingSlot !== -1){
if($existingSlot < $this->inventory->getHotbarSize()){
$this->inventory->setHeldItemIndex($existingSlot);
}else{
$this->inventory->swap($this->inventory->getHeldItemIndex(), $existingSlot);
}
}else{
$firstEmpty = $this->inventory->firstEmpty();
if($firstEmpty === -1){ //full inventory
$this->inventory->setItemInHand($item);
}elseif($firstEmpty < $this->inventory->getHotbarSize()){
$this->inventory->setItem($firstEmpty, $item);
$this->inventory->setHeldItemIndex($firstEmpty);
}else{
$this->inventory->swap($this->inventory->getHeldItemIndex(), $firstEmpty);
$this->inventory->setItemInHand($item);
}
}
$this->equipOrAddPickedItem($existingSlot, $item);
}
return true;
}
public function pickEntity(int $entityId) : bool{
$entity = $this->getWorld()->getEntity($entityId);
if($entity === null){
return true;
}
$item = $entity->getPickedItem();
if($item === null){
return true;
}
$ev = new PlayerEntityPickEvent($this, $entity, $item);
$existingSlot = $this->inventory->first($item);
if($existingSlot === -1 && ($this->hasFiniteResources() || $this->isSpectator())){
$ev->cancel();
}
$ev->call();
if(!$ev->isCancelled()){
$this->equipOrAddPickedItem($existingSlot, $item);
}
return true;
}
private function equipOrAddPickedItem(int $existingSlot, Item $item) : void{
if($existingSlot !== -1){
if($existingSlot < $this->inventory->getHotbarSize()){
$this->inventory->setHeldItemIndex($existingSlot);
}else{
$this->inventory->swap($this->inventory->getHeldItemIndex(), $existingSlot);
}
}else{
$firstEmpty = $this->inventory->firstEmpty();
if($firstEmpty === -1){ //full inventory
$this->inventory->setItemInHand($item);
}elseif($firstEmpty < $this->inventory->getHotbarSize()){
$this->inventory->setItem($firstEmpty, $item);
$this->inventory->setHeldItemIndex($firstEmpty);
}else{
$this->inventory->swap($this->inventory->getHeldItemIndex(), $firstEmpty);
$this->inventory->setItemInHand($item);
}
}
}
/**
* Performs a left-click (attack) action on the block.
*

View File

@@ -2173,19 +2173,25 @@ class World implements ChunkManager{
if($player !== null){
$ev = new PlayerInteractEvent($player, $item, $blockClicked, $clickVector, $face, PlayerInteractEvent::RIGHT_CLICK_BLOCK);
if($player->isSneaking()){
$ev->setUseItem(false);
$ev->setUseBlock($item->isNull()); //opening doors is still possible when sneaking if using an empty hand
}
if($player->isSpectator()){
$ev->cancel(); //set it to cancelled so plugins can bypass this
}
$ev->call();
if(!$ev->isCancelled()){
if((!$player->isSneaking() || $item->isNull()) && $blockClicked->onInteract($item, $face, $clickVector, $player, $returnedItems)){
if($ev->useBlock() && $blockClicked->onInteract($item, $face, $clickVector, $player, $returnedItems)){
return true;
}
$result = $item->onInteractBlock($player, $blockReplace, $blockClicked, $face, $clickVector, $returnedItems);
if($result !== ItemUseResult::NONE){
return $result === ItemUseResult::SUCCESS;
if($ev->useItem()){
$result = $item->onInteractBlock($player, $blockReplace, $blockClicked, $face, $clickVector, $returnedItems);
if($result !== ItemUseResult::NONE){
return $result === ItemUseResult::SUCCESS;
}
}
}else{
return false;