All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.spigotmc.AntiXray Maven / Gradle / Ivy

package org.spigotmc;

import gnu.trove.set.TByteSet;
import gnu.trove.set.hash.TByteHashSet;
import net.minecraft.server.Block;
import net.minecraft.server.BlockPosition;
import net.minecraft.server.Blocks;
import net.minecraft.server.World;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;

public class AntiXray {

  private static final CustomTimingsHandler update = new CustomTimingsHandler("xray - update");
  private static final CustomTimingsHandler obfuscate = new CustomTimingsHandler("xray - obfuscate");
  /*========================================================================*/
  // Used to keep track of which blocks to obfuscate
  private final boolean[] obfuscateBlocks = new boolean[Short.MAX_VALUE];
  // Used to select a random replacement ore
  private final byte[] replacementOres;

  public AntiXray(SpigotWorldConfig config) {
    // Set all listed blocks as true to be obfuscated
    for (int id : (config.engineMode == 1) ? config.hiddenBlocks : config.replaceBlocks) {
      obfuscateBlocks[id] = true;
    }

    // For every block
    TByteSet blocks = new TByteHashSet();
    for (Integer i : config.hiddenBlocks) {
      Block block = Block.getById(i);
      // Check it exists and is not a tile entity
      if (block != null && !block.isTileEntity()) {
        // Add it to the set of replacement blocks
        blocks.add((byte) (int) i);
      }
    }
    // Bake it to a flat array of replacements
    replacementOres = blocks.toArray();
  }

  private static boolean isLoaded(World world, BlockPosition position, int radius) {
    return world.isLoaded(position)
      && (radius == 0 ||
      (isLoaded(world, position.east(), radius - 1)
        && isLoaded(world, position.west(), radius - 1)
        && isLoaded(world, position.up(), radius - 1)
        && isLoaded(world, position.down(), radius - 1)
        && isLoaded(world, position.south(), radius - 1)
        && isLoaded(world, position.north(), radius - 1)));
  }

  private static boolean hasTransparentBlockAdjacent(World world, BlockPosition position, int radius) {
    return !isSolidBlock(world.getType(position, false).getBlock()) /* isSolidBlock */
      || (radius > 0
      && (hasTransparentBlockAdjacent(world, position.east(), radius - 1)
      || hasTransparentBlockAdjacent(world, position.west(), radius - 1)
      || hasTransparentBlockAdjacent(world, position.up(), radius - 1)
      || hasTransparentBlockAdjacent(world, position.down(), radius - 1)
      || hasTransparentBlockAdjacent(world, position.south(), radius - 1)
      || hasTransparentBlockAdjacent(world, position.north(), radius - 1)));
  }

  private static boolean isSolidBlock(Block block) {
    // Mob spawners are treated as solid blocks as far as the
    // game is concerned for lighting and other tasks but for
    // rendering they can be seen through therefor we special
    // case them so that the antixray doesn't show the fake
    // blocks around them.
    return block.isOccluding() && block != Blocks.MOB_SPAWNER && block != Blocks.BARRIER;
  }

  /**
   * Starts the timings handler, then updates all blocks within the set radius
   * of the given coordinate, revealing them if they are hidden ores.
   */
  public void updateNearbyBlocks(World world, BlockPosition position) {
    if (world.spigotConfig.antiXray) {
      update.startTiming();
      updateNearbyBlocks(world, position, 2, false); // 2 is the radius, we shouldn't change it as that would make it exponentially slower
      update.stopTiming();
    }
  }

  /**
   * Starts the timings handler, and then removes all non exposed ores from
   * the chunk buffer.
   */
  public void obfuscateSync(int chunkX, int chunkY, int bitmask, byte[] buffer, World world) {
    if (world.spigotConfig.antiXray) {
      obfuscate.startTiming();
      obfuscate(chunkX, chunkY, bitmask, buffer, world);
      obfuscate.stopTiming();
    }
  }

