From 7932886c824206e3c92138e71c80f12496377e35 Mon Sep 17 00:00:00 2001 From: rearth Date: Fri, 13 Dec 2024 13:03:32 +0100 Subject: [PATCH] Begin adding explosions (aka nuclear hellfile) --- .../blocks/reactor/NuclearExplosionBlock.java | 34 +++ .../reactor/NuclearExplosionEntity.java | 278 ++++++++++++++++++ .../rearth/oritech/init/BlockContent.java | 1 + .../oritech/init/BlockEntitiesContent.java | 6 +- 4 files changed, 315 insertions(+), 4 deletions(-) create mode 100644 common/src/main/java/rearth/oritech/block/blocks/reactor/NuclearExplosionBlock.java create mode 100644 common/src/main/java/rearth/oritech/block/entity/reactor/NuclearExplosionEntity.java diff --git a/common/src/main/java/rearth/oritech/block/blocks/reactor/NuclearExplosionBlock.java b/common/src/main/java/rearth/oritech/block/blocks/reactor/NuclearExplosionBlock.java new file mode 100644 index 00000000..ab4225fc --- /dev/null +++ b/common/src/main/java/rearth/oritech/block/blocks/reactor/NuclearExplosionBlock.java @@ -0,0 +1,34 @@ +package rearth.oritech.block.blocks.reactor; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockEntityProvider; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityTicker; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; +import rearth.oritech.block.entity.reactor.NuclearExplosionEntity; + +public class NuclearExplosionBlock extends Block implements BlockEntityProvider { + public NuclearExplosionBlock(Settings settings) { + super(settings); + } + + @Nullable + @Override + public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new NuclearExplosionEntity(pos, state); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Nullable + @Override + public BlockEntityTicker getTicker(World world, BlockState state, BlockEntityType type) { + return (world1, pos, state1, blockEntity) -> { + if (blockEntity instanceof BlockEntityTicker ticker) + ticker.tick(world1, pos, state1, blockEntity); + }; + } +} diff --git a/common/src/main/java/rearth/oritech/block/entity/reactor/NuclearExplosionEntity.java b/common/src/main/java/rearth/oritech/block/entity/reactor/NuclearExplosionEntity.java new file mode 100644 index 00000000..b5b75eff --- /dev/null +++ b/common/src/main/java/rearth/oritech/block/entity/reactor/NuclearExplosionEntity.java @@ -0,0 +1,278 @@ +package rearth.oritech.block.entity.reactor; + +import net.minecraft.block.Block; +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.registry.tag.BlockTags; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import rearth.oritech.init.BlockContent; +import rearth.oritech.init.BlockEntitiesContent; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class NuclearExplosionEntity extends BlockEntity implements BlockEntityTicker { + + private long startTime = -1; + private final Set removedBlocks = new HashSet<>(); + private final Set borderBlocks = new HashSet<>(); + private final Set waves = new HashSet<>(); + + public NuclearExplosionEntity(BlockPos pos, BlockState state) { + super(BlockEntitiesContent.REACTOR_EXPLOSION_ENTITY, pos, state); + } + + @Override + public void tick(World world, BlockPos pos, BlockState state, NuclearExplosionEntity blockEntity) { + if (world.isClient) return; + + var initialRadius = 9; + + if (startTime == -1) { + startTime = world.getTime(); + explosionSphere(initialRadius + 7, 200, pos); + } + + var age = world.getTime() - startTime; + + if (age == 1) { + createExplosionWaves(initialRadius); + } + + if (age > 1) { + waves.forEach(DirectionExplosionWave::nextGeneration); + processBorderBlocks(initialRadius * initialRadius); + } + + if (age > initialRadius * 2) { + // done + world.setBlockState(pos, Blocks.AIR.getDefaultState()); + } + + } + + private void createExplosionWaves(int initialRadius) { + var rayCount = initialRadius / 2 + 3; + var directions = getRandomRayDirections(rayCount); + for (var direction : directions) { + var data = new DirectionExplosionWave(initialRadius, addRandomOffset(direction, 0.15f), pos.add(0, world.random.nextBetween(-initialRadius / 2, initialRadius / 2), 0).toImmutable()); + waves.add(data); + } + } + + private void processBorderBlocks(int maxDist) { + + borderBlocks.forEach(target -> { + if (removedBlocks.contains(target)) return; + var distSq = target.getSquaredDistance(pos); + var targetBlock = world.getBlockState(target); + var percentageDist = distSq / (maxDist * maxDist) * 8; + var percentageVaried = percentageDist * (world.random.nextFloat() * 0.6 - 0.3 + 1); + + var replaced = false; + var replacementState = Blocks.AIR.getDefaultState(); + + if (targetBlock.isIn(BlockTags.LOGS)) { + replaced = true; + replacementState = world.random.nextFloat() < 0.8 ? Blocks.BASALT.getDefaultState() : Blocks.MAGMA_BLOCK.getDefaultState(); + if (percentageVaried < 0.4f) replacementState = Blocks.AIR.getDefaultState(); + } else if (targetBlock.isIn(BlockTags.LEAVES)) { + replaced = true; + replacementState = world.random.nextFloat() > 0.4 ? Blocks.MANGROVE_ROOTS.getDefaultState() : Blocks.AIR.getDefaultState(); + if (percentageVaried < 0.6f) replacementState = Blocks.AIR.getDefaultState(); + } else if (targetBlock.isIn(BlockTags.SAPLINGS) || targetBlock.isOf(Blocks.SHORT_GRASS)) { + replaced = true; + replacementState = world.random.nextFloat() > 0.4 ? Blocks.DEAD_BUSH.getDefaultState() : Blocks.AIR.getDefaultState(); + if (percentageVaried < 0.5f) replacementState = Blocks.AIR.getDefaultState(); + } else if (targetBlock.isOf(Blocks.GRASS_BLOCK)) { + replaced = true; + if (percentageVaried < 0.05) { + replacementState = world.random.nextFloat() > 0.5 ? Blocks.TUFF.getDefaultState() : Blocks.MAGMA_BLOCK.getDefaultState(); + } else if (percentageVaried < 0.3) { + replacementState = world.random.nextFloat() > 0.2 ? Blocks.TUFF.getDefaultState() : Blocks.MAGMA_BLOCK.getDefaultState(); + } else if (percentageVaried < 0.55) { + replacementState = world.random.nextFloat() > 0.1 ? Blocks.COARSE_DIRT.getDefaultState() : Blocks.MAGMA_BLOCK.getDefaultState(); + } else { + replacementState = Blocks.DIRT.getDefaultState(); + } + + if (world.random.nextFloat() > 0.7) replaced = false; + } else if (targetBlock.isOf(Blocks.DIRT)) { + replaced = true; + if (percentageVaried < 0.15) { + replacementState = world.random.nextFloat() > 0.6 ? Blocks.COARSE_DIRT.getDefaultState() : Blocks.MAGMA_BLOCK.getDefaultState(); + } else if (percentageVaried < 0.3) { + replacementState = world.random.nextFloat() > 0.3 ? Blocks.TUFF.getDefaultState() : Blocks.COARSE_DIRT.getDefaultState(); + } else if (percentageVaried < 0.65) { + replacementState = world.random.nextFloat() > 0.2 ? Blocks.COARSE_DIRT.getDefaultState() : Blocks.TUFF.getDefaultState(); + } else { + replaced = false; + } + + if (world.random.nextFloat() > 0.1) replaced = false; + } else if (targetBlock.isIn(BlockTags.BASE_STONE_OVERWORLD)) { + replaced = true; + if (percentageVaried < 0.3) { + replacementState = world.random.nextFloat() > 0.5 ? Blocks.DEEPSLATE.getDefaultState() : Blocks.MAGMA_BLOCK.getDefaultState(); + } else if (percentageVaried < 0.5) { + replacementState = world.random.nextFloat() > 0.3 ? Blocks.STONE.getDefaultState() : Blocks.MAGMA_BLOCK.getDefaultState(); + } else if (percentageVaried < 0.7) { + replacementState = world.random.nextFloat() > 0.2 ? Blocks.GRANITE.getDefaultState() : Blocks.MAGMA_BLOCK.getDefaultState(); + } else { + replaced = false; + } + } else if (targetBlock.isIn(BlockTags.SAND) || targetBlock.isOf(Blocks.SANDSTONE)) { + replaced = true; + if (percentageVaried < 0.2) { + replacementState = world.random.nextFloat() > 0.7 ? Blocks.SANDSTONE.getDefaultState() : Blocks.MAGMA_BLOCK.getDefaultState(); + } else { + replacementState = Blocks.GLASS.getDefaultState(); + } + + if (percentageVaried > 0.8) replaced = false; + } + + if (replaced) { + world.setBlockState(target, replacementState, Block.SKIP_DROPS | Block.NOTIFY_LISTENERS, 1); + + // random fire chance + if (world.getBlockState(target.up()).isReplaceable() && world.random.nextFloat() > 0.97) { + world.setBlockState(target.up(), Blocks.FIRE.getDefaultState(), Block.SKIP_DROPS | Block.NOTIFY_LISTENERS, 0); + } + } + }); + + borderBlocks.clear(); + } + + private void collectExtraEdgeBlocks(BlockPos center) { + BlockPos.iterate(center.add(-8, -8, -8), center.add(8, 8, 8)).forEach(target -> { + if (removedBlocks.contains(target)) return; + var targetState = world.getBlockState(target); + if (targetState.isAir()) return; + borderBlocks.add(target.toImmutable()); + }); + } + + // remove all blocks in X radius below hardness 'power', return amount of hardness used in total + private int explosionSphere(int radius, int power, BlockPos pos) { + + var radiusSq = radius * radius; + var radiusSqExtra = (radius + 3) * (radius + 3); + var usedPower = 0; + var hardBusters = radius; + + for (var target : BlockPos.iterateOutwards(pos, radius + 2, radius + 2, radius + 2)) { + if (removedBlocks.contains(target)) continue; + var distSq = target.getSquaredDistance(pos); + + if (distSq > radiusSq) { + if (distSq <= (radiusSqExtra)) { + // border block, was almost destroyed + borderBlocks.add(target.toImmutable()); + } + continue; + } + + // if less than half dist, 100%, then slowly ramp up to 0% + var removalPercentage = (distSq - radiusSq / 2f) / radiusSq; + if (world.random.nextFloat() < removalPercentage - 0.2) { + borderBlocks.add(target.toImmutable()); + continue; + } + + var targetState = world.getBlockState(target); + var targetBlock = targetState.getBlock(); + var targetHardness = targetBlock.getBlastResistance(); + + if (targetBlock.equals(BlockContent.REACTOR_EXPLOSION) || targetState.isAir() && !targetState.isLiquid()) continue; + + // skip too hard blocks (except for the first few) + if (targetHardness > power && hardBusters-- < 0) continue; + + usedPower += targetHardness; + + // todo find all onBreak overrides in project and move to onBroken + targetBlock.onBroken(world, pos, targetState); + world.setBlockState(target, Blocks.AIR.getDefaultState(), Block.SKIP_DROPS | Block.NOTIFY_LISTENERS, 0); + removedBlocks.add(target.toImmutable()); + borderBlocks.remove(target.toImmutable()); + + } + + return usedPower; + } + + private List getRandomRayDirections(int count) { + List rayDirections = new ArrayList<>(count); + + // Divide the circle into 12 equal parts + var angleIncrement = 2 * Math.PI / count; // 360 degrees / 12 + + for (int i = 0; i < count; i++) { + // Calculate the base angle for this ray + var baseAngle = i * angleIncrement; + + // Add a small random perturbation to the angle + var randomPerturbation = (world.random.nextFloat() - 0.5) * (angleIncrement / 2); + + // Final angle with randomness + var angle = baseAngle + randomPerturbation; + + // Calculate the direction vector + var x = Math.cos(angle); + var z = Math.sin(angle); + + rayDirections.add(new Vec3d(x, 0, z)); // Horizontal direction + } + + return rayDirections; + } + + private Vec3d addRandomOffset(Vec3d direction, float amount) { + return direction.add(world.random.nextFloat() * amount - amount / 2, world.random.nextFloat() * amount - amount / 2, world.random.nextFloat() * amount - amount / 2); + } + + private class DirectionExplosionWave { + + private final Vec3d direction; + + private int lastRadius; + private BlockPos lastPosition; + private int lastRadiusReduction; + + private DirectionExplosionWave(int initialRadius, Vec3d direction, BlockPos pos) { + this.direction = direction; + this.lastRadius = initialRadius; + this.lastPosition = pos; + this.lastRadiusReduction = 1; + } + + private void nextGeneration() { + var currentRadius = lastRadius - lastRadiusReduction; + if (currentRadius <= 1) return; + var rayOffset = direction.multiply(currentRadius); + var target = lastPosition.add(BlockPos.ofFloored(rayOffset)); + var power = currentRadius * 3; + lastRadius = currentRadius; + lastPosition = target; + + var usedPower = explosionSphere(currentRadius, power, target); + var expectedPower = currentRadius * currentRadius * currentRadius * 3; + if (usedPower > expectedPower) { + lastRadiusReduction = 2; + } + + var isLastGeneration = currentRadius - lastRadiusReduction <= 1; + if (isLastGeneration) + collectExtraEdgeBlocks(target.add(BlockPos.ofFloored(rayOffset.multiply(3)))); + + } + } +} diff --git a/common/src/main/java/rearth/oritech/init/BlockContent.java b/common/src/main/java/rearth/oritech/init/BlockContent.java index 82243415..ed8ef151 100644 --- a/common/src/main/java/rearth/oritech/init/BlockContent.java +++ b/common/src/main/java/rearth/oritech/init/BlockContent.java @@ -208,6 +208,7 @@ public class BlockContent implements ArchitecturyBlockRegistryContainer { public static final Block REACTOR_FUEL_PORT = new ReactorFuelPortBlock(AbstractBlock.Settings.copy(Blocks.IRON_BLOCK)); public static final Block REACTOR_ABSORBER_PORT = new ReactorAbsorberPortBlock(AbstractBlock.Settings.copy(Blocks.IRON_BLOCK)); public static final Block REACTOR_ENERGY_PORT = new ReactorEnergyPortBlock(AbstractBlock.Settings.copy(Blocks.IRON_BLOCK)); + public static final Block REACTOR_EXPLOSION = new NuclearExplosionBlock(AbstractBlock.Settings.copy(Blocks.IRON_BLOCK)); // cooling cell, early game re-fillable component diff --git a/common/src/main/java/rearth/oritech/init/BlockEntitiesContent.java b/common/src/main/java/rearth/oritech/init/BlockEntitiesContent.java index 1a30ab84..e3c92750 100644 --- a/common/src/main/java/rearth/oritech/init/BlockEntitiesContent.java +++ b/common/src/main/java/rearth/oritech/init/BlockEntitiesContent.java @@ -21,10 +21,7 @@ import rearth.oritech.block.entity.pipes.ItemFilterBlockEntity; import rearth.oritech.block.entity.pipes.ItemPipeInterfaceEntity; import rearth.oritech.block.entity.processing.*; -import rearth.oritech.block.entity.reactor.ReactorAbsorberPortEntity; -import rearth.oritech.block.entity.reactor.ReactorControllerBlockEntity; -import rearth.oritech.block.entity.reactor.ReactorEnergyPortEntity; -import rearth.oritech.block.entity.reactor.ReactorFuelPortEntity; +import rearth.oritech.block.entity.reactor.*; import rearth.oritech.block.entity.storage.CreativeStorageBlockEntity; import rearth.oritech.block.entity.storage.LargeStorageBlockEntity; import rearth.oritech.block.entity.storage.SmallFluidTankEntity; @@ -155,6 +152,7 @@ public class BlockEntitiesContent implements ArchitecturyRegistryContainer REACTOR_ABSORBER_PORT_BLOCK_ENTITY = FabricBlockEntityTypeBuilder.create(ReactorAbsorberPortEntity::new, BlockContent.REACTOR_ABSORBER_PORT).build(); @AssignSidedEnergy public static final BlockEntityType REACTOR_ENERGY_PORT_BLOCK_ENTITY = FabricBlockEntityTypeBuilder.create(ReactorEnergyPortEntity::new, BlockContent.REACTOR_ENERGY_PORT).build(); + public static final BlockEntityType REACTOR_EXPLOSION_ENTITY = FabricBlockEntityTypeBuilder.create(NuclearExplosionEntity::new, BlockContent.REACTOR_EXPLOSION).build(); @AssignSidedInventory public static final BlockEntityType ACCELERATOR_CONTROLLER_BLOCK_ENTITY = FabricBlockEntityTypeBuilder.create(AcceleratorControllerBlockEntity::new, BlockContent.ACCELERATOR_CONTROLLER).build();