Skip to content

Commit e33310c

Browse files
committed
feat: support MiniPlaceholders v3 and v2
Figured migration might be slow for plugin authors, MiniPlaceholders also limits its version compatibility scope significantly (compared to ColorParser) which may cause issues for some authors, stuck on older MiniPlaceholders versions. Since I already use reflection for this exact reason, we simple extended the logic significantly while also making a quite nice cleanup. This commit ensures ColorParser works with both 2.X.X and 3.X.X versions of MiniPlaceholders, without making API changes in ColorParser (Hooray).
1 parent b28d13e commit e33310c

File tree

20 files changed

+460
-498
lines changed

20 files changed

+460
-498
lines changed

common/src/main/java/io/github/milkdrinkers/colorparser/common/ComponentBuilder.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import io.github.milkdrinkers.colorparser.common.placeholder.PlaceholderContext;
55
import io.github.milkdrinkers.colorparser.common.placeholder.PlatformPlayer;
66
import io.github.milkdrinkers.colorparser.common.tag.CustomTags;
7+
import net.kyori.adventure.audience.Audience;
8+
import net.kyori.adventure.audience.ForwardingAudience;
79
import net.kyori.adventure.text.Component;
810
import net.kyori.adventure.text.ComponentLike;
911
import net.kyori.adventure.text.minimessage.tag.TagPattern;
@@ -12,6 +14,7 @@
1214
import org.intellij.lang.annotations.Subst;
1315
import org.jetbrains.annotations.NotNull;
1416

17+
import java.lang.reflect.Method;
1518
import java.util.ArrayList;
1619
import java.util.Collection;
1720
import java.util.Collections;
@@ -39,6 +42,33 @@ public abstract class ComponentBuilder<ColorParser extends ComponentBuilder<Colo
3942
private final List<TagResolver> placeholders = new ArrayList<>(8);
4043
private final List<TagResolver> miscTagResolvers = new ArrayList<>(4);
4144

