Compare commits
No commits in common. "master" and "particle_storage_rework" have entirely different histories.
master
...
particle_s
20
Jenkinsfile
vendored
20
Jenkinsfile
vendored
|
|
@ -1,20 +0,0 @@
|
||||||
pipeline {
|
|
||||||
agent any
|
|
||||||
stages {
|
|
||||||
stage('Gradle') {
|
|
||||||
steps {
|
|
||||||
sh 'bash gradlew shadowJar'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Archive') {
|
|
||||||
steps {
|
|
||||||
archiveArtifacts artifacts: 'build/libs/', followSymlinks: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('discord') {
|
|
||||||
steps {
|
|
||||||
discordSend description: "Build: ${BUILD_NUMBER}", showChangeset: true, result: currentBuild.currentResult, title: currentBuild.fullProjectName, webhookURL: env.discordwebhook
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -25,8 +25,7 @@ tasks {
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
// archiveFileName.set("${project.name}-${project.version}.jar")
|
archiveFileName.set("${project.name}-${project.version}.jar")
|
||||||
archiveFileName.set("${project.name}.jar")
|
|
||||||
minimize()
|
minimize()
|
||||||
configurations = listOf(project.configurations.shadow.get())
|
configurations = listOf(project.configurations.shadow.get())
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +37,7 @@ tasks {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Cosmos
|
// Cosmos
|
||||||
compileOnly("com.alttd.cosmos:cosmos-api:1.21.10-R0.1-SNAPSHOT") {
|
compileOnly("com.alttd.cosmos:cosmos-api:1.21.6-R0.1-SNAPSHOT") {
|
||||||
isChanging = true
|
isChanging = true
|
||||||
}
|
}
|
||||||
// Lombok
|
// Lombok
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,9 @@
|
||||||
import org.gradle.kotlin.dsl.maven
|
|
||||||
|
|
||||||
val nexusUser = providers.gradleProperty("alttdSnapshotUsername").orNull ?: System.getenv("NEXUS_USERNAME")
|
|
||||||
val nexusPass = providers.gradleProperty("alttdSnapshotPassword").orNull ?: System.getenv("NEXUS_PASSWORD")
|
|
||||||
|
|
||||||
rootProject.name = "AltitudeParticles"
|
rootProject.name = "AltitudeParticles"
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven {
|
|
||||||
url = uri("https://repo.alttd.com/repository/alttd-snapshot/")
|
|
||||||
credentials {
|
|
||||||
username = nexusUser
|
|
||||||
password = nexusPass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
maven("https://repo.destro.xyz/snapshots") // Galaxy
|
maven("https://repo.destro.xyz/snapshots") // Galaxy
|
||||||
maven("https://papermc.io/repo/repository/maven-public/") // Paper
|
maven("https://papermc.io/repo/repository/maven-public/") // Paper
|
||||||
maven("https://jitpack.io") //PremiumVanish
|
maven("https://jitpack.io") //PremiumVanish
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,16 @@ import com.alttd.config.ParticleConfig;
|
||||||
import com.alttd.database.Database;
|
import com.alttd.database.Database;
|
||||||
import com.alttd.listeners.*;
|
import com.alttd.listeners.*;
|
||||||
import com.alttd.objects.APartType;
|
import com.alttd.objects.APartType;
|
||||||
import com.alttd.storage.AutoReload;
|
|
||||||
import com.alttd.util.Logger;
|
import com.alttd.util.Logger;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
public class AltitudeParticles extends JavaPlugin {
|
public class AltitudeParticles extends JavaPlugin {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public static AltitudeParticles instance;
|
public static AltitudeParticles instance;
|
||||||
|
|
||||||
private static AutoReload autoReload = null;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoad() {
|
public void onLoad() {
|
||||||
instance = this;
|
instance = this;
|
||||||
|
|
@ -53,30 +46,9 @@ public class AltitudeParticles extends JavaPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reload() {
|
public void reload() {
|
||||||
Logger.info("Reloading AltitudeParticles...");
|
|
||||||
Config.reload();
|
Config.reload();
|
||||||
DatabaseConfig.reload();
|
DatabaseConfig.reload();
|
||||||
ParticleConfig.reload();
|
ParticleConfig.reload();
|
||||||
startAutoReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void startAutoReload() {
|
|
||||||
Path path = Path.of(Config.AUTO_RELOAD_PATH);
|
|
||||||
File file = path.toFile();
|
|
||||||
if (file.exists() && file.isDirectory()) {
|
|
||||||
try {
|
|
||||||
if (autoReload != null) {
|
|
||||||
autoReload.stop();
|
|
||||||
}
|
|
||||||
autoReload = new AutoReload(path);
|
|
||||||
autoReload.startWatching();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Logger.severe("Failed to start AutoReload at path %", Config.AUTO_RELOAD_PATH);
|
|
||||||
Logger.error("Failed to start AutoReload", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logger.severe("Failed to start AutoReload at path %", Config.AUTO_RELOAD_PATH);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,4 @@ public final class Config extends AbstractConfig {
|
||||||
CLICK_BLOCK_COOL_DOWN = config.getInt("cool_down.click-block", CLICK_BLOCK_COOL_DOWN);
|
CLICK_BLOCK_COOL_DOWN = config.getInt("cool_down.click-block", CLICK_BLOCK_COOL_DOWN);
|
||||||
TELEPORT_ARRIVE_COOL_DOWN = config.getInt("cool_down.teleport-arrive", TELEPORT_ARRIVE_COOL_DOWN);
|
TELEPORT_ARRIVE_COOL_DOWN = config.getInt("cool_down.teleport-arrive", TELEPORT_ARRIVE_COOL_DOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String AUTO_RELOAD_PATH = "/mnt/configs/AltitudeParticles/particles";
|
|
||||||
private static void loadAutoReload() {
|
|
||||||
AUTO_RELOAD_PATH = config.getString("auto-reload.path", AUTO_RELOAD_PATH);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,34 +8,26 @@ import com.alttd.objects.ParticleSet;
|
||||||
import com.alttd.storage.ParticleStorage;
|
import com.alttd.storage.ParticleStorage;
|
||||||
import com.alttd.util.Logger;
|
import com.alttd.util.Logger;
|
||||||
import com.destroystokyo.paper.ParticleBuilder;
|
import com.destroystokyo.paper.ParticleBuilder;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.bukkit.Color;
|
import org.bukkit.Color;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.Particle;
|
import org.bukkit.Particle;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.*;
|
import java.util.ArrayList;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.util.HexFormat;
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class ParticleConfig {
|
public class ParticleConfig {
|
||||||
|
|
||||||
private static final int MAX_DEPTH = 2;
|
|
||||||
private static final File particlesDir = new File(File.separator + "mnt" + File.separator + "configs"
|
private static final File particlesDir = new File(File.separator + "mnt" + File.separator + "configs"
|
||||||
+ File.separator + "AltitudeParticles" + File.separator + "particles");
|
+ File.separator + "AltitudeParticles" + File.separator + "particles");
|
||||||
private static ParticleConfig instance = null;
|
private static ParticleConfig instance = null;
|
||||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
static {
|
|
||||||
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
|
||||||
objectMapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ParticleConfig getInstance() {
|
private static ParticleConfig getInstance() {
|
||||||
if (instance == null)
|
if (instance == null)
|
||||||
|
|
@ -45,112 +37,32 @@ public class ParticleConfig {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds all files in particles directory that are valid .json files
|
* Finds all files in particles directory that are valid .json files
|
||||||
* Only searches one level deep into subdirectories
|
|
||||||
*
|
|
||||||
* @return all files found
|
* @return all files found
|
||||||
*/
|
*/
|
||||||
private List<File> getJsonFiles() {
|
private List<File> getJsonFiles() {
|
||||||
List<File> files = new ArrayList<>();
|
List<File> files = new ArrayList<>();
|
||||||
|
|
||||||
if (!ensureParticlesDirectoryExists()) {
|
|
||||||
log.debug("Particles directory missing or not creatable: {}", particlesDir.getAbsolutePath());
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Traversing particles directory: {} (exists={}, isDirectory={})", particlesDir.getAbsolutePath(),
|
|
||||||
particlesDir.exists(), particlesDir.isDirectory());
|
|
||||||
|
|
||||||
try {
|
|
||||||
Files.walkFileTree(particlesDir.toPath(),
|
|
||||||
EnumSet.of(FileVisitOption.FOLLOW_LINKS),
|
|
||||||
ParticleConfig.MAX_DEPTH,
|
|
||||||
getJsonFileVistor(files));
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Error while traversing directory: {}", e.getMessage(), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Found {} json files in particles directory", files.size());
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FileVisitor<? super @NotNull Path> getJsonFileVistor(List<File> files) {
|
|
||||||
return new SimpleFileVisitor<>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull FileVisitResult preVisitDirectory(@NotNull Path dir, @NotNull BasicFileAttributes attrs) {
|
|
||||||
log.debug("preVisitDirectory: {}", dir.toAbsolutePath());
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) {
|
|
||||||
if (!attrs.isRegularFile()) {
|
|
||||||
log.debug("Skipping non-regular file path: {} (isDirectory={}, isOther={})", file.toAbsolutePath(), attrs.isDirectory(), attrs.isOther());
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
File physicalFile = file.toFile();
|
|
||||||
log.debug("visitFile: {} (isFile={}, canRead={}, name={})", physicalFile.getAbsolutePath(), physicalFile.isFile(), physicalFile.canRead(), physicalFile.getName());
|
|
||||||
if (isValidJsonFile(physicalFile)) {
|
|
||||||
log.debug("Found JSON file: {}", physicalFile.getAbsolutePath());
|
|
||||||
files.add(physicalFile);
|
|
||||||
} else {
|
|
||||||
log.debug("Ignoring non-json or unreadable file: {}", physicalFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull FileVisitResult postVisitDirectory(@NotNull Path dir, IOException exc) {
|
|
||||||
log.debug("postVisitDirectory: {}", dir.toAbsolutePath());
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that the particles directory exists and is a directory
|
|
||||||
*
|
|
||||||
* @return true if directory exists or was created successfully, false otherwise
|
|
||||||
*/
|
|
||||||
private boolean ensureParticlesDirectoryExists() {
|
|
||||||
if (!particlesDir.exists()) {
|
if (!particlesDir.exists()) {
|
||||||
log.info("Particles directory does not exist, attempting to create: {}", particlesDir.getAbsolutePath());
|
if (!particlesDir.mkdir())
|
||||||
if (!particlesDir.mkdirs()) {
|
Logger.warning("Unable to create particles directory");
|
||||||
Logger.warning("Unable to create particles directory at {}", particlesDir.getAbsolutePath());
|
return files;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
log.info("Created particles directory at {}", particlesDir.getAbsolutePath());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!particlesDir.isDirectory()) {
|
if (!particlesDir.isDirectory()) {
|
||||||
Logger.warning("Particles path exists but is not a directory: {}", particlesDir.getAbsolutePath());
|
Logger.warning("Particles directory doesn't exist (it's a file??)");
|
||||||
return false;
|
return files;
|
||||||
}
|
}
|
||||||
|
File[] validFiles = particlesDir.listFiles(file -> file.isFile() && file.canRead() && file.getName().endsWith(".json"));
|
||||||
return true;
|
if (validFiles == null)
|
||||||
}
|
return files;
|
||||||
|
files.addAll(List.of(validFiles));
|
||||||
/**
|
return files;
|
||||||
* Checks if a file is a valid JSON file
|
|
||||||
*
|
|
||||||
* @param file the file to check
|
|
||||||
* @return true if the file is a valid JSON file
|
|
||||||
*/
|
|
||||||
private boolean isValidJsonFile(File file) {
|
|
||||||
return file.isFile() && file.canRead() && file.getName().endsWith(".json");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a ParticleData object to a ParticleSet
|
* Converts a ParticleData object to a ParticleSet
|
||||||
*
|
|
||||||
* @param particleData The ParticleData object to convert
|
* @param particleData The ParticleData object to convert
|
||||||
* @return A ParticleSet created from the ParticleData
|
* @return A ParticleSet created from the ParticleData
|
||||||
*/
|
*/
|
||||||
public ParticleSet convertToParticleSet(ParticleData particleData) {
|
public ParticleSet convertToParticleSet(ParticleData particleData) {
|
||||||
log.info("Converting ParticleData to ParticleSet for {}", particleData.getParticleName());
|
|
||||||
List<Frame> loadedFrames = new ArrayList<>();
|
List<Frame> loadedFrames = new ArrayList<>();
|
||||||
double randomOffset = particleData.getRandomOffset();
|
double randomOffset = particleData.getRandomOffset();
|
||||||
|
|
||||||
|
|
@ -166,13 +78,30 @@ public class ParticleConfig {
|
||||||
double z = particleInfo.getZ();
|
double z = particleInfo.getZ();
|
||||||
|
|
||||||
ParticleBuilder particleBuilder = new ParticleBuilder(particleType);
|
ParticleBuilder particleBuilder = new ParticleBuilder(particleType);
|
||||||
Class<?> dataType = particleType.getDataType();
|
|
||||||
|
|
||||||
// Handle different particle data types
|
// Handle different particle data types
|
||||||
setParticleType(particleInfo, dataType, particleBuilder);
|
if (particleType.getDataType().equals(Particle.DustOptions.class) && particleInfo.getColor() != null) {
|
||||||
|
int rgb = HexFormat.fromHexDigits(particleInfo.getColor());
|
||||||
|
particleBuilder.data(new Particle.DustOptions(Color.fromRGB(rgb), 1));
|
||||||
|
}
|
||||||
|
// else if (particleType.getDataType().equals(MaterialData.class)) {
|
||||||
|
// //TODO implement
|
||||||
|
// }
|
||||||
|
else if (particleType.getDataType().equals(BlockData.class)) {
|
||||||
|
//TODO implement
|
||||||
|
} else if (particleType.getDataType().equals(Integer.class)) {
|
||||||
|
//TODO implement
|
||||||
|
} else if (particleType.getDataType().equals(Float.class)) {
|
||||||
|
//TODO implement
|
||||||
|
} else if (particleType.getDataType().equals(Particle.DustTransition.class)) {
|
||||||
|
//TODO implement
|
||||||
|
} else if (particleType.getDataType().equals(ItemStack.class)) {
|
||||||
|
//TODO implement
|
||||||
|
} else if (particleInfo.getExtra() != null) {
|
||||||
|
particleBuilder.extra(particleInfo.getExtra());
|
||||||
|
}
|
||||||
|
|
||||||
//Add 0.2 to adjust for the player model being 1.6 blocks high
|
aParticleList.add(new AParticle(x, y, z, randomOffset, particleBuilder));
|
||||||
aParticleList.add(new AParticle(x, y + 0.2, z, randomOffset, particleBuilder));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadedFrames.add(new Frame(aParticleList));
|
loadedFrames.add(new Frame(aParticleList));
|
||||||
|
|
@ -196,72 +125,11 @@ public class ParticleConfig {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setParticleType(ParticleInfo particleInfo, Class<?> dataType, ParticleBuilder particleBuilder) {
|
|
||||||
String color = particleInfo.getColor();
|
|
||||||
if (dataType.equals(Particle.DustOptions.class)) {
|
|
||||||
if (color != null) {
|
|
||||||
particleBuilder.color(getColor(color),
|
|
||||||
particleInfo.getSize());
|
|
||||||
} else {
|
|
||||||
log.error("Dust particle must have a color");
|
|
||||||
}
|
|
||||||
} else if (dataType.equals(Particle.DustTransition.class)) {
|
|
||||||
if (color == null || particleInfo.getColorGradientEnd() != null) {
|
|
||||||
particleBuilder.colorTransition(getColor(color),
|
|
||||||
getColor(particleInfo.getColorGradientEnd()),
|
|
||||||
particleInfo.getSize());
|
|
||||||
} else {
|
|
||||||
log.error("Dust transition particle must have a color gradient start and end");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (dataType.equals(Color.class)) {
|
|
||||||
particleBuilder.color(getColor(color));
|
|
||||||
} else if (dataType.equals(BlockData.class)) {
|
|
||||||
particleBuilder.data(Material.STONE.createBlockData());
|
|
||||||
log.warn("Block data particles are not yet supported");
|
|
||||||
//TODO implement
|
|
||||||
} else if (dataType.equals(Integer.class)) {
|
|
||||||
particleBuilder.data(1);
|
|
||||||
log.warn("Integer data particles are not yet supported");
|
|
||||||
//TODO implement
|
|
||||||
} else if (dataType.equals(Float.class)) {
|
|
||||||
particleBuilder.data(1f);
|
|
||||||
log.warn("Float data particles are not yet supported");
|
|
||||||
//TODO implement
|
|
||||||
} else if (dataType.equals(ItemStack.class)) {
|
|
||||||
particleBuilder.data(new ItemStack(Material.STONE));
|
|
||||||
log.warn("ItemStack data particles are not yet supported");
|
|
||||||
//TODO implement
|
|
||||||
} else if (particleInfo.getExtra() != null) {
|
|
||||||
particleBuilder.extra(particleInfo.getExtra());
|
|
||||||
} else {
|
|
||||||
log.debug("No relevant data type: {}", dataType.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Color getColor(String hexColor) {
|
|
||||||
int hexFormatColor = HexFormat.fromHexDigits(hexColor);
|
|
||||||
Color color;
|
|
||||||
if (hexColor.length() == 6) {
|
|
||||||
color = Color.fromRGB(hexFormatColor);
|
|
||||||
} else {
|
|
||||||
color = Color.fromARGB(hexFormatColor);
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reload() {
|
public static void reload() {
|
||||||
ParticleStorage.clear();
|
ParticleStorage.clear();
|
||||||
instance = getInstance();
|
ParticleConfig instance = getInstance();
|
||||||
log.info("Reloading particles...");
|
|
||||||
|
|
||||||
for (File file : instance.getJsonFiles()) {
|
for (File file : instance.getJsonFiles()) {
|
||||||
loadParticleFromFile(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void loadParticleFromFile(File file) {
|
|
||||||
instance = getInstance();
|
|
||||||
try {
|
try {
|
||||||
ParticleData particleData = objectMapper.readValue(file, ParticleData.class);
|
ParticleData particleData = objectMapper.readValue(file, ParticleData.class);
|
||||||
|
|
||||||
|
|
@ -274,4 +142,5 @@ public class ParticleConfig {
|
||||||
Logger.error("Error processing particle file " + file.getName(), exception);
|
Logger.error("Error processing particle file " + file.getName(), exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.alttd.frame_spawners;
|
package com.alttd.frameSpawners;
|
||||||
|
|
||||||
import com.alttd.AltitudeParticles;
|
import com.alttd.AltitudeParticles;
|
||||||
import com.alttd.config.Config;
|
import com.alttd.config.Config;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.alttd.frame_spawners;
|
package com.alttd.frameSpawners;
|
||||||
|
|
||||||
import com.alttd.AltitudeParticles;
|
import com.alttd.AltitudeParticles;
|
||||||
import com.alttd.config.Config;
|
import com.alttd.config.Config;
|
||||||
|
|
@ -7,19 +7,13 @@ import com.alttd.objects.Frame;
|
||||||
import com.alttd.objects.ParticleSet;
|
import com.alttd.objects.ParticleSet;
|
||||||
import com.alttd.storage.PlayerSettings;
|
import com.alttd.storage.PlayerSettings;
|
||||||
import com.alttd.util.Logger;
|
import com.alttd.util.Logger;
|
||||||
import com.destroystokyo.paper.ParticleBuilder;
|
|
||||||
import de.myzelyam.api.vanish.VanishAPI;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.bukkit.GameMode;
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Particle;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class FrameSpawnerPlayer extends BukkitRunnable {
|
public class FrameSpawnerPlayer extends BukkitRunnable {
|
||||||
|
|
||||||
private int amount;
|
private int amount;
|
||||||
|
|
@ -48,11 +42,7 @@ public class FrameSpawnerPlayer extends BukkitRunnable {
|
||||||
if (!player.isOnline()) {
|
if (!player.isOnline()) {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
if (Config.DEBUG)
|
if (Config.DEBUG)
|
||||||
log.info("Stopped repeating task due to player offline.");
|
Logger.info("Stopped repeating task due to player offline.");
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isVanished(player)) {
|
|
||||||
log.debug("Player {} is vanished, skipping frame spawn.", player.getName());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Location location = player.getLocation();
|
Location location = player.getLocation();
|
||||||
|
|
@ -61,15 +51,14 @@ public class FrameSpawnerPlayer extends BukkitRunnable {
|
||||||
if (activeParticleSet == null || !activeParticleSet.getParticleId().equalsIgnoreCase(uniqueId) || !playerSettings.hasActiveParticles()) {
|
if (activeParticleSet == null || !activeParticleSet.getParticleId().equalsIgnoreCase(uniqueId) || !playerSettings.hasActiveParticles()) {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
if (Config.DEBUG)
|
if (Config.DEBUG)
|
||||||
log.info("Stopped repeating task due to player switching/disabling particles.");
|
Logger.info("Stopped repeating task due to player switching/disabling particles.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (amount == 0) {
|
if (amount == 0) {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
if (Config.DEBUG)
|
if (Config.DEBUG)
|
||||||
log.info("Stopped repeating task due to end of frames.");
|
Logger.info("Stopped repeating task due to end of frames.");
|
||||||
}
|
}
|
||||||
iterator = frames.iterator();
|
|
||||||
new BukkitRunnable() {
|
new BukkitRunnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
@ -84,11 +73,8 @@ public class FrameSpawnerPlayer extends BukkitRunnable {
|
||||||
next.spawn(player.getLocation(), player.getLocation().getYaw());
|
next.spawn(player.getLocation(), player.getLocation().getYaw());
|
||||||
}
|
}
|
||||||
}.runTaskTimerAsynchronously(AltitudeParticles.getInstance(), 0, frameDelay);
|
}.runTaskTimerAsynchronously(AltitudeParticles.getInstance(), 0, frameDelay);
|
||||||
|
iterator = frames.iterator();
|
||||||
if (amount != -1)
|
if (amount != -1)
|
||||||
amount--;
|
amount--;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isVanished(Player player) {
|
|
||||||
return VanishAPI.isInvisible(player) || player.getGameMode().equals(GameMode.SPECTATOR);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -36,14 +36,6 @@ import java.util.List;
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
public class ParticleData {
|
public class ParticleData {
|
||||||
// TODO add optional property for a list of users that can use the particle
|
|
||||||
// If that list is present the particle should be loaded as a dev particle
|
|
||||||
// Dev particles should disable all others while in use and all be grouped together
|
|
||||||
// (since the dev should know what each particle is and does)
|
|
||||||
// Seeing dev particles should require a permission
|
|
||||||
@JsonProperty("user_list")
|
|
||||||
private List<String> userList;
|
|
||||||
|
|
||||||
@JsonProperty("particle_name")
|
@JsonProperty("particle_name")
|
||||||
private String particleName;
|
private String particleName;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,6 @@ public class ParticleInfo {
|
||||||
|
|
||||||
// For DustOptions
|
// For DustOptions
|
||||||
private String color;
|
private String color;
|
||||||
@JsonProperty("color_gradient_end")
|
|
||||||
private String colorGradientEnd;
|
|
||||||
// For DustOptions
|
|
||||||
|
|
||||||
@JsonProperty(value = "size", defaultValue = "1")
|
|
||||||
private int size = 1;
|
|
||||||
|
|
||||||
// For other particle types
|
// For other particle types
|
||||||
private Double extra;
|
private Double extra;
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,16 @@ package com.alttd.objects;
|
||||||
import com.alttd.storage.PlayerSettings;
|
import com.alttd.storage.PlayerSettings;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class Frame {
|
public class Frame {
|
||||||
List<AParticle> aParticles;
|
List<AParticle> AParticles;
|
||||||
|
|
||||||
public Frame(List<AParticle> aParticles) {
|
public Frame(List<AParticle> AParticles) {
|
||||||
this.aParticles = aParticles;
|
this.AParticles = AParticles;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -24,7 +22,7 @@ public class Frame {
|
||||||
*/
|
*/
|
||||||
public void spawn(Location location, float rotation) {
|
public void spawn(Location location, float rotation) {
|
||||||
Location tmpLocation = location.clone();
|
Location tmpLocation = location.clone();
|
||||||
aParticles.forEach(aParticle -> {
|
AParticles.forEach(aParticle -> {
|
||||||
ThreadLocalRandom current = ThreadLocalRandom.current();
|
ThreadLocalRandom current = ThreadLocalRandom.current();
|
||||||
double offsetX = ((aParticle.offset_range() == 0) ? 0 : current.nextDouble(-aParticle.offset_range(), aParticle.offset_range()));
|
double offsetX = ((aParticle.offset_range() == 0) ? 0 : current.nextDouble(-aParticle.offset_range(), aParticle.offset_range()));
|
||||||
double offsetZ = ((aParticle.offset_range() == 0) ? 0 : current.nextDouble(-aParticle.offset_range(), aParticle.offset_range()));
|
double offsetZ = ((aParticle.offset_range() == 0) ? 0 : current.nextDouble(-aParticle.offset_range(), aParticle.offset_range()));
|
||||||
|
|
@ -32,20 +30,12 @@ public class Frame {
|
||||||
XZ xz = new XZ(location.getX(), location.getX() + aParticle.x() + offsetX,
|
XZ xz = new XZ(location.getX(), location.getX() + aParticle.x() + offsetX,
|
||||||
location.getZ(), location.getZ() + aParticle.z() + offsetZ,
|
location.getZ(), location.getZ() + aParticle.z() + offsetZ,
|
||||||
rotation);
|
rotation);
|
||||||
List<Player> receivers = getReceivers(location);
|
aParticle.particleBuilder()
|
||||||
tmpLocation.set(
|
.location(tmpLocation.set(
|
||||||
xz.getRotatedX(),
|
xz.getRotatedX(),
|
||||||
location.getY() + aParticle.y() + offsetY,
|
location.getY() + aParticle.y() + offsetY,
|
||||||
xz.getRotatedZ());
|
xz.getRotatedZ()))
|
||||||
aParticle.particleBuilder()
|
.receivers(Bukkit.getOnlinePlayers().stream()
|
||||||
.location(tmpLocation)
|
|
||||||
.receivers(receivers)
|
|
||||||
.spawn();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull List<Player> getReceivers(Location location) {
|
|
||||||
return Bukkit.getOnlinePlayers().stream()
|
|
||||||
.filter(player -> {
|
.filter(player -> {
|
||||||
PlayerSettings playerSettings = PlayerSettings.getPlayer(player.getUniqueId());
|
PlayerSettings playerSettings = PlayerSettings.getPlayer(player.getUniqueId());
|
||||||
if (playerSettings == null)
|
if (playerSettings == null)
|
||||||
|
|
@ -54,7 +44,9 @@ public class Frame {
|
||||||
return false;
|
return false;
|
||||||
Location playerLocation = player.getLocation();
|
Location playerLocation = player.getLocation();
|
||||||
return location.getWorld().getUID().equals(playerLocation.getWorld().getUID()) && player.getLocation().distance(location) < 100;
|
return location.getWorld().getUID().equals(playerLocation.getWorld().getUID()) && player.getLocation().distance(location) < 100;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList()))
|
||||||
|
.spawn();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class XZ {
|
private static class XZ {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ package com.alttd.objects;
|
||||||
|
|
||||||
import com.alttd.AltitudeParticles;
|
import com.alttd.AltitudeParticles;
|
||||||
import com.alttd.config.Config;
|
import com.alttd.config.Config;
|
||||||
import com.alttd.frame_spawners.FrameSpawnerLocation;
|
import com.alttd.frameSpawners.FrameSpawnerLocation;
|
||||||
import com.alttd.frame_spawners.FrameSpawnerPlayer;
|
import com.alttd.frameSpawners.FrameSpawnerPlayer;
|
||||||
import com.alttd.storage.PlayerSettings;
|
import com.alttd.storage.PlayerSettings;
|
||||||
import com.alttd.util.Logger;
|
import com.alttd.util.Logger;
|
||||||
import de.myzelyam.api.vanish.VanishAPI;
|
import de.myzelyam.api.vanish.VanishAPI;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import org.bukkit.GameMode;
|
import org.bukkit.GameMode;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
|
@ -20,7 +19,6 @@ import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class ParticleSet {
|
public class ParticleSet {
|
||||||
|
|
||||||
private final List<Frame> frames;
|
private final List<Frame> frames;
|
||||||
|
|
@ -55,7 +53,7 @@ public class ParticleSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run(Location location, Player player) {
|
public void run(Location location, Player player) {
|
||||||
if (tooSoon(player.getUniqueId()))
|
if (tooSoon(player.getUniqueId()) || isVanished(player))
|
||||||
return;
|
return;
|
||||||
FrameSpawnerLocation frameSpawnerLocation = new FrameSpawnerLocation(repeat, frames, frameDelay, location, player.getLocation().getYaw());
|
FrameSpawnerLocation frameSpawnerLocation = new FrameSpawnerLocation(repeat, frames, frameDelay, location, player.getLocation().getYaw());
|
||||||
frameSpawnerLocation.runTaskTimerAsynchronously(AltitudeParticles.getInstance(), 0, repeatDelay);
|
frameSpawnerLocation.runTaskTimerAsynchronously(AltitudeParticles.getInstance(), 0, repeatDelay);
|
||||||
|
|
@ -70,6 +68,10 @@ public class ParticleSet {
|
||||||
frameSpawnerPlayer.runTaskTimerAsynchronously(AltitudeParticles.getInstance(), 0, repeatDelay);
|
frameSpawnerPlayer.runTaskTimerAsynchronously(AltitudeParticles.getInstance(), 0, repeatDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isVanished(Player player) {
|
||||||
|
return VanishAPI.isInvisible(player) || player.getGameMode().equals(GameMode.SPECTATOR);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean tooSoon(UUID uuid) {
|
private boolean tooSoon(UUID uuid) {
|
||||||
PlayerSettings ps = PlayerSettings.getPlayer(uuid);
|
PlayerSettings ps = PlayerSettings.getPlayer(uuid);
|
||||||
if (ps.canRun(aPartType))
|
if (ps.canRun(aPartType))
|
||||||
|
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
package com.alttd.storage;
|
|
||||||
|
|
||||||
import com.alttd.config.ParticleConfig;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.*;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class AutoReload {
|
|
||||||
private final WatchService watchService;
|
|
||||||
private final Map<WatchKey, Path> keys;
|
|
||||||
private final Path rootDirectory;
|
|
||||||
private volatile boolean running = true;
|
|
||||||
|
|
||||||
public AutoReload(Path directory) throws IOException {
|
|
||||||
this.watchService = FileSystems.getDefault().newWatchService();
|
|
||||||
this.keys = new HashMap<>();
|
|
||||||
this.rootDirectory = directory;
|
|
||||||
register(directory);
|
|
||||||
registerAll(directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerAll(Path start) throws IOException {
|
|
||||||
Files.walkFileTree(start, new SimpleFileVisitor<>() {
|
|
||||||
@Override
|
|
||||||
public @NotNull FileVisitResult preVisitDirectory(@NotNull Path path, @NotNull BasicFileAttributes attrs) throws IOException {
|
|
||||||
if (path.toFile().isDirectory()) {
|
|
||||||
register(path);
|
|
||||||
}
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void register(@NotNull Path dir) throws IOException {
|
|
||||||
WatchKey key = dir.register(watchService,
|
|
||||||
StandardWatchEventKinds.ENTRY_CREATE,
|
|
||||||
StandardWatchEventKinds.ENTRY_DELETE,
|
|
||||||
StandardWatchEventKinds.ENTRY_MODIFY);
|
|
||||||
keys.put(key, dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startWatching() {
|
|
||||||
log.info("Starting watch thread.");
|
|
||||||
Thread watchThread = new Thread(() -> {
|
|
||||||
log.info("Watch thread started.");
|
|
||||||
while (running) {
|
|
||||||
log.info("Watch thread loop start");
|
|
||||||
WatchKey key;
|
|
||||||
try {
|
|
||||||
key = watchService.take();
|
|
||||||
log.info("Watch thread loop key {}", key.toString());
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
log.error("Interrupted while waiting for key", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!running) {
|
|
||||||
log.info("Exiting watch thread.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Path dir = keys.get(key);
|
|
||||||
if (dir == null) {
|
|
||||||
log.warn("Detected unknown key: {}. Ignoring.", key.toString());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
detectChanges(key, dir);
|
|
||||||
|
|
||||||
if (!key.reset()) {
|
|
||||||
keys.remove(key);
|
|
||||||
if (keys.isEmpty()) {
|
|
||||||
log.info("No longer watching any directories. Exiting.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
watchThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void detectChanges(@NotNull WatchKey key, Path dir) {
|
|
||||||
for (WatchEvent<?> event : key.pollEvents()) {
|
|
||||||
WatchEvent.Kind<?> kind = event.kind();
|
|
||||||
|
|
||||||
if (kind == StandardWatchEventKinds.OVERFLOW) {
|
|
||||||
log.warn("Detected overflow event. Ignoring.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Path child = resolveEventPath(event, dir);
|
|
||||||
boolean isDirectory = Files.isDirectory(child);
|
|
||||||
|
|
||||||
if (shouldIgnoreDirectoryEvent(isDirectory, dir)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kind == StandardWatchEventKinds.ENTRY_CREATE && isDirectory) {
|
|
||||||
handleNewDirectoryCreation(child);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDirectory) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFileEvent(kind, child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull Path resolveEventPath(@NotNull WatchEvent<?> event, Path dir) {
|
|
||||||
Object context = event.context();
|
|
||||||
if (!(context instanceof Path path)) {
|
|
||||||
throw new IllegalArgumentException("Expected event context to be a Path, but got: " + context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dir.resolve(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean shouldIgnoreDirectoryEvent(boolean isDirectory, Path dir) {
|
|
||||||
if (isDirectory && !dir.equals(rootDirectory)) {
|
|
||||||
log.warn("Detected directory {} outside of root directory. Ignoring.", dir);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleNewDirectoryCreation(Path child) {
|
|
||||||
try {
|
|
||||||
log.info("Registering new directory: {}", child);
|
|
||||||
registerAll(child);
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Failed to register directory: {}", child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleFileEvent(WatchEvent.Kind<?> kind, Path child) {
|
|
||||||
if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
|
|
||||||
log.debug("Detected file modification: {}", child);
|
|
||||||
reloadFile(child);
|
|
||||||
} else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
|
|
||||||
log.debug("Detected file deletion: {}", child);
|
|
||||||
handleFileDeletion();
|
|
||||||
} else if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
|
|
||||||
log.debug("Detected file creation: {}", child);
|
|
||||||
reloadFile(child);
|
|
||||||
} else {
|
|
||||||
log.warn("Unknown event kind: {}", kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reloadFile(Path child) {
|
|
||||||
ParticleConfig.loadParticleFromFile(child.toFile());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleFileDeletion() {
|
|
||||||
log.info("Detected file deletion. Reloading all particles.");
|
|
||||||
ParticleConfig.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,25 +2,18 @@ package com.alttd.storage;
|
||||||
|
|
||||||
import com.alttd.objects.APartType;
|
import com.alttd.objects.APartType;
|
||||||
import com.alttd.objects.ParticleSet;
|
import com.alttd.objects.ParticleSet;
|
||||||
import com.alttd.util.Logger;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class ParticleStorage {
|
public class ParticleStorage {
|
||||||
private static final HashMap<APartType, List<ParticleSet>> particles = new HashMap<>();
|
private static final HashMap<APartType, List<ParticleSet>> particles = new HashMap<>();
|
||||||
|
|
||||||
public static void addParticleSet(APartType aPartType, ParticleSet particleSet) {
|
public static void addParticleSet(APartType aPartType, ParticleSet particleSet) {
|
||||||
List<ParticleSet> particleSets = particles.getOrDefault(aPartType, new ArrayList<>());
|
List<ParticleSet> particleSets = particles.getOrDefault(aPartType, new ArrayList<>());
|
||||||
Optional<ParticleSet> existingParticleSet = particleSets.stream()
|
if (particleSets.contains(particleSet))
|
||||||
.filter(p -> p.getParticleId().equalsIgnoreCase(particleSet.getParticleId()))
|
return;
|
||||||
.findAny();
|
|
||||||
if (existingParticleSet.isPresent()) {
|
|
||||||
Logger.warning("Overwriting particle set %", particleSet.getParticleId());
|
|
||||||
particleSets.remove(existingParticleSet.get());
|
|
||||||
}
|
|
||||||
particleSets.add(particleSet);
|
particleSets.add(particleSet);
|
||||||
particles.put(aPartType, particleSets);
|
particles.put(aPartType, particleSets);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user