9
0
mirror of https://github.com/Xiao-MoMi/Custom-Nameplates.git synced 2025-12-19 15:09:23 +00:00

improve spectator mode

This commit is contained in:
XiaoMoMi
2024-10-07 17:23:57 +08:00
parent 5df341b2a1
commit f6de779090
18 changed files with 260 additions and 47 deletions

View File

@@ -61,11 +61,6 @@ public abstract class AbstractCNPlayer implements CNPlayer {
this.player = player;
}
/**
* 将所有处于激活状态的变量统筹起来并返回一个更新任务
*
* @return 更新任务
*/
@Override
public List<Placeholder> activePlaceholdersToRefresh() {
Placeholder[] activePlaceholders = activePlaceholders();

View File

@@ -81,6 +81,14 @@ public interface CNPlayer {
*/
boolean isOnline();
/**
*
* Checks if the player is on spectator mode
*
* @return true if the player is on spectator mode, false otherwise
*/
boolean isSpectator();
/**
* Returns the scale of the player.
*

View File

@@ -118,15 +118,15 @@ public abstract class AbstractTag implements Tag {
}
@Override
public void onPlayerCrouching(boolean isCrouching) {
public void onOpacityChange(boolean dark) {
for (CNPlayer viewer : viewerArray) {
onPlayerCrouching(viewer, isCrouching);
onOpacityChange(viewer, dark);
}
}
@Override
public void onPlayerCrouching(CNPlayer viewer, boolean isCrouching) {
Consumer<List<Object>> modifiers = CustomNameplates.getInstance().getPlatform().createOpacityModifier(isCrouching ? 64 : opacity());
public void onOpacityChange(CNPlayer viewer, boolean dark) {
Consumer<List<Object>> modifiers = CustomNameplates.getInstance().getPlatform().createOpacityModifier(dark ? 64 : opacity());
Object packet = CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(entityID, List.of(modifiers));
CustomNameplates.getInstance().getPacketSender().sendPacket(viewer, packet);
}

View File

@@ -150,20 +150,9 @@ public interface Tag {
*/
double getTextHeight(CNPlayer viewer);
/**
* Updates the tag state when the player crouches.
*
* @param isCrouching true if the player is crouching, false otherwise
*/
void onPlayerCrouching(boolean isCrouching);
void onOpacityChange(boolean dark);
/**
* Updates the tag state when the player crouches for a specific viewer.
*
* @param viewer the player to update
* @param isCrouching true if the player is crouching, false otherwise
*/
void onPlayerCrouching(CNPlayer viewer, boolean isCrouching);
void onOpacityChange(CNPlayer viewer, boolean dark);
/**
* Updates the tag scale when the player's scale changes.

View File

@@ -112,4 +112,14 @@ public interface UnlimitedTagManager extends Reloadable {
*/
@ApiStatus.Internal
void onPlayerAttributeSet(CNPlayer owner, CNPlayer viewer, double scale);
/**
* Internal method for updating a player's game mode
*
* @param owner the player who owns the tags
* @param viewer the player viewing the tags
* @param isSpectator true if the player is a spectator, false otherwise
*/
@ApiStatus.Internal
void onPlayerGameModeChange(CNPlayer owner, CNPlayer viewer, boolean isSpectator);
}

View File

@@ -27,6 +27,7 @@ public class Tracker {
private boolean isCrouching;
private double scale;
private boolean isSpectator;
private final CNPlayer tracker;
private final CopyOnWriteArrayList<Integer> passengerIDs = new CopyOnWriteArrayList<>();
@@ -54,6 +55,14 @@ public class Tracker {
isCrouching = crouching;
}
public boolean isSpectator() {
return isSpectator;
}
public void setSpectator(boolean spectator) {
isSpectator = spectator;
}
public double getScale() {
return scale;
}

View File

@@ -103,6 +103,10 @@ public class BubbleTag extends AbstractTag {
return canShow;
}
@Override
public void onOpacityChange(CNPlayer viewer, boolean dark) {
}
@Override
public boolean canShow(CNPlayer viewer) {
if (!viewer.isMet(owner, manager.viewBubbleRequirements())) {

View File

@@ -63,7 +63,7 @@ public class NameTag extends AbstractTag implements RelationalFeature {
owner.position().add(0,(1.8 + (affectedByCrouching() && tracker.isCrouching() && !owner.isFlying() ? -0.3 : 0) + renderer.hatOffset()) * (affectedByScaling() ? tracker.getScale() : 1),0),
0f, 0f, 0d,
0, 0, 0,
component, config.backgroundColor(), config.opacity(), config.hasShadow(), config.isSeeThrough(), config.useDefaultBackgroundColor(),
component, config.backgroundColor(), opacity(), config.hasShadow(), config.isSeeThrough(), config.useDefaultBackgroundColor(),
config.alignment(), config.viewRange(), config.shadowRadius(), config.shadowStrength(),
(affectedByScaling() ? scale(viewer).multiply(tracker.getScale()) : scale(viewer)),
(affectedByScaling() ? translation(viewer).multiply(tracker.getScale()) : translation(viewer)),
@@ -144,7 +144,7 @@ public class NameTag extends AbstractTag implements RelationalFeature {
@Override
public double getTextHeight(CNPlayer viewer) {
String current = currentText.render(viewer);
Tracker tracker = viewer.getTracker(owner);
Tracker tracker = owner.getTracker(viewer);
int lines = CustomNameplates.getInstance().getAdvanceManager().getLines(current, config.lineWidth());
return ((lines * (9+1) + config.translation().y()) * config.scale().y() * (config.affectedByScaling() ? tracker.getScale() : 1)) / 40;
}
@@ -194,7 +194,7 @@ public class NameTag extends AbstractTag implements RelationalFeature {
@Override
public byte opacity() {
return config.opacity();
return owner.isSpectator() || (owner.isCrouching() && affectedByCrouching()) ? 64 : config.opacity();
}
@Override

View File

@@ -71,32 +71,23 @@ public class TagRendererImpl implements TagRenderer {
HashSet<CNPlayer> playersToUpdatePassengers = new HashSet<>();
for (Tag display : tags) {
boolean canShow = display.canShow();
// 能大众显示
if (canShow) {
// 当前大众显示
if (display.isShown()) {
for (CNPlayer nearby : owner.nearbyPlayers()) {
// 如果已经展示了
if (display.isShown(nearby)) {
// 不满足条件就撤掉
if (!display.canShow(nearby)) {
display.hide(nearby);
}
} else {
// 未展示,则检测条件,可以就上
if (display.canShow(nearby)) {
display.show(nearby);
playersToUpdatePassengers.add(nearby);
}
}
}
// 更新一下文字顺序放在后面是为了防止已经被hide的玩家多收一个包
display.tick();
} else {
// 之前隐藏,现在开始大众显示
// 需要重置文字顺序
display.init();
// 更新一下文字顺序
display.tick();
display.show();
for (CNPlayer nearby : owner.nearbyPlayers()) {
@@ -107,8 +98,6 @@ public class TagRendererImpl implements TagRenderer {
}
}
} else {
// 不能展示的情况
// 如果已经展示了,就咔掉所有玩家
if (display.isShown()) {
display.hide();
}
@@ -264,15 +253,15 @@ public class TagRendererImpl implements TagRenderer {
}
public void handleEntityDataChange(CNPlayer another, boolean isCrouching) {
Tracker properties = owner.getTracker(another);
Tracker tracker = owner.getTracker(another);
// should never be null
if (properties == null) return;
properties.setCrouching(isCrouching);
if (tracker == null) return;
tracker.setCrouching(isCrouching);
for (Tag display : this.tags) {
if (display.affectedByCrouching()) {
if (display.isShown()) {
if (display.isShown(another)) {
display.onPlayerCrouching(another, isCrouching);
display.onOpacityChange(another, isCrouching || tracker.isSpectator());
}
}
}
@@ -280,11 +269,10 @@ public class TagRendererImpl implements TagRenderer {
}
public void handleAttributeChange(CNPlayer another, double scale) {
boolean updatePassengers = false;
Tracker properties = owner.getTracker(another);
Tracker tracker = owner.getTracker(another);
// should never be null
if (properties == null) return;
properties.setScale(scale);
if (tracker == null) return;
tracker.setScale(scale);
for (Tag display : this.tags) {
if (display.affectedByScaling()) {
if (display.isShown()) {
@@ -295,4 +283,18 @@ public class TagRendererImpl implements TagRenderer {
}
}
}
public void handleGameModeChange(CNPlayer another, boolean isSpectator) {
Tracker tracker = owner.getTracker(another);
// can be null
if (tracker == null) return;
tracker.setSpectator(isSpectator);
for (Tag display : this.tags) {
if (display.isShown()) {
if (display.isShown(another)) {
display.onOpacityChange(another, isSpectator || tracker.isCrouching());
}
}
}
}
}

View File

@@ -67,6 +67,7 @@ public class UnlimitedTagManagerImpl implements UnlimitedTagManager, JoinQuitLis
Tracker tracker = player.addPlayerToTracker(player);
tracker.setScale(player.scale());
tracker.setCrouching(player.isCrouching());
tracker.setSpectator(player.isSpectator());
plugin.getUnlimitedTagManager().onAddPlayer(player, player);
((AbstractCNPlayer) player).setPreviewing(true);
}
@@ -179,6 +180,14 @@ public class UnlimitedTagManagerImpl implements UnlimitedTagManager, JoinQuitLis
}
}
@Override
public void onPlayerGameModeChange(CNPlayer owner, CNPlayer viewer, boolean isSpectator) {
TagRendererImpl controller = tagRenderers.get(owner.uuid());
if (controller != null) {
controller.handleGameModeChange(viewer, isSpectator);
}
}
private void loadConfig() {
plugin.getConfigManager().saveResource("configs" + File.separator + "nameplate.yml");
YamlDocument document = plugin.getConfigManager().loadData(new File(plugin.getDataDirectory().toFile(), "configs" + File.separator + "nameplate.yml"));

View File

@@ -1,7 +1,6 @@
# Requirements for sending the bubble
sender-requirements:
permission: bubbles.send
'!gamemode': spectator
potion-effect: "INVISIBILITY<0"
viewer-requirements:

View File

@@ -20,6 +20,7 @@ package net.momirealms.customnameplates.bukkit;
import net.momirealms.customnameplates.api.AbstractCNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.util.Vector3;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
@@ -96,6 +97,11 @@ public class BukkitCNPlayer extends AbstractCNPlayer {
return player.isOnline();
}
@Override
public boolean isSpectator() {
return player.getGameMode() == GameMode.SPECTATOR;
}
@Override
public Set<Integer> passengers() {
return player.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toSet());

View File

@@ -40,6 +40,7 @@ import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class BukkitPlatform implements Platform {
@@ -178,6 +179,29 @@ public class BukkitPlatform implements Platform {
}
}, "PacketPlayOutEntityDestroy", "ClientboundRemoveEntitiesPacket");
registerPacketConsumer((player, event, packet) -> {
try {
EnumSet<?> enums = (EnumSet<?>) Reflections.field$ClientboundPlayerInfoUpdatePacket$actions.get(packet);
if (enums == null) return;
if (!enums.contains(Reflections.enum$ClientboundPlayerInfoUpdatePacket$Action$UPDATE_GAME_MODE)) return;
List<Object> entries = (List<Object>) Reflections.field$ClientboundPlayerInfoUpdatePacket$entries.get(packet);
for (Object entry : entries) {
UUID uuid = (UUID) Reflections.field$ClientboundPlayerInfoUpdatePacket$Entry$profileId.get(entry);
if (uuid == null) continue;
Object gameType = Reflections.field$ClientboundPlayerInfoUpdatePacket$Entry$gameMode.get(entry);
if (gameType == null) continue;
int mode = (int) Reflections.method$GameType$getId.invoke(gameType);
boolean isSpectator = mode == 3;
CNPlayer another = CustomNameplates.getInstance().getPlayer(uuid);
if (another != null) {
CustomNameplates.getInstance().getUnlimitedTagManager().onPlayerGameModeChange(another, player, isSpectator);
}
}
} catch (ReflectiveOperationException e) {
CustomNameplates.getInstance().getPluginLogger().severe("Failed to handle ClientboundPlayerInfoUpdatePacket", e);
}
}, "ClientboundPlayerInfoUpdatePacket");
// for cosmetic plugin compatibility
registerPacketConsumer((player, event, packet) -> {
try {

View File

@@ -49,6 +49,7 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new DebugPerformanceCommand(this, plugin),
new DebugWidthCommand(this, plugin),
new DebugLinesCommand(this, plugin),
new DebugTestCommand(this, plugin),
new NameplatesEquipCommand(this, plugin),
new NameplatesUnEquipCommand(this, plugin),
new NameplatesListCommand(this, plugin),

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* 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 <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.bukkit.command.feature;
import net.momirealms.customnameplates.bukkit.BukkitCustomNameplates;
import net.momirealms.customnameplates.bukkit.command.BukkitCommandFeature;
import net.momirealms.customnameplates.common.command.CustomNameplatesCommandManager;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
public class DebugTestCommand extends BukkitCommandFeature<CommandSender> {
public DebugTestCommand(CustomNameplatesCommandManager<CommandSender> commandManager, BukkitCustomNameplates plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.senderType(Player.class)
.handler(context -> {
});
}
@Override
public String getFeatureID() {
return "debug_test";
}
}

View File

@@ -26,6 +26,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
@@ -741,4 +742,106 @@ public class Reflections {
clazz$AttributeModifier, double.class, 0
)
);
public static final Class<?> clazz$ClientboundGameEventPacket = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundGameEventPacket"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayOutGameStateChange")
)
);
public static final Class<?> clazz$ClientboundGameEventPacket$Type = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundGameEventPacket$Type"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayOutGameStateChange$a")
)
);
public static final Field field$ClientboundGameEventPacket$event = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ClientboundGameEventPacket, clazz$ClientboundGameEventPacket$Type, 0
)
);
public static final Field field$ClientboundGameEventPacket$param = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ClientboundGameEventPacket, float.class, 0
)
);
public static final Field field$ClientboundGameEventPacket$Type$id = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ClientboundGameEventPacket$Type, int.class, 0
)
);
public static final Class<?> clazz$ClientboundPlayerInfoUpdatePacket = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket")
)
);
public static final Field field$ClientboundPlayerInfoUpdatePacket$actions = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ClientboundPlayerInfoUpdatePacket, EnumSet.class, 0
)
);
public static final Field field$ClientboundPlayerInfoUpdatePacket$entries = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ClientboundPlayerInfoUpdatePacket, List.class, 0
)
);
public static final Class<?> clazz$ClientboundPlayerInfoUpdatePacket$Action = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket$a")
)
);
public static final Enum<?> enum$ClientboundPlayerInfoUpdatePacket$Action$UPDATE_GAME_MODE;
static {
Enum<?> updateGameMode;
try {
updateGameMode = Enum.valueOf((Class<Enum>) clazz$ClientboundPlayerInfoUpdatePacket$Action, "UPDATE_GAME_MODE");
} catch (Exception e) {
updateGameMode = Enum.valueOf((Class<Enum>) clazz$ClientboundPlayerInfoUpdatePacket$Action, "c");
}
enum$ClientboundPlayerInfoUpdatePacket$Action$UPDATE_GAME_MODE = updateGameMode;
}
public static final Class<?> clazz$ClientboundPlayerInfoUpdatePacket$Entry = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket$b")
)
);
public static final Class<?> clazz$GameType = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.level.GameType"),
BukkitReflectionUtils.assembleMCClass("world.level.EnumGamemode")
)
);
public static final Field field$ClientboundPlayerInfoUpdatePacket$Entry$gameMode = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ClientboundPlayerInfoUpdatePacket$Entry, clazz$GameType, 0
)
);
public static final Field field$ClientboundPlayerInfoUpdatePacket$Entry$profileId = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ClientboundPlayerInfoUpdatePacket$Entry, UUID.class, 0
)
);
public static final Method method$GameType$getId = requireNonNull(
ReflectionUtils.getMethod(
clazz$GameType, new String[] { "getId", "a" }
)
);
}

View File

@@ -133,4 +133,12 @@ bubbles_list:
enable: true
permission: bubbles.command.list
usage:
- /bubbles list
- /bubbles list
# A command to test some stuffs
# Usage: [COMMAND]
debug_test:
enable: false
permission: nameplates.command.debug.test
usage:
- /nameplates debug test

View File

@@ -1,6 +1,6 @@
# Project settings
# Rule: [major update].[feature update].[bug fix]
project_version=3.0.0-beta-1
project_version=3.0.0-beta-2
config_version=36
project_group=net.momirealms