diff --git a/src/main/java/dynamic_fps/impl/DynamicFPSMod.java b/src/main/java/dynamic_fps/impl/DynamicFPSMod.java index 03bc38a8..f039aa1f 100644 --- a/src/main/java/dynamic_fps/impl/DynamicFPSMod.java +++ b/src/main/java/dynamic_fps/impl/DynamicFPSMod.java @@ -106,8 +106,8 @@ public static int targetFrameRate() { return config.frameRateTarget(); } - public static float volumeMultiplier() { - return config.volumeMultiplier(); + public static float volumeMultiplier(SoundSource source) { + return config.volumeMultiplier(source); } public static boolean shouldShowToasts() { @@ -147,8 +147,10 @@ public static void handleStateChange(PowerState previous, PowerState current) { System.gc(); } - if (before.volumeMultiplier() != config.volumeMultiplier()) { - setVolumeMultiplier(config.volumeMultiplier()); + for (var source : SoundSource.values()) { + if (before.volumeMultiplier(source) != config.volumeMultiplier(source)) { + minecraft.getSoundManager().dynamic_fps$updateVolume(source); + } } if (before.graphicsState() != config.graphicsState()) { @@ -202,29 +204,6 @@ private static void checkForStateChanges0() { } } - private static void setVolumeMultiplier(float multiplier) { - // Set the sound engine to a new volume multiplier, - // Or instead pause it when the multiplier is zero. - - // We can not set the sound engine to a zero volume - // Because it stops all actively playing sounds and - // Makes for a rather jarring experience when music - // Is stopped. Also fixes now-playing compatibility - - var manager = minecraft.getSoundManager(); - - if (multiplier == 0) { - manager.pause(); - } else { - manager.resume(); - - manager.updateSourceVolume( - SoundSource.MASTER, - minecraft.options.getSoundSourceVolume(SoundSource.MASTER) * multiplier - ); - } - } - private static boolean checkForRender(long timeSinceLastRender) { int frameRateTarget = config.frameRateTarget(); diff --git a/src/main/java/dynamic_fps/impl/GraphicsState.java b/src/main/java/dynamic_fps/impl/GraphicsState.java index 2b1e748e..597e9cff 100644 --- a/src/main/java/dynamic_fps/impl/GraphicsState.java +++ b/src/main/java/dynamic_fps/impl/GraphicsState.java @@ -1,12 +1,5 @@ package dynamic_fps.impl; -import java.util.Locale; - -import com.mojang.serialization.Codec; -import com.mojang.serialization.DataResult; -import com.mojang.serialization.DynamicOps; -import com.mojang.serialization.codecs.PrimitiveCodec; - /* * Graphics settings to apply within a given power state. */ @@ -25,27 +18,4 @@ public enum GraphicsState { * Reduce graphics settings to minimal values, this will reload the world! */ MINIMAL; - - public static final Codec CODEC = new PrimitiveCodec() { - @Override - public T write(DynamicOps ops, GraphicsState value) { - return ops.createString(value.toString()); - } - - @Override - public DataResult read(DynamicOps ops, T input) { - var value = ops.getStringValue(input).get().left(); - - if (value.isEmpty()) { - return DataResult.error(() -> "Graphics state must not be empty!"); - } else { - return DataResult.success(GraphicsState.valueOf(value.get().toUpperCase(Locale.ROOT))); - } - } - }; - - @Override - public String toString() { - return super.toString().toLowerCase(Locale.ROOT); - } } diff --git a/src/main/java/dynamic_fps/impl/PowerState.java b/src/main/java/dynamic_fps/impl/PowerState.java index 2fc1cee4..4b475cd2 100644 --- a/src/main/java/dynamic_fps/impl/PowerState.java +++ b/src/main/java/dynamic_fps/impl/PowerState.java @@ -1,12 +1,5 @@ package dynamic_fps.impl; -import java.util.Locale; - -import com.mojang.serialization.Codec; -import com.mojang.serialization.DataResult; -import com.mojang.serialization.DynamicOps; -import com.mojang.serialization.codecs.PrimitiveCodec; - /** * An analog for device power states, applied to the Minecraft window. * @@ -35,24 +28,6 @@ public enum PowerState { public final boolean configurable; - public static final Codec CODEC = new PrimitiveCodec() { - @Override - public T write(DynamicOps ops, PowerState value) { - return ops.createString(value.toString().toLowerCase(Locale.ROOT)); - } - - @Override - public DataResult read(DynamicOps ops, T input) { - var value = ops.getStringValue(input).get().left(); - - if (value.isEmpty()) { - return DataResult.error(() -> "Power state must not be empty!"); - } else { - return DataResult.success(PowerState.valueOf(value.get().toUpperCase(Locale.ROOT))); - } - } - }; - private PowerState(boolean configurable) { this.configurable = configurable; } diff --git a/src/main/java/dynamic_fps/impl/compat/ClothConfig.java b/src/main/java/dynamic_fps/impl/compat/ClothConfig.java index fc726278..a47a3c84 100644 --- a/src/main/java/dynamic_fps/impl/compat/ClothConfig.java +++ b/src/main/java/dynamic_fps/impl/compat/ClothConfig.java @@ -1,10 +1,10 @@ package dynamic_fps.impl.compat; import me.shedaniel.clothconfig2.api.ConfigBuilder; -import me.shedaniel.clothconfig2.api.ConfigEntryBuilder; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundSource; import static dynamic_fps.impl.util.Localization.localized; @@ -48,17 +48,25 @@ public static Screen genConfigScreen(Screen parent) { .build() ); - category.addEntry( - entryBuilder.startIntSlider( - localized("config", "volume_multiplier"), - (int) (config.volumeMultiplier() * 100), - 0, 100 - ) - .setDefaultValue((int) (standard.volumeMultiplier() * 100)) - .setSaveConsumer(value -> config.setVolumeMultiplier(value / 100f)) - .setTextGetter(ClothConfig::volumeMultiplierMessage) - .build() - ); + var volumes = entryBuilder.startSubCategory(localized("config", "volume_multiplier")); + + for (var source : SoundSource.values()) { + var name = source.getName(); + + volumes.add( + entryBuilder.startIntSlider( + Component.translatable("soundCategory." + name), + (int) (config.volumeMultiplier(source) * 100), + 0, 100 + ) + .setDefaultValue((int) (standard.volumeMultiplier(source) * 100)) + .setSaveConsumer(value -> config.setVolumeMultiplier(source, value / 100f)) + .setTextGetter(ClothConfig::volumeMultiplierMessage) + .build() + ); + } + + category.addEntry(volumes.build()); category.addEntry( entryBuilder.startEnumSelector( diff --git a/src/main/java/dynamic_fps/impl/config/Config.java b/src/main/java/dynamic_fps/impl/config/Config.java index d01a3844..530328df 100644 --- a/src/main/java/dynamic_fps/impl/config/Config.java +++ b/src/main/java/dynamic_fps/impl/config/Config.java @@ -1,30 +1,35 @@ package dynamic_fps.impl.config; +import java.util.HashMap; +import java.util.Map; + import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dynamic_fps.impl.GraphicsState; +import dynamic_fps.impl.util.EnumCodec; +import net.minecraft.sounds.SoundSource; public final class Config { private int frameRateTarget; - private float volumeMultiplier; + private Map volumeMultipliers; private GraphicsState graphicsState; private boolean showToasts; private boolean runGarbageCollector; public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.INT.fieldOf("frame_rate_target").forGetter(Config::frameRateTarget), - Codec.FLOAT.fieldOf("volume_multiplier").forGetter(Config::volumeMultiplier), - GraphicsState.CODEC.fieldOf("graphics_state").forGetter(Config::graphicsState), + Codec.unboundedMap(new EnumCodec<>(SoundSource.values()), Codec.FLOAT).fieldOf("volume_multipliers").forGetter(Config::volumeMultipliers), + new EnumCodec<>(GraphicsState.values()).fieldOf("graphics_state").forGetter(Config::graphicsState), Codec.BOOL.fieldOf("show_toasts").forGetter(Config::showToasts), Codec.BOOL.fieldOf("run_garbage_collector").forGetter(Config::runGarbageCollector) ).apply(instance, Config::new)); - public static final Config ACTIVE = new Config(-1, 1.0f, GraphicsState.DEFAULT, true, false); + public static final Config ACTIVE = new Config(-1, new HashMap<>(), GraphicsState.DEFAULT, true, false); - public Config(int frameRateTarget, float volumeMultiplier, GraphicsState graphicsState, boolean showToasts, boolean runGarbageCollector) { + public Config(int frameRateTarget, Map volumeMultipliers, GraphicsState graphicsState, boolean showToasts, boolean runGarbageCollector) { this.frameRateTarget = frameRateTarget; - this.volumeMultiplier = volumeMultiplier; + this.volumeMultipliers = new HashMap<>(volumeMultipliers); // Ensure the map is mutable this.graphicsState = graphicsState; this.showToasts = showToasts; this.runGarbageCollector = runGarbageCollector; @@ -38,12 +43,16 @@ public void setFrameRateTarget(int value) { this.frameRateTarget = value; } - public float volumeMultiplier() { - return this.volumeMultiplier; + public Map volumeMultipliers() { + return this.volumeMultipliers; + } + + public float volumeMultiplier(SoundSource category) { + return this.volumeMultipliers.getOrDefault(category, 1.0f); } - public void setVolumeMultiplier(float value) { - this.volumeMultiplier = value; + public void setVolumeMultiplier(SoundSource category, float value) { + this.volumeMultipliers.put(category, value); } public GraphicsState graphicsState() { diff --git a/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java b/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java index b256cf95..d55f27b4 100644 --- a/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java +++ b/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java @@ -7,9 +7,12 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.EnumMap; +import java.util.HashMap; import java.util.Map; +import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; import com.mojang.serialization.codecs.RecordCodecBuilder; @@ -17,7 +20,9 @@ import dynamic_fps.impl.DynamicFPSMod; import dynamic_fps.impl.GraphicsState; import dynamic_fps.impl.PowerState; +import dynamic_fps.impl.util.EnumCodec; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.sounds.SoundSource; public final class DynamicFPSConfig { private Map configs; @@ -25,7 +30,7 @@ public final class DynamicFPSConfig { private static final Path CONFIGS = FabricLoader.getInstance().getConfigDir(); private static final Path CONFIG_FILE = CONFIGS.resolve(DynamicFPSMod.MOD_ID + ".json"); - private static final Codec> STATES_CODEC = Codec.unboundedMap(PowerState.CODEC, Config.CODEC); + private static final Codec> STATES_CODEC = Codec.unboundedMap(new EnumCodec<>(PowerState.values()), Config.CODEC); private static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( STATES_CODEC.fieldOf("states").forGetter(DynamicFPSConfig::configs) @@ -67,6 +72,8 @@ public static DynamicFPSConfig load() { } var root = JsonParser.parseString(data); + + upgradeConfig((JsonObject) root); var parsed = CODEC.parse(JsonOps.INSTANCE, root); return parsed.getOrThrow(false, RuntimeException::new); @@ -91,17 +98,65 @@ public void save() { public static Config getDefaultConfig(PowerState state) { switch (state) { case HOVERED: { - return new Config(60, 1.0f, GraphicsState.DEFAULT, true, false); + return new Config(60, withMasterVolume(1.0f), GraphicsState.DEFAULT, true, false); } case UNFOCUSED: { - return new Config(1, 0.25f, GraphicsState.DEFAULT, false, false); + return new Config(1, withMasterVolume(0.25f), GraphicsState.DEFAULT, false, false); } case INVISIBLE: { - return new Config(0, 0.0f, GraphicsState.DEFAULT, false, false); + return new Config(0, withMasterVolume(0.0f), GraphicsState.DEFAULT, false, false); } default: { throw new RuntimeException("Getting default configuration for unhandled power state " + state.toString()); } } } + + private static Map withMasterVolume(float value) { + var volumes = new HashMap(); + volumes.put(SoundSource.MASTER, value); + return volumes; + } + + private static void upgradeConfig(JsonObject root) { + upgradeVolumeMultiplier(root); + } + + private static void upgradeVolumeMultiplier(JsonObject root) { + // Convert each old power state config + // - { "volume_multiplier": 0.0, ... } + // + { "volume_multipliers": { "master": 0.0 }, ... } + if (!root.has("states")) { + return; + } + + var states = root.getAsJsonObject("states"); + + if (!states.isJsonObject()) { + return; + } + + for (var key : states.keySet()) { + var element = states.getAsJsonObject(key); + + if (!element.isJsonObject()) { + continue; + } + + if (!element.has("volume_multiplier")) { + continue; + } + + var multiplier = element.get("volume_multiplier"); + + if (!multiplier.isJsonPrimitive() || !((JsonPrimitive)multiplier).isNumber()) { + continue; + } + + var multipliers = new JsonObject(); + multipliers.add("master", multiplier); + + element.add("volume_multipliers", multipliers); + } + } } diff --git a/src/main/java/dynamic_fps/impl/mixin/LoadingOverlayMixin.java b/src/main/java/dynamic_fps/impl/mixin/LoadingOverlayMixin.java index a83c5012..5b61bd22 100644 --- a/src/main/java/dynamic_fps/impl/mixin/LoadingOverlayMixin.java +++ b/src/main/java/dynamic_fps/impl/mixin/LoadingOverlayMixin.java @@ -1,13 +1,14 @@ package dynamic_fps.impl.mixin; -import dynamic_fps.impl.util.DynamicFPSSplashOverlay; import net.minecraft.client.gui.screens.LoadingOverlay; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import dynamic_fps.impl.util.duck.DuckSplashOverlay; + @Mixin(LoadingOverlay.class) -public class LoadingOverlayMixin implements DynamicFPSSplashOverlay { +public class LoadingOverlayMixin implements DuckSplashOverlay { @Shadow private long fadeOutStart; diff --git a/src/main/java/dynamic_fps/impl/mixin/ScreenMixin.java b/src/main/java/dynamic_fps/impl/mixin/ScreenMixin.java index aad68ac2..89683877 100644 --- a/src/main/java/dynamic_fps/impl/mixin/ScreenMixin.java +++ b/src/main/java/dynamic_fps/impl/mixin/ScreenMixin.java @@ -6,12 +6,12 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import dynamic_fps.impl.util.DynamicFPSScreen; import dynamic_fps.impl.util.ModCompatibility; +import dynamic_fps.impl.util.duck.DuckScreen; import net.minecraft.client.gui.screens.Screen; @Mixin(Screen.class) -public class ScreenMixin implements DynamicFPSScreen { +public class ScreenMixin implements DuckScreen { @Unique private boolean dynamic_fps$canOptimize = false; diff --git a/src/main/java/dynamic_fps/impl/mixin/SoundEngineMixin.java b/src/main/java/dynamic_fps/impl/mixin/SoundEngineMixin.java index d99aa446..ce1e3880 100644 --- a/src/main/java/dynamic_fps/impl/mixin/SoundEngineMixin.java +++ b/src/main/java/dynamic_fps/impl/mixin/SoundEngineMixin.java @@ -1,15 +1,83 @@ package dynamic_fps.impl.mixin; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import com.mojang.blaze3d.audio.Listener; import dynamic_fps.impl.DynamicFPSMod; +import dynamic_fps.impl.util.duck.DuckSoundManager; +import net.minecraft.client.resources.sounds.SoundInstance; +import net.minecraft.client.sounds.ChannelAccess; import net.minecraft.client.sounds.SoundEngine; +import net.minecraft.sounds.SoundSource; @Mixin(SoundEngine.class) -public class SoundEngineMixin { +public class SoundEngineMixin implements DuckSoundManager { + @Shadow + private boolean loaded; + + @Shadow + @Final + private Listener listener; + + @Shadow + @Final + private Map instanceToChannel; + + @Shadow + private float getVolume(@Nullable SoundSource source) { + throw new RuntimeException("Failed to find SoundEngine#getVolume."); + } + + @Shadow + private float calculateVolume(SoundInstance instance) { + throw new RuntimeException("Failed to find SoundEngine#calculateVolume."); + }; + + public void dynamic_fps$updateVolume(SoundSource source) { + if (!this.loaded) { + return; + } + + if (source.equals(SoundSource.MASTER)) { + this.listener.setGain(this.getVolume(source)); + return; + } + + // When setting the volume to zero we pause music but cancel other types of sounds + // This results in a less jarring experience when quickly tabbing out and back in. + // Also fixes this compat bug: https://github.com/juliand665/Dynamic-FPS/issues/55 + var isMusic = source.equals(SoundSource.MUSIC) || source.equals(SoundSource.RECORDS); + + this.instanceToChannel.forEach((instance, handle) -> { + float volume = this.calculateVolume((SoundInstance) instance); + + if (instance.getSource().equals(source)) { + handle.execute(channel -> { + if (volume <= 0.0f) { + if (!isMusic) { + channel.stop(); + } else { + channel.pause(); + } + } else { + channel.unpause(); + channel.setVolume(volume); + } + }); + } + }); + } + /** * Cancels playing sounds while we are overwriting the volume to be off. * @@ -17,9 +85,22 @@ public class SoundEngineMixin { * Allows pausing and resuming the sound engine without cancelling active sounds. */ @Inject(method = { "play", "playDelayed" }, at = @At("HEAD"), cancellable = true) - private void play(CallbackInfo callbackInfo) { - if (DynamicFPSMod.volumeMultiplier() == 0.0f) { + private void play(SoundInstance instance, CallbackInfo callbackInfo) { + var master = DynamicFPSMod.volumeMultiplier(SoundSource.MASTER); + var source = DynamicFPSMod.volumeMultiplier(instance.getSource()); + + if (master == 0.0f || source == 0.0f) { callbackInfo.cancel(); } } + + /** + * Applies the user's requested volume multiplier to any newly played sounds. + */ + @Inject(method = "getVolume", at = @At("RETURN"), cancellable = true) + private void getVolume(@Nullable SoundSource source, CallbackInfoReturnable callbackInfo) { + if (source != null) { + callbackInfo.setReturnValue(callbackInfo.getReturnValue() * DynamicFPSMod.volumeMultiplier(source)); + } + } } diff --git a/src/main/java/dynamic_fps/impl/mixin/SoundManagerMixin.java b/src/main/java/dynamic_fps/impl/mixin/SoundManagerMixin.java new file mode 100644 index 00000000..f087c307 --- /dev/null +++ b/src/main/java/dynamic_fps/impl/mixin/SoundManagerMixin.java @@ -0,0 +1,21 @@ +package dynamic_fps.impl.mixin; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import dynamic_fps.impl.util.duck.DuckSoundManager; +import net.minecraft.client.sounds.SoundEngine; +import net.minecraft.client.sounds.SoundManager; +import net.minecraft.sounds.SoundSource; + +@Mixin(SoundManager.class) +public class SoundManagerMixin implements DuckSoundManager { + @Shadow + @Final + private SoundEngine soundEngine; + + public void dynamic_fps$updateVolume(SoundSource source) { + this.soundEngine.dynamic_fps$updateVolume(source); + } +} diff --git a/src/main/java/dynamic_fps/impl/util/DynamicFPSScreen.java b/src/main/java/dynamic_fps/impl/util/DynamicFPSScreen.java deleted file mode 100644 index aa773535..00000000 --- a/src/main/java/dynamic_fps/impl/util/DynamicFPSScreen.java +++ /dev/null @@ -1,11 +0,0 @@ -package dynamic_fps.impl.util; - -public interface DynamicFPSScreen { - public default boolean dynamic_fps$rendersBackground() { - throw new RuntimeException("Dynamic FPS' Screen mixin was not applied."); - } - - public default void dynamic_fps$setRendersBackground() { - throw new RuntimeException("Dynamic FPS' Screen mixin was not applied."); - } -} diff --git a/src/main/java/dynamic_fps/impl/util/DynamicFPSSplashOverlay.java b/src/main/java/dynamic_fps/impl/util/DynamicFPSSplashOverlay.java deleted file mode 100644 index 57043224..00000000 --- a/src/main/java/dynamic_fps/impl/util/DynamicFPSSplashOverlay.java +++ /dev/null @@ -1,7 +0,0 @@ -package dynamic_fps.impl.util; - -public interface DynamicFPSSplashOverlay { - public default boolean dynamic_fps$isReloadComplete() { - throw new RuntimeException("Dynamic FPS' SplashOverlay mixin was not applied."); - } -} diff --git a/src/main/java/dynamic_fps/impl/util/EnumCodec.java b/src/main/java/dynamic_fps/impl/util/EnumCodec.java new file mode 100644 index 00000000..8ba84eed --- /dev/null +++ b/src/main/java/dynamic_fps/impl/util/EnumCodec.java @@ -0,0 +1,47 @@ +package dynamic_fps.impl.util; + +import java.util.Locale; + +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.codecs.PrimitiveCodec; + +public class EnumCodec> implements PrimitiveCodec { + private final E[] members; + + public EnumCodec(E[] members) { + this.members = members; + + if (members.length == 0) { + throw new RuntimeException("EnumCodec has no members!"); + } + } + + @Override + public T write(DynamicOps ops, E value) { + return ops.createString(value.toString().toLowerCase(Locale.ROOT)); + } + + @Override + public DataResult read(DynamicOps ops, T input) { + var value = ops.getStringValue(input).get().left(); + + if (value.isEmpty()) { + return DataResult.error(() -> this.getTypeName() + " must not be empty!"); + } + + var inner = value.get().toUpperCase(Locale.ROOT); + + for (var member : this.members) { + if (member.name().equals(inner)) { + return DataResult.success(member); + } + } + + return DataResult.error(() -> this.getTypeName() + " has no value " + inner + "!"); + } + + private String getTypeName() { + return this.members[0].getDeclaringClass().getName(); + } +} diff --git a/src/main/java/dynamic_fps/impl/util/duck/DuckScreen.java b/src/main/java/dynamic_fps/impl/util/duck/DuckScreen.java new file mode 100644 index 00000000..6d560e40 --- /dev/null +++ b/src/main/java/dynamic_fps/impl/util/duck/DuckScreen.java @@ -0,0 +1,11 @@ +package dynamic_fps.impl.util.duck; + +public interface DuckScreen { + public default boolean dynamic_fps$rendersBackground() { + throw new RuntimeException("No implementation for dynamic_fps$rendersBackground was found."); + } + + public default void dynamic_fps$setRendersBackground() { + throw new RuntimeException("No implementation for dynamic_fps$rendersBackground was found."); + } +} diff --git a/src/main/java/dynamic_fps/impl/util/duck/DuckSoundManager.java b/src/main/java/dynamic_fps/impl/util/duck/DuckSoundManager.java new file mode 100644 index 00000000..41c8f00a --- /dev/null +++ b/src/main/java/dynamic_fps/impl/util/duck/DuckSoundManager.java @@ -0,0 +1,9 @@ +package dynamic_fps.impl.util.duck; + +import net.minecraft.sounds.SoundSource; + +public interface DuckSoundManager { + public default void dynamic_fps$updateVolume(SoundSource source) { + throw new RuntimeException("No implementation for dynamic_fps$updateVolume was found."); + } +} diff --git a/src/main/java/dynamic_fps/impl/util/duck/DuckSplashOverlay.java b/src/main/java/dynamic_fps/impl/util/duck/DuckSplashOverlay.java new file mode 100644 index 00000000..bdedb888 --- /dev/null +++ b/src/main/java/dynamic_fps/impl/util/duck/DuckSplashOverlay.java @@ -0,0 +1,7 @@ +package dynamic_fps.impl.util.duck; + +public interface DuckSplashOverlay { + public default boolean dynamic_fps$isReloadComplete() { + throw new RuntimeException("No implementation for dynamic_fps$isReloadComplete was found."); + } +} diff --git a/src/main/resources/dynamic_fps.mixins.json b/src/main/resources/dynamic_fps.mixins.json index da9d1887..6b661ccc 100644 --- a/src/main/resources/dynamic_fps.mixins.json +++ b/src/main/resources/dynamic_fps.mixins.json @@ -11,6 +11,7 @@ "MinecraftMixin", "ScreenMixin", "SoundEngineMixin", + "SoundManagerMixin", "StatsScreenMixin", "ToastComponentMixin", "WindowMixin", diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 98698870..c7103d0c 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -43,8 +43,10 @@ "custom": { "loom:injected_interfaces": { - "net/minecraft/class_437": ["dynamic_fps/impl/util/DynamicFPSScreen"], - "net/minecraft/class_425": ["dynamic_fps/impl/util/DynamicFPSSplashOverlay"] + "net/minecraft/class_437": ["dynamic_fps/impl/util/duck/DuckScreen"], + "net/minecraft/class_425": ["dynamic_fps/impl/util/duck/DuckSplashOverlay"], + "net/minecraft/class_1140": ["dynamic_fps/impl/util/duck/DuckSoundManager"], + "net/minecraft/class_1144": ["dynamic_fps/impl/util/duck/DuckSoundManager"] }, "dynamic_fps": { "optimized_screens": {