diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptEvent.java b/runelite-api/src/main/java/net/runelite/api/ScriptEvent.java index 2af2c1bd92..b9fa224ea5 100644 --- a/runelite-api/src/main/java/net/runelite/api/ScriptEvent.java +++ b/runelite-api/src/main/java/net/runelite/api/ScriptEvent.java @@ -61,4 +61,9 @@ public interface ScriptEvent * @see net.runelite.api.events.MenuOptionClicked */ String getOpbase(); + + /** + * Parent relative x coordinate for mouse related events + */ + int getMouseX(); } diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptID.java b/runelite-api/src/main/java/net/runelite/api/ScriptID.java index 657ec2dcb8..9d056b34e4 100644 --- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java +++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java @@ -83,6 +83,16 @@ public final class ScriptID @ScriptArguments(integer = 2) public static final int MESSAGE_LAYER_CLOSE = 299; + /** + * Sets the background for sound option bars + * + */ + @ScriptArguments(integer = 6) + public static final int OPTIONS_ALLSOUNDS = 358; + /** * Readies the chatbox panel for things like the chatbox input * Inverse of MESSAGE_LAYER_CLOSE diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java index e4ad3f3baa..f44e61e448 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java @@ -553,7 +553,7 @@ public interface Widget void setOnMouseOverListener(Object... args); /** - * Sets a script to be ran every frame when the mouse is in the widget bounds + * Sets a script to be ran every client tick when the mouse is in the widget bounds * * @param args A ScriptID, then the args for the script */ @@ -567,7 +567,7 @@ public interface Widget void setOnMouseLeaveListener(Object... args); /** - * Sets a script to be ran every frame + * Sets a script to be ran every client tick * * @param args A ScriptID, then the args for the script */ @@ -823,4 +823,31 @@ public interface Widget * Can widgets under this widgets be scrolled in this widgets bounding box */ void setNoScrollThrough(boolean noScrollThrough); + + /** + * {@link net.runelite.api.VarPlayer}s that triggers this widgets varTransmitListener + */ + void setVarTransmitTrigger(int ...trigger); + + /** + * Sets a script to be ran the first client tick the mouse is held ontop of this widget + * + * @param args A ScriptID, then the args for the script + */ + void setOnClickListener(Object ...args); + + /** + * Sets a script to be ran the every client tick the mouse is held ontop of this widget, + * except the first client tick. + * + * @param args A ScriptID, then the args for the script + */ + void setOnHoldListener(Object ...args); + + /** + * Sets a script to be ran the first client tick the mouse is not held ontop of this widget + * + * @param args A ScriptID, then the args for the script + */ + void setOnReleaseListener(Object ...args); } diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java index 81bfc752b9..4c8b3efc80 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java @@ -143,6 +143,7 @@ public class WidgetID public static final int ITEMS_KEPT_ON_DEATH_GROUP_ID = 4; public static final int SEED_VAULT_GROUP_ID = 631; public static final int EXPLORERS_RING_ALCH_GROUP_ID = 483; + public static final int OPTIONS_GROUP_ID = 261; static class WorldMap { @@ -834,4 +835,11 @@ public class WidgetID { static final int INVENTORY = 7; } + + static class Options + { + static final int MUSIC_SLIDER = 44; + static final int SOUND_EFFECT_SLIDER = 50; + static final int AREA_SOUND_SLIDER = 56; + } } diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java index db1b7a57f6..32281cd2c8 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java @@ -498,7 +498,11 @@ public enum WidgetInfo SEED_VAULT_TITLE_CONTAINER(WidgetID.SEED_VAULT_GROUP_ID, WidgetID.SeedVault.TITLE_CONTAINER), SEED_VAULT_ITEM_CONTAINER(WidgetID.SEED_VAULT_GROUP_ID, WidgetID.SeedVault.ITEM_CONTAINER), - SEED_VAULT_ITEM_TEXT(WidgetID.SEED_VAULT_GROUP_ID, WidgetID.SeedVault.ITEM_TEXT); + SEED_VAULT_ITEM_TEXT(WidgetID.SEED_VAULT_GROUP_ID, WidgetID.SeedVault.ITEM_TEXT), + + OPTIONS_MUSIC_SLIDER(WidgetID.OPTIONS_GROUP_ID, WidgetID.Options.MUSIC_SLIDER), + OPTIONS_SOUND_EFFECT_SLIDER(WidgetID.OPTIONS_GROUP_ID, WidgetID.Options.SOUND_EFFECT_SLIDER), + OPTIONS_AREA_SOUND_SLIDER(WidgetID.OPTIONS_GROUP_ID, WidgetID.Options.AREA_SOUND_SLIDER); private final int groupId; private final int childId; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicConfig.java index 20b50545d0..da764e62c9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicConfig.java @@ -27,20 +27,15 @@ package net.runelite.client.plugins.music; import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; -import net.runelite.client.config.Range; @ConfigGroup("music") public interface MusicConfig extends Config { @ConfigItem( keyName = "musicVolume", - name = "Music Volume", - description = "Overrides music volume in game with more granular control", - position = 1 - ) - @Range( - min = 0, - max = 255 + name = "", + description = "", + hidden = true ) default int getMusicVolume() { @@ -48,32 +43,47 @@ public interface MusicConfig extends Config } @ConfigItem( - keyName = "soundEffectVolume", - name = "Sound Effect Volume", - description = "Overrides the sound effect volume in game with more granular control", - position = 2 + keyName = "musicVolume", + name = "", + description = "", + hidden = true ) - @Range( - min = 0, - max = 127 + void setMusicVolume(int vol); + + @ConfigItem( + keyName = "soundEffectVolume", + name = "", + description = "", + hidden = true ) default int getSoundEffectVolume() { return 0; } + @ConfigItem( + keyName = "soundEffectVolume", + name = "", + description = "", + hidden = true + ) + void setSoundEffectVolume(int val); + + @ConfigItem( + keyName = "areaSoundEffectVolume", + name = "", + description = "", + hidden = true + ) + default int getAreaSoundEffectVolume() + { + return 0; + } @ConfigItem( keyName = "areaSoundEffectVolume", - name = "Area Sound Effect Volume", - description = "Overrides the area sound effect volume in game with more granular control", - position = 3 + name = "", + description = "", + hidden = true ) - @Range( - min = 0, - max = 127 - ) - default int getAreaSoundEffectVolume() - { - return 0; - } + void setAreaSoundEffectVolume(int vol); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicPlugin.java index 80ef0c22e3..167032278a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicPlugin.java @@ -29,23 +29,30 @@ import com.google.inject.Provides; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; +import java.util.function.BiConsumer; +import java.util.function.ToIntFunction; import java.util.stream.Collectors; import javax.inject.Inject; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.ScriptID; import net.runelite.api.SoundEffectID; import net.runelite.api.SpriteID; import net.runelite.api.VarClientInt; +import net.runelite.api.VarPlayer; import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.ScriptCallbackEvent; import net.runelite.api.events.VarClientIntChanged; import net.runelite.api.events.VolumeChanged; import net.runelite.api.events.WidgetLoaded; import net.runelite.api.widgets.JavaScriptCallback; import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetConfig; import net.runelite.api.widgets.WidgetID; import net.runelite.api.widgets.WidgetInfo; import net.runelite.api.widgets.WidgetPositionMode; @@ -85,17 +92,15 @@ public class MusicPlugin extends Plugin private MusicState currentMusicFilter = MusicState.ALL; - private int lastMusicVolume; - private int lastEffectVolume; - private int lastAreaEffectVolume; - @Override protected void startUp() { - lastMusicVolume = lastEffectVolume = lastAreaEffectVolume = -1; - - clientThread.invoke(this::addMusicButtons); - clientThread.invoke(this::applyMusicVolumeConfig); + clientThread.invoke(() -> + { + addMusicButtons(); + applyMusicVolumeConfig(); + updateMusicOptions(); + }); } @Override @@ -108,6 +113,8 @@ public class MusicPlugin extends Plugin } tracks = null; + + clientThread.invoke(this::teardownMusicOptions); } @Provides @@ -138,6 +145,10 @@ public class MusicPlugin extends Plugin currentMusicFilter = MusicState.ALL; addMusicButtons(); } + if (widgetLoaded.getGroupId() == WidgetID.OPTIONS_GROUP_ID) + { + updateMusicOptions(); + } } private void addMusicButtons() @@ -206,26 +217,24 @@ public class MusicPlugin extends Plugin private void applyMusicVolumeConfig() { int musicVolume = musicConfig.getMusicVolume(); - // Set the volume if it is >0, or if it was >0 and is now going back to 0 - if (musicVolume > 0 || lastMusicVolume > 0) + if (musicVolume > 0) { - client.setMusicVolume(musicVolume); - lastMusicVolume = musicVolume; + client.setMusicVolume(musicVolume - 1); } int soundEffectVolume = musicConfig.getSoundEffectVolume(); - if (soundEffectVolume > 0 || lastEffectVolume > 0) + if (soundEffectVolume > 0) { - client.setSoundEffectVolume(soundEffectVolume); - lastEffectVolume = soundEffectVolume; + client.setSoundEffectVolume(soundEffectVolume - 1); } int areaSoundEffectVolume = musicConfig.getAreaSoundEffectVolume(); - if (areaSoundEffectVolume > 0 || lastAreaEffectVolume > 0) + if (areaSoundEffectVolume > 0) { - client.setAreaSoundEffectVolume(areaSoundEffectVolume); - lastAreaEffectVolume = areaSoundEffectVolume; + client.setAreaSoundEffectVolume(areaSoundEffectVolume - 1); } + + updateMusicOptions(); } private boolean isOnMusicTab() @@ -351,4 +360,176 @@ public class MusicPlugin extends Plugin private final String name; private final int spriteID; } -} + + @RequiredArgsConstructor + @Getter + private enum MusicSlider + { + MUSIC(WidgetInfo.OPTIONS_MUSIC_SLIDER, VarPlayer.MUSIC_VOLUME, MusicConfig::getMusicVolume, MusicConfig::setMusicVolume, 255), + AREA(WidgetInfo.OPTIONS_AREA_SOUND_SLIDER, VarPlayer.AREA_EFFECT_VOLUME, MusicConfig::getAreaSoundEffectVolume, MusicConfig::setAreaSoundEffectVolume, 127), + EFFECT(WidgetInfo.OPTIONS_SOUND_EFFECT_SLIDER, VarPlayer.SOUND_EFFECT_VOLUME, MusicConfig::getSoundEffectVolume, MusicConfig::setSoundEffectVolume, 127); + + private final WidgetInfo widgetID; + private final VarPlayer var; + private final ToIntFunction getter; + private final BiConsumer setter; + private final int max; + + @Setter + private Widget handle; + + @Setter + private Widget track; + + private static int PADDING = 8; + + private int getX() + { + return getTrack().getRelativeX() + PADDING; + } + + private int getWidth() + { + return getTrack().getWidth() - (PADDING * 2) - handle.getWidth(); + } + } + + private void teardownMusicOptions() + { + for (MusicSlider slider : MusicSlider.values()) + { + Widget icon = client.getWidget(slider.getWidgetID()); + if (icon == null) + { + return; + } + + if (slider.getHandle() != null) + { + { + Widget handle = slider.getHandle(); + Widget[] siblings = handle.getParent().getChildren(); + if (siblings.length < handle.getIndex() || siblings[handle.getIndex()] != handle) + { + continue; + } + siblings[slider.getTrack().getIndex()] = null; + siblings[handle.getIndex()] = null; + } + + Object[] init = icon.getOnLoadListener(); + init[1] = slider.getWidgetID().getId(); + + // Readd the var transmit triggers and rerun options_allsounds + client.runScript(init); + slider.setHandle(null); + slider.setTrack(null); + } + } + } + + private void updateMusicOptions() + { + for (MusicSlider slider : MusicSlider.values()) + { + Widget icon = client.getWidget(slider.getWidgetID()); + if (icon == null) + { + return; + } + + Widget handle = slider.getHandle(); + if (handle != null) + { + Widget[] siblings = handle.getParent().getChildren(); + if (siblings.length < handle.getIndex() || siblings[handle.getIndex()] != handle) + { + handle = null; + } + } + if (handle == null) + { + Object[] init = icon.getOnLoadListener(); + icon.setVarTransmitTrigger((int[]) null); + + Widget track = icon.getParent().createChild(-1, WidgetType.TEXT); + slider.setTrack(track); + handle = icon.getParent().createChild(-1, WidgetType.GRAPHIC); + slider.setHandle(handle); + + { + // First widget of the track + int wid = (Integer) init[2]; + Widget w = client.getWidget(WidgetInfo.TO_GROUP(wid), WidgetInfo.TO_CHILD(wid)); + + track.setOriginalX(w.getRelativeX()); + track.setOriginalY(w.getRelativeY()); + } + { + // Last widget of the track + int wid = (Integer) init[6]; + Widget w = client.getWidget(WidgetInfo.TO_GROUP(wid), WidgetInfo.TO_CHILD(wid)); + + track.setOriginalWidth((w.getRelativeX() + w.getWidth()) - track.getOriginalX()); + } + + track.setOriginalHeight(16); + track.setNoClickThrough(true); + track.revalidate(); + + handle.setSpriteId(SpriteID.OPTIONS_ZOOM_SLIDER_THUMB); + handle.setOriginalWidth(16); + handle.setOriginalHeight(16); + handle.setClickMask(WidgetConfig.DRAG); + + JavaScriptCallback move = ev -> + { + int newVal = ((ev.getMouseX() - MusicSlider.PADDING - (slider.getHandle().getWidth() / 2)) * slider.getMax()) + / slider.getWidth(); + if (newVal < 0) + { + newVal = 0; + } + if (newVal > slider.getMax()) + { + newVal = slider.getMax(); + } + + // We store +1 so we can tell the difference between 0 and muted + slider.getSetter().accept(musicConfig, newVal + 1); + applyMusicVolumeConfig(); + }; + + track.setOnClickListener(move); + track.setOnHoldListener(move); + track.setOnReleaseListener(move); + track.setHasListener(true); + + client.runScript(ScriptID.OPTIONS_ALLSOUNDS, -1, init[2], init[3], init[4], init[5], init[6]); + } + + int value = slider.getGetter().applyAsInt(musicConfig) - 1; + if (value <= -1) + { + // Use the vanilla value + value = ((4 - client.getVar(slider.getVar())) * slider.getMax()) / 4; + } + + int newX = ((value * slider.getWidth()) / slider.getMax()) + slider.getX(); + slider.getHandle().setOriginalX(newX); + slider.getHandle().setOriginalY(slider.getTrack().getOriginalY()); + slider.getHandle().revalidate(); + } + } + + @Subscribe + public void onScriptCallbackEvent(ScriptCallbackEvent ev) + { + switch (ev.getEventName()) + { + case "optionsAllSounds": + // We have to override this script because it gets invoked periodically from the server + client.getIntStack()[client.getIntStackSize() - 1] = -1; + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/scripts/options_allsounds.hash b/runelite-client/src/main/scripts/options_allsounds.hash new file mode 100644 index 0000000000..84863d9bd2 --- /dev/null +++ b/runelite-client/src/main/scripts/options_allsounds.hash @@ -0,0 +1 @@ +950ADB6A28E029005D24F99A65EF4D2AC4486EDC680D8770F4435F0300AA1299 \ No newline at end of file diff --git a/runelite-client/src/main/scripts/options_allsounds.rs2asm b/runelite-client/src/main/scripts/options_allsounds.rs2asm new file mode 100644 index 0000000000..2d4e94e9ce --- /dev/null +++ b/runelite-client/src/main/scripts/options_allsounds.rs2asm @@ -0,0 +1,140 @@ +.id 358 +.int_stack_count 6 +.string_stack_count 0 +.int_var_count 6 +.string_var_count 0 +; callback "optionsAllSounds" +; Used by the MusicPlugin to hide the vanilla (blue) volume handles +; Enable the MusicPlugin and go to the volume options panel. There should +; only be a green handle on the slider + iload 0 + sconst "optionsAllSounds" + runelite_callback + istore 0 + iload 0 + iconst 4 + if_icmpeq LABEL4 + jump LABEL20 +LABEL4: + iconst 687 + iload 1 + if_setgraphic + iconst 693 + iload 2 + if_setgraphic + iconst 694 + iload 3 + if_setgraphic + iconst 695 + iload 4 + if_setgraphic + iconst 696 + iload 5 + if_setgraphic + jump LABEL115 +LABEL20: + iload 0 + iconst 3 + if_icmpeq LABEL24 + jump LABEL40 +LABEL24: + iconst 692 + iload 1 + if_setgraphic + iconst 688 + iload 2 + if_setgraphic + iconst 694 + iload 3 + if_setgraphic + iconst 695 + iload 4 + if_setgraphic + iconst 696 + iload 5 + if_setgraphic + jump LABEL115 +LABEL40: + iload 0 + iconst 2 + if_icmpeq LABEL44 + jump LABEL60 +LABEL44: + iconst 692 + iload 1 + if_setgraphic + iconst 693 + iload 2 + if_setgraphic + iconst 689 + iload 3 + if_setgraphic + iconst 695 + iload 4 + if_setgraphic + iconst 696 + iload 5 + if_setgraphic + jump LABEL115 +LABEL60: + iload 0 + iconst 1 + if_icmpeq LABEL64 + jump LABEL80 +LABEL64: + iconst 692 + iload 1 + if_setgraphic + iconst 693 + iload 2 + if_setgraphic + iconst 694 + iload 3 + if_setgraphic + iconst 690 + iload 4 + if_setgraphic + iconst 696 + iload 5 + if_setgraphic + jump LABEL115 +LABEL80: + iload 0 + iconst 0 + if_icmpeq LABEL84 + jump LABEL100 +LABEL84: + iconst 692 + iload 1 + if_setgraphic + iconst 693 + iload 2 + if_setgraphic + iconst 694 + iload 3 + if_setgraphic + iconst 695 + iload 4 + if_setgraphic + iconst 691 + iload 5 + if_setgraphic + jump LABEL115 +LABEL100: + iconst 692 + iload 1 + if_setgraphic + iconst 693 + iload 2 + if_setgraphic + iconst 694 + iload 3 + if_setgraphic + iconst 695 + iload 4 + if_setgraphic + iconst 696 + iload 5 + if_setgraphic +LABEL115: + return