Compare commits

...
Author SHA1 Message Date
Michał Wojtas 520196417b Use already defined versions of plugins in runServer script
Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>
2026-06-21 12:17:23 +02:00
renovate[bot] fb6db7a008 dependency: Update Gradle to v9.6.0 (#397)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-20 14:14:16 +02:00
renovate[bot] 7d8a01b7d7 dependency: Update Versions.LITE_COMMANDS to v3.11.0 (#398)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-20 14:13:50 +02:00
renovate[bot] 08c7482cee dependency: Update actions/checkout action to v7 (#399)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-20 14:12:36 +02:00
7e24e5afa6 GH-266 New features for Trident and Pearls (#303)
* I've added new options to Pearl and Trident that reset the timer when we use them

* I made the corrections that gemini noticed

* The improvements suggested by the team have been implemented

* Implemented suggestions from the team

* Make tridents throw check work on all tridents, regardless of enchantment

* Implemented suggestions from the team and fixed `FightTridentController` logic

* Corrected comments and field names

* Implemented CitralFlo suggestions

* Implemented CitralFlo suggestions again

* Follow igoyek's review from 21.10.25

* CR

* Simplify naming - extract logic to service for Pearls

* make it more logical

* Refactor trident-related classes and services for improved clarity and consistency

* Rename trident cooldown methods to improve clarity and update comments for riptide usage

* Fixes after tests - everything should be working now!

* Remove commented line

* Add cooldown to enderpearl - hack by:https://www.spigotmc.org/threads/modify-vanilla-ender-pearl-cooldown.629232/

* Bump minecraft version in runServer task to 1.21.11

* Add combat restrictions for Riptide tridents and improve event handling

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/trident/TridentSettings.java

* Apply suggestion from @Jakubk15

* Migrate to commons delay

* Resolve conflicts

* CR

* resolve merge

* resolve merge conflicts and update

---------

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>
Co-authored-by: Michał Wojtas <wojtas.michal90@gmail.com>
Co-authored-by: Rollczi <ndejlich5@gmail.com>
Co-authored-by: Martin Sulikowski <vluckyyy.biznes@gmail.com>
2026-06-07 22:36:05 +02:00
bb130ae10e GH-373 Add opponent placeholder in combat notification (#373)
* Add opponent placeholder in combat notification

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

---------

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Norbert Dejlich <ndejlich5@gmail.com>
2026-06-07 21:46:10 +02:00
Igor MichalskiandRollczi 2c5b007d02 GH-331 Add pearl teleport (#331)
* Add pearl stasis prevention feature

Took 27 minutes

* Add message to pearl stasis prevention feature

Took 8 minutes

* CR

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2026-06-07 21:11:41 +02:00
Martin Sulikowski 51b365df1b bump version to 2.5.2-SNAPSHOT for further development 2026-06-06 21:25:32 +02:00
Martin Sulikowski 4f23105ba1 Release: bump version to 2.5.1 for official release 2026-06-06 21:21:25 +02:00
Martin Sulikowski d0c221c184 Fix: Add PlaceholderAPI dependency and ensure safe API initialization/deinitialization 2026-06-06 21:18:29 +02:00
Martin Sulikowski bf529dce32 Readme: Extend supported Minecraft versions to 26.1.2 2026-06-06 02:04:05 +02:00
Martin Sulikowski 74ec795581 Fix: bump version to 2.5.1-SNAPSHOT for further development 2026-06-06 02:03:30 +02:00
Martin Sulikowski b3550110c8 bump version to 2.5.1-SNAPSHOT for further development 2026-06-06 01:57:48 +02:00
Martin Sulikowski 326f7363f1 bump version to 2.5.0 for release 2026-06-06 01:56:00 +02:00
Martin Sulikowski 5645f0473b Modularize build scripts for publishing and run configurations 2026-06-06 01:52:57 +02:00
Martin Sulikowski d0336441ce GH-389 Use paper native, support 26+ minecraft versions. (#389)
* refactor: migrate plugin to modern Paper API

Switch build metadata to Paper plugin descriptors and paper-api from 1.19.3 onward.

Use native Paper Adventure audiences, async chunk loading, and teleport APIs without relocating Adventure.

Add repository guidance and bug report notes.

* Fix.

* fix: disable config cache for paper plugin descriptor task

The plugin-yml paper generator reads Task.project during task execution, which Gradle 9.5 rejects when storing configuration cache state.

Declare the generated descriptor task incompatible with configuration cache so CI can complete while keeping cache enabled for the rest of the build.

* fix: use paper-compatible multification notices

Replace BukkitNotice builders with core Notice builders after moving to multification-paper.

Use the Adventure sound resolver so config serialization no longer depends on multification-bukkit classes.

* fix: resolve combat potion effects through XSeries

Use XPotion for default combat effects so removed Bukkit enum constants such as DAMAGE_RESISTANCE do not crash on newer Paper versions.

Guard plugin disable cleanup when enable fails before the API provider is initialized.

* Fix.

* fix: support modern Paper without Adventure 5

* fix: update EternalCode Commons

* fix: downgrade Java toolchain to 21 and set custom launcher for runServer task
2026-06-06 01:44:12 +02:00
renovate[bot] bc6df07609 dependency: Update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9.4.2 (#384)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-06 00:46:18 +02:00
renovate[bot] 440f77e387 dependency: Update Versions.CAFFEINE to v3.2.4 (#380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-06 00:46:04 +02:00
renovate[bot] 3dc115c1f6 dependency: Update Gradle to v9.5.1 (#381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-06 00:45:57 +02:00
renovate[bot] 591ba20221 dependency: Update Versions.XSERIES to v13.8.0 (#382)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-06 00:45:50 +02:00
renovate[bot] 4d2d2222aa dependency: Update junit-framework monorepo to v6.1.0 (#383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-06 00:45:39 +02:00
renovate[bot] 62975caf58 dependency: Update Versions.WORLD_GUARD_BUKKIT to v7.0.17 (#385)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-06 00:45:29 +02:00
renovate[bot] d7c7ac2916 dependency: Update Versions.PACKETS_EVENTS to v2.12.2 (#386)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-06 00:45:22 +02:00
renovate[bot] d250a6a720 dependency: Update kotlin monorepo to v2.4.0 (#387)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-06 00:45:17 +02:00
Joël | NoPermissionandRollczi 5c6ac79579 GH-374 fix(combat): patch elytra escape exploit (#374)
* - Changed old methods to new worldguard methods

* fix(knockback): prevent roof teleport exploit & improve safe knockback handling

Fixed an issue where players using fight charge could remain inside knockback regions
and get teleported onto the map roof (above playable area).

### Key changes:
- Reworked knockback direction to push players toward nearest region edge
  instead of center-based vector
- Added velocity dampening system for more consistent knockback behavior
- Improved vertical handling:
  - Separate ground vs air vertical values
  - Prevent excessive vertical stacking while airborne

### Teleport fallback system:
- Introduced optional smart fallback teleport when knockback fails
- Added continuous velocity check before teleporting (prevents false triggers)
- Prevent duplicate fallback tasks using active tracking set
- Improved force knockback logic with edge distance + velocity validation

### Safe location handling:
- Implemented safe ground detection system:
  - Avoid unsafe blocks (lava, cactus, magma, etc.)
  - Ensure 2-block air clearance above ground
- Added configurable fallback scanning from highest block
- Prevent teleport if no safe location is found (optional)

### Stability improvements:
- Added recursion limit for region expansion to prevent infinite loops
- Improved region overlap handling during location generation
- Reduced unnecessary teleports and edge-case glitches

### Config additions:
- vertical / maxAirVertical
- dampenVelocity / dampenFactor
- useTeleport (smart fallback)
- safeGroundCheck + safeHighestFallback
- unsafeGroundBlocks
- groundOffset
- maxAttempts
- cancelIfNoSafeGround

Result:
- Eliminates roof teleport exploit
- More natural and predictable knockback
- Safer teleport fallback system
- Better handling of edge cases and overlapping regions

* fix(combat): prevent elytra escape exploit & improve glide restriction handling

Fixed an issue where players could abuse elytra (e.g. cliff jumping, trident launching, or mid-air tagging) to escape combat and bypass restrictions.

### Key changes:
- Fully disabled elytra usage during combat
- Blocked glide activation via EntityToggleGlideEvent
- Added continuous glide checks to stop mid-air abuse
- Forced downward velocity to prevent spacebar glide spam

### Elytra handling system:
- Introduced automatic elytra unequip on combat tag
- Prevents players from staying airborne when entering combat
- Moves elytra safely to inventory or drops if full
- Eliminates pre-glide and mid-air tagging exploits

### Inventory protection:
- Blocked equipping elytra during combat:
  - Click equip
  - Shift-click
  - Hotbar swap (number keys)
- Prevents all known re-equip bypass methods

### Flight & movement improvements:
- Disabled flight properly on combat start
- Prevents glide reactivation while moving

### Messaging improvements:
- Added dedicated elytra combat message
- Clear feedback when elytra is blocked or removed

### Config additions:
- unequipElytraOnCombat
- blockElytraEquipDuringCombat
- elytraDisabledDuringCombat

Result:
- Eliminates cliff jump & trident launch exploits
- Prevents all elytra-based combat escapes
- Provides consistent and predictable combat behavior
- Matches behavior of high-end PvP combat systems

* CR

* CR 2

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2026-06-05 19:48:47 +02:00
Martin Sulikowski 59f0612405 GH-328 Add /combatlog stats command and related permissions and messages. (#306) 2026-05-07 20:01:54 +02:00
renovate[bot] 84d1d5aeb6 dependency: Update Gradle to v9.5.0 (#378)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-28 20:00:54 +02:00
renovate[bot] e73864923f dependency: Update dependency com.github.retrooper:packetevents-spigot to v2.12.1 (#376)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-28 20:00:47 +02:00
renovate[bot] 195e45ed34 dependency: Update dependency org.jetbrains.kotlin:kotlin-stdlib-jdk8 to v2.3.21 (#377)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-28 20:00:39 +02:00
imgbot[bot]andImgBotApp 459ff06f18 [ImgBot] Optimize images (#361)
*Total -- 20,587.83kb -> 17,156.76kb (16.67%)

/assets/lightning.gif -- 4,208.49kb -> 2,771.07kb (34.16%)
/assets/flare.gif -- 6,525.52kb -> 5,502.98kb (15.67%)
/assets/readme-banner.png -- 112.85kb -> 98.39kb (12.81%)
/assets/border.gif -- 6,004.89kb -> 5,373.86kb (10.51%)
/assets/combatlog.gif -- 2,949.14kb -> 2,658.24kb (9.86%)
/assets/crystals.gif -- 786.95kb -> 752.21kb (4.41%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2026-04-19 20:59:49 +02:00
renovate[bot] 4a153d5557 dependency: Update actions/github-script action to v9 (#372)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-12 11:07:04 +02:00
renovate[bot] 3e5b060923 dependency: Update dependency com.github.retrooper:packetevents-spigot to v2.12.0 (#370)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-12 11:06:38 +02:00
renovate[bot] 03768ccd8d dependency: Update dependency com.sk89q.worldguard:worldguard-bukkit to v7.0.16 (#368)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-12 11:06:14 +02:00
renovate[bot] ba59d85996 dependency: Update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9.4.1 (#369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-12 11:06:04 +02:00
renovate[bot] 1c432cbcb3 dependency: Update dependency com.github.angeschossen:LandsAPI to v7.25.4 (#366)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-12 11:03:33 +02:00
renovate[bot] de6b76526c dependency: Update gradle/actions action to v6 (#367)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-12 11:03:23 +02:00
renovate[bot] 7de452ac15 dependency: Update Gradle to v9.4.1 (#365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-22 09:31:52 +01:00
renovate[bot]andJakub Kędziora 7e578d63f7 dependency: Update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9.4.0 (#363)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>
2026-03-22 09:31:44 +01:00
renovate[bot] e5f86c53a2 dependency: Update dependency org.jetbrains.kotlin:kotlin-stdlib-jdk8 to v2.3.20 (#364)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-22 09:31:15 +01:00
67 changed files with 1612 additions and 796 deletions

No files matched your search

+3 -3
View File
@@ -13,7 +13,7 @@ jobs:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
@@ -24,10 +24,10 @@ jobs:
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
- name: Set Variables & Build
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const isPR = context.eventName === 'pull_request';
+2 -2
View File
@@ -14,7 +14,7 @@ jobs:
contents: read
if: (github.event_name == 'push' && !contains(github.event.head_commit.message, '[ci skip]')) || github.event_name == 'release'
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up JDK 21
@@ -24,7 +24,7 @@ jobs:
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
- name: Publish to Hangar
run: ./gradlew :eternalcombat-plugin:publishPluginPublicationToHangar
+2 -2
View File
@@ -14,7 +14,7 @@ jobs:
contents: read
if: (github.event_name == 'push' && !contains(github.event.head_commit.message, '[ci skip]')) || github.event_name == 'release'
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up JDK 21
@@ -24,7 +24,7 @@ jobs:
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
- name: Publish to Modrinth
run: ./gradlew :eternalcombat-plugin:modrinth
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
environment: deployment
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Set up JDK
uses: actions/setup-java@v5
+1
View File
@@ -1,6 +1,7 @@
.classpath
.idea
.gradle
.kotlin
.project
.settings
bin
+6 -4
View File
@@ -25,7 +25,7 @@
## EternalCombat requires PacketEvents plugin to work.
You can download it from [here](https://modrinth.com/plugin/packetevents)
EternalCombat 2.0 has been tested on Minecraft versions **1.17.1 to 1.21.11**, but it should work seamlessly on most
EternalCombat 2.0 has been tested on Minecraft versions **1.17.1 to 26.1.2**, 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).
@@ -45,7 +45,7 @@ fair, heart-pounding battles that keep players on their toes. Here’s a rundown
in action:
<img alt="Combat log anti logout feature" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/combatlog.gif?raw=true" width="420">
- **Customize combat experience**
- **Customize combat experience**
Add custom effects to players in combat or death. Everything should be configurable and user-friendly:
<img alt="Lightning strikes when players die" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/lightning.gif?raw=true" width="420">
<img alt="Flare indicates players death location" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/flare.gif?raw=true" width="420">
@@ -87,6 +87,8 @@ Control who can use EternalCombat’s powerful features with these permissions:
| `eternalcombat.status` | Check a player’s combat status with `/combatlog status <player>`. |
| `eternalcombat.tag` | Start a fight between players using `/combatlog tag <player> [player2]`. |
| `eternalcombat.untag` | Remove a player from combat with `/combatlog untag <player>`. |
| `eternalcombat.untagall` | Remove all players from combat with `/combatlog untagall`. |
| `eternalcombat.stats` | View combat statistics with `/combatlog stats`. |
| `eternalcombat.reload` | Reload the plugin with `/combatlog reload`. |
| `eternalcombat.receiveupdates` | Receive notifications about new plugin versions on join. |
| `eternalcombat.bypass` | When exclude `excludeAdminsFromCombat` is set to `true` users with this permission will not be tagged |
@@ -137,7 +139,7 @@ With Maven:
With Gradle:
```kts
compileOnly("com.eternalcode:eternalcombat-api:2.5.0-SNAPSHOT")
compileOnly("com.eternalcode:eternalcombat-api:2.5.2-SNAPSHOT")
```
With Maven:
@@ -147,7 +149,7 @@ With Maven:
<dependency>
<groupId>com.eternalcode</groupId>
<artifactId>eternalcombat-api</artifactId>
<version>2.5.0-SNAPSHOT</version>
<version>2.5.2-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
```
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 MiB

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 787 KiB

After

Width:  |  Height:  |  Size: 752 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 MiB

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 98 KiB

+4 -2
View File
@@ -8,8 +8,10 @@ repositories {
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.10")
implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.3.2")
implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.4.2")
implementation("com.modrinth.minotaur:com.modrinth.minotaur.gradle.plugin:2.9.0")
implementation("io.papermc.hangar-publish-plugin:io.papermc.hangar-publish-plugin.gradle.plugin:0.1.4")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.4.0")
implementation("net.minecrell:plugin-yml:0.6.0")
implementation("xyz.jpenilla:run-task:3.0.2")
}
@@ -0,0 +1,39 @@
import org.gradle.api.Project
import org.gradle.api.provider.Provider
internal val Project.isRelease: Boolean
get() = providers.environmentVariable("GITHUB_EVENT_NAME").orNull == "release"
internal val Project.releaseChannel: String
get() = if (isRelease) "Release" else "Snapshot"
internal val Project.changelogText: Provider<String>
get() = providers.environmentVariable("CHANGELOG")
.orElse(providers.exec { commandLine("git", "log", "-1", "--format=%B") }.standardOutput.asText)
internal val Project.paperVersions: List<String>
get() = property("paperVersion").toString()
.split(",")
.map { paperVersion -> paperVersion.trim() }
internal fun Project.configureSnapshotVersionFromGit() {
val isGithubWorkflow = providers.environmentVariable("GITHUB_RUN_NUMBER").isPresent
if (!isGithubWorkflow || isRelease) {
return
}
if ("-SNAPSHOT+" in version.toString()) {
return
}
val describeParts = providers.exec { commandLine("git", "describe", "--tags", "--long") }
.standardOutput.asText.get()
.trim()
.split("-")
val offset = if (describeParts.size >= 3) describeParts[describeParts.size - 2] else "0"
val baseVersion = version.toString().substringBefore("-SNAPSHOT")
version = "$baseVersion-SNAPSHOT+$offset"
}
+18 -20
View File
@@ -1,39 +1,37 @@
object Versions {
const val SPIGOT_API = "1.17.1-R0.1-SNAPSHOT"
// Keep this version for best compatibility.
const val PAPER_API = "1.19.3-R0.1-SNAPSHOT"
const val JUNIT_JUPITER_API = "6.0.3"
const val JUNIT_JUPITER_PARAMS = "6.0.3"
const val JUNIT_JUPITER_ENGINE = "6.0.3"
const val JUNIT_JUPITER_API = "6.1.0"
const val JUNIT_JUPITER_PARAMS = "6.1.0"
const val JUNIT_JUPITER_ENGINE = "6.1.0"
const val JETBRAINS_ANNOTATIONS = "26.1.0"
const val ETERNALCODE_COMMONS = "1.3.4"
const val MULTIFICATION = "1.2.4"
const val PACKETS_EVENTS = "2.11.2"
const val ETERNALCODE_COMMONS = "1.4.1"
const val MULTIFICATION = "1.2.5-SNAPSHOT"
const val PACKETS_EVENTS = "2.12.2"
const val ADVENTURE_PLATFORM_BUKKIT = "4.4.1"
const val ADVENTURE_API = "4.26.1"
const val LITE_COMMANDS = "3.10.9"
const val LITE_COMMANDS = "3.11.0"
const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.13"
const val OKAERI_CONFIGS_SERDES_BUKKIT = "5.0.13"
const val XSERIES = "13.6.0"
const val XSERIES = "13.8.0"
const val CAFFEINE = "3.2.3"
const val CAFFEINE = "3.2.4"
const val B_STATS_BUKKIT = "3.2.1"
const val WORLD_GUARD_BUKKIT = "7.0.15"
const val WORLD_GUARD_BUKKIT = "7.0.17"
const val PLACEHOLDER_API = "2.12.2"
const val LANDS_API = "7.24.1"
const val PAPERLIB = "1.0.8"
const val LANDS_API = "7.25.4"
const val WORLDEDIT = "3ISh7ADm" //cannot use numeric version bc of duplicated version on modrinth
const val PACKETEVENTS = "2.11.1"
const val WORLDGUARD = "7.0.15-beta-01"
const val LUCKPERMS = "5.5.17"
//runServer task
const val FAST_ASYNC_WORLD_EDIT = "2.15.2"
const val PACKETEVENTS = "2.12.2+spigot"
const val WORLDGUARD = "7.0.17"
const val LUCKPERMS = "v5.5.53-bukkit"
}
@@ -3,7 +3,7 @@ plugins {
}
dependencies {
testImplementation("org.spigotmc:spigot-api:${Versions.SPIGOT_API}")
testImplementation("io.papermc.paper:paper-api:${Versions.PAPER_API}")
testImplementation("org.junit.jupiter:junit-jupiter-api:${Versions.JUNIT_JUPITER_API}")
testImplementation("org.junit.jupiter:junit-jupiter-params:${Versions.JUNIT_JUPITER_PARAMS}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${Versions.JUNIT_JUPITER_ENGINE}")
@@ -3,7 +3,7 @@ plugins {
}
group = "com.eternalcode"
version = "2.5.0-SNAPSHOT"
version = "2.5.2-SNAPSHOT"
tasks.compileJava {
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
@@ -0,0 +1,28 @@
import io.papermc.hangarpublishplugin.model.Platforms
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id("io.papermc.hangar-publish-plugin")
}
configureSnapshotVersionFromGit()
pluginManager.withPlugin("com.gradleup.shadow") {
val shadowJarTask = tasks.named<ShadowJar>("shadowJar")
hangarPublish {
publications.register("plugin") {
version.set(project.version.toString())
channel.set(project.releaseChannel)
changelog.set(project.changelogText)
id.set("eternalcombat")
apiKey.set(providers.environmentVariable("HANGAR_API_TOKEN"))
platforms {
register(Platforms.PAPER) {
jar.set(shadowJarTask.flatMap { shadowJar -> shadowJar.archiveFile })
platformVersions.set(project.paperVersions)
}
}
}
}
}
@@ -0,0 +1,23 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id("com.modrinth.minotaur")
}
configureSnapshotVersionFromGit()
pluginManager.withPlugin("com.gradleup.shadow") {
val shadowJarTask = tasks.named<ShadowJar>("shadowJar")
modrinth {
token.set(providers.environmentVariable("MODRINTH_TOKEN"))
projectId.set("eternalcombat")
versionNumber.set(project.version.toString())
versionType.set(if (project.isRelease) "release" else "beta")
uploadFile.set(shadowJarTask)
gameVersions.addAll(project.paperVersions)
loaders.addAll(listOf("paper", "folia", "purpur"))
changelog.set(project.changelogText)
syncBodyFrom.set(rootProject.file("README.md").readText())
}
}
@@ -4,7 +4,7 @@ plugins {
}
group = "com.eternalcode"
version = "2.5.0-SNAPSHOT"
version = "2.5.2-SNAPSHOT"
java {
withSourcesJar()
@@ -3,10 +3,10 @@ plugins {
}
repositories {
mavenLocal()
mavenCentral()
maven("https://repo.eternalcode.pl/releases")
maven("https://repo.eternalcode.pl/snapshots")
mavenCentral()
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://storehouse.okaeri.eu/repository/maven-public/")
@@ -0,0 +1,22 @@
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.jvm.toolchain.JavaToolchainService
plugins {
id("xyz.jpenilla.run-paper")
}
tasks {
val javaToolchains = extensions.getByType<JavaToolchainService>()
runServer {
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(25))
})
minecraftVersion("26.1.2")
downloadPlugins.modrinth("FastAsyncWorldEdit", Versions.FAST_ASYNC_WORLD_EDIT)
downloadPlugins.modrinth("PacketEvents", Versions.PACKETEVENTS)
downloadPlugins.modrinth("WorldGuard", Versions.WORLDGUARD)
downloadPlugins.modrinth("LuckPerms", Versions.LUCKPERMS)
}
}
+2 -2
View File
@@ -6,7 +6,7 @@ plugins {
}
dependencies {
// Spigot api
compileOnlyApi("org.spigotmc:spigot-api:${Versions.SPIGOT_API}")
// Paper api
compileOnlyApi("io.papermc.paper:paper-api:${Versions.PAPER_API}")
api("org.jetbrains:annotations:${Versions.JETBRAINS_ANNOTATIONS}")
}
@@ -4,7 +4,7 @@ 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.pearl.FightPearlService;
import com.eternalcode.combat.fight.pearl.PearlService;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.combat.fight.tagout.FightTagOutService;
@@ -14,7 +14,7 @@ public interface EternalCombatApi {
RegionProvider getRegionProvider();
FightPearlService getFightPearlService();
PearlService getFightPearlService();
FightTagOutService getFightTagOutService();
@@ -23,6 +23,17 @@ public enum CauseOfTag {
*/
CRYSTAL,
/**
* Trident usage extending combat tag.
*/
TRIDENT,
/**
* Ender pearl usage extending combat tag.
*/
ENDER_PEARL,
/**
* A custom cause, typically defined by external plugins or systems, applied the combat tag.
*/
@@ -1,16 +1,16 @@
package com.eternalcode.combat.fight.pearl;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
import org.bukkit.entity.Player;
public interface FightPearlService {
public interface PearlService {
Instant getDelay(UUID uuid);
boolean shouldCancelEvent(UUID playerId);
Duration getRemainingDelay(UUID uuid);
boolean hasDelay(UUID uuid);
void markDelay(UUID uuid);
void handleDelay(Player uuid);
}
@@ -0,0 +1,31 @@
package com.eternalcode.combat.fight.trident;
import java.time.Duration;
import java.util.UUID;
public interface TridentService {
/**
* returns remaining delay for the player to use trident again
* @param uuid unique id of the player
* @return remaining duration left to use trident again by the player or zero
*/
Duration getRemainingDelay(UUID uuid);
/**
* checks if player still has delay left to use trident
* @param uuid unique id of the player
* @return true if user still has cooldown left to use trident
*/
boolean hasDelay(UUID uuid);
/**
* marks start of the delay for the user
* @param uuid unique id of the player
*/
void markDelay(UUID uuid);
void removeDelay(UUID playerId);
}
+40 -85
View File
@@ -1,16 +1,16 @@
import net.minecrell.pluginyml.paper.PaperPluginDescription
import net.minecrell.pluginyml.bukkit.BukkitPluginDescription
import io.papermc.hangarpublishplugin.model.Platforms
import org.gradle.kotlin.dsl.shadowJar
plugins {
`eternalcombat-java`
`eternalcombat-repositories`
`eternalcombat-publish-hangar`
`eternalcombat-publish-modrinth`
`eternalcombat-runserver`
id("net.minecrell.plugin-yml.bukkit")
id("de.eldoria.plugin-yml.paper") version "0.9.0"
id("com.gradleup.shadow")
id("xyz.jpenilla.run-paper")
id("com.modrinth.minotaur") version "2.9.0"
id("io.papermc.hangar-publish-plugin") version "0.1.4"
}
configurations.all {
@@ -27,14 +27,8 @@ configurations.all {
dependencies {
implementation(project(":eternalcombat-api"))
// kyori
implementation("net.kyori:adventure-platform-bukkit:${Versions.ADVENTURE_PLATFORM_BUKKIT}")
implementation("net.kyori:adventure-text-minimessage:${Versions.ADVENTURE_API}")
implementation("net.kyori:adventure-api") {
version {
strictly(Versions.ADVENTURE_API)
}
}
// Paper
compileOnly("io.papermc.paper:paper-api:${Versions.PAPER_API}")
// litecommands
implementation("dev.rollczi:litecommands-bukkit:${Versions.LITE_COMMANDS}")
@@ -69,38 +63,49 @@ dependencies {
compileOnly("com.github.angeschossen:LandsAPI:${Versions.LANDS_API}")
// Multification
implementation("com.eternalcode:multification-bukkit:${Versions.MULTIFICATION}")
implementation("com.eternalcode:multification-paper:${Versions.MULTIFICATION}")
implementation("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION}")
compileOnly("com.github.retrooper:packetevents-spigot:${Versions.PACKETS_EVENTS}")
implementation("io.papermc:paperlib:${Versions.PAPERLIB}")
}
bukkit {
paper {
main = "com.eternalcode.combat.CombatPlugin"
author = "EternalCodeTeam"
apiVersion = "1.13"
authors = listOf("EternalCodeTeam")
apiVersion = "1.19"
prefix = "EternalCombat"
name = "EternalCombat"
generateLibrariesJson = true
load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD
softDepend = listOf(
"Lands",
"WorldGuard"
)
depend = listOf(
"packetevents",
)
version = "${project.version}"
foliaSupported = true
serverDependencies {
register("packetevents") {
required = true
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
}
register("Lands") {
required = false
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
}
register("WorldGuard") {
required = false
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
}
register("PlaceholderAPI") {
required = false
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
}
}
}
tasks {
runServer {
minecraftVersion("1.21.10")
downloadPlugins.modrinth("WorldEdit", Versions.WORLDEDIT)
downloadPlugins.modrinth("PacketEvents", "${Versions.PACKETEVENTS}+spigot")
downloadPlugins.modrinth("WorldGuard", Versions.WORLDGUARD)
downloadPlugins.modrinth("LuckPerms", "v${Versions.LUCKPERMS}-bukkit")
named("generatePaperPluginDescription") {
notCompatibleWithConfigurationCache("The plugin-yml paper generator reads Task.project during execution.")
}
}
@@ -110,17 +115,18 @@ tasks.shadowJar {
exclude(
"org/intellij/lang/annotations/**",
"org/jetbrains/annotations/**",
"net/kyori/**",
"META-INF/**",
"kotlin/**",
"javax/**",
"org/checkerframework/**",
"com/google/errorprone/**",
"com/google/gson/**"
)
val prefix = "com.eternalcode.combat.libs"
listOf(
"eu.okaeri",
"net.kyori",
"org.bstats",
"org.yaml",
"dev.rollczi.litecommands",
@@ -130,59 +136,8 @@ tasks.shadowJar {
"com.eternalcode.commons",
"com.eternalcode.multification",
"com.github.cryptomorin",
"io.papermc.lib",
"com.cryptomorin",
).forEach { pack ->
relocate(pack, "$prefix.$pack")
}
}
val isGithubWorkflow = providers.environmentVariable("GITHUB_RUN_NUMBER").isPresent
val isRelease = providers.environmentVariable("GITHUB_EVENT_NAME").orNull == "release"
val isSnapshot = !isRelease
if (isGithubWorkflow && isSnapshot) {
val parts = providers.exec { commandLine("git", "describe", "--tags", "--long") }
.standardOutput.asText.get().trim()
.split("-")
val offset = if (parts.size >= 3) parts[parts.size - 2] else "0"
val baseVersion = project.version.toString().replace("-SNAPSHOT", "")
version = "$baseVersion-SNAPSHOT+$offset"
}
val changelogText = providers.environmentVariable("CHANGELOG")
.orElse(providers.exec { commandLine("git", "log", "-1", "--format=%B") }.standardOutput.asText)
val paperVersions = property("paperVersion").toString()
.split(",")
.map { it.trim() }
val releaseChannel = if (isRelease) "Release" else "Snapshot"
modrinth {
token.set(providers.environmentVariable("MODRINTH_TOKEN"))
projectId.set("eternalcombat")
versionNumber.set(project.version.toString())
versionType.set(if (isRelease) "release" else "beta")
uploadFile.set(tasks.shadowJar)
gameVersions.addAll(paperVersions)
loaders.addAll(listOf("paper", "spigot", "folia", "purpur"))
changelog.set(changelogText)
syncBodyFrom.set(rootProject.file("README.md").readText())
}
hangarPublish {
publications.register("plugin") {
version.set(project.version.toString())
channel.set(releaseChannel)
changelog.set(changelogText)
id.set("eternalcombat")
apiKey.set(providers.environmentVariable("HANGAR_API_TOKEN"))
platforms {
register(Platforms.PAPER) {
jar.set(tasks.shadowJar.flatMap { it.archiveFile })
platformVersions.set(paperVersions)
}
}
}
}
@@ -1,55 +1,62 @@
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.crystalpvp.RespawnAnchorListener;
import com.eternalcode.combat.config.ConfigService;
import com.eternalcode.combat.config.implementation.PluginConfig;
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.blocker.CommandsBlocker;
import com.eternalcode.combat.fight.blocker.ElytraBlocker;
import com.eternalcode.combat.fight.blocker.ElytraEquipBlocker;
import com.eternalcode.combat.fight.blocker.FlyingBlocker;
import com.eternalcode.combat.fight.controller.FightBypassAdminController;
import com.eternalcode.combat.fight.controller.FightBypassCreativeController;
import com.eternalcode.combat.fight.controller.FightBypassPermissionController;
import com.eternalcode.combat.fight.controller.FightInventoryController;
import com.eternalcode.combat.fight.death.DeathFlareController;
import com.eternalcode.combat.fight.death.DeathLightningController;
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.firework.FireworkController;
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.fight.FightTagCommand;
import com.eternalcode.combat.fight.controller.FightActionBlockerController;
import com.eternalcode.combat.fight.blocker.InventoryContainersBlocker;
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.death.DeathFlareController;
import com.eternalcode.combat.fight.death.DeathLightningController;
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.impl.PercentDropModifier;
import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier;
import com.eternalcode.combat.fight.blocker.PlaceBlockBlocker;
import com.eternalcode.combat.fight.effect.FightEffectController;
import com.eternalcode.combat.event.EventManager;
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.firework.FireworkController;
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.FightPearlServiceImpl;
import com.eternalcode.combat.fight.tagout.FightTagOutController;
import com.eternalcode.combat.fight.tagout.FightTagOutServiceImpl;
import com.eternalcode.combat.fight.pearl.PearlController;
import com.eternalcode.combat.fight.pearl.PearlService;
import com.eternalcode.combat.fight.pearl.PearlServiceImpl;
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.trident.TridentController;
import com.eternalcode.combat.fight.trident.TridentService;
import com.eternalcode.combat.fight.trident.TridentServiceImpl;
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;
@@ -63,8 +70,6 @@ import dev.rollczi.litecommands.bukkit.LiteBukkitFactory;
import dev.rollczi.litecommands.bukkit.LiteBukkitMessages;
import dev.rollczi.litecommands.folia.FoliaExtension;
import java.time.Duration;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bstats.bukkit.Metrics;
import org.bukkit.Server;
@@ -83,7 +88,8 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
private static final int BSTATS_METRICS_ID = 17803;
private FightManager fightManager;
private FightPearlService fightPearlService;
private PearlService pearlService;
private TridentService tridentService;
private FightTagOutService fightTagOutService;
private FightEffectService fightEffectService;
@@ -92,8 +98,8 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
private RegionProvider regionProvider;
private AudienceProvider audienceProvider;
private LiteCommands<CommandSender> liteCommands;
private boolean apiInitialized;
@Override
@@ -111,7 +117,8 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
MinecraftScheduler scheduler = CombatSchedulerAdapter.getAdaptiveScheduler(this);
this.fightManager = new FightManagerImpl(eventManager);
this.fightPearlService = new FightPearlServiceImpl(pluginConfig.pearl);
this.pearlService = new PearlServiceImpl(this.fightManager, pluginConfig, scheduler);
this.tridentService = new TridentServiceImpl(pluginConfig);
this.fightTagOutService = new FightTagOutServiceImpl();
this.fightEffectService = new FightEffectServiceImpl();
@@ -122,13 +129,12 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
UpdaterService updaterService = new UpdaterService(this.getDescription());
this.audienceProvider = BukkitAudiences.create(this);
MiniMessage miniMessage = MiniMessage.builder()
.postProcessor(new AdventureLegacyColorPostProcessor())
.preProcessor(new AdventureLegacyColorPreProcessor())
.build();
NoticeService noticeService = new NoticeService(this.audienceProvider, pluginConfig, miniMessage);
NoticeService noticeService = new NoticeService(pluginConfig, miniMessage);
BridgeService bridgeService = new BridgeService(
pluginConfig,
@@ -181,11 +187,12 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
new FightBypassAdminController(server, pluginConfig),
new FightBypassPermissionController(server, pluginConfig),
new FightBypassCreativeController(server, pluginConfig),
new FightActionBlockerController(this.fightManager, noticeService, pluginConfig, server),
new FightPearlController(pluginConfig.pearl, noticeService, this.fightManager, this.fightPearlService),
new PlaceBlockBlocker(this.fightManager, noticeService, pluginConfig),
new PearlController(pluginConfig, this.pearlService, noticeService, fightManager),
new TridentController(pluginConfig, noticeService, this.fightManager, this.tridentService, server),
new DeathFlareController(pluginConfig, server, scheduler, this),
new DeathLightningController(pluginConfig, server),
new UpdaterNotificationController(updaterService, pluginConfig, this.audienceProvider, miniMessage),
new UpdaterNotificationController(updaterService, pluginConfig, miniMessage),
new KnockbackRegionController(noticeService, this.regionProvider, this.fightManager, knockbackService, server),
new FightEffectController(pluginConfig.effect, this.fightEffectService, this.fightManager, server),
new FightTagOutController(this.fightTagOutService),
@@ -196,7 +203,12 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
new EndCrystalListener(this, this.fightManager, pluginConfig),
new RespawnAnchorListener(this, this.fightManager, pluginConfig),
new FireworkController(this.fightManager, pluginConfig, noticeService),
new FightInventoryController(this.fightManager, pluginConfig, noticeService)
new InventoryContainersBlocker(this.fightManager, pluginConfig, noticeService),
new CommandsBlocker(this.fightManager, noticeService, pluginConfig),
new ElytraBlocker(this.fightManager, pluginConfig),
new ElytraEquipBlocker(this.fightManager, noticeService, pluginConfig, server),
new FlyingBlocker(this.fightManager, pluginConfig, server),
new PlaceBlockBlocker(this.fightManager, noticeService, pluginConfig)
);
eventManager.subscribe(
@@ -212,6 +224,7 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
);
EternalCombatProvider.initialize(this);
this.apiInitialized = true;
long millis = started.elapsed(TimeUnit.MILLISECONDS);
this.getLogger().info("Successfully loaded EternalCombat in " + millis + "ms");
@@ -219,17 +232,18 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
@Override
public void onDisable() {
EternalCombatProvider.deinitialize();
if (this.liteCommands != null) {
this.liteCommands.unregister();
}
if (this.audienceProvider != null) {
this.audienceProvider.close();
if (this.fightManager != null) {
this.fightManager.untagAll();
}
this.fightManager.untagAll();
if (this.apiInitialized) {
EternalCombatProvider.deinitialize();
this.apiInitialized = false;
}
}
@Override
@@ -243,8 +257,8 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
}
@Override
public FightPearlService getFightPearlService() {
return this.fightPearlService;
public PearlService getFightPearlService() {
return this.pearlService;
}
@Override
@@ -2,7 +2,6 @@ package com.eternalcode.combat;
import com.eternalcode.combat.config.ConfigService;
import com.eternalcode.combat.notification.NoticeService;
import com.eternalcode.multification.bukkit.notice.BukkitNotice;
import com.eternalcode.multification.notice.Notice;
import com.google.common.base.Stopwatch;
import dev.rollczi.litecommands.annotations.async.Async;
@@ -16,7 +15,7 @@ import org.bukkit.command.CommandSender;
@Command(name = "combatlog", aliases = "combat")
public class EternalCombatReloadCommand {
private static final Notice RELOAD_MESSAGE = BukkitNotice.builder()
private static final Notice RELOAD_MESSAGE = Notice.builder()
.chat("<b><gradient:#8a1212:#fc6b03>EternalCombat:</gradient></b> Reloaded EternalCombat in <color:#fce303>{TIME}ms!</color>")
.build();
@@ -2,7 +2,6 @@ package com.eternalcode.combat.border.animation.block;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import io.papermc.lib.PaperLib;
import org.bukkit.ChunkSnapshot;
import org.bukkit.entity.Player;
@@ -22,7 +21,7 @@ class ChunkCache {
return snapshot;
}
ChunkSnapshot chunkSnapshot = PaperLib.getChunkAtAsync(player.getWorld(), location.x(), location.z(), false)
ChunkSnapshot chunkSnapshot = player.getWorld().getChunkAtAsync(location.x(), location.z(), false)
.thenApply(chunk -> chunk.getChunkSnapshot())
.join();
@@ -3,9 +3,9 @@ package com.eternalcode.combat.config;
import com.eternalcode.combat.border.animation.block.BlockTypeTransformer;
import com.eternalcode.combat.border.animation.particle.ParticleColorTransformer;
import com.eternalcode.combat.border.animation.particle.ParticleTypeTransformer;
import com.eternalcode.multification.bukkit.notice.resolver.sound.SoundBukkitResolver;
import com.eternalcode.multification.notice.resolver.NoticeResolverDefaults;
import com.eternalcode.multification.notice.resolver.NoticeResolverRegistry;
import com.eternalcode.multification.notice.resolver.sound.SoundAdventureResolver;
import com.eternalcode.multification.okaeri.MultificationSerdesPack;
import eu.okaeri.configs.ConfigManager;
import eu.okaeri.configs.OkaeriConfig;
@@ -28,7 +28,7 @@ public class ConfigService {
YamlSnakeYamlConfigurer configurer = new YamlSnakeYamlConfigurer();
NoticeResolverRegistry noticeRegistry = NoticeResolverDefaults.createRegistry()
.registerResolver(new SoundBukkitResolver());
.registerResolver(new SoundAdventureResolver());
configFile.withConfigurer(configurer,
new SerdesCommons(),
@@ -33,6 +33,14 @@ public class CombatSettings extends OkaeriConfig {
})
public boolean disableFlying = true;
@Comment({
"# Forcefully unequip elytra when a player enters combat.",
"# This prevents players from abusing glide by jumping before being tagged.",
"# The elytra will be moved to the player's inventory.",
"# Recommended: true"
})
public boolean unequipElytraOnCombat = true;
@Comment({
"# Prevent players from boosting themselves while flying with fireworks",
"# This setting blocks usage of fireworks to boost elytra flight during combat"
@@ -1,6 +1,5 @@
package com.eternalcode.combat.config.implementation;
import com.eternalcode.multification.bukkit.notice.BukkitNotice;
import com.eternalcode.multification.notice.Notice;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
@@ -8,189 +7,205 @@ import eu.okaeri.configs.annotation.Comment;
public class MessagesSettings extends OkaeriConfig {
@Comment({
"# Customize messages related to admin commands and notifications.",
"# These messages are displayed to server administrators."
"# Customize messages related to admin commands and notifications.",
"# These messages are displayed to server administrators."
})
public AdminMessages admin = new AdminMessages();
@Comment({
" ",
"# Configure the combat log notification displayed to players.",
"# You can use the {TIME} variable to display the remaining combat time.",
" ",
" ",
"# Configure the combat log notification displayed to players.",
"# You can use the {TIME} variable to display the remaining combat time.",
"# Placeholder {OPPONENT} will be replaced with player name or 'unknownPlayerPlaceholder' variable",
" ",
})
public Notice combatNotification = BukkitNotice.builder()
.actionBar("Combat ends in: <red>{TIME}</red>")
.build();
public Notice combatNotification = Notice.builder()
.actionBar("Combat ends in: <red>{TIME}</red>")
.build();
@Comment({
"# Would you like to display milliseconds instead of seconds in combat notification "
"# 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."
" ",
"# When player is not known this text will be used as {OPPONENT} in 'combatNotification'"
})
public String unknownPlayerPlaceholder = "Unknown";
@Comment({
"# Message displayed when a player lacks permission to execute a command.",
"# The {PERMISSION} placeholder is replaced with the required permission."
})
public Notice noPermission = Notice.chat(
"<gradient:red:dark_red>You don't have permission <dark_gray>(<gray>{PERMISSION}</gray>)</dark_gray> to perform this command!</gradient>");
"<gradient:red:dark_red>You don't have permission <dark_gray>(<gray>{PERMISSION}</gray>)</dark_gray> to perform this command!</gradient>");
@Comment({
"# Message displayed when a specified player is not found.",
"# This message is shown when a command targets a player who is not online or does not exist."
"# Message displayed when a specified player is not found.",
"# This message is shown when a command targets a player who is not online or does not exist."
})
public Notice playerNotFound =
Notice.chat("<gradient:#ff0000:#ff6b6b>The specified player could not be found!</gradient>");
public Notice playerNotFound = Notice
.chat("<gradient:#ff0000:#ff6b6b>The specified player could not be found!</gradient>");
@Comment({
"# Message displayed when a player enters combat.",
"# This message warns the player not to leave the server while in combat."
"# Message displayed when a player enters combat.",
"# This message warns the player not to leave the server while in combat."
})
public Notice playerTagged = Notice.chat(
"<gradient:red:yellow>⚠ <white>You are in combat!</white> <u>Do not leave the server!</gradient>");
"<gradient:red:yellow>⚠ <white>You are in combat!</white> <u>Do not leave the server!</gradient>");
@Comment({
"# Message displayed when a player leaves combat.",
"# This message informs the player that they can safely leave the server."
"# Message displayed when a player leaves combat.",
"# This message informs the player that they can safely leave the server."
})
public Notice playerUntagged = Notice.chat(
"<gradient:#00ff00:#00b300>✌ <white>Combat ended!</white> You can now safely leave!</gradient>");
"<gradient:#00ff00:#00b300>✌ <white>Combat ended!</white> You can now safely leave!</gradient>");
@Comment({
"# Broadcast message displayed when a player logs out during combat.",
"# The {PLAYER} placeholder is replaced with the player's name."
"# Broadcast message displayed when a player logs out during combat.",
"# The {PLAYER} placeholder is replaced with the player's name."
})
public Notice playerLoggedOutDuringCombat =
Notice.chat("<gradient:red:dark_red>⚠ <white>{PLAYER}</white> logged off during combat!</gradient>");
public Notice playerLoggedOutDuringCombat = Notice
.chat("<gradient:red:dark_red>⚠ <white>{PLAYER}</white> logged off during combat!</gradient>");
@Comment({
"# Message displayed when a player attempts to use a disabled command during combat.",
"# This message informs the player that the command is prohibited while in combat."
"# Message displayed when a player attempts to use a disabled command during combat.",
"# This message informs the player that the command is prohibited while in combat."
})
public Notice commandDisabledDuringCombat = Notice.chat(
"<gradient:red:yellow>⚠ <white>Command blocked!</white> Cannot use this during combat!</gradient>");
"<gradient:red:yellow>⚠ <white>Command blocked!</white> Cannot use this during combat!</gradient>");
@Comment({
"# Message displayed when a player uses a command with incorrect arguments.",
"# The {USAGE} placeholder is replaced with the correct command syntax."
"# Message displayed when a player attempts to use an elytra during combat.",
"# This includes gliding, equipping, or having it forcefully removed.",
"# Informs the player that elytra usage is disabled in combat."
})
public Notice elytraDisabledDuringCombat = Notice.chat(
"<gradient:red:yellow>⚠ <white>Elytra disabled!</white> Cannot use elytra during combat!</gradient>"
);
@Comment({
"# Message displayed when a player uses a command with incorrect arguments.",
"# The {USAGE} placeholder is replaced with the correct command syntax."
})
public Notice invalidCommandUsage = Notice.chat("<gray>Usage: <gradient:gold:yellow>{USAGE}</gradient>");
@Comment({
"# Message displayed when a player attempts to open their inventory during combat.",
"# This message informs the player that inventory access is blocked while in combat."
"# Message displayed when a player attempts to open their inventory during combat.",
"# This message informs the player that inventory access is blocked while in combat."
})
public Notice inventoryBlockedDuringCombat = Notice.chat(
"<gradient:red:dark_red>⚠ <white>Inventory access</white> is restricted during combat!</gradient>");
"<gradient:red:dark_red>⚠ <white>Inventory access</white> is restricted during combat!</gradient>");
@Comment({
"# Message displayed when a player attempts to place a block during combat.",
"# The {MODE} placeholder is replaced with the block placement mode (ABOVE/BELOW).",
"# The {Y} placeholder is replaced with the Y coordinate set in the config."
"# Message displayed when a player attempts to place a block during combat.",
"# The {MODE} placeholder is replaced with the block placement mode (ABOVE/BELOW).",
"# The {Y} placeholder is replaced with the Y coordinate set in the config."
})
public Notice blockPlacingBlockedDuringCombat =
Notice.chat("<gradient:red:yellow>⚠ Block placement <white>{MODE} Y:{Y}</white> is restricted!</gradient>");
public Notice blockPlacingBlockedDuringCombat = Notice
.chat("<gradient:red:yellow>⚠ Block placement <white>{MODE} Y:{Y}</white> is restricted!</gradient>");
@Comment({
"# Message sent to the player using fireworks when fireworks during elytra flight are disabled"
"# Message sent to the player using fireworks when fireworks during elytra flight are disabled"
})
public Notice fireworksDisabled = Notice.actionbar("<gradient:red:yellow>⚠ You can't use fireworks during combat ⚠</gradient>");
public Notice fireworksDisabled = Notice
.actionbar("<gradient:red:yellow>⚠ You can't use fireworks during combat ⚠</gradient>");
@Comment({
"# Message displayed when a player attempts to enter a restricted region during combat.",
"# This message informs the player that they cannot enter the region while in combat."
"# Message displayed when a player attempts to enter a restricted region during combat.",
"# This message informs the player that they cannot enter the region while in combat."
})
public Notice cantEnterOnRegion = Notice.chat(
"<gradient:red:dark_red>⚠ Restricted area!</gradient> <white>Cannot enter during combat!</white>");
"<gradient:red:dark_red>⚠ Restricted area!</gradient> <white>Cannot enter during combat!</white>");
public static class AdminMessages extends OkaeriConfig {
@Comment({
"# Message displayed when the console attempts to use a player-only command.",
"# This message informs the console that the command is not available for non-players."
"# Message displayed when the console attempts to use a player-only command.",
"# This message informs the console that the command is not available for non-players."
})
public Notice onlyForPlayers =
Notice.chat("<gradient:red:dark_red>❌ This command is player-only!</gradient>");
public Notice onlyForPlayers = Notice.chat("<gradient:red:dark_red>❌ This command is player-only!</gradient>");
@Comment({
"# Message displayed to an admin when they tag a player.",
"# The {PLAYER} placeholder is replaced with the tagged player's name."
"# Message displayed to an admin when they tag a player.",
"# The {PLAYER} placeholder is replaced with the tagged player's name."
})
public Notice adminTagPlayer =
Notice.chat("<gradient:#00b3ff:#0066ff>⚔ <gray>Tagged player:</gray> <white>{PLAYER}</white></gradient>");
public Notice adminTagPlayer = Notice
.chat("<gradient:#00b3ff:#0066ff>⚔ <gray>Tagged player:</gray> <white>{PLAYER}</white></gradient>");
@Comment({
"# Message displayed when an admin tags multiple players.",
"# The {FIRST_PLAYER} and {SECOND_PLAYER} placeholders are replaced with the players' names."
"# Message displayed when an admin tags multiple players.",
"# The {FIRST_PLAYER} and {SECOND_PLAYER} placeholders are replaced with the players' names."
})
public Notice adminTagMultiplePlayers = Notice.chat(
"<gradient:#00b3ff:#0066ff>⚔ <gray>Tagged:</gray> <white>{FIRST_PLAYER}</white> <gray>and</gray> <white>{SECOND_PLAYER}</white></gradient>");
"<gradient:#00b3ff:#0066ff>⚔ <gray>Tagged:</gray> <white>{FIRST_PLAYER}</white> <gray>and</gray> <white>{SECOND_PLAYER}</white></gradient>");
@Comment({
"# Message displayed to an admin when they remove a player from combat.",
"# The {PLAYER} placeholder is replaced with the player's name."
"# Message displayed to an admin when they remove a player from combat.",
"# The {PLAYER} placeholder is replaced with the player's name."
})
public Notice adminUntagPlayer = Notice.chat(
"<gradient:#00ff88:#00b300>✌ <gray>Removed</gray> <white>{PLAYER}</white> <gray>from combat</gray></gradient>");
"<gradient:#00ff88:#00b300>✌ <gray>Removed</gray> <white>{PLAYER}</white> <gray>from combat</gray></gradient>");
@Comment({
"# Message displayed to an admin when they remove a player from combat.",
"# The {PLAYER} placeholder is replaced with the player's name."
"# 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(
"<gradient:#00ff88:#00b300>✌ <gray>Removed</gray> <white>{COUNT}</white> <gray> players from combat</gray></gradient>");
"<gradient:#00ff88:#00b300>✌ <gray>Removed</gray> <white>{COUNT}</white> <gray> players from combat</gray></gradient>");
@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."
"# 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."
})
public Notice adminPlayerNotInCombat =
Notice.chat("<gradient:red:dark_red>❌ <white>{PLAYER}</white> is not in combat!</gradient>");
public Notice adminPlayerNotInCombat = Notice
.chat("<gradient:red:dark_red>❌ <white>{PLAYER}</white> is not in combat!</gradient>");
@Comment({
"# Message displayed when a player is in combat.",
"# The {PLAYER} placeholder is replaced with the player's name."
"# Message displayed when a player is in combat.",
"# The {PLAYER} placeholder is replaced with the player's name."
})
public Notice playerInCombat =
Notice.chat("<gradient:#ff6666:#ff0000>⚔ <white>{PLAYER}</white> <gray>is in combat!</gray></gradient>");
public Notice playerInCombat = Notice
.chat("<gradient:#ff6666:#ff0000>⚔ <white>{PLAYER}</white> <gray>is in combat!</gray></gradient>");
@Comment({
"# Message displayed when a player is not in combat.",
"# The {PLAYER} placeholder is replaced with the player's name."
"# Message displayed when a player is not in combat.",
"# The {PLAYER} placeholder is replaced with the player's name."
})
public Notice playerNotInCombat =
Notice.chat("<gradient:#00ff00:#00b300>✌ <white>{PLAYER}</white> <gray>is safe</gray></gradient>");
public Notice playerNotInCombat = Notice
.chat("<gradient:#00ff00:#00b300>✌ <white>{PLAYER}</white> <gray>is safe</gray></gradient>");
@Comment({
"# Message displayed when an admin attempts to tag themselves.",
"# This message informs the admin that they cannot tag themselves."
"# Message displayed when an admin attempts to tag themselves.",
"# This message informs the admin that they cannot tag themselves."
})
public Notice adminCannotTagSelf = Notice.chat("<gradient:red:dark_red>❌ Cannot tag yourself!</gradient>");
@Comment({
"# Message displayed when an admin disables combat tagging for themselves.",
"# The {TIME} placeholder is replaced with the remaining fight time."
"# Message displayed when an admin disables combat tagging for themselves.",
"# The {TIME} placeholder is replaced with the remaining fight time."
})
public Notice adminTagOutSelf = Notice.chat(
"<gradient:#00b3ff:#0066ff>🛡 <gray>Self-protection active for</gray> <white>{TIME}</white></gradient>");
"<gradient:#00b3ff:#0066ff>🛡 <gray>Self-protection active for</gray> <white>{TIME}</white></gradient>");
@Comment({
"# Message displayed when an admin disables combat tagging for another player.",
"# The {PLAYER} placeholder is replaced with the player's name.",
"# The {TIME} placeholder is replaced with the remaining fight time."
"# Message displayed when an admin disables combat tagging for another player.",
"# The {PLAYER} placeholder is replaced with the player's name.",
"# The {TIME} placeholder is replaced with the remaining fight time."
})
public Notice adminTagOut = Notice.chat(
"<gradient:#00b3ff:#0066ff>🛡 <gray>Protected</gray> <white>{PLAYER}</white> <gray>for</gray> <white>{TIME}</white></gradient>");
"<gradient:#00b3ff:#0066ff>🛡 <gray>Protected</gray> <white>{PLAYER}</white> <gray>for</gray> <white>{TIME}</white></gradient>");
@Comment({
"# Message displayed to a player when their combat tagging is disabled.",
"# The {TIME} placeholder is replaced with the remaining fight time."
"# Message displayed to a player when their combat tagging is disabled.",
"# The {TIME} placeholder is replaced with the remaining fight time."
})
public Notice playerTagOut = Notice.chat(
"<gradient:#00ff88:#00b300>🛡 <gray>Protection active for</gray> <white>{TIME}</white></gradient>");
"<gradient:#00ff88:#00b300>🛡 <gray>Protection active for</gray> <white>{TIME}</white></gradient>");
@Comment({
"# Message displayed when an admin reenables combat tagging for a player.",
"# The {PLAYER} placeholder is replaced with the player's name."
"# Message displayed when an admin reenables combat tagging for a player.",
"# The {PLAYER} placeholder is replaced with the player's name."
})
public Notice adminTagOutOff = Notice.chat(
"<gradient:#00ff88:#00b300>✌ <gray>Re-enabled tagging for</gray> <white>{PLAYER}</white></gradient>");
@@ -205,7 +220,14 @@ public class MessagesSettings extends OkaeriConfig {
"# Message displayed when an admin attempts to tag a player who has tag-out enabled.",
"# This message informs the admin that the player cannot be tagged at this time."
})
public Notice adminTagOutCanceled =
Notice.chat("<gradient:red:dark_red>❌ Player has tag-out protection!</gradient>");
public Notice adminTagOutCanceled = Notice
.chat("<gradient:red:dark_red>❌ Player has tag-out protection!</gradient>");
@Comment({
"# Message displayed when an admin checks combat statistics.",
"# The {COUNT} placeholder is replaced with the number of players currently in combat."
})
public Notice combatStats = Notice
.chat("<gradient:#ff6666:#ff0000>⚔ <gray>Players in combat:</gray> <white>{COUNT}</white></gradient>");
}
}
@@ -5,7 +5,8 @@ import com.eternalcode.combat.fight.death.DeathSettings;
import com.eternalcode.combat.fight.drop.DropSettings;
import com.eternalcode.combat.fight.effect.FightEffectSettings;
import com.eternalcode.combat.fight.knockback.KnockbackSettings;
import com.eternalcode.combat.fight.pearl.FightPearlSettings;
import com.eternalcode.combat.fight.pearl.PearlSettings;
import com.eternalcode.combat.fight.trident.TridentSettings;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
@@ -34,7 +35,14 @@ public class PluginConfig extends OkaeriConfig {
"# Settings related to Ender Pearls.",
"# Configure cooldowns, restrictions, and other behaviors for Ender Pearls during combat."
})
public FightPearlSettings pearl = new FightPearlSettings();
public PearlSettings pearl = new PearlSettings();
@Comment({
" ",
"# Settings related to Trident",
"# Configure cooldowns, restrictions, and other behaviors for Trident during combat."
})
public TridentSettings trident = new TridentSettings();
@Comment({
" ",
@@ -3,12 +3,11 @@ 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.block.Block;
import org.bukkit.entity.EnderCrystal;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.EntityDamageByBlockEvent;
@@ -23,21 +22,6 @@ public class 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<UUID> getDamagerUniqueIdFromEndCrystal(EntityDamageByEntityEvent event) {
if (event.getDamager() instanceof EnderCrystal enderCrystal) {
List<MetadataValue> metadataValues = enderCrystal.getMetadata(CRYSTAL_METADATA);
@@ -52,20 +36,16 @@ public class CrystalPvpConstants {
}
public static Optional<UUID> getDamagerUniqueIdFromRespawnAnchor(EntityDamageByBlockEvent event) {
if (!CrystalPvpConstants.hasDamagerBlockState()) {
Block damager = event.getDamager();
if (damager == null) {
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)) {
if (damager.getType() != Material.RESPAWN_ANCHOR) {
return Optional.empty();
}
return state.getMetadata(ANCHOR_METADATA).stream()
return damager.getMetadata(ANCHOR_METADATA).stream()
.filter(source -> source instanceof CrystalMetadata)
.map(meta -> (CrystalMetadata) meta)
.findFirst()
@@ -16,10 +16,8 @@ import dev.rollczi.litecommands.annotations.permission.Permission;
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;
@@ -49,7 +47,6 @@ public class FightTagCommand {
.placeholder("{PLAYER}", target.getName())
.viewer(sender)
.send();
}
@Execute(name = "tag")
@@ -74,7 +71,6 @@ public class FightTagCommand {
.placeholder("{PLAYER}", target.getName())
.viewer(sender)
.send();
}
@Execute(name = "tag")
@@ -122,7 +118,6 @@ public class FightTagCommand {
.placeholder("{SECOND_PLAYER}", secondTarget.getName())
.viewer(sender)
.send();
}
@Execute(name = "untag")
@@ -144,7 +139,6 @@ public class FightTagCommand {
return;
}
this.noticeService.create()
.notice(this.config.messagesSettings.admin.adminUntagPlayer)
.placeholder("{PLAYER}", target.getName())
@@ -169,13 +163,28 @@ public class FightTagCommand {
.send();
}
private void tagoutReasonHandler(CommandSender sender, CancelTagReason cancelReason, MessagesSettings messagesSettings) {
@Execute(name = "stats")
@Permission("eternalcombat.stats")
void stats(@Context CommandSender sender) {
long activeCombatPlayers = this.fightManager.getFights().stream()
.filter(fightTag -> !fightTag.isExpired())
.count();
this.noticeService.create()
.notice(this.config.messagesSettings.admin.combatStats)
.placeholder("{COUNT}", String.valueOf(activeCombatPlayers))
.viewer(sender)
.send();
}
private void tagoutReasonHandler(
CommandSender sender, CancelTagReason cancelReason,
MessagesSettings messagesSettings) {
if (cancelReason == CancelTagReason.TAGOUT) {
this.noticeService.create()
.notice(messagesSettings.admin.adminTagOutCanceled)
.viewer(sender)
.send();
}
}
}
@@ -4,6 +4,7 @@ import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.notification.NoticeService;
import com.eternalcode.combat.util.DurationUtil;
import java.util.Optional;
import org.bukkit.Server;
import org.bukkit.entity.Player;
@@ -17,7 +18,7 @@ public class FightTask implements Runnable {
private final FightManager fightManager;
private final NoticeService noticeService;
public FightTask(Server server, PluginConfig config, FightManager fightManager, NoticeService noticeService) {
public FightTask(Server server, PluginConfig config, FightManager fightManager, NoticeService noticeService) {
this.server = server;
this.config = config;
this.fightManager = fightManager;
@@ -42,10 +43,17 @@ public class FightTask implements Runnable {
Duration remaining = fightTag.getRemainingDuration();
String opponent = Optional.ofNullable(fightTag.getTagger())
.filter(uuid -> !uuid.equals(playerUniqueId))
.map(this.server::getPlayer)
.map(Player::getName)
.orElse(this.config.messagesSettings.unknownPlayerPlaceholder);
this.noticeService.create()
.player(player.getUniqueId())
.notice(this.config.messagesSettings.combatNotification)
.placeholder("{TIME}", DurationUtil.format(remaining, this.config.messagesSettings.withoutMillis))
.placeholder("{OPPONENT}", opponent)
.send();
}
@@ -0,0 +1,54 @@
package com.eternalcode.combat.fight.blocker;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NoticeService;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.util.StringUtil;
public class CommandsBlocker implements Listener {
private final FightManager fightManager;
private final NoticeService noticeService;
private final PluginConfig config;
public CommandsBlocker(FightManager fightManager, NoticeService noticeService, PluginConfig config) {
this.fightManager = fightManager;
this.noticeService = noticeService;
this.config = config;
}
@EventHandler
void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
Player player = event.getPlayer();
UUID playerUniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(playerUniqueId)) {
return;
}
String command = event.getMessage().substring(1);
boolean isAnyMatch = this.config.commands.restrictedCommands.stream()
.anyMatch(restrictedCommand -> StringUtil.startsWithIgnoreCase(command, restrictedCommand));
WhitelistBlacklistMode mode = this.config.commands.commandRestrictionMode;
boolean shouldCancel = mode.shouldBlock(isAnyMatch);
if (shouldCancel) {
event.setCancelled(true);
this.noticeService.create()
.player(playerUniqueId)
.notice(this.config.messagesSettings.commandDisabledDuringCombat)
.send();
}
}
}
@@ -0,0 +1,105 @@
package com.eternalcode.combat.fight.blocker;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.time.Duration;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityToggleGlideEvent;
import org.bukkit.event.player.PlayerMoveEvent;
public class ElytraBlocker implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
private final Cache<UUID, Boolean> fallProtection = Caffeine.newBuilder()
.expireAfterWrite(Duration.ofSeconds(10))
.build();
public ElytraBlocker(FightManager fightManager, PluginConfig config) {
this.fightManager = fightManager;
this.config = config;
}
@EventHandler
void onToggleGlide(EntityToggleGlideEvent event) {
if (!this.config.combat.disableElytraUsage) {
return;
}
if (!(event.getEntity() instanceof Player player)) {
return;
}
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
if (event.isGliding()) {
event.setCancelled(true);
player.setGliding(false);
}
}
@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);
player.setVelocity(player.getVelocity().setY(-10));
player.setFallDistance(0f);
fallProtection.put(uniqueId, true);
}
}
@EventHandler
public void onEntityDamage(EntityDamageEvent event) {
if (!(event.getEntity() instanceof Player player)) {
return;
}
if (event.getCause() != EntityDamageEvent.DamageCause.FALL) {
return;
}
if (fallProtection.get(player.getUniqueId(), key -> false)) {
event.setCancelled(true);
}
}
@EventHandler
void onDamage(EntityDamageEvent event) {
if (!this.config.combat.disableElytraOnDamage) {
return;
}
if (!(event.getEntity() instanceof Player player)) {
return;
}
UUID uniqueId = player.getUniqueId();
if (this.fightManager.isInCombat(uniqueId) && player.isGliding()) {
player.setGliding(false);
}
}
}
@@ -0,0 +1,132 @@
package com.eternalcode.combat.fight.blocker;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.notification.NoticeService;
import java.util.HashMap;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
public class ElytraEquipBlocker implements Listener {
private final FightManager fightManager;
private final NoticeService noticeService;
private final PluginConfig config;
private final Server server;
public ElytraEquipBlocker(FightManager fightManager, NoticeService noticeService, PluginConfig config, Server server) {
this.fightManager = fightManager;
this.noticeService = noticeService;
this.config = config;
this.server = server;
}
@EventHandler
void onInventoryClick(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
if (!this.config.combat.unequipElytraOnCombat) {
return;
}
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
if (event.getCurrentItem() == null) {
return;
}
if (event.getCurrentItem().getType() == Material.ELYTRA) {
event.setCancelled(true);
this.noticeService.create()
.player(uniqueId)
.notice(this.config.messagesSettings.elytraDisabledDuringCombat)
.send();
}
}
@EventHandler
void onTag(FightTagEvent event) {
UUID uniqueId = event.getPlayer();
Player player = this.server.getPlayer(uniqueId);
if (player == null) {
return;
}
if (this.config.combat.unequipElytraOnCombat) {
ItemStack chest = player.getInventory().getChestplate();
if (chest != null && chest.getType() == Material.ELYTRA) {
removeChestplateIfElytra(player);
this.noticeService.create()
.player(uniqueId)
.notice(this.config.messagesSettings.elytraDisabledDuringCombat)
.send();
}
}
}
private void removeChestplateIfElytra(Player player) {
ItemStack chestplate = player.getInventory().getChestplate();
if (chestplate != null && chestplate.getType() == Material.ELYTRA) {
player.getInventory().setChestplate(null);
HashMap<Integer, ItemStack> leftover = player.getInventory().addItem(chestplate);
leftover.values().forEach(item ->
player.getWorld().dropItemNaturally(player.getLocation(), item)
);
}
}
@EventHandler
void onInteract(PlayerInteractEvent event) {
if (!this.config.combat.unequipElytraOnCombat) {
return;
}
if (event.getItem() == null) {
return;
}
if (event.getItem().getType() != Material.ELYTRA) {
return;
}
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
switch (event.getAction()) {
case RIGHT_CLICK_AIR, RIGHT_CLICK_BLOCK -> {
event.setCancelled(true);
this.noticeService.create()
.player(uniqueId)
.notice(this.config.messagesSettings.elytraDisabledDuringCombat)
.send();
}
}
}
}
@@ -0,0 +1,86 @@
package com.eternalcode.combat.fight.blocker;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import java.util.UUID;
import org.bukkit.GameMode;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerToggleFlightEvent;
public class FlyingBlocker implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
private final Server server;
public FlyingBlocker(FightManager fightManager, PluginConfig config, Server server) {
this.fightManager = fightManager;
this.config = config;
this.server = server;
}
@EventHandler
void onFly(PlayerToggleFlightEvent event) {
if (!this.config.combat.disableFlying) {
return;
}
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
if (event.isFlying()) {
player.setAllowFlight(false);
event.setCancelled(true);
}
}
@EventHandler
void onTag(FightTagEvent event) {
UUID uniqueId = event.getPlayer();
Player player = this.server.getPlayer(uniqueId);
if (player == null) {
return;
}
if (this.config.combat.disableFlying) {
GameMode gameMode = player.getGameMode();
if (gameMode != GameMode.CREATIVE && gameMode != GameMode.SPECTATOR) {
player.setAllowFlight(false);
player.setFlying(false);
}
}
}
@EventHandler
void onUnTag(FightUntagEvent event) {
if (!this.config.combat.disableFlying) {
return;
}
UUID uniqueId = event.getPlayer();
Player player = this.server.getPlayer(uniqueId);
if (player == null) {
return;
}
GameMode playerGameMode = player.getGameMode();
if (playerGameMode == GameMode.CREATIVE || playerGameMode == GameMode.SPECTATOR) {
player.setAllowFlight(true);
}
}
}
@@ -1,4 +1,4 @@
package com.eternalcode.combat.fight.controller;
package com.eternalcode.combat.fight.blocker;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
@@ -10,13 +10,13 @@ import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.inventory.InventoryType;
public class FightInventoryController implements Listener {
public class InventoryContainersBlocker implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
private final NoticeService noticeService;
public FightInventoryController(FightManager fightManager, PluginConfig config, NoticeService noticeService) {
public InventoryContainersBlocker(FightManager fightManager, PluginConfig config, NoticeService noticeService) {
this.fightManager = fightManager;
this.config = config;
this.noticeService = noticeService;
@@ -0,0 +1,81 @@
package com.eternalcode.combat.fight.blocker;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.config.implementation.BlockPlacementSettings;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NoticeService;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;
import java.util.List;
import java.util.UUID;
public class PlaceBlockBlocker implements Listener {
private final FightManager fightManager;
private final NoticeService noticeService;
private final PluginConfig config;
public PlaceBlockBlocker(FightManager fightManager, NoticeService noticeService, PluginConfig config) {
this.fightManager = fightManager;
this.noticeService = noticeService;
this.config = config;
}
@EventHandler
void onPlace(BlockPlaceEvent event) {
if (!this.config.blockPlacement.disableBlockPlacing) {
return;
}
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
Block block = event.getBlock();
int level = block.getY();
List<Material> specificBlocksToPreventPlacing = this.config.blockPlacement.restrictedBlockTypes;
boolean isPlacementBlocked = this.isPlacementBlocked(level);
if (isPlacementBlocked && specificBlocksToPreventPlacing.isEmpty()) {
event.setCancelled(true);
this.noticeService.create()
.player(uniqueId)
.notice(this.config.messagesSettings.blockPlacingBlockedDuringCombat)
.placeholder("{Y}", String.valueOf(this.config.blockPlacement.blockPlacementYCoordinate))
.placeholder("{MODE}", this.config.blockPlacement.blockPlacementModeDisplayName)
.send();
}
Material blockMaterial = block.getType();
boolean isBlockInDisabledList = specificBlocksToPreventPlacing.contains(blockMaterial);
if (isPlacementBlocked && isBlockInDisabledList) {
event.setCancelled(true);
this.noticeService.create()
.player(uniqueId)
.notice(this.config.messagesSettings.blockPlacingBlockedDuringCombat)
.placeholder("{Y}", String.valueOf(this.config.blockPlacement.blockPlacementYCoordinate))
.placeholder("{MODE}", this.config.blockPlacement.blockPlacementModeDisplayName)
.send();
}
}
private boolean isPlacementBlocked(int level) {
return this.config.blockPlacement.blockPlacementMode == BlockPlacementSettings.BlockPlacingMode.ABOVE
? level > this.config.blockPlacement.blockPlacementYCoordinate
: level < this.config.blockPlacement.blockPlacementYCoordinate;
}
}
@@ -1,216 +0,0 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.config.implementation.BlockPlacementSettings;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import com.eternalcode.combat.notification.NoticeService;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityToggleGlideEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.inventory.InventoryType;
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 noticeService;
private final PluginConfig config;
private final Server server;
public FightActionBlockerController(FightManager fightManager, NoticeService noticeService, PluginConfig config, Server server) {
this.fightManager = fightManager;
this.noticeService = noticeService;
this.config = config;
this.server = server;
}
@EventHandler
void onPlace(BlockPlaceEvent event) {
if (!this.config.blockPlacement.disableBlockPlacing) {
return;
}
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
Block block = event.getBlock();
int level = block.getY();
List<Material> specificBlocksToPreventPlacing = this.config.blockPlacement.restrictedBlockTypes;
boolean isPlacementBlocked = this.isPlacementBlocked(level);
if (isPlacementBlocked && specificBlocksToPreventPlacing.isEmpty()) {
event.setCancelled(true);
this.noticeService.create()
.player(uniqueId)
.notice(this.config.messagesSettings.blockPlacingBlockedDuringCombat)
.placeholder("{Y}", String.valueOf(this.config.blockPlacement.blockPlacementYCoordinate))
.placeholder("{MODE}", this.config.blockPlacement.blockPlacementModeDisplayName)
.send();
}
Material blockMaterial = block.getType();
boolean isBlockInDisabledList = specificBlocksToPreventPlacing.contains(blockMaterial);
if (isPlacementBlocked && isBlockInDisabledList) {
event.setCancelled(true);
this.noticeService.create()
.player(uniqueId)
.notice(this.config.messagesSettings.blockPlacingBlockedDuringCombat)
.placeholder("{Y}", String.valueOf(this.config.blockPlacement.blockPlacementYCoordinate))
.placeholder("{MODE}", this.config.blockPlacement.blockPlacementModeDisplayName)
.send();
}
}
private boolean isPlacementBlocked(int level) {
return this.config.blockPlacement.blockPlacementMode == BlockPlacementSettings.BlockPlacingMode.ABOVE
? level > this.config.blockPlacement.blockPlacementYCoordinate
: level < this.config.blockPlacement.blockPlacementYCoordinate;
}
@EventHandler
void onToggleGlide(EntityToggleGlideEvent event) {
if (!this.config.combat.disableElytraUsage) {
return;
}
if (!(event.getEntity() instanceof Player player)) {
return;
}
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
if (event.isGliding()) {
event.setCancelled(true);
}
}
@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) {
return;
}
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
if (event.isFlying()) {
player.setAllowFlight(false);
event.setCancelled(true);
}
}
@EventHandler
void onUnTag(FightUntagEvent event) {
if (!this.config.combat.disableFlying) {
return;
}
UUID uniqueId = event.getPlayer();
Player player = this.server.getPlayer(uniqueId);
if (player == null) {
return;
}
GameMode playerGameMode = player.getGameMode();
if (playerGameMode == GameMode.CREATIVE || playerGameMode == GameMode.SPECTATOR) {
player.setAllowFlight(true);
}
}
@EventHandler
void onDamage(EntityDamageEvent event) {
if (!this.config.combat.disableElytraOnDamage) {
return;
}
if (!(event.getEntity() instanceof Player player)) {
return;
}
UUID uniqueId = player.getUniqueId();
if (this.fightManager.isInCombat(uniqueId) && player.isGliding()) {
player.setGliding(false);
}
}
@EventHandler
void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
Player player = event.getPlayer();
UUID playerUniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(playerUniqueId)) {
return;
}
String command = event.getMessage().substring(1);
boolean isAnyMatch = this.config.commands.restrictedCommands.stream()
.anyMatch(restrictedCommand -> StringUtil.startsWithIgnoreCase(command, restrictedCommand));
WhitelistBlacklistMode mode = this.config.commands.commandRestrictionMode;
boolean shouldCancel = mode.shouldBlock(isAnyMatch);
if (shouldCancel) {
event.setCancelled(true);
this.noticeService.create()
.player(playerUniqueId)
.notice(this.config.messagesSettings.commandDisabledDuringCombat)
.send();
}
}
}
@@ -4,7 +4,6 @@ import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -50,9 +49,11 @@ public class FightEffectController implements Listener {
}
if (event.getCause() == CauseOfUnTag.LOGOUT) {
Player player = Bukkit.getPlayer(event.getPlayer());
Player player = this.server.getPlayer(event.getPlayer());
if (player == null) {
return;
}
assert player != null;
this.effectService.clearStoredEffects(player);
}
}
@@ -80,9 +81,11 @@ public class FightEffectController implements Listener {
return;
}
if (event.getCause() == CauseOfUnTag.DEATH_BY_PLAYER || event.getCause() == CauseOfUnTag.DEATH) {
Player player = Bukkit.getPlayer(event.getPlayer());
Player player = this.server.getPlayer(event.getPlayer());
if (player == null) {
return;
}
assert player != null;
this.effectService.clearStoredEffects(player);
}
}
@@ -1,11 +1,10 @@
package com.eternalcode.combat.fight.effect;
import com.cryptomorin.xseries.XPotion;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import org.bukkit.potion.PotionEffectType;
import java.util.Map;
import org.bukkit.potion.PotionEffectType;
public class FightEffectSettings extends OkaeriConfig {
@@ -16,11 +15,20 @@ public class FightEffectSettings extends OkaeriConfig {
"# If the option above is set to true, you can add effects to players in combat below",
"# You can find a list of all potion effects here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/potion/PotionEffectType.html",
"# Correct format: 'EFFECT_TYPE:AMPLIFIER' Amplifier strength starts from 0, so level 1 gives effect strength 2",
"# Example: SPEED:1, DAMAGE_RESISTANCE:0",
"# Example: SPEED:1, RESISTANCE:0",
})
public Map<PotionEffectType, Integer> customEffects = Map.of(
PotionEffectType.SPEED, 1,
PotionEffectType.DAMAGE_RESISTANCE, 0
requirePotionEffectType(XPotion.SPEED), 1,
requirePotionEffectType(XPotion.RESISTANCE), 0
);
private static PotionEffectType requirePotionEffectType(XPotion potion) {
PotionEffectType potionEffectType = potion.get();
if (potionEffectType == null) {
throw new IllegalStateException("Potion effect " + potion.name() + " is not supported by this server version");
}
return potionEffectType;
}
}
@@ -3,53 +3,21 @@ package com.eternalcode.combat.fight.knockback;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import org.bukkit.Location;
class KnockbackOutsideRegionGenerator {
static Location generate(Point2D min, Point2D max, Location playerLocation) {
NavigableMap<Double, List<Point2D>> points = generatePoints(min, max, Point2D.from(playerLocation));
NavigableMap<Double, Double> distances = new TreeMap<>();
double totalWeight = 0;
Double maxDistance = points.lastKey();
for (double distance : points.keySet()) {
double weight = createWeight(distance, maxDistance);
distances.put(distance, weight);
totalWeight += weight;
}
double rand = Math.random() * totalWeight;
double cumulativeWeight = 0;
for (Map.Entry<Double, Double> entry : distances.entrySet()) {
double distance = entry.getKey();
double weight = entry.getValue();
cumulativeWeight += weight;
if (rand <= cumulativeWeight) {
return getRandom(points.get(distance))
.toLocation(playerLocation);
}
}
return getRandom(points.firstEntry().getValue())
.toLocation(playerLocation);
}
private static Point2D getRandom(List<Point2D> points) {
return points.get((int) (Math.random() * points.size()));
}
private static double createWeight(double distance, double maxDistance) {
double last = Math.log(maxDistance);
double weight = last - Math.log(distance);
return Math.pow(weight, 10);
static Optional<Location> generate(Point2D min, Point2D max, Location playerLocation, Function<Location, Optional<Location>> safeMapper) {
return generatePoints(min, max, Point2D.from(playerLocation)).entrySet().stream()
.flatMap(entry -> entry.getValue().stream())
.map(point2D -> point2D.toLocation(playerLocation))
.flatMap(safeMapper.andThen(validPoint -> validPoint.stream()))
.findFirst();
}
private static NavigableMap<Double, List<Point2D>> generatePoints(Point2D min, Point2D max, Point2D location) {
@@ -78,5 +46,4 @@ class KnockbackOutsideRegionGenerator {
return Math.sqrt(Math.pow(p2.x() - p1.x(), 2) + Math.pow(p2.z() - p1.z(), 2));
}
}
@@ -1,23 +1,29 @@
package com.eternalcode.combat.fight.knockback;
import com.cryptomorin.xseries.XMaterial;
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.bukkit.scheduler.MinecraftScheduler;
import io.papermc.lib.PaperLib;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.util.Vector;
public final class KnockbackService {
private static final int FORCE_TELEPORT_MAX_ATTEMPTS = 10;
private static final double KNOCKBACK_DAMPEN_FACTOR = 0.8;
public static final int DEPTH_OF_SEARCHING = 10;
private final PluginConfig config;
private final MinecraftScheduler scheduler;
private final RegionProvider regionProvider;
@@ -31,20 +37,63 @@ public final class KnockbackService {
}
public void knockbackLater(Region region, Player player, Duration duration) {
this.scheduler.runLater(() -> this.knockback(region, player), duration);
scheduler.runLater(() -> this.knockback(region, player), duration);
}
public void knockback(Region region, Player player) {
if (player.isInsideVehicle()) {
player.leaveVehicle();
}
Location currentLocation = player.getLocation();
Vector shortestDirection = getShortestDirectionToEdge(region, currentLocation);
// Reduces player's current velocity BEFORE applying knockback. Helps create a smoother and more consistent knockback.
player.setVelocity(player.getVelocity().multiply(KNOCKBACK_DAMPEN_FACTOR));
double y = Math.min(player.getVelocity().getY(), config.knockback.vertical);
Vector velocity = shortestDirection.multiply(config.knockback.multiplier).setY(y);
player.setFallDistance(0);
player.setVelocity(velocity);
}
private Vector getShortestDirectionToEdge(Region region, Location current) {
double directionToBottomEdge = current.getX() - region.getMin().getX();
double directionToTopEdge = region.getMax().getX() - current.getX();
double directionToLeftEdge = current.getZ() - region.getMin().getZ();
double directionToRightEdge = region.getMax().getZ() - current.getZ();
double shortestDirection = Math.min(
Math.min(directionToBottomEdge, directionToTopEdge),
Math.min(directionToLeftEdge, directionToRightEdge)
);
if (shortestDirection == directionToBottomEdge)
return new Vector(-1, 0, 0);
if (shortestDirection == directionToTopEdge)
return new Vector(1, 0, 0);
if (shortestDirection == directionToLeftEdge)
return new Vector(0, 0, -1);
if (shortestDirection == directionToRightEdge)
return new Vector(0, 0, 1);
throw new IllegalStateException("Failed to determine direction to edge");
}
public void forceKnockbackLater(Player player, Region region) {
if (insideRegion.containsKey(player.getUniqueId())) {
UUID playerId = player.getUniqueId();
if (insideRegion.containsKey(playerId)) {
return;
}
insideRegion.put(player.getUniqueId(), region);
insideRegion.put(playerId, region);
scheduler.runLater(player.getLocation(), () -> {
insideRegion.remove(player.getUniqueId());
insideRegion.remove(playerId);
Location playerLocation = player.getLocation();
if (!region.contains(playerLocation) && !regionProvider.isInRegion(playerLocation)) {
return;
}
@@ -53,35 +102,76 @@ public final class KnockbackService {
player.leaveVehicle();
}
Location location = generate(playerLocation, Point2D.from(region.getMin()), Point2D.from(region.getMax()));
PaperLib.teleportAsync(player, location, TeleportCause.PLUGIN);
}, this.config.knockback.forceDelay);
generate(playerLocation, Point2D.from(region.getMin()), Point2D.from(region.getMax()), 0)
.ifPresent(location -> player.teleportAsync(location, TeleportCause.PLUGIN));
}, config.knockback.forceTeleport.delay);
}
private Location generate(Location playerLocation, Point2D minX, Point2D maxX) {
Location location = KnockbackOutsideRegionGenerator.generate(minX, maxX, playerLocation);
Optional<Region> otherRegion = regionProvider.getRegion(location);
private Optional<Location> generate(Location playerLocation, Point2D min, Point2D max, int attempts) {
if (attempts >= FORCE_TELEPORT_MAX_ATTEMPTS) {
return Optional.of(playerLocation);
}
Optional<Location> location = KnockbackOutsideRegionGenerator.generate(min, max, playerLocation, candidate -> makeSafe(candidate));
if (location.isEmpty()) {
int expand = (attempts + 1) * (attempts + 1);
return generate(playerLocation, min.expandMin(expand, expand), max.expandMax(expand, expand), attempts + 1);
}
Optional<Region> otherRegion = regionProvider.getRegion(location.get());
if (otherRegion.isPresent()) {
Region region = otherRegion.get();
return generate(playerLocation, minX.min(region.getMin()), maxX.max(region.getMax()));
return generate(playerLocation, min.min(region.getMin()), max.max(region.getMax()), attempts + 1);
}
return location;
return location.map(loc -> loc.add(0.5, 0, 0.5));
}
public void knockback(Region region, Player player) {
if (player.isInsideVehicle()) {
player.leaveVehicle();
private Optional<Location> makeSafe(Location random) {
Location maybeSafe = random.clone();
int minY = maybeSafe.getBlockY() - DEPTH_OF_SEARCHING;
CachedPillarOfBlocks pillar = new CachedPillarOfBlocks(maybeSafe.getBlockX(), maybeSafe.getBlockZ(), maybeSafe.getWorld());
for (int y = maybeSafe.getBlockY(); y > minY; y--) {
maybeSafe.setY(y);
Material ground = pillar.get(maybeSafe.getBlockY() - 1);
Material legs = pillar.get(maybeSafe.getBlockY());
Material head = pillar.get(maybeSafe.getBlockY() + 1);
if (ground.isSolid()
&& !containsMaterial(config.knockback.forceTeleport.unsafeGroundBlocks, ground)
&& containsMaterial(config.knockback.forceTeleport.airBlocks, legs)
&& containsMaterial(config.knockback.forceTeleport.airBlocks, head)
) {
return Optional.of(maybeSafe);
}
}
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;
Vector configuredVector = new Vector(multiplier, 0.5, multiplier);
player.setVelocity(knockbackVector.multiply(configuredVector));
return Optional.empty();
}
private boolean containsMaterial(Set<XMaterial> materials, Material material) {
return materials.contains(XMaterial.matchXMaterial(material));
}
private static class CachedPillarOfBlocks {
private final int x;
private final int z;
private final World world;
private final Map<Integer, Material> blocksTypes = new HashMap<>();
private CachedPillarOfBlocks(int x, int z, World world) {
this.x = x;
this.z = z;
this.world = world;
}
public Material get(int y) {
return this.blocksTypes.computeIfAbsent(y, __ -> world.getType(x, y, z));
}
}
}
@@ -1,19 +1,138 @@
package com.eternalcode.combat.fight.knockback;
import com.cryptomorin.xseries.XMaterial;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
import java.util.EnumSet;
import java.util.Set;
public class KnockbackSettings extends OkaeriConfig {
@Comment({
"# Adjust the knockback multiplier for restricted regions.",
"# Higher values increase the knockback distance. Avoid using negative values.",
"# A value of 1.0 typically knocks players 2-4 blocks away."
"# Horizontal knockback strength multiplier.",
"# This controls how far the player is pushed horizontally.",
"#",
"# Applied in: direction.multiply(multiplier)",
"# Example: 1.0 ≈ 2–4 blocks push"
})
public double multiplier = 1;
public double multiplier = 1.0;
@Comment({
"# Vertical velocity applied when the player is considered on ground.",
"#",
"# Used when Y velocity is near zero (< 0.08).",
"# Higher values = more upward knockback.",
"# Recommended: 0.15 - 0.3"
})
public double vertical = 0.2;
@Comment({
"# Force teleport settings for players that fail to be knocked back outside the region.",
"# If a player cannot be knocked back outside the region after multiple attempts,",
"# they will be teleported to a safe location outside the region after a short delay.",
"# This ensures players don't get stuck inside the region due to knockback failures."
})
public ForceTeleport forceTeleport = new ForceTeleport();
public static class ForceTeleport extends OkaeriConfig {
@Comment({
"# Delay before force teleport is applied after entering a region.",
"# Recommended: 1-3 seconds to allow knockback attempts before teleporting.",
})
public Duration delay = Duration.ofSeconds(1);
@Comment({
"# Blocks that are considered unsafe to stand on when searching for ground.",
"# These blocks will be ignored during safe ground detection.",
"#",
"# Used in findSafeGround():",
"# - Skips these blocks even if they are solid",
"# - Prevents teleporting onto dangerous or invalid blocks",
"#",
"# Examples:",
"# - BARRIER (invisible collision)",
"# - LAVA / WATER (damage / movement issues)",
"# - CACTUS / MAGMA (damage blocks)",
})
public Set<XMaterial> unsafeGroundBlocks = EnumSet.of(
XMaterial.BARRIER,
XMaterial.LAVA,
XMaterial.WATER,
XMaterial.CACTUS,
XMaterial.MAGMA_BLOCK,
XMaterial.FIRE,
XMaterial.SOUL_FIRE,
XMaterial.COBWEB,
XMaterial.SWEET_BERRY_BUSH,
XMaterial.BEDROCK,
XMaterial.TNT,
XMaterial.SEAGRASS,
XMaterial.TALL_SEAGRASS,
XMaterial.BUBBLE_COLUMN,
XMaterial.POWDER_SNOW,
XMaterial.WITHER_ROSE
);
@Comment({
"# Safe blocks that players can be teleported into",
"# These blocks don't cause damage and allow free movement",
"# Players can safely spawn in these blocks or pass through them",
"# Includes air, grass, flowers, and other non-solid blocks"
})
public Set<XMaterial> airBlocks = EnumSet.of(
XMaterial.AIR,
XMaterial.CAVE_AIR,
XMaterial.VOID_AIR,
XMaterial.TALL_SEAGRASS,
XMaterial.SEAGRASS,
XMaterial.SHORT_GRASS,
XMaterial.TALL_GRASS,
XMaterial.VINE,
XMaterial.STRUCTURE_VOID,
XMaterial.DEAD_BUSH,
XMaterial.DANDELION,
XMaterial.POPPY,
XMaterial.BLUE_ORCHID,
XMaterial.ALLIUM,
XMaterial.AZURE_BLUET,
XMaterial.RED_TULIP,
XMaterial.ORANGE_TULIP,
XMaterial.WHITE_TULIP,
XMaterial.PINK_TULIP,
XMaterial.OXEYE_DAISY,
XMaterial.CORNFLOWER,
XMaterial.LILY_OF_THE_VALLEY,
XMaterial.SUNFLOWER,
XMaterial.LILAC,
XMaterial.ROSE_BUSH,
XMaterial.PEONY,
XMaterial.WITHER_ROSE,
XMaterial.LARGE_FERN,
XMaterial.RAIL,
XMaterial.POWERED_RAIL,
XMaterial.DETECTOR_RAIL,
XMaterial.ACTIVATOR_RAIL,
XMaterial.REDSTONE_WIRE,
XMaterial.COMPARATOR,
XMaterial.REPEATER,
XMaterial.LEVER,
XMaterial.STRING,
XMaterial.SNOW,
XMaterial.CRIMSON_ROOTS,
XMaterial.WARPED_ROOTS,
XMaterial.CRIMSON_FUNGUS,
XMaterial.WARPED_FUNGUS,
XMaterial.TORCH,
XMaterial.WALL_TORCH,
XMaterial.REDSTONE_TORCH,
XMaterial.REDSTONE_WALL_TORCH,
XMaterial.SOUL_TORCH,
XMaterial.SOUL_WALL_TORCH
);
}
@Comment({ "# Time after which the player will be force knocked back outside the safe zone" })
public Duration forceDelay = Duration.ofSeconds(1);
}
@@ -5,11 +5,13 @@ 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;
Location toLocation(Location playerLocation) {
World world = playerLocation.getWorld();
int y = world.getEnvironment() == World.Environment.NETHER
? playerLocation.getBlockY() + 1
: world.getHighestBlockYAt(x, z) + 1;
return new Location(world, x, y, z, location.getYaw(), location.getPitch());
return new Location(world, x, y, z, playerLocation.getYaw(), playerLocation.getPitch());
}
static Point2D from(Location location) {
@@ -24,4 +26,12 @@ record Point2D(int x, int z) {
return new Point2D(Math.max(this.x, max.getBlockX()), Math.max(this.z, max.getBlockZ()));
}
public Point2D expandMax(int x, int z) {
return new Point2D(this.x + x, this.z + z);
}
public Point2D expandMin(int x, int z) {
return new Point2D(this.x - x, this.z - z);
}
}
@@ -1,50 +0,0 @@
package com.eternalcode.combat.fight.pearl;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
public class FightPearlServiceImpl implements FightPearlService {
private final FightPearlSettings pearlSettings;
private final Cache<UUID, Instant> pearlStartTimes;
public FightPearlServiceImpl(FightPearlSettings pearlSettings) {
this.pearlSettings = pearlSettings;
this.pearlStartTimes = Caffeine.newBuilder()
.expireAfterWrite(pearlSettings.pearlThrowDelay)
.build();
}
@Override
public void markDelay(UUID uuid) {
this.pearlStartTimes.put(uuid, Instant.now());
}
@Override
public boolean hasDelay(UUID uuid) {
return this.pearlStartTimes.getIfPresent(uuid) != null;
}
@Override
public Duration getRemainingDelay(UUID 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) {
Instant startTime = this.pearlStartTimes.getIfPresent(uuid);
return startTime != null ? startTime.plus(this.pearlSettings.pearlThrowDelay) : Instant.MIN;
}
}
@@ -1,11 +1,11 @@
package com.eternalcode.combat.fight.pearl;
import com.eternalcode.combat.config.implementation.PluginConfig;
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;
@@ -14,28 +14,26 @@ import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.ProjectileLaunchEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.event.player.PlayerTeleportEvent;
public class FightPearlController implements Listener {
public class PearlController implements Listener {
private final FightPearlSettings settings;
private final PluginConfig pluginConfig;
private final PearlService pearlService;
private final NoticeService noticeService;
private final FightManager fightManager;
private final FightPearlService fightPearlService;
public FightPearlController(
FightPearlSettings settings,
NoticeService noticeService,
FightManager fightManager,
FightPearlService fightPearlService
public PearlController(
PluginConfig pluginConfig,
PearlService pearlService, NoticeService noticeService, FightManager fightManager
) {
this.settings = settings;
this.pluginConfig = pluginConfig;
this.pearlService = pearlService;
this.noticeService = noticeService;
this.fightManager = fightManager;
this.fightPearlService = fightPearlService;
}
@EventHandler(priority = EventPriority.HIGHEST)
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPearlThrow(ProjectileLaunchEvent event) {
if (!(event.getEntity() instanceof EnderPearl)) {
return;
@@ -47,58 +45,68 @@ public class FightPearlController implements Listener {
UUID playerId = player.getUniqueId();
if (!this.fightManager.isInCombat(playerId)) {
return;
}
if (this.settings.pearlThrowDisabledDuringCombat) {
if (this.pearlService.shouldCancelEvent(playerId)) {
event.setCancelled(true);
if (this.pluginConfig.pearl.pearlThrowDisabledDuringCombat) {
this.noticeService.create()
.player(playerId)
.notice(this.pluginConfig.pearl.pearlThrowBlockedDuringCombat)
.send();
return;
}
Duration remainingDelay = this.pearlService.getRemainingDelay(playerId);
this.noticeService.create()
.player(playerId)
.notice(this.settings.pearlThrowBlockedDuringCombat)
.notice(this.pluginConfig.pearl.pearlThrowBlockedDelayDuringCombat)
.placeholder("{TIME}", DurationUtil.format(remainingDelay))
.send();
return;
}
if (this.settings.pearlCooldownEnabled) {
handlePearlCooldown(event, player, playerId);
}
this.pearlService.handleDelay(player);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPearlDamage(EntityDamageByEntityEvent event) {
if (this.settings.pearlThrowDamageEnabled) {
if (this.pluginConfig.pearl.pearlThrowDamageEnabled) {
return;
}
if (!(event.getEntity() instanceof Player) ||
!(event.getDamager() instanceof EnderPearl) ||
event.getCause() != EntityDamageEvent.DamageCause.FALL) {
event.getCause() != EntityDamageEvent.DamageCause.PROJECTILE) {
return;
}
event.setDamage(0.0);
}
private void handlePearlCooldown(ProjectileLaunchEvent event, Player player, UUID playerId) {
if (this.settings.pearlThrowDelay.isZero()) {
@EventHandler(priority = EventPriority.HIGHEST)
public void onPearlTeleport(PlayerTeleportEvent event) {
if (event.getCause() != PlayerTeleportEvent.TeleportCause.ENDER_PEARL) {
return;
}
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();
if (!this.pluginConfig.pearl.pearlThrowDisabledDuringCombat) {
return;
}
this.fightPearlService.markDelay(playerId);
int cooldownTicks = (int) (this.settings.pearlThrowDelay.toMillis() / 50);
player.setCooldown(Material.ENDER_PEARL, cooldownTicks);
Player player = event.getPlayer();
UUID playerId = player.getUniqueId();
if (!this.fightManager.isInCombat(playerId)) {
return;
}
event.setCancelled(true);
this.noticeService.create()
.player(playerId)
.notice(this.pluginConfig.pearl.pearlThrowBlockedDuringCombat)
.send();
}
}
@@ -0,0 +1,81 @@
package com.eternalcode.combat.fight.pearl;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.CauseOfTag;
import com.eternalcode.commons.delay.Delay;
import com.eternalcode.commons.scheduler.Scheduler;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import java.time.Duration;
import java.util.UUID;
public class PearlServiceImpl implements PearlService {
private final FightManager fightManager;
private final PluginConfig pluginConfig;
private final Scheduler scheduler;
private final Delay<UUID> pearlStartTimes;
public PearlServiceImpl(FightManager fightManager, PluginConfig pluginConfig, Scheduler scheduler) {
this.fightManager = fightManager;
this.pluginConfig = pluginConfig;
this.pearlStartTimes = Delay.withDefault(() -> pluginConfig.pearl.pearlThrowDelay);
this.scheduler = scheduler;
}
@Override
public boolean shouldCancelEvent(UUID playerId) {
if (fightManager.isInCombat(playerId)) {
if (this.pluginConfig.pearl.pearlCooldownEnabled) {
return this.pearlStartTimes.hasDelay(playerId);
}
return pluginConfig.pearl.pearlThrowDisabledDuringCombat;
}
return false;
}
@Override
public void handleDelay(Player player) {
UUID uniqueId = player.getUniqueId();
if (this.hasDelay(uniqueId)) {
return;
}
if (this.fightManager.isInCombat(uniqueId)) {
if (this.pluginConfig.pearl.pearlExtendsCombatTag) {
Duration combatTime = this.pluginConfig.settings.combatTimerDuration;
this.fightManager.tag(uniqueId, combatTime, CauseOfTag.ENDER_PEARL);
}
if (this.pluginConfig.pearl.pearlCooldownEnabled && !this.pluginConfig.pearl.pearlThrowDelay.isZero()) {
this.pearlStartTimes.markDelay(uniqueId);
this.scheduler.runLater(
() -> player.setCooldown(
Material.ENDER_PEARL,
(int) this.pluginConfig.pearl.pearlThrowDelay.toMillis() / 50
), Duration.ofMillis(50)
);
}
}
}
@Override
public boolean hasDelay(UUID uuid) {
return this.pearlStartTimes.hasDelay(uuid);
}
@Override
public Duration getRemainingDelay(UUID uuid) {
return this.pearlStartTimes.getRemaining(uuid);
}
}
@@ -1,13 +1,12 @@
package com.eternalcode.combat.fight.pearl;
import com.eternalcode.multification.bukkit.notice.BukkitNotice;
import com.eternalcode.multification.notice.Notice;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
public class FightPearlSettings extends OkaeriConfig {
public class PearlSettings extends OkaeriConfig {
@Comment({ "# Is pearl damage to be enabled?", "# This will work globally" })
public boolean pearlThrowDamageEnabled = true;
@@ -21,6 +20,9 @@ public class FightPearlSettings extends OkaeriConfig {
@Comment("# Set true, If you want add cooldown to pearls")
public boolean pearlCooldownEnabled = false;
@Comment("# Set true, If you want to reset timer when player throws ender pearl")
public boolean pearlExtendsCombatTag = 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"
@@ -28,12 +30,12 @@ public class FightPearlSettings extends OkaeriConfig {
public Duration pearlThrowDelay = Duration.ofSeconds(3);
@Comment("# Message sent when player tries to throw ender pearl, but are disabled")
public Notice pearlThrowBlockedDuringCombat = BukkitNotice.builder()
public Notice pearlThrowBlockedDuringCombat = Notice.builder()
.chat("<red>Throwing ender pearls is prohibited during combat!")
.build();
@Comment("# Message sent when player tries to throw ender pearl, but has delay")
public Notice pearlThrowBlockedDelayDuringCombat = BukkitNotice.builder()
public Notice pearlThrowBlockedDelayDuringCombat = Notice.builder()
.chat("<red>You must wait {TIME} before next throw!")
.build();
@@ -0,0 +1,146 @@
package com.eternalcode.combat.fight.trident;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.CauseOfTag;
import com.eternalcode.combat.fight.event.FightUntagEvent;
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.Server;
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.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerRiptideEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
public class TridentController implements Listener {
private final PluginConfig pluginConfig;
private final NoticeService noticeService;
private final FightManager fightManager;
private final TridentService tridentService;
private final Server server;
public TridentController(
PluginConfig pluginConfig,
NoticeService noticeService,
FightManager fightManager,
TridentService tridentService, Server server
) {
this.pluginConfig = pluginConfig;
this.noticeService = noticeService;
this.fightManager = fightManager;
this.tridentService = tridentService;
this.server = server;
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onRiptideInteract(PlayerInteractEvent event) {
if (!this.isRiptideInteract(event)) {
return;
}
Player player = event.getPlayer();
UUID playerId = player.getUniqueId();
if (!this.fightManager.isInCombat(playerId)) {
return;
}
if (this.pluginConfig.trident.tridentRiptideDisabledDuringCombat) {
event.setCancelled(true);
this.noticeService.create()
.player(playerId)
.notice(this.pluginConfig.trident.tridentRiptideBlocked)
.send();
return;
}
if (!this.tridentService.hasDelay(playerId)) {
return;
}
event.setCancelled(true);
}
// we need to send message without ignoring canceled event because of riptide cooldown
@EventHandler(priority = EventPriority.LOWEST)
public void messageOnRiptideCooldown(PlayerInteractEvent event) {
if (!this.isRiptideInteract(event)) {
return;
}
UUID playerId = event.getPlayer().getUniqueId();
if (!this.tridentService.hasDelay(playerId)) {
return;
}
Duration remainingDelay = this.tridentService.getRemainingDelay(playerId);
this.noticeService.create()
.player(playerId)
.notice(this.pluginConfig.trident.tridentRiptideOnCooldown)
.placeholder("{TIME}", DurationUtil.format(remainingDelay))
.send();
}
@EventHandler(ignoreCancelled = true)
public void onRiptide(PlayerRiptideEvent event) {
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (this.pluginConfig.trident.tridentRiptideDisabledDuringCombat) {
return;
}
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
if (this.tridentService.hasDelay(uniqueId)) {
return;
}
this.tridentService.markDelay(player.getUniqueId());
player.setCooldown(Material.TRIDENT, (int) this.pluginConfig.trident.tridentRiptideDelay.toMillis() / 50);
if (this.pluginConfig.trident.tridentRiptideExtendsCombatTag) {
this.fightManager.tag(uniqueId, this.pluginConfig.settings.combatTimerDuration, CauseOfTag.TRIDENT);
}
}
@EventHandler(ignoreCancelled = true)
public void onUntag(FightUntagEvent event) {
Player player = server.getPlayer(event.getPlayer());
player.setCooldown(Material.TRIDENT, 0);
this.tridentService.removeDelay(player.getUniqueId());
}
private boolean isRiptideInteract(PlayerInteractEvent event) {
EquipmentSlot hand = event.getHand();
if (hand != EquipmentSlot.HAND && hand != EquipmentSlot.OFF_HAND) {
return false;
}
Action action = event.getAction();
if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) {
return false;
}
return this.isRiptideTrident(event.getItem());
}
private boolean isRiptideTrident(ItemStack itemStack) {
return itemStack != null
&& itemStack.getType() == Material.TRIDENT
&& itemStack.containsEnchantment(Enchantment.RIPTIDE);
}
}
@@ -0,0 +1,41 @@
package com.eternalcode.combat.fight.trident;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.commons.delay.Delay;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import java.time.Duration;
import java.util.UUID;
public class TridentServiceImpl implements TridentService {
private final Delay<UUID> delay;
public TridentServiceImpl(PluginConfig pluginConfig) {
this.delay = Delay.withDefault(() -> pluginConfig.trident.tridentRiptideDelay);
}
@Override
public void markDelay(UUID uuid) {
this.delay.markDelay(uuid);
}
@Override
public boolean hasDelay(UUID uuid) {
return this.delay.hasDelay(uuid);
}
@Override
public void removeDelay(UUID playerId) {
this.delay.unmarkDelay(playerId);
}
@Override
public Duration getRemainingDelay(UUID uuid) {
return this.delay.getRemaining(uuid);
}
}
@@ -0,0 +1,39 @@
package com.eternalcode.combat.fight.trident;
import com.eternalcode.multification.notice.Notice;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
public class TridentSettings extends OkaeriConfig {
@Comment({
"# Set to true to disable riptide usage during combat",
"# This setting works globally, but can be overridden by region settings"
})
public boolean tridentRiptideDisabledDuringCombat = false;
@Comment("# Set to true so then riptide usage during combat will extend combat tag duration, works only if riptide usage is not disabled")
public boolean tridentRiptideExtendsCombatTag = false;
@Comment({
"# Should riptide enchantment be on cooldown during combat?",
"# Setting this option to 3s will make players wait 3 seconds between trident throws",
"# Setting this to 0s or below will remove cooldown"
})
public Duration tridentRiptideDelay = Duration.ofSeconds(10);
@Comment("# Message shown when riptide is blocked during combat")
public Notice tridentRiptideBlocked = Notice.builder()
.chat("<red>Using riptide is prohibited during combat!")
.build();
@Comment({
"# Message sent to the player when riptide is on cooldown",
"# Available placeholder: {TIME} - remaining time left to use riptide again"
})
public Notice tridentRiptideOnCooldown = Notice.builder()
.chat("<red>You must wait {TIME} before next riptide!")
.build();
}
@@ -2,24 +2,20 @@ package com.eternalcode.combat.notification;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.multification.adventure.AudienceConverter;
import com.eternalcode.multification.bukkit.BukkitMultification;
import com.eternalcode.multification.paper.PaperMultification;
import com.eternalcode.multification.translation.TranslationProvider;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.ComponentSerializer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public final class NoticeService extends BukkitMultification<PluginConfig> {
public final class NoticeService extends PaperMultification<PluginConfig> {
private final AudienceProvider audienceProvider;
private final PluginConfig pluginConfig;
private final MiniMessage miniMessage;
public NoticeService(AudienceProvider audienceProvider, PluginConfig pluginConfig, MiniMessage miniMessage) {
this.audienceProvider = audienceProvider;
public NoticeService(PluginConfig pluginConfig, MiniMessage miniMessage) {
this.pluginConfig = pluginConfig;
this.miniMessage = miniMessage;
}
@@ -36,13 +32,7 @@ public final class NoticeService extends BukkitMultification<PluginConfig> {
@Override
protected @NotNull AudienceConverter<CommandSender> audienceConverter() {
return commandSender -> {
if (commandSender instanceof Player player) {
return this.audienceProvider.player(player.getUniqueId());
}
return this.audienceProvider.console();
};
return commandSender -> commandSender;
}
}
@@ -8,13 +8,14 @@ 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;
double x = (min.x() + max.x()) / 2.0;
double z = (min.z() + max.z()) / 2.0;
return new Point(this.world, x, z);
}
@@ -22,12 +23,13 @@ record WorldGuardRegion(World world, ProtectedRegion region) implements Region {
@Override
public Location getMin() {
BlockVector3 min = this.region.getMinimumPoint();
return new Location(this.world, min.getX(), min.getY(), min.getZ());
return new Location(this.world, min.x(), min.y(), min.z());
}
@Override
public Location getMax() {
BlockVector3 max = this.region.getMaximumPoint();
return new Location(this.world, max.getX(), max.getY(), max.getZ());
return new Location(this.world, max.x(), max.y(), max.z());
}
}
@@ -2,8 +2,6 @@ package com.eternalcode.combat.updater;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.commons.concurrent.FutureHandler;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -16,20 +14,17 @@ public class UpdaterNotificationController implements Listener {
private final UpdaterService updaterService;
private final PluginConfig pluginConfig;
private final AudienceProvider audienceProvider;
private final MiniMessage miniMessage;
public UpdaterNotificationController(UpdaterService updaterService, PluginConfig pluginConfig, AudienceProvider audienceProvider, MiniMessage miniMessage) {
public UpdaterNotificationController(UpdaterService updaterService, PluginConfig pluginConfig, MiniMessage miniMessage) {
this.updaterService = updaterService;
this.pluginConfig = pluginConfig;
this.audienceProvider = audienceProvider;
this.miniMessage = miniMessage;
}
@EventHandler
void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
Audience audience = this.audienceProvider.player(player.getUniqueId());
if (!shouldNotify(player)) {
return;
@@ -38,7 +33,7 @@ public class UpdaterNotificationController implements Listener {
this.updaterService.checkForUpdate()
.thenAccept(result -> {
if (result.isUpdateAvailable()) {
audience.sendMessage(this.miniMessage.deserialize(NEW_VERSION_AVAILABLE));
player.sendMessage(this.miniMessage.deserialize(NEW_VERSION_AVAILABLE));
}
})
.exceptionally(FutureHandler::handleException);
@@ -1,27 +0,0 @@
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> 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);
}
}
}
+1 -1
View File
@@ -4,4 +4,4 @@ org.gradle.configuration-cache=true
org.gradle.configuration-cache.parallel=true
# Platforms
paperVersion=1.17,1.17.1,1.18,1.18.1,1.18.2,1.19,1.19.1,1.19.2,1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.3,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.2,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11
paperVersion=1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11,26.1.1,26.1.2
Binary file not shown.
+3 -1
View File
@@ -1,7 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+3 -3
View File
@@ -20,7 +20,7 @@
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
# gradlew start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
# ksh gradlew
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
Vendored
+12 -23
View File
@@ -19,12 +19,12 @@
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem gradlew startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,29 +65,18 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%