Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion runelite-plugin.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ displayName=Custom Weapon SFX
author=H-Mill
description=Plays custom sound effects on weapon hits, misses, and max hits — configurable per weapon with triggers, volume, and chance
tags=custom,weapon,sound,sfx,squeaky,toy,max hit,on hit,damage,miss,zero,pvp,death
version=2.3
version=2.4
plugins=com.customweaponsfx.CustomWeaponSfxPlugin
build=standard
78 changes: 70 additions & 8 deletions src/main/java/com/customweaponsfx/CustomWeaponSfxPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
Expand All @@ -29,11 +30,11 @@
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.PluginPanel;
import net.runelite.client.util.AsyncBufferedImage;
import net.runelite.client.util.LinkBrowser;

public class CustomWeaponSfxPanel extends PluginPanel
{
static final String BUNDLED_PREFIX = "bundled:";
private static final String BUILTIN_SUFFIX = " (built-in)";
static final String BUILTIN_SUFFIX = " (built-in)";

static final String RECEIVED_GROUPS_PREFIX = "defaultReceived";

Expand All @@ -47,6 +48,7 @@ public class CustomWeaponSfxPanel extends PluginPanel
private final Runnable onOpenSearch;
private final Runnable onAddEquipped;
private final Consumer<Integer> onRemoveWeapon;
private final Runnable onRefreshSounds;
private final BiConsumer<String, Integer> onTestSound;
private final Runnable onReset;
private final Consumer<Boolean> onIgnoreSmallMaxHitsToggled;
Expand All @@ -59,11 +61,14 @@ public class CustomWeaponSfxPanel extends PluginPanel

private final JPanel weaponListPanel;
private final JPanel mainContent;
private final JPanel updatePanelWrapper;

public CustomWeaponSfxPanel(ConfigManager configManager,
ItemManager itemManager,
Runnable onOpenSearch,
Runnable onAddEquipped,
Consumer<Integer> onRemoveWeapon,
Runnable onRefreshSounds,
BiConsumer<String, Integer> onTestSound,
Runnable onReset,
Consumer<Boolean> onIgnoreSmallMaxHitsToggled,
Expand All @@ -75,6 +80,7 @@ public CustomWeaponSfxPanel(ConfigManager configManager,
this.onOpenSearch = onOpenSearch;
this.onAddEquipped = onAddEquipped;
this.onRemoveWeapon = onRemoveWeapon;
this.onRefreshSounds = onRefreshSounds;
this.onTestSound = onTestSound;
this.onReset = onReset;
this.onIgnoreSmallMaxHitsToggled = onIgnoreSmallMaxHitsToggled;
Expand All @@ -92,7 +98,14 @@ public CustomWeaponSfxPanel(ConfigManager configManager,
mainContent.add(buildTopPanel());
mainContent.add(weaponListPanel);

add(mainContent, BorderLayout.NORTH);
updatePanelWrapper = new JPanel(new BorderLayout());
updatePanelWrapper.setVisible(false);

JPanel root = new JPanel();
root.setLayout(new javax.swing.BoxLayout(root, javax.swing.BoxLayout.Y_AXIS));
root.add(updatePanelWrapper);
root.add(mainContent);
add(root, BorderLayout.NORTH);
}

private JPanel buildTopPanel()
Expand All @@ -107,6 +120,30 @@ private JPanel buildTopPanel()
top.add(title);
top.add(Box.createVerticalStrut(4));

JLabel customSoundDirections = new JLabel("<html>Want a custom sfx?<br>" +
"1. Place <b>.wav</b> files in:</html>");
customSoundDirections.setFont(customSoundDirections.getFont().deriveFont(14f));
customSoundDirections.setAlignmentX(Component.LEFT_ALIGNMENT);
top.add(customSoundDirections);

JButton folderLink = new JButton("<html><u>.runelite/customweaponsfx/</u></html>");
folderLink.setFont(folderLink.getFont().deriveFont(14f));
folderLink.setBorderPainted(false);
folderLink.setContentAreaFilled(false);
folderLink.setFocusPainted(false);
folderLink.setForeground(Color.CYAN);
folderLink.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
folderLink.setAlignmentX(Component.LEFT_ALIGNMENT);
folderLink.addActionListener(e -> LinkBrowser.open(CustomWeaponSfxPlugin.SOUNDS_DIR.toString()));
top.add(folderLink);

JLabel customSoundDirections2 = new JLabel("<html>2. Click Refresh Sounds<br>" +
"3. Click an Add method below and configure it</html>");
customSoundDirections2.setFont(customSoundDirections2.getFont().deriveFont(14f));
customSoundDirections2.setAlignmentX(Component.LEFT_ALIGNMENT);
top.add(customSoundDirections2);
top.add(Box.createVerticalStrut(8));

JPanel btnRow = new JPanel();
btnRow.setLayout(new javax.swing.BoxLayout(btnRow, javax.swing.BoxLayout.X_AXIS));
btnRow.setAlignmentX(Component.LEFT_ALIGNMENT);
Expand Down Expand Up @@ -145,6 +182,10 @@ private JPanel buildTopPanel()
if (confirm == JOptionPane.YES_OPTION) onReset.run();
});
resetRow.add(resetBtn);
resetRow.add(Box.createHorizontalStrut(4));
JButton refreshBtn = new JButton("Refresh Sounds");
refreshBtn.addActionListener(e -> onRefreshSounds.run());
resetRow.add(refreshBtn);

top.add(resetRow);
top.add(Box.createVerticalStrut(6));
Expand Down Expand Up @@ -190,6 +231,31 @@ private JPanel buildTopPanel()
return top;
}

