Compare commits

...
228 Commits
Author SHA1 Message Date
NotAlexNoyle 9502ed2fd9 Exclude CODEOWNERS from language statistics 2026-10-29 16:18:56 -04:00
NotAlexNoyle c3eb6c5595 Add CODEOWNERS (one staff approval) 2026-10-29 16:18:56 -04:00
NotAlexNoyle 410aaf1eb7 Fix regions 2026-09-14 01:50:11 -04:00
NotAlexNoyle 4ab18417be World blacklist -> world whitelist 2026-07-30 18:53:27 -04:00
NotAlexNoyle d739ed6d1f Fix actionbar typo 2026-06-24 10:48:25 -04:00
NotAlexNoyle a98f16d0df Update action bar format 2026-06-16 10:19:02 -04:00
NotAlexNoyle 40c3178312 Restyle action bar 2026-06-16 08:10:23 -04:00
NotAlexNoyle 70505da275 Fix opponent monitoring 2026-06-16 06:37:07 -04:00
NotAlexNoyle 710be6064a Disable combat mode when all opponents logged out 2026-06-16 05:52:25 -04:00
NotAlexNoyle 56d34cc781 Fix pearl throwing 2026-06-15 21:52:14 -04:00
NotAlexNoyle 50116b867c Fix pearl delay, disable environmental and mob combat logging 2026-06-15 12:10:34 -04:00
NotAlexNoyle 4d74dca4f1 Fix first pearl not working in combat 2026-06-15 10:43:23 -04:00
NotAlexNoyle 8ae2ad9db6 Add arenas to whitelist 2026-06-08 09:17:56 -04:00
NotAlexNoyle ea3997a97b Update README.md 2026-05-27 08:20:47 -04:00
NotAlexNoyle 1153258192 Fix NCP hook 2026-05-27 08:18:54 -04:00
NotAlexNoyle 1f3b4a7215 Release EternalCombat-OG 2026-05-27 07:33:11 -04:00
NotAlexNoyle cc85e0ad0c Remove gradients in chat 2026-05-11 08:21:04 -04:00
NotAlexNoyle 6f6ec3e785 Whitelist The Herobrine games 2026-05-11 06:26:23 -04:00
NotAlexNoyle 1efd1ebe79 Fix runtime exec error with static config file 2026-04-22 15:10:34 -04:00
NotAlexNoyle 5abb072ea0 Delete github actions 2026-04-22 13:41:39 -04:00
NotAlexNoyle 8e93f90459 Merge branch 'pr-5'
# Conflicts:
#	.github/CONTRIBUTING.md
#	.github/PULL_REQUEST_TEMPLATE.md
#	.github/workflows/gradle.yml
#	.github/workflows/hangar-publish.yml
#	.github/workflows/modrinth-publish.yml
#	buildSrc/src/main/kotlin/Versions.kt
#	eternalcombat-plugin/build.gradle.kts
#	eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java
2026-04-22 13:38:44 -04:00
imgbot[bot]andImgBotApp 459ff06f18 [ImgBot] Optimize images (#361)
*Total -- 20,587.83kb -> 17,156.76kb (16.67%)

/assets/lightning.gif -- 4,208.49kb -> 2,771.07kb (34.16%)
/assets/flare.gif -- 6,525.52kb -> 5,502.98kb (15.67%)
/assets/readme-banner.png -- 112.85kb -> 98.39kb (12.81%)
/assets/border.gif -- 6,004.89kb -> 5,373.86kb (10.51%)
/assets/combatlog.gif -- 2,949.14kb -> 2,658.24kb (9.86%)
/assets/crystals.gif -- 786.95kb -> 752.21kb (4.41%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2026-04-19 20:59:49 +02:00
renovate[bot] 4a153d5557 dependency: Update actions/github-script action to v9 (#372)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-12 11:07:04 +02:00
renovate[bot] 3e5b060923 dependency: Update dependency com.github.retrooper:packetevents-spigot to v2.12.0 (#370)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-12 11:06:38 +02:00
renovate[bot] 03768ccd8d dependency: Update dependency com.sk89q.worldguard:worldguard-bukkit to v7.0.16 (#368)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-12 11:06:14 +02:00
renovate[bot] ba59d85996 dependency: Update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9.4.1 (#369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-12 11:06:04 +02:00
renovate[bot] 1c432cbcb3 dependency: Update dependency com.github.angeschossen:LandsAPI to v7.25.4 (#366)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-12 11:03:33 +02:00
renovate[bot] de6b76526c dependency: Update gradle/actions action to v6 (#367)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-12 11:03:23 +02:00
NotAlexNoyle 65131df123 Drop Lands support 2026-03-30 04:22:20 -04:00
NotAlexNoyle db9b40d687 Depend on correct PacketEvents 2026-03-25 12:51:16 -04:00
NotAlexNoyle 1324f4b846 Drop dependency on PacketEvents-OG 2026-03-25 10:52:08 -04:00
NotAlexNoyle e2cabb7aad Update PacketEvents API to 2.11.2 2026-03-25 06:05:41 -04:00
NotAlexNoyle 7b7b44457c Merge pull request #4 from true-og/codex/use-existing-config-in-plugin-jar
Use bundled plugin config.yml as canonical resource
2026-03-23 13:32:15 -04:00
NotAlexNoyle 89704d9729 Use bundled plugin config resource 2026-03-23 13:31:58 -04:00
NotAlexNoyle 566022eaea Add config.yml 2026-03-23 13:25:17 -04:00
NotAlexNoyle ebb26266d4 Update PacketEvents-OG 2026-03-23 07:30:48 -04:00
NotAlexNoyle 97c0238487 Merge pull request #3 from true-og/codex/fix-gradle-build-error-for-shadowjar
Fix lazy shadowJar wiring in root build
2026-03-23 07:05:43 -04:00
NotAlexNoyle f06ee72792 Fix lazy shadowJar wiring in root build 2026-03-23 07:05:33 -04:00
NotAlexNoyle 5244e09de9 Merge pull request #2 from true-og/codex/modify-gradle-build-for-shaded-jar
Fix shaded jar output naming
2026-03-23 07:00:35 -04:00
NotAlexNoyle a1dea29fc1 Fix shaded jar output naming 2026-03-23 07:00:23 -04:00
NotAlexNoyle c57e22fd22 v2.4.1 merge from upstream 2026-03-23 06:54:30 -04:00
renovate[bot] 7de452ac15 dependency: Update Gradle to v9.4.1 (#365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-22 09:31:52 +01:00
renovate[bot]andJakub Kędziora 7e578d63f7 dependency: Update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9.4.0 (#363)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>
2026-03-22 09:31:44 +01:00
renovate[bot] e5f86c53a2 dependency: Update dependency org.jetbrains.kotlin:kotlin-stdlib-jdk8 to v2.3.20 (#364)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-22 09:31:15 +01:00
NotAlexNoyle fc16e2f8fe Merge pull request #1 from true-og/codex/fix-runtime-error-in-eternalcombat-plugin
Make root build produce loadable plugin jar by packaging plugin shadow JAR
2026-03-16 05:45:55 -04:00
NotAlexNoyle 838bb16e12 Fix root Gradle artifact to package plugin shadow jar 2026-03-16 05:45:44 -04:00
renovate[bot] 22260e87d0 dependency: Update plugin com.modrinth.minotaur to v2.9.0 (#359)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 19:06:51 +01:00
fe73466cfb GH-352 Add Arc Riders style flare - firework - indicating players death location (#352)
* Innit

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

* geminaj review

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

* Add try catch

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

* CR

* CR 2

* Add flare damage cancel. Add info to config

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

* make it private

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

* Resolve ImDMK review

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

* CR

* CR

* adjust default configuration to vLucky's review

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathFlareController.java

Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>

---------

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>
Co-authored-by: Rollczi <ndejlich5@gmail.com>
Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>
2026-03-08 18:48:58 +01:00
renovate[bot] c299aed434 dependency: Update Versions.ETERNALCODE_COMMONS to v1.3.4 (#351)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-07 11:19:36 +01:00
renovate[bot]andJakub Kędziora 8dbaf9ae98 dependency: Update actions/upload-artifact action to v7 (#353)
* dependency: Update actions/upload-artifact action to v7

* Remove archive option from upload-artifact step

Remove unnecessary archive option from upload-artifact.

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>
2026-03-07 11:12:59 +01:00
renovate[bot] db4ab96377 dependency: Update dependency org.bstats:bstats-bukkit to v3.2.1 (#350)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-07 11:12:51 +01:00
renovate[bot] 0dca7e7d42 dependency: Update Gradle to v9.4.0 (#356)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-07 11:12:24 +01:00
renovate[bot] bb080b5ce9 dependency: Update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9.3.2 (#354)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-07 10:12:10 +00:00
Martin SulikowskiandRollczi 9f4ae311a7 GH-345 Fix enforcing region knockback for mounted combat players (#345)
* fix: enforce region knockback for mounted combat players

* CR

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2026-03-03 23:28:16 +01:00
Martin Sulikowski 96deb783b7 Update README.md 2026-02-23 23:54:02 +01:00
bee80d22aa GH-313 Migrate issue templates to GitHub Forms and add enhanced contribution documentation.
* Migrate issue templates to GitHub Forms and add enhanced contribution documentation.

* Update .github/CONTRIBUTING.md

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update .github/CONTRIBUTING.md

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update pull request template to use `GH-{NUMBER}` in title format.

* Update .github/CONTRIBUTING.md

* Apply suggestions from code review

* Apply suggestions from code review

* Update label for additional context field

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>
2026-02-20 15:52:50 +01:00
renovate[bot] 4202e86eaa dependency: Update Versions.ETERNALCODE_COMMONS to v1.3.3 (#349)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-20 15:34:30 +01:00
renovate[bot] e109b38705 dependency: Update junit-framework monorepo to v6.0.3 (#342)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-20 10:50:16 +01:00
renovate[bot] 9bba5bd418 dependency: Update dependency com.github.angeschossen:LandsAPI to v7.24.1 (#341)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-20 10:50:07 +01:00
renovate[bot] 77940ca7b6 dependency: Update dependency org.jetbrains:annotations to v26.1.0 (#346)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-20 10:49:44 +01:00
Michał Wojtas a4cc6d0e8b GH-347 Add lightning effect on player death and settings (#347)
* Add lightning effect on player death and settings

* Add lightning gif to readme

* typo fix

* use public for controller - consistency
2026-02-19 22:54:25 +01:00
Martin Sulikowski 2028c5eafd GH-338 Add option to block specific inventories during combat (#338)
* feat: Add option to block specific inventories during combat

* refactor: Simplify inventory type reference in FightActionBlockerController

* Move to dedicated class.

* refactor: Remove blocking of player's own and creative inventories in FightInventoryController
2026-02-08 20:02:14 +01:00
renovate[bot] 042c5848df dependency: Update dependency me.clip:placeholderapi to v2.12.2 (#340)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-08 20:01:56 +01:00
renovate[bot] c30860070f dependency: Update dependency com.github.retrooper:packetevents-spigot to v2.11.2 (#330)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-08 11:22:15 +01:00
Jakub Kędziora 20c3515082 GH-339 Move renovate.json to .github directory (#339) 2026-02-07 22:00:46 +01:00
Rollczi 07b22a1141 Fix snapshots 2026-02-07 18:38:10 +01:00
renovate[bot] 2fc9bc580b dependency: Update dependency org.jetbrains.kotlin:kotlin-stdlib-jdk8 to v2.3.10 (#337)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-05 20:53:38 +01:00
renovate[bot] 7730956dbc dependency: Update dependency me.clip:placeholderapi to v2.12.1 (#336)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-05 20:53:20 +01:00
Rollczi 48ef3e5d0a Simplify snapshots 2026-02-02 20:39:49 +01:00
renovate[bot] abd097401f dependency: Update Gradle to v9.3.1 (#332)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-30 16:22:21 +01:00
Martin Sulikowski 1d0d182cc8 Update Discord invite link in README.md 2026-01-26 00:40:24 +01:00
Martin Sulikowski 60b4e6ee9c bump version to 2.5.0-SNAPSHOT for further development. 2026-01-25 21:15:28 +01:00
Martin Sulikowski b096cb5d6f update dependency references in README 2026-01-25 21:13:08 +01:00
Martin Sulikowski d596202278 Release v2.4.1 2026-01-25 21:07:13 +01:00
Martin Sulikowski b879497840 Add WorldGuard as a soft dependency 2026-01-25 21:06:35 +01:00
renovate[bot] ccad96ae96 dependency: Update gradle/actions action to v5 (#329)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-24 21:39:47 +01:00
Martin Sulikowski 0e782a0cf5 Release 2.5.0-SNAPSHOT 2026-01-23 21:16:40 +01:00
Martin Sulikowski 0979d449c6 Release 2.4.0 2026-01-23 20:37:50 +01:00
renovate[bot] dee65e5f59 dependency: Update actions/upload-artifact action to v6 (#327)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 20:30:16 +01:00
renovate[bot] 01d3491d44 dependency: Update dependency com.github.angeschossen:LandsAPI to v7.23.1 (#314)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 20:30:00 +01:00
renovate[bot] 9896069577 dependency: Update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9.3.1 (#316)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 20:29:53 +01:00
renovate[bot] f682508172 dependency: Update actions/setup-java action to v5 (#326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 20:29:45 +01:00
renovate[bot] a988abcd17 dependency: Update dependency com.sk89q.worldguard:worldguard-bukkit to v7.0.15 (#307)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 20:26:53 +01:00
renovate[bot] b0fa197e99 dependency: Update Versions.MULTIFICATION to v1.2.4 (#311)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 20:26:42 +01:00
renovate[bot] 5300b5a007 dependency: Update Versions.LITE_COMMANDS to v3.10.9 (#315)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 20:26:07 +01:00
renovate[bot] c1f282832c dependency: Update dependency net.kyori:adventure-text-minimessage to v4.26.1 (#317)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 20:25:23 +01:00
renovate[bot] 972fc52e69 dependency: Update dependency org.jetbrains.kotlin:kotlin-stdlib-jdk8 to v2.3.0 (#318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 20:25:16 +01:00
renovate[bot] b79ed28edf dependency: Update actions/checkout action to v6 (#320)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 20:25:00 +01:00
renovate[bot] 025f49685c dependency: Update actions/github-script action to v8 (#321)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 20:24:53 +01:00
Martin Sulikowski 4d957d2c8a Update README to include bug reporting, feature requests, and donation link 2026-01-23 20:19:54 +01:00
d7a04ef0c2 GH-249 Skip only collidable blocks in the fake wall. Keep plants proper restoring. (#301)
* Update BorderBlockController.java

* Update BorderBlockController.java

* Update BorderBlockController.java

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Follow suggestions

* Code style

* Save WrappedBlockState

* Use inner point for border rendering

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Rollczi <ndejlich5@gmail.com>
2026-01-23 19:54:10 +01:00
Michał Wojtas 0bd9911df3 GH-325 Fix bypass permission and excludeAdminsFromCombat interaction (#325)
* Fix information about bypass permission

* Changes requested after talking to vLucky
2026-01-23 00:01:09 +01:00
Martin Sulikowski 3062378c66 Improve -SNAPSHOT version update checker. 2026-01-17 02:45:14 +01:00
renovate[bot] 0799081813 dependency: Update dependency me.clip:placeholderapi to v2.11.7 (#300)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-17 02:21:24 +01:00
renovate[bot] 16a2df1a08 dependency: Update junit-framework monorepo to v6.0.2 (#299)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-17 02:21:20 +01:00
renovate[bot] cd3681f8f6 dependency: Update Gradle to v9.3.0 (#298)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-17 02:21:14 +01:00
renovate[bot] 885f514e17 dependency: Update dependency com.github.ben-manes.caffeine:caffeine to v3.2.3 (#297)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-17 02:21:10 +01:00
renovate[bot] 5b84881b94 dependency: Update dependency org.jetbrains.kotlin:kotlin-stdlib-jdk8 to v2.2.21 (#294)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-17 02:21:04 +01:00
renovate[bot] e69a42ba0f dependency: Update dependency com.github.retrooper:packetevents-spigot to v2.11.1 (#293)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-17 02:20:58 +01:00
renovate[bot] 9783d7a025 dependency: Update dependency xyz.jpenilla:run-task to v3.0.2 (#291)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-17 02:20:51 +01:00
Martin Sulikowski 99a93ce249 Bump Hangar publish plugin to version 0.1.4 in build script. 2026-01-17 02:15:29 +01:00
Martin Sulikowski 1c2797225a GH-310 Add Hangar publishing workflow and configure build script for Hangar integration (#312)
* GH-310 Add Hangar publishing workflow and configure build script for Hangar integration

* Expand supported Paper versions in `gradle.properties` to include versions 1.17 through 1.21.11.
2026-01-17 02:09:41 +01:00
renovate[bot] 1af5710ac9 dependency: Update Versions.LITE_COMMANDS to v3.10.8 (#289)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-16 20:14:35 +00:00
Martin Sulikowski 1938ae409d [ci skip] Update tested Minecraft versions in README to include 1.21.11. 2026-01-16 20:48:57 +01:00
Martin Sulikowski 88fa996202 Update Gradle workflow to use latest upload-artifact action and ensure full git history fetch. Add enhanced error handling for versioning in build script. 2026-01-16 20:13:56 +01:00
Martin Sulikowski e3c4f2c26d Improve versioning logic in build script and set fetch-depth to 0 in Modrinth workflow. 2026-01-16 20:04:52 +01:00
Martin Sulikowski e87060d2b9 Simplify changelog retrieval by removing redundant nullable type qualifier. 2026-01-16 19:24:49 +01:00
Martin Sulikowski dfe70a65d7 Enhance Modrinth workflow to include changelog support 2026-01-16 19:21:20 +01:00
Martin Sulikowski fbade09087 Update Modrinth publishing workflow for release compatibility 2026-01-16 00:53:46 +01:00
Martin Sulikowski 660b8b5519 Bump version to 2.4.0-SNAPSHOT. 2026-01-16 00:53:13 +01:00
Martin Sulikowski 13eb6dead2 Switch MODRINTH_TOKEN retrieval to System.getenv in build script. 2026-01-16 00:32:06 +01:00
Martin Sulikowski eafb4c9318 Simplify version number retrieval in Modrinth configuration. 2026-01-16 00:30:14 +01:00
Martin Sulikowski bfc632d518 GH-309 Add Modrinth publishing workflow and configure Minotaur (#309)
* Add Modrinth publishing workflow and configure Minotaur

* Replace println with logger in build script, retrieve environment variables via providers API.
2026-01-16 00:26:42 +01:00
Michał Wojtas 02d5a2f46d GH-308 Fix issue where master head.ref cannot be accessed (#308) 2026-01-11 19:06:55 +01:00
Michał WojtasandRollczi 9e3f28ee10 GH-305 Update GitHub Actions workflow to set build label for each branch (#305)
* Update GitHub Actions workflow to set build label for release and snapshot

* Fix artifact containing not correct name info

* Adjust space

* Use sha in name as @Jakubk15 suggested

* Fix deprecation warnings

* Fix ugly SHA

* Use gradle instead of gradlew made for gradleWrapper

* run fix

* run fix v2

* Simplify by using github script

* Bump gradle cache

* Remove master / snapshot from name - already branch name is used.

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2026-01-10 20:49:04 +01:00
Michał Wojtas e5fb0d4b32 GH-276 Extract bypass conditions to controller class. (#302)
* Extract bypass conditions to extra class.

Took 2 minutes

* Resolve Gemini review

Took 4 minutes

* Move Controller to controller package and remove empty line

Took 5 minutes

* Use build in Modrinth method to download plugins

Took 23 minutes

* Resolve igoyek's review

Took 3 minutes

* Resolve jakubek's review

Took 15 minutes

* Try #1 resolve imDMK comment

Took 5 minutes
2026-01-07 19:13:11 +01:00
Michał Wojtas 3325a2f8d3 GH-285 Remove border when player is not in combat (#290)
* Remove border when player is not in combat. Add WorldEdit for easier tests

* Add 5 tick delay which reduces 90% of done tests - remove initial solution

* Resolve AI review

* Use our scheduler, add isInCombat check to negate race condition

* Add comment explaining race confition, follow rollczi suggestion
2025-10-13 09:50:51 +02:00
a0bd8494f8 GH-253 Add configurable placeholder settings and new placeholders (#253)
* Add configurable placeholder settings and new placeholders

Introduces PlaceholderSettings to allow customization of placeholder output, specifically for isInCombat_formatted. Adds new placeholders isInCombat and isInCombat_formatted to FightTagPlaceholder, updates BridgeService and PluginConfig to support the new settings, and adjusts command permission annotation placement. Also updates build scripts and dependencies.

* rollback version

* Add papi plugin to runServer

* remove unused import

* follow gemini's suggestion

* Add more comments for configuration, merge time combat placeholders

Took 14 minutes

* change download source to spigot - got 503 on download from github during tests

Took 10 minutes

* follow Rollczi's suggestion -> refactored placeholder registry

* empty string -> null (papi, when receiving null in the code, marks it and sends an empty string)

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java

Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>

* Refactor FightTagPlaceholder and update build script

- Simplified conversion to string in handleIsInCombat.
- Adjusted build script to use Modrinth URLs for plugin downloads and rolled back Minecraft version to 1.21.

---------

Co-authored-by: Michał Wojtas <wojtas.michal90@gmail.com>
Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>
2025-10-13 02:35:07 +02:00
Martin Sulikowski 08cfe7c0c4 Release v2.3.0 2025-10-06 19:50:07 +02:00
Martin Sulikowski ed30d43736 Update readme-banner.png asset. 2025-10-06 19:43:10 +02:00
Martin Sulikowski b6347517b6 Update dependencies and support Minecraft 1.21.9 2025-10-06 19:41:14 +02:00
renovate[bot] 3f729beffe dependency: Update dependency net.kyori:adventure-text-minimessage to v4.25.0 (#288)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 18:51:47 +02:00
Jakub Kędziora 337eb7d765 GH-278 Move updater to eternalcode-commons library (#287)
* Move updater to eternalcode-commons library

* Use gson 2.11.0 for WorldGuard and update gradle.properties to latest standards
2025-10-06 18:49:03 +02:00
renovate[bot] b02231bc0c GH-283 Update junit-framework monorepo to v6 (major)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-04 18:01:12 +02:00
renovate[bot] ee6f5aecd3 GH-284 Update dependency xyz.jpenilla:run-task to v3.0.1
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-04 17:57:26 +02:00
renovate[bot] 6eb89607bf GH-286 Update dependency com.github.angeschossen:LandsAPI to v7.17.2
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-04 17:56:50 +02:00
renovate[bot] 9f33de088b GH-280 Update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9.2.2
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-04 17:56:06 +02:00
815b63c7bf GH-188 Add support for folia-based servers. (#275)
* Release v2.3.0-SNAPSHOT

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatSchedulerAdapter.java

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Make `CombatSchedulerAdapter` uninstantiable.

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatSchedulerAdapter.java

Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>

* Resolve gemini issues.

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>
2025-09-29 00:01:58 +02:00
Michał Wojtas 410289a2b8 GH-274 Region priority sort fix (#274) 2025-09-28 23:55:52 +02:00
renovate[bot] e40a37e2ff dependency: Update Gradle to v9.1.0 (#277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-21 21:38:35 +02:00
NotAlexNoyle 53c45aa939 Depend on PacketEvents-OG for 1.19.4 2025-09-19 17:09:55 -04:00
NotAlexNoyle 625ba81281 Update EternalCombat to 2.2.0 for 1.19.4 2025-09-17 14:54:22 -04:00
renovate[bot] 1bae5c547d dependency: Update dependency com.sk89q.worldguard:worldguard-bukkit to v7.0.14 (#256)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 22:53:52 +02:00
renovate[bot] 986895d5a4 dependency: Update dependency org.jetbrains.kotlin:kotlin-stdlib-jdk8 to v2.2.20 (#246)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 22:47:27 +02:00
renovate[bot] 4776714893 dependency: Update dependency com.github.retrooper:packetevents-spigot to v2.9.5 (#268)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 22:45:06 +02:00
renovate[bot] 6154f26977 dependency: Update dependency eu.okaeri:okaeri-configs-serdes-bukkit to v5.0.13 (#270)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 22:43:58 +02:00
renovate[bot] 2f010ee978 dependency: Update Versions.MULTIFICATION to v1.2.2 (#259)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 22:43:37 +02:00
renovate[bot] 7d5d074811 dependency: Update dependency dev.rollczi:litecommands-bukkit to v3.10.5 (#269)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 22:42:48 +02:00
renovate[bot] 8315e198b3 dependency: Update dependency eu.okaeri:okaeri-configs-serdes-commons to v5.0.13 (#271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 22:42:13 +02:00
renovate[bot] b0cbdc04c8 dependency: Update dependency org.jetbrains:annotations to v26.0.2-1 (#272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 22:42:03 +02:00
renovate[bot] f46f9ca268 dependency: Update dependency xyz.jpenilla:run-task to v3 (#273)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 22:41:52 +02:00
renovate[bot] d789d3b47e dependency: Update dependency net.kyori:adventure-text-minimessage to v4.24.0 (#260)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-13 15:43:06 +02:00
renovate[bot] dd7eaf2de8 dependency: Update Versions.ETERNALCODE_COMMONS to v1.3.1 (#261)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-13 15:42:54 +02:00
renovate[bot] a985774272 dependency: Update actions/checkout action to v5 (#262)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-13 15:42:43 +02:00
renovate[bot] fa000b4a3a dependency: Update actions/setup-java action to v5 (#263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-13 15:42:33 +02:00
renovate[bot] 80edd3b425 dependency: Update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9 (#264)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-13 15:42:24 +02:00
renovate[bot] fc4a6c28a3 dependency: Update dependency gradle to v9 (#265)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-13 15:42:15 +02:00
vLuckyyy e4eb847b56 Release v2.3.0-SNAPSHOT 2025-08-30 15:23:19 +02:00
renovate[bot] f5884119d6 dependency: Update dependency net.kyori:adventure-platform-bukkit to v4.4.1 (#257)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 21:01:59 +02:00
renovate[bot] 0cd9d50086 dependency: Update dependency gradle to v8.14.3 (#241)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 21:01:45 +02:00
renovate[bot] 7043d7baff dependency: Update dependency eu.okaeri:okaeri-configs-serdes-bukkit to v5.0.9 (#239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 21:01:28 +02:00
renovate[bot] 26d3f5b1ce dependency: Update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v8.3.9 (#255)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 21:01:13 +02:00
Martin Sulikowski 455b66e136 Use global renovate config. 2025-08-23 20:51:59 +02:00
renovate[bot] 881d9b633a dependency: Update dependency dev.rollczi:litecommands-bukkit to v3.10.4 (#238)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 20:50:44 +02:00
renovate[bot] ac80f1bb0c dependency: Update junit-framework monorepo to v5.13.4 (#243)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 20:49:19 +02:00
renovate[bot] cd36abae37 dependency: Update dependency eu.okaeri:okaeri-configs-serdes-commons to v5.0.9 (#240)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 20:49:03 +02:00
renovate[bot] 817b907170 dependency: Update dependency com.github.retrooper:packetevents-spigot to v2.9.4 (#245)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 20:44:55 +02:00
renovate[bot] b9dfb293e4 dependency: Update dependency org.jetbrains.kotlin:kotlin-stdlib-jdk8 to v2.1.21 (#242)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 20:44:40 +02:00
renovate[bot] 11bfddfc9f dependency: Update dependency com.github.ben-manes.caffeine:caffeine to v3.2.2 (#247)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 20:43:44 +02:00
74d0f487c3 GH-254 Add configuration for disabling fireworks during combat (#254)
* Add FireworkController, move CrystalPvpSettings to configuration package - same as other settings in this project

* Add message sent to the player. Remove unused import

* Add notice service :/

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CombatSettings.java

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/firework/FireworkController.java

Co-authored-by: Igor Michalski <imichalsky00@gmail.com>

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java

Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Igor Michalski <imichalsky00@gmail.com>
Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>
2025-08-22 00:48:46 +02:00
vLuckyyy f4cc51e9a1 Mark as supported for 1.21.8. Fix readme. 2025-07-29 23:23:35 +02:00
Martin SulikowskiandJakubk15 e579ca4a22 Release v2.2.0, Support 1.21.8 (#251)
* Support 1.21.8

* update repo

* Release v2.2.0, remove default sound in combat notification.

---------

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>
2025-07-29 23:06:04 +02:00
Michał Wojtas 4c13a61752 GH-250 Update readme with new .gif and new logo (#250)
* Update readme with new .gif and new logo

Took 8 minutes

* Add notice about PacketEvents requirement update banner and resize gif

Took 10 minutes

* Move Crystal pvp section down

Took 1 minute

* Add accessibility changes for gifs

Took 3 minutes
2025-07-27 21:37:01 +02:00
Michał Wojtasandcoderabbitai[bot] dad09ba196 GH-218 Add CrystalPVP expansion (#218)
* Init

* Add anchor support

* Add anchor to api

* Add meta data contants create static methods to retrive uuid. Follow @ImDMK and @coderabbitai reviews

* Use coderabbit suggestion to version compatibility

* Follow coderabbit review

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Resolve review. Add documentation comment, add ignored worlds check and event.cancelled check. Fix method name to use `UniqueId` instead of `UUID`

* Remove isCancelled() check -> follow coderabbit <3

* follow coderabbit - add one check for hasDamagerBlockState method declaration check

* Resolve vLuckyyy review

* e -> exception rename

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-07-22 23:03:30 +02:00
vLuckyyy 690678c1ea Release v2.1.1 2025-07-11 21:18:10 +02:00
renovate[bot] 40cac29eb0 dependency: Update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v8.3.8 (#236)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 20:29:41 +02:00
renovate[bot] b9c625cdd6 dependency: Update dependency com.github.ben-manes.caffeine:caffeine to v3.2.1 (#235)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 20:26:38 +02:00
renovate[bot] 861aad1852 dependency: Update dependency com.github.angeschossen:LandsAPI to v7.15.20 (#234)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 20:26:24 +02:00
vLuckyyy 4c30b7dad6 Migrate renovate config. 2025-07-11 19:18:12 +02:00
Martin Sulikowski 7a152e0624 GH-233 Fix pearl cooldown (#233)
* Fix pearl cooldown

Signed-off-by: vLuckyyy <vluckyyy.biznes@gmail.com>

* Fix cooldown.

---------

Signed-off-by: vLuckyyy <vluckyyy.biznes@gmail.com>
2025-07-11 19:07:20 +02:00
Martin Sulikowski 487fc971bc GH-232 Fix head drop (#232)
* Fix head drop

Signed-off-by: vLuckyyy <vluckyyy.biznes@gmail.com>

* Add minimessages coloring to head drops.

---------

Signed-off-by: vLuckyyy <vluckyyy.biznes@gmail.com>
2025-06-25 00:05:02 +02:00
renovate[bot] ba5fa97e1e dependency: Update dependency net.kyori:adventure-text-minimessage to v4.23.0 (#230)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 12:10:21 +02:00
renovate[bot] f2ab289794 dependency: Update minor (#228)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-17 20:51:12 +02:00
Martin SulikowskiandRollczi 241898d62a GH-226 Add Lands support. (#226)
* Add Lands support.

* Fix.

* Fix support.

* Remove useless code. Fix lands implementation. Fix a force knockback system. Fix configuration reloads for border features. Improve default border particle configuration for a better client experience (more fps)

* Fix triggers indexing. Update border on tag event

* Cleanup

* Resolve conflicts

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2025-06-17 20:50:53 +02:00
Martin Sulikowski 9e914d6166 Release v2.1.0 2025-06-14 23:58:15 +02:00
Michał Wojtas f9bc56bf33 GH-227 Sort combat regions by priority which supports overlapping regions in knockback feature (#227)
* Sort combat regions by priority which supports overlapping regions in knockback feature

* Sort combat regions by priority which supports overlapping regions in knockback feature

* Resolve DMK and jakubek review's

* Correct priority
2025-06-10 16:43:10 +02:00
Michał WojtasandRollczi d6bf4e628c GH-225 Add better way to check restricted command - previous idea only checked root commands (#225)
* Add better way to check restricted command - previous idea only checked root commands

Took 2 minutes

* Code review

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2025-05-24 23:23:19 +02:00
renovate[bot] b87e64090a dependency: Update minor (#215)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-20 01:03:41 +02:00
7ce6d8bd95 GH-221 Add EventPriority to config for player quit event, add whitelist reasons for kick ex. "Server restart" and untagall command (#221)
* - Added Event priority to config.

* - Some modifications

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java

Co-authored-by: Michał Wojtas <80779749+CitralFlo@users.noreply.github.com>

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java

Co-authored-by: Michał Wojtas <80779749+CitralFlo@users.noreply.github.com>

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java

Co-authored-by: Michał Wojtas <80779749+CitralFlo@users.noreply.github.com>

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java

Co-authored-by: Michał Wojtas <80779749+CitralFlo@users.noreply.github.com>

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java

Co-authored-by: Michał Wojtas <80779749+CitralFlo@users.noreply.github.com>

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java

Co-authored-by: Michał Wojtas <80779749+CitralFlo@users.noreply.github.com>

* - Added whitelist reasons for punishments

* - Added whitelist reasons for punishments

* - Fixed description in CombatSettings

* - Made untag command also executeable via console
- Added untagall command to untag all tagged players at once. useful in cases when you want to do something related to events.

* - Better way of untagging all players.

* - Needs testing - head drops #214
  https://github.com/EternalCodeTeam/EternalCombat/issues/214

* - Added bypass checks for combat

* - Changed the way how bypass is being detected. Using function FightTagController#cannotBeTagged

* - Disabling elytra when get tagged when you are moving. Currently there is a bug where you can still can fly if you are already flying when getting tagged.

* Remove event declaration duplications by DynamicListener and EventManager

---------

Co-authored-by: Michał Wojtas <80779749+CitralFlo@users.noreply.github.com>
Co-authored-by: Rollczi <ndejlich5@gmail.com>
2025-05-20 01:02:30 +02:00
Michał Wojtas 2dc6cbf40c Add option to display milliseconds for last second of combat (#219)
Took 6 minutes
2025-05-11 15:22:21 +02:00
Norbert Dejlich 411e78dda0 GH-213 Fix bossbar configuration serialization (#213) 2025-05-11 14:22:20 +02:00
Michał Wojtas 31d41c900f GH-220 Ignore thorns when causing combat log (#220)
* Ignore thorns damage
* Simply if else statement
2025-04-22 22:03:12 +02:00
Norbert Dejlich a8b48b8804 GH-217 Fix NPE in BorderBlockController (#217) 2025-04-22 20:54:30 +02:00
Martin Sulikowski 312d8e08e6 Release v2.0.1 2025-03-26 21:39:13 +01:00
Martin Sulikowski 7f30d79301 Fix placeholder in "adminPlayerNotInCombat" 2025-03-26 21:37:32 +01:00
Martin Sulikowski 05095857ca Fix PlayerTeleportEvent warn message. 2025-03-26 19:51:17 +01:00
Martin Sulikowski d9f98d3aa2 Fix time formatting for combat message. 2025-03-26 19:40:55 +01:00
Martin Sulikowski bae1b805a7 Release v2.0.1-SNAPSHOT. 2025-03-26 19:36:14 +01:00
Martin Sulikowski 0b119a28c9 Release v2.0.0 2025-03-25 22:09:57 +01:00
Martin Sulikowski a235b799e5 Remove check for updates for packetevents. 2025-03-25 00:21:11 +01:00
Martin Sulikowski 9b8b0a8513 Fix PacketEvents relocation and initialization. 2025-03-25 00:19:33 +01:00
Martin Sulikowski 0da9208323 Update eternalcode-commons. 2025-03-22 22:42:13 +01:00
renovate[bot]andMartin Sulikowski 64e366ccd3 dependency: Update patch (#209)
* dependency: Update patch

* Keep worldguard version.

* Keep multification

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Martin Sulikowski <vLuckyyy.biznes@gmail.com>
2025-03-22 22:41:39 +01:00
renovate[bot] 0540e39af2 dependency: Update all-major (#211)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 22:14:58 +01:00
Martin Sulikowski 824313c6ec announcer -> noticeService 2025-03-22 22:13:29 +01:00
Martin Sulikowski 4cd83670a9 GH-186 Make readme great again. (#208)
* Make readme great again.

* Fix table.

* Update README.md

* Update readme banner.
2025-03-22 17:15:18 +01:00
Martin Sulikowski 7f130493f7 Fix java settings. 2025-03-22 02:42:00 +01:00
renovate[bot] 8c5038e3a4 dependency: Update minor (#210)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 02:35:07 +01:00
Martin Sulikowski bd4fd131c5 Update renovate settings and remove whitesource. 2025-03-22 02:28:04 +01:00
Martin Sulikowski f338fc4109 Update ShadowJar and gradle. 2025-03-22 02:27:29 +01:00
Martin Sulikowski da555c65e9 GH-190 Remove unused dependencies. 2025-03-22 02:13:50 +01:00
Martin Sulikowski 726f1aa78c GH-189 EternalCombat 2.0. (#189) 2025-03-22 01:08:32 +01:00
Martin Sulikowski 0539e69236 Remove useless import. 2025-03-17 23:32:16 +01:00
Martin Sulikowski d54bff5516 Remove useless NotificationSerializer, rename NotificationAnnouncer to NoticeService 2025-03-17 23:28:18 +01:00
Martin Sulikowski 5c099d2de4 GH-207 Refactor Config (#207)
* Refactor config v2.

* Move to minimessages.
2025-03-17 23:24:56 +01:00
Norbert DejlichandMichał Wojtas 5042241e94 GH-204 Add Border (#204)
* Implement border

* More important changes

* Add blocks updater

* Implement it

* Include combat limitation for border

* Fix knockback

* Fix region resolving

* Remove useless comments

* Cleanup code. Add caches for chunk snapshot

* Code style changes

* Add config for the border and the knockback, cleanup

* Bump actions

* Minor configuration adjustments

Took 30 minutes

* Follow Jakubek review

* Follow DMK review

---------

Co-authored-by: Michał Wojtas <wojtas.michal90@gmail.com>
2025-03-15 22:28:23 +01:00
Michał Wojtas b63e2584ac GH-205 Add on FightUntagEvent allow player flight again, coz its disable every time (#205)
* Add on FightUntagEvent allow player flight again, coz its disable every time

* Update config, update var

* Check Creative gamemode
2025-02-16 18:44:21 +01:00
a6984e2694 GH-202 V2.0/config refactor (#202)
* Refactor config description.

* fix.

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java

Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>

* Follow review feedback.

* Refactor default messages.

* Remove gradient from combatNotification.

* Improve some messages.

* Add ASCII tag of EternalCombat in config

---------

Co-authored-by: Michał Wojtas <80779749+CitralFlo@users.noreply.github.com>
Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>
Co-authored-by: CitralFlo <wojtas.michal90@gmail.com>
2025-02-08 22:26:57 +01:00
Martin Sulikowski c7f93d9b25 Merge branch 'master' into 2.0 2025-02-02 18:50:42 +01:00
Michał Wojtas c8483e51e9 GH-200 Add option to exclude Creative mode players from combat (#200)
* Add creative setting, reformat name of config value

* Extract method

* Devide method to two ifs. Following @Rollczi's advice

* Devide method to two ifs. Following @Rollczi's advice
2024-10-31 01:59:49 +01:00
Michał WojtasandRollczi dfc825cd98 GH-107 Multification setup (#197)
* Multification setup

* Bump version of multification

* Override adventure API. Bump LiteCommands

* Fix import

* Fix minor issues with notices

* Change BukktiNotice to Notice

* Resolve Rollczi's suggestions

* MAJOR FIX

* uncomment dev.rollczi.litecommands needed for testing

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2024-10-27 14:25:53 +01:00
Martin Sulikowski bd5a6a4351 Remove checkstyle. 2024-10-24 22:05:56 +02:00
Martin 97fd32a7af Release v1.3.3
Signed-off-by: Martin <vluckyyy.biznes@gmail.com>
2024-10-24 21:30:52 +02:00
Martin Sulikowski 9203309def GH-167 start of normalizing developer api (#195)
* GH-167 start of normalizing developer api

* Extract all of services to interfaces

* Add jetbrains annotatiots.

* Fix checkstyle violations

* Follow citralflo feedback.

* Add CancelTagReason

* remove unsed `OTHER` in CancelTagReason.
2024-10-02 18:32:15 +02:00
Martin Sulikowski 381007a735 GH-187 Use LiteCommands 3x (#191)
* GH-187 Use LiteCommands 3x

* GH-187 Separate commands to a different classes

* Update CI to match 2.0 branch.

* Fix.

* Fix command name mistake.

* Follow Mr. Rollczi's Feedback.

* Follow Mr. Rollczi's Feedback v2.

* :trollface:
2024-09-28 14:45:01 +02:00
Martin Sulikowski 534e2cf857 Initialize 2.0.0 Concept. 2024-09-27 21:42:20 +02:00
Michał WojtasandRollczi 20a4e4a2f5 GH-184 Refactor Knockback Mechanic and Add Support for Deny Entry flag (#184)
* Initial commit

* fix spaces

* fix spaces

* Add Kamicjusz's changes. Normalize knockback vector, fix message.

* Set cooldown for messages

* Resolve half of suggestions

* Remove delay and add PVP regions blockade.

* Improve performance of WorldGuardRegionProvider.

* Use lambda

* Resolve @vLuckyyy review

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2024-09-22 21:33:22 +02:00
Martin Sulikowski fe2569918a Release v1.3.2 2024-09-18 19:21:23 +02:00
Michał Wojtas e17effa4cd Fix op in combat (#183) 2024-09-18 15:30:40 +02:00
Michał Wojtas c63df3ab38 GH-181 Add block placing blockade modes and message formatting. (#181)
* Add block placing blockade modes and message formatting.
2024-09-11 19:25:12 +02:00
Martin 24fade593b Release v1.3.1 2024-08-11 17:38:53 +02:00
renovate[bot] 4b12968a7b GH-177 Update all dependencies (#177)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-11 17:25:42 +02:00
Martin 9ad0063aa5 Java 21 support. 2024-08-11 17:15:57 +02:00
Michał Wojtas 7f6c6341f1 GH-175 Fix readme (#175) 2024-07-17 23:10:39 +02:00
Martin 0620f7fb92 Release v1.3.0 2024-07-16 22:11:51 +02:00
Michał WojtasandRollczi d18b2f013a GH-174 Add progress animation to boss bar, fix issue with boss bar not disappearing (#174)
* Add progress animation to boss bar, fix issue with boss bar not disappearing

* Fix spaces

* Temporary solution for empty duration formatting.

* Remove unused cases solved by @Rollczi

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2024-07-16 22:00:08 +02:00
Michał Wojtas 7b89dfdcfc Fix formatting of placeholder to match minecraft health system (#173) 2024-07-13 16:06:55 +02:00
Michał Wojtas 595370f6a4 GH-170 Add PlaceHolderAPI, enchance FightTag class (#170)
* Add PlaceHolderAPI, create basic placeholders. Add tagger as a person who starts tag countdown for player.

* Update README.md with placeholders. Update return value of placeholders

* Resolve Piotrulla's comments.

* Add PlaceHolder to BridgeService. Resolve Rollczi's comments

* Resolve @vLuckyyy review

* Fixes

* Fix infinite loop :>
2024-07-03 16:19:23 +02:00
194 changed files with 7044 additions and 2785 deletions

No files matched your search

+2 -1
View File
@@ -4,4 +4,5 @@
gradlew text eol=lf
*.bat text eol=crlf
*.jar binary
*.jar binary
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
-2
View File
@@ -1,2 +0,0 @@
* @EternalCodeTeam/eternalcodeteam-maintainers
/.github/CODEOWNERS @EternalCodeTeam/eternalcodeteam-maintainers
-128
View File
@@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
https://discord.gg/FQ7jmGBd6c.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
-36
View File
@@ -1,36 +0,0 @@
# Contributing to EternalCombat
We welcome all contributions to the EternalCombat project!
### How to Contribute
1. Fork the repository.
2. Create a new branch for your feature or bug fix.
3. Make your changes, and test them thoroughly.
4. Commit your changes and push them to your fork.
5. Submit a pull request to the main repository.
### Code of Conduct
We expect all contributors to follow our [code of conduct](CODE_OF_CONDUCT.md).
### Reporting Issues
If you find an issue with the plugin, please report it in
the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). Please provide as much information as
possible, including the version of Minecraft and the plugin you are using, as well as any error messages or logs.
### Compatibility
CombatLog has been tested on versions 1.17.1 - 1.19.3, but probably works on most versions of Minecraft. If you find
compatibility issues, please report them in the Issues tab. CombatLog uses java 11+, so you need to have it installed on
your server.
### Submitting a Pull Request
When submitting a pull request, please make sure to follow these guidelines:
- Make sure that your code adheres to the plugin's existing coding style.
- Test your changes thoroughly before submitting the pull request.
- If your pull request is related to an existing issue, please reference the issue in your pull request.
- Thank you for your contributions to the EternalCombat project!
-33
View File
@@ -1,33 +0,0 @@
---
name: Bug report
about: Create a bug report to help us improve EternalCombat <3
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Platform (please complete the following information):**
- Java Version: [e.g. JDK Temurin 17]
- Server software, ex. PaperMC 1.19.3 build #1
**Additional notes**
Add any other context about the problem here.
-20
View File
@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for EternalCombat <3
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
-1
View File
@@ -1 +0,0 @@
**Please describe the changes made by this PR and why they need to be merged:**
-43
View File
@@ -1,43 +0,0 @@
name: Java CI with Gradle
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@v4.0.2
with:
path: ~/.gradle/caches
key: >-
${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*',
'**/gradle-wrapper.properties') }}
restore-keys: '${{ runner.os }}-gradle-'
- name: Make gradlew executable
run: chmod +x gradlew
- name: Build with Gradle
uses: gradle/gradle-build-action@4c39dd82cd5e1ec7c6fa0173bb41b4b6bb3b86ff
with:
arguments: shadowJar
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4.3.3
with:
name: 'Successfully build EternalCombat'
path: eternalcombat-plugin/build/libs/*.jar
-28
View File
@@ -1,28 +0,0 @@
name: Publish EternalCombat API
on:
release:
branches: [ master ]
types: [ published ]
jobs:
publish:
runs-on: ubuntu-latest
environment: deployment
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Publish with Gradle
run: ./gradlew clean build eternalcombat-api:publish
env:
ETERNAL_CODE_MAVEN_USERNAME: ${{ secrets.ETERNAL_CODE_MAVEN_USERNAME }}
ETERNAL_CODE_MAVEN_PASSWORD: ${{ secrets.ETERNAL_CODE_MAVEN_PASSWORD }}
+1
View File
@@ -5,4 +5,5 @@
.settings
bin
build
buildSrc/.kotlin/
run
-14
View File
@@ -1,14 +0,0 @@
{
"scanSettings": {
"baseBranches": []
},
"checkRunSettings": {
"vulnerableCheckRunConclusionLevel": "failure",
"displayMode": "diff",
"useMendCheckNames": true
},
"issueSettings": {
"minSeverityLevel": "LOW",
"issueType": "DEPENDENCY"
}
}
+23 -76
View File
@@ -1,84 +1,31 @@
<div align="center">
# EternalCombat-OG
![](/assets/readme-banner.png)
A [TrueOG Network](https://true-og.net) fork of [EternalCombat](https://github.com/EternalCodeTeam/EternalCombat)
by the EternalCode Team — a combat-tag / combat-logging plugin for Paper.
[![Supports Paper](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/supported/paper_vector.svg)](https://papermc.io)
[![Supports Spigot](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/supported/spigot_vector.svg)](https://spigotmc.org)
## License & attribution
[![Patreon](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/donate/patreon-plural_vector.svg)](https://www.patreon.com/eternalcode)
[![Website](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/documentation/website_vector.svg)](https://eternalcode.pl/)
[![Discord](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/social/discord-plural_vector.svg)](https://discord.gg/FQ7jmGBd6c)
EternalCombat-OG is a derivative work of **EternalCombat** by the **EternalCode Team**, distributed
under the **Apache License 2.0**. The original copyright and license are retained; see the
[`LICENSE`](LICENSE) file. Upstream project: https://github.com/EternalCodeTeam/EternalCombat
[![Gradle](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/built-with/gradle_vector.svg)](https://gradle.org/)
[![Java](https://raw.githubusercontent.com/intergrav/devins-badges/v3/assets/cozy/built-with/java17_vector.svg)](https://www.java.com/)
</div>
## Changes from upstream
### Information
This fork diverges from upstream EternalCombat in the following ways:
EternalCombat has been tested on Minecraft versions 1.17.1 to 1.20.6, but it should work on most other versions. If you
encounter any compatibility issues, please report them in the
[Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). The plugin requires Java 17 or later, so please
make sure it is installed on your server.
- Renamed the project and plugin to `EternalCombat-OG`.
- Fixed combat knockback flinging players across the map or into the sky near safe zones.
- Players in combat can move between the warzone and wilderness, but not into safe areas.
- Render the glass combat border on the true safe/unsafe boundary (e.g. between the warzone and spawn or the market) instead of a region's bounding box, so no wall shows between the warzone and wilderness or on the outer face of a large enclosing region.
- Mounted players are pushed along with their mount instead of being thrown off.
- Closed gaps that let combat-tagged players slip into shops and the market.
- Removed chat gradients.
- Added a command whitelist for "The Herobrine" minigames.
- Dropped Lands support.
- Removed the upstream GitHub Actions, plus build and dependency cleanup.
## Building
Build EternalCombat using:
## Requirements
`./gradlew shadowJar`
### Permissions for EternalCombat
- `eternalcombat.status` - Allows to check if the player is in combat `/combatlog status <player>`
- `eternalcombat.tag` - Allows to create fights between two
players `/combatlog tag <first_player> <optional_second_player>`
- `eternalcombat.untag` - Allows to remove player from fight `/combatlog untag <player>`
- `eternalcombat.reload` - Allows to reload plugin `/combatlog reload`
- `eternalcombat.receiveupdates` - Allows you to receive notifications about the new version of the plugin when
attaching
### Developer API
#### 1. Add repository:
with Gradle:
```kts
maven {
url = uri("https://repo.eternalcode.pl/releases")
}
```
with Maven:
```xml
<repository>
<id>eternalcode-reposilite-releases</id>
<name>EternalCode Repository</name>
<url>https://repo.eternalcode.pl/releases</url>
</repository>
```
#### 2. Add dependency:
with Gradle:
```kts
compileOnly("com.eternalcode:eternalcombat-api:1.2.2")
```
with Maven:
```xml
<dependency>
<groupId>com.eternalcode</groupId>
<artifactId>eternalcombat-api</artifactId>
<version>1.2.2</version>
<scope>provided</scope>
</dependency>
```
### Contributing
We welcome all contributions to the EternalCombat project! Please check out [contributing](.github/CONTRIBUTING.md) for
more information on how to contribute and our [code of conduct](./.github/CODE_OF_CONDUCT.md)
### Reporting Issues
If you find an issue with the plugin, please report it in
the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). Please provide as much information as
possible, including the version of Minecraft and the plugin you are using, as well as any error messages or logs
- Paper (Java 17+ toolchain)
- [PacketEvents](https://modrinth.com/plugin/packetevents) (required)
- WorldGuard (optional, for region-based combat restrictions)
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 98 KiB

+48
View File
@@ -0,0 +1,48 @@
plugins {
java
eclipse
}
tasks.jar {
enabled = false
}
val pluginProject = project(":eternalcombat-plugin")
val pluginJar by tasks.registering(Copy::class) {
dependsOn(":eternalcombat-plugin:shadowJar")
from(pluginProject.provider {
pluginProject.layout.buildDirectory.file("libs/EternalCombat-OG-${pluginProject.version}.jar").get().asFile
})
into(layout.buildDirectory.dir("libs"))
}
tasks.assemble {
dependsOn(pluginJar)
}
allprojects {
group = "com.eternalcode"
version = "2.2.0"
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.eternalcode.pl/releases")
maven("https://storehouse.okaeri.eu/repository/maven-releases/")
maven("https://maven.reposilite.com/releases")
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
maven("https://repo.codemc.org/repository/maven-public/")
maven("https://maven.enginehub.org/repo/")
maven("https://jitpack.io")
}
}
subprojects {
apply(plugin = "java")
apply(plugin = "eclipse")
java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } }
tasks.withType<JavaCompile>().configureEach {
options.encoding = "UTF-8"
options.release.set(17)
}
}
+6 -3
View File
@@ -1,6 +1,9 @@
plugins {
`kotlin-dsl`
eclipse
}
repositories { gradlePluginPortal(); mavenCentral() }
kotlin { jvmToolchain(17) }
repositories {
mavenCentral()
@@ -8,10 +11,10 @@ repositories {
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.23")
implementation("com.github.johnrengelman:shadow:8.1.1")
implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.4.1")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.20")
implementation("net.minecrell:plugin-yml:0.6.0")
implementation("xyz.jpenilla:run-task:2.2.4")
implementation("xyz.jpenilla:run-task:3.0.2")
}
sourceSets {
+23 -20
View File
@@ -2,33 +2,36 @@ object Versions {
const val SPIGOT_API = "1.17.1-R0.1-SNAPSHOT"
const val JUNIT_JUPITER_API = "5.10.2"
const val JUNIT_JUPITER_PARAMS = "5.10.2"
const val JUNIT_JUPITER_ENGINE = "5.10.2"
const val JUNIT_JUPITER_API = "6.0.3"
const val JUNIT_JUPITER_PARAMS = "6.0.3"
const val JUNIT_JUPITER_ENGINE = "6.0.3"
const val ETERNALCODE_COMMONS = "1.1.2"
// TODO: Multification.
const val JETBRAINS_ANNOTATIONS = "26.1.0"
const val ADVENTURE_PLATFORM_BUKKIT = "4.3.2"
const val ADVENTURE_TEXT_MINIMESSAGE = "4.16.0"
const val ETERNALCODE_COMMONS = "1.3.4"
const val MULTIFICATION = "1.2.4"
const val PACKETEVENTS = "2.11.2"
const val LITE_COMMANDS = "2.8.9"
const val OKAERI_CONFIGS_YAML_BUKKIT = "5.0.1"
const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.1"
const val OKAERI_CONFIGS_SERDES_BUKKIT = "5.0.1"
const val ADVENTURE_PLATFORM_BUKKIT = "4.4.1"
const val ADVENTURE_API = "4.26.1"
const val PANDA_UTILITIES = "0.5.2-alpha"
const val GIT_CHECK = "1.0.0"
const val APACHE_COMMONS = "2.16.1"
const val LITE_COMMANDS = "3.10.9"
const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.13"
const val OKAERI_CONFIGS_SERDES_BUKKIT = "5.0.13"
const val CAFFEINE = "3.1.8"
const val XSERIES = "13.6.0"
const val B_STATS_BUKKIT = "3.0.2"
const val WORLD_GUARD_BUKKIT = "7.0.9"
const val CAFFEINE = "3.2.3"
const val WORLD_GUARD_BUKKIT = "7.0.8"
const val PLACEHOLDER_API = "2.11.7"
const val PAPERLIB = "1.0.8"
const val WORLDEDIT = "3ISh7ADm" //cannot use numeric version bc of duplicated version on modrinth
const val WORLDGUARD = "7.0.8"
const val LUCKPERMS = "5.5.17"
}
@@ -1,36 +1,16 @@
plugins {
`java-library`
checkstyle
}
group = "com.eternalcode"
version = "1.2.2"
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
checkstyle {
toolVersion = "10.16.0"
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
maxErrors = 0
maxWarnings = 0
}
// https://github.com/JabRef/jabref/pull/10812/files#diff-49a96e7eea8a94af862798a45174e6ac43eb4f8b4bd40759b5da63ba31ec3ef7R267
configurations.named("checkstyle") {
resolutionStrategy {
capabilitiesResolution {
withCapability("com.google.collections:google-collections") {
select("com.google.guava:guava:33.1.0-jre")
}
}
}
}
version = "2.5.0-SNAPSHOT"
tasks.compileJava {
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
options.encoding = "UTF-8"
options.release = 17
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}
@@ -4,7 +4,7 @@ plugins {
}
group = "com.eternalcode"
version = "1.2.2"
version = "2.5.0-SNAPSHOT"
java {
withSourcesJar()
@@ -3,12 +3,17 @@ plugins {
}
repositories {
mavenCentral()
maven("https://repo.eternalcode.pl/releases")
maven("https://repo.eternalcode.pl/snapshots")
maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") }
maven { url = uri("https://papermc.io/repo/repository/maven-public/") }
maven { url = uri("https://repo.eternalcode.pl/releases") }
maven { url = uri("https://storehouse.okaeri.eu/repository/maven-public/") }
maven { url = uri("https://repo.panda-lang.org/releases") }
maven { url = uri("https://maven.enginehub.org/repo/") }
mavenCentral()
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://storehouse.okaeri.eu/repository/maven-public/")
maven("https://repo.panda-lang.org/releases")
maven("https://maven.enginehub.org/repo/")
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
maven("https://repo.codemc.io/repository/maven-releases/")
maven("https://jitpack.io/")
maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots/")
}
+6 -42
View File
@@ -1,45 +1,9 @@
plugins {
`eternalcombat-java`
`eternalcombat-repositories`
`eternalcombat-publish`
`eternalcombat-java-unit-test`
}
plugins { `java-library` }
java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } }
dependencies {
// Spigot api
compileOnlyApi("org.spigotmc:spigot-api:${Versions.SPIGOT_API}")
// kyori
api("net.kyori:adventure-platform-bukkit:${Versions.ADVENTURE_PLATFORM_BUKKIT}")
api("net.kyori:adventure-text-minimessage:${Versions.ADVENTURE_TEXT_MINIMESSAGE}")
// litecommands
api("dev.rollczi.litecommands:bukkit-adventure:${Versions.LITE_COMMANDS}")
// Okaeri configs
api("eu.okaeri:okaeri-configs-yaml-bukkit:${Versions.OKAERI_CONFIGS_YAML_BUKKIT}")
api("eu.okaeri:okaeri-configs-serdes-commons:${Versions.OKAERI_CONFIGS_SERDES_COMMONS}")
api("eu.okaeri:okaeri-configs-serdes-bukkit:${Versions.OKAERI_CONFIGS_SERDES_BUKKIT}")
// Panda utilities
api("org.panda-lang:panda-utilities:${Versions.PANDA_UTILITIES}")
// GitCheck
api("com.eternalcode:gitcheck:${Versions.GIT_CHECK}")
// commons
api("commons-io:commons-io:${Versions.APACHE_COMMONS}")
// bstats
api("org.bstats:bstats-bukkit:${Versions.B_STATS_BUKKIT}")
// caffeine
api("com.github.ben-manes.caffeine:caffeine:${Versions.CAFFEINE}")
api("com.eternalcode:eternalcode-commons-bukkit:${Versions.ETERNALCODE_COMMONS}")
api("com.eternalcode:eternalcode-commons-adventure:${Versions.ETERNALCODE_COMMONS}")
// worldguard
compileOnly("com.sk89q.worldguard:worldguard-bukkit:${Versions.WORLD_GUARD_BUKKIT}")
compileOnly("io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT")
compileOnly("org.jetbrains:annotations:26.0.2")
}
@@ -1,137 +0,0 @@
package com.eternalcode.combat;
import com.eternalcode.combat.config.ConfigService;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.CauseOfTag;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import dev.rollczi.litecommands.argument.Arg;
import dev.rollczi.litecommands.command.async.Async;
import dev.rollczi.litecommands.command.execute.Execute;
import dev.rollczi.litecommands.command.permission.Permission;
import dev.rollczi.litecommands.command.route.Route;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
import java.time.Duration;
import java.util.UUID;
@Route(name = "combatlog", aliases = "combat")
public class CombatCommand {
private final FightManager fightManager;
private final ConfigService configService;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public CombatCommand(FightManager fightManager, ConfigService configService, NotificationAnnouncer announcer, PluginConfig config) {
this.fightManager = fightManager;
this.configService = configService;
this.announcer = announcer;
this.config = config;
}
@Execute(route = "status", required = 1)
@Permission("eternalcombat.status")
void status(CommandSender sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
PluginConfig.Messages messages = this.config.messages;
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName());
this.announcer.sendMessage(sender, this.fightManager.isInCombat(targetUniqueId)
? formatter.format(messages.admin.playerInCombat)
: formatter.format(messages.admin.playerNotInCombat));
}
@Execute(route = "tag", required = 1)
@Permission("eternalcombat.tag")
void tag(CommandSender sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
Duration time = this.config.settings.combatDuration;
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName());
FightTagEvent event = this.fightManager.tag(targetUniqueId, time, CauseOfTag.COMMAND);
if (event.isCancelled()) {
this.announcer.sendMessage(sender, event.getCancelMessage());
return;
}
String format = formatter.format(this.config.messages.admin.adminTagPlayer);
this.announcer.sendMessage(sender, format);
}
@Execute(route = "tag", required = 2)
@Permission("eternalcombat.tag")
void tagMultiple(CommandSender sender, @Arg Player firstTarget, @Arg Player secondTarget) {
Duration combatTime = this.config.settings.combatDuration;
PluginConfig.Messages messages = this.config.messages;
if (sender.equals(firstTarget) || sender.equals(secondTarget)) {
this.announcer.sendMessage(sender, messages.admin.adminCannotTagSelf);
return;
}
FightTagEvent firstTagEvent = this.fightManager.tag(firstTarget.getUniqueId(), combatTime, CauseOfTag.COMMAND);
FightTagEvent secondTagEvent = this.fightManager.tag(secondTarget.getUniqueId(), combatTime, CauseOfTag.COMMAND);
Formatter formatter = new Formatter()
.register("{FIRST_PLAYER}", firstTarget.getName())
.register("{SECOND_PLAYER}", secondTarget.getName());
String format = formatter.format(messages.admin.adminTagMultiplePlayers);
if (firstTagEvent.isCancelled()) {
this.announcer.sendMessage(sender, firstTagEvent.getCancelMessage());
}
if (secondTagEvent.isCancelled()) {
this.announcer.sendMessage(sender, firstTagEvent.getCancelMessage());
}
if (firstTagEvent.isCancelled() && secondTagEvent.isCancelled()) {
return;
}
this.announcer.sendMessage(sender, format);
}
@Async
@Execute(route = "reload")
@Permission("eternalcombat.reload")
void execute(CommandSender player) {
this.configService.reload();
this.announcer.sendMessage(player, this.config.messages.admin.reload);
}
@Execute(route = "untag", required = 1)
@Permission("eternalcombat.untag")
void untag(Player sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
if (!this.fightManager.isInCombat(targetUniqueId)) {
this.announcer.sendMessage(sender, this.config.messages.admin.adminPlayerNotInCombat);
return;
}
FightUntagEvent event = this.fightManager.untag(targetUniqueId, CauseOfUnTag.COMMAND);
if (event.isCancelled()) {
return;
}
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName());
String format = formatter.format(this.config.messages.admin.adminUntagPlayer);
this.announcer.sendMessage(sender, format);
}
}
@@ -1,13 +1,12 @@
package com.eternalcode.combat;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.drop.DropKeepInventoryManager;
import com.eternalcode.combat.drop.DropManager;
import com.eternalcode.combat.fight.drop.DropKeepInventoryService;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.drop.DropService;
import com.eternalcode.combat.fight.effect.FightEffectService;
import com.eternalcode.combat.fight.pearl.FightPearlManager;
import com.eternalcode.combat.fight.tagout.FightTagOutService;
import com.eternalcode.combat.fight.pearl.FightPearlService;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.combat.fight.tagout.FightTagOutService;
public interface EternalCombatApi {
@@ -15,16 +14,14 @@ public interface EternalCombatApi {
RegionProvider getRegionProvider();
FightPearlManager getFightPearlManager();
FightPearlService getFightPearlService();
FightTagOutService getFightTagOutService();
FightEffectService getFightEffectService();
DropManager getDropManager();
DropService getDropService();
DropKeepInventoryManager getDropKeepInventoryManager();
PluginConfig getPluginConfig();
DropKeepInventoryService getDropKeepInventoryService();
}
@@ -4,6 +4,10 @@ public final class EternalCombatProvider {
private static EternalCombatApi ETERNAL_COMBAT_API;
static boolean isInitialized() {
return ETERNAL_COMBAT_API != null;
}
public static EternalCombatApi provide() {
if (ETERNAL_COMBAT_API == null) {
throw new IllegalStateException("EternalCombatApi has not been initialized yet!");
@@ -1,49 +0,0 @@
package com.eternalcode.combat.bridge;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.region.DefaultRegionProvider;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.combat.region.WorldGuardRegionProvider;
import org.bukkit.plugin.PluginManager;
import java.util.logging.Logger;
public class BridgeService {
private final PluginConfig pluginConfig;
private final PluginManager pluginManager;
private final Logger logger;
private RegionProvider regionProvider;
public BridgeService(PluginConfig pluginConfig, PluginManager pluginManager, Logger logger) {
this.pluginConfig = pluginConfig;
this.pluginManager = pluginManager;
this.logger = logger;
}
public void init() {
this.initialize("WorldGuard",
() -> this.regionProvider = new WorldGuardRegionProvider(this.pluginConfig.settings.blockedRegions),
() -> {
this.regionProvider = new DefaultRegionProvider(this.pluginConfig.settings.blockedRegionRadius);
this.logger.warning("WorldGuard is not installed, set to default region provider.");
});
}
private void initialize(String pluginName, BridgeInitializer initializer, Runnable runnable) {
if (this.pluginManager.isPluginEnabled(pluginName)) {
initializer.initialize();
this.logger.info("Successfully initialized " + pluginName + " bridge.");
}
else {
runnable.run();
}
}
public RegionProvider getRegionProvider() {
return this.regionProvider;
}
}
@@ -1,35 +0,0 @@
package com.eternalcode.combat.command;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import dev.rollczi.litecommands.command.LiteInvocation;
import dev.rollczi.litecommands.handle.InvalidUsageHandler;
import dev.rollczi.litecommands.schematic.Schematic;
import org.bukkit.command.CommandSender;
import panda.utilities.text.Formatter;
import java.util.List;
public class InvalidUsage implements InvalidUsageHandler<CommandSender> {
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public InvalidUsage(PluginConfig config, NotificationAnnouncer announcer) {
this.config = config;
this.announcer = announcer;
}
@Override
public void handle(CommandSender commandSender, LiteInvocation invocation, Schematic scheme) {
List<String> schematics = scheme.getSchematics();
for (String schematic : schematics) {
Formatter formatter = new Formatter()
.register("{USAGE}", schematic);
this.announcer.sendMessage(commandSender, formatter.format(this.config.messages.invalidCommandUsage));
}
}
}
@@ -1,34 +0,0 @@
package com.eternalcode.combat.command;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import dev.rollczi.litecommands.command.LiteInvocation;
import dev.rollczi.litecommands.command.permission.RequiredPermissions;
import dev.rollczi.litecommands.handle.PermissionHandler;
import org.bukkit.command.CommandSender;
import panda.utilities.text.Formatter;
import panda.utilities.text.Joiner;
public class PermissionMessage implements PermissionHandler<CommandSender> {
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public PermissionMessage(PluginConfig config, NotificationAnnouncer announcer) {
this.config = config;
this.announcer = announcer;
}
@Override
public void handle(CommandSender commandSender, LiteInvocation invocation, RequiredPermissions requiredPermissions) {
String value = Joiner.on(", ")
.join(requiredPermissions.getPermissions())
.toString();
Formatter formatter = new Formatter()
.register("{PERMISSION}", value);
this.announcer.sendMessage(commandSender, formatter.format(this.config.messages.noPermission));
}
}
@@ -1,41 +0,0 @@
package com.eternalcode.combat.config;
import com.eternalcode.combat.notification.serializer.NotificationSerializer;
import eu.okaeri.configs.ConfigManager;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.serdes.commons.SerdesCommons;
import eu.okaeri.configs.yaml.bukkit.YamlBukkitConfigurer;
import eu.okaeri.configs.yaml.bukkit.serdes.SerdesBukkit;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
public class ConfigService {
private final Set<OkaeriConfig> configs = new HashSet<>();
public <T extends OkaeriConfig> T create(Class<T> config, File file) {
T configFile = ConfigManager.create(config);
configFile.withConfigurer(new YamlBukkitConfigurer(), new SerdesCommons(), new SerdesBukkit());
configFile.withSerdesPack(registry -> {
registry.register(new NotificationSerializer());
});
configFile.withBindFile(file);
configFile.withRemoveOrphans(true);
configFile.saveDefaults();
configFile.load(true);
this.configs.add(configFile);
return configFile;
}
public void reload() {
for (OkaeriConfig config : this.configs) {
config.load();
}
}
}
@@ -1,230 +0,0 @@
package com.eternalcode.combat.config.implementation;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.drop.DropSettings;
import com.eternalcode.combat.fight.effect.FightEffectSettings;
import com.eternalcode.combat.fight.pearl.FightPearlSettings;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.implementation.ActionBarNotification;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.event.entity.EntityDamageEvent;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
public class PluginConfig extends OkaeriConfig {
@Comment("# Do you want to change the plugin settings?")
public Settings settings = new Settings();
@Comment({" ", "# Ender pearl settings"})
public FightPearlSettings pearl = new FightPearlSettings();
@Comment({" ", "# Custom effects settings"})
public FightEffectSettings effect = new FightEffectSettings();
@Comment({" ", "# Set a custom way for a player's items to drop on death (if in combat)"})
public DropSettings dropSettings = new DropSettings();
public static class Settings extends OkaeriConfig {
@Comment("# Whether the player should receive information about new plugin updates upon joining the server")
public boolean shouldReceivePluginUpdates = true;
@Comment("# The duration of combat in seconds")
public Duration combatDuration = Duration.ofSeconds(20);
@Comment("# List of worlds to ignore")
public List<String> worldsToIgnore = List.of(
"your_world"
);
@Comment("# List of regions to block")
public List<String> blockedRegions = Collections.singletonList("your_region");
@Comment("# Set the knock multiplier for the blocked region")
public double blockedRegionKnockMultiplier = 1.2;
@Comment("# Set the radius of the blocked region if you aren't using WorldGuard basen on default spawn region!")
public int blockedRegionRadius = 10;
@Comment("# Release attacker after victim dies?")
public boolean shouldReleaseAttacker = true;
@Comment("# If you want to exclude admins from combat, set this to true")
public boolean excludeAdminFromCombat = false;
@Comment("# Command blocking mode, available modes: WHITELIST, BLACKLIST")
public WhitelistBlacklistMode commandBlockingMode = WhitelistBlacklistMode.BLACKLIST;
@Comment({
"# List of commands based on the mode above",
"# Based on BLACKLIST mode, all commands in the list are blocked, and all others are allowed",
"# Based on WHITELIST mode, all commands in the list are allowed, and all others are blocked",
})
public List<String> blockedCommands = List.of(
"gamemode",
"spawn",
"tp"
);
@Comment("# Block the use of elytra?")
public boolean shouldPreventElytraUsage = true;
@Comment("# Block flying? (flying players will fall to the ground)")
public boolean shouldPreventFlying = true;
@Comment("# Disable the use of elytra on damage?")
public boolean shouldElytraDisableOnDamage = true;
@Comment("# Block the opening of inventory?")
public boolean shouldPreventInventoryOpening = true;
@Comment("# Whether to block the placement of blocks?")
public boolean shouldPreventBlockPlacing = true;
@Comment("# The minimum Y level at which block placing is blocked")
public int minBlockPlacingLevel = 40;
@Comment({
"# Disable placing specific blocks?",
"# If you want to block all blocks, enable shouldPreventBlockPlacing and make this list empty",
"# If you want to disable placing only specific blocks, enable shouldPreventBlockPlacing and add blocks to this list above",
"# If you want to disable this feature completely, disable shouldPreventBlockPlacing option above",
})
public List<Material> specificBlocksToPreventPlacing = List.of();
@Comment("# Do You want to enable combat log for non-player causes of damage? - Set to false to disable")
public boolean shouldEnableDamageCauses = false;
@Comment("# Select the mode for damage causes, available modes: WHITELIST, BLACKLIST")
public WhitelistBlacklistMode damageCausesMode = WhitelistBlacklistMode.WHITELIST;
@Comment({
"# After selecting the mode above, select the causes of damage to be logged",
"# You can find a list of all causes here: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html",
"# While using Whitelist mode the player will get a combat log after the damage from the list below",
"# While using Blacklist mode the player will get a combat log after any damage non-listed below",
})
public List<EntityDamageEvent.DamageCause> damageCausesToLog = List.of(
EntityDamageEvent.DamageCause.LAVA,
EntityDamageEvent.DamageCause.CONTACT,
EntityDamageEvent.DamageCause.FIRE,
EntityDamageEvent.DamageCause.FIRE_TICK
);
@Comment({
"# After what type of projectile entity should not tag the player as fighter?",
"# You can find a list of all entity types here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html"
})
public List<EntityType> disabledProjectileEntities = List.of(
EntityType.ENDER_PEARL
);
}
@Comment({" ", "# Do you want to change the plugin messages?"})
public Messages messages = new Messages();
public static class Messages extends OkaeriConfig {
@Comment("# Do you want to change the admin messages?")
public AdminMessages admin = new AdminMessages();
@Comment({
" ",
"# Combat log notification",
"# You can use {TIME} variable to display the time left in combat",
"# Notification types: CHAT, ACTION_BAR, TITLE, SUB_TITLE, BOSS_BAR",
" ",
"# BossBar progress: This is the value of the progress bar. Set it to -1.0 to show the remaining combat time",
"# BossBar colors: https://javadoc.io/static/net.kyori/adventure-api/4.14.0/net/kyori/adventure/bossbar/BossBar.Color.html",
"# BossBar overlays: https://javadoc.io/static/net.kyori/adventure-api/4.14.0/net/kyori/adventure/bossbar/BossBar.Overlay.html"
})
public Notification combatNotification = new ActionBarNotification("&dCombat ends in: &f{TIME}");
@Comment("# Message sent when the player does not have permission to perform a command")
public String noPermission = "&cYou don't have permission to perform this command!";
@Comment("# Message sent when the specified player could not be found")
public String playerNotFound = "&cThe specified player could not be found!";
@Comment("# Message sent when the player enters combat")
public String playerTagged = "&cYou are in combat, do not leave the server!";
@Comment("# Message sent when the player leaves combat")
public String playerUntagged = "&aYou are no longer in combat! You can safely leave the server.";
@Comment("# This is broadcast when the player is in combat and logs out")
public String playerLoggedOutDuringCombat = "&c{PLAYER} logged off during the fight!";
@Comment({
"# Message sent when the player is in combat and tries to use a disabled command",
"# you can configure the list of disabled commands in the blockedCommands section of the config.yml file"
})
public String commandDisabledDuringCombat = "&cUsing this command during combat is prohibited!";
@Comment("# Message sent when player tries to use a command with invalid arguments")
public String invalidCommandUsage = "&7Correct usage: &e{USAGE}";
@Comment("# Message sent when player tries to open inventory, but the inventory open is blocked")
public String inventoryBlockedDuringCombat = "&cYou cannot open this inventory during combat!";
@Comment("# Message sent when player tries to place a block, but the block place is blocked")
public String blockPlacingBlockedDuringCombat = "&cYou cannot place above 40Y coordinate during combat!";
@Comment("# Message sent when player tries to enter a region")
public String cantEnterOnRegion = "&cYou can't enter on this region during combat!";
public static class AdminMessages extends OkaeriConfig {
@Comment("# Message sent when the configuration is reloaded")
public String reload = "&aConfiguration has been successfully reloaded!";
@Comment("# Message sent when console tries to use a command that is only for players")
public String onlyForPlayers = "&cThis command is only available to players!";
@Comment("# Message sent to admin when they tag a player")
public String adminTagPlayer = "&7You have tagged &e{PLAYER}";
@Comment("# Message sent when a player is tagged by an admin")
public String adminTagMultiplePlayers = "&7You have tagged &e{FIRST_PLAYER}&7 and &e{SECOND_PLAYER}&7.";
@Comment("# Message sent to admin when they remove a player from combat")
public String adminUntagPlayer = "&7You have removed &e{PLAYER}&7 from the fight.";
@Comment("# Message sent when the player is not in combat")
public String adminPlayerNotInCombat = "&cThis player is not in combat!";
@Comment("# Message sent when the player is in combat")
public String playerInCombat = "&c{PLAYER} is currently in combat!";
@Comment("# Message sent when a player is not in combat")
public String playerNotInCombat = "&a{PLAYER} is not currently in combat.";
@Comment("# Message sent when an admin tries to tag themselves")
public String adminCannotTagSelf = "&cYou cannot tag yourself!";
@Comment("# Message sent when an admin disables the ability to get tagged for some time")
public String adminTagOutSelf = "&7Successfully disabled tag for Yourself! You will be taggable after &e{TIME} ";
@Comment("# Message sent when an admin disables the ability to get tagged for some time for other player")
public String adminTagOut = "&7Successfully disabled tag for &e{PLAYER}! They will be taggable after &e{TIME} ";
@Comment("# Message sent to the player whom the ability to get tagged for some time has been disabled")
public String playerTagOut = "&7You will be taggable in &e{TIME} !";
@Comment("# Message sent when an admin reenables the ability to get tagged for the player")
public String adminTagOutOff = "&7Successfully enabled tag for &e{PLAYER}!";
@Comment("# Message sent to the player whom the ability to get tagged has been reenabled")
public String playerTagOutOff = "&7You are now taggable!";
@Comment("# Message sent when player cannot be tagged because they have enabled tag-out")
public String adminTagOutCanceled = "&cCannot tag this player due to tag-out!";
}
}
}
@@ -1,79 +0,0 @@
package com.eternalcode.combat.drop;
import com.eternalcode.combat.fight.FightManager;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import java.util.List;
import java.util.UUID;
public class DropController implements Listener {
private final DropManager dropManager;
private final DropKeepInventoryManager keepInventoryManager;
private final DropSettings dropSettings;
private final FightManager fightManager;
public DropController(DropManager dropManager, DropKeepInventoryManager keepInventoryManager, DropSettings dropSettings, FightManager fightManager) {
this.dropManager = dropManager;
this.keepInventoryManager = keepInventoryManager;
this.dropSettings = dropSettings;
this.fightManager = fightManager;
}
@EventHandler(priority = EventPriority.LOW)
void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
DropType dropType = this.dropSettings.dropType;
if (dropType == DropType.UNCHANGED || !this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
List<ItemStack> drops = event.getDrops();
Drop drop = Drop.builder()
.player(player)
.killer(player.getKiller())
.droppedItems(drops)
.droppedExp(player.getTotalExperience())
.build();
DropResult result = this.dropManager.modify(dropType, drop);
if (result == null) {
return;
}
drops.clear();
drops.addAll(result.droppedItems());
this.keepInventoryManager.addItems(player.getUniqueId(), result.removedItems());
if (this.dropSettings.affectExperience) {
event.setDroppedExp(drop.getDroppedExp());
}
}
@EventHandler
void onPlayerRespawn(PlayerRespawnEvent event) {
Player player = event.getPlayer();
UUID playerUniqueId = player.getUniqueId();
if (this.keepInventoryManager.hasItems(playerUniqueId)) {
PlayerInventory playerInventory = player.getInventory();
ItemStack[] itemsToGive = this.keepInventoryManager.nextItems(playerUniqueId)
.toArray(new ItemStack[0]);
playerInventory.addItem(itemsToGive);
}
}
}
@@ -1,23 +0,0 @@
package com.eternalcode.combat.drop;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
public class DropSettings extends OkaeriConfig {
@Comment({
"# UNCHANGED - The default way of item drop defined by the engine",
"# PERCENT - Drops a fixed percentage of items",
"# 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)"
})
public DropType dropType = DropType.UNCHANGED;
@Comment("# What percentage of items should drop from the player? (Only if Drop Type is set to PERCENT)")
public int dropItemPercent = 100;
@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)")
public int playersHealthPercentClamp = 20;
@Comment("# Does the drop modification affect the experience drop?")
public boolean affectExperience = false;
}
@@ -1,20 +0,0 @@
package com.eternalcode.combat.event;
import org.bukkit.Server;
import org.bukkit.event.Event;
public class EventCaller {
private final Server server;
public EventCaller(Server server) {
this.server = server;
}
public <T extends Event> T publishEvent(T event) {
this.server.getPluginManager().callEvent(event);
return event;
}
}
@@ -4,69 +4,31 @@ import com.eternalcode.combat.fight.event.CauseOfTag;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import com.eternalcode.combat.event.EventCaller;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
public class FightManager {
public interface FightManager {
private final Map<UUID, FightTag> fights = new ConcurrentHashMap<>();
private final EventCaller eventCaller;
boolean isInCombat(UUID player);
public FightManager(EventCaller eventCaller) {
this.eventCaller = eventCaller;
}
FightTag getTag(UUID target);
public boolean isInCombat(UUID player) {
if (!this.fights.containsKey(player)) {
return false;
}
Collection<FightTag> getFights();
FightTag fightTag = this.fights.get(player);
// Players who tagged the given player during their current combat session.
@ApiStatus.Experimental
Set<UUID> getOpponents(UUID player);
return !fightTag.isExpired();
}
@ApiStatus.Experimental
FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger);
public FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag) {
FightUntagEvent event = this.eventCaller.publishEvent(new FightUntagEvent(player, causeOfUnTag));
if (event.isCancelled()) {
return event;
}
FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag);
this.fights.remove(player);
return event;
}
FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag);
public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag) {
FightTagEvent event = this.eventCaller.publishEvent(new FightTagEvent(target, causeOfTag));
if (event.isCancelled()) {
return event;
}
Instant now = Instant.now();
Instant endOfCombatLog = now.plus(delay);
FightTag fightTag = new FightTag(target, endOfCombatLog);
this.fights.put(target, fightTag);
return event;
}
public Collection<FightTag> getFights() {
return Collections.unmodifiableCollection(this.fights.values());
}
public FightTag getTag(UUID target) {
return this.fights.get(target);
}
public void untagAll() {
this.fights.clear();
}
void untagAll();
}
@@ -3,38 +3,20 @@ package com.eternalcode.combat.fight;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
public class FightTag {
public interface FightTag {
private final UUID taggedPlayer;
private final Instant endOfCombatLog;
@Nullable
@ApiStatus.Experimental
UUID getTagger();
FightTag(UUID personToAddCombat, Instant endOfCombatLog) {
this.taggedPlayer = personToAddCombat;
this.endOfCombatLog = endOfCombatLog;
Duration getRemainingDuration();
}
boolean isExpired();
public UUID getTaggedPlayer() {
return this.taggedPlayer;
}
public Instant getEndOfCombatLog() {
return this.endOfCombatLog;
}
public boolean isExpired() {
return Instant.now().isAfter(this.endOfCombatLog);
}
public Duration getRemainingDuration() {
Duration between = Duration.between(Instant.now(), this.endOfCombatLog);
if (between.isNegative()) {
return Duration.ZERO;
}
return between;
}
Instant getEndOfCombatLog();
UUID getTaggedPlayer();
}
@@ -1,67 +0,0 @@
package com.eternalcode.combat.fight;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.bossbar.FightBossBarService;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.notification.implementation.BossBarNotification;
import com.eternalcode.combat.util.DurationUtil;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
import java.time.Duration;
import java.util.UUID;
public class FightTask implements Runnable {
private final Server server;
private final PluginConfig config;
private final FightManager fightManager;
private final FightBossBarService bossBarService;
private final NotificationAnnouncer announcer;
public FightTask(Server server, PluginConfig config, FightManager fightManager, FightBossBarService bossBarService, NotificationAnnouncer announcer) {
this.server = server;
this.config = config;
this.fightManager = fightManager;
this.bossBarService = bossBarService;
this.announcer = announcer;
}
@Override
public void run() {
for (FightTag fightTag : this.fightManager.getFights()) {
Player player = this.server.getPlayer(fightTag.getTaggedPlayer());
if (player == null) {
continue;
}
UUID playerUniqueId = player.getUniqueId();
if (fightTag.isExpired()) {
this.fightManager.untag(playerUniqueId, CauseOfUnTag.TIME_EXPIRED);
return;
}
Duration remaining = fightTag.getRemainingDuration();
Formatter formatter = new Formatter()
.register("{TIME}", DurationUtil.format(remaining));
Notification combatNotification = this.config.messages.combatNotification;
this.sendFightNotification(player, fightTag, combatNotification, formatter);
}
}
private void sendFightNotification(Player player, FightTag fightTag, Notification notification, Formatter formatter) {
if (notification instanceof BossBarNotification bossBarNotification) {
this.bossBarService.send(player, fightTag, bossBarNotification, formatter);
return;
}
this.announcer.send(player, notification, formatter);
}
}
@@ -1,9 +0,0 @@
package com.eternalcode.combat.fight.bossbar;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar;
import java.time.Duration;
public record FightBossBar(Audience audience, BossBar bossBar, float progress, Duration combatDuration) {
}
@@ -1,27 +0,0 @@
package com.eternalcode.combat.fight.bossbar;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
class FightBossBarRegistry {
private final Map<UUID, FightBossBar> fightBossBars = new ConcurrentHashMap<>();
void add(UUID uuid, FightBossBar fightBossBar) {
this.fightBossBars.put(uuid, fightBossBar);
}
void remove(UUID uuid) {
this.fightBossBars.remove(uuid);
}
boolean hasFightBossBar(UUID uuid) {
return this.fightBossBars.containsKey(uuid);
}
Optional<FightBossBar> getFightBossBar(UUID uuid) {
return Optional.ofNullable(this.fightBossBars.get(uuid));
}
}
@@ -1,119 +0,0 @@
package com.eternalcode.combat.fight.bossbar;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightTag;
import com.eternalcode.combat.notification.implementation.BossBarNotification;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
public class FightBossBarService {
private final PluginConfig pluginConfig;
private final FightBossBarRegistry bossBarRegistry = new FightBossBarRegistry();
private final AudienceProvider audienceProvider;
private final MiniMessage miniMessage;
public FightBossBarService(PluginConfig pluginConfig, AudienceProvider audienceProvider, MiniMessage miniMessage) {
this.pluginConfig = pluginConfig;
this.audienceProvider = audienceProvider;
this.miniMessage = miniMessage;
}
public void hide(FightTag fightTag, FightBossBar fightBossBar) {
Audience audience = fightBossBar.audience();
BossBar bossBar = fightBossBar.bossBar();
UUID taggedPlayer = fightTag.getTaggedPlayer();
audience.hideBossBar(bossBar);
this.bossBarRegistry.remove(taggedPlayer);
}
public void hide(UUID playerUuid) {
Optional<FightBossBar> bossBar = this.bossBarRegistry.getFightBossBar(playerUuid);
if (bossBar.isPresent()) {
FightBossBar fightBossBar = bossBar.get();
Audience audience = fightBossBar.audience();
BossBar bar = fightBossBar.bossBar();
audience.hideBossBar(bar);
this.bossBarRegistry.remove(playerUuid);
}
}
public void send(Player player, FightTag fightTag, BossBarNotification bossBarNotification, Formatter formatter) {
UUID playerUniqueId = player.getUniqueId();
FightBossBar fightBossBar = this.bossBarRegistry.getFightBossBar(playerUniqueId)
.orElseGet(() -> this.create(player, bossBarNotification, formatter));
if (!this.bossBarRegistry.hasFightBossBar(playerUniqueId)) {
this.show(player, fightBossBar);
return;
}
String message = formatter.format(bossBarNotification.message());
this.update(fightTag, fightBossBar, message);
}
private void update(FightTag fightTag, FightBossBar fightBossBar, String message) {
if (fightTag.isExpired()) {
this.hide(fightTag, fightBossBar);
return;
}
BossBar bossBar = fightBossBar.bossBar();
float fightBossBarProgress = fightBossBar.progress();
if (fightBossBarProgress < 0.0F) {
Instant now = Instant.now();
Instant endOfCombatLog = fightTag.getEndOfCombatLog();
Duration between = Duration.between(now, endOfCombatLog);
Duration combatDuration = fightBossBar.combatDuration();
float difference = (float) between.toMillis() / combatDuration.toMillis();
float progress = Math.max(0.0F, Math.min(1.0F, difference));
bossBar.progress(progress);
}
Component name = this.miniMessage.deserialize(message);
bossBar.name(name);
}
private void show(Player player, FightBossBar fightBossBar) {
UUID playerUniqueId = player.getUniqueId();
Audience audience = this.audienceProvider.player(playerUniqueId);
BossBar bossBar = fightBossBar.bossBar();
audience.showBossBar(bossBar);
this.bossBarRegistry.add(playerUniqueId, fightBossBar);
}
private FightBossBar create(Player player, BossBarNotification bossBarNotification, Formatter formatter) {
Audience audience = this.audienceProvider.player(player.getUniqueId());
Component name = this.miniMessage.deserialize(formatter.format(bossBarNotification.message()));
BossBar bossBar = bossBarNotification.create(name);
float progress = bossBarNotification.progress();
Duration combatDuration = this.pluginConfig.settings.combatDuration;
return new FightBossBar(audience, bossBar, progress, combatDuration);
}
}
@@ -1,163 +0,0 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityToggleGlideEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerToggleFlightEvent;
import java.util.List;
import java.util.UUID;
public class FightActionBlockerController implements Listener {
private final FightManager fightManager;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public FightActionBlockerController(FightManager fightManager, NotificationAnnouncer announcer, PluginConfig config) {
this.fightManager = fightManager;
this.announcer = announcer;
this.config = config;
}
@EventHandler
void onPlace(BlockPlaceEvent event) {
if (!this.config.settings.shouldPreventBlockPlacing) {
return;
}
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
Block block = event.getBlock();
int level = block.getY();
List<Material> specificBlocksToPreventPlacing = this.config.settings.specificBlocksToPreventPlacing;
boolean isBlockPlaceLevel = level > this.config.settings.minBlockPlacingLevel;
if (isBlockPlaceLevel && specificBlocksToPreventPlacing.isEmpty()) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.blockPlacingBlockedDuringCombat);
}
Material blockMaterial = block.getType();
boolean isBlockInDisabledList = specificBlocksToPreventPlacing.contains(blockMaterial);
if (isBlockPlaceLevel && isBlockInDisabledList) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.blockPlacingBlockedDuringCombat);
}
}
@EventHandler
void onToggleGlide(EntityToggleGlideEvent event) {
if (!this.config.settings.shouldPreventElytraUsage) {
return;
}
if (!(event.getEntity() instanceof Player player)) {
return;
}
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
if (event.isGliding()) {
event.setCancelled(true);
}
}
@EventHandler
void onFly(PlayerToggleFlightEvent event) {
if (!this.config.settings.shouldPreventFlying) {
return;
}
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
if (event.isFlying()) {
player.setAllowFlight(false);
event.setCancelled(true);
}
}
@EventHandler
void onDamage(EntityDamageEvent event) {
if (!this.config.settings.shouldElytraDisableOnDamage) {
return;
}
if (!(event.getEntity() instanceof Player player)) {
return;
}
UUID uniqueId = player.getUniqueId();
if (this.fightManager.isInCombat(uniqueId) && player.isGliding()) {
player.setGliding(false);
}
}
@EventHandler
void onOpenInventory(InventoryOpenEvent event) {
if (!this.config.settings.shouldPreventInventoryOpening) {
return;
}
Player player = (Player) event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.inventoryBlockedDuringCombat);
}
@EventHandler
void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
Player player = event.getPlayer();
UUID playerUniqueId = player.getUniqueId();
if (!this.fightManager.isInCombat(playerUniqueId)) {
return;
}
String command = event.getMessage().split(" ")[0].substring(1).toLowerCase();
boolean isMatchCommand = this.config.settings.blockedCommands.stream()
.anyMatch(command::startsWith);
WhitelistBlacklistMode mode = this.config.settings.commandBlockingMode;
boolean shouldCancel = mode.shouldBlock(isMatchCommand);
if (shouldCancel) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.config.messages.commandDisabledDuringCombat);
}
}
}
@@ -1,58 +0,0 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.logout.LogoutService;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
public class FightUnTagController implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
private final LogoutService logoutService;
public FightUnTagController(FightManager fightManager, PluginConfig config, LogoutService logoutService) {
this.fightManager = fightManager;
this.config = config;
this.logoutService = logoutService;
}
@EventHandler
void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
Player killer = player.getKiller();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
CauseOfUnTag cause = this.getDeathCause(player, killer);
this.fightManager.untag(player.getUniqueId(), cause);
if (killer != null && this.config.settings.shouldReleaseAttacker) {
this.fightManager.untag(killer.getUniqueId(), CauseOfUnTag.ATTACKER_RELEASE);
}
}
private CauseOfUnTag getDeathCause(Player player, Player killer) {
if (this.logoutService.hasLoggedOut(player.getUniqueId())) {
return CauseOfUnTag.LOGOUT;
}
if (killer == null) {
return CauseOfUnTag.DEATH;
}
if (this.fightManager.isInCombat(killer.getUniqueId())) {
return CauseOfUnTag.DEATH_BY_PLAYER;
}
return CauseOfUnTag.DEATH;
}
}
@@ -1,4 +1,4 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
@@ -0,0 +1,16 @@
package com.eternalcode.combat.fight.drop;
import java.util.List;
import java.util.UUID;
import org.bukkit.inventory.ItemStack;
public interface DropKeepInventoryService {
List<ItemStack> nextItems(UUID uuid);
boolean hasItems(UUID uuid);
void addItems(UUID uuid, List<ItemStack> item);
void addItem(UUID uuid, ItemStack item);
}
@@ -1,4 +1,4 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
public interface DropModifier {
@@ -1,4 +1,4 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
import org.bukkit.inventory.ItemStack;
@@ -0,0 +1,8 @@
package com.eternalcode.combat.fight.drop;
public interface DropService {
DropResult modify(DropType dropType, Drop drop);
void registerModifier(DropModifier dropModifier);
}
@@ -1,4 +1,4 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
public enum DropType {
@@ -1,75 +1,25 @@
package com.eternalcode.combat.fight.effect;
import java.util.List;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.List;
import java.util.ArrayList;
/**
* Manages custom potion effects on players during combat.
* Active effects before combat are stored and restored after combat ends.
*/
public interface FightEffectService {
public class FightEffectService {
void removeCustomEffect(Player player, PotionEffectType type, Integer amplifier);
private final Map<UUID, List<PotionEffect>> activeEffects = new HashMap<>();
private static final int INFINITE_DURATION = -1;
void applyCustomEffect(Player player, PotionEffectType type, Integer amplifier);
public void storeActiveEffect(Player player, PotionEffect effect) {
List<PotionEffect> effects = this.activeEffects.computeIfAbsent(player.getUniqueId(), k -> new ArrayList<>());
List<PotionEffect> getCurrentEffects(Player player);
effects.add(effect);
}
void clearStoredEffects(Player player);
public void restoreActiveEffects(Player player) {
List<PotionEffect> currentEffects = this.getCurrentEffects(player);
void restoreActiveEffects(Player player);
for (PotionEffect effect : currentEffects) {
player.addPotionEffect(effect);
}
this.clearStoredEffects(player);
}
public void clearStoredEffects(Player player) {
this.activeEffects.remove(player.getUniqueId());
}
public List<PotionEffect> getCurrentEffects(Player player) {
return this.activeEffects.getOrDefault(player.getUniqueId(), new ArrayList<>());
}
public void applyCustomEffect(Player player, PotionEffectType type, Integer amplifier) {
PotionEffect activeEffect = player.getPotionEffect(type);
if (activeEffect == null) {
player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier));
return;
}
if (activeEffect.getAmplifier() > amplifier) {
return;
}
if (activeEffect.getDuration() == -1) {
return;
}
this.storeActiveEffect(player, activeEffect);
player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier));
}
public void removeCustomEffect(Player player, PotionEffectType type, Integer amplifier) {
PotionEffect activeEffect = player.getPotionEffect(type);
if (activeEffect == null) {
return;
}
if (activeEffect.getAmplifier() != amplifier) {
return;
}
player.removePotionEffect(type);
}
void storeActiveEffect(Player player, PotionEffect effect);
}
@@ -0,0 +1,10 @@
package com.eternalcode.combat.fight.event;
public enum CancelTagReason {
TAGOUT,
PERMISSION_BYPASS,
ADMIN,
CREATIVE_MODE,
}
@@ -1,8 +1,30 @@
package com.eternalcode.combat.fight.event;
public enum CauseOfTag {
/**
* The player was tagged in combat as a result of an attack by another player.
*/
PLAYER,
/**
* The player was tagged in combat due to an interaction with a non-player entity
* (e.g., mobs or environmental damage).
*/
NON_PLAYER,
/**
* A command was executed to apply a combat tag to the player.
*/
COMMAND,
/**
* Crystal or anchor explosion caused the tag.
*/
CRYSTAL,
/**
* A custom cause, typically defined by external plugins or systems, applied the combat tag.
*/
CUSTOM
}
@@ -1,11 +1,43 @@
package com.eternalcode.combat.fight.event;
public enum CauseOfUnTag {
/**
* The combat tag expired after a set duration.
*/
TIME_EXPIRED,
/**
* The player died, which resulted in the removal of the combat tag.
*/
DEATH,
/**
* The player was killed by another player, causing the combat tag to be removed.
*/
DEATH_BY_PLAYER,
/**
* The player logged out, triggering the removal of the combat tag.
*/
LOGOUT,
/**
* A command was executed to remove the player's combat tag.
*/
COMMAND,
/**
* The attacker released the player, ending the combat and removing the tag.
*/
ATTACKER_RELEASE,
/**
* All of the player's combat opponents logged out, so there was nobody left to fight.
*/
OPPONENT_LOGOUT,
/**
* A custom cause, typically defined by external plugins or systems.
*/
CUSTOM
}
@@ -5,59 +5,80 @@ import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
/**
* This event is triggered when a player is tagged in a fight.
*/
public class FightTagEvent extends Event implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private static final HandlerList HANDLER_LIST = new HandlerList();
private final UUID player;
private final CauseOfTag cause;
private boolean isCancelled = false;
private String cancelMessage = "";
private boolean cancelled = false;
private CancelTagReason cancelReason;
public FightTagEvent(UUID player, CauseOfTag cause) {
super(false);
this.player = player;
this.cause = cause;
}
/**
* Gets the UUID of the player who was tagged.
*
* @return The UUID of the tagged player.
*/
public UUID getPlayer() {
return this.player;
}
/**
* Gets the reason why the player was tagged.
*
* @return The cause of the combat tag as a {@link CauseOfTag}.
*/
public CauseOfTag getCause() {
return this.cause;
}
@Override
public boolean isCancelled() {
return this.isCancelled;
return this.cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
throw new UnsupportedOperationException("Use #cancel(String) instead");
this.cancelled = cancelled;
}
public void cancel(String cancelMessage) {
this.isCancelled = true;
this.cancelMessage = cancelMessage;
/**
* Gets the reason why the event was cancelled.
*
* @return The cancel reason.
*/
public CancelTagReason getCancelReason() {
return this.cancelReason;
}
public void allow() {
this.isCancelled = false;
this.cancelMessage = "";
}
public String getCancelMessage() {
return this.cancelMessage;
/**
* Sets the reason why the event was cancelled.
*
* @param cancelReason The cancel reason.
*/
public void setCancelReason(CancelTagReason cancelReason) {
this.cancelReason = cancelReason;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
return HANDLER_LIST;
}
public static HandlerList getHandlerList() {
return handlers;
return HANDLER_LIST;
}
}
@@ -5,43 +5,61 @@ import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
/**
* This event is triggered when a player is untagged during a fight.
*/
public class FightUntagEvent extends Event implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private static final HandlerList HANDLER_LIST = new HandlerList();
private final UUID player;
private final CauseOfUnTag cause;
private boolean isCancelled = false;
private boolean cancelled;
public FightUntagEvent(UUID player, CauseOfUnTag cause) {
super(false);
this.player = player;
this.cause = cause;
}
/**
* Gets the UUID of the player who was untagged.
*
* @return The UUID of the untagged player.
*/
public UUID getPlayer() {
return this.player;
}
/**
* Gets the reason for why the player was untagged.
*
* @return The cause of untagging as a {@link CauseOfUnTag}.
*/
public CauseOfUnTag getCause() {
return this.cause;
}
@Override
public boolean isCancelled() {
return this.isCancelled;
return this.cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.isCancelled = cancelled;
this.cancelled = cancelled;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
return HANDLER_LIST;
}
public static HandlerList getHandlerList() {
return handlers;
return HANDLER_LIST;
}
}
@@ -1,47 +0,0 @@
package com.eternalcode.combat.fight.logout;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import panda.utilities.text.Formatter;
public class LogoutController implements Listener {
private final FightManager fightManager;
private final LogoutService logoutService;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public LogoutController(FightManager fightManager, LogoutService logoutService, NotificationAnnouncer announcer, PluginConfig config) {
this.fightManager = fightManager;
this.logoutService = logoutService;
this.announcer = announcer;
this.config = config;
}
@EventHandler(priority = EventPriority.HIGH)
void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
this.logoutService.punishForLogout(player);
player.setHealth(0.0);
Formatter formatter = new Formatter()
.register("{PLAYER}", player.getName());
String format = formatter.format(this.config.messages.playerLoggedOutDuringCombat);
this.announcer.broadcast(format);
}
}
@@ -1,101 +0,0 @@
package com.eternalcode.combat.fight.pearl;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.util.DurationUtil;
import org.bukkit.Material;
import org.bukkit.entity.EnderPearl;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import panda.utilities.text.Formatter;
import java.time.Duration;
import java.util.UUID;
public class FightPearlController implements Listener {
private final FightPearlSettings settings;
private final NotificationAnnouncer announcer;
private final FightManager fightManager;
private final FightPearlManager fightPearlManager;
public FightPearlController(FightPearlSettings settings, NotificationAnnouncer announcer, FightManager fightManager, FightPearlManager fightPearlManager) {
this.settings = settings;
this.announcer = announcer;
this.fightManager = fightManager;
this.fightPearlManager = fightPearlManager;
}
@EventHandler
void onInteract(PlayerInteractEvent event) {
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.settings.pearlThrowBlocked) {
return;
}
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
ItemStack item = event.getItem();
if (item == null || item.getType() != Material.ENDER_PEARL) {
return;
}
Action action = event.getAction();
if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) {
return;
}
if (this.settings.pearlThrowDelay.isZero()) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.settings.pearlThrowBlockedDuringCombat);
return;
}
if (this.fightPearlManager.hasDelay(uniqueId)) {
event.setCancelled(true);
Duration remainingPearlDelay = this.fightPearlManager.getRemainingDelay(uniqueId);
Formatter formatter = new Formatter()
.register("{TIME}", DurationUtil.format(remainingPearlDelay));
String format = formatter.format(this.settings.pearlThrowBlockedDelayDuringCombat);
this.announcer.sendMessage(player, format);
return;
}
this.fightPearlManager.markDelay(uniqueId);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
void onEntityDamage(EntityDamageByEntityEvent event) {
if (this.settings.pearlThrowDamageEnabled) {
return;
}
if (!(event.getEntity() instanceof Player)) {
return;
}
if (!(event.getDamager() instanceof EnderPearl)) {
return;
}
if (event.getCause() != EntityDamageEvent.DamageCause.FALL) {
return;
}
event.setDamage(0.0D);
}
}
@@ -1,37 +0,0 @@
package com.eternalcode.combat.fight.pearl;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
public class FightPearlManager {
private final FightPearlSettings pearlSettings;
private final Cache<UUID, Instant> pearlDelays;
public FightPearlManager(FightPearlSettings pearlSettings) {
this.pearlSettings = pearlSettings;
this.pearlDelays = Caffeine.newBuilder()
.expireAfterWrite(pearlSettings.pearlThrowDelay)
.build();
}
public void markDelay(UUID uuid) {
this.pearlDelays.put(uuid, Instant.now().plus(this.pearlSettings.pearlThrowDelay));
}
public boolean hasDelay(UUID uuid) {
return Instant.now().isBefore(this.getDelay(uuid));
}
public Duration getRemainingDelay(UUID uuid) {
return Duration.between(Instant.now(), this.getDelay(uuid));
}
public Instant getDelay(UUID uuid) {
return this.pearlDelays.asMap().getOrDefault(uuid, Instant.MIN);
}
}
@@ -0,0 +1,16 @@
package com.eternalcode.combat.fight.pearl;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
public interface FightPearlService {
Instant getDelay(UUID uuid);
Duration getRemainingDelay(UUID uuid);
boolean hasDelay(UUID uuid);
void markDelay(UUID uuid);
}
@@ -1,29 +0,0 @@
package com.eternalcode.combat.fight.pearl;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
public class FightPearlSettings extends OkaeriConfig {
@Comment({ "# Is pearl damage to be enabled?", "# This will work globally" })
public boolean pearlThrowDamageEnabled = true;
@Comment("# Set true, If you want to lock pearls during the combat")
public boolean pearlThrowBlocked = false;
@Comment({
"# Block throwing pearls with delay?",
"# If you set this to for example 3s, player will have to wait 3 seconds before throwing another pearl",
"# Set to 0 to disable"
})
public Duration pearlThrowDelay = Duration.ofSeconds(3);
@Comment("# Message sent when player tries to throw ender pearl, but are disabled")
public String pearlThrowBlockedDuringCombat = "&cThrowing ender pearls is prohibited during combat!";
@Comment("# Message sent when player tries to throw ender pearl, but has delay")
public String pearlThrowBlockedDelayDuringCombat = "&cYou must wait {TIME} before next throw!";
}
@@ -1,34 +1,13 @@
package com.eternalcode.combat.fight.tagout;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class FightTagOutService {
public interface FightTagOutService {
private final Map<UUID, Instant> tagOuts = new HashMap<>();
boolean isTaggedOut(UUID player);
public void tagOut(UUID player, Duration duration) {
Instant endTime = Instant.now().plus(duration);
this.tagOuts.put(player, endTime);
}
public void unTagOut(UUID player) {
this.tagOuts.remove(player);
}
public boolean isTaggedOut(UUID player) {
Instant endTime = this.tagOuts.get(player);
if (endTime == null) {
return false;
}
Instant now = Instant.now();
return now.isBefore(endTime);
}
void unTagOut(UUID player);
void tagOut(UUID player, Duration duration);
}
@@ -1,94 +0,0 @@
package com.eternalcode.combat.fight.tagout;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.util.DurationUtil;
import dev.rollczi.litecommands.argument.Arg;
import dev.rollczi.litecommands.command.execute.Execute;
import dev.rollczi.litecommands.command.permission.Permission;
import dev.rollczi.litecommands.command.route.Route;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
@Route(name = "tagout", aliases = "tagimmunity")
public class TagOutCommand {
private final FightTagOutService fightTagOutService;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public TagOutCommand(FightTagOutService fightTagOutService, NotificationAnnouncer announcer, PluginConfig config) {
this.fightTagOutService = fightTagOutService;
this.announcer = announcer;
this.config = config;
}
@Execute(required = 1)
@Permission("eternalcombat.tagout")
void tagout(Player sender, @Arg Duration time) {
UUID targetUniqueId = sender.getUniqueId();
Formatter formatter = new Formatter()
.register("{PLAYER}", sender.getName())
.register("{TIME}", DurationUtil.format(time));
this.fightTagOutService.tagOut(targetUniqueId, time);
String format = formatter.format(this.config.messages.admin.adminTagOutSelf);
this.announcer.sendMessage(sender, format);
}
@Execute(required = 2)
@Permission("eternalcombat.tagout")
void tagout(Player sender, @Arg Player target, @Arg Duration time) {
UUID targetUniqueId = target.getUniqueId();
Instant now = Instant.now();
Duration remaining = Duration.between(now, now.plus(time));
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName())
.register("{TIME}", DurationUtil.format(remaining));
this.fightTagOutService.tagOut(targetUniqueId, time);
String adminTagOutFormat = formatter.format(this.config.messages.admin.adminTagOut);
this.announcer.sendMessage(sender, adminTagOutFormat);
String playerTagOutFormat = formatter.format(this.config.messages.admin.playerTagOut);
this.announcer.sendMessage(target, playerTagOutFormat);
}
@Execute(route = "remove", required = 1)
@Permission("eternalcombat.tagout")
void untagout(Player sender, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
this.fightTagOutService.unTagOut(targetUniqueId);
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName());
if (!targetUniqueId.equals(sender.getUniqueId())) {
String adminUnTagOutFormat = formatter.format(this.config.messages.admin.adminTagOutOff);
this.announcer.sendMessage(sender, adminUnTagOutFormat);
}
this.announcer.sendMessage(target, this.config.messages.admin.playerTagOutOff);
}
@Execute(route = "remove", required = 0)
@Permission("eternalcombat.tagout")
void untagout(Player sender) {
UUID senderUniqueId = sender.getUniqueId();
this.fightTagOutService.unTagOut(senderUniqueId);
this.announcer.sendMessage(sender, this.config.messages.admin.playerTagOutOff);
}
}
@@ -1,8 +0,0 @@
package com.eternalcode.combat.notification;
public interface Notification {
String message();
NotificationType type();
}
@@ -1,79 +0,0 @@
package com.eternalcode.combat.notification;
import com.eternalcode.combat.notification.implementation.BossBarNotification;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.title.Title;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
public final class NotificationAnnouncer {
private final AudienceProvider audienceProvider;
private final MiniMessage miniMessage;
public NotificationAnnouncer(AudienceProvider audienceProvider, MiniMessage miniMessage) {
this.audienceProvider = audienceProvider;
this.miniMessage = miniMessage;
}
public void send(CommandSender sender, Notification notification, Formatter formatter) {
Audience audience = this.audience(sender);
Component message = this.miniMessage.deserialize(formatter.format(notification.message()));
NotificationType type = notification.type();
switch (type) {
case CHAT -> audience.sendMessage(message);
case ACTION_BAR -> audience.sendActionBar(message);
case TITLE -> {
Title title = Title.title(message, Component.empty());
audience.showTitle(title);
}
case SUB_TITLE -> {
Title subTitle = Title.title(Component.empty(), message);
audience.showTitle(subTitle);
}
case BOSS_BAR -> {
BossBarNotification bossBarNotification = (BossBarNotification) notification;
BossBar bossBar = bossBarNotification.create(message);
audience.showBossBar(bossBar);
}
default -> throw new IllegalStateException("Unknown notification type: " + type);
}
}
public void sendMessage(CommandSender commandSender, String text) {
Audience audience = this.audience(commandSender);
Component message = this.miniMessage.deserialize(text);
audience.sendMessage(message);
}
public void broadcast(String text) {
Audience audience = this.audienceProvider.all();
Component message = this.miniMessage.deserialize(text);
audience.sendMessage(message);
}
private Audience audience(CommandSender sender) {
if (sender instanceof Player player) {
return this.audienceProvider.player(player.getUniqueId());
}
return this.audienceProvider.console();
}
}
@@ -1,9 +0,0 @@
package com.eternalcode.combat.notification;
public enum NotificationType {
CHAT,
ACTION_BAR,
TITLE,
SUB_TITLE,
BOSS_BAR
}
@@ -1,12 +0,0 @@
package com.eternalcode.combat.notification.implementation;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationType;
public record ActionBarNotification(String message) implements Notification {
@Override
public NotificationType type() {
return NotificationType.ACTION_BAR;
}
}
@@ -1,20 +0,0 @@
package com.eternalcode.combat.notification.implementation;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationType;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
public record BossBarNotification(String message, float progress, BossBar.Color color, BossBar.Overlay overlay) implements Notification {
public BossBar create(Component name) {
float replacedProgress = this.progress < 0.0F ? BossBar.MAX_PROGRESS : this.progress;
return BossBar.bossBar(name, replacedProgress, this.color, this.overlay);
}
@Override
public NotificationType type() {
return NotificationType.BOSS_BAR;
}
}
@@ -1,12 +0,0 @@
package com.eternalcode.combat.notification.implementation;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationType;
public record ChatNotification(String message) implements Notification {
@Override
public NotificationType type() {
return NotificationType.CHAT;
}
}
@@ -1,12 +0,0 @@
package com.eternalcode.combat.notification.implementation;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationType;
public record SubTitleNotification(String message) implements Notification {
@Override
public NotificationType type() {
return NotificationType.SUB_TITLE;
}
}
@@ -1,12 +0,0 @@
package com.eternalcode.combat.notification.implementation;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationType;
public record TitleNotification(String message) implements Notification {
@Override
public NotificationType type() {
return NotificationType.TITLE;
}
}
@@ -1,64 +0,0 @@
package com.eternalcode.combat.notification.serializer;
import com.eternalcode.combat.notification.Notification;
import com.eternalcode.combat.notification.NotificationType;
import com.eternalcode.combat.notification.implementation.ActionBarNotification;
import com.eternalcode.combat.notification.implementation.BossBarNotification;
import com.eternalcode.combat.notification.implementation.ChatNotification;
import com.eternalcode.combat.notification.implementation.SubTitleNotification;
import com.eternalcode.combat.notification.implementation.TitleNotification;
import eu.okaeri.configs.schema.GenericsDeclaration;
import eu.okaeri.configs.serdes.DeserializationData;
import eu.okaeri.configs.serdes.ObjectSerializer;
import eu.okaeri.configs.serdes.SerializationData;
import net.kyori.adventure.bossbar.BossBar;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
public class NotificationSerializer implements ObjectSerializer<Notification> {
@Override
public boolean supports(@NonNull Class<? super Notification> type) {
return Notification.class.isAssignableFrom(type);
}
@Override
public void serialize(@NonNull Notification notification, @NonNull SerializationData data, @NonNull GenericsDeclaration generics) {
data.add("type", notification.type(), NotificationType.class);
data.add("message", notification.message(), String.class);
if (notification instanceof BossBarNotification bossBarNotification) {
data.add("progress", bossBarNotification.progress(), float.class);
data.add("color", bossBarNotification.color(), BossBar.Color.class);
data.add("overlay", bossBarNotification.overlay(), BossBar.Overlay.class);
}
}
@Override
public Notification deserialize(@NonNull DeserializationData data, @NonNull GenericsDeclaration generics) {
NotificationType type = data.get("type", NotificationType.class);
String message = data.get("message", String.class);
return switch (type) {
case CHAT -> new ChatNotification(message);
case ACTION_BAR -> new ActionBarNotification(message);
case TITLE -> new TitleNotification(message);
case SUB_TITLE -> new SubTitleNotification(message);
case BOSS_BAR -> {
float progress = Optional.ofNullable(data.get("progress", float.class))
.orElse(BossBar.MAX_PROGRESS);
BossBar.Color color = Optional.ofNullable(data.get("color", BossBar.Color.class))
.orElse(BossBar.Color.RED);
BossBar.Overlay overlay = Optional.ofNullable(data.get("overlay", BossBar.Overlay.class))
.orElse(BossBar.Overlay.PROGRESS);
yield new BossBarNotification(message, progress, color, overlay);
}
};
}
}
@@ -1,41 +0,0 @@
package com.eternalcode.combat.region;
import org.bukkit.Location;
public class DefaultRegionProvider implements RegionProvider {
private final int radius;
public DefaultRegionProvider(int radius) {
this.radius = radius;
}
@Override
public boolean isInRegion(Location location) {
Location spawnLocation = location.getWorld().getSpawnLocation();
double x = spawnLocation.getX();
double z = spawnLocation.getZ();
Point min = new Point(x - this.radius, z - this.radius);
Point max = new Point(x + this.radius, z + this.radius);
return this.contains(min, max, location.getX(), location.getZ());
}
@Override
public Location getRegionCenter(Location location) {
Location spawnLocation = location.getWorld().getSpawnLocation();
double x = spawnLocation.getX();
double z = spawnLocation.getZ();
return new Location(location.getWorld(), x, location.getY(), z);
}
public boolean contains(Point min, Point max, double x, double z) {
return x >= min.x() && x < max.x()
&& z >= min.z() && z < max.z();
}
public record Point(double x, double z) {}
}
@@ -0,0 +1,6 @@
package com.eternalcode.combat.region;
import org.bukkit.World;
public record Point(World world, double x, double z) {
}
@@ -0,0 +1,25 @@
package com.eternalcode.combat.region;
import org.bukkit.Location;
public interface Region {
Point getCenter();
Location getMin();
Location getMax();
default boolean contains(Location location) {
return this.contains(location.getX(), location.getY(), location.getZ());
}
default boolean contains(double x, double y, double z) {
Location min = this.getMin();
Location max = this.getMax();
return x >= min.getX() && x < max.getX()
&& y >= min.getY() && y < max.getY()
&& z >= min.getZ() && z < max.getZ();
}
}
@@ -1,78 +0,0 @@
package com.eternalcode.combat.region;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.util.Vector;
public class RegionController implements Listener {
private final NotificationAnnouncer announcer;
private final RegionProvider regionProvider;
private final FightManager fightManager;
private final PluginConfig pluginConfig;
public RegionController(NotificationAnnouncer announcer, RegionProvider regionProvider, FightManager fightManager, PluginConfig pluginConfig) {
this.announcer = announcer;
this.regionProvider = regionProvider;
this.fightManager = fightManager;
this.pluginConfig = pluginConfig;
}
@EventHandler
void onPlayerMove(PlayerMoveEvent event) {
Player player = event.getPlayer();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
Location locationTo = event.getTo();
int xTo = locationTo.getBlockX();
int yTo = locationTo.getBlockY();
int zTo = locationTo.getBlockZ();
Location locationFrom = event.getFrom();
int xFrom = locationFrom.getBlockX();
int yFrom = locationFrom.getBlockY();
int zFrom = locationFrom.getBlockZ();
if (xTo != xFrom || yTo != yFrom || zTo != zFrom) {
if (!this.regionProvider.isInRegion(locationTo)) {
return;
}
Location spawnLocation = this.regionProvider.getRegionCenter(locationTo);
Location playerLocation = player.getLocation().subtract(spawnLocation);
double distance = playerLocation.distance(spawnLocation);
Vector knockback = new Vector(0, 3, 0).multiply(this.pluginConfig.settings.blockedRegionKnockMultiplier / distance);
Vector vector = playerLocation.toVector().add(knockback);
player.setVelocity(vector);
this.announcer.sendMessage(player, this.pluginConfig.messages.cantEnterOnRegion);
}
}
@EventHandler
public void onPlayerTeleport(PlayerTeleportEvent event) {
Player player = event.getPlayer();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
Location targetLocation = event.getTo();
if (this.regionProvider.isInRegion(targetLocation)) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.pluginConfig.messages.cantEnterOnRegion);
}
}
}
@@ -1,11 +1,18 @@
package com.eternalcode.combat.region;
import java.util.Collection;
import java.util.Optional;
import org.bukkit.Location;
import org.bukkit.World;
public interface RegionProvider {
boolean isInRegion(Location location);
Optional<Region> getRegion(Location location);
Location getRegionCenter(Location location);
default boolean isInRegion(Location location) {
return this.getRegion(location).isPresent();
}
Collection<Region> getRegions(World world);
}
@@ -1,69 +0,0 @@
package com.eternalcode.combat.region;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldguard.WorldGuard;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import com.sk89q.worldguard.protection.regions.RegionContainer;
import com.sk89q.worldguard.protection.regions.RegionQuery;
import org.bukkit.Location;
import java.util.List;
public class WorldGuardRegionProvider implements RegionProvider {
private final List<String> regions;
public WorldGuardRegionProvider(List<String> regions) {
this.regions = regions;
}
@Override
public boolean isInRegion(Location location) {
return this.regions.stream().anyMatch(region -> this.isInCombatRegion(location, region));
}
@Override
public Location getRegionCenter(Location location) {
RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer();
RegionQuery regionQuery = regionContainer.createQuery();
ApplicableRegionSet applicableRegions = regionQuery.getApplicableRegions(BukkitAdapter.adapt(location));
double minX = 0;
double maxX = 0;
double minZ = 0;
double maxZ = 0;
for (ProtectedRegion region : applicableRegions.getRegions()) {
BlockVector3 min = region.getMinimumPoint();
BlockVector3 max = region.getMaximumPoint();
if (min.getX() < minX) {
minX = min.getX();
}
if (max.getX() > maxX) {
maxX = max.getX();
}
if (min.getZ() < minZ) {
minZ = min.getZ();
}
if (max.getZ() > maxZ) {
maxZ = max.getZ();
}
}
double x = (maxX - minX) / 2 + minX;
double z = (maxZ - minZ) / 2 + minZ;
return new Location(location.getWorld(), x, location.getY(), z);
}
private boolean isInCombatRegion(Location location, String regionName) {
RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer();
RegionQuery regionQuery = regionContainer.createQuery();
ApplicableRegionSet applicableRegions = regionQuery.getApplicableRegions(BukkitAdapter.adapt(location));
return applicableRegions.getRegions().stream().anyMatch(region -> region.getId().equalsIgnoreCase(regionName));
}
}
@@ -1,35 +0,0 @@
package com.eternalcode.combat.updater;
import com.eternalcode.gitcheck.GitCheck;
import com.eternalcode.gitcheck.GitCheckResult;
import com.eternalcode.gitcheck.git.GitRepository;
import com.eternalcode.gitcheck.git.GitTag;
import org.bukkit.plugin.PluginDescriptionFile;
import panda.std.Lazy;
import java.util.concurrent.CompletableFuture;
public class UpdaterService {
private static final GitRepository GIT_REPOSITORY = GitRepository.of("EternalCodeTeam", "EternalCombat");
private final GitCheck gitCheck = new GitCheck();
private final Lazy<GitCheckResult> gitCheckResult;
public UpdaterService(PluginDescriptionFile descriptionFile) {
this.gitCheckResult = new Lazy<>(() -> {
String version = descriptionFile.getVersion();
return this.gitCheck.checkRelease(GIT_REPOSITORY, GitTag.of("v" + version));
});
}
public CompletableFuture<Boolean> isUpToDate() {
return CompletableFuture.supplyAsync(() -> {
GitCheckResult result = this.gitCheckResult.get();
return result.isUpToDate();
});
}
}
@@ -1,24 +0,0 @@
package com.eternalcode.combat.util;
import com.eternalcode.commons.time.DurationParser;
import com.eternalcode.commons.time.TemporalAmountParser;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public final class DurationUtil {
private static final TemporalAmountParser<Duration> WITHOUT_MILLS = new DurationParser()
.withUnit("s", ChronoUnit.SECONDS)
.withUnit("m", ChronoUnit.MINUTES)
.withUnit("h", ChronoUnit.HOURS)
.withUnit("d", ChronoUnit.DAYS)
.roundOff(ChronoUnit.MILLIS);
public DurationUtil() {
throw new UnsupportedOperationException("This class cannot be instantiated");
}
public static String format(Duration duration) {
return WITHOUT_MILLS.format(duration);
}
}
+171 -18
View File
@@ -1,62 +1,215 @@
import net.minecrell.pluginyml.bukkit.BukkitPluginDescription
import io.papermc.hangarpublishplugin.model.Platforms
plugins {
`eternalcombat-java`
`eternalcombat-repositories`
id("net.minecrell.plugin-yml.bukkit")
id("com.github.johnrengelman.shadow")
id("com.gradleup.shadow")
id("xyz.jpenilla.run-paper")
id("com.modrinth.minotaur") version "2.9.0"
id("io.papermc.hangar-publish-plugin") version "0.1.4"
}
val buildNumber = providers.environmentVariable("GITHUB_RUN_NUMBER").orNull
val isRelease = providers.environmentVariable("GITHUB_EVENT_NAME").orNull == "release"
if (buildNumber != null && !isRelease) {
val offset = try {
val description = providers.exec {
commandLine("git", "describe", "--tags", "--long")
}.standardOutput.asText.get().trim()
val parts = description.split("-")
if (parts.size >= 3) parts[parts.size - 2] else "0"
} catch (e: Exception) {
try {
providers.exec {
commandLine("git", "rev-list", "--count", "HEAD")
}.standardOutput.asText.get().trim()
} catch (e: Exception) {
"0"
}
}
val baseVersion = project.version.toString().replace("-SNAPSHOT", "")
version = "$baseVersion-SNAPSHOT+$offset"
}
val changelogText = providers.environmentVariable("CHANGELOG").orElse(providers.exec {
commandLine("git", "log", "-1", "--format=%B")
}.standardOutput.asText)
logger.lifecycle("Building version: $version")
val paperVersions = (property("paperVersion") as String)
.split(",")
.map { it.trim() }
val hangarToken = providers.environmentVariable("HANGAR_API_TOKEN")
.orElse(providers.gradleProperty("hangarToken"))
val projectVersion = project.version.toString()
val releaseChannel = if (isRelease) "Release" else "Snapshot"
val shadowJarTask = tasks.shadowJar
modrinth {
token.set(providers.environmentVariable("MODRINTH_TOKEN"))
projectId.set("eternalcombat")
versionNumber.set(projectVersion)
versionType.set(if (isRelease) "release" else "beta")
uploadFile.set(shadowJarTask)
gameVersions.addAll(paperVersions)
loaders.addAll(listOf("paper", "spigot", "purpur"))
changelog.set(changelogText)
syncBodyFrom.set(rootProject.file("README.md").readText())
}
hangarPublish {
publications.register("plugin") {
version.set(projectVersion)
channel.set(releaseChannel)
changelog.set(changelogText)
id.set("eternalcombat")
apiKey.set(hangarToken)
platforms {
register(Platforms.PAPER) {
jar.set(shadowJarTask.flatMap { it.archiveFile })
platformVersions.set(paperVersions)
}
}
}
}
configurations.all {
resolutionStrategy {
eachDependency {
if (requested.group == "com.google.code.gson" && requested.name == "gson") {
useVersion("2.11.0")
because("WorldGuard requires strictly gson 2.11.0")
}
}
}
}
sourceSets {
main {
resources.srcDir("src/resources")
}
}
dependencies {
implementation(project(":eternalcombat-api"))
compileOnly("io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.6")
compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.3.0-SNAPSHOT") // Import WorldEdit.
compileOnly(
"com.sk89q.worldguard:worldguard-bukkit:7.0.9"
) { // Import WorldGuard but without its bundled WorldEdit.
exclude(group = "com.sk89q.worldedit")
}
// kyori
implementation("net.kyori:adventure-platform-bukkit:${Versions.ADVENTURE_PLATFORM_BUKKIT}")
implementation("net.kyori:adventure-text-minimessage:${Versions.ADVENTURE_API}")
implementation("net.kyori:adventure-api") {
version {
strictly(Versions.ADVENTURE_API)
}
}
// litecommands
implementation("dev.rollczi:litecommands-bukkit:${Versions.LITE_COMMANDS}")
// Okaeri configs
implementation("eu.okaeri:okaeri-configs-serdes-commons:${Versions.OKAERI_CONFIGS_SERDES_COMMONS}")
implementation("eu.okaeri:okaeri-configs-serdes-bukkit:${Versions.OKAERI_CONFIGS_SERDES_BUKKIT}")
// XSeries
implementation("com.github.cryptomorin:XSeries:${Versions.XSERIES}")
// caffeine
implementation("com.github.ben-manes.caffeine:caffeine:${Versions.CAFFEINE}")
implementation("com.eternalcode:eternalcode-commons-adventure:${Versions.ETERNALCODE_COMMONS}")
implementation("com.eternalcode:eternalcode-commons-bukkit:${Versions.ETERNALCODE_COMMONS}")
implementation("com.eternalcode:eternalcode-commons-shared:${Versions.ETERNALCODE_COMMONS}")
implementation("com.eternalcode:eternalcode-commons-updater:${Versions.ETERNALCODE_COMMONS}")
// worldguard
compileOnly("com.sk89q.worldguard:worldguard-bukkit:${Versions.WORLD_GUARD_BUKKIT}")
// PlaceholderAPI
compileOnly("me.clip:placeholderapi:${Versions.PLACEHOLDER_API}")
// Multification
implementation("com.eternalcode:multification-bukkit:${Versions.MULTIFICATION}")
implementation("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION}")
compileOnly("com.github.retrooper:packetevents-spigot:${Versions.PACKETEVENTS}")
implementation("io.papermc:paperlib:${Versions.PAPERLIB}")
implementation("eu.okaeri:okaeri-configs-core:5.0.9")
implementation("eu.okaeri:okaeri-configs-yaml-snakeyaml:5.0.9")
implementation("eu.okaeri:okaeri-configs-yaml-bukkit:5.0.9")
implementation("eu.okaeri:okaeri-configs-serdes-commons:5.0.9")
implementation("eu.okaeri:okaeri-configs-serdes-bukkit:5.0.9")
}
bukkit {
name = "EternalCombat-OG"
main = "com.eternalcode.combat.CombatPlugin"
author = "EternalCodeTeam"
apiVersion = "1.13"
prefix = "EternalCombat"
name = "EternalCombat"
softDepend = listOf("WorldGuard")
version = project.version.toString()
apiVersion = "1.19"
authors = listOf("EternalCodeTeam", "true-og")
prefix = "EternalCombat-OG"
load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD
softDepend = listOf(
"WorldGuard"
)
depend = listOf(
"PacketEvents",
)
version = "${project.version}"
foliaSupported = false
}
tasks {
runServer {
minecraftVersion("1.19.4")
downloadPlugins.modrinth("WorldEdit", Versions.WORLDEDIT)
downloadPlugins.modrinth("PacketEvents", "${Versions.PACKETEVENTS}+spigot")
downloadPlugins.modrinth("WorldGuard", Versions.WORLDGUARD)
downloadPlugins.modrinth("LuckPerms", "v${Versions.LUCKPERMS}-bukkit")
}
}
tasks.shadowJar {
archiveFileName.set("EternalCombat v${project.version}.jar")
dependsOn("checkstyleMain")
dependsOn("checkstyleTest")
dependsOn("test")
archiveFileName.set("EternalCombat-OG-${project.version}.jar")
exclude(
"org/intellij/lang/annotations/**",
"org/jetbrains/annotations/**",
"META-INF/**",
"kotlin/**",
"javax/**"
"javax/**",
"org/checkerframework/**",
"com/google/errorprone/**"
)
val prefix = "com.eternalcode.combat.libs"
listOf(
"panda.std",
"panda.utilities",
"org.panda-lang",
"eu.okaeri",
"net.kyori",
"org.bstats",
"org.yaml",
"dev.rollczi.litecommands",
"com.eternalcode.gitcheck",
"org.json.simple",
"org.apache.commons",
"javassist",
"com.github.benmanes.caffeine",
"com.eternalcode.commons"
"com.eternalcode.commons",
"com.eternalcode.multification",
"com.github.cryptomorin",
"io.papermc.lib",
).forEach { pack ->
relocate(pack, "$prefix.$pack")
}
@@ -1,99 +1,127 @@
package com.eternalcode.combat;
import com.eternalcode.combat.border.BorderTriggerController;
import com.eternalcode.combat.border.BorderService;
import com.eternalcode.combat.border.BorderServiceImpl;
import com.eternalcode.combat.border.animation.block.BorderBlockController;
import com.eternalcode.combat.border.animation.particle.ParticleController;
import com.eternalcode.combat.bridge.BridgeService;
import com.eternalcode.combat.command.InvalidUsage;
import com.eternalcode.combat.command.PermissionMessage;
import com.eternalcode.combat.config.ConfigService;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.drop.DropController;
import com.eternalcode.combat.drop.DropKeepInventoryManager;
import com.eternalcode.combat.drop.DropManager;
import com.eternalcode.combat.drop.impl.PercentDropModifier;
import com.eternalcode.combat.drop.impl.PlayersHealthDropModifier;
import com.eternalcode.combat.crystalpvp.EndCrystalListener;
import com.eternalcode.combat.crystalpvp.RespawnAnchorListener;
import com.eternalcode.combat.event.EventManager;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightManagerImpl;
import com.eternalcode.combat.fight.FightTagCommand;
import com.eternalcode.combat.fight.FightTask;
import com.eternalcode.combat.fight.controller.FightActionBlockerController;
import com.eternalcode.combat.fight.controller.FightBypassAdminController;
import com.eternalcode.combat.fight.controller.FightBypassCreativeController;
import com.eternalcode.combat.fight.controller.FightBypassPermissionController;
import com.eternalcode.combat.fight.controller.FightInventoryController;
import com.eternalcode.combat.fight.death.DeathFlareController;
import com.eternalcode.combat.fight.death.DeathLightningController;
import com.eternalcode.combat.fight.controller.FightMessageController;
import com.eternalcode.combat.fight.controller.FightTagController;
import com.eternalcode.combat.fight.controller.FightUnTagController;
import com.eternalcode.combat.fight.drop.DropController;
import com.eternalcode.combat.fight.drop.DropKeepInventoryService;
import com.eternalcode.combat.fight.drop.DropKeepInventoryServiceImpl;
import com.eternalcode.combat.fight.drop.DropService;
import com.eternalcode.combat.fight.drop.DropServiceImpl;
import com.eternalcode.combat.fight.drop.impl.PercentDropModifier;
import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier;
import com.eternalcode.combat.fight.effect.FightEffectController;
import com.eternalcode.combat.event.EventCaller;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTask;
import com.eternalcode.combat.fight.bossbar.FightBossBarService;
import com.eternalcode.combat.fight.effect.FightEffectService;
import com.eternalcode.combat.fight.effect.FightEffectServiceImpl;
import com.eternalcode.combat.fight.firework.FireworkController;
import com.eternalcode.combat.fight.knockback.KnockbackRegionController;
import com.eternalcode.combat.fight.knockback.KnockbackService;
import com.eternalcode.combat.fight.logout.LogoutController;
import com.eternalcode.combat.fight.logout.LogoutService;
import com.eternalcode.combat.fight.logout.OpponentLogoutController;
import com.eternalcode.combat.fight.pearl.FightPearlController;
import com.eternalcode.combat.fight.pearl.FightPearlManager;
import com.eternalcode.combat.fight.pearl.FightPearlService;
import com.eternalcode.combat.fight.pearl.FightPearlServiceImpl;
import com.eternalcode.combat.fight.tagout.FightTagOutCommand;
import com.eternalcode.combat.fight.tagout.FightTagOutController;
import com.eternalcode.combat.fight.tagout.FightTagOutService;
import com.eternalcode.combat.fight.tagout.TagOutCommand;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.region.RegionController;
import com.eternalcode.combat.fight.tagout.FightTagOutServiceImpl;
import com.eternalcode.combat.handler.InvalidUsageHandlerImpl;
import com.eternalcode.combat.handler.MissingPermissionHandlerImpl;
import com.eternalcode.combat.notification.NoticeService;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.combat.updater.UpdaterNotificationController;
import com.eternalcode.combat.updater.UpdaterService;
import com.eternalcode.commons.adventure.AdventureLegacyColorPostProcessor;
import com.eternalcode.commons.adventure.AdventureLegacyColorPreProcessor;
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
import com.eternalcode.multification.notice.Notice;
import com.google.common.base.Stopwatch;
import dev.rollczi.litecommands.LiteCommands;
import dev.rollczi.litecommands.bukkit.adventure.platform.LiteBukkitAdventurePlatformFactory;
import dev.rollczi.litecommands.bukkit.tools.BukkitOnlyPlayerContextual;
import dev.rollczi.litecommands.bukkit.tools.BukkitPlayerArgument;
import dev.rollczi.litecommands.bukkit.LiteBukkitFactory;
import dev.rollczi.litecommands.bukkit.LiteBukkitMessages;
import java.io.File;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bstats.bukkit.Metrics;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
private static final String FALLBACK_PREFIX = "eternalcombat";
private static final int BSTATS_METRICS_ID = 17803;
private static final String CONFIG_RESOURCE_PATH = "config.yml";
private FightManager fightManager;
private FightPearlManager fightPearlManager;
private FightPearlService fightPearlService;
private FightTagOutService fightTagOutService;
private FightEffectService fightEffectService;
private LogoutService logoutService;
private DropManager dropManager;
private DropKeepInventoryManager dropKeepInventoryManager;
private DropService dropService;
private DropKeepInventoryService dropKeepInventoryService;
private RegionProvider regionProvider;
private PluginConfig pluginConfig;
private AudienceProvider audienceProvider;
private LiteCommands<CommandSender> liteCommands;
@Override
public void onEnable() {
Stopwatch started = Stopwatch.createStarted();
Server server = this.getServer();
File dataFolder = this.getDataFolder();
File configFile = new File(dataFolder, CONFIG_RESOURCE_PATH);
if (!configFile.exists()) {
this.saveResource(CONFIG_RESOURCE_PATH, false);
}
ConfigService configService = new ConfigService();
EventCaller eventCaller = new EventCaller(server);
EventManager eventManager = new EventManager(this);
PluginConfig pluginConfig = configService.create(PluginConfig.class, configFile);
this.pluginConfig = configService.create(PluginConfig.class, new File(dataFolder, "config.yml"));
MinecraftScheduler scheduler = CombatSchedulerAdapter.getAdaptiveScheduler(this);
this.fightManager = new FightManager(eventCaller);
this.fightPearlManager = new FightPearlManager(this.pluginConfig.pearl);
this.fightTagOutService = new FightTagOutService();
this.fightEffectService = new FightEffectService();
this.logoutService = new LogoutService();
this.dropManager = new DropManager();
this.dropKeepInventoryManager = new DropKeepInventoryManager();
this.fightManager = new FightManagerImpl(eventManager);
this.fightPearlService = new FightPearlServiceImpl(pluginConfig.pearl);
this.fightTagOutService = new FightTagOutServiceImpl();
this.fightEffectService = new FightEffectServiceImpl();
LogoutService logoutService = new LogoutService();
this.dropService = new DropServiceImpl();
this.dropKeepInventoryService = new DropKeepInventoryServiceImpl();
UpdaterService updaterService = new UpdaterService(this.getDescription());
@@ -103,49 +131,85 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
.preProcessor(new AdventureLegacyColorPreProcessor())
.build();
FightBossBarService fightBossBarService = new FightBossBarService(this.pluginConfig, this.audienceProvider, miniMessage);
NoticeService noticeService = new NoticeService(this.audienceProvider, pluginConfig, miniMessage);
BridgeService bridgeService = new BridgeService(
pluginConfig,
server.getPluginManager(),
this.getLogger(),
this,
this.fightManager
);
bridgeService.init(server);
BridgeService bridgeService = new BridgeService(this.pluginConfig, server.getPluginManager(), this.getLogger());
bridgeService.init();
this.regionProvider = bridgeService.getRegionProvider();
BorderService borderService = new BorderServiceImpl(scheduler, server, regionProvider, eventManager, () -> pluginConfig.border);
KnockbackService knockbackService = new KnockbackService(pluginConfig, scheduler, regionProvider);
NotificationAnnouncer notificationAnnouncer = new NotificationAnnouncer(this.audienceProvider, miniMessage);
this.liteCommands = LiteBukkitFactory.builder(FALLBACK_PREFIX, this, server)
.message(LiteBukkitMessages.PLAYER_NOT_FOUND, pluginConfig.messagesSettings.playerNotFound)
.message(LiteBukkitMessages.PLAYER_ONLY, pluginConfig.messagesSettings.admin.onlyForPlayers)
this.liteCommands = LiteBukkitAdventurePlatformFactory.builder(server, FALLBACK_PREFIX, this.audienceProvider)
.argument(Player.class, new BukkitPlayerArgument<>(this.getServer(), this.pluginConfig.messages.playerNotFound))
.contextualBind(Player.class, new BukkitOnlyPlayerContextual<>(this.pluginConfig.messages.admin.onlyForPlayers))
.invalidUsage(new InvalidUsageHandlerImpl(pluginConfig, noticeService))
.missingPermission(new MissingPermissionHandlerImpl(pluginConfig, noticeService))
.invalidUsageHandler(new InvalidUsage(this.pluginConfig, notificationAnnouncer))
.permissionHandler(new PermissionMessage(this.pluginConfig, notificationAnnouncer))
.commands(
new FightTagCommand(this.fightManager, noticeService, pluginConfig),
new FightTagOutCommand(this.fightTagOutService, noticeService, pluginConfig),
new EternalCombatReloadCommand(configService, noticeService)
)
.commandInstance(new CombatCommand(this.fightManager, configService, notificationAnnouncer, this.pluginConfig))
.commandInstance(new TagOutCommand(this.fightTagOutService, notificationAnnouncer, this.pluginConfig))
.result(Notice.class, (invocation, result, chain) -> noticeService.create()
.viewer(invocation.sender())
.notice(result)
.send())
.register();
.build();
FightTask fightTask = new FightTask(server, this.pluginConfig, this.fightManager, fightBossBarService, notificationAnnouncer);
this.getServer().getScheduler().runTaskTimer(this, fightTask, 20L, 20L);
new Metrics(this, BSTATS_METRICS_ID);
FightTask fightTask = new FightTask(server, pluginConfig, this.fightManager, noticeService);
scheduler.timer(fightTask, Duration.ofSeconds(1), Duration.ofSeconds(1));
Stream.of(
new PercentDropModifier(this.pluginConfig.dropSettings),
new PlayersHealthDropModifier(this.pluginConfig.dropSettings, this.logoutService)
).forEach(this.dropManager::registerModifier);
new PercentDropModifier(pluginConfig.drop),
new PlayersHealthDropModifier(pluginConfig.drop, logoutService)
).forEach(this.dropService::registerModifier);
Stream.of(
new DropController(this.dropManager, this.dropKeepInventoryManager, this.pluginConfig.dropSettings, this.fightManager),
new FightTagController(this.fightManager, this.pluginConfig),
new LogoutController(this.fightManager, this.logoutService, notificationAnnouncer, this.pluginConfig),
new FightUnTagController(this.fightManager, this.pluginConfig, this.logoutService),
new FightActionBlockerController(this.fightManager, notificationAnnouncer, this.pluginConfig),
new FightPearlController(this.pluginConfig.pearl, notificationAnnouncer, this.fightManager, this.fightPearlManager),
new UpdaterNotificationController(updaterService, this.pluginConfig, this.audienceProvider, miniMessage),
new RegionController(notificationAnnouncer, this.regionProvider, this.fightManager, this.pluginConfig),
new FightEffectController(this.pluginConfig.effect, this.fightEffectService, this.fightManager, this.getServer()),
new FightTagOutController(this.fightTagOutService, this.pluginConfig),
new FightMessageController(this.fightManager, notificationAnnouncer, fightBossBarService, this.pluginConfig, this.getServer())
).forEach(listener -> this.getServer().getPluginManager().registerEvents(listener, this));
eventManager.subscribe(
new FightTagController(this.fightManager, pluginConfig),
new FightUnTagController(this.fightManager, pluginConfig, logoutService),
new FightBypassAdminController(server, pluginConfig),
new FightBypassPermissionController(server, pluginConfig),
new FightBypassCreativeController(server, pluginConfig),
new FightActionBlockerController(this.fightManager, noticeService, pluginConfig, server),
new FightPearlController(pluginConfig.pearl, noticeService, this.fightManager, this.fightPearlService),
new DeathFlareController(pluginConfig, server, scheduler, this),
new DeathLightningController(pluginConfig, server),
new UpdaterNotificationController(updaterService, pluginConfig, this.audienceProvider, miniMessage),
new KnockbackRegionController(noticeService, this.regionProvider, this.fightManager, knockbackService, server),
new FightEffectController(pluginConfig.effect, this.fightEffectService, this.fightManager, server),
new FightTagOutController(this.fightTagOutService),
new OpponentLogoutController(this.fightManager, server),
new FightMessageController(this.fightManager, noticeService, pluginConfig, server),
new BorderTriggerController(borderService, () -> pluginConfig.border, fightManager, server, scheduler),
new ParticleController(borderService, () -> pluginConfig.border.particle, scheduler, server),
new BorderBlockController(borderService, () -> pluginConfig.border.block, scheduler, server),
new EndCrystalListener(this, this.fightManager, pluginConfig),
new RespawnAnchorListener(this, this.fightManager, pluginConfig),
new FireworkController(this.fightManager, pluginConfig, noticeService),
new FightInventoryController(this.fightManager, pluginConfig, noticeService)
);
eventManager.subscribe(
PlayerDeathEvent.class,
pluginConfig.drop.dropEventPriority,
new DropController(dropService, dropKeepInventoryService, pluginConfig.drop, fightManager, miniMessage)
);
eventManager.subscribe(
PlayerQuitEvent.class,
pluginConfig.combat.quitPunishmentEventPriority,
new LogoutController(this.fightManager, logoutService, noticeService, pluginConfig)
);
EternalCombatProvider.initialize(this);
@@ -155,19 +219,22 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
@Override
public void onDisable() {
EternalCombatProvider.deinitialize();
if (EternalCombatProvider.isInitialized()) {
EternalCombatProvider.deinitialize();
}
if (this.liteCommands != null) {
this.liteCommands.getPlatform().unregisterAll();
this.liteCommands.unregister();
}
if (this.audienceProvider != null) {
this.audienceProvider.close();
}
this.fightManager.untagAll();
if (this.fightManager != null) {
this.fightManager.untagAll();
}
}
@Override
public FightManager getFightManager() {
return this.fightManager;
@@ -179,8 +246,8 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
}
@Override
public FightPearlManager getFightPearlManager() {
return this.fightPearlManager;
public FightPearlService getFightPearlService() {
return this.fightPearlService;
}
@Override
@@ -194,17 +261,12 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
}
@Override
public DropManager getDropManager() {
return this.dropManager;
public DropService getDropService() {
return this.dropService;
}
@Override
public DropKeepInventoryManager getDropKeepInventoryManager() {
return this.dropKeepInventoryManager;
}
@Override
public PluginConfig getPluginConfig() {
return this.pluginConfig;
public DropKeepInventoryService getDropKeepInventoryService() {
return this.dropKeepInventoryService;
}
}
@@ -0,0 +1,20 @@
package com.eternalcode.combat;
import com.eternalcode.commons.bukkit.scheduler.BukkitSchedulerImpl;
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
public final class CombatSchedulerAdapter {
private CombatSchedulerAdapter() {
throw new UnsupportedOperationException("This is an utility class and cannot be instantiated");
}
public static MinecraftScheduler getAdaptiveScheduler(Plugin plugin) {
Logger logger = plugin.getLogger();
logger.info("» Detected Bukkit/Paper environment. Using BukkitScheduler.");
return new BukkitSchedulerImpl(plugin);
}
}
@@ -0,0 +1,46 @@
package com.eternalcode.combat;
import com.eternalcode.combat.config.ConfigService;
import com.eternalcode.combat.notification.NoticeService;
import com.eternalcode.multification.bukkit.notice.BukkitNotice;
import com.eternalcode.multification.notice.Notice;
import com.google.common.base.Stopwatch;
import dev.rollczi.litecommands.annotations.async.Async;
import dev.rollczi.litecommands.annotations.command.Command;
import dev.rollczi.litecommands.annotations.context.Context;
import dev.rollczi.litecommands.annotations.execute.Execute;
import dev.rollczi.litecommands.annotations.permission.Permission;
import java.time.Duration;
import org.bukkit.command.CommandSender;
@Command(name = "combatlog", aliases = "combat")
public class EternalCombatReloadCommand {
private static final Notice RELOAD_MESSAGE = BukkitNotice.builder()
.chat("<b><gradient:#8a1212:#fc6b03>EternalCombat:</gradient></b> Reloaded EternalCombat in <color:#fce303>{TIME}ms!</color>")
.build();
private final ConfigService configService;
private final NoticeService noticeService;
public EternalCombatReloadCommand(ConfigService configService, NoticeService noticeService) {
this.configService = configService;
this.noticeService = noticeService;
}
@Async
@Execute(name = "reload")
@Permission("eternalcombat.reload")
void execute(@Context CommandSender sender) {
Stopwatch stopwatch = Stopwatch.createStarted();
this.configService.reload();
Duration elapsed = stopwatch.elapsed();
this.noticeService.create()
.viewer(sender)
.notice(RELOAD_MESSAGE)
.placeholder("{TIME}", String.valueOf(elapsed.toMillis()))
.send();
}
}
@@ -0,0 +1,66 @@
package com.eternalcode.combat.border;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
class BorderActivePointsIndex {
private final Map<String, Map<UUID, Set<BorderPoint>>> registry = new ConcurrentHashMap<>();
boolean hasPoints(String world, UUID player) {
Map<UUID, Set<BorderPoint>> worldRegistry = this.registry.get(world);
if (worldRegistry == null) {
return false;
}
return worldRegistry.containsKey(player);
}
Set<BorderPoint> putPoints(String world, UUID player, Set<BorderPoint> points) {
Map<UUID, Set<BorderPoint>> worldRegistry = this.registry.computeIfAbsent(world, k -> new ConcurrentHashMap<>());
Set<BorderPoint> oldPoints = worldRegistry.put(player, points);
if (oldPoints != null) {
return calculateRemovedPoints(points, oldPoints);
}
return Set.of();
}
private static Set<BorderPoint> calculateRemovedPoints(Set<BorderPoint> points, Set<BorderPoint> oldPoints) {
Set<BorderPoint> removed = new HashSet<>();
for (BorderPoint oldPoint : oldPoints) {
if (!points.contains(oldPoint)) {
removed.add(oldPoint);
}
}
return Collections.unmodifiableSet(removed);
}
Set<BorderPoint> getPoints(String world, UUID player) {
Map<UUID, Set<BorderPoint>> worldRegistry = this.registry.get(world);
if (worldRegistry == null) {
return Set.of();
}
return worldRegistry.getOrDefault(player, Set.of());
}
Set<BorderPoint> removePoints(String world, UUID player) {
Map<UUID, Set<BorderPoint>> worldRegistry = this.registry.get(world);
if (worldRegistry == null) {
return Set.of();
}
Set<BorderPoint> remove = worldRegistry.remove(player);
if (remove == null) {
return Set.of();
}
return remove;
}
}
@@ -0,0 +1,24 @@
package com.eternalcode.combat.border;
import dev.rollczi.litecommands.shared.Lazy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
class BorderLazyResult implements BorderResult {
private final List<Lazy<List<BorderPoint>>> borderPoints = new ArrayList<>();
void addLazyBorderPoints(Lazy<List<BorderPoint>> supplier) {
borderPoints.add(supplier);
}
@Override
public Set<BorderPoint> collect() {
return borderPoints.stream()
.flatMap(result -> result.get().stream())
.collect(Collectors.toUnmodifiableSet());
}
}
@@ -0,0 +1,25 @@
package com.eternalcode.combat.border;
import org.jetbrains.annotations.Nullable;
public record BorderPoint(int x, int y, int z, @Nullable BorderPoint innerPoint) {
public BorderPoint(int x, int y, int z) {
this(x, y, z, null);
}
/**
* This point represents an adjusted coordinate that takes into account the block's volume (thickness)
* and is used for logical placement and grouping, especially for assigning border blocks to the
* correct chunk when they are on the very edge of a chunk boundary.
* This ensures the block is considered to be on the "inner" side of the border for chunk-based logic.
*/
@Override
public BorderPoint innerPoint() {
if (this.innerPoint == null) {
return this;
}
return this.innerPoint;
}
}
@@ -0,0 +1,9 @@
package com.eternalcode.combat.border;
import java.util.Set;
public interface BorderResult {
Set<BorderPoint> collect();
}
@@ -0,0 +1,15 @@
package com.eternalcode.combat.border;
import java.util.Set;
import org.bukkit.Location;
import org.bukkit.entity.Player;
public interface BorderService {
void updateBorder(Player player, Location to);
void clearBorder(Player player);
Set<BorderPoint> getActiveBorder(Player player);
}
@@ -0,0 +1,172 @@
package com.eternalcode.combat.border;
import com.eternalcode.combat.border.event.BorderHideAsyncEvent;
import com.eternalcode.combat.border.event.BorderShowAsyncEvent;
import com.eternalcode.combat.event.EventManager;
import com.eternalcode.combat.region.Region;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
import dev.rollczi.litecommands.shared.Lazy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
public class BorderServiceImpl implements BorderService {
// Horizontal neighbour offsets (west, east, north, south). The border is a vertical wall, so only
// horizontal transitions between safe and unsafe space matter.
private static final int[][] HORIZONTAL_NEIGHBOURS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
private final MinecraftScheduler scheduler;
private final EventManager eventManager;
private final RegionProvider regionProvider;
private final Supplier<BorderSettings> settings;
private final BorderTriggerIndex borderIndexes;
private final BorderActivePointsIndex activeBorderIndex = new BorderActivePointsIndex();
public BorderServiceImpl(MinecraftScheduler scheduler, Server server, RegionProvider provider, EventManager eventManager, Supplier<BorderSettings> settings) {
this.scheduler = scheduler;
this.eventManager = eventManager;
this.regionProvider = provider;
this.settings = settings;
this.borderIndexes = BorderTriggerIndex.started(server, scheduler, provider, settings);
}
@Override
public void updateBorder(Player player, Location location) {
Optional<BorderResult> result = resolveBorder(location);
String world = player.getWorld().getName();
if (result.isEmpty()) {
if (!this.activeBorderIndex.hasPoints(world, player.getUniqueId())) {
return;
}
this.clearBorder(player);
return;
}
scheduler.runAsync(() -> {
BorderResult borderResult = result.get();
Set<BorderPoint> points = borderResult.collect();
if (!points.isEmpty()) {
BorderShowAsyncEvent event = eventManager.publishEvent(new BorderShowAsyncEvent(player, points));
points = event.getPoints();
}
Set<BorderPoint> removed = this.activeBorderIndex.putPoints(world, player.getUniqueId(), points);
if (!removed.isEmpty()) {
eventManager.publishEvent(new BorderHideAsyncEvent(player, removed));
}
});
}
@Override
public void clearBorder(Player player) {
World world = player.getWorld();
UUID uniqueId = player.getUniqueId();
scheduler.runAsync(() -> {
Set<BorderPoint> removed = this.activeBorderIndex.removePoints(world.getName(), uniqueId);
if (!removed.isEmpty()) {
eventManager.publishEvent(new BorderHideAsyncEvent(player, removed));
}
});
}
@Override
public Set<BorderPoint> getActiveBorder(Player player) {
return this.activeBorderIndex.getPoints(player.getWorld().getName(), player.getUniqueId());
}
private Optional<BorderResult> resolveBorder(Location location) {
// Cheap gate: only build a border when the player is near a restricted region at all.
if (borderIndexes.getTriggered(location).isEmpty()) {
return Optional.empty();
}
BorderLazyResult result = new BorderLazyResult();
result.addLazyBorderPoints(new Lazy<>(() -> this.resolveBorderPoints(location)));
return Optional.of(result);
}
// Render glass on the true boundary between safe (PvP-denied) and unsafe (PvP-allowed/wilderness)
// space, rather than around a region's bounding box. A wall block is placed on a safe column only
// when an adjacent column is unsafe, so the wall appears between the warzone and the safe spawn or
// market, but never between the warzone and the wilderness (both unsafe), and never on the outer
// face of a large umbrella region that merely encloses the warzone.
private List<BorderPoint> resolveBorderPoints(Location playerLocation) {
World world = playerLocation.getWorld();
int px = (int) Math.round(playerLocation.getX());
int py = (int) Math.round(playerLocation.getY());
int pz = (int) Math.round(playerLocation.getZ());
int distance = settings.get().distanceRounded();
// Sample, per column near the player, the safe region present at the player's Y (null = unsafe).
// Sampling one Y keeps this cheap; safe regions are effectively full-height in practice.
Map<Long, Region> columns = new HashMap<>();
for (int dx = -distance - 1; dx <= distance + 1; dx++) {
for (int dz = -distance - 1; dz <= distance + 1; dz++) {
int cx = px + dx;
int cz = pz + dz;
Region region = this.regionProvider.getRegion(new Location(world, cx, py, cz)).orElse(null);
columns.put(key(cx, cz), region);
}
}
List<BorderPoint> points = new ArrayList<>();
for (int dx = -distance; dx <= distance; dx++) {
for (int dz = -distance; dz <= distance; dz++) {
int cx = px + dx;
int cz = pz + dz;
// Only unsafe columns (where the player can stand) cast a wall onto safe neighbours.
if (columns.get(key(cx, cz)) != null) {
continue;
}
for (int[] neighbour : HORIZONTAL_NEIGHBOURS) {
int nx = cx + neighbour[0];
int nz = cz + neighbour[1];
Region safeRegion = columns.get(key(nx, nz));
if (safeRegion == null) {
continue;
}
int minY = Math.max(py - distance, safeRegion.getMin().getBlockY());
int maxY = Math.min(py + distance, safeRegion.getMax().getBlockY());
for (int y = minY; y <= maxY; y++) {
if (isVisible(nx, y, nz, playerLocation)) {
points.add(new BorderPoint(nx, y, nz));
}
}
}
}
}
return points;
}
private static long key(int x, int z) {
return (((long) x) << 32) ^ (z & 0xffffffffL);
}
private boolean isVisible(int x, int y, int z, Location player) {
return Math.sqrt(Math.pow(x - player.getX(), 2) + Math.pow(y - player.getY(), 2) + Math.pow(z - player.getZ(), 2)) <= this.settings.get().distance;
}
}
@@ -0,0 +1,42 @@
package com.eternalcode.combat.border;
import com.eternalcode.combat.border.animation.block.BlockSettings;
import com.eternalcode.combat.border.animation.particle.ParticleSettings;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
import org.jetbrains.annotations.ApiStatus;
public class BorderSettings extends OkaeriConfig {
@Comment("# Border view distance in blocks")
public double distance = 6.5;
@Comment({
" ",
"# Border block animation settings",
"# Configure the block animation that appears during combat."
})
public BlockSettings block = new BlockSettings();
@Comment({
" ",
"# Border particle animation settings",
"# Configure the particle animation that appears during combat."
})
public ParticleSettings particle = new ParticleSettings();
@ApiStatus.Internal
public Duration indexRefreshDelay() {
return Duration.ofSeconds(1);
}
public int distanceRounded() {
return (int) Math.ceil(this.distance);
}
public boolean isEnabled() {
return this.block.enabled || this.particle.enabled;
}
}
@@ -0,0 +1,20 @@
package com.eternalcode.combat.border;
record BorderTrigger(BorderPoint min, BorderPoint max, BorderPoint triggerMin, BorderPoint triggerMax) {
BorderTrigger(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, int distance) {
this(
new BorderPoint(minX, minY, minZ),
new BorderPoint(maxX, maxY, maxZ),
new BorderPoint(minX - distance, minY - distance, minZ - distance),
new BorderPoint(maxX + distance, maxY + distance, maxZ + distance)
);
}
public boolean isTriggered(int x, int y, int z) {
return x >= triggerMin.x() && x <= triggerMax.x()
&& y >= triggerMin.y() && y <= triggerMax.y()
&& z >= triggerMin.z() && z <= triggerMax.z();
}
}
@@ -0,0 +1,108 @@
package com.eternalcode.combat.border;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
import java.time.Duration;
import java.util.function.Supplier;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
public class BorderTriggerController implements Listener {
private static final long BORDER_WORK_AROUND_DELAY_MILLIS = 250;
private final BorderService borderService;
private final Supplier<BorderSettings> border;
private final FightManager fightManager;
private final Server server;
private final MinecraftScheduler scheduler;
public BorderTriggerController(
BorderService borderService,
Supplier<BorderSettings> border,
FightManager fightManager,
Server server,
MinecraftScheduler scheduler
) {
this.borderService = borderService;
this.border = border;
this.fightManager = fightManager;
this.server = server;
this.scheduler = scheduler;
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
void onMove(PlayerMoveEvent event) {
if (!border.get().isEnabled()) {
return;
}
Location to = event.getTo();
Location from = event.getFrom();
if (to.getBlockX() == from.getBlockX() && to.getBlockY() == from.getBlockY() && to.getBlockZ() == from.getBlockZ()) {
return;
}
Player player = event.getPlayer();
if (!fightManager.isInCombat(player.getUniqueId())) {
return;
}
borderService.updateBorder(player, to);
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
void onTeleport(PlayerTeleportEvent event) {
if (!border.get().isEnabled()) {
return;
}
Player player = event.getPlayer();
if (!fightManager.isInCombat(player.getUniqueId())) {
return;
}
borderService.updateBorder(player, event.getTo());
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
void onFightStart(FightTagEvent event) {
if (!border.get().isEnabled()) {
return;
}
Player player = server.getPlayer(event.getPlayer());
if (player == null) {
return;
}
borderService.updateBorder(player, player.getLocation());
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
void onFightEnd(FightUntagEvent event) {
if (!border.get().isEnabled()) {
return;
}
Player player = server.getPlayer(event.getPlayer());
if (player == null) {
return;
}
// Due to race condition in PlayerMoveEvent and FightUntagEvent - border can be triggered after death and untag of player - solution delayed removal of border
this.scheduler.runLater(() -> {
if (!this.fightManager.isInCombat(player.getUniqueId())) {
this.borderService.clearBorder(player);
}
}, Duration.ofMillis(BORDER_WORK_AROUND_DELAY_MILLIS));
}
}
@@ -0,0 +1,82 @@
package com.eternalcode.combat.border;
import com.eternalcode.combat.region.Region;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
import com.eternalcode.commons.scheduler.Scheduler;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
class BorderTriggerIndex {
private final Server server;
private final MinecraftScheduler scheduler;
private final RegionProvider provider;
private final Supplier<BorderSettings> settings;
private final Map<String, BorderTriggerIndexBucket> borderIndexes = new HashMap<>();
private BorderTriggerIndex(Server server, MinecraftScheduler scheduler, RegionProvider provider, Supplier<BorderSettings> settings) {
this.server = server;
this.scheduler = scheduler;
this.provider = provider;
this.settings = settings;
}
private void updateWorlds() {
for (World world : server.getWorlds()) {
this.updateWorld(world.getName(), provider.getRegions(world));
}
}
private void updateWorld(String world, Collection<Region> regions) {
this.scheduler.runAsync(() -> {
int distanceRounded = settings.get().distanceRounded();
List<BorderTrigger> triggers = new ArrayList<>();
for (Region region : regions) {
Location min = region.getMin();
Location max = region.getMax();
triggers.add(new BorderTrigger(
min.getBlockX(), min.getBlockY(), min.getBlockZ(),
max.getBlockX() + 1, max.getBlockY() + 1, max.getBlockZ() + 1,
distanceRounded
));
}
BorderTriggerIndexBucket index = BorderTriggerIndexBucket.create(triggers);
this.borderIndexes.put(world, index);
});
}
static BorderTriggerIndex started(Server server, MinecraftScheduler scheduler, RegionProvider provider, Supplier<BorderSettings> settings) {
BorderTriggerIndex index = new BorderTriggerIndex(server, scheduler, provider, settings);
scheduler.timerAsync(() -> index.updateWorlds(), Duration.ZERO, settings.get().indexRefreshDelay());
return index;
}
public List<BorderTrigger> getTriggered(Location location) {
BorderTriggerIndexBucket index = borderIndexes.get(location.getWorld().getName());
if (index == null) {
return List.of();
}
int x = (int) Math.round(location.getX());
int y = (int) Math.round(location.getY());
int z = (int) Math.round(location.getZ());
return index.getTriggers(x, z)
.stream()
.filter(trigger -> trigger.isTriggered(x, y, z))
.toList();
}
}
@@ -0,0 +1,84 @@
package com.eternalcode.combat.border;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
class BorderTriggerIndexBucket {
/**
* Represents "00000000 00000000 00000000 00000000 11111111 11111111 11111111 11111111" bit mask.
* This allows removing left bits when used with AND bit operator.
* */
private static final long LEFT_INT_MASK = 0xFFFFFFFFL;
/**
* Allows packing the coordination on one axis from 256 into 1
* Why? - We divide the world into fragments of size 256x256
* Why it is 8? Because when you shift bits, then the value will be smaller / bigger in scale 2^n
* E.g `2^4 = 16` (standard minecraft chunk size) in our case, it is `2^8 = 256`
*/
private static final int CHUNK_SHIFT = 8;
private final Map<Long, Set<BorderTrigger>> index = new HashMap<>();
private BorderTriggerIndexBucket() {
}
Set<BorderTrigger> getTriggers(int x, int z) {
long position = packChunk(x >> CHUNK_SHIFT, z >> CHUNK_SHIFT);
return this.index.getOrDefault(position, Set.of());
}
static BorderTriggerIndexBucket create(Collection<BorderTrigger> triggers) {
return new BorderTriggerIndexBucket().with(triggers);
}
private BorderTriggerIndexBucket with(Collection<BorderTrigger> triggers) {
for (BorderTrigger trigger : triggers) {
withTrigger(trigger);
}
return this;
}
private void withTrigger(BorderTrigger trigger) {
int minX = trigger.triggerMin().x() >> CHUNK_SHIFT;
int minZ = trigger.triggerMin().z() >> CHUNK_SHIFT;
int maxX = trigger.triggerMax().x() >> CHUNK_SHIFT;
int maxZ = trigger.triggerMax().z() >> CHUNK_SHIFT;
int startX = Math.min(minX, maxX);
int startZ = Math.min(minZ, maxZ);
int endX = Math.max(minX, maxX);
int endZ = Math.max(minZ, maxZ);
for (int chunkX = startX; chunkX <= endX; chunkX++) {
for (int chunkZ = startZ; chunkZ <= endZ; chunkZ++) {
long packed = packChunk(chunkX, chunkZ);
this.index.computeIfAbsent(packed, key -> new HashSet<>())
.add(trigger);
}
}
}
/**
* Packs two ints into long value.
* <p>
* For example for values:
* <ul>
* <li>X - 00000000 00000000 00000000 00000001 </li>
* <li>Z - 00000000 00000000 00000000 00000111 </li>
* </ul>
* This method will return:<br>
* 00000000 00000000 00000000 00000111 00000000 00000000 00000000 00000001
* </p>
* @param bigChunkX right int to pack
* @param bigChunkZ left int to pack
*/
private static long packChunk(int bigChunkX, int bigChunkZ) {
return (long) bigChunkX & LEFT_INT_MASK | ((long) bigChunkZ & LEFT_INT_MASK) << Integer.SIZE;
}
}
@@ -0,0 +1,18 @@
package com.eternalcode.combat.border.animation;
import java.awt.Color;
public final class BorderColorUtil {
private BorderColorUtil() {
}
public static Color xyzToRainbow(int x, int y, int z) {
float hue = (float) ((Math.sin(x * 0.05) + Math.cos(z * 0.05)) * 0.5 + 0.5);
float saturation = 1.0f;
float brightness = 0.8f + 0.2f * Math.max(0.0f, Math.min(1.0f, (float) y / 255));
return Color.getHSBColor(hue, saturation, brightness);
}
}
@@ -0,0 +1,33 @@
package com.eternalcode.combat.border.animation.block;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
public class BlockSettings extends OkaeriConfig {
@Comment("# Enable block animation?")
public boolean enabled = true;
@Comment({
"# Block type used for rendering the border",
"# Custom: RAINBOW_GLASS, RAINBOW_WOOL, RAINBOW_TERRACOTTA, RAINBOW_CONCRETE",
"# Vanilla: https://javadocs.packetevents.com/com/github/retrooper/packetevents/protocol/world/states/type/StateTypes.html"
})
public BlockType type = BlockType.RAINBOW_GLASS;
@Comment({
"# Delay between each async animation update (need restart to apply)",
"# Lower values will decrease performance but will make the animation smoother",
"# Higher values will increase performance"
})
public Duration updateDelay = Duration.ofMillis(250);
@Comment({
"# Delay between each chunk cache update (need restart to apply)",
"# Lower values will decrease performance",
"# Higher values will increase performance but may cause overlapping existing blocks (this does not modify the world)"
})
public Duration chunkCacheDelay = Duration.ofMillis(300);
}
@@ -0,0 +1,49 @@
package com.eternalcode.combat.border.animation.block;
import com.eternalcode.combat.border.BorderPoint;
import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToConcrete;
import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToGlass;
import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToTerracotta;
import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToWool;
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import java.util.Locale;
public class BlockType {
public static final BlockType RAINBOW_GLASS = new BlockType("RAINBOW_GLASS", point -> xyzToGlass(point));
public static final BlockType RAINBOW_TERRACOTTA = new BlockType("RAINBOW_TERRACOTTA", point -> xyzToTerracotta(point));
public static final BlockType RAINBOW_WOOL = new BlockType("RAINBOW_WOOL", point -> xyzToWool(point));
public static final BlockType RAINBOW_CONCRETE = new BlockType("RAINBOW_CONCRETE", point -> xyzToConcrete(point));
private final String name;
private final TypeProvider type;
private BlockType(String name, TypeProvider type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public StateType getStateType(BorderPoint point) {
return type.provide(point);
}
public static BlockType fromName(String name) {
return switch (name) {
case "RAINBOW_GLASS" -> RAINBOW_GLASS;
case "RAINBOW_WOOL" -> RAINBOW_WOOL;
case "RAINBOW_TERRACOTTA" -> RAINBOW_TERRACOTTA;
case "RAINBOW_CONCRETE" -> RAINBOW_CONCRETE;
default -> new BlockType(name, point -> StateTypes.getByName(name.toLowerCase(Locale.ROOT)));
};
}
private interface TypeProvider {
StateType provide(BorderPoint point);
}
}
Loaded 100 of 194 files, more files were not shown because too many files have changed in this diff. Show more