Compare commits

...

18 Commits

Author SHA1 Message Date
a6042ec7e8 Merge branch 'minor-next' into gameplay-permissions 2024-12-01 23:55:09 +00:00
e494460cfd Introduce invulnerable permission
this eliminates all remaining usages of isCreative(), except for reach distance checks. Reach distance can't be migrated to a permission in an obvious way.
2024-12-01 17:15:26 +00:00
2c299e2b49 Fix CS 2024-12-01 16:51:16 +00:00
ac1e70cd96 Added permission for emoting 2024-12-01 16:50:26 +00:00
0bbd4af496 Remove TODO comment
the remaining bool flags are user input toggles, not abilities, so it doesn't make sense to move them to permissions.

permissions are intended for what the player is *allowed* to do, not what they *want* to do.
2024-12-01 16:48:09 +00:00
b0bfc30b07 Added noclip permission 2024-12-01 16:44:32 +00:00
a91cef37f6 Move some gamemode checks to instabreak checks 2024-12-01 16:42:30 +00:00
26afa97cdc Convert adventure mode checks to permissions 2024-12-01 16:12:22 +00:00
57082c8148 Added instabreak permission 2024-12-01 15:49:19 +00:00
74ee38ab99 Use permissions for more stuff 2024-12-01 15:34:00 +00:00
df069b0418 Fixed behavioural BC break in setAllowFlight() 2024-12-01 15:25:05 +00:00
ea43fd1917 Merge branch 'minor-next' into gameplay-permissions 2024-12-01 15:22:44 +00:00
ca5d9c3731 github web editor try not to suck challenge IMPOSSIBLE 2024-11-29 15:26:40 +00:00
cbcc4d24e0 Update DefaultPermissions.php 2024-11-29 15:25:33 +00:00
223fd74255 piece of shit web editor 2024-11-29 15:24:57 +00:00
0fef4c6683 Merge branch 'minor-next' into gameplay-permissions 2024-11-29 15:23:03 +00:00
e1ae9a7d69 fix CS 2023-01-30 22:21:14 +00:00
10a962daa2 First look at #5512: gameplay permissions 2023-01-16 22:12:07 +00:00
13 changed files with 147 additions and 62 deletions

View File

