From 5d48530eb6d1bca12ccd94edd9d299876e1d223f Mon Sep 17 00:00:00 2001 From: XiaoMoMi <70987828+Xiao-MoMi@users.noreply.github.com> Date: Mon, 21 Oct 2024 03:08:51 +0800 Subject: [PATCH] 3.0.5 --- .../api/AbstractCNPlayer.java | 8 + .../customnameplates/api/CNPlayer.java | 8 + .../api/feature/tag/TeamView.java | 50 +++++ .../bukkit/BukkitCustomNameplates.java | 7 +- .../bukkit/BukkitPlatform.java | 83 ++++++-- .../bukkit/util/Reflections.java | 6 + .../common/util/MoonPhase.java | 55 ++++++ .../NameplatesExtraExpansion.java | 185 ++++++++++++++++++ 8 files changed, 381 insertions(+), 21 deletions(-) create mode 100644 api/src/main/java/net/momirealms/customnameplates/api/feature/tag/TeamView.java create mode 100644 common/src/main/java/net/momirealms/customnameplates/common/util/MoonPhase.java create mode 100644 compatibility/src/main/java/net/momirealms/customnameplates/bukkit/compatibility/NameplatesExtraExpansion.java diff --git a/api/src/main/java/net/momirealms/customnameplates/api/AbstractCNPlayer.java b/api/src/main/java/net/momirealms/customnameplates/api/AbstractCNPlayer.java index f1b2de3..cc9b47f 100644 --- a/api/src/main/java/net/momirealms/customnameplates/api/AbstractCNPlayer.java +++ b/api/src/main/java/net/momirealms/customnameplates/api/AbstractCNPlayer.java @@ -20,6 +20,7 @@ package net.momirealms.customnameplates.api; import io.netty.channel.Channel; import net.momirealms.customnameplates.api.feature.Feature; import net.momirealms.customnameplates.api.feature.TimeStampData; +import net.momirealms.customnameplates.api.feature.tag.TeamView; import net.momirealms.customnameplates.api.network.Tracker; import net.momirealms.customnameplates.api.placeholder.Placeholder; import net.momirealms.customnameplates.api.placeholder.PlayerPlaceholder; @@ -48,6 +49,8 @@ public abstract class AbstractCNPlayer implements CNPlayer { private String equippedNameplate; private String equippedBubble; + private final TeamView teamView = new TeamView(); + private final Map> cachedValues = new ConcurrentHashMap<>(); private final Map>> cachedRelationalValues = new ConcurrentHashMap<>(); @@ -491,6 +494,11 @@ public abstract class AbstractCNPlayer implements CNPlayer { .build(), plugin.getScheduler().async()); } + @Override + public TeamView teamView() { + return teamView; + } + @Override public boolean equals(Object object) { if (this == object) return true; diff --git a/api/src/main/java/net/momirealms/customnameplates/api/CNPlayer.java b/api/src/main/java/net/momirealms/customnameplates/api/CNPlayer.java index 2c3fcc5..3159b73 100644 --- a/api/src/main/java/net/momirealms/customnameplates/api/CNPlayer.java +++ b/api/src/main/java/net/momirealms/customnameplates/api/CNPlayer.java @@ -20,6 +20,7 @@ package net.momirealms.customnameplates.api; import io.netty.channel.Channel; import net.momirealms.customnameplates.api.feature.Feature; import net.momirealms.customnameplates.api.feature.TimeStampData; +import net.momirealms.customnameplates.api.feature.tag.TeamView; import net.momirealms.customnameplates.api.network.Tracker; import net.momirealms.customnameplates.api.placeholder.Placeholder; import net.momirealms.customnameplates.api.requirement.Requirement; @@ -393,4 +394,11 @@ public interface CNPlayer { * Save the player's current nameplate/bubble to database */ void save(); + + /** + * Get the player's team view + * + * @return team view + */ + TeamView teamView(); } diff --git a/api/src/main/java/net/momirealms/customnameplates/api/feature/tag/TeamView.java b/api/src/main/java/net/momirealms/customnameplates/api/feature/tag/TeamView.java new file mode 100644 index 0000000..7793107 --- /dev/null +++ b/api/src/main/java/net/momirealms/customnameplates/api/feature/tag/TeamView.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) <2024> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customnameplates.api.feature.tag; + +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +public class TeamView { + + private final HashMap> teamMembers = new HashMap<>(); + + @Nullable + public Set getTeamMembers(String team) { + return teamMembers.get(team); + } + + public void addTeamMembers(String team, Collection members) { + teamMembers.computeIfAbsent(team, k -> new HashSet<>(members)); + } + + public void removeTeamMembers(String team, Collection members) { + Set membersSet = teamMembers.get(team); + if (membersSet != null) { + membersSet.removeAll(members); + } + } + + public void removeTeam(String team) { + this.teamMembers.remove(team); + } +} diff --git a/bukkit/src/main/java/net/momirealms/customnameplates/bukkit/BukkitCustomNameplates.java b/bukkit/src/main/java/net/momirealms/customnameplates/bukkit/BukkitCustomNameplates.java index 05e4aa3..c31a19f 100644 --- a/bukkit/src/main/java/net/momirealms/customnameplates/bukkit/BukkitCustomNameplates.java +++ b/bukkit/src/main/java/net/momirealms/customnameplates/bukkit/BukkitCustomNameplates.java @@ -17,17 +17,13 @@ package net.momirealms.customnameplates.bukkit; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; import net.momirealms.customnameplates.api.*; import net.momirealms.customnameplates.api.event.NameplatesReloadEvent; import net.momirealms.customnameplates.api.feature.ChatListener; import net.momirealms.customnameplates.api.feature.JoinQuitListener; -import net.momirealms.customnameplates.api.feature.OffsetFont; import net.momirealms.customnameplates.api.feature.PlayerListener; import net.momirealms.customnameplates.api.helper.AdventureHelper; import net.momirealms.customnameplates.api.helper.VersionHelper; -import net.momirealms.customnameplates.api.placeholder.internal.StaticPosition; import net.momirealms.customnameplates.api.util.Vector3; import net.momirealms.customnameplates.backend.feature.actionbar.ActionBarManagerImpl; import net.momirealms.customnameplates.backend.feature.advance.AdvanceManagerImpl; @@ -42,6 +38,7 @@ import net.momirealms.customnameplates.backend.placeholder.PlaceholderManagerImp import net.momirealms.customnameplates.backend.storage.StorageManagerImpl; import net.momirealms.customnameplates.bukkit.command.BukkitCommandManager; import net.momirealms.customnameplates.bukkit.compatibility.NameplatesExpansion; +import net.momirealms.customnameplates.bukkit.compatibility.NameplatesExtraExpansion; import net.momirealms.customnameplates.bukkit.compatibility.cosmetic.MagicCosmeticsHook; import net.momirealms.customnameplates.bukkit.requirement.BukkitRequirementManager; import net.momirealms.customnameplates.bukkit.scheduler.BukkitSchedulerAdapter; @@ -57,7 +54,6 @@ import net.momirealms.customnameplates.common.plugin.logging.PluginLogger; import net.momirealms.customnameplates.common.plugin.scheduler.AbstractJavaScheduler; import net.momirealms.customnameplates.common.plugin.scheduler.SchedulerAdapter; import net.momirealms.customnameplates.common.plugin.scheduler.SchedulerTask; -import net.momirealms.customnameplates.common.util.Pair; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -200,6 +196,7 @@ public class BukkitCustomNameplates extends CustomNameplates implements Listener } if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { new NameplatesExpansion(this).register(); + new NameplatesExtraExpansion(this).register(); } if (Bukkit.getPluginManager().isPluginEnabled("MagicCosmetics")) { try { diff --git a/bukkit/src/main/java/net/momirealms/customnameplates/bukkit/BukkitPlatform.java b/bukkit/src/main/java/net/momirealms/customnameplates/bukkit/BukkitPlatform.java index abb9e72..23ac054 100644 --- a/bukkit/src/main/java/net/momirealms/customnameplates/bukkit/BukkitPlatform.java +++ b/bukkit/src/main/java/net/momirealms/customnameplates/bukkit/BukkitPlatform.java @@ -37,6 +37,7 @@ import net.momirealms.customnameplates.bukkit.util.BiomeUtils; 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 org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.OfflinePlayer; @@ -296,27 +297,77 @@ public class BukkitPlatform implements Platform { } }, "ClientboundSetEntityDataPacket", "PacketPlayOutEntityMetadata"); + // not a perfect solution but would work in most cases registerPacketConsumer((player, event, packet) -> { if (!ConfigManager.nametagModule()) return; if (!ConfigManager.hideTeamNames()) return; try { int method = (int) Reflections.field$ClientboundSetPlayerTeamPacket$method.get(packet); - if (method == 0 || method == 2) { -// How to handle mixed entity team packs -// @SuppressWarnings("unchecked") -// Collection entities = (Collection) Reflections.field$ClientboundSetPlayerTeamPacket$players.get(packet); -// outer: { -// for (String entity : entities) { -// if (!UUIDUtils.isUUID(entity)) { -// break outer; -// } -// } -// } - @SuppressWarnings("unchecked") - Optional optionalParameters = (Optional) Reflections.field$ClientboundSetPlayerTeamPacket$parameters.get(packet); - if (optionalParameters.isPresent()) { - Object parameters = optionalParameters.get(); - Reflections.field$ClientboundSetPlayerTeamPacket$Parameters$nametagVisibility.set(parameters, "never"); + String teamName = (String) Reflections.field$ClientboundSetPlayerTeamPacket$name.get(packet); + switch (method) { + // create + case 0 -> { + @SuppressWarnings("unchecked") + Collection entities = (Collection) Reflections.field$ClientboundSetPlayerTeamPacket$players.get(packet); + player.teamView().addTeamMembers(teamName, entities); + // additional check for those teams with only one member + if (entities.size() <= 1) { + for (String entity : entities) { + // is a player + if (!UUIDUtils.isUUID(entity)) { + Player p = Bukkit.getPlayer(entity); + // it's a fake player + if (p == null) { + return; + } + } + } + } + @SuppressWarnings("unchecked") + Optional optionalParameters = (Optional) Reflections.field$ClientboundSetPlayerTeamPacket$parameters.get(packet); + if (optionalParameters.isPresent()) { + Object parameters = optionalParameters.get(); + Reflections.field$ClientboundSetPlayerTeamPacket$Parameters$nametagVisibility.set(parameters, "never"); + } + } + // remove + case 1 -> { + player.teamView().removeTeam(teamName); + } + // update + case 2 -> { + Set members = player.teamView().getTeamMembers(teamName); + if (members == null) return; + if (members.size() <= 1) { + for (String entity : members) { + // is a player + if (!UUIDUtils.isUUID(entity)) { + Player p = Bukkit.getPlayer(entity); + // it's a fake player + if (p == null) { + return; + } + } + } + } + @SuppressWarnings("unchecked") + Optional optionalParameters = (Optional) Reflections.field$ClientboundSetPlayerTeamPacket$parameters.get(packet); + if (optionalParameters.isPresent()) { + Object parameters = optionalParameters.get(); + Reflections.field$ClientboundSetPlayerTeamPacket$Parameters$nametagVisibility.set(parameters, "never"); + } + } + // add members + case 3 -> { + @SuppressWarnings("unchecked") + Collection entities = (Collection) Reflections.field$ClientboundSetPlayerTeamPacket$players.get(packet); + player.teamView().addTeamMembers(teamName, entities); + } + // remove members + case 4 -> { + @SuppressWarnings("unchecked") + Collection entities = (Collection) Reflections.field$ClientboundSetPlayerTeamPacket$players.get(packet); + player.teamView().removeTeamMembers(teamName, entities); } } } catch (ReflectiveOperationException e) { diff --git a/bukkit/src/main/java/net/momirealms/customnameplates/bukkit/util/Reflections.java b/bukkit/src/main/java/net/momirealms/customnameplates/bukkit/util/Reflections.java index 76649fe..ea12fde 100644 --- a/bukkit/src/main/java/net/momirealms/customnameplates/bukkit/util/Reflections.java +++ b/bukkit/src/main/java/net/momirealms/customnameplates/bukkit/util/Reflections.java @@ -1006,6 +1006,12 @@ public class Reflections { ) ); + public static final Field field$ClientboundSetPlayerTeamPacket$name = requireNonNull( + ReflectionUtils.getInstanceDeclaredField( + clazz$ClientboundSetPlayerTeamPacket, String.class, 0 + ) + ); + public static final Field field$ClientboundSetPlayerTeamPacket$parameters = requireNonNull( ReflectionUtils.getInstanceDeclaredField( clazz$ClientboundSetPlayerTeamPacket, Optional.class, 0 diff --git a/common/src/main/java/net/momirealms/customnameplates/common/util/MoonPhase.java b/common/src/main/java/net/momirealms/customnameplates/common/util/MoonPhase.java new file mode 100644 index 0000000..e0a0cf7 --- /dev/null +++ b/common/src/main/java/net/momirealms/customnameplates/common/util/MoonPhase.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) <2024> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customnameplates.common.util; + +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +public enum MoonPhase { + + FULL_MOON(0L), + WANING_GIBBOUS(1L), + LAST_QUARTER(2L), + WANING_CRESCENT(3L), + NEW_MOON(4L), + WAXING_CRESCENT(5L), + FIRST_QUARTER(6L), + WAXING_GIBBOUS(7L); + + private final long day; + + MoonPhase(long day) { + this.day = day; + } + + private static final Map BY_DAY = new HashMap<>(); + + static { + for (MoonPhase phase : values()) { + BY_DAY.put(phase.day, phase); + } + } + + @NotNull + public static MoonPhase getPhase(long day) { + return BY_DAY.get(day % 8L); + } +} + diff --git a/compatibility/src/main/java/net/momirealms/customnameplates/bukkit/compatibility/NameplatesExtraExpansion.java b/compatibility/src/main/java/net/momirealms/customnameplates/bukkit/compatibility/NameplatesExtraExpansion.java new file mode 100644 index 0000000..6492977 --- /dev/null +++ b/compatibility/src/main/java/net/momirealms/customnameplates/bukkit/compatibility/NameplatesExtraExpansion.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) <2024> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customnameplates.bukkit.compatibility; + +import me.clip.placeholderapi.PlaceholderAPI; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import net.momirealms.customnameplates.api.CustomNameplates; +import net.momirealms.customnameplates.api.CustomNameplatesAPI; +import net.momirealms.customnameplates.api.feature.OffsetFont; +import net.momirealms.customnameplates.api.feature.background.Background; +import net.momirealms.customnameplates.api.helper.AdventureHelper; +import net.momirealms.customnameplates.common.util.MoonPhase; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Locale; +import java.util.Optional; + +public class NameplatesExtraExpansion extends PlaceholderExpansion { + + private final CustomNameplates plugin; + + public NameplatesExtraExpansion(CustomNameplates plugin) { + this.plugin = plugin; + } + + @Override + public @NotNull String getIdentifier() { + return "npex"; + } + + @Override + public @NotNull String getAuthor() { + return "XiaoMoMi"; + } + + @Override + public @NotNull String getVersion() { + return "3.0"; + } + + @Override + public boolean persist() { + return true; + } + + @Override + public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) { + String[] split = params.split("_", 2); + if (split.length == 0) { + return null; + } + switch (split[0]) { + case "offset" -> { + if (split.length != 2) { + return null; + } + return OffsetFont.createOffsets(Float.parseFloat(split[1])); + } + case "background" -> { + if (split.length != 2) { + return null; + } + String subParams = split[1]; + String[] subSplit = subParams.split(":", 4); + // 0 1 2 3 + // config:left:right:advance + Optional optional = CustomNameplatesAPI.getInstance().getBackground(subSplit[0]); + float advance; + try { + advance = Float.parseFloat(subSplit[3]); + } catch (NumberFormatException e) { + String text; + if (subSplit[3].startsWith("{") && subSplit[3].endsWith("}")) { + String before = "%" + subSplit[3].substring(1, subSplit[3].length() - 1) + "%"; + text = PlaceholderAPI.setPlaceholders(player, before); + advance = Float.parseFloat(text); + } else { + return null; + } + } + float finalAdvance = advance; + return optional.map(background -> + AdventureHelper.surroundWithNameplatesFont(background.createImage(finalAdvance, Float.parseFloat(subSplit[1]), Float.parseFloat(subSplit[2])))) + .orElse(null); + } + case "lunarphase" -> { + if (split.length == 1 && player != null && player.isOnline()) { + Player online = player.getPlayer(); + if (online == null) return null; + return MoonPhase.getPhase(online.getWorld().getFullTime() / 24_000).name().toLowerCase(Locale.ENGLISH); + } + if (split.length == 2) { + String world = split[1]; + World bukkitWorld = Bukkit.getWorld(world); + if (bukkitWorld == null) return null; + return MoonPhase.getPhase(bukkitWorld.getFullTime() / 24_000).name().toLowerCase(Locale.ENGLISH); + } + return null; + } + case "weather" -> { + World world = null; + if (split.length == 1 && player != null && player.isOnline()) { + Player online = player.getPlayer(); + if (online == null) return null; + world = online.getWorld(); + } + if (split.length == 2) { + World bukkitWorld = Bukkit.getWorld(split[1]); + if (bukkitWorld == null) return null; + world = bukkitWorld; + } + if (world == null) return null; + String currentWeather; + if (world.isClearWeather()) currentWeather = "clear"; + else if (world.isThundering()) currentWeather = "thunder"; + else currentWeather = "rain"; + return currentWeather; + } + case "time12" -> { + long time; + if (split.length == 1 && player != null && player.isOnline()) { + Player online = player.getPlayer(); + if (online == null) return null; + time = online.getWorld().getTime(); + } else if (split.length == 2) { + String world = split[1]; + World bukkitWorld = Bukkit.getWorld(world); + if (bukkitWorld == null) return null; + time = bukkitWorld.getTime(); + } else { + return null; + } + String ap = time >= 6000 && time < 18000 ? " PM" : " AM"; + int hours = (int) (time / 1000) ; + int minutes = (int) ((time - hours * 1000 ) * 0.06); + hours += 6; + while (hours >= 12) hours -= 12; + if (minutes < 10) return hours + ":0" + minutes + ap; + else return hours + ":" + minutes + ap; + } + case "time24" -> { + long time; + if (split.length == 1 && player != null && player.isOnline()) { + Player online = player.getPlayer(); + if (online == null) return null; + time = online.getWorld().getTime(); + } else if (split.length == 2) { + String world = split[1]; + World bukkitWorld = Bukkit.getWorld(world); + if (bukkitWorld == null) return null; + time = bukkitWorld.getTime(); + } else { + return null; + } + int hours = (int) (time / 1000); + int minutes = (int) ((time - hours * 1000) * 0.06); + hours += 6; + if (hours >= 24) hours -= 24; + String minuteStr = (minutes < 10) ? "0" + minutes : String.valueOf(minutes); + return hours + ":" + minuteStr; + } + } + return null; + } +}