diff --git a/.whitesource b/.whitesource deleted file mode 100644 index 9c7ae90b..00000000 --- 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/README.md b/README.md index 0628dd5c..9a7f474a 100644 --- a/README.md +++ b/README.md @@ -2,96 +2,144 @@ ![](/assets/readme-banner.png) -[![Supports Paper](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/supported/paper_vector.svg)](https://papermc.io) -[![Supports 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/eternalcombat-%E2%9C%94%EF%B8%8F-enchance-your-combat-system-with-eternalcombat.109056/) +[![Available on Modrinth](https://raw.githubusercontent.com/vLuckyyy/badges/main/avaiable-on-modrinth.svg)](https://modrinth.com/plugin/eternalcombat) +[![Available on Hangar](https://raw.githubusercontent.com/vLuckyyy/badges/main/avaiable-on-hangar.svg)](https://hangar.papermc.io/EternalCodeTeam/eternalcombat) -[![Patreon](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/donate/patreon-plural_vector.svg)](https://www.patreon.com/eternalcode) -[![Website](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/documentation/website_vector.svg)](https://eternalcode.pl/) -[![Discord](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/social/discord-plural_vector.svg)](https://discord.gg/FQ7jmGBd6c) - -[![Gradle](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/built-with/gradle_vector.svg)](https://gradle.org/) -[![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/eternalcombat/introduction) +[![Available on BStats](https://raw.githubusercontent.com/vLuckyyy/badges/main/available-on-bstats.svg)](https://bstats.org/plugin/bukkit/EternalCombat/17803) ### Information -EternalCombat has been tested on Minecraft versions 1.17.1 to 1.21.1, but it should work on most other versions. If you -encounter any compatibility issues, please report them in the -[Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). The plugin requires Java 17 or later, so please -make sure it is installed on your server. +EternalCombat 2.0 has been tested on Minecraft versions **1.17.1 to 1.21.5**, but it should work seamlessly on most +other versions too. +If you run into any compatibility issues, please let us know in +the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). +The plugin requires **Java 17 or later**, so +ensure your server is ready. +Ready for action? +Install EternalCombat and dive in now! + +### How Does EternalCombat Work? + +Take your server’s PvP experience to epic heights with **EternalCombat 2.0**! Our robust combat logging system ensures +fair, heart-pounding battles that keep players on their toes. Here’s a rundown of what makes it stand out: + +- **Combat Logging** + No more dodging fights by logging out! Once players are in combat, they’re committed until the showdown ends. Watch it + in action: + ![](/assets/combatlog.gif) + +- **Spawn Protection (Configurable)** + Stop players from fleeing to safety! Block access to spawn or safe zones during combat – tweak it to fit your server’s + rules. See how it works: + ![](/assets/border.gif) + +- **Fully Customizable Combat** + Tailor the combat experience to your liking with a ton of options! From disabling elytra to setting drop rates for + defeated players, you’re in control. Here’s what you can tweak: + + | Feature | Description | + |----------------------|-----------------------------------------------------------------| + | Elytra & Inventory | Disable elytra or inventory access during combat. | + | Commands | Whitelist or blacklist specific commands in combat. | + | Damage & Projectiles | Customize damage causes and projectile tagging settings. | + | Ender Pearls | Add cooldowns to pearl usage in fights. | + | Block Placement | Enable or disable block placement during combat. | + | Drop Rates | Set a percentage of items dropped by defeated players. | + | Temporary Effects | Apply special effects to players in combat for added intensity. | + | And More! | Dive into the config for even more fine-tuning options! | + +Curious for more? Check out our quick and exciting YouTube presentation [here](https://youtu.be/5pELO5B0Hhk) to see +EternalCombat in full swing and learn why it’s a game-changer for your server! -## PlaceholderAPI +### Permissions for EternalCombat -EternalCombat supports PlaceholderAPI, which allows you to use placeholders in other plugins that support it. -To use placeholders, follow PlaceholderAPI's [instructions](https://wiki.placeholderapi.com/users/) and use the placeholders provided by EternalCombat. -Provided placeholders: -- `%eternalcombat_opponent%` - Returns the name of the player with whom the player is fighting. -- `%eternalcombat_opponent_health%` - Returns opponent health in format `00.00` -- `%eternalcombat_remaining_seconds%` - Returns the number of seconds remaining until the player is no longer in combat. -- `%eternalcombat_remaining_millis%` - Returns the number of milliseconds remaining until the player is no longer in combat. +Control who can use EternalCombat’s powerful features with these permissions: -If the player is not in combat, the placeholders will return an empty string. -If combat was not caused by other player, opponent placeholders will return empty string. +| Permission | Description | +|--------------------------------|--------------------------------------------------------------------------| +| `eternalcombat.status` | Check a player’s combat status with `/combatlog status `. | +| `eternalcombat.tag` | Start a fight between players using `/combatlog tag [player2]`. | +| `eternalcombat.untag` | Remove a player from combat with `/combatlog untag `. | +| `eternalcombat.reload` | Reload the plugin with `/combatlog reload`. | +| `eternalcombat.receiveupdates` | Receive notifications about new plugin versions on join. | +| `eternalcombat.bypass` | Bypass combat tagging, need's `excludeAdminsFromCombat` to be enabled | -## Building -Build EternalCombat using: +## PlaceholderAPI -`./gradlew shadowJar` +EternalCombat 2.0 fully supports **PlaceholderAPI**, letting you integrate placeholders into other compatible plugins. +Follow the [PlaceholderAPI instructions](https://wiki.placeholderapi.com/users/) to get started. +Here are the available +placeholders: -### Permissions for EternalCombat +| Placeholder | Description | +|-------------------------------------|---------------------------------------------------------------| +| `%eternalcombat_opponent%` | Returns the name of the player you’re fighting. | +| `%eternalcombat_opponent_health%` | Returns the opponent’s health in `00.00` format. | +| `%eternalcombat_remaining_seconds%` | Returns seconds remaining until the player exits combat. | +| `%eternalcombat_remaining_millis%` | Returns milliseconds remaining until the player exits combat. | -- `eternalcombat.status` - Allows to check if the player is in combat `/combatlog status ` -- `eternalcombat.tag` - Allows to create fights between two - players `/combatlog tag ` -- `eternalcombat.untag` - Allows to remove player from fight `/combatlog untag ` -- `eternalcombat.reload` - Allows to reload plugin `/combatlog reload` -- `eternalcombat.receiveupdates` - Allows you to receive notifications about the new version of the plugin when - attaching +If a player isn’t in combat, placeholders return an empty string. +If combat wasn’t triggered by another player, +opponent-related placeholders will also return empty. ### Developer API #### 1. Add repository: -with Gradle: +With Gradle: + ```kts maven { url = uri("https://repo.eternalcode.pl/releases") } ``` -with Maven: +With Maven: + ```xml + - eternalcode-reposilite-releases - EternalCode Repository - https://repo.eternalcode.pl/releases + eternalcode-reposilite-releases + EternalCode Repository + https://repo.eternalcode.pl/releases ``` #### 2. Add dependency: -with Gradle: +With Gradle: + ```kts -compileOnly("com.eternalcode:eternalcombat-api:1.3.2") +compileOnly("com.eternalcode:eternalcombat-api:2.1.1") ``` -with Maven: +With Maven: + ```xml + - com.eternalcode - eternalcombat-api - 1.3.2 - provided + com.eternalcode + eternalcombat-api + 2.1.1 + provided ``` ### Contributing -We welcome all contributions to the EternalCombat project! Please check out [contributing](.github/CONTRIBUTING.md) for -more information on how to contribute and our [code of conduct](./.github/CODE_OF_CONDUCT.md) +We’d love your help to make EternalCombat even better! +Check out our [contributing guide](.github/CONTRIBUTING.md) for +details on how to get involved and our [code of conduct](./.github/CODE_OF_CONDUCT.md). ### Reporting Issues -If you find an issue with the plugin, please report it in -the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). Please provide as much information as -possible, including the version of Minecraft and the plugin you are using, as well as any error messagesSettings or logs +Found a bug? +Report it in the [Issues tab](https://github.com/eternalcodeteam/eternalcombat/issues). +Please include as much detail as possible, like your Minecraft and plugin +versions, along with any error messages or logs. +Ready to transform your server’s combat experience? +Download EternalCombat 2.0 now and let the battles begin! diff --git a/assets/border.gif b/assets/border.gif new file mode 100644 index 00000000..136db97d Binary files /dev/null and b/assets/border.gif differ diff --git a/assets/combatlog.gif b/assets/combatlog.gif new file mode 100644 index 00000000..23269d70 Binary files /dev/null and b/assets/combatlog.gif differ diff --git a/assets/readme-banner.png b/assets/readme-banner.png index 394d5683..7ef7e26d 100644 Binary files a/assets/readme-banner.png and b/assets/readme-banner.png differ diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 2a856ffb..df76a887 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -8,10 +8,10 @@ repositories { } dependencies { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.25") - implementation("io.github.goooler.shadow:shadow-gradle-plugin:8.1.8") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.20") + implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:8.3.8") implementation("net.minecrell:plugin-yml:0.6.0") - implementation("xyz.jpenilla:run-task:2.3.0") + implementation("xyz.jpenilla:run-task:2.3.1") } sourceSets { diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index a9ffc502..6b930208 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -2,31 +2,29 @@ object Versions { const val SPIGOT_API = "1.17.1-R0.1-SNAPSHOT" - const val JUNIT_JUPITER_API = "5.10.3" - const val JUNIT_JUPITER_PARAMS = "5.10.3" - const val JUNIT_JUPITER_ENGINE = "5.10.3" + const val JUNIT_JUPITER_API = "5.13.1" + const val JUNIT_JUPITER_PARAMS = "5.13.1" + const val JUNIT_JUPITER_ENGINE = "5.13.1" - const val JETBRAINS_ANNOTATIONS = "24.1.0" + const val JETBRAINS_ANNOTATIONS = "26.0.2" - const val ETERNALCODE_COMMONS = "1.1.3" - const val MULTIFICATION = "1.1.4" - const val PACKETS_EVENTS = "2.7.0" + const val ETERNALCODE_COMMONS = "1.1.6" + const val MULTIFICATION = "1.2.1" + const val PACKETS_EVENTS = "2.8.0" - const val ADVENTURE_PLATFORM_BUKKIT = "4.3.4" - const val ADVENTURE_API = "4.17.0" + const val ADVENTURE_PLATFORM_BUKKIT = "4.4.0" + const val ADVENTURE_API = "4.23.0" - const val LITE_COMMANDS = "3.7.0" - const val OKAERI_CONFIGS_YAML_BUKKIT = "5.0.3" - const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.3" - const val OKAERI_CONFIGS_SERDES_BUKKIT = "5.0.3" + const val LITE_COMMANDS = "3.10.0" + const val OKAERI_CONFIGS_YAML_BUKKIT = "5.0.6" + const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.6" + const val OKAERI_CONFIGS_SERDES_BUKKIT = "5.0.6" - const val PANDA_UTILITIES = "0.5.2-alpha" const val GIT_CHECK = "1.0.0" - const val APACHE_COMMONS = "2.16.1" - const val CAFFEINE = "3.1.8" + const val CAFFEINE = "3.2.1" - const val B_STATS_BUKKIT = "3.0.2" + const val B_STATS_BUKKIT = "3.1.0" const val WORLD_GUARD_BUKKIT = "7.0.9" const val PLACEHOLDER_API = "2.11.6" diff --git a/buildSrc/src/main/kotlin/eternalcombat-java.gradle.kts b/buildSrc/src/main/kotlin/eternalcombat-java.gradle.kts index ef36bda0..ea857396 100644 --- a/buildSrc/src/main/kotlin/eternalcombat-java.gradle.kts +++ b/buildSrc/src/main/kotlin/eternalcombat-java.gradle.kts @@ -3,12 +3,7 @@ plugins { } group = "com.eternalcode" -version = "2.0.0-SNAPSHOT" - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} +version = "2.1.1" tasks.compileJava { options.compilerArgs = listOf("-Xlint:deprecation", "-parameters") diff --git a/buildSrc/src/main/kotlin/eternalcombat-publish.gradle.kts b/buildSrc/src/main/kotlin/eternalcombat-publish.gradle.kts index 54b28952..36299762 100644 --- a/buildSrc/src/main/kotlin/eternalcombat-publish.gradle.kts +++ b/buildSrc/src/main/kotlin/eternalcombat-publish.gradle.kts @@ -4,7 +4,7 @@ plugins { } group = "com.eternalcode" -version = "2.0.0-SNAPSHOT" +version = "2.1.1" java { withSourcesJar() diff --git a/buildSrc/src/main/kotlin/eternalcombat-repositories.gradle.kts b/buildSrc/src/main/kotlin/eternalcombat-repositories.gradle.kts index d3bb41cf..e960cbc1 100644 --- a/buildSrc/src/main/kotlin/eternalcombat-repositories.gradle.kts +++ b/buildSrc/src/main/kotlin/eternalcombat-repositories.gradle.kts @@ -13,4 +13,5 @@ repositories { maven("https://maven.enginehub.org/repo/") maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") maven("https://repo.codemc.io/repository/maven-releases/") + maven("https://jitpack.io/") } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfTag.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfTag.java index 940117c6..32ec7eaa 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfTag.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfTag.java @@ -18,6 +18,11 @@ public enum CauseOfTag { */ COMMAND, + /** + * Crystal or anchor explosion caused the tag. + */ + CRYSTAL, + /** * A custom cause, typically defined by external plugins or systems, applied the combat tag. */ diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/Point.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/region/Point.java new file mode 100644 index 00000000..ea370bbb --- /dev/null +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/region/Point.java @@ -0,0 +1,6 @@ +package com.eternalcode.combat.region; + +import org.bukkit.World; + +public record Point(World world, double x, double z) { +} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/Region.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/region/Region.java index 58ab7ea8..95eb0e6b 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/Region.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/region/Region.java @@ -4,7 +4,7 @@ public interface Region { - Location getCenter(); + Point getCenter(); Location getMin(); diff --git a/eternalcombat-plugin/build.gradle.kts b/eternalcombat-plugin/build.gradle.kts index 612c09a8..634f72df 100644 --- a/eternalcombat-plugin/build.gradle.kts +++ b/eternalcombat-plugin/build.gradle.kts @@ -1,9 +1,11 @@ +import net.minecrell.pluginyml.bukkit.BukkitPluginDescription + plugins { `eternalcombat-java` `eternalcombat-repositories` id("net.minecrell.plugin-yml.bukkit") - id("io.github.goooler.shadow") + id("com.gradleup.shadow") id("xyz.jpenilla.run-paper") } @@ -23,19 +25,12 @@ dependencies { implementation("dev.rollczi:litecommands-bukkit:${Versions.LITE_COMMANDS}") // Okaeri configs - implementation("eu.okaeri:okaeri-configs-yaml-bukkit:${Versions.OKAERI_CONFIGS_YAML_BUKKIT}") implementation("eu.okaeri:okaeri-configs-serdes-commons:${Versions.OKAERI_CONFIGS_SERDES_COMMONS}") implementation("eu.okaeri:okaeri-configs-serdes-bukkit:${Versions.OKAERI_CONFIGS_SERDES_BUKKIT}") - // Panda utilities - implementation("org.panda-lang:panda-utilities:${Versions.PANDA_UTILITIES}") - // GitCheck implementation("com.eternalcode:gitcheck:${Versions.GIT_CHECK}") - // commons - implementation("commons-io:commons-io:${Versions.APACHE_COMMONS}") - // bstats implementation("org.bstats:bstats-bukkit:${Versions.B_STATS_BUKKIT}") @@ -50,11 +45,14 @@ dependencies { // PlaceholderAPI compileOnly("me.clip:placeholderapi:${Versions.PLACEHOLDER_API}") + + // Lands + compileOnly("com.github.angeschossen:LandsAPI:7.15.20") // Multification implementation("com.eternalcode:multification-bukkit:${Versions.MULTIFICATION}") implementation("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION}") - implementation("com.github.retrooper:packetevents-spigot:${Versions.PACKETS_EVENTS}") + compileOnly("com.github.retrooper:packetevents-spigot:${Versions.PACKETS_EVENTS}") implementation("io.papermc:paperlib:${Versions.PAPERLIB}") } @@ -64,45 +62,49 @@ bukkit { apiVersion = "1.13" prefix = "EternalCombat" name = "EternalCombat" - softDepend = listOf("WorldGuard", "PlaceholderAPI") + load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD + softDepend = listOf( + "Lands" + ) + depend = listOf( + "packetevents", + ) version = "${project.version}" } tasks { runServer { minecraftVersion("1.21.4") + downloadPlugins.url("https://github.com/retrooper/packetevents/releases/download/v2.8.0/packetevents-spigot-2.8.0.jar") } } tasks.shadowJar { archiveFileName.set("EternalCombat v${project.version}.jar") - dependsOn("test") - exclude( "org/intellij/lang/annotations/**", "org/jetbrains/annotations/**", "META-INF/**", "kotlin/**", - "javax/**" + "javax/**", + "org/checkerframework/**", + "com/google/errorprone/**", ) val prefix = "com.eternalcode.combat.libs" listOf( - "panda.std", - "panda.utilities", - "org.panda-lang", "eu.okaeri", "net.kyori", "org.bstats", + "org.yaml", "dev.rollczi.litecommands", "com.eternalcode.gitcheck", "org.json.simple", - "org.apache.commons", - "javassist", "com.github.benmanes.caffeine", "com.eternalcode.commons", - "com.eternalcode.multification" + "com.eternalcode.multification", + "io.papermc" ).forEach { pack -> relocate(pack, "$prefix.$pack") } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java index d0593a72..1985a28b 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java @@ -1,46 +1,49 @@ package com.eternalcode.combat; -import com.eternalcode.combat.border.BorderTriggerController; import com.eternalcode.combat.border.BorderService; import com.eternalcode.combat.border.BorderServiceImpl; +import com.eternalcode.combat.border.BorderTriggerController; import com.eternalcode.combat.border.animation.block.BorderBlockController; import com.eternalcode.combat.border.animation.particle.ParticleController; import com.eternalcode.combat.bridge.BridgeService; -import com.eternalcode.combat.fight.drop.DropKeepInventoryService; -import com.eternalcode.combat.fight.FightManager; -import com.eternalcode.combat.fight.drop.DropService; -import com.eternalcode.combat.fight.effect.FightEffectService; -import com.eternalcode.combat.fight.knockback.KnockbackService; -import com.eternalcode.combat.fight.tagout.FightTagOutService; -import com.eternalcode.combat.fight.pearl.FightPearlService; -import com.eternalcode.combat.handler.InvalidUsageHandlerImpl; -import com.eternalcode.combat.handler.MissingPermissionHandlerImpl; import com.eternalcode.combat.config.ConfigService; import com.eternalcode.combat.config.implementation.PluginConfig; -import com.eternalcode.combat.fight.drop.DropController; -import com.eternalcode.combat.fight.drop.DropKeepInventoryServiceImpl; -import com.eternalcode.combat.fight.drop.DropServiceImpl; -import com.eternalcode.combat.fight.drop.impl.PercentDropModifier; -import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier; +import com.eternalcode.combat.crystalpvp.EndCrystalListener; +import com.eternalcode.combat.crystalpvp.RespawnAnchorListener; +import com.eternalcode.combat.event.EventManager; +import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.combat.fight.FightManagerImpl; import com.eternalcode.combat.fight.FightTagCommand; +import com.eternalcode.combat.fight.FightTask; import com.eternalcode.combat.fight.controller.FightActionBlockerController; import com.eternalcode.combat.fight.controller.FightMessageController; import com.eternalcode.combat.fight.controller.FightTagController; import com.eternalcode.combat.fight.controller.FightUnTagController; +import com.eternalcode.combat.fight.drop.DropController; +import com.eternalcode.combat.fight.drop.DropKeepInventoryService; +import com.eternalcode.combat.fight.drop.DropKeepInventoryServiceImpl; +import com.eternalcode.combat.fight.drop.DropService; +import com.eternalcode.combat.fight.drop.DropServiceImpl; +import com.eternalcode.combat.fight.drop.DropType; +import com.eternalcode.combat.fight.drop.impl.PercentDropModifier; +import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier; import com.eternalcode.combat.fight.effect.FightEffectController; -import com.eternalcode.combat.event.EventCaller; -import com.eternalcode.combat.fight.FightManagerImpl; -import com.eternalcode.combat.fight.FightTask; +import com.eternalcode.combat.fight.effect.FightEffectService; import com.eternalcode.combat.fight.effect.FightEffectServiceImpl; +import com.eternalcode.combat.fight.knockback.KnockbackRegionController; +import com.eternalcode.combat.fight.knockback.KnockbackService; import com.eternalcode.combat.fight.logout.LogoutController; import com.eternalcode.combat.fight.logout.LogoutService; import com.eternalcode.combat.fight.pearl.FightPearlController; +import com.eternalcode.combat.fight.pearl.FightPearlService; import com.eternalcode.combat.fight.pearl.FightPearlServiceImpl; +import com.eternalcode.combat.fight.tagout.FightTagOutCommand; import com.eternalcode.combat.fight.tagout.FightTagOutController; +import com.eternalcode.combat.fight.tagout.FightTagOutService; import com.eternalcode.combat.fight.tagout.FightTagOutServiceImpl; -import com.eternalcode.combat.fight.tagout.FightTagOutCommand; +import com.eternalcode.combat.handler.InvalidUsageHandlerImpl; +import com.eternalcode.combat.handler.MissingPermissionHandlerImpl; import com.eternalcode.combat.notification.NoticeService; -import com.eternalcode.combat.fight.knockback.KnockbackRegionController; import com.eternalcode.combat.region.RegionProvider; import com.eternalcode.combat.updater.UpdaterNotificationController; import com.eternalcode.combat.updater.UpdaterService; @@ -49,18 +52,19 @@ import com.eternalcode.commons.bukkit.scheduler.BukkitSchedulerImpl; import com.eternalcode.commons.scheduler.Scheduler; import com.eternalcode.multification.notice.Notice; -import com.github.retrooper.packetevents.PacketEvents; import com.google.common.base.Stopwatch; import dev.rollczi.litecommands.LiteCommands; import dev.rollczi.litecommands.bukkit.LiteBukkitFactory; import dev.rollczi.litecommands.bukkit.LiteBukkitMessages; -import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder; 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.Bukkit; import org.bukkit.Server; import org.bukkit.command.CommandSender; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.plugin.java.JavaPlugin; import java.io.File; @@ -77,23 +81,14 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi { private FightTagOutService fightTagOutService; private FightEffectService fightEffectService; - private LogoutService logoutService; - private DropService dropService; private DropKeepInventoryService dropKeepInventoryService; private RegionProvider regionProvider; - private PluginConfig pluginConfig; - private AudienceProvider audienceProvider; private LiteCommands liteCommands; - @Override - public void onLoad() { - PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this)); - PacketEvents.getAPI().load(); - } @Override public void onEnable() { @@ -104,16 +99,18 @@ public void onEnable() { ConfigService configService = new ConfigService(); - EventCaller eventCaller = new EventCaller(server); + EventManager eventManager = new EventManager(this); Scheduler scheduler = new BukkitSchedulerImpl(this); - this.pluginConfig = configService.create(PluginConfig.class, new File(dataFolder, "config.yml")); + PluginConfig pluginConfig = configService.create(PluginConfig.class, new File(dataFolder, "config.yml")); - this.fightManager = new FightManagerImpl(eventCaller); - this.fightPearlService = new FightPearlServiceImpl(this.pluginConfig.pearl); + this.fightManager = new FightManagerImpl(eventManager); + this.fightPearlService = new FightPearlServiceImpl(pluginConfig.pearl); this.fightTagOutService = new FightTagOutServiceImpl(); this.fightEffectService = new FightEffectServiceImpl(); - this.logoutService = new LogoutService(); + + LogoutService logoutService = new LogoutService(); + this.dropService = new DropServiceImpl(); this.dropKeepInventoryService = new DropKeepInventoryServiceImpl(); @@ -125,24 +122,31 @@ public void onEnable() { .preProcessor(new AdventureLegacyColorPreProcessor()) .build(); - BridgeService bridgeService = new BridgeService(this.pluginConfig, server.getPluginManager(), this.getLogger(), this); - bridgeService.init(this.fightManager, server); - this.regionProvider = bridgeService.getRegionProvider(); - BorderService borderService = new BorderServiceImpl(scheduler, server, regionProvider, eventCaller, pluginConfig.border); - KnockbackService knockbackService = new KnockbackService(this.pluginConfig, scheduler); + NoticeService noticeService = new NoticeService(this.audienceProvider, pluginConfig, miniMessage); - NoticeService noticeService = new NoticeService(this.audienceProvider, this.pluginConfig, miniMessage); + BridgeService bridgeService = new BridgeService( + pluginConfig, + server.getPluginManager(), + this.getLogger(), + this, + this.fightManager + ); + bridgeService.init(server); + + this.regionProvider = bridgeService.getRegionProvider(); + BorderService borderService = new BorderServiceImpl(scheduler, server, regionProvider, eventManager, () -> pluginConfig.border); + KnockbackService knockbackService = new KnockbackService(pluginConfig, scheduler, regionProvider); this.liteCommands = LiteBukkitFactory.builder(FALLBACK_PREFIX, this, server) - .message(LiteBukkitMessages.PLAYER_NOT_FOUND, this.pluginConfig.messagesSettings.playerNotFound) - .message(LiteBukkitMessages.PLAYER_ONLY, this.pluginConfig.messagesSettings.admin.onlyForPlayers) + .message(LiteBukkitMessages.PLAYER_NOT_FOUND, pluginConfig.messagesSettings.playerNotFound) + .message(LiteBukkitMessages.PLAYER_ONLY, pluginConfig.messagesSettings.admin.onlyForPlayers) - .invalidUsage(new InvalidUsageHandlerImpl(this.pluginConfig, noticeService)) - .missingPermission(new MissingPermissionHandlerImpl(this.pluginConfig, noticeService)) + .invalidUsage(new InvalidUsageHandlerImpl(pluginConfig, noticeService)) + .missingPermission(new MissingPermissionHandlerImpl(pluginConfig, noticeService)) .commands( - new FightTagCommand(this.fightManager, noticeService, this.pluginConfig), - new FightTagOutCommand(this.fightTagOutService, noticeService, this.pluginConfig), + new FightTagCommand(this.fightManager, noticeService, pluginConfig), + new FightTagOutCommand(this.fightTagOutService, noticeService, pluginConfig), new EternalCombatReloadCommand(configService, noticeService) ) @@ -153,33 +157,56 @@ public void onEnable() { .build(); - FightTask fightTask = new FightTask(server, this.pluginConfig, this.fightManager, noticeService); + FightTask fightTask = new FightTask(server, pluginConfig, this.fightManager, noticeService); this.getServer().getScheduler().runTaskTimer(this, fightTask, 20L, 20L); new Metrics(this, BSTATS_METRICS_ID); Stream.of( - new PercentDropModifier(this.pluginConfig.drop), - new PlayersHealthDropModifier(this.pluginConfig.drop, this.logoutService) + new PercentDropModifier(pluginConfig.drop), + new PlayersHealthDropModifier(pluginConfig.drop, logoutService) ).forEach(this.dropService::registerModifier); - - Stream.of( - new DropController(this.dropService, this.dropKeepInventoryService, this.pluginConfig.drop, this.fightManager), - new FightTagController(this.fightManager, this.pluginConfig), - new LogoutController(this.fightManager, this.logoutService, noticeService, this.pluginConfig), - new FightUnTagController(this.fightManager, this.pluginConfig, this.logoutService), - new FightActionBlockerController(this.fightManager, noticeService, this.pluginConfig, server), - new FightPearlController(this.pluginConfig.pearl, noticeService, this.fightManager, this.fightPearlService), - new UpdaterNotificationController(updaterService, this.pluginConfig, this.audienceProvider, miniMessage), + eventManager.subscribe( + new FightTagController(this.fightManager, pluginConfig), + new FightUnTagController(this.fightManager, pluginConfig, logoutService), + new FightActionBlockerController(this.fightManager, noticeService, pluginConfig, server), + new FightPearlController(pluginConfig.pearl, noticeService, this.fightManager, this.fightPearlService), + new UpdaterNotificationController(updaterService, pluginConfig, this.audienceProvider, miniMessage), new KnockbackRegionController(noticeService, this.regionProvider, this.fightManager, knockbackService, server), - new FightEffectController(this.pluginConfig.effect, this.fightEffectService, this.fightManager, this.getServer()), + new FightEffectController(pluginConfig.effect, this.fightEffectService, this.fightManager, this.getServer()), new FightTagOutController(this.fightTagOutService), - new FightMessageController(this.fightManager, noticeService, this.pluginConfig, this.getServer()), - new BorderTriggerController(borderService, pluginConfig.border, fightManager, server), - new ParticleController(borderService, pluginConfig.border.particle, scheduler, server), - new BorderBlockController(borderService, pluginConfig.border.block, scheduler, server) - ).forEach(listener -> this.getServer().getPluginManager().registerEvents(listener, this)); + new FightMessageController(this.fightManager, noticeService, pluginConfig, this.getServer()), + new BorderTriggerController(borderService, () -> pluginConfig.border, fightManager, server), + new ParticleController(borderService, () -> pluginConfig.border.particle, scheduler, server), + new BorderBlockController(borderService, () -> pluginConfig.border.block, scheduler, server), + new EndCrystalListener(this, this.fightManager, pluginConfig), + new RespawnAnchorListener(this, this.fightManager, pluginConfig) + ); + + eventManager.subscribe( + PlayerDeathEvent.class, + pluginConfig.drop.dropEventPriority, + new DropController(dropService, dropKeepInventoryService, pluginConfig.drop, fightManager, miniMessage) + ); + + eventManager.subscribe( + PlayerQuitEvent.class, + pluginConfig.combat.quitPunishmentEventPriority, + new LogoutController(this.fightManager, logoutService, noticeService, pluginConfig) + ); + + if (!(pluginConfig.drop.dropType == DropType.UNCHANGED)) { + this.dropService = new DropServiceImpl(); + this.dropKeepInventoryService = new DropKeepInventoryServiceImpl(); + Bukkit.getPluginManager().registerEvents(new DropController(this.dropService, + this.dropKeepInventoryService, pluginConfig.drop, this.fightManager, miniMessage), this); + + Stream.of( + new PercentDropModifier(pluginConfig.drop), + new PlayersHealthDropModifier(pluginConfig.drop, logoutService) + ).forEach(this.dropService::registerModifier); + } EternalCombatProvider.initialize(this); @@ -200,8 +227,6 @@ public void onDisable() { } this.fightManager.untagAll(); - - PacketEvents.getAPI().terminate(); } @Override diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/EternalCombatReloadCommand.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/EternalCombatReloadCommand.java index c47a3aa2..5d2c8e8a 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/EternalCombatReloadCommand.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/EternalCombatReloadCommand.java @@ -22,11 +22,11 @@ public class EternalCombatReloadCommand { .build(); private final ConfigService configService; - private final NoticeService announcer; + private final NoticeService noticeService; - public EternalCombatReloadCommand(ConfigService configService, NoticeService announcer) { + public EternalCombatReloadCommand(ConfigService configService, NoticeService noticeService) { this.configService = configService; - this.announcer = announcer; + this.noticeService = noticeService; } @Async @@ -36,7 +36,7 @@ void execute(@Context CommandSender sender) { this.configService.reload(); Duration elapsed = stopwatch.elapsed(); - this.announcer.create() + this.noticeService.create() .viewer(sender) .notice(RELOAD_MESSAGE) .placeholder("{TIME}", String.valueOf(elapsed.toMillis())) diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderServiceImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderServiceImpl.java index 0d88d928..d5029c4c 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderServiceImpl.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderServiceImpl.java @@ -2,7 +2,7 @@ import com.eternalcode.combat.border.event.BorderHideAsyncEvent; import com.eternalcode.combat.border.event.BorderShowAsyncEvent; -import com.eternalcode.combat.event.EventCaller; +import com.eternalcode.combat.event.EventManager; import com.eternalcode.combat.region.RegionProvider; import com.eternalcode.commons.scheduler.Scheduler; import dev.rollczi.litecommands.shared.Lazy; @@ -11,6 +11,7 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.Supplier; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.World; @@ -19,16 +20,16 @@ public class BorderServiceImpl implements BorderService { private final Scheduler scheduler; - private final EventCaller eventCaller; + private final EventManager eventManager; - private final BorderSettings settings; + private final Supplier settings; private final BorderTriggerIndex borderIndexes; private final BorderActivePointsIndex activeBorderIndex = new BorderActivePointsIndex(); - public BorderServiceImpl(Scheduler scheduler, Server server, RegionProvider provider, EventCaller eventCaller, BorderSettings settings) { + public BorderServiceImpl(Scheduler scheduler, Server server, RegionProvider provider, EventManager eventManager, Supplier settings) { this.scheduler = scheduler; - this.eventCaller = eventCaller; + this.eventManager = eventManager; this.settings = settings; this.borderIndexes = BorderTriggerIndex.started(server, scheduler, provider, settings); } @@ -47,19 +48,19 @@ public void updateBorder(Player player, Location location) { return; } - scheduler.async(() -> { + scheduler.runAsync(() -> { BorderResult borderResult = result.get(); Set points = borderResult.collect(); if (!points.isEmpty()) { - BorderShowAsyncEvent event = eventCaller.publishEvent(new BorderShowAsyncEvent(player, points)); + BorderShowAsyncEvent event = eventManager.publishEvent(new BorderShowAsyncEvent(player, points)); points = event.getPoints(); } Set removed = this.activeBorderIndex.putPoints(world, player.getUniqueId(), points); if (!removed.isEmpty()) { - eventCaller.publishEvent(new BorderHideAsyncEvent(player, removed)); + eventManager.publishEvent(new BorderHideAsyncEvent(player, removed)); } }); } @@ -69,10 +70,10 @@ public void clearBorder(Player player) { World world = player.getWorld(); UUID uniqueId = player.getUniqueId(); - scheduler.async(() -> { + scheduler.runAsync(() -> { Set removed = this.activeBorderIndex.removePoints(world.getName(), uniqueId); if (!removed.isEmpty()) { - eventCaller.publishEvent(new BorderHideAsyncEvent(player, removed)); + eventManager.publishEvent(new BorderHideAsyncEvent(player, removed)); } }); } @@ -106,12 +107,13 @@ private List resolveBorderPoints(BorderTrigger trigger, Location pl int y = (int) Math.round(playerLocation.getY()); int z = (int) Math.round(playerLocation.getZ()); - int realMinX = Math.max(borderMin.x(), x - settings.distanceRounded()); - int realMaxX = Math.min(borderMax.x(), x + settings.distanceRounded()); - int realMinY = Math.max(borderMin.y(), y - settings.distanceRounded()); - int realMaxY = Math.min(borderMax.y(), y + settings.distanceRounded()); - int realMinZ = Math.max(borderMin.z(), z - settings.distanceRounded()); - int realMaxZ = Math.min(borderMax.z(), z + settings.distanceRounded()); + int distanceRounded = settings.get().distanceRounded(); + int realMinX = Math.max(borderMin.x(), x - distanceRounded); + int realMaxX = Math.min(borderMax.x(), x + distanceRounded); + int realMinY = Math.max(borderMin.y(), y - distanceRounded); + int realMaxY = Math.min(borderMax.y(), y + distanceRounded); + int realMinZ = Math.max(borderMin.z(), z - distanceRounded); + int realMaxZ = Math.min(borderMax.z(), z + distanceRounded); List points = new ArrayList<>(); @@ -176,7 +178,7 @@ private void addPoint(List points, int x, int y, int z, Location pl } private boolean isVisible(int x, int y, int z, Location player) { - return Math.sqrt(Math.pow(x - player.getX(), 2) + Math.pow(y - player.getY(), 2) + Math.pow(z - player.getZ(), 2)) <= this.settings.distance; + return Math.sqrt(Math.pow(x - player.getX(), 2) + Math.pow(y - player.getY(), 2) + Math.pow(z - player.getZ(), 2)) <= this.settings.get().distance; } } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderSettings.java index cda0af18..133d97ed 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderSettings.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderSettings.java @@ -5,6 +5,7 @@ import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; import java.time.Duration; +import org.jetbrains.annotations.ApiStatus; public class BorderSettings extends OkaeriConfig { @@ -25,6 +26,7 @@ public class BorderSettings extends OkaeriConfig { }) public ParticleSettings particle = new ParticleSettings(); + @ApiStatus.Internal public Duration indexRefreshDelay() { return Duration.ofSeconds(1); } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerController.java index 32e808f1..52d101cc 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerController.java @@ -1,7 +1,9 @@ package com.eternalcode.combat.border; import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.combat.fight.event.FightTagEvent; import com.eternalcode.combat.fight.event.FightUntagEvent; +import java.util.function.Supplier; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.entity.Player; @@ -14,11 +16,11 @@ public class BorderTriggerController implements Listener { private final BorderService borderService; - private final BorderSettings border; + private final Supplier border; private final FightManager fightManager; private final Server server; - public BorderTriggerController(BorderService borderService, BorderSettings border, FightManager fightManager, Server server) { + public BorderTriggerController(BorderService borderService, Supplier border, FightManager fightManager, Server server) { this.borderService = borderService; this.border = border; this.fightManager = fightManager; @@ -27,7 +29,7 @@ public BorderTriggerController(BorderService borderService, BorderSettings borde @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) void onMove(PlayerMoveEvent event) { - if (!border.isEnabled()) { + if (!border.get().isEnabled()) { return; } @@ -47,7 +49,7 @@ void onMove(PlayerMoveEvent event) { @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) void onTeleport(PlayerTeleportEvent event) { - if (!border.isEnabled()) { + if (!border.get().isEnabled()) { return; } @@ -59,9 +61,23 @@ void onTeleport(PlayerTeleportEvent event) { borderService.updateBorder(player, event.getTo()); } + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + void onFightStart(FightTagEvent event) { + if (!border.get().isEnabled()) { + return; + } + + Player player = server.getPlayer(event.getPlayer()); + if (player == null) { + return; + } + + borderService.updateBorder(player, player.getLocation()); + } + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) void onFightEnd(FightUntagEvent event) { - if (!border.isEnabled()) { + if (!border.get().isEnabled()) { return; } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndex.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndex.java index 561ffd19..6ede40ae 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndex.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndex.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.World; @@ -18,11 +19,11 @@ class BorderTriggerIndex { private final Server server; private final Scheduler scheduler; private final RegionProvider provider; - private final BorderSettings settings; + private final Supplier settings; private final Map borderIndexes = new HashMap<>(); - private BorderTriggerIndex(Server server, Scheduler scheduler, RegionProvider provider, BorderSettings settings) { + private BorderTriggerIndex(Server server, Scheduler scheduler, RegionProvider provider, Supplier settings) { this.server = server; this.scheduler = scheduler; this.provider = provider; @@ -36,7 +37,8 @@ private void updateWorlds() { } private void updateWorld(String world, Collection regions) { - this.scheduler.async(() -> { + this.scheduler.runAsync(() -> { + int distanceRounded = settings.get().distanceRounded(); List triggers = new ArrayList<>(); for (Region region : regions) { Location min = region.getMin(); @@ -45,7 +47,7 @@ private void updateWorld(String world, Collection regions) { triggers.add(new BorderTrigger( min.getBlockX(), min.getBlockY(), min.getBlockZ(), max.getBlockX() + 1, max.getBlockY() + 1, max.getBlockZ() + 1, - settings.distanceRounded() + distanceRounded )); } @@ -54,9 +56,9 @@ private void updateWorld(String world, Collection regions) { }); } - static BorderTriggerIndex started(Server server, Scheduler scheduler, RegionProvider provider, BorderSettings settings) { + static BorderTriggerIndex started(Server server, Scheduler scheduler, RegionProvider provider, Supplier settings) { BorderTriggerIndex index = new BorderTriggerIndex(server, scheduler, provider, settings); - scheduler.timerSync(() -> index.updateWorlds(), Duration.ZERO, settings.indexRefreshDelay()); + scheduler.timerAsync(() -> index.updateWorlds(), Duration.ZERO, settings.get().indexRefreshDelay()); return index; } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndexBucket.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndexBucket.java index 8605754f..9518e732 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndexBucket.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndexBucket.java @@ -44,10 +44,10 @@ private BorderTriggerIndexBucket with(Collection triggers) { } private void withTrigger(BorderTrigger trigger) { - int minX = trigger.min().x() >> CHUNK_SHIFT; - int minZ = trigger.min().z() >> CHUNK_SHIFT; - int maxX = trigger.max().x() >> CHUNK_SHIFT; - int maxZ = trigger.max().z() >> CHUNK_SHIFT; + int minX = trigger.triggerMin().x() >> CHUNK_SHIFT; + int minZ = trigger.triggerMin().z() >> CHUNK_SHIFT; + int maxX = trigger.triggerMax().x() >> CHUNK_SHIFT; + int maxZ = trigger.triggerMax().z() >> CHUNK_SHIFT; int startX = Math.min(minX, maxX); int startZ = Math.min(minZ, maxZ); diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockSettings.java index 615e19cd..4f8c9c46 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockSettings.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockSettings.java @@ -17,14 +17,14 @@ public class BlockSettings extends OkaeriConfig { public BlockType type = BlockType.RAINBOW_GLASS; @Comment({ - "# Delay between each async animation update", + "# Delay between each async animation update (need restart to apply)", "# Lower values will decrease performance but will make the animation smoother", "# Higher values will increase performance" }) public Duration updateDelay = Duration.ofMillis(250); @Comment({ - "# Delay between each chunk cache update", + "# Delay between each chunk cache update (need restart to apply)", "# Lower values will decrease performance", "# Higher values will increase performance but may cause overlapping existing blocks (this does not modify the world)" }) diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java index 45e40984..73e2fb95 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java @@ -21,6 +21,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import org.bukkit.ChunkSnapshot; @@ -40,25 +41,25 @@ public class BorderBlockController implements Listener { public static final int MINECRAFT_CHUNK_SHIFT = 4; private final BorderService borderService; - private final BlockSettings settings; + private final Supplier settings; private final Server server; private final Set playersToUpdate = ConcurrentHashMap.newKeySet(); private final Map lockedPlayers = new ConcurrentHashMap<>(); private final ChunkCache chunkCache; - public BorderBlockController(BorderService borderService, BlockSettings settings, Scheduler scheduler, Server server) { + public BorderBlockController(BorderService borderService, Supplier settings, Scheduler scheduler, Server server) { this.borderService = borderService; this.settings = settings; this.server = server; - this.chunkCache = new ChunkCache(settings); + this.chunkCache = new ChunkCache(settings.get()); - scheduler.timerAsync(() -> this.updatePlayers(), settings.updateDelay, settings.updateDelay); + scheduler.timerAsync(() -> this.updatePlayers(), settings.get().updateDelay, settings.get().updateDelay); } @EventHandler void onBorderShowAsyncEvent(BorderShowAsyncEvent event) { - if (!settings.enabled) { + if (!settings.get().enabled) { return; } @@ -72,7 +73,7 @@ void onBorderShowAsyncEvent(BorderShowAsyncEvent event) { @EventHandler void onBorderHideAsyncEvent(BorderHideAsyncEvent event) { - if (!settings.enabled) { + if (!settings.get().enabled) { return; } @@ -88,7 +89,7 @@ void onBorderHideAsyncEvent(BorderHideAsyncEvent event) { } private void updatePlayers() { - if (!settings.enabled) { + if (!settings.get().enabled) { return; } @@ -188,7 +189,7 @@ private Map> splitByChunks(Collection bl } private EncodedBlock toEncodedBlock(BorderPoint point) { - StateType type = settings.type.getStateType(point); + StateType type = settings.get().type.getStateType(point); WrappedBlockState state = WrappedBlockState.getDefaultState(SERVER_VERSION, type); return new EncodedBlock(state.getGlobalId(), point.x(), point.y(), point.z()); } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleController.java index e94a5293..76617ccd 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleController.java @@ -13,6 +13,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; import org.bukkit.Server; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -24,11 +25,11 @@ public class ParticleController implements Listener { private static final PlayerManager PLAYER_MANAGER = PACKET_EVENTS_API.getPlayerManager(); private final BorderService borderService; - private final ParticleSettings particleSettings; + private final Supplier particleSettings; private final Server server; private final Set playersToUpdate = ConcurrentHashMap.newKeySet(); - public ParticleController(BorderService borderService, ParticleSettings particleSettings, Scheduler scheduler, Server server) { + public ParticleController(BorderService borderService, Supplier particleSettings, Scheduler scheduler, Server server) { this.borderService = borderService; this.particleSettings = particleSettings; this.server = server; @@ -37,7 +38,7 @@ public ParticleController(BorderService borderService, ParticleSettings particle @EventHandler void onBorderShowAsyncEvent(BorderShowAsyncEvent event) { - if (!particleSettings.enabled) { + if (!particleSettings.get().enabled) { return; } @@ -50,7 +51,7 @@ void onBorderShowAsyncEvent(BorderShowAsyncEvent event) { @EventHandler void onBorderHideAsyncEvent(BorderHideAsyncEvent event) { - if (!particleSettings.enabled) { + if (!particleSettings.get().enabled) { return; } @@ -61,12 +62,17 @@ void onBorderHideAsyncEvent(BorderHideAsyncEvent event) { } private void updatePlayers() { - if (!particleSettings.enabled) { + if (!particleSettings.get().enabled) { return; } for (UUID uuid : this.playersToUpdate) { Player player = this.server.getPlayer(uuid); + if (player == null || !player.isOnline()) { + this.playersToUpdate.remove(uuid); + continue; + } + Set border = this.borderService.getActiveBorder(player); if (border.isEmpty()) { @@ -81,7 +87,7 @@ private void updatePlayers() { } private void playParticle(Player player, BorderPoint point) { - WrapperPlayServerParticle particle = particleSettings.getParticle(point); + WrapperPlayServerParticle particle = particleSettings.get().getParticle(point); PLAYER_MANAGER.sendPacket(player, particle); } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleSettings.java index 89f138ed..88d0c7a2 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleSettings.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleSettings.java @@ -28,8 +28,8 @@ public class ParticleSettings extends OkaeriConfig { "# You can set hex color e.g. \"#ca4c45\" or use \"RAINBOW\" to generate rainbow gradient based on x and z coordinates." }) public ParticleColor color = ParticleColor.RAINBOW; - public int count = 5; - public float scale = 1.0F; + public int count = 1; + public float scale = 1.7F; public float maxSpeed = 0.0F; public float offsetX = 0.2F; public float offsetY = 0.2F; diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java index a1eeaa31..b4badd50 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java @@ -1,57 +1,86 @@ package com.eternalcode.combat.bridge; +import com.eternalcode.combat.region.lands.LandsRegionProvider; import com.eternalcode.combat.bridge.placeholder.FightTagPlaceholder; import com.eternalcode.combat.config.implementation.PluginConfig; import com.eternalcode.combat.fight.FightManager; -import com.eternalcode.combat.region.DefaultRegionProvider; +import com.eternalcode.combat.region.CompositeRegionProvider; +import com.eternalcode.combat.region.bukkit.DefaultRegionProvider; import com.eternalcode.combat.region.RegionProvider; -import com.eternalcode.combat.region.WorldGuardRegionProvider; +import com.eternalcode.combat.region.worldguard.WorldGuardRegionProvider; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import java.util.stream.Collectors; import org.bukkit.Server; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; -import java.util.logging.Logger; - public class BridgeService { - private final PluginConfig pluginConfig; + private final PluginConfig config; private final PluginManager pluginManager; private final Logger logger; private final Plugin plugin; + private final FightManager fightManager; private RegionProvider regionProvider; - public BridgeService(PluginConfig pluginConfig, PluginManager pluginManager, Logger logger, Plugin plugin) { - this.pluginConfig = pluginConfig; + public BridgeService( + PluginConfig config, + PluginManager pluginManager, + Logger logger, + Plugin plugin, + FightManager fightManager + ) { + this.config = config; this.pluginManager = pluginManager; this.logger = logger; this.plugin = plugin; + this.fightManager = fightManager; } - public void init(FightManager fightManager, Server server) { - this.initialize("WorldGuard", - () -> this.regionProvider = new WorldGuardRegionProvider(this.pluginConfig.regions.blockedRegions, - this.pluginConfig), - () -> { - this.regionProvider = new DefaultRegionProvider(this.pluginConfig.regions.restrictedRegionRadius); + public void init(Server server) { + List providers = new ArrayList<>(); - this.logger.warning("WorldGuard is not installed, set to default region provider."); - }); + this.initialize( + "Lands", + () -> providers.add(new LandsRegionProvider(plugin)), + () -> this.logger.warning("Lands not found; skipping LandsRegionProvider.") + ); - this.initialize("PlaceholderAPI", - () -> new FightTagPlaceholder(fightManager, server, this.plugin).register(), - () -> this.logger.warning("PlaceholderAPI is not installed, placeholders will not be registered.") + this.initialize( + "WorldGuard", + () -> providers.add( new WorldGuardRegionProvider(this.config)), + () -> this.logger.warning("WorldGuard not found; skipping WorldGuardRegionProvider.") + ); + + if (providers.isEmpty()) { + providers.add(new DefaultRegionProvider(this.config.regions.restrictedRegionRadius)); + this.logger.warning("No region plugin found; using DefaultRegionProvider."); + } + + this.regionProvider = new CompositeRegionProvider(providers); + this.logger.info("Using composite region provider with: " + + providers.stream() + .map(p -> p.getClass().getSimpleName()) + .collect(Collectors.joining(", ")) + ); + + initialize( + "PlaceholderAPI", + () -> new FightTagPlaceholder(this.fightManager, server, this.plugin).register(), + () -> this.logger.warning("PlaceholderAPI not found; skipping placeholders.") ); } - private void initialize(String pluginName, BridgeInitializer initializer, Runnable failureHandler) { + private void initialize(String pluginName, BridgeInitializer initializer, Runnable onFailure) { if (this.pluginManager.isPluginEnabled(pluginName)) { initializer.initialize(); - - this.logger.info("Successfully initialized " + pluginName + " bridge."); + this.logger.info("Initialized " + pluginName + " bridge."); } else { - failureHandler.run(); + onFailure.run(); } } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/ConfigService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/ConfigService.java index 4418433b..4ef3edf2 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/ConfigService.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/ConfigService.java @@ -12,9 +12,9 @@ import eu.okaeri.configs.serdes.OkaeriSerdesPack; import eu.okaeri.configs.serdes.SerdesRegistry; import eu.okaeri.configs.serdes.commons.SerdesCommons; -import eu.okaeri.configs.yaml.bukkit.YamlBukkitConfigurer; import eu.okaeri.configs.yaml.bukkit.serdes.SerdesBukkit; +import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer; import java.io.File; import java.util.HashSet; import java.util.Set; @@ -26,11 +26,11 @@ public class ConfigService { public T create(Class config, File file) { T configFile = ConfigManager.create(config); - YamlBukkitConfigurer yamlBukkitConfigurer = new YamlBukkitConfigurer(); + YamlSnakeYamlConfigurer configurer = new YamlSnakeYamlConfigurer(); NoticeResolverRegistry noticeRegistry = NoticeResolverDefaults.createRegistry() .registerResolver(new SoundBukkitResolver()); - configFile.withConfigurer(yamlBukkitConfigurer, + configFile.withConfigurer(configurer, new SerdesCommons(), new SerdesBukkit(), new MultificationSerdesPack(noticeRegistry), diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CombatSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CombatSettings.java index 5064c10e..d1300b73 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CombatSettings.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CombatSettings.java @@ -5,6 +5,7 @@ import eu.okaeri.configs.annotation.Comment; import java.util.List; import org.bukkit.entity.EntityType; +import org.bukkit.event.EventPriority; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; @@ -72,4 +73,23 @@ public class CombatSettings extends OkaeriConfig { EntityType.ENDER_PEARL, EntityType.EGG ); + + @Comment({ + "# The event priority at which quit punishments should be handled.", + "# This determines when the plugin processes combat log punishment during PlayerQuitEvent.", + "# Options: LOWEST, LOW, NORMAL, HIGH, HIGHEST, MONITOR", + "# Tip: Set to LOWEST or LOW if you want quit punishments to happen before most other plugins.", + "# Default: NORMAL" + }) + public EventPriority quitPunishmentEventPriority = EventPriority.NORMAL; + + @Comment({ + "# List of kick reasons where players will NOT be punished for combat logging.", + "# If this list is empty, players are ALWAYS punished when kicked during combat.", + "# If one of the listed phrases is found in the kick reason (case-insensitive),", + "# the player will NOT be punished.", + "# Example: 'Timed out', 'Kicked for inactivity', etc.", + "# To always punish players on kick, set: whitelistedKickReasons: []" + }) + public List whitelistedKickReasons = List.of("Kicked for inactivity", "Timed out", "Server is restarting"); } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java index 6b5c65c9..2384e9bb 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java @@ -29,6 +29,11 @@ public class MessagesSettings extends OkaeriConfig { .sound(Sound.ENTITY_EXPERIENCE_ORB_PICKUP, SoundCategory.PLAYERS, 2.0F, 1.0F) .build(); + @Comment({ + "# Would you like to display milliseconds instead of seconds in combat notification " + }) + public boolean withoutMillis = true; + @Comment({ "# Message displayed when a player lacks permission to execute a command.", "# The {PERMISSION} placeholder is replaced with the required permission." @@ -55,7 +60,7 @@ public class MessagesSettings extends OkaeriConfig { "# This message informs the player that they can safely leave the server." }) public Notice playerUntagged = Notice.chat( - "Combat ended! You can now safely leave!"); + "Combat ended! You can now safely leave!"); @Comment({ "# Broadcast message displayed when a player logs out during combat.", @@ -128,6 +133,13 @@ public static class AdminMessages extends OkaeriConfig { public Notice adminUntagPlayer = Notice.chat( "Removed {PLAYER} from combat"); + @Comment({ + "# Message displayed to an admin when they remove a player from combat.", + "# The {PLAYER} placeholder is replaced with the player's name." + }) + public Notice adminUntagAll = Notice.chat( + "Removed {COUNT} players from combat"); + @Comment({ "# Message displayed when an admin attempts to untag a player who is not in combat.", "# This message informs the admin that the player is not currently in combat." diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java index f183b036..e3e1a6c8 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java @@ -1,6 +1,7 @@ package com.eternalcode.combat.config.implementation; import com.eternalcode.combat.border.BorderSettings; +import com.eternalcode.combat.crystalpvp.CrystalPvpSettings; import com.eternalcode.combat.fight.drop.DropSettings; import com.eternalcode.combat.fight.effect.FightEffectSettings; import com.eternalcode.combat.fight.knockback.KnockbackSettings; @@ -70,6 +71,13 @@ public class PluginConfig extends OkaeriConfig { }) public BlockPlacementSettings blockPlacement = new BlockPlacementSettings(); + @Comment({ + " ", + "# Settings related to crystal PvP.", + "# Configure behaviors, restrictions, and features specific to crystal PvP combat." + }) + public CrystalPvpSettings crystalPvp = new CrystalPvpSettings(); + @Comment({ " ", "# Settings related to commands during combat.", diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalMetadata.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalMetadata.java new file mode 100644 index 00000000..241f4de6 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalMetadata.java @@ -0,0 +1,33 @@ +package com.eternalcode.combat.crystalpvp; + +import java.util.Optional; +import java.util.UUID; +import org.bukkit.metadata.MetadataValueAdapter; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class CrystalMetadata extends MetadataValueAdapter { + + private UUID damager; + + protected CrystalMetadata(@NotNull Plugin owningPlugin, UUID damager) { + super(owningPlugin); + this.damager = damager; + } + + Optional getDamager() { + return Optional.ofNullable(this.damager); + } + + @Override + public @Nullable UUID value() { + return this.damager; + } + + @Override + public void invalidate() { + this.damager = null; + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java new file mode 100644 index 00000000..b1c82e37 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java @@ -0,0 +1,99 @@ +package com.eternalcode.combat.crystalpvp; + +import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.combat.fight.event.CauseOfTag; +import com.eternalcode.combat.util.ReflectUtil; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.entity.EnderCrystal; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.EntityDamageByBlockEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.metadata.MetadataValue; + +public class CrystalPvpConstants { + + private CrystalPvpConstants() { + } + + public static final String CRYSTAL_METADATA = "eternalcombat:crystal"; + public static final String ANCHOR_METADATA = "eternalcombat:anchor"; + + private static final boolean HAS_DAMAGER_BLOCK_STATE = checkForDamagerBlockState(); + + private static boolean checkForDamagerBlockState() { + try { + return EntityDamageByBlockEvent.class.getDeclaredMethod("getDamagerBlockState") != null; + } + catch (NoSuchMethodException exception) { + return false; + } + } + + static boolean hasDamagerBlockState() { + return HAS_DAMAGER_BLOCK_STATE; + } + + public static Optional getDamagerUniqueIdFromEndCrystal(EntityDamageByEntityEvent event) { + if (event.getDamager() instanceof EnderCrystal enderCrystal) { + List metadataValues = enderCrystal.getMetadata(CRYSTAL_METADATA); + return metadataValues + .stream() + .filter(source -> source instanceof CrystalMetadata) + .map(meta -> (CrystalMetadata) meta) + .findFirst() + .flatMap(CrystalMetadata::getDamager); + } + return Optional.empty(); + } + + public static Optional getDamagerUniqueIdFromRespawnAnchor(EntityDamageByBlockEvent event) { + if (!CrystalPvpConstants.hasDamagerBlockState()) { + return Optional.empty(); + } + + Object maybeState = ReflectUtil.invokeMethod(event, "getDamagerBlockState"); + if (!(maybeState instanceof BlockState state)) { + return Optional.empty(); + } + Material type = state.getType(); + if (!type.equals(Material.RESPAWN_ANCHOR)) { + return Optional.empty(); + } + + return state.getMetadata(ANCHOR_METADATA).stream() + .filter(source -> source instanceof CrystalMetadata) + .map(meta -> (CrystalMetadata) meta) + .findFirst() + .flatMap(metadata -> metadata.getDamager()); + } + + static void handleCombatTag( + Optional optionalDamagerUUID, + Player player, + FightManager fightManager, + PluginConfig pluginConfig + ) { + UUID victimUniqueId = player.getUniqueId(); + + if (optionalDamagerUUID.isPresent()) { + UUID damagerUniqueId = optionalDamagerUUID.get(); + if (!damagerUniqueId.equals(victimUniqueId)) { + fightManager.tag( + damagerUniqueId, + pluginConfig.settings.combatTimerDuration, + CauseOfTag.CRYSTAL + ); + fightManager.tag( + victimUniqueId, + pluginConfig.settings.combatTimerDuration, + CauseOfTag.CRYSTAL + ); + } + } + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpSettings.java new file mode 100644 index 00000000..3540a827 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpSettings.java @@ -0,0 +1,14 @@ +package com.eternalcode.combat.crystalpvp; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; + +public class CrystalPvpSettings extends OkaeriConfig { + + @Comment({"# Should player be tagged when damaged from crystal explosion set by other player"}) + public boolean tagFromCrystals = true; + + + @Comment({"#Should player be tagged when damaged from respawn anchor explosion set by other player"}) + public boolean tagFromRespawnAnchor = true; +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/EndCrystalListener.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/EndCrystalListener.java new file mode 100644 index 00000000..df47e44d --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/EndCrystalListener.java @@ -0,0 +1,70 @@ +package com.eternalcode.combat.crystalpvp; + +import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.fight.FightManager; +import java.util.Optional; +import java.util.UUID; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.EnderCrystal; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.plugin.Plugin; +import static com.eternalcode.combat.crystalpvp.CrystalPvpConstants.CRYSTAL_METADATA; + +public class EndCrystalListener implements Listener { + + private final Plugin plugin; + private final FightManager fightManager; + private final PluginConfig pluginConfig; + + public EndCrystalListener(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) { + this.plugin = plugin; + this.fightManager = fightManager; + this.pluginConfig = pluginConfig; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + void onPlayerDamageCrystal(EntityDamageByEntityEvent event) { + if (event.getEntity() instanceof EnderCrystal enderCrystal) { + if (event.getDamager() instanceof Arrow arrow && arrow.getShooter() instanceof Player player) { + enderCrystal.setMetadata(CRYSTAL_METADATA, new CrystalMetadata(this.plugin, player.getUniqueId())); + } + + if (!(event.getDamager() instanceof Player player)) { + return; + } + + enderCrystal.setMetadata(CRYSTAL_METADATA, new CrystalMetadata(this.plugin, player.getUniqueId())); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + void onDamage(EntityDamageByEntityEvent event) { + if (!this.pluginConfig.crystalPvp.tagFromCrystals) { + return; + } + + if (pluginConfig.settings.ignoredWorlds.contains(event.getEntity().getWorld().getName())) { + return; + } + + Optional optionalDamagerUUID = CrystalPvpConstants.getDamagerUniqueIdFromEndCrystal(event); + + if (optionalDamagerUUID.isEmpty()) { + return; + } + + if (event.getEntity() instanceof Player player) { + CrystalPvpConstants.handleCombatTag( + optionalDamagerUUID, + player, + this.fightManager, + this.pluginConfig + ); + } + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java new file mode 100644 index 00000000..057c886a --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java @@ -0,0 +1,89 @@ +package com.eternalcode.combat.crystalpvp; + +import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.fight.FightManager; +import java.util.Optional; +import java.util.UUID; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.data.type.RespawnAnchor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByBlockEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import static com.eternalcode.combat.crystalpvp.CrystalPvpConstants.ANCHOR_METADATA; + +public class RespawnAnchorListener implements Listener { + + private final Plugin plugin; + private final FightManager fightManager; + private final PluginConfig pluginConfig; + + + public RespawnAnchorListener(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) { + this.plugin = plugin; + this.fightManager = fightManager; + this.pluginConfig = pluginConfig; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + void onAnchorInteract(PlayerInteractEvent event) { + Block block = event.getClickedBlock(); + if (block == null || block.getType() != Material.RESPAWN_ANCHOR) { + return; + } + + if (!(block.getBlockData() instanceof RespawnAnchor respawnAnchor)) { + return; + } + + if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + int charges = respawnAnchor.getCharges(); + + ItemStack item = event.getItem(); + boolean isGlowstone = item != null && item.getType() == Material.GLOWSTONE; + + if ((charges > 0 && !isGlowstone) || charges == respawnAnchor.getMaximumCharges()) { + addMetaData(event, block); + } + } + + private void addMetaData(PlayerInteractEvent event, Block block) { + block.setMetadata( + ANCHOR_METADATA, + new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) + ); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + void onAnchorExplosion(EntityDamageByBlockEvent event) { + if (!this.pluginConfig.crystalPvp.tagFromRespawnAnchor) { + return; + } + + if (pluginConfig.settings.ignoredWorlds.contains(event.getEntity().getWorld().getName())) { + return; + } + + if (!(event.getEntity() instanceof Player player)) { + return; + } + + Optional optionalDamagerUniqueId = CrystalPvpConstants.getDamagerUniqueIdFromRespawnAnchor(event); + + if (optionalDamagerUniqueId.isEmpty()) { + return; + } + + CrystalPvpConstants.handleCombatTag(optionalDamagerUniqueId, player, this.fightManager, this.pluginConfig); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/DynamicListener.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/DynamicListener.java new file mode 100644 index 00000000..d970e78b --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/DynamicListener.java @@ -0,0 +1,9 @@ +package com.eternalcode.combat.event; + +import org.bukkit.event.Listener; + +public interface DynamicListener extends Listener { + + void onEvent(E event); + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/EventCaller.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/EventCaller.java deleted file mode 100644 index b2cb5046..00000000 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/EventCaller.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.eternalcode.combat.event; - -import org.bukkit.Server; -import org.bukkit.event.Event; - -public class EventCaller { - - private final Server server; - - public EventCaller(Server server) { - this.server = server; - } - - public T publishEvent(T event) { - this.server.getPluginManager().callEvent(event); - - return event; - } - -} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/EventManager.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/EventManager.java new file mode 100644 index 00000000..4ea65b82 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/EventManager.java @@ -0,0 +1,37 @@ +package com.eternalcode.combat.event; + +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class EventManager { + + private final Plugin plugin; + + public EventManager(Plugin plugin) { + this.plugin = plugin; + } + + public T publishEvent(T event) { + this.plugin.getServer().getPluginManager().callEvent(event); + + return event; + } + + public void subscribe(Listener... listeners) { + for (Listener listener : listeners) { + plugin.getServer().getPluginManager().registerEvents(listener, plugin); + } + } + + public > void subscribe(Class type, EventPriority priority, L listener) { + plugin.getServer().getPluginManager().registerEvents(listener, plugin); + plugin.getServer().getPluginManager().registerEvent(type, listener, priority, (l, event) -> { + if (type.isInstance(event)) { + listener.onEvent(type.cast(event)); + } + }, plugin); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightManagerImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightManagerImpl.java index 67ad0643..a7cdb65f 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightManagerImpl.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightManagerImpl.java @@ -1,6 +1,6 @@ package com.eternalcode.combat.fight; -import com.eternalcode.combat.event.EventCaller; +import com.eternalcode.combat.event.EventManager; import com.eternalcode.combat.fight.event.CauseOfTag; import com.eternalcode.combat.fight.event.CauseOfUnTag; @@ -19,10 +19,10 @@ public class FightManagerImpl implements FightManager { private final Map fights = new ConcurrentHashMap<>(); - private final EventCaller eventCaller; + private final EventManager eventManager; - public FightManagerImpl(EventCaller eventCaller) { - this.eventCaller = eventCaller; + public FightManagerImpl(EventManager eventManager) { + this.eventManager = eventManager; } @Override @@ -38,7 +38,7 @@ public boolean isInCombat(UUID player) { @Override public FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag) { - FightUntagEvent event = this.eventCaller.publishEvent(new FightUntagEvent(player, causeOfUnTag)); + FightUntagEvent event = this.eventManager.publishEvent(new FightUntagEvent(player, causeOfUnTag)); if (event.isCancelled()) { return event; } @@ -55,7 +55,7 @@ public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag) { @ApiStatus.Experimental @Override public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger) { - FightTagEvent event = this.eventCaller.publishEvent(new FightTagEvent(target, causeOfTag)); + FightTagEvent event = this.eventManager.publishEvent(new FightTagEvent(target, causeOfTag)); if (event.isCancelled()) { return event; diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTagCommand.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTagCommand.java index f94749bc..10cd2d3a 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTagCommand.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTagCommand.java @@ -16,7 +16,10 @@ import dev.rollczi.litecommands.annotations.priority.Priority; import dev.rollczi.litecommands.annotations.priority.PriorityValue; import java.time.Duration; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; + import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -24,12 +27,12 @@ public class FightTagCommand { private final FightManager fightManager; - private final NoticeService announcer; + private final NoticeService noticeService; private final PluginConfig config; - public FightTagCommand(FightManager fightManager, NoticeService announcer, PluginConfig config) { + public FightTagCommand(FightManager fightManager, NoticeService noticeService, PluginConfig config) { this.fightManager = fightManager; - this.announcer = announcer; + this.noticeService = noticeService; this.config = config; } @@ -38,7 +41,7 @@ public FightTagCommand(FightManager fightManager, NoticeService announcer, Plugi void status(@Context CommandSender sender, @Arg Player target) { UUID targetUniqueId = target.getUniqueId(); - this.announcer.create() + this.noticeService.create() .notice(this.fightManager.isInCombat(targetUniqueId) ? this.config.messagesSettings.admin.playerInCombat : this.config.messagesSettings.admin.playerNotInCombat @@ -66,7 +69,7 @@ void tag(@Context CommandSender sender, @Arg Player target) { return; } - this.announcer.create() + this.noticeService.create() .notice(this.config.messagesSettings.admin.adminTagPlayer) .placeholder("{PLAYER}", target.getName()) .viewer(sender) @@ -82,7 +85,7 @@ void tagMultiple(@Context CommandSender sender, @Arg Player firstTarget, @Arg Pl if (sender.equals(firstTarget) || sender.equals(secondTarget)) { - this.announcer.create() + this.noticeService.create() .notice(messagesSettings.admin.adminCannotTagSelf) .viewer(sender) .send(); @@ -113,7 +116,7 @@ void tagMultiple(@Context CommandSender sender, @Arg Player firstTarget, @Arg Pl return; } - this.announcer.create() + this.noticeService.create() .notice(messagesSettings.admin.adminTagMultiplePlayers) .placeholder("{FIRST_PLAYER}", firstTarget.getName()) .placeholder("{SECOND_PLAYER}", secondTarget.getName()) @@ -124,13 +127,14 @@ void tagMultiple(@Context CommandSender sender, @Arg Player firstTarget, @Arg Pl @Execute(name = "untag") @Permission("eternalcombat.untag") - void untag(@Context Player sender, @Arg Player target) { + void untag(@Context CommandSender sender, @Arg Player target) { UUID targetUniqueId = target.getUniqueId(); if (!this.fightManager.isInCombat(targetUniqueId)) { - this.announcer.create() - .viewer(sender) + this.noticeService.create() .notice(this.config.messagesSettings.admin.adminPlayerNotInCombat) + .placeholder("{PLAYER}", target.getName()) + .viewer(sender) .send(); return; } @@ -141,16 +145,33 @@ void untag(@Context Player sender, @Arg Player target) { } - this.announcer.create() + this.noticeService.create() .notice(this.config.messagesSettings.admin.adminUntagPlayer) .placeholder("{PLAYER}", target.getName()) .viewer(sender) .send(); } + @Execute(name = "untagall") + @Permission("eternalcombat.untagall") + void untagAll(@Context CommandSender sender) { + int combatPlayersSize = this.fightManager.getFights().size(); + + this.fightManager.getFights().stream() + .map(FightTag::getTaggedPlayer) + .collect(Collectors.toSet()) + .forEach(uuid -> this.fightManager.untag(uuid, CauseOfUnTag.COMMAND)); + + this.noticeService.create() + .notice(this.config.messagesSettings.admin.adminUntagAll) + .placeholder("{COUNT}", String.valueOf(combatPlayersSize)) + .viewer(sender) + .send(); + } + private void tagoutReasonHandler(CommandSender sender, CancelTagReason cancelReason, MessagesSettings messagesSettings) { if (cancelReason == CancelTagReason.TAGOUT) { - this.announcer.create() + this.noticeService.create() .notice(messagesSettings.admin.adminTagOutCanceled) .viewer(sender) .send(); diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java index b7fe6461..76435166 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java @@ -15,13 +15,13 @@ public class FightTask implements Runnable { private final Server server; private final PluginConfig config; private final FightManager fightManager; - private final NoticeService announcer; + private final NoticeService noticeService; - public FightTask(Server server, PluginConfig config, FightManager fightManager, NoticeService announcer) { + public FightTask(Server server, PluginConfig config, FightManager fightManager, NoticeService noticeService) { this.server = server; this.config = config; this.fightManager = fightManager; - this.announcer = announcer; + this.noticeService = noticeService; } @Override @@ -42,10 +42,10 @@ public void run() { Duration remaining = fightTag.getRemainingDuration(); - this.announcer.create() + this.noticeService.create() .player(player.getUniqueId()) .notice(this.config.messagesSettings.combatNotification) - .placeholder("{TIME}", DurationUtil.format(remaining)) + .placeholder("{TIME}", DurationUtil.format(remaining, this.config.messagesSettings.withoutMillis)) .send(); } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightActionBlockerController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightActionBlockerController.java index 9aeb8637..4b6e5e9d 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightActionBlockerController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightActionBlockerController.java @@ -18,21 +18,23 @@ import org.bukkit.event.entity.EntityToggleGlideEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerToggleFlightEvent; import java.util.List; import java.util.UUID; +import org.bukkit.util.StringUtil; public class FightActionBlockerController implements Listener { private final FightManager fightManager; - private final NoticeService announcer; + private final NoticeService noticeService; private final PluginConfig config; private final Server server; - public FightActionBlockerController(FightManager fightManager, NoticeService announcer, PluginConfig config, Server server) { + public FightActionBlockerController(FightManager fightManager, NoticeService noticeService, PluginConfig config, Server server) { this.fightManager = fightManager; - this.announcer = announcer; + this.noticeService = noticeService; this.config = config; this.server = server; } @@ -59,7 +61,7 @@ void onPlace(BlockPlaceEvent event) { if (isPlacementBlocked && specificBlocksToPreventPlacing.isEmpty()) { event.setCancelled(true); - this.announcer.create() + this.noticeService.create() .player(uniqueId) .notice(this.config.messagesSettings.blockPlacingBlockedDuringCombat) .placeholder("{Y}", String.valueOf(this.config.blockPlacement.blockPlacementYCoordinate)) @@ -73,7 +75,7 @@ void onPlace(BlockPlaceEvent event) { if (isPlacementBlocked && isBlockInDisabledList) { event.setCancelled(true); - this.announcer.create() + this.noticeService.create() .player(uniqueId) .notice(this.config.messagesSettings.blockPlacingBlockedDuringCombat) .placeholder("{Y}", String.valueOf(this.config.blockPlacement.blockPlacementYCoordinate)) @@ -109,6 +111,26 @@ void onToggleGlide(EntityToggleGlideEvent event) { } } + @EventHandler + void onMoveWhileGliding(PlayerMoveEvent event) { + if (!this.config.combat.disableElytraUsage) { + return; + } + + Player player = event.getPlayer(); + UUID uniqueId = player.getUniqueId(); + + if (!this.fightManager.isInCombat(uniqueId)) { + return; + } + + if (player.isGliding()) { + player.setGliding(false); + } + } + + + @EventHandler void onFly(PlayerToggleFlightEvent event) { if (!this.config.combat.disableFlying) { @@ -178,7 +200,7 @@ void onOpenInventory(InventoryOpenEvent event) { event.setCancelled(true); - this.announcer.create() + this.noticeService.create() .player(uniqueId) .notice(this.config.messagesSettings.inventoryBlockedDuringCombat) .send(); @@ -194,18 +216,18 @@ void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { return; } - String command = event.getMessage().split(" ")[0].substring(1).toLowerCase(); + String command = event.getMessage().substring(1); - boolean isMatchCommand = this.config.commands.restrictedCommands.stream() - .anyMatch(command::startsWith); + boolean isAnyMatch = this.config.commands.restrictedCommands.stream() + .anyMatch(restrictedCommand -> StringUtil.startsWithIgnoreCase(command, restrictedCommand)); WhitelistBlacklistMode mode = this.config.commands.commandRestrictionMode; - boolean shouldCancel = mode.shouldBlock(isMatchCommand); + boolean shouldCancel = mode.shouldBlock(isAnyMatch); if (shouldCancel) { event.setCancelled(true); - this.announcer.create() + this.noticeService.create() .player(playerUniqueId) .notice(this.config.messagesSettings.commandDisabledDuringCombat) .send(); diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightMessageController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightMessageController.java index dae9b365..5c22d8be 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightMessageController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightMessageController.java @@ -14,13 +14,13 @@ public class FightMessageController implements Listener { private final FightManager fightManager; - private final NoticeService announcer; + private final NoticeService noticeService; private final PluginConfig config; private final Server server; - public FightMessageController(FightManager fightManager, NoticeService announcer, PluginConfig config, Server server) { + public FightMessageController(FightManager fightManager, NoticeService noticeService, PluginConfig config, Server server) { this.fightManager = fightManager; - this.announcer = announcer; + this.noticeService = noticeService; this.config = config; this.server = server; } @@ -37,7 +37,7 @@ void onTag(FightTagEvent event) { return; } - this.announcer.create() + this.noticeService.create() .player(player.getUniqueId()) .notice(this.config.messagesSettings.playerTagged) .send(); @@ -51,7 +51,7 @@ void onUnTag(FightUntagEvent event) { throw new IllegalStateException("Player cannot be null!"); } - this.announcer.create() + this.noticeService.create() .player(player.getUniqueId()) .notice(this.config.messagesSettings.playerUntagged) .send(); diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightTagController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightTagController.java index 5fe8d995..4dc6ec51 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightTagController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightTagController.java @@ -31,6 +31,11 @@ public FightTagController(FightManager fightManager, PluginConfig config) { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + // Thorns are ignored because both users should be in combat from hitting each other + if (event.getCause().equals(EntityDamageEvent.DamageCause.THORNS)) { + return; + } + if (!(event.getEntity() instanceof Player attackedPlayerByPerson)) { return; } @@ -51,10 +56,6 @@ void onEntityDamageByEntity(EntityDamageByEntityEvent event) { return; } - Duration combatTime = this.config.settings.combatTimerDuration; - UUID attackedUniqueId = attackedPlayerByPerson.getUniqueId(); - UUID attackerUniqueId = attacker.getUniqueId(); - if (this.cannotBeTagged(attacker)) { return; } @@ -75,10 +76,15 @@ void onEntityDamageByEntity(EntityDamageByEntityEvent event) { } } + Duration combatTime = this.config.settings.combatTimerDuration; + UUID attackedUniqueId = attackedPlayerByPerson.getUniqueId(); + UUID attackerUniqueId = attacker.getUniqueId(); + this.fightManager.tag(attackedUniqueId, combatTime, CauseOfTag.PLAYER, attackerUniqueId); this.fightManager.tag(attackerUniqueId, combatTime, CauseOfTag.PLAYER, attackedUniqueId); } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) void onEntityDamage(EntityDamageEvent event) { if (!this.config.combat.enableDamageCauseLogging) { @@ -97,8 +103,12 @@ void onEntityDamage(EntityDamageEvent event) { return; } - Duration combatTime = this.config.settings.combatTimerDuration; + boolean hasBypass = player.hasPermission("eternalcombat.bypass"); + if (hasBypass) { + return; + } + Duration combatTime = this.config.settings.combatTimerDuration; UUID uuid = player.getUniqueId(); List damageCauses = this.config.combat.loggedDamageCauses; @@ -115,6 +125,7 @@ void onEntityDamage(EntityDamageEvent event) { this.fightManager.tag(uuid, combatTime, CauseOfTag.NON_PLAYER); } + @Nullable Player getDamager(EntityDamageByEntityEvent event) { if (event.getDamager() instanceof Player damager) { @@ -135,14 +146,17 @@ private boolean isPlayerInDisabledWorld(Player player) { } private boolean cannotBeTagged(Player player) { - if (player.getGameMode().equals(GameMode.CREATIVE) && this.config.admin.excludeCreativePlayersFromCombat) { + if (this.config.admin.excludeAdminsFromCombat && player.hasPermission("eternalcombat.bypass")) { return true; } - if (player.isOp() && this.config.admin.excludeAdminsFromCombat) { + if (this.config.admin.excludeAdminsFromCombat && player.isOp()) { return true; } - return false; + return this.config.admin.excludeCreativePlayersFromCombat && player.getGameMode() == GameMode.CREATIVE; } + + + } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java index aaa8000b..1d9faaa6 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java @@ -1,12 +1,18 @@ package com.eternalcode.combat.fight.controller; +import com.eternalcode.combat.crystalpvp.CrystalPvpConstants; import com.eternalcode.combat.fight.FightManager; import com.eternalcode.combat.fight.event.CauseOfUnTag; import com.eternalcode.combat.config.implementation.PluginConfig; import com.eternalcode.combat.fight.logout.LogoutService; +import java.util.Optional; +import java.util.UUID; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByBlockEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.PlayerDeathEvent; public class FightUnTagController implements Listener { @@ -26,33 +32,55 @@ void onPlayerDeath(PlayerDeathEvent event) { Player player = event.getEntity(); Player killer = player.getKiller(); + UUID playerUniqueId = player.getUniqueId(); + Optional optionalKiller; + + if (killer != null) { + optionalKiller = Optional.of(killer.getUniqueId()); + } else { + optionalKiller = this.getCrystalKiller(player); + } + if (!this.fightManager.isInCombat(player.getUniqueId())) { return; } - CauseOfUnTag cause = this.getDeathCause(player, killer); + CauseOfUnTag cause = this.getDeathCause(playerUniqueId, optionalKiller.orElse(null)); this.fightManager.untag(player.getUniqueId(), cause); - if (killer != null && this.config.combat.releaseAttackerOnVictimDeath) { - this.fightManager.untag(killer.getUniqueId(), CauseOfUnTag.ATTACKER_RELEASE); + if (optionalKiller.isPresent() && this.config.combat.releaseAttackerOnVictimDeath) { + this.fightManager.untag(optionalKiller.get(), CauseOfUnTag.ATTACKER_RELEASE); } } - private CauseOfUnTag getDeathCause(Player player, Player killer) { - if (this.logoutService.hasLoggedOut(player.getUniqueId())) { + private CauseOfUnTag getDeathCause(UUID playerUniqueId, UUID killerUniqueId) { + if (this.logoutService.hasLoggedOut(playerUniqueId)) { return CauseOfUnTag.LOGOUT; } - if (killer == null) { + if (killerUniqueId == null) { return CauseOfUnTag.DEATH; } - if (this.fightManager.isInCombat(killer.getUniqueId())) { + if (this.fightManager.isInCombat(killerUniqueId)) { return CauseOfUnTag.DEATH_BY_PLAYER; } return CauseOfUnTag.DEATH; } + private Optional getCrystalKiller(Player player) { + EntityDamageEvent lastDamageCause = player.getLastDamageCause(); + + if (lastDamageCause instanceof EntityDamageByBlockEvent damageByBlockEvent) { + return CrystalPvpConstants.getDamagerUniqueIdFromRespawnAnchor(damageByBlockEvent); + } + + if (lastDamageCause instanceof EntityDamageByEntityEvent damageByEntityEvent) { + return CrystalPvpConstants.getDamagerUniqueIdFromEndCrystal(damageByEntityEvent); + } + + return Optional.empty(); + } } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropController.java index 1c3ce344..3614c6e5 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropController.java @@ -1,41 +1,60 @@ package com.eternalcode.combat.fight.drop; +import com.eternalcode.combat.event.DynamicListener; import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.commons.adventure.AdventureUtil; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerRespawnEvent; +import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.SkullMeta; import java.util.List; import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; -public class DropController implements Listener { +public class DropController implements DynamicListener { private final DropService dropService; private final DropKeepInventoryService keepInventoryManager; private final DropSettings dropSettings; private final FightManager fightManager; - - public DropController(DropService dropService, DropKeepInventoryService keepInventoryManager, DropSettings dropSettings, FightManager fightManager) { + private final MiniMessage miniMessage; + + public DropController( + DropService dropService, + DropKeepInventoryService keepInventoryManager, + DropSettings dropSettings, + FightManager fightManager, MiniMessage miniMessage + ) { this.dropService = dropService; this.keepInventoryManager = keepInventoryManager; this.dropSettings = dropSettings; this.fightManager = fightManager; + this.miniMessage = miniMessage; } - @EventHandler(priority = EventPriority.LOW) - void onPlayerDeath(PlayerDeathEvent event) { + @Override + public void onEvent(PlayerDeathEvent event) { Player player = event.getEntity(); - + UUID uuid = player.getUniqueId(); DropType dropType = this.dropSettings.dropType; + boolean inCombat = this.fightManager.isInCombat(uuid); + + if (shouldHeadDrop(inCombat)) { + addHeadDrop(event, player); + } - if (dropType == DropType.UNCHANGED || !this.fightManager.isInCombat(player.getUniqueId())) { + if (dropType == DropType.UNCHANGED || !inCombat) { return; } + List drops = event.getDrops(); Drop drop = Drop.builder() @@ -54,15 +73,63 @@ void onPlayerDeath(PlayerDeathEvent event) { drops.clear(); drops.addAll(result.droppedItems()); - this.keepInventoryManager.addItems(player.getUniqueId(), result.removedItems()); + this.keepInventoryManager.addItems(uuid, result.removedItems()); if (this.dropSettings.affectExperience) { - event.setDroppedExp(drop.getDroppedExp()); + event.setDroppedExp(result.droppedExp()); + } + } + + private boolean shouldHeadDrop(boolean inCombat) { + if (!this.dropSettings.headDropEnabled) { + return false; + } + + if (this.dropSettings.headDropOnlyInCombat && inCombat) { + return true; + } + + if (this.dropSettings.headDropChance <= 0.0) { + return false; } + + return ThreadLocalRandom.current().nextDouble(0, 100) <= this.dropSettings.headDropChance; + } + + private void addHeadDrop(PlayerDeathEvent event, Player player) { + ItemStack head = new ItemStack(Material.PLAYER_HEAD); + + if (head.getItemMeta() instanceof SkullMeta meta) { + String killerName = player.getKiller() != null ? player.getKiller().getName() : "Unknown"; + String displayName = this.dropSettings.headDropDisplayName + .replace("{PLAYER}", player.getName()) + .replace("{KILLER}", killerName); + + meta.setOwningPlayer(player); + meta.setDisplayName(AdventureUtil.SECTION_SERIALIZER.serialize(miniMessage.deserialize(displayName))); + + if (!this.dropSettings.headDropLore.isEmpty()) { + List lore = this.dropSettings.headDropLore.stream() + .map(line -> line + .replace("{PLAYER}", player.getName()) + .replace("{KILLER}", killerName)) + .map(line -> AdventureUtil.SECTION_SERIALIZER.serialize(miniMessage.deserialize(line))) + .toList(); + + meta.setLore(lore); + } + + meta.addEnchant(Enchantment.LURE, 1, true); + meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); + + head.setItemMeta(meta); + } + + event.getDrops().add(head); } @EventHandler - void onPlayerRespawn(PlayerRespawnEvent event) { + public void onPlayerRespawn(PlayerRespawnEvent event) { Player player = event.getPlayer(); UUID playerUniqueId = player.getUniqueId(); @@ -75,5 +142,4 @@ void onPlayerRespawn(PlayerRespawnEvent event) { playerInventory.addItem(itemsToGive); } } - } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropSettings.java index 088f290d..d292fac7 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropSettings.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropSettings.java @@ -2,13 +2,27 @@ import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; +import org.bukkit.event.EventPriority; + +import java.util.List; public class DropSettings extends OkaeriConfig { + + @Comment({ + "# The event priority at which the head drop logic should run.", + "# Options: LOWEST, LOW, NORMAL, HIGH, HIGHEST, MONITOR", + "# Useful if you want to control when drops are processed relative to other plugins.", + "# Default: NORMAL" + }) + public EventPriority dropEventPriority = EventPriority.NORMAL; + @Comment({ "# UNCHANGED - The default way of item drop defined by the engine", "# PERCENT - Drops a fixed percentage of items", - "# PLAYERS_HEALTH - Drops inverted percentage of the player's health (i.e. if the player has, for example, 80% HP, he will drop 20% of items. Only works when the player escapes from combat by quiting game)" + "# PLAYERS_HEALTH - Drops inverted percentage of the player's health (i.e. if the player has, for example, 80% HP, he will drop 20% of items. Only works when the player escapes from combat by quiting game)", + "# ", + "# ** REQUIRES RESTART IN ORDER TO TAKE EFFECT **" }) public DropType dropType = DropType.UNCHANGED; @@ -20,4 +34,39 @@ public class DropSettings extends OkaeriConfig { @Comment("# Does the drop modification affect the experience drop?") public boolean affectExperience = false; + + @Comment({ + "", + "# If true, players can drop their head on death based on chance settings below." + }) + public boolean headDropEnabled = false; + + @Comment({ + "# Chance for a head to drop on death (0-100).", + "# Set to 0 to disable even if feature is enabled.", + "# Example: 25.0 means 25% chance." + }) + public double headDropChance = 0.0; + + @Comment({ + "# Only drop the head if the player was in combat at time of death." + }) + public boolean headDropOnlyInCombat = true; + + @Comment({ + "# The display name of the dropped head.", + "# Placeholders: {PLAYER}, {KILLER}", + "# Example: \"{PLAYER}'s Head\"" + }) + public String headDropDisplayName = "{PLAYER}'s Head"; + + @Comment({ + "# Lore lines shown on the head item.", + "# Placeholders: {PLAYER}, {KILLER}", + "# Set to [] to disable lore entirely." + }) + public List headDropLore = List.of( + "Slain by {KILLER}", + "Collected in battle" + ); } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackOutsideRegionGenerator.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackOutsideRegionGenerator.java index 13c48872..75cc3801 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackOutsideRegionGenerator.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackOutsideRegionGenerator.java @@ -8,27 +8,11 @@ import java.util.Set; import java.util.TreeMap; import org.bukkit.Location; -import org.bukkit.World; class KnockbackOutsideRegionGenerator { - private record Point2D(int x, int z) { - - Location toLocation(Location location) { - World world = location.getWorld(); - int y = world.getHighestBlockYAt(x, z) + 1; - - return new Location(world, x, y, z, location.getYaw(), location.getPitch()); - } - - private static Point2D from(Location location) { - return new Point2D(location.getBlockX(), location.getBlockZ()); - } - - } - - static Location generate(Location min, Location max, Location playerLocation) { - NavigableMap> points = generatePoints(Point2D.from(min), Point2D.from(max), Point2D.from(playerLocation)); + static Location generate(Point2D min, Point2D max, Location playerLocation) { + NavigableMap> points = generatePoints(min, max, Point2D.from(playerLocation)); NavigableMap distances = new TreeMap<>(); double totalWeight = 0; diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackRegionController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackRegionController.java index ae092a2f..e4c3143b 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackRegionController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackRegionController.java @@ -18,14 +18,14 @@ public class KnockbackRegionController implements Listener { - private final NoticeService announcer; + private final NoticeService noticeService; private final RegionProvider regionProvider; private final FightManager fightManager; private final KnockbackService knockbackService; private final Server server; - public KnockbackRegionController(NoticeService announcer, RegionProvider regionProvider, FightManager fightManager, KnockbackService knockbackService, Server server) { - this.announcer = announcer; + public KnockbackRegionController(NoticeService noticeService, RegionProvider regionProvider, FightManager fightManager, KnockbackService knockbackService, Server server) { + this.noticeService = noticeService; this.regionProvider = regionProvider; this.fightManager = fightManager; this.knockbackService = knockbackService; @@ -64,7 +64,7 @@ void onPlayerMove(PlayerMoveEvent event) { this.knockbackService.knockbackLater(region, player, Duration.ofMillis(50)); } - this.announcer.create() + this.noticeService.create() .player(player.getUniqueId()) .notice(config -> config.messagesSettings.cantEnterOnRegion) .send(); @@ -82,7 +82,7 @@ void onPlayerTeleport(PlayerTeleportEvent event) { if (this.regionProvider.isInRegion(targetLocation)) { event.setCancelled(true); - this.announcer.create() + this.noticeService.create() .player(player.getUniqueId()) .notice(config -> config.messagesSettings.cantEnterOnRegion) .send(); @@ -105,7 +105,7 @@ void onTag(FightTagEvent event) { this.knockbackService.knockback(region, player); this.knockbackService.forceKnockbackLater(player, region); - this.announcer.create() + this.noticeService.create() .player(player.getUniqueId()) .notice(config -> config.messagesSettings.cantEnterOnRegion) .send(); diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackService.java index 5aeb4742..6f2dd28a 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackService.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackService.java @@ -1,11 +1,14 @@ package com.eternalcode.combat.fight.knockback; import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.region.Point; import com.eternalcode.combat.region.Region; +import com.eternalcode.combat.region.RegionProvider; import com.eternalcode.commons.scheduler.Scheduler; import java.time.Duration; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.UUID; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -16,16 +19,18 @@ public final class KnockbackService { private final PluginConfig config; private final Scheduler scheduler; + private final RegionProvider regionProvider; private final Map insideRegion = new HashMap<>(); - public KnockbackService(PluginConfig config, Scheduler scheduler) { + public KnockbackService(PluginConfig config, Scheduler scheduler, RegionProvider regionProvider) { this.config = config; this.scheduler = scheduler; + this.regionProvider = regionProvider; } public void knockbackLater(Region region, Player player, Duration duration) { - this.scheduler.laterSync(() -> this.knockback(region, player), duration); + this.scheduler.runLater(() -> this.knockback(region, player), duration); } public void forceKnockbackLater(Player player, Region region) { @@ -34,21 +39,33 @@ public void forceKnockbackLater(Player player, Region region) { } insideRegion.put(player.getUniqueId(), region); - scheduler.laterSync(() -> { + scheduler.runLater(() -> { insideRegion.remove(player.getUniqueId()); Location playerLocation = player.getLocation(); - if (!region.contains(playerLocation)) { + if (!region.contains(playerLocation) && !regionProvider.isInRegion(playerLocation)) { return; } - Location location = KnockbackOutsideRegionGenerator.generate(region.getMin(), region.getMax(), playerLocation); + Location location = generate(playerLocation, Point2D.from(region.getMin()), Point2D.from(region.getMax())); + player.teleport(location, PlayerTeleportEvent.TeleportCause.PLUGIN); }, this.config.knockback.forceDelay); } + private Location generate(Location playerLocation, Point2D minX, Point2D maxX) { + Location location = KnockbackOutsideRegionGenerator.generate(minX, maxX, playerLocation); + Optional otherRegion = regionProvider.getRegion(location); + if (otherRegion.isPresent()) { + Region region = otherRegion.get(); + return generate(playerLocation, minX.min(region.getMin()), maxX.max(region.getMax())); + } + + return location; + } + public void knockback(Region region, Player player) { - Location centerOfRegion = region.getCenter(); - Location subtract = player.getLocation().subtract(centerOfRegion); + Point point = region.getCenter(); + Location subtract = player.getLocation().subtract(point.x(), 0, point.z()); Vector knockbackVector = new Vector(subtract.getX(), 0, subtract.getZ()).normalize(); double multiplier = this.config.knockback.multiplier; diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/Point2D.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/Point2D.java new file mode 100644 index 00000000..ec31f567 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/Point2D.java @@ -0,0 +1,27 @@ +package com.eternalcode.combat.fight.knockback; + +import org.bukkit.Location; +import org.bukkit.World; + +record Point2D(int x, int z) { + + Location toLocation(Location location) { + World world = location.getWorld(); + int y = world.getHighestBlockYAt(x, z) + 1; + + return new Location(world, x, y, z, location.getYaw(), location.getPitch()); + } + + static Point2D from(Location location) { + return new Point2D(location.getBlockX(), location.getBlockZ()); + } + + public Point2D min(Location min) { + return new Point2D(Math.min(this.x, min.getBlockX()), Math.min(this.z, min.getBlockZ())); + } + + public Point2D max(Location max) { + return new Point2D(Math.max(this.x, max.getBlockX()), Math.max(this.z, max.getBlockZ())); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java index 44f33f89..d363b196 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java @@ -1,46 +1,81 @@ package com.eternalcode.combat.fight.logout; import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.event.DynamicListener; import com.eternalcode.combat.fight.FightManager; import com.eternalcode.combat.notification.NoticeService; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerKickEvent; import org.bukkit.event.player.PlayerQuitEvent; -public class LogoutController implements Listener { +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class LogoutController implements DynamicListener { private final FightManager fightManager; private final LogoutService logoutService; - private final NoticeService announcer; + private final NoticeService noticeService; private final PluginConfig config; + private final Set shouldNotPunishOnQuit = Collections.newSetFromMap(new ConcurrentHashMap<>()); - public LogoutController(FightManager fightManager, LogoutService logoutService, NoticeService announcer, PluginConfig config) { + public LogoutController(FightManager fightManager, LogoutService logoutService, NoticeService noticeService, PluginConfig config) { this.fightManager = fightManager; this.logoutService = logoutService; - this.announcer = announcer; + this.noticeService = noticeService; this.config = config; } - @EventHandler(priority = EventPriority.HIGH) - void onQuit(PlayerQuitEvent event) { + @EventHandler(priority = EventPriority.LOWEST) + private void onKick(PlayerKickEvent event) { + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + + if (!this.fightManager.isInCombat(uuid)) { + return; + } + + String reason = event.getReason().trim(); + List whitelist = this.config.combat.whitelistedKickReasons; + + if (whitelist.isEmpty()) { + return; + } + + for (String whitelisted : whitelist) { + if (reason.toLowerCase().contains(whitelisted.toLowerCase())) { + this.shouldNotPunishOnQuit.add(uuid); + return; + } + } + } + + @Override + public void onEvent(PlayerQuitEvent event) { Player player = event.getPlayer(); if (!this.fightManager.isInCombat(player.getUniqueId())) { return; } + if (this.shouldNotPunishOnQuit.remove(player.getUniqueId())) { + return; + } + this.logoutService.punishForLogout(player); player.setHealth(0.0); - this.announcer.create() + this.noticeService.create() .notice(this.config.messagesSettings.playerLoggedOutDuringCombat) .placeholder("{PLAYER}", player.getName()) .all() .send(); - } } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java index 56a8c28d..bfd35700 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java @@ -3,103 +3,102 @@ import com.eternalcode.combat.fight.FightManager; import com.eternalcode.combat.notification.NoticeService; import com.eternalcode.combat.util.DurationUtil; +import java.time.Duration; +import java.util.UUID; import org.bukkit.Material; import org.bukkit.entity.EnderPearl; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; import org.bukkit.inventory.ItemStack; -import java.time.Duration; -import java.util.UUID; - public class FightPearlController implements Listener { private final FightPearlSettings settings; - private final NoticeService announcer; + private final NoticeService noticeService; private final FightManager fightManager; private final FightPearlService fightPearlService; - public FightPearlController(FightPearlSettings settings, NoticeService announcer, FightManager fightManager, FightPearlService fightPearlService) { + public FightPearlController( + FightPearlSettings settings, + NoticeService noticeService, + FightManager fightManager, + FightPearlService fightPearlService + ) { this.settings = settings; - this.announcer = announcer; + this.noticeService = noticeService; this.fightManager = fightManager; this.fightPearlService = fightPearlService; } - @EventHandler - void onInteract(PlayerInteractEvent event) { - Player player = event.getPlayer(); - UUID uniqueId = player.getUniqueId(); - - if (!this.settings.pearlThrowBlocked) { + @EventHandler(priority = EventPriority.HIGHEST) + public void onPearlThrow(ProjectileLaunchEvent event) { + if (!(event.getEntity() instanceof EnderPearl)) { return; } - if (!this.fightManager.isInCombat(uniqueId)) { + if (!(event.getEntity().getShooter() instanceof Player player)) { return; } - ItemStack item = event.getItem(); - if (item == null || item.getType() != Material.ENDER_PEARL) { - return; - } + UUID playerId = player.getUniqueId(); - Action action = event.getAction(); - if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) { + if (!this.fightManager.isInCombat(playerId)) { return; } - if (this.settings.pearlThrowDelay.isZero()) { + if (this.settings.pearlThrowDisabledDuringCombat) { event.setCancelled(true); - this.announcer.create() - .player(uniqueId) + this.noticeService.create() + .player(playerId) .notice(this.settings.pearlThrowBlockedDuringCombat) .send(); - return; } - if (this.fightPearlService.hasDelay(uniqueId)) { - event.setCancelled(true); - - Duration remainingPearlDelay = this.fightPearlService.getRemainingDelay(uniqueId); - - this.announcer.create() - .player(uniqueId) - .notice(this.settings.pearlThrowBlockedDelayDuringCombat) - .placeholder("{TIME}", DurationUtil.format(remainingPearlDelay)) - .send(); - - return; + if (this.settings.pearlCooldownEnabled) { + handlePearlCooldown(event, player, playerId); } - - this.fightPearlService.markDelay(uniqueId); } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - void onEntityDamage(EntityDamageByEntityEvent event) { + public void onPearlDamage(EntityDamageByEntityEvent event) { if (this.settings.pearlThrowDamageEnabled) { return; } - if (!(event.getEntity() instanceof Player)) { + if (!(event.getEntity() instanceof Player) || + !(event.getDamager() instanceof EnderPearl) || + event.getCause() != EntityDamageEvent.DamageCause.FALL) { return; } - if (!(event.getDamager() instanceof EnderPearl)) { + event.setDamage(0.0); + } + + private void handlePearlCooldown(ProjectileLaunchEvent event, Player player, UUID playerId) { + if (this.settings.pearlThrowDelay.isZero()) { return; } - if (event.getCause() != EntityDamageEvent.DamageCause.FALL) { + if (this.fightPearlService.hasDelay(playerId)) { + event.setCancelled(true); + Duration remainingDelay = this.fightPearlService.getRemainingDelay(playerId); + + this.noticeService.create() + .player(playerId) + .notice(this.settings.pearlThrowBlockedDelayDuringCombat) + .placeholder("{TIME}", DurationUtil.format(remainingDelay)) + .send(); return; } - event.setDamage(0.0D); + this.fightPearlService.markDelay(playerId); + int cooldownTicks = (int) (this.settings.pearlThrowDelay.toMillis() / 50); + player.setCooldown(Material.ENDER_PEARL, cooldownTicks); } } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlServiceImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlServiceImpl.java index 4101a87b..584d275d 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlServiceImpl.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlServiceImpl.java @@ -10,32 +10,41 @@ public class FightPearlServiceImpl implements FightPearlService { private final FightPearlSettings pearlSettings; - private final Cache pearlDelays; + private final Cache pearlStartTimes; public FightPearlServiceImpl(FightPearlSettings pearlSettings) { this.pearlSettings = pearlSettings; - this.pearlDelays = Caffeine.newBuilder() + this.pearlStartTimes = Caffeine.newBuilder() .expireAfterWrite(pearlSettings.pearlThrowDelay) .build(); } @Override public void markDelay(UUID uuid) { - this.pearlDelays.put(uuid, Instant.now().plus(this.pearlSettings.pearlThrowDelay)); + this.pearlStartTimes.put(uuid, Instant.now()); } @Override public boolean hasDelay(UUID uuid) { - return Instant.now().isBefore(this.getDelay(uuid)); + return this.pearlStartTimes.getIfPresent(uuid) != null; } @Override public Duration getRemainingDelay(UUID uuid) { - return Duration.between(Instant.now(), this.getDelay(uuid)); + Instant startTime = this.pearlStartTimes.getIfPresent(uuid); + if (startTime == null) { + return Duration.ZERO; + } + + Duration elapsed = Duration.between(startTime, Instant.now()); + Duration remaining = this.pearlSettings.pearlThrowDelay.minus(elapsed); + + return remaining.isNegative() ? Duration.ZERO : remaining; } @Override public Instant getDelay(UUID uuid) { - return this.pearlDelays.asMap().getOrDefault(uuid, Instant.MIN); + Instant startTime = this.pearlStartTimes.getIfPresent(uuid); + return startTime != null ? startTime.plus(this.pearlSettings.pearlThrowDelay) : Instant.MIN; } } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlSettings.java index 5042ee04..4cb29803 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlSettings.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlSettings.java @@ -12,13 +12,18 @@ public class FightPearlSettings extends OkaeriConfig { @Comment({ "# Is pearl damage to be enabled?", "# This will work globally" }) public boolean pearlThrowDamageEnabled = true; - @Comment("# Set true, If you want to lock pearls during the combat") - public boolean pearlThrowBlocked = false; + @Comment({ + "# Set true, If you want to disable throwing pearls during combat", + "# This will work globally, but can be overridden by region settings" + }) + public boolean pearlThrowDisabledDuringCombat = true; + + @Comment("# Set true, If you want add cooldown to pearls") + public boolean pearlCooldownEnabled = false; @Comment({ "# Block throwing pearls with delay?", - "# If you set this to for example 3s, player will have to wait 3 seconds before throwing another pearl", - "# Set to 0 to disable" + "# If you set this to for example 3s, player will have to wait 3 seconds before throwing another pearl" }) public Duration pearlThrowDelay = Duration.ofSeconds(3); diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutCommand.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutCommand.java index 09ea32f9..56ef3898 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutCommand.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutCommand.java @@ -17,16 +17,16 @@ public class FightTagOutCommand { private final FightTagOutService fightTagOutService; - private final NoticeService announcer; + private final NoticeService noticeService; private final PluginConfig config; public FightTagOutCommand( FightTagOutService fightTagOutService, - NoticeService announcer, + NoticeService noticeService, PluginConfig config ) { this.fightTagOutService = fightTagOutService; - this.announcer = announcer; + this.noticeService = noticeService; this.config = config; } @@ -36,7 +36,7 @@ void tagout(@Context Player sender, @Arg Duration time) { this.fightTagOutService.tagOut(targetUniqueId, time); - this.announcer.create() + this.noticeService.create() .notice(this.config.messagesSettings.admin.adminTagOutSelf) .placeholder("{TIME}", DurationUtil.format(time)) .viewer(sender) @@ -50,14 +50,14 @@ void tagout(@Context Player sender, @Arg Player target, @Arg Duration time) { this.fightTagOutService.tagOut(targetUniqueId, time); - this.announcer.create() + this.noticeService.create() .notice(this.config.messagesSettings.admin.adminTagOut) .placeholder("{PLAYER}", target.getName()) .placeholder("{TIME}", DurationUtil.format(time)) .viewer(sender) .send(); - this.announcer.create() + this.noticeService.create() .notice(this.config.messagesSettings.admin.playerTagOut) .placeholder("{TIME}", DurationUtil.format(time)) .player(target.getUniqueId()) @@ -73,14 +73,14 @@ void untagout(@Context Player sender, @Arg Player target) { if (!targetUniqueId.equals(sender.getUniqueId())) { - this.announcer.create() + this.noticeService.create() .notice(this.config.messagesSettings.admin.adminTagOutOff) .placeholder("{PLAYER}", target.getName()) .viewer(sender) .send(); } - this.announcer.create() + this.noticeService.create() .notice(this.config.messagesSettings.admin.playerTagOutOff) .player(targetUniqueId) .send(); @@ -92,7 +92,7 @@ void untagout(@Context Player sender) { this.fightTagOutService.unTagOut(senderUniqueId); - this.announcer.create() + this.noticeService.create() .notice(this.config.messagesSettings.admin.playerTagOutOff) .viewer(sender) .send(); diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/InvalidUsageHandlerImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/InvalidUsageHandlerImpl.java index 4d60fadd..f29d2c8c 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/InvalidUsageHandlerImpl.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/InvalidUsageHandlerImpl.java @@ -12,11 +12,11 @@ public class InvalidUsageHandlerImpl implements InvalidUsageHandler { private final PluginConfig config; - private final NoticeService announcer; + private final NoticeService noticeService; - public InvalidUsageHandlerImpl(PluginConfig config, NoticeService announcer) { + public InvalidUsageHandlerImpl(PluginConfig config, NoticeService noticeService) { this.config = config; - this.announcer = announcer; + this.noticeService = noticeService; } @Override @@ -28,7 +28,7 @@ public void handle( Schematic schematic = commandSenderInvalidUsage.getSchematic(); for (String usage : schematic.all()) { - this.announcer.create() + this.noticeService.create() .viewer(invocation.sender()) .notice(this.config.messagesSettings.invalidCommandUsage) .placeholder("{USAGE}", usage) diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/MissingPermissionHandlerImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/MissingPermissionHandlerImpl.java index f5e6862b..e32dbe3c 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/MissingPermissionHandlerImpl.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/MissingPermissionHandlerImpl.java @@ -11,11 +11,11 @@ public class MissingPermissionHandlerImpl implements MissingPermissionsHandler { private final PluginConfig config; - private final NoticeService announcer; + private final NoticeService noticeService; - public MissingPermissionHandlerImpl(PluginConfig config, NoticeService announcer) { + public MissingPermissionHandlerImpl(PluginConfig config, NoticeService noticeService) { this.config = config; - this.announcer = announcer; + this.noticeService = noticeService; } @Override @@ -27,7 +27,7 @@ public void handle( String joinedText = missingPermissions.asJoinedText(); - this.announcer.create() + this.noticeService.create() .viewer(invocation.sender()) .notice(this.config.messagesSettings.noPermission) .placeholder("{PERMISSION}", joinedText) diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/ChunkRegion.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/ChunkRegion.java new file mode 100644 index 00000000..e6b27581 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/ChunkRegion.java @@ -0,0 +1,29 @@ +package com.eternalcode.combat.region; + +import org.bukkit.Location; +import org.bukkit.World; + +public record ChunkRegion(World world, int chunkX, int chunkZ) implements Region { + + @Override + public Point getCenter() { + int centerX = (chunkX << 4) + 8; + int centerZ = (chunkZ << 4) + 8; + return new Point(world, centerX + 0.5, centerZ + 0.5); + } + + @Override + public Location getMin() { + int minX = chunkX << 4; + int minZ = chunkZ << 4; + return new Location(world, minX, world.getMinHeight(), minZ); + } + + @Override + public Location getMax() { + int maxX = (chunkX << 4) + 15; + int maxZ = (chunkZ << 4) + 15; + return new Location(world, maxX, world.getMaxHeight() - 1, maxZ); + } + +} \ No newline at end of file diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/CompositeRegionProvider.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/CompositeRegionProvider.java new file mode 100644 index 00000000..4d887ec8 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/CompositeRegionProvider.java @@ -0,0 +1,38 @@ +package com.eternalcode.combat.region; + +import org.bukkit.Location; +import org.bukkit.World; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +public class CompositeRegionProvider implements RegionProvider { + + private final List providers; + + public CompositeRegionProvider(List providers) { + this.providers = providers; + } + + @Override + public Optional getRegion(Location location) { + for (RegionProvider provider : this.providers) { + Optional region = provider.getRegion(location); + if (region.isPresent()) { + return region; + } + } + return Optional.empty(); + } + + @Override + public Collection getRegions(World world) { + List combined = new ArrayList<>(); + for (RegionProvider provider : this.providers) { + combined.addAll(provider.getRegions(world)); + } + return combined; + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/DefaultRegionProvider.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/bukkit/DefaultRegionProvider.java similarity index 81% rename from eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/DefaultRegionProvider.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/bukkit/DefaultRegionProvider.java index 481ec8d3..58b7e0ec 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/DefaultRegionProvider.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/bukkit/DefaultRegionProvider.java @@ -1,9 +1,11 @@ -package com.eternalcode.combat.region; +package com.eternalcode.combat.region.bukkit; +import com.eternalcode.combat.region.Point; +import com.eternalcode.combat.region.Region; +import com.eternalcode.combat.region.RegionProvider; import java.util.Collection; import java.util.List; import java.util.Optional; -import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; @@ -40,13 +42,13 @@ private Region createSpawnRegion(World world) { Location min = new Location(world, x - this.radius, world.getMinHeight(), z - this.radius); Location max = new Location(world, x + this.radius - 1, world.getMaxHeight() - 1, z + this.radius - 1); - return new DefaultSpawnRegion(min, max, spawnLocation); + return new DefaultSpawnRegion(min, max, new Point(world, x, z)); } - private record DefaultSpawnRegion(Location min, Location max, Location center) implements Region { + private record DefaultSpawnRegion(Location min, Location max, Point point) implements Region { @Override - public Location getCenter() { - return this.center; + public Point getCenter() { + return this.point; } @Override diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/lands/LandsRegionProvider.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/lands/LandsRegionProvider.java new file mode 100644 index 00000000..df951a05 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/lands/LandsRegionProvider.java @@ -0,0 +1,45 @@ +package com.eternalcode.combat.region.lands; + +import com.eternalcode.combat.region.ChunkRegion; +import com.eternalcode.combat.region.Region; +import com.eternalcode.combat.region.RegionProvider; +import me.angeschossen.lands.api.LandsIntegration; +import me.angeschossen.lands.api.land.Land; +import org.bukkit.Location; +import org.bukkit.World; + +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; +import org.bukkit.plugin.Plugin; + +public class LandsRegionProvider implements RegionProvider { + + private final LandsIntegration lands; + + public LandsRegionProvider(Plugin plugin) { + this.lands = LandsIntegration.of(plugin); + } + + @Override + public Optional getRegion(Location location) { + int chunkX = location.getBlockX() >> 4; + int chunkZ = location.getBlockZ() >> 4; + Land land = this.lands.getLandByChunk(location.getWorld(), chunkX, chunkZ); + if (land == null) { + return Optional.empty(); + } + + return Optional.of(new ChunkRegion(location.getWorld(), chunkX, chunkZ)); + } + + @Override + public Collection getRegions(World world) { + return this.lands.getLands().stream() + .map(land -> land.getContainer(world)) + .filter(container -> container != null) + .flatMap(container -> container.getChunks().stream()) + .map(chunk -> new ChunkRegion(world, chunk.getX(), chunk.getZ())) + .collect(Collectors.toList()); + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/worldguard/WorldGuardRegion.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/worldguard/WorldGuardRegion.java new file mode 100644 index 00000000..4d0a5a8b --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/worldguard/WorldGuardRegion.java @@ -0,0 +1,33 @@ +package com.eternalcode.combat.region.worldguard; + +import com.eternalcode.combat.region.Point; +import com.eternalcode.combat.region.Region; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.bukkit.Location; +import org.bukkit.World; + +record WorldGuardRegion(World world, ProtectedRegion region) implements Region { + @Override + public Point getCenter() { + BlockVector3 min = this.region.getMinimumPoint(); + BlockVector3 max = this.region.getMaximumPoint(); + + double x = (double) (min.getX() + max.getX()) / 2; + double z = (double) (min.getZ() + max.getZ()) / 2; + + return new Point(this.world, x, z); + } + + @Override + public Location getMin() { + BlockVector3 min = this.region.getMinimumPoint(); + return new Location(this.world, min.getX(), min.getY(), min.getZ()); + } + + @Override + public Location getMax() { + BlockVector3 max = this.region.getMaximumPoint(); + return new Location(this.world, max.getX(), max.getY(), max.getZ()); + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/WorldGuardRegionProvider.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/worldguard/WorldGuardRegionProvider.java similarity index 64% rename from eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/WorldGuardRegionProvider.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/worldguard/WorldGuardRegionProvider.java index 62279cab..d5fdff9a 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/WorldGuardRegionProvider.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/worldguard/WorldGuardRegionProvider.java @@ -1,8 +1,9 @@ -package com.eternalcode.combat.region; +package com.eternalcode.combat.region.worldguard; import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.region.Region; +import com.eternalcode.combat.region.RegionProvider; import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.protection.ApplicableRegionSet; import com.sk89q.worldguard.protection.flags.Flags; @@ -13,12 +14,13 @@ import com.sk89q.worldguard.protection.regions.RegionQuery; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Optional; import java.util.TreeSet; import org.bukkit.Location; -import java.util.List; import org.bukkit.World; +import org.jetbrains.annotations.Nullable; public class WorldGuardRegionProvider implements RegionProvider { @@ -26,8 +28,8 @@ public class WorldGuardRegionProvider implements RegionProvider { private final TreeSet regions = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); private final PluginConfig pluginConfig; - public WorldGuardRegionProvider(List regions, PluginConfig pluginConfig) { - this.regions.addAll(regions); + public WorldGuardRegionProvider(PluginConfig pluginConfig) { + this.regions.addAll(pluginConfig.regions.blockedRegions); this.pluginConfig = pluginConfig; } @@ -36,12 +38,14 @@ public Optional getRegion(Location location) { RegionQuery regionQuery = this.regionContainer.createQuery(); ApplicableRegionSet applicableRegions = regionQuery.getApplicableRegions(BukkitAdapter.adapt(location)); - for (ProtectedRegion region : applicableRegions.getRegions()) { - if (!this.isCombatRegion(region)) { - continue; - } + ProtectedRegion highestPriorityRegion = this.highestPriorityRegion(applicableRegions); + + if (highestPriorityRegion == null) { + return Optional.empty(); + } - return Optional.of(new WorldGuardRegion(location.getWorld(), region)); + if (this.isCombatRegion(highestPriorityRegion)) { + return Optional.of(new WorldGuardRegion(location.getWorld(), highestPriorityRegion)); } return Optional.empty(); @@ -78,30 +82,11 @@ private boolean isCombatRegion(ProtectedRegion region) { return false; } - private record WorldGuardRegion(World context, ProtectedRegion region) implements Region { - @Override - public Location getCenter() { - BlockVector3 min = this.region.getMinimumPoint(); - BlockVector3 max = this.region.getMaximumPoint(); - - double x = (double) (min.getX() + max.getX()) / 2; - double z = (double) (min.getZ() + max.getZ()) / 2; - double y = (double) (min.getY() + max.getY()) / 2; - - return new Location(this.context, x, y, z); - } - - @Override - public Location getMin() { - BlockVector3 min = this.region.getMinimumPoint(); - return new Location(this.context, min.getX(), min.getY(), min.getZ()); - } - - @Override - public Location getMax() { - BlockVector3 max = this.region.getMaximumPoint(); - return new Location(this.context, max.getX(), max.getY(), max.getZ()); - } + @Nullable + private ProtectedRegion highestPriorityRegion(ApplicableRegionSet applicableRegions) { + return applicableRegions.getRegions().stream() + .min(Comparator.comparingInt(ProtectedRegion::getPriority)) + .orElse(null); } } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/DurationUtil.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/DurationUtil.java index ff193e95..3333dac9 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/DurationUtil.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/DurationUtil.java @@ -2,27 +2,49 @@ import com.eternalcode.commons.time.DurationParser; import com.eternalcode.commons.time.TemporalAmountParser; +import java.math.RoundingMode; import java.time.Duration; import java.time.temporal.ChronoUnit; -public final class DurationUtil { +public class DurationUtil { - private static final TemporalAmountParser WITHOUT_MILLS = new DurationParser() + private static final TemporalAmountParser WITHOUT_MILLIS_FORMAT = new DurationParser() .withUnit("s", ChronoUnit.SECONDS) .withUnit("m", ChronoUnit.MINUTES) .withUnit("h", ChronoUnit.HOURS) .withUnit("d", ChronoUnit.DAYS) - .roundOff(ChronoUnit.MILLIS); + .withRounded(ChronoUnit.MILLIS, RoundingMode.UP); + + private static final TemporalAmountParser STANDARD_FORMAT = new DurationParser() + .withUnit("d", ChronoUnit.DAYS) + .withUnit("h", ChronoUnit.HOURS) + .withUnit("m", ChronoUnit.MINUTES) + .withUnit("s", ChronoUnit.SECONDS) + .withUnit("ms", ChronoUnit.MILLIS); + + public static final Duration ONE_SECOND = Duration.ofSeconds(1); public DurationUtil() { throw new UnsupportedOperationException("This class cannot be instantiated"); } - public static String format(Duration duration) { - if (duration.toMillis() < 1000) { - return "0s"; + public static String format(Duration duration, boolean removeMillis) { + if (removeMillis) { + if (duration.toMillis() < ONE_SECOND.toMillis()) { + return "0s"; + } + + return WITHOUT_MILLIS_FORMAT.format(duration); + } + + if (duration.toMillis() > ONE_SECOND.toMillis()) { + return WITHOUT_MILLIS_FORMAT.format(duration); } - return WITHOUT_MILLS.format(duration); + return STANDARD_FORMAT.format(duration); + } + + public static String format(Duration duration) { + return format(duration, true); } } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/ReflectUtil.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/ReflectUtil.java new file mode 100644 index 00000000..1d963087 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/ReflectUtil.java @@ -0,0 +1,27 @@ +package com.eternalcode.combat.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public final class ReflectUtil { + + private ReflectUtil() { + } + + @SuppressWarnings("unchecked") + public static T invokeMethod(Object object, String name) { + try { + if (object == null) { + return null; + } + + Method method = object.getClass().getDeclaredMethod(name); + method.setAccessible(true); + return (T) method.invoke(object); + } + catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException exception) { + throw new RuntimeException(exception); + } + } + +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..58a9ac1a --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.configuration-cache=true +org.gradle.caching=true +org.gradle.parallel=true +org.gradle.vfs.watch=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 2c352119..1b33c55b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 09523c0e..ca025c83 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.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d..23d15a93 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a218..db3a6ac2 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/renovate.json b/renovate.json index de5fdb6b..957af026 100644 --- a/renovate.json +++ b/renovate.json @@ -1,29 +1,65 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "dependencyDashboard": true, "extends": [ - "config:base" + "config:recommended" ], - "groupName": "all dependencies", - "groupSlug": "all", "lockFileMaintenance": { "enabled": false }, + "pruneStaleBranches": true, + "commitMessagePrefix": "dependency:", + "separateMinorPatch": true, + "separateMultipleMajor": true, "packageRules": [ { - "groupName": "all dependencies", - "groupSlug": "all", - "matchPackagePatterns": [ - "*" + "matchUpdateTypes": [ + "patch" + ], + "automerge": true, + "labels": [ + "🔄 dependencies" + ], + "matchPackageNames": [ + "*", + "!/org.spigotmc*/" ] }, { - "groupName": "spigot dependencies", - "groupSlug": "spigotmc", - "matchPackagePatterns": [ - "org.spigotmc*" + "matchUpdateTypes": [ + "minor" + ], + "automerge": true, + "labels": [ + "🔄 dependencies" + ], + "matchPackageNames": [ + "*", + "!/org.spigotmc*/" + ] + }, + { + "matchUpdateTypes": [ + "major" + ], + "automerge": false, + "labels": [ + "🔄 dependencies" + ], + "matchPackageNames": [ + "*", + "!/org.spigotmc*/" + ] + }, + { + "allowedVersions": "/^\\d+\\.\\d+(\\.\\d+)?-R\\d+\\.\\d+-SNAPSHOT$/", + "automerge": false, + "labels": [ + "🔄 dependencies" + ], + "matchPackageNames": [ + "/org.spigotmc*/" ] } - ], - "separateMajorMinor": true, - "pruneStaleBranches": true + ] }