filename:
common/src/main/java/rearth/oritech/block/blocks/pipes/GenericPipeBlock.java
branch:
1.21
back to repo
package rearth.oritech.block.blocks.pipes;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.sound.SoundCategory;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.BooleanProperty;
import net.minecraft.state.property.IntProperty;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import rearth.oritech.block.entity.pipes.GenericPipeInterfaceEntity;
import rearth.oritech.item.tools.Wrench;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public abstract class GenericPipeBlock extends AbstractPipeBlock implements Wrench.Wrenchable {
// 0 = no connection, 1 = connection (pipe->pipe or pipe->machine)
public static int NO_CONNECTION = 0;
public static int CONNECTION = 1;
public static final IntProperty NORTH = IntProperty.of("north", 0, 1);
public static final IntProperty EAST = IntProperty.of("east", 0, 1);
public static final IntProperty SOUTH = IntProperty.of("south", 0, 1);
public static final IntProperty WEST = IntProperty.of("west", 0, 1);
public static final IntProperty UP = IntProperty.of("up", 0, 1);
public static final IntProperty DOWN = IntProperty.of("down", 0, 1);
public static final BooleanProperty STRAIGHT = BooleanProperty.of("straight");
public GenericPipeBlock(Settings settings) {
super(settings);
this.setDefaultState(getDefaultState()
.with(getNorthProperty(), 0)
.with(getEastProperty(), 0)
.with(getSouthProperty(), 0)
.with(getWestProperty(), 0)
.with(getUpProperty(), 0)
.with(getDownProperty(), 0)
.with(STRAIGHT, false));
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(getNorthProperty(), getEastProperty(), getSouthProperty(), getWestProperty(), getUpProperty(), getDownProperty(), STRAIGHT);
}
protected VoxelShape getShape(BlockState state) {
var shape = boundingShapes[0];
if (state.get(getNorthProperty()) != NO_CONNECTION)
shape = VoxelShapes.union(shape, boundingShapes[1]);
if (state.get(getEastProperty()) != NO_CONNECTION)
shape = VoxelShapes.union(shape, boundingShapes[2]);
if (state.get(getSouthProperty()) != NO_CONNECTION)
shape = VoxelShapes.union(shape, boundingShapes[3]);
if (state.get(getWestProperty()) != NO_CONNECTION)
shape = VoxelShapes.union(shape, boundingShapes[4]);
if (state.get(getUpProperty()) != NO_CONNECTION)
shape = VoxelShapes.union(shape, boundingShapes[5]);
if (state.get(getDownProperty()) != NO_CONNECTION)
shape = VoxelShapes.union(shape, boundingShapes[6]);
return shape;
}
protected VoxelShape[] createShapes() {
VoxelShape inner = Block.createCuboidShape(5, 5, 5, 11, 11, 11);
VoxelShape north = Block.createCuboidShape(5, 5, 0, 11, 11, 5);
VoxelShape east = Block.createCuboidShape(0, 5, 5, 5, 11, 11);
VoxelShape south = Block.createCuboidShape(5, 5, 11, 11, 11, 16);
VoxelShape west = Block.createCuboidShape(11, 5, 5, 16, 11, 11);
VoxelShape up = Block.createCuboidShape(5, 11, 5, 11, 16, 11);
VoxelShape down = Block.createCuboidShape(5, 0, 5, 11, 5, 11);
return new VoxelShape[]{inner, north, west, south, east, up, down};
}
@Override
public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
if (oldState.getBlock().equals(state.getBlock())) return;
else if (oldState.isOf(getConnectionBlock().getBlock())) {
GenericPipeInterfaceEntity.addNode(world, pos, false, state, getNetworkData(world));
return;
}
// transform to interface block on placement when machine is neighbor
if (hasNeighboringMachine(state, world, pos, true)) {
var connectionBlock = getConnectionBlock();
var interfaceState = ((GenericPipeBlock) connectionBlock.getBlock()).addConnectionStates(connectionBlock, world, pos, true);
world.setBlockState(pos, interfaceState);
} else {
// no states need to be added (see getPlacementState)
GenericPipeInterfaceEntity.addNode(world, pos, false, state, getNetworkData(world));
}
updateNeighbors(world, pos, false);
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess worldAccess, BlockPos pos, BlockPos neighborPos) {
var world = (World) worldAccess;
if (world.isClient) return state;
// transform to interface when machine is placed as neighbor
if (hasMachineInDirection(direction, world, pos, apiValidationFunction())) {
// Only update if the neighbor is a new machine
var hasMachine = getNetworkData(world).machinePipeNeighbors.getOrDefault(neighborPos, HashSet.newHashSet(0)).contains(direction.getOpposite());
if (hasMachine) return state;
var connectionBlock = getConnectionBlock();
return ((GenericPipeBlock) connectionBlock.getBlock()).addConnectionStates(connectionBlock, world, pos, direction);
} else if (neighborState.isOf(Blocks.AIR))
// remove potential stale machine -> neighboring pipes mapping
getNetworkData(world).machinePipeNeighbors.remove(neighborPos);
return state;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
super.onStateReplaced(state, world, pos, newState, moved);
if (!state.isOf(newState.getBlock()) && !(newState.getBlock() instanceof GenericPipeBlock)) {
// block was removed/replaced instead of updated
onBlockRemoved(pos, state, world);
}
}
/**
* Updates all the neighboring pipes of the target position.
*
* @param world The target world
* @param pos The target position
* @param neighborToggled Whether the neighbor was toggled
*/
public void updateNeighbors(World world, BlockPos pos, boolean neighborToggled) {
for (var direction : Direction.values()) {
var neighborPos = pos.offset(direction);
var neighborState = world.getBlockState(neighborPos);
// Only update pipes
if (neighborState.getBlock() instanceof AbstractPipeBlock pipeBlock) {
var updatedState = pipeBlock.addConnectionStates(neighborState, world, neighborPos, false);
world.setBlockState(neighborPos, updatedState);
// Update network data if the state was changed
if (!neighborState.equals(updatedState) || pipeBlock instanceof GenericPipeDuctBlock) {
boolean interfaceBlock = updatedState.isOf(getConnectionBlock().getBlock());
if (neighborToggled)
GenericPipeInterfaceEntity.addNode(world, neighborPos, interfaceBlock, updatedState, getNetworkData(world));
}
}
}
}
@Override
public BlockState onBreak(World world, BlockPos pos, BlockState state, PlayerEntity player) {
if (!player.isCreative() && !world.isClient) {
onBlockRemoved(pos, state, world);
}
return super.onBreak(world, pos, state, player);
}
@Override
public ActionResult onWrenchUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand) {
if (player.isSneaking()) {
world.breakBlock(pos, true, player);
return ActionResult.SUCCESS;
}
return !toggleSideConnection(state, getInteractDirection(state, pos, player), world, pos) ? ActionResult.FAIL : ActionResult.SUCCESS;
}
@Override
public ActionResult onWrenchUseNeighbor(BlockState state, BlockState neighborState, World world, BlockPos pos, BlockPos neighborPos, Direction neighborFace, PlayerEntity player, Hand hand) {
return toggleSideConnection(state, neighborFace.getOpposite(), world, pos) ? ActionResult.SUCCESS : ActionResult.FAIL;
}
protected Direction getInteractDirection(BlockState state, BlockPos pos, PlayerEntity player) {
var shapes = getActiveShapes(state);
var start = player.getCameraPosVec(0f);
var end = start.add(player.getRotationVec(0).multiply(5));
var targetShape = shapes.getFirst();
var distance = Double.MAX_VALUE;
var hitPos = Vec3d.ZERO;
for (var shape : shapes) {
var hitResult = shape.raycast(start, end, pos);
if (hitResult == null) continue;
var shapeDistance = hitResult.getPos().distanceTo(start);
if (shapeDistance < distance) {
distance = shapeDistance;
targetShape = shape;
hitPos = hitResult.getPos();
}
}
var center = targetShape.getBoundingBox().getCenter();
var diff = center.subtract(shapes.getFirst().getBoundingBox().getCenter());
if (diff.equals(Vec3d.ZERO))
// center hit
diff = hitPos.subtract(center.add(Vec3d.of(pos)));
return Direction.getFacing(diff.x, diff.y, diff.z);
}
private List<VoxelShape> getActiveShapes(BlockState state) {
var shapes = new ArrayList<VoxelShape>();
shapes.add(boundingShapes[0]);
if (state.get(getNorthProperty()) != NO_CONNECTION)
shapes.add(boundingShapes[1]);
if (state.get(getEastProperty()) != NO_CONNECTION)
shapes.add(boundingShapes[2]);
if (state.get(getSouthProperty()) != NO_CONNECTION)
shapes.add(boundingShapes[3]);
if (state.get(getWestProperty()) != NO_CONNECTION)
shapes.add(boundingShapes[4]);
if (state.get(getUpProperty()) != NO_CONNECTION)
shapes.add(boundingShapes[5]);
if (state.get(getDownProperty()) != NO_CONNECTION)
shapes.add(boundingShapes[6]);
return shapes;
}
/**
* Toggles the connection state of a pipe side between disabled and enabled.
*
* @param state The current pipe block-state
* @param side The side to toggle the connection state
* @param world The target world
* @param pos The target pipe position
*/
protected boolean toggleSideConnection(BlockState state, Direction side, World world, BlockPos pos) {
var property = directionToProperty(side);
var createConnection = state.get(property) == NO_CONNECTION;
// check if connection would be valid if state is toggled
var targetPos = pos.offset(side);
if (createConnection && !isValidConnectionTarget(world.getBlockState(targetPos).getBlock(), world, side.getOpposite(), targetPos))
return false;
// toggle connection state
int nextConnectionState = getNextConnectionState(state, side, world, pos, state.get(property));
var newState = addStraightState(state.with(property, nextConnectionState));
// transform to interface block if side is being enabled and machine is connected
if (!newState.isOf(getConnectionBlock().getBlock()) && createConnection && hasMachineInDirection(side, world, pos, apiValidationFunction())) {
var connectionState = getConnectionBlock();
var interfaceState = ((GenericPipeBlock) connectionState.getBlock()).addConnectionStates(connectionState, world, pos, side);
world.setBlockState(pos, interfaceState);
} else {
world.setBlockState(pos, newState);
GenericPipeInterfaceEntity.addNode(world, pos, false, newState, getNetworkData(world));
// update neighbor if it's a pipe
updateNeighbors(world, pos, true);
}
// play sound
var soundGroup = getSoundGroup(state);
world.playSound(null, pos, soundGroup.getPlaceSound(), SoundCategory.BLOCKS, soundGroup.getVolume() * .5f, soundGroup.getPitch());
return true;
}
/**
* Adds the connection states to the pipe block-state.
*
* @param state The current pipe block-state
* @param world The target world
* @param pos The target pipe position
* @param createConnection Whether to create a connection
* @return The updated block-state
*/
public BlockState addConnectionStates(BlockState state, World world, BlockPos pos, boolean createConnection) {
for (var direction : Direction.values()) {
var property = directionToProperty(direction);
var connection = shouldConnect(state, direction, pos, world, createConnection);
state = state.with(property, connection ? CONNECTION : NO_CONNECTION);
}
return addStraightState(state);
}
/**
* Adds the connection states to the pipe block-state.
* Attempts to create a connection ONLY in the specified direction.
* Useful for when only one connection needs to be created.
*
* @param state The current pipe block-state
* @param world The target world
* @param pos The target pipe position
* @param createDirection The direction to create a connection in
* @return The updated block-state
*/
public BlockState addConnectionStates(BlockState state, World world, BlockPos pos, Direction createDirection) {
for (var direction : Direction.values()) {
var property = directionToProperty(direction);
var connection = shouldConnect(state, direction, pos, world, direction.equals(createDirection));
state = state.with(property, connection ? CONNECTION : NO_CONNECTION);
}
return addStraightState(state);
}
/**
* Adds the straight property to the pipe block-state.
*
* @param state The current pipe block-state
* @return The updated block-state
*/
public BlockState addStraightState(BlockState state) {
var north = state.get(getNorthProperty()) != NO_CONNECTION;
var south = state.get(getSouthProperty()) != NO_CONNECTION;
var east = state.get(getEastProperty()) != NO_CONNECTION;
var west = state.get(getWestProperty()) != NO_CONNECTION;
var up = state.get(getUpProperty()) != NO_CONNECTION;
var down = state.get(getDownProperty()) != NO_CONNECTION;
// Check for straight connections along each axis
boolean straightX = north && south && !east && !west && !up && !down;
boolean straightY = up && down && !north && !south && !east && !west;
boolean straightZ = east && west && !north && !south && !up && !down;
// The pipe is straight if exactly one of the axes has a straight connection
var straight = straightX || straightY || straightZ;
return state.with(STRAIGHT, straight);
}
/**
* Check if the pipe should connect in a specific direction.
*
* @param current The current pipe block-state
* @param direction The direction to check
* @param currentPos The current pipe position
* @param world The target world
* @param createConnection Whether to create a connection
* @return Boolean whether the pipe should connect
*/
public boolean shouldConnect(BlockState current, Direction direction, BlockPos currentPos, World world, boolean createConnection) {
var targetPos = currentPos.offset(direction);
var targetState = world.getBlockState(targetPos);
// If creating a connection we don't check the other pipe's connection state, force the connection
// Otherwise we check if the other pipe is connecting in the opposite direction
if (createConnection) {
return isValidConnectionTarget(targetState.getBlock(), world, direction.getOpposite(), targetPos);
} else if (targetState.getBlock() instanceof AbstractPipeBlock pipeBlock) {
return pipeBlock.isConnectingInDirection(targetState, direction.getOpposite(), targetPos, world, false);
} else
return isConnectingInDirection(current, direction, currentPos, world, false) && isValidInterfaceTarget(targetState.getBlock(), world, direction.getOpposite(), targetPos);
}
/**
* Check if the pipe is connecting in a specific direction.
*
* @param current The target pipe block-state
* @param direction The direction to check
* @param createConnection Whether to create a connection
* @return Boolean whether the pipe is connecting
*/
public boolean isConnectingInDirection(BlockState current, Direction direction, BlockPos currentPos, World world, boolean createConnection) {
var block = current.getBlock();
if (!(block instanceof GenericPipeBlock pipeBlock)) return false;
var property = pipeBlock.directionToProperty(direction);
return current.get(property) >= CONNECTION || createConnection && current.get(property) == NO_CONNECTION;
}
/**
* Converts a {@link Direction} into an IntProperty value for a connection
*
* @param state State to pull the value from
* @param direction Respective direction
* @return the connection value
*/
public int directionToPropertyValue(BlockState state, Direction direction) {
if (direction == Direction.NORTH)
return state.get(getNorthProperty());
else if (direction == Direction.EAST)
return state.get(getEastProperty());
else if (direction == Direction.SOUTH)
return state.get(getSouthProperty());
else if (direction == Direction.WEST)
return state.get(getWestProperty());
else if (direction == Direction.UP)
return state.get(getUpProperty());
else return state.get(getDownProperty());
}
/**
* Converts a {@link Direction} into a {@link IntProperty} for a connection
*
* @param direction Respective direction
* @return the property
*/
public IntProperty directionToProperty(Direction direction) {
if (direction == Direction.NORTH)
return getNorthProperty();
else if (direction == Direction.EAST)
return getEastProperty();
else if (direction == Direction.SOUTH)
return getSouthProperty();
else if (direction == Direction.WEST)
return getWestProperty();
else if (direction == Direction.UP)
return getUpProperty();
else return getDownProperty();
}
protected int getNextConnectionState(BlockState state, Direction side, World world, BlockPos pos, int current) {
return current == NO_CONNECTION ? CONNECTION : NO_CONNECTION;
}
protected void onBlockRemoved(BlockPos pos, BlockState oldState, World world) {
updateNeighbors(world, pos, false);
GenericPipeInterfaceEntity.removeNode(world, pos, false, oldState, getNetworkData(world));
}
@Override
protected float getAmbientOcclusionLightLevel(BlockState state, BlockView world, BlockPos pos) {
return 1.0f;
}
/*
* The following is a hacky implementation to allow child classes to modify the connection properties
*/
public IntProperty getNorthProperty() {
return NORTH;
}
public IntProperty getEastProperty() {
return EAST;
}
public IntProperty getSouthProperty() {
return SOUTH;
}
public IntProperty getWestProperty() {
return WEST;
}
public IntProperty getUpProperty() {
return UP;
}
public IntProperty getDownProperty() {
return DOWN;
}
}