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;
|
||||
|
||||
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<? extends BaseEntityBlock> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
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