Add FTB NBT editor shortcuts

This commit is contained in:
ms2k 2026-11-04 00:42:01 +09:00
commit 30f4dc4933
8 changed files with 263 additions and 0 deletions

View file

@ -0,0 +1,59 @@
package dev.quorate.client.nbt;
import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.ClientTickEvent;
import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent;
import org.lwjgl.glfw.GLFW;
public final class NbtKeys {
private static final KeyMapping OPEN =
new KeyMapping(
"key.saltatory_client.nbt",
InputConstants.Type.KEYSYM,
GLFW.GLFW_KEY_N,
"key.categories.misc");
private NbtKeys() {}
@EventBusSubscriber(modid = "saltatory_client", value = Dist.CLIENT)
public static final class ModEvents {
@SubscribeEvent
public static void register(RegisterKeyMappingsEvent event) {
if (NbtPlugin.supported()) event.register(OPEN);
}
}
@EventBusSubscriber(modid = "saltatory_client", value = Dist.CLIENT)
public static final class GameEvents {
@SubscribeEvent
public static void tick(ClientTickEvent.Post event) {
Minecraft mc = Minecraft.getInstance();
while (OPEN.consumeClick()) {
if (!NbtPlugin.supported()
|| mc.player == null
|| mc.screen != null
|| mc.getConnection() == null) continue;
String target = "item";
if (mc.hitResult instanceof EntityHitResult hit) {
String kind = hit.getEntity() instanceof Player ? "player " : "entity ";
target = kind + hit.getEntity().getStringUUID();
} else if (mc.hitResult instanceof BlockHitResult hit
&& mc.level.getBlockEntity(hit.getBlockPos()) != null) {
var pos = hit.getBlockPos();
target = "block " + pos.getX() + " " + pos.getY() + " " + pos.getZ();
}
mc.getConnection().sendCommand("ftblibrary nbtedit " + target);
}
}
}
}

View file

@ -0,0 +1,45 @@
package dev.quorate.client.nbt;
import dev.quorate.client.mixin.ModRanges;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.util.List;
import java.util.Set;
public final class NbtPlugin implements IMixinConfigPlugin {
static final String FTB = "[2101.1.37]";
public static boolean supported() {
return ModRanges.loaded("ftblibrary", FTB);
}
@Override
public void onLoad(String mixinPackage) {}
@Override
public String getRefMapperConfig() {
return null;
}
@Override
public boolean shouldApplyMixin(String target, String mixin) {
return supported();
}
@Override
public void acceptTargets(Set<String> targets, Set<String> others) {}
@Override
public List<String> getMixins() {
return null;
}
@Override
public void preApply(String target, ClassNode node, String mixin, IMixinInfo info) {}
@Override
public void postApply(String target, ClassNode node, String mixin, IMixinInfo info) {}
}

View file

