geforkt von Mirrors/AxiomPaperPlugin
Commits vergleichen
29 Commits
master
...
multiversi
Autor | SHA1 | Datum | |
---|---|---|---|
5846ba3e8c | |||
f3327c7f13 | |||
9d3d8f7a63 | |||
47ae33a92a | |||
c98ca468fd | |||
028448be8d | |||
610ba248ea | |||
162bfc8fe2 | |||
6f5617ded3 | |||
28823b19aa | |||
a892c966a3 | |||
2213d44289 | |||
307fe08bd6 | |||
5797d0e0be | |||
1045f35f26 | |||
4955631c49 | |||
3d32ddbfac | |||
59eed20542 | |||
15fa621147 | |||
|
af866deeac | ||
81edb4c970 | |||
e31d4e27d1 | |||
a0367320c3 | |||
8cfed96b39 | |||
e9a2b15259 | |||
6f44fad535 | |||
7d82fed462 | |||
35c4443996 | |||
b8fa0aa640 |
@ -18,15 +18,13 @@ java {
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||
maven("https://jitpack.io")
|
||||
maven("https://maven.enginehub.org/repo/")
|
||||
maven("https://repo.papermc.io/repository/maven-public/")
|
||||
maven("https://repo.viaversion.com")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
paperweight.paperDevBundle("1.20.1-R0.1-SNAPSHOT")
|
||||
implementation("xyz.jpenilla:reflection-remapper:0.1.0-SNAPSHOT")
|
||||
|
||||
// Zstd Compression Library
|
||||
implementation("com.github.luben:zstd-jni:1.5.5-4")
|
||||
@ -38,6 +36,9 @@ dependencies {
|
||||
implementation(platform("com.intellectualsites.bom:bom-newest:1.37"))
|
||||
compileOnly("com.intellectualsites.plotsquared:plotsquared-core")
|
||||
compileOnly("com.intellectualsites.plotsquared:plotsquared-bukkit") { isTransitive = false }
|
||||
|
||||
// ViaVersion support
|
||||
compileOnly("com.viaversion:viaversion-api:4.7.0")
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
@ -1,22 +1,22 @@
|
||||
package com.moulberry.axiom;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import org.bukkit.NamespacedKey;
|
||||
|
||||
public class AxiomConstants {
|
||||
|
||||
public static final long MIN_POSITION_LONG = BlockPos.asLong(-33554432, -2048, -33554432);
|
||||
static {
|
||||
if (MIN_POSITION_LONG != 0b1000000000000000000000000010000000000000000000000000100000000000L) {
|
||||
throw new Error("BlockPos representation changed!");
|
||||
}
|
||||
}
|
||||
public static final long MIN_POSITION_LONG = 0b1000000000000000000000000010000000000000000000000000100000000000L;
|
||||
|
||||
public static final int API_VERSION = 5;
|
||||
public static final NamespacedKey ACTIVE_HOTBAR_INDEX = new NamespacedKey("axiom", "active_hotbar_index");
|
||||
public static final NamespacedKey HOTBAR_DATA = new NamespacedKey("axiom", "hotbar_data");
|
||||
|
||||
public static final NamespacedKey ACTIVE_VIEW = new NamespacedKey("axiom", "active_view");
|
||||
public static final NamespacedKey VIEWS = new NamespacedKey("axiom", "views");
|
||||
public static final String PERMISSION = "axiom.*";
|
||||
|
||||
public static final NamespacedKey ACTIVE_HOTBAR_INDEX = axiomKey("active_hotbar_index");
|
||||
public static final NamespacedKey HOTBAR_DATA = axiomKey("hotbar_data");
|
||||
|
||||
public static final NamespacedKey ACTIVE_VIEW = axiomKey("active_view");
|
||||
public static final NamespacedKey VIEWS = axiomKey("views");
|
||||
|
||||
public static NamespacedKey axiomKey(String key) {
|
||||
return new NamespacedKey("axiom", key);
|
||||
}
|
||||
}
|
||||
|
@ -1,105 +1,129 @@
|
||||
package com.moulberry.axiom;
|
||||
|
||||
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
||||
import com.moulberry.axiom.integration.PaperFailMoveListener;
|
||||
import com.moulberry.axiom.packet.*;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.papermc.paper.event.player.PlayerFailMoveEvent;
|
||||
import io.papermc.paper.network.ChannelInitializeListener;
|
||||
import io.papermc.paper.network.ChannelInitializeListenerHolder;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.ConnectionProtocol;
|
||||
import net.minecraft.core.BlockPos;
|
||||
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 org.bukkit.*;
|
||||
import net.minecraft.server.level.*;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.server.network.ServerPlayerConnection;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.plugin.messaging.Messenger;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class AxiomPaper extends JavaPlugin implements Listener {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Bukkit.getPluginManager().registerEvents(this, this);
|
||||
CompressedBlockEntity.initialize(this);
|
||||
|
||||
Messenger msg = Bukkit.getMessenger();
|
||||
|
||||
msg.registerOutgoingPluginChannel(this, "axiom:enable");
|
||||
msg.registerOutgoingPluginChannel(this, "axiom:initialize_hotbars");
|
||||
msg.registerOutgoingPluginChannel(this, "axiom:set_editor_views");
|
||||
msg.registerOutgoingPluginChannel(this, "axiom:block_entities");
|
||||
|
||||
final Set<UUID> activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this, activeAxiomPlayers));
|
||||
msg.registerIncomingPluginChannel(this, "axiom:set_gamemode", new SetGamemodePacketListener());
|
||||
msg.registerIncomingPluginChannel(this, "axiom:set_fly_speed", new SetFlySpeedPacketListener());
|
||||
msg.registerIncomingPluginChannel(this, "axiom:set_block", new SetBlockPacketListener(this));
|
||||
msg.registerIncomingPluginChannel(this, "axiom:set_hotbar_slot", new SetHotbarSlotPacketListener());
|
||||
msg.registerIncomingPluginChannel(this, "axiom:switch_active_hotbar", new SwitchActiveHotbarPacketListener());
|
||||
msg.registerIncomingPluginChannel(this, "axiom:teleport", new TeleportPacketListener());
|
||||
msg.registerIncomingPluginChannel(this, "axiom:set_editor_views", new SetEditorViewsPacketListener());
|
||||
msg.registerIncomingPluginChannel(this, "axiom:request_block_entity", new RequestBlockEntityPacketListener(this));
|
||||
|
||||
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 to 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.getScheduler().scheduleSyncRepeatingTask(this, () -> {
|
||||
HashSet<UUID> newActiveAxiomPlayers = new HashSet<>();
|
||||
|
||||
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
|
||||
if (activeAxiomPlayers.contains(player.getUniqueId())) {
|
||||
if (!player.hasPermission("axiom.*")) {
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
buf.writeBoolean(false);
|
||||
player.sendPluginMessage(this, "axiom:enable", buf.accessByteBufWithCorrectSize());
|
||||
} else {
|
||||
newActiveAxiomPlayers.add(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activeAxiomPlayers.clear();
|
||||
activeAxiomPlayers.addAll(newActiveAxiomPlayers);
|
||||
}, 20, 20);
|
||||
private static AxiomPaper instance;
|
||||
public static Plugin getPlugin() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onFailMove(PlayerFailMoveEvent event) {
|
||||
if (event.getPlayer().hasPermission("axiom.*") &&
|
||||
event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) {
|
||||
event.setAllowed(true);
|
||||
private static final Map<String, AxiomPacketListener> listeners = new ConcurrentHashMap<>();
|
||||
public static AxiomPacketListener getListener(String channel) {
|
||||
return listeners.get(channel);
|
||||
}
|
||||
|
||||
private static final Class<Player> CraftPlayer = Reflection.getClass("org.bukkit.craftbukkit.entity.CraftPlayer");
|
||||
private static final Reflection.Method<Player, ServerPlayer> playerGetHandle = Reflection.getTypedMethod(CraftPlayer, "getHandle", ServerPlayer.class);
|
||||
public static ServerPlayer convert(Player player) {
|
||||
return playerGetHandle.invoke(player);
|
||||
}
|
||||
|
||||
private static final Class<World> CraftWorld = Reflection.getClass("org.bukkit.craftbukkit.CraftWorld");
|
||||
private static final Reflection.Method<World, ServerLevel> worldGetHandle = Reflection.getTypedMethod(CraftWorld, "getHandle", ServerLevel.class);
|
||||
public static ServerLevel convert(World world) {
|
||||
return worldGetHandle.invoke(world);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<Level, LevelChunk> getChunk = Reflection.getTypedMethod(Level.class, LevelChunk.class, int.class, int.class);
|
||||
public static LevelChunk getChunk(Level level, int cx, int cz) {
|
||||
return getChunk.invoke(level, cx, cz);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<BlockPos, BlockPos> of = Reflection.getTypedMethod(BlockPos.class, BlockPos.class, long.class);
|
||||
public static BlockPos convert(long packed) {
|
||||
return of.invoke(null, packed);
|
||||
}
|
||||
|
||||
private static final Reflection.Field<ServerPlayer, ServerGamePacketListenerImpl> serverPlayerConnection = Reflection.getField(ServerPlayer.class, ServerGamePacketListenerImpl.class);
|
||||
public static ServerGamePacketListenerImpl getConnection(ServerPlayer player) {
|
||||
return serverPlayerConnection.get(player);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<ServerLevel, ServerChunkCache> getChunkSource = Reflection.getTypedMethod(ServerLevel.class, ServerChunkCache.class);
|
||||
public static ServerChunkCache getChunkSource(ServerLevel level) {
|
||||
return getChunkSource.invoke(level);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<ServerChunkCache, ThreadedLevelLightEngine> getLightEngine = Reflection.getTypedMethod(ServerChunkCache.class, ThreadedLevelLightEngine.class);
|
||||
public static ThreadedLevelLightEngine getLightEngine(ServerChunkCache chunkSource) {
|
||||
return getLightEngine.invoke(chunkSource);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<ServerPlayerConnection, Void> send = Reflection.getMethod(ServerPlayerConnection.class, Packet.class);
|
||||
public static void sendPacket(ServerPlayer player, Packet<?> packet) {
|
||||
send.invoke(AxiomPaper.getConnection(player), packet);
|
||||
}
|
||||
|
||||
private static final Reflection.Field<ServerChunkCache, ChunkMap> chunkMap = Reflection.getField(ServerChunkCache.class, ChunkMap.class);
|
||||
private static final Reflection.Method<ChunkMap, List> getPlayers = Reflection.getTypedMethod(ChunkMap.class, List.class, ChunkPos.class, boolean.class);
|
||||
public static List<ServerPlayer> getPlayersSeeingChunk(ServerLevel level, ChunkPos pos) {
|
||||
ChunkMap map = chunkMap.get(AxiomPaper.getChunkSource(level));
|
||||
return (List<ServerPlayer>)getPlayers.invoke(map, pos, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
|
||||
try {
|
||||
PlayerFailMoveEvent.class.getName(); //Try load class
|
||||
Bukkit.getPluginManager().registerEvents(new PaperFailMoveListener(), this);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
getLogger().log(java.util.logging.Level.INFO, "Axiom players may move too quickly according to the server. Use a current Paper version or increase the 'moved-too-quickly-multiplier' in spigot.yml.");
|
||||
}
|
||||
|
||||
for(OutChannel channel : OutChannel.values()) {
|
||||
Bukkit.getMessenger().registerOutgoingPluginChannel(this, channel.getChannel());
|
||||
}
|
||||
|
||||
registerInChannel("hello", new HelloPacketListener(new AxiomPlayerManager()));
|
||||
registerInChannel("set_gamemode", new SetGamemodePacketListener());
|
||||
registerInChannel("set_fly_speed", new SetFlySpeedPacketListener());
|
||||
registerInChannel("set_block", new SetBlockPacketListener());
|
||||
registerInChannel("set_hotbar_slot", new SetHotbarSlotPacketListener());
|
||||
registerInChannel("switch_active_hotbar", new SwitchActiveHotbarPacketListener());
|
||||
registerInChannel("teleport", new TeleportPacketListener());
|
||||
registerInChannel("set_editor_views", new SetEditorViewsPacketListener());
|
||||
registerInChannel("request_block_entity", new RequestBlockEntityPacketListener());
|
||||
registerInChannel("set_buffer", new SetBlockBufferPacketListener());
|
||||
}
|
||||
|
||||
private void registerInChannel(String channel, AxiomPacketListener handler) {
|
||||
listeners.put(channel, handler);
|
||||
Bukkit.getMessenger().registerIncomingPluginChannel(this, "axiom:" + channel, (ch, player, message) -> {
|
||||
ByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
if (!player.hasPermission(AxiomConstants.PERMISSION)) {
|
||||
handler.onMissingPerm(player, buf);
|
||||
return;
|
||||
}
|
||||
|
||||
handler.onMessage(player, buf);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
38
src/main/java/com/moulberry/axiom/AxiomPlayerManager.java
Normale Datei
38
src/main/java/com/moulberry/axiom/AxiomPlayerManager.java
Normale Datei
@ -0,0 +1,38 @@
|
||||
package com.moulberry.axiom;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class AxiomPlayerManager implements Listener {
|
||||
|
||||
private final Set<Player> axiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
public AxiomPlayerManager() {
|
||||
Bukkit.getPluginManager().registerEvents(this, AxiomPaper.getPlugin());
|
||||
Bukkit.getScheduler().scheduleSyncRepeatingTask(AxiomPaper.getPlugin(), () -> axiomPlayers.removeIf(player -> {
|
||||
if (!player.hasPermission(AxiomConstants.PERMISSION)) {
|
||||
OutChannel.ENABLE.send(player, new byte[] { 0 });
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}), 20, 20);
|
||||
}
|
||||
|
||||
public void add(Player player) {
|
||||
axiomPlayers.add(player);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
axiomPlayers.remove(event.getPlayer());
|
||||
}
|
||||
|
||||
}
|
32
src/main/java/com/moulberry/axiom/OutChannel.java
Normale Datei
32
src/main/java/com/moulberry/axiom/OutChannel.java
Normale Datei
@ -0,0 +1,32 @@
|
||||
package com.moulberry.axiom;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public enum OutChannel {
|
||||
ENABLE,
|
||||
INITIALIZE_HOTBARS,
|
||||
SET_EDITOR_VIEWS,
|
||||
BLOCK_ENTITIES;
|
||||
|
||||
private final String channel;
|
||||
|
||||
OutChannel() {
|
||||
channel = "axiom:" + name().toLowerCase();
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public void send(Player player, ByteBuf buf) {
|
||||
// trustin https://stackoverflow.com/a/19309312 CC BY-SA 3.0
|
||||
byte[] array = new byte[buf.readableBytes()];
|
||||
buf.getBytes(buf.readerIndex(), array);
|
||||
send(player, array);
|
||||
}
|
||||
|
||||
public void send(Player player, byte[] array) {
|
||||
player.sendPluginMessage(AxiomPaper.getPlugin(), channel, array);
|
||||
}
|
||||
}
|
169
src/main/java/com/moulberry/axiom/Reflection.java
Normale Datei
169
src/main/java/com/moulberry/axiom/Reflection.java
Normale Datei
@ -0,0 +1,169 @@
|
||||
package com.moulberry.axiom;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Reflection {
|
||||
private Reflection() {}
|
||||
|
||||
public interface Field<T, R> {
|
||||
R get(T target);
|
||||
void set(T target, R value);
|
||||
}
|
||||
|
||||
public static <T, R> Field<T, R> getField(Class<T> target, Class<R> fieldType, Class<?>... parameters) {
|
||||
return getField(target, fieldType, 0, parameters);
|
||||
}
|
||||
|
||||
public static <T, R> Field<T, R> getField(Class<T> target, Class<R> fieldType, int index, Class<?>... parameters) {
|
||||
for (final java.lang.reflect.Field field : target.getDeclaredFields()) {
|
||||
if(matching(field, fieldType, parameters) && index-- <= 0) {
|
||||
field.setAccessible(true);
|
||||
|
||||
return new Field<>() {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public R get(T target) {
|
||||
try {
|
||||
return (R) field.get(target);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalArgumentException("Cannot access reflection.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T target, R value) {
|
||||
try {
|
||||
field.set(target, value);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalArgumentException("Cannot access reflection.", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (target.getSuperclass() != null) {
|
||||
try {
|
||||
return getField((Class<T>) target.getSuperclass(), fieldType, index, parameters);
|
||||
} catch (ClassCastException e) {}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
|
||||
}
|
||||
|
||||
private static <T> boolean matching(java.lang.reflect.Field field, Class<T> fieldType, Class<?>... parameters) {
|
||||
if(!fieldType.isAssignableFrom(field.getType()))
|
||||
return false;
|
||||
|
||||
if(parameters.length > 0) {
|
||||
Type[] arguments = ((ParameterizedType)field.getGenericType()).getActualTypeArguments();
|
||||
|
||||
for(int i = 0; i < parameters.length; i++) {
|
||||
if(arguments[i] instanceof ParameterizedType ? ((ParameterizedType) arguments[i]).getRawType() != parameters[i] : arguments[i] != parameters[i])
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public interface Method<T, R> {
|
||||
R invoke(T target, Object... arguments);
|
||||
}
|
||||
|
||||
public static <T> Method<T, Void> getMethod(Class<T> clazz, Class<?>... params) {
|
||||
return getTypedMethod(clazz, null, Void.TYPE, 0, params);
|
||||
}
|
||||
|
||||
public static <T> Method<T, Void> getMethod(Class<T> clazz, int index, Class<?>... params) {
|
||||
return getTypedMethod(clazz, null, Void.TYPE, index, params);
|
||||
}
|
||||
|
||||
public static <T> Method<T, Void> getMethod(Class<T> clazz, String methodName, Class<?>... params) {
|
||||
return getTypedMethod(clazz, methodName, Void.TYPE, 0, params);
|
||||
}
|
||||
|
||||
public static <T, R> Method<T, R> getTypedMethod(Class<T> clazz, Class<R> returnType, Class<?>... params) {
|
||||
return getTypedMethod(clazz, null, returnType, 0, params);
|
||||
}
|
||||
|
||||
public static <T, R> Method<T, R> getTypedMethod(Class<T> clazz, Class<R> returnType, int index, Class<?>... params) {
|
||||
return getTypedMethod(clazz, null, returnType, index, params);
|
||||
}
|
||||
|
||||
public static <T, R> Method<T, R> getTypedMethod(Class<T> clazz, String methodName, Class<R> returnType, Class<?>... params) {
|
||||
return getTypedMethod(clazz, methodName, returnType, 0, params);
|
||||
}
|
||||
|
||||
private static <T, R> Method<T, R> getTypedMethod(Class<T> clazz, String methodName, Class<R> returnType, int index, Class<?>... params) {
|
||||
for (final java.lang.reflect.Method method : clazz.getDeclaredMethods()) {
|
||||
if ((methodName == null || method.getName().equals(methodName))
|
||||
&& (returnType == null || method.getReturnType().equals(returnType))
|
||||
&& Arrays.equals(method.getParameterTypes(), params)
|
||||
&& index-- <= 0) {
|
||||
method.setAccessible(true);
|
||||
|
||||
return (target, arguments) -> {
|
||||
try {
|
||||
return (R) method.invoke(target, arguments);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Cannot invoke method " + method, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (clazz.getSuperclass() != null) {
|
||||
try {
|
||||
return getTypedMethod((Class<T>) clazz.getSuperclass(), methodName, returnType, index, params);
|
||||
} catch (ClassCastException e) {}
|
||||
}
|
||||
|
||||
throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params)));
|
||||
}
|
||||
|
||||
|
||||
public interface Constructor<T> {
|
||||
T newInstance(Object... arguments);
|
||||
}
|
||||
|
||||
public static <T> Constructor<T> getConstructor(Class<T> clazz, Class<?>... params) {
|
||||
try {
|
||||
java.lang.reflect.Constructor<T> constructor = clazz.getDeclaredConstructor(params);
|
||||
constructor.setAccessible(true);
|
||||
|
||||
return arguments -> {
|
||||
try {
|
||||
return constructor.newInstance(arguments);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new IllegalArgumentException("Cannot invoke constructor " + constructor, e);
|
||||
}
|
||||
};
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalStateException("Cannot find matching constructor");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final String ORG_BUKKIT_CRAFTBUKKIT = Bukkit.getServer().getClass().getPackage().getName();
|
||||
public static final int VERSION; // Format: 2 digit minor version, 2 digit CraftBukkit revision: eg. 2001 for v1_20_R1
|
||||
static {
|
||||
String[] version = ORG_BUKKIT_CRAFTBUKKIT.substring(ORG_BUKKIT_CRAFTBUKKIT.lastIndexOf('.')).split("_");
|
||||
VERSION = Integer.parseInt(version[1]) * 100 + Integer.parseInt(version[2].substring(1));
|
||||
}
|
||||
|
||||
public static <T> Class<T> getClass(String name) {
|
||||
if(name.startsWith("org.bukkit.craftbukkit"))
|
||||
name = ORG_BUKKIT_CRAFTBUKKIT + name.substring(22);
|
||||
|
||||
try {
|
||||
return (Class<T>) Class.forName(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException("Cannot find " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,15 +1,12 @@
|
||||
package com.moulberry.axiom;
|
||||
|
||||
import com.moulberry.axiom.buffer.MojBuf;
|
||||
import com.moulberry.axiom.persistence.UUIDDataType;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
@ -17,73 +14,76 @@ import java.util.UUID;
|
||||
|
||||
public final class View {
|
||||
|
||||
public String name;
|
||||
public final UUID uuid;
|
||||
public boolean pinLevel = false;
|
||||
public boolean pinLocation = false;
|
||||
private ResourceKey<Level> level = null;
|
||||
private Vec3 position = null;
|
||||
private float yaw;
|
||||
private float pitch;
|
||||
private final String name;
|
||||
private final UUID uuid;
|
||||
private boolean pinLevel = false;
|
||||
private boolean pinLocation = false;
|
||||
private World level = null;
|
||||
private Location location = null;
|
||||
|
||||
public View(String name, UUID uuid) {
|
||||
this.name = name;
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public void write(FriendlyByteBuf byteBuf) {
|
||||
byteBuf.writeUtf(this.name, 64);
|
||||
byteBuf.writeUUID(this.uuid);
|
||||
public void write(ByteBuf buf) {
|
||||
MojBuf.writeUtf(buf, this.name, 64);
|
||||
MojBuf.writeUUID(buf, this.uuid);
|
||||
|
||||
byteBuf.writeBoolean(this.pinLevel);
|
||||
buf.writeBoolean(this.pinLevel);
|
||||
if (this.pinLevel && this.level != null) {
|
||||
byteBuf.writeBoolean(true);
|
||||
byteBuf.writeResourceKey(this.level);
|
||||
buf.writeBoolean(true);
|
||||
MojBuf.writeKey(buf, this.level.getKey());
|
||||
} else {
|
||||
byteBuf.writeBoolean(false);
|
||||
buf.writeBoolean(false);
|
||||
}
|
||||
|
||||
byteBuf.writeBoolean(this.pinLocation);
|
||||
if (this.position != null) {
|
||||
byteBuf.writeBoolean(true);
|
||||
byteBuf.writeDouble(this.position.x);
|
||||
byteBuf.writeDouble(this.position.y);
|
||||
byteBuf.writeDouble(this.position.z);
|
||||
byteBuf.writeFloat(this.yaw);
|
||||
byteBuf.writeFloat(this.pitch);
|
||||
buf.writeBoolean(this.pinLocation);
|
||||
if (this.location != null) {
|
||||
buf.writeBoolean(true);
|
||||
buf.writeDouble(this.location.getX());
|
||||
buf.writeDouble(this.location.getY());
|
||||
buf.writeDouble(this.location.getZ());
|
||||
buf.writeFloat(this.location.getYaw());
|
||||
buf.writeFloat(this.location.getPitch());
|
||||
} else {
|
||||
byteBuf.writeBoolean(false);
|
||||
buf.writeBoolean(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static View read(FriendlyByteBuf byteBuf) {
|
||||
View view = new View(byteBuf.readUtf(64), byteBuf.readUUID());
|
||||
public static View read(ByteBuf buf) {
|
||||
View view = new View(MojBuf.readUtf(buf, 64), MojBuf.readUUID(buf));
|
||||
|
||||
view.pinLevel = byteBuf.readBoolean();
|
||||
if (byteBuf.readBoolean()) {
|
||||
view.level = byteBuf.readResourceKey(Registries.DIMENSION);
|
||||
view.pinLevel = buf.readBoolean();
|
||||
if (buf.readBoolean()) {
|
||||
view.level = Bukkit.getWorld(MojBuf.readKey(buf));
|
||||
}
|
||||
|
||||
view.pinLocation = byteBuf.readBoolean();
|
||||
if (byteBuf.readBoolean()) {
|
||||
view.position = new Vec3(byteBuf.readDouble(), byteBuf.readDouble(), byteBuf.readDouble());
|
||||
view.yaw = byteBuf.readFloat();
|
||||
view.pitch = byteBuf.readFloat();
|
||||
view.pinLocation = buf.readBoolean();
|
||||
if (buf.readBoolean()) {
|
||||
view.location = new Location(view.level,
|
||||
buf.readDouble(),
|
||||
buf.readDouble(),
|
||||
buf.readDouble(),
|
||||
buf.readFloat(),
|
||||
buf.readFloat()
|
||||
);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private static final NamespacedKey NAME_KEY = new NamespacedKey("axiom", "view_name");
|
||||
private static final NamespacedKey UUID_KEY = new NamespacedKey("axiom", "view_uuid");
|
||||
private static final NamespacedKey PIN_LEVEL_KEY = new NamespacedKey("axiom", "view_pin_level");
|
||||
private static final NamespacedKey LEVEL_KEY = new NamespacedKey("axiom", "view_level");
|
||||
private static final NamespacedKey PIN_LOCATION_KEY = new NamespacedKey("axiom", "view_pin_location");
|
||||
private static final NamespacedKey X_KEY = new NamespacedKey("axiom", "view_x");
|
||||
private static final NamespacedKey Y_KEY = new NamespacedKey("axiom", "view_y");
|
||||
private static final NamespacedKey Z_KEY = new NamespacedKey("axiom", "view_z");
|
||||
private static final NamespacedKey YAW_KEY = new NamespacedKey("axiom", "view_yaw");
|
||||
private static final NamespacedKey PITCH_KEY = new NamespacedKey("axiom", "view_pitch");
|
||||
|
||||
private static final NamespacedKey NAME_KEY = AxiomConstants.axiomKey("view_name");
|
||||
private static final NamespacedKey UUID_KEY = AxiomConstants.axiomKey("view_uuid");
|
||||
private static final NamespacedKey PIN_LEVEL_KEY = AxiomConstants.axiomKey("view_pin_level");
|
||||
private static final NamespacedKey LEVEL_KEY = AxiomConstants.axiomKey("view_level");
|
||||
private static final NamespacedKey PIN_LOCATION_KEY = AxiomConstants.axiomKey("view_pin_location");
|
||||
private static final NamespacedKey X_KEY = AxiomConstants.axiomKey("view_x");
|
||||
private static final NamespacedKey Y_KEY = AxiomConstants.axiomKey("view_y");
|
||||
private static final NamespacedKey Z_KEY = AxiomConstants.axiomKey("view_z");
|
||||
private static final NamespacedKey YAW_KEY = AxiomConstants.axiomKey("view_yaw");
|
||||
private static final NamespacedKey PITCH_KEY = AxiomConstants.axiomKey("view_pitch");
|
||||
|
||||
public void save(PersistentDataContainer container) {
|
||||
container.set(NAME_KEY, PersistentDataType.STRING, this.name);
|
||||
@ -91,16 +91,16 @@ public final class View {
|
||||
|
||||
container.set(PIN_LEVEL_KEY, PersistentDataType.BOOLEAN, this.pinLevel);
|
||||
if (this.pinLevel && this.level != null) {
|
||||
container.set(LEVEL_KEY, PersistentDataType.STRING, this.level.location().toString());
|
||||
container.set(LEVEL_KEY, PersistentDataType.STRING, this.level.getKey().asString());
|
||||
}
|
||||
|
||||
container.set(PIN_LOCATION_KEY, PersistentDataType.BOOLEAN, this.pinLocation);
|
||||
if (this.position != null) {
|
||||
container.set(X_KEY, PersistentDataType.DOUBLE, this.position.x);
|
||||
container.set(Y_KEY, PersistentDataType.DOUBLE, this.position.y);
|
||||
container.set(Z_KEY, PersistentDataType.DOUBLE, this.position.z);
|
||||
container.set(YAW_KEY, PersistentDataType.FLOAT, this.yaw);
|
||||
container.set(PITCH_KEY, PersistentDataType.FLOAT, this.pitch);
|
||||
if (this.location != null) {
|
||||
container.set(X_KEY, PersistentDataType.DOUBLE, this.location.getX());
|
||||
container.set(Y_KEY, PersistentDataType.DOUBLE, this.location.getY());
|
||||
container.set(Z_KEY, PersistentDataType.DOUBLE, this.location.getZ());
|
||||
container.set(YAW_KEY, PersistentDataType.FLOAT, this.location.getYaw());
|
||||
container.set(PITCH_KEY, PersistentDataType.FLOAT, this.location.getPitch());
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,17 +113,18 @@ public final class View {
|
||||
view.pinLevel = tag.getOrDefault(PIN_LEVEL_KEY, PersistentDataType.BOOLEAN, false);
|
||||
if (tag.has(LEVEL_KEY)) {
|
||||
String level = tag.get(LEVEL_KEY, PersistentDataType.STRING);
|
||||
view.level = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(level));
|
||||
view.level = Bukkit.getWorld(NamespacedKey.fromString(level));
|
||||
}
|
||||
|
||||
view.pinLocation = tag.getOrDefault(PIN_LOCATION_KEY, PersistentDataType.BOOLEAN, false);
|
||||
if (tag.has(X_KEY) && tag.has(Y_KEY) && tag.has(Z_KEY)) {
|
||||
double x = tag.getOrDefault(X_KEY, PersistentDataType.DOUBLE, 0.0);
|
||||
double y = tag.getOrDefault(Y_KEY, PersistentDataType.DOUBLE, 0.0);
|
||||
double z = tag.getOrDefault(Z_KEY, PersistentDataType.DOUBLE, 0.0);
|
||||
view.position = new Vec3(x, y, z);
|
||||
view.yaw = tag.getOrDefault(YAW_KEY, PersistentDataType.FLOAT, 0.0f);
|
||||
view.pitch = tag.getOrDefault(PITCH_KEY, PersistentDataType.FLOAT, 0.0f);
|
||||
view.location = new Location(view.level,
|
||||
tag.getOrDefault(X_KEY, PersistentDataType.DOUBLE, 0.0),
|
||||
tag.getOrDefault(Y_KEY, PersistentDataType.DOUBLE, 0.0),
|
||||
tag.getOrDefault(Z_KEY, PersistentDataType.DOUBLE, 0.0),
|
||||
tag.getOrDefault(YAW_KEY, PersistentDataType.FLOAT, 0.0f),
|
||||
tag.getOrDefault(PITCH_KEY, PersistentDataType.FLOAT, 0.0f)
|
||||
);
|
||||
}
|
||||
|
||||
return view;
|
||||
|
@ -1,88 +1,26 @@
|
||||
package com.moulberry.axiom.buffer;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ByteMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.bukkit.block.Biome;
|
||||
|
||||
public class BiomeBuffer {
|
||||
|
||||
private final Position2ByteMap map;
|
||||
private final ResourceKey<Biome>[] palette;
|
||||
private final Object2ByteMap<ResourceKey<Biome>> paletteReverse;
|
||||
private int paletteSize = 0;
|
||||
private final Biome[] palette;
|
||||
|
||||
public BiomeBuffer() {
|
||||
this.map = new Position2ByteMap();
|
||||
this.palette = new ResourceKey[255];
|
||||
this.paletteReverse = new Object2ByteOpenHashMap<>();
|
||||
}
|
||||
|
||||
private BiomeBuffer(Position2ByteMap map, ResourceKey<Biome>[] palette, Object2ByteMap<ResourceKey<Biome>> paletteReverse) {
|
||||
this.map = map;
|
||||
this.palette = palette;
|
||||
this.paletteReverse = paletteReverse;
|
||||
this.paletteSize = this.paletteReverse.size();
|
||||
}
|
||||
|
||||
public void save(FriendlyByteBuf friendlyByteBuf) {
|
||||
friendlyByteBuf.writeByte(this.paletteSize);
|
||||
for (int i = 0; i < this.paletteSize; i++) {
|
||||
friendlyByteBuf.writeResourceKey(this.palette[i]);
|
||||
public BiomeBuffer(ByteBuf buf) {
|
||||
palette = new Biome[buf.readByte()];
|
||||
for (int i = 0; i < palette.length; i++) {
|
||||
palette[i] = Biome.valueOf(MojBuf.readKey(buf).getKey().toUpperCase());
|
||||
}
|
||||
this.map.save(friendlyByteBuf);
|
||||
|
||||
map = Position2ByteMap.load(buf);
|
||||
}
|
||||
|
||||
public static BiomeBuffer load(FriendlyByteBuf friendlyByteBuf) {
|
||||
int paletteSize = friendlyByteBuf.readByte();
|
||||
ResourceKey<Biome>[] palette = new ResourceKey[255];
|
||||
Object2ByteMap<ResourceKey<Biome>> paletteReverse = new Object2ByteOpenHashMap<>();
|
||||
for (int i = 0; i < paletteSize; i++) {
|
||||
ResourceKey<Biome> key = friendlyByteBuf.readResourceKey(Registries.BIOME);
|
||||
palette[i] = key;
|
||||
paletteReverse.put(key, (byte)(i+1));
|
||||
}
|
||||
Position2ByteMap map = Position2ByteMap.load(friendlyByteBuf);
|
||||
return new BiomeBuffer(map, palette, paletteReverse);
|
||||
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.map.clear();
|
||||
}
|
||||
|
||||
public void forEachEntry(PositionConsumer<ResourceKey<Biome>> consumer) {
|
||||
public void forEachEntry(PositionConsumer<Biome> consumer) {
|
||||
this.map.forEachEntry((x, y, z, v) -> {
|
||||
if (v != 0) consumer.accept(x, y, z, this.palette[(v & 0xFF) - 1]);
|
||||
});
|
||||
}
|
||||
|
||||
public ResourceKey<Biome> get(int quartX, int quartY, int quartZ) {
|
||||
int index = this.map.get(quartX, quartY, quartZ) & 0xFF;
|
||||
if (index == 0) return null;
|
||||
return this.palette[index - 1];
|
||||
}
|
||||
|
||||
private int getPaletteIndex(ResourceKey<Biome> biome) {
|
||||
int index = this.paletteReverse.getOrDefault(biome, (byte) 0) & 0xFF;
|
||||
if (index != 0) return index;
|
||||
|
||||
index = this.paletteSize;
|
||||
if (index >= this.palette.length) {
|
||||
throw new UnsupportedOperationException("Too many biomes! :(");
|
||||
}
|
||||
|
||||
this.palette[index] = biome;
|
||||
this.paletteReverse.put(biome, (byte)(index + 1));
|
||||
|
||||
this.paletteSize += 1;
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
public void set(int quartX, int quartY, int quartZ, ResourceKey<Biome> biome) {
|
||||
this.map.put(quartX, quartY, quartZ, (byte) this.getPaletteIndex(biome));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,180 +0,0 @@
|
||||
package com.moulberry.axiom.buffer;
|
||||
|
||||
import com.moulberry.axiom.AxiomConstants;
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
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.PalettedContainer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
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 = AxiomConstants.MIN_POSITION_LONG;
|
||||
private final Long2ObjectMap<Short2ObjectMap<CompressedBlockEntity>> blockEntities = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
public BlockBuffer() {
|
||||
this.values = new Long2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
public BlockBuffer(Long2ObjectMap<PalettedContainer<BlockState>> values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public void save(FriendlyByteBuf friendlyByteBuf) {
|
||||
for (Long2ObjectMap.Entry<PalettedContainer<BlockState>> entry : this.entrySet()) {
|
||||
friendlyByteBuf.writeLong(entry.getLongKey());
|
||||
entry.getValue().write(friendlyByteBuf);
|
||||
|
||||
Short2ObjectMap<CompressedBlockEntity> blockEntities = this.blockEntities.get(entry.getLongKey());
|
||||
if (blockEntities != null) {
|
||||
friendlyByteBuf.writeVarInt(blockEntities.size());
|
||||
for (Short2ObjectMap.Entry<CompressedBlockEntity> entry2 : blockEntities.short2ObjectEntrySet()) {
|
||||
friendlyByteBuf.writeShort(entry2.getShortKey());
|
||||
entry2.getValue().write(friendlyByteBuf);
|
||||
}
|
||||
} else {
|
||||
friendlyByteBuf.writeVarInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG);
|
||||
}
|
||||
|
||||
public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf) {
|
||||
BlockBuffer buffer = new BlockBuffer();
|
||||
|
||||
while (true) {
|
||||
long index = friendlyByteBuf.readLong();
|
||||
if (index == AxiomConstants.MIN_POSITION_LONG) break;
|
||||
|
||||
PalettedContainer<BlockState> palettedContainer = buffer.getOrCreateSection(index);
|
||||
palettedContainer.read(friendlyByteBuf);
|
||||
|
||||
int blockEntitySize = Math.min(4096, friendlyByteBuf.readVarInt());
|
||||
if (blockEntitySize > 0) {
|
||||
Short2ObjectMap<CompressedBlockEntity> map = new Short2ObjectOpenHashMap<>(blockEntitySize);
|
||||
for (int i = 0; i < blockEntitySize; i++) {
|
||||
short offset = friendlyByteBuf.readShort();
|
||||
CompressedBlockEntity blockEntity = CompressedBlockEntity.read(friendlyByteBuf);
|
||||
map.put(offset, blockEntity);
|
||||
}
|
||||
buffer.blockEntities.put(index, map);
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.last = null;
|
||||
this.lastId = AxiomConstants.MIN_POSITION_LONG;
|
||||
this.values.clear();
|
||||
}
|
||||
|
||||
public void putBlockEntity(int x, int y, int z, CompressedBlockEntity blockEntity) {
|
||||
long cpos = BlockPos.asLong(x >> 4, y >> 4, z >> 4);
|
||||
Short2ObjectMap<CompressedBlockEntity> chunkMap = this.blockEntities.computeIfAbsent(cpos, k -> new Short2ObjectOpenHashMap<>());
|
||||
|
||||
int key = (x & 0xF) | ((y & 0xF) << 4) | ((z & 0xF) << 8);
|
||||
chunkMap.put((short)key, blockEntity);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CompressedBlockEntity getBlockEntity(int x, int y, int z) {
|
||||
long cpos = BlockPos.asLong(x >> 4, y >> 4, z >> 4);
|
||||
Short2ObjectMap<CompressedBlockEntity> chunkMap = this.blockEntities.get(cpos);
|
||||
|
||||
if (chunkMap == null) return null;
|
||||
|
||||
int key = (x & 0xF) | ((y & 0xF) << 4) | ((z & 0xF) << 8);
|
||||
return chunkMap.get((short)key);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Short2ObjectMap<CompressedBlockEntity> getBlockEntityChunkMap(long cpos) {
|
||||
return this.blockEntities.get(cpos);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -3,64 +3,57 @@ package com.moulberry.axiom.buffer;
|
||||
import com.github.luben.zstd.Zstd;
|
||||
import com.github.luben.zstd.ZstdDictCompress;
|
||||
import com.github.luben.zstd.ZstdDictDecompress;
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Objects;
|
||||
|
||||
public record CompressedBlockEntity(int originalSize, byte compressionDict, byte[] compressed) {
|
||||
|
||||
private static ZstdDictCompress zstdDictCompress = null;
|
||||
private static ZstdDictDecompress zstdDictDecompress = null;
|
||||
|
||||
public static void initialize(AxiomPaper plugin) {
|
||||
try (InputStream is = Objects.requireNonNull(plugin.getResource("zstd_dictionaries/block_entities_v1.dict"))) {
|
||||
private static final ZstdDictCompress zstdDictCompress;
|
||||
private static final ZstdDictDecompress zstdDictDecompress;
|
||||
static {
|
||||
try (InputStream is = Objects.requireNonNull(CompressedBlockEntity.class.getClassLoader().getResourceAsStream("zstd_dictionaries/block_entities_v1.dict"))) {
|
||||
byte[] bytes = is.readAllBytes();
|
||||
zstdDictCompress = new ZstdDictCompress(bytes, Zstd.defaultCompressionLevel());
|
||||
zstdDictDecompress = new ZstdDictDecompress(bytes);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static CompressedBlockEntity compress(CompoundTag tag, ByteArrayOutputStream baos) {
|
||||
try {
|
||||
baos.reset();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
NbtIo.write(tag, dos);
|
||||
byte[] uncompressed = baos.toByteArray();
|
||||
byte[] compressed = Zstd.compress(uncompressed, zstdDictCompress);
|
||||
return new CompressedBlockEntity(uncompressed.length, (byte) 0, compressed);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Reflection.Method<NbtIo, Void> write = Reflection.getMethod(NbtIo.class, CompoundTag.class, DataOutput.class);
|
||||
public static CompressedBlockEntity compress(CompoundTag tag, ByteArrayOutputStream baos) {
|
||||
baos.reset();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
write.invoke(null, tag, dos);
|
||||
byte[] uncompressed = baos.toByteArray();
|
||||
byte[] compressed = Zstd.compress(uncompressed, zstdDictCompress);
|
||||
return new CompressedBlockEntity(uncompressed.length, (byte) 0, compressed);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<NbtIo, CompoundTag> read = Reflection.getTypedMethod(NbtIo.class, CompoundTag.class, DataInput.class);
|
||||
public CompoundTag decompress() {
|
||||
if (this.compressionDict != 0) throw new UnsupportedOperationException("Unknown compression dict: " + this.compressionDict);
|
||||
|
||||
try {
|
||||
byte[] nbt = Zstd.decompress(this.compressed, zstdDictDecompress, this.originalSize);
|
||||
return NbtIo.read(new DataInputStream(new ByteArrayInputStream(nbt)));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
byte[] nbt = Zstd.decompress(this.compressed, zstdDictDecompress, this.originalSize);
|
||||
return read.invoke(null, new DataInputStream(new ByteArrayInputStream(nbt)));
|
||||
}
|
||||
|
||||
public static CompressedBlockEntity read(FriendlyByteBuf friendlyByteBuf) {
|
||||
int originalSize = friendlyByteBuf.readVarInt();
|
||||
byte compressionDict = friendlyByteBuf.readByte();
|
||||
byte[] compressed = friendlyByteBuf.readByteArray();
|
||||
public static CompressedBlockEntity read(ByteBuf buf) {
|
||||
int originalSize = MojBuf.readVarInt(buf);
|
||||
byte compressionDict = buf.readByte();
|
||||
byte[] compressed = MojBuf.readByteArray(buf);
|
||||
return new CompressedBlockEntity(originalSize, compressionDict, compressed);
|
||||
}
|
||||
|
||||
public void write(FriendlyByteBuf friendlyByteBuf) {
|
||||
friendlyByteBuf.writeVarInt(this.originalSize);
|
||||
friendlyByteBuf.writeByte(this.compressionDict);
|
||||
friendlyByteBuf.writeByteArray(this.compressed);
|
||||
public void write(ByteBuf buf) {
|
||||
MojBuf.writeVarInt(buf, this.originalSize);
|
||||
buf.writeByte(this.compressionDict);
|
||||
MojBuf.writeByteArray(buf, this.compressed);
|
||||
}
|
||||
|
||||
}
|
||||
|
116
src/main/java/com/moulberry/axiom/buffer/MojBuf.java
Normale Datei
116
src/main/java/com/moulberry/axiom/buffer/MojBuf.java
Normale Datei
@ -0,0 +1,116 @@
|
||||
package com.moulberry.axiom.buffer;
|
||||
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class MojBuf {
|
||||
private MojBuf() {}
|
||||
|
||||
public static ByteBuf unpooled() {
|
||||
return new FriendlyByteBuf(Unpooled.buffer());
|
||||
}
|
||||
|
||||
private static final Reflection.Method<FriendlyByteBuf, String> readUtf = Reflection.getTypedMethod(FriendlyByteBuf.class, String.class, int.class);
|
||||
public static String readUtf(ByteBuf buf, int maxLength) {
|
||||
return readUtf.invoke((FriendlyByteBuf) buf, maxLength);
|
||||
}
|
||||
|
||||
public static String readUtf(ByteBuf buf) {
|
||||
return readUtf(buf, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static NamespacedKey readKey(ByteBuf buf) {
|
||||
return NamespacedKey.fromString(readUtf(buf));
|
||||
}
|
||||
|
||||
private static final Reflection.Method<FriendlyByteBuf, FriendlyByteBuf> writeUtf = Reflection.getTypedMethod(FriendlyByteBuf.class, FriendlyByteBuf.class, String.class, int.class);
|
||||
public static void writeUtf(ByteBuf buf, String string, int maxLength) {
|
||||
writeUtf.invoke((FriendlyByteBuf) buf, string, maxLength);
|
||||
}
|
||||
|
||||
public static void writeUtf(ByteBuf buf, String string) {
|
||||
writeUtf(buf, string, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static void writeKey(ByteBuf buf, NamespacedKey key) {
|
||||
writeUtf(buf, key.asString());
|
||||
}
|
||||
|
||||
// is the first method returning an int with no arguments in 1.20.1
|
||||
private static final Reflection.Method<FriendlyByteBuf, Integer> readVarInt = Reflection.getTypedMethod(FriendlyByteBuf.class, int.class);
|
||||
public static int readVarInt(ByteBuf buf) {
|
||||
return readVarInt.invoke((FriendlyByteBuf) buf);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<FriendlyByteBuf, FriendlyByteBuf> writeVarInt = Reflection.getTypedMethod(FriendlyByteBuf.class, FriendlyByteBuf.class, int.class);
|
||||
public static void writeVarInt(ByteBuf buf, int value) {
|
||||
writeVarInt.invoke((FriendlyByteBuf) buf, value);
|
||||
}
|
||||
|
||||
|
||||
private static final Reflection.Method<FriendlyByteBuf, CompoundTag> readNbt = Reflection.getTypedMethod(FriendlyByteBuf.class, CompoundTag.class);
|
||||
public static CompoundTag readNbt(ByteBuf buf) {
|
||||
return readNbt.invoke((FriendlyByteBuf) buf);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<FriendlyByteBuf, UUID> readUUID = Reflection.getTypedMethod(FriendlyByteBuf.class, UUID.class);
|
||||
public static UUID readUUID(ByteBuf buf) {
|
||||
return readUUID.invoke((FriendlyByteBuf) buf);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<FriendlyByteBuf, FriendlyByteBuf> writeUUID = Reflection.getTypedMethod(FriendlyByteBuf.class, FriendlyByteBuf.class, UUID.class);
|
||||
public static void writeUUID(ByteBuf buf, UUID uuid) {
|
||||
writeUUID.invoke((FriendlyByteBuf) buf, uuid);
|
||||
}
|
||||
|
||||
private static final Class<ItemStack> CraftItemStack = Reflection.getClass("org.bukkit.craftbukkit.inventory.CraftItemStack");
|
||||
private static final Reflection.Method<ItemStack, ItemStack> asCraftMirror = Reflection.getTypedMethod(CraftItemStack, "asCraftMirror", CraftItemStack, net.minecraft.world.item.ItemStack.class);
|
||||
public static ItemStack toBukkit(net.minecraft.world.item.ItemStack stack) {
|
||||
return asCraftMirror.invoke(null, stack);
|
||||
}
|
||||
private static final Reflection.Method<ItemStack, net.minecraft.world.item.ItemStack> asNMSCopy = Reflection.getTypedMethod(CraftItemStack, net.minecraft.world.item.ItemStack.class, ItemStack.class);
|
||||
public static net.minecraft.world.item.ItemStack toMojang(ItemStack stack) {
|
||||
return asNMSCopy.invoke(null, stack);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<FriendlyByteBuf, net.minecraft.world.item.ItemStack> readItem = Reflection.getTypedMethod(FriendlyByteBuf.class, net.minecraft.world.item.ItemStack.class);
|
||||
public static ItemStack readItem(ByteBuf buf) {
|
||||
return toBukkit(readItem.invoke((FriendlyByteBuf) buf));
|
||||
}
|
||||
|
||||
private static final Reflection.Method<FriendlyByteBuf, FriendlyByteBuf> writeItem = Reflection.getTypedMethod(FriendlyByteBuf.class, FriendlyByteBuf.class, net.minecraft.world.item.ItemStack.class);
|
||||
public static void writeItem(ByteBuf buf, ItemStack stack) {
|
||||
writeItem.invoke((FriendlyByteBuf) buf, toMojang(stack));
|
||||
}
|
||||
|
||||
public static byte[] readByteArray(ByteBuf buf) {
|
||||
byte[] array = new byte[readVarInt(buf)];
|
||||
buf.readBytes(array);
|
||||
return array;
|
||||
}
|
||||
|
||||
public static void writeByteArray(ByteBuf buf, byte[] array) {
|
||||
writeVarInt(buf, array.length);
|
||||
buf.writeBytes(array);
|
||||
}
|
||||
|
||||
public static <T> List<T> readList(ByteBuf buf, Function<ByteBuf, T> reader) {
|
||||
int length = readVarInt(buf);
|
||||
|
||||
List<T> list = new ArrayList<>(length);
|
||||
for(int i = 0; i < length; i++)
|
||||
list.add(reader.apply(buf));
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
@ -1,14 +1,11 @@
|
||||
package com.moulberry.axiom.buffer;
|
||||
|
||||
import com.moulberry.axiom.AxiomConstants;
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
public class Position2ByteMap {
|
||||
|
||||
@ -18,122 +15,35 @@ public class Position2ByteMap {
|
||||
}
|
||||
|
||||
private final byte defaultValue;
|
||||
private final LongFunction<byte[]> defaultFunction;
|
||||
private final Long2ObjectMap<byte[]> map = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
private long lastChunkPos = AxiomConstants.MIN_POSITION_LONG;
|
||||
private byte[] lastChunk = null;
|
||||
|
||||
public Position2ByteMap() {
|
||||
this((byte) 0);
|
||||
}
|
||||
|
||||
public Position2ByteMap(byte defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
|
||||
if (defaultValue == 0) {
|
||||
this.defaultFunction = k -> new byte[16*16*16];
|
||||
} else {
|
||||
this.defaultFunction = k -> {
|
||||
byte[] array = new byte[16*16*16];
|
||||
Arrays.fill(array, defaultValue);
|
||||
return array;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void save(FriendlyByteBuf friendlyByteBuf) {
|
||||
friendlyByteBuf.writeByte(this.defaultValue);
|
||||
for (Long2ObjectMap.Entry<byte[]> entry : this.map.long2ObjectEntrySet()) {
|
||||
friendlyByteBuf.writeLong(entry.getLongKey());
|
||||
friendlyByteBuf.writeBytes(entry.getValue());
|
||||
}
|
||||
friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG);
|
||||
}
|
||||
|
||||
public static Position2ByteMap load(FriendlyByteBuf friendlyByteBuf) {
|
||||
Position2ByteMap map = new Position2ByteMap(friendlyByteBuf.readByte());
|
||||
public static Position2ByteMap load(ByteBuf byteBuf) {
|
||||
Position2ByteMap map = new Position2ByteMap(byteBuf.readByte());
|
||||
|
||||
while (true) {
|
||||
long pos = friendlyByteBuf.readLong();
|
||||
long pos = byteBuf.readLong();
|
||||
if (pos == AxiomConstants.MIN_POSITION_LONG) break;
|
||||
|
||||
byte[] bytes = new byte[16*16*16];
|
||||
friendlyByteBuf.readBytes(bytes);
|
||||
byteBuf.readBytes(bytes);
|
||||
map.map.put(pos, bytes);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.map.clear();
|
||||
this.lastChunkPos = AxiomConstants.MIN_POSITION_LONG;
|
||||
this.lastChunk = null;
|
||||
}
|
||||
|
||||
public byte get(int x, int y, int z) {
|
||||
int xC = x >> 4;
|
||||
int yC = y >> 4;
|
||||
int zC = z >> 4;
|
||||
|
||||
byte[] array = this.getChunk(xC, yC, zC);
|
||||
if (array == null) return this.defaultValue;
|
||||
|
||||
return array[(x&15) + (y&15)*16 + (z&15)*16*16];
|
||||
}
|
||||
|
||||
public void put(int x, int y, int z, byte v) {
|
||||
int xC = x >> 4;
|
||||
int yC = y >> 4;
|
||||
int zC = z >> 4;
|
||||
|
||||
byte[] array = this.getOrCreateChunk(xC, yC, zC);
|
||||
array[(x&15) + (y&15)*16 + (z&15)*16*16] = v;
|
||||
}
|
||||
|
||||
public byte add(int x, int y, int z, byte v) {
|
||||
if (v == 0) return this.get(x, y, z);
|
||||
|
||||
int xC = x >> 4;
|
||||
int yC = y >> 4;
|
||||
int zC = z >> 4;
|
||||
|
||||
byte[] array = this.getOrCreateChunk(xC, yC, zC);
|
||||
return array[(x&15) + (y&15)*16 + (z&15)*16*16] += v;
|
||||
}
|
||||
|
||||
|
||||
public byte binaryAnd(int x, int y, int z, byte v) {
|
||||
int xC = x >> 4;
|
||||
int yC = y >> 4;
|
||||
int zC = z >> 4;
|
||||
|
||||
byte[] array = this.getOrCreateChunk(xC, yC, zC);
|
||||
return array[(x&15) + (y&15)*16 + (z&15)*16*16] &= v;
|
||||
}
|
||||
|
||||
public boolean min(int x, int y, int z, byte v) {
|
||||
int xC = x >> 4;
|
||||
int yC = y >> 4;
|
||||
int zC = z >> 4;
|
||||
|
||||
byte[] array = this.getOrCreateChunk(xC, yC, zC);
|
||||
int index = (x&15) + (y&15)*16 + (z&15)*16*16;
|
||||
|
||||
if (v < array[index]) {
|
||||
array[index] = v;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Reflection.Method<BlockPos, Integer> getX = Reflection.getTypedMethod(BlockPos.class, int.class, 0, long.class);
|
||||
private static final Reflection.Method<BlockPos, Integer> getY = Reflection.getTypedMethod(BlockPos.class, int.class, 1, long.class);
|
||||
private static final Reflection.Method<BlockPos, Integer> getZ = Reflection.getTypedMethod(BlockPos.class, int.class, 2, long.class);
|
||||
public void forEachEntry(EntryConsumer consumer) {
|
||||
for (Long2ObjectMap.Entry<byte[]> entry : this.map.long2ObjectEntrySet()) {
|
||||
int cx = BlockPos.getX(entry.getLongKey()) * 16;
|
||||
int cy = BlockPos.getY(entry.getLongKey()) * 16;
|
||||
int cz = BlockPos.getZ(entry.getLongKey()) * 16;
|
||||
int cx = getX.invoke(null, entry.getLongKey()) * 16;
|
||||
int cy = getY.invoke(null, entry.getLongKey()) * 16;
|
||||
int cz = getZ.invoke(null, entry.getLongKey()) * 16;
|
||||
|
||||
int index = 0;
|
||||
for (int z=0; z<16; z++) {
|
||||
@ -149,32 +59,4 @@ public class Position2ByteMap {
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getChunk(int xC, int yC, int zC) {
|
||||
return this.getChunk(BlockPos.asLong(xC, yC, zC));
|
||||
}
|
||||
|
||||
public byte[] getChunk(long pos) {
|
||||
if (this.lastChunkPos != pos) {
|
||||
byte[] chunk = this.map.get(pos);
|
||||
this.lastChunkPos = pos;
|
||||
this.lastChunk = chunk;
|
||||
}
|
||||
|
||||
return this.lastChunk;
|
||||
}
|
||||
|
||||
public byte[] getOrCreateChunk(int xC, int yC, int zC) {
|
||||
return this.getOrCreateChunk(BlockPos.asLong(xC, yC, zC));
|
||||
}
|
||||
|
||||
public byte[] getOrCreateChunk(long pos) {
|
||||
if (this.lastChunk == null || this.lastChunkPos != pos) {
|
||||
byte[] chunk = this.map.computeIfAbsent(pos, this.defaultFunction);
|
||||
this.lastChunkPos = pos;
|
||||
this.lastChunk = chunk;
|
||||
}
|
||||
|
||||
return this.lastChunk;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.moulberry.axiom.event;
|
||||
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.moulberry.axiom.event;
|
||||
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.moulberry.axiom.event;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
|
@ -0,0 +1,63 @@
|
||||
package com.moulberry.axiom.integration;
|
||||
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import com.moulberry.axiom.buffer.PositionConsumer;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.core.DefaultedRegistry;
|
||||
import net.minecraft.core.IdMapper;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public class NoVersionTranslator implements VersionTranslator {
|
||||
|
||||
private static IdMapper<BlockState> BLOCK_STATE_REGISTRY = Reflection.getField(Block.class, IdMapper.class, BlockState.class).get(null);
|
||||
public static BlockState idToState(int state) {
|
||||
return BLOCK_STATE_REGISTRY.byId(state);
|
||||
}
|
||||
|
||||
private static final BlockState EMPTY_STATE =
|
||||
Reflection.getTypedMethod(Block.class, BlockState.class).invoke((Block) // Block.defaultBlockState()
|
||||
Reflection.getField(BuiltInRegistries.class, DefaultedRegistry.class, Block.class).get(null) // BuiltInRegistries.BLOCK
|
||||
.get(new ResourceLocation("structure_void"))
|
||||
);
|
||||
public static void iterOverSection(PosBlockState container, PositionConsumer<BlockState> consumer) {
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
BlockState state = container.at(x, y, z);
|
||||
if (state == EMPTY_STATE) continue;
|
||||
|
||||
consumer.accept(x, y, z, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntFunction<BlockState> blockStateMapper(Player player) {
|
||||
return NoVersionTranslator::idToState;
|
||||
}
|
||||
|
||||
private static final PalettedContainer.Strategy SECTION_STATES = Reflection.getField(PalettedContainer.Strategy.class, PalettedContainer.Strategy.class).get(null);
|
||||
private static final Reflection.Method<PalettedContainer, Void> read = Reflection.getMethod(PalettedContainer.class, FriendlyByteBuf.class);
|
||||
@Override
|
||||
public void readPalettedContainer(Player player, ByteBuf buf, PositionConsumer<BlockState> consumer) {
|
||||
//EMPTY_STATE is completely ignored
|
||||
PalettedContainer<BlockState> container = new PalettedContainer<>(BLOCK_STATE_REGISTRY, EMPTY_STATE, SECTION_STATES);
|
||||
read.invoke(container, buf);
|
||||
iterOverSection(container::get, consumer);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface PosBlockState {
|
||||
BlockState at(int sx, int sy, int sz);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.moulberry.axiom.integration;
|
||||
|
||||
import com.moulberry.axiom.AxiomConstants;
|
||||
import io.papermc.paper.event.player.PlayerFailMoveEvent;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
public class PaperFailMoveListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onFailMove(PlayerFailMoveEvent event) {
|
||||
if (event.getPlayer().hasPermission(AxiomConstants.PERMISSION) &&
|
||||
event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) {
|
||||
event.setAllowed(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -4,24 +4,20 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class RegionProtection {
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
private final RegionProtectionWorldGuard worldGuard;
|
||||
public interface RegionProtection {
|
||||
|
||||
public RegionProtection(Player player, World world) {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("WorldGuard")) {
|
||||
this.worldGuard = RegionProtectionWorldGuard.tryCreate(player, world);
|
||||
} else {
|
||||
this.worldGuard = null;
|
||||
BiFunction<Player, World, RegionProtection> getProtection = Bukkit.getPluginManager().isPluginEnabled("WorldGuard") ? WorldGuardProtection::create : (player, world) -> new Dummy();
|
||||
|
||||
boolean canBuildInSection(int cx, int cy, int cz);
|
||||
|
||||
class Dummy implements RegionProtection {
|
||||
|
||||
@Override
|
||||
public boolean canBuildInSection(int cx, int cy, int cz) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canBuildInSection(int cx, int cy, int cz) {
|
||||
if (this.worldGuard != null && !this.worldGuard.canBuildInSection(cx, cy, cz)) return false;
|
||||
// todo: PlotSquared
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
19
src/main/java/com/moulberry/axiom/integration/VersionTranslator.java
Normale Datei
19
src/main/java/com/moulberry/axiom/integration/VersionTranslator.java
Normale Datei
@ -0,0 +1,19 @@
|
||||
package com.moulberry.axiom.integration;
|
||||
|
||||
import com.moulberry.axiom.buffer.PositionConsumer;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public interface VersionTranslator {
|
||||
|
||||
VersionTranslator impl = Bukkit.getPluginManager().isPluginEnabled("ViaVersion") ? new ViaVersionTranslator() : new NoVersionTranslator();
|
||||
|
||||
IntFunction<BlockState> blockStateMapper(Player player);
|
||||
|
||||
void readPalettedContainer(Player player, ByteBuf buf, PositionConsumer<BlockState> consumer);
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.moulberry.axiom.integration;
|
||||
|
||||
import com.moulberry.axiom.buffer.PositionConsumer;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.data.MappingData;
|
||||
import com.viaversion.viaversion.api.minecraft.chunks.DataPalette;
|
||||
import com.viaversion.viaversion.api.minecraft.chunks.PaletteType;
|
||||
import com.viaversion.viaversion.api.protocol.ProtocolManager;
|
||||
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
|
||||
import com.viaversion.viaversion.api.protocol.version.ServerProtocolVersion;
|
||||
import com.viaversion.viaversion.api.type.types.version.PaletteType1_18;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public class ViaVersionTranslator implements VersionTranslator {
|
||||
|
||||
private final ProtocolManager protocolManager;
|
||||
private final ServerProtocolVersion serverVersion;
|
||||
|
||||
public ViaVersionTranslator() {
|
||||
protocolManager = Via.getManager().getProtocolManager();
|
||||
serverVersion = Via.getAPI().getServerVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntFunction<BlockState> blockStateMapper(Player player) {
|
||||
List<ProtocolPathEntry> path = protocolManager.getProtocolPath(Via.getAPI().getPlayerVersion(player.getUniqueId()), serverVersion.highestSupportedVersion());
|
||||
if(path == null)
|
||||
return NoVersionTranslator::idToState;
|
||||
|
||||
List<IntFunction<Integer>> mappers = new ArrayList<>(path.size());
|
||||
for(ProtocolPathEntry entry : path) {
|
||||
MappingData mappings = entry.protocol().getMappingData();
|
||||
if(mappings != null)
|
||||
mappers.add(mappings.getBlockStateMappings().inverse()::getNewId);
|
||||
}
|
||||
|
||||
return id -> {
|
||||
for(IntFunction<Integer> transformer : mappers)
|
||||
id = transformer.apply(id);
|
||||
return NoVersionTranslator.idToState(id);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readPalettedContainer(Player player, ByteBuf buf, PositionConsumer<BlockState> consumer) {
|
||||
//TODO GlobalPaletteBits depend on player version (currently only 1.20.1, depending on Axiom version)
|
||||
DataPalette container;
|
||||
try {
|
||||
container = new PaletteType1_18(PaletteType.BLOCKS, 15).read(buf);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
|
||||
BlockState[] palette = new BlockState[container.size()];
|
||||
IntFunction<BlockState> mapper = blockStateMapper(player);
|
||||
for(int i = 0; i < container.size(); i++) {
|
||||
palette[i] = mapper.apply(container.idByIndex(i));
|
||||
}
|
||||
|
||||
NoVersionTranslator.iterOverSection(
|
||||
(sx, sy, sz) -> palette[container.paletteIndexAt(container.index(sx, sy, sz))],
|
||||
consumer
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -15,37 +15,36 @@ import com.sk89q.worldguard.protection.regions.RegionContainer;
|
||||
import com.sk89q.worldguard.protection.regions.RegionQuery;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class RegionProtectionWorldGuard {
|
||||
public class WorldGuardProtection implements RegionProtection {
|
||||
|
||||
private static final WorldGuardPlatform platform = WorldGuard.getInstance().getPlatform();
|
||||
private static final RegionContainer regionContainer = platform.getRegionContainer();
|
||||
|
||||
private final LocalPlayer player;
|
||||
private final RegionManager regionManager;
|
||||
|
||||
public RegionProtectionWorldGuard(LocalPlayer player, RegionManager regionManager) {
|
||||
public WorldGuardProtection(LocalPlayer player, RegionManager regionManager) {
|
||||
this.player = player;
|
||||
this.regionManager = regionManager;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static RegionProtectionWorldGuard tryCreate(Player player, World world) {
|
||||
WorldGuardPlatform platform = WorldGuard.getInstance().getPlatform();
|
||||
|
||||
RegionContainer regionContainer = platform.getRegionContainer();
|
||||
com.sk89q.worldedit.world.World worldEditWorld = BukkitAdapter.adapt(world);
|
||||
public static RegionProtection create(Player player, World world) {
|
||||
com.sk89q.worldedit.world.World worldGuardWorld = BukkitAdapter.adapt(world);
|
||||
LocalPlayer worldGuardPlayer = WorldGuardPlugin.inst().wrapPlayer(player);
|
||||
|
||||
// Don't do any protection if player has bypass
|
||||
if (platform.getSessionManager().hasBypass(worldGuardPlayer, worldEditWorld)) {
|
||||
return null;
|
||||
if (platform.getSessionManager().hasBypass(worldGuardPlayer, worldGuardWorld)) {
|
||||
return new RegionProtection.Dummy();
|
||||
}
|
||||
|
||||
RegionManager regionManager = regionContainer.get(worldEditWorld);
|
||||
if (regionManager == null) return null;
|
||||
RegionManager regionManager = regionContainer.get(worldGuardWorld);
|
||||
if (regionManager == null) return new RegionProtection.Dummy();
|
||||
|
||||
return new RegionProtectionWorldGuard(worldGuardPlayer, regionManager);
|
||||
return new WorldGuardProtection(worldGuardPlayer, regionManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBuildInSection(int cx, int cy, int cz) {
|
||||
BlockVector3 min = BlockVector3.at(cx*16, cy*16, cz*16);
|
||||
BlockVector3 max = BlockVector3.at(cx*16+15, cy*16+15, cz*16+15);
|
@ -1,66 +1,104 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomConstants;
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.buffer.MojBuf;
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.papermc.paper.network.ConnectionEvent;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.ConnectionProtocol;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
||||
@ChannelHandler.Sharable
|
||||
public class AxiomBigPayloadHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private static final ResourceLocation SET_BUFFER = new ResourceLocation("axiom", "set_buffer");
|
||||
private final int payloadId;
|
||||
private final Connection connection;
|
||||
private final SetBlockBufferPacketListener listener;
|
||||
private static final Reflection.Method<ConnectionProtocol, Integer> getPacketId = Reflection.getTypedMethod(ConnectionProtocol.class, Reflection.VERSION > 1902 ? int.class : Integer.class, PacketFlow.class, Packet.class);
|
||||
private static final int PLUGINMESSAGE_PACKETID = getPacketId.invoke(ConnectionProtocol.PLAY, PacketFlow.SERVERBOUND, new ServerboundCustomPayloadPacket(null, null));
|
||||
|
||||
public AxiomBigPayloadHandler(int payloadId, Connection connection, SetBlockBufferPacketListener listener) {
|
||||
this.payloadId = payloadId;
|
||||
this.connection = connection;
|
||||
this.listener = listener;
|
||||
private final Player player;
|
||||
|
||||
private final List<QueuedPacket> packets = new ArrayList<>();
|
||||
private final BukkitTask task;
|
||||
|
||||
public AxiomBigPayloadHandler(Player player) {
|
||||
this.player = player;
|
||||
this.task = Bukkit.getScheduler().runTaskTimer(AxiomPaper.getPlugin(), this::runHandlers, 1, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
try {
|
||||
int readerIndex = in.readerIndex();
|
||||
int i = in.readableBytes();
|
||||
if (i != 0) {
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(in);
|
||||
int packetId = buf.readVarInt();
|
||||
|
||||
if (packetId == payloadId) {
|
||||
ResourceLocation identifier = buf.readResourceLocation();
|
||||
if (identifier.equals(SET_BUFFER)) {
|
||||
ServerPlayer player = connection.getPlayer();
|
||||
if (player != null && player.getBukkitEntity().hasPermission("axiom.*")) {
|
||||
if (listener.onReceive(player, buf)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
in.readerIndex(readerIndex);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
public void channelRead(@NotNull ChannelHandlerContext ctx, @NotNull Object msg) {
|
||||
if(!(msg instanceof ByteBuf in)) {
|
||||
ctx.fireChannelRead(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.fireChannelRead(in.retain());
|
||||
int readerIndexBackup = in.readerIndex();
|
||||
|
||||
if(in.readableBytes() != 0) {
|
||||
ByteBuf buf = new FriendlyByteBuf(in);
|
||||
if (MojBuf.readVarInt(buf) == PLUGINMESSAGE_PACKETID) {
|
||||
NamespacedKey id = MojBuf.readKey(buf);
|
||||
if (id.getNamespace().equals("axiom") && player.hasPermission(AxiomConstants.PERMISSION)) {
|
||||
synchronized (packets) {
|
||||
packets.add(new QueuedPacket(id.getKey(), buf));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
in.readerIndex(readerIndexBackup);
|
||||
ctx.fireChannelRead(in);
|
||||
}
|
||||
|
||||
@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));
|
||||
ctx.channel().pipeline().addBefore("decoder", "axiom-big-payload-handler", this);
|
||||
}
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(@NotNull ChannelHandlerContext ctx) {
|
||||
if(!task.isCancelled())
|
||||
task.cancel();
|
||||
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
|
||||
private void runHandlers() {
|
||||
List<QueuedPacket> queue;
|
||||
synchronized (packets) {
|
||||
if(packets.isEmpty())
|
||||
return;
|
||||
|
||||
queue = new ArrayList<>(packets);
|
||||
packets.clear();
|
||||
}
|
||||
|
||||
for(QueuedPacket packet : queue) {
|
||||
AxiomPacketListener handler = AxiomPaper.getListener(packet.name);
|
||||
if(handler != null)
|
||||
handler.onMessage(player, packet.in);
|
||||
packet.in.release();
|
||||
}
|
||||
}
|
||||
|
||||
private record QueuedPacket(String name, ByteBuf in) {}
|
||||
}
|
||||
|
12
src/main/java/com/moulberry/axiom/packet/AxiomPacketListener.java
Normale Datei
12
src/main/java/com/moulberry/axiom/packet/AxiomPacketListener.java
Normale Datei
@ -0,0 +1,12 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public interface AxiomPacketListener {
|
||||
|
||||
void onMessage(Player player, ByteBuf buf);
|
||||
|
||||
default void onMissingPerm(Player player, ByteBuf buf) {}
|
||||
|
||||
}
|
144
src/main/java/com/moulberry/axiom/packet/ChunkSectionModifier.java
Normale Datei
144
src/main/java/com/moulberry/axiom/packet/ChunkSectionModifier.java
Normale Datei
@ -0,0 +1,144 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import com.moulberry.axiom.version.VersionWrapper;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
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.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
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.LightEventListener;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChunkSectionModifier {
|
||||
|
||||
private static final Reflection.Method<Heightmap.Types, String> getSerializationKey = Reflection.getTypedMethod(Heightmap.Types.class, String.class);
|
||||
private static final List<Heightmap.Types> types = Arrays.stream(Heightmap.Types.values()).filter(type -> List.of("WORLD_SURFACE", "OCEAN_FLOOR", "MOTION_BLOCKING", "MOTION_BLOCKING_NO_LEAVES").contains(getSerializationKey.invoke(type))).toList();
|
||||
|
||||
private final int cx;
|
||||
private final int cy;
|
||||
private final int cz;
|
||||
|
||||
private final ServerChunkCache chunkSource;
|
||||
private final LightEventListener lightEngine;
|
||||
private final LevelChunk chunk;
|
||||
private final LevelChunkSection section;
|
||||
private final Heightmap[] heightmaps;
|
||||
private final boolean hadOnlyAir;
|
||||
|
||||
private static final Reflection.Method<ChunkAccess, LevelChunkSection> getSection = Reflection.getTypedMethod(ChunkAccess.class, LevelChunkSection.class, int.class);
|
||||
private static final Reflection.Method<LevelChunkSection, Boolean> hasOnlyAir = Reflection.getTypedMethod(LevelChunkSection.class, boolean.class, 1);
|
||||
private static final Reflection.Field<ChunkAccess, Map> chunkHeightmaps = Reflection.getField(ChunkAccess.class, Map.class);
|
||||
public ChunkSectionModifier(World world, int cx, int cy, int cz) {
|
||||
this.cx = cx;
|
||||
this.cy = cy;
|
||||
this.cz = cz;
|
||||
|
||||
ServerLevel level = AxiomPaper.convert(world);
|
||||
chunkSource = AxiomPaper.getChunkSource(level);
|
||||
lightEngine = AxiomPaper.getLightEngine(chunkSource);
|
||||
chunk = AxiomPaper.getChunk(level, cx, cz);
|
||||
|
||||
section = getSection.invoke(chunk, cy - world.getMinHeight() / 16);
|
||||
hadOnlyAir = hasOnlyAir.invoke(section);
|
||||
|
||||
heightmaps = types.stream().map(chunkHeightmaps.get(chunk)::get).toArray(Heightmap[]::new);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<LevelChunkSection, BlockState> setBlockState = Reflection.getTypedMethod(LevelChunkSection.class, BlockState.class, int.class, int.class, int.class, BlockState.class, boolean.class);
|
||||
private static final Reflection.Method<Heightmap, Boolean> update = Reflection.getTypedMethod(Heightmap.class, boolean.class, int.class, int.class, int.class, BlockState.class);
|
||||
private static final Reflection.Method<BlockState, Block> getBlock = Reflection.getTypedMethod(BlockState.class, Block.class);
|
||||
private static final Reflection.Method<ChunkAccess, Void> removeBlockEntity = Reflection.getMethod(ChunkAccess.class, BlockPos.class);
|
||||
private static final Reflection.Method<ServerChunkCache, Void> blockChanged = Reflection.getMethod(ServerChunkCache.class, BlockPos.class);
|
||||
private static final Reflection.Method<LightEventListener, Void> checkBlock = Reflection.getMethod(LightEventListener.class, BlockPos.class);
|
||||
public void setState(int sx, int sy, int sz, BlockState state) {
|
||||
BlockState old = setBlockState.invoke(section, sx, sy, sz, state, false);
|
||||
if (state == old)
|
||||
return;
|
||||
|
||||
int by = cy * 16 + sy;
|
||||
BlockPos pos = new BlockPos(cx * 16 + sx, by, cz * 16 + sz);
|
||||
|
||||
for (Heightmap heightmap : heightmaps)
|
||||
update.invoke(heightmap, sx, by, sz, state);
|
||||
|
||||
if (getBlock.invoke(state) instanceof EntityBlock) {
|
||||
setBlockEntity(state, pos);
|
||||
} else if (getBlock.invoke(old) instanceof EntityBlock) {
|
||||
removeBlockEntity.invoke(chunk, pos);
|
||||
}
|
||||
|
||||
blockChanged.invoke(chunkSource, pos);
|
||||
|
||||
if (VersionWrapper.impl.hasDifferentLightProperties(chunk, pos, old, state)) {
|
||||
checkBlock.invoke(lightEngine, pos);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Reflection.Method<BlockGetter, BlockEntity> getBlockEntity = Reflection.getTypedMethod(BlockGetter.class, BlockEntity.class, BlockPos.class);
|
||||
private static final Reflection.Method<BlockEntity, Void> load = Reflection.getMethod(BlockEntity.class, CompoundTag.class);
|
||||
public void setBlockEntity(int sx, int sy, int sz, CompoundTag tag) {
|
||||
BlockEntity blockEntity = getBlockEntity.invoke(chunk, new BlockPos(cx * 16 + sx, cy * 16 + sy, cz * 16 + sz));
|
||||
|
||||
if(blockEntity != null)
|
||||
load.invoke(blockEntity, tag);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<BlockEntity, BlockEntityType> getType = Reflection.getTypedMethod(BlockEntity.class, BlockEntityType.class);
|
||||
private static final Reflection.Method<BlockEntityType, Boolean> isValid = Reflection.getTypedMethod(BlockEntityType.class, boolean.class, BlockState.class);
|
||||
private static final Reflection.Method<BlockEntity, Void> blockEntitySetBlockState = Reflection.getMethod(BlockEntity.class, BlockState.class);
|
||||
private static final Reflection.Method<LevelChunk, Void> updateBlockEntityTicker = Reflection.getMethod(LevelChunk.class, 2, BlockEntity.class);
|
||||
private static final Reflection.Method<EntityBlock, BlockEntity> newBlockEntity = Reflection.getTypedMethod(EntityBlock.class, BlockEntity.class, BlockPos.class, BlockState.class);
|
||||
private static final Reflection.Method<LevelChunk, Void> addAndRegisterBlockEntity = Reflection.getMethod(LevelChunk.class, BlockEntity.class);
|
||||
private void setBlockEntity(BlockState state, BlockPos pos) {
|
||||
BlockEntity blockEntity = getBlockEntity.invoke(chunk, pos);
|
||||
|
||||
if (blockEntity != null) {
|
||||
if (isValid.invoke(getType.invoke(blockEntity), state)) {
|
||||
// Block entity is here and the type is correct
|
||||
blockEntitySetBlockState.invoke(blockEntity, state);
|
||||
|
||||
updateBlockEntityTicker.invoke(chunk, blockEntity);
|
||||
} else {
|
||||
// Block entity type isn't correct, we need to recreate it
|
||||
removeBlockEntity.invoke(chunk, pos);
|
||||
blockEntity = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (blockEntity == null) {
|
||||
// There isn't a block entity here, create it!
|
||||
EntityBlock block = (EntityBlock)getBlock.invoke(state);
|
||||
blockEntity = newBlockEntity.invoke(block, pos, state);
|
||||
if (blockEntity != null) {
|
||||
addAndRegisterBlockEntity.invoke(chunk, blockEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
private static final Reflection.Method<SectionPos, SectionPos> of = Reflection.getTypedMethod(SectionPos.class, SectionPos.class, int.class, int.class, int.class);
|
||||
private static final Reflection.Method<LightEventListener, Void> updateSectionStatus = Reflection.getMethod(LightEventListener.class, SectionPos.class, boolean.class);
|
||||
private static final Reflection.Method<ChunkAccess, Void> setUnsaved = Reflection.getMethod(ChunkAccess.class, boolean.class);
|
||||
public void finish() {
|
||||
boolean onlyAir = hasOnlyAir.invoke(section);
|
||||
if (hadOnlyAir != onlyAir) {
|
||||
updateSectionStatus.invoke(lightEngine, of.invoke(null, cx, cy, cz), onlyAir);
|
||||
}
|
||||
|
||||
setUnsaved.invoke(chunk, true);
|
||||
}
|
||||
}
|
@ -1,50 +1,43 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomConstants;
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.View;
|
||||
import com.moulberry.axiom.*;
|
||||
import com.moulberry.axiom.buffer.MojBuf;
|
||||
import com.moulberry.axiom.event.AxiomHandshakeEvent;
|
||||
import com.moulberry.axiom.persistence.ItemStackDataType;
|
||||
import com.moulberry.axiom.persistence.UUIDDataType;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class HelloPacketListener implements PluginMessageListener {
|
||||
public class HelloPacketListener implements AxiomPacketListener, Listener {
|
||||
|
||||
private final AxiomPaper plugin;
|
||||
private final Set<UUID> activeAxiomPlayers;
|
||||
private static final Reflection.Field<ServerGamePacketListenerImpl, Connection> packetListenerConnection = Reflection.getField(ServerGamePacketListenerImpl.class, Connection.class);
|
||||
private static final Reflection.Field<Connection, Channel> channel = Reflection.getField(Connection.class, Channel.class);
|
||||
|
||||
public HelloPacketListener(AxiomPaper plugin, Set<UUID> activeAxiomPlayers) {
|
||||
this.plugin = plugin;
|
||||
this.activeAxiomPlayers = activeAxiomPlayers;
|
||||
private final AxiomPlayerManager axiomPlayers;
|
||||
|
||||
public HelloPacketListener(AxiomPlayerManager axiomPlayers) {
|
||||
this.axiomPlayers = axiomPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||
if (!player.hasPermission("axiom.*")) {
|
||||
return;
|
||||
}
|
||||
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
int apiVersion = friendlyByteBuf.readVarInt();
|
||||
friendlyByteBuf.readNbt(); // Discard
|
||||
public void onMessage(Player player, ByteBuf buf) {
|
||||
int apiVersion = MojBuf.readVarInt(buf);
|
||||
MojBuf.readNbt(buf); // Discard
|
||||
|
||||
if (apiVersion != AxiomConstants.API_VERSION) {
|
||||
player.kick(Component.text("Unsupported Axiom API Version. Server supports " + AxiomConstants.API_VERSION +
|
||||
", while client is " + apiVersion));
|
||||
player.sendMessage(Component.text("Unsupported Axiom API Version. Server supports " + AxiomConstants.API_VERSION + ", while client is " + apiVersion));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -55,52 +48,60 @@ public class HelloPacketListener implements PluginMessageListener {
|
||||
return;
|
||||
}
|
||||
|
||||
activeAxiomPlayers.add(player.getUniqueId());
|
||||
axiomPlayers.add(player);
|
||||
// Welcome to multiversioning!
|
||||
channel.get(
|
||||
packetListenerConnection.get(
|
||||
AxiomPaper.getConnection(
|
||||
AxiomPaper.convert(player)
|
||||
)
|
||||
)
|
||||
).pipeline().addBefore("decoder", "axiom-big-payload-handler", new AxiomBigPayloadHandler(player));
|
||||
|
||||
// Enable
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
buf.writeBoolean(true);
|
||||
buf.writeByte(0); // todo: world properties
|
||||
buf.writeInt(handshakeEvent.getMaxBufferSize()); // Max Buffer Size
|
||||
buf.writeBoolean(false); // No source info
|
||||
buf.writeBoolean(false); // No source settings
|
||||
buf.writeVarInt(5); // Maximum Reach
|
||||
buf.writeVarInt(16); // Max editor views
|
||||
buf.writeBoolean(true); // Editable Views
|
||||
player.sendPluginMessage(this.plugin, "axiom:enable", buf.accessByteBufWithCorrectSize());
|
||||
ByteBuf out = MojBuf.unpooled();
|
||||
out.writeBoolean(true);
|
||||
out.writeByte(0); // todo: world properties
|
||||
out.writeInt(handshakeEvent.getMaxBufferSize()); // Max Buffer Size
|
||||
out.writeBoolean(false); // No source info
|
||||
out.writeBoolean(false); // No source settings
|
||||
MojBuf.writeVarInt(out, 5); // Maximum Reach
|
||||
MojBuf.writeVarInt(out, 16); // Max editor views
|
||||
out.writeBoolean(true); // Editable Views
|
||||
OutChannel.ENABLE.send(player, out);
|
||||
|
||||
// Initialize Hotbars
|
||||
PersistentDataContainer container = player.getPersistentDataContainer();
|
||||
int activeHotbarIndex = container.getOrDefault(AxiomConstants.ACTIVE_HOTBAR_INDEX, PersistentDataType.BYTE, (byte) 0);
|
||||
PersistentDataContainer hotbarItems = container.get(AxiomConstants.HOTBAR_DATA, PersistentDataType.TAG_CONTAINER);
|
||||
if (hotbarItems != null) {
|
||||
buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
buf.writeByte((byte) activeHotbarIndex);
|
||||
out = MojBuf.unpooled();
|
||||
out.writeByte((byte) activeHotbarIndex);
|
||||
for (int i=0; i<9*9; i++) {
|
||||
// Ignore selected hotbar
|
||||
if (i / 9 == activeHotbarIndex) {
|
||||
buf.writeItem(net.minecraft.world.item.ItemStack.EMPTY);
|
||||
MojBuf.writeItem(out, new ItemStack(Material.AIR, 0));
|
||||
} else {
|
||||
ItemStack stack = hotbarItems.get(new NamespacedKey("axiom", "slot_"+i), ItemStackDataType.INSTANCE);
|
||||
buf.writeItem(CraftItemStack.asNMSCopy(stack));
|
||||
ItemStack stack = hotbarItems.get(AxiomConstants.axiomKey("slot_"+i), ItemStackDataType.INSTANCE);
|
||||
MojBuf.writeItem(out, stack);
|
||||
}
|
||||
}
|
||||
player.sendPluginMessage(this.plugin, "axiom:initialize_hotbars", buf.accessByteBufWithCorrectSize());
|
||||
OutChannel.INITIALIZE_HOTBARS.send(player, out);
|
||||
}
|
||||
|
||||
// Initialize Views
|
||||
UUID activeView = container.get(AxiomConstants.ACTIVE_VIEW, UUIDDataType.INSTANCE);
|
||||
if (activeView != null) {
|
||||
buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
buf.writeUUID(activeView);
|
||||
out = MojBuf.unpooled();
|
||||
MojBuf.writeUUID(out, activeView);
|
||||
|
||||
PersistentDataContainer[] views = container.get(AxiomConstants.VIEWS, PersistentDataType.TAG_CONTAINER_ARRAY);
|
||||
buf.writeVarInt(views.length);
|
||||
MojBuf.writeVarInt(out, views.length);
|
||||
for (PersistentDataContainer view : views) {
|
||||
View.load(view).write(buf);
|
||||
View.load(view).write(out);
|
||||
}
|
||||
|
||||
player.sendPluginMessage(this.plugin, "axiom:set_editor_views", buf.accessByteBufWithCorrectSize());
|
||||
OutChannel.SET_EDITOR_VIEWS.send(player, out);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,90 +1,77 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.OutChannel;
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
||||
import com.moulberry.axiom.buffer.MojBuf;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.longs.*;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
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.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
public class RequestBlockEntityPacketListener implements PluginMessageListener {
|
||||
public class RequestBlockEntityPacketListener implements AxiomPacketListener {
|
||||
|
||||
private final AxiomPaper plugin;
|
||||
private static final Reflection.Method<ServerLevel, BlockEntity> getBlockEntity = Reflection.getTypedMethod(ServerLevel.class, BlockEntity.class, BlockPos.class);
|
||||
|
||||
public RequestBlockEntityPacketListener(AxiomPaper plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
private static final Reflection.Method<BlockEntity, CompoundTag> saveWithoutMetadata = Reflection.getTypedMethod(BlockEntity.class, CompoundTag.class, 2);
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player bukkitPlayer, @NotNull byte[] message) {
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
long id = friendlyByteBuf.readLong();
|
||||
public void onMessage(@NotNull Player bukkitPlayer, @NotNull ByteBuf buf) {
|
||||
long id = buf.readLong();
|
||||
|
||||
if (!bukkitPlayer.hasPermission("axiom.*")) {
|
||||
// We always send an 'empty' response in order to make the client happy
|
||||
sendEmptyResponse(bukkitPlayer, id);
|
||||
return;
|
||||
}
|
||||
|
||||
ServerPlayer player = ((CraftPlayer)bukkitPlayer).getHandle();
|
||||
MinecraftServer server = player.getServer();
|
||||
if (server == null) {
|
||||
sendEmptyResponse(bukkitPlayer, id);
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceKey<Level> worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION);
|
||||
ServerLevel level = server.getLevel(worldKey);
|
||||
if (level == null) {
|
||||
World world = Bukkit.getWorld(MojBuf.readKey(buf));
|
||||
if (world == null) {
|
||||
sendEmptyResponse(bukkitPlayer, id);
|
||||
return;
|
||||
}
|
||||
|
||||
ServerLevel level = AxiomPaper.convert(world);
|
||||
Long2ObjectMap<CompressedBlockEntity> map = new Long2ObjectOpenHashMap<>();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
|
||||
|
||||
// Save and compress block entities
|
||||
int count = friendlyByteBuf.readVarInt();
|
||||
int count = MojBuf.readVarInt(buf);
|
||||
for (int i = 0; i < count; i++) {
|
||||
long pos = friendlyByteBuf.readLong();
|
||||
BlockEntity blockEntity = level.getBlockEntity(mutableBlockPos.set(pos));
|
||||
long pos = buf.readLong();
|
||||
BlockEntity blockEntity = getBlockEntity.invoke(level, AxiomPaper.convert(pos));
|
||||
if (blockEntity != null) {
|
||||
CompoundTag tag = blockEntity.saveWithoutMetadata();
|
||||
CompoundTag tag = saveWithoutMetadata.invoke(blockEntity);
|
||||
map.put(pos, CompressedBlockEntity.compress(tag, baos));
|
||||
}
|
||||
}
|
||||
|
||||
// Send response packet
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(16));
|
||||
buf.writeLong(id);
|
||||
buf.writeVarInt(map.size());
|
||||
ByteBuf out = MojBuf.unpooled();
|
||||
out.writeLong(id);
|
||||
MojBuf.writeVarInt(out, map.size());
|
||||
for (Long2ObjectMap.Entry<CompressedBlockEntity> entry : map.long2ObjectEntrySet()) {
|
||||
buf.writeLong(entry.getLongKey());
|
||||
entry.getValue().write(buf);
|
||||
out.writeLong(entry.getLongKey());
|
||||
entry.getValue().write(out);
|
||||
}
|
||||
bukkitPlayer.sendPluginMessage(this.plugin, "axiom:block_entities", buf.accessByteBufWithCorrectSize());
|
||||
OutChannel.BLOCK_ENTITIES.send(bukkitPlayer, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMissingPerm(Player player, ByteBuf buf) {
|
||||
sendEmptyResponse(player, buf.readLong());
|
||||
}
|
||||
|
||||
private void sendEmptyResponse(Player player, long id) {
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(16));
|
||||
ByteBuf buf = Unpooled.buffer(16);
|
||||
buf.writeLong(id);
|
||||
buf.writeByte(0); // no block entities
|
||||
player.sendPluginMessage(this.plugin, "axiom:block_entities", buf.accessByteBufWithCorrectSize());
|
||||
OutChannel.BLOCK_ENTITIES.send(player, buf);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,306 +1,87 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomConstants;
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.buffer.BiomeBuffer;
|
||||
import com.moulberry.axiom.buffer.BlockBuffer;
|
||||
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
||||
import com.moulberry.axiom.buffer.MojBuf;
|
||||
import com.moulberry.axiom.event.AxiomModifyWorldEvent;
|
||||
import com.moulberry.axiom.integration.RegionProtection;
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
import com.sk89q.worldguard.LocalPlayer;
|
||||
import com.sk89q.worldguard.WorldGuard;
|
||||
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
|
||||
import com.sk89q.worldguard.protection.ApplicableRegionSet;
|
||||
import com.sk89q.worldguard.protection.flags.Flags;
|
||||
import com.sk89q.worldguard.protection.flags.StateFlag;
|
||||
import com.sk89q.worldguard.protection.managers.RegionManager;
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||
import com.sk89q.worldguard.protection.regions.RegionContainer;
|
||||
import com.sk89q.worldguard.protection.regions.RegionQuery;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
|
||||
import com.moulberry.axiom.integration.VersionTranslator;
|
||||
import com.moulberry.axiom.version.VersionWrapper;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.game.ClientboundChunksBiomesPacket;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
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.ChunkStatus;
|
||||
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.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
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.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class SetBlockBufferPacketListener {
|
||||
public class SetBlockBufferPacketListener implements AxiomPacketListener {
|
||||
|
||||
private final AxiomPaper plugin;
|
||||
private final Method updateBlockEntityTicker;
|
||||
@Override
|
||||
public void onMessage(Player player, ByteBuf buf) {
|
||||
World world = Bukkit.getWorld(MojBuf.readKey(buf));
|
||||
|
||||
public SetBlockBufferPacketListener(AxiomPaper plugin) {
|
||||
this.plugin = plugin;
|
||||
// Call AxiomModifyWorldEvent event
|
||||
AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(player, world);
|
||||
Bukkit.getPluginManager().callEvent(modifyWorldEvent);
|
||||
if (modifyWorldEvent.isCancelled()) return;
|
||||
|
||||
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 boolean onReceive(ServerPlayer player, FriendlyByteBuf friendlyByteBuf) {
|
||||
MinecraftServer server = player.getServer();
|
||||
if (server == null) return false;
|
||||
|
||||
ResourceKey<Level> worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION);
|
||||
friendlyByteBuf.readUUID(); // Discard, we don't need to associate buffers
|
||||
boolean continuation = friendlyByteBuf.readBoolean();
|
||||
|
||||
if (!continuation) {
|
||||
friendlyByteBuf.readNbt(); // Discard sourceInfo
|
||||
MojBuf.readUUID(buf); // Discard, we don't need to associate buffers
|
||||
if (!buf.readBoolean()) {
|
||||
MojBuf.readNbt(buf); // Discard sourceInfo
|
||||
}
|
||||
|
||||
byte type = friendlyByteBuf.readByte();
|
||||
byte type = buf.readByte();
|
||||
if (type == 0) {
|
||||
BlockBuffer buffer = BlockBuffer.load(friendlyByteBuf);
|
||||
applyBlockBuffer(player, server, buffer, worldKey);
|
||||
applyBlockBuffer(world, player, buf);
|
||||
} else if (type == 1) {
|
||||
BiomeBuffer buffer = BiomeBuffer.load(friendlyByteBuf);
|
||||
applyBiomeBuffer(server, buffer, worldKey);
|
||||
applyBiomeBuffer(world, new BiomeBuffer(buf));
|
||||
} else {
|
||||
throw new RuntimeException("Unknown buffer type: " + type);
|
||||
throw new IllegalArgumentException("Unknown buffer type: " + type);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void applyBlockBuffer(ServerPlayer player, MinecraftServer server, BlockBuffer buffer, ResourceKey<Level> worldKey) {
|
||||
server.execute(() -> {
|
||||
ServerLevel world = server.getLevel(worldKey);
|
||||
if (world == null) return;
|
||||
private void applyBlockBuffer(World world, Player player, ByteBuf buf) {
|
||||
RegionProtection regionProtection = RegionProtection.getProtection.apply(player, world);
|
||||
|
||||
// Call AxiomModifyWorldEvent event
|
||||
AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(player.getBukkitEntity(), world.getWorld());
|
||||
Bukkit.getPluginManager().callEvent(modifyWorldEvent);
|
||||
if (modifyWorldEvent.isCancelled()) return;
|
||||
long sectionPos;
|
||||
while ((sectionPos = buf.readLong()) != AxiomConstants.MIN_POSITION_LONG) {
|
||||
int cx = BlockPos.getX(sectionPos);
|
||||
int cy = BlockPos.getY(sectionPos);
|
||||
int cz = BlockPos.getZ(sectionPos);
|
||||
|
||||
RegionProtection regionProtection = new RegionProtection(player.getBukkitEntity(), world.getWorld());
|
||||
boolean canBuildInSection = regionProtection.canBuildInSection(cx, cy, cz);
|
||||
ChunkSectionModifier section = new ChunkSectionModifier(world, cx, cy, cz);
|
||||
|
||||
// Allowed, apply buffer
|
||||
BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos();
|
||||
VersionTranslator.impl.readPalettedContainer(player, buf, canBuildInSection ? section::setState : (x, y, z, blockState) -> {});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!regionProtection.canBuildInSection(cx, cy, cz)) {
|
||||
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 -> {}
|
||||
}
|
||||
}
|
||||
|
||||
Short2ObjectMap<CompressedBlockEntity> blockEntityChunkMap = buffer.getBlockEntityChunkMap(entry.getLongKey());
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (blockEntity != null && blockEntityChunkMap != null) {
|
||||
int key = x | (y << 4) | (z << 8);
|
||||
CompressedBlockEntity savedBlockEntity = blockEntityChunkMap.get((short) key);
|
||||
if (savedBlockEntity != null) {
|
||||
blockEntity.load(savedBlockEntity.decompress());
|
||||
}
|
||||
}
|
||||
} else if (old.hasBlockEntity()) {
|
||||
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);
|
||||
}
|
||||
int blockEntitySize = Math.min(4096, Math.max(0, MojBuf.readVarInt(buf)));
|
||||
for (int i = 0; i < blockEntitySize; i++) {
|
||||
short offset = buf.readShort();
|
||||
CompressedBlockEntity entity = CompressedBlockEntity.read(buf);
|
||||
if(canBuildInSection)
|
||||
section.setBlockEntity(offset & 0xF, (offset >> 4) & 0xF, offset >> 8, entity.decompress());
|
||||
}
|
||||
});
|
||||
|
||||
if(canBuildInSection)
|
||||
section.finish();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void applyBiomeBuffer(MinecraftServer server, BiomeBuffer biomeBuffer, ResourceKey<Level> worldKey) {
|
||||
server.execute(() -> {
|
||||
ServerLevel world = server.getLevel(worldKey);
|
||||
if (world == null) return;
|
||||
|
||||
Set<LevelChunk> changedChunks = new HashSet<>();
|
||||
|
||||
int minSection = world.getMinSection();
|
||||
int maxSection = world.getMaxSection();
|
||||
|
||||
Optional<Registry<Biome>> registryOptional = world.registryAccess().registry(Registries.BIOME);
|
||||
if (registryOptional.isEmpty()) return;
|
||||
|
||||
Registry<Biome> registry = registryOptional.get();
|
||||
|
||||
biomeBuffer.forEachEntry((x, y, z, biome) -> {
|
||||
int cy = y >> 2;
|
||||
if (cy < minSection || cy >= maxSection) {
|
||||
return;
|
||||
}
|
||||
|
||||
var chunk = (LevelChunk) world.getChunk(x >> 2, z >> 2, ChunkStatus.FULL, false);
|
||||
if (chunk == null) return;
|
||||
|
||||
var section = chunk.getSection(cy - minSection);
|
||||
PalettedContainer<Holder<Biome>> container = (PalettedContainer<Holder<Biome>>) section.getBiomes();
|
||||
|
||||
var holder = registry.getHolder(biome);
|
||||
if (holder.isPresent()) {
|
||||
container.set(x & 3, y & 3, z & 3, holder.get());
|
||||
changedChunks.add(chunk);
|
||||
}
|
||||
});
|
||||
|
||||
var chunkMap = world.getChunkSource().chunkMap;
|
||||
HashMap<ServerPlayer, List<LevelChunk>> map = new HashMap<>();
|
||||
for (LevelChunk chunk : changedChunks) {
|
||||
chunk.setUnsaved(true);
|
||||
ChunkPos chunkPos = chunk.getPos();
|
||||
for (ServerPlayer serverPlayer2 : chunkMap.getPlayers(chunkPos, false)) {
|
||||
map.computeIfAbsent(serverPlayer2, serverPlayer -> new ArrayList<>()).add(chunk);
|
||||
}
|
||||
}
|
||||
map.forEach((serverPlayer, list) -> serverPlayer.connection.send(ClientboundChunksBiomesPacket.forChunks(list)));
|
||||
private void applyBiomeBuffer(World world, BiomeBuffer biomeBuffer) {
|
||||
Set<Chunk> changedChunks = new HashSet<>();
|
||||
biomeBuffer.forEachEntry((x, y, z, biome) -> {
|
||||
world.setBiome(x*4, y*4, z*4, biome);
|
||||
int cx = x/4 - (x < 0 ? 1 : 0);
|
||||
int cz = z/4 - (z < 0 ? 1 : 0);
|
||||
changedChunks.add(world.getChunkAt(cx, cz));
|
||||
});
|
||||
|
||||
VersionWrapper.impl.publishBiomeChange(AxiomPaper.convert(world), changedChunks);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,162 +1,68 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import com.moulberry.axiom.buffer.MojBuf;
|
||||
import com.moulberry.axiom.event.AxiomModifyWorldEvent;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import com.moulberry.axiom.integration.RegionProtection;
|
||||
import com.moulberry.axiom.integration.VersionTranslator;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.core.Vec3i;
|
||||
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.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.world.level.Level;
|
||||
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.Bukkit;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
||||
import org.bukkit.World;
|
||||
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 AxiomPacketListener {
|
||||
|
||||
public class SetBlockPacketListener implements PluginMessageListener {
|
||||
private static final Reflection.Method<Level, Boolean> setBlock = Reflection.getTypedMethod(Level.class, boolean.class, BlockPos.class, BlockState.class, int.class);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
private static final Reflection.Method<Vec3i, Integer> getX = Reflection.getTypedMethod(Vec3i.class, int.class, 1);
|
||||
private static final Reflection.Method<Vec3i, Integer> getY = Reflection.getTypedMethod(Vec3i.class, int.class, 2);
|
||||
private static final Reflection.Method<Vec3i, Integer> getZ = Reflection.getTypedMethod(Vec3i.class, int.class, 3);
|
||||
|
||||
private static final Reflection.Method<ServerGamePacketListenerImpl, Void> ackBlockChangesUpTo = Reflection.getMethod(ServerGamePacketListenerImpl.class, int.class);
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player bukkitPlayer, @NotNull byte[] message) {
|
||||
if (!bukkitPlayer.hasPermission("axiom.*")) {
|
||||
return;
|
||||
}
|
||||
public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) {
|
||||
World world = player.getWorld();
|
||||
|
||||
// Check if player is allowed to modify this world
|
||||
AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(bukkitPlayer, bukkitPlayer.getWorld());
|
||||
AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(player, world);
|
||||
Bukkit.getPluginManager().callEvent(modifyWorldEvent);
|
||||
if (modifyWorldEvent.isCancelled()) return;
|
||||
|
||||
// Read packet
|
||||
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();
|
||||
BlockPos pos = AxiomPaper.convert(buf.readLong());
|
||||
BlockState state = VersionTranslator.impl.blockStateMapper(player).apply(MojBuf.readVarInt(buf));
|
||||
boolean updateNeighbors = buf.readBoolean();
|
||||
int sequenceId = buf.readInt();
|
||||
|
||||
ServerPlayer player = ((CraftPlayer)bukkitPlayer).getHandle();
|
||||
ServerPlayer serverPlayer = AxiomPaper.convert(player);
|
||||
int x = getX.invoke(pos);
|
||||
int y = getY.invoke(pos);
|
||||
int z = getZ.invoke(pos);
|
||||
int cx = x / 16 - (x < 0 ? 1 : 0);
|
||||
int cy = y / 16 - (y < 0 ? 1 : 0);
|
||||
int cz = z / 16 - (z < 0 ? 1 : 0);
|
||||
|
||||
// Update blocks
|
||||
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(RegionProtection.getProtection.apply(player, world).canBuildInSection(cx, cy, cz)) {
|
||||
if (updateNeighbors) {
|
||||
setBlock.invoke(AxiomPaper.convert(player.getWorld()), pos, state, 3);
|
||||
} else {
|
||||
ChunkSectionModifier section = new ChunkSectionModifier(player.getWorld(), cx, cy, cz);
|
||||
section.setState(x & 0xF, y & 0xF, z & 0xF, state);
|
||||
section.finish();
|
||||
}
|
||||
}
|
||||
|
||||
if (sequenceId >= 0) {
|
||||
player.connection.ackBlockChangesUpTo(sequenceId);
|
||||
ackBlockChangesUpTo.invoke(AxiomPaper.getConnection(serverPlayer), sequenceId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,30 +1,24 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomConstants;
|
||||
import com.moulberry.axiom.buffer.MojBuf;
|
||||
import com.moulberry.axiom.View;
|
||||
import com.moulberry.axiom.persistence.UUIDDataType;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SetEditorViewsPacketListener implements PluginMessageListener {
|
||||
public class SetEditorViewsPacketListener implements AxiomPacketListener {
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||
if (!player.hasPermission("axiom.*")) {
|
||||
return;
|
||||
}
|
||||
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
UUID uuid = friendlyByteBuf.readUUID();
|
||||
List<View> views = friendlyByteBuf.readList(View::read);
|
||||
public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) {
|
||||
UUID uuid = MojBuf.readUUID(buf);
|
||||
List<View> views = MojBuf.readList(buf, View::read);
|
||||
|
||||
PersistentDataContainer container = player.getPersistentDataContainer();
|
||||
container.set(AxiomConstants.ACTIVE_VIEW, UUIDDataType.INSTANCE, uuid);
|
||||
|
@ -1,28 +1,23 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import com.moulberry.axiom.event.AxiomFlySpeedChangeEvent;
|
||||
import com.moulberry.axiom.event.AxiomGameModeChangeEvent;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Abilities;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
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;
|
||||
|
||||
public class SetFlySpeedPacketListener implements PluginMessageListener {
|
||||
public class SetFlySpeedPacketListener implements AxiomPacketListener {
|
||||
|
||||
private static final Reflection.Method<ServerPlayer, Abilities> getAbilities = Reflection.getTypedMethod(ServerPlayer.class, Abilities.class);
|
||||
private static final Reflection.Field<Abilities, Float> flyingSpeed = Reflection.getField(Abilities.class, float.class);
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||
if (!player.hasPermission("axiom.*")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
float flySpeed = friendlyByteBuf.readFloat();
|
||||
public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) {
|
||||
float flySpeed = buf.readFloat();
|
||||
|
||||
// Call event
|
||||
AxiomFlySpeedChangeEvent flySpeedChangeEvent = new AxiomFlySpeedChangeEvent(player, flySpeed);
|
||||
@ -30,7 +25,7 @@ public class SetFlySpeedPacketListener implements PluginMessageListener {
|
||||
if (flySpeedChangeEvent.isCancelled()) return;
|
||||
|
||||
// Change flying speed
|
||||
((CraftPlayer)player).getHandle().getAbilities().setFlyingSpeed(flySpeed);
|
||||
flyingSpeed.set(getAbilities.invoke(AxiomPaper.convert(player)), flySpeed);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,34 +1,26 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.event.AxiomGameModeChangeEvent;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
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;
|
||||
|
||||
public class SetGamemodePacketListener implements PluginMessageListener {
|
||||
public class SetGamemodePacketListener implements AxiomPacketListener {
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||
if (!player.hasPermission("axiom.*")) {
|
||||
return;
|
||||
}
|
||||
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
GameType gameType = GameType.byId(friendlyByteBuf.readByte());
|
||||
public void onMessage(Player player, ByteBuf buf) {
|
||||
@SuppressWarnings("deprecation")
|
||||
GameMode gameMode = GameMode.getByValue(buf.readByte());
|
||||
assert gameMode != null;
|
||||
|
||||
// Call event
|
||||
AxiomGameModeChangeEvent gameModeChangeEvent = new AxiomGameModeChangeEvent(player, GameMode.getByValue(gameType.getId()));
|
||||
AxiomGameModeChangeEvent gameModeChangeEvent = new AxiomGameModeChangeEvent(player, gameMode);
|
||||
Bukkit.getPluginManager().callEvent(gameModeChangeEvent);
|
||||
if (gameModeChangeEvent.isCancelled()) return;
|
||||
|
||||
// Change gamemode
|
||||
((CraftPlayer)player).getHandle().setGameMode(gameType);
|
||||
player.setGameMode(gameMode);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,35 +1,27 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomConstants;
|
||||
import com.moulberry.axiom.buffer.MojBuf;
|
||||
import com.moulberry.axiom.persistence.ItemStackDataType;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class SetHotbarSlotPacketListener implements PluginMessageListener {
|
||||
public class SetHotbarSlotPacketListener implements AxiomPacketListener {
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||
if (!player.hasPermission("axiom.*")) {
|
||||
return;
|
||||
}
|
||||
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
int index = friendlyByteBuf.readByte();
|
||||
public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) {
|
||||
int index = buf.readByte();
|
||||
if (index < 0 || index >= 9*9) return;
|
||||
net.minecraft.world.item.ItemStack nmsStack = friendlyByteBuf.readItem();
|
||||
ItemStack stack = MojBuf.readItem(buf);
|
||||
|
||||
PersistentDataContainer container = player.getPersistentDataContainer();
|
||||
PersistentDataContainer hotbarItems = container.get(AxiomConstants.HOTBAR_DATA, PersistentDataType.TAG_CONTAINER);
|
||||
if (hotbarItems == null) hotbarItems = container.getAdapterContext().newPersistentDataContainer();
|
||||
hotbarItems.set(new NamespacedKey("axiom", "slot_"+index), ItemStackDataType.INSTANCE, CraftItemStack.asCraftMirror(nmsStack));
|
||||
hotbarItems.set(AxiomConstants.axiomKey("slot_"+index), ItemStackDataType.INSTANCE, stack);
|
||||
container.set(AxiomConstants.HOTBAR_DATA, PersistentDataType.TAG_CONTAINER, hotbarItems);
|
||||
}
|
||||
|
||||
|
@ -1,35 +1,27 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomConstants;
|
||||
import com.moulberry.axiom.buffer.MojBuf;
|
||||
import com.moulberry.axiom.persistence.ItemStackDataType;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class SwitchActiveHotbarPacketListener implements PluginMessageListener {
|
||||
public class SwitchActiveHotbarPacketListener implements AxiomPacketListener {
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||
if (!player.hasPermission("axiom.*")) {
|
||||
return;
|
||||
}
|
||||
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
int oldHotbarIndex = friendlyByteBuf.readByte();
|
||||
int activeHotbarIndex = friendlyByteBuf.readByte();
|
||||
public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) {
|
||||
int oldHotbarIndex = buf.readByte();
|
||||
int activeHotbarIndex = buf.readByte();
|
||||
|
||||
ItemStack[] hotbarItems = new ItemStack[9];
|
||||
for (int i=0; i<9; i++) {
|
||||
hotbarItems[i] = CraftItemStack.asCraftMirror(friendlyByteBuf.readItem());
|
||||
hotbarItems[i] = MojBuf.readItem(buf);
|
||||
}
|
||||
|
||||
PersistentDataContainer container = player.getPersistentDataContainer();
|
||||
@ -45,10 +37,10 @@ public class SwitchActiveHotbarPacketListener implements PluginMessageListener {
|
||||
} else {
|
||||
stack = stack.clone();
|
||||
}
|
||||
containerHotbarItems.set(new NamespacedKey("axiom", "slot_"+index), ItemStackDataType.INSTANCE, stack);
|
||||
containerHotbarItems.set(AxiomConstants.axiomKey("slot_"+index), ItemStackDataType.INSTANCE, stack);
|
||||
}
|
||||
int index = activeHotbarIndex*9 + i;
|
||||
containerHotbarItems.set(new NamespacedKey("axiom", "slot_"+index), ItemStackDataType.INSTANCE, hotbarItems[i].clone());
|
||||
containerHotbarItems.set(AxiomConstants.axiomKey("slot_"+index), ItemStackDataType.INSTANCE, hotbarItems[i].clone());
|
||||
if (player.getGameMode() == GameMode.CREATIVE) player.getInventory().setItem(i, hotbarItems[i]);
|
||||
}
|
||||
|
||||
|
@ -1,35 +1,25 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.event.AxiomGameModeChangeEvent;
|
||||
import com.moulberry.axiom.buffer.MojBuf;
|
||||
import com.moulberry.axiom.event.AxiomTeleportEvent;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.bukkit.*;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class TeleportPacketListener implements PluginMessageListener {
|
||||
public class TeleportPacketListener implements AxiomPacketListener {
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||
if (!player.hasPermission("axiom.*")) {
|
||||
return;
|
||||
}
|
||||
public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) {
|
||||
World world = Bukkit.getWorld(MojBuf.readKey(buf));
|
||||
double x = buf.readDouble();
|
||||
double y = buf.readDouble();
|
||||
double z = buf.readDouble();
|
||||
float yRot = buf.readFloat();
|
||||
float xRot = buf.readFloat();
|
||||
|
||||
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) return;
|
||||
|
||||
// Call event
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.moulberry.axiom.persistence;
|
||||
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import com.moulberry.axiom.buffer.MojBuf;
|
||||
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;
|
||||
@ -10,9 +10,9 @@ 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() {
|
||||
}
|
||||
private ItemStackDataType() {}
|
||||
|
||||
public static final ItemStackDataType INSTANCE = new ItemStackDataType();
|
||||
|
||||
@Override
|
||||
public @NotNull Class<PersistentDataContainer> getPrimitiveType() {
|
||||
@ -24,23 +24,23 @@ public class ItemStackDataType implements PersistentDataType<PersistentDataConta
|
||||
return ItemStack.class;
|
||||
}
|
||||
|
||||
private static final Class<PersistentDataContainer> CraftPersistentDataContainer = Reflection.getClass("org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer");
|
||||
private static final Reflection.Method<PersistentDataContainer, Void> putAll = Reflection.getMethod(CraftPersistentDataContainer, "putAll", CompoundTag.class);
|
||||
private static final Reflection.Method<PersistentDataContainer, CompoundTag> toTagCompound = Reflection.getTypedMethod(CraftPersistentDataContainer, "toTagCompound", CompoundTag.class);
|
||||
|
||||
private static final Reflection.Method<net.minecraft.world.item.ItemStack, CompoundTag> save = Reflection.getTypedMethod(net.minecraft.world.item.ItemStack.class, CompoundTag.class, CompoundTag.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);
|
||||
public @NotNull PersistentDataContainer toPrimitive(@NotNull ItemStack stack, @NotNull PersistentDataAdapterContext context) {
|
||||
CompoundTag tag = save.invoke(MojBuf.toMojang(stack), new CompoundTag());
|
||||
|
||||
PersistentDataContainer container = context.newPersistentDataContainer();
|
||||
((CraftPersistentDataContainer)container).putAll(tag);
|
||||
putAll.invoke(container, tag);
|
||||
return container;
|
||||
}
|
||||
|
||||
private static final Reflection.Method<net.minecraft.world.item.ItemStack, net.minecraft.world.item.ItemStack> of = Reflection.getTypedMethod(net.minecraft.world.item.ItemStack.class, net.minecraft.world.item.ItemStack.class, CompoundTag.class);
|
||||
@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);
|
||||
return MojBuf.toBukkit(of.invoke(null, toTagCompound.invoke(primitive)));
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UUIDDataType implements PersistentDataType<byte[], UUID> {
|
||||
public static UUIDDataType INSTANCE = new UUIDDataType();
|
||||
private UUIDDataType() {
|
||||
}
|
||||
private UUIDDataType() {}
|
||||
|
||||
public static final UUIDDataType INSTANCE = new UUIDDataType();
|
||||
|
||||
public Class<byte[]> getPrimitiveType() {
|
||||
return byte[].class;
|
||||
|
47
src/main/java/com/moulberry/axiom/version/Version19R2.java
Normale Datei
47
src/main/java/com/moulberry/axiom/version/Version19R2.java
Normale Datei
@ -0,0 +1,47 @@
|
||||
package com.moulberry.axiom.version;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.bukkit.Chunk;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class Version19R2 implements VersionWrapper {
|
||||
|
||||
private static final Reflection.Method<BlockState, Integer> getLightBlock = Reflection.getTypedMethod(BlockState.class, int.class, BlockGetter.class, BlockPos.class);
|
||||
private static final Reflection.Field<BlockState, Integer> lightEmission = Reflection.getField(BlockState.class, int.class);
|
||||
private static final Reflection.Field<BlockState, Boolean> useShapeForLightOcclusion = Reflection.getField(BlockState.class, boolean.class);
|
||||
@Override
|
||||
public boolean hasDifferentLightProperties(BlockGetter chunk, BlockPos pos, BlockState old, BlockState state) {
|
||||
return getLightBlock.invoke(old, chunk, pos) != getLightBlock.invoke(state, chunk, pos) || lightEmission.get(old) != lightEmission.get(state) || useShapeForLightOcclusion.get(old) || useShapeForLightOcclusion.get(state);
|
||||
}
|
||||
|
||||
private static final Class<?> LevelLightEngine = Reflection.getClass("net.minecraft.world.level.lighting.LightEngine");
|
||||
private static final Reflection.Constructor<ClientboundLevelChunkWithLightPacket> newClientboundLevelChunkWithLightPacket = Reflection.getConstructor(
|
||||
ClientboundLevelChunkWithLightPacket.class,
|
||||
LevelChunk.class, LevelLightEngine, BitSet.class, BitSet.class, boolean.class
|
||||
);
|
||||
@Override
|
||||
public void publishBiomeChange(ServerLevel level, Set<Chunk> chunks) {
|
||||
ThreadedLevelLightEngine lightEngine = AxiomPaper.getLightEngine(AxiomPaper.getChunkSource(level));
|
||||
|
||||
for (Chunk chunk : chunks) {
|
||||
ChunkPos chunkPos = new ChunkPos(chunk.getX(), chunk.getZ());
|
||||
LevelChunk nativeChunk = AxiomPaper.getChunk(level, chunk.getX(), chunk.getZ());
|
||||
ClientboundLevelChunkWithLightPacket packet = newClientboundLevelChunkWithLightPacket.newInstance(nativeChunk, lightEngine, null, null, true);
|
||||
for (ServerPlayer player : AxiomPaper.getPlayersSeeingChunk(level, chunkPos)) {
|
||||
AxiomPaper.sendPacket(player, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
42
src/main/java/com/moulberry/axiom/version/Version20R1.java
Normale Datei
42
src/main/java/com/moulberry/axiom/version/Version20R1.java
Normale Datei
@ -0,0 +1,42 @@
|
||||
package com.moulberry.axiom.version;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.protocol.game.ClientboundChunksBiomesPacket;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.lighting.LightEngine;
|
||||
import org.bukkit.Chunk;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class Version20R1 implements VersionWrapper {
|
||||
|
||||
private static final Reflection.Method<LightEngine, Boolean> hasDifferentLightProperties = Reflection.getTypedMethod(LightEngine.class, boolean.class, BlockGetter.class, BlockPos.class, BlockState.class, BlockState.class);
|
||||
@Override
|
||||
public boolean hasDifferentLightProperties(BlockGetter chunk, BlockPos pos, BlockState old, BlockState state) {
|
||||
return hasDifferentLightProperties.invoke(null, chunk, pos, old, state);
|
||||
}
|
||||
|
||||
private static final Reflection.Method<ClientboundChunksBiomesPacket, ClientboundChunksBiomesPacket> forChunks = Reflection.getTypedMethod(ClientboundChunksBiomesPacket.class, ClientboundChunksBiomesPacket.class, List.class);
|
||||
@Override
|
||||
public void publishBiomeChange(ServerLevel level, Set<Chunk> chunks) {
|
||||
HashMap<ServerPlayer, List<LevelChunk>> playerChunks = new HashMap<>();
|
||||
for (Chunk chunk : chunks) {
|
||||
ChunkPos chunkPos = new ChunkPos(chunk.getX(), chunk.getZ());
|
||||
LevelChunk nativeChunk = AxiomPaper.getChunk(level, chunk.getX(), chunk.getZ());
|
||||
for (ServerPlayer player : AxiomPaper.getPlayersSeeingChunk(level, chunkPos)) {
|
||||
playerChunks.computeIfAbsent(player, p -> new ArrayList<>()).add(nativeChunk);
|
||||
}
|
||||
}
|
||||
playerChunks.forEach((serverPlayer, list) -> AxiomPaper.sendPacket(serverPlayer, forChunks.invoke(null, list)));
|
||||
}
|
||||
}
|
18
src/main/java/com/moulberry/axiom/version/VersionWrapper.java
Normale Datei
18
src/main/java/com/moulberry/axiom/version/VersionWrapper.java
Normale Datei
@ -0,0 +1,18 @@
|
||||
package com.moulberry.axiom.version;
|
||||
|
||||
import com.moulberry.axiom.Reflection;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.bukkit.Chunk;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface VersionWrapper {
|
||||
VersionWrapper impl = Reflection.VERSION > 1902 ? new Version20R1() : new Version19R2();
|
||||
|
||||
boolean hasDifferentLightProperties(BlockGetter chunk, BlockPos pos, BlockState old, BlockState state);
|
||||
void publishBiomeChange(ServerLevel level, Set<Chunk> chunks);
|
||||
|
||||
}
|
@ -2,9 +2,13 @@ name: $name
|
||||
version: $version
|
||||
main: com.moulberry.axiom.AxiomPaper
|
||||
description: $description
|
||||
softdepend:
|
||||
- ViaVersion
|
||||
- WorldGuard
|
||||
authors:
|
||||
- Moulberry
|
||||
api-version: "$apiVersion"
|
||||
- Lixfel
|
||||
api-version: "1.17"
|
||||
permissions:
|
||||
axiom.*:
|
||||
description: Allows use of all Axiom features
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren