Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc11f46275 | ||
|
|
35e19d0eed | ||
|
|
b2776936ed | ||
|
|
d2aa5dfa84 | ||
|
|
5c88af692d | ||
|
|
390195edb2 | ||
|
|
8808331f0a | ||
|
|
99b3a5aa27 | ||
|
|
9d1716d9db | ||
|
|
7b94ab4b7a | ||
|
|
daee189865 | ||
|
|
1aef1e6f1d | ||
|
|
788a2a668c | ||
|
|
93d7ffdd0f | ||
|
|
fb6db7a008 | ||
|
|
7d8a01b7d7 | ||
|
|
08c7482cee | ||
|
|
7e24e5afa6 | ||
|
|
bb130ae10e | ||
|
|
2c5b007d02 | ||
|
|
51b365df1b | ||
|
|
4f23105ba1 | ||
|
|
d0c221c184 | ||
|
|
bf529dce32 | ||
|
|
74ec795581 | ||
|
|
b3550110c8 | ||
|
|
326f7363f1 | ||
|
|
5645f0473b | ||
|
|
d0336441ce | ||
|
|
bc6df07609 | ||
|
|
440f77e387 | ||
|
|
3dc115c1f6 | ||
|
|
591ba20221 | ||
|
|
4d2d2222aa | ||
|
|
62975caf58 | ||
|
|
d7c7ac2916 | ||
|
|
d250a6a720 | ||
|
|
5c6ac79579 | ||
|
|
59f0612405 | ||
|
|
84d1d5aeb6 | ||
|
|
e73864923f | ||
|
|
195e45ed34 | ||
|
|
459ff06f18 | ||
|
|
4a153d5557 | ||
|
|
3e5b060923 | ||
|
|
03768ccd8d | ||
|
|
ba59d85996 | ||
|
|
1c432cbcb3 | ||
|
|
de6b76526c | ||
|
|
7de452ac15 | ||
|
|
7e578d63f7 | ||
|
|
e5f86c53a2 | ||
|
|
22260e87d0 | ||
|
|
fe73466cfb | ||
|
|
c299aed434 | ||
|
|
8dbaf9ae98 | ||
|
|
db4ab96377 | ||
|
|
0dca7e7d42 | ||
|
|
bb080b5ce9 | ||
|
|
9f4ae311a7 | ||
|
|
96deb783b7 | ||
|
|
bee80d22aa | ||
|
|
4202e86eaa | ||
|
|
e109b38705 | ||
|
|
9bba5bd418 | ||
|
|
77940ca7b6 | ||
|
|
a4cc6d0e8b | ||
|
|
2028c5eafd | ||
|
|
042c5848df | ||
|
|
c30860070f | ||
|
|
20c3515082 | ||
|
|
07b22a1141 | ||
|
|
2fc9bc580b | ||
|
|
7730956dbc | ||
|
|
48ef3e5d0a | ||
|
|
abd097401f | ||
|
|
1d0d182cc8 | ||
|
|
60b4e6ee9c | ||
|
|
b096cb5d6f | ||
|
|
d596202278 | ||
|
|
b879497840 | ||
|
|
ccad96ae96 | ||
|
|
0e782a0cf5 | ||
|
|
0979d449c6 | ||
|
|
dee65e5f59 | ||
|
|
01d3491d44 | ||
|
|
9896069577 | ||
|
|
f682508172 | ||
|
|
a988abcd17 | ||
|
|
b0fa197e99 | ||
|
|
5300b5a007 | ||
|
|
c1f282832c | ||
|
|
972fc52e69 | ||
|
|
b79ed28edf | ||
|
|
025f49685c | ||
|
|
4d957d2c8a | ||
|
|
d7a04ef0c2 | ||
|
|
0bd9911df3 | ||
|
|
3062378c66 | ||
|
|
0799081813 | ||
|
|
16a2df1a08 | ||
|
|
cd3681f8f6 | ||
|
|
885f514e17 | ||
|
|
5b84881b94 | ||
|
|
e69a42ba0f | ||
|
|
9783d7a025 | ||
|
|
99a93ce249 | ||
|
|
1c2797225a | ||
|
|
1af5710ac9 | ||
|
|
1938ae409d | ||
|
|
88fa996202 | ||
|
|
e3c4f2c26d | ||
|
|
e87060d2b9 | ||
|
|
dfe70a65d7 | ||
|
|
fbade09087 | ||
|
|
660b8b5519 | ||
|
|
13eb6dead2 | ||
|
|
eafb4c9318 | ||
|
|
bfc632d518 | ||
|
|
02d5a2f46d | ||
|
|
9e3f28ee10 | ||
|
|
e5fb0d4b32 | ||
|
|
3325a2f8d3 | ||
|
|
a0bd8494f8 | ||
|
|
08cfe7c0c4 | ||
|
|
ed30d43736 | ||
|
|
b6347517b6 | ||
|
|
3f729beffe | ||
|
|
337eb7d765 | ||
|
|
b02231bc0c | ||
|
|
ee6f5aecd3 | ||
|
|
6eb89607bf | ||
|
|
9f33de088b | ||
|
|
815b63c7bf | ||
|
|
410289a2b8 | ||
|
|
e40a37e2ff | ||
|
|
1bae5c547d | ||
|
|
986895d5a4 | ||
|
|
4776714893 | ||
|
|
6154f26977 | ||
|
|
2f010ee978 | ||
|
|
7d5d074811 | ||
|
|
8315e198b3 | ||
|
|
b0cbdc04c8 | ||
|
|
f46f9ca268 | ||
|
|
d789d3b47e | ||
|
|
dd7eaf2de8 | ||
|
|
a985774272 | ||
|
|
fa000b4a3a | ||
|
|
80edd3b425 | ||
|
|
fc4a6c28a3 | ||
|
|
e4eb847b56 | ||
|
|
f5884119d6 | ||
|
|
0cd9d50086 | ||
|
|
7043d7baff | ||
|
|
26d3f5b1ce | ||
|
|
455b66e136 | ||
|
|
881d9b633a | ||
|
|
ac80f1bb0c | ||
|
|
cd36abae37 | ||
|
|
817b907170 | ||
|
|
b9dfb293e4 | ||
|
|
11bfddfc9f | ||
|
|
74d0f487c3 | ||
|
|
f4cc51e9a1 | ||
|
|
e579ca4a22 | ||
|
|
4c13a61752 | ||
|
|
dad09ba196 | ||
|
|
690678c1ea | ||
|
|
40cac29eb0 | ||
|
|
b9c625cdd6 | ||
|
|
861aad1852 | ||
|
|
4c30b7dad6 | ||
|
|
7a152e0624 | ||
|
|
487fc971bc | ||
|
|
ba5fa97e1e | ||
|
|
f2ab289794 | ||
|
|
241898d62a | ||
|
|
9e914d6166 | ||
|
|
f9bc56bf33 | ||
|
|
d6bf4e628c | ||
|
|
b87e64090a | ||
|
|
7ce6d8bd95 | ||
|
|
2dc6cbf40c | ||
|
|
411e78dda0 | ||
|
|
31d41c900f | ||
|
|
a8b48b8804 | ||
|
|
312d8e08e6 | ||
|
|
7f30d79301 | ||
|
|
05095857ca | ||
|
|
d9f98d3aa2 | ||
|
|
bae1b805a7 | ||
|
|
0b119a28c9 | ||
|
|
a235b799e5 | ||
|
|
9b8b0a8513 | ||
|
|
0da9208323 | ||
|
|
64e366ccd3 | ||
|
|
0540e39af2 | ||
|
|
824313c6ec | ||
|
|
4cd83670a9 | ||
|
|
7f130493f7 | ||
|
|
8c5038e3a4 | ||
|
|
bd4fd131c5 | ||
|
|
f338fc4109 | ||
|
|
da555c65e9 | ||
|
|
726f1aa78c | ||
|
|
0539e69236 | ||
|
|
d54bff5516 | ||
|
|
5c099d2de4 | ||
|
|
5042241e94 | ||
|
|
b63e2584ac | ||
|
|
a6984e2694 | ||
|
|
c7f93d9b25 | ||
|
|
bd5a6a4351 | ||
|
|
97fd32a7af |
No files matched your search
@@ -1,36 +1,64 @@
|
||||
# Contributing to EternalCombat
|
||||
# 👋 Contributing to EternalCombat
|
||||
|
||||
We welcome all contributions to the EternalCombat project!
|
||||
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.
|
||||
|
||||
### How to Contribute
|
||||
## 🛠️ Development Setup
|
||||
|
||||
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.
|
||||
To start contributing, you'll need to set up your environment:
|
||||
|
||||
### Code of Conduct
|
||||
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.
|
||||
|
||||
We expect all contributors to follow our [code of conduct](CODE_OF_CONDUCT.md).
|
||||
### 📥 Getting the Code
|
||||
|
||||
### Reporting Issues
|
||||
```bash
|
||||
# Fork the repository on GitHub first!
|
||||
# https://github.com/EternalCodeTeam/EternalCombat/fork
|
||||
git clone https://github.com/YOUR-USERNAME/EternalCombat.git
|
||||
cd EternalCombat
|
||||
```
|
||||
|
||||
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.
|
||||
### 🔨 Building the Project
|
||||
|
||||
### Compatibility
|
||||
Run this command in your terminal:
|
||||
|
||||
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.
|
||||
```bash
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
### Submitting a Pull Request
|
||||
## 🤝 How to Contribute
|
||||
|
||||
When submitting a pull request, please make sure to follow these guidelines:
|
||||
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.
|
||||
|
||||
- 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!
|
||||
## 📏 Code Style
|
||||
|
||||
Make sure that your code adheres to the plugin's existing coding style.
|
||||
|
||||
## 🐛 Reporting Bugs
|
||||
|
||||
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.
|
||||
|
||||
## 💡 Feature Requests
|
||||
|
||||
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 project's [License](../LICENSE).
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,84 @@
|
||||
name: 🐛 Bug Report
|
||||
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: Bug description
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder: I was doing X and Y happened...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
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)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: minecraft-version
|
||||
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
|
||||
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.
|
||||
render: shell
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 💬 Discord Community
|
||||
url: https://discord.com/invite/FQ7jmGBd6c
|
||||
about: Please ask questions and discuss ideas in our Discord server before opening a generic issue.
|
||||
- name: 📖 Documentation
|
||||
url: https://eternalcode.pl/docs/eternalcombat/introduction
|
||||
about: Read the documentation to see if your question is already answered.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,38 @@
|
||||
name: 💡 Feature Request
|
||||
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
|
||||
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: 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: Add any other context or screenshots about the feature request here.
|
||||
@@ -1 +1,35 @@
|
||||
**Please describe the changes made by this PR and why they need to be merged:**
|
||||
---
|
||||
name: Pull Request
|
||||
about: Submit a contribution to EternalCombat
|
||||
title: 'GH-{NUMBER} <title>'
|
||||
labels: []
|
||||
assignees: []
|
||||
---
|
||||
|
||||
## 📝 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
|
||||
-->
|
||||
|
||||
<!-- Describe your changes clearly and concisely. Why is this change needed? What does it accomplish? -->
|
||||
|
||||
## 🎯 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.)
|
||||
|
||||
**Test Environment:**
|
||||
- **Minecraft Version:** <!-- e.g. 1.20.4 -->
|
||||
- **Java Version:** <!-- e.g. 21 -->
|
||||
- **Platform:** <!-- e.g. Windows/Linux/MacOS -->
|
||||
|
||||
## 📸 Screenshots (if applicable)
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"local>EternalCodeTeam/.github:renovate-config"
|
||||
]
|
||||
}
|
||||
@@ -5,38 +5,44 @@ on:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v4.0.2
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: Set Variables & Build
|
||||
uses: actions/github-script@v9
|
||||
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
|
||||
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
|
||||
uses: gradle/gradle-build-action@093dfe9d598ec5a42246855d09b49dc76803c005
|
||||
run: ./gradlew shadowJar
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
arguments: shadowJar
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: 'Successfully build EternalCombat'
|
||||
name: ${{ env.FULL_ARTIFACT_NAME }}
|
||||
path: eternalcombat-plugin/build/libs/*.jar
|
||||
archive: false
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Publish to Hangar
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
if: (github.event_name == 'push' && !contains(github.event.head_commit.message, '[ci skip]')) || github.event_name == 'release'
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v5
|
||||
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:
|
||||
HANGAR_API_TOKEN: ${{ secrets.HANGAR_API_TOKEN }}
|
||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||
CHANGELOG: ${{ github.event_name == 'release' && github.event.release.body || github.event.head_commit.message }}
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Publish to Modrinth
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
if: (github.event_name == 'push' && !contains(github.event.head_commit.message, '[ci skip]')) || github.event_name == 'release'
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v5
|
||||
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:
|
||||
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
|
||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||
CHANGELOG: ${{ github.event_name == 'release' && github.event.release.body || github.event.head_commit.message }}
|
||||
@@ -9,10 +9,10 @@ jobs:
|
||||
environment: deployment
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.classpath
|
||||
.idea
|
||||
.gradle
|
||||
.kotlin
|
||||
.project
|
||||
.settings
|
||||
bin
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"scanSettings": {
|
||||
"baseBranches": []
|
||||
},
|
||||
"checkRunSettings": {
|
||||
"vulnerableCheckRunConclusionLevel": "failure",
|
||||
"displayMode": "diff",
|
||||
"useMendCheckNames": true
|
||||
},
|
||||
"issueSettings": {
|
||||
"minSeverityLevel": "LOW",
|
||||
"issueType": "DEPENDENCY"
|
||||
}
|
||||
}
|
||||
@@ -1,97 +1,170 @@
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||
|
||||
[](https://papermc.io)
|
||||
[](https://spigotmc.org)
|
||||
[](https://www.spigotmc.org/resources/eternalcombat-%E2%9C%94%EF%B8%8F-enchance-your-combat-system-with-eternalcombat.109056/)
|
||||
[](https://modrinth.com/plugin/eternalcombat)
|
||||
[](https://hangar.papermc.io/EternalCodeTeam/eternalcombat)
|
||||
|
||||
[](https://www.patreon.com/eternalcode)
|
||||
[](https://eternalcode.pl/)
|
||||
[](https://discord.gg/FQ7jmGBd6c)
|
||||
[](https://discord.com/invite/FQ7jmGBd6c)
|
||||
[](https://docs.eternalcode.pl/eternalcombat/introduction)
|
||||
[](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)
|
||||
|
||||
<br>
|
||||
|
||||
<a href="https://ko-fi.com/eternalcodeteam">
|
||||
<img src="https://github.com/intergrav/devins-badges/blob/v3/assets/cozy/donate/kofi-plural-alt_64h.png?raw=true" height="64" alt="Support us on Ko-fi">
|
||||
</a>
|
||||
|
||||
[](https://gradle.org/)
|
||||
[](https://www.java.com/)
|
||||
</div>
|
||||
|
||||
### Information
|
||||
|
||||
EternalCombat has been tested on Minecraft versions 1.17.1 to 1.21.1, but it should work on most other versions. If you
|
||||
encounter any compatibility issues, please report them in the
|
||||
[Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). The plugin requires Java 17 or later, so please
|
||||
make sure it is installed on your server.
|
||||
## EternalCombat requires PacketEvents plugin to work.
|
||||
You can download it from [here](https://modrinth.com/plugin/packetevents)
|
||||
|
||||
## PlaceholderAPI
|
||||
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).
|
||||
The plugin requires **Java 21 or later**, so
|
||||
ensure your server is ready.
|
||||
Ready for action?
|
||||
Install EternalCombat and dive in now!
|
||||
|
||||
EternalCombat supports PlaceholderAPI, which allows you to use placeholders in other plugins that support it.
|
||||
To use placeholders, follow PlaceholderAPI's [instructions](https://wiki.placeholderapi.com/users/) and use the placeholders provided by EternalCombat.
|
||||
Provided placeholders:
|
||||
- `%eternalcombat_opponent%` - Returns the name of the player with whom the player is fighting.
|
||||
- `%eternalcombat_opponent_health%` - Returns opponent health in format `00.00`
|
||||
- `%eternalcombat_remaining_seconds%` - Returns the number of seconds remaining until the player is no longer in combat.
|
||||
- `%eternalcombat_remaining_millis%` - Returns the number of milliseconds remaining until the player is no longer in combat.
|
||||
### How Does EternalCombat Work?
|
||||
|
||||
If the player is not in combat, the placeholders will return an empty string.
|
||||
If combat was not caused by other player, opponent placeholders will return empty string.
|
||||
Take your server’s PvP experience to epic heights with **EternalCombat 2.0**! Our robust combat logging system ensures
|
||||
fair, heart-pounding battles that keep players on their toes. Here’s a rundown of what makes it stand out:
|
||||
|
||||
## Building
|
||||
Build EternalCombat using:
|
||||
|
||||
`./gradlew shadowJar`
|
||||
- **Combat Logging**
|
||||
No more dodging fights by logging out! Once players are in combat, they’re committed until the showdown ends. Watch it
|
||||
in action:
|
||||
<img alt="Combat log anti logout feature" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/combatlog.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)**
|
||||
Stop players from fleeing to safety! Block access to spawn or safe zones during combat – tweak it to fit your server’s
|
||||
rules. See how it works:
|
||||
<img alt="Border around protected region" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/border.gif?raw=true" width="420">
|
||||
|
||||
- **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:
|
||||
<img alt="Crystal PvP showcase" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/crystals.gif?raw=true" width="420">
|
||||
|
||||
- **Fully Customizable Combat**
|
||||
Tailor the combat experience to your liking with a ton of options! From disabling elytra to setting drop rates for
|
||||
defeated players, you’re in control. Here’s what you can tweak:
|
||||
|
||||
| Feature | Description |
|
||||
|----------------------|-----------------------------------------------------------------|
|
||||
| Elytra & Inventory | Disable elytra or inventory access during combat. |
|
||||
| Commands | Whitelist or blacklist specific commands in combat. |
|
||||
| Damage & Projectiles | Customize damage causes and projectile tagging settings. |
|
||||
| Ender Pearls | Add cooldowns to pearl usage in fights. |
|
||||
| Block Placement | Enable or disable block placement during combat. |
|
||||
| Drop Rates | Set a percentage of items dropped by defeated players. |
|
||||
| Temporary Effects | Apply special effects to players in combat for added intensity. |
|
||||
| And More! | Dive into the config for even more fine-tuning options! |
|
||||
|
||||
Curious for more? Check out our quick and exciting YouTube presentation [here](https://youtu.be/5pELO5B0Hhk) to see
|
||||
EternalCombat in full swing and learn why it’s a game-changer for your server!
|
||||
|
||||
### 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
|
||||
Control who can use EternalCombat’s powerful features with these permissions:
|
||||
|
||||
| Permission | Description |
|
||||
|--------------------------------|-------------------------------------------------------------------------------------------------------|
|
||||
| `eternalcombat.status` | Check a player’s combat status with `/combatlog status <player>`. |
|
||||
| `eternalcombat.tag` | Start a fight between players using `/combatlog tag <player> [player2]`. |
|
||||
| `eternalcombat.untag` | Remove a player from combat with `/combatlog untag <player>`. |
|
||||
| `eternalcombat.untagall` | Remove all players from combat with `/combatlog untagall`. |
|
||||
| `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 |
|
||||
|
||||
## 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.
|
||||
Here are the available
|
||||
placeholders:
|
||||
|
||||
| Placeholder | Description |
|
||||
|-------------------------------------|---------------------------------------------------------------|
|
||||
| `%eternalcombat_opponent%` | Returns the name of the player you’re fighting. |
|
||||
| `%eternalcombat_opponent_health%` | Returns the opponent’s health in `00.00` format. |
|
||||
| `%eternalcombat_remaining_seconds%` | Returns seconds remaining until the player exits combat. |
|
||||
| `%eternalcombat_remaining_millis%` | Returns milliseconds remaining until the player exits combat. |
|
||||
|
||||
If a player isn’t in combat, placeholders return an empty string.
|
||||
If combat wasn’t triggered by another player,
|
||||
opponent-related placeholders will also return empty.
|
||||
|
||||
### Developer API
|
||||
|
||||
#### 1. Add repository:
|
||||
|
||||
with Gradle:
|
||||
With Gradle:
|
||||
|
||||
```kts
|
||||
maven {
|
||||
url = uri("https://repo.eternalcode.pl/releases")
|
||||
}
|
||||
```
|
||||
|
||||
with Maven:
|
||||
With Maven:
|
||||
|
||||
```xml
|
||||
|
||||
<repository>
|
||||
<id>eternalcode-reposilite-releases</id>
|
||||
<name>EternalCode Repository</name>
|
||||
<url>https://repo.eternalcode.pl/releases</url>
|
||||
<id>eternalcode-reposilite-releases</id>
|
||||
<name>EternalCode Repository</name>
|
||||
<url>https://repo.eternalcode.pl/releases</url>
|
||||
</repository>
|
||||
```
|
||||
|
||||
#### 2. Add dependency:
|
||||
|
||||
with Gradle:
|
||||
With Gradle:
|
||||
|
||||
```kts
|
||||
compileOnly("com.eternalcode:eternalcombat-api:1.3.2")
|
||||
compileOnly("com.eternalcode:eternalcombat-api:2.5.2-SNAPSHOT")
|
||||
```
|
||||
|
||||
with Maven:
|
||||
With Maven:
|
||||
|
||||
```xml
|
||||
|
||||
<dependency>
|
||||
<groupId>com.eternalcode</groupId>
|
||||
<artifactId>eternalcombat-api</artifactId>
|
||||
<version>1.3.2</version>
|
||||
<scope>provided</scope>
|
||||
<groupId>com.eternalcode</groupId>
|
||||
<artifactId>eternalcombat-api</artifactId>
|
||||
<version>2.5.2-SNAPSHOT</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)
|
||||
We’d love your help to make EternalCombat even better!
|
||||
Check out our [contributing guide](https://github.com/EternalCodeTeam/EternalCombat/blob/master/.github/CONTRIBUTING.md) for
|
||||
details on how to get involved and our [code of conduct](https://github.com/EternalCodeTeam/EternalCombat/blob/master/.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
|
||||
Found a bug?
|
||||
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!
|
||||
|
After Width: | Height: | Size: 5.2 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 752 KiB |
|
After Width: | Height: | Size: 5.4 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 98 KiB |
@@ -8,10 +8,12 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.25")
|
||||
implementation("io.github.goooler.shadow:shadow-gradle-plugin:8.1.8")
|
||||
implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.6.1")
|
||||
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("net.minecrell:plugin-yml:0.6.0")
|
||||
implementation("xyz.jpenilla:run-task:2.3.0")
|
||||
implementation("xyz.jpenilla:run-task:3.0.2")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.provider.Provider
|
||||
|
||||
internal val Project.isRelease: Boolean
|
||||
get() = providers.environmentVariable("GITHUB_EVENT_NAME").orNull == "release"
|
||||
|
||||
internal val Project.releaseChannel: String
|
||||
get() = if (isRelease) "Release" else "Snapshot"
|
||||
|
||||
internal val Project.changelogText: Provider<String>
|
||||
get() = providers.environmentVariable("CHANGELOG")
|
||||
.orElse(providers.exec { commandLine("git", "log", "-1", "--format=%B") }.standardOutput.asText)
|
||||
|
||||
internal val Project.paperVersions: List<String>
|
||||
get() = property("paperVersion").toString()
|
||||
.split(",")
|
||||
.map { paperVersion -> paperVersion.trim() }
|
||||
|
||||
internal fun Project.configureSnapshotVersionFromGit() {
|
||||
val isGithubWorkflow = providers.environmentVariable("GITHUB_RUN_NUMBER").isPresent
|
||||
|
||||
if (!isGithubWorkflow || isRelease) {
|
||||
return
|
||||
}
|
||||
|
||||
if ("-SNAPSHOT+" in version.toString()) {
|
||||
return
|
||||
}
|
||||
|
||||
val describeParts = providers.exec { commandLine("git", "describe", "--tags", "--long") }
|
||||
.standardOutput.asText.get()
|
||||
.trim()
|
||||
.split("-")
|
||||
|
||||
val offset = if (describeParts.size >= 3) describeParts[describeParts.size - 2] else "0"
|
||||
val baseVersion = version.toString().substringBefore("-SNAPSHOT")
|
||||
|
||||
version = "$baseVersion-SNAPSHOT+$offset"
|
||||
}
|
||||
@@ -1,34 +1,35 @@
|
||||
object Versions {
|
||||
|
||||
const val SPIGOT_API = "1.17.1-R0.1-SNAPSHOT"
|
||||
// Keep this version for best compatibility.
|
||||
const val PAPER_API = "1.19.3-R0.1-SNAPSHOT"
|
||||
|
||||
const val JUNIT_JUPITER_API = "5.10.3"
|
||||
const val JUNIT_JUPITER_PARAMS = "5.10.3"
|
||||
const val JUNIT_JUPITER_ENGINE = "5.10.3"
|
||||
const val JUNIT_JUPITER_API = "6.1.3"
|
||||
const val JUNIT_JUPITER_PARAMS = "6.1.3"
|
||||
const val JUNIT_JUPITER_ENGINE = "6.1.3"
|
||||
|
||||
const val JETBRAINS_ANNOTATIONS = "24.1.0"
|
||||
const val JETBRAINS_ANNOTATIONS = "26.1.0"
|
||||
|
||||
const val ETERNALCODE_COMMONS = "1.1.3"
|
||||
const val MULTIFICATION = "1.1.4"
|
||||
const val ETERNALCODE_COMMONS = "1.4.1"
|
||||
const val MULTIFICATION = "1.2.5-SNAPSHOT"
|
||||
const val PACKETS_EVENTS = "2.13.0"
|
||||
|
||||
const val ADVENTURE_PLATFORM_BUKKIT = "4.3.4"
|
||||
const val ADVENTURE_API = "4.17.0"
|
||||
const val LITE_COMMANDS = "3.11.0"
|
||||
const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.13"
|
||||
const val OKAERI_CONFIGS_SERDES_BUKKIT = "5.0.13"
|
||||
|
||||
const val LITE_COMMANDS = "3.7.0"
|
||||
const val OKAERI_CONFIGS_YAML_BUKKIT = "5.0.3"
|
||||
const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.3"
|
||||
const val OKAERI_CONFIGS_SERDES_BUKKIT = "5.0.3"
|
||||
const val XSERIES = "13.8.0"
|
||||
|
||||
const val PANDA_UTILITIES = "0.5.2-alpha"
|
||||
const val GIT_CHECK = "1.0.0"
|
||||
const val APACHE_COMMONS = "2.16.1"
|
||||
const val CAFFEINE = "3.2.4"
|
||||
|
||||
const val CAFFEINE = "3.1.8"
|
||||
const val B_STATS_BUKKIT = "3.2.1"
|
||||
const val WORLD_GUARD_BUKKIT = "7.0.17"
|
||||
|
||||
const val B_STATS_BUKKIT = "3.0.2"
|
||||
const val WORLD_GUARD_BUKKIT = "7.0.9"
|
||||
|
||||
const val PLACEHOLDER_API = "2.11.6"
|
||||
const val PLACEHOLDER_API = "2.12.3"
|
||||
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"
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("org.spigotmc:spigot-api:${Versions.SPIGOT_API}")
|
||||
testImplementation("io.papermc.paper:paper-api:${Versions.PAPER_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}")
|
||||
|
||||
@@ -1,40 +1,14 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
checkstyle
|
||||
}
|
||||
|
||||
group = "com.eternalcode"
|
||||
version = "2.0.0-SNAPSHOT"
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.17.0"
|
||||
|
||||
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
|
||||
|
||||
maxErrors = 0
|
||||
maxWarnings = 0
|
||||
}
|
||||
|
||||
// https://github.com/JabRef/jabref/pull/10812/files#diff-49a96e7eea8a94af862798a45174e6ac43eb4f8b4bd40759b5da63ba31ec3ef7R267
|
||||
configurations.named("checkstyle") {
|
||||
resolutionStrategy {
|
||||
capabilitiesResolution {
|
||||
withCapability("com.google.collections:google-collections") {
|
||||
select("com.google.guava:guava:33.2.1-jre")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
version = "2.5.2-SNAPSHOT"
|
||||
|
||||
tasks.compileJava {
|
||||
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
|
||||
options.encoding = "UTF-8"
|
||||
options.release = 17
|
||||
options.release = 21
|
||||
}
|
||||
|
||||
java {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import io.papermc.hangarpublishplugin.model.Platforms
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
id("io.papermc.hangar-publish-plugin")
|
||||
}
|
||||
|
||||
configureSnapshotVersionFromGit()
|
||||
|
||||
pluginManager.withPlugin("com.gradleup.shadow") {
|
||||
val shadowJarTask = tasks.named<ShadowJar>("shadowJar")
|
||||
|
||||
hangarPublish {
|
||||
publications.register("plugin") {
|
||||
version.set(project.version.toString())
|
||||
channel.set(project.releaseChannel)
|
||||
changelog.set(project.changelogText)
|
||||
id.set("eternalcombat")
|
||||
apiKey.set(providers.environmentVariable("HANGAR_API_TOKEN"))
|
||||
platforms {
|
||||
register(Platforms.PAPER) {
|
||||
jar.set(shadowJarTask.flatMap { shadowJar -> shadowJar.archiveFile })
|
||||
platformVersions.set(project.paperVersions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
id("com.modrinth.minotaur")
|
||||
}
|
||||
|
||||
configureSnapshotVersionFromGit()
|
||||
|
||||
pluginManager.withPlugin("com.gradleup.shadow") {
|
||||
val shadowJarTask = tasks.named<ShadowJar>("shadowJar")
|
||||
|
||||
modrinth {
|
||||
token.set(providers.environmentVariable("MODRINTH_TOKEN"))
|
||||
projectId.set("eternalcombat")
|
||||
versionNumber.set(project.version.toString())
|
||||
versionType.set(if (project.isRelease) "release" else "beta")
|
||||
uploadFile.set(shadowJarTask)
|
||||
gameVersions.addAll(project.paperVersions)
|
||||
loaders.addAll(listOf("paper", "folia", "purpur"))
|
||||
changelog.set(project.changelogText)
|
||||
syncBodyFrom.set(rootProject.file("README.md").readText())
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = "com.eternalcode"
|
||||
version = "2.0.0-SNAPSHOT"
|
||||
version = "2.5.2-SNAPSHOT"
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
|
||||
@@ -3,13 +3,17 @@ plugins {
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
|
||||
maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") }
|
||||
maven { url = uri("https://papermc.io/repo/repository/maven-public/") }
|
||||
maven { url = uri("https://repo.eternalcode.pl/releases") }
|
||||
maven { url = uri("https://storehouse.okaeri.eu/repository/maven-public/") }
|
||||
maven { url = uri("https://repo.panda-lang.org/releases") }
|
||||
maven { url = uri("https://maven.enginehub.org/repo/") }
|
||||
maven { url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/")}
|
||||
maven("https://repo.eternalcode.pl/releases")
|
||||
maven("https://repo.eternalcode.pl/snapshots")
|
||||
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,22 @@
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||
import org.gradle.jvm.toolchain.JavaToolchainService
|
||||
|
||||
plugins {
|
||||
id("xyz.jpenilla.run-paper")
|
||||
}
|
||||
|
||||
tasks {
|
||||
val javaToolchains = extensions.getByType<JavaToolchainService>()
|
||||
|
||||
runServer {
|
||||
javaLauncher.set(javaToolchains.launcherFor {
|
||||
languageVersion.set(JavaLanguageVersion.of(25))
|
||||
})
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Spigot api
|
||||
compileOnlyApi("org.spigotmc:spigot-api:${Versions.SPIGOT_API}")
|
||||
// Paper api
|
||||
compileOnlyApi("io.papermc.paper:paper-api:${Versions.PAPER_API}")
|
||||
api("org.jetbrains:annotations:${Versions.JETBRAINS_ANNOTATIONS}")
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import com.eternalcode.combat.fight.drop.DropKeepInventoryService;
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import com.eternalcode.combat.fight.drop.DropService;
|
||||
import com.eternalcode.combat.fight.effect.FightEffectService;
|
||||
import com.eternalcode.combat.fight.pearl.FightPearlService;
|
||||
import com.eternalcode.combat.fight.pearl.PearlService;
|
||||
import com.eternalcode.combat.region.RegionProvider;
|
||||
import com.eternalcode.combat.fight.tagout.FightTagOutService;
|
||||
|
||||
@@ -14,7 +14,7 @@ public interface EternalCombatApi {
|
||||
|
||||
RegionProvider getRegionProvider();
|
||||
|
||||
FightPearlService getFightPearlService();
|
||||
PearlService getFightPearlService();
|
||||
|
||||
FightTagOutService getFightTagOutService();
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.eternalcode.combat.fight.event;
|
||||
|
||||
public enum CancelTagReason {
|
||||
|
||||
TAGOUT
|
||||
TAGOUT,
|
||||
PERMISSION_BYPASS,
|
||||
ADMIN,
|
||||
CREATIVE_MODE,
|
||||
|
||||
}
|
||||
@@ -18,6 +18,27 @@ public enum CauseOfTag {
|
||||
*/
|
||||
COMMAND,
|
||||
|
||||
/**
|
||||
* Crystal or anchor explosion caused the tag.
|
||||
*/
|
||||
CRYSTAL,
|
||||
|
||||
/**
|
||||
* Trident usage extending combat tag.
|
||||
*/
|
||||
TRIDENT,
|
||||
|
||||
/**
|
||||
* Ender pearl usage extending combat tag.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package com.eternalcode.combat.fight.pearl;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public interface FightPearlService {
|
||||
public interface PearlService {
|
||||
|
||||
Instant getDelay(UUID uuid);
|
||||
boolean shouldCancelEvent(UUID playerId);
|
||||
|
||||
Duration getRemainingDelay(UUID uuid);
|
||||
|
||||
boolean hasDelay(UUID uuid);
|
||||
|
||||
void markDelay(UUID uuid);
|
||||
void handleDelay(Player uuid);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.eternalcode.combat.fight.trident;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface TridentService {
|
||||
|
||||
/**
|
||||
* returns remaining delay for the player to use trident again
|
||||
* @param uuid unique id of the player
|
||||
* @return remaining duration left to use trident again by the player or zero
|
||||
*/
|
||||
Duration getRemainingDelay(UUID uuid);
|
||||
|
||||
/**
|
||||
* checks if player still has delay left to use trident
|
||||
* @param uuid unique id of the player
|
||||
* @return true if user still has cooldown left to use trident
|
||||
*/
|
||||
boolean hasDelay(UUID uuid);
|
||||
|
||||
/**
|
||||
* marks start of the delay for the user
|
||||
* @param uuid unique id of the player
|
||||
*/
|
||||
void markDelay(UUID uuid);
|
||||
|
||||
|
||||
void removeDelay(UUID playerId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.eternalcode.combat.region;
|
||||
|
||||
import org.bukkit.World;
|
||||
|
||||
public record Point(World world, double x, double z) {
|
||||
}
|
||||
@@ -4,6 +4,22 @@ import org.bukkit.Location;
|
||||
|
||||
public interface Region {
|
||||
|
||||
Location getCenter();
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.eternalcode.combat.region;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
|
||||
public interface RegionProvider {
|
||||
|
||||
@@ -11,4 +13,6 @@ public interface RegionProvider {
|
||||
return this.getRegion(location).isPresent();
|
||||
}
|
||||
|
||||
Collection<Region> getRegions(World world);
|
||||
|
||||
}
|
||||
@@ -1,40 +1,45 @@
|
||||
|
||||
import net.minecrell.pluginyml.paper.PaperPluginDescription
|
||||
import net.minecrell.pluginyml.bukkit.BukkitPluginDescription
|
||||
|
||||
plugins {
|
||||
`eternalcombat-java`
|
||||
`eternalcombat-repositories`
|
||||
`eternalcombat-publish-hangar`
|
||||
`eternalcombat-publish-modrinth`
|
||||
`eternalcombat-runserver`
|
||||
|
||||
id("net.minecrell.plugin-yml.bukkit")
|
||||
id("io.github.goooler.shadow")
|
||||
id("xyz.jpenilla.run-paper")
|
||||
id("de.eldoria.plugin-yml.paper") version "0.9.0"
|
||||
id("com.gradleup.shadow")
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
eachDependency {
|
||||
if (requested.group == "com.google.code.gson" && requested.name == "gson") {
|
||||
useVersion("2.11.0")
|
||||
because("WorldGuard requires strictly gson 2.11.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":eternalcombat-api"))
|
||||
|
||||
// kyori
|
||||
implementation("net.kyori:adventure-platform-bukkit:${Versions.ADVENTURE_PLATFORM_BUKKIT}")
|
||||
implementation("net.kyori:adventure-text-minimessage:${Versions.ADVENTURE_API}")
|
||||
implementation("net.kyori:adventure-api") {
|
||||
version {
|
||||
strictly(Versions.ADVENTURE_API)
|
||||
}
|
||||
}
|
||||
// Paper
|
||||
compileOnly("io.papermc.paper:paper-api:${Versions.PAPER_API}")
|
||||
|
||||
// litecommands
|
||||
implementation("dev.rollczi:litecommands-bukkit:${Versions.LITE_COMMANDS}")
|
||||
implementation("dev.rollczi:litecommands-folia:${Versions.LITE_COMMANDS}")
|
||||
|
||||
// Okaeri configs
|
||||
implementation("eu.okaeri:okaeri-configs-yaml-bukkit:${Versions.OKAERI_CONFIGS_YAML_BUKKIT}")
|
||||
implementation("eu.okaeri:okaeri-configs-serdes-commons:${Versions.OKAERI_CONFIGS_SERDES_COMMONS}")
|
||||
implementation("eu.okaeri:okaeri-configs-serdes-bukkit:${Versions.OKAERI_CONFIGS_SERDES_BUKKIT}")
|
||||
|
||||
// Panda utilities
|
||||
implementation("org.panda-lang:panda-utilities:${Versions.PANDA_UTILITIES}")
|
||||
|
||||
// GitCheck
|
||||
implementation("com.eternalcode:gitcheck:${Versions.GIT_CHECK}")
|
||||
|
||||
// commons
|
||||
implementation("commons-io:commons-io:${Versions.APACHE_COMMONS}")
|
||||
// XSeries
|
||||
implementation("com.github.cryptomorin:XSeries:${Versions.XSERIES}")
|
||||
|
||||
// bstats
|
||||
implementation("org.bstats:bstats-bukkit:${Versions.B_STATS_BUKKIT}")
|
||||
@@ -42,8 +47,11 @@ dependencies {
|
||||
// caffeine
|
||||
implementation("com.github.ben-manes.caffeine:caffeine:${Versions.CAFFEINE}")
|
||||
|
||||
implementation("com.eternalcode:eternalcode-commons-bukkit:${Versions.ETERNALCODE_COMMONS}")
|
||||
implementation("com.eternalcode:eternalcode-commons-adventure:${Versions.ETERNALCODE_COMMONS}")
|
||||
implementation("com.eternalcode:eternalcode-commons-bukkit:${Versions.ETERNALCODE_COMMONS}")
|
||||
implementation("com.eternalcode:eternalcode-commons-shared:${Versions.ETERNALCODE_COMMONS}")
|
||||
implementation("com.eternalcode:eternalcode-commons-folia:${Versions.ETERNALCODE_COMMONS}")
|
||||
implementation("com.eternalcode:eternalcode-commons-updater:${Versions.ETERNALCODE_COMMONS}")
|
||||
|
||||
// worldguard
|
||||
compileOnly("com.sk89q.worldguard:worldguard-bukkit:${Versions.WORLD_GUARD_BUKKIT}")
|
||||
@@ -51,58 +59,84 @@ dependencies {
|
||||
// PlaceholderAPI
|
||||
compileOnly("me.clip:placeholderapi:${Versions.PLACEHOLDER_API}")
|
||||
|
||||
// Lands
|
||||
compileOnly("com.github.angeschossen:LandsAPI:${Versions.LANDS_API}")
|
||||
|
||||
// Multification
|
||||
implementation("com.eternalcode:multification-bukkit:${Versions.MULTIFICATION}")
|
||||
implementation("com.eternalcode:multification-paper:${Versions.MULTIFICATION}")
|
||||
implementation("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION}")
|
||||
compileOnly("com.github.retrooper:packetevents-spigot:${Versions.PACKETS_EVENTS}")
|
||||
}
|
||||
|
||||
bukkit {
|
||||
paper {
|
||||
main = "com.eternalcode.combat.CombatPlugin"
|
||||
author = "EternalCodeTeam"
|
||||
apiVersion = "1.13"
|
||||
authors = listOf("EternalCodeTeam")
|
||||
apiVersion = "1.19"
|
||||
prefix = "EternalCombat"
|
||||
name = "EternalCombat"
|
||||
softDepend = listOf("WorldGuard", "PlaceholderAPI")
|
||||
generateLibrariesJson = true
|
||||
load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD
|
||||
version = "${project.version}"
|
||||
|
||||
foliaSupported = true
|
||||
|
||||
serverDependencies {
|
||||
register("packetevents") {
|
||||
required = true
|
||||
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
|
||||
}
|
||||
|
||||
register("Lands") {
|
||||
required = false
|
||||
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
|
||||
}
|
||||
|
||||
register("WorldGuard") {
|
||||
required = false
|
||||
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
|
||||
}
|
||||
|
||||
register("PlaceholderAPI") {
|
||||
required = false
|
||||
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
runServer {
|
||||
minecraftVersion("1.21.1")
|
||||
named("generatePaperPluginDescription") {
|
||||
notCompatibleWithConfigurationCache("The plugin-yml paper generator reads Task.project during execution.")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
archiveFileName.set("EternalCombat v${project.version}.jar")
|
||||
|
||||
dependsOn("checkstyleMain")
|
||||
dependsOn("checkstyleTest")
|
||||
dependsOn("test")
|
||||
|
||||
exclude(
|
||||
"org/intellij/lang/annotations/**",
|
||||
"org/jetbrains/annotations/**",
|
||||
"net/kyori/**",
|
||||
"META-INF/**",
|
||||
"kotlin/**",
|
||||
"javax/**"
|
||||
"javax/**",
|
||||
"org/checkerframework/**",
|
||||
"com/google/errorprone/**",
|
||||
"com/google/gson/**"
|
||||
)
|
||||
|
||||
val prefix = "com.eternalcode.combat.libs"
|
||||
listOf(
|
||||
"panda.std",
|
||||
"panda.utilities",
|
||||
"org.panda-lang",
|
||||
"eu.okaeri",
|
||||
"net.kyori",
|
||||
"org.bstats",
|
||||
"org.yaml",
|
||||
"dev.rollczi.litecommands",
|
||||
"com.eternalcode.gitcheck",
|
||||
"org.json.simple",
|
||||
"org.apache.commons",
|
||||
"javassist",
|
||||
"com.github.benmanes.caffeine",
|
||||
"com.eternalcode.commons",
|
||||
"com.eternalcode.multification"
|
||||
"com.eternalcode.multification",
|
||||
"com.github.cryptomorin",
|
||||
"com.cryptomorin",
|
||||
).forEach { pack ->
|
||||
relocate(pack, "$prefix.$pack")
|
||||
}
|
||||
|
||||
@@ -1,56 +1,83 @@
|
||||
package com.eternalcode.combat;
|
||||
|
||||
import com.eternalcode.combat.border.BorderService;
|
||||
import com.eternalcode.combat.border.BorderServiceImpl;
|
||||
import com.eternalcode.combat.border.BorderTriggerController;
|
||||
import com.eternalcode.combat.border.animation.block.BorderBlockController;
|
||||
import com.eternalcode.combat.border.animation.particle.ParticleController;
|
||||
import com.eternalcode.combat.bridge.BridgeService;
|
||||
import com.eternalcode.combat.fight.drop.DropKeepInventoryService;
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import com.eternalcode.combat.fight.drop.DropService;
|
||||
import com.eternalcode.combat.fight.effect.FightEffectService;
|
||||
import com.eternalcode.combat.fight.tagout.FightTagOutService;
|
||||
import com.eternalcode.combat.fight.pearl.FightPearlService;
|
||||
import com.eternalcode.combat.handler.InvalidUsageHandlerImpl;
|
||||
import com.eternalcode.combat.handler.MissingPermissionHandlerImpl;
|
||||
import com.eternalcode.combat.config.ConfigService;
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.drop.DropController;
|
||||
import com.eternalcode.combat.fight.drop.DropKeepInventoryServiceImpl;
|
||||
import com.eternalcode.combat.fight.drop.DropServiceImpl;
|
||||
import com.eternalcode.combat.fight.drop.impl.PercentDropModifier;
|
||||
import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier;
|
||||
import com.eternalcode.combat.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;
|
||||
import com.eternalcode.combat.fight.FightTagCommand;
|
||||
import com.eternalcode.combat.fight.controller.FightActionBlockerController;
|
||||
import com.eternalcode.combat.fight.FightTask;
|
||||
import com.eternalcode.combat.fight.blocker.CommandsBlocker;
|
||||
import com.eternalcode.combat.fight.blocker.ElytraBlocker;
|
||||
import com.eternalcode.combat.fight.blocker.ElytraEquipBlocker;
|
||||
import com.eternalcode.combat.fight.blocker.FlyingBlocker;
|
||||
import com.eternalcode.combat.fight.controller.FightBypassAdminController;
|
||||
import com.eternalcode.combat.fight.controller.FightBypassCreativeController;
|
||||
import com.eternalcode.combat.fight.controller.FightBypassPermissionController;
|
||||
import com.eternalcode.combat.fight.blocker.InventoryContainersBlocker;
|
||||
import com.eternalcode.combat.fight.controller.FightMessageController;
|
||||
import com.eternalcode.combat.fight.controller.FightTagController;
|
||||
import com.eternalcode.combat.fight.controller.FightUnTagController;
|
||||
import com.eternalcode.combat.fight.death.DeathFlareController;
|
||||
import com.eternalcode.combat.fight.death.DeathLightningController;
|
||||
import com.eternalcode.combat.fight.drop.DropController;
|
||||
import com.eternalcode.combat.fight.drop.DropKeepInventoryService;
|
||||
import com.eternalcode.combat.fight.drop.DropKeepInventoryServiceImpl;
|
||||
import com.eternalcode.combat.fight.drop.DropService;
|
||||
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.event.EventCaller;
|
||||
import com.eternalcode.combat.fight.FightManagerImpl;
|
||||
import com.eternalcode.combat.fight.FightTask;
|
||||
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;
|
||||
import com.eternalcode.combat.fight.logout.LogoutService;
|
||||
import com.eternalcode.combat.fight.pearl.FightPearlController;
|
||||
import com.eternalcode.combat.fight.pearl.FightPearlServiceImpl;
|
||||
import com.eternalcode.combat.fight.tagout.FightTagOutController;
|
||||
import com.eternalcode.combat.fight.tagout.FightTagOutServiceImpl;
|
||||
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.tagout.FightTagOutCommand;
|
||||
import com.eternalcode.combat.notification.NotificationAnnouncer;
|
||||
import com.eternalcode.combat.region.RegionController;
|
||||
import com.eternalcode.combat.fight.tagout.FightTagOutController;
|
||||
import com.eternalcode.combat.fight.tagout.FightTagOutService;
|
||||
import com.eternalcode.combat.fight.tagout.FightTagOutServiceImpl;
|
||||
import com.eternalcode.combat.fight.trident.TridentController;
|
||||
import com.eternalcode.combat.fight.trident.TridentService;
|
||||
import com.eternalcode.combat.fight.trident.TridentServiceImpl;
|
||||
import com.eternalcode.combat.handler.InvalidUsageHandlerImpl;
|
||||
import com.eternalcode.combat.handler.MissingPermissionHandlerImpl;
|
||||
import com.eternalcode.combat.notification.NoticeService;
|
||||
import com.eternalcode.combat.region.RegionProvider;
|
||||
import com.eternalcode.combat.updater.UpdaterNotificationController;
|
||||
import com.eternalcode.combat.updater.UpdaterService;
|
||||
import com.eternalcode.commons.adventure.AdventureLegacyColorPostProcessor;
|
||||
import com.eternalcode.commons.adventure.AdventureLegacyColorPreProcessor;
|
||||
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
|
||||
import com.eternalcode.multification.notice.Notice;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import dev.rollczi.litecommands.LiteCommands;
|
||||
import dev.rollczi.litecommands.bukkit.LiteBukkitFactory;
|
||||
import dev.rollczi.litecommands.bukkit.LiteBukkitMessages;
|
||||
import net.kyori.adventure.platform.AudienceProvider;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import dev.rollczi.litecommands.folia.FoliaExtension;
|
||||
import java.time.Duration;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
@@ -63,21 +90,19 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
|
||||
private static final int BSTATS_METRICS_ID = 17803;
|
||||
|
||||
private FightManager fightManager;
|
||||
private FightPearlService fightPearlService;
|
||||
private PearlService pearlService;
|
||||
private TridentService tridentService;
|
||||
private FightTagOutService fightTagOutService;
|
||||
private FightEffectService fightEffectService;
|
||||
|
||||
private LogoutService logoutService;
|
||||
|
||||
private DropService dropService;
|
||||
private DropKeepInventoryService dropKeepInventoryService;
|
||||
|
||||
private RegionProvider regionProvider;
|
||||
|
||||
private PluginConfig pluginConfig;
|
||||
|
||||
private AudienceProvider audienceProvider;
|
||||
private LiteCommands<CommandSender> liteCommands;
|
||||
private boolean apiInitialized;
|
||||
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
@@ -88,79 +113,123 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
|
||||
|
||||
ConfigService configService = new ConfigService();
|
||||
|
||||
EventCaller eventCaller = new EventCaller(server);
|
||||
EventManager eventManager = new EventManager(this);
|
||||
PluginConfig pluginConfig = configService.create(PluginConfig.class, new File(dataFolder, "config.yml"));
|
||||
|
||||
this.pluginConfig = configService.create(PluginConfig.class, new File(dataFolder, "config.yml"));
|
||||
MinecraftScheduler scheduler = CombatSchedulerAdapter.getAdaptiveScheduler(this);
|
||||
|
||||
this.fightManager = new FightManagerImpl(eventCaller);
|
||||
this.fightPearlService = new FightPearlServiceImpl(this.pluginConfig.pearl);
|
||||
this.fightManager = new FightManagerImpl(eventManager);
|
||||
this.pearlService = new PearlServiceImpl(this.fightManager, pluginConfig, scheduler);
|
||||
this.tridentService = new TridentServiceImpl(pluginConfig);
|
||||
this.fightTagOutService = new FightTagOutServiceImpl();
|
||||
this.fightEffectService = new FightEffectServiceImpl();
|
||||
this.logoutService = new LogoutService();
|
||||
|
||||
LogoutService logoutService = new LogoutService();
|
||||
|
||||
this.dropService = new DropServiceImpl();
|
||||
this.dropKeepInventoryService = new DropKeepInventoryServiceImpl();
|
||||
|
||||
UpdaterService updaterService = new UpdaterService(this.getDescription());
|
||||
|
||||
this.audienceProvider = BukkitAudiences.create(this);
|
||||
MiniMessage miniMessage = MiniMessage.builder()
|
||||
.postProcessor(new AdventureLegacyColorPostProcessor())
|
||||
.preProcessor(new AdventureLegacyColorPreProcessor())
|
||||
.build();
|
||||
|
||||
BridgeService bridgeService = new BridgeService(this.pluginConfig, server.getPluginManager(), this.getLogger(), this);
|
||||
bridgeService.init(this.fightManager, server);
|
||||
NoticeService noticeService = new NoticeService(pluginConfig, miniMessage);
|
||||
|
||||
BridgeService bridgeService = new BridgeService(
|
||||
pluginConfig,
|
||||
server.getPluginManager(),
|
||||
this.getLogger(),
|
||||
this,
|
||||
this.fightManager
|
||||
);
|
||||
bridgeService.init(server);
|
||||
|
||||
this.regionProvider = bridgeService.getRegionProvider();
|
||||
|
||||
|
||||
NotificationAnnouncer notificationAnnouncer = new NotificationAnnouncer(this.audienceProvider, this.pluginConfig, miniMessage);
|
||||
BorderService borderService = new BorderServiceImpl(scheduler, server, regionProvider, eventManager, () -> pluginConfig.border);
|
||||
KnockbackService knockbackService = new KnockbackService(pluginConfig, scheduler, regionProvider);
|
||||
|
||||
this.liteCommands = LiteBukkitFactory.builder(FALLBACK_PREFIX, this, server)
|
||||
.message(LiteBukkitMessages.PLAYER_NOT_FOUND, this.pluginConfig.messages.playerNotFound)
|
||||
.message(LiteBukkitMessages.PLAYER_ONLY, this.pluginConfig.messages.admin.onlyForPlayers)
|
||||
.message(LiteBukkitMessages.PLAYER_NOT_FOUND, pluginConfig.messagesSettings.playerNotFound)
|
||||
.message(LiteBukkitMessages.PLAYER_ONLY, pluginConfig.messagesSettings.admin.onlyForPlayers)
|
||||
|
||||
.invalidUsage(new InvalidUsageHandlerImpl(this.pluginConfig, notificationAnnouncer))
|
||||
.missingPermission(new MissingPermissionHandlerImpl(this.pluginConfig, notificationAnnouncer))
|
||||
.invalidUsage(new InvalidUsageHandlerImpl(pluginConfig, noticeService))
|
||||
.missingPermission(new MissingPermissionHandlerImpl(pluginConfig, noticeService))
|
||||
|
||||
.commands(
|
||||
new FightTagCommand(this.fightManager, notificationAnnouncer, this.pluginConfig),
|
||||
new FightTagOutCommand(this.fightTagOutService, notificationAnnouncer, this.pluginConfig),
|
||||
new EternalCombatReloadCommand(configService, notificationAnnouncer)
|
||||
new FightTagCommand(this.fightManager, noticeService, pluginConfig),
|
||||
new FightTagOutCommand(this.fightTagOutService, noticeService, pluginConfig),
|
||||
new EternalCombatReloadCommand(configService, noticeService)
|
||||
)
|
||||
|
||||
.result(Notice.class, (invocation, result, chain) -> notificationAnnouncer.create()
|
||||
.extension(new FoliaExtension(this))
|
||||
|
||||
.result(Notice.class, (invocation, result, chain) -> noticeService.create()
|
||||
.viewer(invocation.sender())
|
||||
.notice(result)
|
||||
.send())
|
||||
|
||||
.build();
|
||||
|
||||
FightTask fightTask = new FightTask(server, this.pluginConfig, this.fightManager, notificationAnnouncer);
|
||||
this.getServer().getScheduler().runTaskTimer(this, fightTask, 20L, 20L);
|
||||
FightTask fightTask = new FightTask(server, pluginConfig, this.fightManager, noticeService);
|
||||
scheduler.timer(fightTask, Duration.ofSeconds(1), Duration.ofSeconds(1));
|
||||
|
||||
new Metrics(this, BSTATS_METRICS_ID);
|
||||
|
||||
Stream.of(
|
||||
new PercentDropModifier(this.pluginConfig.dropSettings),
|
||||
new PlayersHealthDropModifier(this.pluginConfig.dropSettings, this.logoutService)
|
||||
new PercentDropModifier(pluginConfig.drop),
|
||||
new PlayersHealthDropModifier(pluginConfig.drop, logoutService)
|
||||
).forEach(this.dropService::registerModifier);
|
||||
|
||||
|
||||
Stream.of(
|
||||
new DropController(this.dropService, this.dropKeepInventoryService, this.pluginConfig.dropSettings, this.fightManager),
|
||||
new FightTagController(this.fightManager, this.pluginConfig),
|
||||
new LogoutController(this.fightManager, this.logoutService, notificationAnnouncer, this.pluginConfig),
|
||||
new FightUnTagController(this.fightManager, this.pluginConfig, this.logoutService),
|
||||
new FightActionBlockerController(this.fightManager, notificationAnnouncer, this.pluginConfig),
|
||||
new FightPearlController(this.pluginConfig.pearl, notificationAnnouncer, this.fightManager, this.fightPearlService),
|
||||
new UpdaterNotificationController(updaterService, this.pluginConfig, this.audienceProvider, miniMessage),
|
||||
new RegionController(notificationAnnouncer, this.regionProvider, this.fightManager, this.pluginConfig),
|
||||
new FightEffectController(this.pluginConfig.effect, this.fightEffectService, this.fightManager, this.getServer()),
|
||||
eventManager.subscribe(
|
||||
new FightTagController(this.fightManager, pluginConfig),
|
||||
new FightUnTagController(this.fightManager, pluginConfig, logoutService),
|
||||
new FightBypassAdminController(server, pluginConfig),
|
||||
new FightBypassPermissionController(server, pluginConfig),
|
||||
new FightBypassCreativeController(server, pluginConfig),
|
||||
new PlaceBlockBlocker(this.fightManager, noticeService, pluginConfig),
|
||||
new PearlController(pluginConfig, this.pearlService, noticeService, fightManager),
|
||||
new TridentController(pluginConfig, noticeService, this.fightManager, this.tridentService, server),
|
||||
new DeathFlareController(pluginConfig, server, scheduler, this),
|
||||
new DeathLightningController(pluginConfig, server),
|
||||
new UpdaterNotificationController(updaterService, pluginConfig, miniMessage),
|
||||
new KnockbackRegionController(noticeService, this.regionProvider, this.fightManager, knockbackService, server),
|
||||
new FightEffectController(pluginConfig.effect, this.fightEffectService, this.fightManager, server),
|
||||
new FightTagOutController(this.fightTagOutService),
|
||||
new FightMessageController(this.fightManager, notificationAnnouncer, this.pluginConfig, this.getServer())
|
||||
).forEach(listener -> this.getServer().getPluginManager().registerEvents(listener, this));
|
||||
new FightMessageController(this.fightManager, noticeService, pluginConfig, server),
|
||||
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 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),
|
||||
new CommandsBlocker(this.fightManager, noticeService, pluginConfig),
|
||||
new ElytraBlocker(this.fightManager, pluginConfig),
|
||||
new ElytraEquipBlocker(this.fightManager, noticeService, pluginConfig, server),
|
||||
new FlyingBlocker(this.fightManager, pluginConfig, server),
|
||||
new PlaceBlockBlocker(this.fightManager, noticeService, pluginConfig)
|
||||
);
|
||||
|
||||
new KnockbackMountController(noticeService, this.regionProvider, this.fightManager).register(this);
|
||||
|
||||
eventManager.subscribe(
|
||||
PlayerDeathEvent.class,
|
||||
pluginConfig.drop.dropEventPriority,
|
||||
new DropController(dropService, dropKeepInventoryService, pluginConfig.drop, fightManager, miniMessage)
|
||||
);
|
||||
|
||||
eventManager.subscribe(
|
||||
PlayerQuitEvent.class,
|
||||
pluginConfig.combat.quitPunishmentEventPriority,
|
||||
new LogoutController(this.fightManager, logoutService, noticeService, pluginConfig)
|
||||
);
|
||||
|
||||
EternalCombatProvider.initialize(this);
|
||||
this.apiInitialized = true;
|
||||
|
||||
long millis = started.elapsed(TimeUnit.MILLISECONDS);
|
||||
this.getLogger().info("Successfully loaded EternalCombat in " + millis + "ms");
|
||||
@@ -168,17 +237,18 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
EternalCombatProvider.deinitialize();
|
||||
|
||||
if (this.liteCommands != null) {
|
||||
this.liteCommands.unregister();
|
||||
}
|
||||
|
||||
if (this.audienceProvider != null) {
|
||||
this.audienceProvider.close();
|
||||
if (this.fightManager != null) {
|
||||
this.fightManager.untagAll();
|
||||
}
|
||||
|
||||
this.fightManager.untagAll();
|
||||
if (this.apiInitialized) {
|
||||
EternalCombatProvider.deinitialize();
|
||||
this.apiInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,8 +262,8 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public FightPearlService getFightPearlService() {
|
||||
return this.fightPearlService;
|
||||
public PearlService getFightPearlService() {
|
||||
return this.pearlService;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.eternalcode.combat;
|
||||
|
||||
import com.eternalcode.commons.bukkit.scheduler.BukkitSchedulerImpl;
|
||||
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
|
||||
import com.eternalcode.commons.folia.scheduler.FoliaSchedulerImpl;
|
||||
import java.util.logging.Logger;
|
||||
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");
|
||||
}
|
||||
|
||||
public static MinecraftScheduler getAdaptiveScheduler(Plugin plugin) {
|
||||
Logger logger = plugin.getLogger();
|
||||
|
||||
try {
|
||||
Class.forName(FOLIA_CLASS);
|
||||
logger.info("» Detected Folia environment. Using FoliaScheduler.");
|
||||
return new FoliaSchedulerImpl(plugin);
|
||||
}
|
||||
catch (ClassNotFoundException exception) {
|
||||
logger.info("» Detected Bukkit/Paper environment. Using BukkitScheduler.");
|
||||
return new BukkitSchedulerImpl(plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.eternalcode.combat;
|
||||
|
||||
import com.eternalcode.combat.config.ConfigService;
|
||||
import com.eternalcode.combat.notification.NotificationAnnouncer;
|
||||
import com.eternalcode.multification.bukkit.notice.BukkitNotice;
|
||||
import com.eternalcode.combat.notification.NoticeService;
|
||||
import com.eternalcode.multification.notice.Notice;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import dev.rollczi.litecommands.annotations.async.Async;
|
||||
@@ -14,29 +13,29 @@ import java.time.Duration;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
@Command(name = "combatlog", aliases = "combat")
|
||||
@Permission("eternalcombat.reload")
|
||||
public class EternalCombatReloadCommand {
|
||||
|
||||
private static final Notice RELOAD_MESSAGE = BukkitNotice.builder()
|
||||
private static final Notice RELOAD_MESSAGE = Notice.builder()
|
||||
.chat("<b><gradient:#8a1212:#fc6b03>EternalCombat:</gradient></b> Reloaded EternalCombat in <color:#fce303>{TIME}ms!</color>")
|
||||
.build();
|
||||
|
||||
private final ConfigService configService;
|
||||
private final NotificationAnnouncer announcer;
|
||||
private final NoticeService noticeService;
|
||||
|
||||
public EternalCombatReloadCommand(ConfigService configService, NotificationAnnouncer announcer) {
|
||||
public EternalCombatReloadCommand(ConfigService configService, NoticeService noticeService) {
|
||||
this.configService = configService;
|
||||
this.announcer = announcer;
|
||||
this.noticeService = noticeService;
|
||||
}
|
||||
|
||||
@Async
|
||||
@Execute(name = "reload")
|
||||
@Permission("eternalcombat.reload")
|
||||
void execute(@Context CommandSender sender) {
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
this.configService.reload();
|
||||
|
||||
Duration elapsed = stopwatch.elapsed();
|
||||
this.announcer.create()
|
||||
this.noticeService.create()
|
||||
.viewer(sender)
|
||||
.notice(RELOAD_MESSAGE)
|
||||
.placeholder("{TIME}", String.valueOf(elapsed.toMillis()))
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.eternalcode.combat.border;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
class BorderActivePointsIndex {
|
||||
|
||||
private final Map<String, Map<UUID, Set<BorderPoint>>> registry = new ConcurrentHashMap<>();
|
||||
|
||||
boolean hasPoints(String world, UUID player) {
|
||||
Map<UUID, Set<BorderPoint>> worldRegistry = this.registry.get(world);
|
||||
if (worldRegistry == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return worldRegistry.containsKey(player);
|
||||
}
|
||||
|
||||
Set<BorderPoint> putPoints(String world, UUID player, Set<BorderPoint> points) {
|
||||
Map<UUID, Set<BorderPoint>> worldRegistry = this.registry.computeIfAbsent(world, k -> new ConcurrentHashMap<>());
|
||||
Set<BorderPoint> oldPoints = worldRegistry.put(player, points);
|
||||
if (oldPoints != null) {
|
||||
return calculateRemovedPoints(points, oldPoints);
|
||||
}
|
||||
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
private static Set<BorderPoint> calculateRemovedPoints(Set<BorderPoint> points, Set<BorderPoint> oldPoints) {
|
||||
Set<BorderPoint> removed = new HashSet<>();
|
||||
for (BorderPoint oldPoint : oldPoints) {
|
||||
if (!points.contains(oldPoint)) {
|
||||
removed.add(oldPoint);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableSet(removed);
|
||||
}
|
||||
|
||||
Set<BorderPoint> getPoints(String world, UUID player) {
|
||||
Map<UUID, Set<BorderPoint>> worldRegistry = this.registry.get(world);
|
||||
if (worldRegistry == null) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
return worldRegistry.getOrDefault(player, Set.of());
|
||||
}
|
||||
|
||||
Set<BorderPoint> removePoints(String world, UUID player) {
|
||||
Map<UUID, Set<BorderPoint>> worldRegistry = this.registry.get(world);
|
||||
if (worldRegistry == null) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
Set<BorderPoint> remove = worldRegistry.remove(player);
|
||||
if (remove == null) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
return remove;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.eternalcode.combat.border;
|
||||
|
||||
import dev.rollczi.litecommands.shared.Lazy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class BorderLazyResult implements BorderResult {
|
||||
|
||||
private final List<Lazy<List<BorderPoint>>> borderPoints = new ArrayList<>();
|
||||
|
||||
void addLazyBorderPoints(Lazy<List<BorderPoint>> supplier) {
|
||||
borderPoints.add(supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<BorderPoint> collect() {
|
||||
return borderPoints.stream()
|
||||
.flatMap(result -> result.get().stream())
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.eternalcode.combat.border;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public record BorderPoint(int x, int y, int z, @Nullable BorderPoint innerPoint) {
|
||||
|
||||
public BorderPoint(int x, int y, int z) {
|
||||
this(x, y, z, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This point represents an adjusted coordinate that takes into account the block's volume (thickness)
|
||||
* and is used for logical placement and grouping, especially for assigning border blocks to the
|
||||
* correct chunk when they are on the very edge of a chunk boundary.
|
||||
* This ensures the block is considered to be on the "inner" side of the border for chunk-based logic.
|
||||
*/
|
||||
@Override
|
||||
public BorderPoint innerPoint() {
|
||||
if (this.innerPoint == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return this.innerPoint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.eternalcode.combat.border;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface BorderResult {
|
||||
|
||||
Set<BorderPoint> collect();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.eternalcode.combat.border;
|
||||
|
||||
import java.util.Set;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public interface BorderService {
|
||||
|
||||
void updateBorder(Player player, Location to);
|
||||
|
||||
void clearBorder(Player player);
|
||||
|
||||
Set<BorderPoint> getActiveBorder(Player player);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package com.eternalcode.combat.border;
|
||||
|
||||
import com.eternalcode.combat.border.event.BorderHideAsyncEvent;
|
||||
import com.eternalcode.combat.border.event.BorderShowAsyncEvent;
|
||||
import com.eternalcode.combat.event.EventManager;
|
||||
import com.eternalcode.combat.region.RegionProvider;
|
||||
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
|
||||
import com.eternalcode.commons.scheduler.Scheduler;
|
||||
import dev.rollczi.litecommands.shared.Lazy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class BorderServiceImpl implements BorderService {
|
||||
|
||||
private final MinecraftScheduler scheduler;
|
||||
private final EventManager eventManager;
|
||||
|
||||
private final Supplier<BorderSettings> settings;
|
||||
|
||||
private final BorderTriggerIndex borderIndexes;
|
||||
private final BorderActivePointsIndex activeBorderIndex = new BorderActivePointsIndex();
|
||||
|
||||
public BorderServiceImpl(MinecraftScheduler scheduler, Server server, RegionProvider provider, EventManager eventManager, Supplier<BorderSettings> settings) {
|
||||
this.scheduler = scheduler;
|
||||
this.eventManager = eventManager;
|
||||
this.settings = settings;
|
||||
this.borderIndexes = BorderTriggerIndex.started(server, scheduler, provider, settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBorder(Player player, Location location) {
|
||||
Optional<BorderResult> result = resolveBorder(location);
|
||||
String world = player.getWorld().getName();
|
||||
|
||||
if (result.isEmpty()) {
|
||||
if (!this.activeBorderIndex.hasPoints(world, player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearBorder(player);
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler.runAsync(() -> {
|
||||
BorderResult borderResult = result.get();
|
||||
Set<BorderPoint> points = borderResult.collect();
|
||||
|
||||
if (!points.isEmpty()) {
|
||||
BorderShowAsyncEvent event = eventManager.publishEvent(new BorderShowAsyncEvent(player, points));
|
||||
points = event.getPoints();
|
||||
}
|
||||
|
||||
Set<BorderPoint> removed = this.activeBorderIndex.putPoints(world, player.getUniqueId(), points);
|
||||
|
||||
if (!removed.isEmpty()) {
|
||||
eventManager.publishEvent(new BorderHideAsyncEvent(player, removed));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBorder(Player player) {
|
||||
World world = player.getWorld();
|
||||
UUID uniqueId = player.getUniqueId();
|
||||
|
||||
scheduler.runAsync(() -> {
|
||||
Set<BorderPoint> removed = this.activeBorderIndex.removePoints(world.getName(), uniqueId);
|
||||
if (!removed.isEmpty()) {
|
||||
eventManager.publishEvent(new BorderHideAsyncEvent(player, removed));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<BorderPoint> getActiveBorder(Player player) {
|
||||
return this.activeBorderIndex.getPoints(player.getWorld().getName(), player.getUniqueId());
|
||||
}
|
||||
|
||||
private Optional<BorderResult> resolveBorder(Location location) {
|
||||
List<BorderTrigger> triggered = borderIndexes.getTriggered(location);
|
||||
|
||||
if (triggered.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
BorderLazyResult result = new BorderLazyResult();
|
||||
for (BorderTrigger trigger : triggered) {
|
||||
result.addLazyBorderPoints(new Lazy<>(() -> this.resolveBorderPoints(trigger, location)));
|
||||
}
|
||||
|
||||
return Optional.of(result);
|
||||
}
|
||||
|
||||
/* this code is ugly but is fast */
|
||||
private List<BorderPoint> resolveBorderPoints(BorderTrigger trigger, Location playerLocation) {
|
||||
BorderPoint borderMin = trigger.min();
|
||||
BorderPoint borderMax = trigger.max();
|
||||
|
||||
int x = (int) Math.round(playerLocation.getX());
|
||||
int y = (int) Math.round(playerLocation.getY());
|
||||
int z = (int) Math.round(playerLocation.getZ());
|
||||
|
||||
int distanceRounded = settings.get().distanceRounded();
|
||||
int realMinX = Math.max(borderMin.x(), x - distanceRounded);
|
||||
int realMaxX = Math.min(borderMax.x(), x + distanceRounded);
|
||||
int realMinY = Math.max(borderMin.y(), y - distanceRounded);
|
||||
int realMaxY = Math.min(borderMax.y(), y + distanceRounded);
|
||||
int realMinZ = Math.max(borderMin.z(), z - distanceRounded);
|
||||
int realMaxZ = Math.min(borderMax.z(), z + distanceRounded);
|
||||
|
||||
List<BorderPoint> points = new ArrayList<>();
|
||||
|
||||
if (borderMin.y() >= realMinY) { // Bottom wall
|
||||
for (int currentX = realMinX; currentX <= realMaxX - 1; currentX++) {
|
||||
for (int currentZ = realMinZ; currentZ <= realMaxZ - 1; currentZ++) {
|
||||
addPoint(points, currentX, realMinY, currentZ, playerLocation, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (borderMax.y() <= realMaxY) { // Top wall
|
||||
for (int currentX = realMinX; currentX <= realMaxX; currentX++) {
|
||||
for (int currentZ = realMinZ; currentZ <= realMaxZ; currentZ++) {
|
||||
BorderPoint innerPoint = new BorderPoint(Math.max(realMinX, currentX - 1), realMaxY - 1, Math.max(realMinZ, currentZ - 1));
|
||||
addPoint(points, currentX, realMaxY, currentZ, playerLocation, innerPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (borderMin.x() >= realMinX) { // West wall (left)
|
||||
for (int currentY = realMinY; currentY <= realMaxY - 1; currentY++) {
|
||||
for (int currentZ = realMinZ; currentZ <= realMaxZ - 1; currentZ++) {
|
||||
addPoint(points, realMinX, currentY, currentZ, playerLocation, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (borderMax.x() <= realMaxX) { // East wall (right)
|
||||
for (int currentY = realMinY; currentY <= realMaxY; currentY++) {
|
||||
for (int currentZ = realMinZ; currentZ <= realMaxZ; currentZ++) {
|
||||
BorderPoint innerPoint = new BorderPoint(realMaxX - 1, Math.max(realMinY, currentY - 1), Math.max(realMinZ, currentZ - 1));
|
||||
addPoint(points, realMaxX, currentY, currentZ, playerLocation, innerPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (borderMin.z() >= realMinZ) { // North wall (front)
|
||||
for (int currentX = realMinX; currentX <= realMaxX - 1; currentX++) {
|
||||
for (int currentY = realMinY; currentY <= realMaxY - 1; currentY++) {
|
||||
addPoint(points, currentX, currentY, realMinZ, playerLocation, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (borderMax.z() <= realMaxZ) { // South wall (back)
|
||||
for (int currentX = realMinX; currentX <= realMaxX; currentX++) {
|
||||
for (int currentY = realMinY; currentY <= realMaxY; currentY++) {
|
||||
BorderPoint innerPoint = new BorderPoint(Math.max(realMinX, currentX - 1), Math.max(realMinY, currentY - 1), realMaxZ - 1);
|
||||
addPoint(points, currentX, currentY, realMaxZ, playerLocation, innerPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
private void addPoint(List<BorderPoint> points, int x, int y, int z, Location playerLocation, BorderPoint innerPoint) {
|
||||
if (isVisible(x, y, z, playerLocation)) {
|
||||
points.add(new BorderPoint(x, y, z, innerPoint));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isVisible(int x, int y, int z, Location player) {
|
||||
return Math.sqrt(Math.pow(x - player.getX(), 2) + Math.pow(y - player.getY(), 2) + Math.pow(z - player.getZ(), 2)) <= this.settings.get().distance;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.eternalcode.combat.border;
|
||||
|
||||
import com.eternalcode.combat.border.animation.block.BlockSettings;
|
||||
import com.eternalcode.combat.border.animation.particle.ParticleSettings;
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
import java.time.Duration;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
public class BorderSettings extends OkaeriConfig {
|
||||
|
||||
@Comment("# Border view distance in blocks")
|
||||
public double distance = 6.5;
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Border block animation settings",
|
||||
"# Configure the block animation that appears during combat."
|
||||
})
|
||||
public BlockSettings block = new BlockSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Border particle animation settings",
|
||||
"# Configure the particle animation that appears during combat."
|
||||
})
|
||||
public ParticleSettings particle = new ParticleSettings();
|
||||
|
||||
@ApiStatus.Internal
|
||||
public Duration indexRefreshDelay() {
|
||||
return Duration.ofSeconds(1);
|
||||
}
|
||||
|
||||
public int distanceRounded() {
|
||||
return (int) Math.ceil(this.distance);
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.block.enabled || this.particle.enabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.eternalcode.combat.border;
|
||||
|
||||
record BorderTrigger(BorderPoint min, BorderPoint max, BorderPoint triggerMin, BorderPoint triggerMax) {
|
||||
|
||||
BorderTrigger(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, int distance) {
|
||||
this(
|
||||
new BorderPoint(minX, minY, minZ),
|
||||
new BorderPoint(maxX, maxY, maxZ),
|
||||
new BorderPoint(minX - distance, minY - distance, minZ - distance),
|
||||
new BorderPoint(maxX + distance, maxY + distance, maxZ + distance)
|
||||
);
|
||||
}
|
||||
|
||||
public boolean isTriggered(int x, int y, int z) {
|
||||
return x >= triggerMin.x() && x <= triggerMax.x()
|
||||
&& y >= triggerMin.y() && y <= triggerMax.y()
|
||||
&& z >= triggerMin.z() && z <= triggerMax.z();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.eternalcode.combat.border;
|
||||
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import com.eternalcode.combat.fight.event.FightTagEvent;
|
||||
import com.eternalcode.combat.fight.event.FightUntagEvent;
|
||||
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
|
||||
import java.time.Duration;
|
||||
import java.util.function.Supplier;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
|
||||
public class BorderTriggerController implements Listener {
|
||||
|
||||
private static final long BORDER_WORK_AROUND_DELAY_MILLIS = 250;
|
||||
|
||||
private final BorderService borderService;
|
||||
private final Supplier<BorderSettings> border;
|
||||
private final FightManager fightManager;
|
||||
private final Server server;
|
||||
private final MinecraftScheduler scheduler;
|
||||
|
||||
public BorderTriggerController(
|
||||
BorderService borderService,
|
||||
Supplier<BorderSettings> border,
|
||||
FightManager fightManager,
|
||||
Server server,
|
||||
MinecraftScheduler scheduler
|
||||
) {
|
||||
this.borderService = borderService;
|
||||
this.border = border;
|
||||
this.fightManager = fightManager;
|
||||
this.server = server;
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
void onMove(PlayerMoveEvent event) {
|
||||
if (!border.get().isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location to = event.getTo();
|
||||
Location from = event.getFrom();
|
||||
if (to.getBlockX() == from.getBlockX() && to.getBlockY() == from.getBlockY() && to.getBlockZ() == from.getBlockZ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
if (!fightManager.isInCombat(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
borderService.updateBorder(player, to);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
void onTeleport(PlayerTeleportEvent event) {
|
||||
if (!border.get().isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
if (!fightManager.isInCombat(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
borderService.updateBorder(player, event.getTo());
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
void onFightStart(FightTagEvent event) {
|
||||
if (!border.get().isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = server.getPlayer(event.getPlayer());
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
borderService.updateBorder(player, player.getLocation());
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
void onFightEnd(FightUntagEvent event) {
|
||||
if (!border.get().isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = server.getPlayer(event.getPlayer());
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Due to race condition in PlayerMoveEvent and FightUntagEvent - border can be triggered after death and untag of player - solution delayed removal of border
|
||||
this.scheduler.runLater(() -> {
|
||||
if (!this.fightManager.isInCombat(player.getUniqueId())) {
|
||||
this.borderService.clearBorder(player);
|
||||
}
|
||||
}, Duration.ofMillis(BORDER_WORK_AROUND_DELAY_MILLIS));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.eternalcode.combat.border;
|
||||
|
||||
import com.eternalcode.combat.region.Region;
|
||||
import com.eternalcode.combat.region.RegionProvider;
|
||||
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
|
||||
import com.eternalcode.commons.scheduler.Scheduler;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
|
||||
class BorderTriggerIndex {
|
||||
|
||||
private final Server server;
|
||||
private final MinecraftScheduler scheduler;
|
||||
private final RegionProvider provider;
|
||||
private final Supplier<BorderSettings> settings;
|
||||
|
||||
private final Map<String, BorderTriggerIndexBucket> borderIndexes = new HashMap<>();
|
||||
|
||||
private BorderTriggerIndex(Server server, MinecraftScheduler scheduler, RegionProvider provider, Supplier<BorderSettings> settings) {
|
||||
this.server = server;
|
||||
this.scheduler = scheduler;
|
||||
this.provider = provider;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
private void updateWorlds() {
|
||||
for (World world : server.getWorlds()) {
|
||||
this.updateWorld(world.getName(), provider.getRegions(world));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWorld(String world, Collection<Region> regions) {
|
||||
this.scheduler.runAsync(() -> {
|
||||
int distanceRounded = settings.get().distanceRounded();
|
||||
List<BorderTrigger> triggers = new ArrayList<>();
|
||||
for (Region region : regions) {
|
||||
Location min = region.getMin();
|
||||
Location max = region.getMax();
|
||||
|
||||
triggers.add(new BorderTrigger(
|
||||
min.getBlockX(), min.getBlockY(), min.getBlockZ(),
|
||||
max.getBlockX() + 1, max.getBlockY() + 1, max.getBlockZ() + 1,
|
||||
distanceRounded
|
||||
));
|
||||
}
|
||||
|
||||
BorderTriggerIndexBucket index = BorderTriggerIndexBucket.create(triggers);
|
||||
this.borderIndexes.put(world, index);
|
||||
});
|
||||
}
|
||||
|
||||
static BorderTriggerIndex started(Server server, MinecraftScheduler scheduler, RegionProvider provider, Supplier<BorderSettings> settings) {
|
||||
BorderTriggerIndex index = new BorderTriggerIndex(server, scheduler, provider, settings);
|
||||
scheduler.timerAsync(() -> index.updateWorlds(), Duration.ZERO, settings.get().indexRefreshDelay());
|
||||
return index;
|
||||
}
|
||||
|
||||
public List<BorderTrigger> getTriggered(Location location) {
|
||||
BorderTriggerIndexBucket index = borderIndexes.get(location.getWorld().getName());
|
||||
if (index == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
int x = (int) Math.round(location.getX());
|
||||
int y = (int) Math.round(location.getY());
|
||||
int z = (int) Math.round(location.getZ());
|
||||
|
||||
return index.getTriggers(x, z)
|
||||
.stream()
|
||||
.filter(trigger -> trigger.isTriggered(x, y, z))
|
||||
.toList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.eternalcode.combat.border;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
class BorderTriggerIndexBucket {
|
||||
|
||||
/**
|
||||
* Represents "00000000 00000000 00000000 00000000 11111111 11111111 11111111 11111111" bit mask.
|
||||
* This allows removing left bits when used with AND bit operator.
|
||||
* */
|
||||
private static final long LEFT_INT_MASK = 0xFFFFFFFFL;
|
||||
|
||||
/**
|
||||
* Allows packing the coordination on one axis from 256 into 1
|
||||
* Why? - We divide the world into fragments of size 256x256
|
||||
* Why it is 8? Because when you shift bits, then the value will be smaller / bigger in scale 2^n
|
||||
* E.g `2^4 = 16` (standard minecraft chunk size) in our case, it is `2^8 = 256`
|
||||
*/
|
||||
private static final int CHUNK_SHIFT = 8;
|
||||
|
||||
private final Map<Long, Set<BorderTrigger>> index = new HashMap<>();
|
||||
|
||||
private BorderTriggerIndexBucket() {
|
||||
}
|
||||
|
||||
Set<BorderTrigger> getTriggers(int x, int z) {
|
||||
long position = packChunk(x >> CHUNK_SHIFT, z >> CHUNK_SHIFT);
|
||||
return this.index.getOrDefault(position, Set.of());
|
||||
}
|
||||
|
||||
static BorderTriggerIndexBucket create(Collection<BorderTrigger> triggers) {
|
||||
return new BorderTriggerIndexBucket().with(triggers);
|
||||
}
|
||||
|
||||
private BorderTriggerIndexBucket with(Collection<BorderTrigger> triggers) {
|
||||
for (BorderTrigger trigger : triggers) {
|
||||
withTrigger(trigger);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void withTrigger(BorderTrigger trigger) {
|
||||
int minX = trigger.triggerMin().x() >> CHUNK_SHIFT;
|
||||
int minZ = trigger.triggerMin().z() >> CHUNK_SHIFT;
|
||||
int maxX = trigger.triggerMax().x() >> CHUNK_SHIFT;
|
||||
int maxZ = trigger.triggerMax().z() >> CHUNK_SHIFT;
|
||||
|
||||
int startX = Math.min(minX, maxX);
|
||||
int startZ = Math.min(minZ, maxZ);
|
||||
int endX = Math.max(minX, maxX);
|
||||
int endZ = Math.max(minZ, maxZ);
|
||||
|
||||
for (int chunkX = startX; chunkX <= endX; chunkX++) {
|
||||
for (int chunkZ = startZ; chunkZ <= endZ; chunkZ++) {
|
||||
long packed = packChunk(chunkX, chunkZ);
|
||||
this.index.computeIfAbsent(packed, key -> new HashSet<>())
|
||||
.add(trigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Packs two ints into long value.
|
||||
* <p>
|
||||
* For example for values:
|
||||
* <ul>
|
||||
* <li>X - 00000000 00000000 00000000 00000001 </li>
|
||||
* <li>Z - 00000000 00000000 00000000 00000111 </li>
|
||||
* </ul>
|
||||
* This method will return:<br>
|
||||
* 00000000 00000000 00000000 00000111 00000000 00000000 00000000 00000001
|
||||
* </p>
|
||||
* @param bigChunkX right int to pack
|
||||
* @param bigChunkZ left int to pack
|
||||
*/
|
||||
private static long packChunk(int bigChunkX, int bigChunkZ) {
|
||||
return (long) bigChunkX & LEFT_INT_MASK | ((long) bigChunkZ & LEFT_INT_MASK) << Integer.SIZE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.eternalcode.combat.border.animation;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public final class BorderColorUtil {
|
||||
|
||||
private BorderColorUtil() {
|
||||
}
|
||||
|
||||
public static Color xyzToRainbow(int x, int y, int z) {
|
||||
float hue = (float) ((Math.sin(x * 0.05) + Math.cos(z * 0.05)) * 0.5 + 0.5);
|
||||
float saturation = 1.0f;
|
||||
float brightness = 0.8f + 0.2f * Math.max(0.0f, Math.min(1.0f, (float) y / 255));
|
||||
|
||||
return Color.getHSBColor(hue, saturation, brightness);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.eternalcode.combat.border.animation.block;
|
||||
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
import java.time.Duration;
|
||||
|
||||
public class BlockSettings extends OkaeriConfig {
|
||||
|
||||
@Comment("# Enable block animation?")
|
||||
public boolean enabled = true;
|
||||
|
||||
@Comment({
|
||||
"# Block type used for rendering the border",
|
||||
"# Custom: RAINBOW_GLASS, RAINBOW_WOOL, RAINBOW_TERRACOTTA, RAINBOW_CONCRETE",
|
||||
"# Vanilla: https://javadocs.packetevents.com/com/github/retrooper/packetevents/protocol/world/states/type/StateTypes.html"
|
||||
})
|
||||
public BlockType type = BlockType.RAINBOW_GLASS;
|
||||
|
||||
@Comment({
|
||||
"# Delay between each async animation update (need restart to apply)",
|
||||
"# Lower values will decrease performance but will make the animation smoother",
|
||||
"# Higher values will increase performance"
|
||||
})
|
||||
public Duration updateDelay = Duration.ofMillis(250);
|
||||
|
||||
@Comment({
|
||||
"# Delay between each chunk cache update (need restart to apply)",
|
||||
"# Lower values will decrease performance",
|
||||
"# Higher values will increase performance but may cause overlapping existing blocks (this does not modify the world)"
|
||||
})
|
||||
public Duration chunkCacheDelay = Duration.ofMillis(300);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.eternalcode.combat.border.animation.block;
|
||||
|
||||
import com.eternalcode.combat.border.BorderPoint;
|
||||
import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToConcrete;
|
||||
import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToGlass;
|
||||
import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToTerracotta;
|
||||
import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToWool;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
|
||||
import java.util.Locale;
|
||||
|
||||
public class BlockType {
|
||||
|
||||
public static final BlockType RAINBOW_GLASS = new BlockType("RAINBOW_GLASS", point -> xyzToGlass(point));
|
||||
public static final BlockType RAINBOW_TERRACOTTA = new BlockType("RAINBOW_TERRACOTTA", point -> xyzToTerracotta(point));
|
||||
public static final BlockType RAINBOW_WOOL = new BlockType("RAINBOW_WOOL", point -> xyzToWool(point));
|
||||
public static final BlockType RAINBOW_CONCRETE = new BlockType("RAINBOW_CONCRETE", point -> xyzToConcrete(point));
|
||||
|
||||
private final String name;
|
||||
private final TypeProvider type;
|
||||
|
||||
private BlockType(String name, TypeProvider type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public StateType getStateType(BorderPoint point) {
|
||||
return type.provide(point);
|
||||
}
|
||||
|
||||
public static BlockType fromName(String name) {
|
||||
return switch (name) {
|
||||
case "RAINBOW_GLASS" -> RAINBOW_GLASS;
|
||||
case "RAINBOW_WOOL" -> RAINBOW_WOOL;
|
||||
case "RAINBOW_TERRACOTTA" -> RAINBOW_TERRACOTTA;
|
||||
case "RAINBOW_CONCRETE" -> RAINBOW_CONCRETE;
|
||||
default -> new BlockType(name, point -> StateTypes.getByName(name.toLowerCase(Locale.ROOT)));
|
||||
};
|
||||
}
|
||||
|
||||
private interface TypeProvider {
|
||||
StateType provide(BorderPoint point);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.eternalcode.combat.border.animation.block;
|
||||
|
||||
import eu.okaeri.configs.schema.GenericsPair;
|
||||
import eu.okaeri.configs.serdes.BidirectionalTransformer;
|
||||
import eu.okaeri.configs.serdes.SerdesContext;
|
||||
import java.util.Locale;
|
||||
|
||||
public class BlockTypeTransformer extends BidirectionalTransformer<String, BlockType> {
|
||||
|
||||
@Override
|
||||
public GenericsPair<String, BlockType> getPair() {
|
||||
return this.genericsPair(String.class, BlockType.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockType leftToRight(String data, SerdesContext serdesContext) {
|
||||
BlockType blockType = BlockType.fromName(data);
|
||||
if (blockType == null) {
|
||||
throw new IllegalArgumentException("Unknown block type: " + data);
|
||||
}
|
||||
|
||||
return blockType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rightToLeft(BlockType data, SerdesContext serdesContext) {
|
||||
return data.getName().toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package com.eternalcode.combat.border.animation.block;
|
||||
|
||||
import com.eternalcode.combat.border.BorderPoint;
|
||||
import com.eternalcode.combat.border.BorderService;
|
||||
import com.eternalcode.combat.border.event.BorderHideAsyncEvent;
|
||||
import com.eternalcode.combat.border.event.BorderShowAsyncEvent;
|
||||
import com.eternalcode.commons.scheduler.Scheduler;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.PacketEventsAPI;
|
||||
import com.github.retrooper.packetevents.manager.player.PlayerManager;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange.EncodedBlock;
|
||||
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.bukkit.ChunkSnapshot;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
public class BorderBlockController implements Listener {
|
||||
|
||||
public static final PacketEventsAPI<?> PACKET_EVENTS = PacketEvents.getAPI();
|
||||
public static final PlayerManager PLAYER_MANAGER = PACKET_EVENTS.getPlayerManager();
|
||||
public static final ClientVersion SERVER_VERSION = PACKET_EVENTS.getServerManager().getVersion().toClientVersion();
|
||||
|
||||
public static final WrappedBlockState AIR_ID = WrappedBlockState.getDefaultState(SERVER_VERSION, StateTypes.AIR);
|
||||
public static final int MINECRAFT_CHUNK_SHIFT = 4;
|
||||
|
||||
private final BorderService borderService;
|
||||
private final Supplier<BlockSettings> settings;
|
||||
private final Server server;
|
||||
|
||||
private final Set<UUID> playersToUpdate = ConcurrentHashMap.newKeySet();
|
||||
private final Map<UUID, Object> lockedPlayers = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, Map<BorderPoint, WrappedBlockState>> originalBlocks = new ConcurrentHashMap<>();
|
||||
private final ChunkCache chunkCache;
|
||||
|
||||
public BorderBlockController(BorderService borderService, Supplier<BlockSettings> settings, Scheduler scheduler, Server server) {
|
||||
this.borderService = borderService;
|
||||
this.settings = settings;
|
||||
this.server = server;
|
||||
this.chunkCache = new ChunkCache(settings.get());
|
||||
|
||||
scheduler.timerAsync(() -> this.updatePlayers(), settings.get().updateDelay, settings.get().updateDelay);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onBorderShowAsyncEvent(BorderShowAsyncEvent event) {
|
||||
if (!this.settings.get().enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
UUID playerId = player.getUniqueId();
|
||||
|
||||
synchronized (this.getLock(playerId)) {
|
||||
Set<BorderPoint> borderPoints = this.savePassablePoints(player, event.getPoints());
|
||||
|
||||
event.setPoints(borderPoints);
|
||||
this.showBorder(player, borderPoints);
|
||||
this.playersToUpdate.add(playerId);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onBorderHideAsyncEvent(BorderHideAsyncEvent event) {
|
||||
if (!this.settings.get().enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID playerId = event.getPlayer().getUniqueId();
|
||||
|
||||
synchronized (this.getLock(playerId)) {
|
||||
this.hideBorder(event.getPlayer(), event.getPoints());
|
||||
|
||||
Set<BorderPoint> border = this.borderService.getActiveBorder(event.getPlayer());
|
||||
if (border.isEmpty()) {
|
||||
this.playersToUpdate.remove(playerId);
|
||||
this.originalBlocks.remove(playerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onPlayerQuit(PlayerQuitEvent event) {
|
||||
UUID playerId = event.getPlayer().getUniqueId();
|
||||
|
||||
this.playersToUpdate.remove(playerId);
|
||||
this.originalBlocks.remove(playerId);
|
||||
this.lockedPlayers.remove(playerId);
|
||||
}
|
||||
|
||||
private void updatePlayers() {
|
||||
if (!this.settings.get().enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (UUID uuid : this.playersToUpdate) {
|
||||
Player player = this.server.getPlayer(uuid);
|
||||
if (player == null) {
|
||||
this.playersToUpdate.remove(uuid);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.updatePlayer(uuid, player);
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlayer(UUID uuid, Player player) {
|
||||
synchronized (this.getLock(uuid)) {
|
||||
Set<BorderPoint> border = this.borderService.getActiveBorder(player);
|
||||
|
||||
if (border.isEmpty()) {
|
||||
this.playersToUpdate.remove(uuid);
|
||||
this.originalBlocks.remove(uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
Set<BorderPoint> passablePoints = this.savePassablePoints(player, border);
|
||||
this.showBorder(player, passablePoints);
|
||||
}
|
||||
}
|
||||
|
||||
private void showBorder(Player player, Collection<BorderPoint> blocks) {
|
||||
this.groupByChunkPacket(blocks).forEach((chunkPos, chunkPoints) -> {
|
||||
EncodedBlock[] encodedBlocks = chunkPoints.stream()
|
||||
.map(point -> toBorderBlock(point))
|
||||
.toArray(EncodedBlock[]::new);
|
||||
|
||||
PLAYER_MANAGER.sendPacket(player, new WrapperPlayServerMultiBlockChange(chunkPos, true, encodedBlocks));
|
||||
});
|
||||
}
|
||||
|
||||
private void hideBorder(Player player, Collection<BorderPoint> blocks) {
|
||||
UUID playerId = player.getUniqueId();
|
||||
Map<BorderPoint, WrappedBlockState> savedBlocks = this.originalBlocks.get(playerId);
|
||||
|
||||
this.groupByChunkPacket(blocks).forEach((chunkPosition, chunkPoints) -> {
|
||||
EncodedBlock[] originalBlocks = chunkPoints.stream()
|
||||
.map(point -> this.toOriginalBlock(point, savedBlocks))
|
||||
.toArray(EncodedBlock[]::new);
|
||||
|
||||
PLAYER_MANAGER.sendPacket(player, new WrapperPlayServerMultiBlockChange(chunkPosition, true, originalBlocks));
|
||||
});
|
||||
|
||||
if (savedBlocks != null) {
|
||||
blocks.forEach(key -> savedBlocks.remove(key));
|
||||
}
|
||||
}
|
||||
|
||||
private Set<BorderPoint> savePassablePoints(Player player, Collection<BorderPoint> points) {
|
||||
UUID playerId = player.getUniqueId();
|
||||
Map<BorderPoint, WrappedBlockState> savedBlocks = this.originalBlocks.computeIfAbsent(playerId, key -> new ConcurrentHashMap<>());
|
||||
|
||||
return this.groupByChunk(points).entrySet().stream()
|
||||
.flatMap(entry -> this.savePassableBlock(player, entry, savedBlocks))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private Stream<BorderPoint> savePassableBlock(Player player, Entry<ChunkLocation, Set<BorderPoint>> entry, Map<BorderPoint, WrappedBlockState> savedBlocks) {
|
||||
ChunkSnapshot snapshot = this.chunkCache.loadSnapshot(player, entry.getKey());
|
||||
|
||||
if (snapshot == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
ChunkLocation chunk = entry.getKey();
|
||||
return entry.getValue().stream().filter(point -> this.saveBlockIfPassable(point, chunk, snapshot, savedBlocks));
|
||||
}
|
||||
|
||||
private boolean saveBlockIfPassable(BorderPoint point, ChunkLocation chunk, ChunkSnapshot snapshot, Map<BorderPoint, WrappedBlockState> savedBlocks) {
|
||||
int localX = point.x() - (chunk.x() << MINECRAFT_CHUNK_SHIFT);
|
||||
int localZ = point.z() - (chunk.z() << MINECRAFT_CHUNK_SHIFT);
|
||||
|
||||
BlockData blockData = snapshot.getBlockData(localX, point.y(), localZ);
|
||||
if (blockData.getMaterial().isSolid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
savedBlocks.put(point, SpigotConversionUtil.fromBukkitBlockData(blockData));
|
||||
return true;
|
||||
}
|
||||
|
||||
private EncodedBlock toBorderBlock(BorderPoint point) {
|
||||
StateType type = this.settings.get().type.getStateType(point);
|
||||
int blockId = WrappedBlockState.getDefaultState(SERVER_VERSION, type).getGlobalId();
|
||||
|
||||
return new EncodedBlock(blockId, point.x(), point.y(), point.z());
|
||||
}
|
||||
|
||||
private EncodedBlock toOriginalBlock(BorderPoint point, Map<BorderPoint, WrappedBlockState> savedBlocks) {
|
||||
WrappedBlockState blockId = savedBlocks != null ? savedBlocks.getOrDefault(point, AIR_ID) : AIR_ID;
|
||||
|
||||
return new EncodedBlock(blockId, point.x(), point.y(), point.z());
|
||||
}
|
||||
|
||||
private Map<ChunkLocation, Set<BorderPoint>> groupByChunk(Collection<BorderPoint> points) {
|
||||
return points.stream()
|
||||
.map(borderPoint -> borderPoint.innerPoint())
|
||||
.collect(Collectors.groupingBy(point -> new ChunkLocation(point.x() >> MINECRAFT_CHUNK_SHIFT, point.z() >> MINECRAFT_CHUNK_SHIFT),
|
||||
Collectors.toSet()
|
||||
));
|
||||
}
|
||||
|
||||
private Map<Vector3i, Set<BorderPoint>> groupByChunkPacket(Collection<BorderPoint> points) {
|
||||
return points.stream().collect(Collectors.groupingBy(
|
||||
point -> new Vector3i(point.x() >> MINECRAFT_CHUNK_SHIFT, point.y() >> MINECRAFT_CHUNK_SHIFT, point.z() >> MINECRAFT_CHUNK_SHIFT),
|
||||
Collectors.toSet()
|
||||
));
|
||||
}
|
||||
|
||||
private Object getLock(UUID uuid) {
|
||||
return this.lockedPlayers.computeIfAbsent(uuid, k -> new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.eternalcode.combat.border.animation.block;
|
||||
|
||||
import com.eternalcode.combat.border.BorderPoint;
|
||||
import com.eternalcode.combat.border.animation.BorderColorUtil;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.awt.Color;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import org.bukkit.DyeColor;
|
||||
|
||||
final class BorderBlockRainbowUtil {
|
||||
|
||||
private final static Map<DyeColor, StateType> GLASSES = new EnumMap<>(ImmutableMap.<DyeColor, StateType>builder()
|
||||
.put(DyeColor.WHITE, StateTypes.WHITE_STAINED_GLASS)
|
||||
.put(DyeColor.ORANGE, StateTypes.ORANGE_STAINED_GLASS)
|
||||
.put(DyeColor.MAGENTA, StateTypes.MAGENTA_STAINED_GLASS)
|
||||
.put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_STAINED_GLASS)
|
||||
.put(DyeColor.YELLOW, StateTypes.YELLOW_STAINED_GLASS)
|
||||
.put(DyeColor.LIME, StateTypes.LIME_STAINED_GLASS)
|
||||
.put(DyeColor.PINK, StateTypes.PINK_STAINED_GLASS)
|
||||
.put(DyeColor.GRAY, StateTypes.GRAY_STAINED_GLASS)
|
||||
.put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_STAINED_GLASS)
|
||||
.put(DyeColor.CYAN, StateTypes.CYAN_STAINED_GLASS)
|
||||
.put(DyeColor.PURPLE, StateTypes.PURPLE_STAINED_GLASS)
|
||||
.put(DyeColor.BLUE, StateTypes.BLUE_STAINED_GLASS)
|
||||
.put(DyeColor.BROWN, StateTypes.BROWN_STAINED_GLASS)
|
||||
.put(DyeColor.GREEN, StateTypes.GREEN_STAINED_GLASS)
|
||||
.put(DyeColor.RED, StateTypes.RED_STAINED_GLASS)
|
||||
.build()
|
||||
);
|
||||
|
||||
private final static Map<DyeColor, StateType> TERRACOTTA = new EnumMap<>(ImmutableMap.<DyeColor, StateType>builder()
|
||||
.put(DyeColor.WHITE, StateTypes.WHITE_TERRACOTTA)
|
||||
.put(DyeColor.ORANGE, StateTypes.ORANGE_TERRACOTTA)
|
||||
.put(DyeColor.MAGENTA, StateTypes.MAGENTA_TERRACOTTA)
|
||||
.put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_TERRACOTTA)
|
||||
.put(DyeColor.YELLOW, StateTypes.YELLOW_TERRACOTTA)
|
||||
.put(DyeColor.LIME, StateTypes.LIME_TERRACOTTA)
|
||||
.put(DyeColor.PINK, StateTypes.PINK_TERRACOTTA)
|
||||
.put(DyeColor.GRAY, StateTypes.GRAY_TERRACOTTA)
|
||||
.put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_TERRACOTTA)
|
||||
.put(DyeColor.CYAN, StateTypes.CYAN_TERRACOTTA)
|
||||
.put(DyeColor.PURPLE, StateTypes.PURPLE_TERRACOTTA)
|
||||
.put(DyeColor.BLUE, StateTypes.BLUE_TERRACOTTA)
|
||||
.put(DyeColor.BROWN, StateTypes.BROWN_TERRACOTTA)
|
||||
.put(DyeColor.GREEN, StateTypes.GREEN_TERRACOTTA)
|
||||
.put(DyeColor.RED, StateTypes.RED_TERRACOTTA)
|
||||
.build()
|
||||
);
|
||||
|
||||
private final static Map<DyeColor, StateType> WOOLS = new EnumMap<>(ImmutableMap.<DyeColor, StateType>builder()
|
||||
.put(DyeColor.WHITE, StateTypes.WHITE_WOOL)
|
||||
.put(DyeColor.ORANGE, StateTypes.ORANGE_WOOL)
|
||||
.put(DyeColor.MAGENTA, StateTypes.MAGENTA_WOOL)
|
||||
.put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_WOOL)
|
||||
.put(DyeColor.YELLOW, StateTypes.YELLOW_WOOL)
|
||||
.put(DyeColor.LIME, StateTypes.LIME_WOOL)
|
||||
.put(DyeColor.PINK, StateTypes.PINK_WOOL)
|
||||
.put(DyeColor.GRAY, StateTypes.GRAY_WOOL)
|
||||
.put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_WOOL)
|
||||
.put(DyeColor.CYAN, StateTypes.CYAN_WOOL)
|
||||
.put(DyeColor.PURPLE, StateTypes.PURPLE_WOOL)
|
||||
.put(DyeColor.BLUE, StateTypes.BLUE_WOOL)
|
||||
.put(DyeColor.BROWN, StateTypes.BROWN_WOOL)
|
||||
.put(DyeColor.GREEN, StateTypes.GREEN_WOOL)
|
||||
.put(DyeColor.RED, StateTypes.RED_WOOL)
|
||||
.build()
|
||||
);
|
||||
|
||||
private final static Map<DyeColor, StateType> CONCRETE = new EnumMap<>(ImmutableMap.<DyeColor, StateType>builder()
|
||||
.put(DyeColor.WHITE, StateTypes.WHITE_CONCRETE)
|
||||
.put(DyeColor.ORANGE, StateTypes.ORANGE_CONCRETE)
|
||||
.put(DyeColor.MAGENTA, StateTypes.MAGENTA_CONCRETE)
|
||||
.put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_CONCRETE)
|
||||
.put(DyeColor.YELLOW, StateTypes.YELLOW_CONCRETE)
|
||||
.put(DyeColor.LIME, StateTypes.LIME_CONCRETE)
|
||||
.put(DyeColor.PINK, StateTypes.PINK_CONCRETE)
|
||||
.put(DyeColor.GRAY, StateTypes.GRAY_CONCRETE)
|
||||
.put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_CONCRETE)
|
||||
.put(DyeColor.CYAN, StateTypes.CYAN_CONCRETE)
|
||||
.put(DyeColor.PURPLE, StateTypes.PURPLE_CONCRETE)
|
||||
.put(DyeColor.BLUE, StateTypes.BLUE_CONCRETE)
|
||||
.put(DyeColor.BROWN, StateTypes.BROWN_CONCRETE)
|
||||
.put(DyeColor.GREEN, StateTypes.GREEN_CONCRETE)
|
||||
.put(DyeColor.RED, StateTypes.RED_CONCRETE)
|
||||
.build()
|
||||
);
|
||||
|
||||
static StateType xyzToGlass(BorderPoint point) {
|
||||
return GLASSES.get(xyzToDye(point));
|
||||
}
|
||||
|
||||
static StateType xyzToTerracotta(BorderPoint point) {
|
||||
return TERRACOTTA.get(xyzToDye(point));
|
||||
}
|
||||
|
||||
public static StateType xyzToWool(BorderPoint point) {
|
||||
return WOOLS.get(xyzToDye(point));
|
||||
}
|
||||
|
||||
public static StateType xyzToConcrete(BorderPoint point) {
|
||||
return CONCRETE.get(xyzToDye(point));
|
||||
}
|
||||
|
||||
private static DyeColor xyzToDye(BorderPoint point) {
|
||||
Color rgb = BorderColorUtil.xyzToRainbow(point.x(), point.y(), point.z());
|
||||
int distance = Integer.MAX_VALUE;
|
||||
DyeColor dye = DyeColor.WHITE;
|
||||
|
||||
for (DyeColor currentDye : DyeColor.values()) {
|
||||
org.bukkit.Color color = currentDye.getColor();
|
||||
int currentDistance = Math.abs(color.getRed() - rgb.getRed()) + Math.abs(color.getGreen() - rgb.getGreen()) + Math.abs(color.getBlue() - rgb.getBlue());
|
||||
if (currentDistance < distance) {
|
||||
distance = currentDistance;
|
||||
dye = currentDye;
|
||||
}
|
||||
}
|
||||
return dye;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.eternalcode.combat.border.animation.block;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import org.bukkit.ChunkSnapshot;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
class ChunkCache {
|
||||
|
||||
private final Cache<ChunkLocation, ChunkSnapshot> chunkCache;
|
||||
|
||||
ChunkCache(BlockSettings settings) {
|
||||
this.chunkCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(settings.chunkCacheDelay)
|
||||
.build();
|
||||
}
|
||||
|
||||
public ChunkSnapshot loadSnapshot(Player player, ChunkLocation location) {
|
||||
ChunkSnapshot snapshot = chunkCache.getIfPresent(location);
|
||||
if (snapshot != null) {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
ChunkSnapshot chunkSnapshot = player.getWorld().getChunkAtAsync(location.x(), location.z(), false)
|
||||
.thenApply(chunk -> chunk.getChunkSnapshot())
|
||||
.join();
|
||||
|
||||
chunkCache.put(location, chunkSnapshot);
|
||||
return chunkSnapshot;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.eternalcode.combat.border.animation.block;
|
||||
|
||||
record ChunkLocation(int x, int z) {
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.eternalcode.combat.border.animation.particle;
|
||||
|
||||
import com.eternalcode.combat.border.BorderPoint;
|
||||
import com.eternalcode.combat.border.animation.BorderColorUtil;
|
||||
import java.awt.Color;
|
||||
|
||||
public class ParticleColor {
|
||||
|
||||
public static final ParticleColor RAINBOW = new ParticleColor("RAINBOW", point -> BorderColorUtil.xyzToRainbow(point.x(), point.y(), point.z()));
|
||||
|
||||
private final String name;
|
||||
private final ColorProvider color;
|
||||
|
||||
private ParticleColor(String name, ColorProvider color) {
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Color getColor(BorderPoint point) {
|
||||
return color.provide(point);
|
||||
}
|
||||
|
||||
public static ParticleColor fromName(String name) {
|
||||
if (name.equals(RAINBOW.name)) {
|
||||
return RAINBOW;
|
||||
}
|
||||
|
||||
Color decoded = Color.decode(name);
|
||||
return new ParticleColor(name, point -> decoded);
|
||||
}
|
||||
|
||||
private interface ColorProvider {
|
||||
Color provide(BorderPoint point);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.eternalcode.combat.border.animation.particle;
|
||||
|
||||
import eu.okaeri.configs.schema.GenericsPair;
|
||||
import eu.okaeri.configs.serdes.BidirectionalTransformer;
|
||||
import eu.okaeri.configs.serdes.SerdesContext;
|
||||
|
||||
public class ParticleColorTransformer extends BidirectionalTransformer<String, ParticleColor> {
|
||||
|
||||
@Override
|
||||
public GenericsPair<String, ParticleColor> getPair() {
|
||||
return this.genericsPair(String.class, ParticleColor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParticleColor leftToRight(String data, SerdesContext serdesContext) {
|
||||
return ParticleColor.fromName(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rightToLeft(ParticleColor data, SerdesContext serdesContext) {
|
||||
return data.getName();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.eternalcode.combat.border.animation.particle;
|
||||
|
||||
import com.eternalcode.combat.border.BorderPoint;
|
||||
import com.eternalcode.combat.border.BorderService;
|
||||
import com.eternalcode.combat.border.event.BorderHideAsyncEvent;
|
||||
import com.eternalcode.combat.border.event.BorderShowAsyncEvent;
|
||||
import com.eternalcode.commons.scheduler.Scheduler;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.PacketEventsAPI;
|
||||
import com.github.retrooper.packetevents.manager.player.PlayerManager;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerParticle;
|
||||
import java.time.Duration;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
public class ParticleController implements Listener {
|
||||
|
||||
private static final PacketEventsAPI<?> PACKET_EVENTS_API = PacketEvents.getAPI();
|
||||
private static final PlayerManager PLAYER_MANAGER = PACKET_EVENTS_API.getPlayerManager();
|
||||
|
||||
private final BorderService borderService;
|
||||
private final Supplier<ParticleSettings> particleSettings;
|
||||
private final Server server;
|
||||
private final Set<UUID> playersToUpdate = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public ParticleController(BorderService borderService, Supplier<ParticleSettings> particleSettings, Scheduler scheduler, Server server) {
|
||||
this.borderService = borderService;
|
||||
this.particleSettings = particleSettings;
|
||||
this.server = server;
|
||||
scheduler.timerAsync(() -> this.updatePlayers(), Duration.ofMillis(200), Duration.ofMillis(200));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onBorderShowAsyncEvent(BorderShowAsyncEvent event) {
|
||||
if (!particleSettings.get().enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.playersToUpdate.add(event.getPlayer().getUniqueId());
|
||||
|
||||
for (BorderPoint point : event.getPoints()) {
|
||||
this.playParticle(event.getPlayer(), point);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onBorderHideAsyncEvent(BorderHideAsyncEvent event) {
|
||||
if (!particleSettings.get().enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<BorderPoint> border = this.borderService.getActiveBorder(event.getPlayer());
|
||||
if (border.isEmpty()) {
|
||||
this.playersToUpdate.remove(event.getPlayer().getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlayers() {
|
||||
if (!particleSettings.get().enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (UUID uuid : this.playersToUpdate) {
|
||||
Player player = this.server.getPlayer(uuid);
|
||||
if (player == null || !player.isOnline()) {
|
||||
this.playersToUpdate.remove(uuid);
|
||||
continue;
|
||||
}
|
||||
|
||||
Set<BorderPoint> border = this.borderService.getActiveBorder(player);
|
||||
|
||||
if (border.isEmpty()) {
|
||||
this.playersToUpdate.remove(uuid);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (BorderPoint point : border) {
|
||||
this.playParticle(player, point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void playParticle(Player player, BorderPoint point) {
|
||||
WrapperPlayServerParticle particle = particleSettings.get().getParticle(point);
|
||||
|
||||
PLAYER_MANAGER.sendPacket(player, particle);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.eternalcode.combat.border.animation.particle;
|
||||
|
||||
import com.eternalcode.combat.border.BorderPoint;
|
||||
import com.github.retrooper.packetevents.protocol.color.AlphaColor;
|
||||
import com.github.retrooper.packetevents.protocol.particle.Particle;
|
||||
import com.github.retrooper.packetevents.protocol.particle.data.ParticleColorData;
|
||||
import com.github.retrooper.packetevents.protocol.particle.data.ParticleData;
|
||||
import com.github.retrooper.packetevents.protocol.particle.data.ParticleDustData;
|
||||
import com.github.retrooper.packetevents.protocol.particle.type.ParticleType;
|
||||
import com.github.retrooper.packetevents.protocol.particle.type.ParticleTypes;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3f;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerParticle;
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
import java.awt.Color;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class ParticleSettings extends OkaeriConfig {
|
||||
|
||||
@Comment("# Enable particle animation?")
|
||||
public boolean enabled = true;
|
||||
|
||||
@Comment("# Particle type - https://javadocs.packetevents.com/com/github/retrooper/packetevents/protocol/particle/type/ParticleTypes.html")
|
||||
public ParticleType type = ParticleTypes.DUST;
|
||||
@Comment({
|
||||
"# Particle color (used only for DUST or ENTITY_EFFECT particle type)",
|
||||
"# You can set hex color e.g. \"#ca4c45\" or use \"RAINBOW\" to generate rainbow gradient based on x and z coordinates."
|
||||
})
|
||||
public ParticleColor color = ParticleColor.RAINBOW;
|
||||
public int count = 1;
|
||||
public float scale = 1.7F;
|
||||
public float maxSpeed = 0.0F;
|
||||
public float offsetX = 0.2F;
|
||||
public float offsetY = 0.2F;
|
||||
public float offsetZ = 0.2F;
|
||||
|
||||
public WrapperPlayServerParticle getParticle(BorderPoint point) {
|
||||
return getParticle(point, type);
|
||||
}
|
||||
|
||||
private <T extends ParticleData> WrapperPlayServerParticle getParticle(BorderPoint point, ParticleType<T> type) {
|
||||
T particleData = this.createData(type, point);
|
||||
Particle<?> dust = new Particle<>(type, particleData);
|
||||
return new WrapperPlayServerParticle(
|
||||
dust,
|
||||
true,
|
||||
new Vector3d(point.x(), point.y(), point.z()),
|
||||
new Vector3f(orElse(offsetX, 0.1F), orElse(offsetY, 0.1F), orElse(offsetZ, 0.1F)),
|
||||
orElse(maxSpeed, 0.0F),
|
||||
count,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private <T> T orElse(T nullable, T or) {
|
||||
return nullable != null ? nullable : or;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends ParticleData> T createData(ParticleType<T> type, BorderPoint point) {
|
||||
if (type.equals(ParticleTypes.DUST)) {
|
||||
Color color = this.color.getColor(point);
|
||||
return (T) new ParticleDustData(orElse(scale, 1.0F), color.getRed(), color.getGreen(), color.getBlue());
|
||||
}
|
||||
|
||||
if (type.equals(ParticleTypes.ENTITY_EFFECT)) {
|
||||
Color color = this.color.getColor(point);
|
||||
AlphaColor alphaColor = new AlphaColor(color.getAlpha(), color.getRed(), color.getGreen(), color.getBlue());
|
||||
return (T) new ParticleColorData(alphaColor);
|
||||
}
|
||||
|
||||
return ParticleData.emptyData();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.eternalcode.combat.border.animation.particle;
|
||||
|
||||
import com.github.retrooper.packetevents.protocol.particle.type.ParticleType;
|
||||
import com.github.retrooper.packetevents.protocol.particle.type.ParticleTypes;
|
||||
import com.github.retrooper.packetevents.resources.ResourceLocation;
|
||||
import eu.okaeri.configs.schema.GenericsDeclaration;
|
||||
import eu.okaeri.configs.schema.GenericsPair;
|
||||
import eu.okaeri.configs.serdes.BidirectionalTransformer;
|
||||
import eu.okaeri.configs.serdes.SerdesContext;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ParticleTypeTransformer extends BidirectionalTransformer<String, ParticleType<?>> {
|
||||
|
||||
@Override
|
||||
public GenericsPair<String, ParticleType<?>> getPair() {
|
||||
return this.generics(GenericsDeclaration.of(String.class), GenericsDeclaration.of(ParticleType.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParticleType<?> leftToRight(String data, SerdesContext serdesContext) {
|
||||
if (!data.contains(":")) {
|
||||
data = ResourceLocation.normString(data);
|
||||
}
|
||||
|
||||
ParticleType<?> type = ParticleTypes.getByName(data.toLowerCase(Locale.ROOT));
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("Unknown particle type: " + data);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rightToLeft(ParticleType<?> data, SerdesContext serdesContext) {
|
||||
ResourceLocation location = data.getName();
|
||||
if (location.getNamespace().equals(ResourceLocation.VANILLA_NAMESPACE)) {
|
||||
return location.getKey().toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
return location.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.eternalcode.combat.border.event;
|
||||
|
||||
import com.eternalcode.combat.border.BorderPoint;
|
||||
import java.util.Set;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BorderHideAsyncEvent extends Event {
|
||||
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
private final Player player;
|
||||
private final Set<BorderPoint> points;
|
||||
|
||||
public BorderHideAsyncEvent(Player player, Set<BorderPoint> points) {
|
||||
super(true);
|
||||
this.player = player;
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
public Set<BorderPoint> getPoints() {
|
||||
return this.points;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.eternalcode.combat.border.event;
|
||||
|
||||
import com.eternalcode.combat.border.BorderPoint;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BorderShowAsyncEvent extends Event {
|
||||
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
private final Player player;
|
||||
private Set<BorderPoint> points;
|
||||
|
||||
public BorderShowAsyncEvent(Player player, Set<BorderPoint> points) {
|
||||
super(true);
|
||||
this.player = player;
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public Set<BorderPoint> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
public void setPoints(Set<BorderPoint> points) {
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +1,86 @@
|
||||
package com.eternalcode.combat.bridge;
|
||||
|
||||
import com.eternalcode.combat.region.lands.LandsRegionProvider;
|
||||
import com.eternalcode.combat.bridge.placeholder.FightTagPlaceholder;
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import com.eternalcode.combat.region.DefaultRegionProvider;
|
||||
import com.eternalcode.combat.region.CompositeRegionProvider;
|
||||
import com.eternalcode.combat.region.bukkit.DefaultRegionProvider;
|
||||
import com.eternalcode.combat.region.RegionProvider;
|
||||
import com.eternalcode.combat.region.WorldGuardRegionProvider;
|
||||
import com.eternalcode.combat.region.worldguard.WorldGuardRegionProvider;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class BridgeService {
|
||||
|
||||
private final PluginConfig pluginConfig;
|
||||
private final PluginConfig config;
|
||||
private final PluginManager pluginManager;
|
||||
private final Logger logger;
|
||||
private final Plugin plugin;
|
||||
private final FightManager fightManager;
|
||||
|
||||
private RegionProvider regionProvider;
|
||||
|
||||
public BridgeService(PluginConfig pluginConfig, PluginManager pluginManager, Logger logger, Plugin plugin) {
|
||||
this.pluginConfig = pluginConfig;
|
||||
public BridgeService(
|
||||
PluginConfig config,
|
||||
PluginManager pluginManager,
|
||||
Logger logger,
|
||||
Plugin plugin,
|
||||
FightManager fightManager
|
||||
) {
|
||||
this.config = config;
|
||||
this.pluginManager = pluginManager;
|
||||
this.logger = logger;
|
||||
this.plugin = plugin;
|
||||
this.fightManager = fightManager;
|
||||
}
|
||||
|
||||
public void init(FightManager fightManager, Server server) {
|
||||
this.initialize("WorldGuard",
|
||||
() -> this.regionProvider = new WorldGuardRegionProvider(this.pluginConfig.settings.blockedRegions, this.pluginConfig),
|
||||
() -> {
|
||||
this.regionProvider = new DefaultRegionProvider(this.pluginConfig.settings.blockedRegionRadius);
|
||||
public void init(Server server) {
|
||||
List<RegionProvider> providers = new ArrayList<>();
|
||||
|
||||
this.logger.warning("WorldGuard is not installed, set to default region provider.");
|
||||
});
|
||||
this.initialize(
|
||||
"Lands",
|
||||
() -> providers.add(new LandsRegionProvider(plugin)),
|
||||
() -> this.logger.warning("Lands not found; skipping LandsRegionProvider.")
|
||||
);
|
||||
|
||||
this.initialize("PlaceholderAPI",
|
||||
() -> new FightTagPlaceholder(fightManager, server, this.plugin).register(),
|
||||
() -> this.logger.warning("PlaceholderAPI is not installed, placeholders will not be registered.")
|
||||
this.initialize(
|
||||
"WorldGuard",
|
||||
() -> providers.add( new WorldGuardRegionProvider(this.config)),
|
||||
() -> this.logger.warning("WorldGuard not found; skipping WorldGuardRegionProvider.")
|
||||
);
|
||||
|
||||
if (providers.isEmpty()) {
|
||||
providers.add(new DefaultRegionProvider(this.config.regions.restrictedRegionRadius));
|
||||
this.logger.warning("No region plugin found; using DefaultRegionProvider.");
|
||||
}
|
||||
|
||||
this.regionProvider = new CompositeRegionProvider(providers);
|
||||
this.logger.info("Using composite region provider with: " +
|
||||
providers.stream()
|
||||
.map(p -> p.getClass().getSimpleName())
|
||||
.collect(Collectors.joining(", "))
|
||||
);
|
||||
|
||||
initialize(
|
||||
"PlaceholderAPI",
|
||||
() -> new FightTagPlaceholder(this.config, this.fightManager, server, this.plugin).register(),
|
||||
() -> this.logger.warning("PlaceholderAPI not found; skipping placeholders.")
|
||||
);
|
||||
}
|
||||
|
||||
private void initialize(String pluginName, BridgeInitializer initializer, Runnable failureHandler) {
|
||||
private void initialize(String pluginName, BridgeInitializer initializer, Runnable onFailure) {
|
||||
if (this.pluginManager.isPluginEnabled(pluginName)) {
|
||||
initializer.initialize();
|
||||
|
||||
this.logger.info("Successfully initialized " + pluginName + " bridge.");
|
||||
this.logger.info("Initialized " + pluginName + " bridge.");
|
||||
}
|
||||
else {
|
||||
failureHandler.run();
|
||||
onFailure.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.eternalcode.combat.bridge.placeholder;
|
||||
|
||||
import com.eternalcode.combat.config.implementation.PlaceholderSettings;
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import com.eternalcode.combat.fight.FightTag;
|
||||
import com.eternalcode.combat.util.DurationUtil;
|
||||
@@ -14,23 +16,93 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FightTagPlaceholder extends PlaceholderExpansion {
|
||||
|
||||
private static final String IDENTIFIER = "eternalcombat";
|
||||
|
||||
private final PlaceholderSettings placeholderSettings;
|
||||
private final FightManager fightManager;
|
||||
private final Server server;
|
||||
private final Plugin plugin;
|
||||
private static final String IDENTIFIER = "eternalcombat";
|
||||
|
||||
public FightTagPlaceholder(FightManager fightManager, Server server, Plugin plugin) {
|
||||
public FightTagPlaceholder(PluginConfig pluginConfig, FightManager fightManager, Server server, Plugin plugin) {
|
||||
this.placeholderSettings = pluginConfig.placeholders;
|
||||
this.fightManager = fightManager;
|
||||
this.server = server;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onRequest(OfflinePlayer player, String identifier) {
|
||||
return switch (identifier) {
|
||||
case "remaining_millis" -> this.handleRemainingMillis(player);
|
||||
case "remaining_seconds" -> this.handleRemainingSeconds(player);
|
||||
case "opponent" -> this.handleOpponent(player);
|
||||
case "opponent_health" -> this.handleOpponentHealth(player);
|
||||
case "isInCombat" -> this.handleIsInCombat(player);
|
||||
case "isInCombat_formatted" -> this.handleIsInCombatFormatted(player);
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private String handleRemainingMillis(OfflinePlayer player) {
|
||||
return this.getFightTag(player)
|
||||
.map(tag -> DurationParser.TIME_UNITS.format(tag.getRemainingDuration()))
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
private String handleRemainingSeconds(OfflinePlayer player) {
|
||||
return this.getFightTag(player)
|
||||
.map(tag -> DurationUtil.format(tag.getRemainingDuration()))
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
private String handleOpponent(OfflinePlayer player) {
|
||||
return this.getTagger(player)
|
||||
.map(Player::getName)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
private String handleOpponentHealth(OfflinePlayer player) {
|
||||
return this.getTagger(player)
|
||||
.map(tagger -> String.format("%.2f", tagger.getHealth()))
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
private String handleIsInCombat(OfflinePlayer player) {
|
||||
return String.valueOf(this.isPlayerInCombat(player));
|
||||
}
|
||||
|
||||
private String handleIsInCombatFormatted(OfflinePlayer player) {
|
||||
return this.isPlayerInCombat(player)
|
||||
? this.placeholderSettings.isInCombatFormattedTrue
|
||||
: this.placeholderSettings.isInCombatFormattedFalse;
|
||||
}
|
||||
|
||||
private boolean isPlayerInCombat(OfflinePlayer player) {
|
||||
Player onlinePlayer = player.getPlayer();
|
||||
return onlinePlayer != null && this.fightManager.isInCombat(onlinePlayer.getUniqueId());
|
||||
}
|
||||
|
||||
private @NotNull Optional<Player> getTagger(OfflinePlayer player) {
|
||||
return this.getFightTag(player)
|
||||
.map(FightTag::getTagger)
|
||||
.map(this.server::getPlayer);
|
||||
}
|
||||
|
||||
private Optional<FightTag> getFightTag(OfflinePlayer player) {
|
||||
Player onlinePlayer = player.getPlayer();
|
||||
|
||||
if (onlinePlayer == null || !this.fightManager.isInCombat(onlinePlayer.getUniqueId())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(this.fightManager.getTag(onlinePlayer.getUniqueId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRegister() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public @NotNull String getIdentifier() {
|
||||
return IDENTIFIER;
|
||||
@@ -45,54 +117,4 @@ public class FightTagPlaceholder extends PlaceholderExpansion {
|
||||
public @NotNull String getVersion() {
|
||||
return this.plugin.getDescription().getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onRequest(OfflinePlayer player, String identifier) {
|
||||
if (identifier.equals("remaining_seconds")) {
|
||||
return this.getFightTag(player)
|
||||
.map(fightTagInter -> DurationUtil.format(fightTagInter.getRemainingDuration()))
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
if (identifier.equals("remaining_millis")) {
|
||||
return this.getFightTag(player)
|
||||
.map(fightTag -> DurationParser.TIME_UNITS.format(fightTag.getRemainingDuration()))
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
if (identifier.equals("opponent")) {
|
||||
return this.getTagger(player)
|
||||
.map(tagger -> tagger.getName())
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
if (identifier.equals("opponent_health")) {
|
||||
return this.getTagger(player)
|
||||
.map(tagger -> String.format("%.2f", tagger.getHealth()))
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private @NotNull Optional<Player> getTagger(OfflinePlayer player) {
|
||||
return this.getFightTag(player)
|
||||
.map(fightTagInter -> fightTagInter.getTagger())
|
||||
.map(taggerId -> this.server.getPlayer(taggerId));
|
||||
}
|
||||
|
||||
private Optional<FightTag> getFightTag(OfflinePlayer player) {
|
||||
Player onlinePlayer = player.getPlayer();
|
||||
|
||||
if (onlinePlayer != null) {
|
||||
if (!this.fightManager.isInCombat(onlinePlayer.getUniqueId())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(this.fightManager.getTag(onlinePlayer.getUniqueId()));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
package com.eternalcode.combat.config;
|
||||
|
||||
import com.eternalcode.combat.notification.serializer.NotificationSerializer;
|
||||
import com.eternalcode.multification.bukkit.notice.resolver.sound.SoundBukkitResolver;
|
||||
import com.eternalcode.combat.border.animation.block.BlockTypeTransformer;
|
||||
import com.eternalcode.combat.border.animation.particle.ParticleColorTransformer;
|
||||
import com.eternalcode.combat.border.animation.particle.ParticleTypeTransformer;
|
||||
import com.eternalcode.multification.notice.resolver.NoticeResolverDefaults;
|
||||
import com.eternalcode.multification.notice.resolver.NoticeResolverRegistry;
|
||||
import com.eternalcode.multification.notice.resolver.sound.SoundAdventureResolver;
|
||||
import com.eternalcode.multification.okaeri.MultificationSerdesPack;
|
||||
import eu.okaeri.configs.ConfigManager;
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.serdes.OkaeriSerdesPack;
|
||||
import eu.okaeri.configs.serdes.SerdesRegistry;
|
||||
import eu.okaeri.configs.serdes.commons.SerdesCommons;
|
||||
import eu.okaeri.configs.yaml.bukkit.YamlBukkitConfigurer;
|
||||
import eu.okaeri.configs.yaml.bukkit.serdes.SerdesBukkit;
|
||||
|
||||
import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer;
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -22,13 +26,16 @@ public class ConfigService {
|
||||
public <T extends OkaeriConfig> T create(Class<T> config, File file) {
|
||||
T configFile = ConfigManager.create(config);
|
||||
|
||||
YamlBukkitConfigurer yamlBukkitConfigurer = new YamlBukkitConfigurer();
|
||||
YamlSnakeYamlConfigurer configurer = new YamlSnakeYamlConfigurer();
|
||||
NoticeResolverRegistry noticeRegistry = NoticeResolverDefaults.createRegistry()
|
||||
.registerResolver(new SoundBukkitResolver());
|
||||
.registerResolver(new SoundAdventureResolver());
|
||||
|
||||
configFile.withConfigurer(yamlBukkitConfigurer, new SerdesCommons(), new SerdesBukkit());
|
||||
configFile.withConfigurer(yamlBukkitConfigurer, new MultificationSerdesPack(noticeRegistry));
|
||||
configFile.withSerdesPack(registry -> registry.register(new NotificationSerializer()));
|
||||
configFile.withConfigurer(configurer,
|
||||
new SerdesCommons(),
|
||||
new SerdesBukkit(),
|
||||
new MultificationSerdesPack(noticeRegistry),
|
||||
new DefaultSerdesPack()
|
||||
);
|
||||
|
||||
configFile.withBindFile(file);
|
||||
configFile.withRemoveOrphans(true);
|
||||
@@ -45,4 +52,13 @@ public class ConfigService {
|
||||
config.load();
|
||||
}
|
||||
}
|
||||
|
||||
private static class DefaultSerdesPack implements OkaeriSerdesPack {
|
||||
@Override
|
||||
public void register(SerdesRegistry config) {
|
||||
config.register(new ParticleColorTransformer());
|
||||
config.register(new ParticleTypeTransformer());
|
||||
config.register(new BlockTypeTransformer());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.eternalcode.combat.config.implementation;
|
||||
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
|
||||
public class AdminSettings extends OkaeriConfig {
|
||||
@Comment({
|
||||
"# Exclude server administrators from combat tagging and being tagged.",
|
||||
"# Set to 'true' to prevent admins from being tagged or tagging others.",
|
||||
"# Set to 'false' to allow admins to participate in combat."
|
||||
})
|
||||
public boolean excludeAdminsFromCombat = false;
|
||||
|
||||
@Comment({
|
||||
"# Exclude players in creative mode from combat tagging and being tagged.",
|
||||
"# Set to 'true' to prevent creative mode players from being tagged or tagging others.",
|
||||
"# Set to 'false' to allow creative mode players to participate in combat."
|
||||
})
|
||||
public boolean excludeCreativePlayersFromCombat = false;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.eternalcode.combat.config.implementation;
|
||||
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
import java.util.List;
|
||||
import org.bukkit.Material;
|
||||
|
||||
public class BlockPlacementSettings extends OkaeriConfig {
|
||||
@Comment({
|
||||
"# Prevent players from placing blocks during combat.",
|
||||
"# Set to 'true' to block block placement while in combat."
|
||||
})
|
||||
public boolean disableBlockPlacing = true;
|
||||
|
||||
@Comment({
|
||||
"# Restrict block placement above or below a specific Y coordinate.",
|
||||
"# Available modes: ABOVE (blocks cannot be placed above the Y coordinate), BELOW (blocks cannot be placed below the Y coordinate)."
|
||||
})
|
||||
public BlockPlacingMode blockPlacementMode = BlockPlacingMode.ABOVE;
|
||||
|
||||
@Comment({
|
||||
"# Custom name for the block placement mode used in messages.",
|
||||
"# This name will be displayed in notifications related to block placement restrictions."
|
||||
})
|
||||
public String blockPlacementModeDisplayName = "above";
|
||||
|
||||
@Comment({
|
||||
"# Define the Y coordinate for block placement restrictions.",
|
||||
"# This value is relative to the selected block placement mode (ABOVE or BELOW)."
|
||||
})
|
||||
public int blockPlacementYCoordinate = 40;
|
||||
@Comment({
|
||||
"# Restrict the placement of specific blocks during combat.",
|
||||
"# Add blocks to this list to prevent their placement. Leave the list empty to block all blocks.",
|
||||
"# Note: This feature requires 'disableBlockPlacing' to be enabled."
|
||||
})
|
||||
public List<Material> restrictedBlockTypes = List.of();
|
||||
|
||||
public enum BlockPlacingMode {
|
||||
ABOVE,
|
||||
BELOW
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.eternalcode.combat.config.implementation;
|
||||
|
||||
import com.eternalcode.combat.WhitelistBlacklistMode;
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
import java.util.List;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
|
||||
|
||||
public class CombatSettings extends OkaeriConfig {
|
||||
@Comment({
|
||||
"# Automatically release the attacker from combat when the victim dies.",
|
||||
"# Set to 'true' to enable this feature, or 'false' to keep the attacker in combat."
|
||||
})
|
||||
public boolean releaseAttackerOnVictimDeath = true;
|
||||
|
||||
@Comment({
|
||||
"# Disable the use of elytra during combat.",
|
||||
"# Set to 'true' to prevent players from using elytra while in combat."
|
||||
})
|
||||
public boolean disableElytraUsage = true;
|
||||
|
||||
@Comment({
|
||||
"# Disable the use of elytra when a player takes damage.",
|
||||
"# Set to 'true' to disable elytra usage upon taking damage, even when the player is mid-air."
|
||||
})
|
||||
public boolean disableElytraOnDamage = true;
|
||||
|
||||
@Comment({
|
||||
"# Prevent players from flying during combat.",
|
||||
"# Flying players will fall to the ground if this is enabled."
|
||||
})
|
||||
public boolean disableFlying = true;
|
||||
|
||||
@Comment({
|
||||
"# Forcefully unequip elytra when a player enters combat.",
|
||||
"# This prevents players from abusing glide by jumping before being tagged.",
|
||||
"# The elytra will be moved to the player's inventory.",
|
||||
"# Recommended: true"
|
||||
})
|
||||
public boolean unequipElytraOnCombat = true;
|
||||
|
||||
@Comment({
|
||||
"# Prevent players from boosting themselves while flying with fireworks",
|
||||
"# This setting blocks usage of fireworks to boost elytra flight during combat"
|
||||
})
|
||||
public boolean disableFireworks = true;
|
||||
|
||||
@Comment({
|
||||
"# Enable or disable combat logging for damage caused by non-player entities.",
|
||||
"# Set to 'true' to log damage from non-player sources, or 'false' to disable this feature."
|
||||
})
|
||||
public boolean enableDamageCauseLogging = false;
|
||||
|
||||
@Comment({
|
||||
"# Set the mode for logging damage causes.",
|
||||
"# Available modes: WHITELIST (only listed causes are logged), BLACKLIST (all causes except listed ones are logged)."
|
||||
})
|
||||
public WhitelistBlacklistMode damageCauseRestrictionMode = WhitelistBlacklistMode.WHITELIST;
|
||||
|
||||
@Comment({
|
||||
"# List of damage causes to be logged based on the selected mode.",
|
||||
"# In WHITELIST mode, only these causes are logged. In BLACKLIST mode, all causes except these are logged.",
|
||||
"# For a full list of damage causes, visit: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html"
|
||||
})
|
||||
public List<DamageCause> loggedDamageCauses = List.of(
|
||||
DamageCause.LAVA,
|
||||
DamageCause.CONTACT,
|
||||
DamageCause.FIRE,
|
||||
DamageCause.FIRE_TICK
|
||||
);
|
||||
|
||||
@Comment({
|
||||
"# List of projectile types that do not trigger combat tagging.",
|
||||
"# Players hit by these projectiles will not be tagged as in combat.",
|
||||
"# For a full list of entity types, visit: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html"
|
||||
})
|
||||
public List<EntityType> ignoredProjectileTypes = List.of(
|
||||
EntityType.ENDER_PEARL,
|
||||
EntityType.EGG
|
||||
);
|
||||
|
||||
@Comment({
|
||||
"# The event priority at which quit punishments should be handled.",
|
||||
"# This determines when the plugin processes combat log punishment during PlayerQuitEvent.",
|
||||
"# Options: LOWEST, LOW, NORMAL, HIGH, HIGHEST, MONITOR",
|
||||
"# Tip: Set to LOWEST or LOW if you want quit punishments to happen before most other plugins.",
|
||||
"# Default: NORMAL"
|
||||
})
|
||||
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.",
|
||||
"# If one of the listed phrases is found in the kick reason (case-insensitive),",
|
||||
"# the player will NOT be punished.",
|
||||
"# Example: 'Timed out', 'Kicked for inactivity', etc.",
|
||||
"# To always punish players on kick, set: whitelistedKickReasons: []"
|
||||
})
|
||||
public List<String> whitelistedKickReasons = List.of("Kicked for inactivity", "Timed out", "Server is restarting");
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.eternalcode.combat.config.implementation;
|
||||
|
||||
import com.eternalcode.combat.WhitelistBlacklistMode;
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
import java.util.List;
|
||||
|
||||
public class CommandSettings extends OkaeriConfig {
|
||||
@Comment({
|
||||
"# Set the mode for command restrictions during combat.",
|
||||
"# Available modes: WHITELIST (only listed commands are allowed), BLACKLIST (listed commands are blocked)."
|
||||
})
|
||||
public WhitelistBlacklistMode commandRestrictionMode = WhitelistBlacklistMode.BLACKLIST;
|
||||
|
||||
@Comment({
|
||||
"# List of commands affected by the command restriction mode.",
|
||||
"# In BLACKLIST mode, these commands are blocked. In WHITELIST mode, only these commands are allowed."
|
||||
})
|
||||
public List<String> restrictedCommands = List.of(
|
||||
"gamemode",
|
||||
"spawn",
|
||||
"tp",
|
||||
"tpa",
|
||||
"tpaccept"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.eternalcode.combat.config.implementation;
|
||||
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
|
||||
public class CrystalPvpSettings extends OkaeriConfig {
|
||||
|
||||
@Comment({"# Should player be tagged when damaged from crystal explosion set by other player"})
|
||||
public boolean tagFromCrystals = true;
|
||||
|
||||
|
||||
@Comment({"#Should player be tagged when damaged from respawn anchor explosion set by other player"})
|
||||
public boolean tagFromRespawnAnchor = true;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.eternalcode.combat.config.implementation;
|
||||
|
||||
public enum InventoryAccessMode {
|
||||
/**
|
||||
* Allow access to all inventories during combat
|
||||
*/
|
||||
ALLOW_ALL,
|
||||
|
||||
/**
|
||||
* Block access to all inventories during combat
|
||||
*/
|
||||
BLOCK_ALL,
|
||||
|
||||
/**
|
||||
* Only block inventories in the restricted list
|
||||
*/
|
||||
BLACKLIST,
|
||||
|
||||
/**
|
||||
* Only allow inventories in the allowed list
|
||||
*/
|
||||
WHITELIST;
|
||||
|
||||
/**
|
||||
* Determines if an inventory type should be blocked based on the mode and whether it's in the list
|
||||
*
|
||||
* @param isInList whether the inventory type is in the configured list
|
||||
* @return true if the inventory should be blocked
|
||||
*/
|
||||
public boolean shouldBlock(boolean isInList) {
|
||||
return switch (this) {
|
||||
case ALLOW_ALL -> false;
|
||||
case BLOCK_ALL -> true;
|
||||
case BLACKLIST -> isInList;
|
||||
case WHITELIST -> !isInList;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.eternalcode.combat.config.implementation;
|
||||
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class InventorySettings extends OkaeriConfig {
|
||||
|
||||
@Comment({
|
||||
"# Inventory access restriction mode during combat",
|
||||
"# ALLOW_ALL - Players can open all inventories during combat",
|
||||
"# BLOCK_ALL - Players cannot open any inventories during combat",
|
||||
"# BLACKLIST - Block only inventories listed in restrictedInventoryTypes",
|
||||
"# WHITELIST - Allow only inventories listed in restrictedInventoryTypes"
|
||||
})
|
||||
public InventoryAccessMode inventoryAccessMode = InventoryAccessMode.ALLOW_ALL;
|
||||
|
||||
@Comment({
|
||||
"# List of inventory types to restrict or allow (depending on the mode)",
|
||||
"# In BLACKLIST mode: these inventories will be blocked",
|
||||
"# In WHITELIST mode: only these inventories will be allowed",
|
||||
"# Available types: CHEST, ENDER_CHEST, DISPENSER, DROPPER, FURNACE, WORKBENCH,",
|
||||
"# CRAFTING, ENCHANTING, BREWING, MERCHANT, BEACON, ANVIL, SMITHING, HOPPER,",
|
||||
"# SHULKER_BOX, BARREL, BLAST_FURNACE, LECTERN, SMOKER, LOOM, CARTOGRAPHY,",
|
||||
"# GRINDSTONE, STONECUTTER, COMPOSTER, CHISELED_BOOKSHELF, JUKEBOX, SMITHING_NEW,",
|
||||
"# CRAFTER, DECORATED_POT",
|
||||
"# Note: PLAYER and CREATIVE inventories are never blocked to prevent game-breaking issues",
|
||||
"# Full list: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/inventory/InventoryType.html"
|
||||
})
|
||||
public List<InventoryType> restrictedInventoryTypes = List.of(
|
||||
InventoryType.CHEST,
|
||||
InventoryType.ENDER_CHEST,
|
||||
InventoryType.BARREL,
|
||||
InventoryType.SHULKER_BOX
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package com.eternalcode.combat.config.implementation;
|
||||
|
||||
import com.eternalcode.multification.notice.Notice;
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
|
||||
public class MessagesSettings extends OkaeriConfig {
|
||||
|
||||
@Comment({
|
||||
"# Customize messages related to admin commands and notifications.",
|
||||
"# These messages are displayed to server administrators."
|
||||
})
|
||||
public AdminMessages admin = new AdminMessages();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Configure the combat log notification displayed to players.",
|
||||
"# You can use the {TIME} variable to display the remaining combat time.",
|
||||
"# Placeholder {OPPONENT} will be replaced with player name or 'unknownPlayerPlaceholder' variable",
|
||||
" ",
|
||||
})
|
||||
public Notice combatNotification = Notice.builder()
|
||||
.actionBar("Combat ends in: <red>{TIME}</red>")
|
||||
.build();
|
||||
|
||||
@Comment({
|
||||
"# Would you like to display milliseconds instead of seconds in combat notification "
|
||||
})
|
||||
public boolean withoutMillis = true;
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# When player is not known this text will be used as {OPPONENT} in 'combatNotification'"
|
||||
})
|
||||
public String unknownPlayerPlaceholder = "Unknown";
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when a player lacks permission to execute a command.",
|
||||
"# The {PERMISSION} placeholder is replaced with the required permission."
|
||||
})
|
||||
public Notice noPermission = Notice.chat(
|
||||
"<gradient:red:dark_red>You don't have permission <dark_gray>(<gray>{PERMISSION}</gray>)</dark_gray> to perform this command!</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when a specified player is not found.",
|
||||
"# This message is shown when a command targets a player who is not online or does not exist."
|
||||
})
|
||||
public Notice playerNotFound = Notice
|
||||
.chat("<gradient:#ff0000:#ff6b6b>The specified player could not be found!</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when a player enters combat.",
|
||||
"# 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!</white> <u>Do not leave the server!</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when a player leaves combat.",
|
||||
"# This message informs the player that they can safely leave the server."
|
||||
})
|
||||
public Notice playerUntagged = Notice.chat(
|
||||
"<gradient:#00ff00:#00b300>✌ <white>Combat ended!</white> You can now safely leave!</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Broadcast message displayed when a player logs out during combat.",
|
||||
"# The {PLAYER} placeholder is replaced with the player's name."
|
||||
})
|
||||
public Notice playerLoggedOutDuringCombat = Notice
|
||||
.chat("<gradient:red:dark_red>⚠ <white>{PLAYER}</white> logged off during combat!</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when a player attempts to use a disabled command during combat.",
|
||||
"# This message informs the player that the command is prohibited while in combat."
|
||||
})
|
||||
public Notice commandDisabledDuringCombat = Notice.chat(
|
||||
"<gradient:red:yellow>⚠ <white>Command blocked!</white> Cannot use this during combat!</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when a player attempts to use an elytra during combat.",
|
||||
"# This includes gliding, equipping, or having it forcefully removed.",
|
||||
"# Informs the player that elytra usage is disabled in combat."
|
||||
})
|
||||
public Notice elytraDisabledDuringCombat = Notice.chat(
|
||||
"<gradient:red:yellow>⚠ <white>Elytra disabled!</white> Cannot use elytra during combat!</gradient>"
|
||||
);
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when a player uses a command with incorrect arguments.",
|
||||
"# The {USAGE} placeholder is replaced with the correct command syntax."
|
||||
})
|
||||
public Notice invalidCommandUsage = Notice.chat("<gray>Usage: <gradient:gold:yellow>{USAGE}</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when a player attempts to open their inventory during combat.",
|
||||
"# This message informs the player that inventory access is blocked while in combat."
|
||||
})
|
||||
public Notice inventoryBlockedDuringCombat = Notice.chat(
|
||||
"<gradient:red:dark_red>⚠ <white>Inventory access</white> is restricted during combat!</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when a player attempts to place a block during combat.",
|
||||
"# The {MODE} placeholder is replaced with the block placement mode (ABOVE/BELOW).",
|
||||
"# The {Y} placeholder is replaced with the Y coordinate set in the config."
|
||||
})
|
||||
public Notice blockPlacingBlockedDuringCombat = Notice
|
||||
.chat("<gradient:red:yellow>⚠ Block placement <white>{MODE} Y:{Y}</white> is restricted!</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message sent to the player using fireworks when fireworks during elytra flight are disabled"
|
||||
})
|
||||
public Notice fireworksDisabled = Notice
|
||||
.actionbar("<gradient:red:yellow>⚠ You can't use fireworks during combat ⚠</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when a player attempts to enter a restricted region during combat.",
|
||||
"# This message informs the player that they cannot enter the region while in combat."
|
||||
})
|
||||
public Notice cantEnterOnRegion = Notice.chat(
|
||||
"<gradient:red:dark_red>⚠ Restricted area!</gradient> <white>Cannot enter during combat!</white>");
|
||||
|
||||
public static class AdminMessages extends OkaeriConfig {
|
||||
@Comment({
|
||||
"# Message displayed when the console attempts to use a player-only command.",
|
||||
"# This message informs the console that the command is not available for non-players."
|
||||
})
|
||||
public Notice onlyForPlayers = Notice.chat("<gradient:red:dark_red>❌ This command is player-only!</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed to an admin when they tag a player.",
|
||||
"# The {PLAYER} placeholder is replaced with the tagged player's name."
|
||||
})
|
||||
public Notice adminTagPlayer = Notice
|
||||
.chat("<gradient:#00b3ff:#0066ff>⚔ <gray>Tagged player:</gray> <white>{PLAYER}</white></gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when an admin tags multiple players.",
|
||||
"# The {FIRST_PLAYER} and {SECOND_PLAYER} placeholders are replaced with the players' names."
|
||||
})
|
||||
public Notice adminTagMultiplePlayers = Notice.chat(
|
||||
"<gradient:#00b3ff:#0066ff>⚔ <gray>Tagged:</gray> <white>{FIRST_PLAYER}</white> <gray>and</gray> <white>{SECOND_PLAYER}</white></gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed to an admin when they remove a player from combat.",
|
||||
"# The {PLAYER} placeholder is replaced with the player's name."
|
||||
})
|
||||
public Notice adminUntagPlayer = Notice.chat(
|
||||
"<gradient:#00ff88:#00b300>✌ <gray>Removed</gray> <white>{PLAYER}</white> <gray>from combat</gray></gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed to an admin when they remove a player from combat.",
|
||||
"# The {PLAYER} placeholder is replaced with the player's name."
|
||||
})
|
||||
public Notice adminUntagAll = Notice.chat(
|
||||
"<gradient:#00ff88:#00b300>✌ <gray>Removed</gray> <white>{COUNT}</white> <gray> players from combat</gray></gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when an admin attempts to untag a player who is not in combat.",
|
||||
"# This message informs the admin that the player is not currently in combat."
|
||||
})
|
||||
public Notice adminPlayerNotInCombat = Notice
|
||||
.chat("<gradient:red:dark_red>❌ <white>{PLAYER}</white> is not in combat!</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when a player is in combat.",
|
||||
"# The {PLAYER} placeholder is replaced with the player's name."
|
||||
})
|
||||
public Notice playerInCombat = Notice
|
||||
.chat("<gradient:#ff6666:#ff0000>⚔ <white>{PLAYER}</white> <gray>is in combat!</gray></gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when a player is not in combat.",
|
||||
"# The {PLAYER} placeholder is replaced with the player's name."
|
||||
})
|
||||
public Notice playerNotInCombat = Notice
|
||||
.chat("<gradient:#00ff00:#00b300>✌ <white>{PLAYER}</white> <gray>is safe</gray></gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when an admin attempts to tag themselves.",
|
||||
"# This message informs the admin that they cannot tag themselves."
|
||||
})
|
||||
public Notice adminCannotTagSelf = Notice.chat("<gradient:red:dark_red>❌ Cannot tag yourself!</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when an admin disables combat tagging for themselves.",
|
||||
"# The {TIME} placeholder is replaced with the remaining fight time."
|
||||
})
|
||||
public Notice adminTagOutSelf = Notice.chat(
|
||||
"<gradient:#00b3ff:#0066ff>🛡 <gray>Self-protection active for</gray> <white>{TIME}</white></gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when an admin disables combat tagging for another player.",
|
||||
"# The {PLAYER} placeholder is replaced with the player's name.",
|
||||
"# The {TIME} placeholder is replaced with the remaining fight time."
|
||||
})
|
||||
public Notice adminTagOut = Notice.chat(
|
||||
"<gradient:#00b3ff:#0066ff>🛡 <gray>Protected</gray> <white>{PLAYER}</white> <gray>for</gray> <white>{TIME}</white></gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed to a player when their combat tagging is disabled.",
|
||||
"# The {TIME} placeholder is replaced with the remaining fight time."
|
||||
})
|
||||
public Notice playerTagOut = Notice.chat(
|
||||
"<gradient:#00ff88:#00b300>🛡 <gray>Protection active for</gray> <white>{TIME}</white></gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when an admin reenables combat tagging for a player.",
|
||||
"# The {PLAYER} placeholder is replaced with the player's name."
|
||||
})
|
||||
public Notice adminTagOutOff = Notice.chat(
|
||||
"<gradient:#00ff88:#00b300>✌ <gray>Re-enabled tagging for</gray> <white>{PLAYER}</white></gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed to a player when their combat tagging is reenabled.",
|
||||
"# This message informs the player that they can now be tagged again."
|
||||
})
|
||||
public Notice playerTagOutOff = Notice.chat("");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when an admin attempts to tag a player who has tag-out enabled.",
|
||||
"# This message informs the admin that the player cannot be tagged at this time."
|
||||
})
|
||||
public Notice adminTagOutCanceled = Notice
|
||||
.chat("<gradient:red:dark_red>❌ Player has tag-out protection!</gradient>");
|
||||
|
||||
@Comment({
|
||||
"# Message displayed when an admin checks combat statistics.",
|
||||
"# The {COUNT} placeholder is replaced with the number of players currently in combat."
|
||||
})
|
||||
public Notice combatStats = Notice
|
||||
.chat("<gradient:#ff6666:#ff0000>⚔ <gray>Players in combat:</gray> <white>{COUNT}</white></gradient>");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.eternalcode.combat.config.implementation;
|
||||
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
|
||||
public class PlaceholderSettings extends OkaeriConfig {
|
||||
|
||||
@Comment("Text returned by %eternalcombat_isInCombat_formatted% placeholder when the player is in combat")
|
||||
public String isInCombatFormattedTrue = "In Combat";
|
||||
|
||||
@Comment("Text returned by %eternalcombat_isInCombat_formatted% placeholder when the player is out of combat")
|
||||
public String isInCombatFormattedFalse = "Not In Combat";
|
||||
|
||||
}
|
||||
@@ -1,263 +1,172 @@
|
||||
package com.eternalcode.combat.config.implementation;
|
||||
|
||||
import com.eternalcode.combat.WhitelistBlacklistMode;
|
||||
import com.eternalcode.combat.border.BorderSettings;
|
||||
import com.eternalcode.combat.fight.death.DeathSettings;
|
||||
import com.eternalcode.combat.fight.drop.DropSettings;
|
||||
import com.eternalcode.combat.fight.effect.FightEffectSettings;
|
||||
import com.eternalcode.combat.fight.pearl.FightPearlSettings;
|
||||
import com.eternalcode.multification.bukkit.notice.BukkitNotice;
|
||||
import com.eternalcode.multification.notice.Notice;
|
||||
import com.eternalcode.combat.fight.knockback.KnockbackSettings;
|
||||
import com.eternalcode.combat.fight.pearl.PearlSettings;
|
||||
import com.eternalcode.combat.fight.trident.TridentSettings;
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.SoundCategory;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
|
||||
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?")
|
||||
@Comment(" ")
|
||||
@Comment("# _____ _ EternalCombat _ _____ _ _ ")
|
||||
@Comment("# | ___| | o()xxx[{:::::::::> | / __ \\ | | | | ")
|
||||
@Comment("# | |__ | |_ ___ _ __ _ __ __ _| | / \\/ ___ _ __ ___ | |__ __ _| |_ ")
|
||||
@Comment("# | __|| __/ _ \\ '__| '_ \\ / _` | | | / _ \\| '_ ` _ \\| '_ \\ / _` | __| ")
|
||||
@Comment("# | |___| || __/ | | | | | (_| | | \\__/\\ (_) | | | | | | |_) | (_| | |_ ")
|
||||
@Comment("# \\____/ \\__\\___|_| |_| |_|\\__,_|_|\\____/\\___/|_| |_| |_|_.__/ \\__,_|\\__| ")
|
||||
@Comment(" ")
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Settings for the plugin.",
|
||||
"# Modify these to customize the plugin's behavior."
|
||||
})
|
||||
public Settings settings = new Settings();
|
||||
|
||||
@Comment({ " ", "# Ender pearl settings" })
|
||||
public FightPearlSettings pearl = new FightPearlSettings();
|
||||
@Comment({
|
||||
" ",
|
||||
"# Settings related to Ender Pearls.",
|
||||
"# Configure cooldowns, restrictions, and other behaviors for Ender Pearls during combat."
|
||||
})
|
||||
public PearlSettings pearl = new PearlSettings();
|
||||
|
||||
@Comment({ " ", "# Custom effects settings" })
|
||||
@Comment({
|
||||
" ",
|
||||
"# Settings related to Trident",
|
||||
"# Configure cooldowns, restrictions, and other behaviors for Trident during combat."
|
||||
})
|
||||
public TridentSettings trident = new TridentSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Custom effects applied during combat.",
|
||||
"# Configure effects like blindness, slowness, or other debuffs that are applied to players in combat."
|
||||
})
|
||||
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();
|
||||
@Comment({
|
||||
" ",
|
||||
"# This section contains effects displayed on death of the player"
|
||||
})
|
||||
public DeathSettings death = new DeathSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Customize how items are dropped when a player dies during combat.",
|
||||
"# Configure whether items drop, how they drop, and any additional rules for item drops."
|
||||
})
|
||||
public DropSettings drop = new DropSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Settings related to knockback during combat.",
|
||||
"# Configure knockback settings and behaviors for players in combat."
|
||||
})
|
||||
public KnockbackSettings knockback = new KnockbackSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Border Settings",
|
||||
"# Configure the border that appears during combat.",
|
||||
})
|
||||
public BorderSettings border = new BorderSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Settings related to block placement during combat.",
|
||||
"# Configure restrictions and behaviors for block placement while players are in combat."
|
||||
})
|
||||
public BlockPlacementSettings blockPlacement = new BlockPlacementSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Settings related to crystal PvP.",
|
||||
"# Configure behaviors, restrictions, and features specific to crystal PvP combat."
|
||||
})
|
||||
public CrystalPvpSettings crystalPvp = new CrystalPvpSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Settings related to commands during combat.",
|
||||
"# Configure command restrictions and behaviors for players in combat."
|
||||
})
|
||||
public CommandSettings commands = new CommandSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Settings related to the plugin's admin commands and features.",
|
||||
"# Configure admin-specific settings and behaviors for the plugin."
|
||||
})
|
||||
public AdminSettings admin = new AdminSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Settings related to regions.",
|
||||
"# Configure region-specific settings and behaviors for combat."
|
||||
})
|
||||
public RegionSettings regions = new RegionSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Settings related to combat and player tagging.",
|
||||
"# Configure combat rules, and behaviors for player tagging."
|
||||
})
|
||||
public CombatSettings combat = new CombatSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Settings related to inventory access during combat.",
|
||||
"# Configure which inventories players can or cannot access while in combat."
|
||||
})
|
||||
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.",
|
||||
"# Configure default values returned by placeholders"
|
||||
})
|
||||
public PlaceholderSettings placeholders = new PlaceholderSettings();
|
||||
|
||||
@Comment({
|
||||
" ",
|
||||
"# Customize the messages displayed by the plugin.",
|
||||
"# Modify these to change the text and formatting of notifications and alerts."
|
||||
})
|
||||
public MessagesSettings messagesSettings = new MessagesSettings();
|
||||
|
||||
public static class Settings extends OkaeriConfig {
|
||||
@Comment({
|
||||
"# Notify players about new plugin updates when they join the server.",
|
||||
"# Set to 'true' to enable update notifications, or 'false' to disable them."
|
||||
})
|
||||
public boolean notifyAboutUpdates = true;
|
||||
|
||||
@Comment("# Whether the player should receive information about new plugin updates upon joining the server")
|
||||
public boolean shouldReceivePluginUpdates = true;
|
||||
@Comment({
|
||||
"# The duration (in seconds) that a player remains in combat after being attacked.",
|
||||
"# After this time expires, the player will no longer be considered in combat."
|
||||
})
|
||||
public Duration combatTimerDuration = Duration.ofSeconds(20);
|
||||
|
||||
@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(
|
||||
@Comment({
|
||||
"# List of worlds where combat logging is disabled.",
|
||||
"# Players in these worlds will not be tagged or affected by combat rules."
|
||||
})
|
||||
public List<String> ignoredWorlds = 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",
|
||||
"# Values can be decimal. Do NOT use negative values.",
|
||||
"# Setting it around 1 knocks the player around 2-4 blocks away."
|
||||
})
|
||||
public double blockedRegionKnockMultiplier = 1;
|
||||
|
||||
@Comment({ "# Should the player be prevented from entering regions with WorldGuard flag PVP set to DENY " })
|
||||
public boolean shouldPreventPvpRegions = true;
|
||||
|
||||
@Comment("# Set the radius of the blocked region if you aren't using WorldGuard basen on default spawn region!")
|
||||
public int blockedRegionRadius = 10;
|
||||
|
||||
@Comment("# Release attacker after victim dies?")
|
||||
public boolean shouldReleaseAttacker = true;
|
||||
|
||||
@Comment({ "# If you want to exclude admins from combat, ",
|
||||
"# Setting this to true - admins cannot be tagged and will not tag other players on hit",
|
||||
"# Setting this to false - admins can be tagged and can tag other players on hit"
|
||||
})
|
||||
public boolean excludeOpFromCombat = false;
|
||||
|
||||
@Comment({"# If you want to exclude players in creative mode from combat, ",
|
||||
"# Setting this to true - players in creative mode cannot be tagged and will not tag other players on hit",
|
||||
"# Setting this to false - players in creative mode can be tag others"
|
||||
})
|
||||
public boolean excludeCreativeFromCombat = false;
|
||||
|
||||
@Comment("# Command blocking mode, available modes: WHITELIST, BLACKLIST")
|
||||
public WhitelistBlacklistMode commandBlockingMode = WhitelistBlacklistMode.BLACKLIST;
|
||||
|
||||
@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("# Block flying? (flying players will fall to the ground)")
|
||||
public boolean shouldPreventFlying = true;
|
||||
|
||||
@Comment("# Disable the use of elytra on damage?")
|
||||
public boolean shouldElytraDisableOnDamage = true;
|
||||
|
||||
@Comment("# Block the opening of inventory?")
|
||||
public boolean shouldPreventInventoryOpening = true;
|
||||
|
||||
@Comment("# Whether to block the placement of blocks?")
|
||||
public boolean shouldPreventBlockPlacing = true;
|
||||
|
||||
@Comment({ "# Block the placement of blocks above or below a certain Y coordinate",
|
||||
"# Select the mode for block placing, available modes: ABOVE, BELOW"
|
||||
})
|
||||
public BlockPlacingMode blockPlacingMode = BlockPlacingMode.ABOVE;
|
||||
|
||||
@Comment("# Block placing mode custom name used if messages")
|
||||
public String blockPlacingModeName = "above";
|
||||
|
||||
@Comment("# Set the Y coordinate for block placing relative to mode selected above")
|
||||
public int blockPlacingYCoordinate = 40;
|
||||
|
||||
public enum BlockPlacingMode {
|
||||
ABOVE,
|
||||
BELOW
|
||||
}
|
||||
|
||||
@Comment({
|
||||
"# Disable placing specific blocks?",
|
||||
"# 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 Notice combatNotification = BukkitNotice.builder()
|
||||
.actionBar("&dCombat ends in: &f{TIME}")
|
||||
.sound(Sound.ENTITY_EXPERIENCE_ORB_PICKUP, SoundCategory.PLAYERS, 2.0F, 1.0F)
|
||||
.build();
|
||||
|
||||
@Comment("# Message sent when the player does not have permission to perform a command")
|
||||
public Notice noPermission = Notice.chat("&cYou don't have permission \"{PERMISSION}\" to perform this command!");
|
||||
|
||||
@Comment("# Message sent when the specified player could not be found")
|
||||
public Notice playerNotFound = Notice.chat("&cThe specified player could not be found!");
|
||||
|
||||
@Comment("# Message sent when the player enters combat")
|
||||
public Notice playerTagged = Notice.chat("&cYou are in combat, do not leave the server!");
|
||||
|
||||
@Comment("# Message sent when the player leaves combat")
|
||||
public Notice playerUntagged = Notice.chat("&aYou are no longer in combat! You can safely leave the server.");
|
||||
|
||||
@Comment("# This is broadcast when the player is in combat and logs out")
|
||||
public Notice playerLoggedOutDuringCombat = Notice.chat("&c{PLAYER} logged off during the fight!");
|
||||
|
||||
@Comment({
|
||||
"# Message sent when the player is in combat and tries to use a disabled command",
|
||||
"# you can configure the list of disabled commands in the blockedCommands section of the config.yml file"
|
||||
})
|
||||
public Notice commandDisabledDuringCombat = Notice.chat("&cUsing this command during combat is prohibited!");
|
||||
|
||||
@Comment("# Message sent when player tries to use a command with invalid arguments")
|
||||
public Notice invalidCommandUsage = Notice.chat("&7Correct usage: &e{USAGE}");
|
||||
|
||||
@Comment("# Message sent when player tries to open inventory, but the inventory open is blocked")
|
||||
public Notice inventoryBlockedDuringCombat = Notice.chat("&cYou cannot open this inventory during combat!");
|
||||
|
||||
@Comment({ "# Message sent when player tries to place a block, but the block place is blocked",
|
||||
"# Placeholder {Y} is replaced with the Y coordinate set in the config",
|
||||
"# Placeholder {MODE} is replaced with the mode set in the config" })
|
||||
public Notice blockPlacingBlockedDuringCombat = Notice.chat("&cYou cannot place {MODE} {Y} coordinate during combat!");
|
||||
|
||||
@Comment("# Message sent when player tries to enter a region")
|
||||
public Notice cantEnterOnRegion = Notice.chat("&cYou can't enter this region during combat!");
|
||||
|
||||
public static class AdminMessages extends OkaeriConfig {
|
||||
@Comment("# Message sent when console tries to use a command that is only for players")
|
||||
public Notice onlyForPlayers = Notice.chat("&cThis command is only available to players!");
|
||||
|
||||
@Comment("# Message sent to admin when they tag a player")
|
||||
public Notice adminTagPlayer = Notice.chat("&7You have tagged &e{PLAYER}");
|
||||
|
||||
@Comment("# Message sent when a player is tagged by an admin")
|
||||
public Notice adminTagMultiplePlayers = Notice.chat("&7You have tagged &e{FIRST_PLAYER}&7 and &e{SECOND_PLAYER}&7.");
|
||||
|
||||
@Comment("# Message sent to admin when they remove a player from combat")
|
||||
public Notice adminUntagPlayer = Notice.chat("&7You have removed &e{PLAYER}&7 from the fight.");
|
||||
|
||||
@Comment("# Message sent when the player is not in combat")
|
||||
public Notice adminPlayerNotInCombat = Notice.chat("&cThis player is not in combat!");
|
||||
|
||||
@Comment("# Message sent when the player is in combat")
|
||||
public Notice playerInCombat = Notice.chat("&c{PLAYER} is currently in combat!");
|
||||
|
||||
@Comment("# Message sent when a player is not in combat")
|
||||
public Notice playerNotInCombat = Notice.chat("&a{PLAYER} is not currently in combat.");
|
||||
|
||||
@Comment("# Message sent when an admin tries to tag themselves")
|
||||
public Notice adminCannotTagSelf = Notice.chat("&cYou cannot tag yourself!");
|
||||
|
||||
@Comment("# Message sent when an admin disables the ability to get tagged for some time")
|
||||
public Notice adminTagOutSelf = Notice.chat("&7Successfully disabled tag for Yourself! You will be taggable after &e{TIME} ");
|
||||
|
||||
@Comment("# Message sent when an admin disables the ability to get tagged for some time for other player")
|
||||
public Notice adminTagOut = Notice.chat("&7Successfully disabled tag for &e{PLAYER}! They will be taggable after &e{TIME} ");
|
||||
|
||||
@Comment("# Message sent to the player whom the ability to get tagged for some time has been disabled")
|
||||
public Notice playerTagOut = Notice.chat("&7You will be taggable in &e{TIME} !");
|
||||
|
||||
@Comment("# Message sent when an admin reenables the ability to get tagged for the player")
|
||||
public Notice adminTagOutOff = Notice.chat("&7Successfully enabled tag for &e{PLAYER}!");
|
||||
|
||||
@Comment("# Message sent to the player whom the ability to get tagged has been reenabled")
|
||||
public Notice playerTagOutOff = Notice.chat("&7You are now taggable!");
|
||||
|
||||
@Comment("# Message sent when player cannot be tagged because they have enabled tag-out")
|
||||
public Notice adminTagOutCanceled = Notice.chat("&cCannot tag this player due to tag-out!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.eternalcode.combat.config.implementation;
|
||||
|
||||
import eu.okaeri.configs.OkaeriConfig;
|
||||
import eu.okaeri.configs.annotation.Comment;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class RegionSettings extends OkaeriConfig {
|
||||
@Comment({
|
||||
"# List of regions where combat is restricted.",
|
||||
"# Players in these regions will not be able to engage in combat."
|
||||
})
|
||||
public List<String> blockedRegions = Collections.singletonList("your_region");
|
||||
|
||||
@Comment({
|
||||
"# 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;
|
||||
|
||||
@Comment({
|
||||
"# Define the radius of restricted regions if WorldGuard is not used.",
|
||||
"# This setting is based on the default spawn region."
|
||||
})
|
||||
public int restrictedRegionRadius = 10;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.eternalcode.combat.crystalpvp;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.metadata.MetadataValueAdapter;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class CrystalMetadata extends MetadataValueAdapter {
|
||||
|
||||
private UUID damager;
|
||||
|
||||
protected CrystalMetadata(@NotNull Plugin owningPlugin, UUID damager) {
|
||||
super(owningPlugin);
|
||||
this.damager = damager;
|
||||
}
|
||||
|
||||
Optional<UUID> getDamager() {
|
||||
return Optional.ofNullable(this.damager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable UUID value() {
|
||||
return this.damager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
this.damager = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.eternalcode.combat.crystalpvp;
|
||||
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import com.eternalcode.combat.fight.event.CauseOfTag;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.EnderCrystal;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.entity.EntityDamageByBlockEvent;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
|
||||
public class CrystalPvpConstants {
|
||||
|
||||
private CrystalPvpConstants() {
|
||||
}
|
||||
|
||||
public static final String CRYSTAL_METADATA = "eternalcombat:crystal";
|
||||
public static final String ANCHOR_METADATA = "eternalcombat:anchor";
|
||||
|
||||
public static Optional<UUID> getDamagerUniqueIdFromEndCrystal(EntityDamageByEntityEvent event) {
|
||||
if (event.getDamager() instanceof EnderCrystal enderCrystal) {
|
||||
List<MetadataValue> metadataValues = enderCrystal.getMetadata(CRYSTAL_METADATA);
|
||||
return metadataValues
|
||||
.stream()
|
||||
.filter(source -> source instanceof CrystalMetadata)
|
||||
.map(meta -> (CrystalMetadata) meta)
|
||||
.findFirst()
|
||||
.flatMap(CrystalMetadata::getDamager);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static Optional<UUID> getDamagerUniqueIdFromRespawnAnchor(EntityDamageByBlockEvent event) {
|
||||
Block damager = event.getDamager();
|
||||
if (damager == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (damager.getType() != Material.RESPAWN_ANCHOR) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return damager.getMetadata(ANCHOR_METADATA).stream()
|
||||
.filter(source -> source instanceof CrystalMetadata)
|
||||
.map(meta -> (CrystalMetadata) meta)
|
||||
.findFirst()
|
||||
.flatMap(metadata -> metadata.getDamager());
|
||||
}
|
||||
|
||||
static void handleCombatTag(
|
||||
Optional<UUID> optionalDamagerUUID,
|
||||
Player player,
|
||||
FightManager fightManager,
|
||||
PluginConfig pluginConfig
|
||||
) {
|
||||
UUID victimUniqueId = player.getUniqueId();
|
||||
|
||||
if (optionalDamagerUUID.isPresent()) {
|
||||
UUID damagerUniqueId = optionalDamagerUUID.get();
|
||||
if (!damagerUniqueId.equals(victimUniqueId)) {
|
||||
fightManager.tag(
|
||||
damagerUniqueId,
|
||||
pluginConfig.settings.combatTimerDuration,
|
||||
CauseOfTag.CRYSTAL
|
||||
);
|
||||
fightManager.tag(
|
||||
victimUniqueId,
|
||||
pluginConfig.settings.combatTimerDuration,
|
||||
CauseOfTag.CRYSTAL
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.eternalcode.combat.crystalpvp;
|
||||
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.entity.Arrow;
|
||||
import org.bukkit.entity.EnderCrystal;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import static com.eternalcode.combat.crystalpvp.CrystalPvpConstants.CRYSTAL_METADATA;
|
||||
|
||||
public class EndCrystalListener implements Listener {
|
||||
|
||||
private final Plugin plugin;
|
||||
private final FightManager fightManager;
|
||||
private final PluginConfig pluginConfig;
|
||||
|
||||
public EndCrystalListener(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) {
|
||||
this.plugin = plugin;
|
||||
this.fightManager = fightManager;
|
||||
this.pluginConfig = pluginConfig;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
void onPlayerDamageCrystal(EntityDamageByEntityEvent event) {
|
||||
if (event.getEntity() instanceof EnderCrystal enderCrystal) {
|
||||
if (event.getDamager() instanceof Arrow arrow && arrow.getShooter() instanceof Player player) {
|
||||
enderCrystal.setMetadata(CRYSTAL_METADATA, new CrystalMetadata(this.plugin, player.getUniqueId()));
|
||||
}
|
||||
|
||||
if (!(event.getDamager() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
enderCrystal.setMetadata(CRYSTAL_METADATA, new CrystalMetadata(this.plugin, player.getUniqueId()));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
void onDamage(EntityDamageByEntityEvent event) {
|
||||
if (!this.pluginConfig.crystalPvp.tagFromCrystals) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pluginConfig.settings.ignoredWorlds.contains(event.getEntity().getWorld().getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<UUID> optionalDamagerUUID = CrystalPvpConstants.getDamagerUniqueIdFromEndCrystal(event);
|
||||
|
||||
if (optionalDamagerUUID.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getEntity() instanceof Player player) {
|
||||
CrystalPvpConstants.handleCombatTag(
|
||||
optionalDamagerUUID,
|
||||
player,
|
||||
this.fightManager,
|
||||
this.pluginConfig
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.eternalcode.combat.crystalpvp;
|
||||
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.type.RespawnAnchor;
|
||||
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.EntityDamageByBlockEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import static com.eternalcode.combat.crystalpvp.CrystalPvpConstants.ANCHOR_METADATA;
|
||||
|
||||
public class RespawnAnchorListener implements Listener {
|
||||
|
||||
private final Plugin plugin;
|
||||
private final FightManager fightManager;
|
||||
private final PluginConfig pluginConfig;
|
||||
|
||||
|
||||
public RespawnAnchorListener(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) {
|
||||
this.plugin = plugin;
|
||||
this.fightManager = fightManager;
|
||||
this.pluginConfig = pluginConfig;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
void onAnchorInteract(PlayerInteractEvent event) {
|
||||
Block block = event.getClickedBlock();
|
||||
if (block == null || block.getType() != Material.RESPAWN_ANCHOR) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(block.getBlockData() instanceof RespawnAnchor respawnAnchor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
|
||||
return;
|
||||
}
|
||||
|
||||
int charges = respawnAnchor.getCharges();
|
||||
|
||||
ItemStack item = event.getItem();
|
||||
boolean isGlowstone = item != null && item.getType() == Material.GLOWSTONE;
|
||||
|
||||
if ((charges > 0 && !isGlowstone) || charges == respawnAnchor.getMaximumCharges()) {
|
||||
addMetaData(event, block);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMetaData(PlayerInteractEvent event, Block block) {
|
||||
block.setMetadata(
|
||||
ANCHOR_METADATA,
|
||||
new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId())
|
||||
);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
void onAnchorExplosion(EntityDamageByBlockEvent event) {
|
||||
if (!this.pluginConfig.crystalPvp.tagFromRespawnAnchor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pluginConfig.settings.ignoredWorlds.contains(event.getEntity().getWorld().getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(event.getEntity() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<UUID> optionalDamagerUniqueId = CrystalPvpConstants.getDamagerUniqueIdFromRespawnAnchor(event);
|
||||
|
||||
if (optionalDamagerUniqueId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CrystalPvpConstants.handleCombatTag(optionalDamagerUniqueId, player, this.fightManager, this.pluginConfig);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.eternalcode.combat.event;
|
||||
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
public interface DynamicListener<E> extends Listener {
|
||||
|
||||
void onEvent(E event);
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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,37 @@
|
||||
package com.eternalcode.combat.event;
|
||||
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public class EventManager {
|
||||
|
||||
private final Plugin plugin;
|
||||
|
||||
public EventManager(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public <T extends Event> T publishEvent(T event) {
|
||||
this.plugin.getServer().getPluginManager().callEvent(event);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
public void subscribe(Listener... listeners) {
|
||||
for (Listener listener : listeners) {
|
||||
plugin.getServer().getPluginManager().registerEvents(listener, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
public <E extends Event, L extends DynamicListener<E>> void subscribe(Class<E> type, EventPriority priority, L listener) {
|
||||
plugin.getServer().getPluginManager().registerEvents(listener, plugin);
|
||||
plugin.getServer().getPluginManager().registerEvent(type, listener, priority, (l, event) -> {
|
||||
if (type.isInstance(event)) {
|
||||
listener.onEvent(type.cast(event));
|
||||
}
|
||||
}, plugin);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.eternalcode.combat.fight;
|
||||
|
||||
import com.eternalcode.combat.event.EventCaller;
|
||||
import com.eternalcode.combat.event.EventManager;
|
||||
|
||||
import com.eternalcode.combat.fight.event.CauseOfTag;
|
||||
import com.eternalcode.combat.fight.event.CauseOfUnTag;
|
||||
@@ -19,10 +19,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
public class FightManagerImpl implements FightManager {
|
||||
|
||||
private final Map<UUID, FightTag> fights = new ConcurrentHashMap<>();
|
||||
private final EventCaller eventCaller;
|
||||
private final EventManager eventManager;
|
||||
|
||||
public FightManagerImpl(EventCaller eventCaller) {
|
||||
this.eventCaller = eventCaller;
|
||||
public FightManagerImpl(EventManager eventManager) {
|
||||
this.eventManager = eventManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -38,7 +38,7 @@ public class FightManagerImpl implements FightManager {
|
||||
|
||||
@Override
|
||||
public FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag) {
|
||||
FightUntagEvent event = this.eventCaller.publishEvent(new FightUntagEvent(player, causeOfUnTag));
|
||||
FightUntagEvent event = this.eventManager.publishEvent(new FightUntagEvent(player, causeOfUnTag));
|
||||
if (event.isCancelled()) {
|
||||
return event;
|
||||
}
|
||||
@@ -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.eventCaller.publishEvent(new FightTagEvent(target, causeOfTag));
|
||||
FightTagEvent event = this.eventManager.publishEvent(new FightTagEvent(target, causeOfTag));
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return event;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.eternalcode.combat.fight;
|
||||
|
||||
import com.eternalcode.combat.config.implementation.MessagesSettings;
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.event.CancelTagReason;
|
||||
import com.eternalcode.combat.fight.event.CauseOfTag;
|
||||
import com.eternalcode.combat.fight.event.CauseOfUnTag;
|
||||
import com.eternalcode.combat.fight.event.FightTagEvent;
|
||||
import com.eternalcode.combat.fight.event.FightUntagEvent;
|
||||
import com.eternalcode.combat.notification.NotificationAnnouncer;
|
||||
import com.eternalcode.combat.notification.NoticeService;
|
||||
import dev.rollczi.litecommands.annotations.argument.Arg;
|
||||
import dev.rollczi.litecommands.annotations.command.Command;
|
||||
import dev.rollczi.litecommands.annotations.context.Context;
|
||||
@@ -16,6 +17,7 @@ import dev.rollczi.litecommands.annotations.priority.Priority;
|
||||
import dev.rollczi.litecommands.annotations.priority.PriorityValue;
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -23,12 +25,12 @@ import org.bukkit.entity.Player;
|
||||
public class FightTagCommand {
|
||||
|
||||
private final FightManager fightManager;
|
||||
private final NotificationAnnouncer announcer;
|
||||
private final NoticeService noticeService;
|
||||
private final PluginConfig config;
|
||||
|
||||
public FightTagCommand(FightManager fightManager, NotificationAnnouncer announcer, PluginConfig config) {
|
||||
public FightTagCommand(FightManager fightManager, NoticeService noticeService, PluginConfig config) {
|
||||
this.fightManager = fightManager;
|
||||
this.announcer = announcer;
|
||||
this.noticeService = noticeService;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@@ -37,15 +39,14 @@ public class FightTagCommand {
|
||||
void status(@Context CommandSender sender, @Arg Player target) {
|
||||
UUID targetUniqueId = target.getUniqueId();
|
||||
|
||||
this.announcer.create()
|
||||
this.noticeService.create()
|
||||
.notice(this.fightManager.isInCombat(targetUniqueId)
|
||||
? this.config.messages.admin.playerInCombat
|
||||
: this.config.messages.admin.playerNotInCombat
|
||||
? this.config.messagesSettings.admin.playerInCombat
|
||||
: this.config.messagesSettings.admin.playerNotInCombat
|
||||
)
|
||||
.placeholder("{PLAYER}", target.getName())
|
||||
.viewer(sender)
|
||||
.send();
|
||||
|
||||
}
|
||||
|
||||
@Execute(name = "tag")
|
||||
@@ -53,36 +54,35 @@ public class FightTagCommand {
|
||||
@Priority(PriorityValue.HIGH)
|
||||
void tag(@Context CommandSender sender, @Arg Player target) {
|
||||
UUID targetUniqueId = target.getUniqueId();
|
||||
Duration time = this.config.settings.combatDuration;
|
||||
Duration time = this.config.settings.combatTimerDuration;
|
||||
|
||||
FightTagEvent event = this.fightManager.tag(targetUniqueId, time, CauseOfTag.COMMAND);
|
||||
|
||||
if (event.isCancelled()) {
|
||||
CancelTagReason cancelReason = event.getCancelReason();
|
||||
|
||||
this.tagoutReasonHandler(sender, cancelReason, this.config.messages);
|
||||
this.tagoutReasonHandler(sender, cancelReason, this.config.messagesSettings);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.announcer.create()
|
||||
.notice(this.config.messages.admin.adminTagPlayer)
|
||||
this.noticeService.create()
|
||||
.notice(this.config.messagesSettings.admin.adminTagPlayer)
|
||||
.placeholder("{PLAYER}", target.getName())
|
||||
.viewer(sender)
|
||||
.send();
|
||||
|
||||
}
|
||||
|
||||
@Execute(name = "tag")
|
||||
@Permission("eternalcombat.tag")
|
||||
void tagMultiple(@Context CommandSender sender, @Arg Player firstTarget, @Arg Player secondTarget) {
|
||||
Duration combatTime = this.config.settings.combatDuration;
|
||||
PluginConfig.Messages messages = this.config.messages;
|
||||
Duration combatTime = this.config.settings.combatTimerDuration;
|
||||
MessagesSettings messagesSettings = this.config.messagesSettings;
|
||||
|
||||
if (sender.equals(firstTarget) || sender.equals(secondTarget)) {
|
||||
|
||||
this.announcer.create()
|
||||
.notice(messages.admin.adminCannotTagSelf)
|
||||
this.noticeService.create()
|
||||
.notice(messagesSettings.admin.adminCannotTagSelf)
|
||||
.viewer(sender)
|
||||
.send();
|
||||
|
||||
@@ -95,7 +95,7 @@ public class FightTagCommand {
|
||||
if (firstTagEvent.isCancelled()) {
|
||||
CancelTagReason cancelReason = firstTagEvent.getCancelReason();
|
||||
|
||||
this.tagoutReasonHandler(sender, cancelReason, messages);
|
||||
this.tagoutReasonHandler(sender, cancelReason, messagesSettings);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ public class FightTagCommand {
|
||||
if (secondTagEvent.isCancelled()) {
|
||||
CancelTagReason cancelReason = secondTagEvent.getCancelReason();
|
||||
|
||||
this.tagoutReasonHandler(sender, cancelReason, messages);
|
||||
this.tagoutReasonHandler(sender, cancelReason, messagesSettings);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -112,24 +112,24 @@ public class FightTagCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
this.announcer.create()
|
||||
.notice(messages.admin.adminTagMultiplePlayers)
|
||||
this.noticeService.create()
|
||||
.notice(messagesSettings.admin.adminTagMultiplePlayers)
|
||||
.placeholder("{FIRST_PLAYER}", firstTarget.getName())
|
||||
.placeholder("{SECOND_PLAYER}", secondTarget.getName())
|
||||
.viewer(sender)
|
||||
.send();
|
||||
|
||||
}
|
||||
|
||||
@Execute(name = "untag")
|
||||
@Permission("eternalcombat.untag")
|
||||
void untag(@Context Player sender, @Arg Player target) {
|
||||
void untag(@Context CommandSender sender, @Arg Player target) {
|
||||
UUID targetUniqueId = target.getUniqueId();
|
||||
|
||||
if (!this.fightManager.isInCombat(targetUniqueId)) {
|
||||
this.announcer.create()
|
||||
this.noticeService.create()
|
||||
.notice(this.config.messagesSettings.admin.adminPlayerNotInCombat)
|
||||
.placeholder("{PLAYER}", target.getName())
|
||||
.viewer(sender)
|
||||
.notice(this.config.messages.admin.adminPlayerNotInCombat)
|
||||
.send();
|
||||
return;
|
||||
}
|
||||
@@ -139,21 +139,52 @@ public class FightTagCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.announcer.create()
|
||||
.notice(this.config.messages.admin.adminUntagPlayer)
|
||||
this.noticeService.create()
|
||||
.notice(this.config.messagesSettings.admin.adminUntagPlayer)
|
||||
.placeholder("{PLAYER}", target.getName())
|
||||
.viewer(sender)
|
||||
.send();
|
||||
}
|
||||
|
||||
private void tagoutReasonHandler(CommandSender sender, CancelTagReason cancelReason, PluginConfig.Messages messages) {
|
||||
@Execute(name = "untagall")
|
||||
@Permission("eternalcombat.untagall")
|
||||
void untagAll(@Context CommandSender sender) {
|
||||
int combatPlayersSize = this.fightManager.getFights().size();
|
||||
|
||||
this.fightManager.getFights().stream()
|
||||
.map(FightTag::getTaggedPlayer)
|
||||
.collect(Collectors.toSet())
|
||||
.forEach(uuid -> this.fightManager.untag(uuid, CauseOfUnTag.COMMAND));
|
||||
|
||||
this.noticeService.create()
|
||||
.notice(this.config.messagesSettings.admin.adminUntagAll)
|
||||
.placeholder("{COUNT}", String.valueOf(combatPlayersSize))
|
||||
.viewer(sender)
|
||||
.send();
|
||||
}
|
||||
|
||||
@Execute(name = "stats")
|
||||
@Permission("eternalcombat.stats")
|
||||
void stats(@Context CommandSender sender) {
|
||||
long activeCombatPlayers = this.fightManager.getFights().stream()
|
||||
.filter(fightTag -> !fightTag.isExpired())
|
||||
.count();
|
||||
|
||||
this.noticeService.create()
|
||||
.notice(this.config.messagesSettings.admin.combatStats)
|
||||
.placeholder("{COUNT}", String.valueOf(activeCombatPlayers))
|
||||
.viewer(sender)
|
||||
.send();
|
||||
}
|
||||
|
||||
private void tagoutReasonHandler(
|
||||
CommandSender sender, CancelTagReason cancelReason,
|
||||
MessagesSettings messagesSettings) {
|
||||
if (cancelReason == CancelTagReason.TAGOUT) {
|
||||
this.announcer.create()
|
||||
.notice(messages.admin.adminTagOutCanceled)
|
||||
this.noticeService.create()
|
||||
.notice(messagesSettings.admin.adminTagOutCanceled)
|
||||
.viewer(sender)
|
||||
.send();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
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.notification.NotificationAnnouncer;
|
||||
import com.eternalcode.combat.fight.event.FightTagEvent;
|
||||
import com.eternalcode.combat.notification.NoticeService;
|
||||
import com.eternalcode.combat.util.DurationUtil;
|
||||
import java.util.Optional;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -15,13 +18,13 @@ public class FightTask implements Runnable {
|
||||
private final Server server;
|
||||
private final PluginConfig config;
|
||||
private final FightManager fightManager;
|
||||
private final NotificationAnnouncer announcer;
|
||||
private final NoticeService noticeService;
|
||||
|
||||
public FightTask(Server server, PluginConfig config, FightManager fightManager, NotificationAnnouncer announcer) {
|
||||
public FightTask(Server server, PluginConfig config, FightManager fightManager, NoticeService noticeService) {
|
||||
this.server = server;
|
||||
this.config = config;
|
||||
this.fightManager = fightManager;
|
||||
this.announcer = announcer;
|
||||
this.noticeService = noticeService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,12 +43,29 @@ 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();
|
||||
|
||||
this.announcer.create()
|
||||
String opponent = Optional.ofNullable(fightTag.getTagger())
|
||||
.filter(uuid -> !uuid.equals(playerUniqueId))
|
||||
.map(this.server::getPlayer)
|
||||
.map(Player::getName)
|
||||
.orElse(this.config.messagesSettings.unknownPlayerPlaceholder);
|
||||
|
||||
this.noticeService.create()
|
||||
.player(player.getUniqueId())
|
||||
.notice(this.config.messages.combatNotification)
|
||||
.placeholder("{TIME}", DurationUtil.format(remaining))
|
||||
.notice(this.config.messagesSettings.combatNotification)
|
||||
.placeholder("{TIME}", DurationUtil.format(remaining, this.config.messagesSettings.withoutMillis))
|
||||
.placeholder("{OPPONENT}", opponent)
|
||||
.send();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.eternalcode.combat.fight.blocker;
|
||||
|
||||
import com.eternalcode.combat.WhitelistBlacklistMode;
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import com.eternalcode.combat.notification.NoticeService;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.bukkit.util.StringUtil;
|
||||
|
||||
public class CommandsBlocker implements Listener {
|
||||
|
||||
private final FightManager fightManager;
|
||||
private final NoticeService noticeService;
|
||||
private final PluginConfig config;
|
||||
|
||||
public CommandsBlocker(FightManager fightManager, NoticeService noticeService, PluginConfig config) {
|
||||
this.fightManager = fightManager;
|
||||
this.noticeService = noticeService;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
UUID playerUniqueId = player.getUniqueId();
|
||||
|
||||
if (!this.fightManager.isInCombat(playerUniqueId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String command = event.getMessage().substring(1);
|
||||
|
||||
boolean isAnyMatch = this.config.commands.restrictedCommands.stream()
|
||||
.anyMatch(restrictedCommand -> StringUtil.startsWithIgnoreCase(command, restrictedCommand));
|
||||
|
||||
WhitelistBlacklistMode mode = this.config.commands.commandRestrictionMode;
|
||||
|
||||
boolean shouldCancel = mode.shouldBlock(isAnyMatch);
|
||||
|
||||
if (shouldCancel) {
|
||||
event.setCancelled(true);
|
||||
this.noticeService.create()
|
||||
.player(playerUniqueId)
|
||||
.notice(this.config.messagesSettings.commandDisabledDuringCombat)
|
||||
.send();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.eternalcode.combat.fight.blocker;
|
||||
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.EntityToggleGlideEvent;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
|
||||
public class ElytraBlocker implements Listener {
|
||||
|
||||
private final FightManager fightManager;
|
||||
private final PluginConfig config;
|
||||
private final Cache<UUID, Boolean> fallProtection = Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
public ElytraBlocker(FightManager fightManager, PluginConfig config) {
|
||||
this.fightManager = fightManager;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onToggleGlide(EntityToggleGlideEvent event) {
|
||||
if (!this.config.combat.disableElytraUsage) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(event.getEntity() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
UUID uniqueId = player.getUniqueId();
|
||||
|
||||
if (!this.fightManager.isInCombat(uniqueId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.isGliding()) {
|
||||
event.setCancelled(true);
|
||||
player.setGliding(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
void onMoveWhileGliding(PlayerMoveEvent event) {
|
||||
if (!this.config.combat.disableElytraUsage) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
UUID uniqueId = player.getUniqueId();
|
||||
|
||||
if (!this.fightManager.isInCombat(uniqueId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.isGliding()) {
|
||||
player.setGliding(false);
|
||||
|
||||
player.setVelocity(player.getVelocity().setY(-10));
|
||||
player.setFallDistance(0f);
|
||||
fallProtection.put(uniqueId, true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onEntityDamage(EntityDamageEvent event) {
|
||||
if (!(event.getEntity() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getCause() != EntityDamageEvent.DamageCause.FALL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fallProtection.get(player.getUniqueId(), key -> false)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onDamage(EntityDamageEvent event) {
|
||||
if (!this.config.combat.disableElytraOnDamage) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(event.getEntity() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
UUID uniqueId = player.getUniqueId();
|
||||
|
||||
if (this.fightManager.isInCombat(uniqueId) && player.isGliding()) {
|
||||
player.setGliding(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.eternalcode.combat.fight.blocker;
|
||||
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import com.eternalcode.combat.fight.event.FightTagEvent;
|
||||
import com.eternalcode.combat.notification.NoticeService;
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class ElytraEquipBlocker implements Listener {
|
||||
|
||||
private final FightManager fightManager;
|
||||
private final NoticeService noticeService;
|
||||
private final PluginConfig config;
|
||||
private final Server server;
|
||||
|
||||
public ElytraEquipBlocker(FightManager fightManager, NoticeService noticeService, PluginConfig config, Server server) {
|
||||
this.fightManager = fightManager;
|
||||
this.noticeService = noticeService;
|
||||
this.config = config;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onInventoryClick(InventoryClickEvent event) {
|
||||
if (!(event.getWhoClicked() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.config.combat.unequipElytraOnCombat) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uniqueId = player.getUniqueId();
|
||||
|
||||
if (!this.fightManager.isInCombat(uniqueId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getCurrentItem() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getCurrentItem().getType() == Material.ELYTRA) {
|
||||
event.setCancelled(true);
|
||||
|
||||
this.noticeService.create()
|
||||
.player(uniqueId)
|
||||
.notice(this.config.messagesSettings.elytraDisabledDuringCombat)
|
||||
.send();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
void onTag(FightTagEvent event) {
|
||||
UUID uniqueId = event.getPlayer();
|
||||
Player player = this.server.getPlayer(uniqueId);
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.config.combat.unequipElytraOnCombat) {
|
||||
ItemStack chest = player.getInventory().getChestplate();
|
||||
|
||||
if (chest != null && chest.getType() == Material.ELYTRA) {
|
||||
removeChestplateIfElytra(player);
|
||||
|
||||
this.noticeService.create()
|
||||
.player(uniqueId)
|
||||
.notice(this.config.messagesSettings.elytraDisabledDuringCombat)
|
||||
.send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeChestplateIfElytra(Player player) {
|
||||
ItemStack chestplate = player.getInventory().getChestplate();
|
||||
|
||||
if (chestplate != null && chestplate.getType() == Material.ELYTRA) {
|
||||
player.getInventory().setChestplate(null);
|
||||
|
||||
HashMap<Integer, ItemStack> leftover = player.getInventory().addItem(chestplate);
|
||||
leftover.values().forEach(item ->
|
||||
player.getWorld().dropItemNaturally(player.getLocation(), item)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onInteract(PlayerInteractEvent event) {
|
||||
if (!this.config.combat.unequipElytraOnCombat) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getItem() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getItem().getType() != Material.ELYTRA) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
UUID uniqueId = player.getUniqueId();
|
||||
|
||||
if (!this.fightManager.isInCombat(uniqueId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.getAction()) {
|
||||
case RIGHT_CLICK_AIR, RIGHT_CLICK_BLOCK -> {
|
||||
event.setCancelled(true);
|
||||
|
||||
this.noticeService.create()
|
||||
.player(uniqueId)
|
||||
.notice(this.config.messagesSettings.elytraDisabledDuringCombat)
|
||||
.send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.eternalcode.combat.fight.blocker;
|
||||
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import com.eternalcode.combat.fight.event.FightTagEvent;
|
||||
import com.eternalcode.combat.fight.event.FightUntagEvent;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerToggleFlightEvent;
|
||||
|
||||
public class FlyingBlocker implements Listener {
|
||||
|
||||
private final FightManager fightManager;
|
||||
private final PluginConfig config;
|
||||
private final Server server;
|
||||
|
||||
public FlyingBlocker(FightManager fightManager, PluginConfig config, Server server) {
|
||||
this.fightManager = fightManager;
|
||||
this.config = config;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
void onFly(PlayerToggleFlightEvent event) {
|
||||
if (!this.config.combat.disableFlying) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
UUID uniqueId = player.getUniqueId();
|
||||
|
||||
if (!this.fightManager.isInCombat(uniqueId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.isFlying()) {
|
||||
player.setAllowFlight(false);
|
||||
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
void onTag(FightTagEvent event) {
|
||||
UUID uniqueId = event.getPlayer();
|
||||
Player player = this.server.getPlayer(uniqueId);
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.config.combat.disableFlying) {
|
||||
GameMode gameMode = player.getGameMode();
|
||||
|
||||
if (gameMode != GameMode.CREATIVE && gameMode != GameMode.SPECTATOR) {
|
||||
player.setAllowFlight(false);
|
||||
player.setFlying(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onUnTag(FightUntagEvent event) {
|
||||
if (!this.config.combat.disableFlying) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uniqueId = event.getPlayer();
|
||||
Player player = this.server.getPlayer(uniqueId);
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
GameMode playerGameMode = player.getGameMode();
|
||||
if (playerGameMode == GameMode.CREATIVE || playerGameMode == GameMode.SPECTATOR) {
|
||||
player.setAllowFlight(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.eternalcode.combat.fight.blocker;
|
||||
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.FightManager;
|
||||
import com.eternalcode.combat.notification.NoticeService;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
|
||||
public class InventoryContainersBlocker implements Listener {
|
||||
|
||||
private final FightManager fightManager;
|
||||
private final PluginConfig config;
|
||||
private final NoticeService noticeService;
|
||||
|
||||
public InventoryContainersBlocker(FightManager fightManager, PluginConfig config, NoticeService noticeService) {
|
||||
this.fightManager = fightManager;
|
||||
this.config = config;
|
||||
this.noticeService = noticeService;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onOpenInventory(InventoryOpenEvent event) {
|
||||
Player player = (Player) event.getPlayer();
|
||||
UUID uniqueId = player.getUniqueId();
|
||||
|
||||
if (!this.fightManager.isInCombat(uniqueId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
InventoryType inventoryType = event.getInventory().getType();
|
||||
|
||||
|
||||
boolean isInRestrictedList = this.config.inventory.restrictedInventoryTypes.contains(inventoryType);
|
||||
boolean shouldBlock = this.config.inventory.inventoryAccessMode.shouldBlock(isInRestrictedList);
|
||||
|
||||
if (shouldBlock) {
|
||||
event.setCancelled(true);
|
||||
|
||||
this.noticeService.create()
|
||||
.player(uniqueId)
|
||||
.notice(this.config.messagesSettings.inventoryBlockedDuringCombat)
|
||||
.send();
|
||||
}
|
||||
}
|
||||
}
|
||||