/* * This file is part of SpoutcraftPlugin. * * Copyright (c) 2011 SpoutcraftDev <http://spoutcraft.org//> * SpoutcraftPlugin is licensed under the GNU Lesser General Public License. * * SpoutcraftPlugin is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SpoutcraftPlugin 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.getspout.spout.block; import java.io.Serializable; import java.lang.reflect.Field; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import gnu.trove.map.hash.TIntIntHashMap; import net.minecraft.server.v1_6_R3.Chunk; import net.minecraft.server.v1_6_R3.ChunkProviderServer; import net.minecraft.server.v1_6_R3.WorldServer; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.craftbukkit.v1_6_R3.CraftChunk; import org.bukkit.craftbukkit.v1_6_R3.CraftWorld; import org.getspout.spoutapi.Spout; import org.getspout.spoutapi.SpoutManager; import org.getspout.spoutapi.SpoutWorld; import org.getspout.spoutapi.block.SpoutChunk; import org.getspout.spoutapi.material.CustomBlock; import org.getspout.spoutapi.material.MaterialData; public class SpoutCraftChunk extends CraftChunk implements SpoutChunk { protected final ConcurrentHashMap<Integer, Integer> queuedId = new ConcurrentHashMap<Integer, Integer>(); protected final ConcurrentHashMap<Integer, Byte> queuedData = new ConcurrentHashMap<Integer, Byte>(); //TODO Very experimental...may need to be reverted protected static final Set<SpoutCraftChunk> queuedChunks = new ConcurrentSkipListSet<SpoutCraftChunk>(); //protected static final Set<SpoutCraftChunk> queuedChunks = Collections.newSetFromMap(new ConcurrentHashMap<SpoutCraftChunk, Boolean>()); public final TIntIntHashMap powerOverrides = new TIntIntHashMap(); transient private final int worldHeight; transient private final int worldHeightMinusOne; transient private final int xBitShifts; transient private final int zBitShifts; public SpoutCraftChunk(Chunk chunk) { super(chunk); SpoutWorld world = Spout.getServer().getWorld(getWorld().getUID()); this.worldHeight = world != null ? world.getMaxHeight() : 256; this.xBitShifts = world != null ? world.getXBitShifts() : 12; this.zBitShifts = world != null ? world.getZBitShifts() : 8; worldHeightMinusOne = worldHeight - 1; } @Override public Block getBlock(int x, int y, int z) { return new SpoutCraftBlock(this, (getX() << 4) | (x & 0xF), y & worldHeightMinusOne, (getZ() << 4) | (z & 0xF)); } private Block getBlockFromPos(int pos) { int x = (pos >> xBitShifts) & 0xF; int y = (pos) & worldHeightMinusOne; int z = (pos >> zBitShifts) & 0xF; return getBlock(x, y, z); } public void onTick() { //Apply queued ids on blocks final Iterator<Entry<Integer, Integer>> i = queuedId.entrySet().iterator(); while (i.hasNext()) { final Entry<Integer, Integer> entry = i.next(); Block block = getBlockFromPos(entry.getKey()); block.setTypeId(entry.getValue()); i.remove(); } //Apply queued data on blocks if (queuedId.isEmpty()) { final Iterator<Entry<Integer, Byte>> j = queuedData.entrySet().iterator(); while (j.hasNext()) { //If another thread adds to the id queue, we need to halt and let the next tick apply it //TODO Even possible, needed? Doesn't add much overhead to check this... if (!queuedId.isEmpty()) { break; } final Entry<Integer, Byte> entry = j.next(); final Block block = getBlockFromPos(entry.getKey()); block.setData(entry.getValue()); j.remove(); } } } protected void onReset() { // TODO finalize queuing } public static void updateTicks() { Iterator<SpoutCraftChunk> i = SpoutCraftChunk.queuedChunks.iterator(); while (i.hasNext()) { SpoutCraftChunk chunk = i.next(); chunk.onTick(); i.remove(); } } public static void replaceAllBukkitChunks() { replaceAllBukkitChunks(false); } public static void resetAllBukkitChunks() { replaceAllBukkitChunks(true); } private static void replaceAllBukkitChunks(boolean reset) { List<World> worlds = Bukkit.getServer().getWorlds(); for (World world : worlds) { try { CraftWorld cw = (CraftWorld) world; Field worldServer = CraftWorld.class.getDeclaredField("world"); worldServer.setAccessible(true); ChunkProviderServer cps = ((WorldServer) worldServer.get(cw)).chunkProviderServer; for (Chunk c : cps.chunks.values()) { if (reset) { if (c.bukkitChunk instanceof SpoutCraftChunk) { ((SpoutCraftChunk) c.bukkitChunk).onReset(); } resetBukkitChunk(c.bukkitChunk); } else { replaceBukkitChunk(c.bukkitChunk); } } } catch (Exception e) { e.printStackTrace(); } } } public static SpoutCraftChunk getChunkSafe(org.bukkit.Chunk chunk) { if (chunk == null) { return null; } if (replaceBukkitChunk(chunk)) { return (SpoutCraftChunk) ((CraftChunk)chunk).getHandle().bukkitChunk; } return (SpoutCraftChunk) chunk; } public static boolean replaceBukkitChunk(org.bukkit.Chunk chunk) { CraftChunk handle = (CraftChunk) ((CraftChunk) chunk).getHandle().bukkitChunk; if (handle != null) { boolean replace = false; if (handle.getX() != chunk.getX()) { replace = true; } if (handle.getZ() != chunk.getZ()) { replace = true; } if (handle.getClass().hashCode() != SpoutCraftChunk.class.hashCode()) { replace = true; } org.bukkit.Chunk loopbackChunk = ((CraftChunk)chunk).getHandle().bukkitChunk; if (loopbackChunk != chunk) { replace = true; } if (replace) { ((CraftChunk) chunk).getHandle().bukkitChunk = new SpoutCraftChunk(((CraftChunk) chunk).getHandle()); } return true; } return false; } public static void resetBukkitChunk(org.bukkit.Chunk chunk) { ((CraftChunk) chunk).getHandle().bukkitChunk = new CraftChunk(((CraftChunk) chunk).getHandle()); } @Override public Serializable setData(String id, Serializable data) { return SpoutManager.getChunkDataManager().setChunkData(id, getWorld(), getX(), getZ(), data); } @Override public Serializable getData(String id) { return SpoutManager.getChunkDataManager().getChunkData(id, getWorld(), getX(), getZ()); } @Override public Serializable removeData(String id) { return SpoutManager.getChunkDataManager().removeChunkData(id, getWorld(), getX(), getZ()); } @Override public short[] getCustomBlockIds() { return SpoutManager.getChunkDataManager().getCustomBlockIds(getWorld(), getX(), getZ()); } @Override public void setCustomBlockIds(short[] ids) { SpoutManager.getChunkDataManager().setCustomBlockIds(getWorld(), getX(), getZ(), ids); } @Override public short getCustomBlockId(int x, int y, int z) { short[] ids = getCustomBlockIds(); if (ids == null) { return 0; } int index = ((x & 0xF) << xBitShifts) | ((z & 0xF) << zBitShifts) | (y & worldHeightMinusOne); return ids[index]; } @Override public short setCustomBlockId(int x, int y, int z, short id) { short[] ids = getCustomBlockIds(); if (ids == null) { ids = new short[16*16*worldHeight]; setCustomBlockIds(ids); } int index = ((x & 0xF) << xBitShifts) | ((z & 0xF) << zBitShifts) | (y & worldHeightMinusOne); short old = ids[index]; ids[index] = id; return old; } @Override public CustomBlock setCustomBlock(int x, int y, int z, CustomBlock block) { if (block == null) { throw new NullPointerException("Custom Block can not be null!"); } short old = setCustomBlockId(x, y, z, (short) block.getCustomId()); return MaterialData.getCustomBlock(old); } @Override public byte[] getCustomBlockData() { return SpoutManager.getChunkDataManager().getCustomBlockData(getWorld(), getX(), getZ()); } @Override public void setCustomBlockData(byte[] data) { SpoutManager.getChunkDataManager().setCustomBlockData(getWorld(), getX(), getZ(), data); } @Override public byte getCustomBlockData(int x, int y, int z) { byte[] data = getCustomBlockData(); if (data == null) { return 0; } int index = ((x & 0xF) << xBitShifts) | ((z & 0xF) << zBitShifts) | (y & worldHeightMinusOne); return data[index]; } @Override public byte setCustomBlockData(int x, int y, int z, byte data) { byte[] dats = getCustomBlockData(); if (dats == null) { dats = new byte[16*16*worldHeight]; setCustomBlockData(dats); } int index = ((x & 0xF) << xBitShifts) | ((z & 0xF) << zBitShifts) | (y & worldHeightMinusOne); byte old = dats[index]; dats[index] = data; return old; } @Override public CustomBlock setCustomBlock(int x, int y, int z, CustomBlock block, byte data) { if (block == null) { throw new NullPointerException("Custom Block can not be null!"); } short old = setCustomBlockId(x, y, z, (short) block.getCustomId()); setCustomBlockData(x, y, z, data); return MaterialData.getCustomBlock(old); } }