diff --git a/src/main/java/li/cil/oc2/client/model/BusCableBakedModel.java b/src/main/java/li/cil/oc2/client/model/BusCableBakedModel.java index a2123823..c6948f1a 100644 --- a/src/main/java/li/cil/oc2/client/model/BusCableBakedModel.java +++ b/src/main/java/li/cil/oc2/client/model/BusCableBakedModel.java @@ -35,7 +35,7 @@ public final class BusCableBakedModel implements IDynamicBakedModel { @Override public List getQuads(@Nullable final BlockState state, @Nullable final Direction side, final Random rand, final IModelData extraData) { - if (state == null) { + if (state == null || !state.get(BusCableBlock.HAS_CABLE)) { return proxy.getQuads(null, side, rand, extraData); } @@ -96,7 +96,7 @@ public final class BusCableBakedModel implements IDynamicBakedModel { for (final Direction direction : Constants.DIRECTIONS) { if (isNeighborInDirectionSolid(world, pos, direction)) { final EnumProperty property = BusCableBlock.FACING_TO_CONNECTION_MAP.get(direction); - if (state.hasProperty(property) && state.get(property) == BusCableBlock.ConnectionType.PLUG) { + if (state.hasProperty(property) && state.get(property) == BusCableBlock.ConnectionType.INTERFACE) { return tileData; // Plug is already supporting us, bail. } @@ -126,7 +126,7 @@ public final class BusCableBakedModel implements IDynamicBakedModel { for (final Direction direction : Constants.DIRECTIONS) { final EnumProperty property = BusCableBlock.FACING_TO_CONNECTION_MAP.get(direction); if (axis.test(direction)) { - if (state.get(property) != BusCableBlock.ConnectionType.LINK) { + if (state.get(property) != BusCableBlock.ConnectionType.CABLE) { return false; } } else { diff --git a/src/main/java/li/cil/oc2/common/block/BusCableBlock.java b/src/main/java/li/cil/oc2/common/block/BusCableBlock.java index dccf98c5..556f76f7 100644 --- a/src/main/java/li/cil/oc2/common/block/BusCableBlock.java +++ b/src/main/java/li/cil/oc2/common/block/BusCableBlock.java @@ -16,41 +16,46 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.BlockItemUseContext; import net.minecraft.item.ItemStack; import net.minecraft.loot.LootContext; +import net.minecraft.state.BooleanProperty; import net.minecraft.state.EnumProperty; import net.minecraft.state.StateContainer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.*; +import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.util.math.shapes.ISelectionContext; import net.minecraft.util.math.shapes.VoxelShape; import net.minecraft.util.math.shapes.VoxelShapes; import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.util.math.vector.Vector3i; import net.minecraft.world.GameRules; import net.minecraft.world.IBlockReader; import net.minecraft.world.IWorld; import net.minecraft.world.World; +import net.minecraftforge.common.util.Constants.BlockFlags; import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; public final class BusCableBlock extends Block { public enum ConnectionType implements IStringSerializable { NONE, - LINK, - PLUG; + CABLE, + INTERFACE; @Override public String getString() { switch (this) { case NONE: return "none"; - case LINK: - return "link"; - case PLUG: - return "plug"; + case CABLE: + return "cable"; + case INTERFACE: + return "interface"; default: throw new IllegalArgumentException(); } @@ -59,6 +64,7 @@ public final class BusCableBlock extends Block { /////////////////////////////////////////////////////////////////// + public static final BooleanProperty HAS_CABLE = BooleanProperty.create("has_cable"); public static final EnumProperty CONNECTION_NORTH = EnumProperty.create("connection_north", ConnectionType.class); public static final EnumProperty CONNECTION_EAST = EnumProperty.create("connection_east", ConnectionType.class); public static final EnumProperty CONNECTION_SOUTH = EnumProperty.create("connection_south", ConnectionType.class); @@ -99,6 +105,7 @@ public final class BusCableBlock extends Block { for (final EnumProperty property : FACING_TO_CONNECTION_MAP.values()) { defaultState = defaultState.with(property, ConnectionType.NONE); } + defaultState = defaultState.with(HAS_CABLE, true); setDefaultState(defaultState); shapes = makeShapes(); @@ -106,18 +113,29 @@ public final class BusCableBlock extends Block { /////////////////////////////////////////////////////////////////// - public boolean addPlug(final World world, final BlockPos pos, final BlockState state, final Direction side) { - final EnumProperty property = BusCableBlock.FACING_TO_CONNECTION_MAP.get(side); - if (state.get(property) == BusCableBlock.ConnectionType.NONE) { - if (!world.isRemote()) { - world.setBlockState(pos, state.with(property, ConnectionType.PLUG)); - onConnectionTypeChanged(world, pos, side); - WorldUtils.playSound(world, pos, state.getSoundType(), SoundType::getPlaceSound); - } - return true; + public boolean addInterface(final World world, final BlockPos pos, final BlockState state, final Direction side) { + final EnumProperty property = FACING_TO_CONNECTION_MAP.get(side); + if (state.get(property) != ConnectionType.NONE) { + return false; } - return false; + world.setBlockState(pos, state.with(property, ConnectionType.INTERFACE), BlockFlags.DEFAULT_AND_RERENDER); + + onConnectionTypeChanged(world, pos, side); + + return true; + } + + public boolean addCable(final World world, final BlockPos pos, final BlockState state) { + if (state.get(HAS_CABLE)) { + return false; + } + + world.setBlockState(pos, state.with(HAS_CABLE, true), BlockFlags.DEFAULT_AND_RERENDER); + + onConnectionTypeChanged(world, pos, null); + + return true; } @Override @@ -144,11 +162,17 @@ public final class BusCableBlock extends Block { @SuppressWarnings("deprecation") @Override public ActionResultType onBlockActivated(final BlockState state, final World world, final BlockPos pos, final PlayerEntity player, final Hand hand, final BlockRayTraceResult hit) { - if (Wrenches.isWrench(player.getHeldItem(hand)) && tryRemovePlug(state, world, pos, player, hit)) { - return ActionResultType.SUCCESS; + if (player.isSneaking() && Wrenches.isWrench(player.getHeldItem(hand))) { + // NB: leave wrenching logic up to wrench when the to-be-removed interface is the last + // part of this bus. This ensures we properly remove the block itself without having + // to duplicate the logic needed for that. + if (getPartCount(state) > 1) + if (tryRemovePlug(state, world, pos, player, hit) || tryRemoveCable(state, world, pos, player)) { + return ActionResultType.func_233537_a_(world.isRemote()); + } } - return ActionResultType.PASS; + return super.onBlockActivated(state, world, pos, player, hand, hit); } @SuppressWarnings("deprecation") @@ -156,16 +180,20 @@ public final class BusCableBlock extends Block { public List getDrops(final BlockState state, final LootContext.Builder builder) { final List drops = new ArrayList<>(super.getDrops(state, builder)); - int plugCount = 0; + if (state.get(HAS_CABLE)) { + drops.add(new ItemStack(Items.BUS_CABLE.get())); + } + + int interfaceCount = 0; for (final Direction side : Constants.DIRECTIONS) { final ConnectionType connectionType = state.get(FACING_TO_CONNECTION_MAP.get(side)); - if (connectionType == ConnectionType.PLUG) { - plugCount++; + if (connectionType == ConnectionType.INTERFACE) { + interfaceCount++; } } - if (plugCount > 0) { - drops.add(new ItemStack(Items.BUS_INTERFACE.get(), plugCount)); + if (interfaceCount > 0) { + drops.add(new ItemStack(Items.BUS_INTERFACE.get(), interfaceCount)); } return drops; @@ -180,8 +208,9 @@ public final class BusCableBlock extends Block { for (final Map.Entry> entry : FACING_TO_CONNECTION_MAP.entrySet()) { final Direction facing = entry.getKey(); final BlockPos facingPos = position.offset(facing); - if (isCableBlock(world.getBlockState(facingPos))) { - state = state.with(entry.getValue(), ConnectionType.LINK); + if (context.getItem().getItem() == Items.BUS_CABLE.get() && + canHaveCableTo(world.getBlockState(facingPos), facing.getOpposite())) { + state = state.with(entry.getValue(), ConnectionType.CABLE); } } @@ -191,11 +220,16 @@ public final class BusCableBlock extends Block { @SuppressWarnings("deprecation") @Override public BlockState updatePostPlacement(BlockState state, final Direction facing, final BlockState facingState, final IWorld world, final BlockPos currentPos, final BlockPos facingPos) { - if (isCableBlock(facingState)) { - state = state.with(FACING_TO_CONNECTION_MAP.get(facing), ConnectionType.LINK); - } else if (state.get(FACING_TO_CONNECTION_MAP.get(facing)) != ConnectionType.PLUG) { + if (state.get(FACING_TO_CONNECTION_MAP.get(facing)) == ConnectionType.INTERFACE) { + return state; + } + + if (state.get(HAS_CABLE) && canHaveCableTo(facingState, facing.getOpposite())) { + state = state.with(FACING_TO_CONNECTION_MAP.get(facing), ConnectionType.CABLE); + } else { state = state.with(FACING_TO_CONNECTION_MAP.get(facing), ConnectionType.NONE); } + onConnectionTypeChanged(world, currentPos, facing); return state; @@ -213,62 +247,22 @@ public final class BusCableBlock extends Block { protected void fillStateContainer(final StateContainer.Builder builder) { super.fillStateContainer(builder); FACING_TO_CONNECTION_MAP.values().forEach(builder::add); + builder.add(HAS_CABLE); } /////////////////////////////////////////////////////////////////// - private VoxelShape[] makeShapes() { - final VoxelShape coreShape = Block.makeCuboidShape(5, 5, 5, 11, 11, 11); - final VoxelShape[] connectionShapes = new VoxelShape[Constants.DIRECTIONS.length]; - for (int i = 0; i < Constants.DIRECTIONS.length; i++) { - final Direction direction = Constants.DIRECTIONS[i]; - connectionShapes[i] = VoxelShapes.create( - 0.5 + Math.min((-2.0 / (16 - 6)), direction.getXOffset() * 0.5), - 0.5 + Math.min((-2.0 / (16 - 6)), direction.getYOffset() * 0.5), - 0.5 + Math.min((-2.0 / (16 - 6)), direction.getZOffset() * 0.5), - 0.5 + Math.max(2.0 / (16 - 6), direction.getXOffset() * 0.5), - 0.5 + Math.max(2.0 / (16 - 6), direction.getYOffset() * 0.5), - 0.5 + Math.max(2.0 / (16 - 6), direction.getZOffset() * 0.5)); - } - - final VoxelShape[] result = new VoxelShape[1 << 6]; - for (int i = 0; i < result.length; i++) { - VoxelShape shape = coreShape; - - for (int j = 0; j < Constants.DIRECTIONS.length; j++) { - if ((i & (1 << j)) != 0) { - shape = VoxelShapes.or(shape, connectionShapes[j]); - } - } - - result[i] = shape; - } - - return result; + private boolean canHaveCableTo(final BlockState state, final Direction side) { + return state.getBlock() == this && state.get(HAS_CABLE) && state.get(FACING_TO_CONNECTION_MAP.get(side)) != ConnectionType.INTERFACE; } - private int getShapeIndex(final BlockState state) { - int index = 0; - - for (int i = 0; i < Constants.DIRECTIONS.length; i++) { - if (state.get(FACING_TO_CONNECTION_MAP.get(Constants.DIRECTIONS[i])) != ConnectionType.NONE) { - index |= 1 << i; - } - } - - return index; - } - - private boolean isCableBlock(final BlockState state) { - return state.getBlock() == this; - } - - private void onConnectionTypeChanged(final IWorld world, final BlockPos pos, final Direction face) { - final TileEntity tileEntity = world.getTileEntity(pos); - if (tileEntity instanceof BusCableTileEntity) { - final BusCableTileEntity busCable = (BusCableTileEntity) tileEntity; - busCable.handleConnectionTypeChanged(face); + private static int getPartCount(final BlockState state) { + int partCount = 0; + if (state.get(HAS_CABLE)) partCount++; + for (final EnumProperty connectionType : FACING_TO_CONNECTION_MAP.values()) { + if (state.get(connectionType) == ConnectionType.INTERFACE) partCount++; } + return partCount; } private boolean tryRemovePlug(final BlockState state, final World world, final BlockPos pos, final PlayerEntity player, final BlockRayTraceResult hit) { @@ -276,27 +270,169 @@ public final class BusCableBlock extends Block { final Direction side = Direction.getFacingFromVector(localHitPos.x, localHitPos.y, localHitPos.z); final EnumProperty property = FACING_TO_CONNECTION_MAP.get(side); - if (state.get(property) != ConnectionType.PLUG) { + if (state.get(property) != ConnectionType.INTERFACE) { return false; } final BlockPos neighborPos = pos.offset(side); - if (isCableBlock(world.getBlockState(neighborPos))) { - world.setBlockState(pos, state.with(property, ConnectionType.LINK)); + if (state.get(HAS_CABLE) && canHaveCableTo(world.getBlockState(neighborPos), side.getOpposite())) { + world.setBlockState(pos, state.with(property, ConnectionType.CABLE)); } else { world.setBlockState(pos, state.with(property, ConnectionType.NONE)); } + + handlePartRemoved(state, world, pos, side, player, new ItemStack(Items.BUS_INTERFACE.get())); + + return true; + } + + private boolean tryRemoveCable(final BlockState state, final World world, final BlockPos pos, final PlayerEntity player) { + if (!state.get(HAS_CABLE)) { + return false; + } + + world.setBlockState(pos, state.with(HAS_CABLE, false)); + + handlePartRemoved(state, world, pos, null, player, new ItemStack(Items.BUS_CABLE.get())); + + return true; + } + + private void handlePartRemoved(final BlockState state, final World world, final BlockPos pos, @Nullable final Direction side, final PlayerEntity player, final ItemStack drop) { onConnectionTypeChanged(world, pos, side); if (!player.isCreative() && world.getGameRules().getBoolean(GameRules.DO_TILE_DROPS)) { - ItemStackUtils.spawnAsEntity(world, pos, new ItemStack(Items.BUS_INTERFACE.get()), side).ifPresent(entity -> { + ItemStackUtils.spawnAsEntity(world, pos, drop, side).ifPresent(entity -> { entity.setNoPickupDelay(); entity.onCollideWithPlayer(player); }); } WorldUtils.playSound(world, pos, state.getSoundType(), SoundType::getBreakSound); + } - return true; + private void onConnectionTypeChanged(final IWorld world, final BlockPos pos, @Nullable final Direction face) { + final TileEntity tileEntity = world.getTileEntity(pos); + if (tileEntity instanceof BusCableTileEntity) { + final BusCableTileEntity busCable = (BusCableTileEntity) tileEntity; + busCable.handleConnectivityChanged(face); + } + } + + private static VoxelShape[] makeShapes() { + final VoxelShape ownCableBounds = Block.makeCuboidShape(5, 5, 5, 11, 11, 11); + final VoxelShape[] cableShapes = new VoxelShape[Constants.BLOCK_FACE_COUNT]; + final VoxelShape[] interfaceShapes = new VoxelShape[Constants.BLOCK_FACE_COUNT]; + for (int i = 0; i < Constants.BLOCK_FACE_COUNT; i++) { + cableShapes[i] = getCableShape(Constants.DIRECTIONS[i]); + interfaceShapes[i] = getInterfaceShape(Constants.DIRECTIONS[i]); + } + + // We pack info as such: + // [has interface on side] [has cable on side] [has own cable] + // Which is a total of 13 bits (6 + 6 + 1), so 8k combinations. + // It's a lot, but not so much that it's not ok to still cache + // it and avoid recomputing the bounds all the time. + + final int configurations = 1 << (6 + 6 + 1); + final VoxelShape[] result = new VoxelShape[configurations]; + Arrays.fill(result, VoxelShapes.empty()); + + for (int i = 0; i < result.length; i++) { + final int mask = i >> 1; + for (int sideIndex = 0; sideIndex < Constants.BLOCK_FACE_COUNT; sideIndex++) { + final int cableBit = 1 << sideIndex; + if ((mask & cableBit) != 0) { + result[i] = VoxelShapes.or(result[i], cableShapes[sideIndex]); + } + + final int interfaceBit = cableBit << 6; + if ((mask & interfaceBit) != 0) { + result[i] = VoxelShapes.or(result[i], interfaceShapes[sideIndex]); + } + } + + if ((i & 1) != 0) { + result[i] = VoxelShapes.or(result[i], ownCableBounds); + } + } + + return result; + } + + + private static VoxelShape getCableShape(final Direction zDirection) { + final int xSize = 6; + final int ySize = 6; + final int zSize = 5; + + final Direction yDirection = zDirection.getAxis() == Direction.Axis.Y ? Direction.NORTH : Direction.UP; + final Direction xDirection = zDirection.getAxis() == Direction.Axis.Y ? Direction.WEST : zDirection.rotateY(); + + final Vector3i min = new Vector3i(8, 8, 8) + .offset(xDirection, -xSize / 2) + .offset(yDirection, -ySize / 2) + .offset(zDirection, 8 - zSize); + final Vector3i max = new Vector3i(8, 8, 8) + .offset(xDirection, xSize / 2) + .offset(yDirection, ySize / 2) + .offset(zDirection, 8); + + final AxisAlignedBB bounds = new AxisAlignedBB( + Vector3d.copy(min).scale(1 / 16.0), + Vector3d.copy(max).scale(1 / 16.0) + ); + + return VoxelShapes.create(bounds); + } + + private static VoxelShape getInterfaceShape(final Direction zDirection) { + final int xSize = 8; + final int ySize = 8; + final int zSize = 1; + + final Direction yDirection = zDirection.getAxis() == Direction.Axis.Y ? Direction.NORTH : Direction.UP; + final Direction xDirection = zDirection.getAxis() == Direction.Axis.Y ? Direction.WEST : zDirection.rotateY(); + + final Vector3i min = new Vector3i(8, 8, 8) + .offset(xDirection, -xSize / 2) + .offset(yDirection, -ySize / 2) + .offset(zDirection, 8 - zSize); + final Vector3i max = new Vector3i(8, 8, 8) + .offset(xDirection, xSize / 2) + .offset(yDirection, ySize / 2) + .offset(zDirection, 8); + + final AxisAlignedBB bounds = new AxisAlignedBB( + Vector3d.copy(min).scale(1 / 16.0), + Vector3d.copy(max).scale(1 / 16.0) + ); + + return VoxelShapes.or(getCableShape(zDirection), VoxelShapes.create(bounds)); + } + + private static int getShapeIndex(final BlockState state) { + int index = 0; + + for (int sideIndex = 0; sideIndex < Constants.BLOCK_FACE_COUNT; sideIndex++) { + final int cableBit = 1 << sideIndex; + final int interfaceBit = cableBit << 6; + switch (state.get(FACING_TO_CONNECTION_MAP.get(Constants.DIRECTIONS[sideIndex]))) { + case CABLE: + index |= cableBit; + break; + case INTERFACE: + index |= interfaceBit; + break; + } + } + + index = index << 1; + + if (state.get(HAS_CABLE)) { + index |= 1; + } + + return index; } } diff --git a/src/main/java/li/cil/oc2/common/entity/RobotEntity.java b/src/main/java/li/cil/oc2/common/entity/RobotEntity.java index 807325cd..6ea681b9 100644 --- a/src/main/java/li/cil/oc2/common/entity/RobotEntity.java +++ b/src/main/java/li/cil/oc2/common/entity/RobotEntity.java @@ -61,6 +61,7 @@ import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.common.util.Constants.BlockFlags; import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.event.world.WorldEvent; @@ -270,7 +271,7 @@ public final class RobotEntity extends Entity implements Robot { .withParameter(LootParameters.BLOCK_STATE, blockState) .withNullableParameter(LootParameters.BLOCK_ENTITY, tileEntity); final List drops = blockState.getDrops(builder); - world.setBlockState(mutablePosition, Blocks.AIR.getDefaultState(), 3); + world.setBlockState(mutablePosition, Blocks.AIR.getDefaultState()); for (final ItemStack drop : drops) { Block.spawnAsEntity(world, mutablePosition, drop); } diff --git a/src/main/java/li/cil/oc2/common/item/BusCableItem.java b/src/main/java/li/cil/oc2/common/item/BusCableItem.java new file mode 100644 index 00000000..93c6b627 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/item/BusCableItem.java @@ -0,0 +1,66 @@ +package li.cil.oc2.common.item; + +import li.cil.oc2.common.block.Blocks; +import li.cil.oc2.common.block.BusCableBlock; +import li.cil.oc2.common.util.WorldUtils; +import net.minecraft.advancements.CriteriaTriggers; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.SoundType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.item.BlockItemUseContext; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUseContext; +import net.minecraft.util.ActionResultType; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public final class BusCableItem extends ModBlockItem { + public BusCableItem(final Block block) { + super(block); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public ActionResultType onItemUse(final ItemUseContext context) { + final ActionResultType result = tryAddToBlock(context); + return result.isSuccessOrConsume() ? result : super.onItemUse(context); + } + + @Override + public ActionResultType tryPlace(final BlockItemUseContext context) { + final ActionResultType result = tryAddToBlock(context); + return result.isSuccessOrConsume() ? result : super.tryPlace(context); + } + + /////////////////////////////////////////////////////////////////// + + private ActionResultType tryAddToBlock(final ItemUseContext context) { + final BusCableBlock busCableBlock = Blocks.BUS_CABLE.get(); + + final World world = context.getWorld(); + final BlockPos pos = context.getPos(); + final BlockState state = world.getBlockState(pos); + + if (state.getBlock() == busCableBlock && busCableBlock.addCable(world, pos, state)) { + final PlayerEntity player = context.getPlayer(); + final ItemStack stack = context.getItem(); + + if (player instanceof ServerPlayerEntity) { + CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayerEntity) player, pos, stack); + } + + WorldUtils.playSound(world, pos, state.getSoundType(world, pos, player), SoundType::getPlaceSound); + + if (player == null || !player.abilities.isCreativeMode) { + stack.shrink(1); + } + + return ActionResultType.func_233537_a_(world.isRemote); + } + + return ActionResultType.PASS; + } +} diff --git a/src/main/java/li/cil/oc2/common/item/BusInterfaceItem.java b/src/main/java/li/cil/oc2/common/item/BusInterfaceItem.java index 0a014a34..35d3cedd 100644 --- a/src/main/java/li/cil/oc2/common/item/BusInterfaceItem.java +++ b/src/main/java/li/cil/oc2/common/item/BusInterfaceItem.java @@ -2,47 +2,85 @@ package li.cil.oc2.common.item; import li.cil.oc2.common.block.Blocks; import li.cil.oc2.common.block.BusCableBlock; +import li.cil.oc2.common.block.BusCableBlock.ConnectionType; +import li.cil.oc2.common.util.WorldUtils; +import net.minecraft.advancements.CriteriaTriggers; +import net.minecraft.block.Block; import net.minecraft.block.BlockState; +import net.minecraft.block.SoundType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.item.BlockItemUseContext; +import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUseContext; +import net.minecraft.state.EnumProperty; import net.minecraft.util.ActionResultType; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public final class BusInterfaceItem extends ModBlockItem { + public BusInterfaceItem(final Block block) { + super(block); + } + + /////////////////////////////////////////////////////////////////// -public final class BusInterfaceItem extends ModItem { @Override public ActionResultType onItemUse(final ItemUseContext context) { + final Vector3d localHitPos = context.getHitVec().subtract(Vector3d.copyCentered(context.getPos())); + final Direction side = Direction.getFacingFromVector(localHitPos.x, localHitPos.y, localHitPos.z); + final ActionResultType result = tryAddToBlock(context, side); + return result.isSuccessOrConsume() ? result : super.onItemUse(context); + } + + @Override + public ActionResultType tryPlace(final BlockItemUseContext context) { + final ActionResultType result = tryAddToBlock(context, context.getFace().getOpposite()); + return result.isSuccessOrConsume() ? result : super.tryPlace(context); + } + + /////////////////////////////////////////////////////////////////// + + @Nullable + @Override + protected BlockState getStateForPlacement(final BlockItemUseContext context) { + final BlockState state = super.getStateForPlacement(context); + final EnumProperty connectionTypeProperty = + BusCableBlock.FACING_TO_CONNECTION_MAP.get(context.getFace().getOpposite()); + return state + .with(BusCableBlock.HAS_CABLE, false) + .with(connectionTypeProperty, ConnectionType.INTERFACE); + } + + /////////////////////////////////////////////////////////////////// + + private ActionResultType tryAddToBlock(final ItemUseContext context, final Direction side) { + final BusCableBlock busCableBlock = Blocks.BUS_CABLE.get(); + final World world = context.getWorld(); final BlockPos pos = context.getPos(); final BlockState state = world.getBlockState(pos); - final BusCableBlock busCableBlock = Blocks.BUS_CABLE.get(); - if (state.getBlock() == busCableBlock) { - final Vector3d localHitPos = context.getHitVec().subtract(Vector3d.copyCentered(pos)); - final Direction side = Direction.getFacingFromVector(localHitPos.x, localHitPos.y, localHitPos.z); - if (busCableBlock.addPlug(world, pos, state, side)) { - context.getItem().shrink(1); - return ActionResultType.SUCCESS; - } - } else { - final BlockPos neighborPos = pos.offset(context.getFace()); - final BlockState neighborState = world.getBlockState(neighborPos); + if (state.getBlock() == busCableBlock && busCableBlock.addInterface(world, pos, state, side)) { + final PlayerEntity player = context.getPlayer(); + final ItemStack stack = context.getItem(); - // TODO Store in cable whether cable itself is present, i.e. allow bus blocks that are just plugs. -// if (neighborState.isReplaceable(new BlockItemUseContext(context))) { -// world.setBlockState(neighborPos, Blocks.BUS_CABLE.get().getDefaultState()); -// } - - if (neighborState.getBlock() == busCableBlock) { - final Direction side = context.getFace().getOpposite(); - if (busCableBlock.addPlug(world, neighborPos, neighborState, side)) { - context.getItem().shrink(1); - return ActionResultType.SUCCESS; - } + if (player instanceof ServerPlayerEntity) { + CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayerEntity) player, pos, stack); } + + WorldUtils.playSound(world, pos, state.getSoundType(world, pos, player), SoundType::getPlaceSound); + + if (player == null || !player.abilities.isCreativeMode) { + stack.shrink(1); + } + + return ActionResultType.func_233537_a_(world.isRemote); } - return super.onItemUse(context); + return ActionResultType.PASS; } } diff --git a/src/main/java/li/cil/oc2/common/item/Items.java b/src/main/java/li/cil/oc2/common/item/Items.java index fe6ecb6f..9959c7fb 100644 --- a/src/main/java/li/cil/oc2/common/item/Items.java +++ b/src/main/java/li/cil/oc2/common/item/Items.java @@ -10,6 +10,7 @@ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.ForgeRegistries; +import java.util.function.Function; import java.util.function.Supplier; public final class Items { @@ -18,7 +19,8 @@ public final class Items { /////////////////////////////////////////////////////////////////// public static final RegistryObject COMPUTER = register(Constants.COMPUTER_BLOCK_NAME, Blocks.COMPUTER); - public static final RegistryObject BUS_CABLE = register(Constants.BUS_CABLE_BLOCK_NAME, Blocks.BUS_CABLE); + public static final RegistryObject BUS_CABLE = register(Constants.BUS_CABLE_BLOCK_NAME, Blocks.BUS_CABLE, BusCableItem::new); + public static final RegistryObject BUS_INTERFACE = register(Constants.BUS_INTERFACE_ITEM_NAME, Blocks.BUS_CABLE, BusInterfaceItem::new); public static final RegistryObject NETWORK_CONNECTOR = register(Constants.NETWORK_CONNECTOR_BLOCK_NAME, Blocks.NETWORK_CONNECTOR); public static final RegistryObject NETWORK_HUB = register(Constants.NETWORK_HUB_BLOCK_NAME, Blocks.NETWORK_HUB); public static final RegistryObject REDSTONE_INTERFACE = register(Constants.REDSTONE_INTERFACE_BLOCK_NAME, Blocks.REDSTONE_INTERFACE); @@ -28,8 +30,7 @@ public final class Items { public static final RegistryObject WRENCH = register(Constants.WRENCH_ITEM_NAME, WrenchItem::new); - public static final RegistryObject BUS_INTERFACE = register(Constants.BUS_INTERFACE_ITEM_NAME, BusInterfaceItem::new); - public static final RegistryObject NETWORK_CABLE = register(Constants.NETWORK_CABLE_ITEM_NAME, NetworkCableItem::new); + public static final RegistryObject NETWORK_CABLE = register(Constants.NETWORK_CABLE_ITEM_NAME, NetworkCableItem::new); public static final RegistryObject ROBOT = register(Constants.ROBOT_ENTITY_NAME, RobotItem::new); public static final RegistryObject MEMORY = register(Constants.MEMORY_ITEM_NAME, MemoryItem::new); @@ -59,6 +60,10 @@ public final class Items { } private static RegistryObject register(final String name, final RegistryObject block) { - return register(name, () -> new ModBlockItem(block.get())); + return register(name, block, ModBlockItem::new); + } + + private static RegistryObject register(final String name, final RegistryObject block, final Function factory) { + return register(name, () -> factory.apply(block.get())); } } diff --git a/src/main/java/li/cil/oc2/common/item/WrenchItem.java b/src/main/java/li/cil/oc2/common/item/WrenchItem.java index 8d754d47..9ca83d36 100644 --- a/src/main/java/li/cil/oc2/common/item/WrenchItem.java +++ b/src/main/java/li/cil/oc2/common/item/WrenchItem.java @@ -1,40 +1,43 @@ package li.cil.oc2.common.item; -import li.cil.oc2.api.API; -import li.cil.oc2.common.util.WorldUtils; +import li.cil.oc2.common.tags.BlockTags; import net.minecraft.block.BlockState; -import net.minecraft.block.SoundType; +import net.minecraft.client.Minecraft; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUseContext; -import net.minecraft.server.management.PlayerInteractionManager; import net.minecraft.util.ActionResultType; -import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IWorldReader; import net.minecraft.world.World; -import java.util.Objects; - public final class WrenchItem extends ModItem { @Override public ActionResultType onItemUse(final ItemUseContext context) { - final World world = context.getWorld(); - final BlockPos pos = context.getPos(); - final BlockState state = world.getBlockState(pos); - final ResourceLocation registryName = state.getBlock().getRegistryName(); - if (registryName == null || !Objects.equals(registryName.getNamespace(), API.MOD_ID)) { + final PlayerEntity player = context.getPlayer(); + if (!player.isSneaking()) { return super.onItemUse(context); } - final PlayerEntity player = context.getPlayer(); - if (player instanceof ServerPlayerEntity) { - final PlayerInteractionManager interactionManager = ((ServerPlayerEntity) player).interactionManager; - if (interactionManager.tryHarvestBlock(pos)) { - WorldUtils.playSound(world, pos, state.getSoundType(), SoundType::getBreakSound); - return ActionResultType.SUCCESS; - } + final World world = context.getWorld(); + final BlockPos pos = context.getPos(); + final BlockState state = world.getBlockState(pos); + if (!state.isIn(BlockTags.WRENCH_BREAKABLE)) { + return super.onItemUse(context); } - return super.onItemUse(context); + if (world.isRemote()) { + Minecraft.getInstance().playerController.onPlayerDestroyBlock(pos); + } else if (player instanceof ServerPlayerEntity) { + ((ServerPlayerEntity) player).interactionManager.tryHarvestBlock(pos); + } + + return ActionResultType.func_233537_a_(world.isRemote); + } + + @Override + public boolean doesSneakBypassUse(final ItemStack stack, final IWorldReader world, final BlockPos pos, final PlayerEntity player) { + return true; } } diff --git a/src/main/java/li/cil/oc2/common/tags/BlockTags.java b/src/main/java/li/cil/oc2/common/tags/BlockTags.java index c47b0bef..0f271751 100644 --- a/src/main/java/li/cil/oc2/common/tags/BlockTags.java +++ b/src/main/java/li/cil/oc2/common/tags/BlockTags.java @@ -8,6 +8,7 @@ import net.minecraftforge.common.Tags; public final class BlockTags { public static final Tags.IOptionalNamedTag DEVICES = tag("devices"); public static final Tags.IOptionalNamedTag CABLES = tag("cables"); + public static final Tags.IOptionalNamedTag WRENCH_BREAKABLE = tag("wrench_breakable"); /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/common/tileentity/BusCableTileEntity.java b/src/main/java/li/cil/oc2/common/tileentity/BusCableTileEntity.java index 41290bfe..93c01a2e 100644 --- a/src/main/java/li/cil/oc2/common/tileentity/BusCableTileEntity.java +++ b/src/main/java/li/cil/oc2/common/tileentity/BusCableTileEntity.java @@ -36,9 +36,16 @@ public final class BusCableTileEntity extends AbstractTileEntity { requestModelDataUpdate(); } - public void handleConnectionTypeChanged(final Direction side) { - invalidateCapability(Capabilities.DEVICE_BUS_ELEMENT, side); - handleNeighborChanged(getPos().offset(side)); + public void handleConnectivityChanged(@Nullable final Direction side) { + if (side == null) { + busElement.scheduleScan(); + + // TODO Remove if https://github.com/MinecraftForge/MinecraftForge/pull/7595 gets merged. + requestModelDataUpdate(); + } else { + invalidateCapability(Capabilities.DEVICE_BUS_ELEMENT, side); + handleNeighborChanged(getPos().offset(side)); + } } @Override @@ -102,14 +109,14 @@ public final class BusCableTileEntity extends AbstractTileEntity { @Override public boolean canScanContinueTowards(@Nullable final Direction direction) { final BusCableBlock.ConnectionType connectionType = BusCableBlock.getConnectionType(getBlockState(), direction); - return connectionType == BusCableBlock.ConnectionType.LINK || - connectionType == BusCableBlock.ConnectionType.PLUG; + return connectionType == BusCableBlock.ConnectionType.CABLE || + connectionType == BusCableBlock.ConnectionType.INTERFACE; } @Override public boolean canDetectDevicesTowards(@Nullable final Direction direction) { final BusCableBlock.ConnectionType connectionType = BusCableBlock.getConnectionType(getBlockState(), direction); - return connectionType == BusCableBlock.ConnectionType.PLUG; + return connectionType == BusCableBlock.ConnectionType.INTERFACE; } } } diff --git a/src/main/java/li/cil/oc2/common/util/ItemStackUtils.java b/src/main/java/li/cil/oc2/common/util/ItemStackUtils.java index 3ed85ca7..668822f0 100644 --- a/src/main/java/li/cil/oc2/common/util/ItemStackUtils.java +++ b/src/main/java/li/cil/oc2/common/util/ItemStackUtils.java @@ -106,11 +106,15 @@ public final class ItemStackUtils { return Optional.of(entity); } - public static Optional spawnAsEntity(final World world, final BlockPos pos, final ItemStack stack, final Direction direction) { + public static Optional spawnAsEntity(final World world, final BlockPos pos, final ItemStack stack, @Nullable final Direction direction) { return spawnAsEntity(world, Vector3d.copyCentered(pos), stack, direction); } - public static Optional spawnAsEntity(final World world, final Vector3d pos, final ItemStack stack, final Direction direction) { + public static Optional spawnAsEntity(final World world, final Vector3d pos, final ItemStack stack, @Nullable final Direction direction) { + if (direction == null) { + return spawnAsEntity(world, pos, stack); + } + if (world.isRemote() || stack.isEmpty()) { return Optional.empty(); } diff --git a/src/main/java/li/cil/oc2/data/ModBlockStateProvider.java b/src/main/java/li/cil/oc2/data/ModBlockStateProvider.java index 89396637..0d0db995 100644 --- a/src/main/java/li/cil/oc2/data/ModBlockStateProvider.java +++ b/src/main/java/li/cil/oc2/data/ModBlockStateProvider.java @@ -49,7 +49,12 @@ public class ModBlockStateProvider extends BlockStateProvider { // NB: We use a custom model loader + baked model to replace the base part with straight parts and // insert supports where appropriate. - builder.part().modelFile(baseModel).addModel().end(); + + builder.part() + .modelFile(baseModel) + .addModel() + .condition(BusCableBlock.HAS_CABLE, true) + .end(); BusCableBlock.FACING_TO_CONNECTION_MAP.forEach((direction, connectionType) -> { final int rotationY = (int) direction.getHorizontalAngle(); @@ -67,7 +72,7 @@ public class ModBlockStateProvider extends BlockStateProvider { .rotationY(rotationY) .rotationX(rotationX) .addModel() - .condition(connectionType, BusCableBlock.ConnectionType.LINK) + .condition(connectionType, BusCableBlock.ConnectionType.CABLE) .end(); builder.part() @@ -75,7 +80,7 @@ public class ModBlockStateProvider extends BlockStateProvider { .rotationY(rotationY) .rotationX(rotationX) .addModel() - .condition(connectionType, BusCableBlock.ConnectionType.PLUG) + .condition(connectionType, BusCableBlock.ConnectionType.INTERFACE) .end(); }); diff --git a/src/main/java/li/cil/oc2/data/ModBlockTagsProvider.java b/src/main/java/li/cil/oc2/data/ModBlockTagsProvider.java index 10eec62c..8de1d0a9 100644 --- a/src/main/java/li/cil/oc2/data/ModBlockTagsProvider.java +++ b/src/main/java/li/cil/oc2/data/ModBlockTagsProvider.java @@ -7,8 +7,7 @@ import net.minecraftforge.common.data.ExistingFileHelper; import org.jetbrains.annotations.Nullable; import static li.cil.oc2.common.block.Blocks.*; -import static li.cil.oc2.common.tags.BlockTags.CABLES; -import static li.cil.oc2.common.tags.BlockTags.DEVICES; +import static li.cil.oc2.common.tags.BlockTags.*; public final class ModBlockTagsProvider extends BlockTagsProvider { public ModBlockTagsProvider(final DataGenerator generatorIn, @Nullable final ExistingFileHelper existingFileHelper) { @@ -22,6 +21,16 @@ public final class ModBlockTagsProvider extends BlockTagsProvider { REDSTONE_INTERFACE.get(), DISK_DRIVE.get() ); - getOrCreateBuilder(CABLES).add(BUS_CABLE.get()); + getOrCreateBuilder(CABLES).add( + BUS_CABLE.get() + ); + getOrCreateBuilder(WRENCH_BREAKABLE).add( + COMPUTER.get(), + BUS_CABLE.get(), + NETWORK_CONNECTOR.get(), + NETWORK_HUB.get(), + REDSTONE_INTERFACE.get(), + DISK_DRIVE.get() + ); } } diff --git a/src/main/java/li/cil/oc2/data/ModLootTableProvider.java b/src/main/java/li/cil/oc2/data/ModLootTableProvider.java index 9decf4bb..1498018d 100644 --- a/src/main/java/li/cil/oc2/data/ModLootTableProvider.java +++ b/src/main/java/li/cil/oc2/data/ModLootTableProvider.java @@ -42,7 +42,6 @@ public final class ModLootTableProvider extends LootTableProvider { public static final class ModBlockLootTables extends BlockLootTables { @Override protected void addTables() { - registerDropSelfLootTable(Blocks.BUS_CABLE.get()); registerDropSelfLootTable(Blocks.REDSTONE_INTERFACE.get()); registerDropSelfLootTable(Blocks.NETWORK_CONNECTOR.get()); registerDropSelfLootTable(Blocks.NETWORK_HUB.get()); @@ -55,6 +54,7 @@ public final class ModLootTableProvider extends LootTableProvider { protected Iterable getKnownBlocks() { return StreamSupport.stream(super.getKnownBlocks().spliterator(), false) .filter(block -> requireNonNull(block.getRegistryName()).getNamespace().equals(API.MOD_ID)) + .filter(block -> block != Blocks.BUS_CABLE.get()) // All bus drops depend on block state. .collect(Collectors.toList()); } diff --git a/src/main/resources/assets/oc2/blockstates/bus_cable.json b/src/main/resources/assets/oc2/blockstates/bus_cable.json index d6d1d937..ea735b47 100644 --- a/src/main/resources/assets/oc2/blockstates/bus_cable.json +++ b/src/main/resources/assets/oc2/blockstates/bus_cable.json @@ -1,13 +1,16 @@ { "multipart": [ { + "when": { + "has_cable": "true" + }, "apply": { "model": "oc2:block/cable_base" } }, { "when": { - "connection_down": "link" + "connection_down": "cable" }, "apply": { "model": "oc2:block/cable_link", @@ -17,7 +20,7 @@ }, { "when": { - "connection_down": "plug" + "connection_down": "interface" }, "apply": { "model": "oc2:block/cable_plug", @@ -27,7 +30,7 @@ }, { "when": { - "connection_up": "link" + "connection_up": "cable" }, "apply": { "model": "oc2:block/cable_link", @@ -37,7 +40,7 @@ }, { "when": { - "connection_up": "plug" + "connection_up": "interface" }, "apply": { "model": "oc2:block/cable_plug", @@ -47,7 +50,7 @@ }, { "when": { - "connection_north": "link" + "connection_north": "cable" }, "apply": { "model": "oc2:block/cable_link", @@ -56,7 +59,7 @@ }, { "when": { - "connection_north": "plug" + "connection_north": "interface" }, "apply": { "model": "oc2:block/cable_plug", @@ -65,7 +68,7 @@ }, { "when": { - "connection_south": "link" + "connection_south": "cable" }, "apply": { "model": "oc2:block/cable_link" @@ -73,7 +76,7 @@ }, { "when": { - "connection_south": "plug" + "connection_south": "interface" }, "apply": { "model": "oc2:block/cable_plug" @@ -81,7 +84,7 @@ }, { "when": { - "connection_west": "link" + "connection_west": "cable" }, "apply": { "model": "oc2:block/cable_link", @@ -90,7 +93,7 @@ }, { "when": { - "connection_west": "plug" + "connection_west": "interface" }, "apply": { "model": "oc2:block/cable_plug", @@ -99,7 +102,7 @@ }, { "when": { - "connection_east": "link" + "connection_east": "cable" }, "apply": { "model": "oc2:block/cable_link", @@ -108,7 +111,7 @@ }, { "when": { - "connection_east": "plug" + "connection_east": "interface" }, "apply": { "model": "oc2:block/cable_plug", diff --git a/src/main/resources/data/oc2/loot_tables/blocks/bus_cable.json b/src/main/resources/data/oc2/loot_tables/blocks/bus_cable.json deleted file mode 100644 index 0a7a1982..00000000 --- a/src/main/resources/data/oc2/loot_tables/blocks/bus_cable.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "minecraft:block", - "pools": [ - { - "rolls": 1, - "entries": [ - { - "type": "minecraft:item", - "name": "oc2:bus_cable" - } - ], - "conditions": [ - { - "condition": "minecraft:survives_explosion" - } - ] - } - ] -} \ No newline at end of file diff --git a/src/main/resources/data/oc2/tags/blocks/wrench_breakable.json b/src/main/resources/data/oc2/tags/blocks/wrench_breakable.json new file mode 100644 index 00000000..8896a027 --- /dev/null +++ b/src/main/resources/data/oc2/tags/blocks/wrench_breakable.json @@ -0,0 +1,11 @@ +{ + "replace": false, + "values": [ + "oc2:computer", + "oc2:bus_cable", + "oc2:network_connector", + "oc2:network_hub", + "oc2:redstone_interface", + "oc2:disk_drive" + ] +} \ No newline at end of file