NeoForge Port
This commit is contained in:
19 files changed
+936
-7
No files matched your search
@@ -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,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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+25
@@ -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"
|
||||
@@ -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")
|
||||
Reference in new issue
Block a user