Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gameplay permissions #5544

Draft
wants to merge 6 commits into
base: minor-next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/entity/object/ItemEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
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 pocketmine\timings\Timings;
use function max;
Expand Down Expand Up @@ -328,7 +329,7 @@ public function onCollideWithPlayer(Player $player) : void{
};

$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();
}

Expand Down
3 changes: 2 additions & 1 deletion src/entity/projectile/Arrow.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
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;
Expand Down Expand Up @@ -178,7 +179,7 @@ public function onCollideWithPlayer(Player $player) : void{
};

$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())){
Expand Down
3 changes: 2 additions & 1 deletion src/inventory/transaction/action/DropItemAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player;

/**
Expand All @@ -49,7 +50,7 @@ public function validate(Player $source) : void{

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();
Expand Down
14 changes: 7 additions & 7 deletions src/network/mcpe/NetworkSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -1040,16 +1040,16 @@ public function syncAbilities(Player $for) : void{
AbilitiesLayer::ABILITY_OPERATOR => $isOp,
AbilitiesLayer::ABILITY_TELEPORT => $for->hasPermission(DefaultPermissionNames::COMMAND_TELEPORT_SELF),
AbilitiesLayer::ABILITY_INVULNERABLE => $for->isCreative(),
AbilitiesLayer::ABILITY_MUTED => false,
AbilitiesLayer::ABILITY_MUTED => !$for->hasPermission(DefaultPermissionNames::GAME_CHAT),
AbilitiesLayer::ABILITY_WORLD_BUILDER => false,
AbilitiesLayer::ABILITY_INFINITE_RESOURCES => !$for->hasFiniteResources(),
AbilitiesLayer::ABILITY_LIGHTNING => false,
AbilitiesLayer::ABILITY_BUILD => !$for->isSpectator(),
AbilitiesLayer::ABILITY_MINE => !$for->isSpectator(),
AbilitiesLayer::ABILITY_DOORS_AND_SWITCHES => !$for->isSpectator(),
AbilitiesLayer::ABILITY_OPEN_CONTAINERS => !$for->isSpectator(),
AbilitiesLayer::ABILITY_ATTACK_PLAYERS => !$for->isSpectator(),
AbilitiesLayer::ABILITY_ATTACK_MOBS => !$for->isSpectator(),
AbilitiesLayer::ABILITY_BUILD => $for->hasPermission(DefaultPermissionNames::GAME_BLOCK_PLACE),
AbilitiesLayer::ABILITY_MINE => $for->hasPermission(DefaultPermissionNames::GAME_BLOCK_BREAK),
AbilitiesLayer::ABILITY_DOORS_AND_SWITCHES => $for->hasPermission(DefaultPermissionNames::GAME_BLOCK_INTERACT),
AbilitiesLayer::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
AbilitiesLayer::ABILITY_ATTACK_PLAYERS => $for->hasPermission(DefaultPermissionNames::GAME_PLAYER_ATTACK),
AbilitiesLayer::ABILITY_ATTACK_MOBS => $for->hasPermission(DefaultPermissionNames::GAME_ENTITY_ATTACK),
AbilitiesLayer::ABILITY_PRIVILEGED_BUILDER => false,
];

Expand Down
17 changes: 17 additions & 0 deletions src/permission/DefaultPermissionNames.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,24 @@ final class DefaultPermissionNames{
public const COMMAND_WHITELIST_REMOVE = "pocketmine.command.whitelist.remove";
public const COMMAND_XP_OTHER = "pocketmine.command.xp.other";
public const COMMAND_XP_SELF = "pocketmine.command.xp.self";
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";
}
21 changes: 21 additions & 0 deletions src/permission/DefaultPermissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,26 @@ public static function registerCorePermissions() : void{
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_REMOVE, l10n::pocketmine_permission_command_whitelist_remove()), [$operatorRoot]);
self::registerPermission(new Permission(Names::COMMAND_XP_OTHER, l10n::pocketmine_permission_command_xp_other()), [$operatorRoot]);
self::registerPermission(new Permission(Names::COMMAND_XP_SELF, l10n::pocketmine_permission_command_xp_self()), [$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]);
}
}
17 changes: 11 additions & 6 deletions src/player/GameMode.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\Translatable;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\utils\LegacyEnumShimTrait;
use function mb_strtolower;
use function spl_object_id;
Expand All @@ -38,7 +39,7 @@
* @method static GameMode SPECTATOR()
* @method static GameMode SURVIVAL()
*
* @phpstan-type TMetadata array{0: string, 1: Translatable, 2: list<string>}
* @phpstan-type TMetadata array{0: string, 1: Translatable, 2: string, 3: list<string>}
*/
enum GameMode{
use LegacyEnumShimTrait;
Expand Down Expand Up @@ -75,10 +76,10 @@ private function getMetadata() : array{
static $cache = [];

return $cache[spl_object_id($this)] ??= match($this){
self::SURVIVAL => ["Survival", KnownTranslationFactory::gameMode_survival(), ["survival", "s", "0"]],
self::CREATIVE => ["Creative", KnownTranslationFactory::gameMode_creative(), ["creative", "c", "1"]],
self::ADVENTURE => ["Adventure", KnownTranslationFactory::gameMode_adventure(), ["adventure", "a", "2"]],
self::SPECTATOR => ["Spectator", KnownTranslationFactory::gameMode_spectator(), ["spectator", "v", "view", "3"]]
self::SURVIVAL => ["Survival", KnownTranslationFactory::gameMode_survival(), DefaultPermissionNames::GROUP_GAMEMODE_SURVIVAL, ["survival", "s", "0"]],
self::CREATIVE => ["Creative", KnownTranslationFactory::gameMode_creative(), DefaultPermissionNames::GROUP_GAMEMODE_CREATIVE, ["creative", "c", "1"]],
self::ADVENTURE => ["Adventure", KnownTranslationFactory::gameMode_adventure(), DefaultPermissionNames::GROUP_GAMEMODE_ADVENTURE, ["adventure", "a", "2"]],
self::SPECTATOR => ["Spectator", KnownTranslationFactory::gameMode_spectator(), DefaultPermissionNames::GROUP_GAMEMODE_SPECTATOR, ["spectator", "v", "view", "3"]]
};
}

Expand All @@ -90,11 +91,15 @@ public function getTranslatableName() : Translatable{
return $this->getMetadata()[1];
}

public function getPermissionGroupName() : string{
return $this->getMetadata()[2];
}

/**
* @return string[]
*/
public function getAliases() : array{
return $this->getMetadata()[2];
return $this->getMetadata()[3];
}

//TODO: ability sets per gamemode
Expand Down
52 changes: 33 additions & 19 deletions src/player/Player.php
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,6 @@ public static function isValidUserName(?string $name) : bool{

//TODO: Abilities
protected bool $autoJump = true;
protected bool $allowFlight = false;
protected bool $blockCollision = true;
protected bool $flying = false;

Expand Down Expand Up @@ -456,12 +455,16 @@ public function hasPlayedBefore() : bool{
*
* 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);
}

/**
Expand All @@ -471,7 +474,7 @@ public function setAllowFlight(bool $value) : void{
* enter or exit flight mode will be prevented.
*/
public function getAllowFlight() : bool{
return $this->allowFlight;
return $this->hasPermission(DefaultPermissionNames::GAME_FLIGHT);
}

/**
Expand Down Expand Up @@ -1121,9 +1124,12 @@ public function getGamemode() : GameMode{
}

protected function internalSetGameMode(GameMode $gameMode) : void{
if(isset($this->gamemode)){
$this->unsetBasePermission($this->gamemode->getPermissionGroupName());
}
$this->gamemode = $gameMode;

$this->allowFlight = $this->gamemode === GameMode::CREATIVE;
$this->setBasePermission($this->gamemode->getPermissionGroupName(), true);
$this->hungerManager->setEnabled($this->isSurvival());

if($this->isSpectator()){
Expand All @@ -1136,7 +1142,7 @@ protected function internalSetGameMode(GameMode $gameMode) : void{
//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);
Expand Down Expand Up @@ -1205,11 +1211,8 @@ public function isSpectator() : bool{
return $this->gamemode === GameMode::SPECTATOR;
}

/**
* TODO: make this a dynamic ability instead of being hardcoded
*/
public function hasFiniteResources() : bool{
return $this->gamemode !== GameMode::CREATIVE;
return !$this->hasPermission(DefaultPermissionNames::GAME_ITEM_CREATE);
}

public function getDrops() : array{
Expand Down Expand Up @@ -1523,6 +1526,9 @@ public function chat(string $message) : bool{
Timings::$playerCommand->stopTiming();
}else{
$ev = new PlayerChatEvent($this, $messagePart, $this->server->getBroadcastChannelSubscribers(Server::BROADCAST_CHANNEL_USERS), new StandardChatFormatter());
if(!$this->hasPermission(DefaultPermissionNames::GAME_CHAT)){
$ev->cancel();
}
$ev->call();
if(!$ev->isCancelled()){
$this->server->broadcastMessage($ev->getFormatter()->format($ev->getPlayer()->getDisplayName(), $ev->getMessage()), $ev->getRecipients());
Expand Down Expand Up @@ -1610,7 +1616,7 @@ public function useHeldItem() : bool{
$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();
}

Expand Down Expand Up @@ -1674,7 +1680,7 @@ public function consumeHeldItem() : bool{
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;
}

Expand Down Expand Up @@ -1729,7 +1735,7 @@ public function pickEntity(int $entityId) : bool{

$ev = new PlayerEntityPickEvent($this, $entity, $item);
$existingSlot = $this->inventory->first($item);
if($existingSlot === -1 && ($this->hasFiniteResources() || $this->isSpectator())){
if($existingSlot === -1 && $this->hasFiniteResources()){
$ev->cancel();
}
$ev->call();
Expand Down Expand Up @@ -1775,7 +1781,7 @@ public function attackBlock(Vector3 $pos, int $face) : bool{
$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();
Expand Down Expand Up @@ -1885,7 +1891,11 @@ public function attackEntity(Entity $entity) : bool{
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(ServerProperties::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(ServerProperties::PVP)){
$this->logger->debug("Cancelled attack of player " . $entity->getId() . " due to PvP being disabled globally");
$ev->cancel();
}

Expand Down Expand Up @@ -1961,6 +1971,10 @@ public function interactEntity(Entity $entity, Vector3 $clickPos) : bool{
$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();

Expand Down Expand Up @@ -2011,7 +2025,7 @@ public function toggleFlight(bool $fly) : bool{
return true;
}
$ev = new PlayerToggleFlightEvent($this, $fly);
if(!$this->allowFlight){
if(!$this->getAllowFlight()){
$ev->cancel();
}
$ev->call();
Expand Down Expand Up @@ -2505,7 +2519,7 @@ public function attack(EntityDamageEvent $source) : void{
&& $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();
}

Expand Down
7 changes: 4 additions & 3 deletions src/world/World.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
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\Player;
use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver;
Expand Down Expand Up @@ -2066,7 +2067,7 @@ public function useBreakOn(Vector3 $vector, ?Item &$item = null, ?Player $player
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();
}

Expand Down Expand Up @@ -2177,7 +2178,7 @@ public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $cli
$ev->setUseItem(false);
$ev->setUseBlock($item->isNull()); //opening doors is still possible when sneaking if using an empty hand
}
if($player->isSpectator()){
if(!$player->hasPermission(DefaultPermissionNames::GAME_BLOCK_INTERACT)){
$ev->cancel(); //set it to cancelled so plugins can bypass this
}

Expand Down Expand Up @@ -2233,7 +2234,7 @@ public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $cli

if($player !== null){
$ev = new BlockPlaceEvent($player, $tx, $blockClicked, $item);
if($player->isSpectator()){
if(!$player->hasPermission(DefaultPermissionNames::GAME_BLOCK_PLACE)){
$ev->cancel();
}

Expand Down