45+
// Used by adventure (and MiniPlaceholders) when deserialising to add context for who the target is
46+
protected Audience audienceTarget = null;
47+
protected Audience audienceRelation = null;
48+
49+
private static volatile boolean miniPlaceholdersInitialized = false; // Tracks whether we have tried and failed to load MiniPlaceholders 3.X (prevents us from doing it again)
50+
private static volatile Method MINIPLACEHOLDERS_METHOD_RELATIONAL; // Used to send relational placeholders using MiniPlaceholders
51+
52+
/**
53+
* Lazy loads the miniplaceholders method required for using relational placeholders.
54+
*/
55+
private void lazyloadMiniPlaceholders() {
56+
if (miniPlaceholdersInitialized)
57+
return;
58+
59+
synchronized (ComponentBuilder.class) {
60+
Method tempMethod = null;
61+
try {
62+
final Class<?> tempClass = Class.forName("io.github.miniplaceholders.api.types.RelationalAudience");
63+
tempMethod = tempClass.getMethod("from", Audience.class, Audience.class);
64+
} catch (Exception ignored) {
65+
} finally {
66+
MINIPLACEHOLDERS_METHOD_RELATIONAL = tempMethod;
67+
miniPlaceholdersInitialized = true;
68+
}
69+
}
70+
}
71+
4272
/**
4373
* Creates a new ComponentBuilder with the given engine and content.
4474
*
@@ -299,7 +329,23 @@ public Component build() {
299329
System.arraycopy(placeholders.toArray(new TagResolver[0]), 0, resolvers, 0, placeholders.size());
300330
System.arraycopy(miscTagResolvers.toArray(new TagResolver[0]), 0, resolvers, placeholders.size(), miscTagResolvers.size());
301331

302-
return getEngine().getMiniMessage().deserialize(text, resolvers); // Parse the final string to a Component
332+
// Parse the final string to a Component
333+
if (audienceTarget != null) {
334+
335+
// MiniPlaceholders 3.X relational placeholder support
336+
if (audienceRelation != null) {
337+
lazyloadMiniPlaceholders();
338+
if (MINIPLACEHOLDERS_METHOD_RELATIONAL != null) {
339+
try {
340+
final ForwardingAudience.Single relationalAudience = (ForwardingAudience.Single) MINIPLACEHOLDERS_METHOD_RELATIONAL.invoke(null, audienceTarget, audienceRelation);
341+
return getEngine().getMiniMessage().deserialize(text, relationalAudience, resolvers);
342+
} catch (Exception ignored) {
343+
}
344+
}
345+
}
346+
return getEngine().getMiniMessage().deserialize(text, audienceTarget, resolvers);
347+
}
348+
return getEngine().getMiniMessage().deserialize(text, resolvers);
303349
}
304350

305351
/**
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package io.github.milkdrinkers.colorparser.common.placeholder.provider;
2+
3+
import net.kyori.adventure.audience.Audience;
4+
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
5+
import org.jetbrains.annotations.Nullable;
6+
7+
import java.lang.reflect.InvocationTargetException;
8+
import java.lang.reflect.Method;
9+
import java.util.Optional;
10+
11+
/**
12+
* Used by MiniPlaceholders Placeholder Providers to handle reflection and multi-version support.
13+
*/
14+
public abstract class MiniPlaceholdersReflectionImpl {
15+
private enum Version {
16+
V3,
17+
V2V1,
18+
UNKNOWN
19+
}
20+
21+
private volatile boolean initialized = false;
22+
private volatile Version version = Version.UNKNOWN;
23+
private volatile @Nullable Method global = null;
24+
private volatile @Nullable Method audience = null;
25+
private volatile @Nullable Method placeholders = null;
26+
27+
/**
28+
* Lazily initializes fields for MiniPlaceholders usage.
29+
* This also implements code to make usage of MiniPlaceholders version agnostic between V2 and V3.
30+
*/
31+
protected void lazyLoad() {
32+
// Lazily do reflection
33+
if (!initialized) {
34+
synchronized (MiniPlaceholdersReflectionImpl.class) {
35+
try {
36+
final ClassLoader classLoader = this.getClass().getClassLoader();
37+
38+
// Figure out what version of mini placeholders is running based on present classes
39+
try {
40+
final Class<?> miniPlaceholdersClass = classLoader.loadClass("io.github.miniplaceholders.api.MiniPlaceholders");
41+
miniPlaceholdersClass.getMethod("getGlobalPlaceholders");
42+
version = Version.V2V1;
43+
} catch (ClassNotFoundException | NoSuchMethodException | RuntimeException e) {
44+
try {
45+
final Class<?> miniPlaceholdersClass = classLoader.loadClass("io.github.miniplaceholders.api.MiniPlaceholders");
46+
miniPlaceholdersClass.getMethod("globalPlaceholders");
47+
version = Version.V3;
48+
} catch (ClassNotFoundException | NoSuchMethodException | RuntimeException e2) {
49+
version = Version.UNKNOWN;
50+
}
51+
}
52+
53+
// Lazy initialize fields depending on running version
54+
if (!version.equals(Version.UNKNOWN) && classLoader != null) {
55+
final Class<?> miniPlaceholdersClass = classLoader.loadClass("io.github.miniplaceholders.api.MiniPlaceholders");
56+
global = isV3() ? miniPlaceholdersClass.getMethod("globalPlaceholders") : miniPlaceholdersClass.getMethod("getGlobalPlaceholders");
57+
audience = isV3() ? miniPlaceholdersClass.getMethod("audiencePlaceholders") : miniPlaceholdersClass.getMethod("getAudiencePlaceholders", Audience.class);
58+
placeholders = isV3() ? miniPlaceholdersClass.getMethod("relationalPlaceholders") : miniPlaceholdersClass.getMethod("getRelationalPlaceholders", Audience.class, Audience.class);
59+
}
60+
} catch (Exception ignored) {
61+
} finally {
62+
initialized = true;
63+
}
64+
}
65+
}
66+
}
67+
68+
/**
69+
* Whether the running version of MiniPlaceholders is version 3.X.X.
70+
* @return boolean
71+
*/
72+
protected boolean isV3() {
73+
return version.equals(Version.V3);
74+
}
75+
76+
/**
77+
* Whether the running version of MiniPlaceholders is version 2.X.X.
78+
* @return boolean
79+
*/
80+
protected boolean isV2() {
81+
return version.equals(Version.V2V1);
82+
}
83+
84+
/**
85+
* Whether the running version of MiniPlaceholders is version unknown.
86+
* @return boolean
87+
*/
88+
protected boolean isUnknown() {
89+
return version.equals(Version.UNKNOWN);
90+
}
91+
92+
/**
93+
* Get the global tag resolver method as an optional.
94+
* @return optional method
95+
*/
96+
private Optional<Method> getGlobal() {
97+
return Optional.ofNullable(global);
98+
}
99+
100+
/**
101+
* Get the audience tag resolver method as an optional.
102+
* @return optional method
103+
*/
104+
private Optional<Method> getAudience() {
105+
return Optional.ofNullable(audience);
106+
}
107+
108+
/**
109+
* Get the relational tag resolver method as an optional.
110+
* @return optional method
111+
*/
112+
private Optional<Method> getRelational() {
113+
return Optional.ofNullable(placeholders);
114+
}
115+
116+
/**
117+
* Get a global tag resolver from MiniPlaceholders.
118+
* @return the tag resolver or an empty tag resolver as fallback
119+
*/
120+
protected TagResolver resolveGlobal() {
121+
return getGlobal()
122+
.map(method -> {
123+
try {
124+
return (TagResolver) method.invoke(null);
125+
} catch (IllegalAccessException | InvocationTargetException e) {
126+
return TagResolver.empty();
127+
}
128+
})
129+
.orElse(TagResolver.empty());
130+
}
131+
132+
/**
133+
* Get an audience tag resolver from MiniPlaceholders.
134+
* @param audience1 the audience to resolve tags for (or null if using MiniPlaceholders v3.X.X)
135+
* @return the tag resolver or an empty tag resolver as fallback
136+
*/
137+
protected TagResolver resolveAudience(final @Nullable Audience audience1) {
138+
if (audience1 == null)
139+
return resolveGlobal();
140+
141+
return getAudience()
142+
.map(method -> {
143+
try {
144+
return TagResolver.resolver(
145+
resolveGlobal(),
146+
isV3() ?
147+
(TagResolver) method.invoke(null) :
148+
(TagResolver) method.invoke(null, audience1)
149+
);
150+
} catch (IllegalAccessException | InvocationTargetException e) {
151+
return TagResolver.empty();
152+
}
153+
})
154+
.orElse(TagResolver.empty());
155+
}
156+
157+
/**
158+
* Get a relational tag resolver from MiniPlaceholders.
159+
* @param audience1 the audience to resolve tags for (or null if using MiniPlaceholders v3.X.X)
160+
* @param audience2 the audience to resolve tags for (or null if using MiniPlaceholders v3.X.X)
161+
* @return the tag resolver or an empty tag resolver as fallback
162+
*/
163+
protected TagResolver resolveRelational(final @Nullable Audience audience1, final @Nullable Audience audience2) {
164+
if (audience1 == null || audience2 == null)
165+
return resolveAudience(audience1);
166+
167+
return getRelational()
168+
.map(method -> {
169+
try {
170+
return TagResolver.resolver(
171+
resolveAudience(audience1),
172+
isV3() ?
173+
(TagResolver) method.invoke(null) :
174+
(TagResolver) method.invoke(null, audience1, audience2)
175+
);
176+
} catch (IllegalAccessException | InvocationTargetException e) {
177+
return TagResolver.empty();
178+
}
179+
})
180+
.orElse(TagResolver.empty());
181+
}
182+
}

