package slimeknights.tconstruct.gadgets.block; import com.google.common.collect.ImmutableMap; import net.minecraft.block.Block; import net.minecraft.block.ITileEntityProvider; import net.minecraft.block.SoundType; import net.minecraft.block.material.Material; import net.minecraft.block.properties.PropertyBool; import net.minecraft.block.properties.PropertyDirection; import net.minecraft.block.properties.PropertyEnum; import net.minecraft.block.state.BlockStateContainer; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityItem; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.BlockRenderLayer; import net.minecraft.util.EnumFacing; import net.minecraft.util.IStringSerializable; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; import net.minecraftforge.event.entity.item.ItemExpireEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import java.util.ArrayList; import java.util.Locale; import javax.annotation.Nonnull; import javax.annotation.Nullable; import slimeknights.mantle.block.EnumBlock; import slimeknights.tconstruct.gadgets.tileentity.TileSlimeChannel; import slimeknights.tconstruct.library.TinkerRegistry; import slimeknights.tconstruct.shared.block.BlockSlime; import slimeknights.tconstruct.shared.block.BlockSlime.SlimeType; public class BlockSlimeChannel extends EnumBlock<SlimeType> implements ITileEntityProvider { public static final PropertyDirection SIDE = PropertyDirection.create("side"); public static final PropertyEnum<ChannelDirection> DIRECTION = PropertyEnum.create("direction", ChannelDirection.class); public static final PropertyBool POWERED = PropertyBool.create("powered"); public static final PropertyEnum<ChannelConnected> CONNECTED = PropertyEnum.create("connected", ChannelConnected.class); // stored dynamically public static final PropertyEnum<SlimeType> TYPE = BlockSlime.TYPE; public BlockSlimeChannel() { super(Material.CLAY, TYPE, SlimeType.class); this.setDefaultState(this.getBlockState().getBaseState().withProperty(TYPE, SlimeType.GREEN) .withProperty(SIDE, EnumFacing.DOWN) .withProperty(DIRECTION, ChannelDirection.NORTH) .withProperty(POWERED, Boolean.FALSE) .withProperty(CONNECTED, ChannelConnected.NONE)); //this.side = side; this.setHardness(0f); this.setSoundType(SoundType.SLIME); this.setCreativeTab(TinkerRegistry.tabGadgets); this.isBlockContainer = true; // has TE } /* Block state */ @Nonnull @Override protected BlockStateContainer createBlockState() { // CONNECTED determines how the channel is connected to the blocks next to it return new BlockStateContainer(this, TYPE, SIDE, DIRECTION, POWERED, CONNECTED); } // color and side data are stored on the tile entity, but are pulled into the blockstate upon loading the tile entity @Nonnull @Override public TileEntity createNewTileEntity(@Nonnull World worldIn, int meta) { return new TileSlimeChannel(); } /** * Convert the given metadata into a BlockState for this Block */ @Nonnull @Override public IBlockState getStateFromMeta(int meta) { return this.getDefaultState().withProperty(TYPE, SlimeType.fromMeta(meta & 7)) .withProperty(POWERED, (meta & 8) > 0); } /** * Convert the BlockState into the correct metadata value */ @Override public int getMetaFromState(IBlockState state) { int meta = state.getValue(TYPE).getMeta(); if(state.getValue(POWERED)) { meta |= 8; } return meta; } @Nonnull @Override public IBlockState getActualState(@Nonnull IBlockState state, IBlockAccess source, BlockPos pos) { state = addDataFromTE(state, source, pos); // connections! // first, try the outside, the block in front of this // this is checked first since a full peice is better than a partial one in the case of both EnumFacing side = state.getValue(SIDE); EnumFacing flow = state.getValue(DIRECTION).getFlow(side); BlockPos offset = pos.offset(side.getOpposite()); IBlockState check = source.getBlockState(offset); if(check.getBlock() == this && addDataFromTE(check, source, offset).getValue(SIDE).getOpposite() == flow) { return state.withProperty(CONNECTED, ChannelConnected.OUTER); } // if that does not work, try to connect to the inside, or the block behind this offset = pos.offset(side); check = source.getBlockState(offset); if(check.getBlock() == this && addDataFromTE(check, source, offset).getValue(SIDE) == flow) { return state.withProperty(CONNECTED, ChannelConnected.INNER); } // if neither work, no connection return state.withProperty(CONNECTED, ChannelConnected.NONE); } /** * Safe way to grab TE data above since we don't want to call getActualState inside itself for connections * (it would go back and forth and back and forth between the two blocks) */ private IBlockState addDataFromTE(IBlockState state, IBlockAccess source, BlockPos pos) { TileEntity te = source.getTileEntity(pos); if(te instanceof TileSlimeChannel) { TileSlimeChannel channel = (TileSlimeChannel) te; return state.withProperty(SIDE, channel.getSide()) .withProperty(DIRECTION, channel.getDirection()); } return state; } /** * Called by ItemBlocks just before a block is actually set in the world, to allow for adjustments to the * IBlockState */ @Override public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer, ItemStack stack) { // we temporarily store the data in the blockstate until the TE is created return this.getDefaultState().withProperty(TYPE, SlimeType.fromMeta(meta)) .withProperty(SIDE, facing.getOpposite()) .withProperty(DIRECTION, getPlacement(facing.getOpposite(), hitX, hitY, hitZ, placer)); } private ChannelDirection getPlacement(EnumFacing side, float hitX, float hitY, float hitZ, EntityLivingBase placer) { // determine the coordinates that we hit the face on int u = 0, v = 0; // up and down are the same if(side.getAxis() == EnumFacing.Axis.Y) { u = (int) (hitX * 16); v = (int) (hitZ * 16); } else { // all other sides use "y" as the "v" coordinate, but different "u" v = 15 - (int) (hitY * 16); switch(side) { case NORTH: u = (int) (hitX * 16); break; case SOUTH: u = 15 - (int) (hitX * 16); break; case WEST: u = 15 - (int) (hitZ * 16); break; case EAST: u = (int) (hitZ * 16); break; } } // now that we have our UV, determine the direction from that ChannelDirection direction; // top if(v < 5) { // left if(u < 5) { direction = ChannelDirection.NORTHWEST; } // right else if(u > 10) { direction = ChannelDirection.NORTHEAST; } // middle else { direction = ChannelDirection.NORTH; } } // bottom else if(v > 10) { // left if(u < 5) { direction = ChannelDirection.SOUTHWEST; } // right else if(u > 10) { direction = ChannelDirection.SOUTHEAST; } // middle else { direction = ChannelDirection.SOUTH; } } // middle else { // left if(u < 5) { direction = ChannelDirection.WEST; } // right else if(u > 10) { direction = ChannelDirection.EAST; } // exact center defaults to facing else { int facing = MathHelper.floor(placer.rotationYaw * 8.0F / 360.0F + 0.5D) & 7; direction = ChannelDirection.fromIndex(facing); // if on a wall, we rotate it a bit to make sure facing directly towards the wall is up if(side.getAxis() != EnumFacing.Axis.Y) { switch(side) { case SOUTH: direction = direction.getOpposite(); break; case WEST: direction = direction.rotate90(); break; case EAST: direction = direction.rotate90().getOpposite(); break; } } } } // if sneaking, reverse direction if(placer.isSneaking()) { direction = direction.getOpposite(); } return direction; } /** * Called by ItemBlocks after a block is set in the world, to allow post-place logic */ @Override public void onBlockPlacedBy(World worldIn, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack) { TileEntity te = worldIn.getTileEntity(pos); // pull the data we stored earlier into the Tile Entity if(te instanceof TileSlimeChannel) { TileSlimeChannel channel = (TileSlimeChannel) te; channel.setSide(state.getValue(SIDE)); channel.setDirection(state.getValue(DIRECTION)); } } /* Item drops */ @Override public int damageDropped(IBlockState state) { return state.getValue(TYPE).getMeta(); } /* Slimey flow */ /** * Called When an Entity Collided with the Block */ @Override public void onEntityCollidedWithBlock(World world, BlockPos pos, IBlockState state, Entity entity) { if(!state.getValue(POWERED)) { // bounding box to check AxisAlignedBB entityAABB = entity.getCollisionBoundingBox(); if(entityAABB == null) { entityAABB = entity.getEntityBoundingBox(); } // items be dumb double speed = 0.01; boolean item = false; if(entity instanceof EntityItem) { speed *= 1.5; item = true; } // data Motion motion = new Motion(); boolean inBounds = false; state = state.getActualState(world, pos); // get the direction and connected values EnumFacing side = state.getValue(SIDE); // only apply movement if the entity is within the liquid if(entityAABB.intersectsWith(getBounds(state, world, pos).offset(pos))) { inBounds = true; // tell the other bounding box not to reduce gravity again // no drowining in slime channels if(entity.isEntityAlive()) { entity.setAir(300); } // generic liquid stuff entity.setFire(0); entity.fallDistance = 0; ArrayList<EnumFacing> flow = state.getValue(DIRECTION).getFlowDiagonals(side); // its slimy, downward motion is reduced if(!flow.contains(EnumFacing.DOWN) && entity.motionY < 0) { entity.motionY /= 2; } // allow items to float upwards if(flow.contains(EnumFacing.UP) && item) { entity.onGround = false; } // apply motion boosts, may be twice for(EnumFacing facing : flow) { motion.boost(facing, speed); } } // apply additional movement based on the "connected" bounding box ChannelConnected connected = state.getValue(CONNECTED); if(connected == ChannelConnected.OUTER && entityAABB.intersectsWith(getSecondaryBounds(state).offset(pos))) { // only run this if not already in bounds above // mainly to remove redundancy, but it does have an effect with the fall speed if(!inBounds) { // makes the block "slimey", as in you fall through it slowly if(side != EnumFacing.DOWN && entity.motionY < 0) { entity.motionY /= 2; } // stop drowning if(entity.isEntityAlive()) { entity.setAir(300); } // normal liquid entity.setFire(0); entity.fallDistance = 0; } // allow items to float upwards if(side == EnumFacing.UP && item) { entity.onGround = false; } motion.boost(side, speed); } // finally, apply the boost entity.addVelocity(motion.x, motion.y, motion.z); } } // tells the game that the entity is in water @Nonnull @Override public Boolean isEntityInsideMaterial(IBlockAccess world, BlockPos pos, IBlockState state, Entity entity, double yToTest, Material material, boolean testingHead) { if(material != Material.WATER) { return null; } // bounding box to check AxisAlignedBB entityAABB = entity.getCollisionBoundingBox(); if(entityAABB == null) { entityAABB = entity.getEntityBoundingBox(); } // main bounding box state = state.getActualState(world, pos); // connected properties if(entityAABB.intersectsWith(getBounds(state, world, pos).offset(pos))) { return Boolean.TRUE; } // extra box used on sideways channels else if(state.getValue(CONNECTED) == ChannelConnected.OUTER && entityAABB.intersectsWith(getSecondaryBounds(state).offset(pos))) { return Boolean.TRUE; } return Boolean.FALSE; } /* Powering */ @Override public void onBlockAdded(World worldIn, BlockPos pos, IBlockState state) { this.updateState(worldIn, pos, state); } /** * Called when a neighboring block was changed and marks that this state should perform any checks during a neighbor * change. Cases may include when redstone power is updated, cactus blocks popping off due to a neighboring solid * block, etc. */ @Override public void neighborChanged(IBlockState state, World worldIn, BlockPos pos, Block blockIn) { this.updateState(worldIn, pos, state); } public void updateState(World world, BlockPos pos, IBlockState state) { boolean powered = world.isBlockPowered(pos); // don't do any changes if the block is the same if(powered != state.getValue(POWERED).booleanValue()) { world.setBlockState(pos, state.withProperty(POWERED, Boolean.valueOf(powered))); } } /* Bounds */ // Block hitbox and main location for motion private static final ImmutableMap<EnumFacing, AxisAlignedBB> BOUNDS; // "quarter slab" bounds on bottom used for location checks on connected blocks private static final ImmutableMap<EnumFacing, AxisAlignedBB> LOWER_BOUNDS; // "quarter slab" bounds on side for sideways connected private static final ImmutableMap<EnumFacing, AxisAlignedBB> SIDE_BOUNDS; // "quarter slab" bounds on top for bottom outer connected private static final ImmutableMap<EnumFacing, AxisAlignedBB> UPPER_BOUNDS; static { ImmutableMap.Builder<EnumFacing, AxisAlignedBB> builder = ImmutableMap.builder(); builder.put(EnumFacing.UP, new AxisAlignedBB(0, 0.5, 0, 1, 1, 1 )); builder.put(EnumFacing.DOWN, new AxisAlignedBB(0, 0, 0, 1, 0.5, 1 )); builder.put(EnumFacing.NORTH, new AxisAlignedBB(0, 0, 0, 1, 1, 0.5)); builder.put(EnumFacing.SOUTH, new AxisAlignedBB(0, 0, 0.5, 1, 1, 1 )); builder.put(EnumFacing.WEST, new AxisAlignedBB(0, 0, 0, 0.5, 1, 1 )); builder.put(EnumFacing.EAST, new AxisAlignedBB(0.5, 0, 0, 1, 1, 1 )); BOUNDS = builder.build(); builder = ImmutableMap.builder(); builder.put(EnumFacing.NORTH, new AxisAlignedBB(0, 0, 0, 1, 0.5, 0.5)); builder.put(EnumFacing.SOUTH, new AxisAlignedBB(0, 0, 0.5, 1, 0.5, 1 )); builder.put(EnumFacing.WEST, new AxisAlignedBB(0, 0, 0, 0.5, 0.5, 1 )); builder.put(EnumFacing.EAST, new AxisAlignedBB(0.5, 0, 0, 1, 0.5, 1 )); LOWER_BOUNDS = builder.build(); builder = ImmutableMap.builder(); builder.put(EnumFacing.NORTH, new AxisAlignedBB(0, 0, 0, 0.5, 1, 0.5)); builder.put(EnumFacing.SOUTH, new AxisAlignedBB(0.5, 0, 0.5, 1, 1, 1 )); builder.put(EnumFacing.WEST, new AxisAlignedBB(0, 0, 0.5, 0.5, 1, 1 )); builder.put(EnumFacing.EAST, new AxisAlignedBB(0.5, 0, 0, 1, 1, 0.5)); SIDE_BOUNDS = builder.build(); builder = ImmutableMap.builder(); builder.put(EnumFacing.NORTH, new AxisAlignedBB(0, 0.5, 0, 1, 1, 0.5)); builder.put(EnumFacing.SOUTH, new AxisAlignedBB(0, 0.5, 0.5, 1, 1, 1 )); builder.put(EnumFacing.WEST, new AxisAlignedBB(0, 0.5, 0, 0.5, 1, 1 )); builder.put(EnumFacing.EAST, new AxisAlignedBB(0.5, 0.5, 0, 1, 1, 1 )); UPPER_BOUNDS = builder.build(); } @Override public AxisAlignedBB getCollisionBoundingBox(IBlockState state, @Nonnull World worldIn, @Nonnull BlockPos pos) { return NULL_AABB; } @Nonnull @Override public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos) { return getBounds(state.getActualState(source, pos), source, pos); } /** * Returns the bounds for the current state * <br> * Makes sure you pas the actual state into this or it won't take connections into account */ private AxisAlignedBB getBounds(IBlockState state, IBlockAccess source, BlockPos pos) { EnumFacing side = state.getValue(SIDE); EnumFacing facing = state.getValue(DIRECTION).getFacing(); ChannelConnected connected = state.getValue(CONNECTED); // diagonals return null above, and cannot have such connections anyways if(connected == ChannelConnected.INNER && facing != null) { if(side == EnumFacing.DOWN) { return LOWER_BOUNDS.get(facing); } else if(side == EnumFacing.UP) { return UPPER_BOUNDS.get(facing); } else { switch(facing) { case NORTH: return UPPER_BOUNDS.get(side); case SOUTH: return LOWER_BOUNDS.get(side); case WEST: return SIDE_BOUNDS.get(side); case EAST: return SIDE_BOUNDS.get(side.rotateY()); } } } return BOUNDS.get(side); } private AxisAlignedBB getSecondaryBounds(IBlockState state) { EnumFacing side = state.getValue(SIDE); EnumFacing facing = state.getValue(DIRECTION).getFacing(); // this just prevents a NPE in the case of an invalid state // as a block will never be connected and diagonal except in debug if(facing == null) { return FULL_BLOCK_AABB; } if(side == EnumFacing.DOWN) { return UPPER_BOUNDS.get(facing.getOpposite()); } else if(side == EnumFacing.UP) { return LOWER_BOUNDS.get(facing.getOpposite()); } else { switch(facing) { case NORTH: return LOWER_BOUNDS.get(side.getOpposite()); case SOUTH: return UPPER_BOUNDS.get(side.getOpposite()); case WEST: return SIDE_BOUNDS.get(side.getOpposite()); case EAST: return SIDE_BOUNDS.get(side.rotateYCCW()); default: return FULL_BLOCK_AABB; } } } /* Misc */ @Nonnull @Override @SideOnly(Side.CLIENT) public BlockRenderLayer getBlockLayer() { return BlockRenderLayer.TRANSLUCENT; } @Override public boolean isFullCube(IBlockState state) { return false; } @Override public boolean isOpaqueCube(IBlockState state) { return false; } @SuppressWarnings("deprecation") @Override public boolean shouldSideBeRendered(IBlockState state, @Nonnull IBlockAccess blockAccess, @Nonnull BlockPos pos, EnumFacing face) { @SuppressWarnings("unused") int knightminers_sanity_percentage_after_writing_function = 14; // common logic for solid blocks, basically if a solid face is on the side we can skip all of this if(!super.shouldSideBeRendered(state, blockAccess, pos, face)) { return false; } // otherwise back out if the block is not a channel IBlockState offset = blockAccess.getBlockState(pos.offset(face)); if(!(offset.getBlock() == this)) { return true; } // or if it is a different color if(state.getValue(TYPE) != offset.getValue(TYPE)) { return true; } // so, it matches. great, grab directions and connections state = state.getActualState(blockAccess, pos); offset = offset.getActualState(blockAccess, pos.offset(face)); // then set up some data for readability and send it along EnumFacing side = state.getValue(SIDE); ChannelConnected connected = state.getValue(CONNECTED); EnumFacing flow = state.getValue(DIRECTION).getFlow(side); EnumFacing offsetSide = offset.getValue(SIDE); ChannelConnected offsetConnected = offset.getValue(CONNECTED); EnumFacing offsetFlow = offset.getValue(DIRECTION).getFlow(offsetSide); // for diagonals, overwrite the connected value if(flow == null) { connected = ChannelConnected.NONE; } if(offsetFlow == null) { offsetConnected = ChannelConnected.NONE; } // the other channel is against our back if(face == side) { // if its inner, we have a half face if(connected == ChannelConnected.INNER) { // so ask with the half being our direction return !hasHalfSide(flow, face, offsetSide, offsetFlow, offsetConnected); } // otherwise we have a full one return !hasFullSide(face, offsetSide, offsetFlow, offsetConnected); } // the other channel is "above" of ours if(face == side.getOpposite()) { // outer state has one half face if(connected == ChannelConnected.OUTER) { // though the half is opposite the direction now return !hasHalfSide(flow.getOpposite(), face, offsetSide, offsetFlow, offsetConnected); } // if its not the outer face, it really doesn't matter as no faces are here to hide return true; } // the other channel is in front of where we flow to if(face == flow) { // in this case, the face is always there, so just send a generic half side return !hasHalfSide(side, face, offsetSide, offsetFlow, offsetConnected); } // the other channel is behind us if(face.getOpposite() == flow) { // do we have a full face, a half one, or none? switch(connected) { // inner means no face to deal with, so back out case INNER: return true; // outer means a full face case OUTER: return !hasFullSide(face, offsetSide, offsetFlow, offsetConnected); // none means half face case NONE: return !hasHalfSide(side, face, offsetSide, offsetFlow, offsetConnected); } } // last two cases are complicated, as they both can be a quarter, half, or three quarters switch(connected) { // easiest version, means we have a half face like above case NONE: return !hasHalfSide(side, face, offsetSide, offsetFlow, offsetConnected); // outer means we have a stair shape with a hole opposite the side in the direction case OUTER: // if we have a full side, then definatelly if(hasFullSide(face, offsetSide, offsetFlow, offsetConnected)) { return false; } // if its the same shape, there is a possibility if(offsetConnected == ChannelConnected.OUTER) { // option one, we are the same shape if(offsetSide == side) { return offsetFlow != flow; } // option two is option one flipped twice if(offsetSide == flow.getOpposite()) { return offsetFlow != side.getOpposite(); } // neither? just show it return true; } // both inner and none mean we cannot cull since full sides were already matched return true; // inner means we have a quarter in the front, or in the direction we go case INNER: // on any of theses three sides, if it matches a direction below it should not cull, but culls otherwise if(offsetSide == side || offsetSide == face.getOpposite() || offsetSide == flow) { if(offsetConnected == ChannelConnected.INNER && (offsetFlow == side.getOpposite() || offsetFlow == face || offsetFlow == flow.getOpposite())) { return true; } return false; } // the other three can only possibly connect if on the outer side if(offsetConnected == ChannelConnected.OUTER) { // since we already checked the side above, all thats left is the direction which states we should cull if(offsetFlow == face || offsetFlow == side.getOpposite() || offsetFlow == flow.getOpposite()) { return false; } } return true; } // isn't possible as connected cannot be null, but here to prevent errors should it become possible return true; } private static boolean hasFullSide(EnumFacing orginFace, EnumFacing side, EnumFacing flow, ChannelConnected connected) { // back, full unless we are the inner corner if(orginFace == side.getOpposite() && connected != ChannelConnected.INNER) { return true; } // back side face, full if we are connected if(orginFace == flow && connected == ChannelConnected.OUTER) { return true; } return false; } private static boolean hasHalfSide(EnumFacing orginHalf, EnumFacing orginFace, EnumFacing side, EnumFacing flow, ChannelConnected connected) { // if we are on the same side as the half face if(side == orginHalf) { // make sure inner connections face the same direction if(connected == ChannelConnected.INNER) { // the direction is opposite the face of the half return flow == orginFace.getOpposite(); } // both outer and none have this face solid return true; } // pressed up against this, basically the same as above only switched half and direction if(side == orginFace.getOpposite()) { // inner connection must be on the same half as the direction its going if(connected == ChannelConnected.INNER) { return flow == orginHalf; } // both outer and none have this face solid return true; } // opposite side of the block if(side == orginFace) { // it must face the opposite of the half and be connected outer return connected == ChannelConnected.OUTER && flow == orginHalf.getOpposite(); } // there are three remaining directions, but their only chance is if they have the outer chance if(connected == ChannelConnected.OUTER) { // if its facing away, it has a full face here if(flow == orginFace) { return true; } // if the channel is opposite the half, the only valid facing is the one handled above if(side == orginHalf.getOpposite()) { return false; } // otherwise there is an additional valid facing, going the opposite direction of the half leading to "stairs" return flow == orginHalf.getOpposite(); } return false; } /* Helpers */ /** * Stores the direction of the channel, though relative to the side */ public enum ChannelDirection implements IStringSerializable { SOUTH, SOUTHWEST, WEST, NORTHWEST, NORTH, NORTHEAST, EAST, SOUTHEAST; public final int index; private ChannelDirection() { this.index = this.ordinal(); } @Override public String getName() { return this.toString().toLowerCase(Locale.US); } /** * @return an integer representing this value, used for the sake of saving this to the TE */ public int getIndex() { return index; } /** * @return the value corresponding to the integer given, used for loading from the TE */ public static ChannelDirection fromIndex(int index) { if(index < 0 || index >= values().length) { index = 0; } return values()[index]; } /** * @return the opposite direction for the current side */ public ChannelDirection getOpposite() { switch(this) { case SOUTH: return NORTH; case SOUTHWEST: return NORTHEAST; case WEST: return EAST; case NORTHWEST: return SOUTHEAST; case NORTH: return SOUTH; case NORTHEAST: return SOUTHWEST; case EAST: return WEST; case SOUTHEAST: return NORTHWEST; } // not possible, but here because eclipse wants it return null; } /** * @return the opposite direction for the current side */ public ChannelDirection rotate90() { switch(this) { case SOUTH: return WEST; case SOUTHWEST: return NORTHWEST; case WEST: return NORTH; case NORTHWEST: return NORTHEAST; case NORTH: return EAST; case NORTHEAST: return SOUTHEAST; case EAST: return SOUTH; case SOUTHEAST: return SOUTHWEST; } // not possible, but here because eclipse wants it return null; } /** * Gets the EnumFacing value with the same name as one of this Enum */ public EnumFacing getFacing() { switch(this) { case NORTH: return EnumFacing.NORTH; case SOUTH: return EnumFacing.SOUTH; case WEST: return EnumFacing.WEST; case EAST: return EnumFacing.EAST; } return null; } /** * Gets the EnumFacing value with the same name as one of this Enum */ public ChannelDirection fromFacing(EnumFacing facing) { switch(facing) { case NORTH: return NORTH; case SOUTH: return SOUTH; case WEST: return WEST; case EAST: return EAST; } return null; } /** * Returns the direction of flow for the given side * <br> * If the side is a diagonal, it returns null, use getDiagonal below for the two relevant directions */ @Nullable public EnumFacing getFlow(EnumFacing side) { switch(side) { case NORTH: switch(this) { case NORTH: return EnumFacing.UP; case SOUTH: return EnumFacing.DOWN; case WEST: return EnumFacing.WEST; case EAST: return EnumFacing.EAST; } case SOUTH: switch(this) { case NORTH: return EnumFacing.UP; case SOUTH: return EnumFacing.DOWN; case WEST: return EnumFacing.EAST; case EAST: return EnumFacing.WEST; } case WEST: switch(this) { case NORTH: return EnumFacing.UP; case SOUTH: return EnumFacing.DOWN; case WEST: return EnumFacing.SOUTH; case EAST: return EnumFacing.NORTH; } case EAST: switch(this) { case NORTH: return EnumFacing.UP; case SOUTH: return EnumFacing.DOWN; case WEST: return EnumFacing.NORTH; case EAST: return EnumFacing.SOUTH; } default: // note that this returns null for diagonals return this.getFacing(); } } /** * Returns a list of one or two directions for the sake of liquid flow */ public ArrayList<EnumFacing> getFlowDiagonals(@Nonnull EnumFacing side) { ArrayList<EnumFacing> list = new ArrayList<EnumFacing>(); switch(this) { case NORTH: list.add(NORTH.getFlow(side)); break; case SOUTH: list.add(SOUTH.getFlow(side)); break; case WEST: list.add(WEST.getFlow(side)); break; case EAST: list.add(EAST.getFlow(side)); break; case NORTHWEST: list.add(NORTH.getFlow(side)); list.add(WEST.getFlow(side)); break; case NORTHEAST: list.add(NORTH.getFlow(side)); list.add(EAST.getFlow(side)); break; case SOUTHWEST: list.add(SOUTH.getFlow(side)); list.add(WEST.getFlow(side)); break; case SOUTHEAST: list.add(SOUTH.getFlow(side)); list.add(EAST.getFlow(side)); break; } return list; } } /** * Determines in what way the channel connects to other channels */ public enum ChannelConnected implements IStringSerializable { NONE, INNER, OUTER; @Override public String getName() { return this.toString().toLowerCase(Locale.US); } } /** * A convince class to both store and modify the motion for channels */ private static class Motion { public double x, y, z; public Motion() { x = 0; y = 0; z = 0; } public Motion boost(EnumFacing facing, double speed) { switch(facing) { case UP: this.y += speed * 3; // compensate for gravity break; case DOWN: this.y -= speed; break; case NORTH: this.z -= speed; break; case SOUTH: this.z += speed; break; case WEST: this.x -= speed; break; case EAST: this.x += speed; break; } return this; } } /** * Does all of the events used by slime channel */ public static class EventHandler { public static final EventHandler instance = new EventHandler(); private EventHandler() { } // stop items from despawning when inside channels // this won't give them a full 5 minutes upon exiting, only upon attempting to despawn @SubscribeEvent public void onItemExpire(ItemExpireEvent event) { EntityItem item = event.getEntityItem(); if(item.getEntityWorld().getBlockState(item.getPosition()).getBlock() instanceof BlockSlimeChannel) { event.setCanceled(true); } } } }