22 Commits
Author SHA1 Message Date
XiaoMoMi cfef863636 Avoid duplicate placeholder traversal and idle notifications 2026-10-28 02:24:45 +08:00
WarnDa 92fe8b1dc0 Avoid duplicate placeholder walks and idle notification tasks 2026-10-25 20:48:29 +01:00
XiaoMoMi 61d66eb731 Merge pull request #93 from HyperNai/fix/itemsadder-maven-repo
Fix ItemsAdder API dependency after maven.devs.beer outage
2026-09-28 08:40:27 +08:00
arascalik1234 d0a6d7800b Fix ItemsAdder API dependency after maven.devs.beer outage
ItemsAdder now publishes its API to Maven Central as beer.devs:itemsadder-api instead of dev.lone:api-itemsadder via the now-unreachable maven.devs.beer repository. This also unblocks resolution of ZelChat and HMCCosmetics/HibiscusCommons, which were failing due to the hard error from that repo.
2026-09-27 23:16:52 +03:00
XiaoMoMi 77d9006533 bump version 2026-09-28 03:12:44 +08:00
XiaoMoMi 923c7d115a delete old dependencies 2026-09-28 03:03:27 +08:00
XiaoMoMi d561ba7378 26.3 2026-09-28 02:48:56 +08:00
XiaoMoMi dd2a868e81 fix packet sending on folia 2026-09-28 02:31:59 +08:00
XiaoMoMi 22e1ef21ea remove hover events from chat bubble message 2026-09-10 10:50:36 +08:00
XiaoMoMi 5dd7747dba Update PacketEvent.java 2026-08-23 00:10:57 +08:00
XiaoMoMi 4317718e9d possibly fix teleport issue 2026-08-21 09:29:15 +08:00
XiaoMoMi 2a6704444b Update build.gradle.kts 2026-08-21 06:53:32 +08:00
XiaoMoMi c461e872c4 3.0.44 2026-08-21 06:52:18 +08:00
XiaoMoMi 8224aea281 fix convertor 2026-07-21 08:02:07 +08:00
XiaoMoMi bad0e2dbf2 fix reflection 2026-07-21 07:32:15 +08:00
XiaoMoMi c01330a92a fix translation 2026-07-15 03:32:50 +08:00
XiaoMoMi bbdd5e8a6f add durationPerCharacter and maxStayDuration 2026-07-15 03:08:33 +08:00
XiaoMoMi 0e9edf59bc add affected-by-scale-attribute option for bubbles 2026-07-15 02:57:50 +08:00
XiaoMoMi 8917675b46 update oraxen api 2026-07-14 20:44:56 +08:00
XiaoMoMi e09b13677e 3.0.42 2026-07-08 21:24:58 +08:00
XiaoMoMi dd71132845 26.2 2026-07-05 23:01:50 +08:00
XiaoMoMi dfde885861 3.0.40 2026-06-09 10:39:14 +08:00
39 changed files with 499 additions and 187 deletions

No files matched your search

