Custom fluids with ProtocolLib

Showcase video:

//!import org.bukkit.Bukkit
//!import org.bukkit.Material
//!import com.comphenix.protocol.wrappers.WrappedBlockData
//!ProtocolLib

const Bukkit = org.bukkit.Bukkit;
const Material = org.bukkit.Material;

const createDataMethod = getMethod(WrappedBlockData, "createData", ["org.bukkit.Material"]);
const fluidBlocks = new Set([
  Material.WATER,
  Material.LAVA,
  Material.FLOWING_WATER,
  Material.FLOWING_LAVA
]);


var SpoofedBlock = createDataMethod.invoke(null, [Material.OAK_LOG]);
var IsSpooferActive = false;
var listener = null;

const handler = {
  onSend(event) {
    const packet = event.getPacket();
    const type = event.getPacketType().name();

    if (type === "BLOCK_CHANGE") {
      const blockData = packet.getBlockData().read(0);
      const material = blockData.getType();
      if (fluidBlocks.has(material)) {
        packet.getBlockData().write(0, SpoofedBlock);
      }

    } else if (type === "MULTI_BLOCK_CHANGE") {
      const blocks = packet.getBlockDataArrays().read(0);
      for (let i = 0; i < blocks.length; i++) {
       if (fluidBlocks.has(blocks[i].getType())) {
          blocks[i] = SpoofedBlock;
        }
      }
      packet.getBlockDataArrays().write(0, blocks);
    }
  },
  onReceive() {
  }
};

function loadPacketListener() {
  listener = ProtocolLib.registerListener("NORMAL", handler, [
    "Play.Server.BLOCK_CHANGE",
    "Play.Server.MULTI_BLOCK_CHANGE"
  ]);
  log.info("Block spoof listener active.");
}

function setSpooferActive(Active) {
  if (Active === IsSpooferActive) {
    return "The block-spoofer is already " + (Active ? "active" : "inactive");
  }
  if (!Active) {
    if (listener) {
      ProtocolLib.unregisterListener(listener);
      listener = null;
    }
  } else if (!listener) {
    loadPacketListener();
  }
  IsSpooferActive = Active;
  return "Block-spoofer is now " + (Active ? "active" : "inactive");
}

addCommand("blockspoofer", {
  onCommand: function(sender, args) {
    args = toArray(args); // Convert Java array to JS array

    if (args.length === 0) {
      sender.sendMessage("§eUsage:");
      sender.sendMessage("§e/blockspoofer setblock <material>");
      sender.sendMessage("§e/blockspoofer enabled <true|false>");
      return;
    }

    const subcommand = args[0].toLowerCase();

    if (subcommand === "setblock") {
      if (args.length < 2) {
        sender.sendMessage("§cUsage: /blockspoofer setblock <material>");
        return;
      }
      const blockName = args[1].toUpperCase();
      const material = Material.matchMaterial(blockName);
      if (material === null || !material.isBlock()) {
        sender.sendMessage("§cInvalid block: " + blockName);
        return;
      }

      SpoofedBlock = createDataMethod.invoke(null, [material]);
      sender.sendMessage("§aSpoofed block set to: §f" + material.name());
    }

    else if (subcommand === "enabled") {
      if (args.length < 2) {
        sender.sendMessage("§cUsage: /blockspoofer enabled <true|false>");
        return;
      }
      const val = args[1].toLowerCase();
      if (val === "true") {
        sender.sendMessage("§a"+setSpooferActive(true));
      } else if (val === "false") {
        sender.sendMessage("§c"+setSpooferActive(false));
      } else {
        sender.sendMessage("§cInvalid value: use true or false.");
      }
    }

    else {
      sender.sendMessage("§cUnknown subcommand: " + subcommand);
    }
  },

  onTabComplete: function(sender, args) {
    args = toArray(args);

    if (args.length === 1) {
      return toJavaList(["setblock", "enabled"]);
    }

    const sub = args[0].toLowerCase();

    if (args.length === 2 && sub === "enabled") {
      return toJavaList(["true", "false"]);
    }

    if (args.length === 2 && sub === "setblock") {
      const prefix = args[1].toLowerCase();
      const matches = [];

      for (const mat of Material.values()) {
        if (mat.isBlock() && mat.name().toLowerCase().startsWith(prefix)) {
          matches.push(mat.name().toLowerCase());
        }
      }

      return toJavaList(matches.slice(0, 50)); // limit to 50 results for sanity
    }

    return toJavaList([]);
  }
});

setSpooferActive(true);

Last updated

Was this helpful?