forked from SteamWar/SteamWar
Refactor lobby particle runtime for multi-entry particles
Tick particles from per-player cached always/move lists, drop the per-instance listeners in Delayed and RandomParticle, and compute particle viewers once per tick instead of once per spawned point.
This commit is contained in:
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CompiledSet {
|
||||
|
||||
private final List<ParticleElement> always = new ArrayList<>();
|
||||
private final List<ParticleElement> 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));
|
||||
}
|
||||
}
|
||||
@@ -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<Player> consumer) {
|
||||
if (onlySelf) {
|
||||
default void display(ParticleTickData particleTickData, Consumer<Player> 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) {
|
||||
|
||||
@@ -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<Player> viewers(Player root) {
|
||||
Location location = root.getLocation();
|
||||
List<Player> 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;
|
||||
|
||||
@@ -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<Player> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Player, Integer> data = new WeakHashMap<>();
|
||||
private int interval;
|
||||
|
||||
private static class DelayedData implements Listener {
|
||||
private Map<Player, Integer> 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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<Player, Integer> 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<Player, Integer> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<UUID, LobbyPlayer> 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) {
|
||||
|
||||
Reference in New Issue
Block a user