@ -29,7 +29,7 @@ use pocketmine\block\utils\SupportType;
use pocketmine\event\block\BlockTeleportEvent;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\player\GameMode;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player;
use pocketmine\world\particle\DragonEggTeleportParticle;
use pocketmine\world\World;
@ -50,7 +50,7 @@ class DragonEgg extends Transparent implements Fallable{
}
public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
if($player !== null && $player->getGamemode() !== GameMode::CREATIVE){
if($player !== null && !$player->hasPermission(DefaultPermissionNames::GAME_BLOCK_DELETE)){
$this->teleport();
return true;
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player;
class Ice extends Transparent{
@ -39,7 +40,8 @@ class Ice extends Transparent{
}
public function onBreak(Item $item, ?Player $player = null, array &$returnedItems = []) : bool{
if(($player === null || $player->isSurvival()) && !$item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
//TODO: we should probably pass instaBreak in here, since events can override it
if(($player === null || !$player->hasPermission(DefaultPermissionNames::GAME_BLOCK_DELETE)) && !$item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::WATER());
return true;
}

View File

@ -31,6 +31,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\EntityMetadataProperties;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player;
use function max;
use function sqrt;
@ -192,7 +193,7 @@ class ExperienceOrb extends Entity{
if($currentTarget === null){
$newTarget = $this->getWorld()->getNearestEntity($this->location, self::MAX_TARGET_DISTANCE, Human::class);
if($newTarget instanceof Human && !($newTarget instanceof Player && $newTarget->isSpectator()) && $newTarget->getXpManager()->canAttractXpOrbs()){
if($newTarget instanceof Human && !($newTarget instanceof Player && !$newTarget->hasPermission(DefaultPermissionNames::GAME_ITEM_PICKUP)) && $newTarget->getXpManager()->canAttractXpOrbs()){
$currentTarget = $newTarget;
}
}

View File

@ -39,6 +39,7 @@ use pocketmine\network\mcpe\NetworkBroadcastUtils;
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;
@ -328,7 +329,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();
}

View File

@ -38,6 +38,7 @@ use pocketmine\network\mcpe\NetworkBroadcastUtils;
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;
@ -178,10 +179,10 @@ 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())){
if($this->pickupMode === self::PICKUP_NONE || ($this->pickupMode === self::PICKUP_CREATIVE && $player->hasFiniteResources())){
$ev->cancel();
}

View File

@ -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();

View File

@ -29,6 +29,7 @@ use pocketmine\entity\projectile\Projectile;
use pocketmine\event\entity\EntityShootBowEvent;
use pocketmine\event\entity\ProjectileLaunchEvent;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player;
use pocketmine\world\sound\BowShootSound;
use function intdiv;
@ -85,7 +86,7 @@ class Bow extends Tool implements Releasable{
}
$ev = new EntityShootBowEvent($player, $this, $entity, $baseForce * 3);
if($baseForce < 0.1 || $diff < 5 || $player->isSpectator()){
if($baseForce < 0.1 || $diff < 5 || !$player->hasPermission(DefaultPermissionNames::GAME_ITEM_USE)){
$ev->cancel();
}

View File

@ -1039,17 +1039,17 @@ class NetworkSession{
AbilitiesLayer::ABILITY_NO_CLIP => !$for->hasBlockCollision(),
AbilitiesLayer::ABILITY_OPERATOR => $isOp,
AbilitiesLayer::ABILITY_TELEPORT => $for->hasPermission(DefaultPermissionNames::COMMAND_TELEPORT_SELF),
AbilitiesLayer::ABILITY_INVULNERABLE => $for->isCreative(),
AbilitiesLayer::ABILITY_MUTED => false,
AbilitiesLayer::ABILITY_INVULNERABLE => $for->hasPermission(DefaultPermissionNames::GAME_INVULNERABLE),
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_MINE),
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,
];

View File

@ -86,7 +86,30 @@ 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_DELETE = "pocketmine.game.block.delete";
public const GAME_BLOCK_INTERACT = "pocketmine.game.block.interact";
public const GAME_BLOCK_MINE = "pocketmine.game.block.mine";
public const GAME_BLOCK_PLACE = "pocketmine.game.block.place";
public const GAME_CHAT = "pocketmine.game.chat";
public const GAME_EMOTE = "pocketmine.game.emote";
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_BYPASS_CANDESTROY = "pocketmine.game.item.bypass.candestroy";
public const GAME_ITEM_BYPASS_CANPLACEON = "pocketmine.game.item.bypass.canplaceon";
public const GAME_INVULNERABLE = "pocketmine.game.invulnerable";
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_NOCOLLISION = "pocketmine.game.nocollision";
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";
}

View File

@ -114,5 +114,36 @@ abstract class DefaultPermissions{
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]);
self::registerPermission(new Permission(Names::GAME_EMOTE, "Allows the user to emote"), [$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));
$spectatorRoot = self::registerPermission(new Permission(Names::GROUP_GAMEMODE_SPECTATOR));
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_MINE, "Allows the user to mine 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]);
//TODO: maybe we should add deny inherits for the adventure group for these, instead of just granting them to the survival and creative groups
//we'll end up needing to add these to new game modes if they are added
self::registerPermission(new Permission(Names::GAME_ITEM_BYPASS_CANDESTROY, "Allows the user to bypass CanDestroy item restrictions when mining blocks"), [$survivalRoot, $creativeRoot]);
self::registerPermission(new Permission(Names::GAME_ITEM_BYPASS_CANPLACEON, "Allows the user to bypass CanPlaceOn item restrictions when placing blocks"), [$survivalRoot, $creativeRoot]);
self::registerPermission(new Permission(Names::GAME_BLOCK_DELETE, "Allows the user to delete any block without delay, including indestructible blocks"), [$creativeRoot]);
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]);
self::registerPermission(new Permission(Names::GAME_NOCOLLISION, "Allows the user to pass through blocks"), [$spectatorRoot]);
self::registerPermission(new Permission(Names::GAME_INVULNERABLE, "Allows the user to ignore all incoming damage (except suicide)"), [$creativeRoot, $spectatorRoot]);
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\player;
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;
@ -38,7 +39,7 @@ use function spl_object_id;
* @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;
@ -75,10 +76,10 @@ enum GameMode{
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"]]
};
}
@ -90,11 +91,15 @@ enum GameMode{
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

View File

@ -282,8 +282,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;
/** @phpstan-var positive-int|null */
@ -466,12 +464,13 @@ 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.
*
* @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);
}
/**
@ -481,7 +480,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);
}
/**
@ -491,12 +490,13 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* Note: Enabling flight mode in conjunction with this is recommended. A non-flying player will simply fall through
* the ground into the void.
* @see Player::setFlying()
*
* @deprecated This is now controlled by setting a permission, which allows more fine-tuned control.
* @see DefaultPermissionNames::GAME_NOCOLLISION
*/
public function setHasBlockCollision(bool $value) : void{
if($this->blockCollision !== $value){
$this->blockCollision = $value;
$this->getNetworkSession()->syncAbilities($this);
}
$this->setBasePermission(DefaultPermissionNames::GAME_NOCOLLISION, !$value);
$this->getNetworkSession()->syncAbilities($this);
}
/**
@ -504,7 +504,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* If false, the player can move through any block unobstructed.
*/
public function hasBlockCollision() : bool{
return $this->blockCollision;
return !$this->hasPermission(DefaultPermissionNames::GAME_NOCOLLISION);
}
public function setFlying(bool $value) : void{
@ -1155,14 +1155,22 @@ 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 === GameMode::CREATIVE;
$this->setBasePermission($this->gamemode->getPermissionGroupName(), true);
//TODO: this preserves old behaviour of gamemode changes overriding setAllowFlight and setHasBlockCollision
//we should get rid of these when the deprecated setters are removed
$this->unsetBasePermission(DefaultPermissionNames::GAME_FLIGHT);
$this->unsetBasePermission(DefaultPermissionNames::GAME_NOCOLLISION);
$this->hungerManager->setEnabled($this->isSurvival());
if($this->isSpectator()){
$this->setFlying(true);
$this->setHasBlockCollision(false);
$this->setSilent();
$this->onGround = false;
@ -1170,10 +1178,9 @@ 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);
$this->setSilent(false);
$this->checkGroundState(0, 0, 0, 0, 0, 0);
}
@ -1239,11 +1246,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
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{
@ -1485,11 +1489,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->entityBaseTick($tickDiff);
Timings::$entityBaseTick->stopTiming();
if($this->isCreative() && $this->fireTicks > 1){
if($this->hasPermission(DefaultPermissionNames::GAME_INVULNERABLE) && $this->fireTicks > 1){
$this->fireTicks = 1;
}
if(!$this->isSpectator() && $this->isAlive()){
if($this->isAlive()){
Timings::$playerCheckNearEntities->startTiming();
$this->checkNearEntities();
Timings::$playerCheckNearEntities->stopTiming();
@ -1510,7 +1514,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
public function canBreathe() : bool{
return $this->isCreative() || parent::canBreathe();
return $this->hasPermission(DefaultPermissionNames::GAME_INVULNERABLE) || parent::canBreathe();
}
/**
@ -1561,6 +1565,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
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());
@ -1627,7 +1634,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
foreach($this->inventory->addItem(...$extraReturnedItems) as $drop){
//TODO: we can't generate a transaction for this since the items aren't coming from an inventory :(
$ev = new PlayerDropItemEvent($this, $drop);
if($this->isSpectator()){
if(!$this->hasPermission(DefaultPermissionNames::GAME_ITEM_DROP)){
$ev->cancel();
}
$ev->call();
@ -1648,7 +1655,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();
}
@ -1712,7 +1719,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;
}
@ -1767,7 +1774,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$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();
@ -1813,7 +1820,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();
@ -1832,7 +1839,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return true;
}
if(!$this->isCreative() && !$target->getBreakInfo()->breaksInstantly()){
if(!$this->hasPermission(DefaultPermissionNames::GAME_BLOCK_DELETE) && !$target->getBreakInfo()->breaksInstantly()){
$this->blockBreakHandler = new SurvivalBlockBreakHandler($this, $pos, $target, $face, 16);
}
@ -1923,7 +1930,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(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();
}
@ -1999,6 +2010,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();
@ -2049,7 +2064,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();
@ -2091,6 +2106,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
if($currentTick - $this->lastEmoteTick > 5){
$this->lastEmoteTick = $currentTick;
$event = new PlayerEmoteEvent($this, $emoteId);
if(!$this->hasPermission(DefaultPermissionNames::GAME_EMOTE)){
$event->cancel();
}
$event->call();
if(!$event->isCancelled()){
$emoteId = $event->getEmoteId();
@ -2549,11 +2567,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return;
}
if($this->isCreative()
if($this->hasPermission(DefaultPermissionNames::GAME_INVULNERABLE)
&& $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();
}

View File

@ -74,6 +74,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\Player;
use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver;
@ -2064,13 +2065,13 @@ class World implements ChunkManager{
}
if($player !== null){
$ev = new BlockBreakEvent($player, $target, $item, $player->isCreative(), $drops, $xpDrop);
$ev = new BlockBreakEvent($player, $target, $item, $player->hasPermission(DefaultPermissionNames::GAME_BLOCK_DELETE), $drops, $xpDrop);
if($target instanceof Air || ($player->isSurvival() && !$target->getBreakInfo()->isBreakable()) || $player->isSpectator()){
if($target instanceof Air || (!$player->hasPermission(DefaultPermissionNames::GAME_BLOCK_DELETE) && !$target->getBreakInfo()->isBreakable()) || !$player->hasPermission(DefaultPermissionNames::GAME_BLOCK_MINE)){
$ev->cancel();
}
if($player->isAdventure(true) && !$ev->isCancelled()){
if(!$player->hasPermission(DefaultPermissionNames::GAME_ITEM_BYPASS_CANDESTROY) && !$ev->isCancelled()){
$canBreak = false;
$itemParser = LegacyStringToItemParser::getInstance();
foreach($item->getCanDestroy() as $v){
@ -2177,7 +2178,7 @@ class World implements ChunkManager{
$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
}
@ -2233,11 +2234,11 @@ class World implements ChunkManager{
if($player !== null){
$ev = new BlockPlaceEvent($player, $tx, $blockClicked, $item);
if($player->isSpectator()){
if(!$player->hasPermission(DefaultPermissionNames::GAME_BLOCK_PLACE)){
$ev->cancel();
}
if($player->isAdventure(true) && !$ev->isCancelled()){
if(!$player->hasPermission(DefaultPermissionNames::GAME_ITEM_BYPASS_CANPLACEON) && !$ev->isCancelled()){
$canPlace = false;
$itemParser = LegacyStringToItemParser::getInstance();
foreach($item->getCanPlaceOn() as $v){