Compare commits

..
Author SHA1 Message Date
Martin Sulikowski bd8dfbcc83 Improve CI, GitHub metadata, README 2026-08-19 18:09:26 +02:00
39 changed files with 175 additions and 625 deletions

No files matched your search

+1 -2
View File
@@ -1,2 +1 @@
* @EternalCodeTeam/eternalcodeteam-maintainers
/.github/CODEOWNERS @EternalCodeTeam/eternalcodeteam-maintainers
* @EternalCodeTeam/team-maintainer
+33 -43
View File
@@ -1,64 +1,54 @@
# 👋 Contributing to EternalCombat
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.
Thanks for taking the time to contribute! This page covers everything you need to get started.
## 🛠️ Development Setup
## 🛠️ Requirements
To start contributing, you'll need to set up your environment:
- **JDK 21** (the project targets Java 21)
- Git
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.
The Gradle wrapper is committed, so you don't need Gradle installed.
### 📥 Getting the Code
## 🔨 Building and testing
```bash
# Fork the repository on GitHub first!
# https://github.com/EternalCodeTeam/EternalCombat/fork
git clone https://github.com/YOUR-USERNAME/EternalCombat.git
git clone https://github.com/EternalCodeTeam/EternalCombat.git
cd EternalCombat
```
### 🔨 Building the Project
Run this command in your terminal:
```bash
./gradlew build
```
## 🤝 How to Contribute
`./gradlew build` compiles both modules, runs the unit tests and produces the shaded plugin jar in
`eternalcombat-plugin/build/libs/`.
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.
To run only the tests:
## 📏 Code Style
```bash
./gradlew test
```
Make sure that your code adheres to the plugin's existing coding style.
To try your changes on a real server, start a Paper server with the plugin and its dependencies
already installed:
## 🐛 Reporting Bugs
```bash
./gradlew runServer
```
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.
## 📏 Code style
## 💡 Feature Requests
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.
Have an idea? Use the **Feature Request** issue template.
- Explain *why* this feature is useful.
- Describe how it should look or behave.
## 🚀 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.
## 📄 License
By contributing, you agree that your contributions will be licensed under the project's [License](../LICENSE).
By contributing, you agree that your contributions will be licensed under the
[Apache License 2.0](../LICENSE).
+13 -57
View File
@@ -1,20 +1,14 @@
name: 🐛 Bug Report
description: File a bug report to help us improve EternalCombat
description: Report something that is broken in 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: Bug description
description: A clear and concise description of what the bug is.
placeholder: I was doing X and Y happened...
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.
validations:
required: true
@@ -22,63 +16,25 @@ body:
id: reproduction
attributes:
label: Steps to reproduce
description: Please provide a step-by-step guide to reproduce the issue.
placeholder: |
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)
1. Start the server
2. Run /combatlog tag <player>
3. See the error
validations:
required: true
- type: input
id: minecraft-version
id: versions
attributes:
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
label: Versions
description: Server software and version, plus the EternalCombat version.
placeholder: Paper 1.21.11, EternalCombat 2.5.1
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs / Stack Trace
description: Please paste any relevant logs or stack traces here. Use a pastebin service if the log is too long.
label: Logs
description: Any relevant errors or stack traces. Use a paste service for long logs.
render: shell
+5 -25
View File
@@ -3,36 +3,16 @@ description: Suggest an idea for EternalCombat
title: "[Feature]: "
labels: ["feature"]
body:
- type: markdown
attributes:
value: |
Thanks for suggesting a new feature! Please explain your idea in detail.
- type: textarea
id: problem
id: idea
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...
label: What would you like to see?
description: Describe the feature and the problem it would solve.
validations:
required: true
- type: textarea
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
id: context
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here.
description: Optional — alternatives you considered, examples, or screenshots.
+6 -30
View File
@@ -1,35 +1,11 @@
---
name: Pull Request
about: Submit a contribution to EternalCombat
title: 'GH-{NUMBER} <title>'
labels: []
assignees: []
---
## What changed?
## 📝 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
-->
<!-- A short description of the change. -->
<!-- Describe your changes clearly and concisely. Why is this change needed? What does it accomplish? -->
## Why?
## 🎯 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.)
<!-- The problem this solves, or the issue it closes (e.g. Closes #123). -->
**Test Environment:**
- **Minecraft Version:** <!-- e.g. 1.20.4 -->
- **Java Version:** <!-- e.g. 21 -->
- **Platform:** <!-- e.g. Windows/Linux/MacOS -->
## Anything else?
## 📸 Screenshots (if applicable)
<!-- Optional: how you tested it, screenshots, or notes for reviewers. -->
+17
View File
@@ -0,0 +1,17 @@
# 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.
+10 -17
View File
@@ -5,9 +5,12 @@ on:
branches: [ "master" ]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
@@ -15,34 +18,24 @@ 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 17
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: '25'
java-version: '21'
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 shadowJar
run: ./gradlew build
- name: Upload build artifact
uses: actions/upload-artifact@v7
with:
name: ${{ env.FULL_ARTIFACT_NAME }}
path: eternalcombat-plugin/build/libs/*.jar
path: eternalcombat-plugin/build/libs/EternalCombat*.jar
if-no-files-found: error
archive: false
+7 -3
View File
@@ -7,6 +7,10 @@ on:
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-latest
@@ -20,12 +24,12 @@ jobs:
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: '25'
java-version: '21'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Publish to Hangar
run: ./gradlew :eternalcombat-plugin:publishPluginPublicationToHangar
env:
+7 -3
View File
@@ -7,6 +7,10 @@ on:
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-latest
@@ -20,12 +24,12 @@ jobs:
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: '25'
java-version: '21'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Publish to Modrinth
run: ./gradlew :eternalcombat-plugin:modrinth
env:
+15 -7
View File
@@ -1,28 +1,36 @@
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
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'temurin'
java-version: 25
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Publish with Gradle
run: ./gradlew clean build eternalcombat-api:publish
run: ./gradlew build :eternalcombat-api:publish
env:
ETERNAL_CODE_MAVEN_USERNAME: ${{ secrets.ETERNAL_CODE_MAVEN_USERNAME }}
ETERNAL_CODE_MAVEN_PASSWORD: ${{ secrets.ETERNAL_CODE_MAVEN_PASSWORD }}
+17 -17
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://docs.eternalcode.pl/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://eternalcode.pl/docs/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.17.1 to 26.2**, but it should work seamlessly on most
EternalCombat 2.0 has been tested on Minecraft versions **1.19.3 to 26.1.2**, but it should work seamlessly on most
other versions too.
If you run into any compatibility issues, please let us know in
the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues).
@@ -45,13 +45,9 @@ fair, heart-pounding battles that keep players on their toes. Here’s a rundown
in action:
<img alt="Combat log anti logout feature" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/combatlog.gif?raw=true" width="420">
- **Support for newest features**
Our plugin features cooldown and delays for tridents, spears and much more. With **out of the box** configurable feats.
<img alt="Trident and spear cooldown feature" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/spear.gif?raw=true" width="420">
- **Customize combat experience**
Add custom effects to players in combat or death. Everything should be configurable and user-friendly:
<img alt="Lightning strikes when players die" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/lightning.gif?raw=true" width="420">
- **Customize combat experience**
Add custom effects to players in combat or death. Everything should be configurable and user-friendly:
<img alt="Lightning strikes when players die" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/lightning.gif?raw=true" width="420">
<img alt="Flare indicates players death location" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/flare.gif?raw=true" width="420">
- **Spawn Protection (Configurable)**
@@ -61,7 +57,7 @@ fair, heart-pounding battles that keep players on their toes. Here’s a rundown
- **Crystal PvP support**
Engage in intense Crystal PvP battles without worrying about players logging out mid-fight! EternalCombat keeps
everyone in the game until the last anchor hit. Check it out:
everyone in the game until the last anchor hit. Check it out:
<img alt="Crystal PvP showcase" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/crystals.gif?raw=true" width="420">
- **Fully Customizable Combat**
@@ -95,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 exclude `excludeAdminsFromCombat` is set to `true` users with this permission will not be tagged |
| `eternalcombat.bypass` | When `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.
@@ -143,7 +139,7 @@ With Maven:
With Gradle:
```kts
compileOnly("com.eternalcode:eternalcombat-api:2.6.0-SNAPSHOT")
compileOnly("com.eternalcode:eternalcombat-api:2.5.2-SNAPSHOT")
```
With Maven:
@@ -153,7 +149,7 @@ With Maven:
<dependency>
<groupId>com.eternalcode</groupId>
<artifactId>eternalcombat-api</artifactId>
<version>2.6.0-SNAPSHOT</version>
<version>2.5.2-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
```
@@ -167,8 +163,12 @@ 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).
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 MiB

+2 -2
View File
@@ -9,9 +9,9 @@ repositories {
dependencies {
implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.6.1")
implementation("com.modrinth.minotaur:com.modrinth.minotaur.gradle.plugin:2.10.0")
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.20")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.4.10")
implementation("net.minecrell:plugin-yml:0.6.0")
implementation("xyz.jpenilla:run-task:3.1.0")
}
+4 -3
View File
@@ -19,16 +19,17 @@ object Versions {
const val XSERIES = "13.8.0"
const val CAFFEINE = "3.3.0"
const val CAFFEINE = "3.2.4"
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 LANDS_API = "7.25.4"
const val GRIEF_PREVENTION = "16.18.7"
const val WORLDEDIT = "3ISh7ADm" //cannot use numeric version bc of duplicated version on modrinth
const val PACKETEVENTS = "2.14.0"
const val PACKETEVENTS = "2.11.1"
const val WORLDGUARD = "7.0.15-beta-01"
const val LUCKPERMS = "5.5.17"
}
@@ -7,6 +7,7 @@ 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 {
@@ -3,7 +3,7 @@ plugins {
}
group = "com.eternalcode"
version = "2.6.0-SNAPSHOT"
version = "2.5.2-SNAPSHOT"
tasks.compileJava {
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
@@ -4,7 +4,7 @@ plugins {
}
group = "com.eternalcode"
version = "2.6.0-SNAPSHOT"
version = "2.5.2-SNAPSHOT"
java {
withSourcesJar()
@@ -13,11 +13,10 @@ tasks {
languageVersion.set(JavaLanguageVersion.of(25))
})
minecraftVersion("26.3")
downloadPlugins.modrinth("WorldEdit", "7.4.6")
downloadPlugins.modrinth("PacketEvents", "2.14.0+spigot")
downloadPlugins.modrinth("WorldGuard", "7.0.20")
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("GriefPrevention", Versions.GRIEF_PREVENTION)
}
}
@@ -1,11 +0,0 @@
package com.eternalcode.combat.fight.spear;
import java.time.Duration;
import java.util.UUID;
public interface SpearService {
boolean isOnCooldown(UUID player);
void saveCooldown(UUID player);
Duration getRemainingCooldown(UUID player);
void removeCooldown(UUID player);
}
+1 -16
View File
@@ -8,6 +8,7 @@ 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")
@@ -62,11 +63,6 @@ dependencies {
// Lands
compileOnly("com.github.angeschossen:LandsAPI:${Versions.LANDS_API}")
// GriefPrevention
compileOnly("com.github.GriefPrevention:GriefPrevention:${Versions.GRIEF_PREVENTION}") {
isTransitive = false
}
// Multification
implementation("com.eternalcode:multification-paper:${Versions.MULTIFICATION}")
implementation("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION}")
@@ -101,11 +97,6 @@ paper {
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
}
register("GriefPrevention") {
required = false
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
}
register("PlaceholderAPI") {
required = false
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
@@ -117,12 +108,6 @@ tasks {
named("generatePaperPluginDescription") {
notCompatibleWithConfigurationCache("The plugin-yml paper generator reads Task.project during execution.")
}
runPaper.folia.registerTask {
minecraftVersion("26.3")
downloadPlugins.modrinth("PacketEvents", "${Versions.PACKETEVENTS}+spigot")
}
}
tasks.shadowJar {
@@ -8,8 +8,8 @@ import com.eternalcode.combat.border.animation.particle.ParticleController;
import com.eternalcode.combat.bridge.BridgeService;
import com.eternalcode.combat.config.ConfigService;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.crystalpvp.EndCrystalController;
import com.eternalcode.combat.fight.crystalpvp.RespawnAnchorController;
import com.eternalcode.combat.crystalpvp.EndCrystalListener;
import com.eternalcode.combat.crystalpvp.RespawnAnchorListener;
import com.eternalcode.combat.event.EventManager;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightManagerImpl;
@@ -49,9 +49,6 @@ import com.eternalcode.combat.fight.logout.LogoutService;
import com.eternalcode.combat.fight.pearl.PearlController;
import com.eternalcode.combat.fight.pearl.PearlService;
import com.eternalcode.combat.fight.pearl.PearlServiceImpl;
import com.eternalcode.combat.fight.spear.SpearLungeController;
import com.eternalcode.combat.fight.spear.SpearService;
import com.eternalcode.combat.fight.spear.SpearServiceImpl;
import com.eternalcode.combat.fight.tagout.FightTagOutCommand;
import com.eternalcode.combat.fight.tagout.FightTagOutController;
import com.eternalcode.combat.fight.tagout.FightTagOutService;
@@ -154,8 +151,6 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
BorderService borderService = new BorderServiceImpl(scheduler, server, regionProvider, eventManager, () -> pluginConfig.border);
KnockbackService knockbackService = new KnockbackService(pluginConfig, scheduler, regionProvider);
SpearService spearService = new SpearServiceImpl(pluginConfig.spear);
this.liteCommands = LiteBukkitFactory.builder(FALLBACK_PREFIX, this, server)
.message(LiteBukkitMessages.PLAYER_NOT_FOUND, pluginConfig.messagesSettings.playerNotFound)
.message(LiteBukkitMessages.PLAYER_ONLY, pluginConfig.messagesSettings.admin.onlyForPlayers)
@@ -207,8 +202,8 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
new BorderTriggerController(borderService, () -> pluginConfig.border, fightManager, server, scheduler),
new ParticleController(borderService, () -> pluginConfig.border.particle, scheduler, server),
new BorderBlockController(borderService, () -> pluginConfig.border.block, scheduler, server),
new EndCrystalController(this, this.fightManager, pluginConfig),
new RespawnAnchorController(this, this.fightManager, pluginConfig),
new EndCrystalListener(this, this.fightManager, pluginConfig),
new RespawnAnchorListener(this, this.fightManager, pluginConfig),
new FireworkController(this.fightManager, pluginConfig, noticeService),
new InventoryContainersBlocker(this.fightManager, pluginConfig, noticeService),
new SignEditingBlocker(this.fightManager, pluginConfig),
@@ -221,8 +216,6 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
new KnockbackMountController(noticeService, this.regionProvider, this.fightManager).register(this);
new SpearLungeController(this, fightManager, spearService, pluginConfig, noticeService);
eventManager.subscribe(
PlayerDeathEvent.class,
pluginConfig.drop.dropEventPriority,
@@ -8,6 +8,8 @@ import org.bukkit.plugin.Plugin;
public final class CombatSchedulerAdapter {
private static final String FOLIA_CLASS = "io.papermc.paper.threadedregions.RegionizedServer";
private CombatSchedulerAdapter() {
throw new UnsupportedOperationException("This is an utility class and cannot be instantiated");
}
@@ -15,12 +17,14 @@ public final class CombatSchedulerAdapter {
public static MinecraftScheduler getAdaptiveScheduler(Plugin plugin) {
Logger logger = plugin.getLogger();
if (FoliaChecker.isFolia()) {
try {
Class.forName(FOLIA_CLASS);
logger.info("» Detected Folia environment. Using FoliaScheduler.");
return new FoliaSchedulerImpl(plugin);
}
logger.info("» Detected Bukkit/Paper environment. Using BukkitScheduler.");
return new BukkitSchedulerImpl(plugin);
catch (ClassNotFoundException exception) {
logger.info("» Detected Bukkit/Paper environment. Using BukkitScheduler.");
return new BukkitSchedulerImpl(plugin);
}
}
}
@@ -1,25 +0,0 @@
package com.eternalcode.combat;
public final class FoliaChecker {
private static final String FOLIA_CLASS = "io.papermc.paper.threadedregions.RegionizedServer";
private static final boolean IS_FOLIA = detectFoliaEnvironment();
private FoliaChecker() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
public static boolean isFolia() {
return IS_FOLIA;
}
private static boolean detectFoliaEnvironment() {
try {
Class.forName(FOLIA_CLASS);
return true;
}
catch (ClassNotFoundException exception) {
return false;
}
}
}
@@ -7,7 +7,6 @@ import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.region.CompositeRegionProvider;
import com.eternalcode.combat.region.bukkit.DefaultRegionProvider;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.combat.region.griefprevention.GriefPreventionRegionProvider;
import com.eternalcode.combat.region.worldguard.WorldGuardRegionProvider;
import java.util.ArrayList;
import java.util.List;
@@ -56,12 +55,6 @@ public class BridgeService {
() -> this.logger.warning("WorldGuard not found; skipping WorldGuardRegionProvider.")
);
this.initialize(
"GriefPrevention",
() -> providers.add(new GriefPreventionRegionProvider(this.config)),
() -> this.logger.warning("GriefPrevention not found; skipping GriefPreventionRegionProvider.")
);
if (providers.isEmpty()) {
providers.add(new DefaultRegionProvider(this.config.regions.restrictedRegionRadius));
this.logger.warning("No region plugin found; using DefaultRegionProvider.");
@@ -6,7 +6,6 @@ import com.eternalcode.combat.fight.drop.DropSettings;
import com.eternalcode.combat.fight.effect.FightEffectSettings;
import com.eternalcode.combat.fight.knockback.KnockbackSettings;
import com.eternalcode.combat.fight.pearl.PearlSettings;
import com.eternalcode.combat.fight.spear.SpearSettings;
import com.eternalcode.combat.fight.trident.TridentSettings;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
@@ -45,13 +44,6 @@ public class PluginConfig extends OkaeriConfig {
})
public TridentSettings trident = new TridentSettings();
@Comment({
" ",
"# Settings related to Spears with lunge",
"# Set cooldown for spear lunging"
})
public SpearSettings spear = new SpearSettings();
@Comment({
" ",
"# Custom effects applied during combat.",
@@ -13,8 +13,7 @@ public class RegionSettings extends OkaeriConfig {
public List<String> blockedRegions = Collections.singletonList("your_region");
@Comment({
"# Prevent players from entering regions where PVP is disabled by the region plugin",
"# (WorldGuard PVP flag, GriefPrevention no-combat-in-claims settings).",
"# Prevent players from entering regions where PVP is disabled by WorldGuard.",
"# Set to 'true' to enforce this restriction, or 'false' to allow PVP in all regions."
})
public boolean preventPvpInRegions = true;
@@ -1,4 +1,4 @@
package com.eternalcode.combat.fight.crystalpvp;
package com.eternalcode.combat.crystalpvp;
import java.util.Optional;
import java.util.UUID;
@@ -1,4 +1,4 @@
package com.eternalcode.combat.fight.crystalpvp;
package com.eternalcode.combat.crystalpvp;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
@@ -1,4 +1,4 @@
package com.eternalcode.combat.fight.crystalpvp;
package com.eternalcode.combat.crystalpvp;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
@@ -12,15 +12,15 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.plugin.Plugin;
import static com.eternalcode.combat.fight.crystalpvp.CrystalPvpConstants.CRYSTAL_METADATA;
import static com.eternalcode.combat.crystalpvp.CrystalPvpConstants.CRYSTAL_METADATA;
public class EndCrystalController implements Listener {
public class EndCrystalListener implements Listener {
private final Plugin plugin;
private final FightManager fightManager;
private final PluginConfig pluginConfig;
public EndCrystalController(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) {
public EndCrystalListener(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) {
this.plugin = plugin;
this.fightManager = fightManager;
this.pluginConfig = pluginConfig;
@@ -1,4 +1,4 @@
package com.eternalcode.combat.fight.crystalpvp;
package com.eternalcode.combat.crystalpvp;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
@@ -16,16 +16,16 @@ import org.bukkit.event.entity.EntityDamageByBlockEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import static com.eternalcode.combat.fight.crystalpvp.CrystalPvpConstants.ANCHOR_METADATA;
import static com.eternalcode.combat.crystalpvp.CrystalPvpConstants.ANCHOR_METADATA;
public class RespawnAnchorController implements Listener {
public class RespawnAnchorListener implements Listener {
private final Plugin plugin;
private final FightManager fightManager;
private final PluginConfig pluginConfig;
public RespawnAnchorController(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) {
public RespawnAnchorListener(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) {
this.plugin = plugin;
this.fightManager = fightManager;
this.pluginConfig = pluginConfig;
@@ -1,6 +1,5 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.FoliaChecker;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
@@ -56,16 +55,6 @@ public class FightTagController implements Listener {
return;
}
UUID targetId = attackedPlayerByPerson.getUniqueId();
UUID attackerId = attacker.getUniqueId();
if (
targetId.equals(attackerId)
&& FoliaChecker.isFolia()
) {
return;
}
if (this.config.combat.disableFlying) {
if (attackedPlayerByPerson.isFlying()) {
attackedPlayerByPerson.setFlying(false);
@@ -79,9 +68,11 @@ public class FightTagController implements Listener {
}
Duration combatTime = this.config.settings.combatTimerDuration;
UUID attackedUniqueId = attackedPlayerByPerson.getUniqueId();
UUID attackerUniqueId = attacker.getUniqueId();
this.fightManager.tag(targetId, combatTime, CauseOfTag.PLAYER, attackerId);
this.fightManager.tag(attackerId, combatTime, CauseOfTag.PLAYER, targetId);
this.fightManager.tag(attackedUniqueId, combatTime, CauseOfTag.PLAYER, attackerUniqueId);
this.fightManager.tag(attackerUniqueId, combatTime, CauseOfTag.PLAYER, attackedUniqueId);
}
@@ -1,6 +1,6 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.fight.crystalpvp.CrystalPvpConstants;
import com.eternalcode.combat.crystalpvp.CrystalPvpConstants;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.config.implementation.PluginConfig;
@@ -1,93 +0,0 @@
package com.eternalcode.combat.fight.spear;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NoticeService;
import com.eternalcode.combat.util.DurationUtil;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.Plugin;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.UUID;
public class SpearLungeController implements Listener {
private final SpearService spearService;
public SpearLungeController(
Plugin plugin,
FightManager fightManager,
SpearService spearService,
PluginConfig config,
NoticeService noticeService
) {
this.spearService = spearService;
try {
Class<? extends Event> lungeEventClass = Class.forName("io.papermc.paper.event.entity.EntityLungeEvent").asSubclass(Event.class);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle getEntityHandle = lookup.findVirtual(lungeEventClass, "getEntity", MethodType.methodType(Entity.class));
MethodHandle setCancelledHandle = lookup.findVirtual(lungeEventClass, "setCancelled", MethodType.methodType(void.class, boolean.class));
Bukkit.getPluginManager().registerEvent(
lungeEventClass,
this,
EventPriority.NORMAL,
(listener, event) -> {
if (!config.spear.lungeCooldown || !lungeEventClass.isInstance(event)) {
return;
}
try {
Entity entity = (Entity) getEntityHandle.invoke(event);
if (!(entity instanceof Player player)) {
return;
}
UUID playerId = player.getUniqueId();
if (config.spear.onlyForFight && !fightManager.isInCombat(playerId)) {
return;
}
if (this.spearService.isOnCooldown(playerId)) {
setCancelledHandle.invoke(event, true);
noticeService.create()
.player(playerId)
.notice(config.spear.lungeOnCooldown)
.placeholder("{TIME}", DurationUtil.format(spearService.getRemainingCooldown(playerId), !config.spear.useMillis))
.send();
return;
}
spearService.saveCooldown(playerId);
} catch (Throwable e) {
plugin.getLogger().warning("Failed to handle EntityLungeEvent: " + e.getMessage());
}
},
plugin
);
} catch (ClassNotFoundException e) {
// Silently ignore: Server version is too old to have EntityLungeEvent
} catch (Throwable e) {
plugin.getLogger().warning("Failed to hook EntityLungeEvent: " + e.getMessage());
}
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
this.spearService.removeCooldown(event.getPlayer().getUniqueId());
}
}
@@ -1,49 +0,0 @@
package com.eternalcode.combat.fight.spear;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class SpearServiceImpl implements SpearService {
private final SpearSettings settings;
private final Map<UUID, Instant> cooldowns = new ConcurrentHashMap<>();
public SpearServiceImpl(SpearSettings settings) {
this.settings = settings;
}
@Override
public boolean isOnCooldown(UUID uuid) {
return !this.getRemainingCooldown(uuid).isZero();
}
@Override
public void saveCooldown(UUID uuid) {
this.cooldowns.put(uuid, Instant.now().plus(this.settings.lungeCooldownDuration));
}
@Override
public Duration getRemainingCooldown(UUID uuid) {
Instant expiration = this.cooldowns.get(uuid);
if (expiration == null) {
return Duration.ZERO;
}
Instant now = Instant.now();
if (now.isAfter(expiration)) {
this.cooldowns.remove(uuid);
return Duration.ZERO;
}
return Duration.between(now, expiration);
}
@Override
public void removeCooldown(UUID uuid) {
this.cooldowns.remove(uuid);
}
}
@@ -1,30 +0,0 @@
package com.eternalcode.combat.fight.spear;
import com.eternalcode.multification.notice.Notice;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
public class SpearSettings extends OkaeriConfig {
@Comment("# Should spears have lunge cooldown or delay between uses? True for cooldown, False for vanilla mechanics")
public boolean lungeCooldown = false;
@Comment("# Should cooldown be applied only during fights")
public boolean onlyForFight = true;
@Comment("# Duration of cooldown")
public Duration lungeCooldownDuration = Duration.ofSeconds(5);
@Comment("# Should milliseconds be used for last second of cooldown for more precise time")
public boolean useMillis = true;
@Comment({
"# Notice sent to the players that try to use spears before cooldown ends.",
"# Placeholder: {TIME} - time left of cooldown"
})
public Notice lungeOnCooldown = Notice.builder()
.chat("<dark_red>Spear cannot be used for next <red>{TIME}</red></dark_red>")
.build();
}
@@ -1,34 +0,0 @@
package com.eternalcode.combat.region.griefprevention;
import com.eternalcode.combat.region.Point;
import com.eternalcode.combat.region.Region;
import me.ryanhamshire.GriefPrevention.Claim;
import org.bukkit.Location;
import org.bukkit.World;
record GriefPreventionRegion(World world, Claim claim) implements Region {
@Override
public Point getCenter() {
Location min = this.claim.getLesserBoundaryCorner();
Location max = this.claim.getGreaterBoundaryCorner();
double x = (min.getX() + max.getX()) / 2.0;
double z = (min.getZ() + max.getZ()) / 2.0;
return new Point(this.world, x, z);
}
@Override
public Location getMin() {
Location min = this.claim.getLesserBoundaryCorner();
return new Location(this.world, min.getBlockX(), this.world.getMinHeight(), min.getBlockZ());
}
@Override
public Location getMax() {
Location max = this.claim.getGreaterBoundaryCorner();
return new Location(this.world, max.getBlockX(), this.world.getMaxHeight() - 1, max.getBlockZ());
}
}
@@ -1,93 +0,0 @@
package com.eternalcode.combat.region.griefprevention;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.region.Region;
import com.eternalcode.combat.region.RegionProvider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Optional;
import java.util.TreeSet;
import me.ryanhamshire.GriefPrevention.Claim;
import me.ryanhamshire.GriefPrevention.GriefPrevention;
import org.bukkit.Location;
import org.bukkit.World;
public class GriefPreventionRegionProvider implements RegionProvider {
private final TreeSet<String> claims = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
private final PluginConfig pluginConfig;
public GriefPreventionRegionProvider(PluginConfig pluginConfig) {
this.claims.addAll(pluginConfig.regions.blockedRegions);
this.pluginConfig = pluginConfig;
}
@Override
public Optional<Region> getRegion(Location location) {
Claim claim = GriefPrevention.instance.dataStore.getClaimAt(location, true, null);
if (claim == null || !this.isCombatClaim(claim)) {
return Optional.empty();
}
return Optional.of(new GriefPreventionRegion(location.getWorld(), claim));
}
@Override
public Collection<Region> getRegions(World world) {
List<Region> regions = new ArrayList<>();
for (Claim claim : this.snapshotClaims()) {
if (!world.equals(claim.getLesserBoundaryCorner().getWorld())) {
continue;
}
if (this.isCombatClaim(claim)) {
regions.add(new GriefPreventionRegion(world, claim));
}
}
return regions;
}
private boolean isCombatClaim(Claim claim) {
if (this.claims.contains(String.valueOf(claim.getID()))) {
return true;
}
if (!this.pluginConfig.regions.preventPvpInRegions) {
return false;
}
World world = claim.getLesserBoundaryCorner().getWorld();
GriefPrevention griefPrevention = GriefPrevention.instance;
if (world == null || !griefPrevention.pvpRulesApply(world)) {
return false;
}
if (!claim.isAdminClaim()) {
return griefPrevention.config_pvp_noCombatInPlayerLandClaims;
}
if (claim.parent == null) {
return griefPrevention.config_pvp_noCombatInAdminLandClaims;
}
return griefPrevention.config_pvp_noCombatInAdminSubdivisions;
}
// GriefPrevention returns a live view of its claim list, and the border index reads regions asynchronously.
// A failed snapshot is retried by the next border index refresh, so an empty result is safe here.
private List<Claim> snapshotClaims() {
try {
return List.copyOf(GriefPrevention.instance.dataStore.getClaims());
}
catch (ConcurrentModificationException exception) {
return List.of();
}
}
}
+1 -1
View File
@@ -4,4 +4,4 @@ org.gradle.configuration-cache=true
org.gradle.configuration-cache.parallel=true
# Platforms
paperVersion=1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11,26.1.1,26.1.2,26.2
paperVersion=1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11,26.1.1,26.1.2
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500