mirror of
https://github.com/XevianLight/Aphelion.git
synced 2026-05-11 01:50:56 +01:00
added a "PipeGraph" so pipes can keep track of what they're connected to
This commit is contained in:
@@ -1,11 +1,16 @@
|
|||||||
package net.xevianlight.aphelion.block.custom;
|
package net.xevianlight.aphelion.block.custom;
|
||||||
|
|
||||||
|
import com.mojang.serialization.MapCodec;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||||
import net.minecraft.world.level.BlockGetter;
|
import net.minecraft.world.level.BlockGetter;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.LevelAccessor;
|
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.Block;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.block.state.StateDefinition;
|
import net.minecraft.world.level.block.state.StateDefinition;
|
||||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
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.CollisionContext;
|
||||||
import net.minecraft.world.phys.shapes.Shapes;
|
import net.minecraft.world.phys.shapes.Shapes;
|
||||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
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
|
// 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
|
// shortcuts for each directional property because they're pretty verbose
|
||||||
// t/f here means is/isn't connected outgoing in that direction
|
// 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) {
|
public PipeTestBlock(Properties properties) {
|
||||||
super(properties);
|
super(properties, true);
|
||||||
this.registerDefaultState(this.stateDefinition.any()
|
this.registerDefaultState(this.stateDefinition.any()
|
||||||
.setValue(NORTH, false).setValue(SOUTH, false)
|
.setValue(NORTH, false).setValue(SOUTH, false)
|
||||||
.setValue(EAST, false).setValue(WEST, false)
|
.setValue(EAST, false).setValue(WEST, false)
|
||||||
.setValue(UP, false).setValue(DOWN, false));
|
.setValue(UP, false).setValue(DOWN, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method determines the state when first placed
|
@Override
|
||||||
|
protected MapCodec<? extends BaseEntityBlock> codec() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method determines the state; called when first placed
|
||||||
@Override
|
@Override
|
||||||
public BlockState getStateForPlacement(BlockPlaceContext context) {
|
public BlockState getStateForPlacement(BlockPlaceContext context) {
|
||||||
return makeConnections(context.getLevel(), context.getClickedPos());
|
return makeConnections(context.getLevel(), context.getClickedPos());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates the block when a neighbor changes
|
// Updates the block; called when a neighbor changes
|
||||||
@Override
|
@Override
|
||||||
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos currentPos, BlockPos neighborPos) {
|
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos currentPos, BlockPos neighborPos) {
|
||||||
return makeConnections(level, currentPos);
|
return makeConnections(level, currentPos);
|
||||||
@@ -78,17 +95,41 @@ public class PipeTestBlock extends Block {
|
|||||||
|
|
||||||
private BlockState makeConnections(LevelAccessor level, BlockPos pos) {
|
private BlockState makeConnections(LevelAccessor level, BlockPos pos) {
|
||||||
return this.defaultBlockState()
|
return this.defaultBlockState()
|
||||||
.setValue(NORTH, canConnect(level, pos.north()))
|
.setValue(NORTH, canConnect(level, pos.north(), Direction.SOUTH))
|
||||||
.setValue(SOUTH, canConnect(level, pos.south()))
|
.setValue(SOUTH, canConnect(level, pos.south(), Direction.NORTH))
|
||||||
.setValue(EAST, canConnect(level, pos.east()))
|
.setValue(EAST, canConnect(level, pos.east(), Direction.WEST))
|
||||||
.setValue(WEST, canConnect(level, pos.west()))
|
.setValue(WEST, canConnect(level, pos.west(), Direction.EAST))
|
||||||
.setValue(UP, canConnect(level, pos.above()))
|
.setValue(UP, canConnect(level, pos.above(), Direction.DOWN))
|
||||||
.setValue(DOWN, canConnect(level, pos.below()));
|
.setValue(DOWN, canConnect(level, pos.below(), Direction.UP));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canConnect(LevelAccessor level, BlockPos neighborPos) {
|
/// If a PipeTestBlock can connect to this position from the given direction.
|
||||||
// Simplest logic: connect if the neighbor is also a SimplePipeBlock
|
/// If you're going to the NORTH of yourself, you should be accessing the SOUTH side.
|
||||||
return level.getBlockState(neighborPos).getBlock() == this;
|
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
|
@Override
|
||||||
@@ -122,4 +163,9 @@ public class PipeTestBlock extends Block {
|
|||||||
public float getShadeBrightness(BlockState state, BlockGetter world, BlockPos pos) {
|
public float getShadeBrightness(BlockState state, BlockGetter world, BlockPos pos) {
|
||||||
return 1.0F; // Maintains full brightness
|
return 1.0F; // Maintains full brightness
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) {
|
||||||
|
return new PipeTestBlockEntity(blockPos, blockState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<PipeTestBlockEntity> pipes = new ArrayList<>();
|
||||||
|
public Set<PipeOutput> 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<BlockPos> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,4 +51,9 @@ public class ModBlockEntities {
|
|||||||
BLOCK_ENTITIES.register("rocket_assembler_block_entity", () -> BlockEntityType.Builder.of(
|
BLOCK_ENTITIES.register("rocket_assembler_block_entity", () -> BlockEntityType.Builder.of(
|
||||||
RocketAssemblerBlockEntity::new, ModBlocks.ROCKET_ASSEMBLER_BLOCK.get()).build(null)
|
RocketAssemblerBlockEntity::new, ModBlocks.ROCKET_ASSEMBLER_BLOCK.get()).build(null)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public static final Supplier<BlockEntityType<PipeTestBlockEntity>> PIPE_TEST_BLOCK_ENTITY =
|
||||||
|
BLOCK_ENTITIES.register("pipe_test_block_entity", () -> BlockEntityType.Builder.of(
|
||||||
|
PipeTestBlockEntity::new, ModBlocks.PIPE_TEST_BLOCK.get()).build(null)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user