Skip to content

Commit dbcfd0b

Browse files
committed
Add marker manipulation & nbt sanitization
1 parent b694a05 commit dbcfd0b

File tree

9 files changed

+345
-16
lines changed

9 files changed

+345
-16
lines changed

src/main/java/com/moulberry/axiom/AxiomPaper.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public void onEnable() {
8484
msg.registerOutgoingPluginChannel(this, "axiom:set_world_property");
8585
msg.registerOutgoingPluginChannel(this, "axiom:ack_world_properties");
8686
msg.registerOutgoingPluginChannel(this, "axiom:restrictions");
87+
msg.registerOutgoingPluginChannel(this, "axiom:marker_data");
88+
msg.registerOutgoingPluginChannel(this, "axiom:marker_nbt_response");
8789

8890
if (configuration.getBoolean("packet-handlers.hello")) {
8991
msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this));
@@ -127,6 +129,9 @@ public void onEnable() {
127129
if (configuration.getBoolean("packet-handlers.delete-entity")) {
128130
msg.registerIncomingPluginChannel(this, "axiom:delete_entity", new DeleteEntityPacketListener(this));
129131
}
132+
if (configuration.getBoolean("packet-handlers.marker-nbt-request")) {
133+
msg.registerIncomingPluginChannel(this, "axiom:marker_nbt_request", new MarkerNbtRequestPacketListener(this));
134+
}
130135

131136
if (configuration.getBoolean("packet-handlers.set-buffer")) {
132137
SetBlockBufferPacketListener setBlockBufferPacketListener = new SetBlockBufferPacketListener(this);
@@ -238,11 +243,12 @@ public void afterInitChannel(@NonNull Channel channel) {
238243
playerRestrictions.keySet().retainAll(stillActiveAxiomPlayers);
239244
}, 20, 20);
240245

246+
boolean sendMarkers = configuration.getBoolean("send-markers");
241247
int maxChunkRelightsPerTick = configuration.getInt("max-chunk-relights-per-tick");
242248
int maxChunkSendsPerTick = configuration.getInt("max-chunk-sends-per-tick");
243249

244250
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
245-
WorldExtension.tick(MinecraftServer.getServer(), maxChunkRelightsPerTick, maxChunkSendsPerTick);
251+
WorldExtension.tick(MinecraftServer.getServer(), sendMarkers, maxChunkRelightsPerTick, maxChunkSendsPerTick);
246252
}, 1, 1);
247253
}
248254

