Initial commit

This commit is contained in:
ShadowNetReal
2026-07-23 18:21:37 -04:00
commit a35a0892f6
81 changed files with 613418 additions and 0 deletions
@@ -0,0 +1,32 @@
package tf.tuff;
import com.github.retrooper.packetevents.event.PacketListener;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChunkData;
import org.bukkit.World;
import org.bukkit.entity.Player;
public class NetworkListener implements PacketListener {
private final TuffX plugin;
public NetworkListener(TuffX plugin) {
this.plugin = plugin;
}
@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getPacketType() == PacketType.Play.Server.CHUNK_DATA) {
Player player = (Player) event.getPlayer();
if (player == null) return;
WrapperPlayServerChunkData wrapper = new WrapperPlayServerChunkData(event);
int chunkX = wrapper.getColumn().getX();
int chunkZ = wrapper.getColumn().getZ();
World world = player.getWorld();
plugin.y0Plugin.chunkPacketListener.handleChunk(plugin, player, world, chunkX, chunkZ);
}
}
}
+68
View File
@@ -0,0 +1,68 @@
package tf.tuff;
import org.bukkit.plugin.java.JavaPlugin;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import tf.tuff.util.SchedulerCompat;
public class ServerRegistry {
private final JavaPlugin p;
private final String wsUrl;
private final String server;
private WebSocketClient client;
private volatile boolean running = true;
public ServerRegistry(JavaPlugin pl, String registryUrl, String serverAddr) {
p = pl;
wsUrl = registryUrl;
server = serverAddr;
}
public void connect() {
SchedulerCompat.runAsync(p, this::doConnect);
}
private void doConnect() {
if (!running)
return;
try {
client = new WebSocketClient(new URI(wsUrl)) {
@Override
public void onOpen(ServerHandshake h) {
send("{\"type\":\"register\",\"server\":\"" + server + "\"}");
}
@Override
public void onMessage(String msg) {
}
@Override
public void onClose(int code, String reason, boolean remote) {
if (running) {
SchedulerCompat.runAsyncLater(p, ServerRegistry.this::doConnect, 100L);
}
}
@Override
public void onError(Exception e) {
}
};
client.setConnectionLostTimeout(30);
client.connect();
} catch (Exception e) {
if (running) {
SchedulerCompat.runAsyncLater(p, this::doConnect, 100L);
}
}
}
public void disconnect() {
running = false;
if (client != null) {
client.close();
}
}
}
+354
View File
@@ -0,0 +1,354 @@
package tf.tuff;
import java.io.File;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.*;
import org.bukkit.event.entity.EntityToggleSwimEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.java.JavaPluginLoader;
import org.bukkit.plugin.messaging.PluginMessageListener;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketListenerPriority;
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
import tf.tuff.netty.ChunkInjector;
import tf.tuff.tuffactions.TuffActions;
import tf.tuff.util.SchedulerCompat;
import tf.tuff.viablocks.ViaBlocksPlugin;
import tf.tuff.viaentities.ViaEntitiesPlugin;
import tf.tuff.y0.Y0Plugin;
public class TuffX extends JavaPlugin implements Listener, PluginMessageListener {
public ServerRegistry serverRegistry;
public Y0Plugin y0Plugin;
public ViaBlocksPlugin viaBlocksPlugin;
public TuffActions tuffActions;
public ViaEntitiesPlugin viaEntitiesPlugin;
private ChunkInjector chunkInjector;
private boolean packetEventsEnabled;
// required by MockBukkit
public TuffX(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) {
super(loader, description, dataFolder, file);
}
public TuffX() { super(); }
@Override
public void onLoad() {
this.y0Plugin = new Y0Plugin(this);
this.viaBlocksPlugin = new ViaBlocksPlugin(this);
this.tuffActions = new TuffActions(this);
this.viaEntitiesPlugin = new ViaEntitiesPlugin(this);
if (shouldBootstrapPacketEvents()) {
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
PacketEvents.getAPI().getSettings().reEncodeByDefault(false)
.checkForUpdates(false);
PacketEvents.getAPI().load();
packetEventsEnabled = true;
}
}
@Override
public void onEnable() {
if (packetEventsEnabled && PacketEvents.getAPI() != null) {
PacketEvents.getAPI().init();
} else {
packetEventsEnabled = false;
}
saveDefaultConfig();
getLogger().info(SchedulerCompat.isFolia()
? "Folia detected. Using region and entity schedulers."
: "Using standard Bukkit-compatible schedulers.");
y0Plugin.onTuffXEnable();
tuffActions.onTuffXEnable();
viaBlocksPlugin.onTuffXEnable();
viaEntitiesPlugin.onTuffXEnable();
chunkInjector = new ChunkInjector(viaBlocksPlugin.blockListener, y0Plugin);
viaBlocksPlugin.blockListener.setChunkInjector(chunkInjector);
y0Plugin.setChunkInjector(chunkInjector);
getConfig().options().copyDefaults(true);
saveConfig();
getServer().getScheduler().runTaskTimer(this, () -> {
Runtime runtime = Runtime.getRuntime();
long used = runtime.totalMemory() - runtime.freeMemory();
long max = runtime.maxMemory();
double percent = (double) used / (double) max;
if (percent >= 0.80D) {
if (y0Plugin != null) {
y0Plugin.forceClearCache();
}
getLogger().warning("[TuffXPlus] Memory watchdog cleared Y0 cache. Used: " + (used / 1024L / 1024L) + "MB / " + (max / 1024L / 1024L) + "MB");
}
if (percent >= 0.95D) {
System.gc();
getLogger().warning("[TuffXPlus] Emergency GC requested.");
}
}, 20L * 60L, 20L * 60L);
if (packetEventsEnabled) {
PacketEvents.getAPI().getEventManager().registerListener(
new NetworkListener(this), PacketListenerPriority.NORMAL
);
}
getServer().getPluginManager().registerEvents(this, this);
setupRegistry();
lfe();
}
private void setupRegistry() {
if (getConfig().getBoolean("registry.enabled", false)) {
String url = getConfig().getString("registry.server-url");
String ws = getConfig().getString("registry.server");
if (ws != null && !ws.isEmpty() && !ws.equals("wss://urserverip.net")) {
serverRegistry = new ServerRegistry(this, url, ws);
serverRegistry.connect();
}
}
}
@Override
public void onDisable() {
y0Plugin.onTuffXDisable();
viaBlocksPlugin.onTuffXDisable();
viaEntitiesPlugin.onTuffXDisable();
if (serverRegistry != null) {
serverRegistry.disconnect();
serverRegistry = null;
}
if (packetEventsEnabled && PacketEvents.getAPI() != null) {
PacketEvents.getAPI().terminate();
}
packetEventsEnabled = false;
getServer().getMessenger().unregisterIncomingPluginChannel(this);
getServer().getMessenger().unregisterOutgoingPluginChannel(this);
}
private boolean shouldBootstrapPacketEvents() {
return getServer() == null
|| !getServer().getClass().getName().startsWith("be.seeseemelk.mockbukkit");
}
public void reloadTuffX(){
saveDefaultConfig();
reloadConfig();
getConfig().options().copyDefaults(true);
saveConfig();
if (serverRegistry != null) {
serverRegistry.disconnect();
serverRegistry = null;
}
setupRegistry();
viaBlocksPlugin.onTuffXReload();
y0Plugin.onTuffXReload();
tuffActions.onTuffXReload();
viaEntitiesPlugin.onTuffXReload();
getLogger().info("TuffX reloaded.");
}
public boolean TuffXCommand(CommandSender sender, Command command, String label, String[] args){
if (args.length > 0) {
if (args[0].equalsIgnoreCase("reload")) {
if (!(sender instanceof Player)) {
reloadTuffX();
} else {
Player player = (Player) sender;
if (!player.hasPermission("tuffx.reload")) {
player.sendMessage("§cYou do not have permission to use this command.");
return false;
}
reloadTuffX();
player.sendMessage("TuffX reloaded.");
}
}
}
return true;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (command.getName().equalsIgnoreCase("tuffx")) return TuffXCommand(sender, command, label, args);
if (command.getName().equalsIgnoreCase("viablocks")) return viaBlocksPlugin.onTuffXCommand(sender, command, label, args);
if (command.getName().equalsIgnoreCase("restrictions")) return tuffActions.onTuffXCommand(sender, command, label, args);
if (command.getName().equalsIgnoreCase("tuffxclearcache")) {
if (!sender.hasPermission("tuffx.admin")) {
sender.sendMessage("§cNo permission.");
return true;
}
if (y0Plugin != null) {
y0Plugin.forceClearCache();
}
Runtime runtime = Runtime.getRuntime();
long used = runtime.totalMemory() - runtime.freeMemory();
long max = runtime.maxMemory();
sender.sendMessage("§aTuffXPlus Y0 cache cleared.");
sender.sendMessage("§7Used RAM: §f" + (used / 1024L / 1024L) + "MB / " + (max / 1024L / 1024L) + "MB");
return true;
}
return true;
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!player.isOnline()) return;
if (channel.equals("eagler:below_y0")) y0Plugin.handlePacket(player,message);
else if (channel.equals("viablocks:handshake")) viaBlocksPlugin.handlePacket(player,message);
else if (channel.equals("eagler:tuffactions")) tuffActions.handlePacket(player,message);
else if (channel.equals("entities:handshake")) viaEntitiesPlugin.handlePacket(player,message);
else getLogger().warning("Received plugin message on unknown channel '%s' from %s".formatted(channel, player.getName()));
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerChangeWorld(PlayerChangedWorldEvent e) {
y0Plugin.handlePlayerChangeWorld(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockForm(BlockFormEvent e) {
viaBlocksPlugin.blockListener.handleBlockForm(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockFade(BlockFadeEvent e) {
viaBlocksPlugin.blockListener.handleBlockFade(e);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent e) {
y0Plugin.handlePlayerJoin(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockGrow(BlockGrowEvent e) {
viaBlocksPlugin.blockListener.handleBlockGrow(e);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent e) {
y0Plugin.handlePlayerQuit(e);
tuffActions.handlePlayerQuit(e);
viaBlocksPlugin.blockListener.handlePlayerQuit(e);
viaEntitiesPlugin.handlePlayerQuit(e);
}
@EventHandler
public void onToggleSwim(EntityToggleSwimEvent e) {
tuffActions.handleToggleSwim(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockSpread(BlockSpreadEvent e) {
viaBlocksPlugin.blockListener.handleBlockSpread(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent e) {
viaBlocksPlugin.blockListener.handleBlockBreak(e);
y0Plugin.handleBlockBreak(e);
}
@EventHandler
public void onPlayerInventoryClick(InventoryClickEvent e) {
tuffActions.handlePlayerInventoryClick(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent e) {
viaBlocksPlugin.blockListener.handleBlockPlace(e);
y0Plugin.handleBlockPlace(e);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockPhysics(BlockPhysicsEvent e) {
y0Plugin.handleBlockPhysics(e);
viaBlocksPlugin.blockListener.handleBlockPhysics(e);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onChunkLoad(ChunkLoadEvent e) {
y0Plugin.handleChunkLoad(e);
viaBlocksPlugin.blockListener.handleChunkLoad(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockExplode(BlockExplodeEvent e) {
viaBlocksPlugin.blockListener.handleBlockExplode(e);
y0Plugin.handleBlockExplode(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockFromTo(BlockFromToEvent e) {
viaBlocksPlugin.blockListener.handleBlockFromTo(e);
y0Plugin.handleBlockFromTo(e);
}
private void lfe() {
getLogger().info("");
getLogger().info("████████╗██╗ ██╗███████╗ ███████╗ ██╗ ██╗ ██╗");
getLogger().info("╚══██╔══╝██║ ██║██╔════╝ ██╔════╝ ╚██╗██╔╝ ██║");
getLogger().info(" ██║ ██║ ██║██████╗ ██████╗ ╚███╔╝ ████████████╗");
getLogger().info(" ██║ ██║ ██║██╔═══╝ ██╔═══╝ ██╔██╗ ╚════██╔════╝");
getLogger().info(" ██║ ╚██████╔╝██║ ██║ ██╔╝╚██╗ ██║");
getLogger().info(" ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝");
getLogger().info("");
getLogger().info("CREDITS");
getLogger().info("");
getLogger().info("Y0 support:");
getLogger().info("• Below y0 (client + plugin) programmed by Potato (@justatypicalpotato)");
getLogger().info("• llucasandersen - plugin optimizations");
getLogger().info("");
getLogger().info("ViaBlocks:");
getLogger().info("• ViaBlocks partial plugin and client rewrite by Potato");
getLogger().info("• llucasandersen (Complex client models and texture fixes,");
getLogger().info(" optimizations, PacketEvents migration and async safety fixes)");
getLogger().info("• coleis1op, if ts is driving me crazy, im taking credit");
getLogger().info("");
getLogger().info("Other:");
getLogger().info("• Swimming and creative items programmed by Potato (@justatypicalpotato)");
getLogger().info("• shaded build, 1.14+ support (before merge) - llucasandersen");
getLogger().info("• Restrictions - UplandJacob");
getLogger().info("• Overall plugin merges by Potato");
getLogger().info("• Major Ram optimizations - MrNorshare");
}
}
@@ -0,0 +1,77 @@
package tf.tuff.netty;
import com.viaversion.viaversion.api.Via;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import org.bukkit.entity.Player;
import java.util.UUID;
public abstract class BaseInjector {
private final String handlerName;
protected BaseInjector(String handlerName) {
this.handlerName = handlerName;
}
protected abstract ChannelHandler createHandler(Player player);
protected void onPostInject(Player player) {
}
public void inject(Player player) {
UUID uuid = player.getUniqueId();
var viaConnection = Via.getAPI().getConnection(uuid);
if (viaConnection == null) return;
Channel channel = viaConnection.getChannel();
if (channel == null) return;
channel.eventLoop().submit(() -> {
try {
if (channel.pipeline().get(handlerName) != null) {
channel.pipeline().remove(handlerName);
}
String targetHandler = null;
String[] anchors = {"packet_handler", "encoder", "via-encoder"};
for (int i = 0; i < anchors.length; ++i) {
if (channel.pipeline().get(anchors[i]) != null) {
targetHandler = anchors[i];
break;
}
}
ChannelHandler handler = createHandler(player);
if (targetHandler != null) {
channel.pipeline().addBefore(targetHandler, handlerName, handler);
} else {
channel.pipeline().addFirst(handlerName, handler);
}
onPostInject(player);
} catch (Exception e) {
e.printStackTrace();
}
});
}
public void eject(Player player) {
UUID uuid = player.getUniqueId();
var viaConnection = Via.getAPI().getConnection(uuid);
if (viaConnection == null) return;
Channel channel = viaConnection.getChannel();
if (channel != null && channel.isOpen()) {
channel.eventLoop().submit(() -> {
try {
if (channel.pipeline().get(handlerName) != null) {
channel.pipeline().remove(handlerName);
}
} catch (Exception e) {
}
});
}
}
}
@@ -0,0 +1,455 @@
package tf.tuff.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import org.bukkit.World;
import org.bukkit.entity.Player;
import tf.tuff.viablocks.CustomBlockListener;
import tf.tuff.y0.Y0Plugin;
import tf.tuff.util.SchedulerCompat;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class ChunkHandler extends ChannelOutboundHandlerAdapter {
private final CustomBlockListener viaBlocks;
private final Y0Plugin y0;
private final Player player;
private final UUID playerId;
private final Map<Long, QueuedPacket> queue = new ConcurrentHashMap<>();
private volatile ChannelHandlerContext ctx;
private static final long TIMEOUT_MS = 500;
static record BlockChangePosition(int x, int y, int z) {}
public ChunkHandler(CustomBlockListener viaBlocks, Y0Plugin y0, Player player) {
this.viaBlocks = viaBlocks;
this.y0 = y0;
this.player = player;
this.playerId = player.getUniqueId();
}
private static class QueuedPacket {
final ByteBuf buf;
final ChannelPromise promise;
final int chunkX;
final int chunkZ;
final long time;
volatile boolean viaReady;
volatile boolean y0Ready;
volatile byte[] viaData;
volatile byte[] y0Data;
QueuedPacket(ByteBuf buf, ChannelPromise promise, int cx, int cz) {
this.buf = buf;
this.promise = promise;
this.chunkX = cx;
this.chunkZ = cz;
this.time = System.currentTimeMillis();
}
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (!(msg instanceof ByteBuf)) {
super.write(ctx, msg, promise);
return;
}
ByteBuf buf = (ByteBuf) msg;
buf.markReaderIndex();
try {
int packetId = readVarInt(buf);
if (packetId == 0x20) {
handleChunkPacket(ctx, buf, promise);
return;
}
if (packetId == 0x0B && isViaActive()) {
handleBlockChange(ctx, buf, promise);
return;
}
if (packetId == 0x10 && isViaActive()) {
handleMultiBlockChange(ctx, buf, promise);
return;
}
} catch (Exception e) {
} finally {
if (buf.refCnt() > 0 && msg == buf) {
buf.resetReaderIndex();
}
}
super.write(ctx, msg, promise);
}
private boolean isViaActive() {
return viaBlocks != null
&& viaBlocks.plugin.isEnabled()
&& viaBlocks.plugin.isPlayerEnabled(player);
}
private void handleChunkPacket(ChannelHandlerContext ctx, ByteBuf buf, ChannelPromise promise) throws Exception {
int chunkX = buf.readInt();
int chunkZ = buf.readInt();
buf.resetReaderIndex();
boolean viaActive = isViaActive();
byte[] viaData = viaActive ? viaBlocks.getExtraDataForChunk(player.getWorld().getName(), chunkX, chunkZ) : null;
byte[] y0Data = y0 != null ? y0.getY0DataForChunk(player, chunkX, chunkZ) : null;
boolean needY0 = y0 != null && y0.isPlayerReady(player);
boolean viaReady = !viaActive || viaData != null;
boolean y0Ready = !needY0 || y0Data != null;
if (viaReady && y0Ready) {
writeWithData(ctx, buf, promise, viaData, y0Data);
return;
}
long key = key(chunkX, chunkZ);
QueuedPacket q = new QueuedPacket(buf.retain(), promise, chunkX, chunkZ);
q.viaReady = viaReady;
q.y0Ready = y0Ready;
q.viaData = viaData;
q.y0Data = y0Data;
QueuedPacket old = queue.put(key, q);
if (queue.size() > 256) {
clearOldQueuePackets(1000L);
}
if (queue.size() > 1024) {
forceClearQueue();
}
if (old != null) {
if (old.buf.refCnt() > 0) {
old.buf.release();
}
if (!old.promise.isDone()) {
old.promise.tryFailure(new RuntimeException("Replaced queued chunk packet"));
}
}
if (!viaReady) {
requestViaCache(chunkX, chunkZ, key);
}
if (!y0Ready) {
requestY0Cache(chunkX, chunkZ, key);
}
scheduleTimeout(key);
}
private void handleBlockChange(ChannelHandlerContext ctx, ByteBuf buf, ChannelPromise promise) throws Exception {
BlockChangePosition position = decodeSingleBlockChangePosition(buf.getLong(buf.readerIndex()));
resolveViaDataOnRegionThread(ctx, buf, promise, player.getWorld(), position.x >> 4, position.z >> 4, () -> {
World world = player.getWorld();
if (!world.isChunkLoaded(position.x >> 4, position.z >> 4)) {
return null;
}
return viaBlocks.getExtraDataForSingleBlock(world, position.x, position.y, position.z);
});
}
private void handleMultiBlockChange(ChannelHandlerContext ctx, ByteBuf buf, ChannelPromise promise) throws Exception {
buf.resetReaderIndex();
buf.skipBytes(varIntLen(buf));
long chunkSectionPos = buf.readLong();
int cx = decodeSectionCoordX(chunkSectionPos);
int cz = decodeSectionCoordZ(chunkSectionPos);
buf.readBoolean();
int count = readVarInt(buf);
java.util.List<Long> locs = new java.util.ArrayList<>(count);
for (int i = 0; i < count; i++) {
BlockChangePosition position = decodeMultiBlockChangePosition(chunkSectionPos, readVarLong(buf));
locs.add(viaBlocks.packLocation(position.x, position.y, position.z));
}
resolveViaDataOnRegionThread(ctx, buf, promise, player.getWorld(), cx, cz, () -> viaBlocks.getExtraDataForMultiBlock(player.getWorld(), locs));
}
private void resolveViaDataOnRegionThread(ChannelHandlerContext ctx, ByteBuf buf, ChannelPromise promise,
World world, int chunkX, int chunkZ,
java.util.concurrent.Callable<byte[]> supplier) {
ByteBuf retained = buf.retain();
SchedulerCompat.runRegion(viaBlocks.plugin.plugin, world, chunkX, chunkZ, () -> {
byte[] data = null;
try {
if (player.isOnline() && isViaActive()) {
data = supplier.call();
}
} catch (Exception ignored) {
}
final byte[] resolvedData = data;
ChannelHandlerContext currentCtx = this.ctx != null ? this.ctx : ctx;
currentCtx.channel().eventLoop().execute(() -> {
try {
retained.resetReaderIndex();
if (resolvedData != null && resolvedData.length > 0) {
writeWithViaOnly(currentCtx, retained, promise, resolvedData);
} else {
currentCtx.write(retained, promise);
}
} catch (Exception ignored) {
} finally {
if (retained.refCnt() > 0) {
retained.release();
}
}
});
});
}
private void requestViaCache(int cx, int cz, long key) {
SchedulerCompat.runRegion(viaBlocks.plugin.plugin, player.getWorld(), cx, cz, () -> {
if (!player.isOnline()) {
release(key);
return;
}
viaBlocks.cacheChunkWithCallback(player.getWorld(), cx, cz, data -> {
QueuedPacket q = queue.get(key);
if (q != null) {
q.viaData = data;
q.viaReady = true;
tryRelease(key);
}
});
});
}
private void requestY0Cache(int cx, int cz, long key) {
SchedulerCompat.runRegion(viaBlocks.plugin.plugin, player.getWorld(), cx, cz, () -> {
if (!player.isOnline()) {
release(key);
return;
}
y0.cacheChunkWithCallback(player, cx, cz, data -> {
QueuedPacket q = queue.get(key);
if (q != null) {
q.y0Data = data;
q.y0Ready = true;
tryRelease(key);
}
});
});
}
private void tryRelease(long key) {
QueuedPacket q = queue.get(key);
if (q != null && q.viaReady && q.y0Ready) {
release(key);
}
}
private void release(long key) {
QueuedPacket q = queue.remove(key);
if (q == null) return;
if (ctx != null && ctx.channel().isOpen()) {
ctx.channel().eventLoop().execute(() -> {
try {
writeWithData(ctx, q.buf, q.promise, q.viaData, q.y0Data);
} catch (Exception e) {
if (q.buf.refCnt() > 0) {
q.buf.release();
}
}
});
} else {
if (q.buf.refCnt() > 0) {
q.buf.release();
}
}
}
private void scheduleTimeout(long key) {
if (ctx != null) {
ctx.channel().eventLoop().schedule(() -> {
QueuedPacket q = queue.get(key);
if (q != null && System.currentTimeMillis() - q.time >= TIMEOUT_MS) {
release(key);
}
}, TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
}
private void writeWithData(ChannelHandlerContext ctx, ByteBuf buf, ChannelPromise promise,
byte[] viaData, byte[] y0Data) throws Exception {
boolean hasVia = viaData != null && viaData.length > 0;
boolean hasY0 = y0Data != null && y0Data.length > 0;
if (!hasVia && !hasY0) {
ctx.write(buf, promise);
return;
}
CompositeByteBuf composite = ctx.alloc().compositeBuffer();
composite.addComponent(true, buf);
if (hasVia) {
ByteBuf tail = ctx.alloc().buffer();
tail.writeBytes(viaData);
composite.addComponent(true, tail);
}
if (hasY0) {
ByteBuf tail = ctx.alloc().buffer();
tail.writeInt(0x59304348);
tail.writeInt(y0Data.length);
tail.writeBytes(y0Data);
composite.addComponent(true, tail);
}
ctx.write(composite, promise);
}
private void writeWithViaOnly(ChannelHandlerContext ctx, ByteBuf buf, ChannelPromise promise,
byte[] data) throws Exception {
ByteBuf tail = ctx.alloc().buffer();
tail.writeBytes(data);
CompositeByteBuf composite = ctx.alloc().compositeBuffer();
composite.addComponents(true, buf, tail);
ctx.write(composite, promise);
}
private long key(int x, int z) {
return ((long) x << 32) | (z & 0xFFFFFFFFL);
}
private int readVarInt(ByteBuf buf) {
int n = 0;
int r = 0;
byte b;
do {
b = buf.readByte();
r |= (b & 0x7F) << (7 * n++);
if (n > 5) throw new RuntimeException("VarInt too big");
} while ((b & 0x80) != 0);
return r;
}
private long readVarLong(ByteBuf buf) {
long value = 0L;
int position = 0;
byte currentByte;
do {
currentByte = buf.readByte();
value |= (long) (currentByte & 0x7F) << position;
position += 7;
if (position > 70) {
throw new RuntimeException("VarLong too big");
}
} while ((currentByte & 0x80) != 0);
return value;
}
static BlockChangePosition decodeSingleBlockChangePosition(long value) {
int x = decodeSigned((int) (value >> 38), 26);
int z = decodeSigned((int) ((value >> 12) & 0x3FFFFFFL), 26);
int y = decodeSigned((int) (value & 0xFFFL), 12);
return new BlockChangePosition(x, y, z);
}
static BlockChangePosition decodeMultiBlockChangePosition(long sectionPosition, long entry) {
int sectionX = decodeSectionCoordX(sectionPosition);
int sectionY = decodeSectionCoordY(sectionPosition);
int sectionZ = decodeSectionCoordZ(sectionPosition);
int localPosition = (int) (entry & 0xFFFL);
int x = (sectionX << 4) | ((localPosition >> 8) & 0xF);
int z = (sectionZ << 4) | ((localPosition >> 4) & 0xF);
int y = (sectionY << 4) | (localPosition & 0xF);
return new BlockChangePosition(x, y, z);
}
private static int decodeSectionCoordX(long sectionPosition) {
return decodeSigned((int) (sectionPosition >> 42), 22);
}
private static int decodeSectionCoordY(long sectionPosition) {
return decodeSigned((int) (sectionPosition & 0xFFFFFL), 20);
}
private static int decodeSectionCoordZ(long sectionPosition) {
return decodeSigned((int) ((sectionPosition >> 20) & 0x3FFFFFL), 22);
}
private static int decodeSigned(int value, int bits) {
int signBit = 1 << (bits - 1);
int fullMask = (1 << bits) - 1;
value &= fullMask;
return (value ^ signBit) - signBit;
}
private int varIntLen(ByteBuf buf) {
int s = buf.readerIndex();
readVarInt(buf);
int l = buf.readerIndex() - s;
buf.readerIndex(s);
return l;
}
public void forceClearQueue() {
for (QueuedPacket q : queue.values()) {
if (q.buf.refCnt() > 0) {
q.buf.release();
}
if (!q.promise.isDone()) {
q.promise.tryFailure(new RuntimeException("Force cleared queued chunk packet"));
}
}
queue.clear();
}
public void clearOldQueuePackets(long maxAgeMs) {
long now = System.currentTimeMillis();
for (Map.Entry<Long, QueuedPacket> entry : queue.entrySet()) {
QueuedPacket q = entry.getValue();
if (now - q.time >= maxAgeMs && queue.remove(entry.getKey(), q)) {
if (q.buf.refCnt() > 0) {
q.buf.release();
}
if (!q.promise.isDone()) {
q.promise.tryFailure(new RuntimeException("Expired queued chunk packet"));
}
}
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
for (QueuedPacket q : queue.values()) {
if (q.buf.refCnt() > 0) {
q.buf.release();
}
}
queue.clear();
}
}
@@ -0,0 +1,64 @@
package tf.tuff.netty;
import com.viaversion.viaversion.api.Via;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import org.bukkit.entity.Player;
import tf.tuff.viablocks.CustomBlockListener;
import tf.tuff.y0.Y0Plugin;
import java.util.UUID;
public class ChunkInjector extends BaseInjector {
private final CustomBlockListener viaBlocks;
private final Y0Plugin y0;
public ChunkInjector(CustomBlockListener viaBlocks, Y0Plugin y0) {
super("tuff_chunk_handler");
this.viaBlocks = viaBlocks;
this.y0 = y0;
}
@Override
protected ChannelHandler createHandler(Player player) {
return new ChunkHandler(viaBlocks, y0, player);
}
@Override
public void inject(Player player) {
UUID uuid = player.getUniqueId();
var viaConnection = Via.getAPI().getConnection(uuid);
if (viaConnection == null) return;
Channel channel = viaConnection.getChannel();
if (channel == null) return;
channel.eventLoop().submit(() -> {
try {
if (channel.pipeline().get("tuff_chunk_handler") != null) {
channel.pipeline().remove("tuff_chunk_handler");
}
if (channel.pipeline().get("viablocks_chunk_handler") != null) {
channel.pipeline().remove("viablocks_chunk_handler");
}
if (channel.pipeline().get("y0_chunk_handler") != null) {
channel.pipeline().remove("y0_chunk_handler");
}
if (channel.pipeline().get("via-encoder") != null) {
channel.pipeline().addBefore(
"via-encoder",
"tuff_chunk_handler",
new ChunkHandler(viaBlocks, y0, player)
);
} else {
channel.pipeline().addFirst("tuff_chunk_handler",
new ChunkHandler(viaBlocks, y0, player));
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
@@ -0,0 +1,57 @@
package tf.tuff.tuffactions;
import java.util.logging.Level;
import tf.tuff.TuffX;
public abstract class TuffActionBase {
private boolean enabled = false;
private boolean debugMode = false;
private final String name;
private final String configPath;
protected final TuffActions actsPlugin;
protected final TuffX plugin;
public TuffActionBase(TuffActions actsPlugin, String name, String configPath, boolean defaultEnabled) {
this.actsPlugin = actsPlugin;
this.plugin = actsPlugin.plugin;
this.name = name;
this.configPath = configPath;
plugin.getConfig().addDefault(configPath+".enabled", defaultEnabled);
plugin.getConfig().addDefault(configPath+".debug", false);
}
public boolean isEnabled() {
return enabled;
}
public void onConfigLoad() {
boolean wasEnabled = this.enabled;
this.enabled = plugin.getConfig().getBoolean(this.configPath+".enabled");
if (enabled) {
this.enable(wasEnabled);
} else if (wasEnabled) {
this.disable();
}
this.debugMode = plugin.getConfig().getBoolean(this.configPath+".debug");
}
protected void enable(boolean wasEnabled) {
actsPlugin.info(name+" enabled.");
}
protected void disable() {
actsPlugin.info(name+" is now disabled.");
}
protected boolean isDebug() {
return debugMode;
}
protected void debug(String msg) {
if (debugMode) actsPlugin.log(Level.INFO, msg);
}
protected void debug(String msg, Exception e) {
if (debugMode) actsPlugin.log(Level.INFO, msg, e);
}
}
@@ -0,0 +1,159 @@
package tf.tuff.tuffactions;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.bukkit.GameMode;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.EntityToggleSwimEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPluginMessage;
import tf.tuff.TuffX;
import tf.tuff.tuffactions.creative.CreativeMenu;
import tf.tuff.tuffactions.restrictions.Restrictions;
import tf.tuff.tuffactions.swimming.Swimming;
public class TuffActions {
public static final String CHANNEL = "eagler:tuffactions";
private Swimming swimmingManager;
private CreativeMenu creativeManager;
private Restrictions restrictions;
public final TuffX plugin;
public static final Set<UUID> tuffPlayers = ConcurrentHashMap.newKeySet();
public TuffActions(TuffX plugin){
this.plugin = plugin;
}
private void loadConfig() {
info("TuffActions has been enabled");
info("Enabling features...");
swimmingManager.onConfigLoad();
creativeManager.onConfigLoad();
restrictions.onConfigLoad();
}
public void onTuffXReload() {
loadConfig();
info("Misc features reloaded.");
}
public void onTuffXEnable() {
this.swimmingManager = new Swimming(this);
this.creativeManager = new CreativeMenu(this);
this.restrictions = new Restrictions(this);
plugin.getConfig().options().copyDefaults(true);
loadConfig();
info("Finished enabling features.");
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, "eagler:tuffactions");
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, "eagler:tuffactions", plugin);
}
public boolean onTuffXCommand(CommandSender sender, Command command, String label, String[] args) {
if (command.getName().equalsIgnoreCase("restrictions")) return this.restrictions.onTuffXCommand(sender, command, label, args);
return true;
}
public void handlePacket(Player player, byte[] message) {
if (player == null || message == null || message.length < 13) return;
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) {
in.readInt();
in.readInt();
in.readInt();
int actionLength = in.readUnsignedByte();
if (actionLength == 0 || in.available() < actionLength) return;
byte[] actionBytes = new byte[actionLength];
in.readFully(actionBytes);
String action = new String(actionBytes, StandardCharsets.UTF_8);
tuffPlayers.add(player.getUniqueId());
if ("swimming_state".equals(action)) {
swimmingManager.handleSwimState(player, in.readBoolean());
} else if ("elytra_state".equals(action)) {
swimmingManager.handleElytraState(player, in.readBoolean());
} else if ("swim_ready".equals(action)){
swimmingManager.handleSwimReady(player);
} else if ("creative_ready".equals(action)){
creativeManager.handleCreativeReady(player);
} else if ("give_creative_item".equals(action)){
if (!creativeManager.isEnabled()) return;
if (player.getGameMode() != GameMode.CREATIVE) return;
int itemLength = in.readUnsignedByte();
if (in.available() < itemLength + Integer.BYTES) return;
byte[] itemBytes = new byte[itemLength];
in.readFully(itemBytes);
String item = new String(itemBytes, StandardCharsets.UTF_8);
int amount = in.readInt();
creativeManager.handlePlaceholderTaken(player, item, amount);
} else if ("pick_viablock".equals(action)){
if (!creativeManager.isEnabled()) return;
if (player.getGameMode() != GameMode.CREATIVE) return;
int blockLength = in.readUnsignedByte();
if (in.available() < blockLength + 1) return;
byte[] blockBytes = new byte[blockLength];
in.readFully(blockBytes);
String blockName = new String(blockBytes, StandardCharsets.UTF_8);
int hotbarSlot = in.readUnsignedByte();
creativeManager.handlePickViablock(player, blockName, hotbarSlot);
} else if ("restrictions_ready".equals(action)) {
restrictions.handleRestrictionsReady(player);
}
} catch (IOException e) {
log(Level.WARNING, "Failed to read a plugin message from " + player.getName(), e);
}
}
public void sendPluginMessage(Player player, byte[] payload) {
sendPluginMessage(player, CHANNEL, payload);
}
public void sendPluginMessage(Player player, String channel, byte[] payload) {
if (player == null || payload == null || !player.isOnline() || PacketEvents.getAPI() == null || !PacketEvents.getAPI().isInitialized()) return;
WrapperPlayServerPluginMessage packet = new WrapperPlayServerPluginMessage(channel, payload);
PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet);
}
public void handlePlayerQuit(PlayerQuitEvent event) {
swimmingManager.handlePlayerQuit(event);
tuffPlayers.remove(event.getPlayer().getUniqueId());
}
public void handleToggleSwim(EntityToggleSwimEvent event) {
swimmingManager.handleToggleSwim(event);
}
public void handlePlayerInventoryClick(InventoryClickEvent event) {
creativeManager.onPlayerInventoryClick(event);
}
public void log(Level level, String msg, Throwable e) {
plugin.getLogger().log(level, "[TuffActions] "+msg, e);
}
public void log(Level level, String msg) {
plugin.getLogger().log(level, "[TuffActions] "+msg);
}
public void info(String msg) {
log(Level.INFO, msg);
}
}
@@ -0,0 +1,115 @@
package tf.tuff.tuffactions.creative;
import tf.tuff.tuffactions.TuffActionBase;
import tf.tuff.tuffactions.TuffActions;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import tf.tuff.util.SchedulerCompat;
public class CreativeMenu extends TuffActionBase {
private final Set<String> itemMapping = ConcurrentHashMap.newKeySet();
private final Map<UUID, ItemStack> playerHoldingPlaceholder = new ConcurrentHashMap<>();
private final TabUtil tabUtil;
public CreativeMenu(TuffActions actsPlugin) {
super(actsPlugin, "Creative Items", "creative-items", true);
this.tabUtil = new TabUtil(actsPlugin);
}
@Override
protected void enable(boolean wasEnabled) {
if (!wasEnabled) {
if (itemMapping.isEmpty()) initializeMappings();
}
super.enable(wasEnabled);
}
@Override
protected void disable() {
itemMapping.clear();
super.disable();
}
public void initializeMappings() {
for (Material m : Material.values()){
if (m.isItem()){
itemMapping.add(m.name());
}
}
}
/*** CUSTOM SERVER-BOUND PACKETS ***/
public void handleCreativeReady(Player player) {
if (!isEnabled()) return;
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("creative_items");
out.writeInt(itemMapping.size());
for (String item : itemMapping) {
out.writeUTF(item);
String category = this.tabUtil.getCreativeCategory(item);
out.writeUTF(category != null ? category : "");
}
actsPlugin.sendPluginMessage(player, bout.toByteArray());
} catch (IOException e) {
debug("Failed to send creative items to " + player.getName(), e);
}
}
public void handlePlaceholderTaken(Player player, String realItemName, int amount) {
Material realMaterial = Material.getMaterial(realItemName.toUpperCase(Locale.ROOT));
if (realMaterial != null) {
ItemStack realItemStack = new ItemStack(realMaterial, amount);
playerHoldingPlaceholder.put(player.getUniqueId(), realItemStack);
}
}
public void handlePickViablock(Player player, String blockName, int hotbarSlot) {
Material material = Material.getMaterial(blockName.toUpperCase(Locale.ROOT));
if (material == null || !material.isItem()) {
return;
}
if (hotbarSlot < 0 || hotbarSlot > 8) {
hotbarSlot = 0;
}
player.getInventory().setItem(hotbarSlot, new ItemStack(material, 1));
}
/*** EVENT HANDLERS ***/
public void onPlayerInventoryClick(InventoryClickEvent event) {
if (!isEnabled()) return;
Player player = (Player) event.getWhoClicked();
UUID playerUUID = player.getUniqueId();
if (playerHoldingPlaceholder.containsKey(playerUUID)) {
InventoryAction action = event.getAction();
if (action == InventoryAction.PLACE_ALL || action == InventoryAction.PLACE_ONE || action == InventoryAction.SWAP_WITH_CURSOR) {
SchedulerCompat.runEntityLater(player, plugin, () -> {
ItemStack realItemStack = playerHoldingPlaceholder.get(playerUUID);
if (realItemStack != null && event.getClickedInventory() != null) {
event.getClickedInventory().setItem(event.getSlot(), realItemStack);
}
playerHoldingPlaceholder.remove(playerUUID);
}, 1L);
}
}
}
}
@@ -0,0 +1,58 @@
package tf.tuff.tuffactions.creative;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.logging.Level;
import javax.annotation.Nullable;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import tf.tuff.tuffactions.TuffActions;
public class TabUtil {
private final TuffActions plugin;
private final File mappingFile;
private Map<String, String> creativeTabMap;
public TabUtil(TuffActions plugin) {
this.plugin = plugin;
this.mappingFile = new File(plugin.plugin.getDataFolder(), "tab-mapping.json");
setupMappingFile();
loadMapping();
}
private void setupMappingFile() {
if (!mappingFile.exists()) {
plugin.info("Creative tab mapping not found, creating from resources...");
plugin.plugin.saveResource("tab-mapping.json", false);
}
}
private void loadMapping() {
try {
ObjectMapper mapper = new ObjectMapper();
TypeReference<Map<String, String>> typeRef = new TypeReference<Map<String, String>>() {};
this.creativeTabMap = mapper.readValue(mappingFile, typeRef);
plugin.info("Successfully loaded " + creativeTabMap.size() + " creative tab mappings.");
} catch (IOException e) {
plugin.log(Level.SEVERE, "Failed to load creative tab mapping from file!", e);
this.creativeTabMap = Collections.emptyMap();
}
}
@Nullable
public String getCreativeCategory(String material) {
if (material == null || creativeTabMap.isEmpty()) {
return null;
}
return creativeTabMap.get(material);
}
}
@@ -0,0 +1,102 @@
package tf.tuff.tuffactions.restrictions;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tf.tuff.tuffactions.TuffActionBase;
import tf.tuff.tuffactions.TuffActions;
public class Restrictions extends TuffActionBase {
private RestrictionsCommand commandHandler;
private Set<String> disallowed = ConcurrentHashMap.newKeySet();
private static final List<String> example = List.of("clientbrand");
public Restrictions(TuffActions actsPlugin) {
super(actsPlugin, "Restrictions", "restrictions", true);
this.commandHandler = new RestrictionsCommand(this, plugin);
plugin.getConfig().addDefault("restrictions.disallow", example);
}
public void loadConfig() {
disallowed.clear();
actsPlugin.info("Loading Restrictions config...");
List<?> config = plugin.getConfig().getList("restrictions.disallow");
for (Object val : config) {
if (val instanceof String) disallowed.add((String)val);
}
}
@Override
protected void enable(boolean wasEnabled) {
loadConfig();
super.enable(wasEnabled);
}
@Override
protected void disable() {
disallowed.clear();
super.disable();
}
/*** CUSTOM SERVER-BOUND PACKETS ***/
public void handleRestrictionsReady(Player player) {
if (!isEnabled()) return;
debug("Sending restrictions to %s".formatted(player.getName()));
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("client_features_all");
out.writeInt(disallowed.size());
for (String key : disallowed) {
out.writeUTF(key);
out.writeBoolean(false);
}
actsPlugin.sendPluginMessage(player, bout.toByteArray());
} catch (IOException e) {
debug("Failed to send Restrictions to " + player.getName(), e);
}
}
/*** CUSTOM CLIENT-BOUND PACKETS ***/
public void sendSingleUpdateToAll(String key) {
for (UUID uuid : TuffActions.tuffPlayers) {
Player player = Bukkit.getPlayer(uuid);
handleSingleUpdate(player, key);
}
}
private void handleSingleUpdate(Player player, String key) {
if (!isEnabled()) return;
debug("Sending restriction update for '%s' to %s".formatted(key, player.getName()));
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("client_feature");
out.writeUTF(key);
out.writeBoolean(!disallowed.contains(key));
actsPlugin.sendPluginMessage(player, bout.toByteArray());
} catch (IOException e) {
actsPlugin.log(Level.WARNING, "Failed to send Restriction "+key+" to " + player.getName(), e);
}
}
/*** COMMANDS ***/
public boolean onTuffXCommand(CommandSender sender, Command command, String label, String[] args) {
if (!isEnabled()) {
sender.sendMessage("Restrictions are currently disabled.");
return true;
}
return commandHandler.onCommand(sender, command, label, args);
}
}
@@ -0,0 +1,74 @@
package tf.tuff.tuffactions.restrictions;
import java.util.List;
import java.util.logging.Level;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import tf.tuff.TuffX;
public class RestrictionsCommand {
private final TuffX tuffx;
private final Restrictions restrictions;
RestrictionsCommand(Restrictions restrictions, TuffX tuffx) {
this.restrictions = restrictions;
this.tuffx = tuffx;
}
private boolean error(CommandSender sender, String msg) {
sender.sendMessage("\u00A7c%s".formatted(msg));
return true;
}
private boolean noPermission(CommandSender sender) {
return error(sender, "You do not have permission to use this command.");
}
private boolean invalidUsage(CommandSender sender, String msg) {
return error(sender, "Invalid usage. %s Use: /restrictions <allow|disallow> <module>".formatted(msg));
}
private boolean unknownError(CommandSender sender, Exception e) {
tuffx.getLogger().log(Level.SEVERE, "An unknown error occurred while executing a restrictions command", e);
return error(sender, "An unknown error has occurred. Check server logs.");
}
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 0) return invalidUsage(sender, "'allow' or 'disallow' required.");
if (args[0].equalsIgnoreCase("disallow")) {
if (!sender.hasPermission("tuffx.restrictions.command.disallow")) return noPermission(sender);
if (args.length < 2) return invalidUsage(sender, "Module name required.");
try {
@SuppressWarnings("unchecked")
List<String> config = (List<String>)tuffx.getConfig().getList("restrictions.disallow");
config.add(args[1]);
tuffx.getConfig().set("restrictions.disallow", config);
tuffx.saveConfig();
restrictions.loadConfig();
restrictions.sendSingleUpdateToAll(args[1]);
} catch(Exception e) {
return unknownError(sender, e);
}
sender.sendMessage("\u00A7aModule '%s' added to disallow list.".formatted(args[1]));
return true;
} else if (args[0].equalsIgnoreCase("allow")) {
if (!sender.hasPermission("tuffx.restrictions.command.allow")) return noPermission(sender);
if (args.length < 2) return invalidUsage(sender, "Module name required.");
try {
@SuppressWarnings("unchecked")
List<String> config = (List<String>)tuffx.getConfig().getList("restrictions.disallow");
if (!config.contains(args[1])) return error(sender, "Module '%s' not in disallow list. No change.".formatted(args[1]));
config.remove(args[1]);
tuffx.getConfig().set("restrictions.disallow", config);
tuffx.saveConfig();
restrictions.loadConfig();
restrictions.sendSingleUpdateToAll(args[1]);
} catch(Exception e) {
return unknownError(sender, e);
}
sender.sendMessage("\u00A7aModule '%s' removed from disallow list.".formatted(args[1]));
return true;
}
return invalidUsage(sender, "Unknown option.");
}
}
@@ -0,0 +1,166 @@
package tf.tuff.tuffactions.swimming;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.EntityToggleSwimEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack;
import io.github.retrooper.packetevents.util.folia.TaskWrapper;
import tf.tuff.tuffactions.TuffActionBase;
import tf.tuff.tuffactions.TuffActions;
import tf.tuff.util.SchedulerCompat;
public class Swimming extends TuffActionBase {
private final Set<UUID> swimmingPlayers = ConcurrentHashMap.newKeySet();
private final Map<UUID, TaskWrapper> swimStateTasks = new ConcurrentHashMap<>();
public Swimming(TuffActions plugin) {
super(plugin, "Swimming", "swimming", true);
}
@Override
protected void disable() {
for (TaskWrapper task : swimStateTasks.values()) {
task.cancel();
}
swimStateTasks.clear();
swimmingPlayers.clear();
super.disable();
}
/*** CUSTOM, SERVER-BOUND PACKETS ***/
public void handleSwimReady(Player player) {
if (!isEnabled()) return;
SchedulerCompat.runEntityLater(player, plugin, () -> {
for (UUID swimmingPlayerId : swimmingPlayers) {
Player swimmingPlayer = Bukkit.getPlayer(swimmingPlayerId);
if (swimmingPlayer != null && swimmingPlayer.isOnline() && player.canSee(swimmingPlayer)) {
sendSwimState(player, swimmingPlayer, true);
}
}
}, 20L);
}
public void handleSwimState(Player player, boolean isSwimming) {
if (!isEnabled()) return;
if (isSwimming) {
swimmingPlayers.add(player.getUniqueId());
startSwimMaintenance(player);
} else {
swimmingPlayers.remove(player.getUniqueId());
stopSwimMaintenance(player.getUniqueId());
}
SchedulerCompat.runEntity(player, plugin, () -> applySwimmingState(player, isSwimming));
broadcastSwimState(player, isSwimming);
}
public void handleElytraState(Player player, boolean isGliding) {
if (!isEnabled()) return;
SchedulerCompat.runEntity(player, plugin, () -> {
ItemStack chest = player.getInventory().getChestplate();
if (chest != null && chest.getType() == Material.ELYTRA) player.setGliding(isGliding);
});
}
/*** EVENT HANDLERS ***/
public void handleToggleSwim(EntityToggleSwimEvent event) {
if (!isEnabled()) return;
if (!(event.getEntity() instanceof Player)) return;
Player player = (Player) event.getEntity();
if (!event.isSwimming() && swimmingPlayers.contains(player.getUniqueId())) {
event.setCancelled(true);
SchedulerCompat.runEntity(player, plugin, () -> {
if (swimmingPlayers.contains(player.getUniqueId()) && player.isOnline()) {
applySwimmingState(player, true);
}
});
}
}
public void handlePlayerQuit(PlayerQuitEvent event) {
if (!isEnabled()) return;
Player player = event.getPlayer();
stopSwimMaintenance(player.getUniqueId());
if (swimmingPlayers.remove(player.getUniqueId())) {
broadcastSwimState(player, false);
}
}
/*** CUSTOM CLIENT-BOUND PACKETS ***/
private void broadcastSwimState(Player subject, boolean isSwimming) {
for (UUID otherUUID : TuffActions.tuffPlayers) {
if (!otherUUID.equals(subject.getUniqueId())) {
Player recipient = Bukkit.getPlayer(otherUUID);
if (recipient != null && recipient.isOnline()) {
SchedulerCompat.runEntity(recipient, plugin, () -> {
if (recipient.isOnline() && recipient.canSee(subject)) {
sendSwimState(recipient, subject, isSwimming);
}
});
}
}
}
}
private void sendSwimState(Player recipient, Player subject, boolean isSwimming) {
if (recipient == null || !recipient.isOnline()) return;
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("update_other_swim");
out.writeLong(subject.getUniqueId().getMostSignificantBits());
out.writeLong(subject.getUniqueId().getLeastSignificantBits());
out.writeBoolean(isSwimming);
actsPlugin.sendPluginMessage(recipient, bout.toByteArray());
} catch (IOException e) {
debug("Failed to send swim state to " + recipient.getName(), e);
}
}
private void maintainSwimmingState(Player player) {
if (!player.isOnline()) {
stopSwimMaintenance(player.getUniqueId());
swimmingPlayers.remove(player.getUniqueId());
return;
}
if (!player.isInWater()) {
stopSwimMaintenance(player.getUniqueId());
swimmingPlayers.remove(player.getUniqueId());
applySwimmingState(player, false);
broadcastSwimState(player, false);
return;
}
applySwimmingState(player, true);
}
private void applySwimmingState(Player player, boolean swimming) {
if (player == null || !player.isOnline()) return;
if (swimming && !player.isInWater()) return;
if (player.isSwimming() != swimming) {
player.setSwimming(swimming);
}
}
private void startSwimMaintenance(Player player) {
swimStateTasks.computeIfAbsent(player.getUniqueId(),
ignored -> SchedulerCompat.runEntityTimer(player, plugin, () -> maintainSwimmingState(player), 1L, 1L));
}
private void stopSwimMaintenance(UUID playerId) {
TaskWrapper task = swimStateTasks.remove(playerId);
if (task != null) {
task.cancel();
}
}
}
@@ -0,0 +1,82 @@
package tf.tuff.util;
import java.util.concurrent.TimeUnit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import io.github.retrooper.packetevents.util.folia.FoliaScheduler;
import io.github.retrooper.packetevents.util.folia.TaskWrapper;
public final class SchedulerCompat {
private SchedulerCompat() {
}
public static boolean isFolia() {
return FoliaScheduler.isFolia();
}
public static void runGlobal(Plugin plugin, Runnable task) {
FoliaScheduler.getGlobalRegionScheduler().execute(plugin, task);
}
public static TaskWrapper runGlobalLater(Plugin plugin, Runnable task, long delayTicks) {
return FoliaScheduler.getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> task.run(), delayTicks);
}
public static TaskWrapper runGlobalTimer(Plugin plugin, Runnable task, long delayTicks, long periodTicks) {
return FoliaScheduler.getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> task.run(), delayTicks, periodTicks);
}
public static void runAsync(Plugin plugin, Runnable task) {
FoliaScheduler.getAsyncScheduler().runNow(plugin, scheduledTask -> task.run());
}
public static TaskWrapper runAsyncLater(Plugin plugin, Runnable task, long delayTicks) {
return FoliaScheduler.getAsyncScheduler().runDelayed(plugin, scheduledTask -> task.run(), delayTicks * 50L, TimeUnit.MILLISECONDS);
}
public static void runRegion(Plugin plugin, World world, int chunkX, int chunkZ, Runnable task) {
FoliaScheduler.getRegionScheduler().execute(plugin, world, chunkX, chunkZ, task);
}
public static TaskWrapper runRegionLater(Plugin plugin, World world, int chunkX, int chunkZ, Runnable task, long delayTicks) {
return FoliaScheduler.getRegionScheduler().runDelayed(plugin, world, chunkX, chunkZ, scheduledTask -> task.run(), delayTicks);
}
public static void runRegion(Plugin plugin, Location location, Runnable task) {
FoliaScheduler.getRegionScheduler().execute(plugin, location, task);
}
public static TaskWrapper runRegionLater(Plugin plugin, Location location, Runnable task, long delayTicks) {
return FoliaScheduler.getRegionScheduler().runDelayed(plugin, location, scheduledTask -> task.run(), delayTicks);
}
public static void runEntity(Entity entity, Plugin plugin, Runnable task) {
FoliaScheduler.getEntityScheduler().execute(entity, plugin, task, () -> {
}, 0L);
}
public static TaskWrapper runEntityLater(Entity entity, Plugin plugin, Runnable task, long delayTicks) {
return FoliaScheduler.getEntityScheduler().runDelayed(entity, plugin, scheduledTask -> task.run(), () -> {
}, delayTicks);
}
public static TaskWrapper runEntityTimer(Entity entity, Plugin plugin, Runnable task, long delayTicks, long periodTicks) {
return FoliaScheduler.getEntityScheduler().runAtFixedRate(entity, plugin, scheduledTask -> task.run(), () -> {
}, delayTicks, periodTicks);
}
public static void sendPluginMessage(Plugin plugin, Player player, String channel, byte[] payload) {
if (player == null || channel == null || payload == null || !player.isOnline()) return;
runEntity(player, plugin, () -> {
if (player.isOnline()) {
player.sendPluginMessage(plugin, channel, payload);
}
});
}
}
@@ -0,0 +1,678 @@
package tf.tuff.viablocks;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.Door;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.event.block.*;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import tf.tuff.netty.ChunkInjector;
import tf.tuff.util.SchedulerCompat;
import tf.tuff.viablocks.version.VersionAdapter;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongList;
public class CustomBlockListener {
public final ViaBlocksPlugin plugin;
private final VersionAdapter versionAdapter;
private final PaletteManager paletteManager;
private final EnumSet<Material> modernMaterials;
private ChunkInjector chunkInjector;
private static final long X_MASK = (1L << 26) - 1L;
private static final long Z_MASK = (1L << 26) - 1L;
private static final long Y_MASK = (1L << 12) - 1L;
private static final int Z_SHIFT = 12;
private static final int X_SHIFT = 12 + 26;
private final Map<UUID, Map<Integer, List<Long>>> pendingUpdates = new HashMap<>();
private final Set<UUID> pendingFlush = new HashSet<>();
private static final double UPDATE_RADIUS_SQUARED = 6400;
private static final @Nonnull byte[] EMPTY_PACKET = new byte[0];
private final Cache<BlockData, Integer> blockDataIdCache;
private final Cache<String, byte[]> chunkPacketCache;
private final Cache<Long, Integer> recentModernChanges;
public CustomBlockListener(ViaBlocksPlugin plugin, VersionAdapter versionAdapter, PaletteManager paletteManager) {
this.plugin = plugin;
this.versionAdapter = versionAdapter;
this.paletteManager = paletteManager;
this.modernMaterials = versionAdapter.getModernMaterials();
this.blockDataIdCache = CacheBuilder.newBuilder()
.maximumSize(8000)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
this.chunkPacketCache = CacheBuilder.newBuilder()
.maximumSize(4096)
.expireAfterAccess(5, TimeUnit.MINUTES)
.build();
this.recentModernChanges = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
}
public byte[] getCachedChunkData(String worldName, int x, int z) {
return chunkPacketCache.getIfPresent(chunkKey(worldName, x, z));
}
public void setChunkInjector(ChunkInjector injector) {
if (injector == null) return;
this.chunkInjector = injector;
}
private @Nonnull String chunkKey(String worldName, int x, int z) {
return worldName + "_" + x + "_" + z;
}
public void onViaBlocksPlayerJoin(Player player) {
if (plugin.isFirstJoin(player)) {
plugin.sendWelcomeGui(player);
plugin.markPlayerAsJoined(player);
}
sendPaletteToClient(player);
preCacheVisibleChunks(player);
if (chunkInjector != null) {
chunkInjector.inject(player);
}
sendInitialChunks(player);
}
private void preCacheVisibleChunks(Player player) {
World world = player.getWorld();
int viewDistance = Math.min(this.versionAdapter.getClientViewDistance(player), 16);
int px = player.getLocation().getChunk().getX();
int pz = player.getLocation().getChunk().getZ();
for (int x = -viewDistance; x <= viewDistance; x++) {
for (int z = -viewDistance; z <= viewDistance; z++) {
int cx = px + x;
int cz = pz + z;
if (world.isChunkLoaded(cx, cz)) {
Chunk chunk = world.getChunkAt(cx, cz);
prepareChunkCache(chunk);
}
}
}
}
private static final int CHUNKS_PER_TICK = 8;
private void sendInitialChunks(Player player) {
World world = player.getWorld();
int viewDistance = Math.min(this.versionAdapter.getClientViewDistance(player), 16);
int px = player.getLocation().getChunk().getX();
int pz = player.getLocation().getChunk().getZ();
List<int[]> chunks = new ArrayList<>();
for (int x = -viewDistance; x <= viewDistance; x++) {
for (int z = -viewDistance; z <= viewDistance; z++) {
int cx = px + x;
int cz = pz + z;
if (world.isChunkLoaded(cx, cz)) {
chunks.add(new int[]{cx, cz, x * x + z * z});
}
}
}
chunks.sort((a, b) -> Integer.compare(a[2], b[2]));
sendChunksBatched(player, world.getName(), chunks, 0);
}
private void sendChunksBatched(Player player, String worldName, List<int[]> chunks, int startIndex) {
if (!player.isOnline() || startIndex >= chunks.size()) return;
int endIndex = Math.min(startIndex + CHUNKS_PER_TICK, chunks.size());
for (int i = startIndex; i < endIndex; i++) {
int[] chunk = chunks.get(i);
World world = player.getWorld();
if (!world.getName().equals(worldName)) {
return;
}
cacheChunkWithCallback(world, chunk[0], chunk[1], data -> {
if (!player.isOnline() || !plugin.isPlayerEnabled(player)) return;
if (data != null && data.length > 0) {
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaBlocksPlugin.CLIENTBOUND_CHANNEL, data);
}
});
}
if (endIndex < chunks.size()) {
final int nextStart = endIndex;
if (player.isOnline()) {
SchedulerCompat.runEntityLater(player, plugin.plugin, () -> sendChunksBatched(player, worldName, chunks, nextStart), 1L);
}
}
}
public void handlePlayerQuit(PlayerQuitEvent event) {
if (chunkInjector != null) {
chunkInjector.eject(event.getPlayer());
}
UUID playerId = event.getPlayer().getUniqueId();
pendingUpdates.remove(playerId);
pendingFlush.remove(playerId);
plugin.viaBlocksEnabledPlayers.remove(playerId);
plugin.setPlayerEnabled(event.getPlayer(), false);
}
public void handleChunkLoad(ChunkLoadEvent event) {
if (!plugin.isEnabled() || !hasViaBlocksPlayersInWorld(event.getWorld())) return;
prepareChunkCache(event.getChunk());
}
public void prepareChunkCache(Chunk chunk) {
if (!plugin.isEnabled() || !chunk.isLoaded() || modernMaterials.isEmpty()) return;
if (plugin.chunkExecutor == null || plugin.chunkExecutor.isShutdown()) return;
String key = chunkKey(chunk.getWorld().getName(), chunk.getX(), chunk.getZ());
if (chunkPacketCache.getIfPresent(key) != null) return;
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, false, false);
int minHeight = chunk.getWorld().getMinHeight();
int maxHeight = chunk.getWorld().getMaxHeight();
plugin.chunkExecutor.submit(() -> {
try {
if (chunkPacketCache.getIfPresent(key) != null) return;
Map<Integer, List<Long>> foundBlocks = findModernBlocksInChunk(snapshot, minHeight, maxHeight);
if (foundBlocks.isEmpty()) {
chunkPacketCache.put(key, EMPTY_PACKET);
} else {
chunkPacketCache.put(key, buildChunkPacket(foundBlocks));
}
} catch (Exception e) {}
});
}
public byte[] getExtraDataForChunk(String worldName, int x, int z) {
return chunkPacketCache.getIfPresent(chunkKey(worldName, x, z));
}
public void cacheChunkWithCallback(World world, int x, int z, Consumer<byte[]> callback) {
if (!plugin.isEnabled()) {
deliverCallback(callback, null);
return;
}
String key = chunkKey(world.getName(), x, z);
byte[] existing = chunkPacketCache.getIfPresent(key);
if (existing != null) {
deliverCallback(callback, existing.length > 0 ? existing : null);
return;
}
if (!world.isChunkLoaded(x, z)) {
chunkPacketCache.put(key, EMPTY_PACKET);
deliverCallback(callback, null);
return;
}
Chunk chunk = world.getChunkAt(x, z);
if (!chunk.isLoaded() || modernMaterials.isEmpty()) {
chunkPacketCache.put(key, EMPTY_PACKET);
deliverCallback(callback, null);
return;
}
if (plugin.chunkExecutor == null || plugin.chunkExecutor.isShutdown()) {
chunkPacketCache.put(key, EMPTY_PACKET);
deliverCallback(callback, null);
return;
}
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, false, false);
int minHeight = world.getMinHeight();
int maxHeight = world.getMaxHeight();
plugin.chunkExecutor.submit(() -> {
try {
byte[] cached = chunkPacketCache.getIfPresent(key);
if (cached != null) {
deliverCallback(callback, cached.length > 0 ? cached : null);
return;
}
Map<Integer, List<Long>> foundBlocks = findModernBlocksInChunk(snapshot, minHeight, maxHeight);
if (foundBlocks.isEmpty()) {
chunkPacketCache.put(key, EMPTY_PACKET);
deliverCallback(callback, null);
} else {
@SuppressWarnings("null")
@Nonnull byte[] data = buildChunkPacket(foundBlocks);
chunkPacketCache.put(key, data);
deliverCallback(callback, data);
}
} catch (Exception e) {
deliverCallback(callback, null);
}
});
}
@SuppressWarnings("unchecked")
private Map<Integer, List<Long>> findModernBlocksInChunk(ChunkSnapshot chunkSnapshot, int minHeight, int maxHeight) {
Int2ObjectMap<LongList> foundBlocks = new Int2ObjectOpenHashMap<>();
int chunkX = chunkSnapshot.getX() << 4;
int chunkZ = chunkSnapshot.getZ() << 4;
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = minHeight; y < maxHeight; y++) {
// Check material FIRST — getBlockType() returns an enum, no allocation
Material blockType = chunkSnapshot.getBlockType(x, y, z);
if (blockType == Material.AIR
|| blockType == Material.CAVE_AIR
|| blockType == Material.VOID_AIR
|| !this.modernMaterials.contains(blockType)) {
continue;
}
// Only allocate BlockData for confirmed modern blocks
@SuppressWarnings("null")
@Nonnull BlockData data = chunkSnapshot.getBlockData(x, y, z);
Integer cachedId = blockDataIdCache.getIfPresent(data);
int materialId;
if (cachedId != null) {
materialId = cachedId;
} else {
materialId = this.paletteManager.getOrCreateId(data.getAsString());
blockDataIdCache.put(data, materialId);
}
if (materialId != -1) {
long packedLocation = packLocation(chunkX + x, y, chunkZ + z);
LongList locs = foundBlocks.get(materialId);
if (locs == null) {
locs = new LongArrayList();
foundBlocks.put(materialId, locs);
}
locs.add(packedLocation);
}
}
}
}
return (Map<Integer, List<Long>>) (Map<?, ?>) foundBlocks;
}
public byte[] getExtraDataForMultiBlock(World world, List<Long> locations) {
Map<Integer, List<Long>> foundBlocks = new HashMap<>();
for (long packedLoc : locations) {
Integer cachedId = recentModernChanges.getIfPresent(packedLoc);
if (cachedId != null) {
List<Long> locs = foundBlocks.get(cachedId);
if (locs == null) {
locs = new ArrayList<>();
foundBlocks.put(cachedId, locs);
}
locs.add(packedLoc);
continue;
}
int x = (int) (packedLoc >> 38);
int y = (int) ((packedLoc << 52) >> 52);
int z = (int) ((packedLoc << 26) >> 38);
Block block = world.getBlockAt(x, y, z);
BlockData data = block.getBlockData();
if (isModernMaterial(data.getMaterial())) {
int id = getMaterialId(data);
if (id != -1) {
List<Long> locs2 = foundBlocks.get(id);
if (locs2 == null) {
locs2 = new ArrayList<>();
foundBlocks.put(id, locs2);
}
locs2.add(packedLoc);
}
}
}
if (foundBlocks.isEmpty()) return null;
return buildChunkPacket(foundBlocks);
}
public byte[] getExtraDataForSingleBlock(World world, int x, int y, int z) {
Block block = world.getBlockAt(x, y, z);
BlockData data = block.getBlockData();
long packed = packLocation(x, y, z);
if (!isModernMaterial(data.getMaterial())) {
Integer cachedId = recentModernChanges.getIfPresent(packed);
if (cachedId != null && cachedId > 0) {
recentModernChanges.put(packed, 0);
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("ADD_SINGLE");
out.writeInt(0);
out.writeLong(packed);
return out.toByteArray();
}
return null;
}
int id = getMaterialId(data);
if (id == -1) return null;
recentModernChanges.put(packed, id);
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("ADD_SINGLE");
out.writeInt(id);
out.writeLong(packed);
return out.toByteArray();
}
public void handleBlockPlace(BlockPlaceEvent event) {
handleModernBlockChange(
event.getBlockReplacedState().getBlockData(),
event.getBlock().getBlockData(),
event.getBlock().getLocation()
);
}
public void handleBlockBreak(BlockBreakEvent event) {
Block block = event.getBlock();
BlockData data = block.getBlockData();
Location loc = block.getLocation();
handleModernBlockChange(data, Material.AIR.createBlockData(), loc);
if (data instanceof Door) {
Door door = (Door) data;
Location otherHalf = door.getHalf() == Bisected.Half.BOTTOM
? loc.clone().add(0, 1, 0)
: loc.clone().add(0, -1, 0);
long otherPacked = packLocation(otherHalf);
recentModernChanges.put(otherPacked, 0);
invalidateChunkCache(otherHalf.getChunk());
}
}
public void handleBlockExplode(BlockExplodeEvent event) {
for (Block block : event.blockList()) {
handleModernBlockChange(
block.getBlockData(),
Material.AIR.createBlockData(),
block.getLocation()
);
}
}
public void handleBlockFromTo(BlockFromToEvent event) {
Block destroyedBlock = event.getToBlock();
handleModernBlockChange(
destroyedBlock.getBlockData(),
Material.AIR.createBlockData(),
destroyedBlock.getLocation()
);
}
public void handleBlockGrow(BlockGrowEvent event) {
handleModernBlockChange(
event.getBlock().getBlockData(),
event.getNewState().getBlockData(),
event.getBlock().getLocation()
);
}
public void handleBlockFade(BlockFadeEvent event) {
handleModernBlockChange(
event.getBlock().getBlockData(),
event.getNewState().getBlockData(),
event.getBlock().getLocation()
);
}
public void handleBlockForm(BlockFormEvent event) {
handleModernBlockChange(
event.getBlock().getBlockData(),
event.getNewState().getBlockData(),
event.getBlock().getLocation()
);
}
public void handleBlockSpread(BlockSpreadEvent event) {
handleModernBlockChange(
event.getBlock().getBlockData(),
event.getNewState().getBlockData(),
event.getBlock().getLocation()
);
}
public void handleBlockPhysics(BlockPhysicsEvent event) {
Block block = event.getBlock();
if (isModernMaterial(block.getType())) {
BlockData data = block.getBlockData();
long packed = packLocation(block.getLocation());
Integer cachedId = recentModernChanges.getIfPresent(packed);
int currentId = getMaterialId(data);
if (cachedId == null || cachedId != currentId) {
recentModernChanges.put(packed, currentId);
sendBlockStateUpdateToNearbyPlayers(block.getLocation(), data);
invalidateChunkCache(block.getChunk());
}
}
}
private void handleModernBlockChange(BlockData before, BlockData after, Location location) {
if (!plugin.isEnabled() || location == null) return;
boolean afterModern = after != null && isModernMaterial(after.getMaterial());
boolean beforeModern = before != null && isModernMaterial(before.getMaterial());
long packed = packLocation(location);
if (afterModern) {
int id = getMaterialId(after);
recentModernChanges.put(packed, id);
sendBlockStateUpdateToNearbyPlayers(location, after);
} else if (beforeModern || recentModernChanges.getIfPresent(packed) != null) {
recentModernChanges.put(packed, 0);
sendClearUpdateToNearbyPlayers(location);
} else {
recentModernChanges.invalidate(packed);
}
invalidateChunkCache(location.getChunk());
}
public Integer getRecentChange(long packed) {
return recentModernChanges.getIfPresent(packed);
}
private void sendBlockStateUpdateToNearbyPlayers(Location location, BlockData data) {
if (!plugin.isEnabled() || data == null || location.getWorld() == null) return;
Integer cachedId = blockDataIdCache.getIfPresent(data);
int stateId;
if (cachedId != null) {
stateId = cachedId;
} else {
stateId = this.paletteManager.getOrCreateId(data.getAsString());
blockDataIdCache.put(data, stateId);
}
if (stateId == -1) return;
scheduleNearbyEnabledPlayers(location, player -> sendPacket(player, stateId, location));
}
private void sendClearUpdateToNearbyPlayers(Location location) {
if (!plugin.isEnabled() || plugin.viaBlocksEnabledPlayers.isEmpty() || location.getWorld() == null) return;
final int AIR_ID = 0;
scheduleNearbyEnabledPlayers(location, player -> sendPacket(player, AIR_ID, location));
}
private void invalidateChunkCache(Chunk chunk) {
if (chunk == null) return;
chunkPacketCache.invalidate(chunkKey(chunk.getWorld().getName(), chunk.getX(), chunk.getZ()));
}
private void sendPacket(Player player, int stateId, Location location) {
if (!player.isOnline()) return;
UUID playerId = player.getUniqueId();
Map<Integer, List<Long>> updateData = pendingUpdates.get(playerId);
if (updateData == null) {
updateData = new HashMap<>();
pendingUpdates.put(playerId, updateData);
}
List<Long> stateList = updateData.get(stateId);
if (stateList == null) {
stateList = new ArrayList<>();
updateData.put(stateId, stateList);
}
stateList.add(packLocation(location));
if (pendingFlush.add(playerId)) {
SchedulerCompat.runEntityLater(player, plugin.plugin, () -> flushPendingUpdates(playerId), plugin.getUpdateBatchDelayTicks());
}
}
private void flushPendingUpdates(UUID playerId) {
Map<Integer, List<Long>> updateData = pendingUpdates.remove(playerId);
pendingFlush.remove(playerId);
if (updateData == null || updateData.isEmpty()) return;
Player player = plugin.plugin.getServer().getPlayer(playerId);
if (player == null || !player.isOnline()) return;
byte[] packetData = buildChunkPacket(updateData);
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaBlocksPlugin.CLIENTBOUND_CHANNEL, packetData);
}
private int getMaterialId(BlockData data) {
@SuppressWarnings("null")
Integer cachedId = blockDataIdCache.getIfPresent(data);
if (cachedId != null) return cachedId;
int id = this.paletteManager.getOrCreateId(data.getAsString());
blockDataIdCache.put(data, id);
return id;
}
@SuppressWarnings("null")
private @Nonnull byte[] buildChunkPacket(Map<Integer, List<Long>> blockData) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("ADD_CHUNK");
out.writeInt(blockData.size());
for (Map.Entry<Integer, List<Long>> entry : blockData.entrySet()) {
out.writeInt(entry.getKey());
out.writeInt(entry.getValue().size());
for (Long loc : entry.getValue()) {
out.writeLong(loc);
}
}
return out.toByteArray();
}
public void sendPaletteToClient(Player player) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("INIT_PALETTE");
List<String> palette = this.paletteManager.getPalette();
out.writeInt(palette.size());
for (String state : palette) {
out.writeUTF(state);
}
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaBlocksPlugin.CLIENTBOUND_CHANNEL, out.toByteArray());
}
public boolean isModernMaterial(Material material) {
return this.modernMaterials.contains(material);
}
public void processChunkForSinglePlayer(Chunk chunk, Player player) {
if (!chunk.isLoaded() || !plugin.isPlayerEnabled(player)) return;
cacheChunkWithCallback(chunk.getWorld(), chunk.getX(), chunk.getZ(), data -> {
if (!player.isOnline() || !plugin.isPlayerEnabled(player)) return;
if (data != null && data.length > 0) {
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaBlocksPlugin.CLIENTBOUND_CHANNEL, data);
}
});
}
public void clearCache() {
blockDataIdCache.invalidateAll();
pendingUpdates.clear();
pendingFlush.clear();
chunkPacketCache.invalidateAll();
recentModernChanges.invalidateAll();
}
private void deliverCallback(Consumer<byte[]> callback, byte[] data) {
if (callback != null) {
callback.accept(data);
}
}
private boolean hasViaBlocksPlayersInWorld(World world) {
for (UUID playerId : plugin.viaBlocksEnabledPlayers) {
Player player = Bukkit.getPlayer(playerId);
if (player != null && player.isOnline() && world.equals(player.getWorld())) {
return true;
}
}
return false;
}
private void scheduleNearbyEnabledPlayers(Location location, Consumer<Player> action) {
World world = location.getWorld();
if (world == null) return;
SchedulerCompat.runGlobal(plugin.plugin, () -> {
for (Player player : Bukkit.getOnlinePlayers()) {
if (!plugin.isPlayerEnabled(player)) continue;
SchedulerCompat.runEntity(player, plugin.plugin, () -> {
if (!player.isOnline()) return;
if (!world.equals(player.getWorld())) return;
if (player.getLocation().distanceSquared(location) >= UPDATE_RADIUS_SQUARED) return;
action.accept(player);
});
}
});
}
public long packLocation(int x, int y, int z) {
return ((long)x & X_MASK) << X_SHIFT | ((long)z & Z_MASK) << Z_SHIFT | ((long)y & Y_MASK);
}
public long packLocation(Location loc) {
return packLocation(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
}
}
@@ -0,0 +1,137 @@
package tf.tuff.viablocks;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import tf.tuff.util.SchedulerCompat;
import tf.tuff.viablocks.version.VersionAdapter;
public class PaletteManager {
ViaBlocksPlugin plugin;
private final CopyOnWriteArrayList<String> palette = new CopyOnWriteArrayList<>();
private final Map<String, Integer> stateToIdMap = new ConcurrentHashMap<>();
private volatile List<String> paletteSnapshot;
public PaletteManager(VersionAdapter versionAdapter) {
plugin = ViaBlocksPlugin.instance;
generate(versionAdapter);
}
private void generate(VersionAdapter versionAdapter) {
plugin.info("Generating ViaBlocks Pre-Defined Palette...");
List<String> initialPalette = new ArrayList<>();
for (Material material : versionAdapter.getModernMaterials()) {
if (material.isBlock()) {
addEntryInternal(initialPalette, versionAdapter.getMaterialKey(material));
}
}
String[] thickness = {"tip", "frustum", "middle", "base"};
String[] direction = {"up", "down"};
String[] bools = {"true", "false"};
String[] facings = {"north", "south", "east", "west"};
String[] tilts = {"none", "unbalanced", "partial", "full"};
for (String d : direction) {
for (String t : thickness) {
for (String w : bools) {
addEntryInternal(initialPalette, "minecraft:pointed_dripstone[thickness=" + t + ",vertical_direction=" + d + ",waterlogged=" + w + "]");
}
}
}
for (String f : facings) {
for (String t : tilts) {
for (String w : bools) {
addEntryInternal(initialPalette, "minecraft:big_dripleaf[facing=" + f + ",tilt=" + t + ",waterlogged=" + w + "]");
}
}
}
for (String f : facings) {
for (String h : new String[]{"lower", "upper"}) {
addEntryInternal(initialPalette, "minecraft:small_dripleaf[facing=" + f + ",half=" + h + ",waterlogged=false]");
}
}
for (String b : bools) {
addEntryInternal(initialPalette, "minecraft:cave_vines[age=0,berries=" + b + "]");
addEntryInternal(initialPalette, "minecraft:cave_vines_plant[berries=" + b + "]");
}
palette.addAll(initialPalette);
paletteSnapshot = new ArrayList<>(palette);
plugin.info("Palette initialized with " + palette.size() + " entries.");
}
private void addEntryInternal(List<String> localPalette, String state) {
if (!stateToIdMap.containsKey(state)) {
stateToIdMap.put(state, localPalette.size());
localPalette.add(state);
}
}
public int getOrCreateId(String state) {
Integer id = stateToIdMap.get(state);
if (id != null) {
return id;
}
synchronized (this) {
id = stateToIdMap.get(state);
if (id != null) return id;
int newId = palette.size();
palette.add(state);
stateToIdMap.put(state, newId);
paletteSnapshot = null;
broadcastNewPaletteEntry(state);
return newId;
}
}
private void broadcastNewPaletteEntry(String state) {
if (plugin == null || !plugin.plugin.isEnabled() || !plugin.isEnabled()) return;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("NEW_PALETTE_ENTRY");
out.writeUTF(state);
byte[] data = out.toByteArray();
SchedulerCompat.runGlobal(plugin.plugin, () -> {
if (!plugin.plugin.isEnabled() || !plugin.isEnabled()) return;
for (Player player : Bukkit.getOnlinePlayers()) {
if (plugin.isPlayerEnabled(player)) {
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaBlocksPlugin.CLIENTBOUND_CHANNEL, data);
}
}
});
}
public synchronized int getId(String state) {
return stateToIdMap.getOrDefault(state, -1);
}
public synchronized List<String> getPalette() {
List<String> snapshot = paletteSnapshot;
if (snapshot == null) {
snapshot = new ArrayList<>(palette);
paletteSnapshot = snapshot;
}
return snapshot;
}
}
@@ -0,0 +1,317 @@
package tf.tuff.viablocks;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.hover.content.Text;
import tf.tuff.viablocks.version.VersionAdapter;
import tf.tuff.viablocks.version.modern.ModernAdapter;
import tf.tuff.TuffX;
import tf.tuff.util.SchedulerCompat;
public final class ViaBlocksPlugin {
public static final String CLIENTBOUND_CHANNEL = "viablocks:data";
public static final String SERVERBOUND_CHANNEL = "viablocks:handshake";
public final Set<UUID> viaBlocksEnabledPlayers = ConcurrentHashMap.newKeySet();
public CustomBlockListener blockListener;
static ViaBlocksPlugin instance;
private File playerDataFile;
private FileConfiguration playerDataConfig;
private final Set<UUID> joinedPlayersCache = ConcurrentHashMap.newKeySet();
private boolean enabled;
private boolean debug;
private boolean sendWelcomeBook;
public VersionAdapter versionAdapter;
public PaletteManager paletteManager;
private long updateBatchDelayTicks = 1L;
public ExecutorService chunkExecutor;
public TuffX plugin;
public ViaBlocksPlugin(TuffX plugin){
this.plugin = plugin;
}
public void onTuffXReload() {
Set<UUID> previouslyEnabledPlayers = ConcurrentHashMap.newKeySet();
previouslyEnabledPlayers.addAll(viaBlocksEnabledPlayers);
loadSyncSettings();
if (chunkExecutor != null) {
chunkExecutor.shutdownNow();
}
this.chunkExecutor = enabled
? Executors.newFixedThreadPool(Math.max(1, Runtime.getRuntime().availableProcessors()))
: null;
if (playerDataFile == null) {
playerDataFile = new File(plugin.getDataFolder(), "players.yml");
}
playerDataConfig = YamlConfiguration.loadConfiguration(playerDataFile);
if (blockListener != null) {
blockListener.clearCache();
}
viaBlocksEnabledPlayers.clear();
if (enabled && blockListener != null) {
for (Player player : plugin.getServer().getOnlinePlayers()) {
if (!previouslyEnabledPlayers.contains(player.getUniqueId())) continue;
setPlayerEnabled(player, true);
blockListener.onViaBlocksPlayerJoin(player);
}
}
info("ViaBlocks reloaded.");
}
public void onTuffXEnable() {
instance = this;
this.versionAdapter = new ModernAdapter();
this.paletteManager = new PaletteManager(this.versionAdapter);
plugin.saveDefaultConfig();
loadSyncSettings();
this.chunkExecutor = enabled
? Executors.newFixedThreadPool(Math.max(1, Runtime.getRuntime().availableProcessors()))
: null;
setupPlayerData();
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, CLIENTBOUND_CHANNEL);
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, SERVERBOUND_CHANNEL, plugin);
this.blockListener = new CustomBlockListener(this, this.versionAdapter, this.paletteManager);
plugin.getCommand("viablocks").setExecutor(plugin);
if (enabled) {
info("ViaBlocks has been enabled successfully and is listening for client handshakes.");
} else {
info("ViaBlocks is disabled in config.");
}
}
public void handlePacket(Player player, byte[] message) {
if (!isEnabled() || blockListener == null) return;
if (!isPlayerEnabled(player) && isEnabled()) {
debug("Received ViaBlocks handshake from player: " + player.getName() + ". Enabling custom blocks.");
setPlayerEnabled(player, true);
blockListener.onViaBlocksPlayerJoin(player);
}
}
public PaletteManager getPaletteManager() {
return this.paletteManager;
}
public long getUpdateBatchDelayTicks() {
return this.updateBatchDelayTicks;
}
private void loadSyncSettings() {
enabled = plugin.getConfig().getBoolean("viablocks.viablocks-enabled", false);
debug = plugin.getConfig().getBoolean("viablocks.debug", false);
sendWelcomeBook = plugin.getConfig().getBoolean("viablocks.send-welcome-book", true);
String mode = plugin.getConfig().getString("viablocks.sync-mode", "normal");
if (mode == null) {
mode = "normal";
}
this.updateBatchDelayTicks = mode.equalsIgnoreCase("reduced") ? 10L : 1L;
}
public void onTuffXDisable(){
plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, CLIENTBOUND_CHANNEL);
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, SERVERBOUND_CHANNEL);
if (chunkExecutor != null) {
chunkExecutor.shutdownNow();
chunkExecutor = null;
}
viaBlocksEnabledPlayers.clear();
info("ViaBlocks has been disabled.");
}
private void setupPlayerData() {
playerDataFile = new File(plugin.getDataFolder(), "players.yml");
if (!playerDataFile.exists()) {
try {
playerDataFile.createNewFile();
} catch (IOException e) {
severe("Could not create players.yml!");
e.printStackTrace();
}
}
playerDataConfig = YamlConfiguration.loadConfiguration(playerDataFile);
joinedPlayersCache.clear();
for (String uuidStr : playerDataConfig.getStringList("joined-players")) {
try {
joinedPlayersCache.add(UUID.fromString(uuidStr));
} catch (IllegalArgumentException ignored) {
}
}
}
public boolean hasPlayerJoinedBefore(Player player) {
return joinedPlayersCache.contains(player.getUniqueId());
}
public boolean isFirstJoin(Player player) {
return !hasPlayerJoinedBefore(player);
}
public void markPlayerAsJoined(Player player) {
UUID uuid = player.getUniqueId();
if (joinedPlayersCache.add(uuid)) {
List<String> joinedPlayers = playerDataConfig.getStringList("joined-players");
joinedPlayers.add(uuid.toString());
playerDataConfig.set("joined-players", joinedPlayers);
try {
playerDataConfig.save(playerDataFile);
} catch (IOException e) {
severe("Could not save to players.yml!");
e.printStackTrace();
}
}
}
public void sendWelcomeGui(Player player) {
if (!this.sendWelcomeBook) return;
ItemStack book = new ItemStack(Material.WRITTEN_BOOK);
BookMeta meta = (BookMeta) book.getItemMeta();
if (meta == null) return;
meta.setTitle("ViaBlocks Information");
meta.setAuthor("ViaBlocks");
TextComponent welcome = new TextComponent("Welcome to ViaBlocks!");
welcome.setColor(ChatColor.DARK_AQUA);
welcome.setBold(true);
TextComponent body = new TextComponent("\n\nThis feature is in active development!\n\nIf you find any visual bugs or issues, please report them on our ");
body.setColor(ChatColor.BLACK);
TextComponent link = new TextComponent("bug tracker");
link.setColor(ChatColor.BLUE);
link.setUnderlined(true);
link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/TuffNetwork/Tuff-Client-Builds/issues"));
link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(new ComponentBuilder("Click to open the bug tracker!").color(ChatColor.GRAY).create())));
TextComponent disclaimer = new TextComponent("\n\n(Bamboo and kelp are noted.)");
disclaimer.setColor(ChatColor.DARK_GRAY);
disclaimer.setItalic(true);
meta.spigot().addPage(new ComponentBuilder("").append(welcome).append(body).append(link).append(new TextComponent(".")).append(disclaimer).create());
book.setItemMeta(meta);
SchedulerCompat.runEntity(player, plugin, () -> player.openBook(book));
}
public boolean isEnabled() {
return enabled;
}
public boolean isPlayerEnabled(Player player) {
if (player == null) return false;
return viaBlocksEnabledPlayers.contains(player.getUniqueId());
}
public void setPlayerEnabled(Player player, boolean enabled) {
if (enabled && isEnabled()) {
viaBlocksEnabledPlayers.add(player.getUniqueId());
} else {
viaBlocksEnabledPlayers.remove(player.getUniqueId());
}
}
public CustomBlockListener getBlockListener() {
return this.blockListener;
}
public boolean onTuffXCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage("This command can only be executed by a player.");
return true;
}
Player player = (Player) sender;
if (args.length > 0) {
if (args[0].equalsIgnoreCase("get")) {
if (!player.hasPermission("tuffx.viablocks.command.get")) {
player.sendMessage("\u00A7cYou do not have permission to use this command.");
return true;
}
this.versionAdapter.giveCustomBlocks(player);
player.sendMessage("\u00A7aYou have been given a set of custom blocks.");
return true;
} else if (args[0].equalsIgnoreCase("refresh")) {
if (!player.hasPermission("tuffx.viablocks.command.refresh")) {
player.sendMessage("\u00A7cYou do not have permission to use this command.");
return true;
}
if (!isEnabled() || blockListener == null) {
player.sendMessage("\u00A7cViaBlocks is disabled.");
return true;
}
player.sendMessage("\u00A7aRefreshing modern blocks in your view distance...");
World world = player.getWorld();
int viewDistance = this.versionAdapter.getClientViewDistance(player);
int playerChunkX = player.getLocation().getChunk().getX();
int playerChunkZ = player.getLocation().getChunk().getZ();
for (int x = -viewDistance; x <= viewDistance; x++) {
for (int z = -viewDistance; z <= viewDistance; z++) {
int chunkX = playerChunkX + x;
int chunkZ = playerChunkZ + z;
if (world.isChunkLoaded(chunkX, chunkZ)) {
blockListener.processChunkForSinglePlayer(world.getChunkAt(chunkX, chunkZ), player);
}
}
}
player.sendMessage("\u00A7aRefresh complete!");
return true;
}
}
player.sendMessage("\u00A7cInvalid usage. Use: /viablocks <get|refresh>");
return true;
}
public boolean isDebug() {
return debug;
}
public void debug(String message) {
if (isDebug()) info(message);
}
public void log(Level level, String msg, Throwable e) {
plugin.getLogger().log(level, "[ViaBlocks] "+msg, e);
}
public void log(Level level, String msg) {
plugin.getLogger().log(level, "[ViaBlocks] "+msg);
}
public void info(String msg) {
log(Level.INFO, msg);
}
public void severe(String msg) {
log(Level.SEVERE, msg);
}
}
@@ -0,0 +1,14 @@
package tf.tuff.viablocks.version;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import java.util.EnumSet;
public interface VersionAdapter {
String getBlockDataString(Block block);
String getMaterialKey(Material material);
int getClientViewDistance(Player player);
void giveCustomBlocks(Player player);
EnumSet<Material> getModernMaterials();
}
@@ -0,0 +1,669 @@
package tf.tuff.viablocks.version.modern;
import tf.tuff.viablocks.version.VersionAdapter;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.lang.reflect.Method;
import java.util.EnumSet;
public class ModernAdapter implements VersionAdapter {
private static final String[] MODERN_MATERIAL_NAMES = {
"TUBE_CORAL",
"BRAIN_CORAL",
"BUBBLE_CORAL",
"FIRE_CORAL",
"HORN_CORAL",
"TUBE_CORAL_BLOCK",
"BRAIN_CORAL_BLOCK",
"BUBBLE_CORAL_BLOCK",
"FIRE_CORAL_BLOCK",
"HORN_CORAL_BLOCK",
"TUBE_CORAL_FAN",
"BRAIN_CORAL_FAN",
"BUBBLE_CORAL_FAN",
"FIRE_CORAL_FAN",
"HORN_CORAL_FAN",
"TUBE_CORAL_WALL_FAN",
"BRAIN_CORAL_WALL_FAN",
"BUBBLE_CORAL_WALL_FAN",
"FIRE_CORAL_WALL_FAN",
"HORN_CORAL_WALL_FAN",
"DEAD_TUBE_CORAL",
"DEAD_BRAIN_CORAL",
"DEAD_BUBBLE_CORAL",
"DEAD_FIRE_CORAL",
"DEAD_HORN_CORAL",
"DEAD_TUBE_CORAL_BLOCK",
"DEAD_BRAIN_CORAL_BLOCK",
"DEAD_BUBBLE_CORAL_BLOCK",
"DEAD_FIRE_CORAL_BLOCK",
"DEAD_HORN_CORAL_BLOCK",
"DEAD_TUBE_CORAL_FAN",
"DEAD_BRAIN_CORAL_FAN",
"DEAD_BUBBLE_CORAL_FAN",
"DEAD_FIRE_CORAL_FAN",
"DEAD_HORN_CORAL_FAN",
"DEAD_TUBE_CORAL_WALL_FAN",
"DEAD_BRAIN_CORAL_WALL_FAN",
"DEAD_BUBBLE_CORAL_WALL_FAN",
"DEAD_FIRE_CORAL_WALL_FAN",
"DEAD_HORN_CORAL_WALL_FAN",
"BUBBLE_COLUMN",
"SEA_PICKLE",
"KELP",
"KELP_PLANT",
"DRIED_KELP_BLOCK",
"SEAGRASS",
"TALL_SEAGRASS",
"STRIPPED_OAK_LOG",
"STRIPPED_SPRUCE_LOG",
"STRIPPED_BIRCH_LOG",
"STRIPPED_JUNGLE_LOG",
"STRIPPED_ACACIA_LOG",
"STRIPPED_DARK_OAK_LOG",
"STRIPPED_OAK_WOOD",
"STRIPPED_SPRUCE_WOOD",
"STRIPPED_BIRCH_WOOD",
"STRIPPED_JUNGLE_WOOD",
"STRIPPED_ACACIA_WOOD",
"STRIPPED_DARK_OAK_WOOD",
"OAK_WOOD",
"SPRUCE_WOOD",
"BIRCH_WOOD",
"JUNGLE_WOOD",
"ACACIA_WOOD",
"DARK_OAK_WOOD",
"BLUE_ICE",
"CONDUIT",
"PRISMARINE_STAIRS",
"PRISMARINE_BRICK_STAIRS",
"DARK_PRISMARINE_STAIRS",
"PRISMARINE_SLAB",
"PRISMARINE_BRICK_SLAB",
"DARK_PRISMARINE_SLAB",
"TURTLE_EGG",
"SMOOTH_STONE",
"SMOOTH_SANDSTONE",
"SMOOTH_QUARTZ",
"SMOOTH_RED_SANDSTONE",
"CUT_SANDSTONE",
"CUT_RED_SANDSTONE",
"SMOOTH_STONE_SLAB",
"CUT_SANDSTONE_SLAB",
"CUT_RED_SANDSTONE_SLAB",
"BARREL",
"BLAST_FURNACE",
"SMOKER",
"CARTOGRAPHY_TABLE",
"FLETCHING_TABLE",
"GRINDSTONE",
"LECTERN",
"SMITHING_TABLE",
"STONECUTTER",
"BELL",
"LANTERN",
"SCAFFOLDING",
"LOOM",
"COMPOSTER",
"STONE_STAIRS",
"GRANITE_STAIRS",
"POLISHED_GRANITE_STAIRS",
"DIORITE_STAIRS",
"POLISHED_DIORITE_STAIRS",
"ANDESITE_STAIRS",
"POLISHED_ANDESITE_STAIRS",
"STONE_SLAB",
"GRANITE_SLAB",
"POLISHED_GRANITE_SLAB",
"DIORITE_SLAB",
"POLISHED_DIORITE_SLAB",
"ANDESITE_SLAB",
"POLISHED_ANDESITE_SLAB",
"MOSSY_STONE_BRICK_STAIRS",
"MOSSY_STONE_BRICK_SLAB",
"MOSSY_COBBLESTONE_STAIRS",
"MOSSY_COBBLESTONE_SLAB",
"END_STONE_BRICK_STAIRS",
"END_STONE_BRICK_SLAB",
"RED_NETHER_BRICK_STAIRS",
"RED_NETHER_BRICK_SLAB",
"SMOOTH_SANDSTONE_STAIRS",
"SMOOTH_RED_SANDSTONE_STAIRS",
"SMOOTH_QUARTZ_STAIRS",
"GRANITE_WALL",
"DIORITE_WALL",
"ANDESITE_WALL",
"SANDSTONE_WALL",
"RED_SANDSTONE_WALL",
"BRICK_WALL",
"STONE_BRICK_WALL",
"MOSSY_STONE_BRICK_WALL",
"NETHER_BRICK_WALL",
"RED_NETHER_BRICK_WALL",
"CRACKED_NETHER_BRICKS",
"CHISELED_NETHER_BRICKS",
"END_STONE_BRICK_WALL",
"PRISMARINE_WALL",
"CORNFLOWER",
"LILY_OF_THE_VALLEY",
"WITHER_ROSE",
"SWEET_BERRY_BUSH",
"BAMBOO",
"BAMBOO_SAPLING",
"JIGSAW",
"BEE_NEST",
"BEEHIVE",
"HONEY_BLOCK",
"HONEYCOMB_BLOCK",
"CRIMSON_STEM",
"WARPED_STEM",
"STRIPPED_CRIMSON_STEM",
"STRIPPED_WARPED_STEM",
"CRIMSON_HYPHAE",
"WARPED_HYPHAE",
"STRIPPED_CRIMSON_HYPHAE",
"STRIPPED_WARPED_HYPHAE",
"CRIMSON_NYLIUM",
"WARPED_NYLIUM",
"CRIMSON_PLANKS",
"WARPED_PLANKS",
"CRIMSON_STAIRS",
"WARPED_STAIRS",
"CRIMSON_SLAB",
"WARPED_SLAB",
"CRIMSON_FENCE",
"WARPED_FENCE",
"CRIMSON_DOOR",
"WARPED_DOOR",
"CRIMSON_TRAPDOOR",
"WARPED_TRAPDOOR",
"CRIMSON_FENCE_GATE",
"WARPED_FENCE_GATE",
"CRIMSON_BUTTON",
"WARPED_BUTTON",
"CRIMSON_PRESSURE_PLATE",
"WARPED_PRESSURE_PLATE",
"CRIMSON_SIGN",
"WARPED_SIGN",
"CRIMSON_WALL_SIGN",
"WARPED_WALL_SIGN",
"NETHER_GOLD_ORE",
"ANCIENT_DEBRIS",
"CRYING_OBSIDIAN",
"RESPAWN_ANCHOR",
"NETHERITE_BLOCK",
"SOUL_SOIL",
"BASALT",
"POLISHED_BASALT",
"SMOOTH_BASALT",
"SOUL_TORCH",
"SOUL_WALL_TORCH",
"SOUL_LANTERN",
"SOUL_FIRE",
"CAMPFIRE",
"SOUL_CAMPFIRE",
"SHROOMLIGHT",
"TARGET",
"BLACKSTONE",
"GILDED_BLACKSTONE",
"POLISHED_BLACKSTONE",
"POLISHED_BLACKSTONE_BRICKS",
"CHISELED_POLISHED_BLACKSTONE",
"CRACKED_POLISHED_BLACKSTONE_BRICKS",
"BLACKSTONE_STAIRS",
"POLISHED_BLACKSTONE_STAIRS",
"POLISHED_BLACKSTONE_BRICK_STAIRS",
"BLACKSTONE_SLAB",
"POLISHED_BLACKSTONE_SLAB",
"POLISHED_BLACKSTONE_BRICK_SLAB",
"BLACKSTONE_WALL",
"POLISHED_BLACKSTONE_WALL",
"POLISHED_BLACKSTONE_BRICK_WALL",
"CRIMSON_ROOTS",
"WARPED_ROOTS",
"NETHER_SPROUTS",
"CRIMSON_FUNGUS",
"WARPED_FUNGUS",
"WEEPING_VINES",
"WEEPING_VINES_PLANT",
"TWISTING_VINES",
"TWISTING_VINES_PLANT",
"CHAIN",
"IRON_CHAIN",
"LODESTONE",
"DEEPSLATE",
"INFESTED_DEEPSLATE",
"COBBLED_DEEPSLATE",
"POLISHED_DEEPSLATE",
"DEEPSLATE_BRICKS",
"CRACKED_DEEPSLATE_BRICKS",
"DEEPSLATE_TILES",
"CRACKED_DEEPSLATE_TILES",
"CHISELED_DEEPSLATE",
"COBBLED_DEEPSLATE_STAIRS",
"POLISHED_DEEPSLATE_STAIRS",
"DEEPSLATE_BRICK_STAIRS",
"DEEPSLATE_TILE_STAIRS",
"COBBLED_DEEPSLATE_SLAB",
"POLISHED_DEEPSLATE_SLAB",
"DEEPSLATE_BRICK_SLAB",
"DEEPSLATE_TILE_SLAB",
"COBBLED_DEEPSLATE_WALL",
"POLISHED_DEEPSLATE_WALL",
"DEEPSLATE_BRICK_WALL",
"DEEPSLATE_TILE_WALL",
"DEEPSLATE_COAL_ORE",
"DEEPSLATE_IRON_ORE",
"DEEPSLATE_GOLD_ORE",
"DEEPSLATE_DIAMOND_ORE",
"DEEPSLATE_LAPIS_ORE",
"DEEPSLATE_REDSTONE_ORE",
"DEEPSLATE_EMERALD_ORE",
"DEEPSLATE_COPPER_ORE",
"COPPER_ORE",
"COPPER_BLOCK",
"CUT_COPPER",
"COPPER_CHAIN",
"EXPOSED_COPPER",
"EXPOSED_CUT_COPPER",
"EXPOSED_COPPER_CHAIN",
"WEATHERED_COPPER",
"WEATHERED_CUT_COPPER",
"WEATHERED_COPPER_CHAIN",
"OXIDIZED_COPPER",
"OXIDIZED_CUT_COPPER",
"OXIDIZED_COPPER_CHAIN",
"WAXED_COPPER_BLOCK",
"WAXED_CUT_COPPER",
"WAXED_COPPER_CHAIN",
"WAXED_EXPOSED_COPPER",
"WAXED_EXPOSED_CUT_COPPER",
"WAXED_EXPOSED_COPPER_CHAIN",
"WAXED_WEATHERED_COPPER",
"WAXED_WEATHERED_CUT_COPPER",
"WAXED_WEATHERED_COPPER_CHAIN",
"WAXED_OXIDIZED_COPPER",
"WAXED_OXIDIZED_CUT_COPPER",
"WAXED_OXIDIZED_COPPER_CHAIN",
"CUT_COPPER_STAIRS",
"EXPOSED_CUT_COPPER_STAIRS",
"WEATHERED_CUT_COPPER_STAIRS",
"OXIDIZED_CUT_COPPER_STAIRS",
"CUT_COPPER_SLAB",
"EXPOSED_CUT_COPPER_SLAB",
"WEATHERED_CUT_COPPER_SLAB",
"OXIDIZED_CUT_COPPER_SLAB",
"WAXED_CUT_COPPER_STAIRS",
"WAXED_EXPOSED_CUT_COPPER_STAIRS",
"WAXED_WEATHERED_CUT_COPPER_STAIRS",
"WAXED_OXIDIZED_CUT_COPPER_STAIRS",
"WAXED_CUT_COPPER_SLAB",
"WAXED_EXPOSED_CUT_COPPER_SLAB",
"WAXED_WEATHERED_CUT_COPPER_SLAB",
"WAXED_OXIDIZED_CUT_COPPER_SLAB",
"RAW_COPPER_BLOCK",
"RAW_IRON_BLOCK",
"RAW_GOLD_BLOCK",
"TUFF",
"CALCITE",
"AMETHYST_BLOCK",
"BUDDING_AMETHYST",
"AMETHYST_CLUSTER",
"LARGE_AMETHYST_BUD",
"MEDIUM_AMETHYST_BUD",
"SMALL_AMETHYST_BUD",
"DRIPSTONE_BLOCK",
"POINTED_DRIPSTONE",
"MOSS_BLOCK",
"MOSS_CARPET",
"HANGING_ROOTS",
"ROOTED_DIRT",
"AZALEA",
"FLOWERING_AZALEA",
"AZALEA_LEAVES",
"FLOWERING_AZALEA_LEAVES",
"SPORE_BLOSSOM",
"CAVE_VINES",
"CAVE_VINES_PLANT",
"GLOW_BERRIES",
"TINTED_GLASS",
"LIGHT",
"SCULK_SENSOR",
"CANDLE",
"WHITE_CANDLE",
"ORANGE_CANDLE",
"MAGENTA_CANDLE",
"LIGHT_BLUE_CANDLE",
"YELLOW_CANDLE",
"LIME_CANDLE",
"PINK_CANDLE",
"GRAY_CANDLE",
"LIGHT_GRAY_CANDLE",
"CYAN_CANDLE",
"PURPLE_CANDLE",
"BLUE_CANDLE",
"BROWN_CANDLE",
"GREEN_CANDLE",
"RED_CANDLE",
"BLACK_CANDLE",
"CANDLE_CAKE",
"WHITE_CANDLE_CAKE",
"ORANGE_CANDLE_CAKE",
"MAGENTA_CANDLE_CAKE",
"LIGHT_BLUE_CANDLE_CAKE",
"YELLOW_CANDLE_CAKE",
"LIME_CANDLE_CAKE",
"PINK_CANDLE_CAKE",
"GRAY_CANDLE_CAKE",
"LIGHT_GRAY_CANDLE_CAKE",
"CYAN_CANDLE_CAKE",
"PURPLE_CANDLE_CAKE",
"BLUE_CANDLE_CAKE",
"BROWN_CANDLE_CAKE",
"GREEN_CANDLE_CAKE",
"RED_CANDLE_CAKE",
"BLACK_CANDLE_CAKE",
"POWDER_SNOW",
"LIGHTNING_ROD",
"GLOW_LICHEN",
"BIG_DRIPLEAF",
"BIG_DRIPLEAF_STEM",
"SMALL_DRIPLEAF",
"MANGROVE_LOG",
"STRIPPED_MANGROVE_LOG",
"MANGROVE_WOOD",
"STRIPPED_MANGROVE_WOOD",
"MANGROVE_PLANKS",
"MANGROVE_STAIRS",
"MANGROVE_SLAB",
"MANGROVE_FENCE",
"MANGROVE_FENCE_GATE",
"MANGROVE_DOOR",
"MANGROVE_TRAPDOOR",
"MANGROVE_BUTTON",
"MANGROVE_PRESSURE_PLATE",
"MANGROVE_SIGN",
"MANGROVE_WALL_SIGN",
"MANGROVE_LEAVES",
"MANGROVE_ROOTS",
"MUDDY_MANGROVE_ROOTS",
"MANGROVE_PROPAGULE",
"MUD",
"PACKED_MUD",
"MUD_BRICKS",
"MUD_BRICK_STAIRS",
"MUD_BRICK_SLAB",
"MUD_BRICK_WALL",
"PEARLESCENT_FROGLIGHT",
"VERDANT_FROGLIGHT",
"OCHRE_FROGLIGHT",
"FROGSPAWN",
"REINFORCED_DEEPSLATE",
"SCULK",
"SCULK_CATALYST",
"SCULK_SHRIEKER",
"SCULK_VEIN",
"CHERRY_LOG",
"STRIPPED_CHERRY_LOG",
"CHERRY_WOOD",
"STRIPPED_CHERRY_WOOD",
"CHERRY_PLANKS",
"CHERRY_STAIRS",
"CHERRY_SLAB",
"CHERRY_FENCE",
"CHERRY_FENCE_GATE",
"CHERRY_DOOR",
"CHERRY_TRAPDOOR",
"CHERRY_BUTTON",
"CHERRY_PRESSURE_PLATE",
"CHERRY_SIGN",
"CHERRY_WALL_SIGN",
"CHERRY_LEAVES",
"CHERRY_SAPLING",
"PINK_PETALS",
"BAMBOO_BLOCK",
"STRIPPED_BAMBOO_BLOCK",
"BAMBOO_PLANKS",
"BAMBOO_STAIRS",
"BAMBOO_SLAB",
"BAMBOO_FENCE",
"BAMBOO_FENCE_GATE",
"BAMBOO_DOOR",
"BAMBOO_TRAPDOOR",
"BAMBOO_BUTTON",
"BAMBOO_PRESSURE_PLATE",
"BAMBOO_SIGN",
"BAMBOO_WALL_SIGN",
"BAMBOO_MOSAIC",
"BAMBOO_MOSAIC_STAIRS",
"BAMBOO_MOSAIC_SLAB",
"BAMBOO_HANGING_SIGN",
"OAK_HANGING_SIGN",
"SPRUCE_HANGING_SIGN",
"BIRCH_HANGING_SIGN",
"JUNGLE_HANGING_SIGN",
"ACACIA_HANGING_SIGN",
"DARK_OAK_HANGING_SIGN",
"MANGROVE_HANGING_SIGN",
"CHERRY_HANGING_SIGN",
"CRIMSON_HANGING_SIGN",
"WARPED_HANGING_SIGN",
"CHISELED_BOOKSHELF",
"SUSPICIOUS_SAND",
"SUSPICIOUS_GRAVEL",
"DECORATED_POT",
"PIGLIN_HEAD",
"CALIBRATED_SCULK_SENSOR",
"TORCHFLOWER",
"TORCHFLOWER_CROP",
"PITCHER_PLANT",
"PITCHER_CROP",
"SNIFFER_EGG",
"BRUSH",
"CRAFTER",
"TRIAL_SPAWNER",
"VAULT",
"TUFF_STAIRS",
"TUFF_SLAB",
"TUFF_WALL",
"POLISHED_TUFF",
"POLISHED_TUFF_STAIRS",
"POLISHED_TUFF_SLAB",
"POLISHED_TUFF_WALL",
"TUFF_BRICKS",
"TUFF_BRICK_STAIRS",
"TUFF_BRICK_SLAB",
"TUFF_BRICK_WALL",
"CHISELED_TUFF",
"CHISELED_TUFF_BRICKS",
"COPPER_GRATE",
"EXPOSED_COPPER_GRATE",
"WEATHERED_COPPER_GRATE",
"OXIDIZED_COPPER_GRATE",
"WAXED_COPPER_GRATE",
"WAXED_EXPOSED_COPPER_GRATE",
"WAXED_WEATHERED_COPPER_GRATE",
"WAXED_OXIDIZED_COPPER_GRATE",
"COPPER_BULB",
"EXPOSED_COPPER_BULB",
"WEATHERED_COPPER_BULB",
"OXIDIZED_COPPER_BULB",
"WAXED_COPPER_BULB",
"WAXED_EXPOSED_COPPER_BULB",
"WAXED_WEATHERED_COPPER_BULB",
"WAXED_OXIDIZED_COPPER_BULB",
"COPPER_TRAPDOOR",
"EXPOSED_COPPER_TRAPDOOR",
"WEATHERED_COPPER_TRAPDOOR",
"OXIDIZED_COPPER_TRAPDOOR",
"WAXED_COPPER_TRAPDOOR",
"WAXED_EXPOSED_COPPER_TRAPDOOR",
"WAXED_WEATHERED_COPPER_TRAPDOOR",
"WAXED_OXIDIZED_COPPER_TRAPDOOR",
"COPPER_DOOR",
"EXPOSED_COPPER_DOOR",
"WEATHERED_COPPER_DOOR",
"OXIDIZED_COPPER_DOOR",
"WAXED_COPPER_DOOR",
"WAXED_EXPOSED_COPPER_DOOR",
"WAXED_WEATHERED_COPPER_DOOR",
"WAXED_OXIDIZED_COPPER_DOOR",
"CHISELED_COPPER",
"EXPOSED_CHISELED_COPPER",
"WEATHERED_CHISELED_COPPER",
"OXIDIZED_CHISELED_COPPER",
"WAXED_CHISELED_COPPER",
"WAXED_EXPOSED_CHISELED_COPPER",
"WAXED_WEATHERED_CHISELED_COPPER",
"WAXED_OXIDIZED_CHISELED_COPPER",
"HEAVY_CORE",
"COPPER_CHEST",
"EXPOSED_COPPER_CHEST",
"WEATHERED_COPPER_CHEST",
"OXIDIZED_COPPER_CHEST",
"WAXED_COPPER_CHEST",
"WAXED_EXPOSED_COPPER_CHEST",
"WAXED_WEATHERED_COPPER_CHEST",
"WAXED_OXIDIZED_COPPER_CHEST",
"COPPER_LANTERN",
"EXPOSED_COPPER_LANTERN",
"WEATHERED_COPPER_LANTERN",
"OXIDIZED_COPPER_LANTERN",
"WAXED_COPPER_LANTERN",
"WAXED_EXPOSED_COPPER_LANTERN",
"WAXED_WEATHERED_COPPER_LANTERN",
"WAXED_OXIDIZED_COPPER_LANTERN",
"OPEN_EYEBLOSSOM",
"CLOSED_EYEBLOSSOM",
"POLISHED_BLACKSTONE_BUTTON",
"POLISHED_BLACKSTONE_PRESSURE_PLATE",
"DARK_OAK_BUTTON",
"DARK_OAK_PRESSURE_PLATE",
"DARK_OAK_TRAPDOOR",
"JUNGLE_BUTTON",
"JUNGLE_PRESSURE_PLATE",
"JUNGLE_TRAPDOOR",
"ACACIA_BUTTON",
"ACACIA_PRESSURE_PLATE",
"ACACIA_TRAPDOOR",
"SPRUCE_BUTTON",
"SPRUCE_PRESSURE_PLATE",
"SPRUCE_TRAPDOOR",
"BIRCH_BUTTON",
"BIRCH_PRESSURE_PLATE",
"BIRCH_TRAPDOOR",
"BUSH",
"FIREFLY_BUSH",
"SHORT_DRY_GRASS",
"TALL_DRY_GRASS",
"CACTUS_FLOWER",
"LEAF_LITTER",
"WILDFLOWERS",
"WARPED_WART_BLOCK",
"PALE_OAK_LOG",
"STRIPPED_PALE_OAK_LOG",
"PALE_OAK_WOOD",
"STRIPPED_PALE_OAK_WOOD",
"PALE_OAK_PLANKS",
"PALE_OAK_STAIRS",
"PALE_OAK_SLAB",
"PALE_OAK_FENCE",
"PALE_OAK_FENCE_GATE",
"PALE_OAK_DOOR",
"PALE_OAK_TRAPDOOR",
"PALE_OAK_BUTTON",
"PALE_OAK_PRESSURE_PLATE",
"PALE_OAK_SIGN",
"PALE_OAK_HANGING_SIGN",
"PALE_OAK_WALL_SIGN",
"PALE_OAK_LEAVES",
"PALE_OAK_SAPLING",
"PALE_MOSS_BLOCK",
"PALE_MOSS_CARPET",
"PALE_HANGING_MOSS",
"CREAKING_HEART",
"RESIN_BLOCK",
"RESIN_BRICKS",
"RESIN_BRICK_STAIRS",
"RESIN_BRICK_SLAB",
"RESIN_BRICK_WALL",
"CHISELED_RESIN_BRICKS",
"SPRUCE_SHELF",
"CRIMSON_SHELF",
"BAMBOO_SHELF",
"CHERRY_SHELF",
"PALE_OAK_SHELF",
"JUNGLE_SHELF",
"MANGROVE_SHELF",
"DARK_OAK_SHELF",
"OAK_SHELF",
"WARPED_SHELF",
"BIRCH_SHELF",
"ACACIA_SHELF",
};
private static volatile Method clientViewDistanceMethod;
private static volatile boolean clientViewDistanceChecked;
@Override
public EnumSet<Material> getModernMaterials() {
EnumSet<Material> materials = EnumSet.noneOf(Material.class);
for (String name : MODERN_MATERIAL_NAMES) {
Material material = Material.matchMaterial(name);
if (material != null) {
materials.add(material);
}
}
return materials;
}
@Override
public String getBlockDataString(Block block) {
return block.getBlockData().getAsString();
}
@Override
public String getMaterialKey(Material material) {
return material.getKey().toString();
}
@Override
public int getClientViewDistance(Player player) {
if (!clientViewDistanceChecked) {
try {
clientViewDistanceMethod = player.getClass().getMethod("getClientViewDistance");
} catch (Exception e) {
clientViewDistanceMethod = null;
}
clientViewDistanceChecked = true;
}
if (clientViewDistanceMethod != null) {
try {
Object value = clientViewDistanceMethod.invoke(player);
if (value instanceof Integer) {
return (Integer) value;
}
} catch (Exception e) {
return player.getServer().getViewDistance();
}
}
return player.getServer().getViewDistance();
}
@Override
public void giveCustomBlocks(Player player) {
addItemIfPresent(player, "DEEPSLATE");
addItemIfPresent(player, "TUFF");
}
private void addItemIfPresent(Player player, String materialName) {
Material material = Material.matchMaterial(materialName);
if (material != null) {
player.getInventory().addItem(new ItemStack(material));
}
}
}
@@ -0,0 +1,400 @@
package tf.tuff.viaentities;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import org.bukkit.entity.Player;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import tf.tuff.util.SchedulerCompat;
public class EntityDataHandler extends ChannelOutboundHandlerAdapter {
private final ViaEntitiesPlugin plugin;
private final Player player;
private final EntityMappingManager entityMappingManager;
public EntityDataHandler(ViaEntitiesPlugin plugin, Player player) {
this.plugin = plugin;
this.player = player;
this.entityMappingManager = plugin.entityMappingManager;
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
String className = msg.getClass().getName();
String simpleClassName = msg.getClass().getSimpleName();
if (className.contains("Bundle") || simpleClassName.contains("Bundle")) {
try {
Iterable<?> packets = null;
for (java.lang.reflect.Method m : msg.getClass().getMethods()) {
if (m.getName().equals("subPackets") && m.getParameterCount() == 0) {
Object result = m.invoke(msg);
if (result instanceof Iterable) {
packets = (Iterable<?>) result;
break;
}
}
}
if (packets == null) {
Class<?> clazz = msg.getClass();
outer:
while (clazz != null) {
for (java.lang.reflect.Field f : clazz.getDeclaredFields()) {
f.setAccessible(true);
Object val = f.get(msg);
if (val instanceof Iterable) {
packets = (Iterable<?>) val;
break outer;
}
}
clazz = clazz.getSuperclass();
}
}
if (packets != null) {
for (Object subPacket : packets) {
String subClass = subPacket.getClass().getSimpleName();
String subFullClass = subPacket.getClass().getName();
if (subClass.contains("Add") || subClass.contains("Spawn") ||
subFullClass.contains("AddEntity") || subFullClass.contains("SpawnEntity")) {
handleSpawnPacket(subPacket);
}
}
}
} catch (Exception e) {
}
}
boolean isSpawnPacket = className.contains("SpawnEntity") || className.contains("AddEntity") ||
simpleClassName.equals("PacketPlayOutSpawnEntity") ||
simpleClassName.equals("PacketPlayOutSpawnEntityLiving") ||
simpleClassName.equals("ClientboundAddEntityPacket") ||
simpleClassName.equals("ClientboundAddMobPacket");
if (!isSpawnPacket) {
try {
java.lang.reflect.Method getTypeMethod = msg.getClass().getMethod("getType");
java.lang.reflect.Method getIdMethod = msg.getClass().getMethod("getId");
if (getTypeMethod != null && getIdMethod != null) {
Object typeResult = getTypeMethod.invoke(msg);
if (typeResult != null && typeResult.toString().contains("entity")) {
isSpawnPacket = true;
}
}
} catch (NoSuchMethodException e) {
} catch (Exception e) {
}
}
if (isSpawnPacket) {
try {
handleSpawnPacket(msg);
} catch (Exception e) {
}
} else if (className.contains("EntityMetadata") || className.contains("SetEntityData") ||
simpleClassName.equals("PacketPlayOutEntityMetadata") ||
simpleClassName.equals("ClientboundSetEntityDataPacket")) {
try {
int entityId = getIntField(msg, "a", "id", "entityId");
if (entityId != -1) {
sendEntityMetadata(entityId, null);
}
} catch (Exception e) {
}
} else if (className.contains("Animation") ||
simpleClassName.equals("PacketPlayOutAnimation") ||
simpleClassName.equals("ClientboundAnimatePacket")) {
try {
int entityId = getIntField(msg, "a", "id", "entityId");
int animationType = getIntField(msg, "b", "action", "animationType");
if (entityId != -1) {
sendEntityAnimation(entityId, animationType);
}
} catch (Exception e) {
}
} else if (className.contains("EntityDestroy") || className.contains("RemoveEntities") ||
simpleClassName.equals("PacketPlayOutEntityDestroy") ||
simpleClassName.equals("ClientboundRemoveEntitiesPacket")) {
try {
handleDestroyPacket(msg);
} catch (Exception e) {
}
}
super.write(ctx, msg, promise);
}
private void handleSpawnPacket(Object msg) throws Exception {
int entityId = -1;
Object entityTypeObj = null;
double x = 0, y = 0, z = 0;
float yaw = 0, pitch = 0;
java.util.List<Double> doubles = new java.util.ArrayList<>();
java.util.List<Byte> bytes = new java.util.ArrayList<>();
for (java.lang.reflect.Field field : msg.getClass().getDeclaredFields()) {
field.setAccessible(true);
Object value = field.get(msg);
String typeName = field.getType().getName();
if (value == null) continue;
if (typeName.contains("EntityType") || typeName.contains("EntityTypes")) {
entityTypeObj = value;
} else if (value instanceof Integer && entityId == -1) {
entityId = (Integer) value;
} else if (value instanceof Double) {
doubles.add((Double) value);
} else if (value instanceof Byte) {
bytes.add((Byte) value);
}
}
if (doubles.size() >= 3) {
x = doubles.get(0);
y = doubles.get(1);
z = doubles.get(2);
}
if (bytes.size() >= 2) {
pitch = bytes.get(0) * 360.0f / 256.0f;
yaw = bytes.get(1) * 360.0f / 256.0f;
}
if (entityId == -1) return;
if (entityTypeObj == null) return;
String entityTypeStr = entityTypeObj.toString();
String entityTypeName = extractEntityTypeName(entityTypeStr);
if (entityMappingManager.isModernEntity(entityTypeName)) {
sendEntitySpawn(entityId, entityTypeName, x, y, z, yaw, pitch);
}
}
private void handleDestroyPacket(Object msg) throws Exception {
for (java.lang.reflect.Field field : msg.getClass().getDeclaredFields()) {
field.setAccessible(true);
Object value = field.get(msg);
if (value instanceof it.unimi.dsi.fastutil.ints.IntList) {
it.unimi.dsi.fastutil.ints.IntList idList = (it.unimi.dsi.fastutil.ints.IntList) value;
for (int i = 0; i < idList.size(); i++) {
sendEntityDestroy(idList.getInt(i));
}
return;
} else if (value instanceof int[]) {
for (int id : (int[]) value) {
sendEntityDestroy(id);
}
return;
} else if (value instanceof java.util.List) {
for (Object item : (java.util.List<?>) value) {
if (item instanceof Integer) {
sendEntityDestroy((Integer) item);
}
}
return;
}
}
}
private int getIntField(Object msg, String... fieldNames) {
for (String name : fieldNames) {
try {
java.lang.reflect.Field field = msg.getClass().getDeclaredField(name);
field.setAccessible(true);
Object value = field.get(msg);
if (value instanceof Integer) return (Integer) value;
if (value instanceof Number) return ((Number) value).intValue();
} catch (Exception ignored) {}
}
return -1;
}
private double getDoubleField(Object msg, String... fieldNames) {
for (String name : fieldNames) {
try {
java.lang.reflect.Field field = msg.getClass().getDeclaredField(name);
field.setAccessible(true);
Object value = field.get(msg);
if (value instanceof Double) return (Double) value;
if (value instanceof Number) return ((Number) value).doubleValue();
} catch (Exception ignored) {}
}
return 0.0;
}
private float getAngleField(Object msg, String... fieldNames) {
for (String name : fieldNames) {
try {
java.lang.reflect.Field field = msg.getClass().getDeclaredField(name);
field.setAccessible(true);
Object value = field.get(msg);
if (value instanceof Byte) return ((Byte) value) * 360.0f / 256.0f;
if (value instanceof Float) return (Float) value;
if (value instanceof Number) return ((Number) value).floatValue();
} catch (Exception ignored) {}
}
return 0.0f;
}
private Object getField(Object msg, String... fieldNames) {
for (String name : fieldNames) {
try {
java.lang.reflect.Field field = msg.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(msg);
} catch (Exception ignored) {}
}
return null;
}
private String extractEntityTypeName(String typeStr) {
if (typeStr == null) return null;
if (typeStr.startsWith("entity.minecraft.")) {
String name = typeStr.substring("entity.minecraft.".length());
return "minecraft:" + name;
}
if (typeStr.contains("ResourceKey[minecraft:entity_type / minecraft:")) {
int start = typeStr.indexOf("minecraft:", typeStr.indexOf("minecraft:") + 10) + 10;
int end = typeStr.indexOf("]", start);
if (end > start) {
return "minecraft:" + typeStr.substring(start, end);
}
}
if (typeStr.contains("entity_type.minecraft.")) {
int start = typeStr.indexOf("entity_type.minecraft.") + 22;
int end = typeStr.length();
for (int i = start; i < typeStr.length(); i++) {
char c = typeStr.charAt(i);
if (!Character.isLetterOrDigit(c) && c != '_') {
end = i;
break;
}
}
return "minecraft:" + typeStr.substring(start, end);
}
if (typeStr.startsWith("minecraft:")) {
return typeStr;
}
if (typeStr.contains("minecraft:")) {
int start = typeStr.indexOf("minecraft:");
int end = typeStr.length();
for (int i = start + 10; i < typeStr.length(); i++) {
char c = typeStr.charAt(i);
if (!Character.isLetterOrDigit(c) && c != '_' && c != ':') {
end = i;
break;
}
}
return typeStr.substring(start, end);
}
return typeStr;
}
private void sendEntitySpawn(int entityId, String entityType, double x, double y, double z, float yaw, float pitch) {
if (!plugin.isPlayerEnabled(player.getUniqueId())) return;
int paletteIndex = entityMappingManager.getEntityIndex(entityType);
if (paletteIndex == -1) return;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("SPAWN_ENTITY");
out.writeInt(entityId);
out.writeShort(paletteIndex);
out.writeDouble(x);
out.writeDouble(y);
out.writeDouble(z);
out.writeFloat(yaw);
out.writeFloat(pitch);
byte[] data = out.toByteArray();
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaEntitiesPlugin.CLIENTBOUND_CHANNEL, data);
}
private void sendEntityMetadata(int entityId, Object packedItems) {
if (!plugin.isPlayerEnabled(player.getUniqueId())) return;
try {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("ENTITY_METADATA");
out.writeInt(entityId);
if (packedItems instanceof java.util.List) {
java.util.List<?> items = (java.util.List<?>) packedItems;
out.writeInt(items.size());
for (Object item : items) {
java.lang.reflect.Method getIdMethod = item.getClass().getMethod("id");
int metaId = (int) getIdMethod.invoke(item);
out.writeInt(metaId);
java.lang.reflect.Method getValueMethod = item.getClass().getMethod("value");
Object value = getValueMethod.invoke(item);
if (value instanceof Boolean) {
out.writeByte(0);
out.writeBoolean((Boolean) value);
} else if (value instanceof Integer) {
out.writeByte(1);
out.writeInt((Integer) value);
} else if (value instanceof Float) {
out.writeByte(2);
out.writeFloat((Float) value);
} else if (value instanceof String) {
out.writeByte(3);
out.writeUTF((String) value);
} else if (value instanceof Byte) {
out.writeByte(4);
out.writeByte((Byte) value);
} else {
out.writeByte(-1);
}
}
} else {
out.writeInt(0);
}
byte[] data = out.toByteArray();
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaEntitiesPlugin.CLIENTBOUND_CHANNEL, data);
} catch (Exception e) {
}
}
private void sendEntityAnimation(int entityId, int animationType) {
if (!plugin.isPlayerEnabled(player.getUniqueId())) return;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("ENTITY_ANIMATION");
out.writeInt(entityId);
out.writeInt(animationType);
byte[] data = out.toByteArray();
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaEntitiesPlugin.CLIENTBOUND_CHANNEL, data);
}
private void sendEntityDestroy(int entityId) {
if (!plugin.isPlayerEnabled(player.getUniqueId())) return;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("DESTROY_ENTITY");
out.writeInt(entityId);
byte[] data = out.toByteArray();
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaEntitiesPlugin.CLIENTBOUND_CHANNEL, data);
}
}
@@ -0,0 +1,63 @@
package tf.tuff.viaentities;
import io.netty.channel.ChannelHandler;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import tf.tuff.netty.BaseInjector;
import tf.tuff.util.SchedulerCompat;
public class EntityInjector extends BaseInjector {
private final ViaEntitiesPlugin plugin;
public EntityInjector(ViaEntitiesPlugin plugin) {
super("viaentities_handler");
this.plugin = plugin;
}
@Override
protected ChannelHandler createHandler(Player player) {
return new EntityDataHandler(plugin, player);
}
@Override
protected void onPostInject(Player player) {
SchedulerCompat.runEntity(player, plugin.plugin, () -> sendExistingEntities(player));
}
private void sendExistingEntities(Player player) {
int viewDistance = player.getWorld().getViewDistance() * 16;
for (Entity entity : player.getNearbyEntities(viewDistance, viewDistance, viewDistance)) {
if (entity.equals(player)) continue;
if (entity instanceof Player) continue;
double distance = entity.getLocation().distance(player.getLocation());
if (distance > viewDistance) continue;
String entityType = entity.getType().getKey().toString();
if (plugin.entityMappingManager.isModernEntity(entityType)) {
sendEntityData(player, entity.getEntityId(), entityType, entity);
}
}
}
public void sendEntityData(Player player, int entityId, String entityType, Entity entity) {
if (!plugin.isPlayerEnabled(player.getUniqueId())) return;
int paletteIndex = plugin.entityMappingManager.getEntityIndex(entityType);
if (paletteIndex == -1) return;
com.google.common.io.ByteArrayDataOutput out = com.google.common.io.ByteStreams.newDataOutput();
out.writeUTF("SPAWN_ENTITY");
out.writeInt(entityId);
out.writeShort(paletteIndex);
out.writeDouble(entity.getLocation().getX());
out.writeDouble(entity.getLocation().getY());
out.writeDouble(entity.getLocation().getZ());
out.writeFloat(entity.getLocation().getYaw());
out.writeFloat(entity.getLocation().getPitch());
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaEntitiesPlugin.CLIENTBOUND_CHANNEL, out.toByteArray());
}
}
@@ -0,0 +1,146 @@
package tf.tuff.viaentities;
import java.io.*;
import java.util.*;
import com.fasterxml.jackson.databind.*;
public class EntityMappingManager {
private final List<String> modernEntities = new ArrayList<>();
private final Map<String, Integer> entityToIndex = new HashMap<>();
private final Set<String> modernEntitySet = new HashSet<>();
private final Map<String, EntityInfo> entityInfoMap = new HashMap<>();
public EntityMappingManager() {
loadEntityMappingsFromJSON();
}
private void loadEntityMappingsFromJSON() {
try (InputStream is = getClass().getClassLoader().getResourceAsStream("entity_mappings.json")) {
if (is == null) {
loadFallbackEntities();
return;
}
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(is);
JsonNode modernEntitiesNode = root.get("modern_entities");
JsonNode entitySizeNode = root.get("entity_size");
if (modernEntitiesNode != null) {
Iterator<Map.Entry<String, JsonNode>> fields = modernEntitiesNode.fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> entry = fields.next();
String entityType = entry.getKey();
JsonNode info = entry.getValue();
String addedVersion = info.has("added") ? info.get("added").asText() : "1.13";
String model = info.has("model") && !info.get("model").isNull() ? info.get("model").asText() : null;
boolean animated = info.has("animated") && info.get("animated").asBoolean();
double width = 1.0;
double height = 1.0;
if (entitySizeNode != null && entitySizeNode.has(entityType)) {
JsonNode sizeInfo = entitySizeNode.get(entityType);
width = sizeInfo.has("width") ? sizeInfo.get("width").asDouble() : 1.0;
height = sizeInfo.has("height") ? sizeInfo.get("height").asDouble() : 1.0;
}
addEntity(entityType, new EntityInfo(addedVersion, model, animated, width, height));
}
}
} catch (Exception e) {
loadFallbackEntities();
}
}
private void loadFallbackEntities() {
addEntity("minecraft:allay", new EntityInfo("1.19", "allay", true, 0.35, 0.6));
addEntity("minecraft:axolotl", new EntityInfo("1.17", "axolotl", true, 0.75, 0.42));
addEntity("minecraft:bee", new EntityInfo("1.15", "bee", true, 0.7, 0.6));
addEntity("minecraft:camel", new EntityInfo("1.20", "camel", true, 1.7, 2.375));
addEntity("minecraft:cat", new EntityInfo("1.14", "cat", true, 0.6, 0.7));
addEntity("minecraft:fox", new EntityInfo("1.14", "fox", true, 0.6, 0.7));
addEntity("minecraft:frog", new EntityInfo("1.19", "frog", true, 0.5, 0.5));
addEntity("minecraft:goat", new EntityInfo("1.17", "goat", true, 0.9, 1.3));
addEntity("minecraft:hoglin", new EntityInfo("1.16", "hoglin", true, 1.4, 1.4));
addEntity("minecraft:piglin", new EntityInfo("1.16", "piglin", true, 0.6, 1.95));
addEntity("minecraft:strider", new EntityInfo("1.16", "strider", true, 0.9, 1.7));
addEntity("minecraft:warden", new EntityInfo("1.19", "warden", true, 0.9, 2.9));
addEntity("minecraft:sniffer", new EntityInfo("1.20", "sniffer", true, 1.9, 1.75));
addEntity("minecraft:breeze", new EntityInfo("1.21", "breeze", true, 0.6, 1.77));
addEntity("minecraft:wind_charge", new EntityInfo("1.21", "wind_charge", false, 0.3125, 0.3125));
addEntity("minecraft:breeze_wind_charge", new EntityInfo("1.21", "breeze_wind_charge", false, 0.3125, 0.3125));
addEntity("minecraft:armadillo", new EntityInfo("1.20.5", "armadillo", true, 0.7, 0.65));
addEntity("minecraft:bogged", new EntityInfo("1.21", "bogged", true, 0.6, 1.99));
addEntity("minecraft:phantom", new EntityInfo("1.13", "phantom", true, 0.9, 0.5));
addEntity("minecraft:dolphin", new EntityInfo("1.13", "dolphin", true, 0.9, 0.6));
addEntity("minecraft:drowned", new EntityInfo("1.13", "drowned", true, 0.6, 1.95));
addEntity("minecraft:cod", new EntityInfo("1.13", "cod", true, 0.5, 0.3));
addEntity("minecraft:salmon", new EntityInfo("1.13", "salmon", true, 0.7, 0.4));
addEntity("minecraft:tropical_fish", new EntityInfo("1.13", "tropical_fish", true, 0.5, 0.4));
addEntity("minecraft:pufferfish", new EntityInfo("1.13", "pufferfish", true, 0.7, 0.7));
addEntity("minecraft:turtle", new EntityInfo("1.13", "turtle", true, 1.2, 0.4));
addEntity("minecraft:trident", new EntityInfo("1.13", "trident", false, 0.5, 0.5));
addEntity("minecraft:thrown_trident", new EntityInfo("1.13", "trident", false, 0.5, 0.5));
addEntity("minecraft:glow_squid", new EntityInfo("1.17", "glow_squid", true, 0.8, 0.8));
addEntity("minecraft:glow_item_frame", new EntityInfo("1.17", "glow_item_frame", false, 0.5, 0.5));
addEntity("minecraft:tadpole", new EntityInfo("1.19", "tadpole", true, 0.4, 0.3));
addEntity("minecraft:chest_boat", new EntityInfo("1.19", "chest_boat", false, 1.375, 0.5625));
addEntity("minecraft:piglin_brute", new EntityInfo("1.16", "piglin_brute", true, 0.6, 1.95));
addEntity("minecraft:zoglin", new EntityInfo("1.16", "zoglin", true, 1.4, 1.4));
}
private void addEntity(String entityType, EntityInfo info) {
if (!entityToIndex.containsKey(entityType)) {
entityToIndex.put(entityType, modernEntities.size());
modernEntities.add(entityType);
modernEntitySet.add(entityType);
entityInfoMap.put(entityType, info);
}
}
public boolean isModernEntity(String entityType) {
return modernEntitySet.contains(entityType);
}
public int getEntityIndex(String entityType) {
return entityToIndex.getOrDefault(entityType, -1);
}
public String getEntityByIndex(int index) {
if (index >= 0 && index < modernEntities.size()) {
return modernEntities.get(index);
}
return null;
}
public EntityInfo getEntityInfo(String entityType) {
return entityInfoMap.get(entityType);
}
public List<String> getAllModernEntities() {
return Collections.unmodifiableList(modernEntities);
}
public int getModernEntityCount() {
return modernEntities.size();
}
public static class EntityInfo {
public final String addedVersion;
public final String model;
public final boolean animated;
public final double width;
public final double height;
public EntityInfo(String addedVersion, String model, boolean animated, double width, double height) {
this.addedVersion = addedVersion;
this.model = model;
this.animated = animated;
this.width = width;
this.height = height;
}
}
}
@@ -0,0 +1,138 @@
package tf.tuff.viaentities;
import tf.tuff.TuffX;
import org.bukkit.entity.Player;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import tf.tuff.util.SchedulerCompat;
public final class ViaEntitiesPlugin {
public static final String CLIENTBOUND_CHANNEL = "viaentities:data";
public static final String SERVERBOUND_CHANNEL = "entities:handshake";
public final Set<UUID> viaEntitiesEnabledPlayers = new HashSet<>();
static ViaEntitiesPlugin instance;
public EntityMappingManager entityMappingManager;
private EntityInjector entityInjector;
private boolean enabled = true;
private boolean debug = false;
private int maxDistance = -1;
public TuffX plugin;
public ViaEntitiesPlugin(TuffX plugin) {
this.plugin = plugin;
plugin.getConfig().addDefault("viaentities.viaentities-enabled", true);
plugin.getConfig().addDefault("viaentities.debug", false);
plugin.getConfig().addDefault("viaentities.max-distance", -1);
}
public void onTuffXReload() {
loadConfig();
}
private void loadConfig() {
enabled = plugin.getConfig().getBoolean("viaentities.viaentities-enabled");
debug = plugin.getConfig().getBoolean("viaentities.debug");
maxDistance = plugin.getConfig().getInt("viaentities.max-distance");
}
public boolean isDebug() {
return debug;
}
public void debug(String message) {
if (isDebug()) info(message);
}
public void log(Level level, String msg) {
plugin.getLogger().log(level, "[ViaEntities] "+msg);
}
public void info(String msg) {
log(Level.INFO, msg);
}
public void onTuffXEnable() {
instance = this;
loadConfig();
this.entityMappingManager = new EntityMappingManager();
this.entityInjector = new EntityInjector(this);
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, CLIENTBOUND_CHANNEL);
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, SERVERBOUND_CHANNEL, plugin);
if (enabled) {
info("ViaEntities enabled with " + entityMappingManager.getModernEntityCount() + " modern entities");
} else {
info("ViaEntities disabled in config");
}
}
public boolean isEnabled() {
return enabled;
}
public int getMaxDistance() {
return maxDistance;
}
public void handlePacket(Player player, byte[] message) {
if (!enabled) return;
if (!isPlayerEnabled(player.getUniqueId())) {
debug("Received handshake from " + player.getName());
setPlayerEnabled(player.getUniqueId(), true);
entityInjector.inject(player);
sendPaletteToClient(player);
debug("Sent palette with " + entityMappingManager.getModernEntityCount() + " entities to " + player.getName());
}
}
private void sendPaletteToClient(Player player) {
com.google.common.io.ByteArrayDataOutput out = com.google.common.io.ByteStreams.newDataOutput();
out.writeUTF("INIT_PALETTE");
java.util.List<String> palette = entityMappingManager.getAllModernEntities();
out.writeInt(palette.size());
for (String entityType : palette) {
out.writeUTF(entityType);
}
SchedulerCompat.sendPluginMessage(plugin, player, CLIENTBOUND_CHANNEL, out.toByteArray());
}
public void handlePlayerQuit(org.bukkit.event.player.PlayerQuitEvent event) {
entityInjector.eject(event.getPlayer());
viaEntitiesEnabledPlayers.remove(event.getPlayer().getUniqueId());
}
public void onTuffXDisable() {
plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, CLIENTBOUND_CHANNEL);
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, SERVERBOUND_CHANNEL);
}
public boolean isPlayerEnabled(UUID playerId) {
return viaEntitiesEnabledPlayers.contains(playerId);
}
public void setPlayerEnabled(UUID playerId, boolean enabled) {
if (enabled) {
viaEntitiesEnabledPlayers.add(playerId);
} else {
viaEntitiesEnabledPlayers.remove(playerId);
}
}
public EntityInjector getEntityInjector() {
return entityInjector;
}
}
@@ -0,0 +1,29 @@
package tf.tuff.y0;
import tf.tuff.TuffX;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.entity.Player;
import tf.tuff.util.SchedulerCompat;
public class ChunkPacketListener {
public final Y0Plugin plugin;
public ChunkPacketListener(Y0Plugin plugin) {
this.plugin = plugin;
}
public void handleChunk(TuffX plugin, Player player, World world, int chunkX, int chunkZ){
if (!this.plugin.isPlayerReady(player)) return;
SchedulerCompat.runRegion(plugin, world, chunkX, chunkZ, () -> {
if (player.isOnline() && world.isChunkLoaded(chunkX, chunkZ)) {
Chunk chunk = world.getChunkAt(chunkX, chunkZ);
this.plugin.processAndSendChunk(player, chunk);
}
});
}
}
+345
View File
@@ -0,0 +1,345 @@
package tf.tuff.y0;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.viaversion.viabackwards.api.BackwardsProtocol;
import com.viaversion.viabackwards.api.data.BackwardsMappingData;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import tf.tuff.TuffX;
import tf.tuff.util.SchedulerCompat;
public class ViaBlockIds {
private final TuffX p;
private final Y0Plugin plugin;
private final String serverVersion;
private final File mappingsFile;
private Object2ObjectOpenHashMap<String, int[]> legacyMappings = new Object2ObjectOpenHashMap<>();
public ViaBlockIds(TuffX pl) {
p = pl;
plugin = pl.y0Plugin;
serverVersion = getServerMCVersion();
mappingsFile = new File(pl.getDataFolder(), serverVersion + "-mappings.json");
plugin.info("Server Minecraft Version: " + serverVersion);
SchedulerCompat.runGlobalLater(pl, this::initializeMappings, 1L);
}
private void initializeMappings() {
try {
if (Via.getAPI() == null) {
plugin.severe("ViaVersion API not found! Is ViaVersion installed?");
return;
}
} catch (IllegalArgumentException e) {
plugin.severe("ViaVersion API not found! Is ViaVersion installed?");
return;
}
if (!mappingsFile.exists()) {
plugin.info("Mapping file not found, generating...");
if (!p.getDataFolder().exists()) {
p.getDataFolder().mkdirs();
}
generateMappings();
} else {
plugin.info("Loading mappings from " + mappingsFile.getName());
loadMappings();
}
}
private static final int[] DEFAULT_LEGACY = {1, 0};
public int[] toLegacy(String k) {
int[] result = legacyMappings.get(k);
return result != null ? result : DEFAULT_LEGACY;
}
public int[] toLegacy(BlockData bd) {
org.bukkit.Material mat = bd.getMaterial();
if (mat == org.bukkit.Material.CHEST) {
return new int[] {54, getLegacyChestMeta(bd)};
}
if (mat == org.bukkit.Material.TRAPPED_CHEST) {
return new int[] {146, getLegacyChestMeta(bd)};
}
if (mat == org.bukkit.Material.ENDER_CHEST) {
return new int[] {130, getLegacyChestMeta(bd)};
}
String k = bd.getAsString();
if (k.startsWith("minecraft:")) {
k = k.substring(10);
}
return toLegacy(k);
}
private int getLegacyChestMeta(BlockData bd) {
if (bd instanceof org.bukkit.block.data.Directional directional) {
org.bukkit.block.BlockFace face = directional.getFacing();
switch (face) {
case NORTH:
return 2;
case SOUTH:
return 3;
case WEST:
return 4;
case EAST:
return 5;
default:
return 3;
}
}
String s = bd.getAsString();
if (s.contains("facing=north")) {
return 2;
}
if (s.contains("facing=south")) {
return 3;
}
if (s.contains("facing=west")) {
return 4;
}
if (s.contains("facing=east")) {
return 5;
}
return 3;
}
public int[] toLegacy(Block b) {
return toLegacy(b.getBlockData());
}
private String getServerMCVersion() {
String vs = Bukkit.getServer().getVersion();
int mi = vs.indexOf("MC: ");
if (mi != -1) {
int ei = vs.indexOf(')', mi);
return ei != -1 ? vs.substring(mi + 4, ei) : vs.substring(mi + 4);
}
plugin.log(Level.WARNING, "Could not detect Minecraft version. Defaulting to 1.21.");
return "1.21";
}
public static record MappingFile (String version, InputStream stream) {}
public @Nonnull MappingFile findMappingFile(String serverVers) {
String[] vp = serverVers.split("\\.");
int maj, min, pat;
try {
maj = Integer.parseInt(vp[0]);
min = Integer.parseInt(vp[1]);
pat = vp.length > 2 ? Integer.parseInt(vp[2]) : 0;
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
plugin.severe("Could not parse server version string: " + serverVers);
return new MappingFile(serverVers, p.getResource("mapping-" + serverVers + ".json"));
}
plugin.info("Searching for mappings, starting from " + serverVers + " and going down.");
for (int m = min; m >= 0; m--) {
int sp = (m == min) ? pat : 11; // if on correct minor version, start from patch, otherwise start from highest possible patch
for (int pt = sp; pt >= 0; pt--) {
String vtt = maj + "." + m + "." + pt;
String fileName = "mapping-" + vtt + ".json";
InputStream inpStream = p.getResource(fileName);
if (inpStream != null) {
if (!vtt.equals(serverVers)) {
plugin.info("Using fallback mapping file: " + fileName);
} else {
plugin.info("Found exact mapping file: " + fileName);
}
return new MappingFile(vtt, inpStream);
}
}
// Check for the major.min version without patch
String fileName = "mapping-" + maj + "." + m + ".json";
InputStream inpStream = p.getResource(fileName);
if (inpStream != null) {
plugin.info("Using fallback mapping file: " + fileName);
return new MappingFile(maj + "." + m, inpStream);
}
// Switch to 1.21.x versions after 26.0
if (maj >= 26 && m == 0) {
maj = 1;
m = 22; // will be 21 in the next iteration
}
}
plugin.severe("Could not find any suitable mapping file after checking all versions down to 1.0.0");
return new MappingFile(serverVers, null);
}
private void generateMappings() {
try {
MappingFile mapFile = findMappingFile(serverVersion);
if (mapFile.stream == null) {
plugin.severe("Failed to find mapping file for " + serverVersion + " in plugin resources!");
return;
}
ObjectMapper mapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> r = mapper.readValue(mapFile.stream, Map.class);
mapFile.stream.close();
@SuppressWarnings("unchecked")
List<String> states = (List<String>) r.get("blockstates");
if (states == null) {
plugin.severe("'blockstates' key not found in JSON.");
return;
}
Object2ObjectOpenHashMap<String, int[]> newLegacyMappings = new Object2ObjectOpenHashMap<>();
plugin.info("Generating legacy mappings for " + states.size() + " block states...");
ProtocolVersion serverProto = ProtocolVersion.getClosest(mapFile.version); // start from base mappings file version
ProtocolVersion clientProto = ProtocolVersion.v1_12_2;
List<ProtocolPathEntry> protoPath = Via.getManager()
.getProtocolManager()
.getProtocolPath(clientProto, serverProto);
if (protoPath == null) {
plugin.log(Level.SEVERE, "Protocol path is null!");
return;
}
for (int i = 0; i < states.size(); i++) {
String k = states.get(i).replace("minecraft:", "");
String blockName = k.contains("[") ? k.substring(0, k.indexOf("[")) : k;
int[] legacy;
switch (blockName) {
case "chest":
legacy = new int[]{54, 0};
break;
case "ender_chest":
legacy = new int[]{130, 0};
break;
case "trapped_chest":
legacy = new int[]{146, 0};
break;
default:
legacy = convertToLegacy(protoPath, i);
break;
}
newLegacyMappings.put(k, legacy);
}
legacyMappings = newLegacyMappings;
Map<String, Object> outputMap = new Object2ObjectOpenHashMap<>();
outputMap.put("blockstates", legacyMappings);
mappingsFile.getParentFile().mkdirs();
mapper.writerWithDefaultPrettyPrinter().writeValue(mappingsFile, outputMap);
plugin.info("Successfully wrote mappings to " + mappingsFile.getName());
} catch (Exception e) {
plugin.log(Level.SEVERE, "Error generating legacy mappings.", e);
}
}
private void loadMappings() {
try {
ObjectMapper mapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> r = mapper.readValue(mappingsFile, Map.class);
@SuppressWarnings("unchecked")
Map<String, List<Integer>> readMap = (Map<String, List<Integer>>) r.get("blockstates");
if (readMap == null) {
plugin.severe("Invalid format in mappings file. Regenerating...");
generateMappings();
return;
}
legacyMappings = new Object2ObjectOpenHashMap<>();
for (Map.Entry<String, List<Integer>> e : readMap.entrySet()) {
String fullKey = e.getKey();
List<Integer> ll = e.getValue();
if (ll != null && ll.size() == 2) {
String blockName = fullKey.contains("[") ? fullKey.substring(0, fullKey.indexOf("[")) : fullKey;
int[] finalId;
switch (blockName) {
case "chest":
finalId = new int[]{54, 0};
break;
case "ender_chest":
finalId = new int[]{130, 0};
break;
case "trapped_chest":
finalId = new int[]{146, 0};
break;
default:
finalId = new int[]{ll.get(0), ll.get(1)};
break;
}
legacyMappings.put(fullKey, finalId);
}
}
plugin.info("Loaded " + legacyMappings.size() + " legacy mappings.");
} catch (IOException e) {
plugin.log(Level.SEVERE, "Failed to load mappings file.", e);
}
}
public int[] convertToLegacy(List<ProtocolPathEntry> protoPath, int stateId) {
for (int i = protoPath.size() - 1; i >= 0; i--) {
ProtocolPathEntry entry = protoPath.get(i);
Protocol<?, ?, ?, ?> protocol = entry.protocol();
if (protocol instanceof BackwardsProtocol) {
BackwardsMappingData mappingData = ((BackwardsProtocol<?, ?, ?, ?>) protocol).getMappingData();
if (mappingData != null && mappingData.getBlockStateMappings() != null) {
int newStateId = mappingData.getBlockStateMappings().getNewId(stateId);
if (newStateId != -1) stateId = newStateId;
}
}
}
int blockId = stateId >> 4;
int blockMetadata = stateId & 0xF;
return new int[]{blockId, blockMetadata};
}
}
+931
View File
@@ -0,0 +1,931 @@
package tf.tuff.y0;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import tf.tuff.TuffX;
import tf.tuff.netty.ChunkInjector;
import tf.tuff.util.SchedulerCompat;
public class Y0Plugin {
public static final String CHANNEL = "eagler:below_y0";
public ViaBlockIds viaIds;
private ObjectOpenHashSet<String> enabledWorlds;
private boolean debug;
private int processorThreadCount;
private int cacheSize;
private int cacheExp;
private int concLevel;
private boolean kickOutdatedClients;
private final Set<UUID> readyPlayers = ConcurrentHashMap.newKeySet();
private volatile Cache<WorldChunk, ObjectArrayList<byte[]>> chunkCache;
private volatile Cache<WorldChunk, byte[]> chunkCacheCombined;
private volatile ExecutorService chunkProcessor;
private final ThreadLocal<Object2ObjectOpenHashMap<BlockData, int[]>> threadChunkData = ThreadLocal.withInitial(() -> new Object2ObjectOpenHashMap<>(256));
private final ThreadLocal<ByteArrayOutputStream> threadOut = ThreadLocal.withInitial(() -> new ByteArrayOutputStream(8256));
private final ThreadLocal<byte[]> threadData = ThreadLocal.withInitial(() -> new byte[12288]);
private TuffX plugin;
private static final int[] EMPTY_LEGACY = {1, 0};
private static final Map<BlockData, Integer> emissionCache = new ConcurrentHashMap<>();
private static Method getLightEmissionMethod;
public ChunkPacketListener chunkPacketListener;
private ChunkInjector chunkInjector;
static {
try {
getLightEmissionMethod = BlockData.class.getMethod("getLightEmission");
getLightEmissionMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
getLightEmissionMethod = null;
}
}
private static final Map<Material, Integer> legacy_light_map = Map.ofEntries(
Map.entry(Material.TORCH, 14),
Map.entry(Material.SOUL_TORCH, 10),
Map.entry(Material.LANTERN, 15),
Map.entry(Material.SOUL_LANTERN, 10),
Map.entry(Material.GLOWSTONE, 15),
Map.entry(Material.SEA_LANTERN, 15),
Map.entry(Material.REDSTONE_LAMP, 15),
Map.entry(Material.SHROOMLIGHT, 15),
Map.entry(Material.CAMPFIRE, 15),
Map.entry(Material.SOUL_CAMPFIRE, 10),
Map.entry(Material.END_ROD, 14),
Map.entry(Material.MAGMA_BLOCK, 3),
Map.entry(Material.FIRE, 15),
Map.entry(Material.SOUL_FIRE, 10),
Map.entry(Material.CANDLE, 3),
Map.entry(Material.WHITE_CANDLE, 3),
Map.entry(Material.CAKE, 0),
Map.entry(Material.CANDLE_CAKE, 3)
);
public Y0Plugin(TuffX plugin){
this.plugin = plugin;
}
private void debug(String m) {
if (debug) plugin.getLogger().info("[Y0-Debug] " + m);
}
public void log(Level level, String msg, Throwable e) {
plugin.getLogger().log(level, "[Y0] "+msg, e);
}
public void log(Level level, String msg) {
plugin.getLogger().log(level, "[Y0] "+msg);
}
public void info(String msg) {
log(Level.INFO, msg);
}
public void severe(String msg) {
log(Level.SEVERE, msg);
}
public record WorldChunk(String w, int x, int z) {}
private void loadConfig() {
debug = plugin.getConfig().getBoolean("y0.debug-mode");
ObjectArrayList<String> ewList = new ObjectArrayList<>(plugin.getConfig().getStringList("y0.enabled-worlds"));
enabledWorlds = new ObjectOpenHashSet<>(ewList.size());
if (plugin.getConfig().getBoolean("y0.y0-enabled")) enabledWorlds.addAll(ewList);
int threadSetting = plugin.getConfig().getInt("y0.chunk-processor-threads");
if (threadSetting <= 0) {
processorThreadCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
} else {
processorThreadCount = threadSetting;
}
cacheSize = plugin.getConfig().getInt("y0.cache-size");
cacheExp = plugin.getConfig().getInt("y0.cache-expiration");
concLevel = Runtime.getRuntime().availableProcessors();
kickOutdatedClients = plugin.getConfig().getBoolean("y0.kick-outdated-clients");
}
private void startup() {
loadConfig();
chunkCache = CacheBuilder.newBuilder()
.maximumSize(cacheSize)
.expireAfterAccess(cacheExp, TimeUnit.MINUTES)
.concurrencyLevel(concLevel)
.initialCapacity(256)
.build();
chunkCacheCombined = CacheBuilder.newBuilder()
.maximumSize(cacheSize)
.expireAfterAccess(cacheExp, TimeUnit.MINUTES)
.concurrencyLevel(concLevel)
.initialCapacity(256)
.build();
chunkProcessor = Executors.newFixedThreadPool(processorThreadCount, run -> {
Thread thread = new Thread(run, "TuffX-Chunk-" + System.nanoTime());
thread.setDaemon(true);
thread.setPriority(Thread.NORM_PRIORITY - 1);
return thread;
});
}
private void shutdown() {
if (chunkCache != null) {
chunkCache.invalidateAll();
}
if (chunkCacheCombined != null) {
chunkCacheCombined.invalidateAll();
}
if (chunkProcessor != null) {
chunkProcessor.shutdown();
try {
if (!chunkProcessor.awaitTermination(10, TimeUnit.SECONDS)) {
chunkProcessor.shutdownNow();
if (!chunkProcessor.awaitTermination(5, TimeUnit.SECONDS)) {
severe("Failed to shutdown chunk processor pool!");
}
}
} catch (InterruptedException e) {
chunkProcessor.shutdownNow();
Thread.currentThread().interrupt();
} finally {
chunkProcessor = null;
}
}
}
public void onTuffXReload() {
loadConfig();
shutdown();
startup();
emissionCache.clear();
info("Y0 reloaded.");
}
public void forceClearCache() {
if (chunkCache != null) {
chunkCache.invalidateAll();
chunkCache.cleanUp();
}
if (chunkCacheCombined != null) {
chunkCacheCombined.invalidateAll();
chunkCacheCombined.cleanUp();
}
emissionCache.clear();
}
public void onTuffXEnable() {
plugin.getConfig().addDefault("y0.y0-enabled", true);
plugin.getConfig().addDefault("y0.debug-mode", false);
plugin.getConfig().addDefault("y0.enabled-worlds", new ArrayList<>(java.util.Collections.singletonList("world")));
plugin.getConfig().addDefault("y0.chunk-processor-threads", 3);
plugin.getConfig().addDefault("y0.cache-size", 192);
plugin.getConfig().addDefault("y0.cache-expiration", 2);
plugin.getConfig().addDefault("y0.kick-outdated-clients", true);
plugin.getConfig().options().copyDefaults(true);
startup();
this.chunkPacketListener = new ChunkPacketListener(this);
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, CHANNEL);
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, CHANNEL, plugin);
if (viaIds == null) viaIds = new ViaBlockIds(this.plugin);
}
public record Coords(int x, int y, int z) {}
public void onTuffXDisable() {
shutdown();
readyPlayers.clear();
if (viaIds != null) viaIds = null;
}
public boolean isPlayerReady(Player player) {
if (player == null) return false;
return readyPlayers.contains(player.getUniqueId());
}
public void setChunkInjector(ChunkInjector injector) {
if (injector == null) return;
this.chunkInjector = injector;
}
public void handlePacket(Player player, byte[] data) {
try (DataInputStream i = new DataInputStream(new ByteArrayInputStream(data))) {
i.readInt();
i.readInt();
i.readInt();
int al = i.readUnsignedByte();
byte[] ab = new byte[al];
i.readFully(ab);
String subchannel = new String(ab, StandardCharsets.UTF_8);
handlePacket(player, subchannel);
} catch (IOException e) {
log(Level.WARNING, "Failed to parse plugin message from " + player.getName() + ": " + e.getMessage());
}
}
private void handlePacket(Player player, String subchannel) {
if (!enabledWorlds.contains(player.getWorld().getName()) && !subchannel.equalsIgnoreCase("ready")) {
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, y0StatusPkt(false));
return;
}
switch (subchannel.toLowerCase()) {
case "ready2":
debug("Player " + player.getName() + " is READY.");
readyPlayers.add(player.getUniqueId());
if (enabledWorlds.contains(player.getWorld().getName())) {
readyPlayers.add(player.getUniqueId());
preCacheVisibleChunks(player);
if (chunkInjector != null) {
chunkInjector.inject(player);
}
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, y0StatusPkt(true));
resendChunksInView(player);
} else {
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, y0StatusPkt(false));
}
break;
case "use_on_block":
break;
case "ready":
if (kickOutdatedClients) {
player.kickPlayer("§cYour client is not compatible with the version of §6TuffX§c the server has installed!\n§7Please update your client.");
}
}
}
private void preCacheVisibleChunks(Player player) {
World world = player.getWorld();
int viewDistance = player.getClientViewDistance();
int playerChunkX = player.getLocation().getChunk().getX();
int playerChunkZ = player.getLocation().getChunk().getZ();
Object2ObjectOpenHashMap<BlockData, int[]> cvt = new Object2ObjectOpenHashMap<>(256);
for (int x = -viewDistance; x <= viewDistance; x++) {
for (int z = -viewDistance; z <= viewDistance; z++) {
int currentChunkX = playerChunkX + x;
int currentChunkZ = playerChunkZ + z;
if (world.isChunkLoaded(currentChunkX, currentChunkZ)) {
WorldChunk k = new WorldChunk(world.getName(), currentChunkX, currentChunkZ);
if (chunkCache.getIfPresent(k) == null) {
try {
Chunk chunk = world.getChunkAt(currentChunkX, currentChunkZ);
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, false, false);
ObjectArrayList<byte[]> pp = new ObjectArrayList<>(4);
cvt.clear();
for (int sy = -4; sy < 0; sy++) {
byte[] sectionData = createSectionPayload(snapshot, currentChunkX, currentChunkZ, sy, cvt);
if (sectionData != null) {
pp.add(sectionData);
}
}
chunkCache.put(k, pp);
storeCombined(k, pp);
} catch (Exception e) { debug("Exception while pre-caching visible chunks for player "+player.getName()+": "+e.getMessage()); }
}
}
}
}
}
private static final int Y0_CHUNKS_PER_TICK = 8;
public void resendChunksInView(Player player) {
World world = player.getWorld();
int viewDistance = player.getClientViewDistance();
int playerChunkX = player.getLocation().getChunk().getX();
int playerChunkZ = player.getLocation().getChunk().getZ();
List<int[]> chunks = new ArrayList<>();
for (int x = -viewDistance; x <= viewDistance; x++) {
for (int z = -viewDistance; z <= viewDistance; z++) {
int currentChunkX = playerChunkX + x;
int currentChunkZ = playerChunkZ + z;
if (world.isChunkLoaded(currentChunkX, currentChunkZ)) {
chunks.add(new int[]{currentChunkX, currentChunkZ, x * x + z * z});
}
}
}
chunks.sort((a, b) -> Integer.compare(a[2], b[2]));
sendY0ChunksBatched(player, world.getName(), chunks, 0);
}
private void sendY0ChunksBatched(Player player, String worldName, List<int[]> chunks, int startIndex) {
if (!player.isOnline() || startIndex >= chunks.size()) return;
int endIndex = Math.min(startIndex + Y0_CHUNKS_PER_TICK, chunks.size());
for (int i = startIndex; i < endIndex; i++) {
int[] chunk = chunks.get(i);
WorldChunk k = new WorldChunk(worldName, chunk[0], chunk[1]);
ObjectArrayList<byte[]> cachedData = chunkCache.getIfPresent(k);
if (cachedData != null && !cachedData.isEmpty()) {
for (byte[] py : cachedData) {
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, py);
}
}
}
if (endIndex < chunks.size()) {
final int nextStart = endIndex;
if (player.isOnline()) {
SchedulerCompat.runEntityLater(player, plugin, () -> sendY0ChunksBatched(player, worldName, chunks, nextStart), 1L);
}
}
}
private byte[] y0StatusPkt(boolean s) {
try (ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("y0_status");
out.writeBoolean(s);
return bout.toByteArray();
} catch (IOException e) { return null; }
}
private byte[] dimensionChangePkt() {
try (ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("dimension_change");
return bout.toByteArray();
} catch (IOException e) { return null; }
}
public void handlePlayerChangeWorld(PlayerChangedWorldEvent event) {
Player player = event.getPlayer();
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, dimensionChangePkt());
boolean isEnabledWorld = enabledWorlds.contains(player.getWorld().getName());
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, y0StatusPkt(isEnabledWorld));
if (isPlayerReady(player) && isEnabledWorld) {
resendChunksInView(player);
}
}
public void handlePlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, dimensionChangePkt());
boolean isEnabledWorld = enabledWorlds.contains(player.getWorld().getName());
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, y0StatusPkt(isEnabledWorld));
}
public void processAndSendChunk(final Player player, final Chunk c) {
if (c == null || player == null || !player.isOnline()) return;
if (!c.getWorld().equals(player.getWorld())) return;
if (enabledWorlds != null && !enabledWorlds.contains(c.getWorld().getName())) return;
final WorldChunk k = new WorldChunk(c.getWorld().getName(), c.getX(), c.getZ());
ObjectArrayList<byte[]> cachedData = chunkCache.getIfPresent(k);
if (cachedData != null) {
if (player.isOnline() && c.getWorld().equals(player.getWorld())) {
for (byte[] py : cachedData) {
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, py);
}
}
return;
}
final ChunkSnapshot snapshot = c.getChunkSnapshot(false, false, false);
processSnapshotAsync(player, snapshot, c.getX(), c.getZ());
}
private void processSnapshotAsync(final Player player, final ChunkSnapshot snapshot, final int chunkX, final int chunkZ) {
ExecutorService executor = chunkProcessor;
if (executor == null || executor.isShutdown()) return;
final WorldChunk k = new WorldChunk(snapshot.getWorldName(), chunkX, chunkZ);
executor.submit(() -> {
final ObjectArrayList<byte[]> pp = new ObjectArrayList<>(4);
final Object2ObjectOpenHashMap<BlockData, int[]> cvt = threadChunkData.get();
cvt.clear();
for (int sy = -4; sy < 0; sy++) {
if (!player.isOnline()) {
return;
}
try {
byte[] py = createSectionPayload(snapshot, chunkX, chunkZ, sy, cvt);
if (py != null) {
pp.add(py);
}
} catch (IOException e) {
severe("Payload creation failed: " + e.getMessage());
}
}
chunkCache.put(k, pp);
storeCombined(k, pp);
if (!pp.isEmpty()) {
for (byte[] py : pp) {
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, py);
}
}
});
}
private void invalidateChunkCache(World w, int x, int z) {
WorldChunk k = new WorldChunk(w.getName(), x >> 4, z >> 4);
chunkCache.invalidate(k);
chunkCacheCombined.invalidate(k);
}
public byte[] getY0DataForChunk(Player player, int chunkX, int chunkZ) {
if (!isPlayerReady(player)) return null;
World world = player.getWorld();
if (enabledWorlds == null || !enabledWorlds.contains(world.getName())) return null;
WorldChunk k = new WorldChunk(world.getName(), chunkX, chunkZ);
byte[] combined = chunkCacheCombined.getIfPresent(k);
if (combined != null) return combined.length > 0 ? combined : null;
ObjectArrayList<byte[]> cachedData = chunkCache.getIfPresent(k);
if (cachedData != null) {
byte[] cb = buildCombined(cachedData);
if (cb != null) chunkCacheCombined.put(k, cb);
return cb;
}
return null;
}
private byte[] buildCombined(ObjectArrayList<byte[]> sections) {
if (sections.isEmpty()) return null;
int totalLen = 0;
for (int i = 0, l = sections.size(); i < l; i++) {
totalLen += sections.get(i).length;
}
byte[] result = new byte[totalLen];
int offset = 0;
for (int i = 0, l = sections.size(); i < l; i++) {
byte[] s = sections.get(i);
System.arraycopy(s, 0, result, offset, s.length);
offset += s.length;
}
return result;
}
private void storeCombined(@Nonnull WorldChunk k, ObjectArrayList<byte[]> sections) {
byte[] cb = buildCombined(sections);
if (cb != null) {
chunkCacheCombined.put(k, cb);
}
}
public void preCacheY0Data(Player player, int chunkX, int chunkZ) {
if (!isPlayerReady(player)) return;
World world = player.getWorld();
if (enabledWorlds == null || !enabledWorlds.contains(world.getName())) return;
WorldChunk k = new WorldChunk(world.getName(), chunkX, chunkZ);
if (chunkCache.getIfPresent(k) != null) return;
if (!world.isChunkLoaded(chunkX, chunkZ)) return;
Chunk chunk = world.getChunkAt(chunkX, chunkZ);
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, false, false);
ExecutorService executor = chunkProcessor;
if (executor == null || executor.isShutdown()) return;
executor.submit(() -> {
try {
if (chunkCache.getIfPresent(k) != null) return;
Object2ObjectOpenHashMap<BlockData, int[]> cvt = threadChunkData.get();
cvt.clear();
ObjectArrayList<byte[]> pp = new ObjectArrayList<>(4);
for (int sy = -4; sy < 0; sy++) {
byte[] sectionData = createSectionPayload(snapshot, chunkX, chunkZ, sy, cvt);
if (sectionData != null) {
pp.add(sectionData);
}
}
chunkCache.put(k, pp);
storeCombined(k, pp);
} catch (Exception e) { debug("Exception while pre-caching chunk %s, %s for %s: %s".formatted(chunkX, chunkZ, player.getName(), e.getMessage())); }
});
}
public void cacheChunkWithCallback(Player player, int chunkX, int chunkZ, Consumer<byte[]> callback) {
if (!isPlayerReady(player)) {
callback.accept(null);
return;
}
World world = player.getWorld();
if (enabledWorlds == null || !enabledWorlds.contains(world.getName())) {
callback.accept(null);
return;
}
WorldChunk k = new WorldChunk(world.getName(), chunkX, chunkZ);
byte[] combined = chunkCacheCombined.getIfPresent(k);
if (combined != null) {
callback.accept(combined.length > 0 ? combined : null);
return;
}
ObjectArrayList<byte[]> existing = chunkCache.getIfPresent(k);
if (existing != null) {
byte[] cb = buildCombined(existing);
if (cb != null) chunkCacheCombined.put(k, cb);
callback.accept(cb);
return;
}
if (!world.isChunkLoaded(chunkX, chunkZ)) {
callback.accept(null);
return;
}
Chunk chunk = world.getChunkAt(chunkX, chunkZ);
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, false, false);
ExecutorService executor = chunkProcessor;
if (executor == null || executor.isShutdown()) {
callback.accept(null);
return;
}
executor.submit(() -> {
try {
byte[] cached = chunkCacheCombined.getIfPresent(k);
if (cached != null) {
callback.accept(cached.length > 0 ? cached : null);
return;
}
Object2ObjectOpenHashMap<BlockData, int[]> cvt = threadChunkData.get();
cvt.clear();
ObjectArrayList<byte[]> pp = new ObjectArrayList<>(4);
for (int sy = -4; sy < 0; sy++) {
byte[] sectionData = createSectionPayload(snapshot, chunkX, chunkZ, sy, cvt);
if (sectionData != null) {
pp.add(sectionData);
}
}
chunkCache.put(k, pp);
byte[] cb = buildCombined(pp);
if (cb != null) chunkCacheCombined.put(k, cb);
callback.accept(cb);
} catch (Exception e) {
callback.accept(null);
}
});
}
public void handlePlayerQuit(PlayerQuitEvent event) {
if (chunkInjector != null) {
chunkInjector.eject(event.getPlayer());
}
readyPlayers.remove(event.getPlayer().getUniqueId());
}
public void handleChunkLoad(ChunkLoadEvent event) {
if (enabledWorlds == null || !enabledWorlds.contains(event.getWorld().getName())) return;
if (!hasReadyPlayersInWorld(event.getWorld())) return;
Chunk chunk = event.getChunk();
int chunkX = chunk.getX();
int chunkZ = chunk.getZ();
WorldChunk k = new WorldChunk(event.getWorld().getName(), chunkX, chunkZ);
if (chunkCache.getIfPresent(k) != null) return;
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, false, false);
ExecutorService executor = chunkProcessor;
if (executor == null || executor.isShutdown()) return;
executor.submit(() -> {
try {
if (chunkCache.getIfPresent(k) != null) return;
Object2ObjectOpenHashMap<BlockData, int[]> cvt = threadChunkData.get();
cvt.clear();
ObjectArrayList<byte[]> pp = new ObjectArrayList<>(4);
for (int sy = -4; sy < 0; sy++) {
byte[] sectionData = createSectionPayload(snapshot, chunkX, chunkZ, sy, cvt);
if (sectionData != null) {
pp.add(sectionData);
}
}
chunkCache.put(k, pp);
storeCombined(k, pp);
} catch (Exception e) { debug("Exception while handling chunk load: "+e.getMessage()); }
});
}
private byte[] createSectionPayload(ChunkSnapshot s, int x, int z, int sy, Object2ObjectOpenHashMap<BlockData, int[]> c) throws IOException {
// Ensure thread-local buffer is exactly 12,288 bytes to prevent overflow
byte[] bd = threadData.get();
Arrays.fill(bd, (byte) 0);
int idx = 0;
boolean hasContent = false;
int by = sy << 4;
// Optimized Loop Order: Matches standard Minecraft internal memory layouts
for (int xx = 0; xx < 16; xx++) {
for (int zz = 0; zz < 16; zz++) {
for (int y = 0; y < 16; y++) {
int wy = by + y;
BlockData blkData = s.getBlockData(xx, wy, zz);
int[] ld = c.get(blkData); // Fast map lookup
if (ld == null) { // Avoid getOrDefault overhead
ld = (viaIds != null) ? viaIds.toLegacy(blkData) : EMPTY_LEGACY;
c.put(blkData, ld);
}
// Bitwise packing
short lb = (short) ((ld[1] << 12) | (ld[0] & 0xFFF));
byte pl = (byte) ((s.getBlockSkyLight(xx, wy, zz) << 4) | s.getBlockEmittedLight(xx, wy, zz));
// Write sequence
int linear = ((y << 8) | (zz << 4) | xx) * 3;
bd[linear] = (byte) (lb >> 8);
bd[linear + 1] = (byte) lb;
bd[linear + 2] = pl;
if (linear + 3 > idx) idx = linear + 3;
if (lb != 0 || pl != 0) {
hasContent = true;
}
}
}
}
if (!hasContent) return null;
ByteArrayOutputStream bout = threadOut.get();
bout.reset();
// DataOutputStream wrapper safely writes schema
try (DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("chunk_data");
out.writeInt(x);
out.writeInt(z);
out.writeInt(sy);
out.write(bd, 0, idx);
}
return bout.toByteArray();
}
public void handleBlockBreak(BlockBreakEvent event) {
if (event.getBlock().getY() < 0) {
handleBlockChange(event.getBlock().getLocation(), event.getBlock().getBlockData(), Material.AIR.createBlockData());
invalidateChunkCache(event.getBlock().getWorld(), event.getBlock().getX(), event.getBlock().getZ());
}
}
public void handleBlockPlace(BlockPlaceEvent event) {
if (event.getBlock().getY() < 0) {
handleBlockChange(event.getBlock().getLocation(), event.getBlockReplacedState().getBlockData(), event.getBlock().getBlockData());
invalidateChunkCache(event.getBlock().getWorld(), event.getBlock().getX(), event.getBlock().getZ());
}
}
public void handleBlockPhysics(BlockPhysicsEvent event) {
final Block block = event.getBlock();
if (block.getY() < 0) {
final Location loc = block.getLocation();
final World world = loc.getWorld();
SchedulerCompat.runRegionLater(plugin, loc, () -> {
BlockData ud = world.getBlockData(loc);
sendSingleBlockUpdate(loc, ud);
invalidateChunkCache(world, loc.getBlockX(), loc.getBlockZ());
}, 1L);
}
}
public void handleBlockExplode(BlockExplodeEvent event) {
final List<Block> btu = new ArrayList<>(event.blockList());
for (Block block : btu) {
if (block.getY() >= 0) continue;
final Location loc = block.getLocation();
SchedulerCompat.runRegionLater(plugin, loc, () -> {
sendSingleBlockUpdate(loc, Material.AIR.createBlockData());
invalidateChunkCache(loc.getWorld(), loc.getBlockX(), loc.getBlockZ());
}, 1L);
}
}
public void handleBlockFromTo(BlockFromToEvent event) {
final Block block = event.getToBlock();
if (block.getY() < 0) {
SchedulerCompat.runRegionLater(plugin, block.getLocation(), () -> {
sendSingleBlockUpdate(block.getLocation(), block.getBlockData());
invalidateChunkCache(block.getWorld(), block.getX(), block.getZ());
}, 1L);
}
}
private void sendSingleBlockUpdate(Location loc, BlockData data) {
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("block_update");
out.writeInt(loc.getBlockX());
out.writeInt(loc.getBlockY());
out.writeInt(loc.getBlockZ());
int[] ld = viaIds.toLegacy(data);
out.writeShort((short) ((ld[1] << 12) | (ld[0] & 0xFFF)));
byte[] py = bout.toByteArray();
sendToNearbyPlayers(loc, py);
} catch (IOException e) {
severe("Failed to create single block update payload: " + e.getMessage());
}
}
public static int getEmission(BlockData data) {
if (getLightEmissionMethod != null) {
try {
return (int) getLightEmissionMethod.invoke(data);
} catch (Exception e) {}
}
return legacy_light_map.getOrDefault(data.getMaterial(), 0);
}
private void handleBlockChange(Location loc, BlockData od, BlockData nd) {
sendSingleBlockUpdate(loc, nd);
boolean oe = getEmission(od) > 0;
boolean ne = getEmission(nd) > 0;
boolean oo = od.getMaterial().isOccluding();
boolean no = nd.getMaterial().isOccluding();
if (oe != ne || oo != no) {
sendLightUpdate(loc);
}
}
private void sendLightUpdate(Location loc) {
ObjectOpenHashSet<Coords> stu = new ObjectOpenHashSet<>();
World w = loc.getWorld();
int bx = loc.getBlockX();
int by = loc.getBlockY();
int bz = loc.getBlockZ();
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
for (int dz = -1; dz <= 1; dz++) {
int ny = by + dy;
if (ny < -64 || ny >= 0) continue;
stu.add(new Coords(
(bx + dx) >> 4,
ny >> 4,
(bz + dz) >> 4
));
}
}
}
for (Coords sc : stu) {
if (!w.isChunkLoaded(sc.x, sc.z)) continue;
ChunkSnapshot s = w.getChunkAt(sc.x, sc.z).getChunkSnapshot(true, false, false);
if (chunkProcessor != null && !chunkProcessor.isShutdown()) {
chunkProcessor.submit(() -> {
try {
byte[] py = createLightPayload(s, sc);
sendToNearbyPlayers(loc, py);
} catch (IOException e) {
severe("Failed to create lighting payload: " + e.getMessage());
}
});
}
}
}
private void sendToNearbyPlayers(Location loc, byte[] payload) {
if (payload == null || loc.getWorld() == null || readyPlayers.isEmpty()) return;
final World world = loc.getWorld();
SchedulerCompat.runGlobal(plugin, () -> {
for (Player player : Bukkit.getOnlinePlayers()) {
if (!player.isOnline() || !isPlayerReady(player)) continue;
if (!world.equals(player.getWorld())) continue;
if (player.getLocation().distanceSquared(loc) >= 4096) continue;
SchedulerCompat.runEntity(player, plugin, () -> {
if (!player.isOnline() || !isPlayerReady(player)) return;
if (!world.equals(player.getWorld())) return;
if (player.getLocation().distanceSquared(loc) >= 4096) return;
player.sendPluginMessage(plugin, CHANNEL, payload);
});
}
});
}
private byte[] createLightPayload(ChunkSnapshot s, Coords sc) throws IOException {
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(4120);
DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("lighting_update");
out.writeInt(sc.x);
out.writeInt(sc.z);
out.writeInt(sc.y);
byte[] ld = new byte[4096];
int by = sc.y * 16;
int i = 0;
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
int wy = by + y;
int bl = s.getBlockEmittedLight(x, wy, z);
int sl = s.getBlockSkyLight(x, wy, z);
ld[i++] = (byte) ((sl << 4) | bl);
}
}
}
out.write(ld);
return bout.toByteArray();
}
}
private boolean hasReadyPlayersInWorld(World world) {
for (UUID playerId : readyPlayers) {
Player player = plugin.getServer().getPlayer(playerId);
if (player != null && player.isOnline() && world.equals(player.getWorld())) {
return true;
}
}
return false;
}
}
+94
View File
@@ -0,0 +1,94 @@
# \\// //=\\
# || || - ||
# || \\=//
y0:
y0-enabled: true
# Debug mode
debug-mode: false
# Worlds for y0 to be enabled in
enabled-worlds:
- 'world'
# The amount of threads for the chunk processor to use - using more threads processes chunks faster, but increases CPU usage
# -1 is automatic
chunk-processor-threads: 3
# The size of the cache
cache-size: 192
# How long until the cache expires (minutes)
cache-expiration: 2
# Kick outdated clients that (generally) not compatible with this version of TuffX
kick-outdated-clients: false
# ||\\ ||== //_\\ <> //= ==== ||\\ \\//
# ||// ||== || __ || \\ || ||// ||
# ||\\ ||== \\__/ || =// || ||\\ ||
registry:
# Set to true to list your server on the TuffClient discovery
enabled: false
# WebSocket URL for the server discovery
server-url: 'wss://api.tuffest.org/ws'
# IP for your Eaglercraft server
server: 'wss://urserverip.net'
# \\ // <> //\\ ||\\ || //=\\ //= ||// //=
# \\ // || //==\\ ||<< || || || || ||\\ \\
# \\// || // \\ ||// ||== \\=// \\= || \\ =//
viablocks:
viablocks-enabled: false
# Set to true to send a welcome book to players with the ViaBlocks client when they join for the first time.
# This book can explain the feature and provide a link for bug reports.
send-welcome-book: false
# Set to true to enable detailed logging in the server console for debugging.
debug: false
# Sync mode controls how often chunk and block updates are sent to clients.
# normal: base speed for fast sync.
# reduced: much slower updates to minimize client lag.
sync-mode: normal
# \\ // <> //\\ ||== ||\ || ==== <> ==== <> ||== //=
# \\ // || //==\\ ||== ||\\|| || || || || ||== \\
# \\// || // \\ ||== || \|| || || || || ||== =//
viaentities:
viaentities-enabled: true
# Set to true to enable detailed logging in the server console for debugging.
debug: false
# Measured in blocks, leave as '-1' for the default view distance.
max-distance: -1
# ==== || || ||== ||== //\\ //= ==== <> //=\\ ||\ || //=
# || || || ||== ||== //==\\ || || || || || ||\\|| \\
# || \\=// || || // \\ \\= || || \\=// || \|| =//
# 1.13+ swimming
swimming:
enabled: true
debug: false
# Allows players to see 1.13+ things in the creative menu
creative-items:
enabled: true
debug: false
restrictions:
enabled: true
debug: false
# What TuffClient modules are not allowed
disallow:
- clientbrand
+84
View File
@@ -0,0 +1,84 @@
{
"modern_entities": {
"minecraft:allay": {"added": "1.19", "model": "allay", "animated": true},
"minecraft:armadillo": {"added": "1.20.5", "model": "armadillo", "animated": true},
"minecraft:axolotl": {"added": "1.17", "model": "axolotl", "animated": true},
"minecraft:bee": {"added": "1.15", "model": "bee", "animated": true},
"minecraft:bogged": {"added": "1.21", "model": "bogged", "animated": true},
"minecraft:breeze": {"added": "1.21", "model": "breeze", "animated": true},
"minecraft:breeze_wind_charge": {"added": "1.21", "model": "wind_charge", "animated": false},
"minecraft:camel": {"added": "1.20", "model": "camel", "animated": true},
"minecraft:cat": {"added": "1.14", "model": "cat", "animated": true},
"minecraft:cod": {"added": "1.13", "model": "cod", "animated": true},
"minecraft:copper_golem": {"added": "1.21", "model": "copper_golem", "animated": true},
"minecraft:creaking": {"added": "1.21", "model": "creaking", "animated": true},
"minecraft:dolphin": {"added": "1.13", "model": "dolphin", "animated": true},
"minecraft:drowned": {"added": "1.13", "model": "drowned", "animated": true},
"minecraft:fox": {"added": "1.14", "model": "fox", "animated": true},
"minecraft:frog": {"added": "1.19", "model": "frog", "animated": true},
"minecraft:glow_squid": {"added": "1.17", "model": "glow_squid", "animated": true},
"minecraft:goat": {"added": "1.17", "model": "goat", "animated": true},
"minecraft:happy_ghast": {"added": "1.21", "model": "happy_ghast", "animated": true},
"minecraft:hoglin": {"added": "1.16", "model": "hoglin", "animated": true},
"minecraft:panda": {"added": "1.14", "model": "panda", "animated": true},
"minecraft:phantom": {"added": "1.13", "model": "phantom", "animated": true},
"minecraft:piglin": {"added": "1.16", "model": "piglin", "animated": true},
"minecraft:piglin_brute": {"added": "1.16.2", "model": "piglin_brute", "animated": true},
"minecraft:pillager": {"added": "1.14", "model": "pillager", "animated": true},
"minecraft:pufferfish": {"added": "1.13", "model": "pufferfish", "animated": true},
"minecraft:ravager": {"added": "1.14", "model": "ravager", "animated": true},
"minecraft:salmon": {"added": "1.13", "model": "salmon", "animated": true},
"minecraft:sniffer": {"added": "1.20", "model": "sniffer", "animated": true},
"minecraft:strider": {"added": "1.16", "model": "strider", "animated": true},
"minecraft:tadpole": {"added": "1.19", "model": "tadpole", "animated": true},
"minecraft:trader_llama": {"added": "1.14", "model": "trader_llama", "animated": true},
"minecraft:trident": {"added": "1.13", "model": "trident", "animated": false},
"minecraft:tropical_fish": {"added": "1.13", "model": "tropical_fish", "animated": true},
"minecraft:turtle": {"added": "1.13", "model": "turtle", "animated": true},
"minecraft:vex": {"added": "1.11", "model": "vex", "animated": true},
"minecraft:wandering_trader": {"added": "1.14", "model": "wandering_trader", "animated": true},
"minecraft:warden": {"added": "1.19", "model": "warden", "animated": true},
"minecraft:wind_charge": {"added": "1.21", "model": "wind_charge", "animated": false},
"minecraft:zoglin": {"added": "1.16", "model": "zoglin", "animated": true},
"minecraft:glow_item_frame": {"added": "1.17", "model": "item_frame", "animated": false},
"minecraft:marker": {"added": "1.17", "model": null, "animated": false},
"minecraft:ominous_item_spawner": {"added": "1.21", "model": null, "animated": false}
},
"entity_size": {
"minecraft:allay": {"width": 0.35, "height": 0.6},
"minecraft:armadillo": {"width": 0.7, "height": 0.65},
"minecraft:axolotl": {"width": 0.75, "height": 0.42},
"minecraft:bee": {"width": 0.7, "height": 0.6},
"minecraft:bogged": {"width": 0.6, "height": 1.99},
"minecraft:breeze": {"width": 0.6, "height": 1.77},
"minecraft:camel": {"width": 1.7, "height": 2.375},
"minecraft:cat": {"width": 0.6, "height": 0.7},
"minecraft:cod": {"width": 0.5, "height": 0.3},
"minecraft:creaking": {"width": 0.9, "height": 2.7},
"minecraft:dolphin": {"width": 0.9, "height": 0.6},
"minecraft:drowned": {"width": 0.6, "height": 1.95},
"minecraft:fox": {"width": 0.6, "height": 0.7},
"minecraft:frog": {"width": 0.5, "height": 0.5},
"minecraft:glow_squid": {"width": 0.8, "height": 0.8},
"minecraft:goat": {"width": 0.9, "height": 1.3},
"minecraft:happy_ghast": {"width": 4.0, "height": 4.0},
"minecraft:hoglin": {"width": 1.4, "height": 1.4},
"minecraft:panda": {"width": 1.3, "height": 1.25},
"minecraft:phantom": {"width": 0.9, "height": 0.5},
"minecraft:piglin": {"width": 0.6, "height": 1.95},
"minecraft:piglin_brute": {"width": 0.6, "height": 1.95},
"minecraft:pillager": {"width": 0.6, "height": 1.95},
"minecraft:pufferfish": {"width": 0.7, "height": 0.7},
"minecraft:ravager": {"width": 1.95, "height": 2.2},
"minecraft:salmon": {"width": 0.7, "height": 0.4},
"minecraft:sniffer": {"width": 1.9, "height": 1.75},
"minecraft:strider": {"width": 0.9, "height": 1.7},
"minecraft:tadpole": {"width": 0.4, "height": 0.3},
"minecraft:trident": {"width": 0.5, "height": 0.5},
"minecraft:tropical_fish": {"width": 0.5, "height": 0.4},
"minecraft:turtle": {"width": 1.2, "height": 0.4},
"minecraft:vex": {"width": 0.4, "height": 0.8},
"minecraft:warden": {"width": 0.9, "height": 2.9},
"minecraft:zoglin": {"width": 1.4, "height": 1.4}
}
}
+467
View File
@@ -0,0 +1,467 @@
{
"sounds": [
"ambient.cave",
"block.anvil.break",
"block.anvil.destroy",
"block.anvil.fall",
"block.anvil.hit",
"block.anvil.land",
"block.anvil.place",
"block.anvil.step",
"block.anvil.use",
"block.brewing_stand.brew",
"block.chest.close",
"block.chest.locked",
"block.chest.open",
"block.chorus_flower.death",
"block.chorus_flower.grow",
"block.cloth.break",
"block.cloth.fall",
"block.cloth.hit",
"block.cloth.place",
"block.cloth.step",
"block.comparator.click",
"block.dispenser.dispense",
"block.dispenser.fail",
"block.dispenser.launch",
"block.enchantment_table.use",
"block.end_gateway.spawn",
"block.enderchest.close",
"block.enderchest.open",
"block.fence_gate.close",
"block.fence_gate.open",
"block.fire.ambient",
"block.fire.extinguish",
"block.furnace.fire_crackle",
"block.glass.break",
"block.glass.fall",
"block.glass.hit",
"block.glass.place",
"block.glass.step",
"block.grass.break",
"block.grass.fall",
"block.grass.hit",
"block.grass.place",
"block.grass.step",
"block.gravel.break",
"block.gravel.fall",
"block.gravel.hit",
"block.gravel.place",
"block.gravel.step",
"block.iron_door.close",
"block.iron_door.open",
"block.iron_trapdoor.close",
"block.iron_trapdoor.open",
"block.ladder.break",
"block.ladder.fall",
"block.ladder.hit",
"block.ladder.place",
"block.ladder.step",
"block.lava.ambient",
"block.lava.extinguish",
"block.lava.pop",
"block.lever.click",
"block.metal.break",
"block.metal.fall",
"block.metal.hit",
"block.metal.place",
"block.metal.step",
"block.metal_pressureplate.click_off",
"block.metal_pressureplate.click_on",
"block.note.basedrum",
"block.note.bass",
"block.note.harp",
"block.note.hat",
"block.note.pling",
"block.note.snare",
"block.piston.contract",
"block.piston.extend",
"block.portal.ambient",
"block.portal.travel",
"block.portal.trigger",
"block.redstone_torch.burnout",
"block.sand.break",
"block.sand.fall",
"block.sand.hit",
"block.sand.place",
"block.sand.step",
"block.slime.break",
"block.slime.fall",
"block.slime.hit",
"block.slime.place",
"block.slime.step",
"block.snow.break",
"block.snow.fall",
"block.snow.hit",
"block.snow.place",
"block.snow.step",
"block.stone.break",
"block.stone.fall",
"block.stone.hit",
"block.stone.place",
"block.stone.step",
"block.stone_button.click_off",
"block.stone_button.click_on",
"block.stone_pressureplate.click_off",
"block.stone_pressureplate.click_on",
"block.tripwire.attach",
"block.tripwire.click_off",
"block.tripwire.click_on",
"block.tripwire.detach",
"block.water.ambient",
"block.waterlily.place",
"block.wood.break",
"block.wood.fall",
"block.wood.hit",
"block.wood.place",
"block.wood.step",
"block.wood_button.click_off",
"block.wood_button.click_on",
"block.wood_pressureplate.click_off",
"block.wood_pressureplate.click_on",
"block.wooden_door.close",
"block.wooden_door.open",
"block.wooden_trapdoor.close",
"block.wooden_trapdoor.open",
"enchant.thorns.hit",
"entity.armorstand.break",
"entity.armorstand.fall",
"entity.armorstand.hit",
"entity.armorstand.place",
"entity.arrow.hit",
"entity.arrow.hit_player",
"entity.arrow.shoot",
"entity.bat.ambient",
"entity.bat.death",
"entity.bat.hurt",
"entity.bat.loop",
"entity.bat.takeoff",
"entity.blaze.ambient",
"entity.blaze.burn",
"entity.blaze.death",
"entity.blaze.hurt",
"entity.blaze.shoot",
"entity.bobber.splash",
"entity.bobber.throw",
"entity.cat.ambient",
"entity.cat.death",
"entity.cat.hiss",
"entity.cat.hurt",
"entity.cat.purr",
"entity.cat.purreow",
"entity.chicken.ambient",
"entity.chicken.death",
"entity.chicken.egg",
"entity.chicken.hurt",
"entity.chicken.step",
"entity.cow.ambient",
"entity.cow.death",
"entity.cow.hurt",
"entity.cow.milk",
"entity.cow.step",
"entity.creeper.death",
"entity.creeper.hurt",
"entity.creeper.primed",
"entity.donkey.ambient",
"entity.donkey.angry",
"entity.donkey.chest",
"entity.donkey.death",
"entity.donkey.hurt",
"entity.egg.throw",
"entity.elder_guardian.ambient",
"entity.elder_guardian.ambient_land",
"entity.elder_guardian.curse",
"entity.elder_guardian.death",
"entity.elder_guardian.death_land",
"entity.elder_guardian.hurt",
"entity.elder_guardian.hurt_land",
"entity.enderdragon.ambient",
"entity.enderdragon.death",
"entity.enderdragon.flap",
"entity.enderdragon.growl",
"entity.enderdragon.hurt",
"entity.enderdragon.shoot",
"entity.enderdragon_fireball.explode",
"entity.endereye.launch",
"entity.endermen.ambient",
"entity.endermen.death",
"entity.endermen.hurt",
"entity.endermen.scream",
"entity.endermen.stare",
"entity.endermen.teleport",
"entity.endermite.ambient",
"entity.endermite.death",
"entity.endermite.hurt",
"entity.endermite.step",
"entity.enderpearl.throw",
"entity.experience_bottle.throw",
"entity.experience_orb.pickup",
"entity.experience_orb.touch",
"entity.firework.blast",
"entity.firework.blast_far",
"entity.firework.large_blast",
"entity.firework.large_blast_far",
"entity.firework.launch",
"entity.firework.shoot",
"entity.firework.twinkle",
"entity.firework.twinkle_far",
"entity.generic.big_fall",
"entity.generic.burn",
"entity.generic.death",
"entity.generic.drink",
"entity.generic.eat",
"entity.generic.explode",
"entity.generic.extinguish_fire",
"entity.generic.hurt",
"entity.generic.small_fall",
"entity.generic.splash",
"entity.generic.swim",
"entity.ghast.ambient",
"entity.ghast.death",
"entity.ghast.hurt",
"entity.ghast.scream",
"entity.ghast.shoot",
"entity.ghast.warn",
"entity.guardian.ambient",
"entity.guardian.ambient_land",
"entity.guardian.attack",
"entity.guardian.death",
"entity.guardian.death_land",
"entity.guardian.flop",
"entity.guardian.hurt",
"entity.guardian.hurt_land",
"entity.horse.ambient",
"entity.horse.angry",
"entity.horse.armor",
"entity.horse.breathe",
"entity.horse.death",
"entity.horse.eat",
"entity.horse.gallop",
"entity.horse.hurt",
"entity.horse.jump",
"entity.horse.land",
"entity.horse.saddle",
"entity.horse.step",
"entity.horse.step_wood",
"entity.hostile.big_fall",
"entity.hostile.death",
"entity.hostile.hurt",
"entity.hostile.small_fall",
"entity.hostile.splash",
"entity.hostile.swim",
"entity.husk.ambient",
"entity.husk.death",
"entity.husk.hurt",
"entity.husk.step",
"entity.irongolem.attack",
"entity.irongolem.death",
"entity.irongolem.hurt",
"entity.irongolem.step",
"entity.item.break",
"entity.item.pickup",
"entity.itemframe.add_item",
"entity.itemframe.break",
"entity.itemframe.place",
"entity.itemframe.remove_item",
"entity.itemframe.rotate_item",
"entity.leashknot.break",
"entity.leashknot.place",
"entity.lightning.impact",
"entity.lightning.thunder",
"entity.lingeringpotion.throw",
"entity.magmacube.death",
"entity.magmacube.hurt",
"entity.magmacube.jump",
"entity.magmacube.squish",
"entity.minecart.inside",
"entity.minecart.riding",
"entity.mooshroom.shear",
"entity.mule.ambient",
"entity.mule.death",
"entity.mule.hurt",
"entity.painting.break",
"entity.painting.place",
"entity.pig.ambient",
"entity.pig.death",
"entity.pig.hurt",
"entity.pig.saddle",
"entity.pig.step",
"entity.player.attack.crit",
"entity.player.attack.knockback",
"entity.player.attack.nodamage",
"entity.player.attack.strong",
"entity.player.attack.sweep",
"entity.player.attack.weak",
"entity.player.big_fall",
"entity.player.breath",
"entity.player.burp",
"entity.player.death",
"entity.player.hurt",
"entity.player.levelup",
"entity.player.small_fall",
"entity.player.splash",
"entity.player.swim",
"entity.polar_bear.ambient",
"entity.polar_bear.baby_ambient",
"entity.polar_bear.death",
"entity.polar_bear.hurt",
"entity.polar_bear.step",
"entity.polar_bear.warning",
"entity.rabbit.ambient",
"entity.rabbit.attack",
"entity.rabbit.death",
"entity.rabbit.hurt",
"entity.rabbit.jump",
"entity.sheep.ambient",
"entity.sheep.death",
"entity.sheep.hurt",
"entity.sheep.shear",
"entity.sheep.step",
"entity.shulker.ambient",
"entity.shulker.close",
"entity.shulker.death",
"entity.shulker.hurt",
"entity.shulker.hurt_closed",
"entity.shulker.open",
"entity.shulker.shoot",
"entity.shulker.teleport",
"entity.shulker_bullet.hit",
"entity.shulker_bullet.hurt",
"entity.silverfish.ambient",
"entity.silverfish.death",
"entity.silverfish.hurt",
"entity.silverfish.step",
"entity.skeleton.ambient",
"entity.skeleton.death",
"entity.skeleton.hurt",
"entity.skeleton.shoot",
"entity.skeleton.step",
"entity.skeleton_horse.ambient",
"entity.skeleton_horse.death",
"entity.skeleton_horse.hurt",
"entity.slime.attack",
"entity.slime.death",
"entity.slime.hurt",
"entity.slime.jump",
"entity.slime.squish",
"entity.small_magmacube.death",
"entity.small_magmacube.hurt",
"entity.small_magmacube.squish",
"entity.small_slime.death",
"entity.small_slime.hurt",
"entity.small_slime.jump",
"entity.small_slime.squish",
"entity.snowball.throw",
"entity.snowman.ambient",
"entity.snowman.death",
"entity.snowman.hurt",
"entity.snowman.shoot",
"entity.spider.ambient",
"entity.spider.death",
"entity.spider.hurt",
"entity.spider.step",
"entity.splash_potion.break",
"entity.splash_potion.throw",
"entity.squid.ambient",
"entity.squid.death",
"entity.squid.hurt",
"entity.stray.ambient",
"entity.stray.death",
"entity.stray.hurt",
"entity.stray.step",
"entity.tnt.primed",
"entity.villager.ambient",
"entity.villager.death",
"entity.villager.hurt",
"entity.villager.no",
"entity.villager.trading",
"entity.villager.yes",
"entity.witch.ambient",
"entity.witch.death",
"entity.witch.drink",
"entity.witch.hurt",
"entity.witch.throw",
"entity.wither.ambient",
"entity.wither.break_block",
"entity.wither.death",
"entity.wither.hurt",
"entity.wither.shoot",
"entity.wither.spawn",
"entity.wither_skeleton.ambient",
"entity.wither_skeleton.death",
"entity.wither_skeleton.hurt",
"entity.wither_skeleton.step",
"entity.wolf.ambient",
"entity.wolf.death",
"entity.wolf.growl",
"entity.wolf.howl",
"entity.wolf.hurt",
"entity.wolf.pant",
"entity.wolf.shake",
"entity.wolf.step",
"entity.wolf.whine",
"entity.zombie.ambient",
"entity.zombie.attack_door_wood",
"entity.zombie.attack_iron_door",
"entity.zombie.break_door_wood",
"entity.zombie.death",
"entity.zombie.hurt",
"entity.zombie.infect",
"entity.zombie.step",
"entity.zombie_horse.ambient",
"entity.zombie_horse.death",
"entity.zombie_horse.hurt",
"entity.zombie_pig.ambient",
"entity.zombie_pig.angry",
"entity.zombie_pig.death",
"entity.zombie_pig.hurt",
"entity.zombie_villager.ambient",
"entity.zombie_villager.converted",
"entity.zombie_villager.cure",
"entity.zombie_villager.death",
"entity.zombie_villager.hurt",
"entity.zombie_villager.step",
"item.armor.equip_chain",
"item.armor.equip_diamond",
"item.armor.equip_generic",
"item.armor.equip_gold",
"item.armor.equip_iron",
"item.armor.equip_leather",
"item.bottle.fill",
"item.bottle.fill_dragonbreath",
"item.bucket.empty",
"item.bucket.empty_lava",
"item.bucket.fill",
"item.bucket.fill_lava",
"item.chorus_fruit.teleport",
"item.elytra.flying",
"item.firecharge.use",
"item.flintandsteel.use",
"item.hoe.till",
"item.shield.block",
"item.shield.break",
"item.shovel.flatten",
"music.creative",
"music.credits",
"music.dragon",
"music.end",
"music.game",
"music.menu",
"music.nether",
"record.11",
"record.13",
"record.blocks",
"record.cat",
"record.chirp",
"record.far",
"record.mall",
"record.mellohi",
"record.stal",
"record.strad",
"record.wait",
"record.ward",
"ui.button.click",
"weather.rain",
"weather.rain.above"
]
}
+497
View File
@@ -0,0 +1,497 @@
{
"sounds": [
"ambient.cave",
"block.anvil.break",
"block.anvil.destroy",
"block.anvil.fall",
"block.anvil.hit",
"block.anvil.land",
"block.anvil.place",
"block.anvil.step",
"block.anvil.use",
"block.brewing_stand.brew",
"block.chest.close",
"block.chest.locked",
"block.chest.open",
"block.chorus_flower.death",
"block.chorus_flower.grow",
"block.cloth.break",
"block.cloth.fall",
"block.cloth.hit",
"block.cloth.place",
"block.cloth.step",
"block.comparator.click",
"block.dispenser.dispense",
"block.dispenser.fail",
"block.dispenser.launch",
"block.enchantment_table.use",
"block.end_gateway.spawn",
"block.enderchest.close",
"block.enderchest.open",
"block.fence_gate.close",
"block.fence_gate.open",
"block.fire.ambient",
"block.fire.extinguish",
"block.furnace.fire_crackle",
"block.glass.break",
"block.glass.fall",
"block.glass.hit",
"block.glass.place",
"block.glass.step",
"block.grass.break",
"block.grass.fall",
"block.grass.hit",
"block.grass.place",
"block.grass.step",
"block.gravel.break",
"block.gravel.fall",
"block.gravel.hit",
"block.gravel.place",
"block.gravel.step",
"block.iron_door.close",
"block.iron_door.open",
"block.iron_trapdoor.close",
"block.iron_trapdoor.open",
"block.ladder.break",
"block.ladder.fall",
"block.ladder.hit",
"block.ladder.place",
"block.ladder.step",
"block.lava.ambient",
"block.lava.extinguish",
"block.lava.pop",
"block.lever.click",
"block.metal.break",
"block.metal.fall",
"block.metal.hit",
"block.metal.place",
"block.metal.step",
"block.metal_pressureplate.click_off",
"block.metal_pressureplate.click_on",
"block.note.basedrum",
"block.note.bass",
"block.note.harp",
"block.note.hat",
"block.note.pling",
"block.note.snare",
"block.piston.contract",
"block.piston.extend",
"block.portal.ambient",
"block.portal.travel",
"block.portal.trigger",
"block.redstone_torch.burnout",
"block.sand.break",
"block.sand.fall",
"block.sand.hit",
"block.sand.place",
"block.sand.step",
"block.shulker_box.close",
"block.shulker_box.open",
"block.slime.break",
"block.slime.fall",
"block.slime.hit",
"block.slime.place",
"block.slime.step",
"block.snow.break",
"block.snow.fall",
"block.snow.hit",
"block.snow.place",
"block.snow.step",
"block.stone.break",
"block.stone.fall",
"block.stone.hit",
"block.stone.place",
"block.stone.step",
"block.stone_button.click_off",
"block.stone_button.click_on",
"block.stone_pressureplate.click_off",
"block.stone_pressureplate.click_on",
"block.tripwire.attach",
"block.tripwire.click_off",
"block.tripwire.click_on",
"block.tripwire.detach",
"block.water.ambient",
"block.waterlily.place",
"block.wood.break",
"block.wood.fall",
"block.wood.hit",
"block.wood.place",
"block.wood.step",
"block.wood_button.click_off",
"block.wood_button.click_on",
"block.wood_pressureplate.click_off",
"block.wood_pressureplate.click_on",
"block.wooden_door.close",
"block.wooden_door.open",
"block.wooden_trapdoor.close",
"block.wooden_trapdoor.open",
"enchant.thorns.hit",
"entity.armorstand.break",
"entity.armorstand.fall",
"entity.armorstand.hit",
"entity.armorstand.place",
"entity.arrow.hit",
"entity.arrow.hit_player",
"entity.arrow.shoot",
"entity.bat.ambient",
"entity.bat.death",
"entity.bat.hurt",
"entity.bat.loop",
"entity.bat.takeoff",
"entity.blaze.ambient",
"entity.blaze.burn",
"entity.blaze.death",
"entity.blaze.hurt",
"entity.blaze.shoot",
"entity.bobber.splash",
"entity.bobber.throw",
"entity.cat.ambient",
"entity.cat.death",
"entity.cat.hiss",
"entity.cat.hurt",
"entity.cat.purr",
"entity.cat.purreow",
"entity.chicken.ambient",
"entity.chicken.death",
"entity.chicken.egg",
"entity.chicken.hurt",
"entity.chicken.step",
"entity.cow.ambient",
"entity.cow.death",
"entity.cow.hurt",
"entity.cow.milk",
"entity.cow.step",
"entity.creeper.death",
"entity.creeper.hurt",
"entity.creeper.primed",
"entity.donkey.ambient",
"entity.donkey.angry",
"entity.donkey.chest",
"entity.donkey.death",
"entity.donkey.hurt",
"entity.egg.throw",
"entity.elder_guardian.ambient",
"entity.elder_guardian.ambient_land",
"entity.elder_guardian.curse",
"entity.elder_guardian.death",
"entity.elder_guardian.death_land",
"entity.elder_guardian.hurt",
"entity.elder_guardian.flop",
"entity.elder_guardian.hurt_land",
"entity.enderdragon.ambient",
"entity.enderdragon.death",
"entity.enderdragon.flap",
"entity.enderdragon.growl",
"entity.enderdragon.hurt",
"entity.enderdragon.shoot",
"entity.enderdragon_fireball.explode",
"entity.endereye.launch",
"entity.endermen.ambient",
"entity.endermen.death",
"entity.endermen.hurt",
"entity.endermen.scream",
"entity.endermen.stare",
"entity.endermen.teleport",
"entity.endermite.ambient",
"entity.endermite.death",
"entity.endermite.hurt",
"entity.endermite.step",
"entity.enderpearl.throw",
"entity.evocation_fangs.attack",
"entity.evocation_illager.ambient",
"entity.evocation_illager.cast_spell",
"entity.evocation_illager.death",
"entity.evocation_illager.hurt",
"entity.evocation_illager.prepare_attack",
"entity.evocation_illager.prepare_summon",
"entity.evocation_illager.prepare_wololo",
"entity.experience_bottle.throw",
"entity.experience_orb.pickup",
"entity.firework.blast",
"entity.firework.blast_far",
"entity.firework.large_blast",
"entity.firework.large_blast_far",
"entity.firework.launch",
"entity.firework.shoot",
"entity.firework.twinkle",
"entity.firework.twinkle_far",
"entity.generic.big_fall",
"entity.generic.burn",
"entity.generic.death",
"entity.generic.drink",
"entity.generic.eat",
"entity.generic.explode",
"entity.generic.extinguish_fire",
"entity.generic.hurt",
"entity.generic.small_fall",
"entity.generic.splash",
"entity.generic.swim",
"entity.ghast.ambient",
"entity.ghast.death",
"entity.ghast.hurt",
"entity.ghast.scream",
"entity.ghast.shoot",
"entity.ghast.warn",
"entity.guardian.ambient",
"entity.guardian.ambient_land",
"entity.guardian.attack",
"entity.guardian.death",
"entity.guardian.death_land",
"entity.guardian.flop",
"entity.guardian.hurt",
"entity.guardian.hurt_land",
"entity.horse.ambient",
"entity.horse.angry",
"entity.horse.armor",
"entity.horse.breathe",
"entity.horse.death",
"entity.horse.eat",
"entity.horse.gallop",
"entity.horse.hurt",
"entity.horse.jump",
"entity.horse.land",
"entity.horse.saddle",
"entity.horse.step",
"entity.horse.step_wood",
"entity.hostile.big_fall",
"entity.hostile.death",
"entity.hostile.hurt",
"entity.hostile.small_fall",
"entity.hostile.splash",
"entity.hostile.swim",
"entity.husk.ambient",
"entity.husk.death",
"entity.husk.hurt",
"entity.husk.step",
"entity.irongolem.attack",
"entity.irongolem.death",
"entity.irongolem.hurt",
"entity.irongolem.step",
"entity.item.break",
"entity.item.pickup",
"entity.itemframe.add_item",
"entity.itemframe.break",
"entity.itemframe.place",
"entity.itemframe.remove_item",
"entity.itemframe.rotate_item",
"entity.leashknot.break",
"entity.leashknot.place",
"entity.lightning.impact",
"entity.lightning.thunder",
"entity.lingeringpotion.throw",
"entity.llama.ambient",
"entity.llama.angry",
"entity.llama.chest",
"entity.llama.death",
"entity.llama.eat",
"entity.llama.hurt",
"entity.llama.spit",
"entity.llama.step",
"entity.llama.swag",
"entity.magmacube.death",
"entity.magmacube.hurt",
"entity.magmacube.jump",
"entity.magmacube.squish",
"entity.minecart.inside",
"entity.minecart.riding",
"entity.mooshroom.shear",
"entity.mule.ambient",
"entity.mule.chest",
"entity.mule.death",
"entity.mule.hurt",
"entity.painting.break",
"entity.painting.place",
"entity.pig.ambient",
"entity.pig.death",
"entity.pig.hurt",
"entity.pig.saddle",
"entity.pig.step",
"entity.player.attack.crit",
"entity.player.attack.knockback",
"entity.player.attack.nodamage",
"entity.player.attack.strong",
"entity.player.attack.sweep",
"entity.player.attack.weak",
"entity.player.big_fall",
"entity.player.breath",
"entity.player.burp",
"entity.player.death",
"entity.player.hurt",
"entity.player.levelup",
"entity.player.small_fall",
"entity.player.splash",
"entity.player.swim",
"entity.polar_bear.ambient",
"entity.polar_bear.baby_ambient",
"entity.polar_bear.death",
"entity.polar_bear.hurt",
"entity.polar_bear.step",
"entity.polar_bear.warning",
"entity.rabbit.ambient",
"entity.rabbit.attack",
"entity.rabbit.death",
"entity.rabbit.hurt",
"entity.rabbit.jump",
"entity.sheep.ambient",
"entity.sheep.death",
"entity.sheep.hurt",
"entity.sheep.shear",
"entity.sheep.step",
"entity.shulker.ambient",
"entity.shulker.close",
"entity.shulker.death",
"entity.shulker.hurt",
"entity.shulker.hurt_closed",
"entity.shulker.open",
"entity.shulker.shoot",
"entity.shulker.teleport",
"entity.shulker_bullet.hit",
"entity.shulker_bullet.hurt",
"entity.silverfish.ambient",
"entity.silverfish.death",
"entity.silverfish.hurt",
"entity.silverfish.step",
"entity.skeleton.ambient",
"entity.skeleton.death",
"entity.skeleton.hurt",
"entity.skeleton.shoot",
"entity.skeleton.step",
"entity.skeleton_horse.ambient",
"entity.skeleton_horse.death",
"entity.skeleton_horse.hurt",
"entity.slime.attack",
"entity.slime.death",
"entity.slime.hurt",
"entity.slime.jump",
"entity.slime.squish",
"entity.small_magmacube.death",
"entity.small_magmacube.hurt",
"entity.small_magmacube.squish",
"entity.small_slime.death",
"entity.small_slime.hurt",
"entity.small_slime.jump",
"entity.small_slime.squish",
"entity.snowball.throw",
"entity.snowman.ambient",
"entity.snowman.death",
"entity.snowman.hurt",
"entity.snowman.shoot",
"entity.spider.ambient",
"entity.spider.death",
"entity.spider.hurt",
"entity.spider.step",
"entity.splash_potion.break",
"entity.splash_potion.throw",
"entity.squid.ambient",
"entity.squid.death",
"entity.squid.hurt",
"entity.stray.ambient",
"entity.stray.death",
"entity.stray.hurt",
"entity.stray.step",
"entity.tnt.primed",
"entity.vex.ambient",
"entity.vex.charge",
"entity.vex.death",
"entity.vex.hurt",
"entity.villager.ambient",
"entity.villager.death",
"entity.villager.hurt",
"entity.villager.no",
"entity.villager.trading",
"entity.villager.yes",
"entity.vindication_illager.ambient",
"entity.vindication_illager.death",
"entity.vindication_illager.hurt",
"entity.witch.ambient",
"entity.witch.death",
"entity.witch.drink",
"entity.witch.hurt",
"entity.witch.throw",
"entity.wither.ambient",
"entity.wither.break_block",
"entity.wither.death",
"entity.wither.hurt",
"entity.wither.shoot",
"entity.wither.spawn",
"entity.wither_skeleton.ambient",
"entity.wither_skeleton.death",
"entity.wither_skeleton.hurt",
"entity.wither_skeleton.step",
"entity.wolf.ambient",
"entity.wolf.death",
"entity.wolf.growl",
"entity.wolf.howl",
"entity.wolf.hurt",
"entity.wolf.pant",
"entity.wolf.shake",
"entity.wolf.step",
"entity.wolf.whine",
"entity.zombie.ambient",
"entity.zombie.attack_door_wood",
"entity.zombie.attack_iron_door",
"entity.zombie.break_door_wood",
"entity.zombie.death",
"entity.zombie.hurt",
"entity.zombie.infect",
"entity.zombie.step",
"entity.zombie_horse.ambient",
"entity.zombie_horse.death",
"entity.zombie_horse.hurt",
"entity.zombie_pig.ambient",
"entity.zombie_pig.angry",
"entity.zombie_pig.death",
"entity.zombie_pig.hurt",
"entity.zombie_villager.ambient",
"entity.zombie_villager.converted",
"entity.zombie_villager.cure",
"entity.zombie_villager.death",
"entity.zombie_villager.hurt",
"entity.zombie_villager.step",
"item.armor.equip_chain",
"item.armor.equip_diamond",
"item.armor.equip_elytra",
"item.armor.equip_generic",
"item.armor.equip_gold",
"item.armor.equip_iron",
"item.armor.equip_leather",
"item.bottle.empty",
"item.bottle.fill",
"item.bottle.fill_dragonbreath",
"item.bucket.empty",
"item.bucket.empty_lava",
"item.bucket.fill",
"item.bucket.fill_lava",
"item.chorus_fruit.teleport",
"item.elytra.flying",
"item.firecharge.use",
"item.flintandsteel.use",
"item.hoe.till",
"item.shield.block",
"item.shield.break",
"item.shovel.flatten",
"item.totem.use",
"music.creative",
"music.credits",
"music.dragon",
"music.end",
"music.game",
"music.menu",
"music.nether",
"record.11",
"record.13",
"record.blocks",
"record.cat",
"record.chirp",
"record.far",
"record.mall",
"record.mellohi",
"record.stal",
"record.strad",
"record.wait",
"record.ward",
"ui.button.click",
"weather.rain",
"weather.rain.above"
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
{
}
+250
View File
@@ -0,0 +1,250 @@
{
"sounds": [
"ambient.cave.cave",
"ambient.weather.rain",
"ambient.weather.thunder",
"game.player.hurt.fall.big",
"game.player.hurt.fall.small",
"game.neutral.hurt.fall.big",
"game.neutral.hurt.fall.small",
"game.hostile.hurt.fall.big",
"game.hostile.hurt.fall.small",
"game.player.hurt",
"game.neutral.hurt",
"game.hostile.hurt",
"game.player.die",
"game.neutral.die",
"game.hostile.die",
"dig.cloth",
"dig.grass",
"dig.gravel",
"dig.sand",
"dig.snow",
"dig.stone",
"dig.wood",
"fire.fire",
"fire.ignite",
"item.fireCharge.use",
"fireworks.blast",
"fireworks.blast_far",
"fireworks.largeBlast",
"fireworks.largeBlast_far",
"fireworks.launch",
"fireworks.twinkle",
"fireworks.twinkle_far",
"liquid.lava",
"liquid.lavapop",
"game.neutral.swim.splash",
"game.player.swim.splash",
"game.hostile.swim.splash",
"game.player.swim",
"game.neutral.swim",
"game.hostile.swim",
"liquid.water",
"minecart.base",
"minecart.inside",
"mob.bat.death",
"mob.bat.hurt",
"mob.bat.idle",
"mob.bat.loop",
"mob.bat.takeoff",
"mob.blaze.breathe",
"mob.blaze.death",
"mob.blaze.hit",
"mob.guardian.hit",
"mob.guardian.idle",
"mob.guardian.death",
"mob.guardian.elder.hit",
"mob.guardian.elder.idle",
"mob.guardian.elder.death",
"mob.guardian.land.hit",
"mob.guardian.land.idle",
"mob.guardian.land.death",
"mob.guardian.curse",
"mob.guardian.attack",
"mob.guardian.flop",
"mob.cat.hiss",
"mob.cat.hitt",
"mob.cat.meow",
"mob.cat.purr",
"mob.cat.purreow",
"mob.chicken.hurt",
"mob.chicken.plop",
"mob.chicken.say",
"mob.chicken.step",
"mob.cow.hurt",
"mob.cow.say",
"mob.cow.step",
"mob.creeper.death",
"mob.creeper.say",
"mob.enderdragon.end",
"mob.enderdragon.growl",
"mob.enderdragon.hit",
"mob.enderdragon.wings",
"mob.endermen.death",
"mob.endermen.hit",
"mob.endermen.idle",
"mob.endermen.portal",
"mob.endermen.scream",
"mob.endermen.stare",
"mob.ghast.affectionate_scream",
"mob.ghast.charge",
"mob.ghast.death",
"mob.ghast.fireball",
"mob.ghast.moan",
"mob.ghast.scream",
"mob.horse.angry",
"mob.horse.armor",
"mob.horse.breathe",
"mob.horse.death",
"mob.horse.donkey.angry",
"mob.horse.donkey.death",
"mob.horse.donkey.hit",
"mob.horse.donkey.idle",
"mob.horse.gallop",
"mob.horse.hit",
"mob.horse.idle",
"mob.horse.jump",
"mob.horse.land",
"mob.horse.leather",
"mob.horse.skeleton.death",
"mob.horse.skeleton.hit",
"mob.horse.skeleton.idle",
"mob.horse.soft",
"mob.horse.wood",
"mob.horse.zombie.death",
"mob.horse.zombie.hit",
"mob.horse.zombie.idle",
"mob.irongolem.death",
"mob.irongolem.hit",
"mob.irongolem.throw",
"mob.irongolem.walk",
"mob.magmacube.big",
"mob.magmacube.jump",
"mob.magmacube.small",
"mob.pig.death",
"mob.pig.say",
"mob.pig.step",
"mob.rabbit.hurt",
"mob.rabbit.idle",
"mob.rabbit.hop",
"mob.rabbit.death",
"mob.sheep.say",
"mob.sheep.shear",
"mob.sheep.step",
"mob.silverfish.hit",
"mob.silverfish.kill",
"mob.silverfish.say",
"mob.silverfish.step",
"mob.skeleton.death",
"mob.skeleton.hurt",
"mob.skeleton.say",
"mob.skeleton.step",
"mob.slime.attack",
"mob.slime.big",
"mob.slime.small",
"mob.spider.death",
"mob.spider.say",
"mob.spider.step",
"mob.villager.death",
"mob.villager.haggle",
"mob.villager.hit",
"mob.villager.idle",
"mob.villager.no",
"mob.villager.yes",
"mob.wither.death",
"mob.wither.hurt",
"mob.wither.idle",
"mob.wither.shoot",
"mob.wither.spawn",
"mob.wolf.bark",
"mob.wolf.death",
"mob.wolf.growl",
"mob.wolf.howl",
"mob.wolf.hurt",
"mob.wolf.panting",
"mob.wolf.shake",
"mob.wolf.step",
"mob.wolf.whine",
"mob.zombie.death",
"mob.zombie.hurt",
"mob.zombie.infect",
"mob.zombie.metal",
"mob.zombie.remedy",
"mob.zombie.say",
"mob.zombie.step",
"mob.zombie.unfect",
"mob.zombie.wood",
"mob.zombie.woodbreak",
"mob.zombiepig.zpig",
"mob.zombiepig.zpigangry",
"mob.zombiepig.zpigdeath",
"mob.zombiepig.zpighurt",
"note.bass",
"note.bassattack",
"note.bd",
"note.harp",
"note.hat",
"note.pling",
"note.snare",
"portal.portal",
"portal.travel",
"portal.trigger",
"random.anvil_break",
"random.anvil_land",
"random.anvil_use",
"random.bow",
"random.bowhit",
"random.break",
"random.burp",
"random.chestclosed",
"random.chestopen",
"gui.button.press",
"random.click",
"random.door_close",
"random.door_open",
"random.drink",
"random.eat",
"random.explode",
"random.fizz",
"game.tnt.primed",
"creeper.primed",
"dig.glass",
"game.potion.smash",
"random.levelup",
"random.orb",
"random.pop",
"random.splash",
"random.successful_hit",
"random.wood_click",
"records.11",
"records.13",
"records.blocks",
"records.cat",
"records.chirp",
"records.far",
"records.mall",
"records.mellohi",
"records.stal",
"records.strad",
"records.wait",
"records.ward",
"step.cloth",
"step.grass",
"step.gravel",
"step.ladder",
"step.sand",
"step.snow",
"step.stone",
"step.wood",
"tile.piston.in",
"tile.piston.out",
"music.menu",
"music.game",
"music.game.creative",
"music.game.end",
"music.game.end.dragon",
"music.game.end.credits",
"music.game.nether"
]
}
+448
View File
@@ -0,0 +1,448 @@
{
"sounds": [
"ambient.cave",
"block.anvil.break",
"block.anvil.destroy",
"block.anvil.fall",
"block.anvil.hit",
"block.anvil.land",
"block.anvil.place",
"block.anvil.step",
"block.anvil.use",
"block.brewing_stand.brew",
"block.chest.close",
"block.chest.locked",
"block.chest.open",
"block.chorus_flower.death",
"block.chorus_flower.grow",
"block.cloth.break",
"block.cloth.fall",
"block.cloth.hit",
"block.cloth.place",
"block.cloth.step",
"block.comparator.click",
"block.dispenser.dispense",
"block.dispenser.fail",
"block.dispenser.launch",
"block.end_gateway.spawn",
"block.enderchest.close",
"block.enderchest.open",
"block.fence_gate.close",
"block.fence_gate.open",
"block.fire.ambient",
"block.fire.extinguish",
"block.furnace.fire_crackle",
"block.glass.break",
"block.glass.fall",
"block.glass.hit",
"block.glass.place",
"block.glass.step",
"block.grass.break",
"block.grass.fall",
"block.grass.hit",
"block.grass.place",
"block.grass.step",
"block.gravel.break",
"block.gravel.fall",
"block.gravel.hit",
"block.gravel.place",
"block.gravel.step",
"block.iron_door.close",
"block.iron_door.open",
"block.iron_trapdoor.close",
"block.iron_trapdoor.open",
"block.ladder.break",
"block.ladder.fall",
"block.ladder.hit",
"block.ladder.place",
"block.ladder.step",
"block.lava.ambient",
"block.lava.extinguish",
"block.lava.pop",
"block.lever.click",
"block.metal.break",
"block.metal.fall",
"block.metal.hit",
"block.metal.place",
"block.metal.step",
"block.metal_pressureplate.click_off",
"block.metal_pressureplate.click_on",
"block.note.basedrum",
"block.note.bass",
"block.note.harp",
"block.note.hat",
"block.note.pling",
"block.note.snare",
"block.piston.contract",
"block.piston.extend",
"block.portal.ambient",
"block.portal.travel",
"block.portal.trigger",
"block.redstone_torch.burnout",
"block.sand.break",
"block.sand.fall",
"block.sand.hit",
"block.sand.place",
"block.sand.step",
"block.slime.break",
"block.slime.fall",
"block.slime.hit",
"block.slime.place",
"block.slime.step",
"block.snow.break",
"block.snow.fall",
"block.snow.hit",
"block.snow.place",
"block.snow.step",
"block.stone.break",
"block.stone.fall",
"block.stone.hit",
"block.stone.place",
"block.stone.step",
"block.stone_button.click_off",
"block.stone_button.click_on",
"block.stone_pressureplate.click_off",
"block.stone_pressureplate.click_on",
"block.tripwire.attach",
"block.tripwire.click_off",
"block.tripwire.click_on",
"block.tripwire.detach",
"block.water.ambient",
"block.waterlily.place",
"block.wood.break",
"block.wood.fall",
"block.wood.hit",
"block.wood.place",
"block.wood.step",
"block.wood_button.click_off",
"block.wood_button.click_on",
"block.wood_pressureplate.click_off",
"block.wood_pressureplate.click_on",
"block.wooden_door.close",
"block.wooden_door.open",
"block.wooden_trapdoor.close",
"block.wooden_trapdoor.open",
"enchant.thorns.hit",
"entity.armorstand.break",
"entity.armorstand.fall",
"entity.armorstand.hit",
"entity.armorstand.place",
"entity.arrow.hit",
"entity.arrow.hit_player",
"entity.arrow.shoot",
"entity.bat.ambient",
"entity.bat.death",
"entity.bat.hurt",
"entity.bat.loop",
"entity.bat.takeoff",
"entity.blaze.ambient",
"entity.blaze.burn",
"entity.blaze.death",
"entity.blaze.hurt",
"entity.blaze.shoot",
"entity.bobber.splash",
"entity.bobber.throw",
"entity.cat.ambient",
"entity.cat.death",
"entity.cat.hiss",
"entity.cat.hurt",
"entity.cat.purr",
"entity.cat.purreow",
"entity.chicken.ambient",
"entity.chicken.death",
"entity.chicken.egg",
"entity.chicken.hurt",
"entity.chicken.step",
"entity.cow.ambient",
"entity.cow.death",
"entity.cow.hurt",
"entity.cow.milk",
"entity.cow.step",
"entity.creeper.death",
"entity.creeper.hurt",
"entity.creeper.primed",
"entity.donkey.ambient",
"entity.donkey.angry",
"entity.donkey.chest",
"entity.donkey.death",
"entity.donkey.hurt",
"entity.egg.throw",
"entity.elder_guardian.ambient",
"entity.elder_guardian.ambient_land",
"entity.elder_guardian.curse",
"entity.elder_guardian.death",
"entity.elder_guardian.death_land",
"entity.elder_guardian.hurt",
"entity.elder_guardian.hurt_land",
"entity.enderdragon.ambient",
"entity.enderdragon.death",
"entity.enderdragon.flap",
"entity.enderdragon.growl",
"entity.enderdragon.hurt",
"entity.enderdragon.shoot",
"entity.enderdragon_fireball.explode",
"entity.endereye.launch",
"entity.endermen.ambient",
"entity.endermen.death",
"entity.endermen.hurt",
"entity.endermen.scream",
"entity.endermen.stare",
"entity.endermen.teleport",
"entity.endermite.ambient",
"entity.endermite.death",
"entity.endermite.hurt",
"entity.endermite.step",
"entity.enderpearl.throw",
"entity.experience_bottle.throw",
"entity.experience_orb.pickup",
"entity.experience_orb.touch",
"entity.firework.blast",
"entity.firework.blast_far",
"entity.firework.large_blast",
"entity.firework.large_blast_far",
"entity.firework.launch",
"entity.firework.shoot",
"entity.firework.twinkle",
"entity.firework.twinkle_far",
"entity.generic.big_fall",
"entity.generic.burn",
"entity.generic.death",
"entity.generic.drink",
"entity.generic.eat",
"entity.generic.explode",
"entity.generic.extinguish_fire",
"entity.generic.hurt",
"entity.generic.small_fall",
"entity.generic.splash",
"entity.generic.swim",
"entity.ghast.ambient",
"entity.ghast.death",
"entity.ghast.hurt",
"entity.ghast.scream",
"entity.ghast.shoot",
"entity.ghast.warn",
"entity.guardian.ambient",
"entity.guardian.ambient_land",
"entity.guardian.attack",
"entity.guardian.death",
"entity.guardian.death_land",
"entity.guardian.flop",
"entity.guardian.hurt",
"entity.guardian.hurt_land",
"entity.horse.ambient",
"entity.horse.angry",
"entity.horse.armor",
"entity.horse.breathe",
"entity.horse.death",
"entity.horse.eat",
"entity.horse.gallop",
"entity.horse.hurt",
"entity.horse.jump",
"entity.horse.land",
"entity.horse.saddle",
"entity.horse.step",
"entity.horse.step_wood",
"entity.hostile.big_fall",
"entity.hostile.death",
"entity.hostile.hurt",
"entity.hostile.small_fall",
"entity.hostile.splash",
"entity.hostile.swim",
"entity.irongolem.attack",
"entity.irongolem.death",
"entity.irongolem.hurt",
"entity.irongolem.step",
"entity.item.break",
"entity.item.pickup",
"entity.itemframe.add_item",
"entity.itemframe.break",
"entity.itemframe.place",
"entity.itemframe.remove_item",
"entity.itemframe.rotate_item",
"entity.leashknot.break",
"entity.leashknot.place",
"entity.lightning.impact",
"entity.lightning.thunder",
"entity.lingeringpotion.throw",
"entity.magmacube.death",
"entity.magmacube.hurt",
"entity.magmacube.jump",
"entity.magmacube.squish",
"entity.minecart.inside",
"entity.minecart.riding",
"entity.mooshroom.shear",
"entity.mule.ambient",
"entity.mule.death",
"entity.mule.hurt",
"entity.painting.break",
"entity.painting.place",
"entity.pig.ambient",
"entity.pig.death",
"entity.pig.hurt",
"entity.pig.saddle",
"entity.pig.step",
"entity.player.attack.crit",
"entity.player.attack.knockback",
"entity.player.attack.nodamage",
"entity.player.attack.strong",
"entity.player.attack.sweep",
"entity.player.attack.weak",
"entity.player.big_fall",
"entity.player.breath",
"entity.player.burp",
"entity.player.death",
"entity.player.hurt",
"entity.player.levelup",
"entity.player.small_fall",
"entity.player.splash",
"entity.player.swim",
"entity.rabbit.ambient",
"entity.rabbit.attack",
"entity.rabbit.death",
"entity.rabbit.hurt",
"entity.rabbit.jump",
"entity.sheep.ambient",
"entity.sheep.death",
"entity.sheep.hurt",
"entity.sheep.shear",
"entity.sheep.step",
"entity.shulker.ambient",
"entity.shulker.close",
"entity.shulker.death",
"entity.shulker.hurt",
"entity.shulker.hurt_closed",
"entity.shulker.open",
"entity.shulker.shoot",
"entity.shulker.teleport",
"entity.shulker_bullet.hit",
"entity.shulker_bullet.hurt",
"entity.silverfish.ambient",
"entity.silverfish.death",
"entity.silverfish.hurt",
"entity.silverfish.step",
"entity.skeleton.ambient",
"entity.skeleton.death",
"entity.skeleton.hurt",
"entity.skeleton.shoot",
"entity.skeleton.step",
"entity.skeleton_horse.ambient",
"entity.skeleton_horse.death",
"entity.skeleton_horse.hurt",
"entity.slime.attack",
"entity.slime.death",
"entity.slime.hurt",
"entity.slime.jump",
"entity.slime.squish",
"entity.small_magmacube.death",
"entity.small_magmacube.hurt",
"entity.small_magmacube.squish",
"entity.small_slime.death",
"entity.small_slime.hurt",
"entity.small_slime.jump",
"entity.small_slime.squish",
"entity.snowball.throw",
"entity.snowman.ambient",
"entity.snowman.death",
"entity.snowman.hurt",
"entity.snowman.shoot",
"entity.spider.ambient",
"entity.spider.death",
"entity.spider.hurt",
"entity.spider.step",
"entity.splash_potion.break",
"entity.splash_potion.throw",
"entity.squid.ambient",
"entity.squid.death",
"entity.squid.hurt",
"entity.tnt.primed",
"entity.villager.ambient",
"entity.villager.death",
"entity.villager.hurt",
"entity.villager.no",
"entity.villager.trading",
"entity.villager.yes",
"entity.witch.ambient",
"entity.witch.death",
"entity.witch.drink",
"entity.witch.hurt",
"entity.witch.throw",
"entity.wither.ambient",
"entity.wither.break_block",
"entity.wither.death",
"entity.wither.hurt",
"entity.wither.shoot",
"entity.wither.spawn",
"entity.wolf.ambient",
"entity.wolf.death",
"entity.wolf.growl",
"entity.wolf.howl",
"entity.wolf.hurt",
"entity.wolf.pant",
"entity.wolf.shake",
"entity.wolf.step",
"entity.wolf.whine",
"entity.zombie.ambient",
"entity.zombie.attack_door_wood",
"entity.zombie.attack_iron_door",
"entity.zombie.break_door_wood",
"entity.zombie.death",
"entity.zombie.hurt",
"entity.zombie.infect",
"entity.zombie.step",
"entity.zombie_horse.ambient",
"entity.zombie_horse.death",
"entity.zombie_horse.hurt",
"entity.zombie_pig.ambient",
"entity.zombie_pig.angry",
"entity.zombie_pig.death",
"entity.zombie_pig.hurt",
"entity.zombie_villager.ambient",
"entity.zombie_villager.converted",
"entity.zombie_villager.cure",
"entity.zombie_villager.death",
"entity.zombie_villager.hurt",
"entity.zombie_villager.step",
"item.armor.equip_chain",
"item.armor.equip_diamond",
"item.armor.equip_generic",
"item.armor.equip_gold",
"item.armor.equip_iron",
"item.armor.equip_leather",
"item.bottle.fill",
"item.bottle.fill_dragonbreath",
"item.bucket.empty",
"item.bucket.empty_lava",
"item.bucket.fill",
"item.bucket.fill_lava",
"item.chorus_fruit.teleport",
"item.elytra.flying",
"item.firecharge.use",
"item.flintandsteel.use",
"item.hoe.till",
"item.shield.block",
"item.shield.break",
"item.shovel.flatten",
"music.creative",
"music.credits",
"music.dragon",
"music.end",
"music.game",
"music.menu",
"music.nether",
"record.11",
"record.13",
"record.blocks",
"record.cat",
"record.chirp",
"record.far",
"record.mall",
"record.mellohi",
"record.stal",
"record.strad",
"record.wait",
"record.ward",
"ui.button.click",
"weather.rain",
"weather.rain.above"
]
}
+48
View File
@@ -0,0 +1,48 @@
name: ${name}
version: ${version}
main: tf.tuff.TuffX
author: Potato
authors: [Potato, SyntaxSavy, llucasandersen, coleis1op, UplandJacob, MrNorshare]
api-version: 1.18
depend: [ViaVersion, ViaBackwards]
commands:
viablocks:
description: Main command for ViaBlocks.
usage: /viablocks <get|refresh>
permission: tuffx.viablocks.command
tuffx:
description: Main command for TuffX.
usage: /tuffx <reload>
restrictions:
description: Allow or disallow TuffClient modules.
usage: /restrictions <allow|disallow> <module>
tuffxclearcache:
description: Clears TuffXPlus Y0 cache
usage: /tuffxclearcache
permission: tuffx.admin
permissions:
tuffx.reload:
description: Allows use of the /tuffx reload subcommand.
default: op
tuffx.viablocks.command:
description: Allows use of the /viablocks command.
default: op
tuffx.viablocks.command.get:
description: Allows use of the /viablocks get subcommand.
default: op
tuffx.viablocks.command.refresh:
description: Allows use of the /viablocks refresh subcommand.
default: op
tuffx.restrictions.command:
description: Allows use of the /restrictions command.
default: op
tuffx.restrictions.command.allow:
description: Allows use of the /restrictions allow subcommand.
default: op
tuffx.restrictions.command.disallow:
description: Allows use of the /restrictions disallow subcommand.
default: op
tuffx.admin:
default: op
File diff suppressed because it is too large Load Diff
+45
View File
@@ -0,0 +1,45 @@
package tf.tuff;
import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class TuffXTest {
private static ServerMock server;
private static TuffX plugin;
@BeforeEach
void setUp() {
server = MockBukkit.mock();
plugin = MockBukkit.load(TuffX.class);
}
@AfterEach
void tearDown() {
MockBukkit.unmock();
}
@Test
void pluginEnablesSuccessfully() {
assertTrue(plugin.isEnabled(), "Plugin should be enabled after load");
}
@Test
void reloadDoesNotThrow() {
assertDoesNotThrow(() -> plugin.reloadTuffX(),
"reloadTuffX() should not throw");
}
@Test
void findsMappingFile() {
assertTrue(plugin.y0Plugin.viaIds.findMappingFile("26.1.1").version().equals("1.21.11"), "Mapping file for 26.1.1 should be 1.21.11"); // check jump from 26.x to 1.21.x
assertTrue(plugin.y0Plugin.viaIds.findMappingFile("1.21.11").version().equals("1.21.11"), "Mapping file for 1.21.11 should be 1.21.11"); // check direct match
assertTrue(plugin.y0Plugin.viaIds.findMappingFile("1.21.10").version().equals("1.21.9"), "Mapping file for 1.21.10 should be 1.21.9"); // check fallback to one patch version lower
assertTrue(plugin.y0Plugin.viaIds.findMappingFile("1.21.1").version().equals("1.21"), "Mapping file for 1.21.1 should be 1.21"); // check one that won't have a patch version
}
}
@@ -0,0 +1,48 @@
package tf.tuff.netty;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class ChunkHandlerTest {
@Test
void decodesSingleBlockChangePositionWithNegativeCoordinates() {
long packed = packBlockPosition(-21, -64, 37);
ChunkHandler.BlockChangePosition position = ChunkHandler.decodeSingleBlockChangePosition(packed);
assertEquals(-21, position.x());
assertEquals(-64, position.y());
assertEquals(37, position.z());
}
@Test
void decodesSectionBlockChangePositionBelowYZero() {
long sectionPosition = packSectionPosition(12, -5, -9);
long entry = packSectionEntry(3, 11, 7, 8123);
ChunkHandler.BlockChangePosition position = ChunkHandler.decodeMultiBlockChangePosition(sectionPosition, entry);
assertEquals((12 << 4) + 3, position.x());
assertEquals((-5 << 4) + 7, position.y());
assertEquals((-9 << 4) + 11, position.z());
}
private static long packBlockPosition(int x, int y, int z) {
return ((long) x & 0x3FFFFFFL) << 38
| ((long) z & 0x3FFFFFFL) << 12
| ((long) y & 0xFFFL);
}
private static long packSectionPosition(int x, int y, int z) {
return ((long) x & 0x3FFFFFL) << 42
| ((long) z & 0x3FFFFFL) << 20
| ((long) y & 0xFFFFFL);
}
private static long packSectionEntry(int localX, int localZ, int localY, int blockStateId) {
int localPosition = ((localX & 0xF) << 8) | ((localZ & 0xF) << 4) | (localY & 0xF);
return ((long) blockStateId << 12) | (localPosition & 0xFFFL);
}
}