Multiloader scaffold: Architectury common+fabric+forge+neoforge (1.21.1)
Convert the single-loader NeoForge project into an Architectury multiloader: - root/settings/subproject build.gradle, Gradle pinned to 8.10.2 (Loom needs 8.x) - common: generic SkinsgCommands<S> + SkinsgClient bootstrap - per-loader entrypoints (Fabric ClientModInitializer, Forge/NeoForge @Mod) + metadata - migrate-to-multiloader.sh moves the loader/version-agnostic core into common/ - MULTILOADER.md: build/toolchain/proxy + 1.20.1 switch incl. rewritten cape mixin main (working NeoForge 1.21.1) untouched.
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
architectury {
|
||||
common(rootProject.enabled_platforms.split(","))
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Fabric Loader is used at compile time only for the @Environment annotations,
|
||||
// which Architectury remaps to the right annotation on each platform.
|
||||
modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
|
||||
modApi "dev.architectury:architectury:${rootProject.architectury_api_version}"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package xyz.nightsync.skinsg;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import xyz.nightsync.skinsg.client.CapeManager;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Loader-agnostic client bootstrap. Each loader entrypoint calls {@link #init(Path)}
|
||||
* with its own config directory, then wires the client command + client tick using
|
||||
* {@link xyz.nightsync.skinsg.commands.SkinsgCommands} and {@link CapeManager}.
|
||||
*/
|
||||
public final class SkinsgClient {
|
||||
|
||||
private static SkinsgConfig config;
|
||||
|
||||
private SkinsgClient() {}
|
||||
|
||||
public static void init(Path configDir) {
|
||||
config = SkinsgConfig.load(configDir);
|
||||
CapeManager.init(config, () -> {
|
||||
var p = Minecraft.getInstance().player;
|
||||
return p != null ? p.getUUID() : null;
|
||||
});
|
||||
}
|
||||
|
||||
public static SkinsgConfig config() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/** Call every client tick from the loader event. */
|
||||
public static void tick() {
|
||||
CapeManager.clientTick();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package xyz.nightsync.skinsg.commands;
|
||||
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.PlayerInfo;
|
||||
import xyz.nightsync.skinsg.SkinsgConfig;
|
||||
import xyz.nightsync.skinsg.client.CapeManager;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal;
|
||||
import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument;
|
||||
|
||||
/**
|
||||
* Loader-agnostic client command tree, built with RAW Brigadier builders so it is
|
||||
* generic over the command source type {@code S}. Fabric client commands use
|
||||
* {@code FabricClientCommandSource}; Forge/NeoForge use {@code CommandSourceStack}.
|
||||
* Each loader supplies a {@link Feedback} that speaks to its own source, then
|
||||
* registers {@code build(cfg, feedback)} into its client dispatcher.
|
||||
*/
|
||||
public final class SkinsgCommands {
|
||||
|
||||
/** Per-loader chat feedback so common code doesn't touch a loader source type. */
|
||||
public interface Feedback<S> {
|
||||
void success(S source, String message);
|
||||
void failure(S source, String message);
|
||||
}
|
||||
|
||||
public static final String ALL = "@a";
|
||||
|
||||
private SkinsgCommands() {}
|
||||
|
||||
public static <S> LiteralArgumentBuilder<S> build(SkinsgConfig cfg, Feedback<S> fb) {
|
||||
SuggestionProvider<S> targets = (ctx, b) -> {
|
||||
b.suggest(ALL);
|
||||
var conn = Minecraft.getInstance().getConnection();
|
||||
if (conn != null) {
|
||||
for (PlayerInfo pi : conn.getOnlinePlayers()) b.suggest(pi.getProfile().getName());
|
||||
}
|
||||
return b.buildFuture();
|
||||
};
|
||||
|
||||
return literal("skinsg")
|
||||
.then(literal("playgif")
|
||||
.executes(c -> { cfg.setNone(); CapeManager.releaseAll(); return ok(fb, c, "GIF capes: off"); })
|
||||
.then(argument("target", StringArgumentType.word()).suggests(targets)
|
||||
.executes(c -> playgif(fb, cfg, c))))
|
||||
.then(literal("stopgif")
|
||||
.executes(c -> { cfg.setNone(); CapeManager.releaseAll(); return ok(fb, c, "GIF capes: off"); })
|
||||
.then(argument("target", StringArgumentType.word()).suggests(targets)
|
||||
.executes(c -> stopgif(fb, cfg, c))))
|
||||
.then(literal("self")
|
||||
.executes(c -> { cfg.setSelf(); CapeManager.releaseAll(); return ok(fb, c, "GIF capes: self only"); }))
|
||||
.then(literal("speed")
|
||||
.then(argument("target", StringArgumentType.word()).suggests(targets)
|
||||
.then(argument("percent", IntegerArgumentType.integer(1, 2000))
|
||||
.executes(c -> speed(fb, cfg, c)))));
|
||||
}
|
||||
|
||||
private static <S> int playgif(Feedback<S> fb, SkinsgConfig cfg, CommandContext<S> c) {
|
||||
String target = StringArgumentType.getString(c, "target");
|
||||
if (ALL.equals(target)) { cfg.setAll(); return ok(fb, c, "GIF capes: everyone"); }
|
||||
Set<UUID> ids = resolve(target);
|
||||
if (ids.isEmpty()) return err(fb, c, "player not found: " + target);
|
||||
cfg.enableFor(ids);
|
||||
return ok(fb, c, "GIF capes: on for " + target);
|
||||
}
|
||||
|
||||
private static <S> int stopgif(Feedback<S> fb, SkinsgConfig cfg, CommandContext<S> c) {
|
||||
String target = StringArgumentType.getString(c, "target");
|
||||
if (ALL.equals(target)) { cfg.setNone(); CapeManager.releaseAll(); return ok(fb, c, "GIF capes: off"); }
|
||||
Set<UUID> ids = resolve(target);
|
||||
if (ids.isEmpty()) return err(fb, c, "player not found: " + target);
|
||||
cfg.disableFor(ids);
|
||||
for (UUID id : ids) CapeManager.releaseIfPresent(id);
|
||||
return ok(fb, c, "GIF capes: stopped for " + target);
|
||||
}
|
||||
|
||||
private static <S> int speed(Feedback<S> fb, SkinsgConfig cfg, CommandContext<S> c) {
|
||||
String target = StringArgumentType.getString(c, "target");
|
||||
int percent = IntegerArgumentType.getInteger(c, "percent");
|
||||
if (ALL.equals(target)) { cfg.setGlobalSpeed(percent); return ok(fb, c, "GIF speed: " + percent + "% (all)"); }
|
||||
Set<UUID> ids = resolve(target);
|
||||
if (ids.isEmpty()) return err(fb, c, "player not found: " + target);
|
||||
for (UUID id : ids) cfg.setSpeed(id, percent);
|
||||
return ok(fb, c, "GIF speed: " + percent + "% for " + target);
|
||||
}
|
||||
|
||||
private static Set<UUID> resolve(String name) {
|
||||
Set<UUID> out = new HashSet<>();
|
||||
var conn = Minecraft.getInstance().getConnection();
|
||||
if (conn != null) {
|
||||
for (PlayerInfo pi : conn.getOnlinePlayers()) {
|
||||
if (pi.getProfile().getName().equalsIgnoreCase(name)) out.add(pi.getProfile().getId());
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static <S> int ok(Feedback<S> fb, CommandContext<S> c, String text) {
|
||||
fb.success(c.getSource(), "[skinsg] " + text);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static <S> int err(Feedback<S> fb, CommandContext<S> c, String text) {
|
||||
fb.failure(c.getSource(), "[skinsg] " + text);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user