Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
786d480a11 | ||
|
|
6546694c7c | ||
|
|
e3508f3be6 | ||
|
|
fdd5147f18 | ||
|
|
2265b66dfc | ||
|
|
07821a62f0 | ||
|
|
78db24b23f | ||
|
|
9ea261517a | ||
|
|
7363d57b0e | ||
|
|
0951ef3b97 | ||
|
|
8a48eb55bf | ||
|
|
dd1688af28 | ||
|
|
fbce3c2c0b | ||
|
|
2de95627d5 | ||
|
|
f31a459c13 | ||
|
|
9fe7a1d067 | ||
|
|
b5c289c2b1 | ||
|
|
5b4f9d1911 | ||
|
|
e024b2fae9 | ||
|
|
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 | ||
|
|
c8483e51e9 | ||
|
|
dfc825cd98 | ||
|
|
bd5a6a4351 | ||
|
|
97fd32a7af | ||
|
|
9203309def | ||
|
|
381007a735 | ||
|
|
534e2cf857 | ||
|
|
20a4e4a2f5 | ||
|
|
fe2569918a | ||
|
|
e17effa4cd | ||
|
|
c63df3ab38 | ||
|
|
24fade593b | ||
|
|
4b12968a7b | ||
|
|
9ad0063aa5 | ||
|
|
7f6c6341f1 | ||
|
|
0620f7fb92 | ||
|
|
d18b2f013a | ||
|
|
7b89dfdcfc | ||
|
|
595370f6a4 | ||
|
|
e732bdf842 | ||
|
|
6b6329c4a6 | ||
|
|
7a6707b20e | ||
|
|
7a10e268bb | ||
|
|
adce4729f0 | ||
|
|
8560f17bac | ||
|
|
d2d7a4f02c | ||
|
|
cf33b23720 | ||
|
|
e261e06635 | ||
|
|
2856ababff | ||
|
|
f061e1738e | ||
|
|
4118983479 | ||
|
|
988859ddbe | ||
|
|
305ab9e299 | ||
|
|
5aaf8a4f08 | ||
|
|
153657c554 | ||
|
|
5123176cb3 | ||
|
|
1665f39151 | ||
|
|
94113ea326 | ||
|
|
7f581dbaac | ||
|
|
2dd25cc27e | ||
|
|
3ca562f515 | ||
|
|
383c405198 | ||
|
|
c522f7c078 | ||
|
|
fb6efa0646 | ||
|
|
ad7899f10e | ||
|
|
ab5e6a7cf3 | ||
|
|
42ae17bf8d | ||
|
|
20d342d0ca | ||
|
|
c4adeb1aed | ||
|
|
d2d0c5b608 | ||
|
|
eb1febab85 | ||
|
|
8153ba55ab | ||
|
|
89784f3412 | ||
|
|
49e82385d4 | ||
|
|
190034f5aa | ||
|
|
1db744c4ec | ||
|
|
94d7baf791 | ||
|
|
c25af3f6d3 | ||
|
|
3b38c82b47 | ||
|
|
526f831fcb | ||
|
|
e4151cbbe0 | ||
|
|
47d9fed823 | ||
|
|
0890b2abbd | ||
|
|
be99a8646d | ||
|
|
b58266c868 | ||
|
|
eaa02a4166 | ||
|
|
08e99ded13 | ||
|
|
e867e7e697 | ||
|
|
16c40319f4 | ||
|
|
a88c9e74fe | ||
|
|
cceeb82fef | ||
|
|
92535dd7a3 | ||
|
|
3390534d98 | ||
|
|
d45ce7406f | ||
|
|
5861f70010 | ||
|
|
7940a2663b |
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"
|
||||
]
|
||||
}
|
||||
@@ -4,40 +4,45 @@ on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
java-version: '25'
|
||||
distribution: 'temurin'
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v3.3.1
|
||||
|
||||
- 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@e3426b1f20c7262e0fed7b855aae1325633af3b4
|
||||
run: ./gradlew shadowJar
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
arguments: shadowJar
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: 'Successfully build EternalCombat'
|
||||
path: build/libs/*.jar
|
||||
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: '25'
|
||||
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: '25'
|
||||
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 }}
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Publish EternalCombat API
|
||||
on:
|
||||
release:
|
||||
branches: [ master ]
|
||||
types: [ published ]
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
environment: deployment
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 25
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Publish with Gradle
|
||||
run: ./gradlew clean build eternalcombat-api:publish
|
||||
env:
|
||||
ETERNAL_CODE_MAVEN_USERNAME: ${{ secrets.ETERNAL_CODE_MAVEN_USERNAME }}
|
||||
ETERNAL_CODE_MAVEN_PASSWORD: ${{ secrets.ETERNAL_CODE_MAVEN_PASSWORD }}
|
||||
@@ -1,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,47 +1,174 @@
|
||||
<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.20.1, but it should work on most other versions. If you
|
||||
encounter any compatibility issues, please report them in the
|
||||
[Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). The plugin requires Java 17 or later, so please
|
||||
make sure it is installed on your server.
|
||||
## EternalCombat requires PacketEvents plugin to work.
|
||||
You can download it from [here](https://modrinth.com/plugin/packetevents)
|
||||
|
||||
## Building
|
||||
Build EternalCombat using:
|
||||
EternalCombat 2.0 has been tested on Minecraft versions **1.17.1 to 26.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!
|
||||
|
||||
`./gradlew shadowJar`
|
||||
### How Does EternalCombat Work?
|
||||
|
||||
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:
|
||||
|
||||
|
||||
- **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">
|
||||
|
||||
- **Support for newest features**
|
||||
Our plugin features cooldown and delays for tridents, spears and much more. With **out of the box** configurable feats.
|
||||
<img alt="Trident and spear cooldown feature" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/spear.gif?raw=true" width="420">
|
||||
|
||||
- **Customize combat experience**
|
||||
Add custom effects to players in combat or death. Everything should be configurable and user-friendly:
|
||||
<img alt="Lightning strikes when players die" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/lightning.gif?raw=true" width="420">
|
||||
<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:
|
||||
|
||||
```kts
|
||||
maven {
|
||||
url = uri("https://repo.eternalcode.pl/releases")
|
||||
}
|
||||
```
|
||||
|
||||
With Maven:
|
||||
|
||||
```xml
|
||||
|
||||
<repository>
|
||||
<id>eternalcode-reposilite-releases</id>
|
||||
<name>EternalCode Repository</name>
|
||||
<url>https://repo.eternalcode.pl/releases</url>
|
||||
</repository>
|
||||
```
|
||||
|
||||
#### 2. Add dependency:
|
||||
|
||||
With Gradle:
|
||||
|
||||
```kts
|
||||
compileOnly("com.eternalcode:eternalcombat-api:2.6.0-SNAPSHOT")
|
||||
```
|
||||
|
||||
With Maven:
|
||||
|
||||
```xml
|
||||
|
||||
<dependency>
|
||||
<groupId>com.eternalcode</groupId>
|
||||
<artifactId>eternalcombat-api</artifactId>
|
||||
<version>2.6.0-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: 60 KiB After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 4.1 MiB |
@@ -1,129 +0,0 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
checkstyle
|
||||
|
||||
id("net.minecrell.plugin-yml.bukkit") version "0.6.0"
|
||||
id("com.github.johnrengelman.shadow") version "8.1.1"
|
||||
id("xyz.jpenilla.run-paper") version "2.1.0"
|
||||
}
|
||||
|
||||
group = "com.eternalcode"
|
||||
version = "1.1.0"
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.12.2"
|
||||
|
||||
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
|
||||
|
||||
maxErrors = 0
|
||||
maxWarnings = 0
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
|
||||
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://repo.panda-lang.org/releases") }
|
||||
maven { url = uri("https://maven.enginehub.org/repo/") }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Spigot api
|
||||
compileOnly("org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT")
|
||||
|
||||
// kyori
|
||||
implementation("net.kyori:adventure-platform-bukkit:4.3.0")
|
||||
implementation("net.kyori:adventure-text-minimessage:4.14.0")
|
||||
|
||||
// litecommands
|
||||
implementation("dev.rollczi.litecommands:bukkit-adventure:2.8.9")
|
||||
|
||||
// cdn configs
|
||||
implementation("net.dzikoysk:cdn:1.14.4") {
|
||||
exclude(group = "org.jetbrains.kotlin")
|
||||
}
|
||||
|
||||
// GitCheck
|
||||
implementation("com.eternalcode:gitcheck:1.0.0")
|
||||
|
||||
// commons
|
||||
implementation("commons-io:commons-io:2.13.0")
|
||||
|
||||
// bstats
|
||||
implementation("org.bstats:bstats-bukkit:3.0.2")
|
||||
|
||||
// worldguard
|
||||
compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.9")
|
||||
|
||||
// tests
|
||||
testImplementation("org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.0")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
|
||||
}
|
||||
|
||||
bukkit {
|
||||
main = "com.eternalcode.combat.CombatPlugin"
|
||||
author = "EternalCodeTeam"
|
||||
apiVersion = "1.13"
|
||||
prefix = "EternalCombat"
|
||||
name = "EternalCombat"
|
||||
softDepend = listOf("WorldGuard")
|
||||
version = "${project.version}"
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
tasks.getByName<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
tasks {
|
||||
runServer {
|
||||
minecraftVersion("1.19.4")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<ShadowJar> {
|
||||
archiveFileName.set("EternalCombat v${project.version}.jar")
|
||||
|
||||
dependsOn("checkstyleMain")
|
||||
dependsOn("checkstyleTest")
|
||||
dependsOn("test")
|
||||
|
||||
exclude(
|
||||
"org/intellij/lang/annotations/**",
|
||||
"org/jetbrains/annotations/**",
|
||||
"META-INF/**",
|
||||
"javax/**"
|
||||
)
|
||||
|
||||
val prefix = "com.eternalcode.combat.libs"
|
||||
listOf(
|
||||
"panda.std",
|
||||
"panda.utilities",
|
||||
"org.panda-lang",
|
||||
"net.dzikoysk",
|
||||
"net.kyori",
|
||||
"org.bstats",
|
||||
"dev.rollczi.litecommands",
|
||||
"com.eternalcode.gitcheck",
|
||||
"org.json.simple",
|
||||
"commons-io"
|
||||
).forEach { pack ->
|
||||
relocate(pack, "$prefix.$pack")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.6.1")
|
||||
implementation("com.modrinth.minotaur:com.modrinth.minotaur.gradle.plugin:2.10.0")
|
||||
implementation("io.papermc.hangar-publish-plugin:io.papermc.hangar-publish-plugin.gradle.plugin:0.1.4")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.4.20")
|
||||
implementation("net.minecrell:plugin-yml:0.6.0")
|
||||
implementation("xyz.jpenilla:run-task:3.1.0")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java.setSrcDirs(emptyList<String>())
|
||||
groovy.setSrcDirs(emptyList<String>())
|
||||
resources.setSrcDirs(emptyList<String>())
|
||||
}
|
||||
test {
|
||||
java.setSrcDirs(emptyList<String>())
|
||||
kotlin.setSrcDirs(emptyList<String>())
|
||||
groovy.setSrcDirs(emptyList<String>())
|
||||
resources.setSrcDirs(emptyList<String>())
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
object Versions {
|
||||
|
||||
// Keep this version for best compatibility.
|
||||
const val PAPER_API = "1.19.3-R0.1-SNAPSHOT"
|
||||
|
||||
const val JUNIT_JUPITER_API = "6.1.3"
|
||||
const val JUNIT_JUPITER_PARAMS = "6.1.3"
|
||||
const val JUNIT_JUPITER_ENGINE = "6.1.3"
|
||||
|
||||
const val JETBRAINS_ANNOTATIONS = "26.1.0"
|
||||
|
||||
const val ETERNALCODE_COMMONS = "1.4.1"
|
||||
const val MULTIFICATION = "1.2.5-SNAPSHOT"
|
||||
const val PACKETS_EVENTS = "2.13.0"
|
||||
|
||||
const val 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 XSERIES = "13.8.0"
|
||||
|
||||
const val CAFFEINE = "3.3.0"
|
||||
|
||||
const val B_STATS_BUKKIT = "3.2.1"
|
||||
const val WORLD_GUARD_BUKKIT = "7.0.17"
|
||||
|
||||
const val PLACEHOLDER_API = "2.12.3"
|
||||
const val LANDS_API = "7.25.4"
|
||||
const val GRIEF_PREVENTION = "16.18.7"
|
||||
const val WORLDEDIT = "3ISh7ADm" //cannot use numeric version bc of duplicated version on modrinth
|
||||
const val PACKETEVENTS = "2.14.0"
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
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}")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
sourceSets.test {
|
||||
java.setSrcDirs(listOf("test"))
|
||||
resources.setSrcDirs(emptyList<String>())
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
group = "com.eternalcode"
|
||||
version = "2.6.0-SNAPSHOT"
|
||||
|
||||
tasks.compileJava {
|
||||
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
|
||||
options.encoding = "UTF-8"
|
||||
options.release = 21
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
group = "com.eternalcode"
|
||||
version = "2.6.0-SNAPSHOT"
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("maven") {
|
||||
artifactId = "eternalcombat-api"
|
||||
from(project.components["java"])
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven {
|
||||
name = "eternalcodeReleases"
|
||||
url = uri("https://repo.eternalcode.pl/releases")
|
||||
credentials {
|
||||
username = System.getenv("ETERNAL_CODE_MAVEN_USERNAME")
|
||||
password = System.getenv("ETERNAL_CODE_MAVEN_PASSWORD")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
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,23 @@
|
||||
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.3")
|
||||
downloadPlugins.modrinth("WorldEdit", "7.4.6")
|
||||
downloadPlugins.modrinth("PacketEvents", "2.14.0+spigot")
|
||||
downloadPlugins.modrinth("WorldGuard", "7.0.20")
|
||||
downloadPlugins.modrinth("LuckPerms", "v5.5.53-bukkit")
|
||||
downloadPlugins.modrinth("GriefPrevention", Versions.GRIEF_PREVENTION)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
plugins {
|
||||
`eternalcombat-java`
|
||||
`eternalcombat-repositories`
|
||||
`eternalcombat-publish`
|
||||
`eternalcombat-java-unit-test`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Paper api
|
||||
compileOnlyApi("io.papermc.paper:paper-api:${Versions.PAPER_API}")
|
||||
api("org.jetbrains:annotations:${Versions.JETBRAINS_ANNOTATIONS}")
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.eternalcode.combat;
|
||||
|
||||
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.PearlService;
|
||||
import com.eternalcode.combat.region.RegionProvider;
|
||||
import com.eternalcode.combat.fight.tagout.FightTagOutService;
|
||||
|
||||
public interface EternalCombatApi {
|
||||
|
||||
FightManager getFightManager();
|
||||
|
||||
RegionProvider getRegionProvider();
|
||||
|
||||
PearlService getFightPearlService();
|
||||
|
||||
FightTagOutService getFightTagOutService();
|
||||
|
||||
FightEffectService getFightEffectService();
|
||||
|
||||
DropService getDropService();
|
||||
|
||||
DropKeepInventoryService getDropKeepInventoryService();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.eternalcode.combat;
|
||||
|
||||
public final class EternalCombatProvider {
|
||||
|
||||
private static EternalCombatApi ETERNAL_COMBAT_API;
|
||||
|
||||
public static EternalCombatApi provide() {
|
||||
if (ETERNAL_COMBAT_API == null) {
|
||||
throw new IllegalStateException("EternalCombatApi has not been initialized yet!");
|
||||
}
|
||||
|
||||
return ETERNAL_COMBAT_API;
|
||||
}
|
||||
|
||||
static void initialize(EternalCombatApi eternalCombatApi) {
|
||||
if (ETERNAL_COMBAT_API != null) {
|
||||
throw new IllegalStateException("EternalCombatApi has already been initialized!");
|
||||
}
|
||||
|
||||
ETERNAL_COMBAT_API = eternalCombatApi;
|
||||
}
|
||||
|
||||
static void deinitialize() {
|
||||
if (ETERNAL_COMBAT_API == null) {
|
||||
throw new IllegalStateException("EternalCombatApi has not been initialized yet!");
|
||||
}
|
||||
|
||||
ETERNAL_COMBAT_API = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.eternalcode.combat.fight;
|
||||
|
||||
import com.eternalcode.combat.fight.event.CauseOfTag;
|
||||
import com.eternalcode.combat.fight.event.CauseOfUnTag;
|
||||
import com.eternalcode.combat.fight.event.FightTagEvent;
|
||||
import com.eternalcode.combat.fight.event.FightUntagEvent;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface FightManager {
|
||||
|
||||
boolean isInCombat(UUID player);
|
||||
|
||||
FightTag getTag(UUID target);
|
||||
|
||||
Collection<FightTag> getFights();
|
||||
|
||||
@ApiStatus.Experimental
|
||||
FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger);
|
||||
|
||||
FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag);
|
||||
|
||||
FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag);
|
||||
|
||||
void untagAll();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.eternalcode.combat.fight;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface FightTag {
|
||||
|
||||
@Nullable
|
||||
@ApiStatus.Experimental
|
||||
UUID getTagger();
|
||||
|
||||
Duration getRemainingDuration();
|
||||
|
||||
boolean isExpired();
|
||||
|
||||
Instant getEndOfCombatLog();
|
||||
|
||||
UUID getTaggedPlayer();
|
||||
}
|
||||
@@ -1,41 +1,31 @@
|
||||
package com.eternalcode.combat.drop;
|
||||
package com.eternalcode.combat.fight.drop;
|
||||
|
||||
import com.eternalcode.combat.fight.FightDeathCause;
|
||||
import com.eternalcode.combat.fight.FightTag;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Drop {
|
||||
|
||||
private final Player player;
|
||||
private final Player killer;
|
||||
private final FightTag fightTag;
|
||||
private final FightDeathCause deathCause;
|
||||
|
||||
private List<ItemStack> droppedItems;
|
||||
private int droppedExp;
|
||||
private final List<ItemStack> droppedItems;
|
||||
private final int droppedExp;
|
||||
|
||||
private Drop(Player player, Player killer, FightTag fightTag, FightDeathCause deathCause, List<ItemStack> droppedItems, int droppedExp) {
|
||||
private Drop(Player player, Player killer, List<ItemStack> droppedItems, int droppedExp) {
|
||||
this.player = player;
|
||||
this.killer = killer;
|
||||
this.fightTag = fightTag;
|
||||
this.deathCause = deathCause;
|
||||
|
||||
this.droppedItems = droppedItems;
|
||||
this.droppedExp = droppedExp;
|
||||
}
|
||||
|
||||
public List<ItemStack> getDroppedItems() {
|
||||
return this.droppedItems;
|
||||
}
|
||||
|
||||
public void setDroppedItems(List<ItemStack> droppedItems) {
|
||||
this.droppedItems = droppedItems;
|
||||
return Collections.unmodifiableList(this.droppedItems);
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
@@ -46,14 +36,6 @@ public class Drop {
|
||||
return this.killer;
|
||||
}
|
||||
|
||||
public FightTag getFightTag() {
|
||||
return this.fightTag;
|
||||
}
|
||||
|
||||
public FightDeathCause getDeathCause() {
|
||||
return this.deathCause;
|
||||
}
|
||||
|
||||
public boolean hasKiller() {
|
||||
return this.killer != null;
|
||||
}
|
||||
@@ -62,10 +44,6 @@ public class Drop {
|
||||
return this.droppedExp;
|
||||
}
|
||||
|
||||
public void setDroppedExp(int droppedExp) {
|
||||
this.droppedExp = droppedExp;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
@@ -74,8 +52,6 @@ public class Drop {
|
||||
|
||||
private Player player;
|
||||
private Player killer;
|
||||
private FightTag fightTag;
|
||||
private FightDeathCause deathCause;
|
||||
private List<ItemStack> droppedItems;
|
||||
private int droppedExp;
|
||||
|
||||
@@ -89,16 +65,6 @@ public class Drop {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fightTag(@NotNull FightTag fightTag) {
|
||||
this.fightTag = fightTag;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder deathCause(@NotNull FightDeathCause deathCause) {
|
||||
this.deathCause = deathCause;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder droppedItems(@NotNull List<ItemStack> droppedItems) {
|
||||
this.droppedItems = new ArrayList<>(droppedItems);
|
||||
return this;
|
||||
@@ -113,8 +79,6 @@ public class Drop {
|
||||
return new Drop(
|
||||
this.player,
|
||||
this.killer,
|
||||
this.fightTag,
|
||||
this.deathCause,
|
||||
this.droppedItems,
|
||||
this.droppedExp
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.eternalcode.combat.fight.drop;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public interface DropKeepInventoryService {
|
||||
|
||||
List<ItemStack> nextItems(UUID uuid);
|
||||
|
||||
boolean hasItems(UUID uuid);
|
||||
|
||||
void addItems(UUID uuid, List<ItemStack> item);
|
||||
|
||||
void addItem(UUID uuid, ItemStack item);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.eternalcode.combat.fight.drop;
|
||||
|
||||
public interface DropModifier {
|
||||
|
||||
DropType getDropType();
|
||||
|
||||
DropResult modifyDrop(Drop drop);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.eternalcode.combat.fight.drop;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public record DropResult(
|
||||
List<ItemStack> droppedItems,
|
||||
List<ItemStack> removedItems,
|
||||
int droppedExp
|
||||
) {
|
||||
|
||||
@Override
|
||||
public List<ItemStack> droppedItems() {
|
||||
return Collections.unmodifiableList(this.droppedItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> removedItems() {
|
||||
return Collections.unmodifiableList(this.removedItems);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.eternalcode.combat.fight.drop;
|
||||
|
||||
public interface DropService {
|
||||
|
||||
DropResult modify(DropType dropType, Drop drop);
|
||||
|
||||
void registerModifier(DropModifier dropModifier);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.eternalcode.combat.drop;
|
||||
package com.eternalcode.combat.fight.drop;
|
||||
|
||||
public enum DropType {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.eternalcode.combat.fight.effect;
|
||||
|
||||
import java.util.List;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
/**
|
||||
* Manages custom potion effects on players during combat.
|
||||
* Active effects before combat are stored and restored after combat ends.
|
||||
*/
|
||||
public interface FightEffectService {
|
||||
|
||||
void removeCustomEffect(Player player, PotionEffectType type, Integer amplifier);
|
||||
|
||||
void applyCustomEffect(Player player, PotionEffectType type, Integer amplifier);
|
||||
|
||||
List<PotionEffect> getCurrentEffects(Player player);
|
||||
|
||||
void clearStoredEffects(Player player);
|
||||
|
||||
void restoreActiveEffects(Player player);
|
||||
|
||||
void storeActiveEffect(Player player, PotionEffect effect);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.eternalcode.combat.fight.event;
|
||||
|
||||
public enum CancelTagReason {
|
||||
|
||||
TAGOUT,
|
||||
PERMISSION_BYPASS,
|
||||
ADMIN,
|
||||
CREATIVE_MODE,
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.eternalcode.combat.fight.event;
|
||||
|
||||
public enum CauseOfTag {
|
||||
|
||||
/**
|
||||
* The player was tagged in combat as a result of an attack by another player.
|
||||
*/
|
||||
PLAYER,
|
||||
|
||||
/**
|
||||
* The player was tagged in combat due to an interaction with a non-player entity
|
||||
* (e.g., mobs or environmental damage).
|
||||
*/
|
||||
NON_PLAYER,
|
||||
|
||||
/**
|
||||
* A command was executed to apply a combat tag to the player.
|
||||
*/
|
||||
COMMAND,
|
||||
|
||||
/**
|
||||
* Crystal or anchor explosion caused the tag.
|
||||
*/
|
||||
CRYSTAL,
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
CUSTOM
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.eternalcode.combat.fight.event;
|
||||
|
||||
public enum CauseOfUnTag {
|
||||
/**
|
||||
* The combat tag expired after a set duration.
|
||||
*/
|
||||
TIME_EXPIRED,
|
||||
|
||||
/**
|
||||
* The player died, which resulted in the removal of the combat tag.
|
||||
*/
|
||||
DEATH,
|
||||
|
||||
/**
|
||||
* The player was killed by another player, causing the combat tag to be removed.
|
||||
*/
|
||||
DEATH_BY_PLAYER,
|
||||
|
||||
/**
|
||||
* The player logged out, triggering the removal of the combat tag.
|
||||
*/
|
||||
LOGOUT,
|
||||
|
||||
/**
|
||||
* A command was executed to remove the player's combat tag.
|
||||
*/
|
||||
COMMAND,
|
||||
|
||||
/**
|
||||
* The attacker released the player, ending the combat and removing the tag.
|
||||
*/
|
||||
ATTACKER_RELEASE,
|
||||
|
||||
/**
|
||||
* A custom cause, typically defined by external plugins or systems.
|
||||
*/
|
||||
CUSTOM
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.eternalcode.combat.fight.event;
|
||||
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* This event is triggered when a player is tagged in a fight.
|
||||
*/
|
||||
public class FightTagEvent extends Event implements Cancellable {
|
||||
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private final UUID player;
|
||||
private final CauseOfTag cause;
|
||||
private final UUID tagger;
|
||||
private boolean cancelled = false;
|
||||
private CancelTagReason cancelReason;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #FightTagEvent(UUID, CauseOfTag, UUID)} instead, which also carries the tagger.
|
||||
*/
|
||||
@Deprecated
|
||||
@ApiStatus.ScheduledForRemoval
|
||||
public FightTagEvent(UUID player, CauseOfTag cause) {
|
||||
this(player, cause, null);
|
||||
}
|
||||
|
||||
public FightTagEvent(UUID player, CauseOfTag cause, @Nullable UUID tagger) {
|
||||
super(false);
|
||||
|
||||
this.player = player;
|
||||
this.cause = cause;
|
||||
this.tagger = tagger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UUID of the player who was tagged.
|
||||
*
|
||||
* @return The UUID of the tagged player.
|
||||
*/
|
||||
public UUID getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the reason why the player was tagged.
|
||||
*
|
||||
* @return The cause of the combat tag as a {@link CauseOfTag}.
|
||||
*/
|
||||
public CauseOfTag getCause() {
|
||||
return this.cause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UUID of the player who caused this tag, if any.
|
||||
*
|
||||
* @return The UUID of the tagger, or {@code null} if the tag was not caused by another player.
|
||||
*/
|
||||
@Nullable
|
||||
public UUID getTagger() {
|
||||
return this.tagger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the reason why the event was cancelled.
|
||||
*
|
||||
* @return The cancel reason.
|
||||
*/
|
||||
public CancelTagReason getCancelReason() {
|
||||
return this.cancelReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reason why the event was cancelled.
|
||||
*
|
||||
* @param cancelReason The cancel reason.
|
||||
*/
|
||||
public void setCancelReason(CancelTagReason cancelReason) {
|
||||
this.cancelReason = cancelReason;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.eternalcode.combat.fight.event;
|
||||
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* This event is triggered when a player is untagged during a fight.
|
||||
*/
|
||||
public class FightUntagEvent extends Event implements Cancellable {
|
||||
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
private final UUID player;
|
||||
private final CauseOfUnTag cause;
|
||||
private boolean cancelled;
|
||||
|
||||
public FightUntagEvent(UUID player, CauseOfUnTag cause) {
|
||||
super(false);
|
||||
|
||||
this.player = player;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UUID of the player who was untagged.
|
||||
*
|
||||
* @return The UUID of the untagged player.
|
||||
*/
|
||||
public UUID getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the reason for why the player was untagged.
|
||||
*
|
||||
* @return The cause of untagging as a {@link CauseOfUnTag}.
|
||||
*/
|
||||
public CauseOfUnTag getCause() {
|
||||
return this.cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.eternalcode.combat.fight.pearl;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public interface PearlService {
|
||||
|
||||
boolean shouldCancelEvent(UUID playerId);
|
||||
|
||||
Duration getRemainingDelay(UUID uuid);
|
||||
|
||||
boolean hasDelay(UUID uuid);
|
||||
|
||||
void handleDelay(Player uuid);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.eternalcode.combat.fight.spear;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface SpearService {
|
||||
boolean isOnCooldown(UUID player);
|
||||
void saveCooldown(UUID player);
|
||||
Duration getRemainingCooldown(UUID player);
|
||||
void removeCooldown(UUID player);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.eternalcode.combat.fight.tagout;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface FightTagOutService {
|
||||
|
||||
boolean isTaggedOut(UUID player);
|
||||
|
||||
void unTagOut(UUID player);
|
||||
|
||||
void tagOut(UUID player, Duration duration);
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.eternalcode.combat.region;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
public interface Region {
|
||||
|
||||
Point getCenter();
|
||||
|
||||
Location getMin();
|
||||
|
||||
Location getMax();
|
||||
|
||||
default boolean contains(Location location) {
|
||||
return this.contains(location.getX(), location.getY(), location.getZ());
|
||||
}
|
||||
|
||||
default boolean contains(double x, double y, double z) {
|
||||
Location min = this.getMin();
|
||||
Location max = this.getMax();
|
||||
return x >= min.getX() && x < max.getX()
|
||||
&& y >= min.getY() && y < max.getY()
|
||||
&& z >= min.getZ() && z < max.getZ();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.eternalcode.combat.region;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
|
||||
public interface RegionProvider {
|
||||
|
||||
Optional<Region> getRegion(Location location);
|
||||
|
||||
default boolean isInRegion(Location location) {
|
||||
return this.getRegion(location).isPresent();
|
||||
}
|
||||
|
||||
Collection<Region> getRegions(World world);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
|
||||
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("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"))
|
||||
|
||||
// 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-serdes-commons:${Versions.OKAERI_CONFIGS_SERDES_COMMONS}")
|
||||
implementation("eu.okaeri:okaeri-configs-serdes-bukkit:${Versions.OKAERI_CONFIGS_SERDES_BUKKIT}")
|
||||
|
||||
// XSeries
|
||||
implementation("com.github.cryptomorin:XSeries:${Versions.XSERIES}")
|
||||
|
||||
// bstats
|
||||
implementation("org.bstats:bstats-bukkit:${Versions.B_STATS_BUKKIT}")
|
||||
|
||||
// caffeine
|
||||
implementation("com.github.ben-manes.caffeine:caffeine:${Versions.CAFFEINE}")
|
||||
|
||||
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}")
|
||||
|
||||
// PlaceholderAPI
|
||||
compileOnly("me.clip:placeholderapi:${Versions.PLACEHOLDER_API}")
|
||||
|
||||
// Lands
|
||||
compileOnly("com.github.angeschossen:LandsAPI:${Versions.LANDS_API}")
|
||||
|
||||
// GriefPrevention
|
||||
compileOnly("com.github.GriefPrevention:GriefPrevention:${Versions.GRIEF_PREVENTION}") {
|
||||
isTransitive = false
|
||||
}
|
||||
|
||||
// Multification
|
||||
implementation("com.eternalcode:multification-paper:${Versions.MULTIFICATION}")
|
||||
implementation("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION}")
|
||||
compileOnly("com.github.retrooper:packetevents-spigot:${Versions.PACKETS_EVENTS}")
|
||||
}
|
||||
|
||||
paper {
|
||||
main = "com.eternalcode.combat.CombatPlugin"
|
||||
authors = listOf("EternalCodeTeam")
|
||||
apiVersion = "1.19"
|
||||
prefix = "EternalCombat"
|
||||
name = "EternalCombat"
|
||||
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("GriefPrevention") {
|
||||
required = false
|
||||
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
|
||||
}
|
||||
|
||||
register("PlaceholderAPI") {
|
||||
required = false
|
||||
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
named("generatePaperPluginDescription") {
|
||||
notCompatibleWithConfigurationCache("The plugin-yml paper generator reads Task.project during execution.")
|
||||
}
|
||||
|
||||
runPaper.folia.registerTask {
|
||||
minecraftVersion("26.3")
|
||||
|
||||
downloadPlugins.modrinth("PacketEvents", "${Versions.PACKETEVENTS}+spigot")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
archiveFileName.set("EternalCombat v${project.version}.jar")
|
||||
|
||||
exclude(
|
||||
"org/intellij/lang/annotations/**",
|
||||
"org/jetbrains/annotations/**",
|
||||
"net/kyori/**",
|
||||
"META-INF/**",
|
||||
"kotlin/**",
|
||||
"javax/**",
|
||||
"org/checkerframework/**",
|
||||
"com/google/errorprone/**",
|
||||
"com/google/gson/**"
|
||||
)
|
||||
|
||||
val prefix = "com.eternalcode.combat.libs"
|
||||
listOf(
|
||||
"eu.okaeri",
|
||||
"org.bstats",
|
||||
"org.yaml",
|
||||
"dev.rollczi.litecommands",
|
||||
"com.eternalcode.gitcheck",
|
||||
"org.json.simple",
|
||||
"com.github.benmanes.caffeine",
|
||||
"com.eternalcode.commons",
|
||||
"com.eternalcode.multification",
|
||||
"com.github.cryptomorin",
|
||||
"com.cryptomorin",
|
||||
).forEach { pack ->
|
||||
relocate(pack, "$prefix.$pack")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
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.config.ConfigService;
|
||||
import com.eternalcode.combat.config.implementation.PluginConfig;
|
||||
import com.eternalcode.combat.fight.crystalpvp.EndCrystalController;
|
||||
import com.eternalcode.combat.fight.crystalpvp.RespawnAnchorController;
|
||||
import com.eternalcode.combat.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.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.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.PearlController;
|
||||
import com.eternalcode.combat.fight.pearl.PearlService;
|
||||
import com.eternalcode.combat.fight.pearl.PearlServiceImpl;
|
||||
import com.eternalcode.combat.fight.spear.SpearLungeController;
|
||||
import com.eternalcode.combat.fight.spear.SpearService;
|
||||
import com.eternalcode.combat.fight.spear.SpearServiceImpl;
|
||||
import com.eternalcode.combat.fight.tagout.FightTagOutCommand;
|
||||
import com.eternalcode.combat.fight.tagout.FightTagOutController;
|
||||
import com.eternalcode.combat.fight.tagout.FightTagOutService;
|
||||
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 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;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
|
||||
|
||||
private static final String FALLBACK_PREFIX = "eternalcombat";
|
||||
private static final int BSTATS_METRICS_ID = 17803;
|
||||
|
||||
private FightManager fightManager;
|
||||
private PearlService pearlService;
|
||||
private TridentService tridentService;
|
||||
private FightTagOutService fightTagOutService;
|
||||
private FightEffectService fightEffectService;
|
||||
|
||||
private DropService dropService;
|
||||
private DropKeepInventoryService dropKeepInventoryService;
|
||||
|
||||
private RegionProvider regionProvider;
|
||||
|
||||
private LiteCommands<CommandSender> liteCommands;
|
||||
private boolean apiInitialized;
|
||||
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Stopwatch started = Stopwatch.createStarted();
|
||||
Server server = this.getServer();
|
||||
|
||||
File dataFolder = this.getDataFolder();
|
||||
|
||||
ConfigService configService = new ConfigService();
|
||||
|
||||
EventManager eventManager = new EventManager(this);
|
||||
PluginConfig pluginConfig = configService.create(PluginConfig.class, new File(dataFolder, "config.yml"));
|
||||
|
||||
MinecraftScheduler scheduler = CombatSchedulerAdapter.getAdaptiveScheduler(this);
|
||||
|
||||
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();
|
||||
|
||||
LogoutService logoutService = new LogoutService();
|
||||
|
||||
this.dropService = new DropServiceImpl();
|
||||
this.dropKeepInventoryService = new DropKeepInventoryServiceImpl();
|
||||
|
||||
UpdaterService updaterService = new UpdaterService(this.getDescription());
|
||||
|
||||
MiniMessage miniMessage = MiniMessage.builder()
|
||||
.postProcessor(new AdventureLegacyColorPostProcessor())
|
||||
.preProcessor(new AdventureLegacyColorPreProcessor())
|
||||
.build();
|
||||
|
||||
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();
|
||||
BorderService borderService = new BorderServiceImpl(scheduler, server, regionProvider, eventManager, () -> pluginConfig.border);
|
||||
KnockbackService knockbackService = new KnockbackService(pluginConfig, scheduler, regionProvider);
|
||||
|
||||
SpearService spearService = new SpearServiceImpl(pluginConfig.spear);
|
||||
|
||||
this.liteCommands = LiteBukkitFactory.builder(FALLBACK_PREFIX, this, server)
|
||||
.message(LiteBukkitMessages.PLAYER_NOT_FOUND, pluginConfig.messagesSettings.playerNotFound)
|
||||
.message(LiteBukkitMessages.PLAYER_ONLY, pluginConfig.messagesSettings.admin.onlyForPlayers)
|
||||
|
||||
.invalidUsage(new InvalidUsageHandlerImpl(pluginConfig, noticeService))
|
||||
.missingPermission(new MissingPermissionHandlerImpl(pluginConfig, noticeService))
|
||||
|
||||
.commands(
|
||||
new FightTagCommand(this.fightManager, noticeService, pluginConfig),
|
||||
new FightTagOutCommand(this.fightTagOutService, noticeService, pluginConfig),
|
||||
new EternalCombatReloadCommand(configService, noticeService)
|
||||
)
|
||||
|
||||
.extension(new FoliaExtension(this))
|
||||
|
||||
.result(Notice.class, (invocation, result, chain) -> noticeService.create()
|
||||
.viewer(invocation.sender())
|
||||
.notice(result)
|
||||
.send())
|
||||
|
||||
.build();
|
||||
|
||||
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(pluginConfig.drop),
|
||||
new PlayersHealthDropModifier(pluginConfig.drop, logoutService)
|
||||
).forEach(this.dropService::registerModifier);
|
||||
|
||||
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, 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 EndCrystalController(this, this.fightManager, pluginConfig),
|
||||
new RespawnAnchorController(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);
|
||||
|
||||
new SpearLungeController(this, fightManager, spearService, pluginConfig, noticeService);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (this.liteCommands != null) {
|
||||
this.liteCommands.unregister();
|
||||
}
|
||||
|
||||
if (this.fightManager != null) {
|
||||
this.fightManager.untagAll();
|
||||
}
|
||||
|
||||
if (this.apiInitialized) {
|
||||
EternalCombatProvider.deinitialize();
|
||||
this.apiInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FightManager getFightManager() {
|
||||
return this.fightManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegionProvider getRegionProvider() {
|
||||
return this.regionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PearlService getFightPearlService() {
|
||||
return this.pearlService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FightTagOutService getFightTagOutService() {
|
||||
return this.fightTagOutService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FightEffectService getFightEffectService() {
|
||||
return this.fightEffectService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DropService getDropService() {
|
||||
return this.dropService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DropKeepInventoryService getDropKeepInventoryService() {
|
||||
return this.dropKeepInventoryService;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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 CombatSchedulerAdapter() {
|
||||
throw new UnsupportedOperationException("This is an utility class and cannot be instantiated");
|
||||
}
|
||||
|
||||
public static MinecraftScheduler getAdaptiveScheduler(Plugin plugin) {
|
||||
Logger logger = plugin.getLogger();
|
||||
|
||||
if (FoliaChecker.isFolia()) {
|
||||
logger.info("» Detected Folia environment. Using FoliaScheduler.");
|
||||
return new FoliaSchedulerImpl(plugin);
|
||||
}
|
||||
|
||||
logger.info("» Detected Bukkit/Paper environment. Using BukkitScheduler.");
|
||||
return new BukkitSchedulerImpl(plugin);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.eternalcode.combat;
|
||||
|
||||
import com.eternalcode.combat.config.ConfigService;
|
||||
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;
|
||||
import dev.rollczi.litecommands.annotations.command.Command;
|
||||
import dev.rollczi.litecommands.annotations.context.Context;
|
||||
import dev.rollczi.litecommands.annotations.execute.Execute;
|
||||
import dev.rollczi.litecommands.annotations.permission.Permission;
|
||||
import java.time.Duration;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
@Command(name = "combatlog", aliases = "combat")
|
||||
public class EternalCombatReloadCommand {
|
||||
|
||||
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 NoticeService noticeService;
|
||||
|
||||
public EternalCombatReloadCommand(ConfigService configService, NoticeService noticeService) {
|
||||
this.configService = configService;
|
||||
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.noticeService.create()
|
||||
.viewer(sender)
|
||||
.notice(RELOAD_MESSAGE)
|
||||
.placeholder("{TIME}", String.valueOf(elapsed.toMillis()))
|
||||
.send();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.eternalcode.combat;
|
||||
|
||||
public final class FoliaChecker {
|
||||
|
||||
private static final String FOLIA_CLASS = "io.papermc.paper.threadedregions.RegionizedServer";
|
||||
private static final boolean IS_FOLIA = detectFoliaEnvironment();
|
||||
|
||||
private FoliaChecker() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
|
||||
public static boolean isFolia() {
|
||||
return IS_FOLIA;
|
||||
}
|
||||
|
||||
private static boolean detectFoliaEnvironment() {
|
||||
try {
|
||||
Class.forName(FOLIA_CLASS);
|
||||
return true;
|
||||
}
|
||||
catch (ClassNotFoundException exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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.CompositeRegionProvider;
|
||||
import com.eternalcode.combat.region.bukkit.DefaultRegionProvider;
|
||||
import com.eternalcode.combat.region.RegionProvider;
|
||||
import com.eternalcode.combat.region.griefprevention.GriefPreventionRegionProvider;
|
||||
import com.eternalcode.combat.region.worldguard.WorldGuardRegionProvider;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
public class BridgeService {
|
||||
|
||||
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 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(Server server) {
|
||||
List<RegionProvider> providers = new ArrayList<>();
|
||||
|
||||
this.initialize(
|
||||
"Lands",
|
||||
() -> providers.add(new LandsRegionProvider(plugin)),
|
||||
() -> this.logger.warning("Lands not found; skipping LandsRegionProvider.")
|
||||
);
|
||||
|
||||
this.initialize(
|
||||
"WorldGuard",
|
||||
() -> providers.add( new WorldGuardRegionProvider(this.config)),
|
||||
() -> this.logger.warning("WorldGuard not found; skipping WorldGuardRegionProvider.")
|
||||
);
|
||||
|
||||
this.initialize(
|
||||
"GriefPrevention",
|
||||
() -> providers.add(new GriefPreventionRegionProvider(this.config)),
|
||||
() -> this.logger.warning("GriefPrevention not found; skipping GriefPreventionRegionProvider.")
|
||||
);
|
||||
|
||||
if (providers.isEmpty()) {
|
||||
providers.add(new DefaultRegionProvider(this.config.regions.restrictedRegionRadius));
|
||||
this.logger.warning("No region plugin found; using DefaultRegionProvider.");
|
||||
}
|
||||
|
||||
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 onFailure) {
|
||||
if (this.pluginManager.isPluginEnabled(pluginName)) {
|
||||
initializer.initialize();
|
||||
this.logger.info("Initialized " + pluginName + " bridge.");
|
||||
}
|
||||
else {
|
||||
onFailure.run();
|
||||
}
|
||||
}
|
||||
|
||||
public RegionProvider getRegionProvider() {
|
||||
return this.regionProvider;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
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;
|
||||
import com.eternalcode.commons.time.DurationParser;
|
||||
import java.util.Optional;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getAuthor() {
|
||||
return this.plugin.getDescription().getAuthors().get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getVersion() {
|
||||
return this.plugin.getDescription().getVersion();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.eternalcode.combat.config;
|
||||
|
||||
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.serdes.SerdesBukkit;
|
||||
|
||||
import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer;
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class ConfigService {
|
||||
|
||||
private final Set<OkaeriConfig> configs = new HashSet<>();
|
||||
|
||||
public <T extends OkaeriConfig> T create(Class<T> config, File file) {
|
||||
T configFile = ConfigManager.create(config);
|
||||
|
||||
YamlSnakeYamlConfigurer configurer = new YamlSnakeYamlConfigurer();
|
||||
NoticeResolverRegistry noticeRegistry = NoticeResolverDefaults.createRegistry()
|
||||
.registerResolver(new SoundAdventureResolver());
|
||||
|
||||
configFile.withConfigurer(configurer,
|
||||
new SerdesCommons(),
|
||||
new SerdesBukkit(),
|
||||
new MultificationSerdesPack(noticeRegistry),
|
||||
new DefaultSerdesPack()
|
||||
);
|
||||
|
||||
configFile.withBindFile(file);
|
||||
configFile.withRemoveOrphans(true);
|
||||
configFile.saveDefaults();
|
||||
configFile.load(true);
|
||||
|
||||
this.configs.add(configFile);
|
||||
|
||||
return configFile;
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
for (OkaeriConfig config : this.configs) {
|
||||
config.load();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||