├── .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 | ![](/assets/readme-banner.png) 4 | 5 | [![Available on SpigotMC](https://raw.githubusercontent.com/vLuckyyy/badges/main/available-on-spigotmc.svg)](https://www.spigotmc.org/resources/eternalcombat-%E2%9C%94%EF%B8%8F-enchance-your-combat-system-with-eternalcombat.109056/) 6 | [![Available on Modrinth](https://raw.githubusercontent.com/vLuckyyy/badges/main/avaiable-on-modrinth.svg)](https://modrinth.com/plugin/eternalcombat) 7 | [![Available on Hangar](https://raw.githubusercontent.com/vLuckyyy/badges/main/avaiable-on-hangar.svg)](https://hangar.papermc.io/EternalCodeTeam/eternalcombat) 8 | 9 | [![Chat on Discord](https://raw.githubusercontent.com/vLuckyyy/badges/main//chat-with-us-on-discord.svg)](https://discord.com/invite/FQ7jmGBd6c) 10 | [![Read the Docs](https://raw.githubusercontent.com/vLuckyyy/badges/main/read-the-documentation.svg)](https://docs.eternalcode.pl/eternalcombat/introduction) 11 | [![Available on BStats](https://raw.githubusercontent.com/vLuckyyy/badges/main/available-on-bstats.svg)](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 | ![](/assets/combatlog.gif) 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 | ![](/assets/border.gif) 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 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 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 | --------------------------------------------------------------------------------