diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 85a0705..468033b 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -6,9 +6,11 @@ Use this section to tell people about which versions of your project are currently being supported with security updates. | Version | Supported | -| ------- | ------------------ | -| 1.0.x | ❌ | -| 2.0.x | :white_check_mark: | +|---------|--------------------| +| 1.0.x | ❌ | +| 2.0.x | ❌ | +| 3.0.x | ❌ | +| 4.0.x | :white_check_mark: | ## Reporting a Vulnerability diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 02dffda..6ef6214 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -21,10 +21,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: '17' + java-version: '21' distribution: 'temurin' - name: Setup Gradle @@ -37,4 +37,4 @@ jobs: run: ./gradlew build - name: Build the Jar - run: ./gradlew clean shadowJar + run: ./gradlew shadowJar \ No newline at end of file diff --git a/.gitignore b/.gitignore index f35ca9e..14d1f83 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +/doublejump-plugin/run/ ### IntelliJ IDEA ### .idea/ @@ -36,4 +37,4 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index a713207..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,46 +0,0 @@ -plugins { - id("java-library") - id("com.github.johnrengelman.shadow") version "8.1.1" - id("checkstyle") -} - -allprojects { - apply(plugin = "java-library") - apply(plugin = "com.github.johnrengelman.shadow") - apply(plugin = "checkstyle") - - repositories { - mavenCentral() - - maven("https://storehouse.okaeri.eu/repository/maven-public/") - } - - dependencies { - implementation("org.jetbrains:annotations:26.0.2") - - implementation("eu.okaeri:okaeri-configs-yaml-snakeyaml:5.0.5") - implementation("eu.okaeri:okaeri-configs-serdes-commons:5.0.5") - - testImplementation(platform("org.junit:junit-bom:5.12.1")) - testImplementation("org.junit.jupiter:junit-jupiter") - } - - java { - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) - } - - checkstyle { - toolVersion = "10.21.0" - configFile = file("${rootDir}/checkstyle.xml") - } - - tasks.test { - useJUnitPlatform() - } - - tasks.withType { - options.compilerArgs = listOf("-Xlint:deprecation", "-parameters") - options.encoding = "UTF-8" - options.release = 17 - } -} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..ff4aacd --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() +} + +dependencies { + implementation("com.gradleup.shadow:shadow-gradle-plugin:9.3.2") + implementation("net.minecrell:plugin-yml:0.6.0") +} + +sourceSets { + main { + java.setSrcDirs(emptyList()) + groovy.setSrcDirs(emptyList()) + resources.setSrcDirs(emptyList()) + } + test { + java.setSrcDirs(emptyList()) + kotlin.setSrcDirs(emptyList()) + groovy.setSrcDirs(emptyList()) + resources.setSrcDirs(emptyList()) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt new file mode 100644 index 0000000..f025c75 --- /dev/null +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -0,0 +1,28 @@ +object Versions { + + const val SPIGOT_API = "1.21.11-R0.1-SNAPSHOT" + + const val ETERNAL_COMBAT = "2.4.0" + const val WORLDGUARD_API = "7.0.9" + const val WORLDEDIT = "3ISh7ADm" + const val WORLDGUARD = "7.0.15-beta-01" + const val PACKETEVENTS = "2.11.2" + + const val PANDA_DI = "1.8.0" + const val CLASSGRAPH = "4.8.184" + + const val KYORI_PLATFORM_BUKKIT = "4.4.1" + const val KYORI_TEXT_MINIMESSAGE = "4.21.0" + + const val MULTIFICATION_BUKKIT = "1.2.4" + const val MULTIFICATION_OKAERI = "1.2.4" + + const val OKAERI_SNAKEYAML = "5.0.9" + const val OKAERI_SERDES_COMMONS = "5.0.5" + + const val TRIUMPH_GUI = "3.1.13" + const val BSTATS_BUKKIT = "3.1.0" + const val LITECOMMANDS = "3.10.9" + + const val LIBBY_BUKKIT = "2.0.0-SNAPSHOT" +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/doublejump-java-test.gradle.kts b/buildSrc/src/main/kotlin/doublejump-java-test.gradle.kts new file mode 100644 index 0000000..6ffb71d --- /dev/null +++ b/buildSrc/src/main/kotlin/doublejump-java-test.gradle.kts @@ -0,0 +1,16 @@ +plugins { + `java-library` +} + +dependencies { + testImplementation("org.junit.jupiter:junit-jupiter:6.0.2") + testImplementation("org.assertj:assertj-core:3.27.6") + testImplementation("org.mockito:mockito-core:5.21.0") + testImplementation("org.mockito:mockito-junit-jupiter:5.21.0") + testImplementation("org.mockito:mockito-inline:5.2.0") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +tasks.getByName("test") { + useJUnitPlatform() +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/doublejump-java.gradle.kts b/buildSrc/src/main/kotlin/doublejump-java.gradle.kts new file mode 100644 index 0000000..911ec7c --- /dev/null +++ b/buildSrc/src/main/kotlin/doublejump-java.gradle.kts @@ -0,0 +1,16 @@ +plugins { + `java-library` +} + +group = "com.github.imdmk" +version = "4.0.0" + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) +} + +tasks.compileJava { + options.compilerArgs = listOf("-Xlint:deprecation", "-parameters") + options.encoding = "UTF-8" + options.release = 21 +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/doublejump-repositories.gradle.kts b/buildSrc/src/main/kotlin/doublejump-repositories.gradle.kts new file mode 100644 index 0000000..dcb2c78 --- /dev/null +++ b/buildSrc/src/main/kotlin/doublejump-repositories.gradle.kts @@ -0,0 +1,15 @@ +plugins { + `java-library` +} + +repositories { + mavenCentral() + maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") + maven("https://repo.papermc.io/repository/maven-public/") + maven("https://repo.eternalcode.pl/releases") + maven("https://storehouse.okaeri.eu/repository/maven-public/") + maven("https://repo.panda-lang.org/releases") + maven("https://repo.alessiodp.com/releases/") + maven("https://repo.alessiodp.com/snapshots/") + maven("https://maven.enginehub.org/repo/") +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/doublejump-shadow.gradle.kts b/buildSrc/src/main/kotlin/doublejump-shadow.gradle.kts new file mode 100644 index 0000000..1665187 --- /dev/null +++ b/buildSrc/src/main/kotlin/doublejump-shadow.gradle.kts @@ -0,0 +1,39 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import net.minecrell.pluginyml.bukkit.BukkitPluginDescription + +plugins { + `java-library` + id("net.minecrell.plugin-yml.bukkit") + id("com.gradleup.shadow") +} + +open class DoubleJumpShadowExtension { + + internal var bukkitAction: Action? = null + internal var shadowAction: Action? = null + + fun pluginYml(action: Action) { + bukkitAction = action + } + + fun shadowJar(action: Action) { + shadowAction = action + } +} + +extensions.create("doubleJumpShadow", DoubleJumpShadowExtension::class.java) + +afterEvaluate { + + val ext = extensions.getByType(DoubleJumpShadowExtension::class.java) + + ext.bukkitAction?.let { action -> + extensions.configure("bukkit") { + action.execute(this) + } + } + + tasks.withType().configureEach { + ext.shadowAction?.execute(this) + } +} \ No newline at end of file diff --git a/checkstyle.xml b/checkstyle.xml deleted file mode 100644 index b46f726..0000000 --- a/checkstyle.xml +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/doublejump-api/build.gradle.kts b/doublejump-api/build.gradle.kts deleted file mode 100644 index 9c986ee..0000000 --- a/doublejump-api/build.gradle.kts +++ /dev/null @@ -1,12 +0,0 @@ -group = "com.github.imdmk" -version = "1.0.1" - -repositories { - maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") -} - -dependencies { - compileOnly("org.spigotmc:spigot-api:1.17-R0.1-SNAPSHOT") - - implementation("com.github.ben-manes.caffeine:caffeine:3.1.8") -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/DoubleJumpApi.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/DoubleJumpApi.java deleted file mode 100644 index a86dd17..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/DoubleJumpApi.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.github.imdmk.doublejump; - -import com.github.imdmk.doublejump.config.ConfigManager; -import com.github.imdmk.doublejump.jump.cache.JumpPlayerCache; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualCache; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualRepository; -import org.jetbrains.annotations.NotNull; - -/** - * Core public API interface for the DoubleJump plugin. - *

- * Provides access to the main components responsible for configuration management, - * player state caching, restriction checking, and visual effect management related to double jumping. - * This interface serves as the entry point for interacting with the plugin's core services. - *

- *

- * Implementations of this interface are expected to be thread-safe and long-lived for the lifetime of the plugin. - *

- * - * @since 1.0.0 - */ -public interface DoubleJumpApi { - - /** - * Retrieves the {@link ConfigManager} responsible for loading, - * parsing, and providing access to all plugin configuration files. - *

- * This manager provides methods to obtain current configuration values - * and supports dynamic reloads if implemented. - *

- * - * @return a non-null instance of {@link ConfigManager} used by the plugin. - */ - @NotNull - ConfigManager getConfigurationManager(); - - /** - * Returns the {@link JumpPlayerCache} containing runtime data for all active players - * utilizing the double jump feature. - *

- * This cache stores player-specific jump states and metadata, and is optimized - * for concurrent access during gameplay. - *

- * - * @return a non-null {@link JumpPlayerCache} instance managing player jump data. - */ - @NotNull JumpPlayerCache getJumpPlayerCache(); - - /** - * Provides the {@link RestrictionChecker} used to evaluate if a player - * is permitted to perform a double jump based on defined restrictions. - *

- * Restrictions may include world settings, player permissions, cooldowns, - * or other custom rules. - *

- * - * @return a non-null {@link RestrictionChecker} instance responsible for validation. - */ - @NotNull RestrictionChecker getRestrictionChecker(); - - /** - * Retrieves the {@link JumpVisualCache} which caches visual effects related - * to the double jump feature for improved performance and reduced resource usage. - * - * @return a non-null {@link JumpVisualCache} instance. - */ - @NotNull JumpVisualCache getJumpVisualCache(); - - /** - * Provides access to the {@link JumpVisualRepository}, the persistent storage - * layer for visual jump effects configuration and assets. - *

- * This repository handles loading, saving, and querying of visual effect data. - *

- * - * @return a non-null {@link JumpVisualRepository} instance. - */ - @NotNull JumpVisualRepository getJumpVisualRepository(); -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/DoubleJumpApiProvider.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/DoubleJumpApiProvider.java deleted file mode 100644 index d0af7b2..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/DoubleJumpApiProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.github.imdmk.doublejump; - -import org.jetbrains.annotations.NotNull; - -/** - * Static access point for the {@link DoubleJumpApi}. - * Acts as a global registry for the current instance. - *

- * Not thread-safe. - */ -public class DoubleJumpApiProvider { - - private static DoubleJumpApi DOUBLE_JUMP_API; - - private DoubleJumpApiProvider() { - throw new UnsupportedOperationException("This class cannot be instantiated."); - } - - /** - * Returns the registered {@link DoubleJumpApi}. - * - * @return the registered API - * @throws IllegalStateException if the API is not yet registered - */ - public synchronized static DoubleJumpApi get() { - if (DOUBLE_JUMP_API == null) { - throw new IllegalStateException("The DoubleJumpApi isn't registered."); - } - - return DOUBLE_JUMP_API; - } - - /** - * Registers the {@link DoubleJumpApi} instance. - * - * @param api the API instance to register - * @throws IllegalStateException if already registered - */ - static synchronized void register(@NotNull DoubleJumpApi api) { - if (DOUBLE_JUMP_API != null) { - throw new IllegalStateException("The DoubleJumpApi is already registered."); - } - - DOUBLE_JUMP_API = api; - } - - /** - * Forces to register the {@link DoubleJumpApi} instance. - */ - static void forceRegister(@NotNull DoubleJumpApi api) { - DOUBLE_JUMP_API = api; - } - - /** - * Unregisters the {@link DoubleJumpApi}. - * - * @throws IllegalStateException if no API was registered - */ - static synchronized void unregister() { - if (DOUBLE_JUMP_API == null) { - throw new IllegalStateException("The DoubleJumpApi isn't registered."); - } - - DOUBLE_JUMP_API = null; - } -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/config/ConfigLoadException.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/config/ConfigLoadException.java deleted file mode 100644 index 56aa163..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/config/ConfigLoadException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.imdmk.doublejump.config; - -public final class ConfigLoadException extends RuntimeException { - public ConfigLoadException(Throwable cause) { - super("Failed to load configuration", cause); - } - - public ConfigLoadException(String message) { - super(message); - } - - public ConfigLoadException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/config/ConfigManager.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/config/ConfigManager.java deleted file mode 100644 index 9926762..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/config/ConfigManager.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.github.imdmk.doublejump.config; - -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.exception.OkaeriException; -import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer; -import org.jetbrains.annotations.NotNull; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.LoaderOptions; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.Constructor; -import org.yaml.snakeyaml.representer.Representer; -import org.yaml.snakeyaml.resolver.Resolver; - -import java.io.File; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Manages loading, saving, and reloading of configuration sections. - *

- * Uses OkaeriConfig framework with customized YAML configuration. - * Supports asynchronous reload of all configs and tracks created config instances. - *

- */ -public final class ConfigManager { - - private final Set configs = ConcurrentHashMap.newKeySet(); - - private final Logger logger; - private final File dataFolder; - private final ExecutorService executor; - - public ConfigManager(@NotNull Logger logger, @NotNull File dataFolder) { - this.logger = Objects.requireNonNull(logger, "logger cannot be null"); - this.dataFolder = Objects.requireNonNull(dataFolder, "dataFolder cannot be null"); - this.executor = Executors.newSingleThreadExecutor(); - } - - /** - * Creates and loads a configuration section of the specified class type. - * Config will be bound to a file named by the config's {@code getFileName()} method, - * will use the config's specified serdes pack, and will remove orphaned entries. - * Default values are saved if the file does not exist. - * - * @param the type of config section - * @param config the config class to create, must not be null - * @return the created and loaded configuration instance - */ - public T create(@NotNull Class config) { - T configFile = eu.okaeri.configs.ConfigManager.create(config); - String fileName = configFile.getFileName(); - - if (fileName.isEmpty()) { - throw new IllegalArgumentException("config file name cannot be empty"); - } - - File file = new File(this.dataFolder, fileName); - - YamlSnakeYamlConfigurer yamlSnakeYamlConfigurer = this.createYamlSnakeYamlConfigurer(); - - configFile.withConfigurer(yamlSnakeYamlConfigurer); - configFile.withSerdesPack(configFile.getSerdesPack()); - configFile.withBindFile(file); - configFile.withRemoveOrphans(true); - configFile.saveDefaults(); - configFile.load(true); - - this.configs.add(configFile); - - return configFile; - } - - /** - * Creates a custom YAML configurer used by the Okaeri config framework. - * Configures YAML options such as indentation and flow style. - * - * @return a configured {@link YamlSnakeYamlConfigurer} instance - */ - private @NotNull YamlSnakeYamlConfigurer createYamlSnakeYamlConfigurer() { - LoaderOptions loaderOptions = new LoaderOptions(); - Constructor constructor = new Constructor(loaderOptions); - - DumperOptions dumperOptions = new DumperOptions(); - dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.AUTO); - dumperOptions.setIndent(2); - dumperOptions.setSplitLines(false); - - Representer representer = new CustomRepresenter(dumperOptions); - Resolver resolver = new Resolver(); - - Yaml yaml = new Yaml(constructor, representer, dumperOptions, loaderOptions, resolver); - return new YamlSnakeYamlConfigurer(yaml); - } - - /** - * Asynchronously reloads all managed configuration sections. - * Reload is executed in a single-threaded executor to avoid concurrency issues. - * - * @return a CompletableFuture representing the reload task - */ - public @NotNull CompletableFuture reloadAll() { - return CompletableFuture.runAsync(this::loadAll, this.executor); - } - - /** - * Loads all currently tracked configuration sections synchronously. - */ - private void loadAll() { - this.configs.forEach(this::load); - } - - /** - * Loads the specified configuration section from its bound file. - * If loading fails, logs the error and throws a runtime exception. - * - * @param config the configuration instance to load, must not be null - * @throws ConfigLoadException if an error occurs during loading - */ - public void load(@NotNull OkaeriConfig config) { - try { - config.load(true); - } - catch (OkaeriException exception) { - this.logger.log(Level.SEVERE, "Failed to load config: " + config.getClass().getSimpleName(), exception); - throw new ConfigLoadException(exception); - } - } - - /** - * Shuts down the internal executor service used for async operations. - * Should be called on plugin shutdown to release resources. - */ - public void shutdown() { - this.logger.info("Shutting down ConfigurationManager executor"); - this.executor.shutdownNow(); - } - -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/config/ConfigSection.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/config/ConfigSection.java deleted file mode 100644 index f968171..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/config/ConfigSection.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.github.imdmk.doublejump.config; - -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.exception.OkaeriException; -import eu.okaeri.configs.serdes.OkaeriSerdesPack; -import org.jetbrains.annotations.NotNull; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; - -/** - * Abstract base class for configuration sections. - * - *

- * Extends {@link OkaeriConfig} to provide a reusable foundation for plugin - * configuration sections. Subclasses are required to specify the - * serialization/deserialization pack and the configuration file name. - *

- * - *

- * Supports automatic recursive loading of nested {@link ConfigSection} - * subclasses declared as fields inside this class. - *

- */ -public abstract class ConfigSection extends OkaeriConfig { - - /** - * Returns the {@link OkaeriSerdesPack} instance used for serializing and deserializing - * this configuration section. - * - * @return non-null serialization/deserialization pack - */ - public abstract @NotNull OkaeriSerdesPack getSerdesPack(); - - /** - * Returns the filename (including extension) used to persist this configuration section. - * - * @return non-null configuration file name - */ - public abstract @NotNull String getFileName(); - - /** - * Loads the configuration from disk, then processes any post-load logic. - * Recursively loads any nested {@link ConfigSection} fields declared in this class. - * - * @return this configuration instance for chaining - * @throws OkaeriException if an error occurs during loading - */ - @Override - public OkaeriConfig load() throws OkaeriException { - super.load(); - this.loadProcessedProperties(); - try { - this.loadNestedConfigSections(this.getClass()); - } - catch (IllegalAccessException e) { - Logger.getLogger(this.getClass().getName()) - .severe("Could not load nested config sections: " + e.getMessage()); - } - return this; - } - - /** - * Recursively loads nested {@link ConfigSection} fields declared as inner classes - * or fields of this config section. - * - * @param fromClass the class to inspect for nested config sections - * @throws IllegalAccessException if reflection access fails - */ - private void loadNestedConfigSections(Class fromClass) throws IllegalAccessException { - List> nestedClasses = new ArrayList<>(); - - // Collect all inner classes that extend ConfigSection - for (Class declaredClass : fromClass.getDeclaredClasses()) { - if (ConfigSection.class.isAssignableFrom(declaredClass)) { - nestedClasses.add(declaredClass.asSubclass(ConfigSection.class)); - } - } - - // For each field, if its type matches one of the nested ConfigSection classes, - // invoke loading logic on that field's instance. - for (Field field : fromClass.getDeclaredFields()) { - for (Class nestedClass : nestedClasses) { - if (field.getType().equals(nestedClass)) { - field.setAccessible(true); - if (field.canAccess(this)) { - ConfigSection nestedConfig = (ConfigSection) field.get(this); - if (nestedConfig != null) { - nestedConfig.loadProcessedProperties(); - nestedConfig.loadNestedConfigSections(nestedClass); - } - } - } - } - } - } - - /** - * Hook method called after configuration loading is completed. - * Override in subclasses to perform validation or further processing. - */ - public void loadProcessedProperties() { - // Default no-op implementation - } -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/event/EventCaller.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/event/EventCaller.java deleted file mode 100644 index e47f5c3..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/event/EventCaller.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.imdmk.doublejump.event; - -import org.bukkit.event.Event; -import org.jetbrains.annotations.NotNull; - -/** - * Interface representing a caller that can trigger Bukkit events. - */ -public interface EventCaller { - - /** - * Calls (fires) the given Bukkit event. - * - * @param event the event to be called; must not be null - */ - void callEvent(@NotNull Event event); -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/JumpActivationType.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/JumpActivationType.java deleted file mode 100644 index c7f0986..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/JumpActivationType.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.github.imdmk.doublejump.jump; - -/** - * Represents the source or method by which the double jump ability has been activated for a player. - * This is used to differentiate between manual activation (e.g., via command) - * and conditional activation (e.g., based on an equipped item). - */ -public enum JumpActivationType { - - /** - * Indicates that the double jump is not currently activated for the player. - * This is the default state before any activation method has been applied. - */ - NONE, - - /** - * Indicates that the double jump was enabled manually, for example via a command - * or permanent permission, and should persist regardless of player equipment. - */ - COMMAND, - - /** - * Indicates that the double jump was activated automatically when the player joined the server. - * This is typically used when the feature is globally enabled or remembered from a previous session. - */ - JOIN, - - /** - * Indicates that the double jump was enabled due to a specific item, - * such as special boots. This mode should be deactivated if the item is lost or unequipped. - */ - ITEM, - - /** - * Indicates that the double jump was enabled due to the player standing on a specific block, - * such as a configured jump block. This activation is temporary and only applies while the player - * is in contact with the block. - */ - BLOCK - -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/JumpPlayer.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/JumpPlayer.java deleted file mode 100644 index a52f831..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/JumpPlayer.java +++ /dev/null @@ -1,265 +0,0 @@ -package com.github.imdmk.doublejump.jump; - -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionDenyReason; -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocity; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.time.Instant; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; - -/** - * Represents the double jump state of a player. - * Stores runtime flags and timestamps used to control jump availability and restrictions. - */ -public class JumpPlayer { - - private final UUID uuid; - private final String name; - - /** - * Defines how the double jump ability was activated for the player. - * This can be used to differentiate between manual activation (e.g. via command) - * and item-based activation (e.g. special boots). - */ - private JumpActivationType activationType = JumpActivationType.NONE; - - /** - * Indicates whether double jump is currently active for this player (toggled on). - * Does not necessarily mean the player is allowed to jump (see {@link #jumpAllowed}). - */ - private boolean active = false; - - /** - * Indicates whether the player is currently allowed to perform a double jump. - * This is affected by runtime restrictions such as cooldowns or conditions. - * Defaults to {@code true}. - */ - private boolean jumpAllowed = true; - - /** - * Timestamp of the player's performed jump delay. - * Used for applying cooldowns and delay-based restrictions. - */ - private Instant nextAllowedJump; - - /** - * The last restriction reason that was notified to the player. - * Used to prevent sending duplicate restriction messages. - */ - private RestrictionDenyReason lastNotifiedReason; - - /** - * The velocity configuration applied when the player performs a double jump. - * Defines movement parameters such as vertical and horizontal force. - */ - private JumpVelocity jumpVelocity; - - /** - * Creates a new JumpPlayer instance for the given player UUID and name. - * - * @param uuid the unique identifier of the player - * @param name the player name - */ - public JumpPlayer(@NotNull UUID uuid, @NotNull String name) { - this.uuid = uuid; - this.name = name; - } - - /** - * Gets the UUID of the player. - * - * @return the player's UUID - */ - public @NotNull UUID getUuid() { - return this.uuid; - } - - /** - * Gets the name of the player. - * - * @return the player's name - */ - public @NotNull String getName() { - return this.name; - } - - /** - * Gets the current jump activation type for the player. - * This indicates how the double jump was initially enabled (e.g. by command or item). - * - * @return the activation type of the double jump - */ - public JumpActivationType getActivationType() { - return this.activationType; - } - - /** - * Checks whether the player's current activation type matches the given type. - * This can be used to determine how the double jump was enabled (e.g. via item or manually). - * - * @param type the activation type to compare against, must not be null - * @return true if the activation type matches and is not null, false otherwise - */ - public boolean isActivationType(@NotNull JumpActivationType type) { - return this.activationType.equals(type); - } - - /** - * Sets the activation type for the double jump. - * Should be updated if the source of jump permission changes during gameplay. - * - * @param activationType the new activation type - */ - public void setActivationType(@Nullable JumpActivationType activationType) { - this.activationType = activationType; - } - - /** - * Checks whether the jump feature is currently active for the player. - * - * @return true if active, false otherwise - */ - public boolean isActive() { - return this.active; - } - - /** - * Sets whether the jump feature is active for the player. - * - * @param active true to activate, false to deactivate - */ - public void setActive(boolean active) { - this.active = active; - } - - /** - * Checks whether the player is currently allowed to perform a jump. - * - * @return true if jumping is allowed, false otherwise - */ - public boolean isJumpAllowed() { - return this.jumpAllowed; - } - - /** - * Sets whether the player is allowed to jump. - * - * @param jumpAllowed true to allow jumping, false to prevent it - */ - public void setJumpAllowed(boolean jumpAllowed) { - this.jumpAllowed = jumpAllowed; - } - - /** - * Gets the timestamp indicating when the player is next allowed to perform a jump. - * This value is used to enforce cooldowns between jumps. - * - * @return an optional containing the next allowed jump time, or empty if not set - */ - public Optional getNextAllowedJump() { - return Optional.ofNullable(this.nextAllowedJump); - } - - /** - * Sets the timestamp after which the player is allowed to perform the next jump. - * This is typically used to apply a cooldown delay. - * - * @param nextAllowedJump the time after which jumping is allowed again; may be null to clear - */ - public void setNextAllowedJump(@Nullable Instant nextAllowedJump) { - this.nextAllowedJump = nextAllowedJump; - } - - /** - * Checks if the given restriction reason is the same as the last notified one. - * - * @param reason the reason to compare, not null - * @return true if the reason matches the last-notified reason, false otherwise - */ - public boolean isSameAsLastNotifiedReason(@NotNull RestrictionDenyReason reason) { - return Objects.equals(this.lastNotifiedReason, reason); - } - - /** - * Checks if there is a last notified restriction reason stored. - * - * @return true if a last notified reason is set, false otherwise - */ - public boolean hasLastNotifiedReason() { - return this.lastNotifiedReason != null; - } - - /** - * Sets the last restriction reason notified to the player. - * - * @param lastNotifiedReason the reason to set, may be null - */ - public void setLastNotifiedReason(@Nullable RestrictionDenyReason lastNotifiedReason) { - this.lastNotifiedReason = lastNotifiedReason; - } - - /** - * Returns the jump properties assigned to this player. - * - * @return the player's jump properties - */ - public @NotNull JumpVelocity getJumpVelocity() { - return this.jumpVelocity; - } - - /** - * Sets the jump properties to use for this player. - * - * @param jumpVelocity the new jump properties - */ - public void setJumpVelocity(@NotNull JumpVelocity jumpVelocity) { - this.jumpVelocity = jumpVelocity; - } - - /** - * Checks if this {@code JumpPlayer} represents the same player as another instance. - * - * @param o the object to compare - * @return true if UUIDs are equal; false otherwise - */ - @Override - public final boolean equals(Object o) { - if (!(o instanceof JumpPlayer that)) { - return false; - } - - return this.getUuid().equals(that.getUuid()); - } - - /** - * Returns the hash code based on the player's UUID. - * - * @return the hash code - */ - @Override - public int hashCode() { - return this.getUuid().hashCode(); - } - - /** - * Returns a string representation of this {@code JumpPlayer}. - * - * @return string with player state details - */ - @Override - public String toString() { - return "JumpPlayer{" + - "uuid=" + this.uuid + - ", name='" + this.name + '\'' + - ", activationType=" + this.activationType + - ", active=" + this.active + - ", jumpAllowed=" + this.jumpAllowed + - ", nextAllowedJump=" + this.nextAllowedJump + - ", lastNotifiedReason=" + this.lastNotifiedReason + - ", jumpProperties=" + this.jumpVelocity + - '}'; - } -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/cache/JumpPlayerCache.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/cache/JumpPlayerCache.java deleted file mode 100644 index d66079e..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/cache/JumpPlayerCache.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.github.imdmk.doublejump.jump.cache; - -import com.github.imdmk.doublejump.jump.JumpPlayer; -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; - -/** - * Thread-safe cache storing {@link JumpPlayer} instances by their UUIDs. - * Used to track players with active double jump capabilities. - *

- * The cache supports typical operations: add, remove, check existence, - * retrieve, and list all registered players. - *

- */ -public final class JumpPlayerCache { - - private final Map jumpPlayers = new ConcurrentHashMap<>(); - - /** - * Adds or replaces a {@link JumpPlayer} in the cache. - * - * @param uuid The UUID of the player. - * @param jumpPlayer The {@link JumpPlayer} instance to store. - */ - public void put(@NotNull UUID uuid, @NotNull JumpPlayer jumpPlayer) { - this.jumpPlayers.put(uuid, jumpPlayer); - } - - /** - * Removes the {@link JumpPlayer} associated with the given UUID. - * - * @param uuid The UUID of the player. - * @return {@code true} if a player was removed, {@code false} if none was found. - */ - public boolean remove(@NotNull UUID uuid) { - return this.jumpPlayers.remove(uuid) != null; - } - - /** - * Checks whether the cache contains a {@link JumpPlayer} for the given UUID. - * - * @param uuid The UUID to check. - * @return {@code true} if a player is present, {@code false} otherwise. - */ - public boolean hasPlayer(@NotNull UUID uuid) { - return this.jumpPlayers.containsKey(uuid); - } - - /** - * Checks whether the {@link JumpPlayer} associated with the given UUID is active. - * - * @param uuid The UUID of the player. - * @return {@code true} if the player is present and active, {@code false} otherwise. - */ - public boolean isActive(@NotNull UUID uuid) { - return this.getActive(uuid).isPresent(); - } - - /** - * Executes the given action if the player is currently marked as active in the jump cache. - * - * @param uuid the unique identifier of the player - * @param onActive the action to perform if the player is active - */ - public void ifActive(@NotNull UUID uuid, @NotNull Consumer onActive) { - this.getActive(uuid).ifPresent(onActive); - } - - /** - * Retrieves the active {@link JumpPlayer} for the given UUID, if present. - * The player must be marked as active. - * - * @param uuid The UUID of the player. - * @return An {@link Optional} containing the active {@link JumpPlayer} if present, or empty otherwise. - */ - public Optional getActive(@NotNull UUID uuid) { - return this.get(uuid).filter(JumpPlayer::isActive); - } - - /** - * Retrieves the {@link JumpPlayer} for the given UUID, if present. - * - * @param uuid The UUID of the player. - * @return An {@link Optional} containing the {@link JumpPlayer} if present, or empty otherwise. - */ - public Optional get(@NotNull UUID uuid) { - return Optional.ofNullable(this.jumpPlayers.get(uuid)); - } - - /** - * Retrieves the {@link JumpPlayer} associated with the given UUID, or throws an exception if not found. - * - * @param uuid The UUID of the player. - * @return The associated {@link JumpPlayer}. - * @throws IllegalStateException if no player is found for the given UUID. - */ - public @NotNull JumpPlayer getOrThrow(UUID uuid) { - return this.get(uuid).orElseThrow(() -> new IllegalStateException("Jump player not found")); - } - - /** - * Returns an unmodifiable view of all stored {@link JumpPlayer}s. - * - * @return An unmodifiable map containing all cached players by their UUID. - */ - public Map getPlayers() { - return Collections.unmodifiableMap(this.jumpPlayers); - } -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/event/DoubleJumpEvent.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/event/DoubleJumpEvent.java deleted file mode 100644 index 8989d21..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/event/DoubleJumpEvent.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.github.imdmk.doublejump.jump.event; - -import com.github.imdmk.doublejump.jump.JumpPlayer; -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocity; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.jetbrains.annotations.NotNull; - -/** - * Event called when a player performs a double jump. - * This event is cancellable to allow cancelling the double jump action. - */ -public final class DoubleJumpEvent extends Event implements Cancellable { - - private static final HandlerList HANDLERS = new HandlerList(); - - private final @NotNull Player player; - private final @NotNull JumpPlayer jumpPlayer; - private @NotNull JumpVelocity jumpVelocity; - - private boolean cancelled; - - /** - * Constructs a new DoubleJumpEvent. - * - * @param player The player who performed the double jump. - * @param jumpPlayer The JumpPlayer data associated with the player. - * @param jumpVelocity The jump properties (boosts) applied during this double jump. - */ - public DoubleJumpEvent(final @NotNull Player player, final @NotNull JumpPlayer jumpPlayer, final @NotNull JumpVelocity jumpVelocity) { - this.player = player; - this.jumpPlayer = jumpPlayer; - this.jumpVelocity = jumpVelocity; - } - - /** - * Constructs a new DoubleJumpEvent. - * - * @param player The player who performed the double jump. - * @param jumpPlayer The JumpPlayer data associated with the player. - */ - public DoubleJumpEvent(final @NotNull Player player, final @NotNull JumpPlayer jumpPlayer) { - this.player = player; - this.jumpPlayer = jumpPlayer; - this.jumpVelocity = jumpPlayer.getJumpVelocity(); - } - - @Override - public @NotNull HandlerList getHandlers() { - return HANDLERS; - } - - /** - * Returns the static handler list required by Bukkit. - * - * @return HandlerList instance for this event. - */ - public static @NotNull HandlerList getHandlerList() { - return HANDLERS; - } - - /** - * Gets the player who performed the double jump. - * - * @return The player. - */ - public @NotNull Player getPlayer() { - return this.player; - } - - /** - * Gets the JumpPlayer data related to this double jump. - * - * @return The JumpPlayer instance. - */ - public @NotNull JumpPlayer getJumpPlayer() { - return this.jumpPlayer; - } - - /** - * Gets the jump properties (horizontal and vertical boosts) applied during this double jump. - * - * @return The DoubleJumpProperties instance. - */ - public @NotNull JumpVelocity getJumpVelocity() { - return this.jumpVelocity; - } - - /** - * Sets the jump properties to be applied during this double jump. - * - * @param jumpVelocity The new jump properties. - */ - public void setJumpVelocity(@NotNull JumpVelocity jumpVelocity) { - this.jumpVelocity = jumpVelocity; - } - - @Override - public boolean isCancelled() { - return this.cancelled; - } - - @Override - public void setCancelled(boolean cancel) { - this.cancelled = cancel; - } -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/RestrictionChecker.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/RestrictionChecker.java deleted file mode 100644 index 118343d..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/RestrictionChecker.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction; - -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -/** - * Functional interface representing a single restriction check for a player. - *

- * Implementations of this interface encapsulate the logic to determine whether - * a player passes or fails a specific restriction, such as permission, world, - * or game mode restrictions. - *

- */ -@FunctionalInterface -public interface RestrictionChecker { - - /** - * Checks if the given player satisfies the restriction. - * - * @param player the player to check, must not be null - * @return a {@link RestrictionResult} indicating whether the restriction - * was passed or failed, never null - */ - @NotNull RestrictionResult check(@NotNull Player player); -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/RestrictionDenyReason.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/RestrictionDenyReason.java deleted file mode 100644 index 8db8401..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/RestrictionDenyReason.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction; - -/** - * Enumerates reasons why a player may be restricted from using double jump. - *

- * These reasons are used in {@link RestrictionResult} - * to provide context when a restriction check fails. - *

- */ -public enum RestrictionDenyReason { - - /** - * The player's current world is not allowed for double jump. - * Hard restriction: true - */ - WORLD_DISABLED(true), - - /** - * The player's current region (WorldGuard) is not allowed for double jump. - * Hard restriction: true - */ - REGION_DISABLED(true), - - /** - * The player's current game mode is not permitted for double jump. - * Hard restriction: true - */ - GAME_MODE_BLOCKED(true), - - /** - * The player lacks the necessary permission to use double jump. - * Hard restriction: true - */ - PERMISSION_REQUIRED(true), - - /** - * The player's network connection is unstable (ping is too high). - * Hard restriction: true - */ - PLAYER_LAGGING(true), - - /** - * The player is attempting to double jump when gliding (e.x elytra) - * Hard restriction: false - */ - PLAYER_GLIDING(false), - - /** - * The player is attempting to double jump before the configured cooldown - * period has elapsed since the last jump. - * Hard restriction: false - */ - JUMP_DELAY(false); - - /** - * Indicates whether the restriction is considered "hard", - * meaning it should immediately and forcibly disable double jump functionality. - *

- * Hard restrictions typically include world constraints, permission issues, - * game mode blocks, or network conditions that prevent consistent gameplay. - *

- */ - private final boolean hardRestriction; - - RestrictionDenyReason(boolean hardRestriction) { - this.hardRestriction = hardRestriction; - } - - public boolean isHardRestriction() { - return this.hardRestriction; - } - -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/RestrictionResult.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/RestrictionResult.java deleted file mode 100644 index 506a9fa..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/RestrictionResult.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction; - -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; - -/** - * Represents the result of checking all flying or jump-related restrictions. - *

- * A result is considered successful if no restriction has denied the action. - * If the result is a failure, an optional reason is provided to indicate why it was denied. - * - * @param success {@code true} if all restrictions passed, {@code false} otherwise - * @param reason Optional reason describing why the restrictions failed, present only if {@code success == false} - */ -public record RestrictionResult(boolean success, @NotNull Optional reason) { - - /** - * Returns whether the restriction check failed. - * - * @return {@code true} if the result is a failure - */ - public boolean failure() { - return !this.success; - } - - /** - * Returns whether the restriction associated with this result is considered hard, - * which typically requires immediate disabling of the double jump feature. - * - * @return true if the restriction is hard, false otherwise - */ - public boolean isHardRestriction() { - return this.reason().map(RestrictionDenyReason::isHardRestriction).orElse(false); - } - - /** - * Creates a successful restriction result, indicating that no restriction was violated. - * - * @return a successful {@link RestrictionResult} instance - */ - public static RestrictionResult passed() { - return new RestrictionResult(true, Optional.empty()); - } - - /** - * Creates a failed restriction result with the given denial reason. - * - * @param reason the reason why the restriction failed, may be {@code null} - * @return a failed {@link RestrictionResult} instance - */ - public static RestrictionResult failed(RestrictionDenyReason reason) { - return new RestrictionResult(false, Optional.ofNullable(reason)); - } -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/velocity/JumpVelocity.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/velocity/JumpVelocity.java deleted file mode 100644 index 40ad510..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/velocity/JumpVelocity.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.velocity; - -/** - * Immutable holder for double jump velocity boost properties. - *

- * Defines multipliers for both horizontal and vertical components of the player's velocity during a double jump. - * These values determine how far and how high a player will be propelled. - *

- * - * @param horizontalBoost multiplier applied to the player's horizontal velocity during a double jump - * @param verticalBoost multiplier applied to the player's vertical velocity during a double jump - */ -public record JumpVelocity(double horizontalBoost, double verticalBoost) { - - /** - * Factory method to create a new {@link JumpVelocity} instance with the given boost values. - * - * @param horizontalBoost the multiplier for horizontal velocity - * @param verticalBoost the multiplier for vertical velocity - * @return a new {@link JumpVelocity} instance - */ - public static JumpVelocity of(double horizontalBoost, double verticalBoost) { - return new JumpVelocity(horizontalBoost, verticalBoost); - } -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/JumpVisual.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/JumpVisual.java deleted file mode 100644 index 8ce5424..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/JumpVisual.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual; - -import com.github.imdmk.doublejump.jump.feature.visual.particle.JumpParticle; -import com.github.imdmk.doublejump.jump.feature.visual.sound.JumpSound; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -/** - * Represents visual effects (particles and sound) triggered when a player performs a jump. - */ -public final class JumpVisual { - - private List jumpParticles; - private JumpSound jumpSound; - - /** - * Constructs a new {@code JumpVisual} with optional particles and sound. - * - * @param particles list of jump particles (nullable) - * @param sound jump to sound (nullable) - */ - public JumpVisual(@Nullable List particles, @Nullable JumpSound sound) { - this.jumpParticles = particles != null ? List.copyOf(particles) : Collections.emptyList(); - this.jumpSound = sound; - } - - /** - * Returns the list of jump particles. - * - * @return non-null, unmodifiable list - */ - public @NotNull List getJumpParticles() { - return this.jumpParticles; - } - - /** - * Sets the jump particles list. - * - * @param particles nullable list - */ - public void setJumpParticles(@NotNull List particles) { - this.jumpParticles = List.copyOf(particles); - } - - /** - * Adds a jump particle to the current list. - * - * @param particle the particle to add, must not be null - */ - public void addJumpParticle(@NotNull JumpParticle particle) { - List updated = new ArrayList<>(this.jumpParticles); - updated.add(particle); - this.jumpParticles = Collections.unmodifiableList(updated); - } - - /** - * Removes a jump particle from the current list. - * - * @param particle the particle to remove, must not be null - * @return true if the particle was found and removed, false otherwise - */ - public boolean removeJumpParticle(@NotNull JumpParticle particle) { - if (this.jumpParticles == null || !this.jumpParticles.contains(particle)) { - return false; - } - - List updated = new ArrayList<>(this.jumpParticles); - boolean removed = updated.remove(particle); - this.jumpParticles = Collections.unmodifiableList(updated); - return removed; - } - - /** - * Returns the optional jump sound. - * - * @return {@link Optional} containing jump sound or empty - */ - public Optional getJumpSound() { - return Optional.ofNullable(this.jumpSound); - } - - public boolean isJumpSound(@NotNull JumpSound jumpSound) { - return this.getJumpSound() - .map(sound -> sound.equals(jumpSound)) - .orElse(false); - } - - /** - * Sets the jump sound. - * - * @param sound nullable sound - */ - public void setJumpSound(@Nullable JumpSound sound) { - this.jumpSound = sound; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof JumpVisual that)) { - return false; - } - return Objects.equals(this.jumpParticles, that.jumpParticles) - && Objects.equals(this.jumpSound, that.jumpSound); - } - - @Override - public int hashCode() { - return Objects.hash(this.jumpParticles, this.jumpSound); - } - - @Override - public String toString() { - return "JumpVisual{" + - "jumpParticles=" + this.jumpParticles + - ", jumpSound=" + this.jumpSound + - '}'; - } -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/JumpParticle.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/JumpParticle.java deleted file mode 100644 index e4a94f4..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/JumpParticle.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.particle; - -import org.bukkit.Location; -import org.bukkit.Particle; -import org.bukkit.World; -import org.jetbrains.annotations.NotNull; - -/** - * Represents a particle effect with customizable parameters. - */ -public record JumpParticle(@NotNull Particle particle, int count) { - - /** - * Gets the name of particle - */ - public @NotNull String particleName() { - return this.particle.name(); - } - - /** - * Spawns the particle in the given world at the specified location. - */ - public void spawn(@NotNull World world, @NotNull Location location) { - world.spawnParticle(this.particle, location, this.count); - } -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/JumpVisualCache.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/JumpVisualCache.java deleted file mode 100644 index da446af..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/JumpVisualCache.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.repository; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisual; -import org.jetbrains.annotations.NotNull; - -import java.time.Duration; -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.UUID; - -/** - * Cache layer for storing and retrieving {@link JumpVisual} settings based on player UUIDs. - *

- * Uses Caffeine in-memory cache with configurable expiration policies. - * Provides fast access to frequently used visual configuration data. - */ -public class JumpVisualCache { - - /** - * Default expiration duration after write (30 minutes). - */ - private static final Duration EXPIRE_AFTER_WRITE = Duration.ofMinutes(30); - - /** - * Default expiration duration after access (30 minutes). - */ - private static final Duration EXPIRE_AFTER_ACCESS = Duration.ofMinutes(30); - - private final Cache cacheByUuid; - - /** - * Creates a new {@link JumpVisualCache} with custom expiration durations. - * - * @param expireAfterWrite duration after which cache entries expire after being written - * @param expireAfterAccess duration after which cache entries expire after last access - */ - public JumpVisualCache(@NotNull Duration expireAfterWrite, @NotNull Duration expireAfterAccess) { - this.cacheByUuid = Caffeine.newBuilder() - .expireAfterWrite(expireAfterWrite) - .expireAfterAccess(expireAfterAccess) - .maximumSize(1000) - .build(); - } - - /** - * Creates a new {@link JumpVisualCache} with default expiration durations. - */ - public JumpVisualCache() { - this(EXPIRE_AFTER_WRITE, EXPIRE_AFTER_ACCESS); - } - - /** - * Caches the given visual settings for the specified player UUID. - * - * @param uuid unique player identifier - * @param settings visual settings to cache - */ - public void cache(@NotNull UUID uuid, @NotNull JumpVisual settings) { - this.cacheByUuid.put(uuid, settings); - } - - /** - * Evicts the cached visual settings for the specified player UUID. - * - * @param uuid unique player identifier whose settings should be removed - */ - public void evict(@NotNull UUID uuid) { - this.cacheByUuid.invalidate(uuid); - } - - /** - * Clears all cached visual settings. - */ - public void clearCache() { - this.cacheByUuid.invalidateAll(); - } - - /** - * Retrieves cached visual settings for the given player UUID. - * - * @param uuid unique player identifier - * @return optional visual settings, or empty if not cached - */ - public Optional getByUuid(@NotNull UUID uuid) { - return Optional.ofNullable(this.cacheByUuid.getIfPresent(uuid)); - } - - /** - * Returns an unmodifiable collection of all currently cached {@link JumpVisual} entries. - * - * @return collection of cached visual settings - */ - public @NotNull Collection getAllCachedVisuals() { - return Collections.unmodifiableCollection(this.cacheByUuid.asMap().values()); - } -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/JumpVisualRepository.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/JumpVisualRepository.java deleted file mode 100644 index 06969d9..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/JumpVisualRepository.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.repository; - -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisual; -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -/** - * Repository interface for managing persistence of {@link JumpVisual} settings. - *

- * Provides asynchronous operations for retrieving, saving, and deleting visual configurations - * associated with player UUIDs. - */ -public interface JumpVisualRepository { - - /** - * Asynchronously retrieves visual settings associated with the given player UUID. - * - * @param uuid unique player identifier - * @return a {@link CompletableFuture} that completes with an {@link Optional} containing - * the visual settings if present, or empty if not found - */ - CompletableFuture> findByUUID(@NotNull UUID uuid); - - /** - * Asynchronously saves the given visual settings for the specified player UUID. - *

- * May insert or update an existing record depending on the implementation. - * - * @param uuid unique player identifier - * @param settings visual settings to persist - * @return a {@link CompletableFuture} that completes with the saved {@link JumpVisual} - */ - CompletableFuture save(@NotNull UUID uuid, @NotNull JumpVisual settings); - - /** - * Asynchronously deletes the visual settings associated with the given player UUID. - * - * @param uuid unique player identifier - * @return a {@link CompletableFuture} that completes when the deletion is done - */ - CompletableFuture delete(@NotNull UUID uuid); -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/JumpSound.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/JumpSound.java deleted file mode 100644 index 8a8baaa..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/JumpSound.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.sound; - -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -/** - * Represents a sound effect configuration to be played during a jump. - *

- * Wraps a {@link Sound} along with volume and pitch parameters, - * and provides a method to play it for a specific {@link Player}. - * - * @param sound the {@link Sound} to be played (must not be null) - * @param volume the volume of the sound (typically between 0.0 and 1.0+) - * @param pitch the pitch of the sound (typically between 0.5 and 2.0) - */ -public record JumpSound(@NotNull Sound sound, float volume, float pitch) { - - /** - * Gets the name of sound - */ - public @NotNull String getName() { - return this.sound.name(); - } - - /** - * Plays the configured sound effect for the specified player at their current location. - * - * @param player the {@link Player} to play the sound for (must not be null) - */ - public void play(@NotNull Player player) { - player.playSound(player.getLocation(), this.sound, this.volume, this.pitch); - } -} diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/task/TaskScheduler.java b/doublejump-api/src/main/java/com/github/imdmk/doublejump/task/TaskScheduler.java deleted file mode 100644 index af3910c..0000000 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/task/TaskScheduler.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.github.imdmk.doublejump.task; - -import org.jetbrains.annotations.NotNull; - -/** - * Provides methods for scheduling tasks to be executed either synchronously on the main thread - * or asynchronously on a separate thread. - *

- * Implementations of this interface should ensure that synchronous tasks run on the main server thread, - * while asynchronous tasks run off the main thread to prevent blocking. - */ -public interface TaskScheduler { - - /** - * Executes the given {@link Runnable} synchronously on the main server thread as soon as possible. - * - * @param runnable the task to run synchronously; must not be null - * @throws NullPointerException if runnable is null - */ - void run(@NotNull Runnable runnable); - - /** - * Executes the given {@link Runnable} asynchronously on a separate thread as soon as possible. - * - * @param runnable the task to run asynchronously; must not be null - * @throws NullPointerException if runnable is null - */ - void runAsync(@NotNull Runnable runnable); - - /** - * Executes the given {@link Runnable} synchronously after the specified delay. - * The delay unit depends on the implementation (typically server ticks or milliseconds). - * - * @param runnable the task to run synchronously; must not be null - * @param delay the delay before executing the task, in implementation-specific units (e.g. ticks) - * @throws NullPointerException if runnable is null - */ - void runLater(@NotNull Runnable runnable, long delay); - - /** - * Executes the given {@link Runnable} asynchronously after the specified delay. - * The delay unit depends on the implementation (typically server ticks or milliseconds). - * - * @param runnable the task to run asynchronously; must not be null - * @param delay the delay before executing the task, in implementation-specific units (e.g. ticks) - * @throws NullPointerException if runnable is null - */ - void runLaterAsync(@NotNull Runnable runnable, long delay); - - /** - * Executes the given {@link Runnable} repeatedly on a timer asynchronously, - * starting after the initial delay and repeating with the specified period. - * The delay and period units depend on the implementation (e.g., server ticks or milliseconds). - * - * @param runnable the task to run asynchronously on a timer; must not be null - * @param delay the initial delay before first execution, in implementation-specific units - * @param period the period between later executions, in implementation-specific units - * @throws NullPointerException if runnable is null - */ - void runTimerAsync(@NotNull Runnable runnable, long delay, long period); - - /** - * Cancels a scheduled task identified by its task ID. - *

- * If the task with the specified ID is currently scheduled or running, it will be cancelled, - * preventing any future executions of the task. If no task with the given ID exists or - * it has already completed or been cancelled, this method has no effect. - *

- * - * @param taskId the unique identifier of the scheduled task to cancel - */ - void cancelTask(int taskId); - - /** - * Shuts down the scheduler and cancels all pending asynchronous tasks. - *

- * This method should be called during plugin disable or application shutdown - * to ensure that no background tasks continue running after the application stops. - * After calling this method, the scheduler should reject any new tasks. - *

- * - * @implNote Implementations should ensure proper termination of all internal executor services - * or scheduling mechanisms to prevent resource leaks or thread hangs. - */ - void shutdown(); -} diff --git a/doublejump-core/build.gradle.kts b/doublejump-core/build.gradle.kts new file mode 100644 index 0000000..8f44515 --- /dev/null +++ b/doublejump-core/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + `doublejump-java` + `doublejump-java-test` + `doublejump-repositories` +} + +dependencies { + compileOnlyApi("org.spigotmc:spigot-api:${Versions.SPIGOT_API}") + + compileOnly("com.eternalcode:eternalcombat-api:${Versions.ETERNAL_COMBAT}") + compileOnly("com.sk89q.worldguard:worldguard-bukkit:${Versions.WORLDGUARD_API}") + + implementation("org.panda-lang.utilities:di:${Versions.PANDA_DI}") + implementation("io.github.classgraph:classgraph:${Versions.CLASSGRAPH}") + + implementation("net.kyori:adventure-platform-bukkit:${Versions.KYORI_PLATFORM_BUKKIT}") + implementation("net.kyori:adventure-text-minimessage:${Versions.KYORI_TEXT_MINIMESSAGE}") + + implementation("com.eternalcode:multification-bukkit:${Versions.MULTIFICATION_BUKKIT}") + implementation("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION_OKAERI}") + + implementation("eu.okaeri:okaeri-configs-yaml-snakeyaml:${Versions.OKAERI_SNAKEYAML}") + implementation("eu.okaeri:okaeri-configs-serdes-commons:${Versions.OKAERI_SERDES_COMMONS}") + + implementation("dev.rollczi:litecommands-bukkit:${Versions.LITECOMMANDS}") + implementation("dev.rollczi:litecommands-annotations:${Versions.LITECOMMANDS}") + + implementation("dev.triumphteam:triumph-gui:${Versions.TRIUMPH_GUI}") + implementation("org.bstats:bstats-bukkit:${Versions.BSTATS_BUKKIT}") +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/DoubleJumpCore.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/DoubleJumpCore.java new file mode 100644 index 0000000..dc008f6 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/DoubleJumpCore.java @@ -0,0 +1,60 @@ +package com.github.imdmk.doublejump.core; + +import com.github.imdmk.doublejump.core.injector.ComponentManager; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessors; +import com.github.imdmk.doublejump.core.injector.subscriber.LocalPublisher; +import com.github.imdmk.doublejump.core.injector.subscriber.Publisher; +import com.github.imdmk.doublejump.core.injector.subscriber.event.DoubleJumpInitializeEvent; +import com.github.imdmk.doublejump.core.injector.subscriber.event.DoubleJumpShutdownEvent; +import com.google.common.base.Stopwatch; +import org.bukkit.Server; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.panda_lang.utilities.inject.DependencyInjection; +import org.panda_lang.utilities.inject.Injector; + +import java.io.File; +import java.time.Clock; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +final class DoubleJumpCore { + + private static final String BASE_PACKAGE = "com.github.imdmk.doublejump"; + + private final Publisher publisher; + + DoubleJumpCore(Plugin plugin) { + Stopwatch stopwatch = Stopwatch.createStarted(); + Logger logger = plugin.getLogger(); + + Injector injector = DependencyInjection.createInjector(resources -> { + resources.on(Plugin.class).assignInstance(plugin); + resources.on(Server.class).assignInstance(plugin.getServer()); + resources.on(File.class).assignInstance(plugin.getDataFolder()); + resources.on(Logger.class).assignInstance(logger); + resources.on(BukkitScheduler.class).assignInstance(plugin.getServer().getScheduler()); + resources.on(Clock.class).assignInstance(Clock::systemDefaultZone); + }); + + injector.getResources().on(Injector.class).assignInstance(() -> injector); + + publisher = new LocalPublisher(injector); + + ComponentManager componentManager = new ComponentManager(injector, BASE_PACKAGE) + .addProcessors(ComponentProcessors.defaults()) + .addPostProcessor((instance, context) -> publisher.subscribe(instance)); + + componentManager.scanAll(); + componentManager.processAll(); + + publisher.publish(new DoubleJumpInitializeEvent()); + + long elapsedMillis = stopwatch.elapsed(TimeUnit.MILLISECONDS); + logger.info("Successfully loaded plugin in " + elapsedMillis + "ms!"); + } + + void disable() { + publisher.publish(new DoubleJumpShutdownEvent()); + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigAccessException.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigAccessException.java new file mode 100644 index 0000000..22cef77 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigAccessException.java @@ -0,0 +1,17 @@ +package com.github.imdmk.doublejump.core.config; + +public class ConfigAccessException extends RuntimeException { + + public ConfigAccessException(String message) { + super(message); + } + + public ConfigAccessException(String message, Throwable cause) { + super(message, cause); + } + + public ConfigAccessException(Throwable cause) { + super(cause); + } +} + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigConfigurer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigConfigurer.java new file mode 100644 index 0000000..1dced74 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigConfigurer.java @@ -0,0 +1,23 @@ +package com.github.imdmk.doublejump.core.config; + +import eu.okaeri.configs.serdes.OkaeriSerdesPack; +import eu.okaeri.configs.serdes.commons.SerdesCommons; +import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer; + +import java.io.File; + +final class ConfigConfigurer { + + void configure( + ConfigSection config, + File file, + OkaeriSerdesPack... serdesPacks + ) { + YamlSnakeYamlConfigurer configurer = new YamlSnakeYamlConfigurer(YamlFactory.create()); + + config.withConfigurer(configurer, serdesPacks) + .withSerdesPack(new SerdesCommons()) + .withBindFile(file) + .withRemoveOrphans(true); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigCreateException.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigCreateException.java new file mode 100644 index 0000000..f4e50d1 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigCreateException.java @@ -0,0 +1,16 @@ +package com.github.imdmk.doublejump.core.config; + +public class ConfigCreateException extends RuntimeException { + + public ConfigCreateException(String message) { + super(message); + } + + public ConfigCreateException(String message, Throwable cause) { + super(message, cause); + } + + public ConfigCreateException(Throwable cause) { + super(cause); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigFactory.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigFactory.java new file mode 100644 index 0000000..0b783e5 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigFactory.java @@ -0,0 +1,19 @@ +package com.github.imdmk.doublejump.core.config; + +import eu.okaeri.configs.ConfigManager; +import eu.okaeri.configs.exception.OkaeriException; + + +final class ConfigFactory { + + T instantiate(Class type) { + try { + return ConfigManager.create(type); + } catch (OkaeriException e) { + throw new ConfigCreateException( + "Failed to instantiate config: " + type.getName(), e + ); + } + } +} + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigLifecycle.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigLifecycle.java new file mode 100644 index 0000000..dc0dd38 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigLifecycle.java @@ -0,0 +1,28 @@ +package com.github.imdmk.doublejump.core.config; + +import eu.okaeri.configs.exception.OkaeriException; + +final class ConfigLifecycle { + + void initialize(ConfigSection config) { + config.saveDefaults(); + load(config); + } + + void load(ConfigSection config) { + try { + config.load(true); + } catch (OkaeriException e) { + throw new ConfigAccessException("Failed to load config: " + config.getClass().getSimpleName(), e); + } + } + + void save(ConfigSection config) { + try { + config.save(); + } catch (OkaeriException e) { + throw new ConfigAccessException("Failed to save config: " + config.getClass().getSimpleName(), e); + } + } +} + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigSection.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigSection.java new file mode 100644 index 0000000..6cd3891 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigSection.java @@ -0,0 +1,12 @@ +package com.github.imdmk.doublejump.core.config; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.serdes.OkaeriSerdesPack; + +public abstract class ConfigSection extends OkaeriConfig { + + public abstract OkaeriSerdesPack serdesPack(); + + public abstract String fileName(); + +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigService.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigService.java new file mode 100644 index 0000000..58d4c0e --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/ConfigService.java @@ -0,0 +1,85 @@ +package com.github.imdmk.doublejump.core.config; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.injector.subscriber.Subscribe; +import com.github.imdmk.doublejump.core.injector.subscriber.event.DoubleJumpShutdownEvent; +import org.jetbrains.annotations.Unmodifiable; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.io.File; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Service(priority = ComponentPriority.LOWEST, order = 0) +public final class ConfigService { + + private final Set configs = ConcurrentHashMap.newKeySet(); + private final Map, ConfigSection> byType = new ConcurrentHashMap<>(); + + private final File dataFolder; + + private final ConfigFactory factory; + private final ConfigConfigurer configurer; + private final ConfigLifecycle lifecycle; + + @Inject + public ConfigService(File dataFolder) { + this.dataFolder = dataFolder; + + this.factory = new ConfigFactory(); + this.configurer = new ConfigConfigurer(); + this.lifecycle = new ConfigLifecycle(); + } + + public C create(Class type) { + C config = factory.instantiate(type); + File file = new File(dataFolder, config.fileName()); + + configurer.configure(config, file, config.serdesPack()); + lifecycle.initialize(config); + + register(type, config); + return config; + } + + @SuppressWarnings("unchecked") + public C get(Class type) { + return (C) byType.get(type); + } + + public C require(Class type) { + C config = get(type); + if (config == null) { + throw new ConfigAccessException("Config not created: " + type.getName()); + } + + return config; + } + + public void loadAll() { + configs.forEach(lifecycle::load); + } + + public void saveAll() { + configs.forEach(lifecycle::save); + } + + @Unmodifiable + public Set getConfigs() { + return Collections.unmodifiableSet(configs); + } + + @Subscribe(event = DoubleJumpShutdownEvent.class) + private void shutdown() { + configs.clear(); + byType.clear(); + } + + private void register(Class type, ConfigSection config) { + configs.add(config); + byType.put(type, config); + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/YamlFactory.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/YamlFactory.java new file mode 100644 index 0000000..a6ae589 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/YamlFactory.java @@ -0,0 +1,33 @@ +package com.github.imdmk.doublejump.core.config; + +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.Constructor; +import org.yaml.snakeyaml.representer.Representer; +import org.yaml.snakeyaml.resolver.Resolver; + +final class YamlFactory { + + private YamlFactory() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated."); + } + + static Yaml create() { + LoaderOptions loader = new LoaderOptions(); + loader.setAllowRecursiveKeys(false); + loader.setMaxAliasesForCollections(50); + + Constructor constructor = new Constructor(loader); + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setIndent(2); + options.setSplitLines(false); + + Representer representer = new YamlRepresenter(options); + Resolver resolver = new Resolver(); + + return new Yaml(constructor, representer, options, loader, resolver); + } +} + diff --git a/doublejump-api/src/main/java/com/github/imdmk/doublejump/config/CustomRepresenter.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/YamlRepresenter.java similarity index 93% rename from doublejump-api/src/main/java/com/github/imdmk/doublejump/config/CustomRepresenter.java rename to doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/YamlRepresenter.java index 911083e..c0d97b9 100644 --- a/doublejump-api/src/main/java/com/github/imdmk/doublejump/config/CustomRepresenter.java +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/YamlRepresenter.java @@ -1,4 +1,4 @@ -package com.github.imdmk.doublejump.config; +package com.github.imdmk.doublejump.core.config; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.nodes.Node; @@ -11,9 +11,9 @@ import java.util.LinkedHashMap; import java.util.Map; -public final class CustomRepresenter extends Representer { +final class YamlRepresenter extends Representer { - public CustomRepresenter(DumperOptions options) { + YamlRepresenter(DumperOptions options) { super(options); this.representers.put(String.class, new RepresentString()); this.representers.put(Boolean.class, new RepresentBoolean()); diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/serializer/ComponentSerializer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/serializer/ComponentSerializer.java new file mode 100644 index 0000000..a85c939 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/serializer/ComponentSerializer.java @@ -0,0 +1,34 @@ +package com.github.imdmk.doublejump.core.config.serializer; + +import com.github.imdmk.doublejump.core.platform.adventure.AdventureComponents; +import eu.okaeri.configs.schema.GenericsDeclaration; +import eu.okaeri.configs.serdes.DeserializationData; +import eu.okaeri.configs.serdes.ObjectSerializer; +import eu.okaeri.configs.serdes.SerializationData; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; + +public final class ComponentSerializer implements ObjectSerializer { + + @Override + public boolean supports(@NotNull Class type) { + return Component.class.isAssignableFrom(type); + } + + @Override + public void serialize( + @NotNull Component component, + @NotNull SerializationData data, + @NotNull GenericsDeclaration generics + ) { + data.setValue(AdventureComponents.serialize(component), String.class); + } + + @Override + public Component deserialize( + @NotNull DeserializationData data, + @NotNull GenericsDeclaration generics + ) { + return AdventureComponents.text(data.getValue(String.class)); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/serializer/EnchantmentSerializer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/serializer/EnchantmentSerializer.java new file mode 100644 index 0000000..69cf676 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/serializer/EnchantmentSerializer.java @@ -0,0 +1,47 @@ +package com.github.imdmk.doublejump.core.config.serializer; + +import eu.okaeri.configs.schema.GenericsDeclaration; +import eu.okaeri.configs.serdes.DeserializationData; +import eu.okaeri.configs.serdes.ObjectSerializer; +import eu.okaeri.configs.serdes.SerializationData; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.enchantments.Enchantment; +import org.jetbrains.annotations.NotNull; + +public final class EnchantmentSerializer implements ObjectSerializer { + + @Override + public boolean supports(@NotNull Class type) { + return Enchantment.class.isAssignableFrom(type); + } + + @Override + public void serialize( + @NotNull Enchantment enchantment, + @NotNull SerializationData data, + @NotNull GenericsDeclaration generics + ) { + data.setValue(enchantment.getKey().getKey(), String.class); + } + + @Override + public Enchantment deserialize( + @NotNull DeserializationData data, + @NotNull GenericsDeclaration generics + ) { + String value = data.getValue(String.class); + + NamespacedKey key = NamespacedKey.fromString(value); + if (key == null) { + throw new IllegalArgumentException("Invalid enchantment key: " + value); + } + + Enchantment enchantment = Registry.ENCHANTMENT.get(key); + if (enchantment == null) { + throw new IllegalArgumentException("Unknown enchantment: " + value); + } + + return enchantment; + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/serializer/ParticleSerializer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/serializer/ParticleSerializer.java new file mode 100644 index 0000000..8acc04d --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/serializer/ParticleSerializer.java @@ -0,0 +1,50 @@ +package com.github.imdmk.doublejump.core.config.serializer; + +import eu.okaeri.configs.schema.GenericsDeclaration; +import eu.okaeri.configs.serdes.DeserializationData; +import eu.okaeri.configs.serdes.ObjectSerializer; +import eu.okaeri.configs.serdes.SerializationData; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.Registry; +import org.jetbrains.annotations.NotNull; + +public final class ParticleSerializer implements ObjectSerializer { + + @Override + public boolean supports(@NotNull Class type) { + return Particle.class.isAssignableFrom(type); + } + + @Override + public void serialize( + @NotNull Particle particle, + @NotNull SerializationData data, + @NotNull GenericsDeclaration generics + ) { + data.setValue(particle.getKey().getKey(), String.class); + } + + @Override + public Particle deserialize( + @NotNull DeserializationData data, + @NotNull GenericsDeclaration generics + ) { + String raw = data.getValue(String.class); + if (raw == null || raw.isBlank()) { + throw new IllegalArgumentException("Particle cannot be null or empty"); + } + + NamespacedKey key = NamespacedKey.fromString(raw); + if (key == null) { + throw new IllegalArgumentException("Invalid particle key: " + raw); + } + + Particle particle = Registry.PARTICLE_TYPE.get(key); + if (particle == null) { + throw new IllegalArgumentException("Unknown particle: " + raw); + } + + return particle; + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/serializer/SoundSerializer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/serializer/SoundSerializer.java new file mode 100644 index 0000000..8cde381 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/config/serializer/SoundSerializer.java @@ -0,0 +1,43 @@ +package com.github.imdmk.doublejump.core.config.serializer; + +import eu.okaeri.configs.schema.GenericsDeclaration; +import eu.okaeri.configs.serdes.DeserializationData; +import eu.okaeri.configs.serdes.ObjectSerializer; +import eu.okaeri.configs.serdes.SerializationData; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.Sound; +import org.jetbrains.annotations.NotNull; + +public final class SoundSerializer implements ObjectSerializer { + + @Override + public boolean supports(@NotNull Class type) { + return Sound.class.isAssignableFrom(type); + } + + @Override + public void serialize(Sound sound, SerializationData data, @NotNull GenericsDeclaration generics) { + data.setValue(sound.getKey().getKey(), String.class); + } + + @Override + public Sound deserialize(DeserializationData data, @NotNull GenericsDeclaration generics) { + String value = data.getValue(String.class); + if (value == null || value.isBlank()) { + throw new IllegalArgumentException("Sound cannot be null or empty"); + } + + NamespacedKey key = NamespacedKey.fromString(value); + if (key == null) { + throw new IllegalArgumentException("Invalid sound key: " + value); + } + + Sound sound = Registry.SOUNDS.get(key); + if (sound == null) { + throw new IllegalArgumentException("Unknown sound: " + value); + } + + return sound; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpActivationType.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpActivationType.java new file mode 100644 index 0000000..0bedf59 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpActivationType.java @@ -0,0 +1,13 @@ +package com.github.imdmk.doublejump.core.feature.jump; + +public enum JumpActivationType { + + COMMAND, + + JOIN, + + ITEM, + + BLOCK + +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpCommand.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpCommand.java new file mode 100644 index 0000000..b2f06fa --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpCommand.java @@ -0,0 +1,74 @@ +package com.github.imdmk.doublejump.core.feature.jump; + +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteCommand; +import com.github.imdmk.doublejump.core.message.MessageService; +import com.github.imdmk.doublejump.core.platform.flight.FlightService; +import dev.rollczi.litecommands.annotations.argument.Arg; +import dev.rollczi.litecommands.annotations.command.Command; +import dev.rollczi.litecommands.annotations.context.Context; +import dev.rollczi.litecommands.annotations.execute.Execute; +import dev.rollczi.litecommands.annotations.permission.Permission; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +@LiteCommand +@Command(name = "doublejump") +@Permission("command.doublejump") +final class JumpCommand { + + private final MessageService messageService; + private final FlightService flightService; + private final JumpPlayerRepository jumpRepository; + + @Inject + JumpCommand( + MessageService messageService, + FlightService flightService, + JumpPlayerRepository jumpRepository + ) { + this.messageService = messageService; + this.flightService = flightService; + this.jumpRepository = jumpRepository; + } + + @Execute(name = "enable") + void enable(@Context Player player) { + activateCommandJump(player); + } + + @Execute(name = "disable") + void disable(@Context Player player) { + deactivateCommandJump(player); + } + + @Execute(name = "enable-for") + @Permission("command.doublejump.target") + void enableFor(@Context CommandSender sender, @Arg Player target) { + activateCommandJump(target); + messageService.send(sender, n -> n.jumpMessages.enabledForTarget()); + } + + @Execute(name = "disable-for") + @Permission("command.doublejump.target") + void disableFor(@Context CommandSender sender, @Arg Player target) { + deactivateCommandJump(target); + messageService.send(sender, n -> n.jumpMessages.disabledForTarget()); + } + + private void activateCommandJump(Player target) { + JumpPlayer jumpPlayer = JumpPlayer.create(target.getUniqueId(), JumpActivationType.COMMAND); + + flightService.allowFlight(target); + jumpRepository.activate(jumpPlayer); + + messageService.send(target, n -> n.jumpMessages.enabled()); + } + + private void deactivateCommandJump(Player target) { + flightService.refreshFlightState(target); + jumpRepository.deactivate(target.getUniqueId()); + + messageService.send(target, n -> n.jumpMessages.disabled()); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpConfig.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpConfig.java new file mode 100644 index 0000000..b96eb21 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpConfig.java @@ -0,0 +1,143 @@ +package com.github.imdmk.doublejump.core.feature.jump; + +import com.github.imdmk.doublejump.core.config.ConfigSection; +import com.github.imdmk.doublejump.core.config.serializer.ParticleSerializer; +import com.github.imdmk.doublejump.core.config.serializer.SoundSerializer; +import com.github.imdmk.doublejump.core.feature.jump.effect.JumpEffect; +import com.github.imdmk.doublejump.core.feature.jump.effect.JumpEffectSerializer; +import com.github.imdmk.doublejump.core.feature.jump.effect.ParticleEffect; +import com.github.imdmk.doublejump.core.feature.jump.effect.SoundEffect; +import com.github.imdmk.doublejump.core.feature.jump.velocity.JumpVelocity; +import com.github.imdmk.doublejump.core.feature.jump.velocity.JumpVelocitySerializer; +import com.github.imdmk.doublejump.core.injector.annotations.ConfigFile; +import com.github.imdmk.doublejump.core.shared.permission.PermissionValueConfig; +import com.github.imdmk.doublejump.core.shared.permission.PermissionValueConfigSerializer; +import eu.okaeri.configs.annotation.Comment; +import eu.okaeri.configs.serdes.OkaeriSerdesPack; +import org.bukkit.GameMode; +import org.bukkit.Particle; +import org.bukkit.Sound; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@ConfigFile +public final class JumpConfig extends ConfigSection { + + @Comment({"#", "# Should the double jump fall damage be enabled?", "#"}) + public boolean enableFallDamage = false; + + @Comment({"#", "# Should the double jump mode be disabled after death?", "#"}) + public boolean disableAfterDeath = false; + + @Comment({ + "#", + "# Should players have double jump enabled on join?", + "# Permissions can override this value.", + "#" + }) + public PermissionValueConfig enableOnJoin = new PermissionValueConfig<>( + false, + Map.of("doublejump.vip", false) + ); + + @Comment({ + "#", + "# Controls jump velocity (horizontal and vertical boost).", + "# Can be overridden with permissions.", + "#" + }) + public PermissionValueConfig velocities = new PermissionValueConfig<>( + JumpVelocity.of(0.9, 0.35), + Map.of( + "doublejump.vip", JumpVelocity.of(1, 0.35) + ) + ); + + @Comment({ + "#", + "# Cooldown between jumps.", + "# Supports permission-based overrides.", + "#" + }) + public PermissionValueConfig cooldowns = new PermissionValueConfig<>( + Duration.ofSeconds(3), + Map.of( + "doublejump.vip", Duration.ofSeconds(2) + ) + ); + + @Comment({ + "#", + "# Visual effects played when player performs a double jump.", + "# Supports permission-based overrides.", + "# You can define multiple effects (particles, sounds, etc.).", + "#" + }) + public PermissionValueConfig> effects = new PermissionValueConfig<>( + List.of( + new ParticleEffect(Particle.CLOUD, 10, 0.3, 0.01), + new ParticleEffect(Particle.END_ROD, 5, 0.3, 0.01), + new SoundEffect(Sound.ENTITY_BREEZE_JUMP, 0.6f, 1.2f) + ), + Map.of( + "doublejump.vip", List.of( + new ParticleEffect(Particle.FLAME, 15, 0.3, 0.01), + new ParticleEffect(Particle.SOUL_FIRE_FLAME, 10, 0.3, 0.01), + new ParticleEffect(Particle.END_ROD, 10, 0.3, 0.01), + new SoundEffect(Sound.ENTITY_BLAZE_SHOOT, 0.8f, 1.0f) + ) + ) + ); + + @Comment({"#", "# Worlds where double jump is blocked.", "#"}) + public Set blockedWorlds = Set.of( + "example-world" + ); + + @Comment({"#", "# Regions where double jump is blocked.", "# Requires WorldGuard plugin.", "#"}) + public Set blockedRegions = Set.of( + "example-region" + ); + + @Comment({"#", "# Game modes where double jump is disabled.", "#"}) + public Set blockedGameModes = Set.of( + GameMode.CREATIVE, GameMode.ADVENTURE + ); + + @Comment({"#", "# Block double jump when player is in combat", "# Requires EternalCombat plugin.", "#"}) + public boolean blockInCombat = true; + + @Comment({"#", "# Block double jump while player is gliding (elytra).", "#"}) + public boolean blockWhileGliding = true; + + @Comment({"#", "# Block double jump when player is lagging.", "#"}) + public boolean blockWhenLagging = true; + + @Comment({"#", "# Block double jump while player is in fluid (water/lava).", "#"}) + public boolean blockInFluid = true; + + @Comment({"#", "# Block double jump while player is inside a vehicle.", "#"}) + public boolean blockInVehicle = true; + + @Override + public OkaeriSerdesPack serdesPack() { + return registry -> { + registry.register(new JumpVelocitySerializer()); + registry.register(new JumpEffectSerializer()); + registry.register(new SoundSerializer()); + registry.register(new ParticleSerializer()); + + registry.register(new PermissionValueConfigSerializer()); + registry.register(new PermissionValueConfigSerializer()); + registry.register(new PermissionValueConfigSerializer>()); + }; + } + + @Override + public String fileName() { + return "jumpConfig.yml"; + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpContext.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpContext.java new file mode 100644 index 0000000..f281181 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpContext.java @@ -0,0 +1,6 @@ +package com.github.imdmk.doublejump.core.feature.jump; + +import org.bukkit.entity.Player; + +public record JumpContext(Player player, JumpPlayer jumpPlayer) { +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpEvent.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpEvent.java new file mode 100644 index 0000000..39d7537 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpEvent.java @@ -0,0 +1,55 @@ +package com.github.imdmk.doublejump.core.feature.jump; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public final class JumpEvent extends Event implements Cancellable { + + private static final HandlerList HANDLERS = new HandlerList(); + + private final Player player; + private final JumpPlayer jumpPlayer; + + private boolean cancelled; + + public JumpEvent( + @NotNull Player player, + @NotNull JumpPlayer jumpPlayer + ) { + this.player = player; + this.jumpPlayer = jumpPlayer; + } + + @NotNull + public Player getPlayer() { + return player; + } + + @NotNull + public JumpPlayer getJumpPlayer() { + return jumpPlayer; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @Override + @NotNull + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpExecutor.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpExecutor.java new file mode 100644 index 0000000..6283348 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpExecutor.java @@ -0,0 +1,52 @@ +package com.github.imdmk.doublejump.core.feature.jump; + +import com.github.imdmk.doublejump.core.feature.jump.message.JumpMessageService; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpValidator; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.platform.event.EventCaller; +import com.github.imdmk.doublejump.core.platform.flight.FlightService; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +@Service +public final class JumpExecutor { + + private final FlightService flightService; + private final JumpValidator jumpValidator; + private final JumpMessageService jumpMessageService; + private final EventCaller eventCaller; + + @Inject + JumpExecutor( + FlightService flightService, + JumpValidator jumpValidator, + JumpMessageService jumpMessageService, + EventCaller eventCaller + ) { + this.jumpValidator = jumpValidator; + this.flightService = flightService; + this.jumpMessageService = jumpMessageService; + this.eventCaller = eventCaller; + } + + public JumpResult execute(Player player, JumpPlayer jumpPlayer) { + JumpContext context = new JumpContext(player, jumpPlayer); + JumpResult result = jumpValidator.validate(context); + + if (result != JumpResult.ALLOWED) { + jumpMessageService.notify(player, result); + return result; + } + + JumpEvent event = eventCaller.callEvent(new JumpEvent(player, jumpPlayer)); + if (event.isCancelled()) { + return JumpResult.CANCELLED; + } + + flightService.stopFlying(player); + flightService.disallowFlight(player); + + return JumpResult.ALLOWED; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpFlightUpdateTask.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpFlightUpdateTask.java new file mode 100644 index 0000000..df79031 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpFlightUpdateTask.java @@ -0,0 +1,73 @@ +package com.github.imdmk.doublejump.core.feature.jump; + +import com.github.imdmk.doublejump.core.feature.jump.cooldown.JumpCooldownService; +import com.github.imdmk.doublejump.core.feature.jump.message.JumpMessageService; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.injector.annotations.Task; +import com.github.imdmk.doublejump.core.platform.flight.FlightService; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.UUID; + +@Task(delayMillis = 0, periodMillis = 100, async = false) +final class JumpFlightUpdateTask implements Runnable { + + private final Server server; + private final JumpConfig config; + private final FlightService flightService; + private final JumpCooldownService cooldownService; + private final JumpPlayerRepository jumpRepository; + private final JumpMessageService jumpMessageService; + + @Inject + JumpFlightUpdateTask( + Server server, + JumpConfig config, + FlightService flightService, + JumpCooldownService cooldownService, + JumpPlayerRepository jumpRepository, + JumpMessageService jumpMessageService + ) { + this.server = server; + this.config = config; + this.flightService = flightService; + this.cooldownService = cooldownService; + this.jumpRepository = jumpRepository; + this.jumpMessageService = jumpMessageService; + } + + @Override + public void run() { + for (JumpPlayer jumpPlayer : jumpRepository.getActivePlayers()) { + UUID playerId = jumpPlayer.getUuid(); + + Player player = server.getPlayer(playerId); + if (player == null) { + jumpRepository.deactivate(playerId); + continue; + } + + if (cooldownService.isOnCooldown(playerId)) { + jumpMessageService.notify(player, JumpResult.COOLDOWN); + continue; + } + + if (shouldRestoreFlight(player)) { + jumpMessageService.notify(player, JumpResult.ALLOWED); + flightService.allowFlight(player); + } + } + } + + private boolean shouldRestoreFlight(Player player) { + if (config.enableFallDamage + && player.getFallDistance() >= 4.0f + && player.getVelocity().getY() < 0) { + return false; + } + + return !player.getAllowFlight(); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpPlayer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpPlayer.java new file mode 100644 index 0000000..0a19347 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpPlayer.java @@ -0,0 +1,41 @@ +package com.github.imdmk.doublejump.core.feature.jump; + +import java.util.UUID; + +public final class JumpPlayer { + + private final UUID uuid; + private final JumpActivationType activationType; + + private JumpPlayer( + UUID uuid, + JumpActivationType activationType + ) { + this.uuid = uuid; + this.activationType = activationType; + } + + public static JumpPlayer create(UUID uuid, JumpActivationType type) { + return new JumpPlayer(uuid, type); + } + + public UUID getUuid() { + return uuid; + } + + public JumpActivationType getActivationType() { + return activationType; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + JumpPlayer that = (JumpPlayer) o; + return uuid.equals(that.uuid); + } + + @Override + public int hashCode() { + return uuid.hashCode(); + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpPlayerRepository.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpPlayerRepository.java new file mode 100644 index 0000000..ce0a4c5 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/JumpPlayerRepository.java @@ -0,0 +1,44 @@ +package com.github.imdmk.doublejump.core.feature.jump; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; + +import java.util.Collection; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Service(priority = ComponentPriority.LOWEST) +public final class JumpPlayerRepository { + + private final Map activePlayers = new ConcurrentHashMap<>(); + + public void activate(JumpPlayer player) { + activePlayers.put(player.getUuid(), player); + } + + public JumpPlayer deactivate(UUID uuid) { + return activePlayers.remove(uuid); + } + + public boolean isActive(UUID uuid) { + return activePlayers.containsKey(uuid); + } + + public JumpPlayer get(UUID uuid) { + return activePlayers.get(uuid); + } + + public JumpPlayer getOrThrow(UUID uuid) { + JumpPlayer player = activePlayers.get(uuid); + if (player == null) { + throw new IllegalStateException("JumpPlayer not active: " + uuid); + } + + return player; + } + + public Collection getActivePlayers() { + return activePlayers.values(); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/cooldown/JumpCooldownListener.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/cooldown/JumpCooldownListener.java new file mode 100644 index 0000000..4d12f2b --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/cooldown/JumpCooldownListener.java @@ -0,0 +1,32 @@ +package com.github.imdmk.doublejump.core.feature.jump.cooldown; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpEvent; +import com.github.imdmk.doublejump.core.injector.annotations.PluginListener; +import com.github.imdmk.doublejump.core.shared.permission.PermissionBasedValueProvider; +import com.github.imdmk.doublejump.core.shared.permission.PlayerValueProvider; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.time.Duration; + +@PluginListener +final class JumpCooldownListener implements Listener { + + private final JumpCooldownService cooldownService; + private final PlayerValueProvider cooldownProvider; + + @Inject + JumpCooldownListener(JumpCooldownService cooldownService, JumpConfig config) { + this.cooldownService = cooldownService; + this.cooldownProvider = new PermissionBasedValueProvider<>(config.cooldowns); + } + + @EventHandler(ignoreCancelled = true) + void onJump(JumpEvent event) { + Player player = event.getPlayer(); + cooldownService.applyCooldown(player, cooldownProvider.resolve(player)); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/cooldown/JumpCooldownService.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/cooldown/JumpCooldownService.java new file mode 100644 index 0000000..05b8421 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/cooldown/JumpCooldownService.java @@ -0,0 +1,55 @@ +package com.github.imdmk.doublejump.core.feature.jump.cooldown; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.injector.subscriber.Subscribe; +import com.github.imdmk.doublejump.core.injector.subscriber.event.DoubleJumpShutdownEvent; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.time.Clock; +import java.time.Duration; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Service(priority = ComponentPriority.LOWEST) +public final class JumpCooldownService { + + private final Map cooldowns = new ConcurrentHashMap<>(); + private final Clock clock; + + @Inject + JumpCooldownService(Clock clock) { + this.clock = clock; + } + + public void applyCooldown(Player player, Duration cooldown) { + if (cooldown.isZero() || cooldown.isNegative()) { + return; + } + + long now = clock.millis(); + cooldowns.put(player.getUniqueId(), now + cooldown.toMillis()); + } + + public long getRemainingMillis(UUID playerId) { + long now = clock.millis(); + long cooldownUntil = cooldowns.getOrDefault(playerId, 0L); + + return Math.max(0, cooldownUntil - now); + } + + public boolean isOnCooldown(UUID playerId) { + return getRemainingMillis(playerId) > 0; + } + + public void reset(UUID playerId) { + cooldowns.remove(playerId); + } + + @Subscribe(event = DoubleJumpShutdownEvent.class) + private void shutdown() { + cooldowns.clear(); + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffect.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffect.java new file mode 100644 index 0000000..bdab872 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffect.java @@ -0,0 +1,11 @@ +package com.github.imdmk.doublejump.core.feature.jump.effect; + +import org.bukkit.entity.Player; + +public interface JumpEffect { + + void apply(Player player); + + JumpEffectType type(); + +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffectListener.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffectListener.java new file mode 100644 index 0000000..242da20 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffectListener.java @@ -0,0 +1,24 @@ +package com.github.imdmk.doublejump.core.feature.jump.effect; + +import com.github.imdmk.doublejump.core.feature.jump.JumpEvent; +import com.github.imdmk.doublejump.core.injector.annotations.PluginListener; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.panda_lang.utilities.inject.annotations.Inject; + +@PluginListener +final class JumpEffectListener implements Listener { + + private final JumpEffectService effectService; + + @Inject + JumpEffectListener(JumpEffectService effectService) { + this.effectService = effectService; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + void onJump(JumpEvent event) { + effectService.apply(event.getPlayer()); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffectSerializer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffectSerializer.java new file mode 100644 index 0000000..e242d5e --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffectSerializer.java @@ -0,0 +1,71 @@ +package com.github.imdmk.doublejump.core.feature.jump.effect; + +import eu.okaeri.configs.schema.GenericsDeclaration; +import eu.okaeri.configs.serdes.DeserializationData; +import eu.okaeri.configs.serdes.ObjectSerializer; +import eu.okaeri.configs.serdes.SerializationData; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.jetbrains.annotations.NotNull; + +public final class JumpEffectSerializer implements ObjectSerializer { + + @Override + public boolean supports(@NotNull Class type) { + return JumpEffect.class.isAssignableFrom(type); + } + + @Override + public void serialize( + @NotNull JumpEffect effect, + @NotNull SerializationData data, + @NotNull GenericsDeclaration generics + ) { + data.add("type", effect.type(), JumpEffectType.class); + + if (effect instanceof ParticleEffect(Particle particle, int count, double offset, double extra)) { + data.add("particle", particle, Particle.class); + data.add("count", count, int.class); + data.add("offset", offset, double.class); + data.add("extra", extra, double.class); + } + else if (effect instanceof SoundEffect(Sound sound, float volume, float pitch)) { + data.add("sound", sound, Sound.class); + data.add("volume", volume, float.class); + data.add("pitch", pitch, float.class); + } + else { + throw new IllegalArgumentException("Unknown JumpEffect: " + effect.getClass().getName()); + } + } + + @Override + public JumpEffect deserialize( + @NotNull DeserializationData data, + @NotNull GenericsDeclaration generics + ) { + JumpEffectType type = data.get("type", JumpEffectType.class); + + if (type == null) { + throw new IllegalArgumentException("Missing 'type' in JumpEffect config"); + } + + if (type == JumpEffectType.PARTICLE) { + Particle particle = data.get("particle", Particle.class); + int count = data.get("count", int.class); + double offset = data.get("offset", double.class); + double extra = data.get("extra", double.class); + + return new ParticleEffect(particle, count, offset, extra); + } + else if (type == JumpEffectType.SOUND) { + Sound sound = data.get("sound", Sound.class); + float volume = data.get("volume", float.class); + float pitch = data.get("pitch", float.class); + + return new SoundEffect(sound, volume, pitch); + } + + throw new IllegalStateException("Unsupported JumpEffectType: " + type); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffectService.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffectService.java new file mode 100644 index 0000000..ba40ffe --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffectService.java @@ -0,0 +1,28 @@ +package com.github.imdmk.doublejump.core.feature.jump.effect; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.shared.permission.PermissionBasedValueProvider; +import com.github.imdmk.doublejump.core.shared.permission.PlayerValueProvider; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.List; + +@Service(priority = ComponentPriority.LOW) +public final class JumpEffectService { + + private final PlayerValueProvider> effectsProvider; + + @Inject + JumpEffectService(JumpConfig config) { + this.effectsProvider = new PermissionBasedValueProvider<>(config.effects); + } + + public void apply(Player player) { + for (JumpEffect effect : effectsProvider.resolve(player)) { + effect.apply(player); + } + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffectType.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffectType.java new file mode 100644 index 0000000..c168467 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/JumpEffectType.java @@ -0,0 +1,6 @@ +package com.github.imdmk.doublejump.core.feature.jump.effect; + +public enum JumpEffectType { + PARTICLE, + SOUND +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/ParticleEffect.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/ParticleEffect.java new file mode 100644 index 0000000..5d1d5e2 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/ParticleEffect.java @@ -0,0 +1,24 @@ +package com.github.imdmk.doublejump.core.feature.jump.effect; + +import org.bukkit.Particle; +import org.bukkit.entity.Player; + +public record ParticleEffect(Particle particle, int count, double offset, double extra) + implements JumpEffect { + + @Override + public void apply(Player player) { + player.spawnParticle( + particle, + player.getLocation(), + count, + offset, offset, offset, + extra + ); + } + + @Override + public JumpEffectType type() { + return JumpEffectType.PARTICLE; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/SoundEffect.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/SoundEffect.java new file mode 100644 index 0000000..2625fd1 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/effect/SoundEffect.java @@ -0,0 +1,18 @@ +package com.github.imdmk.doublejump.core.feature.jump.effect; + +import org.bukkit.Sound; +import org.bukkit.entity.Player; + +public record SoundEffect(Sound sound, float volume, float pitch) + implements JumpEffect { + + @Override + public void apply(Player player) { + player.playSound(player.getLocation(), sound, volume, pitch); + } + + @Override + public JumpEffectType type() { + return JumpEffectType.SOUND; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/falldamage/JumpFallDamageProtectionListener.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/falldamage/JumpFallDamageProtectionListener.java new file mode 100644 index 0000000..c2afe64 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/falldamage/JumpFallDamageProtectionListener.java @@ -0,0 +1,48 @@ +package com.github.imdmk.doublejump.core.feature.jump.falldamage; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpPlayerRepository; +import com.github.imdmk.doublejump.core.injector.annotations.PluginListener; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; +import org.panda_lang.utilities.inject.annotations.Inject; + +@PluginListener +final class JumpFallDamageProtectionListener implements Listener { + + private final JumpConfig config; + private final JumpPlayerRepository jumpRepository; + + @Inject + JumpFallDamageProtectionListener( + JumpConfig config, + JumpPlayerRepository jumpRepository + ) { + this.config = config; + this.jumpRepository = jumpRepository; + } + + @EventHandler(ignoreCancelled = true) + void onDamage(EntityDamageEvent event) { + if (config.enableFallDamage) { + return; + } + + if (event.getCause() != EntityDamageEvent.DamageCause.FALL) { + return; + } + + if (!(event.getEntity() instanceof Player player)) { + return; + } + + if (!jumpRepository.isActive(player.getUniqueId())) { + return; + } + + event.setCancelled(true); + event.setDamage(0); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/falldamage/JumpFallFlightDisableListener.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/falldamage/JumpFallFlightDisableListener.java new file mode 100644 index 0000000..6a478ee --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/falldamage/JumpFallFlightDisableListener.java @@ -0,0 +1,64 @@ +package com.github.imdmk.doublejump.core.feature.jump.falldamage; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpPlayer; +import com.github.imdmk.doublejump.core.feature.jump.JumpPlayerRepository; +import com.github.imdmk.doublejump.core.injector.annotations.PluginListener; +import com.github.imdmk.doublejump.core.platform.flight.FlightService; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; +import org.panda_lang.utilities.inject.annotations.Inject; + +@PluginListener +final class JumpFallFlightDisableListener implements Listener { + + private final JumpConfig config; + private final JumpPlayerRepository jumpRepository; + private final FlightService flightService; + + @Inject + JumpFallFlightDisableListener( + JumpConfig config, + JumpPlayerRepository jumpRepository, + FlightService flightService + ) { + this.config = config; + this.jumpRepository = jumpRepository; + this.flightService = flightService; + } + + @EventHandler(ignoreCancelled = true) + void onMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); + + if (event.getTo() != null && + event.getFrom().getX() == event.getTo().getX() && + event.getFrom().getY() == event.getTo().getY() && + event.getFrom().getZ() == event.getTo().getZ()) { + return; + } + + if (!config.enableFallDamage) { + return; + } + + JumpPlayer jumpPlayer = jumpRepository.get(player.getUniqueId()); + if (jumpPlayer == null) { + return; + } + + if (player.isFlying() || player.isGliding()) { + return; + } + + if (player.getFallDistance() >= 4.0f) { + if (!player.getAllowFlight()) { + return; + } + + flightService.disallowFlight(player); + } + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItem.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItem.java new file mode 100644 index 0000000..0386f8a --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItem.java @@ -0,0 +1,31 @@ +package com.github.imdmk.doublejump.core.feature.jump.item; + +import com.github.imdmk.doublejump.core.feature.jump.velocity.JumpVelocity; +import dev.triumphteam.gui.builder.item.ItemBuilder; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.Map; + +public record JumpItem( + Material material, + Component name, + List lore, + JumpVelocity velocity, + List flags, + Map enchantments +) { + + public ItemStack toItemStack() { + return ItemBuilder.from(material) + .name(name) + .lore(lore == null ? List.of() : lore) + .enchant(enchantments == null ? Map.of() : enchantments) + .flags((flags == null ? List.of() : flags).toArray(new ItemFlag[0])) + .build(); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItemCommand.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItemCommand.java new file mode 100644 index 0000000..c9ed96c --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItemCommand.java @@ -0,0 +1,54 @@ +package com.github.imdmk.doublejump.core.feature.jump.item; + +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteCommand; +import com.github.imdmk.doublejump.core.message.MessageService; +import dev.rollczi.litecommands.annotations.argument.Arg; +import dev.rollczi.litecommands.annotations.command.Command; +import dev.rollczi.litecommands.annotations.context.Context; +import dev.rollczi.litecommands.annotations.execute.Execute; +import dev.rollczi.litecommands.annotations.permission.Permission; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +@LiteCommand +@Command(name = "doublejump item") +@Permission("command.doublejump.item") +final class JumpItemCommand { + + private final MessageService messageService; + private final JumpItemService itemService; + + @Inject + JumpItemCommand( + MessageService messageService, + JumpItemService itemService + ) { + this.messageService = messageService; + this.itemService = itemService; + } + + @Execute(name = "give") + void giveItem(@Context CommandSender sender, @Arg Player target) { + target.getInventory().addItem(itemService.getItem().toItemStack()); + + messageService.send(target, n -> n.jumpMessages.itemGive()); + messageService.create() + .viewer(sender) + .notice(n -> n.jumpMessages.itemGiveToTarget()) + .placeholder("{PLAYER}", target.getName()) + .send(); + } + + @Execute(name = "remove") + void removeItem(@Context CommandSender sender, @Arg Player target) { + target.getInventory().remove(itemService.getItem().toItemStack()); + + messageService.send(target, n -> n.jumpMessages.itemRemove()); + messageService.create() + .viewer(sender) + .notice(n -> n.jumpMessages.itemRemoveFromTarget()) + .placeholder("{PLAYER}", target.getName()) + .send(); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItemMatcher.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItemMatcher.java new file mode 100644 index 0000000..bb9f2b0 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItemMatcher.java @@ -0,0 +1,49 @@ +package com.github.imdmk.doublejump.core.feature.jump.item; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.Objects; + +public final class JumpItemMatcher { + + private final ItemStack referenceItem; + + public JumpItemMatcher(JumpItem jumpItem) { + this.referenceItem = jumpItem.toItemStack(); + } + + public boolean matches(ItemStack comparedItem) { + if (comparedItem == null) { + return false; + } + + if (referenceItem.getType() != comparedItem.getType()) { + return false; + } + + ItemMeta referenceMeta = referenceItem.getItemMeta(); + ItemMeta comparedMeta = comparedItem.getItemMeta(); + if (referenceMeta == null || comparedMeta == null) { + return referenceMeta == comparedMeta; + } + + return compareMeta(referenceMeta, comparedMeta); + } + + private boolean compareMeta(ItemMeta reference, ItemMeta compared) { + if (!Objects.equals(reference.getDisplayName(), compared.getDisplayName())) { + return false; + } + + if (!Objects.equals(reference.getLore(), compared.getLore())) { + return false; + } + + if (!reference.getEnchants().equals(compared.getEnchants())) { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItemService.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItemService.java new file mode 100644 index 0000000..f463454 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItemService.java @@ -0,0 +1,56 @@ +package com.github.imdmk.doublejump.core.feature.jump.item; + +import com.github.imdmk.doublejump.core.feature.jump.item.config.JumpItemConfig; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.Arrays; + +@Service +public final class JumpItemService { + + private final JumpItemConfig config; + private final JumpItemMatcher matcher; + + @Inject + JumpItemService(JumpItemConfig config) { + this.config = config; + this.matcher = new JumpItemMatcher(config.item); + } + + public boolean isEnabled() { + return config.enabled; + } + + public boolean isJumpItem(ItemStack item) { + return matcher.matches(item); + } + + public boolean isClickMode() { + return config.usage == JumpItemUsage.CLICK_ITEM; + } + + public JumpItem getItem() { + return config.item; + } + + public boolean isUsingItem(Player player) { + PlayerInventory inventory = player.getInventory(); + + return switch (config.usage) { + case HOLD_ITEM -> isJumpItem(inventory.getItemInMainHand()) + || isJumpItem(inventory.getItemInOffHand()); + case WEAR_ITEM -> isJumpItem(inventory.getHelmet()) + || isJumpItem(inventory.getChestplate()) + || isJumpItem(inventory.getLeggings()) + || isJumpItem(inventory.getBoots()); + case HAVE_ITEM -> Arrays.stream(inventory.getContents()) + .anyMatch(this::isJumpItem); + + case CLICK_ITEM -> false; // handled in interact + }; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItemUsage.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItemUsage.java new file mode 100644 index 0000000..a6f9429 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/JumpItemUsage.java @@ -0,0 +1,12 @@ +package com.github.imdmk.doublejump.core.feature.jump.item; + +public enum JumpItemUsage { + + HAVE_ITEM, + + HOLD_ITEM, + + CLICK_ITEM, + + WEAR_ITEM +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/config/JumpItemConfig.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/config/JumpItemConfig.java new file mode 100644 index 0000000..080d098 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/config/JumpItemConfig.java @@ -0,0 +1,93 @@ +package com.github.imdmk.doublejump.core.feature.jump.item.config; + +import com.github.imdmk.doublejump.core.config.ConfigSection; +import com.github.imdmk.doublejump.core.config.serializer.ComponentSerializer; +import com.github.imdmk.doublejump.core.config.serializer.EnchantmentSerializer; +import com.github.imdmk.doublejump.core.feature.jump.item.JumpItem; +import com.github.imdmk.doublejump.core.feature.jump.item.JumpItemUsage; +import com.github.imdmk.doublejump.core.feature.jump.velocity.JumpVelocity; +import com.github.imdmk.doublejump.core.feature.jump.velocity.JumpVelocitySerializer; +import com.github.imdmk.doublejump.core.injector.annotations.ConfigFile; +import com.github.imdmk.doublejump.core.platform.adventure.AdventureComponents; +import eu.okaeri.configs.annotation.Comment; +import eu.okaeri.configs.serdes.OkaeriSerdesPack; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; + +import java.util.List; +import java.util.Map; + +@ConfigFile +public final class JumpItemConfig extends ConfigSection { + + @Comment({"#", "# Enable or disable the double jump item system", "#"}) + public boolean enabled = true; + + @Comment({ + "#", + "# How the item activates double jump", + "# CLICK_ITEM - use (click) the item", + "# HOLD_ITEM - holding/wearing enables jump", + "# WEAR_ITEM - wearing enables jump", + "# HOLD_ITEM - holding enabled jump", + "#" + }) + public JumpItemUsage usage = JumpItemUsage.WEAR_ITEM; + + @Comment({ + "#", + "# Main item configuration", + "# Customize material, name, lore, velocity and more", + "#" + }) + public JumpItem item = new JumpItem( + Material.DIAMOND_BOOTS, + AdventureComponents.notItalic("DOUBLEJUMP ITEM"), + AdventureComponents.notItalic( + " ", + "WEAR to activate double jump!", + " " + ), + JumpVelocity.of(0.9, 0.35), + List.of(ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ATTRIBUTES), + Map.of(Enchantment.UNBREAKING, 3) + ); + + @Comment({ + "#", + "# Prevent repairing the item (anvil, grindstone, etc.)", + "#" + }) + public boolean blockRepairing = true; + + @Comment({ + "#", + "# Prevent dropping the item", + "#" + }) + public boolean blockDrop = false; + + @Comment({ + "#", + "# Prevent enchanting the item", + "#" + }) + public boolean blockEnchanting = true; + + @Override + public OkaeriSerdesPack serdesPack() { + return registry -> { + registry.register(new ComponentSerializer()); + registry.register(new EnchantmentSerializer()); + + registry.register(new JumpItemSerializer()); + registry.register(new JumpVelocitySerializer()); + }; + } + + @Override + public String fileName() { + return "jumpItemConfig.yml"; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/config/JumpItemSerializer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/config/JumpItemSerializer.java new file mode 100644 index 0000000..a3f64d5 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/config/JumpItemSerializer.java @@ -0,0 +1,60 @@ +package com.github.imdmk.doublejump.core.feature.jump.item.config; + +import com.github.imdmk.doublejump.core.feature.jump.item.JumpItem; +import com.github.imdmk.doublejump.core.feature.jump.velocity.JumpVelocity; +import eu.okaeri.configs.schema.GenericsDeclaration; +import eu.okaeri.configs.serdes.DeserializationData; +import eu.okaeri.configs.serdes.ObjectSerializer; +import eu.okaeri.configs.serdes.SerializationData; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; + +final class JumpItemSerializer implements ObjectSerializer { + + @Override + public boolean supports(@NotNull Class type) { + return JumpItem.class.isAssignableFrom(type); + } + + @Override + public void serialize( + @NotNull JumpItem item, + @NotNull SerializationData data, + @NotNull GenericsDeclaration generics + ) { + data.add("material", item.material(), Material.class); + data.add("name", item.name(), Component.class); + data.addCollection("lore", item.lore(), Component.class); + data.add("velocity", item.velocity(), JumpVelocity.class); + data.addCollection("flags", item.flags(), ItemFlag.class); + data.addAsMap("enchantments", item.enchantments(), Enchantment.class, Integer.class); + } + + @Override + public JumpItem deserialize( + @NotNull DeserializationData data, + @NotNull GenericsDeclaration generics + ) { + Material material = data.get("material", Material.class); + Component name = data.get("name", Component.class); + List lore = data.getAsList("lore", Component.class); + JumpVelocity velocity = data.get("velocity", JumpVelocity.class); + List flags = data.getAsList("flags", ItemFlag.class); + Map enchantments = data.getAsMap("enchantments", Enchantment.class, Integer.class); + + return new JumpItem( + material, + name, + lore, + velocity, + flags, + enchantments + ); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/listener/JumpItemBlockListener.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/listener/JumpItemBlockListener.java new file mode 100644 index 0000000..02d806b --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/listener/JumpItemBlockListener.java @@ -0,0 +1,78 @@ +package com.github.imdmk.doublejump.core.feature.jump.item.listener; + +import com.github.imdmk.doublejump.core.feature.jump.item.JumpItemService; +import com.github.imdmk.doublejump.core.feature.jump.item.config.JumpItemConfig; +import com.github.imdmk.doublejump.core.injector.annotations.PluginListener; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.enchantment.EnchantItemEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.panda_lang.utilities.inject.annotations.Inject; + +@PluginListener +final class JumpItemBlockListener implements Listener { + + private static final int ANVIL_REPAIR_SLOT = 2; + + private final JumpItemConfig config; + private final JumpItemService itemService; + + @Inject + JumpItemBlockListener( + JumpItemConfig config, + JumpItemService itemService + ) { + this.config = config; + this.itemService = itemService; + } + + @EventHandler(ignoreCancelled = true) + void onEnchant(EnchantItemEvent event) { + if (!config.blockEnchanting) { + return; + } + + if (itemService.isJumpItem(event.getItem())) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true) + void onAnvilRepair(InventoryClickEvent event) { + if (!config.blockRepairing) { + return; + } + + Inventory inventory = event.getClickedInventory(); + if (inventory == null || inventory.getType() != InventoryType.ANVIL) { + return; + } + + if (event.getSlot() != ANVIL_REPAIR_SLOT) { + return; + } + + ItemStack repair = inventory.getItem(0); + if (itemService.isJumpItem(repair)) { + event.setCancelled(true); + event.setResult(Event.Result.DENY); + } + } + + @EventHandler(ignoreCancelled = true) + void onItemDrop(PlayerDropItemEvent event) { + if (!config.blockDrop) { + return; + } + + ItemStack drop = event.getItemDrop().getItemStack(); + if (itemService.isJumpItem(drop)) { + event.setCancelled(true); + } + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/listener/JumpItemListener.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/listener/JumpItemListener.java new file mode 100644 index 0000000..d7c58c6 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/item/listener/JumpItemListener.java @@ -0,0 +1,104 @@ +package com.github.imdmk.doublejump.core.feature.jump.item.listener; + +import com.github.imdmk.doublejump.core.feature.jump.JumpActivationType; +import com.github.imdmk.doublejump.core.feature.jump.JumpExecutor; +import com.github.imdmk.doublejump.core.feature.jump.JumpPlayer; +import com.github.imdmk.doublejump.core.feature.jump.JumpPlayerRepository; +import com.github.imdmk.doublejump.core.feature.jump.cooldown.JumpCooldownService; +import com.github.imdmk.doublejump.core.feature.jump.item.JumpItemService; +import com.github.imdmk.doublejump.core.feature.jump.item.config.JumpItemConfig; +import com.github.imdmk.doublejump.core.injector.annotations.PluginListener; +import com.github.imdmk.doublejump.core.message.MessageService; +import com.github.imdmk.doublejump.core.platform.flight.FlightService; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.UUID; + +@PluginListener +final class JumpItemListener implements Listener { + + private final JumpItemConfig config; + private final FlightService flightService; + private final JumpCooldownService cooldownService; + private final JumpPlayerRepository jumpRepository; + private final MessageService messageService; + private final JumpExecutor jumpExecutor; + private final JumpItemService itemService; + + @Inject + JumpItemListener( + JumpItemConfig config, + FlightService flightService, + JumpCooldownService cooldownService, + JumpPlayerRepository jumpRepository, + MessageService messageService, + JumpExecutor jumpExecutor, + JumpItemService itemService + ) { + this.config = config; + this.flightService = flightService; + this.cooldownService = cooldownService; + this.messageService = messageService; + this.jumpExecutor = jumpExecutor; + this.jumpRepository = jumpRepository; + this.itemService = itemService; + } + + @EventHandler(ignoreCancelled = true) + void onMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); + UUID playerId = player.getUniqueId(); + + if (!itemService.isEnabled() && !itemService.isClickMode()) { + return; + } + + JumpPlayer jumpPlayer = jumpRepository.get(playerId); + boolean isActive = jumpPlayer != null; + boolean isUsing = itemService.isUsingItem(player); + + if (isActive && jumpPlayer.getActivationType() != JumpActivationType.ITEM) { + return; + } + + boolean isCooldown = cooldownService.isOnCooldown(playerId); + if (!isActive && isUsing && !isCooldown) { + JumpPlayer created = JumpPlayer.create(playerId, JumpActivationType.ITEM); + + flightService.allowFlight(player); + jumpRepository.activate(created); + + messageService.send(player, n -> n.jumpMessages.enabled()); + return; + } + + if (isActive && !isUsing) { + flightService.refreshFlightState(player); + jumpRepository.deactivate(playerId); + + messageService.send(player, n -> n.jumpMessages.disabled()); + } + } + + @EventHandler(ignoreCancelled = true) + void onClick(PlayerInteractEvent event) { + Player player = event.getPlayer(); + UUID playerId = player.getUniqueId(); + + if (!itemService.isEnabled() || !itemService.isClickMode()) { + return; + } + + if (!itemService.isJumpItem(event.getItem())) { + return; + } + + JumpPlayer jumpPlayer = JumpPlayer.create(playerId, JumpActivationType.ITEM); + jumpExecutor.execute(player, jumpPlayer); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/listener/JumpPerformListener.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/listener/JumpPerformListener.java new file mode 100644 index 0000000..32b8675 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/listener/JumpPerformListener.java @@ -0,0 +1,87 @@ +package com.github.imdmk.doublejump.core.feature.jump.listener; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpExecutor; +import com.github.imdmk.doublejump.core.feature.jump.JumpPlayer; +import com.github.imdmk.doublejump.core.feature.jump.JumpPlayerRepository; +import com.github.imdmk.doublejump.core.feature.jump.cooldown.JumpCooldownService; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.injector.annotations.PluginListener; +import com.github.imdmk.doublejump.core.platform.flight.FlightService; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerToggleFlightEvent; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.UUID; + +@PluginListener +final class JumpPerformListener implements Listener { + + private final JumpConfig config; + private final FlightService flightService; + private final JumpCooldownService cooldownService; + private final JumpPlayerRepository jumpRepository; + private final JumpExecutor jumpExecutor; + + @Inject + JumpPerformListener( + JumpConfig config, + FlightService flightService, + JumpCooldownService cooldownService, + JumpPlayerRepository jumpRepository, + JumpExecutor jumpExecutor + ) { + this.config = config; + this.flightService = flightService; + this.cooldownService = cooldownService; + this.jumpRepository = jumpRepository; + this.jumpExecutor = jumpExecutor; + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + void onToggleFlight(PlayerToggleFlightEvent event) { + Player player = event.getPlayer(); + UUID playerId = player.getUniqueId(); + + JumpPlayer jump = jumpRepository.get(playerId); + if (jump == null) { + return; + } + + event.setCancelled(true); + + JumpResult result = jumpExecutor.execute(player, jump); + if (result.isDisableJumpMode()) { + disable(player); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + void onDeath(PlayerDeathEvent event) { + Player player = event.getEntity(); + UUID playerId = player.getUniqueId(); + + JumpPlayer jumpPlayer = jumpRepository.get(playerId); + if (jumpPlayer == null) { + return; + } + + cooldownService.reset(playerId); + + if (config.disableAfterDeath) { + disable(player); + return; + } + + flightService.allowFlight(player); + } + + private void disable(Player player) { + flightService.refreshFlightState(player); + jumpRepository.deactivate(player.getUniqueId()); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/listener/JumpSessionListener.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/listener/JumpSessionListener.java new file mode 100644 index 0000000..1130e61 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/listener/JumpSessionListener.java @@ -0,0 +1,67 @@ +package com.github.imdmk.doublejump.core.feature.jump.listener; + +import com.github.imdmk.doublejump.core.feature.jump.JumpActivationType; +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpPlayer; +import com.github.imdmk.doublejump.core.feature.jump.JumpPlayerRepository; +import com.github.imdmk.doublejump.core.injector.annotations.PluginListener; +import com.github.imdmk.doublejump.core.message.MessageService; +import com.github.imdmk.doublejump.core.platform.flight.FlightService; +import com.github.imdmk.doublejump.core.shared.permission.PermissionBasedValueProvider; +import com.github.imdmk.doublejump.core.shared.permission.PlayerValueProvider; +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 org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.UUID; + +@PluginListener +final class JumpSessionListener implements Listener { + + private final MessageService messageService; + private final FlightService flightService; + private final JumpPlayerRepository jumpRepository; + + private final PlayerValueProvider enableOnJoinProvider; + + @Inject + JumpSessionListener( + JumpConfig config, + MessageService messageService, + FlightService flightService, + JumpPlayerRepository jumpRepository + ) { + this.messageService = messageService; + this.flightService = flightService; + this.jumpRepository = jumpRepository; + this.enableOnJoinProvider = new PermissionBasedValueProvider<>(config.enableOnJoin); + } + + @EventHandler + void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + UUID playerId = player.getUniqueId(); + + boolean enableOnJoin = enableOnJoinProvider.resolve(player); + if (enableOnJoin) { + JumpPlayer jumpPlayer = JumpPlayer.create(playerId, JumpActivationType.JOIN); + + flightService.allowFlight(player); + jumpRepository.activate(jumpPlayer); + messageService.send(player, n -> n.jumpMessages.enabled()); + } + } + + @EventHandler + void onQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + + JumpPlayer jumpPlayer = jumpRepository.deactivate(player.getUniqueId()); + if (jumpPlayer != null) { + flightService.refreshFlightState(player); + } + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/JumpMessageFormatter.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/JumpMessageFormatter.java new file mode 100644 index 0000000..53b0da6 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/JumpMessageFormatter.java @@ -0,0 +1,37 @@ +package com.github.imdmk.doublejump.core.feature.jump.message; + +import com.eternalcode.multification.shared.Formatter; +import com.github.imdmk.doublejump.core.feature.jump.cooldown.JumpCooldownService; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.shared.time.TimeFormatter; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +@Service(priority = ComponentPriority.LOW) +final class JumpMessageFormatter { + + private final JumpCooldownService cooldownService; + private final TimeFormatter timeFormatter; + + @Inject + JumpMessageFormatter( + JumpCooldownService cooldownService, + TimeFormatter timeFormatter + ) { + this.cooldownService = cooldownService; + this.timeFormatter = timeFormatter; + } + + public Formatter create(Player player, JumpResult result) { + Formatter formatter = new Formatter(); + + if (result == JumpResult.COOLDOWN) { + long cooldown = cooldownService.getRemainingMillis(player.getUniqueId()); + formatter.register("{COOLDOWN}", timeFormatter.format(cooldown)); + } + + return formatter; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/JumpMessageResolver.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/JumpMessageResolver.java new file mode 100644 index 0000000..a2c9cbc --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/JumpMessageResolver.java @@ -0,0 +1,43 @@ +package com.github.imdmk.doublejump.core.feature.jump.message; + +import com.eternalcode.multification.notice.Notice; +import com.github.imdmk.doublejump.core.feature.jump.message.config.JumpMessages; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.EnumMap; +import java.util.Map; + +@Service(priority = ComponentPriority.LOW) +final class JumpMessageResolver { + + private final Map messages; + + @Inject + JumpMessageResolver(JumpMessages config) { + messages = new EnumMap<>(JumpResult.class); + + messages.put(JumpResult.ALLOWED, config.available()); + messages.put(JumpResult.CANCELLED, Notice.empty()); + messages.put(JumpResult.COOLDOWN, config.cooldown()); + messages.put(JumpResult.BLOCKED_BY_COMBAT, config.blockedByCombat()); + messages.put(JumpResult.BLOCKED_BY_LAG, config.blockedByLag()); + messages.put(JumpResult.BLOCKED_BY_WORLD, config.blockedByWorld()); + messages.put(JumpResult.BLOCKED_BY_REGION, config.blockedByRegion()); + messages.put(JumpResult.BLOCKED_BY_GAMEMODE, config.blockedByGameMode()); + messages.put(JumpResult.BLOCKED_BY_GLIDING, config.blockedByGliding()); + messages.put(JumpResult.BLOCKED_BY_FLUID, config.blockedByFluid()); + messages.put(JumpResult.BLOCKED_BY_VEHICLE, config.blockedByVehicle()); + } + + public Notice resolve(JumpResult result) { + Notice notice = messages.get(result); + if (notice == null) { + throw new IllegalStateException("Missing message for " + result); + } + + return notice; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/JumpMessageService.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/JumpMessageService.java new file mode 100644 index 0000000..dbfc48c --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/JumpMessageService.java @@ -0,0 +1,35 @@ +package com.github.imdmk.doublejump.core.feature.jump.message; + +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.message.MessageService; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +@Service(priority = ComponentPriority.LOW, order = 2) +public final class JumpMessageService { + + private final MessageService messageService; + private final JumpMessageResolver noticeResolver; + private final JumpMessageFormatter formatterProvider; + + @Inject + JumpMessageService( + MessageService messageService, + JumpMessageResolver noticeResolver, + JumpMessageFormatter formatterProvider + ) { + this.messageService = messageService; + this.noticeResolver = noticeResolver; + this.formatterProvider = formatterProvider; + } + + public void notify(Player player, JumpResult result) { + messageService.create() + .viewer(player) + .notice(noticeResolver.resolve(result)) + .formatter(formatterProvider.create(player, result)) + .send(); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/config/ENJumpMessages.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/config/ENJumpMessages.java new file mode 100644 index 0000000..bdef4a7 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/config/ENJumpMessages.java @@ -0,0 +1,192 @@ +package com.github.imdmk.doublejump.core.feature.jump.message.config; + +import com.eternalcode.multification.notice.Notice; +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; + +public final class ENJumpMessages + extends OkaeriConfig + implements JumpMessages { + + @Comment({"#", "# Sent when double jump is enabled for the player.", "#"}) + Notice enabled = Notice.chat("DoubleJump enabled."); + + @Comment({"#", "# Sent when double jump is disabled for the player.", "#"}) + Notice disabled = Notice.chat("DoubleJump disabled."); + + @Comment({ + "#", + "# Sent when double jump is enabled for another player.", + "# Placeholders:", + "# {PLAYER} - target player name", + "#" + }) + Notice enabledForTarget = Notice.chat("Enabled DoubleJump for {PLAYER}."); + + @Comment({ + "#", + "# Sent when double jump is disabled for another player.", + "# Placeholders:", + "# {PLAYER} - target player name", + "#" + }) + Notice disabledForTarget = Notice.chat("Disabled DoubleJump for {PLAYER}."); + + @Comment({"#", "# Sent when cooldown ends and player can jump again.", "#"}) + Notice available = Notice.actionbar("You can DoubleJump again"); + + @Comment({ + "#", + "# Sent when player tries to jump during cooldown.", + "# Placeholders:", + "# {COOLDOWN} - remaining cooldown time", + "#" + }) + Notice cooldown = Notice.actionbar("Wait {COOLDOWN} before next jump"); + + @Comment({"#", "# Sent when double jump is blocked by game mode.", "#"}) + Notice blockedByGameMode = Notice.chat("DoubleJump is disabled in this game mode"); + + @Comment({"#", "# Sent when player tries to jump while gliding.", "#"}) + Notice blockedByGliding = Notice.actionbar("You cannot DoubleJump while gliding"); + + @Comment({"#", "# Sent when player is in combat.", "#"}) + Notice blockedByCombat = Notice.chat("You are in combat!"); + + @Comment({"#", "# Sent when player ping/connection is too high.", "#"}) + Notice blockedByLag = Notice.chat("Your connection is too unstable"); + + @Comment({"#", "# Sent when double jump is blocked in a region.", "#"}) + Notice blockedByRegion = Notice.chat("DoubleJump is disabled in this region"); + + @Comment({"#", "# Sent when double jump is blocked in a world.", "#"}) + Notice blockedByWorld = Notice.chat("DoubleJump is disabled in this world"); + + @Comment({"#", "# Sent when double jump is blocked in a fluid.", "#"}) + Notice blockedByFluid = Notice.actionbar("DoubleJump is blocked in water or lava"); + + @Comment({"#", "# Sent when double jump is blocked in vehicle", "#"}) + Notice blockedByVehicle = Notice.actionbar("DoubleJump is blocked in vehicles"); + + @Comment({"#", "# Sent when a player receives a double jump item.", "#"}) + Notice itemGive = Notice.chat( + "You received a DoubleJump item." + ); + + @Comment({ + "#", + "# Sent when a player gives a double jump item to another player.", + "# Placeholders:", + "# {PLAYER} - target player name", + "#" + }) + Notice itemGiveToTarget = Notice.chat( + "Gave DoubleJump item to {PLAYER}." + ); + + @Comment({"#", "# Sent when a player's double jump item is removed.", "#"}) + Notice itemRemove = Notice.chat( + "Your DoubleJump item was removed." + ); + + @Comment({ + "#", + "# Sent when removing a double jump item from another player.", + "# Placeholders:", + "# {PLAYER} - target player name", + "#" + }) + Notice itemRemoveFromTarget = Notice.chat( + "Removed DoubleJump item from {PLAYER}." + ); + + @Override + public Notice enabled() { + return enabled; + } + + @Override + public Notice disabled() { + return disabled; + } + + @Override + public Notice enabledForTarget() { + return enabledForTarget; + } + + @Override + public Notice disabledForTarget() { + return disabledForTarget; + } + + @Override + public Notice available() { + return available; + } + + @Override + public Notice cooldown() { + return cooldown; + } + + @Override + public Notice blockedByGameMode() { + return blockedByGameMode; + } + + @Override + public Notice blockedByGliding() { + return blockedByGliding; + } + + @Override + public Notice blockedByCombat() { + return blockedByCombat; + } + + @Override + public Notice blockedByLag() { + return blockedByLag; + } + + @Override + public Notice blockedByRegion() { + return blockedByRegion; + } + + @Override + public Notice blockedByWorld() { + return blockedByWorld; + } + + @Override + public Notice blockedByFluid() { + return blockedByFluid; + } + + @Override + public Notice blockedByVehicle() { + return blockedByVehicle; + } + + @Override + public Notice itemGive() { + return itemGive; + } + + @Override + public Notice itemGiveToTarget() { + return itemGiveToTarget; + } + + @Override + public Notice itemRemove() { + return itemRemove; + } + + @Override + public Notice itemRemoveFromTarget() { + return itemRemoveFromTarget; + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/config/JumpMessages.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/config/JumpMessages.java new file mode 100644 index 0000000..41183db --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/message/config/JumpMessages.java @@ -0,0 +1,30 @@ +package com.github.imdmk.doublejump.core.feature.jump.message.config; + +import com.eternalcode.multification.notice.Notice; + +public interface JumpMessages { + + Notice enabled(); + Notice disabled(); + + Notice enabledForTarget(); + Notice disabledForTarget(); + + Notice available(); + + Notice cooldown(); + + Notice blockedByGameMode(); + Notice blockedByGliding(); + Notice blockedByCombat(); + Notice blockedByLag(); + Notice blockedByRegion(); + Notice blockedByWorld(); + Notice blockedByFluid(); + Notice blockedByVehicle(); + + Notice itemGive(); + Notice itemGiveToTarget(); + Notice itemRemove(); + Notice itemRemoveFromTarget(); +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/JumpResult.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/JumpResult.java new file mode 100644 index 0000000..7bb295d --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/JumpResult.java @@ -0,0 +1,29 @@ +package com.github.imdmk.doublejump.core.feature.jump.rule; + +public enum JumpResult { + ALLOWED(false), + CANCELLED(false), + COOLDOWN(false), + BLOCKED_BY_COMBAT(true), + BLOCKED_BY_LAG(true), + BLOCKED_BY_WORLD(true), + BLOCKED_BY_GAMEMODE(true), + BLOCKED_BY_REGION(true), + BLOCKED_BY_GLIDING(true), + BLOCKED_BY_FLUID(false), + BLOCKED_BY_VEHICLE(false); + + private final boolean disableJumpMode; + + JumpResult(boolean disableJumpMode) { + this.disableJumpMode = disableJumpMode; + } + + public boolean isAllowed() { + return this == ALLOWED; + } + + public boolean isDisableJumpMode() { + return disableJumpMode; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/JumpRule.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/JumpRule.java new file mode 100644 index 0000000..190443e --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/JumpRule.java @@ -0,0 +1,8 @@ +package com.github.imdmk.doublejump.core.feature.jump.rule; + +import com.github.imdmk.doublejump.core.feature.jump.JumpContext; + +@FunctionalInterface +public interface JumpRule { + JumpResult apply(JumpContext context); +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/JumpValidator.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/JumpValidator.java new file mode 100644 index 0000000..c3bae7b --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/JumpValidator.java @@ -0,0 +1,48 @@ +package com.github.imdmk.doublejump.core.feature.jump.rule; + +import com.github.imdmk.doublejump.core.feature.jump.JumpContext; +import com.github.imdmk.doublejump.core.feature.jump.rule.cooldown.CooldownRule; +import com.github.imdmk.doublejump.core.feature.jump.rule.player.CombatRule; +import com.github.imdmk.doublejump.core.feature.jump.rule.player.FluidRule; +import com.github.imdmk.doublejump.core.feature.jump.rule.player.GameModeRule; +import com.github.imdmk.doublejump.core.feature.jump.rule.player.GlidingRule; +import com.github.imdmk.doublejump.core.feature.jump.rule.player.RegionRule; +import com.github.imdmk.doublejump.core.feature.jump.rule.player.VehicleRule; +import com.github.imdmk.doublejump.core.feature.jump.rule.player.WorldRule; +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import org.panda_lang.utilities.inject.Injector; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.List; + +@Service(priority = ComponentPriority.LOWEST, order = 3) +public final class JumpValidator { + + private final List rules; + + @Inject + public JumpValidator(Injector injector) { + this.rules = List.of( + injector.newInstance(CombatRule.class), + injector.newInstance(CooldownRule.class), + injector.newInstance(WorldRule.class), + injector.newInstance(GameModeRule.class), + injector.newInstance(GlidingRule.class), + injector.newInstance(RegionRule.class), + injector.newInstance(FluidRule.class), + injector.newInstance(VehicleRule.class) + ); + } + + public JumpResult validate(JumpContext context) { + for (JumpRule rule : rules) { + JumpResult result = rule.apply(context); + if (!result.isAllowed()) { + return result; + } + } + + return JumpResult.ALLOWED; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/cooldown/CooldownRule.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/cooldown/CooldownRule.java new file mode 100644 index 0000000..d1f6894 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/cooldown/CooldownRule.java @@ -0,0 +1,29 @@ +package com.github.imdmk.doublejump.core.feature.jump.rule.cooldown; + +import com.github.imdmk.doublejump.core.feature.jump.JumpContext; +import com.github.imdmk.doublejump.core.feature.jump.cooldown.JumpCooldownService; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpRule; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.UUID; + +public final class CooldownRule implements JumpRule { + + private final JumpCooldownService cooldownService; + + @Inject + CooldownRule(JumpCooldownService cooldownService) { + this.cooldownService = cooldownService; + } + + @Override + public JumpResult apply(JumpContext context) { + UUID playerId = context.player().getUniqueId(); + if (cooldownService.isOnCooldown(playerId)) { + return JumpResult.COOLDOWN; + } + + return JumpResult.ALLOWED; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/CombatRule.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/CombatRule.java new file mode 100644 index 0000000..37d8a76 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/CombatRule.java @@ -0,0 +1,32 @@ +package com.github.imdmk.doublejump.core.feature.jump.rule.player; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpContext; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpRule; +import com.github.imdmk.doublejump.core.platform.hook.eternalcombat.CombatService; + +public final class CombatRule implements JumpRule { + + private final JumpConfig config; + private final CombatService combatService; + + public CombatRule(JumpConfig config, CombatService combatService) { + this.config = config; + this.combatService = combatService; + } + + @Override + public JumpResult apply(JumpContext context) { + if (!config.blockInCombat) { + return JumpResult.ALLOWED; + } + + boolean isInCombat = combatService.isInCombat(context.player()); + if (isInCombat) { + return JumpResult.BLOCKED_BY_COMBAT; + } + + return JumpResult.ALLOWED; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/FluidRule.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/FluidRule.java new file mode 100644 index 0000000..f1e81d7 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/FluidRule.java @@ -0,0 +1,38 @@ +package com.github.imdmk.doublejump.core.feature.jump.rule.player; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpContext; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpRule; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +public final class FluidRule implements JumpRule { + + private final JumpConfig config; + + @Inject + FluidRule(JumpConfig config) { + this.config = config; + } + + @Override + public JumpResult apply(JumpContext context) { + if (!config.blockInFluid) { + return JumpResult.ALLOWED; + } + + Player player = context.player(); + if (player.isInWater() || player.isSwimming()) { + return JumpResult.BLOCKED_BY_FLUID; + } + + Material type = player.getLocation().getBlock().getType(); + if (type == Material.WATER || type == Material.LAVA) { + return JumpResult.BLOCKED_BY_FLUID; + } + + return JumpResult.ALLOWED; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/GameModeRule.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/GameModeRule.java new file mode 100644 index 0000000..c5e48b9 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/GameModeRule.java @@ -0,0 +1,28 @@ +package com.github.imdmk.doublejump.core.feature.jump.rule.player; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpContext; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpRule; +import org.bukkit.GameMode; +import org.panda_lang.utilities.inject.annotations.Inject; + +public final class GameModeRule implements JumpRule { + + private final JumpConfig config; + + @Inject + GameModeRule(JumpConfig config) { + this.config = config; + } + + @Override + public JumpResult apply(JumpContext context) { + GameMode playerMode = context.player().getGameMode(); + if (config.blockedGameModes.contains(playerMode)) { + return JumpResult.BLOCKED_BY_GAMEMODE; + } + + return JumpResult.ALLOWED; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/GlidingRule.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/GlidingRule.java new file mode 100644 index 0000000..8ec1cb9 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/GlidingRule.java @@ -0,0 +1,28 @@ +package com.github.imdmk.doublejump.core.feature.jump.rule.player; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpContext; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpRule; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +public final class GlidingRule implements JumpRule { + + private final JumpConfig config; + + @Inject + GlidingRule(JumpConfig config) { + this.config = config; + } + + @Override + public JumpResult apply(JumpContext context) { + Player player = context.player(); + if (config.blockWhileGliding && player.isGliding()) { + return JumpResult.BLOCKED_BY_GLIDING; + } + + return JumpResult.ALLOWED; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/LaggingRule.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/LaggingRule.java new file mode 100644 index 0000000..c208e0c --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/LaggingRule.java @@ -0,0 +1,30 @@ +package com.github.imdmk.doublejump.core.feature.jump.rule.player; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpContext; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpRule; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +public final class LaggingRule implements JumpRule { + + private static final int PING_THRESHOLD = 250; + + private final JumpConfig config; + + @Inject + LaggingRule(JumpConfig config) { + this.config = config; + } + + @Override + public JumpResult apply(JumpContext context) { + Player player = context.player(); + if (config.blockWhenLagging && player.getPing() > PING_THRESHOLD) { + return JumpResult.BLOCKED_BY_LAG; + } + + return JumpResult.ALLOWED; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/RegionRule.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/RegionRule.java new file mode 100644 index 0000000..ac19289 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/RegionRule.java @@ -0,0 +1,33 @@ +package com.github.imdmk.doublejump.core.feature.jump.rule.player; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpContext; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpRule; +import com.github.imdmk.doublejump.core.platform.hook.worldguard.RegionService; +import org.panda_lang.utilities.inject.annotations.Inject; + +public final class RegionRule implements JumpRule { + + private final JumpConfig config; + private final RegionService regionService; + + @Inject + RegionRule(JumpConfig config, RegionService regionService) { + this.config = config; + this.regionService = regionService; + } + + @Override + public JumpResult apply(JumpContext context) { + if (config.blockedRegions.isEmpty()) { + return JumpResult.ALLOWED; + } + + return regionService.getRegions(context.player()) + .stream() + .anyMatch(config.blockedRegions::contains) + ? JumpResult.BLOCKED_BY_REGION + : JumpResult.ALLOWED; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/VehicleRule.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/VehicleRule.java new file mode 100644 index 0000000..e04b62d --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/VehicleRule.java @@ -0,0 +1,32 @@ +package com.github.imdmk.doublejump.core.feature.jump.rule.player; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpContext; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpRule; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +public class VehicleRule implements JumpRule { + + private final JumpConfig config; + + @Inject + VehicleRule(JumpConfig config) { + this.config = config; + } + + @Override + public JumpResult apply(JumpContext context) { + if (!config.blockInVehicle) { + return JumpResult.ALLOWED; + } + + Player player = context.player(); + if (player.isInsideVehicle()) { + return JumpResult.BLOCKED_BY_VEHICLE; + } + + return JumpResult.ALLOWED; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/WorldRule.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/WorldRule.java new file mode 100644 index 0000000..51499af --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/rule/player/WorldRule.java @@ -0,0 +1,27 @@ +package com.github.imdmk.doublejump.core.feature.jump.rule.player; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpContext; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpResult; +import com.github.imdmk.doublejump.core.feature.jump.rule.JumpRule; +import org.panda_lang.utilities.inject.annotations.Inject; + +public final class WorldRule implements JumpRule { + + private final JumpConfig config; + + @Inject + WorldRule(JumpConfig config) { + this.config = config; + } + + @Override + public JumpResult apply(JumpContext context) { + String world = context.player().getWorld().getName(); + if (config.blockedWorlds.contains(world)) { + return JumpResult.BLOCKED_BY_WORLD; + } + + return JumpResult.ALLOWED; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/velocity/JumpVelocity.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/velocity/JumpVelocity.java new file mode 100644 index 0000000..f702e29 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/velocity/JumpVelocity.java @@ -0,0 +1,9 @@ +package com.github.imdmk.doublejump.core.feature.jump.velocity; + +public record JumpVelocity(double verticalBoost, double horizontalBoost) { + + public static JumpVelocity of(double verticalBoost, double horizontalBoost) { + return new JumpVelocity(verticalBoost, horizontalBoost); + } + +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/velocity/JumpVelocityListener.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/velocity/JumpVelocityListener.java new file mode 100644 index 0000000..740bc56 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/velocity/JumpVelocityListener.java @@ -0,0 +1,32 @@ +package com.github.imdmk.doublejump.core.feature.jump.velocity; + +import com.github.imdmk.doublejump.core.feature.jump.JumpConfig; +import com.github.imdmk.doublejump.core.feature.jump.JumpEvent; +import com.github.imdmk.doublejump.core.injector.annotations.PluginListener; +import com.github.imdmk.doublejump.core.shared.permission.PermissionBasedValueProvider; +import com.github.imdmk.doublejump.core.shared.permission.PlayerValueProvider; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.panda_lang.utilities.inject.annotations.Inject; + +@PluginListener +final class JumpVelocityListener implements Listener { + + private final JumpVelocityService velocityService; + private final PlayerValueProvider velocityProvider; + + @Inject + JumpVelocityListener(JumpConfig config, JumpVelocityService velocityService) { + this.velocityService = velocityService; + this.velocityProvider = new PermissionBasedValueProvider<>(config.velocities); + } + + @EventHandler(ignoreCancelled = true) + void onJump(JumpEvent event) { + Player player = event.getPlayer(); + JumpVelocity velocity = velocityProvider.resolve(player); + + velocityService.applyVelocity(player, velocity); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/velocity/JumpVelocitySerializer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/velocity/JumpVelocitySerializer.java new file mode 100644 index 0000000..121470d --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/velocity/JumpVelocitySerializer.java @@ -0,0 +1,35 @@ +package com.github.imdmk.doublejump.core.feature.jump.velocity; + +import eu.okaeri.configs.schema.GenericsDeclaration; +import eu.okaeri.configs.serdes.DeserializationData; +import eu.okaeri.configs.serdes.ObjectSerializer; +import eu.okaeri.configs.serdes.SerializationData; +import org.jetbrains.annotations.NotNull; + +public final class JumpVelocitySerializer implements ObjectSerializer { + + @Override + public boolean supports(@NotNull Class type) { + return JumpVelocity.class.isAssignableFrom(type); + } + + @Override + public void serialize( + @NotNull JumpVelocity velocity, + @NotNull SerializationData data, + @NotNull GenericsDeclaration generics + ) { + data.add("horizontal", velocity.horizontalBoost(), double.class); + data.add("vertical", velocity.verticalBoost(), double.class); + } + + @Override + public JumpVelocity deserialize( + @NotNull DeserializationData data, + @NotNull GenericsDeclaration generics + ) { + double horizontal = data.get("horizontal", double.class); + double vertical = data.get("vertical", double.class); + return JumpVelocity.of(horizontal, vertical); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/velocity/JumpVelocityService.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/velocity/JumpVelocityService.java new file mode 100644 index 0000000..bdc6780 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/jump/velocity/JumpVelocityService.java @@ -0,0 +1,66 @@ +package com.github.imdmk.doublejump.core.feature.jump.velocity; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import org.bukkit.Input; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +@Service(priority = ComponentPriority.LOWEST) +final class JumpVelocityService { + + private static final double DEG_TO_RAD = Math.PI / 180.0; + private static final double DIAGONAL_NORMALIZATION = 1.0 / Math.sqrt(2.0); + + public void applyVelocity(Player player, JumpVelocity velocity) { + Vector direction = resolveMovementDirection(player) + .multiply(velocity.horizontalBoost()); + + Vector result = player.getVelocity(); + result.setX(direction.getX()); + result.setZ(direction.getZ()); + result.setY(velocity.verticalBoost()); + + player.setVelocity(result); + } + + private Vector resolveMovementDirection(Player player) { + Input input = player.getCurrentInput(); + + int forward = axis(input.isForward(), input.isBackward()); + int strafe = axis(input.isLeft(), input.isRight()); + + if (forward == 0 && strafe == 0) { + return new Vector(0, 0, 0); + } + + Vector forwardVector = getForwardVector(player); + Vector rightVector = getRightVector(forwardVector); + + Vector direction = forwardVector.clone().multiply(forward) + .add(rightVector.clone().multiply(strafe)); + + if (forward != 0 && strafe != 0) { + direction.multiply(DIAGONAL_NORMALIZATION); + } + + return direction; + } + + private int axis(boolean positive, boolean negative) { + return (positive ? 1 : 0) - (negative ? 1 : 0); + } + + private Vector getForwardVector(Player player) { + double yawRad = player.getLocation().getYaw() * DEG_TO_RAD; + + double x = -Math.sin(yawRad); + double z = Math.cos(yawRad); + + return new Vector(x, 0, z); + } + + private Vector getRightVector(Vector forward) { + return new Vector(forward.getZ(), 0, -forward.getX()); + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/reload/ReloadCommand.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/reload/ReloadCommand.java new file mode 100644 index 0000000..5f6a169 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/reload/ReloadCommand.java @@ -0,0 +1,46 @@ +package com.github.imdmk.doublejump.core.feature.reload; + +import com.github.imdmk.doublejump.core.config.ConfigAccessException; +import com.github.imdmk.doublejump.core.config.ConfigService; +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteCommand; +import com.github.imdmk.doublejump.core.message.MessageService; +import dev.rollczi.litecommands.annotations.command.Command; +import dev.rollczi.litecommands.annotations.context.Context; +import dev.rollczi.litecommands.annotations.execute.Execute; +import dev.rollczi.litecommands.annotations.permission.Permission; +import org.bukkit.command.CommandSender; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.logging.Logger; + +@LiteCommand +@Command(name = "doublejump reload") +@Permission("command.doublejump.reload") +final class ReloadCommand { + + private final Logger logger; + private final ConfigService configService; + private final MessageService messageService; + + @Inject + ReloadCommand( + Logger logger, + ConfigService configService, + MessageService messageService + ) { + this.logger = logger; + this.configService = configService; + this.messageService = messageService; + } + + @Execute + void reload(@Context CommandSender sender) { + try { + configService.loadAll(); + messageService.send(sender, n -> n.reloadMessages.reloaded()); + } catch (ConfigAccessException e) { + logger.severe("Failed to reload plugin configuration: " + e.getMessage()); + messageService.send(sender, n -> n.actionExecutionError); + } + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/reload/messages/ENReloadMessages.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/reload/messages/ENReloadMessages.java new file mode 100644 index 0000000..83ec761 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/reload/messages/ENReloadMessages.java @@ -0,0 +1,16 @@ +package com.github.imdmk.doublejump.core.feature.reload.messages; + +import com.eternalcode.multification.notice.Notice; +import eu.okaeri.configs.OkaeriConfig; + +public final class ENReloadMessages + extends OkaeriConfig + implements ReloadMessages { + + Notice reloaded = Notice.chat("Reloaded DoubleJump configuration"); + + @Override + public Notice reloaded() { + return reloaded; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/reload/messages/ReloadMessages.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/reload/messages/ReloadMessages.java new file mode 100644 index 0000000..650e48b --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/feature/reload/messages/ReloadMessages.java @@ -0,0 +1,9 @@ +package com.github.imdmk.doublejump.core.feature.reload.messages; + +import com.eternalcode.multification.notice.Notice; + +public interface ReloadMessages { + + Notice reloaded(); + +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/Component.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/Component.java new file mode 100644 index 0000000..544a38e --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/Component.java @@ -0,0 +1,65 @@ +package com.github.imdmk.doublejump.core.injector; + +import org.panda_lang.utilities.inject.DependencyInjectionException; +import org.panda_lang.utilities.inject.Injector; + +import java.lang.annotation.Annotation; + +public final class Component { + + private final Class type; + private final A annotation; + private final ComponentPriority componentPriority; + private final int order; + + private Object instance; + + public Component( + Class type, + A annotation, + ComponentPriority componentPriority, + int order + ) { + this.type = type; + this.annotation = annotation; + this.componentPriority = componentPriority; + this.order = order; + } + + public Class type() { + return type; + } + + public A annotation() { + return annotation; + } + + public ComponentPriority priority() { + return componentPriority; + } + + public int order() { + return order; + } + + public Object instance() { + if (instance == null) { + throw new IllegalStateException("Component instance is not created yet."); + } + + return instance; + } + + public void createInstance(Injector injector) throws DependencyInjectionException { + if (instance != null) { + throw new IllegalStateException("Component instance already created."); + } + + instance = injector.newInstance(type); + } +} + + + + + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentFactory.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentFactory.java new file mode 100644 index 0000000..2eeb754 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentFactory.java @@ -0,0 +1,41 @@ +package com.github.imdmk.doublejump.core.injector; + +import java.lang.annotation.Annotation; + +final class ComponentFactory { + + private static final ComponentPriority FALLBACK_PRIORITY = ComponentPriority.NORMAL; + private static final int FALLBACK_ORDER = 0; + + Component create( + Class type, + Class annotationType + ) { + A annotation = type.getAnnotation(annotationType); + + ComponentPriority priority = extractPriority(annotation); + int order = extractOrder(annotation); + + return new Component<>(type, annotation, priority, order); + } + + private ComponentPriority extractPriority(Annotation annotation) { + try { + return (ComponentPriority) annotation.annotationType() + .getMethod("priority") + .invoke(annotation); + } catch (ReflectiveOperationException e) { + return FALLBACK_PRIORITY; + } + } + + private int extractOrder(Annotation annotation) { + try { + return (int) annotation.annotationType() + .getMethod("order") + .invoke(annotation); + } catch (ReflectiveOperationException e) { + return FALLBACK_ORDER; + } + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentManager.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentManager.java new file mode 100644 index 0000000..e48045d --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentManager.java @@ -0,0 +1,126 @@ +package com.github.imdmk.doublejump.core.injector; + +import com.github.imdmk.doublejump.core.injector.processor.ComponentPostProcessor; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessor; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessorContext; +import com.github.imdmk.doublejump.core.injector.processor.ProcessorContainer; +import org.panda_lang.utilities.inject.DependencyInjectionException; +import org.panda_lang.utilities.inject.Injector; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public final class ComponentManager { + + private final Injector injector; + private final ComponentScanner scanner; + private final ComponentSorter sorter; + + // annotation -> processor container + private final Map, ProcessorContainer> processors = new LinkedHashMap<>(); + private final List postProcessors = new ArrayList<>(); + private final List> components = new ArrayList<>(); + + private boolean scanned = false; + + public ComponentManager(Injector injector, String basePackage) { + this.injector = injector; + this.scanner = new ComponentScanner(basePackage); + this.sorter = new ComponentSorter(); + } + + public ComponentManager addProcessor(ProcessorContainer container) { + if (scanned) { + throw new IllegalStateException("Cannot add processors after scanAll()"); + } + + Class type = container.annotationType(); + if (processors.containsKey(type)) { + throw new IllegalStateException( + "Processor already registered for annotation: " + type.getName() + ); + } + + processors.put(type, container); + return this; + } + + public ComponentManager addProcessors(Collection> containers) { + containers.forEach(this::addProcessor); + return this; + } + + public ComponentManager addPostProcessor(ComponentPostProcessor postProcessor) { + postProcessors.add(postProcessor); + return this; + } + + public ComponentManager addPostProcessors(Collection postProcessors) { + postProcessors.forEach(this::addPostProcessor); + return this; + } + + public void scanAll() { + if (scanned) { + throw new IllegalStateException("scanAll() already called"); + } + + for (ProcessorContainer container : processors.values()) { + components.addAll(scanner.scan(container.annotationType())); + } + + scanned = true; + } + + public void processAll() { + if (!scanned) { + throw new IllegalStateException("scanAll() must be called before processAll()"); + } + + ComponentProcessorContext context = new ComponentProcessorContext(injector); + + sorter.sort(components); + + for (Component component : components) { + processComponent(component, context); + } + } + + @SuppressWarnings("unchecked") + private void processComponent( + Component component, + ComponentProcessorContext context + ) { + try { + component.createInstance(injector); + } catch (DependencyInjectionException e) { + throw new IllegalStateException( + "Failed to create instance of " + component.type().getName(), e + ); + } + + ProcessorContainer raw = processors.get(component.annotation().annotationType()); + if (raw == null) { + throw new IllegalStateException( + "No processor found for annotation type: " + component.annotation().annotationType().getName() + ); + } + + ProcessorContainer container = (ProcessorContainer) raw; + ComponentProcessor processor = container.processor(); + + processor.process( + component.instance(), + component.annotation(), + context + ); + + for (ComponentPostProcessor post : postProcessors) { + post.postProcess(component.instance(), context); + } + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentPriority.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentPriority.java new file mode 100644 index 0000000..6368e80 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentPriority.java @@ -0,0 +1,5 @@ +package com.github.imdmk.doublejump.core.injector; + +public enum ComponentPriority { + LOWEST, LOW, NORMAL, HIGH, HIGHEST +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentScanner.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentScanner.java new file mode 100644 index 0000000..bac53b5 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentScanner.java @@ -0,0 +1,44 @@ +package com.github.imdmk.doublejump.core.injector; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ScanResult; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; +import java.util.List; + +final class ComponentScanner { + + private static final String SHADED_LIBS = "com.github.imdmk.doublejump.lib"; + + private final String basePackage; + private final ComponentFactory componentFactory; + + ComponentScanner(String basePackage) { + this.basePackage = basePackage; + this.componentFactory = new ComponentFactory(); + } + + List> scan(Class annotationType) { + try (ScanResult scan = new ClassGraph() + .enableAllInfo() + .acceptPackages(basePackage) + .rejectPackages(SHADED_LIBS) + .scan()) { + + return scan.getClassesWithAnnotation(annotationType.getName()) + .stream() + .map(ClassInfo::loadClass) + .filter(ComponentScanner::isValidComponent) + .map(type -> componentFactory.create(type, annotationType)) + .toList(); + } + } + + private static boolean isValidComponent(Class type) { + return !type.isInterface() && !Modifier.isAbstract(type.getModifiers()); + } +} + + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentSorter.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentSorter.java new file mode 100644 index 0000000..02d85a6 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/ComponentSorter.java @@ -0,0 +1,15 @@ +package com.github.imdmk.doublejump.core.injector; + +import java.util.Comparator; +import java.util.List; + +final class ComponentSorter { + + void sort(List> components) { + components.sort(Comparator + .comparing((Component c) -> c.priority()) + .thenComparingInt(Component::order) + .thenComparing(c -> c.type().getName()) + ); + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/ConfigFile.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/ConfigFile.java new file mode 100644 index 0000000..ff49446 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/ConfigFile.java @@ -0,0 +1,19 @@ +package com.github.imdmk.doublejump.core.injector.annotations; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ConfigFile { + + ComponentPriority priority() default ComponentPriority.LOWEST; + + int order() default 0; + +} + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/PluginListener.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/PluginListener.java new file mode 100644 index 0000000..11c355d --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/PluginListener.java @@ -0,0 +1,17 @@ +package com.github.imdmk.doublejump.core.injector.annotations; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface PluginListener { + + ComponentPriority priority() default ComponentPriority.HIGHEST; + +} + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/Service.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/Service.java new file mode 100644 index 0000000..aad676b --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/Service.java @@ -0,0 +1,19 @@ +package com.github.imdmk.doublejump.core.injector.annotations; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Service { + + ComponentPriority priority() default ComponentPriority.NORMAL; + + int order() default 1; + +} + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/Task.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/Task.java new file mode 100644 index 0000000..df61019 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/Task.java @@ -0,0 +1,21 @@ +package com.github.imdmk.doublejump.core.injector.annotations; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Task { + + long delayMillis(); + + long periodMillis(); + + boolean async() default true; + + ComponentPriority priority() default ComponentPriority.HIGHEST; +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/gui/Gui.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/gui/Gui.java new file mode 100644 index 0000000..f8d52e7 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/gui/Gui.java @@ -0,0 +1,16 @@ +package com.github.imdmk.doublejump.core.injector.annotations.gui; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Gui { + + ComponentPriority priority() default ComponentPriority.HIGHEST; + +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/lite/LiteArgument.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/lite/LiteArgument.java new file mode 100644 index 0000000..0c7a9ec --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/lite/LiteArgument.java @@ -0,0 +1,19 @@ +package com.github.imdmk.doublejump.core.injector.annotations.lite; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface LiteArgument { + + Class type(); + + String key() default ""; + + ComponentPriority priority() default ComponentPriority.HIGHEST; +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/lite/LiteCommand.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/lite/LiteCommand.java new file mode 100644 index 0000000..a378a8d --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/lite/LiteCommand.java @@ -0,0 +1,16 @@ +package com.github.imdmk.doublejump.core.injector.annotations.lite; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface LiteCommand { + + ComponentPriority priority() default ComponentPriority.HIGHEST; +} + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/lite/LiteContext.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/lite/LiteContext.java new file mode 100644 index 0000000..fc9c122 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/lite/LiteContext.java @@ -0,0 +1,18 @@ +package com.github.imdmk.doublejump.core.injector.annotations.lite; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface LiteContext { + + Class type(); + + ComponentPriority priority() default ComponentPriority.HIGHEST; + +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/lite/LiteHandler.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/lite/LiteHandler.java new file mode 100644 index 0000000..459a929 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/annotations/lite/LiteHandler.java @@ -0,0 +1,17 @@ +package com.github.imdmk.doublejump.core.injector.annotations.lite; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface LiteHandler { + + Class value(); + + ComponentPriority priority() default ComponentPriority.HIGHEST; +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentPostProcessor.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentPostProcessor.java new file mode 100644 index 0000000..e7812c0 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentPostProcessor.java @@ -0,0 +1,10 @@ +package com.github.imdmk.doublejump.core.injector.processor; + +@FunctionalInterface +public interface ComponentPostProcessor { + + void postProcess(Object instance, ComponentProcessorContext context); + +} + + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentProcessor.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentProcessor.java new file mode 100644 index 0000000..087a5d1 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentProcessor.java @@ -0,0 +1,33 @@ +package com.github.imdmk.doublejump.core.injector.processor; + +import java.lang.annotation.Annotation; + +public interface ComponentProcessor { + + Class annotation(); + + void process( + Object instance, + A annotation, + ComponentProcessorContext context + ); + + default T requireInstance( + Object instance, + Class expectedType, + Class annotation + ) { + if (!expectedType.isInstance(instance)) { + throw new IllegalStateException( + "@" + annotation.getSimpleName() + + " can only be used on " + + expectedType.getSimpleName() + + ": " + instance.getClass().getName() + ); + } + + return expectedType.cast(instance); + } +} + + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentProcessorContext.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentProcessorContext.java new file mode 100644 index 0000000..4f82be7 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentProcessorContext.java @@ -0,0 +1,6 @@ +package com.github.imdmk.doublejump.core.injector.processor; + +import org.panda_lang.utilities.inject.Injector; + +public record ComponentProcessorContext(Injector injector) { +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentProcessorFunctional.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentProcessorFunctional.java new file mode 100644 index 0000000..3ec8107 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentProcessorFunctional.java @@ -0,0 +1,32 @@ +package com.github.imdmk.doublejump.core.injector.processor; + +import java.lang.annotation.Annotation; + +public final class ComponentProcessorFunctional + implements ComponentProcessor { + + private final Class annotation; + private final ProcessorHandler handler; + + public ComponentProcessorFunctional( + Class annotation, + ProcessorHandler handler + ) { + this.annotation = annotation; + this.handler = handler; + } + + @Override + public Class annotation() { + return annotation; + } + + @Override + public void process( + Object instance, + A annotation, + ComponentProcessorContext context + ) { + handler.handle(instance, annotation, context); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentProcessors.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentProcessors.java new file mode 100644 index 0000000..3e1455a --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ComponentProcessors.java @@ -0,0 +1,40 @@ +package com.github.imdmk.doublejump.core.injector.processor; + +import com.github.imdmk.doublejump.core.injector.annotations.ConfigFile; +import com.github.imdmk.doublejump.core.injector.annotations.PluginListener; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.injector.annotations.Task; +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteArgument; +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteCommand; +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteContext; +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteHandler; +import com.github.imdmk.doublejump.core.injector.processor.processors.ConfigFileProcessor; +import com.github.imdmk.doublejump.core.injector.processor.processors.PluginListenerProcessor; +import com.github.imdmk.doublejump.core.injector.processor.processors.ServiceProcessor; +import com.github.imdmk.doublejump.core.injector.processor.processors.TaskProcessor; +import com.github.imdmk.doublejump.core.injector.processor.processors.lite.LiteArgumentProcessor; +import com.github.imdmk.doublejump.core.injector.processor.processors.lite.LiteCommandProcessor; +import com.github.imdmk.doublejump.core.injector.processor.processors.lite.LiteContextProcessor; +import com.github.imdmk.doublejump.core.injector.processor.processors.lite.LiteHandlerProcessor; + +import java.util.List; + +public final class ComponentProcessors { + + private ComponentProcessors() { + throw new UnsupportedOperationException("This is utility class and cannot be instantiated."); + } + + public static List> defaults() { + return List.of( + ProcessorBuilder.of(ConfigFile.class, ConfigFileProcessor.class).build(), + ProcessorBuilder.of(Service.class, ServiceProcessor.class).build(), + ProcessorBuilder.of(PluginListener.class, PluginListenerProcessor.class).build(), + ProcessorBuilder.of(Task.class, TaskProcessor.class).build(), + ProcessorBuilder.of(LiteCommand.class, LiteCommandProcessor.class).build(), + ProcessorBuilder.of(LiteArgument.class, LiteArgumentProcessor.class).build(), + ProcessorBuilder.of(LiteContext.class, LiteContextProcessor.class).build(), + ProcessorBuilder.of(LiteHandler.class, LiteHandlerProcessor.class).build() + ); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ProcessorBuilder.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ProcessorBuilder.java new file mode 100644 index 0000000..e4cb567 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ProcessorBuilder.java @@ -0,0 +1,62 @@ +package com.github.imdmk.doublejump.core.injector.processor; + +import org.jetbrains.annotations.CheckReturnValue; + +import java.lang.annotation.Annotation; + +public final class ProcessorBuilder { + + private final Class annotation; + private ProcessorHandler handler; + + private ProcessorBuilder(Class annotation) { + this.annotation = annotation; + } + + public static ProcessorBuilder forAnnotation( + Class annotation + ) { + return new ProcessorBuilder<>(annotation); + } + + public static ProcessorBuilder of( + Class annotation, + Class> processor + ) { + return new ProcessorBuilder<>(annotation).processor(processor); + } + + @CheckReturnValue + public ProcessorBuilder handle(ProcessorHandler handler) { + this.handler = handler; + return this; + } + + @CheckReturnValue + public ProcessorBuilder processor(ComponentProcessor processor) { + this.handler = processor::process; + return this; + } + + @CheckReturnValue + public ProcessorBuilder processor(Class> processorClass) { + this.handler = (instance, annotation, ctx) -> + ctx.injector() + .newInstance(processorClass) + .process(instance, annotation, ctx); + return this; + } + + public ProcessorContainer build() { + if (handler == null) { + throw new IllegalStateException( + "Processor for @" + annotation.getSimpleName() + " has no handler defined" + ); + } + + return new ProcessorContainer<>( + annotation, + new ComponentProcessorFunctional<>(annotation, handler) + ); + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ProcessorContainer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ProcessorContainer.java new file mode 100644 index 0000000..69bc0a6 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ProcessorContainer.java @@ -0,0 +1,8 @@ +package com.github.imdmk.doublejump.core.injector.processor; + +import java.lang.annotation.Annotation; + +public record ProcessorContainer( + Class annotationType, + ComponentProcessor processor +) {} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ProcessorHandler.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ProcessorHandler.java new file mode 100644 index 0000000..9a80d39 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/ProcessorHandler.java @@ -0,0 +1,11 @@ +package com.github.imdmk.doublejump.core.injector.processor; + +import java.lang.annotation.Annotation; + +@FunctionalInterface +public interface ProcessorHandler { + + void handle(Object instance, A annotation, ComponentProcessorContext context); + +} + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/ConfigFileProcessor.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/ConfigFileProcessor.java new file mode 100644 index 0000000..4fc076c --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/ConfigFileProcessor.java @@ -0,0 +1,57 @@ +package com.github.imdmk.doublejump.core.injector.processor.processors; + +import com.github.imdmk.doublejump.core.config.ConfigSection; +import com.github.imdmk.doublejump.core.config.ConfigService; +import com.github.imdmk.doublejump.core.injector.annotations.ConfigFile; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessor; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessorContext; +import org.panda_lang.utilities.inject.Resources; + +import java.lang.reflect.Field; + +public final class ConfigFileProcessor implements ComponentProcessor { + + private final ConfigService configService; + + public ConfigFileProcessor(ConfigService configService) { + this.configService = configService; + } + + @Override + public Class annotation() { + return ConfigFile.class; + } + + @Override + public void process( + Object instance, + ConfigFile annotation, + ComponentProcessorContext context + ) { + Resources resources = context.injector().getResources(); + ConfigSection config = requireInstance( + instance, + ConfigSection.class, + ConfigFile.class + ); + + configService.create(config.getClass()); + resources.on(config.getClass()) + .assignInstance(instance); + + for (Field field : config.getClass().getFields()) { + try { + Object value = field.get(config); + if (value != null) { + resources.on(field.getType()) + .assignInstance(value); + } + } catch (IllegalAccessException exception) { + throw new IllegalStateException( + "Failed to read config field: " + field.getName(), + exception + ); + } + } + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/PluginListenerProcessor.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/PluginListenerProcessor.java new file mode 100644 index 0000000..da267a9 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/PluginListenerProcessor.java @@ -0,0 +1,33 @@ +package com.github.imdmk.doublejump.core.injector.processor.processors; + +import com.github.imdmk.doublejump.core.injector.annotations.PluginListener; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessor; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessorContext; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public final class PluginListenerProcessor implements ComponentProcessor { + + private final Plugin plugin; + + public PluginListenerProcessor(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public Class annotation() { + return PluginListener.class; + } + + @Override + public void process(Object instance, PluginListener annotation, ComponentProcessorContext context) { + Listener listener = requireInstance( + instance, + Listener.class, + PluginListener.class + ); + + plugin.getServer().getPluginManager() + .registerEvents(listener, plugin); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/ServiceProcessor.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/ServiceProcessor.java new file mode 100644 index 0000000..95c444d --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/ServiceProcessor.java @@ -0,0 +1,32 @@ +package com.github.imdmk.doublejump.core.injector.processor.processors; + +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessor; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessorContext; +import org.panda_lang.utilities.inject.Resources; + +public final class ServiceProcessor implements ComponentProcessor { + + @Override + public Class annotation() { + return Service.class; + } + + @Override + public void process( + Object instance, + Service annotation, + ComponentProcessorContext context + ) { + Resources resources = context.injector().getResources(); + + // bind + resources.on(instance.getClass()) + .assignInstance(instance); + + // bind interfaces + for (Class interfaces : instance.getClass().getInterfaces()) { + resources.on(interfaces).assignInstance(instance); + } + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/TaskProcessor.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/TaskProcessor.java new file mode 100644 index 0000000..aec8d5f --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/TaskProcessor.java @@ -0,0 +1,40 @@ +package com.github.imdmk.doublejump.core.injector.processor.processors; + +import com.github.imdmk.doublejump.core.injector.annotations.Task; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessor; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessorContext; +import com.github.imdmk.doublejump.core.platform.scheduler.TaskScheduler; + +import java.time.Duration; + +public final class TaskProcessor implements ComponentProcessor { + + private final TaskScheduler scheduler; + + public TaskProcessor(TaskScheduler scheduler) { + this.scheduler = scheduler; + } + + @Override + public Class annotation() { + return Task.class; + } + + @Override + public void process( + Object instance, + Task annotation, + ComponentProcessorContext context + ) { + Runnable runnable = requireInstance(instance, Runnable.class, Task.class); + Duration delay = Duration.ofMillis(annotation.delayMillis()); + Duration period = Duration.ofMillis(annotation.periodMillis()); + boolean async = annotation.async(); + + if (async) { + scheduler.runTimerAsync(runnable, delay, period); + } else { + scheduler.runTimerSync(runnable, delay, period); + } + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/lite/LiteArgumentProcessor.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/lite/LiteArgumentProcessor.java new file mode 100644 index 0000000..ab5d5b8 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/lite/LiteArgumentProcessor.java @@ -0,0 +1,43 @@ +package com.github.imdmk.doublejump.core.injector.processor.processors.lite; + +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteArgument; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessor; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessorContext; +import com.github.imdmk.doublejump.core.platform.litecommand.LiteCommandsConfigurer; +import dev.rollczi.litecommands.LiteCommandsBuilder; +import dev.rollczi.litecommands.argument.ArgumentKey; +import dev.rollczi.litecommands.argument.resolver.ArgumentResolver; + +public final class LiteArgumentProcessor implements ComponentProcessor { + + private final LiteCommandsConfigurer liteCommandsConfigurer; + + public LiteArgumentProcessor(LiteCommandsConfigurer liteCommandsConfigurer) { + this.liteCommandsConfigurer = liteCommandsConfigurer; + } + + @Override + public Class annotation() { + return LiteArgument.class; + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void process( + Object instance, + LiteArgument annotation, + ComponentProcessorContext context + ) { + ArgumentResolver resolver = requireInstance( + instance, + ArgumentResolver.class, + LiteArgument.class + ); + + LiteCommandsBuilder builder = liteCommandsConfigurer.builder(); + Class argumentClass = annotation.type(); + String argumentKey = annotation.key(); + + builder.argument(argumentClass, ArgumentKey.of(argumentKey), resolver); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/lite/LiteCommandProcessor.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/lite/LiteCommandProcessor.java new file mode 100644 index 0000000..e400b71 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/lite/LiteCommandProcessor.java @@ -0,0 +1,29 @@ +package com.github.imdmk.doublejump.core.injector.processor.processors.lite; + +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteCommand; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessor; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessorContext; +import com.github.imdmk.doublejump.core.platform.litecommand.LiteCommandsConfigurer; + +public final class LiteCommandProcessor implements ComponentProcessor { + + private final LiteCommandsConfigurer liteCommandsConfigurer; + + public LiteCommandProcessor(LiteCommandsConfigurer liteCommandsConfigurer) { + this.liteCommandsConfigurer = liteCommandsConfigurer; + } + + @Override + public Class annotation() { + return LiteCommand.class; + } + + @Override + public void process( + Object instance, + LiteCommand annotation, + ComponentProcessorContext context + ) { + liteCommandsConfigurer.builder().commands(instance); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/lite/LiteContextProcessor.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/lite/LiteContextProcessor.java new file mode 100644 index 0000000..db983c9 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/lite/LiteContextProcessor.java @@ -0,0 +1,41 @@ +package com.github.imdmk.doublejump.core.injector.processor.processors.lite; + +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteContext; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessor; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessorContext; +import com.github.imdmk.doublejump.core.platform.litecommand.LiteCommandsConfigurer; +import dev.rollczi.litecommands.LiteCommandsBuilder; +import dev.rollczi.litecommands.context.ContextProvider; + +public final class LiteContextProcessor implements ComponentProcessor { + + private final LiteCommandsConfigurer liteCommandsConfigurer; + + public LiteContextProcessor(LiteCommandsConfigurer liteCommandsConfigurer) { + this.liteCommandsConfigurer = liteCommandsConfigurer; + } + + @Override + public Class annotation() { + return LiteContext.class; + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void process( + Object instance, + LiteContext annotation, + ComponentProcessorContext context + ) { + ContextProvider contextProvider = requireInstance( + instance, + ContextProvider.class, + LiteContext.class + ); + + LiteCommandsBuilder builder = liteCommandsConfigurer.builder(); + Class contextClass = annotation.type(); + + builder.context(contextClass, contextProvider); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/lite/LiteHandlerProcessor.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/lite/LiteHandlerProcessor.java new file mode 100644 index 0000000..82497b6 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/processor/processors/lite/LiteHandlerProcessor.java @@ -0,0 +1,51 @@ +package com.github.imdmk.doublejump.core.injector.processor.processors.lite; + +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteHandler; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessor; +import com.github.imdmk.doublejump.core.injector.processor.ComponentProcessorContext; +import com.github.imdmk.doublejump.core.platform.litecommand.LiteCommandsConfigurer; +import dev.rollczi.litecommands.LiteCommandsBuilder; +import dev.rollczi.litecommands.handler.result.ResultHandler; +import dev.rollczi.litecommands.invalidusage.InvalidUsageHandler; +import dev.rollczi.litecommands.permission.MissingPermissionsHandler; + +public final class LiteHandlerProcessor implements ComponentProcessor { + + private final LiteCommandsConfigurer liteCommandsConfigurer; + + public LiteHandlerProcessor(LiteCommandsConfigurer liteCommandsConfigurer) { + this.liteCommandsConfigurer = liteCommandsConfigurer; + } + + @Override + public Class annotation() { + return LiteHandler.class; + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void process( + Object instance, + LiteHandler annotation, + ComponentProcessorContext context + ) { + LiteCommandsBuilder builder = liteCommandsConfigurer.builder(); + ResultHandler resultHandler = requireInstance( + instance, + ResultHandler.class, + LiteHandler.class + ); + + if (resultHandler instanceof InvalidUsageHandler invalidUsageHandler) { + builder.invalidUsage(invalidUsageHandler); + return; + } + + if (resultHandler instanceof MissingPermissionsHandler missingPermissionsHandler) { + builder.missingPermission(missingPermissionsHandler); + return; + } + + builder.result(annotation.value(), resultHandler); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/LocalPublisher.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/LocalPublisher.java new file mode 100644 index 0000000..18e5a26 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/LocalPublisher.java @@ -0,0 +1,59 @@ +package com.github.imdmk.doublejump.core.injector.subscriber; + +import com.github.imdmk.doublejump.core.injector.subscriber.event.SubscribeEvent; +import org.panda_lang.utilities.inject.Injector; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class LocalPublisher implements Publisher { + + private final Injector injector; + + private final Map, List> + subscribers = new HashMap<>(); + + public LocalPublisher(Injector injector) { + this.injector = injector; + } + + @Override + public void subscribe(Object instance) { + for (Method method : instance.getClass().getDeclaredMethods()) { + Subscribe subscribe = method.getAnnotation(Subscribe.class); + if (subscribe == null) { + continue; + } + + method.setAccessible(true); + + subscribers + .computeIfAbsent(subscribe.event(), k -> new ArrayList<>()) + .add(new SubscriberMethod(instance, method)); + } + } + + @Override + public E publish(E event) { + List list = subscribers.get(event.getClass()); + if (list == null) { + return event; + } + + for (SubscriberMethod subscriber : list) { + Object instance = subscriber.instance(); + Method method = subscriber.method(); + + injector.invokeMethod(method, instance, event); + } + + return event; + } + + private record SubscriberMethod(Object instance, Method method) { + } +} + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/Publisher.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/Publisher.java new file mode 100644 index 0000000..c129b7a --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/Publisher.java @@ -0,0 +1,11 @@ +package com.github.imdmk.doublejump.core.injector.subscriber; + +import com.github.imdmk.doublejump.core.injector.subscriber.event.SubscribeEvent; + +public interface Publisher { + + void subscribe(Object subscriber); + + E publish(E event); + +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/Subscribe.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/Subscribe.java new file mode 100644 index 0000000..a1c4978 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/Subscribe.java @@ -0,0 +1,17 @@ +package com.github.imdmk.doublejump.core.injector.subscriber; + +import com.github.imdmk.doublejump.core.injector.subscriber.event.SubscribeEvent; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Subscribe { + + Class event(); + +} + diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/event/DoubleJumpInitializeEvent.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/event/DoubleJumpInitializeEvent.java new file mode 100644 index 0000000..e9160bd --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/event/DoubleJumpInitializeEvent.java @@ -0,0 +1,4 @@ +package com.github.imdmk.doublejump.core.injector.subscriber.event; + +public final class DoubleJumpInitializeEvent extends SubscribeEvent { +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/event/DoubleJumpShutdownEvent.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/event/DoubleJumpShutdownEvent.java new file mode 100644 index 0000000..48c4da9 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/event/DoubleJumpShutdownEvent.java @@ -0,0 +1,4 @@ +package com.github.imdmk.doublejump.core.injector.subscriber.event; + +public final class DoubleJumpShutdownEvent extends SubscribeEvent { +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/event/SubscribeEvent.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/event/SubscribeEvent.java new file mode 100644 index 0000000..5eeb1c4 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/injector/subscriber/event/SubscribeEvent.java @@ -0,0 +1,4 @@ +package com.github.imdmk.doublejump.core.injector.subscriber.event; + +public abstract class SubscribeEvent { +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/message/MessageConfig.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/message/MessageConfig.java new file mode 100644 index 0000000..f29ab0d --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/message/MessageConfig.java @@ -0,0 +1,100 @@ +package com.github.imdmk.doublejump.core.message; + +import com.eternalcode.multification.notice.Notice; +import com.eternalcode.multification.notice.resolver.NoticeResolverDefaults; +import com.eternalcode.multification.okaeri.MultificationSerdesPack; +import com.github.imdmk.doublejump.core.config.ConfigSection; +import com.github.imdmk.doublejump.core.feature.jump.message.config.ENJumpMessages; +import com.github.imdmk.doublejump.core.feature.reload.messages.ENReloadMessages; +import com.github.imdmk.doublejump.core.injector.annotations.ConfigFile; +import eu.okaeri.configs.annotation.Comment; +import eu.okaeri.configs.serdes.OkaeriSerdesPack; + +@ConfigFile +public final class MessageConfig extends ConfigSection { + + @Comment({ + "#", + "# Sent when a player attempts to execute a command without having all required permissions.", + "#", + "# Placeholders:", + "# {PERMISSIONS} - comma-separated list of missing permission nodes required to execute the command.", + "#" + }) + public Notice commandPermissionMissing = Notice.chat( + "You are missing required permissions {PERMISSIONS} to execute this command." + ); + + @Comment({ + "#", + "# Sent when a player uses a command with an invalid or incomplete syntax.", + "# ", + "# Placeholders:", + "# {USAGE} - correct usage format of the command (e.g. /playtime ).", + "#" + }) + public Notice commandUsageInvalid = Notice.chat( + "Invalid command usage! Correct syntax: {USAGE}." + ); + + @Comment({ + "#", + "# Header shown before listing available correct usages for a command.", + "# Typically used together with 'commandUsageEntry' when there are multiple valid variants.", + "#" + }) + public Notice commandUsageHeader = Notice.chat( + "Correct usage variants:" + ); + + @Comment({ + "#", + "# Single entry in the list of valid command usages.", + "# Displayed under 'commandUsageHeader' for each available usage.", + "# ", + "# Placeholders:", + "# {USAGE} - a single valid usage variant of the command.", + "#" + }) + public Notice commandUsageEntry = Notice.chat( + "{USAGE}" + ); + + @Comment({ + "#", + "# Sent when a command expects a player by name, but no matching player is found.", + "# ", + }) + public Notice playerNotFound = Notice.chat( + "Player with the given name was not found." + ); + + @Comment({ + "#", + "# Fallback message for unexpected errors while executing commands or actions.", + "# Used when the plugin cannot provide a more detailed error description.", + "#" + }) + public Notice actionExecutionError = Notice.chat( + "An unexpected error occurred while performing this action. " + + "If the problem persists, contact an administrator." + ); + + @Comment({"#", "# Jump messages", "#"}) + public ENJumpMessages jumpMessages = new ENJumpMessages(); + + @Comment({"#", "# Reload messages", "#"}) + public ENReloadMessages reloadMessages = new ENReloadMessages(); + + @Override + public OkaeriSerdesPack serdesPack() { + return registry -> registry.register( + new MultificationSerdesPack(NoticeResolverDefaults.createRegistry()) + ); + } + + @Override + public String fileName() { + return "messageConfig.yml"; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/message/MessageService.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/message/MessageService.java new file mode 100644 index 0000000..98f9304 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/message/MessageService.java @@ -0,0 +1,66 @@ +package com.github.imdmk.doublejump.core.message; + +import com.eternalcode.multification.adventure.AudienceConverter; +import com.eternalcode.multification.bukkit.BukkitMultification; +import com.eternalcode.multification.notice.Notice; +import com.eternalcode.multification.notice.provider.NoticeProvider; +import com.eternalcode.multification.translation.TranslationProvider; +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.injector.subscriber.Subscribe; +import com.github.imdmk.doublejump.core.injector.subscriber.event.DoubleJumpShutdownEvent; +import com.github.imdmk.doublejump.core.platform.adventure.AdventureComponents; +import net.kyori.adventure.platform.AudienceProvider; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.panda_lang.utilities.inject.annotations.Inject; + +@Service(priority = ComponentPriority.LOW) +public final class MessageService extends BukkitMultification { + + private final MessageConfig messageConfig; + private final AudienceProvider audienceProvider; + + @Inject + public MessageService(Plugin plugin, MessageConfig messageConfig) { + this.messageConfig = messageConfig; + this.audienceProvider = BukkitAudiences.create(plugin); + } + + @Override + protected TranslationProvider translationProvider() { + return provider -> messageConfig; + } + + @Override + protected ComponentSerializer serializer() { + return AdventureComponents.miniMessage(); + } + + @Override + protected AudienceConverter audienceConverter() { + return sender -> { + if (sender instanceof Player player) { + return audienceProvider.player(player.getUniqueId()); + } + return audienceProvider.console(); + }; + } + + public void send(CommandSender sender, Notice notice) { + create().viewer(sender).notice(notice).send(); + } + + public void send(CommandSender sender, NoticeProvider notice) { + create().viewer(sender).notice(notice).send(); + } + + @Subscribe(event = DoubleJumpShutdownEvent.class) + public void shutdown() { + audienceProvider.close(); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/adventure/AdventureComponentSerializer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/adventure/AdventureComponentSerializer.java new file mode 100644 index 0000000..8f12bb0 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/adventure/AdventureComponentSerializer.java @@ -0,0 +1,26 @@ +package com.github.imdmk.doublejump.core.platform.adventure; + +import eu.okaeri.configs.schema.GenericsDeclaration; +import eu.okaeri.configs.serdes.DeserializationData; +import eu.okaeri.configs.serdes.ObjectSerializer; +import eu.okaeri.configs.serdes.SerializationData; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; + +public final class AdventureComponentSerializer implements ObjectSerializer { + + @Override + public boolean supports(@NotNull Class type) { + return Component.class.isAssignableFrom(type); + } + + @Override + public void serialize(@NotNull Component component, SerializationData data, @NotNull GenericsDeclaration generics) { + data.setValue(AdventureComponents.serialize(component)); + } + + @Override + public Component deserialize(DeserializationData data, @NotNull GenericsDeclaration generics) { + return AdventureComponents.text(data.getValue(String.class)); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/adventure/AdventureComponents.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/adventure/AdventureComponents.java new file mode 100644 index 0000000..066758c --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/adventure/AdventureComponents.java @@ -0,0 +1,88 @@ +package com.github.imdmk.doublejump.core.platform.adventure; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.minimessage.MiniMessage; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public final class AdventureComponents { + + private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage(); + + private AdventureComponents() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated."); + } + + public static Component text(CharSequence text) { + return MINI_MESSAGE.deserialize(text.toString()); + } + + public static List text(CharSequence... texts) { + List out = new ArrayList<>(texts.length); + for (CharSequence text : texts) { + out.add(MINI_MESSAGE.deserialize(text.toString())); + } + + return List.copyOf(out); + } + + public static List text(Iterable texts) { + List out = new ArrayList<>(); + for (CharSequence text : texts) { + out.add(MINI_MESSAGE.deserialize(text.toString())); + } + + return List.copyOf(out); + } + + public static Component notItalic(Component component) { + return component.decoration(TextDecoration.ITALIC, false); + } + + public static Component notItalic(CharSequence text) { + return notItalic(text(text)); + } + + public static Component notItalic(ComponentLike like) { + return like.asComponent().decoration(TextDecoration.ITALIC, false); + } + + public static List notItalic(String... strings) { + List out = new ArrayList<>(); + for (String string : strings) { + out.add(notItalic(string)); + } + + return List.copyOf(out); + } + + public static String serialize(Component component) { + return MINI_MESSAGE.serialize(component); + } + + public static List serialize(Collection components) { + List out = new ArrayList<>(components.size()); + for (ComponentLike component : components) { + out.add(MINI_MESSAGE.serialize(component.asComponent())); + } + + return List.copyOf(out); + } + + public static String serializeJoined(Collection components, CharSequence delimiter) { + List serialized = new ArrayList<>(components.size()); + for (ComponentLike component : components) { + serialized.add(MINI_MESSAGE.serialize(component.asComponent())); + } + + return String.join(delimiter, serialized); + } + + public static MiniMessage miniMessage() { + return MINI_MESSAGE; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/adventure/AdventureFormatter.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/adventure/AdventureFormatter.java new file mode 100644 index 0000000..3198962 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/adventure/AdventureFormatter.java @@ -0,0 +1,53 @@ +package com.github.imdmk.doublejump.core.platform.adventure; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextReplacementConfig; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public final class AdventureFormatter { + + private AdventureFormatter() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static Component format(String input, AdventurePlaceholders placeholders) { + return format(AdventureComponents.text(input), placeholders); + } + + public static List format(List components, AdventurePlaceholders placeholders) { + return components.stream() + .map(component -> format(component, placeholders)) + .collect(Collectors.toList()); + } + + public static Component format(Component input, AdventurePlaceholders placeholders) { + if (placeholders.isEmpty()) { + return input; + } + + // Sort keys by descending length to avoid substring overlap + List> ordered = placeholders.asMap().entrySet().stream() + .sorted(Comparator.>comparingInt(e -> e.getKey().length()).reversed()) + .toList(); + + Component out = input; + for (Map.Entry entry : ordered) { + String key = entry.getKey(); + Component replacement = entry.getValue(); + + TextReplacementConfig config = TextReplacementConfig.builder() + .matchLiteral(key) + .replacement(replacement) + .build(); + + out = out.replaceText(config); + } + + return out; + } + +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/adventure/AdventurePlaceholders.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/adventure/AdventurePlaceholders.java new file mode 100644 index 0000000..cc858c9 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/adventure/AdventurePlaceholders.java @@ -0,0 +1,78 @@ +package com.github.imdmk.doublejump.core.platform.adventure; + +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public final class AdventurePlaceholders { + + private static final AdventurePlaceholders EMPTY = new AdventurePlaceholders(Map.of()); + + private final Map map; + + private AdventurePlaceholders(Map map) { + this.map = Collections.unmodifiableMap(map); + } + + @Unmodifiable + public Map asMap() { + return map; + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public static AdventurePlaceholders empty() { + return EMPTY; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + + private final Map entries = new LinkedHashMap<>(); + + @Contract("_,_ -> this") + public Builder with(String key, Component value) { + this.entries.put(key, value); + return this; + } + + @Contract("_,_ -> this") + public Builder with(String key, String value) { + this.entries.put(key, Component.text(value)); + return this; + } + + @Contract("_ -> this") + public Builder with(AdventurePlaceholders other) { + this.entries.putAll(other.asMap()); + return this; + } + + @Contract("_,_ -> this") + public Builder with(String key, Object value) { + this.entries.put(key, Component.text(String.valueOf(value))); + return this; + } + + public AdventurePlaceholders build() { + if (this.entries.isEmpty()) { + return EMPTY; + } + + return new AdventurePlaceholders(new LinkedHashMap<>(this.entries)); + } + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/event/BukkitEventCaller.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/event/BukkitEventCaller.java new file mode 100644 index 0000000..9b15f6a --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/event/BukkitEventCaller.java @@ -0,0 +1,33 @@ +package com.github.imdmk.doublejump.core.platform.event; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.platform.scheduler.TaskScheduler; +import org.bukkit.Server; +import org.bukkit.event.Event; +import org.panda_lang.utilities.inject.annotations.Inject; + +@Service(priority = ComponentPriority.LOW) +final class BukkitEventCaller implements EventCaller { + + private final Server server; + private final TaskScheduler scheduler; + + @Inject + BukkitEventCaller(Server server, TaskScheduler scheduler) { + this.server = server; + this.scheduler = scheduler; + } + + @Override + public E callEvent(E event) { + if (event.isAsynchronous() || server.isPrimaryThread()) { + server.getPluginManager().callEvent(event); + return event; + } + + scheduler.runSync(() -> server.getPluginManager().callEvent(event)); + return event; + } + +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/event/EventCaller.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/event/EventCaller.java new file mode 100644 index 0000000..e4f2900 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/event/EventCaller.java @@ -0,0 +1,9 @@ +package com.github.imdmk.doublejump.core.platform.event; + +import org.bukkit.event.Event; + +public interface EventCaller { + + E callEvent(E event); + +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/flight/BukkitFlightService.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/flight/BukkitFlightService.java new file mode 100644 index 0000000..26e06ea --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/flight/BukkitFlightService.java @@ -0,0 +1,51 @@ +package com.github.imdmk.doublejump.core.platform.flight; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.platform.scheduler.TaskScheduler; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.function.Consumer; + +@Service(priority = ComponentPriority.LOW) +final class BukkitFlightService implements FlightService { + + private final TaskScheduler scheduler; + private final FlightPolicy policy; + + @Inject + BukkitFlightService(TaskScheduler scheduler, FlightPolicy policy) { + this.scheduler = scheduler; + this.policy = policy; + } + + @Override + public void allowFlight(Player player) { + sync(player, (p) -> p.setAllowFlight(true)); + } + + @Override + public void disallowFlight(Player player) { + sync(player, (p) -> p.setAllowFlight(false)); + } + + @Override + public void stopFlying(Player player) { + sync(player, (p) -> p.setFlying(false)); + } + + @Override + public void refreshFlightState(Player player) { + sync(player, (p) -> { + boolean allowFly = policy.canFly(player.getGameMode()); + + p.setFlying(false); + p.setAllowFlight(allowFly); + }); + } + + private void sync(Player player, Consumer action) { + scheduler.runSyncIfNeeded(() -> action.accept(player)); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/flight/FlightPolicy.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/flight/FlightPolicy.java new file mode 100644 index 0000000..17c1ff4 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/flight/FlightPolicy.java @@ -0,0 +1,14 @@ +package com.github.imdmk.doublejump.core.platform.flight; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import org.bukkit.GameMode; + +@Service(priority = ComponentPriority.LOWEST) +final class FlightPolicy { + + boolean canFly(GameMode mode) { + return mode == GameMode.CREATIVE + || mode == GameMode.SPECTATOR; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/flight/FlightService.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/flight/FlightService.java new file mode 100644 index 0000000..8a3bbca --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/flight/FlightService.java @@ -0,0 +1,14 @@ +package com.github.imdmk.doublejump.core.platform.flight; + +import org.bukkit.entity.Player; + +public interface FlightService { + + void allowFlight(Player player); + void disallowFlight(Player player); + + void stopFlying(Player player); + + void refreshFlightState(Player player); + +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/PluginHookResolver.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/PluginHookResolver.java new file mode 100644 index 0000000..a22e4e1 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/PluginHookResolver.java @@ -0,0 +1,32 @@ +package com.github.imdmk.doublejump.core.platform.hook; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import org.bukkit.Server; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.function.Supplier; +import java.util.logging.Logger; + +@Service(priority = ComponentPriority.LOWEST) +public final class PluginHookResolver { + + private final Logger logger; + private final Server server; + + @Inject + PluginHookResolver(Logger logger, Server server) { + this.logger = logger; + this.server = server; + } + + public T resolve(String pluginName, Supplier supplier, Supplier fallback) { + if (server.getPluginManager().isPluginEnabled(pluginName)) { + logger.info("Hooked into " + pluginName + " plugin."); + return supplier.get(); + } + + logger.info(pluginName + " not found; skipping"); + return fallback.get(); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/eternalcombat/CombatChecker.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/eternalcombat/CombatChecker.java new file mode 100644 index 0000000..5ee79dc --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/eternalcombat/CombatChecker.java @@ -0,0 +1,13 @@ +package com.github.imdmk.doublejump.core.platform.hook.eternalcombat; + +import org.bukkit.entity.Player; + +@FunctionalInterface +public interface CombatChecker { + + boolean isInCombat(Player player); + + static CombatChecker empty() { + return player -> false; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/eternalcombat/CombatService.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/eternalcombat/CombatService.java new file mode 100644 index 0000000..e2573e2 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/eternalcombat/CombatService.java @@ -0,0 +1,26 @@ +package com.github.imdmk.doublejump.core.platform.hook.eternalcombat; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.platform.hook.PluginHookResolver; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +@Service(priority = ComponentPriority.LOWEST, order = 2) +public final class CombatService { + + private final CombatChecker combatChecker; + + @Inject + CombatService(PluginHookResolver resolver) { + this.combatChecker = resolver.resolve( + "EternalCombat", + EternalCombatChecker::new, + CombatChecker::empty + ); + } + + public boolean isInCombat(Player player) { + return combatChecker.isInCombat(player); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/eternalcombat/EternalCombatChecker.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/eternalcombat/EternalCombatChecker.java new file mode 100644 index 0000000..cb06d7c --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/eternalcombat/EternalCombatChecker.java @@ -0,0 +1,16 @@ +package com.github.imdmk.doublejump.core.platform.hook.eternalcombat; + +import com.eternalcode.combat.EternalCombatProvider; +import com.eternalcode.combat.fight.FightManager; +import org.bukkit.entity.Player; + +public final class EternalCombatChecker implements CombatChecker { + + private static final FightManager FIGHT_MANAGER = EternalCombatProvider.provide() + .getFightManager(); + + @Override + public boolean isInCombat(Player player) { + return FIGHT_MANAGER.isInCombat(player.getUniqueId()); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/worldguard/RegionProvider.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/worldguard/RegionProvider.java new file mode 100644 index 0000000..f088adf --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/worldguard/RegionProvider.java @@ -0,0 +1,16 @@ +package com.github.imdmk.doublejump.core.platform.hook.worldguard; + +import org.bukkit.entity.Player; + +import java.util.Set; + +@FunctionalInterface +public interface RegionProvider { + + Set queryPlayerRegions(Player player); + + static RegionProvider empty() { + return player -> Set.of(); + } + +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/worldguard/RegionService.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/worldguard/RegionService.java new file mode 100644 index 0000000..36a504c --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/worldguard/RegionService.java @@ -0,0 +1,28 @@ +package com.github.imdmk.doublejump.core.platform.hook.worldguard; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.platform.hook.PluginHookResolver; +import org.bukkit.entity.Player; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.util.Set; + +@Service(priority = ComponentPriority.LOWEST, order = 2) +public final class RegionService { + + private final RegionProvider regionProvider; + + @Inject + RegionService(PluginHookResolver resolver) { + regionProvider = resolver.resolve( + "WorldGuard", + WorldGuardRegionProvider::new, + RegionProvider::empty + ); + } + + public Set getRegions(Player player) { + return regionProvider.queryPlayerRegions(player); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/worldguard/WorldGuardRegionProvider.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/worldguard/WorldGuardRegionProvider.java new file mode 100644 index 0000000..cd0b23e --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/hook/worldguard/WorldGuardRegionProvider.java @@ -0,0 +1,30 @@ +package com.github.imdmk.doublejump.core.platform.hook.worldguard; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import org.bukkit.entity.Player; + +import java.util.Set; +import java.util.stream.Collectors; + +final class WorldGuardRegionProvider + implements RegionProvider { + + private static final RegionContainer REGION_CONTAINER = WorldGuard.getInstance() + .getPlatform().getRegionContainer(); + + @Override + public Set queryPlayerRegions(Player player) { + Location location = BukkitAdapter.adapt(player.getLocation()); + ApplicableRegionSet regionSet = REGION_CONTAINER.createQuery() + .getApplicableRegions(location); + + return regionSet.getRegions().stream() + .map(ProtectedRegion::getId) + .collect(Collectors.toUnmodifiableSet()); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/litecommand/LiteCommandsConfigurer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/litecommand/LiteCommandsConfigurer.java new file mode 100644 index 0000000..09282f1 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/litecommand/LiteCommandsConfigurer.java @@ -0,0 +1,50 @@ +package com.github.imdmk.doublejump.core.platform.litecommand; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.injector.subscriber.Subscribe; +import com.github.imdmk.doublejump.core.injector.subscriber.event.DoubleJumpInitializeEvent; +import com.github.imdmk.doublejump.core.injector.subscriber.event.DoubleJumpShutdownEvent; +import dev.rollczi.litecommands.LiteCommands; +import dev.rollczi.litecommands.LiteCommandsBuilder; +import dev.rollczi.litecommands.bukkit.LiteBukkitFactory; +import org.bukkit.Server; +import org.bukkit.plugin.Plugin; +import org.panda_lang.utilities.inject.annotations.Inject; + +@Service(priority = ComponentPriority.LOWEST) +public final class LiteCommandsConfigurer { + + private static final String FALLBACK_PREFIX = "DoubleJump"; + + private final LiteCommandsBuilder builder; + private LiteCommands liteCommands; + + @Inject + LiteCommandsConfigurer(Plugin plugin, Server server) { + this.builder = LiteBukkitFactory.builder(FALLBACK_PREFIX, plugin, server); + } + + public LiteCommandsBuilder builder() { + return builder; + } + + public LiteCommands liteCommands() { + if (liteCommands == null) { + throw new IllegalStateException("LiteCommands not initialized yet"); + } + + return liteCommands; + } + + @Subscribe(event = DoubleJumpInitializeEvent.class) + private void initialize() { + liteCommands = builder.build(); + } + + @Subscribe(event = DoubleJumpShutdownEvent.class) + private void shutdown() { + liteCommands.unregister(); + } +} + diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/UsageHandler.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/litecommand/handler/InvalidUsageHandlerImpl.java similarity index 57% rename from doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/UsageHandler.java rename to doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/litecommand/handler/InvalidUsageHandlerImpl.java index 01f3e18..2277aec 100644 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/UsageHandler.java +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/litecommand/handler/InvalidUsageHandlerImpl.java @@ -1,22 +1,23 @@ -package com.github.imdmk.doublejump.command; +package com.github.imdmk.doublejump.core.platform.litecommand.handler; -import com.github.imdmk.doublejump.infrastructure.message.MessageService; +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteHandler; +import com.github.imdmk.doublejump.core.message.MessageService; import dev.rollczi.litecommands.handler.result.ResultHandlerChain; import dev.rollczi.litecommands.invalidusage.InvalidUsage; import dev.rollczi.litecommands.invalidusage.InvalidUsageHandler; import dev.rollczi.litecommands.invocation.Invocation; import dev.rollczi.litecommands.schematic.Schematic; import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; +import org.panda_lang.utilities.inject.annotations.Inject; -import java.util.Objects; - -public class UsageHandler implements InvalidUsageHandler { +@LiteHandler(value = CommandSender.class) +final class InvalidUsageHandlerImpl implements InvalidUsageHandler { private final MessageService messageService; - public UsageHandler(@NotNull MessageService messageService) { - this.messageService = Objects.requireNonNull(messageService, "message service cannot be null"); + @Inject + InvalidUsageHandlerImpl(MessageService messageService) { + this.messageService = messageService; } @Override @@ -25,23 +26,23 @@ public void handle(Invocation invocation, InvalidUsage notice.invalidUsage) + .notice(notice -> notice.commandUsageInvalid) .placeholder("{USAGE}", schematic.first()) .send(); return; } - this.messageService.create() + messageService.create() .viewer(sender) - .notice(notice -> notice.invalidUsageHeader) + .notice(notice -> notice.commandUsageHeader) .send(); for (String scheme : schematic.all()) { - this.messageService.create() + messageService.create() .viewer(sender) - .notice(notice -> notice.invalidUsageEntry) + .notice(notice -> notice.commandUsageEntry) .placeholder("{USAGE}", scheme) .send(); } diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/litecommand/handler/MissingPermissionsHandlerImpl.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/litecommand/handler/MissingPermissionsHandlerImpl.java new file mode 100644 index 0000000..36f4293 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/litecommand/handler/MissingPermissionsHandlerImpl.java @@ -0,0 +1,31 @@ +package com.github.imdmk.doublejump.core.platform.litecommand.handler; + +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteHandler; +import com.github.imdmk.doublejump.core.message.MessageService; +import dev.rollczi.litecommands.handler.result.ResultHandlerChain; +import dev.rollczi.litecommands.invocation.Invocation; +import dev.rollczi.litecommands.permission.MissingPermissions; +import dev.rollczi.litecommands.permission.MissingPermissionsHandler; +import org.bukkit.command.CommandSender; +import org.panda_lang.utilities.inject.annotations.Inject; + + +@LiteHandler(value = CommandSender.class) +final class MissingPermissionsHandlerImpl implements MissingPermissionsHandler { + + private final MessageService messageService; + + @Inject + MissingPermissionsHandlerImpl(MessageService messageService) { + this.messageService = messageService; + } + + @Override + public void handle(Invocation invocation, MissingPermissions permissions, ResultHandlerChain chain) { + messageService.create() + .viewer(invocation.sender()) + .notice(n -> n.commandPermissionMissing) + .placeholder("{PERMISSIONS}", String.join(", ", permissions.getPermissions())) + .send(); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/litecommand/handler/NoticeResultHandlerImpl.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/litecommand/handler/NoticeResultHandlerImpl.java new file mode 100644 index 0000000..245fedb --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/litecommand/handler/NoticeResultHandlerImpl.java @@ -0,0 +1,29 @@ +package com.github.imdmk.doublejump.core.platform.litecommand.handler; + +import com.eternalcode.multification.notice.Notice; +import com.github.imdmk.doublejump.core.injector.annotations.lite.LiteHandler; +import com.github.imdmk.doublejump.core.message.MessageService; +import dev.rollczi.litecommands.handler.result.ResultHandler; +import dev.rollczi.litecommands.handler.result.ResultHandlerChain; +import dev.rollczi.litecommands.invocation.Invocation; +import org.bukkit.command.CommandSender; +import org.panda_lang.utilities.inject.annotations.Inject; + +@LiteHandler(value = Notice.class) +final class NoticeResultHandlerImpl implements ResultHandler { + + private final MessageService messageService; + + @Inject + NoticeResultHandlerImpl(MessageService messageService) { + this.messageService = messageService; + } + + @Override + public void handle(Invocation invocation, Notice notice, ResultHandlerChain chain) { + messageService.create() + .viewer(invocation.sender()) + .notice(n -> notice) + .send(); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/metrics/BMetricsService.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/metrics/BMetricsService.java new file mode 100644 index 0000000..eb4d740 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/metrics/BMetricsService.java @@ -0,0 +1,25 @@ +package com.github.imdmk.doublejump.core.platform.metrics; + +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.injector.subscriber.Subscribe; +import com.github.imdmk.doublejump.core.injector.subscriber.event.DoubleJumpShutdownEvent; +import org.bstats.bukkit.Metrics; +import org.bukkit.plugin.Plugin; +import org.panda_lang.utilities.inject.annotations.Inject; + +@Service +final class BMetricsService { + + private static final int METRICS_ID = 19362; + private final Metrics metrics; + + @Inject + BMetricsService(Plugin plugin) { + this.metrics = new Metrics(plugin, METRICS_ID); + } + + @Subscribe(event = DoubleJumpShutdownEvent.class) + private void shutdown() { + metrics.shutdown(); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/scheduler/BukkitTaskScheduler.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/scheduler/BukkitTaskScheduler.java new file mode 100644 index 0000000..c889d80 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/scheduler/BukkitTaskScheduler.java @@ -0,0 +1,116 @@ +package com.github.imdmk.doublejump.core.platform.scheduler; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import com.github.imdmk.doublejump.core.injector.subscriber.Subscribe; +import com.github.imdmk.doublejump.core.injector.subscriber.event.DoubleJumpShutdownEvent; +import com.github.imdmk.doublejump.core.shared.time.DurationTicksConverter; +import org.bukkit.Server; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; +import org.panda_lang.utilities.inject.annotations.Inject; + +import java.time.Duration; + +@Service(priority = ComponentPriority.LOWEST) +final class BukkitTaskScheduler implements TaskScheduler { + + private final Plugin plugin; + private final Server server; + private final BukkitScheduler scheduler; + + @Inject + BukkitTaskScheduler(Plugin plugin, Server server, BukkitScheduler scheduler) { + this.plugin = plugin; + this.server = server; + this.scheduler = scheduler; + } + + @Override + public BukkitTask runSync(Runnable runnable) { + return scheduler.runTask(plugin, runnable); + } + + @Override + public BukkitTask runSyncIfNeeded(Runnable runnable) { + if (server.isPrimaryThread()) { + runnable.run(); + return null; + } + + return runSync(runnable); + } + + @Override + public BukkitTask runAsync(Runnable runnable) { + return scheduler.runTaskAsynchronously(plugin, runnable); + } + + @Override + public BukkitTask runLaterAsync( + Runnable runnable, + Duration delay + ) { + return scheduler.runTaskLaterAsynchronously( + plugin, + runnable, + DurationTicksConverter.toTicks(delay) + ); + } + + @Override + public BukkitTask runLaterSync( + Runnable runnable, + Duration delay + ) { + return scheduler.runTaskLater( + plugin, + runnable, + DurationTicksConverter.toTicks(delay) + ); + } + + @Override + public BukkitTask runTimerSync( + Runnable runnable, + Duration delay, + Duration period + ) { + return scheduler.runTaskTimer( + plugin, + runnable, + DurationTicksConverter.toTicks(delay), + DurationTicksConverter.toTicks(period) + ); + } + + @Override + public BukkitTask runTimerAsync( + Runnable runnable, + Duration delay, + Duration period + ) { + return scheduler.runTaskTimerAsynchronously( + plugin, + runnable, + DurationTicksConverter.toTicks(delay), + DurationTicksConverter.toTicks(period) + ); + } + + @Override + public void cancelTask(int taskId) { + scheduler.cancelTask(taskId); + } + + @Override + public void cancelAllTasks() { + scheduler.cancelTasks(plugin); + } + + @Subscribe(event = DoubleJumpShutdownEvent.class) + private void shutdown() { + cancelAllTasks(); + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/scheduler/TaskScheduler.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/scheduler/TaskScheduler.java new file mode 100644 index 0000000..6439ddd --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/platform/scheduler/TaskScheduler.java @@ -0,0 +1,22 @@ +package com.github.imdmk.doublejump.core.platform.scheduler; + +import org.bukkit.scheduler.BukkitTask; + +import java.time.Duration; + +public interface TaskScheduler { + + BukkitTask runSync(Runnable runnable); + BukkitTask runSyncIfNeeded(Runnable runnable); + + BukkitTask runAsync(Runnable runnable); + + BukkitTask runLaterAsync(Runnable runnable, Duration delay); + BukkitTask runLaterSync(Runnable runnable, Duration delay); + + BukkitTask runTimerSync(Runnable runnable, Duration delay, Duration period); + BukkitTask runTimerAsync(Runnable runnable, Duration delay, Duration period); + + void cancelTask(int taskId); + void cancelAllTasks(); +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/permission/PermissionBasedValueProvider.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/permission/PermissionBasedValueProvider.java new file mode 100644 index 0000000..f91923b --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/permission/PermissionBasedValueProvider.java @@ -0,0 +1,28 @@ +package com.github.imdmk.doublejump.core.shared.permission; + +import org.bukkit.entity.Player; + +import java.util.Map; + +public final class PermissionBasedValueProvider + implements PlayerValueProvider { + + private final T defaultValue; + private final Map permissionValues; + + public PermissionBasedValueProvider(PermissionValueConfig config) { + this.defaultValue = config.defaultValue(); + this.permissionValues = config.permissionValues(); + } + + @Override + public T resolve(Player player) { + for (Map.Entry entry : permissionValues.entrySet()) { + if (player.hasPermission(entry.getKey())) { + return entry.getValue(); + } + } + + return defaultValue; + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/permission/PermissionValueConfig.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/permission/PermissionValueConfig.java new file mode 100644 index 0000000..98f22d6 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/permission/PermissionValueConfig.java @@ -0,0 +1,6 @@ +package com.github.imdmk.doublejump.core.shared.permission; + +import java.util.Map; + +public record PermissionValueConfig(T defaultValue, Map permissionValues) { +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/permission/PermissionValueConfigSerializer.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/permission/PermissionValueConfigSerializer.java new file mode 100644 index 0000000..fe8f6f3 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/permission/PermissionValueConfigSerializer.java @@ -0,0 +1,58 @@ +package com.github.imdmk.doublejump.core.shared.permission; + +import eu.okaeri.configs.schema.GenericsDeclaration; +import eu.okaeri.configs.serdes.DeserializationData; +import eu.okaeri.configs.serdes.ObjectSerializer; +import eu.okaeri.configs.serdes.SerializationData; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; + +public final class PermissionValueConfigSerializer + implements ObjectSerializer> { + + @Override + public boolean supports(@NotNull Class> type) { + return PermissionValueConfig.class.isAssignableFrom(type); + } + + @Override + @SuppressWarnings("unchecked") + public void serialize( + @NotNull PermissionValueConfig value, + @NotNull SerializationData data, + @NotNull GenericsDeclaration generics + ) { + Class type = (Class) generics.getSubtypeAtOrThrow(0).getType(); + + data.add("base", value.defaultValue(), type); + data.addAsMap("overrides", value.permissionValues(), String.class, type); + } + + @Override + @SuppressWarnings("unchecked") + public PermissionValueConfig deserialize( + @NotNull DeserializationData data, + @NotNull GenericsDeclaration generics + ) { + Class type = (Class) generics.getSubtypeAtOrThrow(0).getType(); + + T defaultValue = data.get("base", type); + + Map permissionValues; + if (data.containsKey("overrides")) { + if (List.class.isAssignableFrom(type)) { + permissionValues = (Map) data.get("overrides", Map.class); + } + else { + permissionValues = data.getAsMap("overrides", String.class, type); + } + + } else { + permissionValues = Map.of(); + } + + return new PermissionValueConfig<>(defaultValue, permissionValues); + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/permission/PlayerValueProvider.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/permission/PlayerValueProvider.java new file mode 100644 index 0000000..1037c77 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/permission/PlayerValueProvider.java @@ -0,0 +1,7 @@ +package com.github.imdmk.doublejump.core.shared.permission; + +import org.bukkit.entity.Player; + +public interface PlayerValueProvider { + T resolve(Player player); +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/time/DurationTicksConverter.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/time/DurationTicksConverter.java new file mode 100644 index 0000000..1bd1af4 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/time/DurationTicksConverter.java @@ -0,0 +1,16 @@ +package com.github.imdmk.doublejump.core.shared.time; + +import java.time.Duration; + +public final class DurationTicksConverter { + + private static final long TICKS_PER_MILLIS = 50; + + private DurationTicksConverter() { + throw new UnsupportedOperationException("This class cannot be instantiated"); + } + + public static long toTicks(Duration duration) { + return duration.toMillis() / TICKS_PER_MILLIS; + } +} \ No newline at end of file diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/time/TimeFormatConfig.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/time/TimeFormatConfig.java new file mode 100644 index 0000000..7b29b29 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/time/TimeFormatConfig.java @@ -0,0 +1,29 @@ +package com.github.imdmk.doublejump.core.shared.time; + +import com.github.imdmk.doublejump.core.config.ConfigSection; +import com.github.imdmk.doublejump.core.injector.annotations.ConfigFile; +import eu.okaeri.configs.annotation.Comment; +import eu.okaeri.configs.serdes.OkaeriSerdesPack; + +@ConfigFile +public final class TimeFormatConfig extends ConfigSection { + + @Comment({"#", "# Suffix for seconds", "#"}) + public String seconds = "s"; + + @Comment({"#", "# Suffix for minutes", "#"}) + public String minutes = "m"; + + @Comment({"#", "# Suffix for hours", "#"}) + public String hours = "h"; + + @Override + public OkaeriSerdesPack serdesPack() { + return registry -> {}; + } + + @Override + public String fileName() { + return "timeFormatConfig.yml"; + } +} diff --git a/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/time/TimeFormatter.java b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/time/TimeFormatter.java new file mode 100644 index 0000000..64d4e78 --- /dev/null +++ b/doublejump-core/src/main/java/com/github/imdmk/doublejump/core/shared/time/TimeFormatter.java @@ -0,0 +1,65 @@ +package com.github.imdmk.doublejump.core.shared.time; + +import com.github.imdmk.doublejump.core.injector.ComponentPriority; +import com.github.imdmk.doublejump.core.injector.annotations.Service; +import org.panda_lang.utilities.inject.annotations.Inject; + +@Service(priority = ComponentPriority.LOWEST, order = 2) +public final class TimeFormatter { + + private final TimeFormatConfig config; + + @Inject + TimeFormatter(TimeFormatConfig config) { + this.config = config; + } + + public String format(long millis) { + if (millis <= 0) { + return "0.0" + config.seconds; + } + + long totalSeconds = millis / 1000; + if (totalSeconds < 60) { + return formatSeconds(millis); + } + + long minutes = totalSeconds / 60; + long seconds = totalSeconds % 60; + if (minutes < 60) { + return formatMinutes(minutes, seconds); + } + long hours = minutes / 60; + long remainingMinutes = minutes % 60; + + return formatHours(hours, remainingMinutes); + } + + private String formatSeconds(long millis) { + long seconds = millis / 1000; + long decimal = (millis % 1000) / 100; + String suffix = config.seconds; + + return seconds + "." + decimal + suffix; + } + + private String formatMinutes(long minutes, long seconds) { + String minutesSuffix = config.minutes; + String secondsSuffix = config.seconds; + if (seconds == 0) { + return minutes + minutesSuffix; + } + + return minutes + minutesSuffix + " " + seconds + secondsSuffix; + } + + private String formatHours(long hours, long minutes) { + String hoursSuffix = config.hours; + String minutesSuffix = config.minutes; + if (minutes == 0) { + return hours + hoursSuffix; + } + + return hours + hoursSuffix + " " + minutes + minutesSuffix; + } +} \ No newline at end of file diff --git a/doublejump-plugin/build.gradle.kts b/doublejump-plugin/build.gradle.kts index edba2b2..bd54baa 100644 --- a/doublejump-plugin/build.gradle.kts +++ b/doublejump-plugin/build.gradle.kts @@ -1,87 +1,62 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - plugins { - id("net.minecrell.plugin-yml.bukkit") version "0.6.0" -} + `doublejump-java` + `doublejump-repositories` + `doublejump-shadow` -group = "com.github.imdmk" -version = "1.0.1" - -repositories { - maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") // SpigotMC - maven("https://repo.eternalcode.pl/releases") // EternalCode - maven("https://maven.enginehub.org/repo/") // World Guard API - maven("https://repo.extendedclip.com/releases/") // Placeholder API + id("xyz.jpenilla.run-paper") version "3.0.2" } dependencies { - implementation(project(":doublejump-api")) - - compileOnly("org.spigotmc:spigot-api:1.17-R0.1-SNAPSHOT") - compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.9") - compileOnly("me.clip:placeholderapi:2.11.6") - - implementation("com.zaxxer:HikariCP:6.2.1") - implementation("com.j256.ormlite:ormlite-jdbc:6.1") - - implementation("org.panda-lang.utilities:di:1.8.0") - - implementation("net.kyori:adventure-platform-bukkit:4.4.0") - implementation("net.kyori:adventure-text-minimessage:4.19.0") - - implementation("com.eternalcode:multification-bukkit:1.1.4") - implementation("com.eternalcode:multification-okaeri:1.1.4") - implementation("com.eternalcode:gitcheck:1.0.0") - - implementation("dev.triumphteam:triumph-gui:3.1.11") - implementation("org.bstats:bstats-bukkit:3.1.0") - - implementation("dev.rollczi:litecommands-bukkit:3.9.7") - implementation("dev.rollczi:litecommands-annotations:3.9.7") + implementation("com.alessiodp.libby:libby-bukkit:${Versions.LIBBY_BUKKIT}") + api(project(":doublejump-core")) } -bukkit { - name = "DoubleJump" - version = "${project.version}" - apiVersion = "1.17" - main = "com.github.imdmk.doublejump.DoubleJumpPlugin" - author = "imDMK" - description = "Game-changing double jump mechanics. Feels native. Lag-free performance." - website = "https://github.com/imDMK/DoubleJump" - softDepend = listOf("WorldGuard", "PlaceholderAPI") +tasks.build { + dependsOn(tasks.test) + dependsOn(tasks.shadowJar) } -tasks.withType { - archiveFileName.set("DoubleJump v${project.version}.jar") - - dependsOn("checkstyleMain") - dependsOn("checkstyleTest") - dependsOn("test") - - exclude( - "org/intellij/lang/annotations/**", - "org/jetbrains/annotations/**", - "META-INF/**", - ) +doubleJumpShadow { + pluginYml { + name = "DoubleJump" + version = project.version.toString() + apiVersion = "1.21" + softDepend = listOf("WorldGuard", "EternalCombat") + main = "com.github.imdmk.doublejump.plugin.DoubleJumpPlugin" + author = "imDMK (dominiks8318@gmail.com)" + description = "Game-changing double jump mechanics. Feels native. Lag-free performance. Fully customizable, lag-free double jumping." + website = "https://github.com/imDMK/DoubleJump" + } - val libPrefix = "com.github.imdmk.doublejump.lib" - listOf( - "com.eternalcode", - "com.github.benmanes", - "dev.rollczi", - "dev.triumphteam", - "com.j256.ormlite", - "eu.okaeri", - "javassist", - "net.kyori", - "org.bstats", - "org.checkerframework", - "org.json", - "org.panda_lang", - "org.yaml", - "panda.std", - "panda.utilities" - ).forEach { lib -> - relocate(lib, "$libPrefix.$lib") + shadowJar { + archiveFileName.set("DoubleJump v${project.version} (MC 1.21.x).jar") + + mergeServiceFiles() + + exclude( + "META-INF/*.SF", + "META-INF/*.DSA", + "META-INF/*.RSA", + "module-info.class", + "org/intellij/lang/annotations/**", + "org/jetbrains/annotations/**" + ) + + val relocationPrefix = "com.github.imdmk.doublejump.lib" + listOf( + "org.bstats", + ).forEach { pkg -> + relocate(pkg, "$relocationPrefix.$pkg") + } } } + +tasks { + runServer { + minecraftVersion("1.21.11") + downloadPlugins.modrinth("WorldGuard", Versions.WORLDGUARD) + downloadPlugins.modrinth("WorldEdit", Versions.WORLDEDIT) + downloadPlugins.modrinth("PacketEvents", "${Versions.PACKETEVENTS}+spigot") + downloadPlugins.modrinth("EternalCombat", Versions.ETERNAL_COMBAT) + } +} \ No newline at end of file diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/DoubleJump.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/DoubleJump.java deleted file mode 100644 index d907c54..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/DoubleJump.java +++ /dev/null @@ -1,388 +0,0 @@ -package com.github.imdmk.doublejump; - -import com.eternalcode.multification.notice.Notice; -import com.github.imdmk.doublejump.command.MissingPermissionHandler; -import com.github.imdmk.doublejump.command.PlayerContextual; -import com.github.imdmk.doublejump.command.UsageHandler; -import com.github.imdmk.doublejump.command.configurator.CommandConfig; -import com.github.imdmk.doublejump.command.configurator.CommandEditor; -import com.github.imdmk.doublejump.config.ConfigManager; -import com.github.imdmk.doublejump.config.PluginConfig; -import com.github.imdmk.doublejump.database.DatabaseConfig; -import com.github.imdmk.doublejump.database.DatabaseService; -import com.github.imdmk.doublejump.event.EventCaller; -import com.github.imdmk.doublejump.infrastructure.gui.GuiManager; -import com.github.imdmk.doublejump.infrastructure.gui.configuration.GuiConfig; -import com.github.imdmk.doublejump.infrastructure.message.MessageConfig; -import com.github.imdmk.doublejump.infrastructure.message.MessageResultHandler; -import com.github.imdmk.doublejump.infrastructure.message.MessageService; -import com.github.imdmk.doublejump.infrastructure.placeholder.PlaceholderHook; -import com.github.imdmk.doublejump.infrastructure.placeholder.PlaceholderRegistry; -import com.github.imdmk.doublejump.infrastructure.scheduler.BukkitTaskScheduler; -import com.github.imdmk.doublejump.jump.JumpConfig; -import com.github.imdmk.doublejump.jump.PlayerFlyingService; -import com.github.imdmk.doublejump.jump.cache.JumpPlayerCache; -import com.github.imdmk.doublejump.jump.controller.DoubleJumpController; -import com.github.imdmk.doublejump.jump.controller.FlightStateController; -import com.github.imdmk.doublejump.jump.controller.JumpPlayerSessionController; -import com.github.imdmk.doublejump.jump.feature.block.JumpBlockController; -import com.github.imdmk.doublejump.jump.feature.command.JumpItemCommand; -import com.github.imdmk.doublejump.jump.feature.command.JumpReloadCommand; -import com.github.imdmk.doublejump.jump.feature.command.JumpTargetCommand; -import com.github.imdmk.doublejump.jump.feature.command.JumpToggleCommand; -import com.github.imdmk.doublejump.jump.feature.command.JumpVisualCommand; -import com.github.imdmk.doublejump.jump.feature.fall.JumpFallDamageController; -import com.github.imdmk.doublejump.jump.feature.item.JumpItemService; -import com.github.imdmk.doublejump.jump.feature.item.controller.JumpItemDisableController; -import com.github.imdmk.doublejump.jump.feature.item.controller.JumpItemInteractController; -import com.github.imdmk.doublejump.jump.feature.item.controller.JumpItemResetController; -import com.github.imdmk.doublejump.jump.feature.item.controller.JumpItemRestrictionController; -import com.github.imdmk.doublejump.jump.feature.item.usage.ItemUsageStrategy; -import com.github.imdmk.doublejump.jump.feature.item.usage.ItemUsageStrategyFactory; -import com.github.imdmk.doublejump.jump.feature.placeholder.JumpAllowedPlaceholder; -import com.github.imdmk.doublejump.jump.feature.placeholder.JumpCooldownPlaceholder; -import com.github.imdmk.doublejump.jump.feature.restriction.JumpRestrictionController; -import com.github.imdmk.doublejump.jump.feature.restriction.JumpRestrictionNotifier; -import com.github.imdmk.doublejump.jump.feature.restriction.JumpRestrictionService; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.restriction.cooldown.CooldownRestrictionController; -import com.github.imdmk.doublejump.jump.feature.restriction.cooldown.CooldownRestrictionService; -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocityService; -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisualService; -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisualSessionController; -import com.github.imdmk.doublejump.jump.feature.visual.configuration.JumpVisualConfig; -import com.github.imdmk.doublejump.jump.feature.visual.gui.JumpVisualGui; -import com.github.imdmk.doublejump.jump.feature.visual.particle.JumpParticleController; -import com.github.imdmk.doublejump.jump.feature.visual.particle.gui.JumpParticleGui; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualCache; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualRepository; -import com.github.imdmk.doublejump.jump.feature.visual.repository.impl.DaoJumpVisualRepository; -import com.github.imdmk.doublejump.jump.feature.visual.repository.impl.EmptyJumpVisualRepository; -import com.github.imdmk.doublejump.jump.feature.visual.sound.JumpSoundController; -import com.github.imdmk.doublejump.jump.feature.visual.sound.gui.JumpSoundGui; -import com.github.imdmk.doublejump.task.TaskScheduler; -import com.google.common.base.Stopwatch; -import dev.rollczi.litecommands.LiteCommands; -import dev.rollczi.litecommands.bukkit.LiteBukkitFactory; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; -import net.kyori.adventure.text.minimessage.MiniMessage; -import org.bstats.bukkit.Metrics; -import org.bukkit.Server; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.DependencyInjection; -import org.panda_lang.utilities.inject.DependencyInjectionException; -import org.panda_lang.utilities.inject.Injector; - -import java.sql.SQLException; -import java.util.Objects; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Stream; - -/** - * The main implementation of the DoubleJump plugin. - *

- * Responsible for initializing all systems including configuration management, - * dependency injection, services, command registration, metrics, and plugin lifecycle. - *

- * This class implements {@link DoubleJumpApi} to expose core APIs for other parts of the plugin or external plugins. - */ -class DoubleJump implements DoubleJumpApi { - - private final Plugin plugin; - private final Server server; - private final Logger logger; - - private final ConfigManager configManager; - - private final MessageService messageService; - private final DatabaseService databaseService; - - private final TaskScheduler taskScheduler; - private final EventCaller eventCaller; - private final Injector injector; - - private JumpPlayerCache jumpPlayerCache; - private JumpVelocityService jumpVelocityService; - private PlayerFlyingService playerFlyingService; - - private JumpVisualCache jumpVisualCache; - private JumpVisualRepository jumpVisualRepository; - private JumpVisualService jumpVisualService; - - private JumpRestrictionService jumpRestrictionService; - private JumpRestrictionNotifier jumpRestrictionNotifier; - - private CooldownRestrictionService cooldownRestrictionService; - - private JumpItemService jumpItemService; - private ItemUsageStrategy itemUsageStrategy; - - private LiteCommands liteCommands; - - private PlaceholderRegistry placeholderRegistry; - - private Metrics metrics; - - /** - * Initializes the plugin, loads configurations, injects services, registers commands and listeners. - * - * @param plugin the Bukkit plugin instance - */ - DoubleJump(@NotNull Plugin plugin) { - DoubleJumpApiProvider.register(this); - Stopwatch stopwatch = Stopwatch.createStarted(); - - this.plugin = plugin; - this.server = plugin.getServer(); - this.logger = plugin.getLogger(); - - /* Configuration */ - this.configManager = new ConfigManager(this.logger, plugin.getDataFolder()); - - PluginConfig pluginConfig = this.configManager.create(PluginConfig.class); - DatabaseConfig databaseConfig = this.configManager.create(DatabaseConfig.class); - MessageConfig messageConfig = this.configManager.create(MessageConfig.class); - CommandConfig commandConfig = this.configManager.create(CommandConfig.class); - - JumpConfig jumpConfig = this.configManager.create(JumpConfig.class); - JumpVisualConfig jumpVisualConfig = this.configManager.create(JumpVisualConfig.class); - - GuiConfig guiConfig = this.configManager.create(GuiConfig.class); - - /* Services */ - this.messageService = new MessageService(this.logger, messageConfig, BukkitAudiences.create(plugin), MiniMessage.miniMessage()); - this.databaseService = new DatabaseService(this.logger, plugin.getDataFolder(), databaseConfig); - - /* Database connection, jump visual */ - this.jumpVisualCache = new JumpVisualCache(); - - try { - this.databaseService.connect(); - this.jumpVisualRepository = new DaoJumpVisualRepository(this.logger, this.databaseService.getConnectionSource(), this.jumpVisualCache); - } - catch (SQLException sqlException) { - this.jumpVisualRepository = new EmptyJumpVisualRepository(); - - this.logger.log(Level.SEVERE, "An error occurred while initializing the JumpVisual repository. The plugin will run, but the functions will not work as expected.", sqlException); - } - - /* Scheduler */ - this.taskScheduler = new BukkitTaskScheduler(plugin, this.server); - - /* Event caller */ - this.eventCaller = event -> this.server.getPluginManager().callEvent(event); - - /* Gui manager */ - GuiManager guiManager = new GuiManager(this.taskScheduler); - - /* Injector */ - this.injector = DependencyInjection.createInjector(resources -> { - resources.on(Plugin.class).assignInstance(plugin); - resources.on(Server.class).assignInstance(this.server); - resources.on(Logger.class).assignInstance(this.logger); - - /* Configuration */ - resources.on(ConfigManager.class).assignInstance(this.configManager); - resources.on(PluginConfig.class).assignInstance(pluginConfig); - resources.on(MessageConfig.class).assignInstance(messageConfig); - resources.on(JumpConfig.class).assignInstance(jumpConfig); - resources.on(JumpVisualConfig.class).assignInstance(jumpVisualConfig); - resources.on(GuiConfig.class).assignInstance(guiConfig); - - /* Services */ - resources.on(MessageService.class).assignInstance(this.messageService); - - /* Scheduler */ - resources.on(TaskScheduler.class).assignInstance(this.taskScheduler); - - /* Event caller */ - resources.on(EventCaller.class).assignInstance(this.eventCaller); - - /* GuiManager */ - resources.on(GuiManager.class).assignInstance(guiManager); - - /* Jump, lazy */ - resources.on(JumpPlayerCache.class).assignInstance(() -> this.jumpPlayerCache); - resources.on(JumpVelocityService.class).assignInstance(() -> this.jumpVelocityService); - resources.on(PlayerFlyingService.class).assignInstance(() -> this.playerFlyingService); - - /* JumpVisual */ - resources.on(JumpVisualCache.class).assignInstance(this.jumpVisualCache); - resources.on(JumpVisualRepository.class).assignInstance(this.jumpVisualRepository); - resources.on(JumpVisualService.class).assignInstance(() -> this.jumpVisualService); // Lazy - - /* Jump restrictions, lazy */ - resources.on(JumpRestrictionService.class).assignInstance(() -> this.jumpRestrictionService); - resources.on(JumpRestrictionNotifier.class).assignInstance(() -> this.jumpRestrictionNotifier); - - resources.on(CooldownRestrictionService.class).assignInstance(() -> this.cooldownRestrictionService); - - /* Jump items, lazy */ - resources.on(JumpItemService.class).assignInstance(() -> this.jumpItemService); - resources.on(ItemUsageStrategy.class).assignInstance(() -> this.itemUsageStrategy); - }); - - try { - this.jumpPlayerCache = this.createInstance(JumpPlayerCache.class); - this.jumpVelocityService = this.createInstance(JumpVelocityService.class); - this.playerFlyingService = this.createInstance(PlayerFlyingService.class); - - this.jumpVisualService = this.createInstance(JumpVisualService.class); - - this.jumpRestrictionService = this.createInstance(JumpRestrictionService.class); - this.jumpRestrictionNotifier = this.createInstance(JumpRestrictionNotifier.class); - - this.cooldownRestrictionService = this.createInstance(CooldownRestrictionService.class); - - this.jumpItemService = this.createInstance(JumpItemService.class); - this.itemUsageStrategy = ItemUsageStrategyFactory.create(jumpConfig.item.usageMode, this.injector); - } - catch (DependencyInjectionException injectionException) { - this.logger.log(Level.SEVERE, "An error occurred while dependency injecting", injectionException); - this.disablePlugin(); - return; - } - - /* Controllers */ - Stream.of( - /* General */ - DoubleJumpController.class, - FlightStateController.class, - JumpPlayerSessionController.class, - - /* Fall damage */ - JumpFallDamageController.class, - - /* Restrictions */ - JumpRestrictionController.class, - CooldownRestrictionController.class, - - /* Jump particles */ - JumpParticleController.class, - - /* Jump sounds */ - JumpSoundController.class, - - /* Jump item */ - JumpItemDisableController.class, - JumpItemInteractController.class, - JumpItemResetController.class, - JumpItemRestrictionController.class, - - /* Jump blocks */ - JumpBlockController.class, - - /* Jump visuals */ - JumpVisualSessionController.class - ).forEach(this::createInstance); - - /* LiteCommands */ - this.liteCommands = LiteBukkitFactory.builder("DoubleJump", plugin, this.server) - .context(Player.class, new PlayerContextual()) - - .missingPermission(new MissingPermissionHandler(this.messageService)) - .invalidUsage(new UsageHandler(this.messageService)) - .result(Notice.class, new MessageResultHandler(this.messageService)) - - .commands( - this.createInstance(JumpToggleCommand.class), - this.createInstance(JumpTargetCommand.class), - this.createInstance(JumpItemCommand.class), - this.createInstance(JumpVisualCommand.class), - this.createInstance(JumpReloadCommand.class) - ) - - .editorGlobal(new CommandEditor(this.logger, commandConfig)) - - .build(); - - /* Guis */ - Stream.of( - JumpVisualGui.class, - JumpParticleGui.class, - JumpSoundGui.class - ).forEach(gui -> guiManager.registerGui(this.createInstance(gui))); - - /* Placeholder API */ - PlaceholderHook placeholderHook = new PlaceholderHook(this.server, jumpConfig.placeholders); - this.placeholderRegistry = new PlaceholderRegistry(placeholderHook); - - if (placeholderHook.isAvailable()) { - Stream.of( - JumpAllowedPlaceholder.class, - JumpCooldownPlaceholder.class - ).forEach(placeholder -> this.placeholderRegistry.register(this.createInstance(placeholder))); - } - - /* Metrics */ - this.metrics = new Metrics(plugin, DoubleJumpPlugin.METRICS_SERVICE_ID); - - this.logger.info("Enabled DoubleJump plugin in " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms."); - this.logger.info("Thank you for using DoubleJump! Best regards from imDMK."); - } - - /** - * Disables the plugin gracefully, closing open services and unregistering commands. - */ - void disable() { - DoubleJumpApiProvider.unregister(); - - this.configManager.shutdown(); - this.databaseService.close(); - this.messageService.close(); - this.taskScheduler.shutdown(); - this.liteCommands.unregister(); - this.placeholderRegistry.unregisterAll(); - this.metrics.shutdown(); - - this.logger.info("Successfully disabled plugin."); - } - - /** - * Creates an instance of the specified class using the dependency injector. - * - * @param clazz the class to instantiate - * @param the type of the class - * @return an instance of the specified class - */ - private T createInstance(Class clazz) throws DependencyInjectionException { - return this.injector.newInstanceWithFields(clazz); - } - - /** - * Forces Bukkit to disable this plugin. - */ - private void disablePlugin() { - this.server.getPluginManager().disablePlugin(this.plugin); - } - - @Override - public @NotNull ConfigManager getConfigurationManager() { - return Objects.requireNonNull(this.configManager, "configurationManager cannot be null"); - } - - @Override - public @NotNull JumpPlayerCache getJumpPlayerCache() { - return Objects.requireNonNull(this.jumpPlayerCache, "jumpPlayerCache cannot be null"); - } - - @Override - public @NotNull RestrictionChecker getRestrictionChecker() { - return Objects.requireNonNull(this.jumpRestrictionService, "jumpRestrictionService cannot be null"); - } - - @Override - public @NotNull JumpVisualCache getJumpVisualCache() { - return Objects.requireNonNull(this.jumpVisualCache, "jumpVisualCache cannot be null"); - } - - @Override - public @NotNull JumpVisualRepository getJumpVisualRepository() { - return Objects.requireNonNull(this.jumpVisualRepository, "jumpVisualRepository cannot be null"); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/DoubleJumpPlugin.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/DoubleJumpPlugin.java deleted file mode 100644 index f3ed6ee..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/DoubleJumpPlugin.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.github.imdmk.doublejump; - -import org.bukkit.plugin.java.JavaPlugin; - -/** - * The main Bukkit plugin entry point for the DoubleJump plugin. - *

- * Responsible for initializing and shutting down the plugin lifecycle - * via the {@link JavaPlugin} lifecycle methods {@code onEnable()} and {@code onDisable()}. - */ -public class DoubleJumpPlugin extends JavaPlugin { - - /** bStats Metrics service ID for reporting plugin statistics */ - public static final int METRICS_SERVICE_ID = 19387; - - /** - * Called by Bukkit when the plugin is being enabled. - *

- * Initializes the main {@link DoubleJump} instance which sets up - * configurations, services, commands, and listeners. - */ - private DoubleJump doubleJump; - - /** - * The main Bukkit plugin entry point for the DoubleJump plugin. - *

- * Responsible for initializing and shutting down the plugin lifecycle - * via the {@link JavaPlugin} lifecycle methods {@code onEnable()} and {@code onDisable()}. - */ - @Override - public void onEnable() { - this.doubleJump = new DoubleJump(this); - } - - /** - * Called by Bukkit when the plugin is being disabled. - *

- * Cleans up plugin resources and shuts down active services safely. - */ - @Override - public void onDisable() { - this.doubleJump.disable(); - this.doubleJump = null; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/MissingPermissionHandler.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/MissingPermissionHandler.java deleted file mode 100644 index c53284b..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/MissingPermissionHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.github.imdmk.doublejump.command; - -import com.github.imdmk.doublejump.infrastructure.message.MessageService; -import dev.rollczi.litecommands.handler.result.ResultHandlerChain; -import dev.rollczi.litecommands.invocation.Invocation; -import dev.rollczi.litecommands.permission.MissingPermissions; -import dev.rollczi.litecommands.permission.MissingPermissionsHandler; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -public class MissingPermissionHandler implements MissingPermissionsHandler { - - private final MessageService messageService; - - public MissingPermissionHandler(@NotNull MessageService messageService) { - this.messageService = Objects.requireNonNull(messageService, "message service cannot be null"); - } - - @Override - public void handle(Invocation invocation, MissingPermissions permissions, ResultHandlerChain resultHandlerChain) { - this.messageService.create() - .viewer(invocation.sender()) - .notice(notice -> notice.noPermission) - .placeholder("{PERMISSIONS}", String.join(", ", permissions.getPermissions())) - .send(); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/PlayerContextual.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/PlayerContextual.java deleted file mode 100644 index 68cdf74..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/PlayerContextual.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.github.imdmk.doublejump.command; - -import dev.rollczi.litecommands.context.ContextProvider; -import dev.rollczi.litecommands.context.ContextResult; -import dev.rollczi.litecommands.invocation.Invocation; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -public class PlayerContextual implements ContextProvider { - - @Override - public ContextResult provide(Invocation invocation) { - if (invocation.sender() instanceof Player player) { - return ContextResult.ok(() -> player); - } - - return ContextResult.error("Only player can use this command."); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/configurator/Command.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/configurator/Command.java deleted file mode 100644 index 37a1a4d..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/configurator/Command.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.github.imdmk.doublejump.command.configurator; - -import eu.okaeri.configs.OkaeriConfig; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class Command extends OkaeriConfig { - - public String name; - - public boolean enabled = true; - - public List aliases = new ArrayList<>(); - public List permissions = new ArrayList<>(); - - public Map subCommands = new HashMap<>(); - - public Command() {} - - public Command(@NotNull String name) { - this.name = name; - } - - public Command(@NotNull String name, @NotNull List aliases) { - this.name = name; - this.aliases = aliases; - } - - public Command(@NotNull String name, @NotNull List aliases, @NotNull List permissions) { - this.name = name; - this.aliases = aliases; - this.permissions = permissions; - } - - public Command(@NotNull String name, boolean enabled, @NotNull List aliases, @NotNull List permissions) { - this.name = name; - this.enabled = enabled; - this.aliases = aliases; - this.permissions = permissions; - } - - public Command( - @NotNull String name, - boolean enabled, - @NotNull List aliases, - @NotNull List permissions, - @NotNull Map subCommands - ) { - this.name = name; - this.enabled = enabled; - this.aliases = aliases; - this.permissions = permissions; - this.subCommands = subCommands; - } - - public @NotNull String name() { - return this.name; - } - - public boolean isEnabled() { - return this.enabled; - } - - public @NotNull List aliases() { - return Collections.unmodifiableList(this.aliases); - } - - public @NotNull List permissions() { - return Collections.unmodifiableList(this.permissions); - } - - public @NotNull Map subCommands() { - return Collections.unmodifiableMap(this.subCommands); - } -} - diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/configurator/CommandConfig.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/configurator/CommandConfig.java deleted file mode 100644 index 408e752..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/configurator/CommandConfig.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.github.imdmk.doublejump.command.configurator; - -import com.github.imdmk.doublejump.config.ConfigSection; -import eu.okaeri.configs.annotation.Comment; -import eu.okaeri.configs.annotation.Header; -import eu.okaeri.configs.serdes.OkaeriSerdesPack; -import org.jetbrains.annotations.NotNull; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -@Header({ - "# ", - "# DoubleJump Premium - Database Configuration", - "# ", - "# This file allows you to configure commands.", - "# Example of how to rename or disable commands and subcommands.", - "# ", - "# Enjoying the plugin? Please leave a review on SpigotMC!", - "# Support development: https://github.com/sponsors/imDMK", - "# " -}) -public class CommandConfig extends ConfigSection { - - @Comment("# Enable or disable the command configurator feature.") - public boolean enabled = false; - - @Comment({ - "# Commands map: each command can be configured individually.", - "# You can rename commands, enable/disable them, add aliases and permissions.", - "# Nested subCommands can also be configured similarly." - }) - public Map commandsToEdit = new HashMap<>(Map.of( - "doublejump", new Command( - "doublejump", - true, - List.of("dj"), - List.of("command.doublejump"), - Map.of( - "visual", new SubCommand( - "visual", - true, - List.of("settings"), - List.of("command.doublejump.visual") - ) - ) - ) - )); - - /** - * Retrieve a command configuration by name. - * @param name Command key - * @return Optional containing the command if present - */ - public Optional getCommand(String name) { - return Optional.ofNullable(this.commandsToEdit.get(name)); - } - - @Override - public @NotNull OkaeriSerdesPack getSerdesPack() { - // No custom serializers needed for now - return registry -> {}; - } - - @Override - public @NotNull String getFileName() { - return "commandConfig.yml"; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/configurator/CommandEditor.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/configurator/CommandEditor.java deleted file mode 100644 index 66e84eb..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/configurator/CommandEditor.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.github.imdmk.doublejump.command.configurator; - -import dev.rollczi.litecommands.command.builder.CommandBuilder; -import dev.rollczi.litecommands.editor.Editor; -import dev.rollczi.litecommands.meta.Meta; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; -import java.util.Objects; -import java.util.logging.Logger; - -public class CommandEditor implements Editor { - - private final Logger logger; - private final CommandConfig commandConfig; - - public CommandEditor(@NotNull Logger logger, @NotNull CommandConfig commandConfig) { - this.logger = Objects.requireNonNull(logger, "logger cannot be null"); - this.commandConfig = Objects.requireNonNull(commandConfig, "commandConfiguration cannot be null"); - } - - @Override - public CommandBuilder edit(CommandBuilder context) { - if (!this.commandConfig.enabled) { - return context; - } - - return this.commandConfig.getCommand(context.name()) - .map(command -> { - CommandBuilder updated = this.updateCommand(context, command); - return this.updateSubCommand(updated, command.subCommands()); - }) - .map(command -> { - this.logger.info("Edited command " + command.name() + " via configuration."); - return command; - }) - .orElse(context); - } - - private @NotNull CommandBuilder updateCommand(@NotNull CommandBuilder context, @NotNull Command command) { - return context - .name(command.name()) - .aliases(command.aliases()) - .applyMeta(meta -> meta.list(Meta.PERMISSIONS, permissions -> permissions.addAll(command.permissions()))) - .enabled(command.isEnabled()); - } - - private @NotNull CommandBuilder updateSubCommand(@NotNull CommandBuilder context, @NotNull Map subCommands) { - for (Map.Entry entry : subCommands.entrySet()) { - String id = entry.getKey(); - SubCommand sub = entry.getValue(); - - context = context.editChild(id, child -> child - .name(sub.name()) - .aliases(sub.aliases()) - .applyMeta(meta -> meta.list(Meta.PERMISSIONS, permissions -> permissions.addAll(sub.permissions()))) - .enabled(sub.isEnabled())); - } - - return context; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/configurator/SubCommand.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/configurator/SubCommand.java deleted file mode 100644 index 6ea9fea..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/command/configurator/SubCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.github.imdmk.doublejump.command.configurator; - -import eu.okaeri.configs.OkaeriConfig; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class SubCommand extends OkaeriConfig { - - public String name; - public boolean enabled; - - public List aliases = new ArrayList<>(); - public List permissions = new ArrayList<>(); - - public SubCommand() {} - - public SubCommand(@NotNull String name, boolean enabled, @NotNull List aliases, @NotNull List permissions) { - this.name = name; - this.enabled = enabled; - this.aliases = aliases; - this.permissions = permissions; - } - - public @NotNull String name() { - return this.name; - } - - public boolean isEnabled() { - return this.enabled; - } - - public @NotNull List aliases() { - return Collections.unmodifiableList(this.aliases); - } - - public @NotNull List permissions() { - return Collections.unmodifiableList(this.permissions); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/config/MissingConfigValueException.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/config/MissingConfigValueException.java deleted file mode 100644 index 9c1145f..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/config/MissingConfigValueException.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.imdmk.doublejump.config; - -import org.jetbrains.annotations.NotNull; - -/** - * Exception thrown when a required configuration value is missing or invalid. - *

- * Used to indicate a fatal misconfiguration that prevents the plugin from starting or behaving correctly. - */ -public class MissingConfigValueException extends RuntimeException { - - /** - * Constructs a new exception with the specified detail message. - * - * @param message the detail message - */ - public MissingConfigValueException(@NotNull String message) { - super(message); - } - - /** - * Constructs a new exception indicating the specific path and reason for failure. - * - * @param path the configuration path that is invalid or missing - * @param reason explanation of why the value is invalid or required - */ - public MissingConfigValueException(@NotNull String path, @NotNull String reason) { - super("Missing or invalid configuration at '" + path + "': " + reason); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/config/PluginConfig.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/config/PluginConfig.java deleted file mode 100644 index 7b5a587..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/config/PluginConfig.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.github.imdmk.doublejump.config; - -import eu.okaeri.configs.annotation.Comment; -import eu.okaeri.configs.annotation.Header; -import eu.okaeri.configs.serdes.OkaeriSerdesPack; -import eu.okaeri.configs.serdes.commons.SerdesCommons; -import org.jetbrains.annotations.NotNull; - -import java.time.Duration; - -@Header({ - "# ", - "# DoubleJump Premium", - "# Thanks for purchasing this plugin!", - "#", - "# If you encounter any issues or need assistance,", - "# feel free to contact me:", - "# ", - "# Discord: imdmk", - "# Email: dominiks8318@gmail.com", - "#", - "# If you enjoy the plugin, please leave a review on", - "# SpigotMC — your support means a lot!", - "#", - "# Support development: https://github.com/sponsors/imDMK", - "# Paypal.me: https://paypal.me/dominiksuliga", - "#", -}) - -public class PluginConfig extends ConfigSection { - - @Comment("# Check for plugin update and send notification after administrator join to server?") - public boolean checkUpdate = true; - - @Comment("# How often should the plugin check for updates? Recommended value: 1 day") - public Duration updateInterval = Duration.ofDays(1); - - @Override - public @NotNull OkaeriSerdesPack getSerdesPack() { - return registry -> { - registry.register(new SerdesCommons()); - }; - } - - @Override - public @NotNull String getFileName() { - return "pluginConfig.yml"; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/config/serializer/ComponentSerializer.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/config/serializer/ComponentSerializer.java deleted file mode 100644 index d19dffb..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/config/serializer/ComponentSerializer.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.imdmk.doublejump.config.serializer; - -import com.github.imdmk.doublejump.util.ComponentUtil; -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import net.kyori.adventure.text.Component; -import org.jetbrains.annotations.NotNull; - -public class ComponentSerializer implements ObjectSerializer { - - @Override - public boolean supports(@NotNull Class type) { - return Component.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NotNull Component component, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) { - data.setValue(ComponentUtil.serialize(component)); - } - - @Override - public Component deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) { - return ComponentUtil.text(data.getValue(String.class)); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/config/serializer/EnchantmentSerializer.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/config/serializer/EnchantmentSerializer.java deleted file mode 100644 index 40a068e..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/config/serializer/EnchantmentSerializer.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.github.imdmk.doublejump.config.serializer; - -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.enchantments.Enchantment; -import org.jetbrains.annotations.NotNull; - -public class EnchantmentSerializer implements ObjectSerializer { - - @Override - public boolean supports(@NotNull Class type) { - return Enchantment.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NotNull Enchantment enchantment, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) { - data.setValue(enchantment.getKey().getKey(), String.class); - } - - @Override - public Enchantment deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) { - return Registry.ENCHANTMENT.get(NamespacedKey.minecraft(data.getValue(String.class))); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/database/DatabaseConfig.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/database/DatabaseConfig.java deleted file mode 100644 index 8ae62c5..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/database/DatabaseConfig.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.github.imdmk.doublejump.database; - -import com.github.imdmk.doublejump.config.ConfigSection; -import eu.okaeri.configs.annotation.Comment; -import eu.okaeri.configs.annotation.Header; -import eu.okaeri.configs.serdes.OkaeriSerdesPack; -import org.jetbrains.annotations.NotNull; - - -@Header({ - "# ", - "# DoubleJump Premium - Database Configuration", - "# Configure the database connection settings below.", - "# ", - "# Enjoying the plugin? Please leave a review on SpigotMC!", - "# Support development: https://github.com/sponsors/imDMK", - "# " -}) -public class DatabaseConfig extends ConfigSection { - - @Comment({ - "# Database mode to use.", - "# Supported: SQLITE, MYSQL" - }) - public DatabaseMode databaseMode = DatabaseMode.SQLITE; - - @Comment("# Database hostname or IP address") - public String hostname = "localhost"; - - @Comment("# Database name") - public String database = "database"; - - @Comment("# Database username") - public String username = "root"; - - @Comment("# Database password") - public String password = "ExamplePassword1101"; - - @Comment("# Database port") - public int port = 3306; - - @Override - public @NotNull OkaeriSerdesPack getSerdesPack() { - return registry -> {}; - } - - @Override - public @NotNull String getFileName() { - return "databaseConfig.yml"; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/database/DatabaseMode.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/database/DatabaseMode.java deleted file mode 100644 index 24d9c0c..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/database/DatabaseMode.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.github.imdmk.doublejump.database; - -public enum DatabaseMode { - SQLITE, MYSQL -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/database/DatabaseService.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/database/DatabaseService.java deleted file mode 100644 index 95a05ef..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/database/DatabaseService.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.github.imdmk.doublejump.database; - -import com.j256.ormlite.jdbc.DataSourceConnectionSource; -import com.j256.ormlite.support.ConnectionSource; -import com.zaxxer.hikari.HikariDataSource; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.sql.SQLException; -import java.util.Objects; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class DatabaseService { - - private final Logger logger; - private final File dataFolder; - private final DatabaseConfig databaseConfig; - - private HikariDataSource dataSource; - private ConnectionSource connectionSource; - - public DatabaseService( - @NotNull Logger logger, - @NotNull File dataFolder, - @NotNull DatabaseConfig databaseConfig - ) { - this.logger = Objects.requireNonNull(logger, "logger cannot be null"); - this.dataFolder = Objects.requireNonNull(dataFolder, "dataFolder cannot be null"); - this.databaseConfig = Objects.requireNonNull(databaseConfig, "databaseConfiguration cannot be null"); - } - - public void connect() throws SQLException, IllegalStateException { - if (this.dataSource != null || this.connectionSource != null) { - throw new IllegalStateException("DatabaseService is already connected."); - } - - if (!this.dataFolder.exists() && !this.dataFolder.mkdirs()) { - throw new IllegalStateException("Unable to create data folder: " + this.dataFolder.getAbsolutePath()); - } - - this.dataSource = this.createHikariDataSource(); - - DatabaseMode mode = this.databaseConfig.databaseMode; - switch (mode) { - case SQLITE -> { - this.dataSource.setDriverClassName("org.sqlite.JDBC"); - this.dataSource.setJdbcUrl("jdbc:sqlite:" + this.dataFolder + "/database.db"); - } - - case MYSQL -> { - this.dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); - this.dataSource.setJdbcUrl("jdbc:mysql://" + this.databaseConfig.hostname + ":" + this.databaseConfig.port + "/" + this.databaseConfig.database); - } - - default -> throw new IllegalStateException("Unknown database mode: " + mode.name()); - } - - try { - this.connectionSource = new DataSourceConnectionSource(this.dataSource, this.dataSource.getJdbcUrl()); - - this.logger.info("Connected to " + mode.name() + " database."); - } - catch (SQLException sqlException) { - this.logger.log(Level.SEVERE, "Failed to connect to database", sqlException); - this.dataSource.close(); // We're closing after exception, otherwise can cause a leak - this.dataSource = null; - throw sqlException; - } - } - - private @NotNull HikariDataSource createHikariDataSource() { - HikariDataSource dataSource = new HikariDataSource(); - dataSource.setMaximumPoolSize(5); - dataSource.setUsername(this.databaseConfig.username); - dataSource.setPassword(this.databaseConfig.password); - dataSource.addDataSourceProperty("cachePrepStmts", true); - dataSource.addDataSourceProperty("prepStmtCacheSize", 250); - dataSource.addDataSourceProperty("prepStmtCacheSqlLimit", 2048); - dataSource.addDataSourceProperty("useServerPrepStmts", true); - return dataSource; - } - - public void close() { - if (this.dataSource == null || this.connectionSource == null) { - this.logger.warning("DatabaseService#close() called, but service was not connected."); - return; - } - - try { - this.connectionSource.close(); - } - catch (Exception e) { - this.logger.log(Level.SEVERE, "Failed to close ConnectionSource", e); - } - - try { - this.dataSource.close(); - } - catch (Exception e) { - this.logger.log(Level.SEVERE, "Failed to close DataSource", e); - } - - this.connectionSource = null; - this.dataSource = null; - - this.logger.info("Database connection closed successfully."); - } - - public @Nullable ConnectionSource getConnectionSource() { - return this.connectionSource; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/AbstractGui.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/AbstractGui.java deleted file mode 100644 index 42e38ea..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/AbstractGui.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.gui; - -import com.github.imdmk.doublejump.infrastructure.gui.configuration.GuiConfig; -import com.github.imdmk.doublejump.infrastructure.injector.DefaultInjectable; -import com.github.imdmk.doublejump.task.TaskScheduler; -import dev.triumphteam.gui.guis.BaseGui; -import dev.triumphteam.gui.guis.GuiItem; -import dev.triumphteam.gui.guis.PaginatedGui; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.util.function.Consumer; - -public abstract class AbstractGui extends DefaultInjectable { - - @Inject protected GuiConfig guiConfig; - @Inject protected TaskScheduler taskScheduler; - @Inject protected GuiManager guiManager; - - protected void setNextPageItem(@NotNull BaseGui gui) { - gui.setItem(this.getNextPageItemSlot(gui.getRows()), this.createNextPageItem(gui)); - } - - protected int getNextPageItemSlot(int rows) { - return switch (rows) { - case 3 -> 25; - case 4 -> 34; - case 5 -> 43; - case 6 -> 52; - default -> throw new IllegalStateException("Unexpected row size: " + rows); - }; - } - - protected GuiItem createNextPageItem(@NotNull BaseGui gui) { - if (!(gui instanceof PaginatedGui paginatedGui)) { - throw new IllegalArgumentException("Gui is not a paginated gui to create a next page item"); - } - - return this.guiConfig.nextItem.asGuiItem(event -> { - if (!paginatedGui.next()) { - paginatedGui.updateItem(event.getSlot(), this.guiConfig.noNextItem.asGuiItem()); - this.restoreItemLater(event, gui, this.createNextPageItem(gui)); - } - }); - } - - protected void setPreviousPageItem(@NotNull BaseGui gui) { - gui.setItem(this.getPreviousPageItemSlot(gui.getRows()), this.createPreviousPageItem(gui)); - } - - protected int getPreviousPageItemSlot(int rows) { - return switch (rows) { - case 3 -> 19; - case 4 -> 28; - case 5 -> 37; - case 6 -> 46; - default -> throw new IllegalStateException("Unexpected row size: " + rows); - }; - } - - protected GuiItem createPreviousPageItem(@NotNull BaseGui gui) { - if (!(gui instanceof PaginatedGui paginatedGui)) { - throw new IllegalArgumentException("Gui is not a paginated gui to create previous page item"); - } - - return this.guiConfig.previousItem.asGuiItem(event -> { - if (!paginatedGui.previous()) { - paginatedGui.updateItem(event.getSlot(), this.guiConfig.noPreviousItem.asGuiItem()); - this.restoreItemLater(event, gui, this.createPreviousPageItem(gui)); - } - }); - } - - protected void setExitPageItem(@NotNull BaseGui gui, @NotNull Consumer exit) { - gui.setItem(this.getExitPageItemSlot(gui.getRows()), this.createExitPageItem(exit)); - } - - protected int getExitPageItemSlot(int rows) { - return switch (rows) { - case 3 -> 22; - case 4 -> 31; - case 5 -> 40; - case 6 -> 49; - default -> throw new IllegalStateException("Unexpected row size: " + rows); - }; - } - - protected GuiItem createExitPageItem(@NotNull Consumer exit) { - return this.guiConfig.exitItem.asGuiItem(exit::accept); - } - - protected void restoreItemLater(@NotNull InventoryClickEvent event, @NotNull BaseGui gui, @NotNull GuiItem item) { - this.taskScheduler.runLaterAsync(() -> gui.updateItem(event.getSlot(), item), 60L); - } - -} - diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/GuiManager.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/GuiManager.java deleted file mode 100644 index 3c9d711..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/GuiManager.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.gui; - -import com.github.imdmk.doublejump.task.TaskScheduler; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Manages registration and opening of GUIs identified by their unique identifiers. - * Responsible for running GUI open operations synchronously on the main server thread. - */ -public class GuiManager { - - private final Map identifiableGuis = new ConcurrentHashMap<>(); - - private final TaskScheduler taskScheduler; - - /** - * Constructs a GuiManager with the given task scheduler. - * - * @param taskScheduler used to schedule synchronous tasks on the main server thread - * @throws NullPointerException if taskScheduler is null - */ - public GuiManager(@NotNull TaskScheduler taskScheduler) { - this.taskScheduler = Objects.requireNonNull(taskScheduler, "taskScheduler cannot be null"); - } - - /** - * Registers a GUI instance with its unique identifier. - * If a GUI with the same identifier already exists, it will be replaced. - * - * @param gui the GUI instance to register - * @throws NullPointerException if gui or its identifier is null - */ - public void registerGui(@NotNull IdentifiableGui gui) { - Objects.requireNonNull(gui, "gui cannot be null"); - Objects.requireNonNull(gui.getIdentifier(), "gui identifier cannot be null"); - - this.identifiableGuis.put(gui.getIdentifier(), gui); - } - - /** - * Opens a simple GUI for the specified player. - * - * @param identifier the unique identifier of the GUI - * @param player the player to open the GUI for - * @throws IllegalArgumentException if no GUI with the given identifier is registered, - * or if the registered GUI is not of type SimpleGui - * @throws NullPointerException if identifier or player is null - */ - public void openGui(@NotNull String identifier, @NotNull Player player) { - Objects.requireNonNull(identifier, "identifier cannot be null"); - Objects.requireNonNull(player, "player cannot be null"); - - IdentifiableGui gui = this.getGuiOrThrow(identifier); - if (gui instanceof SimpleGui simpleGui) { - this.taskScheduler.run(() -> simpleGui.open(player)); - } - else { - throw new IllegalArgumentException("GUI with identifier '" + identifier + "' is not a SimpleGui"); - } - } - - /** - * Opens a parameterized GUI for the specified player with an additional parameter. - * - * @param identifier the unique identifier of the GUI - * @param viewer the player to open the GUI for - * @param parameter the parameter to pass to the GUI - * @param the type of the parameter - * @throws IllegalArgumentException if no GUI with the given identifier is registered, - * or if the registered GUI is not of type ParameterizedGui - * @throws NullPointerException if identifier, viewer, or parameter is null - */ - @SuppressWarnings("unchecked") - public void openGui(@NotNull String identifier, @NotNull Player viewer, @NotNull T parameter) { - Objects.requireNonNull(identifier, "identifier cannot be null"); - Objects.requireNonNull(viewer, "viewer cannot be null"); - Objects.requireNonNull(parameter, "parameter cannot be null"); - - IdentifiableGui gui = this.getGuiOrThrow(identifier); - if (gui instanceof ParameterizedGui paramGui) { - this.taskScheduler.run(() -> ((ParameterizedGui) paramGui).open(viewer, parameter)); - } - else { - throw new IllegalArgumentException("GUI with identifier '" + identifier + "' is not a ParameterizedGui"); - } - } - - /** - * Retrieves the GUI by identifier or throws if not found. - * - * @param identifier unique GUI identifier - * @return the registered GUI instance - * @throws IllegalArgumentException if no GUI with the given identifier is registered - */ - private IdentifiableGui getGuiOrThrow(@NotNull String identifier) { - IdentifiableGui gui = this.identifiableGuis.get(identifier); - if (gui == null) { - throw new IllegalArgumentException("No GUI registered with identifier '" + identifier + "'"); - } - - return gui; - } - -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/IdentifiableGui.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/IdentifiableGui.java deleted file mode 100644 index d09ad41..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/IdentifiableGui.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.gui; - -import org.jetbrains.annotations.NotNull; - -@FunctionalInterface -public interface IdentifiableGui { - - @NotNull String getIdentifier(); - -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/ParameterizedGui.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/ParameterizedGui.java deleted file mode 100644 index a4e8c58..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/ParameterizedGui.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.gui; - -import dev.triumphteam.gui.guis.BaseGui; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -public interface ParameterizedGui extends IdentifiableGui { - - @NotNull BaseGui createGui(@NotNull Player viewer, @NotNull T parameter); - - default void prepareBorderItems(@NotNull BaseGui gui) {} - - default void prepareNavigationItems(@NotNull BaseGui gui, @NotNull Player viewer, @NotNull T parameter) {} - - default void defaultClickAction(@NotNull BaseGui gui, @NotNull Player viewer) {} - - void prepareItems(@NotNull BaseGui gui, @NotNull Player viewer, @NotNull T parameter); - - default void open(@NotNull Player viewer, @NotNull T parameter) { - BaseGui gui = this.createGui(viewer, parameter); - - this.prepareBorderItems(gui); - this.prepareItems(gui, viewer, parameter); - this.prepareNavigationItems(gui, viewer, parameter); - this.defaultClickAction(gui, viewer); - - gui.open(viewer); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/SimpleGui.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/SimpleGui.java deleted file mode 100644 index 60ac450..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/SimpleGui.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.gui; - -import dev.triumphteam.gui.guis.BaseGui; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -public interface SimpleGui extends IdentifiableGui { - - @NotNull BaseGui createGui(@NotNull Player viewer); - - default void prepareBorderItems(@NotNull BaseGui gui) {} - - default void prepareNavigationItems(@NotNull BaseGui gui, @NotNull Player viewer) {} - - default void defaultClickAction(@NotNull BaseGui gui, @NotNull Player viewer) {} - - void prepareItems(@NotNull BaseGui gui, @NotNull Player viewer); - - default void open(@NotNull Player viewer) { - BaseGui gui = this.createGui(viewer); - - this.prepareBorderItems(gui); - this.prepareItems(gui, viewer); - this.prepareNavigationItems(gui, viewer); - this.defaultClickAction(gui, viewer); - - gui.open(viewer); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/configuration/ConfigGuiItem.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/configuration/ConfigGuiItem.java deleted file mode 100644 index 7172e05..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/configuration/ConfigGuiItem.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.gui.configuration; - -import com.github.imdmk.doublejump.util.ComponentUtil; -import dev.triumphteam.gui.builder.item.ItemBuilder; -import dev.triumphteam.gui.components.GuiAction; -import dev.triumphteam.gui.guis.GuiItem; -import net.kyori.adventure.text.Component; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemFlag; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public record ConfigGuiItem(@NotNull Material material, - @NotNull Component name, @NotNull List lore, - int slot, - @NotNull Map enchantments, - @NotNull List flags) { - - public @NotNull GuiItem asGuiItem() { - return this.asGuiItem(event -> {}); - } - - public @NotNull GuiItem asGuiItem(@NotNull GuiAction event) { - return ItemBuilder.from(this.material) - .name(this.name) - .lore(this.lore) - .enchant(this.enchantments) - .flags(this.flags.toArray(new ItemFlag[0])) - .asGuiItem(event); - } - - public static @NotNull Builder builder() { - return new Builder(); - } - - public static class Builder { - - private Material material; - private Component name; - private List lore; - private int slot; - - private final Map enchantments = new HashMap<>(); - private final List flags = new ArrayList<>(); - - private Builder() {} - - @Contract("_ -> this") - public Builder material(@NotNull Material material) { - this.material = material; - return this; - } - - @Contract("_ -> this") - public Builder nameComponent(@NotNull Component name) { - this.name = name; - return this; - } - - @Contract("_ -> this") - public Builder name(@NotNull String name) { - this.name = ComponentUtil.notItalic(name); - return this; - } - - @Contract("_ -> this") - public Builder loreComponent(@NotNull List lore) { - this.lore = lore; - return this; - } - - @Contract("_ -> this") - public Builder lore(@NotNull List lore) { - this.lore = ComponentUtil.notItalic(lore); - return this; - } - - @Contract("_ -> this") - public Builder lore(@NotNull String... lore) { - this.lore = ComponentUtil.notItalic(lore); - return this; - } - - @Contract("_ -> this") - public Builder slot(int slot) { - this.slot = slot; - return this; - } - - @Contract("_ -> this") - public Builder enchantments(@NotNull Map enchantments) { - this.enchantments.putAll(enchantments); - return this; - } - - @Contract("_,_ -> this") - public Builder enchantment(@NotNull Enchantment enchantment, int level) { - this.enchantments.put(enchantment, level); - return this; - } - - @Contract("_ -> this") - public Builder flags(@NotNull ItemFlag... flags) { - this.flags.addAll(List.of(flags)); - return this; - } - - @Contract("_ -> this") - public Builder flags(@NotNull List flags) { - this.flags.addAll(flags); - return this; - } - - @Contract("_ -> this") - public Builder from(@NotNull ConfigGuiItem item) { - return this.material(item.material) - .nameComponent(item.name) - .loreComponent(item.lore) - .slot(item.slot) - .enchantments(item.enchantments) - .flags(item.flags.toArray(new ItemFlag[0])); - } - - public @NotNull ConfigGuiItem build() { - return new ConfigGuiItem(this.material, this.name, this.lore, this.slot, this.enchantments, this.flags); - } - } -} - diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/configuration/ConfigGuiItemSerializer.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/configuration/ConfigGuiItemSerializer.java deleted file mode 100644 index 8614787..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/configuration/ConfigGuiItemSerializer.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.gui.configuration; - -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import net.kyori.adventure.text.Component; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class ConfigGuiItemSerializer implements ObjectSerializer { - - @Override - public boolean supports(@NotNull Class type) { - return ConfigGuiItem.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NotNull ConfigGuiItem item, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) { - data.add("material", item.material(), Material.class); - data.add("name", item.name(), Component.class); - data.addCollection("lore", item.lore(), Component.class); - - if (item.slot() > 0) { - data.add("slot", item.slot(), Integer.class); - } - - if (!item.enchantments().isEmpty()) { - data.addAsMap("enchantments", item.enchantments(), Enchantment.class, Integer.class); - } - - if (!item.flags().isEmpty()) { - data.addCollection("flags", item.flags(), ItemFlag.class); - } - } - - @Override - public ConfigGuiItem deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) { - Material material = data.get("material", Material.class); - Component name = data.get("name", Component.class); - List lore = data.getAsList("lore", Component.class); - - int slot = data.containsKey("slot") ? data.get("slot", Integer.class) : 0; - - Map enchantments = data.containsKey("enchantments") ? - data.getAsMap("enchantments", Enchantment.class, Integer.class) : new HashMap<>(); - - List flags = data.containsKey("flags") ? - data.getAsList("flags", ItemFlag.class) : new ArrayList<>(); - - return ConfigGuiItem.builder() - .material(material) - .nameComponent(name) - .loreComponent(lore) - .slot(slot) - .enchantments(enchantments) - .flags(flags) - .build(); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/configuration/GuiConfig.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/configuration/GuiConfig.java deleted file mode 100644 index 0aa9d5f..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/gui/configuration/GuiConfig.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.gui.configuration; - -import com.github.imdmk.doublejump.config.ConfigSection; -import com.github.imdmk.doublejump.config.serializer.ComponentSerializer; -import com.github.imdmk.doublejump.config.serializer.EnchantmentSerializer; -import com.github.imdmk.doublejump.jump.feature.visual.gui.JumpVisualGuiConfiguration; -import com.github.imdmk.doublejump.jump.feature.visual.sound.configuration.JumpSoundSerializer; -import com.github.imdmk.doublejump.jump.feature.visual.sound.configuration.SoundSerializer; -import eu.okaeri.configs.annotation.Comment; -import eu.okaeri.configs.annotation.Header; -import eu.okaeri.configs.serdes.OkaeriSerdesPack; -import org.bukkit.Material; -import org.jetbrains.annotations.NotNull; - -@Header({ - "# ", - "# DoubleJump Premium - Gui Configuration", - "# Configure GUI items and appearance for the plugin.", - "# ", - "# ", - "# Enjoying the plugin? Please leave a review on SpigotMC!", - "# Support development: https://github.com/sponsors/imDMK", - "# " -}) -public class GuiConfig extends ConfigSection { - - @Comment("# Enable border around all GUIs") - public boolean fillBorder = true; - - @Comment("# Item used as the border around GUIs") - public ConfigGuiItem borderItem = ConfigGuiItem.builder() - .material(Material.GRAY_STAINED_GLASS_PANE) - .name(" ") - .lore(" ") - .build(); - - @Comment("# Item used to navigate to the next page") - public ConfigGuiItem nextItem = ConfigGuiItem.builder() - .material(Material.ARROW) - .name("Next page") - .lore(" ", "Click RIGHT to go to the next page", " ") - .build(); - - @Comment("# Item shown when there is no next page") - public ConfigGuiItem noNextItem = ConfigGuiItem.builder() - .material(Material.BARRIER) - .name("There's no next page!") - .lore(" ", "Sorry, there is no next page available.", " ") - .build(); - - @Comment("# Item used to navigate to the previous page") - public ConfigGuiItem previousItem = ConfigGuiItem.builder() - .material(Material.ARROW) - .name("Previous page") - .lore(" ", "Click LEFT to go to the previous page", " ") - .build(); - - @Comment("# Item shown when there is no previous page") - public ConfigGuiItem noPreviousItem = ConfigGuiItem.builder() - .material(Material.BARRIER) - .name("There's no previous page!") - .lore(" ", "Sorry, there is no previous page available.", " ") - .build(); - - @Comment("# Item used to exit the GUI") - public ConfigGuiItem exitItem = ConfigGuiItem.builder() - .material(Material.ACACIA_BUTTON) - .name("Exit GUI") - .lore(" ", "Click LEFT to exit this GUI", " ") - .build(); - - @Comment("# Configuration for the jump visual GUI") - public JumpVisualGuiConfiguration jumpVisualGui = new JumpVisualGuiConfiguration(); - - @Override - public @NotNull OkaeriSerdesPack getSerdesPack() { - return registry -> { - registry.register(new ComponentSerializer()); - registry.register(new EnchantmentSerializer()); - - registry.register(new SoundSerializer()); - registry.register(new JumpSoundSerializer()); - registry.register(new ConfigGuiItemSerializer()); - }; - } - - @Override - public @NotNull String getFileName() { - return "guiConfig.yml"; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/injector/DefaultInjectable.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/injector/DefaultInjectable.java deleted file mode 100644 index e4f3604..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/injector/DefaultInjectable.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.injector; - -import com.github.imdmk.doublejump.event.EventCaller; -import com.github.imdmk.doublejump.infrastructure.message.MessageService; -import com.github.imdmk.doublejump.jump.JumpConfig; -import com.github.imdmk.doublejump.jump.PlayerFlyingService; -import com.github.imdmk.doublejump.jump.cache.JumpPlayerCache; -import org.bukkit.Server; -import org.bukkit.plugin.Plugin; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.util.logging.Logger; - -/** - * Base class providing common dependencies for plugin components. - *

- * All fields are injected automatically and provide access to core plugin services, - * configurations, and APIs for convenience in subclasses. - *

- */ -public abstract class DefaultInjectable { - - @Inject protected Plugin plugin; - - @Inject protected Server server; - - @Inject protected Logger logger; - - @Inject protected JumpConfig jumpConfig; - - @Inject protected MessageService messageService; - - @Inject protected JumpPlayerCache jumpCache; - - @Inject protected PlayerFlyingService flyingService; - - @Inject protected EventCaller eventCaller; - -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/injector/PluginListener.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/injector/PluginListener.java deleted file mode 100644 index 5541bba..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/injector/PluginListener.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.injector; - -import org.bukkit.Location; -import org.bukkit.event.Listener; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.panda_lang.utilities.inject.annotations.PostConstruct; - -/** - * Base class for Bukkit event listeners that are automatically registered - * with the plugin's event manager after dependency injection. - *

- * Extends {@link DefaultInjectable} to provide injected dependencies. - * Implements {@link Listener} to handle Bukkit events. - *

- */ -public abstract class PluginListener extends DefaultInjectable implements Listener { - - /** - * Automatically registers this listener instance with the Bukkit plugin manager. - */ - @PostConstruct - public void postConstruct() { - this.server.getPluginManager().registerEvents(this, this.plugin); - } - - /** - * Checks whether two locations refer to the same block position (ignoring exact coordinates). - *

- * This method compares the block coordinates (X, Y, Z) of the given locations. - * If the second location is {@code null}, the method returns {@code true} by design, - * assuming the positions are considered the same in that context. - *

- * - * @param from the first location to compare; must not be {@code null} - * @param to the second location to compare; may be {@code null} - * @return {@code true} if {@code to} is {@code null} or both locations are in the same block; {@code false} otherwise - */ - protected boolean isSameBlockPosition(@NotNull Location from, @Nullable Location to) { - if (to == null) { - return true; - } - - return from.getBlockX() == to.getBlockX() - && from.getBlockY() == to.getBlockY() - && from.getBlockZ() == to.getBlockZ(); - } - -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/message/MessageConfig.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/message/MessageConfig.java deleted file mode 100644 index c671695..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/message/MessageConfig.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.message; - -import com.eternalcode.multification.notice.Notice; -import com.eternalcode.multification.notice.resolver.NoticeResolverDefaults; -import com.eternalcode.multification.okaeri.MultificationSerdesPack; -import com.github.imdmk.doublejump.config.ConfigSection; -import eu.okaeri.configs.annotation.Comment; -import eu.okaeri.configs.annotation.Header; -import eu.okaeri.configs.serdes.OkaeriSerdesPack; -import org.jetbrains.annotations.NotNull; - -@Header({ - "# ", - "# DoubleJump Premium - Message Configuration", - "# Configure the all messages below.", - "# ", - "# Enjoying the plugin? Please leave a review on SpigotMC!", - "# Support development: https://github.com/sponsors/imDMK", - "# " -}) -public class MessageConfig extends ConfigSection { - - @Comment("# Sent when all plugin configuration files have been successfully reloaded.") - public Notice reload = Notice.chat("The plugin configuration files have been reloaded. Note that some features may require a full restart to take effect."); - - @Comment("# Sent when an error occurs during plugin configuration reload.") - public Notice reloadError = Notice.chat("Failed to reload plugin configuration files. Please check the console."); - - @Comment("# Message sent when double jump mode is auto-enabled for a player (e.g., on join).") - public Notice autoJumpEnabled = Notice.chat("Double jump mode enabled (by joining)!"); - - @Comment("# Message sent when double jump mode is successfully enabled for the executing player.") - public Notice jumpEnabled = Notice.chat("Double jump mode enabled!"); - - @Comment("# Message sent when double jump mode is successfully enabled for the target player.") - public Notice jumpEnabledTarget = Notice.chat("Double jump mode enabled for the player!"); - - @Comment("# Message sent when double jump mode is successfully disabled for the executing player.") - public Notice jumpDisabled = Notice.chat("Double jump mode disabled!"); - - @Comment("# Message sent when double jump mode is successfully disabled for the target player.") - public Notice jumpDisabledTarget = Notice.chat("Double jump mode disabled for the player!"); - - @Comment("# Message sent when all restrictions are lifted and the player can double jump again.") - public Notice jumpAvailable = Notice.actionbar("You can now double jump!"); - - @Comment("# Sent when double jump is blocked because the player is in a restricted world.") - public Notice worldRestricted = Notice.chat("You are in a disabled world!"); - - @Comment("# Sent when double jump is blocked because the player is in a restricted WorldGuard region.") - public Notice regionRestricted = Notice.chat("You are in a disabled region!"); - - @Comment("# Sent when double jump is blocked due to the player's current game mode.") - public Notice gameModeRestricted = Notice.chat("Your current game mode disables double jump!"); - - @Comment("# Sent when double jump is blocked due to cooldown or delay.") - public Notice jumpDelay = Notice.actionbar("You must wait {TIME} before your next jump!"); - - @Comment("# Message sent when double jump is blocked because the player is gliding (e.g., with elytra)") - public Notice blockedWhileGliding = Notice.actionbar("You cannot use double jump while gliding."); - - @Comment("# Sent when double jump is blocked due to high player ping.") - public Notice playerLagging = Notice.chat("Your ping is too high to use double jump!"); - - @Comment("# Sent when double jump is blocked due to insufficient permissions.") - public Notice jumpPermissionRequired = Notice.chat("You don't have permission to activate double jump!"); - - @Comment("# Sent when a double jump item is successfully added to the target player's inventory.") - public Notice jumpItemAdded = Notice.chat("Added double jump item to player!"); - - @Comment("# Sent when a single double jump item is successfully removed from the player's inventory.") - public Notice jumpItemRemovedSingle = Notice.chat("Removed a double jump item from the player's inventory."); - - @Comment("# Sent when all double jump items are successfully removed from the player's inventory.") - public Notice jumpItemRemovedAll = Notice.chat("Removed all double jump items from the player's inventory."); - - @Comment("# Sent when no double jump item is found in the player's inventory.") - public Notice jumpItemNotFound = Notice.chat("No double jump items found in the player's inventory."); - - @Comment("# Generic error message sent when an unexpected issue occurs.") - public Notice errorOccurred = Notice.chat("An error occurred while performing this action. Please rejoin the server!"); - - @Comment({ - "# Sent when a command is executed without the required permissions.", - "# {PERMISSIONS} - Lists the required permission nodes." - }) - public Notice noPermission = Notice.chat("Missing permissions: {PERMISSIONS}."); - - @Comment({ - "# Sent when a command is executed with invalid arguments.", - "# {USAGE} - Displays the correct command usage." - }) - public Notice invalidUsage = Notice.chat("Invalid usage: {USAGE}."); - - @Comment("# Header message shown when listing multiple valid command usages.") - public Notice invalidUsageHeader = Notice.chat("Invalid usage:"); - - @Comment({ - "# Entry format for each valid command usage.", - "# {USAGE} - Displays the correct command usage." - }) - public Notice invalidUsageEntry = Notice.chat("- {USAGE}"); - - @Override - public @NotNull OkaeriSerdesPack getSerdesPack() { - return registry -> registry.register(new MultificationSerdesPack(NoticeResolverDefaults.createRegistry())); - } - - @Override - public @NotNull String getFileName() { - return "messageConfig.yml"; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/message/MessageResultHandler.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/message/MessageResultHandler.java deleted file mode 100644 index 452670a..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/message/MessageResultHandler.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.message; - -import com.eternalcode.multification.notice.Notice; -import dev.rollczi.litecommands.handler.result.ResultHandler; -import dev.rollczi.litecommands.handler.result.ResultHandlerChain; -import dev.rollczi.litecommands.invocation.Invocation; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -public class MessageResultHandler implements ResultHandler { - - private final MessageService messageService; - - public MessageResultHandler(@NotNull MessageService messageService) { - this.messageService = Objects.requireNonNull(messageService, "messageService cannot be null"); - } - - @Override - public void handle(Invocation invocation, Notice result, ResultHandlerChain chain) { - this.messageService.create() - .viewer(invocation.sender()) - .notice(result) - .send(); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/message/MessageService.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/message/MessageService.java deleted file mode 100644 index f8d70b2..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/message/MessageService.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.message; - -import com.eternalcode.multification.adventure.AudienceConverter; -import com.eternalcode.multification.bukkit.BukkitMultification; -import com.eternalcode.multification.notice.provider.NoticeProvider; -import com.eternalcode.multification.shared.Formatter; -import com.eternalcode.multification.translation.TranslationProvider; -import net.kyori.adventure.platform.AudienceProvider; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.serializer.ComponentSerializer; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; -import java.util.logging.Logger; - -/** - * Service responsible for sending formatted messages to command senders using the configured notice system. - */ -public class MessageService extends BukkitMultification { - - private final Logger logger; - private final MessageConfig messageConfig; - private final AudienceProvider audienceProvider; - private final MiniMessage miniMessage; - - public MessageService( - @NotNull Logger logger, - @NotNull MessageConfig messageConfig, - @NotNull AudienceProvider audienceProvider, - @NotNull MiniMessage miniMessage - ) { - this.logger = Objects.requireNonNull(logger, "logger cannot be null"); - this.messageConfig = Objects.requireNonNull(messageConfig, "messageConfiguration cannot be null"); - this.audienceProvider = Objects.requireNonNull(audienceProvider, "audienceProvider cannot be null"); - this.miniMessage = Objects.requireNonNull(miniMessage, "miniMessage cannot be null"); - } - - @Override - protected @NotNull TranslationProvider translationProvider() { - return locale -> this.messageConfig; - } - - @Override - protected @NotNull ComponentSerializer serializer() { - return this.miniMessage; - } - - @Override - protected @NotNull AudienceConverter audienceConverter() { - return commandSender -> { - if (commandSender instanceof Player player) { - return this.audienceProvider.player(player.getUniqueId()); - } - - return this.audienceProvider.console(); - }; - } - - /** - * Sends a predefined notice message to the given sender. - * - * @param sender the target recipient, must not be {@code null} - * @param notice the notice to send, must not be {@code null} - */ - public void send(@NotNull CommandSender sender, @NotNull NoticeProvider notice) { - this.create().viewer(sender).notice(notice).send(); - } - - /** - * Sends a predefined notice message to the given sender with formatting support. - * - * @param sender the target recipient, must not be {@code null} - * @param notice the notice to send, must not be {@code null} - * @param formatter the formatter used to replace placeholders, must not be {@code null} - */ - public void send(@NotNull CommandSender sender, @NotNull NoticeProvider notice, @NotNull Formatter formatter) { - this.create() - .viewer(sender) - .notice(notice) - .formatter(formatter) - .send(); - } - - /** - * Closes underlying resources used by this service (e.g., audiences). - */ - public void close() { - this.logger.info("Closing MessageService"); - this.audienceProvider.close(); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/placeholder/PlaceholderHook.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/placeholder/PlaceholderHook.java deleted file mode 100644 index c066266..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/placeholder/PlaceholderHook.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.placeholder; - -import com.github.imdmk.doublejump.jump.feature.placeholder.JumpPlaceholderConfig; -import org.bukkit.Server; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -/** - * Hook responsible for detecting and managing PlaceholderAPI integration. - */ -public final class PlaceholderHook { - - private static final String PLACEHOLDER_API_PLUGIN = "PlaceholderAPI"; - - private final Server server; - private final JumpPlaceholderConfig config; - - public PlaceholderHook(@NotNull Server server, @NotNull JumpPlaceholderConfig config) { - this.server = Objects.requireNonNull(server, "server cannot be null"); - this.config = Objects.requireNonNull(config, "config cannot be null"); - } - - /** - * Returns true if integration is enabled in configuration. - * - * @return true if integration should be active - */ - public boolean isIntegrationEnabled() { - return this.config.enabled; - } - - /** - * Returns true if the PlaceholderAPI plugin is installed and enabled. - * - * @return true if PlaceholderAPI plugin is present - */ - public boolean isPluginPresent() { - return this.server.getPluginManager().isPluginEnabled(PLACEHOLDER_API_PLUGIN); - } - - /** - * Returns true if integration is enabled and PlaceholderAPI is present. - * - * @return true if integration is available - */ - public boolean isAvailable() { - return this.isIntegrationEnabled() && this.isPluginPresent(); - } - - /** - * Asserts PlaceholderAPI is available. Throws if not. - * - * @throws IllegalStateException if integration is disabled or PlaceholderAPI is missing - */ - @ApiStatus.Internal - public void assertAvailable() { - if (!this.isAvailable()) { - throw new IllegalStateException("PlaceholderAPI is not available or integration is disabled."); - } - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/placeholder/PlaceholderRegistry.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/placeholder/PlaceholderRegistry.java deleted file mode 100644 index c9fdb60..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/placeholder/PlaceholderRegistry.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.placeholder; - -import me.clip.placeholderapi.expansion.PlaceholderExpansion; -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -/** - * Registry for managing PlaceholderAPI expansions. - * Ensures proper lifecycle management of registered placeholders. - */ -public final class PlaceholderRegistry { - - private final Set registeredExpansions = Collections.synchronizedSet(new HashSet<>()); - - private final PlaceholderHook placeholderHook; - - public PlaceholderRegistry(@NotNull PlaceholderHook placeholderHook) { - this.placeholderHook = Objects.requireNonNull(placeholderHook, "placeholderHook cannot be null"); - } - - /** - * Registers and tracks a {@link PlaceholderExpansion}. - * - * @param expansion the placeholder expansion to register - */ - public void register(@NotNull PlaceholderExpansion expansion) { - if (!this.placeholderHook.isAvailable()) { - return; - } - - if (this.registeredExpansions.add(expansion)) { - expansion.register(); - } - } - - /** - * Unregisters a given {@link PlaceholderExpansion}. - * - * @param expansion the placeholder expansion to unregister - */ - public void unregister(@NotNull PlaceholderExpansion expansion) { - if (this.registeredExpansions.remove(expansion)) { - expansion.unregister(); - } - } - - /** - * Unregisters all tracked {@link PlaceholderExpansion}s. - */ - public void unregisterAll() { - synchronized (this.registeredExpansions) { - for (PlaceholderExpansion expansion : this.registeredExpansions) { - expansion.unregister(); - } - - this.registeredExpansions.clear(); - } - } - - /** - * Returns an unmodifiable view of the currently registered expansions. - * - * @return an unmodifiable set of registered expansions - */ - public Set getRegisteredExpansions() { - return Collections.unmodifiableSet(this.registeredExpansions); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/placeholder/PluginPlaceholder.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/placeholder/PluginPlaceholder.java deleted file mode 100644 index b4f7899..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/placeholder/PluginPlaceholder.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.placeholder; - -import me.clip.placeholderapi.expansion.PlaceholderExpansion; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.panda_lang.utilities.inject.annotations.Inject; - -/** - * Base class for defining custom PlaceholderAPI expansions in the plugin. - *

- * To implement a placeholder, subclass this class and override: - *

    - *
  • {@link #getIdentifier()}
  • - *
  • {@link #onRequestExpansion(OfflinePlayer, String)}
  • - *
- * This class handles version/author forwarding and simplifies the request logic. - */ -public abstract class PluginPlaceholder extends PlaceholderExpansion { - - @Inject private Plugin plugin; - - /** - * Returns the unique identifier for this placeholder. - *

- * For example, if the identifier is {@code "doublejump"}, then - * {@code %doublejump_%} will trigger this expansion. - * - * @return the identifier of the placeholder (without % signs) - */ - @Override - public abstract @NotNull String getIdentifier(); - - /** - * Returns the list of authors as a comma-separated string from plugin.yml. - * - * @return the author(s) of the plugin - */ - @Override - public final @NotNull String getAuthor() { - return String.join(", ", this.plugin.getDescription().getAuthors()); - } - - /** - * Returns the plugin version from plugin.yml. - * - * @return the plugin version - */ - @Override - public final @NotNull String getVersion() { - return this.plugin.getDescription().getVersion(); - } - - /** - * Handles a placeholder request. - *

- * Subclasses must implement this method to provide custom logic. - * - * @param player the player requesting the placeholder (may be offline) - * @param params the placeholder parameters (the string after the identifier) - * @return the result of the placeholder, or {@code null} to return nothing - */ - protected abstract @Nullable String onRequestExpansion(@NotNull OfflinePlayer player, @NotNull String params); - - /** - * Called internally by PlaceholderAPI when a placeholder is requested via {@link OfflinePlayer}. - * - * @param player the offline player requesting the placeholder - * @param params the parameters after the identifier - * @return the placeholder result - */ - @Override - public final @Nullable String onRequest(OfflinePlayer player, @NotNull String params) { - return this.onRequestExpansion(player, params); - } - - /** - * Called internally by PlaceholderAPI when a placeholder is requested via {@link Player}. - * - * @param player the online player requesting the placeholder - * @param params the parameters after the identifier - * @return the placeholder result - */ - @Override - public final @Nullable String onPlaceholderRequest(Player player, @NotNull String params) { - return this.onRequestExpansion(player, params); - } - - /** - * Ensures this expansion persists across PlaceholderAPI reloads. - * - * @return {@code true} to prevent unregistering on reload - */ - @Override - public final boolean persist() { - return true; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/scheduler/BukkitTaskScheduler.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/scheduler/BukkitTaskScheduler.java deleted file mode 100644 index 0150cb8..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/scheduler/BukkitTaskScheduler.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.scheduler; - -import com.github.imdmk.doublejump.task.TaskScheduler; -import org.bukkit.Server; -import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitScheduler; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -public final class BukkitTaskScheduler implements TaskScheduler { - - private final Plugin plugin; - private final BukkitScheduler scheduler; - - public BukkitTaskScheduler(@NotNull Plugin plugin, @NotNull Server server) { - this.plugin = Objects.requireNonNull(plugin, "plugin cannot be null"); - this.scheduler = Objects.requireNonNull(server.getScheduler(), "server scheduler cannot be null"); - } - - @Override - public void run(@NotNull Runnable runnable) { - this.scheduler.runTask(this.plugin, runnable); - } - - @Override - public void runAsync(@NotNull Runnable runnable) { - this.scheduler.runTaskAsynchronously(this.plugin, runnable); - } - - @Override - public void runLater(@NotNull Runnable runnable, long delay) { - this.scheduler.runTaskLater(this.plugin, runnable, delay); - } - - @Override - public void runLaterAsync(@NotNull Runnable runnable, long delay) { - this.scheduler.runTaskLaterAsynchronously(this.plugin, runnable, delay); - } - - @Override - public void runTimerAsync(@NotNull Runnable runnable, long delay, long period) { - this.scheduler.runTaskTimerAsynchronously(this.plugin, runnable, delay, period); - } - - @Override - public void cancelTask(int taskId) { - this.scheduler.cancelTask(taskId); - } - - @Override - public void shutdown() { - this.plugin.getLogger().info("Shutting down TaskScheduler"); - this.scheduler.cancelTasks(this.plugin); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/update/UpdateController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/update/UpdateController.java deleted file mode 100644 index 1aa4897..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/update/UpdateController.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.update; - -import com.eternalcode.gitcheck.GitCheckResult; -import com.eternalcode.gitcheck.git.GitException; -import com.eternalcode.multification.notice.Notice; -import com.github.imdmk.doublejump.config.PluginConfig; -import com.github.imdmk.doublejump.infrastructure.message.MessageService; -import com.github.imdmk.doublejump.task.TaskScheduler; -import com.github.imdmk.doublejump.util.DurationUtil; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -public class UpdateController implements Listener { - - private static final String PREFIX = "[DoubleJump] "; - private static final Notice UPDATE_AVAILABLE = Notice.chat( - " ", - PREFIX + "A new update is available!", - "- We strongly recommend downloading it!", - " " - ); - private static final Notice UPDATE_EXCEPTION = Notice.chat( - " ", - PREFIX + "An error occurred while checking for plugin update! Next update check: {UPDATE_CHECK_INTERVAL}", - " " - ); - - @Inject private Logger logger; - @Inject private PluginConfig pluginConfig; - @Inject private MessageService messageService; - @Inject private UpdateService updateService; - @Inject private TaskScheduler taskScheduler; - - @EventHandler(priority = EventPriority.HIGHEST) - void onPlayerJoin(PlayerJoinEvent event) { - if (!this.pluginConfig.checkUpdate) { - return; - } - - Player player = event.getPlayer(); - - if (!player.isOp()) { - return; - } - - if (this.updateService.shouldCheck()) { - this.taskScheduler.runAsync(() -> this.checkForUpdate(player)); - } - } - - private void checkForUpdate(@NotNull Player player) { - try { - GitCheckResult result = this.updateService.check(); - if (result.isUpToDate()) { - return; - } - - this.sendNotice(player, UPDATE_AVAILABLE); - } - catch (GitException exception) { - this.logger.log(Level.SEVERE, "An error occurred while checking for update", exception); - this.sendNotice(player, UPDATE_EXCEPTION); - } - } - - private void sendNotice(@NotNull Player player, @NotNull Notice notice) { - this.messageService.create() - .notice(notice) - .placeholder("{UPDATE_CHECK_INTERVAL}", DurationUtil.format(this.pluginConfig.updateInterval)) - .viewer(player) - .send(); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/update/UpdateService.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/update/UpdateService.java deleted file mode 100644 index 627954b..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/infrastructure/update/UpdateService.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.github.imdmk.doublejump.infrastructure.update; - -import com.eternalcode.gitcheck.GitCheck; -import com.eternalcode.gitcheck.GitCheckResult; -import com.eternalcode.gitcheck.git.GitRepository; -import com.eternalcode.gitcheck.git.GitTag; -import com.github.imdmk.doublejump.config.PluginConfig; -import org.bukkit.plugin.PluginDescriptionFile; -import org.jetbrains.annotations.NotNull; - -import java.time.Instant; -import java.util.Objects; - -public class UpdateService { - - private static final GitRepository GIT_REPOSITORY = GitRepository.of("imDMK", "DoubleJump"); - private static final GitCheck GIT_CHECK = new GitCheck(); - - private final PluginConfig pluginConfig; - private final PluginDescriptionFile pluginDescriptionFile; - - private Instant latestCheck; - - public UpdateService( - @NotNull PluginConfig pluginConfig, - @NotNull PluginDescriptionFile descriptionFile - ) { - this.pluginConfig = Objects.requireNonNull(pluginConfig, "pluginConfiguration cannot be null"); - this.pluginDescriptionFile = Objects.requireNonNull(descriptionFile, "pluginDescriptionFile cannot be null"); - } - - public @NotNull GitCheckResult check() { - this.latestCheck = Instant.now(); - - GitTag tag = GitTag.of("v" + this.pluginDescriptionFile.getVersion()); - return GIT_CHECK.checkRelease(GIT_REPOSITORY, tag); - } - - public boolean shouldCheck() { - if (this.latestCheck == null) { - return true; - } - - Instant nextCheckTime = this.latestCheck.plus(this.pluginConfig.updateInterval); - return Instant.now().isAfter(nextCheckTime); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/JumpConfig.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/JumpConfig.java deleted file mode 100644 index ac3531c..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/JumpConfig.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.github.imdmk.doublejump.jump; - -import com.github.imdmk.doublejump.config.ConfigSection; -import com.github.imdmk.doublejump.config.MissingConfigValueException; -import com.github.imdmk.doublejump.config.serializer.ComponentSerializer; -import com.github.imdmk.doublejump.config.serializer.EnchantmentSerializer; -import com.github.imdmk.doublejump.jump.feature.block.JumpBlockConfiguration; -import com.github.imdmk.doublejump.jump.feature.block.JumpBlockSerializer; -import com.github.imdmk.doublejump.jump.feature.item.configuration.JumpItemConfiguration; -import com.github.imdmk.doublejump.jump.feature.item.configuration.JumpItemSerializer; -import com.github.imdmk.doublejump.jump.feature.placeholder.JumpPlaceholderConfig; -import com.github.imdmk.doublejump.jump.feature.restriction.JumpRestrictionConfig; -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocity; -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocitySerializer; -import eu.okaeri.configs.annotation.Comment; -import eu.okaeri.configs.annotation.Header; -import eu.okaeri.configs.serdes.OkaeriSerdesPack; -import eu.okaeri.configs.serdes.commons.SerdesCommons; -import org.jetbrains.annotations.NotNull; - -import java.time.Duration; -import java.util.Map; - -@Header({ - "# ", - "# DoubleJump Premium - Jump Configuration", - "# ", - "# This configuration file manages all settings related to the double jump feature.", - "# It controls how and when double jump is enabled, velocity applied, cooldowns,", - "# item and block interactions, as well as any restrictions that affect the ability.", - "# ", - "# Enjoying the plugin? Please leave a review on SpigotMC!", - "# Support development: https://github.com/sponsors/imDMK", - "# " -}) -public class JumpConfig extends ConfigSection { - - @Comment("# Automatically enables double jump for players when they join the server.") - public boolean autoEnableOnJoin = true; - - @Comment("# Automatically enables double jump for players with permission.") - public String autoEnableForPermission = "doublejump.join"; - - @Comment("# Determines whether players receive fall damage after performing a double jump.") - public boolean applyFallDamage = true; - - @Comment({ - "# Mapping of permissions or keys to their respective double jump velocity settings.", - "# Use the 'default' key as a fallback if no specific permission matches.", - "# Velocity settings can apply based on context such as player join or command." - }) - public Map velocities = Map.of( - "default", new JumpVelocity(0.3, 0.6), - "doublejump.join.vip", new JumpVelocity(0.6, 0.9), - "doublejump.join.supervip", new JumpVelocity(0.9, 1.2) - ); - - @Comment("# Cooldown duration that a player must wait before performing another double jump. Set to 0 to disable.") - public Map cooldowns = Map.of( - "default", Duration.ofSeconds(3L), - "doublejump.cooldown.vip", Duration.ofSeconds(2L), - "doublejump.cooldown.supervip", Duration.ofSeconds(1L) - ); - - @Comment("# Configuration for restrictions that limit or disable double jumping under certain conditions.") - public JumpRestrictionConfig restrictions = new JumpRestrictionConfig(); - - @Comment("# Configuration for the item that activates the double jump ability.") - public JumpItemConfiguration item = new JumpItemConfiguration(); - - @Comment("# Configuration for special blocks that interact with the double jump feature.") - public JumpBlockConfiguration blocks = new JumpBlockConfiguration(); - - @Comment("# Configuration for jump placeholders that can be used in-game.") - public JumpPlaceholderConfig placeholders = new JumpPlaceholderConfig(); - - @Override - public void loadProcessedProperties() { - if (!this.velocities.containsKey("default")) { - throw new MissingConfigValueException("velocities.default", "Default jump velocity must be defined for players without specific permissions."); - } - - if (!this.cooldowns.containsKey("default")) { - throw new MissingConfigValueException("cooldowns.default", "Default jump cooldown must be defined for players without specific permissions."); - } - } - - @Override - public @NotNull OkaeriSerdesPack getSerdesPack() { - return registry -> { - registry.register(new SerdesCommons()); - registry.register(new ComponentSerializer()); - - registry.register(new JumpVelocitySerializer()); - - registry.register(new JumpItemSerializer()); - registry.register(new EnchantmentSerializer()); - - registry.register(new JumpBlockSerializer()); - }; - } - - @Override - public @NotNull String getFileName() { - return "jumpConfig.yml"; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/PlayerFlyingService.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/PlayerFlyingService.java deleted file mode 100644 index 336a9ca..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/PlayerFlyingService.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.github.imdmk.doublejump.jump; - -import com.github.imdmk.doublejump.task.TaskScheduler; -import com.github.imdmk.doublejump.util.GameModeUtil; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -/** - * Service responsible for managing the flight state of players. - *

- * All interactions with Bukkit API (e.g., setAllowFlight, setFlying) are dispatched - * synchronously via the {@link TaskScheduler}, as required by Bukkit's threading model. - *

- */ -public class PlayerFlyingService { - - @Inject private TaskScheduler taskScheduler; - - /** - * Enables flight for the player without altering flying state. - *

- * This must be run synchronously due to Bukkit thread safety requirements. - *

- * @param player The player to modify. - */ - public void enable(@NotNull Player player) { - this.taskScheduler.run(() -> player.setAllowFlight(true)); - } - - /** - * Disables flight for the player and sets allowFlight according to player's gameMode - * - * @param player The player to modify. - */ - public void disable(@NotNull Player player) { - this.disable(player, GameModeUtil.isFlyingGameMode(player.getGameMode())); - } - - /** - * Disables the player's current flying state. - * The player's permission to fly in the future is set according to the provided flag. - *

- * This must be run synchronously due to Bukkit thread safety requirements. - *

- * @param player the player whose flying state will be disabled; must not be null - * @param allowFlight true to allow the player to start flying later, false to revoke flight permission - */ - public void disable(@NotNull Player player, boolean allowFlight) { - this.taskScheduler.run(() -> { - player.setFlying(false); - player.setAllowFlight(allowFlight); - }); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/controller/DoubleJumpController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/controller/DoubleJumpController.java deleted file mode 100644 index 0b8ac55..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/controller/DoubleJumpController.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.github.imdmk.doublejump.jump.controller; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.event.DoubleJumpEvent; -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerToggleFlightEvent; -import org.bukkit.util.Vector; - -/** - * Controller responsible for handling the double jump mechanic, - * including detecting flight toggle and executing the double jump action. - */ -public class DoubleJumpController extends PluginListener { - - /** - * Executes the double jump when the DoubleJumpEvent is triggered. - * Applies velocity to the player and disables flight. - * - * @param event Custom DoubleJumpEvent. - */ - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onDoubleJump(DoubleJumpEvent event) { - Player player = event.getPlayer(); - JumpVelocity velocity = event.getJumpVelocity(); - - Vector vector = player.getLocation().getDirection() - .multiply(velocity.horizontalBoost()) - .setY(velocity.verticalBoost()); - - this.flyingService.disable(player, false); - player.setVelocity(vector); - } - - /** - * Handles the player toggling flight event to trigger double jump. - * Cancels the default flight toggle and calls a custom DoubleJumpEvent. - * - * @param event PlayerToggleFlightEvent from Bukkit API. - */ - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onToggleFlight(PlayerToggleFlightEvent event) { - Player player = event.getPlayer(); - - this.jumpCache.getActive(player.getUniqueId()) - .ifPresent(jump -> { - event.setCancelled(true); - this.eventCaller.callEvent(new DoubleJumpEvent(player, jump)); - }); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/controller/FlightStateController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/controller/FlightStateController.java deleted file mode 100644 index c2ebc30..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/controller/FlightStateController.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.github.imdmk.doublejump.jump.controller; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.JumpActivationType; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.server.ServerLoadEvent; - -public class FlightStateController extends PluginListener { - - @EventHandler(priority = EventPriority.LOW) - void onPlayerQuit(PlayerQuitEvent event) { - this.jumpCache.ifActive(event.getPlayer().getUniqueId(), jump -> this.flyingService.disable(event.getPlayer())); - } - - @EventHandler(priority = EventPriority.LOW) - void onServerReload(ServerLoadEvent event) { - if (event.getType() != ServerLoadEvent.LoadType.RELOAD) { - return; - } - - this.server.getOnlinePlayers().forEach(this.flyingService::disable); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onPlayerMove(PlayerMoveEvent event) { - Player player = event.getPlayer(); - - Location from = event.getFrom(); - Location to = event.getTo(); - if (to == null || to.getBlockX() == from.getBlockX() - && to.getBlockY() == from.getBlockY() - && to.getBlockZ() == from.getBlockZ()) { - return; - } - - this.jumpCache.ifActive(player.getUniqueId(), jump -> { - if (jump.isJumpAllowed() && !player.getAllowFlight()) { - this.flyingService.enable(player); - } - }); - } - - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - void onPlayerDeath(PlayerDeathEvent event) { - Player player = event.getEntity(); - - this.jumpCache.getActive(player.getUniqueId()) - .filter(jump -> jump.isActivationType(JumpActivationType.JOIN) || jump.isActivationType(JumpActivationType.COMMAND)) - .ifPresent(jump -> { - jump.setJumpAllowed(true); - jump.setLastNotifiedReason(null); - jump.setNextAllowedJump(null); - - this.flyingService.enable(player); - }); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/controller/JumpPlayerSessionController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/controller/JumpPlayerSessionController.java deleted file mode 100644 index d203b51..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/controller/JumpPlayerSessionController.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.github.imdmk.doublejump.jump.controller; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.JumpPlayer; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.server.ServerLoadEvent; -import org.jetbrains.annotations.NotNull; - -import java.util.UUID; - -public class JumpPlayerSessionController extends PluginListener { - - /** - * Creates a JumpPlayer session when a player joins the server. - * - * @param event Player join event. - */ - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onPlayerJoin(PlayerJoinEvent event) { - this.createJumpPlayer(event.getPlayer()); - } - - /** - * Reinitializes JumpPlayer sessions for all online players on server reload. - * - * @param event The server load event. - */ - @EventHandler(priority = EventPriority.HIGHEST) - void onServerReload(ServerLoadEvent event) { - if (event.getType() != ServerLoadEvent.LoadType.RELOAD) { - return; - } - - this.server.getOnlinePlayers().forEach(this::createJumpPlayer); - } - - /** - * Removes a player's JumpPlayer session when they quit the server. - * - * @param event Player quit event. - */ - @EventHandler(priority = EventPriority.HIGHEST) - void onPlayerQuit(PlayerQuitEvent event) { - this.jumpCache.remove(event.getPlayer().getUniqueId()); - } - - /** - * Adds a player to the jump cache by creating a new JumpPlayer instance. - * - * @param player The player to add. - */ - private void createJumpPlayer(@NotNull Player player) { - UUID uuid = player.getUniqueId(); - this.jumpCache.put(uuid, new JumpPlayer(uuid, player.getName())); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/block/JumpBlock.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/block/JumpBlock.java deleted file mode 100644 index 3e3601d..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/block/JumpBlock.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.block; - -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocity; -import org.bukkit.Material; -import org.jetbrains.annotations.NotNull; - -/** - * Represents a block that grants a double jump boost when a player stands on it. - * - * @param type the material type of the block - * @param jumpVelocity the custom jump velocity applied when jumping from this block - */ -public record JumpBlock(@NotNull Material type, @NotNull JumpVelocity jumpVelocity) { -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/block/JumpBlockConfiguration.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/block/JumpBlockConfiguration.java deleted file mode 100644 index 199b3fd..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/block/JumpBlockConfiguration.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.block; - -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocity; -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.annotation.Comment; -import org.bukkit.Material; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.Optional; - -public class JumpBlockConfiguration extends OkaeriConfig { - - @Comment("# Determines whether block-based double jump is enabled") - public boolean enabled = true; - - @Comment({ - "# List of configured jump blocks", - "# Each block defines a material and custom jump velocity" - }) - public List blocks = List.of( - new JumpBlock(Material.DIAMOND_BLOCK, JumpVelocity.of(0.9, 0.6)), - new JumpBlock(Material.GOLD_BLOCK, JumpVelocity.of(0.3, 0.3)) - ); - - /** - * Finds a jump block configuration by its material. - * - * @param material the block material to search for - * @return optional jump block configuration - */ - public Optional getJumpBlock(@NotNull Material material) { - return this.blocks.stream() - .filter(jumpBlock -> jumpBlock.type().equals(material)) - .findFirst(); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/block/JumpBlockController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/block/JumpBlockController.java deleted file mode 100644 index b2eaea2..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/block/JumpBlockController.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.block; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.JumpActivationType; -import com.github.imdmk.doublejump.jump.JumpPlayer; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; - -public class JumpBlockController extends PluginListener { - - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - void onPlayerDeath(PlayerDeathEvent event) { - Player player = event.getEntity(); - - if (!this.jumpConfig.blocks.enabled) { - return; - } - - this.jumpCache.get(player.getUniqueId()) - .filter(jump -> jump.isActivationType(JumpActivationType.BLOCK)) - .ifPresent(jump -> { - if (this.getJumpBlock(this.getBlockBelow(player).getType()).isPresent()) { - return; - } - - this.deactivateDoubleJump(player, jump); - }); - } - - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - void onPlayerMove(PlayerMoveEvent event) { - Player player = event.getPlayer(); - - if (!this.jumpConfig.blocks.enabled) { - return; - } - - Location from = event.getFrom(); - Location to = event.getTo(); - if (this.isSameBlockPosition(from, to)) { - return; - } - - Location below = player.getLocation().subtract(0, 1, 0); - - this.jumpCache.get(player.getUniqueId()) - .filter(JumpPlayer::isJumpAllowed) - .ifPresent(jumpPlayer -> - this.getJumpBlock(below.getBlock().getType()) - .ifPresentOrElse( - block -> this.activateJumpBlock(player, jumpPlayer, block), - () -> this.deactivateIfBlockActive(player, jumpPlayer, from, to) - )); - } - - /** - * Activates double jump for the player based on the given jump block. - */ - private void activateJumpBlock(@NotNull Player player, @NotNull JumpPlayer jump, @NotNull JumpBlock block) { - if (jump.getJumpVelocity().equals(block.jumpVelocity())) { - return; - } - - jump.setActivationType(JumpActivationType.BLOCK); - jump.setActive(true); - jump.setJumpVelocity(block.jumpVelocity()); - - this.flyingService.enable(player); - } - - /** - * Deactivates block-based double jump if conditions are met (e.g., stepping off the block). - */ - private void deactivateIfBlockActive(@NotNull Player player, @NotNull JumpPlayer jumpPlayer, @NotNull Location from, @NotNull Location to) { - if (!jumpPlayer.isActivationType(JumpActivationType.BLOCK)) { - return; - } - - Material belowMaterial = this.getBlockBelow(player).getType(); - boolean isJumpBlock = this.getJumpBlock(belowMaterial).isPresent(); - boolean isAir = belowMaterial == Material.AIR; - - boolean isDescending = to.getY() < from.getY(); - - if (isDescending && (!isJumpBlock || isAir)) { - this.deactivateDoubleJump(player, jumpPlayer); - } - } - - /** - * Gets the JumpBlock instance for a material, if configured. - */ - private Optional getJumpBlock(@NotNull Material material) { - return this.jumpConfig.blocks.getJumpBlock(material); - } - - /** - * Returns the block directly under the player, or one block further down if first is air. - */ - private Block getBlockBelow(@NotNull Player player) { - Location below = player.getLocation().subtract(0, 1, 0); - Block block = below.getBlock(); - - if (block.getType() != Material.AIR) { - return block; - } - - return player.getLocation().subtract(0, 2, 0).getBlock(); - } - - /** - * Disables the jump and resets activation type. - */ - private void deactivateDoubleJump(@NotNull Player player, @NotNull JumpPlayer jumpPlayer) { - jumpPlayer.setActive(false); - jumpPlayer.setJumpAllowed(true); - jumpPlayer.setActivationType(JumpActivationType.NONE); - - this.flyingService.disable(player); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/block/JumpBlockSerializer.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/block/JumpBlockSerializer.java deleted file mode 100644 index 0b9f413..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/block/JumpBlockSerializer.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.block; - -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocity; -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import org.bukkit.Material; -import org.jetbrains.annotations.NotNull; - -public class JumpBlockSerializer implements ObjectSerializer { - - @Override - public boolean supports(@NotNull Class type) { - return JumpBlock.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NotNull JumpBlock block, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) { - data.add("material", block.type(), Material.class); - data.add("jumpVelocity", block.jumpVelocity(), JumpVelocity.class); - } - - @Override - public JumpBlock deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) { - Material material = data.get("material", Material.class); - JumpVelocity jumpVelocity = data.get("jumpVelocity", JumpVelocity.class); - return new JumpBlock(material, jumpVelocity); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpItemCommand.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpItemCommand.java deleted file mode 100644 index 5486ae9..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpItemCommand.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.command; - -import com.github.imdmk.doublejump.infrastructure.message.MessageService; -import com.github.imdmk.doublejump.jump.feature.item.JumpItemService; -import dev.rollczi.litecommands.annotations.argument.Arg; -import dev.rollczi.litecommands.annotations.command.Command; -import dev.rollczi.litecommands.annotations.context.Context; -import dev.rollczi.litecommands.annotations.execute.Execute; -import dev.rollczi.litecommands.annotations.permission.Permission; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -@Command(name = "doublejump item") -@Permission("command.doublejump.item") -public class JumpItemCommand { - - @Inject private MessageService messageService; - @Inject private JumpItemService jumpItemService; - - @Execute(name = "give") - void give(@Context CommandSender sender, @Arg Player target) { - target.getInventory().addItem(this.jumpItemService.getJumpItem().asItemStack()); - this.messageService.send(sender, notice -> notice.jumpItemAdded); - } - - @Execute(name = "remove") - void remove(@Context CommandSender sender, @Arg Player target) { - if (!this.hasJumpItem(target)) { - this.messageService.send(sender, notice -> notice.jumpItemNotFound); - return; - } - - target.getInventory().removeItem(this.jumpItemService.getJumpItem().asItemStack()); - this.messageService.send(sender, notice -> notice.jumpItemRemovedSingle); - } - - @Execute(name = "remove-all") - void removeAll(@Context CommandSender sender, @Arg Player target) { - if (!this.hasJumpItem(target)) { - this.messageService.send(sender, notice -> notice.jumpItemNotFound); - return; - } - - for (ItemStack item : target.getInventory().getContents()) { - if (this.jumpItemService.isJumpItem(item)) { - target.getInventory().removeItem(item); - } - } - - this.messageService.send(sender, notice -> notice.jumpItemRemovedAll); - } - - private boolean hasJumpItem(@NotNull Player player) { - return player.getInventory().contains(this.jumpItemService.getJumpItem().asItemStack()); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpReloadCommand.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpReloadCommand.java deleted file mode 100644 index 8ca0ee8..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpReloadCommand.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.command; - -import com.github.imdmk.doublejump.config.ConfigManager; -import com.github.imdmk.doublejump.infrastructure.message.MessageService; -import dev.rollczi.litecommands.annotations.command.Command; -import dev.rollczi.litecommands.annotations.context.Context; -import dev.rollczi.litecommands.annotations.execute.Execute; -import dev.rollczi.litecommands.annotations.permission.Permission; -import org.bukkit.command.CommandSender; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -@Command(name = "doublejump reload") -@Permission("command.doublejump.reload") -public class JumpReloadCommand { - - @Inject private Logger logger; - @Inject private ConfigManager configManager; - @Inject private MessageService messageService; - - @Execute - void reload(@Context CommandSender sender) { - this.configManager.reloadAll() - .thenAccept(v -> this.messageService.send(sender, notice -> notice.reload)) - .exceptionally(throwable -> { - this.messageService.send(sender, notice -> notice.reloadError); - this.logger.log(Level.SEVERE, "Failed to reload plugin configuration.", throwable); - return null; - }); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpTargetCommand.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpTargetCommand.java deleted file mode 100644 index 91d70a5..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpTargetCommand.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.command; - -import com.github.imdmk.doublejump.infrastructure.message.MessageService; -import com.github.imdmk.doublejump.jump.JumpActivationType; -import com.github.imdmk.doublejump.jump.JumpPlayer; -import com.github.imdmk.doublejump.jump.PlayerFlyingService; -import com.github.imdmk.doublejump.jump.cache.JumpPlayerCache; -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocityService; -import dev.rollczi.litecommands.annotations.argument.Arg; -import dev.rollczi.litecommands.annotations.command.Command; -import dev.rollczi.litecommands.annotations.context.Context; -import dev.rollczi.litecommands.annotations.execute.Execute; -import dev.rollczi.litecommands.annotations.permission.Permission; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; -import org.panda_lang.utilities.inject.annotations.PostConstruct; - -import java.util.Map; -import java.util.function.Consumer; - -@Command(name = "doublejump") -@Permission("command.doublejump.target") -public class JumpTargetCommand { - - @Inject private MessageService messageService; - @Inject private JumpPlayerCache jumpCache; - @Inject private JumpVelocityService jumpVelocityService; - @Inject private PlayerFlyingService flyingService; - - private Map> toggleNotifiers; - - @PostConstruct - public void postConstruct() { - this.toggleNotifiers = Map.of( - true, sender -> this.messageService.send(sender, notice -> notice.jumpEnabledTarget), - false, sender -> this.messageService.send(sender, notice -> notice.jumpDisabledTarget) - ); - } - - @Execute(name = "enable-for") - void enableFor(@Context CommandSender sender, @Arg Player target) { - JumpPlayer jump = this.jumpCache.getOrThrow(target.getUniqueId()); - this.setDoubleJump(sender, target, jump, true); - } - - @Execute(name = "disable-for") - void disableFor(@Context CommandSender sender, @Arg Player target) { - JumpPlayer jump = this.jumpCache.getOrThrow(target.getUniqueId()); - this.setDoubleJump(sender, target, jump, false); - } - - /** - * Sets the double jump state for a player and updates related flight settings. - * - * @param target the target player - * @param jump the player's jump data - * @param newState {@code true} to enable double jump, {@code false} to disable - */ - private void setDoubleJump(@NotNull CommandSender sender, @NotNull Player target, @NotNull JumpPlayer jump, boolean newState) { - jump.setActive(newState); - jump.setJumpAllowed(true); - - if (newState) { - jump.setActivationType(JumpActivationType.COMMAND); - jump.setJumpVelocity(this.jumpVelocityService.forPlayer(target)); - - this.flyingService.enable(target); - } - else { - jump.setActivationType(JumpActivationType.NONE); - this.flyingService.disable(target); - } - - this.toggleNotifiers.get(newState).accept(sender); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpToggleCommand.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpToggleCommand.java deleted file mode 100644 index 0660a1f..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpToggleCommand.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.command; - -import com.github.imdmk.doublejump.infrastructure.message.MessageService; -import com.github.imdmk.doublejump.jump.JumpActivationType; -import com.github.imdmk.doublejump.jump.JumpPlayer; -import com.github.imdmk.doublejump.jump.PlayerFlyingService; -import com.github.imdmk.doublejump.jump.cache.JumpPlayerCache; -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocityService; -import dev.rollczi.litecommands.annotations.command.Command; -import dev.rollczi.litecommands.annotations.context.Context; -import dev.rollczi.litecommands.annotations.execute.Execute; -import dev.rollczi.litecommands.annotations.permission.Permission; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; -import org.panda_lang.utilities.inject.annotations.PostConstruct; - -import java.util.Map; -import java.util.function.Consumer; - -@Command(name = "doublejump") -@Permission("command.doublejump") -public class JumpToggleCommand { - - @Inject private MessageService messageService; - @Inject private JumpPlayerCache jumpCache; - @Inject private JumpVelocityService jumpVelocityService; - @Inject private PlayerFlyingService flyingService; - - private Map> toggleNotifiers; - - @PostConstruct - public void postConstruct() { - this.toggleNotifiers = Map.of( - true, player -> this.messageService.send(player, notice -> notice.jumpEnabled), - false, player -> this.messageService.send(player, notice -> notice.jumpDisabled) - ); - } - - @Execute - void toggle(@Context Player player) { - this.toggleJump(player, this.jumpCache.getOrThrow(player.getUniqueId())); - } - - /** - * Toggles the player's double-jump state, manages flight accordingly, - * and dispatches the correct notification based on the new state. - * - * @param player the player whose state is toggled - * @param jump the JumpPlayer model for this player - */ - private void toggleJump(@NotNull Player player, @NotNull JumpPlayer jump) { - boolean wasActive = jump.isActive(); - boolean newState = !wasActive; - - jump.setActive(newState); - jump.setJumpAllowed(true); - - if (wasActive) { - jump.setActivationType(JumpActivationType.NONE); - this.flyingService.disable(player); - } - else { - jump.setActivationType(JumpActivationType.COMMAND); - jump.setJumpVelocity(this.jumpVelocityService.forPlayer(player)); - - this.flyingService.enable(player); - } - - this.toggleNotifiers.get(newState).accept(player); - } - -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpVisualCommand.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpVisualCommand.java deleted file mode 100644 index 01b4e99..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/command/JumpVisualCommand.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.command; - -import com.github.imdmk.doublejump.infrastructure.gui.GuiManager; -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisualService; -import com.github.imdmk.doublejump.jump.feature.visual.gui.JumpVisualGui; -import dev.rollczi.litecommands.annotations.command.Command; -import dev.rollczi.litecommands.annotations.context.Context; -import dev.rollczi.litecommands.annotations.execute.Execute; -import dev.rollczi.litecommands.annotations.permission.Permission; -import org.bukkit.entity.Player; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -@Command(name = "doublejump visual") -@Permission("command.doublejump.visual") -public class JumpVisualCommand { - - @Inject private Logger logger; - @Inject private JumpVisualService visualService; - @Inject private GuiManager guiManager; - - @Execute - void openGui(@Context Player player) { - this.visualService.getOrCreate(player.getUniqueId()) - .thenAccept(visual -> this.guiManager.openGui(JumpVisualGui.GUI_IDENTIFIER, player, visual)) - .exceptionally(throwable -> { - this.logger.log(Level.SEVERE, "An error occurred while opening the gui", throwable); - return null; - }); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/fall/JumpFallDamageController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/fall/JumpFallDamageController.java deleted file mode 100644 index 904fd49..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/fall/JumpFallDamageController.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.fall; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.jetbrains.annotations.NotNull; - -/** - * This controller handles two related responsibilities regarding fall damage and flight state. - */ -public class JumpFallDamageController extends PluginListener { - - /** - * The minimum fall distance (in blocks) a player must fall - * before taking fall damage, matching Minecraft's vanilla threshold. - */ - private static final float MIN_FALL_DISTANCE_FOR_DAMAGE = 4.0F; - - /** - * Cancels fall damage for players in the jump cache if fall damage is disabled in the configuration. - * This method listens to damage events and prevents fall damage by cancelling the event and setting damage to zero. - * - * @param event The EntityDamageEvent to handle. - */ - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - void onPlayerDamage(EntityDamageEvent event) { - if (this.jumpConfig.applyFallDamage) { - return; - } - - if (event.getCause() != EntityDamageEvent.DamageCause.FALL - || !(event.getEntity() instanceof Player player)) { - return; - } - - if (this.jumpCache.isActive(player.getUniqueId())) { - event.setCancelled(true); - event.setDamage(0); - } - } - - /** - * Re-enables fall damage by disabling flight allowance for players who have landed, - * assuming fall damage is enabled in the configuration. - * This method listens to player movement to detect when players should take fall damage again. - * - * @param event The PlayerMoveEvent to handle. - */ - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - void onPlayerMove(PlayerMoveEvent event) { - if (!this.jumpConfig.applyFallDamage) { - return; - } - - Player player = event.getPlayer(); - if (player.isFlying() || !this.jumpCache.isActive(player.getUniqueId())) { - return; - } - - if (this.shouldReenableFallDamage(player)) { - this.flyingService.disable(player, false); - } - } - - /** - * Checks whether the player should have fall damage re-enabled. - * The player must have fallen at least the minimum required distance and be standing on a non-air block. - * - * @param player The player to check. - * @return true, if fall damage should be re-enabled. - */ - private boolean shouldReenableFallDamage(@NotNull Player player) { - return this.hasFallenEnough(player) && this.isStandingOnSolidBlock(player); - } - - /** - * Checks if the player has fallen at least the minimum distance to receive fall damage. - * - * @param player The player to check. - * @return true, if fall distance is greater than or equal to a minimum threshold. - */ - private boolean hasFallenEnough(@NotNull Player player) { - return player.getFallDistance() >= MIN_FALL_DISTANCE_FOR_DAMAGE; - } - - /** - * Checks if the block directly beneath the player is solid (not air). - * - * @param player The player to check. - * @return true, if the block below the player is not air. - */ - private boolean isStandingOnSolidBlock(@NotNull Player player) { - Location below = player.getLocation().clone().subtract(0, 1, 0); - return below.getBlock().getType() != Material.AIR; - } - -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/JumpItem.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/JumpItem.java deleted file mode 100644 index dd0844f..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/JumpItem.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item; - -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocity; -import com.github.imdmk.doublejump.util.ComponentUtil; -import dev.triumphteam.gui.builder.item.ItemBuilder; -import net.kyori.adventure.text.Component; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public record JumpItem(@NotNull Material material, - @NotNull Component name, List lore, - @NotNull JumpVelocity jumpVelocity, - List flags, Map enchantments) { - - @Override - public @NotNull Material material() { - return this.material; - } - - @Override - public @NotNull Component name() { - return this.name; - } - - @Override - public @NotNull List lore() { - if (this.lore == null) { - return Collections.emptyList(); - } - - return Collections.unmodifiableList(this.lore); - } - - public @NotNull JumpVelocity jumpVelocity() { - return this.jumpVelocity; - } - - @Override - public @NotNull List flags() { - if (this.flags == null) { - return Collections.emptyList(); - } - - return Collections.unmodifiableList(this.flags); - } - - @Override - public @NotNull Map enchantments() { - if (this.enchantments == null) { - return Collections.emptyMap(); - } - - return Collections.unmodifiableMap(this.enchantments); - } - - public @NotNull ItemStack asItemStack() { - return ItemBuilder.from(this.material()) - .name(this.name()) - .lore(this.lore()) - .enchant(this.enchantments()) - .flags(this.flags().toArray(new ItemFlag[0])) - .build(); - } - - public static @NotNull Builder builder() { - return new Builder(); - } - - public static final class Builder { - - private Material material; - private Component name; - private List lore; - - private JumpVelocity jumpVelocity; - - private List itemFlags; - private Map enchantments = new HashMap<>(); - - private Builder() {} - - @Contract("_ -> this") - public Builder material(@NotNull Material material) { - this.material = material; - return this; - } - - @Contract("_ -> this") - public Builder nameComponent(@NotNull Component name) { - this.name = name; - return this; - } - - @Contract("_ -> this") - public Builder name(@NotNull String name) { - this.name = ComponentUtil.notItalic(name); - return this; - } - - @Contract("_ -> this") - public Builder loreComponent(@NotNull List lore) { - this.lore = lore; - return this; - } - - @Contract("_ -> this") - public Builder lore(@NotNull List lore) { - this.lore = ComponentUtil.notItalic(lore); - return this; - } - - @Contract("_ -> this") - public Builder lore(@NotNull String... lore) { - this.lore = ComponentUtil.notItalic(lore); - return this; - } - - @Contract("_ -> this") - public Builder jumpProperties(@NotNull JumpVelocity jumpVelocity) { - this.jumpVelocity = jumpVelocity; - return this; - } - - @Contract("_ -> this") - public Builder itemFlags(@NotNull List itemFlags) { - this.itemFlags = itemFlags; - return this; - } - - @Contract("_ -> this") - public Builder enchantment(@NotNull Map enchantments) { - this.enchantments = enchantments; - return this; - } - - @Contract("_,_ -> this") - public Builder enchantment(@NotNull Enchantment enchantment, int level) { - this.enchantments.put(enchantment, level); - return this; - } - - public JumpItem build() { - return new JumpItem(this.material, this.name, this.lore, this.jumpVelocity, this.itemFlags, this.enchantments); - } - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/JumpItemService.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/JumpItemService.java deleted file mode 100644 index 2854828..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/JumpItemService.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item; - -import com.github.imdmk.doublejump.jump.JumpConfig; -import com.github.imdmk.doublejump.jump.feature.item.usage.ItemUsage; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.inventory.meta.ItemMeta; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.util.Objects; - -/** - * Service responsible for handling logic related to jump items. - *

- * This includes verifying if an item is a valid jump item, - * checking usage configuration, and retrieving the configured item. - */ -public class JumpItemService { - - @Inject private JumpConfig config; - - /** - * Checks whether the provided item matches the configured jump item. - *

- * This method compares metadata such as damage and enchantments - * to ensure the item is functionally equivalent to the configured jump item. - * - * @param compare the item to compare with the configured jump item - * @return {@code true} if the item is considered a jump item, {@code false} otherwise - */ - public boolean isJumpItem(@Nullable ItemStack compare) { - if (compare == null || !this.isEnabled()) { - return false; - } - - ItemStack jumpItem = this.getJumpItem().asItemStack(); - if (jumpItem.getType() != compare.getType()) { - return false; - } - - ItemMeta jumpMeta = jumpItem.getItemMeta(); - ItemMeta compareMeta = compare.getItemMeta(); - - if (jumpMeta == null && compareMeta == null) { - return true; - } - - if (jumpMeta == null || compareMeta == null) { - return false; - } - - // IGNORE DURABILITY - if (jumpMeta instanceof Damageable jumpDamageable && - compareMeta instanceof Damageable compareDamageable) { - compareDamageable.setDamage(jumpDamageable.getDamage()); - } - - // ENCHANTS - if (!jumpMeta.getEnchants().equals(compareMeta.getEnchants())) { - return false; - } - - // NAME - if (jumpMeta.hasDisplayName() || compareMeta.hasDisplayName()) { - if (!Objects.equals(jumpMeta.getDisplayName(), compareMeta.getDisplayName())) { - return false; - } - } - - // LORE - if (jumpMeta.hasLore() || compareMeta.hasLore()) { - if (!Objects.equals(jumpMeta.getLore(), compareMeta.getLore())) { - return false; - } - } - - return true; - } - - /** - * Retrieves the configured jump item. - * - * @return the jump {@link ItemStack} - */ - public @NotNull JumpItem getJumpItem() { - return this.config.item.item; - } - - /** - * Checks whether the jump item feature is enabled. - * - * @return {@code true} if enabled, {@code false} otherwise - */ - public boolean isEnabled() { - return this.config.item.enabled; - } - - /** - * Checks whether the jump item feature is enabled for a specific usage type. - * - * @param usage the usage mode to check - * @return {@code true} if enabled and matches the given usage mode - */ - public boolean isEnabled(@NotNull ItemUsage usage) { - return this.isEnabled() && this.config.item.usageMode.equals(usage); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/configuration/JumpItemConfiguration.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/configuration/JumpItemConfiguration.java deleted file mode 100644 index ff45320..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/configuration/JumpItemConfiguration.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item.configuration; - -import com.github.imdmk.doublejump.jump.feature.item.JumpItem; -import com.github.imdmk.doublejump.jump.feature.item.usage.ItemUsage; -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocity; -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.annotation.Comment; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; - -import java.util.List; - -public class JumpItemConfiguration extends OkaeriConfig { - - @Comment("# Enables or disables the jump item feature.") - public boolean enabled = true; - - @Comment("# Prevent repairing the jump item using anvils?") - public boolean preventRepair = true; - - @Comment("# Prevent dropping the jump item from the player's inventory?") - public boolean preventDrop = false; - - @Comment("# Prevent enchanting the jump item?") - public boolean preventEnchant = true; - - @Comment({ - "# Defines how the jump item must be used to activate double jump.", - "# Available modes:", - "# - WEAR_ITEM: The item must be worn (e.g. boots in armor slot).", - "# - HOLD_ITEM: The item must be held in main or left hand.", - "# - CLICK_ITEM: The item must be right-clicked.", - "# - HAVE_ITEM: The item just needs to be in the player's inventory.", - }) - public ItemUsage usageMode = ItemUsage.WEAR_ITEM; - - @Comment("# The actual item given or used to enable the double jump.") - public JumpItem item = JumpItem.builder() - .material(Material.DIAMOND_BOOTS) - .name("DOUBLE JUMP") - .lore( - " ", - " WEAR THIS ITEM and you see magic", - " configure jump item usage in config", - " " - ) - .jumpProperties(JumpVelocity.of(0.6, 0.9)) - .itemFlags(List.of(ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ATTRIBUTES)) - .enchantment(Enchantment.LOYALTY, 3) - .build(); - - @Comment({ - "# Specifies how much durability to reduce after a jump.", - "# Example:", - "# reduceDurability: 5", - "# The item's durability will be reduced by 5 on use.", - "# Set to 0 to disable durability reduction." - }) - public int reduceDurability = 5; - -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/configuration/JumpItemSerializer.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/configuration/JumpItemSerializer.java deleted file mode 100644 index 6ee5592..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/configuration/JumpItemSerializer.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item.configuration; - -import com.github.imdmk.doublejump.jump.feature.item.JumpItem; -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocity; -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import net.kyori.adventure.text.Component; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class JumpItemSerializer implements ObjectSerializer { - - @Override - public boolean supports(@NotNull Class type) { - return JumpItem.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NotNull JumpItem item, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) { - data.add("material", item.material(), Material.class); - data.add("name", item.name(), Component.class); - - data.add("jumpVelocity", item.jumpVelocity(), JumpVelocity.class); - - if (!item.lore().isEmpty()) { - data.addCollection("lore", item.lore(), Component.class); - } - - if (!item.flags().isEmpty()) { - data.addCollection("flags", item.flags(), ItemFlag.class); - } - - if (!item.enchantments().isEmpty()) { - data.addAsMap("enchantments", item.enchantments(), Enchantment.class, Integer.class); - } - } - - @Override - public JumpItem deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) { - Material material = data.get("material", Material.class); - Component name = data.get("name", Component.class); - List lore = data.getAsList("lore", Component.class); - - JumpVelocity jumpVelocity = data.get("jumpVelocity", JumpVelocity.class); - - List flags = data.containsKey("flags") ? - data.getAsList("flags", ItemFlag.class) : Collections.emptyList(); - - Map enchantments = data.containsKey("enchantments") ? - data.getAsMap("enchantments", Enchantment.class, Integer.class) : new HashMap<>(); - - return JumpItem.builder() - .material(material) - .nameComponent(name) - .loreComponent(lore) - .jumpProperties(jumpVelocity) - .itemFlags(flags) - .enchantment(enchantments) - .build(); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/controller/JumpItemDisableController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/controller/JumpItemDisableController.java deleted file mode 100644 index 3d37aae..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/controller/JumpItemDisableController.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item.controller; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.JumpActivationType; -import com.github.imdmk.doublejump.jump.feature.item.JumpItemService; -import com.github.imdmk.doublejump.jump.feature.item.usage.ItemUsage; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.player.PlayerItemHeldEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -/** - * Controller responsible for disabling the double jump functionality - * when a player removes or changes the jump-enabling item. - * Listens to inventory and held item change events to manage jump state. - */ -public class JumpItemDisableController extends PluginListener { - - @Inject private JumpItemService jumpItemService; - - /** - * Handles clicks in the inventory that may change equipment slots. - * If a player stops wearing a jump item, disables the double jump ability. - * - * @param event the inventory click event - */ - @EventHandler(ignoreCancelled = true) - public void onInventoryClick(InventoryClickEvent event) { - if (!this.jumpItemService.isEnabled(ItemUsage.WEAR_ITEM)) { - return; - } - - if (!(event.getWhoClicked() instanceof Player player)) { - return; - } - - if (!(event.getInventory().getHolder() instanceof Player)) { - return; - } - - int slot = event.getSlot(); - if (!this.isEquipmentSlot(slot)) { - return; - } - - PlayerInventory inventory = player.getInventory(); - - ItemStack oldItem = inventory.getItem(slot); - ItemStack newItem = event.getCursor(); - - boolean wasWearingJumpItem = oldItem != null && this.jumpItemService.isJumpItem(oldItem); - boolean willWearJumpItem = newItem != null && this.jumpItemService.isJumpItem(newItem); - - if (wasWearingJumpItem && !willWearJumpItem) { - this.disableJump(player); - } - } - - private boolean isEquipmentSlot(int slot) { - return switch (slot) { - case 36, 37, 38, 39, 40, 45 -> true; - default -> false; - }; - } - - /** - * Called when a player changes the item they are holding in their hotbar. - * If the previous item was a jump item, the effect is disabled. - * - * @param event the item held event - */ - @EventHandler - public void onPlayerItemHeld(PlayerItemHeldEvent event) { - Player player = event.getPlayer(); - - if (!this.jumpItemService.isEnabled(ItemUsage.HOLD_ITEM)) { - return; - } - - ItemStack previousItem = player.getInventory().getItem(event.getPreviousSlot()); - if (previousItem != null && this.jumpItemService.isJumpItem(previousItem)) { - this.disableJump(player); - } - } - - /** - * Disables the double jump state for the specified player. - * This method resets the jump state and disables flying mode if the player is currently active. - * - * @param player the player whose jump state should be disabled - */ - private void disableJump(@NotNull Player player) { - this.jumpCache.getActive(player.getUniqueId()) - .ifPresent(jump -> { - jump.setActive(false); - jump.setJumpAllowed(true); - jump.setActivationType(JumpActivationType.NONE); - this.flyingService.disable(player); - }); - } - -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/controller/JumpItemInteractController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/controller/JumpItemInteractController.java deleted file mode 100644 index 8b930f1..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/controller/JumpItemInteractController.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item.controller; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.JumpActivationType; -import com.github.imdmk.doublejump.jump.JumpPlayer; -import com.github.imdmk.doublejump.jump.event.DoubleJumpEvent; -import com.github.imdmk.doublejump.jump.feature.item.JumpItem; -import com.github.imdmk.doublejump.jump.feature.item.JumpItemService; -import com.github.imdmk.doublejump.jump.feature.item.usage.ItemUsage; -import com.github.imdmk.doublejump.jump.feature.item.usage.ItemUsageStrategy; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.util.Arrays; - -public class JumpItemInteractController extends PluginListener { - - @Inject private JumpItemService jumpItemService; - @Inject private ItemUsageStrategy itemUsageStrategy; - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onDoubleJump(DoubleJumpEvent event) { - int reduceDurability = this.jumpConfig.item.reduceDurability; - - if (reduceDurability > 0 && event.getJumpPlayer().isActivationType(JumpActivationType.ITEM)) { - Arrays.stream(event.getPlayer().getInventory().getContents()) - .filter(item -> this.jumpItemService.isJumpItem(item)) - .findFirst() - .ifPresent(jumpItem -> this.reduceDurability(jumpItem, reduceDurability)); - } - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - void onPlayerMove(PlayerMoveEvent event) { - Player player = event.getPlayer(); - - if (!this.jumpItemService.isEnabled()) { - return; - } - - if (this.isSameBlockPosition(event.getFrom(), event.getTo())) { - return; - } - - this.jumpCache.get(player.getUniqueId()) - .filter(JumpPlayer::isJumpAllowed) - .filter(jump -> !jump.isActive()) - .filter(jump -> this.itemUsageStrategy.isItemUsed(player)) - .ifPresent(jump -> { - jump.setActive(true); - jump.setJumpAllowed(true); - - JumpItem jumpItem = this.jumpItemService.getJumpItem(); - jump.setActivationType(JumpActivationType.ITEM); - jump.setJumpVelocity(jumpItem.jumpVelocity()); - - this.flyingService.enable(player); - }); - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - void onItemInteract(PlayerInteractEvent event) { - Player player = event.getPlayer(); - - if (!this.jumpItemService.isEnabled(ItemUsage.CLICK_ITEM)) { - return; - } - - if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - - ItemStack clicked = event.getItem(); - if (clicked != null && this.jumpItemService.isJumpItem(clicked)) { - event.setCancelled(true); - - JumpItem jumpItem = this.jumpItemService.getJumpItem(); - JumpPlayer jumpPlayer = this.jumpCache.getOrThrow(player.getUniqueId()); - - this.useDoubleJump(player, jumpPlayer, jumpItem); - } - } - - /** - * Triggers a double jump action for the given player - *

- * This method fires a {@link DoubleJumpEvent} that can be cancelled - * by other listeners. - * - * @param player the player performing the jump - * @param jump the {@link JumpPlayer} instance associated with the player - */ - private void useDoubleJump(@NotNull Player player, @NotNull JumpPlayer jump, @NotNull JumpItem item) { - this.eventCaller.callEvent(new DoubleJumpEvent(player, jump, item.jumpVelocity())); - } - - /** - * Reduces the durability of the given item by the specified amount. - *

- * If the resulting durability exceeds the item's maximum durability, - * the item will be destroyed (i.e., its amount will be set to 0). - *

- * This method assumes the item uses {@link Damageable} metadata and - * is applicable only to items that can take damage (e.g., tools, weapons, armor). - * - * @param item the {@link ItemStack} whose durability should be reduced - * @param reduceBy the amount of durability to reduce - */ - private void reduceDurability(@NotNull ItemStack item, int reduceBy) { - if (!(item.getItemMeta() instanceof Damageable damageable)) { - return; - } - - int maxDurability = item.getType().getMaxDurability(); - - boolean shouldDestroyItem = (damageable.getDamage() + reduceBy) >= maxDurability; - - if (shouldDestroyItem) { - item.setAmount(0); - return; - } - - damageable.setDamage(damageable.getDamage() + reduceBy); - item.setDurability((short) damageable.getDamage()); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/controller/JumpItemResetController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/controller/JumpItemResetController.java deleted file mode 100644 index efa4633..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/controller/JumpItemResetController.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item.controller; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.JumpActivationType; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.PlayerDeathEvent; - -public class JumpItemResetController extends PluginListener { - - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - void onPlayerDeath(PlayerDeathEvent event) { - Player player = event.getEntity(); - - this.jumpCache.getActive(player.getUniqueId()) - .filter(jump -> jump.isActivationType(JumpActivationType.ITEM)) - .ifPresent(jump -> { - jump.setJumpAllowed(true); - jump.setActive(false); - jump.setActivationType(JumpActivationType.NONE); - - this.flyingService.disable(player); - }); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/controller/JumpItemRestrictionController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/controller/JumpItemRestrictionController.java deleted file mode 100644 index 4568141..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/controller/JumpItemRestrictionController.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item.controller; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.JumpActivationType; -import com.github.imdmk.doublejump.jump.feature.item.JumpItemService; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.enchantment.EnchantItemEvent; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.event.player.PlayerDropItemEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.panda_lang.utilities.inject.annotations.Inject; - -public class JumpItemRestrictionController extends PluginListener { - - @Inject private JumpItemService jumpItemService; - - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - void onItemEnchant(EnchantItemEvent event) { - if (!this.jumpItemService.isEnabled() || !this.jumpConfig.item.preventEnchant) { - return; - } - - if (this.jumpItemService.isJumpItem(event.getItem())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - void onItemAnvil(InventoryClickEvent event) { - if (!this.jumpItemService.isEnabled() || !this.jumpConfig.item.preventRepair) { - return; - } - - Inventory inventory = event.getClickedInventory(); - if (inventory == null || inventory.getType() != InventoryType.ANVIL) { - return; - } - - if (event.getSlot() != 2) { - return; - } - - ItemStack repair = inventory.getItem(0); - if (repair != null && this.jumpItemService.isJumpItem(repair)) { - event.setCancelled(true); - event.setResult(Event.Result.DENY); - } - } - - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - void onItemDrop(PlayerDropItemEvent event) { - Player player = event.getPlayer(); - - if (!this.jumpItemService.isEnabled() || !this.jumpConfig.item.preventDrop) { - return; - } - - ItemStack drop = event.getItemDrop().getItemStack(); - if (this.jumpItemService.isJumpItem(drop)) { - event.setCancelled(true); - return; - } - - // Not a jump item -> reset state - this.jumpCache.ifActive(player.getUniqueId(), jump -> { - jump.setActive(false); - jump.setActivationType(JumpActivationType.NONE); - this.flyingService.disable(player); - }); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/ItemUsage.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/ItemUsage.java deleted file mode 100644 index 56082de..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/ItemUsage.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item.usage; - -/** - * Defines the different modes in which a jump item can be considered "used" by the player. - * These modes determine when double jump functionality is available based on item interaction or possession. - */ -public enum ItemUsage { - - /** - * The item must be present anywhere in the player's inventory. - */ - HAVE_ITEM, - - /** - * The item must be held in the main hand or off-hand. - */ - HOLD_ITEM, - - /** - * The player must actively right-click with the item to activate the jump. - */ - CLICK_ITEM, - - /** - * The item must be equipped (e.g., worn as armor). - */ - WEAR_ITEM -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/ItemUsageStrategy.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/ItemUsageStrategy.java deleted file mode 100644 index e6b0118..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/ItemUsageStrategy.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item.usage; - -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -/** - * Represents a strategy for determining whether a double-jump item is considered "used" by a player. - *

- * Implementations of this interface define how the plugin checks if a player is actively using the jump item - * — for example, by holding it, wearing it, or simply having it in the inventory. - * - *

Common implementations: - *

    - *
  • {@code HoldItemUsageStrategy} – player must be holding the item.
  • - *
  • {@code WearItemUsageStrategy} – player must wear the item.
  • - *
  • {@code HaveItemUsageStrategy} – player must have the item in inventory.
  • - *
- */ -@FunctionalInterface -public interface ItemUsageStrategy { - - /** - * Checks whether the given player is currently using the jump item according to this strategy. - * - * @param player the player to evaluate (never null) - * @return {@code true} if the player satisfies the usage condition, {@code false} otherwise - */ - boolean isItemUsed(@NotNull Player player); - -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/ItemUsageStrategyFactory.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/ItemUsageStrategyFactory.java deleted file mode 100644 index 9dd93e4..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/ItemUsageStrategyFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item.usage; - -import com.github.imdmk.doublejump.jump.feature.item.usage.impl.HaveItemUsageStrategy; -import com.github.imdmk.doublejump.jump.feature.item.usage.impl.HoldItemUsageStrategy; -import com.github.imdmk.doublejump.jump.feature.item.usage.impl.WearItemUsageStrategy; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.DependencyInjectionException; -import org.panda_lang.utilities.inject.Injector; - -/** - * Factory class responsible for creating {@link ItemUsageStrategy} instances - * based on the provided {@link ItemUsage} type. - *

- * The strategies are instantiated using the given {@link Injector}, which - * allows dependency injection into the strategy classes. - */ -public final class ItemUsageStrategyFactory { - - private ItemUsageStrategyFactory() { - throw new UnsupportedOperationException("This is utility class."); - } - - /** - * Creates an {@link ItemUsageStrategy} for the specified usage mode using the provided injector. - * - * @param usage the desired usage mode - * @param injector the injector to instantiate the strategy - * @return an instance of the corresponding {@link ItemUsageStrategy} - * @throws DependencyInjectionException if instantiation fails - */ - public static ItemUsageStrategy create(@NotNull ItemUsage usage, @NotNull Injector injector) throws DependencyInjectionException { - return switch (usage) { - case HAVE_ITEM -> injector.newInstanceWithFields(HaveItemUsageStrategy.class); - case HOLD_ITEM -> injector.newInstanceWithFields(HoldItemUsageStrategy.class); - case WEAR_ITEM -> injector.newInstanceWithFields(WearItemUsageStrategy.class); - default -> player -> false; - }; - } -} - diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/impl/HaveItemUsageStrategy.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/impl/HaveItemUsageStrategy.java deleted file mode 100644 index dcc7a3f..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/impl/HaveItemUsageStrategy.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item.usage.impl; - -import com.github.imdmk.doublejump.jump.feature.item.JumpItemService; -import com.github.imdmk.doublejump.jump.feature.item.usage.ItemUsageStrategy; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -public class HaveItemUsageStrategy implements ItemUsageStrategy { - - @Inject private JumpItemService jumpItemService; - - @Override - public boolean isItemUsed(@NotNull Player player) { - if (!this.jumpItemService.isEnabled()) { - return false; - } - - return player.getInventory().contains(this.jumpItemService.getJumpItem().asItemStack()); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/impl/HoldItemUsageStrategy.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/impl/HoldItemUsageStrategy.java deleted file mode 100644 index 2017202..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/impl/HoldItemUsageStrategy.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item.usage.impl; - -import com.github.imdmk.doublejump.jump.feature.item.JumpItemService; -import com.github.imdmk.doublejump.jump.feature.item.usage.ItemUsageStrategy; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -public class HoldItemUsageStrategy implements ItemUsageStrategy { - - @Inject private JumpItemService jumpItemService; - - @Override - public boolean isItemUsed(@NotNull Player player) { - if (!this.jumpItemService.isEnabled()) { - return false; - } - - ItemStack mainHand = player.getInventory().getItemInMainHand(); - ItemStack offHand = player.getInventory().getItemInOffHand(); - - return this.jumpItemService.isJumpItem(mainHand) || this.jumpItemService.isJumpItem(offHand); - } - -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/impl/WearItemUsageStrategy.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/impl/WearItemUsageStrategy.java deleted file mode 100644 index 120f3bb..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/item/usage/impl/WearItemUsageStrategy.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.item.usage.impl; - -import com.github.imdmk.doublejump.jump.feature.item.JumpItemService; -import com.github.imdmk.doublejump.jump.feature.item.usage.ItemUsageStrategy; -import org.bukkit.entity.Player; -import org.bukkit.inventory.PlayerInventory; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -public class WearItemUsageStrategy implements ItemUsageStrategy { - - @Inject private JumpItemService jumpItemService; - - @Override - public boolean isItemUsed(@NotNull Player player) { - if (!this.jumpItemService.isEnabled()) { - return false; - } - - PlayerInventory inventory = player.getInventory(); - - return this.jumpItemService.isJumpItem(inventory.getHelmet()) - || this.jumpItemService.isJumpItem(inventory.getChestplate()) - || this.jumpItemService.isJumpItem(inventory.getLeggings()) - || this.jumpItemService.isJumpItem(inventory.getBoots()); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/placeholder/JumpAllowedPlaceholder.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/placeholder/JumpAllowedPlaceholder.java deleted file mode 100644 index 7e9a6d4..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/placeholder/JumpAllowedPlaceholder.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.placeholder; - -import com.github.imdmk.doublejump.infrastructure.placeholder.PluginPlaceholder; -import com.github.imdmk.doublejump.jump.JumpConfig; -import com.github.imdmk.doublejump.jump.JumpPlayer; -import com.github.imdmk.doublejump.jump.cache.JumpPlayerCache; -import org.bukkit.OfflinePlayer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.panda_lang.utilities.inject.annotations.Inject; - -public class JumpAllowedPlaceholder extends PluginPlaceholder { - - private static final String PLACEHOLDER_IDENTIFIER = "doublejump-allowed"; - - @Inject private JumpConfig config; - @Inject private JumpPlayerCache cache; - - @Override - public @NotNull String getIdentifier() { - return PLACEHOLDER_IDENTIFIER; - } - - @Override - protected @Nullable String onRequestExpansion(@NotNull OfflinePlayer player, @NotNull String params) { - return this.cache.get(player.getUniqueId()) - .map(this::formatJumpAllowed) - .orElse(this.config.placeholders.jumpDisabledText); - } - - private String formatJumpAllowed(@NotNull JumpPlayer jump) { - return jump.isJumpAllowed() ? this.config.placeholders.jumpAllowedText : this.config.placeholders.jumpNotAllowedText; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/placeholder/JumpCooldownPlaceholder.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/placeholder/JumpCooldownPlaceholder.java deleted file mode 100644 index 7773a32..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/placeholder/JumpCooldownPlaceholder.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.placeholder; - -import com.github.imdmk.doublejump.infrastructure.placeholder.PluginPlaceholder; -import com.github.imdmk.doublejump.jump.JumpConfig; -import com.github.imdmk.doublejump.jump.JumpPlayer; -import com.github.imdmk.doublejump.jump.cache.JumpPlayerCache; -import com.github.imdmk.doublejump.util.DurationUtil; -import org.bukkit.OfflinePlayer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.time.Duration; -import java.time.Instant; - -/** - * Placeholder returning remaining cooldown until the player can double jump again. - * Example: %doublejump_cooldown% -> "3.2s" or "allowed" - */ -public class JumpCooldownPlaceholder extends PluginPlaceholder { - - private static final String PLACEHOLDER_IDENTIFIER = "doublejump-cooldown"; - - @Inject private JumpConfig config; - @Inject private JumpPlayerCache cache; - - @Override - public @NotNull String getIdentifier() { - return PLACEHOLDER_IDENTIFIER; - } - - @Override - protected @Nullable String onRequestExpansion(@NotNull OfflinePlayer player, @NotNull String params) { - return this.cache.get(player.getUniqueId()) - .map(this::formatCooldown) - .orElse(this.config.placeholders.jumpDisabledText); - } - - private String formatCooldown(@NotNull JumpPlayer jump) { - return jump.getNextAllowedJump() - .map(nextJump -> Duration.between(Instant.now(), nextJump)) - .filter(DurationUtil::isValid) - .map(DurationUtil::format) - .orElse(this.config.placeholders.cooldownExpiredText); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/placeholder/JumpPlaceholderConfig.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/placeholder/JumpPlaceholderConfig.java deleted file mode 100644 index dbaf8ac..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/placeholder/JumpPlaceholderConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.placeholder; - -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.annotation.Comment; - -public class JumpPlaceholderConfig extends OkaeriConfig { - - @Comment({ - "# Enable or disable PlaceholderAPI support for DoubleJump placeholders.", - "# Set to false to disable all placeholders." - }) - public boolean enabled = true; - - @Comment("# Text to display when the player's doublejump mode is deactivated.") - public String jumpDisabledText = "doublejump disabled"; - - @Comment({ - "# Text to display when the player's cooldown has expired or is not set.", - "# Example: 'ready', 'now', '✓'" - }) - public String cooldownExpiredText = "ready"; - - @Comment({ - "# Text to display when the player is allowed to jump.", - "# Example: 'allowed', 'yes', '✔'" - }) - public String jumpAllowedText = "allowed"; - - @Comment({ - "# Text to display when the player is NOT allowed to jump.", - "# Example: 'denied', 'no', '✘'" - }) - public String jumpNotAllowedText = "denied"; -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/JumpRestrictionConfig.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/JumpRestrictionConfig.java deleted file mode 100644 index afe4222..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/JumpRestrictionConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction; - -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.annotation.Comment; -import org.bukkit.GameMode; - -import java.util.Set; - -public class JumpRestrictionConfig extends OkaeriConfig { - - @Comment("# List of worlds where double jumping is disabled. Leave empty to disable this restriction.") - public Set disabledWorlds = Set.of("world1"); - - @Comment("# List of WorldGuard regions where double jumping is disabled. Leave empty to disable this restriction.") - public Set disabledRegions = Set.of(); - - @Comment("# List of game modes where double jumping is disabled. Leave empty to disable this restriction.") - public Set disabledGameModes = Set.of(GameMode.CREATIVE, GameMode.ADVENTURE); - - @Comment("# List of permissions that allow double jumping. Players must have at least one. Leave empty to disable this restriction.") - public Set allowedPermissions = Set.of(); - - @Comment("# Disable double jumping if the player is lagging (e.g., high ping). Recommended to set this to true to prevent unintended flying.") - public boolean disableIfPlayerLagging = true; - - @Comment("# Block double jump usage while the player is gliding (e.g., with elytra)") - public boolean blockUsageWhileGliding = true; -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/JumpRestrictionController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/JumpRestrictionController.java deleted file mode 100644 index ffd1d61..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/JumpRestrictionController.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.JumpActivationType; -import com.github.imdmk.doublejump.jump.JumpPlayer; -import com.github.imdmk.doublejump.jump.event.DoubleJumpEvent; -import com.github.imdmk.doublejump.jump.feature.velocity.JumpVelocityService; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -/** - * Handles restrictions related to double jump: - * - Cancels jump events if restrictions are violated - * - Disables double jump in runtime if hard restrictions occur (e.g. world change, ping, permissions) - * - Enables jump on join/world change if allowed - */ -public class JumpRestrictionController extends PluginListener { - - @Inject private JumpRestrictionService restrictionService; - @Inject private JumpRestrictionNotifier resultNotifier; - @Inject private JumpVelocityService velocityService; - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - void onDoubleJump(DoubleJumpEvent event) { - Player player = event.getPlayer(); - this.jumpCache.ifActive(player.getUniqueId(), jump -> this.handleRestrictions(player, jump)); - } - - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - void onPlayerMove(PlayerMoveEvent event) { - Player player = event.getPlayer(); - this.jumpCache.ifActive(player.getUniqueId(), jump -> this.handleRestrictions(player, jump)); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onPlayerJoin(PlayerJoinEvent event) { - this.attemptEnableDoubleJump(event.getPlayer()); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onPlayerChangedWorld(PlayerChangedWorldEvent event) { - this.attemptEnableDoubleJump(event.getPlayer()); - } - - /** - * Applies double jump restrictions to the player. - * Updates jump state, notifies the player if restricted, and disables flight on hard restrictions. - * - * @param player the player to evaluate - * @param jump the player's jump state to update - */ - private void handleRestrictions(@NotNull Player player, @NotNull JumpPlayer jump) { - RestrictionResult result = this.restrictionService.checkAllRestrictions(player); - - this.resultNotifier.notify(player, jump, result); - - if (result.failure()) { - jump.setJumpAllowed(false); - - if (result.isHardRestriction()) { - jump.setActive(false); - // Restore default jump allow so the player can re-enable jump - // (e.g., when stepping on a jump block) - jump.setJumpAllowed(true); - this.flyingService.disable(player); - } - - return; - } - - if (!jump.isJumpAllowed()) { - jump.setJumpAllowed(true); - - if (!jump.hasLastNotifiedReason()) { - this.messageService.send(player, n -> n.jumpAvailable); - } - } - } - - /** - * Attempts to enable double jump for the player if they pass restrictions. - * @param player the player to enable double jump for - */ - private void attemptEnableDoubleJump(@NotNull Player player) { - this.jumpCache.get(player.getUniqueId()) - .filter(jump -> this.shouldEnable(player)) - .ifPresent(jump -> { - jump.setJumpAllowed(true); - jump.setActive(true); - jump.setActivationType(JumpActivationType.JOIN); - jump.setJumpVelocity(this.velocityService.forPlayer(player)); - - this.messageService.send(player, n -> n.autoJumpEnabled); - - this.flyingService.enable(player); - }); - } - - /** - * Determines whether double jump should be enabled for the player. - * @param player the player to check - * @return true if double jump should be enabled - */ - private boolean shouldEnable(@NotNull Player player) { - if (this.restrictionService.isRestricted(player)) { - return false; - } - - String autoEnablePermission = this.jumpConfig.autoEnableForPermission; - if (autoEnablePermission != null - && !autoEnablePermission.isEmpty() - && player.hasPermission(autoEnablePermission)) { - return true; - } - - return this.jumpConfig.autoEnableOnJoin; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/JumpRestrictionNotifier.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/JumpRestrictionNotifier.java deleted file mode 100644 index 2b4a025..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/JumpRestrictionNotifier.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction; - -import com.eternalcode.multification.notice.provider.NoticeProvider; -import com.eternalcode.multification.shared.Formatter; -import com.github.imdmk.doublejump.infrastructure.message.MessageConfig; -import com.github.imdmk.doublejump.infrastructure.message.MessageService; -import com.github.imdmk.doublejump.jump.JumpPlayer; -import com.github.imdmk.doublejump.util.DurationUtil; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.time.Duration; -import java.time.Instant; - -/** - * Handles sending messages to players when their double jump is restricted. - */ -public class JumpRestrictionNotifier { - - @Inject private MessageService messageService; - - public void notify(@NotNull Player player, @NotNull JumpPlayer jump, @NotNull RestrictionResult result) { - if (result.success()) { - jump.setLastNotifiedReason(null); - return; - } - - result.reason() - .filter(reason -> !jump.isSameAsLastNotifiedReason(reason)) - .ifPresent(reason -> { - jump.setLastNotifiedReason(reason); - this.sendMessage(player, reason, this.buildFormatter(jump, reason)); - }); - } - - public void notify(@NotNull Player player, @NotNull RestrictionDenyReason reason) { - this.sendMessage(player, reason, new Formatter()); - } - - public void notify(@NotNull Player player, @NotNull RestrictionDenyReason reason, @NotNull Formatter formatter) { - this.sendMessage(player, reason, formatter); - } - - private void sendMessage(@NotNull Player player, @NotNull RestrictionDenyReason reason, @NotNull Formatter formatter) { - this.messageService.send(player, this.resolveNoticeProvider(reason), formatter); - } - - private @NotNull Formatter buildFormatter(@NotNull JumpPlayer jump, @NotNull RestrictionDenyReason reason) { - return switch (reason) { - case JUMP_DELAY -> new Formatter() - .register("{TIME}", DurationUtil.format(this.calculateRemainingDelay(jump))); - default -> new Formatter(); - }; - } - - private @NotNull Duration calculateRemainingDelay(@NotNull JumpPlayer jump) { - return jump.getNextAllowedJump() - .map(next -> Duration.between(Instant.now(), next)) - .filter(duration -> !duration.isNegative()) - .orElse(Duration.ZERO); - } - - private @NotNull NoticeProvider resolveNoticeProvider(@NotNull RestrictionDenyReason reason) { - return switch (reason) { - case WORLD_DISABLED -> config -> config.worldRestricted; - case REGION_DISABLED -> config -> config.regionRestricted; - case PERMISSION_REQUIRED -> config -> config.jumpPermissionRequired; - case GAME_MODE_BLOCKED -> config -> config.gameModeRestricted; - case JUMP_DELAY -> config -> config.jumpDelay; - case PLAYER_GLIDING -> config -> config.blockedWhileGliding; - case PLAYER_LAGGING -> config -> config.playerLagging; - }; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/JumpRestrictionService.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/JumpRestrictionService.java deleted file mode 100644 index 97f2b7c..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/JumpRestrictionService.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction; - -import com.github.imdmk.doublejump.jump.JumpConfig; -import com.github.imdmk.doublejump.jump.cache.JumpPlayerCache; -import com.github.imdmk.doublejump.jump.feature.restriction.checker.SetRestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.restriction.checker.WorldGuardRestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.restriction.checker.player.GlidingRestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.restriction.checker.player.PermissionRestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.restriction.checker.player.PingRestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.restriction.cooldown.CooldownRestrictionChecker; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; -import org.panda_lang.utilities.inject.annotations.PostConstruct; - -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; - -/** - * Service responsible for evaluating whether a player is restricted from using double jump. - *

- * It evaluates multiple restriction types (e.g., world, game mode, permissions) using registered checkers. - * Each restriction returns a {@link RestrictionResult} that encapsulates success state and failure reason. - *

- */ -public class JumpRestrictionService implements RestrictionChecker { - - @Inject private JumpConfig jumpConfig; - @Inject private JumpPlayerCache jumpCache; - - /** List of active restriction checkers based on configuration. */ - private List checkers; - - @PostConstruct - private void postConstruct() { - this.checkers = this.createDefaultCheckers(this.jumpConfig.restrictions); - } - - /** - * Runs all registered restriction checkers against the given player. - * - * @param player the player to evaluate - * @return the result of the first failed restriction, or a passed result if all checks succeed - */ - public @NotNull RestrictionResult checkAllRestrictions(@NotNull Player player) { - for (RestrictionChecker checker : this.checkers) { - RestrictionResult result = checker.check(player); - if (!result.success()) { - return result; - } - } - - return RestrictionResult.passed(); - } - - /** - * Checks whether the player is restricted from using double jump. - * Shortcut for evaluating {@link #checkAllRestrictions(Player)} and checking failure state. - * - * @param player the player to evaluate - * @return {@code true} if any restriction applies; {@code false} otherwise - */ - public boolean isRestricted(@NotNull Player player) { - return this.checkAllRestrictions(player).failure(); - } - - /** - * Executes the provided action if the player is currently restricted from using double jump. - *

- * This is a utility method that internally runs all registered {@link RestrictionChecker}s via - * {@link #checkAllRestrictions(Player)}. - * If the result indicates failure, the specified - * consumer is invoked with the corresponding {@link RestrictionResult}. - *

- * - * @param player the player to evaluate - * @param onRestricted the action to execute if the player is restricted - */ - public void ifRestricted(@NotNull Player player, @NotNull Consumer onRestricted) { - Optional.of(this.checkAllRestrictions(player)) - .filter(RestrictionResult::failure) - .ifPresent(onRestricted); - } - - /** - * Creates a list of default restriction checkers based on provided configuration. - * - * @param configuration the configuration containing restriction sets - * @return list of initialized restriction checkers - */ - private List createDefaultCheckers(@NotNull JumpRestrictionConfig configuration) { - return List.of( - new CooldownRestrictionChecker(this.jumpCache), - new PingRestrictionChecker(configuration.disableIfPlayerLagging), - new SetRestrictionChecker<>( - configuration.disabledWorlds, - p -> p.getWorld().getName(), - RestrictionDenyReason.WORLD_DISABLED - ), - new WorldGuardRestrictionChecker(configuration.disabledRegions), - new SetRestrictionChecker<>( - configuration.disabledGameModes, - Player::getGameMode, - RestrictionDenyReason.GAME_MODE_BLOCKED - ), - new PermissionRestrictionChecker(configuration.allowedPermissions), - new GlidingRestrictionChecker(configuration.blockUsageWhileGliding) - ); - } - - @Override - public @NotNull RestrictionResult check(@NotNull Player player) { - return this.checkAllRestrictions(player); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/SetRestrictionChecker.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/SetRestrictionChecker.java deleted file mode 100644 index 84db5cd..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/SetRestrictionChecker.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction.checker; - -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionDenyReason; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionResult; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; - -public class SetRestrictionChecker implements RestrictionChecker { - - private final Set disabled; - private final Function valueExtractor; - private final RestrictionDenyReason denyReason; - - public SetRestrictionChecker( - @NotNull Set disabled, - @NotNull Function valueExtractor, - @NotNull RestrictionDenyReason denyReason - ) { - this.disabled = Objects.requireNonNull(disabled, "required cannot be null"); - this.valueExtractor = Objects.requireNonNull(valueExtractor, "valueExtractor cannot be null"); - this.denyReason = Objects.requireNonNull(denyReason, "denyReason cannot be null"); - } - - @Override - public @NotNull RestrictionResult check(@NotNull Player player) { - T value = this.valueExtractor.apply(player); - - if (!this.disabled.isEmpty() && this.disabled.contains(value)) { - return RestrictionResult.failed(this.denyReason); - } - - return RestrictionResult.passed(); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/WorldGuardRestrictionChecker.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/WorldGuardRestrictionChecker.java deleted file mode 100644 index d12d174..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/WorldGuardRestrictionChecker.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction.checker; - -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionDenyReason; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionResult; -import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.util.Location; -import com.sk89q.worldguard.WorldGuard; -import com.sk89q.worldguard.internal.platform.WorldGuardPlatform; -import com.sk89q.worldguard.protection.ApplicableRegionSet; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.protection.regions.RegionContainer; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Restriction checker that blocks double jump for players in specified WorldGuard regions. - */ -public class WorldGuardRestrictionChecker implements RestrictionChecker { - - private final Set disabledRegions; - - /** - * Creates a new instance of the WorldGuard restriction checker. - * - * @param disabledRegions list of region IDs where double jump should be restricted - */ - public WorldGuardRestrictionChecker(@NotNull Set disabledRegions) { - this.disabledRegions = Objects.requireNonNull(disabledRegions, "disabledRegions cannot be null"); - } - - @Override - public @NotNull RestrictionResult check(@NotNull Player player) { - if (this.disabledRegions.isEmpty()) { - return RestrictionResult.passed(); - } - - Set playerRegionIds = this.getPlayerRegions(player).getRegions().stream() - .map(ProtectedRegion::getId) - .collect(Collectors.toSet()); - - for (String disabledRegion : this.disabledRegions) { - if (playerRegionIds.contains(disabledRegion)) { - return RestrictionResult.failed(RestrictionDenyReason.REGION_DISABLED); - } - } - - return RestrictionResult.passed(); - } - - /** - * Retrieves the set of WorldGuard regions the player is currently in. - * - * @param player the player whose location will be evaluated - * @return the applicable region set at the player's current location - */ - public @NotNull ApplicableRegionSet getPlayerRegions(@NotNull Player player) { - Location adaptedLocation = BukkitAdapter.adapt(player.getLocation()); - - WorldGuardPlatform platform = WorldGuard.getInstance().getPlatform(); - RegionContainer container = platform.getRegionContainer(); - - return container.createQuery().getApplicableRegions(adaptedLocation); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/player/GlidingRestrictionChecker.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/player/GlidingRestrictionChecker.java deleted file mode 100644 index 4117ab8..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/player/GlidingRestrictionChecker.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction.checker.player; - -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionDenyReason; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionResult; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -public class GlidingRestrictionChecker implements RestrictionChecker { - - private final boolean enabled; - - public GlidingRestrictionChecker(boolean enabled) { - this.enabled = enabled; - } - - @Override - public @NotNull RestrictionResult check(@NotNull Player player) { - if (!this.enabled) { - return RestrictionResult.passed(); - } - - if (player.isGliding()) { - return RestrictionResult.failed(RestrictionDenyReason.PLAYER_GLIDING); - } - - return RestrictionResult.passed(); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/player/PermissionRestrictionChecker.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/player/PermissionRestrictionChecker.java deleted file mode 100644 index 4a1a11d..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/player/PermissionRestrictionChecker.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction.checker.player; - -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionDenyReason; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionResult; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; -import java.util.Set; - -public class PermissionRestrictionChecker implements RestrictionChecker { - - private final Set allowedPermissions; - - public PermissionRestrictionChecker(@NotNull Set allowedPermissions) { - this.allowedPermissions = Objects.requireNonNull(allowedPermissions, "required cannot be null"); - } - - @Override - public @NotNull RestrictionResult check(@NotNull Player player) { - if (!this.allowedPermissions.isEmpty()) { - for (String permission : this.allowedPermissions) { - if (player.hasPermission(permission)) { - return RestrictionResult.passed(); - } - } - - return RestrictionResult.failed(RestrictionDenyReason.PERMISSION_REQUIRED); - } - - return RestrictionResult.passed(); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/player/PingRestrictionChecker.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/player/PingRestrictionChecker.java deleted file mode 100644 index bdf2aa7..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/checker/player/PingRestrictionChecker.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction.checker.player; - -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionDenyReason; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionResult; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -public class PingRestrictionChecker implements RestrictionChecker { - - private static final int PING_THRESHOLD = 250; - - private final boolean enabled; - - public PingRestrictionChecker(boolean enabled) { - this.enabled = enabled; - } - - @Override - public @NotNull RestrictionResult check(@NotNull Player player) { - if (!this.enabled) { - return RestrictionResult.passed(); - } - - int ping = player.getPing(); - if (ping > PING_THRESHOLD) { - return RestrictionResult.failed(RestrictionDenyReason.PLAYER_LAGGING); - } - - return RestrictionResult.passed(); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/cooldown/CooldownRestrictionChecker.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/cooldown/CooldownRestrictionChecker.java deleted file mode 100644 index 5ef022f..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/cooldown/CooldownRestrictionChecker.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction.cooldown; - -import com.github.imdmk.doublejump.jump.JumpPlayer; -import com.github.imdmk.doublejump.jump.cache.JumpPlayerCache; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionChecker; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionDenyReason; -import com.github.imdmk.doublejump.jump.feature.restriction.RestrictionResult; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.time.Instant; -import java.util.Objects; - -public class CooldownRestrictionChecker implements RestrictionChecker { - - private final JumpPlayerCache jumpCache; - - public CooldownRestrictionChecker(@NotNull JumpPlayerCache jumpCache) { - this.jumpCache = Objects.requireNonNull(jumpCache, "jumpCache cannot be null"); - } - - @Override - public @NotNull RestrictionResult check(@NotNull Player player) { - return this.jumpCache.getActive(player.getUniqueId()) - .filter(this::hasCooldown) - .map(jumpPlayer -> RestrictionResult.failed(RestrictionDenyReason.JUMP_DELAY)) - .orElse(RestrictionResult.passed()); - } - - /** - * Returns true if player’s last jump was within delay period. - * - * @param player jump player data, non-null - * @return true if delay active, false otherwise - */ - private boolean hasCooldown(@NotNull JumpPlayer player) { - Instant now = Instant.now(); - return player.getNextAllowedJump() - .map(now::isBefore) - .orElse(false); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/cooldown/CooldownRestrictionController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/cooldown/CooldownRestrictionController.java deleted file mode 100644 index 03d1398..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/cooldown/CooldownRestrictionController.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction.cooldown; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.event.DoubleJumpEvent; -import com.github.imdmk.doublejump.util.DurationUtil; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.time.Duration; -import java.time.Instant; - -/** - * Applies a cooldown delay after a successful double jump. - * Triggered by {@link DoubleJumpEvent}. - */ -public class CooldownRestrictionController extends PluginListener { - - @Inject private CooldownRestrictionService cooldownRestrictionService; - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onDoubleJump(DoubleJumpEvent event) { - Duration delay = this.cooldownRestrictionService.forPlayer(event.getPlayer()); - if (DurationUtil.isValid(delay)) { - event.getJumpPlayer().setNextAllowedJump(this.calculateNextAllowedJump(delay)); - } - } - - /** - * Calculates the next timestamp when a player is allowed to jump again. - * - * @param delay the configured jump cooldown duration - * @return an Instant indicating when jumping will be allowed again - */ - @NotNull - private Instant calculateNextAllowedJump(@NotNull Duration delay) { - return Instant.now().plus(delay); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/cooldown/CooldownRestrictionService.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/cooldown/CooldownRestrictionService.java deleted file mode 100644 index 1f5415f..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/restriction/cooldown/CooldownRestrictionService.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.restriction.cooldown; - -import com.github.imdmk.doublejump.jump.JumpConfig; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.time.Duration; -import java.util.Map; - -/** - * Service responsible for determining cooldown durations for players based on their permissions. - * - *

Cooldowns are defined in the {@link JumpConfig#cooldowns} map, where keys are permission strings - * and values are the cooldown durations. The "default" key is used when no other permission matches.

- */ -public class CooldownRestrictionService { - - private static final String DEFAULT_IDENTIFIER = "default"; - - @Inject private JumpConfig config; - - /** - * Retrieves the cooldown duration for the given player based on their permissions. - * - *

Iterates through all configured cooldown entries and returns the first matching one - * where the player has the corresponding permission. If no permission matches, - * the default cooldown is returned.

- * - * @param player the player whose cooldown is being determined - * @return the cooldown duration applicable to the player - */ - public @NotNull Duration forPlayer(@NotNull Player player) { - for (Map.Entry entry : this.config.cooldowns.entrySet()) { - String permission = entry.getKey(); - - if (this.isCustomPermissionKey(permission) && player.hasPermission(permission)) { - return entry.getValue(); - } - } - - return this.defaultCooldown(); - } - - /** - * Returns the default cooldown duration defined in the configuration. - * - * @return the default cooldown duration - */ - public @NotNull Duration defaultCooldown() { - return this.config.cooldowns.get(DEFAULT_IDENTIFIER); - } - - /** - * Checks whether the given permission key is considered a custom (non-default) key. - * Used to filter out the special "default" entry during permission lookups. - * - * @param permission the permission key to check - * @return true if the permission is not the default identifier, false otherwise - */ - private boolean isCustomPermissionKey(@NotNull String permission) { - return !permission.equals(DEFAULT_IDENTIFIER); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/velocity/JumpVelocitySerializer.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/velocity/JumpVelocitySerializer.java deleted file mode 100644 index a3a7f5b..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/velocity/JumpVelocitySerializer.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.velocity; - -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import org.jetbrains.annotations.NotNull; - -public class JumpVelocitySerializer implements ObjectSerializer { - - @Override - public boolean supports(@NotNull Class type) { - return JumpVelocity.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NotNull JumpVelocity properties, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) { - data.add("horizontalBoost", properties.horizontalBoost(), double.class); - data.add("verticalBoost", properties.verticalBoost(), double.class); - } - - @Override - public JumpVelocity deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) { - double horizontalBoost = data.get("horizontalBoost", double.class); - double verticalBoost = data.get("verticalBoost", double.class); - return JumpVelocity.of(horizontalBoost, verticalBoost); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/velocity/JumpVelocityService.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/velocity/JumpVelocityService.java deleted file mode 100644 index 85e6bda..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/velocity/JumpVelocityService.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.velocity; - -import com.github.imdmk.doublejump.jump.JumpConfig; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.util.Map; - -public class JumpVelocityService { - - private static final String DEFAULT_IDENTIFIER = "default"; - - @Inject private JumpConfig config; - - /** - * Returns the JumpProperties for the given player based on their permissions. - * If no matching permission is found, the default properties are returned. - * - * @param player the player to check permissions for - * @return the matching JumpProperties instance - */ - public @NotNull JumpVelocity forPlayer(@NotNull Player player) { - for (Map.Entry entry : this.config.velocities.entrySet()) { - String permission = entry.getKey(); - - if (this.isCustomPermissionKey(permission) && player.hasPermission(permission)) { - return entry.getValue(); - } - } - - return this.defaultProperties(); - } - - /** - * Returns the default {@link JumpVelocity} defined in the configuration. - * This is used as a fallback when a player has no matching permission-based properties. - * - * @return the default jump properties - * @throws IllegalStateException if the default properties are missing - */ - public @NotNull JumpVelocity defaultProperties() { - return this.config.velocities.get(DEFAULT_IDENTIFIER); - } - - /** - * Checks whether the given permission key is considered a custom (non-default) key. - * Used to filter out the special "default" entry during permission lookups. - * - * @param permission the permission key to check - * @return true if the permission is not the default identifier, false otherwise - */ - private boolean isCustomPermissionKey(@NotNull String permission) { - return !permission.equals(DEFAULT_IDENTIFIER); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/JumpVisualService.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/JumpVisualService.java deleted file mode 100644 index 5cc9a01..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/JumpVisualService.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual; - -import com.github.imdmk.doublejump.jump.feature.visual.configuration.JumpVisualConfig; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualRepository; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Service responsible for retrieving and initializing visual jump settings. - * Provides access to default or player-specific jump visuals. - */ -public class JumpVisualService { - - @Inject private Logger logger; - @Inject private JumpVisualRepository visualRepository; - @Inject private JumpVisualConfig visualConfiguration; - - /** - * Retrieves visual settings for the given player UUID. - * If no settings are found, default visuals are saved and returned. - * - * @param uuid unique identifier of the player - * @return a future with the resolved visual settings - */ - public CompletableFuture getOrCreate(@NotNull UUID uuid) { - return this.visualRepository.findByUUID(uuid) - .thenCompose(optional -> optional - .map(CompletableFuture::completedFuture) - .orElseGet(() -> { - JumpVisual defaults = this.getDefaultVisuals(); - return this.visualRepository.save(uuid, defaults).thenApply(v -> defaults) - .exceptionally(throwable -> { - this.logger.log(Level.SEVERE, "Could not save default visual: " + uuid, throwable); - return null; - }); - }) - ); - } - - /** - * Returns the default visual settings defined in the global configuration. - * - * @return default jump visuals - */ - public @NotNull JumpVisual getDefaultVisuals() { - return this.visualConfiguration.defaultVisuals; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/JumpVisualSessionController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/JumpVisualSessionController.java deleted file mode 100644 index 8c33a85..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/JumpVisualSessionController.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualCache; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualRepository; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.server.ServerLoadEvent; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.util.UUID; -import java.util.logging.Level; - -public class JumpVisualSessionController extends PluginListener { - - @Inject private JumpVisualCache visualCache; - @Inject private JumpVisualRepository visualRepository; - @Inject private JumpVisualService visualService; - - @EventHandler(priority = EventPriority.HIGHEST) - void onPlayerQuit(final PlayerQuitEvent event) { - UUID uuid = event.getPlayer().getUniqueId(); - - this.visualCache.getByUuid(uuid) - .ifPresent(settings -> this.saveVisual(uuid, settings)); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onPlayerJoin(final PlayerJoinEvent event) { - this.createVisual(event.getPlayer()); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onServerReload(final ServerLoadEvent event) { - if (event.getType() != ServerLoadEvent.LoadType.RELOAD) { - return; - } - - this.server.getOnlinePlayers().forEach(this::createVisual); - } - - private void createVisual(@NotNull Player player) { - this.visualService.getOrCreate(player.getUniqueId()) - .exceptionally(throwable -> { - this.logger.log(Level.SEVERE, "An error occurred while trying to get the visual settings for player: " + player.getUniqueId(), throwable); - return null; - }); - } - - private void saveVisual(@NotNull UUID uuid, @NotNull JumpVisual visual) { - this.visualRepository.save(uuid, visual) - .exceptionally(throwable -> { - this.logger.log(Level.SEVERE, "An error occurred while trying to save the visual settings: " + uuid, throwable); - return null; - }); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/configuration/JumpVisualConfig.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/configuration/JumpVisualConfig.java deleted file mode 100644 index bd64ca2..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/configuration/JumpVisualConfig.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.configuration; - -import com.github.imdmk.doublejump.config.ConfigSection; -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisual; -import com.github.imdmk.doublejump.jump.feature.visual.particle.JumpParticle; -import com.github.imdmk.doublejump.jump.feature.visual.particle.configuration.JumpParticleConfiguration; -import com.github.imdmk.doublejump.jump.feature.visual.particle.configuration.JumpParticleSerializer; -import com.github.imdmk.doublejump.jump.feature.visual.particle.gui.JumpParticleEntrySerializer; -import com.github.imdmk.doublejump.jump.feature.visual.sound.JumpSound; -import com.github.imdmk.doublejump.jump.feature.visual.sound.configuration.JumpSoundConfiguration; -import com.github.imdmk.doublejump.jump.feature.visual.sound.configuration.JumpSoundSerializer; -import com.github.imdmk.doublejump.jump.feature.visual.sound.configuration.SoundSerializer; -import com.github.imdmk.doublejump.jump.feature.visual.sound.gui.JumpSoundEntrySerializer; -import eu.okaeri.configs.annotation.Comment; -import eu.okaeri.configs.annotation.Header; -import eu.okaeri.configs.serdes.OkaeriSerdesPack; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -@Header({ - "# ", - "# DoubleJump Premium - Visual Effects Configuration", - "# Configure default and custom visual and sound effects", - "# displayed when players perform a double jump.", - "# ", - "# Particles and sounds can be customized per player or globally.", - "# ", - "# Enjoying the plugin? Please leave a review on SpigotMC!", - "# Support development: https://github.com/sponsors/imDMK", - "# " -}) -public class JumpVisualConfig extends ConfigSection { - - @Comment({ - "# Default visual settings applied to players without custom visuals.", - "# Set to 'null' to disable all visuals by default.", - "# Typically used on player join or when resetting visuals." - }) - public JumpVisual defaultVisuals = new JumpVisual( - List.of(new JumpParticle(Particle.HEART, 10)), - new JumpSound(Sound.ENTITY_PLAYER_LEVELUP, 0.3F, 0.5F) - ); - - @Comment("# Configuration for particle effects displayed on double jump") - public JumpParticleConfiguration particles = new JumpParticleConfiguration(); - - @Comment("# Configuration for sound effects played on double jump") - public JumpSoundConfiguration sounds = new JumpSoundConfiguration(); - - @Override - public @NotNull OkaeriSerdesPack getSerdesPack() { - return registry -> { - registry.register(new JumpVisualSerializer()); - - registry.register(new JumpParticleSerializer()); - registry.register(new JumpParticleEntrySerializer()); - - registry.register(new JumpSoundSerializer()); - registry.register(new JumpSoundEntrySerializer()); - registry.register(new SoundSerializer()); - }; - } - - @Override - public @NotNull String getFileName() { - return "visualConfig.yml"; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/configuration/JumpVisualSerializer.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/configuration/JumpVisualSerializer.java deleted file mode 100644 index 60b1c23..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/configuration/JumpVisualSerializer.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.configuration; - -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisual; -import com.github.imdmk.doublejump.jump.feature.visual.particle.JumpParticle; -import com.github.imdmk.doublejump.jump.feature.visual.sound.JumpSound; -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class JumpVisualSerializer implements ObjectSerializer { - - @Override - public boolean supports(@NotNull Class type) { - return JumpVisual.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NotNull JumpVisual visual, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) { - if (!visual.getJumpParticles().isEmpty()) { - data.addCollection("jumpParticles", visual.getJumpParticles(), JumpParticle.class); - } - - visual.getJumpSound() - .ifPresent(sound -> data.add("jumpSound", sound, JumpSound.class)); - } - - @Override - public JumpVisual deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) { - List particles = data.getAsList("jumpParticles", JumpParticle.class); - JumpSound sound = data.get("jumpSound", JumpSound.class); - return new JumpVisual(particles, sound); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/gui/JumpVisualGui.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/gui/JumpVisualGui.java deleted file mode 100644 index bfba04a..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/gui/JumpVisualGui.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.gui; - -import com.github.imdmk.doublejump.infrastructure.gui.AbstractGui; -import com.github.imdmk.doublejump.infrastructure.gui.ParameterizedGui; -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisual; -import com.github.imdmk.doublejump.jump.feature.visual.particle.gui.JumpParticleGui; -import com.github.imdmk.doublejump.jump.feature.visual.sound.gui.JumpSoundGui; -import dev.triumphteam.gui.guis.BaseGui; -import dev.triumphteam.gui.guis.Gui; -import dev.triumphteam.gui.guis.GuiItem; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -public class JumpVisualGui extends AbstractGui implements ParameterizedGui { - - public static final String GUI_IDENTIFIER = "jumpVisualGui"; - - private static final int ROWS = 3; - - @Override - public @NotNull BaseGui createGui(@NotNull Player viewer, @NotNull JumpVisual parameter) { - return Gui.gui() - .title(this.getConfig().title) - .rows(ROWS) - .disableAllInteractions() - .create(); - } - - @Override - public void prepareBorderItems(@NotNull BaseGui gui) { - if (this.guiConfig.fillBorder) { - gui.getFiller().fillBorder(this.guiConfig.borderItem.asGuiItem()); - } - } - - @Override - public void prepareNavigationItems(@NotNull BaseGui gui, @NotNull Player viewer, @NotNull JumpVisual visual) { - this.setExitPageItem(gui, e -> gui.close(viewer)); - } - - @Override - public void prepareItems(@NotNull BaseGui gui, @NotNull Player viewer, @NotNull JumpVisual visual) { - gui.setItem(this.getConfig().particleItem.slot(), this.createParticlePageItem(viewer, visual)); - gui.setItem(this.getConfig().soundItem.slot(), this.createSoundPageItem(viewer, visual)); - } - - // Particle page - private @NotNull GuiItem createParticlePageItem(@NotNull Player viewer, @NotNull JumpVisual visual) { - return this.getConfig().particleItem - .asGuiItem(event -> this.openParticlePage(viewer, visual)); - } - - private void openParticlePage(@NotNull Player viewer, @NotNull JumpVisual visual) { - this.guiManager.openGui(JumpParticleGui.GUI_IDENTIFIER, viewer, visual); - } - - // Sound page - private @NotNull GuiItem createSoundPageItem(@NotNull Player viewer, @NotNull JumpVisual visual) { - return this.getConfig().soundItem - .asGuiItem(event -> this.openSoundPage(viewer, visual)); - } - - private void openSoundPage(@NotNull Player viewer, @NotNull JumpVisual visual) { - this.guiManager.openGui(JumpSoundGui.GUI_IDENTIFIER, viewer, visual); - } - - @Override - public @NotNull String getIdentifier() { - return GUI_IDENTIFIER; - } - - private @NotNull JumpVisualGuiConfiguration getConfig() { - return this.guiConfig.jumpVisualGui; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/gui/JumpVisualGuiConfiguration.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/gui/JumpVisualGuiConfiguration.java deleted file mode 100644 index d5ed127..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/gui/JumpVisualGuiConfiguration.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.gui; - -import com.github.imdmk.doublejump.infrastructure.gui.configuration.ConfigGuiItem; -import com.github.imdmk.doublejump.jump.feature.visual.particle.gui.JumpParticleGuiConfiguration; -import com.github.imdmk.doublejump.jump.feature.visual.sound.gui.JumpSoundGuiConfiguration; -import com.github.imdmk.doublejump.util.ComponentUtil; -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.annotation.Comment; -import net.kyori.adventure.text.Component; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; - -public class JumpVisualGuiConfiguration extends OkaeriConfig { - - @Comment("# Enable GUI visual settings?") - public boolean enabled = true; - - @Comment("# GUI title for double jump visual settings") - public Component title = ComponentUtil.text("Double jump visual settings"); - - @Comment("# Button to open the particle settings menu") - public ConfigGuiItem particleItem = ConfigGuiItem.builder() - .material(Material.NETHER_STAR) - .name("Particles") - .lore( - " ", - " Click to open particle settings", - " " - ) - .slot(12) - .flags(ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ATTRIBUTES) - .enchantment(Enchantment.LOYALTY, 1) - .build(); - - @Comment("# Button to open the sound settings menu") - public ConfigGuiItem soundItem = ConfigGuiItem.builder() - .material(Material.NOTE_BLOCK) - .name("Sound") - .lore( - " ", - " Click to open sound settings", - " " - ) - .slot(14) - .flags(ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ATTRIBUTES) - .enchantment(Enchantment.LOYALTY, 1) - .build(); - - @Comment("# Configuration for the particle settings GUI") - public JumpParticleGuiConfiguration particleGui = new JumpParticleGuiConfiguration(); - - @Comment("# Configuration for the sound settings GUI") - public JumpSoundGuiConfiguration soundGui = new JumpSoundGuiConfiguration(); -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/JumpParticleController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/JumpParticleController.java deleted file mode 100644 index 5d09053..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/JumpParticleController.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.particle; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.event.DoubleJumpEvent; -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisual; -import com.github.imdmk.doublejump.jump.feature.visual.configuration.JumpVisualConfig; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualCache; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -public class JumpParticleController extends PluginListener { - - @Inject private JumpVisualCache visualCache; - @Inject private JumpVisualConfig visualConfiguration; - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onDoubleJump(DoubleJumpEvent event) { - if (!this.visualConfiguration.particles.enabled) { - return; - } - - Player player = event.getPlayer(); - - this.visualCache.getByUuid(player.getUniqueId()) - .ifPresent(settings -> this.spawnParticles(player, settings)); - } - - private void spawnParticles(@NotNull Player player, @NotNull JumpVisual visual) { - visual.getJumpParticles().forEach(particle -> particle.spawn(player.getWorld(), player.getLocation())); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/JumpParticleWrapper.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/JumpParticleWrapper.java deleted file mode 100644 index e2e963b..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/JumpParticleWrapper.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.particle; - -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualWrapper; -import com.j256.ormlite.field.DatabaseField; -import com.j256.ormlite.table.DatabaseTable; -import org.bukkit.Particle; -import org.jetbrains.annotations.NotNull; - -/** - * ORM wrapper for JumpParticle used in database persistence. - */ -@DatabaseTable(tableName = "jump_particles") -public class JumpParticleWrapper { - - @DatabaseField(generatedId = true) - private int id; - - @DatabaseField(foreign = true, canBeNull = false, foreignAutoRefresh = true, columnName = "visual_id") - private JumpVisualWrapper visual; - - @DatabaseField(columnName = "particle", canBeNull = false) - private String particle; - - @DatabaseField(columnName = "count", canBeNull = false) - private int count; - - // Required by ORMLite - public JumpParticleWrapper() {} - - public JumpParticleWrapper(@NotNull JumpParticle particle, @NotNull JumpVisualWrapper visual) { - this.particle = particle.particle().name(); - this.count = particle.count(); - this.visual = visual; - } - - public int getId() { - return this.id; - } - - public @NotNull JumpVisualWrapper visual() { - return this.visual; - } - - public @NotNull JumpParticle toParticle() { - return new JumpParticle(Particle.valueOf(this.particle), this.count); - } - - public @NotNull String particleName() { - return this.particle; - } - - public int count() { - return this.count; - } -} - - diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/configuration/JumpParticleConfiguration.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/configuration/JumpParticleConfiguration.java deleted file mode 100644 index a60bfb2..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/configuration/JumpParticleConfiguration.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.particle.configuration; - -import com.github.imdmk.doublejump.jump.feature.visual.particle.JumpParticle; -import com.github.imdmk.doublejump.jump.feature.visual.particle.gui.JumpParticleEntry; -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.annotation.Comment; -import eu.okaeri.configs.annotation.Header; -import org.bukkit.Material; -import org.bukkit.Particle; - -import java.util.List; - -@Header({ - "# ", - "# DoubleJump Premium - Particle Effects Configuration", - "# Configure particle effects shown when a player performs a double jump.", - "# ", - "# 'enabled' toggles all double jump particle effects globally.", - "# 'supportedParticles' lists all particle effects players can choose via GUI.", - "# Each particle is linked with a corresponding item for the GUI selection.", - "# ", - "# Enjoying the plugin? Please leave a review on SpigotMC!", - "# Support development: https://github.com/sponsors/imDMK", - "# " -}) -public class JumpParticleConfiguration extends OkaeriConfig { - - @Comment("# Enables or disables the double jump particle effect globally.") - public boolean enabled = true; - - @Comment("# List of particles available for players to select in the GUI.") - public List supportedParticles = List.of( - new JumpParticleEntry(new JumpParticle(Particle.HEART, 10), Material.RED_DYE), - new JumpParticleEntry(new JumpParticle(Particle.FLAME, 100), Material.BLAZE_POWDER), - new JumpParticleEntry(new JumpParticle(Particle.CRIT, 80), Material.DIAMOND_SWORD), - new JumpParticleEntry(new JumpParticle(Particle.NOTE, 80), Material.NOTE_BLOCK), - new JumpParticleEntry(new JumpParticle(Particle.PORTAL, 100), Material.ENDER_PEARL), - new JumpParticleEntry(new JumpParticle(Particle.FLASH, 80), Material.GLOWSTONE), - new JumpParticleEntry(new JumpParticle(Particle.LAVA, 80), Material.LAVA_BUCKET), - new JumpParticleEntry(new JumpParticle(Particle.COMPOSTER, 50), Material.COMPOSTER), - new JumpParticleEntry(new JumpParticle(Particle.DRAGON_BREATH, 100), Material.DRAGON_BREATH), - new JumpParticleEntry(new JumpParticle(Particle.END_ROD, 100), Material.END_ROD), - new JumpParticleEntry(new JumpParticle(Particle.DAMAGE_INDICATOR, 0), Material.DIAMOND_SWORD), - new JumpParticleEntry(new JumpParticle(Particle.SWEEP_ATTACK, 50), Material.IRON_SWORD), - new JumpParticleEntry(new JumpParticle(Particle.FALLING_DUST, 100), Material.SAND), - new JumpParticleEntry(new JumpParticle(Particle.ELECTRIC_SPARK, 80), Material.LEVER), - new JumpParticleEntry(new JumpParticle(Particle.GLOW, 80), Material.GLOWSTONE), - new JumpParticleEntry(new JumpParticle(Particle.DUST_COLOR_TRANSITION, 80), Material.PAINTING), - new JumpParticleEntry(new JumpParticle(Particle.DRIPPING_DRIPSTONE_WATER, 50), Material.WATER_BUCKET), - new JumpParticleEntry(new JumpParticle(Particle.DRIPPING_DRIPSTONE_LAVA, 50), Material.LAVA_BUCKET), - new JumpParticleEntry(new JumpParticle(Particle.FALLING_DRIPSTONE_WATER, 50), Material.WATER_BUCKET), - new JumpParticleEntry(new JumpParticle(Particle.FALLING_DRIPSTONE_LAVA, 50), Material.LAVA_BUCKET), - new JumpParticleEntry(new JumpParticle(Particle.FALLING_SPORE_BLOSSOM, 50), Material.SPORE_BLOSSOM), - new JumpParticleEntry(new JumpParticle(Particle.SPORE_BLOSSOM_AIR, 50), Material.SPORE_BLOSSOM) - ); -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/configuration/JumpParticleSerializer.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/configuration/JumpParticleSerializer.java deleted file mode 100644 index 16fc274..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/configuration/JumpParticleSerializer.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.particle.configuration; - -import com.github.imdmk.doublejump.jump.feature.visual.particle.JumpParticle; -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import org.bukkit.Particle; -import org.jetbrains.annotations.NotNull; - -public class JumpParticleSerializer implements ObjectSerializer { - - @Override - public boolean supports(@NotNull Class type) { - return JumpParticle.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NotNull JumpParticle particle, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) { - data.add("particle", particle.particle(), Particle.class); - data.add("count", particle.count(), Integer.class); - } - - @Override - public JumpParticle deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) { - Particle particle = data.get("particle", Particle.class); - int count = data.get("count", Integer.class); - return new JumpParticle(particle, count); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/gui/JumpParticleEntry.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/gui/JumpParticleEntry.java deleted file mode 100644 index 9b59ec4..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/gui/JumpParticleEntry.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.particle.gui; - -import com.github.imdmk.doublejump.jump.feature.visual.particle.JumpParticle; -import org.bukkit.Material; -import org.jetbrains.annotations.NotNull; - -/** - * Represents a mapping between a Minecraft particle effect and its corresponding GUI display item. - * - * @param particle The particle effect. - * @param displayItem The item used to visually represent the particle in the GUI. - */ -public record JumpParticleEntry(@NotNull JumpParticle particle, @NotNull Material displayItem) { -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/gui/JumpParticleEntrySerializer.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/gui/JumpParticleEntrySerializer.java deleted file mode 100644 index bc2cfeb..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/gui/JumpParticleEntrySerializer.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.particle.gui; - -import com.github.imdmk.doublejump.jump.feature.visual.particle.JumpParticle; -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import org.bukkit.Material; -import org.jetbrains.annotations.NotNull; - -public class JumpParticleEntrySerializer implements ObjectSerializer { - - @Override - public boolean supports(@NotNull Class type) { - return JumpParticleEntry.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NotNull JumpParticleEntry guiEntry, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) { - data.add("particle", guiEntry.particle(), JumpParticle.class); - data.add("displayItem", guiEntry.displayItem(), Material.class); - } - - @Override - public JumpParticleEntry deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) { - JumpParticle particle = data.get("particle", JumpParticle.class); - Material displayItem = data.get("displayItem", Material.class); - return new JumpParticleEntry(particle, displayItem); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/gui/JumpParticleGui.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/gui/JumpParticleGui.java deleted file mode 100644 index f348746..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/gui/JumpParticleGui.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.particle.gui; - -import com.github.imdmk.doublejump.infrastructure.gui.AbstractGui; -import com.github.imdmk.doublejump.infrastructure.gui.ParameterizedGui; -import com.github.imdmk.doublejump.infrastructure.gui.configuration.ConfigGuiItem; -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisual; -import com.github.imdmk.doublejump.jump.feature.visual.configuration.JumpVisualConfig; -import com.github.imdmk.doublejump.jump.feature.visual.gui.JumpVisualGui; -import com.github.imdmk.doublejump.util.ComponentUtil; -import dev.triumphteam.gui.builder.item.ItemBuilder; -import dev.triumphteam.gui.guis.BaseGui; -import dev.triumphteam.gui.guis.Gui; -import dev.triumphteam.gui.guis.GuiItem; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.inventory.ItemFlag; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.util.Collections; -import java.util.Map; - -public class JumpParticleGui extends AbstractGui implements ParameterizedGui { - - public static final String GUI_IDENTIFIER = "jumpParticleGui"; - - private static final int ROWS = 6; - private static final Map ACTIVE_ENCHANTMENTS = Map.of(Enchantment.LOYALTY, 3); - - @Inject protected JumpVisualConfig visualConfiguration; - - @Override - public @NotNull BaseGui createGui(@NotNull Player viewer, @NotNull JumpVisual visual) { - return Gui.paginated() - .title(this.getConfig().title) - .rows(ROWS) - .disableAllInteractions() - .create(); - } - - @Override - public void prepareNavigationItems(@NotNull BaseGui gui, @NotNull Player viewer, @NotNull JumpVisual visual) { - this.setNextPageItem(gui); - this.setPreviousPageItem(gui); - this.setExitPageItem(gui, e -> this.guiManager.openGui(JumpVisualGui.GUI_IDENTIFIER, viewer, visual)); - } - - @Override - public void prepareBorderItems(@NotNull BaseGui gui) { - if (this.guiConfig.fillBorder) { - gui.getFiller().fillBorder(this.guiConfig.borderItem.asGuiItem()); - } - } - - @Override - public void prepareItems(@NotNull BaseGui gui, @NotNull Player viewer, @NotNull JumpVisual visual) { - gui.addItem(this.buildDisableItem(viewer, visual)); - - for (JumpParticleEntry entry : this.visualConfiguration.particles.supportedParticles) { - gui.addItem(this.buildParticleItem(viewer, visual, entry)); - } - } - - private @NotNull GuiItem buildDisableItem(@NotNull Player viewer, @NotNull JumpVisual visual) { - ConfigGuiItem.Builder builder = ConfigGuiItem.builder().from(this.getConfig().disableItem); - - if (visual.getJumpParticles().isEmpty()) { - builder.enchantments(ACTIVE_ENCHANTMENTS); - } - - return builder.build().asGuiItem(event -> { - if (event.getClick() == ClickType.LEFT) { - visual.setJumpParticles(Collections.emptyList()); - this.getConfig().disableItemClickSound.play(viewer); - - this.open(viewer, visual); - } - }); - } - - private @NotNull GuiItem buildParticleItem(@NotNull Player viewer, @NotNull JumpVisual visual, @NotNull JumpParticleEntry entry) { - ItemBuilder builder = ItemBuilder.from(entry.displayItem()) - .name(ComponentUtil.notItalic(this.getConfig().nameColor + entry.particle().particleName())) - .lore(this.getConfig().defaultLore); - - if (visual.getJumpParticles().contains(entry.particle())) { - builder.enchant(ACTIVE_ENCHANTMENTS); - builder.lore(this.getConfig().activeLore); - builder.flags(ItemFlag.HIDE_ENCHANTS); - } - - return builder.asGuiItem(event -> this.handleParticleClick(event.getClick(), viewer, visual, entry)); - } - - private void handleParticleClick(@NotNull ClickType click, @NotNull Player viewer, @NotNull JumpVisual visual, @NotNull JumpParticleEntry entry) { - if (click != ClickType.LEFT) { - return; - } - - boolean active = visual.getJumpParticles().contains(entry.particle()); - - if (active) { - visual.removeJumpParticle(entry.particle()); - this.getConfig().disableItemClickSound.play(viewer); - } - else { - visual.addJumpParticle(entry.particle()); - this.getConfig().enableParticleSound.play(viewer); - } - - this.open(viewer, visual); - } - - @Override - public @NotNull String getIdentifier() { - return GUI_IDENTIFIER; - } - - private @NotNull JumpParticleGuiConfiguration getConfig() { - return this.guiConfig.jumpVisualGui.particleGui; - } -} - diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/gui/JumpParticleGuiConfiguration.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/gui/JumpParticleGuiConfiguration.java deleted file mode 100644 index 5b71035..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/particle/gui/JumpParticleGuiConfiguration.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.particle.gui; - -import com.github.imdmk.doublejump.infrastructure.gui.configuration.ConfigGuiItem; -import com.github.imdmk.doublejump.jump.feature.visual.sound.JumpSound; -import com.github.imdmk.doublejump.util.ComponentUtil; -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.annotation.Comment; -import net.kyori.adventure.text.Component; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.inventory.ItemFlag; - -import java.util.List; - -public class JumpParticleGuiConfiguration extends OkaeriConfig { - - @Comment("# Title of the jump particle selection GUI") - public Component title = ComponentUtil.text("Choose jump particles"); - - @Comment("# Item used to disable particle effects") - public ConfigGuiItem disableItem = ConfigGuiItem.builder() - .material(Material.BARRIER) - .name("DISABLE PARTICLES") - .lore( - " ", - "Choose this to disable jump particles.", - " " - ) - .flags(ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ATTRIBUTES) - .build(); - - @Comment("# Sound played when the player clicks the disable item.") - public JumpSound disableItemClickSound = new JumpSound(Sound.ENTITY_VILLAGER_NO, 0.5F, 0.5F); - - @Comment("# Prefix color used to display the name of the selected particle.") - public String nameColor = ""; - - @Comment("# Lore shown on the currently selected particle option.") - public List activeLore = ComponentUtil.notItalic( - " ", - "Selected", - "Click LEFT to remove this jump particle.", - " " - ); - - @Comment("# Lore shown on a non-selected particle option.") - public List defaultLore = ComponentUtil.notItalic( - " ", - "Click LEFT to add this jump particle.", - " " - ); - - @Comment("# Sound played when the player clicks to enable particle.") - public JumpSound enableParticleSound = new JumpSound(Sound.ENTITY_VILLAGER_YES, 0.5F, 0.5F); - - @Comment("# Sound played when the player clicks to disable particle.") - public JumpSound disableParticleSound = new JumpSound(Sound.ENTITY_VILLAGER_NO, 0.5F, 0.5F); -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/JumpVisualWrapper.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/JumpVisualWrapper.java deleted file mode 100644 index 268c4d0..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/JumpVisualWrapper.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.repository; - -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisual; -import com.github.imdmk.doublejump.jump.feature.visual.particle.JumpParticle; -import com.github.imdmk.doublejump.jump.feature.visual.particle.JumpParticleWrapper; -import com.github.imdmk.doublejump.jump.feature.visual.sound.JumpSound; -import com.github.imdmk.doublejump.jump.feature.visual.sound.JumpSoundPersister; -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.dao.ForeignCollection; -import com.j256.ormlite.field.DatabaseField; -import com.j256.ormlite.field.ForeignCollectionField; -import com.j256.ormlite.table.DatabaseTable; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.sql.SQLException; -import java.util.List; -import java.util.UUID; - -/** - * ORM entity wrapping JumpVisual settings per player UUID. - * Responsible for persisting particle effects and sound configuration. - */ -@DatabaseTable(tableName = "jump_visuals") -public class JumpVisualWrapper { - - @DatabaseField(id = true, columnName = "uuid", canBeNull = false) - private UUID uuid; - - @ForeignCollectionField(eager = true) - private ForeignCollection particles; - - @DatabaseField(columnName = "sound", persisterClass = JumpSoundPersister.class) - private JumpSound sound; - - /** - * Required by ORMLite. - */ - public JumpVisualWrapper() {} - - private JumpVisualWrapper( - @NotNull UUID uuid, - @NotNull ForeignCollection particles, - @Nullable JumpSound sound - ) { - this.uuid = uuid; - this.particles = particles; - this.sound = sound; - } - - public @NotNull UUID getUuid() { - return this.uuid; - } - - public void setUuid(@NotNull UUID uuid) { - this.uuid = uuid; - } - - /** - * Returns an unmodifiable list of JumpParticle from wrapped JumpParticleWrapper entities. - */ - public @NotNull List getParticles() { - return this.particles.stream() - .map(JumpParticleWrapper::toParticle) - .toList(); - } - - public void setParticles(@NotNull ForeignCollection particles) { - this.particles = particles; - } - - /** - * Adds a single JumpParticleWrapper to the foreign collection. - */ - public void addParticle(@NotNull JumpParticleWrapper particleWrapper) { - this.particles.add(particleWrapper); - } - - public @Nullable JumpSound getSound() { - return this.sound; - } - - public void setSound(@Nullable JumpSound sound) { - this.sound = sound; - } - - /** - * Creates a JumpVisualWrapper entity from runtime JumpVisual instance. - * - * @param uuid player UUID - * @param visual runtime JumpVisual settings - * @param wrapperDao DAO to get an empty foreign collection - * @return new JumpVisualWrapper ready to persist - * @throws SQLException if a foreign collection cannot be created - */ - public static @NotNull JumpVisualWrapper from( - @NotNull UUID uuid, - @NotNull JumpVisual visual, - @NotNull Dao wrapperDao - ) throws SQLException { - JumpVisualWrapper wrapper = new JumpVisualWrapper(); - wrapper.setUuid(uuid); - wrapper.setSound(visual.getJumpSound().orElse(null)); - wrapper.setParticles(wrapperDao.getEmptyForeignCollection("particles")); - - visual.getJumpParticles().stream() - .map(particle -> new JumpParticleWrapper(particle, wrapper)) - .forEach(wrapper::addParticle); - - return wrapper; - } - - /** - * Converts this entity to a runtime JumpVisual object. - */ - public @NotNull JumpVisual toVisual() { - return new JumpVisual(this.getParticles(), this.getSound()); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/impl/DaoJumpVisualRepository.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/impl/DaoJumpVisualRepository.java deleted file mode 100644 index 3cf2bb1..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/impl/DaoJumpVisualRepository.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.repository.impl; - -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisual; -import com.github.imdmk.doublejump.jump.feature.visual.particle.JumpParticleWrapper; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualCache; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualRepository; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualWrapper; -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.dao.DaoManager; -import com.j256.ormlite.support.ConnectionSource; -import com.j256.ormlite.table.TableUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.sql.SQLException; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class DaoJumpVisualRepository implements JumpVisualRepository { - - private final Logger logger; - private final Dao visualDao; - private final ExecutorService executor; - - private final JumpVisualCache visualCache; - - public DaoJumpVisualRepository( - @NotNull Logger logger, - @Nullable ConnectionSource connectionSource, - @NotNull JumpVisualCache visualCache) throws SQLException { - this.logger = Objects.requireNonNull(logger, "logger cannot be null"); - Objects.requireNonNull(connectionSource, "connectionSource cannot be null"); - - this.visualDao = DaoManager.createDao(connectionSource, JumpVisualWrapper.class); - this.executor = Executors.newCachedThreadPool(); - this.visualCache = Objects.requireNonNull(visualCache, "Visual Cache cannot be null"); - - TableUtils.createTableIfNotExists(connectionSource, JumpVisualWrapper.class); - TableUtils.createTableIfNotExists(connectionSource, JumpParticleWrapper.class); - } - - @Override - public CompletableFuture> findByUUID(@NotNull UUID uuid) { - Optional cachedVisual = this.visualCache.getByUuid(uuid); - if (cachedVisual.isPresent()) { - return CompletableFuture.completedFuture(cachedVisual); - } - - return CompletableFuture.supplyAsync(() -> { - try { - Optional visualOptional = Optional.ofNullable(this.visualDao.queryBuilder() - .where().eq("uuid", uuid) - .queryForFirst()) - .map(JumpVisualWrapper::toVisual); - - visualOptional.ifPresent(visual -> this.visualCache.cache(uuid, visual)); - - return visualOptional; - } - catch (SQLException sqlException) { - this.logger.log(Level.SEVERE, "An error occurred while retrieving settings for " + uuid, sqlException); - throw new CompletionException(sqlException); - } - }, this.executor).orTimeout(3L, TimeUnit.SECONDS); - } - - @Override - public CompletableFuture save(@NotNull UUID uuid, @NotNull JumpVisual visual) { - return CompletableFuture.supplyAsync(() -> { - try { - JumpVisualWrapper wrapper = JumpVisualWrapper.from(uuid, visual, this.visualDao); - this.visualDao.createOrUpdate(wrapper); - this.visualCache.cache(uuid, visual); - return visual; - } - catch (SQLException sqlException) { - this.logger.log(Level.SEVERE, "An error occurred while saving settings for " + uuid, sqlException); - throw new CompletionException(sqlException); - } - }, this.executor).orTimeout(3L, TimeUnit.SECONDS); - } - - @Override - public CompletableFuture delete(@NotNull UUID uuid) { - return CompletableFuture.runAsync(() -> { - try { - this.visualDao.deleteById(uuid); - this.visualCache.evict(uuid); - } - catch (SQLException sqlException) { - this.logger.log(Level.SEVERE, "An error occurred while deleting settings for " + uuid, sqlException); - throw new CompletionException(sqlException); - } - }, this.executor).orTimeout(3L, TimeUnit.SECONDS); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/impl/EmptyJumpVisualRepository.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/impl/EmptyJumpVisualRepository.java deleted file mode 100644 index fb91f22..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/repository/impl/EmptyJumpVisualRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.repository.impl; - -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisual; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualRepository; -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -public class EmptyJumpVisualRepository implements JumpVisualRepository { - - @Override - public CompletableFuture> findByUUID(@NotNull UUID uuid) { - return CompletableFuture.failedFuture(new UnsupportedOperationException()); - } - - @Override - public CompletableFuture save(@NotNull UUID uuid, @NotNull JumpVisual visual) { - return CompletableFuture.failedFuture(new UnsupportedOperationException()); - } - - @Override - public CompletableFuture delete(@NotNull UUID uuid) { - return CompletableFuture.failedFuture(new UnsupportedOperationException()); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/JumpSoundController.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/JumpSoundController.java deleted file mode 100644 index 6f72598..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/JumpSoundController.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.sound; - -import com.github.imdmk.doublejump.infrastructure.injector.PluginListener; -import com.github.imdmk.doublejump.jump.event.DoubleJumpEvent; -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisual; -import com.github.imdmk.doublejump.jump.feature.visual.configuration.JumpVisualConfig; -import com.github.imdmk.doublejump.jump.feature.visual.repository.JumpVisualCache; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -public class JumpSoundController extends PluginListener { - - @Inject private JumpVisualCache visualCache; - @Inject private JumpVisualConfig visualConfiguration; - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onDoubleJump(DoubleJumpEvent event) { - if (!this.visualConfiguration.sounds.enabled) { - return; - } - - Player player = event.getPlayer(); - - this.visualCache.getByUuid(player.getUniqueId()) - .ifPresent(visual -> this.playSound(player, visual)); - } - - private void playSound(@NotNull Player player, @NotNull JumpVisual visual) { - visual.getJumpSound().ifPresent(sound -> sound.play(player)); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/JumpSoundPersister.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/JumpSoundPersister.java deleted file mode 100644 index b7fe707..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/JumpSoundPersister.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.sound; - -import com.j256.ormlite.field.FieldType; -import com.j256.ormlite.field.SqlType; -import com.j256.ormlite.field.types.BaseDataType; -import com.j256.ormlite.support.DatabaseResults; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.Sound; - -import java.sql.SQLException; - -public class JumpSoundPersister extends BaseDataType { - - private static final JumpSoundPersister INSTANCE = new JumpSoundPersister(); - - private JumpSoundPersister() { - super(SqlType.LONG_STRING, new Class[] { JumpSoundPersister.class }); - } - - public static JumpSoundPersister getSingleton() { - return INSTANCE; - } - - @Override - public Object javaToSqlArg(FieldType fieldType, Object javaObject) { - JumpSound sound = (JumpSound) javaObject; - - return String.format("%s:%s:%s", sound.sound().getKey().getKey(), sound.volume(), sound.pitch()); - } - - @Override - public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { - return results.getString(columnPos); - } - - @Override - public Object parseDefaultString(FieldType fieldType, String defaultStr) { - return defaultStr; - } - - @Override - public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) { - String arg = (String) sqlArg; - String[] params = arg.split(":"); - if (params.length != 3) { - throw new IllegalArgumentException("Invalid string format: " + arg); - } - - try { - Sound sound = Registry.SOUNDS.get(NamespacedKey.minecraft(params[0])); - float volume = Float.parseFloat(params[1]); - float pitch = Float.parseFloat(params[2]); - - return new JumpSound(sound, volume, pitch); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid string format: " + arg, e); - } - } -} - diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/configuration/JumpSoundConfiguration.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/configuration/JumpSoundConfiguration.java deleted file mode 100644 index 94488d2..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/configuration/JumpSoundConfiguration.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.sound.configuration; - -import com.github.imdmk.doublejump.jump.feature.visual.sound.JumpSound; -import com.github.imdmk.doublejump.jump.feature.visual.sound.gui.JumpSoundEntry; -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.annotation.Comment; -import org.bukkit.Material; -import org.bukkit.Sound; - -import java.util.List; - -public class JumpSoundConfiguration extends OkaeriConfig { - - @Comment("# Enables or disables the double jump sounds.") - public boolean enabled = true; - - @Comment("# List of sounds that players can choose via gui.") - public List supportedSounds = List.of( - new JumpSoundEntry(new JumpSound(Sound.ENTITY_PLAYER_LEVELUP, 0.5F, 0F), Material.EXPERIENCE_BOTTLE), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_BLAZE_SHOOT, 1.0F, 1.0F), Material.BLAZE_ROD), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_CAT_PURR, 1.0F, 1.0F), Material.CAT_SPAWN_EGG), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_CHICKEN_AMBIENT, 1.0F, 1.0F), Material.CHICKEN_SPAWN_EGG), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_COW_AMBIENT, 1.0F, 1.0F), Material.COW_SPAWN_EGG), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_DRAGON_FIREBALL_EXPLODE, 1.0F, 1.0F), Material.DRAGON_BREATH), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_GHAST_SCREAM, 1.0F, 1.0F), Material.GHAST_TEAR), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_GUARDIAN_AMBIENT, 1.0F, 1.0F), Material.PRISMARINE_SHARD), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_HORSE_AMBIENT, 1.0F, 1.0F), Material.SADDLE), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_IRON_GOLEM_ATTACK, 1.0F, 1.0F), Material.IRON_INGOT), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_LIGHTNING_BOLT_THUNDER, 1.0F, 1.0F), Material.GOLDEN_AXE), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_PLAYER_ATTACK_SWEEP, 1.0F, 1.0F), Material.IRON_SWORD), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_SHEEP_AMBIENT, 1.0F, 1.0F), Material.WHITE_WOOL), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_SKELETON_AMBIENT, 1.0F, 1.0F), Material.BONE), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_SLIME_JUMP, 1.0F, 1.0F), Material.SLIME_BALL), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_SNOWBALL_THROW, 1.0F, 1.0F), Material.SNOWBALL), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_SPIDER_AMBIENT, 1.0F, 1.0F), Material.SPIDER_EYE), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_ZOMBIE_AMBIENT, 1.0F, 1.0F), Material.ROTTEN_FLESH), - new JumpSoundEntry(new JumpSound(Sound.BLOCK_ANVIL_LAND, 1.0F, 1.0F), Material.ANVIL), - new JumpSoundEntry(new JumpSound(Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1.0F, 1.0F), Material.ENCHANTING_TABLE), - new JumpSoundEntry(new JumpSound(Sound.BLOCK_GLASS_BREAK, 1.0F, 1.0F), Material.GLASS), - new JumpSoundEntry(new JumpSound(Sound.BLOCK_LAVA_EXTINGUISH, 1.0F, 1.0F), Material.LAVA_BUCKET), - new JumpSoundEntry(new JumpSound(Sound.BLOCK_NOTE_BLOCK_BELL, 1.0F, 1.0F), Material.BELL), - new JumpSoundEntry(new JumpSound(Sound.BLOCK_PORTAL_TRIGGER, 1.0F, 1.0F), Material.ENDER_PEARL), - new JumpSoundEntry(new JumpSound(Sound.BLOCK_SAND_BREAK, 1.0F, 1.0F), Material.SAND), - new JumpSoundEntry(new JumpSound(Sound.BLOCK_WOOD_BREAK, 1.0F, 1.0F), Material.OAK_PLANKS), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.0F), Material.EXPERIENCE_BOTTLE), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1.0F, 1.0F), Material.FIREWORK_ROCKET), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_PLAYER_BURP, 1.0F, 1.0F), Material.APPLE), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_PLAYER_BREATH, 1.0F, 1.0F), Material.POTION), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F), Material.EXPERIENCE_BOTTLE), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_GENERIC_EXPLODE, 1.0F, 1.0F), Material.TNT), - new JumpSoundEntry(new JumpSound(Sound.ENTITY_GENERIC_HURT, 1.0F, 1.0F), Material.IRON_SWORD), - new JumpSoundEntry(new JumpSound(Sound.UI_BUTTON_CLICK, 1.0F, 1.0F), Material.STONE_BUTTON), - new JumpSoundEntry(new JumpSound(Sound.UI_TOAST_CHALLENGE_COMPLETE, 1.0F, 1.0F), Material.CHEST) - ); -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/configuration/JumpSoundSerializer.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/configuration/JumpSoundSerializer.java deleted file mode 100644 index 9f6449a..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/configuration/JumpSoundSerializer.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.sound.configuration; - -import com.github.imdmk.doublejump.jump.feature.visual.sound.JumpSound; -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import org.bukkit.Sound; -import org.jetbrains.annotations.NotNull; - -public class JumpSoundSerializer implements ObjectSerializer { - - @Override - public boolean supports(@NotNull Class type) { - return JumpSound.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NotNull JumpSound healingSound, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) { - data.add("sound", healingSound.sound(), Sound.class); - data.add("volume", healingSound.volume(), float.class); - data.add("pitch", healingSound.pitch(), float.class); - } - - @Override - public JumpSound deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) { - Sound sound = data.get("sound", Sound.class); - float volume = data.get("volume", float.class); - float pitch = data.get("pitch", float.class); - - return new JumpSound(sound, volume, pitch); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/configuration/SoundSerializer.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/configuration/SoundSerializer.java deleted file mode 100644 index 0558ce7..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/configuration/SoundSerializer.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.sound.configuration; - -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import org.bukkit.Sound; -import org.jetbrains.annotations.NotNull; - -public class SoundSerializer implements ObjectSerializer { - - @Override - public boolean supports(@NotNull Class type) { - return Sound.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NotNull Sound sound, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) { - data.setValue(sound.name(), String.class); - } - - @Override - public Sound deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) { - return Sound.valueOf(data.getValue(String.class)); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/gui/JumpSoundEntry.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/gui/JumpSoundEntry.java deleted file mode 100644 index aeb7f7a..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/gui/JumpSoundEntry.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.sound.gui; - -import com.github.imdmk.doublejump.jump.feature.visual.sound.JumpSound; -import org.bukkit.Material; -import org.jetbrains.annotations.NotNull; - -/** - * Represents a mapping between a Minecraft sound effect and its corresponding GUI display item. - * - * @param jumpSound The sound effect. - * @param displayItem The item used to visually represent the sound in the GUI. - */ -public record JumpSoundEntry(@NotNull JumpSound jumpSound, @NotNull Material displayItem) { -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/gui/JumpSoundEntrySerializer.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/gui/JumpSoundEntrySerializer.java deleted file mode 100644 index d2e330c..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/gui/JumpSoundEntrySerializer.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.sound.gui; - -import com.github.imdmk.doublejump.jump.feature.visual.sound.JumpSound; -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import org.bukkit.Material; -import org.jetbrains.annotations.NotNull; - -public class JumpSoundEntrySerializer implements ObjectSerializer { - - @Override - public boolean supports(@NotNull Class type) { - return JumpSoundEntry.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NotNull JumpSoundEntry soundEntry, @NotNull SerializationData data, @NotNull GenericsDeclaration generics) { - data.add("sound", soundEntry.jumpSound(), JumpSound.class); - data.add("displayItem", soundEntry.displayItem(), Material.class); - } - - @Override - public JumpSoundEntry deserialize(@NotNull DeserializationData data, @NotNull GenericsDeclaration generics) { - JumpSound sound = data.get("sound", JumpSound.class); - Material displayItem = data.get("displayItem", Material.class); - return new JumpSoundEntry(sound, displayItem); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/gui/JumpSoundGui.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/gui/JumpSoundGui.java deleted file mode 100644 index f184c59..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/gui/JumpSoundGui.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.sound.gui; - -import com.github.imdmk.doublejump.infrastructure.gui.AbstractGui; -import com.github.imdmk.doublejump.infrastructure.gui.ParameterizedGui; -import com.github.imdmk.doublejump.infrastructure.gui.configuration.ConfigGuiItem; -import com.github.imdmk.doublejump.jump.feature.visual.JumpVisual; -import com.github.imdmk.doublejump.jump.feature.visual.configuration.JumpVisualConfig; -import com.github.imdmk.doublejump.jump.feature.visual.gui.JumpVisualGui; -import com.github.imdmk.doublejump.util.ComponentUtil; -import dev.triumphteam.gui.builder.item.ItemBuilder; -import dev.triumphteam.gui.guis.BaseGui; -import dev.triumphteam.gui.guis.Gui; -import dev.triumphteam.gui.guis.GuiItem; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.inventory.ItemFlag; -import org.jetbrains.annotations.NotNull; -import org.panda_lang.utilities.inject.annotations.Inject; - -import java.util.Map; - -public class JumpSoundGui extends AbstractGui implements ParameterizedGui { - - public static final String GUI_IDENTIFIER = "jumpSoundGui"; - - private static final int ROWS = 6; - private static final Map ACTIVE_ENCHANTMENTS = Map.of(Enchantment.LOYALTY, 3); - - @Inject protected JumpVisualConfig visualConfiguration; - - @Override - public @NotNull BaseGui createGui(@NotNull Player viewer, @NotNull JumpVisual visual) { - return Gui.paginated() - .title(this.getConfig().title) - .rows(ROWS) - .disableAllInteractions() - .create(); - } - - @Override - public void prepareNavigationItems(@NotNull BaseGui gui, @NotNull Player viewer, @NotNull JumpVisual visual) { - this.setNextPageItem(gui); - this.setPreviousPageItem(gui); - this.setExitPageItem(gui, e -> this.guiManager.openGui(JumpVisualGui.GUI_IDENTIFIER, viewer, visual)); - } - - @Override - public void prepareBorderItems(@NotNull BaseGui gui) { - if (this.guiConfig.fillBorder) { - gui.getFiller().fillBorder(this.guiConfig.borderItem.asGuiItem()); - } - } - - @Override - public void prepareItems(@NotNull BaseGui gui, @NotNull Player viewer, @NotNull JumpVisual visual) { - gui.addItem(this.buildDisableItem(viewer, visual)); - - for (JumpSoundEntry entry : this.visualConfiguration.sounds.supportedSounds) { - gui.addItem(this.buildSoundItem(viewer, visual, entry)); - } - } - - private @NotNull GuiItem buildDisableItem(@NotNull Player viewer, @NotNull JumpVisual visual) { - ConfigGuiItem.Builder builder = ConfigGuiItem.builder().from(this.getConfig().disableItem); - - if (visual.getJumpSound().isEmpty()) { - builder.enchantments(ACTIVE_ENCHANTMENTS); - } - - return builder.build().asGuiItem(event -> { - if (event.getClick() != ClickType.RIGHT) { - visual.setJumpSound(null); - this.getConfig().disableItemClickSound.play(viewer); - - this.open(viewer, visual); - } - }); - } - - private @NotNull GuiItem buildSoundItem(@NotNull Player viewer, @NotNull JumpVisual visual, @NotNull JumpSoundEntry entry) { - ItemBuilder builder = ItemBuilder.from(entry.displayItem()) - .name(ComponentUtil.notItalic(this.getConfig().nameColor + entry.jumpSound().getName())) - .lore(this.getConfig().defaultLore); - - if (visual.isJumpSound(entry.jumpSound())) { - builder.lore(this.getConfig().activeLore); - builder.enchant(ACTIVE_ENCHANTMENTS); - builder.flags(ItemFlag.HIDE_ENCHANTS); - } - - return builder.asGuiItem(event -> this.handleSoundClick(event.getClick(), viewer, visual, entry)); - } - - private void handleSoundClick(@NotNull ClickType click, @NotNull Player viewer, @NotNull JumpVisual visual, @NotNull JumpSoundEntry entry) { - entry.jumpSound().play(viewer); - - if (click == ClickType.RIGHT) { - visual.setJumpSound(entry.jumpSound()); - this.open(viewer, visual); - } - } - - @Override - public @NotNull String getIdentifier() { - return GUI_IDENTIFIER; - } - - private @NotNull JumpSoundGuiConfiguration getConfig() { - return this.guiConfig.jumpVisualGui.soundGui; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/gui/JumpSoundGuiConfiguration.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/gui/JumpSoundGuiConfiguration.java deleted file mode 100644 index f90dd1b..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/jump/feature/visual/sound/gui/JumpSoundGuiConfiguration.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.github.imdmk.doublejump.jump.feature.visual.sound.gui; - -import com.github.imdmk.doublejump.infrastructure.gui.configuration.ConfigGuiItem; -import com.github.imdmk.doublejump.jump.feature.visual.sound.JumpSound; -import com.github.imdmk.doublejump.util.ComponentUtil; -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.annotation.Comment; -import net.kyori.adventure.text.Component; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.inventory.ItemFlag; - -import java.util.List; - -public class JumpSoundGuiConfiguration extends OkaeriConfig { - - @Comment("# Title of the jump sound selection GUI") - public Component title = ComponentUtil.text("Choose new jump sound"); - - @Comment("# Item used to disable jump sound") - public ConfigGuiItem disableItem = ConfigGuiItem.builder() - .material(Material.BARRIER) - .name("DISABLE SOUND") - .lore( - " ", - "Choose this to disable jump sound.", - " " - ) - .flags(ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ATTRIBUTES) - .build(); - - @Comment("# Sound played when the player clicks the disable sound item.") - public JumpSound disableItemClickSound = new JumpSound(Sound.ENTITY_VILLAGER_NO, 0.5F, 0.5F); - - @Comment("# Prefix color used to display the name of the selected sound.") - public String nameColor = ""; - - @Comment("# Lore shown on the selected (active) sound option.") - public List activeLore = ComponentUtil.notItalic( - " ", - "Selected", - "Click LEFT to preview this sound.", - " " - ); - - @Comment("# Lore shown on a non-selected (default) sound option.") - public List defaultLore = ComponentUtil.notItalic( - " ", - "Click LEFT to preview this sound.", - "Click RIGHT to choose this sound.", - " " - ); -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/plugin/DoubleJumpCoreWrapper.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/plugin/DoubleJumpCoreWrapper.java new file mode 100644 index 0000000..0bf153a --- /dev/null +++ b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/plugin/DoubleJumpCoreWrapper.java @@ -0,0 +1,60 @@ +package com.github.imdmk.doublejump.plugin; + +import org.bukkit.plugin.Plugin; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +final class DoubleJumpCoreWrapper { + + private static final String LOADER_CORE_CLASS = "com.github.imdmk.doublejump.core.DoubleJumpCore"; + + private final Class coreClass; + private Object core; + + DoubleJumpCoreWrapper(Class coreClass) { + this.coreClass = coreClass; + } + + void enable(Plugin plugin) { + try { + Constructor coreConstructor = this.coreClass.getDeclaredConstructor(Plugin.class); + coreConstructor.setAccessible(true); + + core = coreConstructor.newInstance(plugin); + } catch (InvocationTargetException exception) { + if (exception.getCause() instanceof RuntimeException runtimeException) { + throw runtimeException; + } + + throw new RuntimeException("Can not enable DoubleJump: ", exception.getCause()); + } catch (IllegalAccessException | NoSuchMethodException | InstantiationException exception) { + throw new RuntimeException(exception); + } + } + + void disable() { + try { + Method disableMethod = this.coreClass.getDeclaredMethod("disable"); + + disableMethod.setAccessible(true); + + if (core != null) { + disableMethod.invoke(core); + } + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) { + throw new RuntimeException(exception); + } + } + + static DoubleJumpCoreWrapper create(ClassLoader loader) { + try { + Class coreClass = Class.forName(LOADER_CORE_CLASS, true, loader); + return new DoubleJumpCoreWrapper(coreClass); + } catch (ClassNotFoundException exception) { + throw new RuntimeException(exception); + } + } + +} \ No newline at end of file diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/plugin/DoubleJumpPlugin.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/plugin/DoubleJumpPlugin.java new file mode 100644 index 0000000..4aa5c86 --- /dev/null +++ b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/plugin/DoubleJumpPlugin.java @@ -0,0 +1,21 @@ +package com.github.imdmk.doublejump.plugin; + +import org.bukkit.plugin.java.JavaPlugin; + +public final class DoubleJumpPlugin extends JavaPlugin { + + private DoubleJumpCoreWrapper wrapper; + + @Override + public void onEnable() { + wrapper = DoubleJumpCoreWrapper.create(getClass().getClassLoader()); + wrapper.enable(this); + } + + @Override + public void onDisable() { + if (wrapper != null) { + wrapper.disable(); + } + } +} \ No newline at end of file diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/util/ComponentUtil.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/util/ComponentUtil.java deleted file mode 100644 index 0bd57ee..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/util/ComponentUtil.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.github.imdmk.doublejump.util; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextDecoration; -import net.kyori.adventure.text.minimessage.MiniMessage; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public final class ComponentUtil { - - private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage(); - - private ComponentUtil() { - throw new UnsupportedOperationException("This is utility class."); - } - - public static Component notItalic(String text) { - return MINI_MESSAGE.deserialize(text) - .decoration(TextDecoration.ITALIC, false); - } - - public static List notItalic(String... text) { - return Arrays.stream(text).map(ComponentUtil::notItalic).toList(); - } - - public static List notItalic(List texts) { - return texts.stream().map(ComponentUtil::notItalic).toList(); - } - - public static String serialize(Component component) { - return MINI_MESSAGE.serialize(component); - } - - public static Component text(String text) { - return MINI_MESSAGE.deserialize(text); - } - - public static List text(String... texts) { - List components = new ArrayList<>(); - for (String text : texts) { - components.add(MINI_MESSAGE.deserialize(text)); - } - - return components; - } - - public static List text(List strings) { - return strings.stream() - .map(ComponentUtil::text) - .toList(); - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/util/DurationUtil.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/util/DurationUtil.java deleted file mode 100644 index 88c72f7..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/util/DurationUtil.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.github.imdmk.doublejump.util; - -import dev.rollczi.litecommands.time.DurationParser; -import dev.rollczi.litecommands.time.TemporalAmountParser; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; - -/** - * Utility class for parsing and formatting {@link Duration} values. - */ -public final class DurationUtil { - - public static final TemporalAmountParser DATE_TIME_PARSER = new DurationParser() - .withUnit("s", ChronoUnit.SECONDS) - .withUnit("m", ChronoUnit.MINUTES) - .withUnit("h", ChronoUnit.HOURS) - .withUnit("d", ChronoUnit.DAYS) - .withUnit("w", ChronoUnit.WEEKS) - .withUnit("mo", ChronoUnit.MONTHS) - .withUnit("y", ChronoUnit.YEARS); - - private static final long MILLIS_PER_TICK = 50L; - private static final long ONE_SECOND_IN_MILLIS = 1000L; - - private DurationUtil() { - throw new UnsupportedOperationException("This is a utility class and cannot be instantiated."); - } - - /** - * Formats the provided {@link Duration} into a human-readable string. - * Returns {@code "<1s"} for null, zero, or negative durations. - * - * @param duration the duration to format - * @return a formatted string or {@code "<1s"} - */ - public static @NotNull String format(@NotNull Duration duration) { - if (!isValid(duration)) { - return "0s"; - } - - long millis = duration.toMillis(); - if (millis < ONE_SECOND_IN_MILLIS) { - return millis + "ms"; - } - - return DATE_TIME_PARSER.format(duration); - } - - /** - * Checks whether the given {@link Duration} is valid (non-null, non-zero, positive). - * - * @param duration the duration to check - * @return {@code true} if valid, otherwise {@code false} - */ - public static boolean isValid(@Nullable Duration duration) { - return duration != null && !duration.isZero() && !duration.isNegative(); - } - - /** - * Converts a {@link Duration} to Minecraft ticks (1 tick = 50ms). - * - * @param duration the duration to convert - * @return number of ticks - */ - public static long toTicks(@NotNull Duration duration) { - return isValid(duration) ? duration.toMillis() / MILLIS_PER_TICK : 0L; - } - - /** - * Converts ticks to {@link Duration}. - * 1 tick = 50 milliseconds. - * - * @param ticks the number of ticks - * @return a {@link Duration} representation - */ - public static @NotNull Duration fromTicks(long ticks) { - return ticks > 0 ? Duration.ofMillis(ticks * MILLIS_PER_TICK) : Duration.ZERO; - } -} diff --git a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/util/GameModeUtil.java b/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/util/GameModeUtil.java deleted file mode 100644 index 3ce73bf..0000000 --- a/doublejump-plugin/src/main/java/com/github/imdmk/doublejump/util/GameModeUtil.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.imdmk.doublejump.util; - -import org.bukkit.GameMode; -import org.jetbrains.annotations.NotNull; - -import java.util.EnumSet; -import java.util.Set; - -/** - * Utility class for {@link GameMode}-related operations. - *

- * This class provides helper methods to work with {@link GameMode} enums, - * specifically to identify game modes that allow flying. - *

- * - *

This class is not meant to be instantiated.

- */ -public final class GameModeUtil { - - private static final Set FLYING_GAME_MODES = EnumSet.of(GameMode.SPECTATOR, GameMode.CREATIVE); - - private GameModeUtil() { - throw new UnsupportedOperationException("This is utility class."); - } - - /** - * Checks if the given {@link GameMode} allows flying. - * - * @param gameMode the game mode to check; must not be null - * @return {@code true} if the game mode allows flying (e.g., CREATIVE or SPECTATOR), - * {@code false} otherwise - */ - public static boolean isFlyingGameMode(@NotNull GameMode gameMode) { - return FLYING_GAME_MODES.contains(gameMode); - } -} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..feb6490 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties + +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true +org.gradle.configuration-cache.parallel=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2733ed5..bc79d44 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 4fce4ec..158abfa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,4 @@ rootProject.name = "DoubleJump" -include(":doublejump-api") -include(":doublejump-plugin") +include("doublejump-plugin") +include("doublejump-core") \ No newline at end of file