diff --git a/src/main/java/net/xevianlight/aphelion/block/custom/PipeTestBlock.java b/src/main/java/net/xevianlight/aphelion/block/custom/PipeTestBlock.java index 90bf565..4516634 100644 --- a/src/main/java/net/xevianlight/aphelion/block/custom/PipeTestBlock.java +++ b/src/main/java/net/xevianlight/aphelion/block/custom/PipeTestBlock.java @@ -1,11 +1,16 @@ package net.xevianlight.aphelion.block.custom; +import com.mojang.serialization.MapCodec; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.BaseEntityBlock; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BlockStateProperties; @@ -14,9 +19,16 @@ import net.minecraft.world.phys.shapes.BooleanOp; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; +import net.neoforged.neoforge.capabilities.Capabilities; +import net.xevianlight.aphelion.block.custom.base.BasicEntityBlock; +import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity; +import net.xevianlight.aphelion.block.entity.custom.PipeTestBlockEntity; +import net.xevianlight.aphelion.core.init.ModBlocks; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; // a lot of this is ai slop so take it with a grainlet of salt -public class PipeTestBlock extends Block { +public class PipeTestBlock extends BasicEntityBlock { // shortcuts for each directional property because they're pretty verbose // t/f here means is/isn't connected outgoing in that direction @@ -53,20 +65,25 @@ public class PipeTestBlock extends Block { } public PipeTestBlock(Properties properties) { - super(properties); + super(properties, true); this.registerDefaultState(this.stateDefinition.any() .setValue(NORTH, false).setValue(SOUTH, false) .setValue(EAST, false).setValue(WEST, false) .setValue(UP, false).setValue(DOWN, false)); } - // This method determines the state when first placed + @Override + protected MapCodec codec() { + return null; + } + + // This method determines the state; called when first placed @Override public BlockState getStateForPlacement(BlockPlaceContext context) { return makeConnections(context.getLevel(), context.getClickedPos()); } - // Updates the block when a neighbor changes + // Updates the block; called when a neighbor changes @Override public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos currentPos, BlockPos neighborPos) { return makeConnections(level, currentPos); @@ -78,17 +95,41 @@ public class PipeTestBlock extends Block { private BlockState makeConnections(LevelAccessor level, BlockPos pos) { return this.defaultBlockState() - .setValue(NORTH, canConnect(level, pos.north())) - .setValue(SOUTH, canConnect(level, pos.south())) - .setValue(EAST, canConnect(level, pos.east())) - .setValue(WEST, canConnect(level, pos.west())) - .setValue(UP, canConnect(level, pos.above())) - .setValue(DOWN, canConnect(level, pos.below())); + .setValue(NORTH, canConnect(level, pos.north(), Direction.SOUTH)) + .setValue(SOUTH, canConnect(level, pos.south(), Direction.NORTH)) + .setValue(EAST, canConnect(level, pos.east(), Direction.WEST)) + .setValue(WEST, canConnect(level, pos.west(), Direction.EAST)) + .setValue(UP, canConnect(level, pos.above(), Direction.DOWN)) + .setValue(DOWN, canConnect(level, pos.below(), Direction.UP)); } - private boolean canConnect(LevelAccessor level, BlockPos neighborPos) { - // Simplest logic: connect if the neighbor is also a SimplePipeBlock - return level.getBlockState(neighborPos).getBlock() == this; + /// If a PipeTestBlock can connect to this position from the given direction. + /// If you're going to the NORTH of yourself, you should be accessing the SOUTH side. + public static boolean canConnect(LevelAccessor levelA, BlockPos neighborPos, Direction accessSide) { + // Methinks this is not the best way to test this. + boolean isPipe = levelA.getBlockState(neighborPos).is(ModBlocks.PIPE_TEST_BLOCK.get()); + + /// This code is AI, but I think it works? I think the reason it's a levelAccessor instead of a level + /// is that we're not sure if we're in, for example, an inventory slot or not. + /// Either way, this should only trigger when it makes sense (assuming this is correct in the first place) + boolean isInventory; + if (levelA instanceof Level level) { + isInventory = level.getCapability(Capabilities.ItemHandler.BLOCK, neighborPos, accessSide) != null; + } else { + isInventory = false; + } + + return isPipe || isInventory; + } + + + @Override + public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { + BlockEntity BE = level.getBlockEntity(pos); + if (BE instanceof PipeTestBlockEntity pipe) { + // force everything connected to this pipe to reevaluate + if (pipe.graph != null) pipe.graph.invalidate(); + } } @Override @@ -122,4 +163,9 @@ public class PipeTestBlock extends Block { public float getShadeBrightness(BlockState state, BlockGetter world, BlockPos pos) { return 1.0F; // Maintains full brightness } + + @Override + public @Nullable BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) { + return new PipeTestBlockEntity(blockPos, blockState); + } } diff --git a/src/main/java/net/xevianlight/aphelion/block/entity/custom/PipeTestBlockEntity.java b/src/main/java/net/xevianlight/aphelion/block/entity/custom/PipeTestBlockEntity.java new file mode 100644 index 0000000..0fd7886 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/block/entity/custom/PipeTestBlockEntity.java @@ -0,0 +1,110 @@ +package net.xevianlight.aphelion.block.entity.custom; + +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.HopperBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.fml.common.Mod; +import net.xevianlight.aphelion.Aphelion; +import net.xevianlight.aphelion.block.custom.PipeTestBlock; +import net.xevianlight.aphelion.block.custom.base.TickableBlockEntity; +import net.xevianlight.aphelion.core.init.ModBlockEntities; +import net.xevianlight.aphelion.core.init.ModBlocks; +import net.xevianlight.aphelion.util.FloodFill3D; +import org.apache.commons.lang3.NotImplementedException; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class PipeTestBlockEntity extends BlockEntity implements TickableBlockEntity { + public PipeTestBlockEntity(BlockPos pos, BlockState blockState) { + super(ModBlockEntities.PIPE_TEST_BLOCK_ENTITY.get(), pos, blockState); + } + + @Override + public void clientTick(ClientLevel level, long time, BlockState state, BlockPos pos) { + + } + + @Override + public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) { + if (this.graph == null) initGraph(level, pos); + } + + @Override + public void firstTick(Level level, BlockState state, BlockPos pos) { + + } + + //TODO: move all this somewhere else + public interface PipeInput { + void tick(Level level, BlockState state, BlockPos pos, Direction facingDirection); + } + + public interface PipeOutput { + ItemStack insertItem(ItemStack stack, boolean simulate); + } + + public static class PipeGraph { + boolean isInvalid = false; + public List pipes = new ArrayList<>(); + public Set outputs = new HashSet<>(); + + public ItemStack insertItem(ItemStack stack, boolean simulate) { + if (isInvalid) return stack; + for (PipeOutput output : outputs) { + stack = output.insertItem(stack, simulate); + if (stack.isEmpty()) break; + } + return stack; + } + + public void addPipe(PipeTestBlockEntity pipe) { + //TODO: add outputs the pipe has already if applicable + pipes.add(pipe); + pipe.graph = this; + } + + /// Called whenever a pipe is removed from a graph, or when a new graph comes across an old one. + public void invalidate() { + for (PipeTestBlockEntity pipe : pipes) { + pipe.graph = null; + } + this.isInvalid = true; + } + } + + public @Nullable PipeGraph graph = null; + + // Simplest implementation I can think of + public static void initGraph(Level level, BlockPos pos) { + Aphelion.LOGGER.info("Init graph from {}", pos); + if (!level.getBlockState(pos).is(ModBlocks.PIPE_TEST_BLOCK.get())) return; + Set pipes = FloodFill3D.run(level, pos, 1000, (var v1, var v2, var state, var v4, var v5, var v6) -> state.is(ModBlocks.PIPE_TEST_BLOCK.get()), false); + + Aphelion.LOGGER.info("Got {} pipes", pipes.size()); + PipeGraph graph = new PipeGraph(); + for (BlockPos pipePos : pipes) { + BlockEntity BE = level.getBlockEntity(pipePos); + if (BE instanceof PipeTestBlockEntity pipe) { + // Invalidate any old graphs + if (pipe.graph != null) { + pipe.graph.invalidate(); + } + + graph.addPipe(pipe); + } + } + } +} diff --git a/src/main/java/net/xevianlight/aphelion/core/init/ModBlockEntities.java b/src/main/java/net/xevianlight/aphelion/core/init/ModBlockEntities.java index 600035d..885806f 100644 --- a/src/main/java/net/xevianlight/aphelion/core/init/ModBlockEntities.java +++ b/src/main/java/net/xevianlight/aphelion/core/init/ModBlockEntities.java @@ -51,4 +51,9 @@ public class ModBlockEntities { BLOCK_ENTITIES.register("rocket_assembler_block_entity", () -> BlockEntityType.Builder.of( RocketAssemblerBlockEntity::new, ModBlocks.ROCKET_ASSEMBLER_BLOCK.get()).build(null) ); + + public static final Supplier> PIPE_TEST_BLOCK_ENTITY = + BLOCK_ENTITIES.register("pipe_test_block_entity", () -> BlockEntityType.Builder.of( + PipeTestBlockEntity::new, ModBlocks.PIPE_TEST_BLOCK.get()).build(null) + ); }