Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1c43281
Fix execClientCommand incorrectly stripping all forward slashes from …
FalAut May 30, 2026
63582ee
build: MPP -> 2.0.0-beta.2, CF client/server added
desht Jun 2, 2026
70b0c17
build: Neo -> 26.1.2.72 stable
desht Jun 2, 2026
3df8a11
chore: store & check list of current nbt editors
desht Jun 15, 2026
ec82acc
chore: added start of some gametest framework
desht Jul 7, 2026
72e179a
chore: added small Vec2d class (similar to vanilla Vec2)
desht Jul 7, 2026
1232473
build: version -> 26.1.2.7
desht Jul 7, 2026
1ce6ab9
chore: changelog updated
desht Jul 7, 2026
a5f263b
feat: ImageComponent now has click_action field
desht Jul 9, 2026
e473fa3
feat: added DocsMod abstraction layer
desht Jul 10, 2026
3c79e84
fix: pose leak in SidebarGroupGuiButton
desht Jul 13, 2026
f598672
feat: added 3 icons
desht Jun 30, 2026
91fd9fd
chore: make string config overlay panel a bit wider by default
desht Jul 17, 2026
55c300f
feat: more dynamic "can edit" testing for config editor
desht Jul 20, 2026
4218463
chore: changelog updated
desht Jul 20, 2026
71daf81
chore: changelog updated
desht Jul 29, 2026
56596d1
chore: updated panel scrolling to support both axes
desht Jul 29, 2026
b1539cd
chore: changelog updated
desht Jul 29, 2026
dc9c6f4
chore: added safer atomic file write for Json5Util#save
desht Jul 29, 2026
8944f41
fix: ensure multiline textbox doesn't scroll out of bounds
desht Jul 29, 2026
e38a1cd
build: updated MPP
desht Jul 29, 2026
21a133c
chore: added ItemIcon#ofTemplate
desht Jul 30, 2026
d06afab
fix: use addGuiElement() for non-tiled images in ImageIconRenderer
desht Jul 30, 2026
526e746
fix: tab icons were just plain wrong
desht Jul 31, 2026
af168e9
fix: TextField#getClickableStyleAt use custom ActiveTextCollector
desht Aug 2, 2026
8f5de5d
fix: enforce UTF8 encoding for Json5 reading/writing
desht Aug 3, 2026
a986f19
chore: rework EntityImageIcon a little
desht Aug 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [26.1.2.7]

### Added
* Added `Vec2d` utility class
* Added `EditableConfigValue#setCanEdit` variant taking a `BooleanSupplier`
* Allows for more dynamic control of can-edit visual appearance for entries in config editor screen
* Added "undo" and "redo" icons
* Added `DocsMod` abstraction layer
* `ImageComponent` now has a "click_action" field

### Changed
* Default width of string editor overlays is a bit wider now
* Update panel scrolling to handle both X and Y axes, since this is well-supported by vanilla
* `Panel#scrollPanel` signature has changed
* Soft-breaking change; mods need to update to new API. They won't crash if not but panel scrolling may not function well

### Fixed
* Fixed execClientCommand() incorrectly stripping all forward slashes from commands (thanks @FalAut)
* Fixed pose stack leak which could happen under some circumstances in sidebar rendering

## [26.1.2.6-beta]

### Fixed
Expand Down
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
id 'java-library'
id 'net.fabricmc.fabric-loom' version '1.15.2' apply(false)
id 'net.neoforged.moddev' version '2.0.140' apply(false)
id "me.modmuss50.mod-publish-plugin" version "1.1.0"
id "me.modmuss50.mod-publish-plugin" version "2.1.1"
}

tasks.named('wrapper', Wrapper).configure {
Expand Down Expand Up @@ -124,6 +124,8 @@ publishMods {
minecraftVersions.add(it.trim())
}
javaVersions.add(JavaVersion.VERSION_25)
client = true
server = true
}

curseforge("curseforgeFabric") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import dev.ftb.mods.ftblibrary.config.FTBLibraryServerConfig;
import dev.ftb.mods.ftblibrary.nbtedit.NBTEditResponseHandlers;
import dev.ftb.mods.ftblibrary.net.EditConfigPacket;
import dev.ftb.mods.ftblibrary.net.EditNBTPacket;
import dev.ftb.mods.ftblibrary.net.OpenTestScreenPacket;
import dev.ftb.mods.ftblibrary.platform.Mod;
import dev.ftb.mods.ftblibrary.platform.Platform;
Expand All @@ -32,6 +31,7 @@
import net.minecraft.nbt.StringTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentSerialization;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.permissions.Permissions;
import net.minecraft.util.ProblemReporter;
Expand All @@ -43,15 +43,9 @@
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.storage.TagValueOutput;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import static net.minecraft.commands.Commands.literal;

