Compare commits

...
67 Commits
Author SHA1 Message Date
CitralFlo 037a6054b8 Sort regions by priority 2024-12-23 21:53:57 +01:00
Michał Wojtas c8483e51e9 GH-200 Add option to exclude Creative mode players from combat (#200)
* Add creative setting, reformat name of config value

* Extract method

* Devide method to two ifs. Following @Rollczi's advice

* Devide method to two ifs. Following @Rollczi's advice
2024-10-31 01:59:49 +01:00
Michał WojtasandRollczi dfc825cd98 GH-107 Multification setup (#197)
* Multification setup

* Bump version of multification

* Override adventure API. Bump LiteCommands

* Fix import

* Fix minor issues with notices

* Change BukktiNotice to Notice

* Resolve Rollczi's suggestions

* MAJOR FIX

* uncomment dev.rollczi.litecommands needed for testing

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2024-10-27 14:25:53 +01:00
Martin Sulikowski 9203309def GH-167 start of normalizing developer api (#195)
* GH-167 start of normalizing developer api

* Extract all of services to interfaces

* Add jetbrains annotatiots.

* Fix checkstyle violations

* Follow citralflo feedback.

* Add CancelTagReason

* remove unsed `OTHER` in CancelTagReason.
2024-10-02 18:32:15 +02:00
Martin Sulikowski 381007a735 GH-187 Use LiteCommands 3x (#191)
* GH-187 Use LiteCommands 3x

* GH-187 Separate commands to a different classes

* Update CI to match 2.0 branch.

* Fix.

* Fix command name mistake.

* Follow Mr. Rollczi's Feedback.

* Follow Mr. Rollczi's Feedback v2.

* :trollface:
2024-09-28 14:45:01 +02:00
Martin Sulikowski 534e2cf857 Initialize 2.0.0 Concept. 2024-09-27 21:42:20 +02:00
Michał WojtasandRollczi 20a4e4a2f5 GH-184 Refactor Knockback Mechanic and Add Support for Deny Entry flag (#184)
* Initial commit

* fix spaces

* fix spaces

* Add Kamicjusz's changes. Normalize knockback vector, fix message.

* Set cooldown for messages

* Resolve half of suggestions

* Remove delay and add PVP regions blockade.

* Improve performance of WorldGuardRegionProvider.

* Use lambda

* Resolve @vLuckyyy review

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2024-09-22 21:33:22 +02:00
Martin Sulikowski fe2569918a Release v1.3.2 2024-09-18 19:21:23 +02:00
Michał Wojtas e17effa4cd Fix op in combat (#183) 2024-09-18 15:30:40 +02:00
Michał Wojtas c63df3ab38 GH-181 Add block placing blockade modes and message formatting. (#181)
* Add block placing blockade modes and message formatting.
2024-09-11 19:25:12 +02:00
Martin 24fade593b Release v1.3.1 2024-08-11 17:38:53 +02:00
renovate[bot] 4b12968a7b GH-177 Update all dependencies (#177)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-11 17:25:42 +02:00
Martin 9ad0063aa5 Java 21 support. 2024-08-11 17:15:57 +02:00
Michał Wojtas 7f6c6341f1 GH-175 Fix readme (#175) 2024-07-17 23:10:39 +02:00
Martin 0620f7fb92 Release v1.3.0 2024-07-16 22:11:51 +02:00
Michał WojtasandRollczi d18b2f013a GH-174 Add progress animation to boss bar, fix issue with boss bar not disappearing (#174)
* Add progress animation to boss bar, fix issue with boss bar not disappearing

* Fix spaces

* Temporary solution for empty duration formatting.

* Remove unused cases solved by @Rollczi

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2024-07-16 22:00:08 +02:00
Michał Wojtas 7b89dfdcfc Fix formatting of placeholder to match minecraft health system (#173) 2024-07-13 16:06:55 +02:00
Michał Wojtas 595370f6a4 GH-170 Add PlaceHolderAPI, enchance FightTag class (#170)
* Add PlaceHolderAPI, create basic placeholders. Add tagger as a person who starts tag countdown for player.

* Update README.md with placeholders. Update return value of placeholders

* Resolve Piotrulla's comments.

* Add PlaceHolder to BridgeService. Resolve Rollczi's comments

* Resolve @vLuckyyy review

* Fixes

* Fix infinite loop :>
2024-07-03 16:19:23 +02:00
Martin Sulikowski e732bdf842 GH-165 Use eternalcode commons. (#169)
* Use eternalcode commons.

* Make DurationUtil final.
2024-05-19 23:21:50 +02:00
Martin 6b6329c4a6 Make worldguard compileOnly. 2024-05-19 20:07:17 +02:00
Martin Sulikowski 7a6707b20e GH-164 Normalize gradle architecture. (#166)
* Normalize gradle architecture.

* Add missing version placeholder.
2024-05-19 20:02:29 +02:00
Martin 7a10e268bb Change api version in readme. 2024-04-30 17:57:28 +02:00
Martin adce4729f0 Release v1.2.2 2024-04-30 17:57:04 +02:00
renovate[bot] 8560f17bac GH-160 Update all dependencies (#160)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 17:27:28 +02:00
renovate[bot] d2d7a4f02c GH-159 Update all dependencies (#159)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-23 23:00:05 +02:00
Michał Wojtas cf33b23720 Include admins in combat on default (#158) 2024-04-22 14:16:59 +02:00
renovate[bot] e261e06635 GH-157 Update all dependencies (#157)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-19 21:40:27 +02:00
renovate[bot] 2856ababff GH-155 Update gradle/gradle-build-action digest to 8baac4c (#155)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-16 22:32:52 +02:00
renovate[bot] f061e1738e GH-154 Update all dependencies (#154)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-11 22:40:53 +02:00
renovate[bot] 4118983479 GH-153 Update gradle/gradle-build-action digest to fe59895 (#153)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-07 22:02:05 +02:00
renovate[bot] 988859ddbe GH-152 Update dependency checkstyle to v10.15.0 (#152)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-02 20:30:37 +02:00
renovate[bot] 305ab9e299 GH-151 Update dependency commons-io:commons-io to v2.16.0 (#151)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-02 20:29:15 +02:00
renovate[bot] 5aaf8a4f08 GH-150 Update all dependencies (#150)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-25 23:09:11 +01:00
renovate[bot] 153657c554 GH-149 Update all dependencies (#149)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-20 16:49:16 +01:00
renovate[bot] 5123176cb3 GH-148 Update dependency checkstyle to v10.14.1 (#148)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-12 23:55:31 +01:00
renovate[bot] 1665f39151 GH-142 Update all dependencies (#142)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-08 23:45:16 +01:00
Martin Sulikowski 94113ea326 Fix checkstyle. 2024-03-08 23:37:05 +01:00
renovate[bot] 7f581dbaac GH-146 Update actions/cache action to v4 (#146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-06 21:51:30 +01:00
imgbot[bot]andImgBotApp 2dd25cc27e GH-147 [ImgBot] Optimize images (#147)
/assets/readme-banner.png -- 102.61kb -> 85.78kb (16.4%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2024-03-06 21:49:34 +01:00
Martin Sulikowski 3ca562f515 Update readme banner. 2024-02-18 16:33:55 +01:00
Martin Sulikowski 383c405198 Bump Minecraft version in readme to 1.20.4. 2024-01-16 15:02:11 +01:00
Norbert Dejlich c522f7c078 Bump dependency version in README.md to 1.2.1 2024-01-16 00:10:39 +01:00
Martin Sulikowski fb6efa0646 Release v1.2.1-SNAPSHOT 2024-01-15 22:11:14 +01:00
Martin Sulikowski ad7899f10e Release v1.2.1 2024-01-15 21:51:21 +01:00
Martin Sulikowski ab5e6a7cf3 GH-141 Remove config backup system (Resolve #141) 2024-01-15 21:47:59 +01:00
Martin Sulikowski 42ae17bf8d GH-145 Add method to get region center and use it for knockback in region. (#145) 2024-01-15 21:45:27 +01:00
Martin Sulikowski 20d342d0ca Update eternalcombat-api version in readme. 2024-01-10 14:12:24 +01:00
Martin Sulikowski c4adeb1aed Fix checkstyle violations. 2024-01-09 17:59:31 +01:00
Martin Sulikowski d2d0c5b608 Release v1.2.0 2024-01-09 17:59:23 +01:00
Krystian Michalski eb1febab85 GH-144 Fix fight tag for admin. (#144) 2024-01-09 17:28:14 +01:00
Krystian Michalski 8153ba55ab GH-143 Fix region teleport by example: Chorus, EnderPearl. (#143) 2023-12-27 13:11:49 +01:00
renovate[bot]andMartin Sulikowski 89784f3412 GH-135 Update all dependencies (#135)
* Update all dependencies

* Revert checkstyle.

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Martin Sulikowski <vLuckyyy.biznes@gmail.com>
2023-12-24 12:03:27 +01:00
renovate[bot] 49e82385d4 GH-136 Update all dependencies to v4 (#136)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-24 12:02:54 +01:00
IgorandOsnixer 190034f5aa GH-139 Prevent flying during combat feature (#139)
* Prevent flying during combat feature

* add check is player flying and replace google cache -> caffeine cache

---------

Co-authored-by: Osnixer <Osnixeroo@gmail.com>
2023-12-24 01:28:28 +01:00
Martin SulikowskiandDMK 1db744c4ec GH-132 Add option to exclude admins from combat. (#132)
* Add option to exclude admins from combat.

* revert hashmap

* Update eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightManager.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Fix

* merge master

---------

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>
2023-11-20 23:54:05 +01:00
Michał WojtasandDMK 94d7baf791 GH-134 Remove damage fields from FightTag (#134)
* Add custom Causes, fix code for remove items from drop, remove fields from fightTag, fix issues

* Fix air errors

* Code styles fixes

* Update eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/LogoutService.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/LogoutService.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Fix compile error

* Change logoutManager to logoutService var name. Remove unused dependency

* Follow Rollczi's suggestion

---------

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>
2023-11-19 22:01:51 +01:00
renovate[bot] c25af3f6d3 GH-133 Update all dependencies (#133)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-19 20:48:55 +01:00
renovate[bot] 3b38c82b47 GH-128 Update all dependencies (#128)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 22:53:57 +01:00
Martin Sulikowski 526f831fcb Add SpigotMC group to renovate.json configuration 2023-11-13 22:49:55 +01:00
Martin Sulikowski e4151cbbe0 Enable major/minor separation and pruning stale branches in Renovate 2023-11-13 22:39:55 +01:00
Martin Sulikowski 47d9fed823 GH-126 Central Dependency Management using toml file. (#126)
* Centralized Dependency and Project Configuration Management.

* Fix

* Move to buildSrc
2023-10-21 15:10:15 +02:00
Martin Sulikowski 0890b2abbd Fix CI to follow new path in module. 2023-10-19 19:38:56 +02:00
Martin Sulikowski be99a8646d GH-125 Add info about developer api. (#125) 2023-10-19 19:32:44 +02:00
renovate[bot] b58266c868 GH-103 Update all dependencies (#103)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-19 19:26:23 +02:00
DMK eaa02a4166 GH-115 Fix death cause (#119) 2023-10-19 19:26:01 +02:00
Martin Sulikowski 08e99ded13 Disable custom effects default. 2023-10-15 13:03:48 +02:00
failutee e867e7e697 GH-123 Add null-check for DropResult (#123) 2023-10-15 00:58:22 +02:00
110 changed files with 2094 additions and 1709 deletions

No files matched your search

+6 -7
View File
@@ -4,7 +4,6 @@ on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
@@ -16,14 +15,14 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@v3.3.1
uses: actions/cache@v4.0.2
with:
path: ~/.gradle/caches
key: >-
@@ -33,11 +32,11 @@ jobs:
- name: Make gradlew executable
run: chmod +x gradlew
- name: Build with Gradle
uses: gradle/gradle-build-action@e3426b1f20c7262e0fed7b855aae1325633af3b4
uses: gradle/gradle-build-action@093dfe9d598ec5a42246855d09b49dc76803c005
with:
arguments: shadowJar
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.2
uses: actions/upload-artifact@v4.3.6
with:
name: 'Successfully build EternalCombat'
path: build/libs/*.jar
path: eternalcombat-plugin/build/libs/*.jar
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
+51 -1
View File
@@ -15,11 +15,24 @@
### Information
EternalCombat has been tested on Minecraft versions 1.17.1 to 1.20.1, but it should work on most other versions. If you
EternalCombat has been tested on Minecraft versions 1.17.1 to 1.21.1, but it should work on most other versions. If you
encounter any compatibility issues, please report them in the
[Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). The plugin requires Java 17 or later, so please
make sure it is installed on your server.
## PlaceholderAPI
EternalCombat supports PlaceholderAPI, which allows you to use placeholders in other plugins that support it.
To use placeholders, follow PlaceholderAPI's [instructions](https://wiki.placeholderapi.com/users/) and use the placeholders provided by EternalCombat.
Provided placeholders:
- `%eternalcombat_opponent%` - Returns the name of the player with whom the player is fighting.
- `%eternalcombat_opponent_health%` - Returns opponent health in format `00.00`
- `%eternalcombat_remaining_seconds%` - Returns the number of seconds remaining until the player is no longer in combat.
- `%eternalcombat_remaining_millis%` - Returns the number of milliseconds remaining until the player is no longer in combat.
If the player is not in combat, the placeholders will return an empty string.
If combat was not caused by other player, opponent placeholders will return empty string.
## Building
Build EternalCombat using:
@@ -35,6 +48,43 @@ Build EternalCombat using:
- `eternalcombat.receiveupdates` - Allows you to receive notifications about the new version of the plugin when
attaching
### Developer API
#### 1. Add repository:
with Gradle:
```kts
maven {
url = uri("https://repo.eternalcode.pl/releases")
}
```
with Maven:
```xml
<repository>
<id>eternalcode-reposilite-releases</id>
<name>EternalCode Repository</name>
<url>https://repo.eternalcode.pl/releases</url>
</repository>
```
#### 2. Add dependency:
with Gradle:
```kts
compileOnly("com.eternalcode:eternalcombat-api:1.3.2")
```
with Maven:
```xml
<dependency>
<groupId>com.eternalcode</groupId>
<artifactId>eternalcombat-api</artifactId>
<version>1.3.2</version>
<scope>provided</scope>
</dependency>
```
### Contributing
We welcome all contributions to the EternalCombat project! Please check out [contributing](.github/CONTRIBUTING.md) for
Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 86 KiB

+29
View File
@@ -0,0 +1,29 @@
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.25")
implementation("io.github.goooler.shadow:shadow-gradle-plugin:8.1.8")
implementation("net.minecrell:plugin-yml:0.6.0")
implementation("xyz.jpenilla:run-task:2.3.0")
}
sourceSets {
main {
java.setSrcDirs(emptyList<String>())
groovy.setSrcDirs(emptyList<String>())
resources.setSrcDirs(emptyList<String>())
}
test {
java.setSrcDirs(emptyList<String>())
kotlin.setSrcDirs(emptyList<String>())
groovy.setSrcDirs(emptyList<String>())
resources.setSrcDirs(emptyList<String>())
}
}
+38
View File
@@ -0,0 +1,38 @@
object Versions {
const val SPIGOT_API = "1.17.1-R0.1-SNAPSHOT"
const val JUNIT_JUPITER_API = "5.10.3"
const val JUNIT_JUPITER_PARAMS = "5.10.3"
const val JUNIT_JUPITER_ENGINE = "5.10.3"
const val JETBRAINS_ANNOTATIONS = "24.1.0"
const val ETERNALCODE_COMMONS = "1.1.3"
const val MULTIFICATION = "1.1.4"
const val ADVENTURE_PLATFORM_BUKKIT = "4.3.4"
const val ADVENTURE_API = "4.17.0"
const val LITE_COMMANDS = "3.7.0"
const val OKAERI_CONFIGS_YAML_BUKKIT = "5.0.3"
const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.3"
const val OKAERI_CONFIGS_SERDES_BUKKIT = "5.0.3"
const val PANDA_UTILITIES = "0.5.2-alpha"
const val GIT_CHECK = "1.0.0"
const val APACHE_COMMONS = "2.16.1"
const val CAFFEINE = "3.1.8"
const val B_STATS_BUKKIT = "3.0.2"
const val WORLD_GUARD_BUKKIT = "7.0.9"
const val PLACEHOLDER_API = "2.11.6"
}
@@ -0,0 +1,19 @@
plugins {
`java-library`
}
dependencies {
testImplementation("org.spigotmc:spigot-api:${Versions.SPIGOT_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}")
}
tasks.test {
useJUnitPlatform()
}
sourceSets.test {
java.setSrcDirs(listOf("test"))
resources.setSrcDirs(emptyList<String>())
}
@@ -0,0 +1,42 @@
plugins {
`java-library`
checkstyle
}
group = "com.eternalcode"
version = "2.0.0-SNAPSHOT"
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
checkstyle {
toolVersion = "10.17.0"
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
maxErrors = 0
maxWarnings = 0
}
// https://github.com/JabRef/jabref/pull/10812/files#diff-49a96e7eea8a94af862798a45174e6ac43eb4f8b4bd40759b5da63ba31ec3ef7R267
configurations.named("checkstyle") {
resolutionStrategy {
capabilitiesResolution {
withCapability("com.google.collections:google-collections") {
select("com.google.guava:guava:33.2.1-jre")
}
}
}
}
tasks.compileJava {
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
options.encoding = "UTF-8"
options.release = 17
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}
@@ -0,0 +1,34 @@
plugins {
`java-library`
`maven-publish`
}
group = "com.eternalcode"
version = "2.0.0-SNAPSHOT"
java {
withSourcesJar()
withJavadocJar()
}
publishing {
publications {
create<MavenPublication>("maven") {
artifactId = "eternalcombat-api"
from(project.components["java"])
}
}
repositories {
mavenLocal()
maven {
name = "eternalcodeReleases"
url = uri("https://repo.eternalcode.pl/releases")
credentials {
username = System.getenv("ETERNAL_CODE_MAVEN_USERNAME")
password = System.getenv("ETERNAL_CODE_MAVEN_PASSWORD")
}
}
}
}
@@ -0,0 +1,15 @@
plugins {
`java-library`
}
repositories {
mavenCentral()
maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") }
maven { url = uri("https://papermc.io/repo/repository/maven-public/") }
maven { url = uri("https://repo.eternalcode.pl/releases") }
maven { url = uri("https://storehouse.okaeri.eu/repository/maven-public/") }
maven { url = uri("https://repo.panda-lang.org/releases") }
maven { url = uri("https://maven.enginehub.org/repo/") }
maven { url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/")}
}
+6 -103
View File
@@ -1,109 +1,12 @@
plugins {
`java-library`
checkstyle
id("maven-publish")
}
group = "com.eternalcode"
version = "1.1.1"
checkstyle {
toolVersion = "10.12.2"
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
maxErrors = 0
maxWarnings = 0
}
repositories {
mavenCentral()
maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") }
maven { url = uri("https://papermc.io/repo/repository/maven-public/") }
maven { url = uri("https://repo.eternalcode.pl/releases") }
maven { url = uri("https://storehouse.okaeri.eu/repository/maven-public/") }
maven { url = uri("https://repo.panda-lang.org/releases") }
maven { url = uri("https://maven.enginehub.org/repo/") }
`eternalcombat-java`
`eternalcombat-repositories`
`eternalcombat-publish`
`eternalcombat-java-unit-test`
}
dependencies {
// Spigot api
compileOnlyApi("org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT")
// kyori
api("net.kyori:adventure-platform-bukkit:4.3.0")
api("net.kyori:adventure-text-minimessage:4.14.0")
// litecommands
api("dev.rollczi.litecommands:bukkit-adventure:2.8.9")
// Okaeri configs
api("eu.okaeri:okaeri-configs-yaml-bukkit:5.0.0-beta.5")
api("eu.okaeri:okaeri-configs-serdes-commons:5.0.0-beta.5")
api("eu.okaeri:okaeri-configs-serdes-bukkit:5.0.0-beta.5")
// Panda utilities
api("org.panda-lang:panda-utilities:0.5.2-alpha")
// GitCheck
api("com.eternalcode:gitcheck:1.0.0")
// commons
api("commons-io:commons-io:2.13.0")
// bstats
api("org.bstats:bstats-bukkit:3.0.2")
// worldguard
compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.9")
// tests
testImplementation("org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
compileOnlyApi("org.spigotmc:spigot-api:${Versions.SPIGOT_API}")
api("org.jetbrains:annotations:${Versions.JETBRAINS_ANNOTATIONS}")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.getByName<Test>("test") {
useJUnitPlatform()
}
// Publish
java {
withSourcesJar()
withJavadocJar()
}
publishing {
publications {
create<MavenPublication>("maven") {
artifactId = "eternalcombat-api"
from(project.components["java"])
}
}
}
publishing {
repositories {
mavenLocal()
maven {
name = "eternalcodeReleases"
url = uri("https://repo.eternalcode.pl/releases")
credentials {
username = System.getenv("ETERNAL_CODE_MAVEN_USERNAME")
password = System.getenv("ETERNAL_CODE_MAVEN_PASSWORD")
}
}
}
}
@@ -1,137 +0,0 @@
package com.eternalcode.combat;
import com.eternalcode.combat.config.ConfigService;
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.CauseOfUnTag;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import dev.rollczi.litecommands.argument.Arg;
import dev.rollczi.litecommands.command.async.Async;
import dev.rollczi.litecommands.command.execute.Execute;
import dev.rollczi.litecommands.command.permission.Permission;
import dev.rollczi.litecommands.command.route.Route;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
import java.time.Duration;
import java.util.UUID;
@Route(name = "combatlog", aliases = "combat")
public class CombatCommand {
private final FightManager fightManager;
private final ConfigService configService;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public CombatCommand(FightManager fightManager, ConfigService configService, NotificationAnnouncer announcer, PluginConfig config) {
this.fightManager = fightManager;
this.configService = configService;
this.announcer = announcer;
this.config = config;
}
@Execute(route = "status", required = 1)
@Permission("eternalcombat.status")
void status(CommandSender sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
PluginConfig.Messages messages = this.config.messages;
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName());
this.announcer.sendMessage(sender, this.fightManager.isInCombat(targetUniqueId)
? formatter.format(messages.admin.playerInCombat)
: formatter.format(messages.admin.playerNotInCombat));
}
@Execute(route = "tag", required = 1)
@Permission("eternalcombat.tag")
void tag(CommandSender sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
Duration time = this.config.settings.combatDuration;
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName());
FightTagEvent event = this.fightManager.tag(targetUniqueId, time, CauseOfTag.COMMAND);
if (event.isCancelled()) {
this.announcer.sendMessage(sender, event.getCancelMessage());
return;
}
String format = formatter.format(this.config.messages.admin.adminTagPlayer);
this.announcer.sendMessage(sender, format);
}
@Execute(route = "tag", required = 2)
@Permission("eternalcombat.tag")
void tagMultiple(CommandSender sender, @Arg Player firstTarget, @Arg Player secondTarget) {
Duration combatTime = this.config.settings.combatDuration;
PluginConfig.Messages messages = this.config.messages;
if (sender.equals(firstTarget) || sender.equals(secondTarget)) {
this.announcer.sendMessage(sender, messages.admin.adminCannotTagSelf);
return;
}
FightTagEvent firstTagEvent = this.fightManager.tag(firstTarget.getUniqueId(), combatTime, CauseOfTag.COMMAND);
FightTagEvent secondTagEvent = this.fightManager.tag(secondTarget.getUniqueId(), combatTime, CauseOfTag.COMMAND);
Formatter formatter = new Formatter()
.register("{FIRST_PLAYER}", firstTarget.getName())
.register("{SECOND_PLAYER}", secondTarget.getName());
String format = formatter.format(messages.admin.adminTagMultiplePlayers);
if (firstTagEvent.isCancelled()) {
this.announcer.sendMessage(sender, firstTagEvent.getCancelMessage());
}
if (secondTagEvent.isCancelled()) {
this.announcer.sendMessage(sender, firstTagEvent.getCancelMessage());
}
if (firstTagEvent.isCancelled() && secondTagEvent.isCancelled()) {
return;
}
this.announcer.sendMessage(sender, format);
}
@Async
@Execute(route = "reload")
@Permission("eternalcombat.reload")
void execute(CommandSender player) {
this.configService.reload();
this.announcer.sendMessage(player, this.config.messages.admin.reload);
}
@Execute(route = "untag", required = 1)
@Permission("eternalcombat.untag")
void untag(Player sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
if (!this.fightManager.isInCombat(targetUniqueId)) {
this.announcer.sendMessage(sender, this.config.messages.admin.adminPlayerNotInCombat);
return;
}
FightUntagEvent event = this.fightManager.untag(targetUniqueId, CauseOfUnTag.COMMAND);
if (event.isCancelled()) {
return;
}
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName());
String format = formatter.format(this.config.messages.admin.adminUntagPlayer);
this.announcer.sendMessage(sender, format);
}
}
@@ -1,13 +1,12 @@
package com.eternalcode.combat;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.drop.DropKeepInventoryManager;
import com.eternalcode.combat.drop.DropManager;
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.FightPearlManager;
import com.eternalcode.combat.fight.tagout.FightTagOutService;
import com.eternalcode.combat.fight.pearl.FightPearlService;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.combat.fight.tagout.FightTagOutService;
public interface EternalCombatApi {
@@ -15,16 +14,14 @@ public interface EternalCombatApi {
RegionProvider getRegionProvider();
FightPearlManager getFightPearlManager();
FightPearlService getFightPearlService();
FightTagOutService getFightTagOutService();
FightEffectService getFightEffectService();
DropManager getDropManager();
DropService getDropService();
DropKeepInventoryManager getDropKeepInventoryManager();
PluginConfig getPluginConfig();
DropKeepInventoryService getDropKeepInventoryService();
}
@@ -1,35 +0,0 @@
package com.eternalcode.combat.command;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import dev.rollczi.litecommands.command.LiteInvocation;
import dev.rollczi.litecommands.handle.InvalidUsageHandler;
import dev.rollczi.litecommands.schematic.Schematic;
import org.bukkit.command.CommandSender;
import panda.utilities.text.Formatter;
import java.util.List;
public class InvalidUsage implements InvalidUsageHandler<CommandSender> {
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public InvalidUsage(PluginConfig config, NotificationAnnouncer announcer) {
this.config = config;
this.announcer = announcer;
}
@Override
public void handle(CommandSender commandSender, LiteInvocation invocation, Schematic scheme) {
List<String> schematics = scheme.getSchematics();
for (String schematic : schematics) {
Formatter formatter = new Formatter()
.register("{USAGE}", schematic);
this.announcer.sendMessage(commandSender, formatter.format(this.config.messages.invalidCommandUsage));
}
}
}
@@ -1,34 +0,0 @@
package com.eternalcode.combat.command;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import dev.rollczi.litecommands.command.LiteInvocation;
import dev.rollczi.litecommands.command.permission.RequiredPermissions;
import dev.rollczi.litecommands.handle.PermissionHandler;
import org.bukkit.command.CommandSender;
import panda.utilities.text.Formatter;
import panda.utilities.text.Joiner;
public class PermissionMessage implements PermissionHandler<CommandSender> {
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public PermissionMessage(PluginConfig config, NotificationAnnouncer announcer) {
this.config = config;
this.announcer = announcer;
}
@Override
public void handle(CommandSender commandSender, LiteInvocation invocation, RequiredPermissions requiredPermissions) {
String value = Joiner.on(", ")
.join(requiredPermissions.getPermissions())
.toString();
Formatter formatter = new Formatter()
.register("{PERMISSION}", value);
this.announcer.sendMessage(commandSender, formatter.format(this.config.messages.noPermission));
}
}
@@ -1,119 +0,0 @@
package com.eternalcode.combat.config;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
public class ConfigBackupService {
private static final String BACKUP_FOLDER_NAME = "backup";
private static final String BACKUP_FILE_EXTENSION = ".bak";
private final File dataFolder;
public ConfigBackupService(File dataFolder) {
this.dataFolder = dataFolder;
}
public void createBackup() {
File backupFolder = new File(this.dataFolder, BACKUP_FOLDER_NAME);
if (!backupFolder.exists()) {
backupFolder.mkdirs();
}
LocalDateTime currentDateTime = LocalDateTime.now();
String backupFolderName = BACKUP_FOLDER_NAME + "_" + currentDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm"));
File currentBackupFolder = new File(backupFolder, backupFolderName);
if (!currentBackupFolder.exists()) {
currentBackupFolder.mkdirs();
}
this.copyFolderContents(this.dataFolder, currentBackupFolder);
this.deleteIfOlderDirectory(backupFolder);
}
private void copyFolderContents(File sourceFolder, File targetFolder) {
if (!sourceFolder.exists() || !sourceFolder.isDirectory()) {
return;
}
if (!targetFolder.exists()) {
boolean targetFolderCreated = targetFolder.mkdirs();
if (!targetFolderCreated) {
return;
}
}
File[] filesToBackup = sourceFolder.listFiles();
if (filesToBackup == null) {
return;
}
for (File file : filesToBackup) {
if (file.isDirectory() && !file.getName().equals(BACKUP_FOLDER_NAME)) {
File subFolder = new File(targetFolder, file.getName());
this.copyFolderContents(file, subFolder);
continue;
}
if (file.isFile() && !file.getName().endsWith(BACKUP_FILE_EXTENSION)) {
File backupFile = new File(targetFolder, file.getName() + BACKUP_FILE_EXTENSION);
this.copyToBackupFile(file, backupFile);
}
}
}
private void copyToBackupFile(File targetFolder, File path) {
try {
Files.copy(targetFolder.toPath(), path.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException exception) {
exception.printStackTrace();
}
}
// delete backup folders older than X days
private void deleteIfOlderDirectory(File backupFolder) {
File[] backupFolders = backupFolder.listFiles(File::isDirectory);
if (backupFolders == null) {
return;
}
for (File folder : backupFolders) {
String folderName = folder.getName();
if (!folderName.startsWith(BACKUP_FOLDER_NAME)) {
continue;
}
try {
LocalDate folderDate = LocalDate.parse(folderName.substring(BACKUP_FOLDER_NAME.length() + 1, folderName.lastIndexOf("_")));
LocalTime folderTime = LocalTime.parse(folderName.substring(folderName.lastIndexOf("_") + 1).replace("-", ":"));
LocalDateTime folderDateTime = LocalDateTime.of(folderDate, folderTime);
LocalDateTime currentDateTime = LocalDateTime.now();
long hours = ChronoUnit.HOURS.between(folderDateTime, currentDateTime);
if (hours > 72) {
FileUtils.deleteDirectory(folder);
}
}
catch (DateTimeParseException | IOException exception) {
exception.printStackTrace();
}
}
}
}
@@ -1,10 +0,0 @@
package com.eternalcode.combat.fight;
public enum FightDeathCause {
UNKNOWN,
ESCAPE,
KILLED_BY_PLAYER,
KILLED_BY_ENTITY
}
@@ -4,69 +4,26 @@ import com.eternalcode.combat.fight.event.CauseOfTag;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import com.eternalcode.combat.event.EventCaller;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
public class FightManager {
public interface FightManager {
private final Map<UUID, FightTag> fights = new ConcurrentHashMap<>();
private final EventCaller eventCaller;
boolean isInCombat(UUID player);
public FightManager(EventCaller eventCaller) {
this.eventCaller = eventCaller;
}
FightTag getTag(UUID target);
public boolean isInCombat(UUID player) {
if (!this.fights.containsKey(player)) {
return false;
}
Collection<FightTag> getFights();
FightTag fightTag = this.fights.get(player);
@ApiStatus.Experimental
FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger);
return !fightTag.isExpired();
}
FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag);
public FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag) {
FightUntagEvent event = this.eventCaller.publishEvent(new FightUntagEvent(player, causeOfUnTag));
if (event.isCancelled()) {
return event;
}
FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag);
this.fights.remove(player);
return event;
}
public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag) {
FightTagEvent event = this.eventCaller.publishEvent(new FightTagEvent(target, causeOfTag));
if (event.isCancelled()) {
return event;
}
Instant now = Instant.now();
Instant endOfCombatLog = now.plus(delay);
FightTag fightTag = new FightTag(target, endOfCombatLog);
this.fights.put(target, fightTag);
return event;
}
public Collection<FightTag> getFights() {
return Collections.unmodifiableCollection(this.fights.values());
}
public FightTag getTag(UUID target) {
return this.fights.get(target);
}
public void untagAll() {
this.fights.clear();
}
void untagAll();
}
@@ -3,58 +3,20 @@ package com.eternalcode.combat.fight;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
public class FightTag {
public interface FightTag {
private final UUID taggedPlayer;
private final Instant endOfCombatLog;
@Nullable
@ApiStatus.Experimental
UUID getTagger();
private double healthBeforeDeath;
private FightDeathCause deathCause;
Duration getRemainingDuration();
FightTag(UUID personToAddCombat, Instant endOfCombatLog) {
this.taggedPlayer = personToAddCombat;
this.endOfCombatLog = endOfCombatLog;
boolean isExpired();
this.healthBeforeDeath = 0;
this.deathCause = FightDeathCause.UNKNOWN;
}
Instant getEndOfCombatLog();
public UUID getTaggedPlayer() {
return this.taggedPlayer;
}
public Instant getEndOfCombatLog() {
return this.endOfCombatLog;
}
public boolean isExpired() {
return Instant.now().isAfter(this.endOfCombatLog);
}
public Duration getRemainingDuration() {
Duration between = Duration.between(Instant.now(), this.endOfCombatLog);
if (between.isNegative()) {
return Duration.ZERO;
}
return between;
}
public double getHealthBeforeDeath() {
return this.healthBeforeDeath;
}
public void setHealthBeforeDeath(double health) {
this.healthBeforeDeath = health;
}
public FightDeathCause getDeathCause() {
return this.deathCause;
}
public void setDeathCause(FightDeathCause deathCause) {
this.deathCause = deathCause;
}
UUID getTaggedPlayer();
}
@@ -1,9 +0,0 @@
package com.eternalcode.combat.fight.bossbar;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar;
import java.time.Duration;
public record FightBossBar(Audience audience, BossBar bossBar, float progress, Duration combatDuration) {
}
@@ -1,27 +0,0 @@
package com.eternalcode.combat.fight.bossbar;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
class FightBossBarRegistry {
private final Map<UUID, FightBossBar> fightBossBars = new ConcurrentHashMap<>();
void add(UUID uuid, FightBossBar fightBossBar) {
this.fightBossBars.put(uuid, fightBossBar);
}
void remove(UUID uuid) {
this.fightBossBars.remove(uuid);
}
boolean hasFightBossBar(UUID uuid) {
return this.fightBossBars.containsKey(uuid);
}
Optional<FightBossBar> getFightBossBar(UUID uuid) {
return Optional.ofNullable(this.fightBossBars.get(uuid));
}
}
@@ -1,119 +0,0 @@
package com.eternalcode.combat.fight.bossbar;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightTag;
import com.eternalcode.combat.notification.implementation.BossBarNotification;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
public class FightBossBarService {
private final PluginConfig pluginConfig;
private final FightBossBarRegistry bossBarRegistry = new FightBossBarRegistry();
private final AudienceProvider audienceProvider;
private final MiniMessage miniMessage;
public FightBossBarService(PluginConfig pluginConfig, AudienceProvider audienceProvider, MiniMessage miniMessage) {
this.pluginConfig = pluginConfig;
this.audienceProvider = audienceProvider;
this.miniMessage = miniMessage;
}
public void hide(FightTag fightTag, FightBossBar fightBossBar) {
Audience audience = fightBossBar.audience();
BossBar bossBar = fightBossBar.bossBar();
UUID taggedPlayer = fightTag.getTaggedPlayer();
audience.hideBossBar(bossBar);
this.bossBarRegistry.remove(taggedPlayer);
}
public void hide(UUID playerUuid) {
Optional<FightBossBar> bossBar = this.bossBarRegistry.getFightBossBar(playerUuid);
if (bossBar.isPresent()) {
FightBossBar fightBossBar = bossBar.get();
Audience audience = fightBossBar.audience();
BossBar bar = fightBossBar.bossBar();
audience.hideBossBar(bar);
this.bossBarRegistry.remove(playerUuid);
}
}
public void send(Player player, FightTag fightTag, BossBarNotification bossBarNotification, Formatter formatter) {
UUID playerUniqueId = player.getUniqueId();
FightBossBar fightBossBar = this.bossBarRegistry.getFightBossBar(playerUniqueId)
.orElseGet(() -> this.create(player, bossBarNotification, formatter));
if (!this.bossBarRegistry.hasFightBossBar(playerUniqueId)) {
this.show(player, fightBossBar);
return;
}
String message = formatter.format(bossBarNotification.message());
this.update(fightTag, fightBossBar, message);
}
private void update(FightTag fightTag, FightBossBar fightBossBar, String message) {
if (fightTag.isExpired()) {
this.hide(fightTag, fightBossBar);
return;
}
BossBar bossBar = fightBossBar.bossBar();
float fightBossBarProgress = fightBossBar.progress();
if (fightBossBarProgress < 0.0F) {
Instant now = Instant.now();
Instant endOfCombatLog = fightTag.getEndOfCombatLog();
Duration between = Duration.between(now, endOfCombatLog);
Duration combatDuration = fightBossBar.combatDuration();
float difference = (float) between.toMillis() / combatDuration.toMillis();
float progress = Math.max(0.0F, Math.min(1.0F, difference));
bossBar.progress(progress);
}
Component name = this.miniMessage.deserialize(message);
bossBar.name(name);
}
private void show(Player player, FightBossBar fightBossBar) {
UUID playerUniqueId = player.getUniqueId();
Audience audience = this.audienceProvider.player(playerUniqueId);
BossBar bossBar = fightBossBar.bossBar();
audience.showBossBar(bossBar);
this.bossBarRegistry.add(playerUniqueId, fightBossBar);
}
private FightBossBar create(Player player, BossBarNotification bossBarNotification, Formatter formatter) {
Audience audience = this.audienceProvider.player(player.getUniqueId());
Component name = this.miniMessage.deserialize(formatter.format(bossBarNotification.message()));
BossBar bossBar = bossBarNotification.create(name);
float progress = bossBarNotification.progress();
Duration combatDuration = this.pluginConfig.settings.combatDuration;
return new FightBossBar(audience, bossBar, progress, combatDuration);
}
}
@@ -1,59 +0,0 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.fight.FightDeathCause;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTag;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class FightDeathCauseController implements Listener {
private final FightManager fightManager;
public FightDeathCauseController(FightManager fightManager) {
this.fightManager = fightManager;
}
@EventHandler
public void onEntityDamage(EntityDamageByEntityEvent event) {
Entity victimEntity = event.getEntity();
Entity damagerEntity = event.getDamager();
if (!(victimEntity instanceof Player victim)) {
return;
}
if (!this.fightManager.isInCombat(victim.getUniqueId())) {
return;
}
if (victim.getHealth() > event.getFinalDamage()) {
return;
}
FightTag fightTag = this.fightManager.getTag(damagerEntity.getUniqueId());
if (damagerEntity instanceof Player) {
fightTag.setDeathCause(FightDeathCause.KILLED_BY_PLAYER);
return;
}
fightTag.setDeathCause(FightDeathCause.KILLED_BY_ENTITY);
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
FightTag fightTag = this.fightManager.getTag(player.getUniqueId());
fightTag.setDeathCause(FightDeathCause.ESCAPE);
}
}
@@ -1,45 +0,0 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTag;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import panda.utilities.text.Formatter;
public class FightEscapeController implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public FightEscapeController(FightManager fightManager, PluginConfig config, NotificationAnnouncer announcer) {
this.fightManager = fightManager;
this.config = config;
this.announcer = announcer;
}
@EventHandler
void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
Formatter formatter = new Formatter()
.register("{PLAYER}", player.getName());
String format = formatter.format(this.config.messages.playerLoggedOutDuringCombat);
this.announcer.broadcast(format);
FightTag fightTag = this.fightManager.getTag(player.getUniqueId());
fightTag.setHealthBeforeDeath(player.getHealth());
player.setHealth(0.0); // Untagged in PlayerDeathEvent
}
}
@@ -1,7 +1,5 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
import com.eternalcode.combat.fight.FightDeathCause;
import com.eternalcode.combat.fight.FightTag;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -15,18 +13,13 @@ public class Drop {
private final Player player;
private final Player killer;
private final FightTag fightTag;
private final FightDeathCause deathCause;
private final List<ItemStack> droppedItems;
private final int droppedExp;
private Drop(Player player, Player killer, FightTag fightTag, FightDeathCause deathCause, List<ItemStack> droppedItems, int droppedExp) {
private Drop(Player player, Player killer, List<ItemStack> droppedItems, int droppedExp) {
this.player = player;
this.killer = killer;
this.fightTag = fightTag;
this.deathCause = deathCause;
this.droppedItems = droppedItems;
this.droppedExp = droppedExp;
}
@@ -43,14 +36,6 @@ public class Drop {
return this.killer;
}
public FightTag getFightTag() {
return this.fightTag;
}
public FightDeathCause getDeathCause() {
return this.deathCause;
}
public boolean hasKiller() {
return this.killer != null;
}
@@ -67,8 +52,6 @@ public class Drop {
private Player player;
private Player killer;
private FightTag fightTag;
private FightDeathCause deathCause;
private List<ItemStack> droppedItems;
private int droppedExp;
@@ -82,16 +65,6 @@ public class Drop {
return this;
}
public Builder fightTag(@NotNull FightTag fightTag) {
this.fightTag = fightTag;
return this;
}
public Builder deathCause(@NotNull FightDeathCause deathCause) {
this.deathCause = deathCause;
return this;
}
public Builder droppedItems(@NotNull List<ItemStack> droppedItems) {
this.droppedItems = new ArrayList<>(droppedItems);
return this;
@@ -106,8 +79,6 @@ public class Drop {
return new Drop(
this.player,
this.killer,
this.fightTag,
this.deathCause,
this.droppedItems,
this.droppedExp
);
@@ -0,0 +1,16 @@
package com.eternalcode.combat.fight.drop;
import java.util.List;
import java.util.UUID;
import org.bukkit.inventory.ItemStack;
public interface DropKeepInventoryService {
List<ItemStack> nextItems(UUID uuid);
boolean hasItems(UUID uuid);
void addItems(UUID uuid, List<ItemStack> item);
void addItem(UUID uuid, ItemStack item);
}
@@ -1,4 +1,4 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
public interface DropModifier {
@@ -1,4 +1,4 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
import org.bukkit.inventory.ItemStack;
@@ -0,0 +1,8 @@
package com.eternalcode.combat.fight.drop;
public interface DropService {
DropResult modify(DropType dropType, Drop drop);
void registerModifier(DropModifier dropModifier);
}
@@ -1,4 +1,4 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
public enum DropType {
@@ -1,75 +1,25 @@
package com.eternalcode.combat.fight.effect;
import java.util.List;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.List;
import java.util.ArrayList;
/**
* Manages custom potion effects on players during combat.
* Active effects before combat are stored and restored after combat ends.
*/
public interface FightEffectService {
public class FightEffectService {
void removeCustomEffect(Player player, PotionEffectType type, Integer amplifier);
private final Map<UUID, List<PotionEffect>> activeEffects = new HashMap<>();
private static final int INFINITE_DURATION = -1;
void applyCustomEffect(Player player, PotionEffectType type, Integer amplifier);
public void storeActiveEffect(Player player, PotionEffect effect) {
List<PotionEffect> effects = this.activeEffects.computeIfAbsent(player.getUniqueId(), k -> new ArrayList<>());
List<PotionEffect> getCurrentEffects(Player player);
effects.add(effect);
}
void clearStoredEffects(Player player);
public void restoreActiveEffects(Player player) {
List<PotionEffect> currentEffects = this.getCurrentEffects(player);
void restoreActiveEffects(Player player);
for (PotionEffect effect : currentEffects) {
player.addPotionEffect(effect);
}
this.clearStoredEffects(player);
}
public void clearStoredEffects(Player player) {
this.activeEffects.remove(player.getUniqueId());
}
public List<PotionEffect> getCurrentEffects(Player player) {
return this.activeEffects.getOrDefault(player.getUniqueId(), new ArrayList<>());
}
public void applyCustomEffect(Player player, PotionEffectType type, Integer amplifier) {
PotionEffect activeEffect = player.getPotionEffect(type);
if (activeEffect == null) {
player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier));
return;
}
if (activeEffect.getAmplifier() > amplifier) {
return;
}
if (activeEffect.getDuration() == -1) {
return;
}
this.storeActiveEffect(player, activeEffect);
player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier));
}
public void removeCustomEffect(Player player, PotionEffectType type, Integer amplifier) {
PotionEffect activeEffect = player.getPotionEffect(type);
if (activeEffect == null) {
return;
}
if (activeEffect.getAmplifier() != amplifier) {
return;
}
player.removePotionEffect(type);
}
void storeActiveEffect(Player player, PotionEffect effect);
}
@@ -0,0 +1,7 @@
package com.eternalcode.combat.fight.event;
public enum CancelTagReason {
TAGOUT
}
@@ -1,7 +1,25 @@
package com.eternalcode.combat.fight.event;
public enum CauseOfTag {
/**
* The player was tagged in combat as a result of an attack by another player.
*/
PLAYER,
/**
* The player was tagged in combat due to an interaction with a non-player entity
* (e.g., mobs or environmental damage).
*/
NON_PLAYER,
COMMAND
/**
* A command was executed to apply a combat tag to the player.
*/
COMMAND,
/**
* A custom cause, typically defined by external plugins or systems, applied the combat tag.
*/
CUSTOM
}
@@ -1,10 +1,38 @@
package com.eternalcode.combat.fight.event;
public enum CauseOfUnTag {
/**
* The combat tag expired after a set duration.
*/
TIME_EXPIRED,
/**
* The player died, which resulted in the removal of the combat tag.
*/
DEATH,
/**
* The player was killed by another player, causing the combat tag to be removed.
*/
DEATH_BY_PLAYER,
/**
* The player logged out, triggering the removal of the combat tag.
*/
LOGOUT,
/**
* A command was executed to remove the player's combat tag.
*/
COMMAND,
ATTACKER_RELEASE
/**
* The attacker released the player, ending the combat and removing the tag.
*/
ATTACKER_RELEASE,
/**
* A custom cause, typically defined by external plugins or systems.
*/
CUSTOM
}
@@ -5,59 +5,80 @@ import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
/**
* This event is triggered when a player is tagged in a fight.
*/
public class FightTagEvent extends Event implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private static final HandlerList HANDLER_LIST = new HandlerList();
private final UUID player;
private final CauseOfTag cause;
private boolean isCancelled = false;
private String cancelMessage = "";
private boolean cancelled = false;
private CancelTagReason cancelReason;
public FightTagEvent(UUID player, CauseOfTag cause) {
super(false);
this.player = player;
this.cause = cause;
}
/**
* Gets the UUID of the player who was tagged.
*
* @return The UUID of the tagged player.
*/
public UUID getPlayer() {
return this.player;
}
/**
* Gets the reason why the player was tagged.
*
* @return The cause of the combat tag as a {@link CauseOfTag}.
*/
public CauseOfTag getCause() {
return this.cause;
}
@Override
public boolean isCancelled() {
return this.isCancelled;
return this.cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
throw new UnsupportedOperationException("Use #cancel(String) instead");
this.cancelled = cancelled;
}
public void cancel(String cancelMessage) {
this.isCancelled = true;
this.cancelMessage = cancelMessage;
/**
* Gets the reason why the event was cancelled.
*
* @return The cancel reason.
*/
public CancelTagReason getCancelReason() {
return this.cancelReason;
}
public void allow() {
this.isCancelled = false;
this.cancelMessage = "";
}
public String getCancelMessage() {
return this.cancelMessage;
/**
* Sets the reason why the event was cancelled.
*
* @param cancelReason The cancel reason.
*/
public void setCancelReason(CancelTagReason cancelReason) {
this.cancelReason = cancelReason;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
return HANDLER_LIST;
}
public static HandlerList getHandlerList() {
return handlers;
return HANDLER_LIST;
}
}
@@ -5,43 +5,61 @@ import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
/**
* This event is triggered when a player is untagged during a fight.
*/
public class FightUntagEvent extends Event implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private static final HandlerList HANDLER_LIST = new HandlerList();
private final UUID player;
private final CauseOfUnTag cause;
private boolean isCancelled = false;
private boolean cancelled;
public FightUntagEvent(UUID player, CauseOfUnTag cause) {
super(false);
this.player = player;
this.cause = cause;
}
/**
* Gets the UUID of the player who was untagged.
*
* @return The UUID of the untagged player.
*/
public UUID getPlayer() {
return this.player;
}
/**
* Gets the reason for why the player was untagged.
*
* @return The cause of untagging as a {@link CauseOfUnTag}.
*/
public CauseOfUnTag getCause() {
return this.cause;
}
@Override
public boolean isCancelled() {
return this.isCancelled;
return this.cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.isCancelled = cancelled;
this.cancelled = cancelled;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
return HANDLER_LIST;
}
public static HandlerList getHandlerList() {
return handlers;
return HANDLER_LIST;
}
}
@@ -0,0 +1,16 @@
package com.eternalcode.combat.fight.pearl;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
public interface FightPearlService {
Instant getDelay(UUID uuid);
Duration getRemainingDelay(UUID uuid);
boolean hasDelay(UUID uuid);
void markDelay(UUID uuid);
}
@@ -1,34 +1,13 @@
package com.eternalcode.combat.fight.tagout;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class FightTagOutService {
public interface FightTagOutService {
private final Map<UUID, Instant> tagOuts = new HashMap<>();
boolean isTaggedOut(UUID player);
public void tagOut(UUID player, Duration duration) {
Instant endTime = Instant.now().plus(duration);
this.tagOuts.put(player, endTime);
}
public void unTagOut(UUID player) {
this.tagOuts.remove(player);
}
public boolean isTaggedOut(UUID player) {
Instant endTime = this.tagOuts.get(player);
if (endTime == null) {
return false;
}
Instant now = Instant.now();
return now.isBefore(endTime);
}
void unTagOut(UUID player);
void tagOut(UUID player, Duration duration);
}
@@ -1,94 +0,0 @@
package com.eternalcode.combat.fight.tagout;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.util.DurationUtil;
import dev.rollczi.litecommands.argument.Arg;
import dev.rollczi.litecommands.command.execute.Execute;
import dev.rollczi.litecommands.command.permission.Permission;
import dev.rollczi.litecommands.command.route.Route;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
@Route(name = "tagout", aliases = "tagimmunity")
public class TagOutCommand {
private final FightTagOutService fightTagOutService;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public TagOutCommand(FightTagOutService fightTagOutService, NotificationAnnouncer announcer, PluginConfig config) {
this.fightTagOutService = fightTagOutService;
this.announcer = announcer;
this.config = config;
}
@Execute(required = 1)
@Permission("eternalcombat.tagout")
void tagout(Player sender, @Arg Duration time) {
UUID targetUniqueId = sender.getUniqueId();
Formatter formatter = new Formatter()
.register("{PLAYER}", sender.getName())
.register("{TIME}", DurationUtil.format(time));
this.fightTagOutService.tagOut(targetUniqueId, time);
String format = formatter.format(this.config.messages.admin.adminTagOutSelf);
this.announcer.sendMessage(sender, format);
}
@Execute(required = 2)
@Permission("eternalcombat.tagout")
void tagout(Player sender, @Arg Player target, @Arg Duration time) {
UUID targetUniqueId = target.getUniqueId();
Instant now = Instant.now();
Duration remaining = Duration.between(now, now.plus(time));
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName())
.register("{TIME}", DurationUtil.format(remaining));
this.fightTagOutService.tagOut(targetUniqueId, time);
String adminTagOutFormat = formatter.format(this.config.messages.admin.adminTagOut);
this.announcer.sendMessage(sender, adminTagOutFormat);
String playerTagOutFormat = formatter.format(this.config.messages.admin.playerTagOut);
this.announcer.sendMessage(target, playerTagOutFormat);
}
@Execute(route = "remove", required = 1)
@Permission("eternalcombat.tagout")
void untagout(Player sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
this.fightTagOutService.unTagOut(targetUniqueId);
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName());
if (!targetUniqueId.equals(sender.getUniqueId())) {
String adminUnTagOutFormat = formatter.format(this.config.messages.admin.adminTagOutOff);
this.announcer.sendMessage(sender, adminUnTagOutFormat);
}
this.announcer.sendMessage(target, this.config.messages.admin.playerTagOutOff);
}
@Execute(route = "remove", required = 0)
@Permission("eternalcombat.tagout")
void untagout(Player sender) {
UUID senderUniqueId = sender.getUniqueId();
this.fightTagOutService.unTagOut(senderUniqueId);
this.announcer.sendMessage(sender, this.config.messages.admin.playerTagOutOff);
}
}
@@ -1,79 +0,0 @@
package com.eternalcode.combat.notification;
import com.eternalcode.combat.notification.implementation.BossBarNotification;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.title.Title;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
public final class NotificationAnnouncer {
private final AudienceProvider audienceProvider;
private final MiniMessage miniMessage;
public NotificationAnnouncer(AudienceProvider audienceProvider, MiniMessage miniMessage) {
this.audienceProvider = audienceProvider;
this.miniMessage = miniMessage;
}
public void send(CommandSender sender, Notification notification, Formatter formatter) {
Audience audience = this.audience(sender);
Component message = this.miniMessage.deserialize(formatter.format(notification.message()));
NotificationType type = notification.type();
switch (type) {
case CHAT -> audience.sendMessage(message);
case ACTION_BAR -> audience.sendActionBar(message);
case TITLE -> {
Title title = Title.title(message, Component.empty());
audience.showTitle(title);
}
case SUB_TITLE -> {
Title subTitle = Title.title(Component.empty(), message);
audience.showTitle(subTitle);
}
case BOSS_BAR -> {
BossBarNotification bossBarNotification = (BossBarNotification) notification;
BossBar bossBar = bossBarNotification.create(message);
audience.showBossBar(bossBar);
}
default -> throw new IllegalStateException("Unknown notification type: " + type);
}
}
public void sendMessage(CommandSender commandSender, String text) {
Audience audience = this.audience(commandSender);
Component message = this.miniMessage.deserialize(text);
audience.sendMessage(message);
}
public void broadcast(String text) {
Audience audience = this.audienceProvider.all();
Component message = this.miniMessage.deserialize(text);
audience.sendMessage(message);
}
private Audience audience(CommandSender sender) {
if (sender instanceof Player player) {
return this.audienceProvider.player(player.getUniqueId());
}
return this.audienceProvider.console();
}
}
@@ -1,32 +0,0 @@
package com.eternalcode.combat.region;
import org.bukkit.Location;
public class DefaultRegionProvider implements RegionProvider {
private final int radius;
public DefaultRegionProvider(int radius) {
this.radius = radius;
}
@Override
public boolean isInRegion(Location location) {
Location spawnLocation = location.getWorld().getSpawnLocation();
double x = spawnLocation.getX();
double z = spawnLocation.getZ();
Point min = new Point(x - this.radius, z - this.radius);
Point max = new Point(x + this.radius, z + this.radius);
return this.contains(min, max, location.getX(), location.getZ());
}
public boolean contains(Point min, Point max, double x, double z) {
return x >= min.x() && x < max.x()
&& z >= min.z() && z < max.z();
}
public record Point(double x, double z) {}
}
@@ -0,0 +1,9 @@
package com.eternalcode.combat.region;
import org.bukkit.Location;
public interface Region {
Location getCenter();
}
@@ -1,10 +1,14 @@
package com.eternalcode.combat.region;
import java.util.Optional;
import org.bukkit.Location;
@FunctionalInterface
public interface RegionProvider {
boolean isInRegion(Location location);
Optional<Region> getRegion(Location location);
default boolean isInRegion(Location location) {
return this.getRegion(location).isPresent();
}
}
@@ -1,32 +0,0 @@
package com.eternalcode.combat.region;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldguard.WorldGuard;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.regions.RegionContainer;
import com.sk89q.worldguard.protection.regions.RegionQuery;
import org.bukkit.Location;
import java.util.List;
public class WorldGuardRegionProvider implements RegionProvider {
private final List<String> regions;
public WorldGuardRegionProvider(List<String> regions) {
this.regions = regions;
}
@Override
public boolean isInRegion(Location location) {
return this.regions.stream().anyMatch(region -> this.isInCombatRegion(location, region));
}
private boolean isInCombatRegion(Location location, String regionName) {
RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer();
RegionQuery regionQuery = regionContainer.createQuery();
ApplicableRegionSet applicableRegions = regionQuery.getApplicableRegions(BukkitAdapter.adapt(location));
return applicableRegions.getRegions().stream().anyMatch(region -> region.getId().equalsIgnoreCase(regionName));
}
}
@@ -1,25 +0,0 @@
package com.eternalcode.combat.util;
import java.time.Duration;
public final class DurationUtil {
private DurationUtil() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
public static String format(Duration duration) {
return format(duration, true);
}
public static String format(Duration duration, boolean removeMillis) {
if (removeMillis) {
duration = Duration.ofSeconds(duration.toSeconds());
}
return duration.toString()
.substring(2)
.replaceAll("(\\d[HMS])(?!$)", "$1 ")
.toLowerCase();
}
}
@@ -1,71 +0,0 @@
package com.eternalcode.combat.util;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.ToIntFunction;
public class InventoryUtil {
private static final Random RANDOM = new Random();
private InventoryUtil() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
public static <T> int calculateItemsToDelete(int percent, Collection<T> objectList, ToIntFunction<? super T> mapper) {
return MathUtil.getRoundedCountFromPercentage(percent, MathUtil.sum(objectList, mapper));
}
public static RemoveItemResult removeRandomItems(List<ItemStack> list, int itemsToDelete) {
List<ItemStack> restItems = new ArrayList<>(list);
Map<Integer, ItemStack> removedItems = new HashMap<>();
for (int i = 0; i < itemsToDelete; i++) {
if (restItems.isEmpty()) {
break;
}
int randomIndex = RANDOM.nextInt(restItems.size());
ItemStack item = restItems.get(randomIndex);
int newAmount = item.getAmount() - 1;
if (newAmount <= 0) {
restItems.remove(randomIndex);
incrementItem(removedItems, randomIndex, item);
continue;
}
item.setAmount(newAmount);
incrementItem(removedItems, randomIndex, item);
}
return new RemoveItemResult(restItems, new ArrayList<>(removedItems.values()));
}
// TODO: kinda shitty, but no time for a proper solution now
private static void incrementItem(Map<Integer, ItemStack> items, int index, ItemStack itemStack) {
ItemStack itemInList = items.get(index);
if (itemInList == null) {
ItemStack cloned = itemStack.clone();
cloned.setAmount(1);
items.put(index, cloned);
return;
}
if (!itemInList.isSimilar(itemStack)) {
throw new IllegalStateException("Items are not similar!");
}
itemInList.setAmount(itemInList.getAmount() + 1);
}
}
@@ -1,18 +0,0 @@
package com.eternalcode.combat.util.legacy;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
public final class Legacy {
private static final LegacyComponentSerializer AMPERSAND_SERIALIZER = LegacyComponentSerializer.builder()
.character('&')
.hexColors()
.useUnusualXRepeatedCharacterHexFormat()
.build();
public static Component component(String text) {
return AMPERSAND_SERIALIZER.deserialize(text);
}
}
@@ -1,15 +0,0 @@
package com.eternalcode.combat.util.legacy;
import net.kyori.adventure.text.Component;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
public final class LegacyColorProcessor implements UnaryOperator<Component> {
@Override
public Component apply(Component component) {
return component.replaceText(builder -> builder.match(Pattern.compile(".*"))
.replacement((matchResult, builder1) -> Legacy.component(matchResult.group())));
}
}
@@ -1,53 +0,0 @@
package com.eternalcode.combat.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
class DurationUtilTest {
private static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{ "3m 30s", true, Duration.ofMinutes(3).plusSeconds(30).plusMillis(500) },
{ "3m 30.5s", false, Duration.ofMinutes(3).plusSeconds(30).plusMillis(500) },
{ "59m 59s", true, Duration.ofMinutes(59).plusSeconds(59) },
{ "1h 1m", true, Duration.ofHours(1).plusMinutes(1).plusMillis(1) },
{ "0s", true, Duration.ofMillis(500) },
{ "0.5s", false, Duration.ofMillis(500) },
{ "0s", true, Duration.ofMillis(500) },
{ "0s", false, Duration.ZERO },
{ "30s", true, Duration.ofSeconds(30) },
{ "30s", false, Duration.ofSeconds(30) },
{ "30s", true, Duration.ofSeconds(30).plusMillis(500) },
{ "30.5s", false, Duration.ofSeconds(30).plusMillis(500) },
{ "1h 30m 30s", true, Duration.ofHours(1).plusMinutes(30).plusSeconds(30).plusMillis(500) },
{ "1h 30m 30.5s", false, Duration.ofHours(1).plusMinutes(30).plusSeconds(30).plusMillis(500) },
{ "1h 1m 1s", true, Duration.ofHours(1).plusMinutes(1).plusSeconds(1) },
{ "23h 59m 59s", true, Duration.ofHours(23).plusMinutes(59).plusSeconds(59) },
{ "23h 59m 59s", true, Duration.ofHours(23).plusMinutes(59).plusSeconds(59).plusMillis(999) },
{ "26h 3m 4s", true, Duration.ofDays(1).plusHours(2).plusMinutes(3).plusSeconds(4).plusMillis(500) },
{ "1h 1m 1s", true, Duration.ofHours(1).plusMinutes(1).plusSeconds(1).plusMillis(1) },
{ "1h 1s", true, Duration.ofHours(1).plusSeconds(1) },
{ "1m", true, Duration.ofMinutes(1).plusMillis(1) },
});
}
@ParameterizedTest
@MethodSource("data")
void testFormat(String expected, boolean removeMillis, Duration duration) {
String actual = DurationUtil.format(duration, removeMillis);
assertEquals(expected, actual);
}
}
+57 -41
View File
@@ -1,36 +1,59 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
`java-library`
checkstyle
`eternalcombat-java`
`eternalcombat-repositories`
id("net.minecrell.plugin-yml.bukkit") version "0.6.0"
id("com.github.johnrengelman.shadow") version "8.1.1"
id("xyz.jpenilla.run-paper") version "2.1.0"
}
checkstyle {
toolVersion = "10.12.2"
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
maxErrors = 0
maxWarnings = 0
}
repositories {
mavenCentral()
maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") }
maven { url = uri("https://papermc.io/repo/repository/maven-public/") }
maven { url = uri("https://repo.eternalcode.pl/releases") }
maven { url = uri("https://storehouse.okaeri.eu/repository/maven-public/") }
maven { url = uri("https://repo.panda-lang.org/releases") }
maven { url = uri("https://maven.enginehub.org/repo/") }
id("net.minecrell.plugin-yml.bukkit")
id("io.github.goooler.shadow")
id("xyz.jpenilla.run-paper")
}
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)
}
}
// litecommands
implementation("dev.rollczi:litecommands-bukkit:${Versions.LITE_COMMANDS}")
// Okaeri configs
implementation("eu.okaeri:okaeri-configs-yaml-bukkit:${Versions.OKAERI_CONFIGS_YAML_BUKKIT}")
implementation("eu.okaeri:okaeri-configs-serdes-commons:${Versions.OKAERI_CONFIGS_SERDES_COMMONS}")
implementation("eu.okaeri:okaeri-configs-serdes-bukkit:${Versions.OKAERI_CONFIGS_SERDES_BUKKIT}")
// Panda utilities
implementation("org.panda-lang:panda-utilities:${Versions.PANDA_UTILITIES}")
// GitCheck
implementation("com.eternalcode:gitcheck:${Versions.GIT_CHECK}")
// commons
implementation("commons-io:commons-io:${Versions.APACHE_COMMONS}")
// bstats
implementation("org.bstats:bstats-bukkit:${Versions.B_STATS_BUKKIT}")
// caffeine
implementation("com.github.ben-manes.caffeine:caffeine:${Versions.CAFFEINE}")
implementation("com.eternalcode:eternalcode-commons-bukkit:${Versions.ETERNALCODE_COMMONS}")
implementation("com.eternalcode:eternalcode-commons-adventure:${Versions.ETERNALCODE_COMMONS}")
// worldguard
compileOnly("com.sk89q.worldguard:worldguard-bukkit:${Versions.WORLD_GUARD_BUKKIT}")
// PlaceholderAPI
compileOnly("me.clip:placeholderapi:${Versions.PLACEHOLDER_API}")
// Multification
implementation("com.eternalcode:multification-bukkit:${Versions.MULTIFICATION}")
implementation("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION}")
}
bukkit {
@@ -39,27 +62,17 @@ bukkit {
apiVersion = "1.13"
prefix = "EternalCombat"
name = "EternalCombat"
softDepend = listOf("WorldGuard")
softDepend = listOf("WorldGuard", "PlaceholderAPI")
version = "${project.version}"
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType<JavaCompile> {
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
options.encoding = "UTF-8"
}
tasks {
runServer {
minecraftVersion("1.19.4")
minecraftVersion("1.21.1")
}
}
tasks.withType<ShadowJar> {
tasks.shadowJar {
archiveFileName.set("EternalCombat v${project.version}.jar")
dependsOn("checkstyleMain")
@@ -86,7 +99,10 @@ tasks.withType<ShadowJar> {
"com.eternalcode.gitcheck",
"org.json.simple",
"org.apache.commons",
"javassist"
"javassist",
"com.github.benmanes.caffeine",
"com.eternalcode.commons",
"com.eternalcode.multification"
).forEach { pack ->
relocate(pack, "$prefix.$pack")
}
@@ -1,51 +1,56 @@
package com.eternalcode.combat;
import com.eternalcode.combat.bridge.BridgeService;
import com.eternalcode.combat.command.InvalidUsage;
import com.eternalcode.combat.command.PermissionMessage;
import com.eternalcode.combat.config.ConfigBackupService;
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.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.drop.DropController;
import com.eternalcode.combat.drop.DropKeepInventoryManager;
import com.eternalcode.combat.drop.DropManager;
import com.eternalcode.combat.drop.impl.PercentDropModifier;
import com.eternalcode.combat.drop.impl.PlayersHealthDropModifier;
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.controller.FightDeathCauseController;
import com.eternalcode.combat.fight.controller.FightEscapeController;
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.effect.FightEffectController;
import com.eternalcode.combat.event.EventCaller;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightManagerImpl;
import com.eternalcode.combat.fight.FightTask;
import com.eternalcode.combat.fight.bossbar.FightBossBarService;
import com.eternalcode.combat.fight.effect.FightEffectService;
import com.eternalcode.combat.fight.effect.FightEffectServiceImpl;
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.FightPearlManager;
import com.eternalcode.combat.fight.pearl.FightPearlServiceImpl;
import com.eternalcode.combat.fight.tagout.FightTagOutController;
import com.eternalcode.combat.fight.tagout.FightTagOutService;
import com.eternalcode.combat.fight.tagout.TagOutCommand;
import com.eternalcode.combat.fight.tagout.FightTagOutServiceImpl;
import com.eternalcode.combat.fight.tagout.FightTagOutCommand;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.region.RegionController;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.combat.updater.UpdaterNotificationController;
import com.eternalcode.combat.updater.UpdaterService;
import com.eternalcode.combat.util.legacy.LegacyColorProcessor;
import com.eternalcode.commons.adventure.AdventureLegacyColorPostProcessor;
import com.eternalcode.commons.adventure.AdventureLegacyColorPreProcessor;
import com.eternalcode.multification.notice.Notice;
import com.google.common.base.Stopwatch;
import dev.rollczi.litecommands.LiteCommands;
import dev.rollczi.litecommands.bukkit.adventure.platform.LiteBukkitAdventurePlatformFactory;
import dev.rollczi.litecommands.bukkit.tools.BukkitOnlyPlayerContextual;
import dev.rollczi.litecommands.bukkit.tools.BukkitPlayerArgument;
import dev.rollczi.litecommands.bukkit.LiteBukkitFactory;
import dev.rollczi.litecommands.bukkit.LiteBukkitMessages;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bstats.bukkit.Metrics;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
@@ -54,12 +59,19 @@ import java.util.stream.Stream;
public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
private static final String FALLBACK_PREFIX = "eternalcombat";
private static final int BSTATS_METRICS_ID = 17803;
private FightManager fightManager;
private FightPearlManager fightPearlManager;
private FightPearlService fightPearlService;
private FightTagOutService fightTagOutService;
private FightEffectService fightEffectService;
private DropManager dropManager;
private DropKeepInventoryManager dropKeepInventoryManager;
private LogoutService logoutService;
private DropService dropService;
private DropKeepInventoryService dropKeepInventoryService;
private RegionProvider regionProvider;
private PluginConfig pluginConfig;
@@ -74,70 +86,78 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
File dataFolder = this.getDataFolder();
ConfigBackupService backupService = new ConfigBackupService(dataFolder);
ConfigService configService = new ConfigService(backupService);
ConfigService configService = new ConfigService();
EventCaller eventCaller = new EventCaller(server);
this.pluginConfig = configService.create(PluginConfig.class, new File(dataFolder, "config.yml"));
this.fightManager = new FightManager(eventCaller);
this.fightPearlManager = new FightPearlManager(this.pluginConfig.pearl);
this.fightTagOutService = new FightTagOutService();
this.fightEffectService = new FightEffectService();
this.dropManager = new DropManager();
this.dropKeepInventoryManager = new DropKeepInventoryManager();
this.fightManager = new FightManagerImpl(eventCaller);
this.fightPearlService = new FightPearlServiceImpl(this.pluginConfig.pearl);
this.fightTagOutService = new FightTagOutServiceImpl();
this.fightEffectService = new FightEffectServiceImpl();
this.logoutService = new LogoutService();
this.dropService = new DropServiceImpl();
this.dropKeepInventoryService = new DropKeepInventoryServiceImpl();
UpdaterService updaterService = new UpdaterService(this.getDescription());
this.audienceProvider = BukkitAudiences.create(this);
MiniMessage miniMessage = MiniMessage.builder()
.postProcessor(new LegacyColorProcessor())
.postProcessor(new AdventureLegacyColorPostProcessor())
.preProcessor(new AdventureLegacyColorPreProcessor())
.build();
FightBossBarService fightBossBarService = new FightBossBarService(this.pluginConfig, this.audienceProvider, miniMessage);
BridgeService bridgeService = new BridgeService(this.pluginConfig, server.getPluginManager(), this.getLogger());
bridgeService.init();
BridgeService bridgeService = new BridgeService(this.pluginConfig, server.getPluginManager(), this.getLogger(), this);
bridgeService.init(this.fightManager, server);
this.regionProvider = bridgeService.getRegionProvider();
NotificationAnnouncer notificationAnnouncer = new NotificationAnnouncer(this.audienceProvider, miniMessage);
this.liteCommands = LiteBukkitAdventurePlatformFactory.builder(server, "eternalcombat", this.audienceProvider)
.argument(Player.class, new BukkitPlayerArgument<>(this.getServer(), this.pluginConfig.messages.playerNotFound))
.contextualBind(Player.class, new BukkitOnlyPlayerContextual<>(this.pluginConfig.messages.admin.onlyForPlayers))
NotificationAnnouncer notificationAnnouncer = new NotificationAnnouncer(this.audienceProvider, this.pluginConfig, miniMessage);
.invalidUsageHandler(new InvalidUsage(this.pluginConfig, notificationAnnouncer))
.permissionHandler(new PermissionMessage(this.pluginConfig, notificationAnnouncer))
this.liteCommands = LiteBukkitFactory.builder(FALLBACK_PREFIX, this, server)
.message(LiteBukkitMessages.PLAYER_NOT_FOUND, this.pluginConfig.messages.playerNotFound)
.message(LiteBukkitMessages.PLAYER_ONLY, this.pluginConfig.messages.admin.onlyForPlayers)
.commandInstance(new CombatCommand(this.fightManager, configService, notificationAnnouncer, this.pluginConfig))
.commandInstance(new TagOutCommand(this.fightTagOutService, notificationAnnouncer, this.pluginConfig))
.invalidUsage(new InvalidUsageHandlerImpl(this.pluginConfig, notificationAnnouncer))
.missingPermission(new MissingPermissionHandlerImpl(this.pluginConfig, notificationAnnouncer))
.register();
.commands(
new FightTagCommand(this.fightManager, notificationAnnouncer, this.pluginConfig),
new FightTagOutCommand(this.fightTagOutService, notificationAnnouncer, this.pluginConfig),
new EternalCombatReloadCommand(configService, notificationAnnouncer)
)
FightTask fightTask = new FightTask(server, this.pluginConfig, this.fightManager, fightBossBarService, notificationAnnouncer);
.result(Notice.class, (invocation, result, chain) -> notificationAnnouncer.create()
.viewer(invocation.sender())
.notice(result)
.send())
.build();
FightTask fightTask = new FightTask(server, this.pluginConfig, this.fightManager, notificationAnnouncer);
this.getServer().getScheduler().runTaskTimer(this, fightTask, 20L, 20L);
new Metrics(this, 17803);
new Metrics(this, BSTATS_METRICS_ID);
Stream.of(
new PercentDropModifier(this.pluginConfig.dropSettings),
new PlayersHealthDropModifier(this.pluginConfig.dropSettings)
).forEach(this.dropManager::registerModifier);
new PlayersHealthDropModifier(this.pluginConfig.dropSettings, this.logoutService)
).forEach(this.dropService::registerModifier);
Stream.of(
new FightDeathCauseController(this.fightManager),
new DropController(this.dropManager, this.dropKeepInventoryManager, this.pluginConfig.dropSettings, this.fightManager),
new FightTagController(this.fightManager, this.pluginConfig, notificationAnnouncer),
new FightUnTagController(this.fightManager, this.pluginConfig),
new FightEscapeController(this.fightManager, this.pluginConfig, notificationAnnouncer),
new DropController(this.dropService, this.dropKeepInventoryService, this.pluginConfig.dropSettings, this.fightManager),
new FightTagController(this.fightManager, this.pluginConfig),
new LogoutController(this.fightManager, this.logoutService, notificationAnnouncer, this.pluginConfig),
new FightUnTagController(this.fightManager, this.pluginConfig, this.logoutService),
new FightActionBlockerController(this.fightManager, notificationAnnouncer, this.pluginConfig),
new FightPearlController(this.pluginConfig.pearl, notificationAnnouncer, this.fightManager, this.fightPearlManager),
new FightPearlController(this.pluginConfig.pearl, notificationAnnouncer, this.fightManager, this.fightPearlService),
new UpdaterNotificationController(updaterService, this.pluginConfig, this.audienceProvider, miniMessage),
new RegionController(notificationAnnouncer, this.regionProvider, this.fightManager, this.pluginConfig),
new FightEffectController(this.pluginConfig.effect, this.fightEffectService, this.fightManager, this.getServer()),
new FightTagOutController(this.fightTagOutService, this.pluginConfig),
new FightMessageController(this.fightManager, notificationAnnouncer, fightBossBarService, this.pluginConfig, this.getServer())
new FightTagOutController(this.fightTagOutService),
new FightMessageController(this.fightManager, notificationAnnouncer, this.pluginConfig, this.getServer())
).forEach(listener -> this.getServer().getPluginManager().registerEvents(listener, this));
EternalCombatProvider.initialize(this);
@@ -151,7 +171,7 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
EternalCombatProvider.deinitialize();
if (this.liteCommands != null) {
this.liteCommands.getPlatform().unregisterAll();
this.liteCommands.unregister();
}
if (this.audienceProvider != null) {
@@ -172,8 +192,8 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
}
@Override
public FightPearlManager getFightPearlManager() {
return this.fightPearlManager;
public FightPearlService getFightPearlService() {
return this.fightPearlService;
}
@Override
@@ -187,17 +207,12 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
}
@Override
public DropManager getDropManager() {
return this.dropManager;
public DropService getDropService() {
return this.dropService;
}
@Override
public DropKeepInventoryManager getDropKeepInventoryManager() {
return this.dropKeepInventoryManager;
}
@Override
public PluginConfig getPluginConfig() {
return this.pluginConfig;
public DropKeepInventoryService getDropKeepInventoryService() {
return this.dropKeepInventoryService;
}
}
@@ -0,0 +1,46 @@
package com.eternalcode.combat;
import com.eternalcode.combat.config.ConfigService;
import com.eternalcode.combat.notification.NotificationAnnouncer;
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;
import dev.rollczi.litecommands.annotations.command.Command;
import dev.rollczi.litecommands.annotations.context.Context;
import dev.rollczi.litecommands.annotations.execute.Execute;
import dev.rollczi.litecommands.annotations.permission.Permission;
import java.time.Duration;
import org.bukkit.command.CommandSender;
@Command(name = "combatlog", aliases = "combat")
@Permission("eternalcombat.reload")
public class EternalCombatReloadCommand {
private static final Notice RELOAD_MESSAGE = BukkitNotice.builder()
.chat("<b><gradient:#8a1212:#fc6b03>EternalCombat:</gradient></b> Reloaded EternalCombat in <color:#fce303>{TIME}ms!</color>")
.build();
private final ConfigService configService;
private final NotificationAnnouncer announcer;
public EternalCombatReloadCommand(ConfigService configService, NotificationAnnouncer announcer) {
this.configService = configService;
this.announcer = announcer;
}
@Async
@Execute(name = "reload")
void execute(@Context CommandSender sender) {
Stopwatch stopwatch = Stopwatch.createStarted();
this.configService.reload();
Duration elapsed = stopwatch.elapsed();
this.announcer.create()
.viewer(sender)
.notice(RELOAD_MESSAGE)
.placeholder("{TIME}", String.valueOf(elapsed.toMillis()))
.send();
}
}
@@ -1,9 +1,13 @@
package com.eternalcode.combat.bridge;
import com.eternalcode.combat.bridge.placeholder.FightTagPlaceholder;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.region.DefaultRegionProvider;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.combat.region.WorldGuardRegionProvider;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import java.util.logging.Logger;
@@ -13,33 +17,40 @@ public class BridgeService {
private final PluginConfig pluginConfig;
private final PluginManager pluginManager;
private final Logger logger;
private final Plugin plugin;
private RegionProvider regionProvider;
public BridgeService(PluginConfig pluginConfig, PluginManager pluginManager, Logger logger) {
public BridgeService(PluginConfig pluginConfig, PluginManager pluginManager, Logger logger, Plugin plugin) {
this.pluginConfig = pluginConfig;
this.pluginManager = pluginManager;
this.logger = logger;
this.plugin = plugin;
}
public void init() {
public void init(FightManager fightManager, Server server) {
this.initialize("WorldGuard",
() -> this.regionProvider = new WorldGuardRegionProvider(this.pluginConfig.settings.blockedRegions),
() -> this.regionProvider = new WorldGuardRegionProvider(this.pluginConfig.settings.blockedRegions, this.pluginConfig),
() -> {
this.regionProvider = new DefaultRegionProvider(this.pluginConfig.settings.blockedRegionRadius);
this.regionProvider = new DefaultRegionProvider(this.pluginConfig.settings.blockedRegionRadius);
this.logger.warning("WorldGuard is not installed, set to default region provider.");
});
this.logger.warning("WorldGuard is not installed, set to default region provider.");
});
this.initialize("PlaceholderAPI",
() -> new FightTagPlaceholder(fightManager, server, this.plugin).register(),
() -> this.logger.warning("PlaceholderAPI is not installed, placeholders will not be registered.")
);
}
private void initialize(String pluginName, BridgeInitializer initializer, Runnable runnable) {
private void initialize(String pluginName, BridgeInitializer initializer, Runnable failureHandler) {
if (this.pluginManager.isPluginEnabled(pluginName)) {
initializer.initialize();
this.logger.info("Successfully initialized " + pluginName + " bridge.");
}
else {
runnable.run();
failureHandler.run();
}
}
@@ -0,0 +1,98 @@
package com.eternalcode.combat.bridge.placeholder;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTag;
import com.eternalcode.combat.util.DurationUtil;
import com.eternalcode.commons.time.DurationParser;
import java.util.Optional;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
public class FightTagPlaceholder extends PlaceholderExpansion {
private final FightManager fightManager;
private final Server server;
private final Plugin plugin;
private static final String IDENTIFIER = "eternalcombat";
public FightTagPlaceholder(FightManager fightManager, Server server, Plugin plugin) {
this.fightManager = fightManager;
this.server = server;
this.plugin = plugin;
}
@Override
public boolean canRegister() {
return true;
}
@Override
public @NotNull String getIdentifier() {
return IDENTIFIER;
}
@Override
public @NotNull String getAuthor() {
return this.plugin.getDescription().getAuthors().get(0);
}
@Override
public @NotNull String getVersion() {
return this.plugin.getDescription().getVersion();
}
@Override
public String onRequest(OfflinePlayer player, String identifier) {
if (identifier.equals("remaining_seconds")) {
return this.getFightTag(player)
.map(fightTagInter -> DurationUtil.format(fightTagInter.getRemainingDuration()))
.orElse("");
}
if (identifier.equals("remaining_millis")) {
return this.getFightTag(player)
.map(fightTag -> DurationParser.TIME_UNITS.format(fightTag.getRemainingDuration()))
.orElse("");
}
if (identifier.equals("opponent")) {
return this.getTagger(player)
.map(tagger -> tagger.getName())
.orElse("");
}
if (identifier.equals("opponent_health")) {
return this.getTagger(player)
.map(tagger -> String.format("%.2f", tagger.getHealth()))
.orElse("");
}
return null;
}
private @NotNull Optional<Player> getTagger(OfflinePlayer player) {
return this.getFightTag(player)
.map(fightTagInter -> fightTagInter.getTagger())
.map(taggerId -> this.server.getPlayer(taggerId));
}
private Optional<FightTag> getFightTag(OfflinePlayer player) {
Player onlinePlayer = player.getPlayer();
if (onlinePlayer != null) {
if (!this.fightManager.isInCombat(onlinePlayer.getUniqueId())) {
return Optional.empty();
}
return Optional.of(this.fightManager.getTag(onlinePlayer.getUniqueId()));
}
return Optional.empty();
}
}
@@ -1,6 +1,10 @@
package com.eternalcode.combat.config;
import com.eternalcode.combat.notification.serializer.NotificationSerializer;
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.okaeri.MultificationSerdesPack;
import eu.okaeri.configs.ConfigManager;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.serdes.commons.SerdesCommons;
@@ -13,23 +17,18 @@ import java.util.Set;
public class ConfigService {
private final ConfigBackupService backupService;
private final Set<OkaeriConfig> configs = new HashSet<>();
public ConfigService(ConfigBackupService backupService) {
this.backupService = backupService;
}
public <T extends OkaeriConfig> T create(Class<T> config, File file) {
this.backupService.createBackup();
T configFile = ConfigManager.create(config);
configFile.withConfigurer(new YamlBukkitConfigurer(), new SerdesCommons(), new SerdesBukkit());
configFile.withSerdesPack(registry -> {
registry.register(new NotificationSerializer());
});
YamlBukkitConfigurer yamlBukkitConfigurer = new YamlBukkitConfigurer();
NoticeResolverRegistry noticeRegistry = NoticeResolverDefaults.createRegistry()
.registerResolver(new SoundBukkitResolver());
configFile.withConfigurer(yamlBukkitConfigurer, new SerdesCommons(), new SerdesBukkit());
configFile.withConfigurer(yamlBukkitConfigurer, new MultificationSerdesPack(noticeRegistry));
configFile.withSerdesPack(registry -> registry.register(new NotificationSerializer()));
configFile.withBindFile(file);
configFile.withRemoveOrphans(true);
@@ -1,14 +1,16 @@
package com.eternalcode.combat.config.implementation;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.drop.DropSettings;
import com.eternalcode.combat.fight.drop.DropSettings;
import com.eternalcode.combat.fight.effect.FightEffectSettings;
import com.eternalcode.combat.fight.pearl.FightPearlSettings;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.implementation.ActionBarNotification;
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 org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;
import org.bukkit.entity.EntityType;
import org.bukkit.event.entity.EntityDamageEvent;
@@ -21,13 +23,13 @@ public class PluginConfig extends OkaeriConfig {
@Comment("# Do you want to change the plugin settings?")
public Settings settings = new Settings();
@Comment({" ", "# Ender pearl settings"})
@Comment({ " ", "# Ender pearl settings" })
public FightPearlSettings pearl = new FightPearlSettings();
@Comment({" ", "# Custom effects settings"})
@Comment({ " ", "# Custom effects settings" })
public FightEffectSettings effect = new FightEffectSettings();
@Comment({" ", "# Set a custom way for a player's items to drop on death (if in combat)"})
@Comment({ " ", "# Set a custom way for a player's items to drop on death (if in combat)" })
public DropSettings dropSettings = new DropSettings();
public static class Settings extends OkaeriConfig {
@@ -46,8 +48,15 @@ public class PluginConfig extends OkaeriConfig {
@Comment("# List of regions to block")
public List<String> blockedRegions = Collections.singletonList("your_region");
@Comment("# Set the knock multiplier for the blocked region")
public double blockedRegionKnockMultiplier = 1.2;
@Comment({
"# Set the knock multiplier for the blocked region",
"# Values can be decimal. Do NOT use negative values.",
"# Setting it around 1 knocks the player around 2-4 blocks away."
})
public double blockedRegionKnockMultiplier = 1;
@Comment({ "# Should the player be prevented from entering regions with WorldGuard flag PVP set to DENY " })
public boolean shouldPreventPvpRegions = true;
@Comment("# Set the radius of the blocked region if you aren't using WorldGuard basen on default spawn region!")
public int blockedRegionRadius = 10;
@@ -55,6 +64,18 @@ public class PluginConfig extends OkaeriConfig {
@Comment("# Release attacker after victim dies?")
public boolean shouldReleaseAttacker = true;
@Comment({ "# If you want to exclude admins from combat, ",
"# Setting this to true - admins cannot be tagged and will not tag other players on hit",
"# Setting this to false - admins can be tagged and can tag other players on hit"
})
public boolean excludeOpFromCombat = false;
@Comment({"# If you want to exclude players in creative mode from combat, ",
"# Setting this to true - players in creative mode cannot be tagged and will not tag other players on hit",
"# Setting this to false - players in creative mode can be tag others"
})
public boolean excludeCreativeFromCombat = false;
@Comment("# Command blocking mode, available modes: WHITELIST, BLACKLIST")
public WhitelistBlacklistMode commandBlockingMode = WhitelistBlacklistMode.BLACKLIST;
@@ -72,6 +93,9 @@ public class PluginConfig extends OkaeriConfig {
@Comment("# Block the use of elytra?")
public boolean shouldPreventElytraUsage = true;
@Comment("# Block flying? (flying players will fall to the ground)")
public boolean shouldPreventFlying = true;
@Comment("# Disable the use of elytra on damage?")
public boolean shouldElytraDisableOnDamage = true;
@@ -81,8 +105,21 @@ public class PluginConfig extends OkaeriConfig {
@Comment("# Whether to block the placement of blocks?")
public boolean shouldPreventBlockPlacing = true;
@Comment("# The minimum Y level at which block placing is blocked")
public int minBlockPlacingLevel = 40;
@Comment({ "# Block the placement of blocks above or below a certain Y coordinate",
"# Select the mode for block placing, available modes: ABOVE, BELOW"
})
public BlockPlacingMode blockPlacingMode = BlockPlacingMode.ABOVE;
@Comment("# Block placing mode custom name used if messages")
public String blockPlacingModeName = "above";
@Comment("# Set the Y coordinate for block placing relative to mode selected above")
public int blockPlacingYCoordinate = 40;
public enum BlockPlacingMode {
ABOVE,
BELOW
}
@Comment({
"# Disable placing specific blocks?",
@@ -121,7 +158,7 @@ public class PluginConfig extends OkaeriConfig {
}
@Comment({" ", "# Do you want to change the plugin messages?"})
@Comment({ " ", "# Do you want to change the plugin messages?" })
public Messages messages = new Messages();
public static class Messages extends OkaeriConfig {
@@ -139,86 +176,88 @@ public class PluginConfig extends OkaeriConfig {
"# BossBar colors: https://javadoc.io/static/net.kyori/adventure-api/4.14.0/net/kyori/adventure/bossbar/BossBar.Color.html",
"# BossBar overlays: https://javadoc.io/static/net.kyori/adventure-api/4.14.0/net/kyori/adventure/bossbar/BossBar.Overlay.html"
})
public Notification combatNotification = new ActionBarNotification("&dCombat ends in: &f{TIME}");
public Notice combatNotification = BukkitNotice.builder()
.actionBar("&dCombat ends in: &f{TIME}")
.sound(Sound.ENTITY_EXPERIENCE_ORB_PICKUP, SoundCategory.PLAYERS, 2.0F, 1.0F)
.build();
@Comment("# Message sent when the player does not have permission to perform a command")
public String noPermission = "&cYou don't have permission to perform this command!";
public Notice noPermission = Notice.chat("&cYou don't have permission \"{PERMISSION}\" to perform this command!");
@Comment("# Message sent when the specified player could not be found")
public String playerNotFound = "&cThe specified player could not be found!";
public Notice playerNotFound = Notice.chat("&cThe specified player could not be found!");
@Comment("# Message sent when the player enters combat")
public String playerTagged = "&cYou are in combat, do not leave the server!";
public Notice playerTagged = Notice.chat("&cYou are in combat, do not leave the server!");
@Comment("# Message sent when the player leaves combat")
public String playerUntagged = "&aYou are no longer in combat! You can safely leave the server.";
public Notice playerUntagged = Notice.chat("&aYou are no longer in combat! You can safely leave the server.");
@Comment("# This is broadcast when the player is in combat and logs out")
public String playerLoggedOutDuringCombat = "&c{PLAYER} logged off during the fight!";
public Notice playerLoggedOutDuringCombat = Notice.chat("&c{PLAYER} logged off during the fight!");
@Comment({
"# Message sent when the player is in combat and tries to use a disabled command",
"# you can configure the list of disabled commands in the blockedCommands section of the config.yml file"
})
public String commandDisabledDuringCombat = "&cUsing this command during combat is prohibited!";
public Notice commandDisabledDuringCombat = Notice.chat("&cUsing this command during combat is prohibited!");
@Comment("# Message sent when player tries to use a command with invalid arguments")
public String invalidCommandUsage = "&7Correct usage: &e{USAGE}";
public Notice invalidCommandUsage = Notice.chat("&7Correct usage: &e{USAGE}");
@Comment("# Message sent when player tries to open inventory, but the inventory open is blocked")
public String inventoryBlockedDuringCombat = "&cYou cannot open this inventory during combat!";
public Notice inventoryBlockedDuringCombat = Notice.chat("&cYou cannot open this inventory during combat!");
@Comment("# Message sent when player tries to place a block, but the block place is blocked")
public String blockPlacingBlockedDuringCombat = "&cYou cannot place above 40Y coordinate during combat!";
@Comment({ "# Message sent when player tries to place a block, but the block place is blocked",
"# Placeholder {Y} is replaced with the Y coordinate set in the config",
"# Placeholder {MODE} is replaced with the mode set in the config" })
public Notice blockPlacingBlockedDuringCombat = Notice.chat("&cYou cannot place {MODE} {Y} coordinate during combat!");
@Comment("# Message sent when player tries to enter a region")
public String cantEnterOnRegion = "&cYou can't enter on this region during combat!";
public Notice cantEnterOnRegion = Notice.chat("&cYou can't enter this region during combat!");
public static class AdminMessages extends OkaeriConfig {
@Comment("# Message sent when the configuration is reloaded")
public String reload = "&aConfiguration has been successfully reloaded!";
@Comment("# Message sent when console tries to use a command that is only for players")
public String onlyForPlayers = "&cThis command is only available to players!";
public Notice onlyForPlayers = Notice.chat("&cThis command is only available to players!");
@Comment("# Message sent to admin when they tag a player")
public String adminTagPlayer = "&7You have tagged &e{PLAYER}";
public Notice adminTagPlayer = Notice.chat("&7You have tagged &e{PLAYER}");
@Comment("# Message sent when a player is tagged by an admin")
public String adminTagMultiplePlayers = "&7You have tagged &e{FIRST_PLAYER}&7 and &e{SECOND_PLAYER}&7.";
public Notice adminTagMultiplePlayers = Notice.chat("&7You have tagged &e{FIRST_PLAYER}&7 and &e{SECOND_PLAYER}&7.");
@Comment("# Message sent to admin when they remove a player from combat")
public String adminUntagPlayer = "&7You have removed &e{PLAYER}&7 from the fight.";
public Notice adminUntagPlayer = Notice.chat("&7You have removed &e{PLAYER}&7 from the fight.");
@Comment("# Message sent when the player is not in combat")
public String adminPlayerNotInCombat = "&cThis player is not in combat!";
public Notice adminPlayerNotInCombat = Notice.chat("&cThis player is not in combat!");
@Comment("# Message sent when the player is in combat")
public String playerInCombat = "&c{PLAYER} is currently in combat!";
public Notice playerInCombat = Notice.chat("&c{PLAYER} is currently in combat!");
@Comment("# Message sent when a player is not in combat")
public String playerNotInCombat = "&a{PLAYER} is not currently in combat.";
public Notice playerNotInCombat = Notice.chat("&a{PLAYER} is not currently in combat.");
@Comment("# Message sent when an admin tries to tag themselves")
public String adminCannotTagSelf = "&cYou cannot tag yourself!";
public Notice adminCannotTagSelf = Notice.chat("&cYou cannot tag yourself!");
@Comment("# Message sent when an admin disables the ability to get tagged for some time")
public String adminTagOutSelf = "&7Successfully disabled tag for Yourself! You will be taggable after &e{TIME} ";
public Notice adminTagOutSelf = Notice.chat("&7Successfully disabled tag for Yourself! You will be taggable after &e{TIME} ");
@Comment("# Message sent when an admin disables the ability to get tagged for some time for other player")
public String adminTagOut = "&7Successfully disabled tag for &e{PLAYER}! They will be taggable after &e{TIME} ";
public Notice adminTagOut = Notice.chat("&7Successfully disabled tag for &e{PLAYER}! They will be taggable after &e{TIME} ");
@Comment("# Message sent to the player whom the ability to get tagged for some time has been disabled")
public String playerTagOut = "&7You will be taggable in &e{TIME} !";
public Notice playerTagOut = Notice.chat("&7You will be taggable in &e{TIME} !");
@Comment("# Message sent when an admin reenables the ability to get tagged for the player")
public String adminTagOutOff = "&7Successfully enabled tag for &e{PLAYER}!";
public Notice adminTagOutOff = Notice.chat("&7Successfully enabled tag for &e{PLAYER}!");
@Comment("# Message sent to the player whom the ability to get tagged has been reenabled")
public String playerTagOutOff = "&7You are now taggable!";
public Notice playerTagOutOff = Notice.chat("&7You are now taggable!");
@Comment("# Message sent when player cannot be tagged because they have enabled tag-out")
public String adminTagOutCanceled = "&cCannot tag this player due to tag-out!";
public Notice adminTagOutCanceled = Notice.chat("&cCannot tag this player due to tag-out!");
}
}
}
@@ -0,0 +1,86 @@
package com.eternalcode.combat.fight;
import com.eternalcode.combat.event.EventCaller;
import com.eternalcode.combat.fight.event.CauseOfTag;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
public class FightManagerImpl implements FightManager {
private final Map<UUID, FightTag> fights = new ConcurrentHashMap<>();
private final EventCaller eventCaller;
public FightManagerImpl(EventCaller eventCaller) {
this.eventCaller = eventCaller;
}
@Override
public boolean isInCombat(UUID player) {
if (!this.fights.containsKey(player)) {
return false;
}
FightTag fightTag = this.fights.get(player);
return !fightTag.isExpired();
}
@Override
public FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag) {
FightUntagEvent event = this.eventCaller.publishEvent(new FightUntagEvent(player, causeOfUnTag));
if (event.isCancelled()) {
return event;
}
this.fights.remove(player);
return event;
}
@Override
public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag) {
return this.tag(target, delay, causeOfTag, null);
}
@ApiStatus.Experimental
@Override
public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger) {
FightTagEvent event = this.eventCaller.publishEvent(new FightTagEvent(target, causeOfTag));
if (event.isCancelled()) {
return event;
}
Instant now = Instant.now();
Instant endOfCombatLog = now.plus(delay);
FightTag fightTag = new FightTagImpl(target, endOfCombatLog, tagger);
this.fights.put(target, fightTag);
return event;
}
@Override
public Collection<FightTag> getFights() {
return Collections.unmodifiableCollection(this.fights.values());
}
@Override
public FightTag getTag(UUID target) {
return this.fights.get(target);
}
@Override
public void untagAll() {
this.fights.clear();
}
}
@@ -0,0 +1,159 @@
package com.eternalcode.combat.fight;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.event.CancelTagReason;
import com.eternalcode.combat.fight.event.CauseOfTag;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import dev.rollczi.litecommands.annotations.argument.Arg;
import dev.rollczi.litecommands.annotations.command.Command;
import dev.rollczi.litecommands.annotations.context.Context;
import dev.rollczi.litecommands.annotations.execute.Execute;
import dev.rollczi.litecommands.annotations.permission.Permission;
import dev.rollczi.litecommands.annotations.priority.Priority;
import dev.rollczi.litecommands.annotations.priority.PriorityValue;
import java.time.Duration;
import java.util.UUID;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@Command(name = "combatlog", aliases = "combat")
public class FightTagCommand {
private final FightManager fightManager;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public FightTagCommand(FightManager fightManager, NotificationAnnouncer announcer, PluginConfig config) {
this.fightManager = fightManager;
this.announcer = announcer;
this.config = config;
}
@Execute(name = "status")
@Permission("eternalcombat.status")
void status(@Context CommandSender sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
this.announcer.create()
.notice(this.fightManager.isInCombat(targetUniqueId)
? this.config.messages.admin.playerInCombat
: this.config.messages.admin.playerNotInCombat
)
.placeholder("{PLAYER}", target.getName())
.viewer(sender)
.send();
}
@Execute(name = "tag")
@Permission("eternalcombat.tag")
@Priority(PriorityValue.HIGH)
void tag(@Context CommandSender sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
Duration time = this.config.settings.combatDuration;
FightTagEvent event = this.fightManager.tag(targetUniqueId, time, CauseOfTag.COMMAND);
if (event.isCancelled()) {
CancelTagReason cancelReason = event.getCancelReason();
this.tagoutReasonHandler(sender, cancelReason, this.config.messages);
return;
}
this.announcer.create()
.notice(this.config.messages.admin.adminTagPlayer)
.placeholder("{PLAYER}", target.getName())
.viewer(sender)
.send();
}
@Execute(name = "tag")
@Permission("eternalcombat.tag")
void tagMultiple(@Context CommandSender sender, @Arg Player firstTarget, @Arg Player secondTarget) {
Duration combatTime = this.config.settings.combatDuration;
PluginConfig.Messages messages = this.config.messages;
if (sender.equals(firstTarget) || sender.equals(secondTarget)) {
this.announcer.create()
.notice(messages.admin.adminCannotTagSelf)
.viewer(sender)
.send();
return;
}
FightTagEvent firstTagEvent = this.fightManager.tag(firstTarget.getUniqueId(), combatTime, CauseOfTag.COMMAND);
FightTagEvent secondTagEvent = this.fightManager.tag(secondTarget.getUniqueId(), combatTime, CauseOfTag.COMMAND);
if (firstTagEvent.isCancelled()) {
CancelTagReason cancelReason = firstTagEvent.getCancelReason();
this.tagoutReasonHandler(sender, cancelReason, messages);
return;
}
if (secondTagEvent.isCancelled()) {
CancelTagReason cancelReason = secondTagEvent.getCancelReason();
this.tagoutReasonHandler(sender, cancelReason, messages);
return;
}
if (firstTagEvent.isCancelled() && secondTagEvent.isCancelled()) {
return;
}
this.announcer.create()
.notice(messages.admin.adminTagMultiplePlayers)
.placeholder("{FIRST_PLAYER}", firstTarget.getName())
.placeholder("{SECOND_PLAYER}", secondTarget.getName())
.viewer(sender)
.send();
}
@Execute(name = "untag")
@Permission("eternalcombat.untag")
void untag(@Context Player sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
if (!this.fightManager.isInCombat(targetUniqueId)) {
this.announcer.create()
.viewer(sender)
.notice(this.config.messages.admin.adminPlayerNotInCombat)
.send();
return;
}
FightUntagEvent event = this.fightManager.untag(targetUniqueId, CauseOfUnTag.COMMAND);
if (event.isCancelled()) {
return;
}
this.announcer.create()
.notice(this.config.messages.admin.adminUntagPlayer)
.placeholder("{PLAYER}", target.getName())
.viewer(sender)
.send();
}
private void tagoutReasonHandler(CommandSender sender, CancelTagReason cancelReason, PluginConfig.Messages messages) {
if (cancelReason == CancelTagReason.TAGOUT) {
this.announcer.create()
.notice(messages.admin.adminTagOutCanceled)
.viewer(sender)
.send();
}
}
}
@@ -0,0 +1,53 @@
package com.eternalcode.combat.fight;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
public class FightTagImpl implements FightTag {
private final UUID taggedPlayer;
private final Instant endOfCombatLog;
private final @Nullable UUID tagger;
FightTagImpl(UUID personToAddCombat, Instant endOfCombatLog, @Nullable UUID tagger) {
this.taggedPlayer = personToAddCombat;
this.endOfCombatLog = endOfCombatLog;
this.tagger = tagger;
}
@Override
public UUID getTaggedPlayer() {
return this.taggedPlayer;
}
@Override
public Instant getEndOfCombatLog() {
return this.endOfCombatLog;
}
@Override
public boolean isExpired() {
return Instant.now().isAfter(this.endOfCombatLog);
}
@Override
public Duration getRemainingDuration() {
Duration between = Duration.between(Instant.now(), this.endOfCombatLog);
if (between.isNegative()) {
return Duration.ZERO;
}
return between;
}
@ApiStatus.Experimental
@Override
public @Nullable UUID getTagger() {
return this.tagger;
}
}
@@ -1,15 +1,11 @@
package com.eternalcode.combat.fight;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.bossbar.FightBossBarService;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.notification.implementation.BossBarNotification;
import com.eternalcode.combat.util.DurationUtil;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
import java.time.Duration;
import java.util.UUID;
@@ -19,14 +15,12 @@ public class FightTask implements Runnable {
private final Server server;
private final PluginConfig config;
private final FightManager fightManager;
private final FightBossBarService bossBarService;
private final NotificationAnnouncer announcer;
public FightTask(Server server, PluginConfig config, FightManager fightManager, FightBossBarService bossBarService, NotificationAnnouncer announcer) {
public FightTask(Server server, PluginConfig config, FightManager fightManager, NotificationAnnouncer announcer) {
this.server = server;
this.config = config;
this.fightManager = fightManager;
this.bossBarService = bossBarService;
this.announcer = announcer;
}
@@ -47,21 +41,13 @@ public class FightTask implements Runnable {
}
Duration remaining = fightTag.getRemainingDuration();
Formatter formatter = new Formatter()
.register("{TIME}", DurationUtil.format(remaining));
Notification combatNotification = this.config.messages.combatNotification;
this.announcer.create()
.player(player.getUniqueId())
.notice(this.config.messages.combatNotification)
.placeholder("{TIME}", DurationUtil.format(remaining))
.send();
this.sendFightNotification(player, fightTag, combatNotification, formatter);
}
}
private void sendFightNotification(Player player, FightTag fightTag, Notification notification, Formatter formatter) {
if (notification instanceof BossBarNotification bossBarNotification) {
this.bossBarService.send(player, fightTag, bossBarNotification, formatter);
return;
}
this.announcer.send(player, notification, formatter);
}
}
@@ -14,6 +14,7 @@ import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityToggleGlideEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerToggleFlightEvent;
import java.util.List;
import java.util.UUID;
@@ -48,22 +49,42 @@ public class FightActionBlockerController implements Listener {
List<Material> specificBlocksToPreventPlacing = this.config.settings.specificBlocksToPreventPlacing;
boolean isBlockPlaceLevel = level > this.config.settings.minBlockPlacingLevel;
if (isBlockPlaceLevel && specificBlocksToPreventPlacing.isEmpty()) {
boolean isPlacementBlocked = this.isPlacementBlocked(level);
if (isPlacementBlocked && specificBlocksToPreventPlacing.isEmpty()) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.blockPlacingBlockedDuringCombat);
this.announcer.create()
.player(uniqueId)
.notice(this.config.messages.blockPlacingBlockedDuringCombat)
.placeholder("{Y}", String.valueOf(this.config.settings.blockPlacingYCoordinate))
.placeholder("{MODE}", this.config.settings.blockPlacingModeName)
.send();
}
Material blockMaterial = block.getType();
boolean isBlockInDisabledList = specificBlocksToPreventPlacing.contains(blockMaterial);
if (isBlockPlaceLevel && isBlockInDisabledList) {
if (isPlacementBlocked && isBlockInDisabledList) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.blockPlacingBlockedDuringCombat);
this.announcer.create()
.player(uniqueId)
.notice(this.config.messages.blockPlacingBlockedDuringCombat)
.placeholder("{Y}", String.valueOf(this.config.settings.blockPlacingYCoordinate))
.placeholder("{MODE}", this.config.settings.blockPlacingModeName)
.send();
}
}
private boolean isPlacementBlocked(int level) {
return this.config.settings.blockPlacingMode == PluginConfig.Settings.BlockPlacingMode.ABOVE
? level > this.config.settings.blockPlacingYCoordinate
: level < this.config.settings.blockPlacingYCoordinate;
}
@EventHandler
public void onToggleGlide(EntityToggleGlideEvent event) {
void onToggleGlide(EntityToggleGlideEvent event) {
if (!this.config.settings.shouldPreventElytraUsage) {
return;
}
@@ -83,7 +104,27 @@ public class FightActionBlockerController implements Listener {
}
@EventHandler
public void onDamage(EntityDamageEvent event) {
void onFly(PlayerToggleFlightEvent event) {
if (!this.config.settings.shouldPreventFlying) {
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 onDamage(EntityDamageEvent event) {
if (!this.config.settings.shouldElytraDisableOnDamage) {
return;
}
@@ -113,11 +154,15 @@ public class FightActionBlockerController implements Listener {
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.inventoryBlockedDuringCombat);
this.announcer.create()
.player(uniqueId)
.notice(this.config.messages.inventoryBlockedDuringCombat)
.send();
}
@EventHandler
public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
Player player = event.getPlayer();
UUID playerUniqueId = player.getUniqueId();
@@ -136,7 +181,11 @@ public class FightActionBlockerController implements Listener {
if (shouldCancel) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.commandDisabledDuringCombat);
this.announcer.create()
.player(playerUniqueId)
.notice(this.config.messages.commandDisabledDuringCombat)
.send();
}
}
}
@@ -2,8 +2,8 @@ package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.bossbar.FightBossBarService;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.Server;
import org.bukkit.entity.Player;
@@ -15,14 +15,12 @@ public class FightMessageController implements Listener {
private final FightManager fightManager;
private final NotificationAnnouncer announcer;
private final FightBossBarService bossBarService;
private final PluginConfig config;
private final Server server;
public FightMessageController(FightManager fightManager, NotificationAnnouncer announcer, FightBossBarService bossBarService, PluginConfig config, Server server) {
public FightMessageController(FightManager fightManager, NotificationAnnouncer announcer, PluginConfig config, Server server) {
this.fightManager = fightManager;
this.announcer = announcer;
this.bossBarService = bossBarService;
this.config = config;
this.server = server;
}
@@ -39,23 +37,24 @@ public class FightMessageController implements Listener {
return;
}
this.announcer.sendMessage(player, this.config.messages.playerTagged);
this.announcer.create()
.player(player.getUniqueId())
.notice(this.config.messages.playerTagged)
.send();
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
void onUnTag(FightTagEvent event) {
void onUnTag(FightUntagEvent event) {
Player player = this.server.getPlayer(event.getPlayer());
if (player == null) {
throw new IllegalStateException("Player cannot be null!");
}
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
this.announcer.create()
.player(player.getUniqueId())
.notice(this.config.messages.playerUntagged)
.send();
this.announcer.sendMessage(player, this.config.messages.playerUntagged);
this.bossBarService.hide(event.getPlayer());
}
}
@@ -1,10 +1,10 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.fight.event.CauseOfTag;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.fight.event.CauseOfTag;
import org.bukkit.GameMode;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
@@ -23,12 +23,10 @@ public class FightTagController implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public FightTagController(FightManager fightManager, PluginConfig config, NotificationAnnouncer announcer) {
public FightTagController(FightManager fightManager, PluginConfig config) {
this.fightManager = fightManager;
this.config = config;
this.announcer = announcer;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@@ -38,7 +36,7 @@ public class FightTagController implements Listener {
}
List<EntityType> disabledProjectileEntities = this.config.settings.disabledProjectileEntities;
if (event.getDamager() instanceof Projectile projectile && disabledProjectileEntities.contains(projectile.getType())) {
return;
}
@@ -47,19 +45,38 @@ public class FightTagController implements Listener {
return;
}
Player personToAddCombatTime = this.getDamager(event);
Player attacker = this.getDamager(event);
if (personToAddCombatTime == null) {
if (attacker == null) {
return;
}
Duration combatTime = this.config.settings.combatDuration;
UUID attackedUniqueId = attackedPlayerByPerson.getUniqueId();
UUID personToAddCombatTimeUniqueId = personToAddCombatTime.getUniqueId();
UUID attackerUniqueId = attacker.getUniqueId();
this.fightManager.tag(attackedUniqueId, combatTime, CauseOfTag.PLAYER);
this.fightManager.tag(personToAddCombatTimeUniqueId, combatTime, CauseOfTag.PLAYER);
if (this.cannotBeTagged(attacker)) {
return;
}
if (this.cannotBeTagged(attackedPlayerByPerson)) {
return;
}
if (this.config.settings.shouldPreventFlying) {
if (attackedPlayerByPerson.isFlying()) {
attackedPlayerByPerson.setFlying(false);
attackedPlayerByPerson.setAllowFlight(false);
}
if (attacker.isFlying()) {
attacker.setFlying(false);
attacker.setAllowFlight(false);
}
}
this.fightManager.tag(attackedUniqueId, combatTime, CauseOfTag.PLAYER, attackerUniqueId);
this.fightManager.tag(attackerUniqueId, combatTime, CauseOfTag.PLAYER, attackedUniqueId);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@@ -76,6 +93,10 @@ public class FightTagController implements Listener {
return;
}
if (this.cannotBeTagged(player)) {
return;
}
Duration combatTime = this.config.settings.combatDuration;
UUID uuid = player.getUniqueId();
@@ -113,4 +134,15 @@ public class FightTagController implements Listener {
return this.config.settings.worldsToIgnore.contains(worldName);
}
private boolean cannotBeTagged(Player player) {
if (player.getGameMode().equals(GameMode.CREATIVE) && this.config.settings.excludeCreativeFromCombat) {
return true;
}
if (player.isOp() && this.config.settings.excludeOpFromCombat) {
return true;
}
return false;
}
}
@@ -1,8 +1,9 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.logout.LogoutService;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@@ -12,10 +13,12 @@ public class FightUnTagController implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
private final LogoutService logoutService;
public FightUnTagController(FightManager fightManager, PluginConfig config) {
public FightUnTagController(FightManager fightManager, PluginConfig config, LogoutService logoutService) {
this.fightManager = fightManager;
this.config = config;
this.logoutService = logoutService;
}
@EventHandler
@@ -27,12 +30,29 @@ public class FightUnTagController implements Listener {
return;
}
CauseOfUnTag causeOfUnTag = (killer == null ? CauseOfUnTag.DEATH_BY_PLAYER : CauseOfUnTag.DEATH);
CauseOfUnTag cause = this.getDeathCause(player, killer);
this.fightManager.untag(player.getUniqueId(), causeOfUnTag);
this.fightManager.untag(player.getUniqueId(), cause);
if (killer != null && this.config.settings.shouldReleaseAttacker) {
this.fightManager.untag(killer.getUniqueId(), CauseOfUnTag.ATTACKER_RELEASE);
}
}
private CauseOfUnTag getDeathCause(Player player, Player killer) {
if (this.logoutService.hasLoggedOut(player.getUniqueId())) {
return CauseOfUnTag.LOGOUT;
}
if (killer == null) {
return CauseOfUnTag.DEATH;
}
if (this.fightManager.isInCombat(killer.getUniqueId())) {
return CauseOfUnTag.DEATH_BY_PLAYER;
}
return CauseOfUnTag.DEATH;
}
}
@@ -1,9 +1,9 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTag;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
@@ -15,19 +15,19 @@ import java.util.UUID;
public class DropController implements Listener {
private final DropManager dropManager;
private final DropKeepInventoryManager keepInventoryManager;
private final DropService dropService;
private final DropKeepInventoryService keepInventoryManager;
private final DropSettings dropSettings;
private final FightManager fightManager;
public DropController(DropManager dropManager, DropKeepInventoryManager keepInventoryManager, DropSettings dropSettings, FightManager fightManager) {
this.dropManager = dropManager;
public DropController(DropService dropService, DropKeepInventoryService keepInventoryManager, DropSettings dropSettings, FightManager fightManager) {
this.dropService = dropService;
this.keepInventoryManager = keepInventoryManager;
this.dropSettings = dropSettings;
this.fightManager = fightManager;
}
@EventHandler
@EventHandler(priority = EventPriority.LOW)
void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
@@ -36,21 +36,20 @@ public class DropController implements Listener {
if (dropType == DropType.UNCHANGED || !this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
FightTag fightTag = this.fightManager.getTag(player.getUniqueId());
List<ItemStack> drops = event.getDrops();
Drop drop = Drop.builder()
.player(player)
.killer(player.getKiller())
.fightTag(fightTag)
.deathCause(fightTag.getDeathCause())
.droppedItems(drops)
.droppedExp(player.getTotalExperience())
.build();
DropResult result = this.dropManager.modify(dropType, drop);
DropResult result = this.dropService.modify(dropType, drop);
if (result == null) {
return;
}
drops.clear();
drops.addAll(result.droppedItems());
@@ -1,4 +1,4 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
import org.bukkit.inventory.ItemStack;
@@ -9,22 +9,26 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
public class DropKeepInventoryManager {
public class DropKeepInventoryServiceImpl implements DropKeepInventoryService {
private final Map<UUID, List<ItemStack>> itemsToGiveAfterRespawn = new HashMap<>();
@Override
public void addItem(UUID uuid, ItemStack item) {
this.itemsToGiveAfterRespawn.computeIfAbsent(uuid, k -> new ArrayList<>()).add(item);
}
@Override
public void addItems(UUID uuid, List<ItemStack> item) {
item.forEach(i -> this.addItem(uuid, i));
}
@Override
public boolean hasItems(UUID uuid) {
return this.itemsToGiveAfterRespawn.containsKey(uuid);
}
@Override
public List<ItemStack> nextItems(UUID uuid) {
List<ItemStack> itemStacks = this.itemsToGiveAfterRespawn.remove(uuid);
@@ -1,16 +1,17 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
import java.util.HashMap;
import java.util.Map;
public class DropManager {
public class DropServiceImpl implements DropService {
private final Map<DropType, DropModifier> modifiers;
public DropManager() {
public DropServiceImpl() {
this.modifiers = new HashMap<>();
}
@Override
public void registerModifier(DropModifier dropModifier) {
DropType dropType = dropModifier.getDropType();
@@ -25,6 +26,7 @@ public class DropManager {
this.modifiers.put(dropType, dropModifier);
}
@Override
public DropResult modify(DropType dropType, Drop drop) {
if (!this.modifiers.containsKey(dropType)) {
throw new RuntimeException("No drop modifier found for type '%s'".formatted(dropType.name()));
@@ -1,5 +1,6 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
import com.eternalcode.combat.fight.drop.DropType;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
@@ -1,10 +1,10 @@
package com.eternalcode.combat.drop.impl;
package com.eternalcode.combat.fight.drop.impl;
import com.eternalcode.combat.drop.Drop;
import com.eternalcode.combat.drop.DropModifier;
import com.eternalcode.combat.drop.DropResult;
import com.eternalcode.combat.drop.DropSettings;
import com.eternalcode.combat.drop.DropType;
import com.eternalcode.combat.fight.drop.Drop;
import com.eternalcode.combat.fight.drop.DropModifier;
import com.eternalcode.combat.fight.drop.DropResult;
import com.eternalcode.combat.fight.drop.DropSettings;
import com.eternalcode.combat.fight.drop.DropType;
import com.eternalcode.combat.util.InventoryUtil;
import com.eternalcode.combat.util.MathUtil;
import com.eternalcode.combat.util.RemoveItemResult;
@@ -1,12 +1,12 @@
package com.eternalcode.combat.drop.impl;
package com.eternalcode.combat.fight.drop.impl;
import com.eternalcode.combat.drop.Drop;
import com.eternalcode.combat.drop.DropModifier;
import com.eternalcode.combat.drop.DropResult;
import com.eternalcode.combat.drop.DropSettings;
import com.eternalcode.combat.drop.DropType;
import com.eternalcode.combat.fight.FightDeathCause;
import com.eternalcode.combat.fight.FightTag;
import com.eternalcode.combat.fight.drop.Drop;
import com.eternalcode.combat.fight.drop.DropModifier;
import com.eternalcode.combat.fight.drop.DropResult;
import com.eternalcode.combat.fight.drop.DropSettings;
import com.eternalcode.combat.fight.drop.DropType;
import com.eternalcode.combat.fight.logout.Logout;
import com.eternalcode.combat.fight.logout.LogoutService;
import com.eternalcode.combat.util.InventoryUtil;
import com.eternalcode.combat.util.MathUtil;
import com.eternalcode.combat.util.RemoveItemResult;
@@ -15,13 +15,16 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.List;
import java.util.Optional;
public class PlayersHealthDropModifier implements DropModifier {
private final DropSettings settings;
private final LogoutService logoutService;
public PlayersHealthDropModifier(DropSettings settings) {
public PlayersHealthDropModifier(DropSettings settings, LogoutService logoutService) {
this.settings = settings;
this.logoutService = logoutService;
}
@Override
@@ -31,17 +34,20 @@ public class PlayersHealthDropModifier implements DropModifier {
@Override
public DropResult modifyDrop(Drop drop) {
if (drop.getDeathCause() != FightDeathCause.ESCAPE) {
Optional<Logout> logoutOptional = this.logoutService.nextLogoutFor(drop.getPlayer().getUniqueId());
if (logoutOptional.isEmpty()) {
return null;
}
Logout logout = logoutOptional.get();
Player player = drop.getPlayer();
FightTag fightTag = drop.getFightTag();
List<ItemStack> droppedItems = drop.getDroppedItems();
double maxHealth = player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue();
double health = fightTag.getHealthBeforeDeath();
double health = logout.health();
int percentHealth = MathUtil.getRoundedCountPercentage(health, maxHealth);
int reversedPercent = MathUtil.clamp(100 - percentHealth, this.settings.playersHealthPercentClamp, 100);
@@ -0,0 +1,81 @@
package com.eternalcode.combat.fight.effect;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.List;
import java.util.ArrayList;
public class FightEffectServiceImpl implements FightEffectService {
private final Map<UUID, List<PotionEffect>> activeEffects = new HashMap<>();
private static final int INFINITE_DURATION = -1;
@Override
public void storeActiveEffect(Player player, PotionEffect effect) {
List<PotionEffect> effects = this.activeEffects.computeIfAbsent(player.getUniqueId(), k -> new ArrayList<>());
effects.add(effect);
}
@Override
public void restoreActiveEffects(Player player) {
List<PotionEffect> currentEffects = this.getCurrentEffects(player);
for (PotionEffect effect : currentEffects) {
player.addPotionEffect(effect);
}
this.clearStoredEffects(player);
}
@Override
public void clearStoredEffects(Player player) {
this.activeEffects.remove(player.getUniqueId());
}
@Override
public List<PotionEffect> getCurrentEffects(Player player) {
return this.activeEffects.getOrDefault(player.getUniqueId(), new ArrayList<>());
}
@Override
public void applyCustomEffect(Player player, PotionEffectType type, Integer amplifier) {
PotionEffect activeEffect = player.getPotionEffect(type);
if (activeEffect == null) {
player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier));
return;
}
if (activeEffect.getAmplifier() > amplifier) {
return;
}
if (activeEffect.getDuration() == -1) {
return;
}
this.storeActiveEffect(player, activeEffect);
player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier));
}
@Override
public void removeCustomEffect(Player player, PotionEffectType type, Integer amplifier) {
PotionEffect activeEffect = player.getPotionEffect(type);
if (activeEffect == null) {
return;
}
if (activeEffect.getAmplifier() != amplifier) {
return;
}
player.removePotionEffect(type);
}
}
@@ -10,7 +10,7 @@ import java.util.Map;
public class FightEffectSettings extends OkaeriConfig {
@Comment({"# Do you want to add effects to players in combat?"})
public boolean customEffectsEnabled = true;
public boolean customEffectsEnabled = false;
@Comment({
"# If the option above is set to true, you can add effects to players in combat below",
@@ -0,0 +1,6 @@
package com.eternalcode.combat.fight.logout;
import java.util.UUID;
public record Logout(UUID player, double health) {
}
@@ -0,0 +1,46 @@
package com.eternalcode.combat.fight.logout;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
public class LogoutController implements Listener {
private final FightManager fightManager;
private final LogoutService logoutService;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public LogoutController(FightManager fightManager, LogoutService logoutService, NotificationAnnouncer announcer, PluginConfig config) {
this.fightManager = fightManager;
this.logoutService = logoutService;
this.announcer = announcer;
this.config = config;
}
@EventHandler(priority = EventPriority.HIGH)
void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
this.logoutService.punishForLogout(player);
player.setHealth(0.0);
this.announcer.create()
.notice(this.config.messages.playerLoggedOutDuringCombat)
.placeholder("{PLAYER}", player.getName())
.all()
.send();
}
}
@@ -0,0 +1,37 @@
package com.eternalcode.combat.fight.logout;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.bukkit.entity.Player;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class LogoutService {
private final Cache<UUID, Logout> logouts = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
public void punishForLogout(Player player) {
UUID uniqueId = player.getUniqueId();
this.logouts.put(uniqueId, new Logout(uniqueId, player.getHealth()));
}
public Optional<Logout> nextLogoutFor(UUID player) {
Logout logout = this.logouts.getIfPresent(player);
if (logout == null) {
return Optional.empty();
}
this.logouts.invalidate(player);
return Optional.of(logout);
}
public boolean hasLoggedOut(UUID player) {
return this.logouts.asMap().containsKey(player);
}
}
@@ -14,7 +14,6 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import panda.utilities.text.Formatter;
import java.time.Duration;
import java.util.UUID;
@@ -24,13 +23,13 @@ public class FightPearlController implements Listener {
private final FightPearlSettings settings;
private final NotificationAnnouncer announcer;
private final FightManager fightManager;
private final FightPearlManager fightPearlManager;
private final FightPearlService fightPearlService;
public FightPearlController(FightPearlSettings settings, NotificationAnnouncer announcer, FightManager fightManager, FightPearlManager fightPearlManager) {
public FightPearlController(FightPearlSettings settings, NotificationAnnouncer announcer, FightManager fightManager, FightPearlService fightPearlService) {
this.settings = settings;
this.announcer = announcer;
this.fightManager = fightManager;
this.fightPearlManager = fightPearlManager;
this.fightPearlService = fightPearlService;
}
@EventHandler
@@ -58,24 +57,29 @@ public class FightPearlController implements Listener {
if (this.settings.pearlThrowDelay.isZero()) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.settings.pearlThrowBlockedDuringCombat);
this.announcer.create()
.player(uniqueId)
.notice(this.settings.pearlThrowBlockedDuringCombat)
.send();
return;
}
if (this.fightPearlManager.hasDelay(uniqueId)) {
if (this.fightPearlService.hasDelay(uniqueId)) {
event.setCancelled(true);
Duration remainingPearlDelay = this.fightPearlManager.getRemainingDelay(uniqueId);
Duration remainingPearlDelay = this.fightPearlService.getRemainingDelay(uniqueId);
Formatter formatter = new Formatter()
.register("{TIME}", DurationUtil.format(remainingPearlDelay));
this.announcer.create()
.player(uniqueId)
.notice(this.settings.pearlThrowBlockedDelayDuringCombat)
.placeholder("{TIME}", DurationUtil.format(remainingPearlDelay))
.send();
String format = formatter.format(this.settings.pearlThrowBlockedDelayDuringCombat);
this.announcer.sendMessage(player, format);
return;
}
this.fightPearlManager.markDelay(uniqueId);
this.fightPearlService.markDelay(uniqueId);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@@ -1,36 +1,40 @@
package com.eternalcode.combat.fight.pearl;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
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 FightPearlManager {
public class FightPearlServiceImpl implements FightPearlService {
private final FightPearlSettings pearlSettings;
private final Cache<UUID, Instant> pearlDelays;
public FightPearlManager(FightPearlSettings pearlSettings) {
public FightPearlServiceImpl(FightPearlSettings pearlSettings) {
this.pearlSettings = pearlSettings;
this.pearlDelays = CacheBuilder.newBuilder()
this.pearlDelays = Caffeine.newBuilder()
.expireAfterWrite(pearlSettings.pearlThrowDelay)
.build();
}
@Override
public void markDelay(UUID uuid) {
this.pearlDelays.put(uuid, Instant.now().plus(this.pearlSettings.pearlThrowDelay));
}
@Override
public boolean hasDelay(UUID uuid) {
return Instant.now().isBefore(this.getDelay(uuid));
}
@Override
public Duration getRemainingDelay(UUID uuid) {
return Duration.between(Instant.now(), this.getDelay(uuid));
}
@Override
public Instant getDelay(UUID uuid) {
return this.pearlDelays.asMap().getOrDefault(uuid, Instant.MIN);
}
@@ -1,5 +1,7 @@
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;
@@ -21,9 +23,13 @@ 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 String pearlThrowBlockedDuringCombat = "&cThrowing ender pearls is prohibited during combat!";
public Notice pearlThrowBlockedDuringCombat = BukkitNotice.builder()
.chat("&cThrowing ender pearls is prohibited during combat!")
.build();
@Comment("# Message sent when player tries to throw ender pearl, but has delay")
public String pearlThrowBlockedDelayDuringCombat = "&cYou must wait {TIME} before next throw!";
public Notice pearlThrowBlockedDelayDuringCombat = BukkitNotice.builder()
.chat("&cYou must wait {TIME} before next throw!")
.build();
}
@@ -0,0 +1,102 @@
package com.eternalcode.combat.fight.tagout;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.util.DurationUtil;
import dev.rollczi.litecommands.annotations.argument.Arg;
import dev.rollczi.litecommands.annotations.command.Command;
import dev.rollczi.litecommands.annotations.context.Context;
import dev.rollczi.litecommands.annotations.execute.Execute;
import dev.rollczi.litecommands.annotations.permission.Permission;
import java.time.Duration;
import java.util.UUID;
import org.bukkit.entity.Player;
@Permission("eternalcombat.tagout")
@Command(name = "tagout", aliases = "tagimmunity")
public class FightTagOutCommand {
private final FightTagOutService fightTagOutService;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public FightTagOutCommand(
FightTagOutService fightTagOutService,
NotificationAnnouncer announcer,
PluginConfig config
) {
this.fightTagOutService = fightTagOutService;
this.announcer = announcer;
this.config = config;
}
@Execute
void tagout(@Context Player sender, @Arg Duration time) {
UUID targetUniqueId = sender.getUniqueId();
this.fightTagOutService.tagOut(targetUniqueId, time);
this.announcer.create()
.notice(this.config.messages.admin.adminTagOutSelf)
.placeholder("{TIME}", DurationUtil.format(time))
.viewer(sender)
.send();
}
@Execute
void tagout(@Context Player sender, @Arg Player target, @Arg Duration time) {
UUID targetUniqueId = target.getUniqueId();
this.fightTagOutService.tagOut(targetUniqueId, time);
this.announcer.create()
.notice(this.config.messages.admin.adminTagOut)
.placeholder("{PLAYER}", target.getName())
.placeholder("{TIME}", DurationUtil.format(time))
.viewer(sender)
.send();
this.announcer.create()
.notice(this.config.messages.admin.playerTagOut)
.placeholder("{TIME}", DurationUtil.format(time))
.player(target.getUniqueId())
.send();
}
@Execute(name = "remove")
void untagout(@Context Player sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
this.fightTagOutService.unTagOut(targetUniqueId);
if (!targetUniqueId.equals(sender.getUniqueId())) {
this.announcer.create()
.notice(this.config.messages.admin.adminTagOutOff)
.placeholder("{PLAYER}", target.getName())
.viewer(sender)
.send();
}
this.announcer.create()
.notice(this.config.messages.admin.playerTagOutOff)
.player(targetUniqueId)
.send();
}
@Execute(name = "remove")
void untagout(@Context Player sender) {
UUID senderUniqueId = sender.getUniqueId();
this.fightTagOutService.unTagOut(senderUniqueId);
this.announcer.create()
.notice(this.config.messages.admin.playerTagOutOff)
.viewer(sender)
.send();
}
}
@@ -1,6 +1,6 @@
package com.eternalcode.combat.fight.tagout;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.event.CancelTagReason;
import com.eternalcode.combat.fight.event.FightTagEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@@ -10,11 +10,9 @@ import java.util.UUID;
public class FightTagOutController implements Listener {
private final FightTagOutService tagOutService;
private final PluginConfig config;
public FightTagOutController(FightTagOutService tagOutService, PluginConfig config) {
public FightTagOutController(FightTagOutService tagOutService) {
this.tagOutService = tagOutService;
this.config = config;
}
@EventHandler
@@ -22,7 +20,8 @@ public class FightTagOutController implements Listener {
UUID uniqueId = event.getPlayer();
if (this.tagOutService.isTaggedOut(uniqueId)) {
event.cancel(this.config.messages.admin.adminTagOutCanceled);
event.setCancelReason(CancelTagReason.TAGOUT);
event.setCancelled(true);
}
}
@@ -0,0 +1,37 @@
package com.eternalcode.combat.fight.tagout;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class FightTagOutServiceImpl implements FightTagOutService {
private final Map<UUID, Instant> tagOuts = new HashMap<>();
@Override
public void tagOut(UUID player, Duration duration) {
Instant endTime = Instant.now().plus(duration);
this.tagOuts.put(player, endTime);
}
@Override
public void unTagOut(UUID player) {
this.tagOuts.remove(player);
}
@Override
public boolean isTaggedOut(UUID player) {
Instant endTime = this.tagOuts.get(player);
if (endTime == null) {
return false;
}
Instant now = Instant.now();
return now.isBefore(endTime);
}
}
@@ -0,0 +1,39 @@
package com.eternalcode.combat.handler;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import dev.rollczi.litecommands.handler.result.ResultHandlerChain;
import dev.rollczi.litecommands.invalidusage.InvalidUsage;
import dev.rollczi.litecommands.invalidusage.InvalidUsageHandler;
import dev.rollczi.litecommands.invocation.Invocation;
import dev.rollczi.litecommands.schematic.Schematic;
import org.bukkit.command.CommandSender;
public class InvalidUsageHandlerImpl implements InvalidUsageHandler<CommandSender> {
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public InvalidUsageHandlerImpl(PluginConfig config, NotificationAnnouncer announcer) {
this.config = config;
this.announcer = announcer;
}
@Override
public void handle(
Invocation<CommandSender> invocation,
InvalidUsage<CommandSender> commandSenderInvalidUsage,
ResultHandlerChain<CommandSender> resultHandlerChain
) {
Schematic schematic = commandSenderInvalidUsage.getSchematic();
for (String usage : schematic.all()) {
this.announcer.create()
.viewer(invocation.sender())
.notice(this.config.messages.invalidCommandUsage)
.placeholder("{USAGE}", usage)
.send();
}
}
}
@@ -0,0 +1,38 @@
package com.eternalcode.combat.handler;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import dev.rollczi.litecommands.handler.result.ResultHandlerChain;
import dev.rollczi.litecommands.invocation.Invocation;
import dev.rollczi.litecommands.permission.MissingPermissions;
import dev.rollczi.litecommands.permission.MissingPermissionsHandler;
import org.bukkit.command.CommandSender;
public class MissingPermissionHandlerImpl implements MissingPermissionsHandler<CommandSender> {
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public MissingPermissionHandlerImpl(PluginConfig config, NotificationAnnouncer announcer) {
this.config = config;
this.announcer = announcer;
}
@Override
public void handle(
Invocation<CommandSender> invocation,
MissingPermissions missingPermissions,
ResultHandlerChain<CommandSender> resultHandlerChain
) {
String joinedText = missingPermissions.asJoinedText();
this.announcer.create()
.viewer(invocation.sender())
.notice(this.config.messages.noPermission)
.placeholder("{PERMISSION}", joinedText)
.send();
}
}
@@ -0,0 +1,48 @@
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.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 NotificationAnnouncer extends BukkitMultification<PluginConfig> {
private final AudienceProvider audienceProvider;
private final PluginConfig pluginConfig;
private final MiniMessage miniMessage;
public NotificationAnnouncer(AudienceProvider audienceProvider, PluginConfig pluginConfig, MiniMessage miniMessage) {
this.audienceProvider = audienceProvider;
this.pluginConfig = pluginConfig;
this.miniMessage = miniMessage;
}
@Override
protected @NotNull TranslationProvider<PluginConfig> translationProvider() {
return locale -> this.pluginConfig;
}
@Override
protected @NotNull ComponentSerializer<Component, Component, String> serializer() {
return this.miniMessage;
}
@Override
protected @NotNull AudienceConverter<CommandSender> audienceConverter() {
return commandSender -> {
if (commandSender instanceof Player player) {
return this.audienceProvider.player(player.getUniqueId());
}
return this.audienceProvider.console();
};
}
}
@@ -0,0 +1,36 @@
package com.eternalcode.combat.region;
import java.util.Optional;
import org.bukkit.Location;
import org.bukkit.util.BlockVector;
public class DefaultRegionProvider implements RegionProvider {
private final int radius;
public DefaultRegionProvider(int radius) {
this.radius = radius;
}
@Override
public Optional<Region> getRegion(Location location) {
Location spawnLocation = location.getWorld().getSpawnLocation();
double x = spawnLocation.getX();
double z = spawnLocation.getZ();
BlockVector min = new BlockVector(x - this.radius, 0, z - this.radius);
BlockVector max = new BlockVector(x + this.radius, 0, z + this.radius);
if (this.contains(min, max, location.getX(), location.getZ())) {
return Optional.of(() -> new Location(location.getWorld(), x, location.getY(), z));
}
return Optional.empty();
}
private boolean contains(BlockVector min, BlockVector max, double x, double z) {
return x >= min.getX() && x < max.getX()
&& z >= min.getZ() && z < max.getZ();
}
}
@@ -3,11 +3,13 @@ package com.eternalcode.combat.region;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import java.util.Optional;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.util.Vector;
public class RegionController implements Listener {
@@ -43,18 +45,47 @@ public class RegionController implements Listener {
int zFrom = locationFrom.getBlockZ();
if (xTo != xFrom || yTo != yFrom || zTo != zFrom) {
if (!this.regionProvider.isInRegion(locationTo)) {
Optional<Region> regionOptional = this.regionProvider.getRegion(locationTo);
if (regionOptional.isEmpty()) {
return;
}
Location playerLocation = player.getLocation().subtract(player.getWorld().getSpawnLocation());
double distance = playerLocation.distance(player.getWorld().getSpawnLocation());
Vector knockback = new Vector(0, 3, 0).multiply(this.pluginConfig.settings.blockedRegionKnockMultiplier / distance);
Vector vector = playerLocation.toVector().add(knockback);
Region region = regionOptional.get();
Location centerOfRegion = region.getCenter();
Location subtract = player.getLocation().subtract(centerOfRegion);
player.setVelocity(vector);
Vector knockbackVector = new Vector(subtract.getX(), 0, subtract.getZ()).normalize();
Vector configuredVector = new Vector(
this.pluginConfig.settings.blockedRegionKnockMultiplier,
0.5,
this.pluginConfig.settings.blockedRegionKnockMultiplier);
this.announcer.sendMessage(player, this.pluginConfig.messages.cantEnterOnRegion);
player.setVelocity(knockbackVector.multiply(configuredVector));
this.announcer.create()
.player(player.getUniqueId())
.notice(this.pluginConfig.messages.cantEnterOnRegion)
.send();
}
}
@EventHandler
public void onPlayerTeleport(PlayerTeleportEvent event) {
Player player = event.getPlayer();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
Location targetLocation = event.getTo();
if (this.regionProvider.isInRegion(targetLocation)) {
event.setCancelled(true);
this.announcer.create()
.player(player.getUniqueId())
.notice(this.pluginConfig.messages.cantEnterOnRegion)
.send();
}
}
}
@@ -0,0 +1,81 @@
package com.eternalcode.combat.region;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldguard.WorldGuard;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.flags.Flags;
import com.sk89q.worldguard.protection.flags.StateFlag;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import com.sk89q.worldguard.protection.regions.RegionContainer;
import com.sk89q.worldguard.protection.regions.RegionQuery;
import java.util.ArrayList;
import java.util.Optional;
import java.util.TreeSet;
import org.bukkit.Location;
import java.util.List;
public class WorldGuardRegionProvider implements RegionProvider {
private final RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer();
private final TreeSet<String> regions = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
private final PluginConfig pluginConfig;
public WorldGuardRegionProvider(List<String> regions, PluginConfig pluginConfig) {
this.regions.addAll(regions);
this.pluginConfig = pluginConfig;
}
@Override
public Optional<Region> getRegion(Location location) {
RegionQuery regionQuery = this.regionContainer.createQuery();
ApplicableRegionSet applicableRegions = regionQuery.getApplicableRegions(BukkitAdapter.adapt(location));
List<ProtectedRegion> sortedRegions = new ArrayList<>(applicableRegions.getRegions());
sortedRegions.sort((r1, r2) -> Integer.compare(r2.getPriority(), r1.getPriority()));
for (ProtectedRegion region : sortedRegions) {
if (!this.isCombatRegion(region)) {
continue;
}
return Optional.of(new RegionImpl(location, region));
}
return Optional.empty();
}
private boolean isCombatRegion(ProtectedRegion region) {
if (this.regions.contains(region.getId())) {
return true;
}
if (this.pluginConfig.settings.shouldPreventPvpRegions) {
StateFlag.State flag = region.getFlag(Flags.PVP);
if (flag != null) {
return flag.equals(StateFlag.State.DENY);
}
}
return false;
}
private record RegionImpl(Location contextLocation, ProtectedRegion region) implements Region {
@Override
public Location getCenter() {
BlockVector3 min = this.region.getMinimumPoint();
BlockVector3 max = this.region.getMaximumPoint();
double x = (double) (min.getX() + max.getX()) / 2;
double z = (double) (min.getZ() + max.getZ()) / 2;
return new Location(this.contextLocation.getWorld(), x, this.contextLocation.getY(), z);
}
}
}
@@ -14,6 +14,7 @@ import java.util.concurrent.CompletableFuture;
public class UpdaterNotificationController implements Listener {
private static final String NEW_VERSION_AVAILABLE = "<b><gradient:#8a1212:#fc6b03>EternalCombat:</gradient></b> <color:#fce303>New version of EternalCombat is available, please update!";
private static final String RECEIVE_UPDATES_PERMISSION = "eternalcombat.receiveupdates";
private final UpdaterService updaterService;
private final PluginConfig pluginConfig;
@@ -32,7 +33,7 @@ public class UpdaterNotificationController implements Listener {
Player player = event.getPlayer();
Audience audience = this.audienceProvider.player(player.getUniqueId());
if (!player.hasPermission("eternalcombat.receiveupdates") || !this.pluginConfig.settings.shouldReceivePluginUpdates) {
if (!player.hasPermission(RECEIVE_UPDATES_PERMISSION) || !this.pluginConfig.settings.shouldReceivePluginUpdates) {
return;
}
Loaded 100 of 110 files, more files were not shown because too many files have changed in this diff. Show more