diff --git a/build.gradle b/build.gradle index f603f58..9bef786 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version "${loom_version}" + id 'fabric-loom' version '1.14.10' id 'maven-publish' } @@ -7,17 +7,23 @@ version = project.mod_version group = project.maven_group base { - archivesName = project.archives_base_name + archivesName = project.archivesBaseName } repositories { - // Add repositories to retrieve artifacts from in here. - // You should only use this when depending on other mods because - // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. - // See https://docs.gradle.org/current/userguide/declaring_repositories.html - // for more information about repositories. - maven { url = "https://api.modrinth.com/maven" } - maven { url = "https://maven.terraformersmc.com/" } // Mod Menu + exclusiveContent { + forRepository { + maven { + name = "Terraformers" + url = "https://maven.terraformersmc.com/" + } + } + filter { + includeGroup 'com.terraformersmc' + } + } + // Fallback + mavenCentral() } dependencies { @@ -26,20 +32,21 @@ dependencies { mappings loom.officialMojangMappings() modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}" - // Tells Gradle to use mod menu - modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}", { - exclude module: 'fabric-api' - } + modLocalRuntime(modCompileOnly "com.terraformersmc:modmenu:${project.modmenu_version}") } processResources { inputs.property "version", project.version filesMatching("fabric.mod.json") { - expand "version": project.version + expand "version": project.version, + "id": project.archivesBaseName, + "minecraft_version": project.minecraft_version, + "loader_version": project.loader_version, + "fabric_api_version": project.fabric_api_version, + "modmenu_version": project.modmenu_version } } @@ -67,7 +74,7 @@ jar { publishing { publications { create("mavenJava", MavenPublication) { - artifactId = project.archives_base_name + artifactId = project.archivesBaseName from components.java } } @@ -79,4 +86,4 @@ publishing { // The repositories here will be used for publishing your artifact, not for // retrieving dependencies. } -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties index fb5ca57..d7bc141 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,19 +3,17 @@ org.gradle.jvmargs=-Xmx1G org.gradle.parallel=true # Mod Properties -mod_version = 1.2.4 -maven_group = com.bvengo -archives_base_name = soundcontroller +mod_version=1.2.5 +maven_group=com.bvengo +archivesBaseName=soundcontroller # Versioning ## Fabric: https://fabricmc.net/develop ## ModMenu: https://maven.terraformersmc.com/com/terraformersmc/modmenu - -modmenu_version=17.0.0-alpha.1 +modmenu_version=17.0.0-beta.1 minecraft_version=1.21.11 -loader_version=0.18.1 -loom_version=1.13-SNAPSHOT +loader_version=0.18.4 # Fabric API -fabric_version=0.139.4+1.21.11 \ No newline at end of file +fabric_api_version=0.139.4+1.21.11 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9..f8e1ee3 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca025c8..23449a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f3b75f3..adff685 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -205,15 +203,14 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019..e509b2d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/java/com/bvengo/soundcontroller/SoundController.java b/src/main/java/com/bvengo/soundcontroller/SoundController.java index 9686073..006f352 100644 --- a/src/main/java/com/bvengo/soundcontroller/SoundController.java +++ b/src/main/java/com/bvengo/soundcontroller/SoundController.java @@ -1,16 +1,14 @@ package com.bvengo.soundcontroller; -import net.fabricmc.fabric.api.resource.v1.ResourceLoader; -import net.minecraft.server.packs.PackType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.bvengo.soundcontroller.config.VolumeConfig; - import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.resource.v1.ResourceLoader; +import net.minecraft.server.packs.PackType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Environment(EnvType.CLIENT) public class SoundController implements ClientModInitializer { @@ -26,8 +24,8 @@ public void onInitializeClient() { }); ResourceLoader.get(PackType.CLIENT_RESOURCES) - .registerReloader(SoundReloadListener.ID, new SoundReloadListener()); + .registerReloader(SoundReloadListener.ID, new SoundReloadListener()); - LOGGER.info("{} loaded.", LOGGER.getName()); + LOGGER.info("{} loaded", LOGGER.getName()); } } diff --git a/src/main/java/com/bvengo/soundcontroller/SoundReloadListener.java b/src/main/java/com/bvengo/soundcontroller/SoundReloadListener.java index 92c452c..5ab2906 100644 --- a/src/main/java/com/bvengo/soundcontroller/SoundReloadListener.java +++ b/src/main/java/com/bvengo/soundcontroller/SoundReloadListener.java @@ -11,8 +11,6 @@ public class SoundReloadListener implements ResourceManagerReloadListener { /** * Invoked every time client resources are reloaded * (e.g. F3+T, resource pack change, initial load). - * - * @param resourceManager the active resource manager */ @Override public void onResourceManagerReload(final @NotNull ResourceManager resourceManager) { @@ -20,6 +18,6 @@ public void onResourceManagerReload(final @NotNull ResourceManager resourceManag return; } - SoundController.CONFIG.updateVolumes(); + SoundController.CONFIG.reload(); } } diff --git a/src/main/java/com/bvengo/soundcontroller/Translations.java b/src/main/java/com/bvengo/soundcontroller/Translations.java index c399d13..ce85b45 100644 --- a/src/main/java/com/bvengo/soundcontroller/Translations.java +++ b/src/main/java/com/bvengo/soundcontroller/Translations.java @@ -3,20 +3,20 @@ import net.minecraft.network.chat.Component; public abstract class Translations { - // Text constants - public static final Component SOUND_SCREEN_TITLE = translatableOf("title"); - public static final Component SEARCH_FIELD_PLACEHOLDER = translatableOf("search.placeholder"); - public static final Component SEARCH_FIELD_TITLE = translatableOf("search.title"); - public static final Component FILTER_BUTTON_TOOLTIP = translatableOf("filter.tooltip"); - public static final Component SUBTITLES_BUTTON_TOOLTIP = translatableOf("subtitles.tooltip"); - public static final Component RESET_BUTTON_TOOLTIP = translatableOf("reset.tooltip"); - public static final Component PLAY_BUTTON_TOOLTIP = translatableOf("play.tooltip"); + // Text constants + public static final Component SOUND_SCREEN_TITLE = translatableOf("title"); + public static final Component SEARCH_FIELD_PLACEHOLDER = translatableOf("search.placeholder"); + public static final Component SEARCH_FIELD_TITLE = translatableOf("search.title"); + public static final Component FILTER_BUTTON_TOOLTIP = translatableOf("filter.tooltip"); + public static final Component SUBTITLES_BUTTON_TOOLTIP = translatableOf("subtitles.tooltip"); + public static final Component RESET_BUTTON_TOOLTIP = translatableOf("reset.tooltip"); + public static final Component PLAY_BUTTON_TOOLTIP = translatableOf("play.tooltip"); - public static Component translatableOf(String key) { - return Component.translatable(getTranslationKey(key)); - } + public static Component translatableOf(String key) { + return Component.translatable(getTranslationKey(key)); + } - public static String getTranslationKey(String key) { - return SoundController.MOD_ID + ".options." + key; - } + public static String getTranslationKey(String key) { + return SoundController.MOD_ID + ".options." + key; + } } diff --git a/src/main/java/com/bvengo/soundcontroller/Utils.java b/src/main/java/com/bvengo/soundcontroller/Utils.java deleted file mode 100644 index e90c222..0000000 --- a/src/main/java/com/bvengo/soundcontroller/Utils.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.bvengo.soundcontroller; - -import net.minecraft.client.Minecraft; -import net.minecraft.sounds.SoundSource; - -public class Utils { - public static void updateExistingSounds() { - Minecraft.getInstance().getSoundManager().refreshCategoryVolume(SoundSource.AMBIENT); - } -} diff --git a/src/main/java/com/bvengo/soundcontroller/VolumeData.java b/src/main/java/com/bvengo/soundcontroller/VolumeData.java index 383d7ec..7a6d499 100644 --- a/src/main/java/com/bvengo/soundcontroller/VolumeData.java +++ b/src/main/java/com/bvengo/soundcontroller/VolumeData.java @@ -1,22 +1,14 @@ package com.bvengo.soundcontroller; -import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.client.resources.sounds.SimpleSoundInstance; -import net.minecraft.client.resources.sounds.SoundInstance; -import net.minecraft.client.sounds.SoundManager; import net.minecraft.resources.Identifier; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundSource; -import net.minecraft.util.RandomSource; + +import java.util.Objects; public class VolumeData { public static final Float DEFAULT_VOLUME = 1.0f; private final Identifier soundId; - private Float volume; - - private SimpleSoundInstance currentSoundInstance = null; + private float volume; public VolumeData(Identifier id, float volume) { this.soundId = id; @@ -28,11 +20,11 @@ public VolumeData(Identifier id) { } public Identifier getId() { - return soundId; + return this.soundId; } - public Float getVolume() { - return volume; + public float getVolume() { + return this.volume; } public void setVolume(float volume) { @@ -40,46 +32,25 @@ public void setVolume(float volume) { } public boolean isModified() { - return !this.volume.equals(DEFAULT_VOLUME); + return !Objects.equals(DEFAULT_VOLUME, this.volume); } public boolean inFilter(String search, boolean showModifiedOnly) { - return (this.soundId.toString().toLowerCase().contains(search) && - (!showModifiedOnly || this.isModified())); + return this.soundId.toString().toLowerCase().contains(search) && + (!showModifiedOnly || this.isModified()); } - public void playSound(SoundManager soundManager) { - final LocalPlayer player = Minecraft.getInstance().player; - SoundEvent soundEvent = SoundEvent.createVariableRangeEvent(soundId); - - if (player != null) { - this.currentSoundInstance = new SimpleSoundInstance( - soundEvent, SoundSource.MASTER, - this.volume, // Volume - 1.0f, // Pitch - RandomSource.create(), - player.getX(), player.getY(), player.getZ() // Position - ); - } else { - this.currentSoundInstance = SimpleSoundInstance.forUI( - soundEvent, - this.volume // Volume - ); - } + @Override + public final boolean equals(Object o) { + if (!(o instanceof VolumeData that)) return false; - soundManager.play(this.currentSoundInstance); + return Objects.equals(this.soundId, that.soundId) && Objects.equals(this.getVolume(), that.getVolume()); } - public void toggleSound(SoundManager soundManager) { - if (isActive(soundManager)) { - soundManager.stop(currentSoundInstance); - currentSoundInstance = null; - } else { - playSound(soundManager); - } - } - - public boolean isActive(SoundManager soundManager) { - return soundManager.isActive(currentSoundInstance); - } -} \ No newline at end of file + @Override + public int hashCode() { + int result = Objects.hashCode(this.soundId); + result = 31 * result + Objects.hashCode(this.getVolume()); + return result; + } +} diff --git a/src/main/java/com/bvengo/soundcontroller/config/ConfigParser.java b/src/main/java/com/bvengo/soundcontroller/config/ConfigParser.java index c215e5a..63760f3 100644 --- a/src/main/java/com/bvengo/soundcontroller/config/ConfigParser.java +++ b/src/main/java/com/bvengo/soundcontroller/config/ConfigParser.java @@ -2,61 +2,48 @@ import com.bvengo.soundcontroller.SoundController; import com.bvengo.soundcontroller.VolumeData; -import com.google.gson.*; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.resources.Identifier; -import java.io.*; +import org.slf4j.Logger; + +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Comparator; -import java.util.HashMap; +import java.util.Map; public class ConfigParser { - private static final File file = new File(FabricLoader.getInstance().getConfigDir().toFile(), - SoundController.MOD_ID + ".json"); - private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + private static final Logger LOG = SoundController.LOGGER; + + private static final Path FILE = FabricLoader.getInstance().getConfigDir().resolve(SoundController.MOD_ID + ".json"); + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - /** - * Loads data into a VolumeConfig object from a JSON file. - * - * @param config The VolumeConfig to load the data into. - */ public static void loadConfig(VolumeConfig config) { - if (!file.exists()) { - SoundController.LOGGER.info("Config file not found. Creating a new one."); - buildEmptyConfig(); + if (!Files.exists(FILE)) { + LOG.info("Config file not found. Creating a new one."); return; } - try (Reader reader = new FileReader(file)) { - JsonObject jsonObject = gson.fromJson(reader, JsonObject.class); + try { + JsonObject jsonObject = GSON.fromJson(Files.readString(FILE), JsonObject.class); if (jsonObject == null) { - SoundController.LOGGER.error("Config file is empty, creating a new one."); - buildEmptyConfig(); + LOG.warn("Config file is empty, using defaults"); return; } parseConfig(config, jsonObject); } catch (Exception e) { - SoundController.LOGGER.error("Error reading config file, creating a new one. Original error: ", e); - moveOldConfig(); - buildEmptyConfig(); + LOG.warn("Error reading config file, using defaults", e); } } - /** - * Saves the provided VolumeConfig to a JSON file. - * - * @param config The VolumeConfig to save. - */ - public static void saveConfig(VolumeConfig config) { - JsonObject jsonObject = createJsonConfig(config); - saveToJsonFile(jsonObject); + public static synchronized void saveConfig(VolumeConfig config) { + saveToJsonFile(createJsonConfig(config)); } - /** - * Creates a JsonObject from a VolumeConfig. - * - * @param config The VolumeConfig to convert to JSON. - * @return JsonObject representing the provided VolumeConfig. - */ private static JsonObject createJsonConfig(VolumeConfig config) { JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("version", VolumeConfig.CONFIG_VERSION); @@ -77,92 +64,36 @@ private static JsonObject createJsonConfig(VolumeConfig config) { return jsonObject; } - /** - * Writes a JsonObject to a JSON file. - * - * @param jsonObject The JsonObject to write to file. - */ private static void saveToJsonFile(JsonObject jsonObject) { - try (Writer writer = new FileWriter(file)) { - gson.toJson(jsonObject, writer); - } catch (IOException e) { - SoundController.LOGGER.error("Unable to save sound config to file.", e); + try { + Files.writeString(FILE, GSON.toJson(jsonObject)); + } catch (Exception e) { + LOG.warn("Unable to save sound config to file", e); } } - /** - * Parses volume data from a JSON object into a map. Handles versioning. - * - * @param config The config being updated. - * @param jsonObject The JSON object to parse. - */ private static void parseConfig(VolumeConfig config, JsonObject jsonObject) { int version = jsonObject.has("version") ? jsonObject.get("version").getAsInt() : -1; - // Check if the version key exists to determine the handling strategy if (version == -1) { - String msg = "Config file does not have a version number. Trying to parse old un-versioned format."; - SoundController.LOGGER.warn(msg); - + LOG.warn("Config file does not have a version. Trying to parse old un-versioned format"); parseConfigUnversioned(config, jsonObject); return; } - if (version < 4 || version > VolumeConfig.CONFIG_VERSION) { - String msg = "Version number invalid - must be between 4 and " + VolumeConfig.CONFIG_VERSION + - " (inclusive). Got " + version + " instead. Storing old config file with `old.` prefix, " + - "and initializing a new empty config file."; - SoundController.LOGGER.error(msg); - moveOldConfig(); - buildEmptyConfig(); - return; - } - -// if (version == 4) { - // Currently always true + if (version == 4) { parseConfig4(config, jsonObject); -// } - } - - /** - * Moves the old config file to a new file with a prefix of `old.`. - */ - private static void moveOldConfig() { - File oldFile = new File(file.getParentFile(), "old." + file.getName()); - if (file.renameTo(oldFile)) { - SoundController.LOGGER.info("Renamed old config file to " + oldFile.getName()); - } else { - SoundController.LOGGER.error("Failed to rename old config file."); + return; } - } - /** - * Builds an empty config file with the current version number. - */ - private static void buildEmptyConfig() { - JsonObject newConfig = new JsonObject(); - newConfig.addProperty("version", VolumeConfig.CONFIG_VERSION); - newConfig.add("sounds", new JsonArray()); - - try (Writer writer = new FileWriter(file)) { - gson.toJson(newConfig, writer); - } catch (IOException e) { - SoundController.LOGGER.error("Failed to create a new empty config file.", e); - } + LOG.warn("Unknown config version {}, expected {} - not parsing", version, VolumeConfig.CONFIG_VERSION); } - /** - * Adds a sound ID and volume to the soundVolumes map, provided the sound is valid. - * - * @param soundVolumes The map to store the sound volumes. - * @param soundId The sound ID to add. - * @param volume The volume to add. - */ - private static void addVolumeData(HashMap soundVolumes, String soundId, float volume) { + private static void addVolumeData(Map soundVolumes, String soundId, float volume) { Identifier id = Identifier.tryParse(soundId); if (soundVolumes.containsKey(id)) { - SoundController.LOGGER.warn("Duplicate sound ID found in config: {}. Taking first only.", soundId); + LOG.warn("Duplicate sound ID found in config: {}. Taking first only.", soundId); return; } @@ -171,10 +102,11 @@ private static void addVolumeData(HashMap soundVolumes, if(id != null) { soundVolumes.put(id, volumeData); } else { - SoundController.LOGGER.warn("Invalid sound ID found in config: {}. Skipping.", soundId); + LOG.warn("Invalid sound ID found in config: {}. Skipping.", soundId); } } + /** * Parse V4 configs. The structure is as follows: *
@@ -188,9 +120,6 @@ private static void addVolumeData(HashMap soundVolumes,
 	 *    	...
 	 * }
 	 * 
- * - * @param config The config being updated. - * @param jsonObject The JSON object to parse. */ private static void parseConfig4(VolumeConfig config, JsonObject jsonObject) { JsonElement subtitlesElement = jsonObject.get("subtitlesEnabled"); @@ -198,7 +127,7 @@ private static void parseConfig4(VolumeConfig config, JsonObject jsonObject) { config.subtitlesEnabled = subtitlesElement.getAsBoolean(); } - HashMap soundVolumes = config.getVolumes(); + Map soundVolumes = config.getVolumes(); JsonArray sounds = jsonObject.getAsJsonArray("sounds"); for (JsonElement soundElement : sounds) { JsonObject soundObject = soundElement.getAsJsonObject(); @@ -208,11 +137,12 @@ private static void parseConfig4(VolumeConfig config, JsonObject jsonObject) { addVolumeData(soundVolumes, soundId, volume); } - SoundController.LOGGER.info("Successfully loaded in configs."); + LOG.info("Successfully loaded in configs"); } /** - * Parse un-versioned configs. The structure is as follows: + * Parse un-versioned configs (before 1.1.0/1.20.5). + * The structure is as follows: *
 	 * {
 	 * 		// version 1:
@@ -224,23 +154,20 @@ private static void parseConfig4(VolumeConfig config, JsonObject jsonObject) {
 	 *    		"id": "minecraft:entity.player.hurt",
 	 *    		"volume": 0.5,
 	 *    		"shouldOverride": false
-	 *    	},
-	 *		...
+	 *        },
+	 * 		...
 	 *
 	 * 		// version 3:
 	 *    	"minecraft:entity.player.hurt": {
 	 *    		"soundId": "minecraft:entity.player.hurt",
 	 *    		"volume": 0.5
-	 *    	},
+	 *        },
 	 *    	...
 	 * }
 	 * 
- * - * @param config The config being updated. - * @param jsonObject The JSON object to parse. */ private static void parseConfigUnversioned(VolumeConfig config, JsonObject jsonObject) { - HashMap soundVolumes = config.getVolumes(); + Map soundVolumes = config.getVolumes(); // Iterate over each entry in the JSON object assuming each key is a sound ID jsonObject.entrySet().forEach(entry -> { @@ -250,18 +177,17 @@ private static void parseConfigUnversioned(VolumeConfig config, JsonObject jsonO String soundId; float volume; - if(element.isJsonPrimitive()) { - if(element.getAsJsonPrimitive().isNumber()) { + if (element.isJsonPrimitive()) { + if (element.getAsJsonPrimitive().isNumber()) { // Handle V1 format soundId = key; volume = element.getAsFloat(); } else { String msg = "Unsupported config format for sound ID: " + key; - SoundController.LOGGER.error(msg); + LOG.error(msg); throw new IllegalStateException(msg); } - } - else { + } else { JsonObject soundObject = element.getAsJsonObject(); if (soundObject.has("id")) { // Handle V2 format diff --git a/src/main/java/com/bvengo/soundcontroller/config/VolumeConfig.java b/src/main/java/com/bvengo/soundcontroller/config/VolumeConfig.java index 600a924..ff46fbb 100644 --- a/src/main/java/com/bvengo/soundcontroller/config/VolumeConfig.java +++ b/src/main/java/com/bvengo/soundcontroller/config/VolumeConfig.java @@ -1,28 +1,25 @@ package com.bvengo.soundcontroller.config; -import com.bvengo.soundcontroller.SoundController; import com.bvengo.soundcontroller.VolumeData; -import java.util.HashMap; - import net.minecraft.client.Minecraft; import net.minecraft.client.resources.sounds.SoundInstance; -import net.minecraft.client.sounds.SoundManager; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.Identifier; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundEvents; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; public class VolumeConfig { private static VolumeConfig instance; public static final int CONFIG_VERSION = 4; - private final HashMap soundVolumes; + private final Map soundVolumes; public boolean subtitlesEnabled = false; private VolumeConfig() { - soundVolumes = new HashMap<>(); - updateVolumes(); + this.soundVolumes = new HashMap<>(); + this.reload(); } public static VolumeConfig getInstance() { @@ -32,43 +29,42 @@ public static VolumeConfig getInstance() { return instance; } - public void save() { - ConfigParser.saveConfig(this); + public void saveAsync() { + CompletableFuture.runAsync(() -> ConfigParser.saveConfig(this)); } - public void updateVolumes() { + public void reload() { this.soundVolumes.clear(); - ConfigParser.loadConfig(this); - // Update map with any sounds missing from the config file - SoundManager soundManager = Minecraft.getInstance().getSoundManager(); + Map originalLoadedVolumes = new HashMap<>(this.soundVolumes); - for (Identifier id : soundManager.getAvailableSounds()) { - soundVolumes.putIfAbsent(id, new VolumeData(id)); + // Update map with any sounds missing from the config file + for (Identifier id : Minecraft.getInstance().getSoundManager().getAvailableSounds()) { + this.soundVolumes.computeIfAbsent(id, VolumeData::new); } - ConfigParser.saveConfig(this); - } - - public HashMap getVolumes() { - return soundVolumes; + if (!originalLoadedVolumes.equals(this.soundVolumes)) { + this.saveAsync(); + } } - public VolumeData getVolumeData(Identifier soundId) { - return soundVolumes.getOrDefault(soundId, new VolumeData(soundId)); + public Map getVolumes() { + return this.soundVolumes; } public float getAdjustedVolume(SoundInstance sound, float baseVolume) { - VolumeData volumeData = getVolumeData(sound.getIdentifier()); - return volumeData.getVolume() * baseVolume; + VolumeData volumeData = this.soundVolumes.get(sound.getIdentifier()); + return volumeData != null + ? volumeData.getVolume() * baseVolume + : baseVolume; } public boolean areSubtitlesEnabled() { - return subtitlesEnabled; + return this.subtitlesEnabled; } public void toggleSubtitles() { - subtitlesEnabled = !subtitlesEnabled; + this.subtitlesEnabled = !this.subtitlesEnabled; } } \ No newline at end of file diff --git a/src/main/java/com/bvengo/soundcontroller/gui/AllSoundOptionsScreen.java b/src/main/java/com/bvengo/soundcontroller/gui/AllSoundOptionsScreen.java index 2f6cc49..2e68568 100644 --- a/src/main/java/com/bvengo/soundcontroller/gui/AllSoundOptionsScreen.java +++ b/src/main/java/com/bvengo/soundcontroller/gui/AllSoundOptionsScreen.java @@ -4,103 +4,72 @@ import com.bvengo.soundcontroller.gui.buttons.ToggleButtonWidget; import net.minecraft.client.Options; import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.options.OptionsSubScreen; -import net.minecraft.network.chat.CommonComponents; + import java.util.Comparator; -import static com.bvengo.soundcontroller.Translations.SOUND_SCREEN_TITLE; -import static com.bvengo.soundcontroller.Translations.SEARCH_FIELD_TITLE; -import static com.bvengo.soundcontroller.Translations.SEARCH_FIELD_PLACEHOLDER; import static com.bvengo.soundcontroller.Translations.FILTER_BUTTON_TOOLTIP; +import static com.bvengo.soundcontroller.Translations.SEARCH_FIELD_PLACEHOLDER; +import static com.bvengo.soundcontroller.Translations.SEARCH_FIELD_TITLE; +import static com.bvengo.soundcontroller.Translations.SOUND_SCREEN_TITLE; import static com.bvengo.soundcontroller.Translations.SUBTITLES_BUTTON_TOOLTIP; /** * Screen that displays all sound options. */ public class AllSoundOptionsScreen extends OptionsSubScreen { - VolumeConfig config = VolumeConfig.getInstance(); - - protected final Screen parent; - - private VolumeListWidget volumeListWidget; - private EditBox searchField; + private final VolumeConfig config = VolumeConfig.getInstance(); - private ToggleButtonWidget filterButton; - private ToggleButtonWidget subtitlesButton; + private final EditBox searchField; + private final ToggleButtonWidget filterButton; + private final ToggleButtonWidget subtitlesButton; + private final VolumeListWidget volumeListWidget; private boolean showModifiedOnly = false; + private final OptionsSoundManager optionsSoundManager = new OptionsSoundManager(); + public AllSoundOptionsScreen(Screen parent, Options options) { super(parent, options, SOUND_SCREEN_TITLE); - this.parent = parent; - - // Increase header height to make room for search field. Includes 8 extra padding below. - layout.setHeaderHeight(layout.getHeaderHeight() + 28); - } - - @Override - protected void init() { - addSearchField(); - addFilterButton(); - addSubtitlesButton(); - addVolumeList(); - addDoneButton(); - - this.setInitialFocus(this.searchField); - } - @Override - protected void addOptions() {} - - private void addSearchField() { - // Add search field - x, y, width, height - this.searchField = new EditBox(this.font, 80, 35, this.width - 167, 20, - SEARCH_FIELD_PLACEHOLDER); + this.searchField = new EditBox(this.font, 80, 35, 1, 20, SEARCH_FIELD_PLACEHOLDER); this.searchField.setResponder(serverName -> this.loadOptions()); - this.addWidget(this.searchField); - } - private void addFilterButton() { - // Add filter button - x, y, width, height, textures, pressAction this.filterButton = new ToggleButtonWidget("filter", - this.searchField.getRight() + 8, 35, 20, 20, - (button) -> { - showModifiedOnly = !showModifiedOnly; - loadOptions(); + 1, 35, 20, 20, + btn -> { + this.showModifiedOnly = !this.showModifiedOnly; + this.loadOptions(); }, - false - ); - + false); this.filterButton.setTooltip(Tooltip.create(FILTER_BUTTON_TOOLTIP)); - this.addRenderableWidget(this.filterButton); - } - private void addSubtitlesButton() { - // Add subtitles button - x, y, width, height, textures, pressAction this.subtitlesButton = new ToggleButtonWidget("subtitles", - this.filterButton.getRight() + 4, 35, 20, 20, - (button) -> { - config.toggleSubtitles(); - }, - config.areSubtitlesEnabled()); - + 1, 35, 20, 20, + btn -> this.config.toggleSubtitles(), + this.config.areSubtitlesEnabled()); this.subtitlesButton.setTooltip(Tooltip.create(SUBTITLES_BUTTON_TOOLTIP)); - this.addRenderableWidget(this.subtitlesButton); + + this.volumeListWidget = new VolumeListWidget(this.minecraft); } - private void addVolumeList() { - this.volumeListWidget = new VolumeListWidget(this.minecraft, this.width, this.searchField.getBottom() + 32, this); - loadOptions(); + @Override + protected void addContents() { + this.addRenderableWidget(this.searchField); + this.addRenderableWidget(this.filterButton); + this.addRenderableWidget(this.subtitlesButton); + this.addRenderableWidget(this.volumeListWidget); + this.loadOptions(); + + this.setInitialFocus(this.searchField); } - private void addDoneButton() { - this.addRenderableWidget(Button.builder(CommonComponents.GUI_DONE, button -> this.onClose()) - .bounds(this.width / 2 - 100, this.height - 27, 200, 20).build()); + @Override + protected void addOptions() { } private void loadOptions() { @@ -110,41 +79,39 @@ private void loadOptions() { String search = this.searchField.getValue().toLowerCase(); // Update all buttons - config.getVolumes().values().stream() - .filter(volumeData -> volumeData.inFilter(search, showModifiedOnly)) - .sorted(Comparator.comparing(v -> v.getId().toString())) - .forEach(volumeData -> { - VolumeWidgetEntry volumeEntry = new VolumeWidgetEntry(volumeData, this, this.options); - this.volumeListWidget.addWidgetEntry(volumeEntry); - }); + this.config.getVolumes().values().stream() + .filter(volumeData -> volumeData.inFilter(search, this.showModifiedOnly)) + .sorted(Comparator.comparing(v -> v.getId().toString())) + .map(volumeData -> new VolumeWidgetEntry(volumeData, this.options, this.optionsSoundManager)) + .forEach(this.volumeListWidget::addWidgetEntry); } @Override public void removed() { - config.save(); + this.optionsSoundManager.close(); + this.config.saveAsync(); this.searchField.setValue(""); // Clear search field } @Override - public void resize(int width, int height) { - // Cache search before clearing - String search = this.searchField.getValue(); + protected void repositionElements() { + super.repositionElements(); - this.width = width; - this.height = height; + this.searchField.setWidth(this.width - 167); - this.clearWidgets(); - this.clearFocus(); - this.init(); + this.filterButton.setX(this.searchField.getRight() + 8); + this.subtitlesButton.setX(this.filterButton.getRight() + 4); - this.searchField.setValue(search); + this.volumeListWidget.updateSizeAndPosition( + this.width, + Math.max(this.layout.getContentHeight() - 28, 0), + this.volumeListWidget.getX(), + this.layout.getHeaderHeight() + 28); } @Override public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { super.render(context, mouseX, mouseY, delta); - context.drawCenteredString(this.font, this.title, this.width / 2, 20, 0xFFFFFF); - context.drawString(this.font, SEARCH_FIELD_TITLE, 32, 40, 0xA0A0A0); - this.searchField.render(context, mouseX, mouseY, delta); + context.textRenderer().accept(33, 41, SEARCH_FIELD_TITLE); } -} +} \ No newline at end of file diff --git a/src/main/java/com/bvengo/soundcontroller/gui/OptionsSoundManager.java b/src/main/java/com/bvengo/soundcontroller/gui/OptionsSoundManager.java new file mode 100644 index 0000000..e801222 --- /dev/null +++ b/src/main/java/com/bvengo/soundcontroller/gui/OptionsSoundManager.java @@ -0,0 +1,62 @@ +package com.bvengo.soundcontroller.gui; + +import com.bvengo.soundcontroller.VolumeData; +import com.google.common.cache.CacheBuilder; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.client.sounds.SoundManager; +import net.minecraft.resources.Identifier; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.RandomSource; + +import java.util.Map; +import java.util.Optional; + +public class OptionsSoundManager implements AutoCloseable { + private final Map idSoundInstances = CacheBuilder.newBuilder() + .weakKeys() + .weakValues() + .build() + .asMap(); + + private final SoundManager soundManager = Minecraft.getInstance().getSoundManager(); + + public void toggleSound(VolumeData volumeData) { + Optional.ofNullable(this.idSoundInstances.remove(volumeData.getId())).ifPresentOrElse( + this::tryStopSound, + () -> this.idSoundInstances.put(volumeData.getId(), this.createAndPlaySound(volumeData)) + ); + } + + private void tryStopSound(SimpleSoundInstance soundInstance) { + if (this.soundManager.isActive(soundInstance)) { + this.soundManager.stop(soundInstance); + } + } + + private SimpleSoundInstance createAndPlaySound(VolumeData volumeData) { + final LocalPlayer player = Minecraft.getInstance().player; + SoundEvent soundEvent = SoundEvent.createVariableRangeEvent(volumeData.getId()); + + float volume = volumeData.getVolume(); + SimpleSoundInstance soundInstance = player != null + ? new SimpleSoundInstance( + soundEvent, SoundSource.MASTER, + volume, + 1.0f, // Pitch + RandomSource.create(), + player.getX(), player.getY(), player.getZ()) + : SimpleSoundInstance.forUI(soundEvent, volume); + + this.soundManager.play(soundInstance); + return soundInstance; + } + + @Override + public void close() { + this.idSoundInstances.values().forEach(this::tryStopSound); + this.idSoundInstances.clear(); + } +} diff --git a/src/main/java/com/bvengo/soundcontroller/gui/VolumeListWidget.java b/src/main/java/com/bvengo/soundcontroller/gui/VolumeListWidget.java index b69b072..f8ca3be 100644 --- a/src/main/java/com/bvengo/soundcontroller/gui/VolumeListWidget.java +++ b/src/main/java/com/bvengo/soundcontroller/gui/VolumeListWidget.java @@ -4,18 +4,16 @@ import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.ContainerObjectSelectionList; -import net.minecraft.client.gui.screens.options.OptionsSubScreen; /** * The list widget that contains all the individual records. Contains a list of {@link VolumeWidgetEntry}. */ -@Environment(value=EnvType.CLIENT) +@Environment(EnvType.CLIENT) public class VolumeListWidget extends ContainerObjectSelectionList { - private static final int rowWidth = VolumeWidgetEntry.totalWidth; - private static final int rowHeight = 25; + private static final int ROW_HEIGHT = 25; - public VolumeListWidget(Minecraft client, int width, int i, OptionsSubScreen optionsScreen) { - super(client, width, optionsScreen.layout.getContentHeight(), optionsScreen.layout.getHeaderHeight(), rowHeight); + public VolumeListWidget(Minecraft client) { + super(client, 1, 1, 1, ROW_HEIGHT); this.centerListVertically = false; } @@ -25,7 +23,7 @@ public void addWidgetEntry(VolumeWidgetEntry widget) { @Override public int getRowWidth() { - return rowWidth; + return Math.max(this.getWidth() - 50, 100); } @Override @@ -33,4 +31,3 @@ public void clearEntries() { super.clearEntries(); } } - diff --git a/src/main/java/com/bvengo/soundcontroller/gui/VolumeWidgetEntry.java b/src/main/java/com/bvengo/soundcontroller/gui/VolumeWidgetEntry.java index 1d18cc7..42db25a 100644 --- a/src/main/java/com/bvengo/soundcontroller/gui/VolumeWidgetEntry.java +++ b/src/main/java/com/bvengo/soundcontroller/gui/VolumeWidgetEntry.java @@ -1,77 +1,61 @@ package com.bvengo.soundcontroller.gui; import com.bvengo.soundcontroller.Translations; -import com.bvengo.soundcontroller.Utils; import com.bvengo.soundcontroller.VolumeData; import com.bvengo.soundcontroller.gui.buttons.AudioButtonWidget; import com.bvengo.soundcontroller.gui.buttons.TriggerButtonWidget; -import java.util.List; import net.minecraft.client.Minecraft; import net.minecraft.client.OptionInstance; import net.minecraft.client.Options; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.ContainerObjectSelectionList.Entry; +import net.minecraft.client.gui.components.ResettableOptionWidget; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.narration.NarratableEntry; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.sounds.SoundManager; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundSource; + +import java.util.List; /** * A widget entry allowing control of a single volume. Should be used in a {@link VolumeListWidget}. */ public class VolumeWidgetEntry extends Entry { private final VolumeData volumeData; - private final SoundManager soundManager; - private final Screen screen; - private final Options options; + private final OptionsSoundManager optionsSoundManager; + private final Options gameOptions; - private static final int sliderWidth = 310; - private static final int buttonSize = 20; - private static final int paddingAfterSearch = 8; - private static final int paddingBetweenButtons = 4; - public static final int totalWidth = sliderWidth + buttonSize * 2 + paddingAfterSearch + paddingBetweenButtons; - - public OptionInstance volumeOption; - - public AbstractWidget volumeSlider; - public TriggerButtonWidget playSoundButton; - public TriggerButtonWidget resetButton; + private static final int BUTTON_SIZE = 20; + private static final int PADDING_AFTER_SLIDER = 6; + private static final int PADDING_BETWEEN_BUTTONS = 4; + private static final int NON_VARIABLE_END_WIDTH = + BUTTON_SIZE * 2 + PADDING_AFTER_SLIDER + PADDING_BETWEEN_BUTTONS; private static final float MAX_VOLUME = 2.0f; - public VolumeWidgetEntry(VolumeData volumeData, Screen screen, Options options) { - this.volumeData = volumeData; - this.screen = screen; - this.options = options; - this.soundManager = Minecraft.getInstance().getSoundManager(); - - init(); - } + private final OptionInstance volumeOption; - private int getPercentageValue(double value) { - return (int) Math.round(value * 100); - } + private final AbstractWidget volumeSlider; + private final TriggerButtonWidget playSoundButton; + private final TriggerButtonWidget resetButton; - private float getVolumeFromSlider(double value) { - // 1 -> MAX_VALUE - // Requires multiplying by 100 to round the value to 2dp. - return Math.round(value * MAX_VOLUME * 100) / 100.0f; - } + public VolumeWidgetEntry(VolumeData volumeData, Options gameOptions, OptionsSoundManager optionsSoundManager) { + this.volumeData = volumeData; + this.gameOptions = gameOptions; + this.optionsSoundManager = optionsSoundManager; - private void addSlider() { // Volume slider (options) this.volumeOption = new OptionInstance<>( - volumeData.getId().toString(), + this.volumeData.getId().toString(), OptionInstance.noTooltip(), (prefix, value) -> { // Use volumeData instead of value, noting that it gets updated immediately by the slider as well. // This allows us to list the actual volume (which may be over/under set), not whatever the slider // is clamped to. - int volume = getPercentageValue(volumeData.getVolume()); + int volume = this.getPercentageValue(this.volumeData.getVolume()); if (volume == 0) { return Component.translatable("options.generic_value", prefix, CommonComponents.OPTION_OFF); @@ -88,59 +72,85 @@ private void addSlider() { return Component.translatable("options.percent_value", prefix, volume); }, OptionInstance.UnitDouble.INSTANCE, - Math.clamp(volumeData.getVolume().doubleValue() / MAX_VOLUME, 0.0, 1.0), + this.calcSliderValue(this.volumeData.getVolume()), value -> { - volumeData.setVolume(getVolumeFromSlider(value)); - Utils.updateExistingSounds(); + this.volumeData.setVolume(this.getVolumeFromSlider(value)); + updateExistingSounds(); }); // Volume slider (widget, created from options) - this.volumeSlider = volumeOption.createButton(options, 0, 0, sliderWidth); + this.volumeSlider = this.volumeOption.createButton(this.gameOptions, 0, 0, 1); + + this.playSoundButton = new AudioButtonWidget(0, 0, BUTTON_SIZE, BUTTON_SIZE, this.optionsSoundManager, this.volumeData); + + this.resetButton = new TriggerButtonWidget("reset", 0, 0, BUTTON_SIZE, BUTTON_SIZE, + (button) -> { + this.volumeOption.set(this.calcSliderValue(VolumeData.DEFAULT_VOLUME)); + ((ResettableOptionWidget) this.volumeSlider).resetValue(); + }); + this.resetButton.setTooltip(Tooltip.create(Translations.RESET_BUTTON_TOOLTIP)); } - private void addPlayButton() { - this.playSoundButton = new AudioButtonWidget(0, 0, buttonSize, buttonSize, soundManager, volumeData); + private double calcSliderValue(float volume) { + return Math.clamp(volume / MAX_VOLUME, 0.0, 1.0); } - private void addResetButton() { + private int getPercentageValue(double value) { + return (int) Math.round(value * 100); + } - this.resetButton = new TriggerButtonWidget("reset", 0, 0, buttonSize, buttonSize, - (button) -> { - volumeData.setVolume(VolumeData.DEFAULT_VOLUME); - this.addSlider(); // Update the slider to reflect the new volume - Utils.updateExistingSounds(); - }); + private float getVolumeFromSlider(double value) { + // 1 -> MAX_VALUE + // Requires multiplying by 100 to round the value to 2dp. + return Math.round(value * MAX_VOLUME * 100) / 100.0f; + } - this.resetButton.setTooltip(Tooltip.create(Translations.RESET_BUTTON_TOOLTIP)); + @Override + public void setX(int i) { + super.setX(i); + this.updateElementPositions(); } - private void init() { - addSlider(); - addPlayButton(); - addResetButton(); + @Override + public void setY(int i) { + super.setY(i); + this.updateElementPositions(); + } + + private void updateElementPositions() { + this.volumeSlider.setPosition(this.getContentX(), this.getY()); + this.playSoundButton.setPosition(this.volumeSlider.getRight() + PADDING_AFTER_SLIDER, this.getY()); + this.resetButton.setPosition(this.playSoundButton.getRight() + PADDING_BETWEEN_BUTTONS, this.getY()); } @Override - public void renderContent(GuiGraphics context, int mouseX, int mouseY, boolean hovered, float tickDelta) { - int leftSide = (this.screen.width - totalWidth) / 2; + public void setWidth(int i) { + super.setWidth(i); + this.updateElementWidths(); + } - this.volumeSlider.setPosition(leftSide, getY()); - this.volumeSlider.render(context, mouseX, mouseY, tickDelta); + private void updateElementWidths() { + this.volumeSlider.setWidth(this.getWidth() - NON_VARIABLE_END_WIDTH); + } - this.playSoundButton.setPosition(volumeSlider.getRight() + paddingAfterSearch, getY()); + @Override + public void renderContent(GuiGraphics context, int mouseX, int mouseY, boolean hovered, float tickDelta) { + this.volumeSlider.render(context, mouseX, mouseY, tickDelta); this.playSoundButton.render(context, mouseX, mouseY, tickDelta); - - this.resetButton.setPosition(playSoundButton.getRight() + paddingBetweenButtons, getY()); this.resetButton.render(context, mouseX, mouseY, tickDelta); } @Override public List children() { - return List.of(volumeSlider, playSoundButton, resetButton); + return List.of(this.volumeSlider, this.playSoundButton, this.resetButton); } @Override public List narratables() { - return List.of(volumeSlider, playSoundButton, resetButton); + return List.of(this.volumeSlider, this.playSoundButton, this.resetButton); + } + + private static void updateExistingSounds() { + Minecraft.getInstance().getSoundManager().refreshCategoryVolume(SoundSource.AMBIENT); } } diff --git a/src/main/java/com/bvengo/soundcontroller/gui/buttons/AudioButtonWidget.java b/src/main/java/com/bvengo/soundcontroller/gui/buttons/AudioButtonWidget.java index accddde..1dcf23f 100644 --- a/src/main/java/com/bvengo/soundcontroller/gui/buttons/AudioButtonWidget.java +++ b/src/main/java/com/bvengo/soundcontroller/gui/buttons/AudioButtonWidget.java @@ -2,6 +2,7 @@ import com.bvengo.soundcontroller.Translations; import com.bvengo.soundcontroller.VolumeData; +import com.bvengo.soundcontroller.gui.OptionsSoundManager; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.sounds.SoundManager; @@ -9,9 +10,10 @@ * Custom button widget that is used to trigger audio events. */ public class AudioButtonWidget extends TriggerButtonWidget { - public AudioButtonWidget(int x, int y, int width, int height, SoundManager soundManager, VolumeData volumeData) { - super("audio", x, y, width, height, (button) -> volumeData.toggleSound(soundManager)); - setTooltip(Tooltip.create(Translations.PLAY_BUTTON_TOOLTIP)); + public AudioButtonWidget(int x, int y, int width, int height, + OptionsSoundManager optionsSoundManager, VolumeData volumeData) { + super("audio", x, y, width, height, btn -> optionsSoundManager.toggleSound(volumeData)); + this.setTooltip(Tooltip.create(Translations.PLAY_BUTTON_TOOLTIP)); } @Override diff --git a/src/main/java/com/bvengo/soundcontroller/gui/buttons/ToggleButtonWidget.java b/src/main/java/com/bvengo/soundcontroller/gui/buttons/ToggleButtonWidget.java index 82c1142..79f865f 100644 --- a/src/main/java/com/bvengo/soundcontroller/gui/buttons/ToggleButtonWidget.java +++ b/src/main/java/com/bvengo/soundcontroller/gui/buttons/ToggleButtonWidget.java @@ -18,8 +18,7 @@ public ToggleButtonWidget(String buttonId, int x, int y, int width, int height, @Override public void onPress(InputWithModifiers input) { - // Natural toggle when button is pressed - this.onPress.onPress(this); - isPressed = !isPressed; + super.onPress(input); + this.isPressed = !this.isPressed; } } diff --git a/src/main/java/com/bvengo/soundcontroller/mixin/OptionsSubScreenMixin.java b/src/main/java/com/bvengo/soundcontroller/mixin/OptionsSubScreenMixin.java deleted file mode 100644 index c3d107f..0000000 --- a/src/main/java/com/bvengo/soundcontroller/mixin/OptionsSubScreenMixin.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.bvengo.soundcontroller.mixin; - -import com.bvengo.soundcontroller.Translations; -import com.bvengo.soundcontroller.gui.AllSoundOptionsScreen; -import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import net.minecraft.client.Minecraft; -import net.minecraft.client.Options; -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.layouts.HeaderAndFooterLayout; -import net.minecraft.client.gui.layouts.LinearLayout; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.options.OptionsSubScreen; -import net.minecraft.client.gui.screens.options.SoundOptionsScreen; -import net.minecraft.network.chat.CommonComponents; -import net.minecraft.network.chat.Component; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(OptionsSubScreen.class) -public class OptionsSubScreenMixin { - @Shadow @Final public HeaderAndFooterLayout layout; - - @Shadow @Final protected Screen lastScreen; - - @Shadow @Final protected Options options; - - @WrapMethod(method = "addFooter") - private void replaceDoneButton(Operation original) { - - //noinspection ConstantValue - if (!((Object)this instanceof SoundOptionsScreen)) { - return; - } - - - Minecraft client = Minecraft.getInstance(); - - LinearLayout directionalLayoutWidget = this.layout.addToFooter(LinearLayout.horizontal().spacing(8)); - - // To individual volume options screen - AllSoundOptionsScreen volumeOptionsScreen = new AllSoundOptionsScreen((SoundOptionsScreen)(Object)this, this.options); - soundcontroller$addLayoutButton(client, directionalLayoutWidget, Translations.SOUND_SCREEN_TITLE, volumeOptionsScreen); - soundcontroller$addLayoutButton(client, directionalLayoutWidget, CommonComponents.GUI_DONE, this.lastScreen); - } - - @Unique - private void soundcontroller$addLayoutButton(Minecraft client, LinearLayout layout, Component text, Screen nextScreen) { - layout.addChild(Button.builder(text, button -> { - client.setScreen(nextScreen); - }).build()); - } -} \ No newline at end of file diff --git a/src/main/java/com/bvengo/soundcontroller/mixin/SoundEngineMixin.java b/src/main/java/com/bvengo/soundcontroller/mixin/SoundEngineMixin.java index 7627c7c..b17b1e6 100644 --- a/src/main/java/com/bvengo/soundcontroller/mixin/SoundEngineMixin.java +++ b/src/main/java/com/bvengo/soundcontroller/mixin/SoundEngineMixin.java @@ -11,8 +11,12 @@ import org.spongepowered.asm.mixin.injection.At; @Mixin(SoundEngine.class) -public class SoundEngineMixin { - @WrapOperation(method = "play(Lnet/minecraft/client/resources/sounds/SoundInstance;)Lnet/minecraft/client/sounds/SoundEngine$PlayResult;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/sounds/SoundEngine;calculateVolume(FLnet/minecraft/sounds/SoundSource;)F")) +public abstract class SoundEngineMixin { + @WrapOperation( + method = "play(Lnet/minecraft/client/resources/sounds/SoundInstance;)Lnet/minecraft/client/sounds/SoundEngine$PlayResult;", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/sounds/SoundEngine;calculateVolume(FLnet/minecraft/sounds/SoundSource;)F")) private float modifyH(SoundEngine instance, float volume, SoundSource category, Operation original, SoundInstance sound) { // h comes from getAdjustedVolume(float volume, Category category) - we can't inject there, because no ID is available float h = original.call(instance, volume, category); diff --git a/src/main/java/com/bvengo/soundcontroller/mixin/SoundOptionsScreenMixin.java b/src/main/java/com/bvengo/soundcontroller/mixin/SoundOptionsScreenMixin.java new file mode 100644 index 0000000..92a51b7 --- /dev/null +++ b/src/main/java/com/bvengo/soundcontroller/mixin/SoundOptionsScreenMixin.java @@ -0,0 +1,38 @@ +package com.bvengo.soundcontroller.mixin; + +import com.bvengo.soundcontroller.Translations; +import com.bvengo.soundcontroller.gui.AllSoundOptionsScreen; +import net.minecraft.client.Options; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.layouts.LinearLayout; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.options.OptionsSubScreen; +import net.minecraft.client.gui.screens.options.SoundOptionsScreen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(SoundOptionsScreen.class) +public abstract class SoundOptionsScreenMixin extends OptionsSubScreen { + public SoundOptionsScreenMixin(Screen screen, Options options, Component component) { + super(screen, options, component); + } + + @Override + protected void addFooter() { + LinearLayout footerLayout = this.layout.addToFooter(LinearLayout.horizontal().spacing(8)); + + // To individual volume options screen + addLayoutButton( + footerLayout, + Translations.SOUND_SCREEN_TITLE, + b -> this.minecraft.setScreen(new AllSoundOptionsScreen(this, this.options))); + addLayoutButton(footerLayout, CommonComponents.GUI_DONE, b -> this.onClose()); + } + + @Unique + private static void addLayoutButton(LinearLayout layout, Component text, Button.OnPress onPress) { + layout.addChild(Button.builder(text, onPress).build()); + } +} diff --git a/src/main/java/com/bvengo/soundcontroller/mixin/SubtitleOverlayMixin.java b/src/main/java/com/bvengo/soundcontroller/mixin/SubtitleOverlayMixin.java index f5fa6dc..936f320 100644 --- a/src/main/java/com/bvengo/soundcontroller/mixin/SubtitleOverlayMixin.java +++ b/src/main/java/com/bvengo/soundcontroller/mixin/SubtitleOverlayMixin.java @@ -12,8 +12,12 @@ import org.spongepowered.asm.mixin.injection.At; @Mixin(SubtitleOverlay.class) -public class SubtitleOverlayMixin { - @WrapOperation(method = "onPlaySound", at=@At(value = "INVOKE", target= "Lnet/minecraft/client/sounds/WeighedSoundEvents;getSubtitle()Lnet/minecraft/network/chat/Component;")) +public abstract class SubtitleOverlayMixin { + @WrapOperation( + method = "onPlaySound", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/sounds/WeighedSoundEvents;getSubtitle()Lnet/minecraft/network/chat/Component;")) private Component replaceSubtitleText(WeighedSoundEvents instance, Operation original, SoundInstance sound) { return SoundController.CONFIG.areSubtitlesEnabled() ? Component.translationArg(sound.getIdentifier()) : original.call(instance); } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 98086da..a7cd47d 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,14 +1,16 @@ { "schemaVersion": 1, - "id": "soundcontroller", - "version": "1.2.4", + "id": "${id}", + "version": "${version}", "icon": "assets/soundcontroller/icon.png", - "name": "Sound Controller", "description": "Have complete control over the volume of all sounds played in the game.", "authors": [ "BVengo" ], + "contributors": [ + "litetex" + ], "contact": { "sources": "https://github.com/BVengo/sound-controller", "issues": "https://github.com/BVengo/sound-controller/issues" @@ -27,21 +29,21 @@ "soundcontroller.mixins.json" ], "depends": { - "fabricloader": ">=0.16.0", - "minecraft": ">=1.21.11", - "java": ">=21", "fabric-api": "*" }, "recommends": { - "modmenu": "*" + "fabricloader": ">=${loader_version}", + "minecraft": ">=${minecraft_version}", + "fabric-api": ">=${fabric_api_version}", + "modmenu": ">=${modmenu_version}" }, "custom": { "modmenu": { "links": { - "modmenu.discord": "https://discord.gg/gyTa5v7kKk" - }, - "update_checker": true + "modmenu.discord": "https://discord.gg/gyTa5v7kKk", + "modmenu.modrinth": "https://modrinth.com/mod/${id}" + } } } } diff --git a/src/main/resources/soundcontroller.mixins.json b/src/main/resources/soundcontroller.mixins.json index 9e4e37c..538cdfb 100644 --- a/src/main/resources/soundcontroller.mixins.json +++ b/src/main/resources/soundcontroller.mixins.json @@ -1,14 +1,14 @@ { - "required": true, - "package": "com.bvengo.soundcontroller.mixin", - "compatibilityLevel": "JAVA_21", - "mixins": [], - "client": [ - "OptionsSubScreenMixin", - "SoundEngineMixin", - "SubtitleOverlayMixin" - ], - "injectors": { - "defaultRequire": 1 - } + "required": true, + "package": "com.bvengo.soundcontroller.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [], + "client": [ + "SoundEngineMixin", + "SoundOptionsScreenMixin", + "SubtitleOverlayMixin" + ], + "injectors": { + "defaultRequire": 1 + } }