Compare commits

...
Author SHA1 Message Date
Martin Sulikowski f276a83f50 Experiments 2023-11-13 22:32:37 +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
Norbert Dejlich 16c40319f4 [ci skip] Fix a publish workflow. 2023-10-14 21:44:55 +02:00
Michał WojtasandDMK a88c9e74fe GH-120 Add suport for event API. Add tagout - command to prevent combat log for a set duration (#120)
* Add causes for untag and tag of the player

* Add Cancellable implementation to Events.

* Extend uses of FightTagEvent in FightEffectController, add FightExtendTagEvent

* Small fixup (add this. to isCancelled variable)

* Add sketch for TagOut Commands, add listener for custom events, setup base for event api

* Code fix up. Add EventHandlers in EffectController

* Remove unnecessary file

* Add support for untagout command

* Follow DMK review suggestions

* Extract tag-out commands our of CombatCommand. Add one more user case scenario of removing tag-out

* codestyle fix

* Fix warn

* Follow DMK suggestions

* revert `assert` changes

* add space

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

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

* Follow another set of issues from DMK

* remove unused imports

* fix compilation error

* remove formatter where not needed

* Update src/main/java/com/eternalcode/combat/fight/tagout/TagOutCommand.java

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

* Add FightMessageController

* Small fixes

* Rename enum in CauseOfUnTag.java, fix block placement ban message

* Setup Api, setup Maven Repository - ready to publish

* Add module pointer before execute publish task.

* rename command from tag-out to tagout

* command aliases fix

* change "publish on push" to "publish on release"

* fix release types

---------

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>
2023-10-14 21:42:54 +02:00
cceeb82fef GH-116 Add Effects applied during Combat (#116)
* Add inCombatEffects based on custom Events

* Improve give back of potion effects.

* Fix merge conflicts and add okaeri bukkit serdes.

* Simplification of loops, new methods and optimization

* Change HashMap based on Player to UUID for better security

* Fix issues

* Update src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java

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

* Update src/main/java/com/eternalcode/combat/fight/effect/EffectService.java

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

* Update src/main/java/com/eternalcode/combat/fight/effect/EffectService.java

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

* Update src/main/java/com/eternalcode/combat/fight/effect/EffectService.java

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

* Update src/main/java/com/eternalcode/combat/fight/event/FightDeathEvent.java

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

* Update src/main/java/com/eternalcode/combat/fight/event/FightTagEvent.java

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

* Update src/main/java/com/eternalcode/combat/fight/event/FightDeathEvent.java

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

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

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

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

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

* Update src/main/java/com/eternalcode/combat/fight/effect/EffectService.java

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

* Codestyle fixes

* Codestyle fixes

* Remove unnesesary FightDeathEvent.java, refactor class names to match project style, fixes in codestyle, move to UUID event pass down instead of bukkit player.

* Add brackets

* Follow Rollczi's review

* Follow DMK's review.

* simple rename

---------

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>
Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>
2023-10-02 11:42:04 +02:00
DMKandRollczi 92535dd7a3 GH-86 Add a bossbar implementation (#113)
* Initial commit.

* Fix checkstyle.

* Follow rollczi review suggestions.

* Encapsulate FightBossBarService and FightBossBarRegistry.

* Added hiding boss bar after untagging player by command.

* Added the ability to customize the progress bar on the boss bar.

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2023-09-28 00:28:31 +02:00
DMK 3390534d98 GH-94 Fix item drop 2023-09-16 13:37:09 +02:00
DMK d45ce7406f GH-114 Fix eternal combat status message 2023-09-16 13:35:29 +02:00
Martin SulikowskiandDominik 5861f70010 GH-109 Migrate configuration to use the okaeri configuration (#111)
* Initial commit.

* Follow rollczi suggestions.

* Fixed a bug with file configuration creation.

* Jar file size reduced.

* Added relocating javassist and org.apache.commons.

---------

Co-authored-by: Dominik <dominiks8318@gmail.com>
2023-09-15 18:28:07 +02:00
Martin Sulikowski 7940a2663b Release v1.1.1 2023-08-25 00:20:21 +02:00
Martin Sulikowski 9e9e2956b9 GH-105 Add option to release attacker when victim dies (Fix after mistkate) (#105)
* Add option to release attacker when victim dies (Fix after mistkate)

* Follow @failutee suggestion
2023-08-25 00:07:16 +02:00
Michał Wojtas 7bfb4c22a4 GH-100 Add blacklist option to damage causes (#100)
* Add blacklist option to damage causes

* Small fix

* Resolve description to better match features

* Create WhitelistBlacklistMode enum, make consistent Whitelist and Blacklist uses throughout plugin.
2023-08-22 22:53:34 +02:00
failuteeandJakubk15 1d7a652f23 GH-93 Add PlayersHealthDropModifier (#97)
* Move to feature to separate class

* Register FightEscapeController

* PlayersHealthDropModifier impl & Some changes

* Add FightDeathCasue

* Fix

* Update src/main/java/com/eternalcode/combat/fight/FightTag.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/FightTag.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/FightTag.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/FightDeathCasue.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/FightTag.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/FightTag.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/controller/FightDeathCasueController.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/controller/FightDeathCasueController.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/drop/Drop.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/controller/FightDeathCasueController.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/drop/Drop.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/controller/FightDeathCasueController.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/controller/FightDeathCasueController.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Resolve errors

---------

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>
2023-08-22 15:31:21 +02:00
renovate[bot] d9dfa5396a GH-102 Update gradle/gradle-build-action digest to e3426b1 (#102)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-22 13:47:45 +02:00
renovate[bot] 630b8bc23f GH-90 Update all dependencies (#90)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-19 18:16:53 +02:00
failutee 302fc3ed64 GH-92 Release attacker when victim dies. (#96)
* Init

* Add untag message

* Untag player in PlayerDeathEvent instead of EntityDamageByEntityEvent

* Add setting to config

* Follow Rollczi's suggestions

* Remove unused imports
2023-08-19 18:16:00 +02:00
DMKandNorbert Dejlich fb51b062f6 GH-98 Pearl throw control (#99)
* Initial commit.

* Follow rollczi suggestions.

* Change entityTypesToLog to disabledProjectileEntities.

Co-authored-by: Norbert Dejlich <49173834+Rollczi@users.noreply.github.com>

* Change entityTypesToLog to disabledProjectileEntities.

Co-authored-by: Norbert Dejlich <49173834+Rollczi@users.noreply.github.com>

* Follow rollczi suggestions.

---------

Co-authored-by: Norbert Dejlich <49173834+Rollczi@users.noreply.github.com>
2023-08-19 18:10:26 +02:00
NotAlexNoyleandJakubk15 bfd7557e6d GH-91 Add build instructions
* Add build instructions

* Update README.md

Follow suggestion

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

---------

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>
2023-07-23 14:14:55 +02:00
failutee ba2e55fa84 GH-89 Add DropModifier implementation. (#89)
* Init

* Fix compile error

* Follow suggestions

* Follow suggestions
2023-07-10 18:32:58 +02:00
Martin Sulikowski d1659ac5dd Release v1.1.0 2023-07-01 19:55:30 +02:00
renovate[bot] c58b69bb33 GH-82 Update all dependencies (#82)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-01 12:25:27 +02:00
DMKandRollczi 3ffb9e878a GH-77 Add pearl delay in combat (#77)
* Initial commit.

* Follow rollczi suggestions.

* Fix vulnerability.

* Fix typo in CombatPlugin

* Separate settings of pearl feature to FightPearlSettings.

* Add a description to the enabled variable.

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2023-07-01 12:24:14 +02:00
Martin Sulikowski 4d6cf9b580 Remove the unnecessary dash. 2023-07-01 12:21:27 +02:00
Martin Sulikowski 1e4611b638 GH-87 Update Java CI, keep it simple (#87)
* Update Java CI, keep it simple

* Do not start checkstyle as it is started by DependOn

* Resolve issues by reviewers
2023-07-01 12:19:40 +02:00
Piotr Zych 66d6d1b1c3 GH-79 Add feature spawnline with radius blocker or worldguard region
* Add worldguard line feature

* Remove useless soft depend

* Fix checkstyle

* Move WorldGuard into constructor

* Created new implementation for regions

* Changed way of calculating radius

* Calculate corners in real time.

* Move WorldGuard instance into method, fixing probably import problems

* Remove WorldGuard import
2023-06-28 01:34:19 +02:00
Martin Sulikowski 3bfc1a3c90 GH-83 Update manually apache commons io
* Update manually apache commons io

Signed-off-by: Martin Sulikowski <vLuckyyy.biznes@gmail.com>

* Revert checkstyle version.

---------

Signed-off-by: Martin Sulikowski <vLuckyyy.biznes@gmail.com>
2023-06-26 18:35:14 +02:00
renovate[bot] 6b0f5d2481 GH-81 Update dependency checkstyle to v10.12.1 (#81)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-25 22:10:39 +02:00
renovate[bot] e1264f1499 GH-75 Update all dependencies (#75)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-24 14:05:41 +02:00
failutee 6941670aad GH-78 Use PlayerDeathEvent instead of EntityDeathEvent (#78)
* Use PlayerDeathEvent

* Rename funcation
2023-06-18 18:52:59 +02:00
igoyek fee097efe4 GH-76 Disable elytra in combat (#76)
* Disable elytra in combat

* Fix player definition

* Disable elytra on damage
2023-06-18 18:51:55 +02:00
renovate[bot] 4a02909b6e GH-74 Update dependency dev.rollczi.litecommands:bukkit-adventure to v2.8.8 (#74)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-11 02:05:21 +02:00
renovate[bot] 03c0c82d63 GH-73 Update all dependencies (#73)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-10 17:03:18 +02:00
mend-bolt-for-github[bot] 40fe64bc7a GH-65 Add .whitesource configuration file (#65)
Co-authored-by: mend-bolt-for-github[bot] <42819689+mend-bolt-for-github[bot]@users.noreply.github.com>
2023-04-21 23:01:23 +02:00
Martin Sulikowski 6c96d381e5 GH-68 Create a backup system that stores it 72h from creation (#68) 2023-04-21 23:01:02 +02:00
Martin Sulikowski a0579c6d15 GH-69 Add support to console (#69) 2023-04-21 16:34:51 +02:00
renovate[bot] 1209d16033 GH-70 Update dependency gradle to v8.1.1 (#70)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-21 16:34:31 +02:00
Martin Sulikowski f51064c62d GH-67 Improve configs fields and descriptions, replace with plain list (#67) 2023-04-20 23:31:06 +02:00
Martin Sulikowski 1be395211f GH-66 Add prevent place specified blocks option (#66)
* Add prevent place specified blocks option

* Update PluginConfig.java
2023-04-20 00:02:10 +02:00
Martin Sulikowski 6ef6b9aff9 Release v1.0.3 2023-04-19 18:15:26 +02:00
Martin Sulikowski 456f8d3908 GH-64 Add disabled worlds option (#64) 2023-04-19 18:09:32 +02:00
renovate[bot] 648813fb83 GH-59 Update all dependencies (#59)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-19 15:35:27 +02:00
Martin Sulikowski 34c098fbde GH-62 Add WHITELIST/BLACKLIST mode to command block (#62)
* Add WHITELIST/BLACKLISt mode to command block

* Follow rollczi's suggestions

* change list name

* Follow rollczi suggestion
2023-04-18 23:08:18 +02:00
Martin Sulikowski a259a9acb2 GH-61 Refactor configuration class by correcting texts and sorting fields (#61)
* Refactor Messages class by correcting texts and sorting fields

* Update PluginConfig.java
2023-04-18 15:39:47 +02:00
98 changed files with 3569 additions and 908 deletions

No files matched your search

+28 -22
View File
@@ -1,37 +1,43 @@
name: Java CI with Gradle
on: [ push ]
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java:
- 17
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v3.5.0
- name: 'Set up JDK ${{ matrix.java }}'
uses: actions/setup-java@v3.11.0
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: adopt
java-version: '${{ matrix.java }}'
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@v3.3.1
uses: actions/cache@v3.3.2
with:
path: ~/.gradle
path: ~/.gradle/caches
key: >-
${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*',
'**/gradle-wrapper.properties') }}
restore-keys: '${{ runner.os }}-gradle-'
- name: Grant execute permission for gradlew
restore-keys: '${{ runner.os }}-gradle-'
- name: Make gradlew executable
run: chmod +x gradlew
- name: Build the Jar
run: './gradlew shadowJar'
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.2
- name: Build with Gradle
uses: gradle/gradle-build-action@0bfe00a136db5e61ba3416b372542a65863a9fee
with:
name: 'Successfully build EternalCombat using JDK ${{ matrix.jdk }}'
path: build/libs/EternalCombat*.jar
arguments: shadowJar
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.3
with:
name: 'Successfully build EternalCombat'
path: eternalcombat-plugin/build/libs/*.jar
+28
View File
@@ -0,0 +1,28 @@
name: Publish EternalCombat API
on:
release:
branches: [ master ]
types: [ published ]
jobs:
publish:
runs-on: ubuntu-latest
environment: deployment
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Publish with Gradle
run: ./gradlew clean build eternalcombat-api:publish
env:
ETERNAL_CODE_MAVEN_USERNAME: ${{ secrets.ETERNAL_CODE_MAVEN_USERNAME }}
ETERNAL_CODE_MAVEN_PASSWORD: ${{ secrets.ETERNAL_CODE_MAVEN_PASSWORD }}
+14
View File
@@ -0,0 +1,14 @@
{
"scanSettings": {
"baseBranches": []
},
"checkRunSettings": {
"vulnerableCheckRunConclusionLevel": "failure",
"displayMode": "diff",
"useMendCheckNames": true
},
"issueSettings": {
"minSeverityLevel": "LOW",
"issueType": "DEPENDENCY"
}
}
+43 -1
View File
@@ -15,11 +15,16 @@
### Information
EternalCombat has been tested on Minecraft versions 1.17.1 to 1.19.3, but it should work on most other versions. If you
EternalCombat has been tested on Minecraft versions 1.17.1 to 1.20.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.
## Building
Build EternalCombat using:
`./gradlew shadowJar`
### Permissions for EternalCombat
- `eternalcombat.status` - Allows to check if the player is in combat `/combatlog status <player>`
@@ -30,6 +35,43 @@ make sure it is installed on your server.
- `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.1.1")
```
with Maven:
```xml
<dependency>
<groupId>com.eternalcode</groupId>
<artifactId>eternalcombat-api</artifactId>
<version>1.1.1</version>
<scope>provided</scope>
</dependency>
```
### Contributing
We welcome all contributions to the EternalCombat project! Please check out [contributing](.github/CONTRIBUTING.md) for
-120
View File
@@ -1,120 +0,0 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
`java-library`
checkstyle
id("net.minecrell.plugin-yml.bukkit") version "0.5.3"
id("com.github.johnrengelman.shadow") version "8.1.1"
id("xyz.jpenilla.run-paper") version "2.0.1"
}
group = "com.eternalcode"
version = "1.0.2"
checkstyle {
toolVersion = "10.9.3"
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
maxErrors = 0
maxWarnings = 0
}
repositories {
mavenCentral()
mavenLocal()
maven { url = uri("https://repo.eternalcode.pl/releases") }
maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") }
maven { url = uri("https://repo.panda-lang.org/releases") }
maven { url = uri("https://papermc.io/repo/repository/maven-public/") }
}
dependencies {
// Spigot api
compileOnly("org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT")
// kyori
implementation("net.kyori:adventure-platform-bukkit:4.3.0")
implementation("net.kyori:adventure-text-minimessage:4.13.0")
// litecommands
implementation("dev.rollczi.litecommands:bukkit-adventure:2.8.7")
// cdn configs
implementation("net.dzikoysk:cdn:1.14.4") {
exclude(group = "org.jetbrains.kotlin")
}
// GitCheck
implementation("com.eternalcode:gitcheck:1.0.0")
// bstats
implementation("org.bstats:bstats-bukkit:3.0.2")
// tests
testImplementation("org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.2")
}
bukkit {
main = "com.eternalcode.combat.CombatPlugin"
author = "EternalCodeTeam"
apiVersion = "1.13"
prefix = "EternalCombat"
name = "EternalCombat"
version = "${project.version}"
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.getByName<Test>("test") {
useJUnitPlatform()
}
tasks.withType<JavaCompile> {
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
options.encoding = "UTF-8"
}
tasks {
runServer {
minecraftVersion("1.19.3")
}
}
tasks.withType<ShadowJar> {
archiveFileName.set("EternalCombat v${project.version} (MC 1.8.8-1.19x).jar")
dependsOn("checkstyleMain")
dependsOn("checkstyleTest")
dependsOn("test")
exclude(
"org/intellij/lang/annotations/**",
"org/jetbrains/annotations/**",
"META-INF/**",
"javax/**"
)
val prefix = "com.eternalcode.combat.libs"
listOf(
"panda.std",
"panda.utilities",
"org.panda-lang",
"net.dzikoysk",
"net.kyori",
"org.bstats",
"dev.rollczi.litecommands",
"com.eternalcode.gitcheck",
"org.json.simple",
).forEach { pack ->
relocate(pack, "$prefix.$pack")
}
}
+15
View File
@@ -0,0 +1,15 @@
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10")
implementation("com.github.johnrengelman:shadow:8.1.1")
implementation("net.minecrell:plugin-yml:0.6.0")
implementation("xyz.jpenilla:run-task:2.2.0")
}
@@ -0,0 +1,37 @@
plugins {
`java-library`
checkstyle
}
group = "com.eternalcode"
version = "1.1.1"
checkstyle {
toolVersion = "10.12.4"
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/") }
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.compileJava {
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
options.encoding = "UTF-8"
}
+78
View File
@@ -0,0 +1,78 @@
plugins {
id("eternalcombat.java")
`maven-publish`
}
dependencies {
// Spigot api
compileOnlyApi(libs.spigotApi)
// kyori
api(libs.adventurePlatformBukkit)
api(libs.adventureTextMinimessage)
// litecommands
api(libs.liteCommands)
// Okaeri configs
api(libs.okaeriConfigsYamlBukkit)
api(libs.okaeriConfigsSerdesCommons)
api(libs.okaeriConfigsSerdesBukkit)
// Panda utilities
api(libs.pandaUtilities)
// GitCheck
api(libs.gitCheck)
// commons
api(libs.apacheCommons)
// bstats
api(libs.bStatsBukkit)
// worldguard
compileOnly(libs.worldGuardBukkit)
// tests
testImplementation(libs.spigotApi)
testImplementation(libs.jUnitJupiterApi)
testImplementation(libs.jUnitJupiterParams)
testRuntimeOnly(libs.jUnitJupiterEngine)
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.getByName<Test>("test") {
useJUnitPlatform()
}
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,137 @@
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);
}
}
@@ -0,0 +1,30 @@
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.FightManager;
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.region.RegionProvider;
public interface EternalCombatApi {
FightManager getFightManager();
RegionProvider getRegionProvider();
FightPearlManager getFightPearlManager();
FightTagOutService getFightTagOutService();
FightEffectService getFightEffectService();
DropManager getDropManager();
DropKeepInventoryManager getDropKeepInventoryManager();
PluginConfig getPluginConfig();
}
@@ -0,0 +1,31 @@
package com.eternalcode.combat;
public final class EternalCombatProvider {
private static EternalCombatApi ETERNAL_COMBAT_API;
public static EternalCombatApi provide() {
if (ETERNAL_COMBAT_API == null) {
throw new IllegalStateException("EternalCombatApi has not been initialized yet!");
}
return ETERNAL_COMBAT_API;
}
static void initialize(EternalCombatApi eternalCombatApi) {
if (ETERNAL_COMBAT_API != null) {
throw new IllegalStateException("EternalCombatApi has already been initialized!");
}
ETERNAL_COMBAT_API = eternalCombatApi;
}
static void deinitialize() {
if (ETERNAL_COMBAT_API == null) {
throw new IllegalStateException("EternalCombatApi has not been initialized yet!");
}
ETERNAL_COMBAT_API = null;
}
}
@@ -0,0 +1,13 @@
package com.eternalcode.combat;
public enum WhitelistBlacklistMode {
WHITELIST,
BLACKLIST;
public boolean shouldBlock(boolean isInList) {
return switch (this) {
case WHITELIST -> !isInList;
case BLACKLIST -> isInList;
};
}
}
@@ -0,0 +1,8 @@
package com.eternalcode.combat.bridge;
@FunctionalInterface
public interface BridgeInitializer {
void initialize();
}
@@ -0,0 +1,49 @@
package com.eternalcode.combat.bridge;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.region.DefaultRegionProvider;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.combat.region.WorldGuardRegionProvider;
import org.bukkit.plugin.PluginManager;
import java.util.logging.Logger;
public class BridgeService {
private final PluginConfig pluginConfig;
private final PluginManager pluginManager;
private final Logger logger;
private RegionProvider regionProvider;
public BridgeService(PluginConfig pluginConfig, PluginManager pluginManager, Logger logger) {
this.pluginConfig = pluginConfig;
this.pluginManager = pluginManager;
this.logger = logger;
}
public void init() {
this.initialize("WorldGuard",
() -> this.regionProvider = new WorldGuardRegionProvider(this.pluginConfig.settings.blockedRegions),
() -> {
this.regionProvider = new DefaultRegionProvider(this.pluginConfig.settings.blockedRegionRadius);
this.logger.warning("WorldGuard is not installed, set to default region provider.");
});
}
private void initialize(String pluginName, BridgeInitializer initializer, Runnable runnable) {
if (this.pluginManager.isPluginEnabled(pluginName)) {
initializer.initialize();
this.logger.info("Successfully initialized " + pluginName + " bridge.");
}
else {
runnable.run();
}
}
public RegionProvider getRegionProvider() {
return this.regionProvider;
}
}
@@ -29,7 +29,7 @@ public class InvalidUsage implements InvalidUsageHandler<CommandSender> {
Formatter formatter = new Formatter()
.register("{USAGE}", schematic);
this.announcer.sendMessage(commandSender, formatter.format(this.config.messages.invalidUsage));
this.announcer.sendMessage(commandSender, formatter.format(this.config.messages.invalidCommandUsage));
}
}
}
@@ -0,0 +1,119 @@
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();
}
}
}
}
@@ -0,0 +1,49 @@
package com.eternalcode.combat.config;
import com.eternalcode.combat.notification.serializer.NotificationSerializer;
import eu.okaeri.configs.ConfigManager;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.serdes.commons.SerdesCommons;
import eu.okaeri.configs.yaml.bukkit.YamlBukkitConfigurer;
import eu.okaeri.configs.yaml.bukkit.serdes.SerdesBukkit;
import java.io.File;
import java.util.HashSet;
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());
});
configFile.withBindFile(file);
configFile.withRemoveOrphans(true);
configFile.saveDefaults();
configFile.load(true);
this.configs.add(configFile);
return configFile;
}
public void reload() {
for (OkaeriConfig config : this.configs) {
config.load();
}
}
}
@@ -0,0 +1,224 @@
package com.eternalcode.combat.config.implementation;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.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 eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.event.entity.EntityDamageEvent;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
public class PluginConfig extends OkaeriConfig {
@Comment("# Do you want to change the plugin settings?")
public Settings settings = new Settings();
@Comment({" ", "# Ender pearl settings"})
public FightPearlSettings pearl = new FightPearlSettings();
@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)"})
public DropSettings dropSettings = new DropSettings();
public static class Settings extends OkaeriConfig {
@Comment("# Whether the player should receive information about new plugin updates upon joining the server")
public boolean shouldReceivePluginUpdates = true;
@Comment("# The duration of combat in seconds")
public Duration combatDuration = Duration.ofSeconds(20);
@Comment("# List of worlds to ignore")
public List<String> worldsToIgnore = List.of(
"your_world"
);
@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 radius of the blocked region if you aren't using WorldGuard basen on default spawn region!")
public int blockedRegionRadius = 10;
@Comment("# Release attacker after victim dies?")
public boolean shouldReleaseAttacker = true;
@Comment("# Command blocking mode, available modes: WHITELIST, BLACKLIST")
public WhitelistBlacklistMode commandBlockingMode = WhitelistBlacklistMode.BLACKLIST;
@Comment({
"# List of commands based on the mode above",
"# Based on BLACKLIST mode, all commands in the list are blocked, and all others are allowed",
"# Based on WHITELIST mode, all commands in the list are allowed, and all others are blocked",
})
public List<String> blockedCommands = List.of(
"gamemode",
"spawn",
"tp"
);
@Comment("# Block the use of elytra?")
public boolean shouldPreventElytraUsage = true;
@Comment("# Disable the use of elytra on damage?")
public boolean shouldElytraDisableOnDamage = true;
@Comment("# Block the opening of inventory?")
public boolean shouldPreventInventoryOpening = true;
@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({
"# Disable placing specific blocks?",
"# If you want to block all blocks, enable shouldPreventBlockPlacing and make this list empty",
"# If you want to disable placing only specific blocks, enable shouldPreventBlockPlacing and add blocks to this list above",
"# If you want to disable this feature completely, disable shouldPreventBlockPlacing option above",
})
public List<Material> specificBlocksToPreventPlacing = List.of();
@Comment("# Do You want to enable combat log for non-player causes of damage? - Set to false to disable")
public boolean shouldEnableDamageCauses = false;
@Comment("# Select the mode for damage causes, available modes: WHITELIST, BLACKLIST")
public WhitelistBlacklistMode damageCausesMode = WhitelistBlacklistMode.WHITELIST;
@Comment({
"# After selecting the mode above, select the causes of damage to be logged",
"# You can find a list of all causes here: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html",
"# While using Whitelist mode the player will get a combat log after the damage from the list below",
"# While using Blacklist mode the player will get a combat log after any damage non-listed below",
})
public List<EntityDamageEvent.DamageCause> damageCausesToLog = List.of(
EntityDamageEvent.DamageCause.LAVA,
EntityDamageEvent.DamageCause.CONTACT,
EntityDamageEvent.DamageCause.FIRE,
EntityDamageEvent.DamageCause.FIRE_TICK
);
@Comment({
"# After what type of projectile entity should not tag the player as fighter?",
"# You can find a list of all entity types here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html"
})
public List<EntityType> disabledProjectileEntities = List.of(
EntityType.ENDER_PEARL
);
}
@Comment({" ", "# Do you want to change the plugin messages?"})
public Messages messages = new Messages();
public static class Messages extends OkaeriConfig {
@Comment("# Do you want to change the admin messages?")
public AdminMessages admin = new AdminMessages();
@Comment({
" ",
"# Combat log notification",
"# You can use {TIME} variable to display the time left in combat",
"# Notification types: CHAT, ACTION_BAR, TITLE, SUB_TITLE, BOSS_BAR",
" ",
"# BossBar progress: This is the value of the progress bar. Set it to -1.0 to show the remaining combat time",
"# 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}");
@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!";
@Comment("# Message sent when the specified player could not be found")
public String playerNotFound = "&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!";
@Comment("# Message sent when the player leaves combat")
public String playerUntagged = "&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!";
@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!";
@Comment("# Message sent when player tries to use a command with invalid arguments")
public String invalidCommandUsage = "&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!";
@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 enter a region")
public String cantEnterOnRegion = "&cYou can't enter on 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!";
@Comment("# Message sent to admin when they tag a player")
public String adminTagPlayer = "&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.";
@Comment("# Message sent to admin when they remove a player from combat")
public String adminUntagPlayer = "&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!";
@Comment("# Message sent when the player is in combat")
public String playerInCombat = "&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.";
@Comment("# Message sent when an admin tries to tag themselves")
public String adminCannotTagSelf = "&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} ";
@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} ";
@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} !";
@Comment("# Message sent when an admin reenables the ability to get tagged for the player")
public String adminTagOutOff = "&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!";
@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!";
}
}
}
@@ -0,0 +1,116 @@
package com.eternalcode.combat.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;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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) {
this.player = player;
this.killer = killer;
this.fightTag = fightTag;
this.deathCause = deathCause;
this.droppedItems = droppedItems;
this.droppedExp = droppedExp;
}
public List<ItemStack> getDroppedItems() {
return Collections.unmodifiableList(this.droppedItems);
}
public Player getPlayer() {
return this.player;
}
public @Nullable Player getKiller() {
return this.killer;
}
public FightTag getFightTag() {
return this.fightTag;
}
public FightDeathCause getDeathCause() {
return this.deathCause;
}
public boolean hasKiller() {
return this.killer != null;
}
public int getDroppedExp() {
return this.droppedExp;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Player player;
private Player killer;
private FightTag fightTag;
private FightDeathCause deathCause;
private List<ItemStack> droppedItems;
private int droppedExp;
public Builder player(@NotNull Player player) {
this.player = player;
return this;
}
public Builder killer(@Nullable Player killer) {
this.killer = killer;
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;
}
public Builder droppedExp(int droppedExp) {
this.droppedExp = droppedExp;
return this;
}
public Drop build() {
return new Drop(
this.player,
this.killer,
this.fightTag,
this.deathCause,
this.droppedItems,
this.droppedExp
);
}
}
}
@@ -0,0 +1,84 @@
package com.eternalcode.combat.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.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import java.util.List;
import java.util.UUID;
public class DropController implements Listener {
private final DropManager dropManager;
private final DropKeepInventoryManager keepInventoryManager;
private final DropSettings dropSettings;
private final FightManager fightManager;
public DropController(DropManager dropManager, DropKeepInventoryManager keepInventoryManager, DropSettings dropSettings, FightManager fightManager) {
this.dropManager = dropManager;
this.keepInventoryManager = keepInventoryManager;
this.dropSettings = dropSettings;
this.fightManager = fightManager;
}
@EventHandler
void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
DropType dropType = this.dropSettings.dropType;
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);
if (result == null) {
return;
}
drops.clear();
drops.addAll(result.droppedItems());
this.keepInventoryManager.addItems(player.getUniqueId(), result.removedItems());
if (this.dropSettings.affectExperience) {
event.setDroppedExp(drop.getDroppedExp());
}
}
@EventHandler
void onPlayerRespawn(PlayerRespawnEvent event) {
Player player = event.getPlayer();
UUID playerUniqueId = player.getUniqueId();
if (this.keepInventoryManager.hasItems(playerUniqueId)) {
PlayerInventory playerInventory = player.getInventory();
ItemStack[] itemsToGive = this.keepInventoryManager.nextItems(playerUniqueId)
.toArray(new ItemStack[0]);
playerInventory.addItem(itemsToGive);
}
}
}
@@ -0,0 +1,38 @@
package com.eternalcode.combat.drop;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class DropKeepInventoryManager {
private final Map<UUID, List<ItemStack>> itemsToGiveAfterRespawn = new HashMap<>();
public void addItem(UUID uuid, ItemStack item) {
this.itemsToGiveAfterRespawn.computeIfAbsent(uuid, k -> new ArrayList<>()).add(item);
}
public void addItems(UUID uuid, List<ItemStack> item) {
item.forEach(i -> this.addItem(uuid, i));
}
public boolean hasItems(UUID uuid) {
return this.itemsToGiveAfterRespawn.containsKey(uuid);
}
public List<ItemStack> nextItems(UUID uuid) {
List<ItemStack> itemStacks = this.itemsToGiveAfterRespawn.remove(uuid);
if (itemStacks == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(itemStacks);
}
}
@@ -0,0 +1,36 @@
package com.eternalcode.combat.drop;
import java.util.HashMap;
import java.util.Map;
public class DropManager {
private final Map<DropType, DropModifier> modifiers;
public DropManager() {
this.modifiers = new HashMap<>();
}
public void registerModifier(DropModifier dropModifier) {
DropType dropType = dropModifier.getDropType();
if (dropType == null) {
throw new RuntimeException("Drop type cannot be null! '%s'".formatted(dropModifier.getClass().getSimpleName()));
}
if (dropType == DropType.UNCHANGED) {
throw new RuntimeException("You cannot register DropModifier for this type '%s'".formatted(dropType.name()));
}
this.modifiers.put(dropType, dropModifier);
}
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()));
}
return this.modifiers.get(dropType).modifyDrop(drop);
}
}
@@ -0,0 +1,9 @@
package com.eternalcode.combat.drop;
public interface DropModifier {
DropType getDropType();
DropResult modifyDrop(Drop drop);
}
@@ -0,0 +1,24 @@
package com.eternalcode.combat.drop;
import org.bukkit.inventory.ItemStack;
import java.util.Collections;
import java.util.List;
public record DropResult(
List<ItemStack> droppedItems,
List<ItemStack> removedItems,
int droppedExp
) {
@Override
public List<ItemStack> droppedItems() {
return Collections.unmodifiableList(this.droppedItems);
}
@Override
public List<ItemStack> removedItems() {
return Collections.unmodifiableList(this.removedItems);
}
}
@@ -0,0 +1,23 @@
package com.eternalcode.combat.drop;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
public class DropSettings extends OkaeriConfig {
@Comment({
"# UNCHANGED - The default way of item drop defined by the engine",
"# PERCENT - Drops a fixed percentage of items",
"# PLAYERS_HEALTH - Drops inverted percentage of the player's health (i.e. if the player has, for example, 80% HP, he will drop 20% of items. Only works when the player escapes from combat by quiting game)"
})
public DropType dropType = DropType.UNCHANGED;
@Comment("# What percentage of items should drop from the player? (Only if Drop Type is set to PERCENT)")
public int dropItemPercent = 100;
@Comment("# This option is responsible for the lowest percentage of the player that can drop (i.e. if the player leaves the game while he has 100% of his HP, the percentage of items that is set in this option will drop, if you set this option to 0, then nothing will drop from such a player)")
public int playersHealthPercentClamp = 20;
@Comment("# Does the drop modification affect the experience drop?")
public boolean affectExperience = false;
}
@@ -0,0 +1,9 @@
package com.eternalcode.combat.drop;
public enum DropType {
UNCHANGED,
PERCENT,
PLAYERS_HEALTH
}
@@ -0,0 +1,40 @@
package com.eternalcode.combat.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.util.InventoryUtil;
import com.eternalcode.combat.util.MathUtil;
import com.eternalcode.combat.util.RemoveItemResult;
import org.bukkit.inventory.ItemStack;
import java.util.List;
public class PercentDropModifier implements DropModifier {
private final DropSettings settings;
public PercentDropModifier(DropSettings settings) {
this.settings = settings;
}
@Override
public DropType getDropType() {
return DropType.PERCENT;
}
@Override
public DropResult modifyDrop(Drop drop) {
int dropItemPercent = 100 - MathUtil.clamp(this.settings.dropItemPercent, 0, 100);
List<ItemStack> droppedItems = drop.getDroppedItems();
int itemsToDelete = InventoryUtil.calculateItemsToDelete(dropItemPercent, droppedItems, ItemStack::getAmount);
int droppedExp = MathUtil.getRoundedCountFromPercentage(dropItemPercent, drop.getDroppedExp());
RemoveItemResult result = InventoryUtil.removeRandomItems(droppedItems, itemsToDelete);
return new DropResult(result.restItems(), result.removedItems(), droppedExp);
}
}
@@ -0,0 +1,56 @@
package com.eternalcode.combat.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.util.InventoryUtil;
import com.eternalcode.combat.util.MathUtil;
import com.eternalcode.combat.util.RemoveItemResult;
import org.bukkit.attribute.Attribute;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.List;
public class PlayersHealthDropModifier implements DropModifier {
private final DropSettings settings;
public PlayersHealthDropModifier(DropSettings settings) {
this.settings = settings;
}
@Override
public DropType getDropType() {
return DropType.PLAYERS_HEALTH;
}
@Override
public DropResult modifyDrop(Drop drop) {
if (drop.getDeathCause() != FightDeathCause.ESCAPE) {
return null;
}
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();
int percentHealth = MathUtil.getRoundedCountPercentage(health, maxHealth);
int reversedPercent = MathUtil.clamp(100 - percentHealth, this.settings.playersHealthPercentClamp, 100);
int itemsToDelete = InventoryUtil.calculateItemsToDelete(reversedPercent, droppedItems, ItemStack::getAmount);
int droppedExp = MathUtil.getRoundedCountFromPercentage(reversedPercent, drop.getDroppedExp());
RemoveItemResult result = InventoryUtil.removeRandomItems(droppedItems, itemsToDelete);
return new DropResult(result.restItems(), result.removedItems(), droppedExp);
}
}
@@ -0,0 +1,20 @@
package com.eternalcode.combat.event;
import org.bukkit.Server;
import org.bukkit.event.Event;
public class EventCaller {
private final Server server;
public EventCaller(Server server) {
this.server = server;
}
public <T extends Event> T publishEvent(T event) {
this.server.getPluginManager().callEvent(event);
return event;
}
}
@@ -0,0 +1,10 @@
package com.eternalcode.combat.fight;
public enum FightDeathCause {
UNKNOWN,
ESCAPE,
KILLED_BY_PLAYER,
KILLED_BY_ENTITY
}
@@ -0,0 +1,72 @@
package com.eternalcode.combat.fight;
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;
public class FightManager {
private final Map<UUID, FightTag> fights = new ConcurrentHashMap<>();
private final EventCaller eventCaller;
public FightManager(EventCaller eventCaller) {
this.eventCaller = eventCaller;
}
public boolean isInCombat(UUID player) {
if (!this.fights.containsKey(player)) {
return false;
}
FightTag fightTag = this.fights.get(player);
return !fightTag.isExpired();
}
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;
}
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();
}
}
@@ -7,18 +7,27 @@ import java.util.UUID;
public class FightTag {
private final UUID taggedPlayer;
private final Instant endOfCombatLog;
private double healthBeforeDeath;
private FightDeathCause deathCause;
FightTag(UUID personToAddCombat, Instant endOfCombatLog) {
this.taggedPlayer = personToAddCombat;
this.endOfCombatLog = endOfCombatLog;
this.healthBeforeDeath = 0;
this.deathCause = FightDeathCause.UNKNOWN;
}
public UUID getTaggedPlayer() {
return this.taggedPlayer;
}
public Instant getEndOfCombatLog() {
return this.endOfCombatLog;
}
public boolean isExpired() {
return Instant.now().isAfter(this.endOfCombatLog);
}
@@ -33,4 +42,19 @@ public class FightTag {
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;
}
}
@@ -0,0 +1,67 @@
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.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;
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) {
this.server = server;
this.config = config;
this.fightManager = fightManager;
this.bossBarService = bossBarService;
this.announcer = announcer;
}
@Override
public void run() {
for (FightTag fightTag : this.fightManager.getFights()) {
Player player = this.server.getPlayer(fightTag.getTaggedPlayer());
if (player == null) {
continue;
}
UUID playerUniqueId = player.getUniqueId();
if (fightTag.isExpired()) {
this.fightManager.untag(playerUniqueId, CauseOfUnTag.TIME_EXPIRED);
return;
}
Duration remaining = fightTag.getRemainingDuration();
Formatter formatter = new Formatter()
.register("{TIME}", DurationUtil.format(remaining));
Notification combatNotification = this.config.messages.combatNotification;
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);
}
}
@@ -0,0 +1,9 @@
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) {
}
@@ -0,0 +1,27 @@
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));
}
}
@@ -0,0 +1,119 @@
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);
}
}
@@ -0,0 +1,142 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityToggleGlideEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import java.util.List;
import java.util.UUID;
public class FightActionBlockerController implements Listener {
private final FightManager fightManager;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public FightActionBlockerController(FightManager fightManager, NotificationAnnouncer announcer, PluginConfig config) {
this.fightManager = fightManager;
this.announcer = announcer;
this.config = config;
}
@EventHandler
void onPlace(BlockPlaceEvent event) {
if (!this.config.settings.shouldPreventBlockPlacing) {
return;
}
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
Block block = event.getBlock();
int level = block.getY();
List<Material> specificBlocksToPreventPlacing = this.config.settings.specificBlocksToPreventPlacing;
boolean isBlockPlaceLevel = level > this.config.settings.minBlockPlacingLevel;
if (isBlockPlaceLevel && specificBlocksToPreventPlacing.isEmpty()) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.blockPlacingBlockedDuringCombat);
}
Material blockMaterial = block.getType();
boolean isBlockInDisabledList = specificBlocksToPreventPlacing.contains(blockMaterial);
if (isBlockPlaceLevel && isBlockInDisabledList) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.blockPlacingBlockedDuringCombat);
}
}
@EventHandler
public void onToggleGlide(EntityToggleGlideEvent event) {
if (!this.config.settings.shouldPreventElytraUsage) {
return;
}
if (!(event.getEntity() instanceof Player player)) {
return;
}
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
if (event.isGliding()) {
event.setCancelled(true);
}
}
@EventHandler
public void onDamage(EntityDamageEvent event) {
if (!this.config.settings.shouldElytraDisableOnDamage) {
return;
}
if (!(event.getEntity() instanceof Player player)) {
return;
}
UUID uniqueId = player.getUniqueId();
if (this.fightManager.isInCombat(uniqueId) && player.isGliding()) {
player.setGliding(false);
}
}
@EventHandler
void onOpenInventory(InventoryOpenEvent event) {
if (!this.config.settings.shouldPreventInventoryOpening) {
return;
}
Player player = (Player) event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.inventoryBlockedDuringCombat);
}
@EventHandler
public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
Player player = event.getPlayer();
UUID playerUniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(playerUniqueId)) {
return;
}
String command = event.getMessage().split(" ")[0].substring(1).toLowerCase();
boolean isMatchCommand = this.config.settings.blockedCommands.stream()
.anyMatch(command::startsWith);
WhitelistBlacklistMode mode = this.config.settings.commandBlockingMode;
boolean shouldCancel = mode.shouldBlock(isMatchCommand);
if (shouldCancel) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.commandDisabledDuringCombat);
}
}
}
@@ -0,0 +1,59 @@
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(victim.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);
}
}
@@ -2,40 +2,26 @@ 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.entity.EntityDeathEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import panda.utilities.text.Formatter;
public class FightUnTagController implements Listener {
public class FightEscapeController implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public FightUnTagController(FightManager fightManager, PluginConfig config, NotificationAnnouncer announcer) {
public FightEscapeController(FightManager fightManager, PluginConfig config, NotificationAnnouncer announcer) {
this.fightManager = fightManager;
this.config = config;
this.announcer = announcer;
}
@EventHandler
void onEntityDeath(EntityDeathEvent event) {
if (!(event.getEntity() instanceof Player player)) {
return;
}
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
this.announcer.sendMessage(player, this.config.messages.unTagPlayer);
this.fightManager.untag(player.getUniqueId());
}
@EventHandler
void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
@@ -47,11 +33,13 @@ public class FightUnTagController implements Listener {
Formatter formatter = new Formatter()
.register("{PLAYER}", player.getName());
String format = formatter.format(this.config.messages.playerLoggedInCombat);
String format = formatter.format(this.config.messages.playerLoggedOutDuringCombat);
this.fightManager.untag(player.getUniqueId());
this.announcer.broadcast(player, format);
player.setHealth(0.0);
this.announcer.broadcast(format);
FightTag fightTag = this.fightManager.getTag(player.getUniqueId());
fightTag.setHealthBeforeDeath(player.getHealth());
player.setHealth(0.0); // Untagged in PlayerDeathEvent
}
}
@@ -0,0 +1,61 @@
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.notification.NotificationAnnouncer;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
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) {
this.fightManager = fightManager;
this.announcer = announcer;
this.bossBarService = bossBarService;
this.config = config;
this.server = server;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
void onTag(FightTagEvent 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.sendMessage(player, this.config.messages.playerTagged);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
void onUnTag(FightTagEvent 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.sendMessage(player, this.config.messages.playerUntagged);
this.bossBarService.hide(event.getPlayer());
}
}
@@ -1,8 +1,11 @@
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 org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.EventHandler;
@@ -34,32 +37,34 @@ public class FightTagController implements Listener {
return;
}
List<EntityType> disabledProjectileEntities = this.config.settings.disabledProjectileEntities;
if (event.getDamager() instanceof Projectile projectile && disabledProjectileEntities.contains(projectile.getType())) {
return;
}
if (this.isPlayerInDisabledWorld(attackedPlayerByPerson)) {
return;
}
Player personToAddCombatTime = this.getDamager(event);
if (personToAddCombatTime == null) {
return;
}
Duration combatTime = this.config.settings.combatLogTime;
Duration combatTime = this.config.settings.combatDuration;
UUID attackedUniqueId = attackedPlayerByPerson.getUniqueId();
UUID personToAddCombatTimeUniqueId = personToAddCombatTime.getUniqueId();
if (!this.fightManager.isInCombat(attackedUniqueId)) {
this.announcer.sendMessage(personToAddCombatTime, this.config.messages.tagPlayer);
}
if (!this.fightManager.isInCombat(personToAddCombatTimeUniqueId)) {
this.announcer.sendMessage(attackedPlayerByPerson, this.config.messages.tagPlayer);
}
this.fightManager.tag(attackedUniqueId, combatTime);
this.fightManager.tag(personToAddCombatTimeUniqueId, combatTime);
this.fightManager.tag(attackedUniqueId, combatTime, CauseOfTag.PLAYER);
this.fightManager.tag(personToAddCombatTimeUniqueId, combatTime, CauseOfTag.PLAYER);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
void onEntityDamage(EntityDamageEvent event) {
if (!this.config.settings.enableDamageCauses) {
if (!this.config.settings.shouldEnableDamageCauses) {
return;
}
@@ -67,22 +72,26 @@ public class FightTagController implements Listener {
return;
}
Duration combatTime = this.config.settings.combatLogTime;
UUID uuid = player.getUniqueId();
List<EntityDamageEvent.DamageCause> damageCauses = this.config.settings.damageCauses;
EntityDamageEvent.DamageCause cause = event.getCause();
if (!damageCauses.contains(cause)) {
if (this.isPlayerInDisabledWorld(player)) {
return;
}
if (!this.fightManager.isInCombat(uuid)) {
this.announcer.sendMessage(player, this.config.messages.tagPlayer);
Duration combatTime = this.config.settings.combatDuration;
UUID uuid = player.getUniqueId();
List<EntityDamageEvent.DamageCause> damageCauses = this.config.settings.damageCausesToLog;
WhitelistBlacklistMode mode = this.config.settings.damageCausesMode;
EntityDamageEvent.DamageCause cause = event.getCause();
boolean shouldLog = mode.shouldBlock(damageCauses.contains(cause));
if (shouldLog) {
return;
}
this.fightManager.tag(uuid, combatTime);
this.fightManager.tag(uuid, combatTime, CauseOfTag.NON_PLAYER);
}
@Nullable
@@ -98,4 +107,10 @@ public class FightTagController implements Listener {
return null;
}
private boolean isPlayerInDisabledWorld(Player player) {
String worldName = player.getWorld().getName();
return this.config.settings.worldsToIgnore.contains(worldName);
}
}
@@ -0,0 +1,38 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
public class FightUnTagController implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
public FightUnTagController(FightManager fightManager, PluginConfig config) {
this.fightManager = fightManager;
this.config = config;
}
@EventHandler
void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
Player killer = player.getKiller();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
CauseOfUnTag causeOfUnTag = (killer == null ? CauseOfUnTag.DEATH_BY_PLAYER : CauseOfUnTag.DEATH);
this.fightManager.untag(player.getUniqueId(), causeOfUnTag);
if (killer != null && this.config.settings.shouldReleaseAttacker) {
this.fightManager.untag(killer.getUniqueId(), CauseOfUnTag.ATTACKER_RELEASE);
}
}
}
@@ -0,0 +1,124 @@
package com.eternalcode.combat.fight.effect;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityPotionEffectEvent;
import org.bukkit.potion.PotionEffect;
public class FightEffectController implements Listener {
private final FightEffectService effectService;
private final FightEffectSettings effectSettings;
private final FightManager fightManager;
private final Server server;
public FightEffectController(FightEffectSettings settings, FightEffectService effectService, FightManager fightManager, Server server) {
this.effectSettings = settings;
this.effectService = effectService;
this.fightManager = fightManager;
this.server = server;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTag(FightTagEvent event) {
if (!this.effectSettings.customEffectsEnabled) {
return;
}
Player player = this.server.getPlayer(event.getPlayer());
if (player == null) {
return;
}
this.effectSettings.customEffects.forEach((key, value) ->
this.effectService.applyCustomEffect(player, key, value));
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onQuit(FightUntagEvent event) {
if (!this.effectSettings.customEffectsEnabled) {
return;
}
if (event.getCause() == CauseOfUnTag.LOGOUT) {
Player player = Bukkit.getPlayer(event.getPlayer());
assert player != null;
this.effectService.clearStoredEffects(player);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onUntag(FightUntagEvent event) {
if (!this.effectSettings.customEffectsEnabled) {
return;
}
Player player = this.server.getPlayer(event.getPlayer());
if (player == null) {
return;
}
this.effectSettings.customEffects.forEach((key, value) -> this.effectService.removeCustomEffect(player, key, value));
this.effectService.restoreActiveEffects(player);
}
@EventHandler
public void onDeath(FightUntagEvent event) {
if (!this.effectSettings.customEffectsEnabled) {
return;
}
if (event.getCause() == CauseOfUnTag.DEATH_BY_PLAYER || event.getCause() == CauseOfUnTag.DEATH) {
Player player = Bukkit.getPlayer(event.getPlayer());
assert player != null;
this.effectService.clearStoredEffects(player);
}
}
@EventHandler
public void onEffectChange(EntityPotionEffectEvent event) {
if (!this.effectSettings.customEffectsEnabled) {
return;
}
if (!(event.getEntity() instanceof Player player)) {
return;
}
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
PotionEffect newEffect = event.getNewEffect();
PotionEffect oldEffect = event.getOldEffect();
if (!this.isRemovedEffect(newEffect, oldEffect)) {
return;
}
Integer customAmplifier = this.effectSettings.customEffects.get(oldEffect.getType());
if (customAmplifier == null) {
return;
}
player.addPotionEffect(new PotionEffect(oldEffect.getType(), -1, customAmplifier));
}
private boolean isRemovedEffect(PotionEffect newEffect, PotionEffect oldEffect) {
return newEffect == null && oldEffect != null;
}
}
@@ -0,0 +1,75 @@
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 FightEffectService {
private final Map<UUID, List<PotionEffect>> activeEffects = new HashMap<>();
private static final int INFINITE_DURATION = -1;
public void storeActiveEffect(Player player, PotionEffect effect) {
List<PotionEffect> effects = this.activeEffects.computeIfAbsent(player.getUniqueId(), k -> new ArrayList<>());
effects.add(effect);
}
public void restoreActiveEffects(Player player) {
List<PotionEffect> currentEffects = this.getCurrentEffects(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);
}
}
@@ -0,0 +1,26 @@
package com.eternalcode.combat.fight.effect;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import org.bukkit.potion.PotionEffectType;
import java.util.Map;
public class FightEffectSettings extends OkaeriConfig {
@Comment({"# Do you want to add effects to players in combat?"})
public boolean customEffectsEnabled = false;
@Comment({
"# If the option above is set to true, you can add effects to players in combat below",
"# You can find a list of all potion effects here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/potion/PotionEffectType.html",
"# Correct format: 'EFFECT_TYPE:AMPLIFIER' Amplifier strength starts from 0, so level 1 gives effect strength 2",
"# Example: SPEED:1, DAMAGE_RESISTANCE:0",
})
public Map<PotionEffectType, Integer> customEffects = Map.of(
PotionEffectType.SPEED, 1,
PotionEffectType.DAMAGE_RESISTANCE, 0
);
}
@@ -0,0 +1,7 @@
package com.eternalcode.combat.fight.event;
public enum CauseOfTag {
PLAYER,
NON_PLAYER,
COMMAND
}
@@ -0,0 +1,10 @@
package com.eternalcode.combat.fight.event;
public enum CauseOfUnTag {
TIME_EXPIRED,
DEATH,
DEATH_BY_PLAYER,
LOGOUT,
COMMAND,
ATTACKER_RELEASE
}
@@ -0,0 +1,63 @@
package com.eternalcode.combat.fight.event;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
public class FightTagEvent extends Event implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private final UUID player;
private final CauseOfTag cause;
private boolean isCancelled = false;
private String cancelMessage = "";
public FightTagEvent(UUID player, CauseOfTag cause) {
this.player = player;
this.cause = cause;
}
public UUID getPlayer() {
return this.player;
}
public CauseOfTag getCause() {
return this.cause;
}
@Override
public boolean isCancelled() {
return this.isCancelled;
}
@Override
public void setCancelled(boolean cancelled) {
throw new UnsupportedOperationException("Use #cancel(String) instead");
}
public void cancel(String cancelMessage) {
this.isCancelled = true;
this.cancelMessage = cancelMessage;
}
public void allow() {
this.isCancelled = false;
this.cancelMessage = "";
}
public String getCancelMessage() {
return this.cancelMessage;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}
@@ -0,0 +1,47 @@
package com.eternalcode.combat.fight.event;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
public class FightUntagEvent extends Event implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private final UUID player;
private final CauseOfUnTag cause;
private boolean isCancelled = false;
public FightUntagEvent(UUID player, CauseOfUnTag cause) {
this.player = player;
this.cause = cause;
}
public UUID getPlayer() {
return this.player;
}
public CauseOfUnTag getCause() {
return this.cause;
}
@Override
public boolean isCancelled() {
return this.isCancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.isCancelled = cancelled;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}
@@ -0,0 +1,101 @@
package com.eternalcode.combat.fight.pearl;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.util.DurationUtil;
import org.bukkit.Material;
import org.bukkit.entity.EnderPearl;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import panda.utilities.text.Formatter;
import java.time.Duration;
import java.util.UUID;
public class FightPearlController implements Listener {
private final FightPearlSettings settings;
private final NotificationAnnouncer announcer;
private final FightManager fightManager;
private final FightPearlManager fightPearlManager;
public FightPearlController(FightPearlSettings settings, NotificationAnnouncer announcer, FightManager fightManager, FightPearlManager fightPearlManager) {
this.settings = settings;
this.announcer = announcer;
this.fightManager = fightManager;
this.fightPearlManager = fightPearlManager;
}
@EventHandler
void onInteract(PlayerInteractEvent event) {
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.settings.pearlThrowBlocked) {
return;
}
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
ItemStack item = event.getItem();
if (item == null || item.getType() != Material.ENDER_PEARL) {
return;
}
Action action = event.getAction();
if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) {
return;
}
if (this.settings.pearlThrowDelay.isZero()) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.settings.pearlThrowBlockedDuringCombat);
return;
}
if (this.fightPearlManager.hasDelay(uniqueId)) {
event.setCancelled(true);
Duration remainingPearlDelay = this.fightPearlManager.getRemainingDelay(uniqueId);
Formatter formatter = new Formatter()
.register("{TIME}", DurationUtil.format(remainingPearlDelay));
String format = formatter.format(this.settings.pearlThrowBlockedDelayDuringCombat);
this.announcer.sendMessage(player, format);
return;
}
this.fightPearlManager.markDelay(uniqueId);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
void onEntityDamage(EntityDamageByEntityEvent event) {
if (this.settings.pearlThrowDamageEnabled) {
return;
}
if (!(event.getEntity() instanceof Player)) {
return;
}
if (!(event.getDamager() instanceof EnderPearl)) {
return;
}
if (event.getCause() != EntityDamageEvent.DamageCause.FALL) {
return;
}
event.setDamage(0.0D);
}
}
@@ -0,0 +1,37 @@
package com.eternalcode.combat.fight.pearl;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
public class FightPearlManager {
private final FightPearlSettings pearlSettings;
private final Cache<UUID, Instant> pearlDelays;
public FightPearlManager(FightPearlSettings pearlSettings) {
this.pearlSettings = pearlSettings;
this.pearlDelays = CacheBuilder.newBuilder()
.expireAfterWrite(pearlSettings.pearlThrowDelay)
.build();
}
public void markDelay(UUID uuid) {
this.pearlDelays.put(uuid, Instant.now().plus(this.pearlSettings.pearlThrowDelay));
}
public boolean hasDelay(UUID uuid) {
return Instant.now().isBefore(this.getDelay(uuid));
}
public Duration getRemainingDelay(UUID uuid) {
return Duration.between(Instant.now(), this.getDelay(uuid));
}
public Instant getDelay(UUID uuid) {
return this.pearlDelays.asMap().getOrDefault(uuid, Instant.MIN);
}
}
@@ -0,0 +1,29 @@
package com.eternalcode.combat.fight.pearl;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
public class FightPearlSettings extends OkaeriConfig {
@Comment({ "# Is pearl damage to be enabled?", "# This will work globally" })
public boolean pearlThrowDamageEnabled = true;
@Comment("# Set true, If you want to lock pearls during the combat")
public boolean pearlThrowBlocked = false;
@Comment({
"# Block throwing pearls with delay?",
"# If you set this to for example 3s, player will have to wait 3 seconds before throwing another pearl",
"# Set to 0 to disable"
})
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!";
@Comment("# Message sent when player tries to throw ender pearl, but has delay")
public String pearlThrowBlockedDelayDuringCombat = "&cYou must wait {TIME} before next throw!";
}
@@ -0,0 +1,29 @@
package com.eternalcode.combat.fight.tagout;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.event.FightTagEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import java.util.UUID;
public class FightTagOutController implements Listener {
private final FightTagOutService tagOutService;
private final PluginConfig config;
public FightTagOutController(FightTagOutService tagOutService, PluginConfig config) {
this.tagOutService = tagOutService;
this.config = config;
}
@EventHandler
void onTagOut(FightTagEvent event) {
UUID uniqueId = event.getPlayer();
if (this.tagOutService.isTaggedOut(uniqueId)) {
event.cancel(this.config.messages.admin.adminTagOutCanceled);
}
}
}
@@ -0,0 +1,34 @@
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 {
private final Map<UUID, Instant> tagOuts = new HashMap<>();
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);
}
}
@@ -0,0 +1,94 @@
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);
}
}
@@ -0,0 +1,8 @@
package com.eternalcode.combat.notification;
public interface Notification {
String message();
NotificationType type();
}
@@ -0,0 +1,79 @@
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,9 +1,9 @@
package com.eternalcode.combat.notification;
public enum NotificationType {
ACTION_BAR,
CHAT,
ACTION_BAR,
TITLE,
SUBTITLE,
SUB_TITLE,
BOSS_BAR
}
@@ -0,0 +1,12 @@
package com.eternalcode.combat.notification.implementation;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationType;
public record ActionBarNotification(String message) implements Notification {
@Override
public NotificationType type() {
return NotificationType.ACTION_BAR;
}
}
@@ -0,0 +1,20 @@
package com.eternalcode.combat.notification.implementation;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationType;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
public record BossBarNotification(String message, float progress, BossBar.Color color, BossBar.Overlay overlay) implements Notification {
public BossBar create(Component name) {
float replacedProgress = this.progress < 0.0F ? BossBar.MAX_PROGRESS : this.progress;
return BossBar.bossBar(name, replacedProgress, this.color, this.overlay);
}
@Override
public NotificationType type() {
return NotificationType.BOSS_BAR;
}
}
@@ -0,0 +1,12 @@
package com.eternalcode.combat.notification.implementation;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationType;
public record ChatNotification(String message) implements Notification {
@Override
public NotificationType type() {
return NotificationType.CHAT;
}
}
@@ -0,0 +1,12 @@
package com.eternalcode.combat.notification.implementation;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationType;
public record SubTitleNotification(String message) implements Notification {
@Override
public NotificationType type() {
return NotificationType.SUB_TITLE;
}
}
@@ -0,0 +1,12 @@
package com.eternalcode.combat.notification.implementation;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationType;
public record TitleNotification(String message) implements Notification {
@Override
public NotificationType type() {
return NotificationType.TITLE;
}
}
@@ -0,0 +1,64 @@
package com.eternalcode.combat.notification.serializer;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationType;
import com.eternalcode.combat.notification.implementation.ActionBarNotification;
import com.eternalcode.combat.notification.implementation.BossBarNotification;
import com.eternalcode.combat.notification.implementation.ChatNotification;
import com.eternalcode.combat.notification.implementation.SubTitleNotification;
import com.eternalcode.combat.notification.implementation.TitleNotification;
import eu.okaeri.configs.schema.GenericsDeclaration;
import eu.okaeri.configs.serdes.DeserializationData;
import eu.okaeri.configs.serdes.ObjectSerializer;
import eu.okaeri.configs.serdes.SerializationData;
import net.kyori.adventure.bossbar.BossBar;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
public class NotificationSerializer implements ObjectSerializer<Notification> {
@Override
public boolean supports(@NonNull Class<? super Notification> type) {
return Notification.class.isAssignableFrom(type);
}
@Override
public void serialize(@NonNull Notification notification, @NonNull SerializationData data, @NonNull GenericsDeclaration generics) {
data.add("type", notification.type(), NotificationType.class);
data.add("message", notification.message(), String.class);
if (notification instanceof BossBarNotification bossBarNotification) {
data.add("progress", bossBarNotification.progress(), float.class);
data.add("color", bossBarNotification.color(), BossBar.Color.class);
data.add("overlay", bossBarNotification.overlay(), BossBar.Overlay.class);
}
}
@Override
public Notification deserialize(@NonNull DeserializationData data, @NonNull GenericsDeclaration generics) {
NotificationType type = data.get("type", NotificationType.class);
String message = data.get("message", String.class);
return switch (type) {
case CHAT -> new ChatNotification(message);
case ACTION_BAR -> new ActionBarNotification(message);
case TITLE -> new TitleNotification(message);
case SUB_TITLE -> new SubTitleNotification(message);
case BOSS_BAR -> {
float progress = Optional.ofNullable(data.get("progress", float.class))
.orElse(BossBar.MAX_PROGRESS);
BossBar.Color color = Optional.ofNullable(data.get("color", BossBar.Color.class))
.orElse(BossBar.Color.RED);
BossBar.Overlay overlay = Optional.ofNullable(data.get("overlay", BossBar.Overlay.class))
.orElse(BossBar.Overlay.PROGRESS);
yield new BossBarNotification(message, progress, color, overlay);
}
};
}
}
@@ -0,0 +1,37 @@
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());
}
@Override
public Region getRegion(Location location) {
return null;
}
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,6 @@
package com.eternalcode.combat.region;
import org.bukkit.Location;
public record Region(Location getMin, Location getMax) {
}
@@ -0,0 +1,60 @@
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 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.util.Vector;
public class RegionController implements Listener {
private final NotificationAnnouncer announcer;
private final RegionProvider regionProvider;
private final FightManager fightManager;
private final PluginConfig pluginConfig;
public RegionController(NotificationAnnouncer announcer, RegionProvider regionProvider, FightManager fightManager, PluginConfig pluginConfig) {
this.announcer = announcer;
this.regionProvider = regionProvider;
this.fightManager = fightManager;
this.pluginConfig = pluginConfig;
}
@EventHandler
void onPlayerMove(PlayerMoveEvent event) {
Player player = event.getPlayer();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
Location locationTo = event.getTo();
int xTo = locationTo.getBlockX();
int yTo = locationTo.getBlockY();
int zTo = locationTo.getBlockZ();
Location locationFrom = event.getFrom();
int xFrom = locationFrom.getBlockX();
int yFrom = locationFrom.getBlockY();
int zFrom = locationFrom.getBlockZ();
if (xTo != xFrom || yTo != yFrom || zTo != zFrom) {
if (!this.regionProvider.isInRegion(locationTo)) {
return;
}
Vector direction = locationFrom.toVector().subtract(locationTo.toVector()).normalize();
Vector knockback = direction.multiply(this.pluginConfig.settings.blockedRegionKnockMultiplier);
player.setVelocity(knockback);
this.announcer.sendMessage(player, this.pluginConfig.messages.cantEnterOnRegion);
event.setCancelled(true);
}
}
}
@@ -0,0 +1,10 @@
package com.eternalcode.combat.region;
import org.bukkit.Location;
public interface RegionProvider {
boolean isInRegion(Location location);
Region getRegion(Location location);
}
@@ -0,0 +1,62 @@
package com.eternalcode.combat.region;
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.regions.ProtectedRegion;
import com.sk89q.worldguard.protection.regions.RegionContainer;
import com.sk89q.worldguard.protection.regions.RegionQuery;
import org.bukkit.Location;
import org.bukkit.World;
import java.util.List;
import java.util.Optional;
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));
}
@Override
public Region getRegion(Location location) {
ApplicableRegionSet applicableRegions = getProtectedRegions(location);
Optional<ProtectedRegion> first = applicableRegions.getRegions().stream().findFirst();
if (first.isEmpty()) {
return null;
}
ProtectedRegion protectedRegion = first.get();
BlockVector3 minimumPoint = protectedRegion.getMinimumPoint();
BlockVector3 maximumPoint = protectedRegion.getMaximumPoint();
World world = location.getWorld();
Location adaptMin = BukkitAdapter.adapt(world, minimumPoint);
Location adaptMax = BukkitAdapter.adapt(world, maximumPoint);
return new Region(adaptMin, adaptMax);
}
private boolean isInCombatRegion(Location location, String regionName) {
ApplicableRegionSet applicableRegions = getProtectedRegions(location);
return applicableRegions.getRegions().stream().anyMatch(region -> region.getId().equalsIgnoreCase(regionName));
}
private static ApplicableRegionSet getProtectedRegions(Location location) {
RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer();
RegionQuery regionQuery = regionContainer.createQuery();
return regionQuery.getApplicableRegions(BukkitAdapter.adapt(location));
}
}
@@ -32,7 +32,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.receiveUpdates) {
if (!player.hasPermission("eternalcombat.receiveupdates") || !this.pluginConfig.settings.shouldReceivePluginUpdates) {
return;
}
@@ -0,0 +1,71 @@
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);
}
}
@@ -0,0 +1,35 @@
package com.eternalcode.combat.util;
import java.util.Collection;
import java.util.function.ToIntFunction;
public class MathUtil {
private MathUtil() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
public static double getCountFromPercentage(int percentage, int totalCount) {
return percentage / 100D * totalCount;
}
public static double getCountPercentage(double value, double base) {
return value / base * 100;
}
public static int getRoundedCountPercentage(double value, double base) {
return (int) Math.round(getCountPercentage(value, base));
}
public static int getRoundedCountFromPercentage(int percentage, int baseNumber) {
return (int) Math.round(getCountFromPercentage(percentage, baseNumber));
}
public static <T> int sum(Collection<T> collection, ToIntFunction<? super T> mapper) {
return collection.stream().mapToInt(mapper).sum();
}
public static int clamp(int value, int min, int max) {
return Math.max(min, Math.min(max, value));
}
}
@@ -0,0 +1,22 @@
package com.eternalcode.combat.util;
import org.bukkit.inventory.ItemStack;
import java.util.Collections;
import java.util.List;
public record RemoveItemResult(
List<ItemStack> restItems,
List<ItemStack> removedItems
) {
public List<ItemStack> restItems() {
return Collections.unmodifiableList(this.restItems);
}
@Override
public List<ItemStack> removedItems() {
return Collections.unmodifiableList(this.removedItems);
}
}
+60
View File
@@ -0,0 +1,60 @@
plugins {
id("eternalcombat.java")
id("net.minecrell.plugin-yml.bukkit")
id("com.github.johnrengelman.shadow")
id("xyz.jpenilla.run-paper")
}
dependencies {
implementation(project(":eternalcombat-api"))
}
bukkit {
main = "com.eternalcode.combat.CombatPlugin"
author = "EternalCodeTeam"
apiVersion = "1.13"
prefix = "EternalCombat"
name = "EternalCombat"
softDepend = listOf("WorldGuard")
version = "${project.version}"
}
tasks {
runServer {
minecraftVersion("1.19.4")
}
}
tasks.shadowJar {
archiveFileName.set("EternalCombat v${project.version}.jar")
dependsOn("checkstyleMain")
dependsOn("checkstyleTest")
dependsOn("test")
exclude(
"org/intellij/lang/annotations/**",
"org/jetbrains/annotations/**",
"META-INF/**",
"kotlin/**",
"javax/**"
)
val prefix = "com.eternalcode.combat.libs"
listOf(
"panda.std",
"panda.utilities",
"org.panda-lang",
"eu.okaeri",
"net.kyori",
"org.bstats",
"dev.rollczi.litecommands",
"com.eternalcode.gitcheck",
"org.json.simple",
"org.apache.commons",
"javassist"
).forEach { pack ->
relocate(pack, "$prefix.$pack")
}
}
@@ -0,0 +1,203 @@
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.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.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.FightTask;
import com.eternalcode.combat.fight.bossbar.FightBossBarService;
import com.eternalcode.combat.fight.effect.FightEffectService;
import com.eternalcode.combat.fight.pearl.FightPearlController;
import com.eternalcode.combat.fight.pearl.FightPearlManager;
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.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.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 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;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
private FightManager fightManager;
private FightPearlManager fightPearlManager;
private FightTagOutService fightTagOutService;
private FightEffectService fightEffectService;
private DropManager dropManager;
private DropKeepInventoryManager dropKeepInventoryManager;
private RegionProvider regionProvider;
private PluginConfig pluginConfig;
private AudienceProvider audienceProvider;
private LiteCommands<CommandSender> liteCommands;
@Override
public void onEnable() {
Stopwatch started = Stopwatch.createStarted();
Server server = this.getServer();
File dataFolder = this.getDataFolder();
ConfigBackupService backupService = new ConfigBackupService(dataFolder);
ConfigService configService = new ConfigService(backupService);
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();
UpdaterService updaterService = new UpdaterService(this.getDescription());
this.audienceProvider = BukkitAudiences.create(this);
MiniMessage miniMessage = MiniMessage.builder()
.postProcessor(new LegacyColorProcessor())
.build();
FightBossBarService fightBossBarService = new FightBossBarService(this.pluginConfig, this.audienceProvider, miniMessage);
BridgeService bridgeService = new BridgeService(this.pluginConfig, server.getPluginManager(), this.getLogger());
bridgeService.init();
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))
.invalidUsageHandler(new InvalidUsage(this.pluginConfig, notificationAnnouncer))
.permissionHandler(new PermissionMessage(this.pluginConfig, notificationAnnouncer))
.commandInstance(new CombatCommand(this.fightManager, configService, notificationAnnouncer, this.pluginConfig))
.commandInstance(new TagOutCommand(this.fightTagOutService, notificationAnnouncer, this.pluginConfig))
.register();
FightTask fightTask = new FightTask(server, this.pluginConfig, this.fightManager, fightBossBarService, notificationAnnouncer);
this.getServer().getScheduler().runTaskTimer(this, fightTask, 20L, 20L);
new Metrics(this, 17803);
Stream.of(
new PercentDropModifier(this.pluginConfig.dropSettings),
new PlayersHealthDropModifier(this.pluginConfig.dropSettings)
).forEach(this.dropManager::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 FightActionBlockerController(this.fightManager, notificationAnnouncer, this.pluginConfig),
new FightPearlController(this.pluginConfig.pearl, notificationAnnouncer, this.fightManager, this.fightPearlManager),
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())
).forEach(listener -> this.getServer().getPluginManager().registerEvents(listener, this));
EternalCombatProvider.initialize(this);
long millis = started.elapsed(TimeUnit.MILLISECONDS);
this.getLogger().info("Successfully loaded EternalCombat in " + millis + "ms");
}
@Override
public void onDisable() {
EternalCombatProvider.deinitialize();
if (this.liteCommands != null) {
this.liteCommands.getPlatform().unregisterAll();
}
if (this.audienceProvider != null) {
this.audienceProvider.close();
}
this.fightManager.untagAll();
}
@Override
public FightManager getFightManager() {
return this.fightManager;
}
@Override
public RegionProvider getRegionProvider() {
return this.regionProvider;
}
@Override
public FightPearlManager getFightPearlManager() {
return this.fightPearlManager;
}
@Override
public FightTagOutService getFightTagOutService() {
return this.fightTagOutService;
}
@Override
public FightEffectService getFightEffectService() {
return this.fightEffectService;
}
@Override
public DropManager getDropManager() {
return this.dropManager;
}
@Override
public DropKeepInventoryManager getDropKeepInventoryManager() {
return this.dropKeepInventoryManager;
}
@Override
public PluginConfig getPluginConfig() {
return this.pluginConfig;
}
}
+48
View File
@@ -0,0 +1,48 @@
[versions]
spigotApi = "1.20.2-R0.1-SNAPSHOT"
adventurePlatformBukkit = "4.3.1"
adventureTextMinimessage = "4.14.0"
liteCommands = "2.8.9"
okaeriConfigsYamlBukkit = "5.0.0-beta.5"
okaeriConfigsSerdesCommons = "5.0.0-beta.5"
okaeriConfigsSerdesBukkit = "5.0.0-beta.5"
pandaUtilities = "0.5.2-alpha"
gitCheck = "1.0.0"
apacheCommons = "2.14.0"
bStatsBukkit = "3.0.2"
worldGuardBukkit = "7.0.9"
junitJupiterApi = "5.10.0"
junitJupiterParams = "5.10.0"
junitJupiterEngine = "5.10.0"
[libraries]
spigotApi = { group = "org.spigotmc", name = "spigot-api", version.ref = "spigotApi" }
adventurePlatformBukkit = { group = "net.kyori", name = "adventure-platform-bukkit", version.ref = "adventurePlatformBukkit" }
adventureTextMinimessage = { group = "net.kyori", name = "adventure-text-minimessage", version.ref = "adventureTextMinimessage" }
liteCommands = { group = "dev.rollczi.litecommands", name = "bukkit-adventure", version.ref = "liteCommands" }
okaeriConfigsYamlBukkit = { group = "eu.okaeri", name = "okaeri-configs-yaml-bukkit", version.ref = "okaeriConfigsYamlBukkit" }
okaeriConfigsSerdesCommons = { group = "eu.okaeri", name = "okaeri-configs-serdes-commons", version.ref = "okaeriConfigsSerdesCommons" }
okaeriConfigsSerdesBukkit = { group = "eu.okaeri", name = "okaeri-configs-serdes-bukkit", version.ref = "okaeriConfigsSerdesBukkit" }
pandaUtilities = { group = "org.panda-lang", name = "panda-utilities", version.ref = "pandaUtilities" }
gitCheck = { group = "com.eternalcode", name = "gitcheck", version.ref = "gitCheck" }
apacheCommons = { group = "commons-io", name = "commons-io", version.ref = "apacheCommons" }
bStatsBukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bStatsBukkit" }
worldGuardBukkit = { group = "com.sk89q.worldguard", name = "worldguard-bukkit", version.ref = "worldGuardBukkit" }
jUnitJupiterApi = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junitJupiterApi" }
jUnitJupiterParams = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junitJupiterParams" }
jUnitJupiterEngine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junitJupiterEngine" }
Binary file not shown.
+2 -1
View File
@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+17 -12
View File
@@ -83,10 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,10 +131,13 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
+2
View File
@@ -1 +1,3 @@
rootProject.name = "EternalCombat"
include("eternalcombat-api")
include("eternalcombat-plugin")
@@ -1,120 +0,0 @@
package com.eternalcode.combat;
import com.eternalcode.combat.config.ConfigManager;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
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.Server;
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 ConfigManager configManager;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public CombatCommand(FightManager fightManager, ConfigManager configManager, NotificationAnnouncer announcer, PluginConfig config) {
this.fightManager = fightManager;
this.configManager = configManager;
this.announcer = announcer;
this.config = config;
}
@Execute(route = "status", required = 1)
@Permission("eternalcombat.status")
void status(Player player, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
PluginConfig.Messages messages = this.config.messages;
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName());
this.announcer.sendMessage(player, this.fightManager.isInCombat(targetUniqueId)
? formatter.format(messages.inCombat)
: formatter.format(messages.notInCombat));
}
@Execute(route = "tag", required = 1)
@Permission("eternalcombat.tag")
void tag(Player player, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
Duration time = this.config.settings.combatLogTime;
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName());
this.fightManager.tag(targetUniqueId, time);
String format = formatter.format(this.config.messages.adminTagPlayer);
this.announcer.sendMessage(player, format);
}
@Execute(route = "tag", required = 2)
@Permission("eternalcombat.tag")
public void tagMultiple(Player player, @Arg Player firstTarget, @Arg Player secondTarget) {
Duration combatTime = this.config.settings.combatLogTime;
PluginConfig.Messages messages = this.config.messages;
UUID firstTargetUniqueId = firstTarget.getUniqueId();
UUID secondTargetUniqueId = secondTarget.getUniqueId();
UUID playerUniqueId = player.getUniqueId();
if (playerUniqueId.equals(firstTargetUniqueId) || playerUniqueId.equals(secondTargetUniqueId)) {
this.announcer.sendMessage(player, messages.cantTagSelf);
return;
}
this.fightManager.tag(firstTargetUniqueId, combatTime);
this.fightManager.tag(secondTargetUniqueId, combatTime);
Formatter formatter = new Formatter()
.register("{FIRST_PLAYER}", firstTarget.getName())
.register("{SECOND_PLAYER}", secondTarget.getName());
String format = formatter.format(messages.adminTagPlayerMultiple);
this.announcer.sendMessage(player, format);
this.announcer.sendMessage(firstTarget, messages.tagPlayer);
this.announcer.sendMessage(secondTarget, messages.tagPlayer);
}
@Execute(route = "untag", required = 1)
@Permission("eternalcombat.untag")
void untag(Player player, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
if (!this.fightManager.isInCombat(targetUniqueId)) {
this.announcer.sendMessage(player, this.config.messages.playerIsNoInCombat);
return;
}
this.announcer.sendMessage(target, this.config.messages.unTagPlayer);
this.fightManager.untag(targetUniqueId);
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName());
String format = formatter.format(this.config.messages.adminUnTagPlayer);
this.announcer.sendMessage(player, format);
}
@Async
@Execute(route = "reload")
@Permission("eternalcombat.reload")
void execute(Player player) {
this.configManager.reload();
this.announcer.sendMessage(player, this.config.messages.reload);
}
}
@@ -1,95 +0,0 @@
package com.eternalcode.combat;
import com.eternalcode.combat.command.InvalidUsage;
import com.eternalcode.combat.command.PermissionMessage;
import com.eternalcode.combat.config.ConfigManager;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTask;
import com.eternalcode.combat.fight.controller.FightActionBlockerController;
import com.eternalcode.combat.fight.controller.FightTagController;
import com.eternalcode.combat.fight.controller.FightUnTagController;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.updater.UpdaterNotificationController;
import com.eternalcode.combat.updater.UpdaterService;
import com.eternalcode.combat.util.legacy.LegacyColorProcessor;
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 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.util.concurrent.TimeUnit;
import java.util.stream.Stream;
public final class CombatPlugin extends JavaPlugin {
private FightManager fightManager;
private AudienceProvider audienceProvider;
private LiteCommands<CommandSender> liteCommands;
@Override
public void onEnable() {
Stopwatch started = Stopwatch.createStarted();
Server server = this.getServer();
ConfigManager configManager = new ConfigManager(this.getDataFolder());
PluginConfig pluginConfig = configManager.load(new PluginConfig());
this.fightManager = new FightManager();
UpdaterService updaterService = new UpdaterService(this.getDescription());
this.audienceProvider = BukkitAudiences.create(this);
MiniMessage miniMessage = MiniMessage.builder()
.postProcessor(new LegacyColorProcessor())
.build();
NotificationAnnouncer notificationAnnouncer = new NotificationAnnouncer(this.audienceProvider, miniMessage);
this.liteCommands = LiteBukkitAdventurePlatformFactory.builder(server, "eternalcombat", this.audienceProvider)
.argument(Player.class, new BukkitPlayerArgument<>(this.getServer(), pluginConfig.messages.cantFindPlayer))
.contextualBind(Player.class, new BukkitOnlyPlayerContextual<>(pluginConfig.messages.onlyForPlayers))
.invalidUsageHandler(new InvalidUsage(pluginConfig, notificationAnnouncer))
.permissionHandler(new PermissionMessage(pluginConfig, notificationAnnouncer))
.commandInstance(new CombatCommand(this.fightManager, configManager, notificationAnnouncer, pluginConfig))
.register();
FightTask fightTask = new FightTask(this.fightManager, pluginConfig, server, notificationAnnouncer);
this.getServer().getScheduler().runTaskTimerAsynchronously(this, fightTask, 20L, 20L);
new Metrics(this, 17803);
Stream.of(
new FightTagController(this.fightManager, pluginConfig, notificationAnnouncer),
new FightUnTagController(this.fightManager, pluginConfig, notificationAnnouncer),
new FightActionBlockerController(this.fightManager, notificationAnnouncer, pluginConfig),
new UpdaterNotificationController(updaterService, pluginConfig, this.audienceProvider, miniMessage)
).forEach(listener -> this.getServer().getPluginManager().registerEvents(listener, this));
long millis = started.elapsed(TimeUnit.MILLISECONDS);
this.getLogger().info("Successfully loaded EternalCombat in " + millis + "ms");
}
@Override
public void onDisable() {
if (this.liteCommands != null) {
this.liteCommands.getPlatform().unregisterAll();
}
if (this.audienceProvider != null) {
this.audienceProvider.close();
}
this.fightManager.untagAll();
}
}
@@ -1,45 +0,0 @@
package com.eternalcode.combat.config;
import com.eternalcode.combat.config.composer.DurationComposer;
import net.dzikoysk.cdn.Cdn;
import net.dzikoysk.cdn.CdnFactory;
import java.io.File;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
public class ConfigManager {
private final Cdn cdn = CdnFactory
.createYamlLike()
.getSettings()
.withComposer(Duration.class, new DurationComposer())
.build();
private final Set<ReloadableConfig> configs = new HashSet<>();
private final File dataFolder;
public ConfigManager(File dataFolder) {
this.dataFolder = dataFolder;
}
public <T extends ReloadableConfig> T load(T config) {
this.cdn.load(config.resource(this.dataFolder), config)
.orThrow(RuntimeException::new);
this.cdn.render(config, config.resource(this.dataFolder))
.orThrow(RuntimeException::new);
this.configs.add(config);
return config;
}
public void reload() {
for (ReloadableConfig config : this.configs) {
this.load(config);
}
}
}
@@ -1,12 +0,0 @@
package com.eternalcode.combat.config;
import net.dzikoysk.cdn.source.Resource;
import java.io.File;
public interface ReloadableConfig {
Resource resource(File folder);
}
@@ -1,21 +0,0 @@
package com.eternalcode.combat.config.composer;
import panda.std.Result;
import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.util.Locale;
public class DurationComposer implements SimpleComposer<Duration> {
@Override
public Result<Duration, Exception> deserialize(String source) {
return Result.supplyThrowing(DateTimeParseException.class, () -> Duration.parse("PT" + source.toUpperCase(Locale.ROOT)));
}
@Override
public Result<String, Exception> serialize(Duration entity) {
return Result.ok(entity.toString().substring(2).toLowerCase(Locale.ROOT));
}
}
@@ -1,9 +0,0 @@
package com.eternalcode.combat.config.composer;
import net.dzikoysk.cdn.serdes.Composer;
import net.dzikoysk.cdn.serdes.SimpleDeserializer;
import net.dzikoysk.cdn.serdes.SimpleSerializer;
public interface SimpleComposer<T> extends Composer<T>, SimpleDeserializer<T>, SimpleSerializer<T> {
}
@@ -1,91 +0,0 @@
package com.eternalcode.combat.config.implementation;
import com.eternalcode.combat.config.ReloadableConfig;
import com.eternalcode.combat.notification.NotificationType;
import com.google.common.collect.ImmutableList;
import net.dzikoysk.cdn.entity.Contextual;
import net.dzikoysk.cdn.entity.Description;
import net.dzikoysk.cdn.source.Resource;
import net.dzikoysk.cdn.source.Source;
import org.bukkit.event.entity.EntityDamageEvent;
import java.io.File;
import java.time.Duration;
import java.util.List;
public class PluginConfig implements ReloadableConfig {
@Description("# Do you want to change the plugin messages?")
public Messages messages = new Messages();
@Description({ " ", "# Do you want to change the plugin settings?" })
public Settings settings = new Settings();
@Override
public Resource resource(File folder) {
return Source.of(folder, "config.yml");
}
@Contextual
public static class Settings {
@Description("# Whether the player after entering the server should receive information about the new version of the plugin?")
public boolean receiveUpdates = true;
@Description({ " ", "# The length of time the combat is to last" })
public Duration combatLogTime = Duration.ofSeconds(20);
@Description({ " ", "# Combat log notification type, available types: ACTION_BAR, CHAT, TITLE, SUBTITLE" })
public NotificationType combatNotificationType = NotificationType.ACTION_BAR;
@Description({ " ", "# Blocked commands that the player will not be able to use during combat" })
public List<String> blockedCommands = new ImmutableList.Builder<String>()
.add("gamemode")
.add("tp")
.build();
@Description({ " ", "# Block the opening of inventory?" })
public boolean blockingInventories = true;
@Description({ " ", "# Whether to block the placement of blocks?" })
public boolean blockPlace = true;
@Description({ " ", "# From which level should place blocks be blocked?" })
public int blockPlaceLevel = 40;
@Description({ " ", "# Should the option below be enabled?" })
public boolean enableDamageCauses = true;
@Description({ " ", "# After what type of damage the player should get a combat log?",
"# You can find a list of all stocks here: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html",
"# If you don't want the combatlog to be given to players for a certain damage type, simply remove it from this list" })
public List<EntityDamageEvent.DamageCause> damageCauses = new ImmutableList.Builder<EntityDamageEvent.DamageCause>()
.add(EntityDamageEvent.DamageCause.LAVA)
.add(EntityDamageEvent.DamageCause.CONTACT)
.add(EntityDamageEvent.DamageCause.FIRE)
.add(EntityDamageEvent.DamageCause.FIRE_TICK)
.build();
}
@Contextual
public static class Messages {
public String onlyForPlayers = "&cThis command is only available to players!";
public String noPermission = "&cYou don't have permission to perform this command!";
public String cantFindPlayer = "&cThe specified player could not be found!";
public String playerIsNoInCombat = "&cThis player is not in combat!";
public String combatFormat = "&dCombat ends in: &f{TIME}";
public String tagPlayer = "&cYou are in combat, do not leave the server!";
public String unTagPlayer = "&aYou are no longer in combat! You can safely leave the server.";
public String cantUseCommand = "&cUsing this command during combat is prohibited!";
public String adminTagPlayer = "&7You have tagged &e{PLAYER}";
public String adminTagPlayerMultiple = "&7You have tagged &e{FIRST_PLAYER}&7 and &e{SECOND_PLAYER}&7.";
public String adminUnTagPlayer = "&7You have removed &e{PLAYER}&7 from the fight.";
public String playerLoggedInCombat = "&c{PLAYER} logged off during the fight!";
public String invalidUsage = "&7Correct usage: &e{USAGE}.";
public String inventoryBlocked = "&cYou cannot open this inventory during combat!";
public String blockPlaceBlocked = "&cYou cannot place blocks during combat below 40 blocks!";
public String inCombat = "&c{PLAYER} is in the middle of a fight!";
public String notInCombat = "&a{PLAYER} is not in combat";
public String reload = "&aConfiguration has been successfully reloaded!";
public String cantTagSelf = "&cYou cannot tag yourself!";
}
}
@@ -1,51 +0,0 @@
package com.eternalcode.combat.fight;
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;
public class FightManager {
private final Map<UUID, FightTag> fights = new ConcurrentHashMap<>();
public boolean isInCombat(UUID player) {
if (!this.fights.containsKey(player)) {
return false;
}
FightTag fightTag = this.fights.get(player);
if (fightTag.isExpired()) {
this.fights.remove(player);
return false;
}
return true;
}
public void untag(UUID player) {
this.fights.remove(player);
}
public void tag(UUID target, Duration delay) {
Instant now = Instant.now();
Instant endOfCombatLog = now.plus(delay);
FightTag fightTag = new FightTag(target, endOfCombatLog);
this.fights.put(target, fightTag);
}
public Collection<FightTag> getFights() {
return Collections.unmodifiableCollection(this.fights.values());
}
public void untagAll() {
this.fights.clear();
}
}
@@ -1,51 +0,0 @@
package com.eternalcode.combat.fight;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.util.DurationUtil;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
import java.time.Duration;
public class FightTask implements Runnable {
private final FightManager fightManager;
private final PluginConfig config;
private final Server server;
private final NotificationAnnouncer announcer;
public FightTask(FightManager fightManager, PluginConfig config, Server server, NotificationAnnouncer announcer) {
this.fightManager = fightManager;
this.config = config;
this.server = server;
this.announcer = announcer;
}
@Override
public void run() {
for (FightTag fightTag : this.fightManager.getFights()) {
Player player = this.server.getPlayer(fightTag.getTaggedPlayer());
if (player == null) {
return;
}
if (!fightTag.isExpired()) {
Duration remaining = fightTag.getRemainingDuration();
Formatter formatter = new Formatter()
.register("{TIME}", DurationUtil.format(remaining));
String format = formatter.format(this.config.messages.combatFormat);
this.announcer.send(player, this.config.settings.combatNotificationType, format);
continue;
}
this.fightManager.untag(fightTag.getTaggedPlayer());
this.announcer.send(player, this.config.settings.combatNotificationType, this.config.messages.unTagPlayer);
}
}
}
@@ -1,92 +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.notification.NotificationAnnouncer;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import java.util.UUID;
public class FightActionBlockerController implements Listener {
private final FightManager fightManager;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public FightActionBlockerController(FightManager fightManager, NotificationAnnouncer announcer, PluginConfig config) {
this.fightManager = fightManager;
this.announcer = announcer;
this.config = config;
}
@EventHandler
void onPlace(BlockPlaceEvent event) {
if (!this.config.settings.blockPlace) {
return;
}
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
Block block = event.getBlock();
int level = block.getY();
if (level > this.config.settings.blockPlaceLevel) {
return;
}
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.blockPlaceBlocked);
}
@EventHandler
void onOpenInventory(InventoryOpenEvent event) {
if (!this.config.settings.blockingInventories) {
return;
}
Player player = (Player) event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.inventoryBlocked);
}
@EventHandler
public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
Player player = event.getPlayer();
UUID playerUniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(playerUniqueId)) {
return;
}
String command = event.getMessage().replace("/", "");
for (String blockedCommand : this.config.settings.blockedCommands) {
if (!command.startsWith(blockedCommand)) {
continue;
}
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.cantUseCommand);
}
}
}
@@ -1,61 +0,0 @@
package com.eternalcode.combat.notification;
import net.kyori.adventure.audience.Audience;
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 java.util.Map;
import java.util.function.BiConsumer;
public final class NotificationAnnouncer {
private final static Map<NotificationType, BiConsumer<Audience, Component>> NOTIFICATION_HANDLERS = Map.of(
NotificationType.CHAT, Audience::sendMessage,
NotificationType.ACTION_BAR, Audience::sendActionBar,
NotificationType.TITLE, (audience, component) -> audience.showTitle(Title.title(component, Component.empty())),
NotificationType.SUBTITLE, (audience, component) -> audience.showTitle(Title.title(Component.empty(), component))
);
private final AudienceProvider audienceProvider;
private final MiniMessage miniMessage;
public NotificationAnnouncer(AudienceProvider audienceProvider, MiniMessage miniMessage) {
this.audienceProvider = audienceProvider;
this.miniMessage = miniMessage;
}
public void send(CommandSender commandSender, NotificationType notificationType, String text) {
Audience audience = this.audience(commandSender);
Component component = this.miniMessage.deserialize(text);
BiConsumer<Audience, Component> handler = NOTIFICATION_HANDLERS.get(notificationType);
if (handler == null) {
return;
}
handler.accept(audience, component);
}
public void sendMessage(CommandSender commandSender, String text) {
Audience audience = this.audience(commandSender);
audience.sendMessage(this.miniMessage.deserialize(text));
}
public void broadcast(CommandSender commandSender, String text) {
this.audienceProvider.all().sendMessage(this.miniMessage.deserialize(text));
}
private Audience audience(CommandSender sender) {
if (sender instanceof Player player) {
return this.audienceProvider.player(player.getUniqueId());
}
return this.audienceProvider.console();
}
}
@@ -1,54 +0,0 @@
package com.eternalcode.combat.config.composer;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import panda.std.Result;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class DurationComposerTest {
DurationComposer composer = new DurationComposer();
@CsvSource({
"5400, 1h30m",
"3600, 1h",
"1800, 30m",
"60, 1m",
"1, 1s",
"5401, 1h30m1s",
"3601, 1h1s",
"61, 1m1s",
"3661, 1h1m1s",
})
@ParameterizedTest
void testDeserialize(long expected, String source) {
Result<Duration, Exception> deserialize = this.composer.deserialize(source);
assertTrue(deserialize.isOk());
assertEquals(expected, deserialize.get().getSeconds());
}
@CsvSource({
"1h30m, 5400",
"1h, 3600",
"30m, 1800",
"1m, 60",
"1s, 1",
"1h30m1s, 5401",
"1h1s, 3601",
"1m1s, 61",
"1h1m1s, 3661",
})
@ParameterizedTest
void testSerialize(String expected, long source) {
Result<String, Exception> serialize = this.composer.serialize(Duration.ofSeconds(source));
assertTrue(serialize.isOk());
assertEquals(expected, serialize.get());
}
}