001/* 002 * Copyright (c) 2023. JEFF Media GbR / mfnalex et al. 003 * 004 * This program is free software: you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License as published by 006 * the Free Software Foundation, either version 3 of the License, or 007 * (at your option) any later version. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program. If not, see <https://www.gnu.org/licenses/>. 016 */ 017 018package com.jeff_media.jefflib.data; 019 020import com.google.common.base.Enums; 021import com.jeff_media.jefflib.JeffLib; 022import com.jeff_media.jefflib.NumberUtils; 023import java.util.Locale; 024import java.util.Objects; 025import lombok.AllArgsConstructor; 026import lombok.Builder; 027import lombok.Data; 028import lombok.RequiredArgsConstructor; 029import org.bukkit.Location; 030import org.bukkit.Sound; 031import org.bukkit.SoundCategory; 032import org.bukkit.configuration.ConfigurationSection; 033import org.bukkit.entity.Player; 034import org.jetbrains.annotations.NotNull; 035import org.jetbrains.annotations.Nullable; 036 037// TODO: Test whether something broke after switching from Sound to String 038 039/** 040 * Data class to wrap all information needed to play a sound 041 */ 042@Data 043@Builder 044@RequiredArgsConstructor 045@AllArgsConstructor 046public final class SoundData { 047 048 /** 049 * 050 */ 051 private final String sound; 052 private float volume = 1; 053 private float pitch = 1; 054 private float pitchVariant; 055 private SoundCategory soundCategory = SoundCategory.MASTER; 056 057 /** 058 * Parses a {@link ConfigurationSection} into a {@link SoundData} object. 059 * <p> 060 * <b>Example:</b> 061 * <pre> 062 * SoundData sound = SoundData.fromConfigurationSection(getConfig().getConfigurationSection("my_sound",null); 063 * </pre> 064 * <p> 065 * <b>Example YAML:</b> 066 * <pre> 067 * my_sound: 068 * effect: AMBIENT_CAVE # case insensitive for builtin sounds, CaSe SeNsItIvE for custom sounds 069 * volume: 0.5 # optional, default 1 070 * pitch: 1.5 # optional, default 1 071 * pitch-variant: 0.5 # optional, default 0. If this is set to 0.5, and pitch is set to 1.5, the sound will play with a pitch between 1 and 2 072 * sound-category: MASTER # optional, case insensitive, default MASTER 073 * </pre> 074 * 075 * @param config ConfigurationSection to parse 076 * @param prefix Prefix to use when looking up values in the ConfigurationSection. If non-null, this prefix is prefixed to all keys. For example, if this is set to "foo-", then the keys to look up are "foo-sound", "foo-volume", etc. 077 * @throws IllegalArgumentException When no sound is defined, or if the sound category is not valid. 078 */ 079 public static SoundData fromConfigurationSection(@NotNull final ConfigurationSection config, @Nullable String prefix) throws IllegalArgumentException { 080 if (prefix == null) prefix = ""; 081 String soundName = config.getString(prefix + "effect"); 082 if (soundName == null || soundName.isEmpty()) { 083 throw new IllegalArgumentException("No sound effect defined"); 084 } 085 final Sound sound = Enums.getIfPresent(Sound.class, soundName.toUpperCase(Locale.ROOT)).orNull(); 086 if (sound != null) { 087 soundName = sound.name(); 088 } 089 final float volume = (float) config.getDouble(prefix + "volume", 1.0D); 090 final float pitch = (float) config.getDouble(prefix + "pitch", 1.0D); 091 final float pitchVariant = (float) config.getDouble(prefix + "pitch-variant", 1.0D); 092 final String soundCategoryName = config.getString(prefix + "sound-category", SoundCategory.MASTER.name()).toUpperCase(Locale.ROOT); 093 final SoundCategory soundCategory = Enums.getIfPresent(SoundCategory.class, soundCategoryName.toUpperCase()).orNull(); 094 if (soundCategory == null) { 095 throw new IllegalArgumentException("Unknown sound category: " + soundCategoryName); 096 } 097 try { 098 Sound tmpSound = Sound.valueOf(soundName); 099 soundName = tmpSound.getKey().getKey(); 100 } catch (IllegalArgumentException ignored) { 101 //throw new IllegalArgumentException("Unknown sound: " + soundName); 102 } 103 return new SoundData(soundName.toLowerCase(Locale.ROOT), volume, pitch, pitchVariant, soundCategory); 104 } 105 106 /** 107 * Plays the sound only to the given player 108 * 109 * @param player Player 110 */ 111 public void playToPlayer(final Player player) { 112 player.playSound(player.getLocation(), sound, soundCategory, volume, getFinalPitch()); 113 } 114 115 private float getFinalPitch() { 116 if (NumberUtils.isZeroOrNegative(pitchVariant)) return pitch; 117 return (float) (pitch - (pitchVariant / 2) + JeffLib.getThreadLocalRandom().nextDouble(0, pitchVariant)); 118 } 119 120 /** 121 * Plays the sound only to the given player, at the given location 122 * 123 * @param player Player 124 * @param location Location 125 */ 126 public void playToPlayer(final Player player, final Location location) { 127 player.playSound(location, sound, soundCategory, volume, getFinalPitch()); 128 } 129 130 /** 131 * Plays the sound to all players in the world, at the given location 132 * 133 * @param location Location 134 */ 135 public void playToWorld(final Location location) { 136 Objects.requireNonNull(location.getWorld()).playSound(location, sound, soundCategory, volume, getFinalPitch()); 137 } 138 139} 140