Restore original explosion visuals
Some checks failed
Build / build (push) Failing after 6m33s

This commit is contained in:
MrSphay
2026-05-09 19:04:09 +02:00
parent 55fde4c062
commit 8584bf771f
9 changed files with 1267 additions and 396 deletions

View File

@@ -1,35 +1,51 @@
package com.vinlanx.explosionoverhaul.client;
import com.mojang.blaze3d.systems.RenderSystem;
import com.vinlanx.explosionoverhaul.Config;
import com.vinlanx.explosionoverhaul.CustomGlowParticleOptions;
import com.vinlanx.explosionoverhaul.ModParticles;
import com.vinlanx.explosionoverhaul.PlayTrackedSoundPacket;
import com.vinlanx.explosionoverhaul.PlasmaParticleOptions;
import com.vinlanx.explosionoverhaul.SmokeParticleOptions;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.BlockParticleOption;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.particles.SimpleParticleType;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import net.neoforged.neoforge.client.event.RenderGuiEvent;
public class ClientEffects {
private static final Random RANDOM = new Random();
private static final List<DelayedSound> TRACKED_SOUNDS = new ArrayList<>();
private static final List<DelayedShake> DELAYED_SHAKES = new ArrayList<>();
private static final List<PhysicsBasedExplosionEffect> EXPLOSION_EFFECTS = new ArrayList<>();
private static final List<ShockwaveEffect> SHOCKWAVE_EFFECTS = new ArrayList<>();
private static final List<LingeringFog> LINGERING_FOGS = new ArrayList<>();
private static int shakeTicks;
private static final List<GroundDustEffect> DUST_EFFECTS = new ArrayList<>();
private static final List<GroundMistEffect> MIST_EFFECTS = new ArrayList<>();
private static final List<FlashEffect> FLASH_EFFECTS = new ArrayList<>();
private static float shakeIntensity;
private static int shakeTicks;
private static float pushIntensity;
private static int flashTicks;
private static float flashPower;
private static float lastYawOffset;
private static float lastPitchOffset;
public static void addTrackedSound(PlayTrackedSoundPacket msg) {
synchronized (TRACKED_SOUNDS) {
@@ -38,30 +54,53 @@ public class ClientEffects {
}
public static Vec3 calculateSoundPosition(Player player, Vec3 explosionPos, boolean isPlayerInHouse) {
if (player == null || !isPlayerInHouse) {
if (player == null) {
return explosionPos;
}
Vec3 direction = explosionPos.subtract(player.position());
if (direction.lengthSqr() < 0.0001) {
return explosionPos;
Vec3 playerPos = player.position();
Vec3 start = isPlayerInHouse ? explosionPos : playerPos;
Vec3 end = isPlayerInHouse ? playerPos : explosionPos;
BlockHitResult hit = player.level().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, (Entity)player));
if (hit.getType() == HitResult.Type.MISS) {
Vec3 direction = explosionPos.subtract(playerPos).normalize();
if (direction.lengthSqr() < 0.001) {
direction = player.getLookAngle();
}
return playerPos.add(direction.scale(45.0));
}
return player.position().add(direction.normalize().scale(Math.min(16.0, direction.length())));
Vec3 rayDir = end.subtract(start).normalize();
return hit.getLocation().subtract(rayDir.scale(0.1));
}
public static void triggerLocalCameraShake(float intensity, int durationTicks, float pushIntensity) {
shakeIntensity = Math.max(shakeIntensity, intensity);
shakeTicks = Math.max(shakeTicks, durationTicks);
ClientEffects.pushIntensity = Math.max(ClientEffects.pushIntensity, pushIntensity);
if (!Config.CLIENT.enableCameraShake.get() && !Config.COMMON.enablePlayerShake.get()) {
return;
}
float amplifiedIntensity = Config.CLIENT.enableCameraShake.get()
? intensity * (Config.CLIENT.cameraShakeAmplifier.get().floatValue() * 10.0f)
: 0.0f;
float amplifiedPush = Config.COMMON.enablePlayerShake.get()
? pushIntensity * (Config.COMMON.playerShakeAmplifier.get().floatValue() * 5.0f)
: 0.0f;
if (amplifiedIntensity > shakeIntensity || shakeTicks == 0 || durationTicks > shakeTicks) {
shakeIntensity = amplifiedIntensity;
shakeTicks = durationTicks;
ClientEffects.pushIntensity = amplifiedPush;
}
}
public static void triggerDelayedCameraShake(float intensity, int durationTicks, float pushIntensity, int delayTicks) {
if (delayTicks <= 0) {
triggerLocalCameraShake(intensity, durationTicks, pushIntensity);
return;
}
synchronized (DELAYED_SHAKES) {
DELAYED_SHAKES.add(new DelayedShake(intensity, durationTicks, pushIntensity, Math.max(0, delayTicks)));
DELAYED_SHAKES.add(new DelayedShake(intensity, durationTicks, pushIntensity, delayTicks));
}
}
public static void triggerRealisticExplosion(Vec3 position, float power) {
if (!((Boolean)Config.CLIENT.enableExplosionParticles.get()).booleanValue()) {
if (!Config.CLIENT.enableExplosionParticles.get()) {
return;
}
synchronized (EXPLOSION_EFFECTS) {
@@ -70,68 +109,27 @@ public class ClientEffects {
}
public static void addFlashEffect(Vec3 explosionPos, float power) {
if (!((Boolean)Config.CLIENT.enableFlashEffect.get()).booleanValue()) {
if (!Config.CLIENT.enableFlashEffect.get()) {
return;
}
flashTicks = Math.max(flashTicks, Mth.clamp(9 + Mth.floor(power * 0.85f), 10, 42));
flashPower = Math.max(flashPower, power);
synchronized (FLASH_EFFECTS) {
FLASH_EFFECTS.add(new FlashEffect(explosionPos, power));
}
}
public static void onClientTick() {
Minecraft mc = Minecraft.getInstance();
synchronized (TRACKED_SOUNDS) {
TRACKED_SOUNDS.removeIf(sound -> sound.tick(mc));
}
synchronized (DELAYED_SHAKES) {
DELAYED_SHAKES.removeIf(DelayedShake::tick);
}
synchronized (EXPLOSION_EFFECTS) {
Iterator<PhysicsBasedExplosionEffect> iterator = EXPLOSION_EFFECTS.iterator();
while (iterator.hasNext()) {
PhysicsBasedExplosionEffect effect = iterator.next();
effect.tick();
if (effect.isFinished()) {
iterator.remove();
}
}
}
synchronized (SHOCKWAVE_EFFECTS) {
Iterator<ShockwaveEffect> iterator = SHOCKWAVE_EFFECTS.iterator();
while (iterator.hasNext()) {
ShockwaveEffect effect = iterator.next();
effect.tick();
if (effect.isFinished()) {
iterator.remove();
}
}
}
synchronized (LINGERING_FOGS) {
Iterator<LingeringFog> iterator = LINGERING_FOGS.iterator();
while (iterator.hasNext()) {
LingeringFog fog = iterator.next();
if (fog.tick(mc)) {
iterator.remove();
}
}
}
if (shakeTicks > 0 && mc.player != null) {
float fade = shakeTicks / Math.max(1.0f, shakeTicks + 8.0f);
float yaw = (mc.player.getRandom().nextFloat() - 0.5f) * shakeIntensity * 0.8f * fade;
float pitch = (mc.player.getRandom().nextFloat() - 0.5f) * shakeIntensity * 0.45f * fade;
mc.player.setYRot(mc.player.getYRot() + yaw);
mc.player.setXRot(Mth.clamp(mc.player.getXRot() + pitch, -89.0f, 89.0f));
if (pushIntensity > 0.02f) {
mc.player.setDeltaMovement(mc.player.getDeltaMovement().add(0.0, Math.min(0.03, pushIntensity * 0.004), 0.0));
}
--shakeTicks;
if (shakeTicks <= 0) {
shakeIntensity = 0.0f;
pushIntensity = 0.0f;
}
}
if (flashTicks > 0) {
--flashTicks;
ExplosionWindController.tick();
tickSounds(mc);
tickDelayedShakes();
tickList(EXPLOSION_EFFECTS);
tickList(SHOCKWAVE_EFFECTS);
tickList(DUST_EFFECTS);
tickList(MIST_EFFECTS);
synchronized (FLASH_EFFECTS) {
FLASH_EFFECTS.removeIf(FlashEffect::isFinished);
}
handleCameraShakeTick(mc);
}
public static void renderFlash(RenderGuiEvent.Post event) {
@@ -139,19 +137,36 @@ public class ClientEffects {
}
public static void renderFlash(GuiGraphics graphics) {
if (flashTicks <= 0) {
if (!Config.CLIENT.enableFlashEffect.get()) {
return;
}
Minecraft mc = Minecraft.getInstance();
float alpha = Mth.clamp(flashTicks / 18.0f, 0.0f, 1.0f) * ((Double)Config.CLIENT.flashMaxOpacity.get()).floatValue();
alpha = Mth.clamp(alpha * (0.7f + flashPower * 0.026f), 0.0f, 0.92f);
int color = ((int)(alpha * 255.0f) << 24) | 0xFFFFD7;
graphics.fill(0, 0, mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight(), color);
if (mc.player == null) {
return;
}
float maxOpacity = 0.0f;
synchronized (FLASH_EFFECTS) {
for (FlashEffect effect : FLASH_EFFECTS) {
maxOpacity = Math.max(maxOpacity, effect.getCurrentOpacity(mc.player));
}
}
if (maxOpacity <= 0.0f) {
return;
}
ResourceLocation flashTexture = ResourceLocation.fromNamespaceAndPath("explosionoverhaul", "textures/effects/flash.png");
int width = mc.getWindow().getGuiScaledWidth();
int height = mc.getWindow().getGuiScaledHeight();
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, maxOpacity);
graphics.blit(flashTexture, 0, 0, 0.0f, 0.0f, width, height, width, height);
RenderSystem.disableBlend();
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
}
public static void triggerShockwave(Vec3 position, float power) {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || !((Boolean)Config.CLIENT.enableShockwaveEffect.get()).booleanValue()) {
if (!Config.CLIENT.enableShockwaveEffect.get()) {
return;
}
synchronized (SHOCKWAVE_EFFECTS) {
@@ -160,109 +175,152 @@ public class ClientEffects {
}
public static void triggerDustCloud(Vec3 position, float power) {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || !((Boolean)Config.CLIENT.enableGroundDustEffect.get()).booleanValue()) {
if (!Config.CLIENT.enableGroundDustEffect.get()) {
return;
}
float quality = ((Double)Config.CLIENT.groundDustQuality.get()).floatValue();
int count = Math.min(260, Math.max(36, Math.round(power * 9.5f * quality)));
float coreScale = Mth.clamp(Math.max(1.6f, power * 0.42f), 1.6f, 10.5f);
mc.level.addParticle(new SmokeParticleOptions(coreScale, 110, 0.2f, 0.17f, 0.14f, 0.64f, true, 0.0f, 0.2f, null), position.x, position.y + 0.2, position.z, 0.0, 0.045, 0.0);
for (int i = 0; i < count; ++i) {
Vec3 velocity = randomHorizontal(mc.level.random.nextLong()).scale(0.04 + mc.level.random.nextDouble() * 0.16);
mc.level.addParticle(new SmokeParticleOptions(1.0f + mc.level.random.nextFloat() * 2.1f, 58 + mc.level.random.nextInt(62), 0.18f, 0.16f, 0.13f, 0.52f, true, 0.0f, 0.2f, null), position.x, position.y + 0.1, position.z, velocity.x, 0.025 + mc.level.random.nextDouble() * 0.05, velocity.z);
synchronized (DUST_EFFECTS) {
DUST_EFFECTS.add(new GroundDustEffect(position, power));
}
}
public static void triggerMistCloud(Vec3 position, float power) {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || !((Boolean)Config.CLIENT.enableGroundMistEffect.get()).booleanValue()) {
if (!Config.CLIENT.enableGroundMistEffect.get()) {
return;
}
int count = Math.min(160, Math.max(24, Math.round(power * 6.0f * ((Double)Config.CLIENT.groundMistQuality.get()).floatValue())));
for (int i = 0; i < count; ++i) {
Vec3 velocity = randomHorizontal(mc.level.random.nextLong()).scale(0.025 + mc.level.random.nextDouble() * 0.11);
mc.level.addParticle(new SmokeParticleOptions(1.2f + mc.level.random.nextFloat() * 2.2f, 65 + mc.level.random.nextInt(60), 0.32f, 0.31f, 0.29f, 0.34f, false, 0.0f, 0.1f, null), position.x, position.y + 0.08, position.z, velocity.x, 0.01, velocity.z);
}
synchronized (LINGERING_FOGS) {
LINGERING_FOGS.add(new LingeringFog(position, power));
synchronized (MIST_EFFECTS) {
MIST_EFFECTS.add(new GroundMistEffect(position, power));
}
}
public static void triggerLineSparks(Vec3 position, float power) {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || !((Boolean)Config.CLIENT.enableLineSparks.get()).booleanValue()) {
if (!Config.CLIENT.enableLineSparks.get()) {
return;
}
int count = Math.min(160, Math.max(14, Math.round(power * 6.0f * ((Double)Config.CLIENT.lineSparkAmountMultiplier.get()).floatValue())));
for (int i = 0; i < count; ++i) {
Vec3 velocity = randomHorizontal(mc.level.random.nextLong()).scale(0.25 + mc.level.random.nextDouble() * Math.min(1.2, power * 0.04));
mc.level.addParticle(ModParticles.LINE_SPARK.get(), position.x, position.y + 0.2, position.z, velocity.x, mc.level.random.nextDouble() * 0.35, velocity.z);
Minecraft mc = Minecraft.getInstance();
ClientLevel level = mc.level;
if (level == null) {
return;
}
double amountMultiplier = Config.CLIENT.lineSparkAmountMultiplier.get();
if (amountMultiplier <= 0.0) {
return;
}
int sparkCount = Mth.clamp((int)(power * 15.0f * amountMultiplier), 15, 500);
for (int i = 0; i < sparkCount; ++i) {
Vec3 motion = new Vec3(RANDOM.nextDouble() - 0.5, RANDOM.nextDouble() - 0.5, RANDOM.nextDouble() - 0.5).normalize();
float powerFraction = power / 100.0f;
float force = RANDOM.nextFloat() < 0.25f
? Mth.lerp(powerFraction, 0.5f, 8.0f) * (0.9f + RANDOM.nextFloat() * 0.4f)
: Mth.lerp(powerFraction, 0.3f, 4.5f) * (0.7f + RANDOM.nextFloat() * 0.4f);
motion = motion.scale(force);
level.addParticle((SimpleParticleType)ModParticles.LINE_SPARK.get(), position.x, position.y, position.z, motion.x, motion.y, motion.z);
}
}
public static void triggerAmbientCaveDust(float power) {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || mc.player == null) {
LocalPlayer player = mc.player;
ClientLevel level = mc.level;
if (player == null || level == null) {
return;
}
Vec3 center = mc.player.position();
int count = Math.min(180, Math.max(28, Math.round(power * 5.0f)));
for (int i = 0; i < count; ++i) {
double x = center.x + (mc.level.random.nextDouble() - 0.5) * 16.0;
double z = center.z + (mc.level.random.nextDouble() - 0.5) * 16.0;
double y = center.y + 3.0 + mc.level.random.nextDouble() * 9.0;
mc.level.addParticle(new SmokeParticleOptions(0.6f + mc.level.random.nextFloat() * 1.1f, 55 + mc.level.random.nextInt(45), 0.2f, 0.19f, 0.17f, 0.45f, true, 0.0f, 0.4f, null), x, y, z, 0.0, -0.015 - mc.level.random.nextDouble() * 0.04, 0.0);
int particleCount = Mth.clamp(15 + (int)((power / 10.0f) * 3.5f), 10, 70);
BlockPos playerPos = player.blockPosition();
BlockState ceilingState = Blocks.STONE.defaultBlockState();
for (int i = 3; i < 10; ++i) {
BlockState state = level.getBlockState(playerPos.above(i));
if (!state.isAir()) {
ceilingState = state;
break;
}
}
for (int i = 0; i < particleCount; ++i) {
double x = player.getX() + (RANDOM.nextDouble() - 0.5) * 16.0;
double z = player.getZ() + (RANDOM.nextDouble() - 0.5) * 16.0;
double y = player.getY() + 4.0 + RANDOM.nextDouble() * 6.0;
level.addParticle((ParticleOptions)new BlockParticleOption(ParticleTypes.BLOCK, ceilingState), x, y, z, 0.0, -0.15, 0.0);
}
}
private static Vec3 randomHorizontal(long seed) {
double angle = ((seed ^ (seed >>> 32)) & 0xFFFF) / 65535.0 * Math.PI * 2.0;
return new Vec3(Math.cos(angle), 0.0, Math.sin(angle));
private static <T> void tickList(List<T> effects) {
synchronized (effects) {
Iterator<T> iterator = effects.iterator();
while (iterator.hasNext()) {
T effect = iterator.next();
if (effect instanceof PhysicsBasedExplosionEffect explosion) {
explosion.tick();
if (explosion.isFinished()) {
iterator.remove();
}
} else if (effect instanceof ShockwaveEffect shockwave) {
shockwave.tick();
if (shockwave.isFinished()) {
iterator.remove();
}
} else if (effect instanceof GroundDustEffect dust) {
dust.tick();
if (dust.isFinished()) {
iterator.remove();
}
} else if (effect instanceof GroundMistEffect mist) {
mist.tick();
if (mist.isFinished()) {
iterator.remove();
}
}
}
}
}
private static final class LingeringFog {
private final Vec3 position;
private final float power;
private final int lifetime;
private final float radius;
private int age;
private static void tickSounds(Minecraft mc) {
synchronized (TRACKED_SOUNDS) {
TRACKED_SOUNDS.removeIf(sound -> sound.tick(mc));
}
}
private LingeringFog(Vec3 position, float power) {
this.position = position;
this.power = power;
this.lifetime = Mth.clamp(80 + Math.round(power * 4.0f), 90, 260);
this.radius = Mth.clamp(5.0f + power * 1.8f, 7.0f, 72.0f);
private static void tickDelayedShakes() {
synchronized (DELAYED_SHAKES) {
DELAYED_SHAKES.removeIf(DelayedShake::tick);
}
}
private static void handleCameraShakeTick(Minecraft mc) {
LocalPlayer player = mc.player;
if (player == null) {
lastYawOffset = 0.0f;
lastPitchOffset = 0.0f;
shakeTicks = 0;
shakeIntensity = 0.0f;
pushIntensity = 0.0f;
return;
}
private boolean tick(Minecraft mc) {
if (mc.level == null) {
return true;
}
if (++this.age >= this.lifetime) {
return true;
}
if (!((Boolean)Config.CLIENT.enableGroundMistEffect.get()).booleanValue() && !((Boolean)Config.CLIENT.enableGroundDustEffect.get()).booleanValue()) {
return false;
}
if (this.age % 2 != 0) {
return false;
}
float progress = this.age / (float)this.lifetime;
float fade = Mth.clamp(1.0f - progress, 0.0f, 1.0f);
int count = Mth.clamp(Math.round(this.power * 0.26f * fade), 1, 10);
for (int i = 0; i < count; ++i) {
double angle = mc.level.random.nextDouble() * Math.PI * 2.0;
double distance = this.radius * (0.18 + mc.level.random.nextDouble() * (0.82 + progress * 0.35));
double x = this.position.x + Math.cos(angle) * distance;
double z = this.position.z + Math.sin(angle) * distance;
double y = this.position.y + 0.04 + mc.level.random.nextDouble() * 0.55;
float scale = Mth.clamp(1.1f + this.power * 0.075f + mc.level.random.nextFloat() * 1.6f, 1.0f, 4.8f);
float alpha = Mth.clamp(0.30f * fade, 0.03f, 0.30f);
mc.level.addParticle(new SmokeParticleOptions(scale, 80 + mc.level.random.nextInt(70), 0.29f, 0.28f, 0.26f, alpha, false, 0.0f, 0.04f, null), x, y, z, Math.cos(angle) * 0.018, 0.004, Math.sin(angle) * 0.018);
}
return false;
if (lastYawOffset != 0.0f || lastPitchOffset != 0.0f) {
player.setYRot(player.getYRot() - lastYawOffset);
player.setXRot(Mth.clamp(player.getXRot() - lastPitchOffset, -89.0f, 89.0f));
lastYawOffset = 0.0f;
lastPitchOffset = 0.0f;
}
if (shakeTicks <= 0) {
shakeIntensity = 0.0f;
pushIntensity = 0.0f;
return;
}
float progress = shakeTicks / 30.0f;
float cameraIntensity = shakeIntensity * Mth.sin(progress * (float)Math.PI);
float yawChange = (RANDOM.nextFloat() - 0.5f) * 2.0f * cameraIntensity;
float pitchChange = (RANDOM.nextFloat() - 0.5f) * cameraIntensity;
player.setYRot(player.getYRot() + yawChange);
player.setXRot(Mth.clamp(player.getXRot() + pitchChange, -89.0f, 89.0f));
lastYawOffset = yawChange;
lastPitchOffset = pitchChange;
if (pushIntensity > 0.0f) {
float pushFactor = pushIntensity * Mth.sin(progress * (float)Math.PI) * 0.1f;
player.setDeltaMovement(player.getDeltaMovement().add((RANDOM.nextDouble() - 0.5) * pushFactor, 0.0, (RANDOM.nextDouble() - 0.5) * pushFactor));
}
--shakeTicks;
}
private static final class DelayedSound {
@@ -311,4 +369,104 @@ public class ClientEffects {
return true;
}
}
private static final class FlashEffect {
private final Vec3 explosionPos;
private final float power;
private final long startTime;
private final float maxDurationTicks;
private final float maxOpacity;
private final float maxDistance;
private FlashEffect(Vec3 explosionPos, float power) {
this.explosionPos = explosionPos;
this.power = power;
this.startTime = System.currentTimeMillis();
this.maxDurationTicks = calculateDuration(power) * 20.0f;
this.maxOpacity = calculateMaxOpacity(power);
this.maxDistance = calculateMaxDistance(power);
}
private float getCurrentOpacity(Player player) {
float elapsedTicks = (System.currentTimeMillis() - this.startTime) / 50.0f;
if (elapsedTicks > this.maxDurationTicks) {
return 0.0f;
}
float progress = elapsedTicks / this.maxDurationTicks;
float fadeOpacity = this.maxOpacity * (1.0f - progress * progress);
double distance = player.position().distanceTo(this.explosionPos);
float distanceFactor = Math.max(0.0f, 1.0f - (float)distance / this.maxDistance);
Vec3 viewVec = player.getLookAngle();
Vec3 dirToExplosion = this.explosionPos.subtract(player.position()).normalize();
float angleFactor = Math.max(0.0f, (float)viewVec.dot(dirToExplosion));
if (!isVisible(player, this.explosionPos)) {
return 0.0f;
}
return fadeOpacity * distanceFactor * angleFactor;
}
private boolean isVisible(Player player, Vec3 explosionPos) {
Vec3 start = player.position();
BlockHitResult hit = player.level().clip(new ClipContext(start, explosionPos, ClipContext.Block.VISUAL, ClipContext.Fluid.NONE, (Entity)player));
if (hit.getType() == HitResult.Type.MISS) {
return true;
}
if (this.power > 10.0f) {
int checks = Math.max(1, (int)(this.power / 10.0f));
for (int i = 1; i <= checks; ++i) {
Vec3 elevatedStart = start.add(0.0, 5.0 * i, 0.0);
Vec3 elevatedEnd = explosionPos.add(0.0, 5.0 * i, 0.0);
BlockHitResult elevatedHit = player.level().clip(new ClipContext(elevatedStart, elevatedEnd, ClipContext.Block.VISUAL, ClipContext.Fluid.NONE, (Entity)player));
if (elevatedHit.getType() == HitResult.Type.MISS) {
return true;
}
}
}
return false;
}
private boolean isFinished() {
return (System.currentTimeMillis() - this.startTime) / 50.0f > this.maxDurationTicks;
}
private static float calculateDuration(float power) {
if (power <= 1.0f) {
return 0.5f;
}
if (power <= 5.0f) {
return 1.0f;
}
if (power <= 10.0f) {
return 2.5f;
}
if (power <= 25.0f) {
return 3.5f;
}
return 5.5f;
}
private static float calculateMaxDistance(float power) {
if (power <= 1.0f) {
return 30.0f;
}
if (power <= 5.0f) {
return 30.0f + (power - 1.0f) * 70.0f / 4.0f;
}
if (power <= 10.0f) {
return 100.0f + (power - 5.0f) * 100.0f / 5.0f;
}
if (power <= 25.0f) {
return 200.0f + (power - 10.0f) * 200.0f / 15.0f;
}
if (power <= 40.0f) {
return 400.0f + (power - 25.0f) * 300.0f / 15.0f;
}
return 700.0f + (power - 40.0f) * 50.0f;
}
private static float calculateMaxOpacity(float power) {
float baseOpacity = power <= 1.0f ? 0.5f : power <= 5.0f ? 0.7f : power <= 10.0f ? 0.9f : 1.0f;
return baseOpacity * Config.CLIENT.flashMaxOpacity.get().floatValue();
}
}
}

View File

@@ -1,82 +1,331 @@
package com.vinlanx.explosionoverhaul.client;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.BufferUploader;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.vinlanx.explosionoverhaul.Config;
import com.vinlanx.explosionoverhaul.CustomGlowParticleOptions;
import net.minecraft.client.Camera;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.particle.ParticleRenderType;
import net.minecraft.client.particle.SpriteSet;
import net.minecraft.client.particle.TextureSheetParticle;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
import org.joml.Quaternionf;
import org.joml.Vector3f;
public class CustomGlowParticle extends TextureSheetParticle {
private final SpriteSet sprites;
private final float baseSize;
private static final int GLOW_FRAME_COUNT = 239;
private static final int SGLOW_FRAME_COUNT = 224;
private static final int FRAMES_PER_SHEET = 64;
private static final int FRAMES_PER_ROW = 8;
private final float power;
private final float initialQuadSize;
private final int animationDuration;
private final int animationType;
private final float maxRadius;
private float currentHeightPercent;
private final int peakFireEndTick;
private final int fireTransitionEndTick;
private final int smokeStartTick;
private int baseSheetIndex;
private int baseFrameOnSheet;
private int emissiveSheetIndex;
private int emissiveFrameOnSheet;
public CustomGlowParticle(ClientLevel level, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed, CustomGlowParticleOptions options, SpriteSet sprites) {
super(level, x, y, z, xSpeed, ySpeed, zSpeed);
this.sprites = sprites;
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);
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);
case VANILA -> 22 + this.random.nextInt(12);
};
this.quadSize = this.baseSize;
this.friction = 0.88f;
this.gravity = mode == Config.Client.ParticleRenderMode.VANILA ? -0.01f : -0.018f;
this.hasPhysics = false;
this.xd = xSpeed * 0.85;
this.yd = ySpeed * 0.85 + 0.01;
this.zd = zSpeed * 0.85;
this.alpha = 0.92f;
this.pickSprite(sprites);
applyModeColor(mode, options.getZone());
}
int fadeOutDurationTicks;
this.friction = 0.98f;
this.gravity = 0.0f;
this.xd = xSpeed;
this.yd = ySpeed;
this.zd = zSpeed;
this.power = options.getPower();
this.initialQuadSize = options.getScale();
this.animationType = options.getAnimationType();
this.quadSize = this.initialQuadSize;
this.maxRadius = options.getMaxRadius();
this.currentHeightPercent = options.getHeightPercent();
@Override
public ParticleRenderType getRenderType() {
return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT;
if (this.power <= 1.0f) {
this.animationDuration = 40;
fadeOutDurationTicks = 40;
} else if (this.power <= 10.0f) {
this.animationDuration = interpolate(this.power, 1.0f, 40.0f, 10.0f, 100.0f);
fadeOutDurationTicks = interpolate(this.power, 1.0f, 40.0f, 10.0f, 60.0f);
} else if (this.power <= 60.0f) {
this.animationDuration = interpolate(this.power, 10.0f, 100.0f, 60.0f, 140.0f);
fadeOutDurationTicks = interpolate(this.power, 10.0f, 60.0f, 60.0f, 80.0f);
} else if (this.power <= 100.0f) {
this.animationDuration = interpolate(this.power, 60.0f, 140.0f, 100.0f, 200.0f);
fadeOutDurationTicks = interpolate(this.power, 60.0f, 80.0f, 100.0f, 120.0f);
} else {
this.animationDuration = 200;
fadeOutDurationTicks = 120;
}
this.lifetime = this.animationDuration + fadeOutDurationTicks;
float powerFraction = Mth.clamp(this.power / 100.0f, 0.0f, 1.0f);
float totalFirePercent = Mth.lerp(powerFraction, 0.02f, 0.2f);
float cooldownToSmokePercent = Mth.lerp(powerFraction, 0.04f, 0.08f);
int totalFireDuration = (int)(this.lifetime * totalFirePercent);
this.peakFireEndTick = (int)(totalFireDuration * 0.9f);
this.fireTransitionEndTick = totalFireDuration;
this.smokeStartTick = this.fireTransitionEndTick + (int)(this.lifetime * cooldownToSmokePercent);
if (Config.CLIENT.particleRenderMode.get() == Config.Client.ParticleRenderMode.VANILA) {
this.alpha = 1.0f;
this.rCol = 1.0f;
this.gCol = 0.85f;
this.bCol = 0.5f;
}
}
@Override
public void tick() {
super.tick();
if (this.age >= this.lifetime) {
double targetSpeed;
this.xo = this.x;
this.yo = this.y;
this.zo = this.z;
if (this.age++ >= this.lifetime) {
this.remove();
return;
}
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.85f + progress * 1.15f);
if (Config.CLIENT.particleRenderMode.get() == Config.Client.ParticleRenderMode.VANILA) {
this.tickVanila();
} else if (Config.CLIENT.particleRenderMode.get() == Config.Client.ParticleRenderMode.REALISTIC_2) {
this.tickRealistic2();
} else {
this.tickRealistic();
}
this.move(this.xd, this.yd, this.zd);
this.xd *= this.friction;
this.yd *= this.friction;
this.zd *= this.friction;
float heightPercent = this.updateHeightPercent();
Vec3 direction = ExplosionWindController.getWindDirection();
if (direction.lengthSqr() > 1.0E-4 && (targetSpeed = ExplosionWindController.computeGlowSpeed(heightPercent)) > 0.0) {
double targetX = direction.x * targetSpeed;
double targetZ = direction.z * targetSpeed;
this.xd += (targetX - this.xd) * 0.1;
this.zd += (targetZ - this.zd) * 0.1;
}
}
public boolean shouldCull() {
return false;
private void tickVanila() {
this.baseSheetIndex = ExplosionTextureManager.INDEX_SOFT_GLOW;
this.quadSize = this.initialQuadSize;
if (this.age < this.peakFireEndTick) {
this.alpha = 1.0f;
this.rCol = 1.0f;
this.gCol = 0.85f;
this.bCol = 0.5f;
} else if (this.age < this.fireTransitionEndTick) {
float progress = (this.age - this.peakFireEndTick) / (float)Math.max(1, this.fireTransitionEndTick - this.peakFireEndTick);
this.alpha = 1.0f;
this.rCol = 1.0f;
this.gCol = Mth.lerp(progress, 0.85f, 0.5f);
this.bCol = Mth.lerp(progress, 0.5f, 0.1f);
} else if (this.age < this.smokeStartTick) {
float progress = (this.age - this.fireTransitionEndTick) / (float)Math.max(1, this.smokeStartTick - this.fireTransitionEndTick);
float smokeCol = 0.15f;
this.rCol = Mth.lerp(progress, 1.0f, smokeCol + 0.1f);
this.gCol = Mth.lerp(progress, 0.5f, smokeCol);
this.bCol = Mth.lerp(progress, 0.1f, smokeCol);
this.alpha = Mth.lerp(progress, 1.0f, 0.9f);
} else {
float progress = (this.age - this.smokeStartTick) / (float)Math.max(1, this.lifetime - this.smokeStartTick);
float finalSmokeCol = 0.05f;
this.gCol = this.bCol = Mth.lerp(progress, 0.15f, finalSmokeCol);
this.rCol = this.bCol;
this.alpha = 0.9f * (1.0f - progress * progress * progress);
}
}
private void applyModeColor(Config.Client.ParticleRenderMode mode, int zone) {
if (mode == Config.Client.ParticleRenderMode.VANILA) {
this.rCol = zone == 0 ? 1.0f : 0.95f;
this.gCol = zone == 0 ? 0.55f : 0.72f;
this.bCol = zone == 0 ? 0.15f : 0.3f;
private void tickRealistic2() {
this.quadSize = this.initialQuadSize * 2.0f;
this.setAnimatedFrame();
}
private void tickRealistic() {
this.setAnimatedFrame();
}
private void setAnimatedFrame() {
int frameCount;
int baseSheetStart;
int emissiveSheetStart;
if (this.animationType == 2) {
frameCount = SGLOW_FRAME_COUNT;
baseSheetStart = ExplosionTextureManager.SGLOW_BASE_START;
emissiveSheetStart = ExplosionTextureManager.SGLOW_EMISSIVE_START;
} else if (this.animationType == 1) {
frameCount = GLOW_FRAME_COUNT;
baseSheetStart = ExplosionTextureManager.GLOW2_BASE_START;
emissiveSheetStart = ExplosionTextureManager.GLOW2_EMISSIVE_START;
} else {
frameCount = GLOW_FRAME_COUNT;
baseSheetStart = ExplosionTextureManager.GLOW_BASE_START;
emissiveSheetStart = ExplosionTextureManager.GLOW_EMISSIVE_START;
}
int frameIndex;
if (this.age < this.animationDuration) {
float animProgress = this.age / (float)this.animationDuration;
float easedProgress = 1.0f - (float)Math.pow(1.0f - animProgress, 3.0);
frameIndex = (int)(easedProgress * (frameCount - 1));
this.alpha = 1.0f;
} else {
frameIndex = frameCount - 1;
int fadeDuration = Math.max(1, this.lifetime - this.animationDuration);
int ageInFade = this.age - this.animationDuration;
this.alpha = 1.0f - ageInFade / (float)fadeDuration;
}
frameIndex = Mth.clamp(frameIndex, 0, frameCount - 1);
this.baseSheetIndex = baseSheetStart + frameIndex / FRAMES_PER_SHEET;
this.baseFrameOnSheet = frameIndex % FRAMES_PER_SHEET;
this.emissiveSheetIndex = emissiveSheetStart + frameIndex / FRAMES_PER_SHEET;
this.emissiveFrameOnSheet = frameIndex % FRAMES_PER_SHEET;
}
@Override
public void render(VertexConsumer buffer, Camera renderInfo, float partialTicks) {
Vec3 cameraPos = renderInfo.getPosition();
float x = (float)(Mth.lerp(partialTicks, this.xo, this.x) - cameraPos.x());
float y = (float)(Mth.lerp(partialTicks, this.yo, this.y) - cameraPos.y());
float z = (float)(Mth.lerp(partialTicks, this.zo, this.z) - cameraPos.z());
Quaternionf quaternion = new Quaternionf(renderInfo.rotation());
if (this.roll != 0.0f) {
quaternion.rotateZ(Mth.lerp(partialTicks, this.oRoll, this.roll));
}
Vector3f[] vertices;
if (this.animationType == 2) {
float aspectRatio = 0.5629139f;
vertices = new Vector3f[]{
new Vector3f(-aspectRatio, -1.0f, 0.0f),
new Vector3f(-aspectRatio, 1.0f, 0.0f),
new Vector3f(aspectRatio, 1.0f, 0.0f),
new Vector3f(aspectRatio, -1.0f, 0.0f)};
} else {
vertices = new Vector3f[]{
new Vector3f(-1.0f, -1.0f, 0.0f),
new Vector3f(-1.0f, 1.0f, 0.0f),
new Vector3f(1.0f, 1.0f, 0.0f),
new Vector3f(1.0f, -1.0f, 0.0f)};
}
float quad = this.getQuadSize(partialTicks);
for (Vector3f vertex : vertices) {
vertex.rotate(quaternion);
vertex.mul(quad);
vertex.add(x, y, z);
}
int light = this.getLightColor(partialTicks);
int frame = Config.CLIENT.particleRenderMode.get() == Config.Client.ParticleRenderMode.VANILA ? 0 : this.baseFrameOnSheet;
ResourceLocation baseTexture = ExplosionTextureManager.getInstance().getTexture(this.baseSheetIndex);
this.renderFrame(vertices, baseTexture, frame, this.rCol, this.gCol, this.bCol, this.alpha, light);
if (Config.CLIENT.particleRenderMode.get() == Config.Client.ParticleRenderMode.REALISTIC
|| Config.CLIENT.particleRenderMode.get() == Config.Client.ParticleRenderMode.REALISTIC_2) {
ResourceLocation emissiveTexture = ExplosionTextureManager.getInstance().getTexture(this.emissiveSheetIndex);
this.renderFrame(vertices, emissiveTexture, this.emissiveFrameOnSheet, this.rCol, this.gCol, this.bCol, this.alpha, 240);
} else if (this.age < this.smokeStartTick) {
ResourceLocation emissiveTexture = ExplosionTextureManager.getInstance().getTexture(ExplosionTextureManager.INDEX_SOFT_GLOW_E);
this.renderFrame(vertices, emissiveTexture, 0, this.rCol, this.gCol, this.bCol, this.alpha * 0.8f, 240);
}
}
private void renderFrame(Vector3f[] vertices, ResourceLocation texture, int frame, float red, float green, float blue, float particleAlpha, int light) {
if (texture == null || particleAlpha <= 0.0f) {
return;
}
if (mode == Config.Client.ParticleRenderMode.REALISTIC_2) {
this.rCol = zone == 0 ? 1.0f : 0.82f;
this.gCol = zone == 0 ? 0.76f : 0.92f;
this.bCol = zone == 0 ? 0.42f : 1.0f;
return;
float u0;
float v0;
float u1;
float v1;
if (frame == 0 && Config.CLIENT.particleRenderMode.get() == Config.Client.ParticleRenderMode.VANILA) {
u0 = 0.0f;
v0 = 0.0f;
u1 = 1.0f;
v1 = 1.0f;
} else {
float frameUWidth = 1.0f / FRAMES_PER_ROW;
float frameVHeight = 1.0f / FRAMES_PER_ROW;
int col = frame % FRAMES_PER_ROW;
int row = frame / FRAMES_PER_ROW;
u0 = col * frameUWidth;
v0 = row * frameVHeight;
u1 = u0 + frameUWidth;
v1 = v0 + frameVHeight;
}
this.rCol = zone == 0 ? 1.0f : 1.0f;
this.gCol = zone == 0 ? 0.48f : 0.72f;
this.bCol = zone == 0 ? 0.08f : 0.2f;
RenderSystem.setShader(GameRenderer::getParticleShader);
RenderSystem.setShaderTexture(0, texture);
RenderSystem.enableDepthTest();
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.depthMask(true);
BufferBuilder builder = com.mojang.blaze3d.vertex.Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.PARTICLE);
builder.addVertex(vertices[0].x(), vertices[0].y(), vertices[0].z()).setUv(u0, v1).setColor(red, green, blue, particleAlpha).setLight(light);
builder.addVertex(vertices[1].x(), vertices[1].y(), vertices[1].z()).setUv(u0, v0).setColor(red, green, blue, particleAlpha).setLight(light);
builder.addVertex(vertices[2].x(), vertices[2].y(), vertices[2].z()).setUv(u1, v0).setColor(red, green, blue, particleAlpha).setLight(light);
builder.addVertex(vertices[3].x(), vertices[3].y(), vertices[3].z()).setUv(u1, v1).setColor(red, green, blue, particleAlpha).setLight(light);
BufferUploader.drawWithShader(builder.buildOrThrow());
}
@Override
public ParticleRenderType getRenderType() {
return ParticleRenderType.CUSTOM;
}
@Override
public int getLightColor(float partialTick) {
if (Config.CLIENT.particleRenderMode.get() == Config.Client.ParticleRenderMode.VANILA) {
float currentAge = this.age + partialTick;
if (currentAge < this.fireTransitionEndTick) {
return 240;
}
if (currentAge > this.smokeStartTick) {
return super.getLightColor(partialTick);
}
float progress = (currentAge - this.fireTransitionEndTick) / Math.max(1.0f, this.smokeStartTick - this.fireTransitionEndTick);
int packedAmbient = super.getLightColor(partialTick);
int skyLightAmbient = packedAmbient >> 20 & 0xF;
int blockLightAmbient = packedAmbient >> 4 & 0xF;
int skyLightCurrent = (int)Mth.lerp(progress, 15.0f, skyLightAmbient);
int blockLightCurrent = (int)Mth.lerp(progress, 15.0f, blockLightAmbient);
return skyLightCurrent << 20 | blockLightCurrent << 4;
}
return super.getLightColor(partialTick);
}
private static int interpolate(float power, float p1, float v1, float p2, float v2) {
if (p2 == p1) {
return (int)v1;
}
float fraction = (power - p1) / (p2 - p1);
return (int)(v1 + fraction * (v2 - v1));
}
private float updateHeightPercent() {
if (this.maxRadius <= 0.0f) {
return 0.5f;
}
return Math.max(0.25f, this.currentHeightPercent);
}
}

View File

@@ -1,9 +1,20 @@
package com.vinlanx.explosionoverhaul.client;
import com.vinlanx.explosionoverhaul.Config;
import net.minecraft.resources.ResourceLocation;
public class ExplosionTextureManager {
private static final ExplosionTextureManager INSTANCE = new ExplosionTextureManager();
private static final ResourceLocation FALLBACK = ResourceLocation.fromNamespaceAndPath("minecraft", "textures/particle/generic_0.png");
public static final int INDEX_SOFT_GLOW = 0;
public static final int INDEX_SOFT_GLOW_E = 1;
public static final int GLOW_BASE_START = 2;
public static final int GLOW_EMISSIVE_START = 6;
public static final int GLOW2_BASE_START = 10;
public static final int GLOW2_EMISSIVE_START = 14;
public static final int SGLOW_BASE_START = 18;
public static final int SGLOW_EMISSIVE_START = 22;
public static ExplosionTextureManager getInstance() {
return INSTANCE;
@@ -13,9 +24,48 @@ public class ExplosionTextureManager {
}
public ResourceLocation getTexture(int index) {
return ResourceLocation.fromNamespaceAndPath("minecraft", "textures/particle/generic_0.png");
if (index == INDEX_SOFT_GLOW) {
return texture("soft_glow.png");
}
if (index == INDEX_SOFT_GLOW_E) {
return texture("soft_glow_e.png");
}
ResourceLocation sheet = sheetTexture(index, Config.CLIENT.glowTextureQuality.get() == Config.Client.GlowTextureQuality.QUALITY_64);
return sheet == null ? FALLBACK : sheet;
}
public void clear() {
}
private static ResourceLocation sheetTexture(int index, boolean use64) {
if (index >= GLOW_BASE_START && index < GLOW_BASE_START + 4) {
return sheet("glow", "glow_sheet_", index - GLOW_BASE_START + 1, use64);
}
if (index >= GLOW_EMISSIVE_START && index < GLOW_EMISSIVE_START + 4) {
return sheet("glow", "glow_e_sheet_", index - GLOW_EMISSIVE_START + 1, use64);
}
if (index >= GLOW2_BASE_START && index < GLOW2_BASE_START + 4) {
return sheet("glow_2", "glow_2_sheet_", index - GLOW2_BASE_START + 1, use64);
}
if (index >= GLOW2_EMISSIVE_START && index < GLOW2_EMISSIVE_START + 4) {
return sheet("glow_2", "glow_2_e_sheet_", index - GLOW2_EMISSIVE_START + 1, use64);
}
if (index >= SGLOW_BASE_START && index < SGLOW_BASE_START + 4) {
return sheet("sglow", "sglow_sheet_", index - SGLOW_BASE_START + 1, use64);
}
if (index >= SGLOW_EMISSIVE_START && index < SGLOW_EMISSIVE_START + 4) {
return sheet("sglow", "sglow_e_sheet_", index - SGLOW_EMISSIVE_START + 1, use64);
}
return null;
}
private static ResourceLocation sheet(String folder, String prefix, int number, boolean use64) {
String qualityPart = use64 ? "/64/" : "/";
return texture(folder + qualityPart + prefix + number + ".png");
}
private static ResourceLocation texture(String path) {
return ResourceLocation.fromNamespaceAndPath("explosionoverhaul", "explosions/" + path);
}
}

View File

@@ -1,53 +1,102 @@
package com.vinlanx.explosionoverhaul.client;
import com.vinlanx.explosionoverhaul.Config;
import java.util.Random;
import net.minecraft.client.Minecraft;
import net.minecraft.world.phys.Vec3;
public class ExplosionWindController {
private static final double BASE_SPEED = 0.05;
private static final double MAX_SPEED = 0.06;
private static final double LERP = 0.03;
private static final Random RANDOM = new Random();
private static final double[] DUST_SPEED_PROFILE = new double[]{0.01, 0.03, 0.045, 0.06};
private static final double[] GLOW_SPEED_PROFILE = new double[]{0.003, 0.01, 0.018, 0.025};
public static Vec3 currentWind = Vec3.ZERO;
private static Vec3 targetDirection = Vec3.ZERO;
private static int ticksUntilDirectionShift;
private ExplosionWindController() {
}
public static void tick() {
if (!Config.CLIENT.enableWindEffect.get()) {
currentWind = Vec3.ZERO;
targetDirection = Vec3.ZERO;
return;
}
if (--ticksUntilDirectionShift <= 0 || targetDirection.lengthSqr() < 1.0E-4) {
targetDirection = randomHorizontalDirection();
ticksUntilDirectionShift = 80 + RANDOM.nextInt(120);
}
Vec3 targetWind = targetDirection.scale(BASE_SPEED * getWeatherMultiplier() * Config.CLIENT.windSpeedMultiplier.get());
currentWind = currentWind.add(targetWind.subtract(currentWind).scale(LERP));
if (currentWind.length() > MAX_SPEED) {
currentWind = currentWind.normalize().scale(MAX_SPEED);
}
}
public static void reset() {
currentWind = Vec3.ZERO;
targetDirection = Vec3.ZERO;
ticksUntilDirectionShift = 0;
}
public static Vec3 getWind() {
return Vec3.ZERO;
return Config.CLIENT.enableWindEffect.get() ? currentWind : Vec3.ZERO;
}
public static Vec3 getScaledWind(double scale) {
return Vec3.ZERO;
return getWind().scale(scale);
}
public static Vec3 getWindDirection() {
return Vec3.ZERO;
Vec3 wind = getWind();
if (wind.lengthSqr() < 1.0E-4) {
return Vec3.ZERO;
}
return new Vec3(wind.x, 0.0, wind.z).normalize();
}
public static double computeDustSpeed(double heightPercent) {
return 0.0;
return sampleProfile(heightPercent, DUST_SPEED_PROFILE) * Config.CLIENT.windSpeedMultiplier.get() * getWeatherMultiplier();
}
public static double computeGlowSpeed(double heightPercent) {
return 0.0;
return sampleProfile(heightPercent, GLOW_SPEED_PROFILE) * Config.CLIENT.windSpeedMultiplier.get() * getWeatherMultiplier();
}
public static Vec3 getDustWindVector(double heightPercent) {
return Vec3.ZERO;
return getWindDirection().scale(computeDustSpeed(heightPercent));
}
public static Vec3 getGlowWindVector(double heightPercent) {
return Vec3.ZERO;
return getWindDirection().scale(computeGlowSpeed(heightPercent));
}
public static double sampleProfile(double heightPercent, double[] speeds) {
return speeds.length == 0 ? 0.0 : speeds[0];
if (speeds.length == 0) {
return 0.0;
}
double clamped = Math.max(0.0, Math.min(1.0, heightPercent));
double scaled = clamped * (speeds.length - 1);
int lower = (int)Math.floor(scaled);
int upper = Math.min(speeds.length - 1, lower + 1);
double fraction = scaled - lower;
return speeds[lower] + (speeds[upper] - speeds[lower]) * fraction;
}
public static double getWeatherMultiplier() {
Minecraft minecraft = Minecraft.getInstance();
if (minecraft.level != null && (minecraft.level.isRaining() || minecraft.level.isThundering())) {
return 1.5;
}
return 1.0;
}
private static Vec3 randomHorizontalDirection() {
double angle = RANDOM.nextDouble() * Math.PI * 2.0;
return new Vec3(Math.cos(angle), 0.0, Math.sin(angle)).normalize();
}
}

View File

@@ -1,15 +1,189 @@
package com.vinlanx.explosionoverhaul.client;
import com.vinlanx.explosionoverhaul.Config;
import com.vinlanx.explosionoverhaul.SmokeParticleOptions;
import java.awt.Color;
import java.util.Random;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.Mth;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.MapColor;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
public class GroundDustEffect {
private final ClientLevel level;
private final Vec3 center;
private final float maxRadius;
private final int particlesPerTick;
private final int maxAge;
private final float particleBaseSize;
private final Random random = new Random();
private final int raycastFrequency;
private Color currentDustColor = new Color(128, 128, 128);
private int age;
private boolean finished;
public GroundDustEffect(Vec3 position, float power) {
this.level = Minecraft.getInstance().level;
this.center = position;
float quality = Config.CLIENT.groundDustQuality.get().floatValue();
this.raycastFrequency = Math.max(1, Config.CLIENT.groundDustRaycastFrequency.get());
float powerFraction = Mth.clamp(power / 100.0f, 0.0f, 1.0f);
this.particlesPerTick = Math.max(1, (int)(Mth.lerp(powerFraction, 40.0f, 200.0f) * quality));
this.maxAge = Math.max(1, (int)(Mth.lerp(powerFraction, 30.0f, 70.0f) * quality));
this.maxRadius = power * this.calculateRadiusMultiplier(power);
this.particleBaseSize = Mth.lerp(powerFraction, 1.0f, 12.0f);
if (this.level == null || quality <= 0.0f) {
this.finished = true;
}
}
public void tick() {
if (this.finished || this.level == null) {
return;
}
if (!Config.CLIENT.enableGroundDustEffect.get()) {
this.finished = true;
return;
}
if (++this.age > this.maxAge) {
this.finished = true;
return;
}
float progress = this.age / (float)this.maxAge;
float easedProgress = 1.0f - (float)Math.pow(1.0f - progress, 3.0);
float currentRadius = this.maxRadius * easedProgress;
if (this.age % 10 == 0) {
double checkAngle = this.random.nextDouble() * Math.PI * 2.0;
double checkX = this.center.x + Math.cos(checkAngle) * currentRadius;
double checkZ = this.center.z + Math.sin(checkAngle) * currentRadius;
BlockHitResult hit = this.level.clip(new ClipContext(
new Vec3(checkX, this.center.y + 15.0, checkZ),
new Vec3(checkX, this.center.y - 15.0, checkZ),
ClipContext.Block.COLLIDER,
ClipContext.Fluid.NONE,
null));
if (hit.getType() == HitResult.Type.BLOCK) {
Color color = getDustColorForState(this.level.getBlockState(hit.getBlockPos()));
if (color != null) {
this.currentDustColor = color;
}
}
}
double cachedY = this.center.y;
for (int i = 0; i < this.particlesPerTick; ++i) {
double angle = i / (double)this.particlesPerTick * Math.PI * 2.0 + (this.random.nextDouble() - 0.5) * 0.1;
double radiusFrac = this.random.nextDouble();
double spawnRadius = currentRadius * (0.6 + radiusFrac * 0.4);
double x = this.center.x + Math.cos(angle) * spawnRadius;
double z = this.center.z + Math.sin(angle) * spawnRadius;
if (i % this.raycastFrequency == 0) {
BlockHitResult hit = this.level.clip(new ClipContext(
new Vec3(x, this.center.y + 15.0, z),
new Vec3(x, this.center.y - 15.0, z),
ClipContext.Block.COLLIDER,
ClipContext.Fluid.NONE,
null));
if (hit.getType() != HitResult.Type.BLOCK) {
continue;
}
cachedY = hit.getLocation().y;
}
double spawnY = cachedY;
Vec3 direction = new Vec3(x - this.center.x, 0.0, z - this.center.z).normalize();
double motionStrength = Mth.lerp(1.0f - progress, 0.05f, 0.45f);
double motionX = direction.x * motionStrength + (this.random.nextDouble() - 0.5) * 0.05;
double motionZ = direction.z * motionStrength + (this.random.nextDouble() - 0.5) * 0.05;
double motionY = (this.random.nextDouble() - 0.5) * 0.02;
Vec3 windDirection = ExplosionWindController.getWindDirection();
double windSpeed = ExplosionWindController.computeDustSpeed(0.0);
if (windDirection.lengthSqr() > 1.0E-4 && windSpeed > 0.0) {
motionX += windDirection.x * windSpeed * 0.65;
motionZ += windDirection.z * windSpeed * 0.65;
}
float red;
float green;
float blue;
float alpha;
float scale;
if (radiusFrac <= 0.2) {
red = 0.2f;
green = 0.2f;
blue = 0.2f;
alpha = 0.9f;
scale = this.particleBaseSize * 0.5f;
} else if (radiusFrac <= 0.65) {
red = 0.4f;
green = 0.4f;
blue = 0.4f;
alpha = 0.6f;
scale = this.particleBaseSize * 0.75f;
} else {
red = this.currentDustColor.getRed() / 255.0f;
green = this.currentDustColor.getGreen() / 255.0f;
blue = this.currentDustColor.getBlue() / 255.0f;
alpha = 0.35f;
scale = this.particleBaseSize;
}
SmokeParticleOptions options = new SmokeParticleOptions(
scale * (0.8f + this.random.nextFloat() * 0.4f),
100 + this.random.nextInt(60),
red,
green,
blue,
alpha,
false,
(float)windSpeed,
0.0f,
null);
this.level.addParticle(options, true, x, spawnY + 0.2, z, motionX, motionY, motionZ);
}
}
public boolean isFinished() {
return true;
return this.finished;
}
private Color getDustColorForState(BlockState state) {
if (state.is(BlockTags.WOOL) || state.getMapColor((BlockGetter)this.level, BlockPos.ZERO) == MapColor.NONE) {
return null;
}
if (state.is(Blocks.GRASS_BLOCK)) {
return new Color(Blocks.DIRT.defaultBlockState().getMapColor((BlockGetter)this.level, BlockPos.ZERO).col);
}
if (state.is(Blocks.STONE) || state.is(Blocks.COBBLESTONE)) {
return new Color(Blocks.COBBLESTONE.defaultBlockState().getMapColor((BlockGetter)this.level, BlockPos.ZERO).col);
}
return new Color(state.getMapColor((BlockGetter)this.level, BlockPos.ZERO).col);
}
private float calculateRadiusMultiplier(float power) {
if (power <= 5.0f) {
return 2.0f;
}
if (power <= 40.0f) {
return Mth.lerp((power - 5.0f) / 35.0f, 2.0f, 4.0f);
}
if (power <= 80.0f) {
return Mth.lerp((power - 40.0f) / 40.0f, 4.0f, 5.0f);
}
if (power <= 100.0f) {
return Mth.lerp((power - 80.0f) / 20.0f, 5.0f, 7.0f);
}
return 7.0f;
}
}

View File

@@ -1,15 +1,141 @@
package com.vinlanx.explosionoverhaul.client;
import com.vinlanx.explosionoverhaul.Config;
import com.vinlanx.explosionoverhaul.SmokeParticleOptions;
import java.awt.Color;
import java.util.Random;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.util.Mth;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
public class GroundMistEffect {
private final ClientLevel level;
private final Vec3 center;
private final float maxRadius;
private final int particlesPerTick;
private final int maxAge;
private final float particleBaseSize;
private final Random random = new Random();
private final int raycastFrequency;
private final Color currentMistColor = new Color(255, 255, 255);
private int age;
private boolean finished;
public GroundMistEffect(Vec3 position, float power) {
this.level = Minecraft.getInstance().level;
this.center = position;
float quality = Config.CLIENT.groundMistQuality.get().floatValue();
this.raycastFrequency = Math.max(1, Config.CLIENT.groundMistRaycastFrequency.get());
float powerFraction = Mth.clamp(power / 100.0f, 0.0f, 1.0f);
this.particlesPerTick = Math.max(1, (int)(Mth.lerp(powerFraction, 40.0f, 200.0f) * quality * 1.5f));
this.maxAge = Math.max(1, (int)(Mth.lerp(powerFraction, 30.0f, 70.0f) * quality / 3.0f));
this.maxRadius = power * this.calculateRadiusMultiplier(power) * 3.0f;
this.particleBaseSize = Mth.lerp(powerFraction, 1.0f, 12.0f);
if (this.level == null || quality <= 0.0f) {
this.finished = true;
}
}
public void tick() {
if (this.finished || this.level == null) {
return;
}
if (!Config.CLIENT.enableGroundMistEffect.get()) {
this.finished = true;
return;
}
if (++this.age > this.maxAge) {
this.finished = true;
return;
}
float progress = this.age / (float)this.maxAge;
float easedProgress = 1.0f - (float)Math.pow(1.0f - progress, 3.0);
float currentRadius = this.maxRadius * easedProgress;
double cachedY = this.center.y;
for (int i = 0; i < this.particlesPerTick; ++i) {
double angle = i / (double)this.particlesPerTick * Math.PI * 2.0 + (this.random.nextDouble() - 0.5) * 0.1;
double radiusFrac = this.random.nextDouble();
double spawnRadius = currentRadius * (0.6 + radiusFrac * 0.4);
double x = this.center.x + Math.cos(angle) * spawnRadius;
double z = this.center.z + Math.sin(angle) * spawnRadius;
if (i % this.raycastFrequency == 0) {
Vec3 from = new Vec3(x, this.center.y + 15.0, z);
Vec3 to = new Vec3(x, this.center.y - 15.0, z);
BlockHitResult hit = this.level.clip(new ClipContext(from, to, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, null));
if (hit.getType() != HitResult.Type.BLOCK) {
continue;
}
cachedY = hit.getLocation().y;
}
double spawnY = cachedY;
Vec3 direction = new Vec3(x - this.center.x, 0.0, z - this.center.z).normalize();
double motionStrength = Mth.lerp(1.0f - progress, 0.02f, 0.15f);
double motionX = direction.x * motionStrength + (this.random.nextDouble() - 0.5) * 0.02;
double motionZ = direction.z * motionStrength + (this.random.nextDouble() - 0.5) * 0.02;
double motionY = -0.01 - this.random.nextDouble() * 0.02;
float red;
float green;
float blue;
float alpha;
float scale;
if (radiusFrac <= 0.2) {
red = 0.8f;
green = 0.8f;
blue = 0.8f;
alpha = 0.3f;
scale = this.particleBaseSize * 0.1667f;
} else if (radiusFrac <= 0.65) {
red = 0.9f;
green = 0.9f;
blue = 0.9f;
alpha = 0.2f;
scale = this.particleBaseSize * 0.25f;
} else {
red = this.currentMistColor.getRed() / 255.0f;
green = this.currentMistColor.getGreen() / 255.0f;
blue = this.currentMistColor.getBlue() / 255.0f;
alpha = 0.15f;
scale = this.particleBaseSize * 0.333f;
}
SmokeParticleOptions options = new SmokeParticleOptions(
scale * (0.8f + this.random.nextFloat() * 0.4f),
100 + this.random.nextInt(60),
red,
green,
blue,
alpha,
false,
0.0f,
0.0f,
null);
this.level.addParticle(options, true, x, spawnY + 0.05, z, motionX, motionY, motionZ);
}
}
public boolean isFinished() {
return true;
return this.finished;
}
private float calculateRadiusMultiplier(float power) {
if (power <= 5.0f) {
return 2.0f;
}
if (power <= 40.0f) {
return Mth.lerp((power - 5.0f) / 35.0f, 2.0f, 4.0f);
}
if (power <= 80.0f) {
return Mth.lerp((power - 40.0f) / 40.0f, 4.0f, 5.0f);
}
if (power <= 100.0f) {
return Mth.lerp((power - 80.0f) / 20.0f, 5.0f, 7.0f);
}
return 7.0f;
}
}

View File

@@ -2,164 +2,179 @@ package com.vinlanx.explosionoverhaul.client;
import com.vinlanx.explosionoverhaul.Config;
import com.vinlanx.explosionoverhaul.CustomGlowParticleOptions;
import com.vinlanx.explosionoverhaul.ModParticles;
import com.vinlanx.explosionoverhaul.PlasmaParticleOptions;
import com.vinlanx.explosionoverhaul.SmokeParticleOptions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
public class PhysicsBasedExplosionEffect {
private static final int MAX_AGE = 8;
private final ClientLevel level;
private final Random random;
private final Vec3 position;
private final float power;
private final int lifetime;
private final float stemHeight;
private final float capRadius;
private final float stemRadius;
private final float maxRadius;
private final int particleCount;
private final float particleScaleBase;
private final int totalSparks;
private final List<Vec3> sparkDirections;
private int age;
private boolean finished;
private float sparkSpawnAccumulator;
private int sparkIndex;
public PhysicsBasedExplosionEffect(Vec3 position, float power) {
this.position = position;
this.power = power;
this.lifetime = Mth.clamp(64 + Math.round(power * 4.2f), 78, 210);
this.stemHeight = Mth.clamp(3.6f + power * 0.42f, 4.5f, 28.0f);
this.capRadius = Mth.clamp(3.0f + power * 0.46f, 4.0f, 26.0f);
this.stemRadius = Mth.clamp(0.75f + power * 0.055f, 0.9f, 4.0f);
this.spawnInitialBurst();
this.random = new Random();
this.level = Minecraft.getInstance().level;
if (this.level == null) {
this.finished = true;
this.maxRadius = 0.0f;
this.particleCount = 0;
this.particleScaleBase = 0.0f;
this.totalSparks = 0;
this.sparkDirections = List.of();
return;
}
float calcPower = Mth.clamp(power, 1.0f, 100.0f);
float powerFraction = calcPower / 100.0f;
this.maxRadius = Mth.lerp(powerFraction, 3.0f, 40.0f);
this.particleCount = (int)Mth.lerp(powerFraction, 40.0f, 400.0f);
this.particleScaleBase = Mth.lerp(powerFraction, 5.0f, 37.5f);
this.totalSparks = (int)Mth.lerp(powerFraction, 3.0f, 150.0f);
this.sparkDirections = new ArrayList<>();
if (this.totalSparks > 0) {
double goldenRatio = (1.0 + Math.sqrt(5.0)) / 2.0;
double angleIncrement = Math.PI * 2.0 * goldenRatio;
for (int i = 0; i < this.totalSparks; ++i) {
double denominator = Math.max(1, this.totalSparks - 1);
double y = 1.0 - (double)i / denominator * 2.0;
double radius = Math.sqrt(Math.max(0.0, 1.0 - y * y));
double theta = angleIncrement * i;
double x = Math.cos(theta) * radius;
double z = Math.sin(theta) * radius;
this.sparkDirections.add(new Vec3(x, z, y).normalize());
}
Collections.shuffle(this.sparkDirections, this.random);
}
}
public void tick() {
++this.age;
ClientLevel level = Minecraft.getInstance().level;
if (level == null) {
if (this.finished) {
return;
}
float progress = this.age / (float)this.lifetime;
if (progress < 0.62f) {
this.spawnRisingStem(level, progress);
if (!((Boolean)Config.CLIENT.enableExplosionParticles.get()).booleanValue()) {
this.finished = true;
return;
}
if (progress > 0.06f && progress < 0.86f) {
this.spawnMushroomCap(level, progress);
++this.age;
if (this.age > MAX_AGE) {
this.finished = true;
return;
}
if (progress < 0.58f && ((Boolean)Config.CLIENT.enablePlasmaParticles.get()).booleanValue()) {
this.spawnPlasmaCore(level, progress);
if (Config.CLIENT.particleRenderMode.get() == Config.Client.ParticleRenderMode.REALISTIC_2) {
this.tickRealistic2();
} else {
this.tickRegular();
}
if (progress > 0.34f && progress < 0.94f) {
this.spawnTrailingSmoke(level, progress);
}
private void tickRealistic2() {
if (this.age == 1) {
float progress = this.age / (float)MAX_AGE;
float currentRadius = this.maxRadius * (float)Math.pow(progress, 0.4);
Vec3 particlePos = this.position.add((this.random.nextDouble() - 0.5) * currentRadius, (this.random.nextDouble() - 0.5) * currentRadius, (this.random.nextDouble() - 0.5) * currentRadius);
Vec3 motion = new Vec3((this.random.nextDouble() - 0.5) * 0.05, (this.random.nextDouble() - 0.5) * 0.05, (this.random.nextDouble() - 0.5) * 0.05);
double distance3D = particlePos.distanceTo(this.position);
double heightPercent = this.maxRadius <= 0.0f ? 0.5 : Mth.clamp(distance3D / this.maxRadius, 0.25, 1.0);
Vec3 windDirection = ExplosionWindController.getWindDirection();
double glowSpeed = ExplosionWindController.computeGlowSpeed(heightPercent);
if (windDirection.lengthSqr() > 1.0E-4 && glowSpeed > 0.0) {
motion = motion.add(windDirection.scale(glowSpeed * 0.6));
}
int zone = particlePos.distanceTo(this.position) / this.maxRadius < 0.4 ? 1 : 0;
float particleScale = this.particleScaleBase * ((Double)Config.CLIENT.particleSizeScale.get()).floatValue();
int animationType = this.power <= 5.0f ? 2 : this.random.nextInt(2);
CustomGlowParticleOptions options = new CustomGlowParticleOptions(zone, this.power, particleScale, animationType, (float)this.position.y, this.maxRadius, (float)heightPercent);
this.level.addParticle(options, particlePos.x, particlePos.y, particlePos.z, motion.x, motion.y, motion.z);
}
this.spawnPlasmaSparks(this.maxRadius * (float)Math.pow(this.age / (float)MAX_AGE, 0.4));
}
private void tickRegular() {
int particlesThisTick = this.particleCount / MAX_AGE;
if (this.age == MAX_AGE) {
particlesThisTick = this.particleCount - particlesThisTick * (MAX_AGE - 1);
}
float progress = this.age / (float)MAX_AGE;
float currentRadius = this.maxRadius * (float)Math.pow(progress, 0.4);
for (int i = 0; i < particlesThisTick; ++i) {
double u = this.random.nextDouble();
double v = this.random.nextDouble();
double theta = Math.PI * 2.0 * u;
double phi = Math.acos(2.0 * v - 1.0);
double r = currentRadius;
Vec3 particlePos = this.position.add(r * Math.sin(phi) * Math.cos(theta), r * Math.sin(phi) * Math.sin(theta), r * Math.cos(phi));
Vec3 motion = new Vec3((this.random.nextDouble() - 0.5) * 0.05, (this.random.nextDouble() - 0.5) * 0.05, (this.random.nextDouble() - 0.5) * 0.05);
double distance3D = particlePos.distanceTo(this.position);
double heightPercent = this.maxRadius <= 0.0f ? 0.5 : Mth.clamp(distance3D / this.maxRadius, 0.25, 1.0);
Vec3 windDirection = ExplosionWindController.getWindDirection();
double glowSpeed = ExplosionWindController.computeGlowSpeed(heightPercent);
if (windDirection.lengthSqr() > 1.0E-4 && glowSpeed > 0.0) {
motion = motion.add(windDirection.scale(glowSpeed * 0.6));
}
int zone = particlePos.distanceTo(this.position) / this.maxRadius < 0.4 ? 1 : 0;
float particleScale = this.particleScaleBase * (0.8f + this.random.nextFloat() * 0.4f) * ((Double)Config.CLIENT.particleSizeScale.get()).floatValue();
int animationType = this.power <= 5.0f ? 2 : this.random.nextInt(2);
CustomGlowParticleOptions options = new CustomGlowParticleOptions(zone, this.power, particleScale, animationType, (float)this.position.y, this.maxRadius, (float)heightPercent);
this.level.addParticle(options, particlePos.x, particlePos.y, particlePos.z, motion.x, motion.y, motion.z);
}
this.spawnPlasmaSparks(currentRadius);
}
private void spawnPlasmaSparks(float currentRadius) {
if (this.totalSparks <= 0 || this.sparkIndex >= this.totalSparks || !((Boolean)Config.CLIENT.enablePlasmaParticles.get()).booleanValue()) {
return;
}
this.sparkSpawnAccumulator += this.totalSparks / (float)MAX_AGE;
int sparksToSpawn = (int)this.sparkSpawnAccumulator;
if (sparksToSpawn <= 0) {
return;
}
this.sparkSpawnAccumulator -= sparksToSpawn;
for (int j = 0; j < sparksToSpawn && this.sparkIndex < this.totalSparks; ++j) {
Vec3 plasmaMotion = this.sparkDirections.get(this.sparkIndex++);
Vec3 plasmaSpawnPos = this.position.add(plasmaMotion.scale(currentRadius * 0.8));
float strongSparkChance = 0.1f;
if (this.power > 10.0f) {
float powerFractionForChance = inverseLerp(this.power, 10.0f, 100.0f);
strongSparkChance = Mth.lerp(powerFractionForChance, 0.1f, 0.35f);
}
float force = this.random.nextFloat() < strongSparkChance
? Mth.lerp(this.power / 100.0f, 2.0f, 14.0f) * (0.9f + this.random.nextFloat() * 0.4f)
: Mth.lerp(this.power / 100.0f, 1.3f, 6.5f) * (0.6f + this.random.nextFloat() * 0.4f);
plasmaMotion = plasmaMotion.scale(force);
this.level.addParticle(new PlasmaParticleOptions(this.power), true, plasmaSpawnPos.x, plasmaSpawnPos.y, plasmaSpawnPos.z, plasmaMotion.x, plasmaMotion.y, plasmaMotion.z);
}
}
public boolean isFinished() {
return this.age >= this.lifetime;
return this.finished;
}
private void spawnInitialBurst() {
ClientLevel level = Minecraft.getInstance().level;
if (level == null) {
return;
private static float inverseLerp(float value, float min, float max) {
if (max == min) {
return 0.0f;
}
float modeScale = modeScale();
int glowCount = Mth.clamp(Math.round(this.power * 9.0f * modeScale), 28, 220);
for (int i = 0; i < glowCount; ++i) {
Vec3 velocity = randomDirection(level).scale(0.08 + level.random.nextDouble() * 0.42);
level.addParticle(new CustomGlowParticleOptions(i % 2, this.power, 0.8f + level.random.nextFloat() * 0.9f, i % 3, (float)this.position.y, this.capRadius, 0.0f), this.position.x, this.position.y + 0.2, this.position.z, velocity.x, Math.abs(velocity.y) * 0.45 + 0.04, velocity.z);
}
int smokeCount = Mth.clamp(Math.round(this.power * 5.5f * modeScale), 26, 150);
for (int i = 0; i < smokeCount; ++i) {
Vec3 velocity = randomDirection(level).scale(0.03 + level.random.nextDouble() * 0.18);
float scale = 1.0f + level.random.nextFloat() * Math.max(1.8f, this.power * 0.22f);
level.addParticle(new SmokeParticleOptions(scale, 70 + level.random.nextInt(65), 0.22f, 0.19f, 0.15f, 0.64f, true, 0.0f, 0.12f, null), this.position.x, this.position.y + 0.15, this.position.z, velocity.x, Math.abs(velocity.y) * 0.22 + 0.03, velocity.z);
}
int sparks = Mth.clamp(Math.round(this.power * 4.2f * modeScale), 12, 120);
for (int i = 0; i < sparks; ++i) {
Vec3 velocity = randomDirection(level).scale(0.45 + level.random.nextDouble() * 1.35);
level.addParticle(ModParticles.LINE_SPARK.get(), this.position.x, this.position.y + 0.25, this.position.z, velocity.x, Math.abs(velocity.y) * 0.52, velocity.z);
}
}
private void spawnRisingStem(ClientLevel level, float progress) {
float modeScale = modeScale();
int count = Mth.clamp(Math.round(this.power * 0.28f * modeScale), 2, 10);
float lift = this.stemHeight * Mth.clamp(progress / 0.62f, 0.0f, 1.0f);
for (int i = 0; i < count; ++i) {
double angle = level.random.nextDouble() * Math.PI * 2.0;
double radius = this.stemRadius * Math.sqrt(level.random.nextDouble());
double x = this.position.x + Math.cos(angle) * radius;
double z = this.position.z + Math.sin(angle) * radius;
double y = this.position.y + 0.4 + level.random.nextDouble() * Math.max(1.4, lift);
float fade = 1.0f - progress * 0.45f;
float scale = Mth.clamp(1.3f + this.power * 0.16f + level.random.nextFloat() * 1.4f, 1.2f, 7.2f);
level.addParticle(new SmokeParticleOptions(scale, 92 + level.random.nextInt(76), 0.17f, 0.15f, 0.13f, 0.58f * fade, true, 0.0f, progress, null), x, y, z, Math.cos(angle) * 0.025, 0.075 + level.random.nextDouble() * 0.065, Math.sin(angle) * 0.025);
}
}
private void spawnMushroomCap(ClientLevel level, float progress) {
float capProgress = Mth.clamp((progress - 0.06f) / 0.58f, 0.0f, 1.0f);
float spread = this.capRadius * easeOutCubic(capProgress);
float capY = (float)this.position.y + this.stemHeight * (0.72f + capProgress * 0.28f);
int count = Mth.clamp(Math.round(this.power * 0.34f * modeScale()), 2, 14);
for (int i = 0; i < count; ++i) {
double angle = level.random.nextDouble() * Math.PI * 2.0;
double radius = spread * (0.35 + level.random.nextDouble() * 0.78);
double x = this.position.x + Math.cos(angle) * radius;
double z = this.position.z + Math.sin(angle) * radius;
double y = capY + (level.random.nextDouble() - 0.35) * Math.max(1.0, this.stemHeight * 0.18);
float alpha = Mth.clamp(0.64f * (1.0f - progress * 0.55f), 0.16f, 0.64f);
float scale = Mth.clamp(1.6f + this.power * 0.18f + level.random.nextFloat() * 2.0f, 1.4f, 8.4f);
Vec3 outward = new Vec3(x - this.position.x, 0.0, z - this.position.z);
if (outward.lengthSqr() < 0.0001) {
outward = new Vec3(Math.cos(angle), 0.0, Math.sin(angle));
}
Vec3 velocity = outward.normalize().scale(0.035 + capProgress * 0.055).add(0.0, 0.02 + level.random.nextDouble() * 0.035, 0.0);
level.addParticle(new SmokeParticleOptions(scale, 100 + level.random.nextInt(92), 0.18f, 0.16f, 0.14f, alpha, true, 0.0f, capProgress, null), x, y, z, velocity.x, velocity.y, velocity.z);
}
}
private void spawnPlasmaCore(ClientLevel level, float progress) {
int count = Mth.clamp(Math.round(this.power * 0.16f * modeScale()), 1, 8);
for (int i = 0; i < count; ++i) {
Vec3 velocity = randomDirection(level).scale(0.18 + level.random.nextDouble() * 0.78);
double y = this.position.y + 0.25 + level.random.nextDouble() * this.stemHeight * Mth.clamp(progress * 1.6f, 0.12f, 1.0f);
level.addParticle(new PlasmaParticleOptions(this.power), this.position.x, y, this.position.z, velocity.x, Math.abs(velocity.y) * 0.45, velocity.z);
}
}
private void spawnTrailingSmoke(ClientLevel level, float progress) {
if (!((Boolean)Config.CLIENT.enableGroundMistEffect.get()).booleanValue()) {
return;
}
float fade = 1.0f - progress;
int count = Mth.clamp(Math.round(this.power * 0.14f * modeScale()), 1, 6);
for (int i = 0; i < count; ++i) {
double angle = level.random.nextDouble() * Math.PI * 2.0;
double radius = this.capRadius * (0.15 + level.random.nextDouble() * 0.95);
double x = this.position.x + Math.cos(angle) * radius;
double z = this.position.z + Math.sin(angle) * radius;
double y = this.position.y + 0.1 + level.random.nextDouble() * 1.2;
float scale = Mth.clamp(1.0f + this.power * 0.1f + level.random.nextFloat() * 1.7f, 1.0f, 5.8f);
level.addParticle(new SmokeParticleOptions(scale, 84 + level.random.nextInt(70), 0.26f, 0.25f, 0.23f, 0.28f * fade, false, 0.0f, 0.05f, null), x, y, z, Math.cos(angle) * 0.025, 0.006, Math.sin(angle) * 0.025);
}
}
private static Vec3 randomDirection(ClientLevel level) {
double yaw = level.random.nextDouble() * Math.PI * 2.0;
double y = level.random.nextDouble() * 1.15 - 0.25;
double horizontal = Math.sqrt(Math.max(0.0, 1.0 - y * y));
return new Vec3(Math.cos(yaw) * horizontal, y, Math.sin(yaw) * horizontal);
}
private static float modeScale() {
return switch (Config.CLIENT.particleRenderMode.get()) {
case REALISTIC -> 1.0f;
case REALISTIC_2 -> 1.12f;
case VANILA -> 0.68f;
};
}
private static float easeOutCubic(float t) {
float clamped = Mth.clamp(t, 0.0f, 1.0f);
float inv = 1.0f - clamped;
return 1.0f - inv * inv * inv;
return Mth.clamp((value - min) / (max - min), 0.0f, 1.0f);
}
}

View File

@@ -1,60 +1,74 @@
package com.vinlanx.explosionoverhaul.client;
import com.vinlanx.explosionoverhaul.Config;
import com.vinlanx.explosionoverhaul.ModParticles;
import com.vinlanx.explosionoverhaul.SmokeParticleOptions;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
public class ShockwaveEffect {
private final Vec3 position;
private final float power;
private final int lifetime;
private final int maxAge;
private final float maxRadius;
private int age;
private boolean finished;
public ShockwaveEffect(Vec3 position, float power) {
this.position = position;
this.power = power;
this.lifetime = Mth.clamp(18 + Math.round(power * 0.9f), 24, 72);
this.maxRadius = Mth.clamp(8.0f + power * 2.9f, 10.0f, 96.0f);
float clampedPower = Mth.clamp(power, 5.0f, 100.0f);
float powerFraction = inverseLerp(clampedPower, 5.0f, 100.0f);
this.maxAge = (int)Mth.lerp(powerFraction, 4.0f, 7.0f);
float fireballRadius = Mth.lerp(clampedPower / 100.0f, 3.0f, 40.0f);
float normalizedPowerSqrt = inverseLerp((float)Math.sqrt(clampedPower), (float)Math.sqrt(5.0), (float)Math.sqrt(100.0));
float shockwaveMultiplier = Mth.lerp(normalizedPowerSqrt, 2.0f, 8.0f);
this.maxRadius = fireballRadius * shockwaveMultiplier * 3.0f;
}
public void tick() {
++this.age;
ClientLevel level = Minecraft.getInstance().level;
if (level == null || !((Boolean)Config.CLIENT.enableShockwaveEffect.get()).booleanValue()) {
if (this.finished) {
return;
}
float progress = this.age / (float)this.lifetime;
float radius = this.maxRadius * easeOutCubic(progress);
float fade = Mth.clamp(1.0f - progress, 0.0f, 1.0f);
int points = Mth.clamp(Math.round(8.0f + this.power * 1.15f), 12, 44);
for (int i = 0; i < points; ++i) {
double angle = (Math.PI * 2.0 * i / points) + level.random.nextDouble() * 0.22;
double x = this.position.x + Math.cos(angle) * radius;
double z = this.position.z + Math.sin(angle) * radius;
double y = this.position.y + 0.08 + level.random.nextDouble() * 0.35;
double outwardSpeed = 0.18 + Math.min(0.9, this.power * 0.025);
if (progress < 0.72f) {
level.addParticle(ModParticles.LINE_SPARK.get(), x, y + 0.16, z, Math.cos(angle) * outwardSpeed, 0.015 + level.random.nextDouble() * 0.04, Math.sin(angle) * outwardSpeed);
}
if (((Boolean)Config.CLIENT.enableGroundDustEffect.get()).booleanValue()) {
float scale = Mth.clamp(0.8f + this.power * 0.055f + level.random.nextFloat() * 0.8f, 0.8f, 4.2f);
level.addParticle(new SmokeParticleOptions(scale, 42 + level.random.nextInt(36), 0.28f, 0.25f, 0.21f, 0.34f * fade, true, 0.0f, 0.05f, null), x, y, z, Math.cos(angle) * 0.055, 0.012, Math.sin(angle) * 0.055);
}
if (!Config.CLIENT.enableShockwaveEffect.get()) {
this.finished = true;
return;
}
ClientLevel level = Minecraft.getInstance().level;
if (level == null || ++this.age > this.maxAge) {
this.finished = true;
return;
}
float powerFraction = inverseLerp(Mth.clamp(this.power, 5.0f, 100.0f), 5.0f, 100.0f);
int particlesThisTick = (int)Mth.lerp(powerFraction, 70.0f, 400.0f);
float progress = this.age / (float)this.maxAge;
float currentRadius = this.maxRadius * (float)Math.sin(progress * Math.PI / 2.0);
float shellThickness = Mth.lerp(powerFraction, 0.5f, 4.0f);
for (int i = 0; i < particlesThisTick; ++i) {
double u = level.random.nextDouble();
double v = level.random.nextDouble();
double theta = Math.PI * 2.0 * u;
double phi = Math.acos(2.0 * v - 1.0);
double radius = currentRadius + (level.random.nextDouble() - 0.5) * shellThickness;
double x = this.position.x + radius * Math.sin(phi) * Math.cos(theta);
double y = this.position.y + radius * Math.sin(phi) * Math.sin(theta);
double z = this.position.z + radius * Math.cos(phi);
level.addParticle(ParticleTypes.CLOUD, x, y, z, 0.0, 0.0, 0.0);
}
}
public boolean isFinished() {
return this.age >= this.lifetime;
return this.finished;
}
private static float easeOutCubic(float t) {
float clamped = Mth.clamp(t, 0.0f, 1.0f);
float inv = 1.0f - clamped;
return 1.0f - inv * inv * inv;
private static float inverseLerp(float value, float min, float max) {
if (max == min) {
return 0.0f;
}
return Mth.clamp((value - min) / (max - min), 0.0f, 1.0f);
}
}

View File

@@ -1,38 +1,44 @@
package com.vinlanx.explosionoverhaul.client;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.vinlanx.explosionoverhaul.SmokeParticleOptions;
import net.minecraft.client.Camera;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.particle.ParticleProvider;
import net.minecraft.client.particle.ParticleRenderType;
import net.minecraft.client.particle.SpriteSet;
import net.minecraft.client.particle.TextureSheetParticle;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
public class SmokeParticle extends TextureSheetParticle {
private final SpriteSet sprites;
private final float startSize;
private final float startAlpha;
private final float initialAlpha;
private final boolean heavy;
private final float windSpeed;
private final float heightPercent;
protected SmokeParticle(ClientLevel level, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed, SmokeParticleOptions options, SpriteSet sprites) {
super(level, x, y, z, xSpeed, ySpeed, zSpeed);
this.sprites = sprites;
this.lifetime = Math.max(6, options.getLifetime());
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.lifetime = Math.max(1, options.getLifetime());
this.quadSize = options.getScale();
this.rCol = options.getRed();
this.gCol = options.getGreen();
this.bCol = options.getBlue();
this.alpha = this.startAlpha;
this.friction = options.isHeavy() ? 0.94f : 0.9f;
this.gravity = options.isHeavy() ? -0.004f : -0.012f;
this.alpha = options.getAlpha();
this.initialAlpha = this.alpha;
this.heavy = options.isHeavy();
this.windSpeed = options.getWindSpeed();
this.heightPercent = options.getHeightPercent();
this.friction = 0.96f;
this.gravity = 0.0f;
this.hasPhysics = false;
this.xd = xSpeed + options.getWindSpeed() * 0.02;
this.yd = ySpeed + 0.008 + options.getHeightPercent() * 0.01;
this.xd = xSpeed;
this.yd = ySpeed;
this.zd = zSpeed;
this.pickSprite(sprites);
this.setSpriteFromAge(sprites);
}
@Override
@@ -40,20 +46,50 @@ public class SmokeParticle extends TextureSheetParticle {
return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT;
}
public boolean shouldCull() {
return false;
@Override
public void render(VertexConsumer buffer, Camera renderInfo, float partialTicks) {
RenderSystem.depthMask(this.alpha > 0.3f);
super.render(buffer, renderInfo, partialTicks);
RenderSystem.depthMask(true);
}
@Override
public void tick() {
super.tick();
if (this.age >= this.lifetime) {
this.xo = this.x;
this.yo = this.y;
this.zo = this.z;
if (this.age++ >= this.lifetime) {
this.remove();
return;
}
float progress = this.age / (float)this.lifetime;
if (this.heavy) {
this.yd = -0.04;
}
this.move(this.xd, this.yd, this.zd);
this.xd *= this.friction;
this.yd *= this.friction;
this.zd *= this.friction;
Vec3 direction = ExplosionWindController.getWindDirection();
if (this.windSpeed > 0.0f && direction.lengthSqr() > 1.0E-4) {
Vec3 target = direction.scale(this.windSpeed);
this.xd += (target.x - this.xd) * 0.12;
this.zd += (target.z - this.zd) * 0.12;
} else if (this.heightPercent > 0.0f) {
Vec3 wind = ExplosionWindController.getDustWindVector(this.heightPercent);
this.xd += (wind.x - this.xd) * 0.08;
this.zd += (wind.z - this.zd) * 0.08;
}
float lifeProgress = this.age / (float)this.lifetime;
this.alpha = this.initialAlpha * (1.0f - lifeProgress);
this.setSpriteFromAge(this.sprites);
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);
}
public boolean shouldCull() {
return false;
}
public static class Provider implements ParticleProvider<SmokeParticleOptions> {