diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index a8ed2fba3a..1a36c3be9e 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -969,6 +969,19 @@ public interface Client extends GameEngine */ void playSoundEffect(int id, int x, int y, int range); + /** + * Play a sound effect from some point in the world. + * + * @param id the ID of the sound to play. Any int is allowed, but see + * {@link SoundEffectID} for some common ones + * @param x the ground coordinate on the x axis + * @param y the ground coordinate on the y axis + * @param range the number of tiles away that the sound can be heard + * from + * @param delay the amount of frames before the sound starts playing + */ + void playSoundEffect(int id, int x, int y, int range, int delay); + /** * Gets the clients graphic buffer provider. * diff --git a/runelite-api/src/main/java/net/runelite/api/events/AreaSoundEffectPlayed.java b/runelite-api/src/main/java/net/runelite/api/events/AreaSoundEffectPlayed.java new file mode 100644 index 0000000000..488bff8264 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/AreaSoundEffectPlayed.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018, WooxSolo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api.events; + +import lombok.Data; + +@Data +public class AreaSoundEffectPlayed +{ + private int soundId; + private int sceneX; + private int sceneY; + private int range; + private int delay; +} diff --git a/runelite-api/src/main/java/net/runelite/api/events/SoundEffectPlayed.java b/runelite-api/src/main/java/net/runelite/api/events/SoundEffectPlayed.java new file mode 100644 index 0000000000..127a9a2646 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/SoundEffectPlayed.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018, WooxSolo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api.events; + +import lombok.Data; + +@Data +public class SoundEffectPlayed +{ + private int soundId; + private int delay; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java index 6798d4056b..c33753d4a3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java @@ -119,6 +119,8 @@ class DevToolsPanel extends PluginPanel } }); + container.add(plugin.getSoundEffects()); + return container; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java index 3998651fe5..d1cd2a2005 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java @@ -100,6 +100,9 @@ public class DevToolsPlugin extends Plugin @Inject private WorldMapRegionOverlay mapRegionOverlay; + @Inject + private SoundEffectOverlay soundEffectOverlay; + @Inject private EventBus eventBus; @@ -119,13 +122,14 @@ public class DevToolsPlugin extends Plugin private DevToolsButton validMovement; private DevToolsButton lineOfSight; private DevToolsButton cameraPosition; - private DevToolsButton worldMapLocation ; + private DevToolsButton worldMapLocation; private DevToolsButton tileLocation; private DevToolsButton interacting; private DevToolsButton examine; private DevToolsButton detachedCamera; private DevToolsButton widgetInspector; private DevToolsButton varInspector; + private DevToolsButton soundEffects; private NavigationButton navButton; @Provides @@ -166,6 +170,7 @@ public class DevToolsPlugin extends Plugin detachedCamera = new DevToolsButton("Detached Camera"); widgetInspector = new DevToolsButton("Widget Inspector"); varInspector = new DevToolsButton("Var Inspector"); + soundEffects = new DevToolsButton("Sound Effects"); overlayManager.add(overlay); overlayManager.add(locationOverlay); @@ -173,6 +178,7 @@ public class DevToolsPlugin extends Plugin overlayManager.add(cameraOverlay); overlayManager.add(worldMapLocationOverlay); overlayManager.add(mapRegionOverlay); + overlayManager.add(soundEffectOverlay); final DevToolsPanel panel = injector.getInstance(DevToolsPanel.class); @@ -186,17 +192,21 @@ public class DevToolsPlugin extends Plugin .build(); clientToolbar.addNavigation(navButton); + + eventBus.register(soundEffectOverlay); } @Override protected void shutDown() throws Exception { + eventBus.unregister(soundEffectOverlay); overlayManager.remove(overlay); overlayManager.remove(locationOverlay); overlayManager.remove(sceneOverlay); overlayManager.remove(cameraOverlay); overlayManager.remove(worldMapLocationOverlay); overlayManager.remove(mapRegionOverlay); + overlayManager.remove(soundEffectOverlay); clientToolbar.removeNavigation(navButton); } @@ -336,6 +346,12 @@ public class DevToolsPlugin extends Plugin player.getPlayerComposition().setHash(); break; } + case "sound": + { + int id = Integer.parseInt(args[0]); + client.playSoundEffect(id); + break; + } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/SoundEffectOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/SoundEffectOverlay.java new file mode 100644 index 0000000000..fbe2254066 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/SoundEffectOverlay.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2018, WooxSolo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.devtools; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Player; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.events.AreaSoundEffectPlayed; +import net.runelite.api.events.SoundEffectPlayed; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; + +class SoundEffectOverlay extends Overlay +{ + private final static int MAX_LINES = 16; + private final static Color COLOR_SOUND_EFFECT = Color.WHITE; + private final static Color COLOR_AREA_SOUND_EFFECT = Color.YELLOW; + private final static Color COLOR_SILENT_SOUND_EFFECT = Color.GRAY; + + private final Client client; + private final DevToolsPlugin plugin; + private final PanelComponent panelComponent = new PanelComponent(); + + @Inject + SoundEffectOverlay(Client client, DevToolsPlugin plugin) + { + this.client = client; + this.plugin = plugin; + panelComponent.setPreferredSize(new Dimension(200, 0)); + panelComponent.getChildren().add(LineComponent.builder() + .left("Sound Effects") + .leftColor(Color.CYAN) + .build()); + setPosition(OverlayPosition.TOP_LEFT); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!plugin.getSoundEffects().isActive()) + { + return null; + } + + return panelComponent.render(graphics); + } + + @Subscribe + public void onSoundEffectPlayed(SoundEffectPlayed event) + { + if (!plugin.getSoundEffects().isActive()) + { + return; + } + + String text = + "Id: " + event.getSoundId() + + " - D: " + event.getDelay(); + + panelComponent.getChildren().add(LineComponent.builder() + .left(text) + .leftColor(COLOR_SOUND_EFFECT) + .build()); + + checkMaxLines(); + } + + @Subscribe + public void onAreaSoundEffectPlayed(AreaSoundEffectPlayed event) + { + if (!plugin.getSoundEffects().isActive()) + { + return; + } + + Color textColor = COLOR_AREA_SOUND_EFFECT; + + // Check if the player is within range to hear the sound + Player localPlayer = client.getLocalPlayer(); + if (localPlayer != null) + { + LocalPoint lp = localPlayer.getLocalLocation(); + if (lp != null) + { + int sceneX = lp.getSceneX(); + int sceneY = lp.getSceneY(); + int distance = Math.abs(sceneX - event.getSceneX()) + Math.abs(sceneY - event.getSceneY()); + if (distance > event.getRange()) + { + textColor = COLOR_SILENT_SOUND_EFFECT; + } + } + } + + String text = + "Id: " + event.getSoundId() + + " - L: " + event.getSceneX() + "," + event.getSceneY() + + " - R: " + event.getRange() + + " - D: " + event.getDelay(); + + panelComponent.getChildren().add(LineComponent.builder() + .left(text) + .leftColor(textColor) + .build()); + + checkMaxLines(); + } + + private void checkMaxLines() + { + while (panelComponent.getChildren().size() > MAX_LINES) + { + panelComponent.getChildren().remove(1); + } + } +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/PlaySoundEffectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/SoundEffectMixin.java similarity index 57% rename from runelite-mixins/src/main/java/net/runelite/mixins/PlaySoundEffectMixin.java rename to runelite-mixins/src/main/java/net/runelite/mixins/SoundEffectMixin.java index 79db5b70b9..8effc5a812 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/PlaySoundEffectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/SoundEffectMixin.java @@ -24,25 +24,41 @@ */ package net.runelite.mixins; +import net.runelite.api.events.AreaSoundEffectPlayed; +import net.runelite.api.events.SoundEffectPlayed; +import net.runelite.api.mixins.FieldHook; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSSoundEffect; @Mixin(RSClient.class) -public abstract class PlaySoundEffectMixin implements RSClient +public abstract class SoundEffectMixin implements RSClient { + @Shadow("clientInstance") + private static RSClient client; + + @Inject + private static int lastSoundEffectCount; @Inject @Override public void playSoundEffect(int id) { - playSoundEffect(id, 0, 0, 0); + playSoundEffect(id, 0, 0, 0, 0); } @Inject @Override public void playSoundEffect(int id, int x, int y, int range) + { + playSoundEffect(id, x, y, range, 0); + } + + @Inject + @Override + public void playSoundEffect(int id, int x, int y, int range, int delay) { int position = ((x & 255) << 16) + ((y & 255) << 8) + (range & 255); @@ -54,11 +70,51 @@ public abstract class PlaySoundEffectMixin implements RSClient int queuedSoundEffectCount = getQueuedSoundEffectCount(); queuedSoundEffectIDs[queuedSoundEffectCount] = id; - queuedSoundEffectLoops[queuedSoundEffectCount] = 0; - queuedSoundEffectDelays[queuedSoundEffectCount] = 0; + queuedSoundEffectLoops[queuedSoundEffectCount] = 1; + queuedSoundEffectDelays[queuedSoundEffectCount] = delay; audioEffects[queuedSoundEffectCount] = null; soundLocations[queuedSoundEffectCount] = position; setQueuedSoundEffectCount(queuedSoundEffectCount + 1); } + + @FieldHook("queuedSoundEffectCount") + @Inject + public static void queuedSoundEffectCountChanged(int idx) + { + int soundCount = client.getQueuedSoundEffectCount(); + if (soundCount == lastSoundEffectCount + 1) + { + int soundIndex = soundCount - 1; + int packedLocation = client.getSoundLocations()[soundIndex]; + + if (packedLocation == 0) + { + // Regular sound effect + + SoundEffectPlayed event = new SoundEffectPlayed(); + event.setSoundId(client.getQueuedSoundEffectIDs()[soundIndex]); + event.setDelay(client.getQueuedSoundEffectDelays()[soundIndex]); + client.getCallbacks().post(event); + } + else + { + // Area sound effect + + int x = (packedLocation >> 16) & 0xFF; + int y = (packedLocation >> 8) & 0xFF; + int range = (packedLocation) & 0xFF; + + AreaSoundEffectPlayed event = new AreaSoundEffectPlayed(); + event.setSoundId(client.getQueuedSoundEffectIDs()[soundIndex]); + event.setSceneX(x); + event.setSceneY(y); + event.setRange(range); + event.setDelay(client.getQueuedSoundEffectDelays()[soundIndex]); + client.getCallbacks().post(event); + } + } + + lastSoundEffectCount = soundCount; + } }