Mirror von
https://github.com/Moulberry/AxiomPaperPlugin.git
synchronisiert 2024-11-08 09:30:04 +01:00
Initial
Dieser Commit ist enthalten in:
Ursprung
665381c673
Commit
0b14fddab0
@ -1,8 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = off
|
@ -1,59 +1,64 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
id("io.papermc.paperweight.userdev") version "1.5.5"
|
||||
id("xyz.jpenilla.run-paper") version "2.1.0" // Adds runServer and runMojangMappedServer tasks for testing
|
||||
`java-library`
|
||||
id("io.papermc.paperweight.userdev") version "1.5.5"
|
||||
id("xyz.jpenilla.run-paper") version "2.1.0" // Adds runServer and runMojangMappedServer tasks for testing
|
||||
|
||||
// Shades and relocates dependencies into our plugin jar. See https://imperceptiblethoughts.com/shadow/introduction/
|
||||
id("com.github.johnrengelman.shadow") version "8.1.1"
|
||||
}
|
||||
|
||||
group = "io.papermc.paperweight"
|
||||
group = "com.moulberry.com.moulberry.axiom"
|
||||
version = "1.0.0-SNAPSHOT"
|
||||
description = "Test plugin for paperweight-userdev"
|
||||
description = "Serverside component for Axiom on Paper"
|
||||
|
||||
java {
|
||||
// Configure the java toolchain. This allows gradle to auto-provision JDK 17 on systems that only have JDK 8 installed for example.
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
|
||||
// Configure the java toolchain. This allows gradle to auto-provision JDK 17 on systems that only have JDK 8 installed for example.
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||
maven("https://jitpack.io")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
paperweight.paperDevBundle("1.20.1-R0.1-SNAPSHOT")
|
||||
// paperweight.foliaDevBundle("1.20.1-R0.1-SNAPSHOT")
|
||||
// paperweight.devBundle("com.example.paperfork", "1.20.1-R0.1-SNAPSHOT")
|
||||
paperweight.paperDevBundle("1.20.1-R0.1-SNAPSHOT")
|
||||
implementation("xyz.jpenilla:reflection-remapper:0.1.0-SNAPSHOT")
|
||||
}
|
||||
|
||||
tasks {
|
||||
// Configure reobfJar to run when invoking the build task
|
||||
assemble {
|
||||
dependsOn(reobfJar)
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
|
||||
|
||||
// Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable.
|
||||
// See https://openjdk.java.net/jeps/247 for more information.
|
||||
options.release.set(17)
|
||||
}
|
||||
javadoc {
|
||||
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
|
||||
}
|
||||
processResources {
|
||||
filteringCharset = Charsets.UTF_8.name() // We want UTF-8 for everything
|
||||
val props = mapOf(
|
||||
"name" to project.name,
|
||||
"version" to project.version,
|
||||
"description" to project.description,
|
||||
"apiVersion" to "1.20"
|
||||
)
|
||||
inputs.properties(props)
|
||||
filesMatching("plugin.yml") {
|
||||
expand(props)
|
||||
// Configure reobfJar to run when invoking the build task
|
||||
assemble {
|
||||
dependsOn(reobfJar)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
reobfJar {
|
||||
// This is an example of how you might change the output location for reobfJar. It's recommended not to do this
|
||||
// for a variety of reasons, however it's asked frequently enough that an example of how to do it is included here.
|
||||
outputJar.set(layout.buildDirectory.file("libs/PaperweightTestPlugin-${project.version}.jar"))
|
||||
}
|
||||
*/
|
||||
compileJava {
|
||||
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
|
||||
|
||||
// Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable.
|
||||
// See https://openjdk.java.net/jeps/247 for more information.
|
||||
options.release.set(17)
|
||||
}
|
||||
javadoc {
|
||||
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
|
||||
}
|
||||
processResources {
|
||||
filteringCharset = Charsets.UTF_8.name() // We want UTF-8 for everything
|
||||
val props = mapOf(
|
||||
"name" to project.name,
|
||||
"version" to project.version,
|
||||
"description" to project.description,
|
||||
"apiVersion" to "1.20"
|
||||
)
|
||||
inputs.properties(props)
|
||||
filesMatching("plugin.yml") {
|
||||
expand(props)
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
// helper function to relocate a package into our package
|
||||
fun reloc(pkg: String) = relocate(pkg, "com.moulberry.axiom.dependency.$pkg")
|
||||
reloc("xyz.jpenilla:reflection-remapper")
|
||||
}
|
||||
}
|
||||
|
@ -2,4 +2,4 @@ plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version("0.5.0")
|
||||
}
|
||||
|
||||
rootProject.name = "paperweight-test-plugin"
|
||||
rootProject.name = "AxiomPaper"
|
||||
|
192
src/main/java/com/moulberry/axiom/AxiomPaper.java
Normale Datei
192
src/main/java/com/moulberry/axiom/AxiomPaper.java
Normale Datei
@ -0,0 +1,192 @@
|
||||
package com.moulberry.axiom;
|
||||
|
||||
import com.moulberry.axiom.packet.AxiomBigPayloadHandler;
|
||||
import com.moulberry.axiom.packet.SetBlockBufferPacketListener;
|
||||
import com.moulberry.axiom.packet.SetBlockPacketListener;
|
||||
import com.moulberry.axiom.persistence.ItemStackDataType;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.papermc.paper.network.ChannelInitializeListener;
|
||||
import io.papermc.paper.network.ChannelInitializeListenerHolder;
|
||||
import io.papermc.paper.network.ConnectionEvent;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.ConnectionProtocol;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRegisterChannelEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AxiomPaper extends JavaPlugin implements Listener {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Bukkit.getPluginManager().registerEvents(this, this);
|
||||
|
||||
Bukkit.getMessenger().registerOutgoingPluginChannel(this, "axiom:enable");
|
||||
Bukkit.getMessenger().registerOutgoingPluginChannel(this, "axiom:initialize_hotbars");
|
||||
|
||||
Bukkit.getMessenger().registerIncomingPluginChannel(this, "axiom:set_gamemode", (channel, player, message) -> {
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
GameType gameType = GameType.byId(friendlyByteBuf.readByte());
|
||||
((CraftPlayer)player).getHandle().setGameMode(gameType);
|
||||
});
|
||||
Bukkit.getMessenger().registerIncomingPluginChannel(this, "axiom:set_fly_speed", (channel, player, message) -> {
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
float flySpeed = friendlyByteBuf.readFloat();
|
||||
((CraftPlayer)player).getHandle().getAbilities().setFlyingSpeed(flySpeed);
|
||||
});
|
||||
Bukkit.getMessenger().registerIncomingPluginChannel(this, "axiom:set_block", new SetBlockPacketListener(this));
|
||||
Bukkit.getMessenger().registerIncomingPluginChannel(this, "axiom:set_hotbar_slot", (channel, player, message) -> {
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
int index = friendlyByteBuf.readByte();
|
||||
if (index < 0 || index >= 9*9) return;
|
||||
net.minecraft.world.item.ItemStack nmsStack = friendlyByteBuf.readItem();
|
||||
|
||||
PersistentDataContainer container = player.getPersistentDataContainer();
|
||||
PersistentDataContainer hotbarItems = container.get(HOTBAR_DATA, PersistentDataType.TAG_CONTAINER);
|
||||
if (hotbarItems == null) hotbarItems = container.getAdapterContext().newPersistentDataContainer();
|
||||
hotbarItems.set(new NamespacedKey("axiom", "slot_"+index), ItemStackDataType.INSTANCE, CraftItemStack.asCraftMirror(nmsStack));
|
||||
container.set(HOTBAR_DATA, PersistentDataType.TAG_CONTAINER, hotbarItems);
|
||||
});
|
||||
Bukkit.getMessenger().registerIncomingPluginChannel(this, "axiom:switch_active_hotbar", (channel, player, message) -> {
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
int oldHotbarIndex = friendlyByteBuf.readByte();
|
||||
int activeHotbarIndex = friendlyByteBuf.readByte();
|
||||
|
||||
ItemStack[] hotbarItems = new ItemStack[9];
|
||||
for (int i=0; i<9; i++) {
|
||||
hotbarItems[i] = CraftItemStack.asCraftMirror(friendlyByteBuf.readItem());
|
||||
}
|
||||
|
||||
PersistentDataContainer container = player.getPersistentDataContainer();
|
||||
PersistentDataContainer containerHotbarItems = container.get(HOTBAR_DATA, PersistentDataType.TAG_CONTAINER);
|
||||
if (containerHotbarItems == null) containerHotbarItems = container.getAdapterContext().newPersistentDataContainer();
|
||||
|
||||
for (int i=0; i<9; i++) {
|
||||
if (oldHotbarIndex != activeHotbarIndex) {
|
||||
int index = oldHotbarIndex*9 + i;
|
||||
ItemStack stack = player.getInventory().getItem(i);
|
||||
if (stack == null) {
|
||||
stack = new ItemStack(Material.AIR);
|
||||
} else {
|
||||
stack = stack.clone();
|
||||
}
|
||||
containerHotbarItems.set(new NamespacedKey("axiom", "slot_"+index), ItemStackDataType.INSTANCE, stack);
|
||||
}
|
||||
int index = activeHotbarIndex*9 + i;
|
||||
containerHotbarItems.set(new NamespacedKey("axiom", "slot_"+index), ItemStackDataType.INSTANCE, hotbarItems[i].clone());
|
||||
if (player.getGameMode() == GameMode.CREATIVE) player.getInventory().setItem(i, hotbarItems[i]);
|
||||
}
|
||||
|
||||
container.set(HOTBAR_DATA, PersistentDataType.TAG_CONTAINER, containerHotbarItems);
|
||||
container.set(ACTIVE_HOTBAR_INDEX, PersistentDataType.BYTE, (byte) activeHotbarIndex);
|
||||
});
|
||||
Bukkit.getMessenger().registerIncomingPluginChannel(this, "axiom:teleport", (channel, player, message) -> {
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
ResourceKey<Level> resourceKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION);
|
||||
double x = friendlyByteBuf.readDouble();
|
||||
double y = friendlyByteBuf.readDouble();
|
||||
double z = friendlyByteBuf.readDouble();
|
||||
float yRot = friendlyByteBuf.readFloat();
|
||||
float xRot = friendlyByteBuf.readFloat();
|
||||
|
||||
NamespacedKey namespacedKey = new NamespacedKey(resourceKey.location().getNamespace(), resourceKey.location().getPath());
|
||||
World world = Bukkit.getWorld(namespacedKey);
|
||||
if (world != null) {
|
||||
player.teleport(new Location(world, x, y, z, yRot, xRot));
|
||||
}
|
||||
});
|
||||
|
||||
SetBlockBufferPacketListener setBlockBufferPacketListener = new SetBlockBufferPacketListener(this);
|
||||
|
||||
ChannelInitializeListenerHolder.addListener(Key.key("axiom:handle_big_payload"), new ChannelInitializeListener() {
|
||||
@Override
|
||||
public void afterInitChannel(@NonNull Channel channel) {
|
||||
var packets = ConnectionProtocol.PLAY.getPacketsByIds(PacketFlow.SERVERBOUND);
|
||||
int payloadId = -1;
|
||||
for (Map.Entry<Integer, Class<? extends Packet<?>>> entry : packets.entrySet()) {
|
||||
if (entry.getValue() == ServerboundCustomPayloadPacket.class) {
|
||||
payloadId = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (payloadId < 0) {
|
||||
throw new RuntimeException("Failed ot find ServerboundCustomPayloadPacket id");
|
||||
}
|
||||
|
||||
Connection connection = (Connection) channel.pipeline().get("packet_handler");
|
||||
channel.pipeline().addBefore("decoder", "axiom-big-payload-handler",
|
||||
new AxiomBigPayloadHandler(payloadId, connection, setBlockBufferPacketListener));
|
||||
}
|
||||
});
|
||||
|
||||
// Bukkit.getMessenger().registerIncomingPluginChannel(this, "axiom:set_block_buffer", new SetBlockBufferPacketListener(this));
|
||||
}
|
||||
|
||||
private static final NamespacedKey ACTIVE_HOTBAR_INDEX = new NamespacedKey("axiom", "active_hotbar_index");
|
||||
private static final NamespacedKey HOTBAR_DATA = new NamespacedKey("axiom", "hotbar_data");
|
||||
|
||||
@EventHandler
|
||||
public void onRegisterChannel(PlayerRegisterChannelEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
String channel = event.getChannel();
|
||||
|
||||
switch (channel) {
|
||||
case "axiom:enable" -> {
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
friendlyByteBuf.writeBoolean(true);
|
||||
friendlyByteBuf.writeByte(0); // todo: world properties
|
||||
player.sendPluginMessage(this, "axiom:enable", friendlyByteBuf.array());
|
||||
}
|
||||
case "axiom:initialize_hotbars" -> {
|
||||
PersistentDataContainer container = player.getPersistentDataContainer();
|
||||
int activeHotbarIndex = container.getOrDefault(ACTIVE_HOTBAR_INDEX, PersistentDataType.BYTE, (byte) 0);
|
||||
PersistentDataContainer hotbarItems = container.get(HOTBAR_DATA, PersistentDataType.TAG_CONTAINER);
|
||||
if (hotbarItems != null) {
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
friendlyByteBuf.writeByte((byte) activeHotbarIndex);
|
||||
for (int i=0; i<9*9; i++) {
|
||||
// Ignore selected hotbar
|
||||
if (i / 9 == activeHotbarIndex) continue;
|
||||
|
||||
ItemStack stack = hotbarItems.get(new NamespacedKey("axiom", "slot_"+i), ItemStackDataType.INSTANCE);
|
||||
friendlyByteBuf.writeItem(CraftItemStack.asNMSCopy(stack));
|
||||
}
|
||||
player.sendPluginMessage(this, "axiom:initialize_hotbars", friendlyByteBuf.array());
|
||||
}
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
121
src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java
Normale Datei
121
src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java
Normale Datei
@ -0,0 +1,121 @@
|
||||
package com.moulberry.axiom.buffer;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.*;
|
||||
|
||||
public class BlockBuffer {
|
||||
|
||||
public static final BlockState EMPTY_STATE = Blocks.STRUCTURE_VOID.defaultBlockState();
|
||||
|
||||
private final Long2ObjectMap<PalettedContainer<BlockState>> values;
|
||||
|
||||
private PalettedContainer<BlockState> last = null;
|
||||
private long lastId = Long.MAX_VALUE;
|
||||
private int count;
|
||||
|
||||
public BlockBuffer() {
|
||||
this.values = new Long2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
public BlockBuffer(Long2ObjectMap<PalettedContainer<BlockState>> values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.last = null;
|
||||
this.lastId = Long.MAX_VALUE;
|
||||
this.values.clear();
|
||||
}
|
||||
|
||||
public BlockState get(int x, int y, int z) {
|
||||
var container = this.getSectionForCoord(x, y, z);
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var state = container.get(x & 0xF, y & 0xF, z & 0xF);
|
||||
if (state == EMPTY_STATE) {
|
||||
return null;
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
public void set(int x, int y, int z, BlockState state) {
|
||||
var container = this.getOrCreateSectionForCoord(x, y, z);
|
||||
var old = container.getAndSet(x & 0xF, y & 0xF, z & 0xF, state);
|
||||
|
||||
if (old == EMPTY_STATE) {
|
||||
if (state != EMPTY_STATE) this.count += 1;
|
||||
} else if (state == EMPTY_STATE) {
|
||||
this.count -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void set(int cx, int cy, int cz, int lx, int ly, int lz, BlockState state) {
|
||||
var container = this.getOrCreateSection(BlockPos.asLong(cx, cy, cz));
|
||||
var old = container.getAndSet(lx, ly, lz, state);
|
||||
|
||||
if (old == EMPTY_STATE) {
|
||||
if (state != EMPTY_STATE) this.count += 1;
|
||||
} else if (state == EMPTY_STATE) {
|
||||
this.count -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
public BlockState remove(int x, int y, int z) {
|
||||
var container = this.getSectionForCoord(x, y, z);
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var state = container.get(x & 0xF, y & 0xF, z & 0xF);
|
||||
if (state == EMPTY_STATE) {
|
||||
return null;
|
||||
} else {
|
||||
container.set(x & 0xF, y & 0xF, z & 0xF, EMPTY_STATE);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectSet<Long2ObjectMap.Entry<PalettedContainer<BlockState>>> entrySet() {
|
||||
return this.values.long2ObjectEntrySet();
|
||||
}
|
||||
|
||||
public PalettedContainer<BlockState> getSectionForCoord(int x, int y, int z) {
|
||||
long id = BlockPos.asLong(x >> 4, y >> 4, z >> 4);
|
||||
|
||||
if (id != this.lastId) {
|
||||
this.lastId = id;
|
||||
this.last = this.values.get(id);
|
||||
}
|
||||
|
||||
return this.last;
|
||||
}
|
||||
|
||||
public PalettedContainer<BlockState> getOrCreateSectionForCoord(int x, int y, int z) {
|
||||
long id = BlockPos.asLong(x >> 4, y >> 4, z >> 4);
|
||||
return this.getOrCreateSection(id);
|
||||
}
|
||||
|
||||
public PalettedContainer<BlockState> getOrCreateSection(long id) {
|
||||
if (this.last == null || id != this.lastId) {
|
||||
this.lastId = id;
|
||||
this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY,
|
||||
EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES));
|
||||
}
|
||||
|
||||
return this.last;
|
||||
}
|
||||
|
||||
}
|
65
src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java
Normale Datei
65
src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java
Normale Datei
@ -0,0 +1,65 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.papermc.paper.network.ConnectionEvent;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
||||
|
||||
private static final ResourceLocation SET_BLOCK_BUFFER = new ResourceLocation("axiom", "set_block_buffer");
|
||||
private final int payloadId;
|
||||
private final Connection connection;
|
||||
private final SetBlockBufferPacketListener listener;
|
||||
|
||||
public AxiomBigPayloadHandler(int payloadId, Connection connection, SetBlockBufferPacketListener listener) {
|
||||
this.payloadId = payloadId;
|
||||
this.connection = connection;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
try {
|
||||
int i = in.readableBytes();
|
||||
if (i != 0) {
|
||||
int readerIndex = in.readerIndex();
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(in);
|
||||
int packetId = buf.readVarInt();
|
||||
|
||||
if (packetId == payloadId) {
|
||||
ResourceLocation identifier = buf.readResourceLocation();
|
||||
if (identifier.equals(SET_BLOCK_BUFFER)) {
|
||||
ServerPlayer player = connection.getPlayer();
|
||||
if (player != null) {
|
||||
listener.onReceive(player, buf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
in.readerIndex(readerIndex);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
ctx.fireChannelRead(in.retain());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
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, listener));
|
||||
}
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.buffer.BlockBuffer;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.lighting.LightEngine;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
public class SetBlockBufferPacketListener {
|
||||
|
||||
private final AxiomPaper plugin;
|
||||
private final Method updateBlockEntityTicker;
|
||||
|
||||
public SetBlockBufferPacketListener(AxiomPaper plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar();
|
||||
String methodName = reflectionRemapper.remapMethodName(LevelChunk.class, "updateBlockEntityTicker", BlockEntity.class);
|
||||
|
||||
try {
|
||||
this.updateBlockEntityTicker = LevelChunk.class.getDeclaredMethod(methodName, BlockEntity.class);
|
||||
this.updateBlockEntityTicker.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onReceive(ServerPlayer player, FriendlyByteBuf friendlyByteBuf) {
|
||||
ResourceKey<Level> worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION);
|
||||
BlockBuffer buffer = new BlockBuffer();
|
||||
|
||||
while (true) {
|
||||
long index = friendlyByteBuf.readLong();
|
||||
if (index == Long.MAX_VALUE) break;
|
||||
|
||||
PalettedContainer<BlockState> palettedContainer = buffer.getOrCreateSection(index);
|
||||
palettedContainer.read(friendlyByteBuf);
|
||||
}
|
||||
|
||||
player.getServer().execute(() -> {
|
||||
ServerLevel world = player.getServer().getLevel(worldKey);
|
||||
if (world == null) return;
|
||||
|
||||
BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos();
|
||||
|
||||
var lightEngine = world.getChunkSource().getLightEngine();
|
||||
|
||||
BlockState emptyState = BlockBuffer.EMPTY_STATE;
|
||||
|
||||
for (Long2ObjectMap.Entry<PalettedContainer<BlockState>> entry : buffer.entrySet()) {
|
||||
int cx = BlockPos.getX(entry.getLongKey());
|
||||
int cy = BlockPos.getY(entry.getLongKey());
|
||||
int cz = BlockPos.getZ(entry.getLongKey());
|
||||
PalettedContainer<BlockState> container = entry.getValue();
|
||||
|
||||
if (cy < world.getMinSection() || cy >= world.getMaxSection()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LevelChunk chunk = world.getChunk(cx, cz);
|
||||
chunk.setUnsaved(true);
|
||||
|
||||
LevelChunkSection section = chunk.getSection(world.getSectionIndexFromSectionY(cy));
|
||||
PalettedContainer<BlockState> sectionStates = section.getStates();
|
||||
boolean hasOnlyAir = section.hasOnlyAir();
|
||||
|
||||
Heightmap worldSurface = null;
|
||||
Heightmap oceanFloor = null;
|
||||
Heightmap motionBlocking = null;
|
||||
Heightmap motionBlockingNoLeaves = null;
|
||||
for (Map.Entry<Heightmap.Types, Heightmap> heightmap : chunk.getHeightmaps()) {
|
||||
switch (heightmap.getKey()) {
|
||||
case WORLD_SURFACE -> worldSurface = heightmap.getValue();
|
||||
case OCEAN_FLOOR -> oceanFloor = heightmap.getValue();
|
||||
case MOTION_BLOCKING -> motionBlocking = heightmap.getValue();
|
||||
case MOTION_BLOCKING_NO_LEAVES -> motionBlockingNoLeaves = heightmap.getValue();
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
|
||||
sectionStates.acquire();
|
||||
try {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
BlockState blockState = container.get(x, y, z);
|
||||
if (blockState == emptyState) continue;
|
||||
|
||||
int bx = cx*16 + x;
|
||||
int by = cy*16 + y;
|
||||
int bz = cz*16 + z;
|
||||
|
||||
blockPos.set(bx, by, bz);
|
||||
|
||||
if (hasOnlyAir && blockState.isAir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockState old = section.setBlockState(x, y, z, blockState, false);
|
||||
if (blockState != old) {
|
||||
Block block = blockState.getBlock();
|
||||
motionBlocking.update(x, by, z, blockState);
|
||||
motionBlockingNoLeaves.update(x, by, z, blockState);
|
||||
oceanFloor.update(x, by, z, blockState);
|
||||
worldSurface.update(x, by, z, blockState);
|
||||
|
||||
if (false) { // Full update
|
||||
old.onRemove(world, blockPos, blockState, false);
|
||||
|
||||
if (sectionStates.get(x, y, z).is(block)) {
|
||||
blockState.onPlace(world, blockPos, old, false);
|
||||
}
|
||||
}
|
||||
|
||||
boolean oldHasBlockEntity = old.hasBlockEntity();
|
||||
if (old.is(block)) {
|
||||
if (blockState.hasBlockEntity()) {
|
||||
BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK);
|
||||
if (blockEntity == null) {
|
||||
blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState);
|
||||
if (blockEntity != null) {
|
||||
chunk.addAndRegisterBlockEntity(blockEntity);
|
||||
}
|
||||
} else {
|
||||
blockEntity.setBlockState(blockState);
|
||||
|
||||
try {
|
||||
this.updateBlockEntityTicker.invoke(chunk, blockEntity);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (oldHasBlockEntity) {
|
||||
chunk.removeBlockEntity(blockPos);
|
||||
}
|
||||
|
||||
world.getChunkSource().blockChanged(blockPos); // todo: maybe simply resend chunk instead of this?
|
||||
|
||||
if (LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState)) {
|
||||
lightEngine.checkBlock(blockPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
sectionStates.release();
|
||||
}
|
||||
|
||||
boolean nowHasOnlyAir = section.hasOnlyAir();
|
||||
if (hasOnlyAir != nowHasOnlyAir) {
|
||||
world.getChunkSource().getLightEngine().updateSectionStatus(SectionPos.of(cx, cy, cz), nowHasOnlyAir);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
152
src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java
Normale Datei
152
src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java
Normale Datei
@ -0,0 +1,152 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.lighting.LightEngine;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class SetBlockPacketListener implements PluginMessageListener {
|
||||
|
||||
private final AxiomPaper plugin;
|
||||
private final Method updateBlockEntityTicker;
|
||||
|
||||
public SetBlockPacketListener(AxiomPaper plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar();
|
||||
String methodName = reflectionRemapper.remapMethodName(LevelChunk.class, "updateBlockEntityTicker", BlockEntity.class);
|
||||
|
||||
try {
|
||||
this.updateBlockEntityTicker = LevelChunk.class.getDeclaredMethod(methodName, BlockEntity.class);
|
||||
this.updateBlockEntityTicker.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player bukkitPlayer, @NotNull byte[] message) {
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
BlockPos blockPos = friendlyByteBuf.readBlockPos();
|
||||
BlockState blockState = friendlyByteBuf.readById(Block.BLOCK_STATE_REGISTRY);
|
||||
boolean updateNeighbors = friendlyByteBuf.readBoolean();
|
||||
int sequenceId = friendlyByteBuf.readInt();
|
||||
|
||||
ServerPlayer player = ((CraftPlayer)bukkitPlayer).getHandle();
|
||||
|
||||
if (updateNeighbors) {
|
||||
player.level().setBlock(blockPos, blockState, 3);
|
||||
} else {
|
||||
int bx = blockPos.getX();
|
||||
int by = blockPos.getY();
|
||||
int bz = blockPos.getZ();
|
||||
int x = bx & 0xF;
|
||||
int y = by & 0xF;
|
||||
int z = bz & 0xF;
|
||||
int cx = bx >> 4;
|
||||
int cy = by >> 4;
|
||||
int cz = bz >> 4;
|
||||
|
||||
ServerLevel level = player.serverLevel();
|
||||
LevelChunk chunk = level.getChunk(cx, cz);
|
||||
chunk.setUnsaved(true);
|
||||
|
||||
LevelChunkSection section = chunk.getSection(level.getSectionIndexFromSectionY(cy));
|
||||
boolean hasOnlyAir = section.hasOnlyAir();
|
||||
|
||||
Heightmap worldSurface = null;
|
||||
Heightmap oceanFloor = null;
|
||||
Heightmap motionBlocking = null;
|
||||
Heightmap motionBlockingNoLeaves = null;
|
||||
for (Map.Entry<Heightmap.Types, Heightmap> heightmap : chunk.getHeightmaps()) {
|
||||
switch (heightmap.getKey()) {
|
||||
case WORLD_SURFACE -> worldSurface = heightmap.getValue();
|
||||
case OCEAN_FLOOR -> oceanFloor = heightmap.getValue();
|
||||
case MOTION_BLOCKING -> motionBlocking = heightmap.getValue();
|
||||
case MOTION_BLOCKING_NO_LEAVES -> motionBlockingNoLeaves = heightmap.getValue();
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
|
||||
BlockState old = section.setBlockState(x, y, z, blockState, false);
|
||||
if (blockState != old) {
|
||||
Block block = blockState.getBlock();
|
||||
motionBlocking.update(x, by, z, blockState);
|
||||
motionBlockingNoLeaves.update(x, by, z, blockState);
|
||||
oceanFloor.update(x, by, z, blockState);
|
||||
worldSurface.update(x, by, z, blockState);
|
||||
|
||||
if (blockState.hasBlockEntity()) {
|
||||
BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK);
|
||||
|
||||
if (blockEntity == null) {
|
||||
// There isn't a block entity here, create it!
|
||||
blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState);
|
||||
if (blockEntity != null) {
|
||||
chunk.addAndRegisterBlockEntity(blockEntity);
|
||||
}
|
||||
} else {
|
||||
if (blockEntity.getType().isValid(blockState)) {
|
||||
// Block entity is here and the type is correct
|
||||
// Just update the state and ticker and move on
|
||||
blockEntity.setBlockState(blockState);
|
||||
|
||||
try {
|
||||
this.updateBlockEntityTicker.invoke(chunk, blockEntity);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
// Block entity type isn't correct, we need to recreate it
|
||||
chunk.removeBlockEntity(blockPos);
|
||||
|
||||
blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState);
|
||||
if (blockEntity != null) {
|
||||
chunk.addAndRegisterBlockEntity(blockEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (old.hasBlockEntity()) {
|
||||
chunk.removeBlockEntity(blockPos);
|
||||
}
|
||||
|
||||
level.getChunkSource().blockChanged(blockPos);
|
||||
if (LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState)) {
|
||||
level.getChunkSource().getLightEngine().checkBlock(blockPos);
|
||||
}
|
||||
}
|
||||
|
||||
boolean nowHasOnlyAir = section.hasOnlyAir();
|
||||
if (hasOnlyAir != nowHasOnlyAir) {
|
||||
level.getChunkSource().getLightEngine().updateSectionStatus(SectionPos.of(cx, cy, cz), nowHasOnlyAir);
|
||||
}
|
||||
}
|
||||
|
||||
if (sequenceId >= 0) {
|
||||
player.connection.ackBlockChangesUpTo(sequenceId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
46
src/main/java/com/moulberry/axiom/persistence/ItemStackDataType.java
Normale Datei
46
src/main/java/com/moulberry/axiom/persistence/ItemStackDataType.java
Normale Datei
@ -0,0 +1,46 @@
|
||||
package com.moulberry.axiom.persistence;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.persistence.CraftPersistentDataContainer;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataAdapterContext;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ItemStackDataType implements PersistentDataType<PersistentDataContainer, ItemStack> {
|
||||
public static ItemStackDataType INSTANCE = new ItemStackDataType();
|
||||
private ItemStackDataType() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Class<PersistentDataContainer> getPrimitiveType() {
|
||||
return PersistentDataContainer.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Class<ItemStack> getComplexType() {
|
||||
return ItemStack.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PersistentDataContainer toPrimitive(@NotNull ItemStack complex, @NotNull PersistentDataAdapterContext context) {
|
||||
net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(complex);
|
||||
if (nmsStack == null) nmsStack = net.minecraft.world.item.ItemStack.EMPTY;
|
||||
CompoundTag tag = new CompoundTag();
|
||||
nmsStack.save(tag);
|
||||
|
||||
PersistentDataContainer container = context.newPersistentDataContainer();
|
||||
((CraftPersistentDataContainer)container).putAll(tag);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack fromPrimitive(@NotNull PersistentDataContainer primitive, @NotNull PersistentDataAdapterContext context) {
|
||||
CompoundTag tag = ((CraftPersistentDataContainer)primitive).toTagCompound();
|
||||
net.minecraft.world.item.ItemStack nmsStack = net.minecraft.world.item.ItemStack.of(tag);
|
||||
|
||||
return CraftItemStack.asCraftMirror(nmsStack);
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package io.papermc.paperweight.testplugin;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.ParseResults;
|
||||
import com.mojang.brigadier.StringReader;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import com.mojang.brigadier.suggestion.Suggestion;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.PluginIdentifiableCommand;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.command.VanillaCommandWrapper;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
final class PluginBrigadierCommand extends Command implements PluginIdentifiableCommand {
|
||||
private final Consumer<LiteralArgumentBuilder<CommandSourceStack>> command;
|
||||
private final Plugin plugin;
|
||||
|
||||
PluginBrigadierCommand(
|
||||
final Plugin plugin,
|
||||
final String name,
|
||||
final Consumer<LiteralArgumentBuilder<CommandSourceStack>> command
|
||||
) {
|
||||
super(name);
|
||||
this.plugin = plugin;
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(final CommandSender sender, final String commandLabel, final String[] args) {
|
||||
final String joined = String.join(" ", args);
|
||||
final String argsString = joined.isBlank() ? "" : " " + joined;
|
||||
((CraftServer) Bukkit.getServer()).getServer().getCommands().performPrefixedCommand(
|
||||
VanillaCommandWrapper.getListener(sender),
|
||||
commandLabel + argsString,
|
||||
commandLabel
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabComplete(final CommandSender sender, final String alias, final String[] args, final @Nullable Location location) {
|
||||
final String joined = String.join(" ", args);
|
||||
final String argsString = joined.isBlank() ? "" : joined;
|
||||
final CommandDispatcher<CommandSourceStack> dispatcher = ((CraftServer) Bukkit.getServer()).getServer().getCommands().getDispatcher();
|
||||
final ParseResults<CommandSourceStack> results = dispatcher.parse(new StringReader(alias + " " + argsString), VanillaCommandWrapper.getListener(sender));
|
||||
return dispatcher.getCompletionSuggestions(results)
|
||||
.thenApply(result -> result.getList().stream().map(Suggestion::getText).toList())
|
||||
.join();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return this.plugin;
|
||||
}
|
||||
|
||||
Consumer<LiteralArgumentBuilder<CommandSourceStack>> command() {
|
||||
return this.command;
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
package io.papermc.paperweight.testplugin;
|
||||
|
||||
import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource;
|
||||
import com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent;
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.network.chat.ClickEvent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
import static net.kyori.adventure.text.format.NamedTextColor.BLUE;
|
||||
import static net.minecraft.commands.Commands.argument;
|
||||
import static net.minecraft.commands.Commands.literal;
|
||||
import static net.minecraft.commands.arguments.EntityArgument.players;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class TestPlugin extends JavaPlugin implements Listener {
|
||||
@Override
|
||||
public void onEnable() {
|
||||
this.getServer().getPluginManager().registerEvents(this, this);
|
||||
|
||||
this.registerPluginBrigadierCommand(
|
||||
"paperweight",
|
||||
literal -> literal.requires(stack -> stack.getBukkitSender().hasPermission("paperweight"))
|
||||
.then(literal("hello")
|
||||
.executes(ctx -> {
|
||||
ctx.getSource().getBukkitSender().sendMessage(text("Hello!", BLUE));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}))
|
||||
.then(argument("players", players())
|
||||
.executes(ctx -> {
|
||||
final Collection<ServerPlayer> players = EntityArgument.getPlayers(ctx, "players");
|
||||
for (final ServerPlayer player : players) {
|
||||
player.sendSystemMessage(
|
||||
Component.literal("Hello from Paperweight test plugin!")
|
||||
.withStyle(ChatFormatting.ITALIC, ChatFormatting.GREEN)
|
||||
.withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/paperweight @a")))
|
||||
);
|
||||
}
|
||||
return players.size();
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
private PluginBrigadierCommand registerPluginBrigadierCommand(final String label, final Consumer<LiteralArgumentBuilder<CommandSourceStack>> command) {
|
||||
final PluginBrigadierCommand pluginBrigadierCommand = new PluginBrigadierCommand(this, label, command);
|
||||
this.getServer().getCommandMap().register(this.getName(), pluginBrigadierCommand);
|
||||
((CraftServer) this.getServer()).syncCommands();
|
||||
return pluginBrigadierCommand;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@EventHandler
|
||||
public void onCommandRegistered(final CommandRegisteredEvent<BukkitBrigadierCommandSource> event) {
|
||||
if (!(event.getCommand() instanceof PluginBrigadierCommand pluginBrigadierCommand)) {
|
||||
return;
|
||||
}
|
||||
final LiteralArgumentBuilder<CommandSourceStack> node = literal(event.getCommandLabel());
|
||||
pluginBrigadierCommand.command().accept(node);
|
||||
event.setLiteral((LiteralCommandNode) node.build());
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
name: $name
|
||||
version: $version
|
||||
main: io.papermc.paperweight.testplugin.TestPlugin
|
||||
main: com.moulberry.axiom.AxiomPaper
|
||||
description: $description
|
||||
load: STARTUP
|
||||
authors:
|
||||
- Author
|
||||
- Moulberry
|
||||
api-version: "$apiVersion"
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren