/** * This class was created by <Vazkii>. It's distributed as * part of the Botania Mod. Get the Source Code in github: * https://github.com/Vazkii/Botania * * Botania is Open Source and distributed under the * Botania License: http://botaniamod.net/license.php * * File Created @ [Feb 20, 2014, 4:57:36 PM (GMT)] */ package vazkii.botania.common.block; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.UUID; import javax.annotation.Nonnull; import gnu.trove.map.hash.TObjectIntHashMap; import net.minecraft.block.Block; import net.minecraft.block.BlockPistonExtension; import net.minecraft.block.BlockPistonMoving; import net.minecraft.block.SoundType; import net.minecraft.block.material.EnumPushReaction; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagString; import net.minecraft.server.MinecraftServer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.SoundCategory; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraft.world.WorldSavedData; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; import net.minecraftforge.fml.common.gameevent.TickEvent.Type; import vazkii.botania.api.lexicon.ILexiconable; import vazkii.botania.api.lexicon.LexiconEntry; import vazkii.botania.api.sound.BotaniaSoundEvents; import vazkii.botania.api.wand.IWandable; import vazkii.botania.common.lexicon.LexiconData; import vazkii.botania.common.lib.LibBlockNames; public class BlockPistonRelay extends BlockMod implements IWandable, ILexiconable { // Currently active binding attempts public final Map<UUID, DimWithPos> playerPositions = new HashMap<>(); // Bindings public final Map<DimWithPos, DimWithPos> mappedPositions = new HashMap<>(); private final Set<DimWithPos> removeQueue = new HashSet<>(); private final Set<DimWithPos> checkedCoords = new HashSet<>(); private final TObjectIntHashMap<DimWithPos> coordsToCheck = new TObjectIntHashMap<>(10, 0.5F, -1); public BlockPistonRelay() { super(Material.GOURD, LibBlockNames.PISTON_RELAY); setHardness(2F); setResistance(10F); setSoundType(SoundType.METAL); MinecraftForge.EVENT_BUS.register(this); } @Override public int quantityDropped(IBlockState state, int fortune, @Nonnull Random random) { return 0; } @Override public boolean isOpaqueCube(IBlockState state) { return false; } @Override public void breakBlock(@Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState state) { if(!world.isRemote) mapCoords(world.provider.getDimension(), pos, 2); } private void mapCoords(int world, BlockPos pos, int time) { coordsToCheck.put(new DimWithPos(world, pos), time); } private void decrCoords(DimWithPos key) { int time = getTimeInCoords(key); if(time <= 0) removeQueue.add(key); else coordsToCheck.adjustValue(key, -1); } private int getTimeInCoords(DimWithPos key) { return coordsToCheck.get(key); } private Block getBlockAt(DimWithPos key) { return getStateAt(key).getBlock(); } private IBlockState getStateAt(DimWithPos key) { MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); if(server == null) return Blocks.AIR.getDefaultState(); return server.worldServerForDimension(key.dim).getBlockState(key.blockPos); } @Override public boolean onUsedByWand(EntityPlayer player, ItemStack stack, World world, BlockPos pos, EnumFacing side) { if(player == null || world.isRemote) return false; if(!player.isSneaking()) { playerPositions.put(player.getUniqueID(), new DimWithPos(world.provider.getDimension(), pos)); world.playSound(null, pos, BotaniaSoundEvents.ding, SoundCategory.BLOCKS, 0.5F, 1F); } else { spawnAsEntity(world, pos, new ItemStack(this)); world.setBlockToAir(pos); world.playEvent(2001, pos, Block.getStateId(getDefaultState())); } return true; } @SubscribeEvent public void onWorldLoad(WorldEvent.Load event) { if(!event.getWorld().isRemote) WorldData.get(event.getWorld()); } @SubscribeEvent public void onWorldUnload(WorldEvent.Unload event) { if(!event.getWorld().isRemote) WorldData.get(event.getWorld()).markDirty(); } public static class WorldData extends WorldSavedData { private static final String ID = "PistonRelayPairs"; public WorldData(String id) { super(id); } @Override public void readFromNBT(@Nonnull NBTTagCompound nbttagcompound) { ((BlockPistonRelay) ModBlocks.pistonRelay).mappedPositions.clear(); Collection<String> tags = nbttagcompound.getKeySet(); for(String key : tags) { NBTBase tag = nbttagcompound.getTag(key); if(tag instanceof NBTTagString) { String value = ((NBTTagString) tag).getString(); ((BlockPistonRelay) ModBlocks.pistonRelay).mappedPositions.put(DimWithPos.fromString(key), DimWithPos.fromString(value)); } } } @Nonnull @Override public NBTTagCompound writeToNBT(@Nonnull NBTTagCompound nbttagcompound) { for(DimWithPos s : ((BlockPistonRelay) ModBlocks.pistonRelay).mappedPositions.keySet()) nbttagcompound.setString(s.toString(), ((BlockPistonRelay) ModBlocks.pistonRelay).mappedPositions.get(s).toString()); return nbttagcompound; } public static WorldData get(World world) { if(world.getMapStorage() == null) return null; WorldData data = (WorldData) world.getMapStorage().getOrLoadData(WorldData.class, ID); if (data == null) { data = new WorldData(ID); data.markDirty(); world.getMapStorage().setData(ID, data); } return data; } } @SubscribeEvent public void tickEnd(TickEvent event) { if(event.type == Type.SERVER && event.phase == Phase.END) { for(DimWithPos s : coordsToCheck.keySet()) { decrCoords(s); if(checkedCoords.contains(s)) continue; Block block = getBlockAt(s); if(block == Blocks.PISTON_EXTENSION) { IBlockState state = getStateAt(s); boolean sticky = BlockPistonExtension.EnumPistonType.STICKY == state.getValue(BlockPistonMoving.TYPE); EnumFacing dir = state.getValue(BlockPistonMoving.FACING); MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); if(server != null && getTimeInCoords(s) == 0) { DimWithPos newPos; { int worldId = s.dim, x = s.blockPos.getX(), y = s.blockPos.getY(), z = s.blockPos.getZ(); BlockPos pos = s.blockPos; World world = server.worldServerForDimension(worldId); if(world.isAirBlock(pos.offset(dir))) world.setBlockState(pos.offset(dir), ModBlocks.pistonRelay.getDefaultState()); else if(!world.isRemote) { ItemStack stack = new ItemStack(ModBlocks.pistonRelay); world.spawnEntity(new EntityItem(world, x + dir.getFrontOffsetX(), y + dir.getFrontOffsetY(), z + dir.getFrontOffsetZ(), stack)); } checkedCoords.add(s); newPos = new DimWithPos(world.provider.getDimension(), pos.offset(dir)); } if(mappedPositions.containsKey(s)) { DimWithPos pos = mappedPositions.get(s); int worldId = pos.dim; BlockPos pos2 = pos.blockPos; World world = server.worldServerForDimension(worldId); IBlockState srcState = world.getBlockState(pos2); TileEntity tile = world.getTileEntity(pos2); Material mat = srcState.getMaterial(); if(!sticky && tile == null && mat.getMobilityFlag() == EnumPushReaction.NORMAL && srcState.getBlockHardness(world, pos2) != -1 && !srcState.getBlock().isAir(srcState, world, pos2)) { Material destMat = world.getBlockState(pos2.offset(dir)).getMaterial(); if(world.isAirBlock(pos2.offset(dir)) || destMat.isReplaceable()) { world.setBlockState(pos2, Blocks.AIR.getDefaultState()); world.setBlockState(pos2.offset(dir), srcState, 1 | 2); mappedPositions.put(s, new DimWithPos(world.provider.getDimension(), pos2.offset(dir))); } } pos = mappedPositions.get(s); mappedPositions.remove(s); mappedPositions.put(newPos, pos); save(world); } } } } } for(DimWithPos s : removeQueue) { coordsToCheck.remove(s); checkedCoords.remove(s); } removeQueue.clear(); } public void save(World world) { WorldData data = WorldData.get(world); if(data != null) data.markDirty(); } @Override public LexiconEntry getEntry(World world, BlockPos pos, EntityPlayer player, ItemStack lexicon) { return LexiconData.pistonRelay; } public static class DimWithPos { public final int dim; public final BlockPos blockPos; public DimWithPos(int dim, BlockPos pos) { this.dim = dim; blockPos = pos; } @Override public int hashCode() { return 31 * dim ^ blockPos.hashCode(); } @Override public boolean equals(Object o) { return o instanceof DimWithPos && dim == ((DimWithPos) o).dim && blockPos.equals(((DimWithPos) o).blockPos); } @Override public String toString() { return dim + ":" + blockPos.getX() + ":" + blockPos.getY() + ":" + blockPos.getZ(); } public static DimWithPos fromString(String s) { String[] split = s.split(":"); return new DimWithPos(Integer.parseInt(split[0]), new BlockPos(Integer.parseInt(split[1]), Integer.parseInt(split[2]), Integer.parseInt(split[3]))); } } }