  /**
   * Removes all non exposed ores from the chunk buffer.
   */
  public void obfuscate(int chunkX, int chunkY, int bitmask, byte[] buffer, World world) {
    // If the world is marked as obfuscated
    if (world.spigotConfig.antiXray) {
      // Initial radius to search around for air
      int initialRadius = 1;
      // Which block in the buffer we are looking at, anywhere from 0 to 16^4
      int index = 0;
      // The iterator marking which random ore we should use next
      int randomOre = 0;

      // Chunk corner X and Z blocks
      int startX = chunkX << 4;
      int startZ = chunkY << 4;

      byte replaceWithTypeId;
      switch (world.getWorld().getEnvironment()) {
        case NETHER:
          replaceWithTypeId = (byte) CraftMagicNumbers.getId(Blocks.NETHERRACK);
          break;
        case THE_END:
          replaceWithTypeId = (byte) CraftMagicNumbers.getId(Blocks.END_STONE);
          break;
        default:
          replaceWithTypeId = (byte) CraftMagicNumbers.getId(Blocks.STONE);
          break;
      }

      // Chunks can have up to 16 sections
      for (int i = 0; i < 16; i++) {
        // If the bitmask indicates this chunk is sent...
        if ((bitmask & 1 << i) != 0) {
          // Work through all blocks in the chunk, y,z,x
          for (int y = 0; y < 16; y++) {
            for (int z = 0; z < 16; z++) {
              for (int x = 0; x < 16; x++) {
                // For some reason we can get too far ahead of ourselves (concurrent modification on bulk chunks?) so if we do, just abort and move on
                if (index >= buffer.length) {
                  index++;
                  continue;
                }
                // Grab the block ID in the buffer.
                // TODO: extended IDs are not yet supported
                int blockId = (buffer[index << 1] & 0xFF)
                  | ((buffer[(index << 1) + 1] & 0xFF) << 8);
                blockId >>>= 4;
                // Check if the block should be obfuscated
                if (obfuscateBlocks[blockId]) {
                  // The world isn't loaded, bail out
                  if (!isLoaded(world, new BlockPosition(startX + x, (i << 4) + y, startZ + z), initialRadius)) {
                    index++;
                    continue;
                  }
                  // On the otherhand, if radius is 0, or the nearby blocks are all non air, we can obfuscate
                  if (!hasTransparentBlockAdjacent(world, new BlockPosition(startX + x, (i << 4) + y, startZ + z), initialRadius)) {
                    int newId = blockId;
                    switch (world.spigotConfig.engineMode) {
                      case 1:
                        // Replace with replacement material
                        newId = replaceWithTypeId & 0xFF;
                        break;
                      case 2:
                        // Replace with random ore.
                        if (randomOre >= replacementOres.length) {
                          randomOre = 0;
                        }
                        newId = replacementOres[randomOre++] & 0xFF;
                        break;
                    }
                    newId <<= 4;
                    buffer[index << 1] = (byte) (newId & 0xFF);
                    buffer[(index << 1) + 1] = (byte) ((newId >> 8) & 0xFF);
                  }
                }

                index++;
              }
            }
          }
        }
      }
    }
  }

  private void updateNearbyBlocks(World world, BlockPosition position, int radius, boolean updateSelf) {
    // If the block in question is loaded
    if (world.isLoaded(position)) {
      // Get block id
      Block block = world.getType(position).getBlock();

      // See if it needs update
      if (updateSelf && obfuscateBlocks[Block.getId(block)]) {
        // Send the update
        world.notify(position);
      }

      // Check other blocks for updates
      if (radius > 0) {
        updateNearbyBlocks(world, position.east(), radius - 1, true);
        updateNearbyBlocks(world, position.west(), radius - 1, true);
        updateNearbyBlocks(world, position.up(), radius - 1, true);
        updateNearbyBlocks(world, position.down(), radius - 1, true);
        updateNearbyBlocks(world, position.south(), radius - 1, true);
        updateNearbyBlocks(world, position.north(), radius - 1, true);
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy