generated from MrSphay/codex-agent-repository-kit
Stabilize clustered TNT explosions
All checks were successful
Build / build (push) Successful in 13m47s
All checks were successful
Build / build (push) Successful in 13m47s
This commit is contained in:
@@ -1,9 +1,7 @@
|
|||||||
package com.vinlanx.explosionoverhaul;
|
package com.vinlanx.explosionoverhaul;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
@@ -15,16 +13,25 @@ import net.minecraft.world.phys.Vec3;
|
|||||||
|
|
||||||
public class AsyncCraterManager {
|
public class AsyncCraterManager {
|
||||||
private static final Queue<CraterJob> JOBS = new ArrayDeque<>();
|
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) {
|
public static void submit(ServerLevel level, Vec3 pos, float power) {
|
||||||
List<BlockPos> blocks = new ArrayList<>(CraterDeformer.getCraterBlocks(level, pos, power));
|
|
||||||
synchronized (JOBS) {
|
synchronized (JOBS) {
|
||||||
JOBS.add(new CraterJob(level, pos, power, blocks));
|
for (CraterJob job : JOBS) {
|
||||||
|
if (job.overlaps(level, pos, power)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JOBS.add(new CraterJob(level, pos, power));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void onServerTick(MinecraftServer server) {
|
public static void onServerTick(MinecraftServer server) {
|
||||||
int budget = Math.max(1, (Integer)Config.COMMON.craterApplyBlocksPerTick.get());
|
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) {
|
synchronized (JOBS) {
|
||||||
Iterator<CraterJob> iterator = JOBS.iterator();
|
Iterator<CraterJob> iterator = JOBS.iterator();
|
||||||
while (iterator.hasNext() && budget > 0) {
|
while (iterator.hasNext() && budget > 0) {
|
||||||
@@ -53,37 +60,89 @@ public class AsyncCraterManager {
|
|||||||
private final ServerLevel level;
|
private final ServerLevel level;
|
||||||
private final Vec3 origin;
|
private final Vec3 origin;
|
||||||
private final float power;
|
private final float power;
|
||||||
private final List<BlockPos> blocks;
|
private final float radius;
|
||||||
private int index;
|
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 int debrisSpawned;
|
||||||
|
|
||||||
private CraterJob(ServerLevel level, Vec3 origin, float power, List<BlockPos> blocks) {
|
private CraterJob(ServerLevel level, Vec3 origin, float power) {
|
||||||
this.level = level;
|
this.level = level;
|
||||||
this.origin = origin;
|
this.origin = origin;
|
||||||
this.power = power;
|
this.power = power;
|
||||||
this.blocks = blocks;
|
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) {
|
private int apply(int budget) {
|
||||||
int debrisBudget = Math.max(0, (Integer)Config.COMMON.craterMaxFallingBlocksPerTick.get());
|
int breakEvents = 0;
|
||||||
while (this.index < this.blocks.size() && budget > 0) {
|
int debrisThisTick = 0;
|
||||||
BlockPos pos = this.blocks.get(this.index++);
|
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);
|
BlockState state = this.level.getBlockState(pos);
|
||||||
if (!state.isAir() && state.getDestroySpeed(this.level, pos) >= 0.0f && !ExplosionOverhaul.isBlockStateBlacklisted(state)) {
|
if (!state.isAir() && state.getDestroySpeed(this.level, pos) >= 0.0f && !ExplosionOverhaul.isBlockStateBlacklisted(state)) {
|
||||||
if (this.debrisSpawned < debrisBudget && this.level.random.nextFloat() < 0.035f) {
|
if (this.debrisSpawned < this.maxDebris && debrisThisTick < debrisBudget && this.level.random.nextFloat() < 0.006f) {
|
||||||
CraterDeformer.spawnDebris(this.level, this.origin, this.power, 1);
|
CraterDeformer.spawnDebris(this.level, this.origin, this.power, 1);
|
||||||
++this.debrisSpawned;
|
++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.levelEvent(2001, pos, Block.getId(state));
|
|
||||||
this.level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
|
this.level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
|
||||||
}
|
}
|
||||||
--budget;
|
|
||||||
}
|
}
|
||||||
return budget;
|
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() {
|
private boolean done() {
|
||||||
return this.index >= this.blocks.size();
|
return this.z > this.maxZ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,8 +249,8 @@ public class Config {
|
|||||||
builder.comment("\nAsync crater pipeline (off-thread ray geometry + main-thread batched application)").push("Async Crater");
|
builder.comment("\nAsync crater pipeline (off-thread ray geometry + main-thread batched application)").push("Async Crater");
|
||||||
this.enableAsyncCrater = builder.comment(new String[]{"Enable the asynchronous crater pipeline.", "Compute crater geometry off-thread and apply block changes in small batches per tick to keep TPS smooth on large explosions.", "", "WARNING: When asynchronous and multi-threaded mode is enabled - destruction of VS objects - does not work."}).define("enableAsyncCrater", true);
|
this.enableAsyncCrater = builder.comment(new String[]{"Enable the asynchronous crater pipeline.", "Compute crater geometry off-thread and apply block changes in small batches per tick to keep TPS smooth on large explosions.", "", "WARNING: When asynchronous and multi-threaded mode is enabled - destruction of VS objects - does not work."}).define("enableAsyncCrater", true);
|
||||||
this.craterMaxThreads = builder.comment(new String[]{"Maximum number of threads for off-thread crater geometry precomputation.", "0 = Auto (use all available: " + availableThreadsForCrater + ")", "1 = Single-threaded", "2-" + maxThreadsForSystemCrater + " = Custom thread count for this system"}).defineInRange("craterMaxThreads", 0, 0, maxThreadsForSystemCrater);
|
this.craterMaxThreads = builder.comment(new String[]{"Maximum number of threads for off-thread crater geometry precomputation.", "0 = Auto (use all available: " + availableThreadsForCrater + ")", "1 = Single-threaded", "2-" + maxThreadsForSystemCrater + " = Custom thread count for this system"}).defineInRange("craterMaxThreads", 0, 0, maxThreadsForSystemCrater);
|
||||||
this.craterApplyBlocksPerTick = builder.comment(new String[]{"Maximum number of blocks to evaluate/apply per server tick when building a crater.", "Higher values complete faster but can cause small TPS dips.", "Lower values keep TPS flatter but take longer."}).defineInRange("craterApplyBlocksPerTick", 50000, 0, 150000);
|
this.craterApplyBlocksPerTick = builder.comment(new String[]{"Maximum number of crater positions to scan/apply per server tick.", "Higher values complete faster but can cause TPS and render spikes.", "The runtime clamps this to a safe maximum for clustered TNT stability."}).defineInRange("craterApplyBlocksPerTick", 4096, 0, 150000);
|
||||||
this.craterMaxFallingBlocksPerTick = builder.comment(new String[]{"Limit of falling block entities to spawn per tick during crater application (visual debris).", "Set to 0 to disable spawning via the async pipeline."}).defineInRange("craterMaxFallingBlocksPerTick", 500, 0, 2000);
|
this.craterMaxFallingBlocksPerTick = builder.comment(new String[]{"Limit of falling block entities to spawn per tick during crater application (visual debris).", "Set to 0 to disable spawning via the async pipeline.", "The runtime clamps this to a safe maximum for clustered TNT stability."}).defineInRange("craterMaxFallingBlocksPerTick", 4, 0, 2000);
|
||||||
this.enableDirectChunkWrites = builder.comment(new String[]{"Directly write block states into chunk sections during crater application (much faster).", "When enabled, bypasses Level#setBlock and updates heightmaps/light manually.", "Note: This skips Forge block events and neighbor updates."}).define("enableDirectChunkWrites", true);
|
this.enableDirectChunkWrites = builder.comment(new String[]{"Directly write block states into chunk sections during crater application (much faster).", "When enabled, bypasses Level#setBlock and updates heightmaps/light manually.", "Note: This skips Forge block events and neighbor updates."}).define("enableDirectChunkWrites", true);
|
||||||
this.craterChunksPerTick = builder.comment(new String[]{"Maximum number of chunks to process per tick when direct writes are enabled.", "Used along with block budget to smooth TPS."}).defineInRange("craterChunksPerTick", 120, 0, 500);
|
this.craterChunksPerTick = builder.comment(new String[]{"Maximum number of chunks to process per tick when direct writes are enabled.", "Used along with block budget to smooth TPS."}).defineInRange("craterChunksPerTick", 120, 0, 500);
|
||||||
builder.pop();
|
builder.pop();
|
||||||
|
|||||||
@@ -13,50 +13,63 @@ public class CraterDeformer {
|
|||||||
public static Set<BlockPos> getCraterBlocks(ServerLevel level, Vec3 explosionPos, float power) {
|
public static Set<BlockPos> getCraterBlocks(ServerLevel level, Vec3 explosionPos, float power) {
|
||||||
Set<BlockPos> blocks = new LinkedHashSet<>();
|
Set<BlockPos> blocks = new LinkedHashSet<>();
|
||||||
float radius = calculateRadius(power);
|
float radius = calculateRadius(power);
|
||||||
float coreRatio = ((Double)Config.COMMON.craterCoreRatio.get()).floatValue();
|
|
||||||
float coreRadius = radius * Mth.clamp(coreRatio, 0.1f, 0.95f);
|
|
||||||
float maxResistance = calculateMaxResistance(power);
|
|
||||||
BlockPos origin = BlockPos.containing(explosionPos);
|
BlockPos origin = BlockPos.containing(explosionPos);
|
||||||
int r = Mth.ceil(radius + 2.0f);
|
int r = calculateScanRadius(power);
|
||||||
for (BlockPos cursor : BlockPos.betweenClosed(origin.offset(-r, -r, -r), origin.offset(r, r, r))) {
|
for (BlockPos cursor : BlockPos.betweenClosed(origin.offset(-r, -r, -r), origin.offset(r, r, r))) {
|
||||||
BlockPos pos = cursor.immutable();
|
BlockPos pos = cursor.immutable();
|
||||||
BlockState state = level.getBlockState(pos);
|
if (shouldDestroyCraterBlock(level, pos, explosionPos, power, radius)) {
|
||||||
if (state.isAir() || state.getDestroySpeed(level, pos) < 0.0f || ExplosionOverhaul.isBlockStateBlacklisted(state)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
float resistance = state.getBlock().getExplosionResistance();
|
|
||||||
if (resistance > maxResistance && pos.distToCenterSqr(explosionPos) > coreRadius * coreRadius) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
double dx = (pos.getX() + 0.5 - explosionPos.x()) / radius;
|
|
||||||
double dy = (pos.getY() + 0.5 - explosionPos.y()) / (radius * 0.78);
|
|
||||||
double dz = (pos.getZ() + 0.5 - explosionPos.z()) / radius;
|
|
||||||
double normalized = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
||||||
double noise = roughness(pos, explosionPos, power);
|
|
||||||
double shell = 0.82 + noise * 0.22;
|
|
||||||
double undercut = pos.getY() + 0.5 < explosionPos.y() ? 0.06 : -0.03;
|
|
||||||
if (normalized <= shell + undercut || Math.sqrt(pos.distToCenterSqr(explosionPos)) <= coreRadius * (0.82 + noise * 0.12)) {
|
|
||||||
blocks.add(pos);
|
blocks.add(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean shouldDestroyCraterBlock(ServerLevel level, BlockPos pos, Vec3 explosionPos, float power) {
|
||||||
|
return shouldDestroyCraterBlock(level, pos, explosionPos, power, calculateRadius(power));
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean shouldDestroyCraterBlock(ServerLevel level, BlockPos pos, Vec3 explosionPos, float power, float radius) {
|
||||||
|
float coreRatio = ((Double)Config.COMMON.craterCoreRatio.get()).floatValue();
|
||||||
|
float coreRadius = radius * Mth.clamp(coreRatio, 0.1f, 0.95f);
|
||||||
|
float maxResistance = calculateMaxResistance(power);
|
||||||
|
BlockState state = level.getBlockState(pos);
|
||||||
|
if (state.isAir() || state.getDestroySpeed(level, pos) < 0.0f || ExplosionOverhaul.isBlockStateBlacklisted(state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
float resistance = state.getBlock().getExplosionResistance();
|
||||||
|
double distanceSqr = pos.distToCenterSqr(explosionPos);
|
||||||
|
if (resistance > maxResistance && distanceSqr > coreRadius * coreRadius) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
double dx = (pos.getX() + 0.5 - explosionPos.x()) / radius;
|
||||||
|
double dy = (pos.getY() + 0.5 - explosionPos.y()) / (radius * 0.78);
|
||||||
|
double dz = (pos.getZ() + 0.5 - explosionPos.z()) / radius;
|
||||||
|
double normalized = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
|
double noise = roughness(pos, explosionPos, power);
|
||||||
|
double shell = 0.82 + noise * 0.22;
|
||||||
|
double undercut = pos.getY() + 0.5 < explosionPos.y() ? 0.06 : -0.03;
|
||||||
|
return normalized <= shell + undercut || Math.sqrt(distanceSqr) <= coreRadius * (0.82 + noise * 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
public static float calculateRadius(float power) {
|
public static float calculateRadius(float power) {
|
||||||
float multiplier = ((Double)Config.COMMON.craterSizeMultiplier.get()).floatValue();
|
float multiplier = ((Double)Config.COMMON.craterSizeMultiplier.get()).floatValue();
|
||||||
return Math.max(2.5f, Math.min(70.0f, power * 2.15f * multiplier));
|
return Math.max(2.5f, Math.min(70.0f, power * 2.15f * multiplier));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int calculateScanRadius(float power) {
|
||||||
|
return Mth.ceil(calculateRadius(power) + 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
public static float calculateMaxResistance(float power) {
|
public static float calculateMaxResistance(float power) {
|
||||||
return Math.max(8.0f, power * 6.0f);
|
return Math.max(8.0f, power * 6.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void applyLargeExplosionLogic(ServerLevel level, Vec3 explosionPos, float power) {
|
public static void applyLargeExplosionLogic(ServerLevel level, Vec3 explosionPos, float power) {
|
||||||
spawnDebris(level, explosionPos, power, Math.min(180, Math.round(power * 8.0f)));
|
spawnDebris(level, explosionPos, power, Math.min(72, Math.round(power * 3.0f)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void applySmallExplosionLogic(ServerLevel level, Vec3 explosionPos, float power) {
|
public static void applySmallExplosionLogic(ServerLevel level, Vec3 explosionPos, float power) {
|
||||||
spawnDebris(level, explosionPos, power, Math.min(48, Math.round(power * 5.0f)));
|
spawnDebris(level, explosionPos, power, Math.min(24, Math.round(power * 3.0f)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void spawnDebris(ServerLevel level, Vec3 explosionPos, float power, int maxDebris) {
|
public static void spawnDebris(ServerLevel level, Vec3 explosionPos, float power, int maxDebris) {
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ public class ExplosionClusterHandler {
|
|||||||
List<ExplosionSource> sources = new ArrayList<>();
|
List<ExplosionSource> sources = new ArrayList<>();
|
||||||
sources.add(new ExplosionSource(pos, initialPower));
|
sources.add(new ExplosionSource(pos, initialPower));
|
||||||
for (PrimedTnt tnt : tntEntities) {
|
for (PrimedTnt tnt : tntEntities) {
|
||||||
if (!tnt.position().equals(pos)) {
|
if (tnt.position().distanceToSqr(pos) > 0.0001) {
|
||||||
sources.add(new ExplosionSource(tnt.position(), 4.0f));
|
sources.add(new ExplosionSource(tnt.position(), 4.0f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Creeper creeper : creepers) {
|
for (Creeper creeper : creepers) {
|
||||||
if (!creeper.position().equals(pos)) {
|
if (creeper.position().distanceToSqr(pos) > 0.0001) {
|
||||||
sources.add(new ExplosionSource(creeper.position(), 3.0f));
|
sources.add(new ExplosionSource(creeper.position(), 3.0f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (AbstractMinecart cart : minecarts) {
|
for (AbstractMinecart cart : minecarts) {
|
||||||
if (cart.getType() == EntityType.TNT_MINECART && !cart.position().equals(pos)) {
|
if (cart.getType() == EntityType.TNT_MINECART && cart.position().distanceToSqr(pos) > 0.0001) {
|
||||||
sources.add(new ExplosionSource(cart.position(), 4.0f));
|
sources.add(new ExplosionSource(cart.position(), 4.0f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,17 +67,17 @@ public class ExplosionClusterHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (PrimedTnt tnt : tntEntities) {
|
for (PrimedTnt tnt : tntEntities) {
|
||||||
if (!tnt.position().equals(pos)) {
|
if (tnt.position().distanceToSqr(pos) > 0.0001) {
|
||||||
tnt.discard();
|
tnt.discard();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Creeper creeper : creepers) {
|
for (Creeper creeper : creepers) {
|
||||||
if (!creeper.position().equals(pos)) {
|
if (creeper.position().distanceToSqr(pos) > 0.0001) {
|
||||||
creeper.discard();
|
creeper.discard();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (AbstractMinecart cart : minecarts) {
|
for (AbstractMinecart cart : minecarts) {
|
||||||
if (cart.getType() == EntityType.TNT_MINECART && !cart.position().equals(pos)) {
|
if (cart.getType() == EntityType.TNT_MINECART && cart.position().distanceToSqr(pos) > 0.0001) {
|
||||||
cart.discard();
|
cart.discard();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package com.vinlanx.explosionoverhaul;
|
|||||||
|
|
||||||
import com.vinlanx.explosionoverhaul.api.IExplosionPower;
|
import com.vinlanx.explosionoverhaul.api.IExplosionPower;
|
||||||
import com.vinlanx.explosionoverhaul.mixinhelper.ExplosionAccessor;
|
import com.vinlanx.explosionoverhaul.mixinhelper.ExplosionAccessor;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
@@ -20,6 +22,8 @@ import net.minecraft.world.phys.shapes.CollisionContext;
|
|||||||
|
|
||||||
public class ServerExplosionHandler {
|
public class ServerExplosionHandler {
|
||||||
private static final double SOUND_SPEED_BLOCKS_PER_TICK = 343.0 / 20.0;
|
private static final double SOUND_SPEED_BLOCKS_PER_TICK = 343.0 / 20.0;
|
||||||
|
private static final int DUPLICATE_WINDOW_TICKS = 6;
|
||||||
|
private static final List<RecentExplosion> RECENT_EXPLOSIONS = new ArrayList<>();
|
||||||
|
|
||||||
public static void handleExplosion(ServerLevel level, Explosion explosion, List<BlockPos> affectedBlocks) {
|
public static void handleExplosion(ServerLevel level, Explosion explosion, List<BlockPos> affectedBlocks) {
|
||||||
float power = 4.0f;
|
float power = 4.0f;
|
||||||
@@ -34,6 +38,10 @@ public class ServerExplosionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
power = ExplosionClusterHandler.calculateClusteredPower(level, center, power);
|
power = ExplosionClusterHandler.calculateClusteredPower(level, center, power);
|
||||||
|
if (shouldSkipDuplicate(level, center, power)) {
|
||||||
|
affectedBlocks.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
ExplosionOverhaul.LOGGER.info("Explosion Overhaul handling explosion at {} with power {}", center, power);
|
ExplosionOverhaul.LOGGER.info("Explosion Overhaul handling explosion at {} with power {}", center, power);
|
||||||
|
|
||||||
dispatchPlayerEffects(level, center, power);
|
dispatchPlayerEffects(level, center, power);
|
||||||
@@ -240,9 +248,33 @@ public class ServerExplosionHandler {
|
|||||||
return nearest;
|
return nearest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldSkipDuplicate(ServerLevel level, Vec3 center, float power) {
|
||||||
|
long now = level.getGameTime();
|
||||||
|
synchronized (RECENT_EXPLOSIONS) {
|
||||||
|
Iterator<RecentExplosion> iterator = RECENT_EXPLOSIONS.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
RecentExplosion recent = iterator.next();
|
||||||
|
if (recent.level != level || now - recent.gameTime > DUPLICATE_WINDOW_TICKS) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double duplicateRadius = Math.max(3.0, Math.min(10.0, CraterDeformer.calculateRadius(power) * 0.25));
|
||||||
|
for (RecentExplosion recent : RECENT_EXPLOSIONS) {
|
||||||
|
if (recent.level == level && recent.center.distanceToSqr(center) <= duplicateRadius * duplicateRadius) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RECENT_EXPLOSIONS.add(new RecentExplosion(level, center, power, now));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void register() {
|
public static void register() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public record CameraShakeProfile(float intensity, int durationTicks, float pushIntensity) {
|
public record CameraShakeProfile(float intensity, int durationTicks, float pushIntensity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record RecentExplosion(ServerLevel level, Vec3 center, float power, long gameTime) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
"explosionoverhaul:soft_glow",
|
"explosionoverhaul:plasma",
|
||||||
"explosionoverhaul:soft_glow_e"
|
"explosionoverhaul:plasma_e"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user