Compare commits

..
Author SHA1 Message Date
Martin Sulikowski f276a83f50 Experiments 2023-11-13 22:32:37 +01:00
204 changed files with 3190 additions and 7286 deletions

No files matched your search

+1 -2
View File
@@ -4,5 +4,4 @@
gradlew text eol=lf
*.bat text eol=crlf
*.jar binary
.gitea/CODEOWNERS linguist-detectable=false
*.jar binary
-3
View File
@@ -1,3 +0,0 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+2
View File
@@ -0,0 +1,2 @@
* @EternalCodeTeam/eternalcodeteam-maintainers
/.github/CODEOWNERS @EternalCodeTeam/eternalcodeteam-maintainers
+128
View File
@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
https://discord.gg/FQ7jmGBd6c.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+36
View File
@@ -0,0 +1,36 @@
# Contributing to EternalCombat
We welcome all contributions to the EternalCombat project!
### How to Contribute
1. Fork the repository.
2. Create a new branch for your feature or bug fix.
3. Make your changes, and test them thoroughly.
4. Commit your changes and push them to your fork.
5. Submit a pull request to the main repository.
### Code of Conduct
We expect all contributors to follow our [code of conduct](CODE_OF_CONDUCT.md).
### Reporting Issues
If you find an issue with the plugin, please report it in
the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). Please provide as much information as
possible, including the version of Minecraft and the plugin you are using, as well as any error messages or logs.
### Compatibility
CombatLog has been tested on versions 1.17.1 - 1.19.3, but probably works on most versions of Minecraft. If you find
compatibility issues, please report them in the Issues tab. CombatLog uses java 11+, so you need to have it installed on
your server.
### Submitting a Pull Request
When submitting a pull request, please make sure to follow these guidelines:
- Make sure that your code adheres to the plugin's existing coding style.
- Test your changes thoroughly before submitting the pull request.
- If your pull request is related to an existing issue, please reference the issue in your pull request.
- Thank you for your contributions to the EternalCombat project!
+33
View File
@@ -0,0 +1,33 @@
---
name: Bug report
about: Create a bug report to help us improve EternalCombat <3
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Platform (please complete the following information):**
- Java Version: [e.g. JDK Temurin 17]
- Server software, ex. PaperMC 1.19.3 build #1
**Additional notes**
Add any other context about the problem here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for EternalCombat <3
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+1
View File
@@ -0,0 +1 @@
**Please describe the changes made by this PR and why they need to be merged:**
+43
View File
@@ -0,0 +1,43 @@
name: Java CI with Gradle
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@v3.3.2
with:
path: ~/.gradle/caches
key: >-
${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*',
'**/gradle-wrapper.properties') }}
restore-keys: '${{ runner.os }}-gradle-'
- name: Make gradlew executable
run: chmod +x gradlew
- name: Build with Gradle
uses: gradle/gradle-build-action@0bfe00a136db5e61ba3416b372542a65863a9fee
with:
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 }}
-1
View File
@@ -5,5 +5,4 @@
.settings
bin
build
buildSrc/.kotlin/
run
+14
View File
@@ -0,0 +1,14 @@
{
"scanSettings": {
"baseBranches": []
},
"checkRunSettings": {
"vulnerableCheckRunConclusionLevel": "failure",
"displayMode": "diff",
"useMendCheckNames": true
},
"issueSettings": {
"minSeverityLevel": "LOW",
"issueType": "DEPENDENCY"
}
}
+76 -23
View File
@@ -1,31 +1,84 @@
# EternalCombat-OG
<div align="center">
A [TrueOG Network](https://true-og.net) fork of [EternalCombat](https://github.com/EternalCodeTeam/EternalCombat)
by the EternalCode Team — a combat-tag / combat-logging plugin for Paper.
![](/assets/readme-banner.png)
## License & attribution
[![Supports Paper](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/supported/paper_vector.svg)](https://papermc.io)
[![Supports Spigot](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/supported/spigot_vector.svg)](https://spigotmc.org)
EternalCombat-OG is a derivative work of **EternalCombat** by the **EternalCode Team**, distributed
under the **Apache License 2.0**. The original copyright and license are retained; see the
[`LICENSE`](LICENSE) file. Upstream project: https://github.com/EternalCodeTeam/EternalCombat
[![Patreon](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/donate/patreon-plural_vector.svg)](https://www.patreon.com/eternalcode)
[![Website](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/documentation/website_vector.svg)](https://eternalcode.pl/)
[![Discord](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/social/discord-plural_vector.svg)](https://discord.gg/FQ7jmGBd6c)
## Changes from upstream
[![Gradle](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/built-with/gradle_vector.svg)](https://gradle.org/)
[![Java](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/built-with/java17_vector.svg)](https://www.java.com/)
</div>
This fork diverges from upstream EternalCombat in the following ways:
### Information
- Renamed the project and plugin to `EternalCombat-OG`.
- Fixed combat knockback flinging players across the map or into the sky near safe zones.
- Players in combat can move between the warzone and wilderness, but not into safe areas.
- Render the glass combat border on the true safe/unsafe boundary (e.g. between the warzone and spawn or the market) instead of a region's bounding box, so no wall shows between the warzone and wilderness or on the outer face of a large enclosing region.
- Mounted players are pushed along with their mount instead of being thrown off.
- Closed gaps that let combat-tagged players slip into shops and the market.
- Removed chat gradients.
- Added a command whitelist for "The Herobrine" minigames.
- Dropped Lands support.
- Removed the upstream GitHub Actions, plus build and dependency cleanup.
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.
## Requirements
## Building
Build EternalCombat using:
- Paper (Java 17+ toolchain)
- [PacketEvents](https://modrinth.com/plugin/packetevents) (required)
- WorldGuard (optional, for region-based combat restrictions)
`./gradlew shadowJar`
### Permissions for EternalCombat
- `eternalcombat.status` - Allows to check if the player is in combat `/combatlog status <player>`
- `eternalcombat.tag` - Allows to create fights between two
players `/combatlog tag <first_player> <optional_second_player>`
- `eternalcombat.untag` - Allows to remove player from fight `/combatlog untag <player>`
- `eternalcombat.reload` - Allows to reload plugin `/combatlog reload`
- `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
more information on how to contribute and our [code of conduct](./.github/CODE_OF_CONDUCT.md)
### Reporting Issues
If you find an issue with the plugin, please report it in
the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). Please provide as much information as
possible, including the version of Minecraft and the plugin you are using, as well as any error messages or logs
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 60 KiB

-48
View File
@@ -1,48 +0,0 @@
plugins {
java
eclipse
}
tasks.jar {
enabled = false
}
val pluginProject = project(":eternalcombat-plugin")
val pluginJar by tasks.registering(Copy::class) {
dependsOn(":eternalcombat-plugin:shadowJar")
from(pluginProject.provider {
pluginProject.layout.buildDirectory.file("libs/EternalCombat-OG-${pluginProject.version}.jar").get().asFile
})
into(layout.buildDirectory.dir("libs"))
}
tasks.assemble {
dependsOn(pluginJar)
}
allprojects {
group = "com.eternalcode"
version = "2.2.0"
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.eternalcode.pl/releases")
maven("https://storehouse.okaeri.eu/repository/maven-releases/")
maven("https://maven.reposilite.com/releases")
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
maven("https://repo.codemc.org/repository/maven-public/")
maven("https://maven.enginehub.org/repo/")
maven("https://jitpack.io")
}
}
subprojects {
apply(plugin = "java")
apply(plugin = "eclipse")
java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } }
tasks.withType<JavaCompile>().configureEach {
options.encoding = "UTF-8"
options.release.set(17)
}
}
+3 -20
View File
@@ -1,9 +1,6 @@
plugins {
`kotlin-dsl`
eclipse
}
repositories { gradlePluginPortal(); mavenCentral() }
kotlin { jvmToolchain(17) }
repositories {
mavenCentral()
@@ -11,22 +8,8 @@ repositories {
}
dependencies {
implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.4.1")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.20")
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:3.0.2")
}
sourceSets {
main {
java.setSrcDirs(emptyList<String>())
groovy.setSrcDirs(emptyList<String>())
resources.setSrcDirs(emptyList<String>())
}
test {
java.setSrcDirs(emptyList<String>())
kotlin.setSrcDirs(emptyList<String>())
groovy.setSrcDirs(emptyList<String>())
resources.setSrcDirs(emptyList<String>())
}
implementation("xyz.jpenilla:run-task:2.2.0")
}
-37
View File
@@ -1,37 +0,0 @@
object Versions {
const val SPIGOT_API = "1.17.1-R0.1-SNAPSHOT"
const val JUNIT_JUPITER_API = "6.0.3"
const val JUNIT_JUPITER_PARAMS = "6.0.3"
const val JUNIT_JUPITER_ENGINE = "6.0.3"
const val JETBRAINS_ANNOTATIONS = "26.1.0"
const val ETERNALCODE_COMMONS = "1.3.4"
const val MULTIFICATION = "1.2.4"
const val PACKETEVENTS = "2.11.2"
const val ADVENTURE_PLATFORM_BUKKIT = "4.4.1"
const val ADVENTURE_API = "4.26.1"
const val LITE_COMMANDS = "3.10.9"
const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.13"
const val OKAERI_CONFIGS_SERDES_BUKKIT = "5.0.13"
const val XSERIES = "13.6.0"
const val CAFFEINE = "3.2.3"
const val WORLD_GUARD_BUKKIT = "7.0.8"
const val PLACEHOLDER_API = "2.11.7"
const val PAPERLIB = "1.0.8"
const val WORLDEDIT = "3ISh7ADm" //cannot use numeric version bc of duplicated version on modrinth
const val WORLDGUARD = "7.0.8"
const val LUCKPERMS = "5.5.17"
}
@@ -1,19 +0,0 @@
plugins {
`java-library`
}
dependencies {
testImplementation("org.spigotmc:spigot-api:${Versions.SPIGOT_API}")
testImplementation("org.junit.jupiter:junit-jupiter-api:${Versions.JUNIT_JUPITER_API}")
testImplementation("org.junit.jupiter:junit-jupiter-params:${Versions.JUNIT_JUPITER_PARAMS}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${Versions.JUNIT_JUPITER_ENGINE}")
}
tasks.test {
useJUnitPlatform()
}
sourceSets.test {
java.setSrcDirs(listOf("test"))
resources.setSrcDirs(emptyList<String>())
}
@@ -1,16 +0,0 @@
plugins {
`java-library`
}
group = "com.eternalcode"
version = "2.5.0-SNAPSHOT"
tasks.compileJava {
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
options.encoding = "UTF-8"
options.release = 17
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}
@@ -1,34 +0,0 @@
plugins {
`java-library`
`maven-publish`
}
group = "com.eternalcode"
version = "2.5.0-SNAPSHOT"
java {
withSourcesJar()
withJavadocJar()
}
publishing {
publications {
create<MavenPublication>("maven") {
artifactId = "eternalcombat-api"
from(project.components["java"])
}
}
repositories {
mavenLocal()
maven {
name = "eternalcodeReleases"
url = uri("https://repo.eternalcode.pl/releases")
credentials {
username = System.getenv("ETERNAL_CODE_MAVEN_USERNAME")
password = System.getenv("ETERNAL_CODE_MAVEN_PASSWORD")
}
}
}
}
@@ -1,19 +0,0 @@
plugins {
`java-library`
}
repositories {
maven("https://repo.eternalcode.pl/releases")
maven("https://repo.eternalcode.pl/snapshots")
mavenCentral()
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://storehouse.okaeri.eu/repository/maven-public/")
maven("https://repo.panda-lang.org/releases")
maven("https://maven.enginehub.org/repo/")
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
maven("https://repo.codemc.io/repository/maven-releases/")
maven("https://jitpack.io/")
maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots/")
}
@@ -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"
}
+75 -6
View File
@@ -1,9 +1,78 @@
plugins { `java-library` }
plugins {
id("eternalcombat.java")
java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } }
dependencies {
compileOnly("io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT")
compileOnly("org.jetbrains:annotations:26.0.2")
`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);
}
}
@@ -1,12 +1,13 @@
package com.eternalcode.combat;
import com.eternalcode.combat.fight.drop.DropKeepInventoryService;
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.drop.DropService;
import com.eternalcode.combat.fight.effect.FightEffectService;
import com.eternalcode.combat.fight.pearl.FightPearlService;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.combat.fight.pearl.FightPearlManager;
import com.eternalcode.combat.fight.tagout.FightTagOutService;
import com.eternalcode.combat.region.RegionProvider;
public interface EternalCombatApi {
@@ -14,14 +15,16 @@ public interface EternalCombatApi {
RegionProvider getRegionProvider();
FightPearlService getFightPearlService();
FightPearlManager getFightPearlManager();
FightTagOutService getFightTagOutService();
FightEffectService getFightEffectService();
DropService getDropService();
DropManager getDropManager();
DropKeepInventoryService getDropKeepInventoryService();
DropKeepInventoryManager getDropKeepInventoryManager();
PluginConfig getPluginConfig();
}
@@ -4,10 +4,6 @@ public final class EternalCombatProvider {
private static EternalCombatApi ETERNAL_COMBAT_API;
static boolean isInitialized() {
return ETERNAL_COMBAT_API != null;
}
public static EternalCombatApi provide() {
if (ETERNAL_COMBAT_API == null) {
throw new IllegalStateException("EternalCombatApi has not been initialized yet!");
@@ -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;
}
}
@@ -0,0 +1,35 @@
package com.eternalcode.combat.command;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import dev.rollczi.litecommands.command.LiteInvocation;
import dev.rollczi.litecommands.handle.InvalidUsageHandler;
import dev.rollczi.litecommands.schematic.Schematic;
import org.bukkit.command.CommandSender;
import panda.utilities.text.Formatter;
import java.util.List;
public class InvalidUsage implements InvalidUsageHandler<CommandSender> {
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public InvalidUsage(PluginConfig config, NotificationAnnouncer announcer) {
this.config = config;
this.announcer = announcer;
}
@Override
public void handle(CommandSender commandSender, LiteInvocation invocation, Schematic scheme) {
List<String> schematics = scheme.getSchematics();
for (String schematic : schematics) {
Formatter formatter = new Formatter()
.register("{USAGE}", schematic);
this.announcer.sendMessage(commandSender, formatter.format(this.config.messages.invalidCommandUsage));
}
}
}
@@ -0,0 +1,34 @@
package com.eternalcode.combat.command;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import dev.rollczi.litecommands.command.LiteInvocation;
import dev.rollczi.litecommands.command.permission.RequiredPermissions;
import dev.rollczi.litecommands.handle.PermissionHandler;
import org.bukkit.command.CommandSender;
import panda.utilities.text.Formatter;
import panda.utilities.text.Joiner;
public class PermissionMessage implements PermissionHandler<CommandSender> {
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public PermissionMessage(PluginConfig config, NotificationAnnouncer announcer) {
this.config = config;
this.announcer = announcer;
}
@Override
public void handle(CommandSender commandSender, LiteInvocation invocation, RequiredPermissions requiredPermissions) {
String value = Joiner.on(", ")
.join(requiredPermissions.getPermissions())
.toString();
Formatter formatter = new Formatter()
.register("{PERMISSION}", value);
this.announcer.sendMessage(commandSender, formatter.format(this.config.messages.noPermission));
}
}
@@ -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!";
}
}
}
@@ -1,5 +1,7 @@
package com.eternalcode.combat.fight.drop;
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;
@@ -13,13 +15,18 @@ 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, List<ItemStack> droppedItems, 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;
}
@@ -36,6 +43,14 @@ public class Drop {
return this.killer;
}
public FightTag getFightTag() {
return this.fightTag;
}
public FightDeathCause getDeathCause() {
return this.deathCause;
}
public boolean hasKiller() {
return this.killer != null;
}
@@ -52,6 +67,8 @@ public class Drop {
private Player player;
private Player killer;
private FightTag fightTag;
private FightDeathCause deathCause;
private List<ItemStack> droppedItems;
private int droppedExp;
@@ -65,6 +82,16 @@ public class Drop {
return this;
}
public Builder fightTag(@NotNull FightTag fightTag) {
this.fightTag = fightTag;
return this;
}
public Builder deathCause(@NotNull FightDeathCause deathCause) {
this.deathCause = deathCause;
return this;
}
public Builder droppedItems(@NotNull List<ItemStack> droppedItems) {
this.droppedItems = new ArrayList<>(droppedItems);
return this;
@@ -79,6 +106,8 @@ public class Drop {
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);
}
}
}
@@ -1,4 +1,4 @@
package com.eternalcode.combat.fight.drop;
package com.eternalcode.combat.drop;
import org.bukkit.inventory.ItemStack;
@@ -9,26 +9,22 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
public class DropKeepInventoryServiceImpl implements DropKeepInventoryService {
public class DropKeepInventoryManager {
private final Map<UUID, List<ItemStack>> itemsToGiveAfterRespawn = new HashMap<>();
@Override
public void addItem(UUID uuid, ItemStack item) {
this.itemsToGiveAfterRespawn.computeIfAbsent(uuid, k -> new ArrayList<>()).add(item);
}
@Override
public void addItems(UUID uuid, List<ItemStack> item) {
item.forEach(i -> this.addItem(uuid, i));
}
@Override
public boolean hasItems(UUID uuid) {
return this.itemsToGiveAfterRespawn.containsKey(uuid);
}
@Override
public List<ItemStack> nextItems(UUID uuid) {
List<ItemStack> itemStacks = this.itemsToGiveAfterRespawn.remove(uuid);
@@ -1,17 +1,16 @@
package com.eternalcode.combat.fight.drop;
package com.eternalcode.combat.drop;
import java.util.HashMap;
import java.util.Map;
public class DropServiceImpl implements DropService {
public class DropManager {
private final Map<DropType, DropModifier> modifiers;
public DropServiceImpl() {
public DropManager() {
this.modifiers = new HashMap<>();
}
@Override
public void registerModifier(DropModifier dropModifier) {
DropType dropType = dropModifier.getDropType();
@@ -26,7 +25,6 @@ public class DropServiceImpl implements DropService {
this.modifiers.put(dropType, dropModifier);
}
@Override
public DropResult modify(DropType dropType, Drop drop) {
if (!this.modifiers.containsKey(dropType)) {
throw new RuntimeException("No drop modifier found for type '%s'".formatted(dropType.name()));
@@ -1,4 +1,4 @@
package com.eternalcode.combat.fight.drop;
package com.eternalcode.combat.drop;
public interface DropModifier {
@@ -1,4 +1,4 @@
package com.eternalcode.combat.fight.drop;
package com.eternalcode.combat.drop;
import org.bukkit.inventory.ItemStack;
@@ -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;
}
@@ -1,4 +1,4 @@
package com.eternalcode.combat.fight.drop;
package com.eternalcode.combat.drop;
public enum DropType {
@@ -1,10 +1,10 @@
package com.eternalcode.combat.fight.drop.impl;
package com.eternalcode.combat.drop.impl;
import com.eternalcode.combat.fight.drop.Drop;
import com.eternalcode.combat.fight.drop.DropModifier;
import com.eternalcode.combat.fight.drop.DropResult;
import com.eternalcode.combat.fight.drop.DropSettings;
import com.eternalcode.combat.fight.drop.DropType;
import com.eternalcode.combat.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;
@@ -1,12 +1,12 @@
package com.eternalcode.combat.fight.drop.impl;
package com.eternalcode.combat.drop.impl;
import com.eternalcode.combat.fight.drop.Drop;
import com.eternalcode.combat.fight.drop.DropModifier;
import com.eternalcode.combat.fight.drop.DropResult;
import com.eternalcode.combat.fight.drop.DropSettings;
import com.eternalcode.combat.fight.drop.DropType;
import com.eternalcode.combat.fight.logout.Logout;
import com.eternalcode.combat.fight.logout.LogoutService;
import com.eternalcode.combat.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;
@@ -15,16 +15,13 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.List;
import java.util.Optional;
public class PlayersHealthDropModifier implements DropModifier {
private final DropSettings settings;
private final LogoutService logoutService;
public PlayersHealthDropModifier(DropSettings settings, LogoutService logoutService) {
public PlayersHealthDropModifier(DropSettings settings) {
this.settings = settings;
this.logoutService = logoutService;
}
@Override
@@ -34,20 +31,17 @@ public class PlayersHealthDropModifier implements DropModifier {
@Override
public DropResult modifyDrop(Drop drop) {
Optional<Logout> logoutOptional = this.logoutService.nextLogoutFor(drop.getPlayer().getUniqueId());
if (logoutOptional.isEmpty()) {
if (drop.getDeathCause() != FightDeathCause.ESCAPE) {
return null;
}
Logout logout = logoutOptional.get();
Player player = drop.getPlayer();
FightTag fightTag = drop.getFightTag();
List<ItemStack> droppedItems = drop.getDroppedItems();
double maxHealth = player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue();
double health = logout.health();
double health = fightTag.getHealthBeforeDeath();
int percentHealth = MathUtil.getRoundedCountPercentage(health, maxHealth);
int reversedPercent = MathUtil.clamp(100 - percentHealth, this.settings.playersHealthPercentClamp, 100);
@@ -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
}
@@ -4,31 +4,69 @@ 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.Set;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.ConcurrentHashMap;
public interface FightManager {
public class FightManager {
boolean isInCombat(UUID player);
private final Map<UUID, FightTag> fights = new ConcurrentHashMap<>();
private final EventCaller eventCaller;
FightTag getTag(UUID target);
public FightManager(EventCaller eventCaller) {
this.eventCaller = eventCaller;
}
Collection<FightTag> getFights();
public boolean isInCombat(UUID player) {
if (!this.fights.containsKey(player)) {
return false;
}
// Players who tagged the given player during their current combat session.
@ApiStatus.Experimental
Set<UUID> getOpponents(UUID player);
FightTag fightTag = this.fights.get(player);
@ApiStatus.Experimental
FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger);
return !fightTag.isExpired();
}
FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag);
public FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag) {
FightUntagEvent event = this.eventCaller.publishEvent(new FightUntagEvent(player, causeOfUnTag));
if (event.isCancelled()) {
return event;
}
FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag);
this.fights.remove(player);
return event;
}
void untagAll();
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();
}
}
@@ -3,20 +3,58 @@ package com.eternalcode.combat.fight;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
public interface FightTag {
public class FightTag {
@Nullable
@ApiStatus.Experimental
UUID getTagger();
private final UUID taggedPlayer;
private final Instant endOfCombatLog;
Duration getRemainingDuration();
private double healthBeforeDeath;
private FightDeathCause deathCause;
boolean isExpired();
FightTag(UUID personToAddCombat, Instant endOfCombatLog) {
this.taggedPlayer = personToAddCombat;
this.endOfCombatLog = endOfCombatLog;
Instant getEndOfCombatLog();
this.healthBeforeDeath = 0;
this.deathCause = FightDeathCause.UNKNOWN;
}
UUID getTaggedPlayer();
public UUID getTaggedPlayer() {
return this.taggedPlayer;
}
public Instant getEndOfCombatLog() {
return this.endOfCombatLog;
}
public boolean isExpired() {
return Instant.now().isAfter(this.endOfCombatLog);
}
public Duration getRemainingDuration() {
Duration between = Duration.between(Instant.now(), this.endOfCombatLog);
if (between.isNegative()) {
return Duration.ZERO;
}
return between;
}
public double getHealthBeforeDeath() {
return this.healthBeforeDeath;
}
public void setHealthBeforeDeath(double health) {
this.healthBeforeDeath = health;
}
public FightDeathCause getDeathCause() {
return this.deathCause;
}
public void setDeathCause(FightDeathCause deathCause) {
this.deathCause = deathCause;
}
}
@@ -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);
}
}
@@ -0,0 +1,45 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTag;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import panda.utilities.text.Formatter;
public class FightEscapeController implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public FightEscapeController(FightManager fightManager, PluginConfig config, NotificationAnnouncer announcer) {
this.fightManager = fightManager;
this.config = config;
this.announcer = announcer;
}
@EventHandler
void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
Formatter formatter = new Formatter()
.register("{PLAYER}", player.getName());
String format = formatter.format(this.config.messages.playerLoggedOutDuringCombat);
this.announcer.broadcast(format);
FightTag fightTag = this.fightManager.getTag(player.getUniqueId());
fightTag.setHealthBeforeDeath(player.getHealth());
player.setHealth(0.0); // Untagged in PlayerDeathEvent
}
}
@@ -2,9 +2,9 @@ package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.bossbar.FightBossBarService;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import com.eternalcode.combat.notification.NoticeService;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -14,13 +14,15 @@ import org.bukkit.event.Listener;
public class FightMessageController implements Listener {
private final FightManager fightManager;
private final NoticeService noticeService;
private final NotificationAnnouncer announcer;
private final FightBossBarService bossBarService;
private final PluginConfig config;
private final Server server;
public FightMessageController(FightManager fightManager, NoticeService noticeService, PluginConfig config, Server server) {
public FightMessageController(FightManager fightManager, NotificationAnnouncer announcer, FightBossBarService bossBarService, PluginConfig config, Server server) {
this.fightManager = fightManager;
this.noticeService = noticeService;
this.announcer = announcer;
this.bossBarService = bossBarService;
this.config = config;
this.server = server;
}
@@ -37,24 +39,23 @@ public class FightMessageController implements Listener {
return;
}
this.noticeService.create()
.player(player.getUniqueId())
.notice(this.config.messagesSettings.playerTagged)
.send();
this.announcer.sendMessage(player, this.config.messages.playerTagged);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
void onUnTag(FightUntagEvent event) {
void onUnTag(FightTagEvent event) {
Player player = this.server.getPlayer(event.getPlayer());
if (player == null) {
throw new IllegalStateException("Player cannot be null!");
}
this.noticeService.create()
.player(player.getUniqueId())
.notice(this.config.messagesSettings.playerUntagged)
.send();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
this.announcer.sendMessage(player, this.config.messages.playerUntagged);
this.bossBarService.hide(event.getPlayer());
}
}
@@ -1,9 +1,10 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.fight.event.CauseOfTag;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.CauseOfTag;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
@@ -22,25 +23,22 @@ public class FightTagController implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public FightTagController(FightManager fightManager, PluginConfig config) {
public FightTagController(FightManager fightManager, PluginConfig config, NotificationAnnouncer announcer) {
this.fightManager = fightManager;
this.config = config;
this.announcer = announcer;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
// Thorns are ignored because both users should be in combat from hitting each other
if (event.getCause().equals(EntityDamageEvent.DamageCause.THORNS)) {
return;
}
if (!(event.getEntity() instanceof Player attackedPlayerByPerson)) {
return;
}
List<EntityType> disabledProjectileEntities = this.config.combat.ignoredProjectileTypes;
List<EntityType> disabledProjectileEntities = this.config.settings.disabledProjectileEntities;
if (event.getDamager() instanceof Projectile projectile && disabledProjectileEntities.contains(projectile.getType())) {
return;
}
@@ -49,36 +47,24 @@ public class FightTagController implements Listener {
return;
}
Player attacker = this.getDamager(event);
Player personToAddCombatTime = this.getDamager(event);
if (attacker == null) {
if (personToAddCombatTime == null) {
return;
}
if (this.config.combat.disableFlying) {
if (attackedPlayerByPerson.isFlying()) {
attackedPlayerByPerson.setFlying(false);
attackedPlayerByPerson.setAllowFlight(false);
}
Duration combatTime = this.config.settings.combatDuration;
if (attacker.isFlying()) {
attacker.setFlying(false);
attacker.setAllowFlight(false);
}
}
Duration combatTime = this.config.settings.combatTimerDuration;
UUID attackedUniqueId = attackedPlayerByPerson.getUniqueId();
UUID attackerUniqueId = attacker.getUniqueId();
UUID personToAddCombatTimeUniqueId = personToAddCombatTime.getUniqueId();
this.fightManager.tag(attackedUniqueId, combatTime, CauseOfTag.PLAYER, attackerUniqueId);
this.fightManager.tag(attackerUniqueId, combatTime, CauseOfTag.PLAYER, attackedUniqueId);
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.combat.enableDamageCauseLogging) {
if (!this.config.settings.shouldEnableDamageCauses) {
return;
}
@@ -90,11 +76,12 @@ public class FightTagController implements Listener {
return;
}
Duration combatTime = this.config.settings.combatTimerDuration;
Duration combatTime = this.config.settings.combatDuration;
UUID uuid = player.getUniqueId();
List<EntityDamageEvent.DamageCause> damageCauses = this.config.combat.loggedDamageCauses;
WhitelistBlacklistMode mode = this.config.combat.damageCauseRestrictionMode;
List<EntityDamageEvent.DamageCause> damageCauses = this.config.settings.damageCausesToLog;
WhitelistBlacklistMode mode = this.config.settings.damageCausesMode;
EntityDamageEvent.DamageCause cause = event.getCause();
@@ -107,7 +94,6 @@ public class FightTagController implements Listener {
this.fightManager.tag(uuid, combatTime, CauseOfTag.NON_PLAYER);
}
@Nullable
Player getDamager(EntityDamageByEntityEvent event) {
if (event.getDamager() instanceof Player damager) {
@@ -124,10 +110,7 @@ public class FightTagController implements Listener {
private boolean isPlayerInDisabledWorld(Player player) {
String worldName = player.getWorld().getName();
// Treat allowedWorlds as a whitelist: if the world is not listed, consider it disabled
return !this.config.settings.allowedWorlds.contains(worldName);
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);
}
}
}
@@ -1,16 +0,0 @@
package com.eternalcode.combat.fight.drop;
import java.util.List;
import java.util.UUID;
import org.bukkit.inventory.ItemStack;
public interface DropKeepInventoryService {
List<ItemStack> nextItems(UUID uuid);
boolean hasItems(UUID uuid);
void addItems(UUID uuid, List<ItemStack> item);
void addItem(UUID uuid, ItemStack item);
}
@@ -1,8 +0,0 @@
package com.eternalcode.combat.fight.drop;
public interface DropService {
DropResult modify(DropType dropType, Drop drop);
void registerModifier(DropModifier dropModifier);
}
@@ -1,25 +1,75 @@
package com.eternalcode.combat.fight.effect;
import java.util.List;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
/**
* Manages custom potion effects on players during combat.
* Active effects before combat are stored and restored after combat ends.
*/
public interface FightEffectService {
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.List;
import java.util.ArrayList;
void removeCustomEffect(Player player, PotionEffectType type, Integer amplifier);
public class FightEffectService {
void applyCustomEffect(Player player, PotionEffectType type, Integer amplifier);
private final Map<UUID, List<PotionEffect>> activeEffects = new HashMap<>();
private static final int INFINITE_DURATION = -1;
List<PotionEffect> getCurrentEffects(Player player);
public void storeActiveEffect(Player player, PotionEffect effect) {
List<PotionEffect> effects = this.activeEffects.computeIfAbsent(player.getUniqueId(), k -> new ArrayList<>());
void clearStoredEffects(Player player);
effects.add(effect);
}
void restoreActiveEffects(Player player);
public void restoreActiveEffects(Player player) {
List<PotionEffect> currentEffects = this.getCurrentEffects(player);
void storeActiveEffect(Player player, PotionEffect effect);
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);
}
}
@@ -1,10 +0,0 @@
package com.eternalcode.combat.fight.event;
public enum CancelTagReason {
TAGOUT,
PERMISSION_BYPASS,
ADMIN,
CREATIVE_MODE,
}
@@ -1,30 +1,7 @@
package com.eternalcode.combat.fight.event;
public enum CauseOfTag {
/**
* The player was tagged in combat as a result of an attack by another player.
*/
PLAYER,
/**
* The player was tagged in combat due to an interaction with a non-player entity
* (e.g., mobs or environmental damage).
*/
NON_PLAYER,
/**
* A command was executed to apply a combat tag to the player.
*/
COMMAND,
/**
* Crystal or anchor explosion caused the tag.
*/
CRYSTAL,
/**
* A custom cause, typically defined by external plugins or systems, applied the combat tag.
*/
CUSTOM
COMMAND
}
@@ -1,43 +1,10 @@
package com.eternalcode.combat.fight.event;
public enum CauseOfUnTag {
/**
* The combat tag expired after a set duration.
*/
TIME_EXPIRED,
/**
* The player died, which resulted in the removal of the combat tag.
*/
DEATH,
/**
* The player was killed by another player, causing the combat tag to be removed.
*/
DEATH_BY_PLAYER,
/**
* The player logged out, triggering the removal of the combat tag.
*/
LOGOUT,
/**
* A command was executed to remove the player's combat tag.
*/
COMMAND,
/**
* The attacker released the player, ending the combat and removing the tag.
*/
ATTACKER_RELEASE,
/**
* All of the player's combat opponents logged out, so there was nobody left to fight.
*/
OPPONENT_LOGOUT,
/**
* A custom cause, typically defined by external plugins or systems.
*/
CUSTOM
ATTACKER_RELEASE
}
@@ -5,80 +5,59 @@ import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
/**
* This event is triggered when a player is tagged in a fight.
*/
public class FightTagEvent extends Event implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private static final HandlerList handlers = new HandlerList();
private final UUID player;
private final CauseOfTag cause;
private boolean cancelled = false;
private CancelTagReason cancelReason;
private boolean isCancelled = false;
private String cancelMessage = "";
public FightTagEvent(UUID player, CauseOfTag cause) {
super(false);
this.player = player;
this.cause = cause;
}
/**
* Gets the UUID of the player who was tagged.
*
* @return The UUID of the tagged player.
*/
public UUID getPlayer() {
return this.player;
}
/**
* Gets the reason why the player was tagged.
*
* @return The cause of the combat tag as a {@link CauseOfTag}.
*/
public CauseOfTag getCause() {
return this.cause;
}
@Override
public boolean isCancelled() {
return this.cancelled;
return this.isCancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
throw new UnsupportedOperationException("Use #cancel(String) instead");
}
/**
* Gets the reason why the event was cancelled.
*
* @return The cancel reason.
*/
public CancelTagReason getCancelReason() {
return this.cancelReason;
public void cancel(String cancelMessage) {
this.isCancelled = true;
this.cancelMessage = cancelMessage;
}
/**
* Sets the reason why the event was cancelled.
*
* @param cancelReason The cancel reason.
*/
public void setCancelReason(CancelTagReason cancelReason) {
this.cancelReason = cancelReason;
public void allow() {
this.isCancelled = false;
this.cancelMessage = "";
}
public String getCancelMessage() {
return this.cancelMessage;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
return handlers;
}
public static HandlerList getHandlerList() {
return HANDLER_LIST;
return handlers;
}
}
@@ -5,61 +5,43 @@ import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
/**
* This event is triggered when a player is untagged during a fight.
*/
public class FightUntagEvent extends Event implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private static final HandlerList handlers = new HandlerList();
private final UUID player;
private final CauseOfUnTag cause;
private boolean cancelled;
private boolean isCancelled = false;
public FightUntagEvent(UUID player, CauseOfUnTag cause) {
super(false);
this.player = player;
this.cause = cause;
}
/**
* Gets the UUID of the player who was untagged.
*
* @return The UUID of the untagged player.
*/
public UUID getPlayer() {
return this.player;
}
/**
* Gets the reason for why the player was untagged.
*
* @return The cause of untagging as a {@link CauseOfUnTag}.
*/
public CauseOfUnTag getCause() {
return this.cause;
}
@Override
public boolean isCancelled() {
return this.cancelled;
return this.isCancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
this.isCancelled = cancelled;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
return handlers;
}
public static HandlerList getHandlerList() {
return HANDLER_LIST;
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);
}
}
@@ -1,16 +0,0 @@
package com.eternalcode.combat.fight.pearl;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
public interface FightPearlService {
Instant getDelay(UUID uuid);
Duration getRemainingDelay(UUID uuid);
boolean hasDelay(UUID uuid);
void markDelay(UUID uuid);
}
@@ -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!";
}
@@ -1,6 +1,6 @@
package com.eternalcode.combat.fight.tagout;
import com.eternalcode.combat.fight.event.CancelTagReason;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.event.FightTagEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@@ -10,9 +10,11 @@ import java.util.UUID;
public class FightTagOutController implements Listener {
private final FightTagOutService tagOutService;
private final PluginConfig config;
public FightTagOutController(FightTagOutService tagOutService) {
public FightTagOutController(FightTagOutService tagOutService, PluginConfig config) {
this.tagOutService = tagOutService;
this.config = config;
}
@EventHandler
@@ -20,8 +22,7 @@ public class FightTagOutController implements Listener {
UUID uniqueId = event.getPlayer();
if (this.tagOutService.isTaggedOut(uniqueId)) {
event.setCancelReason(CancelTagReason.TAGOUT);
event.setCancelled(true);
event.cancel(this.config.messages.admin.adminTagOutCanceled);
}
}
@@ -1,13 +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 interface FightTagOutService {
public class FightTagOutService {
boolean isTaggedOut(UUID player);
private final Map<UUID, Instant> tagOuts = new HashMap<>();
void unTagOut(UUID player);
public void tagOut(UUID player, Duration duration) {
Instant endTime = Instant.now().plus(duration);
this.tagOuts.put(player, endTime);
}
public void unTagOut(UUID player) {
this.tagOuts.remove(player);
}
public boolean isTaggedOut(UUID player) {
Instant endTime = this.tagOuts.get(player);
if (endTime == null) {
return false;
}
Instant now = Instant.now();
return now.isBefore(endTime);
}
void tagOut(UUID player, Duration duration);
}
@@ -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();
}
}
@@ -0,0 +1,9 @@
package com.eternalcode.combat.notification;
public enum NotificationType {
CHAT,
ACTION_BAR,
TITLE,
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) {}
}
@@ -1,6 +0,0 @@
package com.eternalcode.combat.region;
import org.bukkit.World;
public record Point(World world, double x, double z) {
}
@@ -2,24 +2,5 @@ package com.eternalcode.combat.region;
import org.bukkit.Location;
public interface Region {
Point getCenter();
Location getMin();
Location getMax();
default boolean contains(Location location) {
return this.contains(location.getX(), location.getY(), location.getZ());
}
default boolean contains(double x, double y, double z) {
Location min = this.getMin();
Location max = this.getMax();
return x >= min.getX() && x < max.getX()
&& y >= min.getY() && y < max.getY()
&& z >= min.getZ() && z < max.getZ();
}
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);
}
}
}
@@ -1,18 +1,10 @@
package com.eternalcode.combat.region;
import java.util.Collection;
import java.util.Optional;
import org.bukkit.Location;
import org.bukkit.World;
public interface RegionProvider {
Optional<Region> getRegion(Location location);
default boolean isInRegion(Location location) {
return this.getRegion(location).isPresent();
}
Collection<Region> getRegions(World world);
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));
}
}
@@ -1,7 +1,6 @@
package com.eternalcode.combat.updater;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.commons.concurrent.FutureHandler;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.text.minimessage.MiniMessage;
@@ -10,6 +9,8 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import java.util.concurrent.CompletableFuture;
public class UpdaterNotificationController implements Listener {
private static final String NEW_VERSION_AVAILABLE = "<b><gradient:#8a1212:#fc6b03>EternalCombat:</gradient></b> <color:#fce303>New version of EternalCombat is available, please update!";
@@ -31,21 +32,23 @@ public class UpdaterNotificationController implements Listener {
Player player = event.getPlayer();
Audience audience = this.audienceProvider.player(player.getUniqueId());
if (!shouldNotify(player)) {
if (!player.hasPermission("eternalcombat.receiveupdates") || !this.pluginConfig.settings.shouldReceivePluginUpdates) {
return;
}
this.updaterService.checkForUpdate()
.thenAccept(result -> {
if (result.isUpdateAvailable()) {
audience.sendMessage(this.miniMessage.deserialize(NEW_VERSION_AVAILABLE));
}
})
.exceptionally(FutureHandler::handleException);
}
CompletableFuture<Boolean> upToDate = this.updaterService.isUpToDate();
private boolean shouldNotify(Player player) {
return player.hasPermission("eternalcombat.receiveupdates") && this.pluginConfig.settings.notifyAboutUpdates;
upToDate.whenComplete((isUpToDate, throwable) -> {
if (throwable != null) {
throwable.printStackTrace();
return;
}
if (!isUpToDate) {
audience.sendMessage(this.miniMessage.deserialize(NEW_VERSION_AVAILABLE));
}
});
}
}
@@ -0,0 +1,35 @@
package com.eternalcode.combat.updater;
import com.eternalcode.gitcheck.GitCheck;
import com.eternalcode.gitcheck.GitCheckResult;
import com.eternalcode.gitcheck.git.GitRepository;
import com.eternalcode.gitcheck.git.GitTag;
import org.bukkit.plugin.PluginDescriptionFile;
import panda.std.Lazy;
import java.util.concurrent.CompletableFuture;
public class UpdaterService {
private static final GitRepository GIT_REPOSITORY = GitRepository.of("EternalCodeTeam", "EternalCombat");
private final GitCheck gitCheck = new GitCheck();
private final Lazy<GitCheckResult> gitCheckResult;
public UpdaterService(PluginDescriptionFile descriptionFile) {
this.gitCheckResult = new Lazy<>(() -> {
String version = descriptionFile.getVersion();
return this.gitCheck.checkRelease(GIT_REPOSITORY, GitTag.of("v" + version));
});
}
public CompletableFuture<Boolean> isUpToDate() {
return CompletableFuture.supplyAsync(() -> {
GitCheckResult result = this.gitCheckResult.get();
return result.isUpToDate();
});
}
}
@@ -0,0 +1,25 @@
package com.eternalcode.combat.util;
import java.time.Duration;
public final class DurationUtil {
private DurationUtil() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
public static String format(Duration duration) {
return format(duration, true);
}
public static String format(Duration duration, boolean removeMillis) {
if (removeMillis) {
duration = Duration.ofSeconds(duration.toSeconds());
}
return duration.toString()
.substring(2)
.replaceAll("(\\d[HMS])(?!$)", "$1 ")
.toLowerCase();
}
}
@@ -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);
}
}
Loaded 100 of 204 files, more files were not shown because too many files have changed in this diff. Show more