├── .gitattributes ├── .gitignore ├── GitHub ├── logo.png ├── logo.afdesign ├── preview.png └── preview.afdesign ├── platforms ├── forge │ ├── gradle.properties │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── META-INF │ │ │ │ ├── services │ │ │ │ │ ├── dynamic_fps.impl.service.Platform │ │ │ │ │ └── dynamic_fps.impl.service.ModCompat │ │ │ │ └── mods.toml │ │ │ ├── pack.mcmeta │ │ │ └── dynamic_fps.mixins.json │ │ │ └── java │ │ │ └── net │ │ │ └── lostluma │ │ │ └── dynamic_fps │ │ │ └── impl │ │ │ └── forge │ │ │ ├── service │ │ │ ├── ForgeModCompat.java │ │ │ └── ForgePlatform.java │ │ │ ├── mixin │ │ │ └── GuiMixin.java │ │ │ └── DynamicFPSForgeMod.java │ └── build.gradle ├── quilt │ ├── gradle.properties │ ├── src │ │ └── main │ │ │ └── resources │ │ │ ├── dynamic_fps.mixins.json │ │ │ └── quilt.mod.json │ └── build.gradle ├── fabric │ ├── gradle.properties │ ├── src │ │ └── main │ │ │ └── resources │ │ │ ├── dynamic_fps.mixins.json │ │ │ └── fabric.mod.json │ └── build.gradle ├── neoforge │ ├── gradle.properties │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── pack.mcmeta │ │ │ ├── META-INF │ │ │ │ ├── services │ │ │ │ │ ├── dynamic_fps.impl.service.Platform │ │ │ │ │ └── dynamic_fps.impl.service.ModCompat │ │ │ │ └── neoforge.mods.toml │ │ │ ├── dynamic_fps.png │ │ │ └── dynamic_fps.mixins.json │ │ │ └── java │ │ │ └── net │ │ │ └── lostluma │ │ │ └── dynamic_fps │ │ │ └── impl │ │ │ └── neoforge │ │ │ ├── service │ │ │ ├── NeoForgeModCompat.java │ │ │ └── NeoForgePlatform.java │ │ │ └── DynamicFPSNeoForgeMod.java │ └── build.gradle ├── common │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── architectury.common.json │ │ │ ├── assets │ │ │ │ └── dynamic_fps │ │ │ │ │ ├── textures │ │ │ │ │ ├── battery │ │ │ │ │ │ ├── license.txt │ │ │ │ │ │ ├── toast │ │ │ │ │ │ │ ├── charging.png │ │ │ │ │ │ │ ├── draining.png │ │ │ │ │ │ │ ├── reminder.png │ │ │ │ │ │ │ ├── background.png │ │ │ │ │ │ │ └── background_icon.png │ │ │ │ │ │ └── icon │ │ │ │ │ │ │ ├── charging_0.png │ │ │ │ │ │ │ ├── charging_1.png │ │ │ │ │ │ │ ├── charging_10.png │ │ │ │ │ │ │ ├── charging_2.png │ │ │ │ │ │ │ ├── charging_3.png │ │ │ │ │ │ │ ├── charging_4.png │ │ │ │ │ │ │ ├── charging_5.png │ │ │ │ │ │ │ ├── charging_6.png │ │ │ │ │ │ │ ├── charging_7.png │ │ │ │ │ │ │ ├── charging_8.png │ │ │ │ │ │ │ ├── charging_9.png │ │ │ │ │ │ │ ├── draining_0.png │ │ │ │ │ │ │ ├── draining_1.png │ │ │ │ │ │ │ ├── draining_10.png │ │ │ │ │ │ │ ├── draining_2.png │ │ │ │ │ │ │ ├── draining_3.png │ │ │ │ │ │ │ ├── draining_4.png │ │ │ │ │ │ │ ├── draining_5.png │ │ │ │ │ │ │ ├── draining_6.png │ │ │ │ │ │ │ ├── draining_7.png │ │ │ │ │ │ │ ├── draining_8.png │ │ │ │ │ │ │ └── draining_9.png │ │ │ │ │ └── icon.png │ │ │ │ │ ├── lang │ │ │ │ │ ├── et_ee.json │ │ │ │ │ ├── pt_pt.json │ │ │ │ │ ├── zh_hk.json │ │ │ │ │ ├── es_ar.json │ │ │ │ │ ├── es_cl.json │ │ │ │ │ ├── es_ec.json │ │ │ │ │ ├── es_es.json │ │ │ │ │ ├── es_uy.json │ │ │ │ │ ├── es_ve.json │ │ │ │ │ ├── sv_se.json │ │ │ │ │ ├── pl_pl.json │ │ │ │ │ ├── en_ud.json │ │ │ │ │ ├── lol_us.json │ │ │ │ │ ├── es_mx.json │ │ │ │ │ ├── en_pt.json │ │ │ │ │ ├── enws.json │ │ │ │ │ ├── ko_kr.json │ │ │ │ │ ├── zh_cn.json │ │ │ │ │ ├── zh_tw.json │ │ │ │ │ ├── ru_ru.json │ │ │ │ │ └── it_it.json │ │ │ │ │ └── data │ │ │ │ │ └── default_config.json │ │ │ ├── dynamic_fps.accesswidener │ │ │ └── dynamic_fps-common.mixins.json │ │ │ └── java │ │ │ └── dynamic_fps │ │ │ └── impl │ │ │ ├── config │ │ │ ├── option │ │ │ │ ├── IdleCondition.java │ │ │ │ ├── IgnoreInitialClick.java │ │ │ │ ├── GraphicsState.java │ │ │ │ ├── BatteryIndicatorPlacement.java │ │ │ │ └── BatteryIndicatorCondition.java │ │ │ ├── IdleConfig.java │ │ │ ├── VolumeTransitionConfig.java │ │ │ ├── BatteryTrackerConfig.java │ │ │ ├── DynamicFPSConfig.java │ │ │ └── Config.java │ │ │ ├── service │ │ │ ├── ModCompat.java │ │ │ ├── Services.java │ │ │ └── Platform.java │ │ │ ├── util │ │ │ ├── duck │ │ │ │ ├── DuckLoadingOverlay.java │ │ │ │ └── DuckSoundEngine.java │ │ │ ├── ResourceLocations.java │ │ │ ├── Logging.java │ │ │ ├── BatteryUtil.java │ │ │ ├── Threads.java │ │ │ ├── Components.java │ │ │ ├── FallbackConfigScreen.java │ │ │ ├── KeyMappingHandler.java │ │ │ ├── JsonUtil.java │ │ │ ├── VariableStepTransformer.java │ │ │ ├── HudInfoRenderer.java │ │ │ └── Version.java │ │ │ ├── mixin │ │ │ ├── LoadingOverlayMixin.java │ │ │ ├── bugfix │ │ │ │ └── BlockableEventLoopMixin.java │ │ │ ├── ToastManagerMixin.java │ │ │ ├── GuiMixin.java │ │ │ ├── GameRendererMixin.java │ │ │ ├── DebugEntryFpsMixin.java │ │ │ ├── MinecraftMixin.java │ │ │ ├── OptionsMixin.java │ │ │ ├── FramerateLimitTrackerMixin.java │ │ │ └── SoundEngineMixin.java │ │ │ ├── feature │ │ │ ├── battery │ │ │ │ ├── ErrorToast.java │ │ │ │ ├── BatteryToast.java │ │ │ │ ├── BaseToast.java │ │ │ │ └── BatteryTracker.java │ │ │ ├── state │ │ │ │ ├── ClickIgnoreHandler.java │ │ │ │ ├── OptionHolder.java │ │ │ │ ├── WindowObserver.java │ │ │ │ └── IdleHandler.java │ │ │ └── volume │ │ │ │ └── SmoothVolumeHandler.java │ │ │ ├── Constants.java │ │ │ ├── PowerState.java │ │ │ └── compat │ │ │ └── GLFW.java │ └── build.gradle.kts └── textile │ ├── src │ └── main │ │ ├── resources │ │ ├── META-INF │ │ │ └── services │ │ │ │ ├── dynamic_fps.impl.service.ModCompat │ │ │ │ └── dynamic_fps.impl.service.Platform │ │ └── dynamic_fps-textile.mixins.json │ │ └── java │ │ └── net │ │ └── lostluma │ │ └── dynamic_fps │ │ └── impl │ │ └── textile │ │ ├── compat │ │ ├── ModMenu.java │ │ └── FREX.java │ │ ├── mixin │ │ ├── GuiMixin.java │ │ └── OptionsMixin.java │ │ └── service │ │ ├── TextileModCompat.java │ │ └── TextilePlatform.java │ └── build.gradle ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── changelog.txt ├── .git-blame-ignore-revs ├── .editorconfig ├── gradle.properties ├── settings.gradle.kts ├── docs └── manual-natives-install.md ├── LICENSE ├── .github └── workflows │ └── build.yaml ├── README.md └── gradlew.bat /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .architectury-transformer/ 2 | .gradle/ 3 | .idea/ 4 | build/ 5 | run/ 6 | -------------------------------------------------------------------------------- /GitHub/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/GitHub/logo.png -------------------------------------------------------------------------------- /platforms/forge/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=forge 2 | supported_platforms=forge 3 | -------------------------------------------------------------------------------- /platforms/quilt/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=quilt 2 | supported_platforms=quilt 3 | -------------------------------------------------------------------------------- /platforms/fabric/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=fabric 2 | supported_platforms=fabric 3 | -------------------------------------------------------------------------------- /GitHub/logo.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/GitHub/logo.afdesign -------------------------------------------------------------------------------- /GitHub/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/GitHub/preview.png -------------------------------------------------------------------------------- /platforms/neoforge/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=neoforge 2 | supported_platforms=neoforge 3 | -------------------------------------------------------------------------------- /GitHub/preview.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/GitHub/preview.afdesign -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /platforms/common/src/main/resources/architectury.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "accessWidener": "dynamic_fps.accesswidener" 3 | } 4 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Lilly Rose Berner - All Rights Reserved 2 | -------------------------------------------------------------------------------- /platforms/forge/src/main/resources/META-INF/services/dynamic_fps.impl.service.Platform: -------------------------------------------------------------------------------- 1 | net.lostluma.dynamic_fps.impl.forge.service.ForgePlatform 2 | -------------------------------------------------------------------------------- /platforms/forge/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "Dynamic FPS", 4 | "pack_format": 15 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /platforms/forge/src/main/resources/META-INF/services/dynamic_fps.impl.service.ModCompat: -------------------------------------------------------------------------------- 1 | net.lostluma.dynamic_fps.impl.forge.service.ForgeModCompat 2 | -------------------------------------------------------------------------------- /platforms/neoforge/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "Dynamic FPS", 4 | "pack_format": 15 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /platforms/neoforge/src/main/resources/META-INF/services/dynamic_fps.impl.service.Platform: -------------------------------------------------------------------------------- 1 | net.lostluma.dynamic_fps.impl.neoforge.service.NeoForgePlatform 2 | -------------------------------------------------------------------------------- /platforms/textile/src/main/resources/META-INF/services/dynamic_fps.impl.service.ModCompat: -------------------------------------------------------------------------------- 1 | net.lostluma.dynamic_fps.impl.textile.service.TextileModCompat 2 | -------------------------------------------------------------------------------- /platforms/textile/src/main/resources/META-INF/services/dynamic_fps.impl.service.Platform: -------------------------------------------------------------------------------- 1 | net.lostluma.dynamic_fps.impl.textile.service.TextilePlatform 2 | -------------------------------------------------------------------------------- /platforms/neoforge/src/main/resources/META-INF/services/dynamic_fps.impl.service.ModCompat: -------------------------------------------------------------------------------- 1 | net.lostluma.dynamic_fps.impl.neoforge.service.NeoForgeModCompat 2 | -------------------------------------------------------------------------------- /platforms/neoforge/src/main/resources/dynamic_fps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/neoforge/src/main/resources/dynamic_fps.png -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | - Fix NeoForge build not working ([commit 3c8dc8854f](https://github.com/juliand665/Dynamic-FPS/commit/3c8dc8854f8fce152e6e026cdc15cd1493237341) by @LostLuma) 2 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/icon.png -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/config/option/IdleCondition.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.config.option; 2 | 3 | public enum IdleCondition { 4 | NONE, 5 | VANILLA, 6 | ON_BATTERY; 7 | } 8 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/dynamic_fps.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v2 named 2 | 3 | accessible field net/minecraft/client/sounds/SoundManager soundEngine Lnet/minecraft/client/sounds/SoundEngine; 4 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/config/option/IgnoreInitialClick.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.config.option; 2 | 3 | public enum IgnoreInitialClick { 4 | DISABLED, 5 | IN_WORLD, 6 | CONSTANT; 7 | } 8 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/toast/charging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/toast/charging.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/toast/draining.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/toast/draining.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/toast/reminder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/toast/reminder.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_0.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_1.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_10.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_2.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_3.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_4.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_5.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_6.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_7.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_8.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/charging_9.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_0.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_1.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_10.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_2.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_3.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_4.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_5.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_6.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_7.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_8.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/icon/draining_9.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/toast/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/toast/background.png -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/toast/background_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliand665/Dynamic-FPS/HEAD/platforms/common/src/main/resources/assets/dynamic_fps/textures/battery/toast/background_icon.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/service/ModCompat.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.service; 2 | 3 | public interface ModCompat { 4 | boolean isDisabled(); 5 | 6 | boolean disableOverlayOptimization(); 7 | 8 | static ModCompat getInstance() { 9 | return Services.MOD_COMPAT; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/duck/DuckLoadingOverlay.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util.duck; 2 | 3 | public interface DuckLoadingOverlay { 4 | public default boolean dynamic_fps$isReloadComplete() { 5 | throw new RuntimeException("No implementation for dynamic_fps$isReloadComplete was found."); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Normalize whitespace via .editorconfig 2 | 6e4757c0aa79fa1a64d556a9088d73cc967cdb0d 3 | 4 | # Renames package for the mod ID change, 5 | # Move all code to internal impl package 6 | db024e48db528898eb6782d810dba8a4c9ce38f1 7 | 8 | # Update lang files due to config change 9 | 2ebdd4c177f055468cac54ada8270b5046a45849 10 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/ResourceLocations.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util; 2 | 3 | import net.minecraft.resources.Identifier; 4 | 5 | public class ResourceLocations { 6 | public static Identifier of(String namespace, String path) { 7 | return Identifier.fromNamespaceAndPath(namespace, path); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | tab_width = 4 5 | charset = utf-8 6 | 7 | indent_size = 4 8 | indent_style = space 9 | 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.yaml] 14 | indent_size = 2 15 | 16 | [*.{gradle,java}] 17 | indent_style = tab 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/duck/DuckSoundEngine.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util.duck; 2 | 3 | import net.minecraft.sounds.SoundSource; 4 | 5 | public interface DuckSoundEngine { 6 | default void dynamic_fps$updateVolume(SoundSource source) { 7 | throw new RuntimeException("No implementation for dynamic_fps$updateVolume was found."); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/Logging.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util; 2 | 3 | import dynamic_fps.impl.Constants; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | public class Logging { 8 | private static final Logger logger = LoggerFactory.getLogger(Constants.MOD_ID); 9 | 10 | public static Logger getLogger() { 11 | return logger; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /platforms/forge/src/main/resources/dynamic_fps.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "net.lostluma.dynamic_fps.impl.forge.mixin", 4 | "minVersion": "0.8", 5 | "client": [ 6 | "GuiMixin" 7 | ], 8 | "mixins": [], 9 | "server": [], 10 | "injectors": { 11 | "defaultRequire": 1 12 | }, 13 | "overwrites": { 14 | "conformVisibility": true, 15 | "requireAnnotations": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /platforms/fabric/src/main/resources/dynamic_fps.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "net.lostluma.dynamic_fps.impl.fabric.mixin", 4 | "minVersion": "0.8", 5 | "client": [], 6 | "mixins": [], 7 | "server": [], 8 | "injectors": { 9 | "defaultRequire": 1 10 | }, 11 | "overwrites": { 12 | "conformVisibility": true, 13 | "requireAnnotations": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /platforms/quilt/src/main/resources/dynamic_fps.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "net.lostluma.dynamic_fps.impl.quilt.mixin", 4 | "minVersion": "0.8", 5 | "client": [], 6 | "mixins": [], 7 | "server": [], 8 | "injectors": { 9 | "defaultRequire": 1 10 | }, 11 | "overwrites": { 12 | "conformVisibility": true, 13 | "requireAnnotations": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /platforms/neoforge/src/main/resources/dynamic_fps.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "net.lostluma.dynamic_fps.impl.neoforge.mixin", 4 | "minVersion": "0.8", 5 | "client": [], 6 | "mixins": [], 7 | "server": [], 8 | "injectors": { 9 | "defaultRequire": 1 10 | }, 11 | "overwrites": { 12 | "conformVisibility": true, 13 | "requireAnnotations": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /platforms/textile/src/main/java/net/lostluma/dynamic_fps/impl/textile/compat/ModMenu.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.textile.compat; 2 | 3 | import com.terraformersmc.modmenu.api.ConfigScreenFactory; 4 | import com.terraformersmc.modmenu.api.ModMenuApi; 5 | import dynamic_fps.impl.DynamicFPSMod; 6 | 7 | public class ModMenu implements ModMenuApi { 8 | @Override 9 | public ConfigScreenFactory getModConfigScreenFactory() { 10 | return DynamicFPSMod::getConfigScreen; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/et_ee.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Dynamic FPS seadistus", 3 | 4 | "config.dynamic_fps.category.general": "Üldine", 5 | 6 | "key.dynamic_fps.toggle_forced": "Sunni vähendatud ks (lüliti)", 7 | "key.dynamic_fps.toggle_disabled": "Keela Dynamic FPS (lüliti)", 8 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: sunnin vähendatud kaadrisagedust", 9 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS keelatud" 10 | } 11 | -------------------------------------------------------------------------------- /platforms/textile/src/main/resources/dynamic_fps-textile.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "net.lostluma.dynamic_fps.impl.textile.mixin", 4 | "minVersion": "0.8", 5 | "client": [ 6 | "GuiMixin", 7 | "OptionsMixin" 8 | ], 9 | "mixins": [], 10 | "server": [], 11 | "injectors": { 12 | "defaultRequire": 1 13 | }, 14 | "overwrites": { 15 | "conformVisibility": true, 16 | "requireAnnotations": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /platforms/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("dynamic_fps.module") 3 | } 4 | 5 | val platforms = project.property("enabled_platforms").toString() 6 | 7 | architectury { 8 | common(platforms.split(",")) 9 | } 10 | 11 | loom { 12 | accessWidenerPath = file("src/main/resources/dynamic_fps.accesswidener") 13 | } 14 | 15 | dependencies { 16 | modImplementation(libs.cloth.config) 17 | // Note: This is only here for the @Environment annotation, do not use! 18 | modImplementation(libs.fabric.loader) 19 | } 20 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/pt_pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Definições do Dynamic FPS", 3 | 4 | "config.dynamic_fps.category.general": "Geral", 5 | 6 | "key.dynamic_fps.toggle_forced": "Forçar redução da taxa de quadros (alternável)", 7 | "key.dynamic_fps.toggle_disabled": "Desativar o Disable Dynamic (alternável)", 8 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: forçando a redução da taxa de quadros", 9 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS desativado" 10 | } 11 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle Properties 2 | org.gradle.caching = true 3 | org.gradle.parallel = false 4 | org.gradle.jvmargs = -Xmx3G 5 | 6 | # Loom Properties 7 | loom.ignoreDependencyLoomVersionValidation=true 8 | 9 | # Mod Properties 10 | mod_version = 3.11.1 11 | 12 | maven_group = juliand665 13 | archives_name = dynamic-fps 14 | 15 | # File naming version 16 | minecraft_version = 1.21.11 17 | # Version for publishing 18 | minecraft_version_min = 1.21.11 19 | minecraft_version_max = 1.21.11 20 | 21 | enabled_platforms=fabric,neoforge,quilt 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/config/option/GraphicsState.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.config.option; 2 | 3 | /** 4 | * Graphics settings to apply within a given power state. 5 | */ 6 | public enum GraphicsState { 7 | /** 8 | * User-defined graphics settings via the options menu. 9 | */ 10 | DEFAULT, 11 | 12 | /** 13 | * Reduce graphics settings which do not cause the world to reload. 14 | */ 15 | REDUCED, 16 | 17 | /** 18 | * Reduce graphics settings to minimal values, this will reload the world! 19 | */ 20 | MINIMAL; 21 | } 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/BatteryUtil.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util; 2 | 3 | import net.lostluma.battery.api.State; 4 | 5 | public class BatteryUtil { 6 | /** 7 | * @return whether the state is charging or full. 8 | */ 9 | public static boolean isCharging(State state) { 10 | // Some devices seem to like frantically changing from 11 | // Charging to full, even when the battery is not full 12 | // Prevents frequent HUD changes and notification spam 13 | return state == State.CHARGING || state == State.FULL; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /platforms/forge/src/main/java/net/lostluma/dynamic_fps/impl/forge/service/ForgeModCompat.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.forge.service; 2 | 3 | import dynamic_fps.impl.service.ModCompat; 4 | import dynamic_fps.impl.service.Platform; 5 | import net.minecraftforge.fml.ModList; 6 | 7 | public class ForgeModCompat implements ModCompat { 8 | @Override 9 | public boolean isDisabled() { 10 | return false; 11 | } 12 | 13 | @Override 14 | public boolean disableOverlayOptimization() { 15 | return Platform.getInstance().isModLoaded("rrls"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/config/IdleConfig.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.config; 2 | 3 | import dynamic_fps.impl.config.option.IdleCondition; 4 | 5 | public class IdleConfig { 6 | private int timeout; 7 | private IdleCondition condition; 8 | 9 | public int timeout() { 10 | return this.timeout; 11 | } 12 | 13 | public void setTimeout(int value) { 14 | this.timeout = value; 15 | } 16 | 17 | public IdleCondition condition() { 18 | return this.condition; 19 | } 20 | 21 | public void setCondition(IdleCondition value) { 22 | this.condition = value; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/mixin/LoadingOverlayMixin.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.mixin; 2 | 3 | import net.minecraft.client.gui.screens.LoadingOverlay; 4 | 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Shadow; 7 | 8 | import dynamic_fps.impl.util.duck.DuckLoadingOverlay; 9 | 10 | @Mixin(LoadingOverlay.class) 11 | public class LoadingOverlayMixin implements DuckLoadingOverlay { 12 | @Shadow 13 | private long fadeOutStart; 14 | 15 | @Override 16 | public boolean dynamic_fps$isReloadComplete() { 17 | return this.fadeOutStart > -1L; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/Threads.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util; 2 | 3 | import net.minecraft.client.Minecraft; 4 | 5 | public class Threads { 6 | /** 7 | * Schedule a task on the main thread. 8 | */ 9 | public static void runOnMainThread(Runnable runnable) { 10 | Minecraft.getInstance().schedule(runnable); 11 | } 12 | 13 | /** 14 | * Create a thread and immediately start it. 15 | */ 16 | public static Thread create(String name, Runnable runnable) { 17 | Thread thread = new Thread(runnable, "dynamic-fps-" + name); 18 | thread.start(); 19 | return thread; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/service/Services.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.service; 2 | 3 | import java.util.Optional; 4 | import java.util.ServiceLoader; 5 | 6 | class Services { 7 | static Platform PLATFORM = loadService(Platform.class); 8 | static ModCompat MOD_COMPAT = loadService(ModCompat.class); 9 | 10 | static T loadService(Class type) { 11 | Optional optional = ServiceLoader.load(type).findFirst(); 12 | 13 | if (optional.isPresent()) { 14 | return optional.get(); 15 | } else { 16 | throw new RuntimeException("Failed to load Dynamic FPS " + type.getSimpleName() + " service!"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { url = uri("https://maven.fabricmc.net/") } 4 | maven { url = uri("https://maven.architectury.dev/") } 5 | maven { url = uri("https://maven.minecraftforge.net/") } 6 | maven { url = uri("https://maven.neoforged.net/releases/") } 7 | gradlePluginPortal() 8 | } 9 | } 10 | 11 | plugins { 12 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" 13 | } 14 | 15 | rootProject.name = "dynamic-fps" 16 | includeBuild("build-logic") 17 | 18 | include(":platforms:common") 19 | include(":platforms:fabric") 20 | // include(":platforms:forge") 21 | include(":platforms:neoforge") 22 | include(":platforms:quilt") 23 | include(":platforms:textile") 24 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/dynamic_fps-common.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "dynamic_fps.impl.mixin", 4 | "minVersion": "0.8", 5 | "client": [ 6 | "DebugEntryFpsMixin", 7 | "FramerateLimitTrackerMixin", 8 | "GameRendererMixin", 9 | "GuiMixin", 10 | "LoadingOverlayMixin", 11 | "MinecraftMixin", 12 | "OptionsMixin", 13 | "SoundEngineMixin", 14 | "ToastManagerMixin", 15 | "bugfix.BlockableEventLoopMixin" 16 | ], 17 | "mixins": [], 18 | "server": [], 19 | "injectors": { 20 | "defaultRequire": 1 21 | }, 22 | "overwrites": { 23 | "conformVisibility": true, 24 | "requireAnnotations": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/feature/battery/ErrorToast.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.feature.battery; 2 | 3 | import dynamic_fps.impl.util.Components; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.network.chat.Component; 6 | 7 | public class ErrorToast extends BaseToast { 8 | private static final Component TITLE = Components.translatable("toast", "error"); 9 | 10 | private ErrorToast(Component description) { 11 | super(TITLE, description, null); 12 | } 13 | 14 | /** 15 | * Queue some information to be shown as a toast. 16 | */ 17 | public static void queueToast(Component description) { 18 | ErrorToast toast = new ErrorToast(description); 19 | Minecraft.getInstance().getToastManager().addToast(toast); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/mixin/bugfix/BlockableEventLoopMixin.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.mixin.bugfix; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | import org.spongepowered.asm.mixin.Overwrite; 5 | 6 | import net.minecraft.util.thread.BlockableEventLoop; 7 | 8 | import java.util.concurrent.locks.LockSupport; 9 | 10 | @Mixin(BlockableEventLoop.class) 11 | public final class BlockableEventLoopMixin { 12 | /** 13 | * @author Julian Dunskus 14 | * @reason The vanilla version is simply broken, taking up way too many resources in the background. 15 | */ 16 | @Overwrite 17 | public void waitForTasks() { 18 | // yield() here is a terrible idea 19 | LockSupport.parkNanos("waiting for tasks", 500_000); // increased wait to 0.5 ms 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /platforms/textile/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("dynamic_fps.module") 3 | } 4 | 5 | architectury { 6 | common rootProject.enabled_platforms.split(',') 7 | } 8 | 9 | repositories { 10 | exclusiveContent { 11 | filter { includeGroup("com.terraformersmc") } 12 | forRepository { maven { url = "https://maven.terraformersmc.com/releases" } } 13 | } 14 | } 15 | 16 | dependencies { 17 | modImplementation(libs.fabric.loader) 18 | 19 | modImplementation(fabricApi.module("fabric-resource-loader-v0", libs.versions.fabric.api.get())) 20 | modImplementation(fabricApi.module("fabric-lifecycle-events-v1", libs.versions.fabric.api.get())) 21 | 22 | modApi(libs.modmenu) 23 | 24 | compileOnly(project(path: ':platforms:common', configuration: 'namedElements')) { transitive = false } 25 | } 26 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/mixin/ToastManagerMixin.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.mixin; 2 | 3 | import net.minecraft.client.gui.components.toasts.ToastManager; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.injection.At; 6 | import org.spongepowered.asm.mixin.injection.Inject; 7 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 8 | 9 | import dynamic_fps.impl.DynamicFPSMod; 10 | 11 | @Mixin(ToastManager.class) 12 | public class ToastManagerMixin { 13 | @Inject(method = "freeSlotCount", at = @At("HEAD"), cancellable = true) 14 | private void freeSlotCount(CallbackInfoReturnable callbackInfo) { 15 | if (!DynamicFPSMod.shouldShowToasts()) { 16 | callbackInfo.setReturnValue(0); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/mixin/GuiMixin.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.mixin; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | import org.spongepowered.asm.mixin.injection.At; 5 | import org.spongepowered.asm.mixin.injection.Inject; 6 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 7 | 8 | import dynamic_fps.impl.DynamicFPSMod; 9 | import net.minecraft.client.gui.Gui; 10 | 11 | @Mixin(Gui.class) 12 | public class GuiMixin { 13 | /** 14 | * Cancels rendering the GUI if it is determined to currently not be visible. 15 | */ 16 | @Inject(method = "render", at = @At("HEAD"), cancellable = true) 17 | private void shouldRender(CallbackInfo callbackInfo) { 18 | if (!DynamicFPSMod.shouldShowLevels()) { 19 | callbackInfo.cancel(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/manual-natives-install.md: -------------------------------------------------------------------------------- 1 | # Manual Natives Installation 2 | 3 | How to install the battery library without having the mod do it on your behalf: 4 | 5 | - Visit the [releases section](https://code.lostluma.net/LostLuma/battery/releases) of the library and find the right version: 6 | - Currently Dynamic FPS uses version 1.3.0 7 | - Download the correct dynamic library for your system: 8 | - You will want to download the file called `libbattery-jni-1.2.0+..` 9 | - If you're not sure which one to use simply download all of them, redundant ones are ignored at runtime 10 | - Locate Dynamic FPS' cache directory in your Minecraft instance 11 | - This is currently `/.cache/dynamic_fps/` for every mod loader 12 | - Drop the dynamic library into this folder 13 | - Start the game! The battery features should now be working 14 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/Constants.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl; 2 | 3 | import dynamic_fps.impl.service.Platform; 4 | 5 | public class Constants { 6 | // Meta 7 | public static final String MOD_ID = "dynamic_fps"; 8 | public static final boolean DEBUG = Platform.getInstance().isDevelopmentEnvironment(); 9 | 10 | // Miscellaneous 11 | 12 | // Minimum limit, for lower FPS we cancel frames 13 | public static final int MIN_FRAME_RATE_LIMIT = 15; 14 | // Minecraft considers limits >=260 as unlimited 15 | public static final int NO_FRAME_RATE_LIMIT = 260; 16 | // Default frame rate limit on all title screens 17 | public static final int TITLE_FRAME_RATE_LIMIT = 60; 18 | 19 | // The Cloth Config mod ID has changed a few times 20 | public static final String[] CLOTH_CONFIG_ID = new String[] { "cloth-config", "cloth_config", "cloth-config2" }; 21 | } 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/service/Platform.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.service; 2 | 3 | import dynamic_fps.impl.util.Version; 4 | 5 | import java.nio.file.Path; 6 | import java.util.Arrays; 7 | import java.util.Optional; 8 | 9 | public interface Platform { 10 | String getName(); 11 | 12 | Path getCacheDir(); 13 | Path getConfigDir(); 14 | boolean isDevelopmentEnvironment(); 15 | 16 | boolean isModLoaded(String modId); 17 | Optional getModVersion(String modId); 18 | 19 | void registerStartTickEvent(StartTickEvent event); 20 | 21 | default boolean isModLoaded(String ...modId) { 22 | return Arrays.stream(modId).anyMatch(this::isModLoaded); 23 | } 24 | 25 | @FunctionalInterface 26 | interface StartTickEvent { 27 | void onStartTick(); 28 | } 29 | 30 | static Platform getInstance() { 31 | return Services.PLATFORM; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/Components.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util; 2 | 3 | import dynamic_fps.impl.Constants; 4 | import net.minecraft.network.chat.Component; 5 | import net.minecraft.network.chat.MutableComponent; 6 | 7 | public final class Components { 8 | /** e.g. keyString("title", "config") -> "title.dynamic_fps.config") */ 9 | public static String translationKey(String domain, String path) { 10 | return domain + "." + Constants.MOD_ID + "." + path; 11 | } 12 | 13 | public static MutableComponent literal(String value) { 14 | return Component.literal(value); 15 | } 16 | 17 | public static MutableComponent translatable(String path, Object... args) { 18 | return Component.translatable(path, args); 19 | } 20 | 21 | public static MutableComponent translatable(String domain, String path, Object... args) { 22 | return Component.translatable(translationKey(domain, path), args); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /platforms/forge/src/main/java/net/lostluma/dynamic_fps/impl/forge/mixin/GuiMixin.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.forge.mixin; 2 | 3 | import com.llamalad7.mixinextras.sugar.Local; 4 | import dynamic_fps.impl.util.HudInfoRenderer; 5 | import net.minecraft.client.gui.Gui; 6 | import net.minecraft.client.gui.GuiGraphics; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(Gui.class) 13 | public class GuiMixin { 14 | /** 15 | * Render info on whether Dynamic FPS is disabled or always reducing the user's FPS. 16 | */ 17 | @Inject(method = "renderSavingIndicator", at = @At("HEAD")) 18 | private void renderSavingIndicator(CallbackInfo callbackInfo, @Local(argsOnly = true) GuiGraphics guiGraphics) { 19 | HudInfoRenderer.renderInfo(guiGraphics); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /platforms/textile/src/main/java/net/lostluma/dynamic_fps/impl/textile/mixin/GuiMixin.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.textile.mixin; 2 | 3 | import com.llamalad7.mixinextras.sugar.Local; 4 | import dynamic_fps.impl.util.HudInfoRenderer; 5 | import net.minecraft.client.gui.Gui; 6 | import net.minecraft.client.gui.GuiGraphics; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(Gui.class) 13 | public class GuiMixin { 14 | /** 15 | * Render info on whether Dynamic FPS is disabled or always reducing the user's FPS. 16 | */ 17 | @Inject(method = "renderSavingIndicator", at = @At("HEAD")) 18 | private void renderSavingIndicator(CallbackInfo callbackInfo, @Local(argsOnly = true) GuiGraphics guiGraphics) { 19 | HudInfoRenderer.renderInfo(guiGraphics); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/config/option/BatteryIndicatorPlacement.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.config.option; 2 | 3 | import com.mojang.blaze3d.platform.Window; 4 | 5 | /** 6 | * Screen corner to render the battery indicator in. 7 | */ 8 | public enum BatteryIndicatorPlacement { 9 | TOP_LEFT(window -> new int[] {4, 4}), 10 | TOP_RIGHT(window -> new int[] {window.getGuiScaledWidth() - 47, 4}), 11 | BOTTOM_LEFT(window -> new int[] {4, window.getGuiScaledHeight() - 20}), 12 | BOTTOM_RIGHT(window -> new int[] {window.getGuiScaledWidth() - 47, window.getGuiScaledHeight() - 20}); 13 | 14 | private final DynamicPlacement placement; 15 | 16 | BatteryIndicatorPlacement(DynamicPlacement placement) { 17 | this.placement = placement; 18 | } 19 | 20 | public int[] get(Window window) { 21 | return this.placement.get(window); 22 | } 23 | 24 | @FunctionalInterface 25 | private interface DynamicPlacement { 26 | int[] get(Window window); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/zh_hk.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "設定 Dynamic FPS", 3 | 4 | "config.dynamic_fps.category.hovered": "聚焦時", 5 | "config.dynamic_fps.category.unfocused": "未聚焦時", 6 | "config.dynamic_fps.category.invisible": "不可見時", 7 | 8 | "config.dynamic_fps.frame_rate_target": "FPS 目標", 9 | "config.dynamic_fps.frame_rate_target_description": "將 FPS 目標設為 -1 以停用限制 FPS。", 10 | "config.dynamic_fps.volume_multiplier": "音量倍增器", 11 | "config.dynamic_fps.graphics_state": "圖形選項", 12 | "config.dynamic_fps.show_toasts": "顯示彈出提示框", 13 | "config.dynamic_fps.run_garbage_collector": "使用垃圾回收器", 14 | 15 | "key.dynamic_fps.toggle_forced": "強制未聚焦模式(切換)", 16 | "key.dynamic_fps.toggle_disabled": "停用 Dynamic FPS(切換)", 17 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS:強制限制 FPS", 18 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS 已停用", 19 | 20 | "modmenu.descriptionTranslation.dynamic_fps": "動態調整 FPS,使 Minecraft 在背景時不會占用很多資源。" 21 | } 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/config/VolumeTransitionConfig.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.config; 2 | 3 | public class VolumeTransitionConfig { 4 | private float up; 5 | private float down; 6 | 7 | private static final float IMMEDIATE = 10.0f; 8 | 9 | protected VolumeTransitionConfig(float up, float down) { 10 | this.up = up; 11 | this.down = down; 12 | } 13 | 14 | public float getUp() { 15 | if (this.up == -1) { 16 | return IMMEDIATE; 17 | } else { 18 | return this.up; 19 | } 20 | } 21 | 22 | public void setUp(float value) { 23 | if (value >= IMMEDIATE) { 24 | this.up = -1.0f; 25 | } else { 26 | this.up = value; 27 | } 28 | } 29 | 30 | public float getDown() { 31 | if (this.down == -1) { 32 | return IMMEDIATE; 33 | } else { 34 | return this.down; 35 | } 36 | } 37 | 38 | public void setDown(float value) { 39 | if (value >= IMMEDIATE) { 40 | this.down = -1.0f; 41 | } else { 42 | this.down = value; 43 | } 44 | } 45 | 46 | public boolean isActive() { 47 | return this.up != -1.0f || this.down != -1.0f; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 - 2024 Julian Dunskus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /platforms/neoforge/src/main/java/net/lostluma/dynamic_fps/impl/neoforge/service/NeoForgeModCompat.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.neoforge.service; 2 | 3 | import com.electronwill.nightconfig.core.CommentedConfig; 4 | import dynamic_fps.impl.service.ModCompat; 5 | import dynamic_fps.impl.service.Platform; 6 | import net.neoforged.fml.ModList; 7 | import net.neoforged.neoforgespi.language.IModInfo; 8 | 9 | public class NeoForgeModCompat implements ModCompat { 10 | private static boolean disableOverlayOptimization = false; 11 | 12 | static { 13 | ModList.get().getMods().forEach(NeoForgeModCompat::parseModMetadata); 14 | } 15 | 16 | @Override 17 | public boolean isDisabled() { 18 | return false; 19 | } 20 | 21 | @Override 22 | public boolean disableOverlayOptimization() { 23 | return disableOverlayOptimization || Platform.getInstance().isModLoaded("rrls"); 24 | } 25 | 26 | private static void parseModMetadata(IModInfo modInfo) { 27 | Object config = modInfo.getModProperties().get("dynamic_fps"); 28 | 29 | if (config == null) { 30 | return; 31 | } 32 | 33 | Boolean value = ((CommentedConfig) config).get("optimized_overlay"); 34 | 35 | if (value != null && !value) { 36 | disableOverlayOptimization = true; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/PowerState.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl; 2 | 3 | /** 4 | * An analog for device power states, applied to the Minecraft window. 5 | * 6 | * Power states are prioritized based on their order here, see DynamicFPSMod.checkForStateChanges for impl details. 7 | */ 8 | public enum PowerState { 9 | /** 10 | * Window is currently focused. 11 | */ 12 | FOCUSED(ConfigurabilityLevel.NONE), 13 | 14 | /** 15 | * Mouse positioned over unfocused window. 16 | */ 17 | HOVERED(ConfigurabilityLevel.FULL), 18 | 19 | /** 20 | * Another application is focused. 21 | */ 22 | UNFOCUSED(ConfigurabilityLevel.FULL), 23 | 24 | /** 25 | * Window minimized or otherwise hidden. 26 | */ 27 | INVISIBLE(ConfigurabilityLevel.FULL), 28 | 29 | /** 30 | * The device is currently on battery. 31 | */ 32 | UNPLUGGED(ConfigurabilityLevel.SOME), 33 | 34 | /** 35 | * User hasn't sent input for some time. 36 | */ 37 | ABANDONED(ConfigurabilityLevel.FULL); 38 | 39 | public final ConfigurabilityLevel configurabilityLevel; 40 | 41 | PowerState(ConfigurabilityLevel configurabilityLevel) { 42 | this.configurabilityLevel = configurabilityLevel; 43 | } 44 | 45 | public enum ConfigurabilityLevel { 46 | NONE, 47 | SOME, 48 | FULL; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/es_ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Configurar Dynamic FPS", 3 | 4 | "config.dynamic_fps.category.hovered": "Sobre", 5 | "config.dynamic_fps.category.unfocused": "Desenfocado", 6 | "config.dynamic_fps.category.invisible": "Invisible", 7 | 8 | "config.dynamic_fps.frame_rate_target": "Objetivo de Velocidad de Fotogramas", 9 | "config.dynamic_fps.frame_rate_target_description": "Establecer el objetivo de FPS en -1 para desactivar la reducción de FPS.", 10 | "config.dynamic_fps.volume_multiplier": "Multiplicador de Volumen", 11 | "config.dynamic_fps.graphics_state": "Opciones Gráficas", 12 | "config.dynamic_fps.show_toasts": "Mostrar Notificaciones", 13 | "config.dynamic_fps.run_garbage_collector": "Ejecutar el Garbage Collector", 14 | 15 | "key.dynamic_fps.toggle_forced": "Forzar el modo desenfocado (Alternar)", 16 | "key.dynamic_fps.toggle_disabled": "Deshabilitar Dynamic FPS (Alternar)", 17 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: Forzando FPS reducidos", 18 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS Deshabilitado", 19 | 20 | "modmenu.descriptionTranslation.dynamic_fps": "Ajusta dinámicamente los FPS para que Minecraft no acapare recursos en segundo plano." 21 | } 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/es_cl.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Configurar Dynamic FPS", 3 | 4 | "config.dynamic_fps.category.hovered": "Sobre", 5 | "config.dynamic_fps.category.unfocused": "Desenfocado", 6 | "config.dynamic_fps.category.invisible": "Invisible", 7 | 8 | "config.dynamic_fps.frame_rate_target": "Objetivo de Velocidad de Fotogramas", 9 | "config.dynamic_fps.frame_rate_target_description": "Establecer el objetivo de FPS en -1 para desactivar la reducción de FPS.", 10 | "config.dynamic_fps.volume_multiplier": "Multiplicador de Volumen", 11 | "config.dynamic_fps.graphics_state": "Opciones Gráficas", 12 | "config.dynamic_fps.show_toasts": "Mostrar Notificaciones", 13 | "config.dynamic_fps.run_garbage_collector": "Ejecutar el Garbage Collector", 14 | 15 | "key.dynamic_fps.toggle_forced": "Forzar el modo desenfocado (Alternar)", 16 | "key.dynamic_fps.toggle_disabled": "Deshabilitar Dynamic FPS (Alternar)", 17 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: Forzando FPS reducidos", 18 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS Deshabilitado", 19 | 20 | "modmenu.descriptionTranslation.dynamic_fps": "Ajusta dinámicamente los FPS para que Minecraft no acapare recursos en segundo plano." 21 | } 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/es_ec.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Configurar Dynamic FPS", 3 | 4 | "config.dynamic_fps.category.hovered": "Sobre", 5 | "config.dynamic_fps.category.unfocused": "Desenfocado", 6 | "config.dynamic_fps.category.invisible": "Invisible", 7 | 8 | "config.dynamic_fps.frame_rate_target": "Objetivo de Velocidad de Fotogramas", 9 | "config.dynamic_fps.frame_rate_target_description": "Establecer el objetivo de FPS en -1 para desactivar la reducción de FPS.", 10 | "config.dynamic_fps.volume_multiplier": "Multiplicador de Volumen", 11 | "config.dynamic_fps.graphics_state": "Opciones Gráficas", 12 | "config.dynamic_fps.show_toasts": "Mostrar Notificaciones", 13 | "config.dynamic_fps.run_garbage_collector": "Ejecutar el Garbage Collector", 14 | 15 | "key.dynamic_fps.toggle_forced": "Forzar el modo desenfocado (Alternar)", 16 | "key.dynamic_fps.toggle_disabled": "Deshabilitar Dynamic FPS (Alternar)", 17 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: Forzando FPS reducidos", 18 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS Deshabilitado", 19 | 20 | "modmenu.descriptionTranslation.dynamic_fps": "Ajusta dinámicamente los FPS para que Minecraft no acapare recursos en segundo plano." 21 | } 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/es_es.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Configurar Dynamic FPS", 3 | 4 | "config.dynamic_fps.category.hovered": "Sobre", 5 | "config.dynamic_fps.category.unfocused": "Desenfocado", 6 | "config.dynamic_fps.category.invisible": "Invisible", 7 | 8 | "config.dynamic_fps.frame_rate_target": "Objetivo de Velocidad de Fotogramas", 9 | "config.dynamic_fps.frame_rate_target_description": "Establecer el objetivo de FPS en -1 para desactivar la reducción de FPS.", 10 | "config.dynamic_fps.volume_multiplier": "Multiplicador de Volumen", 11 | "config.dynamic_fps.graphics_state": "Opciones Gráficas", 12 | "config.dynamic_fps.show_toasts": "Mostrar Notificaciones", 13 | "config.dynamic_fps.run_garbage_collector": "Ejecutar el Garbage Collector", 14 | 15 | "key.dynamic_fps.toggle_forced": "Forzar el modo desenfocado (Alternar)", 16 | "key.dynamic_fps.toggle_disabled": "Deshabilitar Dynamic FPS (Alternar)", 17 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: Forzando FPS reducidos", 18 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS Deshabilitado", 19 | 20 | "modmenu.descriptionTranslation.dynamic_fps": "Ajusta dinámicamente los FPS para que Minecraft no acapare recursos en segundo plano." 21 | } 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/es_uy.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Configurar Dynamic FPS", 3 | 4 | "config.dynamic_fps.category.hovered": "Sobre", 5 | "config.dynamic_fps.category.unfocused": "Desenfocado", 6 | "config.dynamic_fps.category.invisible": "Invisible", 7 | 8 | "config.dynamic_fps.frame_rate_target": "Objetivo de Velocidad de Fotogramas", 9 | "config.dynamic_fps.frame_rate_target_description": "Establecer el objetivo de FPS en -1 para desactivar la reducción de FPS.", 10 | "config.dynamic_fps.volume_multiplier": "Multiplicador de Volumen", 11 | "config.dynamic_fps.graphics_state": "Opciones Gráficas", 12 | "config.dynamic_fps.show_toasts": "Mostrar Notificaciones", 13 | "config.dynamic_fps.run_garbage_collector": "Ejecutar el Garbage Collector", 14 | 15 | "key.dynamic_fps.toggle_forced": "Forzar el modo desenfocado (Alternar)", 16 | "key.dynamic_fps.toggle_disabled": "Deshabilitar Dynamic FPS (Alternar)", 17 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: Forzando FPS reducidos", 18 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS Deshabilitado", 19 | 20 | "modmenu.descriptionTranslation.dynamic_fps": "Ajusta dinámicamente los FPS para que Minecraft no acapare recursos en segundo plano." 21 | } 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/es_ve.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Configurar Dynamic FPS", 3 | 4 | "config.dynamic_fps.category.hovered": "Sobre", 5 | "config.dynamic_fps.category.unfocused": "Desenfocado", 6 | "config.dynamic_fps.category.invisible": "Invisible", 7 | 8 | "config.dynamic_fps.frame_rate_target": "Objetivo de Velocidad de Fotogramas", 9 | "config.dynamic_fps.frame_rate_target_description": "Establecer el objetivo de FPS en -1 para desactivar la reducción de FPS.", 10 | "config.dynamic_fps.volume_multiplier": "Multiplicador de Volumen", 11 | "config.dynamic_fps.graphics_state": "Opciones Gráficas", 12 | "config.dynamic_fps.show_toasts": "Mostrar Notificaciones", 13 | "config.dynamic_fps.run_garbage_collector": "Ejecutar el Garbage Collector", 14 | 15 | "key.dynamic_fps.toggle_forced": "Forzar el modo desenfocado (Alternar)", 16 | "key.dynamic_fps.toggle_disabled": "Deshabilitar Dynamic FPS (Alternar)", 17 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: Forzando FPS reducidos", 18 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS Deshabilitado", 19 | 20 | "modmenu.descriptionTranslation.dynamic_fps": "Ajusta dinámicamente los FPS para que Minecraft no acapare recursos en segundo plano." 21 | } 22 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/feature/battery/BatteryToast.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.feature.battery; 2 | 3 | import dynamic_fps.impl.util.Components; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.network.chat.Component; 6 | import net.minecraft.resources.Identifier; 7 | 8 | public class BatteryToast extends BaseToast { 9 | private static BatteryToast queuedToast; 10 | 11 | private BatteryToast(Component title, Identifier icon) { 12 | super(title, Component.empty(), icon); 13 | } 14 | 15 | /** 16 | * Queue some information to be shown as a toast. 17 | * If an older toast of the same type is already queued its information will be replaced. 18 | */ 19 | public static void queueToast(Component title, Identifier icon) { 20 | if (queuedToast != null) { 21 | queuedToast.title = title; 22 | queuedToast.icon = icon; 23 | } else { 24 | queuedToast = new BatteryToast(title, icon); 25 | Minecraft.getInstance().getToastManager().addToast(queuedToast); 26 | } 27 | } 28 | 29 | @Override 30 | public void onFirstRender() { 31 | if (this == queuedToast) { 32 | queuedToast = null; 33 | } 34 | 35 | // Initialize when first rendering so the battery percentage is mostly up-to-date 36 | this.description = Components.translatable("toast", "battery_charge", BatteryTracker.charge()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/config/option/BatteryIndicatorCondition.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.config.option; 2 | 3 | import dynamic_fps.impl.config.DynamicFPSConfig; 4 | import dynamic_fps.impl.feature.battery.BatteryTracker; 5 | import net.lostluma.battery.api.State; 6 | 7 | /** 8 | * Condition under which the battery indicator HUD is shown. 9 | */ 10 | public enum BatteryIndicatorCondition { 11 | /** 12 | * Never show the battery indicator. 13 | */ 14 | DISABLED((() -> false)), 15 | 16 | /** 17 | * Show battery indicator when the battery is being drained. 18 | */ 19 | DRAINING(() -> BatteryTracker.status() == State.DISCHARGING), 20 | 21 | /** 22 | * Show battery indicator when the battery is at a critical level. 23 | */ 24 | CRITICAL(() -> { 25 | int critical = DynamicFPSConfig.INSTANCE.batteryTracker().criticalLevel(); 26 | return DRAINING.isConditionMet() && BatteryTracker.charge() <= critical; 27 | }), 28 | 29 | /** 30 | * Show battery indicator at all times. 31 | */ 32 | CONSTANT(() -> true); 33 | 34 | private final Condition condition; 35 | 36 | BatteryIndicatorCondition(Condition condition) { 37 | this.condition = condition; 38 | } 39 | 40 | public boolean isConditionMet() { 41 | return this.condition.isMet(); 42 | } 43 | 44 | @FunctionalInterface 45 | private interface Condition { 46 | boolean isMet(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /platforms/textile/src/main/java/net/lostluma/dynamic_fps/impl/textile/mixin/OptionsMixin.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.textile.mixin; 2 | 3 | import com.google.common.collect.Lists; 4 | import dynamic_fps.impl.util.KeyMappingHandler; 5 | import net.minecraft.client.KeyMapping; 6 | import net.minecraft.client.Options; 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Mutable; 10 | import org.spongepowered.asm.mixin.Pseudo; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 15 | 16 | import java.util.List; 17 | 18 | @Mixin(Options.class) 19 | @Pseudo 20 | public class OptionsMixin { 21 | @Shadow 22 | @Final 23 | @Mutable 24 | public KeyMapping[] keyMappings; 25 | 26 | /** 27 | * Add the mod's key mappings to the vanilla options screen. 28 | */ 29 | @Inject(method = "load", at = @At("HEAD")) 30 | private void load(CallbackInfo callbackInfo) { 31 | List mappings = Lists.newArrayList(this.keyMappings); 32 | 33 | for (KeyMappingHandler handler : KeyMappingHandler.getHandlers()) { 34 | mappings.add(handler.keyMapping()); 35 | } 36 | 37 | this.keyMappings = mappings.toArray(new KeyMapping[0]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | java: [ 10 | 21, 11 | ] 12 | os: [ 13 | ubuntu-24.04, 14 | windows-2022, 15 | ] 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v5 20 | - name: Setup JDK ${{ matrix.java }} 21 | uses: actions/setup-java@v5 22 | with: 23 | distribution: temurin 24 | java-version: ${{ matrix.java }} 25 | - name: Make Gradle wrapper executable 26 | if: ${{ runner.os != 'Windows' }} 27 | run: chmod +x ./gradlew 28 | - name: Setup Gradle 29 | uses: gradle/actions/setup-gradle@v5 30 | with: 31 | cache-read-only: ${{ github.ref_name != github.event.repository.default_branch }} 32 | - name: Build 33 | run: ./gradlew build --warning-mode=all 34 | - name: Capture build artifacts 35 | if: ${{ runner.os == 'Linux' && matrix.java == '21' }} # Upload artifacts from one job, ignore the rest 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: dynamic-fps-artifacts 39 | path: | 40 | platforms/**/build/libs 41 | !platforms/common/build/libs/* 42 | !platforms/**/build/libs/*-all.jar 43 | if-no-files-found: error 44 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/mixin/GameRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.mixin; 2 | 3 | import org.objectweb.asm.Opcodes; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.injection.At; 6 | import org.spongepowered.asm.mixin.injection.Inject; 7 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 8 | 9 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 10 | 11 | import dynamic_fps.impl.DynamicFPSMod; 12 | import net.minecraft.client.renderer.GameRenderer; 13 | 14 | @Mixin(GameRenderer.class) 15 | public class GameRendererMixin { 16 | /** 17 | * Implements the mod's big feature. 18 | * 19 | * Note: Inject after the pause on lost focus check, 20 | * This allows the feature to work even at zero FPS. 21 | */ 22 | @ModifyExpressionValue( 23 | method = "render", 24 | at = @At( 25 | value = "FIELD", 26 | target = "Lnet/minecraft/client/Minecraft;noRender:Z", 27 | opcode = Opcodes.GETFIELD 28 | ) 29 | ) 30 | private boolean skipRendering(boolean original) { 31 | return original || !DynamicFPSMod.renderedCurrentFrame(); 32 | } 33 | 34 | /** 35 | * Cancels rendering the world if it is determined to currently not be visible. 36 | */ 37 | @Inject(method = "renderLevel", at = @At("HEAD"), cancellable = true) 38 | private void shouldRender(CallbackInfo callbackInfo) { 39 | if (!DynamicFPSMod.shouldShowLevels()) { 40 | callbackInfo.cancel(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /platforms/neoforge/src/main/java/net/lostluma/dynamic_fps/impl/neoforge/DynamicFPSNeoForgeMod.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.neoforge; 2 | 3 | import dynamic_fps.impl.Constants; 4 | import dynamic_fps.impl.DynamicFPSMod; 5 | import dynamic_fps.impl.util.HudInfoRenderer; 6 | import dynamic_fps.impl.util.KeyMappingHandler; 7 | import net.neoforged.bus.api.IEventBus; 8 | import net.neoforged.fml.ModLoadingContext; 9 | import net.neoforged.fml.common.Mod; 10 | import net.neoforged.fml.loading.FMLLoader; 11 | import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent; 12 | import net.neoforged.neoforge.client.event.RenderGuiEvent; 13 | import net.neoforged.neoforge.client.gui.IConfigScreenFactory; 14 | import net.neoforged.neoforge.common.NeoForge; 15 | 16 | @Mod(Constants.MOD_ID) 17 | public class DynamicFPSNeoForgeMod { 18 | public DynamicFPSNeoForgeMod(IEventBus modEventBus) { 19 | if (FMLLoader.getCurrent().getDist().isDedicatedServer()) { 20 | return; 21 | } 22 | 23 | ModLoadingContext.get().registerExtensionPoint( 24 | IConfigScreenFactory.class, 25 | () -> (minecraft, screen) -> DynamicFPSMod.getConfigScreen(screen) 26 | ); 27 | 28 | modEventBus.addListener(this::registerKeyMappings); 29 | NeoForge.EVENT_BUS.addListener(this::renderGuiOverlay); 30 | } 31 | 32 | public void renderGuiOverlay(RenderGuiEvent.Pre event) { 33 | HudInfoRenderer.renderInfo(event.getGuiGraphics()); 34 | } 35 | 36 | public void registerKeyMappings(RegisterKeyMappingsEvent event) { 37 | for (KeyMappingHandler handler : KeyMappingHandler.getHandlers()) { 38 | event.register(handler.keyMapping()); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /platforms/textile/src/main/java/net/lostluma/dynamic_fps/impl/textile/service/TextileModCompat.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.textile.service; 2 | 3 | import dynamic_fps.impl.service.ModCompat; 4 | import net.fabricmc.loader.api.FabricLoader; 5 | import net.fabricmc.loader.api.ModContainer; 6 | import net.fabricmc.loader.api.metadata.CustomValue; 7 | import net.fabricmc.loader.api.metadata.ModMetadata; 8 | import net.lostluma.dynamic_fps.impl.textile.compat.FREX; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | public class TextileModCompat implements ModCompat { 12 | private static boolean disableOverlayOptimization = false; 13 | 14 | static { 15 | FabricLoader.getInstance().getAllMods().forEach(TextileModCompat::parseModMetadata); 16 | } 17 | 18 | @Override 19 | public boolean isDisabled() { 20 | return FREX.isFlawlessFramesActive(); 21 | } 22 | 23 | @Override 24 | public boolean disableOverlayOptimization() { 25 | return disableOverlayOptimization; 26 | } 27 | 28 | private static void parseModMetadata(ModContainer mod) { 29 | CustomValue.CvObject root; 30 | ModMetadata data = mod.getMetadata(); 31 | 32 | try { 33 | root = data.getCustomValue("dynamic_fps").getAsObject(); 34 | } catch (ClassCastException | NullPointerException e) { 35 | return; // Object is either missing or is of an invalid type 36 | } 37 | 38 | parseOverlayOverride(root.get("optimized_overlay")); 39 | } 40 | 41 | private static void parseOverlayOverride(@Nullable CustomValue value) { 42 | if (value != null && value.getType() == CustomValue.CvType.BOOLEAN && !value.getAsBoolean()) { 43 | disableOverlayOptimization = true; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /platforms/textile/src/main/java/net/lostluma/dynamic_fps/impl/textile/compat/FREX.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.textile.compat; 2 | 3 | import dynamic_fps.impl.DynamicFPSMod; 4 | import net.fabricmc.api.ClientModInitializer; 5 | import net.fabricmc.loader.api.FabricLoader; 6 | 7 | import java.util.Set; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.function.Consumer; 10 | import java.util.function.Function; 11 | 12 | /** 13 | *

14 | * Implements the FREX Flawless Frames API to allow other mods to request all 15 | * frames to be processed until requested to 16 | * go back to normal operation, such as ReplayMod rendering a video. 17 | *

18 | * See https://github.com/grondag/frex/pull/9 19 | */ 20 | public final class FREX implements ClientModInitializer { 21 | private static final Set ACTIVE = ConcurrentHashMap.newKeySet(); 22 | 23 | /** 24 | * Returns whether one or more mods have requested Flawless Frames to be active, 25 | * and therefore frames should not be skipped. 26 | */ 27 | public static boolean isFlawlessFramesActive() { 28 | return !ACTIVE.isEmpty(); 29 | } 30 | 31 | @Override 32 | @SuppressWarnings("unchecked") 33 | public void onInitializeClient() { 34 | Function> provider = name -> { 35 | Object token = new Object(); 36 | 37 | return active -> { 38 | if (active) { 39 | ACTIVE.add(token); 40 | } else { 41 | ACTIVE.remove(token); 42 | } 43 | 44 | DynamicFPSMod.onStatusChanged(false); 45 | }; 46 | }; 47 | 48 | FabricLoader.getInstance().getEntrypoints("frex_flawless_frames", Consumer.class).forEach(api -> api.accept(provider)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/FallbackConfigScreen.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.client.gui.GuiGraphics; 5 | import net.minecraft.client.gui.components.Button; 6 | import net.minecraft.client.gui.screens.Screen; 7 | import net.minecraft.network.chat.CommonComponents; 8 | import net.minecraft.network.chat.Component; 9 | 10 | public class FallbackConfigScreen extends Screen { 11 | private final Screen parent; 12 | 13 | private static final Component WARNING_0 = Components.translatable("config.dynamic_fps.warn_cloth_config.0"); 14 | private static final Component WARNING_1 = Components.translatable("config.dynamic_fps.warn_cloth_config.1"); 15 | 16 | public FallbackConfigScreen(Screen parent) { 17 | super(Components.translatable("config.dynamic_fps.title")); 18 | 19 | this.parent = parent; 20 | } 21 | 22 | @Override 23 | protected void init() { 24 | int width = 152; 25 | int height = 20; 26 | int x = (this.width - width) / 2; 27 | int y = this.height - height - 5; 28 | 29 | this.addRenderableWidget( 30 | Button.builder(CommonComponents.GUI_BACK, button -> this.onClose()).bounds(x, y, width, height).build() 31 | ); 32 | } 33 | 34 | @Override 35 | public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { 36 | super.render(guiGraphics, mouseX, mouseY, partialTicks); 37 | 38 | int width = guiGraphics.guiWidth() / 2; 39 | int height = guiGraphics.guiHeight() / 3; 40 | 41 | guiGraphics.drawCenteredString(this.font, WARNING_0.getVisualOrderText(), width, height, -1); 42 | guiGraphics.drawCenteredString(this.font, WARNING_1.getVisualOrderText(), width, height + 10, -1); 43 | } 44 | 45 | @Override 46 | public void onClose() { 47 | Minecraft.getInstance().setScreen(this.parent); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/mixin/DebugEntryFpsMixin.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import dynamic_fps.impl.DynamicFPSMod; 6 | import dynamic_fps.impl.PowerState; 7 | import net.minecraft.client.gui.components.debug.DebugEntryFps; 8 | import net.minecraft.client.gui.components.debug.DebugScreenDisplayer; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | 12 | import java.util.Locale; 13 | 14 | @Mixin(DebugEntryFps.class) 15 | public class DebugEntryFpsMixin { 16 | /** 17 | * Add extra information to the FPS debug line when not focused. 18 | */ 19 | @WrapOperation( 20 | method = "display", 21 | at = @At( 22 | value = "INVOKE", 23 | target = "Lnet/minecraft/client/gui/components/debug/DebugScreenDisplayer;addPriorityLine(Ljava/lang/String;)V" 24 | ) 25 | ) 26 | private void display(DebugScreenDisplayer instance, String line, Operation original) { 27 | PowerState state = DynamicFPSMod.powerState(); 28 | 29 | if (state != PowerState.FOCUSED) { 30 | int target = DynamicFPSMod.targetFrameRate(); 31 | 32 | StringBuilder extra = new StringBuilder(); 33 | extra.append("§c ("); 34 | 35 | // When running below the actual target frame rate 36 | // Add rT (real Target) with the actual frame rate 37 | if (target < 15) { 38 | extra.append("rT: "); 39 | extra.append(target); 40 | extra.append(" "); 41 | } 42 | 43 | // Show which non-default power state is in effect 44 | extra.append("P: "); 45 | extra.append(state.toString().toLowerCase(Locale.ROOT)); 46 | 47 | extra.append(")§r"); 48 | line += extra.toString(); 49 | } 50 | 51 | original.call(instance, line); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/KeyMappingHandler.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util; 2 | 3 | import dynamic_fps.impl.DynamicFPSMod; 4 | import dynamic_fps.impl.service.Platform; 5 | import net.minecraft.client.KeyMapping; 6 | 7 | import com.mojang.blaze3d.platform.InputConstants; 8 | 9 | public final class KeyMappingHandler { 10 | private final KeyMapping keyMapping; 11 | private boolean isHoldingKey = false; 12 | private final PressHandler pressHandler; 13 | 14 | private static final KeyMappingHandler[] KEY_MAPPING_HANDLERS = { 15 | new KeyMappingHandler( 16 | Components.translationKey("key", "toggle_forced"), 17 | KeyMapping.Category.MISC, 18 | DynamicFPSMod::toggleForceLowFPS 19 | ), 20 | new KeyMappingHandler( 21 | Components.translationKey("key", "toggle_disabled"), 22 | KeyMapping.Category.MISC, 23 | DynamicFPSMod::toggleDisabled 24 | ) 25 | }; 26 | 27 | private KeyMappingHandler(String translationKey, KeyMapping.Category category, PressHandler pressHandler) { 28 | this.keyMapping = new KeyMapping( 29 | translationKey, 30 | InputConstants.Type.KEYSYM, 31 | InputConstants.UNKNOWN.getValue(), 32 | category 33 | ); 34 | 35 | this.pressHandler = pressHandler; 36 | Platform.getInstance().registerStartTickEvent(this::onStartTick); 37 | } 38 | 39 | public static KeyMappingHandler[] getHandlers() { 40 | return KEY_MAPPING_HANDLERS; 41 | } 42 | 43 | public KeyMapping keyMapping() { 44 | return this.keyMapping; 45 | } 46 | 47 | private void onStartTick() { 48 | if (this.keyMapping.isDown()) { 49 | if (!this.isHoldingKey) { 50 | this.pressHandler.handlePress(); 51 | } 52 | this.isHoldingKey = true; 53 | } else { 54 | this.isHoldingKey = false; 55 | } 56 | } 57 | 58 | @FunctionalInterface 59 | private interface PressHandler { 60 | void handlePress(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/mixin/MinecraftMixin.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; 4 | import com.mojang.blaze3d.platform.Window; 5 | import com.mojang.blaze3d.systems.CommandEncoder; 6 | import com.mojang.blaze3d.textures.GpuTexture; 7 | import dynamic_fps.impl.DynamicFPSMod; 8 | import dynamic_fps.impl.feature.state.IdleHandler; 9 | import net.minecraft.client.Minecraft; 10 | import net.minecraft.client.Options; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | 18 | @Mixin(Minecraft.class) 19 | public class MinecraftMixin { 20 | @Shadow 21 | @Final 22 | private Window window; 23 | 24 | @Shadow 25 | @Final 26 | public Options options; 27 | 28 | @Inject(method = "", at = @At("TAIL")) 29 | private void onInit(CallbackInfo callbackInfo) { 30 | DynamicFPSMod.init(); 31 | DynamicFPSMod.setWindow(this.window.handle()); 32 | } 33 | 34 | @Inject(method = "setScreen", at = @At("HEAD")) 35 | private void setScreen(CallbackInfo callbackInfo) { 36 | IdleHandler.onActivity(); 37 | } 38 | 39 | /** 40 | * Delay cleaning up the previously rendered frame until we are rendering another frame. 41 | */ 42 | @WrapWithCondition( 43 | method = "runTick", 44 | at = @At( 45 | value = "INVOKE", 46 | target = "Lcom/mojang/blaze3d/systems/CommandEncoder;clearColorAndDepthTextures(Lcom/mojang/blaze3d/textures/GpuTexture;ILcom/mojang/blaze3d/textures/GpuTexture;D)V" 47 | ) 48 | ) 49 | private boolean runTick(CommandEncoder instance, GpuTexture gpuTexture, int i, GpuTexture gpuTexture2, double v) { 50 | return DynamicFPSMod.checkForRender(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util; 2 | 3 | import com.google.gson.*; 4 | 5 | import java.lang.reflect.Type; 6 | import java.util.Locale; 7 | 8 | public class JsonUtil { 9 | @SuppressWarnings("deprecation") 10 | private static final Gson GSON = new GsonBuilder() 11 | .setLenient() 12 | .serializeNulls() 13 | .setPrettyPrinting() 14 | .enableComplexMapKeySerialization() 15 | .registerTypeHierarchyAdapter(Enum.class, new EnumSerializer<>()) 16 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 17 | .create(); 18 | 19 | public static String toJson(Object object) { 20 | return GSON.toJson(object); 21 | } 22 | 23 | public static String toJson(JsonElement element) { 24 | return GSON.toJson(element); 25 | } 26 | 27 | public static JsonElement toJsonTree(Object object) { 28 | return GSON.toJsonTree(object); 29 | } 30 | 31 | public static T fromJson(String data, Class type) { 32 | return GSON.fromJson(data, type); 33 | } 34 | 35 | public static T fromJson(JsonElement data, Class type) { 36 | return GSON.fromJson(data, type); 37 | } 38 | 39 | private static final class EnumSerializer> implements JsonSerializer, JsonDeserializer { 40 | @Override 41 | public JsonElement serialize(T instance, Type type, JsonSerializationContext context) { 42 | return new JsonPrimitive(instance.toString().toLowerCase(Locale.ROOT)); 43 | } 44 | 45 | @Override 46 | public T deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException { 47 | try { 48 | @SuppressWarnings("unchecked") 49 | Class class_ = (Class) Class.forName(type.getTypeName()); 50 | return Enum.valueOf(class_, element.getAsString().toUpperCase(Locale.ROOT)); 51 | } catch (ClassNotFoundException | IllegalArgumentException e) { 52 | throw new JsonParseException(e); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/mixin/OptionsMixin.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyReturnValue; 4 | import com.llamalad7.mixinextras.sugar.Local; 5 | import dynamic_fps.impl.DynamicFPSMod; 6 | import dynamic_fps.impl.config.option.GraphicsState; 7 | import dynamic_fps.impl.feature.state.OptionHolder; 8 | import dynamic_fps.impl.feature.volume.SmoothVolumeHandler; 9 | import net.minecraft.client.Minecraft; 10 | import net.minecraft.client.Options; 11 | import net.minecraft.sounds.SoundSource; 12 | import org.jetbrains.annotations.Nullable; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | 18 | @Mixin(Options.class) 19 | public abstract class OptionsMixin { 20 | // Reset runtime modified graphics settings to the user-specified defaults during saving. 21 | // Prevents the game from saving the reduced or minimal preset to disk and loading it again. 22 | @Inject(method = "save", at = @At("HEAD")) 23 | private void save0(CallbackInfo callbackInfo) { 24 | if (DynamicFPSMod.graphicsState() != GraphicsState.DEFAULT) { 25 | OptionHolder.applyOptions(Minecraft.getInstance().options, GraphicsState.DEFAULT); 26 | } 27 | } 28 | 29 | @Inject(method = "save", at = @At("RETURN")) 30 | private void save1(CallbackInfo callbackInfo) { 31 | if (DynamicFPSMod.graphicsState() != GraphicsState.DEFAULT) { 32 | OptionHolder.applyOptions(Minecraft.getInstance().options, DynamicFPSMod.graphicsState()); 33 | } 34 | } 35 | 36 | /** 37 | * Apply the volume multiplier to any newly-played sounds. 38 | */ 39 | @ModifyReturnValue(method = "getSoundSourceVolume", at = @At("RETURN")) 40 | private float getSoundSourceVolume(float value, @Local(argsOnly = true) @Nullable SoundSource source) { 41 | return value * SmoothVolumeHandler.volumeMultiplier(source); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/sv_se.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Konfigurera Dynamic FPS", 3 | 4 | "config.dynamic_fps.category.general": "Allmänt", 5 | "config.dynamic_fps.category.hovered": "Hovrande", 6 | "config.dynamic_fps.category.unfocused": "Ofokuserad", 7 | "config.dynamic_fps.category.abandoned": "Inaktiv", 8 | "config.dynamic_fps.category.invisible": "Osynlig", 9 | 10 | "config.dynamic_fps.disabled": "Inaktiverad", 11 | "config.dynamic_fps.minutes": "%d Minut(er)", 12 | 13 | "config.dynamic_fps.idle_time": "Inaktivitetstid", 14 | "config.dynamic_fps.idle_time_tooltip": "Minuter utan inmatning tills Dynamic FPS aktiverar Inaktivetesläget medans spelet är fokuserat.", 15 | 16 | "config.dynamic_fps.frame_rate_target": "Bildfrekvensmål", 17 | "config.dynamic_fps.volume_multiplier": "Volym Multiplikator", 18 | 19 | "config.dynamic_fps.graphics_state": "Bildskärmsinställningar", 20 | "config.dynamic_fps.graphics_state_default": "Standard", 21 | "config.dynamic_fps.graphics_state_reduced": "Reducerad", 22 | "config.dynamic_fps.graphics_state_minimal": "Minimalt", 23 | "config.dynamic_fps.graphics_state_minimal_tooltip": "Minimal grafik orsakar att världen laddar om!", 24 | 25 | "config.dynamic_fps.show_toasts": "Visa Toasts", 26 | "config.dynamic_fps.show_toasts_tooltip": "Om du vill fortsätta visa eller fördröja Toast aviseringar", 27 | 28 | "config.dynamic_fps.run_garbage_collector": "Starta GC", 29 | "config.dynamic_fps.run_garbage_collector_tooltip": "Frigör oanvänt minne när du växlar till denna status", 30 | 31 | "key.dynamic_fps.toggle_forced": "Tvinga Reducerad FPS (Växla)", 32 | "key.dynamic_fps.toggle_disabled": "Inaktivera Dynamic FPS (Växla)", 33 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: Tvingar Reducerad BPS", 34 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS Inaktiverad", 35 | 36 | "modmenu.descriptionTranslation.dynamic_fps": "Dynamiskt justera BPS så att Minecraft inte äter upp resurser i bakgrunden." 37 | } 38 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/compat/GLFW.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.compat; 2 | 3 | import dynamic_fps.impl.DynamicFPSMod; 4 | import dynamic_fps.impl.util.Threads; 5 | import dynamic_fps.impl.util.Version; 6 | import net.minecraft.client.Minecraft; 7 | 8 | public class GLFW { 9 | private static final boolean enterEventBroken = isEnterEventBroken(); 10 | 11 | /** 12 | * Apply a workaround for the cursor enter event not working if needed. 13 | * 14 | * This fixes an issue when running GLFW version 3.3.x or earlier where 15 | * the cursor enter event will only work if the window is not capturing 16 | * The mouse cursor. Since this is often not the case when switching windows 17 | * Dynamic FPS releases and captures the cursor in tandem with window focus. 18 | */ 19 | public static void applyWorkaround() { 20 | Minecraft minecraft = Minecraft.getInstance(); 21 | 22 | if (!useWorkaround()) { 23 | return; 24 | } 25 | 26 | if (DynamicFPSMod.getWindow() == null) { 27 | return; 28 | } 29 | 30 | if (!DynamicFPSMod.getWindow().isFocused()) { 31 | minecraft.mouseHandler.releaseMouse(); 32 | } else { 33 | // Grabbing the mouse only works while Minecraft 34 | // Agrees that the window is focused. The mod is 35 | // A little too fast for this, so we schedule it 36 | // For the next client tick (before next frame). 37 | Threads.runOnMainThread(minecraft.mouseHandler::grabMouse); 38 | } 39 | } 40 | 41 | private static boolean useWorkaround() { 42 | Minecraft minecraft = Minecraft.getInstance(); 43 | return enterEventBroken && minecraft.screen == null && !minecraft.options.pauseOnLostFocus; 44 | } 45 | 46 | private static boolean isEnterEventBroken() { 47 | Version active = getGLFWVersion(); 48 | return active.compareTo(Version.of(3, 3, 0)) < 0; // Versions before 3.3.0 are broken 49 | } 50 | 51 | private static Version getGLFWVersion() { 52 | int[] major = new int[1]; 53 | int[] minor = new int[1]; 54 | int[] patch = new int[1]; 55 | 56 | org.lwjgl.glfw.GLFW.glfwGetVersion(major, minor, patch); 57 | return Version.of(major[0], minor[0], patch[0]); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/config/BatteryTrackerConfig.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.config; 2 | 3 | import dynamic_fps.impl.config.option.BatteryIndicatorCondition; 4 | import dynamic_fps.impl.config.option.BatteryIndicatorPlacement; 5 | 6 | public class BatteryTrackerConfig { 7 | private boolean enabled; 8 | private int criticalLevel; 9 | private boolean switchStates; 10 | private boolean notifications; 11 | private boolean showWhenDebug; 12 | private DisplayConfig display; 13 | 14 | public boolean enabled() { 15 | return this.enabled; 16 | } 17 | 18 | public void setEnabled(boolean value) { 19 | this.enabled = value; 20 | } 21 | 22 | public int criticalLevel() { 23 | return this.criticalLevel; 24 | } 25 | 26 | public void setCriticalLevel(int value) { 27 | this.criticalLevel = value; 28 | } 29 | 30 | public boolean switchStates() { 31 | return this.switchStates; 32 | } 33 | 34 | public void setSwitchStates(boolean value) { 35 | this.switchStates = value; 36 | } 37 | 38 | public boolean notifications() { 39 | return this.notifications; 40 | } 41 | 42 | public void setNotifications(boolean value) { 43 | this.notifications = value; 44 | } 45 | 46 | public boolean showWhenDebug() { 47 | return this.showWhenDebug; 48 | } 49 | 50 | public void setShowWhenDebug(boolean value) { 51 | this.showWhenDebug = value; 52 | } 53 | 54 | public DisplayConfig display() { 55 | return this.display; 56 | } 57 | 58 | public static class DisplayConfig { 59 | private BatteryIndicatorCondition condition; 60 | private BatteryIndicatorPlacement placement; 61 | 62 | public BatteryIndicatorCondition condition() { 63 | return this.condition; 64 | } 65 | 66 | public void setCondition(BatteryIndicatorCondition value) { 67 | this.condition = value; 68 | } 69 | 70 | public BatteryIndicatorPlacement placement() { 71 | return this.placement; 72 | } 73 | 74 | public void setPlacement(BatteryIndicatorPlacement value) { 75 | this.placement = value; 76 | } 77 | 78 | public boolean isActive() { 79 | return this.condition != BatteryIndicatorCondition.DISABLED; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /platforms/neoforge/src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "[1,)" 3 | license = "MIT" 4 | issueTrackerURL = "https://github.com/juliand665/Dynamic-FPS/issues" 5 | 6 | [[mods]] 7 | modId = "dynamic_fps" 8 | namespace = "net.lostluma" 9 | version = "${version}" 10 | displayName = "Dynamic FPS" 11 | authors = "juliand665 & LostLuma" 12 | description = "Reduce resource usage while Minecraft is in the background or idle." 13 | logoFile = "dynamic_fps.png" 14 | displayTest = "IGNORE_ALL_VERSION" 15 | credits = """ 16 | Thanks to: 17 | Authors: 18 | juliand665 19 | LostLuma 20 | 21 | Contributors: 22 | altrisi 23 | DenaryDev 24 | dima-dencep 25 | EuropaYou 26 | N4TH4NOT 27 | parly 28 | Pixaurora 29 | Setadokalo 30 | Siphalor 31 | sisby-folk 32 | triphora 33 | 34 | Illustrators: 35 | Pixaurora 36 | 37 | Translators: 38 | AC19970 39 | Alexander317 40 | AlphaKR93 41 | Altegar 42 | atroniax 43 | BurrConnie 44 | DecafDawren 45 | egeesin 46 | EuropaYou 47 | Felix14-v2 48 | FITFC 49 | godkyo98 50 | GuNanOvO 51 | Hubry 52 | ImVietnam 53 | ishi-sama 54 | kau19an 55 | kyrtion 56 | Lucanoria 57 | LotuxPunk 58 | Madis0 59 | mpustovoi 60 | N4TH4NOT 61 | notlin4 62 | Q2297045667 63 | Pixaurora 64 | raspberrygitq 65 | Rhbarber 66 | RinixGG 67 | Samekichi 68 | Shihyeon 69 | StarmanMine142 70 | stijnvdkolk 71 | Taarek 72 | TheBossMagnus 73 | TheLegendofSaram 74 | wicivo 75 | XfedeX 76 | yichifauzi 77 | """ 78 | modUrl = "https://dapprgames.com/mods" 79 | displayURL = "https://dapprgames.com/mods" 80 | updateJSONURL = "https://api.lostluma.net/updates/dynamic-fps?platform=neoforge" 81 | 82 | [[mixins]] 83 | config = "dynamic_fps.mixins.json" 84 | 85 | [[mixins]] 86 | config = "dynamic_fps-common.mixins.json" 87 | 88 | [[accessTransformers]] 89 | file="META-INF/accesstransformer.cfg" 90 | 91 | [[dependencies.dynamic_fps]] 92 | modId = "minecraft" 93 | mandatory = true 94 | versionRange = "[1.21.9,)" 95 | ordering = "NONE" 96 | side = "CLIENT" 97 | -------------------------------------------------------------------------------- /platforms/forge/src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "[1,)" 3 | license = "MIT" 4 | issueTrackerURL = "https://github.com/juliand665/Dynamic-FPS/issues" 5 | 6 | [[mods]] 7 | modId = "dynamic_fps" 8 | namespace = "net.lostluma" 9 | version = "${version}" 10 | displayName = "Dynamic FPS" 11 | authors = "juliand665 & LostLuma" 12 | description = "Reduce resource usage while Minecraft is in the background or idle." 13 | logoFile = "assets/dynamic_fps/textures/icon.png" 14 | displayTest = "IGNORE_ALL_VERSION" 15 | credits = """ 16 | Thanks to: 17 | Authors: 18 | juliand665 19 | LostLuma 20 | 21 | Contributors: 22 | altrisi 23 | DenaryDev 24 | dima-dencep 25 | EuropaYou 26 | N4TH4NOT 27 | parly 28 | Pixaurora 29 | Setadokalo 30 | Siphalor 31 | sisby-folk 32 | triphora 33 | 34 | Illustrators: 35 | Pixaurora 36 | 37 | Translators: 38 | AC19970 39 | Alexander317 40 | AlphaKR93 41 | Altegar 42 | atroniax 43 | BurrConnie 44 | DecafDawren 45 | egeesin 46 | EuropaYou 47 | Felix14-v2 48 | FITFC 49 | godkyo98 50 | GuNanOvO 51 | Hubry 52 | ImVietnam 53 | ishi-sama 54 | kau19an 55 | kyrtion 56 | Lucanoria 57 | LotuxPunk 58 | Madis0 59 | mpustovoi 60 | N4TH4NOT 61 | notlin4 62 | Q2297045667 63 | Pixaurora 64 | raspberrygitq 65 | Rhbarber 66 | RinixGG 67 | Samekichi 68 | Shihyeon 69 | StarmanMine142 70 | stijnvdkolk 71 | Taarek 72 | TheBossMagnus 73 | TheLegendofSaram 74 | wicivo 75 | XfedeX 76 | yichifauzi 77 | """ 78 | modUrl = "https://dapprgames.com/mods" 79 | displayURL = "https://dapprgames.com/mods" 80 | updateJSONURL = "https://api.lostluma.net/updates/dynamic-fps?platform=forge" 81 | 82 | [[mixins]] 83 | config = "dynamic_fps.mixins.json" 84 | 85 | [[mixins]] 86 | config = "dynamic_fps-common.mixins.json" 87 | 88 | [[accessTransformers]] 89 | file="META-INF/accesstransformer.cfg" 90 | 91 | [[dependencies.dynamic_fps]] 92 | modId = "minecraft" 93 | mandatory = true 94 | versionRange = "[1.21.9,)" 95 | ordering = "NONE" 96 | side = "CLIENT" 97 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | minecraft = "1.21.11" 3 | 4 | # Platform libraries 5 | 6 | fabric_loader = "0.17.2" 7 | fabric_api = "0.133.7+1.21.9" 8 | 9 | forge = "1.21.5-55.0.3" 10 | 11 | neoforge = "21.11.0-beta" 12 | 13 | quilt_loader = "0.29.1" 14 | 15 | # Regular libraries 16 | 17 | battery = "1.3.0" 18 | 19 | # Modding libraries 20 | 21 | modmenu = "11.0.0-beta.1" 22 | cloth_config = "15.0.127" 23 | 24 | mixinextras = "0.4.1" 25 | 26 | architectury = "3.4.161" 27 | architectury_loom = "1.13.467" 28 | 29 | mod_publish = "0.8.4" 30 | 31 | [libraries] 32 | minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" } 33 | 34 | # Platform libraries 35 | 36 | fabric_loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric_loader" } 37 | # fabric_api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric_api" } 38 | 39 | forge = { module = "net.minecraftforge:forge", version.ref = "forge" } 40 | 41 | neoforge = { module = "net.neoforged:neoforge", version.ref = "neoforge" } 42 | 43 | quilt_loader = { module = "org.quiltmc:quilt-loader", version.ref = "quilt_loader" } 44 | 45 | # Regular libraries 46 | 47 | battery = { module = "net.lostluma:battery", version.ref = "battery" } 48 | 49 | # Modding libraries 50 | 51 | modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" } 52 | cloth_config = { module = "me.shedaniel.cloth:cloth-config", version.ref = "cloth_config" } 53 | 54 | mixinextras_common = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixinextras" } 55 | mixinextras_forge = { module = "io.github.llamalad7:mixinextras-forge", version.ref = "mixinextras" } 56 | 57 | architectury_plugin = { module = "architectury-plugin:architectury-plugin.gradle.plugin", version.ref = "architectury" } 58 | architectury_loom = { module = "dev.architectury.loom:dev.architectury.loom.gradle.plugin", version.ref = "architectury_loom" } 59 | 60 | [plugins] 61 | shadow = { id = "com.github.johnrengelman.shadow", version = "8.1.1" } 62 | architectury_loom = { id = "dev.architectury.loom", version.ref = "architectury_loom" } 63 | mod_publish_plugin = { id = "me.modmuss50.mod-publish-plugin", version.ref = "mod_publish" } 64 | -------------------------------------------------------------------------------- /platforms/forge/src/main/java/net/lostluma/dynamic_fps/impl/forge/DynamicFPSForgeMod.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.forge; 2 | 3 | import dynamic_fps.impl.Constants; 4 | import dynamic_fps.impl.DynamicFPSMod; 5 | import dynamic_fps.impl.service.Platform; 6 | import dynamic_fps.impl.util.KeyMappingHandler; 7 | import net.minecraftforge.client.ConfigScreenHandler; 8 | import net.minecraftforge.client.event.RegisterKeyMappingsEvent; 9 | import net.minecraftforge.common.MinecraftForge; 10 | import net.minecraftforge.event.TickEvent; 11 | import net.minecraftforge.fml.ModLoadingContext; 12 | import net.minecraftforge.fml.common.Mod; 13 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 14 | import net.minecraftforge.fml.loading.FMLLoader; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | @Mod(Constants.MOD_ID) 20 | public class DynamicFPSForgeMod { 21 | private static final List TICK_EVENT_LISTENERS = new ArrayList<>(); 22 | 23 | public DynamicFPSForgeMod(FMLJavaModLoadingContext context) { 24 | if (FMLLoader.getDist().isDedicatedServer()) { 25 | return; 26 | } 27 | 28 | context.registerExtensionPoint( 29 | ConfigScreenHandler.ConfigScreenFactory.class, 30 | () -> new ConfigScreenHandler.ConfigScreenFactory( 31 | (minecraft, screen) -> DynamicFPSMod.getConfigScreen(screen) 32 | ) 33 | ); 34 | 35 | MinecraftForge.EVENT_BUS.addListener(this::onClientTick); 36 | // MinecraftForge.EVENT_BUS.addListener(this::renderGuiOverlay); 37 | 38 | context.getModEventBus().addListener(this::registerKeyMappings); 39 | } 40 | 41 | /* 42 | public void renderGuiOverlay(RenderGuiOverlayEvent event) { 43 | HudInfoRenderer.renderInfo(event.getGuiGraphics()); 44 | } 45 | */ 46 | 47 | public void registerKeyMappings(RegisterKeyMappingsEvent event) { 48 | for (KeyMappingHandler handler : KeyMappingHandler.getHandlers()) { 49 | event.register(handler.keyMapping()); 50 | } 51 | } 52 | 53 | public void onClientTick(TickEvent.ClientTickEvent event) { 54 | for (Platform.StartTickEvent listener : TICK_EVENT_LISTENERS) { 55 | listener.onStartTick(); 56 | } 57 | } 58 | 59 | public static void addTickEventListener(Platform.StartTickEvent event) { 60 | TICK_EVENT_LISTENERS.add(event); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /platforms/forge/src/main/java/net/lostluma/dynamic_fps/impl/forge/service/ForgePlatform.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.forge.service; 2 | 3 | import dynamic_fps.impl.Constants; 4 | import dynamic_fps.impl.service.Platform; 5 | import dynamic_fps.impl.util.Version; 6 | import net.lostluma.dynamic_fps.impl.forge.DynamicFPSForgeMod; 7 | import net.minecraftforge.fml.ModContainer; 8 | import net.minecraftforge.fml.ModList; 9 | import net.minecraftforge.fml.loading.FMLLoader; 10 | import net.minecraftforge.fml.loading.FMLPaths; 11 | import net.minecraftforge.fml.loading.LoadingModList; 12 | 13 | import java.io.IOException; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.util.Optional; 17 | 18 | public class ForgePlatform implements Platform { 19 | @Override 20 | public String getName() { 21 | return "Forge"; 22 | } 23 | 24 | @Override 25 | public Path getCacheDir() { 26 | Path base = FMLPaths.GAMEDIR.get(); 27 | return this.ensureDir(base.resolve(".cache").resolve(Constants.MOD_ID)); 28 | } 29 | 30 | @Override 31 | public Path getConfigDir() { 32 | return FMLPaths.CONFIGDIR.get(); 33 | } 34 | 35 | @Override 36 | public boolean isDevelopmentEnvironment() { 37 | return !FMLLoader.isProduction(); 38 | } 39 | 40 | @Override 41 | public boolean isModLoaded(String modId) { 42 | return LoadingModList.get().getModFileById(modId) != null; 43 | } 44 | 45 | @Override 46 | public Optional getModVersion(String modId) { 47 | Optional optional = ModList.get().getModContainerById(modId); 48 | 49 | if (!optional.isPresent()) { 50 | return Optional.empty(); 51 | } 52 | 53 | String raw = optional.get().getModInfo().getVersion().toString(); 54 | 55 | try { 56 | return Optional.of(Version.of(raw)); 57 | } catch (Version.VersionParseException e) { 58 | throw new RuntimeException(e); 59 | } 60 | } 61 | 62 | @Override 63 | public void registerStartTickEvent(StartTickEvent event) { 64 | DynamicFPSForgeMod.addTickEventListener(event); 65 | } 66 | 67 | private Path ensureDir(Path path) { 68 | try { 69 | Files.createDirectories(path); 70 | } catch (IOException e) { 71 | throw new RuntimeException("Failed to create Dynamic FPS directory.", e); 72 | } 73 | 74 | return path; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /platforms/textile/src/main/java/net/lostluma/dynamic_fps/impl/textile/service/TextilePlatform.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.textile.service; 2 | 3 | import dynamic_fps.impl.Constants; 4 | import dynamic_fps.impl.service.Platform; 5 | import dynamic_fps.impl.util.Version; 6 | import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; 7 | import net.fabricmc.loader.api.FabricLoader; 8 | import net.fabricmc.loader.api.ModContainer; 9 | 10 | import java.io.IOException; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.util.Optional; 14 | 15 | public class TextilePlatform implements Platform { 16 | @Override 17 | public String getName() { 18 | return isModLoaded("quilt_loader") ? "Quilt" : "Fabric"; 19 | } 20 | 21 | @Override 22 | public Path getCacheDir() { 23 | Path base = FabricLoader.getInstance().getGameDir(); 24 | return this.ensureDir(base.resolve(".cache").resolve(Constants.MOD_ID)); 25 | } 26 | 27 | @Override 28 | public Path getConfigDir() { 29 | return FabricLoader.getInstance().getConfigDir(); 30 | } 31 | 32 | @Override 33 | public boolean isDevelopmentEnvironment() { 34 | return FabricLoader.getInstance().isDevelopmentEnvironment(); 35 | } 36 | 37 | @Override 38 | public boolean isModLoaded(String modId) { 39 | return FabricLoader.getInstance().isModLoaded(modId); 40 | } 41 | 42 | @Override 43 | public Optional getModVersion(String modId) { 44 | Optional optional = FabricLoader.getInstance().getModContainer(modId); 45 | 46 | if (!optional.isPresent()) { 47 | return Optional.empty(); 48 | } 49 | 50 | String raw = optional.get().getMetadata().getVersion().getFriendlyString(); 51 | 52 | try { 53 | return Optional.of(Version.of(raw)); 54 | } catch (Version.VersionParseException e) { 55 | throw new RuntimeException(e); 56 | } 57 | } 58 | 59 | @Override 60 | public void registerStartTickEvent(StartTickEvent event) { 61 | ClientTickEvents.START_CLIENT_TICK.register((minecraft) -> event.onStartTick()); 62 | } 63 | 64 | private Path ensureDir(Path path) { 65 | try { 66 | Files.createDirectories(path); 67 | } catch (IOException e) { 68 | throw new RuntimeException("Failed to create Dynamic FPS directory.", e); 69 | } 70 | 71 | return path; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/pl_pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Skonfiguruj Dynamic FPS", 3 | 4 | "config.dynamic_fps.category.general": "Ogólne", 5 | "config.dynamic_fps.category.hovered": "Zawieszone", 6 | "config.dynamic_fps.category.unfocused": "Bez koncentracji", 7 | "config.dynamic_fps.category.abandoned": "Bezczynne", 8 | "config.dynamic_fps.category.invisible": "Niewidzialne", 9 | 10 | "config.dynamic_fps.disabled": "Wyłączony", 11 | "config.dynamic_fps.minutes": "%d Minut(y)", 12 | 13 | "config.dynamic_fps.idle_time": "Czas bezczynności", 14 | "config.dynamic_fps.idle_time_tooltip": "Minuty bez wprowadzania danych, zanim Dynamic FPS aktywuje stan bezczynności, gdy gra jest skupiona.", 15 | 16 | "config.dynamic_fps.uncap_menu_frame_rate": "Odblokuj menu FPS", 17 | "config.dynamic_fps.uncap_menu_frame_rate_tooltip": "Usuń limit 60 FPS w menu głównym.", 18 | 19 | "config.dynamic_fps.frame_rate_target": "Docelowa liczba klatek na sekundę", 20 | "config.dynamic_fps.volume_multiplier": "Mnożnik głośności", 21 | 22 | "config.dynamic_fps.graphics_state": "Opcje graficzne", 23 | "config.dynamic_fps.graphics_state_default": "Domyślna", 24 | "config.dynamic_fps.graphics_state_reduced": "Zmniejszona", 25 | "config.dynamic_fps.graphics_state_minimal": "Minimalna", 26 | "config.dynamic_fps.graphics_state_minimal_tooltip": "Minimalna grafika powoduje przeładowanie świata!", 27 | 28 | "config.dynamic_fps.show_toasts": "Pokaż toasty", 29 | "config.dynamic_fps.show_toasts_tooltip": "Czy wyświetlać lub opóźniać powiadomienia o toastach", 30 | 31 | "config.dynamic_fps.run_garbage_collector": "Wywołaj Garbage Collector", 32 | "config.dynamic_fps.run_garbage_collector_tooltip": "Zwolnienie nieużywanej pamięci podczas przełączania do tego stanu.", 33 | 34 | "key.dynamic_fps.toggle_forced": "Wymuś obniżenie FPS (przełącznik)", 35 | "key.dynamic_fps.toggle_disabled": "Wyłącz Dynamic FPS (przełącznik)", 36 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: wymuszono obniżenie FPS", 37 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS wyłączone", 38 | 39 | "modmenu.descriptionTranslation.dynamic_fps": "Dynamicznie dostosowuje liczbę klatek na sekundę, dzięki czemu Minecraft nie blokuje zasobów w tle." 40 | } 41 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/en_ud.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "SԀℲ ɔᴉɯɐuʎᗡ ǝɹnɓᴉɟuoƆ", 3 | 4 | "config.dynamic_fps.category.general": "ʅɐɹǝuǝ⅁", 5 | "config.dynamic_fps.category.hovered": "pǝɹǝʌoH", 6 | "config.dynamic_fps.category.unfocused": "pǝsnɔoɟu∩", 7 | "config.dynamic_fps.category.abandoned": "ǝʅpI", 8 | "config.dynamic_fps.category.invisible": "ǝʅqᴉsᴉʌuI", 9 | 10 | "config.dynamic_fps.enabled": "pǝʅqɐuƎ", 11 | 12 | "config.dynamic_fps.disabled": "pǝʅqɐsᴉᗡ", 13 | "config.dynamic_fps.minutes": "(s)ǝʇnuᴉW p%", 14 | 15 | "config.dynamic_fps.idle_time": "ǝɯᴉʇ ǝʅpI", 16 | "config.dynamic_fps.idle_time_tooltip": "·pǝsnɔoɟ sᴉ ǝɯɐɓ ǝɥʇ ǝʅᴉɥʍ ǝʇɐʇs ǝʅpI ǝɥʇ sǝʇɐʌᴉʇɔɐ SԀℲ ɔᴉɯɐuʎᗡ ʅᴉʇun ʇnduᴉ ʇnoɥʇᴉʍ sǝʇnuᴉW", 17 | 18 | "config.dynamic_fps.uncap_menu_frame_rate": "SԀℲ nuǝW dɐɔu∩", 19 | "config.dynamic_fps.uncap_menu_frame_rate_tooltip": "·nuǝɯ uᴉɐɯ ǝɥʇ uᴉ ʇᴉɯᴉʅ SԀℲ 09 ǝɥʇ ǝʌoɯǝꓤ", 20 | 21 | "config.dynamic_fps.frame_rate_target": "ʇǝɓɹɐ⟘ ǝʇɐᴚ ǝɯɐɹℲ", 22 | "config.dynamic_fps.volume_multiplier": "ɹǝᴉʅdᴉʇʅnW ǝɯnʅoɅ", 23 | 24 | "config.dynamic_fps.graphics_state": "suoᴉʇdO sɔᴉɥdɐɹ⅁", 25 | "config.dynamic_fps.graphics_state_default": "ʇʅnɐɟǝᗡ", 26 | "config.dynamic_fps.graphics_state_reduced": "pǝɔnpǝᴚ", 27 | "config.dynamic_fps.graphics_state_minimal": "ʅɐɯᴉuᴉW", 28 | "config.dynamic_fps.graphics_state_minimal_tooltip": "¡pɐoʅǝɹ oʇ pʅɹoʍ ǝɥʇ ǝsnɐɔ sɔᴉɥdɐɹɓ ʅɐɯᴉuᴉW", 29 | 30 | "config.dynamic_fps.show_toasts": "sʇsɐo⟘ ʍoɥS", 31 | "config.dynamic_fps.show_toasts_tooltip": "suoᴉʇɐɔᴉɟᴉʇou ʇsɐoʇ ʎɐʅǝp ɹo ɓuᴉʎɐʅdsᴉp dǝǝʞ oʇ ɹǝɥʇǝɥM", 32 | 33 | "config.dynamic_fps.run_garbage_collector": "ɹoʇɔǝʅʅoƆ ǝɓɐqɹɐ⅁ ǝʞoʌuI", 34 | "config.dynamic_fps.run_garbage_collector_tooltip": "snʇɐʇs sᴉɥʇ oʇ ɓuᴉɥɔʇᴉʍs uǝɥʍ ʎɹoɯǝɯ pǝsnun dn ǝǝɹℲ", 35 | 36 | "key.dynamic_fps.toggle_forced": "(ǝʅɓɓo⟘) ǝpoW pǝsnɔoɟu∩ ǝɔɹoℲ", 37 | "key.dynamic_fps.toggle_disabled": "(ǝʅɓɓo⟘) SԀℲ ɔᴉɯɐuʎᗡ ǝʅqɐsᴉᗡ", 38 | "gui.dynamic_fps.hud.reducing": "SԀℲ pǝɔnpǝꓤ ɓuᴉɔɹoℲ :SԀℲ ɔᴉɯɐuʎᗡ", 39 | "gui.dynamic_fps.hud.disabled": "pǝʅqɐsᴉᗡ SԀℲ ɔᴉɯɐuʎᗡ", 40 | 41 | "modmenu.nameTranslation.dynamic_fps": "SԀℲ ɔᴉɯɐuʎᗡ", 42 | "modmenu.descriptionTranslation.dynamic_fps": "·punoɹɓʞɔɐq uᴉ sǝɔɹnosǝɹ ɓoɥ ʇˌusǝop ʇɟɐɹɔǝuᴉW os SԀℲ sʇsnꓩpɐ ʎʅʅɐɔᴉɯɐuʎᗡ" 43 | } 44 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/lol_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Configure Dynamik MPS (meows/s)", 3 | 4 | "config.dynamic_fps.category.general": "Generlol", 5 | "config.dynamic_fps.category.hovered": "hoverd", 6 | "config.dynamic_fps.category.unfocused": "distractd", 7 | "config.dynamic_fps.category.abandoned": "away frum catboard", 8 | "config.dynamic_fps.category.invisible": "inviz", 9 | 10 | "config.dynamic_fps.enabled": "on,", 11 | 12 | "config.dynamic_fps.disabled": "off.", 13 | "config.dynamic_fps.minutes": "%d minit(z)", 14 | 15 | "config.dynamic_fps.idle_time": "afc time", 16 | "config.dynamic_fps.idle_time_tooltip": "minitz witowt playing befor Dynamik MPS activatez the away frum catboard mode while da game is focused.", 17 | 18 | "config.dynamic_fps.uncap_menu_frame_rate": "Uncat Menu MPS", 19 | "config.dynamic_fps.uncap_menu_frame_rate_tooltip": "No moar 60 FPS limit in the main menu!", 20 | 21 | "config.dynamic_fps.frame_rate_target": "Meow Rate Target", 22 | "config.dynamic_fps.volume_multiplier": "loudness", 23 | 24 | "config.dynamic_fps.graphics_state": "Grefix Opshuns", 25 | "config.dynamic_fps.graphics_state_default": "Regulr", 26 | "config.dynamic_fps.graphics_state_reduced": "Redoosed", 27 | "config.dynamic_fps.graphics_state_minimal": "low", 28 | "config.dynamic_fps.graphics_state_minimal_tooltip": "low grefix causez da worl to reload!", 29 | 30 | "config.dynamic_fps.show_toasts": "sho breadz", 31 | "config.dynamic_fps.show_toasts_tooltip": "shoul keep dizplay or wait to sho bread notificationz??", 32 | 33 | "config.dynamic_fps.run_garbage_collector": "Invoke Litter Collector", 34 | "config.dynamic_fps.run_garbage_collector_tooltip": "Free up unused memoriez when swap to le state", 35 | 36 | "key.dynamic_fps.toggle_forced": "force distractd mode (Toggle)", 37 | "key.dynamic_fps.toggle_disabled": "turn off Dynamik MPS (Toggle)", 38 | "gui.dynamic_fps.hud.reducing": "Dynamik MPS: forcin' redoosed mps", 39 | "gui.dynamic_fps.hud.disabled": "Dynamik MPS turnd off", 40 | 41 | "modmenu.nameTranslation.dynamic_fps": "Dynamik MPS", 42 | "modmenu.descriptionTranslation.dynamic_fps": "Dynamikally adjusts mps (meows/s) so Minceraft doesn't eet all da resourcez in background." 43 | } 44 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/feature/state/ClickIgnoreHandler.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.feature.state; 2 | 3 | import dynamic_fps.impl.config.DynamicFPSConfig; 4 | import dynamic_fps.impl.config.option.IgnoreInitialClick; 5 | import net.minecraft.client.Minecraft; 6 | import org.lwjgl.glfw.GLFW; 7 | import org.lwjgl.glfw.GLFWMouseButtonCallback; 8 | import org.lwjgl.glfw.GLFWWindowFocusCallback; 9 | 10 | import java.time.Instant; 11 | 12 | public class ClickIgnoreHandler { 13 | private final long address; 14 | private long focusedAt; 15 | 16 | private final GLFWWindowFocusCallback previousFocusCallback; 17 | private final GLFWMouseButtonCallback previousClickCallback; 18 | 19 | public ClickIgnoreHandler(long address) { 20 | this.address = address; 21 | 22 | this.previousFocusCallback = GLFW.glfwSetWindowFocusCallback(this.address, this::onFocusChanged); 23 | this.previousClickCallback = GLFW.glfwSetMouseButtonCallback(this.address, this::onMouseClicked); 24 | } 25 | 26 | public static boolean isFeatureActive() { 27 | return DynamicFPSConfig.INSTANCE.ignoreInitialClick() != IgnoreInitialClick.DISABLED; 28 | } 29 | 30 | private boolean shouldIgnoreClick() { 31 | Minecraft minecraft = Minecraft.getInstance(); 32 | IgnoreInitialClick config = DynamicFPSConfig.INSTANCE.ignoreInitialClick(); 33 | 34 | if (config == IgnoreInitialClick.DISABLED) { 35 | return false; 36 | } 37 | 38 | if (config == IgnoreInitialClick.IN_WORLD && minecraft.screen != null) { 39 | return false; 40 | } 41 | 42 | return this.focusedAt + 10 >= Instant.now().toEpochMilli(); 43 | } 44 | 45 | private void onFocusChanged(long address, boolean focused) { 46 | if (this.isCurrentWindow(address) && focused) { 47 | this.focusedAt = Instant.now().toEpochMilli(); 48 | } 49 | 50 | if (this.previousFocusCallback != null) { 51 | this.previousFocusCallback.invoke(address, focused); 52 | } 53 | } 54 | 55 | private void onMouseClicked(long window, int button, int action, int mods) { 56 | if (this.isCurrentWindow(window) && shouldIgnoreClick()) { 57 | return; 58 | } 59 | 60 | if (this.previousClickCallback != null) { 61 | this.previousClickCallback.invoke(window, button, action, mods); 62 | } 63 | } 64 | 65 | private boolean isCurrentWindow(long address) { 66 | return address == this.address; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /platforms/neoforge/src/main/java/net/lostluma/dynamic_fps/impl/neoforge/service/NeoForgePlatform.java: -------------------------------------------------------------------------------- 1 | package net.lostluma.dynamic_fps.impl.neoforge.service; 2 | 3 | import dynamic_fps.impl.Constants; 4 | import dynamic_fps.impl.service.Platform; 5 | import dynamic_fps.impl.util.Version; 6 | import net.neoforged.fml.ModContainer; 7 | import net.neoforged.fml.ModList; 8 | import net.neoforged.fml.loading.FMLLoader; 9 | import net.neoforged.fml.loading.FMLPaths; 10 | import net.neoforged.neoforge.client.event.ClientTickEvent; 11 | import net.neoforged.neoforge.common.NeoForge; 12 | 13 | import java.io.IOException; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.util.Optional; 17 | 18 | public class NeoForgePlatform implements Platform { 19 | @Override 20 | public String getName() { 21 | return "NeoForge"; 22 | } 23 | 24 | @Override 25 | public Path getCacheDir() { 26 | Path base = FMLPaths.GAMEDIR.get(); 27 | return this.ensureDir(base.resolve(".cache").resolve(Constants.MOD_ID)); 28 | } 29 | 30 | @Override 31 | public Path getConfigDir() { 32 | return FMLPaths.CONFIGDIR.get(); 33 | } 34 | 35 | @Override 36 | public boolean isDevelopmentEnvironment() { 37 | return !FMLLoader.getCurrent().isProduction(); 38 | } 39 | 40 | @Override 41 | public boolean isModLoaded(String modId) { 42 | return FMLLoader.getCurrent().getLoadingModList().getModFileById(modId) != null; 43 | } 44 | 45 | @Override 46 | public Optional getModVersion(String modId) { 47 | Optional optional = ModList.get().getModContainerById(modId); 48 | 49 | if (!optional.isPresent()) { 50 | return Optional.empty(); 51 | } 52 | 53 | String raw = optional.get().getModInfo().getVersion().toString(); 54 | 55 | try { 56 | return Optional.of(Version.of(raw)); 57 | } catch (Version.VersionParseException e) { 58 | throw new RuntimeException(e); 59 | } 60 | } 61 | 62 | @Override 63 | public void registerStartTickEvent(StartTickEvent event) { 64 | NeoForge.EVENT_BUS.addListener(ClientTickEvent.Pre.class, (unused) -> event.onStartTick()); 65 | } 66 | 67 | private Path ensureDir(Path path) { 68 | try { 69 | Files.createDirectories(path); 70 | } catch (IOException e) { 71 | throw new RuntimeException("Failed to create Dynamic FPS directory.", e); 72 | } 73 | 74 | return path; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/feature/state/OptionHolder.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.feature.state; 2 | 3 | import dynamic_fps.impl.config.option.GraphicsState; 4 | import net.minecraft.client.CloudStatus; 5 | import net.minecraft.client.GraphicsPreset; 6 | import net.minecraft.client.Options; 7 | import net.minecraft.server.level.ParticleStatus; 8 | 9 | /* 10 | * Helper for saving, overriding, and re-applying vanilla options. 11 | * 12 | * Different power states may be configured to use different graphics settings. 13 | */ 14 | public class OptionHolder { 15 | private static CloudStatus cloudStatus; 16 | private static GraphicsPreset graphicsStatus; 17 | private static boolean ambientOcclusion; 18 | private static ParticleStatus particlesStatus; 19 | private static boolean entityShadows; 20 | private static double entityDistance; 21 | 22 | /* 23 | * Create an in-memory copy of current vanilla graphics options. 24 | * 25 | * This MUST be called while graphics options have not been changed yet. 26 | */ 27 | public static void copyOptions(Options options) { 28 | cloudStatus = options.getCloudsType(); 29 | graphicsStatus = options.graphicsPreset().get(); 30 | ambientOcclusion = options.ambientOcclusion().get(); 31 | particlesStatus = options.particles().get(); 32 | entityShadows = options.entityShadows().get(); 33 | entityDistance = options.entityDistanceScaling().get(); 34 | } 35 | 36 | /* 37 | * Apply or revert the graphics options for the specified graphics state. 38 | */ 39 | public static void applyOptions(Options options, GraphicsState state) { 40 | if (state == GraphicsState.DEFAULT) { 41 | options.cloudStatus().set(cloudStatus); 42 | options.graphicsPreset().set(graphicsStatus); 43 | options.ambientOcclusion().set(ambientOcclusion); 44 | options.particles().set(particlesStatus); 45 | options.entityShadows().set(entityShadows); 46 | options.entityDistanceScaling().set(entityDistance); 47 | } else { // state == GraphicsState.REDUCED 48 | options.cloudStatus().set(CloudStatus.OFF); 49 | options.particles().set(ParticleStatus.MINIMAL); 50 | options.entityShadows().set(false); 51 | options.entityDistanceScaling().set(0.5); 52 | 53 | if (state == GraphicsState.MINIMAL) { 54 | options.graphicsPreset().set(GraphicsPreset.FAST); 55 | options.ambientOcclusion().set(false); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/data/default_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "uncap_menu_frame_rate": false, 4 | "ignore_initial_click": "disabled", 5 | "idle": { 6 | "timeout": 300, 7 | "condition": "vanilla" 8 | }, 9 | "battery_tracker": { 10 | "enabled": false, 11 | "critical_level": 10, 12 | "switch_states": false, 13 | "notifications": true, 14 | "show_when_debug": false, 15 | "display": { 16 | "condition": "critical", 17 | "placement": "top_left" 18 | } 19 | }, 20 | "volume_transition_speed": { 21 | "up": 1.0, 22 | "down": 0.5 23 | }, 24 | "states": { 25 | "hovered": { 26 | "frame_rate_target": 60, 27 | "enable_vsync": "false", 28 | "volume_multipliers": {}, 29 | "graphics_state": "default", 30 | "show_toasts": true, 31 | "run_garbage_collector": false 32 | }, 33 | "unfocused": { 34 | "frame_rate_target": 1, 35 | "enable_vsync": "false", 36 | "volume_multipliers": { 37 | "master": 0.25 38 | }, 39 | "graphics_state": "default", 40 | "show_toasts": false, 41 | "run_garbage_collector": false 42 | }, 43 | "invisible": { 44 | "frame_rate_target": 0, 45 | "enable_vsync": "false", 46 | "volume_multipliers": { 47 | "master": 0.0 48 | }, 49 | "graphics_state": "default", 50 | "show_toasts": false, 51 | "run_garbage_collector": false 52 | }, 53 | "unplugged": { 54 | "frame_rate_target": -1, 55 | "enable_vsync": "true", 56 | "volume_multipliers": {}, 57 | "graphics_state": "default", 58 | "show_toasts": true, 59 | "run_garbage_collector": false 60 | }, 61 | "abandoned": { 62 | "frame_rate_target": 10, 63 | "enable_vsync": "false", 64 | "volume_multipliers": {}, 65 | "graphics_state": "default", 66 | "show_toasts": false, 67 | "run_garbage_collector": false 68 | } 69 | }, 70 | "download_natives": true, 71 | "mock_battery_data": false 72 | } 73 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/VariableStepTransformer.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Comparator; 5 | import java.util.List; 6 | 7 | /** 8 | * Allows transforming a value into steps for e.g. a UI slider and back. 9 | * The transformer can handle different value ranges having different step sizes. 10 | * 11 | * @author Lilly Rose Berner, Pixaurora 12 | */ 13 | public class VariableStepTransformer { 14 | private boolean unsorted; 15 | private final List steps; 16 | 17 | public VariableStepTransformer() { 18 | this.steps = new ArrayList<>(); 19 | } 20 | 21 | /** 22 | * Add a new transformation step. 23 | * 24 | * @param change The current step size. 25 | * @param max The maximum value using this step size. 26 | */ 27 | public void addStep(int change, int max) { 28 | this.unsorted = true; 29 | this.steps.add(new Step(change, max)); 30 | } 31 | 32 | /** 33 | * Convert a value to its corresponding step. 34 | */ 35 | public int toStep(int value) { 36 | if (this.unsorted) { 37 | this.sortSteps(); 38 | } 39 | 40 | int step = 0; 41 | 42 | int currentChange = 0; 43 | int currentValue = value; 44 | 45 | for (Step pair : this.steps.reversed()) { 46 | if (currentValue > pair.max && currentChange != 0) { 47 | step += Math.floorDiv(currentValue - pair.max, currentChange); 48 | currentValue = pair.max; 49 | } 50 | 51 | currentChange = pair.change; 52 | } 53 | 54 | step += Math.floorDiv(currentValue, currentChange); 55 | 56 | return step; 57 | } 58 | 59 | /** 60 | * Convert a step to its corresponding value. 61 | */ 62 | public int toValue(int step) { 63 | if (this.unsorted) { 64 | this.sortSteps(); 65 | } 66 | 67 | int value = 0; 68 | int currentStep = 0; 69 | 70 | for (Step pair : this.steps) { 71 | int stepsTaken = Math.min(Math.floorDiv((pair.max - value), pair.change), step - currentStep); 72 | 73 | value += stepsTaken * pair.change; 74 | currentStep += stepsTaken; 75 | } 76 | 77 | return value; 78 | } 79 | 80 | private void sortSteps() { 81 | this.unsorted = false; 82 | 83 | this.steps.sort(new Comparator() { 84 | @Override 85 | public int compare(Step self, Step other) { 86 | return Integer.compare(self.max, other.max); 87 | } 88 | }); 89 | } 90 | 91 | private record Step(int change, int max) {} 92 | } 93 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/es_mx.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Configurar Dynamic FPS", 3 | 4 | "config.dynamic_fps.category.general": "General", 5 | "config.dynamic_fps.category.hovered": "Sobre", 6 | "config.dynamic_fps.category.unfocused": "Desenfocado", 7 | "config.dynamic_fps.category.abandoned": "Inactivo", 8 | "config.dynamic_fps.category.invisible": "Invisible", 9 | 10 | "config.dynamic_fps.enabled": "Activado", 11 | 12 | "config.dynamic_fps.disabled": "Desactivado", 13 | "config.dynamic_fps.minutes": "%d Minuto(s)", 14 | 15 | "config.dynamic_fps.idle_time": "Tiempo de inactividad", 16 | "config.dynamic_fps.idle_time_tooltip": "Minutos sin actividad hasta que Dynamic FPS active el estado de Inactividad mientras el juego está enfocado.", 17 | 18 | "config.dynamic_fps.uncap_menu_frame_rate": "Liberar los FPS en Menu", 19 | "config.dynamic_fps.uncap_menu_frame_rate_tooltip": "Remueve el limite de 60 FPS en el menú principal", 20 | 21 | "config.dynamic_fps.frame_rate_target": "Objetivo de Velocidad de Fotogramas", 22 | "config.dynamic_fps.volume_multiplier": "Multiplicador de Volumen", 23 | 24 | "config.dynamic_fps.graphics_state": "Opciones Gráficas", 25 | "config.dynamic_fps.graphics_state_default": "Por defecto", 26 | "config.dynamic_fps.graphics_state_reduced": "Reducidos", 27 | "config.dynamic_fps.graphics_state_minimal": "Mínimos", 28 | "config.dynamic_fps.graphics_state_minimal_tooltip": "¡Los gráficos mínimos hacen que el mundo se vuelva a cargar!", 29 | 30 | "config.dynamic_fps.show_toasts": "Mostrar Notificaciones", 31 | "config.dynamic_fps.show_toasts_tooltip": "Si seguir mostrando o retrasar las notificaciones del sistema.", 32 | 33 | "config.dynamic_fps.run_garbage_collector": "Ejecutar el Garbage Collector", 34 | "config.dynamic_fps.run_garbage_collector_tooltip": "Libera memoria sin usar al cambiar a este estado", 35 | 36 | "key.dynamic_fps.toggle_forced": "Forzar el modo desenfocado (Alternar)", 37 | "key.dynamic_fps.toggle_disabled": "Deshabilitar Dynamic FPS (Alternar)", 38 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: Forzando FPS reducidos", 39 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS Deshabilitado", 40 | 41 | "modmenu.descriptionTranslation.dynamic_fps": "Ajusta dinámicamente los FPS para que Minecraft no acapare recursos en segundo plano." 42 | } 43 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/en_pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Dynamic Anchor Code o' Conduct", 3 | 4 | "config.dynamic_fps.category.general": "Main Deck", 5 | "config.dynamic_fps.category.hovered": "Makin' Chase", 6 | "config.dynamic_fps.category.unfocused": "Abandoned Ship", 7 | "config.dynamic_fps.category.abandoned": "Nappin' Below Deck", 8 | "config.dynamic_fps.category.invisible": "Lost in Davy Jones' Locker", 9 | 10 | "config.dynamic_fps.enabled": "Merry Sailin'", 11 | 12 | "config.dynamic_fps.disabled": "Avast!", 13 | "config.dynamic_fps.minutes": "%d Minut'(s)", 14 | 15 | "config.dynamic_fps.idle_time": "Time Before Nappin'", 16 | "config.dynamic_fps.idle_time_tooltip": "Minut's without workin' on deck before Dynamic Anchor declares you are Nappin' Below Deck.", 17 | 18 | "config.dynamic_fps.uncap_menu_frame_rate": "Banish th' Menu Speed Limit?", 19 | "config.dynamic_fps.uncap_menu_frame_rate_tooltip": "Makes th' 60-knots Speed Limit in th' main menu walk th' plank.", 20 | 21 | "config.dynamic_fps.frame_rate_target": "Speed Limit Target", 22 | "config.dynamic_fps.volume_multiplier": "Noise Multiplier", 23 | 24 | "config.dynamic_fps.graphics_state": "Spyglass Lens", 25 | "config.dynamic_fps.graphics_state_default": "Fair Condition", 26 | "config.dynamic_fps.graphics_state_reduced": "Budget'd", 27 | "config.dynamic_fps.graphics_state_minimal": "Blurry", 28 | "config.dynamic_fps.graphics_state_minimal_tooltip": "A blurry lens causes th' sea to be remapped!", 29 | 30 | "config.dynamic_fps.show_toasts": "Notify th' Captain", 31 | "config.dynamic_fps.show_toasts_tooltip": "Notify th' Captain", 32 | 33 | "config.dynamic_fps.run_garbage_collector": "Order th' Ship Janitor to Swab th' Deck", 34 | "config.dynamic_fps.run_garbage_collector_tooltip": "Frees up ship resources when switching to th' state.", 35 | 36 | "key.dynamic_fps.toggle_forced": "Lower th' Anchor (Switch)", 37 | "key.dynamic_fps.toggle_disabled": "Dynamic Anchor, walk th' plank! (Switch)", 38 | "gui.dynamic_fps.hud.reducing": "Dynamic Anchor: Lowerin' th' anchor", 39 | "gui.dynamic_fps.hud.disabled": "Avast, be th' Dynamic Anchor! Sailing at Full Mast", 40 | 41 | "modmenu.nameTranslation.dynamic_fps": "Dynamic Anchor", 42 | "modmenu.descriptionTranslation.dynamic_fps": "Magically edits yer Ship's speed limit so Minecraft doesn't steal yer booty when yer not lookin'." 43 | } 44 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/enws.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Changing Wishes for Changing FPS", 3 | 4 | "config.dynamic_fps.category.general": "Humblest Wishes", 5 | "config.dynamic_fps.category.hovered": "A Call'd Upon Mood", 6 | "config.dynamic_fps.category.unfocused": "Busied Mood", 7 | "config.dynamic_fps.category.abandoned": "Absent of Mind", 8 | "config.dynamic_fps.category.invisible": "A Mood Yet Unseen", 9 | 10 | "config.dynamic_fps.enabled": "Beest Active", 11 | 12 | "config.dynamic_fps.disabled": "Unactive", 13 | "config.dynamic_fps.minutes": "%d Minute(s)", 14 | 15 | "config.dynamic_fps.idle_time": "Absent time", 16 | "config.dynamic_fps.idle_time_tooltip": "There is a special mood, / If you be away; / Thou present in all but mind.", 17 | 18 | "config.dynamic_fps.uncap_menu_frame_rate": "No Limit to Menu FPS", 19 | "config.dynamic_fps.uncap_menu_frame_rate_tooltip": "I prithee, give no limits to my FPS. I am a menu, and privileged to render.", 20 | 21 | "config.dynamic_fps.frame_rate_target": "Frame Rate's Aim", 22 | "config.dynamic_fps.volume_multiplier": "Volume Multiplying Factors", 23 | 24 | "config.dynamic_fps.graphics_state": "Thy Wishes of Visages", 25 | "config.dynamic_fps.graphics_state_default": "Exquisite", 26 | "config.dynamic_fps.graphics_state_reduced": "Dull", 27 | "config.dynamic_fps.graphics_state_minimal": "Quite degraded", 28 | "config.dynamic_fps.graphics_state_minimal_tooltip": "Then I degraded from normal graphics, / And come now to reload the lands!", 29 | 30 | "config.dynamic_fps.show_toasts": "See Letters", 31 | "config.dynamic_fps.show_toasts_tooltip": "Shall thy messenger send toasty letters or await your return?", 32 | 33 | "config.dynamic_fps.run_garbage_collector": "Give Call to Garbage Collector", 34 | "config.dynamic_fps.run_garbage_collector_tooltip": "Degrade unused words into nothing when gaining a mood thus.", 35 | 36 | "key.dynamic_fps.toggle_forced": "Order Busied Mood (Change)", 37 | "key.dynamic_fps.toggle_disabled": "Stop Changing FPS (Change)", 38 | "gui.dynamic_fps.hud.reducing": "Changing FPS: Diminishing thy FPS", 39 | "gui.dynamic_fps.hud.disabled": "Changing FPS Unactive", 40 | 41 | "modmenu.nameTranslation.dynamic_fps": "Changing FPS", 42 | "modmenu.descriptionTranslation.dynamic_fps": "The seasons alter: Machination-usage and FPS / Fall in the fresh mind of the calmed user;" 43 | } 44 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/feature/volume/SmoothVolumeHandler.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.feature.volume; 2 | 3 | import dynamic_fps.impl.DynamicFPSMod; 4 | import dynamic_fps.impl.config.DynamicFPSConfig; 5 | import dynamic_fps.impl.config.VolumeTransitionConfig; 6 | import dynamic_fps.impl.service.Platform; 7 | import dynamic_fps.impl.util.duck.DuckSoundEngine; 8 | import net.minecraft.client.Minecraft; 9 | import net.minecraft.sounds.SoundSource; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class SmoothVolumeHandler { 15 | private static boolean active = false; 16 | private static boolean needsUpdating = false; 17 | 18 | private static final Map currentOverrides = new HashMap<>(); 19 | 20 | public static void init() { 21 | if (active || !DynamicFPSConfig.INSTANCE.volumeTransitionSpeed().isActive()) { 22 | return; 23 | } 24 | 25 | active = true; 26 | Platform.getInstance().registerStartTickEvent(SmoothVolumeHandler::tickVolumes); 27 | } 28 | 29 | public static void onStateChange() { 30 | if (active) { 31 | needsUpdating = true; 32 | } else { 33 | for (SoundSource source : SoundSource.values()) { 34 | updateVolume(source); 35 | } 36 | } 37 | } 38 | 39 | public static float volumeMultiplier(SoundSource source) { 40 | if (!active) { 41 | return DynamicFPSMod.volumeMultiplier(source); 42 | } else { 43 | return currentOverrides.getOrDefault(source, 1.0f); 44 | } 45 | } 46 | 47 | private static void tickVolumes() { 48 | if (!needsUpdating) { 49 | return; 50 | } 51 | 52 | boolean didUpdate = false; 53 | VolumeTransitionConfig config = DynamicFPSConfig.INSTANCE.volumeTransitionSpeed(); 54 | 55 | for (SoundSource source : SoundSource.values()) { 56 | float desired = DynamicFPSMod.volumeMultiplier(source); 57 | float current = currentOverrides.getOrDefault(source, 1.0f); 58 | 59 | if (current != desired) { 60 | didUpdate = true; 61 | 62 | if (current < desired) { 63 | currentOverrides.put(source, Math.min(desired, current + config.getUp() / 20.0f)); 64 | } else { 65 | currentOverrides.put(source, Math.max(desired, current - config.getDown() / 20.0f)); 66 | } 67 | 68 | updateVolume(source); 69 | } 70 | } 71 | 72 | if (!didUpdate) { 73 | needsUpdating = false; 74 | } 75 | } 76 | 77 | private static void updateVolume(SoundSource source) { 78 | // Update volume of currently playing sounds 79 | Minecraft minecraft = Minecraft.getInstance(); 80 | ((DuckSoundEngine) minecraft.getSoundManager().soundEngine).dynamic_fps$updateVolume(source); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.config; 2 | 3 | import java.util.Map; 4 | 5 | import com.google.gson.annotations.SerializedName; 6 | import dynamic_fps.impl.PowerState; 7 | import dynamic_fps.impl.config.option.IgnoreInitialClick; 8 | 9 | public final class DynamicFPSConfig { 10 | private boolean enabled; 11 | private boolean uncapMenuFrameRate; 12 | private IgnoreInitialClick ignoreInitialClick; 13 | private IdleConfig idle; 14 | private BatteryTrackerConfig batteryTracker; 15 | private VolumeTransitionConfig volumeTransitionSpeed; 16 | private boolean downloadNatives; 17 | private boolean mockBatteryData; 18 | 19 | @SerializedName("states") 20 | private Map configs; 21 | 22 | public static final DynamicFPSConfig DEFAULTS; 23 | public static final DynamicFPSConfig INSTANCE; 24 | 25 | static { 26 | DEFAULTS = Serialization.loadDefault(); 27 | INSTANCE = Serialization.loadPersonalized(); 28 | 29 | for (Map.Entry entry: INSTANCE.configs.entrySet()) { 30 | entry.getValue().state = entry.getKey(); 31 | } 32 | } 33 | 34 | public Config get(PowerState state) { 35 | if (state == PowerState.FOCUSED) { 36 | return Config.ACTIVE; 37 | } else { 38 | return configs.get(state); 39 | } 40 | } 41 | 42 | public boolean enabled() { 43 | return this.enabled; 44 | } 45 | 46 | public void setEnabled(boolean value) { 47 | this.enabled = value; 48 | } 49 | 50 | public IdleConfig idle() { 51 | return this.idle; 52 | } 53 | 54 | public BatteryTrackerConfig batteryTracker() { 55 | return this.batteryTracker; 56 | } 57 | 58 | public VolumeTransitionConfig volumeTransitionSpeed() { 59 | return this.volumeTransitionSpeed; 60 | } 61 | 62 | public boolean uncapMenuFrameRate() { 63 | return this.uncapMenuFrameRate; 64 | } 65 | 66 | public void setUncapMenuFrameRate(boolean value) { 67 | this.uncapMenuFrameRate = value; 68 | } 69 | 70 | public IgnoreInitialClick ignoreInitialClick() { 71 | return this.ignoreInitialClick; 72 | } 73 | 74 | public void setIgnoreInitialClick(IgnoreInitialClick value) { 75 | this.ignoreInitialClick = value; 76 | } 77 | 78 | public boolean downloadNatives() { 79 | return this.downloadNatives; 80 | } 81 | 82 | public void setDownloadNatives(boolean value) { 83 | this.downloadNatives = value; 84 | } 85 | 86 | public boolean mockBatteryData() { 87 | return this.mockBatteryData; 88 | } 89 | 90 | public void setMockBatteryData(boolean value) { 91 | this.mockBatteryData = value; 92 | } 93 | 94 | public void save() { 95 | Serialization.save(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/HudInfoRenderer.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util; 2 | 3 | import dynamic_fps.impl.config.BatteryTrackerConfig; 4 | import dynamic_fps.impl.config.DynamicFPSConfig; 5 | import dynamic_fps.impl.feature.battery.BatteryTracker; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.gui.GuiGraphics; 8 | import net.minecraft.client.renderer.RenderPipelines; 9 | import net.minecraft.network.chat.Component; 10 | 11 | import net.minecraft.resources.Identifier; 12 | 13 | import dynamic_fps.impl.DynamicFPSMod; 14 | 15 | public final class HudInfoRenderer { 16 | public static void renderInfo(GuiGraphics guiGraphics) { 17 | Minecraft minecraft = Minecraft.getInstance(); 18 | 19 | if (minecraft.options.hideGui || minecraft.screen != null) { 20 | return; 21 | } 22 | 23 | if (DynamicFPSConfig.INSTANCE.batteryTracker().enabled()) { 24 | drawBatteryOverlay(guiGraphics); 25 | } 26 | 27 | if (DynamicFPSMod.disabledByUser()) { 28 | drawCenteredText(guiGraphics, Components.translatable("gui", "hud.disabled")); 29 | } else if (DynamicFPSMod.isForcingLowFPS()) { 30 | drawCenteredText(guiGraphics, Components.translatable("gui", "hud.reducing")); 31 | } 32 | } 33 | 34 | private static void drawCenteredText(GuiGraphics guiGraphics, Component component) { 35 | int width = guiGraphics.guiWidth() / 2; 36 | Minecraft minecraft = Minecraft.getInstance(); 37 | 38 | guiGraphics.drawCenteredString(minecraft.font, component, width, 32, -1); 39 | } 40 | 41 | private static void drawBatteryOverlay(GuiGraphics graphics) { 42 | Minecraft minecraft = Minecraft.getInstance(); 43 | BatteryTrackerConfig config = DynamicFPSConfig.INSTANCE.batteryTracker(); 44 | 45 | if ((!config.showWhenDebug() && minecraft.debugEntries.isOverlayVisible()) || !BatteryTracker.hasBatteries()) { 46 | return; 47 | } 48 | 49 | if (!config.display().condition().isConditionMet()) { 50 | return; 51 | } 52 | 53 | int index = BatteryTracker.charge() / 10; 54 | String type = BatteryUtil.isCharging(BatteryTracker.status()) ? "charging" : "draining"; 55 | Identifier icon = ResourceLocations.of("dynamic_fps", "textures/battery/icon/" + type + "_" + index + ".png"); 56 | 57 | // pair of coordinates 58 | int[] position = config.display().placement().get(minecraft.getWindow()); 59 | 60 | // resource, x, y, z, ?, ?, width, height, width, height 61 | graphics.blit(RenderPipelines.GUI_TEXTURED, icon, position[0], position[1], 0.0f, 0, 16, 16, 16, 16); 62 | // font, text, x, y, text color 63 | graphics.drawString(minecraft.font, BatteryTracker.charge() + "%", position[0] + 20, position[1] + 4, -1); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/feature/state/WindowObserver.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.feature.state; 2 | 3 | import org.lwjgl.glfw.GLFW; 4 | import org.lwjgl.glfw.GLFWCursorEnterCallback; 5 | import org.lwjgl.glfw.GLFWWindowFocusCallback; 6 | import org.lwjgl.glfw.GLFWWindowIconifyCallback; 7 | 8 | import dynamic_fps.impl.DynamicFPSMod; 9 | 10 | import java.time.Instant; 11 | 12 | public class WindowObserver { 13 | private final long address; 14 | 15 | private boolean isFocused = true; 16 | private final GLFWWindowFocusCallback previousFocusCallback; 17 | 18 | private boolean isHovered = true; 19 | private final GLFWCursorEnterCallback previousMouseCallback; 20 | 21 | private boolean isIconified = false; 22 | private final GLFWWindowIconifyCallback previousIconifyCallback; 23 | 24 | public WindowObserver(long address) { 25 | this.address = address; 26 | 27 | this.previousFocusCallback = GLFW.glfwSetWindowFocusCallback(this.address, this::onFocusChanged); 28 | this.previousMouseCallback = GLFW.glfwSetCursorEnterCallback(this.address, this::onMouseChanged); 29 | 30 | // Vanilla doesn't use this (currently), other mods might register this callback though ... 31 | this.previousIconifyCallback = GLFW.glfwSetWindowIconifyCallback(this.address, this::onIconifyChanged); 32 | } 33 | 34 | private boolean isCurrentWindow(long address) { 35 | return address == this.address; 36 | } 37 | 38 | public long address() { 39 | return this.address; 40 | } 41 | 42 | public boolean isFocused() { 43 | return this.isFocused; 44 | } 45 | 46 | private void onFocusChanged(long address, boolean focused) { 47 | if (this.isCurrentWindow(address)) { 48 | this.isFocused = focused; 49 | DynamicFPSMod.onStatusChanged(true); 50 | } 51 | 52 | if (this.previousFocusCallback != null) { 53 | this.previousFocusCallback.invoke(address, focused); 54 | } 55 | } 56 | 57 | public boolean isHovered() { 58 | return this.isHovered; 59 | } 60 | 61 | private void onMouseChanged(long address, boolean hovered) { 62 | if (this.isCurrentWindow(address)) { 63 | this.isHovered = hovered; 64 | DynamicFPSMod.onStatusChanged(true); 65 | } 66 | 67 | if (this.previousMouseCallback != null) { 68 | this.previousMouseCallback.invoke(address, hovered); 69 | } 70 | } 71 | 72 | public boolean isIconified() { 73 | return this.isIconified; 74 | } 75 | 76 | private void onIconifyChanged(long address, boolean iconified) { 77 | if (this.isCurrentWindow(address)) { 78 | this.isIconified = iconified; 79 | DynamicFPSMod.onStatusChanged(true); 80 | } 81 | 82 | if (this.previousIconifyCallback != null) { 83 | this.previousIconifyCallback.invoke(address, iconified); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/feature/battery/BaseToast.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.feature.battery; 2 | 3 | import dynamic_fps.impl.util.ResourceLocations; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.gui.Font; 6 | import net.minecraft.client.gui.GuiGraphics; 7 | import net.minecraft.client.gui.components.toasts.Toast; 8 | import net.minecraft.client.gui.components.toasts.ToastManager; 9 | import net.minecraft.client.renderer.RenderPipelines; 10 | import net.minecraft.network.chat.Component; 11 | import net.minecraft.resources.Identifier; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | public class BaseToast implements Toast { 16 | private long firstRender; 17 | private Visibility visibility; 18 | 19 | protected Component title; 20 | protected Component description; 21 | protected @Nullable Identifier icon; 22 | 23 | private static final Identifier MOD_ICON = ResourceLocations.of("dynamic_fps", "textures/battery/toast/background_icon.png"); 24 | private static final Identifier BACKGROUND_IMAGE = ResourceLocations.of("dynamic_fps", "textures/battery/toast/background.png"); 25 | 26 | protected BaseToast(Component title, Component description, @Nullable Identifier icon) { 27 | this.title = title; 28 | this.description = description; 29 | 30 | this.icon = icon; 31 | 32 | this.visibility = Visibility.SHOW; 33 | } 34 | 35 | @Override 36 | public @NotNull Visibility getWantedVisibility() { 37 | return this.visibility; 38 | } 39 | 40 | @Override 41 | public void update(ToastManager toastManager, long currentTime) { 42 | if (this.firstRender == 0) { 43 | return; 44 | } 45 | 46 | if (currentTime - this.firstRender >= 5000.0 * toastManager.getNotificationDisplayTimeMultiplier()) { 47 | this.visibility = Visibility.HIDE; 48 | } 49 | } 50 | 51 | @Override 52 | public void render(GuiGraphics graphics, Font font, long currentTime) { 53 | if (this.firstRender == 0) { 54 | this.onFirstRender(); 55 | this.firstRender = currentTime; 56 | } 57 | 58 | // type, resource, x, y, ?, ?, width, height, width, height 59 | graphics.blit(RenderPipelines.GUI_TEXTURED, BACKGROUND_IMAGE, 0, 0, 0.0f, 0, this.width(), this.height(), this.width(), this.height()); 60 | 61 | int x = 8; 62 | 63 | if (this.icon != null) { 64 | x += 22; 65 | 66 | graphics.blit(RenderPipelines.GUI_TEXTURED, MOD_ICON, 2, 2, 0.0f, 0, 8, 8, 8, 8); 67 | graphics.blit(RenderPipelines.GUI_TEXTURED, this.icon, 8, 8, 0.0f, 0, 16, 16, 16, 16); 68 | } 69 | 70 | graphics.drawString(Minecraft.getInstance().font, this.title, x, 7, 0xff5f3315, false); 71 | graphics.drawString(Minecraft.getInstance().font, this.description, x, 18, -16777216, false); 72 | } 73 | 74 | public void onFirstRender() {} 75 | } 76 | -------------------------------------------------------------------------------- /platforms/fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "dynamic_fps", 4 | "version": "${version}", 5 | "name": "Dynamic FPS", 6 | "description": "Reduce resource usage while Minecraft is in the background or idle.", 7 | "authors": [ 8 | "juliand665", 9 | "LostLuma" 10 | ], 11 | "contributors": [ 12 | "AC19970", 13 | "Alexander317", 14 | "AlphaKR93", 15 | "Altegar", 16 | "altrisi", 17 | "atroniax", 18 | "BurrConnie", 19 | "DecafDawren", 20 | "DenaryDev", 21 | "dima-dencep", 22 | "egeesin", 23 | "EuropaYou", 24 | "Felix14-v2", 25 | "FITFC", 26 | "godkyo98", 27 | "GuNanOvO", 28 | "Hubry", 29 | "ImVietnam", 30 | "ishi-sama", 31 | "kau19an", 32 | "kyrtion", 33 | "Lucanoria", 34 | "LotuxPunk", 35 | "Madis0", 36 | "mpustovoi", 37 | "N4TH4NOT", 38 | "notlin4", 39 | "parly", 40 | "Pixaurora", 41 | "Q2297045667", 42 | "raspberrygitq", 43 | "Rhbarber", 44 | "RinixGG", 45 | "Samekichi", 46 | "Setadokalo", 47 | "Shihyeon", 48 | "Siphalor", 49 | "sisby-folk", 50 | "StarmanMine142", 51 | "stijnvdkolk", 52 | "Taarek", 53 | "TheBossMagnus", 54 | "TheLegendofSaram", 55 | "triphora", 56 | "wicivo", 57 | "XfedeX", 58 | "yichifauzi" 59 | ], 60 | "contact": { 61 | "homepage": "https://dapprgames.com/mods", 62 | "issues": "https://github.com/juliand665/Dynamic-FPS/issues", 63 | "sources": "https://github.com/juliand665/Dynamic-FPS" 64 | }, 65 | "license": "MIT", 66 | "icon": "assets/dynamic_fps/textures/icon.png", 67 | "environment": "client", 68 | "entrypoints": { 69 | "client": [ 70 | "net.lostluma.dynamic_fps.impl.textile.compat.FREX" 71 | ], 72 | "modmenu": [ 73 | "net.lostluma.dynamic_fps.impl.textile.compat.ModMenu" 74 | ] 75 | }, 76 | "depends": { 77 | "fabricloader": ">=0.15.10", 78 | "minecraft": ">=1.21.9-beta.1", 79 | "mixinextras": ">=0.3.2", 80 | "fabric-resource-loader-v0": "*", 81 | "fabric-lifecycle-events-v1": "*" 82 | }, 83 | "conflicts": { 84 | "optifabric": "*", 85 | "smoothmenu": "*" 86 | }, 87 | "recommends": { 88 | "modmenu": "*", 89 | "cloth-config": "*" 90 | }, 91 | "mixins": [ 92 | "dynamic_fps.mixins.json", 93 | "dynamic_fps-common.mixins.json", 94 | "dynamic_fps-textile.mixins.json" 95 | ], 96 | "accessWidener": "dynamic_fps.accesswidener" 97 | } 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # Dynamic FPS 6 | 7 | Reduce resource usage while Minecraft is in the background or idle. 8 | 9 | ## Features 10 | 11 | Dynamic FPS will detect whether the Minecraft window is currently active, being hovered over, unfocused, or invisible. 12 | For each of these states you're able to adjust the frame rate, volume, and whether [toast notifications](https://minecraft.wiki/w/Toasts) are temporarily paused. 13 | 14 | You can also configure these settings for when you're idling (with a custom timeout) or while your laptop / handheld is on battery. 15 | Optionally you may also display the current battery status on the in-game HUD and receive toast notifications about battery activity. 16 | 17 | --- 18 | 19 | In addition to this Dynamic FPS fixes a vanilla bug causing higher-than-necessary background CPU usage and stops 20 | rendering the world while it's being obscured by resource loading overlay, helping especially on low-end systems. 21 | 22 | ## Installation 23 | 24 | Dynamic FPS is available for download on [GitHub](https://github.com/juliand665/Dynamic-FPS/releases), [Modrinth](https://modrinth.com/mod/dynamic-fps), and [CurseForge](https://www.curseforge.com/minecraft/mc-mods/dynamic-fps). 25 | To access the in-game config screen you'll also need to install [Mod Menu](https://modrinth.com/mod/modmenu) and [Cloth Config](https://modrinth.com/mod/cloth-config). 26 | 27 | ## Frequently Asked Questions 28 | 29 | - Why is Minecraft still running at 15 FPS? 30 | 31 | Dynamic FPS will only slow the client render loop to a minimum of 15 cycles per second. 32 | Lower frame rates are achieved by then cancelling the rendering of all superfluous frames, e.g. 14 out of 15 frames are cancelled for 1 FPS. 33 | 34 | This lets you resume playing almost instantly after switching back to the game: 35 | Instead of having to wait for up to a second until the next rendered frame comes along, the game checks back within 1/15th of a second. 36 | 37 | ## Disclaimer 38 | 39 | Enabling the battery integration requires downloading an additional library at runtime. 40 | The mod contains the hashes for these files ahead of time and will verify them before usage. 41 | 42 | You may disable this behavior in the mod's settings, or [install the library yourself](docs/manual-natives-install.md) if you wish. 43 | 44 | ## License 45 | 46 | Dynamic FPS' code and translations are available under the [MIT license](LICENSE). 47 | Other assets included in this repository may not be released under an open source license. 48 | 49 | ## Developer Info 50 | 51 | If Dynamic FPS' optimizations conflict with a feature of your mod you can request to disable them. 52 | The process of doing so is as simple as adding some additional metadata Dynamic FPS reads to your mod metadata. 53 | 54 | **Disable the loading overlay optimization:** 55 | 56 | Fabric / Quilt: 57 | 58 | ```json 59 | "dynamic_fps": { 60 | "optimized_overlay": false 61 | }, 62 | ``` 63 | 64 | Forge / NeoForge 65 | 66 | ```toml 67 | [modproperties.your_mod_id] 68 | dynamic_fps = {optimized_overlay = false} 69 | ``` 70 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /platforms/forge/build.gradle: -------------------------------------------------------------------------------- 1 | import me.modmuss50.mpp.ReleaseType 2 | import net.fabricmc.loom.task.RemapJarTask 3 | 4 | plugins { 5 | id("dynamic_fps.module") 6 | alias(libs.plugins.shadow) 7 | alias(libs.plugins.mod.publish.plugin) 8 | } 9 | 10 | architectury { 11 | platformSetupLoomIde() 12 | forge() 13 | } 14 | 15 | loom { 16 | accessWidenerPath = project(":platforms:common").loom.accessWidenerPath 17 | 18 | forge { 19 | convertAccessWideners = true 20 | extraAccessWideners.add loom.accessWidenerPath.get().asFile.name 21 | 22 | mixinConfig "dynamic_fps.mixins.json" 23 | mixinConfig "dynamic_fps-common.mixins.json" 24 | } 25 | } 26 | 27 | configurations { 28 | common 29 | shadowCommon // Don't use shadow from the shadow plugin since it *excludes* files. 30 | compileClasspath.extendsFrom common 31 | runtimeClasspath.extendsFrom common 32 | developmentForge.extendsFrom common 33 | } 34 | 35 | dependencies { 36 | forge libs.forge 37 | 38 | include(libs.battery) 39 | 40 | compileOnly(libs.mixinextras.common) 41 | annotationProcessor(libs.mixinextras.common) 42 | 43 | include(libs.mixinextras.forge) 44 | implementation(libs.mixinextras.forge) 45 | 46 | common(project(path: ":platforms:common", configuration: "namedElements")) { transitive = false } 47 | shadowCommon(project(path: ":platforms:common", configuration: "transformProductionForge")) { transitive = false } 48 | } 49 | 50 | shadowJar { 51 | exclude "fabric.mod.json" 52 | exclude "architectury.common.json" 53 | 54 | configurations = [project.configurations.shadowCommon] 55 | } 56 | 57 | remapJar { 58 | input.set shadowJar.archiveFile 59 | dependsOn shadowJar 60 | } 61 | 62 | sourcesJar { 63 | def commonSources = project(":platforms:common").sourcesJar 64 | dependsOn commonSources 65 | from commonSources.archiveFile.map { zipTree(it) } 66 | } 67 | 68 | components.java { 69 | withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { 70 | skip() 71 | } 72 | } 73 | 74 | var modVersion = project.property("mod_version").toString() 75 | 76 | publishMods { 77 | version = modVersion 78 | displayName = "v${modVersion}" 79 | 80 | type = modVersion.contains("-") ? ReleaseType.BETA : ReleaseType.STABLE; 81 | modLoaders.addAll(project.property("supported_platforms").toString().split(",")) 82 | 83 | changelog = file(rootDir.toPath().resolve("changelog.txt")).text 84 | file = tasks.withType(RemapJarTask).named("remapJar").get().archiveFile 85 | additionalFiles.from(tasks.withType(Jar).named("sourcesJar").get().archiveFile) 86 | 87 | curseforge { 88 | accessToken = providers.environmentVariable("CURSEFORGE_SECRET") 89 | projectId = "335493" 90 | 91 | minecraftVersionRange { 92 | start = project.property("minecraft_version_min").toString() 93 | end = project.property("minecraft_version_max").toString() 94 | } 95 | 96 | clientRequired = true 97 | serverRequired = false 98 | 99 | optional("cloth-config") 100 | } 101 | 102 | modrinth { 103 | accessToken = providers.environmentVariable("MODRINTH_SECRET") 104 | projectId = "LQ3K71Q1" 105 | 106 | minecraftVersionRange { 107 | start = project.property("minecraft_version_min").toString() 108 | end = project.property("minecraft_version_max").toString() 109 | } 110 | 111 | optional("cloth-config") 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/feature/state/IdleHandler.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.feature.state; 2 | 3 | import dynamic_fps.impl.DynamicFPSMod; 4 | import dynamic_fps.impl.config.DynamicFPSConfig; 5 | import dynamic_fps.impl.config.option.IdleCondition; 6 | import dynamic_fps.impl.feature.battery.BatteryTracker; 7 | import dynamic_fps.impl.service.Platform; 8 | import net.lostluma.battery.api.State; 9 | import net.minecraft.client.Minecraft; 10 | import net.minecraft.util.Util; 11 | import net.minecraft.world.entity.player.Player; 12 | import net.minecraft.world.phys.Vec3; 13 | import org.jetbrains.annotations.Nullable; 14 | import org.lwjgl.glfw.GLFW; 15 | import org.lwjgl.glfw.GLFWCursorPosCallback; 16 | 17 | public class IdleHandler { 18 | private static boolean active = false; 19 | private static boolean wasIdle = false; 20 | 21 | private static long previousActivity = 0L; 22 | 23 | private static Vec3 prevPosition = Vec3.ZERO; 24 | private static Vec3 prevLookAngle = Vec3.ZERO; 25 | 26 | private static @Nullable GLFWCursorPosCallback previousCursorPosCallback; 27 | 28 | public static void init() { 29 | if (active) { 30 | return; 31 | } 32 | 33 | DynamicFPSConfig config = DynamicFPSConfig.INSTANCE; 34 | 35 | if (config.idle().timeout() == 0) { 36 | return; 37 | } 38 | 39 | if (config.idle().condition() == IdleCondition.ON_BATTERY && !BatteryTracker.hasBatteries()) { 40 | return; 41 | } 42 | 43 | active = true; 44 | 45 | if (DynamicFPSMod.getWindow() != null) { 46 | setWindow(DynamicFPSMod.getWindow().address()); 47 | } 48 | 49 | Platform.getInstance().registerStartTickEvent(IdleHandler::checkActivity); 50 | } 51 | 52 | public static void setWindow(long address) { 53 | if (active) { 54 | previousCursorPosCallback = GLFW.glfwSetCursorPosCallback(address, IdleHandler::onMove); 55 | } 56 | } 57 | 58 | public static void onActivity() { 59 | previousActivity = Util.getEpochMillis(); 60 | } 61 | 62 | public static boolean isIdle() { 63 | DynamicFPSConfig config = DynamicFPSConfig.INSTANCE; 64 | 65 | if (config.idle().timeout() == 0) { 66 | return false; 67 | } 68 | 69 | if (config.idle().condition() == IdleCondition.ON_BATTERY && !(BatteryTracker.status() == State.DISCHARGING)) { 70 | return false; 71 | } 72 | 73 | return (Util.getEpochMillis() - previousActivity) >= (long) config.idle().timeout() * 1000; 74 | } 75 | 76 | private static void checkActivity() { 77 | checkPlayerActivity(); 78 | 79 | boolean idle = isIdle(); 80 | 81 | if (idle != wasIdle) { 82 | wasIdle = idle; 83 | DynamicFPSMod.onStatusChanged(!idle); 84 | } 85 | } 86 | 87 | private static void checkPlayerActivity() { 88 | Player player = Minecraft.getInstance().player; 89 | 90 | if (player == null) { 91 | return; 92 | } 93 | 94 | Vec3 position = player.position(); 95 | Vec3 lookAngle = player.getLookAngle(); 96 | 97 | if (!position.equals(prevPosition) || !lookAngle.equals(prevLookAngle)) { 98 | onActivity(); 99 | } 100 | 101 | prevPosition = position; 102 | prevLookAngle = lookAngle; 103 | } 104 | 105 | // Mouse events 106 | 107 | private static void onMove(long address, double x, double y) { 108 | onActivity(); 109 | 110 | if (previousCursorPosCallback != null) { 111 | previousCursorPosCallback.invoke(address, x, y); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /platforms/neoforge/build.gradle: -------------------------------------------------------------------------------- 1 | import me.modmuss50.mpp.ReleaseType 2 | import net.fabricmc.loom.task.RemapJarTask 3 | 4 | plugins { 5 | id("dynamic_fps.module") 6 | alias(libs.plugins.shadow) 7 | alias(libs.plugins.mod.publish.plugin) 8 | } 9 | 10 | architectury { 11 | platformSetupLoomIde() 12 | neoForge() 13 | } 14 | 15 | loom { 16 | accessWidenerPath = project(":platforms:common").loom.accessWidenerPath 17 | } 18 | 19 | configurations { 20 | common 21 | shadowCommon // Don't use shadow from the shadow plugin since it *excludes* files. 22 | compileClasspath.extendsFrom common 23 | runtimeClasspath.extendsFrom common 24 | developmentForge.extendsFrom common 25 | } 26 | 27 | repositories { 28 | maven { 29 | name = 'NeoForged' 30 | url = 'https://maven.neoforged.net/releases' 31 | } 32 | } 33 | 34 | dependencies { 35 | neoForge libs.neoforge 36 | 37 | include(libs.battery) 38 | 39 | common(project(path: ":platforms:common", configuration: "namedElements")) { transitive = false } 40 | shadowCommon(project(path: ":platforms:common", configuration: "transformProductionNeoForge")) { transitive = false } 41 | } 42 | 43 | shadowJar { 44 | exclude "fabric.mod.json" 45 | exclude "architectury.common.json" 46 | 47 | exclude "assets/textures/dynamic_fps/icon.png" 48 | 49 | // Sue me 50 | // Fixes crash in prod from our GameRenderer mixin 51 | // Idk anything about NeoForge, if you do explain. 52 | exclude "dynamic-fps-platforms_common-refmap.json" 53 | 54 | configurations = [project.configurations.shadowCommon] 55 | } 56 | 57 | remapJar { 58 | input.set shadowJar.archiveFile 59 | dependsOn shadowJar 60 | 61 | injectAccessWidener.set(true) 62 | atAccessWideners.add("dynamic_fps.accesswidener") 63 | } 64 | 65 | sourcesJar { 66 | def commonSources = project(":platforms:common").sourcesJar 67 | dependsOn commonSources 68 | from commonSources.archiveFile.map { zipTree(it) } 69 | } 70 | 71 | components.java { 72 | withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { 73 | skip() 74 | } 75 | } 76 | 77 | var modVersion = project.property("mod_version").toString() 78 | 79 | publishMods { 80 | version = modVersion 81 | displayName = "v${modVersion}" 82 | 83 | type = modVersion.contains("-") ? ReleaseType.BETA : ReleaseType.STABLE; 84 | modLoaders.addAll(project.property("supported_platforms").toString().split(",")) 85 | 86 | changelog = file(rootDir.toPath().resolve("changelog.txt")).text 87 | file = tasks.withType(RemapJarTask).named("remapJar").get().archiveFile 88 | additionalFiles.from(tasks.withType(Jar).named("sourcesJar").get().archiveFile) 89 | 90 | curseforge { 91 | accessToken = providers.environmentVariable("CURSEFORGE_SECRET") 92 | projectId = "335493" 93 | 94 | minecraftVersionRange { 95 | start = project.property("minecraft_version_min").toString() 96 | end = project.property("minecraft_version_max").toString() 97 | } 98 | 99 | clientRequired = true 100 | serverRequired = false 101 | 102 | optional("cloth-config") 103 | } 104 | 105 | modrinth { 106 | accessToken = providers.environmentVariable("MODRINTH_SECRET") 107 | projectId = "LQ3K71Q1" 108 | 109 | minecraftVersionRange { 110 | start = project.property("minecraft_version_min").toString() 111 | end = project.property("minecraft_version_max").toString() 112 | } 113 | 114 | optional("cloth-config") 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/config/Config.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.config; 2 | 3 | import java.util.HashMap; 4 | import java.util.Locale; 5 | import java.util.Map; 6 | 7 | import dynamic_fps.impl.Constants; 8 | import dynamic_fps.impl.PowerState; 9 | import dynamic_fps.impl.config.option.GraphicsState; 10 | import net.minecraft.sounds.SoundSource; 11 | 12 | public class Config { 13 | private int frameRateTarget; 14 | private boolean enableVsync; 15 | private final Map volumeMultipliers; 16 | private GraphicsState graphicsState; 17 | private boolean showToasts; 18 | private boolean runGarbageCollector; 19 | 20 | protected transient PowerState state; // Set by main config, allows retrieving values from the default power state config 21 | 22 | public static final Config ACTIVE = new Config(-1, false, new HashMap<>(), GraphicsState.DEFAULT, true, false); 23 | 24 | public Config(int frameRateTarget, boolean enableVsync, Map volumeMultipliers, GraphicsState graphicsState, boolean showToasts, boolean runGarbageCollector) { 25 | this.frameRateTarget = frameRateTarget; 26 | this.enableVsync = enableVsync; 27 | this.volumeMultipliers = new HashMap<>(volumeMultipliers); // Ensure the map is mutable 28 | this.graphicsState = graphicsState; 29 | this.showToasts = showToasts; 30 | this.runGarbageCollector = runGarbageCollector; 31 | } 32 | 33 | public int frameRateTarget() { 34 | if (this.frameRateTarget != -1) { 35 | return this.frameRateTarget; 36 | } else { 37 | return Constants.NO_FRAME_RATE_LIMIT; 38 | } 39 | } 40 | 41 | public void setFrameRateTarget(int value) { 42 | if (value == Constants.NO_FRAME_RATE_LIMIT) { 43 | this.frameRateTarget = -1; 44 | } else { 45 | this.frameRateTarget = value; 46 | } 47 | } 48 | 49 | public boolean enableVsync() { 50 | return this.enableVsync; 51 | } 52 | 53 | public void setEnableVsync(boolean value) { 54 | this.enableVsync = value; 55 | } 56 | 57 | public float volumeMultiplier(SoundSource source) { 58 | if (this.rawVolumeMultiplier(SoundSource.MASTER) == 0.0f) { 59 | return 0.0f; 60 | } else { 61 | return this.rawVolumeMultiplier(source); 62 | } 63 | } 64 | 65 | public float rawVolumeMultiplier(SoundSource source) { 66 | String key = soundSourceName(source); 67 | return this.volumeMultipliers.getOrDefault(key, 1.0f); 68 | } 69 | 70 | public void setVolumeMultiplier(SoundSource source, float value) { 71 | String key = soundSourceName(source); 72 | Config defaultConfig = DynamicFPSConfig.DEFAULTS.get(this.state); 73 | 74 | if (value != 1.0f || defaultConfig.rawVolumeMultiplier(source) != 1.0f) { 75 | this.volumeMultipliers.put(key, value); 76 | } else { 77 | this.volumeMultipliers.remove(key); // Same as default value 78 | } 79 | } 80 | 81 | private static String soundSourceName(SoundSource source) { 82 | return source.getName().toLowerCase(Locale.ROOT); 83 | } 84 | 85 | public GraphicsState graphicsState() { 86 | return this.graphicsState; 87 | } 88 | 89 | public void setGraphicsState(GraphicsState value) { 90 | this.graphicsState = value; 91 | } 92 | 93 | public boolean showToasts() { 94 | return this.showToasts; 95 | } 96 | 97 | public void setShowToasts(boolean value) { 98 | this.showToasts = value; 99 | } 100 | 101 | public boolean runGarbageCollector() { 102 | return this.runGarbageCollector; 103 | } 104 | 105 | public void setRunGarbageCollector(boolean value) { 106 | this.runGarbageCollector = value; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /platforms/quilt/build.gradle: -------------------------------------------------------------------------------- 1 | import me.modmuss50.mpp.ReleaseType 2 | import net.fabricmc.loom.task.RemapJarTask 3 | 4 | plugins { 5 | id("dynamic_fps.module") 6 | alias(libs.plugins.shadow) 7 | alias(libs.plugins.mod.publish.plugin) 8 | } 9 | 10 | repositories { 11 | maven { url = "https://maven.quiltmc.org/repository/release" } 12 | } 13 | 14 | architectury { 15 | platformSetupLoomIde() 16 | loader("quilt") 17 | } 18 | 19 | loom { 20 | accessWidenerPath = project(":platforms:common").loom.accessWidenerPath 21 | } 22 | 23 | configurations { 24 | common { 25 | canBeResolved = true 26 | canBeConsumed = false 27 | } 28 | // Files in this configuration will be bundled into your mod using the Shadow plugin. 29 | // Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files. 30 | shadowBundle { 31 | canBeResolved = true 32 | canBeConsumed = false 33 | } 34 | compileClasspath.extendsFrom common 35 | runtimeClasspath.extendsFrom common 36 | developmentFabric.extendsFrom common 37 | } 38 | 39 | dependencies { 40 | modImplementation(libs.quilt.loader) 41 | 42 | include(libs.battery) 43 | 44 | common(project(path: ":platforms:common", configuration: "namedElements")) { transitive = false } 45 | shadowBundle(project(path: ":platforms:common", configuration: "transformProductionQuilt")) { transitive = false } 46 | 47 | common(project(path: ":platforms:textile", configuration: "namedElements")) { transitive = false } 48 | shadowBundle(project(path: ":platforms:textile", configuration: "transformProductionQuilt")) { transitive = false } 49 | } 50 | 51 | shadowJar { 52 | exclude "architectury.common.json" 53 | configurations = [project.configurations.shadowBundle] 54 | } 55 | 56 | remapJar { 57 | injectAccessWidener = true 58 | input.set shadowJar.archiveFile 59 | dependsOn shadowJar 60 | } 61 | 62 | sourcesJar { 63 | def commonSources = project(":platforms:common").sourcesJar 64 | dependsOn commonSources 65 | from commonSources.archiveFile.map { zipTree(it) } 66 | } 67 | 68 | components.java { 69 | withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { 70 | skip() 71 | } 72 | } 73 | 74 | var modVersion = project.property("mod_version").toString() 75 | 76 | publishMods { 77 | version = modVersion 78 | displayName = "v${modVersion}" 79 | 80 | type = modVersion.contains("-") ? ReleaseType.BETA : ReleaseType.STABLE; 81 | modLoaders.addAll(project.property("supported_platforms").toString().split(",")) 82 | 83 | changelog = file(rootDir.toPath().resolve("changelog.txt")).text 84 | file = tasks.withType(RemapJarTask).named("remapJar").get().archiveFile 85 | additionalFiles.from(tasks.withType(Jar).named("sourcesJar").get().archiveFile) 86 | 87 | curseforge { 88 | accessToken = providers.environmentVariable("CURSEFORGE_SECRET") 89 | projectId = "335493" 90 | 91 | minecraftVersionRange { 92 | start = project.property("minecraft_version_min").toString() 93 | end = project.property("minecraft_version_max").toString() 94 | } 95 | 96 | clientRequired = true 97 | serverRequired = false 98 | 99 | requires("qsl") 100 | optional("cloth-config", "modmenu") 101 | } 102 | 103 | modrinth { 104 | accessToken = providers.environmentVariable("MODRINTH_SECRET") 105 | projectId = "LQ3K71Q1" 106 | 107 | minecraftVersionRange { 108 | start = project.property("minecraft_version_min").toString() 109 | end = project.property("minecraft_version_max").toString() 110 | } 111 | 112 | requires("qsl") 113 | optional("cloth-config", "modmenu") 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /platforms/fabric/build.gradle: -------------------------------------------------------------------------------- 1 | import me.modmuss50.mpp.ReleaseType 2 | import net.fabricmc.loom.task.RemapJarTask 3 | 4 | plugins { 5 | id("dynamic_fps.module") 6 | alias(libs.plugins.shadow) 7 | alias(libs.plugins.mod.publish.plugin) 8 | } 9 | 10 | architectury { 11 | platformSetupLoomIde() 12 | fabric() 13 | } 14 | 15 | loom { 16 | accessWidenerPath = project(":platforms:common").loom.accessWidenerPath 17 | } 18 | 19 | configurations { 20 | common { 21 | canBeResolved = true 22 | canBeConsumed = false 23 | } 24 | // Files in this configuration will be bundled into your mod using the Shadow plugin. 25 | // Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files. 26 | shadowBundle { 27 | canBeResolved = true 28 | canBeConsumed = false 29 | } 30 | compileClasspath.extendsFrom common 31 | runtimeClasspath.extendsFrom common 32 | developmentFabric.extendsFrom common 33 | } 34 | 35 | dependencies { 36 | modImplementation(libs.fabric.loader) 37 | 38 | modImplementation(fabricApi.module("fabric-resource-loader-v0", libs.versions.fabric.api.get())) 39 | modImplementation(fabricApi.module("fabric-lifecycle-events-v1", libs.versions.fabric.api.get())) 40 | 41 | include(libs.battery) 42 | 43 | common(project(path: ":platforms:common", configuration: "namedElements")) { transitive = false } 44 | shadowBundle(project(path: ":platforms:common", configuration: "transformProductionFabric")) { transitive = false } 45 | 46 | common(project(path: ":platforms:textile", configuration: "namedElements")) { transitive = false } 47 | shadowBundle(project(path: ":platforms:textile", configuration: "transformProductionFabric")) { transitive = false } 48 | } 49 | 50 | shadowJar { 51 | exclude "architectury.common.json" 52 | configurations = [project.configurations.shadowBundle] 53 | } 54 | 55 | remapJar { 56 | injectAccessWidener = true 57 | input.set shadowJar.archiveFile 58 | dependsOn shadowJar 59 | } 60 | 61 | sourcesJar { 62 | def commonSources = project(":platforms:common").sourcesJar 63 | dependsOn commonSources 64 | from commonSources.archiveFile.map { zipTree(it) } 65 | } 66 | 67 | components.java { 68 | withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { 69 | skip() 70 | } 71 | } 72 | 73 | var modVersion = project.property("mod_version").toString() 74 | 75 | publishMods { 76 | version = modVersion 77 | displayName = "v${modVersion}" 78 | 79 | type = modVersion.contains("-") ? ReleaseType.BETA : ReleaseType.STABLE; 80 | modLoaders.addAll(project.property("supported_platforms").toString().split(",")) 81 | 82 | changelog = file(rootDir.toPath().resolve("changelog.txt")).text 83 | file = tasks.withType(RemapJarTask).named("remapJar").get().archiveFile 84 | additionalFiles.from(tasks.withType(Jar).named("sourcesJar").get().archiveFile) 85 | 86 | curseforge { 87 | accessToken = providers.environmentVariable("CURSEFORGE_SECRET") 88 | projectId = "335493" 89 | 90 | minecraftVersionRange { 91 | start = project.property("minecraft_version_min").toString() 92 | end = project.property("minecraft_version_max").toString() 93 | } 94 | 95 | clientRequired = true 96 | serverRequired = false 97 | 98 | requires("fabric-api") 99 | optional("cloth-config", "modmenu") 100 | } 101 | 102 | modrinth { 103 | accessToken = providers.environmentVariable("MODRINTH_SECRET") 104 | projectId = "LQ3K71Q1" 105 | 106 | minecraftVersionRange { 107 | start = project.property("minecraft_version_min").toString() 108 | end = project.property("minecraft_version_max").toString() 109 | } 110 | 111 | requires("fabric-api") 112 | optional("cloth-config", "modmenu") 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/util/Version.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.util; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | public class Version implements Comparable { 10 | private final int[] components; 11 | private final @Nullable String preRelease; 12 | private final @Nullable String buildMetadata; 13 | 14 | // This is not *fully compliant* with the SemVer spec: 15 | // Specifying the patch version is optional since Minecraft doesn't on 1.x.0 releases 16 | // Some other version strings with invalid pre-release or build metadata are accepted 17 | private static final Pattern VERSION_PATTERN = Pattern.compile( 18 | "^(?\\d+)\\.(?\\d+)(?:\\.(?\\d+))?(?:-(?[\\da-zA-Z\\-\\.]{2,}))?(?:\\+(?[\\da-zA-Z\\-\\.]+))?$" 19 | ); 20 | 21 | private Version(int major, int minor, int patch, @Nullable String preRelease, @Nullable String buildMetadata) { 22 | this.components = new int[]{major, minor, patch}; 23 | 24 | this.preRelease = preRelease; 25 | this.buildMetadata = buildMetadata; 26 | } 27 | 28 | public static Version of(int major, int minor, int patch) { 29 | return new Version(major, minor, patch, null, null); 30 | } 31 | 32 | public static Version of(String raw) throws VersionParseException { 33 | Matcher matcher = VERSION_PATTERN.matcher(raw); 34 | 35 | if (!matcher.matches()) { 36 | throw new VersionParseException(raw); 37 | } 38 | 39 | int major = Integer.parseInt(matcher.group("major")); 40 | int minor = Integer.parseInt(matcher.group("minor")); 41 | int patch = 0; 42 | 43 | // Patch is optional due to Minecraft ... 44 | if (matcher.group("patch") != null) { 45 | patch = Integer.parseInt(matcher.group("patch")); 46 | } 47 | 48 | String preRelease = matcher.group("prerelease"); 49 | String buildMetadata = matcher.group("metadata"); 50 | 51 | return new Version(major, minor, patch, preRelease, buildMetadata); 52 | } 53 | 54 | public int major() { 55 | return this.components[0]; 56 | } 57 | 58 | public int minor() { 59 | return this.components[1]; 60 | } 61 | 62 | public int patch() { 63 | return this.components[2]; 64 | } 65 | 66 | public boolean isPreRelease() { 67 | return this.preRelease() != null; 68 | } 69 | 70 | public @Nullable String preRelease() { 71 | return this.preRelease; 72 | } 73 | 74 | public boolean hasBuildMetadata() { 75 | return this.buildMetadata() != null; 76 | } 77 | 78 | public @Nullable String buildMetadata() { 79 | return this.buildMetadata; 80 | } 81 | 82 | /** 83 | * @return the version as a string. May be different from raw input due to normalization. 84 | */ 85 | @Override 86 | public String toString() { 87 | String result = String.format("%s.%s.%s", this.major(), this.minor(), this.patch()); 88 | 89 | if (this.isPreRelease()) { 90 | result += "-" + this.preRelease(); 91 | } 92 | 93 | if (this.hasBuildMetadata()) { 94 | result += "+" + this.buildMetadata(); 95 | } 96 | 97 | return result; 98 | } 99 | 100 | @Override 101 | public int compareTo(@NotNull Version other) { 102 | for (int index = 0; index < 3; index++) { 103 | int result = Integer.compare(this.components[index], other.components[index]); 104 | 105 | if (result != 0) { 106 | return result; 107 | } 108 | } 109 | 110 | if (this.isPreRelease() && other.isPreRelease()) { 111 | // noinspection DataFlowIssue (if statement has guards) 112 | return this.preRelease().compareTo(other.preRelease()); 113 | } else if (this.isPreRelease()) { 114 | return -1; 115 | } else if (other.isPreRelease()) { 116 | return +1; 117 | } 118 | 119 | return 0; 120 | } 121 | 122 | public static class VersionParseException extends Exception { 123 | private VersionParseException(String version) { 124 | super(version + " is not a semantic version!"); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/mixin/FramerateLimitTrackerMixin.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.mixin; 2 | 3 | import com.mojang.blaze3d.platform.FramerateLimitTracker; 4 | import dynamic_fps.impl.Constants; 5 | import dynamic_fps.impl.DynamicFPSMod; 6 | import dynamic_fps.impl.PowerState; 7 | import dynamic_fps.impl.config.DynamicFPSConfig; 8 | import dynamic_fps.impl.config.option.IdleCondition; 9 | import net.minecraft.client.Minecraft; 10 | import net.minecraft.client.Options; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import org.spongepowered.asm.mixin.Unique; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 18 | 19 | @Mixin(FramerateLimitTracker.class) 20 | public class FramerateLimitTrackerMixin { 21 | @Shadow 22 | @Final 23 | private Options options; 24 | 25 | @Shadow 26 | @Final 27 | private Minecraft minecraft; 28 | 29 | @Shadow 30 | private int framerateLimit; 31 | 32 | @Inject(method = "getFramerateLimit", at = @At("HEAD"), cancellable = true) 33 | private void getFramerateLimit(CallbackInfoReturnable callbackInfo) { 34 | PowerState state = DynamicFPSMod.powerState(); 35 | 36 | if (state != PowerState.FOCUSED) { 37 | // Instruct Minecraft to render a minimum of 15 FPS 38 | // Going lower here makes resuming again feel sluggish 39 | callbackInfo.setReturnValue(Math.max(this.getFramerateTarget(), Constants.MIN_FRAME_RATE_LIMIT)); 40 | } else { 41 | IdleCondition condition = DynamicFPSConfig.INSTANCE.idle().condition(); 42 | 43 | // Bypass all the vanilla idle checking code 44 | // Note: If Dynamic FPS thinks the user is idle the power state would be different above 45 | if (condition != IdleCondition.VANILLA) { 46 | // Since we're bypassing the idle checking code we also need to set the menu FPS here as it's bundled now 47 | if (isInLevel()) { 48 | callbackInfo.setReturnValue(this.framerateLimit); 49 | } else if(DynamicFPSConfig.INSTANCE.uncapMenuFrameRate()) { 50 | callbackInfo.setReturnValue(this.getMenuFramerateLimit()); 51 | }else { 52 | callbackInfo.setReturnValue(Constants.TITLE_FRAME_RATE_LIMIT); 53 | } 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * Conditionally bypasses the main menu frame rate limit while using the vanilla idle code. 60 | *

61 | * This is done in two cases: 62 | * - The window is active, and the user wants to uncap the frame rate 63 | * - The window is inactive, and the current FPS limit should be lower 64 | */ 65 | @Inject(method = "getFramerateLimit", at = @At(value = "CONSTANT", args = "intValue=60"), cancellable = true) 66 | private void getMenuFramerateLimit(CallbackInfoReturnable callbackInfo) { 67 | int limit = this.getFramerateTarget(); 68 | 69 | if (DynamicFPSMod.powerState() != PowerState.FOCUSED) { 70 | // Vanilla returns 60 here 71 | // Only overwrite if our current limit is lower 72 | if (limit < 60) { 73 | callbackInfo.setReturnValue(limit); 74 | } 75 | } else if (DynamicFPSConfig.INSTANCE.uncapMenuFrameRate()) { 76 | callbackInfo.setReturnValue(this.getMenuFramerateLimit()); 77 | } 78 | } 79 | 80 | @Unique 81 | private int getFramerateTarget() { 82 | return DynamicFPSMod.targetFrameRate(); 83 | } 84 | 85 | @Unique 86 | private int getMenuFramerateLimit() { 87 | if (this.options.enableVsync().get()) { 88 | // VSync will regulate to a non-infinite value 89 | return Constants.NO_FRAME_RATE_LIMIT; 90 | } else { 91 | // Even though the option "uncaps" the frame rate the limit is 250 FPS. 92 | // Since otherwise this will just cause coil whine with no real benefit 93 | return Math.min(this.framerateLimit, Constants.NO_FRAME_RATE_LIMIT - 10); 94 | } 95 | } 96 | 97 | @Unique 98 | private boolean isInLevel() { 99 | return this.minecraft.level != null || this.minecraft.screen == null && this.minecraft.getOverlay() == null; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/ko_kr.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Dynamic FPS 구성", 3 | 4 | "config.dynamic_fps.warn_cloth_config.0": "Minecraft에서 Dynamic FPS 구성을 변경하려면 Cloth Config가 필요합니다.", 5 | "config.dynamic_fps.warn_cloth_config.1": "Cloth Config를 설치하거나, ​​구성 파일(config file)을 편집하거나, 대신 기본 설정을 사용하세요.", 6 | 7 | "config.dynamic_fps.category.general": "일반", 8 | "config.dynamic_fps.category.advanced": "고급", 9 | 10 | "config.dynamic_fps.category.hovered": "마우스 올림", 11 | "config.dynamic_fps.category.unfocused": "비포커스", 12 | "config.dynamic_fps.category.invisible": "비가시", 13 | "config.dynamic_fps.category.unplugged": "배터리 켜짐", 14 | "config.dynamic_fps.category.abandoned": "대기 중", 15 | 16 | "config.dynamic_fps.enabled": "Dynamic FPS 활성화", 17 | 18 | "config.dynamic_fps.disabled": "비활성화", 19 | "config.dynamic_fps.minutes": "%d 분(초)", 20 | 21 | "config.dynamic_fps.idle_time": "대기 시간 초과", 22 | "config.dynamic_fps.idle_time_tooltip": "게임이 활성화된 상태에서 입력이 없어진 후 Dynamic FPS가 대기 상태를 활성화하는 시간(분).", 23 | 24 | "config.dynamic_fps.idle_condition": "대기 상태", 25 | "config.dynamic_fps.idle_condition_none": "없음", 26 | "config.dynamic_fps.idle_condition_on_battery": "배터리 사용 중", 27 | 28 | "config.dynamic_fps.uncap_menu_frame_rate": "메뉴 화면 FPS 제한 해제", 29 | "config.dynamic_fps.uncap_menu_frame_rate_tooltip": "메인 메뉴의 60FPS 제한을 해제합니다.", 30 | 31 | "config.dynamic_fps.battery_tracker": "배터리 추적", 32 | "config.dynamic_fps.battery_tracker_tooltip": "배터리 관련 기능 모두 켜기/끄기.", 33 | 34 | "config.dynamic_fps.battery_tracker_switch_states": "배터리 상태 활성화", 35 | "config.dynamic_fps.battery_tracker_switch_states_tooltip": "배터리 사용 시 자동으로 배터리 상태로 전환합니다.", 36 | 37 | "config.dynamic_fps.battery_tracker_notifications": "배터리 알림", 38 | "config.dynamic_fps.battery_tracker_notifications_tooltip": "게임 내 배터리 상태 알림을 받습니다.", 39 | 40 | "config.dynamic_fps.battery_indicator_condition": "배터리 표시기 상태", 41 | "config.dynamic_fps.battery_indicator_condition_disabled": "비활성화", 42 | "config.dynamic_fps.battery_indicator_condition_draining": "방전중", 43 | "config.dynamic_fps.battery_indicator_condition_critical": "위험", 44 | "config.dynamic_fps.battery_indicator_condition_constant": "고정", 45 | 46 | "config.dynamic_fps.battery_indicator_placement": "표시기 배치", 47 | "config.dynamic_fps.battery_indicator_placement_top_left": "좌측 위", 48 | "config.dynamic_fps.battery_indicator_placement_top_right": "우측 위", 49 | "config.dynamic_fps.battery_indicator_placement_bottom_left": "좌측 아래", 50 | "config.dynamic_fps.battery_indicator_placement_bottom_right": "우측 아래", 51 | 52 | "config.dynamic_fps.volume_transition_speed_up": "볼륨 증가 전환 속도", 53 | "config.dynamic_fps.volume_transition_speed_down": "볼륨 감소 전환 속도", 54 | "config.dynamic_fps.volume_transition_speed_instant": "즉시", 55 | "config.dynamic_fps.volume_transition_speed_tooltip": "초당 게임 볼륨이 조정되는 속도를 설정합니다.", 56 | 57 | "config.dynamic_fps.frame_rate_target": "프레임 속도", 58 | "config.dynamic_fps.volume_multiplier": "볼륨 배수", 59 | 60 | "config.dynamic_fps.graphics_state": "그래픽 옵션", 61 | "config.dynamic_fps.graphics_state_default": "기본", 62 | "config.dynamic_fps.graphics_state_reduced": "감소", 63 | "config.dynamic_fps.graphics_state_minimal": "최소", 64 | "config.dynamic_fps.graphics_state_minimal_tooltip": "최소 그래픽 설정으로 인해 세계가 다시로드됩니다!", 65 | 66 | "config.dynamic_fps.show_toasts": "토스트 보이기", 67 | "config.dynamic_fps.show_toasts_tooltip": "토스트 알림을 계속 표시할지 또는 지연할지 여부", 68 | 69 | "config.dynamic_fps.run_garbage_collector": "가비지 컬렉터 호출", 70 | "config.dynamic_fps.run_garbage_collector_tooltip": "이 상태로 전환할 때 사용되지 않는 메모리를 확보합니다.", 71 | 72 | "config.dynamic_fps.download_natives": "네이티브 다운로드", 73 | "config.dynamic_fps.download_natives_description_0": "배터리 기능을 사용하려면 추가 라이브러리가 필요합니다.", 74 | "config.dynamic_fps.download_natives_description_1": "모드가 대신 이 구성 요소를 다운로드할 수 있는지 여부를 설정합니다.", 75 | 76 | "key.dynamic_fps.toggle_forced": "강제로 FPS 감소", 77 | "key.dynamic_fps.toggle_disabled": "Dynamic FPS 비활성화", 78 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: 강제로 FPS 감소중", 79 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS 비활성화", 80 | 81 | "toast.dynamic_fps.battery_charging": "충전 중!", 82 | "toast.dynamic_fps.battery_draining": "방전 중!", 83 | "toast.dynamic_fps.battery_critical": "배터리 부족!", 84 | 85 | "toast.dynamic_fps.battery_charge": "현재 %d%%", 86 | 87 | "modmenu.descriptionTranslation.dynamic_fps": "Minecraft가 백그라운드에 있거나 대기 상태인 동안 리소스 사용량을 줄입니다." 88 | } 89 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/zh_cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Dynamic FPS配置", 3 | 4 | "config.dynamic_fps.warn_cloth_config.0": "需要Cloth Config才能在Minecraft中配置动态帧率。", 5 | "config.dynamic_fps.warn_cloth_config.1": "要么安装Cloth Config,要么编辑配置文件,要么使用默认设置。", 6 | 7 | "config.dynamic_fps.category.general": "一般", 8 | "config.dynamic_fps.category.advanced": "高级设置", 9 | 10 | "config.dynamic_fps.category.hovered": "聚焦时", 11 | "config.dynamic_fps.category.unfocused": "未聚焦时", 12 | "config.dynamic_fps.category.invisible": "后台隐藏", 13 | "config.dynamic_fps.category.unplugged": "电池供电时", 14 | "config.dynamic_fps.category.abandoned": "空闲模式", 15 | 16 | "config.dynamic_fps.enabled": "已启用", 17 | 18 | "config.dynamic_fps.disabled": "已禁用", 19 | "config.dynamic_fps.minutes": "%d分钟(s)", 20 | 21 | "config.dynamic_fps.idle_time": "空闲超时", 22 | "config.dynamic_fps.idle_time_tooltip": "在处于聚焦状态下,需要多长时间激活空闲状态。", 23 | 24 | "config.dynamic_fps.idle_condition": "空闲条件", 25 | 26 | "config.dynamic_fps.idle_condition_none": "无", 27 | "config.dynamic_fps.idle_condition_vanilla": "原版", 28 | "config.dynamic_fps.idle_condition_on_battery": "在使用电池时", 29 | 30 | "config.dynamic_fps.idle_condition_none_tooltip": "当长时间无活动时,始终激活空闲状态。", 31 | "config.dynamic_fps.idle_condition_vanilla_tooltip": "禁用动态帧率的空闲功能。改用原版设置。", 32 | "config.dynamic_fps.idle_condition_on_battery_tooltip": "在使用电池且长时间无活动时,启用空闲状态。", 33 | 34 | "config.dynamic_fps.uncap_menu_frame_rate": "解除菜单FPS限制", 35 | "config.dynamic_fps.uncap_menu_frame_rate_tooltip": "移除主菜单中的60帧/秒限制。", 36 | 37 | "config.dynamic_fps.battery_tracker": "电池跟踪", 38 | "config.dynamic_fps.battery_tracker_tooltip": "开启或关闭所有与电池相关的功能。", 39 | 40 | "config.dynamic_fps.battery_tracker_switch_states": "启用电池状态", 41 | "config.dynamic_fps.battery_tracker_switch_states_tooltip": "在使用电池时自动启用电池状态。", 42 | 43 | "config.dynamic_fps.battery_tracker_notifications": "电池通知", 44 | "config.dynamic_fps.battery_tracker_notifications_tooltip": "在游戏中接收有关电池状态的通知。", 45 | 46 | "config.dynamic_fps.battery_indicator_condition": "电池指示条件", 47 | "config.dynamic_fps.battery_indicator_condition_disabled": "禁用", 48 | "config.dynamic_fps.battery_indicator_condition_draining": "放电", 49 | "config.dynamic_fps.battery_indicator_condition_critical": "临界", 50 | "config.dynamic_fps.battery_indicator_condition_constant": "持续", 51 | 52 | "config.dynamic_fps.battery_indicator_placement": "指示器位置", 53 | "config.dynamic_fps.battery_indicator_placement_top_left": "左上角", 54 | "config.dynamic_fps.battery_indicator_placement_top_right": "右上角", 55 | "config.dynamic_fps.battery_indicator_placement_bottom_left": "左下角", 56 | "config.dynamic_fps.battery_indicator_placement_bottom_right": "右下角", 57 | 58 | "config.dynamic_fps.volume_transition_speed_up": "向上音量过渡速度", 59 | "config.dynamic_fps.volume_transition_speed_down": "向下音量过渡速度", 60 | "config.dynamic_fps.volume_transition_speed_instant": "立即", 61 | "config.dynamic_fps.volume_transition_speed_tooltip": "设置每秒游戏音量调整的速度。", 62 | 63 | "config.dynamic_fps.frame_rate_target": "帧率限制", 64 | "config.dynamic_fps.volume_multiplier": "音量设置", 65 | 66 | "config.dynamic_fps.graphics_state": "图形选项", 67 | "config.dynamic_fps.graphics_state_default": "默认", 68 | "config.dynamic_fps.graphics_state_reduced": "减少", 69 | "config.dynamic_fps.graphics_state_minimal": "最小", 70 | "config.dynamic_fps.graphics_state_minimal_tooltip": "最小的图形也会导致世界重新加载!", 71 | 72 | "config.dynamic_fps.show_toasts": "显示提示", 73 | "config.dynamic_fps.show_toasts_tooltip": "是否持续显示或延迟显示提示通知", 74 | 75 | "config.dynamic_fps.run_garbage_collector": "调用内存垃圾回收器", 76 | "config.dynamic_fps.run_garbage_collector_tooltip": "切换到此状态时释放未使用的内存", 77 | 78 | "config.dynamic_fps.download_natives": "下载本地文件", 79 | "config.dynamic_fps.download_natives_description_0": "使用电池功能需要额外的库。", 80 | "config.dynamic_fps.download_natives_description_1": "设置是否允许该模组为您下载此组件。", 81 | 82 | "config.dynamic_fps.mock_battery_data": "使用模拟电池数据", 83 | 84 | "key.dynamic_fps.toggle_forced": "强制限制帧率", 85 | "key.dynamic_fps.toggle_disabled": "禁用帧率限制", 86 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS:已限制帧率", 87 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS已禁用", 88 | 89 | "toast.dynamic_fps.error": "Dynamic FPS电池错误", 90 | "toast.dynamic_fps.no_support": "您的电脑不受支持", 91 | "toast.dynamic_fps.no_library": "库下载已禁用", 92 | 93 | "toast.dynamic_fps.battery_charging": "充电中!", 94 | "toast.dynamic_fps.battery_draining": "放电中!", 95 | "toast.dynamic_fps.battery_critical": "电池电量低!", 96 | 97 | "toast.dynamic_fps.battery_charge": "当前电量为%d%%", 98 | 99 | "modmenu.descriptionTranslation.dynamic_fps": "动态限制游戏帧率,使Minecraft 处于后台时占用更少的资源。" 100 | } 101 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/zh_tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "設定Dynamic FPS", 3 | 4 | "config.dynamic_fps.warn_cloth_config.0": "需要Cloth Config才能在Minecraft內設定Dynamic FPS。", 5 | "config.dynamic_fps.warn_cloth_config.1": "安裝Cloth Config、編輯設定檔,或是使用預設設定。", 6 | 7 | "config.dynamic_fps.category.general": "一般", 8 | "config.dynamic_fps.category.advanced": "進階", 9 | 10 | "config.dynamic_fps.category.hovered": "聚焦時", 11 | "config.dynamic_fps.category.unfocused": "未聚焦時", 12 | "config.dynamic_fps.category.invisible": "不可見時", 13 | "config.dynamic_fps.category.unplugged": "電池供電時", 14 | "config.dynamic_fps.category.abandoned": "閒置時", 15 | 16 | "config.dynamic_fps.enabled": "已啟用", 17 | 18 | "config.dynamic_fps.disabled": "已停用", 19 | "config.dynamic_fps.minutes": "%d分鐘", 20 | 21 | "config.dynamic_fps.idle_time": "閒置逾時", 22 | "config.dynamic_fps.idle_time_tooltip": "在遊戲聚焦時,幾分鐘無操作後Dynamic FPS會啟動閒置狀態。", 23 | 24 | "config.dynamic_fps.idle_condition": "閒置條件", 25 | 26 | "config.dynamic_fps.idle_condition_none": "無", 27 | "config.dynamic_fps.idle_condition_vanilla": "原版", 28 | "config.dynamic_fps.idle_condition_on_battery": "電池供電時", 29 | 30 | "config.dynamic_fps.idle_condition_none_tooltip": "閒置過久時一律啟用閒置狀態。", 31 | "config.dynamic_fps.idle_condition_vanilla_tooltip": "停用Dynamic FPS閒置。改用原版設定。", 32 | "config.dynamic_fps.idle_condition_on_battery_tooltip": "閒置過久且使用電池供電時啟用閒置狀態。", 33 | 34 | "config.dynamic_fps.uncap_menu_frame_rate": "移除選單FPS限制", 35 | "config.dynamic_fps.uncap_menu_frame_rate_tooltip": "移除主選單的60FPS限制。", 36 | 37 | "config.dynamic_fps.battery_tracker": "電池追蹤", 38 | "config.dynamic_fps.battery_tracker_tooltip": "開啟或關閉所有與電池相關的功能。", 39 | 40 | "config.dynamic_fps.battery_tracker_switch_states": "啟用電池狀態", 41 | "config.dynamic_fps.battery_tracker_switch_states_tooltip": "在電池供電時自動啟用電池狀態。", 42 | 43 | "config.dynamic_fps.battery_tracker_notifications": "電池通知", 44 | "config.dynamic_fps.battery_tracker_notifications_tooltip": "接收有關電池狀態的遊戲內通知。", 45 | 46 | "config.dynamic_fps.battery_indicator_condition": "電池指示條件", 47 | "config.dynamic_fps.battery_indicator_condition_disabled": "已停用", 48 | "config.dynamic_fps.battery_indicator_condition_draining": "電池放電時", 49 | "config.dynamic_fps.battery_indicator_condition_critical": "危急/少於", 50 | "config.dynamic_fps.battery_indicator_condition_constant": "持續顯示", 51 | 52 | "config.dynamic_fps.battery_indicator_placement": "指示器位置", 53 | "config.dynamic_fps.battery_indicator_placement_top_left": "左上方", 54 | "config.dynamic_fps.battery_indicator_placement_top_right": "右上方", 55 | "config.dynamic_fps.battery_indicator_placement_bottom_left": "左下方", 56 | "config.dynamic_fps.battery_indicator_placement_bottom_right": "右下方", 57 | 58 | "config.dynamic_fps.volume_transition_speed_up": "上升音量轉換速度", 59 | "config.dynamic_fps.volume_transition_speed_down": "下降音量轉換速度", 60 | "config.dynamic_fps.volume_transition_speed_instant": "立即", 61 | "config.dynamic_fps.volume_transition_speed_tooltip": "設定每秒調整遊戲音量的速度。", 62 | 63 | "config.dynamic_fps.frame_rate_target": "目標FPS", 64 | "config.dynamic_fps.volume_multiplier": "音量倍率", 65 | 66 | "config.dynamic_fps.graphics_state": "顯示設定", 67 | "config.dynamic_fps.graphics_state_default": "預設", 68 | "config.dynamic_fps.graphics_state_reduced": "減少", 69 | "config.dynamic_fps.graphics_state_minimal": "最低", 70 | "config.dynamic_fps.graphics_state_minimal_tooltip": "最低圖形會重新載入世界!", 71 | 72 | "config.dynamic_fps.show_toasts": "顯示提示", 73 | "config.dynamic_fps.show_toasts_tooltip": "是否持續顯示或延遲提示通知", 74 | 75 | "config.dynamic_fps.run_garbage_collector": "執行垃圾回收", 76 | "config.dynamic_fps.run_garbage_collector_tooltip": "切換到此狀態時釋放未使用的記憶體", 77 | 78 | "config.dynamic_fps.download_natives": "下載原生程式庫", 79 | "config.dynamic_fps.download_natives_description_0": "使用電池功能需要額外的程式庫。", 80 | "config.dynamic_fps.download_natives_description_1": "設定模組是否可以代表您下載這個元件。", 81 | 82 | "config.dynamic_fps.mock_battery_data": "使用模擬電池資料", 83 | 84 | "key.dynamic_fps.toggle_forced": "強制失去焦點模式(切換)", 85 | "key.dynamic_fps.toggle_disabled": "停用Dynamic FPS(切換)", 86 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS:強制限制FPS", 87 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS 已停用", 88 | 89 | "toast.dynamic_fps.error": "Dynamic FPS電池錯誤", 90 | "toast.dynamic_fps.no_support": "電腦不支援", 91 | "toast.dynamic_fps.no_library": "程式庫下載已停用", 92 | 93 | "toast.dynamic_fps.battery_charging": "正在充電!", 94 | "toast.dynamic_fps.battery_draining": "正在放電!", 95 | "toast.dynamic_fps.battery_critical": "電量不足!", 96 | 97 | "toast.dynamic_fps.battery_charge": "目前電量為%d%%", 98 | 99 | "modmenu.descriptionTranslation.dynamic_fps": "當Minecraft在背景執行或閒置時,降低資源使用量。" 100 | } 101 | -------------------------------------------------------------------------------- /platforms/quilt/src/main/resources/quilt.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema_version": 1, 3 | "quilt_loader": { 4 | "group": "net.lostluma", 5 | "id": "dynamic_fps", 6 | "version": "${version}", 7 | "metadata": { 8 | "name": "Dynamic FPS", 9 | "description": "Reduce resource usage while Minecraft is in the background or idle.", 10 | "contributors": { 11 | "juliand665": "Author", 12 | "LostLuma": "Author", 13 | "AC19970": "Translator", 14 | "altrisi": "Contributor", 15 | "DenaryDev": "Contributor", 16 | "dima-dencep": "Contributor", 17 | "EuropaYou": ["Contributor", "Translator"], 18 | "N4TH4NOT": ["Contributor", "Translator"], 19 | "parly": "Contributor", 20 | "Pixaurora": ["Contributor" , "Illustrator", "Translator"], 21 | "Setadokalo": "Contributor", 22 | "Siphalor": "Contributor", 23 | "sisby-folk": "Contributor", 24 | "triphora": "Contributor", 25 | "Alexander317": "Translator", 26 | "AlphaKR93": "Translator", 27 | "Altegar": "Translator", 28 | "atroniax": "Translator", 29 | "BurrConnie": "Translator", 30 | "DecafDawren": "Translator", 31 | "egeesin": "Translator", 32 | "Felix14-v2": "Translator", 33 | "FITFC": "Translator", 34 | "godkyo98": "Translator", 35 | "GuNanOvO": "Translator", 36 | "Hubry": "Translator", 37 | "ImVietnam": "Translator", 38 | "ishi-sama": "Translator", 39 | "kau19an": "Translator", 40 | "kyrtion": "Translator", 41 | "Lucanoria": "Translator", 42 | "LotuxPunk": "Translator", 43 | "Madis0": "Translator", 44 | "mpustovoi": "Translator", 45 | "notlin4": "Translator", 46 | "Q2297045667": "Translator", 47 | "raspberrygitq": "Translator", 48 | "Rhbarber": "Translator", 49 | "RinixGG": "Translator", 50 | "Samekichi": "Translator", 51 | "Shihyeon": "Translator", 52 | "StarmanMine142": "Translator", 53 | "stijnvdkolk": "Translator", 54 | "Taarek": "Translator", 55 | "TheBossMagnus": "Translator", 56 | "TheLegendofSaram": "Translator", 57 | "wicivo": "Translator", 58 | "XfedeX": "Translator", 59 | "yichifauzi": "Translator" 60 | }, 61 | "contact": { 62 | "homepage": "https://dapprgames.com/mods", 63 | "issues": "https://github.com/juliand665/Dynamic-FPS/issues", 64 | "sources": "https://github.com/juliand665/Dynamic-FPS" 65 | }, 66 | "icon": "assets/dynamic_fps/textures/icon.png", 67 | "license": { 68 | "id": "MIT", 69 | "name": "MIT License", 70 | "url": "https://github.com/juliand665/Dynamic-FPS/blob/main/LICENSE" 71 | } 72 | }, 73 | "intermediate_mappings": "net.fabricmc:intermediary", 74 | "entrypoints": { 75 | "client": [ 76 | "net.lostluma.dynamic_fps.impl.textile.compat.FREX" 77 | ], 78 | "modmenu": [ 79 | "net.lostluma.dynamic_fps.impl.textile.compat.ModMenu" 80 | ] 81 | }, 82 | "depends": [ 83 | { 84 | "id": "quilt_loader", 85 | "versions": ">=0.25.0" 86 | }, 87 | { 88 | "id": "minecraft", 89 | "versions": ">=1.21.9-beta.1" 90 | }, 91 | { 92 | "id": "mixinextras", 93 | "versions": ">=0.3.2" 94 | }, 95 | { 96 | "id": "fabric-resource-loader-v0", 97 | "versions": "*" 98 | }, 99 | { 100 | "id": "fabric-lifecycle-events-v1", 101 | "versions": "*" 102 | }, 103 | { 104 | "id": "modmenu", 105 | "versions": "*", 106 | "optional": true 107 | }, 108 | { 109 | "id": "cloth-config", 110 | "versions": "*", 111 | "optional": true 112 | } 113 | ] 114 | }, 115 | "minecraft": { 116 | "environment": "client" 117 | }, 118 | "mixin": [ 119 | "dynamic_fps.mixins.json", 120 | "dynamic_fps-common.mixins.json", 121 | "dynamic_fps-textile.mixins.json" 122 | ], 123 | "access_widener": "dynamic_fps.accesswidener" 124 | } 125 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/ru_ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Настройки Dynamic FPS", 3 | "config.dynamic_fps.warn_cloth_config.0": "Для настройки Dynamic FPS через интерфейс Minecraft требуется Cloth Config.", 4 | "config.dynamic_fps.warn_cloth_config.1": "Можно использовать настройки по умолчанию, отредактировать файл настроек вручную или установить Cloth Config для настройки через игру.", 5 | 6 | "config.dynamic_fps.category.general": "Основные", 7 | "config.dynamic_fps.category.advanced": "Дополнительные", 8 | 9 | "config.dynamic_fps.category.hovered": "Наведение мыши", 10 | "config.dynamic_fps.category.unfocused": "Фоновый режим", 11 | "config.dynamic_fps.category.invisible": "Свёрнутый режим", 12 | "config.dynamic_fps.category.unplugged": "Экономия энергии", 13 | "config.dynamic_fps.category.abandoned": "Режим ожидания", 14 | 15 | "config.dynamic_fps.enabled": "Включён", 16 | 17 | "config.dynamic_fps.disabled": "Отключено", 18 | "config.dynamic_fps.minutes": "Минут: %d", 19 | 20 | "config.dynamic_fps.idle_time": "Время перехода в режим ожидания", 21 | "config.dynamic_fps.idle_time_tooltip": "Время простоя перед переходом в режим ожидания,\nкогда игра не свёрнута и находится в фокусе.", 22 | 23 | "config.dynamic_fps.idle_condition": "Условие для перехода", 24 | "config.dynamic_fps.idle_condition_none": "Всегда", 25 | "config.dynamic_fps.idle_condition_on_battery": "Работа от батареи", 26 | 27 | "config.dynamic_fps.uncap_menu_frame_rate": "Неограниченная частота кадров в меню", 28 | "config.dynamic_fps.uncap_menu_frame_rate_tooltip": "Снять ограничение в 60 кадров в секунду в главном меню.", 29 | 30 | "config.dynamic_fps.battery_tracker": "Отслеживание заряда батареи", 31 | "config.dynamic_fps.battery_tracker_tooltip": "Переключить все функции, связанные с батареей.", 32 | 33 | "config.dynamic_fps.battery_tracker_switch_states": "Переход в режим экономии энергии", 34 | "config.dynamic_fps.battery_tracker_switch_states_tooltip": "Автоматически включать режим экономии\nэнергии при питании от батареи.", 35 | 36 | "config.dynamic_fps.battery_tracker_notifications": "Уведомления батареи", 37 | "config.dynamic_fps.battery_tracker_notifications_tooltip": "Выводить стилизованные уведомления о состоянии батареи.", 38 | 39 | "config.dynamic_fps.battery_indicator_condition": "Отображение индикатора батареи", 40 | "config.dynamic_fps.battery_indicator_condition_disabled": "Отключён", 41 | "config.dynamic_fps.battery_indicator_condition_draining": "При разрядке", 42 | "config.dynamic_fps.battery_indicator_condition_critical": "Низкий заряд", 43 | "config.dynamic_fps.battery_indicator_condition_constant": "Всегда", 44 | 45 | "config.dynamic_fps.battery_indicator_placement": "Положение индикатора", 46 | "config.dynamic_fps.battery_indicator_placement_top_left": "Сверху слева", 47 | "config.dynamic_fps.battery_indicator_placement_top_right": "Сверху справа", 48 | "config.dynamic_fps.battery_indicator_placement_bottom_left": "Снизу слева", 49 | "config.dynamic_fps.battery_indicator_placement_bottom_right": "Снизу справа", 50 | 51 | "config.dynamic_fps.volume_transition_speed_up": "Скорость нарастания громкости", 52 | "config.dynamic_fps.volume_transition_speed_down": "Скорость угасания громкости", 53 | "config.dynamic_fps.volume_transition_speed_instant": "Мгновенно", 54 | "config.dynamic_fps.volume_transition_speed_tooltip": "Скорость изменения громкости звуков игры за секунду.", 55 | 56 | "config.dynamic_fps.frame_rate_target": "Частота кадров", 57 | "config.dynamic_fps.volume_multiplier": "Громкость звуков", 58 | 59 | "config.dynamic_fps.graphics_state": "Снижение настроек графики", 60 | "config.dynamic_fps.graphics_state_default": "Отключено", 61 | "config.dynamic_fps.graphics_state_reduced": "Среднее", 62 | "config.dynamic_fps.graphics_state_minimal": "Сильное", 63 | "config.dynamic_fps.graphics_state_minimal_tooltip": "Сильное снижение настроек графики вызывает перезагрузку чанков,\nчто может привести к бóльшим затратам, чем к экономии.", 64 | 65 | "config.dynamic_fps.show_toasts": "Показывать уведомления", 66 | "config.dynamic_fps.show_toasts_tooltip": "Вывод уведомлений можно отложить до возвращения к игре.", 67 | 68 | "config.dynamic_fps.run_garbage_collector": "Запускать сборщик мусора", 69 | "config.dynamic_fps.run_garbage_collector_tooltip": "Освобождать неиспользуемую оперативную память при активации режима.", 70 | 71 | "config.dynamic_fps.download_natives": "Загрузка библиотек", 72 | "config.dynamic_fps.download_natives_description_0": "Разрешить автоматическую загрузку необходимых компонентов для вас", 73 | "config.dynamic_fps.download_natives_description_1": "(для проверки состояния батареи требуется дополнительная библиотека).", 74 | 75 | "key.dynamic_fps.toggle_forced": "Переключить фоновый режим", 76 | "key.dynamic_fps.toggle_disabled": "Переключить работу Dynamic FPS", 77 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: принудительное снижениие частоты кадров", 78 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS отключён", 79 | 80 | "toast.dynamic_fps.battery_charging": "Зарядка...", 81 | "toast.dynamic_fps.battery_draining": "Батарея разряжается", 82 | "toast.dynamic_fps.battery_critical": "Низкий уровень заряда", 83 | 84 | "toast.dynamic_fps.battery_charge": "Уровень заряда: %d%%", 85 | 86 | "modmenu.descriptionTranslation.dynamic_fps": "Динамическое изменение энергозатратных настроек в зависимости от состояния окна игры.", 87 | "modmenu.summaryTranslation.dynamic_fps": "Автоматическое снижение потребления игрой ресурсов в фоне и в простое." 88 | } 89 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/mixin/SoundEngineMixin.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.mixin; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import dynamic_fps.impl.feature.volume.SmoothVolumeHandler; 8 | import dynamic_fps.impl.service.Platform; 9 | import dynamic_fps.impl.util.Logging; 10 | import dynamic_fps.impl.util.Version; 11 | import net.minecraft.client.Minecraft; 12 | import org.spongepowered.asm.mixin.Final; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.Unique; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | 20 | import dynamic_fps.impl.util.duck.DuckSoundEngine; 21 | import net.minecraft.client.resources.sounds.SoundInstance; 22 | import net.minecraft.client.sounds.ChannelAccess; 23 | import net.minecraft.client.sounds.SoundEngine; 24 | import net.minecraft.sounds.SoundSource; 25 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 26 | 27 | @Mixin(SoundEngine.class) 28 | public class SoundEngineMixin implements DuckSoundEngine { 29 | @Shadow 30 | private boolean loaded; 31 | 32 | @Shadow 33 | @Final 34 | private Map instanceToChannel; 35 | 36 | @Shadow 37 | private float calculateVolume(SoundInstance instance) { 38 | throw new RuntimeException("Failed to find SoundEngine.calculateVolume."); 39 | } 40 | 41 | @Unique 42 | private static final Minecraft dynamic_fps$minecraft = Minecraft.getInstance(); 43 | 44 | @Override 45 | public void dynamic_fps$updateVolume(SoundSource source) { 46 | if (!this.loaded) { 47 | return; 48 | } 49 | 50 | boolean isMaster = source.equals(SoundSource.MASTER); 51 | 52 | // Create a copy of all currently active sounds, as iterating over this collection 53 | // Can throw if a sound instance stops playing while we are updating sound volumes 54 | List sounds; 55 | 56 | try { 57 | sounds = new ArrayList<>(this.instanceToChannel.keySet()); 58 | } catch (Throwable e) { 59 | Logging.getLogger().error("Unable to update source volume!", e); 60 | return; 61 | } 62 | 63 | // Using our copy should now be safe as long as we check the channel handle exists 64 | for (SoundInstance instance : sounds) { 65 | ChannelAccess.ChannelHandle handle = this.instanceToChannel.get(instance); 66 | 67 | if (handle == null || (!isMaster && !instance.getSource().equals(source))) { 68 | continue; 69 | } 70 | 71 | float volume = this.calculateVolume(instance); 72 | 73 | // When setting the volume to zero we pause music but cancel other types of sounds 74 | // This results in a less jarring experience when quickly tabbing out and back in. 75 | // Also fixes this compat bug: https://github.com/juliand665/Dynamic-FPS/issues/55 76 | boolean isMusic = instance.getSource().equals(SoundSource.MUSIC) || instance.getSource().equals(SoundSource.RECORDS); 77 | boolean playsPaused = isMusic || instance.getSource().equals(SoundSource.UI); 78 | 79 | handle.execute(channel -> { 80 | if (volume <= 0.0f) { 81 | // Pause music unconditionally when volume is zero 82 | // Otherwise if vanilla doesn't pause the sound set the volume to zero 83 | // This allows long sounds (e.g. sonic boom) to be heard when tabbing back in 84 | if (isMusic) { 85 | channel.pause(); 86 | } else if (!dynamic_fps$minecraft.isPaused()) { 87 | channel.setVolume(volume); 88 | } 89 | } else { 90 | if (playsPaused && this.dynamic_fps$resumeMusic()) { 91 | channel.unpause(); 92 | } 93 | 94 | channel.setVolume(volume); 95 | } 96 | }); 97 | } 98 | } 99 | 100 | /** 101 | * Cancels playing sounds while we are overwriting the volume to be off. 102 | *
103 | * This is done in favor of actually setting the volume to zero because it 104 | * Allows pausing and resuming the sound engine without cancelling all active sounds. 105 | */ 106 | @Inject(method = "play", at = @At("HEAD"), cancellable = true) 107 | private void play(SoundInstance instance, CallbackInfoReturnable callbackInfo) { 108 | if (SmoothVolumeHandler.volumeMultiplier(instance.getSource()) == 0.0f) { 109 | callbackInfo.setReturnValue(SoundEngine.PlayResult.NOT_STARTED); 110 | } 111 | } 112 | 113 | /** 114 | * Cancels scheduling sounds while we are overwriting the volume to be off. 115 | *
116 | * This is done in favor of actually setting the volume to zero because it 117 | * Allows pausing and resuming the sound engine without cancelling all active sounds. 118 | */ 119 | @Inject(method = "playDelayed", at = @At("HEAD"), cancellable = true) 120 | private void playDelayed(SoundInstance instance, int i, CallbackInfo callbackInfo) { 121 | if (SmoothVolumeHandler.volumeMultiplier(instance.getSource()) == 0.0f) { 122 | callbackInfo.cancel(); 123 | } 124 | } 125 | 126 | /** 127 | * Whether music and ui sounds should be resumed. This changes depending on the Minecraft version. 128 | */ 129 | @Unique 130 | private boolean dynamic_fps$resumeMusic() { 131 | if (!dynamic_fps$minecraft.isPaused()) { 132 | return true; 133 | } 134 | 135 | Platform platform = Platform.getInstance(); 136 | 137 | if (platform.isModLoaded("pause_music_on_pause")) { 138 | return false; 139 | } 140 | 141 | Version version; 142 | 143 | try { 144 | version = Version.of("1.21.6-alpha.25.20.a"); 145 | } catch (Version.VersionParseException e) { 146 | throw new RuntimeException(e); 147 | } 148 | 149 | return platform.getModVersion("minecraft").get().compareTo(version) >= 0; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /platforms/common/src/main/java/dynamic_fps/impl/feature/battery/BatteryTracker.java: -------------------------------------------------------------------------------- 1 | package dynamic_fps.impl.feature.battery; 2 | 3 | import dynamic_fps.impl.DynamicFPSMod; 4 | import dynamic_fps.impl.config.DynamicFPSConfig; 5 | import dynamic_fps.impl.service.Platform; 6 | import dynamic_fps.impl.util.Components; 7 | import dynamic_fps.impl.util.Logging; 8 | import dynamic_fps.impl.util.Threads; 9 | import net.lostluma.battery.api.Battery; 10 | import net.lostluma.battery.api.Manager; 11 | import net.lostluma.battery.api.State; 12 | import net.lostluma.battery.api.exception.LibraryLoadError; 13 | import net.lostluma.battery.api.util.LibraryUtil; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.io.IOException; 17 | import java.time.Duration; 18 | import java.time.temporal.ChronoUnit; 19 | import java.util.Collection; 20 | import java.util.Collections; 21 | 22 | public class BatteryTracker { 23 | private static boolean readInitialData = false; 24 | 25 | private static volatile int charge = 0; 26 | private static volatile State status = State.UNKNOWN; 27 | 28 | private static @Nullable Manager manager = null; 29 | private static Collection batteries = Collections.emptyList(); 30 | 31 | private static final Duration updateInterval = Duration.of(15, ChronoUnit.SECONDS); 32 | 33 | public static int charge() { 34 | if (DynamicFPSConfig.INSTANCE.mockBatteryData()) { 35 | return 64; 36 | } else { 37 | return charge; 38 | } 39 | } 40 | 41 | public static State status() { 42 | if (DynamicFPSConfig.INSTANCE.mockBatteryData()) { 43 | return State.CHARGING; 44 | } else { 45 | return status; 46 | } 47 | } 48 | 49 | public static boolean hasBatteries() { 50 | if (DynamicFPSConfig.INSTANCE.mockBatteryData()) { 51 | return true; 52 | } else { 53 | return !batteries.isEmpty(); 54 | } 55 | } 56 | 57 | public static void init() { 58 | if (manager != null || !isFeatureEnabled()) { 59 | return; 60 | } 61 | 62 | customizeInstallation(); 63 | 64 | Manager temp = createManager(); 65 | batteries = getBatteries(temp); 66 | 67 | if (batteries.isEmpty()) { 68 | if (temp != null) { 69 | temp.close(); 70 | } 71 | } else { 72 | manager = temp; // Keep around to allow updating batteries 73 | Threads.create("refresh-battery", BatteryTracker::updateBatteries); 74 | } 75 | } 76 | 77 | public static boolean isFeatureEnabled() { 78 | return DynamicFPSConfig.INSTANCE.batteryTracker().enabled(); 79 | } 80 | 81 | private static State mergeStates(State a, State b) { 82 | if (a == b) { 83 | return a; 84 | } else if (a == State.CHARGING || b == State.CHARGING) { 85 | return State.CHARGING; 86 | } else if (a == State.DISCHARGING || b == State.DISCHARGING) { 87 | return State.DISCHARGING; 88 | } else { 89 | return a == State.UNKNOWN ? b : a; 90 | } 91 | } 92 | 93 | private static void updateState() { 94 | boolean changed = false; 95 | 96 | float aggregate = 0.0f; 97 | State newStatus = State.UNKNOWN; 98 | 99 | for (Battery battery : batteries) { 100 | aggregate += battery.stateOfCharge(); 101 | newStatus = mergeStates(newStatus, battery.state()); 102 | } 103 | 104 | int newCharge = Math.round(aggregate / batteries.size()); 105 | 106 | if (readInitialData && charge != newCharge) { 107 | changed = true; 108 | 109 | int current = charge; 110 | Threads.runOnMainThread(() -> DynamicFPSMod.onBatteryChargeChanged(current, newCharge)); 111 | } 112 | 113 | if (readInitialData && status != newStatus) { 114 | changed = true; 115 | 116 | State current = status; 117 | State updated = newStatus; 118 | Threads.runOnMainThread(() -> DynamicFPSMod.onBatteryStatusChanged(current, updated)); 119 | } 120 | 121 | charge = newCharge; 122 | status = newStatus; 123 | 124 | if (!readInitialData || changed) { 125 | readInitialData = true; 126 | // Unplugged state may have toggled 127 | DynamicFPSMod.onStatusChanged(false); 128 | } 129 | } 130 | 131 | private static void updateBatteries() { 132 | boolean active = true; 133 | 134 | while (active) { 135 | for (Battery battery : batteries) { 136 | try { 137 | battery.update(); 138 | } catch (IOException e) { 139 | Logging.getLogger().warn("Failed to update battery!", e); 140 | } 141 | } 142 | 143 | updateState(); 144 | 145 | try { 146 | Thread.sleep(updateInterval); 147 | } catch (InterruptedException e) { 148 | active = false; 149 | Thread.currentThread().interrupt(); 150 | } 151 | } 152 | 153 | if (manager != null) { 154 | manager.close(); 155 | } 156 | } 157 | 158 | private static void customizeInstallation() { 159 | LibraryUtil.setCacheDir(Platform.getInstance().getCacheDir()); 160 | LibraryUtil.setAllowDownloads(DynamicFPSConfig.INSTANCE.downloadNatives()); 161 | } 162 | 163 | private static Manager createManager() { 164 | Manager result = null; 165 | 166 | try { 167 | result = Manager.create(); 168 | } catch (IOException e) { 169 | Logging.getLogger().warn("Failed to create battery manager!", e); 170 | } catch (LibraryLoadError e) { 171 | // No native backend library is available for this OS or platform 172 | Logging.getLogger().warn("Battery tracker feature unavailable!"); 173 | 174 | String path; 175 | 176 | if (DynamicFPSConfig.INSTANCE.downloadNatives()) { 177 | path = "no_support"; 178 | } else { 179 | path = "no_library"; 180 | } 181 | 182 | Threads.runOnMainThread(() -> ErrorToast.queueToast(Components.translatable("toast", path))); 183 | } 184 | 185 | return result; 186 | } 187 | 188 | private static Collection getBatteries(@Nullable Manager manager) { 189 | Collection result = Collections.emptyList(); 190 | 191 | if (manager == null) { 192 | return result; 193 | } 194 | 195 | try { 196 | result = manager.batteries(); 197 | } catch (IOException e) { 198 | Logging.getLogger().warn("Failed to query system batteries!", e); 199 | } 200 | 201 | return result; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /platforms/common/src/main/resources/assets/dynamic_fps/lang/it_it.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.dynamic_fps.title": "Configura Dynamic FPS", 3 | 4 | "config.dynamic_fps.warn_cloth_config.0": "Cloth Config è necessario per configurare Dynamic FPS all'interno di Minecraft.", 5 | "config.dynamic_fps.warn_cloth_config.1": "Installa Cloth Config, modifica il file di configurazione o usa le impostazioni predefinite.", 6 | 7 | "config.dynamic_fps.category.general": "Generale", 8 | "config.dynamic_fps.category.advanced": "Avanzato", 9 | 10 | "config.dynamic_fps.category.hovered": "In Sovrapposizione", 11 | "config.dynamic_fps.category.unfocused": "Non a Fuoco", 12 | "config.dynamic_fps.category.invisible": "Invisibile", 13 | "config.dynamic_fps.category.unplugged": "A Batteria", 14 | "config.dynamic_fps.category.abandoned": "Inattivo", 15 | 16 | "config.dynamic_fps.enabled": "Abilitato", 17 | 18 | "config.dynamic_fps.disabled": "Disabilitato", 19 | "config.dynamic_fps.minutes": "%d Minuto/i", 20 | 21 | "config.dynamic_fps.idle_time": "Timeout di inattività", 22 | "config.dynamic_fps.idle_time_tooltip": "Minuti senza input prima che Dynamic FPS attivi lo stato di inattività mentre il gioco è attivo.", 23 | 24 | "config.dynamic_fps.idle_condition": "Condizione di inattività", 25 | 26 | "config.dynamic_fps.idle_condition_none": "Nessuno", 27 | "config.dynamic_fps.idle_condition_vanilla": "Vanilla", 28 | "config.dynamic_fps.idle_condition_on_battery": "Quando a batteria", 29 | 30 | "config.dynamic_fps.idle_condition_none_tooltip": "Attiva sempre lo stato di inattività quando inattivo per troppo tempo.", 31 | "config.dynamic_fps.idle_condition_vanilla_tooltip": "Disattiva l'inattività di Dynamic FPS. Usa invece l'impostazione vanilla.", 32 | "config.dynamic_fps.idle_condition_on_battery_tooltip": "Attiva lo stato di inattività quando inattivo per troppo tempo e a batteria.", 33 | 34 | "config.dynamic_fps.uncap_menu_frame_rate": "Rimuovi il limite di FPS nel Menu", 35 | "config.dynamic_fps.uncap_menu_frame_rate_tooltip": "Rimuove il limite di 60 FPS nel menu principale.", 36 | 37 | "config.dynamic_fps.battery_tracker": "Monitoraggio della Batteria", 38 | "config.dynamic_fps.battery_tracker_tooltip": "Attiva o disattiva tutte le funzionalità relative alla batteria.", 39 | 40 | "config.dynamic_fps.battery_tracker_switch_states": "Abilita Stato a Batteria", 41 | "config.dynamic_fps.battery_tracker_switch_states_tooltip": "Abilita automaticamente lo stato a batteria quando si è a batteria.", 42 | 43 | "config.dynamic_fps.battery_tracker_notifications": "Notifiche Batteria", 44 | "config.dynamic_fps.battery_tracker_notifications_tooltip": "Ricevi notifiche in-game sullo stato della batteria.", 45 | 46 | "config.dynamic_fps.battery_indicator_condition": "Condizione Indicatore di Batteria", 47 | "config.dynamic_fps.battery_indicator_condition_disabled": "Disabilitato", 48 | "config.dynamic_fps.battery_indicator_condition_draining": "Durante lo scaricamento", 49 | "config.dynamic_fps.battery_indicator_condition_critical": "Critico", 50 | "config.dynamic_fps.battery_indicator_condition_constant": "Costante", 51 | 52 | "config.dynamic_fps.battery_indicator_placement": "Posizionamento Indicatore", 53 | "config.dynamic_fps.battery_indicator_placement_top_left": "In Alto a Sinistra", 54 | "config.dynamic_fps.battery_indicator_placement_top_right": "In Alto a Destra", 55 | "config.dynamic_fps.battery_indicator_placement_bottom_left": "In Basso a Sinistra", 56 | "config.dynamic_fps.battery_indicator_placement_bottom_right": "In Basso a Destra", 57 | 58 | "config.dynamic_fps.volume_transition_speed_up": "Velocità di Transizione del Volume (Aumenta)", 59 | "config.dynamic_fps.volume_transition_speed_down": "Velocità di Transizione del Volume (Diminuisce)", 60 | "config.dynamic_fps.volume_transition_speed_instant": "Immediato", 61 | "config.dynamic_fps.volume_transition_speed_tooltip": "Imposta la velocità di regolazione del volume del gioco al secondo.", 62 | 63 | "config.dynamic_fps.frame_rate_target": "Obiettivo di Frame Rate", 64 | "config.dynamic_fps.volume_multiplier": "Moltiplicatore del Volume", 65 | 66 | "config.dynamic_fps.graphics_state": "Opzioni Grafiche", 67 | "config.dynamic_fps.graphics_state_default": "Predefinito", 68 | "config.dynamic_fps.graphics_state_reduced": "Ridotto", 69 | "config.dynamic_fps.graphics_state_minimal": "Minimale", 70 | "config.dynamic_fps.graphics_state_minimal_tooltip": "La grafica minimale causa il ricaricamento del mondo!", 71 | 72 | "config.dynamic_fps.show_toasts": "Mostra Toast", 73 | "config.dynamic_fps.show_toasts_tooltip": "Decidi se continuare a mostrare o ritardare le notifiche toast", 74 | 75 | "config.dynamic_fps.run_garbage_collector": "Invoca il Garbage Collector", 76 | "config.dynamic_fps.run_garbage_collector_tooltip": "Libera memoria non utilizzata quando si passa a questo stato", 77 | 78 | "config.dynamic_fps.download_natives": "Scarica Componenti Nativi", 79 | "config.dynamic_fps.download_natives_description_0": "L'uso delle funzionalità della batteria richiede una libreria aggiuntiva.", 80 | "config.dynamic_fps.download_natives_description_1": "Imposta se la mod può scaricare questo componente per te.", 81 | 82 | "config.dynamic_fps.mock_battery_data": "Usa dati della batteria simulati", 83 | 84 | "key.dynamic_fps.toggle_forced": "Forza Modalità Non a Fuoco (Toggle)", 85 | "key.dynamic_fps.toggle_disabled": "Disabilita Dynamic FPS (Toggle)", 86 | "gui.dynamic_fps.hud.reducing": "Dynamic FPS: Riduzione Forzata di FPS", 87 | "gui.dynamic_fps.hud.disabled": "Dynamic FPS Disabilitato", 88 | 89 | "toast.dynamic_fps.battery_charging": "In Carica!", 90 | "toast.dynamic_fps.battery_draining": "Scaricamento!", 91 | "toast.dynamic_fps.battery_critical": "Batteria Bassa!", 92 | 93 | "toast.dynamic_fps.battery_charge": "Attualmente al %d%%", 94 | 95 | "modmenu.descriptionTranslation.dynamic_fps": "Riduce l'uso delle risorse mentre Minecraft è in background o inattivo." 96 | } 97 | --------------------------------------------------------------------------------