diff --git a/LobbySystem/src/de/steamwar/lobby/particle/CompiledSet.java b/LobbySystem/src/de/steamwar/lobby/particle/CompiledSet.java
new file mode 100644
index 00000000..bfbecb7c
--- /dev/null
+++ b/LobbySystem/src/de/steamwar/lobby/particle/CompiledSet.java
@@ -0,0 +1,61 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2026 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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CompiledSet {
+
+ private final List always = new ArrayList<>();
+ private final List move = new ArrayList<>();
+
+ public static CompiledSet of(ParticleElement... particleElements) {
+ CompiledSet compiledSet = new CompiledSet();
+ for (ParticleElement particleElement : particleElements) {
+ compiledSet.add(particleElement);
+ }
+ return compiledSet;
+ }
+
+ public void add(ParticleElement particleElement) {
+ if (particleElement.tickType() == ParticleTickType.ALWAYS) {
+ always.add(particleElement);
+ } else {
+ move.add(particleElement);
+ }
+ }
+
+ public boolean hasAlways() {
+ return !always.isEmpty();
+ }
+
+ public boolean hasMove() {
+ return !move.isEmpty();
+ }
+
+ public void tickAlways(ParticleTickData particleTickData) {
+ always.forEach(particleElement -> particleElement.tick(particleTickData));
+ }
+
+ public void tickMove(ParticleTickData particleTickData) {
+ move.forEach(particleElement -> particleElement.tick(particleTickData));
+ }
+}
diff --git a/LobbySystem/src/de/steamwar/lobby/particle/ParticleElement.java b/LobbySystem/src/de/steamwar/lobby/particle/ParticleElement.java
index 5b900192..8a4bd6ef 100644
--- a/LobbySystem/src/de/steamwar/lobby/particle/ParticleElement.java
+++ b/LobbySystem/src/de/steamwar/lobby/particle/ParticleElement.java
@@ -19,9 +19,7 @@
package de.steamwar.lobby.particle;
-import org.bukkit.Bukkit;
import org.bukkit.Color;
-import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.entity.Player;
@@ -47,20 +45,18 @@ public interface ParticleElement {
return new Particle.DustOptions(Color.fromRGB(r, g, b), (float) (RANDOM.nextFloat() / 2 + 0.5));
}
- default void display(Location location, Player root, boolean onlySelf, boolean onlyOther, Consumer consumer) {
- if (onlySelf) {
+ default void display(ParticleTickData particleTickData, Consumer consumer) {
+ Player root = particleTickData.getPlayer();
+ if (particleTickData.isOnlySelf()) {
consumer.accept(root);
return;
}
- Bukkit.getOnlinePlayers().forEach(player -> {
- if (onlyOther && player == root) {
- return;
+ for (Player player : particleTickData.getViewers()) {
+ if (particleTickData.isOnlyOthers() && player == root) {
+ continue;
}
- int viewDistance = player.getClientViewDistance() * 16;
- if (location.distanceSquared(player.getLocation()) <= viewDistance * viewDistance) {
- consumer.accept(player);
- }
- });
+ consumer.accept(player);
+ }
}
default void aggregateAttributes(ParticleData particleData) {
diff --git a/LobbySystem/src/de/steamwar/lobby/particle/ParticleListener.java b/LobbySystem/src/de/steamwar/lobby/particle/ParticleListener.java
index 52261647..90a0dc9d 100644
--- a/LobbySystem/src/de/steamwar/lobby/particle/ParticleListener.java
+++ b/LobbySystem/src/de/steamwar/lobby/particle/ParticleListener.java
@@ -27,6 +27,7 @@ import de.steamwar.lobby.util.LobbyPlayer;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import org.bukkit.Bukkit;
+import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
@@ -36,7 +37,9 @@ import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
@Linked
@@ -58,14 +61,9 @@ public class ParticleListener implements Listener {
Bukkit.getOnlinePlayers().forEach(player -> {
if (disabled(player)) return;
if (JumpAndRun.isPlayerInJumpAndRun(player)) return;
- LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player.getUniqueId());
- ParticleEnum particle = lobbyPlayer.getParticle();
- if (particle == null) return;
- ParticleData particleData = particle.getParticle();
- ParticleElement particleElement = particleData.getParticleElement();
- if (particleElement.tickType() == ParticleTickType.ALWAYS) {
- particleElement.tick(new ParticleTickData(player.getWorld(), player, deg, movingPlayers.containsKey(player)));
- }
+ CompiledSet compiledSet = LobbyPlayer.getLobbyPlayer(player.getUniqueId()).getCompiledSet();
+ if (compiledSet == null || !compiledSet.hasAlways()) return;
+ compiledSet.tickAlways(new ParticleTickData(player.getWorld(), player, deg, movingPlayers.containsKey(player), viewers(player)));
});
}, 0, 1);
}
@@ -85,14 +83,11 @@ public class ParticleListener implements Listener {
Player player = event.getPlayer();
if (disabled(player)) return;
if (JumpAndRun.isPlayerInJumpAndRun(player)) return;
- LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player.getUniqueId());
- ParticleEnum particle = lobbyPlayer.getParticle();
- if (particle == null) return;
+ CompiledSet compiledSet = LobbyPlayer.getLobbyPlayer(player.getUniqueId()).getCompiledSet();
+ if (compiledSet == null) return;
- ParticleData particleData = particle.getParticle();
- ParticleElement particleElement = particleData.getParticleElement();
- if (particleElement.tickType() == ParticleTickType.MOVE) {
- particleElement.tick(new ParticleTickData(player.getWorld(), player, deg, true));
+ if (compiledSet.hasMove()) {
+ compiledSet.tickMove(new ParticleTickData(player.getWorld(), player, deg, true, viewers(player)));
}
if (event.getFrom().getX() != event.getTo().getX() || event.getFrom().getY() != event.getTo().getY() || event.getFrom().getZ() != event.getTo().getZ()) {
movingPlayers.put(player, 5);
@@ -111,6 +106,18 @@ public class ParticleListener implements Listener {
}
}
+ private static List viewers(Player root) {
+ Location location = root.getLocation();
+ List viewers = new ArrayList<>();
+ for (Player player : root.getWorld().getPlayers()) {
+ int viewDistance = player.getClientViewDistance() * 16;
+ if (location.distanceSquared(player.getLocation()) <= viewDistance * viewDistance) {
+ viewers.add(player);
+ }
+ }
+ return viewers;
+ }
+
public static boolean disabled(Player player) {
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
if (Bukkit.getOnlinePlayers().size() > SERVER_TEAM_MAX_SIZE) return true;
diff --git a/LobbySystem/src/de/steamwar/lobby/particle/ParticleTickData.java b/LobbySystem/src/de/steamwar/lobby/particle/ParticleTickData.java
index cfbea1bf..f5f7cb37 100644
--- a/LobbySystem/src/de/steamwar/lobby/particle/ParticleTickData.java
+++ b/LobbySystem/src/de/steamwar/lobby/particle/ParticleTickData.java
@@ -26,6 +26,8 @@ import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
+import java.util.List;
+
@RequiredArgsConstructor
@AllArgsConstructor
@Getter
@@ -36,6 +38,7 @@ public class ParticleTickData {
private Location location;
private final double deg;
private final boolean isMoving;
+ private final List viewers;
private boolean onlySelf = false;
private boolean onlyOthers = false;
@@ -65,6 +68,6 @@ public class ParticleTickData {
}
public ParticleTickData copy() {
- return new ParticleTickData(world, player, location, deg, isMoving, onlySelf, onlyOthers);
+ return new ParticleTickData(world, player, location, deg, isMoving, viewers, onlySelf, onlyOthers);
}
}
diff --git a/LobbySystem/src/de/steamwar/lobby/particle/elements/Delayed.java b/LobbySystem/src/de/steamwar/lobby/particle/elements/Delayed.java
index 3a45f0e5..b26e8510 100644
--- a/LobbySystem/src/de/steamwar/lobby/particle/elements/Delayed.java
+++ b/LobbySystem/src/de/steamwar/lobby/particle/elements/Delayed.java
@@ -19,35 +19,21 @@
package de.steamwar.lobby.particle.elements;
-import de.steamwar.lobby.LobbySystem;
import de.steamwar.lobby.particle.ParticleElement;
import de.steamwar.lobby.particle.ParticleTickData;
-import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerQuitEvent;
-import java.util.HashMap;
import java.util.Map;
+import java.util.WeakHashMap;
public class Delayed extends DelegatingParticleElement {
- private DelayedData delayedData = new DelayedData();
+ // Weak keys drop players once they quit, so no quit listener has to be registered per instance
+ private final Map data = new WeakHashMap<>();
private int interval;
- private static class DelayedData implements Listener {
- private Map data = new HashMap<>();
-
- @EventHandler(ignoreCancelled = true)
- public void onPlayerQuit(PlayerQuitEvent event) {
- data.remove(event.getPlayer());
- }
- }
-
public Delayed(ParticleElement particleElement, int interval) {
super(particleElement);
- Bukkit.getPluginManager().registerEvents(delayedData, LobbySystem.getInstance());
this.interval = interval;
if (interval <= 0) {
throw new IllegalArgumentException("Interval must be greater than 0");
@@ -61,8 +47,8 @@ public class Delayed extends DelegatingParticleElement {
@Override
public void tick(ParticleTickData particleTickData) {
- int currentNumber = delayedData.data.getOrDefault(particleTickData.getPlayer(), 0) % interval;
- delayedData.data.put(particleTickData.getPlayer(), currentNumber + 1);
+ int currentNumber = data.getOrDefault(particleTickData.getPlayer(), 0) % interval;
+ data.put(particleTickData.getPlayer(), currentNumber + 1);
if (currentNumber != 0) {
return;
}
diff --git a/LobbySystem/src/de/steamwar/lobby/particle/elements/DustParticle.java b/LobbySystem/src/de/steamwar/lobby/particle/elements/DustParticle.java
index 375e80d1..fba37c4c 100644
--- a/LobbySystem/src/de/steamwar/lobby/particle/elements/DustParticle.java
+++ b/LobbySystem/src/de/steamwar/lobby/particle/elements/DustParticle.java
@@ -71,7 +71,7 @@ public class DustParticle implements ParticleElement {
} else {
dustOptions = randomParticleDust();
}
- display(location, particleTickData.getPlayer(), particleTickData.isOnlySelf(), particleTickData.isOnlyOthers(), player -> {
+ display(particleTickData, player -> {
player.spawnParticle(particle, location, count, vx, vy, vz, speed, dustOptions);
});
}
diff --git a/LobbySystem/src/de/steamwar/lobby/particle/elements/RandomParticle.java b/LobbySystem/src/de/steamwar/lobby/particle/elements/RandomParticle.java
index 3dc0b809..94842466 100644
--- a/LobbySystem/src/de/steamwar/lobby/particle/elements/RandomParticle.java
+++ b/LobbySystem/src/de/steamwar/lobby/particle/elements/RandomParticle.java
@@ -19,58 +19,30 @@
package de.steamwar.lobby.particle.elements;
-import de.steamwar.lobby.LobbySystem;
import de.steamwar.lobby.particle.ParticleElement;
import de.steamwar.lobby.particle.ParticleEnum;
import de.steamwar.lobby.particle.ParticleTickData;
-import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerJoinEvent;
-import org.bukkit.event.player.PlayerQuitEvent;
-import java.util.HashMap;
import java.util.Map;
-import java.util.Random;
+import java.util.WeakHashMap;
public class RandomParticle implements ParticleElement {
- private RandomData randomData;
- private ParticleEnum[] particleEnums;
-
- private static class RandomData implements Listener {
- private Random random = new Random();
- private int max;
- private Map data = new HashMap<>();
-
- public RandomData(int max) {
- this.max = max;
- }
-
- @EventHandler(ignoreCancelled = true)
- public void onPlayerJoin(PlayerJoinEvent event) {
- data.put(event.getPlayer(), random.nextInt(max));
- }
-
- @EventHandler(ignoreCancelled = true)
- public void onPlayerQuit(PlayerQuitEvent event) {
- data.remove(event.getPlayer());
- }
- }
+ // Weak keys drop players once they quit, so each player rolls again on their next session
+ private final Map data = new WeakHashMap<>();
+ private final ParticleEnum[] particleEnums;
public RandomParticle(ParticleEnum... particleEnums) {
if (particleEnums.length == 0) {
throw new IllegalArgumentException("ParticleEnums must not be empty");
}
- this.randomData = new RandomData(particleEnums.length);
- Bukkit.getPluginManager().registerEvents(randomData, LobbySystem.getInstance());
this.particleEnums = particleEnums;
}
@Override
public void tick(ParticleTickData particleTickData) {
- int currentNumber = randomData.data.getOrDefault(particleTickData.getPlayer(), 0);
+ int currentNumber = data.computeIfAbsent(particleTickData.getPlayer(), player -> RANDOM.nextInt(particleEnums.length));
particleEnums[currentNumber].getParticle().getParticleElement().tick(particleTickData);
}
}
diff --git a/LobbySystem/src/de/steamwar/lobby/particle/elements/SimpleParticle.java b/LobbySystem/src/de/steamwar/lobby/particle/elements/SimpleParticle.java
index 23b556cb..cfd9762f 100644
--- a/LobbySystem/src/de/steamwar/lobby/particle/elements/SimpleParticle.java
+++ b/LobbySystem/src/de/steamwar/lobby/particle/elements/SimpleParticle.java
@@ -74,7 +74,7 @@ public class SimpleParticle implements ParticleElement {
@Override
public void tick(ParticleTickData particleTickData) {
Location location = particleTickData.getLocation().add(0.0, 0.2, 0.0);
- display(location, particleTickData.getPlayer(), particleTickData.isOnlySelf(), particleTickData.isOnlyOthers(), player -> {
+ display(particleTickData, player -> {
if (customVelocity) {
player.spawnParticle(particle, location, count, vx, vy, vz, time);
} else {
diff --git a/LobbySystem/src/de/steamwar/lobby/util/LobbyPlayer.java b/LobbySystem/src/de/steamwar/lobby/util/LobbyPlayer.java
index a98abfff..1eaab799 100644
--- a/LobbySystem/src/de/steamwar/lobby/util/LobbyPlayer.java
+++ b/LobbySystem/src/de/steamwar/lobby/util/LobbyPlayer.java
@@ -19,6 +19,7 @@
package de.steamwar.lobby.util;
+import de.steamwar.lobby.particle.CompiledSet;
import de.steamwar.lobby.particle.ParticleEnum;
import de.steamwar.lobby.particle.ParticleInventory;
import de.steamwar.sql.SteamwarUser;
@@ -34,6 +35,7 @@ public class LobbyPlayer {
private static Map cache = new HashMap<>();
private ParticleEnum particle;
+ private CompiledSet compiledSet;
private int userId;
private boolean fly;
@@ -46,6 +48,7 @@ public class LobbyPlayer {
if (saved != null) {
particle = ParticleInventory.convertFromString(saved);
}
+ compile();
}
public boolean isFlying() {
@@ -68,6 +71,15 @@ public class LobbyPlayer {
UserConfig.updatePlayerConfig(userId, "lobby-particle", saved);
}
this.particle = particle;
+ compile();
+ }
+
+ public CompiledSet getCompiledSet() {
+ return compiledSet;
+ }
+
+ private void compile() {
+ compiledSet = particle == null ? null : CompiledSet.of(particle.getParticle().getParticleElement());
}
public static LobbyPlayer getLobbyPlayer(UUID uuid) {