Compare commits

..
Author SHA1 Message Date
Adrian Gonzales 1a31d98149 feat: Added RelationalPlaceholder builder 2025-10-04 22:34:51 -05:00
Adrian Gonzales ae5a8e235e Merge branch 'main' into feat/complex-filter 2025-10-04 22:02:33 -05:00
Adrian Gonzales da6256def1 feat: Implement AudiencePlaceholder Builder 2025-10-01 15:48:02 -05:00
Adrian Gonzales 40fd7e7873 test: Improved Placeholders tests 2025-09-23 14:12:56 -05:00
Adrian Gonzales aa47088828 Merge branch 'main' into feat/complex-filter
# Conflicts:
#	api/src/main/java/io/github/miniplaceholders/api/types/RelationalAudience.java
2025-09-23 12:58:47 -05:00
Adrian Gonzales fff5acdb7b feat: Add complex-audience-filter 2025-09-05 00:50:10 -05:00
77 changed files with 1228 additions and 1486 deletions

No files matched your search

+6 -6
View File
@@ -8,24 +8,24 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Set up JDK 25
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: 25
distribution: 'zulu'
java-version: 21
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
-42
View File
@@ -1,42 +0,0 @@
<!--
MiniPlaceholders Changelog
-->
## 3.2.0 (unreleased)
### Features
- Added support to Fabric 26.1+
- Added Minestom support
### Fixes
### API Additions
## 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)
+2 -3
View File
@@ -12,10 +12,9 @@ MiniMessage Component-based Placeholders API for Minecraft Platforms.
## Compatibility
- Paper 1.21+
- Velocity 3.5+
- Fabric 26.1+
- Velocity 3.4+
- Fabric 1.21.6+
- Sponge API 12+
- Minestom 1.21.11+
## User Usage
+1 -2
View File
@@ -40,8 +40,7 @@ tasks {
o.encoding = Charsets.UTF_8.name()
o.links(
"https://jd.advntr.dev/api/${libs.versions.adventure.get()}/",
"https://jd.advntr.dev/text-minimessage/${libs.versions.adventure.get()}/",
"https://javadoc.io/doc/org.jspecify/jspecify/latest/"
"https://jd.advntr.dev/text-minimessage/${libs.versions.adventure.get()}/"
)
o.tags(
"apiNote:a:API Note:",
@@ -13,7 +13,7 @@ import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.Collection;
@@ -34,360 +34,390 @@ import java.util.Collection;
* TagResolver resolver = expansion.resolver();
* player.sendMessage(MiniMessage.miniMessage().deserialize("Hello {@literal <luckperms_prefix> <player_name>}", player, resolver));
* </pre>
*
*
* @since 1.0.0
*/
@NullMarked
public sealed interface Expansion permits ExpansionImpl {
/**
* Get the expansion name
*
* @return the expansion name
* @since 1.0.0
*/
String name();
/**
* Get the expansion name
*
* @return the expansion name
* @since 1.0.0
*/
String name();
/**
* Get the author of this expansion
*
* @return the author or null
* @since 2.3.0
*/
@Nullable String author();
/**
* Get the version of this expansion
*
* @return the version or null
* @since 2.3.0
*/
@Nullable String version();
/**
* Get the {@link TagResolver} of an {@link Audience}
*
* @return A TagResolver with variable placeholders of an Audience
* @since 1.0.0
*/
TagResolver audiencePlaceholders();
/**
* Get the relational placeholders based on two audiences
*
* <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>
*
* @return A TagResolver with variable placeholders between 2 {@link Audience}s
* @since 1.0.0
*/
TagResolver relationalPlaceholders();
/**
* Get global placeholders
*
* @return the global placeholders
* @since 1.0.0
*/
TagResolver globalPlaceholders();
/**
* Register this expansion
*
* @throws IllegalStateException if the expansion you are trying to register is already registered
* @see Expansion#registered()
* @since 1.0.0
*/
void register();
/**
* Unregisters this expansion
*
* @throws IllegalStateException if the expansion you are trying to unregister is not registered
* @see Expansion#registered()
* @since 2.1.0
*/
void unregister();
/**
* Get the registration status of this expansion
*
* @return true if the expansion is registered, otherwise false
* @since 2.1.0
*/
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) {
return new ExpansionImpl.Builder(name);
}
/**
* Get a shorter ToString with the details of this expansion.
*
* @return a shorter ToString
*/
@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>
* <pre>
* Expansion.Builder builder = Expansion.builder("player");
* 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())));
* Expansion expansion = builder.build();
* </pre>
* <p>
* then use it:
*
* <pre>
* Player player = event.getPlayer();
* TagResolver resolver = expansion.resolver(player);
* Component messageReplaced = MiniMessage.deserialize({@link String}, resolver);
* </pre>
*
* @since 1.0.0
*/
@NullMarked
interface Builder extends AbstractBuilder<Expansion> {
/**
* Get the author of this expansion
* Adds an audience placeholder
*
* @return the author or null
* <p>This type of Placeholder depends on a specific audience to obtain its values</p>
*
* <p>The content of this Placeholder is cached
* and can mutate depending on when it is invoked</p>
*
* @param targetClass the target class
* @param key the placeholder key, cannot be an empty or black string
* @param audiencePlaceholder the single placeholder
* @param <A> the type of audience that this placeholder supports, must be the same as targetClass
* @return the {@link Builder} itself
* @since 1.0.0
*/
<A extends Audience> Builder audiencePlaceholder(
final @Nullable Class<A> targetClass,
final String key,
final AudienceTagResolver<A> audiencePlaceholder
);
/**
* Adds an audience placeholder
*
* <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);
}
<A extends Audience> Builder audiencePlaceholder(
final Class<A> targetClass,
final AudiencePlaceholder.Builder.Provider<A> builderProvider
);
default Builder audiencePlaceholder(final AudiencePlaceholder.Builder.Provider<Audience> builderProvider) {
return audiencePlaceholder(Audience.class, builderProvider);
}
/**
* Adds a Relational Placeholder based on two audiences
*
* <p>This type of placeholder allows you to create
* components based on a 2-audiences relationship,
* one is the one on which the placeholder
* is based and the other is the one on which
* the placeholder is displayed.</p>
*
* <p>The content of this Placeholder is cached
* and can mutate depending on when it is invoked</p>
*
* @param targetClass the target class
* @param key the placeholder key, cannot be an empty or black string
* @param relationalPlaceholder the relational placeholder
* @param <A> the type of audience that this placeholder supports, must be the same as targetClass
* @return the {@link Builder} itself
* @since 3.0.0
*/
<A extends Audience> Builder relationalPlaceholder(
final @Nullable Class<A> targetClass,
final String key,
final RelationalTagResolver<A> relationalPlaceholder
);
/**
* Adds a Relational Placeholder based on two audiences
*
* <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);
}
<A extends Audience> Builder relationalPlaceholder(
final Class<A> targetClass,
final RelationalPlaceholder.Builder.Provider<A> builderProvider
);
default Builder relationalPlaceholder(final RelationalPlaceholder.Builder.Provider<Audience> builderProvider) {
return relationalPlaceholder(Audience.class, builderProvider);
}
/**
* Adds a global placeholder
*
* <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
* @return the {@link Builder} itself
* @since 3.0.0
*/
Builder globalPlaceholder(final String key, final GlobalTagResolver resolver);
/**
* Adds a global placeholder
*
* <p>This placeholder is not cached and is immutable</p>
*
* @param key the placeholder key, cannot be an empty or black string
* @param tag the tag of this placeholder
* @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);
}
/**
* Sets the author of this expansion
*
* @param author the author of this expansion
* @return the {@link Builder} itself
* @since 2.3.0
*/
@Nullable String author();
@Contract("_ -> this")
Builder author(final @Nullable String author);
/**
* Get the version of this expansion
* Sets the version of this expansion
*
* @return the version or null
* @param version the version of this expansion
* @return the {@link Builder} itself
* @since 2.3.0
*/
@Nullable String version();
/**
* Get the {@link TagResolver} of an {@link Audience}
* @return A TagResolver with variable placeholders of an Audience
* @since 1.0.0
*/
TagResolver audiencePlaceholders();
/**
* Get the relational placeholders based on two audiences
*
* <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>
* @return A TagResolver with variable placeholders between 2 {@link Audience}s
* @since 1.0.0
*/
TagResolver relationalPlaceholders();
/**
* Get global placeholders
* @return the global placeholders
* @since 1.0.0
*/
TagResolver globalPlaceholders();
/**
* Register this expansion
* @since 1.0.0
* @throws IllegalStateException if the expansion you are trying to register is already registered
* @see Expansion#registered()
*/
void register();
/**
* Unregisters this expansion
* @since 2.1.0
* @throws IllegalStateException if the expansion you are trying to unregister is not registered
* @see Expansion#registered()
*/
void unregister();
/**
* Get the registration status of this expansion
* @return true if the expansion is registered, otherwise false
* @since 2.1.0
*/
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){
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>
* <pre>
* Expansion.Builder builder = Expansion.builder("player");
* 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())));
* Expansion expansion = builder.build();
* </pre>
*
* then use it:
*
* <pre>
* Player player = event.getPlayer();
* TagResolver resolver = expansion.resolver(player);
* Component messageReplaced = MiniMessage.deserialize({@link String}, resolver);
* </pre>
*
* @since 1.0.0
*/
@NullMarked
interface Builder extends AbstractBuilder<Expansion> {
/**
* Adds an audience placeholder
*
* <p>This type of Placeholder depends on a specific audience to obtain its values</p>
*
* <p>The content of this Placeholder is cached
* and can mutate depending on when it is invoked</p>
*
* @param targetClass the target class
* @param key the placeholder key, cannot be an empty or black string
* @param audiencePlaceholder the single placeholder
* @param <A> the type of audience that this placeholder supports, must be the same as targetClass
* @return the {@link Builder} itself
* @since 1.0.0
*/
<A extends Audience> Builder audiencePlaceholder(
final @Nullable Class<A> targetClass,
final String key,
final AudienceTagResolver<A> audiencePlaceholder
);
/**
* Adds an audience placeholder
*
* <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);
}
/**
* Adds a Relational Placeholder based on two audiences
*
* <p>This type of placeholder allows you to create
* components based on a 2-audiences relationship,
* one is the one on which the placeholder
* is based and the other is the one on which
* the placeholder is displayed.</p>
*
* <p>The content of this Placeholder is cached
* and can mutate depending on when it is invoked</p>
*
* @param targetClass the target class
* @param key the placeholder key, cannot be an empty or black string
* @param relationalPlaceholder the relational placeholder
* @param <A> the type of audience that this placeholder supports, must be the same as targetClass
* @return the {@link Builder} itself
* @since 3.0.0
*/
<A extends Audience> Builder relationalPlaceholder(
final @Nullable Class<A> targetClass,
final String key,
final RelationalTagResolver<A> relationalPlaceholder
);
/**
* Adds a Relational Placeholder based on two audiences
*
* <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);
}
/**
* Adds a global placeholder
*
* <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
* @return the {@link Builder} itself
* @since 3.0.0
*/
Builder globalPlaceholder(final String key, final GlobalTagResolver resolver);
/**
* Adds a global placeholder
*
* <p>This placeholder is not cached and is immutable</p>
* @param key the placeholder key, cannot be an empty or black string
* @param tag the tag of this placeholder
* @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);
}
/**
* Sets the author of this expansion
*
* @param author the author of this expansion
* @return the {@link Builder} itself
* @since 2.3.0
*/
@Contract("_ -> this")
Builder author(final @Nullable String author);
/**
* Sets the version of this expansion
*
* @param version the version of this expansion
* @return the {@link Builder} itself
* @since 2.3.0
*/
@Contract("_ -> this")
Builder version(final @Nullable String version);
}
@Contract("_ -> this")
Builder version(final @Nullable String version);
}
}
@@ -8,8 +8,9 @@ import io.github.miniplaceholders.api.types.PlaceholderType;
import net.kyori.adventure.audience.Audience;
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.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import java.util.*;
@@ -17,6 +18,7 @@ import java.util.*;
import static io.github.miniplaceholders.api.utils.Conditions.nonNullOrEmptyString;
import static java.util.Objects.requireNonNull;
@NullMarked
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];
@@ -32,23 +34,23 @@ final class ExpansionImpl implements Expansion {
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
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;
? new PlaceholderTagResolver(audiencePlaceholders.toArray(EMPTY_SINGLE_AUDIENCE))
: PlaceholderTagResolver.EMPTY;
this.relationalPlaceholders = relationalPlaceholders != null
? new PlaceholderTagResolver(relationalPlaceholders.toArray(EMPTY_RELATIONAL_AUDIENCE))
: PlaceholderTagResolver.EMPTY;
? new PlaceholderTagResolver(relationalPlaceholders.toArray(EMPTY_RELATIONAL_AUDIENCE))
: PlaceholderTagResolver.EMPTY;
this.globalPlaceholders = globalPlaceholders == null
? PlaceholderTagResolver.EMPTY
: new PlaceholderTagResolver(globalPlaceholders.toArray(EMPTY_GLOBAL_PLACEHOLDER));
? PlaceholderTagResolver.EMPTY
: new PlaceholderTagResolver(globalPlaceholders.toArray(EMPTY_GLOBAL_PLACEHOLDER));
this.author = author;
this.version = version;
}
@@ -162,7 +164,7 @@ final class ExpansionImpl implements Expansion {
}
@NullUnmarked
static final class Builder implements Expansion.Builder {
public static final class Builder implements Expansion.Builder {
private final String expansionName;
@Subst("server")
private final String expansionPrefix;
@@ -172,31 +174,49 @@ final class ExpansionImpl implements Expansion {
private String author;
private String version;
Builder(final @NonNull String name) {
Builder(@NotNull final String name) {
this.expansionName = nonNullOrEmptyString(name, "Expansion name");
this.expansionPrefix = name.toLowerCase(Locale.ROOT).concat("_");
}
@Override
public <A extends Audience> Expansion.Builder audiencePlaceholder(
final Class<A> targetClass,
final @NotNull @Subst("placeholder") String key,
final @NotNull AudienceTagResolver<@NotNull A> audiencePlaceholder
) {
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(audiencePlaceholder, "the audience placeholder cannot be null");
if (this.audiencePlaceholders == null) this.audiencePlaceholders = new HashSet<>();
this.audiencePlaceholders.add(AudiencePlaceholder.single(targetClass, expansionPrefix + key, key, audiencePlaceholder));
return this;
}
@Override
public <A extends Audience> @NonNull Builder audiencePlaceholder(
final @Nullable Class<A> targetClass,
final @Subst("name") @NonNull String key,
final @NonNull AudienceTagResolver<@NonNull A> audiencePlaceholder
public <A extends Audience> Expansion.Builder audiencePlaceholder(
final @NotNull Class<A> targetClass,
final @NotNull AudiencePlaceholder.Builder.Provider<A> builderProvider
) {
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(audiencePlaceholder, "the audience placeholder cannot be null");
requireNonNull(targetClass, "target class");
requireNonNull(builderProvider, "builder");
if (this.audiencePlaceholders == null) this.audiencePlaceholders = new HashSet<>();
this.audiencePlaceholders.add(AudiencePlaceholder.single(targetClass, expansionPrefix + key, key, audiencePlaceholder));
final AudiencePlaceholder.Builder<A> builder = AudiencePlaceholder.builder(targetClass);
builderProvider.provide(builder);
final AudiencePlaceholder<@NotNull A> placeholder = builder.build(expansionPrefix);
this.audiencePlaceholders.add(placeholder);
return this;
}
@Override
public <A extends Audience> @NonNull Builder relationalPlaceholder(
final Class<A> targetClass,
final @Subst("relation") @NonNull String key,
final @NonNull RelationalTagResolver<@NonNull A> relationalPlaceholder
public <A extends Audience> @NotNull Builder relationalPlaceholder(
@Nullable Class<A> targetClass,
@Subst("relation") @NotNull String key,
@NotNull RelationalTagResolver<@NotNull A> relationalPlaceholder
) {
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(relationalPlaceholder, "the relational placeholder cannot be null");
@@ -209,9 +229,27 @@ final class ExpansionImpl implements Expansion {
}
@Override
public @NonNull Builder globalPlaceholder(
final @Subst("time") @NonNull String key,
final @NonNull GlobalTagResolver function
public <A extends Audience> Expansion.Builder relationalPlaceholder(
final @NotNull Class<A> targetClass,
final @NotNull RelationalPlaceholder.Builder.Provider<A> builderProvider
) {
requireNonNull(targetClass, "target class");
requireNonNull(builderProvider, "builder");
if (this.relationalPlaceholders == null) this.relationalPlaceholders = new HashSet<>();
final RelationalPlaceholder.Builder<A> builder = RelationalPlaceholder.builder(targetClass);
builderProvider.provide(builder);
final RelationalPlaceholder<@NotNull A> placeholder = builder.build(expansionPrefix);
this.relationalPlaceholders.add(placeholder);
return this;
}
@Override
public @NotNull Builder globalPlaceholder(
@Subst("time") @NotNull final String key,
@NotNull final GlobalTagResolver function
) {
nonNullOrEmptyString(key, "Placeholder key");
requireNonNull(function, "the global placeholder cannot be null");
@@ -224,26 +262,26 @@ final class ExpansionImpl implements Expansion {
}
@Override
public @NonNull Builder author(final @Nullable String author) {
public Expansion.@NotNull Builder author(@Nullable final String author) {
this.author = author;
return this;
}
@Override
public @NonNull Builder version(final @Nullable String version) {
public Expansion.@NotNull Builder version(@Nullable final String version) {
this.version = version;
return this;
}
@Override
public @NonNull Expansion build() {
public @NotNull Expansion build() {
return new ExpansionImpl(
this.expansionName,
this.audiencePlaceholders,
this.relationalPlaceholders,
this.globalPlaceholders,
this.author,
this.version
this.expansionName,
this.audiencePlaceholders,
this.relationalPlaceholders,
this.globalPlaceholders,
this.author,
this.version
);
}
}
@@ -264,12 +302,12 @@ final class ExpansionImpl implements Expansion {
@Override
public String toString() {
return "ExpansionImpl{" +
"name='" + name + '\'' +
", audiencePlaceholders=" + audiencePlaceholders +
", relationalPlaceholders=" + relationalPlaceholders +
", globalPlaceholders=" + globalPlaceholders +
", author='" + author + '\'' +
", version='" + version + '\'' +
'}';
"name='" + name + '\'' +
", audiencePlaceholders=" + audiencePlaceholders +
", relationalPlaceholders=" + relationalPlaceholders +
", globalPlaceholders=" + globalPlaceholders +
", author='" + author + '\'' +
", version='" + version + '\'' +
'}';
}
}
@@ -6,8 +6,9 @@ import io.github.miniplaceholders.api.types.RelationalAudience;
import io.github.miniplaceholders.connect.InternalPlatform;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
@@ -39,13 +40,12 @@ public final class MiniPlaceholders {
* @return the platform
* @since 3.0.0
*/
public static Platform platform() {
public static @NotNull Platform platform() {
return switch (InternalPlatform.platform()) {
case PAPER -> Platform.PAPER;
case VELOCITY -> Platform.VELOCITY;
case FABRIC -> Platform.FABRIC;
case SPONGE -> Platform.SPONGE;
case MINESTOM -> Platform.MINESTOM;
};
}
@@ -59,7 +59,7 @@ public final class MiniPlaceholders {
* @see TagResolver
* @since 3.0.0
*/
public static TagResolver globalPlaceholders() {
public static @NotNull TagResolver globalPlaceholders() {
final TagResolver.Builder builder = TagResolver.builder();
for (final Expansion expansion : expansions) {
final TagResolver resolver = expansion.globalPlaceholders();
@@ -80,7 +80,7 @@ public final class MiniPlaceholders {
* @return {@link TagResolver} with placeholders based on an audience
* @since 3.0.0
*/
public static TagResolver audiencePlaceholders() {
public static @NotNull TagResolver audiencePlaceholders() {
final TagResolver.Builder resolvers = TagResolver.builder();
for (final Expansion expansion : expansions) {
final TagResolver resolver = expansion.audiencePlaceholders();
@@ -104,7 +104,7 @@ public final class MiniPlaceholders {
* @since 3.0.0
* @see RelationalAudience
*/
public static TagResolver relationalPlaceholders() {
public static @NotNull TagResolver relationalPlaceholders() {
final TagResolver.Builder builder = TagResolver.builder();
for (final Expansion expansion : expansions) {
applyIfNotEmpty(expansion.relationalPlaceholders(), builder);
@@ -133,7 +133,7 @@ public final class MiniPlaceholders {
* @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 audienceGlobalPlaceholders() {
final TagResolver.Builder builder = TagResolver.builder();
for (final Expansion expansion : expansions) {
@@ -165,7 +165,7 @@ public final class MiniPlaceholders {
* @apiNote In the case of audience placeholders, the audience to be used will be the {@link RelationalAudience#audience()}
* @see RelationalAudience
*/
public static TagResolver relationalGlobalPlaceholders() {
public static @NotNull TagResolver relationalGlobalPlaceholders() {
final TagResolver.Builder builder = TagResolver.builder();
for (final Expansion expansion : expansions) {
applyIfNotEmpty(expansion.audiencePlaceholders(), builder);
@@ -222,7 +222,7 @@ public final class MiniPlaceholders {
* @see Expansion#builder(String)
* @since 3.0.0
*/
public static @Nullable Expansion expansionByName(final String name) {
public static @Nullable Expansion expansionByName(final @NotNull String name) {
for (final Expansion expansion : expansions) {
if (Objects.equals(expansion.name(), name)) {
return expansion;
@@ -8,45 +8,90 @@ 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.intellij.lang.annotations.Subst;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import java.util.Objects;
import java.util.function.Predicate;
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
* @param targetClass the type of audience from which this placeholder can obtain information
* @param targetFilter the filter that will be applied to the audience before being able to evaluate its return tag
* @param key the full placeholder key
* @param name the name of this placeholder
* @param resolver the object responsible for obtaining audience information
* @param <A> the parametrized type
*/
@NullMarked
public record AudiencePlaceholder<A extends Audience>(
@Nullable Class<A> targetClass,
@TagPattern String key,
String name,
AudienceTagResolver<A> resolver
@Nullable Class<A> targetClass,
@Nullable Predicate<A> targetFilter,
@TagPattern String key,
@TagPattern String name,
AudienceTagResolver<A> resolver,
@Nullable PlaceholderMetadata placeholderMetadata
) implements Placeholder {
/**
* 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
* @since 3.0.0
*/
public AudiencePlaceholder(
final @Nullable Class<A> targetClass,
final @TagPattern String key,
final @TagPattern String name,
final AudienceTagResolver<A> resolver
) {
this(targetClass, null, key, name, resolver, null);
}
/**
* 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
* @param key the placeholder key
* @param name the name of this placeholder
* @param resolver the object responsible for obtaining audience information
* @param <A> the parametrized type
* @return a new AudiencePlaceholder
* @param <A> the parametrized type
* @since 3.0.0
*/
public static <A extends Audience> AudiencePlaceholder<A> single(
@Nullable Class<A> targetClass,
@TagPattern String key,
String name,
AudienceTagResolver<A> resolver
@Nullable Class<A> targetClass,
@TagPattern String key,
@TagPattern String name,
AudienceTagResolver<A> resolver
) {
return new AudiencePlaceholder<>(targetClass, requireNonNull(key), requireNonNull(name), requireNonNull(resolver));
return new AudiencePlaceholder<>(
targetClass,
null,
requireNonNull(key),
requireNonNull(name),
requireNonNull(resolver),
null
);
}
public static <A extends Audience> Builder<A> builder(final Class<A> clazz) {
requireNonNull(clazz);
return new Builder<>(clazz);
}
public static Builder<Audience> builder() {
return new Builder<>(Audience.class);
}
@Override
@@ -63,13 +108,16 @@ public record AudiencePlaceholder<A extends Audience>(
if (audience == null) {
return null;
}
if (targetFilter != null && !targetFilter.test(audience)) {
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;
? resolver.tag(audience, queue, ctx)
: null;
}
@SuppressWarnings("OverrideOnly")
@@ -104,7 +152,63 @@ public record AudiencePlaceholder<A extends Audience>(
@Override
public String toString() {
return "AudiencePlaceholder{" +
"key='" + key + '\'' +
'}';
"key='" + key + '\'' +
'}';
}
@NullUnmarked
public static final class Builder<A extends Audience> {
@NotNull
private final Class<A> targetClass;
@TagPattern
private String name;
@Nullable
private Predicate<A> targetFilter;
private AudienceTagResolver<@NotNull A> resolver;
@Nullable
private PlaceholderMetadata metadata;
Builder(final @NotNull Class<A> targetClass) {
this.targetClass = targetClass;
}
public Builder<A> name(final @NonNull @TagPattern String name) {
this.name = requireNonNull(name, "placeholder name");
return this;
}
public Builder<A> targetFilter(final Predicate<A> targetFilter) {
this.targetFilter = targetFilter;
return this;
}
public Builder<A> resolver(final @NotNull AudienceTagResolver<@NotNull A> resolver) {
this.resolver = requireNonNull(resolver);
return this;
}
public Builder<A> metadata(final PlaceholderMetadata metadata) {
this.metadata = metadata;
return this;
}
public @NonNull AudiencePlaceholder<@NonNull A> build(final @NotNull @Subst("expansion") String expansionPrefix) {
requireNonNull(this.name, "name");
requireNonNull(this.resolver, "resolver");
return new AudiencePlaceholder<>(
targetClass,
targetFilter,
expansionPrefix+name,
name,
resolver,
metadata
);
}
@FunctionalInterface
public interface Provider<A extends Audience> {
void provide(Builder<A> builderProvider);
}
}
}
@@ -5,7 +5,8 @@ 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 org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
@@ -19,17 +20,17 @@ import java.util.Objects;
*/
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) {
public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) {
return this.has(name) ? resolver.tag(arguments, ctx) : null;
}
@Override
public boolean has(final String name) {
public boolean has(@NotNull String name) {
return key.equalsIgnoreCase(name);
}
@Override
public boolean equals(final Object obj) {
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
@@ -1,10 +1,12 @@
package io.github.miniplaceholders.api.placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jspecify.annotations.NullMarked;
/**
* Base class of all Placeholders
*/
@NullMarked
public sealed interface Placeholder extends TagResolver permits AudiencePlaceholder, GlobalPlaceholder, RelationalPlaceholder {
/**
* The key of this placeholder.<br>
@@ -0,0 +1,17 @@
package io.github.miniplaceholders.api.placeholder;
import org.jspecify.annotations.NullMarked;
import static java.util.Objects.requireNonNull;
@NullMarked
public record PlaceholderMetadata(String displayName, String description) {
public PlaceholderMetadata {
requireNonNull(displayName);
requireNonNull(description);
}
public static PlaceholderMetadata data(final String displayName, final String description) {
return new PlaceholderMetadata(displayName, description);
}
}
@@ -5,6 +5,7 @@ import net.kyori.adventure.text.minimessage.ParsingException;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -15,7 +16,7 @@ public record PlaceholderTagResolver(Placeholder... placeholders) implements Tag
public static final PlaceholderTagResolver EMPTY = new PlaceholderTagResolver();
@Override
public @Nullable Tag resolve(final String name, final ArgumentQueue arguments, final Context ctx) throws ParsingException {
public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) throws ParsingException {
@Nullable Tag tag;
for (final Placeholder placeholder : placeholders) {
tag = placeholder.resolve(name, arguments, ctx);
@@ -27,7 +28,7 @@ public record PlaceholderTagResolver(Placeholder... placeholders) implements Tag
}
@Override
public boolean has(final String name) {
public boolean has(final @NotNull String name) {
for (final Placeholder placeholder : placeholders) {
if (placeholder.has(name)) {
return true;
@@ -1,7 +1,7 @@
package io.github.miniplaceholders.api.placeholder;
import io.github.miniplaceholders.api.types.RelationalAudience;
import io.github.miniplaceholders.api.resolver.RelationalTagResolver;
import io.github.miniplaceholders.api.types.RelationalAudience;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import net.kyori.adventure.pointer.Pointered;
@@ -9,9 +9,15 @@ 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.intellij.lang.annotations.Subst;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import java.util.Objects;
import java.util.function.Predicate;
import static java.util.Objects.requireNonNull;
@@ -32,13 +38,25 @@ import static java.util.Objects.requireNonNull;
* the {@linkplain #key()} method will return {@code server_name}.
* @see RelationalTagResolver
*/
@NullMarked
public record RelationalPlaceholder<A extends Audience>(
@Nullable Class<A> targetClass,
@Nullable Predicate<A> targetFilter,
@TagPattern String key,
String name,
RelationalTagResolver<A> resolver
@TagPattern String name,
RelationalTagResolver<A> resolver,
@Nullable PlaceholderMetadata placeholderMetadata
) implements Placeholder {
public RelationalPlaceholder(
@Nullable Class<A> targetClass,
@TagPattern String key,
@TagPattern String name,
RelationalTagResolver<A> resolver
) {
this(targetClass, null, key, name, resolver, null);
}
/**
* Static method capable of creating an instance of a relational placeholder.
*
@@ -52,16 +70,24 @@ public record RelationalPlaceholder<A extends Audience>(
* @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
@Nullable Class<A> targetClass,
@TagPattern String key,
@TagPattern String name,
RelationalTagResolver<A> resolver
) {
return new RelationalPlaceholder<>(targetClass, requireNonNull(key), name, requireNonNull(resolver));
}
public static <A extends Audience> Builder<A> builder(Class<A> targetClazz) {
return new Builder<>(targetClazz);
}
public static Builder<Audience> builder() {
return new Builder<>(Audience.class);
}
@Override
public @Nullable Tag resolve(final String name, final ArgumentQueue arguments, final Context ctx) {
public @Nullable Tag resolve(String name, ArgumentQueue arguments, Context ctx) {
final Pointered targetRaw = ctx.target();
if (targetRaw == null) {
return null;
@@ -81,15 +107,18 @@ public record RelationalPlaceholder<A extends Audience>(
if (relationalAudience == null) {
return null;
}
if (targetFilter != null && (!targetFilter.test(audience) || !targetFilter.test(relationalAudience))) {
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
String name,
A audience,
A relationalAudience,
ArgumentQueue arguments,
Context ctx
) {
return this.has(name)
? resolver.tag(audience, relationalAudience, arguments, ctx)
@@ -109,7 +138,7 @@ public record RelationalPlaceholder<A extends Audience>(
}
@Override
public boolean has(final String name) {
public boolean has(String name) {
return key.equalsIgnoreCase(name);
}
@@ -131,4 +160,59 @@ public record RelationalPlaceholder<A extends Audience>(
"key='" + key + '\'' +
'}';
}
@NullUnmarked
public static final class Builder<A extends Audience> {
@NotNull
private final Class<A> targetClass;
@TagPattern
private String name;
@Nullable
private Predicate<A> targetFilter;
private RelationalTagResolver<@NotNull A> resolver;
@Nullable
private PlaceholderMetadata metadata;
Builder(final @NotNull Class<A> targetClass) {
this.targetClass = targetClass;
}
public Builder<A> name(final @NonNull @TagPattern String name) {
this.name = requireNonNull(name, "placeholder name");
return this;
}
public Builder<A> targetFilter(final Predicate<A> targetFilter) {
this.targetFilter = targetFilter;
return this;
}
public Builder<A> resolver(final @NotNull RelationalTagResolver<@NotNull A> resolver) {
this.resolver = requireNonNull(resolver);
return this;
}
public Builder<A> metadata(final PlaceholderMetadata metadata) {
this.metadata = metadata;
return this;
}
public @NonNull RelationalPlaceholder<@NonNull A> build(final @NotNull @Subst("expansion") String expansionPrefix) {
requireNonNull(this.name, "name");
requireNonNull(this.resolver, "resolver");
return new RelationalPlaceholder<>(
targetClass,
targetFilter,
expansionPrefix + name,
name,
resolver,
metadata
);
}
@FunctionalInterface
public interface Provider<A extends Audience> {
void provide(final Builder<A> builderProvider);
}
}
}
@@ -5,8 +5,9 @@ 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 java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Objects.requireNonNull;
@@ -16,6 +17,7 @@ import static java.util.Objects.requireNonNull;
*
* @since 3.0.0
*/
@NullMarked
public sealed interface LoadRequirement {
/**
@@ -160,12 +162,6 @@ public sealed interface LoadRequirement {
@ApiStatus.Internal
@NullMarked
record MultiLoadRequirement(LoadRequirement... requirements) implements LoadRequirement {
@Override
public String toString() {
return "MultiLoadRequirement{" +
"requirements=" + Arrays.toString(requirements) +
'}';
}
}
/**
@@ -179,8 +175,11 @@ public sealed interface LoadRequirement {
record PlatformRequirement(Platform... platforms) implements LoadRequirement {
@Override
public String toString() {
final String platforms = Stream.of(this.platforms)
.map(Platform::name)
.collect(Collectors.joining(", "));
return "PlatformRequirement{" +
"platforms=" + Arrays.toString(platforms()) +
"platforms=" + platforms +
'}';
}
}
@@ -213,11 +212,5 @@ public sealed interface LoadRequirement {
@ApiStatus.Internal
@NullMarked
record AnyOfRequirement(LoadRequirement... requirements) implements LoadRequirement {
@Override
public String toString() {
return "AnyOfRequirement{" +
"requirements=" + Arrays.toString(requirements) +
'}';
}
}
}
@@ -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,10 +1,12 @@
package io.github.miniplaceholders.api.resolver;
import org.jetbrains.annotations.Nullable;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import org.jspecify.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
/**
* Audience Placeholder based on an Audience
@@ -12,6 +14,7 @@ import org.jspecify.annotations.Nullable;
* @param <A> the audience type
*/
@FunctionalInterface
@NullMarked
public interface AudienceTagResolver<A extends Audience> {
/**
* A Tag based on an Audience
@@ -3,6 +3,7 @@ package io.github.miniplaceholders.api.resolver;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
/**
@@ -19,5 +20,5 @@ public interface GlobalTagResolver {
* @since 3.0.0
*/
@Nullable
Tag tag(ArgumentQueue queue, Context context);
Tag tag(@NonNull ArgumentQueue queue, @NonNull Context context);
}
@@ -4,7 +4,8 @@ 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;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
/**
* Relational Placeholder based on two Audiences.
@@ -14,6 +15,7 @@ import org.jspecify.annotations.Nullable;
* @since 3.0.0
*/
@FunctionalInterface
@NullMarked
public interface RelationalTagResolver<A extends Audience> {
/**
* A Tag based on two audiences
@@ -29,11 +29,5 @@ public enum Platform {
*
* @since 2.2.0
*/
SPONGE,
/**
* Minestom Platform
*
* @since 3.2.0
*/
MINESTOM,
SPONGE
}
@@ -2,33 +2,34 @@ package io.github.miniplaceholders.api.types;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import org.jspecify.annotations.NullMarked;
import static java.util.Objects.requireNonNull;
/**
* A relationship between 2 Audiences.
*
* @param audience the principal audience
* @param audience the principal audience
* @param relational the secondary audience, with which the relationship will be evaluated
* @param <A> the audiences type
* @param <A> the audiences type
* @since 3.0.0
*/
@NullMarked
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);
}
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
* @param <A> the audiences type
* @return a new relation
* @since 3.0.0
*/
public static <A extends Audience> RelationalAudience<A> from(A audience, A relational) {
return new RelationalAudience<>(audience, relational);
}
}
@@ -1,4 +1,2 @@
/**
* Plugin types
*/
/**Plugin enums */
package io.github.miniplaceholders.api.types;
@@ -4,9 +4,7 @@ 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(){}
/**
@@ -1,6 +1,7 @@
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;
@@ -22,7 +23,7 @@ public final class Conditions {
* @throws IllegalStateException if the provided string is empty
* @since 1.0.0
*/
public static String nonNullOrEmptyString(final @Nullable String string, final Supplier<String> name) {
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())
@@ -39,7 +40,7 @@ public final class Conditions {
* @throws IllegalStateException if the provided string is empty
* @since 2.2.0
*/
public static String nonNullOrEmptyString(final @Nullable String string, final String name) {
public static String nonNullOrEmptyString(@Nullable String string, @NotNull String name) {
if (string == null)
throw new NullPointerException(name + " cannot be null");
if (string.isBlank())
@@ -56,7 +57,7 @@ public final class Conditions {
* @throws IllegalStateException if the provided string is empty
* @since 1.0.0
*/
public static String nonEmptyString(final String string, final Supplier<String> reason) {
public static String nonEmptyString(@NotNull String string, @NotNull Supplier<@NotNull String> reason) {
if (string.isBlank()) throw new IllegalStateException(reason.get());
return string;
}
@@ -70,7 +71,7 @@ public final class Conditions {
* @throws IllegalStateException if the provided string is empty
* @since 2.2.0
*/
public static String nonEmptyString(final String string, final String reason) {
public static String nonEmptyString(@NotNull String string, @NotNull String reason) {
if (string.isBlank()) throw new IllegalStateException(reason);
return string;
}
@@ -4,7 +4,8 @@ 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 org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.function.Function;
@@ -16,6 +17,7 @@ import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
*
* @since 3.0.0
*/
@NullMarked
public final class LegacyStrings {
private LegacyStrings() {
}
@@ -24,10 +26,7 @@ public final class 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()
@@ -1,12 +1,14 @@
package io.github.miniplaceholders.api.utils;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jspecify.annotations.NullMarked;
/**
* Class of utilities to manage TagResolvers.
*
* @since 1.2.0
*/
@NullMarked
public final class Resolvers {
private Resolvers() {}
@@ -6,7 +6,7 @@ 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;
import org.jspecify.annotations.NonNull;
/**
* Tags basic instances.
@@ -29,7 +29,7 @@ public final class Tags {
*
* @since 3.0.0
*/
public static final @Nullable Tag NULL_TAG = null;
public static final Tag NULL_TAG = null;
/**
* An Audience placeholder that returns a null Tag
@@ -38,7 +38,7 @@ public final class Tags {
* @return an empty audience resolver
* @since 3.0.0
*/
public static <A extends Audience> AudienceTagResolver<A> emptyAudienceResolver() {
public static <A extends Audience> AudienceTagResolver<@NonNull A> emptyAudienceResolver() {
return (audience, queue, ctx) -> NULL_TAG;
}
@@ -49,10 +49,17 @@ public final class Tags {
* @return an empty relational resolver
* @since 3.0.0
*/
public static <A extends Audience> RelationalTagResolver<A> emptyRelationalResolver() {
public static <A extends Audience> RelationalTagResolver<@NonNull A> emptyRelationalResolver() {
return (audience, relational, queue, ctx) -> NULL_TAG;
}
/**
* A Global placeholder that returns a null tag
*
* @since 3.0.0
*/
private static final GlobalTagResolver EMPTY_GLOBAL_PLACEHOLDER = (queue, ctx) -> NULL_TAG;
/**
* A Global placeholder that returns a null tag
*
@@ -60,7 +67,7 @@ public final class Tags {
* @return an empty global placeholder
*/
public static GlobalTagResolver emptyGlobalPlaceholder() {
return (queue, ctx) -> NULL_TAG;
return EMPTY_GLOBAL_PLACEHOLDER;
}
}
-3
View File
@@ -1,9 +1,6 @@
import org.jspecify.annotations.NullMarked;
/**
* MiniPlaceholders API Module
*/
@NullMarked
open module io.github.miniplaceholders.api {
requires io.github.miniplaceholders.connect;
@@ -0,0 +1,32 @@
package io.github.miniplaceholders.test;
import io.github.miniplaceholders.api.Expansion;
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 net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ArgumentTest implements MiniTest {
@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());
assertContentEquals(parsed, Component.text("Arguments: 2"));
}
}
@@ -14,13 +14,13 @@ import static org.junit.jupiter.api.Assertions.*;
class ExpansionsTest implements MiniTest {
@Test
@DisplayName("Expansion Equality")
void equalsExpansions(){
void equalsExpansions() {
final Expansion firstExpansion = Expansion.builder("equals")
.audiencePlaceholder("test", Tags.emptyAudienceResolver())
.build();
.audiencePlaceholder("test", Tags.emptyAudienceResolver())
.build();
final Expansion secondExpansion = Expansion.builder("equals")
.audiencePlaceholder("test", Tags.emptyAudienceResolver())
.build();
.audiencePlaceholder("test", Tags.emptyAudienceResolver())
.build();
assertEquals(firstExpansion, secondExpansion);
}
@@ -1,83 +1,22 @@
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 org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.MiniPlaceholders;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.Tag;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
class PlaceholderTest implements MiniTest {
@Test
@DisplayName("Audience Placeholder Test")
void audienceExpansionPlaceholdersTest() {
TestAudience player = new TestAudience("4drian3d");
Expansion expansion = Expansion.builder("example")
.audiencePlaceholder("name", (aud, queue, ctx) -> {
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());
assertContentEquals(expected, result);
}
@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)
))
.build();
assertEquals(
MiniMessage.miniMessage().deserialize("You are <red>Enemy"),
MiniMessage.miniMessage().deserialize("You are <relational_rel_enemy>",
RelationalAudience.from(p1, p2),
expansion.relationalPlaceholders()));
}
@Test
@DisplayName("Global Placeholder Test")
void globalExpansionPlaceholderTest() {
Expansion expansion = Expansion.builder("global")
.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());
assertContentEquals(expected, actual);
}
@Test
@DisplayName("Registered Placeholders")
void instancePlaceholdersTest() {
Expansion.builder("instance")
.globalPlaceholder("hello", (queue, ctx) -> Tag.selfClosingInserting(Component.text("hello", NamedTextColor.RED)))
.globalPlaceholder("hello", (queue, ctx) ->
Tag.selfClosingInserting(Component.text("hello", NamedTextColor.RED)))
.build()
.register();
@@ -87,65 +26,4 @@ class PlaceholderTest implements MiniTest {
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())))
.build();
Audience player = new TestAudience("TestPlayer04");
Audience emptyAudience = Audience.empty();
String string = "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()));
}
@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());
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();
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.");
assertContentEquals(parsed, expected);
}
private boolean isEnemy(TestAudience a1, TestAudience a2) {
return a1.name().equals("PlayerOne") && a2.name().equals("PlayerTwo");
}
}
@@ -10,9 +10,10 @@ 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.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TagTest implements MiniTest {
@@ -34,7 +35,7 @@ public class TagTest implements MiniTest {
public record FilteringTag(ArgumentQueue queue, Context context) implements Modifying {
@Override
public Component apply(@NonNull Component current, int depth) {
public Component apply(@NotNull Component current, int depth) {
if (depth != 0) {
return Component.empty();
}
@@ -1,7 +1,7 @@
package io.github.miniplaceholders.test.instances;
import net.kyori.adventure.audience.Audience;
import org.jspecify.annotations.NullMarked;
import org.jetbrains.annotations.NotNull;
@NullMarked
public record TestAudience(String name) implements Audience {}
import net.kyori.adventure.audience.Audience;
public record TestAudience(@NotNull String name) implements Audience {}
@@ -1,12 +1,11 @@
package io.github.miniplaceholders.test.instances;
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;
}
}
@@ -0,0 +1,93 @@
package io.github.miniplaceholders.test.placeholder;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.placeholder.PlaceholderMetadata;
import io.github.miniplaceholders.test.MiniTest;
import io.github.miniplaceholders.test.instances.TestAudience;
import io.github.miniplaceholders.test.instances.TestAudienceHolder;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.Tag;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
public class AudiencePlaceholderTest implements MiniTest {
@Test
@DisplayName("Audience Placeholder Test")
void audienceExpansionPlaceholdersTest() {
TestAudience player = new TestAudience("4drian3d");
Expansion expansion = Expansion.builder("example")
.audiencePlaceholder("name", (aud, queue, ctx) -> {
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());
assertContentEquals(expected, result);
}
@Test
@DisplayName("Forwarding Audience Parsing Test")
void forwardingAudienceTest() {
Expansion expansion = Expansion.builder("test")
.audiencePlaceholder(TestAudience.class, "testing",
(audience, queue, ctx) -> Tag.selfClosingInserting(Component.text("Name: " + audience.name())))
.build();
TestAudience testAudience = new TestAudience("4drian3d");
Audience forward = new TestAudienceHolder(testAudience);
Component parsed = miniMessage().deserialize("<test_testing>.", forward, expansion.audiencePlaceholders());
Component expected = Component.text("Name: 4drian3d.");
assertContentEquals(parsed, expected);
}
@Test
@DisplayName("Filtered Expansion")
void filteredExpansion() {
Expansion expansion = Expansion.builder("filter")
.audiencePlaceholder(TestAudience.class, "name",
(aud, queue, ctx) -> Tag.selfClosingInserting(Component.text(aud.name())))
.build();
Audience player = new TestAudience("TestPlayer04");
Audience emptyAudience = Audience.empty();
String string = "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()));
}
@Test
@DisplayName("AudiencePlaceholder Builder creation")
void builderAudiencePlaceholderCreation() {
final Expansion expansion = Expansion.builder("builder")
.audiencePlaceholder(
TestAudience.class,
builder -> builder.name("test")
.resolver((audience, queue, ctx) -> {
final String name = audience.name();
return Tag.preProcessParsed(name);
})
.metadata(PlaceholderMetadata.data("Builder Test", "Returns the Audience Name"))
.targetFilter(target -> target.name().length() <= 16)
).build();
final TestAudience audience = new TestAudience("4drian3d");
Component expected = Component.text("My name is: 4drian3d");
Component parsed = miniMessage().deserialize("My name is: <builder_test>", audience, expansion.audiencePlaceholders());
assertContentEquals(expected, parsed);
}
}
@@ -0,0 +1,26 @@
package io.github.miniplaceholders.test.placeholder;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.test.MiniTest;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.Tag;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class GlobalPlaceholderTest implements MiniTest {
@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());
assertContentEquals(expected, actual);
}
}
@@ -0,0 +1,117 @@
package io.github.miniplaceholders.test.placeholder;
import io.github.miniplaceholders.api.Expansion;
import io.github.miniplaceholders.api.placeholder.PlaceholderMetadata;
import io.github.miniplaceholders.api.types.RelationalAudience;
import io.github.miniplaceholders.test.MiniTest;
import io.github.miniplaceholders.test.instances.TestAudience;
import io.github.miniplaceholders.test.instances.TestAudienceHolder;
import net.kyori.adventure.audience.Audience;
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 org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class RelationalPlaceholderTest implements MiniTest {
@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)
))
.build();
assertEquals(
MiniMessage.miniMessage().deserialize("You are <red>Enemy"),
MiniMessage.miniMessage().deserialize("You are <relational_rel_enemy>",
RelationalAudience.from(p1, p2),
expansion.relationalPlaceholders()));
}
private boolean isEnemy(TestAudience a1, TestAudience a2) {
return a1.name().equals("PlayerOne") && a2.name().equals("PlayerTwo");
}
@Test
@DisplayName("Relational Forwarding Audience Parsing Test")
void relationalForwardingAudienceTest() {
Expansion expansion = Expansion.builder("test")
.relationalPlaceholder(TestAudience.class, "testing",
(audience, another, queue, ctx) ->
Tag.selfClosingInserting(Component.text("Name: " + audience.name() + ", Other Name: " + another.name())))
.build();
TestAudience testAudience = new TestAudience("4drian3d");
TestAudience secondAudience = new TestAudience("PepitoAlcachofa");
Audience forward = new TestAudienceHolder(testAudience);
Audience secondForward = new TestAudienceHolder(secondAudience);
Component parsed = miniMessage().deserialize("<test_rel_testing>.",
RelationalAudience.from(forward, secondForward), expansion.relationalPlaceholders());
Component expected = Component.text("Name: 4drian3d, Other Name: PepitoAlcachofa.");
assertContentEquals(expected, parsed);
}
@Test
@DisplayName("Relational Filtered Expansion")
void relationalFilteredExpansion() {
Expansion expansion = Expansion.builder("filter")
.relationalPlaceholder(TestAudience.class, "name",
(aud, another, queue, ctx) ->
Tag.selfClosingInserting(Component.text(aud.name().replace(another.name(), ""))))
.build();
Audience player = new TestAudience("TestPlayer04");
Audience second = new TestAudience("Player");
Audience emptyAudience = Audience.empty();
String string = "Player Name: <filter_rel_name>";
Component playerExpected = Component.text("Player Name: Test04");
Component emptyExpected = Component.text("Player Name: <filter_rel_name>");
assertContentEquals(playerExpected, MiniMessage.miniMessage().deserialize(string,
RelationalAudience.from(player, second), expansion.relationalPlaceholders()));
assertContentEquals(emptyExpected, MiniMessage.miniMessage().deserialize(string,
RelationalAudience.from(player, emptyAudience), expansion.relationalPlaceholders()));
}
@Test
@DisplayName("RelationalPlaceholder Builder creation")
void builderRelationalPlaceholderCreation() {
final Expansion expansion = Expansion.builder("builder")
.relationalPlaceholder(
TestAudience.class,
builder -> builder.name("test")
.resolver((audience, relational, queue, ctx) -> {
final String name = audience.name();
final String relationalName = relational.name();
return Tag.preProcessParsed(name + " and " + relationalName);
})
.metadata(PlaceholderMetadata.data("Builder Test", "Returns the Audience plus the Relational Audience combined Names"))
.targetFilter(target -> target.name().length() <= 16)
).build();
final TestAudience audience = new TestAudience("4drian3d");
final TestAudience relational = new TestAudience("PepitoAlcachofa");
final RelationalAudience<@NotNull TestAudience> relationalAudience = RelationalAudience.from(audience, relational);
Component expected = Component.text("Our names are: 4drian3d and PepitoAlcachofa");
Component parsed = miniMessage().deserialize("Our names are: <builder_test>", relationalAudience, expansion.relationalPlaceholders());
assertContentEquals(expected, parsed);
}
}
+1 -2
View File
@@ -5,7 +5,6 @@ plugins {
dependencies {
implementation(libs.plugin.shadow)
implementation(libs.plugin.kotlin)
implementation(libs.plugin.runtask)
}
repositories {
@@ -14,6 +13,6 @@ repositories {
kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(25)
languageVersion = JavaLanguageVersion.of(21)
}
}
+1 -1
View File
@@ -4,4 +4,4 @@ val Project.capitalizeName: String
get() = this.name.substring(17)
.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
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(21)
}
jar {
manifest {
@@ -22,7 +22,7 @@ tasks {
java{
toolchain{
languageVersion.set(JavaLanguageVersion.of(25))
vendor.set(JvmVendorSpec.AZUL)
languageVersion.set(JavaLanguageVersion.of(21))
//vendor.set(JvmVendorSpec.ADOPTIUM)
}
}
@@ -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
)
}
}
}
@@ -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("org.incendo.cloud", "io.github.miniplaceholders.libs.cloud")
// if (project.simpleName() != "sponge") {
// relocate("io.leangen.geantyref", "io.github.miniplaceholders.libs.geantyref")
// }
destinationDirectory.set(file("${project.rootDir}/jar"))
}
build {
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
kotlin("jvm") version "2.3.20" apply false
kotlin("jvm") version "2.2.20" apply false
}
tasks {
-1
View File
@@ -14,7 +14,6 @@ dependencies {
compileOnly(libs.adventure.minimessage)
compileOnly(libs.jspecify)
compileOnly(libs.brigadier)
compileOnly(libs.bstats.core)
}
sourceSets {
@@ -23,13 +23,13 @@ public interface PlaceholdersPlugin {
default void registerPlatformCommand() {
}
default void loadProvidedExpansions(final Path providersFolderDirectory) throws Exception {
default void loadProvidedExpansions(Path providersFolderDirectory) throws Exception {
final List<Expansion> loadedExpansions = new ArrayList<>();
final PlatformData platformData = new PlatformData(this.platformServerInstance(), this);
final Injector injector = Injector.create(binder -> binder.bind(PlatformData.class).toInstance(platformData));
for (Class<?> providerClass : ExpansionProviderLoader.loadProvidersFromFolder(providersFolderDirectory)) {
final ExpansionProvider provider = (ExpansionProvider) injector.getInstance(providerClass);
final ProviderLoadResult loadResult = tryLoad(provider, provider.loadRequirement());
for (ExpansionProvider provider : ExpansionProviderLoader.loadProvidersFromFolder(providersFolderDirectory)) {
injector.injectMembers(provider);
ProviderLoadResult loadResult = tryLoad(provider, provider.loadRequirement());
final ExpansionProvider loadedProvider = loadResult.provider();
if (loadedProvider != null) {
final Expansion loadedExpansion = loadedProvider.provideExpansion();
@@ -39,7 +39,7 @@ public interface PlaceholdersPlugin {
}
final FailedToLoadExpansion failedToLoad = loadResult.failed();
logError(Component.text("The expansion "
+ providerClass +
+ provider.getClass() +
" could not be loaded because the requirement " + failedToLoad.requirement() + " could not be resolved",
NamedTextColor.RED));
}
@@ -1,10 +1,10 @@
package io.github.miniplaceholders.common.command;
import net.kyori.adventure.audience.Audience;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@FunctionalInterface
public interface AudienceConverter {
@Nullable Audience convert(@NonNull String string);
@Nullable Audience convert(@NotNull String string);
}
@@ -80,7 +80,7 @@ public final class CommandConstants {
)
.append(newline(), miniMessage().deserialize(
"<gradient:aqua:#94d1ff>" + command + "</gradient> <aqua>expansions</aqua>")
.clickEvent(ClickEvent.runCommand(command + " expansions"))
.clickEvent(ClickEvent.runCommand(command + " help"))
)
.append(newline(), FOOTER)
.build();
@@ -8,6 +8,4 @@ import org.jspecify.annotations.NullMarked;
@FunctionalInterface
public interface PermissionTester {
TriState permissionValue(Audience audience, String permission);
PermissionTester NO_OP = (audience, permission) -> TriState.NOT_SET;
}
@@ -21,7 +21,7 @@ import java.util.jar.JarInputStream;
@NullMarked
public final class ExpansionProviderLoader {
public static List<Class<?>> loadProvidersFromFolder(final Path folferPath) throws Exception {
public static List<ExpansionProvider> loadProvidersFromFolder(final Path folferPath) throws Exception {
if (Files.notExists(folferPath)) {
try {
Files.createDirectories(folferPath);
@@ -32,7 +32,7 @@ public final class ExpansionProviderLoader {
}
try (final DirectoryStream<Path> stream = Files.newDirectoryStream(folferPath,
filtered -> Files.isRegularFile(filtered) && filtered.toString().endsWith(".jar"))) {
final List<Class<?>> providerList = new ArrayList<>();
final List<ExpansionProvider> providerList = new ArrayList<>();
for (final Path possibleProviderPath : stream) {
loadPossibleProviderFromPath(possibleProviderPath, providerList);
}
@@ -42,7 +42,7 @@ public final class ExpansionProviderLoader {
private static void loadPossibleProviderFromPath(
final Path filePath,
final List<Class<?>> providers
final List<ExpansionProvider> providers
) throws Exception {
String mainClassName = null;
try (final JarInputStream jarStream = new JarInputStream(Files.newInputStream(filePath))) {
@@ -58,7 +58,9 @@ public final class ExpansionProviderLoader {
mainClassName = properties.getProperty("provider-class");
break;
} catch (IOException e) {
throw new IOException("Unable to read expansion_provider.properties from " + filePath + "file", e);
throw new IOException(
"Unable to read expansion_provider.properties from " + filePath + "file",
e);
}
}
}
@@ -72,15 +74,13 @@ public final class ExpansionProviderLoader {
private static void loadFoundProvider(
final String providerClass,
final List<Class<?>> providers,
final List<ExpansionProvider> providers,
final Path providerPath
) throws Exception {
//noinspection resource
final URLClassLoader classLoader = new URLClassLoader(
"MiniPlaceholders ClassLoader",
new URL[] { providerPath.toUri().toURL() },
ExpansionProviderLoader.class.getClassLoader()
);
final URLClassLoader classLoader = new URLClassLoader(new URL[] {
providerPath.toUri().toURL(),
}, ExpansionProviderLoader.class.getClassLoader());
Class<?> mainClass;
try {
mainClass = classLoader.loadClass(providerClass);
@@ -90,6 +90,10 @@ public final class ExpansionProviderLoader {
if (!ExpansionProvider.class.isAssignableFrom(mainClass) || mainClass.isInterface()) {
return;
}
providers.add(mainClass);
try {
providers.add((ExpansionProvider) mainClass.getConstructors()[0].newInstance());
} catch (Exception e) {
throw new RuntimeException("Unable to create expansion provider from file " + providerPath, e);
}
}
}
@@ -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();
}
}
@@ -3,7 +3,7 @@ package io.github.miniplaceholders.connect;
public enum InternalPlatform {
PAPER, VELOCITY,
FABRIC, SPONGE, MINESTOM;
FABRIC, SPONGE;
private static InternalPlatform actualPlatform;
+1 -1
View File
@@ -4,7 +4,7 @@ plugins {
alias(libs.plugins.blossom)
}
version = "1.4.0"
version = "1.0.0"
dependencies {
compileOnly(projects.miniplaceholdersApi)
@@ -3,62 +3,34 @@ 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 {}
public final class ExampleProvider implements ExpansionProvider {
// For advanced usage only
@team.unnamed.inject.Inject
private io.github.miniplaceholders.api.provider.PlatformData platformData;
@Override
public Expansion provideExpansion() {
return Expansion.builder("example")
.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 Expansion provideExpansion() {
@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")
);
}
return Expansion.builder("example")
.version("2.0.0")
.author("MiniPlaceholders Contributors")
.globalPlaceholder("test", (queue, context) ->
Tag.preProcessParsed("""
Platform Instance Class: %s
MiniPlaceholders Instance Class: %s
""".formatted(
platformData.serverInstance(),
platformData.complementInstance())))
.version(ExpansionConstants.VERSION)
.build();
}
@Override
public LoadRequirement loadRequirement() {
return LoadRequirement.allOf(LoadRequirement.allOf(LoadRequirement.platform(Platform.VELOCITY)));
}
}
+15 -49
View File
@@ -1,35 +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("quiet-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.desertwell)
includeDependency(libs.unnamedinject)
shadeModule(projects.miniplaceholdersApi)
shadeModule(projects.miniplaceholdersCommon)
@@ -37,19 +25,21 @@ 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
@@ -59,37 +49,13 @@ tasks {
expand("version" to projectVersion)
}
}
jar {
remapJar {
inputFile.set(shadowJar.get().archiveFile)
archiveFileName.set("MiniPlaceholders-Fabric-${projectVersion}.jar")
destinationDirectory.set(file("${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)
}
}
@@ -54,9 +54,7 @@ public final class FabricMod implements DedicatedServerModInitializer, Placehold
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;
case DEFAULT -> commandSourceStack.hasPermission(3) ? TriState.TRUE : TriState.NOT_SET;
};
}
);
+6 -6
View File
@@ -20,11 +20,11 @@
]
},
"depends": {
"fabricloader": ">=0.18.0",
"fabric-api": "*",
"java": ">=25",
"adventure-platform-fabric": ">6.8.0",
"minecraft": ">=26.1",
"fabric-permissions-api-v0": ">=0.7.0"
"fabricloader": ">=0.14.19",
"fabric": "*",
"java": ">=21",
"adventure-platform-fabric": "~6.6.0",
"minecraft": ">=1.21.6 <=1.21.8",
"fabric-permissions-api-v0": "~0.4.1"
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
group = io.github.miniplaceholders
version = 3.2.0
version = 3.1.0-SNAPSHOT
description = MiniMessage Component-based Placeholders API
org.gradle.jvmargs=-Xmx2G
+30 -46
View File
@@ -2,75 +2,59 @@ metadata.format.version = "1.1"
[versions]
# Platform
minecraft = "26.1"
paper = "1.21.11-R0.1-SNAPSHOT"
velocity = "3.5.0-SNAPSHOT"
minestom = "2026.03.03-1.21.11"
fabric-loader = "0.18.5"
fabric-api = "0.144.3+26.1"
velocity = "3.4.0-SNAPSHOT"
paper = "1.21.8-R0.1-SNAPSHOT"
minecraft = "1.21.8"
# Libraries
kotlin = "2.3.20"
adventure = "4.26.1"
adventureModded = "6.9.0-SNAPSHOT"
desertwell = "2.0.4"
bstats = "3.2.1"
jspecify = "1.0.0"
# Gradle Plugins
runtask = "3.0.2"
blossom = "2.2.0"
shadow = "9.4.0"
runtask = "3.0.0"
blossom = "2.1.0"
shadow = "9.2.2"
jmh = "0.7.3"
dokka = "2.2.0"
dokka = "2.0.0"
adventure = "4.24.0"
adventureModded = "6.6.0"
kotlin = "2.2.20"
fabric-api = "0.133.14+1.21.9"
fabric-loader = "0.17.2"
desertwell = "2.0.4"
[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.4.1" }
# 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-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" }
plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
plugin-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
# Libraries
desertwell = { group = "net.william278", name = "DesertWell", version.ref = "desertwell" }
junit-bom = "org.junit:junit-bom:6.0.3"
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
luckopermissionsapi = { group = "me.lucko", name = "fabric-permissions-api", version = "0.7.0" }
junit-bom = "org.junit:junit-bom:6.0.0"
jspecify = "org.jspecify:jspecify:1.0.0"
unnamedinject = "team.unnamed:inject:2.0.1"
brigadier = "com.mojang:brigadier:1.0.500"
# 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" }
[plugins]
# Platform Plugins
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.15.5"}
spongegradle = { id = "org.spongepowered.gradle.plugin", version = "2.3.0" }
# General Plugins
blossom = { id = "net.kyori.blossom", version.ref = "blossom" }
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
jmh = { id = "me.champeau.jmh", version.ref = "jmh" }
idea-ext = { id = "org.jetbrains.gradle.plugin.idea-ext", version = "1.3" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
idea-ext = { id = "org.jetbrains.gradle.plugin.idea-ext", version = "1.4.1" }
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+1 -1
View File
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
+7 -4
View File
@@ -1,12 +1,11 @@
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("org.jetbrains.dokka") version "2.0.0"
}
dependencies {
@@ -19,7 +18,7 @@ dependencies {
kotlin {
explicitApi()
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(25))
languageVersion.set(JavaLanguageVersion.of(21))
}
}
@@ -76,8 +75,12 @@ mavenPublishing {
}
}
configure(KotlinJvm(
// configures the -javadoc artifact, possible values:
// - `JavadocJar.None()` don't publish this artifact
// - `JavadocJar.Empty()` publish an empty jar
// - `JavadocJar.Dokka("dokkaHtml")` when using Kotlin with Dokka, where `dokkaHtml` is the name of the Dokka task that should be used as input
javadocJar = JavadocJar.Dokka("dokkaGeneratePublicationHtml"),
sourcesJar = SourcesJar.Sources(),
sourcesJar = true,
))
}
-3
View File
@@ -1,3 +0,0 @@
#run minestom files
/run
/test
-82
View File
@@ -1,82 +0,0 @@
import java.time.LocalDate
plugins {
id("miniplaceholders.shadow")
id("miniplaceholders.auto.module")
id("miniplaceholders.runtask")
id("com.vanniktech.maven.publish")
}
dependencies {
compileOnly(libs.minestom)
compileOnly(libs.adventure.api)
annotationProcessor(libs.minestom)
implementation(projects.miniplaceholdersCommon)
implementation(projects.miniplaceholdersApi)
implementation(projects.miniplaceholdersConnect)
testImplementation(libs.minestom)
testImplementation(libs.adventure.api)
testImplementation(libs.adventure.minimessage)
testImplementation("ch.qos.logback:logback-classic:1.5.32") // logger
testImplementation("com.google.guava:guava:33.5.0-jre")
}
tasks.test {
failOnNoDiscoveredTests = false
}
mavenPublishing {
publishToMavenCentral()
signAllPublications()
coordinates(project.group as String, "miniplaceholders-minestom", 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")
}
}
configurations {
named("runtimeElements") {
outgoing {
artifacts.clear()
artifact(tasks.shadowJar)
}
}
named("apiElements") {
outgoing {
artifacts.clear()
artifact(tasks.shadowJar)
}
}
}
}
@@ -1,69 +0,0 @@
package io.github.miniplaceholders.minestom;
import io.github.miniplaceholders.common.PlaceholdersPlugin;
import io.github.miniplaceholders.common.command.PermissionTester;
import io.github.miniplaceholders.connect.InternalPlatform;
import io.github.miniplaceholders.minestom.command.MinestomCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.minestom.server.MinecraftServer;
import java.nio.file.Path;
public final class MinestomMiniPlaceholders implements PlaceholdersPlugin {
private static final ComponentLogger LOGGER = ComponentLogger.logger("miniplaceholders");
private final PermissionTester permissionTester;
private MinestomMiniPlaceholders(PermissionTester permissionTester) {
this.permissionTester = permissionTester;
}
public static MinestomMiniPlaceholders initialize(Path dataDirectory, PermissionTester permissionTester) {
InternalPlatform.platform(InternalPlatform.MINESTOM);
LOGGER.info(Component.text("Initializing MiniPlaceholders", NamedTextColor.GREEN));
final MinestomMiniPlaceholders miniPlaceholders = new MinestomMiniPlaceholders(permissionTester);
miniPlaceholders.registerPlatformCommand();
try {
miniPlaceholders.loadProvidedExpansions(dataDirectory.resolve("expansions"));
} catch (Throwable e) {
LOGGER.error("Unable to load expansion providers", e);
}
return miniPlaceholders;
}
/**
* Initializes MiniPlaceholders with a default no-op permission tester.
* This is not recommended for production use!
*/
public static MinestomMiniPlaceholders initialize(final Path dataDirectory) {
return MinestomMiniPlaceholders.initialize(dataDirectory, PermissionTester.NO_OP);
}
@Override
public void registerPlatformCommand() {
MinecraftServer.getCommandManager().register(new MinestomCommand(permissionTester));
}
@Override
public boolean platformHasComplementLoaded(String complementName) {
return false; // Minestom doesn't plugins.
}
@Override
public Object platformServerInstance() {
return MinecraftServer.getServer();
}
@Override
public void logInfo(Component component) {
LOGGER.info(component);
}
@Override
public void logError(Component component) {
LOGGER.error(component);
}
}
@@ -1,148 +0,0 @@
package io.github.miniplaceholders.minestom.command;
import io.github.miniplaceholders.common.command.AudienceConverter;
import io.github.miniplaceholders.common.command.PermissionTester;
import io.github.miniplaceholders.common.command.PlaceholderCommandExecutor;
import io.github.miniplaceholders.common.command.PlayersNameProvider;
import io.github.miniplaceholders.common.command.node.*;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.CommandSender;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandContext;
import net.minestom.server.command.builder.CommandExecutor;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.command.builder.condition.CommandCondition;
import net.minestom.server.command.builder.suggestion.SuggestionEntry;
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import java.util.ArrayList;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class MinestomCommand extends Command {
private final PermissionTester permissionTester;
private final PlayersNameProvider playerSuggestions;
private final AudienceConverter audienceConverter;
public MinestomCommand(PermissionTester permissionTester) {
super("miniplaceholders");
this.permissionTester = permissionTester;
this.playerSuggestions = () -> MinecraftServer.getConnectionManager().getOnlinePlayers()
.stream()
.map(Player::getName)
.map(PlainTextComponentSerializer.plainText()::serialize)
.collect(Collectors.toCollection(ArrayList::new));
this.audienceConverter = string -> MinecraftServer.getConnectionManager().getOnlinePlayerByUsername(string);
final RootNode rootNode = new RootNode(permissionTester);
this.setCondition(new WrappingCondition(rootNode::hasPermission));
this.setDefaultExecutor(new WrappingExecutor(rootNode::execute));
this.addSubcommands(new ExpansionsCommand(), new HelpCommand(), new ParseCommand(), new ParseRelCommand());
}
private class ExpansionsCommand extends Command {
public ExpansionsCommand() {
super("expansions");
final ExpansionsNode expansionsNode = new ExpansionsNode(permissionTester);
this.setCondition(new WrappingCondition(expansionsNode::hasPermission));
this.setDefaultExecutor(new WrappingExecutor(expansionsNode::showExpansions));
}
}
private class HelpCommand extends Command {
public HelpCommand() {
super("help");
final HelpNode helpNode = new HelpNode(permissionTester);
this.setCondition(new WrappingCondition(helpNode::hasPermission));
this.setDefaultExecutor(new WrappingExecutor(helpNode::execute));
}
}
private class ParseCommand extends Command {
public ParseCommand() {
super("parse");
final ParseNode parseNode = new ParseNode(playerSuggestions, audienceConverter, permissionTester);
final Argument<String> sourceArgument = ArgumentType.String("source")
.setSuggestionCallback((_, _, suggestion) -> parseNode.providePlayerSuggestions()
.stream()
.filter(s -> {
final String[] args = suggestion.getInput().replace("\0", "").split(" ");
return args.length < 3 || s.toLowerCase().startsWith(args[2].toLowerCase());
})
.map(SuggestionEntry::new)
.forEach(suggestion::addEntry));
final Argument<String[]> messageArgument = ArgumentType.StringArray("message");
this.setCondition(new WrappingCondition(parseNode::hasPermission));
this.addSyntax((sender, context) -> {
final String source = context.get(sourceArgument);
final String[] message = context.get(messageArgument);
parseNode.parseString(sender, source, String.join(" ", message));
}, sourceArgument, messageArgument);
}
}
private class ParseRelCommand extends Command {
public ParseRelCommand() {
super("parserel");
final ParseRelNode parseRelNode = new ParseRelNode(playerSuggestions, audienceConverter, permissionTester);
final Argument<String> sourceArgument = ArgumentType.String("source")
.setSuggestionCallback((_, _, suggestion) -> parseRelNode.providePlayerSuggestions()
.stream()
.filter(s -> {
final String[] args = suggestion.getInput().replace("\0", "").split(" ");
return args.length < 3 || s.toLowerCase().startsWith(args[2].toLowerCase());
})
.map(SuggestionEntry::new)
.forEach(suggestion::addEntry));
final Argument<String> relationalSourceArgument = ArgumentType.String("relational")
.setSuggestionCallback((_, _, suggestion) -> parseRelNode.providePlayerSuggestions()
.stream()
.filter(s -> {
final String[] args = suggestion.getInput().replace("\0", "").split(" ");
return args.length < 4 || s.toLowerCase().startsWith(args[3].toLowerCase());
})
.map(SuggestionEntry::new)
.forEach(suggestion::addEntry));
final Argument<String[]> messageArgument = ArgumentType.StringArray("message");
this.setCondition(new WrappingCondition(parseRelNode::hasPermission));
this.addSyntax((sender, context) -> {
final String source = context.get(sourceArgument);
final String relationalSource = context.get(relationalSourceArgument);
final String[] message = context.get(messageArgument);
parseRelNode.parseString(sender, source, relationalSource, String.join(" ", message));
}, sourceArgument, relationalSourceArgument, messageArgument);
}
}
private record WrappingCondition(Predicate<Audience> predicate) implements CommandCondition {
@Override
public boolean canUse(@NonNull CommandSender sender, @Nullable String commandString) {
return predicate.test(sender);
}
}
private record WrappingExecutor(PlaceholderCommandExecutor executor) implements CommandExecutor {
@Override
public void apply(@NonNull CommandSender sender, @NonNull CommandContext context) {
executor.execute(sender);
}
}
}
@@ -1,89 +0,0 @@
package io.github.miniplaceholders.minestom;
import io.github.miniplaceholders.minestom.listener.AsyncPlayerConfigurationListener;
import net.kyori.adventure.util.TriState;
import net.minestom.server.Auth;
import net.minestom.server.MinecraftServer;
import net.minestom.server.extras.lan.OpenToLAN;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
public class MinestomServer {
private static final Path MINIPLACEHOLDERS_TEST_FOLDER = Path.of("minestom", "test", "miniplaceholders");
private static final Path TEST_EXPANSION_FOLDER = Path.of("example-expansion-provider", "build", "libs");
static void main() throws IOException {
final MinecraftServer server = MinecraftServer.init(new Auth.Online());
MinecraftServer.getGlobalEventHandler().addListener(new AsyncPlayerConfigurationListener());
MinestomServer.copyTestExpansion();
MinestomMiniPlaceholders.initialize(
MINIPLACEHOLDERS_TEST_FOLDER,
(_, _) -> TriState.TRUE
);
OpenToLAN.open();
server.start("0.0.0.0", 25565);
Thread.ofPlatform().start(() -> {
try {
Thread.sleep(Duration.ofMinutes(2));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.exit(0);
});
}
private static void copyTestExpansion() throws IOException {
if (!Files.exists(TEST_EXPANSION_FOLDER)) {
return;
}
final Path expansionsFolder = MINIPLACEHOLDERS_TEST_FOLDER.resolve("expansions");
// Delete previous expansions files
try (DirectoryStream<Path> stream = Files.newDirectoryStream(
expansionsFolder,
file -> file.getFileName().toString().endsWith(".jar"))
) {
stream.forEach(file -> {
try {
Files.delete(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
// Copy latest example expansion file
try (final DirectoryStream<Path> stream = Files.newDirectoryStream(
TEST_EXPANSION_FOLDER,
file -> file.getFileName()
.toString().startsWith("miniplaceholders-example-expansion-provider"))
) {
stream.forEach(file -> {
if (!Files.exists(expansionsFolder)) {
try {
Files.createDirectories(expansionsFolder);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
try {
Files.copy(file, expansionsFolder.resolve(file.getFileName()), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
} catch (IOException e) {
System.out.println("An error occurred while loading expansion provider" + e);
}
}
}
@@ -1,34 +0,0 @@
package io.github.miniplaceholders.minestom.listener;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import org.jspecify.annotations.NonNull;
public class AsyncPlayerConfigurationListener implements EventListener<AsyncPlayerConfigurationEvent> {
private final Instance instance;
public AsyncPlayerConfigurationListener() {
this.instance = MinecraftServer.getInstanceManager().createInstanceContainer();
instance.setGenerator(unit -> unit.modifier().fillHeight(0, 32, Block.GRASS_BLOCK));
}
@Override
public @NonNull Class<AsyncPlayerConfigurationEvent> eventType() {
return AsyncPlayerConfigurationEvent.class;
}
@Override
public @NonNull Result run(AsyncPlayerConfigurationEvent event) {
final Player player = event.getPlayer();
event.setSpawningInstance(instance);
player.setRespawnPoint(new Pos(0, 33, 0));
return Result.SUCCESS;
}
}
+1 -4
View File
@@ -1,7 +1,6 @@
plugins {
id("miniplaceholders.auto.module")
id("miniplaceholders.shadow")
id("miniplaceholders.runtask")
alias(libs.plugins.runpaper)
}
@@ -10,17 +9,15 @@ dependencies {
implementation(projects.miniplaceholdersCommon)
implementation(projects.miniplaceholdersApi)
implementation(projects.miniplaceholdersConnect)
implementation(libs.bstats.bukkit)
}
tasks {
runServer {
jvmArgs("-Dcom.mojang.eula.agree=true")
minecraftVersion(libs.versions.minecraft.get())
}
val projectVersion = project.version
processResources {
//val version = project.version
filesMatching("paper-plugin.yml") {
expand("version" to projectVersion)
}
@@ -11,17 +11,16 @@ import net.kyori.adventure.util.TriState;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jspecify.annotations.NullMarked;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@SuppressWarnings({"unused", "UnstableApiUsage"})
@NullMarked
public final class PaperBootstrap implements PluginBootstrap {
@Override
public void bootstrap(final BootstrapContext bootstrapContext) {
public void bootstrap(@NotNull BootstrapContext bootstrapContext) {
InternalPlatform.platform(InternalPlatform.PAPER);
final LiteralCommandNode<CommandSourceStack> command = BrigadierCommandProvider.provideCommand(
"miniplaceholders",
@@ -1,13 +1,9 @@
package io.github.miniplaceholders.paper;
import io.github.miniplaceholders.common.PlaceholdersPlugin;
import io.github.miniplaceholders.common.metrics.LoadedExpansionsMetric;
import io.github.miniplaceholders.paper.listener.ServerFinishedLoadListener;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SingleLineChart;
import org.bukkit.plugin.java.JavaPlugin;
import static net.kyori.adventure.text.Component.text;
@@ -19,9 +15,11 @@ public final class PaperPlugin extends JavaPlugin implements PlaceholdersPlugin
final ComponentLogger componentLogger = getComponentLogger();
componentLogger.info(text("Starting MiniPlaceholders Paper", NamedTextColor.GREEN));
new ServerFinishedLoadListener(this).register();
final Metrics metrics = new Metrics(this, 27515);
metrics.addCustomChart(new SingleLineChart("loaded_expansions", new LoadedExpansionsMetric()));
try {
this.loadProvidedExpansions(getDataPath().resolve("expansions"));
} catch (Throwable e) {
componentLogger.error("Unable to load expansion providers", e);
}
}
@Override
@@ -1,32 +0,0 @@
package io.github.miniplaceholders.paper.listener;
import io.github.miniplaceholders.paper.PaperPlugin;
import org.bukkit.event.Event;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerLoadEvent;
import org.bukkit.plugin.EventExecutor;
import org.jspecify.annotations.NonNull;
public record ServerFinishedLoadListener(PaperPlugin plugin) implements Listener, EventExecutor {
public void register() {
plugin.getServer()
.getPluginManager()
.registerEvent(ServerLoadEvent.class, this, EventPriority.HIGH, this, plugin);
}
@Override
public void execute(final @NonNull Listener listener, final @NonNull Event event) {
final ServerLoadEvent loadEvent = (ServerLoadEvent) event;
if (loadEvent.getType() != ServerLoadEvent.LoadType.STARTUP) {
return;
}
try {
plugin.loadProvidedExpansions(plugin.getDataPath().resolve("expansions"));
} catch (Throwable e) {
plugin.getComponentLogger().error("Unable to load expansion providers", e);
}
}
}
+16 -5
View File
@@ -16,19 +16,31 @@ pluginManagement {
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
maven("https://central.sonatype.com/repository/maven-snapshots/") {
name = "sonatype-snapshots"
mavenContent {
snapshotsOnly()
}
}
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.maven.apache.org/maven2/")
maven("https://repo.spongepowered.org/repository/")
maven("https://jitpack.io")
maven("https://jitpack.io") {
mavenContent {
includeGroup("net.william278")
}
}
maven("https://repo.jpenilla.xyz/snapshots/")
}
}
plugins {
id("quiet-fabric-loom") version "1.11.325"
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
id("com.vanniktech.maven.publish") version "0.36.0" apply false
kotlin("jvm") version "2.3.20" apply false
id("com.vanniktech.maven.publish") version "0.34.0" apply false
kotlin("jvm") version "2.2.20" apply false
}
arrayOf(
@@ -37,7 +49,6 @@ arrayOf(
"kotlin-ext",
"common",
"paper",
"minestom",
"velocity",
"fabric",
"sponge",
@@ -45,4 +56,4 @@ arrayOf(
).forEach {
include("miniplaceholders-$it")
project(":miniplaceholders-$it").projectDir = file(it)
}
}
+1 -31
View File
@@ -1,13 +1,10 @@
import org.spongepowered.gradle.plugin.config.PluginLoaders
import org.spongepowered.plugin.metadata.model.PluginDependency
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import kotlin.io.path.deleteIfExists
plugins {
id("miniplaceholders.auto.module")
id("miniplaceholders.shadow")
alias(libs.plugins.spongegradle)
id("org.spongepowered.gradle.plugin") version "2.3.0"
}
dependencies {
@@ -16,7 +13,6 @@ dependencies {
implementation(projects.miniplaceholdersConnect)
compileOnly(libs.jspecify)
implementation(libs.bstats.sponge)
}
sponge {
@@ -45,29 +41,3 @@ sponge {
}
}
}
tasks {
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
)
}
}
}
@@ -2,20 +2,16 @@ package io.github.miniplaceholders.sponge;
import com.google.inject.Inject;
import io.github.miniplaceholders.common.PlaceholdersPlugin;
import io.github.miniplaceholders.common.metrics.LoadedExpansionsMetric;
import io.github.miniplaceholders.connect.InternalPlatform;
import io.github.miniplaceholders.sponge.command.SpongeCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.apache.logging.log4j.Logger;
import org.bstats.charts.SingleLineChart;
import org.bstats.sponge.Metrics;
import org.spongepowered.api.Server;
import org.spongepowered.api.command.Command;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.lifecycle.RegisterCommandEvent;
import org.spongepowered.api.event.lifecycle.StartedEngineEvent;
import org.spongepowered.api.event.lifecycle.StartingEngineEvent;
import org.spongepowered.plugin.PluginContainer;
import org.spongepowered.plugin.builtin.jvm.Plugin;
@@ -32,11 +28,8 @@ public class SpongePlugin implements PlaceholdersPlugin {
private Path configDir;
@Inject
private PluginContainer pluginContainer;
@Inject
private Metrics.Factory factory;
private Server server;
private Metrics metrics;
@Listener
public void onServerStart(final StartingEngineEvent<Server> event) {
@@ -44,20 +37,11 @@ public class SpongePlugin implements PlaceholdersPlugin {
InternalPlatform.platform(InternalPlatform.SPONGE);
logger.info("Starting MiniPlaceholders Sponge");
this.metrics = factory.make(27517);
}
@Listener
public void onLateServerStart(final StartedEngineEvent<Server> event) {
try {
this.loadProvidedExpansions(configDir.resolve("expansions"));
} catch (Throwable e) {
logger.error("Unable to load expansion providers", e);
}
// bstats sponge weird initialization workaround
this.metrics.startup(null);
this.metrics.addCustomChart(new SingleLineChart("expansions_loaded", new LoadedExpansionsMetric()));
}
@Listener
@@ -3,6 +3,7 @@ package io.github.miniplaceholders.sponge.command;
import io.github.miniplaceholders.common.command.*;
import io.github.miniplaceholders.common.command.node.*;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.util.TriState;
import org.jspecify.annotations.NullMarked;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.*;
@@ -21,7 +22,7 @@ import java.util.stream.Collectors;
public final class SpongeCommand {
public static Command.Parameterized provideCommand() {
final AudienceExtractor<CommandCause> audienceExtractor = CommandCause::audience;
final PermissionTester permissionTester = PermissionTester.NO_OP; // Not really used here
final PermissionTester permissionTester = (audience, permission) -> TriState.NOT_SET; // Not really used here
final PlayersNameProvider playerSuggestions = () -> Sponge.server().onlinePlayers()
.stream()
.map(ServerPlayer::user)
-3
View File
@@ -1,7 +1,6 @@
plugins {
id("miniplaceholders.shadow")
id("miniplaceholders.auto.module")
id("miniplaceholders.runtask")
alias(libs.plugins.runvelocity)
}
@@ -11,8 +10,6 @@ dependencies {
implementation(projects.miniplaceholdersCommon)
implementation(projects.miniplaceholdersApi)
implementation(projects.miniplaceholdersConnect)
implementation(libs.bstats.velocity)
}
tasks {
@@ -15,13 +15,10 @@ import com.velocitypowered.api.proxy.ProxyServer;
import io.github.miniplaceholders.common.PlaceholdersPlugin;
import io.github.miniplaceholders.common.PluginConstants;
import io.github.miniplaceholders.common.command.brigadier.BrigadierCommandProvider;
import io.github.miniplaceholders.common.metrics.LoadedExpansionsMetric;
import io.github.miniplaceholders.connect.InternalPlatform;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.bstats.charts.SingleLineChart;
import org.bstats.velocity.Metrics;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -44,8 +41,6 @@ public final class VelocityPlugin implements PlaceholdersPlugin {
private CommandManager commandManager;
@Inject
private ComponentLogger componentLogger;
@Inject
private Metrics.Factory metricsFactory;
@Subscribe(priority = 999)
public void onEarlyProxyInitialize(final ProxyInitializeEvent event) {
@@ -53,9 +48,6 @@ public final class VelocityPlugin implements PlaceholdersPlugin {
componentLogger.info(Component.text("Starting MiniPlaceholders Velocity", NamedTextColor.GREEN));
this.registerPlatformCommand();
final Metrics metrics = metricsFactory.make(this, 27516);
metrics.addCustomChart(new SingleLineChart("expansions_count", new LoadedExpansionsMetric()));
}
@Subscribe(priority = -404)
@@ -71,7 +63,7 @@ public final class VelocityPlugin implements PlaceholdersPlugin {
public void registerPlatformCommand() {
final LiteralCommandNode<CommandSource> node = BrigadierCommandProvider.provideCommand(
"vminiplaceholders",
() -> this.proxyServer.getAllPlayers()
() -> proxyServer.getAllPlayers()
.stream()
.map(Player::getUsername)
.collect(Collectors.toCollection(ArrayList::new)),
@@ -81,10 +73,10 @@ public final class VelocityPlugin implements PlaceholdersPlugin {
.toAdventureTriState()
);
final BrigadierCommand command = new BrigadierCommand(node);
final CommandMeta meta = this.commandManager.metaBuilder(command)
final CommandMeta meta = commandManager.metaBuilder(command)
.plugin(this)
.build();
this.commandManager.register(meta, command);
commandManager.register(meta, command);
}
@Override