Compare commits

..
166 Commits
Author SHA1 Message Date
Adrian Gonzales 0ffc47a889 feat: Added some kotlin-ext API 2025-08-20 14:05:35 -05:00
FabianAdrian 3f3c3f9ec5 fix:remove sponge plugin from the root project (#277) 2025-08-20 12:19:24 -05:00
renovate[bot] bfc05edb26 fix(deps): update kotlin monorepo to v2.2.20-rc (#259)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 22:58:26 -05:00
renovate[bot] c7c1576fe1 fix(deps): update dependency org.junit:junit-bom to v5.13.4 (#274)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 21:09:05 -05:00
renovate[bot] e84a484fcd chore(deps): update actions/checkout action to v5 (#275)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 21:08:56 -05:00
renovate[bot] 6743e35109 chore(deps): update plugin org.gradle.toolchains.foojay-resolver-convention to v1 (#276)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 21:08:47 -05:00
renovate[bot] fe2bdd8165 fix(deps): update dependency me.lucko:fabric-permissions-api to v0.4.1 (#273)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 20:59:49 -05:00
renovate[bot] 8ce7846aa1 fix(deps): update cloud.minecraft to v2.0.0-snapshot (#271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 20:59:28 -05:00
renovate[bot] 7c415f19ac chore(deps): update plugin quiet-fabric-loom to v1.11.324 (#261)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 20:59:15 -05:00
renovate[bot] 771cf33788 chore(deps): update plugin idea-ext to v1.2 (#272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 20:32:25 -05:00
renovate[bot] 4edf4180cd fix(deps): update dependency net.kyori:adventure-platform-fabric to v6.6.0 (#264)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 20:32:03 -05:00
renovate[bot] bb6f2d67c3 fix(deps): update dependency net.fabricmc.fabric-api:fabric-api to v0.132.1+1.21.9 (#247)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 20:31:26 -05:00
renovate[bot] 0c51aeb82d fix(deps): update dependency org.incendo:cloud-fabric to v2.0.0-snapshot (#244)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 20:31:12 -05:00
renovate[bot] c2771a42e2 fix(deps): update shadow to v9.0.2 (#243)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 20:30:27 -05:00
Adrian 431270e8b5 MiniPlaceholders V3: Placeholders Refactor (#256)
* refactor: Changed the way of processing and creating placeholders, now based on the MiniMessage parsing context

* fix: Fixed version string

* feat: Added ability to obtain placeholders directly

resolves #174

* feat: Implement GlobalPlaceholder instance

* feat: Reimplemented support for ForwardingAudiences

* test: Fixed resolver equality tests

* test: Implemented some test based on recent API changes

* docs: Added some documentation

* docs: Added some placeholders documentation

* test: Added complex tag tests

* fix: Fixed javadoc generation

* feat: Implement Expansion Providers

resolves #9

* fix: Apply changes from #265

* feat: Removed default expansions

This allows more control by server admins to be able to control which Expansions are used on a server. The default expansions will be included in external expansions.

* docs: A few advances in documentation

This commit took over three months to publish...

* feat: Added error messages to expansion providers loading process

* build: Updated to Gradle 9.0.0

* docs: Finished javadocs documentation

build: Use gradle configuration cache

* fix: Fixed dynamic expansions loading

* feat: Added basic dependency injection for ExpansionProvider

This allows certain expansions to obtain data from the platform on which they are running where there is no static context for their platform or plugin/mod instances.
This feature will be used by a minority of expansions, but it eliminates the need to create expansions that load as plugins, as in MiniPlaceholders v1 and v2.

* fix: Expansions initialization on Paper
2025-08-19 17:21:42 -05:00
FabianAdrian a87226d33b update cloud snapshot repository (#268) 2025-06-29 12:00:37 -05:00
Willsr71 98d27e40a1 Fix expansion author and version being built backwards (#265) 2025-04-12 20:50:13 -05:00
renovate[bot] 59de135b95 chore(deps): update dependency gradle to v8.13 (#249)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 13:00:37 -05:00
Adrian f939fac882 build: Back to snapshots 2025-02-23 15:08:26 -05:00
Adrian adde850c52 release: Version 2.3.0 2025-02-23 14:03:35 -05:00
Adrian 94d97b97eb Added expansions subcommand (#255)
* refactor: Refactor commands organization

Also, added expansions subcommand

* feat: Added expansions subcommand to help menu

* feat: Added default author and version to built-in expansions

* feat: ReImplemented Sponge Module

At the moment the commands are disabled, but this will be resolved in a future release
2025-02-23 10:59:39 -05:00
renovate[bot] 122e338e4e chore(deps): update plugin idea-ext to v1.1.10 (#251)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-22 23:59:48 -05:00
renovate[bot] 32fa6bc38d chore(deps): update plugin quiet-fabric-loom to v1.10.316 (#248)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-22 23:55:10 -05:00
renovate[bot] 373c061089 fix(deps): update kotlin monorepo to v2.1.10 (#250)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-22 23:54:57 -05:00
renovate[bot] 8addfe3829 chore(deps): update dependency me.champeau.jmh to v0.7.3 (#252)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-22 23:54:46 -05:00
renovate[bot] 0e7fe5fec2 fix(deps): update dependency org.junit:junit-bom to v5.12.0 (#254)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-22 23:54:38 -05:00
renovate[bot] f0bce37135 fix(deps): update adventure to v4.19.0 (#253)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-22 23:54:29 -05:00
Adrian 4bd423bba8 fix(docs): Fixed Dokka docs generation and updated to V2 2025-02-22 23:53:15 -05:00
Adrian 2d75b5f36e build: Updated gradle to 8.12.1 2025-02-22 18:22:56 -05:00
Adrian a0fd80f883 misc: Fixed Deprecation version notice 2025-02-22 17:56:37 -05:00
renovate[bot] e846e2989b fix(deps): update dependency org.jetbrains.dokka:dokka-gradle-plugin to v2 (#245)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 23:06:45 -05:00
renovate[bot] aa0e6a1d51 chore(deps): update dependency org.jetbrains.dokka to v2 (#239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 23:06:39 -05:00
renovate[bot] 702465f922 fix(deps): update dependency org.junit:junit-bom to v5.11.4 (#238)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 23:06:33 -05:00
renovate[bot] 012a322dca chore(deps): update plugin org.gradle.toolchains.foojay-resolver-convention to v0.9.0 (#237)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 23:06:27 -05:00
renovate[bot] c780f1c046 chore(deps): update plugin quiet-fabric-loom to v1.9.312 (#241)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 23:06:20 -05:00
renovate[bot] 51c1c71b7f fix(deps): update cloud.minecraft to v2.0.0-snapshot (#242)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 23:06:12 -05:00
renovate[bot] 530f0ae96b fix(deps): update dependency net.fabricmc:fabric-loader to v0.16.10 (#246)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 23:06:02 -05:00
renovate[bot] c5fdf94d27 fix(deps): update kotlin monorepo to v2.1.0 (#236)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 22:44:30 -05:00
renovate[bot] 855f9ca7ff fix(deps): update dependency net.fabricmc.fabric-api:fabric-api to v0.114.3+1.21.5 (#215)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 22:44:14 -05:00
Adrian e24c08fad0 Merge pull request #240 from powercasgamer/ver/1.21.4
1.21.4
2024-12-25 11:56:51 -05:00
powercas_gamer b1faea8c2d remove/update more deps 2024-12-24 19:35:12 +00:00
powercas_gamer ed5005be67 1.21.4 2024-12-24 19:08:00 +00:00
Adrian 1003e666e4 Merge pull request #230 from MiniPlaceholders/renovate/fabric.loader
fix(deps): update dependency net.fabricmc:fabric-loader to v0.16.9
2024-11-10 15:09:27 -05:00
Adrian c7dde0986a Merge pull request #226 from MiniPlaceholders/renovate/cloud.minecraft
fix(deps): update cloud.minecraft to v2.0.0-snapshot
2024-11-10 15:09:16 -05:00
Adrian 167c9101be Merge pull request #231 from MiniPlaceholders/renovate/adventurefabric
fix(deps): update dependency net.kyori:adventure-platform-fabric to v5.14.2
2024-11-10 15:09:00 -05:00
renovate[bot] a04b7ef182 fix(deps): update dependency net.kyori:adventure-platform-fabric to v5.14.2 2024-11-10 19:47:35 +00:00
Adrian 97b5f2b77e Merge pull request #218 from MiniPlaceholders/renovate/userdev
chore(deps): update dependency io.papermc.paperweight.userdev to v1.7.4
2024-11-10 10:10:47 -05:00
Adrian 32cfd1b0a7 Merge pull request #224 from MiniPlaceholders/renovate/org.jetbrains.gradle.plugin.idea-ext-1.x
chore(deps): update plugin idea-ext to v1.1.9
2024-11-10 10:10:39 -05:00
Adrian 9be5a6c676 Merge pull request #217 from MiniPlaceholders/renovate/kotlin-monorepo
fix(deps): update kotlin monorepo to v2.0.21
2024-11-10 10:10:30 -05:00
renovate[bot] 64e634033e fix(deps): update dependency net.fabricmc:fabric-loader to v0.16.9 2024-11-10 15:10:05 +00:00
renovate[bot] d66dadadab fix(deps): update cloud.minecraft to v2.0.0-snapshot 2024-11-10 15:09:56 +00:00
Adrian 3a78626d80 Merge pull request #216 from MiniPlaceholders/renovate/fabric-loom-1.x
chore(deps): update plugin fabric-loom to v1.8.12
2024-11-10 10:09:53 -05:00
Adrian 7f8325b605 Merge pull request #220 from MiniPlaceholders/renovate/gradle-8.x
chore(deps): update dependency gradle to v8.10.2
2024-11-10 10:09:42 -05:00
Adrian 759ca10c4f Merge pull request #221 from MiniPlaceholders/renovate/junit5-monorepo
fix(deps): update dependency org.junit:junit-bom to v5.11.3
2024-11-10 10:09:34 -05:00
Adrian 6e5bca56c1 Merge pull request #225 from MiniPlaceholders/renovate/shadow
chore(deps): update shadow to v8.3.5
2024-11-10 10:09:11 -05:00
Adrian c21201e948 Merge pull request #227 from MiniPlaceholders/renovate/cloud.minecraft.modded
fix(deps): update dependency org.incendo:cloud-fabric to v2.0.0-snapshot
2024-11-10 10:09:01 -05:00
renovate[bot] 41a35ce3ce chore(deps): update shadow to v8.3.5 2024-11-03 02:16:32 +00:00
renovate[bot] 31f5f286eb chore(deps): update plugin fabric-loom to v1.8.12 2024-11-01 16:19:35 +00:00
renovate[bot] ce4c694f87 chore(deps): update dependency io.papermc.paperweight.userdev to v1.7.4 2024-10-25 22:34:48 +00:00
renovate[bot] 62ab9feaea fix(deps): update dependency org.junit:junit-bom to v5.11.3 2024-10-21 15:49:26 +00:00
renovate[bot] 076aa128fd fix(deps): update kotlin monorepo to v2.0.21 2024-10-10 10:50:03 +00:00
renovate[bot] e2dba8c7ac fix(deps): update dependency org.incendo:cloud-fabric to v2.0.0-snapshot 2024-10-03 00:51:59 +00:00
renovate[bot] d40e6e2cd8 chore(deps): update plugin idea-ext to v1.1.9 2024-10-02 22:53:31 +00:00
renovate[bot] ecbf3d3ee4 chore(deps): update dependency gradle to v8.10.2 2024-10-02 22:53:25 +00:00
Adrian b0c980672d Merge pull request #223 from powercasgamer/ver/1.21
update to 1.21.1
2024-10-02 17:50:07 -05:00
powercas_gamer 40890cfbd0 remove unneeded dependency 2024-09-20 10:19:09 +00:00
powercas_gamer beee6c6266 fix: typo 2024-09-20 09:56:51 +00:00
powercas_gamer b0fb3dc4d8 fabric 2024-09-20 09:49:39 +00:00
powercas_gamer a9c3128698 progress
would be more progress if IntellIJ actually worked..
2024-09-20 09:37:27 +00:00
powercas_gamer a7a7558fdf update/fix versions 2024-09-20 09:19:11 +00:00
powercas_gamer 5adab32ef0 update to 1.21.1 2024-09-20 09:07:48 +00:00
Adrian 36ca8be51b Merge pull request #213 from MiniPlaceholders/renovate/adventurefabric
fix(deps): update dependency net.kyori:adventure-platform-fabric to v5.14.1
2024-07-26 17:58:40 -05:00
renovate[bot] 6254e29eec fix(deps): update dependency net.kyori:adventure-platform-fabric to v5.14.1 2024-07-26 03:34:32 +00:00
Adrian 08e57e8e98 fix: Fixed getting Expansions by name 2024-07-25 10:17:12 -05:00
Adrian a1cdb1a212 fix: Re-added custom MinecraftExceptionHandlers 2024-07-25 08:46:48 -05:00
Adrian aa86a36ba1 misc: Removed some outdated mentions 2024-07-24 18:23:23 -05:00
Adrian 0310f1a260 Merge pull request #199 from Stampede2011/cloud2
Cloud v2 Implementation
2024-07-24 18:09:14 -05:00
Adrian 28b56abf3b Finished Cloud 2 implementation 2024-07-24 18:01:56 -05:00
Adrian 504eff952f Merge branch 'main' into cloud2 2024-07-24 15:55:44 -05:00
Adrian cb75bcb84b Merge pull request #205 from MiniPlaceholders/renovate/adventurefabric
fix(deps): update dependency net.kyori:adventure-platform-fabric to v5.14.0
2024-07-18 15:11:18 -05:00
Adrian a78e988132 Merge pull request #202 from MiniPlaceholders/renovate/junit5-monorepo
fix(deps): update dependency org.junit:junit-bom to v5.10.3
2024-07-18 15:11:10 -05:00
Adrian e5b8b36014 Merge pull request #192 from MiniPlaceholders/renovate/fabric.api
fix(deps): update dependency net.fabricmc.fabric-api:fabric-api to v0.100.7+1.21
2024-07-18 15:11:02 -05:00
renovate[bot] 0cc1c4bdd6 fix(deps): update dependency net.kyori:adventure-platform-fabric to v5.14.0 2024-07-18 20:04:07 +00:00
renovate[bot] ee4e23d658 fix(deps): update dependency net.fabricmc.fabric-api:fabric-api to v0.100.7+1.21 2024-07-18 20:03:05 +00:00
renovate[bot] 407807948c fix(deps): update dependency org.junit:junit-bom to v5.10.3 2024-07-18 20:03:01 +00:00
Adrian 634011dee0 misc: Updated to Java 21 2024-07-18 15:00:51 -05:00
Adrian 12596860b1 Merge pull request #207 from MiniPlaceholders/renovate/fabric.loader
fix(deps): update dependency net.fabricmc:fabric-loader to v0.16.0
2024-07-18 11:30:30 -05:00
Adrian 1ecabc43d6 Merge pull request #206 from MiniPlaceholders/renovate/major-kotlin-monorepo
fix(deps): update kotlin monorepo to v2 (major)
2024-07-18 11:30:23 -05:00
Adrian bae4722945 Merge pull request #203 from MiniPlaceholders/renovate/runtask
chore(deps): update runtask to v2.3.0
2024-07-18 11:26:43 -05:00
Adrian df07b08a45 Merge pull request #204 from MiniPlaceholders/renovate/me.lucko-fabric-permissions-api-0.x
fix(deps): update dependency me.lucko:fabric-permissions-api to v0.3.1
2024-07-18 11:25:32 -05:00
renovate[bot] b2c73f521d fix(deps): update dependency net.fabricmc:fabric-loader to v0.16.0 2024-07-12 16:56:58 +00:00
renovate[bot] 977ba115d0 fix(deps): update kotlin monorepo to v2 2024-07-12 11:26:08 +00:00
renovate[bot] 2b2c3ecddf fix(deps): update dependency me.lucko:fabric-permissions-api to v0.3.1 2024-07-12 07:03:35 +00:00
renovate[bot] c63e59aca6 chore(deps): update dependency gradle to v8.9 (#198)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-11 22:07:34 -05:00
renovate[bot] de9b8e78d1 chore(deps): update plugin fabric-loom to v1.7.2 (#197)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-11 22:07:26 -05:00
renovate[bot] 922e0dceb0 chore(deps): update plugin idea-ext to v1.1.8 (#196)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-11 22:07:18 -05:00
renovate[bot] 68644fd9cd fix(deps): update dependency net.fabricmc:fabric-loader to v0.15.11 (#201)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-11 22:07:11 -05:00
renovate[bot] d3e3092ca1 chore(deps): update dependency io.papermc.paperweight.userdev to v1.7.1 (#200)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-11 22:07:02 -05:00
renovate[bot] af07305052 chore(deps): update runtask to v2.3.0 2024-07-12 03:05:40 +00:00
renovate[bot] ef2c7d80bc fix(deps): update kotlin monorepo to v1.9.24 (#195)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-11 22:05:23 -05:00
renovate[bot] be94b7e078 fix(deps): update dependency org.jetbrains.dokka:dokka-gradle-plugin to v1.9.20 (#194)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-11 22:04:58 -05:00
renovate[bot] 901335174c chore(deps): update dependency org.jetbrains.dokka to v1.9.20 (#193)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-11 22:04:50 -05:00
renovate[bot] 2b05d77cf8 fix(deps): update adventure to v4.17.0 (#188)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-11 22:04:42 -05:00
Stampede f4c0a7e100 Remove duplicate repository 2024-03-27 12:26:17 -05:00
Stampede 87465740c6 Fix shadow relocation 2024-03-27 12:24:02 -05:00
Stampede 0f8144a9fb Updated to Cloud v2 2024-03-27 11:04:35 -05:00
Adrian 3808fc9d89 Add LegacyUtils methods to check if a string contains legacy characters (#191)
* feat: Add LegacyUtils methods to check if a string contains legacy characters

* chore: Rename to #mapLegacyOrElse
2024-03-03 15:48:43 -05:00
renovate[bot] a7cc73fe7d fix(deps): update dependency net.kyori:adventure-platform-fabric to v5.12.0 (#189)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-28 22:27:16 -05:00
renovate[bot] 8ac0c398d7 chore(deps): update plugin fabric-loom to v1.5.8 (#190)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-28 22:27:07 -05:00
renovate[bot] 25618c0d42 fix(deps): update dependency net.fabricmc.fabric-api:fabric-api to v0.96.4+1.20.5 (#187)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-28 22:27:00 -05:00
renovate[bot] 9b61e8efa0 fix(deps): update dependency net.fabricmc.fabric-api:fabric-api to v0.96.2+1.20.5 (#177)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-17 09:37:01 -05:00
renovate[bot] ad95e239bc fix(deps): update dependency net.fabricmc:fabric-loader to v0.15.7 (#184)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-15 12:14:09 -05:00
powercas_gamer d18ff640d2 feat(api): expansion metadata (#185)
* feat(api): expansion metadata

* more

* actually expose it

* javadoc

* oopsie whoopsie import

* change javadoc version
2024-02-15 12:13:47 -05:00
Adrian 7536d32055 Merge pull request #182 from MiniPlaceholders/renovate/runtask
chore(deps): update dependency xyz.jpenilla.run-velocity to v2.2.3
2024-02-04 15:14:01 -05:00
Adrian 5dffb9c251 Merge pull request #183 from MiniPlaceholders/renovate/junit5-monorepo
fix(deps): update dependency org.junit:junit-bom to v5.10.2
2024-02-04 15:13:54 -05:00
renovate[bot] e75baceb77 fix(deps): update dependency org.junit:junit-bom to v5.10.2 2024-02-04 11:23:07 +00:00
Adrian f2c6aa08f4 release: Version 2.2.4 2024-02-03 22:06:57 -05:00
Adrian 7727dca940 misc: Removed old Krypton module sources 2024-02-03 21:57:41 -05:00
Adrian 151b2dc2a4 misc: Deprecated TagsUtils#staticTag 2024-02-03 21:57:06 -05:00
renovate[bot] 16e3d09204 chore(deps): update runtask to v2.2.3 2024-02-04 02:55:42 +00:00
Adrian 62c11dd84a Merge pull request #176 from MiniPlaceholders/renovate/fabric.loader
fix(deps): update dependency net.fabricmc:fabric-loader to v0.15.6
2024-02-03 21:54:18 -05:00
Adrian 77913be0fa Merge pull request #178 from MiniPlaceholders/renovate/fabric-loom-1.x
chore(deps): update plugin fabric-loom to v1.5.7
2024-02-03 21:54:03 -05:00
Adrian 685f67146d Merge pull request #179 from MiniPlaceholders/renovate/adventurefabric
fix(deps): update dependency net.kyori:adventure-platform-fabric to v5.11.0
2024-02-03 21:53:56 -05:00
Adrian 6f524b056a Merge pull request #180 from MiniPlaceholders/renovate/nick-invision-retry-3.x
chore(deps): update nick-invision/retry action to v3
2024-02-03 21:53:50 -05:00
Adrian 36f1cce813 Merge pull request #181 from MiniPlaceholders/renovate/gradle-8.x
chore(deps): update dependency gradle to v8.6
2024-02-03 21:53:43 -05:00
renovate[bot] 4061c8acbe chore(deps): update dependency gradle to v8.6 2024-02-02 18:22:06 +00:00
renovate[bot] 319731cef0 chore(deps): update nick-invision/retry action to v3 2024-01-31 18:53:24 +00:00
renovate[bot] 9e29afa9c5 chore(deps): update plugin fabric-loom to v1.5.7 2024-01-25 09:35:54 +00:00
renovate[bot] 9655509c2f fix(deps): update dependency net.kyori:adventure-platform-fabric to v5.11.0 2024-01-21 07:40:06 +00:00
renovate[bot] 26f1042d17 fix(deps): update dependency net.fabricmc:fabric-loader to v0.15.6 2024-01-18 03:44:36 +00:00
Adrian 84c39e9205 Merge pull request #171 from MiniPlaceholders/renovate/fabric-loom-1.x
chore(deps): update plugin fabric-loom to v1.5.5
2024-01-15 21:25:24 -05:00
Adrian 77327661f3 Merge pull request #172 from MiniPlaceholders/renovate/fabric.api
fix(deps): update dependency net.fabricmc.fabric-api:fabric-api to v0.93.1+1.20.5
2024-01-15 21:25:17 -05:00
Adrian b725fd2bae Merge pull request #173 from MiniPlaceholders/renovate/org.gradle.toolchains.foojay-resolver-convention-0.x
chore(deps): update plugin org.gradle.toolchains.foojay-resolver-convention to v0.8.0
2024-01-15 21:25:10 -05:00
renovate[bot] f9a5acaaaf chore(deps): update plugin fabric-loom to v1.5.5 2024-01-13 11:05:36 +00:00
renovate[bot] fe613ce82d chore(deps): update plugin org.gradle.toolchains.foojay-resolver-convention to v0.8.0 2024-01-12 16:49:57 +00:00
renovate[bot] 21839e1e37 fix(deps): update dependency net.fabricmc.fabric-api:fabric-api to v0.93.1+1.20.5 2024-01-12 14:14:32 +00:00
Adrian 25d1588032 Merge pull request #167 from MiniPlaceholders/renovate/fabric.api
fix(deps): update dependency net.fabricmc.fabric-api:fabric-api to v0.92.0+1.20.5
2023-12-29 19:45:03 -05:00
renovate[bot] 92583013db fix(deps): update dependency net.fabricmc.fabric-api:fabric-api to v0.92.0+1.20.5 2023-12-29 15:38:27 +00:00
Adrian 89a7bdb208 Merge pull request #165 from MiniPlaceholders/renovate/fabric.loader
fix(deps): update dependency net.fabricmc:fabric-loader to v0.15.3
2023-12-29 10:37:41 -05:00
Adrian 0e7d4cb6dd Merge pull request #169 from MiniPlaceholders/renovate/kotlin-monorepo
fix(deps): update kotlin monorepo to v1.9.22
2023-12-29 10:37:23 -05:00
renovate[bot] 159b96579f fix(deps): update kotlin monorepo to v1.9.22 2023-12-21 17:01:40 +00:00
renovate[bot] efb155a171 fix(deps): update dependency net.fabricmc:fabric-loader to v0.15.3 2023-12-18 18:01:40 +00:00
Adrian 71ad7fb399 Merge pull request #168 from MiniPlaceholders/renovate/adventure
fix(deps): update adventure to v4.15.0
2023-12-18 07:04:22 -05:00
renovate[bot] 8b219fd553 fix(deps): update adventure to v4.15.0 2023-12-18 10:44:32 +00:00
Adrian 9899f0bab4 Merge pull request #166 from MiniPlaceholders/renovate/actions-upload-artifact-4.x
chore(deps): update actions/upload-artifact action to v4
2023-12-14 18:29:38 -05:00
renovate[bot] 7044e43483 chore(deps): update actions/upload-artifact action to v4 2023-12-14 20:59:42 +00:00
Adrian 36f1739704 Merge pull request #162 from MiniPlaceholders/renovate/actions-setup-java-4.x
chore(deps): update actions/setup-java action to v4
2023-12-12 18:11:11 -05:00
Adrian d173de4250 Merge pull request #163 from MiniPlaceholders/renovate/userdev
chore(deps): update dependency io.papermc.paperweight.userdev to v1.5.11
2023-12-12 18:10:25 -05:00
Adrian 410fe06117 Merge pull request #164 from MiniPlaceholders/renovate/fabric-loom-1.x
chore(deps): update plugin fabric-loom to v1.4.5
2023-12-12 18:10:08 -05:00
Adrian e198769698 Merge pull request #161 from MiniPlaceholders/renovate/fabric.loader
fix(deps): update dependency net.fabricmc:fabric-loader to v0.15.1
2023-12-12 12:59:04 -05:00
renovate[bot] 41ff8c1e12 fix(deps): update dependency net.fabricmc:fabric-loader to v0.15.1 2023-12-11 16:06:24 +00:00
Adrian 344af47d19 Merge pull request #144 from MiniPlaceholders/renovate/fabric.api
fix(deps): update dependency net.fabricmc.fabric-api:fabric-api to v0.91.2+1.20.4
2023-12-11 11:06:00 -05:00
Adrian 756b1e9b43 Merge pull request #160 from MiniPlaceholders/renovate/gradle-8.x
chore(deps): update dependency gradle to v8.5
2023-12-11 11:05:48 -05:00
renovate[bot] 85b7a24770 chore(deps): update plugin fabric-loom to v1.4.5 2023-12-10 01:26:49 +00:00
renovate[bot] 7f484f33f0 fix(deps): update dependency net.fabricmc.fabric-api:fabric-api to v0.91.2+1.20.4 2023-12-09 01:44:53 +00:00
renovate[bot] 5d21522fa7 chore(deps): update dependency io.papermc.paperweight.userdev to v1.5.11 2023-12-08 18:53:59 +00:00
renovate[bot] 176266c9d5 chore(deps): update actions/setup-java action to v4 2023-11-29 16:22:40 +00:00
renovate[bot] c5bf073888 chore(deps): update dependency gradle to v8.5 2023-11-29 14:51:10 +00:00
Adrian f2cbd1db75 Merge pull request #159 from MiniPlaceholders/renovate/fabric.loader
fix(deps): update dependency net.fabricmc:fabric-loader to v0.14.25
2023-11-29 09:46:01 -05:00
renovate[bot] e995e5441e fix(deps): update dependency net.fabricmc:fabric-loader to v0.14.25 2023-11-28 12:37:22 +00:00
Adrian f776f8ae61 Merge pull request #157 from MiniPlaceholders/renovate/org.jetbrains.kotlin.jvm-1.x
chore(deps): update plugin org.jetbrains.kotlin.jvm to v1.9.21
2023-11-27 17:04:38 -05:00
Adrian a26f92e75c Merge pull request #158 from MiniPlaceholders/renovate/kotlin-monorepo
fix(deps): update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v1.9.21
2023-11-27 17:04:30 -05:00
renovate[bot] b030f02aac fix(deps): update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v1.9.21 2023-11-24 07:39:15 +00:00
renovate[bot] 4336746bf3 chore(deps): update plugin org.jetbrains.kotlin.jvm to v1.9.21 2023-11-23 16:49:59 +00:00
Adrian e7e87a72dd Merge pull request #156 from MiniPlaceholders/renovate/userdev
chore(deps): update dependency io.papermc.paperweight.userdev to v1.5.10
2023-11-21 19:11:24 -05:00
renovate[bot] 0dce995222 chore(deps): update dependency io.papermc.paperweight.userdev to v1.5.10 2023-11-21 22:38:10 +00:00
100 changed files with 2990 additions and 1753 deletions

No files matched your search

+6 -6
View File
@@ -8,24 +8,24 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Set up JDK 17
uses: actions/setup-java@v3
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 21
distribution: 'temurin'
cache: 'gradle'
- name: Build with Gradle
uses: nick-invision/retry@v2
uses: nick-invision/retry@v3
with:
timeout_minutes: 10
max_attempts: 2
command: gradle build
- name: Upload Artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: MiniPlaceholders
path: jar/MiniPlaceholders-*.jar
+1 -1
View File
@@ -1,3 +1,3 @@
FROM gitpod/workspace-full
RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh && sdk install java 17.0.1.fx-zulu"
RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh && sdk install java 21.0.6.fx-zulu"
+2 -2
View File
@@ -5,7 +5,7 @@
![Modrinth Downloads](https://img.shields.io/modrinth/dt/HQyibRsN?logo=Modrinth&style=flat-square)
![GitHub Downloads](https://img.shields.io/github/downloads/MiniPlaceholders/MiniPlaceholders/total?logo=GitHub&style=flat-square)
MiniMessage Component-based Placeholders for Paper, Fabric, Sponge, Folia and Velocity platforms
MiniMessage Component-based Placeholders API for Minecraft Platforms.
## Compatibility
- Paper 1.19.3, 1.19.4+
@@ -21,7 +21,7 @@ MiniMessage Component-based Placeholders for Paper, Fabric, Sponge, Folia and Ve
- `/vminiplaceholders parse me "[message with placeholders]"`
- `/vminiplaceholders parse player [some-player] "[message with placeholders]"`
### Paper | Folia | Fabric | Krypton
### Paper | Folia | Fabric
- `/miniplaceholders <parse|help> player [some-player] "[message with placeholders]`
- `/miniplaceholders parse player [some-player] "[message with placeholders]"`
+14 -4
View File
@@ -6,17 +6,20 @@ plugins {
dependencies {
compileOnly(libs.adventure.api)
compileOnly(libs.adventure.minimesssage)
compileOnly(libs.adventure.minimessage)
compileOnly(libs.adventure.serializer.legacy)
compileOnly(projects.miniplaceholdersConnect)
compileOnlyApi(libs.unnamedinject)
testImplementation(platform(libs.junit.bom))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation(libs.adventure.api)
testImplementation(libs.adventure.minimesssage)
testImplementation(libs.adventure.minimessage)
testImplementation(libs.adventure.serializer.plain)
testImplementation(libs.adventure.serializer.legacy)
compileOnly(libs.jspecify)
}
jmh {
@@ -34,11 +37,18 @@ tasks {
filteringCharset = Charsets.UTF_8.name()
}
javadoc {
options.encoding = Charsets.UTF_8.name()
(options as StandardJavadocDocletOptions).links(
val o = options as StandardJavadocDocletOptions
o.encoding = Charsets.UTF_8.name()
o.links(
"https://jd.advntr.dev/api/${libs.versions.adventure.get()}/",
"https://jd.advntr.dev/text-minimessage/${libs.versions.adventure.get()}/"
)
o.tags(
"apiNote:a:API Note:",
"implSpec:a:Implementation Requirements:",
"implNote:a:Implementation Note:",
"sinceMinecraft:a:Since Minecraft:"
)
}
compileTestJava {
options.encoding = Charsets.UTF_8.name()
@@ -12,39 +12,46 @@ import org.openjdk.jmh.annotations.State;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.MiniPlaceholders;
@SuppressWarnings("unused")
@State(Scope.Benchmark)
public class PlaceholderBenchmark {
@Benchmark
public void placeholderBench(){
BenchAudience player = new BenchAudience("4drian3d");
Expansion.Builder expansion = Expansion.builder("benchmark")
@Benchmark
public void placeholderBench() {
BenchAudience player = new BenchAudience("4drian3d");
Expansion.Builder expansion = Expansion.builder("benchmark")
.audiencePlaceholder(
"name", (aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(((BenchAudience)aud).getName()))
BenchAudience.class,
"name",
(aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(aud.getName()))
)
.audiencePlaceholder(
"uuid", (aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(((BenchAudience)aud).getUUID().toString()))
BenchAudience.class,
"uuid",
(aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(aud.getUUID().toString()))
)
.audiencePlaceholder(
"tablistfooter", (aud, queue, ctx) ->Tag.selfClosingInserting(Component.text("footer"))
"tablistfooter",
(aud, queue, ctx) -> Tag.selfClosingInserting(Component.text("footer"))
);
Expansion expansionBuilded = expansion.build();
expansionBuilded.audiencePlaceholders(player);
final TagResolver resolvers = MiniPlaceholders.getAudiencePlaceholders(player);
Expansion expansionBuilded = expansion.build();
expansionBuilded.register();
final TagResolver resolvers = MiniPlaceholders.audiencePlaceholders();
MiniMessage.miniMessage().deserialize("Player Name: <benchmark-name>", resolvers);
}
MiniMessage.miniMessage().deserialize("Player Name: <benchmark_name>", player, resolvers);
}
@Benchmark
public void singleBench(){
Expansion.builder("single")
.globalPlaceholder("servername", (queue, ctx) -> Tag.selfClosingInserting(Component.text("Peruviankkit")))
@Benchmark
public void singleBench() {
Expansion.builder("single")
.globalPlaceholder("servername",
(queue, ctx) -> Tag.selfClosingInserting(Component.text("Peruviankkit")))
.build()
.register();
TagResolver resolvers = MiniPlaceholders.getGlobalPlaceholders();
TagResolver resolvers = MiniPlaceholders.globalPlaceholders();
MiniMessage.miniMessage().deserialize("Server name: <single-servername>", resolvers);
}
MiniMessage.miniMessage().deserialize("Server name: <single_servername>", resolvers);
}
}
@@ -1,20 +1,23 @@
package io.github.miniplaceholders.api;
import io.github.miniplaceholders.api.placeholder.AudiencePlaceholder;
import io.github.miniplaceholders.api.placeholder.GlobalPlaceholder;
import io.github.miniplaceholders.api.placeholder.RelationalPlaceholder;
import io.github.miniplaceholders.api.resolver.AudienceTagResolver;
import io.github.miniplaceholders.api.resolver.GlobalTagResolver;
import io.github.miniplaceholders.api.resolver.RelationalTagResolver;
import io.github.miniplaceholders.api.types.PlaceholderType;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.builder.AbstractBuilder;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.builder.AbstractBuilder;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import java.util.Collection;
/**
* Expansion that contains placeholders
@@ -23,33 +26,50 @@ import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
* <pre>
* Player player = event.getPlayer();
* Expansion.Builder builder = Expansion.builder("player")
* .audiencePlaceholder("name", (p, queue, ctx) -> Tag.selfClosingInserting(Component.text(((Player)p).getUsername()))
* .resolver(Player.class, "name", (p, queue, ctx) -> Tag.selfClosingInserting(Component.text(p.getUsername()))
* .build();
* Expansion expansion = builder.build();
* // You can also call the {@link Expansion#register} method to register
* // the {@link Expansion} in the {@link MiniPlaceholders} global Extensions and
* // use it in {@link MiniPlaceholders#getAudiencePlaceholders(Audience)} e.g.
* TagResolver resolver = expansion.audiencePlaceholder(player);
* player.sendMessage(MiniMessage.miniMessage().deserialize("Hello {@literal <luckperms_prefix> <player_name>}", resolver));
* // use it in {@link MiniPlaceholders#audiencePlaceholders()} e.g.
* TagResolver resolver = expansion.resolver();
* player.sendMessage(MiniMessage.miniMessage().deserialize("Hello {@literal <luckperms_prefix> <player_name>}", player, resolver));
* </pre>
*
* @since 1.0.0
*/
@NullMarked
public sealed interface Expansion permits ExpansionImpl {
/**
* Get the expansion name
*
* @return the expansion name
* @since 1.0.0
*/
@NotNull String name();
String name();
/**
* Get the {@link TagResolver} of the desired {@link Audience}
* @param audience the audience
* Get the author of this expansion
*
* @return the author or null
* @since 2.3.0
*/
@Nullable String author();
/**
* Get the version of this expansion
*
* @return the version or null
* @since 2.3.0
*/
@Nullable String version();
/**
* Get the {@link TagResolver} of an {@link Audience}
* @return A TagResolver with variable placeholders of an Audience
* @since 1.0.0
*/
@NotNull TagResolver audiencePlaceholders(final @NotNull Audience audience);
TagResolver audiencePlaceholders();
/**
* Get the relational placeholders based on two audiences
@@ -57,19 +77,17 @@ public sealed interface Expansion permits ExpansionImpl {
* <p>The resulting TagResolver will give results according
* to the 2 {@link Audience} provided and when called
* at any time will retrieve the required values</p>
* @param audience the main audience
* @param otherAudience the secondary audience
* @return A TagResolver with variable placeholders between 2 {@link Audience}s
* @since 1.0.0
*/
@NotNull TagResolver relationalPlaceholders(final @NotNull Audience audience, final @NotNull Audience otherAudience);
TagResolver relationalPlaceholders();
/**
* Get global placeholders
* @return the global placeholders
* @since 1.0.0
*/
@NotNull TagResolver globalPlaceholders();
TagResolver globalPlaceholders();
/**
* Register this expansion
@@ -94,16 +112,128 @@ public sealed interface Expansion permits ExpansionImpl {
*/
boolean registered();
// TODO: Docs and Tests
/**
* Get the registered audience placeholders
*
* @return registered audience placeholders
*/
Collection<AudiencePlaceholder<?>> registeredAudiencePlaceholders();
/**
* Get the registered relational placeholders
*
* @return registered relational placeholders
*/
Collection<RelationalPlaceholder<?>> registeredRelationalPlaceholders();
/**
* Get an Audience Placeholder by name
*
* <pre>{@code
* Expansion expansion = MiniPlaceholders.expansionByName("player")
* AudiencePlaceholder<?> placeholder = expansion.audiencePlaceholderByName("name");
* }</pre>
* @param name the name of the placeholder to search
* @return the desired placeholder or null
*/
@Nullable AudiencePlaceholder<?> audiencePlaceholderByName(final String name);
/**
* Get a Relational Placeholder by name
*
* <pre>{@code
* Expansion expansion = MiniPlaceholders.expansionByName("expansion")
* RelationalPlaceholder<?> placeholder = expansion.relationalPlaceholderByName("placeholder");
* }</pre>
* @param name the name of the placeholder to search
* @return the desired placeholder or null
* @apiNote The name of a relational placeholder does not contain the prefix “rel”,
* just the name assigned when it was created
*/
@Nullable RelationalPlaceholder<?> relationalPlaceholderByName(final String name);
/**
* Get a Global Placeholder by name
*
* <pre>{@code
* Expansion expansion = MiniPlaceholders.expansionByName("player")
* GlobalPlaceholder placeholder = expansion.globalPlaceholderByName("name");
* }</pre>
* @param name the name of the placeholder to search
* @return the desired placeholder or null
*/
@Nullable GlobalPlaceholder globalPlaceholderByName(final String name);
/**
* Checks whether an audience placeholder with the specified name has been registered in this expansion.
*
* @param name the name of the placeholder to check
* @return true if the placeholder is registered, else false
*/
default boolean hasAudiencePlaceholder(final String name) {
return audiencePlaceholderByName(name) != null;
}
/**
* Checks whether a relational placeholder with the specified name has been registered in this expansion.
*
* @param name the name of the placeholder to check
* @return true if the placeholder is registered, else false
*/
default boolean hasRelationalPlaceholder(final String name) {
return relationalPlaceholderByName(name) != null;
}
/**
* Checks whether a global placeholder with the specified name has been registered in this expansion.
*
* @param name the name of the placeholder to check
* @return true if the placeholder is registered, else false
*/
default boolean hasGlobalPlaceholder(final String name) {
return globalPlaceholderByName(name) != null;
}
/**
* Get a TagResolver that contains all placeholders of a specific type in this expansion.
*
* @param type the desired type
* @return a TagResolver containing all placeholder of a specific type
*/
TagResolver placeholdersByType(PlaceholderType type);
/**
* Creates a new Expansion Builder
* @param name the expansion name
* @return a new expansion builder
* @since 1.0.0
*/
static @NotNull Expansion.Builder builder(final @NotNull String name){
static Expansion.Builder builder(final String name){
return new ExpansionImpl.Builder(name);
}
/**
* Get a shorter ToString with the details of this expansion.
* @return a shorter ToString
*/
@ApiStatus.Internal
default String shortToString() {
final StringBuilder builder = new StringBuilder(name()).append('[');
if (author() != null) {
builder.append("Author: ").append(author());
}
if (version() != null) {
if (author() != null) {
builder.append(", ");
}
builder.append("Version: ").append(version());
}
builder.append(']');
return builder.toString();
}
/**
* Expansion Builder
* <p>Example use:</p>
@@ -112,7 +242,7 @@ public sealed interface Expansion permits ExpansionImpl {
* builder
* // Thanks to this filter, a cast can be performed without the probability of a ClassCastException
* .filter(Player.class)
* .audiencePlaceholder("name", (audience, queue, ctx) -> Tag.selfClosingInserting(Component.text(((Player)audience).getUsername())));
* .resolver("name", (audience, queue, ctx) -> Tag.selfClosingInserting(Component.text(((Player)audience).getUsername())));
* Expansion expansion = builder.build();
* </pre>
*
@@ -120,13 +250,36 @@ public sealed interface Expansion permits ExpansionImpl {
*
* <pre>
* Player player = event.getPlayer();
* TagResolver resolver = expansion.audiencePlaceholder(player);
* TagResolver resolver = expansion.resolver(player);
* Component messageReplaced = MiniMessage.deserialize({@link String}, resolver);
* </pre>
*
* @since 1.0.0
*/
@NullMarked
interface Builder extends AbstractBuilder<Expansion> {
/**
* Adds an audience placeholder
*
* <p>This type of Placeholder depends on a specific audience to obtain its values</p>
*
* <p>The content of this Placeholder is cached
* and can mutate depending on when it is invoked</p>
*
* @param targetClass the target class
* @param key the placeholder key, cannot be an empty or black string
* @param audiencePlaceholder the single placeholder
* @param <A> the type of audience that this placeholder supports, must be the same as targetClass
* @return the {@link Builder} itself
* @since 1.0.0
*/
<A extends Audience> Builder audiencePlaceholder(
final @Nullable Class<A> targetClass,
final String key,
final AudienceTagResolver<A> audiencePlaceholder
);
/**
* Adds an audience placeholder
*
@@ -138,9 +291,36 @@ public sealed interface Expansion permits ExpansionImpl {
* @param key the placeholder key, cannot be an empty or black string
* @param audiencePlaceholder the single placeholder
* @return the {@link Builder} itself
* @since 1.0.0
* @since 3.0.0
*/
@NotNull Builder audiencePlaceholder(final @NotNull String key, final @NotNull AudiencePlaceholder audiencePlaceholder);
default Builder audiencePlaceholder(final String key, final AudienceTagResolver<Audience> audiencePlaceholder) {
return this.audiencePlaceholder(null, key, audiencePlaceholder);
}
/**
* Adds a Relational Placeholder based on two audiences
*
* <p>This type of placeholder allows you to create
* components based on a 2-audiences relationship,
* one is the one on which the placeholder
* is based and the other is the one on which
* the placeholder is displayed.</p>
*
* <p>The content of this Placeholder is cached
* and can mutate depending on when it is invoked</p>
*
* @param targetClass the target class
* @param key the placeholder key, cannot be an empty or black string
* @param relationalPlaceholder the relational placeholder
* @param <A> the type of audience that this placeholder supports, must be the same as targetClass
* @return the {@link Builder} itself
* @since 3.0.0
*/
<A extends Audience> Builder relationalPlaceholder(
final @Nullable Class<A> targetClass,
final String key,
final RelationalTagResolver<A> relationalPlaceholder
);
/**
* Adds a Relational Placeholder based on two audiences
@@ -155,11 +335,16 @@ public sealed interface Expansion permits ExpansionImpl {
* and can mutate depending on when it is invoked</p>
*
* @param key the placeholder key, cannot be an empty or black string
* @param relationalPlaceholder the relational placeholder
* @param relationalResolver the relational placeholder
* @return the {@link Builder} itself
* @since 1.0.0
* @since 3.0.0
*/
@NotNull Builder relationalPlaceholder(final @NotNull String key, final @NotNull RelationalPlaceholder relationalPlaceholder);
default Builder relationalPlaceholder(
final String key,
final RelationalTagResolver<Audience> relationalResolver
) {
return this.relationalPlaceholder(null, key, relationalResolver);
}
/**
* Adds a global placeholder
@@ -167,11 +352,11 @@ public sealed interface Expansion permits ExpansionImpl {
* <p>The content of this Placeholder is cached
* and can mutate depending on when it is invoked</p>
* @param key the placeholder key, cannot be an empty or black string
* @param function the function
* @param resolver the function
* @return the {@link Builder} itself
* @since 1.0.0
* @since 3.0.0
*/
@NotNull Builder globalPlaceholder(final @NotNull String key, final @NotNull BiFunction<@NotNull ArgumentQueue, @NotNull Context, @Nullable Tag> function);
Builder globalPlaceholder(final String key, final GlobalTagResolver resolver);
/**
* Adds a global placeholder
@@ -182,40 +367,28 @@ public sealed interface Expansion permits ExpansionImpl {
* @return the {@link Builder} itself
* @since 1.1.0
*/
@NotNull Builder globalPlaceholder(final @NotNull String key, final @NotNull Tag tag);
default Builder globalPlaceholder(final String key, final Tag tag) {
return this.globalPlaceholder(key, (queue, context) -> tag);
}
/**
* Filter the type of Audiences that this expansion can receive
* <p>In case the {@link Expansion#audiencePlaceholders(Audience)} or
* {@link Expansion#relationalPlaceholders(Audience, Audience)} method is called
* and the provided audiences are not instances of
* the specified class, a {@link TagResolver#empty} will be returned</p>
* Sets the author of this expansion
*
* <p>This eliminates the need to perform a manual
* <pre>if(!(audience instanceof Player)) return TagResolver.empty();</pre>
*
* @param clazz the class to filter
* @param author the author of this expansion
* @return the {@link Builder} itself
* @since 1.0.0
* @since 2.3.0
*/
@Contract("_ -> this")
@NotNull Builder filter(final @Nullable Class<? extends Audience> clazz);
@NotNull Builder author(final @Nullable String author);
/**
* Filters the audiences that this expansion can receive through a Predicate
* <p>Example:</p>
* <pre>
* Expansion.builder("example")
* .filter(aud -> aud instanceof Player player{@literal &&} isInProtectedServer(player)
* .audiencePlaceholder("hello", (aud, queue, ctx) -> Tag.selfInsertingClosing(Component.text("you are in protected server")))
* .build();
* </pre>
* Sets the version of this expansion
*
* @param predicate the check to realize
* @param version the version of this expansion
* @return the {@link Builder} itself
* @since 1.0.0
* @since 2.3.0
*/
@Contract("_ -> this")
@NotNull Builder filter(final @Nullable Predicate<@NotNull Audience> predicate);
@NotNull Builder version(final @Nullable String version);
}
}
@@ -1,226 +1,277 @@
package io.github.miniplaceholders.api;
import io.github.miniplaceholders.api.placeholder.AudiencePlaceholder;
import io.github.miniplaceholders.api.placeholder.RelationalPlaceholder;
import io.github.miniplaceholders.api.placeholder.*;
import io.github.miniplaceholders.api.resolver.AudienceTagResolver;
import io.github.miniplaceholders.api.resolver.GlobalTagResolver;
import io.github.miniplaceholders.api.resolver.RelationalTagResolver;
import io.github.miniplaceholders.api.types.PlaceholderType;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.intellij.lang.annotations.Subst;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import static io.github.miniplaceholders.api.utils.Conditions.nonNullOrEmptyString;
import static java.util.Objects.requireNonNull;
@NullMarked
final class ExpansionImpl implements Expansion {
private static final Tags.Single[] EMPTY_SINGLE_AUDIENCE = new Tags.Single[0];
private static final Tags.Relational[] EMPTY_RELATIONAL_AUDIENCE = new Tags.Relational[0];
private final String name;
private final Tags.Single[] audiencePlaceholders;
private final Tags.Relational[] relationalPlaceholders;
private final TagResolver globalPlaceholders;
private final Class<? extends Audience> filterClass;
private final Predicate<Audience> predicateFilter;
private static final AudiencePlaceholder<?>[] EMPTY_SINGLE_AUDIENCE = new AudiencePlaceholder[0];
private static final RelationalPlaceholder<?>[] EMPTY_RELATIONAL_AUDIENCE = new RelationalPlaceholder[0];
private static final GlobalPlaceholder[] EMPTY_GLOBAL_PLACEHOLDER = new GlobalPlaceholder[0];
ExpansionImpl(
@NotNull final String expansionName,
@Nullable final Collection<Tags.Single> audiencePlaceholders,
@Nullable final Collection<Tags.Relational> relationalPlaceholders,
@Nullable final TagResolver globalPlaceholders,
@Nullable final Class<? extends Audience> filterClass,
@Nullable final Predicate<Audience> predicateFilter){
this.name = expansionName+"-";
this.audiencePlaceholders = audiencePlaceholders != null
? audiencePlaceholders.toArray(EMPTY_SINGLE_AUDIENCE)
: EMPTY_SINGLE_AUDIENCE;
this.relationalPlaceholders = relationalPlaceholders != null
? relationalPlaceholders.toArray(EMPTY_RELATIONAL_AUDIENCE)
: EMPTY_RELATIONAL_AUDIENCE;
this.globalPlaceholders = globalPlaceholders;
this.filterClass = filterClass;
this.predicateFilter = predicateFilter;
private final String name;
private final PlaceholderTagResolver audiencePlaceholders;
private final PlaceholderTagResolver relationalPlaceholders;
private final PlaceholderTagResolver globalPlaceholders;
@Nullable
private final String author;
@Nullable
private final String version;
ExpansionImpl(
final String expansionName,
@Nullable final Collection<AudiencePlaceholder<?>> audiencePlaceholders,
@Nullable final Collection<RelationalPlaceholder<?>> relationalPlaceholders,
@Nullable final Collection<GlobalPlaceholder> globalPlaceholders,
@Nullable final String author,
@Nullable final String version
) {
this.name = expansionName;
this.audiencePlaceholders = audiencePlaceholders != null
? new PlaceholderTagResolver(audiencePlaceholders.toArray(EMPTY_SINGLE_AUDIENCE))
: PlaceholderTagResolver.EMPTY;
this.relationalPlaceholders = relationalPlaceholders != null
? new PlaceholderTagResolver(relationalPlaceholders.toArray(EMPTY_RELATIONAL_AUDIENCE))
: PlaceholderTagResolver.EMPTY;
this.globalPlaceholders = globalPlaceholders == null
? PlaceholderTagResolver.EMPTY
: new PlaceholderTagResolver(globalPlaceholders.toArray(EMPTY_GLOBAL_PLACEHOLDER));
this.author = author;
this.version = version;
}
@Override
public @NotNull String name() {
return this.name;
}
@Override
public @Nullable String author() {
return this.author;
}
@Override
public @Nullable String version() {
return this.version;
}
@Override
public @NotNull TagResolver audiencePlaceholders() {
if (audiencePlaceholders.placeholders().length == 0) return TagResolver.empty();
return this.audiencePlaceholders;
}
@Override
public @NotNull TagResolver relationalPlaceholders() {
if (relationalPlaceholders.placeholders().length == 0) return TagResolver.empty();
return this.relationalPlaceholders;
}
@Override
public @NotNull TagResolver globalPlaceholders() {
if (globalPlaceholders.placeholders().length == 0) return TagResolver.empty();
return this.globalPlaceholders;
}
@Override
public void register() {
if (this.registered()) {
throw new IllegalStateException("Expansion" + this.name + "is already registered");
}
MiniPlaceholders.expansions.add(this);
}
@Override
public void unregister() {
if (!this.registered()) {
throw new IllegalStateException("Expansion " + this.name + " is not registered");
}
MiniPlaceholders.expansions.remove(this);
}
@Override
public boolean registered() {
return MiniPlaceholders.expansions.contains(this);
}
@Override
public Collection<AudiencePlaceholder<?>> registeredAudiencePlaceholders() {
return List.of((AudiencePlaceholder<?>[]) audiencePlaceholders.placeholders());
}
@Override
public Collection<RelationalPlaceholder<?>> registeredRelationalPlaceholders() {
return List.of((RelationalPlaceholder<?>[]) relationalPlaceholders.placeholders());
}
@Override
public @Nullable AudiencePlaceholder<?> audiencePlaceholderByName(String name) {
requireNonNull(name);
for (Placeholder audiencePlaceholder : audiencePlaceholders.placeholders()) {
if (Objects.equals(audiencePlaceholder.name(), name)) {
return (AudiencePlaceholder<?>) audiencePlaceholder;
}
}
return null;
}
@Override
public @Nullable RelationalPlaceholder<?> relationalPlaceholderByName(String name) {
requireNonNull(name);
for (Placeholder relationalPlaceholder : relationalPlaceholders.placeholders()) {
if (Objects.equals(relationalPlaceholder.name(), name)) {
return (RelationalPlaceholder<?>) relationalPlaceholder;
}
}
return null;
}
@Override
public @Nullable GlobalPlaceholder globalPlaceholderByName(String name) {
for (Placeholder placeholder : globalPlaceholders.placeholders()) {
if (Objects.equals(placeholder.name(), name)) {
return (GlobalPlaceholder) placeholder;
}
}
return null;
}
@Override
public TagResolver placeholdersByType(PlaceholderType type) {
return switch (type) {
case AUDIENCE -> audiencePlaceholders();
case RELATIONAL -> relationalPlaceholders();
case GLOBAL -> globalPlaceholders();
};
}
@NullUnmarked
static final class Builder implements Expansion.Builder {
private final String expansionName;
@Subst("server")
private final String expansionPrefix;
private Set<AudiencePlaceholder<?>> audiencePlaceholders;
private Set<RelationalPlaceholder<?>> relationalPlaceholders;
private Set<GlobalPlaceholder> globalPlaceholders;
private String author;
private String version;
Builder(@NotNull final String name) {
this.expansionName = nonNullOrEmptyString(name, "Expansion name");
this.expansionPrefix = name.toLowerCase(Locale.ROOT).concat("_");
}
@Override
public @NotNull String name(){
return this.name;
public <A extends Audience> @NotNull Builder audiencePlaceholder(
final @Nullable Class<A> targetClass,
@Subst("name") final @NotNull String key,
final @NotNull AudienceTagResolver<@NotNull A> audiencePlaceholder
) {
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(audiencePlaceholder, "the audience placeholder cannot be null");
if (this.audiencePlaceholders == null) this.audiencePlaceholders = new HashSet<>();
this.audiencePlaceholders.add(AudiencePlaceholder.single(targetClass, expansionPrefix + key, key, audiencePlaceholder));
return this;
}
@Override
public @NotNull TagResolver audiencePlaceholders(@NotNull Audience audience) {
if (audiencePlaceholders.length == 0) return TagResolver.empty();
public <A extends Audience> @NotNull Builder relationalPlaceholder(
@Nullable Class<A> targetClass,
@Subst("relation") @NotNull String key,
@NotNull RelationalTagResolver<@NotNull A> relationalPlaceholder
) {
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(relationalPlaceholder, "the relational placeholder cannot be null");
requireNonNull(audience, "the audience cannot be null");
if (this.relationalPlaceholders == null) this.relationalPlaceholders = new HashSet<>();
if ((audience = expansionFilter(audience)) == null) return TagResolver.empty();
final TagResolver.Builder placeholders = TagResolver.builder();
for (final Tags.Single pl : this.audiencePlaceholders) {
placeholders.resolver(pl.of(audience));
}
return placeholders.build();
}
@SuppressWarnings("OverrideOnly")
private Audience expansionFilter(final Audience audience) {
if (audience == null || filterClass == null && predicateFilter == null) {
return audience;
}
final boolean eligible = filterClass != null && filterClass.isInstance(audience)
|| predicateFilter != null && predicateFilter.test(audience);
if (eligible) {
return audience;
}
if (audience instanceof final ForwardingAudience.Single forward) {
return expansionFilter(forward.audience());
}
return null;
final var relationalTag = RelationalPlaceholder.relational(targetClass, expansionPrefix + "rel_" + key, key, relationalPlaceholder);
this.relationalPlaceholders.add(relationalTag);
return this;
}
@Override
public @NotNull TagResolver relationalPlaceholders(@NotNull Audience audience, @NotNull Audience otherAudience){
if (relationalPlaceholders.length == 0) return TagResolver.empty();
public @NotNull Builder globalPlaceholder(
@Subst("time") @NotNull final String key,
@NotNull final GlobalTagResolver function
) {
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(function, "the global placeholder cannot be null");
requireNonNull(audience, "the audience cannot be null");
requireNonNull(otherAudience, "the other audience cannot be null");
if (this.globalPlaceholders == null) this.globalPlaceholders = new HashSet<>();
audience = expansionFilter(audience);
otherAudience = expansionFilter(otherAudience);
if (audience == null || otherAudience == null) {
return TagResolver.empty();
}
final TagResolver.Builder placeholders = TagResolver.builder();
for (final Tags.Relational pl : this.relationalPlaceholders) {
placeholders.resolver(pl.of(audience, otherAudience));
}
return placeholders.build();
final var globalResolver = new GlobalPlaceholder(expansionPrefix + key, key, function);
this.globalPlaceholders.add(globalResolver);
return this;
}
@Override
public @NotNull TagResolver globalPlaceholders(){
return this.globalPlaceholders;
public Expansion.@NotNull Builder author(@Nullable final String author) {
this.author = author;
return this;
}
@Override
public void register() {
if (this.registered()) {
throw new IllegalStateException("Expansion" + this.name + "is already registered");
}
MiniPlaceholders.expansions.add(this);
public Expansion.@NotNull Builder version(@Nullable final String version) {
this.version = version;
return this;
}
@Override
public void unregister() {
if (!this.registered()) {
throw new IllegalStateException("Expansion " + this.name + " is not registered");
}
MiniPlaceholders.expansions.remove(this);
public @NotNull Expansion build() {
return new ExpansionImpl(
this.expansionName,
this.audiencePlaceholders,
this.relationalPlaceholders,
this.globalPlaceholders,
this.author,
this.version
);
}
}
@Override
public boolean registered() {
return MiniPlaceholders.expansions.contains(this);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof Expansion that)) return false;
static final class Builder implements Expansion.Builder {
private final String expansionName;
private Set<Tags.Single> audiencePlaceholders;
private Set<Tags.Relational> relationalPlaceholders;
private TagResolver.Builder globalPlaceholders;
private Class<? extends Audience> filterClass;
private Predicate<Audience> predicateFilter;
return that.name().equalsIgnoreCase(this.name());
}
Builder(@NotNull final String name){
this.expansionName = nonNullOrEmptyString(name, "Expansion name")
.toLowerCase(Locale.ROOT).concat("_");
}
@Override
public int hashCode() {
return Objects.hash(name.hashCode());
}
@Override
public @NotNull Builder audiencePlaceholder(@NotNull final String key, @NotNull final AudiencePlaceholder audiencePlaceholder){
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(audiencePlaceholder, "the audience placeholder cannot be null");
if (this.audiencePlaceholders == null) this.audiencePlaceholders = new HashSet<>();
this.audiencePlaceholders.add(Tags.single(expansionName+key, audiencePlaceholder));
return this;
}
@Override
public @NotNull Builder relationalPlaceholder(@NotNull final String key, @NotNull final RelationalPlaceholder relationalPlaceholder){
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(relationalPlaceholder, "the relational placeholder cannot be null");
if (this.relationalPlaceholders == null) this.relationalPlaceholders = new HashSet<>();
this.relationalPlaceholders.add(Tags.relational(expansionName+"rel_"+key, relationalPlaceholder));
return this;
}
@Override
public @NotNull Builder globalPlaceholder(@NotNull final String key, @NotNull final BiFunction<ArgumentQueue, Context, Tag> function){
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(function, "the global placeholder cannot be null");
if (this.globalPlaceholders == null) this.globalPlaceholders = TagResolver.builder();
this.globalPlaceholders.tag(expansionName+key, function);
return this;
}
@Override
public @NotNull Builder globalPlaceholder(@NotNull final String key, @NotNull final Tag tag){
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(tag, "the tag cannot be null");
if (this.globalPlaceholders == null) this.globalPlaceholders = TagResolver.builder();
this.globalPlaceholders.tag(expansionName+key, tag);
return this;
}
@Override
public @NotNull Builder filter(@Nullable final Class<? extends Audience> clazz) {
this.filterClass = clazz;
return this;
}
@Override
public @NotNull Builder filter(@Nullable final Predicate<Audience> predicate){
this.predicateFilter = predicate;
return this;
}
@Override
public @NotNull Expansion build(){
return new ExpansionImpl(
this.expansionName,
this.audiencePlaceholders,
this.relationalPlaceholders,
this.globalPlaceholders != null ? this.globalPlaceholders.build() : TagResolver.empty(),
this.filterClass,
this.predicateFilter
);
}
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof Expansion that)) return false;
return that.name().equalsIgnoreCase(this.name());
}
@Override
public int hashCode(){
return Objects.hash(name.hashCode());
}
@Override
public String toString() {
return "ExpansionImpl{" +
"name='" + name + '\'' +
", audiencePlaceholders=" + audiencePlaceholders +
", relationalPlaceholders=" + relationalPlaceholders +
", globalPlaceholders=" + globalPlaceholders +
", author='" + author + '\'' +
", version='" + version + '\'' +
'}';
}
}
@@ -1,21 +1,24 @@
package io.github.miniplaceholders.api;
import io.github.miniplaceholders.api.enums.Platform;
import io.github.miniplaceholders.api.types.PlaceholderType;
import io.github.miniplaceholders.api.types.Platform;
import io.github.miniplaceholders.api.types.RelationalAudience;
import io.github.miniplaceholders.connect.InternalPlatform;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static io.github.miniplaceholders.api.utils.Resolvers.applyIfNotEmpty;
import static java.util.Objects.requireNonNull;
/**
* MiniPlaceholders
* MiniPlaceholders, a component-based placeholders API.
*
* <p>This class allows you to obtain the {@link TagResolver}
* that other plugins provide based on single {@link Audience},
@@ -31,15 +34,15 @@ public final class MiniPlaceholders {
static final Set<Expansion> expansions = ConcurrentHashMap.newKeySet();
/**
* Get the platform
* Get the platform on which MiniPlaceholders is running.
*
* @return the platform
* @since 1.0.0
* @since 3.0.0
*/
public static @NotNull Platform getPlatform() {
public static @NotNull Platform platform() {
return switch (InternalPlatform.platform()) {
case PAPER -> Platform.PAPER;
case VELOCITY -> Platform.VELOCITY;
case KRYPTON -> Platform.KRYPTON;
case FABRIC -> Platform.FABRIC;
case SPONGE -> Platform.SPONGE;
};
@@ -48,39 +51,38 @@ public final class MiniPlaceholders {
/**
* Get the global placeholders
*
* <pre>TagResolver resolver = MiniPlaceholders.getGlobalPlaceholders();
* <pre>TagResolver resolver = MiniPlaceholders.globalPlaceholders();
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, resolver);</pre>
*
* @return global placeholders independent of any audience
* @see TagResolver
* @since 1.0.0
* @since 3.0.0
*/
public static @NotNull TagResolver getGlobalPlaceholders() {
public static @NotNull TagResolver globalPlaceholders() {
final TagResolver.Builder builder = TagResolver.builder();
for (final Expansion expansion : expansions) {
final TagResolver resolver = expansion.globalPlaceholders();
if (resolver != TagResolver.empty())
builder.resolver(resolver);
applyIfNotEmpty(resolver, builder);
}
return builder.build();
}
/**
* Get the TagResolver based on an Audience
* Gets the TagResolver that can get data from an Audience.
* <br>
* The audience is provided at the time of parsing from the respective MiniMessage instance.
*
* <pre>TagResolver resolver = MiniPlaceholders.getAudiencePlaceholders({@link Audience});
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, resolver);</pre>
* <pre>TagResolver resolver = MiniPlaceholders.audiencePlaceholders();
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, {@link Audience}, resolver);
* </pre>
*
* @param audience the audience
* @return {@link TagResolver} with placeholders based on an audience
* @since 1.0.0
* @since 3.0.0
*/
public static @NotNull TagResolver getAudiencePlaceholders(final @NotNull Audience audience) {
requireNonNull(audience, "audience cannot be null");
public static @NotNull TagResolver audiencePlaceholders() {
final TagResolver.Builder resolvers = TagResolver.builder();
for (Expansion expansion : expansions) {
final TagResolver resolver = expansion.audiencePlaceholders(audience);
for (final Expansion expansion : expansions) {
final TagResolver resolver = expansion.audiencePlaceholders();
applyIfNotEmpty(resolver, resolvers);
}
return resolvers.build();
@@ -88,54 +90,53 @@ public final class MiniPlaceholders {
/**
* Get the relational placeholders based on two audiences
* <br>
* The audiences are provided at the time of parsing
* from the respective MiniMessage instance through the use of a {@link RelationalAudience}.
*
* <pre>TagResolver resolver = MiniPlaceholders.getRelationalPlaceholders({@link Audience}, {@link Audience});
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, resolver);</pre>
* <pre>{@code
* TagResolver resolver = MiniPlaceholders.relationalPlaceholders();
* Component parsed = MiniMessage.miniMessage().deserialize(@link String, {@link RelationalAudience}, resolver);
* }</pre>
*
* @param audience an audience
* @param otherAudience another audience
* @return placeholders based on two audiences
* @since 1.0.0
* @since 3.0.0
* @see RelationalAudience
*/
public static @NotNull TagResolver getRelationalPlaceholders(final @NotNull Audience audience, final @NotNull Audience otherAudience) {
requireNonNull(audience, "audience cannot be null");
requireNonNull(otherAudience, "otherAudience cannot be null");
public static @NotNull TagResolver relationalPlaceholders() {
final TagResolver.Builder builder = TagResolver.builder();
for (final Expansion expansion : expansions) {
applyIfNotEmpty(
expansion.relationalPlaceholders(audience, otherAudience),
builder
);
applyIfNotEmpty(expansion.relationalPlaceholders(), builder);
}
return builder.build();
}
/**
* Get the TagResolver based on an Audience and the global placeholders
* Get a TagResolver that can obtain data based on a relationship of 2 audiences
* and at the same time from the main audience and global placeholders.
* <br>
* The audience is provided at the time of parsing from the respective MiniMessage instance.
*
* <pre>
* TagResolver resolver = MiniPlaceholders.getAudienceGlobalPlaceholders({@link Audience});
* TagResolver resolver = MiniPlaceholders.audienceGlobalPlaceholders();
* TagResolver resolver2 = TagResolver.resolver(
* MiniPlaceholders.getAudienceGlobalPlaceholders({@link Audience}),
* MiniPlaceholders.getGlobalPlaceholders()
* MiniPlaceholders.audienceGlobalPlaceholders(),
* MiniPlaceholders.globalPlaceholders()
* );
* // This two resolvers returns the same TagResolver
* assertEquals(resolver, resolver2);
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, resolver);
* Component parsed = MiniMessage.miniMessage().deserialize({@link String}, {@link Audience}, resolver);
* </pre>
*
* @param audience the audience
* @return {@link TagResolver} with placeholders based on an audience and the global placeholders
* @since 1.1.0
*/
public static @NotNull TagResolver getAudienceGlobalPlaceholders(final @NotNull Audience audience) {
requireNonNull(audience, "audience cannot be null");
public static @NotNull TagResolver audienceGlobalPlaceholders() {
final TagResolver.Builder builder = TagResolver.builder();
for (final Expansion expansion : expansions) {
applyIfNotEmpty(expansion.audiencePlaceholders(audience), builder);
applyIfNotEmpty(expansion.audiencePlaceholders(), builder);
applyIfNotEmpty(expansion.globalPlaceholders(), builder);
}
@@ -147,30 +148,27 @@ public final class MiniPlaceholders {
* and the global placeholders
*
* <pre>
* TagResolver resolver = MiniPlaceholders.getRelationalGlobalPlaceholders({@link Audience}, {@link Audience});
* TagResolver resolver = MiniPlaceholders.relationalGlobalPlaceholders();
* TagResolver resolver2 = TagResolver.resolver(
* MiniPlaceholders.getRelationalPlaceholders(audience1, audience2),
* MiniPlaceholders.getAudiencePlaceholders(audience1),
* MiniPlaceholders.getGlobalPlaceholders()
* MiniPlaceholders.relationalPlaceholders(),
* MiniPlaceholders.audiencePlaceholders(),
* MiniPlaceholders.globalPlaceholders()
* );
* // This methods should return the same TagResolver
* assertEquals(resolver, resolver2);
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, resolver);
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, {@link RelationalAudience}, resolver);
* </pre>
*
* @param audience an audience
* @param otherAudience another audience
* @return the placeholders based on two audiences, placeholders based on the first audience and the global placeholders
* @since 1.1.0
* @since 3.0.0
* @apiNote In the case of audience placeholders, the audience to be used will be the {@link RelationalAudience#audience()}
* @see RelationalAudience
*/
public static @NotNull TagResolver getRelationalGlobalPlaceholders(final @NotNull Audience audience, final @NotNull Audience otherAudience) {
requireNonNull(audience, "audience cannot be null");
requireNonNull(otherAudience, "otherAudience cannot be null");
public static @NotNull TagResolver relationalGlobalPlaceholders() {
final TagResolver.Builder builder = TagResolver.builder();
for (final Expansion expansion : expansions) {
applyIfNotEmpty(expansion.audiencePlaceholders(audience), builder);
applyIfNotEmpty(expansion.relationalPlaceholders(audience, otherAudience), builder);
applyIfNotEmpty(expansion.audiencePlaceholders(), builder);
applyIfNotEmpty(expansion.relationalPlaceholders(), builder);
applyIfNotEmpty(expansion.globalPlaceholders(), builder);
}
@@ -178,11 +176,33 @@ public final class MiniPlaceholders {
}
/**
* Get the amount of expansion registered
* @return the amount of expansions registered
* @since 1.0.0
* Get a TagResolver based on the desired placeholder type.
* <br>
* {@link PlaceholderType#GLOBAL} will return {@link #globalPlaceholders()}
* <br>
* {@link PlaceholderType#AUDIENCE} will return {@link #audiencePlaceholders()}
* <br>
* {@link PlaceholderType#RELATIONAL} will return {@link #relationalPlaceholders()}
*
* @param type the desired type
* @return the TagResolvers from the respective type
* @see PlaceholderType
*/
public static int getExpansionCount(){
public static TagResolver placeholdersByType(final PlaceholderType type) {
return switch (type) {
case GLOBAL -> globalPlaceholders();
case AUDIENCE -> audiencePlaceholders();
case RELATIONAL -> relationalPlaceholders();
};
}
/**
* Get the amount of expansion registered.
*
* @return the amount of expansions registered
* @since 3.0.0
*/
public static int expansionCount(){
return expansions.size();
}
@@ -193,14 +213,15 @@ public final class MiniPlaceholders {
* Expansion expansion = Expansion.builder("example").build();
* expansion.register();
*
* assertThat(MiniPlaceholders.getExpansionByName("example")).isNotNull();
* assertThat(MiniPlaceholders.expansionByName("example")).isNotNull();
* </pre>
*
* @param name the name of the required expansion
* @return the required expansion, if not present, will return null
* @see Expansion#builder(String)
* @since 2.1.0
* @since 3.0.0
*/
public static @Nullable Expansion getExpansionByName(final @NotNull String name) {
public static @Nullable Expansion expansionByName(final @NotNull String name) {
for (final Expansion expansion : expansions) {
if (Objects.equals(expansion.name(), name)) {
return expansion;
@@ -208,4 +229,14 @@ public final class MiniPlaceholders {
}
return null;
}
/**
* Obtain all available registered expansions
*
* @return all available registered expansions
* @since 3.0.0
*/
public static Collection<Expansion> expansionsAvailable() {
return Collections.unmodifiableSet(expansions);
}
}
@@ -1,107 +0,0 @@
package io.github.miniplaceholders.api;
import java.util.Objects;
import io.github.miniplaceholders.api.placeholder.AudiencePlaceholder;
import io.github.miniplaceholders.api.placeholder.RelationalPlaceholder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.ParsingException;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
final class Tags {
private Tags() {}
static Relational relational(@NotNull final String name, @NotNull final RelationalPlaceholder relationalPlaceholder){
return new Tags.Relational(name, relationalPlaceholder);
}
static Single single(@NotNull final String name, @NotNull final AudiencePlaceholder audiencePlaceholder){
return new Single(name, audiencePlaceholder);
}
static final class Relational {
private final RelationalPlaceholder relationalPlaceholder;
private final String key;
Relational(final String key, final RelationalPlaceholder relationalPlaceholder){
this.relationalPlaceholder = relationalPlaceholder;
this.key = key;
}
TagResolver of(@NotNull final Audience audience, @NotNull final Audience otherAudience){
return new TagResolver() {
@Override
public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments,
@NotNull Context ctx) throws ParsingException {
return this.has(name)
? relationalPlaceholder.tag(audience, otherAudience, arguments, ctx)
: null;
}
@Override
public boolean has(@NotNull String name) {
return key.equalsIgnoreCase(name);
}
};
}
@Override
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof final Relational that)) return false;
return that.key.equalsIgnoreCase(this.key);
}
@Override
public int hashCode(){
return Objects.hash(key);
}
}
static final class Single {
private final AudiencePlaceholder audiencePlaceholder;
private final String key;
private Single(@NotNull final String key, @NotNull final AudiencePlaceholder audiencePlaceholder){
this.key = key;
this.audiencePlaceholder = audiencePlaceholder;
}
TagResolver of(@NotNull final Audience audience){
return new TagResolver() {
@Override
public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments,
@NotNull Context ctx) throws ParsingException {
return this.has(name)
? audiencePlaceholder.tag(audience, arguments, ctx)
: null;
}
@Override
public boolean has(@NotNull String name) {
return key.equalsIgnoreCase(name);
}
};
}
@Override
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof final Single that)) return false;
return that.key.equalsIgnoreCase(this.key);
}
@Override
public int hashCode(){
return Objects.hash(key);
}
}
}
@@ -1,15 +0,0 @@
package io.github.miniplaceholders.api.enums;
/**Platforms on which MiniPlaceholders can be used */
public enum Platform {
/**Paper Platform */
PAPER,
/**Velocity Platform */
VELOCITY,
/**Krypton Platform */
KRYPTON,
/**Fabric Platform */
FABRIC,
/**Sponge Platform */
SPONGE
}
@@ -1,2 +0,0 @@
/**Plugin enums */
package io.github.miniplaceholders.api.enums;
@@ -1,27 +1,112 @@
package io.github.miniplaceholders.api.placeholder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import io.github.miniplaceholders.api.resolver.AudienceTagResolver;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import net.kyori.adventure.pointer.Pointered;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.TagPattern;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
/**Audience Placeholder based on an Audience */
@FunctionalInterface
public interface AudiencePlaceholder {
/**
* A Tag based on an Audience
* @param audience the audience
* @param queue the argument queue
* @param ctx the context
* @return a Tag
* @since 1.0.0
*/
@Nullable Tag tag(
@NotNull final Audience audience,
@NotNull final ArgumentQueue queue,
@NotNull final Context ctx
);
}
import java.util.Objects;
import static java.util.Objects.requireNonNull;
/**
* A placeholder that can be applied based on an audience of the specified type.
*
* @param targetClass the type of audience from which this placeholder can obtain information
* @param key the full placeholder key
* @param name the name of this placeholder
* @param resolver the object responsible for obtaining audience information
* @param <A> the parametrized type
*/
@NullMarked
public record AudiencePlaceholder<A extends Audience>(
@Nullable Class<A> targetClass,
@TagPattern String key,
String name,
AudienceTagResolver<A> resolver
) implements Placeholder {
/**
* Creates a new AudiencePlaceholder.
*
* @param targetClass the type of audience from which this placeholder can obtain information
* @param key the placeholder key
* @param name the name of this placeholder
* @param resolver the object responsible for obtaining audience information
* @return a new AudiencePlaceholder
* @param <A> the parametrized type
*/
public static <A extends Audience> AudiencePlaceholder<A> single(
@Nullable Class<A> targetClass,
@TagPattern String key,
String name,
AudienceTagResolver<A> resolver
) {
return new AudiencePlaceholder<>(targetClass, requireNonNull(key), requireNonNull(name), requireNonNull(resolver));
}
@Override
public @Nullable Tag resolve(final String name, final ArgumentQueue arguments, final Context ctx) {
final Pointered targetRaw = ctx.target();
if (targetRaw == null) {
return null;
}
if (targetClass == null) {
//noinspection unchecked
return this.resolveA(name, (A) targetRaw, arguments, ctx);
}
final @Nullable A audience = forwardingFilter(targetRaw);
if (audience == null) {
return null;
}
return resolveA(name, audience, arguments, ctx);
}
private @Nullable Tag resolveA(String key, A audience, ArgumentQueue queue, Context ctx) {
return this.has(key)
? resolver.tag(audience, queue, ctx)
: null;
}
@SuppressWarnings("OverrideOnly")
private @Nullable A forwardingFilter(final Pointered source) {
//noinspection DataFlowIssue
if (targetClass.isInstance(source)) {
return targetClass.cast(source);
}
if (source instanceof final ForwardingAudience.Single forward) {
return forwardingFilter(forward.audience());
}
return null;
}
@Override
public boolean has(String name) {
return key.equalsIgnoreCase(name);
}
@Override
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof AudiencePlaceholder<?> that)) return false;
return that.key.equalsIgnoreCase(this.key);
}
@Override
public int hashCode() {
return Objects.hash(key);
}
@Override
public String toString() {
return "AudiencePlaceholder{" +
"key='" + key + '\'' +
'}';
}
}
@@ -0,0 +1,50 @@
package io.github.miniplaceholders.api.placeholder;
import io.github.miniplaceholders.api.resolver.GlobalTagResolver;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.TagPattern;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* A placeholder that can obtain data without the need to provide an {@link net.kyori.adventure.audience.Audience}
* or {@link io.github.miniplaceholders.api.types.RelationalAudience}.
*
* @param key the placeholder key
* @param name the placeholder name
* @param resolver the object responsible for providing information
*/
public record GlobalPlaceholder(@TagPattern String key, String name, GlobalTagResolver resolver) implements Placeholder {
@Override
public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) {
return this.has(name) ? resolver.tag(arguments, ctx) : null;
}
@Override
public boolean has(@NotNull String name) {
return key.equalsIgnoreCase(name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof GlobalPlaceholder(var oKey, var __, var ___)) {
return Objects.equals(oKey, this.key);
}
return false;
}
@Override
public String toString() {
return "GlobalPlaceholder{" +
"key='" + key + '\'' +
"name=" + name + '\'' +
'}';
}
}
@@ -0,0 +1,28 @@
package io.github.miniplaceholders.api.placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jspecify.annotations.NullMarked;
/**
* Base class of all Placeholders
*/
@NullMarked
public sealed interface Placeholder extends TagResolver permits AudiencePlaceholder, GlobalPlaceholder, RelationalPlaceholder {
/**
* The key of this placeholder.<br>
* For example, the placeholder <pre>{@code <expansion_placeholder}</pre>
* will have the key <code>expansion_placeholder</code>
*
* @return this placeholder's key
*/
String key();
/**
* The key of this placeholder.<br>
* For example, the placeholder <pre>{@code <expansion_placeholder}</pre>
* will have the key <code>placeholder</code>
*
* @return this placeholder's name
*/
String name();
}
@@ -0,0 +1,39 @@
package io.github.miniplaceholders.api.placeholder;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.ParsingException;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A TagResolver responsible for containing and processing multiple placeholders.
* @param placeholders the placeholders to process
*/
public record PlaceholderTagResolver(Placeholder... placeholders) implements TagResolver {
public static final PlaceholderTagResolver EMPTY = new PlaceholderTagResolver();
@Override
public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) throws ParsingException {
@Nullable Tag tag;
for (final Placeholder placeholder : placeholders) {
tag = placeholder.resolve(name, arguments, ctx);
if (tag != null) {
return tag;
}
}
return null;
}
@Override
public boolean has(final @NotNull String name) {
for (final Placeholder placeholder : placeholders) {
if (placeholder.has(name)) {
return true;
}
}
return false;
}
}
@@ -1,29 +1,137 @@
package io.github.miniplaceholders.api.placeholder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import io.github.miniplaceholders.api.types.RelationalAudience;
import io.github.miniplaceholders.api.resolver.RelationalTagResolver;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import net.kyori.adventure.pointer.Pointered;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.TagPattern;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
/**Relational Placeholder based on two Audiences*/
@FunctionalInterface
public interface RelationalPlaceholder {
/**
* A Tag based on two audiences
* @param audience the principal audience
* @param otherAudience the another audience
* @param queue the argument queue
* @param ctx the context
* @return a tag
* @since 1.0.0
*/
@Nullable Tag tag(
@NotNull final Audience audience,
@NotNull final Audience otherAudience,
@NotNull final ArgumentQueue queue,
@NotNull final Context ctx
);
}
import java.util.Objects;
import static java.util.Objects.requireNonNull;
/**
* Relational Placeholder.
* <br>
* This placeholder is able to obtain data based on a relationship between 2 audiences
* using a type-safe filter and a relational resolver.
*
* @param targetClass the target class, can be null
* @param key the key of the entire placeholder
* @param name the name of this placeholder
* @param resolver the relational resolver,
* in charge of obtaining data based on a relationship of 2 audiences
* @param <A> the audience type, if not specified, will be a generic {@linkplain Audience}
* @see RelationalTagResolver
* @apiNote The {@link #key()} method returns the full name of the placeholder,
* for example, if the placeholder is {@code <server_name>},
* the {@linkplain #key()} method will return {@code server_name}.
*/
@NullMarked
public record RelationalPlaceholder<A extends Audience>(
@Nullable Class<A> targetClass,
@TagPattern String key,
String name,
RelationalTagResolver<A> resolver
) implements Placeholder {
/**
* Static method capable of creating an instance of a relational placeholder.
*
* @param targetClass the target class, can be null
* @param key the key of the entire placeholder
* @param name the name of this placeholder
* @param resolver the relational resolver,
* in charge of obtaining data based on a relationship of 2 audiences
* @return a new RelationalPlaceholder
* @param <A> the audience type, if not specified, will be a generic {@linkplain Audience}
* @since 3.0.0
*/
public static <A extends Audience> RelationalPlaceholder<A> relational(
@Nullable Class<A> targetClass,
@TagPattern String key,
String name,
RelationalTagResolver<A> resolver
) {
return new RelationalPlaceholder<>(targetClass, requireNonNull(key), name, requireNonNull(resolver));
}
@Override
public @Nullable Tag resolve(String name, ArgumentQueue arguments, Context ctx) {
final Pointered targetRaw = ctx.target();
if (targetRaw == null) {
return null;
}
if (!(targetRaw instanceof RelationalAudience<?>(Audience a, Audience b))) {
return null;
}
if (targetClass == null) {
//noinspection unchecked
return this.resolveA(name, (A)a, (A)b, arguments, ctx);
}
final @Nullable A audience = forwardingFilter(a);
if (audience == null) {
return null;
}
final @Nullable A relationalAudience = forwardingFilter(b);
if (relationalAudience == null) {
return null;
}
return this.resolveA(name, audience, relationalAudience, arguments, ctx);
}
private @Nullable Tag resolveA(
String name,
A audience,
A relationalAudience,
ArgumentQueue arguments,
Context ctx
) {
return this.has(name)
? resolver.tag(audience, relationalAudience, arguments, ctx)
: null;
}
@SuppressWarnings("OverrideOnly")
private @Nullable A forwardingFilter(final Pointered source) {
//noinspection DataFlowIssue
if (targetClass.isInstance(source)) {
return targetClass.cast(source);
}
if (source instanceof final ForwardingAudience.Single forward) {
return forwardingFilter(forward.audience());
}
return null;
}
@Override
public boolean has(@NotNull String name) {
return key.equalsIgnoreCase(name);
}
@Override
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof final RelationalPlaceholder<?> that)) return false;
return that.key.equalsIgnoreCase(this.key);
}
@Override
public int hashCode() {
return Objects.hash(key);
}
@Override
public String toString() {
return "RelationalPlaceholder{" +
"key='" + key + '\'' +
'}';
}
}
@@ -1,4 +1,6 @@
/**
* Placeholders available based on information obtained from Audiences
* Placeholders Package.
*
* @since 3.0.0
*/
package io.github.miniplaceholders.api.placeholder;
package io.github.miniplaceholders.api.placeholder;
@@ -0,0 +1,27 @@
package io.github.miniplaceholders.api.provider;
import io.github.miniplaceholders.api.Expansion;
/**
* An expansion provider.
*
* @apiNote If necessary, you can obtain platform-dependent instances
* by adding a non-final field of the {@link PlatformData} class
* with the {@link team.unnamed.inject.Inject} annotation to your ExpansionProvider class.
*/
public interface ExpansionProvider {
/**
* Provides an expansion.
*
* @return an expansion
* @apiNote If used outside its MiniPlaceholders dynamic expansion loading context,
* you should always check that the loading requirements specified in {@link #loadRequirement} are met.
*/
Expansion provideExpansion();
/**
* Obtain the requirements that must be met for this expansion to load successfully.
* @return the load requirements of this expansion provider
*/
LoadRequirement loadRequirement();
}
@@ -0,0 +1,138 @@
package io.github.miniplaceholders.api.provider;
import io.github.miniplaceholders.api.types.Platform;
import net.kyori.examination.Examinable;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Objects.requireNonNull;
/**
* This class specifies the conditions that must be met
* in order to load an {@link io.github.miniplaceholders.api.Expansion} from an {@link ExpansionProvider}.
*/
public sealed interface LoadRequirement {
/**
* It means that there is no requirement to load an expansion.
* @return a LoadRequirement that does not perform any validation
*/
static LoadRequirement none() {
return NoneLoadRequirement.INSTANCE;
}
/**
* Requirement for a complement, whether a plugin or mod, to be present in order for an expansion to be loaded.<br>
* The ability to provide multiple strings is provided because names may vary on some platforms.
* For example, Velocity requires plugin IDs to be lowercase, such as {@code miniplaceholders},
* while Paper can contain uppercase letters, such as {@code MiniPlaceholders}.
* @param name the principal name of this complement
* @param aliases the platform aliases of a complement
* @return a load requirement that checks for an available complement
*/
static LoadRequirement requiredComplement(final String name, String... aliases) {
requireNonNull(name);
return new AvailableComplementRequirement(name, aliases);
}
/**
* Multiple requirement, in case an expansion needs to depend on a platform
* as well as one or more add-ons that are installed.
*
* @param requirements the load requirements
* @return a load requirement of many load requirements
*/
static LoadRequirement allOf(LoadRequirement... requirements) {
requireNonNull(requirements);
assert requirements.length != 0;
return new MultiLoadRequirement(requirements);
}
/**
* Requirement that the current execution be on a specific platform.
* @param platforms the supported platforms
* @return a load requirement that checks for a supported platform
*/
static LoadRequirement platform(Platform... platforms) {
requireNonNull(platforms);
assert platforms.length != 0;
return new PlatformRequirement(platforms);
}
/**
* NoneLoadRequirement
*/
@ApiStatus.Internal
enum NoneLoadRequirement implements LoadRequirement {
/**
* NoneLoadRequirement instance
*/
INSTANCE
}
/**
* AvailableComplementRequirement
* @param name the main complement name
* @param platformAliases the complement aliases
*/
@ApiStatus.Internal
@NullMarked
record AvailableComplementRequirement(String name, String... platformAliases) implements LoadRequirement {
/**
* Checks if a complement is loaded
* @param platformComplementTester the checker predicate
* @return is this expansion should load
*/
public boolean shouldLoad(Predicate<String> platformComplementTester) {
if (platformComplementTester.test(name)) {
return true;
}
for (String alias : platformAliases) {
if (platformComplementTester.test(alias)) {
return true;
}
}
return false;
}
@Override
public String toString() {
return "AvailableComplementRequirement{" +
"complement='" + name + '\'' +
'}';
}
}
/**
* MultiLoadRequirement
* @param requirements the load requirements
*/
@ApiStatus.Internal
@NullMarked
record MultiLoadRequirement(LoadRequirement... requirements) implements LoadRequirement {
}
/**
* PlatformLoadRequirement
* @param platforms the platforms supported
*/
@ApiStatus.Internal
@NullMarked
record PlatformRequirement(Platform... platforms) implements LoadRequirement, Examinable {
@Override
public String toString() {
final String platforms = Stream.of(this.platforms)
.map(Platform::name)
.collect(Collectors.joining(", "));
return "PlatformRequirement{" +
"platforms=" + platforms +
'}';
}
}
}
@@ -0,0 +1,21 @@
package io.github.miniplaceholders.api.provider;
import io.github.miniplaceholders.api.MiniPlaceholders;
/**
* Wrapper for platform-dependent instances.
*
* @param serverInstance The server instance.<br>
* In Paper it will be {@code org.bukkit.Server},<br>
* in Velocity it will be {@code com.velocitypowered.api.proxy.ProxyServer},<br>
* in Sponge it will be {@code org.spongepowered.api.Server},<br>
* and in Fabric it will be {@code net.minecraft.server.MinecraftServer}.
* @param complementInstance The main class of MiniPlaceholders, either as a plugin or mod.<br>
* In Paper it will be {@code org.bukkit.plugin.java.JavaPlugin},<br>
* in Velocity it will be {@code io.github.miniplaceholders.velocity.VelocityPlugin},<br>
* in Sponge it will be {@code io.github.miniplaceholders.sponge.SpongePlugin},<br>
* and in Fabric it will be {@code net.fabricmc.api.ModInitializer}.
* @see MiniPlaceholders#platform()
*/
public record PlatformData(Object serverInstance, Object complementInstance) {
}
@@ -0,0 +1,29 @@
package io.github.miniplaceholders.api.resolver;
import org.jetbrains.annotations.Nullable;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import org.jspecify.annotations.NullMarked;
/**
* Audience Placeholder based on an Audience
*
* @param <A> the audience type
*/
@FunctionalInterface
@NullMarked
public interface AudienceTagResolver<A extends Audience> {
/**
* A Tag based on an Audience
* @param audience the audience
* @param queue the argument queue
* @param ctx the context
* @return a Tag
* @since 3.0.0
*/
@Nullable
Tag tag(final A audience, final ArgumentQueue queue, final Context ctx);
}
@@ -0,0 +1,24 @@
package io.github.miniplaceholders.api.resolver;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
/**
* A GlobalResolver is responsible for providing information regardless of additional conditions such as an audience.
*/
@FunctionalInterface
public interface GlobalTagResolver {
/**
* A global tag.
*
* @param queue the argument queue
* @param context the parsing context
* @return a tag
* @since 3.0.0
*/
@Nullable
Tag tag(@NonNull ArgumentQueue queue, @NonNull Context context);
}
@@ -0,0 +1,32 @@
package io.github.miniplaceholders.api.resolver;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
/**
* Relational Placeholder based on two Audiences.
*
* @param <A> the audiences type
*
* @since 3.0.0
*/
@FunctionalInterface
@NullMarked
public interface RelationalTagResolver<A extends Audience> {
/**
* A Tag based on two audiences
*
* @param audience the principal audience
* @param relational the another audience
* @param queue the argument queue
* @param ctx the context
* @return a tag
* @since 3.0.0
*/
@Nullable
Tag tag(final A audience, final A relational, final ArgumentQueue queue, final Context ctx);
}
@@ -0,0 +1,4 @@
/**
* Resolvers available based on information obtained from Audiences
*/
package io.github.miniplaceholders.api.resolver;
@@ -0,0 +1,27 @@
package io.github.miniplaceholders.api.types;
/**
* Type of Placeholder.
*
* @since 3.0.0
*/
public enum PlaceholderType {
/**
* The global placeholder type.
* <br>
* It does not require any type of audience to operate.
*/
GLOBAL,
/**
* The audience placeholder type.
* <br>
* Obtains information from a provided audience.
*/
AUDIENCE,
/**
* The relational placeholder type.
* <br>
* Requires 2 audiences to work, these audiences need to be of the same type.
*/
RELATIONAL
}
@@ -0,0 +1,33 @@
package io.github.miniplaceholders.api.types;
/**
* Platforms on which MiniPlaceholders can be used.
*
* @since 1.0.0
*/
public enum Platform {
/**
* Paper Platform
*
* @since 1.0.0
*/
PAPER,
/**
* Velocity Platform
*
* @since 1.0.0
*/
VELOCITY,
/**
* Fabric Platform
*
* @since 2.1.0
*/
FABRIC,
/**
* Sponge Platform
*
* @since 2.2.0
*/
SPONGE
}
@@ -0,0 +1,29 @@
package io.github.miniplaceholders.api.types;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import org.jspecify.annotations.NullMarked;
/**
* A relationship between 2 Audiences.
*
* @param audience the principal audience
* @param relational the secondary audience, with which the relationship will be evaluated
* @param <A> the audiences type
* @since 3.0.0
*/
@NullMarked
public record RelationalAudience<A extends Audience>(A audience, A relational) implements ForwardingAudience.Single {
/**
* Creates a new relation between two audiences.
*
* @param audience the principal audience
* @param relational the secondary audience, with which the relationship will be evaluated
* @return a new relation
* @param <A> the audiences type
* @since 3.0.0
*/
public static <A extends Audience> RelationalAudience<A> from(A audience, A relational) {
return new RelationalAudience<>(audience, relational);
}
}
@@ -0,0 +1,2 @@
/**Plugin enums */
package io.github.miniplaceholders.api.types;
@@ -8,23 +8,27 @@ import net.kyori.adventure.text.format.NamedTextColor;
public final class Components {
private Components(){}
/**
* "True" Component
* "True" Component.
*
* @since 1.1.0
*/
public static final TextComponent TRUE_COMPONENT = Component.text("true", NamedTextColor.GREEN);
/**
* False Component
* "False" Component.
*
* @since 1.1.0
*/
public static final TextComponent FALSE_COMPONENT = Component.text("false", NamedTextColor.RED);
/**
* "Yes" Component
* "Yes" Component.
*
* @since 1.1.0
*/
public static final TextComponent YES_COMPONENT = Component.text("yes", NamedTextColor.GREEN);
/**
* "No" Component
* "No" Component.
*
* @since 1.1.0
*/
public static final TextComponent NO_COMPONENT = Component.text("no", NamedTextColor.RED);
@@ -5,70 +5,75 @@ import org.jetbrains.annotations.Nullable;
import java.util.function.Supplier;
/** Checks required for the correct operation of the expansions*/
/**
* Checks required for the correct operation of the expansions
*
* @since 1.0.0
*/
public final class Conditions {
private Conditions() {}
private Conditions() {
}
/**
* Checks if a string is null or empty or consists of whitespace characters only.
*
* @param string the string
* @param name the name supplier
* @return the string if it is not null or empty
* @since 1.0.0
* @throws IllegalStateException if the provided string is empty
*/
public static String nonNullOrEmptyString(@Nullable String string, @NotNull Supplier<@NotNull String> name){
if (string == null)
throw new NullPointerException(name.get() + " cannot be null");
if (string.isBlank())
throw new IllegalStateException(name.get() + "cannot be an empty string");
return string;
}
/**
* Checks if a string is null or empty or consists of whitespace characters only.
*
* @param string the string
* @param name the name supplier
* @return the string if it is not null or empty
* @throws IllegalStateException if the provided string is empty
* @since 1.0.0
*/
public static String nonNullOrEmptyString(@Nullable String string, @NotNull Supplier<@NotNull String> name) {
if (string == null)
throw new NullPointerException(name.get() + " cannot be null");
if (string.isBlank())
throw new IllegalStateException(name.get() + "cannot be an empty string");
return string;
}
/**
* Checks if a string is null or empty or consists of whitespace characters only.
*
* @param string the string
* @param name the name
* @return the string if it is not null or empty
* @since 2.2.0
* @throws IllegalStateException if the provided string is empty
*/
public static String nonNullOrEmptyString(@Nullable String string, @NotNull String name){
if (string == null)
throw new NullPointerException(name + " cannot be null");
if (string.isBlank())
throw new IllegalStateException(name + "cannot be an empty string");
return string;
}
/**
* Checks if a string is null or empty or consists of whitespace characters only.
*
* @param string the string
* @param name the name
* @return the string if it is not null or empty
* @throws IllegalStateException if the provided string is empty
* @since 2.2.0
*/
public static String nonNullOrEmptyString(@Nullable String string, @NotNull String name) {
if (string == null)
throw new NullPointerException(name + " cannot be null");
if (string.isBlank())
throw new IllegalStateException(name + "cannot be an empty string");
return string;
}
/**
* Check if a string is empty or consists of whitespace characters only.
*
* @param string the string
* @param reason the reason supplier
* @return the string if it not empty
* @since 1.0.0
* @throws IllegalStateException if the provided string is empty
*/
public static String nonEmptyString(@NotNull String string, @NotNull Supplier<@NotNull String> reason){
if (string.isBlank()) throw new IllegalStateException(reason.get());
return string;
}
/**
* Check if a string is empty or consists of whitespace characters only.
*
* @param string the string
* @param reason the reason supplier
* @return the string if it not empty
* @throws IllegalStateException if the provided string is empty
* @since 1.0.0
*/
public static String nonEmptyString(@NotNull String string, @NotNull Supplier<@NotNull String> reason) {
if (string.isBlank()) throw new IllegalStateException(reason.get());
return string;
}
/**
* Check if a string is empty or consists of whitespace characters only.
*
* @param string the string
* @param reason the reason
* @return the string if it not empty
* @throws IllegalStateException if the provided string is empty
* @since 2.2.0
*/
public static String nonEmptyString(@NotNull String string, @NotNull String reason) {
if (string.isBlank()) throw new IllegalStateException(reason);
return string;
}
/**
* Check if a string is empty or consists of whitespace characters only.
*
* @param string the string
* @param reason the reason
* @return the string if it not empty
* @since 2.2.0
* @throws IllegalStateException if the provided string is empty
*/
public static String nonEmptyString(@NotNull String string, @NotNull String reason){
if (string.isBlank()) throw new IllegalStateException(reason);
return string;
}
}
@@ -0,0 +1,164 @@
package io.github.miniplaceholders.api.utils;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.function.Function;
import static java.util.Objects.requireNonNull;
import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
/**
* Legacy String utilities.
*
* @since 3.0.0
*/
@NullMarked
public final class LegacyStrings {
private LegacyStrings() {
}
/**
* Legacy serializer with hex support
* <p>Use this as the last available alternative,
* it is recommended to use MiniMessage instead of this serializer</p>
*/
public static final LegacyComponentSerializer LEGACY_HEX_SERIALIZER = LegacyComponentSerializer.builder()
.character('&')
.hexColors()
.build();
/**
* Parse a string with possible legacy Ampersand/Section symbols
* <br>
* <p><b>
* This method should only be used in case a String is provided from a legacy plugin/mod
* or legacy source, where it is very likely to get a legacy string.
* It is not recommended to use this method regularly in any sense
* </b></p>
*
* @param string the string
* @return a parsed string
* @since 1.0.0
*/
public static Component parsePossibleLegacy(@Nullable String string) {
if (string == null || string.isEmpty()) return Component.empty();
if (string.indexOf(LegacyComponentSerializer.SECTION_CHAR) != -1) {
string = string.replace(LegacyComponentSerializer.SECTION_CHAR, LegacyComponentSerializer.AMPERSAND_CHAR);
}
if (string.indexOf(LegacyComponentSerializer.AMPERSAND_CHAR) == -1) {
return miniMessage().deserialize(string);
}
return miniMessage().deserialize(
miniMessage().serialize(LEGACY_HEX_SERIALIZER.deserialize(string))
.replace("\\<", "<")
.replace("\\>", ">")
);
}
/**
* Parse a string with possible legacy Ampersand/Section symbols
* using a parsing Context
* <p><b>
* This method should only be used in case a String is provided from a legacy plugin/mod
* or legacy source, where it is very likely to get a legacy string.
* It is not recommended to use this method regularly in any sense
* </b></p>
*
* @param string the string
* @param context the provided parsing context
* @return a parsed string
* @since 2.2.1
*/
public static Component parsePossibleLegacy(@Nullable String string, final Context context) {
if (string == null || string.isEmpty()) return Component.empty();
if (string.indexOf(LegacyComponentSerializer.SECTION_CHAR) != -1) {
string = string.replace(LegacyComponentSerializer.SECTION_CHAR, LegacyComponentSerializer.AMPERSAND_CHAR);
}
if (string.indexOf(LegacyComponentSerializer.AMPERSAND_CHAR) == -1) {
return context.deserialize(string);
}
return context.deserialize(
miniMessage().serialize(LEGACY_HEX_SERIALIZER.deserialize(string))
.replace("\\<", "<")
.replace("\\>", ">")
);
}
/**
* Parse a string with possible legacy Ampersand/Section symbols
* using the provided resolver
* <p><b>
* This method should only be used in case a String is provided from a legacy plugin/mod
* or legacy source, where it is very likely to get a legacy string.
* It is not recommended to use this method regularly in any sense
* </b></p>
*
* @param string the string
* @param resolver a resolver
* @return a parsed string
* @since 2.2.1
*/
public static Component parsePossibleLegacy(@Nullable String string, final TagResolver resolver) {
if (string == null || string.isEmpty()) return Component.empty();
if (string.indexOf(LegacyComponentSerializer.SECTION_CHAR) != -1) {
string = string.replace(LegacyComponentSerializer.SECTION_CHAR, LegacyComponentSerializer.AMPERSAND_CHAR);
}
if (string.indexOf(LegacyComponentSerializer.AMPERSAND_CHAR) == -1) {
return miniMessage().deserialize(string, resolver);
}
return miniMessage().deserialize(
miniMessage().serialize(LEGACY_HEX_SERIALIZER.deserialize(string))
.replace("\\<", "<")
.replace("\\>", ">"),
resolver
);
}
/**
* Checks if the provided string contains legacy formatting.
*
* @param string the string input
* @return whether the string has legacy content
* @see LegacyComponentSerializer#AMPERSAND_CHAR
* @see LegacyComponentSerializer#SECTION_CHAR
* @since 2.3.0
*/
public static boolean hasLegacyFormat(final String string) {
requireNonNull(string, "string");
return string.indexOf(LegacyComponentSerializer.SECTION_CHAR) != -1
|| string.indexOf(LegacyComponentSerializer.AMPERSAND_CHAR) != -1;
}
/**
* Maps the input to a type depending on whether it contains legacy formatting or not.
*
* @param string the string input
* @param legacyMapping the mapping function in case the string contains legacy formatting
* @param modernMapping the mapping function in case the string does not contain any legacy character type,
* being safe to use with MiniMessage
* @param <R> the result type
* @return the mapped result
* @see LegacyComponentSerializer#AMPERSAND_CHAR
* @see LegacyComponentSerializer#SECTION_CHAR
* @see #hasLegacyFormat(String)
* @since 2.3.0
*/
public static <R> R mapLegacyOrElse(
final String string,
final Function<String, R> legacyMapping,
final Function<String, R> modernMapping
) {
requireNonNull(legacyMapping, "legacyMapping");
requireNonNull(modernMapping, "modernInput");
if (hasLegacyFormat(string)) {
return legacyMapping.apply(string);
} else {
return modernMapping.apply(string);
}
}
}
@@ -1,115 +0,0 @@
package io.github.miniplaceholders.api.utils;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
/**
* Legacy utils
*/
public final class LegacyUtils {
private LegacyUtils() {
}
/**
* Legacy serializer with hex support
* <p>Use this as the last available alternative,
* it is recommended to use MiniMessage instead of this serializer</p>
*/
public static final LegacyComponentSerializer LEGACY_HEX_SERIALIZER = LegacyComponentSerializer.builder()
.character('&')
.hexColors()
.build();
/**
* Parse a string with possible legacy Ampersand/Section symbols
* <br>
* <p><b>
* This method should only be used in case a String is provided from a legacy plugin/mod
* or legacy source, where it is very likely to get a legacy string.
* It is not recommended to use this method regularly in any sense
* </b></p>
*
* @param string the string
* @return a parsed string
* @since 1.0.0
*/
public static @NotNull Component parsePossibleLegacy(@Nullable String string) {
if (string == null || string.isEmpty()) return Component.empty();
if (string.indexOf(LegacyComponentSerializer.SECTION_CHAR) != -1) {
string = string.replace(LegacyComponentSerializer.SECTION_CHAR, LegacyComponentSerializer.AMPERSAND_CHAR);
}
if (string.indexOf(LegacyComponentSerializer.AMPERSAND_CHAR) == -1) {
return miniMessage().deserialize(string);
}
return miniMessage().deserialize(
miniMessage().serialize(LEGACY_HEX_SERIALIZER.deserialize(string))
.replace("\\<", "<")
.replace("\\>", ">")
);
}
/**
* Parse a string with possible legacy Ampersand/Section symbols
* using a parsing Context
* <p><b>
* This method should only be used in case a String is provided from a legacy plugin/mod
* or legacy source, where it is very likely to get a legacy string.
* It is not recommended to use this method regularly in any sense
* </b></p>
*
* @param string the string
* @param context the provided parsing context
* @return a parsed string
* @since 2.2.1
*/
public static @NotNull Component parsePossibleLegacy(@Nullable String string, @NotNull final Context context) {
if (string == null || string.isEmpty()) return Component.empty();
if (string.indexOf(LegacyComponentSerializer.SECTION_CHAR) != -1) {
string = string.replace(LegacyComponentSerializer.SECTION_CHAR, LegacyComponentSerializer.AMPERSAND_CHAR);
}
if (string.indexOf(LegacyComponentSerializer.AMPERSAND_CHAR) == -1) {
return context.deserialize(string);
}
return context.deserialize(
miniMessage().serialize(LEGACY_HEX_SERIALIZER.deserialize(string))
.replace("\\<", "<")
.replace("\\>", ">")
);
}
/**
* Parse a string with possible legacy Ampersand/Section symbols
* using the provided resolver
* <p><b>
* This method should only be used in case a String is provided from a legacy plugin/mod
* or legacy source, where it is very likely to get a legacy string.
* It is not recommended to use this method regularly in any sense
* </b></p>
*
* @param string the string
* @param resolver a resolver
* @return a parsed string
* @since 2.2.1
*/
public static @NotNull Component parsePossibleLegacy(@Nullable String string, @NotNull final TagResolver resolver) {
if (string == null || string.isEmpty()) return Component.empty();
if (string.indexOf(LegacyComponentSerializer.SECTION_CHAR) != -1) {
string = string.replace(LegacyComponentSerializer.SECTION_CHAR, LegacyComponentSerializer.AMPERSAND_CHAR);
}
if (string.indexOf(LegacyComponentSerializer.AMPERSAND_CHAR) == -1) {
return miniMessage().deserialize(string, resolver);
}
return miniMessage().deserialize(
miniMessage().serialize(LEGACY_HEX_SERIALIZER.deserialize(string))
.replace("\\<", "<")
.replace("\\>", ">"),
resolver
);
}
}
@@ -1,45 +1,49 @@
package io.github.miniplaceholders.api.utils;
import org.jetbrains.annotations.NotNull;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jspecify.annotations.NullMarked;
/**
* Class of utilities to manage TagResolvers
* Class of utilities to manage TagResolvers.
*
* @since 1.2.0
*/
@NullMarked
public final class Resolvers {
private Resolvers() {}
/**
* Check if the TagResolver is an instance of TagResolver.empty(),
* that is, it will always return null
* that is, it will always return null.
*
* @param resolver the resolver to check
* @return true if the TagResolver is the same as TagResolver.empty(), else false
* @since 1.2.0
*/
public static boolean isEmpty(final @NotNull TagResolver resolver) {
public static boolean isEmpty(final TagResolver resolver) {
return resolver == TagResolver.empty();
}
/**
* Checks if the TagResolver provided is not a TagResolver.empty()
* Checks if the TagResolver provided is not a TagResolver.empty().
*
* @param resolver the resolver to check
* @return false if the TagResolver is the same as TagResolver.empty(), else true
* @since 1.2.0
*
*/
public static boolean isNotEmpty(final @NotNull TagResolver resolver) {
public static boolean isNotEmpty(final TagResolver resolver) {
return resolver != TagResolver.empty();
}
/**
* Applies a TagResolver to a TagResolver.Builder in case it is not a TagResolver.empty()
* Applies a TagResolver to a TagResolver.Builder in case it is not a TagResolver.empty().
*
* @param resolver the resolver to check
* @param builder the resolver that can be added to the builder
* @since 1.2.0
*/
public static void applyIfNotEmpty(final @NotNull TagResolver resolver, final @NotNull TagResolver.Builder builder) {
public static void applyIfNotEmpty(final TagResolver resolver, final TagResolver.Builder builder) {
if (isNotEmpty(resolver)) {
builder.resolver(resolver);
}
@@ -0,0 +1,73 @@
package io.github.miniplaceholders.api.utils;
import io.github.miniplaceholders.api.resolver.AudienceTagResolver;
import io.github.miniplaceholders.api.resolver.GlobalTagResolver;
import io.github.miniplaceholders.api.resolver.RelationalTagResolver;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag;
import org.jspecify.annotations.NonNull;
/**
* Tags basic instances.
*
* @since 3.0.0
*/
public final class Tags {
private Tags() {
}
/**
* Empty Tag
*
* @since 3.0.0
*/
public static final Tag EMPTY_TAG = Tag.selfClosingInserting(Component.empty());
/**
* Null Tag
*
* @since 3.0.0
*/
public static final Tag NULL_TAG = null;
/**
* An Audience placeholder that returns a null Tag
*
* @param <A> the placeholder supported type
* @return an empty audience resolver
* @since 3.0.0
*/
public static <A extends Audience> AudienceTagResolver<@NonNull A> emptyAudienceResolver() {
return (audience, queue, ctx) -> NULL_TAG;
}
/**
* A Relational placeholder that returns a null tag
*
* @param <A> the audience type supported
* @return an empty relational resolver
* @since 3.0.0
*/
public static <A extends Audience> RelationalTagResolver<@NonNull A> emptyRelationalResolver() {
return (audience, relational, queue, ctx) -> NULL_TAG;
}
/**
* A Global placeholder that returns a null tag
*
* @since 3.0.0
*/
private static final GlobalTagResolver EMPTY_GLOBAL_PLACEHOLDER = (queue, ctx) -> NULL_TAG;
/**
* A Global placeholder that returns a null tag
*
* @since 3.0.0
* @return an empty global placeholder
*/
public static GlobalTagResolver emptyGlobalPlaceholder() {
return EMPTY_GLOBAL_PLACEHOLDER;
}
}
@@ -1,58 +0,0 @@
package io.github.miniplaceholders.api.utils;
import io.github.miniplaceholders.api.placeholder.AudiencePlaceholder;
import io.github.miniplaceholders.api.placeholder.RelationalPlaceholder;
import org.jetbrains.annotations.NotNull;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag;
/**Tags utils */
public final class TagsUtils {
private TagsUtils(){}
/**
* Empty Tag
* @since 1.0.0
*/
public static final Tag EMPTY_TAG = Tag.selfClosingInserting(Component.empty());
/**
* Null Tag
* @since 1.1.0
*/
public static final Tag NULL_TAG = null;
/**
* An Audience placeholder that returns a null Tag
* @since 1.1.0
*/
public static final AudiencePlaceholder NULL_AUDIENCE_PLACEHOLDER = (aud, queue, ctx) -> NULL_TAG;
/**
* A Relational placeholder that returns a null tag
* @since 1.1.0
*/
public static final RelationalPlaceholder NULL_RELATIONAL_PLACEHOLDER = (aud, otheraud, queue, ctx) -> NULL_TAG;
/**
* Creates a new Tag based on a String
* @param string the string
* @return a new basic self closing Tag of a string
* @since 1.1.0
*/
public static @NotNull Tag staticTag(@NotNull String string) {
return Tag.selfClosingInserting(Component.text(string));
}
/**
* Creates a new basic Tag based on a Component
* @param component the component
* @return a new basic self closing Tag of a component
* @since 1.1.0
*/
public static @NotNull Tag staticTag(@NotNull Component component) {
return Tag.selfClosingInserting(component);
}
}
+16 -11
View File
@@ -1,16 +1,21 @@
/** MiniPlaceholders API Module */
/**
* MiniPlaceholders API Module
*/
open module io.github.miniplaceholders.api {
requires io.github.miniplaceholders.connect;
requires io.github.miniplaceholders.connect;
requires net.kyori.adventure;
requires net.kyori.adventure.text.minimessage;
requires net.kyori.adventure.text.serializer.legacy;
requires net.kyori.examination.api;
requires net.kyori.adventure;
requires net.kyori.adventure.text.minimessage;
requires net.kyori.adventure.text.serializer.legacy;
requires net.kyori.examination.api;
requires static org.jetbrains.annotations;
requires static org.jetbrains.annotations;
requires static org.jspecify;
exports io.github.miniplaceholders.api;
exports io.github.miniplaceholders.api.enums;
exports io.github.miniplaceholders.api.utils;
exports io.github.miniplaceholders.api.placeholder;
exports io.github.miniplaceholders.api;
exports io.github.miniplaceholders.api.types;
exports io.github.miniplaceholders.api.utils;
exports io.github.miniplaceholders.api.resolver;
exports io.github.miniplaceholders.api.placeholder;
exports io.github.miniplaceholders.api.provider;
}
@@ -1,45 +1,65 @@
package io.github.miniplaceholders.test;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.utils.TagsUtils;
import io.github.miniplaceholders.test.testobjects.TestAudienceHolder;
import net.kyori.adventure.text.Component;
import io.github.miniplaceholders.api.placeholder.GlobalPlaceholder;
import io.github.miniplaceholders.api.types.PlaceholderType;
import io.github.miniplaceholders.api.utils.Tags;
import io.github.miniplaceholders.test.instances.TestAudience;
import net.kyori.adventure.text.minimessage.tag.Tag;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.github.miniplaceholders.test.PlaceholderTest.assertContentEquals;
import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.*;
class ExpansionsTest {
class ExpansionsTest implements MiniTest {
@Test
@DisplayName("Expansion Equality")
void equalsExpansions(){
final Expansion firstExpansion = Expansion.builder("equals")
.audiencePlaceholder("test", TagsUtils.NULL_AUDIENCE_PLACEHOLDER)
.audiencePlaceholder("test", Tags.emptyAudienceResolver())
.build();
final Expansion secondExpansion = Expansion.builder("equals")
.audiencePlaceholder("test", TagsUtils.NULL_AUDIENCE_PLACEHOLDER)
.audiencePlaceholder("test", Tags.emptyAudienceResolver())
.build();
assertEquals(firstExpansion, secondExpansion);
}
@Test
void nullForwardedAudience() {
TestAudienceHolder audience = new TestAudienceHolder(null);
void placeholderRetrievalTest() {
final Expansion expansion = Expansion.builder("test")
.filter(aud -> aud.toString().isBlank())
.audiencePlaceholder("testing", (aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(aud.toString())))
.globalPlaceholder("some_placeholder", Tag.preProcessParsed("test"))
.build();
Component result = assertDoesNotThrow(
() -> miniMessage().deserialize("<test_testing>", expansion.audiencePlaceholders(audience))
);
assertTrue(expansion.hasGlobalPlaceholder("some_placeholder"));
final GlobalPlaceholder somePlaceholder = expansion.globalPlaceholderByName("some_placeholder");
assertNotNull(somePlaceholder);
assertContentEquals(result, Component.text("<test_testing>"));
final var expansionResult = parse("Result: <test_some_placeholder>", expansion.globalPlaceholders());
final var placeholderResult = parse("Result: <test_some_placeholder>", somePlaceholder);
assertSimilarity("Result: test", expansionResult);
assertSimilarity("Result: test", placeholderResult);
}
@Test
void placeholdersByType() {
final Expansion expansion = Expansion.builder("idk")
.audiencePlaceholder(TestAudience.class, "test", Tags.emptyAudienceResolver())
.build();
var placeholders = expansion.placeholdersByType(PlaceholderType.AUDIENCE);
assertNotNull(placeholders);
assertEquals(placeholders, expansion.audiencePlaceholders());
}
@Test
void placeholdersByName() {
final Expansion expansion = Expansion.builder("test")
.globalPlaceholder("someexpansion", Tags.emptyGlobalPlaceholder())
.build();
final GlobalPlaceholder placeholder = expansion.globalPlaceholderByName("someexpansion");
assertNotNull(placeholder);
assertEquals("someexpansion", placeholder.name());
}
}
@@ -1,20 +1,42 @@
package io.github.miniplaceholders.test;
import io.github.miniplaceholders.api.utils.LegacyUtils;
import io.github.miniplaceholders.api.utils.LegacyStrings;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Inserting;
import net.kyori.adventure.text.minimessage.tag.PreProcess;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.*;
class LegacyTest {
class LegacyTest implements MiniTest {
@Test
void testLegacyParsing() {
final String string = "<rainbow>hello... i &8hate legacy format §bso much";
final Component parsed = LegacyUtils.parsePossibleLegacy(string);
final Component parsed = LegacyStrings.parsePossibleLegacy(string);
final String result = PlainTextComponentSerializer.plainText().serialize(parsed);
final String expected = "hello... i hate legacy format so much";
assertEquals(expected, result);
}
@Test
void testMapLegacyOrElse() {
final String legacyInput = "&fhi <red>aaaa";
Tag legacyProcessedTag = LegacyStrings.mapLegacyOrElse(
legacyInput,
legacy -> Tag.inserting(LegacyStrings.parsePossibleLegacy(legacy)),
Tag::preProcessParsed
);
assertInstanceOf(Inserting.class, legacyProcessedTag);
final String modernInput = "<rainbow>aaaaaaaaaaaaa";
Tag modernProcessedTag = LegacyStrings.mapLegacyOrElse(
modernInput,
__ -> fail(),
Tag::preProcessParsed
);
assertInstanceOf(PreProcess.class, modernProcessedTag);
}
}
@@ -1,32 +1,28 @@
package io.github.miniplaceholders.test;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.MiniPlaceholders;
import io.github.miniplaceholders.api.utils.TagsUtils;
import net.kyori.adventure.audience.Audience;
import io.github.miniplaceholders.api.utils.Tags;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MiniPlaceholdersTest {
class MiniPlaceholdersTest implements MiniTest {
@Test
@Disabled("Cannot correctly calculate the equity of 2 TagResolvers obtained in different ways")
void methodEquality(){
Expansion.builder("equality")
.audiencePlaceholder("audience", TagsUtils.NULL_AUDIENCE_PLACEHOLDER)
.globalPlaceholder("global", TagsUtils.EMPTY_TAG)
.audiencePlaceholder("audience", Tags.emptyAudienceResolver())
.globalPlaceholder("global", (queue, ctx) -> Tags.EMPTY_TAG)
.build()
.register();
assertEquals(
MiniPlaceholders.getAudienceGlobalPlaceholders(Audience.empty()),
MiniPlaceholders.audienceGlobalPlaceholders(),
TagResolver.resolver(
MiniPlaceholders.getAudiencePlaceholders(Audience.empty()),
MiniPlaceholders.getGlobalPlaceholders()
MiniPlaceholders.audiencePlaceholders(),
MiniPlaceholders.globalPlaceholders()
)
);
}
@@ -37,6 +33,8 @@ class MiniPlaceholdersTest {
.build();
expansion.register();
assertNotNull(MiniPlaceholders.expansionByName("testregistration"));
assertTrue(expansion.registered());
assertThrows(IllegalStateException.class, expansion::register);
@@ -0,0 +1,30 @@
package io.github.miniplaceholders.test;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public interface MiniTest {
default void assertContentEquals(Component first, Component second) {
String firstSerialized = PlainTextComponentSerializer.plainText().serialize(first);
String secondSerialized = PlainTextComponentSerializer.plainText().serialize(second);
assertEquals(firstSerialized, secondSerialized);
}
default void assertSimilarity(String string, Component component) {
final String serialized = PlainTextComponentSerializer.plainText().serializeOrNull(component);
assertNotNull(serialized);
assertEquals(string, serialized);
}
default Component parse(String string, TagResolver resolver) {
return MiniMessage.miniMessage().deserialize(string, resolver);
}
}
@@ -3,8 +3,9 @@ package io.github.miniplaceholders.test;
import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
import static org.junit.jupiter.api.Assertions.*;
import io.github.miniplaceholders.test.testobjects.TestAudience;
import io.github.miniplaceholders.test.testobjects.TestAudienceHolder;
import io.github.miniplaceholders.api.types.RelationalAudience;
import io.github.miniplaceholders.test.instances.TestAudience;
import io.github.miniplaceholders.test.instances.TestAudienceHolder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
@@ -15,140 +16,136 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
class PlaceholderTest {
class PlaceholderTest implements MiniTest {
@Test
@DisplayName("Audience Placeholder Test")
void audienceExpansionPlaceholdersTest(){
TestAudience player = new TestAudience("4drian3d");
@Test
@DisplayName("Audience Placeholder Test")
void audienceExpansionPlaceholdersTest() {
TestAudience player = new TestAudience("4drian3d");
Expansion expansion = Expansion.builder("example")
Expansion expansion = Expansion.builder("example")
.audiencePlaceholder("name", (aud, queue, ctx) -> {
TestAudience audience = (TestAudience) aud;
return Tag.selfClosingInserting(Component.text(audience.name()));
TestAudience audience = (TestAudience) aud;
return Tag.selfClosingInserting(Component.text(audience.name()));
}).build();
final Component expected = Component.text("Player Name: 4drian3d");
final Component result = miniMessage().deserialize("Player Name: <example_name>", expansion.audiencePlaceholders(player));
final Component expected = Component.text("Player Name: 4drian3d");
final Component result = miniMessage().deserialize("Player Name: <example_name>", player, expansion.audiencePlaceholders());
assertContentEquals(expected, result);
}
assertContentEquals(expected, result);
}
@Test
@DisplayName("Relational Placeholder Test")
void relationalExpansionPlaceholdersTest(){
Audience p1 = new TestAudience("PlayerOne");
Audience p2 = new TestAudience("PlayerTwo");
@Test
@DisplayName("Relational Placeholder Test")
void relationalExpansionPlaceholdersTest() {
Audience p1 = new TestAudience("PlayerOne");
Audience p2 = new TestAudience("PlayerTwo");
Expansion expansion = Expansion.builder("relational")
.relationalPlaceholder("enemy",
(aud, otheraud, queue, ctx) -> Tag.selfClosingInserting(this.isEnemy(((TestAudience)aud),((TestAudience)otheraud))
? Component.text("Enemy", NamedTextColor.RED)
: Component.text("Neutral", NamedTextColor.GREEN)
))
Expansion expansion = Expansion.builder("relational")
.relationalPlaceholder(TestAudience.class, "enemy",
(aud, relational, queue, ctx) ->
Tag.selfClosingInserting(this.isEnemy(aud, relational)
? Component.text("Enemy", NamedTextColor.RED)
: Component.text("Neutral", NamedTextColor.GREEN)
))
.build();
assertEquals(MiniMessage.miniMessage().deserialize("You are <red>Enemy"), MiniMessage.miniMessage().deserialize("You are <relational_rel_enemy>", expansion.relationalPlaceholders(p1, p2)));
}
assertEquals(
MiniMessage.miniMessage().deserialize("You are <red>Enemy"),
MiniMessage.miniMessage().deserialize("You are <relational_rel_enemy>",
RelationalAudience.from(p1, p2),
expansion.relationalPlaceholders()));
}
@Test
@DisplayName("Global Placeholder Test")
void globalExpansionPlaceholderTest(){
Expansion expansion = Expansion.builder("global")
@Test
@DisplayName("Global Placeholder Test")
void globalExpansionPlaceholderTest() {
Expansion expansion = Expansion.builder("global")
.globalPlaceholder("players", (queue, ctx) -> Tag.selfClosingInserting(Component.text(1305)))
.globalPlaceholder("servers", (queue, ctx) -> Tag.selfClosingInserting(Component.text(7)))
.build();
final Component expected = Component.text("Online players: 1305 | Servers: 7");
final Component actual = MiniMessage.miniMessage().deserialize("Online players: <global_players> | Servers: <global_servers>", expansion.globalPlaceholders());
final Component expected = Component.text("Online players: 1305 | Servers: 7");
final Component actual = MiniMessage.miniMessage().deserialize("Online players: <global_players> | Servers: <global_servers>",
expansion.globalPlaceholders());
assertContentEquals(expected, actual);
}
assertContentEquals(expected, actual);
}
@Test
@DisplayName("Registered Placeholders")
void instancePlaceholdersTest(){
Expansion.builder("instance")
@Test
@DisplayName("Registered Placeholders")
void instancePlaceholdersTest() {
Expansion.builder("instance")
.globalPlaceholder("hello", (queue, ctx) -> Tag.selfClosingInserting(Component.text("hello", NamedTextColor.RED)))
.build()
.register();
Component expected = Component.text("hello player", NamedTextColor.RED);
Component actual = MiniMessage.miniMessage().deserialize("<instance_hello> player", MiniPlaceholders.getGlobalPlaceholders());
Component expected = Component.text("hello player", NamedTextColor.RED);
Component actual = MiniMessage.miniMessage().deserialize("<instance_hello> player", MiniPlaceholders.globalPlaceholders());
assertContentEquals(expected, actual);
}
assertContentEquals(expected, actual);
}
@Test
@DisplayName("Filtered Expansion")
void filteredExpansion(){
Expansion expansion = Expansion.builder("filter")
.audiencePlaceholder("name", (aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(((TestAudience)aud).name())))
.filter(TestAudience.class)
@Test
@DisplayName("Filtered Expansion")
void filteredExpansion() {
Expansion expansion = Expansion.builder("filter")
.audiencePlaceholder(TestAudience.class, "name",
(aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(aud.name())))
.build();
Audience player = new TestAudience("TestPlayer04");
Audience emptyAudience = Audience.empty();
Audience player = new TestAudience("TestPlayer04");
Audience emptyAudience = Audience.empty();
String string = "Player Name: <filter_name>";
String string = "Player Name: <filter_name>";
Component playerExpected = Component.text("Player Name: TestPlayer04");
Component emptyExpected = Component.text("Player Name: <filter_name>");
Component playerExpected = Component.text("Player Name: TestPlayer04");
Component emptyExpected = Component.text("Player Name: <filter_name>");
assertContentEquals(playerExpected, MiniMessage.miniMessage().deserialize(string, expansion.audiencePlaceholders(player)));
assertContentEquals(emptyExpected, MiniMessage.miniMessage().deserialize(string, expansion.audiencePlaceholders(emptyAudience)));
}
assertContentEquals(playerExpected, MiniMessage.miniMessage().deserialize(string, player, expansion.audiencePlaceholders()));
assertContentEquals(emptyExpected, MiniMessage.miniMessage().deserialize(string, emptyAudience, expansion.audiencePlaceholders()));
}
@Test
@DisplayName("Empty Arguments")
void testEmptyArguments() {
final Expansion expansion = Expansion.builder("test")
.globalPlaceholder("testing", (queue, ctx) -> {
int arguments = 0;
while (queue.hasNext()) {
arguments++;
Tag.Argument argument = queue.pop();
assertTrue(argument.value().isBlank());
}
return Tag.selfClosingInserting(Component.text("Arguments: "+arguments));
})
.build();
@Test
@DisplayName("Empty Arguments")
void testEmptyArguments() {
final Expansion expansion = Expansion.builder("test")
.globalPlaceholder("testing", (queue, ctx) -> {
int arguments = 0;
while (queue.hasNext()) {
arguments++;
Tag.Argument argument = queue.pop();
assertTrue(argument.value().isBlank());
}
return Tag.selfClosingInserting(Component.text("Arguments: " + arguments));
})
.build();
Component parsed = miniMessage().deserialize("<test_testing::>", expansion.globalPlaceholders());
Component parsed = miniMessage().deserialize("<test_testing::>", expansion.globalPlaceholders());
assertContentEquals(parsed, Component.text("Arguments: 2"));
}
assertContentEquals(parsed, Component.text("Arguments: 2"));
}
@Test
@DisplayName("Forwarding Audience Parsing Test")
void forwardingAudienceTest() {
Expansion expansion = Expansion.builder("test")
.filter(TestAudience.class)
.audiencePlaceholder("testing", (audience, queue, ctx) -> {
TestAudience testAudience = (TestAudience) audience;
return Tag.selfClosingInserting(Component.text("Name: "+testAudience.name()));
}).build();
@Test
@DisplayName("Forwarding Audience Parsing Test")
void forwardingAudienceTest() {
Expansion expansion = Expansion.builder("test")
.audiencePlaceholder(TestAudience.class, "testing",
(audience, queue, ctx) -> Tag.selfClosingInserting(Component.text("Name: " + audience.name())))
.build();
TestAudience testAudience = new TestAudience("4drian3d");
Audience forward = new TestAudienceHolder(testAudience);
TestAudience testAudience = new TestAudience("4drian3d");
Audience forward = new TestAudienceHolder(testAudience);
Component parsed = miniMessage().deserialize("<test_testing>.", expansion.audiencePlaceholders(forward));
Component expected = Component.text("Name: 4drian3d.");
Component parsed = miniMessage().deserialize("<test_testing>.", forward, expansion.audiencePlaceholders());
Component expected = Component.text("Name: 4drian3d.");
assertContentEquals(parsed, expected);
}
assertContentEquals(parsed, expected);
}
static void assertContentEquals(Component first, Component second){
String firstSerialized = PlainTextComponentSerializer.plainText().serialize(first);
String secondSerialized = PlainTextComponentSerializer.plainText().serialize(second);
assertEquals(firstSerialized, secondSerialized);
}
private boolean isEnemy(TestAudience a1, TestAudience a2){
return a1.name().equals("PlayerOne") && a2.name().equals("PlayerTwo");
}
private boolean isEnemy(TestAudience a1, TestAudience a2) {
return a1.name().equals("PlayerOne") && a2.name().equals("PlayerTwo");
}
}
@@ -0,0 +1,49 @@
package io.github.miniplaceholders.test;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.MiniPlaceholders;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.Modifying;
import net.kyori.adventure.text.minimessage.tag.ParserDirective;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TagTest implements MiniTest {
@Test
void testTag() {
Expansion.builder("tags")
.globalPlaceholder("modifying", FilteringTag::new)
.globalPlaceholder("parser_directive", ParserDirective.RESET)
.build()
.register();
final TagResolver globalResolver = MiniPlaceholders.globalPlaceholders();
assertSimilarity("------ replacement ------", parse("------ <tags_modifying>test</tags_modifying> ------", globalResolver));
assertEquals(Component.text()
.append(Component.text("aaaaaa", NamedTextColor.RED), Component.text("aaaaaaa"))
.build(),
parse("<red>aaaaaa<tags_parser_directive>aaaaaaa", globalResolver));
}
public record FilteringTag(ArgumentQueue queue, Context context) implements Modifying {
@Override
public Component apply(@NotNull Component current, int depth) {
if (depth != 0) {
return Component.empty();
}
final String serialized = PlainTextComponentSerializer.plainText().serialize(current);
if (serialized.contains("test")) {
return Component.text(serialized.replace("test", "replacement"));
}
return current;
}
}
}
@@ -1,23 +1,17 @@
package io.github.miniplaceholders.test;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.resolver.GlobalTagResolver;
import io.github.miniplaceholders.api.utils.Tags;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.function.BiFunction;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.MiniPlaceholders;
import io.github.miniplaceholders.api.utils.TagsUtils;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
//yea... it is intended xd
@SuppressWarnings("DataFlowIssue")
class ThrowableTest {
class ThrowableTest implements MiniTest {
@Test
@DisplayName("Nullable Exception")
void throwOnNull(){
@@ -25,17 +19,8 @@ class ThrowableTest {
Expansion.Builder expansionTest = assertDoesNotThrow(() -> Expansion.builder("throwable"));
assertThrows(NullPointerException.class, () -> expansionTest.audiencePlaceholder(null, (aud, queue, ctx) -> TagsUtils.EMPTY_TAG));
assertThrows(NullPointerException.class, () -> expansionTest.globalPlaceholder("test", (BiFunction<ArgumentQueue, Context, Tag>)null));
Expansion.builder("testThrows")
.audiencePlaceholder("test", (aud, queue, ctx) -> TagsUtils.EMPTY_TAG)
.relationalPlaceholder("test2", (aud, aud2, queue, ctx) -> TagsUtils.EMPTY_TAG)
.build()
.register();
assertThrows(NullPointerException.class, () -> MiniPlaceholders.getAudiencePlaceholders(null));
assertThrows(NullPointerException.class, () -> MiniPlaceholders.getRelationalPlaceholders(null, null));
assertThrows(NullPointerException.class, () -> expansionTest.audiencePlaceholder(null, (aud, queue, ctx) -> Tags.EMPTY_TAG));
assertThrows(NullPointerException.class, () -> expansionTest.globalPlaceholder("test", (GlobalTagResolver) null));
}
@Test
@@ -43,9 +28,9 @@ class ThrowableTest {
void throwOnBlankString(){
Expansion.Builder builder = assertDoesNotThrow(() -> Expansion.builder("hello"));
assertThrows(IllegalStateException.class, () -> builder.audiencePlaceholder("", (aud, queue, ctx) -> TagsUtils.EMPTY_TAG));
assertThrows(IllegalStateException.class, () -> builder.globalPlaceholder(" ", (queue, ctx) -> TagsUtils.EMPTY_TAG));
assertThrows(IllegalStateException.class, () -> builder.audiencePlaceholder("", (aud, queue, ctx) -> Tags.EMPTY_TAG));
assertThrows(IllegalStateException.class, () -> builder.globalPlaceholder(" ", (queue, ctx) -> Tags.EMPTY_TAG));
assertDoesNotThrow(() -> builder.globalPlaceholder("hehe", (queue, ctx) -> TagsUtils.EMPTY_TAG));
assertDoesNotThrow(() -> builder.globalPlaceholder("hehe", (queue, ctx) -> Tags.EMPTY_TAG));
}
}
@@ -1,4 +1,4 @@
package io.github.miniplaceholders.test.testobjects;
package io.github.miniplaceholders.test.instances;
import org.jetbrains.annotations.NotNull;
@@ -1,4 +1,4 @@
package io.github.miniplaceholders.test.testobjects;
package io.github.miniplaceholders.test.instances;
import net.kyori.adventure.audience.ForwardingAudience;
import org.jetbrains.annotations.NotNull;
@@ -0,0 +1,21 @@
package io.github.miniplaceholders.test.loader;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.provider.ExpansionProvider;
import io.github.miniplaceholders.api.provider.LoadRequirement;
import io.github.miniplaceholders.api.types.Platform;
public class TestExpansionProvider implements ExpansionProvider {
@Override
public Expansion provideExpansion() {
return Expansion.builder("provided").build();
}
@Override
public LoadRequirement loadRequirement() {
return LoadRequirement.allOf(
LoadRequirement.platform(Platform.FABRIC, Platform.PAPER),
LoadRequirement.requiredComplement("luckperms", "LuckPerms")
);
}
}
+1 -1
View File
@@ -14,6 +14,6 @@ repositories {
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(17))
languageVersion = JavaLanguageVersion.of(21)
}
}
+1 -1
View File
@@ -15,5 +15,5 @@ dependencyResolutionManagement {
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0"
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
+2 -2
View File
@@ -1,7 +1,7 @@
import org.gradle.api.Project
import org.gradle.configurationcache.extensions.capitalized
val Project.capitalizeName: String
get() = this.name.substring(17).capitalized()
get() = this.name.substring(17)
.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
fun Project.simpleName(): String = this.name.substring(17)
@@ -7,7 +7,7 @@ plugins {
tasks {
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release.set(17)
options.release.set(21)
}
jar {
manifest {
@@ -22,7 +22,7 @@ tasks {
java{
toolchain{
languageVersion.set(JavaLanguageVersion.of(17))
vendor.set(JvmVendorSpec.ADOPTIUM)
languageVersion.set(JavaLanguageVersion.of(21))
//vendor.set(JvmVendorSpec.ADOPTIUM)
}
}
@@ -5,24 +5,45 @@ plugins {
signing
id("org.jetbrains.dokka")
}
/*
val dokkaHtmlJar = tasks.register<Jar>("dokkaHtmlJar") {
dependsOn(tasks.dokkaHtml)
from(tasks.dokkaHtml.flatMap { it.outputDirectory })
val dokkaHtmlJar by tasks.registering(Jar::class) {
description = "A HTML Documentation JAR containing Dokka HTML"
from(tasks.dokkaGeneratePublicationHtml.flatMap { it.outputDirectory })
archiveClassifier.set("javadoc")
}
dokka {
dokkaSourceSets.main {
includes.from("README.md")
sourceLink {
localDirectory.set(rootDir)
remoteUrl("https://github.com/MiniPlaceholders/MiniPlaceholders/tree/main")
remoteLineSuffix.set("#L")
}
}
pluginsConfiguration.html {
footerMessage.set("(c) MiniPlaceholders | 4drian3d")
}
}
val projectVersion = project.version.toString()
val projectName = project.name
val projectDescription = project.description
publishing {
publications {
create<MavenPublication>("maven") {
artifact(tasks["dokkaHtmlJar"])
register<MavenPublication>("maven") {
artifact(dokkaHtmlJar)
repositories {
maven {
credentials(PasswordCredentials::class)
credentials {
username = findProperty("sonatypeTokenUsername") as? String ?: ""
password = findProperty("sonatypeTokenPassword") as? String ?: ""
}
val central = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
val snapshots = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
val snapshots = "https://central.sonatype.com/repository/maven-snapshots/"
if (project.version.toString().endsWith("SNAPSHOT")) {
if (projectVersion.endsWith("SNAPSHOT") || projectVersion.contains("RC")) {
name = "SonatypeSnapshots"
setUrl(snapshots)
} else {
@@ -30,6 +51,9 @@ publishing {
setUrl(central)
}
}
maven(file("build/dev-maven")) {
name = "DevMaven"
}
}
from(components["java"])
pom {
@@ -60,8 +84,8 @@ publishing {
name.set("GitHub Actions")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders/actions")
}
name.set(project.name)
description.set(project.description)
name.set(projectName)
description.set(projectDescription)
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders")
}
}
@@ -72,9 +96,9 @@ signing {
useGpgCmd()
sign(configurations.archives.get())
sign(publishing.publications["maven"])
sign(tasks["dokkaHtmlJar"])
sign(dokkaHtmlJar.get())
}
java {
withSourcesJar()
}*/
}
@@ -3,17 +3,21 @@ plugins {
`maven-publish`
signing
}
/*
publishing {
publications {
create<MavenPublication>("maven") {
repositories {
maven {
credentials(PasswordCredentials::class)
credentials {
username = findProperty("sonatypeTokenUsername") as? String ?: ""
password = findProperty("sonatypeTokenPassword") as? String ?: ""
}
val central = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
val snapshots = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
val snapshots = "https://central.sonatype.com/repository/maven-snapshots/"
if (project.version.toString().endsWith("SNAPSHOT")) {
val projectVersion = project.version.toString()
if (projectVersion.endsWith("SNAPSHOT") || projectVersion.contains("RC")) {
name = "SonatypeSnapshots"
setUrl(snapshots)
} else {
@@ -64,7 +68,7 @@ signing {
sign(configurations.archives.get())
sign(publishing.publications["maven"])
}*/
}
java {
withSourcesJar()
@@ -1,6 +1,6 @@
plugins {
id("miniplaceholders.base")
id("com.github.johnrengelman.shadow")
id("com.gradleup.shadow")
}
tasks {
@@ -9,10 +9,10 @@ tasks {
archiveClassifier.set("")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
relocate("net.william278.desertwell", "io.github.miniplaceholders.libs.desertwell")
relocate("cloud.commandframework", "io.github.miniplaceholders.libs.cloud")
if (project.simpleName() != "sponge") {
relocate("io.leangen.geantyref", "io.github.miniplaceholders.libs.geantyref")
}
relocate("org.incendo.cloud", "io.github.miniplaceholders.libs.cloud")
// if (project.simpleName() != "sponge") {
// relocate("io.leangen.geantyref", "io.github.miniplaceholders.libs.geantyref")
// }
destinationDirectory.set(file("${project.rootDir}/jar"))
}
build {
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
kotlin("jvm") version "1.9.20" apply false
kotlin("jvm") version "2.2.20-RC" apply false
}
tasks {
+2 -1
View File
@@ -10,11 +10,12 @@ dependencies {
exclude("org.json")
}
implementation(libs.cloud.core)
implementation(libs.unnamedinject)
implementation(libs.cloud.extras) {
isTransitive = false
}
compileOnly(libs.adventure.api)
compileOnly(libs.adventure.minimesssage)
compileOnly(libs.adventure.minimessage)
}
sourceSets {
@@ -1,198 +0,0 @@
package io.github.miniplaceholders.common;
import cloud.commandframework.ArgumentDescription;
import cloud.commandframework.Command;
import cloud.commandframework.CommandManager;
import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.standard.StringArgument;
import cloud.commandframework.minecraft.extras.AudienceProvider;
import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler;
import io.github.miniplaceholders.api.MiniPlaceholders;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.builder.AbstractBuilder;
import net.kyori.adventure.permission.PermissionChecker;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.util.TriState;
import net.william278.desertwell.about.AboutMenu;
import net.william278.desertwell.util.Version;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Supplier;
import static java.util.Objects.requireNonNull;
import static net.kyori.adventure.text.Component.newline;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
public final class PlaceholdersCommand<A extends Audience> {
private final Supplier<ArrayList<String>> playersSuggestions;
private final Function<String, Audience> toAudience;
private final BiPredicate<A, String> hasPermission;
private final String command;
private final CommandManager<A> commandManager;
private PlaceholdersCommand(
final Supplier<ArrayList<String>> playersSuggestions,
final Function<String, Audience> toAudience,
final BiPredicate<A, String> hasPermission,
final String command,
final CommandManager<A> commandManager
) {
this.playersSuggestions = playersSuggestions;
this.toAudience = toAudience;
this.hasPermission = hasPermission;
this.command = command;
this.commandManager = commandManager;
}
private static final Component TITLE = miniMessage().deserialize("<gradient:#4d8bff:#a4ff96>MiniPlaceholders</gradient>");
private static final Component HELP = text()
.append(newline(), miniMessage().deserialize(
"<gradient:aqua:white:aqua><st><b> </st> <title> <st><b> </st>", Placeholder.component("title", TITLE)))
.append(newline(), miniMessage().deserialize(
"<gradient:#9694ff:#5269ff>Commands:</gradient>"))
.append(newline(), miniMessage().deserialize(
"<gradient:aqua:#94d1ff>/miniplaceholders</gradient> <aqua>help</aqua>"))
.append(newline(), miniMessage().deserialize(
"<gradient:aqua:#94d1ff>/miniplaceholders</gradient> <aqua>parse</aqua> <#8fadff><player | me></#8fadff> <#99ffb6><player></#99ffb6>"))
.append(newline(), miniMessage().deserialize(
"<gradient:aqua:white:aqua><st><b> "))
.build();
private static final Component INFO = AboutMenu.builder()
.title(TITLE)
.description(text("MiniMessage Component-based Placeholders for Paper, Folia, Fabric, Velocity, Sponge and Krypton platforms"))
.credits("Author", AboutMenu.Credit.of("4drian3d").url("https://github.com/4drian3d"))
.credits("Contributors", AboutMenu.Credit.of("Sliman4"))
.buttons(
AboutMenu.Link.of("https://github.com/MiniPlaceholders/MiniPlaceholders/wiki/User-Getting-Started").text("Documentation").icon("⛏"),
AboutMenu.Link.of("https://discord.gg/5NMMzK5mAn").text("Discord").color(TextColor.color(0x7289da)).icon("⭐"),
AboutMenu.Link.of("https://modrinth.com/plugin/miniplaceholders").text("Downloads").color(TextColor.color(0xff496e)).icon("↓")
).version(Version.fromString(PluginConstants.VERSION))
.build()
.toComponent();
private Command.Builder<A> rootBuilder() {
return commandManager.commandBuilder(command);
}
public void register() {
new MinecraftExceptionHandler<A>()
.withDefaultHandlers()
.withDecorator(component -> Component.text().append(TITLE).appendSpace().append(component).build())
.apply(commandManager, AudienceProvider.nativeAudience());
commandManager.command(rootBuilder()
.permission(src -> {
if (hasPermission != null) {
return hasPermission.test(src, "miniplaceholders.command");
}
return src
.get(PermissionChecker.POINTER)
.map(checker -> checker.value("miniplaceholders.command"))
.orElse(TriState.FALSE) != TriState.FALSE;
})
.handler(handler -> handler.getSender().sendMessage(INFO)));
final CommandArgument<A, String> sourceArgument = StringArgument.<A>builder("source")
.single()
.withDefaultDescription(ArgumentDescription.of("The source from which the message will be parsed"))
.withSuggestionsProvider((ctx, st) -> {
final List<String> suggestions = playersSuggestions.get();
suggestions.add("me");
return suggestions;
})
.build();
final CommandArgument<A, String> stringArgument = StringArgument.<A>builder("message")
.greedy()
.withDefaultDescription(ArgumentDescription.of("Message to be parsed"))
.build();
commandManager.command(
rootBuilder().literal("parse")
.argument(sourceArgument)
.argument(stringArgument)
.permission("miniplaceholders.command.parse")
.handler(handler -> {
final String source = handler.get(sourceArgument);
final Audience objective = "me".equals(source)
? handler.getSender()
: toAudience.apply(source);
if (objective == null){
handler.getSender().sendMessage(text("You must specify a valid player", RED));
return;
}
final String toParse = handler.get(stringArgument);
final Component parsed = miniMessage().deserialize(
toParse,
MiniPlaceholders.getAudienceGlobalPlaceholders(objective)
);
handler.getSender().sendMessage(parsed);
})
);
commandManager.command(
rootBuilder().literal("help")
.permission("miniplaceholders.command.help")
.handler(handler -> handler.getSender().sendMessage(HELP))
);
}
public static <A extends Audience> Builder<A> builder() {
return new Builder<>();
}
public final static class Builder<A extends Audience> implements AbstractBuilder<PlaceholdersCommand<A>> {
private Supplier<ArrayList<String>> playersSuggestions;
private Function<String, Audience> toAudience;
private BiPredicate<A, String> hasPermission;
private String command;
private CommandManager<A> commandManager;
private Builder() {}
public Builder<A> playerSuggestions(final Supplier<@NotNull ArrayList<@NotNull String>> playersSuggestions) {
this.playersSuggestions = playersSuggestions;
return this;
}
public Builder<A> toAudience(final @NotNull Function<@NotNull String, @Nullable Audience> argumentToAudience) {
this.toAudience = argumentToAudience;
return this;
}
public Builder<A> hasPermissionCheck(final @NotNull BiPredicate<@NotNull A, @NotNull String> hasPermission) {
this.hasPermission = hasPermission;
return this;
}
public Builder<A> command(final String command) {
this.command = command;
return this;
}
public Builder<A> manager(CommandManager<A> commandManager) {
this.commandManager = commandManager;
return this;
}
@Override
public @NotNull PlaceholdersCommand<A> build() {
return new PlaceholdersCommand<>(
requireNonNull(playersSuggestions),
requireNonNull(toAudience),
hasPermission,
command,
commandManager
);
}
}
}
@@ -1,7 +1,91 @@
package io.github.miniplaceholders.common;
public interface PlaceholdersPlugin {
void loadDefaultExpansions();
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.MiniPlaceholders;
import io.github.miniplaceholders.api.provider.ExpansionProvider;
import io.github.miniplaceholders.api.provider.LoadRequirement;
import io.github.miniplaceholders.api.provider.PlatformData;
import io.github.miniplaceholders.api.types.Platform;
import io.github.miniplaceholders.common.loader.ExpansionProviderLoader;
import io.github.miniplaceholders.common.loader.FailedToLoadExpansion;
import io.github.miniplaceholders.common.loader.ProviderLoadResult;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import team.unnamed.inject.Injector;
void registerPlatformCommand();
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public interface PlaceholdersPlugin {
default void registerPlatformCommand() {
}
default void loadProvidedExpansions(Path providersFolderDirectory) throws Exception {
final List<Expansion> loadedExpansions = new ArrayList<>();
final PlatformData platformData = new PlatformData(this.platformServerInstance(), this);
final Injector injector = Injector.create(binder -> binder.bind(PlatformData.class).toInstance(platformData));
for (ExpansionProvider provider : ExpansionProviderLoader.loadProvidersFromFolder(providersFolderDirectory)) {
injector.injectMembers(provider);
ProviderLoadResult loadResult = tryLoad(provider, provider.loadRequirement());
final Expansion loadedExpansion = loadResult.expansion();
if (loadedExpansion != null) {
loadedExpansions.add(loadedExpansion);
loadedExpansion.register();
continue;
}
final FailedToLoadExpansion failedToLoad = loadResult.failed();
logError(Component.text("The expansion "
+ provider.getClass() +
" could not be loaded because the requirement " + failedToLoad.requirement() + " could not be resolved",
NamedTextColor.RED));
}
if (loadedExpansions.isEmpty()) {
logInfo(Component.text("Not found expansions", NamedTextColor.GRAY));
return;
}
final String expansionsInfo = loadedExpansions.stream()
.map(Expansion::shortToString)
.collect(Collectors.joining(","));
logInfo(Component.text("Loaded expansions: " + expansionsInfo, NamedTextColor.GREEN));
}
default ProviderLoadResult tryLoad(final ExpansionProvider provider, final LoadRequirement loadRequirement) {
return switch (loadRequirement) {
case LoadRequirement.PlatformRequirement platformRequirement -> {
for (final Platform supportedPlatform : platformRequirement.platforms()) {
if (supportedPlatform == MiniPlaceholders.platform()) {
yield ProviderLoadResult.ofLoaded(provider.provideExpansion());
}
}
yield ProviderLoadResult.ofFailed(platformRequirement);
}
case LoadRequirement.AvailableComplementRequirement complementRequirement ->
complementRequirement.shouldLoad(this::platformHasComplementLoaded)
? ProviderLoadResult.ofLoaded(provider.provideExpansion())
: ProviderLoadResult.ofFailed(complementRequirement);
case LoadRequirement.MultiLoadRequirement multiLoadRequirement -> {
ProviderLoadResult loadResult;
for (final LoadRequirement requirement : multiLoadRequirement.requirements()) {
if ((loadResult = this.tryLoad(provider, requirement)).failed() != null) {
yield loadResult;
}
}
yield ProviderLoadResult.ofLoaded(provider.provideExpansion());
}
// NoneRequirement
default -> ProviderLoadResult.ofLoaded(provider.provideExpansion());
};
}
boolean platformHasComplementLoaded(String complementName);
void logError(Component component);
void logInfo(Component component);
Object platformServerInstance();
}
@@ -0,0 +1,10 @@
package io.github.miniplaceholders.common.command;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@FunctionalInterface
public interface AudienceConverter {
@Nullable Audience convert(@NotNull String string);
}
@@ -0,0 +1,9 @@
package io.github.miniplaceholders.common.command;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
@FunctionalInterface
public interface PermissionTester<S extends Audience> {
boolean hasPermission(@NotNull S source, @NotNull String permission);
}
@@ -0,0 +1,98 @@
package io.github.miniplaceholders.common.command;
import io.github.miniplaceholders.common.command.node.ExpansionsNode;
import io.github.miniplaceholders.common.command.node.HelpNode;
import io.github.miniplaceholders.common.command.node.ParseNode;
import io.github.miniplaceholders.common.command.node.RootNode;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.builder.AbstractBuilder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.minecraft.extras.AudienceProvider;
import org.incendo.cloud.minecraft.extras.MinecraftExceptionHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.stream.Stream;
import static java.util.Objects.requireNonNull;
import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
public record PlaceholdersCommand<A extends Audience>(
PlayerSuggestions playersSuggestions,
AudienceConverter audienceConverter,
PermissionTester<A> hasPermission,
String command,
CommandManager<A> commandManager
) {
public static final Component TITLE = miniMessage().deserialize("<gradient:#4d8bff:#a4ff96>MiniPlaceholders</gradient>");
public static final Component HEADER = miniMessage().deserialize(
"<gradient:aqua:white:aqua><st><b> </st> <title> <st><b> </st>", Placeholder.component("title", TITLE));
public static final Component FOOTER = miniMessage().deserialize(
"<gradient:aqua:white:aqua><st><b> ");
public void register() {
//noinspection unchecked
MinecraftExceptionHandler.create(AudienceProvider.nativeAudience())
.defaultHandlers()
.decorator(component -> Component.text().append(TITLE).appendSpace().append(component))
.registerTo((CommandManager<Audience>) commandManager);
// Nodes
Stream.of(new RootNode<>(hasPermission), new HelpNode<A>(), new ParseNode<A>(playersSuggestions, audienceConverter), new ExpansionsNode<A>())
.map(handler -> handler.apply(commandManager.commandBuilder(command)))
.forEach(commandManager::command);
}
public static <A extends Audience> Builder<A> builder() {
return new Builder<>();
}
public final static class Builder<A extends Audience> implements AbstractBuilder<PlaceholdersCommand<A>> {
private PlayerSuggestions playersSuggestions;
private AudienceConverter audienceConverter;
private PermissionTester<A> hasPermission;
private String command;
private CommandManager<A> commandManager;
private Builder() {}
public Builder<A> playerSuggestions(final @NotNull PlayerSuggestions playersSuggestions) {
this.playersSuggestions = playersSuggestions;
return this;
}
public Builder<A> toAudience(final @NotNull AudienceConverter audienceConverter) {
this.audienceConverter = audienceConverter;
return this;
}
public Builder<A> hasPermissionCheck(final @Nullable PermissionTester<A> hasPermission) {
this.hasPermission = hasPermission;
return this;
}
public Builder<A> command(final @NotNull String command) {
this.command = command;
return this;
}
public Builder<A> manager(final @NotNull CommandManager<A> commandManager) {
this.commandManager = commandManager;
return this;
}
@Override
public @NotNull PlaceholdersCommand<A> build() {
return new PlaceholdersCommand<>(
requireNonNull(playersSuggestions),
requireNonNull(audienceConverter),
hasPermission,
requireNonNull(command),
requireNonNull(commandManager)
);
}
}
}
@@ -0,0 +1,10 @@
package io.github.miniplaceholders.common.command;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@FunctionalInterface
public interface PlayerSuggestions {
@NotNull ArrayList<String> suggest();
}
@@ -0,0 +1,70 @@
package io.github.miniplaceholders.common.command.node;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.MiniPlaceholders;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import org.incendo.cloud.Command;
import java.util.Collection;
import static io.github.miniplaceholders.common.command.PlaceholdersCommand.FOOTER;
import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
public final class ExpansionsNode<S extends Audience> implements Node<S> {
@Override
public Command.Builder<S> apply(Command.Builder<S> rootBuilder) {
return rootBuilder.literal("expansions")
.permission("miniplaceholders.command.expansions")
.handler(handler -> {
TextComponent.Builder builder = Component.text()
.appendNewline()
.append(miniMessage().deserialize(
"<gradient:aqua:white:aqua><st><b> </st> <gradient:#4d8bff:#a4ff96>MiniPlaceholders</gradient> Expansions <st><b> </gradient>"
))
.appendNewline();
final Collection<Expansion> expansions = MiniPlaceholders.expansionsAvailable();
if (expansions.isEmpty()) {
builder.append(miniMessage().deserialize("""
<aqua>It seems that you don't have any <#a1ff4a>Expansion</#a1ff4a> installed yet,
You can find the <#a1ff4a>Expansion</#a1ff4a> you want here"""))
.appendSpace()
.append(Component.text()
.content("[MiniPlaceholders Expansions]")
.color(NamedTextColor.BLUE)
.clickEvent(ClickEvent.openUrl("https://github.com/MiniPlaceholders/MiniPlaceholders/wiki/Placeholders"))
.hoverEvent(Component.text("Search Expansions", NamedTextColor.GOLD)));
} else {
builder.append(expansions.stream()
.map(expansion -> {
final TextComponent.Builder expansionMetaBuilder = Component.text();
final String expansionName = expansion.name();
final String expansionAuthor = expansion.author();
final String expansionVersion = expansion.version();
expansionMetaBuilder.append(Component.text(expansionName, NamedTextColor.GRAY));
if (expansionAuthor != null) {
expansionMetaBuilder.appendNewline()
.append(Component.text("Author: ", TextColor.color(0x29fbff)))
.append(Component.text(expansionAuthor, NamedTextColor.GRAY));
}
if (expansionVersion != null){
expansionMetaBuilder.appendNewline()
.append(Component.text("Version: ", TextColor.color(0xff7b61)))
.append(Component.text(expansionVersion, NamedTextColor.GRAY));
}
return Component.text()
.content(expansion.name())
.hoverEvent(expansionMetaBuilder.build())
.build();
}).collect(Component.toComponent(Component.text(" | "))));
}
builder.appendNewline().append(FOOTER);
handler.sender().sendMessage(builder.build());
});
}
}
@@ -0,0 +1,33 @@
package io.github.miniplaceholders.common.command.node;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import org.incendo.cloud.Command;
import static io.github.miniplaceholders.common.command.PlaceholdersCommand.FOOTER;
import static io.github.miniplaceholders.common.command.PlaceholdersCommand.HEADER;
import static net.kyori.adventure.text.Component.newline;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
public final class HelpNode<S extends Audience> implements Node<S> {
private static final Component HELP = text()
.append(newline(), HEADER)
.append(newline(), miniMessage().deserialize(
"<gradient:#9694ff:#5269ff>Commands:</gradient>"))
.append(newline(), miniMessage().deserialize(
"<gradient:aqua:#94d1ff>/miniplaceholders</gradient> <aqua>help</aqua>"))
.append(newline(), miniMessage().deserialize(
"<gradient:aqua:#94d1ff>/miniplaceholders</gradient> <aqua>parse</aqua> <#8fadff><player | me></#8fadff> <#99ffb6><player></#99ffb6>"))
.append(newline(), miniMessage().deserialize(
"<gradient:aqua:#94d1ff>/miniplaceholders</gradient> <aqua>expansions</aqua>"))
.append(newline(), FOOTER)
.build();
@Override
public Command.Builder<S> apply(Command.Builder<S> rootBuilder) {
return rootBuilder.literal("help")
.permission("miniplaceholders.command.help")
.handler(handler -> handler.sender().sendMessage(HELP));
}
}
@@ -0,0 +1,8 @@
package io.github.miniplaceholders.common.command.node;
import net.kyori.adventure.audience.Audience;
import org.incendo.cloud.Command;
public sealed interface Node<S extends Audience> permits ExpansionsNode, HelpNode, ParseNode, RootNode {
Command.Builder<S> apply(Command.Builder<S> rootBuilder);
}
@@ -0,0 +1,65 @@
package io.github.miniplaceholders.common.command.node;
import io.github.miniplaceholders.api.MiniPlaceholders;
import io.github.miniplaceholders.common.command.AudienceConverter;
import io.github.miniplaceholders.common.command.PlayerSuggestions;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import org.incendo.cloud.Command;
import org.incendo.cloud.component.CommandComponent;
import org.incendo.cloud.component.TypedCommandComponent;
import org.incendo.cloud.description.Description;
import org.incendo.cloud.parser.standard.StringParser;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.util.List;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
public record ParseNode<S extends Audience>(
PlayerSuggestions playerSuggestions,
AudienceConverter audienceConverter
) implements Node<S> {
@Override
public Command.Builder<S> apply(Command.Builder<S> rootBuilder) {
final TypedCommandComponent<S, String> sourceArgument = CommandComponent.<S, String>ofType(String.class, "source")
.parser(StringParser.stringParser(StringParser.StringMode.SINGLE))
.description(Description.of("The source from which the message will be parsed"))
.suggestionProvider(SuggestionProvider.blockingStrings((ctx, st) -> {
final List<String> suggestions = playerSuggestions.suggest();
suggestions.add("me");
return suggestions;
}))
.build();
final TypedCommandComponent<S, String> stringArgument = CommandComponent.<S, String>ofType(String.class, "message")
.parser(StringParser.greedyStringParser())
.description(Description.of("Message to be parsed"))
.build();
return rootBuilder.literal("parse")
.argument(sourceArgument)
.argument(stringArgument)
.permission("miniplaceholders.command.parse")
.handler(handler -> {
final String source = handler.get(sourceArgument);
final Audience objective = "me".equals(source)
? handler.sender()
: audienceConverter.convert(source);
if (objective == null){
handler.sender().sendMessage(text("You must specify a valid player", RED));
return;
}
final String toParse = handler.get(stringArgument);
final Component parsed = miniMessage().deserialize(
toParse,
objective,
MiniPlaceholders.audienceGlobalPlaceholders()
);
handler.sender().sendMessage(parsed);
});
}
}
@@ -0,0 +1,52 @@
package io.github.miniplaceholders.common.command.node;
import io.github.miniplaceholders.common.PluginConstants;
import io.github.miniplaceholders.common.command.PermissionTester;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.permission.PermissionChecker;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.util.TriState;
import net.william278.desertwell.about.AboutMenu;
import net.william278.desertwell.util.Version;
import org.incendo.cloud.Command;
import org.incendo.cloud.permission.Permission;
import org.incendo.cloud.permission.PermissionResult;
import static io.github.miniplaceholders.common.command.PlaceholdersCommand.TITLE;
import static net.kyori.adventure.text.Component.text;
public record RootNode<S extends Audience>(PermissionTester<S> hasPermission) implements Node<S> {
private static final Component INFO = AboutMenu.builder()
.title(TITLE)
.description(text("MiniMessage Component-based Placeholders for Paper, Folia, Fabric, Velocity and Sponge platforms"))
.credits("Author", AboutMenu.Credit.of("4drian3d").url("https://github.com/4drian3d"))
.credits("Contributors",
AboutMenu.Credit.of("Sliman4"),
AboutMenu.Credit.of("Stampede2011"),
AboutMenu.Credit.of("powercasgamer")
)
.buttons(
AboutMenu.Link.of("https://github.com/MiniPlaceholders/MiniPlaceholders/wiki/User-Getting-Started").text("Documentation").icon("⛏"),
AboutMenu.Link.of("https://discord.gg/5NMMzK5mAn").text("Discord").color(TextColor.color(0x7289da)).icon("⭐"),
AboutMenu.Link.of("https://modrinth.com/plugin/miniplaceholders").text("Downloads").color(TextColor.color(0xff496e)).icon("↓")
).version(Version.fromString(PluginConstants.VERSION))
.build()
.toComponent();
@Override
public Command.Builder<S> apply(Command.Builder<S> rootBuilder) {
return rootBuilder
.permission(src -> {
if (hasPermission != null) {
return PermissionResult.of(hasPermission.hasPermission(src, "miniplaceholders.command"), Permission.permission("miniplaceholders.command"));
}
return PermissionResult.of(src.get(PermissionChecker.POINTER)
.map(checker -> checker.value("miniplaceholders.command"))
.orElse(TriState.FALSE) != TriState.FALSE,
Permission.permission("miniplaceholders.command")
);
})
.handler(handler -> handler.sender().sendMessage(INFO));
}
}
@@ -0,0 +1,97 @@
package io.github.miniplaceholders.common.loader;
import io.github.miniplaceholders.api.provider.ExpansionProvider;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
public final class ExpansionProviderLoader {
public static List<ExpansionProvider> loadProvidersFromFolder(final Path folferPath) throws Exception {
if (Files.notExists(folferPath)) {
try {
Files.createDirectories(folferPath);
} catch (IOException e) {
throw new RuntimeException("Unable to create expansions directory", e);
}
return List.of();
}
try (final DirectoryStream<Path> stream = Files.newDirectoryStream(folferPath,
filtered -> Files.isRegularFile(filtered) && filtered.toString().endsWith(".jar"))) {
final List<ExpansionProvider> providerList = new ArrayList<>();
for (final Path possibleProviderPath : stream) {
loadPossibleProviderFromPath(possibleProviderPath, providerList);
}
return providerList;
}
}
private static void loadPossibleProviderFromPath(
final Path filePath,
final List<ExpansionProvider> providers
) throws Exception {
String mainClassName = null;
try (final JarInputStream jarStream = new JarInputStream(Files.newInputStream(filePath))) {
JarEntry actualEntry;
while ((actualEntry = jarStream.getNextJarEntry()) != null) {
final String entryName = actualEntry.getName();
if (!Objects.equals(entryName, "expansion-provider.properties")) {
continue;
}
try (final Reader reader = new InputStreamReader(jarStream)) {
Properties properties = new Properties();
properties.load(reader);
mainClassName = properties.getProperty("provider-class");
break;
} catch (IOException e) {
throw new IOException(
"Unable to read expansion_provider.properties from " + filePath + "file",
e);
}
}
}
if (mainClassName == null) {
return;
}
loadFoundProvider(mainClassName, providers, filePath);
}
private static void loadFoundProvider(
final String providerClass,
final List<ExpansionProvider> providers,
final Path providerPath
) throws Exception {
//noinspection resource
final URLClassLoader classLoader = new URLClassLoader(new URL[] {
providerPath.toUri().toURL(),
}, ExpansionProviderLoader.class.getClassLoader());
Class<?> mainClass;
try {
mainClass = classLoader.loadClass(providerClass);
} catch (ClassNotFoundException e) {
throw new ClassNotFoundException("Unable to load main class from file " + providerPath, e);
}
if (!ExpansionProvider.class.isAssignableFrom(mainClass) || mainClass.isInterface()) {
return;
}
try {
providers.add((ExpansionProvider) mainClass.getConstructors()[0].newInstance());
} catch (Exception e) {
throw new RuntimeException("Unable to create expansion provider from file " + providerPath, e);
}
}
}
@@ -0,0 +1,6 @@
package io.github.miniplaceholders.common.loader;
import io.github.miniplaceholders.api.provider.LoadRequirement;
public record FailedToLoadExpansion(LoadRequirement requirement) {
}
@@ -0,0 +1,14 @@
package io.github.miniplaceholders.common.loader;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.provider.LoadRequirement;
public record ProviderLoadResult(Expansion expansion, FailedToLoadExpansion failed) {
public static ProviderLoadResult ofLoaded(Expansion expansion) {
return new ProviderLoadResult(expansion, null);
}
public static ProviderLoadResult ofFailed(LoadRequirement requirement) {
return new ProviderLoadResult(null, new FailedToLoadExpansion(requirement));
}
}
@@ -2,7 +2,8 @@ package io.github.miniplaceholders.connect;
public enum InternalPlatform {
PAPER, VELOCITY, KRYPTON, FABRIC, SPONGE;
PAPER, VELOCITY,
FABRIC, SPONGE;
private static InternalPlatform actualPlatform;
@@ -0,0 +1,23 @@
plugins {
id("miniplaceholders.auto.module")
alias(libs.plugins.idea.ext)
alias(libs.plugins.blossom)
}
version = "1.0.0"
dependencies {
compileOnly(projects.miniplaceholdersApi)
compileOnly(libs.adventure.api)
compileOnly(libs.adventure.minimessage)
}
sourceSets {
main {
blossom {
javaSources {
property("version", project.version.toString())
}
}
}
}
@@ -0,0 +1,7 @@
package io.github.miniplaceholders.provider.example;
public final class ExpansionConstants {
public static final String VERSION = "{{ version }}";
private ExpansionConstants() {}
}
@@ -0,0 +1,36 @@
package io.miniplaceholders.provider.example;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.provider.ExpansionProvider;
import io.github.miniplaceholders.api.provider.LoadRequirement;
import io.github.miniplaceholders.api.types.Platform;
import io.github.miniplaceholders.provider.example.ExpansionConstants;
import net.kyori.adventure.text.minimessage.tag.Tag;
public final class ExampleProvider implements ExpansionProvider {
// For advanced usage only
@team.unnamed.inject.Inject
private io.github.miniplaceholders.api.provider.PlatformData platformData;
@Override
public Expansion provideExpansion() {
return Expansion.builder("example")
.version("2.0.0")
.author("MiniPlaceholders Contributors")
.globalPlaceholder("test", (queue, context) ->
Tag.preProcessParsed("""
Platform Instance Class: %s
MiniPlaceholders Instance Class: %s
""".formatted(
platformData.serverInstance(),
platformData.complementInstance())))
.version(ExpansionConstants.VERSION)
.build();
}
@Override
public LoadRequirement loadRequirement() {
return LoadRequirement.allOf(LoadRequirement.allOf(LoadRequirement.platform(Platform.VELOCITY)));
}
}
@@ -0,0 +1 @@
provider-class=io.miniplaceholders.provider.example.ExampleProvider
+9 -4
View File
@@ -1,6 +1,6 @@
plugins {
id("miniplaceholders.auto.module")
id("fabric-loom")
id("quiet-fabric-loom")
alias(libs.plugins.shadow)
}
@@ -14,6 +14,7 @@ dependencies {
includeDependency(libs.adventure.platform.fabric)
includeDependency(libs.adventure.serializer.legacy)
includeDependency(libs.adventure.minimessage)
includeDependency(libs.luckopermissionsapi)
includeDependency(libs.cloud.fabric)
includeDependency(libs.cloud.core)
@@ -39,17 +40,21 @@ fun DependencyHandlerScope.includeDependency(dependency: Any) {
include(dependency)
}
tasks {
val projectVersion = project.version
val rootDir = project.rootDir
processResources {
filteringCharset = Charsets.UTF_8.name()
filesMatching("fabric.mod.json") {
expand("version" to project.version)
expand("version" to projectVersion)
}
}
remapJar {
inputFile.set(shadowJar.get().archiveFile)
archiveFileName.set("MiniPlaceholders-Fabric-${project.version}.jar")
destinationDirectory.set(file("${project.rootDir}/jar"))
archiveFileName.set("MiniPlaceholders-Fabric-${projectVersion}.jar")
destinationDirectory.set(file("${rootDir}/jar"))
}
shadowJar {
configurations = listOf(shade)
@@ -1,105 +1,52 @@
package io.github.miniplaceholders.fabric;
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator;
import cloud.commandframework.fabric.FabricServerCommandManager;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.common.PlaceholdersCommand;
import io.github.miniplaceholders.common.PlaceholdersPlugin;
import io.github.miniplaceholders.common.command.PlaceholdersCommand;
import io.github.miniplaceholders.connect.InternalPlatform;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.packs.PackResources;
import net.minecraft.world.entity.Entity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.incendo.cloud.SenderMapper;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.fabric.FabricServerCommandManager;
import java.text.DecimalFormat;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class FabricMod implements ModInitializer, PlaceholdersPlugin {
private final Logger logger = LoggerFactory.getLogger("miniplaceholders");
private static final DecimalFormat MSPT_FORMAT = new DecimalFormat("###.###");
private final TickManager tickManager = new TickManager();
private final ComponentLogger componentLogger = ComponentLogger.logger("miniplaceholders");
private MinecraftServer minecraftServer;
@Override
public void onInitialize() {
logger.info("Starting MiniPlaceholders Fabric");
componentLogger.info("Starting MiniPlaceholders Fabric");
InternalPlatform.platform(InternalPlatform.FABRIC);
ServerLifecycleEvents.SERVER_STARTING.register(minecraftServer -> {
this.minecraftServer = minecraftServer;
this.loadDefaultExpansions();
try {
final Path expansionsFolder = FabricLoader.getInstance().getConfigDir()
.resolve("miniplaceholders").resolve("expansions");
this.loadProvidedExpansions(expansionsFolder);
} catch (Throwable e) {
componentLogger.error("Unable to load expansion providers", e);
}
});
this.registerPlatformCommand();
}
@Override
public void loadDefaultExpansions() {
Expansion.builder("server")
.globalPlaceholder("name", Tag.preProcessParsed(this.minecraftServer.getServerModName()))
.globalPlaceholder("online", (queue, ctx) -> Tag.preProcessParsed(Integer.toString(this.minecraftServer.getPlayerCount())))
.globalPlaceholder("version", (ctx, queue) -> Tag.preProcessParsed(this.minecraftServer.getServerVersion()))
.globalPlaceholder("max_players", (ctx, queue) -> Tag.preProcessParsed(Integer.toString(this.minecraftServer.getMaxPlayers())))
.globalPlaceholder("unique_joins", (queue, ctx) -> Tag.preProcessParsed(Integer.toString(this.minecraftServer.getProfileCache().load().size())))
.globalPlaceholder("tps_1m", (ctx, queue) -> Tag.preProcessParsed(tickManager.getTps1m().formattedAverage()))
.globalPlaceholder("tps_5m", (ctx, queue) -> Tag.preProcessParsed(tickManager.getTps5m().formattedAverage()))
//.globalPlaceholder("tps_15m", (ctx, queue) -> Tag.preProcessParsed(tickManager.getTps15m().formattedAverage()))
.globalPlaceholder("tick_count", (queue, ctx) -> Tag.preProcessParsed(Integer.toString(this.minecraftServer.getTickCount())))
.globalPlaceholder("has_whitelist", (queue, ctx) -> Tag.preProcessParsed(Boolean.toString(this.minecraftServer.getPlayerList().isUsingWhitelist())))
.globalPlaceholder("total_chunks", (queue, ctx) -> {
int chunks = 0;
for (final ServerLevel level : this.minecraftServer.getAllLevels()) {
chunks += level.getChunkSource().getLoadedChunksCount();
}
return Tag.preProcessParsed(Integer.toString(chunks));
})
.globalPlaceholder("total_entities", (queue, ctx) -> {
int entities = 0;
for (final ServerLevel level : this.minecraftServer.getAllLevels()) {
for (final Entity ignored : level.getAllEntities()) {
entities++;
}
}
return Tag.preProcessParsed(Integer.toString(entities));
})
.globalPlaceholder("mspt", (queue, ctx) -> Tag.preProcessParsed(MSPT_FORMAT.format(this.minecraftServer.getAverageTickTime())))
.globalPlaceholder("datapack_list", (queue, ctx) ->
Tag.selfClosingInserting(this.minecraftServer.getResourceManager()
.listPacks()
.map(PackResources::packId)
.map(id -> Component.text()
.content("[")
.append(Component.text(id))
.append(Component.text("]"))
.build())
.collect(Component.toComponent(Component.space())))
)
.globalPlaceholder("datapack_count", (queue, ctx) ->
Tag.preProcessParsed(Long.toString(this.minecraftServer.getResourceManager()
.listPacks()
.count()))
)
.build()
.register();
}
@Override
public void registerPlatformCommand() {
final FabricServerCommandManager<CommandSourceStack> commandManager = new FabricServerCommandManager<>(
AsynchronousCommandExecutionCoordinator.simpleCoordinator(),
Function.identity(),
Function.identity()
ExecutionCoordinator.simpleCoordinator(),
SenderMapper.identity()
);
PlaceholdersCommand.<CommandSourceStack>builder()
.hasPermissionCheck((source, permission) -> Permissions.check(source, permission, 4))
@@ -110,4 +57,24 @@ public class FabricMod implements ModInitializer, PlaceholdersPlugin {
.build()
.register();
}
@Override
public boolean platformHasComplementLoaded(String complementName) {
return FabricLoader.getInstance().isModLoaded(complementName);
}
@Override
public void logError(Component component) {
componentLogger.error(component);
}
@Override
public void logInfo(Component component) {
componentLogger.info(component);
}
@Override
public Object platformServerInstance() {
return this.minecraftServer;
}
}
@@ -1,59 +0,0 @@
package io.github.miniplaceholders.fabric;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
/**
* Based on the MIT licensed Paper-Server patch "Further improve server tick loop".
*
* @author Daniel Ennis/Aikar
*/
final class RollingAverage {
private static final DecimalFormat TPS_FORMAT = new DecimalFormat("###.##");
static { TPS_FORMAT.setRoundingMode(RoundingMode.HALF_UP); }
private static final int TPS = 20;
private static final long SEC_IN_NANO = 1000000000;
private final int size;
private final BigDecimal[] samples;
private final long[] times;
private long time;
private BigDecimal total;
private int index = 0;
RollingAverage(final int size) {
this.size = size;
this.samples = new BigDecimal[size];
this.times = new long[size];
this.time = size * SEC_IN_NANO;
this.total = dec(TPS).multiply(dec(SEC_IN_NANO)).multiply(dec(size));
for (int i = 0; i < size; i++) {
this.samples[i] = dec(TPS);
this.times[i] = SEC_IN_NANO;
}
}
private static BigDecimal dec(final long t) {
return new BigDecimal(t);
}
void add(final BigDecimal x, final long t) {
this.time -= this.times[this.index];
this.total = this.total.subtract(this.samples[this.index].multiply(dec(this.times[this.index])));
this.samples[this.index] = x;
this.times[this.index] = t;
this.time += t;
this.total = this.total.add(x.multiply(dec(t)));
if (++this.index == this.size) {
this.index = 0;
}
}
double average() {
return this.total.divide(dec(this.time), 30, RoundingMode.HALF_UP).doubleValue();
}
String formattedAverage() {
return TPS_FORMAT.format(average());
}
}
@@ -1,45 +0,0 @@
package io.github.miniplaceholders.fabric;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import java.math.BigDecimal;
import java.math.RoundingMode;
final class TickManager {
private static final int TICK_DURATION = 20;
private static final BigDecimal TPS_BASE = new BigDecimal("1E9").multiply(new BigDecimal(TICK_DURATION));
private final RollingAverage tps1m = new RollingAverage(60);
private final RollingAverage tps5m = new RollingAverage(60 * 5);
private final RollingAverage tps15m = new RollingAverage(60 * 15);
private long lastStartTick = 0;
private long lastEndTick = 0;
TickManager() {
ServerTickEvents.START_SERVER_TICK.register(server -> this.lastStartTick = System.nanoTime());
ServerTickEvents.END_SERVER_TICK.register(server -> {
if (server.getTickCount() % TICK_DURATION == 0) {
final long diff = lastStartTick - lastEndTick;
final BigDecimal currentTps = TPS_BASE.divide(new BigDecimal(diff), 30, RoundingMode.HALF_UP);
this.tps1m.add(currentTps, diff);
this.tps5m.add(currentTps, diff);
this.tps15m.add(currentTps, diff);
this.lastEndTick = System.nanoTime();
}
});
}
public RollingAverage getTps1m() {
return tps1m;
}
public RollingAverage getTps5m() {
return tps5m;
}
public RollingAverage getTps15m() {
return tps15m;
}
}
+5 -3
View File
@@ -3,7 +3,7 @@
"id": "miniplaceholders",
"version": "${version}",
"name": "MiniPlaceholders",
"description": "MiniMessage Component-based Placeholders for PaperMC, Fabric, Krypton and Velocity platforms",
"description": "MiniMessage Component-based Placeholders API",
"authors": [
"4drian3d"
],
@@ -22,8 +22,10 @@
"depends": {
"fabricloader": ">=0.14.19",
"fabric": "*",
"java": ">=17",
"minecraft": ">=1.20.2",
"java": ">=21",
"cloud": "*",
"adventure-platform-fabric": "*",
"minecraft": ">=1.21.4",
"fabric-permissions-api-v0": "*"
}
}
+6 -3
View File
@@ -1,5 +1,8 @@
group = io.github.miniplaceholders
version = 2.2.3
description = MiniMessage Component-based Placeholders for Paper, Fabric, Sponge, Folia and Velocity platforms
version = 3.0.0-SNAPSHOT
description = MiniMessage Component-based Placeholders API
org.gradle.jvmargs=-Xmx2G
org.gradle.jvmargs=-Xmx2G
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
#org.gradle.configuration-cache=true
+31 -39
View File
@@ -2,27 +2,25 @@ metadata.format.version = "1.1"
[versions]
velocity = "3.2.0-SNAPSHOT"
paper = "1.20.2-R0.1-SNAPSHOT"
minecraft = "1.20.2"
velocity = "3.4.0-SNAPSHOT"
paper = "1.21.8-R0.1-SNAPSHOT"
minecraft = "1.21.8"
runtask = "2.2.2"
pluginyml = "0.6.0"
userdev = "1.5.9"
runtask = "2.3.1"
blossom = "2.1.0"
indra = "3.1.3"
shadow = "8.1.1"
jmh = "0.7.2"
dokka = "1.9.10"
shadow = "9.0.2"
jmh = "0.7.3"
dokka = "2.0.0"
cloud = "1.8.4"
adventure = "4.14.0"
adventurefabric = "5.10.1"
brigadier = "1.0.500"
kotlin="1.9.20"
cloud = "2.0.0"
cloud-minecraft = "2.0.0-SNAPSHOT"
cloud-minecraft-modded = "2.0.0-SNAPSHOT"
adventure = "4.24.0"
adventureModded = "6.6.0"
kotlin = "2.2.20-RC"
fabric-api = "0.90.1+1.20.3"
fabric-loader = "0.14.24"
fabric-api = "0.132.1+1.21.9"
fabric-loader = "0.17.2"
desertwell = "2.0.4"
@@ -34,42 +32,36 @@ minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft
fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" }
fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" }
luckopermissionsapi = { group = "me.lucko", name = "fabric-permissions-api", version = "0.2-SNAPSHOT" }
luckopermissionsapi = { group = "me.lucko", name = "fabric-permissions-api", version = "0.4.1" }
adventure-api = { group = "net.kyori", name = "adventure-api", version.ref = "adventure" }
adventure-minimesssage = { group = "net.kyori", name = "adventure-text-minimessage", version.ref = "adventure" }
adventure-platform-fabric = { group = "net.kyori", name = "adventure-platform-fabric", version.ref = "adventurefabric" }
adventure-minimessage = { group = "net.kyori", name = "adventure-text-minimessage", version.ref = "adventure" }
adventure-platform-fabric = { group = "net.kyori", name = "adventure-platform-fabric", version.ref = "adventureModded" }
adventure-serializer-legacy = { group = "net.kyori", name = "adventure-text-serializer-legacy", version.ref = "adventure" }
adventure-serializer-plain = { group = "net.kyori", name = "adventure-text-serializer-plain", version.ref = "adventure" }
cloud-core = { group = "cloud.commandframework", name = "cloud-core", version.ref = "cloud" }
cloud-annotations = { group = "cloud.commandframework", name = "cloud-annotations", version.ref = "cloud" }
cloud-velocity = { group = "cloud.commandframework", name = "cloud-velocity", version.ref = "cloud" }
cloud-paper = { group = "cloud.commandframework", name = "cloud-paper", version.ref = "cloud" }
cloud-fabric = { group = "cloud.commandframework", name = "cloud-fabric", version.ref = "cloud" }
cloud-sponge = { group = "cloud.commandframework", name = "cloud-sponge", version = "1.8.0-SNAPSHOT" }
cloud-extras = { group = "cloud.commandframework", name = "cloud-minecraft-extras", version.ref = "cloud" }
cloud-core = { group = "org.incendo", name = "cloud-core", version.ref = "cloud" }
cloud-velocity = { group = "org.incendo", name = "cloud-velocity", version.ref = "cloud-minecraft" }
cloud-paper = { group = "org.incendo", name = "cloud-paper", version.ref = "cloud-minecraft" }
cloud-fabric = { group = "org.incendo", name = "cloud-fabric", version.ref = "cloud-minecraft-modded" }
cloud-extras = { group = "org.incendo", name = "cloud-minecraft-extras", version.ref = "cloud-minecraft" }
brigadier = { group = "com.mojang", name = "brigadier", version.ref = "brigadier" }
plugin-shadow = { group = "com.github.johnrengelman", name = "shadow", version.ref = "shadow" }
plugin-shadow = { module = "com.gradleup.shadow:shadow-gradle-plugin", version.ref = "shadow" }
plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
plugin-indra-common = { module = "net.kyori:indra-common", version.ref = "indra" }
plugin-indra-spotless = { module = "net.kyori:indra-licenser-spotless", version.ref = "indra" }
plugin-dokka = "org.jetbrains.dokka:dokka-gradle-plugin:1.9.10"
plugin-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
desertwell = { group = "net.william278", name = "DesertWell", version.ref = "desertwell" }
junit-bom = "org.junit:junit-bom:5.10.1"
junit-bom = "org.junit:junit-bom:5.13.4"
jspecify = "org.jspecify:jspecify:1.0.0"
unnamedinject = "team.unnamed:inject:2.0.1"
[plugins]
pluginyml = { id = "net.minecrell.plugin-yml.bukkit", version.ref = "pluginyml" }
userdev = { id = "io.papermc.paperweight.userdev", version.ref = "userdev" }
runpaper = { id = "xyz.jpenilla.run-paper", version.ref = "runtask" }
runvelocity = { id = "xyz.jpenilla.run-velocity", version.ref = "runtask" }
blossom = { id = "net.kyori.blossom", version.ref = "blossom" }
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
jmh = { id = "me.champeau.jmh", version.ref = "jmh" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
idea-ext = { id = "org.jetbrains.gradle.plugin.idea-ext", version = "1.1.7" }
idea-ext = { id = "org.jetbrains.gradle.plugin.idea-ext", version = "1.2" }
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+8 -6
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -112,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -203,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+14 -12
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
+1
View File
@@ -0,0 +1 @@
# Package io.github.miniplaceholders.kotlin
+2 -2
View File
@@ -6,14 +6,14 @@ plugins {
dependencies {
compileOnly(kotlin("stdlib"))
compileOnly(libs.adventure.api)
compileOnly(libs.adventure.minimesssage)
compileOnly(libs.adventure.minimessage)
compileOnlyApi(projects.miniplaceholdersApi)
}
kotlin {
explicitApi()
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(17))
languageVersion.set(JavaLanguageVersion.of(21))
}
}
@@ -1,6 +1,11 @@
package io.github.miniplaceholders.kotlin
import io.github.miniplaceholders.api.Expansion
import io.github.miniplaceholders.api.resolver.AudienceTagResolver
import io.github.miniplaceholders.api.resolver.GlobalTagResolver
import io.github.miniplaceholders.api.resolver.RelationalTagResolver
import net.kyori.adventure.audience.Audience
import net.kyori.adventure.text.minimessage.tag.Tag
/**
* Create a new Expansion
@@ -12,5 +17,23 @@ import io.github.miniplaceholders.api.Expansion
* @see Expansion.builder
* @see Expansion.Builder.build
* */
public fun expansion(name: String, builder: Expansion.Builder.() -> Expansion.Builder): Expansion
= builder(Expansion.builder(name)).build()
public fun expansion(name: String, builder: Expansion.Builder.() -> Expansion.Builder): Expansion =
builder(Expansion.builder(name)).build()
public fun Expansion.Builder.global(placeholderName: String, resolver: GlobalTagResolver): Expansion.Builder =
this.globalPlaceholder(placeholderName, resolver)
public fun Expansion.Builder.global(placeholderName: String, tag: Tag): Expansion.Builder =
this.globalPlaceholder(placeholderName, tag)
public inline fun <reified A : Audience> Expansion.Builder.audience(
placeholderName: String,
resolver: AudienceTagResolver<A>
): Expansion.Builder =
this.audiencePlaceholder(A::class.java, placeholderName, resolver)
public inline fun <reified A : Audience> Expansion.Builder.relational(
placeholderName: String,
resolver: RelationalTagResolver<A>
): Expansion.Builder =
this.relationalPlaceholder(A::class.java, placeholderName, resolver)
@@ -0,0 +1,14 @@
package io.github.miniplaceholders.kotlin
import io.github.miniplaceholders.api.utils.LegacyStrings
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.minimessage.Context
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver
public fun String.hasLegacyFormat() : Boolean = LegacyStrings.hasLegacyFormat(this)
public fun String.parsePossibleLegacy(resolver: TagResolver = TagResolver.empty()) : Component =
LegacyStrings.parsePossibleLegacy(this, resolver)
public fun String.parsePossibleLegacy(context: Context) : Component =
LegacyStrings.parsePossibleLegacy(this, context)
-27
View File
@@ -1,27 +0,0 @@
plugins {
kotlin("jvm")
kotlin("kapt")
alias(libs.plugins.shadow)
}
repositories {
maven("https://repo.kryptonmc.org/releases")
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
maven("https://repo.kryptonmc.org/snapshots")
}
dependencies {
compileOnly(libs.krypton.server)
compileOnly(kotlin("stdlib"))
kapt(libs.krypton.annotation)
implementation(projects.miniplaceholdersCommon)
implementation(projects.miniplaceholdersApi)
implementation(projects.miniplaceholdersConnect)
implementation(projects.miniplaceholdersKotlinExt)
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
@@ -1,85 +0,0 @@
package io.github.miniplaceholders.krypton
import com.google.inject.Inject
import io.github.miniplaceholders.api.utils.TagsUtils
import io.github.miniplaceholders.common.PlaceholdersCommand
import io.github.miniplaceholders.common.PlaceholdersPlugin
import io.github.miniplaceholders.common.PluginConstants
import io.github.miniplaceholders.connect.InternalPlatform
import io.github.miniplaceholders.kotlin.asClosingTag
import io.github.miniplaceholders.kotlin.expansion
import net.kyori.adventure.text.Component
import org.apache.logging.log4j.Logger
import org.kryptonmc.api.command.BrigadierCommand
import org.kryptonmc.api.command.CommandExecutionContext
import org.kryptonmc.api.event.Listener
import org.kryptonmc.api.event.server.ServerStartEvent
import org.kryptonmc.api.plugin.annotation.Plugin
import org.kryptonmc.krypton.KryptonServer
import org.kryptonmc.krypton.command.KryptonCommandMeta
@Plugin(
name = "MiniPlaceholders",
id = "miniplaceholders",
version = PluginConstants.VERSION,
authors = ["4drian3d"]
)
class KotlinKryptonPlugin @Inject constructor(
private val server: KryptonServer,
private val logger: Logger
) : PlaceholdersPlugin {
init {
InternalPlatform.platform(InternalPlatform.KRYPTON)
}
@Listener
@Suppress("UNUSED")
fun onServerStart(event: ServerStartEvent) {
logger.info("Starting MiniPlaceholders Krypton")
this.loadDefaultExpansions()
this.registerPlatformCommand()
}
override fun loadDefaultExpansions() {
expansion("server") {
globalPlaceholder("total_entities") { _, _ ->
var entityCount = 0
for ((_, value) in server.worldManager.worlds) {
entityCount += value.entities.size
}
Component.text(entityCount).asClosingTag()
}
globalPlaceholder("total_chunks") { _, _ ->
var chunkCount = 0
for ((_, value) in server.worldManager.worlds) {
chunkCount += value.chunks.size
}
Component.text(chunkCount).asClosingTag()
}
globalPlaceholder("name", TagsUtils.staticTag(server.platform.name))
globalPlaceholder("online") { queue, _ ->
if (queue.hasNext()) {
val worldName = queue.pop().value()
val players = server.players.filter { it.world.name == worldName }
return@globalPlaceholder Component.text(players.joinToString(", ")).asClosingTag()
}
Component.text(server.players.size).asClosingTag()
}
globalPlaceholder("version", TagsUtils.staticTag(server.platform.version))
globalPlaceholder("max_players", Component.text(server.config.maxPlayers).asClosingTag())
}.register()
}
override fun registerPlatformCommand() {
/*val command = PlaceholdersCommand.builder<CommandExecutionContext>()
.fromSourceToAudience { it.sender }
.playerSuggestions { server.players.map { it.name } }
.toAudience { server.getPlayer(it) }
.build()
val brigadierCMD = BrigadierCommand.of(command.asNode("miniplaceholders"))
val meta = KryptonCommandMeta.Builder("miniplaceholders").build()
server.commandManager.register(brigadierCMD, meta)*/
}
}
+4 -2
View File
@@ -14,11 +14,13 @@ dependencies {
tasks {
runServer {
minecraftVersion("1.20.2")
minecraftVersion(libs.versions.minecraft.get())
}
val projectVersion = project.version
processResources {
//val version = project.version
filesMatching("paper-plugin.yml") {
expand("version" to project.version)
expand("version" to projectVersion)
}
}
}
@@ -0,0 +1,51 @@
package io.github.miniplaceholders.paper;
import io.github.miniplaceholders.common.command.PlaceholdersCommand;
import io.github.miniplaceholders.connect.InternalPlatform;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.plugin.bootstrap.BootstrapContext;
import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.incendo.cloud.SenderMapper;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.paper.PaperCommandManager;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.stream.Collectors;
@SuppressWarnings({"unused", "UnstableApiUsage"})
public final class PaperBootstrap implements PluginBootstrap {
@Override
public void bootstrap(@NotNull BootstrapContext bootstrapContext) {
InternalPlatform.platform(InternalPlatform.PAPER);
PaperCommandManager<SourceWrapper> commandManager = PaperCommandManager
.builder(SenderMapper.create(SourceWrapper::new, SourceWrapper::stack))
.executionCoordinator(ExecutionCoordinator.simpleCoordinator())
.buildBootstrapped(bootstrapContext);
PlaceholdersCommand.<SourceWrapper>builder()
.playerSuggestions(() -> Bukkit.getServer().getOnlinePlayers()
.stream()
.map(Player::getName)
.collect(Collectors.toCollection(ArrayList::new)))
.toAudience(st -> Bukkit.getServer().getPlayer(st))
.hasPermissionCheck((aud, p) -> aud.stack.getSender().hasPermission(p))
.manager(commandManager)
.command("miniplaceholders")
.build()
.register();
}
public record SourceWrapper(CommandSourceStack stack) implements ForwardingAudience.Single {
@Override
public @NotNull Audience audience() {
return this.stack.getSender();
}
}
}
@@ -1,109 +1,44 @@
package io.github.miniplaceholders.paper;
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator;
import cloud.commandframework.paper.PaperCommandManager;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.common.PlaceholdersCommand;
import io.github.miniplaceholders.common.PlaceholdersPlugin;
import io.github.miniplaceholders.connect.InternalPlatform;
import io.papermc.paper.datapack.Datapack;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.minimessage.tag.Tag;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permissible;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.bukkit.plugin.java.JavaPlugin;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.function.Function;
import java.util.stream.Collectors;
import static net.kyori.adventure.text.Component.text;
@SuppressWarnings("unused")
public final class PaperPlugin extends JavaPlugin implements PlaceholdersPlugin {
private static final DecimalFormat TPS_FORMAT = new DecimalFormat("###.##");
private static final DecimalFormat MSPT_FORMAT = new DecimalFormat("###.###");
static {
InternalPlatform.platform(InternalPlatform.PAPER);
TPS_FORMAT.setRoundingMode(RoundingMode.HALF_UP);
MSPT_FORMAT.setRoundingMode(RoundingMode.HALF_UP);
}
@Override
public void onEnable(){
this.getSLF4JLogger().info("Starting MiniPlaceholders Paper");
this.loadDefaultExpansions();
this.registerPlatformCommand();
}
public void onEnable() {
final ComponentLogger componentLogger = getComponentLogger();
componentLogger.info(text("Starting MiniPlaceholders Paper", NamedTextColor.GREEN));
@Override
public void loadDefaultExpansions() {
Expansion.builder("server")
.globalPlaceholder("name", Tag.preProcessParsed(this.getServer().getName()))
.globalPlaceholder("online", (queue, ctx) -> Tag.preProcessParsed(Integer.toString(this.getServer().getOnlinePlayers().size())))
.globalPlaceholder("version", Tag.preProcessParsed(this.getServer().getVersion()))
.globalPlaceholder("max_players", (queue, ctx) -> Tag.preProcessParsed(Integer.toString(this.getServer().getMaxPlayers())))
.globalPlaceholder("unique_joins", (queue, ctx) -> Tag.preProcessParsed(Integer.toString(this.getServer().getOfflinePlayers().length)))
.globalPlaceholder("tps_1", (queue, ctx) -> Tag.preProcessParsed(TPS_FORMAT.format(this.getServer().getTPS()[0])))
.globalPlaceholder("tps_5", (queue, ctx) -> Tag.preProcessParsed(TPS_FORMAT.format(this.getServer().getTPS()[1])))
.globalPlaceholder("tps_15", (queue, ctx) -> Tag.preProcessParsed(TPS_FORMAT.format(this.getServer().getTPS()[2])))
.globalPlaceholder("has_whitelist", (queue, ctx) -> Tag.preProcessParsed(Boolean.toString(this.getServer().hasWhitelist())))
.globalPlaceholder("total_chunks", (queue, ctx) -> {
int chunkCount = 0;
for (World world : this.getServer().getWorlds()){
chunkCount += world.getLoadedChunks().length;
}
return Tag.preProcessParsed(Integer.toString(chunkCount));
})
.globalPlaceholder("total_entities", (queue, ctx) -> {
int entityCount = 0;
for (World world : this.getServer().getWorlds()){
entityCount += world.getEntityCount();
}
return Tag.preProcessParsed(Integer.toString(entityCount));
})
.globalPlaceholder("mspt", (queue, ctx) -> Tag.preProcessParsed(MSPT_FORMAT.format(this.getServer().getAverageTickTime())))
.globalPlaceholder("datapack_list", (queue, ctx) -> {
final TextComponent.Builder builder = Component.text();
for (final Datapack datapack : this.getServer().getDatapackManager().getEnabledPacks()) {
builder.append(Component.text("[").append(Component.text(datapack.getName()).append(Component.text("] "))));
}
return Tag.selfClosingInserting(builder.build());
})
.globalPlaceholder("datapack_count", (queue, ctx) -> Tag.preProcessParsed(Integer.toString(this.getServer().getDatapackManager().getEnabledPacks().size())))
.build()
.register();
}
@Override
@SuppressWarnings({"sonarlint(java:s1874)"})
public void registerPlatformCommand() {
try {
PaperCommandManager<CommandSender> commandManager = new PaperCommandManager<>(
this,
AsynchronousCommandExecutionCoordinator.simpleCoordinator(),
Function.identity(),
Function.identity()
);
commandManager.registerBrigadier();
PlaceholdersCommand.<CommandSender>builder()
.playerSuggestions(() -> getServer().getOnlinePlayers()
.stream()
.map(Player::getName)
.collect(Collectors.toCollection(ArrayList::new)))
.toAudience(st -> getServer().getPlayer(st))
.hasPermissionCheck(Permissible::hasPermission)
.manager(commandManager)
.command("miniplaceholders")
.build()
.register();
} catch (Exception e) {
throw new RuntimeException(e);
this.loadProvidedExpansions(getDataPath().resolve("expansions"));
} catch (Throwable e) {
componentLogger.error("Unable to load expansion providers", e);
}
}
@Override
public boolean platformHasComplementLoaded(String complementName) {
return getServer().getPluginManager().isPluginEnabled(complementName);
}
@Override
public void logError(Component component) {
getComponentLogger().error(component);
}
@Override
public void logInfo(Component component) {
getComponentLogger().info(component);
}
@Override
public Object platformServerInstance() {
return this.getServer();
}
}
+7 -3
View File
@@ -1,6 +1,10 @@
name: MiniPlaceholders
authors:
- MiniPlaceholders Contributors
version: '${version}'
main: io.github.miniplaceholders.paper.PaperPlugin
description: MiniMessage Component-based Placeholders for PaperMC, Krypton and Velocity platforms
api-version: '1.19'
folia-supported: true
bootstrapper: io.github.miniplaceholders.paper.PaperBootstrap
description: MiniMessage Component-based Placeholders API
api-version: '1.21'
folia-supported: true
has-open-classloader: true
+1 -2
View File
@@ -2,6 +2,5 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"ignoreDeps": ["krypton", "krypton-api", "krypton-annotation-processor", "krypton-server"]
]
}
+13 -7
View File
@@ -11,28 +11,34 @@ pluginManagement {
mavenCentral()
maven("https://maven.fabricmc.net/")
maven("https://maven.quiltmc.org/repository/release/")
maven("https://repo.jpenilla.xyz/snapshots/")
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
maven("https://papermc.io/repo/repository/maven-public/")
maven("https://central.sonatype.com/repository/maven-snapshots/") {
name = "sonatype-snapshots"
mavenContent {
snapshotsOnly()
}
}
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.maven.apache.org/maven2/")
maven("https://repo.spongepowered.org/repository/")
maven("https://jitpack.io") {
mavenContent {
includeGroup("net.william278")
}
}
maven("https://oss.sonatype.org/content/repositories/snapshots")
maven("https://repo.jpenilla.xyz/snapshots/")
}
}
plugins {
id("fabric-loom") version "1.4.4"
id("org.spongepowered.gradle.plugin") version "2.2.0"
id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0"
id("quiet-fabric-loom") version "1.11.324"
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
arrayOf(
@@ -42,9 +48,9 @@ arrayOf(
"common",
"paper",
"velocity",
//"krypton",
"fabric",
"sponge"
"sponge",
"example-expansion-provider"
).forEach {
include("miniplaceholders-$it")
project(":miniplaceholders-$it").projectDir = file(it)
+5 -9
View File
@@ -4,17 +4,19 @@ import org.spongepowered.plugin.metadata.model.PluginDependency
plugins {
id("miniplaceholders.auto.module")
id("miniplaceholders.shadow")
id("org.spongepowered.gradle.plugin")
id("org.spongepowered.gradle.plugin") version "2.3.0"
}
dependencies {
implementation(projects.miniplaceholdersCommon)
implementation(projects.miniplaceholdersCommon) {
exclude(group = "org.incendo")
}
implementation(projects.miniplaceholdersApi)
implementation(projects.miniplaceholdersConnect)
implementation(libs.cloud.sponge)
}
sponge {
injectRepositories(false)
apiVersion("8.1.0")
license("GPL-3")
loader {
@@ -39,9 +41,3 @@ sponge {
}
}
}
tasks {
shadowJar {
exclude("io/leangen/geantyref/**")
}
}
@@ -1,39 +1,30 @@
package io.github.miniplaceholders.sponge;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.sponge.SpongeCommandManager;
import com.google.inject.Inject;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.common.PlaceholdersCommand;
import io.github.miniplaceholders.common.PlaceholdersPlugin;
import io.github.miniplaceholders.connect.InternalPlatform;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.Server;
import org.spongepowered.api.command.Command;
import org.spongepowered.api.command.CommandCause;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.lifecycle.RegisterCommandEvent;
import org.spongepowered.api.event.lifecycle.StartingEngineEvent;
import org.spongepowered.api.world.server.ServerWorld;
import org.spongepowered.plugin.PluginContainer;
import org.spongepowered.plugin.builtin.jvm.Plugin;
import java.util.ArrayList;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Plugin("miniplaceholders")
public class SpongePlugin implements PlaceholdersPlugin {
@Inject
private PluginContainer pluginContainer;
@Inject
private Logger logger;
@Inject
@ConfigDir(sharedRoot = false)
private Path configDir;
private Server server;
@@ -43,7 +34,11 @@ public class SpongePlugin implements PlaceholdersPlugin {
logger.info("Starting MiniPlaceholders Sponge");
InternalPlatform.platform(InternalPlatform.SPONGE);
this.loadDefaultExpansions();
try {
this.loadProvidedExpansions(configDir.resolve("expansions"));
} catch (Throwable e) {
logger.error("Unable to load expansion providers", e);
}
}
private final AtomicInteger registration = new AtomicInteger(0);
@@ -56,60 +51,23 @@ public class SpongePlugin implements PlaceholdersPlugin {
}
@Override
public void loadDefaultExpansions() {
Expansion.builder("server")
.globalPlaceholder("name", Tag.preProcessParsed("Sponge"))
.globalPlaceholder("online", (queue, ctx) -> Tag.preProcessParsed(Integer.toString(server.onlinePlayers().size())))
.globalPlaceholder("max_players", (queue, ctx) -> Tag.preProcessParsed(Integer.toString(server.maxPlayers())))
.globalPlaceholder("unique_joins", (queue, ctx) -> Tag.preProcessParsed(Long.toString(server.userManager().streamAll().count())))
.globalPlaceholder("has_whitelist", (queue, ctx) -> Tag.preProcessParsed(Boolean.toString(server.isWhitelistEnabled())))
.globalPlaceholder("total_chunks", (queue, ctx) -> {
int chunkCount = 0;
for (ServerWorld world : server.worldManager().worlds()){
chunkCount += world.entities().size();
}
return Tag.preProcessParsed(Integer.toString(chunkCount));
})
.globalPlaceholder("total_entities", (queue, ctx) -> {
int entityCount = 0;
for (ServerWorld world : server.worldManager().worlds()){
entityCount += world.entities().size();
}
return Tag.preProcessParsed(Integer.toString(entityCount));
})
.globalPlaceholder("mspt", (queue, ctx) -> Tag.preProcessParsed(Double.toString(server.averageTickTime())))
.build()
.register();
public boolean platformHasComplementLoaded(String complementName) {
return server.game().pluginManager().plugin(complementName).isPresent();
}
// TODO: Replace this with some implementation of a ComponentLogger
@Override
public void logError(Component component) {
logger.error(PlainTextComponentSerializer.plainText().serialize(component));
}
@Override
public void registerPlatformCommand() {
final SpongeCommandManager<AudienceHolder> commandManager = new SpongeCommandManager<>(
pluginContainer,
CommandExecutionCoordinator.simpleCoordinator(),
AudienceHolder::cause,
AudienceHolder::new
);
PlaceholdersCommand.<AudienceHolder>builder()
.manager(commandManager)
.command("miniplaceholders")
.playerSuggestions(() -> server.onlinePlayers()
.stream()
.map(ServerPlayer::name)
.collect(Collectors.toCollection(ArrayList::new)))
.toAudience(st -> server.player(st).orElse(null))
.hasPermissionCheck((holder, permission) -> holder.cause.hasPermission(permission))
.build()
.register();
public void logInfo(Component component) {
logger.info(PlainTextComponentSerializer.plainText().serialize(component));
}
record AudienceHolder(@NotNull CommandCause cause) implements ForwardingAudience.Single {
@Override
public @NotNull Audience audience() {
return cause.audience();
}
@Override
public Object platformServerInstance() {
return this.server;
}
}
@@ -1,8 +1,5 @@
package io.github.miniplaceholders.velocity;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.velocity.CloudInjectionModule;
import cloud.commandframework.velocity.VelocityCommandManager;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
@@ -11,93 +8,102 @@ import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.utils.Components;
import io.github.miniplaceholders.common.PlaceholdersCommand;
import io.github.miniplaceholders.common.PlaceholdersPlugin;
import io.github.miniplaceholders.common.PluginConstants;
import io.github.miniplaceholders.common.command.PlaceholdersCommand;
import io.github.miniplaceholders.connect.InternalPlatform;
import net.kyori.adventure.text.minimessage.tag.Tag;
import org.slf4j.Logger;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.incendo.cloud.SenderMapper;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.velocity.CloudInjectionModule;
import org.incendo.cloud.velocity.VelocityCommandManager;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.function.Function;
import java.util.stream.Collectors;
@Plugin(
name = "MiniPlaceholders",
id = "miniplaceholders",
version = PluginConstants.VERSION,
authors = {"4drian3d"}
authors = {"MiniPlaceholders Contributors", "4drian3d"},
description = "MiniMessage Component-based Placeholders API"
)
public final class VelocityPlugin implements PlaceholdersPlugin {
private final Logger logger;
private final ProxyServer proxy;
@Inject
private Injector injector;
@Inject
public VelocityPlugin(final Logger logger, final ProxyServer proxy) {
this.logger = logger;
this.proxy = proxy;
}
@DataDirectory
private Path dataDirectory;
@Inject
private ProxyServer proxyServer;
@Inject
private ComponentLogger componentLogger;
@Subscribe
public void onProxyInitialize(final ProxyInitializeEvent event) {
logger.info("Starting MiniPlaceholders Velocity");
@Subscribe(priority = 999)
public void onEarlyProxyInitialize(final ProxyInitializeEvent event) {
componentLogger.info(Component.text("Starting MiniPlaceholders Velocity", NamedTextColor.GREEN));
InternalPlatform.platform(InternalPlatform.VELOCITY);
this.loadDefaultExpansions();
this.registerPlatformCommand();
}
@Override
public void loadDefaultExpansions() {
Expansion.builder("proxy")
.globalPlaceholder("online_players", (queue, ctx) -> {
if (queue.hasNext()) {
String server = queue.pop().value();
return Tag.preProcessParsed(proxy.getServer(server)
.map(sv -> sv.getPlayersConnected().size())
.map(size -> Integer.toString(size))
.orElse("0"));
}
return Tag.preProcessParsed(Integer.toString(proxy.getPlayerCount()));
})
.globalPlaceholder("server_count", (queue, ctx) -> Tag.preProcessParsed(Integer.toString(proxy.getAllServers().size())))
.globalPlaceholder("is_player_online", (queue, ctx) -> {
String playerName = queue.popOr(() -> "you need to introduce an argument").value();
return Tag.selfClosingInserting(proxy.getPlayer(playerName).isPresent() ? Components.YES_COMPONENT : Components.NO_COMPONENT);
})
.globalPlaceholder("version", Tag.preProcessParsed(proxy.getVersion().getVersion()))
.build()
.register();
@Subscribe(priority = -404)
public void onLateProxyInitialize(final ProxyInitializeEvent event) {
try {
this.loadProvidedExpansions(dataDirectory.resolve("expansions"));
} catch (Throwable e) {
componentLogger.error("Unable to load expansion providers", e);
}
}
@Override
public void registerPlatformCommand() {
injector = injector.createChildInjector(
new CloudInjectionModule<>(
CommandSource.class,
CommandExecutionCoordinator.simpleCoordinator(),
Function.identity(),
Function.identity()
)
new CloudInjectionModule<>(
CommandSource.class,
ExecutionCoordinator.simpleCoordinator(),
SenderMapper.identity()
)
);
final VelocityCommandManager<CommandSource> commandManager = injector.getInstance(
Key.get(new TypeLiteral<>() {})
Key.get(new TypeLiteral<>() {
})
);
PlaceholdersCommand.<CommandSource>builder()
.playerSuggestions(() -> proxy.getAllPlayers()
.playerSuggestions(() -> proxyServer.getAllPlayers()
.stream()
.map(Player::getUsername)
.collect(Collectors.toCollection(ArrayList::new)))
.toAudience(st -> proxy.getPlayer(st).orElse(null))
.toAudience(st -> proxyServer.getPlayer(st).orElse(null))
.command("vminiplaceholders")
.manager(commandManager)
.build().register();
}
@Override
public boolean platformHasComplementLoaded(String complementName) {
return this.proxyServer.getPluginManager().isLoaded(complementName);
}
@Override
public void logError(Component component) {
componentLogger.error(component);
}
@Override
public void logInfo(Component component) {
componentLogger.info(component);
}
@Override
public Object platformServerInstance() {
return this.proxyServer;
}
}