filename:
common/src/main/java/rearth/oritech/block/entity/accelerator/BlackHoleBlockEntity.java
branch:
1.21
back to repo
package rearth.oritech.block.entity.accelerator;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityTicker;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import rearth.oritech.Oritech;
import rearth.oritech.block.blocks.accelerator.AcceleratorPassthroughBlock;
import rearth.oritech.client.init.ParticleContent;
import rearth.oritech.init.BlockContent;
import rearth.oritech.init.BlockEntitiesContent;
import rearth.oritech.init.TagContent;
import rearth.oritech.network.NetworkContent;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class BlackHoleBlockEntity extends BlockEntity implements BlockEntityTicker<BlackHoleBlockEntity> {
public BlockPos currentlyPullingFrom;
public BlockState currentlyPulling;
public long pullingStartedAt;
public long pullTime;
// if nothing is in influence, don't search so often
private int waitTicks;
// cache for outgoing hits
private final Map<BlockPos, ParticleCollectorBlockEntity> cachedCollectors = new HashMap<>();
public BlackHoleBlockEntity(BlockPos pos, BlockState state) {
super(BlockEntitiesContent.BLACK_HOLE_ENTITY, pos, state);
}
@Override
public void tick(World world, BlockPos pos, BlockState state, BlackHoleBlockEntity blockEntity) {
if (world.isClient || waitTicks-- > 0) return;
if (currentlyPullingFrom != null && pullingStartedAt + pullTime - 5 < world.getTime()) {
onPullingFinished();
currentlyPullingFrom = null;
}
if (currentlyPullingFrom != null) return;
int pullRange = Oritech.CONFIG.pullRange();
for (var candidate : BlockPos.iterateOutwards(pos, pullRange, pullRange, pullRange)) {
var candidateState = world.getBlockState(candidate);
if (candidate.equals(pos) || candidateState.isAir() || candidateState.getFluidState().isStill() || candidateState.getBlock().equals(Blocks.MOVING_PISTON) || candidateState.getBlock().equals(BlockContent.BLACK_HOLE_BLOCK)) continue;
currentlyPullingFrom = candidate;
currentlyPulling = candidateState;
pullingStartedAt = world.getTime();
pullTime = (long) candidate.getManhattanDistance(pos) * Oritech.CONFIG.pullTimeMultiplier();
NetworkContent.MACHINE_CHANNEL.serverHandle(this).send(new NetworkContent.BlackHoleSuckPacket(pos, currentlyPullingFrom, pullingStartedAt, pullTime));
world.setBlockState(candidate, Blocks.AIR.getDefaultState());
return;
}
if (currentlyPullingFrom == null) {
waitTicks = Oritech.CONFIG.idleWaitTicks();
}
}
private void onPullingFinished() {
var from = currentlyPullingFrom;
var pulledDir = Vec3d.of(pos.subtract(from));
pulledDir = pulledDir.normalize();
for (int i = 0; i < 5; i++) {
var shootDir = pulledDir.addRandom(world.getRandom(), 0.5f);
var cacheKey = getRayEnd(pos.toCenterPos(), shootDir.normalize());
var cachedHit = tryGetCachedCollector(cacheKey);
if (cachedHit != null) {
// re-use existing result
ParticleContent.BLACK_HOLE_EMISSION.spawn(world, pos.toCenterPos(), cachedHit.getPos().toCenterPos());
cachedHit.onParticleCollided();
} else {
// find target along exit line, and add it to cache
var impactPos = basicRaycast(pos.toCenterPos().add(pulledDir.multiply(1.2)), shootDir, 12, world);
if (impactPos != null) {
ParticleContent.BLACK_HOLE_EMISSION.spawn(world, pos.toCenterPos(), impactPos.toCenterPos());
var candidate = world.getBlockEntity(impactPos);
if (candidate instanceof ParticleCollectorBlockEntity collectorEntity) {
collectorEntity.onParticleCollided();
cachedCollectors.put(cacheKey, collectorEntity);
} else {
// only cast one particle if no collector has been found (for performance sake to avoid all those searches)
break;
}
} else {
// only cast one particle if no block has been found (for performance sake to avoid all those searches)
ParticleContent.BLACK_HOLE_EMISSION.spawn(world, pos.toCenterPos(), pos.toCenterPos().add(shootDir.multiply(15)));
break;
}
}
}
}
private static BlockPos getRayEnd(Vec3d shotFrom, Vec3d shotDirection) {
return BlockPos.ofFloored(shotFrom.add(shotDirection.multiply(12)));
}
private ParticleCollectorBlockEntity tryGetCachedCollector(BlockPos key) {
var cachedResult = cachedCollectors.get(key);
if (cachedResult == null) {
// no cache
return null;
} else if (cachedResult.isRemoved()) {
cachedCollectors.remove(key);
return null;
}
return cachedResult;
}
public static BlockPos basicRaycast(Vec3d from, Vec3d direction, int range, World world) {
var checkedPositions = new HashSet<BlockPos>();
for (float i = 0; i < range; i += 0.3f) {
var to = from.add(direction.multiply(i));
var targetBlockPos = BlockPos.ofFloored(to);
// avoid double checks
if (checkedPositions.contains(targetBlockPos)) continue;
checkedPositions.add(targetBlockPos);
var targetState = world.getBlockState(targetBlockPos);
if (!canPassThrough(targetState, targetBlockPos)) return targetBlockPos;
}
return null;
}
private static boolean canPassThrough(BlockState state, BlockPos blockPos) {
// When targetting entities, don't let grass, vines, small mushrooms, pressure plates, etc. get in the way of the laser
return state.isAir() || state.getFluidState().isStill() || state.isIn(TagContent.LASER_PASSTHROUGH) || state.getBlock() instanceof AcceleratorPassthroughBlock;
}
public void onClientPullEvent(NetworkContent.BlackHoleSuckPacket packet) {
this.currentlyPullingFrom = packet.from();
this.pullTime = packet.duration();
this.pullingStartedAt = world.getTime();
this.currentlyPulling = world.getBlockState(packet.from());
}
}