package pneumaticCraft.common.semiblock;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraft.block.Block;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.world.ChunkPosition;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.world.ChunkDataEvent;
import net.minecraftforge.event.world.ChunkEvent;
import pneumaticCraft.PneumaticCraft;
import pneumaticCraft.common.network.NetworkHandler;
import pneumaticCraft.common.network.PacketDescription;
import pneumaticCraft.common.network.PacketSetSemiBlock;
import pneumaticCraft.common.util.PneumaticCraftUtils;
import pneumaticCraft.lib.Log;
import com.google.common.collect.HashBiMap;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import cpw.mods.fml.common.registry.GameRegistry;
public class SemiBlockManager{
private final Map<Chunk, Map<ChunkPosition, ISemiBlock>> semiBlocks = new HashMap<Chunk, Map<ChunkPosition, ISemiBlock>>();
private final List<ISemiBlock> addingBlocks = new ArrayList<ISemiBlock>();
private final Map<Chunk, Set<EntityPlayerMP>> syncList = new HashMap<Chunk, Set<EntityPlayerMP>>();
private final Set<Chunk> chunksMarkedForRemoval = new HashSet<Chunk>();
public static final int SYNC_DISTANCE = 64;
private static final HashBiMap<String, Class<? extends ISemiBlock>> registeredTypes = HashBiMap.create();
private static final HashBiMap<Class<? extends ISemiBlock>, Item> semiBlockToItems = HashBiMap.create();
private static final SemiBlockManager INSTANCE = new SemiBlockManager();
private static final SemiBlockManager CLIENT_INSTANCE = new SemiBlockManager();
public static SemiBlockManager getServerInstance(){
return INSTANCE;
}
public static SemiBlockManager getClientOldInstance(){
return CLIENT_INSTANCE;
}
public static SemiBlockManager getInstance(World world){
return world.isRemote ? CLIENT_INSTANCE : INSTANCE;
}
public static Item registerSemiBlock(String key, Class<? extends ISemiBlock> semiBlock, boolean addItem){
if(registeredTypes.containsKey(key)) throw new IllegalArgumentException("Duplicate registration key: " + key);
registeredTypes.put(key, semiBlock);
if(addItem) {
ItemSemiBlockBase item = new ItemSemiBlockBase(key);
GameRegistry.registerItem(item, key);
PneumaticCraft.proxy.registerSemiBlockRenderer(item);
registerSemiBlockToItemMapping(semiBlock, item);
return item;
} else {
return null;
}
}
public static void registerSemiBlockToItemMapping(Class<? extends ISemiBlock> semiBlock, Item item){
semiBlockToItems.put(semiBlock, item);
}
public static Item getItemForSemiBlock(ISemiBlock semiBlock){
return getItemForSemiBlock(semiBlock.getClass());
}
public static Item getItemForSemiBlock(Class<? extends ISemiBlock> semiBlock){
return semiBlockToItems.get(semiBlock);
}
public static Class<? extends ISemiBlock> getSemiBlockForItem(Item item){
return semiBlockToItems.inverse().get(item);
}
public static String getKeyForSemiBlock(ISemiBlock semiBlock){
return getKeyForSemiBlock(semiBlock.getClass());
}
public static String getKeyForSemiBlock(Class<? extends ISemiBlock> semiBlock){
return registeredTypes.inverse().get(semiBlock);
}
public static ISemiBlock getSemiBlockForKey(String key){
try {
Class<? extends ISemiBlock> clazz = registeredTypes.get(key);
if(clazz != null) {
return clazz.newInstance();
} else {
Log.warning("Semi Block with id \"" + key + "\" isn't registered!");
return null;
}
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
@SubscribeEvent
public void onChunkUnLoad(ChunkEvent.Unload event){
if(!event.world.isRemote) {
chunksMarkedForRemoval.add(event.getChunk());
}
}
@SubscribeEvent
public void onChunkSave(ChunkDataEvent.Save event){
Map<ChunkPosition, ISemiBlock> map = semiBlocks.get(event.getChunk());
if(map != null && map.size() > 0) {
NBTTagList tagList = new NBTTagList();
for(Map.Entry<ChunkPosition, ISemiBlock> entry : map.entrySet()) {
NBTTagCompound t = new NBTTagCompound();
entry.getValue().writeToNBT(t);
t.setInteger("x", entry.getKey().chunkPosX);
t.setInteger("y", entry.getKey().chunkPosY);
t.setInteger("z", entry.getKey().chunkPosZ);
t.setString("type", getKeyForSemiBlock(entry.getValue()));
tagList.appendTag(t);
}
event.getData().setTag("SemiBlocks", tagList);
}
}
@SubscribeEvent
public void onChunkLoad(ChunkDataEvent.Load event){
try {
if(!event.world.isRemote) {
if(event.getData().hasKey("SemiBlocks")) {
Map<ChunkPosition, ISemiBlock> map = getOrCreateMap(event.getChunk());
map.clear();
NBTTagList tagList = event.getData().getTagList("SemiBlocks", 10);
for(int i = 0; i < tagList.tagCount(); i++) {
NBTTagCompound t = tagList.getCompoundTagAt(i);
ISemiBlock semiBlock = getSemiBlockForKey(t.getString("type"));
if(semiBlock != null) {
semiBlock.readFromNBT(t);
setSemiBlock(event.world, t.getInteger("x"), t.getInteger("y"), t.getInteger("z"), semiBlock, event.getChunk());
}
}
}
}
} catch(Throwable e) {
e.printStackTrace();
}
}
@SubscribeEvent
public void onServerTick(TickEvent.ServerTickEvent event){
for(ISemiBlock semiBlock : addingBlocks) {
Chunk chunk = semiBlock.getWorld().getChunkFromBlockCoords(semiBlock.getPos().chunkPosX, semiBlock.getPos().chunkPosZ);
getOrCreateMap(chunk).put(semiBlock.getPos(), semiBlock);
chunk.setChunkModified();
for(EntityPlayerMP player : syncList.get(chunk)) {
NetworkHandler.sendTo(new PacketSetSemiBlock(semiBlock), player);
PacketDescription descPacket = semiBlock.getDescriptionPacket();
if(descPacket != null) NetworkHandler.sendTo(descPacket, player);
}
}
addingBlocks.clear();
for(Chunk removingChunk : chunksMarkedForRemoval) {
if(!removingChunk.isChunkLoaded) {
semiBlocks.remove(removingChunk);
syncList.remove(removingChunk);
}
}
chunksMarkedForRemoval.clear();
for(Map<ChunkPosition, ISemiBlock> map : semiBlocks.values()) {
for(ISemiBlock semiBlock : map.values()) {
if(!semiBlock.isInvalid()) semiBlock.update();
}
Iterator<ISemiBlock> iterator = map.values().iterator();
while(iterator.hasNext()) {
if(iterator.next().isInvalid()) {
iterator.remove();
}
}
}
}
@SubscribeEvent
public void onClientTick(TickEvent.ClientTickEvent event){
if(this == getServerInstance()) getClientOldInstance().onClientTick(event);
else {
EntityPlayer player = PneumaticCraft.proxy.getPlayer();
if(player != null) {
for(ISemiBlock semiBlock : addingBlocks) {
Chunk chunk = semiBlock.getWorld().getChunkFromBlockCoords(semiBlock.getPos().chunkPosX, semiBlock.getPos().chunkPosZ);
getOrCreateMap(chunk).put(semiBlock.getPos(), semiBlock);
}
addingBlocks.clear();
Iterator<Map.Entry<Chunk, Map<ChunkPosition, ISemiBlock>>> iterator = semiBlocks.entrySet().iterator();
while(iterator.hasNext()) {
Map.Entry<Chunk, Map<ChunkPosition, ISemiBlock>> entry = iterator.next();
if(PneumaticCraftUtils.distBetween(player.posX, 0, player.posZ, entry.getKey().xPosition * 16 - 8, 0, entry.getKey().zPosition * 16 - 8) > SYNC_DISTANCE + 10) {
iterator.remove();
} else {
for(ISemiBlock semiBlock : entry.getValue().values()) {
if(!semiBlock.isInvalid()) semiBlock.update();
}
Iterator<ISemiBlock> it = entry.getValue().values().iterator();
while(it.hasNext()) {
if(it.next().isInvalid()) {
it.remove();
}
}
}
}
} else {
semiBlocks.clear();
}
}
}
@SubscribeEvent
public void onWorldTick(TickEvent.WorldTickEvent event){
if(!event.world.isRemote) {
syncWithPlayers(event.world);
}
}
private void syncWithPlayers(World world){
List<EntityPlayerMP> players = world.playerEntities;
for(Map.Entry<Chunk, Set<EntityPlayerMP>> entry : syncList.entrySet()) {
Chunk chunk = entry.getKey();
Set<EntityPlayerMP> syncedPlayers = entry.getValue();
int chunkX = chunk.xPosition * 16 - 8;
int chunkZ = chunk.zPosition * 16 - 8;
for(EntityPlayerMP player : players) {
if(chunk.worldObj == world) {
double dist = PneumaticCraftUtils.distBetween(player.posX, 0, player.posZ, chunkX, 0, chunkZ);
if(dist < SYNC_DISTANCE) {
if(syncedPlayers.add(player)) {
for(ISemiBlock semiBlock : semiBlocks.get(chunk).values()) {
if(!semiBlock.isInvalid()) {
NetworkHandler.sendTo(new PacketSetSemiBlock(semiBlock), player);
PacketDescription descPacket = semiBlock.getDescriptionPacket();
if(descPacket != null) NetworkHandler.sendTo(descPacket, player);
}
}
}
} else if(dist > SYNC_DISTANCE + 5) {
syncedPlayers.remove(player);
}
} else {
syncedPlayers.remove(player);
}
}
}
}
@SubscribeEvent
public void onInteraction(PlayerInteractEvent event){
if(!event.world.isRemote && event.action == PlayerInteractEvent.Action.RIGHT_CLICK_BLOCK) {
ItemStack curItem = event.entityPlayer.getCurrentEquippedItem();
if(curItem != null && curItem.getItem() instanceof ISemiBlockItem) {
if(getSemiBlock(event.world, event.x, event.y, event.z) != null) {
if(event.entityPlayer.capabilities.isCreativeMode) {
setSemiBlock(event.world, event.x, event.y, event.z, null);
} else {
breakSemiBlock(event.world, event.x, event.y, event.z, event.entityPlayer);
}
event.setCanceled(true);
} else {
ISemiBlock newBlock = ((ISemiBlockItem)curItem.getItem()).getSemiBlock(event.world, event.x, event.y, event.z, curItem);
newBlock.initialize(event.world, new ChunkPosition(event.x, event.y, event.z));
if(newBlock.canPlace()) {
setSemiBlock(event.world, event.x, event.y, event.z, newBlock);
newBlock.onPlaced(event.entityPlayer, curItem);
event.world.playSoundEffect(event.x + 0.5, event.y + 0.5, event.z + 0.5, Block.soundTypeGlass.func_150496_b(), (Block.soundTypeGlass.getVolume() + 1.0F) / 2.0F, Block.soundTypeGlass.getPitch() * 0.8F);
if(!event.entityPlayer.capabilities.isCreativeMode) {
curItem.stackSize--;
if(curItem.stackSize <= 0) event.entityPlayer.setCurrentItemOrArmor(0, null);
}
event.setCanceled(true);
}
}
}
}
}
private Map<ChunkPosition, ISemiBlock> getOrCreateMap(Chunk chunk){
Map<ChunkPosition, ISemiBlock> map = semiBlocks.get(chunk);
if(map == null) {
map = new HashMap<ChunkPosition, ISemiBlock>();
semiBlocks.put(chunk, map);
syncList.put(chunk, new HashSet<EntityPlayerMP>());
}
return map;
}
public void breakSemiBlock(World world, int x, int y, int z){
breakSemiBlock(world, x, y, z, null);
}
public void breakSemiBlock(World world, int x, int y, int z, EntityPlayer player){
ISemiBlock semiBlock = getSemiBlock(world, x, y, z);
if(semiBlock != null) {
List<ItemStack> drops = new ArrayList<ItemStack>();
semiBlock.addDrops(drops);
for(ItemStack stack : drops) {
EntityItem item = new EntityItem(world, x + 0.5, y + 0.5, z + 0.5, stack);
world.spawnEntityInWorld(item);
if(player != null) item.onCollideWithPlayer(player);
}
setSemiBlock(world, x, y, z, null);
}
}
public void setSemiBlock(World world, int x, int y, int z, ISemiBlock semiBlock){
setSemiBlock(world, x, y, z, semiBlock, world.getChunkFromBlockCoords(x, z));
}
private void setSemiBlock(World world, int x, int y, int z, ISemiBlock semiBlock, Chunk chunk){
if(semiBlock != null && !registeredTypes.containsValue(semiBlock.getClass())) throw new IllegalStateException("ISemiBlock \"" + semiBlock + "\" was not registered!");
ChunkPosition pos = new ChunkPosition(x, y, z);
if(semiBlock != null) {
semiBlock.initialize(world, pos);
addingBlocks.add(semiBlock);
} else {
ISemiBlock removedBlock = getOrCreateMap(chunk).get(pos);
if(removedBlock != null) {
removedBlock.invalidate();
for(EntityPlayerMP player : syncList.get(chunk)) {
NetworkHandler.sendTo(new PacketSetSemiBlock(pos, null), player);
}
}
}
chunk.setChunkModified();
}
public ISemiBlock getSemiBlock(World world, int x, int y, int z){
for(ISemiBlock semiBlock : addingBlocks) {
if(semiBlock.getWorld() == world && semiBlock.getPos().equals(new ChunkPosition(x, y, z))) return semiBlock;
}
Chunk chunk = world.getChunkFromBlockCoords(x, z);
Map<ChunkPosition, ISemiBlock> map = semiBlocks.get(chunk);
if(map != null) {
return map.get(new ChunkPosition(x, y, z));
} else {
return null;
}
}
public Map<Chunk, Map<ChunkPosition, ISemiBlock>> getSemiBlocks(){
return semiBlocks;
}
}