3
0
Mirror von https://github.com/Moulberry/AxiomPaperPlugin.git synchronisiert 2024-11-13 19:50:09 +01:00

Support annotations, add more restrictions, improve version detection

Dieser Commit ist enthalten in:
Moulberry 2024-08-21 09:26:37 +08:00
Ursprung 54e780a6db
Commit 3481216653
22 geänderte Dateien mit 869 neuen und 117 gelöschten Zeilen

Datei anzeigen

@ -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 {

Datei anzeigen

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

Datei anzeigen

@ -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<String> 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<String> 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;
}

Datei anzeigen

@ -10,20 +10,36 @@ 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<PlotSquaredIntegration.PlotBox> 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);
buf.writeBoolean(this.canCreateAnnotations);
buf.writeInt(this.allowedCapabilities);
buf.writeInt(this.infiniteReachLimit);
buf.writeVarInt(this.maxSectionsPerSecond);
@ -65,7 +81,6 @@ public class Restrictions {
buf.getBytes(0, bytes);
player.sendPluginMessage(plugin, "axiom:restrictions", bytes);
}
}
@Override
public String toString() {

Datei anzeigen

@ -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) {

Datei anzeigen

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

Datei anzeigen

@ -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<World, ServerAnnotations> serverAnnotationCache = new WeakHashMap<>();
private static final NamespacedKey ANNOTATION_DATA_KEY = new NamespacedKey(AxiomPaper.PLUGIN, "annotation_data");
final LinkedHashMap<UUID, AnnotationData> annotations = new LinkedHashMap<>();
private static void sendAnnotationUpdates(List<AnnotationUpdateAction> actions, List<ServerPlayer> 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<AnnotationUpdateAction> 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<UUID, AnnotationData> 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<AnnotationUpdateAction> 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<ServerPlayer> 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);
}
}
}

Datei anzeigen

@ -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<PersistentDataContainer, ServerAnnotations> {
public static final ServerAnnotationsAdapater INSTANCE = new ServerAnnotationsAdapater();
@Override
public @NotNull Class<PersistentDataContainer> getPrimitiveType() {
return PersistentDataContainer.class;
}
@Override
public @NotNull Class<ServerAnnotations> 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<UUID, AnnotationData> 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;
}
}

Datei anzeigen

@ -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;
}
}
}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -20,19 +20,23 @@ public class AxiomBigPayloadHandler extends MessageToMessageDecoder<ByteBuf> {
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<ByteBuf> {
" 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<ByteBuf> {
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);
}

Datei anzeigen

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

Datei anzeigen

@ -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<org.bukkit.block.BlockState> blockStates = new ArrayList<>();
for (Map.Entry<BlockPos, BlockState> 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;

Datei anzeigen

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

Datei anzeigen

@ -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;
}

Datei anzeigen

@ -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

Datei anzeigen

@ -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