Compare commits

...
Author SHA1 Message Date
NotAlexNoyle 838bb16e12 Fix root Gradle artifact to package plugin shadow jar 2026-03-16 05:45:44 -04: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
Martin Sulikowski e732bdf842 GH-165 Use eternalcode commons. (#169)
* Use eternalcode commons.

* Make DurationUtil final.
2024-05-19 23:21:50 +02:00
Martin 6b6329c4a6 Make worldguard compileOnly. 2024-05-19 20:07:17 +02:00
Martin Sulikowski 7a6707b20e GH-164 Normalize gradle architecture. (#166)
* Normalize gradle architecture.

* Add missing version placeholder.
2024-05-19 20:02:29 +02:00
Martin 7a10e268bb Change api version in readme. 2024-04-30 17:57:28 +02:00
Martin adce4729f0 Release v1.2.2 2024-04-30 17:57:04 +02:00
renovate[bot] 8560f17bac GH-160 Update all dependencies (#160)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 17:27:28 +02:00
renovate[bot] d2d7a4f02c GH-159 Update all dependencies (#159)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-23 23:00:05 +02:00
Michał Wojtas cf33b23720 Include admins in combat on default (#158) 2024-04-22 14:16:59 +02:00
renovate[bot] e261e06635 GH-157 Update all dependencies (#157)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-19 21:40:27 +02:00
renovate[bot] 2856ababff GH-155 Update gradle/gradle-build-action digest to 8baac4c (#155)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-16 22:32:52 +02:00
renovate[bot] f061e1738e GH-154 Update all dependencies (#154)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-11 22:40:53 +02:00
renovate[bot] 4118983479 GH-153 Update gradle/gradle-build-action digest to fe59895 (#153)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-07 22:02:05 +02:00
renovate[bot] 988859ddbe GH-152 Update dependency checkstyle to v10.15.0 (#152)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-02 20:30:37 +02:00
renovate[bot] 305ab9e299 GH-151 Update dependency commons-io:commons-io to v2.16.0 (#151)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-02 20:29:15 +02:00
renovate[bot] 5aaf8a4f08 GH-150 Update all dependencies (#150)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-25 23:09:11 +01:00
renovate[bot] 153657c554 GH-149 Update all dependencies (#149)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-20 16:49:16 +01:00
renovate[bot] 5123176cb3 GH-148 Update dependency checkstyle to v10.14.1 (#148)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-12 23:55:31 +01:00
renovate[bot] 1665f39151 GH-142 Update all dependencies (#142)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-08 23:45:16 +01:00
Martin Sulikowski 94113ea326 Fix checkstyle. 2024-03-08 23:37:05 +01:00
renovate[bot] 7f581dbaac GH-146 Update actions/cache action to v4 (#146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-06 21:51:30 +01:00
imgbot[bot]andImgBotApp 2dd25cc27e GH-147 [ImgBot] Optimize images (#147)
/assets/readme-banner.png -- 102.61kb -> 85.78kb (16.4%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2024-03-06 21:49:34 +01:00
Martin Sulikowski 3ca562f515 Update readme banner. 2024-02-18 16:33:55 +01:00
Martin Sulikowski 383c405198 Bump Minecraft version in readme to 1.20.4. 2024-01-16 15:02:11 +01:00
Norbert Dejlich c522f7c078 Bump dependency version in README.md to 1.2.1 2024-01-16 00:10:39 +01:00
Martin Sulikowski fb6efa0646 Release v1.2.1-SNAPSHOT 2024-01-15 22:11:14 +01:00
Martin Sulikowski ad7899f10e Release v1.2.1 2024-01-15 21:51:21 +01:00
Martin Sulikowski ab5e6a7cf3 GH-141 Remove config backup system (Resolve #141) 2024-01-15 21:47:59 +01:00
Martin Sulikowski 42ae17bf8d GH-145 Add method to get region center and use it for knockback in region. (#145) 2024-01-15 21:45:27 +01:00
Martin Sulikowski 20d342d0ca Update eternalcombat-api version in readme. 2024-01-10 14:12:24 +01:00
Martin Sulikowski c4adeb1aed Fix checkstyle violations. 2024-01-09 17:59:31 +01:00
Martin Sulikowski d2d0c5b608 Release v1.2.0 2024-01-09 17:59:23 +01:00
Krystian Michalski eb1febab85 GH-144 Fix fight tag for admin. (#144) 2024-01-09 17:28:14 +01:00
Krystian Michalski 8153ba55ab GH-143 Fix region teleport by example: Chorus, EnderPearl. (#143) 2023-12-27 13:11:49 +01:00
renovate[bot]andMartin Sulikowski 89784f3412 GH-135 Update all dependencies (#135)
* Update all dependencies

* Revert checkstyle.

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Martin Sulikowski <vLuckyyy.biznes@gmail.com>
2023-12-24 12:03:27 +01:00
renovate[bot] 49e82385d4 GH-136 Update all dependencies to v4 (#136)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-24 12:02:54 +01:00
IgorandOsnixer 190034f5aa GH-139 Prevent flying during combat feature (#139)
* Prevent flying during combat feature

* add check is player flying and replace google cache -> caffeine cache

---------

Co-authored-by: Osnixer <Osnixeroo@gmail.com>
2023-12-24 01:28:28 +01:00
Martin SulikowskiandDMK 1db744c4ec GH-132 Add option to exclude admins from combat. (#132)
* Add option to exclude admins from combat.

* revert hashmap

* Update eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightManager.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Fix

* merge master

---------

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>
2023-11-20 23:54:05 +01:00
Michał WojtasandDMK 94d7baf791 GH-134 Remove damage fields from FightTag (#134)
* Add custom Causes, fix code for remove items from drop, remove fields from fightTag, fix issues

* Fix air errors

* Code styles fixes

* Update eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

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

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

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

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/LogoutService.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/LogoutService.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Fix compile error

* Change logoutManager to logoutService var name. Remove unused dependency

* Follow Rollczi's suggestion

---------

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>
2023-11-19 22:01:51 +01:00
renovate[bot] c25af3f6d3 GH-133 Update all dependencies (#133)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-19 20:48:55 +01:00
renovate[bot] 3b38c82b47 GH-128 Update all dependencies (#128)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 22:53:57 +01:00
Martin Sulikowski 526f831fcb Add SpigotMC group to renovate.json configuration 2023-11-13 22:49:55 +01:00
Martin Sulikowski e4151cbbe0 Enable major/minor separation and pruning stale branches in Renovate 2023-11-13 22:39:55 +01:00
Martin Sulikowski 47d9fed823 GH-126 Central Dependency Management using toml file. (#126)
* Centralized Dependency and Project Configuration Management.

* Fix

* Move to buildSrc
2023-10-21 15:10:15 +02:00
Martin Sulikowski 0890b2abbd Fix CI to follow new path in module. 2023-10-19 19:38:56 +02:00
Martin Sulikowski be99a8646d GH-125 Add info about developer api. (#125) 2023-10-19 19:32:44 +02:00
renovate[bot] b58266c868 GH-103 Update all dependencies (#103)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-19 19:26:23 +02:00
DMK eaa02a4166 GH-115 Fix death cause (#119) 2023-10-19 19:26:01 +02:00
Martin Sulikowski 08e99ded13 Disable custom effects default. 2023-10-15 13:03:48 +02:00
failutee e867e7e697 GH-123 Add null-check for DropResult (#123) 2023-10-15 00:58:22 +02:00
181 changed files with 6012 additions and 2752 deletions

No files matched your search

+1 -1
View File
@@ -18,7 +18,7 @@ We expect all contributors to follow our [code of conduct](CODE_OF_CONDUCT.md).
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.
possible, including the version of Minecraft and the plugin you are using, as well as any error messagesSettings or logs.
### Compatibility
+6 -7
View File
@@ -4,7 +4,6 @@ on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
@@ -16,14 +15,14 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v5
- name: Set up JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@v3.3.1
uses: actions/cache@v4
with:
path: ~/.gradle/caches
key: >-
@@ -33,11 +32,11 @@ jobs:
- name: Make gradlew executable
run: chmod +x gradlew
- name: Build with Gradle
uses: gradle/gradle-build-action@e3426b1f20c7262e0fed7b855aae1325633af3b4
uses: gradle/gradle-build-action@093dfe9d598ec5a42246855d09b49dc76803c005
with:
arguments: shadowJar
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.2
uses: actions/upload-artifact@v4
with:
name: 'Successfully build EternalCombat'
path: build/libs/*.jar
path: eternalcombat-plugin/build/libs/*.jar
+2 -2
View File
@@ -9,10 +9,10 @@ jobs:
environment: deployment
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Set up JDK
uses: actions/setup-java@v3
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 17
+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"
}
}
+135 -28
View File
@@ -1,47 +1,154 @@
<div align="center">
![](/assets/readme-banner.png)
![Readme banner](https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/readme-banner.png?raw=true)
[![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)
[![Available on SpigotMC](https://raw.githubusercontent.com/vLuckyyy/badges/main/available-on-spigotmc.svg)](https://www.spigotmc.org/resources/eternalcombat-%E2%9C%94%EF%B8%8F-enchance-your-combat-system-with-eternalcombat.109056/)
[![Available on Modrinth](https://raw.githubusercontent.com/vLuckyyy/badges/main/avaiable-on-modrinth.svg)](https://modrinth.com/plugin/eternalcombat)
[![Available on Hangar](https://raw.githubusercontent.com/vLuckyyy/badges/main/avaiable-on-hangar.svg)](https://hangar.papermc.io/EternalCodeTeam/eternalcombat)
[![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)
[![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/)
[![Chat on Discord](https://raw.githubusercontent.com/vLuckyyy/badges/main//chat-with-us-on-discord.svg)](https://discord.com/invite/FQ7jmGBd6c)
[![Read the Docs](https://raw.githubusercontent.com/vLuckyyy/badges/main/read-the-documentation.svg)](https://docs.eternalcode.pl/eternalcombat/introduction)
[![Available on BStats](https://raw.githubusercontent.com/vLuckyyy/badges/main/available-on-bstats.svg)](https://bstats.org/plugin/bukkit/EternalCombat/17803)
</div>
### Information
EternalCombat has been tested on Minecraft versions 1.17.1 to 1.20.1, but it should work on most other versions. If you
encounter any compatibility issues, please report them in the
[Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). The plugin requires Java 17 or later, so please
make sure it is installed on your server.
## EternalCombat requires PacketEvents plugin to work.
You can download it from [here](https://modrinth.com/plugin/packetevents)
## Building
Build EternalCombat using:
EternalCombat 2.0 has been tested on Minecraft versions **1.17.1 to 1.21.8**, but it should work seamlessly on most
other versions too.
If you run into any compatibility issues, please let us know in
the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues).
The plugin requires **Java 17 or later**, so
ensure your server is ready.
Ready for action?
Install EternalCombat and dive in now!
`./gradlew shadowJar`
### How Does EternalCombat Work?
Take your server’s PvP experience to epic heights with **EternalCombat 2.0**! Our robust combat logging system ensures
fair, heart-pounding battles that keep players on their toes. Here’s a rundown of what makes it stand out:
- **Combat Logging**
No more dodging fights by logging out! Once players are in combat, they’re committed until the showdown ends. Watch it
in action:
![Combat log anti logout feature](https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/combatlog.gif?raw=true)
- **Spawn Protection (Configurable)**
Stop players from fleeing to safety! Block access to spawn or safe zones during combat – tweak it to fit your server’s
rules. See how it works:
![Border around protected region](https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/border.gif?raw=true)
- **Crystal PvP support**
Engage in intense Crystal PvP battles without worrying about players logging out mid-fight! EternalCombat keeps
everyone in the game until the last anchor hit. Check it out:
![Crystal PvP showcase](https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/crystals.gif?raw=true)
- **Fully Customizable Combat**
Tailor the combat experience to your liking with a ton of options! From disabling elytra to setting drop rates for
defeated players, you’re in control. Here’s what you can tweak:
| Feature | Description |
|----------------------|-----------------------------------------------------------------|
| Elytra & Inventory | Disable elytra or inventory access during combat. |
| Commands | Whitelist or blacklist specific commands in combat. |
| Damage & Projectiles | Customize damage causes and projectile tagging settings. |
| Ender Pearls | Add cooldowns to pearl usage in fights. |
| Block Placement | Enable or disable block placement during combat. |
| Drop Rates | Set a percentage of items dropped by defeated players. |
| Temporary Effects | Apply special effects to players in combat for added intensity. |
| And More! | Dive into the config for even more fine-tuning options! |
Curious for more? Check out our quick and exciting YouTube presentation [here](https://youtu.be/5pELO5B0Hhk) to see
EternalCombat in full swing and learn why it’s a game-changer for your server!
### Permissions for EternalCombat
- `eternalcombat.status` - Allows to check if the player is in combat `/combatlog status <player>`
- `eternalcombat.tag` - Allows to create fights between two
players `/combatlog tag <first_player> <optional_second_player>`
- `eternalcombat.untag` - Allows to remove player from fight `/combatlog untag <player>`
- `eternalcombat.reload` - Allows to reload plugin `/combatlog reload`
- `eternalcombat.receiveupdates` - Allows you to receive notifications about the new version of the plugin when
attaching
Control who can use EternalCombat’s powerful features with these permissions:
| Permission | Description |
|--------------------------------|--------------------------------------------------------------------------|
| `eternalcombat.status` | Check a player’s combat status with `/combatlog status <player>`. |
| `eternalcombat.tag` | Start a fight between players using `/combatlog tag <player> [player2]`. |
| `eternalcombat.untag` | Remove a player from combat with `/combatlog untag <player>`. |
| `eternalcombat.reload` | Reload the plugin with `/combatlog reload`. |
| `eternalcombat.receiveupdates` | Receive notifications about new plugin versions on join. |
| `eternalcombat.bypass` | Bypass combat tagging, need's `excludeAdminsFromCombat` to be enabled |
## PlaceholderAPI
EternalCombat 2.0 fully supports **PlaceholderAPI**, letting you integrate placeholders into other compatible plugins.
Follow the [PlaceholderAPI instructions](https://wiki.placeholderapi.com/users/) to get started.
Here are the available
placeholders:
| Placeholder | Description |
|-------------------------------------|---------------------------------------------------------------|
| `%eternalcombat_opponent%` | Returns the name of the player you’re fighting. |
| `%eternalcombat_opponent_health%` | Returns the opponent’s health in `00.00` format. |
| `%eternalcombat_remaining_seconds%` | Returns seconds remaining until the player exits combat. |
| `%eternalcombat_remaining_millis%` | Returns milliseconds remaining until the player exits combat. |
If a player isn’t in combat, placeholders return an empty string.
If combat wasn’t triggered by another player,
opponent-related placeholders will also return empty.
### Developer API
#### 1. Add repository:
With Gradle:
```kts
maven {
url = uri("https://repo.eternalcode.pl/releases")
}
```
With Maven:
```xml
<repository>
<id>eternalcode-reposilite-releases</id>
<name>EternalCode Repository</name>
<url>https://repo.eternalcode.pl/releases</url>
</repository>
```
#### 2. Add dependency:
With Gradle:
```kts
compileOnly("com.eternalcode:eternalcombat-api:2.3.0-SNAPSHOT")
```
With Maven:
```xml
<dependency>
<groupId>com.eternalcode</groupId>
<artifactId>eternalcombat-api</artifactId>
<version>2.3.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
```
### Contributing
We welcome all contributions to the EternalCombat project! Please check out [contributing](.github/CONTRIBUTING.md) for
more information on how to contribute and our [code of conduct](./.github/CODE_OF_CONDUCT.md)
We’d love your help to make EternalCombat even better!
Check out our [contributing guide](https://github.com/EternalCodeTeam/EternalCombat/blob/master/.github/CONTRIBUTING.md) for
details on how to get involved and our [code of conduct](https://github.com/EternalCodeTeam/EternalCombat/blob/master/.github/CODE_OF_CONDUCT.md).
### Reporting Issues
If you find an issue with the plugin, please report it in
the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). Please provide as much information as
possible, including the version of Minecraft and the plugin you are using, as well as any error messages or logs
Found a bug?
Report it in the [Issues tab](https://github.com/eternalcodeteam/eternalcombat/issues).
Please include as much detail as possible, like your Minecraft and plugin
versions, along with any error messages or logs.
Ready to transform your server’s combat experience?
Download EternalCombat 2.0 now and let the battles begin!
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 188 KiB

+47
View File
@@ -0,0 +1,47 @@
plugins {
java
eclipse
id("xyz.jpenilla.run-paper") version "2.2.3" apply false
}
tasks.jar {
enabled = false
}
val pluginJar by tasks.registering(Copy::class) {
dependsOn(":eternalcombat-plugin:shadowJar")
from(layout.projectDirectory.file("eternalcombat-plugin/build/libs/EternalCombat v${project.version}.jar"))
into(layout.buildDirectory.dir("libs"))
rename { "EternalCombat-${project.version}.jar" }
}
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)
}
}
+4
View File
@@ -0,0 +1,4 @@
plugins { `kotlin-dsl` }
repositories { gradlePluginPortal(); mavenCentral() }
kotlin { jvmToolchain(17) }
+38
View File
@@ -0,0 +1,38 @@
object Versions {
const val SPIGOT_API = "1.17.1-R0.1-SNAPSHOT"
const val JUNIT_JUPITER_API = "5.13.4"
const val JUNIT_JUPITER_PARAMS = "5.13.4"
const val JUNIT_JUPITER_ENGINE = "5.13.4"
const val JETBRAINS_ANNOTATIONS = "26.0.2-1"
const val ETERNALCODE_COMMONS = "1.3.1"
const val MULTIFICATION = "1.2.2"
const val PACKETS_EVENTS = "2.9.5"
const val ADVENTURE_PLATFORM_BUKKIT = "4.4.1"
const val ADVENTURE_API = "4.24.0"
const val LITE_COMMANDS = "3.10.5"
const val OKAERI_CONFIGS_YAML_BUKKIT = "5.0.6"
const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.13"
const val OKAERI_CONFIGS_SERDES_BUKKIT = "5.0.13"
const val GIT_CHECK = "1.0.0"
const val CAFFEINE = "3.2.2"
const val B_STATS_BUKKIT = "3.1.0"
const val WORLD_GUARD_BUKKIT = "7.0.14"
const val PLACEHOLDER_API = "2.11.6"
const val PAPERLIB = "1.0.8"
}
@@ -0,0 +1,19 @@
plugins {
`java-library`
}
dependencies {
testImplementation("org.spigotmc:spigot-api:${Versions.SPIGOT_API}")
testImplementation("org.junit.jupiter:junit-jupiter-api:${Versions.JUNIT_JUPITER_API}")
testImplementation("org.junit.jupiter:junit-jupiter-params:${Versions.JUNIT_JUPITER_PARAMS}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${Versions.JUNIT_JUPITER_ENGINE}")
}
tasks.test {
useJUnitPlatform()
}
sourceSets.test {
java.setSrcDirs(listOf("test"))
resources.setSrcDirs(emptyList<String>())
}
@@ -0,0 +1,16 @@
plugins {
`java-library`
}
group = "com.eternalcode"
version = "2.3.0-SNAPSHOT"
tasks.compileJava {
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
options.encoding = "UTF-8"
options.release = 17
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}
@@ -0,0 +1,34 @@
plugins {
`java-library`
`maven-publish`
}
group = "com.eternalcode"
version = "2.3.0-SNAPSHOT"
java {
withSourcesJar()
withJavadocJar()
}
publishing {
publications {
create<MavenPublication>("maven") {
artifactId = "eternalcombat-api"
from(project.components["java"])
}
}
repositories {
mavenLocal()
maven {
name = "eternalcodeReleases"
url = uri("https://repo.eternalcode.pl/releases")
credentials {
username = System.getenv("ETERNAL_CODE_MAVEN_USERNAME")
password = System.getenv("ETERNAL_CODE_MAVEN_PASSWORD")
}
}
}
}
@@ -0,0 +1,18 @@
plugins {
`java-library`
}
repositories {
mavenCentral()
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.eternalcode.pl/releases")
maven("https://repo.eternalcode.pl/snapshots")
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/")
}
+4 -104
View File
@@ -1,109 +1,9 @@
plugins {
`java-library`
checkstyle
plugins { `java-library` }
id("maven-publish")
}
group = "com.eternalcode"
version = "1.1.1"
checkstyle {
toolVersion = "10.12.2"
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
maxErrors = 0
maxWarnings = 0
}
repositories {
mavenCentral()
maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") }
maven { url = uri("https://papermc.io/repo/repository/maven-public/") }
maven { url = uri("https://repo.eternalcode.pl/releases") }
maven { url = uri("https://storehouse.okaeri.eu/repository/maven-public/") }
maven { url = uri("https://repo.panda-lang.org/releases") }
maven { url = uri("https://maven.enginehub.org/repo/") }
}
java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } }
dependencies {
// Spigot api
compileOnlyApi("org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT")
// kyori
api("net.kyori:adventure-platform-bukkit:4.3.0")
api("net.kyori:adventure-text-minimessage:4.14.0")
// litecommands
api("dev.rollczi.litecommands:bukkit-adventure:2.8.9")
// Okaeri configs
api("eu.okaeri:okaeri-configs-yaml-bukkit:5.0.0-beta.5")
api("eu.okaeri:okaeri-configs-serdes-commons:5.0.0-beta.5")
api("eu.okaeri:okaeri-configs-serdes-bukkit:5.0.0-beta.5")
// Panda utilities
api("org.panda-lang:panda-utilities:0.5.2-alpha")
// GitCheck
api("com.eternalcode:gitcheck:1.0.0")
// commons
api("commons-io:commons-io:2.13.0")
// bstats
api("org.bstats:bstats-bukkit:3.0.2")
// worldguard
compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.9")
// tests
testImplementation("org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.getByName<Test>("test") {
useJUnitPlatform()
}
// Publish
java {
withSourcesJar()
withJavadocJar()
}
publishing {
publications {
create<MavenPublication>("maven") {
artifactId = "eternalcombat-api"
from(project.components["java"])
}
}
}
publishing {
repositories {
mavenLocal()
maven {
name = "eternalcodeReleases"
url = uri("https://repo.eternalcode.pl/releases")
credentials {
username = System.getenv("ETERNAL_CODE_MAVEN_USERNAME")
password = System.getenv("ETERNAL_CODE_MAVEN_PASSWORD")
}
}
}
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();
}
@@ -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,119 +0,0 @@
package com.eternalcode.combat.config;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
public class ConfigBackupService {
private static final String BACKUP_FOLDER_NAME = "backup";
private static final String BACKUP_FILE_EXTENSION = ".bak";
private final File dataFolder;
public ConfigBackupService(File dataFolder) {
this.dataFolder = dataFolder;
}
public void createBackup() {
File backupFolder = new File(this.dataFolder, BACKUP_FOLDER_NAME);
if (!backupFolder.exists()) {
backupFolder.mkdirs();
}
LocalDateTime currentDateTime = LocalDateTime.now();
String backupFolderName = BACKUP_FOLDER_NAME + "_" + currentDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm"));
File currentBackupFolder = new File(backupFolder, backupFolderName);
if (!currentBackupFolder.exists()) {
currentBackupFolder.mkdirs();
}
this.copyFolderContents(this.dataFolder, currentBackupFolder);
this.deleteIfOlderDirectory(backupFolder);
}
private void copyFolderContents(File sourceFolder, File targetFolder) {
if (!sourceFolder.exists() || !sourceFolder.isDirectory()) {
return;
}
if (!targetFolder.exists()) {
boolean targetFolderCreated = targetFolder.mkdirs();
if (!targetFolderCreated) {
return;
}
}
File[] filesToBackup = sourceFolder.listFiles();
if (filesToBackup == null) {
return;
}
for (File file : filesToBackup) {
if (file.isDirectory() && !file.getName().equals(BACKUP_FOLDER_NAME)) {
File subFolder = new File(targetFolder, file.getName());
this.copyFolderContents(file, subFolder);
continue;
}
if (file.isFile() && !file.getName().endsWith(BACKUP_FILE_EXTENSION)) {
File backupFile = new File(targetFolder, file.getName() + BACKUP_FILE_EXTENSION);
this.copyToBackupFile(file, backupFile);
}
}
}
private void copyToBackupFile(File targetFolder, File path) {
try {
Files.copy(targetFolder.toPath(), path.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException exception) {
exception.printStackTrace();
}
}
// delete backup folders older than X days
private void deleteIfOlderDirectory(File backupFolder) {
File[] backupFolders = backupFolder.listFiles(File::isDirectory);
if (backupFolders == null) {
return;
}
for (File folder : backupFolders) {
String folderName = folder.getName();
if (!folderName.startsWith(BACKUP_FOLDER_NAME)) {
continue;
}
try {
LocalDate folderDate = LocalDate.parse(folderName.substring(BACKUP_FOLDER_NAME.length() + 1, folderName.lastIndexOf("_")));
LocalTime folderTime = LocalTime.parse(folderName.substring(folderName.lastIndexOf("_") + 1).replace("-", ":"));
LocalDateTime folderDateTime = LocalDateTime.of(folderDate, folderTime);
LocalDateTime currentDateTime = LocalDateTime.now();
long hours = ChronoUnit.HOURS.between(folderDateTime, currentDateTime);
if (hours > 72) {
FileUtils.deleteDirectory(folder);
}
}
catch (DateTimeParseException | IOException exception) {
exception.printStackTrace();
}
}
}
}
@@ -1,49 +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 ConfigBackupService backupService;
private final Set<OkaeriConfig> configs = new HashSet<>();
public ConfigService(ConfigBackupService backupService) {
this.backupService = backupService;
}
public <T extends OkaeriConfig> T create(Class<T> config, File file) {
this.backupService.createBackup();
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,224 +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("# 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("# 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,80 +0,0 @@
package com.eternalcode.combat.drop;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTag;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
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
void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
DropType dropType = this.dropSettings.dropType;
if (dropType == DropType.UNCHANGED || !this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
FightTag fightTag = this.fightManager.getTag(player.getUniqueId());
List<ItemStack> drops = event.getDrops();
Drop drop = Drop.builder()
.player(player)
.killer(player.getKiller())
.fightTag(fightTag)
.deathCause(fightTag.getDeathCause())
.droppedItems(drops)
.droppedExp(player.getTotalExperience())
.build();
DropResult result = this.dropManager.modify(dropType, drop);
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;
}
}
@@ -1,10 +0,0 @@
package com.eternalcode.combat.fight;
public enum FightDeathCause {
UNKNOWN,
ESCAPE,
KILLED_BY_PLAYER,
KILLED_BY_ENTITY
}
@@ -4,69 +4,26 @@ 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.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);
@ApiStatus.Experimental
FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger);
return !fightTag.isExpired();
}
FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag);
public FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag) {
FightUntagEvent event = this.eventCaller.publishEvent(new FightUntagEvent(player, causeOfUnTag));
if (event.isCancelled()) {
return event;
}
FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag);
this.fights.remove(player);
return event;
}
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,58 +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();
private double healthBeforeDeath;
private FightDeathCause deathCause;
Duration getRemainingDuration();
FightTag(UUID personToAddCombat, Instant endOfCombatLog) {
this.taggedPlayer = personToAddCombat;
this.endOfCombatLog = endOfCombatLog;
boolean isExpired();
this.healthBeforeDeath = 0;
this.deathCause = FightDeathCause.UNKNOWN;
}
Instant getEndOfCombatLog();
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;
}
public double getHealthBeforeDeath() {
return this.healthBeforeDeath;
}
public void setHealthBeforeDeath(double health) {
this.healthBeforeDeath = health;
}
public FightDeathCause getDeathCause() {
return this.deathCause;
}
public void setDeathCause(FightDeathCause deathCause) {
this.deathCause = deathCause;
}
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,142 +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 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
public 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
public 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
public 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,59 +0,0 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.fight.FightDeathCause;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTag;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class FightDeathCauseController implements Listener {
private final FightManager fightManager;
public FightDeathCauseController(FightManager fightManager) {
this.fightManager = fightManager;
}
@EventHandler
public void onEntityDamage(EntityDamageByEntityEvent event) {
Entity victimEntity = event.getEntity();
Entity damagerEntity = event.getDamager();
if (!(victimEntity instanceof Player victim)) {
return;
}
if (!this.fightManager.isInCombat(victim.getUniqueId())) {
return;
}
if (victim.getHealth() > event.getFinalDamage()) {
return;
}
FightTag fightTag = this.fightManager.getTag(damagerEntity.getUniqueId());
if (damagerEntity instanceof Player) {
fightTag.setDeathCause(FightDeathCause.KILLED_BY_PLAYER);
return;
}
fightTag.setDeathCause(FightDeathCause.KILLED_BY_ENTITY);
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
FightTag fightTag = this.fightManager.getTag(player.getUniqueId());
fightTag.setDeathCause(FightDeathCause.ESCAPE);
}
}
@@ -1,45 +0,0 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTag;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import panda.utilities.text.Formatter;
public class FightEscapeController implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public FightEscapeController(FightManager fightManager, PluginConfig config, NotificationAnnouncer announcer) {
this.fightManager = fightManager;
this.config = config;
this.announcer = announcer;
}
@EventHandler
void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
Formatter formatter = new Formatter()
.register("{PLAYER}", player.getName());
String format = formatter.format(this.config.messages.playerLoggedOutDuringCombat);
this.announcer.broadcast(format);
FightTag fightTag = this.fightManager.getTag(player.getUniqueId());
fightTag.setHealthBeforeDeath(player.getHealth());
player.setHealth(0.0); // Untagged in PlayerDeathEvent
}
}
@@ -1,38 +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 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;
public FightUnTagController(FightManager fightManager, PluginConfig config) {
this.fightManager = fightManager;
this.config = config;
}
@EventHandler
void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
Player killer = player.getKiller();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
CauseOfUnTag causeOfUnTag = (killer == null ? CauseOfUnTag.DEATH_BY_PLAYER : CauseOfUnTag.DEATH);
this.fightManager.untag(player.getUniqueId(), causeOfUnTag);
if (killer != null && this.config.settings.shouldReleaseAttacker) {
this.fightManager.untag(killer.getUniqueId(), CauseOfUnTag.ATTACKER_RELEASE);
}
}
}
@@ -1,7 +1,5 @@
package com.eternalcode.combat.drop;
package com.eternalcode.combat.fight.drop;
import com.eternalcode.combat.fight.FightDeathCause;
import com.eternalcode.combat.fight.FightTag;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -15,18 +13,13 @@ public class Drop {
private final Player player;
private final Player killer;
private final FightTag fightTag;
private final FightDeathCause deathCause;
private final List<ItemStack> droppedItems;
private final int droppedExp;
private Drop(Player player, Player killer, FightTag fightTag, FightDeathCause deathCause, List<ItemStack> droppedItems, int droppedExp) {
private Drop(Player player, Player killer, List<ItemStack> droppedItems, int droppedExp) {
this.player = player;
this.killer = killer;
this.fightTag = fightTag;
this.deathCause = deathCause;
this.droppedItems = droppedItems;
this.droppedExp = droppedExp;
}
@@ -43,14 +36,6 @@ public class Drop {
return this.killer;
}
public FightTag getFightTag() {
return this.fightTag;
}
public FightDeathCause getDeathCause() {
return this.deathCause;
}
public boolean hasKiller() {
return this.killer != null;
}
@@ -67,8 +52,6 @@ public class Drop {
private Player player;
private Player killer;
private FightTag fightTag;
private FightDeathCause deathCause;
private List<ItemStack> droppedItems;
private int droppedExp;
@@ -82,16 +65,6 @@ public class Drop {
return this;
}
public Builder fightTag(@NotNull FightTag fightTag) {
this.fightTag = fightTag;
return this;
}
public Builder deathCause(@NotNull FightDeathCause deathCause) {
this.deathCause = deathCause;
return this;
}
public Builder droppedItems(@NotNull List<ItemStack> droppedItems) {
this.droppedItems = new ArrayList<>(droppedItems);
return this;
@@ -106,8 +79,6 @@ public class Drop {
return new Drop(
this.player,
this.killer,
this.fightTag,
this.deathCause,
this.droppedItems,
this.droppedExp
);
@@ -0,0 +1,16 @@
package com.eternalcode.combat.fight.drop;
import java.util.List;
import java.util.UUID;
import org.bukkit.inventory.ItemStack;
public interface DropKeepInventoryService {
List<ItemStack> nextItems(UUID uuid);
boolean hasItems(UUID uuid);
void addItems(UUID uuid, List<ItemStack> item);
void addItem(UUID uuid, ItemStack item);
}
@@ -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,7 @@
package com.eternalcode.combat.fight.event;
public enum CancelTagReason {
TAGOUT
}
@@ -1,7 +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,
COMMAND
/**
* 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,10 +1,38 @@
package com.eternalcode.combat.fight.event;
public enum CauseOfUnTag {
/**
* The combat tag expired after a set duration.
*/
TIME_EXPIRED,
/**
* The player died, which resulted in the removal of the combat tag.
*/
DEATH,
/**
* The player was killed by another player, causing the combat tag to be removed.
*/
DEATH_BY_PLAYER,
/**
* The player logged out, triggering the removal of the combat tag.
*/
LOGOUT,
/**
* A command was executed to remove the player's combat tag.
*/
COMMAND,
ATTACKER_RELEASE
/**
* The attacker released the player, ending the combat and removing the tag.
*/
ATTACKER_RELEASE,
/**
* A custom cause, typically defined by external plugins or systems.
*/
CUSTOM
}
@@ -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,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.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
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 = CacheBuilder.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,32 +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());
}
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,60 +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.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 playerLocation = player.getLocation().subtract(player.getWorld().getSpawnLocation());
double distance = playerLocation.distance(player.getWorld().getSpawnLocation());
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);
}
}
}
@@ -1,10 +1,18 @@
package com.eternalcode.combat.region;
import java.util.Collection;
import java.util.Optional;
import org.bukkit.Location;
import org.bukkit.World;
@FunctionalInterface
public interface RegionProvider {
boolean isInRegion(Location location);
Optional<Region> getRegion(Location location);
default boolean isInRegion(Location location) {
return this.getRegion(location).isPresent();
}
Collection<Region> getRegions(World world);
}
@@ -1,32 +0,0 @@
package com.eternalcode.combat.region;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldguard.WorldGuard;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
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));
}
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,25 +0,0 @@
package com.eternalcode.combat.util;
import java.time.Duration;
public final class DurationUtil {
private DurationUtil() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
public static String format(Duration duration) {
return format(duration, true);
}
public static String format(Duration duration, boolean removeMillis) {
if (removeMillis) {
duration = Duration.ofSeconds(duration.toSeconds());
}
return duration.toString()
.substring(2)
.replaceAll("(\\d[HMS])(?!$)", "$1 ")
.toLowerCase();
}
}
@@ -1,71 +0,0 @@
package com.eternalcode.combat.util;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.ToIntFunction;
public class InventoryUtil {
private static final Random RANDOM = new Random();
private InventoryUtil() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
public static <T> int calculateItemsToDelete(int percent, Collection<T> objectList, ToIntFunction<? super T> mapper) {
return MathUtil.getRoundedCountFromPercentage(percent, MathUtil.sum(objectList, mapper));
}
public static RemoveItemResult removeRandomItems(List<ItemStack> list, int itemsToDelete) {
List<ItemStack> restItems = new ArrayList<>(list);
Map<Integer, ItemStack> removedItems = new HashMap<>();
for (int i = 0; i < itemsToDelete; i++) {
if (restItems.isEmpty()) {
break;
}
int randomIndex = RANDOM.nextInt(restItems.size());
ItemStack item = restItems.get(randomIndex);
int newAmount = item.getAmount() - 1;
if (newAmount <= 0) {
restItems.remove(randomIndex);
incrementItem(removedItems, randomIndex, item);
continue;
}
item.setAmount(newAmount);
incrementItem(removedItems, randomIndex, item);
}
return new RemoveItemResult(restItems, new ArrayList<>(removedItems.values()));
}
// TODO: kinda shitty, but no time for a proper solution now
private static void incrementItem(Map<Integer, ItemStack> items, int index, ItemStack itemStack) {
ItemStack itemInList = items.get(index);
if (itemInList == null) {
ItemStack cloned = itemStack.clone();
cloned.setAmount(1);
items.put(index, cloned);
return;
}
if (!itemInList.isSimilar(itemStack)) {
throw new IllegalStateException("Items are not similar!");
}
itemInList.setAmount(itemInList.getAmount() + 1);
}
}
@@ -1,18 +0,0 @@
package com.eternalcode.combat.util.legacy;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
public final class Legacy {
private static final LegacyComponentSerializer AMPERSAND_SERIALIZER = LegacyComponentSerializer.builder()
.character('&')
.hexColors()
.useUnusualXRepeatedCharacterHexFormat()
.build();
public static Component component(String text) {
return AMPERSAND_SERIALIZER.deserialize(text);
}
}
@@ -1,15 +0,0 @@
package com.eternalcode.combat.util.legacy;
import net.kyori.adventure.text.Component;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
public final class LegacyColorProcessor implements UnaryOperator<Component> {
@Override
public Component apply(Component component) {
return component.replaceText(builder -> builder.match(Pattern.compile(".*"))
.replacement((matchResult, builder1) -> Legacy.component(matchResult.group())));
}
}
@@ -1,53 +0,0 @@
package com.eternalcode.combat.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
class DurationUtilTest {
private static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{ "3m 30s", true, Duration.ofMinutes(3).plusSeconds(30).plusMillis(500) },
{ "3m 30.5s", false, Duration.ofMinutes(3).plusSeconds(30).plusMillis(500) },
{ "59m 59s", true, Duration.ofMinutes(59).plusSeconds(59) },
{ "1h 1m", true, Duration.ofHours(1).plusMinutes(1).plusMillis(1) },
{ "0s", true, Duration.ofMillis(500) },
{ "0.5s", false, Duration.ofMillis(500) },
{ "0s", true, Duration.ofMillis(500) },
{ "0s", false, Duration.ZERO },
{ "30s", true, Duration.ofSeconds(30) },
{ "30s", false, Duration.ofSeconds(30) },
{ "30s", true, Duration.ofSeconds(30).plusMillis(500) },
{ "30.5s", false, Duration.ofSeconds(30).plusMillis(500) },
{ "1h 30m 30s", true, Duration.ofHours(1).plusMinutes(30).plusSeconds(30).plusMillis(500) },
{ "1h 30m 30.5s", false, Duration.ofHours(1).plusMinutes(30).plusSeconds(30).plusMillis(500) },
{ "1h 1m 1s", true, Duration.ofHours(1).plusMinutes(1).plusSeconds(1) },
{ "23h 59m 59s", true, Duration.ofHours(23).plusMinutes(59).plusSeconds(59) },
{ "23h 59m 59s", true, Duration.ofHours(23).plusMinutes(59).plusSeconds(59).plusMillis(999) },
{ "26h 3m 4s", true, Duration.ofDays(1).plusHours(2).plusMinutes(3).plusSeconds(4).plusMillis(500) },
{ "1h 1m 1s", true, Duration.ofHours(1).plusMinutes(1).plusSeconds(1).plusMillis(1) },
{ "1h 1s", true, Duration.ofHours(1).plusSeconds(1) },
{ "1m", true, Duration.ofMinutes(1).plusMillis(1) },
});
}
@ParameterizedTest
@MethodSource("data")
void testFormat(String expected, boolean removeMillis, Duration duration) {
String actual = DurationUtil.format(duration, removeMillis);
assertEquals(expected, actual);
}
}
+65 -63
View File
@@ -1,93 +1,95 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import xyz.jpenilla.runpaper.task.RunServer
plugins {
`java-library`
checkstyle
java
id("com.gradleup.shadow") version "8.3.6"
id("net.minecrell.plugin-yml.bukkit") version "0.6.0"
id("com.github.johnrengelman.shadow") version "8.1.1"
id("xyz.jpenilla.run-paper") version "2.1.0"
}
checkstyle {
toolVersion = "10.12.2"
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
maxErrors = 0
maxWarnings = 0
}
repositories {
mavenCentral()
maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") }
maven { url = uri("https://papermc.io/repo/repository/maven-public/") }
maven { url = uri("https://repo.eternalcode.pl/releases") }
maven { url = uri("https://storehouse.okaeri.eu/repository/maven-public/") }
maven { url = uri("https://repo.panda-lang.org/releases") }
maven { url = uri("https://maven.enginehub.org/repo/") }
id("xyz.jpenilla.run-paper")
}
dependencies {
implementation(project(":eternalcombat-api"))
compileOnly("io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT")
compileOnly("com.github.retrooper:packetevents-spigot:2.8.0")
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")
}
compileOnly("com.github.angeschossen:LandsAPI:7.15.20")
// EternalCode Commons (split modules)
implementation("com.eternalcode:eternalcode-commons-shared:+")
implementation("com.eternalcode:eternalcode-commons-bukkit:+")
implementation("com.eternalcode:eternalcode-commons-adventure:+")
// Other libs
implementation("net.kyori:adventure-api:4.17.0")
implementation("net.kyori:adventure-text-minimessage:4.17.0")
implementation("net.kyori:adventure-platform-bukkit:4.3.3")
implementation("dev.rollczi:litecommands-bukkit:3.10.5")
implementation("com.github.ben-manes.caffeine:caffeine:3.1.8")
implementation("org.bstats:bstats-bukkit:3.0.2")
implementation("io.papermc:paperlib:1.0.8")
implementation("com.eternalcode:multification-bukkit:1.2.2")
implementation("com.eternalcode:multification-okaeri:1.1.4")
implementation("com.eternalcode:gitcheck:1.0.0")
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")
}
java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } }
bukkit {
main = "com.eternalcode.combat.CombatPlugin"
author = "EternalCodeTeam"
apiVersion = "1.13"
prefix = "EternalCombat"
name = "EternalCombat"
softDepend = listOf("WorldGuard")
version = "${project.version}"
main = "com.eternalcode.combat.CombatPlugin"
version = project.version.toString()
apiVersion = "1.19"
authors = listOf("EternalCodeTeam")
prefix = "EternalCombat"
softDepend = listOf("Lands")
depend = listOf("PacketEvents-OG")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType<JavaCompile> {
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
options.encoding = "UTF-8"
}
tasks {
runServer {
minecraftVersion("1.19.4")
}
}
tasks.withType<ShadowJar> {
tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
archiveFileName.set("EternalCombat v${project.version}.jar")
dependsOn("checkstyleMain")
dependsOn("checkstyleTest")
dependsOn("test")
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"
).forEach { pack ->
relocate(pack, "$prefix.$pack")
"com.github.benmanes.caffeine",
"com.eternalcode.commons",
"com.eternalcode.multification",
"io.papermc"
).forEach { relocate(it, "$prefix.$it") }
}
tasks.named<RunServer>("runServer") {
minecraftVersion("1.19.4")
downloadPlugins {
url("https://github.com/retrooper/packetevents/releases/download/v2.8.0/packetevents-spigot-2.8.0.jar")
}
}
@@ -1,51 +1,69 @@
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.ConfigBackupService;
import com.eternalcode.combat.crystalpvp.RespawnAnchorListener;
import com.eternalcode.combat.crystalpvp.EndCrystalListener;
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.firework.FireworkController;
import com.eternalcode.combat.fight.knockback.KnockbackService;
import com.eternalcode.combat.fight.tagout.FightTagOutService;
import com.eternalcode.combat.fight.pearl.FightPearlService;
import com.eternalcode.combat.handler.InvalidUsageHandlerImpl;
import com.eternalcode.combat.handler.MissingPermissionHandlerImpl;
import com.eternalcode.combat.config.ConfigService;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.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.fight.drop.DropController;
import com.eternalcode.combat.fight.drop.DropKeepInventoryServiceImpl;
import com.eternalcode.combat.fight.drop.DropServiceImpl;
import com.eternalcode.combat.fight.drop.impl.PercentDropModifier;
import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier;
import com.eternalcode.combat.fight.FightTagCommand;
import com.eternalcode.combat.fight.controller.FightActionBlockerController;
import com.eternalcode.combat.fight.controller.FightDeathCauseController;
import com.eternalcode.combat.fight.controller.FightEscapeController;
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.effect.FightEffectController;
import com.eternalcode.combat.event.EventCaller;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.event.EventManager;
import com.eternalcode.combat.fight.FightManagerImpl;
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.logout.LogoutController;
import com.eternalcode.combat.fight.logout.LogoutService;
import com.eternalcode.combat.fight.pearl.FightPearlController;
import com.eternalcode.combat.fight.pearl.FightPearlManager;
import com.eternalcode.combat.fight.pearl.FightPearlServiceImpl;
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.fight.tagout.FightTagOutCommand;
import com.eternalcode.combat.notification.NoticeService;
import com.eternalcode.combat.fight.knockback.KnockbackRegionController;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.combat.updater.UpdaterNotificationController;
import com.eternalcode.combat.updater.UpdaterService;
import com.eternalcode.combat.util.legacy.LegacyColorProcessor;
import com.eternalcode.commons.adventure.AdventureLegacyColorPostProcessor;
import com.eternalcode.commons.adventure.AdventureLegacyColorPreProcessor;
import com.eternalcode.commons.bukkit.scheduler.BukkitSchedulerImpl;
import com.eternalcode.commons.scheduler.Scheduler;
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 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;
@@ -54,19 +72,23 @@ import java.util.stream.Stream;
public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
private static final String FALLBACK_PREFIX = "eternalcombat";
private static final int BSTATS_METRICS_ID = 17803;
private FightManager fightManager;
private FightPearlManager fightPearlManager;
private FightPearlService fightPearlService;
private FightTagOutService fightTagOutService;
private FightEffectService fightEffectService;
private DropManager dropManager;
private DropKeepInventoryManager dropKeepInventoryManager;
private RegionProvider regionProvider;
private PluginConfig pluginConfig;
private DropService dropService;
private DropKeepInventoryService dropKeepInventoryService;
private RegionProvider regionProvider;
private AudienceProvider audienceProvider;
private LiteCommands<CommandSender> liteCommands;
@Override
public void onEnable() {
Stopwatch started = Stopwatch.createStarted();
@@ -74,71 +96,105 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
File dataFolder = this.getDataFolder();
ConfigBackupService backupService = new ConfigBackupService(dataFolder);
ConfigService configService = new ConfigService(backupService);
ConfigService configService = new ConfigService();
EventCaller eventCaller = new EventCaller(server);
EventManager eventManager = new EventManager(this);
Scheduler scheduler = new BukkitSchedulerImpl(this);
this.pluginConfig = configService.create(PluginConfig.class, new File(dataFolder, "config.yml"));
PluginConfig pluginConfig = configService.create(PluginConfig.class, new File(dataFolder, "config.yml"));
this.fightManager = new FightManager(eventCaller);
this.fightPearlManager = new FightPearlManager(this.pluginConfig.pearl);
this.fightTagOutService = new FightTagOutService();
this.fightEffectService = new FightEffectService();
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());
this.audienceProvider = BukkitAudiences.create(this);
MiniMessage miniMessage = MiniMessage.builder()
.postProcessor(new LegacyColorProcessor())
.postProcessor(new AdventureLegacyColorPostProcessor())
.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, "eternalcombat", 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);
FightTask fightTask = new FightTask(server, pluginConfig, this.fightManager, noticeService);
this.getServer().getScheduler().runTaskTimer(this, fightTask, 20L, 20L);
new Metrics(this, 17803);
new Metrics(this, BSTATS_METRICS_ID);
Stream.of(
new PercentDropModifier(this.pluginConfig.dropSettings),
new PlayersHealthDropModifier(this.pluginConfig.dropSettings)
).forEach(this.dropManager::registerModifier);
new PercentDropModifier(pluginConfig.drop),
new PlayersHealthDropModifier(pluginConfig.drop, logoutService)
).forEach(this.dropService::registerModifier);
Stream.of(
new FightDeathCauseController(this.fightManager),
new DropController(this.dropManager, this.dropKeepInventoryManager, this.pluginConfig.dropSettings, this.fightManager),
new FightTagController(this.fightManager, this.pluginConfig, notificationAnnouncer),
new FightUnTagController(this.fightManager, this.pluginConfig),
new FightEscapeController(this.fightManager, this.pluginConfig, notificationAnnouncer),
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 FightActionBlockerController(this.fightManager, noticeService, pluginConfig, server),
new FightPearlController(pluginConfig.pearl, noticeService, this.fightManager, this.fightPearlService),
new UpdaterNotificationController(updaterService, pluginConfig, this.audienceProvider, miniMessage),
new KnockbackRegionController(noticeService, this.regionProvider, this.fightManager, knockbackService, server),
new FightEffectController(pluginConfig.effect, this.fightEffectService, this.fightManager, this.getServer()),
new FightTagOutController(this.fightTagOutService),
new FightMessageController(this.fightManager, noticeService, pluginConfig, this.getServer()),
new BorderTriggerController(borderService, () -> pluginConfig.border, fightManager, server),
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)
);
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);
@@ -151,7 +207,7 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
EternalCombatProvider.deinitialize();
if (this.liteCommands != null) {
this.liteCommands.getPlatform().unregisterAll();
this.liteCommands.unregister();
}
if (this.audienceProvider != null) {
@@ -172,8 +228,8 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
}
@Override
public FightPearlManager getFightPearlManager() {
return this.fightPearlManager;
public FightPearlService getFightPearlService() {
return this.fightPearlService;
}
@Override
@@ -187,17 +243,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,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")
@Permission("eternalcombat.reload")
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")
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,19 @@
package com.eternalcode.combat.border;
import org.jetbrains.annotations.Nullable;
public record BorderPoint(int x, int y, int z, @Nullable BorderPoint inclusive) {
public BorderPoint(int x, int y, int z) {
this(x, y, z, null);
}
public BorderPoint toInclusive() {
if (inclusive == null) {
return this;
}
return inclusive;
}
}
@@ -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,184 @@
package com.eternalcode.combat.border;
import com.eternalcode.combat.border.event.BorderHideAsyncEvent;
import com.eternalcode.combat.border.event.BorderShowAsyncEvent;
import com.eternalcode.combat.event.EventManager;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.commons.scheduler.Scheduler;
import dev.rollczi.litecommands.shared.Lazy;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
public class BorderServiceImpl implements BorderService {
private final Scheduler scheduler;
private final EventManager eventManager;
private final Supplier<BorderSettings> settings;
private final BorderTriggerIndex borderIndexes;
private final BorderActivePointsIndex activeBorderIndex = new BorderActivePointsIndex();
public BorderServiceImpl(Scheduler scheduler, Server server, RegionProvider provider, EventManager eventManager, Supplier<BorderSettings> settings) {
this.scheduler = scheduler;
this.eventManager = eventManager;
this.settings = settings;
this.borderIndexes = BorderTriggerIndex.started(server, scheduler, provider, settings);
}
@Override
public void updateBorder(Player player, Location location) {
Optional<BorderResult> result = resolveBorder(location);
String world = player.getWorld().getName();
if (result.isEmpty()) {
if (!this.activeBorderIndex.hasPoints(world, player.getUniqueId())) {
return;
}
this.clearBorder(player);
return;
}
scheduler.runAsync(() -> {
BorderResult borderResult = result.get();
Set<BorderPoint> points = borderResult.collect();
if (!points.isEmpty()) {
BorderShowAsyncEvent event = eventManager.publishEvent(new BorderShowAsyncEvent(player, points));
points = event.getPoints();
}
Set<BorderPoint> removed = this.activeBorderIndex.putPoints(world, player.getUniqueId(), points);
if (!removed.isEmpty()) {
eventManager.publishEvent(new BorderHideAsyncEvent(player, removed));
}
});
}
@Override
public void clearBorder(Player player) {
World world = player.getWorld();
UUID uniqueId = player.getUniqueId();
scheduler.runAsync(() -> {
Set<BorderPoint> removed = this.activeBorderIndex.removePoints(world.getName(), uniqueId);
if (!removed.isEmpty()) {
eventManager.publishEvent(new BorderHideAsyncEvent(player, removed));
}
});
}
@Override
public Set<BorderPoint> getActiveBorder(Player player) {
return this.activeBorderIndex.getPoints(player.getWorld().getName(), player.getUniqueId());
}
private Optional<BorderResult> resolveBorder(Location location) {
List<BorderTrigger> triggered = borderIndexes.getTriggered(location);
if (triggered.isEmpty()) {
return Optional.empty();
}
BorderLazyResult result = new BorderLazyResult();
for (BorderTrigger trigger : triggered) {
result.addLazyBorderPoints(new Lazy<>(() -> this.resolveBorderPoints(trigger, location)));
}
return Optional.of(result);
}
/* this code is ugly but is fast */
private List<BorderPoint> resolveBorderPoints(BorderTrigger trigger, Location playerLocation) {
BorderPoint borderMin = trigger.min();
BorderPoint borderMax = trigger.max();
int x = (int) Math.round(playerLocation.getX());
int y = (int) Math.round(playerLocation.getY());
int z = (int) Math.round(playerLocation.getZ());
int distanceRounded = settings.get().distanceRounded();
int realMinX = Math.max(borderMin.x(), x - distanceRounded);
int realMaxX = Math.min(borderMax.x(), x + distanceRounded);
int realMinY = Math.max(borderMin.y(), y - distanceRounded);
int realMaxY = Math.min(borderMax.y(), y + distanceRounded);
int realMinZ = Math.max(borderMin.z(), z - distanceRounded);
int realMaxZ = Math.min(borderMax.z(), z + distanceRounded);
List<BorderPoint> points = new ArrayList<>();
if (borderMin.y() >= realMinY) { // Bottom wall
for (int currentX = realMinX; currentX <= realMaxX - 1; currentX++) {
for (int currentZ = realMinZ; currentZ <= realMaxZ - 1; currentZ++) {
addPoint(points, currentX, realMinY, currentZ, playerLocation, null);
}
}
}
if (borderMax.y() <= realMaxY) { // Top wall
for (int currentX = realMinX; currentX <= realMaxX; currentX++) {
for (int currentZ = realMinZ; currentZ <= realMaxZ; currentZ++) {
BorderPoint inclusive = new BorderPoint(Math.max(realMinX, currentX - 1), realMaxY - 1, Math.max(realMinZ, currentZ - 1));
addPoint(points, currentX, realMaxY, currentZ, playerLocation, inclusive);
}
}
}
if (borderMin.x() >= realMinX) { // West wall (left)
for (int currentY = realMinY; currentY <= realMaxY - 1; currentY++) {
for (int currentZ = realMinZ; currentZ <= realMaxZ - 1; currentZ++) {
addPoint(points, realMinX, currentY, currentZ, playerLocation, null);
}
}
}
if (borderMax.x() <= realMaxX) { // East wall (right)
for (int currentY = realMinY; currentY <= realMaxY; currentY++) {
for (int currentZ = realMinZ; currentZ <= realMaxZ; currentZ++) {
BorderPoint inclusive = new BorderPoint(realMaxX - 1, Math.max(realMinY, currentY - 1), Math.max(realMinZ, currentZ - 1));
addPoint(points, realMaxX, currentY, currentZ, playerLocation, inclusive);
}
}
}
if (borderMin.z() >= realMinZ) { // North wall (front)
for (int currentX = realMinX; currentX <= realMaxX - 1; currentX++) {
for (int currentY = realMinY; currentY <= realMaxY - 1; currentY++) {
addPoint(points, currentX, currentY, realMinZ, playerLocation, null);
}
}
}
if (borderMax.z() <= realMaxZ) { // South wall (back)
for (int currentX = realMinX; currentX <= realMaxX; currentX++) {
for (int currentY = realMinY; currentY <= realMaxY; currentY++) {
BorderPoint inclusive = new BorderPoint(Math.max(realMinX, currentX - 1), Math.max(realMinY, currentY - 1), realMaxZ - 1);
addPoint(points, currentX, currentY, realMaxZ, playerLocation, inclusive);
}
}
}
return points;
}
private void addPoint(List<BorderPoint> points, int x, int y, int z, Location playerLocation, BorderPoint inclusive) {
if (isVisible(x, y, z, playerLocation)) {
points.add(new BorderPoint(x, y, z, inclusive));
}
}
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,92 @@
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 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 final BorderService borderService;
private final Supplier<BorderSettings> border;
private final FightManager fightManager;
private final Server server;
public BorderTriggerController(BorderService borderService, Supplier<BorderSettings> border, FightManager fightManager, Server server) {
this.borderService = borderService;
this.border = border;
this.fightManager = fightManager;
this.server = server;
}
@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;
}
borderService.clearBorder(player);
}
}
@@ -0,0 +1,81 @@
package com.eternalcode.combat.border;
import com.eternalcode.combat.region.Region;
import com.eternalcode.combat.region.RegionProvider;
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 Scheduler scheduler;
private final RegionProvider provider;
private final Supplier<BorderSettings> settings;
private final Map<String, BorderTriggerIndexBucket> borderIndexes = new HashMap<>();
private BorderTriggerIndex(Server server, Scheduler 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, Scheduler scheduler, RegionProvider provider, Supplier<BorderSettings> settings) {
BorderTriggerIndex index = new BorderTriggerIndex(server, scheduler, provider, settings);
scheduler.timerAsync(() -> index.updateWorlds(), Duration.ZERO, settings.get().indexRefreshDelay());
return index;
}
public List<BorderTrigger> getTriggered(Location location) {
BorderTriggerIndexBucket index = borderIndexes.get(location.getWorld().getName());
if (index == null) {
return List.of();
}
int x = (int) Math.round(location.getX());
int y = (int) Math.round(location.getY());
int z = (int) Math.round(location.getZ());
return index.getTriggers(x, z)
.stream()
.filter(trigger -> trigger.isTriggered(x, y, z))
.toList();
}
}
@@ -0,0 +1,84 @@
package com.eternalcode.combat.border;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
class BorderTriggerIndexBucket {
/**
* Represents "00000000 00000000 00000000 00000000 11111111 11111111 11111111 11111111" bit mask.
* This allows removing left bits when used with AND bit operator.
* */
private static final long LEFT_INT_MASK = 0xFFFFFFFFL;
/**
* Allows packing the coordination on one axis from 256 into 1
* Why? - We divide the world into fragments of size 256x256
* Why it is 8? Because when you shift bits, then the value will be smaller / bigger in scale 2^n
* E.g `2^4 = 16` (standard minecraft chunk size) in our case, it is `2^8 = 256`
*/
private static final int CHUNK_SHIFT = 8;
private final Map<Long, Set<BorderTrigger>> index = new HashMap<>();
private BorderTriggerIndexBucket() {
}
Set<BorderTrigger> getTriggers(int x, int z) {
long position = packChunk(x >> CHUNK_SHIFT, z >> CHUNK_SHIFT);
return this.index.getOrDefault(position, Set.of());
}
static BorderTriggerIndexBucket create(Collection<BorderTrigger> triggers) {
return new BorderTriggerIndexBucket().with(triggers);
}
private BorderTriggerIndexBucket with(Collection<BorderTrigger> triggers) {
for (BorderTrigger trigger : triggers) {
withTrigger(trigger);
}
return this;
}
private void withTrigger(BorderTrigger trigger) {
int minX = trigger.triggerMin().x() >> CHUNK_SHIFT;
int minZ = trigger.triggerMin().z() >> CHUNK_SHIFT;
int maxX = trigger.triggerMax().x() >> CHUNK_SHIFT;
int maxZ = trigger.triggerMax().z() >> CHUNK_SHIFT;
int startX = Math.min(minX, maxX);
int startZ = Math.min(minZ, maxZ);
int endX = Math.max(minX, maxX);
int endZ = Math.max(minZ, maxZ);
for (int chunkX = startX; chunkX <= endX; chunkX++) {
for (int chunkZ = startZ; chunkZ <= endZ; chunkZ++) {
long packed = packChunk(chunkX, chunkZ);
this.index.computeIfAbsent(packed, key -> new HashSet<>())
.add(trigger);
}
}
}
/**
* Packs two ints into long value.
* <p>
* For example for values:
* <ul>
* <li>X - 00000000 00000000 00000000 00000001 </li>
* <li>Z - 00000000 00000000 00000000 00000111 </li>
* </ul>
* This method will return:<br>
* 00000000 00000000 00000000 00000111 00000000 00000000 00000000 00000001
* </p>
* @param bigChunkX right int to pack
* @param bigChunkZ left int to pack
*/
private static long packChunk(int bigChunkX, int bigChunkZ) {
return (long) bigChunkX & LEFT_INT_MASK | ((long) bigChunkZ & LEFT_INT_MASK) << Integer.SIZE;
}
}
@@ -0,0 +1,18 @@
package com.eternalcode.combat.border.animation;
import java.awt.Color;
public final class BorderColorUtil {
private BorderColorUtil() {
}
public static Color xyzToRainbow(int x, int y, int z) {
float hue = (float) ((Math.sin(x * 0.05) + Math.cos(z * 0.05)) * 0.5 + 0.5);
float saturation = 1.0f;
float brightness = 0.8f + 0.2f * Math.max(0.0f, Math.min(1.0f, (float) y / 255));
return Color.getHSBColor(hue, saturation, brightness);
}
}
@@ -0,0 +1,33 @@
package com.eternalcode.combat.border.animation.block;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
public class BlockSettings extends OkaeriConfig {
@Comment("# Enable block animation?")
public boolean enabled = true;
@Comment({
"# Block type used for rendering the border",
"# Custom: RAINBOW_GLASS, RAINBOW_WOOL, RAINBOW_TERRACOTTA, RAINBOW_CONCRETE",
"# Vanilla: https://javadocs.packetevents.com/com/github/retrooper/packetevents/protocol/world/states/type/StateTypes.html"
})
public BlockType type = BlockType.RAINBOW_GLASS;
@Comment({
"# Delay between each async animation update (need restart to apply)",
"# Lower values will decrease performance but will make the animation smoother",
"# Higher values will increase performance"
})
public Duration updateDelay = Duration.ofMillis(250);
@Comment({
"# Delay between each chunk cache update (need restart to apply)",
"# Lower values will decrease performance",
"# Higher values will increase performance but may cause overlapping existing blocks (this does not modify the world)"
})
public Duration chunkCacheDelay = Duration.ofMillis(300);
}
@@ -0,0 +1,49 @@
package com.eternalcode.combat.border.animation.block;
import com.eternalcode.combat.border.BorderPoint;
import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToConcrete;
import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToGlass;
import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToTerracotta;
import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToWool;
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import java.util.Locale;
public class BlockType {
public static final BlockType RAINBOW_GLASS = new BlockType("RAINBOW_GLASS", point -> xyzToGlass(point));
public static final BlockType RAINBOW_TERRACOTTA = new BlockType("RAINBOW_TERRACOTTA", point -> xyzToTerracotta(point));
public static final BlockType RAINBOW_WOOL = new BlockType("RAINBOW_WOOL", point -> xyzToWool(point));
public static final BlockType RAINBOW_CONCRETE = new BlockType("RAINBOW_CONCRETE", point -> xyzToConcrete(point));
private final String name;
private final TypeProvider type;
private BlockType(String name, TypeProvider type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public StateType getStateType(BorderPoint point) {
return type.provide(point);
}
public static BlockType fromName(String name) {
return switch (name) {
case "RAINBOW_GLASS" -> RAINBOW_GLASS;
case "RAINBOW_WOOL" -> RAINBOW_WOOL;
case "RAINBOW_TERRACOTTA" -> RAINBOW_TERRACOTTA;
case "RAINBOW_CONCRETE" -> RAINBOW_CONCRETE;
default -> new BlockType(name, point -> StateTypes.getByName(name.toLowerCase(Locale.ROOT)));
};
}
private interface TypeProvider {
StateType provide(BorderPoint point);
}
}
@@ -0,0 +1,30 @@
package com.eternalcode.combat.border.animation.block;
import eu.okaeri.configs.schema.GenericsPair;
import eu.okaeri.configs.serdes.BidirectionalTransformer;
import eu.okaeri.configs.serdes.SerdesContext;
import java.util.Locale;
public class BlockTypeTransformer extends BidirectionalTransformer<String, BlockType> {
@Override
public GenericsPair<String, BlockType> getPair() {
return this.genericsPair(String.class, BlockType.class);
}
@Override
public BlockType leftToRight(String data, SerdesContext serdesContext) {
BlockType blockType = BlockType.fromName(data);
if (blockType == null) {
throw new IllegalArgumentException("Unknown block type: " + data);
}
return blockType;
}
@Override
public String rightToLeft(BlockType data, SerdesContext serdesContext) {
return data.getName().toUpperCase(Locale.ROOT);
}
}
@@ -0,0 +1,197 @@
package com.eternalcode.combat.border.animation.block;
import com.eternalcode.combat.border.BorderPoint;
import com.eternalcode.combat.border.BorderService;
import com.eternalcode.combat.border.event.BorderHideAsyncEvent;
import com.eternalcode.combat.border.event.BorderShowAsyncEvent;
import com.eternalcode.commons.scheduler.Scheduler;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.PacketEventsAPI;
import com.github.retrooper.packetevents.manager.player.PlayerManager;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange.EncodedBlock;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
public class BorderBlockController implements Listener {
public static final PacketEventsAPI<?> PACKET_EVENTS = PacketEvents.getAPI();
public static final PlayerManager PLAYER_MANAGER = PACKET_EVENTS.getPlayerManager();
public static final ClientVersion SERVER_VERSION = PACKET_EVENTS.getServerManager().getVersion().toClientVersion();
public static final int AIR_ID = WrappedBlockState.getDefaultState(SERVER_VERSION, StateTypes.AIR).getGlobalId();
public static final int MINECRAFT_CHUNK_SHIFT = 4;
private final BorderService borderService;
private final Supplier<BlockSettings> settings;
private final Server server;
private final Set<UUID> playersToUpdate = ConcurrentHashMap.newKeySet();
private final Map<UUID, Object> lockedPlayers = new ConcurrentHashMap<>();
private final ChunkCache chunkCache;
public BorderBlockController(BorderService borderService, Supplier<BlockSettings> settings, Scheduler scheduler, Server server) {
this.borderService = borderService;
this.settings = settings;
this.server = server;
this.chunkCache = new ChunkCache(settings.get());
scheduler.timerAsync(() -> this.updatePlayers(), settings.get().updateDelay, settings.get().updateDelay);
}
@EventHandler
void onBorderShowAsyncEvent(BorderShowAsyncEvent event) {
if (!settings.get().enabled) {
return;
}
Player player = event.getPlayer();
Set<BorderPoint> borderPoints = this.getPointsWithoutAir(player, event.getPoints());
event.setPoints(borderPoints);
this.showBlocks(player, borderPoints);
this.playersToUpdate.add(player.getUniqueId());
}
@EventHandler
void onBorderHideAsyncEvent(BorderHideAsyncEvent event) {
if (!settings.get().enabled) {
return;
}
Object lock = lockedPlayers.computeIfAbsent(event.getPlayer().getUniqueId(), k -> new Object());
synchronized (lock) {
this.hideBlocks(event.getPlayer(), event.getPoints());
Set<BorderPoint> border = this.borderService.getActiveBorder(event.getPlayer());
if (border.isEmpty()) {
this.playersToUpdate.remove(event.getPlayer().getUniqueId());
}
}
}
private void updatePlayers() {
if (!settings.get().enabled) {
return;
}
for (UUID uuid : this.playersToUpdate) {
Player player = this.server.getPlayer(uuid);
if (player == null) {
this.playersToUpdate.remove(uuid);
continue;
}
this.updatePlayer(uuid, player);
}
}
private void updatePlayer(UUID uuid, Player player) {
Object lock = lockedPlayers.computeIfAbsent(uuid, k -> new Object());
synchronized (lock) {
Set<BorderPoint> border = this.borderService.getActiveBorder(player);
if (border.isEmpty()) {
this.playersToUpdate.remove(uuid);
return;
}
this.showBlocks(player, border);
}
}
private void showBlocks(Player player, Collection<BorderPoint> blocks) {
this.splitByChunks(blocks).entrySet().stream()
.map(chunkBlocks -> toMultiBlockChangePacket(chunkBlocks))
.forEach(change -> PLAYER_MANAGER.sendPacket(player, change));
}
private WrapperPlayServerMultiBlockChange toMultiBlockChangePacket(Entry<Vector3i, Set<BorderPoint>> chunkBlocks) {
EncodedBlock[] encodedBlocks = chunkBlocks.getValue().stream()
.map(borderPoint -> this.toEncodedBlock(borderPoint))
.toArray(value -> new EncodedBlock[value]);
return new WrapperPlayServerMultiBlockChange(chunkBlocks.getKey(), true, encodedBlocks);
}
private void hideBlocks(Player player, Collection<BorderPoint> blocks) {
this.splitByChunks(blocks).entrySet().stream()
.map(chunkBlocks -> toMultiAirChangePacket(chunkBlocks))
.forEach(change -> PLAYER_MANAGER.sendPacket(player, change));
}
private WrapperPlayServerMultiBlockChange toMultiAirChangePacket(Entry<Vector3i, Set<BorderPoint>> chunkBlocks) {
EncodedBlock[] encodedBlocks = chunkBlocks.getValue().stream()
.map(point -> new EncodedBlock(AIR_ID, point.x(), point.y(), point.z()))
.toArray(value -> new EncodedBlock[value]);
return new WrapperPlayServerMultiBlockChange(chunkBlocks.getKey(), true, encodedBlocks);
}
private Set<BorderPoint> getPointsWithoutAir(Player player, Collection<BorderPoint> blocks) {
Map<ChunkLocation, Set<BorderPoint>> chunksToProcess = blocks.stream()
.map(borderPoint -> borderPoint.toInclusive())
.collect(Collectors.groupingBy(
inclusive -> new ChunkLocation(inclusive.x() >> MINECRAFT_CHUNK_SHIFT, inclusive.z() >> MINECRAFT_CHUNK_SHIFT),
Collectors.toSet()
));
return chunksToProcess.entrySet().stream()
.flatMap(entry -> getPointsWithoutAirOnChunk(player, entry))
.collect(Collectors.toSet());
}
private Stream<BorderPoint> getPointsWithoutAirOnChunk(Player player, Entry<ChunkLocation, Set<BorderPoint>> entry) {
ChunkSnapshot snapshot = this.chunkCache.loadSnapshot(player, entry.getKey());
if (snapshot == null) {
return Stream.empty();
}
return entry.getValue().stream()
.filter(point -> isAir(entry.getKey(), point, snapshot));
}
private static boolean isAir(ChunkLocation location, BorderPoint point, ChunkSnapshot chunk) {
int xInsideChunk = point.x() - (location.x() << MINECRAFT_CHUNK_SHIFT);
int zInsideChunk = point.z() - (location.z() << MINECRAFT_CHUNK_SHIFT);
Material material = chunk.getBlockType(xInsideChunk, point.y(), zInsideChunk);
return material.isAir();
}
private Map<Vector3i, Set<BorderPoint>> splitByChunks(Collection<BorderPoint> blocks) {
return blocks.stream().collect(Collectors.groupingBy(
block -> new Vector3i(
block.x() >> MINECRAFT_CHUNK_SHIFT,
block.y() >> MINECRAFT_CHUNK_SHIFT,
block.z() >> MINECRAFT_CHUNK_SHIFT
),
Collectors.toSet()
));
}
private EncodedBlock toEncodedBlock(BorderPoint point) {
StateType type = settings.get().type.getStateType(point);
WrappedBlockState state = WrappedBlockState.getDefaultState(SERVER_VERSION, type);
return new EncodedBlock(state.getGlobalId(), point.x(), point.y(), point.z());
}
}
@@ -0,0 +1,122 @@
package com.eternalcode.combat.border.animation.block;
import com.eternalcode.combat.border.BorderPoint;
import com.eternalcode.combat.border.animation.BorderColorUtil;
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import com.google.common.collect.ImmutableMap;
import java.awt.Color;
import java.util.EnumMap;
import java.util.Map;
import org.bukkit.DyeColor;
final class BorderBlockRainbowUtil {
private final static Map<DyeColor, StateType> GLASSES = new EnumMap<>(ImmutableMap.<DyeColor, StateType>builder()
.put(DyeColor.WHITE, StateTypes.WHITE_STAINED_GLASS)
.put(DyeColor.ORANGE, StateTypes.ORANGE_STAINED_GLASS)
.put(DyeColor.MAGENTA, StateTypes.MAGENTA_STAINED_GLASS)
.put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_STAINED_GLASS)
.put(DyeColor.YELLOW, StateTypes.YELLOW_STAINED_GLASS)
.put(DyeColor.LIME, StateTypes.LIME_STAINED_GLASS)
.put(DyeColor.PINK, StateTypes.PINK_STAINED_GLASS)
.put(DyeColor.GRAY, StateTypes.GRAY_STAINED_GLASS)
.put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_STAINED_GLASS)
.put(DyeColor.CYAN, StateTypes.CYAN_STAINED_GLASS)
.put(DyeColor.PURPLE, StateTypes.PURPLE_STAINED_GLASS)
.put(DyeColor.BLUE, StateTypes.BLUE_STAINED_GLASS)
.put(DyeColor.BROWN, StateTypes.BROWN_STAINED_GLASS)
.put(DyeColor.GREEN, StateTypes.GREEN_STAINED_GLASS)
.put(DyeColor.RED, StateTypes.RED_STAINED_GLASS)
.build()
);
private final static Map<DyeColor, StateType> TERRACOTTA = new EnumMap<>(ImmutableMap.<DyeColor, StateType>builder()
.put(DyeColor.WHITE, StateTypes.WHITE_TERRACOTTA)
.put(DyeColor.ORANGE, StateTypes.ORANGE_TERRACOTTA)
.put(DyeColor.MAGENTA, StateTypes.MAGENTA_TERRACOTTA)
.put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_TERRACOTTA)
.put(DyeColor.YELLOW, StateTypes.YELLOW_TERRACOTTA)
.put(DyeColor.LIME, StateTypes.LIME_TERRACOTTA)
.put(DyeColor.PINK, StateTypes.PINK_TERRACOTTA)
.put(DyeColor.GRAY, StateTypes.GRAY_TERRACOTTA)
.put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_TERRACOTTA)
.put(DyeColor.CYAN, StateTypes.CYAN_TERRACOTTA)
.put(DyeColor.PURPLE, StateTypes.PURPLE_TERRACOTTA)
.put(DyeColor.BLUE, StateTypes.BLUE_TERRACOTTA)
.put(DyeColor.BROWN, StateTypes.BROWN_TERRACOTTA)
.put(DyeColor.GREEN, StateTypes.GREEN_TERRACOTTA)
.put(DyeColor.RED, StateTypes.RED_TERRACOTTA)
.build()
);
private final static Map<DyeColor, StateType> WOOLS = new EnumMap<>(ImmutableMap.<DyeColor, StateType>builder()
.put(DyeColor.WHITE, StateTypes.WHITE_WOOL)
.put(DyeColor.ORANGE, StateTypes.ORANGE_WOOL)
.put(DyeColor.MAGENTA, StateTypes.MAGENTA_WOOL)
.put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_WOOL)
.put(DyeColor.YELLOW, StateTypes.YELLOW_WOOL)
.put(DyeColor.LIME, StateTypes.LIME_WOOL)
.put(DyeColor.PINK, StateTypes.PINK_WOOL)
.put(DyeColor.GRAY, StateTypes.GRAY_WOOL)
.put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_WOOL)
.put(DyeColor.CYAN, StateTypes.CYAN_WOOL)
.put(DyeColor.PURPLE, StateTypes.PURPLE_WOOL)
.put(DyeColor.BLUE, StateTypes.BLUE_WOOL)
.put(DyeColor.BROWN, StateTypes.BROWN_WOOL)
.put(DyeColor.GREEN, StateTypes.GREEN_WOOL)
.put(DyeColor.RED, StateTypes.RED_WOOL)
.build()
);
private final static Map<DyeColor, StateType> CONCRETE = new EnumMap<>(ImmutableMap.<DyeColor, StateType>builder()
.put(DyeColor.WHITE, StateTypes.WHITE_CONCRETE)
.put(DyeColor.ORANGE, StateTypes.ORANGE_CONCRETE)
.put(DyeColor.MAGENTA, StateTypes.MAGENTA_CONCRETE)
.put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_CONCRETE)
.put(DyeColor.YELLOW, StateTypes.YELLOW_CONCRETE)
.put(DyeColor.LIME, StateTypes.LIME_CONCRETE)
.put(DyeColor.PINK, StateTypes.PINK_CONCRETE)
.put(DyeColor.GRAY, StateTypes.GRAY_CONCRETE)
.put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_CONCRETE)
.put(DyeColor.CYAN, StateTypes.CYAN_CONCRETE)
.put(DyeColor.PURPLE, StateTypes.PURPLE_CONCRETE)
.put(DyeColor.BLUE, StateTypes.BLUE_CONCRETE)
.put(DyeColor.BROWN, StateTypes.BROWN_CONCRETE)
.put(DyeColor.GREEN, StateTypes.GREEN_CONCRETE)
.put(DyeColor.RED, StateTypes.RED_CONCRETE)
.build()
);
static StateType xyzToGlass(BorderPoint point) {
return GLASSES.get(xyzToDye(point));
}
static StateType xyzToTerracotta(BorderPoint point) {
return TERRACOTTA.get(xyzToDye(point));
}
public static StateType xyzToWool(BorderPoint point) {
return WOOLS.get(xyzToDye(point));
}
public static StateType xyzToConcrete(BorderPoint point) {
return CONCRETE.get(xyzToDye(point));
}
private static DyeColor xyzToDye(BorderPoint point) {
Color rgb = BorderColorUtil.xyzToRainbow(point.x(), point.y(), point.z());
int distance = Integer.MAX_VALUE;
DyeColor dye = DyeColor.WHITE;
for (DyeColor currentDye : DyeColor.values()) {
org.bukkit.Color color = currentDye.getColor();
int currentDistance = Math.abs(color.getRed() - rgb.getRed()) + Math.abs(color.getGreen() - rgb.getGreen()) + Math.abs(color.getBlue() - rgb.getBlue());
if (currentDistance < distance) {
distance = currentDistance;
dye = currentDye;
}
}
return dye;
}
}
@@ -0,0 +1,33 @@
package com.eternalcode.combat.border.animation.block;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import io.papermc.lib.PaperLib;
import org.bukkit.ChunkSnapshot;
import org.bukkit.entity.Player;
class ChunkCache {
private final Cache<ChunkLocation, ChunkSnapshot> chunkCache;
ChunkCache(BlockSettings settings) {
this.chunkCache = CacheBuilder.newBuilder()
.expireAfterWrite(settings.chunkCacheDelay)
.build();
}
public ChunkSnapshot loadSnapshot(Player player, ChunkLocation location) {
ChunkSnapshot snapshot = chunkCache.getIfPresent(location);
if (snapshot != null) {
return snapshot;
}
ChunkSnapshot chunkSnapshot = PaperLib.getChunkAtAsync(player.getWorld(), location.x(), location.z(), false)
.thenApply(chunk -> chunk.getChunkSnapshot())
.join();
chunkCache.put(location, chunkSnapshot);
return chunkSnapshot;
}
}
Loaded 100 of 181 files, more files were not shown because too many files have changed in this diff. Show more