diff --git a/.editorconfig b/.editorconfig index 9e8a141..c013838 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,22 +1,31 @@ +root = true + [*] charset = utf-8 end_of_line = lf insert_final_newline = true indent_style = space indent_size = 4 +ij_any_block_comment_add_space = false +ij_any_block_comment_at_first_column = false +ij_any_line_comment_at_first_column = false +ij_any_line_comment_add_space = true [*.bat] end_of_line = crlf -[{*.yaml,*.yml}] +[*.yml] indent_size = 2 -ij_yaml_align_values_properties = do_not_align -ij_yaml_autoinsert_sequence_marker = true -ij_yaml_block_mapping_on_new_line = false -ij_yaml_indent_sequence_value = true -ij_yaml_keep_indents_on_empty_lines = false -ij_yaml_keep_line_breaks = true -ij_yaml_sequence_on_new_line = false -ij_yaml_space_before_colon = false -ij_yaml_spaces_within_braces = true -ij_yaml_spaces_within_brackets = true + +[*.java] +ij_continuation_indent_size = 4 +ij_java_class_count_to_use_import_on_demand = 999999 +ij_java_insert_inner_class_imports = false +ij_java_names_count_to_use_import_on_demand = 999999 +ij_java_imports_layout = *, |, $* +ij_java_generate_final_locals = true +ij_java_generate_final_parameters = true +ij_java_method_parameters_new_line_after_left_paren = true +ij_java_method_parameters_right_paren_on_new_line = true +ij_java_use_fq_class_names = false +ij_java_class_names_in_javadoc = 1 \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 18366f3..6b47b94 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to LobbyHeads -:Wave: Hello there! +🖐 Hello there! We wholeheartedly welcome all contributions to LobbyHeads and are excited to know you are considering making one. To ensure a robust and ideal solution, we recommend going through this guide. diff --git a/.whitesource b/.whitesource deleted file mode 100644 index 9c7ae90..0000000 --- a/.whitesource +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scanSettings": { - "baseBranches": [] - }, - "checkRunSettings": { - "vulnerableCheckRunConclusionLevel": "failure", - "displayMode": "diff", - "useMendCheckNames": true - }, - "issueSettings": { - "minSeverityLevel": "LOW", - "issueType": "DEPENDENCY" - } -} \ No newline at end of file diff --git a/LICENSE b/LICENSE index b6b555f..9666c00 100644 --- a/LICENSE +++ b/LICENSE @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Copyright (C) 2023 EternalCodeteam + Copyright (C) 2025 EternalCodeteam This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. diff --git a/README.md b/README.md index b909194..fcd0151 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,13 @@ ![LobbyHeads](/assets/lobbyheads-banner.png) -[![Supported by Paper](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/supported/paper_vector.svg)](https://papermc.io) -[![Supported by Spigot](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/supported/spigot_vector.svg)](https://spigotmc.org) +[![Available on SpigotMC](https://raw.githubusercontent.com/vLuckyyy/badges/main/available-on-spigotmc.svg)](https://www.spigotmc.org/resources/lobbyheads-%E2%9C%A8-decorative-heads-for-your-server-hub.113065/) +[![Available on Modrinth](https://raw.githubusercontent.com/vLuckyyy/badges/main/avaiable-on-modrinth.svg)](https://modrinth.com/plugin/LobbyHeads) +[![Available on Hangar](https://raw.githubusercontent.com/vLuckyyy/badges/main/avaiable-on-hangar.svg)](https://hangar.papermc.io/EternalCodeTeam/LobbyHeads) -[![Patreon](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/donate/patreon-plural_vector.svg)](https://www.patreon.com/eternalcode) -[![Official Website](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/documentation/website_vector.svg)](https://eternalcode.pl/) -[![Join Our Discord](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/social/discord-plural_vector.svg)](https://discord.gg/FQ7jmGBd6c) - -[![Built with Gradle](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/built-with/gradle_vector.svg)](https://gradle.org/) -[![Built with Java](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/built-with/java17_vector.svg)](https://www.java.com/) +[![Chat on Discord](https://raw.githubusercontent.com/vLuckyyy/badges/main//chat-with-us-on-discord.svg)](https://discord.com/invite/FQ7jmGBd6c) +[![Read the Docs](https://raw.githubusercontent.com/vLuckyyy/badges/main/read-the-documentation.svg)](https://docs.eternalcode.pl/eternalcore/introduction) +[![Available on BStats](https://raw.githubusercontent.com/vLuckyyy/badges/main/available-on-bstats.svg)](https://bstats.org/plugin/bukkit/LobbyHeads/20048) ## About LobbyHeads @@ -24,11 +22,11 @@ If you find any bugs, issues or inconsistencies, kindly report them [here](https ## Plugin Preview ### How head work? (You can use this example for **premium** ranks on your server!) -![gif](/assets/head%20work%2020fps.gif) +![gif](https://github.com/EternalCodeTeam/LobbyHeads/blob/master/assets/head%20work%2020fps.gif?raw=true) ### How to set up heads? (`/heads add` comand) -![gif](assets/head%20setup%2020fps.gif) +![gif](https://github.com/EternalCodeTeam/LobbyHeads/blob/master/assets/head%20setup%2020fps.gif?raw=true) ## Permission Assignments @@ -59,7 +57,7 @@ The output file `LobbyHeads-.jar` will be located in the `build/libs` d ### Contributions -We wholeheartedly welcome your contributions to LobbyHeads! Please refer to our [contribution guidelines](.github/CONTRIBUTING.md) for detailed procedures on how you can contribute, and our [code of conduct](./.github/CODE_OF_CONDUCT.md) to ensure a harmonious and welcoming community. +We wholeheartedly welcome your contributions to LobbyHeads! Please refer to our [contribution guidelines](https://github.com/EternalCodeTeam/LobbyHeads/blob/master/.github/CONTRIBUTING.md) for detailed procedures on how you can contribute, and our [code of conduct](./.github/CODE_OF_CONDUCT.md) to ensure a harmonious and welcoming community. ### Issue Reporting diff --git a/buildSrc/src/main/kotlin/lobbyheads-java-17.gradle.kts b/buildSrc/src/main/kotlin/lobbyheads-java-17.gradle.kts deleted file mode 100644 index b42d870..0000000 --- a/buildSrc/src/main/kotlin/lobbyheads-java-17.gradle.kts +++ /dev/null @@ -1,8 +0,0 @@ -plugins { - id("java-library") -} - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} diff --git a/buildSrc/src/main/kotlin/lobbyheads-java-21.gradle.kts b/buildSrc/src/main/kotlin/lobbyheads-java-21.gradle.kts new file mode 100644 index 0000000..afb9183 --- /dev/null +++ b/buildSrc/src/main/kotlin/lobbyheads-java-21.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("java-library") +} + +group = "com.eternalcode" +version = "1.0.4-SNAPSHOT" + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) +} + +tasks.compileJava { + options.compilerArgs = listOf("-Xlint:deprecation", "-parameters") + options.encoding = "UTF-8" + options.release = 21 +} diff --git a/buildSrc/src/main/kotlin/lobbyheads-java-unit-test.gradle.kts b/buildSrc/src/main/kotlin/lobbyheads-java-unit-test.gradle.kts deleted file mode 100644 index 0c960a6..0000000 --- a/buildSrc/src/main/kotlin/lobbyheads-java-unit-test.gradle.kts +++ /dev/null @@ -1,19 +0,0 @@ -plugins { - `java-library` -} - -dependencies { - testImplementation("org.codehaus.groovy:groovy-all:3.0.23") - testImplementation(platform("org.junit:junit-bom:5.11.3")) - testImplementation("org.junit.jupiter:junit-jupiter") - testImplementation("org.mockito:mockito-core:5.14.2") -} - -tasks.getByName("test") { - useJUnitPlatform() -} - -sourceSets.test { - java.setSrcDirs(listOf("test")) - resources.setSrcDirs(emptyList()) -} diff --git a/buildSrc/src/main/kotlin/lobbyheads-repositories.gradle.kts b/buildSrc/src/main/kotlin/lobbyheads-repositories.gradle.kts index e50ea63..839db69 100644 --- a/buildSrc/src/main/kotlin/lobbyheads-repositories.gradle.kts +++ b/buildSrc/src/main/kotlin/lobbyheads-repositories.gradle.kts @@ -5,11 +5,11 @@ plugins { repositories { mavenCentral() - maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") } + maven { url = uri("https://repo.papermc.io/repository/maven-public/") } maven { url = uri("https://jitpack.io") } maven { url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/") } maven { url = uri("https://repo.eternalcode.pl/releases") } maven { url = uri("https://storehouse.okaeri.eu/repository/maven-public/") } - maven { url = uri("https://repository.minecodes.pl/releases") } maven { url = uri("https://libraries.minecraft.net/") } + maven { url = uri("https://repo.fancyplugins.de/releases") } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index df97d72..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/lobbyheads-api/build.gradle.kts b/lobbyheads-api/build.gradle.kts index 2922763..000ba76 100644 --- a/lobbyheads-api/build.gradle.kts +++ b/lobbyheads-api/build.gradle.kts @@ -1,11 +1,11 @@ plugins { - `lobbyheads-java-17` + `lobbyheads-java-21` `lobbyheads-repositories` `lobbyheads-checkstyle` } dependencies { - api("org.spigotmc:spigot-api:1.20.2-R0.1-SNAPSHOT") + compileOnlyApi("io.papermc.paper:paper-api:1.21.7-R0.1-SNAPSHOT") api("org.jetbrains:annotations:24.1.0") } diff --git a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/LobbyHeadsApi.java b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/LobbyHeadsApi.java index 7f795f4..21d4c62 100644 --- a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/LobbyHeadsApi.java +++ b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/LobbyHeadsApi.java @@ -1,8 +1,12 @@ package com.eternalcode.lobbyheads; import com.eternalcode.lobbyheads.head.HeadManager; +import org.jetbrains.annotations.NotNull; +/** + * Root entry point for interacting with the LobbyHeads API. + */ public interface LobbyHeadsApi { - HeadManager getHeadManager(); + @NotNull HeadManager getHeadManager(); } diff --git a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/LobbyHeadsApiProvider.java b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/LobbyHeadsApiProvider.java index 8b3dfbb..abcc38e 100644 --- a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/LobbyHeadsApiProvider.java +++ b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/LobbyHeadsApiProvider.java @@ -1,30 +1,44 @@ package com.eternalcode.lobbyheads; -public class LobbyHeadsApiProvider { +import org.jetbrains.annotations.NotNull; - private static LobbyHeadsApi lobbyHeadsApi; +/** + * Provides global access to the LobbyHeads API. + */ +public final class LobbyHeadsApiProvider { - public static LobbyHeadsApi provide() { - if (lobbyHeadsApi == null) { - throw new IllegalStateException("LobbyHeadsApi has not been initialized yet!"); - } + private static volatile LobbyHeadsApi api; - return lobbyHeadsApi; + private LobbyHeadsApiProvider() { + throw new UnsupportedOperationException("This is a utility class."); } - static void initialize(LobbyHeadsApi lobbyHeadsApi) { - if (LobbyHeadsApiProvider.lobbyHeadsApi != null) { - throw new IllegalStateException("LobbyHeadsApi has already been initialized!"); + /** + * @return The initialized LobbyHeadsApi instance. + * @throws IllegalStateException if not initialized. + */ + public static @NotNull LobbyHeadsApi get() { + LobbyHeadsApi instance = api; + if (instance == null) { + throw new IllegalStateException("LobbyHeadsApi is not initialized yet."); } + return instance; + } - LobbyHeadsApiProvider.lobbyHeadsApi = lobbyHeadsApi; + /** + * Internal use only. + */ + static void initialize(@NotNull LobbyHeadsApi lobbyHeadsApi) { + if (api != null) { + throw new IllegalStateException("LobbyHeadsApi is already initialized."); + } + api = lobbyHeadsApi; } static void deinitialize() { - if (lobbyHeadsApi == null) { - throw new IllegalStateException("LobbyHeadsApi has not been initialized yet!"); + if (api == null) { + throw new IllegalStateException("LobbyHeadsApi is not initialized."); } - - lobbyHeadsApi = null; + api = null; } } diff --git a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/Head.java b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/Head.java index 7e73768..ab4bc44 100644 --- a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/Head.java +++ b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/Head.java @@ -1,40 +1,54 @@ package com.eternalcode.lobbyheads.head; -import com.eternalcode.lobbyheads.position.Position; -import com.eternalcode.lobbyheads.position.PositionAdapter; +import java.io.Serializable; import java.util.Objects; import java.util.UUID; import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; -public class Head { +public class Head implements Serializable { + + private final String world; + private final double x, y, z; + private final float yaw, pitch; - private final Position position; private String playerName; private UUID playerUUID; - public Head(Position position, String playerName, UUID playerUUID) { - this.position = position; + public Head() { + this.world = null; + this.x = this.y = this.z = 0; + this.yaw = this.pitch = 0; + } + + public Head(@NotNull Location location, String playerName, UUID playerUUID) { + this.world = location.getWorld().getName(); + this.x = location.getX(); + this.y = location.getY(); + this.z = location.getZ(); + this.yaw = location.getYaw(); + this.pitch = location.getPitch(); this.playerName = playerName; this.playerUUID = playerUUID; } - public Position getPosition() { - return this.position; + public @NotNull Location getLocation() { + return new Location( + org.bukkit.Bukkit.getWorld(this.world), + this.x, this.y, this.z, + this.yaw, this.pitch + ); } - public Location getLocation() { - return PositionAdapter.convert(this.position); + public @NotNull UUID getPlayerUuid() { + return this.playerUUID; } - public String getPlayerName() { + public @NotNull String getPlayerName() { return this.playerName; } - public UUID getPlayerUUID() { - return this.playerUUID; - } - - public void replacePlayer(String newPlayerName, UUID newPlayerUUID) { + public void replacePlayer(@NotNull String newPlayerName, @NotNull UUID newPlayerUUID) { this.playerName = newPlayerName; this.playerUUID = newPlayerUUID; } @@ -44,17 +58,15 @@ public boolean equals(Object object) { if (this == object) { return true; } - - if (object == null || getClass() != object.getClass()) { + if (!(object instanceof Head other)) { return false; } - - Head head = (Head) object; - return Objects.equals(this.position, head.position); + return this.world.equals(other.world) && + this.x == other.x && this.y == other.y && this.z == other.z; } @Override public int hashCode() { - return Objects.hash(this.position); + return Objects.hash(this.world, this.x, this.y, this.z); } } diff --git a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/HeadManager.java b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/HeadManager.java index 8a2b564..578b304 100644 --- a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/HeadManager.java +++ b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/HeadManager.java @@ -3,16 +3,36 @@ import java.util.Collection; import org.bukkit.Location; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +/** + * Manages all heads in the lobby. + */ public interface HeadManager { - void addHead(Player player, Location location); + /** + * Adds a head for the given player at the specified location. + */ + void addHead(@NotNull Player player, @NotNull Location location); - void removeHead(Location location); + /** + * Removes a head from the specified location. + */ + void removeHead(@NotNull Location location); - void updateHead(Player player, Location location); + /** + * Updates an existing head's data (e.g. skin) for the given player. + */ + void updateHead(@NotNull Player player, @NotNull Location location); - Head getHead(Location location); + /** + * Returns the head at the specified location, or null if none exists. + */ + @Nullable Head getHead(@NotNull Location location); - Collection getHeads(); + /** + * Returns all known heads. + */ + @NotNull Collection getHeads(); } diff --git a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadCreateEvent.java b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadCreateEvent.java index 0eb64a8..8692ce7 100644 --- a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadCreateEvent.java +++ b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadCreateEvent.java @@ -2,39 +2,26 @@ import java.util.UUID; import org.bukkit.Location; -import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; /** - * This event is called when a head is created. - **/ -public class HeadCreateEvent extends Event { + * Called when a head is created in the world. + */ +public class HeadCreateEvent extends HeadEvent { private static final HandlerList HANDLER_LIST = new HandlerList(); - private final UUID playerUniqueId; - private final Location location; - - public HeadCreateEvent(UUID playerUniqueId, Location location) { - this.playerUniqueId = playerUniqueId; - this.location = location; + public HeadCreateEvent(@NotNull UUID playerUuid, @NotNull Location location) { + super(playerUuid, location); } - public static HandlerList getHandlerList() { + @Override + public @NotNull HandlerList getHandlers() { return HANDLER_LIST; } - public UUID getPlayerUniqueId() { - return this.playerUniqueId; - } - - public Location getLocation() { - return this.location; - } - - @Override - public @NotNull HandlerList getHandlers() { + public static @NotNull HandlerList getHandlerList() { return HANDLER_LIST; } } diff --git a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadEvent.java b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadEvent.java new file mode 100644 index 0000000..51bfec6 --- /dev/null +++ b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadEvent.java @@ -0,0 +1,35 @@ +package com.eternalcode.lobbyheads.head.event; + +import java.util.UUID; +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Base class for all head-related events. + */ +public abstract class HeadEvent extends Event { + + private final UUID playerUuid; + private final Location location; + + protected HeadEvent(@NotNull UUID playerUuid, @NotNull Location location) { + this.playerUuid = playerUuid; + this.location = location; + } + + /** + * @return The UUID of the player associated with this head. + */ + public @NotNull UUID getPlayerUuid() { + return playerUuid; + } + + /** + * @return The location of the head. + */ + public @NotNull Location getLocation() { + return location; + } +} diff --git a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadRemoveEvent.java b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadRemoveEvent.java index e412e90..54b07e7 100644 --- a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadRemoveEvent.java +++ b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadRemoveEvent.java @@ -2,45 +2,29 @@ import java.util.UUID; import org.bukkit.Location; -import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; /** - * This event is called when a head is removed from the database. - **/ -public class HeadRemoveEvent extends Event { + * Called when a head is removed from the world or database. + * + *

Note: This refers to the UUID of the player who owned the head, + * not necessarily the player who removed it.

+ */ +public class HeadRemoveEvent extends HeadEvent { private static final HandlerList HANDLER_LIST = new HandlerList(); - private final UUID playerUniqueId; - private final Location location; - - public HeadRemoveEvent(UUID playerUniqueId, Location location) { - this.playerUniqueId = playerUniqueId; - this.location = location; + public HeadRemoveEvent(@NotNull UUID playerUuid, @NotNull Location location) { + super(playerUuid, location); } - public static HandlerList getHandlerList() { + @Override + public @NotNull HandlerList getHandlers() { return HANDLER_LIST; } - /** - * Returns the unique identifier of the player who owns the removed head. - * Please note, this is not the unique identifier of the player who removed the head. - * - * @return The unique identifier of the player owning the removed head. - */ - public UUID getPlayerUniqueId() { - return this.playerUniqueId; - } - - public Location getLocation() { - return this.location; - } - - @Override - public @NotNull HandlerList getHandlers() { + public static @NotNull HandlerList getHandlerList() { return HANDLER_LIST; } } diff --git a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadUpdateEvent.java b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadUpdateEvent.java index e50784d..c7f0f69 100644 --- a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadUpdateEvent.java +++ b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/head/event/HeadUpdateEvent.java @@ -2,39 +2,26 @@ import java.util.UUID; import org.bukkit.Location; -import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; /** - * This event is called when a head is updated. - **/ -public class HeadUpdateEvent extends Event { + * Called when a head is updated (e.g., name or skin is refreshed). + */ +public class HeadUpdateEvent extends HeadEvent { private static final HandlerList HANDLER_LIST = new HandlerList(); - private final UUID playerUniqueId; - private final Location location; - - public HeadUpdateEvent(UUID playerUniqueId, Location location) { - this.playerUniqueId = playerUniqueId; - this.location = location; + public HeadUpdateEvent(@NotNull UUID playerUuid, @NotNull Location location) { + super(playerUuid, location); } - public static HandlerList getHandlerList() { + @Override + public @NotNull HandlerList getHandlers() { return HANDLER_LIST; } - public UUID getPlayerUniqueId() { - return this.playerUniqueId; - } - - public Location getLocation() { - return this.location; - } - - @Override - public @NotNull HandlerList getHandlers() { + public static @NotNull HandlerList getHandlerList() { return HANDLER_LIST; } } diff --git a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/position/Position.java b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/position/Position.java deleted file mode 100644 index 0b5d078..0000000 --- a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/position/Position.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.eternalcode.lobbyheads.position; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Disclaimer - Bukkit {@link org.bukkit.Location} storage may cause a memory leak, because it is a wrapper for - * coordinates and {@link org.bukkit.World} reference. If you need to store location use {@link Position} and - * {@link PositionAdapter}. - */ -public record Position(double x, double y, double z, String world) { - - private static final Pattern PARSE_FORMAT = Pattern.compile("Position\\{x=(?-?[\\d.]+), y=(?-?[\\d.]+), z=(?-?[\\d.]+), world='(?.+)'}"); - - public static Position parse(String parse) { - Matcher matcher = PARSE_FORMAT.matcher(parse); - - if (!matcher.find()) { - throw new IllegalArgumentException("Invalid position format: " + parse); - } - - return new Position( - Double.parseDouble(matcher.group("x")), - Double.parseDouble(matcher.group("y")), - Double.parseDouble(matcher.group("z")), - matcher.group("world") - ); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; - } - - Position position = (Position) o; - - return Double.compare(position.x, this.x) == 0 - && Double.compare(position.y, this.y) == 0 - && Double.compare(position.z, this.z) == 0 - && this.world.equals(position.world); - } - - @Override - public String toString() { - return "Position{" + - "x=" + this.x + - ", y=" + this.y + - ", z=" + this.z + - ", world='" + this.world + '\'' + - '}'; - } -} diff --git a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/position/PositionAdapter.java b/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/position/PositionAdapter.java deleted file mode 100644 index 256f94e..0000000 --- a/lobbyheads-api/src/main/java/com/eternalcode/lobbyheads/position/PositionAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.eternalcode.lobbyheads.position; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; - -public final class PositionAdapter { - - private PositionAdapter() {} - - public static Position convert(Location location) { - if (location.getWorld() == null) { - throw new IllegalStateException("World is not defined"); - } - - return new Position(location.getX(), location.getY(), location.getZ(), location.getWorld().getName()); - } - - public static Location convert(Position position) { - World world = Bukkit.getWorld(position.world()); - - if (world == null) { - throw new IllegalStateException("World is not defined"); - } - - return new Location(world, position.x(), position.y(), position.z()); - } -} diff --git a/lobbyheads-core/build.gradle.kts b/lobbyheads-core/build.gradle.kts index 5136201..a39a6ae 100644 --- a/lobbyheads-core/build.gradle.kts +++ b/lobbyheads-core/build.gradle.kts @@ -1,42 +1,44 @@ +import net.minecrell.pluginyml.bukkit.BukkitPluginDescription +import net.minecrell.pluginyml.paper.PaperPluginDescription + plugins { - `lobbyheads-java-17` + `lobbyheads-java-21` `lobbyheads-repositories` - `lobbyheads-java-unit-test` `lobbyheads-checkstyle` - id("net.minecrell.plugin-yml.bukkit") version "0.6.0" + id("de.eldoria.plugin-yml.bukkit") version "0.7.1" id("com.gradleup.shadow") version "8.3.5" id("xyz.jpenilla.run-paper") version "2.3.1" } + +val mainPackage = "com.eternalcode.lobbyheads" +val projectPrefix = "LobbyHeads" + dependencies { // okaeri configs - val okaeriConfigsVersion = "5.0.5" + val okaeriConfigsVersion = "5.0.9" implementation("eu.okaeri:okaeri-configs-yaml-snakeyaml:${okaeriConfigsVersion}") implementation("eu.okaeri:okaeri-configs-serdes-commons:${okaeriConfigsVersion}") // api api(project(":lobbyheads-api")) - // a cool library, kyori - implementation("net.kyori:adventure-platform-bukkit:4.3.4") - implementation("net.kyori:adventure-text-minimessage:4.17.0") - // Rollczi's skullapi implementation("dev.rollczi:liteskullapi:1.3.0") // eternalcode commons - implementation("com.eternalcode:eternalcode-commons-adventure:1.1.4") + val eternalCodeCommonsVersion = "1.1.7" + implementation("com.eternalcode:eternalcode-commons-adventure:$eternalCodeCommonsVersion") + implementation("com.eternalcode:eternalcode-commons-shared:$eternalCodeCommonsVersion") + implementation("com.eternalcode:eternalcode-commons-bukkit:$eternalCodeCommonsVersion") - // spigot-api - compileOnly("org.spigotmc:spigot-api:1.20.2-R0.1-SNAPSHOT") - testImplementation("org.spigotmc:spigot-api:1.20.2-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:1.21.7-R0.1-SNAPSHOT") - // mojang's authlib - compileOnly("com.mojang:authlib:5.0.47") + implementation("com.github.cryptomorin:XSeries:12.1.0") - // HoloEasy based on top of the protocolib - implementation("com.github.unldenis:holoeasy:3.0.1") + compileOnly("de.oliver:FancyHolograms:2.4.0") + compileOnly("com.github.decentsoftware-eu:decentholograms:2.9.5") // PlaceholderAPI, if anyone wants to parse placeholders in the head's name compileOnly("me.clip:placeholderapi:2.11.6") @@ -46,27 +48,20 @@ dependencies { // GitCheck implementation("com.eternalcode:gitcheck:1.0.0") - - // tests setup - testImplementation("org.codehaus.groovy:groovy-all:3.0.23") - testImplementation(platform("org.junit:junit-bom:5.11.3")) - testImplementation("org.junit.jupiter:junit-jupiter") - testImplementation("org.mockito:mockito-core:5.14.2") -} - -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } bukkit { - main = "com.eternalcode.lobbyheads.HeadsPlugin" - apiVersion = "1.13" - prefix = "LobbyHeads" - author = "EternalCodeTeam" - name = "LobbyHeads" - website = "www.eternalcode.pl" + name = projectPrefix version = "${project.version}" - depend = listOf("PlaceholderAPI", "ProtocolLib") + author = "EternalCoreTeam" + website = "https://eternalcode.pl/" + prefix = projectPrefix + + main = "$mainPackage.HeadsPlugin" + apiVersion = "1.13" + + depend = listOf("PlaceholderAPI") + softDepend = listOf("DecentHolograms", "FancyHolograms") commands { register("heads") { @@ -75,21 +70,10 @@ bukkit { usage = "add|remove|reload" } } - -} - -tasks.compileJava { - options.compilerArgs = listOf("-Xlint:deprecation", "-parameters") - options.encoding = "UTF-8" - options.release = 17 -} - -tasks.test { - useJUnitPlatform() } tasks.runServer { - minecraftVersion("1.20.1") + minecraftVersion("1.21.7") downloadPlugins { hangar("ProtocolLib", "5.1.0") @@ -106,9 +90,6 @@ tasks.shadowJar { "META-INF/**", ) - dependsOn("checkstyleMain") - dependsOn("checkstyleTest") - dependsOn("test") mergeServiceFiles() @@ -118,8 +99,6 @@ tasks.shadowJar { "eu.okaeri", "panda", "org.yaml", - "net.kyori", - "com.unldenis", "org.bstats", "com.eternalcode.gitcheck", "com.eternalcode.commons", diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/HeadsPlugin.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/HeadsPlugin.java index 186d186..ac4fa24 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/HeadsPlugin.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/HeadsPlugin.java @@ -15,6 +15,8 @@ import com.eternalcode.lobbyheads.head.command.HeadCommand; import com.eternalcode.lobbyheads.head.hologram.HologramController; import com.eternalcode.lobbyheads.head.hologram.HologramService; +import com.eternalcode.lobbyheads.head.hologram.provider.HologramProvider; +import com.eternalcode.lobbyheads.head.hologram.provider.HologramProviderPicker; import com.eternalcode.lobbyheads.head.particle.ParticleController; import com.eternalcode.lobbyheads.head.sound.SoundController; import com.eternalcode.lobbyheads.notification.NotificationAnnouncer; @@ -23,28 +25,28 @@ import com.eternalcode.lobbyheads.updater.UpdaterService; import dev.rollczi.liteskullapi.LiteSkullFactory; import dev.rollczi.liteskullapi.SkullAPI; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import java.io.File; import java.time.Duration; import java.util.stream.Stream; -import net.kyori.adventure.platform.AudienceProvider; -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.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; public class HeadsPlugin extends JavaPlugin implements LobbyHeadsApi { private SkullAPI skullAPI; - private AudienceProvider audienceProvider; - private HeadManagerImpl headManagerImpl; + private HeadManager headManager; @Override public void onEnable() { Server server = this.getServer(); ConfigurationService configurationService = new ConfigurationService(); - HeadsConfiguration config = configurationService.create(HeadsConfiguration.class, new File(this.getDataFolder(), "config.yml")); + HeadsConfiguration config = + configurationService.create(HeadsConfiguration.class, new File(this.getDataFolder(), "config.yml")); EventCaller eventCaller = new EventCaller(server); @@ -53,24 +55,33 @@ public void onEnable() { .bukkitScheduler(this) .build(); - this.audienceProvider = BukkitAudiences.create(this); MiniMessage miniMessage = MiniMessage.builder() .postProcessor(new AdventureLegacyColorPostProcessor()) .preProcessor(new AdventureLegacyColorPreProcessor()) .build(); - NotificationAnnouncer notificationAnnouncer = new NotificationAnnouncer(this.audienceProvider, miniMessage); + NotificationAnnouncer notificationAnnouncer = new NotificationAnnouncer(miniMessage); UpdaterService updaterService = new UpdaterService(this.getDescription()); HeadRepository headRepository = new HeadRepositoryImpl(config, configurationService); - this.headManagerImpl = new HeadManagerImpl(eventCaller, headRepository); - this.headManagerImpl.loadHeads(); + HeadManagerImpl headManagerImpl = new HeadManagerImpl(eventCaller, headRepository); + headManagerImpl.loadHeads(); + this.headManager = headManagerImpl; - HologramService hologramService = new HologramService(this, config, miniMessage, server, this.headManagerImpl); + HologramProviderPicker hologramProviderPicker = new HologramProviderPicker(); + HologramProvider hologramProvider = hologramProviderPicker.pickProvider(this); + + HologramService hologramService = new HologramService( + config, + miniMessage, + server, + this.headManager, + hologramProvider + ); hologramService.loadHolograms(); - BlockService blockService = new BlockService(config, notificationAnnouncer, this.headManagerImpl); + BlockService blockService = new BlockService(config, notificationAnnouncer, this.headManager); ReloadService reloadService = new ReloadService() .register(configurationService) @@ -80,7 +91,7 @@ public void onEnable() { .setExecutor(new HeadCommand(config, notificationAnnouncer, blockService, reloadService)); Stream.of( - new HeadController(config, this.headManagerImpl, notificationAnnouncer), + new HeadController(config, this.headManager, notificationAnnouncer), // sub-controllers new SoundController(server, config), @@ -97,21 +108,13 @@ public void onEnable() { @Override public void onDisable() { - if (this.headManagerImpl != null) { - this.headManagerImpl.clearHeads(); - } - if (this.skullAPI != null) { this.skullAPI.shutdown(); } - - if (this.audienceProvider != null) { - this.audienceProvider.close(); - } } @Override - public HeadManager getHeadManager() { - return this.headManagerImpl; + public @NotNull HeadManager getHeadManager() { + return this.headManager; } } diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/ConfigurationService.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/ConfigurationService.java index d2cf0cc..948589c 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/ConfigurationService.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/ConfigurationService.java @@ -1,8 +1,10 @@ package com.eternalcode.lobbyheads.configuration; +import com.eternalcode.lobbyheads.configuration.migration.HeadsConfigMigration_2025_07_17; +import com.eternalcode.lobbyheads.configuration.migration.HeadsPositionMigration_2025_07_17; +import com.eternalcode.lobbyheads.configuration.serializer.SoundSerializer; import com.eternalcode.lobbyheads.reload.Reloadable; -import com.eternalcode.lobbyheads.configuration.serializer.HeadSerializer; -import com.eternalcode.lobbyheads.configuration.serializer.PositionSerializer; +import com.eternalcode.lobbyheads.configuration.serializer.PositionTransformer; import eu.okaeri.configs.ConfigManager; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.serdes.commons.SerdesCommons; @@ -29,12 +31,16 @@ public T create(Class config, File file) { configFile .withConfigurer(yamlConfigurer, new SerdesCommons()) - .withSerdesPack(registry -> registry.register(new HeadSerializer())) - .withSerdesPack(registry -> registry.register(new PositionSerializer())) + .withSerdesPack(registry -> registry.register(new PositionTransformer())) + .withSerdesPack(registry -> registry.register(new SoundSerializer())) .withBindFile(file) - .withRemoveOrphans(true) .saveDefaults() - .load(true); + .load(true) + .migrate( + new HeadsConfigMigration_2025_07_17(), + new HeadsPositionMigration_2025_07_17() + ); + this.configs.add(configFile); diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/implementation/HeadsConfiguration.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/implementation/HeadsConfiguration.java index c776a1d..d7870d4 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/implementation/HeadsConfiguration.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/implementation/HeadsConfiguration.java @@ -1,21 +1,17 @@ package com.eternalcode.lobbyheads.configuration.implementation; +import com.cryptomorin.xseries.XSound; import com.eternalcode.lobbyheads.delay.DelaySettings; import com.eternalcode.lobbyheads.head.Head; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; import eu.okaeri.configs.annotation.Header; -import eu.okaeri.configs.annotation.NameModifier; -import eu.okaeri.configs.annotation.NameStrategy; -import eu.okaeri.configs.annotation.Names; import org.bukkit.Particle; -import org.bukkit.Sound; import java.time.Duration; import java.util.ArrayList; import java.util.List; -@Names(strategy = NameStrategy.HYPHEN_CASE, modifier = NameModifier.TO_LOWER_CASE) @Header("# ") @Header("# LobbyHeads configuration file") @Header("# Permissions:") @@ -28,7 +24,7 @@ public class HeadsConfiguration extends OkaeriConfig implements DelaySettings { public Duration headReplacementDelay = Duration.ofSeconds(15); @Comment({ " ", "# Update check" }) - public boolean receivePluginUpdates = true; + public boolean checkUpdates = true; @Comment({ " ", "# Heads list, don't touch this!" }) public List heads = new ArrayList<>(); @@ -37,39 +33,39 @@ public class HeadsConfiguration extends OkaeriConfig implements DelaySettings { public Messages messages = new Messages(); @Comment({ " ", "# Head configuration, you can change head settings here" }) - public HeadSection headSection = new HeadSection(); + public HeadSettings headSettings = new HeadSettings(); @Override public Duration delay() { return this.headReplacementDelay; } - public static class HeadSection extends OkaeriConfig { + public static class HeadSettings extends OkaeriConfig { @Comment("# Format of the head, you can use PlaceholderAPI here") public String defaultHeadFormat = "{PLAYER}"; public String headFormat = "%luckperms_prefix% {PLAYER}"; @Comment({ " ", "# Sound when a player replaces head" }) public boolean soundEnabled = true; - public Sound sound = Sound.ENTITY_PLAYER_LEVELUP; + public XSound sound = XSound.ENTITY_PLAYER_LEVELUP; public int volume = 1; public int pitch = 1; @Comment("# Particle when a player replaces head") public boolean particleEnabled = true; - public Particle particle = Particle.VILLAGER_HAPPY; + public Particle particle = Particle.HEART; public int count = 10; } public static class Messages extends OkaeriConfig { @Comment("# Message when usage is invalid") - public String commandInvalidUsage = "Hmmm, this doesn't look like a proper usage. Try: /head "; + public String invalidCommand = "Hmmm, this doesn't look like a proper usage. Try: /head "; @Comment("# Reload configs") public String configurationReloaded = "LobbyHeads configuration reloaded!"; @Comment("# Message when a head already exists at the block intended for a new one") - public String headAlreadyExists = "Oops! It looks like this space is already occupied by another head."; + public String headExists = "Oops! It looks like this space is already occupied by another head."; @Comment("# Message when a head has been added successfully") public String headAdded = "Head added!"; @@ -81,18 +77,21 @@ public static class Messages extends OkaeriConfig { public String onlyForPlayers = "Hey! This command is only for real players!"; @Comment("# Message when a player already replaced a head") - public String playerAlreadyReplaceThisHead = "Hey! It looks like you've already swapped this head."; + public String alreadyReplaced = "Hey! It looks like you've already swapped this head."; @Comment("# Message when a player must wait to replace another head") - public String playerMustWaitToReplaceHead = "Hey! You must wait {duration} seconds for next head replace."; + public String replaceCooldown = "Hey! You must wait {duration} seconds for next head replace."; @Comment("# Message when a player is not looking at one") - public String playerNotLookingAtHead = "Hey! You need to be looking at a head to do this."; + public String notLookingAtHead = "Hey! You need to be looking at a head to do this."; @Comment("# Message when a player is not permitted to replace heads") - public String playerNotPermittedToReplaceHeads = "I'm sorry, but head swapping is only available for VIP members."; + public String noPermissionReplace = "I'm sorry, but head swapping is only available for VIP members."; @Comment("# Message when a player is not permitted to use an admin command") - public String playerNotPermittedToUseThisCommand = "You're not permitted to use this command!"; + public String noPermissionCommand = "You're not permitted to use this command!"; + + @Comment("# Message when a player is not looking at a block") + public String noBlockInSight = "Hey! You need to be looking at a block to do this."; } } diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/migration/HeadsConfigMigration_2025_07_17.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/migration/HeadsConfigMigration_2025_07_17.java new file mode 100644 index 0000000..e584136 --- /dev/null +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/migration/HeadsConfigMigration_2025_07_17.java @@ -0,0 +1,30 @@ +package com.eternalcode.lobbyheads.configuration.migration; + +import static eu.okaeri.configs.migrate.ConfigMigrationDsl.move; + +import eu.okaeri.configs.migrate.builtin.NamedMigration; + +public class HeadsConfigMigration_2025_07_17 extends NamedMigration { + + public HeadsConfigMigration_2025_07_17() { + super( + "Rename config keys to improved camelCase names", + // top‑level sections + move("headSection", "headSettings"), + + // message keys + move("messages.commandInvalidUsage", "messages.invalidCommand"), + move("messages.configurationReloaded", "messages.configReloaded"), + move("messages.headAlreadyExists", "messages.headExists"), + move("messages.headAdded", "messages.headAdded"), + move("messages.headRemoved", "messages.headRemoved"), + move("messages.onlyForPlayers", "messages.playersOnly"), + move("messages.playerAlreadyReplaceThisHead", "messages.alreadyReplaced"), + move("messages.playerMustWaitToReplaceHead", "messages.replaceCooldown"), + move("messages.playerNotLookingAtHead", "messages.notLookingAtHead"), + move("messages.playerNotPermittedToReplaceHeads", "messages.noPermissionReplace"), + move("messages.playerNotPermittedToUseThisCommand", "messages.noPermissionCommand"), + move("messages.noBlockInSight", "messages.noBlockInSightMessage") + ); + } +} diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/migration/HeadsPositionMigration_2025_07_17.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/migration/HeadsPositionMigration_2025_07_17.java new file mode 100644 index 0000000..b1bb828 --- /dev/null +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/migration/HeadsPositionMigration_2025_07_17.java @@ -0,0 +1,134 @@ +package com.eternalcode.lobbyheads.configuration.migration; + +import eu.okaeri.configs.migrate.ConfigMigration; +import eu.okaeri.configs.migrate.builtin.NamedMigration; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class HeadsPositionMigration_2025_07_17 extends NamedMigration { + + private static final Pattern POSITION_PATTERN = Pattern.compile( + "Position\\{x=([^,]+), y=([^,]+), z=([^,]+), world='([^']+)'\\}" + ); + + public HeadsPositionMigration_2025_07_17() { + super( + "Migrate heads position format from old string-based to new structured format", + migrateHeadsPosition() + ); + } + + private static ConfigMigration migrateHeadsPosition() { + return (config, view) -> { + if (!view.exists("heads")) { + return false; + } + + Object headsValue = view.get("heads"); + if (!(headsValue instanceof List)) { + return false; + } + + @SuppressWarnings("unchecked") + List> oldHeads = (List>) headsValue; + List> newHeads = new ArrayList<>(); + + boolean migrated = false; + + for (Map oldHead : oldHeads) { + Object positionValue = oldHead.get("position"); + Object playerValue = oldHead.get("player"); + Object uuidValue = oldHead.get("uuid"); + + if (positionValue instanceof String positionString) { + Matcher matcher = POSITION_PATTERN.matcher(positionString); + + if (matcher.matches()) { + Map newHead = new LinkedHashMap<>(); + + // Extract world from old position string + String world = matcher.group(4); + newHead.put("world", world); + + // Extract and parse coordinates + try { + double x = Double.parseDouble(matcher.group(1)); + double y = Double.parseDouble(matcher.group(2)); + double z = Double.parseDouble(matcher.group(3)); + + newHead.put("x", x); + newHead.put("y", y); + newHead.put("z", z); + } + catch (NumberFormatException e) { + // If parsing fails, use defaults + newHead.put("x", 0.0); + newHead.put("y", 0.0); + newHead.put("z", 0.0); + } + + // Set default yaw and pitch + newHead.put("yaw", 0.0F); + newHead.put("pitch", 0.0F); + + // Migrate player name and UUID + newHead.put("playerName", playerValue); + newHead.put("playerUUID", uuidValue); + + newHeads.add(newHead); + migrated = true; + } + else { + // If position string doesn't match pattern, create default entry + Map defaultHead = createDefaultHead(); + defaultHead.put("playerName", playerValue); + defaultHead.put("playerUUID", uuidValue); + newHeads.add(defaultHead); + migrated = true; + } + } + else { + // If position is not a string, it might already be in new format + // or it's corrupted - add as is or create default + if (oldHead.containsKey("world") && oldHead.containsKey("x") && + oldHead.containsKey("y") && oldHead.containsKey("z")) { + // Already in new format + newHeads.add(oldHead); + } + else { + // Create default entry + Map defaultHead = createDefaultHead(); + defaultHead.put("playerName", playerValue); + defaultHead.put("playerUUID", uuidValue); + newHeads.add(defaultHead); + migrated = true; + } + } + } + + if (migrated) { + view.set("heads", newHeads); + return true; + } + + return false; + }; + } + + private static Map createDefaultHead() { + Map defaultHead = new LinkedHashMap<>(); + defaultHead.put("world", null); + defaultHead.put("x", 0.0); + defaultHead.put("y", 0.0); + defaultHead.put("z", 0.0); + defaultHead.put("yaw", 0.0F); + defaultHead.put("pitch", 0.0F); + defaultHead.put("playerName", null); + defaultHead.put("playerUUID", null); + return defaultHead; + } +} diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/serializer/HeadSerializer.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/serializer/HeadSerializer.java deleted file mode 100644 index 7847879..0000000 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/serializer/HeadSerializer.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.eternalcode.lobbyheads.configuration.serializer; - -import com.eternalcode.lobbyheads.head.Head; -import com.eternalcode.lobbyheads.position.Position; -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 java.util.UUID; - -public class HeadSerializer implements ObjectSerializer { - - @Override - public boolean supports(Class type) { - return Head.class.isAssignableFrom(type); - } - - @Override - public void serialize(Head head, SerializationData data, GenericsDeclaration generics) { - data.add("position", head.getPosition()); - data.add("player", head.getPlayerName()); - data.add("uuid", head.getPlayerUUID().toString()); - } - - @Override - public Head deserialize(DeserializationData data, GenericsDeclaration generics) { - Position position = data.get("position", Position.class); - String playerName = data.get("player", String.class); - UUID playerUUID = data.get("uuid", UUID.class); - - return new Head(position, playerName, playerUUID); - } -} diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/serializer/PositionSerializer.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/serializer/PositionTransformer.java similarity index 85% rename from lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/serializer/PositionSerializer.java rename to lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/serializer/PositionTransformer.java index 94b4757..cd7b039 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/serializer/PositionSerializer.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/serializer/PositionTransformer.java @@ -1,12 +1,12 @@ package com.eternalcode.lobbyheads.configuration.serializer; -import com.eternalcode.lobbyheads.position.Position; +import com.eternalcode.commons.bukkit.position.Position; import eu.okaeri.configs.schema.GenericsDeclaration; import eu.okaeri.configs.serdes.DeserializationData; import eu.okaeri.configs.serdes.ObjectSerializer; import eu.okaeri.configs.serdes.SerializationData; -public class PositionSerializer implements ObjectSerializer { +public class PositionTransformer implements ObjectSerializer { @Override public boolean supports(Class type) { diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/serializer/SoundSerializer.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/serializer/SoundSerializer.java new file mode 100644 index 0000000..c2b5798 --- /dev/null +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/configuration/serializer/SoundSerializer.java @@ -0,0 +1,30 @@ +package com.eternalcode.lobbyheads.configuration.serializer; + +import com.cryptomorin.xseries.XSound; +import eu.okaeri.configs.schema.GenericsDeclaration; +import eu.okaeri.configs.serdes.DeserializationData; +import eu.okaeri.configs.serdes.ObjectSerializer; +import eu.okaeri.configs.serdes.SerializationData; + +public class SoundSerializer implements ObjectSerializer { + + @Override + public boolean supports(Class type) { + return XSound.class.isAssignableFrom(type); + } + + @Override + public void serialize( + XSound object, + SerializationData data, + GenericsDeclaration generics + ) { + data.setValue(object.name()); + } + + @Override + public XSound deserialize(DeserializationData data, GenericsDeclaration generics) { + String value = data.getValue(String.class); + return XSound.of(value).orElseThrow(() -> new IllegalArgumentException("Unknown sound: " + value)); + } +} diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/HeadController.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/HeadController.java index fe91b1b..8b3d2dd 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/HeadController.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/HeadController.java @@ -3,7 +3,8 @@ import com.eternalcode.lobbyheads.configuration.implementation.HeadsConfiguration; import com.eternalcode.lobbyheads.delay.Delay; import com.eternalcode.lobbyheads.notification.NotificationAnnouncer; -import com.eternalcode.lobbyheads.position.PositionAdapter; +import java.time.Duration; +import java.util.UUID; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -13,21 +14,22 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.EquipmentSlot; -import java.time.Duration; -import java.util.UUID; - public class HeadController implements Listener { public static final String HEAD_REPLACE_PERMISSION = "lobbyheads.replace"; private final HeadsConfiguration config; private final Delay delay; - private final HeadManagerImpl headManagerImpl; + private final HeadManager headManager; private final NotificationAnnouncer notificationAnnouncer; - public HeadController(HeadsConfiguration config, HeadManagerImpl headManagerImpl, NotificationAnnouncer notificationAnnouncer) { + public HeadController( + HeadsConfiguration config, + HeadManager headManager, + NotificationAnnouncer notificationAnnouncer + ) { this.config = config; - this.headManagerImpl = headManagerImpl; + this.headManager = headManager; this.notificationAnnouncer = notificationAnnouncer; this.delay = new Delay<>(this.config); } @@ -47,7 +49,7 @@ private void onPlayerInteract(PlayerInteractEvent event) { Location location = clickedBlock.getLocation(); - Head head = this.headManagerImpl.getHead(PositionAdapter.convert(location)); + Head head = this.headManager.getHead(location); if (head == null) { return; @@ -56,24 +58,24 @@ private void onPlayerInteract(PlayerInteractEvent event) { UUID playerUUID = player.getUniqueId(); if (!player.hasPermission(HEAD_REPLACE_PERMISSION)) { - this.notificationAnnouncer.sendMessage(player, this.config.messages.playerNotPermittedToReplaceHeads); + this.notificationAnnouncer.sendMessage(player, this.config.messages.noPermissionReplace); return; } if (this.delay.hasDelay(playerUUID)) { Duration durationToExpire = this.delay.getDurationToExpire(playerUUID); - this.notificationAnnouncer.sendMessage(player, this.config.messages.playerMustWaitToReplaceHead - .replace("{duration}", String.valueOf(durationToExpire.toSeconds()))); + this.notificationAnnouncer.sendMessage(player, this.config.messages.replaceCooldown + .replace("{duration}", String.valueOf(durationToExpire.toSeconds()))); return; } - if (head.getPlayerUUID().equals(playerUUID)) { - this.notificationAnnouncer.sendMessage(player, this.config.messages.playerAlreadyReplaceThisHead); + if (head.getPlayerUuid().equals(playerUUID)) { + this.notificationAnnouncer.sendMessage(player, this.config.messages.alreadyReplaced); return; } - this.headManagerImpl.updateHead(player, PositionAdapter.convert(location)); + this.headManager.updateHead(player, location); this.delay.markDelay(playerUUID); } @@ -82,7 +84,7 @@ private void onBlockBreak(BlockBreakEvent event) { Block clickedBlock = event.getBlock(); Location location = clickedBlock.getLocation(); - Head head = this.headManagerImpl.getHead(PositionAdapter.convert(location)); + Head head = this.headManager.getHead(location); if (head == null) { return; diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/HeadManagerImpl.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/HeadManagerImpl.java index 090c69c..81a2dd2 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/HeadManagerImpl.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/HeadManagerImpl.java @@ -1,104 +1,90 @@ package com.eternalcode.lobbyheads.head; +import com.eternalcode.commons.bukkit.position.Position; +import com.eternalcode.commons.bukkit.position.PositionAdapter; import com.eternalcode.lobbyheads.event.EventCaller; import com.eternalcode.lobbyheads.head.event.HeadCreateEvent; import com.eternalcode.lobbyheads.head.event.HeadRemoveEvent; import com.eternalcode.lobbyheads.head.event.HeadUpdateEvent; -import com.eternalcode.lobbyheads.position.Position; -import com.eternalcode.lobbyheads.position.PositionAdapter; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.bukkit.Location; import org.bukkit.entity.Player; -import org.jetbrains.annotations.ApiStatus.Internal; +import org.jetbrains.annotations.NotNull; public class HeadManagerImpl implements HeadManager { private final Map heads = new HashMap<>(); private final EventCaller eventCaller; - private final HeadRepository headRepository; - public HeadManagerImpl(EventCaller eventCaller, HeadRepository headRepository) { + public HeadManagerImpl(@NotNull EventCaller eventCaller, @NotNull HeadRepository headRepository) { this.eventCaller = eventCaller; this.headRepository = headRepository; } - @Internal - public void addHead(Player player, Position position) { - this.heads.computeIfAbsent(position, pos -> { - UUID uniqueId = player.getUniqueId(); - Head head = new Head(pos, player.getName(), uniqueId); - this.headRepository.saveHead(head); - this.eventCaller.callEvent(new HeadCreateEvent(uniqueId, PositionAdapter.convert(pos))); - return head; - }); - } - @Override - public void addHead(Player player, Location location) { - this.addHead(player, PositionAdapter.convert(location)); + public void addHead(@NotNull Player player, @NotNull Location location) { + Position position = PositionAdapter.convert(location); + heads.computeIfAbsent( + position, pos -> { + UUID uuid = player.getUniqueId(); + String name = player.getName(); + Head head = new Head(location, name, uuid); + + headRepository.saveHead(head); + eventCaller.callEvent(new HeadCreateEvent(uuid, PositionAdapter.convert(pos))); + return head; + }); } - @Internal - public void removeHead(Position position) { - if (!this.heads.containsKey(position)) { + @Override + public void removeHead(@NotNull Location location) { + Position position = PositionAdapter.convert(location); + Head removed = heads.remove(position); + if (removed == null) { return; } - Head head = this.heads.get(position); - this.heads.remove(position); - this.headRepository.removeHead(head); - this.eventCaller.callEvent(new HeadRemoveEvent(head.getPlayerUUID(), PositionAdapter.convert(position))); + headRepository.removeHead(removed); + eventCaller.callEvent(new HeadRemoveEvent(removed.getPlayerUuid(), PositionAdapter.convert(position))); } @Override - public void removeHead(Location location) { - this.removeHead(PositionAdapter.convert(location)); - } - - @Internal - public void updateHead(Player player, Position position) { - if (!this.heads.containsKey(position)) { + public void updateHead(@NotNull Player player, @NotNull Location location) { + Position position = PositionAdapter.convert(location); + Head head = heads.get(position); + if (head == null) { return; } - Head head = this.heads.get(position); head.replacePlayer(player.getName(), player.getUniqueId()); - this.headRepository.updateHead(head); - this.eventCaller.callEvent(new HeadUpdateEvent(player.getUniqueId(), PositionAdapter.convert(position))); + headRepository.updateHead(head); + eventCaller.callEvent(new HeadUpdateEvent(player.getUniqueId(), PositionAdapter.convert(position))); } @Override - public void updateHead(Player player, Location location) { - this.updateHead(player, PositionAdapter.convert(location)); - } - - @Internal - public Head getHead(Position position) { + public Head getHead(@NotNull Location location) { + Position position = PositionAdapter.convert(location); return this.heads.get(position); } - @Override - public Head getHead(Location location) { - return this.getHead(PositionAdapter.convert(location)); - } @Override - public Collection getHeads() { - return this.heads.values(); + public @NotNull Collection getHeads() { + return Collections.unmodifiableCollection(heads.values()); } public void loadHeads() { - this.headRepository.loadHeads().thenAccept(loadedHeads -> { - this.heads.clear(); - loadedHeads.forEach(head -> this.heads.put(head.getPosition(), head)); + headRepository.loadHeads().thenAccept(loadedHeads -> { + heads.clear(); + for (Head head : loadedHeads) { + Position pos = PositionAdapter.convert(head.getLocation()); + heads.put(pos, head); + } }); } - - public void clearHeads() { - this.heads.clear(); - } } diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/HeadRepositoryImpl.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/HeadRepositoryImpl.java index f4a550f..2157cd5 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/HeadRepositoryImpl.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/HeadRepositoryImpl.java @@ -1,12 +1,11 @@ package com.eternalcode.lobbyheads.head; - import com.eternalcode.lobbyheads.configuration.ConfigurationService; import com.eternalcode.lobbyheads.configuration.implementation.HeadsConfiguration; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; public class HeadRepositoryImpl implements HeadRepository { @@ -16,30 +15,42 @@ public class HeadRepositoryImpl implements HeadRepository { public HeadRepositoryImpl(HeadsConfiguration config, ConfigurationService configurationService) { this.config = config; this.configurationService = configurationService; + + if (!(this.config.heads instanceof CopyOnWriteArrayList)) { + this.config.heads = new CopyOnWriteArrayList<>(this.config.heads); + } } @Override public CompletableFuture saveHead(Head head) { - this.config.heads.add(head); - return CompletableFuture.runAsync(() -> this.configurationService.save(this.config)); + return CompletableFuture.runAsync(() -> { + this.config.heads.add(head); + this.configurationService.save(this.config); + }); } @Override public CompletableFuture removeHead(Head head) { - this.config.heads.remove(head); - return CompletableFuture.runAsync(() -> this.configurationService.save(this.config)); + return CompletableFuture.runAsync(() -> { + this.config.heads.remove(head); + this.configurationService.save(this.config); + }); } @Override public CompletableFuture updateHead(Head head) { - int index = this.config.heads.indexOf(head); - - if (index == -1) { - return this.saveHead(head); - } - - this.config.heads.set(index, head); - return CompletableFuture.runAsync(() -> this.configurationService.save(this.config)); + return CompletableFuture.runAsync(() -> { + int index = this.config.heads.indexOf(head); + + if (index == -1) { + this.config.heads.add(head); + } + else { + this.config.heads.set(index, head); + } + + this.configurationService.save(this.config); + }); } @Override diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/block/BlockController.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/block/BlockController.java index 0173617..475fe11 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/block/BlockController.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/block/BlockController.java @@ -1,13 +1,14 @@ package com.eternalcode.lobbyheads.head.block; +import com.eternalcode.commons.bukkit.position.Position; +import com.eternalcode.commons.bukkit.position.PositionAdapter; import com.eternalcode.lobbyheads.head.event.HeadCreateEvent; import com.eternalcode.lobbyheads.head.event.HeadUpdateEvent; -import com.eternalcode.lobbyheads.position.Position; -import com.eternalcode.lobbyheads.position.PositionAdapter; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; import dev.rollczi.liteskullapi.SkullAPI; import dev.rollczi.liteskullapi.SkullData; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.Skull; @@ -16,14 +17,8 @@ import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitScheduler; -import java.lang.reflect.Field; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - public class BlockController implements Listener { - private static final String SKULL_TEXTURE_PROPERTY_KEY = "textures"; - private final Plugin plugin; private final BukkitScheduler scheduler; private final SkullAPI skullAPI; @@ -37,7 +32,7 @@ public BlockController(Plugin plugin, BukkitScheduler scheduler, SkullAPI skullA @EventHandler void headCreate(HeadCreateEvent event) { Position position = PositionAdapter.convert(event.getLocation()); - UUID uuid = event.getPlayerUniqueId(); + UUID uuid = event.getPlayerUuid(); this.processSkullUpdate(position, uuid); } @@ -45,7 +40,7 @@ void headCreate(HeadCreateEvent event) { @EventHandler private void headUpdate(HeadUpdateEvent event) { Position position = PositionAdapter.convert(event.getLocation()); - UUID uuid = event.getPlayerUniqueId(); + UUID uuid = event.getPlayerUuid(); this.processSkullUpdate(position, uuid); } @@ -59,27 +54,15 @@ private void processSkullUpdate(Position position, UUID uuid) { } SkullData skullData = this.skullAPI.awaitSkullData(uuid, 5, TimeUnit.SECONDS); - this.prepareSkullUpdate(this.scheduler, skullData, skull); + this.prepareSkullUpdate(this.scheduler, uuid, skull); } - private void prepareSkullUpdate(BukkitScheduler scheduler, SkullData skullData, Skull skull) { - scheduler.runTask(this.plugin, () -> this.updateSkull(skullData, skull)); + private void prepareSkullUpdate(BukkitScheduler scheduler, UUID uuid, Skull skull) { + scheduler.runTask(this.plugin, () -> this.updateSkull(uuid, skull)); } - private void updateSkull(SkullData skullData, Skull skull) { - GameProfile gameProfile = new GameProfile(UUID.randomUUID(), ""); - gameProfile.getProperties().put(SKULL_TEXTURE_PROPERTY_KEY, - new Property(SKULL_TEXTURE_PROPERTY_KEY, skullData.getValue())); - - try { - Field profileField = skull.getClass().getDeclaredField("profile"); - profileField.setAccessible(true); - profileField.set(skull, gameProfile); - } - catch (NoSuchFieldException | IllegalAccessException exception) { - exception.printStackTrace(); - } - + private void updateSkull(UUID uuid, Skull skull) { + skull.setOwningPlayer(Bukkit.getOfflinePlayer(uuid)); skull.update(); } } diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/block/BlockService.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/block/BlockService.java index 3cbb855..8df9b7d 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/block/BlockService.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/block/BlockService.java @@ -2,9 +2,8 @@ import com.eternalcode.lobbyheads.configuration.implementation.HeadsConfiguration; import com.eternalcode.lobbyheads.head.Head; -import com.eternalcode.lobbyheads.head.HeadManagerImpl; +import com.eternalcode.lobbyheads.head.HeadManager; import com.eternalcode.lobbyheads.notification.NotificationAnnouncer; -import com.eternalcode.lobbyheads.position.Position; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -13,38 +12,42 @@ public class BlockService { private final HeadsConfiguration config; private final NotificationAnnouncer notificationAnnouncer; - private final HeadManagerImpl headManagerImpl; + private final HeadManager headManager; - public BlockService(HeadsConfiguration config, NotificationAnnouncer notificationAnnouncer, HeadManagerImpl headManagerImpl) { + public BlockService( + HeadsConfiguration config, + NotificationAnnouncer notificationAnnouncer, + HeadManager headManager + ) { this.config = config; this.notificationAnnouncer = notificationAnnouncer; - this.headManagerImpl = headManagerImpl; + this.headManager = headManager; } - public void createHeadBlock(Location location, Player player, Position convert) { + public void createHeadBlock(Location location, Player player) { if (location.getBlock().getType() != Material.PLAYER_HEAD) { - this.notificationAnnouncer.sendMessage(player, this.config.messages.playerNotLookingAtHead); + this.notificationAnnouncer.sendMessage(player, this.config.messages.notLookingAtHead); return; } - if (this.headManagerImpl.getHead(convert) != null) { - this.notificationAnnouncer.sendMessage(player, this.config.messages.headAlreadyExists); + if (this.headManager.getHead(location) != null) { + this.notificationAnnouncer.sendMessage(player, this.config.messages.headExists); return; } - this.headManagerImpl.addHead(player, convert); + this.headManager.addHead(player, location); this.notificationAnnouncer.sendMessage(player, this.config.messages.headAdded); } - public void removeHeadBlock(Position convert, Player player) { - Head head = this.headManagerImpl.getHead(convert); + public void removeHeadBlock(Location location, Player player) { + Head head = this.headManager.getHead(location); if (head == null) { - this.notificationAnnouncer.sendMessage(player, this.config.messages.playerNotLookingAtHead); + this.notificationAnnouncer.sendMessage(player, this.config.messages.notLookingAtHead); return; } - this.headManagerImpl.removeHead(convert); + this.headManager.removeHead(location); this.notificationAnnouncer.sendMessage(player, this.config.messages.headRemoved); } } diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/command/HeadCommand.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/command/HeadCommand.java index c4071aa..fd75ff7 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/command/HeadCommand.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/command/HeadCommand.java @@ -3,80 +3,87 @@ import com.eternalcode.lobbyheads.configuration.implementation.HeadsConfiguration; import com.eternalcode.lobbyheads.head.block.BlockService; import com.eternalcode.lobbyheads.notification.NotificationAnnouncer; -import com.eternalcode.lobbyheads.position.Position; -import com.eternalcode.lobbyheads.position.PositionAdapter; import com.eternalcode.lobbyheads.reload.ReloadService; -import org.bukkit.Location; +import java.util.Locale; +import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; -import org.bukkit.command.TabCompleter; import org.bukkit.entity.Player; -import java.util.List; - -public class HeadCommand implements CommandExecutor, TabCompleter { +public class HeadCommand implements CommandExecutor { private static final String HEAD_MANAGEMENT_PERMISSION = "lobbyheads.admin"; private final HeadsConfiguration config; - private final NotificationAnnouncer notificationAnnouncer; + private final NotificationAnnouncer announcer; private final BlockService blockService; private final ReloadService reloadService; - public HeadCommand(HeadsConfiguration config, NotificationAnnouncer notificationAnnouncer, - BlockService blockService, ReloadService reloadService) { + public HeadCommand( + HeadsConfiguration config, + NotificationAnnouncer announcer, + BlockService blockService, + ReloadService reloadService + ) { this.config = config; - this.notificationAnnouncer = notificationAnnouncer; + this.announcer = announcer; this.blockService = blockService; this.reloadService = reloadService; } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!sender.hasPermission(HEAD_MANAGEMENT_PERMISSION)) { - this.notificationAnnouncer.sendMessage(sender, this.config.messages.playerNotPermittedToUseThisCommand); - return false; + if (!canUse(sender)) { + announcer.sendMessage(sender, config.messages.noPermissionCommand); + return true; } if (!(sender instanceof Player player)) { - this.notificationAnnouncer.sendMessage(sender, this.config.messages.onlyForPlayers); + announcer.sendMessage(sender, config.messages.onlyForPlayers); return true; } - String invalidUsage = this.config.messages.commandInvalidUsage; - if (args.length < 1) { - this.notificationAnnouncer.sendMessage(player, invalidUsage); + announcer.sendMessage(player, config.messages.invalidCommand); return true; } - String subcommand = args[0].toLowerCase(); - - Block block = player.getTargetBlock(null, 5); - Location location = block.getLocation(); - Position convert = PositionAdapter.convert(location); + String subcommand = args[0].toLowerCase(Locale.ROOT); switch (subcommand) { - case "add" -> this.blockService.createHeadBlock(location, player, convert); - case "remove" -> this.blockService.removeHeadBlock(convert, player); + case "add" -> { + Block targetBlock = player.getTargetBlockExact(5); + if (targetBlock == null || targetBlock.getType() == Material.AIR) { + announcer.sendMessage(player, config.messages.noBlockInSight); + return true; + } + blockService.createHeadBlock(targetBlock.getLocation(), player); + return true; + } + case "remove" -> { + Block targetBlock = player.getTargetBlockExact(5); + if (targetBlock == null || targetBlock.getType() == Material.AIR) { + announcer.sendMessage(player, config.messages.noBlockInSight); + return true; + } + blockService.removeHeadBlock(targetBlock.getLocation(), player); + return true; + } case "reload" -> { - this.reloadService.reload(); - this.notificationAnnouncer.sendMessage(player, this.config.messages.configurationReloaded); + reloadService.reload(); + announcer.sendMessage(player, config.messages.configurationReloaded); + return true; + } + default -> { + announcer.sendMessage(player, config.messages.invalidCommand); + return true; } - default -> this.notificationAnnouncer.sendMessage(player, invalidUsage); } - return true; } - - @Override - public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { - if (sender.hasPermission(HEAD_MANAGEMENT_PERMISSION)) { - return List.of("add", "remove", "reload"); - } - - return List.of(); + private boolean canUse(CommandSender sender) { + return sender.hasPermission(HEAD_MANAGEMENT_PERMISSION); } } diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/HologramController.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/HologramController.java index 270e233..289a46d 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/HologramController.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/HologramController.java @@ -1,17 +1,18 @@ package com.eternalcode.lobbyheads.head.hologram; +import com.eternalcode.commons.bukkit.position.PositionAdapter; import com.eternalcode.lobbyheads.configuration.implementation.HeadsConfiguration; import com.eternalcode.lobbyheads.head.event.HeadCreateEvent; import com.eternalcode.lobbyheads.head.event.HeadRemoveEvent; import com.eternalcode.lobbyheads.head.event.HeadUpdateEvent; -import com.eternalcode.lobbyheads.position.PositionAdapter; -import java.util.UUID; import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.Server; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import java.util.UUID; + public class HologramController implements Listener { private final HologramService hologramService; @@ -25,26 +26,23 @@ public HologramController(HologramService hologramService, HeadsConfiguration co } @EventHandler - void createHologram(HeadCreateEvent event) { - UUID player = event.getPlayerUniqueId(); - OfflinePlayer offlinePlayer = this.server.getOfflinePlayer(player); - - this.hologramService.createHologram( - offlinePlayer, - PositionAdapter.convert(event.getLocation()), - this.config.headSection.defaultHeadFormat); + void onHeadCreate(HeadCreateEvent event) { + UUID playerUuid = event.getPlayerUuid(); + OfflinePlayer player = server.getOfflinePlayer(playerUuid); + + hologramService.createHologram(player, PositionAdapter.convert(event.getLocation()), config.headSettings.defaultHeadFormat); } @EventHandler - void removeHologram(HeadRemoveEvent event) { - this.hologramService.removeHologram(PositionAdapter.convert(event.getLocation())); + void onHeadRemove(HeadRemoveEvent event) { + hologramService.removeHologram(PositionAdapter.convert(event.getLocation())); } @EventHandler - void updateHologram(HeadUpdateEvent event) { - UUID player = event.getPlayerUniqueId(); - + void onHeadUpdate(HeadUpdateEvent event) { + UUID playerUuid = event.getPlayerUuid(); Location location = event.getLocation(); - this.hologramService.updateHologram(PositionAdapter.convert(location), player); + + hologramService.updateHologram(PositionAdapter.convert(location), playerUuid); } } diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/HologramNameUtil.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/HologramNameUtil.java new file mode 100644 index 0000000..1cd1e88 --- /dev/null +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/HologramNameUtil.java @@ -0,0 +1,22 @@ +package com.eternalcode.lobbyheads.head.hologram; + +import com.eternalcode.commons.bukkit.position.Position; + +class HologramNameUtil { + + private static final String HOLOGRAM_NAME_TEMPLATE = "heads_%s_%s_%s_%s"; + + static String generateHologramName(Position position) { + return String.format( + HOLOGRAM_NAME_TEMPLATE, + sanitize(position.world()), + sanitize(String.valueOf(position.x())), + sanitize(String.valueOf(position.y())), + sanitize(String.valueOf(position.z())) + ); + } + + private static String sanitize(String input) { + return input.replaceAll("[^a-zA-Z0-9_-]", "_"); + } +} diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/HologramService.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/HologramService.java index b4bc7c5..bdec997 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/HologramService.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/HologramService.java @@ -1,11 +1,12 @@ package com.eternalcode.lobbyheads.head.hologram; import com.eternalcode.commons.adventure.AdventureUtil; +import com.eternalcode.commons.bukkit.position.Position; +import com.eternalcode.commons.bukkit.position.PositionAdapter; import com.eternalcode.lobbyheads.configuration.implementation.HeadsConfiguration; import com.eternalcode.lobbyheads.head.Head; -import com.eternalcode.lobbyheads.head.HeadManagerImpl; -import com.eternalcode.lobbyheads.position.Position; -import com.eternalcode.lobbyheads.position.PositionAdapter; +import com.eternalcode.lobbyheads.head.HeadManager; +import com.eternalcode.lobbyheads.head.hologram.provider.HologramProvider; import com.eternalcode.lobbyheads.reload.Reloadable; import me.clip.placeholderapi.PlaceholderAPI; import net.kyori.adventure.text.Component; @@ -13,93 +14,71 @@ import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.Server; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.holoeasy.HoloEasy; -import org.holoeasy.config.HologramKey; -import org.holoeasy.hologram.Hologram; -import org.holoeasy.pool.IHologramPool; import java.util.UUID; -import static org.holoeasy.builder.HologramBuilder.hologram; -import static org.holoeasy.builder.HologramBuilder.textline; +import static com.eternalcode.lobbyheads.head.hologram.HologramNameUtil.generateHologramName; public class HologramService implements Reloadable { - private static final String HOLOGRAM_NAME_PREFIX = "heads#%s,%s,%s,%s"; - private static final int SPAWN_DISTANCE = 50; - - private final Plugin plugin; private final HeadsConfiguration config; private final MiniMessage miniMessage; private final Server server; - private final HeadManagerImpl headManagerImpl; - - private final IHologramPool hologramPool; - - public HologramService(Plugin plugin, HeadsConfiguration config, MiniMessage miniMessage, - Server server, HeadManagerImpl headManagerImpl) { - this.plugin = plugin; + private final HeadManager headManager; + private final HologramProvider provider; + + public HologramService( + HeadsConfiguration config, + MiniMessage miniMessage, + Server server, + HeadManager headManager, + HologramProvider provider + ) { this.config = config; this.miniMessage = miniMessage; this.server = server; - this.headManagerImpl = headManagerImpl; - - this.hologramPool = HoloEasy.startPool(plugin, SPAWN_DISTANCE); + this.headManager = headManager; + this.provider = provider; } - public void createHologram(OfflinePlayer player, Position position, String headName) { - String replace = headName.replace("{PLAYER}", player.getName()); - String string = PlaceholderAPI.setPlaceholders(player, replace); - Component deserialize = this.miniMessage.deserialize(string); - String serialize = AdventureUtil.SECTION_SERIALIZER.serialize(deserialize); - - HologramKey key = new HologramKey(this.plugin, this.getHologramName(position), this.hologramPool); - Hologram hologram = hologram(key, PositionAdapter.convert(this.getLocationOffset(position)), () -> - textline(serialize)); + public void createHologram(OfflinePlayer owner, Position position, String format) { + String parsedText = PlaceholderAPI.setPlaceholders(owner, format.replace("{PLAYER}", owner.getName())); + Component component = miniMessage.deserialize(parsedText); + String legacyText = AdventureUtil.SECTION_SERIALIZER.serialize(component); + Location location = getOffsetLocation(position); - this.showHologramToPlayers(hologram); - } - - public void loadHolograms() { - String defaultHeadFormat = this.config.headSection.defaultHeadFormat; - - for (Head head : this.headManagerImpl.getHeads()) { - OfflinePlayer offlinePlayer = this.server.getOfflinePlayer(head.getPlayerUUID()); - this.createHologram(offlinePlayer, head.getPosition(), defaultHeadFormat); - } + this.provider.createHologram(generateHologramName(position), location, legacyText); } public void removeHologram(Position position) { - HologramKey key = new HologramKey(this.plugin, this.getHologramName(position), this.hologramPool); - this.hologramPool.remove(key); + this.provider.removeHologram(generateHologramName(position)); } public void updateHologram(Position position, UUID uuid) { this.removeHologram(position); - this.createHologram(this.server.getOfflinePlayer(uuid), position, this.config.headSection.headFormat); + this.createHologram(server.getOfflinePlayer(uuid), position, config.headSettings.headFormat); } - public void updateHolograms() { - for (Head head : this.headManagerImpl.getHeads()) { - this.updateHologram(head.getPosition(), head.getPlayerUUID()); - } - } + public void loadHolograms() { + String defaultFormat = config.headSettings.defaultHeadFormat; + + for (Head head : headManager.getHeads()) { + OfflinePlayer owner = server.getOfflinePlayer(head.getPlayerUuid()); + Position position = PositionAdapter.convert(head.getLocation()); - private Position getLocationOffset(Position position) { - Location location = PositionAdapter.convert(position).clone().add(0.5, -0.3, 0.5); - return PositionAdapter.convert(location); + this.createHologram(owner, position, defaultFormat); + } } - private void showHologramToPlayers(Hologram hologram) { - for (Player onlinePlayer : this.plugin.getServer().getOnlinePlayers()) { - hologram.show(onlinePlayer); + public void updateHolograms() { + for (Head head : headManager.getHeads()) { + Position position = PositionAdapter.convert(head.getLocation()); + updateHologram(position, head.getPlayerUuid()); } } - private String getHologramName(Position position) { - return String.format(HOLOGRAM_NAME_PREFIX, position.world(), position.x(), position.y(), position.z()); + private Location getOffsetLocation(Position position) { + return PositionAdapter.convert(position).clone().add(0.5, 1.0, 0.5); } @Override diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/HologramProvider.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/HologramProvider.java new file mode 100644 index 0000000..8426e98 --- /dev/null +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/HologramProvider.java @@ -0,0 +1,13 @@ +package com.eternalcode.lobbyheads.head.hologram.provider; + +import org.bukkit.Location; + +public interface HologramProvider { + + void createHologram(String hologramName, Location location, String text); + + void removeHologram(String hologramName); + + void updateHologram(String hologramName, String text); + +} diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/HologramProviderNotFoundException.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/HologramProviderNotFoundException.java new file mode 100644 index 0000000..b7644db --- /dev/null +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/HologramProviderNotFoundException.java @@ -0,0 +1,7 @@ +package com.eternalcode.lobbyheads.head.hologram.provider; + +public class HologramProviderNotFoundException extends RuntimeException { + public HologramProviderNotFoundException(String message) { + super(message); + } +} diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/HologramProviderPicker.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/HologramProviderPicker.java new file mode 100644 index 0000000..e5c08fa --- /dev/null +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/HologramProviderPicker.java @@ -0,0 +1,20 @@ +package com.eternalcode.lobbyheads.head.hologram.provider; + +import com.eternalcode.lobbyheads.head.hologram.provider.decentholograms.DecentHologramsProvider; +import com.eternalcode.lobbyheads.head.hologram.provider.fancyholograms.FancyHologramsProvider; +import org.bukkit.plugin.Plugin; + +public class HologramProviderPicker { + + public HologramProvider pickProvider(Plugin plugin) { + if (plugin.getServer().getPluginManager().isPluginEnabled("FancyHolograms")) { + return new FancyHologramsProvider(plugin); + } + + if (plugin.getServer().getPluginManager().isPluginEnabled("DecentHolograms")) { + return new DecentHologramsProvider(plugin); + } + + throw new HologramProviderNotFoundException("No supported hologram plugin found."); + } +} diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/decentholograms/DecentHologramsProvider.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/decentholograms/DecentHologramsProvider.java new file mode 100644 index 0000000..42ad166 --- /dev/null +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/decentholograms/DecentHologramsProvider.java @@ -0,0 +1,55 @@ +package com.eternalcode.lobbyheads.head.hologram.provider.decentholograms; + +import com.eternalcode.lobbyheads.head.hologram.provider.HologramProvider; +import eu.decentsoftware.holograms.api.DHAPI; +import eu.decentsoftware.holograms.api.holograms.Hologram; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import java.util.Collections; + +public class DecentHologramsProvider implements HologramProvider { + + private final Plugin plugin; + + public DecentHologramsProvider(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public void createHologram(String hologramName, Location location, String text) { + Bukkit.getScheduler().runTask(plugin, () -> { + if (DHAPI.getHologram(hologramName) != null) { + return; + } + + Hologram hologram = DHAPI.createHologram(hologramName, location, false, Collections.singletonList(text)); + + for (Player player : Bukkit.getOnlinePlayers()) { + hologram.setShowPlayer(player); + } + }); + } + + @Override + public void removeHologram(String hologramName) { + Bukkit.getScheduler().runTask(plugin, () -> { + Hologram hologram = DHAPI.getHologram(hologramName); + if (hologram != null) { + hologram.delete(); + } + }); + } + + @Override + public void updateHologram(String hologramName, String text) { + Bukkit.getScheduler().runTask(plugin, () -> { + Hologram hologram = DHAPI.getHologram(hologramName); + if (hologram != null) { + DHAPI.setHologramLines(hologram, Collections.singletonList(text)); + } + }); + } +} diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/fancyholograms/FancyHologramsProvider.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/fancyholograms/FancyHologramsProvider.java new file mode 100644 index 0000000..eabe4d5 --- /dev/null +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/hologram/provider/fancyholograms/FancyHologramsProvider.java @@ -0,0 +1,67 @@ +package com.eternalcode.lobbyheads.head.hologram.provider.fancyholograms; + +import com.eternalcode.lobbyheads.head.hologram.provider.HologramProvider; +import de.oliver.fancyholograms.api.FancyHologramsPlugin; +import de.oliver.fancyholograms.api.HologramManager; +import de.oliver.fancyholograms.api.data.HologramData; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.hologram.Hologram; +import java.util.Optional; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +public class FancyHologramsProvider implements HologramProvider { + + private final Plugin plugin; + + public FancyHologramsProvider(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public void createHologram(String hologramName, Location location, String text) { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + HologramManager hologramManager = FancyHologramsPlugin.get().getHologramManager(); + + TextHologramData hologramData = new TextHologramData(hologramName, location); + hologramData.removeLine(0); // remove useless information line + hologramData.setPersistent(false); + hologramData.addLine(text); + + Hologram hologram = hologramManager.create(hologramData); + + for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { + hologram.updateShownStateFor(onlinePlayer); + hologram.forceShowHologram(onlinePlayer); + } + + hologramManager.addHologram(hologram); + }); + } + + @Override + public void removeHologram(String hologramName) { + HologramManager hologramManager = FancyHologramsPlugin.get().getHologramManager(); + Optional hologramOptional = hologramManager.getHologram(hologramName); + + hologramOptional.ifPresent(hologramManager::removeHologram); + } + + @Override + public void updateHologram(String hologramName, String text) { + HologramManager hologramManager = FancyHologramsPlugin.get().getHologramManager(); + Optional hologramOptional = hologramManager.getHologram(hologramName); + + hologramOptional.ifPresent(hologram -> { + HologramData hologramData = hologram.getData(); + if (hologramData instanceof TextHologramData textData) { + textData.removeLine(0); + textData.addLine(text); + } + }); + } + + +} diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/particle/ParticleController.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/particle/ParticleController.java index 9860269..c76ee54 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/particle/ParticleController.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/particle/ParticleController.java @@ -20,18 +20,18 @@ public ParticleController(Server server, HeadsConfiguration config) { @EventHandler private void onHeadUpdate(HeadUpdateEvent event) { - UUID uuid = event.getPlayerUniqueId(); + UUID uuid = event.getPlayerUuid(); Player player = this.server.getPlayer(uuid); if (player == null) { return; } - if (this.config.headSection.particleEnabled) { + if (this.config.headSettings.particleEnabled) { player.spawnParticle( - this.config.headSection.particle, + this.config.headSettings.particle, event.getLocation(), - this.config.headSection.count, + this.config.headSettings.count, 0.5, 0.5, 0.5); diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/sound/SoundController.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/sound/SoundController.java index 3ad1a5f..d85f2f2 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/sound/SoundController.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/head/sound/SoundController.java @@ -21,15 +21,15 @@ public SoundController(Server server, HeadsConfiguration config) { @EventHandler private void onHeadUpdate(HeadUpdateEvent event) { - UUID uuid = event.getPlayerUniqueId(); + UUID uuid = event.getPlayerUuid(); Player player = this.server.getPlayer(uuid); if (player == null) { return; } - if (this.config.headSection.soundEnabled) { - player.playSound(event.getLocation(), this.config.headSection.sound, this.config.headSection.volume, this.config.headSection.pitch); + if (this.config.headSettings.soundEnabled) { + this.config.headSettings.sound.play(player, this.config.headSettings.volume, this.config.headSettings.pitch); } } } diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/notification/NotificationAnnouncer.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/notification/NotificationAnnouncer.java index 2920368..8925a97 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/notification/NotificationAnnouncer.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/notification/NotificationAnnouncer.java @@ -1,32 +1,17 @@ package com.eternalcode.lobbyheads.notification; -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.platform.AudienceProvider; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; public final class NotificationAnnouncer { - private final AudienceProvider audienceProvider; private final MiniMessage miniMessage; - public NotificationAnnouncer(AudienceProvider audienceProvider, MiniMessage miniMessage) { - this.audienceProvider = audienceProvider; + public NotificationAnnouncer(MiniMessage miniMessage) { this.miniMessage = miniMessage; } - public void sendMessage(CommandSender commandSender, String text) { - Audience audience = this.audience(commandSender); - - audience.sendMessage(this.miniMessage.deserialize(text)); - } - - private Audience audience(CommandSender sender) { - if (sender instanceof Player player) { - return this.audienceProvider.player(player.getUniqueId()); - } - - return this.audienceProvider.console(); + public void sendMessage(CommandSender sender, String text) { + sender.sendMessage(this.miniMessage.deserialize(text)); } } diff --git a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/updater/UpdaterNotificationController.java b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/updater/UpdaterNotificationController.java index 48591eb..4346166 100644 --- a/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/updater/UpdaterNotificationController.java +++ b/lobbyheads-core/src/main/java/com/eternalcode/lobbyheads/updater/UpdaterNotificationController.java @@ -28,7 +28,7 @@ public UpdaterNotificationController(UpdaterService updaterService, HeadsConfigu void onJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); - if (!player.hasPermission("lobbyheads.receiveupdates") || !this.config.receivePluginUpdates) { + if (!player.hasPermission("lobbyheads.receiveupdates") || !this.config.checkUpdates) { return; } diff --git a/lobbyheads-core/test/com/eternalcode/lobbyheads/position/PositionAdapterTest.java b/lobbyheads-core/test/com/eternalcode/lobbyheads/position/PositionAdapterTest.java deleted file mode 100644 index caf820a..0000000 --- a/lobbyheads-core/test/com/eternalcode/lobbyheads/position/PositionAdapterTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.eternalcode.lobbyheads.position; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; - -class PositionAdapterTest { - - private static final String WORLD_NAME = "Martin"; - - @Test - @DisplayName("Test converting location to position") - void testConvertLocationToPosition() { - World world = mock(World.class); - when(world.getName()).thenReturn(WORLD_NAME); - - Location location = new Location(world, 1, 2, 3); - - Position position = PositionAdapter.convert(location); - - assertEquals(1, position.x()); - assertEquals(2, position.y()); - assertEquals(3, position.z()); - assertEquals(WORLD_NAME, position.world()); - } - - @Test - @DisplayName("Test converting position to location") - void testConvertPositionToLocation() { - try (MockedStatic mocked = mockStatic(Bukkit.class)) { - World world = mock(World.class); - mocked.when(() -> Bukkit.getWorld(WORLD_NAME)).thenReturn(world); - - Position position = new Position(1, 2, 3, WORLD_NAME); - Location location = PositionAdapter.convert(position); - - assertEquals(1, location.getX()); - assertEquals(2, location.getY()); - assertEquals(3, location.getZ()); - assertEquals(world, location.getWorld()); - } - } - - @Test - @DisplayName("Test converting position to location with invalid world") - void testNullPointerExceptionWhenWorldIsNull() { - Location location = new Location(null, 1, 2, 3); - - assertThrows(IllegalStateException.class, () -> PositionAdapter.convert(location)); - } -} diff --git a/lobbyheads-core/test/com/eternalcode/lobbyheads/position/PositionTest.java b/lobbyheads-core/test/com/eternalcode/lobbyheads/position/PositionTest.java deleted file mode 100644 index 9439471..0000000 --- a/lobbyheads-core/test/com/eternalcode/lobbyheads/position/PositionTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.eternalcode.lobbyheads.position; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.BeforeEach; - -import static org.junit.jupiter.api.Assertions.*; - -class PositionTest { - - public static final String TEST_WORLD = "TestWorld"; - private Position position; - - @BeforeEach - void setUp() { - this.position = new Position(10.5, 20.5, 30.5, TEST_WORLD); - } - - @Test - void testGetX() { - assertEquals(10.5, position.x()); - } - - @Test - void testGetY() { - assertEquals(20.5, position.y()); - } - - @Test - void testGetZ() { - assertEquals(30.5, position.z()); - } - - @Test - void testGetWorld(){ - assertEquals(TEST_WORLD, position.world()); - } - - @Test - void testParse() { - Position parsed = Position.parse("Position{x=10.5, y=20.5, z=30.5, world='TestWorld'}"); - assertEquals(position, parsed); - } - - @Test - void testToString() { - assertEquals("Position{x=10.5, y=20.5, z=30.5, world='TestWorld'}", position.toString()); - } -} diff --git a/renovate.json b/renovate.json index de5fdb6..288193a 100644 --- a/renovate.json +++ b/renovate.json @@ -1,29 +1,5 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "config:base" - ], - "groupName": "all dependencies", - "groupSlug": "all", - "lockFileMaintenance": { - "enabled": false - }, - "packageRules": [ - { - "groupName": "all dependencies", - "groupSlug": "all", - "matchPackagePatterns": [ - "*" - ] - }, - { - "groupName": "spigot dependencies", - "groupSlug": "spigotmc", - "matchPackagePatterns": [ - "org.spigotmc*" - ] - } - ], - "separateMajorMinor": true, - "pruneStaleBranches": true + "local>EternalCodeTeam/.github:renovate-config" + ] }