From 728644c104fc58fe34eae555d0285d3cf26031ab Mon Sep 17 00:00:00 2001 From: SquidDev Date: Tue, 9 May 2017 23:03:42 +0100 Subject: [PATCH] Initial attempt at improving cable/wired modem interactions - Cable and modem can be broken individually - Ray tracing will go through "holes" in the cable. - Pick block will determine which part you are looking at. - Selection box will only highlight the region you are looking at: modem or cable. --- .../proxy/ComputerCraftProxyClient.java | 2 + .../client/render/RenderOverlayCable.java | 215 ++++++++++++++++++ .../shared/common/BlockGeneric.java | 4 +- .../shared/peripheral/common/BlockCable.java | 125 +++++++++- .../shared/peripheral/modem/TileCable.java | 4 +- .../computercraft/shared/util/WorldUtil.java | 46 +++- 6 files changed, 385 insertions(+), 11 deletions(-) create mode 100644 src/main/java/dan200/computercraft/client/render/RenderOverlayCable.java diff --git a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java index db2da9330..ad9267680 100644 --- a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java +++ b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java @@ -8,6 +8,7 @@ package dan200.computercraft.client.proxy; import dan200.computercraft.ComputerCraft; import dan200.computercraft.client.gui.*; +import dan200.computercraft.client.render.RenderOverlayCable; import dan200.computercraft.client.render.TileEntityMonitorRenderer; import dan200.computercraft.shared.computer.blocks.ComputerState; import dan200.computercraft.shared.computer.blocks.TileComputer; @@ -442,6 +443,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon { ForgeHandlers handlers = new ForgeHandlers(); MinecraftForge.EVENT_BUS.register( handlers ); + MinecraftForge.EVENT_BUS.register( new RenderOverlayCable() ); } public class ForgeHandlers diff --git a/src/main/java/dan200/computercraft/client/render/RenderOverlayCable.java b/src/main/java/dan200/computercraft/client/render/RenderOverlayCable.java new file mode 100644 index 000000000..5ee9e430f --- /dev/null +++ b/src/main/java/dan200/computercraft/client/render/RenderOverlayCable.java @@ -0,0 +1,215 @@ +package dan200.computercraft.client.render; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.peripheral.PeripheralType; +import dan200.computercraft.shared.peripheral.common.BlockCable; +import dan200.computercraft.shared.peripheral.modem.TileCable; +import dan200.computercraft.shared.util.WorldUtil; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderGlobal; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.world.World; +import net.minecraftforge.client.event.DrawBlockHighlightEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.lwjgl.opengl.GL11; + +public class RenderOverlayCable +{ + private static final float EXPAND = 0.002f; + private static final double MIN = TileCable.MIN - EXPAND; + private static final double MAX = TileCable.MAX + EXPAND; + + @SubscribeEvent + public void drawHighlight( DrawBlockHighlightEvent event ) + { + if( event.getTarget().typeOfHit != RayTraceResult.Type.BLOCK ) return; + + BlockPos pos = event.getTarget().getBlockPos(); + World world = event.getPlayer().getEntityWorld(); + + IBlockState state = world.getBlockState( pos ); + if( state.getBlock() != ComputerCraft.Blocks.cable ) return; + + TileEntity tile = world.getTileEntity( pos ); + if( tile == null || !(tile instanceof TileCable) ) return; + + event.setCanceled( true ); + TileCable cable = (TileCable) tile; + + PeripheralType type = cable.getPeripheralType(); + + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate( GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0 ); + GlStateManager.color( 0.0f, 0.0f, 0.0f, 0.4f ); + GL11.glLineWidth( 2.0F ); + GlStateManager.disableTexture2D(); + GlStateManager.depthMask( false ); + GlStateManager.pushMatrix(); + + EnumFacing direction = type != PeripheralType.Cable ? cable.getDirection() : null; + + { + EntityPlayer player = event.getPlayer(); + double x = player.lastTickPosX + (player.posX - player.lastTickPosX) * event.getPartialTicks(); + double y = player.lastTickPosY + (player.posY - player.lastTickPosY) * event.getPartialTicks(); + double z = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * event.getPartialTicks(); + + GlStateManager.translate( -x + pos.getX(), -y + pos.getY(), -z + pos.getZ() ); + } + + if( type != PeripheralType.Cable && WorldUtil.isVecInsideInclusive( cable.getModemBounds(), event.getTarget().hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) ) + { + RenderGlobal.drawSelectionBoundingBox( cable.getModemBounds(), 0, 0, 0, 0.4f ); + } + else + { + int flags = 0; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + for( EnumFacing facing : EnumFacing.VALUES ) + { + if( direction == facing || BlockCable.isCable( world, pos.offset( facing ) ) ) + { + flags |= 1 << facing.ordinal(); + + + switch( facing.getAxis() ) + { + case X: + { + double offset = facing == EnumFacing.WEST ? -EXPAND : 1 + EXPAND; + double centre = facing == EnumFacing.WEST ? MIN : MAX; + + buffer.begin( GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION ); + buffer.pos( offset, MIN, MIN ).endVertex(); + buffer.pos( offset, MAX, MIN ).endVertex(); + buffer.pos( offset, MAX, MAX ).endVertex(); + buffer.pos( offset, MIN, MAX ).endVertex(); + buffer.pos( offset, MIN, MIN ).endVertex(); + tessellator.draw(); + + buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION ); + buffer.pos( offset, MIN, MIN ).endVertex(); + buffer.pos( centre, MIN, MIN ).endVertex(); + buffer.pos( offset, MAX, MIN ).endVertex(); + buffer.pos( centre, MAX, MIN ).endVertex(); + buffer.pos( offset, MAX, MAX ).endVertex(); + buffer.pos( centre, MAX, MAX ).endVertex(); + buffer.pos( offset, MIN, MAX ).endVertex(); + buffer.pos( centre, MIN, MAX ).endVertex(); + tessellator.draw(); + break; + } + case Y: + { + double offset = facing == EnumFacing.DOWN ? -EXPAND : 1 + EXPAND; + double centre = facing == EnumFacing.DOWN ? MIN : MAX; + + buffer.begin( GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION ); + buffer.pos( MIN, offset, MIN ).endVertex(); + buffer.pos( MAX, offset, MIN ).endVertex(); + buffer.pos( MAX, offset, MAX ).endVertex(); + buffer.pos( MIN, offset, MAX ).endVertex(); + buffer.pos( MIN, offset, MIN ).endVertex(); + tessellator.draw(); + + buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION ); + buffer.pos( MIN, offset, MIN ).endVertex(); + buffer.pos( MIN, centre, MIN ).endVertex(); + buffer.pos( MAX, offset, MIN ).endVertex(); + buffer.pos( MAX, centre, MIN ).endVertex(); + buffer.pos( MAX, offset, MAX ).endVertex(); + buffer.pos( MAX, centre, MAX ).endVertex(); + buffer.pos( MIN, offset, MAX ).endVertex(); + buffer.pos( MIN, centre, MAX ).endVertex(); + tessellator.draw(); + break; + } + case Z: + { + double offset = facing == EnumFacing.NORTH ? -EXPAND : 1 + EXPAND; + double centre = facing == EnumFacing.NORTH ? MIN : MAX; + + buffer.begin( GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION ); + buffer.pos( MIN, MIN, offset ).endVertex(); + buffer.pos( MAX, MIN, offset ).endVertex(); + buffer.pos( MAX, MAX, offset ).endVertex(); + buffer.pos( MIN, MAX, offset ).endVertex(); + buffer.pos( MIN, MIN, offset ).endVertex(); + tessellator.draw(); + + buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION ); + buffer.pos( MIN, MIN, offset ).endVertex(); + buffer.pos( MIN, MIN, centre ).endVertex(); + buffer.pos( MAX, MIN, offset ).endVertex(); + buffer.pos( MAX, MIN, centre ).endVertex(); + buffer.pos( MAX, MAX, offset ).endVertex(); + buffer.pos( MAX, MAX, centre ).endVertex(); + buffer.pos( MIN, MAX, offset ).endVertex(); + buffer.pos( MIN, MAX, centre ).endVertex(); + tessellator.draw(); + break; + } + } + } + } + + buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION ); + + draw( buffer, flags, EnumFacing.WEST, EnumFacing.DOWN, EnumFacing.Axis.Z ); + draw( buffer, flags, EnumFacing.WEST, EnumFacing.UP, EnumFacing.Axis.Z ); + draw( buffer, flags, EnumFacing.EAST, EnumFacing.DOWN, EnumFacing.Axis.Z ); + draw( buffer, flags, EnumFacing.EAST, EnumFacing.UP, EnumFacing.Axis.Z ); + + draw( buffer, flags, EnumFacing.WEST, EnumFacing.NORTH, EnumFacing.Axis.Y ); + draw( buffer, flags, EnumFacing.WEST, EnumFacing.SOUTH, EnumFacing.Axis.Y ); + draw( buffer, flags, EnumFacing.EAST, EnumFacing.NORTH, EnumFacing.Axis.Y ); + draw( buffer, flags, EnumFacing.EAST, EnumFacing.SOUTH, EnumFacing.Axis.Y ); + + draw( buffer, flags, EnumFacing.DOWN, EnumFacing.NORTH, EnumFacing.Axis.X ); + draw( buffer, flags, EnumFacing.DOWN, EnumFacing.SOUTH, EnumFacing.Axis.X ); + draw( buffer, flags, EnumFacing.UP, EnumFacing.NORTH, EnumFacing.Axis.X ); + draw( buffer, flags, EnumFacing.UP, EnumFacing.SOUTH, EnumFacing.Axis.X ); + + tessellator.draw(); + } + + GlStateManager.popMatrix(); + GlStateManager.depthMask( true ); + GlStateManager.enableTexture2D(); + GlStateManager.disableBlend(); + } + + private static void draw( BufferBuilder buffer, int flags, EnumFacing a, EnumFacing b, EnumFacing.Axis other ) + { + if( ((flags >> a.ordinal()) & 1) != ((flags >> b.ordinal()) & 1) ) return; + + double offA = a.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE ? MIN : MAX; + double offB = b.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE ? MIN : MAX; + switch( other ) + { + case X: + buffer.pos( MIN, offA, offB ).endVertex(); + buffer.pos( MAX, offA, offB ).endVertex(); + break; + case Y: + buffer.pos( offA, MIN, offB ).endVertex(); + buffer.pos( offA, MAX, offB ).endVertex(); + break; + case Z: + buffer.pos( offA, offB, MIN ).endVertex(); + buffer.pos( offA, offB, MAX ).endVertex(); + break; + } + } +} diff --git a/src/main/java/dan200/computercraft/shared/common/BlockGeneric.java b/src/main/java/dan200/computercraft/shared/common/BlockGeneric.java index cf65d541b..983c7e1f5 100644 --- a/src/main/java/dan200/computercraft/shared/common/BlockGeneric.java +++ b/src/main/java/dan200/computercraft/shared/common/BlockGeneric.java @@ -68,7 +68,7 @@ public abstract class BlockGeneric extends Block implements } @Override - public final boolean removedByPlayer( @Nonnull IBlockState state, World world, @Nonnull BlockPos pos, @Nonnull EntityPlayer player, boolean willHarvest ) + public boolean removedByPlayer( @Nonnull IBlockState state, World world, @Nonnull BlockPos pos, @Nonnull EntityPlayer player, boolean willHarvest ) { if( !world.isRemote ) { @@ -122,7 +122,7 @@ public abstract class BlockGeneric extends Block implements @Nonnull @Override - public final ItemStack getPickBlock( @Nonnull IBlockState state, RayTraceResult target, @Nonnull World world, @Nonnull BlockPos pos, EntityPlayer player ) + public ItemStack getPickBlock( @Nonnull IBlockState state, RayTraceResult target, @Nonnull World world, @Nonnull BlockPos pos, EntityPlayer player ) { TileEntity tile = world.getTileEntity( pos ); if( tile != null && tile instanceof TileGeneric ) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/common/BlockCable.java b/src/main/java/dan200/computercraft/shared/peripheral/common/BlockCable.java index 2fb4abb35..4e5d2419b 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/common/BlockCable.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/common/BlockCable.java @@ -7,21 +7,32 @@ package dan200.computercraft.shared.peripheral.common; import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.common.TileGeneric; import dan200.computercraft.shared.peripheral.PeripheralType; import dan200.computercraft.shared.peripheral.modem.TileCable; +import dan200.computercraft.shared.util.WorldUtil; import net.minecraft.block.Block; import net.minecraft.block.properties.PropertyBool; import net.minecraft.block.properties.PropertyEnum; import net.minecraft.block.state.BlockFaceShape; import net.minecraft.block.state.BlockStateContainer; import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; public class BlockCable extends BlockPeripheralBase { @@ -195,7 +206,7 @@ public class BlockCable extends BlockPeripheralBase TileEntity tile = world.getTileEntity( pos ); if( tile != null && tile instanceof TilePeripheralBase ) { - TilePeripheralBase peripheral = (TilePeripheralBase)tile; + TilePeripheralBase peripheral = (TilePeripheralBase) tile; anim = peripheral.getAnim(); } else @@ -208,7 +219,7 @@ public class BlockCable extends BlockPeripheralBase { modem = BlockCableModemVariant.values()[ 1 + 6 * anim + modem.getFacing().getIndex() - ]; + ]; } state = state.withProperty( Properties.MODEM, modem ); @@ -253,6 +264,116 @@ public class BlockCable extends BlockPeripheralBase return new TileCable(); } + @Nullable + @Override + @Deprecated + public RayTraceResult collisionRayTrace( IBlockState blockState, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Vec3d start, @Nonnull Vec3d end ) + { + TileEntity tile = world.getTileEntity( pos ); + if( tile != null && tile instanceof TileGeneric && tile.hasWorld() ) + { + TileGeneric generic = (TileGeneric) tile; + + double distance = Double.POSITIVE_INFINITY; + RayTraceResult result = null; + + List bounds = new ArrayList( 7 ); + generic.getCollisionBounds( bounds ); + + Vec3d startOff = start.subtract( pos.getX(), pos.getY(), pos.getZ() ); + Vec3d endOff = end.subtract( pos.getX(), pos.getY(), pos.getZ() ); + + for( AxisAlignedBB bb : bounds ) + { + RayTraceResult hit = bb.calculateIntercept( startOff, endOff ); + if( hit != null ) + { + double newDistance = hit.hitVec.squareDistanceTo( startOff ); + if( newDistance <= distance ) + { + distance = newDistance; + result = hit; + } + } + } + + return result == null ? null : new RayTraceResult( result.hitVec.addVector( pos.getX(), pos.getY(), pos.getZ() ), result.sideHit, pos ); + } + else + { + return super.collisionRayTrace( blockState, world, pos, start, end ); + } + } + + @Override + public boolean removedByPlayer( @Nonnull IBlockState state, World world, @Nonnull BlockPos pos, @Nonnull EntityPlayer player, boolean willHarvest ) + { + PeripheralType type = getPeripheralType( world, pos ); + if( type == PeripheralType.WiredModemWithCable ) + { + RayTraceResult hit = state.collisionRayTrace( world, pos, WorldUtil.getRayStart( player ), WorldUtil.getRayEnd( player ) ); + if( hit != null ) + { + TileEntity tile = world.getTileEntity( pos ); + if( tile != null && tile instanceof TileCable && tile.hasWorld() ) + { + TileCable cable = (TileCable) tile; + + ItemStack item; + + AxisAlignedBB bb = cable.getModemBounds(); + if( WorldUtil.isVecInsideInclusive( bb, hit.hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) ) + { + world.setBlockState( pos, state.withProperty( Properties.MODEM, BlockCableModemVariant.None ), 3 ); + cable.networkChanged(); + item = PeripheralItemFactory.create( PeripheralType.WiredModem, null, 1 ); + } + else + { + world.setBlockState( pos, state.withProperty( Properties.CABLE, false ), 3 ); + cable.networkChanged(); + item = PeripheralItemFactory.create( PeripheralType.Cable, null, 1 ); + } + + if( !world.isRemote && !player.capabilities.isCreativeMode ) dropItem( world, pos, item ); + + return false; + } + } + } + + return super.removedByPlayer( state, world, pos, player, willHarvest ); + } + + @Override + public ItemStack getPickBlock( @Nonnull IBlockState state, RayTraceResult hit, @Nonnull World world, @Nonnull BlockPos pos, EntityPlayer player ) + { + TileEntity tile = world.getTileEntity( pos ); + if( tile != null && tile instanceof TileCable && tile.hasWorld() ) + { + TileCable cable = (TileCable) tile; + PeripheralType type = getPeripheralType( state ); + + if( type == PeripheralType.WiredModemWithCable ) + { + if( hit == null || WorldUtil.isVecInsideInclusive( cable.getModemBounds(), hit.hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) ) + { + return PeripheralItemFactory.create( PeripheralType.WiredModem, null, 1 ); + } + else + { + return PeripheralItemFactory.create( PeripheralType.Cable, null, 1 ); + } + } + else + { + return PeripheralItemFactory.create( type, null, 1 ); + } + } + + return PeripheralItemFactory.create( PeripheralType.Cable, null, 1 ); + } + @Override @Deprecated public final boolean isOpaqueCube( IBlockState state ) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/TileCable.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/TileCable.java index b463a0c25..212fe807f 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/TileCable.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/TileCable.java @@ -46,8 +46,8 @@ import static dan200.computercraft.core.apis.ArgumentHelper.getString; public class TileCable extends TileModemBase implements IPacketNetwork { - private static final double MIN = 0.375; - private static final double MAX = 1 - MIN; + public static final double MIN = 0.375; + public static final double MAX = 1 - MIN; private static final AxisAlignedBB BOX_CENTRE = new AxisAlignedBB( MIN, MIN, MIN, MAX, MAX, MAX ); private static final AxisAlignedBB[] BOXES = new AxisAlignedBB[]{ diff --git a/src/main/java/dan200/computercraft/shared/util/WorldUtil.java b/src/main/java/dan200/computercraft/shared/util/WorldUtil.java index ff7289a4d..a2785a429 100644 --- a/src/main/java/dan200/computercraft/shared/util/WorldUtil.java +++ b/src/main/java/dan200/computercraft/shared/util/WorldUtil.java @@ -6,11 +6,18 @@ package dan200.computercraft.shared.util; +import net.minecraft.client.Minecraft; import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; -import net.minecraft.util.*; -import net.minecraft.util.math.*; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import org.apache.commons.lang3.tuple.Pair; @@ -47,9 +54,9 @@ public class WorldUtil } // Check for entities - float xStretch = (Math.abs(vecDir.x) > 0.25f) ? 0.0f : 1.0f; - float yStretch = (Math.abs(vecDir.y) > 0.25f) ? 0.0f : 1.0f; - float zStretch = (Math.abs(vecDir.z) > 0.25f) ? 0.0f : 1.0f; + float xStretch = Math.abs(vecDir.x) > 0.25f ? 0.0f : 1.0f; + float yStretch = Math.abs(vecDir.y) > 0.25f ? 0.0f : 1.0f; + float zStretch = Math.abs(vecDir.z) > 0.25f ? 0.0f : 1.0f; AxisAlignedBB bigBox = new AxisAlignedBB( Math.min(vecStart.x, vecEnd.x) - 0.375f * xStretch, Math.min(vecStart.y, vecEnd.y) - 0.375f * yStretch, @@ -113,6 +120,35 @@ public class WorldUtil return null; } + public static Vec3d getRayStart( EntityLivingBase entity ) + { + return new Vec3d( entity.posX, entity.posY + entity.getEyeHeight(), entity.posZ ); + } + + public static Vec3d getRayEnd( EntityPlayer player) { + double reach = 4.5; + if( player instanceof EntityPlayerMP ) + { + reach = ((EntityPlayerMP) player).interactionManager.getBlockReachDistance(); + } + else if( player.getEntityWorld().isRemote ) + { + reach = Minecraft.getMinecraft().playerController.getBlockReachDistance(); + } + else if( player.capabilities.isCreativeMode ) + { + reach = 5.0; + } + + Vec3d look = player.getLookVec(); + + return getRayStart( player ).addVector( look.x * reach, look.y * reach, look.z * reach ); + } + + public static boolean isVecInsideInclusive(AxisAlignedBB bb , Vec3d vec) { + return vec.x >= bb.minX && vec.x <= bb.maxX && vec.y >= bb.minY && vec.y <= bb.maxY && vec.z >= bb.minZ && vec.z <= bb.maxZ; + } + public static void dropItemStack( @Nonnull ItemStack stack, World world, BlockPos pos ) { dropItemStack( stack, world, pos, null );