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) {