Support spawn, manipulate and delete packets

Dieser Commit ist enthalten in:
Moulberry 2024-01-14 16:24:59 +08:00
Ursprung 2acc42b125
Commit 8fd71e59ba
6 geänderte Dateien mit 435 neuen und 1 gelöschten Zeilen

Datei anzeigen

@ -124,6 +124,15 @@ public class AxiomPaper extends JavaPlugin implements Listener {
if (configuration.getBoolean("packet-handlers.request-chunk-data")) { if (configuration.getBoolean("packet-handlers.request-chunk-data")) {
msg.registerIncomingPluginChannel(this, "axiom:request_chunk_data", new RequestChunkDataPacketListener(this)); msg.registerIncomingPluginChannel(this, "axiom:request_chunk_data", new RequestChunkDataPacketListener(this));
} }
if (configuration.getBoolean("packet-handlers.spawn-entity")) {
msg.registerIncomingPluginChannel(this, "axiom:spawn_entity", new SpawnEntityPacketListener(this));
}
if (configuration.getBoolean("packet-handlers.manipulate-entity")) {
msg.registerIncomingPluginChannel(this, "axiom:manipulate_entity", new ManipulateEntityPacketListener(this));
}
if (configuration.getBoolean("packet-handlers.delete-entity")) {
msg.registerIncomingPluginChannel(this, "axiom:delete_entity", new DeleteEntityPacketListener(this));
}
if (configuration.getBoolean("packet-handlers.set-buffer")) { if (configuration.getBoolean("packet-handlers.set-buffer")) {
SetBlockBufferPacketListener setBlockBufferPacketListener = new SetBlockBufferPacketListener(this); SetBlockBufferPacketListener setBlockBufferPacketListener = new SetBlockBufferPacketListener(this);

Datei anzeigen

@ -0,0 +1,68 @@
package com.moulberry.axiom.packet;
import com.moulberry.axiom.AxiomPaper;
import io.netty.buffer.Unpooled;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.decoration.HangingEntity;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.phys.Vec3;
import org.bukkit.craftbukkit.v1_20_R3.CraftWorld;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
public class DeleteEntityPacketListener implements PluginMessageListener {
private final AxiomPaper plugin;
public DeleteEntityPacketListener(AxiomPaper plugin) {
this.plugin = plugin;
}
@Override
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
if (!this.plugin.canUseAxiom(player)) {
return;
}
if (!player.hasPermission("axiom.entity.*") && !player.hasPermission("axiom.entity.delete")) {
return;
}
if (!this.plugin.canModifyWorld(player, player.getWorld())) {
return;
}
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
List<UUID> delete = friendlyByteBuf.readList(FriendlyByteBuf::readUUID);
ServerLevel serverLevel = ((CraftWorld)player.getWorld()).getHandle();
List<String> whitelistedEntities = this.plugin.configuration.getStringList("whitelist-entities");
List<String> blacklistedEntities = this.plugin.configuration.getStringList("blacklist-entities");
for (UUID uuid : delete) {
Entity entity = serverLevel.getEntity(uuid);
if (entity == null || entity instanceof net.minecraft.world.entity.player.Player || entity.hasPassenger(e -> e instanceof net.minecraft.world.entity.player.Player)) continue;
String type = EntityType.getKey(entity.getType()).toString();
if (!whitelistedEntities.isEmpty() && !whitelistedEntities.contains(type)) continue;
if (blacklistedEntities.contains(type)) continue;
entity.remove(Entity.RemovalReason.DISCARDED);
}
}
}

Datei anzeigen

@ -0,0 +1,201 @@
package com.moulberry.axiom.packet;
import com.moulberry.axiom.AxiomPaper;
import io.netty.buffer.Unpooled;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.RelativeMovement;
import net.minecraft.world.entity.decoration.HangingEntity;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.phys.Vec3;
import org.bukkit.craftbukkit.v1_20_R3.CraftWorld;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public class ManipulateEntityPacketListener implements PluginMessageListener {
private final AxiomPaper plugin;
public ManipulateEntityPacketListener(AxiomPaper plugin) {
this.plugin = plugin;
}
public enum PassengerManipulation {
NONE,
REMOVE_ALL,
ADD_LIST,
REMOVE_LIST
}
public record ManipulateEntry(UUID uuid, @Nullable Set<RelativeMovement> relativeMovementSet, @Nullable Vec3 position,
float yaw, float pitch, CompoundTag merge, PassengerManipulation passengerManipulation, List<UUID> passengers) {
public static ManipulateEntry read(FriendlyByteBuf friendlyByteBuf) {
UUID uuid = friendlyByteBuf.readUUID();
int flags = friendlyByteBuf.readByte();
Set<RelativeMovement> relativeMovementSet = null;
Vec3 position = null;
float yaw = 0;
float pitch = 0;
if (flags >= 0) {
relativeMovementSet = RelativeMovement.unpack(flags);
position = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
yaw = friendlyByteBuf.readFloat();
pitch = friendlyByteBuf.readFloat();
}
CompoundTag nbt = friendlyByteBuf.readNbt();
PassengerManipulation passengerManipulation = friendlyByteBuf.readEnum(PassengerManipulation.class);
List<UUID> passengers = List.of();
if (passengerManipulation == PassengerManipulation.ADD_LIST || passengerManipulation == PassengerManipulation.REMOVE_LIST) {
passengers = friendlyByteBuf.readList(FriendlyByteBuf::readUUID);
}
return new ManipulateEntry(uuid, relativeMovementSet, position, yaw, pitch, nbt,
passengerManipulation, passengers);
}
}
private static final Rotation[] ROTATION_VALUES = Rotation.values();
@Override
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
if (!this.plugin.canUseAxiom(player)) {
return;
}
if (!player.hasPermission("axiom.entity.*") && !player.hasPermission("axiom.entity.manipulate")) {
return;
}
if (!this.plugin.canModifyWorld(player, player.getWorld())) {
return;
}
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
List<ManipulateEntry> entries = friendlyByteBuf.readList(ManipulateEntry::read);
ServerLevel serverLevel = ((CraftWorld)player.getWorld()).getHandle();
List<String> whitelistedEntities = this.plugin.configuration.getStringList("whitelist-entities");
List<String> blacklistedEntities = this.plugin.configuration.getStringList("blacklist-entities");
for (ManipulateEntry entry : entries) {
Entity entity = serverLevel.getEntity(entry.uuid);
if (entity == null || entity instanceof net.minecraft.world.entity.player.Player || entity.hasPassenger(ManipulateEntityPacketListener::isPlayer)) continue;
String type = EntityType.getKey(entity.getType()).toString();
if (!whitelistedEntities.isEmpty() && !whitelistedEntities.contains(type)) continue;
if (blacklistedEntities.contains(type)) continue;
if (entry.merge != null && !entry.merge.isEmpty()) {
CompoundTag compoundTag = entity.saveWithoutId(new CompoundTag());
compoundTag = merge(compoundTag, entry.merge);
entity.load(compoundTag);
}
Vec3 entryPos = entry.position();
if (entryPos != null && entry.relativeMovementSet != null) {
double newX = entry.relativeMovementSet.contains(RelativeMovement.X) ? entity.position().x + entryPos.x : entryPos.x;
double newY = entry.relativeMovementSet.contains(RelativeMovement.Y) ? entity.position().y + entryPos.y : entryPos.y;
double newZ = entry.relativeMovementSet.contains(RelativeMovement.Z) ? entity.position().z + entryPos.z : entryPos.z;
float newYaw = entry.relativeMovementSet.contains(RelativeMovement.Y_ROT) ? entity.getYRot() + entry.yaw : entry.yaw;
float newPitch = entry.relativeMovementSet.contains(RelativeMovement.X_ROT) ? entity.getXRot() + entry.pitch : entry.pitch;
if (entity instanceof HangingEntity hangingEntity) {
float changedYaw = newYaw - entity.getYRot();
int rotations = Math.round(changedYaw / 90);
hangingEntity.rotate(ROTATION_VALUES[rotations & 3]);
if (entity instanceof ItemFrame itemFrame && itemFrame.getDirection().getAxis() == Direction.Axis.Y) {
itemFrame.setRotation(itemFrame.getRotation() - Math.round(changedYaw / 45));
}
}
entity.teleportTo(serverLevel, newX, newY, newZ, Set.of(), newYaw, newPitch);
entity.setYHeadRot(newYaw);
}
switch (entry.passengerManipulation) {
case NONE -> {}
case REMOVE_ALL -> entity.ejectPassengers();
case ADD_LIST -> {
for (UUID passengerUuid : entry.passengers) {
Entity passenger = serverLevel.getEntity(passengerUuid);
if (passenger == null || passenger.isPassenger() ||
passenger instanceof net.minecraft.world.entity.player.Player || passenger.hasPassenger(ManipulateEntityPacketListener::isPlayer)) continue;
String passengerType = EntityType.getKey(passenger.getType()).toString();
if (!whitelistedEntities.isEmpty() && !whitelistedEntities.contains(passengerType)) continue;
if (blacklistedEntities.contains(passengerType)) continue;
// Prevent mounting loop
if (passenger.getSelfAndPassengers().anyMatch(entity2 -> entity2 == entity)) {
continue;
}
passenger.startRiding(entity, true);
}
}
case REMOVE_LIST -> {
for (UUID passengerUuid : entry.passengers) {
Entity passenger = serverLevel.getEntity(passengerUuid);
if (passenger == null || passenger == entity || passenger instanceof net.minecraft.world.entity.player.Player ||
passenger.hasPassenger(ManipulateEntityPacketListener::isPlayer)) continue;
String passengerType = EntityType.getKey(passenger.getType()).toString();
if (!whitelistedEntities.isEmpty() && !whitelistedEntities.contains(passengerType)) continue;
if (blacklistedEntities.contains(passengerType)) continue;
Entity vehicle = passenger.getVehicle();
if (vehicle == entity) {
passenger.stopRiding();
}
}
}
}
}
}
private static CompoundTag merge(CompoundTag left, CompoundTag right) {
for (String key : right.getAllKeys()) {
Tag tag = right.get(key);
if (tag instanceof CompoundTag compound) {
if (compound.isEmpty()) {
left.remove(key);
} else if (left.contains(key, Tag.TAG_COMPOUND)) {
CompoundTag child = left.getCompound(key);
merge(child, compound);
} else {
left.put(key, tag.copy());
}
} else {
left.put(key, tag.copy());
}
}
return left;
}
private static boolean isPlayer(Entity entity) {
return entity instanceof net.minecraft.world.entity.player.Player;
}
}

Datei anzeigen

@ -0,0 +1,130 @@
package com.moulberry.axiom.packet;
import com.moulberry.axiom.AxiomPaper;
import com.moulberry.axiom.event.AxiomTeleportEvent;
import com.moulberry.axiom.event.AxiomUnknownTeleportEvent;
import io.netty.buffer.Unpooled;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.decoration.HangingEntity;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.Location;
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.util.CraftNamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
public class SpawnEntityPacketListener implements PluginMessageListener {
private final AxiomPaper plugin;
public SpawnEntityPacketListener(AxiomPaper plugin) {
this.plugin = plugin;
}
private record SpawnEntry(UUID newUuid, double x, double y, double z, float yaw, float pitch,
@Nullable UUID copyFrom, CompoundTag tag) {
public SpawnEntry(FriendlyByteBuf friendlyByteBuf) {
this(friendlyByteBuf.readUUID(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(),
friendlyByteBuf.readDouble(), friendlyByteBuf.readFloat(), friendlyByteBuf.readFloat(),
friendlyByteBuf.readNullable(FriendlyByteBuf::readUUID), friendlyByteBuf.readNbt());
}
}
private static final Rotation[] ROTATION_VALUES = Rotation.values();
@Override
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
if (!this.plugin.canUseAxiom(player)) {
return;
}
if (!player.hasPermission("axiom.entity.*") && !player.hasPermission("axiom.entity.spawn")) {
return;
}
if (!this.plugin.canModifyWorld(player, player.getWorld())) {
return;
}
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
List<SpawnEntry> entries = friendlyByteBuf.readList(SpawnEntry::new);
ServerLevel serverLevel = ((CraftWorld)player.getWorld()).getHandle();
List<String> whitelistedEntities = this.plugin.configuration.getStringList("whitelist-entities");
List<String> blacklistedEntities = this.plugin.configuration.getStringList("blacklist-entities");
for (SpawnEntry entry : entries) {
Vec3 position = new Vec3(entry.x, entry.y, entry.z);
BlockPos blockPos = BlockPos.containing(position);
if (!Level.isInSpawnableBounds(blockPos)) {
continue;
}
CompoundTag tag = entry.tag == null ? new CompoundTag() : entry.tag;
if (serverLevel.getEntity(entry.newUuid) != null) continue;
if (entry.copyFrom != null) {
Entity entityCopyFrom = serverLevel.getEntity(entry.copyFrom);
if (entityCopyFrom != null) {
CompoundTag compoundTag = new CompoundTag();
if (entityCopyFrom.saveAsPassenger(compoundTag)) {
compoundTag.remove("Dimension");
tag = tag.merge(compoundTag);
}
}
}
if (!tag.contains("id")) continue;
Entity spawned = EntityType.loadEntityRecursive(tag, serverLevel, entity -> {
String type = EntityType.getKey(entity.getType()).toString();
if (!whitelistedEntities.isEmpty() && !whitelistedEntities.contains(type)) return null;
if (blacklistedEntities.contains(type)) return null;
entity.setUUID(entry.newUuid);
if (entity instanceof HangingEntity hangingEntity) {
float changedYaw = entry.yaw - entity.getYRot();
int rotations = Math.round(changedYaw / 90);
hangingEntity.rotate(ROTATION_VALUES[rotations & 3]);
if (entity instanceof ItemFrame itemFrame && itemFrame.getDirection().getAxis() == Direction.Axis.Y) {
itemFrame.setRotation(itemFrame.getRotation() - Math.round(changedYaw / 45));
}
}
entity.moveTo(position.x, position.y, position.z, entry.yaw, entry.pitch);
entity.setYHeadRot(entity.getYRot());
return entity;
});
if (spawned != null) {
serverLevel.tryAddFreshEntityWithPassengers(spawned);
}
}
}
}

Datei anzeigen

@ -34,6 +34,20 @@ block-buffer-rate-limit: 0
# Log large block buffer changes # Log large block buffer changes
log-large-block-buffer-changes: false log-large-block-buffer-changes: false
# Whitelist entities that can be spawned/manipulated/deleted by the client
whitelist-entities:
- "minecraft:item_display"
- "minecraft:block_display"
- "minecraft:text_display"
- "minecraft:painting"
- "minecraft:armor_stand"
- "minecraft:item_frame"
- "minecraft:glow_item_frame"
# Blacklist entities that can be spawned/manipulated/deleted by the client
blacklist-entities:
# - "minecraft:ender_dragon"
# Disallowed blocks # Disallowed blocks
disallowed-blocks: disallowed-blocks:
# - "minecraft:wheat" # - "minecraft:wheat"
@ -53,3 +67,6 @@ packet-handlers:
set-editor-views: true set-editor-views: true
request-chunk-data: true request-chunk-data: true
set-buffer: true set-buffer: true
spawn-entity: true
manipulate-entity: true
delete-entity: true

Datei anzeigen

@ -7,6 +7,15 @@ authors:
api-version: "$apiVersion" api-version: "$apiVersion"
permissions: permissions:
axiom.*: axiom.*:
description: Allows use of all Axiom features description: Allows use of all default Axiom features
default: op default: op
axiom.entity.*:
description: Allows use of all entity-related features (spawning, manipulating, deleting)
default: op
axiom.entity.spawn:
description: Allows entity spawning
axiom.entity.manipulate:
description: Allows entity manipulation
axiom.entity.delete:
description: Allows entity deletion