/*
* 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.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import com.lishid.orebfuscator.DeprecatedMethods;
import com.lishid.orebfuscator.Orebfuscator;
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 ProximityHider extends Thread implements Runnable {
private static final Map<Player, ProximityHiderPlayer> proximityHiderTracker = new WeakHashMap<Player, ProximityHiderPlayer>();
private static final Map<Player, Location> playersToCheck = new HashMap<Player, Location>();
private static final HashSet<Player> playersToReload = new HashSet<Player>();
private static ProximityHider thread = new ProximityHider();
private Map<Player, ProximityHiderPlayer> proximityHiderTrackerLocal = new WeakHashMap<Player, ProximityHiderPlayer>();
private long lastExecute = System.currentTimeMillis();
private AtomicBoolean kill = new AtomicBoolean(false);
private static boolean running = false;
public static void Load() {
running = true;
if (thread == null || thread.isInterrupted() || !thread.isAlive()) {
thread = new ProximityHider();
thread.setName("Orebfuscator ProximityHider Thread");
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
}
public static void terminate() {
if (thread != null) {
thread.kill.set(true);
}
}
public void run() {
while (!this.isInterrupted() && !kill.get()) {
try {
// Wait until necessary
long timeWait = lastExecute + Orebfuscator.config.getProximityHiderRate() - System.currentTimeMillis();
lastExecute = System.currentTimeMillis();
if (timeWait > 0) {
Thread.sleep(timeWait);
}
if (!Orebfuscator.config.isProximityHiderEnabled()) {
running = false;
return;
}
HashMap<Player, Location> checkPlayers = new HashMap<Player, Location>();
synchronized (playersToCheck) {
checkPlayers.putAll(playersToCheck);
playersToCheck.clear();
}
for (Player p : checkPlayers.keySet()) {
if (p == null) {
continue;
}
synchronized (proximityHiderTracker) {
if (!proximityHiderTracker.containsKey(p)) {
continue;
}
}
Location oldLocation = checkPlayers.get(p);
if(oldLocation != null) {
Location curLocation = p.getLocation();
// Player didn't actually move
if (curLocation.getBlockX() == oldLocation.getBlockX() && curLocation.getBlockY() == oldLocation.getBlockY() && curLocation.getBlockZ() == oldLocation.getBlockZ()) {
continue;
}
}
ProximityHiderPlayer localPlayerInfo = proximityHiderTrackerLocal.get(p);
if(localPlayerInfo == null) {
proximityHiderTrackerLocal.put(p, localPlayerInfo = new ProximityHiderPlayer(p.getWorld()));
}
synchronized (proximityHiderTracker) {
ProximityHiderPlayer playerInfo = proximityHiderTracker.get(p);
if (playerInfo != null) {
if(!localPlayerInfo.getWorld().equals(playerInfo.getWorld())) {
localPlayerInfo.setWorld(playerInfo.getWorld());
localPlayerInfo.clearChunks();
}
localPlayerInfo.copyChunks(playerInfo);
playerInfo.clearChunks();
}
}
if(localPlayerInfo.getWorld() == null || p.getWorld() == null || !p.getWorld().equals(localPlayerInfo.getWorld())) {
localPlayerInfo.clearChunks();
continue;
}
WorldConfig worldConfig = Orebfuscator.configManager.getWorld(p.getWorld());
ProximityHiderConfig proximityHider = worldConfig.getProximityHiderConfig();
int checkRadius = proximityHider.getDistance() >> 4;
if((proximityHider.getDistance() & 0xf) != 0) {
checkRadius++;
}
int distanceSquared = proximityHider.getDistanceSquared();
ArrayList<BlockCoord> removedBlocks = new ArrayList<BlockCoord>();
Location playerLocation = p.getLocation();
int minChunkX = (playerLocation.getBlockX() >> 4) - checkRadius;
int maxChunkX = minChunkX + (checkRadius << 1);
int minChunkZ = (playerLocation.getBlockZ() >> 4) - checkRadius;
int maxChunkZ = minChunkZ + (checkRadius << 1);
for(int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) {
for(int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) {
ArrayList<BlockCoord> blocks = localPlayerInfo.getBlocks(chunkX, chunkZ);
if(blocks == null) continue;
removedBlocks.clear();
for (BlockCoord b : blocks) {
if (b == null) {
removedBlocks.add(b);
continue;
}
Location blockLocation = new Location(localPlayerInfo.getWorld(), b.x, b.y, b.z);
if (proximityHider.isObfuscateAboveY() || playerLocation.distanceSquared(blockLocation) < distanceSquared) {
removedBlocks.add(b);
BlockState blockState = Orebfuscator.nms.getBlockState(localPlayerInfo.getWorld(), b.x, b.y, b.z);
if (blockState != null) {
DeprecatedMethods.sendBlockChange(p, blockLocation, blockState);
final BlockCoord block = b;
final Player player = p;
Orebfuscator.instance.runTask(new Runnable() {
public void run() {
Orebfuscator.nms.updateBlockTileEntity(block, player);
}
});
}
}
}
if(blocks.size() == removedBlocks.size()) {
localPlayerInfo.removeChunk(chunkX, chunkZ);
} else {
blocks.removeAll(removedBlocks);
}
}
}
}
} catch (Exception e) {
Orebfuscator.log(e);
}
}
running = false;
}
private static void restart() {
synchronized (thread) {
if (thread.isInterrupted() || !thread.isAlive())
running = false;
if (!running && Orebfuscator.config.isProximityHiderEnabled()) {
// Load ProximityHider
ProximityHider.Load();
}
}
}
public static void addProximityBlocks(Player player, int chunkX, int chunkZ, ArrayList<BlockCoord> blocks) {
ProximityHiderConfig proximityHider = Orebfuscator.configManager.getWorld(player.getWorld()).getProximityHiderConfig();
if (!proximityHider.isEnabled()) return;
restart();
synchronized (proximityHiderTracker) {
ProximityHiderPlayer playerInfo = proximityHiderTracker.get(player);
World world = player.getWorld();
if (playerInfo == null) {
proximityHiderTracker.put(player, playerInfo = new ProximityHiderPlayer(world));
} else if(!playerInfo.getWorld().equals(world)) {
playerInfo.setWorld(world);
playerInfo.clearChunks();
}
if(blocks.size() > 0) {
playerInfo.putBlocks(chunkX, chunkZ, blocks);
} else {
playerInfo.removeChunk(chunkX, chunkZ);
}
}
boolean isPlayerToReload;
synchronized (playersToReload) {
isPlayerToReload = playersToReload.remove(player);
}
if(isPlayerToReload) {
addPlayerToCheck(player, null);
}
}
public static void clearPlayer(Player player) {
synchronized (proximityHiderTracker) {
proximityHiderTracker.remove(player);
}
}
public static void clearBlocksForOldWorld(Player player) {
synchronized (proximityHiderTracker) {
ProximityHiderPlayer playerInfo = proximityHiderTracker.get(player);
if(playerInfo != null) {
World world = player.getWorld();
if(!playerInfo.getWorld().equals(world)) {
playerInfo.setWorld(world);
playerInfo.clearChunks();
}
}
}
}
public static void addPlayerToCheck(Player player, Location location) {
synchronized (playersToCheck) {
if (!playersToCheck.containsKey(player)) {
playersToCheck.put(player, location);
}
}
}
public static void addPlayersToReload(HashSet<Player> players) {
if (!Orebfuscator.config.isProximityHiderEnabled()) return;
synchronized (playersToReload) {
playersToReload.addAll(players);
}
}
}