generated from MrSphay/codex-agent-repository-kit
This commit is contained in:
@@ -18,7 +18,7 @@ minecraft_version=1.21.1
|
||||
# as they do not follow standard versioning conventions.
|
||||
minecraft_version_range=[1.21.1]
|
||||
# The Neo version must agree with the Minecraft version to get a valid artifact
|
||||
neo_version=21.1.225
|
||||
neo_version=21.1.228
|
||||
# The loader version range can only use the major version of FML as bounds
|
||||
loader_version_range=[1,)
|
||||
|
||||
|
||||
@@ -1,20 +1,152 @@
|
||||
package com.vinlanx.explosionoverhaul;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public class AmbientExplosionManager {
|
||||
private static final Map<ServerPlayer, Integer> PLAYER_TIMERS = new HashMap<>();
|
||||
private static final Map<ServerPlayer, AmbientShot> PENDING_SHOTS = new HashMap<>();
|
||||
|
||||
public static void onServerTick(MinecraftServer server) {
|
||||
if (!((Boolean)Config.COMMON.ambient.enableAmbientExplosions.get()).booleanValue()) {
|
||||
PLAYER_TIMERS.clear();
|
||||
PENDING_SHOTS.clear();
|
||||
return;
|
||||
}
|
||||
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
|
||||
PLAYER_TIMERS.computeIfAbsent(player, AmbientExplosionManager::nextDelay);
|
||||
}
|
||||
Iterator<Map.Entry<ServerPlayer, Integer>> timers = PLAYER_TIMERS.entrySet().iterator();
|
||||
while (timers.hasNext()) {
|
||||
Map.Entry<ServerPlayer, Integer> entry = timers.next();
|
||||
ServerPlayer player = entry.getKey();
|
||||
if (player.isRemoved()) {
|
||||
timers.remove();
|
||||
continue;
|
||||
}
|
||||
int ticks = entry.getValue() - 1;
|
||||
if (ticks > 0) {
|
||||
entry.setValue(ticks);
|
||||
continue;
|
||||
}
|
||||
playAmbient(player);
|
||||
entry.setValue(nextDelay(player));
|
||||
}
|
||||
Iterator<Map.Entry<ServerPlayer, AmbientShot>> shots = PENDING_SHOTS.entrySet().iterator();
|
||||
while (shots.hasNext()) {
|
||||
Map.Entry<ServerPlayer, AmbientShot> entry = shots.next();
|
||||
if (entry.getValue().tick(entry.getKey())) {
|
||||
shots.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void onPlayerLoggedIn(ServerPlayer player) {
|
||||
PLAYER_TIMERS.put(player, nextDelay(player));
|
||||
}
|
||||
|
||||
public static void onPlayerLoggedOut(ServerPlayer player) {
|
||||
PLAYER_TIMERS.remove(player);
|
||||
PENDING_SHOTS.remove(player);
|
||||
}
|
||||
|
||||
public static ServerExplosionHandler.CameraShakeProfile determineCameraShakeProfile(float power, double distance, boolean playerInCave, ServerLevel level) {
|
||||
return new ServerExplosionHandler.CameraShakeProfile(0.0f, 0, 0.0f);
|
||||
return ServerExplosionHandler.determineCameraShakeProfile(power, distance, playerInCave, level);
|
||||
}
|
||||
|
||||
private static void playAmbient(ServerPlayer player) {
|
||||
ServerLevel level = player.serverLevel();
|
||||
int minDistance = (Integer)Config.COMMON.ambient.minExplosionDistance.get();
|
||||
int maxDistance = (Integer)Config.COMMON.ambient.maxExplosionDistance.get();
|
||||
int distance = Mth.randomBetweenInclusive(level.random, minDistance, maxDistance);
|
||||
double angle = level.random.nextDouble() * Math.PI * 2.0;
|
||||
Vec3 pos = player.position().add(Math.cos(angle) * distance, level.random.nextInt(80) - 20, Math.sin(angle) * distance);
|
||||
float power = weightedPower(level);
|
||||
boolean cave = !level.canSeeSky(player.blockPosition()) && ((Boolean)Config.COMMON.ambient.soundTypes.enableCaveSounds.get()).booleanValue();
|
||||
ResourceLocation sound = ServerExplosionHandler.soundFor(power, distance, cave, cave, false);
|
||||
PacketHandler.sendToPlayer(player, new PlayTrackedSoundPacket(pos, sound, 0.9f, 0.82f + level.random.nextFloat() * 0.18f, 0L, false));
|
||||
if (cave && ((Boolean)Config.COMMON.ambient.soundTypes.enableAmbientCaveDust.get()).booleanValue()) {
|
||||
PacketHandler.sendToPlayer(player, new SpawnAmbientCaveDustPacket(power));
|
||||
}
|
||||
int chainWeight = (Integer)Config.COMMON.ambient.scenarios.chainReactionWeight.get();
|
||||
int shellWeight = (Integer)Config.COMMON.ambient.scenarios.shellingWeight.get();
|
||||
int roll = level.random.nextInt(Math.max(1, chainWeight + shellWeight + (Integer)Config.COMMON.ambient.scenarios.singleExplosionWeight.get()));
|
||||
if (roll < chainWeight) {
|
||||
int shots = Mth.randomBetweenInclusive(level.random, (Integer)Config.COMMON.ambient.scenarios.minChainReactionShots.get(), (Integer)Config.COMMON.ambient.scenarios.maxChainReactionShots.get());
|
||||
PENDING_SHOTS.put(player, new AmbientShot(pos, power, shots, 12, true));
|
||||
} else if (roll < chainWeight + shellWeight) {
|
||||
int delay = Mth.randomBetweenInclusive(level.random, (Integer)Config.COMMON.ambient.scenarios.minShellingDelay.get(), (Integer)Config.COMMON.ambient.scenarios.maxShellingDelay.get());
|
||||
PENDING_SHOTS.put(player, new AmbientShot(pos, power * 1.25f, 1, delay, false));
|
||||
}
|
||||
}
|
||||
|
||||
private static int nextDelay(ServerPlayer player) {
|
||||
int min = (Integer)Config.COMMON.ambient.minTimeBetweenExplosions.get();
|
||||
int max = (Integer)Config.COMMON.ambient.maxTimeBetweenExplosions.get();
|
||||
return Mth.randomBetweenInclusive(player.serverLevel().random, Math.min(min, max), Math.max(min, max));
|
||||
}
|
||||
|
||||
private static float weightedPower(ServerLevel level) {
|
||||
int[] weights = {
|
||||
(Integer)Config.COMMON.ambient.powerTiers.tier1_weight.get(),
|
||||
(Integer)Config.COMMON.ambient.powerTiers.tier2_weight.get(),
|
||||
(Integer)Config.COMMON.ambient.powerTiers.tier3_weight.get(),
|
||||
(Integer)Config.COMMON.ambient.powerTiers.tier4_weight.get(),
|
||||
(Integer)Config.COMMON.ambient.powerTiers.tier5_weight.get()
|
||||
};
|
||||
int total = 0;
|
||||
for (int weight : weights) {
|
||||
total += Math.max(0, weight);
|
||||
}
|
||||
int roll = level.random.nextInt(Math.max(1, total));
|
||||
int tier = 0;
|
||||
for (; tier < weights.length - 1; ++tier) {
|
||||
roll -= Math.max(0, weights[tier]);
|
||||
if (roll < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return switch (tier) {
|
||||
case 0 -> Mth.randomBetween(level.random, 1.0f, 4.0f);
|
||||
case 1 -> Mth.randomBetween(level.random, 5.0f, 15.0f);
|
||||
case 2 -> Mth.randomBetween(level.random, 16.0f, 40.0f);
|
||||
case 3 -> Mth.randomBetween(level.random, 41.0f, 80.0f);
|
||||
default -> Mth.randomBetween(level.random, 81.0f, ((Double)Config.COMMON.ambient.maxAmbientExplosionPower.get()).floatValue());
|
||||
};
|
||||
}
|
||||
|
||||
private static final class AmbientShot {
|
||||
private final Vec3 pos;
|
||||
private float power;
|
||||
private int remaining;
|
||||
private int delay;
|
||||
private final boolean chain;
|
||||
|
||||
private AmbientShot(Vec3 pos, float power, int remaining, int delay, boolean chain) {
|
||||
this.pos = pos;
|
||||
this.power = power;
|
||||
this.remaining = remaining;
|
||||
this.delay = delay;
|
||||
this.chain = chain;
|
||||
}
|
||||
|
||||
private boolean tick(ServerPlayer player) {
|
||||
if (this.delay-- > 0) {
|
||||
return false;
|
||||
}
|
||||
double distance = player.position().distanceTo(this.pos);
|
||||
PacketHandler.sendToPlayer(player, new PlayTrackedSoundPacket(this.pos, ServerExplosionHandler.soundFor(this.power, distance, false, false, false), 0.8f, 0.85f, 0L, false));
|
||||
this.power *= this.chain ? 1.12f : 1.0f;
|
||||
--this.remaining;
|
||||
this.delay = Mth.randomBetweenInclusive(player.serverLevel().random, (Integer)Config.COMMON.ambient.scenarios.minTimeBetweenChainShots.get(), (Integer)Config.COMMON.ambient.scenarios.maxTimeBetweenChainShots.get());
|
||||
return this.remaining <= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,89 @@
|
||||
package com.vinlanx.explosionoverhaul;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public class AsyncCraterManager {
|
||||
private static final Queue<CraterJob> JOBS = new ArrayDeque<>();
|
||||
|
||||
public static void submit(ServerLevel level, Vec3 pos, float power) {
|
||||
List<BlockPos> blocks = new ArrayList<>(CraterDeformer.getCraterBlocks(level, pos, power));
|
||||
synchronized (JOBS) {
|
||||
JOBS.add(new CraterJob(level, pos, power, blocks));
|
||||
}
|
||||
}
|
||||
|
||||
public static void onServerTick(MinecraftServer server) {
|
||||
int budget = Math.max(1, (Integer)Config.COMMON.craterApplyBlocksPerTick.get());
|
||||
synchronized (JOBS) {
|
||||
Iterator<CraterJob> iterator = JOBS.iterator();
|
||||
while (iterator.hasNext() && budget > 0) {
|
||||
CraterJob job = iterator.next();
|
||||
budget = job.apply(budget);
|
||||
if (job.done()) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void onLevelUnload(ServerLevel level) {
|
||||
synchronized (JOBS) {
|
||||
JOBS.removeIf(job -> job.level == level);
|
||||
}
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
synchronized (JOBS) {
|
||||
JOBS.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CraterJob {
|
||||
private final ServerLevel level;
|
||||
private final Vec3 origin;
|
||||
private final float power;
|
||||
private final List<BlockPos> blocks;
|
||||
private int index;
|
||||
private int debrisSpawned;
|
||||
|
||||
private CraterJob(ServerLevel level, Vec3 origin, float power, List<BlockPos> blocks) {
|
||||
this.level = level;
|
||||
this.origin = origin;
|
||||
this.power = power;
|
||||
this.blocks = blocks;
|
||||
}
|
||||
|
||||
private int apply(int budget) {
|
||||
int debrisBudget = Math.max(0, (Integer)Config.COMMON.craterMaxFallingBlocksPerTick.get());
|
||||
while (this.index < this.blocks.size() && budget > 0) {
|
||||
BlockPos pos = this.blocks.get(this.index++);
|
||||
BlockState state = this.level.getBlockState(pos);
|
||||
if (!state.isAir() && state.getDestroySpeed(this.level, pos) >= 0.0f && !ExplosionOverhaul.isBlockStateBlacklisted(state)) {
|
||||
if (this.debrisSpawned < debrisBudget && this.level.random.nextFloat() < 0.035f) {
|
||||
CraterDeformer.spawnDebris(this.level, this.origin, this.power, 1);
|
||||
++this.debrisSpawned;
|
||||
}
|
||||
this.level.levelEvent(2001, pos, Block.getId(state));
|
||||
this.level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
|
||||
}
|
||||
--budget;
|
||||
}
|
||||
return budget;
|
||||
}
|
||||
|
||||
private boolean done() {
|
||||
return this.index >= this.blocks.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,114 @@
|
||||
package com.vinlanx.explosionoverhaul;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.RedstoneLampBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
public class BlockIndexManager {
|
||||
public static volatile boolean ENABLED = false;
|
||||
private static final Map<ResourceKey<Level>, EnumMap<BlockType, Set<BlockPos>>> INDEX = new ConcurrentHashMap<>();
|
||||
private static volatile int totalChunksToScan;
|
||||
private static volatile int chunksScanned;
|
||||
private static volatile boolean scanning;
|
||||
private static volatile boolean rescanMode;
|
||||
|
||||
public static int getTotalChunksToScan() {
|
||||
return 0;
|
||||
return totalChunksToScan;
|
||||
}
|
||||
|
||||
public static int getChunksScanned() {
|
||||
return 0;
|
||||
return chunksScanned;
|
||||
}
|
||||
|
||||
public static float getScanProgress() {
|
||||
return 0.0f;
|
||||
return totalChunksToScan <= 0 ? (scanning ? 0.0f : 1.0f) : Math.min(1.0f, chunksScanned / (float)totalChunksToScan);
|
||||
}
|
||||
|
||||
public static boolean isScanningComplete() {
|
||||
return true;
|
||||
return !scanning;
|
||||
}
|
||||
|
||||
public static int getLampsFound() {
|
||||
return 0;
|
||||
return count(BlockType.LAMP);
|
||||
}
|
||||
|
||||
public static int getDripstonesFound() {
|
||||
return 0;
|
||||
return count(BlockType.DRIPSTONE);
|
||||
}
|
||||
|
||||
public static int getGlassBlocksFound() {
|
||||
return 0;
|
||||
return count(BlockType.GLASS);
|
||||
}
|
||||
|
||||
public static void startManualScan() {
|
||||
rescanMode = true;
|
||||
scanning = true;
|
||||
}
|
||||
|
||||
public static void loadExistingData() {
|
||||
scanning = false;
|
||||
}
|
||||
|
||||
public static void startNewScan() {
|
||||
resetScanState();
|
||||
scanning = true;
|
||||
}
|
||||
|
||||
public static void cancelManualScan() {
|
||||
scanning = false;
|
||||
}
|
||||
|
||||
public static void resetScanState() {
|
||||
INDEX.clear();
|
||||
totalChunksToScan = 0;
|
||||
chunksScanned = 0;
|
||||
scanning = false;
|
||||
rescanMode = false;
|
||||
}
|
||||
|
||||
public static void forceServerScan(MinecraftServer server) {
|
||||
if (server == null) {
|
||||
return;
|
||||
}
|
||||
startNewScan();
|
||||
for (ServerLevel level : server.getAllLevels()) {
|
||||
scanLoadedLevel(level);
|
||||
}
|
||||
scanning = false;
|
||||
}
|
||||
|
||||
public static void forceSingleplayerScan(ServerLevel level) {
|
||||
startNewScan();
|
||||
scanLoadedLevel(level);
|
||||
scanning = false;
|
||||
}
|
||||
|
||||
public static void showRescanPrompt(ServerPlayer player) {
|
||||
ENABLED = ((Boolean)Config.COMMON.scan.enableBlockIndexing.get()).booleanValue();
|
||||
if (ENABLED && player.hasPermissions(2) && hasSaveFile(player.getServer())) {
|
||||
PacketHandler.sendToPlayer(player, new ScanLoadPromptPacket(true));
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isReinforcedGlass(BlockState state) {
|
||||
return false;
|
||||
ResourceLocation id = BuiltInRegistries.BLOCK.getKey(state.getBlock());
|
||||
return id != null && getReinforcedGlassBlacklist().contains(id.toString());
|
||||
}
|
||||
|
||||
public static List<String> getReinforcedGlassBlacklist() {
|
||||
@@ -90,21 +134,109 @@ public class BlockIndexManager {
|
||||
}
|
||||
|
||||
public static boolean isRescanMode() {
|
||||
return false;
|
||||
return rescanMode;
|
||||
}
|
||||
|
||||
public static void register(ServerLevel level, BlockPos pos, BlockType type) {
|
||||
bucket(level, type).add(pos.immutable());
|
||||
}
|
||||
|
||||
public static void unregister(ServerLevel level, BlockPos pos, BlockType type) {
|
||||
bucket(level, type).remove(pos);
|
||||
}
|
||||
|
||||
public static List<BlockPos> getNearby(ServerLevel level, BlockPos center, int radius, BlockType type) {
|
||||
return Collections.emptyList();
|
||||
ENABLED = ((Boolean)Config.COMMON.scan.enableBlockIndexing.get()).booleanValue();
|
||||
if (!ENABLED) {
|
||||
return scanNearby(level, center, radius, type);
|
||||
}
|
||||
List<BlockPos> result = new ArrayList<>();
|
||||
int radiusSq = radius * radius;
|
||||
for (BlockPos pos : bucket(level, type)) {
|
||||
if (pos.distSqr(center) <= radiusSq && matches(level.getBlockState(pos), type)) {
|
||||
result.add(pos);
|
||||
}
|
||||
}
|
||||
if (result.isEmpty()) {
|
||||
result.addAll(scanNearby(level, center, radius, type));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean isPlayerAuthorized(ServerPlayer player) {
|
||||
return true;
|
||||
return player != null && player.hasPermissions(2);
|
||||
}
|
||||
|
||||
private static void scanLoadedLevel(ServerLevel level) {
|
||||
ENABLED = ((Boolean)Config.COMMON.scan.enableBlockIndexing.get()).booleanValue();
|
||||
EnumMap<BlockType, Set<BlockPos>> map = new EnumMap<>(BlockType.class);
|
||||
for (BlockType type : BlockType.values()) {
|
||||
map.put(type, ConcurrentHashMap.newKeySet());
|
||||
}
|
||||
INDEX.put(level.dimension(), map);
|
||||
List<ServerPlayer> players = level.players();
|
||||
int horizontalRadius = players.isEmpty() ? 192 : 256;
|
||||
Set<BlockPos> centers = new HashSet<>();
|
||||
for (ServerPlayer player : players) {
|
||||
centers.add(player.blockPosition());
|
||||
}
|
||||
if (centers.isEmpty()) {
|
||||
centers.add(level.getSharedSpawnPos());
|
||||
}
|
||||
totalChunksToScan = centers.size();
|
||||
for (BlockPos center : centers) {
|
||||
BlockPos min = center.offset(-horizontalRadius, level.getMinBuildHeight() - center.getY(), -horizontalRadius);
|
||||
BlockPos max = center.offset(horizontalRadius, level.getMaxBuildHeight() - center.getY() - 1, horizontalRadius);
|
||||
for (BlockPos cursor : BlockPos.betweenClosed(min, max)) {
|
||||
BlockState state = level.getBlockState(cursor);
|
||||
for (BlockType type : BlockType.values()) {
|
||||
if (matches(state, type)) {
|
||||
register(level, cursor.immutable(), type);
|
||||
}
|
||||
}
|
||||
}
|
||||
++chunksScanned;
|
||||
for (ServerPlayer player : players) {
|
||||
PacketHandler.sendToPlayer(player, new ScanProgressPacket(totalChunksToScan, chunksScanned, !scanning, getLampsFound(), getDripstonesFound(), getGlassBlocksFound()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<BlockPos> scanNearby(ServerLevel level, BlockPos center, int radius, BlockType type) {
|
||||
List<BlockPos> result = new ArrayList<>();
|
||||
for (BlockPos cursor : BlockPos.betweenClosed(center.offset(-radius, -radius, -radius), center.offset(radius, radius, radius))) {
|
||||
if (cursor.distSqr(center) <= radius * radius && matches(level.getBlockState(cursor), type)) {
|
||||
result.add(cursor.immutable());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean matches(BlockState state, BlockType type) {
|
||||
return switch (type) {
|
||||
case LAMP -> state.is(Blocks.REDSTONE_LAMP) && state.hasProperty(RedstoneLampBlock.LIT);
|
||||
case DRIPSTONE -> state.is(Blocks.POINTED_DRIPSTONE);
|
||||
case GLASS -> GlassBreakingEffects.isGlass(state) && !isReinforcedGlass(state);
|
||||
};
|
||||
}
|
||||
|
||||
private static Set<BlockPos> bucket(ServerLevel level, BlockType type) {
|
||||
EnumMap<BlockType, Set<BlockPos>> map = INDEX.computeIfAbsent(level.dimension(), ignored -> {
|
||||
EnumMap<BlockType, Set<BlockPos>> created = new EnumMap<>(BlockType.class);
|
||||
for (BlockType blockType : BlockType.values()) {
|
||||
created.put(blockType, ConcurrentHashMap.newKeySet());
|
||||
}
|
||||
return created;
|
||||
});
|
||||
return map.computeIfAbsent(type, ignored -> ConcurrentHashMap.newKeySet());
|
||||
}
|
||||
|
||||
private static int count(BlockType type) {
|
||||
int count = 0;
|
||||
for (EnumMap<BlockType, Set<BlockPos>> map : INDEX.values()) {
|
||||
count += map.getOrDefault(type, Collections.emptySet()).size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public enum BlockType {
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
*/
|
||||
package com.vinlanx.explosionoverhaul;
|
||||
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.neoforge.client.event.sound.PlaySoundEvent;
|
||||
import net.neoforged.neoforge.client.event.ClientTickEvent;
|
||||
import net.neoforged.bus.api.EventPriority;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
|
||||
@EventBusSubscriber(modid = ExplosionOverhaul.MODID, value = Dist.CLIENT)
|
||||
public class ClientSoundHandler {
|
||||
private static boolean suppressVanillaExplosionSound = false;
|
||||
private static int suppressionTicksRemaining = 0;
|
||||
@@ -18,7 +21,7 @@ public class ClientSoundHandler {
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onClientTick(ClientTickEvent.Post event) {
|
||||
public static void onClientTick(ClientTickEvent.Post event) {
|
||||
if (suppressionTicksRemaining > 0) {
|
||||
--suppressionTicksRemaining;
|
||||
} else if (suppressionTicksRemaining == 0 && suppressVanillaExplosionSound) {
|
||||
@@ -27,7 +30,7 @@ public class ClientSoundHandler {
|
||||
}
|
||||
|
||||
@SubscribeEvent(priority=EventPriority.HIGH)
|
||||
public void onExplosionSound(PlaySoundEvent event) {
|
||||
public static void onExplosionSound(PlaySoundEvent event) {
|
||||
if (event.getSound() != null && event.getSound().getLocation().getPath().equals("entity.generic.explode") && suppressVanillaExplosionSound) {
|
||||
event.setSound(null);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.vinlanx.explosionoverhaul;
|
||||
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.neoforge.event.level.LevelEvent;
|
||||
import net.neoforged.neoforge.event.tick.ServerTickEvent;
|
||||
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
|
||||
|
||||
@EventBusSubscriber(modid = ExplosionOverhaul.MODID)
|
||||
public final class CommonEvents {
|
||||
private CommonEvents() {
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerTick(ServerTickEvent.Post event) {
|
||||
ExplosionOverhaul.onServerTick(event.getServer());
|
||||
AmbientExplosionManager.onServerTick(event.getServer());
|
||||
AsyncCraterManager.onServerTick(event.getServer());
|
||||
RedstoneLampEffects.onServerTick();
|
||||
GlassBreakingEffects.onServerTick();
|
||||
for (ServerLevel level : event.getServer().getAllLevels()) {
|
||||
DripstoneEffects.onServerTick(level);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLevelUnload(LevelEvent.Unload event) {
|
||||
if (event.getLevel() instanceof ServerLevel level) {
|
||||
AsyncCraterManager.onLevelUnload(level);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
|
||||
if (event.getEntity() instanceof ServerPlayer player) {
|
||||
AmbientExplosionManager.onPlayerLoggedIn(player);
|
||||
BlockIndexManager.showRescanPrompt(player);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) {
|
||||
if (event.getEntity() instanceof ServerPlayer player) {
|
||||
AmbientExplosionManager.onPlayerLoggedOut(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,95 @@
|
||||
package com.vinlanx.explosionoverhaul;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.item.FallingBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public class CraterDeformer {
|
||||
public static Set<BlockPos> getCraterBlocks(ServerLevel level, Vec3 explosionPos, float power) {
|
||||
return Collections.emptySet();
|
||||
Set<BlockPos> blocks = new LinkedHashSet<>();
|
||||
float radius = calculateRadius(power);
|
||||
float coreRatio = ((Double)Config.COMMON.craterCoreRatio.get()).floatValue();
|
||||
float coreRadius = radius * Mth.clamp(coreRatio, 0.1f, 0.95f);
|
||||
float maxResistance = calculateMaxResistance(power);
|
||||
BlockPos origin = BlockPos.containing(explosionPos);
|
||||
int r = Mth.ceil(radius + 2.0f);
|
||||
for (BlockPos cursor : BlockPos.betweenClosed(origin.offset(-r, -r, -r), origin.offset(r, r, r))) {
|
||||
BlockPos pos = cursor.immutable();
|
||||
BlockState state = level.getBlockState(pos);
|
||||
if (state.isAir() || state.getDestroySpeed(level, pos) < 0.0f || ExplosionOverhaul.isBlockStateBlacklisted(state)) {
|
||||
continue;
|
||||
}
|
||||
float resistance = state.getBlock().getExplosionResistance();
|
||||
if (resistance > maxResistance && pos.distToCenterSqr(explosionPos) > coreRadius * coreRadius) {
|
||||
continue;
|
||||
}
|
||||
double dx = (pos.getX() + 0.5 - explosionPos.x()) / radius;
|
||||
double dy = (pos.getY() + 0.5 - explosionPos.y()) / (radius * 0.78);
|
||||
double dz = (pos.getZ() + 0.5 - explosionPos.z()) / radius;
|
||||
double normalized = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
double noise = roughness(pos, explosionPos, power);
|
||||
double shell = 0.82 + noise * 0.22;
|
||||
double undercut = pos.getY() + 0.5 < explosionPos.y() ? 0.06 : -0.03;
|
||||
if (normalized <= shell + undercut || Math.sqrt(pos.distToCenterSqr(explosionPos)) <= coreRadius * (0.82 + noise * 0.12)) {
|
||||
blocks.add(pos);
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public static float calculateRadius(float power) {
|
||||
return Math.max(1.0f, power);
|
||||
float multiplier = ((Double)Config.COMMON.craterSizeMultiplier.get()).floatValue();
|
||||
return Math.max(2.5f, Math.min(70.0f, power * 2.15f * multiplier));
|
||||
}
|
||||
|
||||
public static float calculateMaxResistance(float power) {
|
||||
return Float.MAX_VALUE;
|
||||
return Math.max(8.0f, power * 6.0f);
|
||||
}
|
||||
|
||||
public static void applyLargeExplosionLogic(ServerLevel level, Vec3 explosionPos, float power) {
|
||||
spawnDebris(level, explosionPos, power, Math.min(180, Math.round(power * 8.0f)));
|
||||
}
|
||||
|
||||
public static void applySmallExplosionLogic(ServerLevel level, Vec3 explosionPos, float power) {
|
||||
spawnDebris(level, explosionPos, power, Math.min(48, Math.round(power * 5.0f)));
|
||||
}
|
||||
|
||||
public static void spawnDebris(ServerLevel level, Vec3 explosionPos, float power, int maxDebris) {
|
||||
if (!((Boolean)Config.COMMON.enableFallingBlocks.get()).booleanValue() || maxDebris <= 0) {
|
||||
return;
|
||||
}
|
||||
float radius = calculateRadius(power);
|
||||
BlockPos origin = BlockPos.containing(explosionPos);
|
||||
int spawned = 0;
|
||||
int attempts = maxDebris * 4;
|
||||
for (int i = 0; i < attempts && spawned < maxDebris; ++i) {
|
||||
double angle = level.random.nextDouble() * Math.PI * 2.0;
|
||||
double distance = radius * (0.25 + level.random.nextDouble() * 0.7);
|
||||
BlockPos pos = origin.offset(Mth.floor(Math.cos(angle) * distance), level.random.nextInt(5) - 1, Mth.floor(Math.sin(angle) * distance));
|
||||
BlockState state = level.getBlockState(pos);
|
||||
if (state.isAir() || state.getDestroySpeed(level, pos) < 0.0f || ExplosionOverhaul.isBlockStateBlacklisted(state)) {
|
||||
continue;
|
||||
}
|
||||
FallingBlockEntity falling = FallingBlockEntity.fall(level, pos, state);
|
||||
Vec3 motion = new Vec3(pos.getX() + 0.5 - explosionPos.x(), 0.3 + level.random.nextDouble() * 0.9, pos.getZ() + 0.5 - explosionPos.z()).normalize().scale(0.18 + Math.min(1.4, power * 0.035));
|
||||
falling.setDeltaMovement(motion);
|
||||
falling.time = 1;
|
||||
++spawned;
|
||||
}
|
||||
}
|
||||
|
||||
private static double roughness(BlockPos pos, Vec3 origin, float power) {
|
||||
long seed = pos.asLong() ^ Double.doubleToLongBits(origin.x()) ^ (Double.doubleToLongBits(origin.z()) << 1) ^ (long)(power * 1000.0f);
|
||||
seed ^= seed >>> 33;
|
||||
seed *= 0xff51afd7ed558ccdL;
|
||||
seed ^= seed >>> 33;
|
||||
seed *= 0xc4ceb9fe1a85ec53L;
|
||||
seed ^= seed >>> 33;
|
||||
return (seed & 0xFFFF) / 65535.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,16 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.minecraft.core.particles.BlockParticleOption;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
@@ -67,6 +71,8 @@ public class ExplosionOverhaul {
|
||||
private static final Path CONFIG_DIR = Paths.get("config", "explosionoverhaul");
|
||||
private static final Path BLACKLIST_JSON = CONFIG_DIR.resolve("DestroyingBlacklist.json");
|
||||
private static final Path SOURCE_MODES_JSON = CONFIG_DIR.resolve("ExplosionSourceBlacklist.json");
|
||||
private static final List<DelayedSound> DELAYED_SOUNDS = new ArrayList<>();
|
||||
private static final List<DelayedParticle> DELAYED_PARTICLES = new ArrayList<>();
|
||||
|
||||
public ExplosionOverhaul(IEventBus modEventBus, ModContainer modContainer) {
|
||||
modContainer.registerConfig(ModConfig.Type.COMMON, Config.COMMON_SPEC, "explosionoverhaul/explosionoverhaul-common.toml");
|
||||
@@ -77,6 +83,7 @@ public class ExplosionOverhaul {
|
||||
ModItems.register(modEventBus);
|
||||
ModBlockEntities.register(modEventBus);
|
||||
ModCreativeTabs.register(modEventBus);
|
||||
modEventBus.addListener(PacketHandler::registerPayloads);
|
||||
modEventBus.addListener(this::commonSetup);
|
||||
}
|
||||
|
||||
@@ -195,9 +202,30 @@ public class ExplosionOverhaul {
|
||||
}
|
||||
|
||||
public static void addDelayedSound(ServerPlayer player, SoundEvent sound, SoundSource source, float x, float y, float z, float volume, float pitch, long seed, long delayTicks) {
|
||||
if (player == null || sound == null) {
|
||||
return;
|
||||
}
|
||||
synchronized (DELAYED_SOUNDS) {
|
||||
DELAYED_SOUNDS.add(new DelayedSound(player, sound, source, x, y, z, volume, pitch, seed, Math.max(0L, delayTicks)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void addDelayedParticle(ServerLevel level, BlockParticleOption particleOption, double x, double y, double z, int count, double dx, double dy, double dz, double speed, long delayTicks, long durationTicks) {
|
||||
if (level == null || particleOption == null) {
|
||||
return;
|
||||
}
|
||||
synchronized (DELAYED_PARTICLES) {
|
||||
DELAYED_PARTICLES.add(new DelayedParticle(level, particleOption, x, y, z, count, dx, dy, dz, speed, Math.max(0L, delayTicks), Math.max(1L, durationTicks)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void onServerTick(MinecraftServer server) {
|
||||
synchronized (DELAYED_SOUNDS) {
|
||||
DELAYED_SOUNDS.removeIf(sound -> sound.tick());
|
||||
}
|
||||
synchronized (DELAYED_PARTICLES) {
|
||||
DELAYED_PARTICLES.removeIf(particle -> particle.tick());
|
||||
}
|
||||
}
|
||||
|
||||
public enum ExplosionSourceMode {
|
||||
@@ -209,6 +237,82 @@ public class ExplosionOverhaul {
|
||||
|
||||
public static class CaveEffects {
|
||||
public static void spawnFallingBlocksAndDust(ServerLevel level, Vec3 explosionPos, ServerPlayer player, float power, long initialDelayTicks) {
|
||||
int count = Math.max(8, Math.min(80, Math.round(power * 5.0f)));
|
||||
BlockParticleOption option = new BlockParticleOption(net.minecraft.core.particles.ParticleTypes.BLOCK, Blocks.STONE.defaultBlockState());
|
||||
addDelayedParticle(level, option, explosionPos.x(), explosionPos.y() + 1.0, explosionPos.z(), count, power * 0.3, power * 0.18, power * 0.3, 0.18, initialDelayTicks, 26);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DelayedSound {
|
||||
private final ServerPlayer player;
|
||||
private final SoundEvent sound;
|
||||
private final SoundSource source;
|
||||
private final double x;
|
||||
private final double y;
|
||||
private final double z;
|
||||
private final float volume;
|
||||
private final float pitch;
|
||||
private final long seed;
|
||||
private long delayTicks;
|
||||
|
||||
private DelayedSound(ServerPlayer player, SoundEvent sound, SoundSource source, double x, double y, double z, float volume, float pitch, long seed, long delayTicks) {
|
||||
this.player = player;
|
||||
this.sound = sound;
|
||||
this.source = source;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.volume = volume;
|
||||
this.pitch = pitch;
|
||||
this.seed = seed;
|
||||
this.delayTicks = delayTicks;
|
||||
}
|
||||
|
||||
private boolean tick() {
|
||||
if (this.player.isRemoved() || this.player.connection == null) {
|
||||
return true;
|
||||
}
|
||||
if (this.delayTicks-- > 0L) {
|
||||
return false;
|
||||
}
|
||||
this.player.connection.send(new ClientboundSoundPacket(Holder.direct(this.sound), this.source, this.x, this.y, this.z, this.volume, this.pitch, this.seed));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DelayedParticle {
|
||||
private final ServerLevel level;
|
||||
private final BlockParticleOption option;
|
||||
private final double x;
|
||||
private final double y;
|
||||
private final double z;
|
||||
private final int count;
|
||||
private final double dx;
|
||||
private final double dy;
|
||||
private final double dz;
|
||||
private final double speed;
|
||||
private long delayTicks;
|
||||
|
||||
private DelayedParticle(ServerLevel level, BlockParticleOption option, double x, double y, double z, int count, double dx, double dy, double dz, double speed, long delayTicks, long durationTicks) {
|
||||
this.level = level;
|
||||
this.option = option;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.count = count;
|
||||
this.dx = dx;
|
||||
this.dy = dy;
|
||||
this.dz = dz;
|
||||
this.speed = speed;
|
||||
this.delayTicks = delayTicks;
|
||||
}
|
||||
|
||||
private boolean tick() {
|
||||
if (this.delayTicks-- > 0L) {
|
||||
return false;
|
||||
}
|
||||
this.level.sendParticles(this.option, this.x, this.y, this.z, this.count, this.dx, this.dy, this.dz, this.speed);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import com.vinlanx.explosionoverhaul.compat.network.NetworkRegistry;
|
||||
import com.vinlanx.explosionoverhaul.compat.network.simple.SimpleChannel;
|
||||
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||
|
||||
public class PacketHandler {
|
||||
private static final String PROTOCOL_VERSION = "1";
|
||||
@@ -60,8 +61,12 @@ public class PacketHandler {
|
||||
INSTANCE.registerMessage(messageId++, ScanLoadControlPacket.class, ScanLoadControlPacket::encode, ScanLoadControlPacket::decode, ScanLoadControlPacket::handle);
|
||||
}
|
||||
|
||||
public static void registerPayloads(RegisterPayloadHandlersEvent event) {
|
||||
INSTANCE.registerPayloads(event);
|
||||
}
|
||||
|
||||
public static void sendToPlayer(ServerPlayer player, Object message) {
|
||||
INSTANCE.send(null, message);
|
||||
INSTANCE.send(player, message);
|
||||
}
|
||||
|
||||
public static void sendToAll(Object message) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.vinlanx.explosionoverhaul;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
@@ -10,36 +13,95 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public class RedstoneLampEffects {
|
||||
private static final List<FlickerTask> TASKS = new ArrayList<>();
|
||||
|
||||
public static void triggerLampFlicker(ServerLevel level, ServerPlayer player, float power, long delayTicks, double distance) {
|
||||
triggerNearbyLamps(level, player.position(), power);
|
||||
triggerNearbyLamps(level, player.position(), power, delayTicks);
|
||||
}
|
||||
|
||||
public static void triggerNearbyLamps(ServerLevel level, Vec3 explosionPos, float power) {
|
||||
triggerNearbyLamps(level, explosionPos, power, 0L);
|
||||
}
|
||||
|
||||
private static void triggerNearbyLamps(ServerLevel level, Vec3 explosionPos, float power, long delayTicks) {
|
||||
if (!((Boolean)Config.COMMON.enableLampFlicker.get()).booleanValue()) {
|
||||
return;
|
||||
}
|
||||
int radius = Math.min((Integer)Config.COMMON.lampFlickerSearchRadius.get(), Math.max(8, Math.round(power * 6.0f)));
|
||||
BlockPos origin = BlockPos.containing(explosionPos);
|
||||
int changed = 0;
|
||||
List<BlockPos> candidates = new ArrayList<>(BlockIndexManager.getNearby(level, origin, radius, BlockIndexManager.BlockType.LAMP));
|
||||
if (candidates.isEmpty()) {
|
||||
for (BlockPos pos : BlockPos.betweenClosed(origin.offset(-radius, -radius, -radius), origin.offset(radius, radius, radius))) {
|
||||
if (changed >= 96) {
|
||||
candidates.add(pos.immutable());
|
||||
}
|
||||
}
|
||||
int scheduled = 0;
|
||||
synchronized (TASKS) {
|
||||
for (BlockPos pos : candidates) {
|
||||
if (scheduled >= 96) {
|
||||
break;
|
||||
}
|
||||
BlockState state = level.getBlockState(pos);
|
||||
if (!state.is(Blocks.REDSTONE_LAMP) || !state.hasProperty(RedstoneLampBlock.LIT)) {
|
||||
if (!state.is(Blocks.REDSTONE_LAMP) || !state.hasProperty(RedstoneLampBlock.LIT) || !state.getValue(RedstoneLampBlock.LIT)) {
|
||||
continue;
|
||||
}
|
||||
BlockPos immutable = pos.immutable();
|
||||
boolean lit = state.getValue(RedstoneLampBlock.LIT);
|
||||
level.setBlock(immutable, state.setValue(RedstoneLampBlock.LIT, !lit), 3);
|
||||
level.playSound(null, immutable, ModSounds.LAMP_FLICKER_SPARK_1.get(), SoundSource.BLOCKS, 0.25f, 0.8f + level.random.nextFloat() * 0.4f);
|
||||
if (level.hasNeighborSignal(immutable)) {
|
||||
level.setBlock(immutable, state.setValue(RedstoneLampBlock.LIT, true), 3);
|
||||
long localDelay = delayTicks + Math.round(Math.sqrt(pos.distToCenterSqr(explosionPos)) / 343.0 * 20.0);
|
||||
TASKS.add(new FlickerTask(level, pos.immutable(), localDelay, 18 + level.random.nextInt(24), 2 + level.random.nextInt(4)));
|
||||
++scheduled;
|
||||
}
|
||||
++changed;
|
||||
}
|
||||
}
|
||||
|
||||
public static void onServerTick() {
|
||||
synchronized (TASKS) {
|
||||
Iterator<FlickerTask> iterator = TASKS.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
if (iterator.next().tick()) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class FlickerTask {
|
||||
private final ServerLevel level;
|
||||
private final BlockPos pos;
|
||||
private final int interval;
|
||||
private long delayTicks;
|
||||
private int durationTicks;
|
||||
private int intervalTicks;
|
||||
private boolean currentLit = true;
|
||||
|
||||
private FlickerTask(ServerLevel level, BlockPos pos, long delayTicks, int durationTicks, int interval) {
|
||||
this.level = level;
|
||||
this.pos = pos;
|
||||
this.delayTicks = delayTicks;
|
||||
this.durationTicks = durationTicks;
|
||||
this.interval = interval;
|
||||
this.intervalTicks = interval;
|
||||
}
|
||||
|
||||
private boolean tick() {
|
||||
if (this.delayTicks-- > 0L) {
|
||||
return false;
|
||||
}
|
||||
BlockState state = this.level.getBlockState(this.pos);
|
||||
if (!state.is(Blocks.REDSTONE_LAMP) || !state.hasProperty(RedstoneLampBlock.LIT)) {
|
||||
return true;
|
||||
}
|
||||
if (this.durationTicks-- <= 0) {
|
||||
this.level.setBlock(this.pos, state.setValue(RedstoneLampBlock.LIT, this.level.hasNeighborSignal(this.pos)), 3);
|
||||
return true;
|
||||
}
|
||||
if (this.intervalTicks-- <= 0) {
|
||||
this.currentLit = !this.currentLit;
|
||||
this.level.setBlock(this.pos, state.setValue(RedstoneLampBlock.LIT, this.currentLit), 3);
|
||||
if (this.level.random.nextFloat() < 0.35f) {
|
||||
this.level.playSound(null, this.pos, ModSounds.LAMP_FLICKER_SOUNDS.get(this.level.random.nextInt(ModSounds.LAMP_FLICKER_SOUNDS.size())).get(), SoundSource.BLOCKS, 0.24f, 0.75f + this.level.random.nextFloat() * 0.5f);
|
||||
}
|
||||
this.intervalTicks = this.interval;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class ScanControlPacket {
|
||||
ServerPlayer player = ((NetworkEvent.Context)ctx.get()).getSender();
|
||||
if (player != null && BlockIndexManager.isPlayerAuthorized(player)) {
|
||||
if (msg.startScan) {
|
||||
BlockIndexManager.startManualScan();
|
||||
BlockIndexManager.forceServerScan(player.getServer());
|
||||
} else {
|
||||
BlockIndexManager.cancelManualScan();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class ScanLoadControlPacket {
|
||||
if (msg.loadExisting) {
|
||||
BlockIndexManager.loadExistingData();
|
||||
} else {
|
||||
BlockIndexManager.startNewScan();
|
||||
BlockIndexManager.forceServerScan(player.getServer());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,70 +6,237 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.Explosion;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public class ServerExplosionHandler {
|
||||
private static final double SOUND_SPEED_BLOCKS_PER_TICK = 343.0 / 20.0;
|
||||
|
||||
public static void handleExplosion(ServerLevel level, Explosion explosion, List<BlockPos> affectedBlocks) {
|
||||
if (!((Boolean)Config.COMMON.enableCraterDestruction.get()).booleanValue()) {
|
||||
affectedBlocks.clear();
|
||||
return;
|
||||
}
|
||||
float power = 4.0f;
|
||||
if (explosion instanceof IExplosionPower explosionPower) {
|
||||
power = Math.max(1.0f, explosionPower.getPower());
|
||||
}
|
||||
Vec3 center = explosion instanceof ExplosionAccessor accessor ? accessor.explosionoverhaul$getCenter() : Vec3.ZERO;
|
||||
Entity source = explosion instanceof ExplosionAccessor accessor ? accessor.explosionoverhaul$getSource() : null;
|
||||
ExplosionOverhaul.ExplosionSourceMode sourceMode = sourceMode(source);
|
||||
if (sourceMode == ExplosionOverhaul.ExplosionSourceMode.VANILLA) {
|
||||
return;
|
||||
}
|
||||
|
||||
power = ExplosionClusterHandler.calculateClusteredPower(level, center, power);
|
||||
int radius = Math.max(5, Math.min(18, Math.round(power * 2.0f * ((Double)Config.COMMON.craterSizeMultiplier.get()).floatValue())));
|
||||
ExplosionOverhaul.LOGGER.info("Explosion Overhaul handling explosion at {} with power {} and radius {}", center, power, radius);
|
||||
ExplosionOverhaul.LOGGER.info("Explosion Overhaul handling explosion at {} with power {}", center, power);
|
||||
|
||||
dispatchPlayerEffects(level, center, power);
|
||||
GlassBreakingEffects.trigger(level, center, power);
|
||||
DripstoneEffects.handleDripstoneFall(level, BlockPos.containing(center), Math.round(power), level.random);
|
||||
RedstoneLampEffects.triggerNearbyLamps(level, center, power);
|
||||
Set<BlockPos> existing = new HashSet<>(affectedBlocks);
|
||||
BlockPos origin = BlockPos.containing(center);
|
||||
double radiusSq = radius * radius;
|
||||
int directBlocksChanged = 0;
|
||||
for (BlockPos pos : BlockPos.betweenClosed(origin.offset(-radius, -radius, -radius), origin.offset(radius, radius, radius))) {
|
||||
double distanceSq = pos.distToCenterSqr(center);
|
||||
if (distanceSq > radiusSq) {
|
||||
continue;
|
||||
ExplosionOverhaul.CaveEffects.spawnFallingBlocksAndDust(level, center, nearestPlayer(level, center), power, 8L);
|
||||
|
||||
boolean noDestruction = sourceMode == ExplosionOverhaul.ExplosionSourceMode.NO_DESTRUCTION
|
||||
|| sourceMode == ExplosionOverhaul.ExplosionSourceMode.NO_DESTRUCTION_GLASSWORKS
|
||||
|| !((Boolean)Config.COMMON.enableCraterDestruction.get()).booleanValue();
|
||||
if (noDestruction) {
|
||||
affectedBlocks.clear();
|
||||
} else if (((Boolean)Config.COMMON.enableAsyncCrater.get()).booleanValue()) {
|
||||
affectedBlocks.clear();
|
||||
AsyncCraterManager.submit(level, center, power);
|
||||
} else {
|
||||
Set<BlockPos> craterBlocks = new HashSet<>(CraterDeformer.getCraterBlocks(level, center, power));
|
||||
affectedBlocks.clear();
|
||||
affectedBlocks.addAll(craterBlocks);
|
||||
if (power >= 12.0f) {
|
||||
CraterDeformer.applyLargeExplosionLogic(level, center, power);
|
||||
} else {
|
||||
CraterDeformer.applySmallExplosionLogic(level, center, power);
|
||||
}
|
||||
BlockState state = level.getBlockState(pos);
|
||||
if (state.isAir() || state.getDestroySpeed(level, pos) < 0.0f) {
|
||||
continue;
|
||||
}
|
||||
BlockPos immutable = pos.immutable();
|
||||
if (existing.add(immutable)) {
|
||||
affectedBlocks.add(immutable);
|
||||
}
|
||||
if (directBlocksChanged < 2500) {
|
||||
level.setBlock(immutable, Blocks.AIR.defaultBlockState(), 3);
|
||||
++directBlocksChanged;
|
||||
}
|
||||
}
|
||||
level.sendParticles(ParticleTypes.EXPLOSION_EMITTER, center.x(), center.y(), center.z(), Math.max(2, radius / 3), 0.5, 0.5, 0.5, 0.0);
|
||||
level.sendParticles(ParticleTypes.LARGE_SMOKE, center.x(), center.y(), center.z(), radius * 8, radius * 0.35, radius * 0.25, radius * 0.35, 0.08);
|
||||
level.sendParticles(ParticleTypes.FLAME, center.x(), center.y(), center.z(), radius * 4, radius * 0.25, radius * 0.2, radius * 0.25, 0.05);
|
||||
spawnCustomExplosionParticles(level, center, power, radius);
|
||||
}
|
||||
|
||||
private static void spawnCustomExplosionParticles(ServerLevel level, Vec3 center, float power, int radius) {
|
||||
int glowCount = Math.min(220, Math.max(24, radius * 9));
|
||||
int smokeCount = Math.min(260, Math.max(32, radius * 11));
|
||||
int plasmaCount = Math.min(120, Math.max(12, radius * 5));
|
||||
int sparkCount = Math.min(80, Math.max(10, radius * 3));
|
||||
spawnServerParticles(level, center, power);
|
||||
}
|
||||
|
||||
private static void dispatchPlayerEffects(ServerLevel level, Vec3 center, float power) {
|
||||
for (ServerPlayer player : level.players()) {
|
||||
double distance = player.position().distanceTo(center);
|
||||
if (distance > Math.max(96.0, power * 90.0)) {
|
||||
continue;
|
||||
}
|
||||
boolean playerInCave = isInCave(level, player.blockPosition());
|
||||
boolean explosionInCave = isInCave(level, BlockPos.containing(center));
|
||||
boolean playerInHouse = isInHouse(level, player.blockPosition());
|
||||
boolean los = hasLineOfSight(level, player.getEyePosition(), center);
|
||||
int delayTicks = soundDelay(distance);
|
||||
PacketHandler.sendToPlayer(player, new SuppressExplosionSoundPacket(power));
|
||||
PacketHandler.sendToPlayer(player, new PlayTrackedSoundPacket(center, soundFor(power, distance, explosionInCave, playerInCave, playerInHouse), soundVolume(power, distance), soundPitch(power), delayTicks, playerInHouse));
|
||||
PacketHandler.sendToPlayer(player, new ExplosionVisualsPacket(center, power));
|
||||
PacketHandler.sendToPlayer(player, new FlashEffectPacket(center, power));
|
||||
CameraShakeProfile profile = determineCameraShakeProfile(power, distance, playerInCave, level);
|
||||
if (profile.durationTicks() > 0) {
|
||||
PacketHandler.sendToPlayer(player, new CameraShakePacket(profile.intensity(), profile.durationTicks(), profile.pushIntensity(), delayTicks));
|
||||
}
|
||||
PacketHandler.sendToPlayer(player, new StartConcussionPacket(power, distance, los, explosionInCave));
|
||||
PacketHandler.sendToPlayer(player, new SpawnShockwavePacket(center, power));
|
||||
PacketHandler.sendToPlayer(player, new SpawnDustCloudPacket(center, power));
|
||||
PacketHandler.sendToPlayer(player, new SpawnMistCloudPacket(center, power));
|
||||
PacketHandler.sendToPlayer(player, new SpawnLineSparksPacket(center, power));
|
||||
if (playerInCave || explosionInCave) {
|
||||
PacketHandler.sendToPlayer(player, new SpawnAmbientCaveDustPacket(power));
|
||||
}
|
||||
if (distance < Math.max(12.0, power * 4.0)) {
|
||||
pushPlayer(player, center, power, distance);
|
||||
}
|
||||
RedstoneLampEffects.triggerLampFlicker(level, player, power, delayTicks, distance);
|
||||
}
|
||||
}
|
||||
|
||||
private static void spawnServerParticles(ServerLevel level, Vec3 center, float power) {
|
||||
int radius = Math.round(CraterDeformer.calculateRadius(power));
|
||||
int glowCount = Math.min(320, Math.max(32, radius * 9));
|
||||
int smokeCount = Math.min(360, Math.max(48, radius * 12));
|
||||
int plasmaCount = Math.min(180, Math.max(14, radius * 5));
|
||||
int sparkCount = Math.min(140, Math.max(12, radius * 4));
|
||||
float maxRadius = Math.max(4.0f, radius);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
level.sendParticles(new CustomGlowParticleOptions(i % 2, power, 0.95f + i * 0.2f, i, (float)center.y(), maxRadius, 0.5f), center.x(), center.y(), center.z(), glowCount / 3, radius * 0.18, radius * 0.12, radius * 0.18, 0.08);
|
||||
level.sendParticles(new CustomGlowParticleOptions(i % 2, power, 0.95f + i * 0.22f, i, (float)center.y(), maxRadius, 0.5f), center.x(), center.y(), center.z(), glowCount / 3, radius * 0.2, radius * 0.14, radius * 0.2, 0.1);
|
||||
}
|
||||
level.sendParticles(new SmokeParticleOptions(Math.max(1.2f, radius * 0.28f), 58 + radius * 3, 0.18f, 0.16f, 0.14f, 0.7f, true, 0.0f, 0.35f, null), center.x(), center.y() + 0.2, center.z(), smokeCount, radius * 0.35, radius * 0.22, radius * 0.35, 0.05);
|
||||
level.sendParticles(new PlasmaParticleOptions(power), center.x(), center.y(), center.z(), plasmaCount, radius * 0.16, radius * 0.1, radius * 0.16, 0.45);
|
||||
level.sendParticles(ModParticles.LINE_SPARK.get(), center.x(), center.y(), center.z(), sparkCount, radius * 0.22, radius * 0.16, radius * 0.22, 0.55);
|
||||
level.sendParticles(new SmokeParticleOptions(Math.max(1.2f, radius * 0.3f), 70 + radius * 3, 0.18f, 0.16f, 0.14f, 0.72f, true, 0.0f, 0.35f, null), center.x(), center.y() + 0.25, center.z(), smokeCount, radius * 0.4, radius * 0.26, radius * 0.4, 0.06);
|
||||
level.sendParticles(new PlasmaParticleOptions(power), center.x(), center.y(), center.z(), plasmaCount, radius * 0.18, radius * 0.12, radius * 0.18, 0.48);
|
||||
level.sendParticles(ModParticles.LINE_SPARK.get(), center.x(), center.y(), center.z(), sparkCount, radius * 0.24, radius * 0.18, radius * 0.24, 0.6);
|
||||
}
|
||||
|
||||
public static ResourceLocation soundFor(float power, double distance, boolean explosionInCave, boolean playerInCave, boolean playerInHouse) {
|
||||
int tier = powerTier(power);
|
||||
int variant = 1 + Math.floorMod((int)(power * 31.0f + distance), 3);
|
||||
String band = distance < 90.0 ? "close" : distance < 700.0 ? "medium" : distance < 2100.0 ? "far" : "superfar";
|
||||
String environment = "";
|
||||
if (!band.equals("close")) {
|
||||
if (explosionInCave && playerInHouse) {
|
||||
environment = "_cave_to_house";
|
||||
} else if (playerInHouse) {
|
||||
environment = "_to_house";
|
||||
} else if (explosionInCave || playerInCave) {
|
||||
environment = band.equals("medium") && playerInCave && !explosionInCave ? "_underground" : "_cave";
|
||||
}
|
||||
}
|
||||
String name = "explode_" + band + environment + "_power_" + tier + "_" + variant;
|
||||
ResourceLocation id = ResourceLocation.fromNamespaceAndPath(ExplosionOverhaul.MODID, name);
|
||||
if (!BuiltInRegistries.SOUND_EVENT.containsKey(id)) {
|
||||
id = ResourceLocation.fromNamespaceAndPath(ExplosionOverhaul.MODID, "explode_" + band + "_power_" + tier + "_" + variant);
|
||||
}
|
||||
if (!BuiltInRegistries.SOUND_EVENT.containsKey(id)) {
|
||||
id = ResourceLocation.fromNamespaceAndPath(ExplosionOverhaul.MODID, "explode_close_power_" + tier + "_" + variant);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private static ExplosionOverhaul.ExplosionSourceMode sourceMode(Entity source) {
|
||||
if (source == null || source.getType() == null) {
|
||||
return ExplosionOverhaul.ExplosionSourceMode.DEFAULT;
|
||||
}
|
||||
ResourceLocation id = BuiltInRegistries.ENTITY_TYPE.getKey(source.getType());
|
||||
return id == null ? ExplosionOverhaul.ExplosionSourceMode.DEFAULT : ExplosionOverhaul.getSourceMode(id.toString());
|
||||
}
|
||||
|
||||
private static int powerTier(float power) {
|
||||
if (power <= 4.0f) {
|
||||
return 1;
|
||||
}
|
||||
if (power <= 8.0f) {
|
||||
return 2;
|
||||
}
|
||||
if (power <= 14.0f) {
|
||||
return 3;
|
||||
}
|
||||
if (power <= 24.0f) {
|
||||
return 4;
|
||||
}
|
||||
if (power <= 40.0f) {
|
||||
return 5;
|
||||
}
|
||||
if (power <= 70.0f) {
|
||||
return 6;
|
||||
}
|
||||
return 7;
|
||||
}
|
||||
|
||||
private static int soundDelay(double distance) {
|
||||
if (!((Boolean)Config.COMMON.enableAdvancedSoundSpeed.get()).booleanValue()) {
|
||||
return Math.max(0, (int)Math.round(distance / SOUND_SPEED_BLOCKS_PER_TICK));
|
||||
}
|
||||
return Math.max(0, (int)Math.round(distance / SOUND_SPEED_BLOCKS_PER_TICK));
|
||||
}
|
||||
|
||||
private static float soundVolume(float power, double distance) {
|
||||
return Mth.clamp((float)(1.2 + power * 0.08 - distance / 3500.0), 0.22f, 5.0f);
|
||||
}
|
||||
|
||||
private static float soundPitch(float power) {
|
||||
return Mth.clamp(1.08f - power * 0.006f, 0.65f, 1.08f);
|
||||
}
|
||||
|
||||
public static CameraShakeProfile determineCameraShakeProfile(float power, double distance, boolean playerInCave, ServerLevel level) {
|
||||
if (!((Boolean)Config.CLIENT.enableCameraShake.get()).booleanValue() && !((Boolean)Config.COMMON.enablePlayerShake.get()).booleanValue()) {
|
||||
return new CameraShakeProfile(0.0f, 0, 0.0f);
|
||||
}
|
||||
double maxDistance = Math.max(18.0, power * (playerInCave ? 8.0 : 5.0));
|
||||
double percent = Math.max(0.0, 1.0 - distance / maxDistance);
|
||||
float amplifier = ((Double)Config.COMMON.playerShakeAmplifier.get()).floatValue();
|
||||
float intensity = (float)Mth.clamp(percent * (0.45 + power * 0.035) * amplifier, 0.0, 4.0);
|
||||
int duration = intensity <= 0.02f ? 0 : Mth.clamp((int)Math.round(10 + power * 1.4 + percent * 45.0), 6, 140);
|
||||
float push = (float)Mth.clamp(percent * power * 0.07, 0.0, 2.0);
|
||||
return new CameraShakeProfile(intensity, duration, push);
|
||||
}
|
||||
|
||||
private static void pushPlayer(ServerPlayer player, Vec3 center, float power, double distance) {
|
||||
Vec3 direction = player.position().subtract(center);
|
||||
if (direction.lengthSqr() < 0.0001) {
|
||||
direction = new Vec3(0.0, 1.0, 0.0);
|
||||
}
|
||||
double strength = Mth.clamp((1.0 - distance / Math.max(12.0, power * 4.0)) * power * 0.06, 0.0, 1.4);
|
||||
player.setDeltaMovement(player.getDeltaMovement().add(direction.normalize().scale(strength).add(0.0, Math.min(0.45, strength * 0.35), 0.0)));
|
||||
}
|
||||
|
||||
private static boolean hasLineOfSight(ServerLevel level, Vec3 from, Vec3 to) {
|
||||
return level.clip(new ClipContext(from, to, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, null)).getType() == HitResult.Type.MISS;
|
||||
}
|
||||
|
||||
private static boolean isInCave(ServerLevel level, BlockPos pos) {
|
||||
return !level.canSeeSky(pos) && solidBlocksAbove(level, pos, 16) >= 5;
|
||||
}
|
||||
|
||||
private static boolean isInHouse(ServerLevel level, BlockPos pos) {
|
||||
return !level.canSeeSky(pos) && solidBlocksAbove(level, pos, 8) >= 2;
|
||||
}
|
||||
|
||||
private static int solidBlocksAbove(ServerLevel level, BlockPos pos, int height) {
|
||||
int count = 0;
|
||||
for (int y = 1; y <= height; ++y) {
|
||||
if (!level.getBlockState(pos.above(y)).isAir()) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static ServerPlayer nearestPlayer(ServerLevel level, Vec3 pos) {
|
||||
ServerPlayer nearest = null;
|
||||
double nearestDistance = Double.MAX_VALUE;
|
||||
for (ServerPlayer player : level.players()) {
|
||||
double distance = player.position().distanceToSqr(pos);
|
||||
if (distance < nearestDistance) {
|
||||
nearestDistance = distance;
|
||||
nearest = player;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
|
||||
@@ -1,18 +1,44 @@
|
||||
package com.vinlanx.explosionoverhaul.client;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
public class CameraShakeConcussionEffect {
|
||||
private static int ticksRemaining;
|
||||
private static int totalTicks;
|
||||
private static float intensity;
|
||||
|
||||
public static void start(int seconds, float intensity) {
|
||||
CameraShakeConcussionEffect.totalTicks = Math.max(1, seconds * 20);
|
||||
CameraShakeConcussionEffect.ticksRemaining = Math.max(CameraShakeConcussionEffect.ticksRemaining, CameraShakeConcussionEffect.totalTicks);
|
||||
CameraShakeConcussionEffect.intensity = Math.max(CameraShakeConcussionEffect.intensity, intensity);
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
ticksRemaining = 0;
|
||||
totalTicks = 0;
|
||||
intensity = 0.0f;
|
||||
}
|
||||
|
||||
public static void onClientTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (ticksRemaining <= 0 || mc.player == null) {
|
||||
intensity = 0.0f;
|
||||
return;
|
||||
}
|
||||
applySway(mc.player, totalTicks);
|
||||
--ticksRemaining;
|
||||
}
|
||||
|
||||
public static void applySway(LocalPlayer player, float totalTicks) {
|
||||
float progress = 1.0f - ticksRemaining / Math.max(1.0f, totalTicks);
|
||||
float eased = easeInOutCubic(Math.min(1.0f, progress));
|
||||
float fade = 1.0f - eased;
|
||||
float yaw = (float)Math.sin(player.tickCount * 0.31f) * intensity * 0.7f * fade;
|
||||
float pitch = (float)Math.cos(player.tickCount * 0.23f) * intensity * 0.35f * fade;
|
||||
player.setYRot(player.getYRot() + yaw);
|
||||
player.setXRot(Mth.clamp(player.getXRot() + pitch, -89.0f, 89.0f));
|
||||
}
|
||||
|
||||
public static float easeInOutCubic(float t) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.vinlanx.explosionoverhaul.client;
|
||||
|
||||
import com.vinlanx.explosionoverhaul.ExplosionOverhaul;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.neoforge.client.event.ClientTickEvent;
|
||||
import net.neoforged.neoforge.client.event.RenderGuiEvent;
|
||||
|
||||
@EventBusSubscriber(modid = ExplosionOverhaul.MODID, value = Dist.CLIENT)
|
||||
public final class ClientEffectEvents {
|
||||
private ClientEffectEvents() {
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onClientTick(ClientTickEvent.Post event) {
|
||||
ClientEffects.onClientTick();
|
||||
ConcussionAudioEffect.onClientTick();
|
||||
CameraShakeConcussionEffect.onClientTick();
|
||||
LowPassConcussionEffect.onClientTick();
|
||||
DeafnessConcussionEffect.onClientTick();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderGui(RenderGuiEvent.Post event) {
|
||||
ClientEffects.renderFlash(event);
|
||||
ConcussionAudioEffect.renderHeartbeatHUD(event.getGuiGraphics());
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,247 @@
|
||||
package com.vinlanx.explosionoverhaul.client;
|
||||
|
||||
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 net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.neoforged.neoforge.client.event.RenderGuiEvent;
|
||||
|
||||
public class ClientEffects {
|
||||
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 int shakeTicks;
|
||||
private static float shakeIntensity;
|
||||
private static float pushIntensity;
|
||||
private static int flashTicks;
|
||||
private static float flashPower;
|
||||
|
||||
public static void addTrackedSound(PlayTrackedSoundPacket msg) {
|
||||
synchronized (TRACKED_SOUNDS) {
|
||||
TRACKED_SOUNDS.add(new DelayedSound(msg, Math.max(0L, msg.getDelayTicks())));
|
||||
}
|
||||
}
|
||||
|
||||
public static Vec3 calculateSoundPosition(Player player, Vec3 explosionPos, boolean isPlayerInHouse) {
|
||||
if (player == null || !isPlayerInHouse) {
|
||||
return explosionPos;
|
||||
}
|
||||
Vec3 direction = explosionPos.subtract(player.position());
|
||||
if (direction.lengthSqr() < 0.0001) {
|
||||
return explosionPos;
|
||||
}
|
||||
return player.position().add(direction.normalize().scale(Math.min(16.0, direction.length())));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static void triggerDelayedCameraShake(float intensity, int durationTicks, float pushIntensity, int delayTicks) {
|
||||
synchronized (DELAYED_SHAKES) {
|
||||
DELAYED_SHAKES.add(new DelayedShake(intensity, durationTicks, pushIntensity, Math.max(0, delayTicks)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void triggerRealisticExplosion(Vec3 position, float power) {
|
||||
if (!((Boolean)Config.CLIENT.enableExplosionParticles.get()).booleanValue()) {
|
||||
return;
|
||||
}
|
||||
synchronized (EXPLOSION_EFFECTS) {
|
||||
EXPLOSION_EFFECTS.add(new PhysicsBasedExplosionEffect(position, power));
|
||||
}
|
||||
}
|
||||
|
||||
public static void addFlashEffect(Vec3 explosionPos, float power) {
|
||||
if (!((Boolean)Config.CLIENT.enableFlashEffect.get()).booleanValue()) {
|
||||
return;
|
||||
}
|
||||
flashTicks = Math.max(flashTicks, 8 + Mth.floor(power * 0.5f));
|
||||
flashPower = Math.max(flashPower, 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public static void renderFlash(RenderGuiEvent.Post event) {
|
||||
renderFlash(event.getGuiGraphics());
|
||||
}
|
||||
|
||||
public static void renderFlash(GuiGraphics graphics) {
|
||||
if (flashTicks <= 0) {
|
||||
return;
|
||||
}
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
float alpha = Mth.clamp(flashTicks / 12.0f, 0.0f, 1.0f) * ((Double)Config.CLIENT.flashMaxOpacity.get()).floatValue();
|
||||
alpha = Mth.clamp(alpha * (0.6f + flashPower * 0.018f), 0.0f, 0.92f);
|
||||
int color = ((int)(alpha * 255.0f) << 24) | 0xFFFFD7;
|
||||
graphics.fill(0, 0, mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight(), color);
|
||||
}
|
||||
|
||||
public static void triggerShockwave(Vec3 position, float power) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || !((Boolean)Config.CLIENT.enableShockwaveEffect.get()).booleanValue()) {
|
||||
return;
|
||||
}
|
||||
int count = Math.min(120, Math.max(24, Math.round(power * 5.0f)));
|
||||
for (int i = 0; i < count; ++i) {
|
||||
double angle = Math.PI * 2.0 * i / count;
|
||||
double speed = 0.18 + Math.min(0.75, power * 0.018);
|
||||
mc.level.addParticle(ModParticles.LINE_SPARK.get(), position.x, position.y + 0.12, position.z, Math.cos(angle) * speed, 0.01, Math.sin(angle) * speed);
|
||||
}
|
||||
}
|
||||
|
||||
public static void triggerDustCloud(Vec3 position, float power) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || !((Boolean)Config.CLIENT.enableGroundDustEffect.get()).booleanValue()) {
|
||||
return;
|
||||
}
|
||||
int count = Math.min(180, Math.max(30, Math.round(power * 8.0f * ((Double)Config.CLIENT.groundDustQuality.get()).floatValue())));
|
||||
mc.level.addParticle(new SmokeParticleOptions(Math.max(1.4f, power * 0.35f), 80, 0.2f, 0.17f, 0.14f, 0.68f, true, 0.0f, 0.2f, null), position.x, position.y + 0.2, position.z, 0.0, 0.04, 0.0);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
Vec3 velocity = randomHorizontal(mc.player == null ? 0 : mc.player.getRandom().nextLong()).scale(0.04 + mc.level.random.nextDouble() * 0.16);
|
||||
mc.level.addParticle(new SmokeParticleOptions(0.9f + mc.level.random.nextFloat() * 1.7f, 45 + mc.level.random.nextInt(55), 0.18f, 0.16f, 0.13f, 0.58f, true, 0.0f, 0.2f, null), position.x, position.y + 0.1, position.z, velocity.x, 0.03 + mc.level.random.nextDouble() * 0.05, velocity.z);
|
||||
}
|
||||
}
|
||||
|
||||
public static void triggerMistCloud(Vec3 position, float power) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || !((Boolean)Config.CLIENT.enableGroundMistEffect.get()).booleanValue()) {
|
||||
return;
|
||||
}
|
||||
int count = Math.min(120, Math.max(20, Math.round(power * 5.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);
|
||||
}
|
||||
}
|
||||
|
||||
public static void triggerLineSparks(Vec3 position, float power) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || !((Boolean)Config.CLIENT.enableLineSparks.get()).booleanValue()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public static void triggerAmbientCaveDust(float power) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || mc.player == 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);
|
||||
}
|
||||
}
|
||||
|
||||
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 final class DelayedSound {
|
||||
private final PlayTrackedSoundPacket packet;
|
||||
private long delayTicks;
|
||||
|
||||
private DelayedSound(PlayTrackedSoundPacket packet, long delayTicks) {
|
||||
this.packet = packet;
|
||||
this.delayTicks = delayTicks;
|
||||
}
|
||||
|
||||
private boolean tick(Minecraft mc) {
|
||||
if (this.delayTicks-- > 0L) {
|
||||
return false;
|
||||
}
|
||||
if (mc.level == null || mc.player == null) {
|
||||
return true;
|
||||
}
|
||||
SoundEvent event = BuiltInRegistries.SOUND_EVENT.get(this.packet.getSoundId());
|
||||
if (event != null) {
|
||||
Vec3 pos = calculateSoundPosition(mc.player, this.packet.getExplosionPos(), this.packet.isPlayerInHouse());
|
||||
mc.level.playLocalSound(pos.x, pos.y, pos.z, event, SoundSource.AMBIENT, this.packet.getVolume(), this.packet.getPitch(), false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DelayedShake {
|
||||
private final float intensity;
|
||||
private final int durationTicks;
|
||||
private final float pushIntensity;
|
||||
private int delayTicks;
|
||||
|
||||
private DelayedShake(float intensity, int durationTicks, float pushIntensity, int delayTicks) {
|
||||
this.intensity = intensity;
|
||||
this.durationTicks = durationTicks;
|
||||
this.pushIntensity = pushIntensity;
|
||||
this.delayTicks = delayTicks;
|
||||
}
|
||||
|
||||
private boolean tick() {
|
||||
if (this.delayTicks-- > 0) {
|
||||
return false;
|
||||
}
|
||||
ClientEffects.triggerLocalCameraShake(this.intensity, this.durationTicks, this.pushIntensity);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,97 @@
|
||||
package com.vinlanx.explosionoverhaul.client;
|
||||
|
||||
import com.vinlanx.explosionoverhaul.Config;
|
||||
import com.vinlanx.explosionoverhaul.ModSounds;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
|
||||
public class ConcussionAudioEffect {
|
||||
private static int ticksRemaining;
|
||||
private static float currentIntensity;
|
||||
private static int heartbeatTicks;
|
||||
private static int tinnitusCooldown;
|
||||
|
||||
public static void start(float power, double distance, boolean hasDirectLineOfSight, boolean explosionInCave) {
|
||||
if (!((Boolean)Config.CLIENT.enableConcussion.get()).booleanValue()) {
|
||||
return;
|
||||
}
|
||||
double rawPercent = computePercent(power, distance, explosionInCave);
|
||||
double effectivePercent = hasDirectLineOfSight ? rawPercent : applyOcclusion(rawPercent);
|
||||
if (effectivePercent <= 1.0) {
|
||||
return;
|
||||
}
|
||||
float chance = ((Double)Config.CLIENT.concussionChanceMultiplier.get()).floatValue();
|
||||
currentIntensity = Math.max(currentIntensity, (float)(computeBaseIntensityPercent(effectivePercent) / 100.0 * chance));
|
||||
ticksRemaining = Math.max(ticksRemaining, (int)Math.round((computeBaseSilentSeconds(effectivePercent, explosionInCave) + power * 0.08) * 20.0 * ((Double)Config.CLIENT.concussionDurationMultiplier.get()).floatValue()));
|
||||
ticksRemaining = Math.min(ticksRemaining, 20 * 18);
|
||||
if (((Boolean)Config.CLIENT.enableCameraSway.get()).booleanValue()) {
|
||||
CameraShakeConcussionEffect.start(Math.max(1, ticksRemaining / 20), currentIntensity * ((Double)Config.CLIENT.cameraSwayIntensity.get()).floatValue());
|
||||
}
|
||||
if (((Boolean)Config.CLIENT.enableLowPass.get()).booleanValue()) {
|
||||
LowPassConcussionEffect.start(Math.max(1, ticksRemaining / 20), currentIntensity, effectivePercent, hasDirectLineOfSight ? "direct" : "occluded", Math.round(currentIntensity * 100.0f));
|
||||
}
|
||||
if (((Boolean)Config.CLIENT.enableDeafness.get()).booleanValue()) {
|
||||
DeafnessConcussionEffect.start(currentIntensity, computeBaseSilentSeconds(effectivePercent, explosionInCave), effectivePercent, hasDirectLineOfSight ? "direct" : "occluded", Math.round(currentIntensity * 100.0f));
|
||||
tinnitusCooldown = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static void stopAll() {
|
||||
ticksRemaining = 0;
|
||||
currentIntensity = 0.0f;
|
||||
heartbeatTicks = 0;
|
||||
tinnitusCooldown = 0;
|
||||
}
|
||||
|
||||
public static void onClientTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (ticksRemaining <= 0 || mc.player == null || mc.level == null) {
|
||||
currentIntensity = 0.0f;
|
||||
return;
|
||||
}
|
||||
--ticksRemaining;
|
||||
currentIntensity *= 0.992f;
|
||||
if (((Boolean)Config.CLIENT.enableHeartbeatPulse.get()).booleanValue()) {
|
||||
updateHeartbeat(mc.player, currentIntensity);
|
||||
}
|
||||
if (tinnitusCooldown > 0 && --tinnitusCooldown == 0) {
|
||||
mc.level.playLocalSound(mc.player.getX(), mc.player.getY(), mc.player.getZ(), ModSounds.LOW_SOUND.get(), SoundSource.AMBIENT, Math.min(0.8f, currentIntensity), 1.25f, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateHeartbeat(LocalPlayer player, float currentIntensity) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || heartbeatTicks-- > 0) {
|
||||
return;
|
||||
}
|
||||
heartbeatTicks = Math.max(10, (int)(32 - currentIntensity * 16.0f));
|
||||
mc.level.playLocalSound(player.getX(), player.getY(), player.getZ(), ModSounds.HEART_LAB.get(), SoundSource.PLAYERS, 0.28f + currentIntensity * 0.35f, 1.0f, false);
|
||||
if (heartbeatTicks > 4) {
|
||||
mc.level.playLocalSound(player.getX(), player.getY(), player.getZ(), ModSounds.HEART_DAB.get(), SoundSource.PLAYERS, 0.22f + currentIntensity * 0.25f, 1.0f, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getCurrentHeartbeatVisual() {
|
||||
return 0.0f;
|
||||
return Math.max(0.0f, Math.min(1.0f, currentIntensity));
|
||||
}
|
||||
|
||||
public static void renderHeartbeatHUD(GuiGraphics guiGraphics) {
|
||||
if (!((Boolean)Config.CLIENT.showHeartbeatHUD.get()).booleanValue() || currentIntensity <= 0.02f) {
|
||||
return;
|
||||
}
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
String text = "Heartbeat " + Math.round(60 + currentIntensity * 80) + " BPM";
|
||||
guiGraphics.drawString(mc.font, Component.literal(text), mc.getWindow().getGuiScaledWidth() - mc.font.width(text) - 8, 8, 0xFFFF7777);
|
||||
}
|
||||
|
||||
public static void sendDebugMessage(Minecraft mc, double effectivePercent, double rawPercent, boolean hasDirectLineOfSight, double distance, double maxDistance) {
|
||||
if (mc.player != null) {
|
||||
mc.player.displayClientMessage(Component.literal("Concussion " + Math.round(effectivePercent) + "% / " + Math.round(rawPercent) + "%").withStyle(pickColor(effectivePercent)), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static double computePercent(float power, double distance, boolean explosionInCave) {
|
||||
@@ -34,7 +103,7 @@ public class ConcussionAudioEffect {
|
||||
}
|
||||
|
||||
public static double computeMaxDistance(float power, boolean explosionInCave) {
|
||||
return explosionInCave ? Math.max(10.0, power * 4.0) : Math.max(5.0, power * 2.5);
|
||||
return explosionInCave ? Math.max(18.0, power * 8.0) : Math.max(10.0, power * 5.0);
|
||||
}
|
||||
|
||||
public static double applyOcclusion(double percent) {
|
||||
@@ -42,7 +111,7 @@ public class ConcussionAudioEffect {
|
||||
}
|
||||
|
||||
public static double computeBaseSilentSeconds(double effectivePercent, boolean explosionInCave) {
|
||||
return Math.max(0.0, effectivePercent / 25.0);
|
||||
return Math.max(0.0, effectivePercent / (explosionInCave ? 16.0 : 24.0));
|
||||
}
|
||||
|
||||
public static double computeBaseIntensityPercent(double effectivePercent) {
|
||||
@@ -54,31 +123,31 @@ public class ConcussionAudioEffect {
|
||||
}
|
||||
|
||||
public static double computeLowpassPowerChance(float power, boolean explosionInCave) {
|
||||
return 0.0;
|
||||
return Math.min(1.0, (power / (explosionInCave ? 18.0 : 28.0)) * ((Double)Config.CLIENT.lowPassChanceMultiplier.get()).doubleValue());
|
||||
}
|
||||
|
||||
public static double computePowerChance(float power, boolean explosionInCave) {
|
||||
return 0.0;
|
||||
return Math.min(1.0, (power / (explosionInCave ? 14.0 : 24.0)) * ((Double)Config.CLIENT.deafnessChanceMultiplier.get()).doubleValue());
|
||||
}
|
||||
|
||||
public static double computeBaseBlurSilentSeconds(double effectivePercent, boolean explosionInCave) {
|
||||
return 0.0;
|
||||
return computeBaseSilentSeconds(effectivePercent, explosionInCave) * 0.65;
|
||||
}
|
||||
|
||||
public static double computeBlurPowerMultiplier(float power) {
|
||||
return 1.0;
|
||||
return Math.max(1.0, power / 16.0);
|
||||
}
|
||||
|
||||
public static double computeBaseBlurIntensityPercent(double effectivePercent) {
|
||||
return 0.0;
|
||||
return Math.min(100.0, effectivePercent * 0.8);
|
||||
}
|
||||
|
||||
public static double computeBaseSwayIntensityPercent(double effectivePercent) {
|
||||
return 0.0;
|
||||
return Math.min(100.0, effectivePercent * 0.75);
|
||||
}
|
||||
|
||||
public static double computeBaseLowpassIntensityPercent(double effectivePercent) {
|
||||
return 0.0;
|
||||
return Math.min(100.0, effectivePercent * 0.9);
|
||||
}
|
||||
|
||||
public static ChatFormatting pickColor(double percent) {
|
||||
|
||||
@@ -48,7 +48,6 @@ public class CustomGlowParticle extends TextureSheetParticle {
|
||||
return;
|
||||
}
|
||||
float progress = this.age / (float)this.lifetime;
|
||||
this.setSpriteFromAge(this.sprites);
|
||||
this.alpha = Mth.clamp(1.0f - progress, 0.0f, 1.0f) * 0.92f;
|
||||
this.quadSize = this.baseSize * (0.8f + progress * 1.8f);
|
||||
}
|
||||
|
||||
@@ -5,24 +5,48 @@ import net.minecraft.client.Minecraft;
|
||||
public class DeafnessConcussionEffect {
|
||||
public static boolean debugShowChat = false;
|
||||
public static volatile boolean enabled = true;
|
||||
private static int ticksRemaining;
|
||||
private static int totalTicks;
|
||||
private static float intensity;
|
||||
|
||||
public static boolean start(float intensity, double silentSeconds, double effectivePercent, String visibility, int intensityPercent) {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
DeafnessConcussionEffect.intensity = Math.max(DeafnessConcussionEffect.intensity, intensity);
|
||||
totalTicks = Math.max(1, (int)Math.round(Math.max(1.0, silentSeconds) * 20.0));
|
||||
ticksRemaining = Math.max(ticksRemaining, totalTicks);
|
||||
LowPassConcussionEffect.setDeafnessGain((float)Math.max(0.18, 1.0 - intensity * 0.75));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
ticksRemaining = 0;
|
||||
intensity = 0.0f;
|
||||
resetVolume();
|
||||
}
|
||||
|
||||
public static boolean isActive() {
|
||||
return false;
|
||||
return enabled && ticksRemaining > 0 && intensity > 0.01f;
|
||||
}
|
||||
|
||||
public static void resetVolume() {
|
||||
LowPassConcussionEffect.setDeafnessGain(1.0f);
|
||||
}
|
||||
|
||||
public static void applyMasterVolume(Minecraft mc, double volume) {
|
||||
}
|
||||
|
||||
public static void onClientTick() {
|
||||
if (ticksRemaining <= 0) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
--ticksRemaining;
|
||||
double remaining = ticksRemaining / Math.max(1.0, totalTicks);
|
||||
LowPassConcussionEffect.setDeafnessGain((float)Math.max(0.18, lerp(1.0, 1.0 - intensity * 0.75, easeOutQuad(remaining))));
|
||||
}
|
||||
|
||||
public static double lerp(double a, double b, double t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ public class FirstTimeScreen extends Screen {
|
||||
private static final int BORDER = 0xFFFF7A70;
|
||||
private static final int BORDER_HOVER = 0xFFFF9B91;
|
||||
private static final Choice[] CHOICES = new Choice[] {
|
||||
new Choice("gui_screen_1.png", 10, 18, "REALISTIC", Config.Client.ParticleRenderMode.REALISTIC),
|
||||
new Choice("gui_screen_2.png", 8, 18, "VANILLA-LIKE", Config.Client.ParticleRenderMode.VANILA),
|
||||
new Choice("gui_screen_3.png", 28, 35, "REALISTIC 2", Config.Client.ParticleRenderMode.REALISTIC_2)
|
||||
new Choice("gui_screen_1.png", 0, 18, "REALISTIC", Config.Client.ParticleRenderMode.REALISTIC),
|
||||
new Choice("gui_screen_2.png", 0, 18, "VANILLA-LIKE", Config.Client.ParticleRenderMode.VANILA),
|
||||
new Choice("gui_screen_3.png", 0, 35, "REALISTIC 2", Config.Client.ParticleRenderMode.REALISTIC_2)
|
||||
};
|
||||
|
||||
private final Rect[] cardRects = new Rect[] { Rect.empty(), Rect.empty(), Rect.empty() };
|
||||
@@ -38,15 +38,15 @@ public class FirstTimeScreen extends Screen {
|
||||
graphics.drawCenteredString(this.font, Component.literal("You can change this anytime in the config"), this.width / 2, top + 25, 0xFFE5E5E5);
|
||||
graphics.drawCenteredString(this.font, Component.literal("The game may differ from the animations because compression has altered them."), this.width / 2, top + 48, 0xFF9C9C9C);
|
||||
|
||||
int availableCardH = Math.max(54, (this.height - top - 150) / 2);
|
||||
int cardW = Math.min(300, Math.max(120, (this.width - 190) / 2));
|
||||
int availableCardH = Math.max(54, (this.height - top - 178) / 2);
|
||||
int cardW = Math.min(260, Math.max(110, (this.width - 220) / 2));
|
||||
cardW = Math.min(cardW, availableCardH * FRAME_WIDTH / FRAME_HEIGHT);
|
||||
int cardH = cardW * FRAME_HEIGHT / FRAME_WIDTH;
|
||||
int gap = Math.max(34, this.width / 28);
|
||||
int firstX = this.width / 2 - cardW - gap / 2;
|
||||
int secondX = this.width / 2 + gap / 2;
|
||||
int firstRowY = top + 70;
|
||||
int secondRowY = firstRowY + cardH + 40;
|
||||
int secondRowY = firstRowY + cardH + 34;
|
||||
|
||||
this.cardRects[0] = new Rect(firstX, firstRowY, cardW, cardH + 34);
|
||||
this.cardRects[1] = new Rect(secondX, firstRowY, cardW, cardH + 34);
|
||||
|
||||
@@ -41,8 +41,8 @@ public class GuideSlidesScreen extends Screen {
|
||||
this.drawAmbient(graphics);
|
||||
|
||||
int imageY = Math.max(28, this.height / 9);
|
||||
int maxImageH = Math.max(96, this.height - imageY - 135);
|
||||
int imageW = Math.min(Math.min(this.width - 290, 860), maxImageH * FRAME_WIDTH / FRAME_HEIGHT);
|
||||
int maxImageH = Math.max(90, this.height - imageY - 185);
|
||||
int imageW = Math.min(Math.min(this.width - 360, 820), maxImageH * FRAME_WIDTH / FRAME_HEIGHT);
|
||||
imageW = Math.max(180, imageW);
|
||||
int imageH = imageW * FRAME_HEIGHT / FRAME_WIDTH;
|
||||
int imageX = (this.width - imageW) / 2;
|
||||
@@ -61,7 +61,7 @@ public class GuideSlidesScreen extends Screen {
|
||||
int textY = imageY + imageH + 24;
|
||||
int textBottom = this.drawCenteredWrappedText(graphics, slide.text, this.width / 2, textY, textWidth, 0xFFFFFFFF);
|
||||
|
||||
int arrowSize = 34;
|
||||
int arrowSize = 24;
|
||||
int arrowY = imageY + imageH / 2 - arrowSize / 2;
|
||||
this.leftArrow = new Rect(Math.max(18, imageX / 2 - arrowSize / 2), arrowY, arrowSize, arrowSize);
|
||||
this.rightArrow = new Rect(Math.min(this.width - arrowSize - 18, imageX + imageW + (this.width - imageX - imageW) / 2 - arrowSize / 2), arrowY, arrowSize, arrowSize);
|
||||
@@ -72,9 +72,9 @@ public class GuideSlidesScreen extends Screen {
|
||||
this.drawArrow(graphics, this.rightArrow, ">", this.rightArrow.contains(mouseX, mouseY));
|
||||
}
|
||||
|
||||
int skipW = 96;
|
||||
int skipH = 24;
|
||||
int skipY = Math.min(Math.max(textBottom + 10, this.height - skipH - 18), this.height - skipH - 8);
|
||||
int skipW = 78;
|
||||
int skipH = 20;
|
||||
int skipY = Math.min(Math.max(textBottom + 14, this.height - skipH - 14), this.height - skipH - 6);
|
||||
this.skipButton = new Rect((this.width - skipW) / 2, skipY, skipW, skipH);
|
||||
this.drawFlatButton(graphics, this.skipButton, "Skip Guide", this.skipButton.contains(mouseX, mouseY));
|
||||
}
|
||||
|
||||
@@ -3,21 +3,45 @@ package com.vinlanx.explosionoverhaul.client;
|
||||
public class LowPassConcussionEffect {
|
||||
public static boolean debugShowChat = false;
|
||||
public static volatile boolean enabled = true;
|
||||
private static int ticksRemaining;
|
||||
private static int totalTicks;
|
||||
private static float targetIntensity;
|
||||
private static float currentHfMultiplier = 1.0f;
|
||||
private static float currentGainMultiplier = 1.0f;
|
||||
private static boolean compatibilityMode;
|
||||
|
||||
public static void start(int durationSeconds, float intensity) {
|
||||
start(durationSeconds, intensity, intensity * 100.0, "direct", Math.round(intensity * 100.0f));
|
||||
}
|
||||
|
||||
public static void start(int durationSeconds, float intensity, double effectivePercent, String visibility, int intensityPercent) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
totalTicks = Math.max(1, durationSeconds * 20);
|
||||
ticksRemaining = Math.max(ticksRemaining, totalTicks);
|
||||
targetIntensity = Math.max(targetIntensity, Math.max(0.0f, intensity));
|
||||
updateMultipliers();
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
ticksRemaining = 0;
|
||||
targetIntensity = 0.0f;
|
||||
currentHfMultiplier = 1.0f;
|
||||
currentGainMultiplier = 1.0f;
|
||||
}
|
||||
|
||||
public static boolean isActive() {
|
||||
return false;
|
||||
return enabled && ticksRemaining > 0 && targetIntensity > 0.01f;
|
||||
}
|
||||
|
||||
public static void onClientTick() {
|
||||
if (ticksRemaining <= 0) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
--ticksRemaining;
|
||||
updateMultipliers();
|
||||
}
|
||||
|
||||
public static void ensureFilterExists() {
|
||||
@@ -30,20 +54,24 @@ public class LowPassConcussionEffect {
|
||||
}
|
||||
|
||||
public static void applyFilterParamsOnAudioThread(float hf, float gain) {
|
||||
currentHfMultiplier = hf;
|
||||
currentGainMultiplier = gain;
|
||||
}
|
||||
|
||||
public static void setCompatibilityMode(boolean enabled) {
|
||||
compatibilityMode = enabled;
|
||||
}
|
||||
|
||||
public static void setDeafnessGain(float gain) {
|
||||
currentGainMultiplier = Math.min(currentGainMultiplier, gain);
|
||||
}
|
||||
|
||||
public static float getCurrentHfMultiplier() {
|
||||
return 1.0f;
|
||||
return currentHfMultiplier;
|
||||
}
|
||||
|
||||
public static float getCurrentGainMultiplier() {
|
||||
return 1.0f;
|
||||
return currentGainMultiplier;
|
||||
}
|
||||
|
||||
public static float lerp(float a, float b, double t) {
|
||||
@@ -57,4 +85,12 @@ public class LowPassConcussionEffect {
|
||||
public static float easeInQuad(double t) {
|
||||
return (float)(t * t);
|
||||
}
|
||||
|
||||
private static void updateMultipliers() {
|
||||
double remaining = ticksRemaining / Math.max(1.0, totalTicks);
|
||||
double curve = easeOutQuad(Math.max(0.0, Math.min(1.0, remaining)));
|
||||
float amount = (float)(targetIntensity * curve);
|
||||
currentHfMultiplier = lerp(1.0f, compatibilityMode ? 0.45f : 0.18f, amount);
|
||||
currentGainMultiplier = lerp(1.0f, 0.55f, amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,87 @@
|
||||
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 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 final Vec3 position;
|
||||
private final float power;
|
||||
private final int lifetime;
|
||||
private int age;
|
||||
|
||||
public PhysicsBasedExplosionEffect(Vec3 position, float power) {
|
||||
this.position = position;
|
||||
this.power = power;
|
||||
this.lifetime = Mth.clamp(18 + Math.round(power * 1.4f), 22, 90);
|
||||
this.spawnInitialBurst();
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
++this.age;
|
||||
ClientLevel level = Minecraft.getInstance().level;
|
||||
if (level == null) {
|
||||
return;
|
||||
}
|
||||
float progress = this.age / (float)this.lifetime;
|
||||
if (progress < 0.45f) {
|
||||
int count = Math.max(2, Math.round(this.power * 0.7f));
|
||||
for (int i = 0; i < count; ++i) {
|
||||
Vec3 velocity = randomDirection(level).scale(0.05 + level.random.nextDouble() * 0.18);
|
||||
level.addParticle(new CustomGlowParticleOptions(i % 2, this.power, 0.75f + level.random.nextFloat() * 0.75f, i % 3, (float)this.position.y, this.power * 2.2f, progress), this.position.x, this.position.y, this.position.z, velocity.x, Math.abs(velocity.y) * 0.45, velocity.z);
|
||||
}
|
||||
}
|
||||
if (progress < 0.8f && ((Boolean)Config.CLIENT.enablePlasmaParticles.get()).booleanValue()) {
|
||||
int count = Math.max(1, Math.round(this.power * 0.28f));
|
||||
for (int i = 0; i < count; ++i) {
|
||||
Vec3 velocity = randomDirection(level).scale(0.18 + level.random.nextDouble() * 0.7);
|
||||
level.addParticle(new PlasmaParticleOptions(this.power), this.position.x, this.position.y, this.position.z, velocity.x, velocity.y, velocity.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return true;
|
||||
return this.age >= this.lifetime;
|
||||
}
|
||||
|
||||
private void spawnInitialBurst() {
|
||||
ClientLevel level = Minecraft.getInstance().level;
|
||||
if (level == null) {
|
||||
return;
|
||||
}
|
||||
Config.Client.ParticleRenderMode mode = Config.CLIENT.particleRenderMode.get();
|
||||
int glowCount = switch (mode) {
|
||||
case REALISTIC -> Math.round(this.power * 10.0f);
|
||||
case REALISTIC_2 -> Math.round(this.power * 12.0f);
|
||||
case VANILA -> Math.round(this.power * 6.0f);
|
||||
};
|
||||
glowCount = Mth.clamp(glowCount, 24, 260);
|
||||
for (int i = 0; i < glowCount; ++i) {
|
||||
Vec3 velocity = randomDirection(level).scale(0.04 + level.random.nextDouble() * 0.22);
|
||||
level.addParticle(new CustomGlowParticleOptions(i % 2, this.power, 0.75f + level.random.nextFloat() * 0.9f, i % 3, (float)this.position.y, this.power * 2.2f, 0.0f), this.position.x, this.position.y, this.position.z, velocity.x, velocity.y * 0.35, velocity.z);
|
||||
}
|
||||
int smokeCount = Mth.clamp(Math.round(this.power * 7.0f), 18, 180);
|
||||
for (int i = 0; i < smokeCount; ++i) {
|
||||
Vec3 velocity = randomDirection(level).scale(0.02 + level.random.nextDouble() * 0.12);
|
||||
level.addParticle(new SmokeParticleOptions(0.8f + level.random.nextFloat() * Math.max(1.6f, this.power * 0.28f), 60 + level.random.nextInt(60), 0.18f, 0.16f, 0.13f, 0.62f, true, 0.0f, 0.2f, null), this.position.x, this.position.y + 0.1, this.position.z, velocity.x, Math.abs(velocity.y) * 0.25 + 0.02, velocity.z);
|
||||
}
|
||||
int sparks = Mth.clamp(Math.round(this.power * 5.0f), 10, 140);
|
||||
for (int i = 0; i < sparks; ++i) {
|
||||
Vec3 velocity = randomDirection(level).scale(0.28 + level.random.nextDouble() * 1.1);
|
||||
level.addParticle(ModParticles.LINE_SPARK.get(), this.position.x, this.position.y + 0.15, this.position.z, velocity.x, Math.abs(velocity.y) * 0.5, velocity.z);
|
||||
}
|
||||
}
|
||||
|
||||
private static Vec3 randomDirection(ClientLevel level) {
|
||||
double yaw = level.random.nextDouble() * Math.PI * 2.0;
|
||||
double y = level.random.nextDouble() * 0.9 - 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,22 @@ public final class NetworkEvent {
|
||||
}
|
||||
|
||||
public static class Context {
|
||||
private final ServerPlayer sender;
|
||||
|
||||
public Context() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public Context(ServerPlayer sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
public void enqueueWork(Runnable runnable) {
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
public ServerPlayer getSender() {
|
||||
return null;
|
||||
return this.sender;
|
||||
}
|
||||
|
||||
public void setPacketHandled(boolean handled) {
|
||||
|
||||
@@ -1,18 +1,119 @@
|
||||
package com.vinlanx.explosionoverhaul.compat.network.simple;
|
||||
|
||||
import com.vinlanx.explosionoverhaul.ExplosionOverhaul;
|
||||
import com.vinlanx.explosionoverhaul.compat.network.NetworkEvent;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.neoforged.neoforge.client.network.ClientPacketDistributor;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
|
||||
|
||||
public class SimpleChannel {
|
||||
private static final int MAX_PAYLOAD_SIZE = 1_048_576;
|
||||
private final Map<Integer, MessageRegistration<?>> registrationsById = new HashMap<>();
|
||||
private final Map<Class<?>, Integer> idsByType = new HashMap<>();
|
||||
|
||||
public <MSG> void registerMessage(int id, Class<MSG> messageType, BiConsumer<MSG, FriendlyByteBuf> encoder, Function<FriendlyByteBuf, MSG> decoder, BiConsumer<MSG, Supplier<NetworkEvent.Context>> handler) {
|
||||
MessageRegistration<MSG> registration = new MessageRegistration<>(messageType, encoder, decoder, handler);
|
||||
this.registrationsById.put(id, registration);
|
||||
this.idsByType.put(messageType, id);
|
||||
}
|
||||
|
||||
public void registerPayloads(RegisterPayloadHandlersEvent event) {
|
||||
PayloadRegistrar registrar = event.registrar("1");
|
||||
registrar.playBidirectional(LegacyPayload.TYPE, LegacyPayload.STREAM_CODEC, this::handlePayload);
|
||||
}
|
||||
|
||||
public void send(Object target, Object message) {
|
||||
LegacyPayload payload = this.encodePayload(message);
|
||||
if (payload == null) {
|
||||
return;
|
||||
}
|
||||
if (target instanceof ServerPlayer player) {
|
||||
PacketDistributor.sendToPlayer(player, payload);
|
||||
return;
|
||||
}
|
||||
PacketDistributor.sendToAllPlayers(payload);
|
||||
}
|
||||
|
||||
public void sendToServer(Object message) {
|
||||
LegacyPayload payload = this.encodePayload(message);
|
||||
if (payload != null) {
|
||||
ClientPacketDistributor.sendToServer(payload);
|
||||
}
|
||||
}
|
||||
|
||||
private LegacyPayload encodePayload(Object message) {
|
||||
Integer id = this.idsByType.get(message.getClass());
|
||||
if (id == null) {
|
||||
ExplosionOverhaul.LOGGER.warn("Tried to send unregistered legacy packet {}", message.getClass().getName());
|
||||
return null;
|
||||
}
|
||||
MessageRegistration<Object> registration = this.registration(id);
|
||||
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer());
|
||||
registration.encoder.accept(message, buffer);
|
||||
byte[] data = new byte[buffer.readableBytes()];
|
||||
buffer.readBytes(data);
|
||||
return new LegacyPayload(id, data);
|
||||
}
|
||||
|
||||
private void handlePayload(LegacyPayload payload, IPayloadContext context) {
|
||||
MessageRegistration<Object> registration = this.registration(payload.messageId());
|
||||
if (registration == null) {
|
||||
ExplosionOverhaul.LOGGER.warn("Received unknown legacy packet id {}", payload.messageId());
|
||||
return;
|
||||
}
|
||||
Player contextPlayer;
|
||||
try {
|
||||
contextPlayer = context.player();
|
||||
} catch (Exception ignored) {
|
||||
contextPlayer = null;
|
||||
}
|
||||
ServerPlayer sender = contextPlayer instanceof ServerPlayer serverPlayer ? serverPlayer : null;
|
||||
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.wrappedBuffer(payload.data()));
|
||||
Object decoded = registration.decoder.apply(buffer);
|
||||
NetworkEvent.Context legacyContext = new NetworkEvent.Context(sender);
|
||||
registration.handler.accept(decoded, () -> legacyContext);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private MessageRegistration<Object> registration(int id) {
|
||||
return (MessageRegistration<Object>)this.registrationsById.get(id);
|
||||
}
|
||||
|
||||
private record MessageRegistration<MSG>(
|
||||
Class<MSG> messageType,
|
||||
BiConsumer<MSG, FriendlyByteBuf> encoder,
|
||||
Function<FriendlyByteBuf, MSG> decoder,
|
||||
BiConsumer<MSG, Supplier<NetworkEvent.Context>> handler) {
|
||||
}
|
||||
|
||||
public record LegacyPayload(int messageId, byte[] data) implements CustomPacketPayload {
|
||||
public static final CustomPacketPayload.Type<LegacyPayload> TYPE =
|
||||
new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(ExplosionOverhaul.MODID, "legacy_packet"));
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, LegacyPayload> STREAM_CODEC = StreamCodec.of(
|
||||
(buffer, value) -> {
|
||||
buffer.writeVarInt(value.messageId);
|
||||
buffer.writeByteArray(value.data);
|
||||
},
|
||||
buffer -> new LegacyPayload(buffer.readVarInt(), buffer.readByteArray(MAX_PAYLOAD_SIZE)));
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
{
|
||||
"variants": {
|
||||
"facing=north": { "model": "explosionoverhaul:block/vinlanx_the_light" },
|
||||
"facing=south": { "model": "explosionoverhaul:block/vinlanx_the_light", "y": 180 },
|
||||
"facing=east": { "model": "explosionoverhaul:block/vinlanx_the_light", "y": 90 },
|
||||
"facing=west": { "model": "explosionoverhaul:block/vinlanx_the_light", "y": 270 }
|
||||
"": { "model": "explosionoverhaul:block/vinlanx_the_light" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,6 @@
|
||||
{
|
||||
"textures": [
|
||||
"explosionoverhaul:soft_glow",
|
||||
"explosionoverhaul:soft_glow_e",
|
||||
"explosionoverhaul:glow/glow_sheet_1",
|
||||
"explosionoverhaul:glow/glow_sheet_2",
|
||||
"explosionoverhaul:glow/glow_sheet_3",
|
||||
"explosionoverhaul:glow/glow_sheet_4",
|
||||
"explosionoverhaul:glow_2/glow_2_sheet_1",
|
||||
"explosionoverhaul:glow_2/glow_2_sheet_2",
|
||||
"explosionoverhaul:glow_2/glow_2_sheet_3",
|
||||
"explosionoverhaul:glow_2/glow_2_sheet_4",
|
||||
"explosionoverhaul:sglow/sglow_sheet_1",
|
||||
"explosionoverhaul:sglow/sglow_sheet_2",
|
||||
"explosionoverhaul:sglow/sglow_sheet_3",
|
||||
"explosionoverhaul:sglow/sglow_sheet_4",
|
||||
"explosionoverhaul:glow/glow_e_sheet_1",
|
||||
"explosionoverhaul:glow/glow_e_sheet_2",
|
||||
"explosionoverhaul:glow/glow_e_sheet_3",
|
||||
"explosionoverhaul:glow/glow_e_sheet_4",
|
||||
"explosionoverhaul:glow_2/glow_2_e_sheet_1",
|
||||
"explosionoverhaul:glow_2/glow_2_e_sheet_2",
|
||||
"explosionoverhaul:glow_2/glow_2_e_sheet_3",
|
||||
"explosionoverhaul:glow_2/glow_2_e_sheet_4",
|
||||
"explosionoverhaul:sglow/sglow_e_sheet_1",
|
||||
"explosionoverhaul:sglow/sglow_e_sheet_2",
|
||||
"explosionoverhaul:sglow/sglow_e_sheet_3",
|
||||
"explosionoverhaul:sglow/sglow_e_sheet_4",
|
||||
"explosionoverhaul:glow/64/glow_sheet_1",
|
||||
"explosionoverhaul:glow/64/glow_sheet_2",
|
||||
"explosionoverhaul:glow/64/glow_sheet_3",
|
||||
"explosionoverhaul:glow/64/glow_sheet_4",
|
||||
"explosionoverhaul:glow_2/64/glow_2_sheet_1",
|
||||
"explosionoverhaul:glow_2/64/glow_2_sheet_2",
|
||||
"explosionoverhaul:glow_2/64/glow_2_sheet_3",
|
||||
"explosionoverhaul:glow_2/64/glow_2_sheet_4",
|
||||
"explosionoverhaul:sglow/64/sglow_sheet_1",
|
||||
"explosionoverhaul:sglow/64/sglow_sheet_2",
|
||||
"explosionoverhaul:sglow/64/sglow_sheet_3",
|
||||
"explosionoverhaul:sglow/64/sglow_sheet_4",
|
||||
"explosionoverhaul:glow/64/glow_e_sheet_1",
|
||||
"explosionoverhaul:glow/64/glow_e_sheet_2",
|
||||
"explosionoverhaul:glow/64/glow_e_sheet_3",
|
||||
"explosionoverhaul:glow/64/glow_e_sheet_4",
|
||||
"explosionoverhaul:glow_2/64/glow_2_e_sheet_1",
|
||||
"explosionoverhaul:glow_2/64/glow_2_e_sheet_2",
|
||||
"explosionoverhaul:glow_2/64/glow_2_e_sheet_3",
|
||||
"explosionoverhaul:glow_2/64/glow_2_e_sheet_4",
|
||||
"explosionoverhaul:sglow/64/sglow_e_sheet_1",
|
||||
"explosionoverhaul:sglow/64/sglow_e_sheet_2",
|
||||
"explosionoverhaul:sglow/64/sglow_e_sheet_3",
|
||||
"explosionoverhaul:sglow/64/sglow_e_sheet_4"
|
||||
"explosionoverhaul:soft_glow_e"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user