diff --git a/build.gradle.kts b/build.gradle.kts index 0f56c99..6aba9c5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } group = "com.moulberry.axiom" -version = "1.5.12" +version = "3.3.0" description = "Serverside component for Axiom on Paper" java { diff --git a/src/main/java/com/moulberry/axiom/AxiomConstants.java b/src/main/java/com/moulberry/axiom/AxiomConstants.java index 06186b3..d1dc0fa 100644 --- a/src/main/java/com/moulberry/axiom/AxiomConstants.java +++ b/src/main/java/com/moulberry/axiom/AxiomConstants.java @@ -12,7 +12,7 @@ public class AxiomConstants { } } - public static final int API_VERSION = 7; + public static final int API_VERSION = 8; public static final NamespacedKey ACTIVE_HOTBAR_INDEX = new NamespacedKey("axiom", "active_hotbar_index"); public static final NamespacedKey HOTBAR_DATA = new NamespacedKey("axiom", "hotbar_data"); diff --git a/src/main/java/com/moulberry/axiom/AxiomPaper.java b/src/main/java/com/moulberry/axiom/AxiomPaper.java index 8eeafe7..d24bc8e 100644 --- a/src/main/java/com/moulberry/axiom/AxiomPaper.java +++ b/src/main/java/com/moulberry/axiom/AxiomPaper.java @@ -65,6 +65,7 @@ public class AxiomPaper extends JavaPlugin implements Listener { private boolean logLargeBlockBufferChanges = false; public Path blueprintFolder = null; + public boolean allowAnnotations = false; @Override public void onEnable() { @@ -87,6 +88,11 @@ public class AxiomPaper extends JavaPlugin implements Listener { List disallowedBlocks = this.configuration.getStringList("disallowed-blocks"); this.allowedBlockRegistry = DisallowedBlocks.createAllowedBlockRegistry(disallowedBlocks); + this.allowAnnotations = this.configuration.getBoolean("allow-annotations"); + + int allowedCapabilities = calculateAllowedCapabilities(); + int infiniteReachLimit = this.configuration.getInt("infinite-reach-limit"); + Bukkit.getPluginManager().registerEvents(this, this); // Bukkit.getPluginManager().registerEvents(new WorldPropertiesExample(), this); CompressedBlockEntity.initialize(this); @@ -103,6 +109,7 @@ public class AxiomPaper extends JavaPlugin implements Listener { msg.registerOutgoingPluginChannel(this, "axiom:restrictions"); msg.registerOutgoingPluginChannel(this, "axiom:marker_data"); msg.registerOutgoingPluginChannel(this, "axiom:marker_nbt_response"); + msg.registerOutgoingPluginChannel(this, "axiom:annotation_update"); if (configuration.getBoolean("packet-handlers.hello")) { msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this)); @@ -158,6 +165,7 @@ public class AxiomPaper extends JavaPlugin implements Listener { if (configuration.getBoolean("packet-handlers.set-buffer")) { SetBlockBufferPacketListener setBlockBufferPacketListener = new SetBlockBufferPacketListener(this); UploadBlueprintPacketListener uploadBlueprintPacketListener = new UploadBlueprintPacketListener(this); + UpdateAnnotationPacketListener updateAnnotationPacketListener = new UpdateAnnotationPacketListener(this); RequestChunkDataPacketListener requestChunkDataPacketListener = allowLargeChunkDataRequest ? new RequestChunkDataPacketListener(this) : null; @@ -179,7 +187,7 @@ public class AxiomPaper extends JavaPlugin implements Listener { Connection connection = (Connection) channel.pipeline().get("packet_handler"); channel.pipeline().addBefore("decoder", "axiom-big-payload-handler", new AxiomBigPayloadHandler(payloadId, connection, setBlockBufferPacketListener, - uploadBlueprintPacketListener, requestChunkDataPacketListener)); + uploadBlueprintPacketListener, updateAnnotationPacketListener, requestChunkDataPacketListener)); } }); } @@ -252,12 +260,19 @@ public class AxiomPaper extends JavaPlugin implements Listener { } boolean allowImportingBlocks = player.hasPermission("axiom.can_import_blocks"); + boolean canCreateAnnotations = this.allowAnnotations && player.hasPermission("axiom.annotation.create"); if (restrictions.maxSectionsPerSecond != rateLimit || restrictions.canImportBlocks != allowImportingBlocks || + restrictions.canCreateAnnotations != canCreateAnnotations || + restrictions.allowedCapabilities != allowedCapabilities || + restrictions.infiniteReachLimit != infiniteReachLimit || !Objects.equals(restrictions.bounds, bounds)) { restrictions.maxSectionsPerSecond = rateLimit; restrictions.canImportBlocks = allowImportingBlocks; + restrictions.canCreateAnnotations = canCreateAnnotations; + restrictions.allowedCapabilities = allowedCapabilities; + restrictions.infiniteReachLimit = infiniteReachLimit; restrictions.bounds = bounds; send = true; } @@ -302,6 +317,25 @@ public class AxiomPaper extends JavaPlugin implements Listener { } } + private int calculateAllowedCapabilities() { + Set allowed = new HashSet<>(this.configuration.getStringList("allow-capabilities")); + if (allowed.contains("all")) { + return -1; + } + + int allowedCapabilities = 0; + if (allowed.contains("bulldozer")) allowedCapabilities |= Restrictions.ALLOW_BULLDOZER; + if (allowed.contains("replace_mode")) allowedCapabilities |= Restrictions.ALLOW_REPLACE_MODE; + if (allowed.contains("force_place")) allowedCapabilities |= Restrictions.ALLOW_FORCE_PLACE; + if (allowed.contains("no_updates")) allowedCapabilities |= Restrictions.ALLOW_NO_UPDATES; + if (allowed.contains("tinker")) allowedCapabilities |= Restrictions.ALLOW_TINKER; + if (allowed.contains("infinite_reach")) allowedCapabilities |= Restrictions.ALLOW_INFINITE_REACH; + if (allowed.contains("fast_place")) allowedCapabilities |= Restrictions.ALLOW_FAST_PLACE; + if (allowed.contains("angel_placement")) allowedCapabilities |= Restrictions.ALLOW_ANGEL_PLACEMENT; + if (allowed.contains("no_clip")) allowedCapabilities |= Restrictions.ALLOW_NO_CLIP; + return allowedCapabilities; + } + public boolean logLargeBlockBufferChanges() { return this.logLargeBlockBufferChanges; } diff --git a/src/main/java/com/moulberry/axiom/Restrictions.java b/src/main/java/com/moulberry/axiom/Restrictions.java index c36329c..c90b334 100644 --- a/src/main/java/com/moulberry/axiom/Restrictions.java +++ b/src/main/java/com/moulberry/axiom/Restrictions.java @@ -10,61 +10,76 @@ import java.util.Set; public class Restrictions { + public static final int ALLOW_BULLDOZER = 1; + public static final int ALLOW_REPLACE_MODE = 2; + public static final int ALLOW_FORCE_PLACE = 4; + public static final int ALLOW_NO_UPDATES = 8; + public static final int ALLOW_TINKER = 16; + public static final int ALLOW_INFINITE_REACH = 32; + public static final int ALLOW_FAST_PLACE = 64; + public static final int ALLOW_ANGEL_PLACEMENT = 128; + public static final int ALLOW_NO_CLIP = 256; + public static final int ALLOW_ALL = -1; + public boolean canImportBlocks = true; public boolean canUseEditor = true; public boolean canEditDisplayEntities = true; + public boolean canCreateAnnotations = false; + public int allowedCapabilities = ALLOW_ALL; + public int infiniteReachLimit = -1; + public int maxSectionsPerSecond = 0; public Set bounds = Set.of(); - public PlotSquaredIntegration.PlotBounds lastPlotBounds = null; public void send(AxiomPaper plugin, Player player) { - if (player.getListeningPluginChannels().contains("axiom:restrictions")) { - FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); - buf.writeBoolean(this.canImportBlocks); - buf.writeBoolean(this.canUseEditor); - buf.writeBoolean(this.canEditDisplayEntities); + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeBoolean(this.canImportBlocks); + buf.writeBoolean(this.canUseEditor); + buf.writeBoolean(this.canEditDisplayEntities); + buf.writeBoolean(this.canCreateAnnotations); + buf.writeInt(this.allowedCapabilities); + buf.writeInt(this.infiniteReachLimit); - buf.writeVarInt(this.maxSectionsPerSecond); + buf.writeVarInt(this.maxSectionsPerSecond); - 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; - if (minY < -2047) minY = -2047; - if (minY > 2047) minY = 2047; - if (minZ < -33554431) minZ = -33554431; - if (minZ > 33554431) minZ = 33554431; - - if (maxX < -33554431) maxX = -33554431; - if (maxX > 33554431) maxX = 33554431; - if (maxY < -2047) maxY = -2047; - if (maxY > 2047) maxY = 2047; - if (maxZ < -33554431) maxZ = -33554431; - if (maxZ > 33554431) maxZ = 33554431; - - buf.writeBlockPos(new BlockPos(minX, minY, minZ)); - buf.writeBlockPos(new BlockPos(maxX, maxY, maxZ)); + int count = Math.min(64, bounds.size()); + buf.writeVarInt(count); + for (PlotSquaredIntegration.PlotBox bound : this.bounds) { + if (count > 0) { + count -= 1; + } else { + break; } - byte[] bytes = new byte[buf.writerIndex()]; - buf.getBytes(0, bytes); - player.sendPluginMessage(plugin, "axiom:restrictions", bytes); + 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; + if (minY < -2047) minY = -2047; + if (minY > 2047) minY = 2047; + if (minZ < -33554431) minZ = -33554431; + if (minZ > 33554431) minZ = 33554431; + + if (maxX < -33554431) maxX = -33554431; + if (maxX > 33554431) maxX = 33554431; + if (maxY < -2047) maxY = -2047; + if (maxY > 2047) maxY = 2047; + if (maxZ < -33554431) maxZ = -33554431; + if (maxZ > 33554431) maxZ = 33554431; + + buf.writeBlockPos(new BlockPos(minX, minY, minZ)); + buf.writeBlockPos(new BlockPos(maxX, maxY, maxZ)); } + + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + player.sendPluginMessage(plugin, "axiom:restrictions", bytes); } @Override diff --git a/src/main/java/com/moulberry/axiom/WorldExtension.java b/src/main/java/com/moulberry/axiom/WorldExtension.java index 2152bb2..88031f6 100644 --- a/src/main/java/com/moulberry/axiom/WorldExtension.java +++ b/src/main/java/com/moulberry/axiom/WorldExtension.java @@ -1,5 +1,6 @@ package com.moulberry.axiom; +import com.moulberry.axiom.annotations.ServerAnnotations; import com.moulberry.axiom.marker.MarkerData; import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.longs.*; @@ -35,6 +36,10 @@ public class WorldExtension { public static void onPlayerJoin(World world, Player player) { ServerLevel level = ((CraftWorld)world).getHandle(); get(level).onPlayerJoin(player); + + if (AxiomPaper.PLUGIN.canUseAxiom(player, "axiom.annotations.view")) { + ServerAnnotations.sendAll(world, ((CraftPlayer)player).getHandle()); + } } public static void tick(MinecraftServer server, boolean sendMarkers, int maxChunkRelightsPerTick, int maxChunkSendsPerTick) { diff --git a/src/main/java/com/moulberry/axiom/annotations/AnnotationUpdateAction.java b/src/main/java/com/moulberry/axiom/annotations/AnnotationUpdateAction.java new file mode 100644 index 0000000..789e036 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/annotations/AnnotationUpdateAction.java @@ -0,0 +1,93 @@ +package com.moulberry.axiom.annotations; + +import com.moulberry.axiom.annotations.data.AnnotationData; +import net.minecraft.network.FriendlyByteBuf; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.UUID; + +public interface AnnotationUpdateAction { + + void write(FriendlyByteBuf friendlyByteBuf); + + static AnnotationUpdateAction read(FriendlyByteBuf friendlyByteBuf) { + byte type = friendlyByteBuf.readByte(); + if (type == 0) { + UUID uuid = friendlyByteBuf.readUUID(); + AnnotationData annotationData = AnnotationData.read(friendlyByteBuf); + if (annotationData == null) { + return null; + } + return new CreateAnnotation(uuid, annotationData); + } else if (type == 1) { + return new DeleteAnnotation(friendlyByteBuf.readUUID()); + } else if (type == 2) { + return new MoveAnnotation(friendlyByteBuf.readUUID(), new Vector3f( + friendlyByteBuf.readFloat(), + friendlyByteBuf.readFloat(), + friendlyByteBuf.readFloat() + )); + } else if (type == 3) { + return new ClearAllAnnotations(); + } else if (type == 4) { + return new RotateAnnotation(friendlyByteBuf.readUUID(), new Quaternionf( + friendlyByteBuf.readFloat(), + friendlyByteBuf.readFloat(), + friendlyByteBuf.readFloat(), + friendlyByteBuf.readFloat() + )); + } else { + return null; + } + } + + record CreateAnnotation(UUID uuid, AnnotationData annotationData) implements AnnotationUpdateAction { + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByte(0); + friendlyByteBuf.writeUUID(this.uuid); + this.annotationData.write(friendlyByteBuf); + } + } + + record DeleteAnnotation(UUID uuid) implements AnnotationUpdateAction { + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByte(1); + friendlyByteBuf.writeUUID(this.uuid); + } + } + + record MoveAnnotation(UUID uuid, Vector3f to) implements AnnotationUpdateAction { + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByte(2); + friendlyByteBuf.writeUUID(this.uuid); + friendlyByteBuf.writeFloat(this.to.x); + friendlyByteBuf.writeFloat(this.to.y); + friendlyByteBuf.writeFloat(this.to.z); + } + } + + record ClearAllAnnotations() implements AnnotationUpdateAction { + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByte(3); + } + } + + + record RotateAnnotation(UUID uuid, Quaternionf to) implements AnnotationUpdateAction { + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByte(4); + friendlyByteBuf.writeUUID(this.uuid); + friendlyByteBuf.writeFloat(this.to.x); + friendlyByteBuf.writeFloat(this.to.y); + friendlyByteBuf.writeFloat(this.to.z); + friendlyByteBuf.writeFloat(this.to.w); + } + } + +} diff --git a/src/main/java/com/moulberry/axiom/annotations/ServerAnnotations.java b/src/main/java/com/moulberry/axiom/annotations/ServerAnnotations.java new file mode 100644 index 0000000..a7a118b --- /dev/null +++ b/src/main/java/com/moulberry/axiom/annotations/ServerAnnotations.java @@ -0,0 +1,136 @@ +package com.moulberry.axiom.annotations; + +import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.VersionHelper; +import com.moulberry.axiom.annotations.data.AnnotationData; +import io.netty.buffer.Unpooled; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.datafix.DataFixTypes; +import net.minecraft.world.level.saveddata.SavedData; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.WeakHashMap; + +public class ServerAnnotations { + + private static final WeakHashMap serverAnnotationCache = new WeakHashMap<>(); + private static final NamespacedKey ANNOTATION_DATA_KEY = new NamespacedKey(AxiomPaper.PLUGIN, "annotation_data"); + + final LinkedHashMap annotations = new LinkedHashMap<>(); + + private static void sendAnnotationUpdates(List actions, List players) { + FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + friendlyByteBuf.writeCollection(actions, (buffer, action) -> action.write(buffer)); + + byte[] bytes = new byte[friendlyByteBuf.writerIndex()]; + friendlyByteBuf.getBytes(0, bytes); + for (ServerPlayer serverPlayer : players) { + VersionHelper.sendCustomPayload(serverPlayer, VersionHelper.createResourceLocation("axiom:annotation_update"), bytes); + } + } + + public static void sendAll(World world, ServerPlayer player) { + if (!AxiomPaper.PLUGIN.allowAnnotations) { + return; + } + + List actions = new ArrayList<>(); + + actions.add(new AnnotationUpdateAction.ClearAllAnnotations()); + + ServerAnnotations serverAnnotations = serverAnnotationCache.get(world); + if (serverAnnotations == null) { + serverAnnotations = world.getPersistentDataContainer().get(ANNOTATION_DATA_KEY, ServerAnnotationsAdapater.INSTANCE); + serverAnnotationCache.put(world, serverAnnotations); + } + + if (serverAnnotations != null) { + for (Map.Entry entry : serverAnnotations.annotations.entrySet()) { + actions.add(new AnnotationUpdateAction.CreateAnnotation(entry.getKey(), entry.getValue())); + } + } + + sendAnnotationUpdates(actions, List.of(player)); + } + + public static void handleUpdates(World world, List actions) { + if (!AxiomPaper.PLUGIN.allowAnnotations) { + return; + } + + ServerAnnotations serverAnnotations = serverAnnotationCache.get(world); + if (serverAnnotations == null) { + serverAnnotations = world.getPersistentDataContainer().get(ANNOTATION_DATA_KEY, ServerAnnotationsAdapater.INSTANCE); + serverAnnotationCache.put(world, serverAnnotations); + } + if (serverAnnotations == null) { + serverAnnotations = new ServerAnnotations(); + serverAnnotationCache.put(world, serverAnnotations); + } + + boolean dirty = false; + + for (AnnotationUpdateAction action : actions) { + if (action instanceof AnnotationUpdateAction.CreateAnnotation create) { + serverAnnotations.annotations.put(create.uuid(), create.annotationData()); + dirty = true; + } else if (action instanceof AnnotationUpdateAction.DeleteAnnotation delete) { + AnnotationData removed = serverAnnotations.annotations.remove(delete.uuid()); + if (removed != null) { + dirty = true; + } + } else if (action instanceof AnnotationUpdateAction.MoveAnnotation move) { + AnnotationData annotation = serverAnnotations.annotations.get(move.uuid()); + if (annotation != null) { + annotation.setPosition(move.to()); + dirty = true; + } + } else if (action instanceof AnnotationUpdateAction.ClearAllAnnotations) { + if (!serverAnnotations.annotations.isEmpty()) { + serverAnnotations.annotations.clear(); + dirty = true; + } + } else if (action instanceof AnnotationUpdateAction.RotateAnnotation rotate) { + AnnotationData annotation = serverAnnotations.annotations.get(rotate.uuid()); + if (annotation != null) { + annotation.setRotation(rotate.to()); + dirty = true; + } + } else { + throw new UnsupportedOperationException("Unknown action: " + action.getClass()); + } + } + + if (dirty) { + world.getPersistentDataContainer().set(ANNOTATION_DATA_KEY, ServerAnnotationsAdapater.INSTANCE, serverAnnotations); + } + + // Forward actions back to clients + List playersWithAxiom = new ArrayList<>(); + + for (ServerPlayer player : ((CraftWorld)world).getHandle().players()) { + if (AxiomPaper.PLUGIN.canUseAxiom(player.getBukkitEntity(), "axiom.annotations.view")) { + playersWithAxiom.add(player); + } + } + + if (!playersWithAxiom.isEmpty()) { + sendAnnotationUpdates(actions, playersWithAxiom); + } + } + +} diff --git a/src/main/java/com/moulberry/axiom/annotations/ServerAnnotationsAdapater.java b/src/main/java/com/moulberry/axiom/annotations/ServerAnnotationsAdapater.java new file mode 100644 index 0000000..c1d0ef0 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/annotations/ServerAnnotationsAdapater.java @@ -0,0 +1,73 @@ +package com.moulberry.axiom.annotations; + +import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.annotations.data.AnnotationData; +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import org.bukkit.NamespacedKey; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.UUID; + +public class ServerAnnotationsAdapater implements PersistentDataType { + public static final ServerAnnotationsAdapater INSTANCE = new ServerAnnotationsAdapater(); + + @Override + public @NotNull Class getPrimitiveType() { + return PersistentDataContainer.class; + } + + @Override + public @NotNull Class getComplexType() { + return ServerAnnotations.class; + } + + @Override + public @NotNull PersistentDataContainer toPrimitive(@NotNull ServerAnnotations serverAnnotations, @NotNull PersistentDataAdapterContext context) { + PersistentDataContainer container = context.newPersistentDataContainer(); + + FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + + for (Map.Entry entry : serverAnnotations.annotations.entrySet()) { + try { + friendlyByteBuf.writerIndex(0); + entry.getValue().write(friendlyByteBuf); + + byte[] bytes = new byte[friendlyByteBuf.writerIndex()]; + friendlyByteBuf.getBytes(0, bytes); + + container.set(new NamespacedKey(AxiomPaper.PLUGIN, entry.getKey().toString()), + PersistentDataType.BYTE_ARRAY, bytes); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return container; + } + + @Override + public @NotNull ServerAnnotations fromPrimitive(@NotNull PersistentDataContainer container, @NotNull PersistentDataAdapterContext context) { + ServerAnnotations serverAnnotations = new ServerAnnotations(); + + for (NamespacedKey key : container.getKeys()) { + try { + String uuidString = key.value(); + UUID uuid = UUID.fromString(uuidString); + + byte[] bytes = container.get(key, PersistentDataType.BYTE_ARRAY); + AnnotationData annotation = AnnotationData.read(new FriendlyByteBuf(Unpooled.wrappedBuffer(bytes))); + + serverAnnotations.annotations.put(uuid, annotation); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return serverAnnotations; + } +} diff --git a/src/main/java/com/moulberry/axiom/annotations/data/AnnotationData.java b/src/main/java/com/moulberry/axiom/annotations/data/AnnotationData.java new file mode 100644 index 0000000..6cae27a --- /dev/null +++ b/src/main/java/com/moulberry/axiom/annotations/data/AnnotationData.java @@ -0,0 +1,32 @@ +package com.moulberry.axiom.annotations.data; + +import net.minecraft.network.FriendlyByteBuf; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public interface AnnotationData { + + void setPosition(Vector3f position); + void setRotation(Quaternionf rotation); + void write(FriendlyByteBuf friendlyByteBuf); + + static AnnotationData read(FriendlyByteBuf friendlyByteBuf) { + byte type = friendlyByteBuf.readByte(); + if (type == 0) { + return LineAnnotationData.read(friendlyByteBuf); + } else if (type == 1) { + return TextAnnotationData.read(friendlyByteBuf); + } else if (type == 2) { + return ImageAnnotationData.read(friendlyByteBuf); + } else if (type == 3) { + return FreehandOutlineAnnotationData.read(friendlyByteBuf); + } else if (type == 4) { + return LinesOutlineAnnotationData.read(friendlyByteBuf); + } else if (type == 5) { + return BoxOutlineAnnotationData.read(friendlyByteBuf); + } else { + return null; + } + } + +} diff --git a/src/main/java/com/moulberry/axiom/annotations/data/BoxOutlineAnnotationData.java b/src/main/java/com/moulberry/axiom/annotations/data/BoxOutlineAnnotationData.java new file mode 100644 index 0000000..2a1af9c --- /dev/null +++ b/src/main/java/com/moulberry/axiom/annotations/data/BoxOutlineAnnotationData.java @@ -0,0 +1,44 @@ +package com.moulberry.axiom.annotations.data; + +import net.minecraft.network.FriendlyByteBuf; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public record BoxOutlineAnnotationData(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int colour) implements AnnotationData { + + public BoxOutlineAnnotationData { + colour = 0xFF000000 | colour; + } + + @Override + public void setPosition(Vector3f position) { + } + + @Override + public void setRotation(Quaternionf rotation) { + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByte(5); + friendlyByteBuf.writeVarInt(this.fromX); + friendlyByteBuf.writeVarInt(this.fromY); + friendlyByteBuf.writeVarInt(this.fromZ); + friendlyByteBuf.writeVarInt(this.toX); + friendlyByteBuf.writeVarInt(this.toY); + friendlyByteBuf.writeVarInt(this.toZ); + friendlyByteBuf.writeInt(this.colour); + } + + public static BoxOutlineAnnotationData read(FriendlyByteBuf friendlyByteBuf) { + int fromX = friendlyByteBuf.readVarInt(); + int fromY = friendlyByteBuf.readVarInt(); + int fromZ = friendlyByteBuf.readVarInt(); + int toX = friendlyByteBuf.readVarInt(); + int toY = friendlyByteBuf.readVarInt(); + int toZ = friendlyByteBuf.readVarInt(); + int colour = friendlyByteBuf.readInt(); + return new BoxOutlineAnnotationData(fromX, fromY, fromZ, toX, toY, toZ, colour); + } + +} diff --git a/src/main/java/com/moulberry/axiom/annotations/data/FreehandOutlineAnnotationData.java b/src/main/java/com/moulberry/axiom/annotations/data/FreehandOutlineAnnotationData.java new file mode 100644 index 0000000..f5dbcc7 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/annotations/data/FreehandOutlineAnnotationData.java @@ -0,0 +1,43 @@ +package com.moulberry.axiom.annotations.data; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public record FreehandOutlineAnnotationData(BlockPos start, byte[] offsets, int offsetCount, int colour) implements AnnotationData { + + public FreehandOutlineAnnotationData { + colour = 0xFF000000 | colour; + } + + @Override + public void setPosition(Vector3f position) { + } + + @Override + public void setRotation(Quaternionf rotation) { + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByte(3); + friendlyByteBuf.writeVarInt(this.start.getX()); + friendlyByteBuf.writeVarInt(this.start.getY()); + friendlyByteBuf.writeVarInt(this.start.getZ()); + friendlyByteBuf.writeVarInt(this.offsetCount); + friendlyByteBuf.writeInt(this.colour); + friendlyByteBuf.writeByteArray(this.offsets); + } + + public static FreehandOutlineAnnotationData read(FriendlyByteBuf friendlyByteBuf) { + int x = friendlyByteBuf.readVarInt(); + int y = friendlyByteBuf.readVarInt(); + int z = friendlyByteBuf.readVarInt(); + int offsetCount = friendlyByteBuf.readVarInt(); + int colour = friendlyByteBuf.readInt(); + byte[] offsets = friendlyByteBuf.readByteArray(); + return new FreehandOutlineAnnotationData(new BlockPos(x, y, z), offsets, offsetCount, colour); + } + +} diff --git a/src/main/java/com/moulberry/axiom/annotations/data/ImageAnnotationData.java b/src/main/java/com/moulberry/axiom/annotations/data/ImageAnnotationData.java new file mode 100644 index 0000000..8461a0e --- /dev/null +++ b/src/main/java/com/moulberry/axiom/annotations/data/ImageAnnotationData.java @@ -0,0 +1,53 @@ +package com.moulberry.axiom.annotations.data; + +import net.minecraft.core.Direction; +import net.minecraft.network.FriendlyByteBuf; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public record ImageAnnotationData(String imageUrl, Vector3f position, Quaternionf rotation, Direction direction, float fallbackYaw, float width, int billboardMode) implements AnnotationData { + + @Override + public void setPosition(Vector3f position) { + this.position.set(position); + } + + @Override + public void setRotation(Quaternionf rotation) { + this.rotation.set(rotation); + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByte(2); + friendlyByteBuf.writeUtf(this.imageUrl); + friendlyByteBuf.writeFloat(this.position.x); + friendlyByteBuf.writeFloat(this.position.y); + friendlyByteBuf.writeFloat(this.position.z); + friendlyByteBuf.writeFloat(this.rotation.x); + friendlyByteBuf.writeFloat(this.rotation.y); + friendlyByteBuf.writeFloat(this.rotation.z); + friendlyByteBuf.writeFloat(this.rotation.w); + friendlyByteBuf.writeByte(this.direction.get3DDataValue()); + friendlyByteBuf.writeFloat(this.fallbackYaw); + friendlyByteBuf.writeFloat(this.width); + friendlyByteBuf.writeByte(this.billboardMode); + } + + public static ImageAnnotationData read(FriendlyByteBuf friendlyByteBuf) { + String imageUrl = friendlyByteBuf.readUtf(); + float x = friendlyByteBuf.readFloat(); + float y = friendlyByteBuf.readFloat(); + float z = friendlyByteBuf.readFloat(); + float rotX = friendlyByteBuf.readFloat(); + float rotY = friendlyByteBuf.readFloat(); + float rotZ = friendlyByteBuf.readFloat(); + float rotW = friendlyByteBuf.readFloat(); + Direction direction = Direction.from3DDataValue(friendlyByteBuf.readByte()); + float fallbackYaw = friendlyByteBuf.readFloat(); + float width = friendlyByteBuf.readFloat(); + int billboardMode = friendlyByteBuf.readByte(); + return new ImageAnnotationData(imageUrl, new Vector3f(x, y, z), new Quaternionf(rotX, rotY, rotZ, rotW), direction, fallbackYaw, width, billboardMode); + } + +} diff --git a/src/main/java/com/moulberry/axiom/annotations/data/LineAnnotationData.java b/src/main/java/com/moulberry/axiom/annotations/data/LineAnnotationData.java new file mode 100644 index 0000000..f0aa5ce --- /dev/null +++ b/src/main/java/com/moulberry/axiom/annotations/data/LineAnnotationData.java @@ -0,0 +1,43 @@ +package com.moulberry.axiom.annotations.data; + +import net.minecraft.core.Vec3i; +import net.minecraft.network.FriendlyByteBuf; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public record LineAnnotationData(Vec3i startQuantized, byte[] offsets, float lineWidth, int colour) implements AnnotationData { + + public LineAnnotationData { + colour = 0xFF000000 | colour; + } + + @Override + public void setPosition(Vector3f position) { + } + + @Override + public void setRotation(Quaternionf rotation) { + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByte(0); + friendlyByteBuf.writeVarInt(this.startQuantized.getX()); + friendlyByteBuf.writeVarInt(this.startQuantized.getY()); + friendlyByteBuf.writeVarInt(this.startQuantized.getZ()); + friendlyByteBuf.writeFloat(this.lineWidth); + friendlyByteBuf.writeInt(this.colour); + friendlyByteBuf.writeByteArray(this.offsets); + } + + public static LineAnnotationData read(FriendlyByteBuf friendlyByteBuf) { + int x = friendlyByteBuf.readVarInt(); + int y = friendlyByteBuf.readVarInt(); + int z = friendlyByteBuf.readVarInt(); + float lineWidth = friendlyByteBuf.readFloat(); + int colour = friendlyByteBuf.readInt(); + byte[] offsets = friendlyByteBuf.readByteArray(); + return new LineAnnotationData(new Vec3i(x, y, z), offsets, lineWidth, colour); + } + +} diff --git a/src/main/java/com/moulberry/axiom/annotations/data/LinesOutlineAnnotationData.java b/src/main/java/com/moulberry/axiom/annotations/data/LinesOutlineAnnotationData.java new file mode 100644 index 0000000..7c4a6c3 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/annotations/data/LinesOutlineAnnotationData.java @@ -0,0 +1,41 @@ +package com.moulberry.axiom.annotations.data; + +import net.minecraft.network.FriendlyByteBuf; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public record LinesOutlineAnnotationData(long[] positions, int colour) implements AnnotationData { + + public LinesOutlineAnnotationData { + colour = 0xFF000000 | colour; + } + + @Override + public void setPosition(Vector3f position) { + } + + @Override + public void setRotation(Quaternionf rotation) { + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByte(4); + friendlyByteBuf.writeVarInt(this.positions.length); + for (long position : this.positions) { + friendlyByteBuf.writeLong(position); + } + friendlyByteBuf.writeInt(this.colour); + } + + public static LinesOutlineAnnotationData read(FriendlyByteBuf friendlyByteBuf) { + int positionCount = friendlyByteBuf.readVarInt(); + long[] positions = new long[positionCount]; + for (int i = 0; i < positionCount; i++) { + positions[i] = friendlyByteBuf.readLong(); + } + int colour = friendlyByteBuf.readInt(); + return new LinesOutlineAnnotationData(positions, colour); + } + +} diff --git a/src/main/java/com/moulberry/axiom/annotations/data/TextAnnotationData.java b/src/main/java/com/moulberry/axiom/annotations/data/TextAnnotationData.java new file mode 100644 index 0000000..c995872 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/annotations/data/TextAnnotationData.java @@ -0,0 +1,58 @@ +package com.moulberry.axiom.annotations.data; + +import net.minecraft.core.Direction; +import net.minecraft.network.FriendlyByteBuf; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public record TextAnnotationData(String text, Vector3f position, Quaternionf rotation, Direction direction, float fallbackYaw, float scale, + int billboardMode, int colour, boolean shadow) implements AnnotationData { + + @Override + public void setPosition(Vector3f position) { + this.position.set(position); + } + + @Override + public void setRotation(Quaternionf rotation) { + this.rotation.set(rotation); + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByte(1); + friendlyByteBuf.writeUtf(this.text); + friendlyByteBuf.writeFloat(this.position.x); + friendlyByteBuf.writeFloat(this.position.y); + friendlyByteBuf.writeFloat(this.position.z); + friendlyByteBuf.writeFloat(this.rotation.x); + friendlyByteBuf.writeFloat(this.rotation.y); + friendlyByteBuf.writeFloat(this.rotation.z); + friendlyByteBuf.writeFloat(this.rotation.w); + friendlyByteBuf.writeByte(this.direction.get3DDataValue()); + friendlyByteBuf.writeFloat(this.fallbackYaw); + friendlyByteBuf.writeFloat(this.scale); + friendlyByteBuf.writeByte(this.billboardMode); + friendlyByteBuf.writeInt(this.colour); + friendlyByteBuf.writeBoolean(this.shadow); + } + + public static TextAnnotationData read(FriendlyByteBuf friendlyByteBuf) { + String text = friendlyByteBuf.readUtf(); + float x = friendlyByteBuf.readFloat(); + float y = friendlyByteBuf.readFloat(); + float z = friendlyByteBuf.readFloat(); + float rotX = friendlyByteBuf.readFloat(); + float rotY = friendlyByteBuf.readFloat(); + float rotZ = friendlyByteBuf.readFloat(); + float rotW = friendlyByteBuf.readFloat(); + Direction direction = Direction.from3DDataValue(friendlyByteBuf.readByte()); + float fallbackYaw = friendlyByteBuf.readFloat(); + float scale = friendlyByteBuf.readFloat(); + int billboardMode = friendlyByteBuf.readByte(); + int colour = friendlyByteBuf.readInt(); + boolean shadow = friendlyByteBuf.readBoolean(); + return new TextAnnotationData(text, new Vector3f(x, y, z), new Quaternionf(rotX, rotY, rotZ, rotW), direction, fallbackYaw, scale, billboardMode, colour, shadow); + } + +} diff --git a/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java b/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java index 855cea7..e1c4557 100644 --- a/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java +++ b/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java @@ -20,19 +20,23 @@ public class AxiomBigPayloadHandler extends MessageToMessageDecoder { private static final ResourceLocation SET_BUFFER = VersionHelper.createResourceLocation("axiom", "set_buffer"); private static final ResourceLocation UPLOAD_BLUEPRINT = VersionHelper.createResourceLocation("axiom", "upload_blueprint"); + private static final ResourceLocation UPDATE_ANNOTATIONS = VersionHelper.createResourceLocation("axiom", "annotation_update"); private static final ResourceLocation REQUEST_CHUNK_DATA = VersionHelper.createResourceLocation("axiom", "request_chunk_data"); private final int payloadId; private final Connection connection; private final SetBlockBufferPacketListener setBlockBuffer; private final UploadBlueprintPacketListener uploadBlueprint; + private final UpdateAnnotationPacketListener updateAnnotation; private final RequestChunkDataPacketListener requestChunkDataPacketListener; public AxiomBigPayloadHandler(int payloadId, Connection connection, SetBlockBufferPacketListener setBlockBuffer, - UploadBlueprintPacketListener uploadBlueprint, RequestChunkDataPacketListener requestChunkDataPacketListener) { + UploadBlueprintPacketListener uploadBlueprint, UpdateAnnotationPacketListener updateAnnotation, + RequestChunkDataPacketListener requestChunkDataPacketListener) { this.payloadId = payloadId; this.connection = connection; this.setBlockBuffer = setBlockBuffer; this.uploadBlueprint = uploadBlueprint; + this.updateAnnotation = updateAnnotation; this.requestChunkDataPacketListener = requestChunkDataPacketListener; } @@ -80,6 +84,14 @@ public class AxiomBigPayloadHandler extends MessageToMessageDecoder { " bytes extra whilst reading packet"); } return; + } else if (identifier.equals(UPDATE_ANNOTATIONS)) { + updateAnnotation.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); @@ -122,7 +134,7 @@ public class AxiomBigPayloadHandler extends MessageToMessageDecoder { if (evt == ConnectionEvent.COMPRESSION_THRESHOLD_SET || evt == ConnectionEvent.COMPRESSION_DISABLED) { ctx.channel().pipeline().remove("axiom-big-payload-handler"); ctx.channel().pipeline().addBefore("decoder", "axiom-big-payload-handler", - new AxiomBigPayloadHandler(payloadId, connection, setBlockBuffer, uploadBlueprint, requestChunkDataPacketListener)); + new AxiomBigPayloadHandler(payloadId, connection, setBlockBuffer, uploadBlueprint, updateAnnotation, requestChunkDataPacketListener)); } super.userEventTriggered(ctx, evt); } diff --git a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java index 3e492da..425a653 100644 --- a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java @@ -2,6 +2,7 @@ package com.moulberry.axiom.packet; import com.google.common.util.concurrent.RateLimiter; import com.moulberry.axiom.*; +import com.moulberry.axiom.annotations.ServerAnnotations; import com.moulberry.axiom.blueprint.ServerBlueprintManager; import com.moulberry.axiom.event.AxiomHandshakeEvent; import com.moulberry.axiom.persistence.ItemStackDataType; @@ -20,6 +21,7 @@ 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.CraftWorld; import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; import org.bukkit.entity.Player; @@ -49,13 +51,6 @@ public class HelloPacketListener implements PluginMessageListener { } } - private static int normalizeDataVersion(int dataVersion) { - if (dataVersion == 3955) { // 1.21.1 - return 3953; // 1.21 - } - return dataVersion; - } - private void process(Player player, byte[] message) { if (!this.plugin.hasAxiomPermission(player)) { return; @@ -63,11 +58,32 @@ public class HelloPacketListener implements PluginMessageListener { FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); int apiVersion = friendlyByteBuf.readVarInt(); + + if (apiVersion != AxiomConstants.API_VERSION) { + String versions = " (C="+apiVersion+" S="+AxiomConstants.API_VERSION+")"; + Component text; + if (apiVersion < AxiomConstants.API_VERSION) { + text = Component.text("Unable to use Axiom, you're on an outdated version! Please update to the latest version of Axiom to use it on this server." + versions); + } else { + text = Component.text("Unable to use Axiom, server hasn't updated Axiom yet." + versions); + } + + String unsupportedAxiomVersion = plugin.configuration.getString("unsupported-axiom-version"); + if (unsupportedAxiomVersion == null) unsupportedAxiomVersion = "kick"; + if (unsupportedAxiomVersion.equals("warn")) { + player.sendMessage(text.color(NamedTextColor.RED)); + return; + } else if (!unsupportedAxiomVersion.equals("ignore")) { + player.kick(text); + return; + } + } + int dataVersion = friendlyByteBuf.readVarInt(); - // note - skipping NBT here. friendlyByteBuf.readNBT(); + int protocolVersion = friendlyByteBuf.readVarInt(); int serverDataVersion = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - if (normalizeDataVersion(dataVersion) != normalizeDataVersion(serverDataVersion)) { + if (protocolVersion != SharedConstants.getProtocolVersion()) { String incompatibleDataVersion = plugin.configuration.getString("incompatible-data-version"); if (incompatibleDataVersion == null) incompatibleDataVersion = "warn"; @@ -83,35 +99,11 @@ public class HelloPacketListener implements PluginMessageListener { 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 <= 3839) { - playerVersion = 766; // 1.20.5 / 1.20.6 - } else if (dataVersion <= 3955) { - playerVersion = 767; // 1.21.1 - } else { - player.sendMessage(incompatibleWarning.color(NamedTextColor.RED)); - return; - } - } - IdMapper mapper; try { - mapper = ViaVersionHelper.getBlockRegistryForVersion(this.plugin.allowedBlockRegistry, playerVersion); + mapper = ViaVersionHelper.getBlockRegistryForVersion(this.plugin.allowedBlockRegistry, protocolVersion); } catch (Exception e) { - String clientDescription = "client: " + ProtocolVersion.getProtocol(playerVersion); + String clientDescription = "client: " + ProtocolVersion.getProtocol(protocolVersion); String serverDescription = "server: " + ProtocolVersion.getProtocol(SharedConstants.getProtocolVersion()); String description = clientDescription + " <-> " + serverDescription; Component text = Component.text("Axiom+ViaVersion: " + e.getMessage() + " (" + description + ")"); @@ -125,7 +117,7 @@ public class HelloPacketListener implements PluginMessageListener { } this.plugin.playerBlockRegistry.put(player.getUniqueId(), mapper); - this.plugin.playerProtocolVersion.put(player.getUniqueId(), playerVersion); + this.plugin.playerProtocolVersion.put(player.getUniqueId(), protocolVersion); 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"); @@ -133,35 +125,6 @@ public class HelloPacketListener implements PluginMessageListener { } } - if (apiVersion != AxiomConstants.API_VERSION) { - Component text = Component.text("Unsupported Axiom API Version. Server supports " + AxiomConstants.API_VERSION + - ", while client is " + apiVersion); - - String unsupportedAxiomVersion = plugin.configuration.getString("unsupported-axiom-version"); - if (unsupportedAxiomVersion == null) unsupportedAxiomVersion = "kick"; - if (unsupportedAxiomVersion.equals("warn")) { - player.sendMessage(text.color(NamedTextColor.RED)); - return; - } else if (!unsupportedAxiomVersion.equals("ignore")) { - player.kick(text); - return; - } - } - - if (!player.getListeningPluginChannels().contains("axiom:restrictions")) { - 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 = "warn"; - if (unsupportedRestrictions.equals("warn")) { - player.sendMessage(text.color(NamedTextColor.RED)); - return; - } else if (!unsupportedRestrictions.equals("ignore")) { - player.kick(text); - return; - } - } - // Call handshake event int maxBufferSize = plugin.configuration.getInt("max-block-buffer-packet-size"); AxiomHandshakeEvent handshakeEvent = new AxiomHandshakeEvent(player, maxBufferSize); diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java index d7a8ea5..5fdb20d 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java @@ -33,10 +33,16 @@ 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.CapturedBlockState; import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlock; +import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R3.event.CraftEventFactory; +import org.bukkit.craftbukkit.v1_20_R3.util.CraftMagicNumbers; import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -44,6 +50,8 @@ import xyz.jpenilla.reflectionremapper.ReflectionRemapper; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -107,6 +115,8 @@ public class SetBlockPacketListener implements PluginMessageListener { player.connection.ackBlockChangesUpTo(sequenceId); } + BlockPlaceContext blockPlaceContext = new BlockPlaceContext(player, hand, player.getItemInHand(hand), blockHit); + if (!blockHit.getLocation().equals(Vec3.ZERO)) { org.bukkit.inventory.ItemStack heldItem; if (hand == InteractionHand.MAIN_HAND) { @@ -126,12 +136,30 @@ public class SetBlockPacketListener implements PluginMessageListener { if (!playerInteractEvent.callEvent()) { return; } + + // Call BlockMultiPlace / BlockPlace event + List blockStates = new ArrayList<>(); + for (Map.Entry entry : blocks.entrySet()) { + blockStates.add(CraftBlockStates.getBlockState(entry.getKey(), entry.getValue(), null)); + } + + Cancellable event = null; + if (blockStates.size() > 1) { + event = CraftEventFactory.callBlockMultiPlaceEvent(player.serverLevel(), + player, hand, blockStates, blockHit.getBlockPos().getX(), + blockHit.getBlockPos().getY(), blockHit.getBlockPos().getZ()); + } else if (blockStates.size() == 1) { + event = CraftEventFactory.callBlockPlaceEvent(player.serverLevel(), + player, hand, blockStates.get(0), blockHit.getBlockPos().getX(), + blockHit.getBlockPos().getY(), blockHit.getBlockPos().getZ()); + } + if (event != null && event.isCancelled()) { + return; + } } CraftWorld world = player.level().getWorld(); - BlockPlaceContext blockPlaceContext = new BlockPlaceContext(player, hand, player.getItemInHand(hand), blockHit); - // Update blocks if (updateNeighbors) { int count = 0; diff --git a/src/main/java/com/moulberry/axiom/packet/UpdateAnnotationPacketListener.java b/src/main/java/com/moulberry/axiom/packet/UpdateAnnotationPacketListener.java new file mode 100644 index 0000000..774a3e5 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/packet/UpdateAnnotationPacketListener.java @@ -0,0 +1,57 @@ +package com.moulberry.axiom.packet; + +import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.annotations.AnnotationUpdateAction; +import com.moulberry.axiom.annotations.ServerAnnotations; +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; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +public class UpdateAnnotationPacketListener { + + private final AxiomPaper plugin; + public UpdateAnnotationPacketListener(AxiomPaper plugin) { + this.plugin = plugin; + } + + public void onReceive(ServerPlayer serverPlayer, FriendlyByteBuf friendlyByteBuf) { + if (!this.plugin.allowAnnotations || !this.plugin.canUseAxiom(serverPlayer.getBukkitEntity(), "axiom.annotation.create")) { + friendlyByteBuf.writerIndex(friendlyByteBuf.readerIndex()); + return; + } + + // Read actions + int length = friendlyByteBuf.readVarInt(); + List actions = new ArrayList<>(Math.min(256, length)); + for (int i = 0; i < length; i++) { + AnnotationUpdateAction action = AnnotationUpdateAction.read(friendlyByteBuf); + if (action != null) { + actions.add(action); + } + } + + // Execute + serverPlayer.getServer().execute(() -> { + try { + ServerAnnotations.handleUpdates(serverPlayer.serverLevel().getWorld(), actions); + } catch (Throwable t) { + serverPlayer.getBukkitEntity().kick(net.kyori.adventure.text.Component.text( + "An error occured while updating annotations: " + t.getMessage())); + } + }); + } + +} diff --git a/src/main/java/com/moulberry/axiom/packet/UploadBlueprintPacketListener.java b/src/main/java/com/moulberry/axiom/packet/UploadBlueprintPacketListener.java index 0a6d2eb..d950c5a 100644 --- a/src/main/java/com/moulberry/axiom/packet/UploadBlueprintPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/UploadBlueprintPacketListener.java @@ -25,6 +25,7 @@ public class UploadBlueprintPacketListener { public void onReceive(ServerPlayer serverPlayer, FriendlyByteBuf friendlyByteBuf) { if (!this.plugin.canUseAxiom(serverPlayer.getBukkitEntity(), "axiom.blueprint.upload")) { + friendlyByteBuf.writerIndex(friendlyByteBuf.readerIndex()); return; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 7273f2c..f8f2b7f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -18,12 +18,10 @@ blueprint-sharing: false allow-large-chunk-data-request: false # Action to take when a user with an incompatible Minecraft version or Axiom version joins -# Valid actions are 'kick', 'warn' and 'ignore' +# Valid actions are 'kick' and 'warn' # 'warn' will give the player a warning and disable Axiom -# Using 'ignore' may result in corruption and is only provided for debugging purposes incompatible-data-version: "warn" unsupported-axiom-version: "warn" -client-doesnt-support-restrictions: "ignore" # Maximum packet size. Must not be less than 32767 max-block-buffer-packet-size: 0x100000 @@ -59,11 +57,28 @@ blacklist-entities: # True allows players to see/manipulate marker entities send-markers: false +# True to enable Annotations on this server +allow-annotations: false + # Disallowed blocks disallowed-blocks: # - "minecraft:wheat" # - "minecraft:oak_stairs[waterlogged=true]" +allow-capabilities: + - "all" + # - "bulldozer" + # - "replace_mode" + # - "force_place" + # - "no_updates" + # - "tinker" + # - "infinite_reach" + # - "fast_place" + # - "angel_placement" + # - "no_clip" + +infinite-reach-limit: 256 + # Toggles for individual packet handlers. May break certain features packet-handlers: hello: true diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 030c8eb..574246a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -32,6 +32,12 @@ permissions: axiom.blueprint.upload: true axiom.blueprint.request: true axiom.blueprint.manifest: true + axiom.annotation.*: + description: Allows creating and viewing annotations + default: op + children: + axiom.annotation.create: true + axiom.annotation.view: true axiom.chunk.*: description: Allows use of all chunk-related features default: op