Compare commits

..
Author SHA1 Message Date
Michał Wojtas 520196417b Use already defined versions of plugins in runServer script
Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>
2026-06-21 12:17:23 +02:00
34 changed files with 239 additions and 484 deletions

No files matched your search

+2 -1
View File
@@ -1 +1,2 @@
* @EternalCodeTeam/team-maintainer
* @EternalCodeTeam/eternalcodeteam-maintainers
/.github/CODEOWNERS @EternalCodeTeam/eternalcodeteam-maintainers
+43 -33
View File
@@ -1,54 +1,64 @@
# 👋 Contributing to EternalCombat
Thanks for taking the time to contribute! This page covers everything you need to get started.
First off, thanks for taking the time to contribute! 🎉
We love user contributions and want to make it as easy as possible for you to get involved.
## 🛠️ Requirements
## 🛠️ Development Setup
- **JDK 21** (the project targets Java 21)
- Git
To start contributing, you'll need to set up your environment:
The Gradle wrapper is committed, so you don't need Gradle installed.
1. **Java 21**: We use Java 21 for development. Ensure you have a JDK 21 installed.
2. **IDE**: We recommend [IntelliJ IDEA](https://www.jetbrains.com/idea/).
3. **Git**: Ensure you have Git installed.
## 🔨 Building and testing
### 📥 Getting the Code
```bash
git clone https://github.com/EternalCodeTeam/EternalCombat.git
# Fork the repository on GitHub first!
# https://github.com/EternalCodeTeam/EternalCombat/fork
git clone https://github.com/YOUR-USERNAME/EternalCombat.git
cd EternalCombat
```
### 🔨 Building the Project
Run this command in your terminal:
```bash
./gradlew build
```
`./gradlew build` compiles both modules, runs the unit tests and produces the shaded plugin jar in
`eternalcombat-plugin/build/libs/`.
## 🤝 How to Contribute
To run only the tests:
1. **Fork & Clone**: Fork the repo to your own account and clone it.
2. **Branch**: Create a new branch for your feature or fix.
```bash
git checkout -b feat/my-awesome-feature
```
3. **Code**: Hack away! 💻
4. **Test**: Run manual test by `runServer` to ensure you haven't broken anything.
```bash
./gradlew runServer
```
5. **Push**: Push your changes to your fork.
6. **Pull Request**: Open a PR against the `master` branch.
```bash
./gradlew test
```
## 📏 Code Style
To try your changes on a real server, start a Paper server with the plugin and its dependencies
already installed:
Make sure that your code adheres to the plugin's existing coding style.
```bash
./gradlew runServer
```
## 🐛 Reporting Bugs
## 📏 Code style
If you find a bug, please use the **Bug Report** issue template.
- Check if the issue already exists.
- Provide a minimal reproduction if possible.
- Include logs and screenshots.
Formatting rules live in [`.editorconfig`](../.editorconfig) — 4 spaces, UTF-8, LF endings.
Enable EditorConfig support in your IDE and match the style of the surrounding code.
## 💡 Feature Requests
## 🚀 Pull requests
1. Fork the repository and create a branch off `master`.
2. Keep the change focused — one topic per pull request.
3. Add or update tests when you change behaviour that can be tested without a server.
4. Make sure `./gradlew build` passes before opening the PR.
5. Prefix the PR title with the type of change, for example
`feat:`, `fix:`, `chore:`, `docs:`, or `GH-<issue>` when it closes an issue.
6. Describe what you changed and why — the pull request template is intentionally short.
Have an idea? Use the **Feature Request** issue template.
- Explain *why* this feature is useful.
- Describe how it should look or behave.
## 📄 License
By contributing, you agree that your contributions will be licensed under the
[Apache License 2.0](../LICENSE).
By contributing, you agree that your contributions will be licensed under the project's [License](../LICENSE).
+57 -13
View File
@@ -1,14 +1,20 @@
name: 🐛 Bug Report
description: Report something that is broken in EternalCombat
description: File a bug report to help us improve EternalCombat
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
Please ensure that you have searched the existing issues to see if this bug has already been reported.
- type: textarea
id: description
attributes:
label: What happened?
description: What went wrong, and what did you expect instead?
placeholder: I was doing X and Y happened, but I expected Z.
label: Bug description
description: A clear and concise description of what the bug is.
placeholder: I was doing X and Y happened...
validations:
required: true
@@ -16,25 +22,63 @@ body:
id: reproduction
attributes:
label: Steps to reproduce
description: Please provide a step-by-step guide to reproduce the issue.
placeholder: |
1. Start the server
2. Run /combatlog tag <player>
3. See the error
1. Launch the server
2. Tag yourself using the '/combat tag' command
3. See error
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots/Video
description: If applicable, add screenshots or a video to help explain your problem.
- type: dropdown
id: server-software
attributes:
label: Server Software
description: What server software are you using?
options:
- Paper
- Purpur
- Spigot
- Folia
- Other (please specify in logs)
validations:
required: true
- type: input
id: versions
id: minecraft-version
attributes:
label: Versions
description: Server software and version, plus the EternalCombat version.
placeholder: Paper 1.21.11, EternalCombat 2.5.1
label: Minecraft Version
description: What version of Minecraft are you running? (e.g. 1.21.11)
placeholder: 1.21.11
validations:
required: true
- type: input
id: plugin-version
attributes:
label: Plugin Version
description: What version of EternalCombat are you running? (Check with /eternalcombat version)
placeholder: v2.4.1
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs
description: Any relevant errors or stack traces. Use a paste service for long logs.
label: Logs / Stack Trace
description: Please paste any relevant logs or stack traces here. Use a pastebin service if the log is too long.
render: shell
+26 -6
View File
@@ -3,16 +3,36 @@ description: Suggest an idea for EternalCombat
title: "[Feature]: "
labels: ["feature"]
body:
- type: textarea
id: idea
- type: markdown
attributes:
label: What would you like to see?
description: Describe the feature and the problem it would solve.
value: |
Thanks for suggesting a new feature! Please explain your idea in detail.
- type: textarea
id: problem
attributes:
label: Is your feature request related to a problem?
description: Please describe the problem you are trying to solve.
placeholder: I'm always frustrated when...
validations:
required: true
- type: textarea
id: context
id: solution
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Optional — alternatives you considered, examples, or screenshots.
description: Add any other context or screenshots about the feature request here.
+30 -6
View File
@@ -1,11 +1,35 @@
## What changed?
---
name: Pull Request
about: Submit a contribution to EternalCombat
title: 'GH-{NUMBER} <title>'
labels: []
assignees: []
---
<!-- A short description of the change. -->
## 📝 Description
<!--
Please submit your PR using the following title format:
feat: Description of the new feature
fix: Description of the bug fix
chore: Maintenance tasks
docs: Documentation changes
-->
## Why?
<!-- Describe your changes clearly and concisely. Why is this change needed? What does it accomplish? -->
<!-- The problem this solves, or the issue it closes (e.g. Closes #123). -->
## 🎯 Type of Change
<!-- Please check the boxes that apply to your changes -->
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue)
- [ ] ✨ New feature (non-breaking change which adds functionality)
- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] 🔧 Refactoring (no functional changes, just code improvements)
- [ ] 🎨 Style/Cosmetic (formatting, whitespace, visual changes)
- [ ] 📚 Documentation (updates to documentation or README)
- [ ] 🤖 CI/CD/Build (changes to workflows, gradle, etc.)
## Anything else?
**Test Environment:**
- **Minecraft Version:** <!-- e.g. 1.20.4 -->
- **Java Version:** <!-- e.g. 21 -->
- **Platform:** <!-- e.g. Windows/Linux/MacOS -->
<!-- Optional: how you tested it, screenshots, or notes for reviewers. -->
## 📸 Screenshots (if applicable)
-17
View File
@@ -1,17 +0,0 @@
# Security Policy
## Supported versions
Only the latest release of EternalCombat receives security fixes.
Please update before reporting an issue.
## Reporting a vulnerability
**Please do not open a public issue for security problems.**
Report them privately to the maintainers on our
[Discord server](https://discord.com/invite/FQ7jmGBd6c) — open a ticket or send a
direct message to a team member.
Include the plugin version, your server software and version, and the steps needed to
reproduce the problem. We will confirm the report and let you know once a fix is released.
+17 -10
View File
@@ -5,12 +5,9 @@ on:
branches: [ "master" ]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
@@ -18,24 +15,34 @@ jobs:
- name: Checkout
uses: actions/checkout@v7
with:
# The build derives the snapshot version from `git describe --tags`.
fetch-depth: 0
- name: Set up JDK 21
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
java-version: '21'
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Set Variables & Build
uses: actions/github-script@v9
with:
script: |
const isPR = context.eventName === 'pull_request';
const branch = isPR ? context.payload.pull_request.head.ref.replace(/\//g, '-') : 'master';
const sha = (isPR ? context.payload.pull_request.head.sha : context.sha).substring(0, 7);
const artifactName = `EternalCombat ${branch}+${sha}`;
core.exportVariable('FULL_ARTIFACT_NAME', artifactName);
- name: Build with Gradle
run: ./gradlew build
run: ./gradlew shadowJar
- name: Upload build artifact
uses: actions/upload-artifact@v7
with:
path: eternalcombat-plugin/build/libs/EternalCombat*.jar
if-no-files-found: error
name: ${{ env.FULL_ARTIFACT_NAME }}
path: eternalcombat-plugin/build/libs/*.jar
archive: false
+2 -6
View File
@@ -7,10 +7,6 @@ on:
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-latest
@@ -26,10 +22,10 @@ jobs:
with:
java-version: '21'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Publish to Hangar
run: ./gradlew :eternalcombat-plugin:publishPluginPublicationToHangar
env:
+2 -6
View File
@@ -7,10 +7,6 @@ on:
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-latest
@@ -26,10 +22,10 @@ jobs:
with:
java-version: '21'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Publish to Modrinth
run: ./gradlew :eternalcombat-plugin:modrinth
env:
+7 -15
View File
@@ -1,36 +1,28 @@
name: Publish EternalCombat API
on:
release:
branches: [ master ]
types: [ published ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-latest
environment: deployment
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up JDK 21
- name: Set up JDK
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'temurin'
java-version: 17
cache: 'gradle'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Publish with Gradle
run: ./gradlew build :eternalcombat-api:publish
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 }}
+7 -11
View File
@@ -6,8 +6,8 @@
[![Available on Modrinth](https://raw.githubusercontent.com/vLuckyyy/badges/main/avaiable-on-modrinth.svg)](https://modrinth.com/plugin/eternalcombat)
[![Available on Hangar](https://raw.githubusercontent.com/vLuckyyy/badges/main/avaiable-on-hangar.svg)](https://hangar.papermc.io/EternalCodeTeam/eternalcombat)
[![Chat on Discord](https://raw.githubusercontent.com/vLuckyyy/badges/main/chat-with-us-on-discord.svg)](https://discord.com/invite/FQ7jmGBd6c)
[![Read the Docs](https://raw.githubusercontent.com/vLuckyyy/badges/main/read-the-documentation.svg)](https://eternalcode.pl/docs/eternalcombat/introduction)
[![Chat on Discord](https://raw.githubusercontent.com/vLuckyyy/badges/main//chat-with-us-on-discord.svg)](https://discord.com/invite/FQ7jmGBd6c)
[![Read the Docs](https://raw.githubusercontent.com/vLuckyyy/badges/main/read-the-documentation.svg)](https://docs.eternalcode.pl/eternalcombat/introduction)
[![Available on BStats](https://raw.githubusercontent.com/vLuckyyy/badges/main/available-on-bstats.svg)](https://bstats.org/plugin/bukkit/EternalCombat/17803)
[Report Bug](https://github.com/EternalCodeTeam/EternalCombat/issues) • [Request Feature](https://github.com/EternalCodeTeam/EternalCombat/issues) • [Join Discord](https://discord.com/invite/FQ7jmGBd6c)
@@ -22,10 +22,10 @@
### Information
#### EternalCombat requires PacketEvents plugin to work.
## EternalCombat requires PacketEvents plugin to work.
You can download it from [here](https://modrinth.com/plugin/packetevents)
EternalCombat 2.0 has been tested on Minecraft versions **1.19.3 to 26.1.2**, but it should work seamlessly on most
EternalCombat 2.0 has been tested on Minecraft versions **1.17.1 to 26.1.2**, but it should work seamlessly on most
other versions too.
If you run into any compatibility issues, please let us know in
the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues).
@@ -91,9 +91,9 @@ Control who can use EternalCombat’s powerful features with these permissions:
| `eternalcombat.stats` | View combat statistics with `/combatlog stats`. |
| `eternalcombat.reload` | Reload the plugin with `/combatlog reload`. |
| `eternalcombat.receiveupdates` | Receive notifications about new plugin versions on join. |
| `eternalcombat.bypass` | When `excludeAdminsFromCombat` is set to `true`, users with this permission will not be tagged |
| `eternalcombat.bypass` | When exclude `excludeAdminsFromCombat` is set to `true` users with this permission will not be tagged |
### PlaceholderAPI
## PlaceholderAPI
EternalCombat 2.0 fully supports **PlaceholderAPI**, letting you integrate placeholders into other compatible plugins.
Follow the [PlaceholderAPI instructions](https://wiki.placeholderapi.com/users/) to get started.
@@ -163,12 +163,8 @@ details on how to get involved and our [code of conduct](https://github.com/Eter
### Reporting Issues
Found a bug?
Report it in the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues).
Report it in the [Issues tab](https://github.com/eternalcodeteam/eternalcombat/issues).
Please include as much detail as possible, like your Minecraft and plugin
versions, along with any error messages or logs.
Ready to transform your server’s combat experience?
Download EternalCombat 2.0 now and let the battles begin!
### License
EternalCombat is licensed under the [Apache License 2.0](LICENSE).
+3 -3
View File
@@ -8,12 +8,12 @@ repositories {
}
dependencies {
implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.6.1")
implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.4.2")
implementation("com.modrinth.minotaur:com.modrinth.minotaur.gradle.plugin:2.9.0")
implementation("io.papermc.hangar-publish-plugin:io.papermc.hangar-publish-plugin.gradle.plugin:0.1.4")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.4.10")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.4.0")
implementation("net.minecrell:plugin-yml:0.6.0")
implementation("xyz.jpenilla:run-task:3.1.0")
implementation("xyz.jpenilla:run-task:3.0.2")
}
sourceSets {
+11 -9
View File
@@ -3,15 +3,15 @@ object Versions {
// Keep this version for best compatibility.
const val PAPER_API = "1.19.3-R0.1-SNAPSHOT"
const val JUNIT_JUPITER_API = "6.1.3"
const val JUNIT_JUPITER_PARAMS = "6.1.3"
const val JUNIT_JUPITER_ENGINE = "6.1.3"
const val JUNIT_JUPITER_API = "6.1.0"
const val JUNIT_JUPITER_PARAMS = "6.1.0"
const val JUNIT_JUPITER_ENGINE = "6.1.0"
const val JETBRAINS_ANNOTATIONS = "26.1.0"
const val ETERNALCODE_COMMONS = "1.4.1"
const val MULTIFICATION = "1.2.5-SNAPSHOT"
const val PACKETS_EVENTS = "2.13.0"
const val PACKETS_EVENTS = "2.12.2"
const val LITE_COMMANDS = "3.11.0"
const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.13"
@@ -24,12 +24,14 @@ object Versions {
const val B_STATS_BUKKIT = "3.2.1"
const val WORLD_GUARD_BUKKIT = "7.0.17"
const val PLACEHOLDER_API = "2.12.3"
const val PLACEHOLDER_API = "2.12.2"
const val LANDS_API = "7.25.4"
const val WORLDEDIT = "3ISh7ADm" //cannot use numeric version bc of duplicated version on modrinth
const val PACKETEVENTS = "2.11.1"
const val WORLDGUARD = "7.0.15-beta-01"
const val LUCKPERMS = "5.5.17"
//runServer task
const val FAST_ASYNC_WORLD_EDIT = "2.15.2"
const val PACKETEVENTS = "2.12.2+spigot"
const val WORLDGUARD = "7.0.17"
const val LUCKPERMS = "v5.5.53-bukkit"
}
@@ -7,7 +7,6 @@ dependencies {
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}")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.test {
@@ -14,9 +14,9 @@ tasks {
})
minecraftVersion("26.1.2")
downloadPlugins.modrinth("FastAsyncWorldEdit", "2.15.2")
downloadPlugins.modrinth("PacketEvents", "2.12.2+spigot")
downloadPlugins.modrinth("WorldGuard", "7.0.17")
downloadPlugins.modrinth("LuckPerms", "v5.5.53-bukkit")
downloadPlugins.modrinth("FastAsyncWorldEdit", Versions.FAST_ASYNC_WORLD_EDIT)
downloadPlugins.modrinth("PacketEvents", Versions.PACKETEVENTS)
downloadPlugins.modrinth("WorldGuard", Versions.WORLDGUARD)
downloadPlugins.modrinth("LuckPerms", Versions.LUCKPERMS)
}
}
@@ -34,11 +34,6 @@ public enum CauseOfTag {
ENDER_PEARL,
/**
* The player's combat tag was kept active because they were below the void combat height.
*/
VOID,
/**
* A custom cause, typically defined by external plugins or systems, applied the combat tag.
*/
@@ -5,9 +5,7 @@ import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This event is triggered when a player is tagged in a fight.
@@ -17,25 +15,14 @@ public class FightTagEvent extends Event implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final UUID player;
private final CauseOfTag cause;
private final UUID tagger;
private boolean cancelled = false;
private CancelTagReason cancelReason;
/**
* @deprecated use {@link #FightTagEvent(UUID, CauseOfTag, UUID)} instead, which also carries the tagger.
*/
@Deprecated
@ApiStatus.ScheduledForRemoval
public FightTagEvent(UUID player, CauseOfTag cause) {
this(player, cause, null);
}
public FightTagEvent(UUID player, CauseOfTag cause, @Nullable UUID tagger) {
super(false);
this.player = player;
this.cause = cause;
this.tagger = tagger;
}
/**
@@ -56,16 +43,6 @@ public class FightTagEvent extends Event implements Cancellable {
return this.cause;
}
/**
* Gets the UUID of the player who caused this tag, if any.
*
* @return The UUID of the tagger, or {@code null} if the tag was not caused by another player.
*/
@Nullable
public UUID getTagger() {
return this.tagger;
}
@Override
public boolean isCancelled() {
return this.cancelled;
-1
View File
@@ -8,7 +8,6 @@ plugins {
`eternalcombat-publish-hangar`
`eternalcombat-publish-modrinth`
`eternalcombat-runserver`
`eternalcombat-java-unit-test`
id("de.eldoria.plugin-yml.paper") version "0.9.0"
id("com.gradleup.shadow")
@@ -36,12 +36,10 @@ import com.eternalcode.combat.fight.drop.DropServiceImpl;
import com.eternalcode.combat.fight.drop.impl.PercentDropModifier;
import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier;
import com.eternalcode.combat.fight.blocker.PlaceBlockBlocker;
import com.eternalcode.combat.fight.blocker.SignEditingBlocker;
import com.eternalcode.combat.fight.effect.FightEffectController;
import com.eternalcode.combat.fight.effect.FightEffectService;
import com.eternalcode.combat.fight.effect.FightEffectServiceImpl;
import com.eternalcode.combat.fight.firework.FireworkController;
import com.eternalcode.combat.fight.knockback.KnockbackMountController;
import com.eternalcode.combat.fight.knockback.KnockbackRegionController;
import com.eternalcode.combat.fight.knockback.KnockbackService;
import com.eternalcode.combat.fight.logout.LogoutController;
@@ -206,7 +204,6 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
new RespawnAnchorListener(this, this.fightManager, pluginConfig),
new FireworkController(this.fightManager, pluginConfig, noticeService),
new InventoryContainersBlocker(this.fightManager, pluginConfig, noticeService),
new SignEditingBlocker(this.fightManager, pluginConfig),
new CommandsBlocker(this.fightManager, noticeService, pluginConfig),
new ElytraBlocker(this.fightManager, pluginConfig),
new ElytraEquipBlocker(this.fightManager, noticeService, pluginConfig, server),
@@ -214,8 +211,6 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
new PlaceBlockBlocker(this.fightManager, noticeService, pluginConfig)
);
new KnockbackMountController(noticeService, this.regionProvider, this.fightManager).register(this);
eventManager.subscribe(
PlayerDeathEvent.class,
pluginConfig.drop.dropEventPriority,
@@ -90,19 +90,6 @@ public class CombatSettings extends OkaeriConfig {
})
public EventPriority quitPunishmentEventPriority = EventPriority.NORMAL;
@Comment({
"# Keep a player's combat tag continuously active while they are below 'voidCombatHeight'.",
"# This prevents players from evading combat by falling into the void (e.g. in the End).",
"# Set to 'true' to keep resetting the combat timer to its full duration while below that height."
})
public boolean keepCombatActiveInVoid = false;
@Comment({
"# The Y height below which the combat timer is kept active (see 'keepCombatActiveInVoid').",
"# Default: 0, matching the void threshold in worlds such as the End."
})
public double voidCombatHeight = 0;
@Comment({
"# List of kick reasons where players will NOT be punished for combat logging.",
"# If this list is empty, players are ALWAYS punished when kicked during combat.",
@@ -50,12 +50,10 @@ public class MessagesSettings extends OkaeriConfig {
@Comment({
"# Message displayed when a player enters combat.",
"# This message warns the player not to leave the server while in combat.",
"# The {OPPONENT} placeholder is replaced with the name of the player who caused the tag,",
"# or the 'unknownPlayerPlaceholder' variable if the tag was not caused by a player."
"# This message warns the player not to leave the server while in combat."
})
public Notice playerTagged = Notice.chat(
"<gradient:red:yellow>⚠ <white>You are in combat with</white> <yellow>{OPPONENT}</yellow>! <u>Do not leave the server!</gradient>");
"<gradient:red:yellow>⚠ <white>You are in combat!</white> <u>Do not leave the server!</gradient>");
@Comment({
"# Message displayed when a player leaves combat.",
@@ -127,13 +127,6 @@ public class PluginConfig extends OkaeriConfig {
})
public InventorySettings inventory = new InventorySettings();
@Comment({
" ",
"# Settings related to sign editing during combat.",
"# Created for sign traps where opening the sign editor can block ender pearl throws."
})
public SignEditingSettings signEditing = new SignEditingSettings();
@Comment({
" ",
"# Settings related to placeholders used in the plugin.",
@@ -1,13 +0,0 @@
package com.eternalcode.combat.config.implementation;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
public class SignEditingSettings extends OkaeriConfig {
@Comment({
"# Prevent players from editing signs during combat.",
"# Disabled by default; created for sign traps where sign editing blocks ender pearl throws."
})
public boolean disableSignEditingDuringCombat = false;
}
@@ -66,14 +66,12 @@ public class CrystalPvpConstants {
fightManager.tag(
damagerUniqueId,
pluginConfig.settings.combatTimerDuration,
CauseOfTag.CRYSTAL,
victimUniqueId
CauseOfTag.CRYSTAL
);
fightManager.tag(
victimUniqueId,
pluginConfig.settings.combatTimerDuration,
CauseOfTag.CRYSTAL,
damagerUniqueId
CauseOfTag.CRYSTAL
);
}
}
@@ -55,7 +55,7 @@ public class FightManagerImpl implements FightManager {
@ApiStatus.Experimental
@Override
public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger) {
FightTagEvent event = this.eventManager.publishEvent(new FightTagEvent(target, causeOfTag, tagger));
FightTagEvent event = this.eventManager.publishEvent(new FightTagEvent(target, causeOfTag));
if (event.isCancelled()) {
return event;
@@ -1,9 +1,7 @@
package com.eternalcode.combat.fight;
import com.eternalcode.combat.config.implementation.PluginConfig;
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.notification.NoticeService;
import com.eternalcode.combat.util.DurationUtil;
import java.util.Optional;
@@ -43,16 +41,6 @@ public class FightTask implements Runnable {
return;
}
if (this.config.combat.keepCombatActiveInVoid
&& player.getLocation().getY() < this.config.combat.voidCombatHeight
&& !this.config.settings.ignoredWorlds.contains(player.getWorld().getName())) {
FightTagEvent tagEvent = this.fightManager.tag(playerUniqueId, this.config.settings.combatTimerDuration, CauseOfTag.VOID, fightTag.getTagger());
if (!tagEvent.isCancelled()) {
fightTag = this.fightManager.getTag(playerUniqueId);
}
}
Duration remaining = fightTag.getRemainingDuration();
String opponent = Optional.ofNullable(fightTag.getTagger())
@@ -1,63 +0,0 @@
package com.eternalcode.combat.fight.blocker;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Event.Result;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
public class SignEditingBlocker implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
public SignEditingBlocker(FightManager fightManager, PluginConfig config) {
this.fightManager = fightManager;
this.config = config;
}
@EventHandler(ignoreCancelled = true)
void onInteract(PlayerInteractEvent event) {
if (!this.config.signEditing.disableSignEditingDuringCombat) {
return;
}
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
Block clickedBlock = event.getClickedBlock();
if (clickedBlock == null || !isSign(clickedBlock.getType())) {
return;
}
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
event.setUseInteractedBlock(Result.DENY);
ItemStack item = event.getItem();
if (item != null && isEnderPearl(item.getType()) && event.useItemInHand() != Result.DENY) {
event.setUseItemInHand(Result.ALLOW);
}
}
static boolean isSign(Material material) {
return material.name().endsWith("_SIGN");
}
static boolean isEnderPearl(Material material) {
return material == Material.ENDER_PEARL;
}
}
@@ -5,8 +5,6 @@ import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import com.eternalcode.combat.notification.NoticeService;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -39,23 +37,12 @@ public class FightMessageController implements Listener {
return;
}
String opponent = this.resolveOpponentName(event.getTagger(), player.getUniqueId());
this.noticeService.create()
.player(player.getUniqueId())
.notice(this.config.messagesSettings.playerTagged)
.placeholder("{OPPONENT}", opponent)
.send();
}
private String resolveOpponentName(UUID tagger, UUID taggedPlayer) {
return Optional.ofNullable(tagger)
.filter(uuid -> !uuid.equals(taggedPlayer))
.map(this.server::getPlayer)
.map(Player::getName)
.orElse(this.config.messagesSettings.unknownPlayerPlaceholder);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
void onUnTag(FightUntagEvent event) {
Player player = this.server.getPlayer(event.getPlayer());
@@ -1,129 +0,0 @@
package com.eternalcode.combat.fight.knockback;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NoticeService;
import com.eternalcode.combat.region.RegionProvider;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityEvent;
import org.bukkit.plugin.Plugin;
// EntityMountEvent moved between org.spigotmc, io.papermc.paper and org.bukkit across versions,
// so we resolve it at runtime to stay compatible without bumping the compile API.
public final class KnockbackMountController implements Listener {
private static final String[] EVENT_CLASS_CANDIDATES = {
"org.bukkit.event.entity.EntityMountEvent",
"io.papermc.paper.event.entity.EntityMountEvent",
"org.spigotmc.event.entity.EntityMountEvent",
};
private final NoticeService noticeService;
private final RegionProvider regionProvider;
private final FightManager fightManager;
private final AtomicBoolean mountFailureLogged = new AtomicBoolean();
public KnockbackMountController(NoticeService noticeService, RegionProvider regionProvider, FightManager fightManager) {
this.noticeService = noticeService;
this.regionProvider = regionProvider;
this.fightManager = fightManager;
}
public void register(Plugin plugin) {
Class<? extends Event> eventClass = null;
for (String candidate : EVENT_CLASS_CANDIDATES) {
try {
eventClass = Class.forName(candidate).asSubclass(Event.class);
break;
} catch (ClassNotFoundException | ClassCastException ignored) {
}
}
if (eventClass == null) {
plugin.getLogger().warning("Could not find any EntityMountEvent class; mount protection in regions is disabled.");
return;
}
MethodHandle getMountHandle;
try {
getMountHandle = this.resolveGetMountHandle(eventClass);
} catch (NoSuchMethodException | IllegalAccessException exception) {
plugin.getLogger().log(Level.WARNING, "EntityMountEvent has incompatible getMount() method; mount protection in regions is disabled.", exception);
return;
}
plugin.getServer().getPluginManager().registerEvent(
eventClass,
this,
EventPriority.HIGHEST,
(listener, event) -> this.handle(event, getMountHandle, plugin),
plugin,
true
);
}
private MethodHandle resolveGetMountHandle(Class<? extends Event> eventClass) throws NoSuchMethodException, IllegalAccessException {
if (!EntityEvent.class.isAssignableFrom(eventClass) || !Cancellable.class.isAssignableFrom(eventClass)) {
throw new NoSuchMethodException(eventClass.getName() + " is not a cancellable entity event");
}
Method getMount = eventClass.getMethod("getMount");
if (!Entity.class.isAssignableFrom(getMount.getReturnType())) {
throw new NoSuchMethodException(eventClass.getName() + "#getMount() does not return Entity");
}
return MethodHandles.publicLookup()
.unreflect(getMount)
.asType(MethodType.methodType(Entity.class, Event.class));
}
private void handle(Event event, MethodHandle getMountHandle, Plugin plugin) {
if (!(event instanceof EntityEvent entityEvent) || !(event instanceof Cancellable cancellable)) {
return;
}
if (!(entityEvent.getEntity() instanceof Player player)) {
return;
}
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
Entity mount;
try {
mount = (Entity) getMountHandle.invokeExact(event);
} catch (Throwable exception) {
this.logMountFailure(plugin, exception);
return;
}
if (mount == null || !this.regionProvider.isInRegion(mount.getLocation())) {
return;
}
cancellable.setCancelled(true);
this.noticeService.create()
.player(player.getUniqueId())
.notice(config -> config.messagesSettings.cantEnterOnRegion)
.send();
}
private void logMountFailure(Plugin plugin, Throwable exception) {
if (!this.mountFailureLogged.compareAndSet(false, true)) {
return;
}
plugin.getLogger().log(Level.WARNING, "Could not read EntityMountEvent mount; mount protection in regions is skipped.", exception);
}
}
@@ -17,6 +17,7 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.vehicle.VehicleMoveEvent;
import org.spigotmc.event.entity.EntityMountEvent;
public class KnockbackRegionController implements Listener {
@@ -131,6 +132,27 @@ public class KnockbackRegionController implements Listener {
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
void onEntityMount(EntityMountEvent event) {
if (!(event.getEntity() instanceof Player player)) {
return;
}
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
if (!this.regionProvider.isInRegion(event.getMount().getLocation())) {
return;
}
event.setCancelled(true);
this.noticeService.create()
.player(player.getUniqueId())
.notice(config -> config.messagesSettings.cantEnterOnRegion)
.send();
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
void onTag(FightTagEvent event) {
Player player = this.server.getPlayer(event.getPlayer());
@@ -1,15 +0,0 @@
package com.eternalcode.combat.config.implementation;
import static org.junit.jupiter.api.Assertions.assertFalse;
import org.junit.jupiter.api.Test;
class SignEditingSettingsTest {
@Test
void shouldDisableSignEditingBlockerByDefault() {
SignEditingSettings signEditingSettings = new SignEditingSettings();
assertFalse(signEditingSettings.disableSignEditingDuringCombat);
}
}
@@ -1,34 +0,0 @@
package com.eternalcode.combat.fight.blocker;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.bukkit.Material;
import org.junit.jupiter.api.Test;
class SignEditingBlockerTest {
@Test
void shouldRecognizeStandingSignMaterials() {
assertTrue(SignEditingBlocker.isSign(Material.OAK_SIGN));
assertTrue(SignEditingBlocker.isSign(Material.CRIMSON_SIGN));
}
@Test
void shouldRecognizeWallSignMaterials() {
assertTrue(SignEditingBlocker.isSign(Material.OAK_WALL_SIGN));
assertTrue(SignEditingBlocker.isSign(Material.WARPED_WALL_SIGN));
}
@Test
void shouldIgnoreNonSignMaterials() {
assertFalse(SignEditingBlocker.isSign(Material.CHEST));
assertFalse(SignEditingBlocker.isSign(Material.ENDER_PEARL));
}
@Test
void shouldRecognizeEnderPearlMaterial() {
assertTrue(SignEditingBlocker.isEnderPearl(Material.ENDER_PEARL));
assertFalse(SignEditingBlocker.isEnderPearl(Material.OAK_SIGN));
}
}
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500