public class FTBLibraryCommands {
public static final Map<UUID, CompoundTag> EDITING_NBT = new HashMap<>();

public static void registerCommands(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext ignoredCtx, Commands.CommandSelection ignoredType) {
var command = literal(FTBLibrary.MOD_ID)
.then(literal("gamemode")
Expand Down Expand Up @@ -128,39 +122,32 @@ private static int editNBT(CommandContext<CommandSourceStack> context, NBTEditCa
var tag = new CompoundTag();
data.accept(info, tag);

if (!info.isEmpty()) {
EDITING_NBT.put(player.getUUID(), info);
Server2PlayNetworking.send(player, new EditNBTPacket(info, tag));
return Command.SINGLE_SUCCESS;
}

return 0;
return NBTEditResponseHandlers.INSTANCE.sendRequestPacket(player, info, tag);
}

private static void editItemNBT(CommandContext<CommandSourceStack> context, CompoundTag info, CompoundTag tag) throws CommandSyntaxException {
var player = context.getSource().getPlayerOrException();

ItemStack stack = player.getMainHandItem();
if (stack.isEmpty()) {
return;
if (!stack.isEmpty()) {
info.store("type", Identifier.CODEC, NBTEditResponseHandlers.ITEM);
ItemStack.CODEC.encodeStart(player.level().registryAccess().createSerializationContext(NbtOps.INSTANCE), stack)
.ifSuccess(res -> {
if (res instanceof CompoundTag t) tag.merge(t);
});
var key = RegistryHelper.getIdentifier(stack.getItem(), Registries.ITEM);
info.put("text", InfoBuilder.create(context)
.add("Class", Component.literal(stack.getItem().getClass().getName()))
.add("ID", Component.literal(key == null ? "null" : key.toString()))
.add("Mod", Component.literal(key == null ? "null" : Platform.get().getMod(key.getNamespace()).map(Mod::name).orElse("Unknown")))
.build());
}
info.putString("type", "item");
ItemStack.CODEC.encodeStart(player.level().registryAccess().createSerializationContext(NbtOps.INSTANCE), stack)
.ifSuccess(res -> {
if (res instanceof CompoundTag t) tag.merge(t);
});
var key = RegistryHelper.getIdentifier(stack.getItem(), Registries.ITEM);
info.put("text", InfoBuilder.create(context)
.add("Class", Component.literal(stack.getItem().getClass().getName()))
.add("ID", Component.literal(key == null ? "null" : key.toString()))
.add("Mod", Component.literal(key == null ? "null" : Platform.get().getMod(key.getNamespace()).map(Mod::name).orElse("Unknown")))
.build());
}

private static void editPlayerNBT(CommandContext<CommandSourceStack> context, CompoundTag info, CompoundTag tag) throws CommandSyntaxException {
var player = EntityArgument.getPlayer(context, "player");

info.putString("type", NBTEditResponseHandlers.PLAYER.toString());
info.store("type", Identifier.CODEC, NBTEditResponseHandlers.PLAYER);
info.store("id", UUIDUtil.CODEC, player.getUUID());

TagValueOutput output = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, player.level().registryAccess());
Expand All @@ -183,7 +170,7 @@ private static void editEntityNBT(CommandContext<CommandSourceStack> context, Co
return;
}

info.putString("type", NBTEditResponseHandlers.ENTITY.toString());
info.store("type", Identifier.CODEC, NBTEditResponseHandlers.ENTITY);
info.putInt("id", entity.getId());

TagValueOutput output = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, entity.registryAccess());
Expand All @@ -197,8 +184,7 @@ private static void editEntityNBT(CommandContext<CommandSourceStack> context, Co
.add("Mod", Component.literal(key == null ? "null" : Platform.get().getMod(key.getNamespace()).map(Mod::name).orElse("Unknown")))
.build());

String name = entity.getDisplayName() == null ? "?" : entity.getDisplayName().getString();
info.putString("title", name);
info.putString("title", entity.getDisplayName().getString());
}

private static void editBlockNBT(CommandContext<CommandSourceStack> context, CompoundTag info, CompoundTag tag) throws CommandSyntaxException {
Expand All @@ -211,7 +197,7 @@ private static void editBlockNBT(CommandContext<CommandSourceStack> context, Com
return;
}

info.putString("type", NBTEditResponseHandlers.BLOCK.toString());
info.store("type", Identifier.CODEC, NBTEditResponseHandlers.BLOCK);
BlockPos.CODEC.encodeStart(NbtOps.INSTANCE, pos).ifSuccess(nbt -> info.put("pos", nbt));
tag.merge(blockEntity.saveWithFullMetadata(context.getSource().getLevel().registryAccess()));
tag.remove("x");
Expand All @@ -228,7 +214,7 @@ private static void editBlockNBT(CommandContext<CommandSourceStack> context, Com
.add("Block Class", Component.literal(blockEntity.getBlockState().getBlock().getClass().getName()))
.add("Position", Component.literal("[" + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + "]"))
.add("Mod", Component.literal(key == null ? "null" : Platform.get().getMod(key.getNamespace()).map(Mod::name).orElse("Unknown")))
.add("Ticking", Component.literal(isTicking(blockEntity) ? "true" : "false"))
.add("Ticking", Component.literal(Boolean.toString(isTicking(blockEntity))))
.build());

var title = blockEntity instanceof Nameable n ? n.getDisplayName() : null;
Expand All @@ -239,7 +225,9 @@ private static void editBlockNBT(CommandContext<CommandSourceStack> context, Com
}

private static boolean isTicking(BlockEntity be) {
return be.getBlockState().getBlock() instanceof EntityBlock eb && eb.getTicker(be.getLevel(), be.getBlockState(), be.getType()) != null;
return be.getLevel() != null
&& be.getBlockState().getBlock() instanceof EntityBlock eb
&& eb.getTicker(be.getLevel(), be.getBlockState(), be.getType()) != null;
}

private interface NBTEditCallback {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.jspecify.annotations.Nullable;

import java.util.Objects;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;

/**
Expand All @@ -40,7 +41,7 @@ public abstract class EditableConfigValue<T> implements Comparable<EditableConfi
private int order = 0;
private String nameKey = "";
private Icon<?> icon = Icons.SETTINGS;
private boolean canEdit = true;
private BooleanSupplier canEdit = () -> true;

protected static Component info(String key) {
return Component.literal(key + ":").withStyle(ChatFormatting.AQUA);
Expand Down Expand Up @@ -225,10 +226,15 @@ public EditableConfigValue<T> setOrder(int o) {
}

public boolean getCanEdit() {
return canEdit;
return canEdit.getAsBoolean();
}

public EditableConfigValue<T> setCanEdit(boolean e) {
canEdit = () -> e;
return this;
}

public EditableConfigValue<T> setCanEdit(BooleanSupplier e) {
canEdit = e;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ public Component getStringForGUI(FluidStack value) {

@Override
public void onClicked(Widget clickedWidget, MouseButton button, ConfigCallback callback) {
if (getCanEdit()) {
new SelectFluidScreen(this, callback).openGui();
}
new SelectFluidScreen(this, callback).openGui();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ public Component getStringForGUI(@Nullable ItemStack value) {

@Override
public void onClicked(Widget clickedWidget, MouseButton button, ConfigCallback callback) {
if (getCanEdit()) {
new SelectItemStackScreen(this, callback).openGui();
}
new SelectItemStackScreen(this, callback).openGui();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ public abstract class EditableVariantConfig<T> extends EditableConfigValue<T> {

@Override
public void onClicked(Widget clickedWidget, MouseButton button, ConfigCallback callback) {
if (getCanEdit()) {
boolean changed = updateValue(getIteration(value, button.isLeft()));
callback.save(changed);
}
boolean changed = updateValue(getIteration(value, button.isLeft()));
callback.save(changed);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,17 +159,19 @@ public void addMouseOverText(TooltipList list) {

@Override
public void onClicked(MouseButton button) {
playClickSound();
CV listType = listConfig.getType();
listType.setValue(listType.copy(listType.getDefaultValue()));
listType.onClicked(this, button, accepted -> {
if (accepted) {
localValues.add(listType.getValue());
changed = true;
}
if (listConfig.getCanEdit()) {
playClickSound();
CV listType = listConfig.getType();
listType.setValue(listType.copy(listType.getDefaultValue()));
listType.onClicked(this, button, accepted -> {
if (accepted) {
localValues.add(listType.getValue());
changed = true;
}

openGui();
});
openGui();
});
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

import static dev.ftb.mods.ftblibrary.util.TextComponentUtils.hotkeyTooltip;

Expand Down Expand Up @@ -81,9 +82,9 @@ public EditConfigScreen(EditableConfigGroup configGroup, boolean readOnly) {
}

buttonExpandAll = new SimpleButton(topPanel, List.of(Component.translatable("gui.expand_all"), hotkeyTooltip("="), hotkeyTooltip("+")), Icons.EXPAND,
(widget, button) -> toggleAll(false));
(_, _) -> toggleAll(false));
buttonCollapseAll = new SimpleButton(topPanel, List.of(Component.translatable("gui.collapse_all"), hotkeyTooltip("-")), Icons.COLLAPSE,
(widget, button) -> toggleAll(true));
(_, _) -> toggleAll(true));
}

private void toggleAll(boolean collapsed) {
Expand Down Expand Up @@ -113,7 +114,7 @@ public boolean onInit() {

allConfigButtons.forEach(w -> {
if (w instanceof ConfigEntryButton<?> eb) {
widestKey = Math.max(widestKey, getTheme().getFont().width(eb.keyText));
widestKey = Math.max(widestKey, getTheme().getFont().width(eb.keyText.get()));
widestValue = Math.max(widestValue, getTheme().getFont().width(eb.getValueStr()));
} else if (w instanceof ConfigGroupButton gb) {
widestGroup.setValue(Math.max(widestGroup.intValue(), getTheme().getStringWidth(gb.title)));
Expand Down Expand Up @@ -269,25 +270,25 @@ public void onClicked(MouseButton button) {
private class ConfigEntryButton<T> extends Button implements EditStringConfigOverlay.PosProvider {
private final ConfigGroupButton groupButton;
private final EditableConfigValue<T> configValue;
private final Component keyText;
private final Supplier<Component> keyText;

public ConfigEntryButton(Panel panel, ConfigGroupButton groupButton, EditableConfigValue<T> configValue) {
super(panel);
setHeight(getTheme().getFontHeight() + 2);
this.groupButton = groupButton;
this.configValue = configValue;

keyText = this.configValue.getCanEdit() ?
keyText = () -> this.configValue.getCanEdit() ?
Component.literal(this.configValue.getName()) :
Component.literal(this.configValue.getName()).withStyle(ChatFormatting.GRAY);
Component.literal(this.configValue.getName()).withStyle(getTheme().hasDarkBackground() ? ChatFormatting.DARK_GRAY : ChatFormatting.GRAY);
}

@Override
public void draw(GuiGraphicsExtractor graphics, Theme theme, int x, int y, int w, int h) {
IconHelper.renderIcon(Icons.COLOR_BLANK.withColor(Color4I.GRAY), graphics, x, y + 1, 10, 10);
IconHelper.renderIcon(Icons.INFO, graphics, x + 1, y + 2, 8, 8);

theme.drawStringOnBackground(graphics, keyText, x + 13, y + 2);//Bits.setFlag(0, Theme.SHADOW, isMouseOver()));
theme.drawStringOnBackground(graphics, keyText.get(), x + 13, y + 2);

Component valueText = configValue.getStringForGUI(configValue.getValue());

Expand All @@ -297,7 +298,7 @@ public void draw(GuiGraphicsExtractor graphics, Theme theme, int x, int y, int w
}

var textCol = configValue.getColor(theme).mutable();
textCol.setAlpha(255);
textCol.setAlpha(configValue.getCanEdit() ? 255 : 128);

if (isMouseOver()) {
textCol.addBrightness(60);
Expand All @@ -311,7 +312,7 @@ public void draw(GuiGraphicsExtractor graphics, Theme theme, int x, int y, int w

@Override
public void onClicked(MouseButton button) {
if (!readOnly && getMouseY() >= 20) {
if (configValue.getCanEdit() && !readOnly && getMouseY() >= 20) {
playClickSound();
configValue.onClicked(this, button, accepted -> {
if (accepted) changed = true;
Expand All @@ -325,7 +326,7 @@ public void addMouseOverText(TooltipList list) {
if (getMouseY() > 18) {
int x = getMouseX() - getX();
if (x < 16) {
list.add(keyText.copy().withStyle(ChatFormatting.UNDERLINE));
list.add(keyText.get().copy().withStyle(ChatFormatting.UNDERLINE));
var tooltip = configValue.getTooltip();

if (!tooltip.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public EditStringConfigOverlay(Panel panel, EditableStringifiedConfig<T> config,
this.currentValue = config.copy(config.getValue());
this.title = title;

int stringWidth = getGui().getTheme().getStringWidth(config.getStringFromValue(currentValue));
width = Math.min(getWindow().getGuiScaledWidth() / 2, stringWidth + config.getExtraEditorWidth());
width = Math.clamp(currentValue == null ? 0 : getGui().getTheme().getStringWidth(config.getStringFromValue(currentValue)) + 86,
150, getWindow().getGuiScaledWidth() * 3 / 4);

titleField = new TextField(this).addFlags(Theme.SHADOW).setText(Objects.requireNonNullElse(title, Component.empty()));
titleField.setSize(0, 0);
Expand Down Expand Up @@ -165,12 +165,13 @@ public void onEnterPressed() {
}

@Override
public boolean mouseScrolled(double scroll) {
return config.scrollValue(currentValue, scroll > 0).map(v -> {
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
var directionlessDelta = scrollX != 0 ? scrollX : scrollY;
return config.scrollValue(currentValue, directionlessDelta > 0).map(v -> {
textBox.setText(config.getStringFromValue(v));
textBox.setSelectionPos(textBox.getCursorPos());
return true;
}).orElse(super.mouseScrolled(scroll));
}).orElse(super.mouseScrolled(mouseX, mouseY, scrollX, scrollY));
}
}
}
Loading
Loading