diff --git a/.gitignore b/.gitignore index 621429a..e0e1035 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,9 @@ out/ plugin/bin/ api/bin/ +# VSCode +.vscode/ + # Compiled class file *.class diff --git a/README.md b/README.md index 6806e14..9965de8 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,13 @@ Serverside component for Axiom ## Download https://modrinth.com/plugin/axiom-paper-plugin/ + +## FAQ + +**Axiom works in singleplayer but not when I connect to a multiplayer server running the Axiom Paper Plugin. What gives?** + +First, the player must be an op on the server. If the player does not have op permissions, run `/op `. This player must then disconnect from the server and reconnect. + +If you're using an alternative solution for permission management, you must give players the `axiom.*` permission. + +If players continue to have issues, they can run the `/whynoaxiom` command for more information. diff --git a/build.gradle.kts b/build.gradle.kts index 157957d..ad9212f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,43 +1,52 @@ plugins { `java-library` - id("io.papermc.paperweight.userdev") version "1.5.11" - id("xyz.jpenilla.run-paper") version "2.2.2" // Adds runServer and runMojangMappedServer tasks for testing + alias(libs.plugins.paperweight.userdev) + alias(libs.plugins.run.paper) // Adds runServer and runMojangMappedServer tasks for testing // Shades and relocates dependencies into our plugin jar. See https://imperceptiblethoughts.com/shadow/introduction/ - id("com.github.johnrengelman.shadow") version "8.1.1" + alias(libs.plugins.shadow) } group = "com.moulberry.axiom" -version = "1.5.9" +version = "1.5.11" description = "Serverside component for Axiom on Paper" java { - // Configure the java toolchain. This allows gradle to auto-provision JDK 17 on systems that only have JDK 8 installed for example. - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) + // Configure the java toolchain. This allows gradle to auto-provision JDK 21 on systems that only have JDK 11 installed for example. + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } repositories { mavenCentral() + maven("https://repo.viaversion.com") maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") maven("https://jitpack.io") maven("https://repo.papermc.io/repository/maven-public/") maven("https://maven.enginehub.org/repo/") + maven("https://maven.playpro.com") } dependencies { - paperweight.paperDevBundle("1.20.4-R0.1-SNAPSHOT") - implementation("xyz.jpenilla:reflection-remapper:0.1.0-SNAPSHOT") + paperweight.paperDevBundle(libs.versions.paper) + implementation(libs.reflection.remapper) + implementation(libs.cloud.paper) // Zstd Compression Library - implementation("com.github.luben:zstd-jni:1.5.5-4") + implementation(libs.zstd.jni) + + // ViaVersion support + compileOnly(libs.viaversion.api) // WorldGuard support - compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.1.0-SNAPSHOT") + compileOnly(libs.worldguard.bukkit) // PlotSquared support - implementation(platform("com.intellectualsites.bom:bom-newest:1.37")) - compileOnly("com.intellectualsites.plotsquared:plotsquared-core") - compileOnly("com.intellectualsites.plotsquared:plotsquared-bukkit") { isTransitive = false } + implementation(platform(libs.bom.newest)) + compileOnly(libs.plotsquared.core) + compileOnly(libs.plotsquared.bukkit) { isTransitive = false } + + // CoreProtect support + compileOnly(libs.coreprotect) } tasks { @@ -51,7 +60,7 @@ tasks { // Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable. // See https://openjdk.java.net/jeps/247 for more information. - options.release.set(17) + options.release.set(21) } javadoc { options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..3939ced --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,32 @@ +[versions] +# Dependencies +bom-newest = "1.37" +cloud-paper = "2.0.0-20240516.054251-69" +coreprotect = "22.4" +paper = "1.20.6-R0.1-SNAPSHOT" +plotsquared = "7.3.9-20240513.192211-13" +reflection-remapper = "0.1.2-20240315.033304-2" +viaversion-api = "4.10.1-20240505.124211-22" +worldguard-bukkit = "7.1.0-20240503.180049-12" +zstd-jni = "1.5.5-4" + +# Plugins +paperweight-userdev = "1.7.1" +run-paper = "2.2.4" +shadow = "8.1.7" + +[libraries] +bom-newest = { group = "com.intellectualsites.bom", name = "bom-newest", version.ref = "bom-newest" } +cloud-paper = { group = "org.incendo", name = "cloud-paper", version.ref = "cloud-paper" } +coreprotect = { group = "net.coreprotect", name = "coreprotect", version.ref = "coreprotect" } +plotsquared-bukkit = { group = "com.intellectualsites.plotsquared", name = "plotsquared-bukkit", version.ref = "plotsquared" } +plotsquared-core = { group = "com.intellectualsites.plotsquared", name = "plotsquared-core", version.ref = "plotsquared" } +reflection-remapper = { group = "xyz.jpenilla", name = "reflection-remapper", version.ref = "reflection-remapper" } +viaversion-api = { group = "com.viaversion", name = "viaversion-api", version.ref = "viaversion-api" } +worldguard-bukkit = { group = "com.sk89q.worldguard", name = "worldguard-bukkit", version.ref = "worldguard-bukkit" } +zstd-jni = { group = "com.github.luben", name = "zstd-jni", version = "1.5.5-4" } + +[plugins] +paperweight-userdev = { id = "io.papermc.paperweight.userdev", version.ref = "paperweight-userdev" } +run-paper = { id = "xyz.jpenilla.run-paper", version.ref = "run-paper" } +shadow = { id = "io.github.goooler.shadow", version.ref = "shadow" } \ No newline at end of file diff --git a/src/main/java/com/moulberry/axiom/AxiomPaper.java b/src/main/java/com/moulberry/axiom/AxiomPaper.java index 44f6919..feb4050 100644 --- a/src/main/java/com/moulberry/axiom/AxiomPaper.java +++ b/src/main/java/com/moulberry/axiom/AxiomPaper.java @@ -3,8 +3,10 @@ package com.moulberry.axiom; import com.google.common.util.concurrent.RateLimiter; import com.moulberry.axiom.blueprint.ServerBlueprintManager; import com.moulberry.axiom.buffer.CompressedBlockEntity; +import com.moulberry.axiom.commands.AxiomDebugCommand; import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent; import com.moulberry.axiom.event.AxiomModifyWorldEvent; +import com.moulberry.axiom.integration.coreprotect.CoreProtectIntegration; import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; import com.moulberry.axiom.packet.*; import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; @@ -15,17 +17,19 @@ import io.papermc.paper.event.world.WorldGameRuleChangeEvent; import io.papermc.paper.network.ChannelInitializeListener; import io.papermc.paper.network.ChannelInitializeListenerHolder; import net.kyori.adventure.key.Key; +import net.minecraft.SharedConstants; import net.minecraft.core.BlockPos; import net.minecraft.core.IdMapper; -import net.minecraft.network.Connection; -import net.minecraft.network.ConnectionProtocol; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.*; import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.DiscardedPayload; +import net.minecraft.network.protocol.game.GameProtocols; +import net.minecraft.network.protocol.game.ServerGamePacketListener; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.block.state.BlockState; import org.bukkit.*; +import org.bukkit.command.CommandSender; import org.bukkit.configuration.Configuration; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -34,9 +38,13 @@ import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.messaging.Messenger; import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.bukkit.CloudBukkitCapabilities; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.paper.LegacyPaperCommandManager; import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.lang.ref.WeakReference; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -49,6 +57,8 @@ public class AxiomPaper extends JavaPlugin implements Listener { public final Set activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>()); public final Map playerBlockBufferRateLimiters = new ConcurrentHashMap<>(); public final Map playerRestrictions = new ConcurrentHashMap<>(); + public final Map> playerBlockRegistry = new ConcurrentHashMap<>(); + public final Map playerProtocolVersion = new ConcurrentHashMap<>(); public Configuration configuration; public IdMapper allowedBlockRegistry = null; @@ -151,21 +161,16 @@ public class AxiomPaper extends JavaPlugin implements Listener { RequestChunkDataPacketListener requestChunkDataPacketListener = allowLargeChunkDataRequest ? new RequestChunkDataPacketListener(this) : null; + // Hack to figure out the id of the CustomPayload packet + ProtocolInfo protocol = GameProtocols.SERVERBOUND.bind(k -> new RegistryFriendlyByteBuf(k, + MinecraftServer.getServer().registryAccess())); + RegistryFriendlyByteBuf friendlyByteBuf = new RegistryFriendlyByteBuf(Unpooled.buffer(), MinecraftServer.getServer().registryAccess()); + protocol.codec().encode(friendlyByteBuf, new ServerboundCustomPayloadPacket(new DiscardedPayload(new ResourceLocation("dummy"), Unpooled.buffer()))); + int payloadId = friendlyByteBuf.readVarInt(); + ChannelInitializeListenerHolder.addListener(Key.key("axiom:handle_big_payload"), new ChannelInitializeListener() { @Override public void afterInitChannel(@NonNull Channel channel) { - var packets = ConnectionProtocol.PLAY.getPacketsByIds(PacketFlow.SERVERBOUND); - int payloadId = -1; - for (Map.Entry>> entry : packets.entrySet()) { - if (entry.getValue() == ServerboundCustomPayloadPacket.class) { - payloadId = entry.getKey(); - break; - } - } - if (payloadId < 0) { - throw new RuntimeException("Failed to find ServerboundCustomPayloadPacket id"); - } - Connection connection = (Connection) channel.pipeline().get("packet_handler"); channel.pipeline().addBefore("decoder", "axiom-big-payload-handler", new AxiomBigPayloadHandler(payloadId, connection, setBlockBufferPacketListener, @@ -213,32 +218,31 @@ public class AxiomPaper extends JavaPlugin implements Listener { send = true; } - BlockPos boundsMin = null; - BlockPos boundsMax = null; + Set bounds = Set.of(); if (!player.hasPermission("axiom.allow_copying_other_plots")) { if (PlotSquaredIntegration.isPlotWorld(player.getWorld())) { PlotSquaredIntegration.PlotBounds editable = PlotSquaredIntegration.getCurrentEditablePlot(player); if (editable != null) { restrictions.lastPlotBounds = editable; - boundsMin = editable.min(); - boundsMax = editable.max(); + bounds = editable.boxes(); } else if (restrictions.lastPlotBounds != null && restrictions.lastPlotBounds.worldName().equals(player.getWorld().getName())) { - boundsMin = restrictions.lastPlotBounds.min(); - boundsMax = restrictions.lastPlotBounds.max(); + bounds = restrictions.lastPlotBounds.boxes(); } else { - boundsMin = BlockPos.ZERO; - boundsMax = BlockPos.ZERO; + bounds = Set.of(new PlotSquaredIntegration.PlotBox(BlockPos.ZERO, BlockPos.ZERO)); } } - int min = Integer.MIN_VALUE; - int max = Integer.MAX_VALUE; - if (boundsMin != null && boundsMax != null && - boundsMin.getX() == min && boundsMin.getY() == min && boundsMin.getZ() == min && - boundsMax.getX() == max && boundsMax.getY() == max && boundsMax.getZ() == max) { - boundsMin = null; - boundsMax = null; + if (bounds.size() == 1) { + PlotSquaredIntegration.PlotBox plotBounds = bounds.iterator().next(); + + int min = Integer.MIN_VALUE; + int max = Integer.MAX_VALUE; + + if (plotBounds.min().getX() == min && plotBounds.min().getY() == min && plotBounds.min().getZ() == min && + plotBounds.max().getX() == max && plotBounds.max().getY() == max && plotBounds.max().getZ() == max) { + bounds = Set.of(); + } } } @@ -246,12 +250,10 @@ public class AxiomPaper extends JavaPlugin implements Listener { if (restrictions.maxSectionsPerSecond != rateLimit || restrictions.canImportBlocks != allowImportingBlocks || - !Objects.equals(restrictions.boundsMin, boundsMin) || - !Objects.equals(restrictions.boundsMax, boundsMax)) { + !Objects.equals(restrictions.bounds, bounds)) { restrictions.maxSectionsPerSecond = rateLimit; restrictions.canImportBlocks = allowImportingBlocks; - restrictions.boundsMin = boundsMin; - restrictions.boundsMax = boundsMax; + restrictions.bounds = bounds; send = true; } @@ -265,6 +267,8 @@ public class AxiomPaper extends JavaPlugin implements Listener { activeAxiomPlayers.retainAll(stillActiveAxiomPlayers); playerBlockBufferRateLimiters.keySet().retainAll(stillActiveAxiomPlayers); playerRestrictions.keySet().retainAll(stillActiveAxiomPlayers); + playerBlockRegistry.keySet().retainAll(stillActiveAxiomPlayers); + playerProtocolVersion.keySet().retainAll(stillActiveAxiomPlayers); }, 20, 20); boolean sendMarkers = configuration.getBoolean("send-markers"); @@ -274,6 +278,25 @@ public class AxiomPaper extends JavaPlugin implements Listener { Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> { WorldExtension.tick(MinecraftServer.getServer(), sendMarkers, maxChunkRelightsPerTick, maxChunkSendsPerTick); }, 1, 1); + + try { + LegacyPaperCommandManager manager = LegacyPaperCommandManager.createNative( + this, + ExecutionCoordinator.simpleCoordinator() + ); + + if (manager.hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) { + manager.registerBrigadier(); + } + + AxiomDebugCommand.register(this, manager); + } catch (Exception e) { + e.printStackTrace(); + } + + if (CoreProtectIntegration.isEnabled()) { + this.getLogger().info("CoreProtect integration enabled"); + } } public boolean logLargeBlockBufferChanges() { @@ -281,17 +304,46 @@ public class AxiomPaper extends JavaPlugin implements Listener { } public boolean hasAxiomPermission(Player player) { - return player.hasPermission("axiom.*") || player.isOp(); + return hasAxiomPermission(player, null, false); + } + + public boolean hasAxiomPermission(Player player, String permission, boolean strict) { + if (player.hasPermission("axiom.*") || player.isOp()) { + return !strict || permission == null || player.hasPermission("axiom.all") || player.hasPermission(permission); + } else if (permission != null && !player.hasPermission(permission)) { + return false; + } + return player.hasPermission("axiom.use"); } public boolean canUseAxiom(Player player) { - return hasAxiomPermission(player) && activeAxiomPlayers.contains(player.getUniqueId()); + return canUseAxiom(player, null, false); + } + + public boolean canUseAxiom(Player player, String permission) { + return canUseAxiom(player, permission, false); + } + + public boolean canUseAxiom(Player player, String permission, boolean strict) { + return activeAxiomPlayers.contains(player.getUniqueId()) && hasAxiomPermission(player, permission, strict); } public @Nullable RateLimiter getBlockBufferRateLimiter(UUID uuid) { return this.playerBlockBufferRateLimiters.get(uuid); } + public boolean isMismatchedDataVersion(UUID uuid) { + return this.playerProtocolVersion.containsKey(uuid); + } + + public int getProtocolVersionFor(UUID uuid) { + return this.playerProtocolVersion.getOrDefault(uuid, SharedConstants.getProtocolVersion()); + } + + public IdMapper getBlockRegistry(UUID uuid) { + return this.playerBlockRegistry.getOrDefault(uuid, this.allowedBlockRegistry); + } + private final WeakHashMap worldProperties = new WeakHashMap<>(); public @Nullable ServerWorldPropertiesRegistry getWorldPropertiesIfPresent(World world) { @@ -363,7 +415,7 @@ public class AxiomPaper extends JavaPlugin implements Listener { } private ServerWorldPropertiesRegistry createWorldProperties(World world) { - ServerWorldPropertiesRegistry registry = new ServerWorldPropertiesRegistry(world); + ServerWorldPropertiesRegistry registry = new ServerWorldPropertiesRegistry(new WeakReference<>(world)); AxiomCreateWorldPropertiesEvent createEvent = new AxiomCreateWorldPropertiesEvent(world, registry); Bukkit.getPluginManager().callEvent(createEvent); diff --git a/src/main/java/com/moulberry/axiom/DisallowedBlocks.java b/src/main/java/com/moulberry/axiom/DisallowedBlocks.java index f80d2b8..ef4ab3a 100644 --- a/src/main/java/com/moulberry/axiom/DisallowedBlocks.java +++ b/src/main/java/com/moulberry/axiom/DisallowedBlocks.java @@ -3,6 +3,7 @@ package com.moulberry.axiom; import com.mojang.brigadier.StringReader; import com.mojang.datafixers.util.Either; import com.moulberry.axiom.buffer.BlockBuffer; +import com.viaversion.viaversion.api.data.Mappings; import net.minecraft.commands.arguments.blocks.BlockPredicateArgument; import net.minecraft.commands.arguments.blocks.BlockStateParser; import net.minecraft.core.IdMapper; diff --git a/src/main/java/com/moulberry/axiom/Restrictions.java b/src/main/java/com/moulberry/axiom/Restrictions.java index 5dd0180..c36329c 100644 --- a/src/main/java/com/moulberry/axiom/Restrictions.java +++ b/src/main/java/com/moulberry/axiom/Restrictions.java @@ -6,14 +6,15 @@ import net.minecraft.core.BlockPos; import net.minecraft.network.FriendlyByteBuf; import org.bukkit.entity.Player; +import java.util.Set; + public class Restrictions { public boolean canImportBlocks = true; public boolean canUseEditor = true; public boolean canEditDisplayEntities = true; public int maxSectionsPerSecond = 0; - public BlockPos boundsMin = null; - public BlockPos boundsMax = null; + public Set bounds = Set.of(); public PlotSquaredIntegration.PlotBounds lastPlotBounds = null; @@ -26,16 +27,21 @@ public class Restrictions { buf.writeVarInt(this.maxSectionsPerSecond); - if (this.boundsMin == null || this.boundsMax == null) { - buf.writeBoolean(false); - } else { - buf.writeBoolean(true); - int minX = this.boundsMin.getX(); - int minY = this.boundsMin.getY(); - int minZ = this.boundsMin.getZ(); - int maxX = this.boundsMax.getX(); - int maxY = this.boundsMax.getY(); - int maxZ = this.boundsMax.getZ(); + int count = Math.min(64, bounds.size()); + buf.writeVarInt(count); + for (PlotSquaredIntegration.PlotBox bound : this.bounds) { + if (count > 0) { + count -= 1; + } else { + break; + } + + int minX = bound.min().getX(); + int minY = bound.min().getY(); + int minZ = bound.min().getZ(); + int maxX = bound.max().getX(); + int maxY = bound.max().getY(); + int maxZ = bound.max().getZ(); if (minX < -33554431) minX = -33554431; if (minX > 33554431) minX = 33554431; @@ -61,4 +67,15 @@ public class Restrictions { } } + @Override + public String toString() { + return "Restrictions{" + + "canImportBlocks=" + canImportBlocks + + ", canUseEditor=" + canUseEditor + + ", canEditDisplayEntities=" + canEditDisplayEntities + + ", maxSectionsPerSecond=" + maxSectionsPerSecond + + ", bounds=" + bounds + + ", lastPlotBounds=" + lastPlotBounds + + '}'; + } } diff --git a/src/main/java/com/moulberry/axiom/VersionHelper.java b/src/main/java/com/moulberry/axiom/VersionHelper.java index 2729968..902c9f0 100644 --- a/src/main/java/com/moulberry/axiom/VersionHelper.java +++ b/src/main/java/com/moulberry/axiom/VersionHelper.java @@ -1,14 +1,15 @@ package com.moulberry.axiom; -import com.moulberry.axiom.packet.CustomByteArrayPayload; +import io.netty.buffer.Unpooled; import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.DiscardedPayload; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; public class VersionHelper { public static void sendCustomPayload(ServerPlayer serverPlayer, ResourceLocation id, byte[] data) { - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(new CustomByteArrayPayload(id, data))); + serverPlayer.connection.send(new ClientboundCustomPayloadPacket(new DiscardedPayload(id, Unpooled.wrappedBuffer(data)))); } } diff --git a/src/main/java/com/moulberry/axiom/WorldExtension.java b/src/main/java/com/moulberry/axiom/WorldExtension.java index 2152bb2..7f610a8 100644 --- a/src/main/java/com/moulberry/axiom/WorldExtension.java +++ b/src/main/java/com/moulberry/axiom/WorldExtension.java @@ -16,8 +16,8 @@ import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.LevelChunk; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import java.util.*; @@ -65,7 +65,7 @@ public class WorldExtension { FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); buf.writeCollection(markerData, MarkerData::write); - buf.writeCollection(Set.of(), FriendlyByteBuf::writeUUID); + buf.writeCollection(Set.of(), (buffer, uuid) -> buffer.writeUUID(uuid)); byte[] bytes = new byte[buf.writerIndex()]; buf.getBytes(0, bytes); @@ -106,7 +106,7 @@ public class WorldExtension { if (!changedData.isEmpty() || !oldUuids.isEmpty()) { FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); buf.writeCollection(changedData, MarkerData::write); - buf.writeCollection(oldUuids, FriendlyByteBuf::writeUUID); + buf.writeCollection(oldUuids, (buffer, uuid) -> buffer.writeUUID(uuid)); byte[] bytes = new byte[buf.writerIndex()]; buf.getBytes(0, bytes); diff --git a/src/main/java/com/moulberry/axiom/blueprint/BlueprintIo.java b/src/main/java/com/moulberry/axiom/blueprint/BlueprintIo.java index 59bb487..01f56df 100644 --- a/src/main/java/com/moulberry/axiom/blueprint/BlueprintIo.java +++ b/src/main/java/com/moulberry/axiom/blueprint/BlueprintIo.java @@ -128,7 +128,7 @@ public class BlueprintIo { CompoundTag blockStates = compoundTag.getCompound("BlockStates"); blockStates = DFUHelper.updatePalettedContainer(blockStates, dataVersion); PalettedContainer container = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, blockStates) - .getOrThrow(false, err -> {}); + .getOrThrow(); map.put(BlockPos.asLong(cx, cy, cz), container); } } @@ -205,8 +205,7 @@ public class BlueprintIo { tag.putInt("X", cx); tag.putInt("Y", cy); tag.putInt("Z", cz); - Tag encoded = BlueprintIo.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, container) - .getOrThrow(false, err -> {}); + Tag encoded = BlueprintIo.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, container).getOrThrow(); tag.put("BlockStates", encoded); savedBlockRegions.add(tag); } diff --git a/src/main/java/com/moulberry/axiom/blueprint/ServerBlueprintManager.java b/src/main/java/com/moulberry/axiom/blueprint/ServerBlueprintManager.java index daec893..d6939cd 100644 --- a/src/main/java/com/moulberry/axiom/blueprint/ServerBlueprintManager.java +++ b/src/main/java/com/moulberry/axiom/blueprint/ServerBlueprintManager.java @@ -6,7 +6,7 @@ import io.netty.buffer.Unpooled; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.entity.CraftPlayer; import java.io.BufferedInputStream; import java.io.IOException; diff --git a/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java b/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java index aa24244..b7fdcd7 100644 --- a/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java +++ b/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java @@ -9,6 +9,7 @@ import it.unimi.dsi.fastutil.objects.ObjectSet; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; import net.minecraft.core.BlockPos; +import net.minecraft.core.IdMapper; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; @@ -23,18 +24,16 @@ public class BlockBuffer { private final Long2ObjectMap> values; + private IdMapper registry; private PalettedContainer last = null; private long lastId = AxiomConstants.MIN_POSITION_LONG; private final Long2ObjectMap> blockEntities = new Long2ObjectOpenHashMap<>(); private long totalBlockEntities = 0; private long totalBlockEntityBytes = 0; - public BlockBuffer() { + public BlockBuffer(IdMapper registry) { this.values = new Long2ObjectOpenHashMap<>(); - } - - public BlockBuffer(Long2ObjectMap> values) { - this.values = values; + this.registry = registry; } public void save(FriendlyByteBuf friendlyByteBuf) { @@ -57,8 +56,9 @@ public class BlockBuffer { friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG); } - public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf, @Nullable RateLimiter rateLimiter, AtomicBoolean reachedRateLimit) { - BlockBuffer buffer = new BlockBuffer(); + public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf, @Nullable RateLimiter rateLimiter, AtomicBoolean reachedRateLimit, + IdMapper registry) { + BlockBuffer buffer = new BlockBuffer(registry); long totalBlockEntities = 0; long totalBlockEntityBytes = 0; @@ -177,7 +177,7 @@ public class BlockBuffer { public PalettedContainer getOrCreateSection(long id) { if (this.last == null || id != this.lastId) { this.lastId = id; - this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(AxiomPaper.PLUGIN.allowedBlockRegistry, + this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(this.registry, EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES)); } diff --git a/src/main/java/com/moulberry/axiom/commands/AxiomDebugCommand.java b/src/main/java/com/moulberry/axiom/commands/AxiomDebugCommand.java new file mode 100644 index 0000000..fe6b2c5 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/commands/AxiomDebugCommand.java @@ -0,0 +1,155 @@ +package com.moulberry.axiom.commands; + +import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.Restrictions; +import com.moulberry.axiom.integration.Integration; +import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; +import com.moulberry.axiom.integration.worldguard.WorldGuardIntegration; +import net.kyori.adventure.text.Component; +import org.bukkit.block.Block; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.bukkit.BukkitCommandManager; +import org.incendo.cloud.parser.standard.EnumParser; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.permission.PredicatePermission; + +import java.util.UUID; + +public class AxiomDebugCommand { + + /** + * Command requires either the axiom.debug permission or for you to have the UUID d0e05de7-6067-454d-beae-c6d19d886191 + * The command isn't capable of modifying the world, only checking various properties for debugging purposes + * It should be 100% safe to give to any player, but is locked behind some restrictions due to the potential + * for lagging the server by spamming certain commands + */ + + private static final UUID MOULBERRY_UUID = UUID.fromString("d0e05de7-6067-454d-beae-c6d19d886191"); + + public static void register(AxiomPaper axiomPaper, BukkitCommandManager manager) { + manager.command( + base(manager, "hasAxiomPermission").handler(context -> { + if (!(context.sender() instanceof Player player)) return; + boolean hasAxiomPermission = axiomPaper.hasAxiomPermission(player); + context.sender().sendMessage(Component.text("hasAxiomPermission: " + hasAxiomPermission)); + }) + ); + manager.command( + base(manager, "canUseAxiom").handler(context -> { + if (!(context.sender() instanceof Player player)) return; + boolean canUseAxiom = axiomPaper.canUseAxiom(player); + context.sender().sendMessage(Component.text("canUseAxiom: " + canUseAxiom)); + }) + ); + manager.command( + base(manager, "isMismatchedDataVersion").handler(context -> { + if (!(context.sender() instanceof Player player)) return; + boolean isMismatchedDataVersion = axiomPaper.isMismatchedDataVersion(player.getUniqueId()); + context.sender().sendMessage(Component.text("isMismatchedDataVersion: " + isMismatchedDataVersion)); + }) + ); + manager.command( + base(manager, "canModifyWorld").handler(context -> { + if (!(context.sender() instanceof Player player)) return; + boolean canModifyWorld = axiomPaper.canModifyWorld(player, player.getWorld()); + context.sender().sendMessage(Component.text("canModifyWorld: " + canModifyWorld)); + }) + ); + manager.command( + base(manager, "isClientListening").required("channel", StringParser.greedyStringParser()).handler(context -> { + if (!(context.sender() instanceof Player player)) return; + String channel = context.get("channel"); + boolean isClientListening = player.getListeningPluginChannels().contains(channel); + context.sender().sendMessage(Component.text("listening to " + channel +": " + isClientListening)); + }) + ); + manager.command( + base(manager, "hasPermission").required("permission", StringParser.greedyStringParser()).handler(context -> { + String permission = context.get("permission"); + boolean hasPermission = context.sender().hasPermission(permission); + context.sender().sendMessage(Component.text("has permission " + permission +": " + hasPermission)); + }) + ); + manager.command( + base(manager, "getRestrictions").handler(context -> { + if (!(context.sender() instanceof Player player)) return; + Restrictions restrictions = axiomPaper.playerRestrictions.get(player.getUniqueId()); + if (restrictions == null) { + context.sender().sendMessage(Component.text("no restrictions")); + } else { + context.sender().sendMessage(Component.text("restrictions: " + restrictions)); + } + }) + ); + enum IntegrationType { + PLOT_SQUARED, + WORLD_GUARD + } + manager.command( + base(manager, "canBreakBlockAtCurrentPosition").optional("type", EnumParser.enumParser(IntegrationType.class)).handler(context -> { + if (!(context.sender() instanceof Player player)) return; + IntegrationType integrationType = (IntegrationType) context.optional("type").orElse(null); + + Block block = player.getWorld().getBlockAt(player.getLocation()); + + boolean canBreakBlock; + if (integrationType == IntegrationType.PLOT_SQUARED) { + canBreakBlock = PlotSquaredIntegration.canBreakBlock(player, block); + } else if (integrationType == IntegrationType.WORLD_GUARD) { + canBreakBlock = WorldGuardIntegration.canBreakBlock(player, block.getLocation()); + } else { + canBreakBlock = Integration.canBreakBlock(player, block); + } + context.sender().sendMessage(Component.text("canBreakBlock: " + canBreakBlock)); + }) + ); + manager.command( + base(manager, "canPlaceBlockAtCurrentPosition").optional("type", EnumParser.enumParser(IntegrationType.class)).handler(context -> { + if (!(context.sender() instanceof Player player)) return; + IntegrationType integrationType = (IntegrationType) context.optional("type").orElse(null); + + boolean canPlaceBlock; + if (integrationType == IntegrationType.PLOT_SQUARED) { + canPlaceBlock = PlotSquaredIntegration.canPlaceBlock(player, player.getLocation()); + } else if (integrationType == IntegrationType.WORLD_GUARD) { + canPlaceBlock = WorldGuardIntegration.canPlaceBlock(player, player.getLocation()); + } else { + canPlaceBlock = Integration.canPlaceBlock(player, player.getLocation()); + } + context.sender().sendMessage(Component.text("canPlaceBlock: " + canPlaceBlock)); + }) + ); + manager.command( + base(manager, "isPlotWorld").handler(context -> { + if (!(context.sender() instanceof Player player)) return; + boolean isPlotWorld = PlotSquaredIntegration.isPlotWorld(player.getWorld()); + context.sender().sendMessage(Component.text("isPlotWorld: " + isPlotWorld)); + }) + ); + manager.command( + base(manager, "getCurrentEditablePlot").handler(context -> { + if (!(context.sender() instanceof Player player)) return; + PlotSquaredIntegration.PlotBounds plotBounds = PlotSquaredIntegration.getCurrentEditablePlot(player); + context.sender().sendMessage(Component.text("plotBounds: " + plotBounds)); + }) + ); + } + + private static Command.Builder base(BukkitCommandManager manager, String subcommand) { + return manager.commandBuilder("axiompaperdebug") + .literal(subcommand) + .senderType(CommandSender.class) + .permission(PredicatePermission.of(sender -> { + if (sender instanceof Player player) { + return player.hasPermission("axiom.debug") || player.getUniqueId().equals(MOULBERRY_UUID); + } else { + return false; + } + })); + } + + + +} diff --git a/src/main/java/com/moulberry/axiom/integration/SectionPermissionChecker.java b/src/main/java/com/moulberry/axiom/integration/SectionPermissionChecker.java index 97f4eb5..50f1a5d 100644 --- a/src/main/java/com/moulberry/axiom/integration/SectionPermissionChecker.java +++ b/src/main/java/com/moulberry/axiom/integration/SectionPermissionChecker.java @@ -13,10 +13,10 @@ public interface SectionPermissionChecker { if (first.noneAllowed() || second.noneAllowed()) { return NONE_ALLOWED; } - if (first.allAllowed()) { + if (first == ALL_ALLOWED) { return second; } - if (second.allAllowed()) { + if (second == ALL_ALLOWED) { return first; } @@ -25,6 +25,10 @@ public interface SectionPermissionChecker { return NONE_ALLOWED; } + if (first.allAllowed() && second.allAllowed()) { + return new AllAllowedInBox(intersect); + } + return new SectionPermissionChecker() { @Override public boolean allAllowed() { diff --git a/src/main/java/com/moulberry/axiom/integration/coreprotect/CoreProtectIntegration.java b/src/main/java/com/moulberry/axiom/integration/coreprotect/CoreProtectIntegration.java new file mode 100644 index 0000000..60d43ec --- /dev/null +++ b/src/main/java/com/moulberry/axiom/integration/coreprotect/CoreProtectIntegration.java @@ -0,0 +1,29 @@ +package com.moulberry.axiom.integration.coreprotect; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.craftbukkit.CraftWorld; + +public class CoreProtectIntegration { + public static boolean isEnabled() { + return CoreProtectIntegrationImpl.isEnabled(); + } + + public static void logPlacement(String name, BlockState blockState, CraftWorld world, BlockPos pos) { + if (!CoreProtectIntegrationImpl.isEnabled()) { + return; + } + + CoreProtectIntegrationImpl.logPlacement(name, blockState, world, pos); + } + + public static void logRemoval(String name, BlockState blockState, CraftWorld world, BlockPos pos) { + if (!CoreProtectIntegrationImpl.isEnabled()) { + return; + } + + CoreProtectIntegrationImpl.logRemoval(name, blockState, world, pos); + } + +} diff --git a/src/main/java/com/moulberry/axiom/integration/coreprotect/CoreProtectIntegrationImpl.java b/src/main/java/com/moulberry/axiom/integration/coreprotect/CoreProtectIntegrationImpl.java new file mode 100644 index 0000000..65fc3ec --- /dev/null +++ b/src/main/java/com/moulberry/axiom/integration/coreprotect/CoreProtectIntegrationImpl.java @@ -0,0 +1,83 @@ +package com.moulberry.axiom.integration.coreprotect; + +import com.moulberry.axiom.AxiomPaper; +import net.coreprotect.CoreProtect; +import net.coreprotect.CoreProtectAPI; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftBlockState; +import org.bukkit.plugin.Plugin; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +public class CoreProtectIntegrationImpl { + private static final CoreProtectAPI COREPROTECT_API; + private static final boolean COREPROTECT_ENABLED; + private static final Constructor CRAFT_BLOCK_STATE_CONSTRUCTOR; + + static { + COREPROTECT_API = getCoreProtect(); + Constructor constructor = null; + + if (COREPROTECT_API != null) { + try { + constructor = CraftBlockState.class.getDeclaredConstructor(World.class, BlockPos.class, BlockState.class); + constructor.setAccessible(true); + } catch (NoSuchMethodException | SecurityException e) { + AxiomPaper.PLUGIN.getLogger().warning("Failed to get CraftBlockState constructor for CoreProtect: " + e); + } + } + + CRAFT_BLOCK_STATE_CONSTRUCTOR = constructor; + COREPROTECT_ENABLED = COREPROTECT_API != null && CRAFT_BLOCK_STATE_CONSTRUCTOR != null; + } + + private static CoreProtectAPI getCoreProtect() { + Plugin plugin = Bukkit.getPluginManager().getPlugin("CoreProtect"); + + if (!(plugin instanceof CoreProtect)) { + return null; + } + + CoreProtectAPI coreProtect = ((CoreProtect) plugin).getAPI(); + if (!coreProtect.isEnabled()) { + return null; + } + + if (coreProtect.APIVersion() < 10) { + return null; + } + + return coreProtect; + } + + private static CraftBlockState createCraftBlockState(World world, BlockPos pos, BlockState blockState) { + try { + return CRAFT_BLOCK_STATE_CONSTRUCTOR.newInstance(world, pos, blockState); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + AxiomPaper.PLUGIN.getLogger().warning("Failed to create CraftBlockState for CoreProtect: " + e); + return null; + } + } + + static boolean isEnabled() { + return COREPROTECT_ENABLED; + } + + static void logPlacement(String name, BlockState blockState, CraftWorld world, BlockPos pos) { + if (blockState.isAir()) { + return; + } + + COREPROTECT_API.logPlacement(name, createCraftBlockState(world, pos, blockState)); + } + + static void logRemoval(String name, BlockState blockState, CraftWorld world, BlockPos pos) { + COREPROTECT_API.logRemoval(name, createCraftBlockState(world, pos, blockState)); + } + +} diff --git a/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegration.java b/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegration.java index deab2cf..586930c 100644 --- a/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegration.java +++ b/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegration.java @@ -10,26 +10,13 @@ import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Player; +import java.util.Set; + public class PlotSquaredIntegration { - public record PlotBounds(BlockPos min, BlockPos max, String worldName) { - public PlotBounds(CuboidRegion cuboidRegion, String worldName) { - this( - new BlockPos( - cuboidRegion.getMinimumPoint().getBlockX(), - cuboidRegion.getMinimumPoint().getBlockY(), - cuboidRegion.getMinimumPoint().getBlockZ() - ), - new BlockPos( - cuboidRegion.getMaximumPoint().getBlockX(), - cuboidRegion.getMaximumPoint().getBlockY(), - cuboidRegion.getMaximumPoint().getBlockZ() - ), - worldName - ); - } - } + public record PlotBox(BlockPos min, BlockPos max) {} + public record PlotBounds(Set boxes, String worldName) {} public static boolean canBreakBlock(Player player, Block block) { if (!Bukkit.getPluginManager().isPluginEnabled("PlotSquared")) { diff --git a/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegrationImpl.java b/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegrationImpl.java index 87fa49e..b6b71d7 100644 --- a/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegrationImpl.java +++ b/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegrationImpl.java @@ -18,6 +18,7 @@ import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.world.block.BlockType; +import net.minecraft.core.BlockPos; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -147,16 +148,12 @@ public class PlotSquaredIntegrationImpl { BukkitPlayer pp = BukkitUtil.adapt(player); Plot plot = area.getPlot(location); if (plot != null) { - Location bottom = plot.getExtendedBottomAbs(); - Location top = plot.getExtendedTopAbs(); - CuboidRegion cuboidRegion = new CuboidRegion(bottom.getBlockVector3(), top.getBlockVector3()); - // check unowned plots if (!plot.hasOwner()) { if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_UNOWNED, false)) { return null; } else { - return new PlotSquaredIntegration.PlotBounds(cuboidRegion, player.getWorld().getName()); + return createBounds(plot, player.getWorld().getName()); } } // player is breaking another player's plot @@ -164,7 +161,7 @@ public class PlotSquaredIntegrationImpl { if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER, false)) { return null; } else { - return new PlotSquaredIntegration.PlotBounds(cuboidRegion, player.getWorld().getName()); + return createBounds(plot, player.getWorld().getName()); } } // plot is 'done' @@ -172,15 +169,35 @@ public class PlotSquaredIntegrationImpl { if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER, false)) { return null; } else { - return new PlotSquaredIntegration.PlotBounds(cuboidRegion, player.getWorld().getName()); + return createBounds(plot, player.getWorld().getName()); } } - return new PlotSquaredIntegration.PlotBounds(cuboidRegion, player.getWorld().getName()); + return createBounds(plot, player.getWorld().getName()); } return null; } + private static PlotSquaredIntegration.PlotBounds createBounds(Plot plot, String worldName) { + Set boxes = new HashSet<>(); + + for (CuboidRegion region : plot.getRegions()) { + BlockPos min = new BlockPos( + region.getMinimumPoint().getBlockX(), + region.getMinimumPoint().getBlockY(), + region.getMinimumPoint().getBlockZ() + ); + BlockPos max = new BlockPos( + region.getMaximumPoint().getBlockX(), + region.getMaximumPoint().getBlockY(), + region.getMaximumPoint().getBlockZ() + ); + boxes.add(new PlotSquaredIntegration.PlotBox(min, max)); + } + + return new PlotSquaredIntegration.PlotBounds(boxes, worldName); + } + static SectionPermissionChecker checkSection(Player player, World world, int sectionX, int sectionY, int sectionZ) { int minX = sectionX * 16; int minY = sectionY * 16; @@ -220,12 +237,12 @@ public class PlotSquaredIntegrationImpl { BlockVector3 minPoint = region.getMinimumPoint(); BlockVector3 maxPoint = region.getMaximumPoint(); - int minPlotX = Math.max(minPoint.getX(), minX); - int minPlotY = Math.max(minPoint.getY(), minY); - int minPlotZ = Math.max(minPoint.getZ(), minZ); - int maxPlotX = Math.min(maxPoint.getX(), maxX); - int maxPlotY = Math.min(maxPoint.getY(), maxY); - int maxPlotZ = Math.min(maxPoint.getZ(), maxZ); + int minPlotX = Math.max(minPoint.x(), minX); + int minPlotY = Math.max(minPoint.y(), minY); + int minPlotZ = Math.max(minPoint.z(), minZ); + int maxPlotX = Math.min(maxPoint.x(), maxX); + int maxPlotY = Math.min(maxPoint.y(), maxY); + int maxPlotZ = Math.min(maxPoint.z(), maxZ); if (minPlotX > maxPlotX) continue; if (minPlotY > maxPlotY) continue; diff --git a/src/main/java/com/moulberry/axiom/integration/worldguard/WorldGuardIntegrationImpl.java b/src/main/java/com/moulberry/axiom/integration/worldguard/WorldGuardIntegrationImpl.java index 3110655..1eb3d07 100644 --- a/src/main/java/com/moulberry/axiom/integration/worldguard/WorldGuardIntegrationImpl.java +++ b/src/main/java/com/moulberry/axiom/integration/worldguard/WorldGuardIntegrationImpl.java @@ -34,15 +34,19 @@ public class WorldGuardIntegrationImpl { private static boolean testBuild(Player player, org.bukkit.Location loc, StateFlag flag) { WorldGuardPlatform platform = WorldGuard.getInstance().getPlatform(); + com.sk89q.worldedit.world.World worldEditWorld = BukkitAdapter.adapt(loc.getWorld()); + LocalPlayer worldGuardPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + + if (platform.getSessionManager().hasBypass(worldGuardPlayer, worldEditWorld)) { + return true; + } + RegionContainer regionContainer = platform.getRegionContainer(); if (regionContainer == null) { return true; } RegionQuery query = regionContainer.createQuery(); - - LocalPlayer worldGuardPlayer = WorldGuardPlugin.inst().wrapPlayer(player); - return query.testBuild(BukkitAdapter.adapt(loc), worldGuardPlayer, flag); } @@ -118,12 +122,12 @@ public class WorldGuardIntegrationImpl { BlockVector3 regionMin = region.getMinimumPoint(); BlockVector3 regionMax = region.getMaximumPoint(); - int regionMinX = Math.max(regionMin.getBlockX(), cx*16) - minX; - int regionMinY = Math.max(regionMin.getBlockY(), cy*16) - minY; - int regionMinZ = Math.max(regionMin.getBlockZ(), cz*16) - minZ; - int regionMaxX = Math.min(regionMax.getBlockX(), cx*16+15) - minX; - int regionMaxY = Math.min(regionMax.getBlockY(), cy*16+15) - minY; - int regionMaxZ = Math.min(regionMax.getBlockZ(), cz*16+15) - minZ; + int regionMinX = Math.max(regionMin.x(), cx*16) - minX; + int regionMinY = Math.max(regionMin.y(), cy*16) - minY; + int regionMinZ = Math.max(regionMin.z(), cz*16) - minZ; + int regionMaxX = Math.min(regionMax.x(), cx*16+15) - minX; + int regionMaxY = Math.min(regionMax.y(), cy*16+15) - minY; + int regionMaxZ = Math.min(regionMax.z(), cz*16+15) - minZ; Box box = new Box(regionMinX, regionMinY, regionMinZ, regionMaxX, regionMaxY, regionMaxZ); if (value == StateFlag.State.DENY) { diff --git a/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java b/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java index 83eb52d..3f3b004 100644 --- a/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java +++ b/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java @@ -11,6 +11,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; +import java.io.IOException; import java.util.List; public class AxiomBigPayloadHandler extends ByteToMessageDecoder { @@ -35,59 +36,90 @@ public class AxiomBigPayloadHandler extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - // Don't process if channel isn't active - if (!ctx.channel().isActive()) { - in.skipBytes(in.readableBytes()); + // No bytes to read?! Go away + if (in.readableBytes() == 0) { return; } - int i = in.readableBytes(); - if (i != 0) { - int readerIndex = in.readerIndex(); - boolean success = false; - try { - FriendlyByteBuf buf = new FriendlyByteBuf(in); - int packetId = buf.readVarInt(); + // Don't handle if player doesn't have permission to use Axiom + ServerPlayer player = connection.getPlayer(); + if (player == null || !AxiomPaper.PLUGIN.canUseAxiom(player.getBukkitEntity())) { + ctx.fireChannelRead(in.retain()); - if (packetId == payloadId) { - ResourceLocation identifier = buf.readResourceLocation(); - if (identifier.equals(SET_BUFFER)) { - ServerPlayer player = connection.getPlayer(); - if (AxiomPaper.PLUGIN.canUseAxiom(player.getBukkitEntity())) { - setBlockBuffer.onReceive(player, buf); - success = true; - in.skipBytes(in.readableBytes()); - return; - } - } else if (identifier.equals(UPLOAD_BLUEPRINT)) { - ServerPlayer player = connection.getPlayer(); - if (AxiomPaper.PLUGIN.canUseAxiom(player.getBukkitEntity())) { - player.getServer().execute(() -> uploadBlueprint.onReceive(player, buf)); + // Skip remaining bytes + if (in.readableBytes() > 0) { + in.skipBytes(in.readableBytes()); + } + return; + } - success = true; - in.skipBytes(in.readableBytes()); - return; - } - } else if (requestChunkDataPacketListener != null && identifier.equals(REQUEST_CHUNK_DATA)) { - ServerPlayer player = connection.getPlayer(); - if (AxiomPaper.PLUGIN.canUseAxiom(player.getBukkitEntity())) { - byte[] bytes = new byte[buf.writerIndex() - buf.readerIndex()]; - buf.getBytes(buf.readerIndex(), bytes); + // Don't process if channel isn't active + if (!ctx.channel().isActive()) { + if (in.readableBytes() > 0) { + in.skipBytes(in.readableBytes()); + } + return; + } - player.getServer().execute(() -> requestChunkDataPacketListener.onPluginMessageReceived( - identifier.toString(), player.getBukkitEntity(), bytes)); + int readerIndex = in.readerIndex(); + boolean success = false; + try { + FriendlyByteBuf buf = new FriendlyByteBuf(in); + int packetId = buf.readVarInt(); - success = true; - in.skipBytes(in.readableBytes()); - return; - } + if (packetId == payloadId) { + ResourceLocation identifier = buf.readResourceLocation(); + if (identifier.equals(SET_BUFFER)) { + setBlockBuffer.onReceive(player, buf); + success = true; + if (in.readableBytes() > 0) { + throw new IOException("Axiom packet " + identifier + " was larger than I expected, found " + in.readableBytes() + + " bytes extra whilst reading packet"); } + return; + } else if (identifier.equals(UPLOAD_BLUEPRINT)) { + uploadBlueprint.onReceive(player, buf); + success = true; + if (in.readableBytes() > 0) { + throw new IOException("Axiom packet " + identifier + " was larger than I expected, found " + in.readableBytes() + + " bytes extra whilst reading packet"); + } + return; + } else if (requestChunkDataPacketListener != null && identifier.equals(REQUEST_CHUNK_DATA)) { + byte[] bytes = new byte[buf.writerIndex() - buf.readerIndex()]; + buf.getBytes(buf.readerIndex(), bytes); + + player.getServer().execute(() -> { + try { + requestChunkDataPacketListener.onPluginMessageReceived( + identifier.toString(), player.getBukkitEntity(), bytes); + } catch (Throwable t) { + player.getBukkitEntity().kick(net.kyori.adventure.text.Component.text( + "An error occured while requesting chunk data: " + t.getMessage())); + } + }); + + success = true; + if (in.readableBytes() > 0) { + in.skipBytes(in.readableBytes()); + } + return; } - } catch (Throwable ignored) { - } finally { - if (!success) { - in.readerIndex(readerIndex); + } + } catch (Throwable t) { + if (!(t instanceof IndexOutOfBoundsException)) { + // Skip remaining bytes + success = true; + if (in.readableBytes() > 0) { + in.skipBytes(in.readableBytes()); } + + // Throw error, will disconnect client + throw t; + } + } finally { + if (!success) { + in.readerIndex(readerIndex); } } diff --git a/src/main/java/com/moulberry/axiom/packet/BlueprintRequestPacketListener.java b/src/main/java/com/moulberry/axiom/packet/BlueprintRequestPacketListener.java index e563cb1..8b920e0 100644 --- a/src/main/java/com/moulberry/axiom/packet/BlueprintRequestPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/BlueprintRequestPacketListener.java @@ -6,9 +6,11 @@ import com.moulberry.axiom.blueprint.RawBlueprint; import com.moulberry.axiom.blueprint.ServerBlueprintManager; import com.moulberry.axiom.blueprint.ServerBlueprintRegistry; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; +import net.minecraft.SharedConstants; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -26,7 +28,20 @@ public class BlueprintRequestPacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); + } + } + + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.blueprint.request")) { + return; + } + + if (this.plugin.isMismatchedDataVersion(player.getUniqueId())) { + player.sendMessage(Component.text("Axiom+ViaVersion: This feature isn't supported. Switch your client version to " + SharedConstants.VERSION_STRING + " to use this")); return; } diff --git a/src/main/java/com/moulberry/axiom/packet/CustomByteArrayPayload.java b/src/main/java/com/moulberry/axiom/packet/CustomByteArrayPayload.java deleted file mode 100644 index cb2bcd7..0000000 --- a/src/main/java/com/moulberry/axiom/packet/CustomByteArrayPayload.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.moulberry.axiom.packet; - -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; - -public record CustomByteArrayPayload(ResourceLocation id, byte[] bytes) implements CustomPacketPayload { - @Override - public void write(FriendlyByteBuf buf) { - buf.writeBytes(bytes); - } -} diff --git a/src/main/java/com/moulberry/axiom/packet/DeleteEntityPacketListener.java b/src/main/java/com/moulberry/axiom/packet/DeleteEntityPacketListener.java index 2402d71..b74a0e4 100644 --- a/src/main/java/com/moulberry/axiom/packet/DeleteEntityPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/DeleteEntityPacketListener.java @@ -4,11 +4,12 @@ import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.integration.Integration; import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -26,11 +27,15 @@ public class DeleteEntityPacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { - return; + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); } + } - if (!player.hasPermission("axiom.entity.*") && !player.hasPermission("axiom.entity.delete")) { + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.entity.delete", true)) { return; } @@ -40,7 +45,7 @@ public class DeleteEntityPacketListener implements PluginMessageListener { FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); List delete = friendlyByteBuf.readCollection(FriendlyByteBuf.limitValue(ArrayList::new, 1000), - FriendlyByteBuf::readUUID); + buf -> buf.readUUID()); ServerLevel serverLevel = ((CraftWorld)player.getWorld()).getHandle(); diff --git a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java index 2553159..9d52748 100644 --- a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java @@ -1,25 +1,29 @@ package com.moulberry.axiom.packet; import com.google.common.util.concurrent.RateLimiter; -import com.moulberry.axiom.AxiomConstants; -import com.moulberry.axiom.AxiomPaper; -import com.moulberry.axiom.View; -import com.moulberry.axiom.WorldExtension; +import com.moulberry.axiom.*; import com.moulberry.axiom.blueprint.ServerBlueprintManager; import com.moulberry.axiom.event.AxiomHandshakeEvent; import com.moulberry.axiom.persistence.ItemStackDataType; import com.moulberry.axiom.persistence.UUIDDataType; +import com.moulberry.axiom.viaversion.ViaVersionHelper; import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import io.netty.buffer.Unpooled; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minecraft.SharedConstants; +import net.minecraft.core.IdMapper; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.block.state.BlockState; import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataContainer; @@ -28,7 +32,6 @@ import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; import java.util.List; -import java.util.Set; import java.util.UUID; public class HelloPacketListener implements PluginMessageListener { @@ -41,6 +44,14 @@ public class HelloPacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); + } + } + + private void process(Player player, byte[] message) { if (!this.plugin.hasAxiomPermission(player)) { return; } @@ -48,21 +59,70 @@ public class HelloPacketListener implements PluginMessageListener { FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); int apiVersion = friendlyByteBuf.readVarInt(); int dataVersion = friendlyByteBuf.readVarInt(); - friendlyByteBuf.readNbt(); // Discard + // note - skipping NBT here. friendlyByteBuf.readNBT(); int serverDataVersion = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); if (dataVersion != serverDataVersion) { - Component text = Component.text("Axiom: Incompatible data version detected (client " + dataVersion + - ", server " + serverDataVersion + "), are you using ViaVersion?"); - String incompatibleDataVersion = plugin.configuration.getString("incompatible-data-version"); - if (incompatibleDataVersion == null) incompatibleDataVersion = "kick"; - if (incompatibleDataVersion.equals("warn")) { + if (incompatibleDataVersion == null) incompatibleDataVersion = "warn"; + + Component incompatibleWarning = Component.text("Axiom: Incompatible data version detected (client " + dataVersion + + ", server " + serverDataVersion + ")"); + + if (!Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) { + if (incompatibleDataVersion.equals("warn")) { + player.sendMessage(incompatibleWarning.color(NamedTextColor.RED)); + return; + } else if (!incompatibleDataVersion.equals("ignore")) { + player.kick(incompatibleWarning); + return; + } + } else { + int playerVersion = Via.getAPI().getPlayerVersion(player.getUniqueId()); + if (playerVersion == SharedConstants.getProtocolVersion()) { + // Likely using via on the proxy, try to get protocol version from data version + if (dataVersion < 3337) { + player.sendMessage(incompatibleWarning.color(NamedTextColor.RED)); + return; + } else if (dataVersion == 3337) { + playerVersion = 762; // 1.19.4 + } else if (dataVersion <= 3465) { + playerVersion = 763; // 1.20.1 + } else if (dataVersion <= 3578) { + playerVersion = 764; // 1.20.2 + } else if (dataVersion <= 3700) { + playerVersion = 765; // 1.20.3 / 1.20.4 + } else if (dataVersion <= 3837) { + playerVersion = 766; // 1.20.3 / 1.20.4 + } else { + player.sendMessage(incompatibleWarning.color(NamedTextColor.RED)); + return; + } + } + + IdMapper mapper; + try { + mapper = ViaVersionHelper.getBlockRegistryForVersion(this.plugin.allowedBlockRegistry, playerVersion); + } catch (Exception e) { + String clientDescription = "client: " + ProtocolVersion.getProtocol(playerVersion); + String serverDescription = "server: " + ProtocolVersion.getProtocol(SharedConstants.getProtocolVersion()); + String description = clientDescription + " <-> " + serverDescription; + Component text = Component.text("Axiom+ViaVersion: " + e.getMessage() + " (" + description + ")"); + + if (incompatibleDataVersion.equals("warn")) { + player.sendMessage(text.color(NamedTextColor.RED)); + } else { + player.kick(text); + } + return; + } + + this.plugin.playerBlockRegistry.put(player.getUniqueId(), mapper); + this.plugin.playerProtocolVersion.put(player.getUniqueId(), playerVersion); + + Component text = Component.text("Axiom: Warning, client and server versions don't match. " + + "Axiom will try to use ViaVersion conversions, but this process may cause problems"); player.sendMessage(text.color(NamedTextColor.RED)); - return; - } else if (!incompatibleDataVersion.equals("ignore")) { - player.kick(text); - return; } } @@ -85,7 +145,7 @@ public class HelloPacketListener implements PluginMessageListener { Component text = Component.text("This server requires the use of Axiom 2.3 or later. Contact the server administrator if you believe this is unintentional"); String unsupportedRestrictions = plugin.configuration.getString("client-doesnt-support-restrictions"); - if (unsupportedRestrictions == null) unsupportedRestrictions = "kick"; + if (unsupportedRestrictions == null) unsupportedRestrictions = "warn"; if (unsupportedRestrictions.equals("warn")) { player.sendMessage(text.color(NamedTextColor.RED)); return; @@ -110,7 +170,7 @@ public class HelloPacketListener implements PluginMessageListener { } // Enable - FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.buffer(), MinecraftServer.getServer().registryAccess()); buf.writeBoolean(true); buf.writeInt(handshakeEvent.getMaxBufferSize()); // Max Buffer Size buf.writeBoolean(false); // No source info @@ -125,30 +185,32 @@ public class HelloPacketListener implements PluginMessageListener { // Initialize Hotbars PersistentDataContainer container = player.getPersistentDataContainer(); - int activeHotbarIndex = container.getOrDefault(AxiomConstants.ACTIVE_HOTBAR_INDEX, PersistentDataType.BYTE, (byte) 0); - PersistentDataContainer hotbarItems = container.get(AxiomConstants.HOTBAR_DATA, PersistentDataType.TAG_CONTAINER); - if (hotbarItems != null) { - buf = new FriendlyByteBuf(Unpooled.buffer()); - buf.writeByte((byte) activeHotbarIndex); - for (int i=0; i<9*9; i++) { - // Ignore selected hotbar - if (i / 9 == activeHotbarIndex) { - buf.writeItem(net.minecraft.world.item.ItemStack.EMPTY); - } else { - ItemStack stack = hotbarItems.get(new NamespacedKey("axiom", "slot_"+i), ItemStackDataType.INSTANCE); - buf.writeItem(CraftItemStack.asNMSCopy(stack)); + if (!this.plugin.isMismatchedDataVersion(player.getUniqueId())) { + int activeHotbarIndex = container.getOrDefault(AxiomConstants.ACTIVE_HOTBAR_INDEX, PersistentDataType.BYTE, (byte) 0); + PersistentDataContainer hotbarItems = container.get(AxiomConstants.HOTBAR_DATA, PersistentDataType.TAG_CONTAINER); + if (hotbarItems != null) { + buf = new RegistryFriendlyByteBuf(Unpooled.buffer(), MinecraftServer.getServer().registryAccess()); + buf.writeByte((byte) activeHotbarIndex); + for (int i=0; i<9*9; i++) { + // Ignore selected hotbar + if (i / 9 == activeHotbarIndex) { + net.minecraft.world.item.ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, net.minecraft.world.item.ItemStack.EMPTY); + } else { + ItemStack stack = hotbarItems.get(new NamespacedKey("axiom", "slot_"+i), ItemStackDataType.INSTANCE); + net.minecraft.world.item.ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, CraftItemStack.asNMSCopy(stack)); + } } - } - byte[] bytes = new byte[buf.writerIndex()]; - buf.getBytes(0, bytes); - player.sendPluginMessage(this.plugin, "axiom:initialize_hotbars", bytes); + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + player.sendPluginMessage(this.plugin, "axiom:initialize_hotbars", bytes); + } } // Initialize Views UUID activeView = container.get(AxiomConstants.ACTIVE_VIEW, UUIDDataType.INSTANCE); if (activeView != null) { - buf = new FriendlyByteBuf(Unpooled.buffer()); + buf = new RegistryFriendlyByteBuf(Unpooled.buffer(), MinecraftServer.getServer().registryAccess()); buf.writeUUID(activeView); PersistentDataContainer[] views = container.get(AxiomConstants.VIEWS, PersistentDataType.TAG_CONTAINER_ARRAY); diff --git a/src/main/java/com/moulberry/axiom/packet/ManipulateEntityPacketListener.java b/src/main/java/com/moulberry/axiom/packet/ManipulateEntityPacketListener.java index b2f7521..e59c372 100644 --- a/src/main/java/com/moulberry/axiom/packet/ManipulateEntityPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/ManipulateEntityPacketListener.java @@ -4,7 +4,11 @@ import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.NbtSanitization; import com.moulberry.axiom.event.AxiomManipulateEntityEvent; import com.moulberry.axiom.integration.Integration; +import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; +import com.moulberry.axiom.viaversion.UnknownVersionHelper; +import com.moulberry.axiom.viaversion.ViaVersionHelper; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -19,7 +23,7 @@ import net.minecraft.world.entity.decoration.ItemFrame; import net.minecraft.world.level.block.Rotation; import net.minecraft.world.phys.Vec3; import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -46,7 +50,7 @@ public class ManipulateEntityPacketListener implements PluginMessageListener { public record ManipulateEntry(UUID uuid, @Nullable Set relativeMovementSet, @Nullable Vec3 position, float yaw, float pitch, CompoundTag merge, PassengerManipulation passengerManipulation, List passengers) { - public static ManipulateEntry read(FriendlyByteBuf friendlyByteBuf) { + public static ManipulateEntry read(FriendlyByteBuf friendlyByteBuf, Player player) { UUID uuid = friendlyByteBuf.readUUID(); int flags = friendlyByteBuf.readByte(); @@ -61,13 +65,13 @@ public class ManipulateEntityPacketListener implements PluginMessageListener { pitch = friendlyByteBuf.readFloat(); } - CompoundTag nbt = friendlyByteBuf.readNbt(); + CompoundTag nbt = UnknownVersionHelper.readTagUnknown(friendlyByteBuf, player); PassengerManipulation passengerManipulation = friendlyByteBuf.readEnum(PassengerManipulation.class); List passengers = List.of(); if (passengerManipulation == PassengerManipulation.ADD_LIST || passengerManipulation == PassengerManipulation.REMOVE_LIST) { passengers = friendlyByteBuf.readCollection(FriendlyByteBuf.limitValue(ArrayList::new, 1000), - FriendlyByteBuf::readUUID); + buffer -> buffer.readUUID()); } return new ManipulateEntry(uuid, relativeMovementSet, position, yaw, pitch, nbt, @@ -79,11 +83,15 @@ public class ManipulateEntityPacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { - return; + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); } + } - if (!player.hasPermission("axiom.entity.*") && !player.hasPermission("axiom.entity.manipulate")) { + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.entity.manipulate", true)) { return; } @@ -93,7 +101,7 @@ public class ManipulateEntityPacketListener implements PluginMessageListener { FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); List entries = friendlyByteBuf.readCollection(FriendlyByteBuf.limitValue(ArrayList::new, 1000), - ManipulateEntry::read); + buf -> ManipulateEntry.read(buf, player)); ServerLevel serverLevel = ((CraftWorld)player.getWorld()).getHandle(); diff --git a/src/main/java/com/moulberry/axiom/packet/MarkerNbtRequestPacketListener.java b/src/main/java/com/moulberry/axiom/packet/MarkerNbtRequestPacketListener.java index ff640c0..0902d2c 100644 --- a/src/main/java/com/moulberry/axiom/packet/MarkerNbtRequestPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/MarkerNbtRequestPacketListener.java @@ -3,12 +3,13 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.marker.MarkerData; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Marker; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -24,11 +25,15 @@ public class MarkerNbtRequestPacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { - return; + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); } + } - if (!player.hasPermission("axiom.entity.*") && !player.hasPermission("axiom.entity.manipulate")) { + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.entity.manipulate", true)) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java b/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java index 8872b55..b538c44 100644 --- a/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java @@ -5,8 +5,10 @@ import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.VersionHelper; import com.moulberry.axiom.buffer.CompressedBlockEntity; import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; +import com.viaversion.viaversion.api.Via; import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.longs.*; +import net.kyori.adventure.text.Component; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; @@ -20,11 +22,11 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.PalettedContainer; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -41,12 +43,20 @@ public class RequestChunkDataPacketListener implements PluginMessageListener { } @Override - public void onPluginMessageReceived(@NotNull String channel, @NotNull Player bukkitPlayer, @NotNull byte[] message) { + public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); + } + } + + private void process(Player bukkitPlayer, byte[] message) { ServerPlayer player = ((CraftPlayer)bukkitPlayer).getHandle(); FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); long id = friendlyByteBuf.readLong(); - if (!this.plugin.canUseAxiom(bukkitPlayer)) { + if (!this.plugin.canUseAxiom(bukkitPlayer, "axiom.chunk.request") || this.plugin.isMismatchedDataVersion(bukkitPlayer.getUniqueId())) { // We always send an 'empty' response in order to make the client happy sendEmptyResponse(player, id); return; @@ -105,7 +115,7 @@ public class RequestChunkDataPacketListener implements PluginMessageListener { BlockEntity blockEntity = chunk.getBlockEntity(mutableBlockPos, LevelChunk.EntityCreationType.IMMEDIATE); if (blockEntity != null) { - CompoundTag tag = blockEntity.saveWithoutMetadata(); + CompoundTag tag = blockEntity.saveWithoutMetadata(player.registryAccess()); blockEntityMap.put(pos, CompressedBlockEntity.compress(tag, baos)); } } @@ -144,7 +154,7 @@ public class RequestChunkDataPacketListener implements PluginMessageListener { mutableBlockPos.set(sx*16 + x, sy*16 + y, sz*16 + z); BlockEntity blockEntity = chunk.getBlockEntity(mutableBlockPos, LevelChunk.EntityCreationType.CHECK); if (blockEntity != null) { - CompoundTag tag = blockEntity.saveWithoutMetadata(); + CompoundTag tag = blockEntity.saveWithoutMetadata(player.registryAccess()); blockEntityMap.put(mutableBlockPos.asLong(), CompressedBlockEntity.compress(tag, baos)); } } diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java index 48615af..86b5e03 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java @@ -8,7 +8,8 @@ import com.moulberry.axiom.buffer.BlockBuffer; import com.moulberry.axiom.buffer.CompressedBlockEntity; import com.moulberry.axiom.integration.Integration; import com.moulberry.axiom.integration.SectionPermissionChecker; -import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; +import com.moulberry.axiom.integration.coreprotect.CoreProtectIntegration; +import com.moulberry.axiom.viaversion.UnknownVersionHelper; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; import net.minecraft.ChatFormatting; @@ -33,7 +34,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.PalettedContainer; @@ -77,7 +78,7 @@ public class SetBlockBufferPacketListener { boolean continuation = friendlyByteBuf.readBoolean(); if (!continuation) { - friendlyByteBuf.readNbt(); // Discard sourceInfo + UnknownVersionHelper.skipTagUnknown(friendlyByteBuf, player.getBukkitEntity()); } RateLimiter rateLimiter = this.plugin.getBlockBufferRateLimiter(player.getUUID()); @@ -85,7 +86,7 @@ public class SetBlockBufferPacketListener { byte type = friendlyByteBuf.readByte(); if (type == 0) { AtomicBoolean reachedRateLimit = new AtomicBoolean(false); - BlockBuffer buffer = BlockBuffer.load(friendlyByteBuf, rateLimiter, reachedRateLimit); + BlockBuffer buffer = BlockBuffer.load(friendlyByteBuf, rateLimiter, reachedRateLimit, this.plugin.getBlockRegistry(player.getUUID())); if (reachedRateLimit.get()) { player.sendSystemMessage(Component.literal("[Axiom] Exceeded server rate-limit of " + (int)rateLimiter.getRate() + " sections per second") .withStyle(ChatFormatting.RED)); @@ -124,7 +125,7 @@ public class SetBlockBufferPacketListener { ServerLevel world = player.serverLevel(); if (!world.dimension().equals(worldKey)) return; - if (!this.plugin.canUseAxiom(player.getBukkitEntity())) { + if (!this.plugin.canUseAxiom(player.getBukkitEntity(), "axiom.build.section")) { return; } @@ -281,12 +282,20 @@ public class SetBlockBufferPacketListener { int key = x | (y << 4) | (z << 8); CompressedBlockEntity savedBlockEntity = blockEntityChunkMap.get((short) key); if (savedBlockEntity != null) { - blockEntity.load(savedBlockEntity.decompress()); + blockEntity.loadWithComponents(savedBlockEntity.decompress(), player.registryAccess()); } } } else if (old.hasBlockEntity()) { chunk.removeBlockEntity(blockPos); } + + if (CoreProtectIntegration.isEnabled() && old != blockState) { + String changedBy = player.getBukkitEntity().getName(); + BlockPos changedPos = new BlockPos(bx, by, bz); + + CoreProtectIntegration.logRemoval(changedBy, old, world.getWorld(), changedPos); + CoreProtectIntegration.logPlacement(changedBy, blockState, world.getWorld(), changedPos); + } } } } @@ -316,7 +325,7 @@ public class SetBlockBufferPacketListener { ServerLevel world = player.serverLevel(); if (!world.dimension().equals(worldKey)) return; - if (!this.plugin.canUseAxiom(player.getBukkitEntity())) { + if (!this.plugin.canUseAxiom(player.getBukkitEntity(), "axiom.build.section")) { return; } @@ -340,16 +349,16 @@ public class SetBlockBufferPacketListener { return; } - var chunk = (LevelChunk) world.getChunk(x >> 2, z >> 2, ChunkStatus.FULL, false); - if (chunk == null) return; - - var section = chunk.getSection(cy - minSection); - PalettedContainer> container = (PalettedContainer>) section.getBiomes(); - var holder = registry.getHolder(biome); if (holder.isPresent()) { + var chunk = (LevelChunk) world.getChunk(x >> 2, z >> 2, ChunkStatus.FULL, false); + if (chunk == null) return; + + var section = chunk.getSection(cy - minSection); + PalettedContainer> container = (PalettedContainer>) section.getBiomes(); + if (!Integration.canPlaceBlock(player.getBukkitEntity(), - new Location(player.getBukkitEntity().getWorld(), x+1, y+1, z+1))) return; + new Location(player.getBukkitEntity().getWorld(), (x<<2)+1, (y<<2)+1, (z<<2)+1))) return; container.set(x & 3, y & 3, z & 3, holder.get()); changedChunks.add(chunk); diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java index 1a3ef52..0bf6d59 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java @@ -3,10 +3,13 @@ package com.moulberry.axiom.packet; import com.google.common.collect.Maps; import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.integration.Integration; +import com.moulberry.axiom.integration.coreprotect.CoreProtectIntegration; import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.IdMapper; import net.minecraft.core.SectionPos; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; @@ -29,9 +32,9 @@ import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import org.bukkit.Location; import org.bukkit.block.BlockFace; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlock; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; @@ -67,8 +70,16 @@ public class SetBlockPacketListener implements PluginMessageListener { } @Override - public void onPluginMessageReceived(@NotNull String channel, @NotNull Player bukkitPlayer, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(bukkitPlayer)) { + public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); + } + } + + private void process(Player bukkitPlayer, byte[] message) { + if (!this.plugin.canUseAxiom(bukkitPlayer, "axiom.build.place")) { return; } @@ -79,8 +90,9 @@ public class SetBlockPacketListener implements PluginMessageListener { // Read packet FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); IntFunction> mapFunction = FriendlyByteBuf.limitValue(Maps::newLinkedHashMapWithExpectedSize, 512); + IdMapper registry = this.plugin.getBlockRegistry(bukkitPlayer.getUniqueId()); Map blocks = friendlyByteBuf.readMap(mapFunction, - FriendlyByteBuf::readBlockPos, buf -> buf.readById(this.plugin.allowedBlockRegistry)); + buf -> buf.readBlockPos(), buf -> buf.readById(registry::byIdOrThrow)); boolean updateNeighbors = friendlyByteBuf.readBoolean(); int reason = friendlyByteBuf.readVarInt(); @@ -129,6 +141,10 @@ public class SetBlockPacketListener implements PluginMessageListener { BlockPos blockPos = entry.getKey(); BlockState blockState = entry.getValue(); + if (blockState == null) { + continue; + } + // Disallow in unloaded chunks if (!player.level().isLoaded(blockPos)) { continue; @@ -143,8 +159,14 @@ public class SetBlockPacketListener implements PluginMessageListener { continue; } + if (CoreProtectIntegration.isEnabled()) { + BlockState old = player.level().getBlockState(blockPos); + CoreProtectIntegration.logRemoval(bukkitPlayer.getName(), old, world, blockPos); + } + // Place block player.level().setBlock(blockPos, blockState, 3); + CoreProtectIntegration.logPlacement(bukkitPlayer.getName(), blockState, world, blockPos); } } else { int count = 0; @@ -154,6 +176,10 @@ public class SetBlockPacketListener implements PluginMessageListener { BlockPos blockPos = entry.getKey(); BlockState blockState = entry.getValue(); + if (blockState == null) { + continue; + } + // Disallow in unloaded chunks if (!player.level().isLoaded(blockPos)) { continue; @@ -258,6 +284,14 @@ public class SetBlockPacketListener implements PluginMessageListener { if (oldPoi.isPresent()) level.getPoiManager().remove(blockPos); if (newPoi.isPresent()) level.getPoiManager().add(blockPos, newPoi.get()); } + + if (CoreProtectIntegration.isEnabled()) { + String changedBy = player.getBukkitEntity().getName(); + BlockPos changedPos = new BlockPos(bx, by, bz); + + CoreProtectIntegration.logRemoval(changedBy, old, world, changedPos); + CoreProtectIntegration.logPlacement(changedBy, blockState, world, changedPos); + } } boolean nowHasOnlyAir = section.hasOnlyAir(); diff --git a/src/main/java/com/moulberry/axiom/packet/SetEditorViewsPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetEditorViewsPacketListener.java index ce1465a..e718080 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetEditorViewsPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetEditorViewsPacketListener.java @@ -6,6 +6,7 @@ import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.View; import com.moulberry.axiom.persistence.UUIDDataType; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.network.FriendlyByteBuf; import org.bukkit.entity.Player; import org.bukkit.persistence.PersistentDataContainer; @@ -26,7 +27,15 @@ public class SetEditorViewsPacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); + } + } + + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.editor.views")) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java index 8feb59a..421e745 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java @@ -3,9 +3,10 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.event.AxiomFlySpeedChangeEvent; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.network.FriendlyByteBuf; import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -19,7 +20,15 @@ public class SetFlySpeedPacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); + } + } + + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.player.speed")) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/SetGamemodePacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetGamemodePacketListener.java index 0251410..de1c535 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetGamemodePacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetGamemodePacketListener.java @@ -3,11 +3,12 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.event.AxiomGameModeChangeEvent; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.level.GameType; import org.bukkit.Bukkit; import org.bukkit.GameMode; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -21,7 +22,15 @@ public class SetGamemodePacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); + } + } + + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.player.gamemode")) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/SetHotbarSlotPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetHotbarSlotPacketListener.java index aa414af..f8a1a67 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetHotbarSlotPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetHotbarSlotPacketListener.java @@ -3,10 +3,15 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomConstants; import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.persistence.ItemStackDataType; +import com.viaversion.viaversion.api.Via; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.world.item.ItemStack; import org.bukkit.NamespacedKey; -import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.entity.Player; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; @@ -22,14 +27,22 @@ public class SetHotbarSlotPacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); + } + } + + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.player.hotbar") || this.plugin.isMismatchedDataVersion(player.getUniqueId())) { return; } - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); + RegistryFriendlyByteBuf friendlyByteBuf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(message), ((CraftPlayer)player).getHandle().registryAccess()); int index = friendlyByteBuf.readByte(); if (index < 0 || index >= 9*9) return; - net.minecraft.world.item.ItemStack nmsStack = friendlyByteBuf.readItem(); + net.minecraft.world.item.ItemStack nmsStack = ItemStack.OPTIONAL_STREAM_CODEC.decode(friendlyByteBuf); PersistentDataContainer container = player.getPersistentDataContainer(); PersistentDataContainer hotbarItems = container.get(AxiomConstants.HOTBAR_DATA, PersistentDataType.TAG_CONTAINER); diff --git a/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java index 11ce53d..f1bca47 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java @@ -4,6 +4,7 @@ import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.event.AxiomTimeChangeEvent; import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.core.registries.Registries; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceKey; @@ -11,7 +12,7 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -25,7 +26,15 @@ public class SetTimePacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); + } + } + + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.world.time")) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java b/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java index a6b4691..73bd459 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java @@ -5,6 +5,7 @@ import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; import com.moulberry.axiom.world_properties.server.ServerWorldPropertyHolder; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import org.bukkit.entity.Player; @@ -20,7 +21,15 @@ public class SetWorldPropertyListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); + } + } + + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.world.property")) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/SpawnEntityPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SpawnEntityPacketListener.java index bf8ea46..90543ee 100644 --- a/src/main/java/com/moulberry/axiom/packet/SpawnEntityPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SpawnEntityPacketListener.java @@ -4,7 +4,10 @@ import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.NbtSanitization; import com.moulberry.axiom.integration.Integration; import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; +import com.moulberry.axiom.viaversion.UnknownVersionHelper; +import com.moulberry.axiom.viaversion.ViaVersionHelper; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -18,7 +21,7 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Rotation; import net.minecraft.world.phys.Vec3; import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -38,22 +41,21 @@ public class SpawnEntityPacketListener implements PluginMessageListener { private record SpawnEntry(UUID newUuid, double x, double y, double z, float yaw, float pitch, @Nullable UUID copyFrom, CompoundTag tag) { - public SpawnEntry(FriendlyByteBuf friendlyByteBuf) { - this(friendlyByteBuf.readUUID(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), - friendlyByteBuf.readDouble(), friendlyByteBuf.readFloat(), friendlyByteBuf.readFloat(), - friendlyByteBuf.readNullable(FriendlyByteBuf::readUUID), friendlyByteBuf.readNbt()); - } } private static final Rotation[] ROTATION_VALUES = Rotation.values(); @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { - return; + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); } + } - if (!player.hasPermission("axiom.entity.*") && !player.hasPermission("axiom.entity.spawn")) { + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.entity.spawn", true)) { return; } @@ -62,7 +64,10 @@ public class SpawnEntityPacketListener implements PluginMessageListener { } FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); - List entries = friendlyByteBuf.readCollection(FriendlyByteBuf.limitValue(ArrayList::new, 1000), SpawnEntry::new); + List entries = friendlyByteBuf.readCollection(FriendlyByteBuf.limitValue(ArrayList::new, 1000), + buf -> new SpawnEntry(buf.readUUID(), buf.readDouble(), buf.readDouble(), + buf.readDouble(), buf.readFloat(), buf.readFloat(), + buf.readNullable(buffer -> buffer.readUUID()), UnknownVersionHelper.readTagUnknown(buf, player))); ServerLevel serverLevel = ((CraftWorld)player.getWorld()).getHandle(); diff --git a/src/main/java/com/moulberry/axiom/packet/SwitchActiveHotbarPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SwitchActiveHotbarPacketListener.java index 5307ec3..f1056ef 100644 --- a/src/main/java/com/moulberry/axiom/packet/SwitchActiveHotbarPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SwitchActiveHotbarPacketListener.java @@ -4,11 +4,14 @@ import com.moulberry.axiom.AxiomConstants; import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.persistence.ItemStackDataType; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; import org.bukkit.GameMode; import org.bukkit.Material; import org.bukkit.NamespacedKey; -import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataContainer; @@ -25,17 +28,25 @@ public class SwitchActiveHotbarPacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); + } + } + + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.player.hotbar") || this.plugin.isMismatchedDataVersion(player.getUniqueId())) { return; } - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); + RegistryFriendlyByteBuf friendlyByteBuf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(message), ((CraftPlayer)player).getHandle().registryAccess()); int oldHotbarIndex = friendlyByteBuf.readByte(); int activeHotbarIndex = friendlyByteBuf.readByte(); ItemStack[] hotbarItems = new ItemStack[9]; for (int i=0; i<9; i++) { - hotbarItems[i] = CraftItemStack.asCraftMirror(friendlyByteBuf.readItem()); + hotbarItems[i] = CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.OPTIONAL_STREAM_CODEC.decode(friendlyByteBuf)); } PersistentDataContainer container = player.getPersistentDataContainer(); diff --git a/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java b/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java index a36a277..dfbeb82 100644 --- a/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java @@ -4,13 +4,14 @@ import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.event.AxiomUnknownTeleportEvent; import com.moulberry.axiom.event.AxiomTeleportEvent; import io.netty.buffer.Unpooled; +import net.kyori.adventure.text.Component; import net.minecraft.core.registries.Registries; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.Level; import org.bukkit.*; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -24,7 +25,15 @@ public class TeleportPacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!this.plugin.canUseAxiom(player)) { + try { + this.process(player, message); + } catch (Throwable t) { + player.kick(Component.text("Error while processing packet " + channel + ": " + t.getMessage())); + } + } + + private void process(Player player, byte[] message) { + if (!this.plugin.canUseAxiom(player, "axiom.world.teleport")) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/UploadBlueprintPacketListener.java b/src/main/java/com/moulberry/axiom/packet/UploadBlueprintPacketListener.java index e85270b..0a6d2eb 100644 --- a/src/main/java/com/moulberry/axiom/packet/UploadBlueprintPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/UploadBlueprintPacketListener.java @@ -5,7 +5,9 @@ import com.moulberry.axiom.blueprint.BlueprintIo; import com.moulberry.axiom.blueprint.RawBlueprint; import com.moulberry.axiom.blueprint.ServerBlueprintManager; import com.moulberry.axiom.blueprint.ServerBlueprintRegistry; +import net.minecraft.SharedConstants; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import java.io.BufferedOutputStream; @@ -22,7 +24,12 @@ public class UploadBlueprintPacketListener { } public void onReceive(ServerPlayer serverPlayer, FriendlyByteBuf friendlyByteBuf) { - if (!this.plugin.canUseAxiom(serverPlayer.getBukkitEntity())) { + if (!this.plugin.canUseAxiom(serverPlayer.getBukkitEntity(), "axiom.blueprint.upload")) { + return; + } + + if (this.plugin.isMismatchedDataVersion(serverPlayer.getUUID())) { + serverPlayer.sendSystemMessage(Component.literal("Axiom+ViaVersion: This feature isn't supported. Switch your client version to " + SharedConstants.VERSION_STRING + " to use this")); return; } @@ -47,25 +54,34 @@ public class UploadBlueprintPacketListener { return; } - Path path = this.plugin.blueprintFolder.resolve(relative); + String pathName = pathStr.substring(0, pathStr.length()-3); - // Write file - try { - Files.createDirectories(path.getParent()); - } catch (IOException e) { - return; - } - try (OutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(path))) { - BlueprintIo.writeRaw(outputStream, rawBlueprint); - } catch (IOException e) { - return; - } + serverPlayer.getServer().execute(() -> { + try { + Path path = this.plugin.blueprintFolder.resolve(relative); - // Update registry - registry.blueprints().put("/" + pathStr.substring(0, pathStr.length()-3), rawBlueprint); + // Write file + try { + Files.createDirectories(path.getParent()); + } catch (IOException e) { + return; + } + try (OutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(path))) { + BlueprintIo.writeRaw(outputStream, rawBlueprint); + } catch (IOException e) { + return; + } - // Resend manifest - ServerBlueprintManager.sendManifest(serverPlayer.getServer().getPlayerList().getPlayers()); + // Update registry + registry.blueprints().put("/" + pathName, rawBlueprint); + + // Resend manifest + ServerBlueprintManager.sendManifest(serverPlayer.getServer().getPlayerList().getPlayers()); + } catch (Throwable t) { + serverPlayer.getBukkitEntity().kick(net.kyori.adventure.text.Component.text( + "An error occured while uploading blueprint: " + t.getMessage())); + } + }); } } diff --git a/src/main/java/com/moulberry/axiom/persistence/ItemStackDataType.java b/src/main/java/com/moulberry/axiom/persistence/ItemStackDataType.java index 14a2fe6..963d503 100644 --- a/src/main/java/com/moulberry/axiom/persistence/ItemStackDataType.java +++ b/src/main/java/com/moulberry/axiom/persistence/ItemStackDataType.java @@ -1,8 +1,9 @@ package com.moulberry.axiom.persistence; import net.minecraft.nbt.CompoundTag; -import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; -import org.bukkit.craftbukkit.v1_20_R3.persistence.CraftPersistentDataContainer; +import net.minecraft.server.MinecraftServer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataAdapterContext; import org.bukkit.persistence.PersistentDataContainer; @@ -28,8 +29,7 @@ public class ItemStackDataType implements PersistentDataType> blockRegistryCache = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectOpenHashMap blockRegistryErrorCache = new Int2ObjectOpenHashMap<>(); + + public static IdMapper getBlockRegistryForVersion(IdMapper mapper, int playerVersion) { + if (blockRegistryErrorCache.containsKey(playerVersion)) { + throw new RuntimeException(blockRegistryErrorCache.get(playerVersion)); + } + if (blockRegistryCache.containsKey(playerVersion)) { + return blockRegistryCache.get(playerVersion); + } + + List path = Via.getManager().getProtocolManager().getProtocolPath(playerVersion, + SharedConstants.getProtocolVersion()); + + if (path == null) { + blockRegistryErrorCache.put(playerVersion, "Failed to find protocol path"); + throw new RuntimeException("Failed to find protocol path"); + } + + for (int i = path.size()-1; i >= 0; i--) { + ProtocolPathEntry protocolPathEntry = path.get(i); + + MappingData mappingData = protocolPathEntry.protocol().getMappingData(); + + if (mappingData == null) { + blockRegistryErrorCache.put(playerVersion, "Failed to load mapping data (" + protocolPathEntry + ")"); + throw new RuntimeException("Failed to load mapping data (" + protocolPathEntry + ")"); + } + + Mappings blockStateMappings = mappingData.getBlockStateMappings(); + + if (blockStateMappings == null) { + blockRegistryErrorCache.put(playerVersion, "Failed to load BlockState mappings (" + protocolPathEntry + ")"); + throw new RuntimeException("Failed to load BlockState mappings (" + protocolPathEntry + ")"); + } + + mapper = ViaVersionHelper.applyMappings(mapper, blockStateMappings); + } + + blockRegistryCache.put(playerVersion, mapper); + return mapper; + } + + private static IdMapper applyMappings(IdMapper registry, Mappings mappings) { + IdMapper newBlockRegistry = new IdMapper<>(); + + // Add empty mappings for non-existent blocks + int size = mappings.mappedSize(); + for (int i = 0; i < size; i++) { + newBlockRegistry.addMapping(BlockBuffer.EMPTY_STATE, i); + } + + // Map blocks + for (int i = 0; i < registry.size(); i++) { + BlockState blockState = registry.byId(i); + + if (blockState != null) { + int newId = mappings.getNewId(i); + if (newId >= 0) { + newBlockRegistry.addMapping(blockState, newId); + } + } + } + + // Ensure block -> id is correct for the empty state + int newEmptyStateId = mappings.getNewId(registry.getId(BlockBuffer.EMPTY_STATE)); + if (newEmptyStateId >= 0) { + newBlockRegistry.addMapping(BlockBuffer.EMPTY_STATE, newEmptyStateId); + } + + return newBlockRegistry; + } + + private static final int UNNAMED_COMPOUND_TAG_CHANGE_VERSION = 764; // 1.20.2 + + public static void skipTagViaVersion(FriendlyByteBuf friendlyByteBuf, Player player) throws Exception { + skipTagViaVersion(friendlyByteBuf, AxiomPaper.PLUGIN.getProtocolVersionFor(player.getUniqueId())); + } + + public static void skipTagViaVersion(FriendlyByteBuf friendlyByteBuf, int playerVersion) throws Exception { + getTagType(playerVersion).read(friendlyByteBuf); + } + + public static CompoundTag readTagViaVersion(FriendlyByteBuf friendlyByteBuf, Player player) throws Exception { + return readTagViaVersion(friendlyByteBuf, AxiomPaper.PLUGIN.getProtocolVersionFor(player.getUniqueId())); + } + + public static CompoundTag readTagViaVersion(FriendlyByteBuf friendlyByteBuf, int playerVersion) throws Exception { + Type from = getTagType(playerVersion); + Type to = getTagType(SharedConstants.getProtocolVersion()); + + return readTagViaVersion(friendlyByteBuf, from, to); + } + + private static Type getTagType(int version) { + if (version < UNNAMED_COMPOUND_TAG_CHANGE_VERSION) { + return Type.NAMED_COMPOUND_TAG; + } else { + return Type.COMPOUND_TAG; + } + } + + private static CompoundTag readTagViaVersion(FriendlyByteBuf friendlyByteBuf, + Type from, + Type to) throws Exception { + if (from == to) { + return friendlyByteBuf.readNbt(); + } + + com.viaversion.viaversion.libs.opennbt.tag.builtin.CompoundTag tag = from.read(friendlyByteBuf); + FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer()); + to.write(buffer, tag); + return buffer.readNbt(); + } + } diff --git a/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java index 23790f6..46a58a5 100644 --- a/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java +++ b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java @@ -87,7 +87,7 @@ public abstract class WorldPropertyDataType { if (value == null) value = Items.AIR; FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8)); - buf.writeId(BuiltInRegistries.ITEM, value); + buf.writeById(BuiltInRegistries.ITEM::getIdOrThrow, value); byte[] bytes = new byte[buf.writerIndex()]; buf.getBytes(0, bytes); @@ -97,7 +97,7 @@ public abstract class WorldPropertyDataType { @Override public Item deserialize(byte[] bytes) { FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(bytes)); - return buf.readById(BuiltInRegistries.ITEM); + return buf.readById(BuiltInRegistries.ITEM::byIdOrThrow); } }; @@ -112,7 +112,7 @@ public abstract class WorldPropertyDataType { if (value == null) value = Blocks.AIR; FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8)); - buf.writeId(BuiltInRegistries.BLOCK, value); + buf.writeById(BuiltInRegistries.BLOCK::getIdOrThrow, value); byte[] bytes = new byte[buf.writerIndex()]; buf.getBytes(0, bytes); @@ -122,7 +122,7 @@ public abstract class WorldPropertyDataType { @Override public Block deserialize(byte[] bytes) { FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(bytes)); - return buf.readById(BuiltInRegistries.BLOCK); + return buf.readById(BuiltInRegistries.BLOCK::byIdOrThrow); } }; diff --git a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java index 09e0b9d..f26aaee 100644 --- a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java +++ b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java @@ -9,19 +9,20 @@ import net.minecraft.server.level.ServerLevel; import org.bukkit.GameRule; import org.bukkit.NamespacedKey; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; +import java.lang.ref.Reference; import java.util.*; public class ServerWorldPropertiesRegistry { private final LinkedHashMap>> propertyList = new LinkedHashMap<>(); private final Map> propertyMap = new HashMap<>(); - private final World world; + private final Reference world; - public ServerWorldPropertiesRegistry(World world) { + public ServerWorldPropertiesRegistry(Reference world) { this.world = world; this.registerDefault(); } @@ -32,9 +33,14 @@ public class ServerWorldPropertiesRegistry { @SuppressWarnings("unchecked") public void addCategory(WorldPropertyCategory category, List> properties) { + World world = this.world.get(); + if (world == null) { + return; + } + List> holders = new ArrayList<>(); for (ServerWorldPropertyBase property : properties) { - Object defaultValue = property.getDefaultValue(this.world); + Object defaultValue = property.getDefaultValue(world); holders.add(new ServerWorldPropertyHolder<>(defaultValue, (ServerWorldPropertyBase) property)); } diff --git a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertyBase.java b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertyBase.java index 2d7837b..9a3c300 100644 --- a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertyBase.java +++ b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertyBase.java @@ -6,7 +6,7 @@ import com.moulberry.axiom.world_properties.WorldPropertyWidgetType; import net.minecraft.resources.ResourceLocation; import org.bukkit.NamespacedKey; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.entity.Player; public abstract class ServerWorldPropertyBase { diff --git a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertyHolder.java b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertyHolder.java index 0755b69..28cfbe0 100644 --- a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertyHolder.java +++ b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertyHolder.java @@ -9,7 +9,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import org.bukkit.NamespacedKey; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.entity.Player; import java.util.Objects; diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 53267da..030c8eb 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,20 +5,63 @@ description: $description authors: - Moulberry api-version: "$apiVersion" +softdepend: ["CoreProtect", "ViaVersion", "WorldGuard", "PlotSquared"] permissions: - axiom.*: + axiom.all: description: Allows use of all default Axiom features default: op - + children: + axiom.*: true + axiom.use: + description: Allows basic use of axiom + default: op + axiom.debug: + description: Allows use of the /axiompaperdebug command + default: op axiom.entity.*: description: Allows use of all entity-related features (spawning, manipulating, deleting) default: op - axiom.entity.spawn: - description: Allows entity spawning - axiom.entity.manipulate: - description: Allows entity manipulation - axiom.entity.delete: - description: Allows entity deletion + children: + axiom.entity.spawn: true + axiom.entity.manipulate: true + axiom.entity.delete: true + axiom.blueprint.*: + description: Allows use of all blueprint-related features + default: op + children: + axiom.blueprint.upload: true + axiom.blueprint.request: true + axiom.blueprint.manifest: true + axiom.chunk.*: + description: Allows use of all chunk-related features + default: op + children: + axiom.chunk.request: true + axiom.build.*: + description: Allows use of all build-related features + default: op + children: + axiom.build.place: true + axiom.build.section: true + axiom.editor.*: + description: Allows use of all editor-related features + default: op + children: + axiom.editor.views: true + axiom.player.*: + description: Allows use of all player-related features + default: op + children: + axiom.player.speed: true + axiom.player.gamemode: true + axiom.player.hotbar: true + axiom.world.*: + description: Allows use of all world-related features + default: op + children: + axiom.world.time: true + axiom.world.property: true + axiom.world.teleport: true axiom.allow_copying_other_plots: description: This permission allows users to copy other user's plots