From db1a425215f2f389d93b3e7378398416c204ec02 Mon Sep 17 00:00:00 2001 From: Sn0wStorm Date: Sat, 7 Nov 2020 22:37:32 +0100 Subject: [PATCH] Color fading for Cauldron Particles --- src/com/dre/brewery/BCauldron.java | 126 ++++++++++++++++-- src/com/dre/brewery/P.java | 5 +- .../dre/brewery/recipe/BCauldronRecipe.java | 49 ++++++- src/com/dre/brewery/utility/BUtil.java | 30 +++++ 4 files changed, 193 insertions(+), 17 deletions(-) diff --git a/src/com/dre/brewery/BCauldron.java b/src/com/dre/brewery/BCauldron.java index c92b8b8..8ba2788 100644 --- a/src/com/dre/brewery/BCauldron.java +++ b/src/com/dre/brewery/BCauldron.java @@ -6,6 +6,8 @@ import com.dre.brewery.recipe.BCauldronRecipe; import com.dre.brewery.recipe.RecipeItem; import com.dre.brewery.utility.BUtil; import com.dre.brewery.utility.LegacyUtil; +import com.dre.brewery.utility.Tuple; +import org.bukkit.Color; import org.bukkit.Effect; import org.bukkit.Location; import org.bukkit.Material; @@ -19,6 +21,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -32,17 +35,17 @@ public class BCauldron { private static Set plInteracted = new HashSet<>(); // Interact Event helper public static Map bcauldrons = new HashMap<>(); // All active cauldrons. Mapped to their block for fast retrieve - public static Particle particle = Particle.CLOUD; - private BIngredients ingredients = new BIngredients(); private final Block block; private int state = 0; private boolean changed = false; + private Optional particleRecipe; // null if we haven't checked, empty if there is none + private Color particleColor; private Location particleLocation; public BCauldron(Block block) { this.block = block; - particleLocation = block.getLocation().add(0.5, 0.8, 0.5); + particleLocation = block.getLocation().add(0.5, 0.9, 0.5); } // loading from file @@ -50,7 +53,7 @@ public class BCauldron { this.block = block; this.state = state; this.ingredients = ingredients; - particleLocation = block.getLocation().add(0.5, 0.8, 0.5); + particleLocation = block.getLocation().add(0.5, 0.9, 0.5); } /** @@ -84,6 +87,7 @@ public class BCauldron { ingredients = ingredients.copy(); changed = false; } + particleColor = null; } // add an ingredient to the cauldron @@ -94,6 +98,8 @@ public class BCauldron { changed = false; } + particleRecipe = null; + particleColor = null; ingredients.add(ingredient, rItem); block.getWorld().playEffect(block.getLocation(), Effect.EXTINGUISH, 0); if (state > 0) { @@ -233,18 +239,28 @@ public class BCauldron { public void cookEffect() { if (BUtil.isChunkLoaded(block) && LegacyUtil.isCauldronHeatsource(block.getRelative(BlockFace.DOWN))) { - if (particleRandom.nextFloat() > 0.75) { - // Pixely cloud at 0.4 random in x and z - // 0 count enables direction, send to y = 1 with speed 0.07 - block.getWorld().spawnParticle(Particle.CLOUD, getRandParticleLoc(), 0, 0, 1, 0, 0.07); + if (particleRandom.nextFloat() > 0.85) { + // Dark pixely smoke cloud at 0.4 random in x and z + // 0 count enables direction, send to y = 1 with speed 0.09 + block.getWorld().spawnParticle(Particle.SMOKE_LARGE, getRandParticleLoc(), 0, 0, 1, 0, 0.09); } if (particleRandom.nextFloat() > 0.2) { // A Water Splash with 0.2 offset in x and z block.getWorld().spawnParticle(Particle.WATER_SPLASH, particleLocation, 1, 0.2, 0, 0.2); } + Color color = getParticleColor(); // Colorable spirally spell, 0 count enables color instead of the offset variables // Configurable RGB color. 1025 seems to be the best for color brightness and upwards motion - block.getWorld().spawnParticle(Particle.SPELL_MOB, getRandParticleLoc(), 0, 180.0/255.0, 40.0/255.0, 1.0/255.0, 1025.0); + block.getWorld().spawnParticle(Particle.SPELL_MOB, getRandParticleLoc(), 0, + ((double) color.getRed()) / 255.0, + ((double) color.getGreen()) / 255.0, + ((double) color.getBlue()) / 255.0, + 1025.0); + + if (particleRandom.nextFloat() > 0.4) { + // Two hovering pixely dust clouds, a bit offset with DustOptions to give some color and size + block.getWorld().spawnParticle(Particle.REDSTONE, particleLocation, 2, 0.15, 0.2, 0.15, new Particle.DustOptions(color, 1.5f)); + } } } @@ -255,7 +271,82 @@ public class BCauldron { particleLocation.getZ() + (particleRandom.nextDouble() * 0.8) - 0.4); } - public static void cookEffects() { + /** + * Get or calculate the particle color from the current best Cauldron Recipe + * Also calculates the best Cauldron Recipe if not yet done + * + * @return the Particle Color, after potentially calculating it + */ + @NotNull + public Color getParticleColor() { + if (state < 1) { + return Color.fromRGB(153, 221, 255); // Bright Blue + } + if (particleColor != null) { + return particleColor; + } + if (particleRecipe == null) { + // Check for Cauldron Recipe + particleRecipe = Optional.ofNullable(ingredients.getCauldronRecipe()); + } + + List> colorList = null; + if (particleRecipe.isPresent()) { + colorList = particleRecipe.get().getParticleColor(); + } + + if (colorList == null || colorList.isEmpty()) { + // No color List configured, or no recipe found + colorList = new ArrayList<>(1); + colorList.add(new Tuple<>(10, Color.fromRGB(77, 166, 255))); // Dark Aqua kind of Blue + } + int index = 0; + while (index < colorList.size() - 1 && colorList.get(index).a() < state) { + // Find the first index where the colorList Minute is higher than the state + index++; + } + + int minute = colorList.get(index).a(); + if (minute > state) { + // going towards the minute + int prevPos; + Color prevColor; + if (index > 0) { + // has previous colours + prevPos = colorList.get(index - 1).a(); + prevColor = colorList.get(index - 1).b(); + } else { + prevPos = 0; + prevColor = Color.fromRGB(153, 221, 255); // Bright Blue + } + + particleColor = BUtil.weightedMixColor(prevColor, prevPos, state, colorList.get(index).b(), minute); + } else if (minute == state) { + // reached the minute + particleColor = colorList.get(index).b(); + } else { + // passed the last minute configured + if (index > 0) { + // We have more than one color, just use the last one + particleColor = colorList.get(index).b(); + } else { + // Only have one color, go towards a Gray + Color nextColor = Color.fromRGB(138, 153, 168); // Dark Teal, Gray + int nextPos = (int) (minute * 2.6f); + + if (nextPos <= state) { + // We are past the next color (Gray) as well, keep using it + particleColor = nextColor; + } else { + particleColor = BUtil.weightedMixColor(colorList.get(index).b(), minute, state, nextColor, nextPos); + } + } + } + //P.p.log("RGB: " + particleColor.getRed() + "|" + particleColor.getGreen() + "|" + particleColor.getBlue()); + return particleColor; + } + + public static void processNextCookEffects() { if (!BConfig.enableCauldronParticles) return; int size = bcauldrons.size(); if (size <= 0) { @@ -422,6 +513,21 @@ public class BCauldron { } } + /** + * Recalculate the Cauldron Particle Recipe + */ + public static void reload() { + for (BCauldron cauldron : BCauldron.bcauldrons.values()) { + cauldron.particleRecipe = null; + cauldron.particleColor = null; + if (BConfig.enableCauldronParticles) { + if (BUtil.isChunkLoaded(cauldron.block) && LegacyUtil.isCauldronHeatsource(cauldron.block.getRelative(BlockFace.DOWN))) { + cauldron.getParticleColor(); + } + } + } + } + /** * reset to normal cauldron */ diff --git a/src/com/dre/brewery/P.java b/src/com/dre/brewery/P.java index f332561..3f5c5a0 100644 --- a/src/com/dre/brewery/P.java +++ b/src/com/dre/brewery/P.java @@ -198,6 +198,9 @@ public class P extends JavaPlugin { return; } + // Reload Cauldron Particle Recipes + BCauldron.reload(); + // Reload Recipes boolean successful = true; for (Brew brew : Brew.legacyPotions.values()) { @@ -485,7 +488,7 @@ public class P extends JavaPlugin { public class CauldronParticles implements Runnable { @Override public void run() { - BCauldron.cookEffects(); + BCauldron.processNextCookEffects(); } } diff --git a/src/com/dre/brewery/recipe/BCauldronRecipe.java b/src/com/dre/brewery/recipe/BCauldronRecipe.java index 11cefdc..353c3b6 100644 --- a/src/com/dre/brewery/recipe/BCauldronRecipe.java +++ b/src/com/dre/brewery/recipe/BCauldronRecipe.java @@ -9,11 +9,7 @@ import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; /** @@ -28,8 +24,8 @@ public class BCauldronRecipe { private String name; private List ingredients; - //private List particles private PotionColor color; + private List> particleColor = new ArrayList<>(); private List lore; private int cmData; // Custom Model Data private boolean saveInData; // If this recipe should be saved in data and loaded again when the server restarts. Applicable to non-config recipes @@ -78,6 +74,33 @@ public class BCauldronRecipe { //return null; } + for (String entry : cfg.getStringList(id + ".cookParticles")) { + String[] split = entry.split("/"); + int minute; + if (split.length == 1) { + minute = 10; + } else if (split.length == 2) { + minute = P.p.parseInt(split[1]); + } else { + P.p.errorLog("cookParticle: '" + entry + "' in: " + recipe.name); + return null; + } + if (minute < 1) { + P.p.errorLog("cookParticle: '" + entry + "' in: " + recipe.name); + return null; + } + PotionColor partCol = PotionColor.fromString(split[0]); + if (partCol == PotionColor.WATER && !split[0].equals("WATER")) { + P.p.errorLog("Color of cookParticle: '" + entry + "' in: " + recipe.name); + return null; + } + recipe.particleColor.add(new Tuple<>(minute, partCol.getColor())); + } + if (!recipe.particleColor.isEmpty()) { + // Sort by minute + recipe.particleColor.sort(Comparator.comparing(Tuple::first)); + } + List> lore = BRecipe.loadLore(cfg, id + ".lore"); if (lore != null && !lore.isEmpty()) { @@ -107,6 +130,11 @@ public class BCauldronRecipe { return color; } + @NotNull + public List> getParticleColor() { + return particleColor; + } + @Nullable public List getLore() { return lore; @@ -337,6 +365,11 @@ public class BCauldronRecipe { return this; } + public Builder addParticleColor(int atMinute, Color color) { + recipe.particleColor.add(new Tuple<>(atMinute, color)); + return this; + } + public Builder addLore(String line) { if (recipe.lore == null) { recipe.lore = new ArrayList<>(); @@ -358,6 +391,10 @@ public class BCauldronRecipe { if (recipe.ingredients == null || recipe.ingredients.isEmpty()) { throw new IllegalArgumentException("CauldronRecipe has no ingredients"); } + if (!recipe.particleColor.isEmpty()) { + // Sort particleColor by minute + recipe.particleColor.sort(Comparator.comparing(Tuple::first)); + } return recipe; } } diff --git a/src/com/dre/brewery/utility/BUtil.java b/src/com/dre/brewery/utility/BUtil.java index 68a94f1..3a18463 100644 --- a/src/com/dre/brewery/utility/BUtil.java +++ b/src/com/dre/brewery/utility/BUtil.java @@ -6,6 +6,7 @@ import com.dre.brewery.P; import com.dre.brewery.api.events.barrel.BarrelDestroyEvent; import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.Color; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; @@ -48,6 +49,35 @@ public class BUtil { return msg; } + /** + * Creates a weighted mix between the two given colours + *

where the weight is calculated from the distance of the currentPos to the prev and next + * + * @param prevColor Previous Color + * @param prevPos Position of the Previous Color + * @param currentPos Current Position + * @param nextColor Next Color + * @param nextPos Position of the Next Color + * @return + */ + public static Color weightedMixColor(Color prevColor, int prevPos, int currentPos, Color nextColor, int nextPos) { + float diffPrev = currentPos - prevPos; + float diffNext = nextPos - currentPos; + float total = diffNext + diffPrev; + float percentNext = diffPrev / total; + float percentPrev = diffNext / total; + + /*5 #8# 15 + 8-5 = 3 -> 3/10 + 15-8 = 7 -> 7/10*/ + + return Color.fromRGB( + Math.min(255, (int) ((nextColor.getRed() * percentNext) + (prevColor.getRed() * percentPrev))), + Math.min(255, (int) ((nextColor.getGreen() * percentNext) + (prevColor.getGreen() * percentPrev))), + Math.min(255, (int) ((nextColor.getBlue() * percentNext) + (prevColor.getBlue() * percentPrev))) + ); + } + /** * Returns either uuid or Name of player, depending on bukkit version */