paper/src/main/java/io/github/milkdrinkers/colorparser/paper/PaperComponentBuilder.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import org.jetbrains.annotations.NotNull;
1313
import org.jetbrains.annotations.Nullable;
1414

15+
import java.util.Objects;
16+
1517
/**
1618
* Platform-specific {@link ComponentBuilder} for creating Adventure {@link Component}'s.
1719
* <p>
@@ -121,6 +123,7 @@ public PaperComponentBuilder mini() {
121123
*/
122124
@NotNull
123125
public PaperComponentBuilder mini(@NotNull Player player) {
126+
audienceTarget = player.filterAudience(a -> true);
124127
miniPlaceholdersContext = new PaperPlaceholderContext(PlaceholderContext.Type.PLAYER, new PaperPlayer(player), null);
125128
miniPlaceholdersEnabled = true;
126129
return this;
@@ -135,6 +138,8 @@ public PaperComponentBuilder mini(@NotNull Player player) {
135138
*/
136139
@NotNull
137140
public PaperComponentBuilder mini(@NotNull OfflinePlayer player) {
141+
if (player.isOnline())
142+
audienceTarget = Objects.requireNonNull(player.getPlayer()).filterAudience(a -> true);
138143
miniPlaceholdersContext = new PaperPlaceholderContext(PlaceholderContext.Type.PLAYER, new PaperPlayer(player), null);
139144
miniPlaceholdersEnabled = true;
140145
return this;
@@ -149,6 +154,8 @@ public PaperComponentBuilder mini(@NotNull OfflinePlayer player) {
149154
*/
150155
@NotNull
151156
public PaperComponentBuilder mini(@NotNull Player player1, @NotNull Player player2) {
157+
audienceTarget = player1.filterAudience(a -> true);
158+
audienceRelation = player2.filterAudience(a -> true);
152159
miniPlaceholdersContext = new PaperPlaceholderContext(PlaceholderContext.Type.RELATIONAL, new PaperPlayer(player1), new PaperPlayer(player2));
153160
miniPlaceholdersEnabled = true;
154161
return this;

0 commit comments

Comments
 (0)