Skip to content
Merged
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
13 changes: 9 additions & 4 deletions src/main/java/com/gmail/nossr50/config/SoundConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protected void loadKeys() {
@Override
protected boolean validateKeys() {
for (SoundType soundType : SoundType.values()) {
if (config.getDouble("Sounds." + soundType.toString() + ".Volume") < 0) {
if (config.getDouble("Sounds." + soundType + ".Volume") < 0) {
LogUtils.debug(mcMMO.p.getLogger(),
"[mcMMO] Sound volume cannot be below 0 for " + soundType);
return false;
Expand All @@ -52,17 +52,22 @@ public float getMasterVolume() {
}

public float getVolume(SoundType soundType) {
String key = "Sounds." + soundType.toString() + ".Volume";
String key = "Sounds." + soundType + ".Volume";
return (float) config.getDouble(key, 1.0);
}

public float getPitch(SoundType soundType) {
String key = "Sounds." + soundType.toString() + ".Pitch";
String key = "Sounds." + soundType + ".Pitch";
return (float) config.getDouble(key, 1.0);
}

public String getSound(SoundType soundType) {
final String key = "Sounds." + soundType + ".CustomSoundId";
return config.getString(key);
}

public boolean getIsEnabled(SoundType soundType) {
String key = "Sounds." + soundType.toString() + ".Enabled";
String key = "Sounds." + soundType + ".Enable";
return config.getBoolean(key, true);
Copy link

Copilot AI Aug 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value for EnableCustomSounds should be 'false' to match the configuration file default, not 'true'. This could cause unexpected behavior when the configuration is not explicitly set.

Suggested change
return config.getBoolean(key, true);
return config.getBoolean(key, false);

Copilot uses AI. Check for mistakes.
}
}
86 changes: 73 additions & 13 deletions src/main/java/com/gmail/nossr50/util/sounds/SoundManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@
import com.gmail.nossr50.util.Misc;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;
import org.bukkit.World;
import org.bukkit.entity.Player;

public class SoundManager {
private static Sound CRIPPLE_SOUND;

private static final Map<SoundType, Sound> soundCache = new ConcurrentHashMap<>();
private static final String NULL_FALLBACK_ID = null;
private static Sound CRIPPLE_SOUND;
private static final String ITEM_MACE_SMASH_GROUND = "ITEM_MACE_SMASH_GROUND";

private static final String VALUE_OF = "valueOf";

private static final String ORG_BUKKIT_SOUND = "org.bukkit.Sound";

/**
Expand Down Expand Up @@ -98,16 +100,78 @@ private static float getVolume(SoundType soundType) {
}

private static float getPitch(SoundType soundType) {
if (soundType == SoundType.FIZZ) {
return getFizzPitch();
} else if (soundType == SoundType.POP) {
return getPopPitch();
return switch (soundType)
{
case FIZZ -> getFizzPitch();
case POP -> getPopPitch();
default -> SoundConfig.getInstance().getPitch(soundType);
};
}

private static Sound getSound(SoundType soundType) {
final String soundId = SoundConfig.getInstance().getSound(soundType);

// Legacy versions use a different lookup method
if (SoundRegistryUtils.useLegacyLookup()) {
return getSoundLegacyCustom(soundId, soundType);
}

if (soundCache.containsKey(soundType)) {
return soundCache.get(soundType);
}

Sound sound;
if (soundId != null && !soundId.isEmpty()) {
sound = SoundRegistryUtils.getSound(soundId, soundType.id());
} else {
return SoundConfig.getInstance().getPitch(soundType);
sound = SoundRegistryUtils.getSound(soundType.id(), NULL_FALLBACK_ID);
}

if (sound != null) {
soundCache.putIfAbsent(soundType, sound);
return sound;
}

throw new RuntimeException("Could not find Sound for SoundType: " + soundType);
}

private static Sound getSound(SoundType soundType) {
private static Sound getSoundLegacyCustom(String id, SoundType soundType) {
if (soundCache.containsKey(soundType)) {
return soundCache.get(soundType);
}

// Try to look up a custom legacy sound
if (id != null && !id.isEmpty()) {
Sound sound;
if (Sound.class.isEnum()) {
// Sound is only an ENUM in legacy versions
// Use reflection to loop through the values, finding the first enum matching our ID
try {
Method method = Sound.class.getMethod("getKey");
for (Object legacyEnumEntry : Sound.class.getEnumConstants()) {
// This enum extends Keyed which adds the getKey() method
// we need to invoke this method to get the NamespacedKey and compare to our ID
if (method.invoke(legacyEnumEntry).toString().equals(id)) {
sound = (Sound) legacyEnumEntry;
soundCache.putIfAbsent(soundType, sound);
return sound;
}
}
} catch (NoSuchMethodException | InvocationTargetException |
IllegalAccessException e) {
// Ignore
}
}
throw new RuntimeException("Unable to find legacy sound by ID %s for SoundType %s"
.formatted(id, soundType));
}
// Failsafe -- we haven't found a matching sound
final Sound sound = getSoundLegacyFallBack(soundType);
soundCache.putIfAbsent(soundType, sound);
return sound;
}

private static Sound getSoundLegacyFallBack(SoundType soundType) {
return switch (soundType) {
case ANVIL -> Sound.BLOCK_ANVIL_PLACE;
case ITEM_BREAK -> Sound.ENTITY_ITEM_BREAK;
Expand Down Expand Up @@ -153,8 +217,4 @@ public static float getFizzPitch() {
public static float getPopPitch() {
return ((Misc.getRandom().nextFloat() - Misc.getRandom().nextFloat()) * 0.7F + 1.0F) * 2.0F;
}

public static float getKrakenPitch() {
return (Misc.getRandom().nextFloat() - Misc.getRandom().nextFloat()) * 0.2F + 1.0F;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.gmail.nossr50.util.sounds;

import static java.lang.String.format;

import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.AttributeMapper;
import com.gmail.nossr50.util.LogUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Locale;
import org.bukkit.NamespacedKey;
import org.bukkit.Sound;
import org.jetbrains.annotations.Nullable;

public final class SoundRegistryUtils {

private static Method registryLookup;
private static Object soundReg;

public static final String PAPER_SOUND_REGISTRY_FIELD = "SOUND_EVENT";
public static final String SPIGOT_SOUND_REGISTRY_FIELD = "SOUNDS";
public static final String METHOD_GET_OR_THROW_NAME = "getOrThrow";
public static final String METHOD_GET_NAME = "get";

static {
boolean foundRegistry = false;
Class<?> registry;
try {
registry = Class.forName(AttributeMapper.ORG_BUKKIT_REGISTRY);
try {
// First check for Paper's sound registry, held by field SOUND_EVENT
soundReg = registry.getField(PAPER_SOUND_REGISTRY_FIELD).get(null);
foundRegistry = true;
} catch (NoSuchFieldException | IllegalAccessException e) {
try {
soundReg = registry.getField(SPIGOT_SOUND_REGISTRY_FIELD);
foundRegistry = true;
} catch (NoSuchFieldException ex) {
// ignored
}
}
} catch (ClassNotFoundException e) {
// ignored
}

if (foundRegistry) {
try {
// getOrThrow isn't in all API versions, but we use it if it exists
registryLookup = soundReg.getClass().getMethod(METHOD_GET_OR_THROW_NAME,
NamespacedKey.class);
} catch (NoSuchMethodException e) {
try {
registryLookup = soundReg.getClass().getMethod(METHOD_GET_NAME,
NamespacedKey.class);
} catch (NoSuchMethodException ex) {
// ignored exception
registryLookup = null;
}
}
}
}

public static boolean useLegacyLookup() {
return registryLookup == null;
}

public static @Nullable Sound getSound(String id, String fallBackId) {
if (registryLookup != null) {
try {
return (Sound) registryLookup.invoke(soundReg, NamespacedKey.fromString(id));
} catch(InvocationTargetException | IllegalAccessException
| IllegalArgumentException e) {
if (fallBackId != null) {
LogUtils.debug(mcMMO.p.getLogger(),
format("Could not find sound with ID '%s', trying fallback ID '%s'", id,
fallBackId));
try {
return (Sound) registryLookup.invoke(soundReg,
NamespacedKey.fromString(fallBackId));
} catch (IllegalAccessException | InvocationTargetException ex) {
mcMMO.p.getLogger().severe(format("Could not find sound with ID %s,"
+ " fallback ID of %s also failed.", id, fallBackId));
}
} else {
mcMMO.p.getLogger().severe(format("Could not find sound with ID %s.", id));
}
throw new RuntimeException(e);
}
}
return null;
}
}
60 changes: 34 additions & 26 deletions src/main/java/com/gmail/nossr50/util/sounds/SoundType.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
package com.gmail.nossr50.util.sounds;

public enum SoundType {
ANVIL,
LEVEL_UP,
FIZZ,
ITEM_BREAK,
POP,
CHIMAERA_WING,
ROLL_ACTIVATED,
SKILL_UNLOCKED,
DEFLECT_ARROWS,
TOOL_READY,
ABILITY_ACTIVATED_GENERIC,
ABILITY_ACTIVATED_BERSERK,
BLEED,
GLASS,
ITEM_CONSUMED,
CRIPPLE,
TIRED;
ANVIL("minecraft:block.anvil.place"),
ITEM_BREAK("minecraft:entity.item.break"),
POP("minecraft:entity.item.pickup"),
CHIMAERA_WING("minecraft:entity.bat.takeoff"),
LEVEL_UP("minecraft:entity.player.levelup"),
FIZZ("minecraft:block.fire.extinguish"),
TOOL_READY("minecraft:item.armor.equip_gold"),
ROLL_ACTIVATED("minecraft:entity.llama.swag"),
SKILL_UNLOCKED("minecraft:ui.toast.challenge_complete"),
ABILITY_ACTIVATED_BERSERK("minecraft:block.conduit.ambient"),
TIRED("minecraft:block.conduit.ambient"),
ABILITY_ACTIVATED_GENERIC("minecraft:item.trident.riptide_3"),
DEFLECT_ARROWS("minecraft:entity.ender_eye.death"),
BLEED("minecraft:entity.ender_eye.death"),
GLASS("minecraft:block.glass.break"),
ITEM_CONSUMED("minecraft:item.bottle.empty"),
CRIPPLE("minecraft:block.anvil.place");
Copy link

Copilot AI Aug 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CRIPPLE sound is using 'minecraft:block.anvil.place' as fallback, but based on the sounds.yml configuration, it should use 'minecraft:item.mace.smash_ground'. This inconsistency means the fallback won't match the configured sound ID.

Suggested change
CRIPPLE("minecraft:block.anvil.place");
CRIPPLE("minecraft:item.mace.smash_ground");

Copilot uses AI. Check for mistakes.

private final String soundRegistryId;

public boolean usesCustomPitch() {
switch (this) {
case POP:
case FIZZ:
return true;
default:
return false;
}
SoundType(String soundRegistryId) {
this.soundRegistryId = soundRegistryId;
}
}

public String id() {
return soundRegistryId;
}

public boolean usesCustomPitch()
{
return switch (this) {
case POP, FIZZ -> true;
default -> false;
};
}
}
Loading