mirror of
https://github.com/TotalFreedomMC/PlayerParticles.git
synced 2025-02-11 03:29:53 +00:00
WorldGuard region support
This commit is contained in:
parent
4afc2135b1
commit
179c6b58e1
17 changed files with 172 additions and 27 deletions
|
@ -10,7 +10,7 @@ sourceCompatibility = 1.8
|
|||
targetCompatibility = 1.8
|
||||
compileJava.options.encoding = 'UTF-8'
|
||||
group = 'dev.esophose'
|
||||
version = '7.11'
|
||||
version = '7.12'
|
||||
|
||||
java {
|
||||
withJavadocJar()
|
||||
|
@ -31,6 +31,7 @@ dependencies {
|
|||
compile 'org.slf4j:slf4j-nop:1.7.25'
|
||||
compile 'com.zaxxer:HikariCP:3.2.0'
|
||||
compile 'org.bstats:bstats-bukkit-lite:1.7'
|
||||
compile 'org.codemc.worldguardwrapper:worldguardwrapper:1.1.6-SNAPSHOT'
|
||||
shadow 'com.googlecode.json-simple:json-simple:1.1.1'
|
||||
shadow 'org.jetbrains:annotations:16.0.2'
|
||||
shadow 'me.clip:placeholderapi:2.10.4'
|
||||
|
@ -45,6 +46,7 @@ shadowJar {
|
|||
relocate('org.bstats', 'dev.esophose.playerparticles.libs.bstats')
|
||||
relocate('org.slf4j', 'dev.esophose.playerparticles.libs.slf4j')
|
||||
relocate('com.zaxxer.hikari', 'dev.esophose.playerparticles.libs.hikaricp')
|
||||
relocate('org.codemc.worldguardwrapper', 'dev.esophose.playerparticles.libs.worldguardwrapper')
|
||||
}
|
||||
|
||||
processResources {
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
=== v7.12 ===
|
||||
+ Added WorldGuard region support
|
||||
+ Added permission 'playerparticles.worldguard.bypass'
|
||||
+ Added placeholder %playerparticles_is_in_allowed_region%
|
||||
* Fixed empty player list bug with versions older than 1.9
|
||||
=== v7.11 ===
|
||||
+ Added support for Spigot 1.8.8 and 1.7.10. I make no guarantees this will work perfectly.
|
||||
* The plugin will now disable and print an error message on startup if the server is running CraftBukkit
|
||||
|
|
|
@ -36,6 +36,8 @@ public class ParticlePlaceholderExpansion extends PlaceholderExpansion {
|
|||
return String.valueOf(pplayer.isMoving());
|
||||
case "is_in_combat":
|
||||
return String.valueOf(pplayer.isInCombat());
|
||||
case "is_in_allowed_region":
|
||||
return String.valueOf(pplayer.isInAllowedRegion());
|
||||
case "can_see_particles":
|
||||
return String.valueOf(pplayer.canSeeParticles());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package dev.esophose.playerparticles.hook;
|
||||
|
||||
import dev.esophose.playerparticles.manager.ConfigurationManager.Setting;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.codemc.worldguardwrapper.WorldGuardWrapper;
|
||||
import org.codemc.worldguardwrapper.region.IWrappedRegion;
|
||||
|
||||
public class WorldGuardHook {
|
||||
|
||||
private static Boolean enabled;
|
||||
private static WorldGuardWrapper worldGuardWrapper;
|
||||
|
||||
/**
|
||||
* @return true if WorldGuard is enabled, otherwise false
|
||||
*/
|
||||
public static boolean enabled() {
|
||||
if (enabled != null)
|
||||
return enabled;
|
||||
enabled = Bukkit.getPluginManager().getPlugin("WorldGuard") != null;
|
||||
worldGuardWrapper = WorldGuardWrapper.getInstance();
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a location is in a region that allows particles to spawn
|
||||
*
|
||||
* @param location The location to check
|
||||
* @return true if the location is in an allowed region, otherwise false
|
||||
*/
|
||||
public static boolean isInAllowedRegion(Location location) {
|
||||
if (!enabled())
|
||||
return true;
|
||||
|
||||
Set<IWrappedRegion> regions = worldGuardWrapper.getRegions(location);
|
||||
|
||||
List<String> disallowedRegionIds = Setting.WORLDGUARD_DISALLOWED_REGIONS.getStringList();
|
||||
if (regions.stream().map(IWrappedRegion::getId).anyMatch(disallowedRegionIds::contains))
|
||||
return false;
|
||||
|
||||
if (Setting.WORLDGUARD_USE_ALLOWED_REGIONS.getBoolean()) {
|
||||
List<String> allowedRegionIds = Setting.WORLDGUARD_ALLOWED_REGIONS.getStringList();
|
||||
return regions.stream().map(IWrappedRegion::getId).anyMatch(allowedRegionIds::contains);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -57,6 +57,13 @@ public class ConfigurationManager extends Manager {
|
|||
DUST_SIZE("dust-size", 1.0, "How large should dust particles appear?", "Note: Can include decimals", "Only works in 1.13+"),
|
||||
GUI_GROUP_CREATION_MESSAGE_DISPLAY_AREA("gui-group-creation-message-display-area", "ACTION_BAR", "Valid values: ACTION_BAR, TITLE, CHAT", "Where should the GUI group creation countdown message be displayed?", "Note: Server versions less than 1.11.2 will always use CHAT"),
|
||||
|
||||
WORLDGUARD_SETTINGS("worldguard-settings", null, "Settings for WorldGuard", "If WorldGuard is not installed, these settings will do nothing"),
|
||||
WORLDGUARD_USE_ALLOWED_REGIONS("worldguard-settings.use-allowed-regions", false, "If true, particles will be able to spawn unless they are in a disallowed region", "If false, particles will not be able to spawn unless they are in an allowed region"),
|
||||
WORLDGUARD_ALLOWED_REGIONS("worldguard-settings.allowed-regions", Arrays.asList("example_region_1", "example_region_2"), "Regions that particles will be allowed to spawn in"),
|
||||
WORLDGUARD_DISALLOWED_REGIONS("worldguard-settings.disallowed-regions", Arrays.asList("example_region_3", "example_region_4"), "Regions that particles will be blocked from spawning in", "This overrides allowed regions if they overlap"),
|
||||
WORLDGUARD_CHECK_INTERVAL("worldguard-settings.check-interval", 10, "How often to check if a player is in a region that allows spawning particles", "Measured in ticks"),
|
||||
WORLDGUARD_ENABLE_BYPASS_PERMISSION("worldguard-settings.enable-bypass-permission", false, "If true, the permission playerparticles.worldguard.bypass will allow", "the player to bypass the region requirements"),
|
||||
|
||||
MYSQL_SETTINGS("mysql-settings", null, "Settings for if you want to use MySQL for data management"),
|
||||
MYSQL_ENABLED("mysql-settings.enabled", false, "Enable MySQL", "If false, SQLite will be used instead"),
|
||||
MYSQL_HOSTNAME("mysql-settings.hostname", "", "MySQL Database Hostname"),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package dev.esophose.playerparticles.manager;
|
||||
|
||||
import dev.esophose.playerparticles.PlayerParticles;
|
||||
import dev.esophose.playerparticles.hook.WorldGuardHook;
|
||||
import dev.esophose.playerparticles.manager.ConfigurationManager.Setting;
|
||||
import dev.esophose.playerparticles.particles.ConsolePPlayer;
|
||||
import dev.esophose.playerparticles.particles.FixedParticleEffect;
|
||||
|
@ -43,6 +44,11 @@ public class ParticleManager extends Manager implements Listener, Runnable {
|
|||
*/
|
||||
private BukkitTask particleTask;
|
||||
|
||||
/**
|
||||
* The task that checks player worldguard region statuses
|
||||
*/
|
||||
private BukkitTask worldGuardTask;
|
||||
|
||||
/**
|
||||
* Rainbow particle effect hue and note color used for rainbow colorable effects
|
||||
*/
|
||||
|
@ -67,10 +73,20 @@ public class ParticleManager extends Manager implements Listener, Runnable {
|
|||
if (this.particleTask != null)
|
||||
this.particleTask.cancel();
|
||||
|
||||
if (this.worldGuardTask != null) {
|
||||
this.worldGuardTask.cancel();
|
||||
this.worldGuardTask = null;
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(this.playerParticles, () -> {
|
||||
long ticks = Setting.TICKS_PER_PARTICLE.getLong();
|
||||
this.particleTask = Bukkit.getScheduler().runTaskTimerAsynchronously(this.playerParticles, this, 5, ticks);
|
||||
}, 1);
|
||||
this.particleTask = Bukkit.getScheduler().runTaskTimerAsynchronously(this.playerParticles, this, 0, ticks);
|
||||
|
||||
if (WorldGuardHook.enabled()) {
|
||||
long worldGuardTicks = Setting.WORLDGUARD_CHECK_INTERVAL.getLong();
|
||||
this.worldGuardTask = Bukkit.getScheduler().runTaskTimer(this.playerParticles, this::updateWorlGuardStatuses, 0, worldGuardTicks);
|
||||
}
|
||||
}, 5);
|
||||
|
||||
this.particlePlayers.clear();
|
||||
DataManager dataManager = this.playerParticles.getManager(DataManager.class);
|
||||
|
@ -164,6 +180,25 @@ public class ParticleManager extends Manager implements Listener, Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the WorldGuard region statuses for players
|
||||
*/
|
||||
private void updateWorlGuardStatuses() {
|
||||
PermissionManager permissionManager = this.playerParticles.getManager(PermissionManager.class);
|
||||
|
||||
for (PPlayer pplayer : this.particlePlayers.values()) {
|
||||
Player player = pplayer.getPlayer();
|
||||
if (player == null)
|
||||
continue;
|
||||
|
||||
boolean inAllowedRegion = WorldGuardHook.isInAllowedRegion(player.getLocation());
|
||||
if (!inAllowedRegion && Setting.WORLDGUARD_ENABLE_BYPASS_PERMISSION.getBoolean())
|
||||
inAllowedRegion = permissionManager.hasWorldGuardBypass(player);
|
||||
|
||||
pplayer.setInAllowedRegion(inAllowedRegion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays particles at the given player location with their settings
|
||||
*
|
||||
|
@ -176,6 +211,9 @@ public class ParticleManager extends Manager implements Listener, Runnable {
|
|||
if (Setting.TOGGLE_ON_COMBAT.getBoolean() && particle.getStyle().canToggleWithCombat() && pplayer.isInCombat())
|
||||
return;
|
||||
|
||||
if (!pplayer.isInAllowedRegion())
|
||||
return;
|
||||
|
||||
if (particle.getStyle().canToggleWithMovement() && pplayer.isMoving()) {
|
||||
switch (Setting.TOGGLE_ON_MOVE.getString().toUpperCase()) {
|
||||
case "DISPLAY_FEET":
|
||||
|
@ -207,14 +245,22 @@ public class ParticleManager extends Manager implements Listener, Runnable {
|
|||
/**
|
||||
* An alternative method used for event styles
|
||||
*
|
||||
* @param player The player the particles are spawning from, nullable for special cases
|
||||
* @param pplayer The PPlayer the particles are spawning from, nullable for special cases
|
||||
* @param world The world the particles are spawning in
|
||||
* @param particle The ParticlePair to use for getting particle settings
|
||||
* @param particles The particles to display
|
||||
* @param isLongRange If the particle can be viewed from long range
|
||||
*/
|
||||
public void displayParticles(Player player, World world, ParticlePair particle, List<PParticle> particles, boolean isLongRange) {
|
||||
public void displayParticles(PPlayer pplayer, World world, ParticlePair particle, List<PParticle> particles, boolean isLongRange) {
|
||||
PermissionManager permissionManager = this.playerParticles.getManager(PermissionManager.class);
|
||||
|
||||
Player player = null;
|
||||
if (pplayer != null) {
|
||||
if (!pplayer.isInAllowedRegion())
|
||||
return;
|
||||
player = pplayer.getPlayer();
|
||||
}
|
||||
|
||||
if ((player != null && (NMSUtil.getVersionNumber() < 8 || player.getGameMode() == GameMode.SPECTATOR)) || !permissionManager.isWorldEnabled(world.getName()))
|
||||
return;
|
||||
|
||||
|
|
|
@ -45,7 +45,9 @@ public class PermissionManager extends Manager {
|
|||
PARTICLES_UNLIMITED("particles.unlimited"),
|
||||
|
||||
GROUPS_MAX("groups.max"),
|
||||
GROUPS_UNLIMITED("groups.unlimited");
|
||||
GROUPS_UNLIMITED("groups.unlimited"),
|
||||
|
||||
WORLDGUARD_BYPASS("worldguard.bypass");
|
||||
|
||||
private final String permissionString;
|
||||
|
||||
|
@ -139,6 +141,8 @@ public class PermissionManager extends Manager {
|
|||
pluginManager.addPermission(new Permission("playerparticles.groups.max"));
|
||||
pluginManager.addPermission(new Permission("playerparticles.groups.unlimited"));
|
||||
|
||||
pluginManager.addPermission(new Permission("playerparticles.worldguard.bypass"));
|
||||
|
||||
// Register all non-child permissions
|
||||
Map<String, Boolean> childPermissions = new HashMap<>();
|
||||
for (Permission permission : allPermissions) {
|
||||
|
@ -426,7 +430,7 @@ public class PermissionManager extends Manager {
|
|||
* Checks if a player can use /ppo
|
||||
*
|
||||
* @param sender The CommandSender to check
|
||||
* @return If the player can use /ppo
|
||||
* @return If the sender can use /ppo
|
||||
*/
|
||||
public boolean canOverride(CommandSender sender) {
|
||||
if (this.isConsole(sender))
|
||||
|
@ -435,6 +439,16 @@ public class PermissionManager extends Manager {
|
|||
return PPermission.OVERRIDE.check(sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a player has the WorldGuard bypass permission
|
||||
*
|
||||
* @param player The Player to check
|
||||
* @return If the player has the WorldGuard bypass permission
|
||||
*/
|
||||
public boolean hasWorldGuardBypass(Player player) {
|
||||
return PPermission.WORLDGUARD_BYPASS.check(player);
|
||||
}
|
||||
|
||||
private boolean isConsole(PPlayer pplayer) {
|
||||
return this.isConsole(pplayer.getUnderlyingExecutor());
|
||||
}
|
||||
|
|
|
@ -48,6 +48,11 @@ public class PPlayer {
|
|||
*/
|
||||
private boolean inCombat;
|
||||
|
||||
/**
|
||||
* If the player is in an allowed region
|
||||
*/
|
||||
private boolean inAllowedRegion;
|
||||
|
||||
/**
|
||||
* Constructs a new PPlayer
|
||||
*
|
||||
|
@ -64,6 +69,7 @@ public class PPlayer {
|
|||
this.particlesHidden = particlesHidden;
|
||||
this.isMoving = false;
|
||||
this.inCombat = false;
|
||||
this.inAllowedRegion = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,16 +136,14 @@ public class PPlayer {
|
|||
/**
|
||||
* Sets the player's movement state
|
||||
*
|
||||
* @param isMoving True if the player is moving, otherwise false if they are standing still
|
||||
* @param isMoving true if the player is moving, otherwise false if they are standing still
|
||||
*/
|
||||
public void setMoving(boolean isMoving) {
|
||||
this.isMoving = isMoving;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if a player is moving
|
||||
*
|
||||
* @return True if the player is moving
|
||||
* @return true if the player is moving, otherwise false
|
||||
*/
|
||||
public boolean isMoving() {
|
||||
return this.isMoving;
|
||||
|
@ -148,21 +152,35 @@ public class PPlayer {
|
|||
/**
|
||||
* Sets the player's combat state
|
||||
*
|
||||
* @param inCombat True if the player is in combat, otherwise false
|
||||
* @param inCombat true if the player is in combat, otherwise false
|
||||
*/
|
||||
public void setInCombat(boolean inCombat) {
|
||||
this.inCombat = inCombat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if a player is in combat
|
||||
*
|
||||
* @return True if the player is in combat
|
||||
* @return true if the player is in combat, otherwise false
|
||||
*/
|
||||
public boolean isInCombat() {
|
||||
return this.inCombat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the player's region state
|
||||
*
|
||||
* @param inAllowedRegion true if the player is in an allowed region, otherwise false
|
||||
*/
|
||||
public void setInAllowedRegion(boolean inAllowedRegion) {
|
||||
this.inAllowedRegion = inAllowedRegion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the player is in an allowed region, otherwise false
|
||||
*/
|
||||
public boolean isInAllowedRegion() {
|
||||
return this.inAllowedRegion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a ParticleGroup this player has by its name
|
||||
*
|
||||
|
|
|
@ -74,7 +74,7 @@ public class ParticleStyleBlockBreak extends DefaultParticleStyle implements Lis
|
|||
|
||||
for (ParticlePair particle : pplayer.getActiveParticlesForStyle(DefaultStyles.BLOCKBREAK)) {
|
||||
Location loc = event.getBlock().getLocation().clone();
|
||||
particleManager.displayParticles(player, player.getWorld(), particle, DefaultStyles.BLOCKBREAK.getParticles(particle, loc), false);
|
||||
particleManager.displayParticles(pplayer, player.getWorld(), particle, DefaultStyles.BLOCKBREAK.getParticles(particle, loc), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ public class ParticleStyleBlockPlace extends DefaultParticleStyle implements Lis
|
|||
|
||||
for (ParticlePair particle : pplayer.getActiveParticlesForStyle(DefaultStyles.BLOCKPLACE)) {
|
||||
Location loc = event.getBlock().getLocation().clone();
|
||||
particleManager.displayParticles(player, player.getWorld(), particle, DefaultStyles.BLOCKPLACE.getParticles(particle, loc), false);
|
||||
particleManager.displayParticles(pplayer, player.getWorld(), particle, DefaultStyles.BLOCKPLACE.getParticles(particle, loc), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@ public class ParticleStyleCelebration extends DefaultParticleStyle {
|
|||
trail.setEffect(ParticleStyleCelebration.this.fuseEffect);
|
||||
trail.setStyle(DefaultStyles.CELEBRATION);
|
||||
|
||||
particleManager.displayParticles(player, this.location.getWorld(), trail, Collections.singletonList(new PParticle(this.location)), true);
|
||||
particleManager.displayParticles(pplayer, this.location.getWorld(), trail, Collections.singletonList(new PParticle(this.location)), true);
|
||||
|
||||
this.location.add(0, ParticleStyleCelebration.this.fuseSpacing, 0);
|
||||
} else {
|
||||
|
@ -146,7 +146,7 @@ public class ParticleStyleCelebration extends DefaultParticleStyle {
|
|||
|
||||
particles.add(new PParticle(this.location.clone().add(dx, dy, dz)));
|
||||
}
|
||||
particleManager.displayParticles(player, this.location.getWorld(), particle, particles, true);
|
||||
particleManager.displayParticles(pplayer, this.location.getWorld(), particle, particles, true);
|
||||
|
||||
this.cancel();
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public class ParticleStyleHurt extends DefaultParticleStyle implements Listener
|
|||
if (pplayer != null) {
|
||||
for (ParticlePair particle : pplayer.getActiveParticlesForStyle(DefaultStyles.HURT)) {
|
||||
Location loc = player.getLocation().clone().add(0, 1, 0);
|
||||
particleManager.displayParticles(player, player.getWorld(), particle, DefaultStyles.HURT.getParticles(particle, loc), false);
|
||||
particleManager.displayParticles(pplayer, player.getWorld(), particle, DefaultStyles.HURT.getParticles(particle, loc), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ public class ParticleStyleMove extends DefaultParticleStyle implements Listener
|
|||
for (ParticlePair particle : pplayer.getActiveParticlesForStyle(DefaultStyles.MOVE)) {
|
||||
Location loc = player.getLocation().clone();
|
||||
loc.setY(loc.getY() + 0.05);
|
||||
particleManager.displayParticles(player, player.getWorld(), particle, DefaultStyles.MOVE.getParticles(particle, loc), false);
|
||||
particleManager.displayParticles(pplayer, player.getWorld(), particle, DefaultStyles.MOVE.getParticles(particle, loc), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ public class ParticleStyleSwords extends DefaultParticleStyle implements Listene
|
|||
|
||||
for (ParticlePair particle : pplayer.getActiveParticlesForStyle(DefaultStyles.SWORDS)) {
|
||||
Location loc = entity.getLocation().clone().add(0, 1, 0);
|
||||
particleManager.displayParticles(player, player.getWorld(), particle, DefaultStyles.SWORDS.getParticles(particle, loc), false);
|
||||
particleManager.displayParticles(pplayer, player.getWorld(), particle, DefaultStyles.SWORDS.getParticles(particle, loc), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,14 +87,14 @@ public class ParticleStyleTeleport extends DefaultParticleStyle implements Liste
|
|||
if (this.before) {
|
||||
Location loc1 = player.getLocation().clone();
|
||||
loc1.setY(loc1.getY() + 1);
|
||||
particleManager.displayParticles(player, player.getWorld(), particle, DefaultStyles.TELEPORT.getParticles(particle, loc1), false);
|
||||
particleManager.displayParticles(pplayer, player.getWorld(), particle, DefaultStyles.TELEPORT.getParticles(particle, loc1), false);
|
||||
}
|
||||
|
||||
if (this.after) {
|
||||
Bukkit.getScheduler().runTaskLater(PlayerParticles.getInstance(), () -> {
|
||||
Location loc2 = player.getLocation().clone();
|
||||
loc2.setY(loc2.getY() + 1);
|
||||
particleManager.displayParticles(player, player.getWorld(), particle, DefaultStyles.TELEPORT.getParticles(particle, loc2), false);
|
||||
particleManager.displayParticles(pplayer, player.getWorld(), particle, DefaultStyles.TELEPORT.getParticles(particle, loc2), false);
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ public class ParticleStyleTrail extends DefaultParticleStyle implements Listener
|
|||
for (ParticlePair particle : pplayer.getActiveParticlesForStyle(DefaultStyles.TRAIL)) {
|
||||
Location loc = player.getLocation().clone();
|
||||
loc.setY(loc.getY() + 1);
|
||||
particleManager.displayParticles(player, player.getWorld(), particle, DefaultStyles.TRAIL.getParticles(particle, loc), false);
|
||||
particleManager.displayParticles(pplayer, player.getWorld(), particle, DefaultStyles.TRAIL.getParticles(particle, loc), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ api-version: '1.13'
|
|||
description: Display particles around your player and blocks using customized styles and data!
|
||||
author: Esophose
|
||||
website: https://www.spigotmc.org/resources/playerparticles.40261/
|
||||
softdepend: [PlaceholderAPI]
|
||||
softdepend: [PlaceholderAPI, WorldGuard, WorldEdit]
|
||||
commands:
|
||||
pp:
|
||||
description: The main PlayerParticles command. By default, opens the GUI.
|
||||
|
|
Loading…
Reference in a new issue