package jk_5.nailed.plugins.worldedit;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.blocks.LazyBlock;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.internal.Constants;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.TreeGenerator.TreeType;
import com.sk89q.worldedit.world.AbstractWorld;
import com.sk89q.worldedit.world.biome.BaseBiome;
import com.sk89q.worldedit.world.registry.WorldData;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.inventory.IInventory;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.BlockPos;
import net.minecraft.util.LongHashMap;
import net.minecraft.world.ChunkCoordIntPair;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraft.world.gen.ChunkProviderServer;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* An adapter to Minecraft worlds for WorldEdit.
*/
public class WorldEditWorld extends AbstractWorld {
private static final int UPDATE = 1, NOTIFY = 2, NOTIFY_CLIENT = 4;
private static final Logger logger = Logger.getLogger(WorldEditWorld.class.getCanonicalName());
private final WeakReference<World> worldRef;
/**
* Construct a new world.
*
* @param world the world
*/
WorldEditWorld(World world) {
checkNotNull(world);
this.worldRef = new WeakReference<World>(world);
}
/**
* Get the underlying handle to the world.
*
* @return the world
* @throws WorldEditException thrown if a reference to the world was lost (i.e. world was unloaded)
*/
public World getWorldChecked() throws WorldEditException {
World world = worldRef.get();
if (world != null) {
return world;
} else {
throw new WorldReferenceLostException("The reference to the world was lost (i.e. the world may have been unloaded)");
}
}
/**
* Get the underlying handle to the world.
*
* @return the world
* @throws RuntimeException thrown if a reference to the world was lost (i.e. world was unloaded)
*/
public World getWorld() {
World world = worldRef.get();
if (world != null) {
return world;
} else {
throw new RuntimeException("The reference to the world was lost (i.e. the world may have been unloaded)");
}
}
@Override
public String getName() {
return getWorld().getWorldInfo().getWorldName();
}
@Override
public boolean setBlock(Vector position, BaseBlock block, boolean notifyAndLight) throws WorldEditException {
checkNotNull(position);
checkNotNull(block);
World world = getWorldChecked();
int x = position.getBlockX();
int y = position.getBlockY();
int z = position.getBlockZ();
// First set the block
Chunk chunk = world.getChunkFromChunkCoords(x >> 4, z >> 4);
BlockPos pos = new BlockPos(x, y, z);
IBlockState old = chunk.getBlockState(pos);
IBlockState newState = Block.getBlockById(block.getId()).getStateFromMeta(block.getData());
IBlockState successState = chunk.setBlockState(pos, newState);
boolean successful = successState != null;
// Create the TileEntity
if (successful) {
if (block.hasNbtData()) {
// Kill the old TileEntity
world.removeTileEntity(pos);
NBTTagCompound nativeTag = NBTConverter.toNative(block.getNbtData());
nativeTag.setString("id", block.getNbtId());
TileEntityUtils.setTileEntity(world, position, nativeTag);
}
}
if(notifyAndLight){
if(!successful){
newState = old;
}
world.checkLight(pos);
////
world.markBlockForUpdate(pos);
world.notifyBlockOfStateChange(pos, newState.getBlock());
if(newState.getBlock().hasComparatorInputOverride()){
world.updateComparatorOutputLevel(pos, newState.getBlock());
}
//world.markAndNotifyBlock(pos, chunk, old, newState, UPDATE | NOTIFY);
}
return successful;
}
@Override
public int getBlockLightLevel(Vector position) {
checkNotNull(position);
return getWorld().getLight(new BlockPos(position.getBlockX(), position.getBlockY(), position.getBlockZ()));
}
@Override
public boolean clearContainerBlockContents(Vector position) {
checkNotNull(position);
TileEntity tile = getWorld().getTileEntity(new BlockPos(position.getBlockX(), position.getBlockY(), position.getBlockZ()));
if ((tile instanceof IInventory)) {
IInventory inv = (IInventory) tile;
int size = inv.getSizeInventory();
for (int i = 0; i < size; i++) {
inv.setInventorySlotContents(i, null);
}
return true;
}
return false;
}
@Override
public BaseBiome getBiome(Vector2D position) {
checkNotNull(position);
return new BaseBiome(getWorld().getBiomeGenForCoords(new BlockPos(position.getBlockX(), 0, position.getBlockZ())).biomeID);
}
@Override
public boolean setBiome(Vector2D position, BaseBiome biome) {
checkNotNull(position);
checkNotNull(biome);
Chunk chunk = getWorld().getChunkFromBlockCoords(new BlockPos(position.getBlockX(), 0, position.getBlockZ()));
if ((chunk != null) && (chunk.isLoaded())) {
chunk.getBiomeArray()[((position.getBlockZ() & 0xF) << 4 | position.getBlockX() & 0xF)] = (byte) biome.getId();
return true;
}
return false;
}
@Override
public void dropItem(Vector position, BaseItemStack item) {
checkNotNull(position);
checkNotNull(item);
if (item.getType() == 0) {
return;
}
EntityItem entity = new EntityItem(getWorld(), position.getX(), position.getY(), position.getZ(), NailedWorldEditPlatform.toVanilla(item));
entity.setPickupDelay(10);
getWorld().spawnEntityInWorld(entity);
}
@Override
public boolean regenerate(Region region, EditSession editSession) {
BaseBlock[] history = new BaseBlock[256 * (getMaxY() + 1)];
for (Vector2D chunk : region.getChunks()) {
Vector min = new Vector(chunk.getBlockX() * 16, 0, chunk.getBlockZ() * 16);
for (int x = 0; x < 16; x++) {
for (int y = 0; y < getMaxY() + 1; y++) {
for (int z = 0; z < 16; z++) {
Vector pt = min.add(x, y, z);
int index = y * 16 * 16 + z * 16 + x;
history[index] = editSession.getBlock(pt);
}
}
}
try {
Set<Vector2D> chunks = region.getChunks();
IChunkProvider provider = getWorld().getChunkProvider();
if (!(provider instanceof ChunkProviderServer)) {
return false;
}
ChunkProviderServer chunkServer = (ChunkProviderServer) provider;
Field u;
try {
u = ChunkProviderServer.class.getDeclaredField("field_73248_b"); // chunksToUnload
} catch(NoSuchFieldException e) {
u = ChunkProviderServer.class.getDeclaredField("chunksToUnload");
}
u.setAccessible(true);
Set<?> unloadQueue = (Set<?>) u.get(chunkServer);
Field m;
try {
m = ChunkProviderServer.class.getDeclaredField("field_73244_f"); // loadedChunkHashMap
} catch(NoSuchFieldException e) {
m = ChunkProviderServer.class.getDeclaredField("loadedChunkHashMap");
}
m.setAccessible(true);
LongHashMap loadedMap = (LongHashMap) m.get(chunkServer);
Field lc;
try {
lc = ChunkProviderServer.class.getDeclaredField("field_73245_g"); // loadedChunkHashMap
} catch(NoSuchFieldException e) {
lc = ChunkProviderServer.class.getDeclaredField("loadedChunks");
}
lc.setAccessible(true);
@SuppressWarnings("unchecked") List<Chunk> loaded = (List<Chunk>) lc.get(chunkServer);
Field p;
try {
p = ChunkProviderServer.class.getDeclaredField("field_73246_d"); // currentChunkProvider
} catch(NoSuchFieldException e) {
p = ChunkProviderServer.class.getDeclaredField("currentChunkProvider");
}
p.setAccessible(true);
IChunkProvider chunkProvider = (IChunkProvider) p.get(chunkServer);
for (Vector2D coord : chunks) {
long pos = ChunkCoordIntPair.chunkXZ2Int(coord.getBlockX(), coord.getBlockZ());
Chunk mcChunk;
if (chunkServer.chunkExists(coord.getBlockX(), coord.getBlockZ())) {
mcChunk = chunkServer.loadChunk(coord.getBlockX(), coord.getBlockZ());
mcChunk.onChunkUnload();
}
unloadQueue.remove(pos);
loadedMap.remove(pos);
mcChunk = chunkProvider.provideChunk(coord.getBlockX(), coord.getBlockZ());
loadedMap.add(pos, mcChunk);
loaded.add(mcChunk);
if (mcChunk != null) {
mcChunk.onChunkLoad();
mcChunk.populateChunk(chunkProvider, chunkProvider, coord.getBlockX(), coord.getBlockZ());
}
}
} catch (Throwable t) {
logger.log(Level.WARNING, "Failed to generate chunk", t);
return false;
}
for (int x = 0; x < 16; x++) {
for (int y = 0; y < getMaxY() + 1; y++) {
for (int z = 0; z < 16; z++) {
Vector pt = min.add(x, y, z);
int index = y * 16 * 16 + z * 16 + x;
if (!region.contains(pt))
editSession.smartSetBlock(pt, history[index]);
else {
editSession.rememberChange(pt, history[index], editSession.rawGetBlock(pt));
}
}
}
}
}
return false;
}
@Override
public boolean generateTree(TreeType type, EditSession editSession, Vector position) throws MaxChangedBlocksException {
return false;
}
@Override
public WorldData getWorldData() {
return WorldEditWorldData.instance();
}
@Override
public boolean isValidBlockType(int id) {
return (id == 0) || (net.minecraft.block.Block.getBlockById(id) != null);
}
@Override
public BaseBlock getBlock(Vector position) {
World world = getWorld();
BlockPos pos = new BlockPos(position.getBlockX(), position.getBlockY(), position.getBlockZ());
IBlockState state = world.getBlockState(pos);
TileEntity tile = getWorld().getTileEntity(pos);
if (tile != null) {
return new TileEntityBaseBlock(Block.getIdFromBlock(state.getBlock()), state.getBlock().getMetaFromState(state), tile);
} else {
return new BaseBlock(Block.getIdFromBlock(state.getBlock()), state.getBlock().getMetaFromState(state));
}
}
@Override
public BaseBlock getLazyBlock(Vector position) {
World world = getWorld();
BlockPos pos = new BlockPos(position.getBlockX(), position.getBlockY(), position.getBlockZ());
IBlockState state = world.getBlockState(pos);
return new LazyBlock(Block.getIdFromBlock(state.getBlock()), state.getBlock().getMetaFromState(state), this, position);
}
@Override
public int hashCode() {
return getWorld().hashCode();
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
} else if ((o instanceof WorldEditWorld)) {
WorldEditWorld other = ((WorldEditWorld) o);
World otherWorld = other.worldRef.get();
World thisWorld = worldRef.get();
return otherWorld != null && thisWorld != null && otherWorld.equals(thisWorld);
} else if (o instanceof com.sk89q.worldedit.world.World) {
return ((com.sk89q.worldedit.world.World) o).getName().equals(getName());
} else {
return false;
}
}
@Override
@SuppressWarnings("unchecked")
public List<? extends Entity> getEntities(Region region) {
List<Entity> entities = new ArrayList<Entity>();
World world = getWorld();
List<net.minecraft.entity.Entity> ents = world.loadedEntityList;
for (net.minecraft.entity.Entity entity : ents) {
if (region.contains(new Vector(entity.posX, entity.posY, entity.posZ))) {
entities.add(new WorldEditEntity(entity));
}
}
return entities;
}
@Override
public List<? extends Entity> getEntities() {
List<Entity> entities = new ArrayList<Entity>();
for (Object entity : getWorld().loadedEntityList) {
entities.add(new WorldEditEntity((net.minecraft.entity.Entity) entity));
}
return entities;
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity) {
World world = getWorld();
net.minecraft.entity.Entity createdEntity = EntityList.createEntityByName(entity.getTypeId(), world);
if (createdEntity != null) {
CompoundTag nativeTag = entity.getNbtData();
if (nativeTag != null) {
NBTTagCompound tag = NBTConverter.toNative(entity.getNbtData());
for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
tag.removeTag(name);
}
createdEntity.readFromNBT(tag);
}
createdEntity.setLocationAndAngles(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
world.spawnEntityInWorld(createdEntity);
return new WorldEditEntity(createdEntity);
} else {
return null;
}
}
/**
* Thrown when the reference to the world is lost.
*/
private static class WorldReferenceLostException extends WorldEditException {
private WorldReferenceLostException(String message) {
super(message);
}
}
public static WorldEditWorld wrap(net.minecraft.world.World world) {
return new WorldEditWorld(world);
}
}