RyanHub – file viewer
filename: common/src/main/java/rearth/oritech/block/entity/reactor/NuclearExplosionEntity.java
branch: 1.21
back to repo
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.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.damage.DamageTypes;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.sound.SoundCategory;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.Vec3i;
import net.minecraft.world.World;
import rearth.oritech.block.blocks.reactor.NuclearExplosionBlock;
import rearth.oritech.init.BlockEntitiesContent;
import rearth.oritech.init.SoundContent;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class NuclearExplosionEntity extends BlockEntity implements BlockEntityTicker<NuclearExplosionEntity> {
    
    private long startTime = -1;
    private final Set<BlockPos> removedBlocks = new HashSet<>();
    private final Set<BlockPos> borderBlocks = new HashSet<>();
    private final Set<DirectionExplosionWave> waves = new HashSet<>();
    private final int size;
    
    public NuclearExplosionEntity(BlockPos pos, BlockState state, int size) {
        super(BlockEntitiesContent.REACTOR_EXPLOSION_ENTITY, pos, state);
        this.size = size;
    }
    
    public NuclearExplosionEntity(BlockPos pos, BlockState state) {
        super(BlockEntitiesContent.REACTOR_EXPLOSION_ENTITY, pos, state);
        this.size = 9;
    }
    
    @Override
    public void tick(World world, BlockPos pos, BlockState state, NuclearExplosionEntity blockEntity) {
        if (world.isClient) return;
        
        var initialRadius = size;
        
        if (startTime == -1) {
            startTime = world.getTime();
            explosionSphere(initialRadius + 7, 200, pos);
            world.playSound(null, pos, SoundContent.NUKE_EXPLOSION, SoundCategory.BLOCKS, 30f, 1f);
        }
        
        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
    // also damage entities
    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 + 3, radius + 3, radius + 3)) {
            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 instanceof NuclearExplosionBlock || targetState.isAir() && !targetState.getFluidState().isStill()) 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());
            
            // damages all entities in radius based on distance
            var entityCandidates = world.getEntitiesByClass(
              LivingEntity.class,
              new Box(pos.subtract(new Vec3i(radius, radius, radius)).toCenterPos(), pos.add(new Vec3i(radius, radius, radius)).toCenterPos()),
              EntityPredicates.VALID_LIVING_ENTITY.and(EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR));
            
            entityCandidates.forEach(entity -> {
                var entityDist = entity.squaredDistanceTo(pos.toCenterPos());
                var distPercentage = entityDist / radiusSq;
                var damage = radiusSq / distPercentage; // closer entities take much more damage
                System.out.println(entityDist + ":" + damage);
                entity.damage(new DamageSource(world.getRegistryManager().get(RegistryKeys.DAMAGE_TYPE).entryOf(DamageTypes.EXPLOSION)), (float) damage);
            });
            
        }
        
        return usedPower;
    }
    
    private List<Vec3d> getRandomRayDirections(int count) {
        List<Vec3d> 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))));
            
        }
    }
}