Dieser Commit ist enthalten in:
Moulberry 2023-07-02 18:23:44 +08:00
Ursprung 665381c673
Commit 0b14fddab0
12 geänderte Dateien mit 813 neuen und 200 gelöschten Zeilen

Datei anzeigen

@ -1,8 +0,0 @@
root = true
[*]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = off

Datei anzeigen

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

Datei anzeigen

@ -2,4 +2,4 @@ plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.5.0")
}
rootProject.name = "paperweight-test-plugin"
rootProject.name = "AxiomPaper"

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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