NeoForge Port

This commit is contained in:
Joseph T. McQuigg committed 2025-03-30 14:51:58 -04:00
1 parent 5184ef170f
commit 39a2aee5a0
19 files changed
+936 -7

No files matched your search

+1 -1
View File
@@ -5,5 +5,5 @@ plugins {
dependencies {
implementation("com.gradleup.shadow:shadow-gradle-plugin:8.3.6")
implementation("io.freefair.gradle:lombok-plugin:8.13")
implementation("fabric-loom:fabric-loom.gradle.plugin:1.10-SNAPSHOT")
implementation("dev.architectury.loom:dev.architectury.loom.gradle.plugin:1.9-SNAPSHOT")
}
+3
View File
@@ -3,6 +3,9 @@ rootProject.name = "build-logic"
dependencyResolutionManagement {
repositories {
maven("https://maven.fabricmc.net/")
maven("https://maven.architectury.dev/")
maven("https://maven.minecraftforge.net/")
maven("https://maven.neoforged.net/releases/")
gradlePluginPortal()
mavenCentral()
}
+2 -4
View File
@@ -1,5 +1,5 @@
plugins {
id("fabric-loom")
id("dev.architectury.loom")
}
repositories {
@@ -25,9 +25,7 @@ dependencies {
modImplementation(fabricApi.module("fabric-command-api-v2", version))
}
loom {
accessWidenerPath.set(file("src/main/resources/resources/tab.accesswidener"))
}
loom.accessWidenerPath.set(file("src/main/resources/resources/tab.accesswidener"))
tasks {
compileJava {
@@ -1,4 +1,4 @@
accessWidener v1 named
accessWidener v2 named
# Scoreboard packets
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket method I
+1 -1
View File
@@ -30,7 +30,7 @@ tasks {
}
registerPlatform(rootProject.projects.fabric.dependencyProject, rootProject.projects.fabric.dependencyProject.tasks.named<org.gradle.jvm.tasks.Jar>("remapJar").get())
registerPlatform(rootProject.projects.neoforge.dependencyProject, rootProject.projects.neoforge.dependencyProject.tasks.named<org.gradle.jvm.tasks.Jar>("remapJar").get())
}
build {
dependsOn(shadowJar)
+33
View File
@@ -0,0 +1,33 @@
plugins {
id("dev.architectury.loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
maven("https://maven.nucleoid.xyz/")
maven("https://maven.neoforged.net/releases")
maven("https://maven.parchmentmc.org")
}
@Suppress("UnstableApiUsage")
dependencies {
minecraft("com.mojang:minecraft:1.21.4")
mappings(loom.layered{
officialMojangMappings()
parchment("org.parchmentmc.data:parchment-1.21.4:2025.03.23@zip")
})
neoForge("net.neoforged:neoforge:21.4.123")
api(projects.shared)
}
loom.neoForge.accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
tasks {
compileJava {
options.release.set(21)
}
}
+1
View File
@@ -0,0 +1 @@
loom.platform=neoforge
@@ -0,0 +1,64 @@
package me.neznamy.tab.platforms.neoforge;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
import net.minecraft.server.level.ServerBossEvent;
import net.minecraft.world.BossEvent.BossBarColor;
import net.minecraft.world.BossEvent.BossBarOverlay;
import org.jetbrains.annotations.NotNull;
/**
* BossBar implementation for NeoForge using packets.
*/
@RequiredArgsConstructor
public class NeoForgeBossBar extends SafeBossBar<ServerBossEvent> {
/** Player this BossBar belongs to */
@NotNull
private final NeoForgeTabPlayer player;
@Override
@NotNull
public ServerBossEvent constructBossBar(@NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
ServerBossEvent bar = new ServerBossEvent(
title.convert(),
BossBarColor.valueOf(color.name()),
BossBarOverlay.valueOf(style.name())
);
bar.setProgress(progress);
return bar;
}
@Override
public void create(@NotNull BossBarInfo bar) {
bar.getBossBar().addPlayer(player.getPlayer());
}
@Override
public void updateTitle(@NotNull BossBarInfo bar) {
bar.getBossBar().setName(bar.getTitle().convert());
}
@Override
public void updateProgress(@NotNull BossBarInfo bar) {
bar.getBossBar().setProgress(bar.getProgress());
}
@Override
public void updateStyle(@NotNull BossBarInfo bar) {
bar.getBossBar().setOverlay(BossBarOverlay.valueOf(bar.getStyle().name()));
}
@Override
public void updateColor(@NotNull BossBarInfo bar) {
bar.getBossBar().setColor(BossBarColor.valueOf(bar.getColor().name()));
}
@Override
public void remove(@NotNull BossBarInfo bar) {
bar.getBossBar().removePlayer(player.getPlayer());
}
}
@@ -0,0 +1,37 @@
package me.neznamy.tab.platforms.neoforge;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
/**
* Event listener for NeoForge.
*/
public class NeoForgeEventListener implements EventListener<ServerPlayer> {
/**
* Registers all event listeners.
*/
public void register() {
IEventBus eventBus = NeoForge.EVENT_BUS;
eventBus.addListener((PlayerEvent.PlayerLoggedInEvent event) -> join((ServerPlayer) event.getEntity()));
eventBus.addListener((PlayerEvent.PlayerLoggedOutEvent event) -> quit(event.getEntity().getUUID()));
eventBus.addListener((PlayerEvent.PlayerRespawnEvent event) -> {
ServerPlayer player = (ServerPlayer) event.getEntity();
replacePlayer(player.getUUID(), player);
worldChange(player.getUUID(), NeoForgeTAB.getLevelName(player.level()));
});
eventBus.addListener((PlayerEvent.PlayerChangedDimensionEvent event) -> worldChange(event.getEntity().getUUID(), NeoForgeTAB.getLevelName(event.getEntity().level())));
}
@Override
@NotNull
public TabPlayer createPlayer(@NotNull ServerPlayer player) {
return new NeoForgeTabPlayer((NeoForgePlatform) TAB.getInstance().getPlatform(), player);
}
}
@@ -0,0 +1,25 @@
package me.neznamy.tab.platforms.neoforge;
import io.netty.channel.Channel;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
/**
* Pipeline injector for Fabric.
*/
public class NeoForgePipelineInjector extends NettyPipelineInjector {
/**
* Constructs new instance.
*/
public NeoForgePipelineInjector() {
super("packet_handler");
}
@Override
@NotNull
protected Channel getChannel(@NotNull TabPlayer player) {
return ((NeoForgeTabPlayer)player).getPlayer().connection.getConnection().channel();
}
}
@@ -0,0 +1,193 @@
package me.neznamy.tab.platforms.neoforge;
import com.mojang.logging.LogUtils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.chat.component.KeybindComponent;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.chat.component.TextComponent;
import me.neznamy.chat.component.TranslatableComponent;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.SharedConstants;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.TextColor;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.loading.FMLPaths;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
/**
* Platform implementation for NeoForge
*/
@RequiredArgsConstructor
@Getter
public class NeoForgePlatform implements BackendPlatform {
/** Minecraft server reference */
private final MinecraftServer server;
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
registerDummyPlaceholder(identifier);
}
@Override
public void loadPlayers() {
for (ServerPlayer player : getOnlinePlayers()) {
TAB.getInstance().addPlayer(new NeoForgeTabPlayer(this, player));
}
}
private Collection<ServerPlayer> getOnlinePlayers() {
// It's nullable on startup
return server.getPlayerList() == null ? Collections.emptyList() : server.getPlayerList().getPlayers();
}
@Override
@NotNull
public PipelineInjector createPipelineInjector() {
return new NeoForgePipelineInjector();
}
@Override
@NotNull
public TabExpansion createTabExpansion() {
return new EmptyTabExpansion();
}
@Override
@Nullable
public TabFeature getPerWorldPlayerList(@NotNull PerWorldPlayerListConfiguration configuration) {
return null;
}
@Override
public void logInfo(@NotNull TabComponent message) {
LogUtils.getLogger().info("[TAB] " + message.toRawText());
}
@Override
public void logWarn(@NotNull TabComponent message) {
LogUtils.getLogger().warn("[TAB] " + message.toRawText());
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Fabric] " + SharedConstants.getCurrentVersion().getName();
}
@Override
public void registerListener() {
new NeoForgeEventListener().register();
}
@Override
public void registerCommand() {
// Event listener must be registered in main class
}
@Override
public void startMetrics() {
// Not available
}
@Override
@NotNull
public File getDataFolder() {
return FMLPaths.CONFIGDIR.get().resolve(TabConstants.PLUGIN_ID).toFile();
}
@Override
@NotNull
public Component convertComponent(@NotNull TabComponent component) {
// Component type
MutableComponent nmsComponent;
if (component instanceof TextComponent) {
nmsComponent = Component.literal(((TextComponent) component).getText());
} else if (component instanceof TranslatableComponent) {
nmsComponent = Component.translatable(((TranslatableComponent) component).getKey());
} else if (component instanceof KeybindComponent) {
nmsComponent = Component.keybind(((KeybindComponent) component).getKeybind());
} else {
throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
}
// Component style
nmsComponent.setStyle(new Style(
component.getModifier().getColor() == null ? null : TextColor.fromRgb(component.getModifier().getColor().getRgb()),
component.getModifier().getShadowColor(),
component.getModifier().getBold(),
component.getModifier().getItalic(),
component.getModifier().getUnderlined(),
component.getModifier().getStrikethrough(),
component.getModifier().getObfuscated(),
null,
null,
null,
component.getModifier().getFont() == null ? null : ResourceLocation.tryParse(component.getModifier().getFont())
));
// Extra
for (TabComponent extra : component.getExtra()) {
nmsComponent.getSiblings().add(convertComponent(extra));
}
return nmsComponent;
}
@Override
@NotNull
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
return new NeoForgeScoreboard((NeoForgeTabPlayer) player);
}
@Override
@NotNull
public BossBar createBossBar(@NotNull TabPlayer player) {
return new NeoForgeBossBar((NeoForgeTabPlayer) player);
}
@Override
@NotNull
public TabList createTabList(@NotNull TabPlayer player) {
return new NeoForgeTabList((NeoForgeTabPlayer) player);
}
@Override
public boolean supportsScoreboards() {
return true;
}
@Override
public double getTPS() {
double mspt = getMSPT();
if (mspt < 50) return 20;
return Math.round(1000 / mspt);
}
@Override
public double getMSPT() {
return (float) server.getAverageTickTimeNanos() / 1000000;
}
}
@@ -0,0 +1,144 @@
package me.neznamy.tab.platforms.neoforge;
import lombok.NonNull;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
/**
* Scoreboard implementation for NeoForge using packets.
*/
public class NeoForgeScoreboard extends SafeScoreboard<NeoForgeTabPlayer> {
private static final ChatFormatting[] formats = ChatFormatting.values();
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NeoForgeScoreboard(NeoForgeTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(),
RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], obj));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
obj.setDisplayName(objective.getTitle().convert());
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
sendPacket(new ClientboundSetScorePacket(
score.getHolder(),
score.getObjective().getName(),
score.getValue(),
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new PlayerTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.getPlayers().addAll(team.getPlayers());
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
}
private void updateTeamProperties(@NonNull Team team) {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(formats[team.getColor().getLegacyColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
public void onPacketSend(@NonNull Object packet) {
if (isAntiOverrideScoreboard()) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
}
if (isAntiOverrideTeams() && packet instanceof ClientboundSetPlayerTeamPacket team) {
if (team.method == TeamAction.UPDATE) return;
team.players = onTeamPacket(team.method, team.getName(), team.getPlayers());
}
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
player.getPlayer().connection.send(packet);
}
}
@@ -0,0 +1,34 @@
package me.neznamy.tab.platforms.neoforge;
import me.neznamy.tab.shared.TAB;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
import net.neoforged.neoforge.event.server.ServerStartingEvent;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
import org.jetbrains.annotations.NotNull;
@Mod(value = "tab", dist = Dist.DEDICATED_SERVER)
public class NeoForgeTAB {
public NeoForgeTAB(final IEventBus eventBus) {
IEventBus EVENT_BUS = NeoForge.EVENT_BUS;
EVENT_BUS.addListener((RegisterCommandsEvent event) -> new NeoForgeTabCommand().onRegisterCommands(event.getDispatcher()));
EVENT_BUS.addListener((ServerStartingEvent event) -> TAB.create(new NeoForgePlatform(event.getServer())));
EVENT_BUS.addListener((ServerStoppingEvent event) -> TAB.getInstance().unload());
}
@NotNull
public static String getLevelName(@NotNull Level level) {
String path = level.dimension().location().getPath();
return ((ServerLevelData)level.getLevelData()).getLevelName() + switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
}
}
@@ -0,0 +1,96 @@
package me.neznamy.tab.platforms.neoforge;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
/**
* Command handler for plugin's command for Fabric.
*/
public class NeoForgeTabCommand {
/**
* Handles command register request from server and registers own command.
*
* @param dispatcher
* Dispatcher to register command to
*/
public void onRegisterCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
LiteralCommandNode<CommandSourceStack> command = Commands.literal("tab") // TODO extract it from Platform somehow
.executes(context -> executeCommand(context.getSource(), new String[0]))
.build();
ArgumentCommandNode<CommandSourceStack, String> args = Commands.argument("args", StringArgumentType.greedyString())
.suggests((context, builder) -> getSuggestions(context.getSource(), getArguments(context), builder))
.executes(context -> executeCommand(context.getSource(), getArguments(context)))
.build();
command.addChild(args);
dispatcher.getRoot().addChild(command);
}
@NotNull
private String[] getArguments(@NotNull CommandContext<CommandSourceStack> context) {
String input = context.getInput();
int firstSpace = input.indexOf(' ');
if (firstSpace == -1) return new String[0];
String rawArgs = input.substring(firstSpace + 1);
String[] args = rawArgs.split(" ");
if (rawArgs.endsWith(" ")) {
args = Arrays.copyOf(args, args.length+1);
args[args.length-1] = "";
}
return args;
}
@SuppressWarnings("SameReturnValue") // Unused by plugin
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
boolean hasReloadPermission = true; //PermissionsAPIHook.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = true; //PermissionsAPIHook.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
source.sendSystemMessage(TabComponent.fromColoredText(message).convert());
}
} else {
if (source.getEntity() == null) {
TAB.getInstance().getCommand().execute(null, args);
} else {
TabPlayer player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
if (player != null) TAB.getInstance().getCommand().execute(player, args);
}
}
return 0;
}
@NotNull
private CompletableFuture<Suggestions> getSuggestions(@NotNull CommandSourceStack source, @NotNull String[] args,
@NotNull SuggestionsBuilder builder) {
TabPlayer player = null;
if (source.getEntity() != null) {
player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
if (player == null) return Suggestions.empty();
}
SuggestionsBuilder newBuilder = builder;
int lastSpace = newBuilder.getRemaining().lastIndexOf(' ');
if (lastSpace != -1) {
newBuilder = newBuilder.createOffset(lastSpace + 1 + newBuilder.getStart());
}
for (String suggestion : TAB.getInstance().getCommand().complete(player, args)) {
newBuilder.suggest(suggestion);
}
return newBuilder.buildFuture();
}
}
@@ -0,0 +1,187 @@
package me.neznamy.tab.platforms.neoforge;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
import net.minecraft.world.level.GameType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* TabList implementation for Fabric using packets.
*/
public class NeoForgeTabList extends TrackedTabList<NeoForgeTabPlayer> {
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NeoForgeTabList(@NotNull NeoForgeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
sendPacket(updateHat, entry, "", null, false, 0, 0, null, 0, showHat);
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
}
@Override
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return true; // TODO?
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = player.getPlayer().getGameProfile().getProperties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.value(), property.signature());
}
@Override
public void onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
displayName = expectedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, nmsData.profileId(), latency);
if (newLatency != latency) {
latency = newLatency;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) info.entries = updatedList;
}
}
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
latency,
GameType.byId(gameMode),
displayName == null ? null : displayName.convert(),
showHat,
listOrder,
null
));
sendPacket(packet);
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NotNull
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
player.getPlayer().connection.send(packet);
}
}
@@ -0,0 +1,83 @@
package me.neznamy.tab.platforms.neoforge;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import net.minecraft.SharedConstants;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
/**
* TabPlayer implementation for NeoForge.
*/
public class NeoForgeTabPlayer extends BackendTabPlayer {
/**
* Constructs new instance with given parameters.
*
* @param platform
* Server platform
* @param player
* Platform's player object
*/
public NeoForgeTabPlayer(@NotNull NeoForgePlatform platform, @NotNull ServerPlayer player) {
super(platform, player, player.getUUID(), player.getGameProfile().getName(),
NeoForgeTAB.getLevelName(player.level()), SharedConstants.getProtocolVersion());
}
@Override
public boolean hasPermission(@NotNull String permission) {
return true; // PermissionsAPIHook.hasPermission(getPlayer(), permission);
}
@Override
public int getPing() {
return getPlayer().connection.latency();
}
@Override
public void sendMessage(@NotNull TabComponent message) {
getPlayer().sendSystemMessage(message.convert());
}
@Override
public boolean hasInvisibilityPotion() {
return false;
}
@Override
public boolean isDisguised() {
return false;
}
@Override
@NotNull
public ServerPlayer getPlayer() {
return (ServerPlayer) player;
}
@Override
public NeoForgePlatform getPlatform() {
return (NeoForgePlatform) platform;
}
@Override
public boolean isVanished0() {
return false;
}
@Override
public int getGamemode() {
return getPlayer().gameMode.getGameModeForPlayer().getId();
}
@Override
public double getHealth() {
return getPlayer().getHealth();
}
@Override
@NotNull
public String getDisplayName() {
return getPlayer().getGameProfile().getName(); // Will make it work properly if someone asks
}
}
@@ -0,0 +1,4 @@
public net.minecraft.network.chat.Style <init>(Lnet/minecraft/network/chat/TextColor;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lnet/minecraft/network/chat/ClickEvent;Lnet/minecraft/network/chat/HoverEvent;Ljava/lang/String;Lnet/minecraft/resources/ResourceLocation;)V # constructor
public-f net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket entries # entries
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket method # method
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket players # players
@@ -0,0 +1,25 @@
modLoader = "javafml"
loaderVersion = "[4,)"
issueTrackerURL = ""
license = "All Rights Reserved"
[[mods]]
modId = "tab"
version = "@version@"
displayName = "@name@"
authors = "@author@"
description = '''@description@'''
[[dependencies.tab]]
modId = "neoforge"
type = "required"
versionRange = "[21.4.121,)"
ordering = "NONE"
side = "BOTH"
[[dependencies.tab]]
modId = "minecraft"
type = "required"
versionRange = "[1.21.4,)"
ordering = "NONE"
side = "BOTH"
+2
View File
@@ -20,6 +20,7 @@ pluginManagement {
includeBuild("build-logic")
repositories {
maven("https://repo.spongepowered.org/repository/maven-public/")
maven("https://maven.architectury.dev/")
gradlePluginPortal()
mavenCentral()
}
@@ -38,4 +39,5 @@ include(":bungeecord")
include(":component")
include(":sponge")
include(":fabric")
include(":neoforge")
include(":jar")