Restore instant crater debris and glow sprites
All checks were successful
Build / build (push) Successful in 9m32s

This commit is contained in:
MrSphay
2026-05-09 16:46:14 +02:00
parent b9e06a6a19
commit 77a55286b8
7 changed files with 123 additions and 143 deletions

View File

@@ -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<CraterJob> 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<CraterJob> 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;
}
}
}

View File

@@ -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<BlockPos> getCraterBlocks(ServerLevel level, Vec3 explosionPos, float power) {
Set<BlockPos> 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<BlockPos> craterBlocks = getCraterBlocks(level, explosionPos, power);
int debrisLimit = resolveDebrisLimit(power, defaultDebrisLimit(power));
List<DebrisCandidate> 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<DebrisCandidate> sampleDebrisCandidates(ServerLevel level, Vec3 explosionPos, Set<BlockPos> craterBlocks, int maxDebris) {
List<DebrisCandidate> 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) {
}
}

View File

@@ -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() {

View File

@@ -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);
}

View File

@@ -1,6 +1,6 @@
{
"textures": [
"explosionoverhaul:plasma",
"explosionoverhaul:plasma_e"
"explosionoverhaul:soft_glow",
"explosionoverhaul:soft_glow_e"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB