/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.internal.managed.sounds;
import com.jcwhatever.nucleus.Nucleus;
import com.jcwhatever.nucleus.collections.timed.TimedArrayList;
import com.jcwhatever.nucleus.events.sounds.PlayResourceSoundEvent;
import com.jcwhatever.nucleus.events.sounds.ResourceSoundEndEvent;
import com.jcwhatever.nucleus.internal.NucMsg;
import com.jcwhatever.nucleus.managed.messaging.IMessenger.LineWrapping;
import com.jcwhatever.nucleus.managed.resourcepacks.IResourcePack;
import com.jcwhatever.nucleus.managed.resourcepacks.ResourcePacks;
import com.jcwhatever.nucleus.managed.resourcepacks.sounds.types.IResourceSound;
import com.jcwhatever.nucleus.managed.resourcepacks.sounds.types.IVoiceSound;
import com.jcwhatever.nucleus.managed.sounds.ISoundContext;
import com.jcwhatever.nucleus.managed.sounds.ISoundManager;
import com.jcwhatever.nucleus.managed.sounds.SoundSettings;
import com.jcwhatever.nucleus.managed.sounds.Transcript;
import com.jcwhatever.nucleus.utils.ArrayUtils;
import com.jcwhatever.nucleus.utils.PreCon;
import com.jcwhatever.nucleus.utils.ThreadSingletons;
import com.jcwhatever.nucleus.utils.TimeScale;
import com.jcwhatever.nucleus.utils.Utils;
import com.jcwhatever.nucleus.utils.coords.LocationUtils;
import com.jcwhatever.nucleus.utils.nms.INmsSoundEffectHandler;
import com.jcwhatever.nucleus.utils.nms.NmsUtils;
import com.jcwhatever.nucleus.utils.observer.future.IFutureResult;
import com.jcwhatever.nucleus.utils.observer.update.UpdateSubscriber;
import com.jcwhatever.nucleus.utils.text.TextUtils;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Nucleus implementation of {@link ISoundManager}.
*/
public final class InternalSoundManager implements ISoundManager {
private static final ThreadSingletons<Location> EFFECT_LOCATION = LocationUtils.createThreadSingleton();
private final Map<UUID, TimedArrayList<ISoundContext>> _playing = new HashMap<>(100);
@Nullable
@Override
public IResourceSound get(String soundPath) {
PreCon.notNull(soundPath);
soundPath = soundPath.toLowerCase();
String packName;
String soundName;
String[] components = TextUtils.PATTERN_DOT.split(soundPath);
if (components.length == 1) {
packName = "_default";
soundName = soundPath;
}
else if (components.length == 2) {
packName = components[0];
soundName = components[1];
}
else {
return null;
}
IResourcePack pack = ResourcePacks.get(packName);
if (pack == null)
return null;
return pack.getSounds().get(soundName);
}
@Override
public Collection<IResourceSound> getPlaying(Player player) {
return getPlaying(player, new ArrayList<IResourceSound>(10));
}
@Override
public <T extends Collection<IResourceSound>> T getPlaying(Player player, T output) {
PreCon.notNull(player);
PreCon.notNull(output);
List<ISoundContext> playing = _playing.get(player.getUniqueId());
if (playing == null || playing.isEmpty())
return output;
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (playing) {
for (ISoundContext play : playing) {
output.add(play.getResourceSound());
}
}
return output;
}
@Override
public Collection<ISoundContext> getContexts(Player player) {
return getContexts(player, new ArrayList<ISoundContext>(7));
}
@Override
public <T extends Collection<ISoundContext>> T getContexts(Player player, T output) {
PreCon.notNull(player);
PreCon.notNull(output);
List<ISoundContext> playing = _playing.get(player.getUniqueId());
if (playing == null || playing.isEmpty())
return output;
output.addAll(playing);
return output;
}
@Override
public IFutureResult<ISoundContext> playSound(Plugin plugin, Player player, IResourceSound sound) {
return playSound(plugin, player, sound, new SoundSettings(), null);
}
@Override
public IFutureResult<ISoundContext> playSound(Plugin plugin, final Player player, IResourceSound sound,
SoundSettings settings) {
return playSound(plugin, player, sound, settings, null);
}
@Override
public IFutureResult<ISoundContext> playSound(final Plugin plugin, Player player, IResourceSound sound,
SoundSettings settings,
final @Nullable Collection<Player> transcriptViewers) {
PreCon.notNull(plugin);
PreCon.notNull(sound);
PreCon.notNull(settings);
settings = new SoundSettings(settings);
// substitute players location if no locations are provided.
if (!settings.hasLocations())
settings.addLocations(player.getLocation());
SoundContext context = new SoundContext(player, sound, settings);
// run event
PlayResourceSoundEvent event = new PlayResourceSoundEvent(player, sound, settings);
Nucleus.getEventManager().callBukkit(null, event);
// see if the event was cancelled
if (event.isCancelled())
return context.setFinished();
Collection<Player> target = ArrayUtils.asList(player);
for (Location location : settings.getLocations()) {
if (location.getWorld() == null || !location.getWorld().equals(player.getWorld()))
continue;
sendSound(target, location, event.getResourceSound().getClientName(),
settings.getVolume(), settings.getPitch());
}
// get timed list to store playing object in.
TimedArrayList<ISoundContext> currentPlaying = _playing.get(player.getUniqueId());
if (currentPlaying == null) {
// create timed list for player and add a subscriber to handle sounds ending.
currentPlaying = new TimedArrayList<ISoundContext>(Nucleus.getPlugin(), 3)
.onLifespanEnd(new UpdateSubscriber<ISoundContext>() {
@Override
public void on(ISoundContext item) {
((SoundContext)item).setFinished();
ResourceSoundEndEvent event = new ResourceSoundEndEvent(item.getPlayer(),
item.getResourceSound(), item.getSettings());
Nucleus.getEventManager().callBukkit(this, event);
}
});
_playing.put(player.getUniqueId(), currentPlaying);
}
// add playing sound to timed list, will expire when the song ends.
currentPlaying.add(context, sound.getDurationTicks(), TimeScale.TICKS);
// display transcript to players if the sound is a voice
// sound and transcript viewers are provided.
if (sound instanceof IVoiceSound &&
transcriptViewers != null &&
!transcriptViewers.isEmpty()) {
Transcript transcript = ((IVoiceSound) sound).getTranscript();
if (transcript != null) {
transcript.run(plugin, new UpdateSubscriber<String>() {
@Override
public void on(String text) {
for (Player viewer : transcriptViewers) {
NucMsg.tell(plugin, viewer, LineWrapping.DISABLED, text);
}
}
});
}
}
return context.getFuture();
}
@Override
public void playEffect(String clientSoundName, Player player) {
PreCon.notNullOrEmpty(clientSoundName);
PreCon.notNull(player);
sendSound(ArrayUtils.asList(player),
player.getLocation(EFFECT_LOCATION.get()), clientSoundName, 1.0f, 1.0f);
}
@Override
public void playEffect(String clientSoundName, Collection<Player> players) {
PreCon.notNullOrEmpty(clientSoundName);
PreCon.notNull(players);
for (Player player : players) {
playEffect(clientSoundName, player);
}
}
@Override
public void playEffect(String clientSoundName, Player player, Location location) {
PreCon.notNullOrEmpty(clientSoundName);
PreCon.notNull(player);
PreCon.notNull(location);
sendSound(ArrayUtils.asList(player),
location, clientSoundName, 1.0f, 1.0f);
}
@Override
public void playEffect(String clientSoundName, Collection<Player> players, Location location) {
PreCon.notNullOrEmpty(clientSoundName);
PreCon.notNull(players);
PreCon.notNull(location);
sendSound(players,
location, clientSoundName, 1.0f, 1.0f);
}
@Override
public void playEffect(String clientSoundName, Player player, float volume, float pitch) {
PreCon.notNullOrEmpty(clientSoundName);
PreCon.notNull(player);
sendSound(ArrayUtils.asList(player),
player.getLocation(EFFECT_LOCATION.get()), clientSoundName, volume, pitch);
}
@Override
public void playEffect(String clientSoundName, Collection<Player> players, float volume, float pitch) {
PreCon.notNullOrEmpty(clientSoundName);
PreCon.notNull(players);
for (Player player : players) {
playEffect(clientSoundName, player, volume, pitch);
}
}
@Override
public void playEffect(String clientSoundName, Player player, Location location,
float volume, float pitch) {
PreCon.notNullOrEmpty(clientSoundName);
PreCon.notNull(player);
PreCon.notNull(location);
sendSound(ArrayUtils.asList(player),
location, clientSoundName, volume, pitch);
}
@Override
public void playEffect(String clientSoundName, Collection<Player> players, Location location,
float volume, float pitch) {
PreCon.notNullOrEmpty(clientSoundName);
PreCon.notNull(players);
PreCon.notNull(location);
sendSound(players, location, clientSoundName, volume, pitch);
}
private static void sendSound(Collection<Player> players, Location location, String soundName,
float volume, float pitch) {
INmsSoundEffectHandler nmsHandler = NmsUtils.getSoundEffectHandler();
if (nmsHandler != null) {
// send sound packet to player
nmsHandler.send(players, soundName,
location.getX(), location.getY(), location.getZ(),
volume, pitch);
}
else {
// fallback to using console commands if NMS is unavailable
for (Player player : players) {
String cmd = getPlaySoundCommand(soundName, player,
location, volume, pitch);
Utils.executeAsConsole(cmd);
}
}
}
/*
* Get the command to run to make a player hear a sound.
*/
private static String getPlaySoundCommand(String soundName, Player p, Location loc,
float volume, float pitch) {
return "playsound " + soundName + ' ' + p.getName() + ' ' +
loc.getBlockX() + ' ' + loc.getBlockY() + ' ' + loc.getBlockZ() + ' '
+ volume + ' ' + pitch;
}
}