@@ -292,17 +298,22 @@ public boolean canModifyWorld(Player player, World world) {
292298

293299
@EventHandler
294300
public void onFailMove(PlayerFailMoveEvent event) {
295-
if (event.getPlayer().hasPermission("axiom.*")) {
296-
if (event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) {
297-
event.setAllowed(true); // Support for arcball camera
298-
} else if (event.getPlayer().isFlying()) {
299-
event.setAllowed(true); // Support for noclip
300-
}
301+
if (!this.activeAxiomPlayers.contains(event.getPlayer().getUniqueId())) {
302+
return;
303+
}
304+
if (event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) {
305+
event.setAllowed(true); // Support for arcball camera
306+
} else if (event.getPlayer().isFlying()) {
307+
event.setAllowed(true); // Support for noclip
301308
}
302309
}
303310

304311
@EventHandler
305312
public void onChangedWorld(PlayerChangedWorldEvent event) {
313+
if (!this.activeAxiomPlayers.contains(event.getPlayer().getUniqueId())) {
314+
return;
315+
}
316+
306317
World world = event.getPlayer().getWorld();
307318

308319
ServerWorldPropertiesRegistry properties = getOrCreateWorldProperties(world);
@@ -312,6 +323,8 @@ public void onChangedWorld(PlayerChangedWorldEvent event) {
312323
} else {
313324
properties.registerFor(this, event.getPlayer());
314325
}
326+
327+
WorldExtension.onPlayerJoin(world, event.getPlayer());
315328
}
316329

317330
@EventHandler
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.moulberry.axiom;
2+
3+
import net.minecraft.nbt.CompoundTag;
4+
import net.minecraft.nbt.ListTag;
5+
import net.minecraft.nbt.Tag;
6+
7+
import java.util.Set;
8+
9+
public class NbtSanitization {
10+
11+
private static final Set<String> ALLOWED_KEYS = Set.of(
12+
"id", // entity id
13+
// generic
14+
"Pos",
15+
"Rotation",
16+
"Invulnerable",
17+
"CustomName",
18+
"CustomNameVisible",
19+
"Silent",
20+
"NoGravity",
21+
"Glowing",
22+
"Tags",
23+
"Passengers",
24+
// marker
25+
"data",
26+
// display entity
27+
"transformation",
28+
"interpolation_duration",
29+
"start_interpolation",
30+
"teleport_duration",
31+
"billboard",
32+
"view_range",
33+
"shadow_radius",
34+
"shadow_strength",
35+
"width",
36+
"height",
37+
"glow_color_override",
38+
"brightness",
39+
"line_width",
40+
"text_opacity",
41+
"background",
42+
"shadow",
43+
"see_through",
44+
"default_background",
45+
"alignment",
46+
"text",
47+
"block_state",
48+
"item",
49+
"item_display"
50+
);
51+
52+
public static void sanitizeEntity(CompoundTag entityRoot) {
53+
entityRoot.getAllKeys().retainAll(ALLOWED_KEYS);
54+
55+
if (entityRoot.contains("Passengers", Tag.TAG_LIST)) {
56+
ListTag listTag = entityRoot.getList("Passengers", Tag.TAG_COMPOUND);
57+
for (Tag tag : listTag) {
58+
sanitizeEntity((CompoundTag) tag);
59+
}
60+
}
61+
}
62+
63+
}

src/main/java/com/moulberry/axiom/WorldExtension.java

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
package com.moulberry.axiom;
22

3+
import com.moulberry.axiom.marker.MarkerData;
4+
import io.netty.buffer.Unpooled;
35
import it.unimi.dsi.fastutil.longs.*;
6+
import net.minecraft.network.FriendlyByteBuf;
47
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
58
import net.minecraft.resources.ResourceKey;
69
import net.minecraft.server.MinecraftServer;
710
import net.minecraft.server.level.ChunkMap;
811
import net.minecraft.server.level.ServerLevel;
912
import net.minecraft.server.level.ServerPlayer;
13+
import net.minecraft.world.entity.Entity;
14+
import net.minecraft.world.entity.Marker;
1015
import net.minecraft.world.level.ChunkPos;
1116
import net.minecraft.world.level.Level;
1217
import net.minecraft.world.level.chunk.LevelChunk;
18+
import org.bukkit.World;
19+
import org.bukkit.craftbukkit.v1_20_R3.CraftWorld;
20+
import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer;
21+
import org.bukkit.entity.Player;
1322

1423
import java.util.*;
1524

@@ -23,22 +32,24 @@ public static WorldExtension get(ServerLevel serverLevel) {
2332
return extension;
2433
}
2534

26-
public static void tick(MinecraftServer server, int maxChunkRelightsPerTick, int maxChunkSendsPerTick) {
35+
public static void onPlayerJoin(World world, Player player) {
36+
ServerLevel level = ((CraftWorld)world).getHandle();
37+
get(level).onPlayerJoin(player);
38+
}
39+
40+
public static void tick(MinecraftServer server, boolean sendMarkers, int maxChunkRelightsPerTick, int maxChunkSendsPerTick) {
2741
extensions.keySet().retainAll(server.levelKeys());
2842

2943
for (ServerLevel level : server.getAllLevels()) {
30-
WorldExtension extension = extensions.get(level.dimension());
31-
if (extension != null) {
32-
extension.level = level;
33-
extension.tick(maxChunkRelightsPerTick, maxChunkSendsPerTick);
34-
}
44+
get(level).tick(sendMarkers, maxChunkRelightsPerTick, maxChunkSendsPerTick);
3545
}
3646
}
3747

3848
private ServerLevel level;
3949

4050
private final LongSet pendingChunksToSend = new LongOpenHashSet();
4151
private final LongSet pendingChunksToLight = new LongOpenHashSet();
52+
private final Map<UUID, MarkerData> previousMarkerData = new HashMap<>();
4253

4354
public void sendChunk(int cx, int cz) {
4455
this.pendingChunksToSend.add(ChunkPos.asLong(cx, cz));
@@ -48,7 +59,66 @@ public void lightChunk(int cx, int cz) {
4859
this.pendingChunksToLight.add(ChunkPos.asLong(cx, cz));
4960
}
5061

51-
public void tick(int maxChunkRelightsPerTick, int maxChunkSendsPerTick) {
62+
public void onPlayerJoin(Player player) {
63+
if (!this.previousMarkerData.isEmpty()) {
64+
List<MarkerData> markerData = new ArrayList<>(this.previousMarkerData.values());
65+
66+
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
67+
buf.writeCollection(markerData, MarkerData::write);
68+
buf.writeCollection(Set.of(), FriendlyByteBuf::writeUUID);
69+
byte[] bytes = new byte[buf.writerIndex()];
70+
buf.getBytes(0, bytes);
71+
72+
player.sendPluginMessage(AxiomPaper.PLUGIN, "axiom:marker_data", bytes);
73+
}
74+
}
75+
76+
public void tick(boolean sendMarkers, int maxChunkRelightsPerTick, int maxChunkSendsPerTick) {
77+
if (sendMarkers) {
78+
this.tickMarkers();
79+
}
80+
this.tickChunkRelight(maxChunkRelightsPerTick, maxChunkSendsPerTick);
81+
}
82+
83+
private void tickMarkers() {
84+
List<MarkerData> changedData = new ArrayList<>();
85+
86+
Set<UUID> allMarkers = new HashSet<>();
87+
88+
for (Entity entity : this.level.getEntities().getAll()) {
89+
if (entity instanceof Marker marker) {
90+
MarkerData currentData = MarkerData.createFrom(marker);
91+
92+
MarkerData previousData = this.previousMarkerData.get(marker.getUUID());
93+
if (!Objects.equals(currentData, previousData)) {
94+
this.previousMarkerData.put(marker.getUUID(), currentData);
95+
changedData.add(currentData);
96+
}
97+
98+
allMarkers.add(marker.getUUID());
99+
}
100+
}
101+
102+
Set<UUID> oldUuids = new HashSet<>(this.previousMarkerData.keySet());
103+
oldUuids.removeAll(allMarkers);
104+
this.previousMarkerData.keySet().removeAll(oldUuids);
105+
106+
if (!changedData.isEmpty() || !oldUuids.isEmpty()) {
107+
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
108+
buf.writeCollection(changedData, MarkerData::write);
109+
buf.writeCollection(oldUuids, FriendlyByteBuf::writeUUID);
110+
byte[] bytes = new byte[buf.writerIndex()];
111+
buf.getBytes(0, bytes);
112+
113+
for (ServerPlayer player : this.level.players()) {
114+
if (AxiomPaper.PLUGIN.activeAxiomPlayers.contains(player.getUUID())) {
115+
player.getBukkitEntity().sendPluginMessage(AxiomPaper.PLUGIN, "axiom:marker_data", bytes);
116+
}
117+
}
118+
}
119+
}
120+
121+
private void tickChunkRelight(int maxChunkRelightsPerTick, int maxChunkSendsPerTick) {
52122
ChunkMap chunkMap = this.level.getChunkSource().chunkMap;
53123

54124
boolean sendAll = maxChunkSendsPerTick <= 0;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.moulberry.axiom.marker;
2+
3+
import net.minecraft.nbt.CompoundTag;
4+
import net.minecraft.nbt.ListTag;
5+
import net.minecraft.nbt.Tag;
6+
import net.minecraft.network.FriendlyByteBuf;
7+
import net.minecraft.world.entity.Marker;
8+
import net.minecraft.world.level.block.entity.BlockEntity;
9+
import net.minecraft.world.level.chunk.LevelChunk;
10+
import net.minecraft.world.phys.Vec3;
11+
import org.jetbrains.annotations.Nullable;
12+
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;
13+
14+
import java.lang.reflect.Field;
15+
import java.lang.reflect.Method;
16+
import java.util.UUID;
17+
18+
public record MarkerData(UUID uuid, Vec3 position, @Nullable String name, @Nullable Vec3 minRegion, @Nullable Vec3 maxRegion) {
19+
public static MarkerData read(FriendlyByteBuf friendlyByteBuf) {
20+
UUID uuid = friendlyByteBuf.readUUID();
21+
Vec3 position = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
22+
String name = friendlyByteBuf.readNullable(FriendlyByteBuf::readUtf);
23+
24+
Vec3 minRegion = null;
25+
Vec3 maxRegion = null;
26+
if (friendlyByteBuf.readBoolean()) {
27+
minRegion = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
28+
maxRegion = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
29+
}
30+
31+
return new MarkerData(uuid, position, name, minRegion, maxRegion);
32+
}
33+
34+
public static void write(FriendlyByteBuf friendlyByteBuf, MarkerData markerData) {
35+
friendlyByteBuf.writeUUID(markerData.uuid);
36+
friendlyByteBuf.writeDouble(markerData.position.x);
37+
friendlyByteBuf.writeDouble(markerData.position.y);
38+
friendlyByteBuf.writeDouble(markerData.position.z);
39+
friendlyByteBuf.writeNullable(markerData.name, FriendlyByteBuf::writeUtf);
40+
41+
if (markerData.minRegion != null && markerData.maxRegion != null) {
42+
friendlyByteBuf.writeBoolean(true);
43+
friendlyByteBuf.writeDouble(markerData.minRegion.x);
44+
friendlyByteBuf.writeDouble(markerData.minRegion.y);
45+
friendlyByteBuf.writeDouble(markerData.minRegion.z);
46+
friendlyByteBuf.writeDouble(markerData.maxRegion.x);
47+
friendlyByteBuf.writeDouble(markerData.maxRegion.y);
48+
friendlyByteBuf.writeDouble(markerData.maxRegion.z);
49+
} else {
50+
friendlyByteBuf.writeBoolean(false);
51+
}
52+
}
53+
54+
private static final Field dataField;
55+
static {
56+
ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar();
57+
String fieldName = reflectionRemapper.remapFieldName(Marker.class, "data");
58+
59+
try {
60+
dataField = Marker.class.getDeclaredField(fieldName);
61+
dataField.setAccessible(true);
62+
} catch (Exception e) {
63+
e.printStackTrace();
64+
throw new RuntimeException(e);
65+
}
66+
}
67+
68+
public static CompoundTag getData(Marker marker) {
69+
try {
70+
return (CompoundTag) dataField.get(marker);
71+
} catch (Exception e) {
72+
e.printStackTrace();
73+
throw new RuntimeException(e);
74+
}
75+
}
76+
77+
public static MarkerData createFrom(Marker marker) {
78+
Vec3 position = marker.position();
79+
CompoundTag data = getData(marker);
80+
81+
String name = data.getString("name").trim();
82+
if (name.isEmpty()) name = null;
83+
84+
Vec3 minRegion = null;
85+
Vec3 maxRegion = null;
86+
if (data.contains("min", Tag.TAG_LIST) && data.contains("max", Tag.TAG_LIST)) {
87+
ListTag min = data.getList("min", Tag.TAG_DOUBLE);
88+
ListTag max = data.getList("max", Tag.TAG_DOUBLE);
89+
90+
if (min.size() == 3 && max.size() == 3) {
91+
double minX = min.getDouble(0);
92+
double minY = min.getDouble(1);
93+
double minZ = min.getDouble(2);
94+
double maxX = max.getDouble(0);
95+
double maxY = max.getDouble(1);
96+
double maxZ = max.getDouble(2);
97+
minRegion = new Vec3(minX, minY, minZ);
98+
maxRegion = new Vec3(maxX, maxY, maxZ);
99+
}
100+
101+
}
102+
103+
return new MarkerData(marker.getUUID(), position, name, minRegion, maxRegion);
104+
}
105+
}

src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.moulberry.axiom.AxiomConstants;
55
import com.moulberry.axiom.AxiomPaper;
66
import com.moulberry.axiom.View;
7+
import com.moulberry.axiom.WorldExtension;
78
import com.moulberry.axiom.event.AxiomHandshakeEvent;
89
import com.moulberry.axiom.persistence.ItemStackDataType;
910
import com.moulberry.axiom.persistence.UUIDDataType;
@@ -167,6 +168,8 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player pla
167168
} else {
168169
properties.registerFor(plugin, player);
169170
}
171+
172+
WorldExtension.onPlayerJoin(world, player);
170173
}
171174

172175
}

src/main/java/com/moulberry/axiom/packet/ManipulateEntityPacketListener.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.moulberry.axiom.packet;
22

33
import com.moulberry.axiom.AxiomPaper;
4+
import com.moulberry.axiom.NbtSanitization;
45
import io.netty.buffer.Unpooled;
56
import net.minecraft.core.BlockPos;
67
import net.minecraft.core.Direction;
@@ -104,6 +105,8 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player pla
104105
if (blacklistedEntities.contains(type)) continue;
105106

106107
if (entry.merge != null && !entry.merge.isEmpty()) {
108+
NbtSanitization.sanitizeEntity(entry.merge);
109+
107110
CompoundTag compoundTag = entity.saveWithoutId(new CompoundTag());
108111
compoundTag = merge(compoundTag, entry.merge);
109112
entity.load(compoundTag);

0 commit comments

Comments
 (0)