diff --git a/src/main/java/com/vinlanx/explosionoverhaul/AsyncCraterManager.java b/src/main/java/com/vinlanx/explosionoverhaul/AsyncCraterManager.java index 12679e9..a5ac49c 100644 --- a/src/main/java/com/vinlanx/explosionoverhaul/AsyncCraterManager.java +++ b/src/main/java/com/vinlanx/explosionoverhaul/AsyncCraterManager.java @@ -1,148 +1,20 @@ package com.vinlanx.explosionoverhaul; -import java.util.ArrayDeque; -import java.util.Iterator; -import java.util.Queue; -import net.minecraft.core.BlockPos; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; public class AsyncCraterManager { - private static final Queue JOBS = new ArrayDeque<>(); - private static final int MAX_SCAN_POSITIONS_PER_TICK = 4096; - private static final int MAX_BREAK_EVENTS_PER_TICK = 96; - private static final int MAX_DEBRIS_PER_TICK = 4; - private static final int MAX_DEBRIS_PER_EXPLOSION = 64; - public static void submit(ServerLevel level, Vec3 pos, float power) { - synchronized (JOBS) { - for (CraterJob job : JOBS) { - if (job.overlaps(level, pos, power)) { - return; - } - } - JOBS.add(new CraterJob(level, pos, power)); - } + CraterDeformer.applyInstantCrater(level, pos, power); } public static void onServerTick(MinecraftServer server) { - int configuredBudget = Math.max(1, (Integer)Config.COMMON.craterApplyBlocksPerTick.get()); - int budget = Math.max(256, Math.min(configuredBudget, MAX_SCAN_POSITIONS_PER_TICK)); - synchronized (JOBS) { - Iterator iterator = JOBS.iterator(); - while (iterator.hasNext() && budget > 0) { - CraterJob job = iterator.next(); - budget = job.apply(budget); - if (job.done()) { - iterator.remove(); - } - } - } } public static void onLevelUnload(ServerLevel level) { - synchronized (JOBS) { - JOBS.removeIf(job -> job.level == level); - } } public static void shutdown() { - synchronized (JOBS) { - JOBS.clear(); - } - } - - private static final class CraterJob { - private final ServerLevel level; - private final Vec3 origin; - private final float power; - private final float radius; - private final int minX; - private final int maxX; - private final int minY; - private final int maxY; - private final int minZ; - private final int maxZ; - private final int maxDebris; - private int x; - private int y; - private int z; - private int debrisSpawned; - - private CraterJob(ServerLevel level, Vec3 origin, float power) { - this.level = level; - this.origin = origin; - this.power = power; - this.radius = CraterDeformer.calculateRadius(power); - int scanRadius = CraterDeformer.calculateScanRadius(power); - BlockPos center = BlockPos.containing(origin); - this.minX = center.getX() - scanRadius; - this.maxX = center.getX() + scanRadius; - this.minY = Math.max(level.getMinBuildHeight(), center.getY() - scanRadius); - this.maxY = Math.min(level.getMaxBuildHeight() - 1, center.getY() + scanRadius); - this.minZ = center.getZ() - scanRadius; - this.maxZ = center.getZ() + scanRadius; - this.x = this.minX; - this.y = this.minY; - this.z = this.minZ; - this.maxDebris = Math.min(MAX_DEBRIS_PER_EXPLOSION, Math.max(4, Math.round(power * 2.5f))); - } - - private int apply(int budget) { - int breakEvents = 0; - int debrisThisTick = 0; - int configuredDebris = Math.max(0, (Integer)Config.COMMON.craterMaxFallingBlocksPerTick.get()); - int debrisBudget = Math.min(configuredDebris, MAX_DEBRIS_PER_TICK); - while (!this.done() && budget > 0) { - BlockPos pos = new BlockPos(this.x, this.y, this.z); - this.advance(); - --budget; - if (!CraterDeformer.shouldDestroyCraterBlock(this.level, pos, this.origin, this.power, this.radius)) { - continue; - } - BlockState state = this.level.getBlockState(pos); - if (!state.isAir() && state.getDestroySpeed(this.level, pos) >= 0.0f && !ExplosionOverhaul.isBlockStateBlacklisted(state)) { - if (this.debrisSpawned < this.maxDebris && debrisThisTick < debrisBudget && this.level.random.nextFloat() < 0.006f) { - CraterDeformer.spawnDebris(this.level, this.origin, this.power, 1); - ++this.debrisSpawned; - ++debrisThisTick; - } - if (breakEvents < MAX_BREAK_EVENTS_PER_TICK && this.level.random.nextFloat() < 0.035f) { - this.level.levelEvent(2001, pos, Block.getId(state)); - ++breakEvents; - } - this.level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3); - } - } - return budget; - } - - private boolean overlaps(ServerLevel level, Vec3 pos, float power) { - if (this.level != level || this.done()) { - return false; - } - double threshold = Math.max(3.0, Math.min(this.radius, CraterDeformer.calculateRadius(power)) * 0.45); - return this.origin.distanceToSqr(pos) <= threshold * threshold; - } - - private void advance() { - if (++this.x <= this.maxX) { - return; - } - this.x = this.minX; - if (++this.y <= this.maxY) { - return; - } - this.y = this.minY; - ++this.z; - } - - private boolean done() { - return this.z > this.maxZ; - } } } diff --git a/src/main/java/com/vinlanx/explosionoverhaul/CraterDeformer.java b/src/main/java/com/vinlanx/explosionoverhaul/CraterDeformer.java index b0ca199..9f825c9 100644 --- a/src/main/java/com/vinlanx/explosionoverhaul/CraterDeformer.java +++ b/src/main/java/com/vinlanx/explosionoverhaul/CraterDeformer.java @@ -1,15 +1,22 @@ package com.vinlanx.explosionoverhaul; +import java.util.ArrayList; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; import net.minecraft.world.entity.item.FallingBlockEntity; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; public class CraterDeformer { + private static final int MAX_BREAK_EVENTS_PER_EXPLOSION = 128; + private static final int MAX_DEBRIS_PER_EXPLOSION = 96; + public static Set getCraterBlocks(ServerLevel level, Vec3 explosionPos, float power) { Set blocks = new LinkedHashSet<>(); float radius = calculateRadius(power); @@ -64,6 +71,29 @@ public class CraterDeformer { return Math.max(8.0f, power * 6.0f); } + public static void applyInstantCrater(ServerLevel level, Vec3 explosionPos, float power) { + Set craterBlocks = getCraterBlocks(level, explosionPos, power); + int debrisLimit = resolveDebrisLimit(power, defaultDebrisLimit(power)); + List debris = sampleDebrisCandidates(level, explosionPos, craterBlocks, debrisLimit); + for (DebrisCandidate candidate : debris) { + launchDebris(level, explosionPos, power, candidate); + } + + int breakEvents = 0; + int maxBreakEvents = Math.min(MAX_BREAK_EVENTS_PER_EXPLOSION, Math.max(24, Math.round(power * 5.0f))); + for (BlockPos pos : craterBlocks) { + BlockState state = level.getBlockState(pos); + if (state.isAir() || state.getDestroySpeed(level, pos) < 0.0f || ExplosionOverhaul.isBlockStateBlacklisted(state)) { + continue; + } + if (breakEvents < maxBreakEvents && level.random.nextFloat() < 0.035f) { + level.levelEvent(2001, pos, Block.getId(state)); + ++breakEvents; + } + level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3); + } + } + public static void applyLargeExplosionLogic(ServerLevel level, Vec3 explosionPos, float power) { spawnDebris(level, explosionPos, power, Math.min(72, Math.round(power * 3.0f))); } @@ -73,29 +103,97 @@ public class CraterDeformer { } public static void spawnDebris(ServerLevel level, Vec3 explosionPos, float power, int maxDebris) { - if (!((Boolean)Config.COMMON.enableFallingBlocks.get()).booleanValue() || maxDebris <= 0) { + int debrisLimit = resolveDebrisLimit(power, maxDebris); + if (debrisLimit <= 0) { return; } float radius = calculateRadius(power); BlockPos origin = BlockPos.containing(explosionPos); int spawned = 0; - int attempts = maxDebris * 4; - for (int i = 0; i < attempts && spawned < maxDebris; ++i) { + int attempts = debrisLimit * 5; + for (int i = 0; i < attempts && spawned < debrisLimit; ++i) { double angle = level.random.nextDouble() * Math.PI * 2.0; double distance = radius * (0.25 + level.random.nextDouble() * 0.7); BlockPos pos = origin.offset(Mth.floor(Math.cos(angle) * distance), level.random.nextInt(5) - 1, Mth.floor(Math.sin(angle) * distance)); BlockState state = level.getBlockState(pos); - if (state.isAir() || state.getDestroySpeed(level, pos) < 0.0f || ExplosionOverhaul.isBlockStateBlacklisted(state)) { + if (!canLaunchDebris(level, pos, state)) { continue; } - FallingBlockEntity falling = FallingBlockEntity.fall(level, pos, state); - Vec3 motion = new Vec3(pos.getX() + 0.5 - explosionPos.x(), 0.3 + level.random.nextDouble() * 0.9, pos.getZ() + 0.5 - explosionPos.z()).normalize().scale(0.18 + Math.min(1.4, power * 0.035)); - falling.setDeltaMovement(motion); - falling.time = 1; + launchDebris(level, explosionPos, power, new DebrisCandidate(pos.immutable(), state)); ++spawned; } } + private static List sampleDebrisCandidates(ServerLevel level, Vec3 explosionPos, Set craterBlocks, int maxDebris) { + List candidates = new ArrayList<>(Math.max(0, maxDebris)); + if (maxDebris <= 0) { + return candidates; + } + int seen = 0; + for (BlockPos pos : craterBlocks) { + BlockState state = level.getBlockState(pos); + if (!canLaunchDebris(level, pos, state)) { + continue; + } + double distance = Math.sqrt(pos.distToCenterSqr(explosionPos)); + if (distance < 1.5 || level.random.nextFloat() < 0.22f) { + continue; + } + DebrisCandidate candidate = new DebrisCandidate(pos.immutable(), state); + ++seen; + if (candidates.size() < maxDebris) { + candidates.add(candidate); + } else { + int replacement = level.random.nextInt(seen); + if (replacement < maxDebris) { + candidates.set(replacement, candidate); + } + } + } + return candidates; + } + + private static boolean canLaunchDebris(ServerLevel level, BlockPos pos, BlockState state) { + return !state.isAir() + && state.getDestroySpeed(level, pos) >= 0.0f + && state.getFluidState().isEmpty() + && !ExplosionOverhaul.isBlockStateBlacklisted(state); + } + + private static void launchDebris(ServerLevel level, Vec3 explosionPos, float power, DebrisCandidate candidate) { + BlockState state = level.getBlockState(candidate.pos()); + if (state.isAir() || !state.is(candidate.state().getBlock())) { + return; + } + FallingBlockEntity falling = FallingBlockEntity.fall(level, candidate.pos(), candidate.state()); + Vec3 offset = Vec3.atCenterOf(candidate.pos()).subtract(explosionPos); + Vec3 horizontal = new Vec3(offset.x, 0.0, offset.z); + if (horizontal.lengthSqr() < 0.0001) { + double angle = level.random.nextDouble() * Math.PI * 2.0; + horizontal = new Vec3(Math.cos(angle), 0.0, Math.sin(angle)); + } + double horizontalSpeed = 0.22 + Math.min(1.45, power * 0.045) + level.random.nextDouble() * 0.45; + double verticalSpeed = 0.32 + Math.min(0.75, power * 0.02) + level.random.nextDouble() * 0.65; + falling.setDeltaMovement(horizontal.normalize().scale(horizontalSpeed).add(0.0, verticalSpeed, 0.0)); + falling.time = 1; + } + + private static int defaultDebrisLimit(float power) { + return power >= 12.0f ? Math.min(72, Math.round(power * 3.0f)) : Math.min(24, Math.round(power * 3.0f)); + } + + private static int resolveDebrisLimit(float power, int requestedLimit) { + if (!((Boolean)Config.COMMON.enableFallingBlocks.get()).booleanValue() || requestedLimit <= 0) { + return 0; + } + int configuredLimit = Math.max(0, (Integer)Config.COMMON.craterMaxFallingBlocksPerTick.get()); + if (configuredLimit <= 0) { + return 0; + } + int visibleLimit = Math.max(requestedLimit, Math.round(power * 4.0f)); + return Math.min(MAX_DEBRIS_PER_EXPLOSION, Math.max(configuredLimit, visibleLimit)); + } + private static double roughness(BlockPos pos, Vec3 origin, float power) { long seed = pos.asLong() ^ Double.doubleToLongBits(origin.x()) ^ (Double.doubleToLongBits(origin.z()) << 1) ^ (long)(power * 1000.0f); seed ^= seed >>> 33; @@ -105,4 +203,7 @@ public class CraterDeformer { seed ^= seed >>> 33; return (seed & 0xFFFF) / 65535.0; } + + private record DebrisCandidate(BlockPos pos, BlockState state) { + } } diff --git a/src/main/java/com/vinlanx/explosionoverhaul/client/CustomGlowParticle.java b/src/main/java/com/vinlanx/explosionoverhaul/client/CustomGlowParticle.java index 4307007..e8690c4 100644 --- a/src/main/java/com/vinlanx/explosionoverhaul/client/CustomGlowParticle.java +++ b/src/main/java/com/vinlanx/explosionoverhaul/client/CustomGlowParticle.java @@ -18,7 +18,13 @@ public class CustomGlowParticle extends TextureSheetParticle { Config.Client.ParticleRenderMode mode = Config.CLIENT.particleRenderMode.get(); float configScale = ((Double)Config.CLIENT.particleSizeScale.get()).floatValue(); float powerScale = Mth.clamp(options.getPower() / 4.0f, 0.8f, 4.0f); - this.baseSize = options.getScale() * configScale * powerScale * (0.85f + this.random.nextFloat() * 0.35f); + float requestedSize = options.getScale() * configScale * powerScale * (0.85f + this.random.nextFloat() * 0.35f); + float maxSize = switch (mode) { + case REALISTIC -> 5.0f; + case REALISTIC_2 -> 5.6f; + case VANILA -> 3.8f; + }; + this.baseSize = Mth.clamp(requestedSize, 0.2f, maxSize); this.lifetime = switch (mode) { case REALISTIC -> 34 + this.random.nextInt(15); case REALISTIC_2 -> 44 + this.random.nextInt(22); @@ -49,7 +55,7 @@ public class CustomGlowParticle extends TextureSheetParticle { } float progress = this.age / (float)this.lifetime; this.alpha = Mth.clamp(1.0f - progress, 0.0f, 1.0f) * 0.92f; - this.quadSize = this.baseSize * (0.8f + progress * 1.8f); + this.quadSize = this.baseSize * (0.85f + progress * 1.15f); } public boolean shouldCull() { diff --git a/src/main/java/com/vinlanx/explosionoverhaul/client/SmokeParticle.java b/src/main/java/com/vinlanx/explosionoverhaul/client/SmokeParticle.java index 23b5784..46c8be8 100644 --- a/src/main/java/com/vinlanx/explosionoverhaul/client/SmokeParticle.java +++ b/src/main/java/com/vinlanx/explosionoverhaul/client/SmokeParticle.java @@ -18,7 +18,8 @@ public class SmokeParticle extends TextureSheetParticle { super(level, x, y, z, xSpeed, ySpeed, zSpeed); this.sprites = sprites; this.lifetime = Math.max(6, options.getLifetime()); - this.startSize = options.getScale() * (options.isHeavy() ? 1.45f : 1.0f); + float requestedSize = options.getScale() * (options.isHeavy() ? 1.25f : 1.0f); + this.startSize = Mth.clamp(requestedSize, 0.12f, options.isHeavy() ? 8.0f : 5.5f); this.startAlpha = options.getAlpha(); this.quadSize = this.startSize; this.rCol = options.getRed(); @@ -51,7 +52,7 @@ public class SmokeParticle extends TextureSheetParticle { } float progress = this.age / (float)this.lifetime; this.setSpriteFromAge(this.sprites); - this.quadSize = this.startSize * (1.0f + progress * 1.6f); + this.quadSize = Math.min(this.startSize + 5.5f, this.startSize * (1.0f + progress * 1.25f)); this.alpha = this.startAlpha * Mth.clamp(1.0f - progress, 0.0f, 1.0f); } diff --git a/src/main/resources/assets/explosionoverhaul/particles/custom_glow.json b/src/main/resources/assets/explosionoverhaul/particles/custom_glow.json index 4d367e3..abe91f7 100644 --- a/src/main/resources/assets/explosionoverhaul/particles/custom_glow.json +++ b/src/main/resources/assets/explosionoverhaul/particles/custom_glow.json @@ -1,6 +1,6 @@ { "textures": [ - "explosionoverhaul:plasma", - "explosionoverhaul:plasma_e" + "explosionoverhaul:soft_glow", + "explosionoverhaul:soft_glow_e" ] } diff --git a/src/main/resources/assets/explosionoverhaul/textures/particle/soft_glow.png b/src/main/resources/assets/explosionoverhaul/textures/particle/soft_glow.png new file mode 100644 index 0000000..590e934 Binary files /dev/null and b/src/main/resources/assets/explosionoverhaul/textures/particle/soft_glow.png differ diff --git a/src/main/resources/assets/explosionoverhaul/textures/particle/soft_glow_e.png b/src/main/resources/assets/explosionoverhaul/textures/particle/soft_glow_e.png new file mode 100644 index 0000000..590e934 Binary files /dev/null and b/src/main/resources/assets/explosionoverhaul/textures/particle/soft_glow_e.png differ