From a632eb778c4d975b2992b47ddfd5a62f22e2b507 Mon Sep 17 00:00:00 2001 From: Paul Reilly Date: Thu, 1 Jun 2023 23:02:01 -0500 Subject: [PATCH] Start implementations --- .../fossil/items/ClownfishItem.java | 28 ++++ .../totalfreedom/fossil/items/ShopItem.java | 34 +++++ .../totalfreedom/fossil/items/TrailItem.java | 21 +++ .../me/totalfreedom/fossil/trail/Trailer.java | 41 ++++++ .../fossil/trail/types/BasicTrail.java | 32 +++++ .../fossil/trail/types/RainbowTrail.java | 46 +++++++ .../fossil/trail/types/SimpleTrail.java | 90 +++++++++++++ .../fossil/trail/types/TrailProvider.java | 12 ++ .../me/totalfreedom/api/Interpolator.java | 7 + .../java/me/totalfreedom/particle/Trail.java | 112 ++++++++++++++-- .../utils/InterpolationUtils.java | 120 ++++++++++++++++++ 11 files changed, 535 insertions(+), 8 deletions(-) create mode 100644 Fossil/src/main/java/me/totalfreedom/fossil/items/ClownfishItem.java create mode 100644 Fossil/src/main/java/me/totalfreedom/fossil/items/ShopItem.java create mode 100644 Fossil/src/main/java/me/totalfreedom/fossil/items/TrailItem.java create mode 100644 Fossil/src/main/java/me/totalfreedom/fossil/trail/Trailer.java create mode 100644 Fossil/src/main/java/me/totalfreedom/fossil/trail/types/BasicTrail.java create mode 100644 Fossil/src/main/java/me/totalfreedom/fossil/trail/types/RainbowTrail.java create mode 100644 Fossil/src/main/java/me/totalfreedom/fossil/trail/types/SimpleTrail.java create mode 100644 Fossil/src/main/java/me/totalfreedom/fossil/trail/types/TrailProvider.java create mode 100644 Patchwork/src/main/java/me/totalfreedom/api/Interpolator.java create mode 100644 Patchwork/src/main/java/me/totalfreedom/utils/InterpolationUtils.java diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/items/ClownfishItem.java b/Fossil/src/main/java/me/totalfreedom/fossil/items/ClownfishItem.java new file mode 100644 index 0000000..834d8c1 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/items/ClownfishItem.java @@ -0,0 +1,28 @@ +package me.totalfreedom.fossil.items; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class ClownfishItem extends ShopItem +{ + public ClownfishItem() + { + super(Material.TROPICAL_FISH); + } + + @Override + public void runAction(final @NotNull Player user, final @Nullable Entity target) + { + if (target == null) return; + + final Location location = user.getEyeLocation().clone(); + final Vector vector = location.getDirection().multiply(2); + + target.setVelocity(vector); + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/items/ShopItem.java b/Fossil/src/main/java/me/totalfreedom/fossil/items/ShopItem.java new file mode 100644 index 0000000..04f6bb9 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/items/ShopItem.java @@ -0,0 +1,34 @@ +package me.totalfreedom.fossil.items; + +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class ShopItem +{ + private final ItemStack item; + private final ItemMeta meta; + + protected ShopItem(final Material material) + { + this.item = new ItemStack(material, 1); + + this.meta = this.item.getItemMeta(); + } + + public abstract void runAction(@NotNull final Player user, @Nullable final Entity target); + + public ItemStack getItem() + { + return this.item; + } + + public ItemMeta getMeta() + { + return this.meta; + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/items/TrailItem.java b/Fossil/src/main/java/me/totalfreedom/fossil/items/TrailItem.java new file mode 100644 index 0000000..a9e6a7d --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/items/TrailItem.java @@ -0,0 +1,21 @@ +package me.totalfreedom.fossil.items; + +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class TrailItem extends ShopItem +{ + public TrailItem(final Material material) + { + super(material); + } + + @Override + public void runAction(final @NotNull Player user, final @Nullable Entity target) + { + + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/Trailer.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/Trailer.java new file mode 100644 index 0000000..ae51c0b --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/Trailer.java @@ -0,0 +1,41 @@ +package me.totalfreedom.fossil.trail; + +import me.totalfreedom.particle.Trail; +import me.totalfreedom.particle.TrailType; +import me.totalfreedom.service.Service; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class Trailer extends Service +{ + private final List trailList = new ArrayList<>(); + + public Trailer() { + super("trailer_service"); + } + + public void addTrail(final Trail trail) { + this.trailList.add(trail); + } + + public void removeTrail(final Trail trail) { + this.trailList.remove(trail); + } + + @Override + public void tick() + { + for (final Trail trail : trailList) { + if (trail.getAssociatedPlayer().isOnline()) { + final Player player = (Player) trail.getAssociatedPlayer(); + + } + } + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/BasicTrail.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/BasicTrail.java new file mode 100644 index 0000000..94fbb46 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/BasicTrail.java @@ -0,0 +1,32 @@ +package me.totalfreedom.fossil.trail.types; + +import me.totalfreedom.particle.TrailType; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.entity.Player; + +public final class BasicTrail extends SimpleTrail +{ + protected BasicTrail(final Player player) + { + super(player, TrailType.DEFAULT); + super.setColor(Color.RED); + } + + @Override + public void spawnParticle() + { + // Exit immediately if either condition is false. + if (!isActive() || !getAssociatedPlayer().isOnline()) return; + + // Trail is active and the player is online. + final Particle particle = getTrailType().getType(); + final Particle.DustOptions options = new Particle.DustOptions(getColor(), 3); + final Player player = (Player) getAssociatedPlayer(); + final Location location = player.getLocation() + .clone() + .subtract(0, 1, 0); + location.getWorld().spawnParticle(particle, location, 1, 0.0, 0.5, 0.0, options); + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/RainbowTrail.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/RainbowTrail.java new file mode 100644 index 0000000..36b9108 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/RainbowTrail.java @@ -0,0 +1,46 @@ +package me.totalfreedom.fossil.trail.types; + +import me.totalfreedom.particle.TrailType; +import me.totalfreedom.utils.InterpolationUtils; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.entity.Player; + +import java.util.Iterator; + +public final class RainbowTrail extends SimpleTrail +{ + private Iterator currentColor; + + protected RainbowTrail(final Player player) + { + super(player, TrailType.DEFAULT); + setColors(InterpolationUtils.rainbow(40 % 7)); + this.currentColor = getColors().iterator(); + } + + @Override + public void spawnParticle() + { + // Exit immediately if either case is false. + if (!isActive() || !getAssociatedPlayer().isOnline()) return; + + // Re-initialize the color iterator if the iterator has previously reached the end of its index. + if (!currentColor.hasNext()) + { + this.currentColor = getColors().iterator(); + } + + final Color color = currentColor.next(); + final Player player = (Player) getAssociatedPlayer(); + final Particle particle = getTrailType().getType(); + final Particle.DustOptions options = new Particle.DustOptions(color, 3); + final Location location = player.getLocation() + .clone() + .subtract(0, 1, 0); + + location.getWorld() + .spawnParticle(particle, location, 1, 0.0, 0.5, 0.0, options); + } +} \ No newline at end of file diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/SimpleTrail.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/SimpleTrail.java new file mode 100644 index 0000000..c217c64 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/SimpleTrail.java @@ -0,0 +1,90 @@ +package me.totalfreedom.fossil.trail.types; + +import me.totalfreedom.particle.Trail; +import me.totalfreedom.particle.TrailType; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; +import java.util.UUID; + +public abstract class SimpleTrail implements Trail +{ + private final UUID associatedPlayerUUID; + private final TrailType trailType; + + private Color staticColor = null; + private Set gradientColor = null; + private boolean active = false; + + protected SimpleTrail(final Player player, final TrailType trailType) { + this.associatedPlayerUUID = player.getUniqueId(); + this.trailType = trailType; + } + + @Override + public @NotNull UUID getAssociatedPlayerUUID() + { + return associatedPlayerUUID; + } + + @Override + public @NotNull OfflinePlayer getAssociatedPlayer() + { + return Bukkit.getOfflinePlayer(getAssociatedPlayerUUID()); + } + + @Override + public @NotNull TrailType getTrailType() + { + return trailType; + } + + @Override + public @Nullable Color getColor() + { + return staticColor; + } + + @Override + public void setColor(@NotNull final Color color) + { + this.gradientColor = null; + this.staticColor = color; + } + + @Override + public @Nullable Set getColors() + { + return this.gradientColor; + } + + @Override + public void setColors(@NotNull final Set colors) + { + this.staticColor = null; + this.gradientColor = colors; + } + + @Override + public boolean isGradient() + { + return gradientColor != null; + } + + @Override + public boolean isActive() + { + return active; + } + + @Override + public void setActive(final boolean active) + { + this.active = active; + } +} diff --git a/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/TrailProvider.java b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/TrailProvider.java new file mode 100644 index 0000000..427bb09 --- /dev/null +++ b/Fossil/src/main/java/me/totalfreedom/fossil/trail/types/TrailProvider.java @@ -0,0 +1,12 @@ +package me.totalfreedom.fossil.trail.types; + +import org.bukkit.entity.Player; + +public final class TrailProvider +{ + public BasicTrail basicTrail(final Player player) { + return new BasicTrail(player); + } + + +} diff --git a/Patchwork/src/main/java/me/totalfreedom/api/Interpolator.java b/Patchwork/src/main/java/me/totalfreedom/api/Interpolator.java new file mode 100644 index 0000000..7e71edc --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/api/Interpolator.java @@ -0,0 +1,7 @@ +package me.totalfreedom.api; + +@FunctionalInterface +public interface Interpolator +{ + double[] interpolate(final double from, final double to, final int max); +} diff --git a/Patchwork/src/main/java/me/totalfreedom/particle/Trail.java b/Patchwork/src/main/java/me/totalfreedom/particle/Trail.java index 891a56e..2581463 100644 --- a/Patchwork/src/main/java/me/totalfreedom/particle/Trail.java +++ b/Patchwork/src/main/java/me/totalfreedom/particle/Trail.java @@ -1,31 +1,127 @@ package me.totalfreedom.particle; +import me.totalfreedom.api.Interpolator; +import me.totalfreedom.utils.InterpolationUtils; import org.bukkit.Color; -import org.bukkit.entity.Player; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.Particle; +import org.bukkit.World; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Set; import java.util.UUID; +/** + * Represents a Trail instance for a specific player. + */ public interface Trail { + /** + * Returns the UUID of the player associated with the trail. This is for usage with our persistant storage + * container so that we can safely send and retrieve the trails without having to directly reference a player + * object. + *
+ * TL;DR Memory optimization! + * + * @return The UUID of the player associated with this trail. + */ @NotNull - UUID getAssocPlayerUUID(); + UUID getAssociatedPlayerUUID(); - // Nullable because the player may not be online and trail selections should be persistent whether they are - // active or not. - @Nullable - Player getAssocPlayer(); + /** + * Returns the player associated with this trail. Trails are user specific, and should be persistent across all + * usages. This is also used when displaying the particles, as they will be relative to the player's back, which + * is an inverse offset of the player's eye location. We use OfflinePlayer as we can make a simple check and cast + * to determine if the player is online when spawning trails. + * + * @return The player associated with this Trail. + */ + @NotNull + OfflinePlayer getAssociatedPlayer(); + /** + * Gets the Trail Type of this trail. This is used to determine what type of trail this is, and what + * {@link Particle} it should use. + * + * @return The Trail Type of this trail. + * @see TrailType + */ @NotNull TrailType getTrailType(); - @NotNull + /** + * This method is nullable because if the value of {@link #isGradient()} is true, then + * {@link #getColors()} should be used instead, as that will contain the color data for our trail. + *
+ * However, this method will also be null if the particle type is not colorable. + * + * @return The color of the trail, or null if the trail is a gradient or non-colorable. + * @see Particle + * @see #getColors(); + */ + @Nullable Color getColor(); + /** + * Sets the static color of the trail. If you are trying to use a gradient, use {@link #setColors(Set)} instead. + *
+ * + * @param color The color to set the trail to. + */ void setColor(@NotNull Color color); + /** + * This method is nullable because if the value of {@link #isGradient()} is false, then + * {@link #getColor()} should be used instead, as our trail is a single static color. + *
+ * However, this method will also be null if the particle type is not colorable. + * + * @return The colors of the trail, or null if the trail is not a gradient or non-colorable. + * @see #getColor() + * @see Particle + * @see InterpolationUtils + * @see Interpolator + */ + @Nullable + Set getColors(); + + /** + * Sets the colors of the trail. If you are trying to use a static color, use {@link #setColor(Color)} instead. + *
+ * This should be used for trails that iterate over a set of colors, such as a rainbow trail. + * + * @param colors The colors to set the trail to. It is recommended to use {@link InterpolationUtils} to generate + * interpolated gradients for this. + */ + void setColors(@NotNull Set colors); + + /** + * Validates whether this Trail is a gradient or a static trail. + *
+ * This is entirely based on whether {@link #getColors()} returns null or not. + * + * @return True if {@link #getColors()} is not null, false otherwise. + */ + boolean isGradient(); + + /** + * Gets whether the trail is active. + * + * @return True if the trail is active, false if it is not. + */ boolean isActive(); - void setActive(boolean active); + /** + * Turn the trail on or off. + * + * @param active True if the trail should be active, false if it should not. + */ + void setActive(final boolean active); + + /** + * Spawns a particle (if gradient, the next particle) on the supplied location object. + */ + void spawnParticle(); } diff --git a/Patchwork/src/main/java/me/totalfreedom/utils/InterpolationUtils.java b/Patchwork/src/main/java/me/totalfreedom/utils/InterpolationUtils.java new file mode 100644 index 0000000..587f05d --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/utils/InterpolationUtils.java @@ -0,0 +1,120 @@ +package me.totalfreedom.utils; + +import me.totalfreedom.api.Interpolator; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.Color; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class InterpolationUtils +{ + public static Set rainbow(final int length) + { + final LinkedHashSet base = new LinkedHashSet<>(); + final Set redToOrange = hsvGradient(length, Color.RED, Color.ORANGE, InterpolationUtils::linear); + final Set orangeToYellow = hsvGradient(length, Color.ORANGE, Color.YELLOW, InterpolationUtils::linear); + final Set yellowToGreen = hsvGradient(length, Color.YELLOW, Color.GREEN, InterpolationUtils::linear); + final Set greenToBlue = hsvGradient(length, Color.GREEN, Color.BLUE, InterpolationUtils::linear); + final Set blueToPurple = hsvGradient(length, Color.BLUE, Color.PURPLE, InterpolationUtils::linear); + final Set purpleToRed = hsvGradient(length, Color.PURPLE, Color.RED, InterpolationUtils::linear); + base.addAll(redToOrange); + base.addAll(orangeToYellow); + base.addAll(yellowToGreen); + base.addAll(greenToBlue); + base.addAll(blueToPurple); + base.addAll(purpleToRed); + return base; + } + + private static Set hsvGradient(final int length, final Color from, final Color to, + final Interpolator interpolator) + { + // returns a float-array where hsv[0] = hue, hsv[1] = saturation, hsv[2] = value/brightness + final float[] hsvFrom = java.awt.Color.RGBtoHSB(from.getRed(), from.getGreen(), from.getBlue(), null); + final float[] hsvTo = java.awt.Color.RGBtoHSB(to.getRed(), to.getGreen(), to.getBlue(), null); + + final double[] h = interpolator.interpolate(hsvFrom[0], hsvTo[0], length); + final double[] s = interpolator.interpolate(hsvFrom[1], hsvTo[1], length); + final double[] v = interpolator.interpolate(hsvFrom[2], hsvTo[2], length); + + final LinkedHashSet gradient = new LinkedHashSet<>(); + + for (int i = 0; i < length; i++) + { + final int rgb = java.awt.Color.HSBtoRGB((float) h[i], (float) s[i], (float) v[i]); + final Color color = Color.fromRGB(rgb); + gradient.add(color); + } + return gradient; + } + + private static double[] linear(final double from, final double to, final int max) + { + final double[] res = new double[max]; + for (int i = 0; i < max; i++) + { + res[i] = from + i * ((to - from) / (max - 1)); + } + return res; + } + + public static Set rgbGradient(final int length, final Color from, final Color to, + final Interpolator interpolator) + { + final double[] r = interpolator.interpolate(from.getRed(), to.getRed(), length); + final double[] g = interpolator.interpolate(from.getGreen(), to.getGreen(), length); + final double[] b = interpolator.interpolate(from.getBlue(), to.getBlue(), length); + + final LinkedHashSet gradient = new LinkedHashSet<>(); + + for (int i = 0; i < length; i++) + { + final Color color = Color.fromRGB((int) r[i], (int) g[i], (int) b[i]); + gradient.add(color); + } + return gradient; + } + + public static Set rainbowComponent(final int length) + { + final LinkedHashSet base = new LinkedHashSet<>(); + final Set redToOrange = componentRGBGradient(length, NamedTextColor.RED, + NamedTextColor.GOLD, InterpolationUtils::linear); + final Set orangeToYellow = componentRGBGradient(length, NamedTextColor.GOLD, + NamedTextColor.YELLOW, InterpolationUtils::linear); + final Set yellowToGreen = componentRGBGradient(length, NamedTextColor.YELLOW, + NamedTextColor.GREEN, InterpolationUtils::linear); + final Set greenToBlue = componentRGBGradient(length, NamedTextColor.GREEN, + NamedTextColor.BLUE, InterpolationUtils::linear); + final Set blueToPurple = componentRGBGradient(length, NamedTextColor.BLUE, + NamedTextColor.LIGHT_PURPLE, InterpolationUtils::linear); + final Set purpleToRed = componentRGBGradient(length, TextColor.color(75, 0, 130), + TextColor.color(255, 0, 0), InterpolationUtils::linear); + base.addAll(redToOrange); + base.addAll(orangeToYellow); + base.addAll(yellowToGreen); + base.addAll(greenToBlue); + base.addAll(blueToPurple); + base.addAll(purpleToRed); + return base; + } + + public static Set componentRGBGradient(final int length, final TextColor from, final TextColor to, + final Interpolator interpolator) + { + final double[] r = interpolator.interpolate(from.red(), to.red(), length); + final double[] g = interpolator.interpolate(from.green(), to.green(), length); + final double[] b = interpolator.interpolate(from.blue(), to.blue(), length); + + final LinkedHashSet gradient = new LinkedHashSet<>(); + + for (int i = 0; i < length; i++) + { + final TextColor color = TextColor.color((int) r[i], (int) g[i], (int) b[i]); + gradient.add(color); + } + return gradient; + } +}