From 10a962daa2aed61915fcc73e0a9434efbcf0684f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 16 Jan 2023 22:12:07 +0000 Subject: [PATCH] First look at #5512: gameplay permissions --- src/entity/object/ItemEntity.php | 3 +- src/entity/projectile/Arrow.php | 3 +- .../transaction/action/CreateItemAction.php | 1 + .../transaction/action/DropItemAction.php | 3 +- src/network/mcpe/NetworkSession.php | 14 ++--- src/permission/DefaultPermissionNames.php | 17 ++++++ src/permission/DefaultPermissions.php | 21 ++++++++ src/player/GameMode.php | 12 +++-- src/player/Player.php | 53 ++++++++++++------- src/world/World.php | 7 +-- 10 files changed, 98 insertions(+), 36 deletions(-) diff --git a/src/entity/object/ItemEntity.php b/src/entity/object/ItemEntity.php index 4a58620f75..76336d84ef 100644 --- a/src/entity/object/ItemEntity.php +++ b/src/entity/object/ItemEntity.php @@ -38,6 +38,7 @@ use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\protocol\AddItemActorPacket; use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper; +use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; use function max; @@ -311,7 +312,7 @@ class ItemEntity extends Entity{ }; $ev = new EntityItemPickupEvent($player, $this, $item, $playerInventory); - if($player->hasFiniteResources() && $playerInventory === null){ + if(($player->hasFiniteResources() && $playerInventory === null) || !$player->hasPermission(DefaultPermissionNames::GAME_ITEM_PICKUP)){ $ev->cancel(); } diff --git a/src/entity/projectile/Arrow.php b/src/entity/projectile/Arrow.php index 0d284d496d..3c5d356d2c 100644 --- a/src/entity/projectile/Arrow.php +++ b/src/entity/projectile/Arrow.php @@ -36,6 +36,7 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags; +use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; use pocketmine\world\sound\ArrowHitSound; use function ceil; @@ -184,7 +185,7 @@ class Arrow extends Projectile{ }; $ev = new EntityItemPickupEvent($player, $this, $item, $playerInventory); - if($player->hasFiniteResources() && $playerInventory === null){ + if(($player->hasFiniteResources() && $playerInventory === null) || !$player->hasPermission(DefaultPermissionNames::GAME_ITEM_PICKUP)){ $ev->cancel(); } if($this->pickupMode === self::PICKUP_NONE || ($this->pickupMode === self::PICKUP_CREATIVE && !$player->isCreative())){ diff --git a/src/inventory/transaction/action/CreateItemAction.php b/src/inventory/transaction/action/CreateItemAction.php index 99605bf963..82386b0bde 100644 --- a/src/inventory/transaction/action/CreateItemAction.php +++ b/src/inventory/transaction/action/CreateItemAction.php @@ -27,6 +27,7 @@ use pocketmine\inventory\CreativeInventory; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; use pocketmine\item\VanillaItems; +use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; /** diff --git a/src/inventory/transaction/action/DropItemAction.php b/src/inventory/transaction/action/DropItemAction.php index 636317fcf7..a7582ed1dc 100644 --- a/src/inventory/transaction/action/DropItemAction.php +++ b/src/inventory/transaction/action/DropItemAction.php @@ -27,6 +27,7 @@ use pocketmine\event\player\PlayerDropItemEvent; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; use pocketmine\item\VanillaItems; +use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; /** @@ -49,7 +50,7 @@ class DropItemAction extends InventoryAction{ public function onPreExecute(Player $source) : bool{ $ev = new PlayerDropItemEvent($source, $this->targetItem); - if($source->isSpectator()){ + if(!$source->hasPermission(DefaultPermissionNames::GAME_ITEM_DROP)){ $ev->cancel(); } $ev->call(); diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 49f87793fb..ba0b351688 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -854,16 +854,16 @@ class NetworkSession{ UpdateAbilitiesPacketLayer::ABILITY_OPERATOR => $isOp, UpdateAbilitiesPacketLayer::ABILITY_TELEPORT => $for->hasPermission(DefaultPermissionNames::COMMAND_TELEPORT_SELF), UpdateAbilitiesPacketLayer::ABILITY_INVULNERABLE => $for->isCreative(), - UpdateAbilitiesPacketLayer::ABILITY_MUTED => false, + UpdateAbilitiesPacketLayer::ABILITY_MUTED => !$for->hasPermission(DefaultPermissionNames::GAME_CHAT), UpdateAbilitiesPacketLayer::ABILITY_WORLD_BUILDER => false, UpdateAbilitiesPacketLayer::ABILITY_INFINITE_RESOURCES => !$for->hasFiniteResources(), UpdateAbilitiesPacketLayer::ABILITY_LIGHTNING => false, - UpdateAbilitiesPacketLayer::ABILITY_BUILD => !$for->isSpectator(), - UpdateAbilitiesPacketLayer::ABILITY_MINE => !$for->isSpectator(), - UpdateAbilitiesPacketLayer::ABILITY_DOORS_AND_SWITCHES => !$for->isSpectator(), - UpdateAbilitiesPacketLayer::ABILITY_OPEN_CONTAINERS => !$for->isSpectator(), - UpdateAbilitiesPacketLayer::ABILITY_ATTACK_PLAYERS => !$for->isSpectator(), - UpdateAbilitiesPacketLayer::ABILITY_ATTACK_MOBS => !$for->isSpectator(), + UpdateAbilitiesPacketLayer::ABILITY_BUILD => $for->hasPermission(DefaultPermissionNames::GAME_BLOCK_PLACE), + UpdateAbilitiesPacketLayer::ABILITY_MINE => $for->hasPermission(DefaultPermissionNames::GAME_BLOCK_BREAK), + UpdateAbilitiesPacketLayer::ABILITY_DOORS_AND_SWITCHES => $for->hasPermission(DefaultPermissionNames::GAME_BLOCK_INTERACT), + UpdateAbilitiesPacketLayer::ABILITY_OPEN_CONTAINERS => $for->hasPermission(DefaultPermissionNames::GAME_BLOCK_INTERACT) || $for->hasPermission(DefaultPermissionNames::GAME_ENTITY_INTERACT), //not perfect, but this is a pain to implement right now + UpdateAbilitiesPacketLayer::ABILITY_ATTACK_PLAYERS => $for->hasPermission(DefaultPermissionNames::GAME_PLAYER_ATTACK), + UpdateAbilitiesPacketLayer::ABILITY_ATTACK_MOBS => $for->hasPermission(DefaultPermissionNames::GAME_ENTITY_ATTACK), ]; $this->sendDataPacket(UpdateAbilitiesPacket::create( diff --git a/src/permission/DefaultPermissionNames.php b/src/permission/DefaultPermissionNames.php index 6dc86c9ae0..7ea4b306d4 100644 --- a/src/permission/DefaultPermissionNames.php +++ b/src/permission/DefaultPermissionNames.php @@ -91,7 +91,24 @@ final class DefaultPermissionNames{ public const COMMAND_WHITELIST_LIST = "pocketmine.command.whitelist.list"; public const COMMAND_WHITELIST_RELOAD = "pocketmine.command.whitelist.reload"; public const COMMAND_WHITELIST_REMOVE = "pocketmine.command.whitelist.remove"; + public const GAME_BLOCK_BREAK = "pocketmine.game.block.break"; + public const GAME_BLOCK_INTERACT = "pocketmine.game.block.interact"; + public const GAME_BLOCK_PLACE = "pocketmine.game.block.place"; + public const GAME_CHAT = "pocketmine.game.chat"; + public const GAME_ENTITY_ATTACK = "pocketmine.game.entity.attack"; + public const GAME_ENTITY_INTERACT = "pocketmine.game.entity.interact"; + public const GAME_FLIGHT = "pocketmine.game.flight"; + public const GAME_ITEM_CREATE = "pocketmine.game.item.create"; + public const GAME_ITEM_DROP = "pocketmine.game.item.drop"; + public const GAME_ITEM_PICKUP = "pocketmine.game.item.pickup"; + public const GAME_ITEM_USE = "pocketmine.game.item.use"; + public const GAME_PLAYER_ATTACK = "pocketmine.game.player.attack"; + public const GAME_PLAYER_INTERACT = "pocketmine.game.player.interact"; public const GROUP_CONSOLE = "pocketmine.group.console"; + public const GROUP_GAMEMODE_ADVENTURE = "pocketmine.group.gamemode.adventure"; + public const GROUP_GAMEMODE_CREATIVE = "pocketmine.group.gamemode.creative"; + public const GROUP_GAMEMODE_SPECTATOR = "pocketmine.group.gamemode.spectator"; + public const GROUP_GAMEMODE_SURVIVAL = "pocketmine.group.gamemode.survival"; public const GROUP_OPERATOR = "pocketmine.group.operator"; public const GROUP_USER = "pocketmine.group.user"; } diff --git a/src/permission/DefaultPermissions.php b/src/permission/DefaultPermissions.php index be68ebd3f8..c951e2de04 100644 --- a/src/permission/DefaultPermissions.php +++ b/src/permission/DefaultPermissions.php @@ -136,5 +136,26 @@ abstract class DefaultPermissions{ self::registerPermission(new Permission(Names::COMMAND_WHITELIST_LIST, "Allows the user to list all players on the server whitelist"), [$operatorRoot]); self::registerPermission(new Permission(Names::COMMAND_WHITELIST_RELOAD, "Allows the user to reload the server whitelist"), [$operatorRoot]); self::registerPermission(new Permission(Names::COMMAND_WHITELIST_REMOVE, "Allows the user to remove a player from the server whitelist"), [$operatorRoot]); + + self::registerPermission(new Permission(Names::GAME_CHAT, "Allows the user to chat"), [$everyoneRoot]); + + $survivalRoot = self::registerPermission(new Permission(Names::GROUP_GAMEMODE_SURVIVAL)); + $creativeRoot = self::registerPermission(new Permission(Names::GROUP_GAMEMODE_CREATIVE)); + $adventureRoot = self::registerPermission(new Permission(Names::GROUP_GAMEMODE_ADVENTURE)); + self::registerPermission(new Permission(Names::GROUP_GAMEMODE_SPECTATOR)); //not currently used, but will be in the future + + self::registerPermission(new Permission(Names::GAME_BLOCK_BREAK, "Allows the user to break blocks"), [$survivalRoot, $creativeRoot, $adventureRoot]); + self::registerPermission(new Permission(Names::GAME_BLOCK_INTERACT, "Allows the user to interact with blocks"), [$survivalRoot, $creativeRoot, $adventureRoot]); + self::registerPermission(new Permission(Names::GAME_BLOCK_PLACE, "Allows the user to place blocks"), [$survivalRoot, $creativeRoot, $adventureRoot]); + self::registerPermission(new Permission(Names::GAME_ENTITY_ATTACK, "Allows the user to attack entities"), [$survivalRoot, $creativeRoot, $adventureRoot]); + self::registerPermission(new Permission(Names::GAME_ENTITY_INTERACT, "Allows the user to interact with entities"), [$survivalRoot, $creativeRoot, $adventureRoot]); + self::registerPermission(new Permission(Names::GAME_ITEM_DROP, "Allows the user to drop items"), [$survivalRoot, $creativeRoot, $adventureRoot]); + self::registerPermission(new Permission(Names::GAME_ITEM_PICKUP, "Allows the user to pick up items"), [$survivalRoot, $creativeRoot, $adventureRoot]); + self::registerPermission(new Permission(Names::GAME_ITEM_USE, "Allows the user to use items such as snowballs"), [$survivalRoot, $creativeRoot, $adventureRoot]); + self::registerPermission(new Permission(Names::GAME_PLAYER_ATTACK, "Allows the user to attack other players"), [$survivalRoot, $creativeRoot, $adventureRoot]); + self::registerPermission(new Permission(Names::GAME_PLAYER_INTERACT, "Allows the user to interact with other players"), [$survivalRoot, $creativeRoot, $adventureRoot]); + + self::registerPermission(new Permission(Names::GAME_ITEM_CREATE, "Allows the user to use the creative inventory"), [$creativeRoot]); + self::registerPermission(new Permission(Names::GAME_FLIGHT, "Allows the user to toggle flight mode"), [$creativeRoot]); } } diff --git a/src/player/GameMode.php b/src/player/GameMode.php index eb16d74ac6..f41067921a 100644 --- a/src/player/GameMode.php +++ b/src/player/GameMode.php @@ -25,6 +25,7 @@ namespace pocketmine\player; use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\Translatable; +use pocketmine\permission\DefaultPermissionNames; use pocketmine\utils\EnumTrait; use function mb_strtolower; @@ -50,10 +51,10 @@ final class GameMode{ protected static function setup() : void{ self::registerAll( - new self("survival", "Survival", KnownTranslationFactory::gameMode_survival(), ["survival", "s", "0"]), - new self("creative", "Creative", KnownTranslationFactory::gameMode_creative(), ["creative", "c", "1"]), - new self("adventure", "Adventure", KnownTranslationFactory::gameMode_adventure(), ["adventure", "a", "2"]), - new self("spectator", "Spectator", KnownTranslationFactory::gameMode_spectator(), ["spectator", "v", "view", "3"]) + new self("survival", "Survival", KnownTranslationFactory::gameMode_survival(), DefaultPermissionNames::GROUP_GAMEMODE_SURVIVAL, ["survival", "s", "0"]), + new self("creative", "Creative", KnownTranslationFactory::gameMode_creative(), DefaultPermissionNames::GROUP_GAMEMODE_CREATIVE, ["creative", "c", "1"]), + new self("adventure", "Adventure", KnownTranslationFactory::gameMode_adventure(), DefaultPermissionNames::GROUP_GAMEMODE_ADVENTURE, ["adventure", "a", "2"]), + new self("spectator", "Spectator", KnownTranslationFactory::gameMode_spectator(), DefaultPermissionNames::GROUP_GAMEMODE_SPECTATOR, ["spectator", "v", "view", "3"]) ); } @@ -76,6 +77,7 @@ final class GameMode{ string $enumName, private string $englishName, private Translatable $translatableName, + private string $permissionGroupName, private array $aliases = [] ){ $this->Enum___construct($enumName); @@ -87,6 +89,8 @@ final class GameMode{ public function getTranslatableName() : Translatable{ return $this->translatableName; } + public function getPermissionGroupName() : string{ return $this->permissionGroupName; } + /** * @return string[] */ diff --git a/src/player/Player.php b/src/player/Player.php index 8d9afcbc15..3b899cb8fd 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -261,7 +261,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ //TODO: Abilities protected bool $autoJump = true; - protected bool $allowFlight = false; protected bool $blockCollision = true; protected bool $flying = false; @@ -436,12 +435,16 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ * * Note: Setting this to false DOES NOT change whether the player is currently flying. Use * {@link Player::setFlying()} for that purpose. + * + * Note: As of 4.13, this will override any game mode flight restrictions for the duration of the game session. + * This differs from previous behaviour, where the flag would get overwritten by game mode changes. + * + * @deprecated This is now controlled by setting a permission, which allows more fine-tuned control. + * @see DefaultPermissionNames::GAME_FLIGHT */ public function setAllowFlight(bool $value) : void{ - if($this->allowFlight !== $value){ - $this->allowFlight = $value; - $this->getNetworkSession()->syncAbilities($this); - } + $this->setBasePermission(DefaultPermissionNames::GAME_FLIGHT, $value); + $this->getNetworkSession()->syncAbilities($this); } /** @@ -451,7 +454,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ * enter or exit flight mode will be prevented. */ public function getAllowFlight() : bool{ - return $this->allowFlight; + return $this->hasPermission(DefaultPermissionNames::GAME_FLIGHT); } /** @@ -1056,9 +1059,13 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ } protected function internalSetGameMode(GameMode $gameMode) : void{ + if(isset($this->gamemode)){ + $this->unsetBasePermission($this->gamemode->getPermissionGroupName()); + } $this->gamemode = $gameMode; - $this->allowFlight = $this->gamemode->equals(GameMode::CREATIVE()); + $this->setBasePermission($this->gamemode->getPermissionGroupName(), true); + $this->hungerManager->setEnabled($this->isSurvival()); if($this->isSpectator()){ @@ -1071,7 +1078,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ //this is a yucky hack but we don't have any other options :( $this->sendPosition($this->location, null, null, MovePlayerPacket::MODE_TELEPORT); }else{ - if($this->isSurvival()){ + if(!$this->hasPermission(DefaultPermissionNames::GAME_FLIGHT)){ $this->setFlying(false); } $this->setHasBlockCollision(true); @@ -1140,11 +1147,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return $this->gamemode->equals(GameMode::SPECTATOR()); } - /** - * TODO: make this a dynamic ability instead of being hardcoded - */ public function hasFiniteResources() : bool{ - return $this->gamemode->equals(GameMode::SURVIVAL()) || $this->gamemode->equals(GameMode::ADVENTURE()); + return !$this->hasPermission(DefaultPermissionNames::GAME_ITEM_CREATE); } public function isFireProof() : bool{ @@ -1452,6 +1456,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ Timings::$playerCommand->stopTiming(); }else{ $ev = new PlayerChatEvent($this, $ev->getMessage(), $this->server->getBroadcastChannelSubscribers(Server::BROADCAST_CHANNEL_USERS)); + if(!$this->hasPermission(DefaultPermissionNames::GAME_CHAT)){ + $ev->cancel(); + } $ev->call(); if(!$ev->isCancelled()){ $this->server->broadcastMessage($this->getServer()->getLanguage()->translateString($ev->getFormat(), [$ev->getPlayer()->getDisplayName(), $ev->getMessage()]), $ev->getRecipients()); @@ -1494,7 +1501,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $oldItem = clone $item; $ev = new PlayerItemUseEvent($this, $item, $directionVector); - if($this->hasItemCooldown($item) || $this->isSpectator()){ + if($this->hasItemCooldown($item) || !$this->hasPermission(DefaultPermissionNames::GAME_ITEM_USE)){ $ev->cancel(); } @@ -1565,7 +1572,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ public function releaseHeldItem() : bool{ try{ $item = $this->inventory->getItemInHand(); - if(!$this->isUsingItem() || $this->hasItemCooldown($item)){ + if(!$this->isUsingItem() || $this->hasItemCooldown($item) || !$this->hasPermission(DefaultPermissionNames::GAME_ITEM_USE)){ return false; } @@ -1599,7 +1606,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $ev = new PlayerBlockPickEvent($this, $block, $item); $existingSlot = $this->inventory->first($item); - if($existingSlot === -1 && ($this->hasFiniteResources() || $this->isSpectator())){ + if($existingSlot === -1 && $this->hasFiniteResources()){ $ev->cancel(); } $ev->call(); @@ -1641,7 +1648,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $target = $this->getWorld()->getBlock($pos); $ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, null, $face, PlayerInteractEvent::LEFT_CLICK_BLOCK); - if($this->isSpectator()){ + if(!$this->hasPermission(DefaultPermissionNames::GAME_BLOCK_INTERACT)){ $ev->cancel(); } $ev->call(); @@ -1759,7 +1766,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ if(!$this->canInteract($entity->getLocation(), self::MAX_REACH_DISTANCE_ENTITY_INTERACTION)){ $this->logger->debug("Cancelled attack of entity " . $entity->getId() . " due to not currently being interactable"); $ev->cancel(); - }elseif($this->isSpectator() || ($entity instanceof Player && !$this->server->getConfigGroup()->getConfigBool("pvp"))){ + }elseif(!$this->hasPermission($entity instanceof Player ? DefaultPermissionNames::GAME_PLAYER_ATTACK : DefaultPermissionNames::GAME_ENTITY_ATTACK)){ + $this->logger->debug("Cancelled attack of entity " . $entity->getId() . " due to lack of attack permission"); + $ev->cancel(); + }elseif($entity instanceof Player && !$this->server->getConfigGroup()->getConfigBool("pvp")){ + $this->logger->debug("Cancelled attack of player " . $entity->getId() . " due to PvP being disabled globally"); $ev->cancel(); } @@ -1825,6 +1836,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->logger->debug("Cancelled interaction with entity " . $entity->getId() . " due to not currently being interactable"); $ev->cancel(); } + if(!$this->hasPermission($entity instanceof Player ? DefaultPermissionNames::GAME_PLAYER_INTERACT : DefaultPermissionNames::GAME_ENTITY_INTERACT)){ + $this->logger->debug("Cancelled interaction with entity " . $entity->getId() . " due to lack of permission"); + $ev->cancel(); + } $ev->call(); @@ -1875,7 +1890,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return true; } $ev = new PlayerToggleFlightEvent($this, $fly); - if(!$this->allowFlight){ + if(!$this->getAllowFlight()){ $ev->cancel(); } $ev->call(); @@ -2373,7 +2388,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ && $source->getCause() !== EntityDamageEvent::CAUSE_SUICIDE ){ $source->cancel(); - }elseif($this->allowFlight && $source->getCause() === EntityDamageEvent::CAUSE_FALL){ + }elseif($this->getAllowFlight() && $source->getCause() === EntityDamageEvent::CAUSE_FALL){ $source->cancel(); } diff --git a/src/world/World.php b/src/world/World.php index ced320550c..f804e691f4 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -69,6 +69,7 @@ use pocketmine\network\mcpe\protocol\BlockActorDataPacket; use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\network\mcpe\protocol\UpdateBlockPacket; +use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\ChunkSelector; use pocketmine\player\Player; use pocketmine\promise\Promise; @@ -1869,7 +1870,7 @@ class World implements ChunkManager{ if($player !== null){ $ev = new BlockBreakEvent($player, $target, $item, $player->isCreative(), $drops, $xpDrop); - if($target instanceof Air || ($player->isSurvival() && !$target->getBreakInfo()->isBreakable()) || $player->isSpectator()){ + if($target instanceof Air || ($player->isSurvival() && !$target->getBreakInfo()->isBreakable()) || !$player->hasPermission(DefaultPermissionNames::GAME_BLOCK_BREAK)){ $ev->cancel(); } @@ -1966,7 +1967,7 @@ class World implements ChunkManager{ if($player !== null){ $ev = new PlayerInteractEvent($player, $item, $blockClicked, $clickVector, $face, PlayerInteractEvent::RIGHT_CLICK_BLOCK); - if($player->isSpectator()){ + if(!$player->hasPermission(DefaultPermissionNames::GAME_BLOCK_INTERACT)){ $ev->cancel(); //set it to cancelled so plugins can bypass this } @@ -2020,7 +2021,7 @@ class World implements ChunkManager{ if($player !== null){ $ev = new BlockPlaceEvent($player, $hand, $blockReplace, $blockClicked, $item); - if($player->isSpectator()){ + if(!$player->hasPermission(DefaultPermissionNames::GAME_BLOCK_PLACE)){ $ev->cancel(); }