/*
* Copyright (C) 2011-2014 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.orebfuscator.obfuscation;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Player;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtType;
import com.lishid.orebfuscator.DeprecatedMethods;
import com.lishid.orebfuscator.Orebfuscator;
import com.lishid.orebfuscator.cache.ObfuscatedCachedChunk;
import com.lishid.orebfuscator.cache.ObfuscatedDataCache;
import com.lishid.orebfuscator.chunkmap.ChunkData;
import com.lishid.orebfuscator.chunkmap.ChunkMapManager;
import com.lishid.orebfuscator.config.ProximityHiderConfig;
import com.lishid.orebfuscator.config.WorldConfig;
import com.lishid.orebfuscator.types.BlockCoord;
import com.lishid.orebfuscator.types.BlockState;
public class Calculations {
private static Random random = new Random();
public static byte[] obfuscateOrUseCache(ChunkData chunkData, Player player) throws IOException {
if(chunkData.primaryBitMask == 0) return null;
if (!Orebfuscator.config.isEnabled() || !Orebfuscator.config.obfuscateForPlayer(player)) {
return null;
}
WorldConfig worldConfig = Orebfuscator.configManager.getWorld(player.getWorld());
if(!worldConfig.isEnabled()) {
return null;
}
byte[] output;
ObfuscatedCachedChunk cache = tryUseCache(chunkData, player);
if(cache != null && cache.data != null) {
//Orebfuscator.log("Read from cache");/*debug*/
output = cache.data;
} else {
// Blocks kept track for ProximityHider
ArrayList<BlockCoord> proximityBlocks = new ArrayList<BlockCoord>();
output = obfuscate(chunkData, player, proximityBlocks);
if (cache != null) {
// If cache is still allowed
if(chunkData.useCache) {
// Save cache
int[] proximityList = new int[proximityBlocks.size() * 3];
int index = 0;
for (int i = 0; i < proximityBlocks.size(); i++) {
BlockCoord b = proximityBlocks.get(i);
if (b != null) {
proximityList[index++] = b.x;
proximityList[index++] = b.y;
proximityList[index++] = b.z;
}
}
cache.write(cache.hash, output, proximityList);
//Orebfuscator.log("Write to cache");/*debug*/
}
cache.free();
}
}
//Orebfuscator.log("Send chunk x = " + chunkData.chunkX + ", z = " + chunkData.chunkZ + " to player " + player.getName());/*debug*/
return output;
}
private static byte[] obfuscate(ChunkData chunkData, Player player, ArrayList<BlockCoord> proximityBlocks) throws IOException {
WorldConfig worldConfig = Orebfuscator.configManager.getWorld(player.getWorld());
ProximityHiderConfig proximityHider = worldConfig.getProximityHiderConfig();
int initialRadius = Orebfuscator.config.getInitialRadius();
// Track of pseudo-randomly assigned randomBlock
int randomIncrement = 0;
int randomIncrement2 = 0;
int randomCave = 0;
int engineMode = Orebfuscator.config.getEngineMode();
int maxChance = worldConfig.getAirGeneratorMaxChance();
int incrementMax = maxChance;
int randomBlocksLength = worldConfig.getRandomBlocks().length;
boolean randomAlternate = false;
int startX = chunkData.chunkX << 4;
int startZ = chunkData.chunkZ << 4;
BlockState blockState = new BlockState();
ChunkMapManager manager = new ChunkMapManager(chunkData);
manager.init();
for(int i = 0; i < manager.getSectionCount(); i++) {
worldConfig.shuffleRandomBlocks();
for(int offsetY = 0; offsetY < 16; offsetY++) {
for(int offsetZ = 0; offsetZ < 16; offsetZ++) {
incrementMax = (maxChance + random(maxChance)) / 2;
for(int offsetX = 0; offsetX < 16; offsetX++) {
int blockData = manager.readNextBlock();
ChunkMapManager.blockDataToState(blockData, blockState);
if (blockState.id < 256) {
int x = startX | offsetX;
int y = manager.getY();
int z = startZ | offsetZ;
// Initialize data
boolean obfuscate = false;
boolean specialObfuscate = false;
// Check if the block should be obfuscated for the default engine modes
if (worldConfig.isObfuscated(blockState.id)) {
if (initialRadius == 0) {
// Do not interfere with PH
if (proximityHider.isEnabled() && proximityHider.isProximityObfuscated(y, blockState.id)) {
if (!areAjacentBlocksTransparent(manager, player.getWorld(), false, x, y, z, 1)) {
obfuscate = true;
}
} else {
// Obfuscate all blocks
obfuscate = true;
}
} else {
// Check if any nearby blocks are transparent
if (!areAjacentBlocksTransparent(manager, player.getWorld(), false, x, y, z, initialRadius)) {
obfuscate = true;
}
}
}
// Check if the block should be obfuscated because of proximity check
if (!obfuscate && proximityHider.isEnabled() && proximityHider.isProximityObfuscated(y, blockState.id)) {
BlockCoord block = new BlockCoord(x, y, z);
if (block != null) {
proximityBlocks.add(block);
}
obfuscate = true;
if (proximityHider.isUseSpecialBlock()) {
specialObfuscate = true;
}
}
// Check if the block is obfuscated
if (obfuscate && canObfuscate(chunkData, x, y, z, blockState)) {
if (specialObfuscate) {
// Proximity hider
blockState.id = proximityHider.getSpecialBlockID();
} else {
if (engineMode == 1) {
// Engine mode 1, replace with stone
blockState.id = worldConfig.getMode1BlockId();
} else if (engineMode == 2) {
// Ending mode 2, replace with random block
if (randomBlocksLength > 1) {
randomIncrement = CalculationsUtil.increment(randomIncrement, randomBlocksLength);
}
blockState.id = worldConfig.getRandomBlock(randomIncrement, randomAlternate);
randomAlternate = !randomAlternate;
}
// Anti texturepack and freecam
if (worldConfig.isAntiTexturePackAndFreecam()) {
// Add random air blocks
randomIncrement2 = random(incrementMax);
if (randomIncrement2 == 0) {
randomCave = 1 + random(3);
}
if (randomCave > 0) {
blockState.id = 0;
randomCave--;
}
}
}
blockState.meta = 0;
}
// Check if the block should be obfuscated because of the darkness
if (!obfuscate && worldConfig.isDarknessHideBlocks() && worldConfig.isDarknessObfuscated(blockState.id)) {
if (!areAjacentBlocksBright(player.getWorld(), x, y, z, 1)) {
// Hide block, setting it to air
blockState.id = 0;
blockState.meta = 0;
}
}
blockData = ChunkMapManager.blockStateToData(blockState);
} else {
blockData = 0;
}
if(offsetY == 0 && offsetZ == 0 && offsetX == 0) {
manager.finalizeOutput();
manager.initOutputPalette();
addBlocksToPalette(manager, worldConfig);
manager.initOutputSection();
}
manager.writeOutputBlock(blockData);
}
}
}
}
manager.finalizeOutput();
byte[] output = manager.createOutput();
ProximityHider.addProximityBlocks(player, chunkData.chunkX, chunkData.chunkZ, proximityBlocks);
//Orebfuscator.log("Create new chunk data for x = " + chunkData.chunkX + ", z = " + chunkData.chunkZ);/*debug*/
return output;
}
private static boolean canObfuscate(ChunkData chunkData, int x, int y, int z, BlockState blockState) {
if(chunkData.blockEntities == null
|| (
blockState.id != DeprecatedMethods.getMaterialId(Material.WALL_SIGN)
&& blockState.id != DeprecatedMethods.getMaterialId(Material.SIGN_POST)
)
)
{
return true;
}
NbtCompound tag = getNbtTag(chunkData, x, y, z);
return tag == null ||
isSignTextEmpty(tag, "Text1")
&& isSignTextEmpty(tag, "Text2")
&& isSignTextEmpty(tag, "Text3")
&& isSignTextEmpty(tag, "Text4");
}
private static boolean isSignTextEmpty(NbtCompound compound, String key) {
NbtBase<?> tag = compound.getValue(key);
if(tag == null || tag.getType() != NbtType.TAG_STRING) {
return true;
}
String json = (String)tag.getValue();
if(json == null || json.isEmpty()) {
return true;
}
String text = Orebfuscator.nms.getTextFromChatComponent(json);
return text == null || text.isEmpty();
}
private static NbtCompound getNbtTag(ChunkData chunkData, int x, int y, int z) {
for(NbtCompound tag : chunkData.blockEntities) {
if(tag != null) {
if(x == tag.getInteger("x")
&& y == tag.getInteger("y")
&& z == tag.getInteger("z")
)
{
return tag;
}
}
}
return null;
}
private static void addBlocksToPalette(ChunkMapManager manager, WorldConfig worldConfig) {
if(!manager.inputHasNonAirBlock()) {
return;
}
for(int id : worldConfig.getPaletteBlocks()) {
int blockData = ChunkMapManager.getBlockDataFromId(id);
manager.addToOutputPalette(blockData);
}
}
private static ObfuscatedCachedChunk tryUseCache(ChunkData chunkData, Player player) {
if (!Orebfuscator.config.isUseCache()) return null;
chunkData.useCache = true;
// Hash the chunk
long hash = CalculationsUtil.Hash(chunkData.data, chunkData.data.length);
// Get cache folder
File cacheFolder = new File(ObfuscatedDataCache.getCacheFolder(), player.getWorld().getName());
// Create cache objects
ObfuscatedCachedChunk cache = new ObfuscatedCachedChunk(cacheFolder, chunkData.chunkX, chunkData.chunkZ);
// Check if hash is consistent
cache.read();
long storedHash = cache.getHash();
if (storedHash == hash && cache.data != null) {
int[] proximityList = cache.proximityList;
ArrayList<BlockCoord> proximityBlocks = new ArrayList<BlockCoord>();
// Decrypt chest list
if (proximityList != null) {
int index = 0;
while (index < proximityList.length) {
int x = proximityList[index++];
int y = proximityList[index++];
int z = proximityList[index++];
BlockCoord b = new BlockCoord(x, y, z);
if(b != null) {
proximityBlocks.add(b);
}
}
}
// ProximityHider add blocks
ProximityHider.addProximityBlocks(player, chunkData.chunkX, chunkData.chunkZ, proximityBlocks);
// Hash match, use the cached data instead and skip calculations
return cache;
}
cache.hash = hash;
cache.data = null;
return cache;
}
public static boolean areAjacentBlocksTransparent(
ChunkMapManager manager,
World world,
boolean checkCurrentBlock,
int x,
int y,
int z,
int countdown
) throws IOException
{
if (y >= world.getMaxHeight() || y < 0)
return true;
if(checkCurrentBlock) {
ChunkData chunkData = manager.getChunkData();
int blockData = manager.get(x, y, z);
int id;
if (blockData < 0) {
id = Orebfuscator.nms.getBlockId(world, x, y, z);
if (id < 0) {
id = 1;
chunkData.useCache = false;
}
} else {
id = ChunkMapManager.getBlockIdFromData(blockData);
}
if (Orebfuscator.config.isBlockTransparent(id)) {
return true;
}
}
if (countdown == 0)
return false;
if (areAjacentBlocksTransparent(manager, world, true, x, y + 1, z, countdown - 1))
return true;
if (areAjacentBlocksTransparent(manager, world, true, x, y - 1, z, countdown - 1))
return true;
if (areAjacentBlocksTransparent(manager, world, true, x + 1, y, z, countdown - 1))
return true;
if (areAjacentBlocksTransparent(manager, world, true, x - 1, y, z, countdown - 1))
return true;
if (areAjacentBlocksTransparent(manager, world, true, x, y, z + 1, countdown - 1))
return true;
if (areAjacentBlocksTransparent(manager, world, true, x, y, z - 1, countdown - 1))
return true;
return false;
}
public static boolean areAjacentBlocksBright(World world, int x, int y, int z, int countdown) {
if(Orebfuscator.nms.getBlockLightLevel(world, x, y, z) > 0) {
return true;
}
if (countdown == 0)
return false;
if (areAjacentBlocksBright(world, x, y + 1, z, countdown - 1))
return true;
if (areAjacentBlocksBright(world, x, y - 1, z, countdown - 1))
return true;
if (areAjacentBlocksBright(world, x + 1, y, z, countdown - 1))
return true;
if (areAjacentBlocksBright(world, x - 1, y, z, countdown - 1))
return true;
if (areAjacentBlocksBright(world, x, y, z + 1, countdown - 1))
return true;
if (areAjacentBlocksBright(world, x, y, z - 1, countdown - 1))
return true;
return false;
}
private static int random(int max) {
return random.nextInt(max);
}
}