@ -0,0 +1,105 @@
package dev.quorate.client.nbt.mixin;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import dev.ftb.mods.ftblibrary.nbtedit.NBTEditorScreen;
import dev.ftb.mods.ftblibrary.nbtedit.NBTEditorScreen.ButtonNBT;
import dev.ftb.mods.ftblibrary.nbtedit.NBTEditorScreen.ButtonNBTCollection;
import dev.ftb.mods.ftblibrary.nbtedit.NBTEditorScreen.ButtonNBTMap;
import dev.ftb.mods.ftblibrary.ui.input.Key;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.TagParser;
import net.minecraft.network.chat.Component;
import org.lwjgl.glfw.GLFW;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(value = NBTEditorScreen.class, remap = false)
public abstract class EditorMixin {
@Shadow private ButtonNBT selected;
@Shadow
private void setSelected(ButtonNBT node) {
throw new AssertionError();
}
@Inject(method = "keyPressed", at = @At("RETURN"), cancellable = true)
private void saltatory$keys(Key key, CallbackInfoReturnable<Boolean> result) {
if (result.getReturnValue() || !key.modifiers.control()) return;
if (key.is(GLFW.GLFW_KEY_C)) {
saltatory$copy();
result.setReturnValue(true);
} else if (key.is(GLFW.GLFW_KEY_V)) {
saltatory$paste();
result.setReturnValue(true);
} else if (key.is(GLFW.GLFW_KEY_D) || key.is(GLFW.GLFW_KEY_X)) {
NodeAccess node = (NodeAccess) selected;
ButtonNBTCollection parent = node.saltatory$parent();
if (parent != null) {
if (key.is(GLFW.GLFW_KEY_X)) saltatory$copy();
parent.setTag(node.saltatory$key(), null);
parent.updateChildren(false);
setSelected(parent);
parent.getGui().refreshWidgets();
}
result.setReturnValue(true);
}
}
@Unique
private void saltatory$copy() {
NodeAccess node = (NodeAccess) selected;
ButtonNBTCollection parent = node.saltatory$parent();
CompoundTag copied = new CompoundTag();
if (parent == null && selected instanceof ButtonNBTMap root) {
for (String name : root.children.keySet()) {
copied.put(name, root.getTag(name).copy());
}
} else if (parent != null) {
copied.put(node.saltatory$key(), parent.getTag(node.saltatory$key()).copy());
}
Minecraft.getInstance().keyboardHandler.setClipboard(copied.toString());
}
@Unique
private void saltatory$paste() {
if (!(selected instanceof ButtonNBTCollection target)) return;
CompoundTag copied;
try {
copied = TagParser.parseTag(Minecraft.getInstance().keyboardHandler.getClipboard());
} catch (CommandSyntaxException e) {
Minecraft.getInstance()
.player
.displayClientMessage(Component.literal("Invalid NBT clipboard"), true);
return;
}
byte elementType = 0;
for (String name : copied.getAllKeys()) {
Tag tag = copied.get(name);
if (!target.canCreateNew(tag.getId())
|| (!(target instanceof ButtonNBTMap)
&& elementType != 0
&& elementType != tag.getId())) {
Minecraft.getInstance()
.player
.displayClientMessage(
Component.literal("NBT type does not fit selection"), true);
return;
}
elementType = tag.getId();
}
for (String name : copied.getAllKeys()) {
target.setTag(target instanceof ButtonNBTMap ? name : "-1", copied.get(name).copy());
}
target.updateChildren(false);
target.getGui().refreshWidgets();
}
}

View file

@ -0,0 +1,16 @@
package dev.quorate.client.nbt.mixin;
import dev.ftb.mods.ftblibrary.nbtedit.NBTEditorScreen.ButtonNBT;
import dev.ftb.mods.ftblibrary.nbtedit.NBTEditorScreen.ButtonNBTCollection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(value = ButtonNBT.class, remap = false)
public interface NodeAccess {
@Accessor("parent")
ButtonNBTCollection saltatory$parent();
@Accessor("key")
String saltatory$key();
}

View file

@ -14,6 +14,9 @@ config="client.mixins.json"
[[mixins]]
config="client.jemi.mixins.json"
[[mixins]]
config="client.nbt.mixins.json"
[[dependencies.saltatory_client]]
modId="neoforge"
type="required"

View file

@ -0,0 +1,3 @@
{
"key.saltatory_client.nbt": "Edit targeted NBT"
}

View file

@ -0,0 +1,9 @@
{
"required": true,
"minVersion": "0.8.7",
"package": "dev.quorate.client.nbt.mixin",
"compatibilityLevel": "JAVA_21",
"plugin": "dev.quorate.client.nbt.NbtPlugin",
"client": ["NodeAccess", "EditorMixin"],
"injectors": {"defaultRequire": 1}
}

View file

@ -0,0 +1,18 @@
package dev.quorate.client.nbt;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import dev.quorate.client.mixin.ModRanges;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.junit.jupiter.api.Test;
final class NbtPluginTest {
@Test
void targetsOnlyInspectedFtbVersion() {
assertTrue(ModRanges.contains(new DefaultArtifactVersion("2101.1.37"), NbtPlugin.FTB));
assertFalse(ModRanges.contains(new DefaultArtifactVersion("2101.1.36"), NbtPlugin.FTB));
assertFalse(ModRanges.contains(new DefaultArtifactVersion("2101.1.38"), NbtPlugin.FTB));
}
}