generated from MrSphay/codex-agent-repository-kit
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user