diff --git a/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties b/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties index 8e7388e6..b43920e9 100644 --- a/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties +++ b/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties @@ -142,6 +142,7 @@ PARTICLE_DESIGN_PULSE_SHIMMER = §fShimmer PARTICLE_DESIGN_HEART_BEAT = §fHeartbeat PARTICLE_PARAM_PARTICLE = §fParticle +PARTICLE_PARAM_GLYPH_FLIGHT = §fGlyph flight PARTICLE_PARAM_BURST_TRIGGER = §fWhen PARTICLE_PARAM_BURST_SHAPE = §fShape PARTICLE_PARAM_BURST_RADIUS = §fRing radius @@ -228,6 +229,9 @@ PARTICLE_CHOICE_TRIGGER_LAND = §fOn landing PARTICLE_CHOICE_TRIGGER_SNEAK = §fOn sneaking PARTICLE_CHOICE_BURSTSHAPE_PUFF = §fPuff PARTICLE_CHOICE_BURSTSHAPE_RING = §fRing +PARTICLE_CHOICE_GLYPHFLIGHT_NORMAL = §fFloat down +PARTICLE_CHOICE_GLYPHFLIGHT_INTO_PLAYER = §fInto the player +PARTICLE_CHOICE_GLYPHFLIGHT_OUT_OF_PLAYER = §fOut of the player # 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 0fe6c771..cecc7084 100644 --- a/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties +++ b/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties @@ -142,6 +142,7 @@ PARTICLE_DESIGN_PULSE_SHIMMER = §fSchimmer PARTICLE_DESIGN_HEART_BEAT = §fHerzschlag PARTICLE_PARAM_PARTICLE = §fPartikel +PARTICLE_PARAM_GLYPH_FLIGHT = §fGlyphenflug PARTICLE_PARAM_BURST_TRIGGER = §fWann PARTICLE_PARAM_BURST_SHAPE = §fForm PARTICLE_PARAM_BURST_RADIUS = §fRingradius @@ -228,6 +229,9 @@ PARTICLE_CHOICE_TRIGGER_LAND = §fBeim Landen PARTICLE_CHOICE_TRIGGER_SNEAK = §fBeim Schleichen PARTICLE_CHOICE_BURSTSHAPE_PUFF = §fWolke PARTICLE_CHOICE_BURSTSHAPE_RING = §fRing +PARTICLE_CHOICE_GLYPHFLIGHT_NORMAL = §fHerabschweben +PARTICLE_CHOICE_GLYPHFLIGHT_INTO_PLAYER = §fIn den Spieler +PARTICLE_CHOICE_GLYPHFLIGHT_OUT_OF_PLAYER = §fAus dem Spieler # Particle types PARTICLE_TYPE_DUST = §fStaub diff --git a/LobbySystem/src/de/steamwar/lobby/particle/design/EntryParameters.java b/LobbySystem/src/de/steamwar/lobby/particle/design/EntryParameters.java index 29ba76e0..1bd972c4 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/design/EntryParameters.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/design/EntryParameters.java @@ -19,6 +19,7 @@ package de.steamwar.lobby.particle.design; +import de.steamwar.lobby.particle.ParticleRequirement; import de.steamwar.lobby.particle.ParticleElement; import de.steamwar.lobby.particle.elements.*; import de.steamwar.lobby.particle.elements.custom.NonMoving; @@ -43,6 +44,13 @@ public class EntryParameters { BODY, } + // Enchanting glyphs can fly between each point and the player instead of floating down + public enum GlyphFlight { + NORMAL, + INTO_PLAYER, + OUT_OF_PLAYER, + } + public enum Movement { ANY(element -> element), MOVING(element -> data -> { @@ -196,6 +204,12 @@ public class EntryParameters { public final DoubleParameter TRAIL_TARGET_X = new DoubleParameter("trailTargetX", "PARTICLE_PARAM_TRAIL_TARGET_X", 0, -5, 5, 0.1); public final DoubleParameter TRAIL_TARGET_Y = new DoubleParameter("trailTargetY", "PARTICLE_PARAM_TRAIL_TARGET_Y", 1, -5, 5, 0.1); public final DoubleParameter TRAIL_TARGET_Z = new DoubleParameter("trailTargetZ", "PARTICLE_PARAM_TRAIL_TARGET_Z", 0, -5, 5, 0.1); + public final ChoiceParameter GLYPH_FLIGHT = ChoiceParameter.ofEnum("glyphFlight", "PARTICLE_PARAM_GLYPH_FLIGHT", GlyphFlight.NORMAL, flight -> false, + flight -> switch (flight) { + case NORMAL -> ParticleRequirement.NO_REQUIRMENT; + case INTO_PLAYER -> Unlocks.WARGEAR_SEASON_PLACEMENT.get(); + case OUT_OF_PLAYER -> Unlocks.WARGEAR_SEASON.get(); + }); public final ChoiceParameter PLACEMENT = ChoiceParameter.ofEnum("placement", "PARTICLE_PARAM_PLACEMENT", Placement.WORLD); public final ChoiceParameter TICK_MODE = ChoiceParameter.ofEnum("tickMode", "PARTICLE_PARAM_TICK_MODE", TickMode.MOVE); public final ChoiceParameter MOVEMENT = ChoiceParameter.ofEnum("movement", "PARTICLE_PARAM_MOVEMENT", Movement.ANY); @@ -205,7 +219,7 @@ public class EntryParameters { public final ChoiceParameter VISIBILITY = ChoiceParameter.ofEnum("visibility", "PARTICLE_PARAM_VISIBILITY", Visibility.EVERYONE); public final List> ALL = List.of( - PARTICLE, COUNT, SPREAD_X, SPREAD_Y, SPREAD_Z, SPEED, COLORS, BLOCK, + PARTICLE, GLYPH_FLIGHT, COUNT, SPREAD_X, SPREAD_Y, SPREAD_Z, SPEED, COLORS, BLOCK, TRAIL_DURATION, TRAIL_TARGET_X, TRAIL_TARGET_Y, TRAIL_TARGET_Z, OFFSET_X, OFFSET_Y, OFFSET_Z, PLACEMENT, INTERVAL, TICK_MODE, MOVEMENT, GLIDING, GROUND, SNEAK, VISIBILITY diff --git a/LobbySystem/src/de/steamwar/lobby/particle/elements/touch/GlyphPath.java b/LobbySystem/src/de/steamwar/lobby/particle/elements/touch/GlyphPath.java new file mode 100644 index 00000000..836a07a3 --- /dev/null +++ b/LobbySystem/src/de/steamwar/lobby/particle/elements/touch/GlyphPath.java @@ -0,0 +1,65 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package de.steamwar.lobby.particle.elements.touch; + +import de.steamwar.lobby.particle.ParticleElement; +import de.steamwar.lobby.particle.ParticleTickData; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.util.Vector; + +// Enchanting glyphs that fly between each point and the player's chest. Spawned with an amount of 0, the offset becomes +// the glyph's path: it appears at location + offset and flies to the location, like glyphs flying into an enchanting +// table. +public class GlyphPath implements ParticleElement { + + public enum Direction { + INTO_PLAYER, + OUT_OF_PLAYER, + } + + private static final double CHEST = 1.2; + + private final Direction direction; + private final double spreadX; + private final double spreadY; + private final double spreadZ; + private final int count; + + public GlyphPath(Direction direction, double spreadX, double spreadY, double spreadZ, int count) { + this.direction = direction; + this.spreadX = spreadX; + this.spreadY = spreadY; + this.spreadZ = spreadZ; + this.count = count; + } + + @Override + public void tick(ParticleTickData particleTickData) { + Location chest = particleTickData.getPlayer().getLocation().add(0, CHEST, 0); + for (int i = 0; i < count; i++) { + // Spread like the game spreads particles, as the game does not spread those spawned with an amount of 0 + Location point = particleTickData.getLocation().clone().add(RANDOM.nextGaussian() * spreadX, RANDOM.nextGaussian() * spreadY, RANDOM.nextGaussian() * spreadZ); + Vector fromChest = point.toVector().subtract(chest.toVector()); + Location target = direction == Direction.INTO_PLAYER ? chest : point; + Vector path = direction == Direction.INTO_PLAYER ? fromChest : fromChest.multiply(-1); + display(particleTickData, player -> player.spawnParticle(Particle.ENCHANT, target, 0, path.getX(), path.getY(), path.getZ(), 1)); + } + } +} diff --git a/LobbySystem/src/de/steamwar/lobby/particle/elements/touch/TeamLink.java b/LobbySystem/src/de/steamwar/lobby/particle/elements/touch/TeamLink.java index 96b5c020..c904e09c 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/elements/touch/TeamLink.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/elements/touch/TeamLink.java @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2026 SteamWar.de-Serverteam + * Copyright (C) 2025 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -16,7 +16,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - package de.steamwar.lobby.particle.elements.touch; import de.steamwar.lobby.particle.BodyMotion; @@ -31,21 +30,27 @@ import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; -// A line of particles between nearby players who show the same reward. Each pair is drawn by only one of them. +// A line of particles between nearby players who show the same link. It starts in one player's particle and blends +// into the other's towards them. Each pair is drawn by only one of them. public class TeamLink extends DelegatingParticleElement { public static final double RANGE = 8; private static final double CHEST = 1.2; + // Spreads the blend evenly along the line, so it does not flicker from tick to tick + private static final double GOLDEN_RATIO = 0.6180339887; - // Per reward, who showed it in which tick - private static final Map> SHOWING = new HashMap<>(); + private record Showing(long tick, ParticleElement particle) { + } - private final String reward; + // Per link, who showed it in which tick with which particle + private static final Map> SHOWING = new HashMap<>(); + + private final String link; private final double spacing; - public TeamLink(String reward, ParticleElement particleElement, double spacing) { + public TeamLink(String link, ParticleElement particleElement, double spacing) { super(particleElement); - this.reward = reward; + this.link = link; this.spacing = spacing; } @@ -53,14 +58,14 @@ public class TeamLink extends DelegatingParticleElement { public void tick(ParticleTickData particleTickData) { Player player = particleTickData.getPlayer(); long now = BodyMotion.currentTick(); - Map showing = SHOWING.computeIfAbsent(reward, key -> new WeakHashMap<>()); - showing.put(player, now); + Map showing = SHOWING.computeIfAbsent(link, key -> new WeakHashMap<>()); + showing.put(player, new Showing(now, particleElement)); Location from = player.getLocation().add(0, CHEST, 0); - for (Map.Entry other : showing.entrySet()) { + for (Map.Entry other : showing.entrySet()) { Player partner = other.getKey(); // Still showing it, and drawn by the player with the smaller id only - if (partner == player || other.getValue() < now - 1 || player.getUniqueId().compareTo(partner.getUniqueId()) > 0) continue; + if (partner == player || other.getValue().tick() < now - 1 || player.getUniqueId().compareTo(partner.getUniqueId()) > 0) continue; Location to = partner.getLocation().add(0, CHEST, 0); if (to.getWorld() != from.getWorld() || to.distanceSquared(from) > RANGE * RANGE) continue; @@ -68,7 +73,9 @@ public class TeamLink extends DelegatingParticleElement { int points = (int) Math.ceil(step.length() / spacing); step.multiply(1.0 / points); for (int i = 1; i < points; i++) { - particleElement.tick(particleTickData.withLocation(from.clone().add(step.clone().multiply(i)))); + double towardsPartner = (double) i / points; + ParticleElement particle = (i * GOLDEN_RATIO) % 1 < towardsPartner ? other.getValue().particle() : particleElement; + particle.tick(particleTickData.withLocation(from.clone().add(step.clone().multiply(i)))); } } } diff --git a/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java b/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java index 8184f74a..cfa20384 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java @@ -126,6 +126,8 @@ class EntryEditor { inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 2, parameterItem(context, COLORS)); } else if (type.usesBlock()) { inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 2, parameterItem(context, BLOCK)); + } else if (type == ParticleType.ENCHANT) { + inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 2, parameterItem(context, GLYPH_FLIGHT)); } // The trail target follows the particle; its duration goes to the end of the particle row List> particleRow = new ArrayList<>(design.replacesAmount(entry.getValues()) ? List.of(SPEED, SPREAD_X, SPREAD_Y, SPREAD_Z) : List.of(COUNT, SPEED, SPREAD_X, SPREAD_Y, SPREAD_Z)); diff --git a/LobbySystem/src/de/steamwar/lobby/particle/slot/ParticleEntry.java b/LobbySystem/src/de/steamwar/lobby/particle/slot/ParticleEntry.java index 358f380f..e6b63492 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/slot/ParticleEntry.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/slot/ParticleEntry.java @@ -28,6 +28,7 @@ import de.steamwar.lobby.particle.UnlockContext; import de.steamwar.lobby.particle.design.*; import de.steamwar.lobby.particle.elements.*; import de.steamwar.lobby.particle.elements.touch.AfterJump; +import de.steamwar.lobby.particle.elements.touch.GlyphPath; import lombok.Getter; import org.bukkit.Location; import org.bukkit.Material; @@ -149,6 +150,10 @@ public class ParticleEntry { case ZERO_FLOAT -> (tick, location) -> 0f; }; + if (type == ParticleType.ENCHANT && values.get(GLYPH_FLIGHT) != GlyphFlight.NORMAL) { + GlyphPath.Direction direction = values.get(GLYPH_FLIGHT) == GlyphFlight.INTO_PLAYER ? GlyphPath.Direction.INTO_PLAYER : GlyphPath.Direction.OUT_OF_PLAYER; + return new GlyphPath(direction, spreadX, spreadY, spreadZ, count); + } if (type.isDust()) { return new DustParticle(type.getParticle(), spreadX, spreadY, spreadZ, speed, count, gradient); } @@ -183,7 +188,11 @@ public class ParticleEntry { return false; } } - return !design.usesEntryParticle() || PARTICLE.requirement(values.get(PARTICLE)).test(context); + if (!design.usesEntryParticle()) { + return true; + } + ParticleType type = values.get(PARTICLE); + return type.getRequirement().test(context) && (type != ParticleType.ENCHANT || GLYPH_FLIGHT.requirement(values.get(GLYPH_FLIGHT)).test(context)); } // Particles each point spawns; designs that set their own point count replace the amount with 1