diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0b9c9f8a..e484cb76 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -8,7 +8,7 @@ jobs: matrix: java: [ 17, - 20, + 21, ] os: [ ubuntu-22.04, @@ -34,7 +34,7 @@ jobs: arguments: build --warning-mode=all cache-read-only: ${{ github.ref != 'refs/heads/main' }} - name: capture build artifacts - if: ${{ runner.os == 'Linux' && matrix.java == '20' }} # Upload artifacts from one job, ignore the rest + if: ${{ runner.os == 'Linux' && matrix.java == '21' }} # Upload artifacts from one job, ignore the rest uses: actions/upload-artifact@v3 with: name: dynamic-fps-artifacts diff --git a/build.gradle b/build.gradle index 4271c4b4..06cab9b5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'maven-publish' + id "maven-publish" alias libs.plugins.fabric.loom } @@ -19,13 +19,17 @@ dependencies { minecraft libs.minecraft mappings loom.officialMojangMappings() + modImplementation libs.fabric.api modImplementation libs.fabric.loader - modImplementation libs.bundles.fabric.api modApi libs.modmenu modApi libs.cloth.config } +loom { + accessWidenerPath = file("src/main/resources/dynamic_fps.accesswidener") +} + processResources { inputs.property "version", generateVersion() @@ -42,7 +46,7 @@ java { } tasks.withType(JavaCompile).configureEach { - it.options.release = 17 + it.options.release.set(17) it.options.encoding = "UTF-8" it.options.compilerArgs += ["-Xlint:deprecation"] diff --git a/gradle.properties b/gradle.properties index c8d8a2d1..b066072f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,6 +3,6 @@ org.gradle.parallel = true org.gradle.jvmargs = -Xmx1G # Mod Properties -mod_version = 3.2.1 +mod_version = 3.3.0 maven_group = juliand665 archives_base_name = dynamic-fps diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a7fbb25c..539e9bf3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,6 @@ fabric_api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fab cloth_config = { module = "me.shedaniel.cloth:cloth-config-fabric", version.ref = "cloth_config" } [bundles] -fabric_api = ["fabric_api"] [plugins] -fabric_loom = { id = "fabric-loom", version = "1.3.8" } +fabric_loom = { id = "fabric-loom", version = "1.4.5" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 033e24c4..7f93135c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34e..1af9e093 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index fcb6fca1..0adc8e1a 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/settings.gradle b/settings.gradle index 1f5e4657..a7add4a7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,10 +4,6 @@ pluginManagement { name = 'Fabric' url = 'https://maven.fabricmc.net/' } - maven { - name = 'Quilt' - url = 'https://maven.quiltmc.org/repository/release' - } gradlePluginPortal() } } diff --git a/src/main/java/dynamic_fps/impl/DynamicFPSMod.java b/src/main/java/dynamic_fps/impl/DynamicFPSMod.java index 8acbdaa0..8147c666 100644 --- a/src/main/java/dynamic_fps/impl/DynamicFPSMod.java +++ b/src/main/java/dynamic_fps/impl/DynamicFPSMod.java @@ -8,14 +8,15 @@ import dynamic_fps.impl.util.Logging; import dynamic_fps.impl.util.ModCompatibility; import dynamic_fps.impl.util.OptionsHolder; -import dynamic_fps.impl.util.WindowObserver; +import dynamic_fps.impl.util.event.InputObserver; +import dynamic_fps.impl.util.event.WindowObserver; import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.LoadingOverlay; -import net.minecraft.client.gui.screens.PauseScreen; import net.minecraft.sounds.SoundSource; import static dynamic_fps.impl.util.Localization.translationKey; @@ -33,10 +34,15 @@ public class DynamicFPSMod implements ClientModInitializer { private static boolean isForcingLowFPS = false; private static Minecraft minecraft; + private static WindowObserver window; + private static InputObserver devices; private static long lastRender; + private static boolean wasIdle = false; + private static boolean tickEventRegistered = false; + // we always render one last frame before actually reducing FPS, so the hud text // shows up instantly when forcing low fps. // additionally, this would enable mods which render differently while mc is @@ -63,11 +69,10 @@ public class DynamicFPSMod implements ClientModInitializer { @Override public void onInitializeClient() { - modConfig.save(); // Force create file on disk - toggleForcedKeyBinding.register(); toggleDisabledKeyBinding.register(); + registerTickEvent(); HudRenderCallback.EVENT.register(new HudInfoRenderer()); } @@ -81,6 +86,11 @@ public static void onStatusChanged() { checkForStateChanges(); } + public static void onConfigChanged() { + modConfig.save(); + registerTickEvent(); + } + public static PowerState powerState() { return state; } @@ -91,6 +101,7 @@ public static boolean isForcingLowFPS() { public static void setWindow(long address) { window = new WindowObserver(address); + devices = new InputObserver(address); } public static boolean checkForRender() { @@ -109,8 +120,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() { @@ -127,18 +138,45 @@ private static boolean isDisabledInternal() { return isDisabled || FREX.isFlawlessFramesActive(); } - private static boolean isPauseScreenOpened() { - return minecraft.screen instanceof PauseScreen; - } - private static boolean isLevelCoveredByScreen() { return minecraft.screen != null && minecraft.screen.dynamic_fps$rendersBackground(); } + private static boolean isIdle() { + var idleTime = modConfig.idleTime(); + + if (idleTime == 0) { + return false; + } + + return (Util.getEpochMillis() - devices.lastActionTime()) >= idleTime * 1000; + } + private static boolean isLevelCoveredByOverlay() { return OVERLAY_OPTIMIZATION_ACTIVE && minecraft.getOverlay() instanceof LoadingOverlay loadingOverlay && loadingOverlay.dynamic_fps$isReloadComplete(); } + private static void registerTickEvent() { + if (tickEventRegistered) { + return; + } + + if (modConfig.idleTime() == -1) { + return; + } + + tickEventRegistered = true; + + ClientTickEvents.START_CLIENT_TICK.register((minecraft) -> { + var idle = isIdle(); + + if (idle != wasIdle) { + wasIdle = idle; + onStatusChanged(); + } + }); + } + @SuppressWarnings("squid:S1215") // Garbage collector call public static void handleStateChange(PowerState previous, PowerState current) { if (DEBUG) { @@ -154,8 +192,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().soundEngine.dynamic_fps$updateVolume(source); + } } if (before.graphicsState() != config.graphicsState()) { @@ -176,6 +216,15 @@ private static void checkForStateChanges() { minecraft = Minecraft.getInstance(); } + if (minecraft.isSameThread()) { + checkForStateChanges0(); + } else { + // Schedule check for the beginning of the next frame + minecraft.tell(DynamicFPSMod::checkForStateChanges0); + } + } + + private static void checkForStateChanges0() { PowerState current; if (isDisabledInternal()) { @@ -183,10 +232,10 @@ private static void checkForStateChanges() { } else if (isForcingLowFPS) { current = PowerState.UNFOCUSED; } else if (window.isFocused()) { - if (!isPauseScreenOpened()) { + if (!isIdle()) { current = PowerState.FOCUSED; } else { - current = PowerState.SUSPENDED; + current = PowerState.ABANDONED; } } else if (window.isHovered()) { current = PowerState.HOVERED; @@ -204,29 +253,6 @@ private static void checkForStateChanges() { } } - 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 3f749711..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,22 +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().toLowerCase(Locale.ROOT)); - } - - @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))); - } - } - }; } diff --git a/src/main/java/dynamic_fps/impl/PowerState.java b/src/main/java/dynamic_fps/impl/PowerState.java index 00a6d530..fd1ad563 100644 --- a/src/main/java/dynamic_fps/impl/PowerState.java +++ b/src/main/java/dynamic_fps/impl/PowerState.java @@ -1,63 +1,38 @@ 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. * * Power states are prioritized based on their order here, see DynamicFPSMod.checkForStateChanges for impl details. */ public enum PowerState { - /* + /** * Window is currently focused. */ FOCUSED(false), - /* + /** * Mouse positioned over unfocused window. */ HOVERED(true), - /* + /** * Another application is focused. */ UNFOCUSED(true), - /* - * Window minimized or otherwise hidden. + /** + * User hasn't sent input for some time. */ - INVISIBLE(true), + ABANDONED(true), - /* - * User is currently on the pause screen. + /** + * Window minimized or otherwise hidden. */ - SUSPENDED(false); + INVISIBLE(true); 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 c83a53fc..8f54694d 100644 --- a/src/main/java/dynamic_fps/impl/compat/ClothConfig.java +++ b/src/main/java/dynamic_fps/impl/compat/ClothConfig.java @@ -1,11 +1,15 @@ 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; +import java.util.Optional; + import dynamic_fps.impl.DynamicFPSMod; import dynamic_fps.impl.GraphicsState; import dynamic_fps.impl.PowerState; @@ -13,12 +17,29 @@ public final class ClothConfig { public static Screen genConfigScreen(Screen parent) { - ConfigBuilder builder = ConfigBuilder.create() + var builder = ConfigBuilder.create() .setParentScreen(parent) .setTitle(localized("config", "title")) - .setSavingRunnable(DynamicFPSMod.modConfig::save); + .setSavingRunnable(DynamicFPSMod::onConfigChanged); + + var entryBuilder = builder.entryBuilder(); + + var general = builder.getOrCreateCategory( + localized("config", "category.general") + ); - ConfigEntryBuilder entryBuilder = builder.entryBuilder(); + general.addEntry( + entryBuilder.startIntSlider( + localized("config", "idle_time"), + DynamicFPSMod.modConfig.idleTime() / 60, + 0, 30 + ) + .setDefaultValue(0) + .setSaveConsumer(value -> DynamicFPSMod.modConfig.setIdleTime(value * 60)) + .setTextGetter(ClothConfig::idleTimeMessage) + .setTooltip(localized("config", "idle_time_tooltip")) + .build() + ); for (var state : PowerState.values()) { if (!state.configurable) { @@ -28,52 +49,131 @@ public static Screen genConfigScreen(Screen parent) { var config = DynamicFPSMod.modConfig.get(state); var standard = DynamicFPSConfig.getDefaultConfig(state); - builder.getOrCreateCategory( - localized("config", "category." + state.toString().toLowerCase())) - .addEntry( - entryBuilder - .startTextDescription( - localized("config", "frame_rate_target_description")).build()) - .addEntry(entryBuilder - .startIntSlider( - localized("config", "frame_rate_target"), - config.frameRateTarget(), - -1, 60) - .setDefaultValue(standard.frameRateTarget()) - .setSaveConsumer(config::setFrameRateTarget) - .build()) - .addEntry(entryBuilder - .startIntSlider( - localized("config", "volume_multiplier"), - (int) (config.volumeMultiplier() * 100), - 0, 100) - .setDefaultValue((int) (standard.volumeMultiplier() * 100)) - .setSaveConsumer(value -> config.setVolumeMultiplier(value / 100f)) - .build()) - .addEntry(entryBuilder - .startEnumSelector( - localized("config", "graphics_state"), - GraphicsState.class, - config.graphicsState()) - .setDefaultValue(standard.graphicsState()) - .setSaveConsumer(config::setGraphicsState) - .build()) - .addEntry(entryBuilder - .startBooleanToggle( - localized("config", "show_toasts"), - config.showToasts()) - .setDefaultValue(standard.showToasts()) - .setSaveConsumer(config::setShowToasts) - .build()) - .addEntry(entryBuilder - .startBooleanToggle( - localized("config", "run_garbage_collector"), - config.runGarbageCollector()) - .setDefaultValue(standard.runGarbageCollector()) - .setSaveConsumer(config::setRunGarbageCollector) - .build()); + var category = builder.getOrCreateCategory( + localized("config", "category." + state.toString().toLowerCase()) + ); + + category.addEntry( + entryBuilder.startIntSlider( + localized("config", "frame_rate_target"), + fromConfigFpsTarget(config.frameRateTarget()), + 0, 61 + ) + .setDefaultValue(fromConfigFpsTarget(standard.frameRateTarget())) + .setSaveConsumer(value -> config.setFrameRateTarget(toConfigFpsTarget(value))) + .setTextGetter(ClothConfig::fpsTargetMessage) + .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( + localized("config", "graphics_state"), + GraphicsState.class, + config.graphicsState() + ) + .setDefaultValue(standard.graphicsState()) + .setSaveConsumer(config::setGraphicsState) + .setEnumNameProvider(ClothConfig::graphicsStateMessage) + .setTooltipSupplier(ClothConfig::graphicsStateTooltip) + .build() + ); + + category.addEntry( + entryBuilder.startBooleanToggle( + localized("config", "show_toasts"), + config.showToasts() + ) + .setDefaultValue(standard.showToasts()) + .setSaveConsumer(config::setShowToasts) + .setTooltip(localized("config", "show_toasts_tooltip")) + .build() + ); + + category.addEntry( + entryBuilder.startBooleanToggle( + localized("config", "run_garbage_collector"), + config.runGarbageCollector() + ) + .setDefaultValue(standard.runGarbageCollector()) + .setSaveConsumer(config::setRunGarbageCollector) + .setTooltip(localized("config", "run_garbage_collector_tooltip")) + .build() + ); } return builder.build(); } + + private static Component idleTimeMessage(int value) { + if (value == 0) { + return localized("config", "disabled"); + } else { + return localized("config", "minutes", value); + } + } + + // Convert magic -1 number to 61 (and reverse) + // So the "unlocked" FPS value is on the right + private static int toConfigFpsTarget(int value) { + return value == 61 ? -1 : value; + } + + private static int fromConfigFpsTarget(int value) { + return value == -1 ? 61 : value; + } + + private static Component fpsTargetMessage(int value) { + if (toConfigFpsTarget(value) != -1) { + return Component.translatable("options.framerate", value); + } else { + return Component.translatable("options.framerateLimit.max"); + } + } + + private static Component volumeMultiplierMessage(int value) { + return Component.literal(Integer.toString(value) + "%"); + } + + private static Component graphicsStateMessage(Enum graphicsState) { + String key; + + if (graphicsState.equals(GraphicsState.DEFAULT)) { + key = "options.gamma.default"; + } else if (graphicsState.equals(GraphicsState.MINIMAL)) { + key = "options.particles.minimal"; + } else { + key = "options.particles.decreased"; + } + + return Component.translatable(key); + // return localized("config", "graphics_state_" + graphicsState.toString()); + } + + private static Optional graphicsStateTooltip(GraphicsState graphicsState) { + if (!graphicsState.equals(GraphicsState.MINIMAL)) { + return Optional.empty(); + } + + return Optional.of(new Component[]{ localized("config", "graphics_state_minimal_tooltip").withStyle(ChatFormatting.RED) }); + } } diff --git a/src/main/java/dynamic_fps/impl/config/Config.java b/src/main/java/dynamic_fps/impl/config/Config.java index f6186d06..530328df 100644 --- a/src/main/java/dynamic_fps/impl/config/Config.java +++ b/src/main/java/dynamic_fps/impl/config/Config.java @@ -1,31 +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 SUSPENDED = new Config(60, 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; @@ -39,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 b030b860..421440a9 100644 --- a/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java +++ b/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java @@ -5,10 +5,14 @@ import java.nio.file.Files; import java.nio.file.NoSuchFileException; 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; @@ -16,19 +20,26 @@ 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 int idleTime; // Seconds private Map configs; - private static final Path PATH = FabricLoader.getInstance().getConfigDir().resolve(DynamicFPSMod.MOD_ID + ".json"); - private static final Codec> STATES_CODEC = Codec.unboundedMap(PowerState.CODEC, Config.CODEC); + 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(new EnumCodec<>(PowerState.values()), Config.CODEC); private static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.intRange(0, 30 * 60).fieldOf("idle_time").forGetter(DynamicFPSConfig::idleTime), STATES_CODEC.fieldOf("states").forGetter(DynamicFPSConfig::configs) ).apply(instance, DynamicFPSConfig::new)); - private DynamicFPSConfig(Map configs) { + private DynamicFPSConfig(int abandonTime, Map configs) { + this.idleTime = abandonTime; this.configs = new EnumMap<>(configs); for (var state : PowerState.values()) { @@ -39,19 +50,21 @@ private DynamicFPSConfig(Map configs) { } public Config get(PowerState state) { - switch (state) { - case FOCUSED: { - return Config.ACTIVE; - } - case SUSPENDED: { - return Config.SUSPENDED; - } - default: { - return configs.get(state); - } + if (state == PowerState.FOCUSED) { + return Config.ACTIVE; + } else { + return configs.get(state); } } + public int idleTime() { + return this.idleTime; + } + + public void setIdleTime(int value) { + this.idleTime = value; + } + private Map configs() { return this.configs; } @@ -60,14 +73,18 @@ public static DynamicFPSConfig load() { String data; try { - data = Files.readString(PATH); + data = Files.readString(CONFIG_FILE); } catch (NoSuchFileException e) { - return new DynamicFPSConfig(new EnumMap<>(PowerState.class)); + var config = new DynamicFPSConfig(0, new EnumMap<>(PowerState.class)); + config.save(); + return config; } catch (IOException e) { throw new RuntimeException("Failed to load Dynamic FPS config.", e); } var root = JsonParser.parseString(data); + + upgradeConfig((JsonObject) root); var parsed = CODEC.parse(JsonOps.INSTANCE, root); return parsed.getOrThrow(false, RuntimeException::new); @@ -78,27 +95,90 @@ public void save() { var root = data.getOrThrow(false, RuntimeException::new); try { - Files.writeString(PATH, root.toString(), StandardCharsets.UTF_8); + var temp = Files.createTempFile(CONFIGS, "dynamic_fps", ".json"); + Files.writeString(temp, root.toString(), StandardCharsets.UTF_8); + + Files.deleteIfExists(CONFIG_FILE); + Files.move(temp, CONFIG_FILE, StandardCopyOption.ATOMIC_MOVE); } catch (IOException e) { - // Cloth Config's automatic saving does not support catching exceptions - throw new RuntimeException("Failed to save Dynamic FPS config.", e); + // Cloth Config's built-in saving does not support catching exceptions :( + throw new RuntimeException("Failed to save or modify Dynamic FPS config!", e); } } 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 ABANDONED: { + return new Config(10, withMasterVolume(1.0f), 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) { + upgradeIdleTime(root); + upgradeVolumeMultiplier(root); + } + + private static void upgradeIdleTime(JsonObject root) { + // Add idle_time field if it's missing + if (!root.has("idle_time")) { + root.addProperty("idle_time", 0); + } + } + + 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/MinecraftMixin.java b/src/main/java/dynamic_fps/impl/mixin/MinecraftMixin.java index 7f40e2db..0b5ab38b 100644 --- a/src/main/java/dynamic_fps/impl/mixin/MinecraftMixin.java +++ b/src/main/java/dynamic_fps/impl/mixin/MinecraftMixin.java @@ -1,15 +1,28 @@ package dynamic_fps.impl.mixin; +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 com.mojang.blaze3d.platform.Window; + import dynamic_fps.impl.DynamicFPSMod; import net.minecraft.client.Minecraft; @Mixin(Minecraft.class) public class MinecraftMixin { + @Shadow + @Final + private Window window; + + @Inject(method = "", at = @At("TAIL")) + private void onInit(CallbackInfo callbackInfo) { + DynamicFPSMod.setWindow(this.window.window); + } + @Inject(method = "setScreen", at = @At("TAIL")) private void onSetScreen(CallbackInfo callbackInfo) { DynamicFPSMod.onStatusChanged(); 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..6eb53dd7 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.DuckSoundEngine; +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 DuckSoundEngine { + @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/WindowMixin.java b/src/main/java/dynamic_fps/impl/mixin/WindowMixin.java index 1550fcf4..3663a496 100644 --- a/src/main/java/dynamic_fps/impl/mixin/WindowMixin.java +++ b/src/main/java/dynamic_fps/impl/mixin/WindowMixin.java @@ -1,11 +1,8 @@ package dynamic_fps.impl.mixin; -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.platform.Window; @@ -14,15 +11,6 @@ @Mixin(Window.class) public class WindowMixin { - @Shadow - @Final - private long window; - - @Inject(method = "", at = @At("TAIL")) - private void postinit(CallbackInfo callbackInfo) { - DynamicFPSMod.setWindow(this.window); - } - /** * Sets a frame rate limit while we're cancelling some or all rendering. */ 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/Localization.java b/src/main/java/dynamic_fps/impl/util/Localization.java index 7d47bb1d..93fc0d1b 100644 --- a/src/main/java/dynamic_fps/impl/util/Localization.java +++ b/src/main/java/dynamic_fps/impl/util/Localization.java @@ -2,6 +2,7 @@ import dynamic_fps.impl.DynamicFPSMod; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; public final class Localization { /** e.g. keyString("title", "config") -> "title.dynamic_fps.config") */ @@ -9,7 +10,7 @@ public static String translationKey(String domain, String path) { return domain + "." + DynamicFPSMod.MOD_ID + "." + path; } - public static Component localized(String domain, String path, Object... args) { + public static MutableComponent localized(String domain, String path, Object... args) { return Component.translatable(translationKey(domain, path), args); } 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/DuckSoundEngine.java b/src/main/java/dynamic_fps/impl/util/duck/DuckSoundEngine.java new file mode 100644 index 00000000..3b305d41 --- /dev/null +++ b/src/main/java/dynamic_fps/impl/util/duck/DuckSoundEngine.java @@ -0,0 +1,9 @@ +package dynamic_fps.impl.util.duck; + +import net.minecraft.sounds.SoundSource; + +public interface DuckSoundEngine { + 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/java/dynamic_fps/impl/util/event/InputObserver.java b/src/main/java/dynamic_fps/impl/util/event/InputObserver.java new file mode 100644 index 00000000..3038c0b5 --- /dev/null +++ b/src/main/java/dynamic_fps/impl/util/event/InputObserver.java @@ -0,0 +1,99 @@ +package dynamic_fps.impl.util.event; + +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWCharModsCallback; +import org.lwjgl.glfw.GLFWCursorPosCallback; +import org.lwjgl.glfw.GLFWDropCallback; +import org.lwjgl.glfw.GLFWKeyCallback; +import org.lwjgl.glfw.GLFWMouseButtonCallback; +import org.lwjgl.glfw.GLFWScrollCallback; + +import net.minecraft.Util; + +public class InputObserver { + private final long window; + + private long lastAction = Util.getEpochMillis(); + + // Keyboard + private final GLFWKeyCallback previousKeyCallback; + private final GLFWCharModsCallback previousCharModsCallback; + + // Mouse / Trackpad etc. + private final GLFWDropCallback previousDropCallback; + private final GLFWScrollCallback previousScrollCallback; + private final GLFWCursorPosCallback previousCursorPosCallback; + private final GLFWMouseButtonCallback previousMouseClickCallback; + + public InputObserver(long address) { + this.window = address; + + this.previousKeyCallback = GLFW.glfwSetKeyCallback(this.window, this::onKey); + this.previousCharModsCallback = GLFW.glfwSetCharModsCallback(this.window, this::onCharMods); + + this.previousDropCallback = GLFW.glfwSetDropCallback(this.window, this::onDrop); + this.previousScrollCallback = GLFW.glfwSetScrollCallback(this.window, this::onScroll); + this.previousCursorPosCallback = GLFW.glfwSetCursorPosCallback(this.window, this::onMove); + this.previousMouseClickCallback = GLFW.glfwSetMouseButtonCallback(this.window, this::onPress); + } + + public long lastActionTime() { + return this.lastAction; + } + + private void updateTime() { + this.lastAction = Util.getEpochMillis(); + } + + // Keyboard events + + private void onKey(long address, int key, int scancode, int action, int mods) { + this.updateTime(); + + if (this.previousKeyCallback != null) { + this.previousKeyCallback.invoke(address, key, scancode, action, mods); + } + } + + private void onCharMods(long address, int codepoint, int mods) { + this.updateTime(); + + if (this.previousCharModsCallback != null) { + this.previousCharModsCallback.invoke(address, codepoint, mods); + } + } + + // Mouse events + + private void onDrop(long address, int count, long names) { + this.updateTime(); + + if (this.previousDropCallback != null) { + this.previousDropCallback.invoke(address, count, names); + } + } + + private void onScroll(long address, double xoffset, double yoffset) { + this.updateTime(); + + if (this.previousScrollCallback != null) { + this.previousScrollCallback.invoke(address, xoffset, yoffset); + } + } + + private void onMove(long address, double x, double y) { + this.updateTime(); + + if (this.previousCursorPosCallback != null) { + this.previousCursorPosCallback.invoke(address, x, y); + } + } + + private void onPress(long address, int button, int action, int mods) { + this.updateTime(); + + if (this.previousMouseClickCallback != null) { + this.previousMouseClickCallback.invoke(address, button, action, mods); + } + } +} diff --git a/src/main/java/dynamic_fps/impl/util/WindowObserver.java b/src/main/java/dynamic_fps/impl/util/event/WindowObserver.java similarity index 70% rename from src/main/java/dynamic_fps/impl/util/WindowObserver.java rename to src/main/java/dynamic_fps/impl/util/event/WindowObserver.java index a0885ee9..6257bcaa 100644 --- a/src/main/java/dynamic_fps/impl/util/WindowObserver.java +++ b/src/main/java/dynamic_fps/impl/util/event/WindowObserver.java @@ -1,4 +1,4 @@ -package dynamic_fps.impl.util; +package dynamic_fps.impl.util.event; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWCursorEnterCallback; @@ -22,11 +22,11 @@ public class WindowObserver { public WindowObserver(long address) { this.window = address; - previousFocusCallback = GLFW.glfwSetWindowFocusCallback(this.window, this::onFocusChanged); - previousMouseCallback = GLFW.glfwSetCursorEnterCallback(this.window, this::onMouseChanged); + this.previousFocusCallback = GLFW.glfwSetWindowFocusCallback(this.window, this::onFocusChanged); + this.previousMouseCallback = GLFW.glfwSetCursorEnterCallback(this.window, this::onMouseChanged); // Vanilla doesn't use this (currently), other mods might register this callback though ... - previousIconifyCallback = GLFW.glfwSetWindowIconifyCallback(this.window, this::onIconifyChanged); + this.previousIconifyCallback = GLFW.glfwSetWindowIconifyCallback(this.window, this::onIconifyChanged); } private boolean isCurrentWindow(long address) { @@ -43,8 +43,8 @@ private void onFocusChanged(long address, boolean focused) { DynamicFPSMod.onStatusChanged(); } - if (previousFocusCallback != null) { - previousFocusCallback.invoke(address, focused); + if (this.previousFocusCallback != null) { + this.previousFocusCallback.invoke(address, focused); } } @@ -58,8 +58,8 @@ private void onMouseChanged(long address, boolean hovered) { DynamicFPSMod.onStatusChanged(); } - if (previousMouseCallback != null) { - previousMouseCallback.invoke(address, hovered); + if (this.previousMouseCallback != null) { + this.previousMouseCallback.invoke(address, hovered); } } @@ -73,8 +73,8 @@ private void onIconifyChanged(long address, boolean iconified) { DynamicFPSMod.onStatusChanged(); } - if (previousIconifyCallback != null) { - previousIconifyCallback.invoke(address, iconified); + if (this.previousIconifyCallback != null) { + this.previousIconifyCallback.invoke(address, iconified); } } } diff --git a/src/main/resources/assets/dynamic_fps/lang/de_at.json b/src/main/resources/assets/dynamic_fps/lang/de_at.json index 669392ba..69e747a3 100644 --- a/src/main/resources/assets/dynamic_fps/lang/de_at.json +++ b/src/main/resources/assets/dynamic_fps/lang/de_at.json @@ -1,16 +1,26 @@ { "config.dynamic_fps.title": "Dynamic FPS Konfigurieren", + "config.dynamic_fps.category.general": "Allgemein", "config.dynamic_fps.category.hovered": "Hover", "config.dynamic_fps.category.unfocused": "Unfokussiert", + "config.dynamic_fps.category.abandoned": "Untätig", "config.dynamic_fps.category.invisible": "Unsichtbar", "config.dynamic_fps.frame_rate_target": "Zielbildrate", - "config.dynamic_fps.frame_rate_target_description": "Setze die Zielbildrate auf -1, um die Bildrate nicht zu reduzieren.", "config.dynamic_fps.volume_multiplier": "Lautstärke", + "config.dynamic_fps.graphics_state": "Grafikoptionen", + "config.dynamic_fps.graphics_state_default": "Standard", + "config.dynamic_fps.graphics_state_reduced": "Reduziert", + "config.dynamic_fps.graphics_state_minimal": "Minimal", + "config.dynamic_fps.graphics_state_minimal_tooltip": "Minimale Grafik bewirkt, dass die Welt neu lädt!", + "config.dynamic_fps.show_toasts": "Toasts Anzeigen", + "config.dynamic_fps.show_toasts_tooltip": "Toast Benachrichtigungen weiterhin anzeigen oder verzögern", + "config.dynamic_fps.run_garbage_collector": "GC auslösen", + "config.dynamic_fps.run_garbage_collector_tooltip": "Ungenutzen Arbeitsspeicher frei machen, wenn dieser Status aktiviert wird", "key.dynamic_fps.toggle_forced": "Unfokussierten Modus forcieren (Toggle)", "key.dynamic_fps.toggle_disabled": "Dynamic FPS deaktivieren (Toggle)", diff --git a/src/main/resources/assets/dynamic_fps/lang/de_ch.json b/src/main/resources/assets/dynamic_fps/lang/de_ch.json index 669392ba..69e747a3 100644 --- a/src/main/resources/assets/dynamic_fps/lang/de_ch.json +++ b/src/main/resources/assets/dynamic_fps/lang/de_ch.json @@ -1,16 +1,26 @@ { "config.dynamic_fps.title": "Dynamic FPS Konfigurieren", + "config.dynamic_fps.category.general": "Allgemein", "config.dynamic_fps.category.hovered": "Hover", "config.dynamic_fps.category.unfocused": "Unfokussiert", + "config.dynamic_fps.category.abandoned": "Untätig", "config.dynamic_fps.category.invisible": "Unsichtbar", "config.dynamic_fps.frame_rate_target": "Zielbildrate", - "config.dynamic_fps.frame_rate_target_description": "Setze die Zielbildrate auf -1, um die Bildrate nicht zu reduzieren.", "config.dynamic_fps.volume_multiplier": "Lautstärke", + "config.dynamic_fps.graphics_state": "Grafikoptionen", + "config.dynamic_fps.graphics_state_default": "Standard", + "config.dynamic_fps.graphics_state_reduced": "Reduziert", + "config.dynamic_fps.graphics_state_minimal": "Minimal", + "config.dynamic_fps.graphics_state_minimal_tooltip": "Minimale Grafik bewirkt, dass die Welt neu lädt!", + "config.dynamic_fps.show_toasts": "Toasts Anzeigen", + "config.dynamic_fps.show_toasts_tooltip": "Toast Benachrichtigungen weiterhin anzeigen oder verzögern", + "config.dynamic_fps.run_garbage_collector": "GC auslösen", + "config.dynamic_fps.run_garbage_collector_tooltip": "Ungenutzen Arbeitsspeicher frei machen, wenn dieser Status aktiviert wird", "key.dynamic_fps.toggle_forced": "Unfokussierten Modus forcieren (Toggle)", "key.dynamic_fps.toggle_disabled": "Dynamic FPS deaktivieren (Toggle)", diff --git a/src/main/resources/assets/dynamic_fps/lang/de_de.json b/src/main/resources/assets/dynamic_fps/lang/de_de.json index 669392ba..69e747a3 100644 --- a/src/main/resources/assets/dynamic_fps/lang/de_de.json +++ b/src/main/resources/assets/dynamic_fps/lang/de_de.json @@ -1,16 +1,26 @@ { "config.dynamic_fps.title": "Dynamic FPS Konfigurieren", + "config.dynamic_fps.category.general": "Allgemein", "config.dynamic_fps.category.hovered": "Hover", "config.dynamic_fps.category.unfocused": "Unfokussiert", + "config.dynamic_fps.category.abandoned": "Untätig", "config.dynamic_fps.category.invisible": "Unsichtbar", "config.dynamic_fps.frame_rate_target": "Zielbildrate", - "config.dynamic_fps.frame_rate_target_description": "Setze die Zielbildrate auf -1, um die Bildrate nicht zu reduzieren.", "config.dynamic_fps.volume_multiplier": "Lautstärke", + "config.dynamic_fps.graphics_state": "Grafikoptionen", + "config.dynamic_fps.graphics_state_default": "Standard", + "config.dynamic_fps.graphics_state_reduced": "Reduziert", + "config.dynamic_fps.graphics_state_minimal": "Minimal", + "config.dynamic_fps.graphics_state_minimal_tooltip": "Minimale Grafik bewirkt, dass die Welt neu lädt!", + "config.dynamic_fps.show_toasts": "Toasts Anzeigen", + "config.dynamic_fps.show_toasts_tooltip": "Toast Benachrichtigungen weiterhin anzeigen oder verzögern", + "config.dynamic_fps.run_garbage_collector": "GC auslösen", + "config.dynamic_fps.run_garbage_collector_tooltip": "Ungenutzen Arbeitsspeicher frei machen, wenn dieser Status aktiviert wird", "key.dynamic_fps.toggle_forced": "Unfokussierten Modus forcieren (Toggle)", "key.dynamic_fps.toggle_disabled": "Dynamic FPS deaktivieren (Toggle)", diff --git a/src/main/resources/assets/dynamic_fps/lang/en_us.json b/src/main/resources/assets/dynamic_fps/lang/en_us.json index 20a81af4..db9cc49c 100644 --- a/src/main/resources/assets/dynamic_fps/lang/en_us.json +++ b/src/main/resources/assets/dynamic_fps/lang/en_us.json @@ -1,16 +1,32 @@ { "config.dynamic_fps.title": "Configure Dynamic FPS", + "config.dynamic_fps.category.general": "General", "config.dynamic_fps.category.hovered": "Hovered", "config.dynamic_fps.category.unfocused": "Unfocused", + "config.dynamic_fps.category.abandoned": "Idle", "config.dynamic_fps.category.invisible": "Invisible", + "config.dynamic_fps.disabled": "Disabled", + "config.dynamic_fps.minutes": "%d Minute(s)", + + "config.dynamic_fps.idle_time": "Idle time", + "config.dynamic_fps.idle_time_tooltip": "Minutes without input until Dynamic FPS activates the Idle state while the game is focused.", + "config.dynamic_fps.frame_rate_target": "Frame Rate Target", - "config.dynamic_fps.frame_rate_target_description": "Set Frame Rate Target to -1 to disable reducing FPS.", "config.dynamic_fps.volume_multiplier": "Volume Multiplier", + "config.dynamic_fps.graphics_state": "Graphics Options", + "config.dynamic_fps.graphics_state_default": "Default", + "config.dynamic_fps.graphics_state_reduced": "Reduced", + "config.dynamic_fps.graphics_state_minimal": "Minimal", + "config.dynamic_fps.graphics_state_minimal_tooltip": "Minimal graphics cause the world to reload!", + "config.dynamic_fps.show_toasts": "Show Toasts", + "config.dynamic_fps.show_toasts_tooltip": "Whether to keep displaying or delay toast notifications", + "config.dynamic_fps.run_garbage_collector": "Invoke Garbage Collector", + "config.dynamic_fps.run_garbage_collector_tooltip": "Free up unused memory when switching to this status", "key.dynamic_fps.toggle_forced": "Force Unfocused Mode (Toggle)", "key.dynamic_fps.toggle_disabled": "Disable Dynamic FPS (Toggle)", diff --git a/src/main/resources/assets/dynamic_fps/lang/et_ee.json b/src/main/resources/assets/dynamic_fps/lang/et_ee.json index 8cbaa4b5..3b8eb698 100644 --- a/src/main/resources/assets/dynamic_fps/lang/et_ee.json +++ b/src/main/resources/assets/dynamic_fps/lang/et_ee.json @@ -1,6 +1,8 @@ { "config.dynamic_fps.title": "Dynamic FPS seadistus", + "config.dynamic_fps.category.general": "Üldine", + "key.dynamic_fps.toggle_forced": "Sunni vähendatud ks (lüliti)", "key.dynamic_fps.toggle_disabled": "Keela Dynamic FPS (lüliti)", "gui.dynamic_fps.hud.reducing": "Dynamic FPS: sunnin vähendatud kaadrisagedust", diff --git a/src/main/resources/assets/dynamic_fps/lang/fr_ca.json b/src/main/resources/assets/dynamic_fps/lang/fr_ca.json index e38ac54a..8b41381f 100644 --- a/src/main/resources/assets/dynamic_fps/lang/fr_ca.json +++ b/src/main/resources/assets/dynamic_fps/lang/fr_ca.json @@ -1,6 +1,7 @@ { "config.dynamic_fps.title": "Configuration de Dynamic FPS", + "config.dynamic_fps.category.general": "Général", "config.dynamic_fps.category.hovered": "Survolé", "config.dynamic_fps.category.unfocused": "Non-focalisé", "config.dynamic_fps.category.invisible": "Invisible", diff --git a/src/main/resources/assets/dynamic_fps/lang/fr_fr.json b/src/main/resources/assets/dynamic_fps/lang/fr_fr.json index e38ac54a..8b41381f 100644 --- a/src/main/resources/assets/dynamic_fps/lang/fr_fr.json +++ b/src/main/resources/assets/dynamic_fps/lang/fr_fr.json @@ -1,6 +1,7 @@ { "config.dynamic_fps.title": "Configuration de Dynamic FPS", + "config.dynamic_fps.category.general": "Général", "config.dynamic_fps.category.hovered": "Survolé", "config.dynamic_fps.category.unfocused": "Non-focalisé", "config.dynamic_fps.category.invisible": "Invisible", diff --git a/src/main/resources/assets/dynamic_fps/lang/it_it.json b/src/main/resources/assets/dynamic_fps/lang/it_it.json index 49910133..9258e51a 100644 --- a/src/main/resources/assets/dynamic_fps/lang/it_it.json +++ b/src/main/resources/assets/dynamic_fps/lang/it_it.json @@ -1,6 +1,7 @@ { "config.dynamic_fps.title": "Configura Dynamic FPS", + "config.dynamic_fps.category.general": "Generale", "config.dynamic_fps.category.hovered": "Sopra il cursore", "config.dynamic_fps.category.unfocused": "Non in primo piano", "config.dynamic_fps.category.invisible": "Invisibile", @@ -18,4 +19,4 @@ "gui.dynamic_fps.hud.disabled": "Dynamic FPS disabilitato", "modmenu.descriptionTranslation.dynamic_fps": "Regola dinamicamente il FPS in modo che Minecraft non consumi risorse in background." -} \ No newline at end of file +} diff --git a/src/main/resources/assets/dynamic_fps/lang/ko_kr.json b/src/main/resources/assets/dynamic_fps/lang/ko_kr.json index a10cc222..1ecf817e 100644 --- a/src/main/resources/assets/dynamic_fps/lang/ko_kr.json +++ b/src/main/resources/assets/dynamic_fps/lang/ko_kr.json @@ -1,6 +1,8 @@ { "config.dynamic_fps.title": "Dynamic FPS 구성", + "config.dynamic_fps.category.general": "일반", + "key.dynamic_fps.toggle_forced": "강제로 FPS 감소 (토글)", "key.dynamic_fps.toggle_disabled": "Dynamic FPS 비활성화 (Toggle)", "gui.dynamic_fps.hud.reducing": "Dynamic FPS: 강제로 FPS 감소중", diff --git a/src/main/resources/assets/dynamic_fps/lang/pl_pl.json b/src/main/resources/assets/dynamic_fps/lang/pl_pl.json index 272a6d77..87c9cf88 100644 --- a/src/main/resources/assets/dynamic_fps/lang/pl_pl.json +++ b/src/main/resources/assets/dynamic_fps/lang/pl_pl.json @@ -1,6 +1,8 @@ { "config.dynamic_fps.title": "Skonfiguruj Dynamic FPS", + "config.dynamic_fps.category.general": "Ogólne", + "key.dynamic_fps.toggle_forced": "Wymuś obniżenie FPS (przełącznik)", "key.dynamic_fps.toggle_disabled": "Wyłącz Dynamic FPS (przełącznik)", "gui.dynamic_fps.hud.reducing": "Dynamic FPS: wymuszono obniżenie FPS", diff --git a/src/main/resources/assets/dynamic_fps/lang/pt_br.json b/src/main/resources/assets/dynamic_fps/lang/pt_br.json index 21475c90..ff7ce802 100644 --- a/src/main/resources/assets/dynamic_fps/lang/pt_br.json +++ b/src/main/resources/assets/dynamic_fps/lang/pt_br.json @@ -1,6 +1,8 @@ { "config.dynamic_fps.title": "Definições do Dynamic FPS", + "config.dynamic_fps.category.general": "Geral", + "key.dynamic_fps.toggle_forced": "Forçar redução da taxa de quadros (alternável)", "key.dynamic_fps.toggle_disabled": "Desativar o Disable Dynamic (alternável)", "gui.dynamic_fps.hud.reducing": "Dynamic FPS: forçando a redução da taxa de quadros", diff --git a/src/main/resources/assets/dynamic_fps/lang/pt_pt.json b/src/main/resources/assets/dynamic_fps/lang/pt_pt.json index 21475c90..ff7ce802 100644 --- a/src/main/resources/assets/dynamic_fps/lang/pt_pt.json +++ b/src/main/resources/assets/dynamic_fps/lang/pt_pt.json @@ -1,6 +1,8 @@ { "config.dynamic_fps.title": "Definições do Dynamic FPS", + "config.dynamic_fps.category.general": "Geral", + "key.dynamic_fps.toggle_forced": "Forçar redução da taxa de quadros (alternável)", "key.dynamic_fps.toggle_disabled": "Desativar o Disable Dynamic (alternável)", "gui.dynamic_fps.hud.reducing": "Dynamic FPS: forçando a redução da taxa de quadros", diff --git a/src/main/resources/assets/dynamic_fps/lang/ru_ru.json b/src/main/resources/assets/dynamic_fps/lang/ru_ru.json index ae9046cb..84dc6c83 100644 --- a/src/main/resources/assets/dynamic_fps/lang/ru_ru.json +++ b/src/main/resources/assets/dynamic_fps/lang/ru_ru.json @@ -1,6 +1,7 @@ { "config.dynamic_fps.title": "Настройки Dynamic FPS", + "config.dynamic_fps.category.general": "Основные", "config.dynamic_fps.category.hovered": "Наведён", "config.dynamic_fps.category.unfocused": "Расфокусирован", "config.dynamic_fps.category.invisible": "Свёрнут", diff --git a/src/main/resources/assets/dynamic_fps/lang/sv_se.json b/src/main/resources/assets/dynamic_fps/lang/sv_se.json index 9ccc4c14..ad5fb933 100644 --- a/src/main/resources/assets/dynamic_fps/lang/sv_se.json +++ b/src/main/resources/assets/dynamic_fps/lang/sv_se.json @@ -1,17 +1,33 @@ { "config.dynamic_fps.title": "Konfigurera Dynamic FPS", + "config.dynamic_fps.category.general": "Allmänt", "config.dynamic_fps.category.hovered": "Hovrande", "config.dynamic_fps.category.unfocused": "Ofokuserad", - "config.dynamic_fps.category.invisible": "Osynlig", + "config.dynamic_fps.category.abandoned": "Inaktiv", + "config.dynamic_fps.category.invisible": "Osynlig", + + "config.dynamic_fps.disabled": "Inaktiverad", + "config.dynamic_fps.minutes": "%d Minut(er)", + + "config.dynamic_fps.idle_time": "Inaktivitetstid", + "config.dynamic_fps.idle_time_tooltip": "Minuter utan inmatning tills Dynamic FPS aktiverar Inaktivetesläget medans spelet är fokuserat.", "config.dynamic_fps.frame_rate_target": "Bildfrekvensmål", - "config.dynamic_fps.frame_rate_target_description": "Ställ in bildfrekvensmålet till -1 för att stänga av bildfrekvens reducering.", "config.dynamic_fps.volume_multiplier": "Volym Multiplikator", + "config.dynamic_fps.graphics_state": "Bildskärmsinställningar", + "config.dynamic_fps.graphics_state_default": "Standard", + "config.dynamic_fps.graphics_state_reduced": "Reducerad", + "config.dynamic_fps.graphics_state_minimal": "Minimalt", + "config.dynamic_fps.graphics_state_minimal_tooltip": "Minimal grafik orsakar att världen laddar om!", + "config.dynamic_fps.show_toasts": "Visa Toasts", + "config.dynamic_fps.show_toasts_tooltip": "Om du vill fortsätta visa eller fördröja Toast aviseringar", + "config.dynamic_fps.run_garbage_collector": "Starta GC", - + "config.dynamic_fps.run_garbage_collector_tooltip": "Frigör oanvänt minne när du växlar till denna status", + "key.dynamic_fps.toggle_forced": "Tvinga Reducerad FPS (Växla)", "key.dynamic_fps.toggle_disabled": "Inaktivera Dynamic FPS (Växla)", "gui.dynamic_fps.hud.reducing": "Dynamic FPS: Tvingar Reducerad BPS", diff --git a/src/main/resources/assets/dynamic_fps/lang/tr_tr.json b/src/main/resources/assets/dynamic_fps/lang/tr_tr.json index e80f5f30..53bc3857 100644 --- a/src/main/resources/assets/dynamic_fps/lang/tr_tr.json +++ b/src/main/resources/assets/dynamic_fps/lang/tr_tr.json @@ -1,8 +1,37 @@ { "config.dynamic_fps.title": "Dinamik FPS Ayarları", + "config.dynamic_fps.category.general": "Genel", + "config.dynamic_fps.category.hovered": "Üzerinde Durulan", + "config.dynamic_fps.category.unfocused": "Odaklanmamış", + "config.dynamic_fps.category.abandoned": "Boşta", + "config.dynamic_fps.category.invisible": "Görünmez", + + "config.dynamic_fps.disabled": "Devre Dışı", + "config.dynamic_fps.minutes": "%d Dakika", + + "config.dynamic_fps.idle_time": "Boşta Geçen Süre", + "config.dynamic_fps.idle_time_tooltip": "Oyun odaklıyken giriş olmadan geçen dakikalar boyunca Dinamik FPS'nin Boşta durumunu etkinleştirmesi.", + + "config.dynamic_fps.frame_rate_target": "Kare Hızı Hedefi", + "config.dynamic_fps.volume_multiplier": "Ses Çarpanı", + + "config.dynamic_fps.graphics_state": "Grafik Seçenekleri", + "config.dynamic_fps.graphics_state_default": "Varsayılan", + "config.dynamic_fps.graphics_state_reduced": "Azaltılmış", + "config.dynamic_fps.graphics_state_minimal": "Minimal", + "config.dynamic_fps.graphics_state_minimal_tooltip": "Minimal grafikler dünyanın yeniden yüklenmesine neden olur!!", + + "config.dynamic_fps.show_toasts": "Bilgi Pencerelerini Göster", + "config.dynamic_fps.show_toasts_tooltip": "Bilgi pencerelerini göstermeye devam edip etmemeye veya ertelemeye karar verir", + + "config.dynamic_fps.run_garbage_collector": "Garbage Collector Çalıştır", + "config.dynamic_fps.run_garbage_collector_tooltip": "Bu duruma geçişte kullanılmayan belleği serbest bırak", + "key.dynamic_fps.toggle_forced": "Zorla Azaltılmış FPS (Aç / kapat)", "key.dynamic_fps.toggle_disabled": "Dinamik FPS'yi Devre Dışı Bırak (Aç / kapat)", "gui.dynamic_fps.hud.reducing": "Dinamik FPS: Azaltılmış FPS Zorlanıyor", - "gui.dynamic_fps.hud.disabled": "Dinamik FPS Devre Dışı Bırakıldı" + "gui.dynamic_fps.hud.disabled": "Dinamik FPS Devre Dışı Bırakıldı", + + "modmenu.descriptionTranslation.dynamic_fps": "Minecraft'in arka planda kaynakları tüketmemesi için FPS'yi dinamik olarak ayarlar." } diff --git a/src/main/resources/assets/dynamic_fps/lang/uk_ua.json b/src/main/resources/assets/dynamic_fps/lang/uk_ua.json index 0d04bd75..0f86ec37 100644 --- a/src/main/resources/assets/dynamic_fps/lang/uk_ua.json +++ b/src/main/resources/assets/dynamic_fps/lang/uk_ua.json @@ -1,6 +1,8 @@ { "config.dynamic_fps.title": "Параметри Dynamic FPS", + "config.dynamic_fps.category.general": "Загальні", + "key.dynamic_fps.toggle_forced": "Примусово знижувати частоту кадрів (перемикання)", "key.dynamic_fps.toggle_disabled": "Вимкнути Dynamic FPS (перемикання)", "gui.dynamic_fps.hud.reducing": "Dynamic FPS: Увімкнено примусове зниження частоти кадрів", diff --git a/src/main/resources/assets/dynamic_fps/lang/vi_vn.json b/src/main/resources/assets/dynamic_fps/lang/vi_vn.json index 991dd378..52bd7bd1 100644 --- a/src/main/resources/assets/dynamic_fps/lang/vi_vn.json +++ b/src/main/resources/assets/dynamic_fps/lang/vi_vn.json @@ -1,6 +1,8 @@ { "config.dynamic_fps.title": "Định cấu hình Dynamic FPS", + "config.dynamic_fps.category.general": "Chung", + "key.dynamic_fps.toggle_forced": "Buộc giảm FPS (Đổi)", "key.dynamic_fps.toggle_disabled": "Vô hiệu hoá Dynamic FPS (Đổi)", "gui.dynamic_fps.hud.reducing": "Dynamic FPS: Buộc giảm FPS", diff --git a/src/main/resources/assets/dynamic_fps/lang/zh_cn.json b/src/main/resources/assets/dynamic_fps/lang/zh_cn.json index f54e3669..ff72aca7 100644 --- a/src/main/resources/assets/dynamic_fps/lang/zh_cn.json +++ b/src/main/resources/assets/dynamic_fps/lang/zh_cn.json @@ -1,6 +1,7 @@ { "config.dynamic_fps.title": "Dynamic FPS 动态帧率配置", + "config.dynamic_fps.category.general": "预设", "config.dynamic_fps.category.hovered": "悬停", "config.dynamic_fps.category.unfocused": "失焦", "config.dynamic_fps.category.invisible": "不可见", diff --git a/src/main/resources/assets/dynamic_fps/lang/zh_tw.json b/src/main/resources/assets/dynamic_fps/lang/zh_tw.json index 96938631..bf83dd9d 100644 --- a/src/main/resources/assets/dynamic_fps/lang/zh_tw.json +++ b/src/main/resources/assets/dynamic_fps/lang/zh_tw.json @@ -1,6 +1,7 @@ { "config.dynamic_fps.title": "設定 Dynamic FPS", + "config.dynamic_fps.category.general": "一般", "config.dynamic_fps.category.hovered": "聚焦時", "config.dynamic_fps.category.unfocused": "未聚焦時", "config.dynamic_fps.category.invisible": "不可見時", diff --git a/src/main/resources/dynamic_fps.accesswidener b/src/main/resources/dynamic_fps.accesswidener new file mode 100644 index 00000000..0298cddf --- /dev/null +++ b/src/main/resources/dynamic_fps.accesswidener @@ -0,0 +1,4 @@ +accessWidener v2 named + +accessible field com/mojang/blaze3d/platform/Window window J +accessible field net/minecraft/client/sounds/SoundManager soundEngine Lnet/minecraft/client/sounds/SoundEngine; diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 6eb5fbdd..95f35a25 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -31,6 +31,7 @@ "mixins": [ "dynamic_fps.mixins.json" ], + "accessWidener": "dynamic_fps.accesswidener", "depends": { "minecraft": "=1.19.4", @@ -43,8 +44,9 @@ "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/DuckSoundEngine"] }, "dynamic_fps": { "optimized_screens": {