├── .editorconfig
├── .gitattributes
├── .github
├── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ └── feature-request.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── gradle.yml
│ └── publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── assets
├── border.gif
├── combatlog.gif
└── readme-banner.png
├── buildSrc
├── build.gradle.kts
└── src
│ └── main
│ └── kotlin
│ ├── Versions.kt
│ ├── eternalcombat-java-unit-test.gradle.kts
│ ├── eternalcombat-java.gradle.kts
│ ├── eternalcombat-publish.gradle.kts
│ └── eternalcombat-repositories.gradle.kts
├── config
└── checkstyle
│ ├── checkstyle.xml
│ └── suppressions.xml
├── eternalcombat-api
├── build.gradle.kts
└── src
│ └── main
│ └── java
│ └── com
│ └── eternalcode
│ └── combat
│ ├── EternalCombatApi.java
│ ├── EternalCombatProvider.java
│ ├── fight
│ ├── FightManager.java
│ ├── FightTag.java
│ ├── drop
│ │ ├── Drop.java
│ │ ├── DropKeepInventoryService.java
│ │ ├── DropModifier.java
│ │ ├── DropResult.java
│ │ ├── DropService.java
│ │ └── DropType.java
│ ├── effect
│ │ └── FightEffectService.java
│ ├── event
│ │ ├── CancelTagReason.java
│ │ ├── CauseOfTag.java
│ │ ├── CauseOfUnTag.java
│ │ ├── FightTagEvent.java
│ │ └── FightUntagEvent.java
│ ├── pearl
│ │ └── FightPearlService.java
│ └── tagout
│ │ └── FightTagOutService.java
│ └── region
│ ├── Region.java
│ └── RegionProvider.java
├── eternalcombat-plugin
├── build.gradle.kts
└── src
│ └── main
│ └── java
│ └── com
│ └── eternalcode
│ └── combat
│ ├── CombatPlugin.java
│ ├── EternalCombatReloadCommand.java
│ ├── WhitelistBlacklistMode.java
│ ├── border
│ ├── BorderActivePointsIndex.java
│ ├── BorderLazyResult.java
│ ├── BorderPoint.java
│ ├── BorderResult.java
│ ├── BorderService.java
│ ├── BorderServiceImpl.java
│ ├── BorderSettings.java
│ ├── BorderTrigger.java
│ ├── BorderTriggerController.java
│ ├── BorderTriggerIndex.java
│ ├── BorderTriggerIndexBucket.java
│ ├── animation
│ │ ├── BorderColorUtil.java
│ │ ├── block
│ │ │ ├── BlockSettings.java
│ │ │ ├── BlockType.java
│ │ │ ├── BlockTypeTransformer.java
│ │ │ ├── BorderBlockController.java
│ │ │ ├── BorderBlockRainbowUtil.java
│ │ │ ├── ChunkCache.java
│ │ │ └── ChunkLocation.java
│ │ └── particle
│ │ │ ├── ParticleColor.java
│ │ │ ├── ParticleColorTransformer.java
│ │ │ ├── ParticleController.java
│ │ │ ├── ParticleSettings.java
│ │ │ └── ParticleTypeTransformer.java
│ └── event
│ │ ├── BorderHideAsyncEvent.java
│ │ └── BorderShowAsyncEvent.java
│ ├── bridge
│ ├── BridgeInitializer.java
│ ├── BridgeService.java
│ └── placeholder
│ │ └── FightTagPlaceholder.java
│ ├── config
│ ├── ConfigService.java
│ └── implementation
│ │ ├── AdminSettings.java
│ │ ├── BlockPlacementSettings.java
│ │ ├── CombatSettings.java
│ │ ├── CommandSettings.java
│ │ ├── MessagesSettings.java
│ │ ├── PluginConfig.java
│ │ └── RegionSettings.java
│ ├── event
│ ├── DynamicListener.java
│ └── EventManager.java
│ ├── fight
│ ├── FightManagerImpl.java
│ ├── FightTagCommand.java
│ ├── FightTagImpl.java
│ ├── FightTask.java
│ ├── controller
│ │ ├── FightActionBlockerController.java
│ │ ├── FightMessageController.java
│ │ ├── FightTagController.java
│ │ └── FightUnTagController.java
│ ├── drop
│ │ ├── DropController.java
│ │ ├── DropKeepInventoryServiceImpl.java
│ │ ├── DropServiceImpl.java
│ │ ├── DropSettings.java
│ │ └── impl
│ │ │ ├── PercentDropModifier.java
│ │ │ └── PlayersHealthDropModifier.java
│ ├── effect
│ │ ├── FightEffectController.java
│ │ ├── FightEffectServiceImpl.java
│ │ └── FightEffectSettings.java
│ ├── knockback
│ │ ├── KnockbackOutsideRegionGenerator.java
│ │ ├── KnockbackRegionController.java
│ │ ├── KnockbackService.java
│ │ └── KnockbackSettings.java
│ ├── logout
│ │ ├── Logout.java
│ │ ├── LogoutController.java
│ │ └── LogoutService.java
│ ├── pearl
│ │ ├── FightPearlController.java
│ │ ├── FightPearlServiceImpl.java
│ │ └── FightPearlSettings.java
│ └── tagout
│ │ ├── FightTagOutCommand.java
│ │ ├── FightTagOutController.java
│ │ └── FightTagOutServiceImpl.java
│ ├── handler
│ ├── InvalidUsageHandlerImpl.java
│ └── MissingPermissionHandlerImpl.java
│ ├── notification
│ └── NoticeService.java
│ ├── region
│ ├── DefaultRegionProvider.java
│ └── WorldGuardRegionProvider.java
│ ├── updater
│ ├── UpdaterNotificationController.java
│ └── UpdaterService.java
│ └── util
│ ├── DurationUtil.java
│ ├── InventoryUtil.java
│ ├── MathUtil.java
│ └── RemoveItemResult.java
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── renovate.json
└── settings.gradle.kts
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset = utf-8
3 | end_of_line = lf
4 | insert_final_newline = true
5 | indent_style = space
6 | indent_size = 4
7 |
8 | [*.bat]
9 | end_of_line = crlf
10 |
11 | [*.yml]
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
3 | *.sh text eol=lf
4 | gradlew text eol=lf
5 | *.bat text eol=crlf
6 |
7 | *.jar binary
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @EternalCodeTeam/eternalcodeteam-maintainers
2 | /.github/CODEOWNERS @EternalCodeTeam/eternalcodeteam-maintainers
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | https://discord.gg/FQ7jmGBd6c.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to EternalCombat
2 |
3 | We welcome all contributions to the EternalCombat project!
4 |
5 | ### How to Contribute
6 |
7 | 1. Fork the repository.
8 | 2. Create a new branch for your feature or bug fix.
9 | 3. Make your changes, and test them thoroughly.
10 | 4. Commit your changes and push them to your fork.
11 | 5. Submit a pull request to the main repository.
12 |
13 | ### Code of Conduct
14 |
15 | We expect all contributors to follow our [code of conduct](CODE_OF_CONDUCT.md).
16 |
17 | ### Reporting Issues
18 |
19 | If you find an issue with the plugin, please report it in
20 | the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). Please provide as much information as
21 | possible, including the version of Minecraft and the plugin you are using, as well as any error messagesSettings or logs.
22 |
23 | ### Compatibility
24 |
25 | CombatLog has been tested on versions 1.17.1 - 1.19.3, but probably works on most versions of Minecraft. If you find
26 | compatibility issues, please report them in the Issues tab. CombatLog uses java 11+, so you need to have it installed on
27 | your server.
28 |
29 | ### Submitting a Pull Request
30 |
31 | When submitting a pull request, please make sure to follow these guidelines:
32 |
33 | - Make sure that your code adheres to the plugin's existing coding style.
34 | - Test your changes thoroughly before submitting the pull request.
35 | - If your pull request is related to an existing issue, please reference the issue in your pull request.
36 | - Thank you for your contributions to the EternalCombat project!
37 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a bug report to help us improve EternalCombat <3
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 |
16 | 1. Go to '...'
17 | 2. Click on '....'
18 | 3. Scroll down to '....'
19 | 4. See error
20 |
21 | **Expected behavior**
22 | A clear and concise description of what you expected to happen.
23 |
24 | **Screenshots**
25 | If applicable, add screenshots to help explain your problem.
26 |
27 | **Platform (please complete the following information):**
28 |
29 | - Java Version: [e.g. JDK Temurin 17]
30 | - Server software, ex. PaperMC 1.19.3 build #1
31 |
32 | **Additional notes**
33 | Add any other context about the problem here.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for EternalCombat <3
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **Please describe the changes made by this PR and why they need to be merged:**
--------------------------------------------------------------------------------
/.github/workflows/gradle.yml:
--------------------------------------------------------------------------------
1 | name: Java CI with Gradle
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | build:
13 |
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v4
19 | - name: Set up JDK 17
20 | uses: actions/setup-java@v4
21 | with:
22 | java-version: '17'
23 | distribution: 'temurin'
24 | - name: Cache Gradle
25 | uses: actions/cache@v4
26 | with:
27 | path: ~/.gradle/caches
28 | key: >-
29 | ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*',
30 | '**/gradle-wrapper.properties') }}
31 | restore-keys: '${{ runner.os }}-gradle-'
32 | - name: Make gradlew executable
33 | run: chmod +x gradlew
34 | - name: Build with Gradle
35 | uses: gradle/gradle-build-action@093dfe9d598ec5a42246855d09b49dc76803c005
36 | with:
37 | arguments: shadowJar
38 | - name: Upload a Build Artifact
39 | uses: actions/upload-artifact@v4
40 | with:
41 | name: 'Successfully build EternalCombat'
42 | path: eternalcombat-plugin/build/libs/*.jar
43 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish EternalCombat API
2 | on:
3 | release:
4 | branches: [ master ]
5 | types: [ published ]
6 | jobs:
7 | publish:
8 | runs-on: ubuntu-latest
9 | environment: deployment
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v4
13 |
14 | - name: Set up JDK
15 | uses: actions/setup-java@v4
16 | with:
17 | distribution: 'temurin'
18 | java-version: 17
19 | cache: 'gradle'
20 |
21 | - name: Grant execute permission for gradlew
22 | run: chmod +x gradlew
23 |
24 | - name: Publish with Gradle
25 | run: ./gradlew clean build eternalcombat-api:publish
26 | env:
27 | ETERNAL_CODE_MAVEN_USERNAME: ${{ secrets.ETERNAL_CODE_MAVEN_USERNAME }}
28 | ETERNAL_CODE_MAVEN_PASSWORD: ${{ secrets.ETERNAL_CODE_MAVEN_PASSWORD }}
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .classpath
2 | .idea
3 | .gradle
4 | .project
5 | .settings
6 | bin
7 | build
8 | run
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 
4 |
5 | [](https://www.spigotmc.org/resources/eternalcombat-%E2%9C%94%EF%B8%8F-enchance-your-combat-system-with-eternalcombat.109056/)
6 | [](https://modrinth.com/plugin/eternalcombat)
7 | [](https://hangar.papermc.io/EternalCodeTeam/eternalcombat)
8 |
9 | [](https://discord.com/invite/FQ7jmGBd6c)
10 | [](https://docs.eternalcode.pl/eternalcombat/introduction)
11 | [](https://bstats.org/plugin/bukkit/EternalCombat/17803)
12 |
13 |
14 | ### Information
15 |
16 | EternalCombat 2.0 has been tested on Minecraft versions **1.17.1 to 1.21.4**, but it should work seamlessly on most
17 | other versions too.
18 | If you run into any compatibility issues, please let us know in
19 | the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues).
20 | The plugin requires **Java 17 or later**, so
21 | ensure your server is ready.
22 | Ready for action?
23 | Install EternalCombat and dive in now!
24 |
25 | ### How Does EternalCombat Work?
26 |
27 | Take your server’s PvP experience to epic heights with **EternalCombat 2.0**! Our robust combat logging system ensures
28 | fair, heart-pounding battles that keep players on their toes. Here’s a rundown of what makes it stand out:
29 |
30 | - **Combat Logging**
31 | No more dodging fights by logging out! Once players are in combat, they’re committed until the showdown ends. Watch it
32 | in action:
33 | 
34 |
35 | - **Spawn Protection (Configurable)**
36 | Stop players from fleeing to safety! Block access to spawn or safe zones during combat – tweak it to fit your server’s
37 | rules. See how it works:
38 | 
39 |
40 | - **Fully Customizable Combat**
41 | Tailor the combat experience to your liking with a ton of options! From disabling elytra to setting drop rates for
42 | defeated players, you’re in control. Here’s what you can tweak:
43 |
44 | | Feature | Description |
45 | |----------------------|-----------------------------------------------------------------|
46 | | Elytra & Inventory | Disable elytra or inventory access during combat. |
47 | | Commands | Whitelist or blacklist specific commands in combat. |
48 | | Damage & Projectiles | Customize damage causes and projectile tagging settings. |
49 | | Ender Pearls | Add cooldowns to pearl usage in fights. |
50 | | Block Placement | Enable or disable block placement during combat. |
51 | | Drop Rates | Set a percentage of items dropped by defeated players. |
52 | | Temporary Effects | Apply special effects to players in combat for added intensity. |
53 | | And More! | Dive into the config for even more fine-tuning options! |
54 |
55 | Curious for more? Check out our quick and exciting YouTube presentation [here](https://youtu.be/5pELO5B0Hhk) to see
56 | EternalCombat in full swing and learn why it’s a game-changer for your server!
57 |
58 | ### Permissions for EternalCombat
59 |
60 | Control who can use EternalCombat’s powerful features with these permissions:
61 |
62 | | Permission | Description |
63 | |--------------------------------|--------------------------------------------------------------------------|
64 | | `eternalcombat.status` | Check a player’s combat status with `/combatlog status `. |
65 | | `eternalcombat.tag` | Start a fight between players using `/combatlog tag [player2]`. |
66 | | `eternalcombat.untag` | Remove a player from combat with `/combatlog untag `. |
67 | | `eternalcombat.reload` | Reload the plugin with `/combatlog reload`. |
68 | | `eternalcombat.receiveupdates` | Receive notifications about new plugin versions on join. |
69 |
70 | ## PlaceholderAPI
71 |
72 | EternalCombat 2.0 fully supports **PlaceholderAPI**, letting you integrate placeholders into other compatible plugins.
73 | Follow the [PlaceholderAPI instructions](https://wiki.placeholderapi.com/users/) to get started.
74 | Here are the available
75 | placeholders:
76 |
77 | | Placeholder | Description |
78 | |-------------------------------------|---------------------------------------------------------------|
79 | | `%eternalcombat_opponent%` | Returns the name of the player you’re fighting. |
80 | | `%eternalcombat_opponent_health%` | Returns the opponent’s health in `00.00` format. |
81 | | `%eternalcombat_remaining_seconds%` | Returns seconds remaining until the player exits combat. |
82 | | `%eternalcombat_remaining_millis%` | Returns milliseconds remaining until the player exits combat. |
83 |
84 | If a player isn’t in combat, placeholders return an empty string.
85 | If combat wasn’t triggered by another player,
86 | opponent-related placeholders will also return empty.
87 |
88 | ### Developer API
89 |
90 | #### 1. Add repository:
91 |
92 | With Gradle:
93 |
94 | ```kts
95 | maven {
96 | url = uri("https://repo.eternalcode.pl/releases")
97 | }
98 | ```
99 |
100 | With Maven:
101 |
102 | ```xml
103 |
104 |
105 | eternalcode-reposilite-releases
106 | EternalCode Repository
107 | https://repo.eternalcode.pl/releases
108 |
109 | ```
110 |
111 | #### 2. Add dependency:
112 |
113 | With Gradle:
114 |
115 | ```kts
116 | compileOnly("com.eternalcode:eternalcombat-api:2.0.1")
117 | ```
118 |
119 | With Maven:
120 |
121 | ```xml
122 |
123 |
124 | com.eternalcode
125 | eternalcombat-api
126 | 2.0.1
127 | provided
128 |
129 | ```
130 |
131 | ### Contributing
132 |
133 | We’d love your help to make EternalCombat even better!
134 | Check out our [contributing guide](.github/CONTRIBUTING.md) for
135 | details on how to get involved and our [code of conduct](./.github/CODE_OF_CONDUCT.md).
136 |
137 | ### Reporting Issues
138 |
139 | Found a bug?
140 | Report it in the [Issues tab](https://github.com/eternalcodeteam/eternalcombat/issues).
141 | Please include as much detail as possible, like your Minecraft and plugin
142 | versions, along with any error messages or logs.
143 | Ready to transform your server’s combat experience?
144 | Download EternalCombat 2.0 now and let the battles begin!
145 |
--------------------------------------------------------------------------------
/assets/border.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EternalCodeTeam/EternalCombat/d6bf4e628c451b399f40d685b21003e8055f698d/assets/border.gif
--------------------------------------------------------------------------------
/assets/combatlog.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EternalCodeTeam/EternalCombat/d6bf4e628c451b399f40d685b21003e8055f698d/assets/combatlog.gif
--------------------------------------------------------------------------------
/assets/readme-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EternalCodeTeam/EternalCombat/d6bf4e628c451b399f40d685b21003e8055f698d/assets/readme-banner.png
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | }
4 |
5 | repositories {
6 | mavenCentral()
7 | gradlePluginPortal()
8 | }
9 |
10 | dependencies {
11 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.20")
12 | implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:8.3.5")
13 | implementation("net.minecrell:plugin-yml:0.6.0")
14 | implementation("xyz.jpenilla:run-task:2.3.1")
15 | }
16 |
17 | sourceSets {
18 | main {
19 | java.setSrcDirs(emptyList())
20 | groovy.setSrcDirs(emptyList())
21 | resources.setSrcDirs(emptyList())
22 | }
23 | test {
24 | java.setSrcDirs(emptyList())
25 | kotlin.setSrcDirs(emptyList())
26 | groovy.setSrcDirs(emptyList())
27 | resources.setSrcDirs(emptyList())
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/Versions.kt:
--------------------------------------------------------------------------------
1 | object Versions {
2 |
3 | const val SPIGOT_API = "1.17.1-R0.1-SNAPSHOT"
4 |
5 | const val JUNIT_JUPITER_API = "5.12.1"
6 | const val JUNIT_JUPITER_PARAMS = "5.12.1"
7 | const val JUNIT_JUPITER_ENGINE = "5.12.1"
8 |
9 | const val JETBRAINS_ANNOTATIONS = "26.0.2"
10 |
11 | const val ETERNALCODE_COMMONS = "1.1.6"
12 | const val MULTIFICATION = "1.2.1"
13 | const val PACKETS_EVENTS = "2.8.0"
14 |
15 | const val ADVENTURE_PLATFORM_BUKKIT = "4.4.0"
16 | const val ADVENTURE_API = "4.21.0"
17 |
18 | const val LITE_COMMANDS = "3.9.7"
19 | const val OKAERI_CONFIGS_YAML_BUKKIT = "5.0.6"
20 | const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.6"
21 | const val OKAERI_CONFIGS_SERDES_BUKKIT = "5.0.6"
22 |
23 | const val GIT_CHECK = "1.0.0"
24 |
25 | const val CAFFEINE = "3.2.0"
26 |
27 | const val B_STATS_BUKKIT = "3.1.0"
28 | const val WORLD_GUARD_BUKKIT = "7.0.9"
29 |
30 | const val PLACEHOLDER_API = "2.11.6"
31 | const val PAPERLIB = "1.0.8"
32 |
33 | }
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/eternalcombat-java-unit-test.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `java-library`
3 | }
4 |
5 | dependencies {
6 | testImplementation("org.spigotmc:spigot-api:${Versions.SPIGOT_API}")
7 | testImplementation("org.junit.jupiter:junit-jupiter-api:${Versions.JUNIT_JUPITER_API}")
8 | testImplementation("org.junit.jupiter:junit-jupiter-params:${Versions.JUNIT_JUPITER_PARAMS}")
9 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${Versions.JUNIT_JUPITER_ENGINE}")
10 | }
11 |
12 | tasks.test {
13 | useJUnitPlatform()
14 | }
15 |
16 | sourceSets.test {
17 | java.setSrcDirs(listOf("test"))
18 | resources.setSrcDirs(emptyList())
19 | }
20 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/eternalcombat-java.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `java-library`
3 | }
4 |
5 | group = "com.eternalcode"
6 | version = "2.0.1"
7 |
8 | tasks.compileJava {
9 | options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
10 | options.encoding = "UTF-8"
11 | options.release = 17
12 | }
13 |
14 | java {
15 | toolchain.languageVersion.set(JavaLanguageVersion.of(21))
16 | }
17 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/eternalcombat-publish.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `java-library`
3 | `maven-publish`
4 | }
5 |
6 | group = "com.eternalcode"
7 | version = "2.0.1"
8 |
9 | java {
10 | withSourcesJar()
11 | withJavadocJar()
12 | }
13 |
14 | publishing {
15 | publications {
16 | create("maven") {
17 | artifactId = "eternalcombat-api"
18 | from(project.components["java"])
19 | }
20 | }
21 |
22 | repositories {
23 | mavenLocal()
24 | maven {
25 | name = "eternalcodeReleases"
26 | url = uri("https://repo.eternalcode.pl/releases")
27 | credentials {
28 | username = System.getenv("ETERNAL_CODE_MAVEN_USERNAME")
29 | password = System.getenv("ETERNAL_CODE_MAVEN_PASSWORD")
30 | }
31 | }
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/eternalcombat-repositories.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `java-library`
3 | }
4 |
5 | repositories {
6 | mavenCentral()
7 |
8 | maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
9 | maven("https://papermc.io/repo/repository/maven-public/")
10 | maven("https://repo.eternalcode.pl/releases")
11 | maven("https://storehouse.okaeri.eu/repository/maven-public/")
12 | maven("https://repo.panda-lang.org/releases")
13 | maven("https://maven.enginehub.org/repo/")
14 | maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
15 | maven("https://repo.codemc.io/repository/maven-releases/")
16 | }
17 |
--------------------------------------------------------------------------------
/config/checkstyle/suppressions.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/eternalcombat-api/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `eternalcombat-java`
3 | `eternalcombat-repositories`
4 | `eternalcombat-publish`
5 | `eternalcombat-java-unit-test`
6 | }
7 |
8 | dependencies {
9 | // Spigot api
10 | compileOnlyApi("org.spigotmc:spigot-api:${Versions.SPIGOT_API}")
11 | api("org.jetbrains:annotations:${Versions.JETBRAINS_ANNOTATIONS}")
12 | }
13 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/EternalCombatApi.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat;
2 |
3 | import com.eternalcode.combat.fight.drop.DropKeepInventoryService;
4 | import com.eternalcode.combat.fight.FightManager;
5 | import com.eternalcode.combat.fight.drop.DropService;
6 | import com.eternalcode.combat.fight.effect.FightEffectService;
7 | import com.eternalcode.combat.fight.pearl.FightPearlService;
8 | import com.eternalcode.combat.region.RegionProvider;
9 | import com.eternalcode.combat.fight.tagout.FightTagOutService;
10 |
11 | public interface EternalCombatApi {
12 |
13 | FightManager getFightManager();
14 |
15 | RegionProvider getRegionProvider();
16 |
17 | FightPearlService getFightPearlService();
18 |
19 | FightTagOutService getFightTagOutService();
20 |
21 | FightEffectService getFightEffectService();
22 |
23 | DropService getDropService();
24 |
25 | DropKeepInventoryService getDropKeepInventoryService();
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/EternalCombatProvider.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat;
2 |
3 | public final class EternalCombatProvider {
4 |
5 | private static EternalCombatApi ETERNAL_COMBAT_API;
6 |
7 | public static EternalCombatApi provide() {
8 | if (ETERNAL_COMBAT_API == null) {
9 | throw new IllegalStateException("EternalCombatApi has not been initialized yet!");
10 | }
11 |
12 | return ETERNAL_COMBAT_API;
13 | }
14 |
15 | static void initialize(EternalCombatApi eternalCombatApi) {
16 | if (ETERNAL_COMBAT_API != null) {
17 | throw new IllegalStateException("EternalCombatApi has already been initialized!");
18 | }
19 |
20 | ETERNAL_COMBAT_API = eternalCombatApi;
21 | }
22 |
23 | static void deinitialize() {
24 | if (ETERNAL_COMBAT_API == null) {
25 | throw new IllegalStateException("EternalCombatApi has not been initialized yet!");
26 | }
27 |
28 | ETERNAL_COMBAT_API = null;
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightManager.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight;
2 |
3 | import com.eternalcode.combat.fight.event.CauseOfTag;
4 | import com.eternalcode.combat.fight.event.CauseOfUnTag;
5 | import com.eternalcode.combat.fight.event.FightTagEvent;
6 | import com.eternalcode.combat.fight.event.FightUntagEvent;
7 | import java.time.Duration;
8 | import java.util.Collection;
9 | import java.util.UUID;
10 | import org.jetbrains.annotations.ApiStatus;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | public interface FightManager {
14 |
15 | boolean isInCombat(UUID player);
16 |
17 | FightTag getTag(UUID target);
18 |
19 | Collection getFights();
20 |
21 | @ApiStatus.Experimental
22 | FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger);
23 |
24 | FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag);
25 |
26 | FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag);
27 |
28 | void untagAll();
29 | }
30 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightTag.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight;
2 |
3 | import java.time.Duration;
4 | import java.time.Instant;
5 | import java.util.UUID;
6 | import org.jetbrains.annotations.ApiStatus;
7 | import org.jetbrains.annotations.Nullable;
8 |
9 | public interface FightTag {
10 |
11 | @Nullable
12 | @ApiStatus.Experimental
13 | UUID getTagger();
14 |
15 | Duration getRemainingDuration();
16 |
17 | boolean isExpired();
18 |
19 | Instant getEndOfCombatLog();
20 |
21 | UUID getTaggedPlayer();
22 | }
23 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/Drop.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.drop;
2 |
3 | import org.bukkit.entity.Player;
4 | import org.bukkit.inventory.ItemStack;
5 | import org.jetbrains.annotations.NotNull;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collections;
10 | import java.util.List;
11 |
12 | public class Drop {
13 |
14 | private final Player player;
15 | private final Player killer;
16 |
17 | private final List droppedItems;
18 | private final int droppedExp;
19 |
20 | private Drop(Player player, Player killer, List droppedItems, int droppedExp) {
21 | this.player = player;
22 | this.killer = killer;
23 | this.droppedItems = droppedItems;
24 | this.droppedExp = droppedExp;
25 | }
26 |
27 | public List getDroppedItems() {
28 | return Collections.unmodifiableList(this.droppedItems);
29 | }
30 |
31 | public Player getPlayer() {
32 | return this.player;
33 | }
34 |
35 | public @Nullable Player getKiller() {
36 | return this.killer;
37 | }
38 |
39 | public boolean hasKiller() {
40 | return this.killer != null;
41 | }
42 |
43 | public int getDroppedExp() {
44 | return this.droppedExp;
45 | }
46 |
47 | public static Builder builder() {
48 | return new Builder();
49 | }
50 |
51 | public static class Builder {
52 |
53 | private Player player;
54 | private Player killer;
55 | private List droppedItems;
56 | private int droppedExp;
57 |
58 | public Builder player(@NotNull Player player) {
59 | this.player = player;
60 | return this;
61 | }
62 |
63 | public Builder killer(@Nullable Player killer) {
64 | this.killer = killer;
65 | return this;
66 | }
67 |
68 | public Builder droppedItems(@NotNull List droppedItems) {
69 | this.droppedItems = new ArrayList<>(droppedItems);
70 | return this;
71 | }
72 |
73 | public Builder droppedExp(int droppedExp) {
74 | this.droppedExp = droppedExp;
75 | return this;
76 | }
77 |
78 | public Drop build() {
79 | return new Drop(
80 | this.player,
81 | this.killer,
82 | this.droppedItems,
83 | this.droppedExp
84 | );
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropKeepInventoryService.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.drop;
2 |
3 | import java.util.List;
4 | import java.util.UUID;
5 | import org.bukkit.inventory.ItemStack;
6 |
7 | public interface DropKeepInventoryService {
8 |
9 | List nextItems(UUID uuid);
10 |
11 | boolean hasItems(UUID uuid);
12 |
13 | void addItems(UUID uuid, List item);
14 |
15 | void addItem(UUID uuid, ItemStack item);
16 | }
17 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropModifier.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.drop;
2 |
3 | public interface DropModifier {
4 |
5 | DropType getDropType();
6 |
7 | DropResult modifyDrop(Drop drop);
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropResult.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.drop;
2 |
3 | import org.bukkit.inventory.ItemStack;
4 |
5 | import java.util.Collections;
6 | import java.util.List;
7 |
8 | public record DropResult(
9 | List droppedItems,
10 | List removedItems,
11 | int droppedExp
12 | ) {
13 |
14 | @Override
15 | public List droppedItems() {
16 | return Collections.unmodifiableList(this.droppedItems);
17 | }
18 |
19 | @Override
20 | public List removedItems() {
21 | return Collections.unmodifiableList(this.removedItems);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropService.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.drop;
2 |
3 | public interface DropService {
4 |
5 | DropResult modify(DropType dropType, Drop drop);
6 |
7 | void registerModifier(DropModifier dropModifier);
8 | }
9 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropType.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.drop;
2 |
3 | public enum DropType {
4 |
5 | UNCHANGED,
6 | PERCENT,
7 | PLAYERS_HEALTH
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/effect/FightEffectService.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.effect;
2 |
3 | import java.util.List;
4 | import org.bukkit.entity.Player;
5 | import org.bukkit.potion.PotionEffect;
6 | import org.bukkit.potion.PotionEffectType;
7 |
8 | /**
9 | * Manages custom potion effects on players during combat.
10 | * Active effects before combat are stored and restored after combat ends.
11 | */
12 | public interface FightEffectService {
13 |
14 | void removeCustomEffect(Player player, PotionEffectType type, Integer amplifier);
15 |
16 | void applyCustomEffect(Player player, PotionEffectType type, Integer amplifier);
17 |
18 | List getCurrentEffects(Player player);
19 |
20 | void clearStoredEffects(Player player);
21 |
22 | void restoreActiveEffects(Player player);
23 |
24 | void storeActiveEffect(Player player, PotionEffect effect);
25 | }
26 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CancelTagReason.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.event;
2 |
3 | public enum CancelTagReason {
4 |
5 | TAGOUT
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfTag.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.event;
2 |
3 | public enum CauseOfTag {
4 |
5 | /**
6 | * The player was tagged in combat as a result of an attack by another player.
7 | */
8 | PLAYER,
9 |
10 | /**
11 | * The player was tagged in combat due to an interaction with a non-player entity
12 | * (e.g., mobs or environmental damage).
13 | */
14 | NON_PLAYER,
15 |
16 | /**
17 | * A command was executed to apply a combat tag to the player.
18 | */
19 | COMMAND,
20 |
21 | /**
22 | * A custom cause, typically defined by external plugins or systems, applied the combat tag.
23 | */
24 | CUSTOM
25 | }
26 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfUnTag.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.event;
2 |
3 | public enum CauseOfUnTag {
4 | /**
5 | * The combat tag expired after a set duration.
6 | */
7 | TIME_EXPIRED,
8 |
9 | /**
10 | * The player died, which resulted in the removal of the combat tag.
11 | */
12 | DEATH,
13 |
14 | /**
15 | * The player was killed by another player, causing the combat tag to be removed.
16 | */
17 | DEATH_BY_PLAYER,
18 |
19 | /**
20 | * The player logged out, triggering the removal of the combat tag.
21 | */
22 | LOGOUT,
23 |
24 | /**
25 | * A command was executed to remove the player's combat tag.
26 | */
27 | COMMAND,
28 |
29 | /**
30 | * The attacker released the player, ending the combat and removing the tag.
31 | */
32 | ATTACKER_RELEASE,
33 |
34 | /**
35 | * A custom cause, typically defined by external plugins or systems.
36 | */
37 | CUSTOM
38 | }
39 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/FightTagEvent.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.event;
2 |
3 | import org.bukkit.event.Cancellable;
4 | import org.bukkit.event.Event;
5 | import org.bukkit.event.HandlerList;
6 |
7 | import java.util.UUID;
8 | import org.jetbrains.annotations.NotNull;
9 |
10 | /**
11 | * This event is triggered when a player is tagged in a fight.
12 | */
13 | public class FightTagEvent extends Event implements Cancellable {
14 |
15 | private static final HandlerList HANDLER_LIST = new HandlerList();
16 | private final UUID player;
17 | private final CauseOfTag cause;
18 | private boolean cancelled = false;
19 | private CancelTagReason cancelReason;
20 |
21 | public FightTagEvent(UUID player, CauseOfTag cause) {
22 | super(false);
23 |
24 | this.player = player;
25 | this.cause = cause;
26 | }
27 |
28 | /**
29 | * Gets the UUID of the player who was tagged.
30 | *
31 | * @return The UUID of the tagged player.
32 | */
33 | public UUID getPlayer() {
34 | return this.player;
35 | }
36 |
37 | /**
38 | * Gets the reason why the player was tagged.
39 | *
40 | * @return The cause of the combat tag as a {@link CauseOfTag}.
41 | */
42 | public CauseOfTag getCause() {
43 | return this.cause;
44 | }
45 |
46 | @Override
47 | public boolean isCancelled() {
48 | return this.cancelled;
49 | }
50 |
51 | @Override
52 | public void setCancelled(boolean cancelled) {
53 | this.cancelled = cancelled;
54 | }
55 |
56 | /**
57 | * Gets the reason why the event was cancelled.
58 | *
59 | * @return The cancel reason.
60 | */
61 | public CancelTagReason getCancelReason() {
62 | return this.cancelReason;
63 | }
64 |
65 | /**
66 | * Sets the reason why the event was cancelled.
67 | *
68 | * @param cancelReason The cancel reason.
69 | */
70 | public void setCancelReason(CancelTagReason cancelReason) {
71 | this.cancelReason = cancelReason;
72 | }
73 |
74 | @NotNull
75 | @Override
76 | public HandlerList getHandlers() {
77 | return HANDLER_LIST;
78 | }
79 |
80 | public static HandlerList getHandlerList() {
81 | return HANDLER_LIST;
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/FightUntagEvent.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.event;
2 |
3 | import org.bukkit.event.Cancellable;
4 | import org.bukkit.event.Event;
5 | import org.bukkit.event.HandlerList;
6 |
7 | import java.util.UUID;
8 | import org.jetbrains.annotations.NotNull;
9 |
10 | /**
11 | * This event is triggered when a player is untagged during a fight.
12 | */
13 | public class FightUntagEvent extends Event implements Cancellable {
14 |
15 | private static final HandlerList HANDLER_LIST = new HandlerList();
16 |
17 | private final UUID player;
18 | private final CauseOfUnTag cause;
19 | private boolean cancelled;
20 |
21 | public FightUntagEvent(UUID player, CauseOfUnTag cause) {
22 | super(false);
23 |
24 | this.player = player;
25 | this.cause = cause;
26 | }
27 |
28 | /**
29 | * Gets the UUID of the player who was untagged.
30 | *
31 | * @return The UUID of the untagged player.
32 | */
33 | public UUID getPlayer() {
34 | return this.player;
35 | }
36 |
37 | /**
38 | * Gets the reason for why the player was untagged.
39 | *
40 | * @return The cause of untagging as a {@link CauseOfUnTag}.
41 | */
42 | public CauseOfUnTag getCause() {
43 | return this.cause;
44 | }
45 |
46 | @Override
47 | public boolean isCancelled() {
48 | return this.cancelled;
49 | }
50 |
51 | @Override
52 | public void setCancelled(boolean cancelled) {
53 | this.cancelled = cancelled;
54 | }
55 |
56 | @NotNull
57 | @Override
58 | public HandlerList getHandlers() {
59 | return HANDLER_LIST;
60 | }
61 |
62 | public static HandlerList getHandlerList() {
63 | return HANDLER_LIST;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlService.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.pearl;
2 |
3 | import java.time.Duration;
4 | import java.time.Instant;
5 | import java.util.UUID;
6 |
7 | public interface FightPearlService {
8 |
9 | Instant getDelay(UUID uuid);
10 |
11 | Duration getRemainingDelay(UUID uuid);
12 |
13 | boolean hasDelay(UUID uuid);
14 |
15 | void markDelay(UUID uuid);
16 | }
17 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutService.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.tagout;
2 |
3 | import java.time.Duration;
4 | import java.util.UUID;
5 |
6 | public interface FightTagOutService {
7 |
8 | boolean isTaggedOut(UUID player);
9 |
10 | void unTagOut(UUID player);
11 |
12 | void tagOut(UUID player, Duration duration);
13 | }
14 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/region/Region.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.region;
2 |
3 | import org.bukkit.Location;
4 |
5 | public interface Region {
6 |
7 | Location getCenter();
8 |
9 | Location getMin();
10 |
11 | Location getMax();
12 |
13 | default boolean contains(Location location) {
14 | return this.contains(location.getX(), location.getY(), location.getZ());
15 | }
16 |
17 | default boolean contains(double x, double y, double z) {
18 | Location min = this.getMin();
19 | Location max = this.getMax();
20 | return x >= min.getX() && x < max.getX()
21 | && y >= min.getY() && y < max.getY()
22 | && z >= min.getZ() && z < max.getZ();
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/eternalcombat-api/src/main/java/com/eternalcode/combat/region/RegionProvider.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.region;
2 |
3 | import java.util.Collection;
4 | import java.util.Optional;
5 | import org.bukkit.Location;
6 | import org.bukkit.World;
7 |
8 | public interface RegionProvider {
9 |
10 | Optional getRegion(Location location);
11 |
12 | default boolean isInRegion(Location location) {
13 | return this.getRegion(location).isPresent();
14 | }
15 |
16 | Collection getRegions(World world);
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import net.minecrell.pluginyml.bukkit.BukkitPluginDescription
2 |
3 | plugins {
4 | `eternalcombat-java`
5 | `eternalcombat-repositories`
6 |
7 | id("net.minecrell.plugin-yml.bukkit")
8 | id("com.gradleup.shadow")
9 | id("xyz.jpenilla.run-paper")
10 | }
11 |
12 | dependencies {
13 | implementation(project(":eternalcombat-api"))
14 |
15 | // kyori
16 | implementation("net.kyori:adventure-platform-bukkit:${Versions.ADVENTURE_PLATFORM_BUKKIT}")
17 | implementation("net.kyori:adventure-text-minimessage:${Versions.ADVENTURE_API}")
18 | implementation("net.kyori:adventure-api") {
19 | version {
20 | strictly(Versions.ADVENTURE_API)
21 | }
22 | }
23 |
24 | // litecommands
25 | implementation("dev.rollczi:litecommands-bukkit:${Versions.LITE_COMMANDS}")
26 |
27 | // Okaeri configs
28 | implementation("eu.okaeri:okaeri-configs-serdes-commons:${Versions.OKAERI_CONFIGS_SERDES_COMMONS}")
29 | implementation("eu.okaeri:okaeri-configs-serdes-bukkit:${Versions.OKAERI_CONFIGS_SERDES_BUKKIT}")
30 |
31 | // GitCheck
32 | implementation("com.eternalcode:gitcheck:${Versions.GIT_CHECK}")
33 |
34 | // bstats
35 | implementation("org.bstats:bstats-bukkit:${Versions.B_STATS_BUKKIT}")
36 |
37 | // caffeine
38 | implementation("com.github.ben-manes.caffeine:caffeine:${Versions.CAFFEINE}")
39 |
40 | implementation("com.eternalcode:eternalcode-commons-bukkit:${Versions.ETERNALCODE_COMMONS}")
41 | implementation("com.eternalcode:eternalcode-commons-adventure:${Versions.ETERNALCODE_COMMONS}")
42 |
43 | // worldguard
44 | compileOnly("com.sk89q.worldguard:worldguard-bukkit:${Versions.WORLD_GUARD_BUKKIT}")
45 |
46 | // PlaceholderAPI
47 | compileOnly("me.clip:placeholderapi:${Versions.PLACEHOLDER_API}")
48 |
49 | // Multification
50 | implementation("com.eternalcode:multification-bukkit:${Versions.MULTIFICATION}")
51 | implementation("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION}")
52 | implementation("com.github.retrooper:packetevents-spigot:${Versions.PACKETS_EVENTS}")
53 | implementation("io.papermc:paperlib:${Versions.PAPERLIB}")
54 | }
55 |
56 | bukkit {
57 | main = "com.eternalcode.combat.CombatPlugin"
58 | author = "EternalCodeTeam"
59 | apiVersion = "1.13"
60 | prefix = "EternalCombat"
61 | name = "EternalCombat"
62 | load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD
63 | softDepend = listOf(
64 | "WorldGuard",
65 | "PlaceholderAPI",
66 | "ProtocolLib",
67 | "ProtocolSupport",
68 | "ViaVersion",
69 | "ViaBackwards",
70 | "ViaRewind",
71 | "Geyser-Spigot"
72 | )
73 | version = "${project.version}"
74 | }
75 |
76 | tasks {
77 | runServer {
78 | minecraftVersion("1.21.4")
79 | }
80 | }
81 |
82 | tasks.shadowJar {
83 | archiveFileName.set("EternalCombat v${project.version}.jar")
84 |
85 | exclude(
86 | "org/intellij/lang/annotations/**",
87 | "org/jetbrains/annotations/**",
88 | "META-INF/**",
89 | "kotlin/**",
90 | "javax/**",
91 | "org/checkerframework/**",
92 | "com/google/errorprone/**",
93 | )
94 |
95 | val prefix = "com.eternalcode.combat.libs"
96 | listOf(
97 | "eu.okaeri",
98 | "net.kyori",
99 | "org.bstats",
100 | "org.yaml",
101 | "dev.rollczi.litecommands",
102 | "com.eternalcode.gitcheck",
103 | "org.json.simple",
104 | "com.github.benmanes.caffeine",
105 | "com.eternalcode.commons",
106 | "com.eternalcode.multification",
107 | "io.papermc"
108 | ).forEach { pack ->
109 | relocate(pack, "$prefix.$pack")
110 | }
111 |
112 | relocate("com.github.retrooper.packetevents", "$prefix.packetevents.api")
113 | relocate("io.github.retrooper.packetevents", "$prefix.packetevents.impl")
114 | }
115 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/EternalCombatReloadCommand.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat;
2 |
3 | import com.eternalcode.combat.config.ConfigService;
4 | import com.eternalcode.combat.notification.NoticeService;
5 | import com.eternalcode.multification.bukkit.notice.BukkitNotice;
6 | import com.eternalcode.multification.notice.Notice;
7 | import com.google.common.base.Stopwatch;
8 | import dev.rollczi.litecommands.annotations.async.Async;
9 | import dev.rollczi.litecommands.annotations.command.Command;
10 | import dev.rollczi.litecommands.annotations.context.Context;
11 | import dev.rollczi.litecommands.annotations.execute.Execute;
12 | import dev.rollczi.litecommands.annotations.permission.Permission;
13 | import java.time.Duration;
14 | import org.bukkit.command.CommandSender;
15 |
16 | @Command(name = "combatlog", aliases = "combat")
17 | @Permission("eternalcombat.reload")
18 | public class EternalCombatReloadCommand {
19 |
20 | private static final Notice RELOAD_MESSAGE = BukkitNotice.builder()
21 | .chat("EternalCombat: Reloaded EternalCombat in {TIME}ms!")
22 | .build();
23 |
24 | private final ConfigService configService;
25 | private final NoticeService noticeService;
26 |
27 | public EternalCombatReloadCommand(ConfigService configService, NoticeService noticeService) {
28 | this.configService = configService;
29 | this.noticeService = noticeService;
30 | }
31 |
32 | @Async
33 | @Execute(name = "reload")
34 | void execute(@Context CommandSender sender) {
35 | Stopwatch stopwatch = Stopwatch.createStarted();
36 | this.configService.reload();
37 |
38 | Duration elapsed = stopwatch.elapsed();
39 | this.noticeService.create()
40 | .viewer(sender)
41 | .notice(RELOAD_MESSAGE)
42 | .placeholder("{TIME}", String.valueOf(elapsed.toMillis()))
43 | .send();
44 |
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/WhitelistBlacklistMode.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat;
2 |
3 | public enum WhitelistBlacklistMode {
4 | WHITELIST,
5 | BLACKLIST;
6 |
7 | public boolean shouldBlock(boolean isInList) {
8 | return switch (this) {
9 | case WHITELIST -> !isInList;
10 | case BLACKLIST -> isInList;
11 | };
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderActivePointsIndex.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border;
2 |
3 | import java.util.Collections;
4 | import java.util.HashSet;
5 | import java.util.Map;
6 | import java.util.Set;
7 | import java.util.UUID;
8 | import java.util.concurrent.ConcurrentHashMap;
9 |
10 | class BorderActivePointsIndex {
11 |
12 | private final Map>> registry = new ConcurrentHashMap<>();
13 |
14 | boolean hasPoints(String world, UUID player) {
15 | Map> worldRegistry = this.registry.get(world);
16 | if (worldRegistry == null) {
17 | return false;
18 | }
19 |
20 | return worldRegistry.containsKey(player);
21 | }
22 |
23 | Set putPoints(String world, UUID player, Set points) {
24 | Map> worldRegistry = this.registry.computeIfAbsent(world, k -> new ConcurrentHashMap<>());
25 | Set oldPoints = worldRegistry.put(player, points);
26 | if (oldPoints != null) {
27 | return calculateRemovedPoints(points, oldPoints);
28 | }
29 |
30 | return Set.of();
31 | }
32 |
33 | private static Set calculateRemovedPoints(Set points, Set oldPoints) {
34 | Set removed = new HashSet<>();
35 | for (BorderPoint oldPoint : oldPoints) {
36 | if (!points.contains(oldPoint)) {
37 | removed.add(oldPoint);
38 | }
39 | }
40 | return Collections.unmodifiableSet(removed);
41 | }
42 |
43 | Set getPoints(String world, UUID player) {
44 | Map> worldRegistry = this.registry.get(world);
45 | if (worldRegistry == null) {
46 | return Set.of();
47 | }
48 |
49 | return worldRegistry.getOrDefault(player, Set.of());
50 | }
51 |
52 | Set removePoints(String world, UUID player) {
53 | Map> worldRegistry = this.registry.get(world);
54 | if (worldRegistry == null) {
55 | return Set.of();
56 | }
57 |
58 | Set remove = worldRegistry.remove(player);
59 | if (remove == null) {
60 | return Set.of();
61 | }
62 |
63 | return remove;
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderLazyResult.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border;
2 |
3 | import dev.rollczi.litecommands.shared.Lazy;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 | import java.util.Set;
7 | import java.util.stream.Collectors;
8 |
9 | class BorderLazyResult implements BorderResult {
10 |
11 | private final List>> borderPoints = new ArrayList<>();
12 |
13 | void addLazyBorderPoints(Lazy> supplier) {
14 | borderPoints.add(supplier);
15 | }
16 |
17 | @Override
18 | public Set collect() {
19 | return borderPoints.stream()
20 | .flatMap(result -> result.get().stream())
21 | .collect(Collectors.toUnmodifiableSet());
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderPoint.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border;
2 |
3 | import org.jetbrains.annotations.Nullable;
4 |
5 | public record BorderPoint(int x, int y, int z, @Nullable BorderPoint inclusive) {
6 |
7 | public BorderPoint(int x, int y, int z) {
8 | this(x, y, z, null);
9 | }
10 |
11 | public BorderPoint toInclusive() {
12 | if (inclusive == null) {
13 | return this;
14 | }
15 |
16 | return inclusive;
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderResult.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border;
2 |
3 | import java.util.Set;
4 |
5 | public interface BorderResult {
6 |
7 | Set collect();
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderService.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border;
2 |
3 | import java.util.Set;
4 | import org.bukkit.Location;
5 | import org.bukkit.entity.Player;
6 |
7 | public interface BorderService {
8 |
9 | void updateBorder(Player player, Location to);
10 |
11 | void clearBorder(Player player);
12 |
13 | Set getActiveBorder(Player player);
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderSettings.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border;
2 |
3 | import com.eternalcode.combat.border.animation.block.BlockSettings;
4 | import com.eternalcode.combat.border.animation.particle.ParticleSettings;
5 | import eu.okaeri.configs.OkaeriConfig;
6 | import eu.okaeri.configs.annotation.Comment;
7 | import java.time.Duration;
8 |
9 | public class BorderSettings extends OkaeriConfig {
10 |
11 | @Comment("# Border view distance in blocks")
12 | public double distance = 6.5;
13 |
14 | @Comment({
15 | " ",
16 | "# Border block animation settings",
17 | "# Configure the block animation that appears during combat."
18 | })
19 | public BlockSettings block = new BlockSettings();
20 |
21 | @Comment({
22 | " ",
23 | "# Border particle animation settings",
24 | "# Configure the particle animation that appears during combat."
25 | })
26 | public ParticleSettings particle = new ParticleSettings();
27 |
28 | public Duration indexRefreshDelay() {
29 | return Duration.ofSeconds(1);
30 | }
31 |
32 | public int distanceRounded() {
33 | return (int) Math.ceil(this.distance);
34 | }
35 |
36 | public boolean isEnabled() {
37 | return this.block.enabled || this.particle.enabled;
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTrigger.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border;
2 |
3 | record BorderTrigger(BorderPoint min, BorderPoint max, BorderPoint triggerMin, BorderPoint triggerMax) {
4 |
5 | BorderTrigger(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, int distance) {
6 | this(
7 | new BorderPoint(minX, minY, minZ),
8 | new BorderPoint(maxX, maxY, maxZ),
9 | new BorderPoint(minX - distance, minY - distance, minZ - distance),
10 | new BorderPoint(maxX + distance, maxY + distance, maxZ + distance)
11 | );
12 | }
13 |
14 | public boolean isTriggered(int x, int y, int z) {
15 | return x >= triggerMin.x() && x <= triggerMax.x()
16 | && y >= triggerMin.y() && y <= triggerMax.y()
17 | && z >= triggerMin.z() && z <= triggerMax.z();
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerController.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border;
2 |
3 | import com.eternalcode.combat.fight.FightManager;
4 | import com.eternalcode.combat.fight.event.FightUntagEvent;
5 | import org.bukkit.Location;
6 | import org.bukkit.Server;
7 | import org.bukkit.entity.Player;
8 | import org.bukkit.event.EventHandler;
9 | import org.bukkit.event.EventPriority;
10 | import org.bukkit.event.Listener;
11 | import org.bukkit.event.player.PlayerMoveEvent;
12 | import org.bukkit.event.player.PlayerTeleportEvent;
13 |
14 | public class BorderTriggerController implements Listener {
15 |
16 | private final BorderService borderService;
17 | private final BorderSettings border;
18 | private final FightManager fightManager;
19 | private final Server server;
20 |
21 | public BorderTriggerController(BorderService borderService, BorderSettings border, FightManager fightManager, Server server) {
22 | this.borderService = borderService;
23 | this.border = border;
24 | this.fightManager = fightManager;
25 | this.server = server;
26 | }
27 |
28 | @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
29 | void onMove(PlayerMoveEvent event) {
30 | if (!border.isEnabled()) {
31 | return;
32 | }
33 |
34 | Location to = event.getTo();
35 | Location from = event.getFrom();
36 | if (to.getBlockX() == from.getBlockX() && to.getBlockY() == from.getBlockY() && to.getBlockZ() == from.getBlockZ()) {
37 | return;
38 | }
39 |
40 | Player player = event.getPlayer();
41 | if (!fightManager.isInCombat(player.getUniqueId())) {
42 | return;
43 | }
44 |
45 | borderService.updateBorder(player, to);
46 | }
47 |
48 | @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
49 | void onTeleport(PlayerTeleportEvent event) {
50 | if (!border.isEnabled()) {
51 | return;
52 | }
53 |
54 | Player player = event.getPlayer();
55 | if (!fightManager.isInCombat(player.getUniqueId())) {
56 | return;
57 | }
58 |
59 | borderService.updateBorder(player, event.getTo());
60 | }
61 |
62 | @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
63 | void onFightEnd(FightUntagEvent event) {
64 | if (!border.isEnabled()) {
65 | return;
66 | }
67 |
68 | Player player = server.getPlayer(event.getPlayer());
69 | if (player == null) {
70 | return;
71 | }
72 |
73 | borderService.clearBorder(player);
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndex.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border;
2 |
3 | import com.eternalcode.combat.region.Region;
4 | import com.eternalcode.combat.region.RegionProvider;
5 | import com.eternalcode.commons.scheduler.Scheduler;
6 | import java.time.Duration;
7 | import java.util.ArrayList;
8 | import java.util.Collection;
9 | import java.util.HashMap;
10 | import java.util.List;
11 | import java.util.Map;
12 | import org.bukkit.Location;
13 | import org.bukkit.Server;
14 | import org.bukkit.World;
15 |
16 | class BorderTriggerIndex {
17 |
18 | private final Server server;
19 | private final Scheduler scheduler;
20 | private final RegionProvider provider;
21 | private final BorderSettings settings;
22 |
23 | private final Map borderIndexes = new HashMap<>();
24 |
25 | private BorderTriggerIndex(Server server, Scheduler scheduler, RegionProvider provider, BorderSettings settings) {
26 | this.server = server;
27 | this.scheduler = scheduler;
28 | this.provider = provider;
29 | this.settings = settings;
30 | }
31 |
32 | private void updateWorlds() {
33 | for (World world : server.getWorlds()) {
34 | this.updateWorld(world.getName(), provider.getRegions(world));
35 | }
36 | }
37 |
38 | private void updateWorld(String world, Collection regions) {
39 | this.scheduler.runAsync(() -> {
40 | List triggers = new ArrayList<>();
41 | for (Region region : regions) {
42 | Location min = region.getMin();
43 | Location max = region.getMax();
44 |
45 | triggers.add(new BorderTrigger(
46 | min.getBlockX(), min.getBlockY(), min.getBlockZ(),
47 | max.getBlockX() + 1, max.getBlockY() + 1, max.getBlockZ() + 1,
48 | settings.distanceRounded()
49 | ));
50 | }
51 |
52 | BorderTriggerIndexBucket index = BorderTriggerIndexBucket.create(triggers);
53 | this.borderIndexes.put(world, index);
54 | });
55 | }
56 |
57 | static BorderTriggerIndex started(Server server, Scheduler scheduler, RegionProvider provider, BorderSettings settings) {
58 | BorderTriggerIndex index = new BorderTriggerIndex(server, scheduler, provider, settings);
59 | scheduler.timerAsync(() -> index.updateWorlds(), Duration.ZERO, settings.indexRefreshDelay());
60 | return index;
61 | }
62 |
63 | public List getTriggered(Location location) {
64 | BorderTriggerIndexBucket index = borderIndexes.get(location.getWorld().getName());
65 | if (index == null) {
66 | return List.of();
67 | }
68 |
69 | int x = (int) Math.round(location.getX());
70 | int y = (int) Math.round(location.getY());
71 | int z = (int) Math.round(location.getZ());
72 |
73 | return index.getTriggers(x, z)
74 | .stream()
75 | .filter(trigger -> trigger.isTriggered(x, y, z))
76 | .toList();
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndexBucket.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border;
2 |
3 | import java.util.Collection;
4 | import java.util.HashMap;
5 | import java.util.HashSet;
6 | import java.util.Map;
7 | import java.util.Set;
8 |
9 | class BorderTriggerIndexBucket {
10 |
11 | /**
12 | * Represents "00000000 00000000 00000000 00000000 11111111 11111111 11111111 11111111" bit mask.
13 | * This allows removing left bits when used with AND bit operator.
14 | * */
15 | private static final long LEFT_INT_MASK = 0xFFFFFFFFL;
16 |
17 | /**
18 | * Allows packing the coordination on one axis from 256 into 1
19 | * Why? - We divide the world into fragments of size 256x256
20 | * Why it is 8? Because when you shift bits, then the value will be smaller / bigger in scale 2^n
21 | * E.g `2^4 = 16` (standard minecraft chunk size) in our case, it is `2^8 = 256`
22 | */
23 | private static final int CHUNK_SHIFT = 8;
24 |
25 | private final Map> index = new HashMap<>();
26 |
27 | private BorderTriggerIndexBucket() {
28 | }
29 |
30 | Set getTriggers(int x, int z) {
31 | long position = packChunk(x >> CHUNK_SHIFT, z >> CHUNK_SHIFT);
32 | return this.index.getOrDefault(position, Set.of());
33 | }
34 |
35 | static BorderTriggerIndexBucket create(Collection triggers) {
36 | return new BorderTriggerIndexBucket().with(triggers);
37 | }
38 |
39 | private BorderTriggerIndexBucket with(Collection triggers) {
40 | for (BorderTrigger trigger : triggers) {
41 | withTrigger(trigger);
42 | }
43 | return this;
44 | }
45 |
46 | private void withTrigger(BorderTrigger trigger) {
47 | int minX = trigger.min().x() >> CHUNK_SHIFT;
48 | int minZ = trigger.min().z() >> CHUNK_SHIFT;
49 | int maxX = trigger.max().x() >> CHUNK_SHIFT;
50 | int maxZ = trigger.max().z() >> CHUNK_SHIFT;
51 |
52 | int startX = Math.min(minX, maxX);
53 | int startZ = Math.min(minZ, maxZ);
54 | int endX = Math.max(minX, maxX);
55 | int endZ = Math.max(minZ, maxZ);
56 |
57 | for (int chunkX = startX; chunkX <= endX; chunkX++) {
58 | for (int chunkZ = startZ; chunkZ <= endZ; chunkZ++) {
59 | long packed = packChunk(chunkX, chunkZ);
60 | this.index.computeIfAbsent(packed, key -> new HashSet<>())
61 | .add(trigger);
62 | }
63 | }
64 | }
65 |
66 | /**
67 | * Packs two ints into long value.
68 | *
69 | * For example for values:
70 | *
71 | * - X - 00000000 00000000 00000000 00000001
72 | * - Z - 00000000 00000000 00000000 00000111
73 | *
74 | * This method will return:
75 | * 00000000 00000000 00000000 00000111 00000000 00000000 00000000 00000001
76 | *
77 | * @param bigChunkX right int to pack
78 | * @param bigChunkZ left int to pack
79 | */
80 | private static long packChunk(int bigChunkX, int bigChunkZ) {
81 | return (long) bigChunkX & LEFT_INT_MASK | ((long) bigChunkZ & LEFT_INT_MASK) << Integer.SIZE;
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/BorderColorUtil.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.animation;
2 |
3 | import java.awt.Color;
4 |
5 | public final class BorderColorUtil {
6 |
7 | private BorderColorUtil() {
8 | }
9 |
10 | public static Color xyzToRainbow(int x, int y, int z) {
11 | float hue = (float) ((Math.sin(x * 0.05) + Math.cos(z * 0.05)) * 0.5 + 0.5);
12 | float saturation = 1.0f;
13 | float brightness = 0.8f + 0.2f * Math.max(0.0f, Math.min(1.0f, (float) y / 255));
14 |
15 | return Color.getHSBColor(hue, saturation, brightness);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockSettings.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.animation.block;
2 |
3 | import eu.okaeri.configs.OkaeriConfig;
4 | import eu.okaeri.configs.annotation.Comment;
5 | import java.time.Duration;
6 |
7 | public class BlockSettings extends OkaeriConfig {
8 |
9 | @Comment("# Enable block animation?")
10 | public boolean enabled = true;
11 |
12 | @Comment({
13 | "# Block type used for rendering the border",
14 | "# Custom: RAINBOW_GLASS, RAINBOW_WOOL, RAINBOW_TERRACOTTA, RAINBOW_CONCRETE",
15 | "# Vanilla: https://javadocs.packetevents.com/com/github/retrooper/packetevents/protocol/world/states/type/StateTypes.html"
16 | })
17 | public BlockType type = BlockType.RAINBOW_GLASS;
18 |
19 | @Comment({
20 | "# Delay between each async animation update",
21 | "# Lower values will decrease performance but will make the animation smoother",
22 | "# Higher values will increase performance"
23 | })
24 | public Duration updateDelay = Duration.ofMillis(250);
25 |
26 | @Comment({
27 | "# Delay between each chunk cache update",
28 | "# Lower values will decrease performance",
29 | "# Higher values will increase performance but may cause overlapping existing blocks (this does not modify the world)"
30 | })
31 | public Duration chunkCacheDelay = Duration.ofMillis(300);
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockType.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.animation.block;
2 |
3 | import com.eternalcode.combat.border.BorderPoint;
4 | import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToConcrete;
5 | import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToGlass;
6 | import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToTerracotta;
7 | import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToWool;
8 | import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
9 | import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
10 | import java.util.Locale;
11 |
12 | public class BlockType {
13 |
14 | public static final BlockType RAINBOW_GLASS = new BlockType("RAINBOW_GLASS", point -> xyzToGlass(point));
15 | public static final BlockType RAINBOW_TERRACOTTA = new BlockType("RAINBOW_TERRACOTTA", point -> xyzToTerracotta(point));
16 | public static final BlockType RAINBOW_WOOL = new BlockType("RAINBOW_WOOL", point -> xyzToWool(point));
17 | public static final BlockType RAINBOW_CONCRETE = new BlockType("RAINBOW_CONCRETE", point -> xyzToConcrete(point));
18 |
19 | private final String name;
20 | private final TypeProvider type;
21 |
22 | private BlockType(String name, TypeProvider type) {
23 | this.name = name;
24 | this.type = type;
25 | }
26 |
27 | public String getName() {
28 | return name;
29 | }
30 |
31 | public StateType getStateType(BorderPoint point) {
32 | return type.provide(point);
33 | }
34 |
35 | public static BlockType fromName(String name) {
36 | return switch (name) {
37 | case "RAINBOW_GLASS" -> RAINBOW_GLASS;
38 | case "RAINBOW_WOOL" -> RAINBOW_WOOL;
39 | case "RAINBOW_TERRACOTTA" -> RAINBOW_TERRACOTTA;
40 | case "RAINBOW_CONCRETE" -> RAINBOW_CONCRETE;
41 | default -> new BlockType(name, point -> StateTypes.getByName(name.toLowerCase(Locale.ROOT)));
42 | };
43 | }
44 |
45 | private interface TypeProvider {
46 | StateType provide(BorderPoint point);
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockTypeTransformer.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.animation.block;
2 |
3 | import eu.okaeri.configs.schema.GenericsPair;
4 | import eu.okaeri.configs.serdes.BidirectionalTransformer;
5 | import eu.okaeri.configs.serdes.SerdesContext;
6 | import java.util.Locale;
7 |
8 | public class BlockTypeTransformer extends BidirectionalTransformer {
9 |
10 | @Override
11 | public GenericsPair getPair() {
12 | return this.genericsPair(String.class, BlockType.class);
13 | }
14 |
15 | @Override
16 | public BlockType leftToRight(String data, SerdesContext serdesContext) {
17 | BlockType blockType = BlockType.fromName(data);
18 | if (blockType == null) {
19 | throw new IllegalArgumentException("Unknown block type: " + data);
20 | }
21 |
22 | return blockType;
23 | }
24 |
25 | @Override
26 | public String rightToLeft(BlockType data, SerdesContext serdesContext) {
27 | return data.getName().toUpperCase(Locale.ROOT);
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockRainbowUtil.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.animation.block;
2 |
3 | import com.eternalcode.combat.border.BorderPoint;
4 | import com.eternalcode.combat.border.animation.BorderColorUtil;
5 | import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
6 | import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
7 | import com.google.common.collect.ImmutableMap;
8 | import java.awt.Color;
9 | import java.util.EnumMap;
10 | import java.util.Map;
11 | import org.bukkit.DyeColor;
12 |
13 | final class BorderBlockRainbowUtil {
14 |
15 | private final static Map GLASSES = new EnumMap<>(ImmutableMap.builder()
16 | .put(DyeColor.WHITE, StateTypes.WHITE_STAINED_GLASS)
17 | .put(DyeColor.ORANGE, StateTypes.ORANGE_STAINED_GLASS)
18 | .put(DyeColor.MAGENTA, StateTypes.MAGENTA_STAINED_GLASS)
19 | .put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_STAINED_GLASS)
20 | .put(DyeColor.YELLOW, StateTypes.YELLOW_STAINED_GLASS)
21 | .put(DyeColor.LIME, StateTypes.LIME_STAINED_GLASS)
22 | .put(DyeColor.PINK, StateTypes.PINK_STAINED_GLASS)
23 | .put(DyeColor.GRAY, StateTypes.GRAY_STAINED_GLASS)
24 | .put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_STAINED_GLASS)
25 | .put(DyeColor.CYAN, StateTypes.CYAN_STAINED_GLASS)
26 | .put(DyeColor.PURPLE, StateTypes.PURPLE_STAINED_GLASS)
27 | .put(DyeColor.BLUE, StateTypes.BLUE_STAINED_GLASS)
28 | .put(DyeColor.BROWN, StateTypes.BROWN_STAINED_GLASS)
29 | .put(DyeColor.GREEN, StateTypes.GREEN_STAINED_GLASS)
30 | .put(DyeColor.RED, StateTypes.RED_STAINED_GLASS)
31 | .build()
32 | );
33 |
34 | private final static Map TERRACOTTA = new EnumMap<>(ImmutableMap.builder()
35 | .put(DyeColor.WHITE, StateTypes.WHITE_TERRACOTTA)
36 | .put(DyeColor.ORANGE, StateTypes.ORANGE_TERRACOTTA)
37 | .put(DyeColor.MAGENTA, StateTypes.MAGENTA_TERRACOTTA)
38 | .put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_TERRACOTTA)
39 | .put(DyeColor.YELLOW, StateTypes.YELLOW_TERRACOTTA)
40 | .put(DyeColor.LIME, StateTypes.LIME_TERRACOTTA)
41 | .put(DyeColor.PINK, StateTypes.PINK_TERRACOTTA)
42 | .put(DyeColor.GRAY, StateTypes.GRAY_TERRACOTTA)
43 | .put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_TERRACOTTA)
44 | .put(DyeColor.CYAN, StateTypes.CYAN_TERRACOTTA)
45 | .put(DyeColor.PURPLE, StateTypes.PURPLE_TERRACOTTA)
46 | .put(DyeColor.BLUE, StateTypes.BLUE_TERRACOTTA)
47 | .put(DyeColor.BROWN, StateTypes.BROWN_TERRACOTTA)
48 | .put(DyeColor.GREEN, StateTypes.GREEN_TERRACOTTA)
49 | .put(DyeColor.RED, StateTypes.RED_TERRACOTTA)
50 | .build()
51 | );
52 |
53 | private final static Map WOOLS = new EnumMap<>(ImmutableMap.builder()
54 | .put(DyeColor.WHITE, StateTypes.WHITE_WOOL)
55 | .put(DyeColor.ORANGE, StateTypes.ORANGE_WOOL)
56 | .put(DyeColor.MAGENTA, StateTypes.MAGENTA_WOOL)
57 | .put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_WOOL)
58 | .put(DyeColor.YELLOW, StateTypes.YELLOW_WOOL)
59 | .put(DyeColor.LIME, StateTypes.LIME_WOOL)
60 | .put(DyeColor.PINK, StateTypes.PINK_WOOL)
61 | .put(DyeColor.GRAY, StateTypes.GRAY_WOOL)
62 | .put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_WOOL)
63 | .put(DyeColor.CYAN, StateTypes.CYAN_WOOL)
64 | .put(DyeColor.PURPLE, StateTypes.PURPLE_WOOL)
65 | .put(DyeColor.BLUE, StateTypes.BLUE_WOOL)
66 | .put(DyeColor.BROWN, StateTypes.BROWN_WOOL)
67 | .put(DyeColor.GREEN, StateTypes.GREEN_WOOL)
68 | .put(DyeColor.RED, StateTypes.RED_WOOL)
69 | .build()
70 | );
71 |
72 | private final static Map CONCRETE = new EnumMap<>(ImmutableMap.builder()
73 | .put(DyeColor.WHITE, StateTypes.WHITE_CONCRETE)
74 | .put(DyeColor.ORANGE, StateTypes.ORANGE_CONCRETE)
75 | .put(DyeColor.MAGENTA, StateTypes.MAGENTA_CONCRETE)
76 | .put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_CONCRETE)
77 | .put(DyeColor.YELLOW, StateTypes.YELLOW_CONCRETE)
78 | .put(DyeColor.LIME, StateTypes.LIME_CONCRETE)
79 | .put(DyeColor.PINK, StateTypes.PINK_CONCRETE)
80 | .put(DyeColor.GRAY, StateTypes.GRAY_CONCRETE)
81 | .put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_CONCRETE)
82 | .put(DyeColor.CYAN, StateTypes.CYAN_CONCRETE)
83 | .put(DyeColor.PURPLE, StateTypes.PURPLE_CONCRETE)
84 | .put(DyeColor.BLUE, StateTypes.BLUE_CONCRETE)
85 | .put(DyeColor.BROWN, StateTypes.BROWN_CONCRETE)
86 | .put(DyeColor.GREEN, StateTypes.GREEN_CONCRETE)
87 | .put(DyeColor.RED, StateTypes.RED_CONCRETE)
88 | .build()
89 | );
90 |
91 | static StateType xyzToGlass(BorderPoint point) {
92 | return GLASSES.get(xyzToDye(point));
93 | }
94 |
95 | static StateType xyzToTerracotta(BorderPoint point) {
96 | return TERRACOTTA.get(xyzToDye(point));
97 | }
98 |
99 | public static StateType xyzToWool(BorderPoint point) {
100 | return WOOLS.get(xyzToDye(point));
101 | }
102 |
103 | public static StateType xyzToConcrete(BorderPoint point) {
104 | return CONCRETE.get(xyzToDye(point));
105 | }
106 |
107 | private static DyeColor xyzToDye(BorderPoint point) {
108 | Color rgb = BorderColorUtil.xyzToRainbow(point.x(), point.y(), point.z());
109 | int distance = Integer.MAX_VALUE;
110 | DyeColor dye = DyeColor.WHITE;
111 |
112 | for (DyeColor currentDye : DyeColor.values()) {
113 | org.bukkit.Color color = currentDye.getColor();
114 | int currentDistance = Math.abs(color.getRed() - rgb.getRed()) + Math.abs(color.getGreen() - rgb.getGreen()) + Math.abs(color.getBlue() - rgb.getBlue());
115 | if (currentDistance < distance) {
116 | distance = currentDistance;
117 | dye = currentDye;
118 | }
119 | }
120 | return dye;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/ChunkCache.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.animation.block;
2 |
3 | import com.google.common.cache.Cache;
4 | import com.google.common.cache.CacheBuilder;
5 | import io.papermc.lib.PaperLib;
6 | import org.bukkit.ChunkSnapshot;
7 | import org.bukkit.entity.Player;
8 |
9 | class ChunkCache {
10 |
11 | private final Cache chunkCache;
12 |
13 | ChunkCache(BlockSettings settings) {
14 | this.chunkCache = CacheBuilder.newBuilder()
15 | .expireAfterWrite(settings.chunkCacheDelay)
16 | .build();
17 | }
18 |
19 | public ChunkSnapshot loadSnapshot(Player player, ChunkLocation location) {
20 | ChunkSnapshot snapshot = chunkCache.getIfPresent(location);
21 | if (snapshot != null) {
22 | return snapshot;
23 | }
24 |
25 | ChunkSnapshot chunkSnapshot = PaperLib.getChunkAtAsync(player.getWorld(), location.x(), location.z(), false)
26 | .thenApply(chunk -> chunk.getChunkSnapshot())
27 | .join();
28 |
29 | chunkCache.put(location, chunkSnapshot);
30 | return chunkSnapshot;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/ChunkLocation.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.animation.block;
2 |
3 | record ChunkLocation(int x, int z) {
4 | }
5 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleColor.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.animation.particle;
2 |
3 | import com.eternalcode.combat.border.BorderPoint;
4 | import com.eternalcode.combat.border.animation.BorderColorUtil;
5 | import java.awt.Color;
6 |
7 | public class ParticleColor {
8 |
9 | public static final ParticleColor RAINBOW = new ParticleColor("RAINBOW", point -> BorderColorUtil.xyzToRainbow(point.x(), point.y(), point.z()));
10 |
11 | private final String name;
12 | private final ColorProvider color;
13 |
14 | private ParticleColor(String name, ColorProvider color) {
15 | this.name = name;
16 | this.color = color;
17 | }
18 |
19 | public String getName() {
20 | return name;
21 | }
22 |
23 | public Color getColor(BorderPoint point) {
24 | return color.provide(point);
25 | }
26 |
27 | public static ParticleColor fromName(String name) {
28 | if (name.equals(RAINBOW.name)) {
29 | return RAINBOW;
30 | }
31 |
32 | Color decoded = Color.decode(name);
33 | return new ParticleColor(name, point -> decoded);
34 | }
35 |
36 | private interface ColorProvider {
37 | Color provide(BorderPoint point);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleColorTransformer.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.animation.particle;
2 |
3 | import eu.okaeri.configs.schema.GenericsPair;
4 | import eu.okaeri.configs.serdes.BidirectionalTransformer;
5 | import eu.okaeri.configs.serdes.SerdesContext;
6 |
7 | public class ParticleColorTransformer extends BidirectionalTransformer {
8 |
9 | @Override
10 | public GenericsPair getPair() {
11 | return this.genericsPair(String.class, ParticleColor.class);
12 | }
13 |
14 | @Override
15 | public ParticleColor leftToRight(String data, SerdesContext serdesContext) {
16 | return ParticleColor.fromName(data);
17 | }
18 |
19 | @Override
20 | public String rightToLeft(ParticleColor data, SerdesContext serdesContext) {
21 | return data.getName();
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleController.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.animation.particle;
2 |
3 | import com.eternalcode.combat.border.BorderPoint;
4 | import com.eternalcode.combat.border.BorderService;
5 | import com.eternalcode.combat.border.event.BorderHideAsyncEvent;
6 | import com.eternalcode.combat.border.event.BorderShowAsyncEvent;
7 | import com.eternalcode.commons.scheduler.Scheduler;
8 | import com.github.retrooper.packetevents.PacketEvents;
9 | import com.github.retrooper.packetevents.PacketEventsAPI;
10 | import com.github.retrooper.packetevents.manager.player.PlayerManager;
11 | import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerParticle;
12 | import java.time.Duration;
13 | import java.util.Set;
14 | import java.util.UUID;
15 | import java.util.concurrent.ConcurrentHashMap;
16 | import org.bukkit.Server;
17 | import org.bukkit.entity.Player;
18 | import org.bukkit.event.EventHandler;
19 | import org.bukkit.event.Listener;
20 |
21 | public class ParticleController implements Listener {
22 |
23 | private static final PacketEventsAPI> PACKET_EVENTS_API = PacketEvents.getAPI();
24 | private static final PlayerManager PLAYER_MANAGER = PACKET_EVENTS_API.getPlayerManager();
25 |
26 | private final BorderService borderService;
27 | private final ParticleSettings particleSettings;
28 | private final Server server;
29 | private final Set playersToUpdate = ConcurrentHashMap.newKeySet();
30 |
31 | public ParticleController(BorderService borderService, ParticleSettings particleSettings, Scheduler scheduler, Server server) {
32 | this.borderService = borderService;
33 | this.particleSettings = particleSettings;
34 | this.server = server;
35 | scheduler.timerAsync(() -> this.updatePlayers(), Duration.ofMillis(200), Duration.ofMillis(200));
36 | }
37 |
38 | @EventHandler
39 | void onBorderShowAsyncEvent(BorderShowAsyncEvent event) {
40 | if (!particleSettings.enabled) {
41 | return;
42 | }
43 |
44 | this.playersToUpdate.add(event.getPlayer().getUniqueId());
45 |
46 | for (BorderPoint point : event.getPoints()) {
47 | this.playParticle(event.getPlayer(), point);
48 | }
49 | }
50 |
51 | @EventHandler
52 | void onBorderHideAsyncEvent(BorderHideAsyncEvent event) {
53 | if (!particleSettings.enabled) {
54 | return;
55 | }
56 |
57 | Set border = this.borderService.getActiveBorder(event.getPlayer());
58 | if (border.isEmpty()) {
59 | this.playersToUpdate.remove(event.getPlayer().getUniqueId());
60 | }
61 | }
62 |
63 | private void updatePlayers() {
64 | if (!particleSettings.enabled) {
65 | return;
66 | }
67 |
68 | for (UUID uuid : this.playersToUpdate) {
69 | Player player = this.server.getPlayer(uuid);
70 | if (player == null || !player.isOnline()) {
71 | this.playersToUpdate.remove(uuid);
72 | continue;
73 | }
74 |
75 | Set border = this.borderService.getActiveBorder(player);
76 |
77 | if (border.isEmpty()) {
78 | this.playersToUpdate.remove(uuid);
79 | continue;
80 | }
81 |
82 | for (BorderPoint point : border) {
83 | this.playParticle(player, point);
84 | }
85 | }
86 | }
87 |
88 | private void playParticle(Player player, BorderPoint point) {
89 | WrapperPlayServerParticle particle = particleSettings.getParticle(point);
90 |
91 | PLAYER_MANAGER.sendPacket(player, particle);
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleSettings.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.animation.particle;
2 |
3 | import com.eternalcode.combat.border.BorderPoint;
4 | import com.github.retrooper.packetevents.protocol.color.AlphaColor;
5 | import com.github.retrooper.packetevents.protocol.particle.Particle;
6 | import com.github.retrooper.packetevents.protocol.particle.data.ParticleColorData;
7 | import com.github.retrooper.packetevents.protocol.particle.data.ParticleData;
8 | import com.github.retrooper.packetevents.protocol.particle.data.ParticleDustData;
9 | import com.github.retrooper.packetevents.protocol.particle.type.ParticleType;
10 | import com.github.retrooper.packetevents.protocol.particle.type.ParticleTypes;
11 | import com.github.retrooper.packetevents.util.Vector3d;
12 | import com.github.retrooper.packetevents.util.Vector3f;
13 | import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerParticle;
14 | import eu.okaeri.configs.OkaeriConfig;
15 | import eu.okaeri.configs.annotation.Comment;
16 | import java.awt.Color;
17 |
18 | @SuppressWarnings("unchecked")
19 | public class ParticleSettings extends OkaeriConfig {
20 |
21 | @Comment("# Enable particle animation?")
22 | public boolean enabled = true;
23 |
24 | @Comment("# Particle type - https://javadocs.packetevents.com/com/github/retrooper/packetevents/protocol/particle/type/ParticleTypes.html")
25 | public ParticleType type = ParticleTypes.DUST;
26 | @Comment({
27 | "# Particle color (used only for DUST or ENTITY_EFFECT particle type)",
28 | "# You can set hex color e.g. \"#ca4c45\" or use \"RAINBOW\" to generate rainbow gradient based on x and z coordinates."
29 | })
30 | public ParticleColor color = ParticleColor.RAINBOW;
31 | public int count = 5;
32 | public float scale = 1.0F;
33 | public float maxSpeed = 0.0F;
34 | public float offsetX = 0.2F;
35 | public float offsetY = 0.2F;
36 | public float offsetZ = 0.2F;
37 |
38 | public WrapperPlayServerParticle getParticle(BorderPoint point) {
39 | return getParticle(point, type);
40 | }
41 |
42 | private WrapperPlayServerParticle getParticle(BorderPoint point, ParticleType type) {
43 | T particleData = this.createData(type, point);
44 | Particle> dust = new Particle<>(type, particleData);
45 | return new WrapperPlayServerParticle(
46 | dust,
47 | true,
48 | new Vector3d(point.x(), point.y(), point.z()),
49 | new Vector3f(orElse(offsetX, 0.1F), orElse(offsetY, 0.1F), orElse(offsetZ, 0.1F)),
50 | orElse(maxSpeed, 0.0F),
51 | count,
52 | true
53 | );
54 | }
55 |
56 | private T orElse(T nullable, T or) {
57 | return nullable != null ? nullable : or;
58 | }
59 |
60 | @SuppressWarnings("unchecked")
61 | private T createData(ParticleType type, BorderPoint point) {
62 | if (type.equals(ParticleTypes.DUST)) {
63 | Color color = this.color.getColor(point);
64 | return (T) new ParticleDustData(orElse(scale, 1.0F), color.getRed(), color.getGreen(), color.getBlue());
65 | }
66 |
67 | if (type.equals(ParticleTypes.ENTITY_EFFECT)) {
68 | Color color = this.color.getColor(point);
69 | AlphaColor alphaColor = new AlphaColor(color.getAlpha(), color.getRed(), color.getGreen(), color.getBlue());
70 | return (T) new ParticleColorData(alphaColor);
71 | }
72 |
73 | return ParticleData.emptyData();
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleTypeTransformer.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.animation.particle;
2 |
3 | import com.github.retrooper.packetevents.protocol.particle.type.ParticleType;
4 | import com.github.retrooper.packetevents.protocol.particle.type.ParticleTypes;
5 | import com.github.retrooper.packetevents.resources.ResourceLocation;
6 | import eu.okaeri.configs.schema.GenericsDeclaration;
7 | import eu.okaeri.configs.schema.GenericsPair;
8 | import eu.okaeri.configs.serdes.BidirectionalTransformer;
9 | import eu.okaeri.configs.serdes.SerdesContext;
10 | import java.util.Locale;
11 |
12 | public class ParticleTypeTransformer extends BidirectionalTransformer> {
13 |
14 | @Override
15 | public GenericsPair> getPair() {
16 | return this.generics(GenericsDeclaration.of(String.class), GenericsDeclaration.of(ParticleType.class));
17 | }
18 |
19 | @Override
20 | public ParticleType> leftToRight(String data, SerdesContext serdesContext) {
21 | if (!data.contains(":")) {
22 | data = ResourceLocation.normString(data);
23 | }
24 |
25 | ParticleType> type = ParticleTypes.getByName(data.toLowerCase(Locale.ROOT));
26 | if (type == null) {
27 | throw new IllegalArgumentException("Unknown particle type: " + data);
28 | }
29 |
30 | return type;
31 | }
32 |
33 | @Override
34 | public String rightToLeft(ParticleType> data, SerdesContext serdesContext) {
35 | ResourceLocation location = data.getName();
36 | if (location.getNamespace().equals(ResourceLocation.VANILLA_NAMESPACE)) {
37 | return location.getKey().toUpperCase(Locale.ROOT);
38 | }
39 |
40 | return location.toString();
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/event/BorderHideAsyncEvent.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.event;
2 |
3 | import com.eternalcode.combat.border.BorderPoint;
4 | import java.util.Set;
5 | import org.bukkit.entity.Player;
6 | import org.bukkit.event.Event;
7 | import org.bukkit.event.HandlerList;
8 | import org.jetbrains.annotations.NotNull;
9 |
10 | public class BorderHideAsyncEvent extends Event {
11 |
12 | private static final HandlerList HANDLER_LIST = new HandlerList();
13 |
14 | private final Player player;
15 | private final Set points;
16 |
17 | public BorderHideAsyncEvent(Player player, Set points) {
18 | super(true);
19 | this.player = player;
20 | this.points = points;
21 | }
22 |
23 | public Player getPlayer() {
24 | return this.player;
25 | }
26 |
27 | public Set getPoints() {
28 | return this.points;
29 | }
30 |
31 | @NotNull
32 | @Override
33 | public HandlerList getHandlers() {
34 | return HANDLER_LIST;
35 | }
36 |
37 | public static HandlerList getHandlerList() {
38 | return HANDLER_LIST;
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/event/BorderShowAsyncEvent.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.border.event;
2 |
3 | import com.eternalcode.combat.border.BorderPoint;
4 | import java.util.Set;
5 | import java.util.UUID;
6 | import org.bukkit.entity.Player;
7 | import org.bukkit.event.Event;
8 | import org.bukkit.event.HandlerList;
9 | import org.jetbrains.annotations.NotNull;
10 |
11 | public class BorderShowAsyncEvent extends Event {
12 |
13 | private static final HandlerList HANDLER_LIST = new HandlerList();
14 |
15 | private final Player player;
16 | private Set points;
17 |
18 | public BorderShowAsyncEvent(Player player, Set points) {
19 | super(true);
20 | this.player = player;
21 | this.points = points;
22 | }
23 |
24 | public Player getPlayer() {
25 | return player;
26 | }
27 |
28 | public Set getPoints() {
29 | return points;
30 | }
31 |
32 | public void setPoints(Set points) {
33 | this.points = points;
34 | }
35 |
36 | @NotNull
37 | @Override
38 | public HandlerList getHandlers() {
39 | return HANDLER_LIST;
40 | }
41 |
42 | public static HandlerList getHandlerList() {
43 | return HANDLER_LIST;
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeInitializer.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.bridge;
2 |
3 | @FunctionalInterface
4 | public interface BridgeInitializer {
5 |
6 | void initialize();
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.bridge;
2 |
3 | import com.eternalcode.combat.bridge.placeholder.FightTagPlaceholder;
4 | import com.eternalcode.combat.config.implementation.PluginConfig;
5 | import com.eternalcode.combat.fight.FightManager;
6 | import com.eternalcode.combat.region.DefaultRegionProvider;
7 | import com.eternalcode.combat.region.RegionProvider;
8 | import com.eternalcode.combat.region.WorldGuardRegionProvider;
9 | import org.bukkit.Server;
10 | import org.bukkit.plugin.Plugin;
11 | import org.bukkit.plugin.PluginManager;
12 |
13 | import java.util.logging.Logger;
14 |
15 | public class BridgeService {
16 |
17 | private final PluginConfig pluginConfig;
18 | private final PluginManager pluginManager;
19 | private final Logger logger;
20 | private final Plugin plugin;
21 |
22 | private RegionProvider regionProvider;
23 |
24 | public BridgeService(PluginConfig pluginConfig, PluginManager pluginManager, Logger logger, Plugin plugin) {
25 | this.pluginConfig = pluginConfig;
26 | this.pluginManager = pluginManager;
27 | this.logger = logger;
28 | this.plugin = plugin;
29 | }
30 |
31 | public void init(FightManager fightManager, Server server) {
32 | this.initialize("WorldGuard",
33 | () -> this.regionProvider = new WorldGuardRegionProvider(this.pluginConfig.regions.blockedRegions,
34 | this.pluginConfig),
35 | () -> {
36 | this.regionProvider = new DefaultRegionProvider(this.pluginConfig.regions.restrictedRegionRadius);
37 |
38 | this.logger.warning("WorldGuard is not installed, set to default region provider.");
39 | });
40 |
41 | this.initialize("PlaceholderAPI",
42 | () -> new FightTagPlaceholder(fightManager, server, this.plugin).register(),
43 | () -> this.logger.warning("PlaceholderAPI is not installed, placeholders will not be registered.")
44 | );
45 | }
46 |
47 | private void initialize(String pluginName, BridgeInitializer initializer, Runnable failureHandler) {
48 | if (this.pluginManager.isPluginEnabled(pluginName)) {
49 | initializer.initialize();
50 |
51 | this.logger.info("Successfully initialized " + pluginName + " bridge.");
52 | }
53 | else {
54 | failureHandler.run();
55 | }
56 | }
57 |
58 | public RegionProvider getRegionProvider() {
59 | return this.regionProvider;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.bridge.placeholder;
2 |
3 | import com.eternalcode.combat.fight.FightManager;
4 | import com.eternalcode.combat.fight.FightTag;
5 | import com.eternalcode.combat.util.DurationUtil;
6 | import com.eternalcode.commons.time.DurationParser;
7 | import java.util.Optional;
8 | import me.clip.placeholderapi.expansion.PlaceholderExpansion;
9 | import org.bukkit.OfflinePlayer;
10 | import org.bukkit.Server;
11 | import org.bukkit.entity.Player;
12 | import org.bukkit.plugin.Plugin;
13 | import org.jetbrains.annotations.NotNull;
14 |
15 | public class FightTagPlaceholder extends PlaceholderExpansion {
16 |
17 | private final FightManager fightManager;
18 | private final Server server;
19 | private final Plugin plugin;
20 | private static final String IDENTIFIER = "eternalcombat";
21 |
22 | public FightTagPlaceholder(FightManager fightManager, Server server, Plugin plugin) {
23 | this.fightManager = fightManager;
24 | this.server = server;
25 | this.plugin = plugin;
26 | }
27 |
28 | @Override
29 | public boolean canRegister() {
30 | return true;
31 | }
32 |
33 |
34 | @Override
35 | public @NotNull String getIdentifier() {
36 | return IDENTIFIER;
37 | }
38 |
39 | @Override
40 | public @NotNull String getAuthor() {
41 | return this.plugin.getDescription().getAuthors().get(0);
42 | }
43 |
44 | @Override
45 | public @NotNull String getVersion() {
46 | return this.plugin.getDescription().getVersion();
47 | }
48 |
49 | @Override
50 | public String onRequest(OfflinePlayer player, String identifier) {
51 | if (identifier.equals("remaining_seconds")) {
52 | return this.getFightTag(player)
53 | .map(fightTagInter -> DurationUtil.format(fightTagInter.getRemainingDuration()))
54 | .orElse("");
55 | }
56 |
57 | if (identifier.equals("remaining_millis")) {
58 | return this.getFightTag(player)
59 | .map(fightTag -> DurationParser.TIME_UNITS.format(fightTag.getRemainingDuration()))
60 | .orElse("");
61 | }
62 |
63 | if (identifier.equals("opponent")) {
64 | return this.getTagger(player)
65 | .map(tagger -> tagger.getName())
66 | .orElse("");
67 | }
68 |
69 | if (identifier.equals("opponent_health")) {
70 | return this.getTagger(player)
71 | .map(tagger -> String.format("%.2f", tagger.getHealth()))
72 | .orElse("");
73 | }
74 |
75 | return null;
76 | }
77 |
78 | private @NotNull Optional getTagger(OfflinePlayer player) {
79 | return this.getFightTag(player)
80 | .map(fightTagInter -> fightTagInter.getTagger())
81 | .map(taggerId -> this.server.getPlayer(taggerId));
82 | }
83 |
84 | private Optional getFightTag(OfflinePlayer player) {
85 | Player onlinePlayer = player.getPlayer();
86 |
87 | if (onlinePlayer != null) {
88 | if (!this.fightManager.isInCombat(onlinePlayer.getUniqueId())) {
89 | return Optional.empty();
90 | }
91 |
92 | return Optional.of(this.fightManager.getTag(onlinePlayer.getUniqueId()));
93 | }
94 |
95 | return Optional.empty();
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/ConfigService.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.config;
2 |
3 | import com.eternalcode.combat.border.animation.block.BlockTypeTransformer;
4 | import com.eternalcode.combat.border.animation.particle.ParticleColorTransformer;
5 | import com.eternalcode.combat.border.animation.particle.ParticleTypeTransformer;
6 | import com.eternalcode.multification.bukkit.notice.resolver.sound.SoundBukkitResolver;
7 | import com.eternalcode.multification.notice.resolver.NoticeResolverDefaults;
8 | import com.eternalcode.multification.notice.resolver.NoticeResolverRegistry;
9 | import com.eternalcode.multification.okaeri.MultificationSerdesPack;
10 | import eu.okaeri.configs.ConfigManager;
11 | import eu.okaeri.configs.OkaeriConfig;
12 | import eu.okaeri.configs.serdes.OkaeriSerdesPack;
13 | import eu.okaeri.configs.serdes.SerdesRegistry;
14 | import eu.okaeri.configs.serdes.commons.SerdesCommons;
15 | import eu.okaeri.configs.yaml.bukkit.serdes.SerdesBukkit;
16 |
17 | import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer;
18 | import java.io.File;
19 | import java.util.HashSet;
20 | import java.util.Set;
21 |
22 | public class ConfigService {
23 |
24 | private final Set configs = new HashSet<>();
25 |
26 | public T create(Class config, File file) {
27 | T configFile = ConfigManager.create(config);
28 |
29 | YamlSnakeYamlConfigurer configurer = new YamlSnakeYamlConfigurer();
30 | NoticeResolverRegistry noticeRegistry = NoticeResolverDefaults.createRegistry()
31 | .registerResolver(new SoundBukkitResolver());
32 |
33 | configFile.withConfigurer(configurer,
34 | new SerdesCommons(),
35 | new SerdesBukkit(),
36 | new MultificationSerdesPack(noticeRegistry),
37 | new DefaultSerdesPack()
38 | );
39 |
40 | configFile.withBindFile(file);
41 | configFile.withRemoveOrphans(true);
42 | configFile.saveDefaults();
43 | configFile.load(true);
44 |
45 | this.configs.add(configFile);
46 |
47 | return configFile;
48 | }
49 |
50 | public void reload() {
51 | for (OkaeriConfig config : this.configs) {
52 | config.load();
53 | }
54 | }
55 |
56 | private static class DefaultSerdesPack implements OkaeriSerdesPack {
57 | @Override
58 | public void register(SerdesRegistry config) {
59 | config.register(new ParticleColorTransformer());
60 | config.register(new ParticleTypeTransformer());
61 | config.register(new BlockTypeTransformer());
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/AdminSettings.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.config.implementation;
2 |
3 | import eu.okaeri.configs.OkaeriConfig;
4 | import eu.okaeri.configs.annotation.Comment;
5 |
6 | public class AdminSettings extends OkaeriConfig {
7 | @Comment({
8 | "# Exclude server administrators from combat tagging and being tagged.",
9 | "# Set to 'true' to prevent admins from being tagged or tagging others.",
10 | "# Set to 'false' to allow admins to participate in combat."
11 | })
12 | public boolean excludeAdminsFromCombat = false;
13 |
14 | @Comment({
15 | "# Exclude players in creative mode from combat tagging and being tagged.",
16 | "# Set to 'true' to prevent creative mode players from being tagged or tagging others.",
17 | "# Set to 'false' to allow creative mode players to participate in combat."
18 | })
19 | public boolean excludeCreativePlayersFromCombat = false;
20 | }
21 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/BlockPlacementSettings.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.config.implementation;
2 |
3 | import eu.okaeri.configs.OkaeriConfig;
4 | import eu.okaeri.configs.annotation.Comment;
5 | import java.util.List;
6 | import org.bukkit.Material;
7 |
8 | public class BlockPlacementSettings extends OkaeriConfig {
9 | @Comment({
10 | "# Prevent players from placing blocks during combat.",
11 | "# Set to 'true' to block block placement while in combat."
12 | })
13 | public boolean disableBlockPlacing = true;
14 |
15 | @Comment({
16 | "# Restrict block placement above or below a specific Y coordinate.",
17 | "# Available modes: ABOVE (blocks cannot be placed above the Y coordinate), BELOW (blocks cannot be placed below the Y coordinate)."
18 | })
19 | public BlockPlacingMode blockPlacementMode = BlockPlacingMode.ABOVE;
20 |
21 | @Comment({
22 | "# Custom name for the block placement mode used in messages.",
23 | "# This name will be displayed in notifications related to block placement restrictions."
24 | })
25 | public String blockPlacementModeDisplayName = "above";
26 |
27 | @Comment({
28 | "# Define the Y coordinate for block placement restrictions.",
29 | "# This value is relative to the selected block placement mode (ABOVE or BELOW)."
30 | })
31 | public int blockPlacementYCoordinate = 40;
32 | @Comment({
33 | "# Restrict the placement of specific blocks during combat.",
34 | "# Add blocks to this list to prevent their placement. Leave the list empty to block all blocks.",
35 | "# Note: This feature requires 'disableBlockPlacing' to be enabled."
36 | })
37 | public List restrictedBlockTypes = List.of();
38 |
39 | public enum BlockPlacingMode {
40 | ABOVE,
41 | BELOW
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CombatSettings.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.config.implementation;
2 |
3 | import com.eternalcode.combat.WhitelistBlacklistMode;
4 | import eu.okaeri.configs.OkaeriConfig;
5 | import eu.okaeri.configs.annotation.Comment;
6 | import java.util.List;
7 | import org.bukkit.entity.EntityType;
8 | import org.bukkit.event.EventPriority;
9 | import org.bukkit.event.entity.EntityDamageEvent;
10 | import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
11 |
12 | public class CombatSettings extends OkaeriConfig {
13 | @Comment({
14 | "# Automatically release the attacker from combat when the victim dies.",
15 | "# Set to 'true' to enable this feature, or 'false' to keep the attacker in combat."
16 | })
17 | public boolean releaseAttackerOnVictimDeath = true;
18 |
19 | @Comment({
20 | "# Disable the use of elytra during combat.",
21 | "# Set to 'true' to prevent players from using elytra while in combat."
22 | })
23 | public boolean disableElytraUsage = true;
24 |
25 | @Comment({
26 | "# Disable the use of elytra when a player takes damage.",
27 | "# Set to 'true' to disable elytra usage upon taking damage, even when the player is mid-air."
28 | })
29 | public boolean disableElytraOnDamage = true;
30 |
31 | @Comment({
32 | "# Prevent players from flying during combat.",
33 | "# Flying players will fall to the ground if this is enabled."
34 | })
35 | public boolean disableFlying = true;
36 |
37 | @Comment({
38 | "# Prevent players from opening their inventory during combat.",
39 | "# Set to 'true' to block inventory access while in combat."
40 | })
41 | public boolean disableInventoryAccess = false;
42 |
43 | @Comment({
44 | "# Enable or disable combat logging for damage caused by non-player entities.",
45 | "# Set to 'true' to log damage from non-player sources, or 'false' to disable this feature."
46 | })
47 | public boolean enableDamageCauseLogging = false;
48 |
49 | @Comment({
50 | "# Set the mode for logging damage causes.",
51 | "# Available modes: WHITELIST (only listed causes are logged), BLACKLIST (all causes except listed ones are logged)."
52 | })
53 | public WhitelistBlacklistMode damageCauseRestrictionMode = WhitelistBlacklistMode.WHITELIST;
54 |
55 | @Comment({
56 | "# List of damage causes to be logged based on the selected mode.",
57 | "# In WHITELIST mode, only these causes are logged. In BLACKLIST mode, all causes except these are logged.",
58 | "# For a full list of damage causes, visit: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html"
59 | })
60 | public List loggedDamageCauses = List.of(
61 | DamageCause.LAVA,
62 | DamageCause.CONTACT,
63 | DamageCause.FIRE,
64 | DamageCause.FIRE_TICK
65 | );
66 |
67 | @Comment({
68 | "# List of projectile types that do not trigger combat tagging.",
69 | "# Players hit by these projectiles will not be tagged as in combat.",
70 | "# For a full list of entity types, visit: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html"
71 | })
72 | public List ignoredProjectileTypes = List.of(
73 | EntityType.ENDER_PEARL,
74 | EntityType.EGG
75 | );
76 |
77 | @Comment({
78 | "# The event priority at which quit punishments should be handled.",
79 | "# This determines when the plugin processes combat log punishment during PlayerQuitEvent.",
80 | "# Options: LOWEST, LOW, NORMAL, HIGH, HIGHEST, MONITOR",
81 | "# Tip: Set to LOWEST or LOW if you want quit punishments to happen before most other plugins.",
82 | "# Default: NORMAL"
83 | })
84 | public EventPriority quitPunishmentEventPriority = EventPriority.NORMAL;
85 |
86 | @Comment({
87 | "# List of kick reasons where players will NOT be punished for combat logging.",
88 | "# If this list is empty, players are ALWAYS punished when kicked during combat.",
89 | "# If one of the listed phrases is found in the kick reason (case-insensitive),",
90 | "# the player will NOT be punished.",
91 | "# Example: 'Timed out', 'Kicked for inactivity', etc.",
92 | "# To always punish players on kick, set: whitelistedKickReasons: []"
93 | })
94 | public List whitelistedKickReasons = List.of("Kicked for inactivity", "Timed out", "Server is restarting");
95 | }
96 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CommandSettings.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.config.implementation;
2 |
3 | import com.eternalcode.combat.WhitelistBlacklistMode;
4 | import eu.okaeri.configs.OkaeriConfig;
5 | import eu.okaeri.configs.annotation.Comment;
6 | import java.util.List;
7 |
8 | public class CommandSettings extends OkaeriConfig {
9 | @Comment({
10 | "# Set the mode for command restrictions during combat.",
11 | "# Available modes: WHITELIST (only listed commands are allowed), BLACKLIST (listed commands are blocked)."
12 | })
13 | public WhitelistBlacklistMode commandRestrictionMode = WhitelistBlacklistMode.BLACKLIST;
14 |
15 | @Comment({
16 | "# List of commands affected by the command restriction mode.",
17 | "# In BLACKLIST mode, these commands are blocked. In WHITELIST mode, only these commands are allowed."
18 | })
19 | public List restrictedCommands = List.of(
20 | "gamemode",
21 | "spawn",
22 | "tp",
23 | "tpa",
24 | "tpaccept"
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.config.implementation;
2 |
3 | import com.eternalcode.combat.border.BorderSettings;
4 | import com.eternalcode.combat.fight.drop.DropSettings;
5 | import com.eternalcode.combat.fight.effect.FightEffectSettings;
6 | import com.eternalcode.combat.fight.knockback.KnockbackSettings;
7 | import com.eternalcode.combat.fight.pearl.FightPearlSettings;
8 | import eu.okaeri.configs.OkaeriConfig;
9 | import eu.okaeri.configs.annotation.Comment;
10 | import java.time.Duration;
11 | import java.util.List;
12 |
13 | public class PluginConfig extends OkaeriConfig {
14 |
15 | @Comment(" ")
16 | @Comment("# _____ _ EternalCombat _ _____ _ _ ")
17 | @Comment("# | ___| | o()xxx[{:::::::::> | / __ \\ | | | | ")
18 | @Comment("# | |__ | |_ ___ _ __ _ __ __ _| | / \\/ ___ _ __ ___ | |__ __ _| |_ ")
19 | @Comment("# | __|| __/ _ \\ '__| '_ \\ / _` | | | / _ \\| '_ ` _ \\| '_ \\ / _` | __| ")
20 | @Comment("# | |___| || __/ | | | | | (_| | | \\__/\\ (_) | | | | | | |_) | (_| | |_ ")
21 | @Comment("# \\____/ \\__\\___|_| |_| |_|\\__,_|_|\\____/\\___/|_| |_| |_|_.__/ \\__,_|\\__| ")
22 | @Comment(" ")
23 |
24 | @Comment({
25 | " ",
26 | "# Settings for the plugin.",
27 | "# Modify these to customize the plugin's behavior."
28 | })
29 | public Settings settings = new Settings();
30 |
31 | @Comment({
32 | " ",
33 | "# Settings related to Ender Pearls.",
34 | "# Configure cooldowns, restrictions, and other behaviors for Ender Pearls during combat."
35 | })
36 | public FightPearlSettings pearl = new FightPearlSettings();
37 |
38 | @Comment({
39 | " ",
40 | "# Custom effects applied during combat.",
41 | "# Configure effects like blindness, slowness, or other debuffs that are applied to players in combat."
42 | })
43 | public FightEffectSettings effect = new FightEffectSettings();
44 |
45 | @Comment({
46 | " ",
47 | "# Customize how items are dropped when a player dies during combat.",
48 | "# Configure whether items drop, how they drop, and any additional rules for item drops."
49 | })
50 | public DropSettings drop = new DropSettings();
51 |
52 | @Comment({
53 | " ",
54 | "# Settings related to knockback during combat.",
55 | "# Configure knockback settings and behaviors for players in combat."
56 | })
57 | public KnockbackSettings knockback = new KnockbackSettings();
58 |
59 | @Comment({
60 | " ",
61 | "# Border Settings",
62 | "# Configure the border that appears during combat.",
63 | })
64 | public BorderSettings border = new BorderSettings();
65 |
66 | @Comment({
67 | " ",
68 | "# Settings related to block placement during combat.",
69 | "# Configure restrictions and behaviors for block placement while players are in combat."
70 | })
71 | public BlockPlacementSettings blockPlacement = new BlockPlacementSettings();
72 |
73 | @Comment({
74 | " ",
75 | "# Settings related to commands during combat.",
76 | "# Configure command restrictions and behaviors for players in combat."
77 | })
78 | public CommandSettings commands = new CommandSettings();
79 |
80 | @Comment({
81 | " ",
82 | "# Settings related to the plugin's admin commands and features.",
83 | "# Configure admin-specific settings and behaviors for the plugin."
84 | })
85 | public AdminSettings admin = new AdminSettings();
86 |
87 | @Comment({
88 | " ",
89 | "# Settings related to regions.",
90 | "# Configure region-specific settings and behaviors for combat."
91 | })
92 | public RegionSettings regions = new RegionSettings();
93 |
94 | @Comment({
95 | " ",
96 | "# Settings related to combat and player tagging.",
97 | "# Configure combat rules, and behaviors for player tagging."
98 | })
99 | public CombatSettings combat = new CombatSettings();
100 |
101 | @Comment({
102 | " ",
103 | "# Customize the messages displayed by the plugin.",
104 | "# Modify these to change the text and formatting of notifications and alerts."
105 | })
106 | public MessagesSettings messagesSettings = new MessagesSettings();
107 |
108 | public static class Settings extends OkaeriConfig {
109 | @Comment({
110 | "# Notify players about new plugin updates when they join the server.",
111 | "# Set to 'true' to enable update notifications, or 'false' to disable them."
112 | })
113 | public boolean notifyAboutUpdates = true;
114 |
115 | @Comment({
116 | "# The duration (in seconds) that a player remains in combat after being attacked.",
117 | "# After this time expires, the player will no longer be considered in combat."
118 | })
119 | public Duration combatTimerDuration = Duration.ofSeconds(20);
120 |
121 | @Comment({
122 | "# List of worlds where combat logging is disabled.",
123 | "# Players in these worlds will not be tagged or affected by combat rules."
124 | })
125 | public List ignoredWorlds = List.of(
126 | "your_world"
127 | );
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/RegionSettings.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.config.implementation;
2 |
3 | import eu.okaeri.configs.OkaeriConfig;
4 | import eu.okaeri.configs.annotation.Comment;
5 | import java.util.Collections;
6 | import java.util.List;
7 |
8 | public class RegionSettings extends OkaeriConfig {
9 | @Comment({
10 | "# List of regions where combat is restricted.",
11 | "# Players in these regions will not be able to engage in combat."
12 | })
13 | public List blockedRegions = Collections.singletonList("your_region");
14 |
15 | @Comment({
16 | "# Prevent players from entering regions where PVP is disabled by WorldGuard.",
17 | "# Set to 'true' to enforce this restriction, or 'false' to allow PVP in all regions."
18 | })
19 | public boolean preventPvpInRegions = true;
20 |
21 | @Comment({
22 | "# Define the radius of restricted regions if WorldGuard is not used.",
23 | "# This setting is based on the default spawn region."
24 | })
25 | public int restrictedRegionRadius = 10;
26 | }
27 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/DynamicListener.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.event;
2 |
3 | import org.bukkit.event.Listener;
4 |
5 | public interface DynamicListener extends Listener {
6 |
7 | void onEvent(E event);
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/EventManager.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.event;
2 |
3 | import org.bukkit.event.Event;
4 | import org.bukkit.event.EventPriority;
5 | import org.bukkit.event.Listener;
6 | import org.bukkit.plugin.Plugin;
7 |
8 | public class EventManager {
9 |
10 | private final Plugin plugin;
11 |
12 | public EventManager(Plugin plugin) {
13 | this.plugin = plugin;
14 | }
15 |
16 | public T publishEvent(T event) {
17 | this.plugin.getServer().getPluginManager().callEvent(event);
18 |
19 | return event;
20 | }
21 |
22 | public void subscribe(Listener... listeners) {
23 | for (Listener listener : listeners) {
24 | plugin.getServer().getPluginManager().registerEvents(listener, plugin);
25 | }
26 | }
27 |
28 | public > void subscribe(Class type, EventPriority priority, L listener) {
29 | plugin.getServer().getPluginManager().registerEvents(listener, plugin);
30 | plugin.getServer().getPluginManager().registerEvent(type, listener, priority, (l, event) -> {
31 | if (type.isInstance(event)) {
32 | listener.onEvent(type.cast(event));
33 | }
34 | }, plugin);
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightManagerImpl.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight;
2 |
3 | import com.eternalcode.combat.event.EventManager;
4 |
5 | import com.eternalcode.combat.fight.event.CauseOfTag;
6 | import com.eternalcode.combat.fight.event.CauseOfUnTag;
7 | import com.eternalcode.combat.fight.event.FightTagEvent;
8 | import com.eternalcode.combat.fight.event.FightUntagEvent;
9 | import java.time.Duration;
10 | import java.time.Instant;
11 | import java.util.Collection;
12 | import java.util.Collections;
13 | import java.util.Map;
14 | import java.util.UUID;
15 | import java.util.concurrent.ConcurrentHashMap;
16 | import org.jetbrains.annotations.ApiStatus;
17 | import org.jetbrains.annotations.Nullable;
18 |
19 | public class FightManagerImpl implements FightManager {
20 |
21 | private final Map fights = new ConcurrentHashMap<>();
22 | private final EventManager eventManager;
23 |
24 | public FightManagerImpl(EventManager eventManager) {
25 | this.eventManager = eventManager;
26 | }
27 |
28 | @Override
29 | public boolean isInCombat(UUID player) {
30 | if (!this.fights.containsKey(player)) {
31 | return false;
32 | }
33 |
34 | FightTag fightTag = this.fights.get(player);
35 |
36 | return !fightTag.isExpired();
37 | }
38 |
39 | @Override
40 | public FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag) {
41 | FightUntagEvent event = this.eventManager.publishEvent(new FightUntagEvent(player, causeOfUnTag));
42 | if (event.isCancelled()) {
43 | return event;
44 | }
45 |
46 | this.fights.remove(player);
47 | return event;
48 | }
49 |
50 | @Override
51 | public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag) {
52 | return this.tag(target, delay, causeOfTag, null);
53 | }
54 |
55 | @ApiStatus.Experimental
56 | @Override
57 | public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger) {
58 | FightTagEvent event = this.eventManager.publishEvent(new FightTagEvent(target, causeOfTag));
59 |
60 | if (event.isCancelled()) {
61 | return event;
62 | }
63 | Instant now = Instant.now();
64 | Instant endOfCombatLog = now.plus(delay);
65 |
66 | FightTag fightTag = new FightTagImpl(target, endOfCombatLog, tagger);
67 |
68 | this.fights.put(target, fightTag);
69 | return event;
70 | }
71 |
72 | @Override
73 | public Collection getFights() {
74 | return Collections.unmodifiableCollection(this.fights.values());
75 | }
76 |
77 | @Override
78 | public FightTag getTag(UUID target) {
79 | return this.fights.get(target);
80 | }
81 |
82 | @Override
83 | public void untagAll() {
84 | this.fights.clear();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTagImpl.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight;
2 |
3 | import java.time.Duration;
4 | import java.time.Instant;
5 | import java.util.UUID;
6 | import org.jetbrains.annotations.ApiStatus;
7 | import org.jetbrains.annotations.Nullable;
8 |
9 | public class FightTagImpl implements FightTag {
10 |
11 | private final UUID taggedPlayer;
12 | private final Instant endOfCombatLog;
13 | private final @Nullable UUID tagger;
14 |
15 | FightTagImpl(UUID personToAddCombat, Instant endOfCombatLog, @Nullable UUID tagger) {
16 | this.taggedPlayer = personToAddCombat;
17 | this.endOfCombatLog = endOfCombatLog;
18 | this.tagger = tagger;
19 | }
20 |
21 | @Override
22 | public UUID getTaggedPlayer() {
23 | return this.taggedPlayer;
24 | }
25 |
26 | @Override
27 | public Instant getEndOfCombatLog() {
28 | return this.endOfCombatLog;
29 | }
30 |
31 | @Override
32 | public boolean isExpired() {
33 | return Instant.now().isAfter(this.endOfCombatLog);
34 | }
35 |
36 | @Override
37 | public Duration getRemainingDuration() {
38 | Duration between = Duration.between(Instant.now(), this.endOfCombatLog);
39 |
40 | if (between.isNegative()) {
41 | return Duration.ZERO;
42 | }
43 |
44 | return between;
45 | }
46 |
47 | @ApiStatus.Experimental
48 | @Override
49 | public @Nullable UUID getTagger() {
50 | return this.tagger;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight;
2 |
3 | import com.eternalcode.combat.config.implementation.PluginConfig;
4 | import com.eternalcode.combat.fight.event.CauseOfUnTag;
5 | import com.eternalcode.combat.notification.NoticeService;
6 | import com.eternalcode.combat.util.DurationUtil;
7 | import org.bukkit.Server;
8 | import org.bukkit.entity.Player;
9 |
10 | import java.time.Duration;
11 | import java.util.UUID;
12 |
13 | public class FightTask implements Runnable {
14 |
15 | private final Server server;
16 | private final PluginConfig config;
17 | private final FightManager fightManager;
18 | private final NoticeService noticeService;
19 |
20 | public FightTask(Server server, PluginConfig config, FightManager fightManager, NoticeService noticeService) {
21 | this.server = server;
22 | this.config = config;
23 | this.fightManager = fightManager;
24 | this.noticeService = noticeService;
25 | }
26 |
27 | @Override
28 | public void run() {
29 | for (FightTag fightTag : this.fightManager.getFights()) {
30 | Player player = this.server.getPlayer(fightTag.getTaggedPlayer());
31 |
32 | if (player == null) {
33 | continue;
34 | }
35 |
36 | UUID playerUniqueId = player.getUniqueId();
37 |
38 | if (fightTag.isExpired()) {
39 | this.fightManager.untag(playerUniqueId, CauseOfUnTag.TIME_EXPIRED);
40 | return;
41 | }
42 |
43 | Duration remaining = fightTag.getRemainingDuration();
44 |
45 | this.noticeService.create()
46 | .player(player.getUniqueId())
47 | .notice(this.config.messagesSettings.combatNotification)
48 | .placeholder("{TIME}", DurationUtil.format(remaining, this.config.messagesSettings.withoutMillis))
49 | .send();
50 |
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightMessageController.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.controller;
2 |
3 | import com.eternalcode.combat.config.implementation.PluginConfig;
4 | import com.eternalcode.combat.fight.FightManager;
5 | import com.eternalcode.combat.fight.event.FightTagEvent;
6 | import com.eternalcode.combat.fight.event.FightUntagEvent;
7 | import com.eternalcode.combat.notification.NoticeService;
8 | import org.bukkit.Server;
9 | import org.bukkit.entity.Player;
10 | import org.bukkit.event.EventHandler;
11 | import org.bukkit.event.EventPriority;
12 | import org.bukkit.event.Listener;
13 |
14 | public class FightMessageController implements Listener {
15 |
16 | private final FightManager fightManager;
17 | private final NoticeService noticeService;
18 | private final PluginConfig config;
19 | private final Server server;
20 |
21 | public FightMessageController(FightManager fightManager, NoticeService noticeService, PluginConfig config, Server server) {
22 | this.fightManager = fightManager;
23 | this.noticeService = noticeService;
24 | this.config = config;
25 | this.server = server;
26 | }
27 |
28 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
29 | void onTag(FightTagEvent event) {
30 | Player player = this.server.getPlayer(event.getPlayer());
31 |
32 | if (player == null) {
33 | throw new IllegalStateException("Player cannot be null!");
34 | }
35 |
36 | if (this.fightManager.isInCombat(player.getUniqueId())) {
37 | return;
38 | }
39 |
40 | this.noticeService.create()
41 | .player(player.getUniqueId())
42 | .notice(this.config.messagesSettings.playerTagged)
43 | .send();
44 | }
45 |
46 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
47 | void onUnTag(FightUntagEvent event) {
48 | Player player = this.server.getPlayer(event.getPlayer());
49 |
50 | if (player == null) {
51 | throw new IllegalStateException("Player cannot be null!");
52 | }
53 |
54 | this.noticeService.create()
55 | .player(player.getUniqueId())
56 | .notice(this.config.messagesSettings.playerUntagged)
57 | .send();
58 |
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightTagController.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.controller;
2 |
3 | import com.eternalcode.combat.WhitelistBlacklistMode;
4 | import com.eternalcode.combat.config.implementation.PluginConfig;
5 | import com.eternalcode.combat.fight.FightManager;
6 | import com.eternalcode.combat.fight.event.CauseOfTag;
7 | import org.bukkit.GameMode;
8 | import org.bukkit.entity.EntityType;
9 | import org.bukkit.entity.Player;
10 | import org.bukkit.entity.Projectile;
11 | import org.bukkit.event.EventHandler;
12 | import org.bukkit.event.EventPriority;
13 | import org.bukkit.event.Listener;
14 | import org.bukkit.event.entity.EntityDamageByEntityEvent;
15 | import org.bukkit.event.entity.EntityDamageEvent;
16 |
17 | import javax.annotation.Nullable;
18 | import java.time.Duration;
19 | import java.util.List;
20 | import java.util.UUID;
21 |
22 | public class FightTagController implements Listener {
23 |
24 | private final FightManager fightManager;
25 | private final PluginConfig config;
26 |
27 | public FightTagController(FightManager fightManager, PluginConfig config) {
28 | this.fightManager = fightManager;
29 | this.config = config;
30 | }
31 |
32 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
33 | void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
34 | // Thorns are ignored because both users should be in combat from hitting each other
35 | if (event.getCause().equals(EntityDamageEvent.DamageCause.THORNS)) {
36 | return;
37 | }
38 |
39 | if (!(event.getEntity() instanceof Player attackedPlayerByPerson)) {
40 | return;
41 | }
42 |
43 | List disabledProjectileEntities = this.config.combat.ignoredProjectileTypes;
44 |
45 | if (event.getDamager() instanceof Projectile projectile && disabledProjectileEntities.contains(projectile.getType())) {
46 | return;
47 | }
48 |
49 | if (this.isPlayerInDisabledWorld(attackedPlayerByPerson)) {
50 | return;
51 | }
52 |
53 | Player attacker = this.getDamager(event);
54 |
55 | if (attacker == null) {
56 | return;
57 | }
58 |
59 | if (this.cannotBeTagged(attacker)) {
60 | return;
61 | }
62 |
63 | if (this.cannotBeTagged(attackedPlayerByPerson)) {
64 | return;
65 | }
66 |
67 | if (this.config.combat.disableFlying) {
68 | if (attackedPlayerByPerson.isFlying()) {
69 | attackedPlayerByPerson.setFlying(false);
70 | attackedPlayerByPerson.setAllowFlight(false);
71 | }
72 |
73 | if (attacker.isFlying()) {
74 | attacker.setFlying(false);
75 | attacker.setAllowFlight(false);
76 | }
77 | }
78 |
79 | Duration combatTime = this.config.settings.combatTimerDuration;
80 | UUID attackedUniqueId = attackedPlayerByPerson.getUniqueId();
81 | UUID attackerUniqueId = attacker.getUniqueId();
82 |
83 | this.fightManager.tag(attackedUniqueId, combatTime, CauseOfTag.PLAYER, attackerUniqueId);
84 | this.fightManager.tag(attackerUniqueId, combatTime, CauseOfTag.PLAYER, attackedUniqueId);
85 | }
86 |
87 |
88 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
89 | void onEntityDamage(EntityDamageEvent event) {
90 | if (!this.config.combat.enableDamageCauseLogging) {
91 | return;
92 | }
93 |
94 | if (!(event.getEntity() instanceof Player player)) {
95 | return;
96 | }
97 |
98 | if (this.isPlayerInDisabledWorld(player)) {
99 | return;
100 | }
101 |
102 | if (this.cannotBeTagged(player)) {
103 | return;
104 | }
105 |
106 | boolean hasBypass = player.hasPermission("eternalcombat.bypass");
107 | if (hasBypass) {
108 | return;
109 | }
110 |
111 | Duration combatTime = this.config.settings.combatTimerDuration;
112 | UUID uuid = player.getUniqueId();
113 |
114 | List damageCauses = this.config.combat.loggedDamageCauses;
115 | WhitelistBlacklistMode mode = this.config.combat.damageCauseRestrictionMode;
116 |
117 | EntityDamageEvent.DamageCause cause = event.getCause();
118 |
119 | boolean shouldLog = mode.shouldBlock(damageCauses.contains(cause));
120 |
121 | if (shouldLog) {
122 | return;
123 | }
124 |
125 | this.fightManager.tag(uuid, combatTime, CauseOfTag.NON_PLAYER);
126 | }
127 |
128 |
129 | @Nullable
130 | Player getDamager(EntityDamageByEntityEvent event) {
131 | if (event.getDamager() instanceof Player damager) {
132 | return damager;
133 | }
134 |
135 | if (event.getDamager() instanceof Projectile projectile && projectile.getShooter() instanceof Player shooter) {
136 | return shooter;
137 | }
138 |
139 | return null;
140 | }
141 |
142 | private boolean isPlayerInDisabledWorld(Player player) {
143 | String worldName = player.getWorld().getName();
144 |
145 | return this.config.settings.ignoredWorlds.contains(worldName);
146 | }
147 |
148 | private boolean cannotBeTagged(Player player) {
149 | boolean hasBypass = player.hasPermission("eternalcombat.bypass");
150 | if (hasBypass) {
151 | return true;
152 | }
153 | if (player.getGameMode().equals(GameMode.CREATIVE) && this.config.admin.excludeCreativePlayersFromCombat) {
154 | return true;
155 | }
156 |
157 | return player.isOp() && this.config.admin.excludeAdminsFromCombat;
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.controller;
2 |
3 | import com.eternalcode.combat.fight.FightManager;
4 | import com.eternalcode.combat.fight.event.CauseOfUnTag;
5 | import com.eternalcode.combat.config.implementation.PluginConfig;
6 | import com.eternalcode.combat.fight.logout.LogoutService;
7 | import org.bukkit.entity.Player;
8 | import org.bukkit.event.EventHandler;
9 | import org.bukkit.event.Listener;
10 | import org.bukkit.event.entity.PlayerDeathEvent;
11 |
12 | public class FightUnTagController implements Listener {
13 |
14 | private final FightManager fightManager;
15 | private final PluginConfig config;
16 | private final LogoutService logoutService;
17 |
18 | public FightUnTagController(FightManager fightManager, PluginConfig config, LogoutService logoutService) {
19 | this.fightManager = fightManager;
20 | this.config = config;
21 | this.logoutService = logoutService;
22 | }
23 |
24 | @EventHandler
25 | void onPlayerDeath(PlayerDeathEvent event) {
26 | Player player = event.getEntity();
27 | Player killer = player.getKiller();
28 |
29 | if (!this.fightManager.isInCombat(player.getUniqueId())) {
30 | return;
31 | }
32 |
33 | CauseOfUnTag cause = this.getDeathCause(player, killer);
34 |
35 | this.fightManager.untag(player.getUniqueId(), cause);
36 |
37 | if (killer != null && this.config.combat.releaseAttackerOnVictimDeath) {
38 | this.fightManager.untag(killer.getUniqueId(), CauseOfUnTag.ATTACKER_RELEASE);
39 | }
40 | }
41 |
42 | private CauseOfUnTag getDeathCause(Player player, Player killer) {
43 | if (this.logoutService.hasLoggedOut(player.getUniqueId())) {
44 | return CauseOfUnTag.LOGOUT;
45 | }
46 |
47 | if (killer == null) {
48 | return CauseOfUnTag.DEATH;
49 | }
50 |
51 | if (this.fightManager.isInCombat(killer.getUniqueId())) {
52 | return CauseOfUnTag.DEATH_BY_PLAYER;
53 | }
54 |
55 | return CauseOfUnTag.DEATH;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropController.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.drop;
2 |
3 | import com.eternalcode.combat.event.DynamicListener;
4 | import com.eternalcode.combat.fight.FightManager;
5 | import org.bukkit.Material;
6 | import org.bukkit.entity.Player;
7 | import org.bukkit.event.EventHandler;
8 | import org.bukkit.event.entity.PlayerDeathEvent;
9 | import org.bukkit.event.player.PlayerRespawnEvent;
10 | import org.bukkit.inventory.ItemStack;
11 | import org.bukkit.inventory.PlayerInventory;
12 | import org.bukkit.inventory.meta.SkullMeta;
13 |
14 | import java.util.List;
15 | import java.util.UUID;
16 | import java.util.concurrent.ThreadLocalRandom;
17 |
18 | public class DropController implements DynamicListener {
19 |
20 | private final DropService dropService;
21 | private final DropKeepInventoryService keepInventoryManager;
22 | private final DropSettings dropSettings;
23 | private final FightManager fightManager;
24 |
25 | public DropController(DropService dropService, DropKeepInventoryService keepInventoryManager, DropSettings dropSettings, FightManager fightManager) {
26 | this.dropService = dropService;
27 | this.keepInventoryManager = keepInventoryManager;
28 | this.dropSettings = dropSettings;
29 | this.fightManager = fightManager;
30 | }
31 |
32 | @Override
33 | public void onEvent(PlayerDeathEvent event) {
34 | Player player = event.getEntity();
35 | UUID uuid = player.getUniqueId();
36 | DropType dropType = this.dropSettings.dropType;
37 | boolean inCombat = this.fightManager.isInCombat(uuid);
38 |
39 | if (shouldHeadDrop(inCombat)) {
40 | addHeadDrop(event, player);
41 | }
42 |
43 | if (dropType == DropType.UNCHANGED || !inCombat) {
44 | return;
45 | }
46 |
47 | List drops = event.getDrops();
48 |
49 | Drop drop = Drop.builder()
50 | .player(player)
51 | .killer(player.getKiller())
52 | .droppedItems(drops)
53 | .droppedExp(player.getTotalExperience())
54 | .build();
55 |
56 | DropResult result = this.dropService.modify(dropType, drop);
57 |
58 | if (result == null) {
59 | return;
60 | }
61 |
62 | drops.clear();
63 | drops.addAll(result.droppedItems());
64 |
65 | this.keepInventoryManager.addItems(uuid, result.removedItems());
66 |
67 | if (this.dropSettings.affectExperience) {
68 | event.setDroppedExp(drop.getDroppedExp());
69 | }
70 | }
71 |
72 | private boolean shouldHeadDrop(boolean isCombat) {
73 | if (this.dropSettings.headDropOnlyInCombat && !isCombat) {
74 | return false;
75 | }
76 |
77 | if (!this.dropSettings.headDropEnabled || this.dropSettings.headDropChance <= 0.0) {
78 | return false;
79 | }
80 |
81 | return ThreadLocalRandom.current().nextDouble(0, 100) <= this.dropSettings.headDropChance;
82 | }
83 |
84 | private void addHeadDrop(PlayerDeathEvent event, Player player) {
85 | ItemStack head = new ItemStack(Material.PLAYER_HEAD);
86 |
87 | if (head.getItemMeta() instanceof SkullMeta meta) {
88 | String killerName = player.getKiller() != null ? player.getKiller().getName() : "Unknown";
89 | String displayName = this.dropSettings.headDropDisplayName
90 | .replace("{PLAYER}", player.getName())
91 | .replace("{KILLER}", killerName);
92 |
93 | meta.setOwningPlayer(player);
94 | meta.setDisplayName(displayName);
95 |
96 | if (!this.dropSettings.headDropLore.isEmpty()) {
97 | List lore = this.dropSettings.headDropLore.stream()
98 | .map(line -> line.replace("{PLAYER}", player.getName()).replace("{KILLER}", killerName))
99 | .toList();
100 | meta.setLore(lore);
101 | }
102 |
103 | head.setItemMeta(meta);
104 | }
105 |
106 | event.getDrops().add(head);
107 | }
108 |
109 | @EventHandler
110 | public void onPlayerRespawn(PlayerRespawnEvent event) {
111 | Player player = event.getPlayer();
112 | UUID playerUniqueId = player.getUniqueId();
113 |
114 | if (this.keepInventoryManager.hasItems(playerUniqueId)) {
115 | PlayerInventory playerInventory = player.getInventory();
116 |
117 | ItemStack[] itemsToGive = this.keepInventoryManager.nextItems(playerUniqueId)
118 | .toArray(new ItemStack[0]);
119 |
120 | playerInventory.addItem(itemsToGive);
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropKeepInventoryServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.drop;
2 |
3 | import org.bukkit.inventory.ItemStack;
4 |
5 | import java.util.ArrayList;
6 | import java.util.Collections;
7 | import java.util.HashMap;
8 | import java.util.List;
9 | import java.util.Map;
10 | import java.util.UUID;
11 |
12 | public class DropKeepInventoryServiceImpl implements DropKeepInventoryService {
13 |
14 | private final Map> itemsToGiveAfterRespawn = new HashMap<>();
15 |
16 | @Override
17 | public void addItem(UUID uuid, ItemStack item) {
18 | this.itemsToGiveAfterRespawn.computeIfAbsent(uuid, k -> new ArrayList<>()).add(item);
19 | }
20 |
21 | @Override
22 | public void addItems(UUID uuid, List item) {
23 | item.forEach(i -> this.addItem(uuid, i));
24 | }
25 |
26 | @Override
27 | public boolean hasItems(UUID uuid) {
28 | return this.itemsToGiveAfterRespawn.containsKey(uuid);
29 | }
30 |
31 | @Override
32 | public List nextItems(UUID uuid) {
33 | List itemStacks = this.itemsToGiveAfterRespawn.remove(uuid);
34 |
35 | if (itemStacks == null) {
36 | return Collections.emptyList();
37 | }
38 |
39 | return Collections.unmodifiableList(itemStacks);
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.drop;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | public class DropServiceImpl implements DropService {
7 |
8 | private final Map modifiers;
9 |
10 | public DropServiceImpl() {
11 | this.modifiers = new HashMap<>();
12 | }
13 |
14 | @Override
15 | public void registerModifier(DropModifier dropModifier) {
16 | DropType dropType = dropModifier.getDropType();
17 |
18 | if (dropType == null) {
19 | throw new RuntimeException("Drop type cannot be null! '%s'".formatted(dropModifier.getClass().getSimpleName()));
20 | }
21 |
22 | if (dropType == DropType.UNCHANGED) {
23 | throw new RuntimeException("You cannot register DropModifier for this type '%s'".formatted(dropType.name()));
24 | }
25 |
26 | this.modifiers.put(dropType, dropModifier);
27 | }
28 |
29 | @Override
30 | public DropResult modify(DropType dropType, Drop drop) {
31 | if (!this.modifiers.containsKey(dropType)) {
32 | throw new RuntimeException("No drop modifier found for type '%s'".formatted(dropType.name()));
33 | }
34 |
35 | return this.modifiers.get(dropType).modifyDrop(drop);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropSettings.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.drop;
2 |
3 | import eu.okaeri.configs.OkaeriConfig;
4 | import eu.okaeri.configs.annotation.Comment;
5 | import org.bukkit.event.EventPriority;
6 |
7 | import java.util.List;
8 |
9 | public class DropSettings extends OkaeriConfig {
10 |
11 |
12 | @Comment({
13 | "# The event priority at which the head drop logic should run.",
14 | "# Options: LOWEST, LOW, NORMAL, HIGH, HIGHEST, MONITOR",
15 | "# Useful if you want to control when drops are processed relative to other plugins.",
16 | "# Default: NORMAL"
17 | })
18 | public EventPriority dropEventPriority = EventPriority.NORMAL;
19 |
20 | @Comment({
21 | "# UNCHANGED - The default way of item drop defined by the engine",
22 | "# PERCENT - Drops a fixed percentage of items",
23 | "# PLAYERS_HEALTH - Drops inverted percentage of the player's health (i.e. if the player has, for example, 80% HP, he will drop 20% of items. Only works when the player escapes from combat by quiting game)"
24 | })
25 | public DropType dropType = DropType.UNCHANGED;
26 |
27 | @Comment("# What percentage of items should drop from the player? (Only if Drop Type is set to PERCENT)")
28 | public int dropItemPercent = 100;
29 |
30 | @Comment("# This option is responsible for the lowest percentage of the player that can drop (i.e. if the player leaves the game while he has 100% of his HP, the percentage of items that is set in this option will drop, if you set this option to 0, then nothing will drop from such a player)")
31 | public int playersHealthPercentClamp = 20;
32 |
33 | @Comment("# Does the drop modification affect the experience drop?")
34 | public boolean affectExperience = false;
35 |
36 | @Comment({
37 | "",
38 | "# If true, players can drop their head on death based on chance settings below."
39 | })
40 | public boolean headDropEnabled = false;
41 |
42 | @Comment({
43 | "# Chance for a head to drop on death (0-100).",
44 | "# Set to 0 to disable even if feature is enabled.",
45 | "# Example: 25.0 means 25% chance."
46 | })
47 | public double headDropChance = 0.0;
48 |
49 | @Comment({
50 | "# Only drop the head if the player was in combat at time of death."
51 | })
52 | public boolean headDropOnlyInCombat = true;
53 |
54 | @Comment({
55 | "# The display name of the dropped head.",
56 | "# Placeholders: {PLAYER}, {KILLER}",
57 | "# Example: \"{PLAYER}'s Head\""
58 | })
59 | public String headDropDisplayName = "{PLAYER}'s Head";
60 |
61 | @Comment({
62 | "# Lore lines shown on the head item.",
63 | "# Placeholders: {PLAYER}, {KILLER}",
64 | "# Set to [] to disable lore entirely."
65 | })
66 | public List headDropLore = List.of(
67 | "Slain by {KILLER}",
68 | "Collected in battle"
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/impl/PercentDropModifier.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.drop.impl;
2 |
3 | import com.eternalcode.combat.fight.drop.Drop;
4 | import com.eternalcode.combat.fight.drop.DropModifier;
5 | import com.eternalcode.combat.fight.drop.DropResult;
6 | import com.eternalcode.combat.fight.drop.DropSettings;
7 | import com.eternalcode.combat.fight.drop.DropType;
8 | import com.eternalcode.combat.util.InventoryUtil;
9 | import com.eternalcode.combat.util.MathUtil;
10 | import com.eternalcode.combat.util.RemoveItemResult;
11 | import org.bukkit.inventory.ItemStack;
12 |
13 | import java.util.List;
14 |
15 | public class PercentDropModifier implements DropModifier {
16 |
17 | private final DropSettings settings;
18 |
19 | public PercentDropModifier(DropSettings settings) {
20 | this.settings = settings;
21 | }
22 |
23 | @Override
24 | public DropType getDropType() {
25 | return DropType.PERCENT;
26 | }
27 |
28 | @Override
29 | public DropResult modifyDrop(Drop drop) {
30 | int dropItemPercent = 100 - MathUtil.clamp(this.settings.dropItemPercent, 0, 100);
31 | List droppedItems = drop.getDroppedItems();
32 |
33 | int itemsToDelete = InventoryUtil.calculateItemsToDelete(dropItemPercent, droppedItems, ItemStack::getAmount);
34 | int droppedExp = MathUtil.getRoundedCountFromPercentage(dropItemPercent, drop.getDroppedExp());
35 |
36 | RemoveItemResult result = InventoryUtil.removeRandomItems(droppedItems, itemsToDelete);
37 |
38 | return new DropResult(result.restItems(), result.removedItems(), droppedExp);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/impl/PlayersHealthDropModifier.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.drop.impl;
2 |
3 | import com.eternalcode.combat.fight.drop.Drop;
4 | import com.eternalcode.combat.fight.drop.DropModifier;
5 | import com.eternalcode.combat.fight.drop.DropResult;
6 | import com.eternalcode.combat.fight.drop.DropSettings;
7 | import com.eternalcode.combat.fight.drop.DropType;
8 | import com.eternalcode.combat.fight.logout.Logout;
9 | import com.eternalcode.combat.fight.logout.LogoutService;
10 | import com.eternalcode.combat.util.InventoryUtil;
11 | import com.eternalcode.combat.util.MathUtil;
12 | import com.eternalcode.combat.util.RemoveItemResult;
13 | import org.bukkit.attribute.Attribute;
14 | import org.bukkit.entity.Player;
15 | import org.bukkit.inventory.ItemStack;
16 |
17 | import java.util.List;
18 | import java.util.Optional;
19 |
20 | public class PlayersHealthDropModifier implements DropModifier {
21 |
22 | private final DropSettings settings;
23 | private final LogoutService logoutService;
24 |
25 | public PlayersHealthDropModifier(DropSettings settings, LogoutService logoutService) {
26 | this.settings = settings;
27 | this.logoutService = logoutService;
28 | }
29 |
30 | @Override
31 | public DropType getDropType() {
32 | return DropType.PLAYERS_HEALTH;
33 | }
34 |
35 | @Override
36 | public DropResult modifyDrop(Drop drop) {
37 | Optional logoutOptional = this.logoutService.nextLogoutFor(drop.getPlayer().getUniqueId());
38 |
39 | if (logoutOptional.isEmpty()) {
40 | return null;
41 | }
42 |
43 |
44 | Logout logout = logoutOptional.get();
45 | Player player = drop.getPlayer();
46 |
47 | List droppedItems = drop.getDroppedItems();
48 |
49 | double maxHealth = player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue();
50 | double health = logout.health();
51 |
52 | int percentHealth = MathUtil.getRoundedCountPercentage(health, maxHealth);
53 | int reversedPercent = MathUtil.clamp(100 - percentHealth, this.settings.playersHealthPercentClamp, 100);
54 |
55 | int itemsToDelete = InventoryUtil.calculateItemsToDelete(reversedPercent, droppedItems, ItemStack::getAmount);
56 | int droppedExp = MathUtil.getRoundedCountFromPercentage(reversedPercent, drop.getDroppedExp());
57 |
58 | RemoveItemResult result = InventoryUtil.removeRandomItems(droppedItems, itemsToDelete);
59 |
60 | return new DropResult(result.restItems(), result.removedItems(), droppedExp);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/effect/FightEffectController.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.effect;
2 |
3 | import com.eternalcode.combat.fight.FightManager;
4 | import com.eternalcode.combat.fight.event.CauseOfUnTag;
5 | import com.eternalcode.combat.fight.event.FightTagEvent;
6 | import com.eternalcode.combat.fight.event.FightUntagEvent;
7 | import org.bukkit.Bukkit;
8 | import org.bukkit.Server;
9 | import org.bukkit.entity.Player;
10 | import org.bukkit.event.EventHandler;
11 | import org.bukkit.event.EventPriority;
12 | import org.bukkit.event.Listener;
13 | import org.bukkit.event.entity.EntityPotionEffectEvent;
14 | import org.bukkit.potion.PotionEffect;
15 |
16 | public class FightEffectController implements Listener {
17 |
18 | private final FightEffectService effectService;
19 | private final FightEffectSettings effectSettings;
20 | private final FightManager fightManager;
21 | private final Server server;
22 |
23 | public FightEffectController(FightEffectSettings settings, FightEffectService effectService, FightManager fightManager, Server server) {
24 | this.effectSettings = settings;
25 | this.effectService = effectService;
26 | this.fightManager = fightManager;
27 | this.server = server;
28 | }
29 |
30 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
31 | public void onTag(FightTagEvent event) {
32 | if (!this.effectSettings.customEffectsEnabled) {
33 | return;
34 | }
35 |
36 | Player player = this.server.getPlayer(event.getPlayer());
37 |
38 | if (player == null) {
39 | return;
40 | }
41 |
42 | this.effectSettings.customEffects.forEach((key, value) ->
43 | this.effectService.applyCustomEffect(player, key, value));
44 | }
45 |
46 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
47 | public void onQuit(FightUntagEvent event) {
48 | if (!this.effectSettings.customEffectsEnabled) {
49 | return;
50 | }
51 |
52 | if (event.getCause() == CauseOfUnTag.LOGOUT) {
53 | Player player = Bukkit.getPlayer(event.getPlayer());
54 |
55 | assert player != null;
56 | this.effectService.clearStoredEffects(player);
57 | }
58 | }
59 |
60 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
61 | public void onUntag(FightUntagEvent event) {
62 | if (!this.effectSettings.customEffectsEnabled) {
63 | return;
64 | }
65 |
66 | Player player = this.server.getPlayer(event.getPlayer());
67 |
68 | if (player == null) {
69 | return;
70 | }
71 |
72 | this.effectSettings.customEffects.forEach((key, value) -> this.effectService.removeCustomEffect(player, key, value));
73 |
74 | this.effectService.restoreActiveEffects(player);
75 | }
76 |
77 | @EventHandler
78 | public void onDeath(FightUntagEvent event) {
79 | if (!this.effectSettings.customEffectsEnabled) {
80 | return;
81 | }
82 | if (event.getCause() == CauseOfUnTag.DEATH_BY_PLAYER || event.getCause() == CauseOfUnTag.DEATH) {
83 | Player player = Bukkit.getPlayer(event.getPlayer());
84 |
85 | assert player != null;
86 | this.effectService.clearStoredEffects(player);
87 | }
88 | }
89 |
90 | @EventHandler
91 | public void onEffectChange(EntityPotionEffectEvent event) {
92 | if (!this.effectSettings.customEffectsEnabled) {
93 | return;
94 | }
95 |
96 | if (!(event.getEntity() instanceof Player player)) {
97 | return;
98 | }
99 |
100 | if (!this.fightManager.isInCombat(player.getUniqueId())) {
101 | return;
102 | }
103 |
104 | PotionEffect newEffect = event.getNewEffect();
105 | PotionEffect oldEffect = event.getOldEffect();
106 |
107 | if (!this.isRemovedEffect(newEffect, oldEffect)) {
108 | return;
109 | }
110 |
111 | Integer customAmplifier = this.effectSettings.customEffects.get(oldEffect.getType());
112 |
113 | if (customAmplifier == null) {
114 | return;
115 | }
116 |
117 | player.addPotionEffect(new PotionEffect(oldEffect.getType(), -1, customAmplifier));
118 | }
119 |
120 | private boolean isRemovedEffect(PotionEffect newEffect, PotionEffect oldEffect) {
121 | return newEffect == null && oldEffect != null;
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/effect/FightEffectServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.effect;
2 |
3 | import org.bukkit.entity.Player;
4 | import org.bukkit.potion.PotionEffect;
5 | import org.bukkit.potion.PotionEffectType;
6 |
7 | import java.util.HashMap;
8 | import java.util.Map;
9 | import java.util.UUID;
10 | import java.util.List;
11 | import java.util.ArrayList;
12 |
13 | public class FightEffectServiceImpl implements FightEffectService {
14 |
15 | private final Map> activeEffects = new HashMap<>();
16 | private static final int INFINITE_DURATION = -1;
17 |
18 | @Override
19 | public void storeActiveEffect(Player player, PotionEffect effect) {
20 | List effects = this.activeEffects.computeIfAbsent(player.getUniqueId(), k -> new ArrayList<>());
21 |
22 | effects.add(effect);
23 | }
24 |
25 | @Override
26 | public void restoreActiveEffects(Player player) {
27 | List currentEffects = this.getCurrentEffects(player);
28 |
29 | for (PotionEffect effect : currentEffects) {
30 | player.addPotionEffect(effect);
31 | }
32 |
33 | this.clearStoredEffects(player);
34 | }
35 |
36 | @Override
37 | public void clearStoredEffects(Player player) {
38 | this.activeEffects.remove(player.getUniqueId());
39 | }
40 |
41 | @Override
42 | public List getCurrentEffects(Player player) {
43 | return this.activeEffects.getOrDefault(player.getUniqueId(), new ArrayList<>());
44 | }
45 |
46 | @Override
47 | public void applyCustomEffect(Player player, PotionEffectType type, Integer amplifier) {
48 | PotionEffect activeEffect = player.getPotionEffect(type);
49 |
50 | if (activeEffect == null) {
51 | player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier));
52 | return;
53 | }
54 |
55 | if (activeEffect.getAmplifier() > amplifier) {
56 | return;
57 | }
58 |
59 | if (activeEffect.getDuration() == -1) {
60 | return;
61 | }
62 |
63 | this.storeActiveEffect(player, activeEffect);
64 | player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier));
65 | }
66 |
67 | @Override
68 | public void removeCustomEffect(Player player, PotionEffectType type, Integer amplifier) {
69 | PotionEffect activeEffect = player.getPotionEffect(type);
70 |
71 | if (activeEffect == null) {
72 | return;
73 | }
74 |
75 | if (activeEffect.getAmplifier() != amplifier) {
76 | return;
77 | }
78 |
79 | player.removePotionEffect(type);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/effect/FightEffectSettings.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.effect;
2 |
3 | import eu.okaeri.configs.OkaeriConfig;
4 | import eu.okaeri.configs.annotation.Comment;
5 |
6 | import org.bukkit.potion.PotionEffectType;
7 |
8 | import java.util.Map;
9 |
10 | public class FightEffectSettings extends OkaeriConfig {
11 |
12 | @Comment({"# Do you want to add effects to players in combat?"})
13 | public boolean customEffectsEnabled = false;
14 |
15 | @Comment({
16 | "# If the option above is set to true, you can add effects to players in combat below",
17 | "# You can find a list of all potion effects here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/potion/PotionEffectType.html",
18 | "# Correct format: 'EFFECT_TYPE:AMPLIFIER' Amplifier strength starts from 0, so level 1 gives effect strength 2",
19 | "# Example: SPEED:1, DAMAGE_RESISTANCE:0",
20 | })
21 | public Map customEffects = Map.of(
22 | PotionEffectType.SPEED, 1,
23 | PotionEffectType.DAMAGE_RESISTANCE, 0
24 | );
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackOutsideRegionGenerator.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.knockback;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashSet;
5 | import java.util.List;
6 | import java.util.Map;
7 | import java.util.NavigableMap;
8 | import java.util.Set;
9 | import java.util.TreeMap;
10 | import org.bukkit.Location;
11 | import org.bukkit.World;
12 |
13 | class KnockbackOutsideRegionGenerator {
14 |
15 | private record Point2D(int x, int z) {
16 |
17 | Location toLocation(Location location) {
18 | World world = location.getWorld();
19 | int y = world.getHighestBlockYAt(x, z) + 1;
20 |
21 | return new Location(world, x, y, z, location.getYaw(), location.getPitch());
22 | }
23 |
24 | private static Point2D from(Location location) {
25 | return new Point2D(location.getBlockX(), location.getBlockZ());
26 | }
27 |
28 | }
29 |
30 | static Location generate(Location min, Location max, Location playerLocation) {
31 | NavigableMap> points = generatePoints(Point2D.from(min), Point2D.from(max), Point2D.from(playerLocation));
32 | NavigableMap distances = new TreeMap<>();
33 | double totalWeight = 0;
34 |
35 | Double maxDistance = points.lastKey();
36 |
37 | for (double distance : points.keySet()) {
38 | double weight = createWeight(distance, maxDistance);
39 | distances.put(distance, weight);
40 | totalWeight += weight;
41 | }
42 |
43 | double rand = Math.random() * totalWeight;
44 | double cumulativeWeight = 0;
45 |
46 | for (Map.Entry entry : distances.entrySet()) {
47 | double distance = entry.getKey();
48 | double weight = entry.getValue();
49 |
50 | cumulativeWeight += weight;
51 | if (rand <= cumulativeWeight) {
52 | return getRandom(points.get(distance))
53 | .toLocation(playerLocation);
54 | }
55 | }
56 |
57 | return getRandom(points.firstEntry().getValue())
58 | .toLocation(playerLocation);
59 | }
60 |
61 | private static Point2D getRandom(List points) {
62 | return points.get((int) (Math.random() * points.size()));
63 | }
64 |
65 | private static double createWeight(double distance, double maxDistance) {
66 | double last = Math.log(maxDistance);
67 | double weight = last - Math.log(distance);
68 | return Math.pow(weight, 10);
69 | }
70 |
71 | private static NavigableMap> generatePoints(Point2D min, Point2D max, Point2D location) {
72 | Set points = new HashSet<>();
73 |
74 | for (int i = min.x() - 1; i <= max.x() + 1; i++) {
75 | points.add(new Point2D(i, min.z() - 1));
76 | points.add(new Point2D(i, max.z() + 1));
77 | }
78 | for (int i = min.z(); i <= max.z(); i++) {
79 | points.add(new Point2D(min.x() - 1, i));
80 | points.add(new Point2D(max.x() + 1, i));
81 | }
82 |
83 | TreeMap> result = new TreeMap<>();
84 |
85 | for (Point2D point : points) {
86 | double distance = distance(location, point);
87 | result.computeIfAbsent(distance, k -> new ArrayList<>()).add(point);
88 | }
89 |
90 | return result;
91 | }
92 |
93 | private static double distance(Point2D p1, Point2D p2) {
94 | return Math.sqrt(Math.pow(p2.x() - p1.x(), 2) + Math.pow(p2.z() - p1.z(), 2));
95 | }
96 |
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackRegionController.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.knockback;
2 |
3 | import com.eternalcode.combat.fight.FightManager;
4 | import com.eternalcode.combat.fight.event.FightTagEvent;
5 | import com.eternalcode.combat.notification.NoticeService;
6 | import com.eternalcode.combat.region.Region;
7 | import com.eternalcode.combat.region.RegionProvider;
8 | import java.time.Duration;
9 | import java.util.Optional;
10 | import org.bukkit.Location;
11 | import org.bukkit.Server;
12 | import org.bukkit.entity.Player;
13 | import org.bukkit.event.EventHandler;
14 | import org.bukkit.event.EventPriority;
15 | import org.bukkit.event.Listener;
16 | import org.bukkit.event.player.PlayerMoveEvent;
17 | import org.bukkit.event.player.PlayerTeleportEvent;
18 |
19 | public class KnockbackRegionController implements Listener {
20 |
21 | private final NoticeService noticeService;
22 | private final RegionProvider regionProvider;
23 | private final FightManager fightManager;
24 | private final KnockbackService knockbackService;
25 | private final Server server;
26 |
27 | public KnockbackRegionController(NoticeService noticeService, RegionProvider regionProvider, FightManager fightManager, KnockbackService knockbackService, Server server) {
28 | this.noticeService = noticeService;
29 | this.regionProvider = regionProvider;
30 | this.fightManager = fightManager;
31 | this.knockbackService = knockbackService;
32 | this.server = server;
33 | }
34 |
35 | @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
36 | void onPlayerMove(PlayerMoveEvent event) {
37 | Player player = event.getPlayer();
38 | if (!this.fightManager.isInCombat(player.getUniqueId())) {
39 | return;
40 | }
41 |
42 | Location locationTo = event.getTo();
43 | int xTo = locationTo.getBlockX();
44 | int yTo = locationTo.getBlockY();
45 | int zTo = locationTo.getBlockZ();
46 |
47 | Location locationFrom = event.getFrom();
48 | int xFrom = locationFrom.getBlockX();
49 | int yFrom = locationFrom.getBlockY();
50 | int zFrom = locationFrom.getBlockZ();
51 |
52 | if (xTo != xFrom || yTo != yFrom || zTo != zFrom) {
53 | Optional regionOptional = this.regionProvider.getRegion(locationTo);
54 | if (regionOptional.isEmpty()) {
55 | return;
56 | }
57 |
58 | Region region = regionOptional.get();
59 | if (region.contains(locationFrom)) {
60 | this.knockbackService.knockback(region, player);
61 | this.knockbackService.forceKnockbackLater(player, region);
62 | } else {
63 | event.setCancelled(true);
64 | this.knockbackService.knockbackLater(region, player, Duration.ofMillis(50));
65 | }
66 |
67 | this.noticeService.create()
68 | .player(player.getUniqueId())
69 | .notice(config -> config.messagesSettings.cantEnterOnRegion)
70 | .send();
71 | }
72 | }
73 |
74 | @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
75 | void onPlayerTeleport(PlayerTeleportEvent event) {
76 | Player player = event.getPlayer();
77 | if (!this.fightManager.isInCombat(player.getUniqueId())) {
78 | return;
79 | }
80 |
81 | Location targetLocation = event.getTo();
82 |
83 | if (this.regionProvider.isInRegion(targetLocation)) {
84 | event.setCancelled(true);
85 | this.noticeService.create()
86 | .player(player.getUniqueId())
87 | .notice(config -> config.messagesSettings.cantEnterOnRegion)
88 | .send();
89 | }
90 | }
91 |
92 | @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
93 | void onTag(FightTagEvent event) {
94 | Player player = this.server.getPlayer(event.getPlayer());
95 | if (player == null) {
96 | throw new IllegalStateException("Player cannot be null!");
97 | }
98 |
99 | Optional regionOptional = this.regionProvider.getRegion(player.getLocation());
100 | if (regionOptional.isEmpty()) {
101 | return;
102 | }
103 |
104 | Region region = regionOptional.get();
105 | this.knockbackService.knockback(region, player);
106 | this.knockbackService.forceKnockbackLater(player, region);
107 |
108 | this.noticeService.create()
109 | .player(player.getUniqueId())
110 | .notice(config -> config.messagesSettings.cantEnterOnRegion)
111 | .send();
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackService.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.knockback;
2 |
3 | import com.eternalcode.combat.config.implementation.PluginConfig;
4 | import com.eternalcode.combat.region.Region;
5 | import com.eternalcode.commons.scheduler.Scheduler;
6 | import java.time.Duration;
7 | import java.util.HashMap;
8 | import java.util.Map;
9 | import java.util.UUID;
10 | import org.bukkit.Location;
11 | import org.bukkit.entity.Player;
12 | import org.bukkit.event.player.PlayerTeleportEvent;
13 | import org.bukkit.util.Vector;
14 |
15 | public final class KnockbackService {
16 |
17 | private final PluginConfig config;
18 | private final Scheduler scheduler;
19 |
20 | private final Map insideRegion = new HashMap<>();
21 |
22 | public KnockbackService(PluginConfig config, Scheduler scheduler) {
23 | this.config = config;
24 | this.scheduler = scheduler;
25 | }
26 |
27 | public void knockbackLater(Region region, Player player, Duration duration) {
28 | this.scheduler.runLater(() -> this.knockback(region, player), duration);
29 | }
30 |
31 | public void forceKnockbackLater(Player player, Region region) {
32 | if (insideRegion.containsKey(player.getUniqueId())) {
33 | return;
34 | }
35 |
36 | insideRegion.put(player.getUniqueId(), region);
37 | scheduler.runLater(() -> {
38 | insideRegion.remove(player.getUniqueId());
39 | Location playerLocation = player.getLocation();
40 | if (!region.contains(playerLocation)) {
41 | return;
42 | }
43 |
44 | Location location = KnockbackOutsideRegionGenerator.generate(region.getMin(), region.getMax(), playerLocation);
45 | player.teleport(location, PlayerTeleportEvent.TeleportCause.PLUGIN);
46 | }, this.config.knockback.forceDelay);
47 | }
48 |
49 | public void knockback(Region region, Player player) {
50 | Location centerOfRegion = region.getCenter();
51 | Location subtract = player.getLocation().subtract(centerOfRegion);
52 |
53 | Vector knockbackVector = new Vector(subtract.getX(), 0, subtract.getZ()).normalize();
54 | double multiplier = this.config.knockback.multiplier;
55 | Vector configuredVector = new Vector(multiplier, 0.5, multiplier);
56 |
57 | player.setVelocity(knockbackVector.multiply(configuredVector));
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackSettings.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.knockback;
2 |
3 | import eu.okaeri.configs.OkaeriConfig;
4 | import eu.okaeri.configs.annotation.Comment;
5 | import java.time.Duration;
6 |
7 | public class KnockbackSettings extends OkaeriConfig {
8 |
9 | @Comment({
10 | "# Adjust the knockback multiplier for restricted regions.",
11 | "# Higher values increase the knockback distance. Avoid using negative values.",
12 | "# A value of 1.0 typically knocks players 2-4 blocks away."
13 | })
14 | public double multiplier = 1;
15 |
16 | @Comment({ "# Time after which the player will be force knocked back outside the safe zone" })
17 | public Duration forceDelay = Duration.ofSeconds(1);
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/Logout.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.logout;
2 |
3 | import java.util.UUID;
4 |
5 | public record Logout(UUID player, double health) {
6 | }
7 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.logout;
2 |
3 | import com.eternalcode.combat.config.implementation.PluginConfig;
4 | import com.eternalcode.combat.event.DynamicListener;
5 | import com.eternalcode.combat.fight.FightManager;
6 | import com.eternalcode.combat.notification.NoticeService;
7 | import org.bukkit.entity.Player;
8 | import org.bukkit.event.EventHandler;
9 | import org.bukkit.event.EventPriority;
10 | import org.bukkit.event.player.PlayerKickEvent;
11 | import org.bukkit.event.player.PlayerQuitEvent;
12 |
13 | import java.util.Collections;
14 | import java.util.List;
15 | import java.util.Set;
16 | import java.util.UUID;
17 | import java.util.concurrent.ConcurrentHashMap;
18 |
19 | public class LogoutController implements DynamicListener {
20 |
21 | private final FightManager fightManager;
22 | private final LogoutService logoutService;
23 | private final NoticeService noticeService;
24 | private final PluginConfig config;
25 |
26 | private final Set shouldNotPunishOnQuit = Collections.newSetFromMap(new ConcurrentHashMap<>());
27 |
28 | public LogoutController(FightManager fightManager, LogoutService logoutService, NoticeService noticeService, PluginConfig config) {
29 | this.fightManager = fightManager;
30 | this.logoutService = logoutService;
31 | this.noticeService = noticeService;
32 | this.config = config;
33 | }
34 |
35 | @EventHandler(priority = EventPriority.LOWEST)
36 | private void onKick(PlayerKickEvent event) {
37 | Player player = event.getPlayer();
38 | UUID uuid = player.getUniqueId();
39 |
40 | if (!this.fightManager.isInCombat(uuid)) {
41 | return;
42 | }
43 |
44 | String reason = event.getReason().trim();
45 | List whitelist = this.config.combat.whitelistedKickReasons;
46 |
47 | if (whitelist.isEmpty()) {
48 | return;
49 | }
50 |
51 | for (String whitelisted : whitelist) {
52 | if (reason.toLowerCase().contains(whitelisted.toLowerCase())) {
53 | this.shouldNotPunishOnQuit.add(uuid);
54 | return;
55 | }
56 | }
57 | }
58 |
59 | @Override
60 | public void onEvent(PlayerQuitEvent event) {
61 | Player player = event.getPlayer();
62 |
63 | if (!this.fightManager.isInCombat(player.getUniqueId())) {
64 | return;
65 | }
66 |
67 | if (this.shouldNotPunishOnQuit.remove(player.getUniqueId())) {
68 | return;
69 | }
70 |
71 | this.logoutService.punishForLogout(player);
72 | player.setHealth(0.0);
73 |
74 | this.noticeService.create()
75 | .notice(this.config.messagesSettings.playerLoggedOutDuringCombat)
76 | .placeholder("{PLAYER}", player.getName())
77 | .all()
78 | .send();
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutService.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.logout;
2 |
3 | import com.google.common.cache.Cache;
4 | import com.google.common.cache.CacheBuilder;
5 | import org.bukkit.entity.Player;
6 |
7 | import java.util.Optional;
8 | import java.util.UUID;
9 | import java.util.concurrent.TimeUnit;
10 |
11 | public class LogoutService {
12 |
13 | private final Cache logouts = CacheBuilder.newBuilder()
14 | .expireAfterWrite(1, TimeUnit.MINUTES)
15 | .build();
16 |
17 | public void punishForLogout(Player player) {
18 | UUID uniqueId = player.getUniqueId();
19 |
20 | this.logouts.put(uniqueId, new Logout(uniqueId, player.getHealth()));
21 | }
22 |
23 | public Optional nextLogoutFor(UUID player) {
24 | Logout logout = this.logouts.getIfPresent(player);
25 |
26 | if (logout == null) {
27 | return Optional.empty();
28 | }
29 |
30 | this.logouts.invalidate(player);
31 | return Optional.of(logout);
32 | }
33 |
34 | public boolean hasLoggedOut(UUID player) {
35 | return this.logouts.asMap().containsKey(player);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.pearl;
2 |
3 | import com.eternalcode.combat.fight.FightManager;
4 | import com.eternalcode.combat.notification.NoticeService;
5 | import com.eternalcode.combat.util.DurationUtil;
6 | import org.bukkit.Material;
7 | import org.bukkit.entity.EnderPearl;
8 | import org.bukkit.entity.Player;
9 | import org.bukkit.event.EventHandler;
10 | import org.bukkit.event.EventPriority;
11 | import org.bukkit.event.Listener;
12 | import org.bukkit.event.block.Action;
13 | import org.bukkit.event.entity.EntityDamageByEntityEvent;
14 | import org.bukkit.event.entity.EntityDamageEvent;
15 | import org.bukkit.event.player.PlayerInteractEvent;
16 | import org.bukkit.inventory.ItemStack;
17 |
18 | import java.time.Duration;
19 | import java.util.UUID;
20 |
21 | public class FightPearlController implements Listener {
22 |
23 | private final FightPearlSettings settings;
24 | private final NoticeService noticeService;
25 | private final FightManager fightManager;
26 | private final FightPearlService fightPearlService;
27 |
28 | public FightPearlController(FightPearlSettings settings, NoticeService noticeService, FightManager fightManager, FightPearlService fightPearlService) {
29 | this.settings = settings;
30 | this.noticeService = noticeService;
31 | this.fightManager = fightManager;
32 | this.fightPearlService = fightPearlService;
33 | }
34 |
35 | @EventHandler
36 | void onInteract(PlayerInteractEvent event) {
37 | Player player = event.getPlayer();
38 | UUID uniqueId = player.getUniqueId();
39 |
40 | if (!this.settings.pearlThrowBlocked) {
41 | return;
42 | }
43 |
44 | if (!this.fightManager.isInCombat(uniqueId)) {
45 | return;
46 | }
47 |
48 | ItemStack item = event.getItem();
49 | if (item == null || item.getType() != Material.ENDER_PEARL) {
50 | return;
51 | }
52 |
53 | Action action = event.getAction();
54 | if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) {
55 | return;
56 | }
57 |
58 | if (this.settings.pearlThrowDelay.isZero()) {
59 | event.setCancelled(true);
60 | this.noticeService.create()
61 | .player(uniqueId)
62 | .notice(this.settings.pearlThrowBlockedDuringCombat)
63 | .send();
64 |
65 | return;
66 | }
67 |
68 | if (this.fightPearlService.hasDelay(uniqueId)) {
69 | event.setCancelled(true);
70 |
71 | Duration remainingPearlDelay = this.fightPearlService.getRemainingDelay(uniqueId);
72 |
73 | this.noticeService.create()
74 | .player(uniqueId)
75 | .notice(this.settings.pearlThrowBlockedDelayDuringCombat)
76 | .placeholder("{TIME}", DurationUtil.format(remainingPearlDelay))
77 | .send();
78 |
79 | return;
80 | }
81 |
82 | this.fightPearlService.markDelay(uniqueId);
83 | }
84 |
85 | @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
86 | void onEntityDamage(EntityDamageByEntityEvent event) {
87 | if (this.settings.pearlThrowDamageEnabled) {
88 | return;
89 | }
90 |
91 | if (!(event.getEntity() instanceof Player)) {
92 | return;
93 | }
94 |
95 | if (!(event.getDamager() instanceof EnderPearl)) {
96 | return;
97 | }
98 |
99 | if (event.getCause() != EntityDamageEvent.DamageCause.FALL) {
100 | return;
101 | }
102 |
103 | event.setDamage(0.0D);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.pearl;
2 |
3 | import com.github.benmanes.caffeine.cache.Cache;
4 | import com.github.benmanes.caffeine.cache.Caffeine;
5 |
6 | import java.time.Duration;
7 | import java.time.Instant;
8 | import java.util.UUID;
9 |
10 | public class FightPearlServiceImpl implements FightPearlService {
11 |
12 | private final FightPearlSettings pearlSettings;
13 | private final Cache pearlDelays;
14 |
15 | public FightPearlServiceImpl(FightPearlSettings pearlSettings) {
16 | this.pearlSettings = pearlSettings;
17 | this.pearlDelays = Caffeine.newBuilder()
18 | .expireAfterWrite(pearlSettings.pearlThrowDelay)
19 | .build();
20 | }
21 |
22 | @Override
23 | public void markDelay(UUID uuid) {
24 | this.pearlDelays.put(uuid, Instant.now().plus(this.pearlSettings.pearlThrowDelay));
25 | }
26 |
27 | @Override
28 | public boolean hasDelay(UUID uuid) {
29 | return Instant.now().isBefore(this.getDelay(uuid));
30 | }
31 |
32 | @Override
33 | public Duration getRemainingDelay(UUID uuid) {
34 | return Duration.between(Instant.now(), this.getDelay(uuid));
35 | }
36 |
37 | @Override
38 | public Instant getDelay(UUID uuid) {
39 | return this.pearlDelays.asMap().getOrDefault(uuid, Instant.MIN);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlSettings.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.pearl;
2 |
3 | import com.eternalcode.multification.bukkit.notice.BukkitNotice;
4 | import com.eternalcode.multification.notice.Notice;
5 | import eu.okaeri.configs.OkaeriConfig;
6 | import eu.okaeri.configs.annotation.Comment;
7 |
8 | import java.time.Duration;
9 |
10 | public class FightPearlSettings extends OkaeriConfig {
11 |
12 | @Comment({ "# Is pearl damage to be enabled?", "# This will work globally" })
13 | public boolean pearlThrowDamageEnabled = true;
14 |
15 | @Comment("# Set true, If you want to lock pearls during the combat")
16 | public boolean pearlThrowBlocked = false;
17 |
18 | @Comment({
19 | "# Block throwing pearls with delay?",
20 | "# If you set this to for example 3s, player will have to wait 3 seconds before throwing another pearl",
21 | "# Set to 0 to disable"
22 | })
23 | public Duration pearlThrowDelay = Duration.ofSeconds(3);
24 |
25 | @Comment("# Message sent when player tries to throw ender pearl, but are disabled")
26 | public Notice pearlThrowBlockedDuringCombat = BukkitNotice.builder()
27 | .chat("Throwing ender pearls is prohibited during combat!")
28 | .build();
29 |
30 | @Comment("# Message sent when player tries to throw ender pearl, but has delay")
31 | public Notice pearlThrowBlockedDelayDuringCombat = BukkitNotice.builder()
32 | .chat("You must wait {TIME} before next throw!")
33 | .build();
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutCommand.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.tagout;
2 |
3 | import com.eternalcode.combat.config.implementation.PluginConfig;
4 | import com.eternalcode.combat.notification.NoticeService;
5 | import com.eternalcode.combat.util.DurationUtil;
6 | import dev.rollczi.litecommands.annotations.argument.Arg;
7 | import dev.rollczi.litecommands.annotations.command.Command;
8 | import dev.rollczi.litecommands.annotations.context.Context;
9 | import dev.rollczi.litecommands.annotations.execute.Execute;
10 | import dev.rollczi.litecommands.annotations.permission.Permission;
11 | import java.time.Duration;
12 | import java.util.UUID;
13 | import org.bukkit.entity.Player;
14 |
15 | @Permission("eternalcombat.tagout")
16 | @Command(name = "tagout", aliases = "tagimmunity")
17 | public class FightTagOutCommand {
18 |
19 | private final FightTagOutService fightTagOutService;
20 | private final NoticeService noticeService;
21 | private final PluginConfig config;
22 |
23 | public FightTagOutCommand(
24 | FightTagOutService fightTagOutService,
25 | NoticeService noticeService,
26 | PluginConfig config
27 | ) {
28 | this.fightTagOutService = fightTagOutService;
29 | this.noticeService = noticeService;
30 | this.config = config;
31 | }
32 |
33 | @Execute
34 | void tagout(@Context Player sender, @Arg Duration time) {
35 | UUID targetUniqueId = sender.getUniqueId();
36 |
37 | this.fightTagOutService.tagOut(targetUniqueId, time);
38 |
39 | this.noticeService.create()
40 | .notice(this.config.messagesSettings.admin.adminTagOutSelf)
41 | .placeholder("{TIME}", DurationUtil.format(time))
42 | .viewer(sender)
43 | .send();
44 |
45 | }
46 |
47 | @Execute
48 | void tagout(@Context Player sender, @Arg Player target, @Arg Duration time) {
49 | UUID targetUniqueId = target.getUniqueId();
50 |
51 | this.fightTagOutService.tagOut(targetUniqueId, time);
52 |
53 | this.noticeService.create()
54 | .notice(this.config.messagesSettings.admin.adminTagOut)
55 | .placeholder("{PLAYER}", target.getName())
56 | .placeholder("{TIME}", DurationUtil.format(time))
57 | .viewer(sender)
58 | .send();
59 |
60 | this.noticeService.create()
61 | .notice(this.config.messagesSettings.admin.playerTagOut)
62 | .placeholder("{TIME}", DurationUtil.format(time))
63 | .player(target.getUniqueId())
64 | .send();
65 |
66 | }
67 |
68 | @Execute(name = "remove")
69 | void untagout(@Context Player sender, @Arg Player target) {
70 | UUID targetUniqueId = target.getUniqueId();
71 |
72 | this.fightTagOutService.unTagOut(targetUniqueId);
73 |
74 |
75 | if (!targetUniqueId.equals(sender.getUniqueId())) {
76 | this.noticeService.create()
77 | .notice(this.config.messagesSettings.admin.adminTagOutOff)
78 | .placeholder("{PLAYER}", target.getName())
79 | .viewer(sender)
80 | .send();
81 | }
82 |
83 | this.noticeService.create()
84 | .notice(this.config.messagesSettings.admin.playerTagOutOff)
85 | .player(targetUniqueId)
86 | .send();
87 | }
88 |
89 | @Execute(name = "remove")
90 | void untagout(@Context Player sender) {
91 | UUID senderUniqueId = sender.getUniqueId();
92 |
93 | this.fightTagOutService.unTagOut(senderUniqueId);
94 |
95 | this.noticeService.create()
96 | .notice(this.config.messagesSettings.admin.playerTagOutOff)
97 | .viewer(sender)
98 | .send();
99 |
100 | }
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutController.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.tagout;
2 |
3 | import com.eternalcode.combat.fight.event.CancelTagReason;
4 | import com.eternalcode.combat.fight.event.FightTagEvent;
5 | import org.bukkit.event.EventHandler;
6 | import org.bukkit.event.Listener;
7 |
8 | import java.util.UUID;
9 |
10 | public class FightTagOutController implements Listener {
11 |
12 | private final FightTagOutService tagOutService;
13 |
14 | public FightTagOutController(FightTagOutService tagOutService) {
15 | this.tagOutService = tagOutService;
16 | }
17 |
18 | @EventHandler
19 | void onTagOut(FightTagEvent event) {
20 | UUID uniqueId = event.getPlayer();
21 |
22 | if (this.tagOutService.isTaggedOut(uniqueId)) {
23 | event.setCancelReason(CancelTagReason.TAGOUT);
24 | event.setCancelled(true);
25 | }
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.fight.tagout;
2 |
3 | import java.time.Duration;
4 | import java.time.Instant;
5 | import java.util.HashMap;
6 | import java.util.Map;
7 | import java.util.UUID;
8 |
9 | public class FightTagOutServiceImpl implements FightTagOutService {
10 |
11 | private final Map tagOuts = new HashMap<>();
12 |
13 | @Override
14 | public void tagOut(UUID player, Duration duration) {
15 | Instant endTime = Instant.now().plus(duration);
16 |
17 | this.tagOuts.put(player, endTime);
18 | }
19 |
20 | @Override
21 | public void unTagOut(UUID player) {
22 | this.tagOuts.remove(player);
23 | }
24 |
25 | @Override
26 | public boolean isTaggedOut(UUID player) {
27 | Instant endTime = this.tagOuts.get(player);
28 |
29 | if (endTime == null) {
30 | return false;
31 | }
32 | Instant now = Instant.now();
33 |
34 | return now.isBefore(endTime);
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/InvalidUsageHandlerImpl.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.handler;
2 |
3 | import com.eternalcode.combat.config.implementation.PluginConfig;
4 | import com.eternalcode.combat.notification.NoticeService;
5 | import dev.rollczi.litecommands.handler.result.ResultHandlerChain;
6 | import dev.rollczi.litecommands.invalidusage.InvalidUsage;
7 | import dev.rollczi.litecommands.invalidusage.InvalidUsageHandler;
8 | import dev.rollczi.litecommands.invocation.Invocation;
9 | import dev.rollczi.litecommands.schematic.Schematic;
10 | import org.bukkit.command.CommandSender;
11 |
12 | public class InvalidUsageHandlerImpl implements InvalidUsageHandler {
13 |
14 | private final PluginConfig config;
15 | private final NoticeService noticeService;
16 |
17 | public InvalidUsageHandlerImpl(PluginConfig config, NoticeService noticeService) {
18 | this.config = config;
19 | this.noticeService = noticeService;
20 | }
21 |
22 | @Override
23 | public void handle(
24 | Invocation invocation,
25 | InvalidUsage commandSenderInvalidUsage,
26 | ResultHandlerChain resultHandlerChain
27 | ) {
28 | Schematic schematic = commandSenderInvalidUsage.getSchematic();
29 |
30 | for (String usage : schematic.all()) {
31 | this.noticeService.create()
32 | .viewer(invocation.sender())
33 | .notice(this.config.messagesSettings.invalidCommandUsage)
34 | .placeholder("{USAGE}", usage)
35 | .send();
36 | }
37 |
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/MissingPermissionHandlerImpl.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.handler;
2 |
3 | import com.eternalcode.combat.config.implementation.PluginConfig;
4 | import com.eternalcode.combat.notification.NoticeService;
5 | import dev.rollczi.litecommands.handler.result.ResultHandlerChain;
6 | import dev.rollczi.litecommands.invocation.Invocation;
7 | import dev.rollczi.litecommands.permission.MissingPermissions;
8 | import dev.rollczi.litecommands.permission.MissingPermissionsHandler;
9 | import org.bukkit.command.CommandSender;
10 |
11 | public class MissingPermissionHandlerImpl implements MissingPermissionsHandler {
12 |
13 | private final PluginConfig config;
14 | private final NoticeService noticeService;
15 |
16 | public MissingPermissionHandlerImpl(PluginConfig config, NoticeService noticeService) {
17 | this.config = config;
18 | this.noticeService = noticeService;
19 | }
20 |
21 | @Override
22 | public void handle(
23 | Invocation invocation,
24 | MissingPermissions missingPermissions,
25 | ResultHandlerChain resultHandlerChain
26 | ) {
27 | String joinedText = missingPermissions.asJoinedText();
28 |
29 |
30 | this.noticeService.create()
31 | .viewer(invocation.sender())
32 | .notice(this.config.messagesSettings.noPermission)
33 | .placeholder("{PERMISSION}", joinedText)
34 | .send();
35 |
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/notification/NoticeService.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.notification;
2 |
3 | import com.eternalcode.combat.config.implementation.PluginConfig;
4 | import com.eternalcode.multification.adventure.AudienceConverter;
5 | import com.eternalcode.multification.bukkit.BukkitMultification;
6 | import com.eternalcode.multification.translation.TranslationProvider;
7 | import net.kyori.adventure.platform.AudienceProvider;
8 | import net.kyori.adventure.text.Component;
9 | import net.kyori.adventure.text.minimessage.MiniMessage;
10 | import net.kyori.adventure.text.serializer.ComponentSerializer;
11 | import org.bukkit.command.CommandSender;
12 | import org.bukkit.entity.Player;
13 | import org.jetbrains.annotations.NotNull;
14 |
15 | public final class NoticeService extends BukkitMultification {
16 |
17 | private final AudienceProvider audienceProvider;
18 | private final PluginConfig pluginConfig;
19 | private final MiniMessage miniMessage;
20 |
21 | public NoticeService(AudienceProvider audienceProvider, PluginConfig pluginConfig, MiniMessage miniMessage) {
22 | this.audienceProvider = audienceProvider;
23 | this.pluginConfig = pluginConfig;
24 | this.miniMessage = miniMessage;
25 | }
26 |
27 | @Override
28 | protected @NotNull TranslationProvider translationProvider() {
29 | return locale -> this.pluginConfig;
30 | }
31 |
32 | @Override
33 | protected @NotNull ComponentSerializer serializer() {
34 | return this.miniMessage;
35 | }
36 |
37 | @Override
38 | protected @NotNull AudienceConverter audienceConverter() {
39 | return commandSender -> {
40 | if (commandSender instanceof Player player) {
41 | return this.audienceProvider.player(player.getUniqueId());
42 | }
43 |
44 | return this.audienceProvider.console();
45 | };
46 |
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/DefaultRegionProvider.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.region;
2 |
3 | import java.util.Collection;
4 | import java.util.List;
5 | import java.util.Optional;
6 | import org.bukkit.Bukkit;
7 | import org.bukkit.Location;
8 | import org.bukkit.World;
9 |
10 | public class DefaultRegionProvider implements RegionProvider {
11 |
12 | private final int radius;
13 |
14 | public DefaultRegionProvider(int radius) {
15 | this.radius = radius;
16 | }
17 |
18 | @Override
19 | public Optional getRegion(Location location) {
20 | World world = location.getWorld();
21 | Region spawnRegion = this.createSpawnRegion(world);
22 | if (spawnRegion.contains(location.getX(), location.getY(), location.getZ())) {
23 | return Optional.of(spawnRegion);
24 | }
25 |
26 | return Optional.empty();
27 | }
28 |
29 | @Override
30 | public Collection getRegions(World world) {
31 | Region spawnRegion = this.createSpawnRegion(world);
32 | return List.of(spawnRegion);
33 | }
34 |
35 | private Region createSpawnRegion(World world) {
36 | Location spawnLocation = world.getSpawnLocation();
37 | double x = spawnLocation.getX();
38 | double z = spawnLocation.getZ();
39 |
40 | Location min = new Location(world, x - this.radius, world.getMinHeight(), z - this.radius);
41 | Location max = new Location(world, x + this.radius - 1, world.getMaxHeight() - 1, z + this.radius - 1);
42 |
43 | return new DefaultSpawnRegion(min, max, spawnLocation);
44 | }
45 |
46 | private record DefaultSpawnRegion(Location min, Location max, Location center) implements Region {
47 | @Override
48 | public Location getCenter() {
49 | return this.center;
50 | }
51 |
52 | @Override
53 | public Location getMin() {
54 | return this.min;
55 | }
56 |
57 | @Override
58 | public Location getMax() {
59 | return this.max;
60 | }
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/WorldGuardRegionProvider.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.region;
2 |
3 | import com.eternalcode.combat.config.implementation.PluginConfig;
4 | import com.sk89q.worldedit.bukkit.BukkitAdapter;
5 | import com.sk89q.worldedit.math.BlockVector3;
6 | import com.sk89q.worldguard.WorldGuard;
7 | import com.sk89q.worldguard.protection.ApplicableRegionSet;
8 | import com.sk89q.worldguard.protection.flags.Flags;
9 | import com.sk89q.worldguard.protection.flags.StateFlag;
10 | import com.sk89q.worldguard.protection.managers.RegionManager;
11 | import com.sk89q.worldguard.protection.regions.ProtectedRegion;
12 | import com.sk89q.worldguard.protection.regions.RegionContainer;
13 | import com.sk89q.worldguard.protection.regions.RegionQuery;
14 | import java.util.Collection;
15 | import java.util.Collections;
16 | import java.util.Optional;
17 | import java.util.TreeSet;
18 | import org.bukkit.Location;
19 |
20 | import java.util.List;
21 | import org.bukkit.World;
22 |
23 | public class WorldGuardRegionProvider implements RegionProvider {
24 |
25 | private final RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer();
26 | private final TreeSet regions = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
27 | private final PluginConfig pluginConfig;
28 |
29 | public WorldGuardRegionProvider(List regions, PluginConfig pluginConfig) {
30 | this.regions.addAll(regions);
31 | this.pluginConfig = pluginConfig;
32 | }
33 |
34 | @Override
35 | public Optional getRegion(Location location) {
36 | RegionQuery regionQuery = this.regionContainer.createQuery();
37 | ApplicableRegionSet applicableRegions = regionQuery.getApplicableRegions(BukkitAdapter.adapt(location));
38 |
39 | for (ProtectedRegion region : applicableRegions.getRegions()) {
40 | if (!this.isCombatRegion(region)) {
41 | continue;
42 | }
43 |
44 | return Optional.of(new WorldGuardRegion(location.getWorld(), region));
45 | }
46 |
47 | return Optional.empty();
48 | }
49 |
50 | @Override
51 | public Collection getRegions(World world) {
52 | RegionManager regionManager = this.regionContainer.get(BukkitAdapter.adapt(world));
53 | if (regionManager == null) {
54 | return Collections.emptyList();
55 | }
56 |
57 | return regionManager.getRegions()
58 | .values()
59 | .stream()
60 | .filter(region -> this.isCombatRegion(region))
61 | .map(region -> (Region) new WorldGuardRegion(world, region))
62 | .toList();
63 | }
64 |
65 | private boolean isCombatRegion(ProtectedRegion region) {
66 | if (this.regions.contains(region.getId())) {
67 | return true;
68 | }
69 |
70 | if (this.pluginConfig.regions.preventPvpInRegions) {
71 | StateFlag.State flag = region.getFlag(Flags.PVP);
72 |
73 | if (flag != null) {
74 | return flag.equals(StateFlag.State.DENY);
75 | }
76 | }
77 |
78 | return false;
79 | }
80 |
81 | private record WorldGuardRegion(World context, ProtectedRegion region) implements Region {
82 | @Override
83 | public Location getCenter() {
84 | BlockVector3 min = this.region.getMinimumPoint();
85 | BlockVector3 max = this.region.getMaximumPoint();
86 |
87 | double x = (double) (min.getX() + max.getX()) / 2;
88 | double z = (double) (min.getZ() + max.getZ()) / 2;
89 | double y = (double) (min.getY() + max.getY()) / 2;
90 |
91 | return new Location(this.context, x, y, z);
92 | }
93 |
94 | @Override
95 | public Location getMin() {
96 | BlockVector3 min = this.region.getMinimumPoint();
97 | return new Location(this.context, min.getX(), min.getY(), min.getZ());
98 | }
99 |
100 | @Override
101 | public Location getMax() {
102 | BlockVector3 max = this.region.getMaximumPoint();
103 | return new Location(this.context, max.getX(), max.getY(), max.getZ());
104 | }
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/updater/UpdaterNotificationController.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.updater;
2 |
3 | import com.eternalcode.combat.config.implementation.PluginConfig;
4 | import net.kyori.adventure.audience.Audience;
5 | import net.kyori.adventure.platform.AudienceProvider;
6 | import net.kyori.adventure.text.minimessage.MiniMessage;
7 | import org.bukkit.entity.Player;
8 | import org.bukkit.event.EventHandler;
9 | import org.bukkit.event.Listener;
10 | import org.bukkit.event.player.PlayerJoinEvent;
11 |
12 | import java.util.concurrent.CompletableFuture;
13 |
14 | public class UpdaterNotificationController implements Listener {
15 |
16 | private static final String NEW_VERSION_AVAILABLE = "EternalCombat: New version of EternalCombat is available, please update!";
17 | private static final String RECEIVE_UPDATES_PERMISSION = "eternalcombat.receiveupdates";
18 |
19 | private final UpdaterService updaterService;
20 | private final PluginConfig pluginConfig;
21 | private final AudienceProvider audienceProvider;
22 | private final MiniMessage miniMessage;
23 |
24 | public UpdaterNotificationController(UpdaterService updaterService, PluginConfig pluginConfig, AudienceProvider audienceProvider, MiniMessage miniMessage) {
25 | this.updaterService = updaterService;
26 | this.pluginConfig = pluginConfig;
27 | this.audienceProvider = audienceProvider;
28 | this.miniMessage = miniMessage;
29 | }
30 |
31 | @EventHandler
32 | void onJoin(PlayerJoinEvent event) {
33 | Player player = event.getPlayer();
34 | Audience audience = this.audienceProvider.player(player.getUniqueId());
35 |
36 | if (!player.hasPermission(RECEIVE_UPDATES_PERMISSION) || !this.pluginConfig.settings.notifyAboutUpdates) {
37 | return;
38 | }
39 |
40 | CompletableFuture upToDate = this.updaterService.isUpToDate();
41 |
42 | upToDate.whenComplete((isUpToDate, throwable) -> {
43 | if (throwable != null) {
44 | throwable.printStackTrace();
45 |
46 | return;
47 | }
48 |
49 | if (!isUpToDate) {
50 | audience.sendMessage(this.miniMessage.deserialize(NEW_VERSION_AVAILABLE));
51 | }
52 | });
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/updater/UpdaterService.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.updater;
2 |
3 | import com.eternalcode.gitcheck.GitCheck;
4 | import com.eternalcode.gitcheck.GitCheckResult;
5 | import com.eternalcode.gitcheck.git.GitRepository;
6 | import com.eternalcode.gitcheck.git.GitTag;
7 | import dev.rollczi.litecommands.shared.Lazy;
8 | import org.bukkit.plugin.PluginDescriptionFile;
9 |
10 | import java.util.concurrent.CompletableFuture;
11 |
12 | public class UpdaterService {
13 |
14 | private static final GitRepository GIT_REPOSITORY = GitRepository.of("EternalCodeTeam", "EternalCombat");
15 |
16 | private final GitCheck gitCheck = new GitCheck();
17 | private final Lazy gitCheckResult;
18 |
19 | public UpdaterService(PluginDescriptionFile descriptionFile) {
20 | this.gitCheckResult = new Lazy<>(() -> {
21 | String version = descriptionFile.getVersion();
22 |
23 | return this.gitCheck.checkRelease(GIT_REPOSITORY, GitTag.of("v" + version));
24 | });
25 | }
26 |
27 | public CompletableFuture isUpToDate() {
28 | return CompletableFuture.supplyAsync(() -> {
29 | GitCheckResult result = this.gitCheckResult.get();
30 |
31 | return result.isUpToDate();
32 | });
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/DurationUtil.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.util;
2 |
3 | import com.eternalcode.commons.time.DurationParser;
4 | import com.eternalcode.commons.time.TemporalAmountParser;
5 | import java.math.RoundingMode;
6 | import java.time.Duration;
7 | import java.time.temporal.ChronoUnit;
8 |
9 | public class DurationUtil {
10 |
11 | private static final TemporalAmountParser WITHOUT_MILLIS_FORMAT = new DurationParser()
12 | .withUnit("s", ChronoUnit.SECONDS)
13 | .withUnit("m", ChronoUnit.MINUTES)
14 | .withUnit("h", ChronoUnit.HOURS)
15 | .withUnit("d", ChronoUnit.DAYS)
16 | .withRounded(ChronoUnit.MILLIS, RoundingMode.UP);
17 |
18 | private static final TemporalAmountParser STANDARD_FORMAT = new DurationParser()
19 | .withUnit("d", ChronoUnit.DAYS)
20 | .withUnit("h", ChronoUnit.HOURS)
21 | .withUnit("m", ChronoUnit.MINUTES)
22 | .withUnit("s", ChronoUnit.SECONDS)
23 | .withUnit("ms", ChronoUnit.MILLIS);
24 |
25 | public static final Duration ONE_SECOND = Duration.ofSeconds(1);
26 |
27 | public DurationUtil() {
28 | throw new UnsupportedOperationException("This class cannot be instantiated");
29 | }
30 |
31 | public static String format(Duration duration, boolean removeMillis) {
32 | if (removeMillis) {
33 | if (duration.toMillis() < ONE_SECOND.toMillis()) {
34 | return "0s";
35 | }
36 |
37 | return WITHOUT_MILLIS_FORMAT.format(duration);
38 | }
39 |
40 | if (duration.toMillis() > ONE_SECOND.toMillis()) {
41 | return WITHOUT_MILLIS_FORMAT.format(duration);
42 | }
43 |
44 | return STANDARD_FORMAT.format(duration);
45 | }
46 |
47 | public static String format(Duration duration) {
48 | return format(duration, true);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/InventoryUtil.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.util;
2 |
3 | import org.bukkit.inventory.ItemStack;
4 |
5 | import java.util.ArrayList;
6 | import java.util.Collection;
7 | import java.util.List;
8 | import java.util.Random;
9 | import java.util.function.ToIntFunction;
10 |
11 | public class InventoryUtil {
12 |
13 | private static final Random RANDOM = new Random();
14 |
15 | private InventoryUtil() {
16 | throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
17 | }
18 |
19 | public static int calculateItemsToDelete(int percent, Collection objectList, ToIntFunction super T> mapper) {
20 | return MathUtil.getRoundedCountFromPercentage(percent, MathUtil.sum(objectList, mapper));
21 | }
22 |
23 | public static RemoveItemResult removeRandomItems(List list, int itemsToDelete) {
24 | List currentItems = new ArrayList<>(list);
25 | List removedItems = new ArrayList<>();
26 |
27 | int currentItemsToDelete = itemsToDelete;
28 | while (currentItemsToDelete > 0) {
29 | int randomIndex = RANDOM.nextInt(list.size());
30 | ItemStack currentItem = currentItems.get(randomIndex);
31 |
32 | int amount = currentItem.getAmount();
33 | int randomAmount = RANDOM.nextInt(0, Math.min(currentItemsToDelete, amount) + 1);
34 |
35 | if (amount <= 0 || randomAmount <= 0) {
36 | continue;
37 | }
38 |
39 | ItemStack removedItem = currentItem.clone();
40 | removedItem.setAmount(randomAmount);
41 | removedItems.add(removedItem);
42 |
43 | currentItem.setAmount(amount - randomAmount);
44 |
45 | currentItemsToDelete -= randomAmount;
46 | }
47 |
48 | return new RemoveItemResult(currentItems, removedItems);
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/MathUtil.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.util;
2 |
3 | import java.util.Collection;
4 | import java.util.function.ToIntFunction;
5 |
6 | public class MathUtil {
7 |
8 | private MathUtil() {
9 | throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
10 | }
11 |
12 | public static double getCountFromPercentage(int percentage, int totalCount) {
13 | return percentage / 100D * totalCount;
14 | }
15 |
16 | public static double getCountPercentage(double value, double base) {
17 | return value / base * 100;
18 | }
19 |
20 | public static int getRoundedCountPercentage(double value, double base) {
21 | return (int) Math.round(getCountPercentage(value, base));
22 | }
23 |
24 | public static int getRoundedCountFromPercentage(int percentage, int baseNumber) {
25 | return (int) Math.round(getCountFromPercentage(percentage, baseNumber));
26 | }
27 |
28 | public static int sum(Collection collection, ToIntFunction super T> mapper) {
29 | return collection.stream().mapToInt(mapper).sum();
30 | }
31 |
32 | public static int clamp(int value, int min, int max) {
33 | return Math.max(min, Math.min(max, value));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/RemoveItemResult.java:
--------------------------------------------------------------------------------
1 | package com.eternalcode.combat.util;
2 |
3 | import org.bukkit.inventory.ItemStack;
4 |
5 | import java.util.Collections;
6 | import java.util.List;
7 |
8 | public record RemoveItemResult(
9 | List restItems,
10 | List removedItems
11 | ) {
12 |
13 | public List restItems() {
14 | return Collections.unmodifiableList(this.restItems);
15 | }
16 |
17 | @Override
18 | public List removedItems() {
19 | return Collections.unmodifiableList(this.removedItems);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EternalCodeTeam/EternalCombat/d6bf4e628c451b399f40d685b21003e8055f698d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "dependencyDashboard": true,
4 | "extends": [
5 | "config:base"
6 | ],
7 | "groupName": "all dependencies",
8 | "groupSlug": "all",
9 | "lockFileMaintenance": {
10 | "enabled": false
11 | },
12 | "separateMajorMinor": true,
13 | "pruneStaleBranches": true,
14 | "commitMessagePrefix": "dependency:",
15 | "packageRules": [
16 | {
17 | "groupName": "patch",
18 | "matchPackagePatterns": [
19 | "*"
20 | ],
21 | "excludePackagePatterns": ["org.spigotmc*"],
22 | "updateTypes": ["patch"],
23 | "automerge": true
24 | },
25 | {
26 | "groupName": "minor",
27 | "matchPackagePatterns": [
28 | "*"
29 | ],
30 | "excludePackagePatterns": ["org.spigotmc*"],
31 | "updateTypes": ["minor"],
32 | "automerge": true
33 | },
34 | {
35 | "groupName": "all-major",
36 | "matchPackagePatterns": [
37 | "*"
38 | ],
39 | "updateTypes": ["major"],
40 | "excludePackagePatterns": ["org.spigotmc*"],
41 | "automerge": false
42 | },
43 | {
44 | "groupName": "spigotmc",
45 | "matchPackagePatterns": [
46 | "org.spigotmc*"
47 | ],
48 | "allowedVersions": "/^\\d+\\.\\d+(\\.\\d+)?-R\\d+\\.\\d+-SNAPSHOT$/",
49 | "automerge": false
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "EternalCombat"
2 | include("eternalcombat-api")
3 | include("eternalcombat-plugin")
4 |
--------------------------------------------------------------------------------