From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: lexikiq Date: Sat, 19 Jun 2021 22:55:10 -0400 Subject: [PATCH] Add SoundEvent diff --git a/src/main/java/gg/projecteden/parchment/event/sound/SoundEvent.java b/src/main/java/gg/projecteden/parchment/event/sound/SoundEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..14fa1d766f49eed2907214fc536222bae9167a85 --- /dev/null +++ b/src/main/java/gg/projecteden/parchment/event/sound/SoundEvent.java @@ -0,0 +1,359 @@ +package gg.projecteden.parchment.event.sound; + +import gg.projecteden.parchment.HasLocation; +import gg.projecteden.parchment.OptionalHumanEntity; +import net.kyori.adventure.sound.Sound; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Called when a sound is sent to a player. + * Cancelling this event will prevent the packet from sending. + */ +public final class SoundEvent extends Event implements Cancellable { + private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(SoundEvent.class); + + private static final org.bukkit.event.HandlerList handlers = new org.bukkit.event.HandlerList(); + public static final @NotNull Function DEFAULT_DISTANCE_FUNCTION = event -> event.volume() > 1.0F ? (double) (16.0F * event.volume()) : 16.0D; + public static final @NotNull Function> DEFAULT_RECIPIENTS_FUNCTION = new WrappedRecipientsFunction(event -> { + final double distance = event.calculateDistance(); + final Location loc = event.getEmitter().location(); + return loc.getWorld().getPlayers().stream() + .filter(player -> { + Location pl = player.getLocation(); + double x = loc.getX() - pl.getX(); + double y = loc.getY() - pl.getY(); + double z = loc.getZ() - pl.getZ(); + return x * x + y * y + z * z < distance * distance; + }) + .toList(); + }); + + private @Nullable HumanEntity except; + private @NotNull Function<@NotNull Sound, @NotNull Double> distanceFunction; + private @NotNull Function<@NotNull SoundEvent, @NotNull List<@NotNull Player>> recipientsFunction; + private @NotNull Sound sound; + private @NotNull Emitter emitter; + private boolean cancelled; + private @Nullable BiFunction<@NotNull SoundEvent, @NotNull Player, @Nullable Sound> soundOverrideFunction; + private @Nullable BiFunction<@NotNull SoundEvent, @NotNull Player, @Nullable Emitter> emitterOverrideFunction; + + public SoundEvent(@Nullable HumanEntity except, @NotNull Sound sound, @NotNull Emitter emitter, @Nullable Function distanceFunction, @Nullable Function> recipientsFunction) { + super(true); + this.except = except; + this.sound = Objects.requireNonNull(sound, "sound cannot be null"); + this.emitter = Objects.requireNonNull(emitter, "emitter cannot be null"); + this.distanceFunction = Objects.requireNonNullElse(distanceFunction, DEFAULT_DISTANCE_FUNCTION); + this.recipientsFunction = wrapRecipientsFunction(Objects.requireNonNullElse(recipientsFunction, DEFAULT_RECIPIENTS_FUNCTION)); + } + + /** + * Gets the player that won't be receiving this sound. + * + * @return player excluded from receiving this sound + */ + public @Nullable HumanEntity getException() { + return except; + } + + /** + * Sets the player that won't be receiving this sound. + * + * @param except player excluded from receiving this sound + */ + public void setException(@Nullable HumanEntity except) { + this.except = except; + } + + /** + * Gets the sound that will be sent. + * + * @return sound that will be sent + */ + public @NotNull Sound getSound() { + return sound; + } + + /** + * Sets the sound that will be sent. + * + * @param sound sound that will be sent + */ + public void setSound(@NotNull Sound sound) { + this.sound = Objects.requireNonNull(sound, "sound cannot be null"); + } + + /** + * Gets the emitter which determines how and where the sound will be played from. + * + * @return emitter which determines how and where the sound will be played from + */ + public @NotNull Emitter getEmitter() { + return emitter; + } + + /** + * Sets the emitter which determines how and where the sound will be played from. + * + * @param emitter emitter which determines how and where the sound will be played from + */ + public void setEmitter(@NotNull Emitter emitter) { + this.emitter = Objects.requireNonNull(emitter, "emitter cannot be null"); + } + + /** + * Calculates the distance of the sound. + *

+ * The distance value is dynamically calculated using a + * {@link Function Function<SoundEvent, Double>}. + * In vanilla Minecraft, the default function is {@link #DEFAULT_DISTANCE_FUNCTION} + * ({@code event -> event.getVolume() > 1.0F ? (double) (16.0F * event.getVolume()) : 16.0D}). + *

+ * This is used by the vanilla implementation of {@link #calculateRecipients()}, though custom + * implementations won't always use this method. + * + * @return calculated distance + * @see #getDistanceFunction() + * @see #setDistanceFunction(Function) + */ + public double calculateDistance() { + return distanceFunction.apply(sound); + } + + /** + * Gets the function that calculates the distance of the sound. + * + * @return distance function + * @see #calculateDistance() + * @see #setDistanceFunction(Function) + */ + public @NotNull Function<@NotNull Sound, @NotNull Double> getDistanceFunction() { + return distanceFunction; + } + + /** + * Sets the function that calculates the distance of the sound. + * + * @param distanceFunction distance function + * @see #calculateDistance() + * @see #getDistanceFunction() + */ + public void setDistanceFunction(@NotNull Function<@NotNull Sound, @NotNull Double> distanceFunction) { + this.distanceFunction = Objects.requireNonNull(distanceFunction, "distanceFunction cannot be null"); + } + + /** + * Determines which players will receive this sound packet. + * + * @return immutable list of players + * @see #getRecipientsFunction() + * @see #setRecipientsFunction(Function) + */ + public @NotNull List calculateRecipients() { + return recipientsFunction.apply(this); + } + + /** + * Gets the function that determines which players will receive the sound packet. + * + * @return recipients function + * @see #calculateRecipients() + * @see #setRecipientsFunction(Function) + */ + public @NotNull Function<@NotNull SoundEvent, @NotNull List<@NotNull Player>> getRecipientsFunction() { + return recipientsFunction; + } + + /** + * Sets the function that determines which players will receive the sound packet. + *

+ * This function does not need to query {@link #getException()} as this is done automatically. + * + * @param recipientsFunction recipients function + * @see #calculateRecipients() + * @see #getRecipientsFunction() + */ + public void setRecipientsFunction(@NotNull Function<@NotNull SoundEvent, @NotNull List<@NotNull Player>> recipientsFunction) { + this.recipientsFunction = wrapRecipientsFunction(Objects.requireNonNull(recipientsFunction, "recipientsFunction cannot be null")); + } + + /** + * Gets the function that overrides what {@link Sound} is sent to a {@link Player}. + * + * @return sound override function (or {@code null} if not overridden) + */ + public @Nullable BiFunction<@NotNull SoundEvent, @NotNull Player, @Nullable Sound> getSoundOverrideFunction() { + return soundOverrideFunction; + } + + /** + * Sets the function that overrides what {@link Sound} is sent to a {@link Player}. + * + * @param soundOverrideFunction function which accepts a sound event and a player and returns + * a sound (or {@code null} if the default sound should be used) + */ + public void setSoundOverrideFunction(@Nullable BiFunction<@NotNull SoundEvent, @NotNull Player, @Nullable Sound> soundOverrideFunction) { + this.soundOverrideFunction = soundOverrideFunction; + } + + /** + * Calculates the sound that will be sent to a {@link Player}. + * + * @param player player to calculate the sound for + * @return sound that will be sent to the player + */ + public @NotNull Sound calculateSound(@NotNull Player player) { + if (soundOverrideFunction != null) { + try { + Sound override = soundOverrideFunction.apply(this, player); + if (override != null) { + return override; + } + } catch (Throwable e) { + LOGGER.error("Error while overriding sound for player " + player.getName(), e); + } + } + return sound; + } + + /** + * Gets the function that overrides what {@link Emitter} is used when playing this sound to a + * {@link Player}. + * + * @return emitter override function (or {@code null} if not overridden) + */ + public @Nullable BiFunction<@NotNull SoundEvent, @NotNull Player, @Nullable Emitter> getEmitterOverrideFunction() { + return emitterOverrideFunction; + } + + /** + * Sets the function that overrides what {@link Emitter} is used when playing this sound to a + * {@link Player}. + * + * @param emitterOverrideFunction function which accepts a sound event and a player and returns + * an emitter + * (or {@code null} if the default emitter should be used) + */ + public void setEmitterOverrideFunction(@Nullable BiFunction<@NotNull SoundEvent, @NotNull Player, @Nullable Emitter> emitterOverrideFunction) { + this.emitterOverrideFunction = emitterOverrideFunction; + } + + /** + * Calculates the emitter that will be used when playing this sound to a {@link Player}. + * + * @param player player to calculate the emitter for + * @return emitter that will be used when playing the sound to the player + */ + public @NotNull Emitter calculateEmitter(@NotNull Player player) { + if (emitterOverrideFunction != null) { + try { + Emitter override = emitterOverrideFunction.apply(this, player); + if (override != null) { + return override; + } + } catch (Throwable e) { + LOGGER.error("Error while overriding emitter for player " + player.getName(), e); + } + } + return emitter; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + public static org.bukkit.event.HandlerList getHandlerList() { + return handlers; + } + + @Override + public @NotNull org.bukkit.event.HandlerList getHandlers() { + return handlers; + } + + private record WrappedRecipientsFunction(@NotNull Function> wrapped) implements Function> { + @Override + public @NotNull List apply(@NotNull SoundEvent event) { + List recipients = wrapped.apply(event); + HumanEntity except = event.getException(); + if (except != null) { + List filteredRecipients = new ArrayList<>(recipients.size()); + for (Player player : recipients) { + if (!player.getUniqueId().equals(except.getUniqueId())) + filteredRecipients.add(player); + } + return filteredRecipients; + } + return recipients; + } + } + + @NotNull + private static Function> wrapRecipientsFunction(@NotNull Function> recipientsFunction) { + if (recipientsFunction instanceof WrappedRecipientsFunction) + return recipientsFunction; + else + return new WrappedRecipientsFunction(recipientsFunction); + } + + /** + * The class which determines where a sound will emit from. + */ + public sealed interface Emitter extends HasLocation permits EntityEmitter, LocationEmitter { + /** + * Gets the location at which the sound will be played. + * + * @return sound's location + * @deprecated use {@link #getLocation()} instead + */ + @NotNull + @Deprecated + default Location location() { + return getLocation(); + } + } + + /** + * An emitter which plays a sound from an entity. + * + * @param entity the entity from which the sound will be played + */ + public record EntityEmitter(@NotNull Entity entity) implements Emitter { + @Override + public @NotNull Location getLocation() { + return entity.getLocation(); + } + } + + /** + * An emitter which plays a sound from a location. + * + * @param location the location from which the sound will be played + */ + public record LocationEmitter(@NotNull Location location) implements Emitter { + @Override + public @NotNull Location getLocation() { + return location; + } + } +}