Compare commits

..
118 changed files with 2065 additions and 4290 deletions

No files matched your search

+7 -7
View File
@@ -8,24 +8,24 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Set up JDK 25
uses: actions/setup-java@v6
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 25
distribution: 'zulu'
java-version: 17
distribution: 'temurin'
cache: 'gradle'
- name: Build with Gradle
uses: nick-invision/retry@v4
uses: nick-invision/retry@v3
with:
timeout_minutes: 10
max_attempts: 2
command: gradle build
- name: Upload Artifacts
uses: actions/upload-artifact@v7
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 21.0.6.fx-zulu"
RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh && sdk install java 17.0.1.fx-zulu"
-55
View File
@@ -1,55 +0,0 @@
<!--
MiniPlaceholders Changelog
-->
# 3.2.2 (unreleased)
### Features
### Fixes
### API Additions
<!-- -->
# 3.2.1 (07/09/2026)
### Fixes
- Fixed Fabric 26.2 support
## 3.2.0 (27/03/2026)
### Features
- Added support to Fabric 26.1+
- Added Minestom support
- Updated to Java 25
## 3.1.0 (10/10/2025)
### Features
- [feat: Sponge commands implementation](https://github.com/MiniPlaceholders/MiniPlaceholders/commit/7633ada70ed62571b707cbf709fbb5c027c44f46)
- [feat: Implement parserel subcommand](https://github.com/MiniPlaceholders/MiniPlaceholders/commit/ea725a81210911a8779545781b56a647d6b84cf3)
- [feat: Add --null argument to parse node](https://github.com/MiniPlaceholders/MiniPlaceholders/commit/3cdb7de9f2e675f494c84df693142a817535fe9a)
- [misc: Added bstats metrics](https://github.com/MiniPlaceholders/MiniPlaceholders/commit/f648dd8f836d5f2911eaf0bc53f6769ed2a0fdcc)
### Fixes
- [fix: Fixed multiple loading expansions](https://github.com/MiniPlaceholders/MiniPlaceholders/commit/f67690f41347031a32d4acd7bb4bb493b42bb4f9)
- [fix: Added nullability checks to RelationalAudience](https://github.com/MiniPlaceholders/MiniPlaceholders/commit/8aec3e592ff6774737602a3a3a6187ddb577a681)
- [fix: Fixed command formatting](https://github.com/MiniPlaceholders/MiniPlaceholders/commit/de7d14d55d6aeff8dbaf1706dd06bb6812b54390)
### API Additions
- [feat: Added LoadRequirement#requiredClass and LoadRequirement#anyOf](https://github.com/MiniPlaceholders/MiniPlaceholders/commit/31ee16e41234cabf992736f81eda5bbbdc09d954)
- [docs: Replaced some uses of JetBrains annotations with jspecify](https://github.com/MiniPlaceholders/MiniPlaceholders/commit/0f48a4baed91ec5d0fe285649822f189e69a5b57)
- [feat: Allow the use of injected constructors when creating an ExpansionProvider](https://github.com/MiniPlaceholders/MiniPlaceholders/commit/bf995b71030064a174532d38282407ab6baa7170)
+35 -19
View File
@@ -1,31 +1,45 @@
# MiniPlaceholders
[![GitHub WorkFlow](https://img.shields.io/github/actions/workflow/status/MiniPlaceholders/MiniPlaceholders/build.yml?logo=GitHub&style=flat-square)](https://github.com/MiniPlaceholders/MiniPlaceholders/actions)
[![Jenkins Build](https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fci.codemc.io%2Fjob%2FMiniPlaceholders%2Fjob%2FMiniPlaceholders%2F&style=flat-square&logo=jenkins)](https://ci.codemc.io/job/MiniPlaceholders/job/MiniPlaceholders/)
[![WorkFlow](https://img.shields.io/github/actions/workflow/status/MiniPlaceholders/MiniPlaceholders/build.yml?style=flat-square)](https://github.com/MiniPlaceholders/MiniPlaceholders/actions)
![Latest Version](https://img.shields.io/github/v/release/MiniPlaceholders/MiniPlaceholders?style=flat-square)
[![Discord](https://img.shields.io/discord/899740810956910683?color=7289da&logo=Discord&label=Discord&style=flat-square)](https://discord.gg/5NMMzK5mAn)
![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 API for Minecraft Platforms.
MiniMessage Component-based Placeholders for Paper, Fabric, Sponge, Folia and Velocity platforms
## Compatibility
- Paper 1.21+
- Velocity 3.5+
- Fabric 26.1+
- Sponge API 12+
- Minestom 1.21.11+
- Paper 1.19.3, 1.19.4+
- Folia 1.19.4+
- Velocity 3.1.2+
- Fabric 1.19.4+
- Sponge API 8+
## Commands
### Velocity
- `/vminiplaceholders parse me "[message with placeholders]"`
- `/vminiplaceholders parse player [some-player] "[message with placeholders]"`
### Paper | Folia | Fabric | Krypton
- `/miniplaceholders <parse|help> player [some-player] "[message with placeholders]`
- `/miniplaceholders parse player [some-player] "[message with placeholders]"`
#### Example:
- `/miniplaceholders parse me "<player_xp>"`
- `/vminiplaceholders parse player 4drian3d "<player_name>"`
## User Usage
Check our user usage wiki [here](https://miniplaceholders.github.io/docs/user-guide/User-Getting-Started)
Check our user usage wiki [here](https://github.com/MiniPlaceholders/MiniPlaceholders/wiki/User-Getting-Started)
## API
Check the available [Javadocs](https://javadoc.io/doc/io.github.miniplaceholders/miniplaceholders-api)
Or check the [Developer Wiki](https://miniplaceholders.github.io/docs/developer-guide/Developer-Getting-Started)
Or check the [Developer Wiki](https://github.com/MiniPlaceholders/MiniPlaceholders/wiki/Developer-Getting-Started)
### Java
```java
@@ -33,7 +47,9 @@ Or check the [Developer Wiki](https://miniplaceholders.github.io/docs/developer-
class Main {
public static void registerExpansion() {
final Expansion expansion = Expansion.builder("my-expansion")
.audiencePlaceholder(Player.class, "name", (player, ctx, queue) -> {
.filter(Player.class)
.audiencePlaceholder("name", (audience, ctx, queue) -> {
final Player player = (player) audience;
return Tag.selfClosingInserting(player.getName());
})
.globalPlaceholder("tps", (ctx, queue) ->
@@ -43,8 +59,8 @@ class Main {
expansion.register();
Player player;
final TagResolver playerResolver = MiniPlaceholders.audiencePlaceholders();
player.sendMessage(miniMessage().deserialize("Player Name: <my-expansion_name>", player, playerResolver));
final TagResolver playerResolver = MiniPlaceholders.getAudiencePlaceholders(player);
player.sendMessage(miniMessage().deserialize("Player Name: <my-expansion_name>", playerResolver));
}
}
@@ -54,10 +70,10 @@ class Main {
```kotlin
fun register() {
val expansion = expansion("my-expansion") {
audience<Player>("name") { player, _, _ ->
audiencePlaceholder("name") { aud, _, _ ->
aud.getName().asClosingTag()
}
global("tps") { _, _ ->
globalPlaceholder("tps") { _, _ ->
Component.text(Bukkit.getTps()[0]).asInsertingTag()
}
}
@@ -65,7 +81,7 @@ fun register() {
expansion.register()
val player: Player
val playerResolver = MiniPlaceholders.audiencePlaceholders()
player.sendMessage(miniMessage().deserialize("Player Name: <my-expansion_name>", player, playerResolver))
val playerResolver = MiniPlaceholders.getAudiencePlaceholders(player)
player.sendMessage(miniMessage().deserialize("Player Name: <my-expansion_name>", playerResolver))
}
```
+11 -59
View File
@@ -1,28 +1,22 @@
import java.time.LocalDate
plugins {
id("miniplaceholders.base")
alias(libs.plugins.jmh)
`java-library`
id("com.vanniktech.maven.publish")
id("miniplaceholders.publish")
}
dependencies {
compileOnly(libs.adventure.api)
compileOnly(libs.adventure.minimessage)
compileOnly(libs.adventure.minimesssage)
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.minimessage)
testImplementation(libs.adventure.minimesssage)
testImplementation(libs.adventure.serializer.plain)
testImplementation(libs.adventure.serializer.legacy)
compileOnly(libs.jspecify)
}
jmh {
@@ -31,21 +25,19 @@ jmh {
fork.set(2)
}
java {
withJavadocJar()
}
tasks {
processResources {
filteringCharset = Charsets.UTF_8.name()
}
javadoc {
val o = options as StandardJavadocDocletOptions
o.encoding = Charsets.UTF_8.name()
o.links(
"https://jd.papermc.io/adventure/${libs.versions.adventure.get()}/",
)
o.tags(
"apiNote:a:API Note:",
"implSpec:a:Implementation Requirements:",
"implNote:a:Implementation Note:",
"sinceMinecraft:a:Since Minecraft:"
options.encoding = Charsets.UTF_8.name()
(options as StandardJavadocDocletOptions).links(
"https://jd.advntr.dev/api/${libs.versions.adventure.get()}/",
"https://jd.advntr.dev/text-minimessage/${libs.versions.adventure.get()}/"
)
}
compileTestJava {
@@ -58,43 +50,3 @@ tasks {
}
}
}
mavenPublishing {
publishToMavenCentral()
signAllPublications()
coordinates(project.group as String, "miniplaceholders-api", project.version as String)
pom {
name.set(project.name)
description.set(project.description)
inceptionYear.set(LocalDate.now().year.toString())
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders")
licenses {
license {
name.set("The Apache License, Version 2.0")
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
distribution.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
}
}
developers {
developer {
id.set("4drian3d")
name.set("Adrian Gonzales")
email.set("adriangonzalesval@gmail.com")
}
}
scm {
connection.set("scm:git:https://github.com/MiniPlaceholders/MiniPlaceholders.git")
developerConnection.set("scm:git:ssh://git@github.com/MiniPlaceholders/MiniPlaceholders.git")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders")
}
ciManagement {
name.set("GitHub Actions")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders/actions")
}
issueManagement {
name.set("GitHub")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders/issues")
}
}
}
@@ -12,46 +12,39 @@ 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(
BenchAudience.class,
"name",
(aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(aud.getName()))
"name", (aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(((BenchAudience)aud).getName()))
)
.audiencePlaceholder(
BenchAudience.class,
"uuid",
(aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(aud.getUUID().toString()))
"uuid", (aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(((BenchAudience)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.register();
final TagResolver resolvers = MiniPlaceholders.audiencePlaceholders();
Expansion expansionBuilded = expansion.build();
expansionBuilded.audiencePlaceholders(player);
final TagResolver resolvers = MiniPlaceholders.getAudiencePlaceholders(player);
MiniMessage.miniMessage().deserialize("Player Name: <benchmark_name>", player, resolvers);
}
MiniMessage.miniMessage().deserialize("Player Name: <benchmark-name>", 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.globalPlaceholders();
TagResolver resolvers = MiniPlaceholders.getGlobalPlaceholders();
MiniMessage.miniMessage().deserialize("Server name: <single_servername>", resolvers);
}
MiniMessage.miniMessage().deserialize("Server name: <single-servername>", resolvers);
}
}
@@ -1,22 +1,20 @@
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 org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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 org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.Collection;
/**
* Expansion that contains placeholders
@@ -25,14 +23,14 @@ import java.util.Collection;
* <pre>
* Player player = event.getPlayer();
* Expansion.Builder builder = Expansion.builder("player")
* .resolver(Player.class, "name", (p, queue, ctx) -> Tag.selfClosingInserting(Component.text(p.getUsername()))
* .audiencePlaceholder("name", (p, queue, ctx) -> Tag.selfClosingInserting(Component.text(((Player)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#audiencePlaceholders()} e.g.
* TagResolver resolver = expansion.resolver();
* player.sendMessage(MiniMessage.miniMessage().deserialize("Hello {@literal <luckperms_prefix> <player_name>}", player, resolver));
* // 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));
* </pre>
*
* @since 1.0.0
@@ -40,15 +38,13 @@ import java.util.Collection;
public sealed interface Expansion permits ExpansionImpl {
/**
* Get the expansion name
*
* @return the expansion name
* @since 1.0.0
*/
String name();
@NotNull String name();
/**
* Get the author of this expansion
*
* @return the author or null
* @since 2.3.0
*/
@@ -56,18 +52,18 @@ public sealed interface Expansion permits ExpansionImpl {
/**
* 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}
* Get the {@link TagResolver} of the desired {@link Audience}
* @param audience the audience
* @return A TagResolver with variable placeholders of an Audience
* @since 1.0.0
*/
TagResolver audiencePlaceholders();
@NotNull TagResolver audiencePlaceholders(final @NotNull Audience audience);
/**
* Get the relational placeholders based on two audiences
@@ -75,17 +71,19 @@ 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
*/
TagResolver relationalPlaceholders();
@NotNull TagResolver relationalPlaceholders(final @NotNull Audience audience, final @NotNull Audience otherAudience);
/**
* Get global placeholders
* @return the global placeholders
* @since 1.0.0
*/
TagResolver globalPlaceholders();
@NotNull TagResolver globalPlaceholders();
/**
* Register this expansion
@@ -110,129 +108,16 @@ public sealed interface Expansion permits ExpansionImpl {
*/
boolean registered();
/**
* 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 Expansion.Builder builder(final String name){
static @NotNull Expansion.Builder builder(final @NotNull String name){
return new ExpansionImpl.Builder(name);
}
/**
* Get a shorter ToString with the details of this expansion.
* @return a shorter ToString
* @deprecated Starting with MiniPlaceholders 3.1, the way loaded expansions
* are displayed in the console has changed, so this method is no longer used
*/
@ApiStatus.Internal
@Deprecated(forRemoval = true, since = "3.1.0")
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>
@@ -241,7 +126,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)
* .resolver("name", (audience, queue, ctx) -> Tag.selfClosingInserting(Component.text(((Player)audience).getUsername())));
* .audiencePlaceholder("name", (audience, queue, ctx) -> Tag.selfClosingInserting(Component.text(((Player)audience).getUsername())));
* Expansion expansion = builder.build();
* </pre>
*
@@ -249,15 +134,13 @@ public sealed interface Expansion permits ExpansionImpl {
*
* <pre>
* Player player = event.getPlayer();
* TagResolver resolver = expansion.resolver(player);
* TagResolver resolver = expansion.audiencePlaceholder(player);
* Component messageReplaced = MiniMessage.deserialize({@link String}, resolver);
* </pre>
*
* @since 1.0.0
*/
@NullMarked
interface Builder extends AbstractBuilder<Expansion> {
/**
* Adds an audience placeholder
*
@@ -266,35 +149,12 @@ 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 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
*
* <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 key the placeholder key, cannot be an empty or black string
* @param audiencePlaceholder the single placeholder
* @return the {@link Builder} itself
* @since 3.0.0
*/
default Builder audiencePlaceholder(final String key, final AudienceTagResolver<Audience> audiencePlaceholder) {
return this.audiencePlaceholder(null, key, audiencePlaceholder);
}
@NotNull Builder audiencePlaceholder(final @NotNull String key, final @NotNull AudiencePlaceholder audiencePlaceholder);
/**
* Adds a Relational Placeholder based on two audiences
@@ -308,42 +168,12 @@ 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 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
* @since 1.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
*
* <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 key the placeholder key, cannot be an empty or black string
* @param relationalResolver the relational placeholder
* @return the {@link Builder} itself
* @since 3.0.0
*/
default Builder relationalPlaceholder(
final String key,
final RelationalTagResolver<Audience> relationalResolver
) {
return this.relationalPlaceholder(null, key, relationalResolver);
}
@NotNull Builder relationalPlaceholder(final @NotNull String key, final @NotNull RelationalPlaceholder relationalPlaceholder);
/**
* Adds a global placeholder
@@ -351,11 +181,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 resolver the function
* @param function the function
* @return the {@link Builder} itself
* @since 3.0.0
* @since 1.0.0
*/
Builder globalPlaceholder(final String key, final GlobalTagResolver resolver);
@NotNull Builder globalPlaceholder(final @NotNull String key, final @NotNull BiFunction<@NotNull ArgumentQueue, @NotNull Context, @Nullable Tag> function);
/**
* Adds a global placeholder
@@ -366,9 +196,41 @@ public sealed interface Expansion permits ExpansionImpl {
* @return the {@link Builder} itself
* @since 1.1.0
*/
default Builder globalPlaceholder(final String key, final Tag tag) {
return this.globalPlaceholder(key, (queue, context) -> tag);
}
@NotNull Builder globalPlaceholder(final @NotNull String key, final @NotNull Tag 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>
*
* <p>This eliminates the need to perform a manual
* <pre>if(!(audience instanceof Player)) return TagResolver.empty();</pre>
*
* @param clazz the class to filter
* @return the {@link Builder} itself
* @since 1.0.0
*/
@Contract("_ -> this")
@NotNull Builder filter(final @Nullable Class<? extends Audience> clazz);
/**
* 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>
*
* @param predicate the check to realize
* @return the {@link Builder} itself
* @since 1.0.0
*/
@Contract("_ -> this")
@NotNull Builder filter(final @Nullable Predicate<@NotNull Audience> predicate);
/**
* Sets the author of this expansion
@@ -378,7 +240,7 @@ public sealed interface Expansion permits ExpansionImpl {
* @since 2.3.0
*/
@Contract("_ -> this")
Builder author(final @Nullable String author);
@NotNull Builder author(final @Nullable String author);
/**
* Sets the version of this expansion
@@ -388,6 +250,6 @@ public sealed interface Expansion permits ExpansionImpl {
* @since 2.3.0
*/
@Contract("_ -> this")
Builder version(final @Nullable String version);
@NotNull Builder version(final @Nullable String version);
}
}
@@ -1,275 +1,259 @@
package io.github.miniplaceholders.api;
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 io.github.miniplaceholders.api.placeholder.AudiencePlaceholder;
import io.github.miniplaceholders.api.placeholder.RelationalPlaceholder;
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.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.jspecify.annotations.NullUnmarked;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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;
final class ExpansionImpl implements Expansion {
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];
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 final String author;
private final String version;
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 String name() {
return this.name;
}
@Override
public @Nullable String author() {
return this.author;
}
@Override
public @Nullable String version() {
return this.version;
}
@Override
public TagResolver audiencePlaceholders() {
if (audiencePlaceholders.placeholders().length == 0) return TagResolver.empty();
return this.audiencePlaceholders;
}
@Override
public TagResolver relationalPlaceholders() {
if (relationalPlaceholders.placeholders().length == 0) return TagResolver.empty();
return this.relationalPlaceholders;
}
@Override
public 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(final @NonNull String name) {
this.expansionName = nonNullOrEmptyString(name, "Expansion name");
this.expansionPrefix = name.toLowerCase(Locale.ROOT).concat("_");
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,
@Nullable final String author,
@Nullable final String version
) {
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;
this.author = author;
this.version = version;
}
@Override
public <A extends Audience> @NonNull Builder audiencePlaceholder(
final @Nullable Class<A> targetClass,
final @Subst("name") @NonNull String key,
final @NonNull AudienceTagResolver<@NonNull 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;
public @NotNull String name(){
return this.name;
}
@Override
public <A extends Audience> @NonNull Builder relationalPlaceholder(
final Class<A> targetClass,
final @Subst("relation") @NonNull String key,
final @NonNull RelationalTagResolver<@NonNull A> relationalPlaceholder
) {
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(relationalPlaceholder, "the relational placeholder cannot be null");
if (this.relationalPlaceholders == null) this.relationalPlaceholders = new HashSet<>();
final var relationalTag = RelationalPlaceholder.relational(targetClass, expansionPrefix + "rel_" + key, key, relationalPlaceholder);
this.relationalPlaceholders.add(relationalTag);
return this;
public @Nullable String author(){
return this.author;
}
@Override
public @NonNull Builder globalPlaceholder(
final @Subst("time") @NonNull String key,
final @NonNull GlobalTagResolver function
) {
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(function, "the global placeholder cannot be null");
if (this.globalPlaceholders == null) this.globalPlaceholders = new HashSet<>();
final var globalResolver = new GlobalPlaceholder(expansionPrefix + key, key, function);
this.globalPlaceholders.add(globalResolver);
return this;
public @Nullable String version(){
return this.version;
}
@Override
public @NonNull Builder author(final @Nullable String author) {
this.author = author;
return this;
public @NotNull TagResolver audiencePlaceholders(@NotNull Audience audience) {
if (audiencePlaceholders.length == 0) return TagResolver.empty();
requireNonNull(audience, "the audience cannot be null");
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;
}
@Override
public @NonNull Builder version(final @Nullable String version) {
this.version = version;
return this;
public @NotNull TagResolver relationalPlaceholders(@NotNull Audience audience, @NotNull Audience otherAudience){
if (relationalPlaceholders.length == 0) return TagResolver.empty();
requireNonNull(audience, "the audience cannot be null");
requireNonNull(otherAudience, "the other audience cannot be null");
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();
}
@Override
public @NonNull Expansion build() {
return new ExpansionImpl(
this.expansionName,
this.audiencePlaceholders,
this.relationalPlaceholders,
this.globalPlaceholders,
this.author,
this.version
);
public @NotNull TagResolver globalPlaceholders(){
return this.globalPlaceholders;
}
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof Expansion that)) return false;
@Override
public void register() {
if (this.registered()) {
throw new IllegalStateException("Expansion" + this.name + "is already registered");
}
MiniPlaceholders.expansions.add(this);
}
return that.name().equalsIgnoreCase(this.name());
}
@Override
public void unregister() {
if (!this.registered()) {
throw new IllegalStateException("Expansion " + this.name + " is not registered");
}
MiniPlaceholders.expansions.remove(this);
}
@Override
public int hashCode() {
return Objects.hash(name.hashCode());
}
@Override
public boolean registered() {
return MiniPlaceholders.expansions.contains(this);
}
@Override
public String toString() {
return "ExpansionImpl{" +
"name='" + name + '\'' +
", audiencePlaceholders=" + audiencePlaceholders +
", relationalPlaceholders=" + relationalPlaceholders +
", globalPlaceholders=" + globalPlaceholders +
", author='" + author + '\'' +
", version='" + version + '\'' +
'}';
}
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;
private String author;
private String version;
Builder(@NotNull final String name){
this.expansionName = nonNullOrEmptyString(name, "Expansion name")
.toLowerCase(Locale.ROOT).concat("_");
}
@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 Expansion.@NotNull Builder author(@Nullable final String author) {
this.author = author;
return this;
}
@Override
public Expansion.@NotNull Builder version(@Nullable final String version) {
this.version = version;
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,
this.version,
this.author
);
}
}
@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());
}
}
@@ -1,24 +1,21 @@
package io.github.miniplaceholders.api;
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.api.enums.Platform;
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.Unmodifiable;
import org.jspecify.annotations.Nullable;
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, a component-based placeholders API.
* MiniPlaceholders
*
* <p>This class allows you to obtain the {@link TagResolver}
* that other plugins provide based on single {@link Audience},
@@ -34,56 +31,56 @@ public final class MiniPlaceholders {
static final Set<Expansion> expansions = ConcurrentHashMap.newKeySet();
/**
* Get the platform on which MiniPlaceholders is running.
*
* Get the platform
* @return the platform
* @since 3.0.0
* @since 1.0.0
*/
public static Platform platform() {
public static @NotNull Platform getPlatform() {
return switch (InternalPlatform.platform()) {
case PAPER -> Platform.PAPER;
case VELOCITY -> Platform.VELOCITY;
case KRYPTON -> Platform.KRYPTON;
case FABRIC -> Platform.FABRIC;
case SPONGE -> Platform.SPONGE;
case MINESTOM -> Platform.MINESTOM;
};
}
/**
* Get the global placeholders
*
* <pre>TagResolver resolver = MiniPlaceholders.globalPlaceholders();
* <pre>TagResolver resolver = MiniPlaceholders.getGlobalPlaceholders();
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, resolver);</pre>
*
* @return global placeholders independent of any audience
* @see TagResolver
* @since 3.0.0
* @since 1.0.0
*/
public static TagResolver globalPlaceholders() {
public static @NotNull TagResolver getGlobalPlaceholders() {
final TagResolver.Builder builder = TagResolver.builder();
for (final Expansion expansion : expansions) {
final TagResolver resolver = expansion.globalPlaceholders();
applyIfNotEmpty(resolver, builder);
if (resolver != TagResolver.empty())
builder.resolver(resolver);
}
return builder.build();
}
/**
* 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.
* Get the TagResolver based on an Audience
*
* <pre>TagResolver resolver = MiniPlaceholders.audiencePlaceholders();
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, {@link Audience}, resolver);
* </pre>
* <pre>TagResolver resolver = MiniPlaceholders.getAudiencePlaceholders({@link Audience});
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, resolver);</pre>
*
* @param audience the audience
* @return {@link TagResolver} with placeholders based on an audience
* @since 3.0.0
* @since 1.0.0
*/
public static TagResolver audiencePlaceholders() {
public static @NotNull TagResolver getAudiencePlaceholders(final @NotNull Audience audience) {
requireNonNull(audience, "audience cannot be null");
final TagResolver.Builder resolvers = TagResolver.builder();
for (final Expansion expansion : expansions) {
final TagResolver resolver = expansion.audiencePlaceholders();
for (Expansion expansion : expansions) {
final TagResolver resolver = expansion.audiencePlaceholders(audience);
applyIfNotEmpty(resolver, resolvers);
}
return resolvers.build();
@@ -91,53 +88,54 @@ 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>{@code
* TagResolver resolver = MiniPlaceholders.relationalPlaceholders();
* Component parsed = MiniMessage.miniMessage().deserialize(@link String, {@link RelationalAudience}, resolver);
* }</pre>
* <pre>TagResolver resolver = MiniPlaceholders.getRelationalPlaceholders({@link Audience}, {@link Audience});
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, resolver);</pre>
*
* @param audience an audience
* @param otherAudience another audience
* @return placeholders based on two audiences
* @since 3.0.0
* @see RelationalAudience
* @since 1.0.0
*/
public static TagResolver relationalPlaceholders() {
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");
final TagResolver.Builder builder = TagResolver.builder();
for (final Expansion expansion : expansions) {
applyIfNotEmpty(expansion.relationalPlaceholders(), builder);
applyIfNotEmpty(
expansion.relationalPlaceholders(audience, otherAudience),
builder
);
}
return builder.build();
}
/**
* 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.
* Get the TagResolver based on an Audience and the global placeholders
*
* <pre>
* TagResolver resolver = MiniPlaceholders.audienceGlobalPlaceholders();
* TagResolver resolver = MiniPlaceholders.getAudienceGlobalPlaceholders({@link Audience});
* TagResolver resolver2 = TagResolver.resolver(
* MiniPlaceholders.audienceGlobalPlaceholders(),
* MiniPlaceholders.globalPlaceholders()
* MiniPlaceholders.getAudienceGlobalPlaceholders({@link Audience}),
* MiniPlaceholders.getGlobalPlaceholders()
* );
* // This two resolvers returns the same TagResolver
* assertEquals(resolver, resolver2);
* Component parsed = MiniMessage.miniMessage().deserialize({@link String}, {@link Audience}, resolver);
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, 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 TagResolver audienceGlobalPlaceholders() {
public static @NotNull TagResolver getAudienceGlobalPlaceholders(final @NotNull Audience audience) {
requireNonNull(audience, "audience cannot be null");
final TagResolver.Builder builder = TagResolver.builder();
for (final Expansion expansion : expansions) {
applyIfNotEmpty(expansion.audiencePlaceholders(), builder);
applyIfNotEmpty(expansion.audiencePlaceholders(audience), builder);
applyIfNotEmpty(expansion.globalPlaceholders(), builder);
}
@@ -149,27 +147,30 @@ public final class MiniPlaceholders {
* and the global placeholders
*
* <pre>
* TagResolver resolver = MiniPlaceholders.relationalGlobalPlaceholders();
* TagResolver resolver = MiniPlaceholders.getRelationalGlobalPlaceholders({@link Audience}, {@link Audience});
* TagResolver resolver2 = TagResolver.resolver(
* MiniPlaceholders.relationalPlaceholders(),
* MiniPlaceholders.audiencePlaceholders(),
* MiniPlaceholders.globalPlaceholders()
* MiniPlaceholders.getRelationalPlaceholders(audience1, audience2),
* MiniPlaceholders.getAudiencePlaceholders(audience1),
* MiniPlaceholders.getGlobalPlaceholders()
* );
* // This methods should return the same TagResolver
* assertEquals(resolver, resolver2);
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, {@link RelationalAudience}, resolver);
* Component messageParsed = MiniMessage.miniMessage().deserialize({@link String}, 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 3.0.0
* @apiNote In the case of audience placeholders, the audience to be used will be the {@link RelationalAudience#audience()}
* @see RelationalAudience
* @since 1.1.0
*/
public static TagResolver relationalGlobalPlaceholders() {
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");
final TagResolver.Builder builder = TagResolver.builder();
for (final Expansion expansion : expansions) {
applyIfNotEmpty(expansion.audiencePlaceholders(), builder);
applyIfNotEmpty(expansion.relationalPlaceholders(), builder);
applyIfNotEmpty(expansion.audiencePlaceholders(audience), builder);
applyIfNotEmpty(expansion.relationalPlaceholders(audience, otherAudience), builder);
applyIfNotEmpty(expansion.globalPlaceholders(), builder);
}
@@ -177,33 +178,11 @@ public final class MiniPlaceholders {
}
/**
* 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 TagResolver placeholdersByType(final PlaceholderType type) {
return switch (type) {
case GLOBAL -> globalPlaceholders();
case AUDIENCE -> audiencePlaceholders();
case RELATIONAL -> relationalPlaceholders();
};
}
/**
* Get the amount of expansion registered.
*
* Get the amount of expansion registered
* @return the amount of expansions registered
* @since 3.0.0
* @since 1.0.0
*/
public static int expansionCount(){
public static int getExpansionCount(){
return expansions.size();
}
@@ -214,15 +193,14 @@ public final class MiniPlaceholders {
* Expansion expansion = Expansion.builder("example").build();
* expansion.register();
*
* assertThat(MiniPlaceholders.expansionByName("example")).isNotNull();
* assertThat(MiniPlaceholders.getExpansionByName("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 3.0.0
* @since 2.1.0
*/
public static @Nullable Expansion expansionByName(final String name) {
public static @Nullable Expansion getExpansionByName(final @NotNull String name) {
for (final Expansion expansion : expansions) {
if (Objects.equals(expansion.name(), name)) {
return expansion;
@@ -230,15 +208,4 @@ public final class MiniPlaceholders {
}
return null;
}
/**
* Obtain all available registered expansions
*
* @return all available registered expansions
* @since 3.0.0
*/
@Unmodifiable
public static Collection<Expansion> expansionsAvailable() {
return Collections.unmodifiableSet(expansions);
}
}
@@ -0,0 +1,107 @@
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);
}
}
}
@@ -0,0 +1,15 @@
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
}
@@ -0,0 +1,2 @@
/**Plugin enums */
package io.github.miniplaceholders.api.enums;
@@ -1,109 +1,27 @@
package io.github.miniplaceholders.api.placeholder;
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.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
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 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
*/
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;
}
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 + '\'' +
'}';
}
}
/**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
);
}
@@ -1,49 +0,0 @@
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.jspecify.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(final String name, final ArgumentQueue arguments, final Context ctx) {
return this.has(name) ? resolver.tag(arguments, ctx) : null;
}
@Override
public boolean has(final String name) {
return key.equalsIgnoreCase(name);
}
@Override
public boolean equals(final 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 + '\'' +
'}';
}
}
@@ -1,26 +0,0 @@
package io.github.miniplaceholders.api.placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
/**
* Base class of all Placeholders
*/
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();
}
@@ -1,38 +0,0 @@
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.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(final String name, final ArgumentQueue arguments, final 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 String name) {
for (final Placeholder placeholder : placeholders) {
if (placeholder.has(name)) {
return true;
}
}
return false;
}
}
@@ -1,133 +1,29 @@
package io.github.miniplaceholders.api.placeholder;
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 java.util.Objects;
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 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}
* @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}.
* @see RelationalTagResolver
*/
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
* @param <A> the audience type, if not specified, will be a generic {@linkplain Audience}
* @return a new RelationalPlaceholder
* @since 3.0.0
*/
public static <A extends Audience> RelationalPlaceholder<A> relational(
final @Nullable Class<A> targetClass,
final @TagPattern String key,
final String name,
final RelationalTagResolver<A> resolver
) {
return new RelationalPlaceholder<>(targetClass, requireNonNull(key), 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 (!(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(
final String name,
final A audience,
final A relationalAudience,
final ArgumentQueue arguments,
final Context ctx
) {
return this.has(name)
? resolver.tag(audience, relationalAudience, arguments, ctx)
: null;
}
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(final 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 + '\'' +
'}';
}
}
/**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
);
}
@@ -1,6 +1,4 @@
/**
* Placeholders Package.
*
* @since 3.0.0
* Placeholders available based on information obtained from Audiences
*/
package io.github.miniplaceholders.api.placeholder;
package io.github.miniplaceholders.api.placeholder;
@@ -1,27 +0,0 @@
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();
}
@@ -1,223 +0,0 @@
package io.github.miniplaceholders.api.provider;
import io.github.miniplaceholders.api.types.Platform;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import java.util.Arrays;
import java.util.function.Predicate;
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}.
*
* @since 3.0.0
*/
public sealed interface LoadRequirement {
/**
* It means that there is no requirement to load an expansion.
*
* @return a LoadRequirement that does not perform any validation
* @since 3.0.0
*/
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
* @since 3.0.0
*/
static LoadRequirement requiredComplement(final String name, final String... aliases) {
requireNonNull(name);
requireNonNull(aliases);
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
* @since 3.0.0
*/
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
* @since 3.0.0
*/
static LoadRequirement platform(final Platform... platforms) {
requireNonNull(platforms);
assert platforms.length != 0;
return new PlatformRequirement(platforms);
}
/**
* Requirement that a specific class be present.
*
* @param clazz the specified class.
* @return a load requirement that checks for a present class
* @since 3.1.0
*/
static LoadRequirement requiredClass(
@Pattern("^([a-z_][a-z0-9_]*)(\\.[a-z_][a-z0-9_]*)*(\\.[A-Z][A-Za-z0-9_]*)$") final String clazz
) {
requireNonNull(clazz);
return new ClassRequirement(clazz);
}
/**
* This requirement checks whether any of the conditions presented are satisfied.
*
* @param requirements the requerimients
* @return a requirement that checks whether a condition is valid
* @since 3.1.0
*/
static LoadRequirement anyOf(LoadRequirement... requirements) {
requireNonNull(requirements);
assert requirements.length != 0;
return new AnyOfRequirement(requirements);
}
/**
* NoneLoadRequirement
*
* @since 3.0.0
*/
@ApiStatus.Internal
enum NoneLoadRequirement implements LoadRequirement {
/**
* NoneLoadRequirement instance
*/
INSTANCE
}
/**
* AvailableComplementRequirement
*
* @param name the main complement name
* @param platformAliases the complement aliases
* @since 3.0.0
*/
@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
* @since 3.0.0
*/
public boolean shouldLoad(final 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
* @since 3.0.0
*/
@ApiStatus.Internal
@NullMarked
record MultiLoadRequirement(LoadRequirement... requirements) implements LoadRequirement {
@Override
public String toString() {
return "MultiLoadRequirement{" +
"requirements=" + Arrays.toString(requirements) +
'}';
}
}
/**
* PlatformLoadRequirement
*
* @param platforms the platforms supported
* @since 3.0.0
*/
@ApiStatus.Internal
@NullMarked
record PlatformRequirement(Platform... platforms) implements LoadRequirement {
@Override
public String toString() {
return "PlatformRequirement{" +
"platforms=" + Arrays.toString(platforms()) +
'}';
}
}
/**
* ClassRequirement
*
* @param clazz a specified class
* @since 3.1.0
*/
@NullMarked
@ApiStatus.Internal
record ClassRequirement(String clazz) implements LoadRequirement {
public boolean shouldLoad() {
try {
Class.forName(clazz);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
/**
* AnyOfLoadRequirement
*
* @param requirements the requirements
* @since 3.1.0
*/
@ApiStatus.Internal
@NullMarked
record AnyOfRequirement(LoadRequirement... requirements) implements LoadRequirement {
@Override
public String toString() {
return "AnyOfRequirement{" +
"requirements=" + Arrays.toString(requirements) +
'}';
}
}
}
@@ -1,21 +0,0 @@
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) {
}
@@ -1,6 +0,0 @@
/**
* Package responsible for providing tools to load expansions provided through installable files
*
* @since 3.0.0
*/
package io.github.miniplaceholders.api.provider;
@@ -1,26 +0,0 @@
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.jspecify.annotations.Nullable;
/**
* Audience Placeholder based on an Audience
*
* @param <A> the audience type
*/
@FunctionalInterface
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);
}
@@ -1,23 +0,0 @@
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.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(ArgumentQueue queue, Context context);
}
@@ -1,30 +0,0 @@
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.jspecify.annotations.Nullable;
/**
* Relational Placeholder based on two Audiences.
*
* @param <A> the audiences type
*
* @since 3.0.0
*/
@FunctionalInterface
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);
}
@@ -1,4 +0,0 @@
/**
* Resolvers available based on information obtained from Audiences
*/
package io.github.miniplaceholders.api.resolver;
@@ -1,27 +0,0 @@
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
}
@@ -1,39 +0,0 @@
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,
/**
* Minestom Platform
*
* @since 3.2.0
*/
MINESTOM,
}
@@ -1,34 +0,0 @@
package io.github.miniplaceholders.api.types;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import static java.util.Objects.requireNonNull;
/**
* 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
*/
public record RelationalAudience<A extends Audience>(A audience, A relational) implements ForwardingAudience.Single {
public RelationalAudience {
requireNonNull(audience);
requireNonNull(relational);
}
/**
* 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);
}
}
@@ -1,4 +0,0 @@
/**
* Plugin types
*/
package io.github.miniplaceholders.api.types;
@@ -4,33 +4,27 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
/**
* Common components used in expansions
*/
/**Common components used in expansions */
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);
@@ -1,78 +1,74 @@
package io.github.miniplaceholders.api.utils;
import org.jspecify.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Supplier;
/**
* Checks required for the correct operation of the expansions
*
* @since 1.0.0
*/
/** Checks required for the correct operation of the expansions*/
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
* @throws IllegalStateException if the provided string is empty
* @since 1.0.0
*/
public static String nonNullOrEmptyString(final @Nullable String string, final Supplier<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
* @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
* @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(final @Nullable String string, final 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
* @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;
}
/**
* 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(final String string, final Supplier<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(final String string, final 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 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
* @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;
}
}
@@ -1,165 +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.jspecify.annotations.Nullable;
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
*/
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>
*
* @deprecated Legacy format “support” will be removed in future versions
*/
@Deprecated(since = "3.1.0")
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);
}
}
}
@@ -0,0 +1,167 @@
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 java.util.function.Function;
import static java.util.Objects.requireNonNull;
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,
final @NotNull 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,
final @NotNull 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
* @return the mapped result
* @param <R> the result type
* @see LegacyComponentSerializer#AMPERSAND_CHAR
* @see LegacyComponentSerializer#SECTION_CHAR
* @see #hasLegacyFormat(String)
* @since 2.3.0
*/
public static <R> R mapLegacyOrElse(
final @NotNull String string,
final @NotNull Function<String, R> legacyMapping,
final @NotNull Function<String, R> modernMapping
) {
requireNonNull(legacyMapping, "legacyMapping");
requireNonNull(modernMapping, "modernInput");
if (hasLegacyFormat(string)) {
return legacyMapping.apply(string);
} else {
return modernMapping.apply(string);
}
}
}
@@ -1,10 +1,11 @@
package io.github.miniplaceholders.api.utils;
import org.jetbrains.annotations.NotNull;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
/**
* Class of utilities to manage TagResolvers.
*
* Class of utilities to manage TagResolvers
* @since 1.2.0
*/
public final class Resolvers {
@@ -12,36 +13,33 @@ public final class 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 TagResolver resolver) {
public static boolean isEmpty(final @NotNull 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 TagResolver resolver) {
public static boolean isNotEmpty(final @NotNull 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 TagResolver resolver, final TagResolver.Builder builder) {
public static void applyIfNotEmpty(final @NotNull TagResolver resolver, final @NotNull TagResolver.Builder builder) {
if (isNotEmpty(resolver)) {
builder.resolver(resolver);
}
@@ -1,66 +0,0 @@
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.Nullable;
/**
* 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 @Nullable 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<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<A> emptyRelationalResolver() {
return (audience, relational, 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 (queue, ctx) -> NULL_TAG;
}
}
@@ -0,0 +1,60 @@
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
*/
@Deprecated
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
*/
@Deprecated
public static @NotNull Tag staticTag(@NotNull Component component) {
return Tag.selfClosingInserting(component);
}
}
+11 -19
View File
@@ -1,24 +1,16 @@
import org.jspecify.annotations.NullMarked;
/**
* MiniPlaceholders API Module
*/
@NullMarked
/** MiniPlaceholders API Module */
open module io.github.miniplaceholders.api {
requires io.github.miniplaceholders.connect;
requires io.github.miniplaceholders.connect;
requires net.kyori.adventure.api;
requires net.kyori.adventure.text.minimessage;
requires net.kyori.adventure.text.serializer.legacy;
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.jspecify;
requires static team.unnamed.inject;
requires static org.jetbrains.annotations;
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;
exports io.github.miniplaceholders.api;
exports io.github.miniplaceholders.api.enums;
exports io.github.miniplaceholders.api.utils;
exports io.github.miniplaceholders.api.placeholder;
}
@@ -1,65 +1,45 @@
package io.github.miniplaceholders.test;
import io.github.miniplaceholders.api.Expansion;
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 io.github.miniplaceholders.api.utils.TagsUtils;
import io.github.miniplaceholders.test.testobjects.TestAudienceHolder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
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;
class ExpansionsTest implements MiniTest {
class ExpansionsTest {
@Test
@DisplayName("Expansion Equality")
void equalsExpansions(){
final Expansion firstExpansion = Expansion.builder("equals")
.audiencePlaceholder("test", Tags.emptyAudienceResolver())
.audiencePlaceholder("test", TagsUtils.NULL_AUDIENCE_PLACEHOLDER)
.build();
final Expansion secondExpansion = Expansion.builder("equals")
.audiencePlaceholder("test", Tags.emptyAudienceResolver())
.audiencePlaceholder("test", TagsUtils.NULL_AUDIENCE_PLACEHOLDER)
.build();
assertEquals(firstExpansion, secondExpansion);
}
@Test
void placeholderRetrievalTest() {
void nullForwardedAudience() {
TestAudienceHolder audience = new TestAudienceHolder(null);
final Expansion expansion = Expansion.builder("test")
.globalPlaceholder("some_placeholder", Tag.preProcessParsed("test"))
.filter(aud -> aud.toString().isBlank())
.audiencePlaceholder("testing", (aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(aud.toString())))
.build();
assertTrue(expansion.hasGlobalPlaceholder("some_placeholder"));
final GlobalPlaceholder somePlaceholder = expansion.globalPlaceholderByName("some_placeholder");
assertNotNull(somePlaceholder);
Component result = assertDoesNotThrow(
() -> miniMessage().deserialize("<test_testing>", expansion.audiencePlaceholders(audience))
);
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());
assertContentEquals(result, Component.text("<test_testing>"));
}
}
@@ -1,6 +1,6 @@
package io.github.miniplaceholders.test;
import io.github.miniplaceholders.api.utils.LegacyStrings;
import io.github.miniplaceholders.api.utils.LegacyUtils;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Inserting;
import net.kyori.adventure.text.minimessage.tag.PreProcess;
@@ -10,11 +10,11 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class LegacyTest implements MiniTest {
class LegacyTest {
@Test
void testLegacyParsing() {
final String string = "<rainbow>hello... i &8hate legacy format §bso much";
final Component parsed = LegacyStrings.parsePossibleLegacy(string);
final Component parsed = LegacyUtils.parsePossibleLegacy(string);
final String result = PlainTextComponentSerializer.plainText().serialize(parsed);
final String expected = "hello... i hate legacy format so much";
@@ -24,15 +24,15 @@ class LegacyTest implements MiniTest {
@Test
void testMapLegacyOrElse() {
final String legacyInput = "&fhi <red>aaaa";
Tag legacyProcessedTag = LegacyStrings.mapLegacyOrElse(
Tag legacyProcessedTag = LegacyUtils.mapLegacyOrElse(
legacyInput,
legacy -> Tag.inserting(LegacyStrings.parsePossibleLegacy(legacy)),
legacy -> Tag.inserting(LegacyUtils.parsePossibleLegacy(legacy)),
Tag::preProcessParsed
);
assertInstanceOf(Inserting.class, legacyProcessedTag);
final String modernInput = "<rainbow>aaaaaaaaaaaaa";
Tag modernProcessedTag = LegacyStrings.mapLegacyOrElse(
Tag modernProcessedTag = LegacyUtils.mapLegacyOrElse(
modernInput,
__ -> fail(),
Tag::preProcessParsed
@@ -1,28 +1,32 @@
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.Tags;
import io.github.miniplaceholders.api.utils.TagsUtils;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MiniPlaceholdersTest implements MiniTest {
class MiniPlaceholdersTest {
@Test
@Disabled("Cannot correctly calculate the equity of 2 TagResolvers obtained in different ways")
void methodEquality(){
Expansion.builder("equality")
.audiencePlaceholder("audience", Tags.emptyAudienceResolver())
.globalPlaceholder("global", (_, _) -> Tags.EMPTY_TAG)
.audiencePlaceholder("audience", TagsUtils.NULL_AUDIENCE_PLACEHOLDER)
.globalPlaceholder("global", TagsUtils.EMPTY_TAG)
.build()
.register();
assertEquals(
MiniPlaceholders.audienceGlobalPlaceholders(),
MiniPlaceholders.getAudienceGlobalPlaceholders(Audience.empty()),
TagResolver.resolver(
MiniPlaceholders.audiencePlaceholders(),
MiniPlaceholders.globalPlaceholders()
MiniPlaceholders.getAudiencePlaceholders(Audience.empty()),
MiniPlaceholders.getGlobalPlaceholders()
)
);
}
@@ -33,8 +37,6 @@ class MiniPlaceholdersTest implements MiniTest {
.build();
expansion.register();
assertNotNull(MiniPlaceholders.expansionByName("testregistration"));
assertTrue(expansion.registered());
assertThrows(IllegalStateException.class, expansion::register);
@@ -1,30 +0,0 @@
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,9 +3,8 @@ 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.api.types.RelationalAudience;
import io.github.miniplaceholders.test.instances.TestAudience;
import io.github.miniplaceholders.test.instances.TestAudienceHolder;
import io.github.miniplaceholders.test.testobjects.TestAudience;
import io.github.miniplaceholders.test.testobjects.TestAudienceHolder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
@@ -16,136 +15,140 @@ 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 implements MiniTest {
class PlaceholderTest {
@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>", player, expansion.audiencePlaceholders());
final Component expected = Component.text("Player Name: 4drian3d");
final Component result = miniMessage().deserialize("Player Name: <example_name>", expansion.audiencePlaceholders(player));
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(TestAudience.class, "enemy",
(aud, relational, queue, ctx) ->
Tag.selfClosingInserting(this.isEnemy(aud, relational)
? Component.text("Enemy", NamedTextColor.RED)
: Component.text("Neutral", NamedTextColor.GREEN)
))
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)
))
.build();
assertEquals(
MiniMessage.miniMessage().deserialize("You are <red>Enemy"),
MiniMessage.miniMessage().deserialize("You are <relational_rel_enemy>",
RelationalAudience.from(p1, p2),
expansion.relationalPlaceholders()));
}
assertEquals(MiniMessage.miniMessage().deserialize("You are <red>Enemy"), MiniMessage.miniMessage().deserialize("You are <relational_rel_enemy>", expansion.relationalPlaceholders(p1, p2)));
}
@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.globalPlaceholders());
Component expected = Component.text("hello player", NamedTextColor.RED);
Component actual = MiniMessage.miniMessage().deserialize("<instance_hello> player", MiniPlaceholders.getGlobalPlaceholders());
assertContentEquals(expected, actual);
}
assertContentEquals(expected, actual);
}
@Test
@DisplayName("Filtered Expansion")
void filteredExpansion() {
Expansion expansion = Expansion.builder("filter")
.audiencePlaceholder(TestAudience.class, "name",
(aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(aud.name())))
@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)
.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, player, expansion.audiencePlaceholders()));
assertContentEquals(emptyExpected, MiniMessage.miniMessage().deserialize(string, emptyAudience, expansion.audiencePlaceholders()));
}
assertContentEquals(playerExpected, MiniMessage.miniMessage().deserialize(string, expansion.audiencePlaceholders(player)));
assertContentEquals(emptyExpected, MiniMessage.miniMessage().deserialize(string, expansion.audiencePlaceholders(emptyAudience)));
}
@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")
.audiencePlaceholder(TestAudience.class, "testing",
(audience, queue, ctx) -> Tag.selfClosingInserting(Component.text("Name: " + audience.name())))
.build();
@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();
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>.", forward, expansion.audiencePlaceholders());
Component expected = Component.text("Name: 4drian3d.");
Component parsed = miniMessage().deserialize("<test_testing>.", expansion.audiencePlaceholders(forward));
Component expected = Component.text("Name: 4drian3d.");
assertContentEquals(parsed, expected);
}
assertContentEquals(parsed, expected);
}
private boolean isEnemy(TestAudience a1, TestAudience a2) {
return a1.name().equals("PlayerOne") && a2.name().equals("PlayerTwo");
}
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");
}
}
@@ -1,48 +0,0 @@
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.jspecify.annotations.NonNull;
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(@NonNull 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,17 +1,23 @@
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 implements MiniTest {
class ThrowableTest {
@Test
@DisplayName("Nullable Exception")
void throwOnNull(){
@@ -19,8 +25,17 @@ class ThrowableTest implements MiniTest {
Expansion.Builder expansionTest = assertDoesNotThrow(() -> Expansion.builder("throwable"));
assertThrows(NullPointerException.class, () -> expansionTest.audiencePlaceholder(null, (aud, queue, ctx) -> Tags.EMPTY_TAG));
assertThrows(NullPointerException.class, () -> expansionTest.globalPlaceholder("test", (GlobalTagResolver) null));
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));
}
@Test
@@ -28,9 +43,9 @@ class ThrowableTest implements MiniTest {
void throwOnBlankString(){
Expansion.Builder builder = assertDoesNotThrow(() -> Expansion.builder("hello"));
assertThrows(IllegalStateException.class, () -> builder.audiencePlaceholder("", (aud, queue, ctx) -> Tags.EMPTY_TAG));
assertThrows(IllegalStateException.class, () -> builder.globalPlaceholder(" ", (queue, ctx) -> Tags.EMPTY_TAG));
assertThrows(IllegalStateException.class, () -> builder.audiencePlaceholder("", (aud, queue, ctx) -> TagsUtils.EMPTY_TAG));
assertThrows(IllegalStateException.class, () -> builder.globalPlaceholder(" ", (queue, ctx) -> TagsUtils.EMPTY_TAG));
assertDoesNotThrow(() -> builder.globalPlaceholder("hehe", (queue, ctx) -> Tags.EMPTY_TAG));
assertDoesNotThrow(() -> builder.globalPlaceholder("hehe", (queue, ctx) -> TagsUtils.EMPTY_TAG));
}
}
@@ -1,7 +0,0 @@
package io.github.miniplaceholders.test.instances;
import net.kyori.adventure.audience.Audience;
import org.jspecify.annotations.NullMarked;
@NullMarked
public record TestAudience(String name) implements Audience {}
@@ -1,21 +0,0 @@
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")
);
}
}
@@ -0,0 +1,7 @@
package io.github.miniplaceholders.test.testobjects;
import org.jetbrains.annotations.NotNull;
import net.kyori.adventure.audience.Audience;
public record TestAudience(@NotNull String name) implements Audience {}
@@ -1,12 +1,11 @@
package io.github.miniplaceholders.test.instances;
package io.github.miniplaceholders.test.testobjects;
import net.kyori.adventure.audience.ForwardingAudience;
import org.jspecify.annotations.NullMarked;
import org.jetbrains.annotations.NotNull;
@NullMarked
public record TestAudienceHolder(TestAudience audience) implements ForwardingAudience.Single {
@Override
public TestAudience audience() {
public @NotNull TestAudience audience() {
return audience;
}
}
+2 -2
View File
@@ -5,7 +5,7 @@ plugins {
dependencies {
implementation(libs.plugin.shadow)
implementation(libs.plugin.kotlin)
implementation(libs.plugin.runtask)
implementation(libs.plugin.dokka)
}
repositories {
@@ -14,6 +14,6 @@ repositories {
kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(25)
languageVersion.set(JavaLanguageVersion.of(17))
}
}
+1 -1
View File
@@ -15,5 +15,5 @@ dependencyResolutionManagement {
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
+3 -3
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)
.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
get() = this.name.substring(17).capitalized()
fun Project.simpleName(): String = this.name.substring(17)
fun Project.simpleName(): String = this.name.substring(17)
@@ -1,4 +1,4 @@
import java.util.*
import java.util.Date
plugins {
java
@@ -7,7 +7,7 @@ plugins {
tasks {
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release.set(25)
options.release.set(17)
}
jar {
manifest {
@@ -22,7 +22,7 @@ tasks {
java{
toolchain{
languageVersion.set(JavaLanguageVersion.of(25))
vendor.set(JvmVendorSpec.AZUL)
languageVersion.set(JavaLanguageVersion.of(17))
vendor.set(JvmVendorSpec.ADOPTIUM)
}
}
@@ -0,0 +1,80 @@
plugins {
kotlin("jvm")
`java-library`
`maven-publish`
signing
id("org.jetbrains.dokka")
}
/*
val dokkaHtmlJar = tasks.register<Jar>("dokkaHtmlJar") {
dependsOn(tasks.dokkaHtml)
from(tasks.dokkaHtml.flatMap { it.outputDirectory })
archiveClassifier.set("javadoc")
}
publishing {
publications {
create<MavenPublication>("maven") {
artifact(tasks["dokkaHtmlJar"])
repositories {
maven {
credentials(PasswordCredentials::class)
val central = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
val snapshots = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
if (project.version.toString().endsWith("SNAPSHOT")) {
name = "SonatypeSnapshots"
setUrl(snapshots)
} else {
name = "OSSRH"
setUrl(central)
}
}
}
from(components["java"])
pom {
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders")
licenses {
license {
name.set("Apache License 2.0")
url.set("https://opensource.org/license/apache-2-0/")
}
}
scm {
connection.set("scm:git:https://github.com/MiniPlaceholders/MiniPlaceholders.git")
developerConnection.set("scm:git:ssh://git@github.com/MiniPlaceholders/MiniPlaceholders.git")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders")
}
developers {
developer {
id.set("4drian3d")
name.set("Adrian Gonzales")
email.set("adriangonzalesval@gmail.com")
}
}
issueManagement {
name.set("GitHub")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders/issues")
}
ciManagement {
name.set("GitHub Actions")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders/actions")
}
name.set(project.name)
description.set(project.description)
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders")
}
}
}
}
signing {
useGpgCmd()
sign(configurations.archives.get())
sign(publishing.publications["maven"])
sign(tasks["dokkaHtmlJar"])
}
java {
withSourcesJar()
}*/
@@ -0,0 +1,71 @@
plugins {
`java-library`
`maven-publish`
signing
}
/*
publishing {
publications {
create<MavenPublication>("maven") {
repositories {
maven {
credentials(PasswordCredentials::class)
val central = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
val snapshots = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
if (project.version.toString().endsWith("SNAPSHOT")) {
name = "SonatypeSnapshots"
setUrl(snapshots)
} else {
name = "OSSRH"
setUrl(central)
}
}
}
from(components["java"])
pom {
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders")
licenses {
license {
name.set("Apache License 2.0")
url.set("https://opensource.org/license/apache-2-0/")
}
}
scm {
connection.set("scm:git:https://github.com/MiniPlaceholders/MiniPlaceholders.git")
developerConnection.set("scm:git:ssh://git@github.com/MiniPlaceholders/MiniPlaceholders.git")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders")
}
developers {
developer {
id.set("4drian3d")
name.set("Adrian Gonzales")
email.set("adriangonzalesval@gmail.com")
}
}
issueManagement {
name.set("GitHub")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders/issues")
}
ciManagement {
name.set("GitHub Actions")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders/actions")
}
name.set(project.name)
description.set(project.description)
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders")
}
}
}
}
signing {
useGpgCmd()
sign(configurations.archives.get())
sign(publishing.publications["maven"])
}*/
java {
withSourcesJar()
}
@@ -1,32 +0,0 @@
import org.gradle.api.tasks.bundling.Jar
import xyz.jpenilla.runtask.task.RunWithPlugins
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import kotlin.io.path.deleteIfExists
tasks {
withType<RunWithPlugins> {
val exampleExpansionProviderProject = project(":miniplaceholders-example-expansion-provider")
val exampleExpansionJarTask = exampleExpansionProviderProject.tasks.named<Jar>("jar")
dependsOn(exampleExpansionJarTask)
doFirst {
val folderName = if (project.name.contains("velocity")) "miniplaceholders" else "MiniPlaceholders"
val expansionsDirectory = runDirectory.get().file("plugins/$folderName/expansions").asFile.toPath()
if (Files.exists(expansionsDirectory)) {
Files.newDirectoryStream(expansionsDirectory).use {
it.forEach { file ->
file.deleteIfExists()
}
}
} else {
Files.createDirectories(expansionsDirectory)
}
val exampleExpansionFile = exampleExpansionJarTask.get().archiveFile.get().asFile.toPath()
Files.copy(
exampleExpansionFile,
expansionsDirectory.resolve(exampleExpansionFile.fileName),
StandardCopyOption.REPLACE_EXISTING
)
}
}
}
@@ -1,6 +1,6 @@
plugins {
id("miniplaceholders.base")
id("com.gradleup.shadow")
id("com.github.johnrengelman.shadow")
}
tasks {
@@ -9,8 +9,10 @@ tasks {
archiveClassifier.set("")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
relocate("net.william278.desertwell", "io.github.miniplaceholders.libs.desertwell")
relocate("org.bstats", "io.github.miniplaceholders.libs.bstats")
relocate("cloud.commandframework", "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 "2.4.21" apply false
kotlin("jvm") version "1.9.22" apply false
}
tasks {
+5 -5
View File
@@ -9,12 +9,12 @@ dependencies {
implementation(libs.desertwell) {
exclude("org.json")
}
implementation(libs.unnamedinject)
implementation(libs.cloud.core)
implementation(libs.cloud.extras) {
isTransitive = false
}
compileOnly(libs.adventure.api)
compileOnly(libs.adventure.minimessage)
compileOnly(libs.jspecify)
compileOnly(libs.brigadier)
compileOnly(libs.bstats.core)
compileOnly(libs.adventure.minimesssage)
}
sourceSets {
@@ -0,0 +1,198 @@
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,138 +1,7 @@
package io.github.miniplaceholders.common;
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.ComponentLike;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import team.unnamed.inject.Injector;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public interface PlaceholdersPlugin {
default void registerPlatformCommand() {
}
void loadDefaultExpansions();
default void loadProvidedExpansions(final 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 (Class<?> providerClass : ExpansionProviderLoader.loadProvidersFromFolder(providersFolderDirectory)) {
final ExpansionProvider provider = (ExpansionProvider) injector.getInstance(providerClass);
final ProviderLoadResult loadResult = tryLoad(provider, provider.loadRequirement());
final ExpansionProvider loadedProvider = loadResult.provider();
if (loadedProvider != null) {
final Expansion loadedExpansion = loadedProvider.provideExpansion();
loadedExpansions.add(loadedExpansion);
loadedExpansion.register();
continue;
}
final FailedToLoadExpansion failedToLoad = loadResult.failed();
logError(Component.text("The expansion "
+ providerClass +
" 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 Component expansionsInfo = loadedExpansions.stream()
.map(this::expansionInfo)
// Adventure 5.x/4.x support
.map(ComponentLike::asComponent)
.collect(Component.toComponent(Component.text(", ", NamedTextColor.WHITE)));
logInfo(Component.text("Loaded expansions: ", NamedTextColor.GREEN).append(expansionsInfo));
}
private ComponentLike expansionInfo(Expansion expansion) {
final TextComponent.Builder builder = Component.text();
builder.append(Component.text(expansion.name(), NamedTextColor.AQUA));
final String author = expansion.author();
final String version = expansion.version();
if (author == null && version == null) {
return builder.build();
}
builder.append(Component.text('[', NamedTextColor.DARK_GRAY));
if (author != null) {
builder.append(
Component.text("Author", NamedTextColor.GRAY),
Component.text(':', NamedTextColor.WHITE),
Component.space(),
Component.text(author, TextColor.color(0xd4ff54))
);
}
if (version != null) {
if (author != null) {
builder.append(Component.text(", ", NamedTextColor.WHITE));
}
builder.append(
Component.text("Version", NamedTextColor.GRAY),
Component.text(':', NamedTextColor.WHITE),
Component.space(),
Component.text(version, TextColor.color(0xd4ff54))
);
}
builder.append(Component.text(']', NamedTextColor.DARK_GRAY));
return builder;
}
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);
}
}
yield ProviderLoadResult.ofFailed(platformRequirement);
}
case LoadRequirement.AvailableComplementRequirement complementRequirement ->
complementRequirement.shouldLoad(this::platformHasComplementLoaded)
? ProviderLoadResult.ofLoaded(provider)
: 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);
}
case LoadRequirement.ClassRequirement classRequirement -> classRequirement.shouldLoad()
? ProviderLoadResult.ofLoaded(provider) : ProviderLoadResult.ofFailed(classRequirement);
case LoadRequirement.AnyOfRequirement anyOfRequirement -> {
ProviderLoadResult loadResult;
for (final LoadRequirement requirement : anyOfRequirement.requirements()) {
if ((loadResult = this.tryLoad(provider, requirement)).provider() != null) {
yield loadResult;
}
}
yield ProviderLoadResult.ofFailed(anyOfRequirement);
}
case LoadRequirement.NoneLoadRequirement ignored -> ProviderLoadResult.ofLoaded(provider);
};
}
boolean platformHasComplementLoaded(String complementName);
void logError(Component component);
void logInfo(Component component);
Object platformServerInstance();
void registerPlatformCommand();
}
@@ -1,10 +0,0 @@
package io.github.miniplaceholders.common.command;
import net.kyori.adventure.audience.Audience;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
@FunctionalInterface
public interface AudienceConverter {
@Nullable Audience convert(@NonNull String string);
}
@@ -1,10 +0,0 @@
package io.github.miniplaceholders.common.command;
import net.kyori.adventure.audience.Audience;
import org.jspecify.annotations.NullMarked;
@NullMarked
@FunctionalInterface
public interface AudienceExtractor<S> {
Audience extract(S source);
}
@@ -1,104 +0,0 @@
package io.github.miniplaceholders.common.command;
import com.google.common.base.Suppliers;
import io.github.miniplaceholders.api.MiniPlaceholders;
import io.github.miniplaceholders.api.types.Platform;
import io.github.miniplaceholders.common.PluginConstants;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.william278.desertwell.about.AboutMenu;
import net.william278.desertwell.util.Version;
import java.util.function.Supplier;
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 CommandConstants {
// Global
public static final Component TITLE = miniMessage().deserialize("<b><gradient:#4d8bff:#a4ff96>MiniPlaceholders</b>");
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> ");
// -- /miniplaceholders --
public static final Component INFO = AboutMenu.builder()
.title(TITLE)
.description(text("MiniMessage Component-based Placeholders API"))
.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"),
AboutMenu.Credit.of("fabianmakila"),
AboutMenu.Credit.of("Willsr71")
)
.buttons(
AboutMenu.Link.of("https://miniplaceholders.github.io/docs/category/user-guide").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();
// -- /miniplaceholders help --
public static final Supplier<Component> HELP = Suppliers.memoize(() -> {
final Platform platform = MiniPlaceholders.platform();
final String command = platform == Platform.VELOCITY ? "/vminiplaceholders" : "/miniplaceholders";
return text()
.append(newline(), HEADER)
.append(newline(), miniMessage().deserialize(
"<gradient:#9694ff:#5269ff>Commands:</gradient>")
)
.append(newline(), miniMessage().deserialize(
"<gradient:aqua:#94d1ff>" + command + "</gradient> <aqua>help</aqua>")
.clickEvent(ClickEvent.runCommand(command + " help"))
)
.append(newline(), miniMessage().deserialize(
"""
<gradient:aqua:#94d1ff>%s</gradient> \
<aqua>parse</aqua> \
<#8fadff><player | me | --null></#8fadff> \
<#99ffb6><message></#99ffb6>""".formatted(command))
.clickEvent(ClickEvent.suggestCommand(command + " parse "))
)
.append(newline(), miniMessage().deserialize(
"""
<gradient:aqua:#94d1ff>%s</gradient> \
<aqua>parserel</aqua> \
<#8fadff><player | me></#8fadff> \
<#8fadff><relational player | me></#8fadff> \
<#99ffb6><message></#99ffb6>""".formatted(command))
.clickEvent(ClickEvent.suggestCommand(command + " parserel "))
)
.append(newline(), miniMessage().deserialize(
"<gradient:aqua:#94d1ff>" + command + "</gradient> <aqua>expansions</aqua>")
.clickEvent(ClickEvent.runCommand(command + " expansions"))
)
.append(newline(), FOOTER)
.build();
});
// -- /miniplaceholders expansions --
public static final Component EXPANSIONS_TITLE = miniMessage().deserialize(
"""
<br><gradient:aqua:white:aqua><st><b> </st> \
<title> Expansions <st><b> \
</gradient><br>""", Placeholder.component("title", TITLE)
);
public static final Supplier<Component> NON_INSTALLED_EXPANSIONS = Suppliers.memoize(() -> 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 <br>\
<blue><click:open_url:"https://miniplaceholders.github.io/docs/user-guide/Placeholders">\
<hover:show_text:"<gold>Search Expansions">[MiniPlaceholders Expansions]</blue>""")
);
}
@@ -1,13 +0,0 @@
package io.github.miniplaceholders.common.command;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.util.TriState;
import org.jspecify.annotations.NullMarked;
@NullMarked
@FunctionalInterface
public interface PermissionTester {
TriState permissionValue(Audience audience, String permission);
PermissionTester NO_OP = (audience, permission) -> TriState.NOT_SET;
}
@@ -1,9 +0,0 @@
package io.github.miniplaceholders.common.command;
import net.kyori.adventure.audience.Audience;
import org.jspecify.annotations.NonNull;
@FunctionalInterface
public interface PlaceholderCommandExecutor {
void execute(@NonNull Audience audience);
}
@@ -1,10 +0,0 @@
package io.github.miniplaceholders.common.command;
import org.jspecify.annotations.NonNull;
import java.util.ArrayList;
@FunctionalInterface
public interface PlayersNameProvider {
@NonNull ArrayList<String> provide();
}
@@ -1,97 +0,0 @@
package io.github.miniplaceholders.common.command.brigadier;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.LiteralCommandNode;
import io.github.miniplaceholders.common.command.*;
import io.github.miniplaceholders.common.command.node.*;
import net.kyori.adventure.audience.Audience;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class BrigadierCommandProvider {
public static <A> LiteralCommandNode<A> provideCommand(
final String command,
final PlayersNameProvider playersSuggestions,
final AudienceConverter audienceConverter,
final AudienceExtractor<A> audienceExtractor,
final PermissionTester permissionChecker
) {
// Inspired by MiniMotd command implementation (MIT licenced)
class WrappingExecutor implements Command<A> {
private final PlaceholderCommandExecutor executor;
private WrappingExecutor(final PlaceholderCommandExecutor executor) {
this.executor = executor;
}
@Override
public int run(final CommandContext<A> commandContext) {
executor.execute(audienceExtractor.extract(commandContext.getSource()));
return Command.SINGLE_SUCCESS;
}
}
// Command Nodes
final RootNode rootNode = new RootNode(permissionChecker);
final ExpansionsNode expansionsNode = new ExpansionsNode(permissionChecker);
final HelpNode helpNode = new HelpNode(permissionChecker);
final ParseNode parseNode = new ParseNode(playersSuggestions, audienceConverter, permissionChecker);
final ParseRelNode parseRelNode = new ParseRelNode(playersSuggestions, audienceConverter, permissionChecker);
return LiteralArgumentBuilder.<A>literal(command)
.requires(src -> rootNode.hasPermission(audienceExtractor.extract(src)))
.executes(new WrappingExecutor(rootNode::execute))
.then(LiteralArgumentBuilder.<A>literal("expansions")
.requires(src -> expansionsNode.hasPermission(audienceExtractor.extract(src)))
.executes(new WrappingExecutor(expansionsNode::showExpansions))
.build()
)
.then(LiteralArgumentBuilder.<A>literal("help")
.executes(new WrappingExecutor(helpNode::execute))
.requires(src -> helpNode.hasPermission(audienceExtractor.extract(src)))
)
.then(LiteralArgumentBuilder.<A>literal("parse")
.requires(src -> parseNode.hasPermission(audienceExtractor.extract(src)))
.then(RequiredArgumentBuilder.<A, String>argument("source", StringArgumentType.word())
.suggests(new BrigadierSuggestionProvider<>(parseNode))
.then(RequiredArgumentBuilder.<A, String>argument("message", StringArgumentType.greedyString())
.executes(ctx -> {
final Audience audience = audienceExtractor.extract(ctx.getSource());
final String source = StringArgumentType.getString(ctx, "source");
final String message = StringArgumentType.getString(ctx, "message");
parseNode.parseString(audience, source, message);
return Command.SINGLE_SUCCESS;
})
)
)
)
.then(LiteralArgumentBuilder.<A>literal("parserel")
.requires(src -> parseRelNode.hasPermission(audienceExtractor.extract(src)))
.then(RequiredArgumentBuilder.<A, String>argument("source", StringArgumentType.word())
.suggests(new BrigadierSuggestionProvider<>(parseRelNode))
.then(RequiredArgumentBuilder.<A, String>argument("relational", StringArgumentType.word())
.suggests(new BrigadierSuggestionProvider<>(parseRelNode))
.then(RequiredArgumentBuilder.<A, String>argument("message", StringArgumentType.greedyString())
.executes(ctx -> {
final Audience audience = audienceExtractor.extract(ctx.getSource());
final String source = StringArgumentType.getString(ctx, "source");
final String relational = StringArgumentType.getString(ctx, "source");
final String message = StringArgumentType.getString(ctx, "message");
parseRelNode.parseString(audience, source, relational, message);
return Command.SINGLE_SUCCESS;
})
)
)
)
)
.build();
}
}
@@ -1,19 +0,0 @@
package io.github.miniplaceholders.common.command.brigadier;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import io.github.miniplaceholders.common.command.node.PlayerCompleterNode;
import org.jspecify.annotations.NullMarked;
import java.util.concurrent.CompletableFuture;
@NullMarked
public record BrigadierSuggestionProvider<A>(PlayerCompleterNode playerSuggestions) implements SuggestionProvider<A> {
@Override
public CompletableFuture<Suggestions> getSuggestions(final CommandContext<A> context, final SuggestionsBuilder builder) {
this.playerSuggestions.providePlayerSuggestions().forEach(builder::suggest);
return builder.buildFuture();
}
}
@@ -1,65 +0,0 @@
package io.github.miniplaceholders.common.command.node;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.MiniPlaceholders;
import io.github.miniplaceholders.common.command.PermissionTester;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import org.jspecify.annotations.NullMarked;
import java.util.Collection;
import static io.github.miniplaceholders.common.command.CommandConstants.*;
@NullMarked
public record ExpansionsNode(PermissionTester permissionTester) implements Node {
public void showExpansions(final Audience audience) {
final TextComponent.Builder builder = Component.text()
.append(EXPANSIONS_TITLE);
final Collection<Expansion> expansions = MiniPlaceholders.expansionsAvailable();
if (expansions.isEmpty()) {
// In virtually all installations, there will always be some expansion installed,
// so there is no need to cache this component in that case
builder.append(NON_INSTALLED_EXPANSIONS.get());
} 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(" | ", NamedTextColor.DARK_GRAY))));
}
builder.appendNewline().append(FOOTER);
audience.sendMessage(builder.build());
}
@Override
public boolean hasPermission(Audience audience) {
return permissionTester.permissionValue(audience, permission()).toBooleanOrElse(false);
}
@Override
public String permission() {
return "miniplaceholders.command.expansions";
}
}
@@ -1,24 +0,0 @@
package io.github.miniplaceholders.common.command.node;
import io.github.miniplaceholders.common.command.PermissionTester;
import net.kyori.adventure.audience.Audience;
import org.jspecify.annotations.NullMarked;
import static io.github.miniplaceholders.common.command.CommandConstants.HELP;
@NullMarked
public record HelpNode(PermissionTester permissionChecker) implements Node {
public void execute(final Audience audience) {
audience.sendMessage(HELP.get());
}
@Override
public boolean hasPermission(Audience audience) {
return permissionChecker.permissionValue(audience, permission()).toBooleanOrElse(false);
}
@Override
public String permission() {
return "miniplaceholders.command.help";
}
}
@@ -1,11 +0,0 @@
package io.github.miniplaceholders.common.command.node;
import net.kyori.adventure.audience.Audience;
import org.jspecify.annotations.NullMarked;
@NullMarked
public sealed interface Node permits ExpansionsNode, HelpNode, ParseNode, ParseRelNode, RootNode {
boolean hasPermission(Audience audience);
String permission();
}
@@ -1,59 +0,0 @@
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.PermissionTester;
import io.github.miniplaceholders.common.command.PlayersNameProvider;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jspecify.annotations.NullMarked;
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;
@NullMarked
public record ParseNode(
PlayersNameProvider playersNameProvider,
AudienceConverter audienceConverter,
PermissionTester permissionChecker
) implements Node, PlayerCompleterNode {
public void parseString(final Audience sender, final String providedSource, final String providedString) {
final Audience source = switch (providedSource) {
case "me" -> sender;
case "--null" -> Audience.empty();
default -> audienceConverter.convert(providedSource);
};
if (source == null) {
sender.sendMessage(text("You must specify a valid player", RED));
return;
}
final TagResolver resolver = source == Audience.empty()
? MiniPlaceholders.globalPlaceholders()
: MiniPlaceholders.audienceGlobalPlaceholders();
final Component parsed = miniMessage().deserialize(providedString, source, resolver);
sender.sendMessage(parsed);
}
@Override
public boolean hasPermission(final Audience audience) {
return permissionChecker.permissionValue(audience, permission()).toBooleanOrElse(false);
}
@Override
public String permission() {
return "miniplaceholders.command.parse";
}
@Override
public List<String> providePlayerSuggestions() {
final List<String> suggestions = PlayerCompleterNode.super.providePlayerSuggestions();
suggestions.add("--null");
return suggestions;
}
}
@@ -1,59 +0,0 @@
package io.github.miniplaceholders.common.command.node;
import io.github.miniplaceholders.api.MiniPlaceholders;
import io.github.miniplaceholders.api.types.RelationalAudience;
import io.github.miniplaceholders.common.command.AudienceConverter;
import io.github.miniplaceholders.common.command.PermissionTester;
import io.github.miniplaceholders.common.command.PlayersNameProvider;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import org.jspecify.annotations.NullMarked;
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;
@NullMarked
public record ParseRelNode(
PlayersNameProvider playersNameProvider,
AudienceConverter audienceConverter,
PermissionTester permissionChecker
) implements Node, PlayerCompleterNode {
public void parseString(final Audience sender, final String source, final String relational, final String toParse) {
final Audience senderTarget = "me".equals(source)
? sender
: audienceConverter.convert(source);
if (senderTarget == null) {
sender.sendMessage(text("You must specify a valid player", RED));
return;
}
final Audience relationalTarget = "me".equals(relational)
? sender
: audienceConverter.convert(relational);
if (relationalTarget == null) {
sender.sendMessage(text("You must specify a valid relational player", RED));
return;
}
final RelationalAudience<Audience> relationalAudience = RelationalAudience.from(senderTarget, relationalTarget);
final Component parsed = miniMessage().deserialize(
toParse,
relationalAudience,
MiniPlaceholders.relationalGlobalPlaceholders()
);
sender.sendMessage(parsed);
}
@Override
public boolean hasPermission(Audience audience) {
return permissionChecker.permissionValue(audience, permission()).toBooleanOrElse(false);
}
@Override
public String permission() {
return "miniplaceholders.command.parse";
}
}
@@ -1,15 +0,0 @@
package io.github.miniplaceholders.common.command.node;
import io.github.miniplaceholders.common.command.PlayersNameProvider;
import java.util.List;
public interface PlayerCompleterNode {
default List<String> providePlayerSuggestions() {
final List<String> suggestions = playersNameProvider().provide();
suggestions.add("me");
return suggestions;
}
PlayersNameProvider playersNameProvider();
}
@@ -1,26 +0,0 @@
package io.github.miniplaceholders.common.command.node;
import io.github.miniplaceholders.common.command.PermissionTester;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.util.TriState;
import org.jspecify.annotations.NullMarked;
import static io.github.miniplaceholders.common.command.CommandConstants.INFO;
@NullMarked
public record RootNode(PermissionTester permissionChecker) implements Node {
public void execute(Audience audience) {
audience.sendMessage(INFO);
}
@Override
public boolean hasPermission(Audience audience) {
return permissionChecker.permissionValue(audience, permission()) != TriState.FALSE;
}
@Override
public String permission() {
return "miniplaceholders.command";
}
}
@@ -1,95 +0,0 @@
package io.github.miniplaceholders.common.loader;
import io.github.miniplaceholders.api.provider.ExpansionProvider;
import org.jspecify.annotations.NullMarked;
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;
@NullMarked
public final class ExpansionProviderLoader {
public static List<Class<?>> 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<Class<?>> providerList = new ArrayList<>();
for (final Path possibleProviderPath : stream) {
loadPossibleProviderFromPath(possibleProviderPath, providerList);
}
return providerList;
}
}
private static void loadPossibleProviderFromPath(
final Path filePath,
final List<Class<?>> 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<Class<?>> providers,
final Path providerPath
) throws Exception {
//noinspection resource
final URLClassLoader classLoader = new URLClassLoader(
"MiniPlaceholders ClassLoader",
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;
}
providers.add(mainClass);
}
}
@@ -1,7 +0,0 @@
package io.github.miniplaceholders.common.loader;
import io.github.miniplaceholders.api.provider.LoadRequirement;
import org.jspecify.annotations.NonNull;
public record FailedToLoadExpansion(@NonNull LoadRequirement requirement) {
}
@@ -1,14 +0,0 @@
package io.github.miniplaceholders.common.loader;
import io.github.miniplaceholders.api.provider.ExpansionProvider;
import io.github.miniplaceholders.api.provider.LoadRequirement;
public record ProviderLoadResult(ExpansionProvider provider, FailedToLoadExpansion failed) {
public static ProviderLoadResult ofLoaded(ExpansionProvider provider) {
return new ProviderLoadResult(provider, null);
}
public static ProviderLoadResult ofFailed(LoadRequirement requirement) {
return new ProviderLoadResult(null, new FailedToLoadExpansion(requirement));
}
}
@@ -1,12 +0,0 @@
package io.github.miniplaceholders.common.metrics;
import io.github.miniplaceholders.api.MiniPlaceholders;
import java.util.concurrent.Callable;
public final class LoadedExpansionsMetric implements Callable<Integer> {
@Override
public Integer call() {
return MiniPlaceholders.expansionsAvailable().size();
}
}
@@ -2,8 +2,7 @@ package io.github.miniplaceholders.connect;
public enum InternalPlatform {
PAPER, VELOCITY,
FABRIC, SPONGE, MINESTOM;
PAPER, VELOCITY, KRYPTON, FABRIC, SPONGE;
private static InternalPlatform actualPlatform;
@@ -1,23 +0,0 @@
plugins {
id("miniplaceholders.auto.module")
alias(libs.plugins.idea.ext)
alias(libs.plugins.blossom)
}
version = "1.4.0"
dependencies {
compileOnly(projects.miniplaceholdersApi)
compileOnly(libs.adventure.api)
compileOnly(libs.adventure.minimessage)
}
sourceSets {
main {
blossom {
javaSources {
property("version", project.version.toString())
}
}
}
}
@@ -1,7 +0,0 @@
package io.github.miniplaceholders.provider.example;
public final class ExpansionConstants {
public static final String VERSION = "{{ version }}";
private ExpansionConstants() {}
}
@@ -1,64 +0,0 @@
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.provider.PlatformData;
import io.github.miniplaceholders.api.types.Platform;
import io.github.miniplaceholders.provider.example.ExpansionConstants;
import net.kyori.adventure.text.minimessage.tag.Tag;
import team.unnamed.inject.Inject;
public record ExampleProvider(PlatformData platformData) implements ExpansionProvider {
@Inject
public ExampleProvider {}
@Override
public Expansion provideExpansion() {
return Expansion.builder("example")
.author("MiniPlaceholders Contributors")
.globalPlaceholder("test", (queue, context) ->
Tag.preProcessParsed("""
Platform Instance Class: %s
MiniPlaceholders Instance Class: %s
""".formatted(
platformData.serverInstance(),
platformData.complementInstance()
)))
.audiencePlaceholder("audience", (audience, queue, ctx) -> Tag.preProcessParsed("""
Audience Instance Class: %s
MiniPlaceholders Instance Class: %s
""".formatted(
audience,
platformData.complementInstance()
)))
.relationalPlaceholder("test", (audience, relational, queue, ctx) -> Tag.preProcessParsed("""
Audience Instance Class: %s
Relational Instance Class: %s
Is Audience equals Relational? %s
MiniPlaceholders Instance Class: %s
""".formatted(
audience,
relational,
audience.equals(relational),
platformData.complementInstance()
)))
.version(ExpansionConstants.VERSION)
.build();
}
@Override
public LoadRequirement loadRequirement() {
return LoadRequirement.anyOf(
LoadRequirement.platform(Platform.SPONGE),
LoadRequirement.allOf(
LoadRequirement.requiredComplement(
"MiniPlaceholders",
"miniplaceholders"
),
LoadRequirement.platform(Platform.PAPER)
),
LoadRequirement.requiredClass("io.github.miniplaceholders.common.command.node.PlayerCompleterNode")
);
}
}
@@ -1 +0,0 @@
provider-class=io.miniplaceholders.provider.example.ExampleProvider
+18 -55
View File
@@ -1,34 +1,23 @@
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import kotlin.io.path.deleteIfExists
plugins {
alias(libs.plugins.fabric.loom)
id("miniplaceholders.auto.module")
}
repositories {
maven("https://central.sonatype.com/repository/maven-snapshots/") {
name = "sonatype-snapshots"
mavenContent {
snapshotsOnly()
}
}
maven("https://jitpack.io")
id("fabric-loom")
alias(libs.plugins.shadow)
}
val shade: Configuration by configurations.creating
dependencies {
minecraft(libs.minecraft)
compileOnly(libs.fabric.loader)
compileOnly(libs.fabric.api)
mappings(loom.officialMojangMappings())
modImplementation(libs.fabric.loader)
modImplementation(libs.fabric.api)
includeDependency(libs.adventure.platform.fabric)
includeDependency(libs.adventure.serializer.legacy)
includeDependency(libs.adventure.minimessage)
includeDependency(libs.luckopermissionsapi)
includeDependency(libs.unnamedinject)
includeDependency(libs.cloud.fabric)
includeDependency(libs.cloud.core)
includeDependency(libs.cloud.extras)
includeDependency(libs.desertwell)
shadeModule(projects.miniplaceholdersApi)
@@ -37,59 +26,33 @@ dependencies {
}
fun DependencyHandlerScope.shadeModule(module: ProjectDependency) {
implementation(module) {
shade(module) {
isTransitive = false
}
include(module) {
implementation(module) {
isTransitive = false
}
}
fun DependencyHandlerScope.includeDependency(dependency: Any) {
implementation(dependency)
modImplementation(dependency)
include(dependency)
}
tasks {
val projectVersion = project.version
val rootDir = project.rootDir
processResources {
filteringCharset = Charsets.UTF_8.name()
filesMatching("fabric.mod.json") {
expand("version" to projectVersion)
expand("version" to project.version)
}
}
jar {
archiveFileName.set("MiniPlaceholders-Fabric-${projectVersion}.jar")
destinationDirectory.set(file("${rootDir}/jar"))
remapJar {
inputFile.set(shadowJar.get().archiveFile)
archiveFileName.set("MiniPlaceholders-Fabric-${project.version}.jar")
destinationDirectory.set(file("${project.rootDir}/jar"))
}
// I know it's exactly the same logic for all modules,
// but for some reason in this module it only works
// if it's declared in the same project configuration
// and not with a plugin like with all other projects...
// ¯\_(ツ)_/¯
runServer {
val exampleExpansionProviderProject = project(":miniplaceholders-example-expansion-provider")
val exampleExpansionJarTask = exampleExpansionProviderProject.tasks.named<Jar>("jar")
dependsOn(exampleExpansionJarTask)
doFirst {
val expansionsDirectory = projectDir.resolve("run/config/miniplaceholders/expansions").toPath()
if (Files.exists(expansionsDirectory)) {
Files.newDirectoryStream(expansionsDirectory).use {
it.forEach { file ->
file.deleteIfExists()
}
}
} else {
Files.createDirectories(expansionsDirectory)
}
val exampleExpansionFile = exampleExpansionJarTask.get().archiveFile.get().asFile.toPath()
Files.copy(
exampleExpansionFile,
expansionsDirectory.resolve(exampleExpansionFile.fileName),
StandardCopyOption.REPLACE_EXISTING
)
}
shadowJar {
configurations = listOf(shade)
}
}
@@ -1,88 +1,113 @@
package io.github.miniplaceholders.fabric;
import com.mojang.brigadier.tree.LiteralCommandNode;
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.brigadier.BrigadierCommandProvider;
import io.github.miniplaceholders.connect.InternalPlatform;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.fabricmc.api.DedicatedServerModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
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.logger.slf4j.ComponentLogger;
import net.kyori.adventure.util.TriState;
import net.kyori.adventure.text.minimessage.tag.Tag;
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 java.nio.file.Path;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public final class FabricMod implements DedicatedServerModInitializer, PlaceholdersPlugin {
private final ComponentLogger componentLogger = ComponentLogger.logger("miniplaceholders");
private MinecraftServer minecraftServer;
public class FabricMod implements ModInitializer, PlaceholdersPlugin {
private final Logger logger = LoggerFactory.getLogger("miniplaceholders");
private static final DecimalFormat MSPT_FORMAT = new DecimalFormat("###.###");
@Override
public void onInitializeServer() {
componentLogger.info("Starting MiniPlaceholders Fabric");
InternalPlatform.platform(InternalPlatform.FABRIC);
private final TickManager tickManager = new TickManager();
private MinecraftServer minecraftServer;
ServerLifecycleEvents.SERVER_STARTING.register(minecraftServer -> {
this.minecraftServer = minecraftServer;
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);
}
});
@Override
public void onInitialize() {
logger.info("Starting MiniPlaceholders Fabric");
InternalPlatform.platform(InternalPlatform.FABRIC);
this.registerPlatformCommand();
}
ServerLifecycleEvents.SERVER_STARTING.register(minecraftServer -> {
this.minecraftServer = minecraftServer;
this.loadDefaultExpansions();
});
@Override
public void registerPlatformCommand() {
final LiteralCommandNode<CommandSourceStack> node = BrigadierCommandProvider.provideCommand(
"miniplaceholders",
() -> new ArrayList<>(List.of(this.minecraftServer.getPlayerNames())),
string -> this.minecraftServer.getPlayerList().getPlayerByName(string),
aud -> aud,
(audience, permission) -> {
final CommandSourceStack commandSourceStack = (CommandSourceStack) audience;
return switch (Permissions.getPermissionValue(commandSourceStack, permission)) {
case TRUE -> TriState.TRUE;
case FALSE -> TriState.FALSE;
case DEFAULT -> commandSourceStack.permissions()
.hasPermission(net.minecraft.server.permissions.Permissions.COMMANDS_GAMEMASTER)
? TriState.TRUE : TriState.NOT_SET;
};
}
);
CommandRegistrationCallback.EVENT.register(
(dispatcher, ctx, selection) ->
dispatcher.getRoot().addChild(node)
);
}
this.registerPlatformCommand();
}
@Override
public boolean platformHasComplementLoaded(String complementName) {
return FabricLoader.getInstance().isModLoaded(complementName);
}
@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 logError(Component component) {
componentLogger.error(component);
}
@Override
public void logInfo(Component component) {
componentLogger.info(component);
}
@Override
public Object platformServerInstance() {
return this.minecraftServer;
}
@Override
public void registerPlatformCommand() {
final FabricServerCommandManager<CommandSourceStack> commandManager = new FabricServerCommandManager<>(
AsynchronousCommandExecutionCoordinator.simpleCoordinator(),
Function.identity(),
Function.identity()
);
PlaceholdersCommand.<CommandSourceStack>builder()
.hasPermissionCheck((source, permission) -> Permissions.check(source, permission, 4))
.toAudience(string -> this.minecraftServer.getPlayerList().getPlayerByName(string))
.playerSuggestions(() -> new ArrayList<>(List.of(this.minecraftServer.getPlayerNames())))
.manager(commandManager)
.command("miniplaceholders")
.build()
.register();
}
}
@@ -0,0 +1,59 @@
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());
}
}
@@ -0,0 +1,45 @@
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;
}
}
+8 -9
View File
@@ -3,28 +3,27 @@
"id": "miniplaceholders",
"version": "${version}",
"name": "MiniPlaceholders",
"description": "MiniMessage Component-based Placeholders API",
"description": "MiniMessage Component-based Placeholders for PaperMC, Fabric, Krypton and Velocity platforms",
"authors": [
"4drian3d"
],
"contact": {
"homepage": "https://miniplaceholders.github.io/",
"homepage": "https://github.com/MiniPlaceholders/MiniPlaceholders",
"sources": "https://github.com/MiniPlaceholders/MiniPlaceholders",
"issues": "https://github.com/MiniPlaceholders/MiniPlaceholders/issues"
},
"license": "Apache-2",
"environment": "server",
"entrypoints": {
"server": [
"main": [
"io.github.miniplaceholders.fabric.FabricMod"
]
},
"depends": {
"fabricloader": ">=0.18.0",
"fabric-api": "*",
"java": ">=25",
"adventure-platform-fabric": ">6.8.0",
"minecraft": ">=26.2",
"fabric-permissions-api-v0": ">=0.7.0"
"fabricloader": ">=0.14.19",
"fabric": "*",
"java": ">=17",
"minecraft": ">=1.20.2",
"fabric-permissions-api-v0": "*"
}
}
+3 -6
View File
@@ -1,8 +1,5 @@
group = io.github.miniplaceholders
version = 3.2.2-SNAPSHOT
description = MiniMessage Component-based Placeholders API
version = 2.3.0-SNAPSHOT
description = MiniMessage Component-based Placeholders for Paper, Fabric, Sponge, Folia and Velocity platforms
org.gradle.jvmargs=-Xmx2G
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
#org.gradle.configuration-cache=true
org.gradle.jvmargs=-Xmx2G
+49 -51
View File
@@ -2,76 +2,74 @@ metadata.format.version = "1.1"
[versions]
# Platform
minecraft = "26.1"
paper = "1.21.11-R0.1-SNAPSHOT"
velocity = "4.3.0"
minestom = "2026.10.07-26.2"
fabric-loader = "0.20.1"
fabric-api = "0.145.0+26.1"
velocity = "3.2.0-SNAPSHOT"
paper = "1.20.2-R0.1-SNAPSHOT"
minecraft = "1.20.2"
runtask = "2.2.3"
pluginyml = "0.6.0"
userdev = "1.7.1"
blossom = "2.1.0"
indra = "3.1.3"
shadow = "8.1.1"
jmh = "0.7.2"
dokka = "1.9.10"
cloud = "1.8.4"
adventure = "4.15.0"
adventurefabric = "5.12.0"
brigadier = "1.0.500"
kotlin="1.9.22"
fabric-api = "0.96.4+1.20.5"
fabric-loader = "0.15.7"
# Libraries
kotlin = "2.4.21"
adventure = "5.2.0"
adventureModded = "7.2.0"
desertwell = "2.0.4"
bstats = "3.2.1"
jspecify = "1.0.1"
# Gradle Plugins
runtask = "3.1.0"
blossom = "2.2.0"
shadow = "9.6.1"
jmh = "0.7.3"
dokka = "2.2.0"
[libraries]
# Platforms
minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" }
paper = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" }
velocity = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocity" }
minestom = { group = "net.minestom", name = "minestom", version.ref = "minestom" }
fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" }
fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" }
paper = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" }
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" }
# Adventure
adventure-api = { group = "net.kyori", name = "adventure-api", version.ref = "adventure" }
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-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-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" }
# Build-Logic Gradle Plugins
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-runtask = { module = "xyz.jpenilla.run-velocity:xyz.jpenilla.run-velocity.gradle.plugin", version.ref = "runtask" }
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" }
brigadier = { group = "com.mojang", name = "brigadier", version.ref = "brigadier" }
plugin-shadow = { group = "com.github.johnrengelman", name = "shadow", 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"
# Libraries
desertwell = { group = "net.william278", name = "DesertWell", version.ref = "desertwell" }
junit-bom = "org.junit:junit-bom:6.1.3"
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
luckopermissionsapi = { group = "me.lucko", name = "fabric-permissions-api", version = "0.7.0" }
unnamedinject = "team.unnamed:inject:2.0.1"
brigadier = "com.mojang:brigadier:1.0.500"
slf4j = "org.slf4j:slf4j-api:2.0.20"
# Bstats
bstats-core = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" }
bstats-velocity = { group = "org.bstats", name = "bstats-velocity", version.ref = "bstats" }
bstats-sponge = { group = "org.bstats", name = "bstats-sponge", version.ref = "bstats" }
bstats-bukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bstats" }
junit-bom = "org.junit:junit-bom:5.10.2"
[plugins]
# Platform 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" }
fabric-loom = { id = "net.fabricmc.fabric-loom", version = "1.18.4"}
spongegradle = { id = "org.spongepowered.gradle.plugin", version = "2.5.1" }
# General Plugins
blossom = { id = "net.kyori.blossom", version.ref = "blossom" }
shadow = { id = "com.github.johnrengelman.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.4.1" }
idea-ext = { id = "org.jetbrains.gradle.plugin.idea-ext", version = "1.1.7" }
Binary file not shown.
+1 -3
View File
@@ -1,9 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+10 -9
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
# Copyright © 2015-2021 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,12 +15,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# gradlew start up script for POSIX generated by Gradle.
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +27,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh gradlew
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
@@ -57,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -86,7 +84,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 -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -114,6 +112,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -171,6 +170,7 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -203,14 +203,15 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_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.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
Vendored
+34 -54
View File
@@ -13,45 +13,16 @@
@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 ##########################################################################
@rem
@rem gradlew startup script for Windows
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
@rem Catch executions from older scripts and ensure they exit cleanly.
@rem This can be removed once we can be reasonably confident that few people
@rem will be migrating directly to this new wrapper.
goto afterSafetyNet
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
goto exitWithErrorLevel
:afterSafetyNet
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@@ -72,14 +43,13 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
1>&2 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.
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.
"%COMSPEC%" /c exit 1
goto exitWithErrorLevel
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
@@ -87,26 +57,36 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
1>&2 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.
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.
"%COMSPEC%" /c exit 1
goto exitWithErrorLevel
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
@rem This label must not be changed. We rely on old scripts being able to jump to this point.
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
-1
View File
@@ -1 +0,0 @@
# Package io.github.miniplaceholders.kotlin
+4 -69
View File
@@ -1,84 +1,19 @@
import com.vanniktech.maven.publish.KotlinJvm
import com.vanniktech.maven.publish.JavadocJar
import com.vanniktech.maven.publish.SourcesJar
import java.time.LocalDate
plugins {
kotlin("jvm")
id("com.vanniktech.maven.publish")
alias(libs.plugins.dokka)
id("miniplaceholders.kotlin.publish")
}
dependencies {
compileOnly(kotlin("stdlib"))
compileOnly(libs.adventure.api)
compileOnly(libs.adventure.minimessage)
compileOnly(projects.miniplaceholdersApi)
compileOnly(libs.adventure.minimesssage)
compileOnlyApi(projects.miniplaceholdersApi)
}
kotlin {
explicitApi()
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(25))
languageVersion.set(JavaLanguageVersion.of(17))
}
}
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")
}
}
mavenPublishing {
publishToMavenCentral()
signAllPublications()
coordinates(project.group as String, "miniplaceholders-kotlin-ext", project.version as String)
pom {
name.set(project.name)
description.set(project.description)
inceptionYear.set(LocalDate.now().year.toString())
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders")
licenses {
license {
name.set("The Apache License, Version 2.0")
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
distribution.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
}
}
developers {
developer {
id.set("4drian3d")
name.set("Adrian Gonzales")
email.set("adriangonzalesval@gmail.com")
}
}
scm {
connection.set("scm:git:https://github.com/MiniPlaceholders/MiniPlaceholders.git")
developerConnection.set("scm:git:ssh://git@github.com/MiniPlaceholders/MiniPlaceholders.git")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders")
}
ciManagement {
name.set("GitHub Actions")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders/actions")
}
issueManagement {
name.set("GitHub")
url.set("https://github.com/MiniPlaceholders/MiniPlaceholders/issues")
}
}
configure(KotlinJvm(
javadocJar = JavadocJar.Dokka("dokkaGeneratePublicationHtml"),
sourcesJar = SourcesJar.Sources(),
))
}
@@ -1,11 +1,6 @@
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
@@ -17,23 +12,5 @@ import net.kyori.adventure.text.minimessage.tag.Tag
* @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.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)
public fun expansion(name: String, builder: Expansion.Builder.() -> Expansion.Builder): Expansion
= builder(Expansion.builder(name)).build()
@@ -1,14 +0,0 @@
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)
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff. Show more