abilities);
+
+ /**
+ * Enables the given ability.
+ *
+ * @param ability the ability to enable
+ */
+ default void addAbility(PlayerAbility ability) {
+ setAbility(ability, true);
+ }
+
+ /**
+ * Enables the given abilities.
+ *
+ * @param abilities the abilities to enable
+ */
+ default void addAbilities(PlayerAbility... abilities) {
+ setAbilities(Set.of(abilities), true);
+ }
+
+ /**
+ * Disables the given ability.
+ *
+ * @param ability the abilities to disable
+ */
+ default void removeAbility(PlayerAbility ability) {
+ setAbility(ability, false);
+ }
+
+ /**
+ * Disables the given abilities.
+ *
+ * @param abilities the abilities to disable
+ */
+ default void removeAbilities(PlayerAbility... abilities) {
+ setAbilities(Set.of(abilities), false);
+ }
+
+ /**
+ * Checks whether the player may currently place blocks.
+ *
+ * Always true for operators, always false for spectator, adventure modes and immutableWorld,
+ * actual ability value otherwise.
+ *
+ * Directly reflects client-side placement behavior.
+ *
+ * @return {@code true} if placing blocks is currently allowed, {@code false} otherwise
+ */
+ boolean canPlaceBlocks();
+
+ /**
+ * Checks whether the player may currently break blocks.
+ *
+ * Always true for operators, always false for spectator, adventure modes and immutableWorld,
+ * actual ability value otherwise.
+ *
+ * Directly reflects client-side mining behavior.
+ *
+ * @return {@code true} if breaking blocks is currently allowed, {@code false} otherwise
+ */
+ boolean canBreakBlocks();
+
+ /**
+ * Checks whether the player may currently interact with blocks.
+ *
+ * Always true for operators, always false for spectator mode and immutable world, actual ability
+ * value otherwise.
+ *
+ * Directly reflects client-side interaction behavior.
+ *
+ * @return {@code true} if block interaction is currently allowed, {@code false} otherwise
+ */
+ boolean canInteractWithBlocks();
+
+ /**
+ * Checks whether the player may currently open containers.
+ *
+ * Always true for operators, always false for spectator mode and immutable world, actual ability
+ * value otherwise.
+ *
+ * @return {@code true} if opening containers is currently allowed, {@code false} otherwise
+ */
+ boolean canOpenContainers();
+
+ /**
+ * Checks whether the player may currently attack other players.
+ *
+ * Always true for operators, always false for spectator mode, actual ability value otherwise.
+ *
+ * @return {@code true} if attacking players is currently allowed, {@code false} otherwise
+ */
+ boolean canAttackPlayers();
+
+ /**
+ * Checks whether the player may currently attack mobs.
+ *
+ * Always true for operators, always false for spectator mode, actual ability value otherwise.
+ *
+ * @return {@code true} if attacking mobs is currently allowed, {@code false} otherwise
+ */
+ boolean canAttackMobs();
+
+ /**
+ * Checks whether the player may currently fly.
+ *
+ * Always true if isAlwaysFlying and spectator mode, actual ability value otherwise.
+ *
+ * @return {@code true} if flying is currently allowed, {@code false} otherwise
+ */
+ boolean canFly();
+
+ /**
+ * Checks whether placed blocks should not be consumed from inventory.
+ *
+ * Usually {@code true} in creative mode, but always reflects the actual ability value.
+ *
+ * @return {@code true} if placed blocks are not consumed, {@code false} otherwise
+ */
+ boolean hasInfiniteBlock();
+
+ /**
+ * Checks whether the player is currently in no clip mode.
+ *
+ * Always true for spectator mode, actual ability value otherwise.
+ *
+ * @return {@code true} if player is in no clip mode, {@code false} otherwise.
+ */
+ boolean isNoClip();
+
+ /**
+ * Checks whether the player is currently treated as having immutable world enabled.
+ *
+ * Always false for operators, always true for adventure and spectator modes, actual value
+ * otherwise.
+ *
+ * @return {@code true} if immutable world is currently active, {@code false} otherwise
+ */
+ boolean isImmutableWorld();
+
+ /**
+ * Sets whether immutable world should be enabled for the player.
+ *
+ * Forces the client to treat the world as non-interactive, similar to adventure mode.
+ *
+ * @param immutableWorld {@code true} to enable immutable world, {@code false} to disable it
+ */
+ void setImmutableWorld(boolean immutableWorld);
+
+ /**
+ * Checks whether the player is currently forced to be always flying.
+ *
+ * Always true for spectator mode and no clip, actual value otherwise.
+ *
+ * @return {@code true} if always flying is currently active, {@code false} otherwise
+ */
+ boolean isAlwaysFlying();
+
+ /**
+ * Sets whether always flying should be active for the player.
+ *
+ * @param alwaysFlying {@code true} to enable always flying, {@code false} to disable it
+ */
+ void setAlwaysFlying(boolean alwaysFlying);
/**
* Views a player list change. The provided players will be added to the player list.
diff --git a/api/src/main/java/org/allaymc/api/player/PlayerAbility.java b/api/src/main/java/org/allaymc/api/player/PlayerAbility.java
new file mode 100644
index 000000000..9539c3da1
--- /dev/null
+++ b/api/src/main/java/org/allaymc/api/player/PlayerAbility.java
@@ -0,0 +1,69 @@
+package org.allaymc.api.player;
+
+/**
+ * Represents a player ability tracked by the server.
+ *
+ * @author zernix2077
+ */
+public enum PlayerAbility {
+ /**
+ * Whether the player can place blocks.
+ *
+ * Directly affects client-side ability to place blocks.
+ */
+ PLACE_BLOCK,
+
+ /**
+ * Whether the player can break blocks.
+ *
+ * Directly affects client-side ability to break blocks.
+ */
+ BREAK_BLOCK,
+
+ /**
+ * Whether the player can interact with blocks, like doors and switches.
+ *
+ * Directly affects client-side ability to perform those interactions.
+ */
+ INTERACT_BLOCK,
+
+ /**
+ * Whether blocks are not consumed when placed down.
+ *
+ * Generally enabled for creative mode, but may also be enabled for any other gamemode, resulting
+ * in the same infinite block behavior client-side.
+ */
+ INFINITE_BLOCK,
+
+ /**
+ * Whether the player can open containers.
+ */
+ OPEN_CONTAINER,
+
+ /**
+ * Whether the player can attack other players.
+ */
+ ATTACK_PLAYER,
+
+ /**
+ * Whether the player can attack mobs.
+ */
+ ATTACK_MOB,
+
+ /**
+ * Whether the player is currently flying.
+ */
+ FLYING,
+
+ /**
+ * Whether the player may start flying.
+ */
+ MAY_FLY,
+
+ /**
+ * Whether the player can move through blocks.
+ *
+ * Directly affects client-side movement behavior.
+ */
+ NO_CLIP,
+}
diff --git a/api/src/main/java/org/allaymc/api/player/PlayerData.java b/api/src/main/java/org/allaymc/api/player/PlayerData.java
index 3975f3579..811529ec5 100644
--- a/api/src/main/java/org/allaymc/api/player/PlayerData.java
+++ b/api/src/main/java/org/allaymc/api/player/PlayerData.java
@@ -9,6 +9,9 @@
import org.allaymc.api.utils.identifier.Identifier;
import org.allaymc.api.world.dimension.DimensionTypes;
import org.cloudburstmc.nbt.NbtMap;
+import org.cloudburstmc.nbt.NbtType;
+
+import java.util.EnumSet;
import static org.allaymc.api.utils.AllayNBTUtils.writeVector2f;
import static org.allaymc.api.utils.AllayNBTUtils.writeVector3f;
@@ -28,6 +31,7 @@ public class PlayerData {
protected static final String TAG_NBT = "NBT";
protected static final String TAG_WORLD = "World";
protected static final String TAG_DIMENSION = "Dimension";
+ protected static final String TAG_ABILITIES = "Abilities";
// EntityPlayer's nbt, which can be generated through the method EntityPlayer#saveNBT()
protected NbtMap nbt;
@@ -38,6 +42,7 @@ public class PlayerData {
// world and dimension the player is in.
protected String world;
protected String dimension;
+ protected EnumSet abilities;
public static PlayerData save(Player player) {
var entity = player.getControlledEntity();
@@ -50,6 +55,7 @@ public static PlayerData save(Player player) {
.nbt(entity.saveNBT())
.world(entity.getWorld().getWorldData().getDisplayName())
.dimension(entity.getDimension().getDimensionType().getIdentifier().toString())
+ .abilities(player.getAbilities().isEmpty() ? EnumSet.noneOf(PlayerAbility.class) : EnumSet.copyOf(player.getAbilities()))
.build();
}
@@ -84,6 +90,19 @@ public static PlayerData fromNBT(NbtMap nbt) {
builder.nbt(nbt.getCompound(TAG_NBT))
.world(nbt.getString(TAG_WORLD))
.dimension(readDimension(nbt));
+
+ if (nbt.containsKey(TAG_ABILITIES)) {
+ var abilities = EnumSet.noneOf(PlayerAbility.class);
+ for (var abilityName : nbt.getList(TAG_ABILITIES, NbtType.STRING)) {
+ try {
+ abilities.add(PlayerAbility.valueOf(abilityName));
+ } catch (IllegalArgumentException e) {
+ log.warn("Unknown stored player ability {}, ignoring it", abilityName);
+ }
+ }
+ builder.abilities(abilities);
+ }
+
return builder.build();
}
@@ -97,6 +116,11 @@ public NbtMap toNBT() {
.putCompound(TAG_NBT, nbt)
.putString(TAG_WORLD, world)
.putString(TAG_DIMENSION, dimension);
+
+ if (abilities != null) {
+ builder.putList(TAG_ABILITIES, NbtType.STRING, abilities.stream().map(Enum::name).toList());
+ }
+
return builder.build();
}
diff --git a/server/src/main/java/org/allaymc/server/command/defaults/GameTestCommand.java b/server/src/main/java/org/allaymc/server/command/defaults/GameTestCommand.java
index aae4e81b1..604b0393e 100644
--- a/server/src/main/java/org/allaymc/server/command/defaults/GameTestCommand.java
+++ b/server/src/main/java/org/allaymc/server/command/defaults/GameTestCommand.java
@@ -28,6 +28,7 @@
import org.allaymc.api.message.LangCode;
import org.allaymc.api.message.TrKeys;
import org.allaymc.api.player.HudElement;
+import org.allaymc.api.player.PlayerAbility;
import org.allaymc.api.registry.Registries;
import org.allaymc.api.server.Server;
import org.allaymc.api.utils.AllayStringUtils;
@@ -838,6 +839,22 @@ public void prepareCommandTree(CommandTree tree) {
return context.success();
}, SenderType.PLAYER)
.root()
+ .key("setimmutableworld")
+ .bool("value")
+ .exec((context, sender) -> {
+ boolean value = context.getResult(1);
+ sender.getController().setImmutableWorld(value);
+ return context.success();
+ }, SenderType.PLAYER)
+ .root()
+ .key("setnoblocksconsumption")
+ .bool("value")
+ .exec((context, sender) -> {
+ boolean value = context.getResult(1);
+ sender.getController().setAbility(PlayerAbility.INFINITE_BLOCK, value);
+ return context.success();
+ }, SenderType.PLAYER)
+ .root()
.key("attachprimitiveshape")
.target("target")
.optional()
@@ -867,6 +884,5 @@ public void prepareCommandTree(CommandTree tree) {
context.addOutput("Done.");
return context.success();
}, SenderType.PLAYER);
-
}
}
diff --git a/server/src/main/java/org/allaymc/server/container/impl/BaseContainer.java b/server/src/main/java/org/allaymc/server/container/impl/BaseContainer.java
index 10a91d876..c94e24abe 100644
--- a/server/src/main/java/org/allaymc/server/container/impl/BaseContainer.java
+++ b/server/src/main/java/org/allaymc/server/container/impl/BaseContainer.java
@@ -10,10 +10,12 @@
import org.allaymc.api.container.Container;
import org.allaymc.api.container.ContainerType;
import org.allaymc.api.container.ContainerViewer;
+import org.allaymc.api.container.interfaces.BlockContainer;
import org.allaymc.api.eventbus.event.container.ContainerCloseEvent;
import org.allaymc.api.eventbus.event.container.ContainerOpenEvent;
import org.allaymc.api.item.ItemStack;
import org.allaymc.api.item.interfaces.ItemAirStack;
+import org.allaymc.api.player.Player;
import org.allaymc.api.utils.NBTIO;
import org.cloudburstmc.nbt.NbtList;
import org.cloudburstmc.nbt.NbtMap;
@@ -89,6 +91,9 @@ public boolean addViewer(ContainerViewer viewer) {
removeViewer(viewer);
return addViewer(viewer);
}
+ if (viewer instanceof Player player && this instanceof BlockContainer && !player.canOpenContainers()) {
+ return false;
+ }
var event = new ContainerOpenEvent(viewer, this);
if (!event.call()) {
diff --git a/server/src/main/java/org/allaymc/server/container/impl/DoubleChestContainerImpl.java b/server/src/main/java/org/allaymc/server/container/impl/DoubleChestContainerImpl.java
index 84fb4a51d..e4fe0e204 100644
--- a/server/src/main/java/org/allaymc/server/container/impl/DoubleChestContainerImpl.java
+++ b/server/src/main/java/org/allaymc/server/container/impl/DoubleChestContainerImpl.java
@@ -17,6 +17,7 @@
import org.allaymc.api.eventbus.event.container.ContainerOpenEvent;
import org.allaymc.api.item.ItemStack;
import org.allaymc.api.math.position.Position3ic;
+import org.allaymc.api.player.Player;
import org.cloudburstmc.nbt.NbtMap;
import java.util.*;
@@ -193,6 +194,9 @@ public boolean addViewer(ContainerViewer viewer) {
removeViewer(viewer);
return addViewer(viewer);
}
+ if (viewer instanceof Player player && !player.canOpenContainers()) {
+ return false;
+ }
var event = new ContainerOpenEvent(viewer, this);
if (!event.call()) {
diff --git a/server/src/main/java/org/allaymc/server/entity/component/player/EntityPlayerBaseComponentImpl.java b/server/src/main/java/org/allaymc/server/entity/component/player/EntityPlayerBaseComponentImpl.java
index ced229422..a9f6be17f 100644
--- a/server/src/main/java/org/allaymc/server/entity/component/player/EntityPlayerBaseComponentImpl.java
+++ b/server/src/main/java/org/allaymc/server/entity/component/player/EntityPlayerBaseComponentImpl.java
@@ -24,6 +24,7 @@
import org.allaymc.api.message.TrContainer;
import org.allaymc.api.player.GameMode;
import org.allaymc.api.player.Player;
+import org.allaymc.api.player.PlayerAbility;
import org.allaymc.api.player.Skin;
import org.allaymc.api.server.Server;
import org.allaymc.api.utils.AllayNBTUtils;
@@ -177,7 +178,7 @@ public void setController(Player controller) {
@Override
public void onPermissionChange() {
if (isActualPlayer()) {
- this.controller.viewPlayerPermission(this.controller);
+ this.controller.viewPlayerAbilities(this.controller);
}
}
@@ -205,6 +206,7 @@ public void setGameMode(GameMode gameMode) {
}
gameMode = event.getNewGameMode();
+ var oldGameMode = this.gameMode;
this.gameMode = gameMode;
this.manager.callEvent(new CPlayerGameModeChangeEvent(this.gameMode));
@@ -216,10 +218,16 @@ public void setGameMode(GameMode gameMode) {
thisPlayer.setAirSupplyTicks(thisPlayer.getAirSupplyMaxTicks());
}
+ // Update abilities
+ if (isActualPlayer()) {
+ this.controller.setAbilities(oldGameMode.getAbilities(), false);
+ this.controller.setAbilities(gameMode.getAbilities(), true);
+ }
+
if (isActualPlayer()) {
this.controller.viewPlayerGameMode(thisPlayer);
// Send permission after game mode to make overriding client's state (e.g., mayfly) possible
- this.controller.viewPlayerPermission(this.controller);
+ this.controller.viewPlayerAbilities(this.controller);
}
forEachViewers(viewer -> viewer.viewPlayerGameMode(thisPlayer));
}
@@ -229,7 +237,7 @@ public void setFlying(boolean flying) {
if (this.flying != flying) {
this.flying = flying;
if (isActualPlayer()) {
- this.controller.viewPlayerPermission(this.controller);
+ this.controller.setAbility(PlayerAbility.FLYING, flying);
}
}
}
diff --git a/server/src/main/java/org/allaymc/server/item/component/ItemBaseComponentImpl.java b/server/src/main/java/org/allaymc/server/item/component/ItemBaseComponentImpl.java
index edbdbef8e..d094991ad 100644
--- a/server/src/main/java/org/allaymc/server/item/component/ItemBaseComponentImpl.java
+++ b/server/src/main/java/org/allaymc/server/item/component/ItemBaseComponentImpl.java
@@ -384,7 +384,7 @@ protected void tryApplyBlockEntityNBT(Dimension dimension, Vector3ic placeBlockP
}
protected void tryConsumeItem(EntityPlayer player) {
- if (player == null || player.getGameMode() != GameMode.CREATIVE) {
+ if (player == null || !player.getController().hasInfiniteBlock()) {
thisItemStack.reduceCount(1);
}
}
diff --git a/server/src/main/java/org/allaymc/server/network/processor/PacketProcessorHolder.java b/server/src/main/java/org/allaymc/server/network/processor/PacketProcessorHolder.java
index 5ad099482..e97a466ad 100644
--- a/server/src/main/java/org/allaymc/server/network/processor/PacketProcessorHolder.java
+++ b/server/src/main/java/org/allaymc/server/network/processor/PacketProcessorHolder.java
@@ -148,6 +148,7 @@ private void registerInGamePacketProcessors() {
registerProcessor(ClientState.IN_GAME, new BookEditPacketProcessor());
registerProcessor(ClientState.IN_GAME, new MapInfoRequestPacketProcessor());
registerProcessor(ClientState.IN_GAME, new RequestAbilityPacketProcessor());
+ registerProcessor(ClientState.IN_GAME, new RequestPermissionsPacketProcessor());
registerProcessor(ClientState.IN_GAME, new NPCRequestPacketProcessor());
registerProcessor(ClientState.IN_GAME, new LecternUpdatePacketProcessor());
registerProcessor(ClientState.IN_GAME, new CommandBlockUpdatePacketProcessor());
diff --git a/server/src/main/java/org/allaymc/server/network/processor/ingame/InventoryTransactionPacketProcessor.java b/server/src/main/java/org/allaymc/server/network/processor/ingame/InventoryTransactionPacketProcessor.java
index d32c01273..9a5f14283 100644
--- a/server/src/main/java/org/allaymc/server/network/processor/ingame/InventoryTransactionPacketProcessor.java
+++ b/server/src/main/java/org/allaymc/server/network/processor/ingame/InventoryTransactionPacketProcessor.java
@@ -7,6 +7,7 @@
import org.allaymc.api.container.ContainerTypes;
import org.allaymc.api.entity.component.EntityLivingComponent;
import org.allaymc.api.entity.damage.DamageContainer;
+import org.allaymc.api.entity.interfaces.EntityPlayer;
import org.allaymc.api.eventbus.event.player.*;
import org.allaymc.api.player.Player;
import org.allaymc.api.world.sound.AttackSound;
@@ -85,6 +86,12 @@ public void handleSync(Player player, InventoryTransactionPacket packet, long re
clickPos, blockFace
);
+ if (!player.canInteractWithBlocks()) {
+ player.viewBlockUpdate(clickBlockPos, 0, dimension.getBlockState(clickBlockPos));
+ player.viewBlockUpdate(placeBlockPos, 0, dimension.getBlockState(placeBlockPos));
+ break;
+ }
+
var event = new PlayerInteractBlockEvent(entity, interactInfo, PlayerInteractBlockEvent.Action.RIGHT_CLICK);
if (!event.call()) {
player.viewBlockUpdate(clickBlockPos, 0, dimension.getBlockState(clickBlockPos));
@@ -116,6 +123,11 @@ public void handleSync(Player player, InventoryTransactionPacket packet, long re
break;
}
+ if (!player.canPlaceBlocks()) {
+ player.viewBlockUpdate(placeBlockPos, 0, dimension.getBlockState(placeBlockPos));
+ break;
+ }
+
if (!itemInHand.placeBlock(dimension, placeBlockPos, interactInfo)) {
var blockStateReplaced = dimension.getBlockState(placeBlockPos);
player.viewBlockUpdate(placeBlockPos, 0, blockStateReplaced);
@@ -193,6 +205,13 @@ public void handleSync(Player player, InventoryTransactionPacket packet, long re
if (!(target instanceof EntityLivingComponent damageable)) {
return;
}
+ if (target instanceof EntityPlayer) {
+ if (!player.canAttackPlayers()) {
+ return;
+ }
+ } else if (!player.canAttackMobs()) {
+ return;
+ }
var damage = itemInHand.calculateAttackDamage(entity, target);
if (damage == 0) {
diff --git a/server/src/main/java/org/allaymc/server/network/processor/ingame/PlayerActionPacketProcessor.java b/server/src/main/java/org/allaymc/server/network/processor/ingame/PlayerActionPacketProcessor.java
index 487bb8315..44839f813 100644
--- a/server/src/main/java/org/allaymc/server/network/processor/ingame/PlayerActionPacketProcessor.java
+++ b/server/src/main/java/org/allaymc/server/network/processor/ingame/PlayerActionPacketProcessor.java
@@ -22,6 +22,9 @@ public PacketSignal handleAsync(Player player, PlayerActionPacket packet, long r
log.debug("Player {} tried to start item use on without stopping", player.getOriginName());
yield PacketSignal.HANDLED;
}
+ if (!player.canInteractWithBlocks()) {
+ yield PacketSignal.HANDLED;
+ }
entity.setUsingItemOnBlock(true);
yield PacketSignal.HANDLED;
diff --git a/server/src/main/java/org/allaymc/server/network/processor/ingame/PlayerAuthInputPacketProcessor.java b/server/src/main/java/org/allaymc/server/network/processor/ingame/PlayerAuthInputPacketProcessor.java
index 2de07eb75..e8a8b82f5 100644
--- a/server/src/main/java/org/allaymc/server/network/processor/ingame/PlayerAuthInputPacketProcessor.java
+++ b/server/src/main/java/org/allaymc/server/network/processor/ingame/PlayerAuthInputPacketProcessor.java
@@ -34,6 +34,7 @@
import org.cloudburstmc.protocol.bedrock.packet.ItemStackRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.PacketSignal;
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
+import org.joml.Vector3d;
import org.joml.Vector3i;
import java.util.List;
@@ -98,7 +99,7 @@ protected void handleBlockAction(Player player, List bloc
switch (action.getAction()) {
case START_BREAK -> {
- if (isInvalidGameType(player)) {
+ if (isInvalidGameType(player) || !player.canBreakBlocks()) {
continue;
}
@@ -106,7 +107,7 @@ protected void handleBlockAction(Player player, List bloc
}
case BLOCK_CONTINUE_DESTROY -> {
// When a player switches to breaking another block halfway through breaking one
- if (isInvalidGameType(player)) {
+ if (isInvalidGameType(player) || !player.canBreakBlocks()) {
continue;
}
@@ -119,7 +120,9 @@ protected void handleBlockAction(Player player, List bloc
startBreak(player, pos.getX(), pos.getY(), pos.getZ(), action.getFace());
}
case BLOCK_PREDICT_DESTROY -> {
- if (isInvalidGameType(player)) {
+ if (isInvalidGameType(player) || !player.canBreakBlocks()) {
+ var state = player.getControlledEntity().getLocation().dimension().getBlockState(new Vector3d(pos.getX(), pos.getY(), pos.getZ()));
+ player.viewBlockUpdate(new Vector3i(pos.getX(), pos.getY(), pos.getZ()), 0, state);
continue;
}
@@ -348,10 +351,10 @@ protected void handleInputData(Player player, Set inputData
entity.exhaust(entity.isSprinting() ? 0.2f : 0.05f);
}
case START_FLYING -> {
- if (!entity.canFly()) {
+ if (!player.canFly()) {
// Reset client-side flying state
var controller = entity.getController();
- controller.viewPlayerPermission(controller);
+ controller.viewPlayerAbilities(controller);
log.warn("Player {} tried to start flying without permission", player.getOriginName());
return;
@@ -359,7 +362,15 @@ protected void handleInputData(Player player, Set inputData
entity.setFlying(true);
}
- case STOP_FLYING -> entity.setFlying(false);
+ case STOP_FLYING -> {
+ entity.setFlying(player.isAlwaysFlying());
+ if (player.isAlwaysFlying()) {
+ entity.setFlying(true);
+ player.viewPlayerAbilities(player);
+ } else {
+ entity.setFlying(false);
+ }
+ }
case MISSED_SWING -> {
var event = new PlayerPunchAirEvent(entity);
if (event.call()) {
diff --git a/server/src/main/java/org/allaymc/server/network/processor/ingame/RequestAbilityPacketProcessor.java b/server/src/main/java/org/allaymc/server/network/processor/ingame/RequestAbilityPacketProcessor.java
index 5ffd26c80..fa346d93e 100644
--- a/server/src/main/java/org/allaymc/server/network/processor/ingame/RequestAbilityPacketProcessor.java
+++ b/server/src/main/java/org/allaymc/server/network/processor/ingame/RequestAbilityPacketProcessor.java
@@ -2,6 +2,7 @@
import org.allaymc.api.player.Player;
import org.allaymc.server.network.processor.PacketProcessor;
+import org.cloudburstmc.protocol.bedrock.data.Ability;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacketType;
import org.cloudburstmc.protocol.bedrock.packet.PacketSignal;
import org.cloudburstmc.protocol.bedrock.packet.RequestAbilityPacket;
@@ -12,8 +13,29 @@
public class RequestAbilityPacketProcessor extends PacketProcessor {
@Override
- public PacketSignal handleAsync(Player player, RequestAbilityPacket packet, long receiveTime) {
- return PacketSignal.HANDLED;
+ public void handleSync(Player player, RequestAbilityPacket packet, long receiveTime) {
+ if (packet.getAbility() != Ability.FLYING || packet.getType() != Ability.Type.BOOLEAN) {
+ return;
+ }
+
+ var entity = player.getControlledEntity();
+ if (packet.isBoolValue()) {
+ if (!player.canFly()) {
+ player.viewPlayerAbilities(player);
+ return;
+ }
+
+ entity.setFlying(true);
+ return;
+ }
+
+ if (player.isAlwaysFlying()) {
+ entity.setFlying(true);
+ player.viewPlayerAbilities(player);
+ return;
+ }
+
+ entity.setFlying(false);
}
@Override
diff --git a/server/src/main/java/org/allaymc/server/network/processor/ingame/RequestPermissionsPacketProcessor.java b/server/src/main/java/org/allaymc/server/network/processor/ingame/RequestPermissionsPacketProcessor.java
new file mode 100644
index 000000000..892ba8cf5
--- /dev/null
+++ b/server/src/main/java/org/allaymc/server/network/processor/ingame/RequestPermissionsPacketProcessor.java
@@ -0,0 +1,85 @@
+package org.allaymc.server.network.processor.ingame;
+
+import org.allaymc.api.eventbus.event.server.PlayerAbilitiesUpdateRequestEvent;
+import org.allaymc.api.player.Player;
+import org.allaymc.api.player.PlayerAbility;
+import org.allaymc.api.server.Server;
+import org.allaymc.server.network.processor.PacketProcessor;
+import org.cloudburstmc.protocol.bedrock.data.Ability;
+import org.cloudburstmc.protocol.bedrock.packet.BedrockPacketType;
+import org.cloudburstmc.protocol.bedrock.packet.RequestPermissionsPacket;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * @author zernix2077
+ */
+public class RequestPermissionsPacketProcessor extends PacketProcessor {
+ protected static final PlayerAbility[] CONTROLLABLE_ABILITIES = {
+ PlayerAbility.PLACE_BLOCK,
+ PlayerAbility.BREAK_BLOCK,
+ PlayerAbility.INTERACT_BLOCK,
+ PlayerAbility.OPEN_CONTAINER,
+ PlayerAbility.ATTACK_PLAYER,
+ PlayerAbility.ATTACK_MOB
+ };
+
+ @Override
+ public void handleSync(Player player, RequestPermissionsPacket packet, long receiveTime) {
+ if (!Server.getInstance().getPlayerManager().isOperator(player)) {
+ return;
+ }
+
+ Player target = null;
+ for (var onlinePlayer : Server.getInstance().getPlayerManager().getPlayers().values()) {
+ var entity = onlinePlayer.getControlledEntity();
+ if (entity != null && entity.getUniqueId().getLeastSignificantBits() == packet.getUniqueEntityId()) {
+ target = onlinePlayer;
+ break;
+ }
+ }
+ if (target == null) {
+ return;
+ }
+
+ var updated = EnumSet.noneOf(PlayerAbility.class);
+ updated.addAll(target.getAbilities());
+ var requestedPermissions = packet.getCustomPermissions();
+ for (var ability : CONTROLLABLE_ABILITIES) {
+ if (hasNetworkAbility(requestedPermissions, ability)) {
+ updated.add(ability);
+ } else {
+ updated.remove(ability);
+ }
+ }
+
+ if (new PlayerAbilitiesUpdateRequestEvent(player, target, updated).call()) {
+ target.setAbilities(updated);
+ }
+ }
+
+ protected boolean hasNetworkAbility(Set abilities, PlayerAbility ability) {
+ if (abilities == null) {
+ return false;
+ }
+
+ return abilities.contains(switch (ability) {
+ case PLACE_BLOCK -> Ability.BUILD;
+ case BREAK_BLOCK -> Ability.MINE;
+ case INTERACT_BLOCK -> Ability.DOORS_AND_SWITCHES;
+ case OPEN_CONTAINER -> Ability.OPEN_CONTAINERS;
+ case ATTACK_PLAYER -> Ability.ATTACK_PLAYERS;
+ case ATTACK_MOB -> Ability.ATTACK_MOBS;
+ case FLYING -> Ability.FLYING;
+ case MAY_FLY -> Ability.MAY_FLY;
+ case INFINITE_BLOCK -> Ability.INSTABUILD;
+ case NO_CLIP -> Ability.NO_CLIP;
+ });
+ }
+
+ @Override
+ public BedrockPacketType getPacketType() {
+ return BedrockPacketType.REQUEST_PERMISSIONS;
+ }
+}
diff --git a/server/src/main/java/org/allaymc/server/player/AllayPlayer.java b/server/src/main/java/org/allaymc/server/player/AllayPlayer.java
index 3074fc667..5800cb48c 100644
--- a/server/src/main/java/org/allaymc/server/player/AllayPlayer.java
+++ b/server/src/main/java/org/allaymc/server/player/AllayPlayer.java
@@ -40,6 +40,7 @@
import org.allaymc.api.entity.interfaces.*;
import org.allaymc.api.entity.type.EntityTypes;
import org.allaymc.api.eventbus.event.server.PlayerDisconnectEvent;
+import org.allaymc.api.eventbus.event.server.PlayerAbilitiesUpdateEvent;
import org.allaymc.api.eventbus.event.server.PlayerLoginEvent;
import org.allaymc.api.eventbus.event.server.PlayerSpawnEvent;
import org.allaymc.api.form.type.CustomForm;
@@ -218,6 +219,12 @@ public class AllayPlayer implements Player {
// Fog
protected final List fogStack = new ArrayList<>();
+ // Abilities
+ protected final EnumSet abilities;
+ protected boolean shouldSendAbilities;
+ protected boolean immutableWorld;
+ protected boolean alwaysFlying;
+
// NetEase
@Getter
@Setter
@@ -251,6 +258,9 @@ public AllayPlayer(BedrockServerSession session, AllayNetworkInterface sourceInt
// Hud
this.hiddenHudElements = EnumSet.noneOf(HudElement.class);
+
+ // Abilities
+ this.abilities = EnumSet.noneOf(PlayerAbility.class);
}
protected static LevelChunkPacket createSubChunkLevelChunkPacket(AllayUnsafeChunk chunk, ChunkCache cache, UUID playerId, int cacheGen) {
@@ -346,6 +356,13 @@ public void tick(long currentTick) {
this.sendHudElements();
this.shouldSendHudElements = false;
}
+
+ if (this.shouldSendAbilities &&
+ this.controlledEntity != null &&
+ getClientState().ordinal() >= ClientState.SPAWNED.ordinal()) {
+ broadcastPlayerAbilities();
+ this.shouldSendAbilities = false;
+ }
}
public void handlePacketSync(BedrockPacket packet, long receiveTime) {
@@ -384,8 +401,12 @@ protected void onFullyJoin() {
viewContainerContents(this.controlledEntity.getContainer(ContainerTypes.INVENTORY));
viewContainerContents(this.controlledEntity.getContainer(ContainerTypes.OFFHAND));
viewContainerContents(this.controlledEntity.getContainer(ContainerTypes.ARMOR));
- viewPlayerPermission(this);
+ viewPlayerAbilities(this);
viewPlayerListChange(playerManager.getPlayers().values(), true);
+ playerManager.getPlayers().values().stream()
+ .filter(player -> player != this && player.getControlledEntity() != null)
+ .forEach(this::viewPlayerAbilities);
+ broadcastPlayerAbilities();
sendSpeed(this.speed);
sendExperienceLevel(this.controlledEntity.getExperienceLevel());
@@ -491,17 +512,11 @@ public void removeEntity(Entity entity) {
public void viewPlayerGameMode(EntityPlayer player) {
var gameMode = player.getGameMode();
if (this.controlledEntity == player) {
- var packet1 = new SetPlayerGameTypePacket();
- packet1.setGamemode(toNetwork(player.getGameMode()).ordinal());
- sendPacket(packet1);
-
- var packet2 = new UpdateAdventureSettingsPacket();
- packet2.setNoPvM(gameMode == GameMode.SPECTATOR);
- packet2.setNoMvP(gameMode == GameMode.SPECTATOR);
- packet2.setShowNameTags(gameMode != GameMode.SPECTATOR);
- packet2.setImmutableWorld(gameMode == GameMode.SPECTATOR);
- packet2.setAutoJump(true);
- sendPacket(packet2);
+ var packet = new SetPlayerGameTypePacket();
+ packet.setGamemode(toNetwork(player.getGameMode()).ordinal());
+ sendPacket(packet);
+
+ sendAdventureSettings();
} else {
var packet = new UpdatePlayerGameTypePacket();
packet.setGameType(toNetwork(player.getGameMode()));
@@ -2957,7 +2972,7 @@ public int getPing() {
}
@Override
- public void viewPlayerPermission(Player player) {
+ public void viewPlayerAbilities(Player player) {
var packet = new UpdateAbilitiesPacket();
var entity = Preconditions.checkNotNull(player.getControlledEntity());
@@ -2966,12 +2981,12 @@ public void viewPlayerPermission(Player player) {
// If this player does not have specific command permissions, the command description won't even be sent to the client
packet.setCommandPermission(entity.hasPermission(Permissions.ABILITY_OPERATOR_COMMAND_QUICK_BAR).asBoolean() ? CommandPermission.GAME_DIRECTORS : CommandPermission.ANY);
// PlayerPermissions is the permission level of the player as it shows up in the player list built up using the PlayerList packet
- packet.setPlayerPermission(calculatePlayerPermission(entity));
+ packet.setPlayerPermission(calculatePlayerPermission(player));
var layer = new AbilityLayer();
layer.setLayerType(AbilityLayer.Type.BASE);
layer.getAbilitiesSet().addAll(Arrays.asList(Ability.values()));
- layer.getAbilityValues().addAll(calculateAbilities(entity));
+ layer.getAbilityValues().addAll(calculateAbilities(player));
// NOTICE: this shouldn't be changed
layer.setWalkSpeed((float) DEFAULT_SPEED.calculate());
layer.setFlySpeed((float) player.getFlySpeed().calculate());
@@ -2981,56 +2996,341 @@ public void viewPlayerPermission(Player player) {
sendPacket(packet);
if (player == this) {
+ sendAdventureSettings();
this.shouldSendCommands = true;
+ this.shouldSendAbilities = false;
}
}
- protected EnumSet calculateAbilities(EntityPlayer player) {
- var gameMode = player.getGameMode();
+ protected void broadcastPlayerAbilities() {
+ if (this.controlledEntity == null) {
+ return;
+ }
+ viewPlayerAbilities(this);
+ this.controlledEntity.forEachViewers(viewer -> {
+ if (viewer instanceof Player playerViewer && playerViewer != this) {
+ playerViewer.viewPlayerAbilities(this);
+ }
+ });
+ }
+
+ protected EnumSet calculateAbilities(Player player) {
var abilities = EnumSet.noneOf(Ability.class);
- abilities.add(Ability.TELEPORT);
- abilities.add(Ability.WALK_SPEED);
- abilities.add(Ability.FLY_SPEED);
- abilities.add(Ability.VERTICAL_FLY_SPEED);
-
- if (gameMode != GameMode.SPECTATOR) {
- abilities.add(Ability.BUILD);
- abilities.add(Ability.MINE);
- abilities.add(Ability.DOORS_AND_SWITCHES);
- abilities.add(Ability.OPEN_CONTAINERS);
- abilities.add(Ability.ATTACK_PLAYERS);
- abilities.add(Ability.ATTACK_MOBS);
- } else {
- abilities.add(Ability.NO_CLIP);
- abilities.add(Ability.FLYING);
+ for (var ability : player.getAbilities()) {
+ abilities.add(toNetworkAbility(ability));
}
- if (gameMode == GameMode.CREATIVE) {
- abilities.add(Ability.INSTABUILD);
+ if (player.getControlledEntity() != null && player.getControlledEntity().hasPermission(Permissions.ABILITY_OPERATOR_COMMAND_QUICK_BAR).asBoolean()) {
+ abilities.add(Ability.OPERATOR_COMMANDS);
}
- if (player.canFly()) {
- abilities.add(Ability.MAY_FLY);
+ if (!player.canFly()) {
+ abilities.remove(Ability.MAY_FLY);
+ abilities.remove(Ability.FLYING);
}
- if (player.isFlying() && abilities.contains(Ability.MAY_FLY)) {
+ if (player.isAlwaysFlying()) {
+ abilities.add(Ability.MAY_FLY);
abilities.add(Ability.FLYING);
}
- if (player.isActualPlayer() && Server.getInstance().getPlayerManager().isOperator(player.getController())) {
- abilities.add(Ability.OPERATOR_COMMANDS);
+ return abilities;
+ }
+
+ protected PlayerPermission calculatePlayerPermission(Player player) {
+ var build = player.canPlaceBlocks();
+ var mine = player.canBreakBlocks();
+ var doorsAndSwitches = player.canInteractWithBlocks();
+ var openContainers = player.canOpenContainers();
+ var attackPlayers = player.canAttackPlayers();
+ var attackMobs = player.canAttackMobs();
+
+ if (
+ Server.getInstance().getPlayerManager().isOperator(player) &&
+ build && mine && doorsAndSwitches && openContainers && attackPlayers && attackMobs
+ ) {
+ return PlayerPermission.OPERATOR;
+ }
+
+ if (build && mine && doorsAndSwitches && openContainers && attackPlayers && attackMobs) {
+ return PlayerPermission.MEMBER;
+ }
+
+ if (!build && !mine && !doorsAndSwitches && !openContainers && !attackPlayers && !attackMobs) {
+ return PlayerPermission.VISITOR;
}
+ return PlayerPermission.CUSTOM;
+ }
+
+ protected void sendAdventureSettings() {
+ if (this.controlledEntity == null || getClientState().ordinal() < ClientState.SPAWNED.ordinal()) {
+ return;
+ }
+
+ var packet = new UpdateAdventureSettingsPacket();
+ packet.setNoPvM(!canAttackMobs());
+ packet.setNoMvP(!canAttackMobs());
+ packet.setShowNameTags(this.controlledEntity.getGameMode() != GameMode.SPECTATOR);
+ packet.setImmutableWorld(isImmutableWorld());
+ packet.setAutoJump(true);
+ sendPacket(packet);
+ }
+
+ protected static EnumSet createBaseAbilitySet() {
+ return EnumSet.noneOf(PlayerAbility.class);
+ }
+
+ protected static EnumSet abilitiesFromPermission(PlayerPermission permission) {
+ var abilities = createBaseAbilitySet();
+ switch (permission) {
+ case OPERATOR -> abilities.addAll(EnumSet.of(
+ PlayerAbility.PLACE_BLOCK,
+ PlayerAbility.BREAK_BLOCK,
+ PlayerAbility.INTERACT_BLOCK,
+ PlayerAbility.OPEN_CONTAINER,
+ PlayerAbility.ATTACK_PLAYER,
+ PlayerAbility.ATTACK_MOB
+ ));
+ case MEMBER -> abilities.addAll(EnumSet.of(
+ PlayerAbility.PLACE_BLOCK,
+ PlayerAbility.BREAK_BLOCK,
+ PlayerAbility.INTERACT_BLOCK,
+ PlayerAbility.OPEN_CONTAINER,
+ PlayerAbility.ATTACK_PLAYER,
+ PlayerAbility.ATTACK_MOB
+ ));
+ case VISITOR, CUSTOM -> {
+ // Keep only the common non-permission abilities.
+ }
+ }
return abilities;
}
- protected PlayerPermission calculatePlayerPermission(EntityPlayer player) {
- if (player.isActualPlayer() && Server.getInstance().getPlayerManager().isOperator(player.getController())) {
- return PlayerPermission.OPERATOR;
+ protected Ability toNetworkAbility(PlayerAbility ability) {
+ return switch (ability) {
+ case PLACE_BLOCK -> Ability.BUILD;
+ case BREAK_BLOCK -> Ability.MINE;
+ case INTERACT_BLOCK -> Ability.DOORS_AND_SWITCHES;
+ case OPEN_CONTAINER -> Ability.OPEN_CONTAINERS;
+ case ATTACK_PLAYER -> Ability.ATTACK_PLAYERS;
+ case ATTACK_MOB -> Ability.ATTACK_MOBS;
+ case FLYING -> Ability.FLYING;
+ case MAY_FLY -> Ability.MAY_FLY;
+ case INFINITE_BLOCK -> Ability.INSTABUILD;
+ case NO_CLIP -> Ability.NO_CLIP;
+ };
+ }
+
+ protected void scheduleAbilitiesUpdate(Set previous) {
+ if (this.controlledEntity != null) {
+ new PlayerAbilitiesUpdateEvent(this, previous, this.abilities).call();
+ }
+ this.shouldSendAbilities = true;
+ }
+
+ @Override
+ public @UnmodifiableView Set getAbilities() {
+ return Collections.unmodifiableSet(abilities);
+ }
+
+ @Override
+ public boolean hasAbility(PlayerAbility ability) {
+ return this.abilities.contains(ability);
+ }
+
+ @Override
+ public void setAbility(PlayerAbility ability, boolean value) {
+ if (this.abilities.contains(ability) == value) {
+ return;
+ }
+
+ var wasAlwaysFlying = isAlwaysFlying();
+ var couldFly = canFly();
+
+ var snap = EnumSet.copyOf(this.abilities);
+ if (value) {
+ this.abilities.add(ability);
+ } else {
+ this.abilities.remove(ability);
+ }
+ scheduleAbilitiesUpdate(snap);
+
+ syncFlyingState(wasAlwaysFlying, couldFly);
+ }
+
+ @Override
+ public void setAbilities(Set abilities, boolean value) {
+ if ((value && this.abilities.containsAll(abilities)) || (!value && Collections.disjoint(this.abilities, abilities))) {
+ return;
+ }
+
+ var wasAlwaysFlying = isAlwaysFlying();
+ var couldFly = canFly();
+
+ var snap = EnumSet.copyOf(this.abilities);
+ if (value) {
+ this.abilities.addAll(abilities);
+ } else {
+ this.abilities.removeAll(abilities);
+ }
+ scheduleAbilitiesUpdate(snap);
+
+ syncFlyingState(wasAlwaysFlying, couldFly);
+ }
+
+ @Override
+ public void setAbilities(Set abilities) {
+ if (this.abilities.equals(abilities)) {
+ return;
+ }
+
+ var wasAlwaysFlying = isAlwaysFlying();
+ var couldFly = canFly();
+
+ var snap = EnumSet.copyOf(this.abilities);
+ this.abilities.clear();
+ this.abilities.addAll(abilities);
+ scheduleAbilitiesUpdate(snap);
+
+ syncFlyingState(wasAlwaysFlying, couldFly);
+ }
+
+ @Override
+ public boolean canPlaceBlocks() {
+ if (isImmutableWorld()) {
+ return false;
+ }
+ if (this.controlledEntity != null && this.controlledEntity.getGameMode() == GameMode.ADVENTURE) {
+ return false;
+ }
+ if (Server.getInstance().getPlayerManager().isOperator(this)) {
+ return true;
+ }
+ return hasAbility(PlayerAbility.PLACE_BLOCK);
+ }
+
+ @Override
+ public boolean canBreakBlocks() {
+ if (isImmutableWorld()) {
+ return false;
+ }
+ if (this.controlledEntity != null && this.controlledEntity.getGameMode() == GameMode.ADVENTURE) {
+ return false;
+ }
+ if (Server.getInstance().getPlayerManager().isOperator(this)) {
+ return true;
+ }
+ return hasAbility(PlayerAbility.BREAK_BLOCK);
+ }
+
+ @Override
+ public boolean canInteractWithBlocks() {
+ if (isImmutableWorld()) {
+ return false;
+ }
+ if (this.controlledEntity != null && this.controlledEntity.getGameMode() == GameMode.SPECTATOR) {
+ return false;
+ }
+ if (Server.getInstance().getPlayerManager().isOperator(this)) {
+ return true;
+ }
+ return hasAbility(PlayerAbility.INTERACT_BLOCK);
+ }
+
+ @Override
+ public boolean canOpenContainers() {
+ if (this.controlledEntity != null && this.controlledEntity.getGameMode() == GameMode.SPECTATOR) {
+ return false;
+ }
+ if (Server.getInstance().getPlayerManager().isOperator(this)) {
+ return true;
+ }
+ return hasAbility(PlayerAbility.OPEN_CONTAINER);
+ }
+
+ @Override
+ public boolean canAttackPlayers() {
+ if (this.controlledEntity != null && this.controlledEntity.getGameMode() == GameMode.SPECTATOR) {
+ return false;
+ }
+ if (Server.getInstance().getPlayerManager().isOperator(this)) {
+ return true;
+ }
+ return hasAbility(PlayerAbility.ATTACK_PLAYER);
+ }
+
+ @Override
+ public boolean canAttackMobs() {
+ if (this.controlledEntity != null && this.controlledEntity.getGameMode() == GameMode.SPECTATOR) {
+ return false;
+ }
+ if (Server.getInstance().getPlayerManager().isOperator(this)) {
+ return true;
+ }
+ return hasAbility(PlayerAbility.ATTACK_MOB);
+ }
+
+ @Override
+ public boolean canFly() {
+ return this.controlledEntity != null && (isAlwaysFlying() || hasAbility(PlayerAbility.MAY_FLY));
+ }
+
+ @Override
+ public boolean hasInfiniteBlock() {
+ return hasAbility(PlayerAbility.INFINITE_BLOCK);
+ }
+
+ @Override
+ public boolean isNoClip() {
+ return this.controlledEntity != null && (hasAbility(PlayerAbility.NO_CLIP) || this.controlledEntity.getGameMode() == GameMode.SPECTATOR);
+ }
+
+ @Override
+ public boolean isImmutableWorld() {
+ if (Server.getInstance().getPlayerManager().isOperator(this)) {
+ return false;
+ }
+
+ if (this.immutableWorld) {
+ return true;
+ }
+
+ if (this.controlledEntity != null) {
+ return this.controlledEntity.getGameMode() == GameMode.SPECTATOR || this.controlledEntity.getGameMode() == GameMode.ADVENTURE;
}
- return PlayerPermission.MEMBER;
+ return false;
+ }
+
+ @Override
+ public void setImmutableWorld(boolean immutableWorld) {
+ if (this.immutableWorld != immutableWorld) {
+ this.immutableWorld = immutableWorld;
+ this.shouldSendAbilities = true;
+ }
+ }
+
+ @Override
+ public boolean isAlwaysFlying() {
+ return this.alwaysFlying || hasAbility(PlayerAbility.NO_CLIP) || (this.controlledEntity != null && this.controlledEntity.getGameMode() == GameMode.SPECTATOR);
+ }
+
+ @Override
+ public void setAlwaysFlying(boolean alwaysFlying) {
+ var wasAlwaysFlying = this.alwaysFlying;
+ this.alwaysFlying = alwaysFlying;
+ this.shouldSendAbilities = true;
+ syncFlyingState(wasAlwaysFlying, canFly());
+ }
+
+ protected void syncFlyingState(boolean wasAlwaysFlying, boolean couldFly) {
+ if (!wasAlwaysFlying && isAlwaysFlying()) {
+ this.controlledEntity.setFlying(true);
+ } else if (couldFly && !canFly()) {
+ this.controlledEntity.setFlying(false);
+ }
}
@Override
@@ -3071,7 +3371,7 @@ protected void sendSpeed(Speed speed) {
public void setFlySpeed(Speed flySpeed) {
if (!this.flySpeed.equals(flySpeed)) {
this.flySpeed = flySpeed;
- viewPlayerPermission(this);
+ viewPlayerAbilities(this);
}
}
@@ -3079,7 +3379,7 @@ public void setFlySpeed(Speed flySpeed) {
public void setVerticalFlySpeed(Speed verticalFlySpeed) {
if (!this.verticalFlySpeed.equals(verticalFlySpeed)) {
this.verticalFlySpeed = verticalFlySpeed;
- viewPlayerPermission(this);
+ viewPlayerAbilities(this);
}
}
@@ -3157,6 +3457,17 @@ public void spawnEntityPlayer() {
currentPos = readVector3f(playerData.getNbt(), "Pos");
}
+ var storedAbilities = playerData.getAbilities();
+ if (storedAbilities == null) {
+ var permissionName = AllayServer.getSettings().genericSettings().defaultPermission().toUpperCase();
+ this.abilities.addAll(abilitiesFromPermission(PlayerPermission.valueOf(permissionName)));
+
+ var gameMode = GameMode.from(playerData.getNbt().getInt("PlayerGameMode", NetworkHelper.toNetwork(dimension.getWorld().getWorldData().getGameMode()).ordinal()));
+ this.abilities.addAll(gameMode.getAbilities());
+ } else {
+ this.abilities.addAll(storedAbilities);
+ }
+
this.controlledEntity = EntityTypes.PLAYER.createEntity();
this.controlledEntity.setSkin(this.loginData.getSkin());
this.controlledEntity.setDisplayName(loginData.getXname());
@@ -3237,7 +3548,7 @@ protected void startGame(World spawnWorld, PlayerData playerData, Dimension dime
packet.getGamerules().addAll(NetworkHelper.toNetwork(spawnWorld.getWorldData().getGameRules().getGameRules()));
packet.setUniqueEntityId(this.controlledEntity.getUniqueId().getLeastSignificantBits());
packet.setRuntimeEntityId(this.controlledEntity.getRuntimeId());
- packet.setPlayerGameType(GameType.from(playerData.getNbt().getInt("GameType", NetworkHelper.toNetwork(spawnWorld.getWorldData().getGameMode()).ordinal())));
+ packet.setPlayerGameType(GameType.from(playerData.getNbt().getInt("PlayerGameMode", NetworkHelper.toNetwork(spawnWorld.getWorldData().getGameMode()).ordinal())));
var loc = this.controlledEntity.getLocation();
var worldSpawn = spawnWorld.getWorldData().getSpawnPoint();
packet.setDefaultSpawn(Vector3i.from(worldSpawn.x(), worldSpawn.y(), worldSpawn.z()));
diff --git a/server/src/main/java/org/allaymc/server/player/AllayPlayerManager.java b/server/src/main/java/org/allaymc/server/player/AllayPlayerManager.java
index f8c809c0d..b58eea8fa 100644
--- a/server/src/main/java/org/allaymc/server/player/AllayPlayerManager.java
+++ b/server/src/main/java/org/allaymc/server/player/AllayPlayerManager.java
@@ -265,7 +265,7 @@ public void setOperator(String uuidOrName, boolean value) {
players.values().stream()
.filter(p -> p.getLoginData().getUuid().toString().equals(uuidOrName) || p.getOriginName().equals(uuidOrName))
.findFirst()
- .ifPresent(player -> player.viewPlayerPermission(player));
+ .ifPresent(player -> this.players.values().forEach(viewer -> viewer.viewPlayerAbilities(player)));
}
public void startNetworkInterfaces() {