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
+ *
+ * - int Value of the slider (0-4)
+ * - int (WidgetID) * 5, segments of the slider
+ *
+ */
+ @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