PrettyPipes/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlock.java

333 lines
14 KiB
Java
Raw Normal View History

2020-04-16 04:42:42 +02:00
package de.ellpeck.prettypipes.pipe;
2020-04-13 21:48:35 +02:00
import com.google.common.collect.ImmutableMap;
2024-03-07 16:06:46 +01:00
import com.mojang.serialization.MapCodec;
2020-10-17 14:29:37 +02:00
import de.ellpeck.prettypipes.Registry;
2020-04-14 01:38:48 +02:00
import de.ellpeck.prettypipes.Utility;
import de.ellpeck.prettypipes.items.IModule;
2020-04-14 04:21:28 +02:00
import de.ellpeck.prettypipes.network.PipeNetwork;
2021-12-02 12:31:04 +01:00
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
2021-12-02 14:44:26 +01:00
import net.minecraft.tags.FluidTags;
2021-12-02 12:31:04 +01:00
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
2021-12-02 14:44:26 +01:00
import net.minecraft.world.entity.LivingEntity;
2021-12-02 12:31:04 +01:00
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
2021-12-02 14:44:26 +01:00
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
2021-12-02 12:31:04 +01:00
import net.minecraft.world.level.Level;
2021-12-02 14:44:26 +01:00
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
2024-03-07 16:06:46 +01:00
import net.minecraft.world.level.block.state.BlockBehaviour;
2021-12-02 12:31:04 +01:00
import net.minecraft.world.level.block.state.BlockState;
2021-12-02 14:44:26 +01:00
import net.minecraft.world.level.block.state.StateDefinition;
2021-12-02 12:31:04 +01:00
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.EnumProperty;
2021-12-02 14:44:26 +01:00
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
2023-07-08 10:38:52 +02:00
import net.minecraft.world.level.material.MapColor;
2021-12-02 12:31:04 +01:00
import net.minecraft.world.phys.BlockHitResult;
2021-12-02 14:44:26 +01:00
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
2021-12-02 12:31:04 +01:00
import net.minecraft.world.phys.shapes.VoxelShape;
2024-02-03 22:01:43 +01:00
import net.neoforged.neoforge.capabilities.Capabilities;
2024-02-03 15:17:58 +01:00
import net.neoforged.neoforge.items.ItemHandlerHelper;
import org.apache.commons.lang3.mutable.MutableObject;
2020-10-17 00:58:31 +02:00
import org.apache.commons.lang3.tuple.Pair;
2020-04-13 21:48:35 +02:00
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
2020-04-13 21:48:35 +02:00
2021-12-02 12:31:04 +01:00
public class PipeBlock extends BaseEntityBlock {
2020-04-13 21:48:35 +02:00
2024-03-07 16:06:46 +01:00
public static final MapCodec<PipeBlock> CODEC = BlockBehaviour.simpleCodec(PipeBlock::new);
2020-04-13 21:48:35 +02:00
public static final Map<Direction, EnumProperty<ConnectionType>> DIRECTIONS = new HashMap<>();
2020-10-17 00:58:31 +02:00
private static final Map<Pair<BlockState, BlockState>, VoxelShape> SHAPE_CACHE = new HashMap<>();
private static final Map<Pair<BlockState, BlockState>, VoxelShape> COLL_SHAPE_CACHE = new HashMap<>();
2022-06-27 13:57:06 +02:00
private static final VoxelShape CENTER_SHAPE = Block.box(5, 5, 5, 11, 11, 11);
2020-04-13 22:54:18 +02:00
public static final Map<Direction, VoxelShape> DIR_SHAPES = ImmutableMap.<Direction, VoxelShape>builder()
2022-06-27 13:57:06 +02:00
.put(Direction.UP, Block.box(5, 10, 5, 11, 16, 11))
.put(Direction.DOWN, Block.box(5, 0, 5, 11, 6, 11))
.put(Direction.NORTH, Block.box(5, 5, 0, 11, 11, 6))
.put(Direction.SOUTH, Block.box(5, 5, 10, 11, 11, 16))
.put(Direction.EAST, Block.box(10, 5, 5, 16, 11, 11))
.put(Direction.WEST, Block.box(0, 5, 5, 6, 11, 11))
2020-04-13 21:48:35 +02:00
.build();
static {
2021-12-02 15:25:46 +01:00
for (var dir : Direction.values())
2022-06-27 13:57:06 +02:00
PipeBlock.DIRECTIONS.put(dir, EnumProperty.create(dir.getName(), ConnectionType.class));
2020-04-13 21:48:35 +02:00
}
2024-03-07 16:06:46 +01:00
public PipeBlock(Block.Properties properties) {
super(properties);
2020-04-13 21:48:35 +02:00
2021-12-02 15:25:46 +01:00
var state = this.defaultBlockState().setValue(BlockStateProperties.WATERLOGGED, false);
2022-06-27 13:57:06 +02:00
for (var prop : PipeBlock.DIRECTIONS.values())
2021-12-02 12:31:04 +01:00
state = state.setValue(prop, ConnectionType.DISCONNECTED);
this.registerDefaultState(state);
2020-04-13 21:48:35 +02:00
}
2020-04-14 01:38:48 +02:00
@Override
2021-12-02 12:31:04 +01:00
public InteractionResult use(BlockState state, Level worldIn, BlockPos pos, Player player, InteractionHand handIn, BlockHitResult result) {
2021-12-02 15:25:46 +01:00
var tile = Utility.getBlockEntity(PipeBlockEntity.class, worldIn, pos);
2020-04-14 01:38:48 +02:00
if (tile == null)
2021-12-02 12:31:04 +01:00
return InteractionResult.PASS;
if (!tile.canHaveModules())
2021-12-02 12:31:04 +01:00
return InteractionResult.PASS;
2021-12-02 15:25:46 +01:00
var stack = player.getItemInHand(handIn);
if (stack.getItem() instanceof IModule) {
2021-12-02 15:25:46 +01:00
var copy = stack.copy();
copy.setCount(1);
2021-12-02 15:25:46 +01:00
var remain = ItemHandlerHelper.insertItem(tile.modules, copy, false);
if (remain.isEmpty()) {
stack.shrink(1);
2021-12-02 12:31:04 +01:00
return InteractionResult.SUCCESS;
}
2021-12-02 12:31:04 +01:00
} else if (handIn == InteractionHand.MAIN_HAND && stack.isEmpty()) {
if (!worldIn.isClientSide)
2024-03-07 16:00:49 +01:00
player.openMenu(tile, pos);
2021-12-02 12:31:04 +01:00
return InteractionResult.SUCCESS;
}
2021-12-02 12:31:04 +01:00
return InteractionResult.PASS;
2020-04-14 01:38:48 +02:00
}
2020-04-13 21:48:35 +02:00
@Override
2021-12-02 14:44:26 +01:00
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
2022-06-27 13:57:06 +02:00
builder.add(PipeBlock.DIRECTIONS.values().toArray(new EnumProperty[0]));
builder.add(BlockStateProperties.WATERLOGGED);
}
@Override
2020-09-22 19:14:07 +02:00
public FluidState getFluidState(BlockState state) {
2021-12-02 14:44:26 +01:00
return state.getValue(BlockStateProperties.WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state);
2020-04-13 21:48:35 +02:00
}
@Override
2021-12-02 14:44:26 +01:00
public void neighborChanged(BlockState state, Level worldIn, BlockPos pos, Block blockIn, BlockPos fromPos, boolean isMoving) {
2021-12-02 15:25:46 +01:00
var newState = this.createState(worldIn, pos, state);
2020-04-14 01:38:48 +02:00
if (newState != state) {
2021-12-02 14:44:26 +01:00
worldIn.setBlockAndUpdate(pos, newState);
2022-06-27 13:57:06 +02:00
PipeBlock.onStateChanged(worldIn, pos, newState);
2020-04-14 01:38:48 +02:00
}
2020-04-13 21:48:35 +02:00
}
@Nullable
@Override
2021-12-02 14:44:26 +01:00
public BlockState getStateForPlacement(BlockPlaceContext context) {
return this.createState(context.getLevel(), context.getClickedPos(), this.defaultBlockState());
2020-04-13 21:48:35 +02:00
}
@Override
2021-12-02 14:44:26 +01:00
public BlockState updateShape(BlockState stateIn, Direction facing, BlockState facingState, LevelAccessor worldIn, BlockPos currentPos, BlockPos facingPos) {
if (stateIn.getValue(BlockStateProperties.WATERLOGGED))
worldIn.scheduleTick(currentPos, Fluids.WATER, Fluids.WATER.getTickDelay(worldIn));
return super.updateShape(stateIn, facing, facingState, worldIn, currentPos, facingPos);
}
2020-04-14 14:10:58 +02:00
@Override
2021-12-02 14:44:26 +01:00
public void setPlacedBy(Level worldIn, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
2022-06-27 13:57:06 +02:00
PipeBlock.onStateChanged(worldIn, pos, state);
2020-04-14 14:10:58 +02:00
}
2020-04-13 21:48:35 +02:00
@Override
2021-12-02 14:44:26 +01:00
public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) {
2022-06-27 13:57:06 +02:00
return this.cacheAndGetShape(state, worldIn, pos, s -> s.getShape(worldIn, pos, context), PipeBlock.SHAPE_CACHE, null);
}
@Override
2021-12-02 14:44:26 +01:00
public VoxelShape getCollisionShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) {
2022-06-27 13:57:06 +02:00
return this.cacheAndGetShape(state, worldIn, pos, s -> s.getCollisionShape(worldIn, pos, context), PipeBlock.COLL_SHAPE_CACHE, s -> {
// make the shape a bit higher so we can jump up onto a higher block
2021-12-02 15:25:46 +01:00
var newShape = new MutableObject<VoxelShape>(Shapes.empty());
2021-12-02 14:44:26 +01:00
s.forAllBoxes((x1, y1, z1, x2, y2, z2) -> newShape.setValue(Shapes.join(Shapes.create(x1, y1, z1, x2, y2 + 3 / 16F, z2), newShape.getValue(), BooleanOp.OR)));
return newShape.getValue().optimize();
});
}
2021-12-02 14:44:26 +01:00
private VoxelShape cacheAndGetShape(BlockState state, BlockGetter worldIn, BlockPos pos, Function<BlockState, VoxelShape> coverShapeSelector, Map<Pair<BlockState, BlockState>, VoxelShape> cache, Function<VoxelShape, VoxelShape> shapeModifier) {
2020-10-17 00:58:31 +02:00
VoxelShape coverShape = null;
BlockState cover = null;
2021-12-02 15:25:46 +01:00
var tile = Utility.getBlockEntity(PipeBlockEntity.class, worldIn, pos);
2020-10-17 00:58:31 +02:00
if (tile != null && tile.cover != null) {
cover = tile.cover;
// try catch since the block might expect to find itself at the position
try {
coverShape = coverShapeSelector.apply(cover);
2020-10-17 00:58:31 +02:00
} catch (Exception ignored) {
}
}
2021-12-02 15:25:46 +01:00
var key = Pair.of(state, cover);
var shape = cache.get(key);
2020-10-17 00:58:31 +02:00
if (shape == null) {
2022-06-27 13:57:06 +02:00
shape = PipeBlock.CENTER_SHAPE;
for (var entry : PipeBlock.DIRECTIONS.entrySet()) {
2021-12-02 14:44:26 +01:00
if (state.getValue(entry.getValue()).isConnected())
2022-06-27 13:57:06 +02:00
shape = Shapes.or(shape, PipeBlock.DIR_SHAPES.get(entry.getKey()));
2020-10-17 00:58:31 +02:00
}
if (shapeModifier != null)
shape = shapeModifier.apply(shape);
2020-10-17 00:58:31 +02:00
if (coverShape != null)
2021-12-02 14:44:26 +01:00
shape = Shapes.or(shape, coverShape);
cache.put(key, shape);
2020-04-13 21:48:35 +02:00
}
return shape;
}
2021-12-02 14:44:26 +01:00
private BlockState createState(Level world, BlockPos pos, BlockState curr) {
2021-12-02 15:25:46 +01:00
var state = this.defaultBlockState();
var fluid = world.getFluidState(pos);
2021-12-02 14:44:26 +01:00
if (fluid.is(FluidTags.WATER) && fluid.getAmount() == 8)
state = state.setValue(BlockStateProperties.WATERLOGGED, true);
2021-12-02 15:25:46 +01:00
for (var dir : Direction.values()) {
2022-06-27 13:57:06 +02:00
var prop = PipeBlock.DIRECTIONS.get(dir);
2021-12-02 15:25:46 +01:00
var type = this.getConnectionType(world, pos, dir, state);
2020-04-16 19:47:09 +02:00
// don't reconnect on blocked faces
2021-12-02 14:44:26 +01:00
if (type.isConnected() && curr.getValue(prop) == ConnectionType.BLOCKED)
type = ConnectionType.BLOCKED;
2021-12-02 14:44:26 +01:00
state = state.setValue(prop, type);
2020-04-13 22:54:18 +02:00
}
return state;
}
2021-12-02 14:44:26 +01:00
protected ConnectionType getConnectionType(Level world, BlockPos pos, Direction direction, BlockState state) {
2021-12-02 15:25:46 +01:00
var offset = pos.relative(direction);
2021-12-02 14:44:26 +01:00
if (!world.isLoaded(offset))
2020-04-13 22:54:18 +02:00
return ConnectionType.DISCONNECTED;
2021-12-02 15:25:46 +01:00
var opposite = direction.getOpposite();
var tile = world.getBlockEntity(offset);
2020-04-15 18:35:00 +02:00
if (tile != null) {
2024-03-07 18:21:05 +01:00
var connectable = world.getCapability(Registry.pipeConnectableCapability, offset, tile.getBlockState(), tile, opposite);
2020-10-17 14:29:37 +02:00
if (connectable != null)
return connectable.getConnectionType(pos, direction);
2024-03-07 18:21:05 +01:00
var handler = world.getCapability(Capabilities.ItemHandler.BLOCK, offset, tile.getBlockState(), tile, opposite);
2020-04-15 18:35:00 +02:00
if (handler != null)
return ConnectionType.CONNECTED;
2020-04-14 01:38:48 +02:00
}
2021-12-02 15:25:46 +01:00
var blockHandler = Utility.getBlockItemHandler(world, offset, opposite);
if (blockHandler != null)
return ConnectionType.CONNECTED;
2021-12-02 15:25:46 +01:00
var offState = world.getBlockState(offset);
2022-06-27 13:57:06 +02:00
if (PipeBlock.hasLegsTo(world, offState, offset, direction)) {
if (PipeBlock.DIRECTIONS.values().stream().noneMatch(d -> state.getValue(d) == ConnectionType.LEGS))
2020-04-16 19:47:09 +02:00
return ConnectionType.LEGS;
}
2020-04-15 18:35:00 +02:00
return ConnectionType.DISCONNECTED;
2020-04-13 22:54:18 +02:00
}
2021-12-02 14:44:26 +01:00
protected static boolean hasLegsTo(Level world, BlockState state, BlockPos pos, Direction direction) {
2020-04-16 19:47:09 +02:00
if (state.getBlock() instanceof WallBlock || state.getBlock() instanceof FenceBlock)
return direction == Direction.DOWN;
2024-02-03 22:01:43 +01:00
var mapColor = state.getMapColor(world, pos);
2023-07-08 10:38:52 +02:00
if (mapColor == MapColor.STONE || mapColor == MapColor.METAL)
return Block.canSupportCenter(world, pos, direction.getOpposite());
2020-04-16 19:47:09 +02:00
return false;
}
2021-12-02 14:44:26 +01:00
public static void onStateChanged(Level world, BlockPos pos, BlockState newState) {
// wait a few ticks before checking if we have to drop our modules, so that things like iron -> gold chest work
2021-12-02 15:25:46 +01:00
var tile = Utility.getBlockEntity(PipeBlockEntity.class, world, pos);
if (tile != null)
tile.moduleDropCheck = 5;
2020-04-14 04:21:28 +02:00
2021-12-02 15:25:46 +01:00
var network = PipeNetwork.get(world);
var connections = 0;
var force = false;
for (var dir : Direction.values()) {
2022-06-27 13:57:06 +02:00
var value = newState.getValue(PipeBlock.DIRECTIONS.get(dir));
2020-04-14 04:21:28 +02:00
if (!value.isConnected())
continue;
connections++;
2021-12-02 15:25:46 +01:00
var otherState = world.getBlockState(pos.relative(dir));
2020-04-19 17:38:13 +02:00
// force a node if we're connecting to a different block (inventory etc.)
if (otherState.getBlock() != newState.getBlock()) {
force = true;
2020-04-14 04:21:28 +02:00
break;
}
}
2020-04-19 17:38:13 +02:00
if (force || connections > 2) {
2020-04-14 04:21:28 +02:00
network.addNode(pos, newState);
} else {
network.removeNode(pos);
}
network.onPipeChanged(pos, newState);
2020-04-14 01:38:48 +02:00
}
2020-04-13 21:48:35 +02:00
2020-04-14 01:38:48 +02:00
@Override
2021-12-02 14:44:26 +01:00
public void onRemove(BlockState state, Level worldIn, BlockPos pos, BlockState newState, boolean isMoving) {
2020-04-14 01:38:48 +02:00
if (state.getBlock() != newState.getBlock()) {
2021-12-02 15:25:46 +01:00
var network = PipeNetwork.get(worldIn);
2020-04-14 04:21:28 +02:00
network.removeNode(pos);
network.onPipeChanged(pos, state);
2024-03-07 18:58:30 +01:00
if (worldIn.getBlockEntity(pos) instanceof PipeBlockEntity pipe) {
pipe.getItems().clear();
for (var lock : pipe.craftIngredientRequests)
network.resolveNetworkLock(lock);
}
2021-12-02 14:44:26 +01:00
super.onRemove(state, worldIn, pos, newState, isMoving);
2020-04-13 21:48:35 +02:00
}
2020-04-14 01:38:48 +02:00
}
2020-04-13 21:48:35 +02:00
@Override
2024-02-03 22:01:43 +01:00
public BlockState playerWillDestroy(Level worldIn, BlockPos pos, BlockState state, Player player) {
2022-06-27 13:57:06 +02:00
PipeBlock.dropItems(worldIn, pos, player);
2024-02-03 22:01:43 +01:00
return super.playerWillDestroy(worldIn, pos, state, player);
}
2020-05-03 15:33:56 +02:00
@Override
2021-12-02 14:44:26 +01:00
public boolean hasAnalogOutputSignal(BlockState state) {
2020-05-03 15:33:56 +02:00
return true;
}
@Override
2021-12-02 14:44:26 +01:00
public int getAnalogOutputSignal(BlockState state, Level world, BlockPos pos) {
2021-12-02 15:25:46 +01:00
var pipe = Utility.getBlockEntity(PipeBlockEntity.class, world, pos);
2020-05-03 15:33:56 +02:00
if (pipe == null)
return 0;
return Math.min(15, pipe.getItems().size());
}
2021-12-02 14:44:26 +01:00
@org.jetbrains.annotations.Nullable
2020-04-14 01:38:48 +02:00
@Override
2021-12-02 14:44:26 +01:00
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
2021-12-02 16:55:04 +01:00
return new PipeBlockEntity(pos, state);
2020-04-14 01:38:48 +02:00
}
2024-03-07 16:06:46 +01:00
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return PipeBlock.CODEC;
}
2020-04-14 01:38:48 +02:00
@Override
2021-12-02 14:44:26 +01:00
public RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
2020-04-13 21:48:35 +02:00
}
@org.jetbrains.annotations.Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) {
2022-06-27 13:57:06 +02:00
return BaseEntityBlock.createTickerHelper(type, Registry.pipeBlockEntity, PipeBlockEntity::tick);
}
2021-12-02 14:44:26 +01:00
public static void dropItems(Level worldIn, BlockPos pos, Player player) {
2021-12-02 15:25:46 +01:00
var tile = Utility.getBlockEntity(PipeBlockEntity.class, worldIn, pos);
if (tile != null) {
Utility.dropInventory(tile, tile.modules);
2021-12-02 15:25:46 +01:00
for (var item : tile.getItems())
item.drop(worldIn, item.getContent());
if (tile.cover != null)
2021-12-02 14:44:26 +01:00
tile.removeCover(player, InteractionHand.MAIN_HAND);
}
}
2024-02-03 22:01:43 +01:00
2020-04-13 21:48:35 +02:00
}