+1 -1
View File
@@ -21,7 +21,7 @@ dependencies {
// Cache
compileOnly("com.github.ben-manes.caffeine:caffeine:${rootProject.properties["caffeine_version"]}")
// Netty
compileOnly("io.netty:netty-all:4.1.117.Final")
compileOnly("io.netty:netty-all:${rootProject.properties["netty_version"]}")
// GSON
compileOnly("com.google.code.gson:gson:${rootProject.properties["gson_version"]}")
// Fast util
@@ -20,6 +20,7 @@ package net.momirealms.customnameplates.api;
import io.netty.channel.Channel;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
import net.momirealms.customnameplates.api.feature.Feature;
import net.momirealms.customnameplates.api.feature.TimeStampData;
import net.momirealms.customnameplates.api.feature.tag.TeamView;
@@ -33,8 +34,6 @@ import net.momirealms.customnameplates.api.storage.data.PlayerData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
@@ -115,11 +114,11 @@ public abstract class AbstractCNPlayer<P> implements CNPlayer {
@Override
public List<Placeholder> activePlaceholdersToRefresh() {
Placeholder[] activePlaceholders = activePlaceholders();
List<Placeholder> placeholderWithChildren = new ObjectArrayList<>();
Set<Placeholder> placeholderWithChildren = new ObjectLinkedOpenHashSet<>();
for (Placeholder placeholder : activePlaceholders) {
childrenFirstList(placeholder, placeholderWithChildren);
}
return placeholderWithChildren.stream().distinct().toList();
return List.copyOf(placeholderWithChildren);
}
private String updatePlayerPlaceholder(PlayerPlaceholder placeholder) {
@@ -187,11 +186,10 @@ public abstract class AbstractCNPlayer<P> implements CNPlayer {
@Override
public void forceUpdatePlaceholders(Set<Placeholder> placeholders, Collection<CNPlayer> others) {
if (placeholders.isEmpty()) return;
List<Placeholder> placeholderWithChildren = new ObjectArrayList<>();
Set<Placeholder> placeholderWithChildren = new ObjectLinkedOpenHashSet<>();
for (Placeholder placeholder : placeholders) {
childrenFirstList(placeholder, placeholderWithChildren);
}
placeholderWithChildren = placeholderWithChildren.stream().distinct().toList();
for (Placeholder placeholder : placeholderWithChildren) {
if (placeholder instanceof PlayerPlaceholder playerPlaceholder) {
updatePlayerPlaceholder(playerPlaceholder);
@@ -203,15 +201,12 @@ public abstract class AbstractCNPlayer<P> implements CNPlayer {
}
}
private void childrenFirstList(Placeholder placeholder, List<Placeholder> list) {
if (placeholder.children().isEmpty()) {
list.add(placeholder);
} else {
for (Placeholder child : placeholder.children()) {
childrenFirstList(child, list);
}
list.add(placeholder);
private void childrenFirstList(Placeholder placeholder, Set<Placeholder> list) {
if (list.contains(placeholder)) return;
for (Placeholder child : placeholder.children()) {
childrenFirstList(child, list);
}
list.add(placeholder);
}
private void parentLastList(Placeholder placeholder, List<Placeholder> list) {
@@ -112,6 +112,13 @@ public interface BubbleConfig {
*/
Vector3 scale();
/**
* Checks if the bubble is affected by scaling.
*
* @return true if affected by scaling, false otherwise
*/
boolean affectedByScaling();
/**
* Creates a new builder for constructing a BubbleConfig.
*
@@ -206,6 +213,14 @@ public interface BubbleConfig {
*/
Builder scale(Vector3 scale);
/**
* Sets if the bubble is affected by scaling
*
* @param affectedByScaling affected by scaling
* @return the builder instance
*/
Builder affectedByScaling(boolean affectedByScaling);
/**
* Sets if the text has shadow
*
@@ -41,6 +41,7 @@ public class BubbleConfigImpl implements BubbleConfig {
private final Vector3 scale;
private final boolean hasShadow;
private final Billboard billboard;
private final boolean affectedByScaling;
/**
* Constructs a new BubbleConfigImpl.
@@ -56,8 +57,10 @@ public class BubbleConfigImpl implements BubbleConfig {
* @param textSuffix a suffix that will be added after the text in the bubble
* @param scale the scale of the bubble display (as a Vector3)
* @param hasShadow has shadow or not
* @param billboard billboard
* @param affectedByScaling if the bubble is affected by scaling
*/
public BubbleConfigImpl(String id, String commandSuggestion, String displayName, int backgroundColor, int lineWidth, int maxLines, Bubble[] bubbles, String textPrefix, String textSuffix, Vector3 scale, boolean hasShadow, Billboard billboard) {
public BubbleConfigImpl(String id, String commandSuggestion, String displayName, int backgroundColor, int lineWidth, int maxLines, Bubble[] bubbles, String textPrefix, String textSuffix, Vector3 scale, boolean hasShadow, Billboard billboard, boolean affectedByScaling) {
this.backgroundColor = backgroundColor;
this.lineWidth = lineWidth;
this.maxLines = maxLines;
@@ -70,6 +73,7 @@ public class BubbleConfigImpl implements BubbleConfig {
this.scale = requireNonNull(scale);
this.hasShadow = hasShadow;
this.billboard = billboard;
this.affectedByScaling = affectedByScaling;
}
@Override
@@ -132,6 +136,11 @@ public class BubbleConfigImpl implements BubbleConfig {
return billboard;
}
@Override
public boolean affectedByScaling() {
return affectedByScaling;
}
/**
* Builder implementation for creating BubbleConfigImpl instances.
*/
@@ -148,6 +157,7 @@ public class BubbleConfigImpl implements BubbleConfig {
private Vector3 scale;
private boolean hasShadow;
private Billboard billboard;
private boolean affectedByScaling = true;
@Override
public Builder id(String id) {
@@ -221,9 +231,15 @@ public class BubbleConfigImpl implements BubbleConfig {
return this;
}
@Override
public Builder affectedByScaling(boolean affectedByScaling) {
this.affectedByScaling = affectedByScaling;
return this;
}
@Override
public BubbleConfig build() {
return new BubbleConfigImpl(id, Optional.ofNullable(this.commandSuggestion).orElse(id), displayName, backgroundColor, lineWidth, maxLines, bubbles, textPrefix, textSuffix, scale, hasShadow, billboard);
return new BubbleConfigImpl(id, Optional.ofNullable(this.commandSuggestion).orElse(id), displayName, backgroundColor, lineWidth, maxLines, bubbles, textPrefix, textSuffix, scale, hasShadow, billboard, affectedByScaling);
}
}
}
@@ -137,6 +137,21 @@ public interface BubbleManager extends Reloadable {
*/
int stayDuration();
/**
* Returns the additional duration (in ticks) added per visible character in the Bubble text.
*
* @return the duration per character in ticks
*/
double durationPerCharacter();
/**
* Returns the maximum duration (in ticks) for which the Bubble remains visible.
* A value of 0 means no maximum.
*
* @return the maximum stay duration in ticks
*/
int maxStayDuration();
/**
* Returns the duration (in ticks) for the Bubble to appear.
*
@@ -27,6 +27,7 @@ import net.kyori.adventure.text.serializer.json.JSONOptions;
import net.kyori.adventure.text.serializer.json.legacyimpl.NBTLegacyHoverEventSerializer;
import net.momirealms.customnameplates.api.ConfigManager;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.common.text.minimessage.HeadTextureTag;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -52,8 +53,9 @@ public class AdventureHelper {
private final Cache<String, String> jsonToMiniMessageCache;
private AdventureHelper() {
this.miniMessage = MiniMessage.builder().build();
this.miniMessageStrict = MiniMessage.builder().strict(true).build();
MiniMessage.Builder miniMessageBuilder = MiniMessage.builder().editTags(tags -> tags.resolver(HeadTextureTag.INSTANCE));
this.miniMessage = miniMessageBuilder.build();
this.miniMessageStrict = miniMessageBuilder.strict(true).build();
GsonComponentSerializer.Builder builder = GsonComponentSerializer.builder();
if (!VersionHelper.isVersionNewerThan1_20_5()) {
builder.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get());
@@ -179,6 +179,14 @@ public class VersionHelper {
return version >= 260100;
}
public static boolean isVersionNewerThan26_2() {
return version >= 260200;
}
public static boolean isVersionNewerThan26_3() {
return version >= 260300;
}
/**
* Checks if the server version is newer than 1.21.9
*
@@ -19,13 +19,18 @@ package net.momirealms.customnameplates.api.network;
import net.momirealms.customnameplates.common.event.Cancellable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
/**
* Represents a packet event, which can be cancelled and supports delayed tasks that are executed later.
*/
public class PacketEvent implements Cancellable {
private boolean cancelled;
private final Object packet;
private boolean cancelled;
private List<Runnable> afterSendTasks;
/**
* Constructs a new PacketEvent with the specified packet.
@@ -45,6 +50,42 @@ public class PacketEvent implements Cancellable {
return packet;
}
/**
* Registers a task that must run only after this packet has been forwarded
* to the next outbound handler.
*
* @param task task to execute after the packet is sent
*/
public void afterSend(Runnable task) {
this.afterSendTasks().add(Objects.requireNonNull(task, "task"));
}
public List<Runnable> afterSendTasks() {
if (this.afterSendTasks == null) {
this.afterSendTasks = new ArrayList<>(1);
}
return this.afterSendTasks;
}
/**
* Executes and clears all registered after-send tasks. A failure in one
* task is reported without preventing the remaining tasks from running.
*
* @param exceptionHandler handler for failures raised by after-send tasks
*/
public void runAfterSendTasks(Consumer<Throwable> exceptionHandler) {
if (this.afterSendTasks != null) {
Objects.requireNonNull(exceptionHandler, "exceptionHandler");
for (Runnable task : this.afterSendTasks) {
try {
task.run();
} catch (Throwable throwable) {
exceptionHandler.accept(throwable);
}
}
}
}
/**
* Checks if the event has been cancelled.
*
@@ -64,4 +105,4 @@ public class PacketEvent implements Cancellable {
public void cancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}
}
@@ -19,7 +19,9 @@ package net.momirealms.customnameplates.api.network;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.momirealms.customnameplates.api.CNPlayer;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.WeakReference;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -31,7 +33,7 @@ public class Tracker {
private boolean isCrouching;
private double scale;
private boolean isSpectator;
private final CNPlayer tracker;
private final WeakReference<CNPlayer> tracker;
private final CopyOnWriteArrayList<Integer> passengerIDs = new CopyOnWriteArrayList<>();
/**
@@ -42,16 +44,17 @@ public class Tracker {
public Tracker(CNPlayer tracker) {
this.isCrouching = false;
this.scale = 1;
this.tracker = tracker;
this.tracker = new WeakReference<>(tracker);
}
/**
* Retrieves the tracker
*
* @return the tracker
* @return the tracker, or {@code null} if it is no longer reachable
*/
@Nullable
public CNPlayer tracker() {
return tracker;
return tracker.get();
}
/**
@@ -419,6 +419,10 @@ public enum Dependency {
return s.replace("{}", ".");
}
public String toLocalPath() {
return rewriteEscaping(groupId).replace(".", "/") + "/" + rewriteEscaping(artifactId) + "/" + getVersion();
}
/**
* Returns the filename for the dependency's JAR file, optionally with a classifier.
*
@@ -35,8 +35,10 @@ import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -53,7 +55,7 @@ public class DependencyManagerImpl implements DependencyManager {
/** The classpath appender to preload dependencies into */
private final ClassPathAppender classPathAppender;
/** A map of dependencies which have already been loaded. */
private final EnumMap<Dependency, Path> loaded = new EnumMap<>(Dependency.class);
private final Map<Dependency, Path> loaded = Collections.synchronizedMap(new EnumMap<>(Dependency.class));
/** A map of isolated classloaders which have been created. */
private final Map<Set<Dependency>, IsolatedClassLoader> loaders = new HashMap<>();
/** Cached relocation handler instance. */
@@ -152,7 +154,8 @@ public class DependencyManagerImpl implements DependencyManager {
private Path downloadDependency(Dependency dependency) throws DependencyDownloadException {
String fileName = dependency.getFileName(null);
Path file = this.cacheDirectory.resolve(fileName);
Path file = this.cacheDirectory.resolve(dependency.toLocalPath()).resolve(fileName);
cleanOutdatedVersions(file.getParent());
// if the file already exists, don't attempt to re-download it.
if (Files.exists(file)) {
@@ -179,13 +182,39 @@ public class DependencyManagerImpl implements DependencyManager {
throw Objects.requireNonNull(lastError);
}
private void cleanOutdatedVersions(Path currentVersionDirectory) {
Path artifactDirectory = currentVersionDirectory.getParent();
if (!Files.isDirectory(artifactDirectory)) return;
Set<Path> loadedVersionDirectories = new HashSet<>();
synchronized (this.loaded) {
this.loaded.values().forEach(path -> loadedVersionDirectories.add(path.getParent()));
}
try (DirectoryStream<Path> directories = Files.newDirectoryStream(artifactDirectory)) {
for (Path directory : directories) {
if (!Files.isDirectory(directory) || directory.equals(currentVersionDirectory)
|| loadedVersionDirectories.contains(directory)) continue;
try (Stream<Path> paths = Files.walk(directory)) {
for (Path path : paths.sorted(Comparator.reverseOrder()).toList()) {
Files.delete(path);
}
this.plugin.getPluginLogger().info("Cleaned up outdated dependency " + directory);
} catch (IOException | RuntimeException e) {
this.plugin.getPluginLogger().warn("Failed to clean outdated dependency " + directory, e);
}
}
} catch (IOException e) {
this.plugin.getPluginLogger().warn("Failed to clean outdated dependencies in " + artifactDirectory, e);
}
}
private Path remapDependency(Dependency dependency, Path normalFile) throws Exception {
List<Relocation> rules = new ArrayList<>(dependency.getRelocations());
if (rules.isEmpty()) {
return normalFile;
}
Path remappedFile = this.cacheDirectory.resolve(dependency.getFileName(DependencyRegistry.isGsonRelocated() ? "remapped-legacy" : "remapped"));
Path remappedFile = normalFile.resolveSibling(dependency.getFileName(DependencyRegistry.isGsonRelocated() ? "remapped-legacy" : "remapped"));
// if the remapped source exists already, just use that.
if (Files.exists(remappedFile)) {
@@ -202,6 +231,7 @@ public class DependencyManagerImpl implements DependencyManager {
Path cacheDirectory = plugin.getDataDirectory().resolve("libs");
try {
if (Files.exists(cacheDirectory) && (Files.isDirectory(cacheDirectory) || Files.isSymbolicLink(cacheDirectory))) {
cleanDirectoryJars(plugin, cacheDirectory);
return cacheDirectory;
}
@@ -217,6 +247,19 @@ public class DependencyManagerImpl implements DependencyManager {
return cacheDirectory;
}
private static void cleanDirectoryJars(NameplatesPlugin plugin, Path directory) throws IOException {
try (DirectoryStream<Path> files = Files.newDirectoryStream(directory, "*.jar")) {
for (Path file : files) {
if (!Files.isRegularFile(file)) continue;
try {
Files.delete(file);
} catch (IOException e) {
plugin.getPluginLogger().warn("Failed to clean legacy dependency " + file, e);
}
}
}
}
@Override
public void close() {
IOException firstEx = null;
@@ -142,6 +142,7 @@ public enum DependencyRepository {
*/
public void download(Dependency dependency, Path file) throws DependencyDownloadException {
try {
Files.createDirectories(file.getParent());
Files.write(file, download(dependency));
} catch (IOException e) {
throw new DependencyDownloadException(e);
@@ -38,7 +38,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import static java.util.Objects.requireNonNull;
@@ -20,8 +20,8 @@ package net.momirealms.customnameplates.common.locale;
import dev.dejvokep.boostedyaml.YamlDocument;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.translation.Translator;
import net.momirealms.customnameplates.api.helper.AdventureHelper;
import net.momirealms.customnameplates.common.plugin.CustomNameplatesProperties;
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
import net.momirealms.customnameplates.common.util.Pair;
@@ -69,7 +69,7 @@ public class TranslationManager {
this.plugin.getConfigManager().saveResource("translations/" + lang + ".yml");
}
this.registry = MiniMessageTranslationRegistry.create(Key.key("customnameplates", "main"), MiniMessage.miniMessage());
this.registry = MiniMessageTranslationRegistry.create(Key.key("customnameplates", "main"), AdventureHelper.miniMessage());
this.registry.defaultLocale(DEFAULT_LOCALE);
this.loadFromFileSystem(this.translationsDirectory, false);
MiniMessageTranslator.translator().addSource(this.registry);
@@ -25,6 +25,7 @@
package net.momirealms.customnameplates.common.plugin.scheduler;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
import java.lang.Thread.UncaughtExceptionHandler;
@@ -96,7 +97,7 @@ public abstract class AbstractJavaScheduler<T> implements SchedulerAdapter<T> {
private void reportRunningTasks(Predicate<Thread> predicate) {
Thread.getAllStackTraces().forEach((thread, stack) -> {
if (predicate.test(thread)) {
this.plugin.getPluginLogger().warn("Thread " + thread.getName() + " is blocked, and may be the reason for the slow shutdown!\n" +
CustomNameplates.getInstance().getPluginLogger().warn("Thread " + thread.getName() + " is blocked, and may be the reason for the slow shutdown!\n" +
Arrays.stream(stack).map(el -> " " + el).collect(Collectors.joining("\n"))
);
}
@@ -115,10 +116,10 @@ public abstract class AbstractJavaScheduler<T> implements SchedulerAdapter<T> {
}
}
private final class ExceptionHandler implements UncaughtExceptionHandler {
private static final class ExceptionHandler implements UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
AbstractJavaScheduler.this.plugin.getPluginLogger().warn("Thread " + t.getName() + " threw an uncaught exception", e);
CustomNameplates.getInstance().getPluginLogger().warn("Thread " + t.getName() + " threw an uncaught exception", e);
}
}
}
@@ -25,7 +25,6 @@
package net.momirealms.customnameplates.common.sender;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
import net.momirealms.customnameplates.common.util.Tristate;
@@ -54,8 +53,6 @@ public abstract class SenderFactory<P extends NameplatesPlugin, T> implements Au
protected abstract String getName(T sender);
public abstract Audience getAudience(T sender);
protected abstract void sendMessage(T sender, Component message);
protected abstract Tristate getPermissionValue(T sender, String node);
@@ -0,0 +1,59 @@
package net.momirealms.customnameplates.common.text.minimessage;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.ParsingException;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.object.ObjectContents;
import net.kyori.adventure.text.object.PlayerHeadObjectContents;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public final class HeadTextureTag implements TagResolver {
public static final HeadTextureTag INSTANCE = new HeadTextureTag();
private HeadTextureTag() {
}
@Override
public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) throws ParsingException {
if (!has(name)) return null;
String texture = arguments.popOr("No texture hash provided").lowerValue();
if (!isTextureHash(texture)) {
throw ctx.newException("Invalid texture hash", arguments);
}
if (arguments.hasNext()) {
throw ctx.newException("Too many arguments present", arguments);
}
String profile = "{\"textures\":{\"SKIN\":{\"url\":\"https://textures.minecraft.net/texture/" + texture + "\"}}}";
String encoded = Base64.getEncoder().encodeToString(profile.getBytes(StandardCharsets.UTF_8));
return Tag.selfClosingInserting(Component.object(ObjectContents.playerHead()
.profileProperty(PlayerHeadObjectContents.property("textures", encoded))
.build()));
}
@Override
public boolean has(@NotNull String name) {
return name.equals("head_texture");
}
private static boolean isTextureHash(String texture) {
int length = texture.length();
if (length == 0 || length > 64) {
return false;
}
for (int i = 0; i < length; i++) {
char c = texture.charAt(i);
if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) {
return false;
}
}
return true;
}
}
@@ -54,7 +54,6 @@ import net.momirealms.customnameplates.api.util.CharacterUtils;
import net.momirealms.customnameplates.backend.util.FreeTypeUtils;
import net.momirealms.customnameplates.common.util.Tuple;
import org.apache.commons.io.FileUtils;
import org.bson.types.MaxKey;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryStack;
@@ -29,6 +29,7 @@ import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings;
import dev.dejvokep.boostedyaml.utils.format.NodeRole;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.ConfigManager;
import net.momirealms.customnameplates.api.CustomNameplates;
@@ -52,6 +53,9 @@ import java.util.*;
import java.util.concurrent.TimeUnit;
public class BubbleManagerImpl implements BubbleManager, ChatListener {
private static final TagResolver IGNORE_HOVER = TagResolver.resolver(
"hover", (arguments, context) -> net.kyori.adventure.text.minimessage.tag.Tag.styling());
private final CustomNameplates plugin;
private final Map<String, Bubble> bubbles = new Object2ObjectOpenHashMap<>();
private Requirement[] sendBubbleRequirements;
@@ -61,6 +65,8 @@ public class BubbleManagerImpl implements BubbleManager, ChatListener {
private int stayDuration;
private int appearDuration;
private int disappearDuration;
private double durationPerCharacter;
private int maxStayDuration;
private float viewRange;
private Set<String> blacklistChannels;
private ChannelMode channelMode;
@@ -162,6 +168,16 @@ public class BubbleManagerImpl implements BubbleManager, ChatListener {
return stayDuration;
}
@Override
public double durationPerCharacter() {
return durationPerCharacter;
}
@Override
public int maxStayDuration() {
return maxStayDuration;
}
@Override
public int appearDuration() {
return appearDuration;
@@ -219,7 +235,9 @@ public class BubbleManagerImpl implements BubbleManager, ChatListener {
viewBubbleRequirements = plugin.getRequirementManager().parseRequirements(document.getSection("viewer-requirements"));
defaultBubbleId = document.getString("default-bubble", "chat");
yOffset = document.getDouble("y-offset", 0.2);
stayDuration = document.getInt("stay-duration", 160);
stayDuration = document.getInt("stay-duration", 100);
durationPerCharacter = document.getDouble("duration-per-character", 2.0);
maxStayDuration = document.getInt("max-stay-duration", 0);
appearDuration = document.getInt("appear-duration", 20);
disappearDuration = document.getInt("disappear-duration", 10);
viewRange = document.getFloat("view-range", 0.5f);
@@ -248,6 +266,7 @@ public class BubbleManagerImpl implements BubbleManager, ChatListener {
.scale(ConfigUtils.vector3(inner.getString("scale", "1,1,1")))
.hasShadow(inner.getBoolean("has-shadow", false))
.billboard(inner.getEnum("billboard", Billboard.class, Billboard.CENTER))
.affectedByScaling(inner.getBoolean("affected-by-scale-attribute", true))
.build();
this.bubbleConfigs.put(bubble.id(), bubble);
this.bubbleConfigsByCommand.put(bubble.commandSuggestion(), bubble);
@@ -319,6 +338,8 @@ public class BubbleManagerImpl implements BubbleManager, ChatListener {
}
String fullText = config.textPrefix().fastCreate(player).render(player) + message.replace("\\", "\\\\") + config.textSuffix().fastCreate(player).render(player);
// Bubbles only display text; discard hover payloads before layout and component conversion.
fullText = AdventureHelper.miniMessage().serialize(AdventureHelper.miniMessage().deserialize(fullText, IGNORE_HOVER));
int lines = plugin.getAdvanceManager().getLines(fullText, config.lineWidth());
if (lines > config.maxLines()) return;
if (lines <= 0) return;
@@ -342,9 +363,10 @@ public class BubbleManagerImpl implements BubbleManager, ChatListener {
advance = config.lineWidth();
}
int textLength = AdventureHelper.stripTags(message).length();
BubbleTag bubbleTag = new BubbleTag(player, renderer, channel, config,
AdventureHelper.miniMessageToMinecraftComponent(fullText),
bubble == null ? null : AdventureHelper.miniMessageToMinecraftComponent(AdventureHelper.surroundWithNameplatesFont(bubble.createImage(advance, 1,1))), this);
bubble == null ? null : AdventureHelper.miniMessageToMinecraftComponent(AdventureHelper.surroundWithNameplatesFont(bubble.createImage(advance, 1,1))), this, textLength);
renderer.addTag(bubbleTag);
if (delay != 0) {
plugin.getScheduler().asyncLater(() -> bubbleTag.setCanShow(true), delay * 50L, TimeUnit.MILLISECONDS);
@@ -26,7 +26,6 @@ import net.momirealms.customnameplates.api.feature.tag.Tag;
import net.momirealms.customnameplates.api.feature.tag.TagRenderer;
import net.momirealms.customnameplates.api.network.Tracker;
import net.momirealms.customnameplates.api.util.Alignment;
import net.momirealms.customnameplates.api.util.Billboard;
import net.momirealms.customnameplates.api.util.SelfIncreaseEntityID;
import net.momirealms.customnameplates.api.util.Vector3;
import org.jetbrains.annotations.Nullable;
@@ -45,6 +44,7 @@ public class BubbleTag extends AbstractTag {
private final Object background;
private final BubbleManager manager;
private final BubbleConfig bubbleConfig;
private final int stayDuration;
private int ticker;
private boolean canShow;
private final String channel;
@@ -53,13 +53,19 @@ public class BubbleTag extends AbstractTag {
protected final int subEntityID = SelfIncreaseEntityID.getAndIncrease();
protected final UUID subEntityUUID = UUID.randomUUID();
public BubbleTag(CNPlayer owner, TagRenderer renderer, String channel, BubbleConfig bubbleConfig, Object text, @Nullable Object background, BubbleManager bubbleManager) {
public BubbleTag(CNPlayer owner, TagRenderer renderer, String channel, BubbleConfig bubbleConfig, Object text, @Nullable Object background, BubbleManager bubbleManager, int textLength) {
super(owner, renderer);
this.text = text;
this.manager = bubbleManager;
this.bubbleConfig = bubbleConfig;
this.channel = channel;
this.background = background;
int base = bubbleManager.stayDuration();
int extra = (int) (textLength * bubbleManager.durationPerCharacter());
int duration = base + extra;
int max = bubbleManager.maxStayDuration();
if (max > 0 && duration > max) duration = max;
this.stayDuration = duration;
}
@Override
@@ -74,7 +80,7 @@ public class BubbleTag extends AbstractTag {
text, bubbleConfig.backgroundColor(), (byte) -1, bubbleConfig.hasShadow(), false, false,
Alignment.CENTER, bubbleConfig.billboard(), manager.viewRange(), 0.0f, 1.0f,
new Vector3(0.001, 0.001, 0.001),
affectedByScaling() ? translation.multiply(tracker.getScale()).add(0.01, 0, 0.01) : translation,
affectedByScaling() ? translation.multiply(tracker.getScale()).add(0.02, 0, 0.02) : translation.add(0.02, 0, 0.02),
bubbleConfig.lineWidth(),
(affectedByCrouching() && tracker.isCrouching())
));
@@ -162,7 +168,7 @@ public class BubbleTag extends AbstractTag {
@Override
public void tick() {
if (!canShow) return;
if (ticker >= manager.stayDuration()) {
if (ticker >= stayDuration) {
renderer.removeTag(this);
return;
}
@@ -173,7 +179,7 @@ public class BubbleTag extends AbstractTag {
public void onPlayerScaleUpdate(CNPlayer viewer, double scale) {
Consumer<List<Object>> modifier1 = CustomNameplates.getInstance().getPlatform().createScaleModifier(scale(viewer).multiply(scale));
Vector3 translation = translation(viewer);
Consumer<List<Object>> modifier2 = CustomNameplates.getInstance().getPlatform().createTranslationModifier(translation.multiply(scale).add(0.01,0,0.01));
Consumer<List<Object>> modifier2 = CustomNameplates.getInstance().getPlatform().createTranslationModifier(translation.multiply(scale).add(0.02,0,0.02));
Object packet1 = CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(entityID, List.of(modifier1, modifier2));
if (background != null) {
Consumer<List<Object>> modifier3 = CustomNameplates.getInstance().getPlatform().createTranslationModifier(translation.multiply(scale));
@@ -189,10 +195,10 @@ public class BubbleTag extends AbstractTag {
Tracker tracker = owner.getTracker(viewer);
if (tracker != null) {
Vector3 translation = translation(viewer);
Consumer<List<Object>> modifier1 = CustomNameplates.getInstance().getPlatform().createTranslationModifier(translation.multiply(tracker.getScale()).add(0.01,0,0.01));
Consumer<List<Object>> modifier1 = CustomNameplates.getInstance().getPlatform().createTranslationModifier(affectedByScaling() ? translation.multiply(tracker.getScale()).add(0.02,0,0.02) : translation.add(0.02,0,0.02));
Object packet1 = CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(entityID, List.of(modifier1));
if (background != null) {
Consumer<List<Object>> modifier2 = CustomNameplates.getInstance().getPlatform().createTranslationModifier(translation.multiply(tracker.getScale()));
Consumer<List<Object>> modifier2 = CustomNameplates.getInstance().getPlatform().createTranslationModifier(affectedByScaling() ? translation.multiply(tracker.getScale()) : translation);
Object packet2 = CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(subEntityID, List.of(modifier2));
CustomNameplates.getInstance().getPacketSender().sendPacket(viewer, List.of(packet1, packet2));
} else {
@@ -309,7 +315,7 @@ public class BubbleTag extends AbstractTag {
@Override
public boolean affectedByScaling() {
return true;
return bubbleConfig.affectedByScaling();
}
@Override
@@ -33,7 +33,6 @@ import net.momirealms.customnameplates.api.feature.image.Animation;
import net.momirealms.customnameplates.api.feature.image.Image;
import net.momirealms.customnameplates.api.feature.nameplate.Nameplate;
import net.momirealms.customnameplates.api.feature.pack.ResourcePackManager;
import net.momirealms.customnameplates.api.helper.AdventureHelper;
import net.momirealms.customnameplates.api.util.CharacterUtils;
import net.momirealms.customnameplates.api.util.ZipUtils;
import org.apache.commons.io.FileUtils;
@@ -79,7 +79,7 @@ public class TagRendererImpl implements TagRenderer {
@Override
public synchronized void onTick() {
if (!isValid()) return;
if (!isValid() || tagArray.length == 0) return;
this.ticks++;
Set<CNPlayer> playersToUpdatePassengers = new ObjectOpenHashSet<>();
@@ -185,7 +185,7 @@ public class TagRendererImpl implements TagRenderer {
}
@Override
public void addTag(Tag tag) {
public synchronized void addTag(Tag tag) {
if (tagVector.contains(tag)) {
return;
}
@@ -200,7 +200,7 @@ public class TagRendererImpl implements TagRenderer {
}
@Override
public void addTag(Tag tag, int index) {
public synchronized void addTag(Tag tag, int index) {
if (index < 0 || index > this.tagArray.length) {
throw new IndexOutOfBoundsException("Index out of bounds: " + index);
}
@@ -220,13 +220,12 @@ public class TagRendererImpl implements TagRenderer {
}
@Override
public int removeTagIf(Predicate<Tag> predicate) {
List<Integer> removedIndexes = new ArrayList<>();
public synchronized int removeTagIf(Predicate<Tag> predicate) {
List<Tag> toRemove = new ArrayList<>();
boolean hasRTag = false;
for (int i = 0; i < this.tagArray.length; i++) {
Tag tag = this.tagArray[i];
for (Tag tag : this.tagVector) {
if (predicate.test(tag)) {
removedIndexes.add(i);
toRemove.add(tag);
tag.hide();
if (tag instanceof Feature feature) {
this.owner.removeFeature(feature);
@@ -236,18 +235,17 @@ public class TagRendererImpl implements TagRenderer {
}
}
}
if (removedIndexes.isEmpty()) {
if (toRemove.isEmpty()) {
return 0;
}
Collections.reverse(removedIndexes);
for (int index : removedIndexes) {
this.tagVector.remove(index);
for (Tag tag : toRemove) {
this.tagVector.remove(tag);
}
resetTagArray();
if (hasRTag) {
resetRTagArray();
}
return removedIndexes.size();
return toRemove.size();
}
@Override
@@ -261,7 +259,7 @@ public class TagRendererImpl implements TagRenderer {
}
@Override
public void removeTag(Tag tag) {
public synchronized void removeTag(Tag tag) {
int i = 0;
boolean has = false;
for (Tag display : this.tagArray) {
@@ -593,8 +593,8 @@ public class PlaceholderManagerImpl implements PlaceholderManager {
public void refreshPlaceholders() {
for (CNPlayer player : plugin.getOnlinePlayers()) {
if (!player.isOnline()) continue;
Set<Feature> featuresToNotifyUpdates = new ObjectOpenHashSet<>();
Map<Feature, List<CNPlayer>> relationalFeaturesToNotifyUpdates = new Object2ObjectOpenHashMap<>();
Set<Feature> featuresToNotifyUpdates = new ObjectOpenHashSet<>(0);
Map<Feature, List<CNPlayer>> relationalFeaturesToNotifyUpdates = new Object2ObjectOpenHashMap<>(0);
List<RelationalPlaceholder> delayedPlaceholdersToUpdate = new ObjectArrayList<>();
for (Placeholder placeholder : player.activePlaceholdersToRefresh()) {
if (placeholder instanceof PlayerPlaceholder playerPlaceholder) {
@@ -706,6 +706,8 @@ public class PlaceholderManagerImpl implements PlaceholderManager {
}
}
if (featuresToNotifyUpdates.isEmpty() && relationalFeaturesToNotifyUpdates.isEmpty()) continue;
// Switch to another thread for updating
plugin.getScheduler().async().execute(() -> {
// Async task takes time and the player might have been offline
@@ -13,7 +13,12 @@ channel-mode: ALL
default-bubble: 'chat'
y-offset: 0.1
view-range: 0.5
stay-duration: 150
stay-duration: 100
# Additional ticks added per visible character of the chat message.
# Set to 0.0 to disable length-based duration.
duration-per-character: 2.0
# Maximum stay duration in ticks. 0 means no limit.
max-stay-duration: 0
appear-duration: 4
disappear-duration: 2
bubble-settings:
@@ -28,4 +33,5 @@ bubble-settings:
background-color: 0,0,0,0
text-prefix: "<black><font:{namespace}:shift_0>" # {namespace} would be replaced by the namespace set in config.yml
text-suffix: "</font></black>"
command-suggestion: "White Chat Bubble"
command-suggestion: "White Chat Bubble"
affected-by-scale-attribute: true
+30 -30
View File
@@ -1,6 +1,6 @@
# Project settings
# Rule: [major update].[feature update].[bug fix]
project_version=3.0.39
project_version=3.0.45
config_version=40
project_group=net.momirealms
@@ -9,42 +9,42 @@ lang=en,zh_cn,ko_kr
# Dependency settings
paper_version=1.20.4
jetbrains_annotations_version=26.0.2
slf4j_version=2.0.17
log4j_version=2.25.2
gson_version=2.13.2
asm_version=9.9.1
asm_commons_version=9.9.1
jetbrains_annotations_version=26.1.0
slf4j_version=2.0.20
log4j_version=2.26.1
gson_version=2.14.0
asm_version=9.10.1
asm_commons_version=9.10.1
jar_relocator_version=1.7
h2_driver_version=2.4.240
sqlite_driver_version=3.50.3.0
adventure_bundle_version=5.0.1
adventure_platform_version=4.4.1
cloud_core_version=2.0.0
cloud_services_version=2.0.0
cloud_brigadier_version=2.0.0-beta.15
cloud_bukkit_version=2.0.0-beta.15
cloud_paper_version=2.0.0-beta.15
cloud_minecraft_extras_version=2.0.0-beta.15
h2_driver_version=2.5.252
sqlite_driver_version=3.53.4.0
adventure_bundle_version=5.2.0
cloud_core_version=2.1.0
cloud_services_version=2.1.0
cloud_brigadier_version=2.0.1
cloud_bukkit_version=2.0.1
cloud_paper_version=2.0.1
cloud_minecraft_extras_version=2.0.1
boosted_yaml_version=1.3.7
byte_buddy_version=1.18.8
byte_buddy_version=1.18.14
mojang_brigadier_version=1.0.18
mongodb_driver_version=5.6.5
mariadb_driver_version=3.5.8
mysql_driver_version=9.6.0
hikari_version=7.0.2
commons_pool_version=2.12.1
mongodb_driver_version=5.13.0
mariadb_driver_version=3.5.10
mysql_driver_version=26.7.0
hikari_version=7.1.0
commons_pool_version=2.13.1
bstats_version=3.2.1
geantyref_version=1.3.16
caffeine_version=3.2.3
jedis_version=7.4.1
geantyref_version=2.0.1
caffeine_version=3.3.0
jedis_version=8.0.1
exp4j_version=0.4.8
placeholder_api_version=2.11.7
vault_version=1.7
guava_version=33.5.0-jre
commons_io_version=2.21.0
lwjgl_version=3.4.1
fastutil_version=8.5.18
guava_version=33.7.1-jre
commons_io_version=2.22.0
lwjgl_version=3.4.3
netty_version=4.2.18.Final
fastutil_version=8.5.19
# Proxy settings
#systemProp.socks.proxyHost=127.0.0.1
+14 -3
View File
@@ -13,6 +13,7 @@ repositories {
maven("https://repo.papermc.io/repository/maven-public/") // paper
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") // spigot
maven("https://repo.momirealms.net/releases/")
}
dependencies {
@@ -24,6 +25,8 @@ dependencies {
compileOnly("dev.folia:folia-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:${rootProject.properties["placeholder_api_version"]}")
compileOnly("com.mojang:datafixerupper:10.0.21")
compileOnly("com.mojang:brigadier:1.3.10")
// YAML
compileOnly("dev.dejvokep:boosted-yaml:${rootProject.properties["boosted_yaml_version"]}")
@@ -31,11 +34,17 @@ dependencies {
// Adventure
implementation("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}")
implementation("net.kyori:adventure-text-minimessage:${rootProject.properties["adventure_bundle_version"]}")
implementation("net.kyori:adventure-platform-bukkit:${rootProject.properties["adventure_platform_version"]}")
implementation("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}") {
exclude("com.google.code.gson", "gson")
}
implementation("net.kyori:adventure-text-serializer-json-legacy-impl:${rootProject.properties["adventure_bundle_version"]}")
implementation("net.kyori:adventure-text-serializer-legacy:${project.properties["adventure_bundle_version"]}")
implementation("net.momirealms:sparrow-reflection:0.34")
implementation("net.momirealms:sparrow-nbt:0.22")
implementation("net.momirealms:sparrow-nbt-codec:0.22")
implementation("net.momirealms:sparrow-nbt-legacy-codec:0.22")
implementation("net.momirealms:sparrow-nbt-parser:0.22")
// BStats
compileOnly("org.bstats:bstats-bukkit:${rootProject.properties["bstats_version"]}")
@@ -45,7 +54,7 @@ dependencies {
compileOnly("org.incendo:cloud-minecraft-extras:${rootProject.properties["cloud_minecraft_extras_version"]}")
compileOnly("org.incendo:cloud-paper:${rootProject.properties["cloud_paper_version"]}")
// Netty
compileOnly("io.netty:netty-all:4.1.117.Final")
compileOnly("io.netty:netty-all:${rootProject.properties["netty_version"]}")
}
tasks {
@@ -67,6 +76,8 @@ tasks {
relocate("com.github.benmanes.caffeine", "net.momirealms.customnameplates.libraries.caffeine")
relocate("net.objecthunter.exp4j", "net.momirealms.customnameplates.libraries.exp4j")
relocate("redis.clients.jedis", "net.momirealms.customnameplates.libraries.jedis")
relocate("net.momirealms.sparrow.reflection", "net.momirealms.customnameplates.libraries.reflection")
relocate("net.momirealms.sparrow.nbt", "net.momirealms.customnameplates.libraries.nbt")
}
}
@@ -102,4 +113,4 @@ tasks.register("run-paper", RunServer::class) {
jvmArgs("-Ddisable.watchdog=true")
jvmArgs("-Xlog:redefine+class*=info")
jvmArgs("-XX:+AllowEnhancedClassRedefinition")
}
}
@@ -13,7 +13,6 @@ repositories {
maven("https://repo.opencollab.dev/main/") // geyser
maven("https://maven.enginehub.org/repo/") // worldguard worldedit
maven("https://repo.alessiodp.com/releases/") // parties
maven("https://maven.devs.beer/") // ia
maven("https://repo.pinodev.it/releases/") // zelchat
maven("https://repo.hibiscusmc.com/releases") // hmccosmetics
}
@@ -50,8 +49,8 @@ dependencies {
// compileOnly("com.github.Brikster:Chatty:v2.19.14")
compileOnly(files("libs/Chatty-3.0.0-SNAPSHOT.jar"))
// Emoji
compileOnly("dev.lone:api-itemsadder:4.0.10")
compileOnly("io.th0rgal:oraxen:1.182.0")
compileOnly("beer.devs:itemsadder-api:4.0.17")
compileOnly("io.th0rgal:oraxen:1.217.0")
// PAPI
compileOnly("me.clip:placeholderapi:${rootProject.properties["placeholder_api_version"]}")
// Disguise
@@ -22,11 +22,9 @@ import mineverse.Aust1n46.chat.api.MineverseChatPlayer;
import mineverse.Aust1n46.chat.api.events.VentureChatEvent;
import mineverse.Aust1n46.chat.channel.ChatChannel;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.ConfigManager;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.chat.AbstractChatMessageProvider;
import net.momirealms.customnameplates.api.feature.chat.ChatManager;
import net.momirealms.customnameplates.api.helper.AdventureHelper;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -19,7 +19,6 @@ package net.momirealms.customnameplates.bukkit.compatibility.chat;
import it.pino.zelchat.api.ZelChatAPI;
import it.pino.zelchat.api.message.ChatMessage;
import it.pino.zelchat.api.message.channel.ChannelType;
import it.pino.zelchat.api.message.channel.ChatChannel;
import it.pino.zelchat.api.message.state.MessageState;
import it.pino.zelchat.api.module.ChatModule;
@@ -18,49 +18,27 @@
package net.momirealms.customnameplates.bukkit.compatibility.emoji;
import io.th0rgal.oraxen.OraxenPlugin;
import io.th0rgal.oraxen.font.FontManager;
import io.th0rgal.oraxen.font.Glyph;
import io.th0rgal.oraxen.fonts.FontManager;
import io.th0rgal.oraxen.glyphs.Glyph;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.feature.chat.emoji.EmojiProvider;
import org.bukkit.entity.Player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.function.Function;
public class OraxenEmojiProvider implements EmojiProvider {
private final FontManager fontManager;
private final Function<Glyph, String> characterFunction;
public OraxenEmojiProvider(int version) {
if (version == 1) {
this.fontManager = OraxenPlugin.get().getFontManager();
this.characterFunction = Glyph::getCharacter;
} else {
try {
Method fm = OraxenPlugin.class.getMethod("fontManager");
this.fontManager = (FontManager) fm.invoke(OraxenPlugin.get());
Method cm = Glyph.class.getMethod("character");
this.characterFunction = (glyph -> {
try {
return (String) cm.invoke(glyph);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
});
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public OraxenEmojiProvider() {
this.fontManager = OraxenPlugin.get().getFontManager();
}
@Override
public String replace(CNPlayer player, String text) {
for (Map.Entry<String, Glyph> entry : this.fontManager.getGlyphByPlaceholderMap().entrySet()) {
if (entry.getValue().hasPermission((Player) player.player())) {
text = text.replace(entry.getKey(), "<white><font:default>" + characterFunction.apply(entry.getValue()) + "</font></white>");
}
String unicode = entry.getValue().getCharacters();
if (entry.getValue().hasPermission((Player) player.player()))
text = text.replace(entry.getKey(), "<white>" + unicode + "</white>");
}
return text;
}
@@ -21,17 +21,17 @@ import io.netty.channel.Channel;
import net.momirealms.customnameplates.api.AbstractCNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.util.Vector3;
import org.bukkit.*;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
public class BukkitCNPlayer extends AbstractCNPlayer<Player> {
@@ -78,7 +78,7 @@ public class BukkitChatManager extends AbstractChatManager {
}
if (Bukkit.getPluginManager().getPlugin("Oraxen") != null) {
try {
this.emojiProviders.add(new OraxenEmojiProvider(Bukkit.getPluginManager().getPlugin("Oraxen").getDescription().getVersion().startsWith("1") ? 1 : 2));
this.emojiProviders.add(new OraxenEmojiProvider());
} catch (Exception ignore) {
}
}
@@ -34,7 +34,6 @@ import net.momirealms.customnameplates.backend.feature.bubble.BubbleManagerImpl;
import net.momirealms.customnameplates.backend.feature.image.ImageManagerImpl;
import net.momirealms.customnameplates.backend.feature.nameplate.NameplateManagerImpl;
import net.momirealms.customnameplates.backend.feature.pack.ResourcePackManagerImpl;
import net.momirealms.customnameplates.backend.feature.tag.AbstractUnlimitedTagManager;
import net.momirealms.customnameplates.backend.placeholder.PlaceholderManagerImpl;
import net.momirealms.customnameplates.backend.storage.StorageManagerImpl;
import net.momirealms.customnameplates.bukkit.command.BukkitCommandManager;
@@ -429,23 +428,34 @@ public class BukkitCustomNameplates extends CustomNameplates implements Listener
if (cnPlayer == null) {
return;
}
for (JoinQuitListener listener : joinQuitListeners) {
listener.onPlayerQuit(cnPlayer);
}
entityIDFastLookup.remove(cnPlayer.entityID());
if (VersionHelper.isFolia()) {
foliaLocationTracker.remove(player.getName());
try {
for (JoinQuitListener listener : joinQuitListeners) {
listener.onPlayerQuit(cnPlayer);
}
} finally {
removePlayerFromTrackers(cnPlayer);
entityIDFastLookup.remove(cnPlayer.entityID());
if (VersionHelper.isFolia()) {
foliaLocationTracker.remove(player.getName());
}
}
}
public void handleQuit(CNPlayer cnPlayer) {
entityIDFastLookup.remove(cnPlayer.entityID());
onlinePlayerMap.remove(cnPlayer.uuid());
removePlayerFromTrackers(cnPlayer);
if (VersionHelper.isFolia()) {
foliaLocationTracker.remove(cnPlayer.name());
}
}
private void removePlayerFromTrackers(CNPlayer player) {
for (CNPlayer onlinePlayer : getOnlinePlayers()) {
onlinePlayer.removePlayerFromTracker(player);
}
}
@EventHandler(ignoreCancelled = true)
public void onChangeWorld(PlayerChangedWorldEvent event) {
if (VersionHelper.isFolia()) return;
@@ -469,6 +479,9 @@ public class BukkitCustomNameplates extends CustomNameplates implements Listener
@EventHandler(ignoreCancelled = true)
public void onTeleport(PlayerTeleportEvent event) {
if (!event.getFrom().getWorld().equals(event.getTo().getWorld())) {
return;
}
CNPlayer cnPlayer = getPlayer(event.getPlayer().getUniqueId());
if (cnPlayer != null) {
for (PlayerListener listener : playerListeners) {
@@ -74,17 +74,28 @@ public class BukkitNetworkManager implements PacketSender, PipelineInjector {
};
} else {
packetConsumer = (player, packet) -> {
Vector3 vector3 = player.position();
Location location = new Location(Bukkit.getWorld(player.world()), vector3.x(), vector3.y(), vector3.z());
plugin.getScheduler().executeSync(() -> {
try {
Reflections.method$SendPacket.invoke(
Reflections.field$PlayerConnection.get(
Reflections.method$CraftPlayer$getHandle.invoke(player.player())), packet);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}, location);
Player bukkitPlayer = (Player) player.player();
if (VersionHelper.isFolia()) {
// Follow the player across regions/worlds and discard tasks when the entity retires.
bukkitPlayer.getScheduler().execute(plugin.getBootstrap(), () -> {
if (!bukkitPlayer.isOnline()) return;
try {
Reflections.method$SendPacket.invoke(Reflections.field$PlayerConnection.get(Reflections.method$CraftPlayer$getHandle.invoke(bukkitPlayer)), packet);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}, null, 1L);
} else {
Vector3 vector3 = player.position();
Location location = new Location(Bukkit.getWorld(player.world()), vector3.x(), vector3.y(), vector3.z());
plugin.getScheduler().executeSync(() -> {
try {
Reflections.method$SendPacket.invoke(Reflections.field$PlayerConnection.get(Reflections.method$CraftPlayer$getHandle.invoke(player.player())), packet);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}, location);
}
};
}
this.active = true;
@@ -289,15 +300,17 @@ public class BukkitNetworkManager implements PacketSender, PipelineInjector {
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) throws Exception {
PacketEvent event = new PacketEvent(packet);
try {
PacketEvent event = new PacketEvent(packet);
plugin.getPlatform().onPacketSend(player, event);
if (event.cancelled()) return;
super.write(context, packet, channelPromise);
} catch (Throwable e) {
plugin.getPluginLogger().severe("An error occurred when reading packets", e);
plugin.getPluginLogger().severe("An error occurred when processing an outbound packet", e);
super.write(context, packet, channelPromise);
return;
}
if (event.cancelled()) return;
super.write(context, packet, channelPromise);
event.runAfterSendTasks(e -> plugin.getPluginLogger().severe("An error occurred in an after-send packet task", e));
}
}
@@ -17,8 +17,13 @@
package net.momirealms.customnameplates.bukkit;
import com.google.gson.JsonNull;
import it.unimi.dsi.fastutil.ints.IntList;
import me.clip.placeholderapi.PlaceholderAPI;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.event.DataComponentValueConverterRegistry;
import net.kyori.adventure.text.serializer.gson.GsonDataComponentValue;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.ConfigManager;
import net.momirealms.customnameplates.api.CustomNameplates;
@@ -40,6 +45,18 @@ import net.momirealms.customnameplates.bukkit.util.EntityData;
import net.momirealms.customnameplates.bukkit.util.Reflections;
import net.momirealms.customnameplates.common.util.TriConsumer;
import net.momirealms.customnameplates.common.util.UUIDUtils;
import net.momirealms.sparrow.nbt.EndTag;
import net.momirealms.sparrow.nbt.Tag;
import net.momirealms.sparrow.nbt.codec.JsonOps;
import net.momirealms.sparrow.nbt.codec.LegacyJavaOps;
import net.momirealms.sparrow.nbt.codec.LegacyJsonOps;
import net.momirealms.sparrow.nbt.codec.NBTOps;
import net.momirealms.sparrow.nbt.parser.TagParser;
import net.momirealms.sparrow.reflection.clazz.SparrowClass;
import net.momirealms.sparrow.reflection.constructor.SConstructor2;
import net.momirealms.sparrow.reflection.constructor.matcher.ConstructorMatcher;
import net.momirealms.sparrow.reflection.field.SField;
import net.momirealms.sparrow.reflection.field.matcher.FieldMatcher;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
@@ -47,6 +64,7 @@ import org.bukkit.entity.Player;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.regex.Pattern;
@@ -100,6 +118,8 @@ public class BukkitPlatform implements Platform {
}
static {
injectAdventure();
// ThrowableFunction<Object, String> scoreContentNameFunction = VersionHelper.isVersionNewerThan1_21_2() ? (o -> {
// @SuppressWarnings("unchecked")
// Optional<String> optional = (Optional<String>) Reflections.method$Either$right.invoke(o);
@@ -208,9 +228,11 @@ public class BukkitPlatform implements Platform {
int entityID = (int) Reflections.field$ClientboundAddEntityPacket$entityId.get(packet);
CNPlayer added = CustomNameplates.getInstance().getPlayer(entityID);
if (added != null) {
Tracker tracker = added.addPlayerToTracker(player);
tracker.setSpectator(added.isSpectator());
CustomNameplates.getInstance().getUnlimitedTagManager().onAddPlayer(added, player);
event.afterSend(() -> {
Tracker tracker = added.addPlayerToTracker(player);
tracker.setSpectator(added.isSpectator());
CustomNameplates.getInstance().getUnlimitedTagManager().onAddPlayer(added, player);
});
}
} catch (ReflectiveOperationException e) {
CustomNameplates.getInstance().getPluginLogger().severe("Failed to handle ClientboundAddEntityPacket", e);
@@ -224,9 +246,11 @@ public class BukkitPlatform implements Platform {
int entityID = (int) Reflections.field$PacketPlayOutNamedEntitySpawn$entityId.get(packet);
CNPlayer added = CustomNameplates.getInstance().getPlayer(entityID);
if (added != null) {
Tracker tracker = added.addPlayerToTracker(player);
tracker.setSpectator(added.isSpectator());
CustomNameplates.getInstance().getUnlimitedTagManager().onAddPlayer(added, player);
event.afterSend(() -> {
Tracker tracker = added.addPlayerToTracker(player);
tracker.setSpectator(added.isSpectator());
CustomNameplates.getInstance().getUnlimitedTagManager().onAddPlayer(added, player);
});
}
} catch (ReflectiveOperationException e) {
CustomNameplates.getInstance().getPluginLogger().severe("Failed to handle PacketPlayOutNamedEntitySpawn", e);
@@ -418,6 +442,7 @@ public class BukkitPlatform implements Platform {
}, "ClientboundSetEntityDataPacket", "PacketPlayOutEntityMetadata");
// not a perfect solution but would work in most cases
SField visibilityField = VersionHelper.isVersionNewerThan26_2() ? SparrowClass.of(Reflections.clazz$ClientboundSetPlayerTeamPacket$Parameters).getDeclaredSparrowField(FieldMatcher.named("nameTagVisibility")).mh() : null;
registerPacketConsumer((player, event, packet) -> {
if (!ConfigManager.nametagModule()) return;
if (!ConfigManager.hideTeamNames()) return;
@@ -451,7 +476,11 @@ public class BukkitPlatform implements Platform {
Optional<Object> optionalParameters = (Optional<Object>) Reflections.field$ClientboundSetPlayerTeamPacket$parameters.get(packet);
if (optionalParameters.isPresent()) {
Object parameters = optionalParameters.get();
Reflections.field$ClientboundSetPlayerTeamPacket$Parameters$nametagVisibility.set(parameters, VersionHelper.isVersionNewerThan1_21_5() ? Reflections.instance$Team$Visibility$NEVER : "never");
if (VersionHelper.isVersionNewerThan26_2()) {
visibilityField.set(parameters, Reflections.instance$Team$Visibility$NEVER);
} else {
Reflections.field$ClientboundSetPlayerTeamPacket$Parameters$nametagVisibility.set(parameters, VersionHelper.isVersionNewerThan1_21_5() ? Reflections.instance$Team$Visibility$NEVER : "never");
}
}
}
// remove
@@ -481,7 +510,11 @@ public class BukkitPlatform implements Platform {
Optional<Object> optionalParameters = (Optional<Object>) Reflections.field$ClientboundSetPlayerTeamPacket$parameters.get(packet);
if (optionalParameters.isPresent()) {
Object parameters = optionalParameters.get();
Reflections.field$ClientboundSetPlayerTeamPacket$Parameters$nametagVisibility.set(parameters, VersionHelper.isVersionNewerThan1_21_5() ? Reflections.instance$Team$Visibility$NEVER : "never");
if (VersionHelper.isVersionNewerThan26_2()) {
visibilityField.set(parameters, Reflections.instance$Team$Visibility$NEVER);
} else {
Reflections.field$ClientboundSetPlayerTeamPacket$Parameters$nametagVisibility.set(parameters, VersionHelper.isVersionNewerThan1_21_5() ? Reflections.instance$Team$Visibility$NEVER : "never");
}
}
}
// add members
@@ -762,4 +795,38 @@ public class BukkitPlatform implements Platform {
Optional.ofNullable(packetFunctions.get(packet.getClass().getSimpleName()))
.ifPresent(function -> function.accept(player, event, packet));
}
@SuppressWarnings("unchecked")
public static void injectAdventure() {
Map<Class<?>, Map<Class<?>, Object>> CACHE = (Map<Class<?>, Map<Class<?>, Object>>) SparrowClass.of(SparrowClass.find("net.kyori.adventure.text.event.DataComponentValueConverterRegistry$ConversionCache"))
.getDeclaredSparrowField(FieldMatcher.named("CACHE"))
.mh()
.get(null);
Class<?> tagClass = SparrowClass.find("net.kyori.adventure.nbt.api.BinaryTagHolderImpl");
DataComponentValueConverterRegistry.Conversion<BinaryTagHolder, GsonDataComponentValue> convertor1 = DataComponentValueConverterRegistry.Conversion.convert(
BinaryTagHolder.class,
GsonDataComponentValue.class,
(key, srcValue) -> {
try {
Tag tag = TagParser.parseTagFully(srcValue.string());
if (tag == EndTag.INSTANCE) {
return GsonDataComponentValue.gsonDataComponentValue(JsonNull.INSTANCE);
} else {
if (VersionHelper.isVersionNewerThan1_20_5()) {
return GsonDataComponentValue.gsonDataComponentValue(NBTOps.INSTANCE.convertTo(JsonOps.INSTANCE, tag));
} else {
return GsonDataComponentValue.gsonDataComponentValue(LegacyJavaOps.INSTANCE.convertTo(LegacyJsonOps.INSTANCE, tag));
}
}
} catch (Throwable e) {
return GsonDataComponentValue.gsonDataComponentValue(JsonNull.INSTANCE);
}
}
);
SConstructor2 constructor = SparrowClass.of(SparrowClass.find("net.kyori.adventure.text.event.DataComponentValueConverterRegistry$RegisteredConversion"))
.getDeclaredSparrowConstructor(ConstructorMatcher.takeArguments(Key.class, DataComponentValueConverterRegistry.Conversion.class))
.asm$2();
CACHE.computeIfAbsent(tagClass, $ -> new ConcurrentHashMap<>())
.computeIfAbsent(GsonDataComponentValue.class, $ -> constructor.newInstance(Key.key("nameplates", "serializer/nbt"), convertor1));
}
}
@@ -25,9 +25,8 @@
package net.momirealms.customnameplates.bukkit;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.momirealms.customnameplates.api.helper.AdventureHelper;
import net.momirealms.customnameplates.bukkit.util.Reflections;
import net.momirealms.customnameplates.common.sender.Sender;
@@ -41,11 +40,9 @@ import org.bukkit.entity.Player;
import java.util.UUID;
public class BukkitSenderFactory extends SenderFactory<BukkitCustomNameplates, CommandSender> {
private final BukkitAudiences audiences;
public BukkitSenderFactory(BukkitCustomNameplates plugin) {
super(plugin);
this.audiences = BukkitAudiences.create(plugin.getBootstrap());
}
@Override
@@ -64,11 +61,6 @@ public class BukkitSenderFactory extends SenderFactory<BukkitCustomNameplates, C
return Sender.CONSOLE_UUID;
}
@Override
public Audience getAudience(CommandSender sender) {
return this.audiences.sender(sender);
}
@Override
protected void sendMessage(CommandSender sender, Component message) {
if (sender instanceof Player player) {
@@ -78,10 +70,8 @@ public class BukkitSenderFactory extends SenderFactory<BukkitCustomNameplates, C
} catch (ReflectiveOperationException e) {
getPlugin().getPluginLogger().warn("Failed to send message to player " + sender.getName(), e);
}
} else if (sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender) {
getAudience(sender).sendMessage(message);
} else {
getPlugin().getScheduler().executeSync(() -> getAudience(sender).sendMessage(message));
sender.sendMessage(LegacyComponentSerializer.legacySection().serialize(message));
}
}
@@ -114,6 +104,5 @@ public class BukkitSenderFactory extends SenderFactory<BukkitCustomNameplates, C
@Override
public void close() {
super.close();
this.audiences.close();
}
}
@@ -22,7 +22,6 @@ import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.ConfigManager;
import net.momirealms.customnameplates.api.feature.bubble.BubbleConfig;
import net.momirealms.customnameplates.api.helper.AdventureHelper;
import net.momirealms.customnameplates.api.util.CharacterUtils;
import net.momirealms.customnameplates.bukkit.BukkitCustomNameplates;
import net.momirealms.customnameplates.bukkit.command.BukkitCommandFeature;
import net.momirealms.customnameplates.common.command.CustomNameplatesCommandManager;
@@ -0,0 +1,15 @@
package net.momirealms.customnameplates.bukkit.reflection;
import net.momirealms.sparrow.reflection.clazz.SparrowClass;
import net.momirealms.sparrow.reflection.proxy.ASMProxyFactory;
import net.momirealms.sparrow.reflection.proxy.annotation.MethodInvoker;
import net.momirealms.sparrow.reflection.proxy.annotation.ReflectionProxy;
@ReflectionProxy(name = "net.minecraft.nbt.CompoundTag")
public interface CompoundTagProxy {
CompoundTagProxy INSTANCE = ASMProxyFactory.create(CompoundTagProxy.class);
Class<?> CLASS = SparrowClass.find("net.minecraft.nbt.CompoundTag");
@MethodInvoker(name = "get")
Object get(Object target, String key);
}
@@ -1000,7 +1000,7 @@ public final class Reflections {
public static final Field field$ClientboundSetPlayerTeamPacket$players = requireNonNull(
ReflectionUtils.getInstanceDeclaredField(
clazz$ClientboundSetPlayerTeamPacket, Collection.class, 0
clazz$ClientboundSetPlayerTeamPacket, VersionHelper.isVersionNewerThan26_3() ? List.class : Collection.class, 0
)
);
@@ -1210,7 +1210,7 @@ public final class Reflections {
);
public static final Method method$GsonComponentSerializer$builder = requireNonNull(
ReflectionUtils.getMethod(
ReflectionUtils.getStaticMethod(
clazz$GsonComponentSerializer, clazz$GsonComponentSerializer$Builder
)
);