public void showCorrectPanel(String savedVersion, String currentVersion, String patchNotes, Runnable onDismiss)
{
if (!currentVersion.isEmpty() && !currentVersion.equals(savedVersion))
{
updatePanelWrapper.removeAll();
updatePanelWrapper.add(new CustomWeaponSfxUpdatePanel(currentVersion, patchNotes, () ->
{
onDismiss.run();
updatePanelWrapper.setVisible(false);
mainContent.setVisible(true);
revalidate();
repaint();
}), BorderLayout.CENTER);
updatePanelWrapper.setVisible(true);
mainContent.setVisible(false);
}
else
{
updatePanelWrapper.setVisible(false);
mainContent.setVisible(true);
}
revalidate();
repaint();
}

public void resetToggles()
{
SwingUtilities.invokeLater(() ->
Expand Down Expand Up @@ -724,16 +790,12 @@ private String configToDisplay(String configValue)
{
if (configValue == null || configValue.isEmpty())
return "squeak" + BUILTIN_SUFFIX;
if (configValue.startsWith(BUNDLED_PREFIX))
return configValue.substring(BUNDLED_PREFIX.length()) + BUILTIN_SUFFIX;
return configValue;
}

private static String displayToConfig(String display)
{
if (display == null) return BUNDLED_PREFIX + "squeak";
if (display.endsWith(BUILTIN_SUFFIX))
return BUNDLED_PREFIX + display.substring(0, display.length() - BUILTIN_SUFFIX.length());
if (display == null) return "squeak" + BUILTIN_SUFFIX;
return display;
}
}
105 changes: 98 additions & 7 deletions src/main/java/com/customweaponsfx/CustomWeaponSfxPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.File;
import java.io.InputStream;
import java.util.Properties;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
Expand Down Expand Up @@ -44,6 +49,7 @@
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.ClientToolbar;
import net.runelite.client.ui.NavigationButton;
import net.runelite.client.RuneLite;
import net.runelite.client.util.ImageUtil;

@Slf4j
Expand All @@ -56,6 +62,8 @@
public class CustomWeaponSfxPlugin extends Plugin
{
static final String CONFIG_GROUP = "customweaponsfx";
static final String CONFIG_KEY_VERSION = "version";
static final File SOUNDS_DIR = new File(RuneLite.RUNELITE_DIR, "customweaponsfx");

private static final int VARP_SPEC_PERCENT = 300;
private static final int PENDING_SPEC_TIMEOUT_TICKS = 10;
Expand All @@ -68,6 +76,7 @@ public class CustomWeaponSfxPlugin extends Plugin
@Inject private ConfigManager configManager;
@Inject private ItemManager itemManager;
@Inject private WeaponChatboxSearch weaponSearch;
@Inject private Gson gson;

private ExecutorService executor;
private CustomWeaponSfxPanel panel;
Expand All @@ -77,6 +86,7 @@ public class CustomWeaponSfxPlugin extends Plugin
private final List<TriggerGroup> receivedGroups = new CopyOnWriteArrayList<>();
private List<String> bundledSounds = new ArrayList<>();
private List<String> availableSounds = new ArrayList<>();
private String currentVersion = "";

private int lastSpecPct = -1;
private int pendingSpecItemId = -1;
Expand All @@ -96,24 +106,39 @@ public class CustomWeaponSfxPlugin extends Plugin
@Override
protected void startUp()
{
SOUNDS_DIR.mkdirs();
executor = Executors.newSingleThreadExecutor();

try
{
Properties props = new Properties();
try (InputStream is = CustomWeaponSfxPlugin.class.getResourceAsStream("/customweaponsfx_version.txt"))
{
if (is != null) props.load(is);
}
currentVersion = props.getProperty("version", "");
}
catch (Exception e)
{
log.debug("Could not load plugin version", e);
}

loadWeaponEntries();
loadDefaultGroups(receivedGroups, CustomWeaponSfxPanel.RECEIVED_GROUPS_PREFIX);
seedFirstRunGroups(receivedGroups, CustomWeaponSfxPanel.RECEIVED_GROUPS_PREFIX, EnumSet.of(Triggers.REGULAR_ZERO));
bundledSounds = scanBundledSounds();
availableSounds = new ArrayList<>();
availableSounds = scanSounds();

String ignoreVal = configManager.getConfiguration(CONFIG_GROUP, "ignoreSmallMaxHits");
ignoreSmallMaxHits = ignoreVal == null || Boolean.parseBoolean(ignoreVal);

String ignoreZeroVal = configManager.getConfiguration(CONFIG_GROUP, "ignoreReceivedZeroWithPrayer");
ignoreReceivedZeroWithPrayer = ignoreZeroVal == null || Boolean.parseBoolean(ignoreZeroVal);

String ignoreThrallZeroVal = configManager.getConfiguration(CONFIG_GROUP, "ignoreZeroWhileThrallActive");
ignoreZeroWhileThrallActive = ignoreThrallZeroVal == null || Boolean.parseBoolean(ignoreThrallZeroVal);

panel = new CustomWeaponSfxPanel(configManager, itemManager, this::openWeaponSearch, this::addEquippedWeapon, this::removeWeapon, this::playSoundFile, this::resetAllData, this::onIgnoreSmallMaxHitsChanged, this::onIgnoreReceivedZeroWithPrayerChanged, this::onIgnoreZeroWhileThrallActiveChanged);
panel = new CustomWeaponSfxPanel(configManager, itemManager,
this::openWeaponSearch, this::addEquippedWeapon, this::removeWeapon,
this::refreshSounds, this::playSoundFile, this::resetAllData, this::onIgnoreSmallMaxHitsChanged,
this::onIgnoreReceivedZeroWithPrayerChanged, this::onIgnoreZeroWhileThrallActiveChanged);

navButton = NavigationButton.builder()
.tooltip("Custom Weapon SFX")
Expand All @@ -125,6 +150,12 @@ protected void startUp()

panel.rebuild(new ArrayList<>(weaponEntries), availableSounds, bundledSounds, receivedGroups);

String savedVersion = getSavedVersionString();
String currentVer = currentVersion;
String notes = loadPatchNotes(currentVer);
SwingUtilities.invokeLater(() ->
panel.showCorrectPanel(savedVersion, currentVer, notes, () -> setSavedVersionString(currentVer)));

clientThread.invoke(() -> lastSpecPct = client.getVarpValue(VARP_SPEC_PERCENT));

log.debug("Custom Weapon SFX started!");
Expand Down Expand Up @@ -487,6 +518,7 @@ private void removeWeapon(int itemId)

private void refreshSounds()
{
availableSounds = scanSounds();
rebuildPanel();
}

Expand Down Expand Up @@ -765,17 +797,76 @@ private void playSoundFile(String soundFile, int volume)
if (volume == 0) return;
float gain = volumeToGain(volume);

if (soundFile == null || soundFile.isEmpty() || soundFile.startsWith(CustomWeaponSfxPanel.BUNDLED_PREFIX))
if (soundFile == null || soundFile.isEmpty() || soundFile.endsWith(CustomWeaponSfxPanel.BUILTIN_SUFFIX))
{
String name = (soundFile == null || soundFile.isEmpty())
? "squeak.wav"
: soundFile.substring(CustomWeaponSfxPanel.BUNDLED_PREFIX.length()) + ".wav";
: soundFile.substring(0, soundFile.length() - CustomWeaponSfxPanel.BUILTIN_SUFFIX.length()) + ".wav";
executor.submit(() ->
{
try { audioPlayer.play(CustomWeaponSfxPlugin.class, name, gain); }
catch (Exception e) { log.debug("Failed to play bundled sound {}", name, e); }
});
}
else
{
File f = new File(SOUNDS_DIR, soundFile + ".wav");
if (!f.exists())
{
log.debug("Sound file missing: {}", f.getAbsolutePath());
return;
}
executor.submit(() ->
{
try { audioPlayer.play(f, gain); }
catch (Exception e) { log.debug("Failed to play {}", soundFile, e); }
});
}
}

public String getCurrentVersionString() { return currentVersion; }

public String getSavedVersionString()
{
String v = configManager.getConfiguration(CONFIG_GROUP, CONFIG_KEY_VERSION);
return v == null ? "" : v;
}

public void setSavedVersionString(String version)
{
configManager.setConfiguration(CONFIG_GROUP, CONFIG_KEY_VERSION, version);
}

private String loadPatchNotes(String version)
{
try (InputStream is = CustomWeaponSfxPlugin.class.getResourceAsStream("patch_notes.json"))
{
if (is == null) return "";
String json = new String(is.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8);
JsonObject obj = gson.fromJson(json, JsonObject.class);
if (obj.has(version)) return obj.get(version).getAsString();
}
catch (Exception e)
{
log.debug("Could not load patch notes", e);
}
return "";
}

private List<String> scanSounds()
{
List<String> sounds = new ArrayList<>();
File[] files = SOUNDS_DIR.listFiles(f -> f.isFile() && f.getName().toLowerCase().endsWith(".wav"));
if (files != null)
{
for (File f : files)
{
String name = f.getName();
sounds.add(name.substring(0, name.length() - 4));
}
sounds.sort(String.CASE_INSENSITIVE_ORDER);
}
return sounds;
}

private List<String> scanBundledSounds()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,13 @@ class CustomWeaponSfxUpdatePanel extends JPanel
dismissBtn.setAlignmentX(Component.CENTER_ALIGNMENT);
dismissBtn.addActionListener(e -> onDismiss.run());

JLabel dismissHint = new JLabel("Won't show again until the next update");
dismissHint.setFont(FontManager.getRunescapeSmallFont());
dismissHint.setHorizontalAlignment(JLabel.CENTER);
dismissHint.setAlignmentX(Component.CENTER_ALIGNMENT);

JPanel content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
content.add(titleLabel);
content.add(Box.createRigidArea(new Dimension(0, 8)));
content.add(notesPanel);
content.add(Box.createRigidArea(new Dimension(0, 10)));
content.add(dismissBtn);
content.add(Box.createRigidArea(new Dimension(0, 4)));
content.add(dismissHint);

setLayout(new BorderLayout());
setBorder(new EmptyBorder(5, 0, 10, 0));
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/com/customweaponsfx/patch_notes.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"2.0": "Rebranded from Squeaky Toy to Custom Weapon SFX.\n\nThis is a full overhaul of the original plugin, which supported only a single sound at a fixed volume triggered by all player attacks / player zeros, or all attacks received / zeros received.\n\n- Custom sounds — place .wav files in .runelite/customweaponsfx/ and select them in the panel\n- Per-weapon configuration — add weapons individually by name search or by equipping them\n- Sound groups — each weapon supports multiple sound groups, each independently configured\n- Triggers — control exactly when each group fires: zero damage, regular hit, max hit, special zero, special hit, special max, or any damaging hit\n- Trigger chance — set a probability (0–100%) that a group fires when its trigger condition is met\n- Per-group volume — each group has its own volume level\n- Multi-sound groups — assign multiple sounds to a group and one is chosen at random on each fire\n- Received Attacks — a dedicated section for sounds that play when you take damage, separate from outgoing attacks",
"2.1": "Add \"Ignore max hits <=3\" and \"Ignore zeros with prayer\" toggles.",
"2.2": "Add \"Ignore zeroes with thrall\" toggle, until I find a better way to avoid issues with thralls (this may be never).",
"2.3": "Add \"Player Kill\" trigger, it can be used alone or in combination with other damage triggers to play sfx when you send another player to the shadow realm.\n\nAdd \"Player Death\" trigger (Received Attacks only), plays sfx when your character dies regardless of the cause."
"2.3": "Add \"Player Kill\" trigger, it can be used alone or in combination with other damage triggers to play sfx when you send another player to the shadow realm.\n\nAdd \"Player Death\" trigger (Received Attacks only), plays sfx when your character dies regardless of the cause.",
"2.4": "Add ability to use custom sounds\n\nAdd patch notes"
}