diff --git a/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties b/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties index 40477799..15f50cb3 100644 --- a/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties +++ b/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties @@ -171,6 +171,8 @@ PARTICLE_PARAM_WING_IMAGE = §fWing shape PARTICLE_PARAM_WING_SIZE = §fWing size PARTICLE_PARAM_DRAWN_SHAPE = §fShape PARTICLE_PARAM_WING_DETAIL = §fPoints per wing +PARTICLE_PARAM_WING_COLORING = §fColour mode +PARTICLE_PARAM_WING_SPARKLE = §fSparkle (points shown, %) PARTICLE_CHOICE_TICKMODE_MOVE = §fWhile moving PARTICLE_CHOICE_TICKMODE_ALWAYS = §fAlways @@ -194,11 +196,11 @@ PARTICLE_CHOICE_VISIBILITY_ONLY_OTHERS = §fOnly others PARTICLE_CHOICE_IMAGE_SIMPLE = §fSimple PARTICLE_CHOICE_IMAGE_COMPLEX = §fComplex PARTICLE_CHOICE_IMAGE_SWORD = §fSword -PARTICLE_CHOICE_SHAPE_ANGEL = §fAngel PARTICLE_CHOICE_SHAPE_BAT = §fBat PARTICLE_CHOICE_SHAPE_BUTTERFLY = §fButterfly PARTICLE_CHOICE_SHAPE_FAIRY = §fFairy -PARTICLE_CHOICE_SHAPE_PHOENIX = §fPhoenix +PARTICLE_CHOICE_COLORING_OVER_TIME = §fChange over time +PARTICLE_CHOICE_COLORING_ALONG_WING = §fFrom the back to the tips # Particle types PARTICLE_TYPE_DUST = §fDust diff --git a/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties b/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties index f1da7ad6..54bd05ec 100644 --- a/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties +++ b/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties @@ -171,6 +171,8 @@ PARTICLE_PARAM_WING_IMAGE = §fFlügelform PARTICLE_PARAM_WING_SIZE = §fFlügelgröße PARTICLE_PARAM_DRAWN_SHAPE = §fForm PARTICLE_PARAM_WING_DETAIL = §fPunkte pro Flügel +PARTICLE_PARAM_WING_COLORING = §fFarbmodus +PARTICLE_PARAM_WING_SPARKLE = §fFunkeln (sichtbare Punkte, %) PARTICLE_CHOICE_TICKMODE_MOVE = §fBeim Bewegen PARTICLE_CHOICE_TICKMODE_ALWAYS = §fImmer @@ -194,11 +196,11 @@ PARTICLE_CHOICE_VISIBILITY_ONLY_OTHERS = §fNur andere PARTICLE_CHOICE_IMAGE_SIMPLE = §fEinfach PARTICLE_CHOICE_IMAGE_COMPLEX = §fKomplex PARTICLE_CHOICE_IMAGE_SWORD = §fSchwert -PARTICLE_CHOICE_SHAPE_ANGEL = §fEngel PARTICLE_CHOICE_SHAPE_BAT = §fFledermaus PARTICLE_CHOICE_SHAPE_BUTTERFLY = §fSchmetterling PARTICLE_CHOICE_SHAPE_FAIRY = §fFee -PARTICLE_CHOICE_SHAPE_PHOENIX = §fPhönix +PARTICLE_CHOICE_COLORING_OVER_TIME = §fWechseln mit der Zeit +PARTICLE_CHOICE_COLORING_ALONG_WING = §fVom Rücken zu den Spitzen # Particle types PARTICLE_TYPE_DUST = §fStaub diff --git a/LobbySystem/src/de/steamwar/lobby/particle/DrawnWings.java b/LobbySystem/src/de/steamwar/lobby/particle/DrawnWings.java index ef8bd082..6b122185 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/DrawnWings.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/DrawnWings.java @@ -30,18 +30,6 @@ import java.util.Map; // Coordinates use the same units as the wing images: x is sideways from the spine, y upwards, about 11 by 13. public enum DrawnWings { - // A curved upper edge, a row of covert feathers and long flight feathers ending in a scalloped edge - ANGEL { - @Override - List> rightWing() { - List> lines = new ArrayList<>(); - lines.add(curve(p(0.5, 7), p(3, 10), p(6, 11.8), p(9, 12.4), p(10.8, 11.2), p(11, 8.5))); - lines.add(curve(p(1, 6.6), p(4, 8.3), p(7, 9.3), p(10, 9.8))); - lines.add(lineThrough(p(0.5, 5.5), p(2.2, 3.8), p(3.4, 4.6), p(4.2, 2.8), p(5.4, 3.9), p(6.3, 2.6), p(7.5, 4), - p(8.3, 3.4), p(9.4, 5.2), p(10, 5), p(10.7, 6.6), p(11, 7.5), p(11, 8.5))); - return lines; - } - }, // An arm bone along the top, fingers spreading from the wrist and skin hanging between them BAT { @Override @@ -82,19 +70,6 @@ public enum DrawnWings { return List.of(ellipse(5.2, 9.3, 5, 1.9, 35), ellipse(3.8, 4.6, 3.6, 1.5, -30)); } }, - // An upswept lower edge with flame tongues rising from it, each bending out and curling back at the tip - PHOENIX { - @Override - List> rightWing() { - List> lines = new ArrayList<>(); - lines.add(curve(p(0.5, 5), p(4, 5.2), p(8, 7), p(11, 10), p(12, 13))); - double[][] tongues = {{1.5, 5.2, 2.5, 10}, {4, 5.4, 5.5, 11.5}, {6.5, 6.2, 8.2, 12.8}, {9, 8, 10.5, 14}}; - for (double[] tongue : tongues) { - lines.add(curve(p(tongue[0], tongue[1]), p(tongue[0] + 1.2, tongue[1] + 1.8), p(tongue[2] - 0.8, tongue[3] - 1.5), p(tongue[2], tongue[3]))); - } - return lines; - } - }, ; // The right wing as lines; the left wing mirrors it @@ -172,11 +147,6 @@ public enum DrawnWings { return result; } - // Straight segments between the points, for sharp corners like feather tips - private static List lineThrough(Vector... points) { - return new ArrayList<>(List.of(points)); - } - private static List ellipse(double centerX, double centerY, double radiusX, double radiusY, double tiltDegrees) { double tilt = Math.toRadians(tiltDegrees); List result = new ArrayList<>(); diff --git a/LobbySystem/src/de/steamwar/lobby/particle/ParticleTickData.java b/LobbySystem/src/de/steamwar/lobby/particle/ParticleTickData.java index 569af6fa..fce733ae 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/ParticleTickData.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/ParticleTickData.java @@ -43,6 +43,8 @@ public class ParticleTickData { private boolean onlyOthers = false; // Set for entries placed relative to the body, null for world placement private BodyFrame frame; + // Where a point sits along its shape, from 0 to 1; when set, gradients colour by it instead of by time + private Double shade; public ParticleTickData withLocation(Location location) { ParticleTickData particleTickData = copy(); @@ -57,6 +59,17 @@ public class ParticleTickData { return particleTickData; } + public ParticleTickData withShade(double shade) { + ParticleTickData particleTickData = copy(); + particleTickData.shade = shade; + return particleTickData; + } + + // The gradient position for this tick: along the shape when a shade is set, otherwise moving with time + public double gradientPosition() { + return shade != null ? Math.min(shade, 0.9999) * 360 : (deg * 10) % 360; + } + public ParticleTickData onlySelf() { ParticleTickData particleTickData = copy(); particleTickData.onlySelf = true; @@ -77,6 +90,6 @@ public class ParticleTickData { } public ParticleTickData copy() { - return new ParticleTickData(world, player, location, deg, isMoving, viewers, onlySelf, onlyOthers, frame); + return new ParticleTickData(world, player, location, deg, isMoving, viewers, onlySelf, onlyOthers, frame, shade); } } diff --git a/LobbySystem/src/de/steamwar/lobby/particle/design/Designs.java b/LobbySystem/src/de/steamwar/lobby/particle/design/Designs.java index c6a351bb..c23fe63f 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/design/Designs.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/design/Designs.java @@ -84,15 +84,26 @@ public class Designs { )); public final DoubleParameter WING_SIZE = new DoubleParameter("size", "PARTICLE_PARAM_WING_SIZE", 0.15, 0.05, 0.3, 0.01); - public final ParticleDesign WING = new ParticleDesign("wing", Material.ELYTRA, "PARTICLE_DESIGN_WING", WING_IMAGE, WING_SIZE) { + public enum WingColoring { + // The whole wing changes colour through the entry's colours over time + OVER_TIME, + // The colours run from the spine to the wing tips + ALONG_WING, + } + + public final ChoiceParameter WING_COLORING = ChoiceParameter.ofEnum("coloring", "PARTICLE_PARAM_WING_COLORING", WingColoring.OVER_TIME); + // Percent of the points shown each tick; fewer makes the wing twinkle and costs less + public final IntParameter WING_SPARKLE = new IntParameter("sparkle", "PARTICLE_PARAM_WING_SPARKLE", 100, 10, 100); + + public final ParticleDesign WING = new ParticleDesign("wing", Material.ELYTRA, "PARTICLE_DESIGN_WING", WING_IMAGE, WING_SIZE, WING_COLORING, WING_SPARKLE) { @Override public ParticleElement build(ParameterValues values, ParticleElement particle) { - return new Wing(particle, values.get(WING_SIZE), values.get(WING_IMAGE)); + return wing(values, particle, values.get(WING_IMAGE)); } @Override public int points(ParameterValues values) { - return values.get(WING_IMAGE).getVectors().length; + return shown(values, values.get(WING_IMAGE).getVectors().length); } // Wings spawn a particle on every pixel of the image, so they start sparse, and sit on the back even while gliding @@ -106,19 +117,20 @@ public class Designs { } }; - public final ChoiceParameter DRAWN_SHAPE = ChoiceParameter.ofEnum("shape", "PARTICLE_PARAM_DRAWN_SHAPE", DrawnWings.ANGEL); + public final ChoiceParameter DRAWN_SHAPE = ChoiceParameter.ofEnum("shape", "PARTICLE_PARAM_DRAWN_SHAPE", DrawnWings.BAT); public final IntParameter WING_DETAIL = new IntParameter("detail", "PARTICLE_PARAM_WING_DETAIL", 20, 6, 60); // Wings drawn from lines, with a chosen number of points per wing; sparse ones stay cheap enough to show often - public final ParticleDesign DRAWN_WING = new ParticleDesign("drawn_wing", Material.FEATHER, "PARTICLE_DESIGN_DRAWN_WING", DRAWN_SHAPE, WING_DETAIL, WING_SIZE) { + public final ParticleDesign DRAWN_WING = new ParticleDesign("drawn_wing", Material.FEATHER, "PARTICLE_DESIGN_DRAWN_WING", + DRAWN_SHAPE, WING_DETAIL, WING_SIZE, WING_COLORING, WING_SPARKLE) { @Override public ParticleElement build(ParameterValues values, ParticleElement particle) { - return new Wing(particle, values.get(WING_SIZE), values.get(DRAWN_SHAPE).withDetail(values.get(WING_DETAIL))); + return wing(values, particle, values.get(DRAWN_SHAPE).withDetail(values.get(WING_DETAIL))); } @Override public int points(ParameterValues values) { - return 2 * values.get(WING_DETAIL); + return shown(values, 2 * values.get(WING_DETAIL)); } @Override @@ -134,6 +146,15 @@ public class Designs { } }; + private ParticleElement wing(ParameterValues values, ParticleElement particle, WingDesign shape) { + return new Wing(particle, values.get(WING_SIZE), shape, values.get(WING_COLORING) == WingColoring.ALONG_WING, values.get(WING_SPARKLE)); + } + + // Points shown per tick on average with the sparkle setting + private int shown(ParameterValues values, int points) { + return (int) Math.ceil(points * values.get(WING_SPARKLE) / 100.0); + } + private Map registry() { if (registry == null) { Map designs = new LinkedHashMap<>(); diff --git a/LobbySystem/src/de/steamwar/lobby/particle/elements/DataParticle.java b/LobbySystem/src/de/steamwar/lobby/particle/elements/DataParticle.java index 5f8d3fce..4c7f78f7 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/elements/DataParticle.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/elements/DataParticle.java @@ -54,7 +54,7 @@ public class DataParticle implements ParticleElement { if (gradient == null) { return Color.fromRGB(RANDOM.nextInt(256), RANDOM.nextInt(256), RANDOM.nextInt(256)); } - java.awt.Color color = gradient.getColor((particleTickData.getDeg() * 10) % 360, 360); + java.awt.Color color = gradient.getColor(particleTickData.gradientPosition(), 360); return Color.fromRGB(color.getRed(), color.getGreen(), color.getBlue()); } diff --git a/LobbySystem/src/de/steamwar/lobby/particle/elements/DustParticle.java b/LobbySystem/src/de/steamwar/lobby/particle/elements/DustParticle.java index fba37c4c..4342aae7 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/elements/DustParticle.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/elements/DustParticle.java @@ -65,8 +65,7 @@ public class DustParticle implements ParticleElement { Location location = particleTickData.getLocation().add(0.0, 0.2, 0.0); Particle.DustOptions dustOptions; if (gradient != null) { - double result = (particleTickData.getDeg() * 10) % 360; - Color color = gradient.getColor(result, 360); + Color color = gradient.getColor(particleTickData.gradientPosition(), 360); dustOptions = withColor(color.getRed(), color.getGreen(), color.getBlue()); } else { dustOptions = randomParticleDust(); diff --git a/LobbySystem/src/de/steamwar/lobby/particle/elements/Wing.java b/LobbySystem/src/de/steamwar/lobby/particle/elements/Wing.java index 7c9e9d8d..f0778880 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/elements/Wing.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/elements/Wing.java @@ -30,6 +30,11 @@ public class Wing extends DelegatingParticleElement { private double size; private WingDesign wingDesign; + // Colour each point by its distance from the spine instead of by time + private boolean colorAlongWing; + // Share of the points shown each tick, in percent; below 100 the wing twinkles + private int density = 100; + private double widest; public Wing(ParticleElement particleElement, double size, WingDesign wingDesign) { super(particleElement); @@ -37,6 +42,15 @@ public class Wing extends DelegatingParticleElement { this.wingDesign = wingDesign; } + public Wing(ParticleElement particleElement, double size, WingDesign wingDesign, boolean colorAlongWing, int density) { + this(particleElement, size, wingDesign); + this.colorAlongWing = colorAlongWing; + this.density = density; + for (Vector vector : wingDesign.getVectors()) { + widest = Math.max(widest, Math.abs(vector.getX())); + } + } + @Override public void tick(ParticleTickData particleTickData) { BodyFrame frame = particleTickData.getFrame(); @@ -45,19 +59,33 @@ public class Wing extends DelegatingParticleElement { double sneak = particleTickData.getPlayer().isSneaking() ? 0.5 : 0; Location base = particleTickData.getLocation(); for (Vector dVector : wingDesign.getVectors()) { + if (hidden()) continue; Vector local = frame.local(dVector.getX() * size, 0.6 + dVector.getY() * size - sneak, -0.5); - particleElement.tick(particleTickData.withLocation(base.clone().add(local))); + particleElement.tick(shaded(particleTickData, dVector).withLocation(base.clone().add(local))); } return; } for (Vector dVector : wingDesign.getVectors()) { + if (hidden()) continue; Vector vector = new Vector(dVector.getX() * size, 0.6 + dVector.getY() * size - (particleTickData.getPlayer().isSneaking() ? 0.5 : 0), 0.5); vector.rotateAroundY(Math.toRadians(particleTickData.getPlayer().getLocation().getYaw() * -1)); vector.setX(-vector.getX()); vector.setZ(-vector.getZ()); Location location = particleTickData.getPlayer().getLocation().clone().add(vector); - ParticleTickData current = particleTickData.withLocation(location); + ParticleTickData current = shaded(particleTickData, dVector).withLocation(location); particleElement.tick(current); } } + + // Full wings never draw a random number, so presets keep their exact look + private boolean hidden() { + return density < 100 && RANDOM.nextInt(100) >= density; + } + + private ParticleTickData shaded(ParticleTickData particleTickData, Vector point) { + if (!colorAlongWing || widest == 0) { + return particleTickData; + } + return particleTickData.withShade(Math.abs(point.getX()) / widest); + } } diff --git a/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java b/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java index 10e79cca..b2d55a44 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java @@ -88,7 +88,9 @@ class EntryEditor { Map.entry(Designs.WING_IMAGE, Material.PHANTOM_MEMBRANE), Map.entry(Designs.WING_SIZE, Material.SCAFFOLDING), Map.entry(Designs.DRAWN_SHAPE, Material.FEATHER), - Map.entry(Designs.WING_DETAIL, Material.AMETHYST_SHARD) + Map.entry(Designs.WING_DETAIL, Material.AMETHYST_SHARD), + Map.entry(Designs.WING_COLORING, Material.CYAN_DYE), + Map.entry(Designs.WING_SPARKLE, Material.AMETHYST_CLUSTER) ); private record Context(Player player, SteamwarUser user, int index, int entryIndex, ParticleSlot slot, ParticleEntry entry) {