From 725392ab2603bc5cf629155af0b7f052ccc883e9 Mon Sep 17 00:00:00 2001 From: Algent Date: Tue, 3 Nov 2026 14:39:59 +0100 Subject: [PATCH 1/2] Improve brigadier command error reporting (#493) --- .../gtnhlib/brigadier/BrigadierApi.java | 79 +++++++++++++++++-- .../mixins/early/MixinCommandHandler.java | 12 ++- .../resources/assets/gtnhlib/lang/en_US.lang | 4 + 3 files changed, 86 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/brigadier/BrigadierApi.java b/src/main/java/com/gtnewhorizon/gtnhlib/brigadier/BrigadierApi.java index 642d7ec2..8761b282 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/brigadier/BrigadierApi.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/brigadier/BrigadierApi.java @@ -8,21 +8,26 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import net.minecraft.command.CommandException; +import net.minecraft.command.CommandNotFoundException; import net.minecraft.command.ICommand; import net.minecraft.command.ICommandSender; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.ChatComponentText; +import net.minecraft.util.ChatComponentTranslation; +import net.minecraft.util.EnumChatFormatting; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import com.gtnewhorizon.gtnhlib.util.CommandUtils; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.ParseResults; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.ParsedCommandNode; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestion; import com.mojang.brigadier.tree.CommandNode; @@ -32,6 +37,8 @@ import lombok.extern.log4j.Log4j2; @Log4j2 public class BrigadierApi { + private static final int MAX_REPORTED_USAGES = 5; + @Nullable private static CommandDispatcher dispatcher = new CommandDispatcher<>(); @@ -75,23 +82,83 @@ public class BrigadierApi { /** * Execute the command by brigadier. - * + * * @param sender the executor * @param command the command to execute * @return the result */ public static int executeCommand(ICommandSender sender, String command) { + CommandDispatcher dispatcher = getCommandDispatcher(); + ParseResults parse = dispatcher.parse(command, sender); try { - return getCommandDispatcher().execute(command, sender); + return dispatcher.execute(parse); } catch (CommandSyntaxException e) { - // TODO: make the exception message better - throw new CommandException(e.getMessage()); + if (parse.getContext().getNodes().isEmpty()) { + // nothing matched at all, let vanilla report it like any other unknown command + throw new CommandNotFoundException(); + } + reportSyntaxError(sender, parse, e); + return 0; + } + } + + /** + * Report a parse failure as what went wrong, where it went wrong and how the matched command is meant to be used. + * Brigadier's own message is not a translation key, so it cannot be handed to a {@link CommandException}. + * + * @param sender the executor + * @param parse the parse results the failure came from + * @param e the failure + */ + private static void reportSyntaxError(ICommandSender sender, ParseResults parse, + CommandSyntaxException e) { + if (e.getType() == CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand()) { + // brigadier reports a command that parsed but has nothing to run as unknown + CommandUtils.error(sender, "gtnhlib.chat.command.incomplete"); + } else { + sender.addChatMessage( + CommandUtils.colorChatComponent(EnumChatFormatting.RED, e.getRawMessage().getString())); + } + + String input = e.getInput(); + int cursor = e.getCursor(); + if (input != null && cursor >= 0 && cursor < input.length()) { + ChatComponentText accepted = CommandUtils + .colorChatComponent(EnumChatFormatting.GRAY, "/" + input.substring(0, cursor)); + ChatComponentText rejected = CommandUtils + .colorChatComponent(EnumChatFormatting.RED, input.substring(cursor)); + rejected.getChatStyle().setUnderlined(true); + sender.addChatMessage(accepted.appendSibling(rejected)); + } + + List> nodes = parse.getContext().getNodes(); + if (nodes.isEmpty()) return; + + StringBuilder path = new StringBuilder("/"); + for (ParsedCommandNode node : nodes) { + path.append(node.getNode().getUsageText()).append(' '); + } + + String[] usages = getCommandDispatcher().getAllUsage(nodes.get(nodes.size() - 1).getNode(), sender, true); + for (int i = 0; i < usages.length && i < MAX_REPORTED_USAGES; i++) { + ChatComponentTranslation usage = new ChatComponentTranslation( + "commands.generic.usage", + (path + usages[i]).trim()); + usage.getChatStyle().setColor(EnumChatFormatting.GRAY); + sender.addChatMessage(usage); + } + if (usages.length > MAX_REPORTED_USAGES) { + ChatComponentTranslation more = new ChatComponentTranslation( + "gtnhlib.chat.command.more_usages", + usages.length - MAX_REPORTED_USAGES); + more.getChatStyle().setColor(EnumChatFormatting.GRAY); + sender.addChatMessage(more); } } /** * Return the suggestions for the given sender with the given typed command. - * + * * @param sender the given sender * @param command the given typed command * @return the suggestions in text @@ -109,7 +176,7 @@ public class BrigadierApi { /** * Return the wrapped commands that the given sender has the permission to execute. - * + * * @param sender the given sender * @return the wrapped commands */ diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/mixins/early/MixinCommandHandler.java b/src/main/java/com/gtnewhorizon/gtnhlib/mixins/early/MixinCommandHandler.java index cade5bd2..d6a3788b 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/mixins/early/MixinCommandHandler.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/mixins/early/MixinCommandHandler.java @@ -31,10 +31,16 @@ public class MixinCommandHandler { cancellable = true) private void gtnhlib$fallbackToBrigadier(ICommandSender sender, String command, CallbackInfoReturnable cir) { - if (gtnhlib$isServerCommandManager()) { - int value = BrigadierApi.executeCommand(sender, command); - cir.setReturnValue(value); + if (!gtnhlib$isServerCommandManager()) return; + + // an empty console line or a lone slash is not a command, so do not report it as one + if (command.trim().isEmpty()) { + cir.setReturnValue(0); + return; } + + int value = BrigadierApi.executeCommand(sender, command); + cir.setReturnValue(value); } @Inject( diff --git a/src/main/resources/assets/gtnhlib/lang/en_US.lang b/src/main/resources/assets/gtnhlib/lang/en_US.lang index 4aa57c23..428e102a 100644 --- a/src/main/resources/assets/gtnhlib/lang/en_US.lang +++ b/src/main/resources/assets/gtnhlib/lang/en_US.lang @@ -295,3 +295,7 @@ commands.title.show.title.single=Showed title to %s commands.title.show.subtitle.single=Showed subtitle to %s commands.title.times.single=Changed title display times for %s commands.title.icon.single=Changed title icon for %s + +# Brigadier command errors +gtnhlib.chat.command.incomplete=Incomplete command +gtnhlib.chat.command.more_usages=... and %s more From 70a7fd81ab1cd9aac62feabd2555d5df2ab71796 Mon Sep 17 00:00:00 2001 From: ah-OOG-ah <75745146+ah-OOG-ah@users.noreply.github.com> Date: Tue, 3 Nov 2026 14:46:32 -0500 Subject: [PATCH 2/2] Use Angelica/Celeritas AO when Angelica is present (#440) Co-authored-by: Armisael <193531144+Armisael5@users.noreply.github.com> --- .../com/gtnewhorizon/gtnhlib/CommonProxy.java | 2 + .../gtnhlib/client/model/AngelicaHelper.java | 30 ++++++++++++ .../gtnhlib/client/model/ModelISBRH.java | 20 +++++++- .../cel/model/quad/ModelQuadView.java | 9 ++-- .../test/block/BlockLightUnderStair.java | 45 +++++++++++++++++ .../blockstates/light_under_stair.json | 8 +++ .../resources/assets/gtnhlib/lang/en_US.lang | 1 + .../models/blocks/light_test_stairs.json | 8 +++ .../models/blocks/light_under_stair.json | 49 +++++++++++++++++++ 9 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/gtnewhorizon/gtnhlib/client/model/AngelicaHelper.java create mode 100644 src/main/java/com/gtnewhorizon/gtnhlib/test/block/BlockLightUnderStair.java create mode 100644 src/main/resources/assets/gtnhlib/blockstates/light_under_stair.json create mode 100644 src/main/resources/assets/gtnhlib/models/blocks/light_test_stairs.json create mode 100644 src/main/resources/assets/gtnhlib/models/blocks/light_under_stair.json diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java b/src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java index 1bec581e..27176095 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/CommonProxy.java @@ -34,6 +34,7 @@ import com.gtnewhorizon.gtnhlib.network.PacketMessageAboveHotbar; import com.gtnewhorizon.gtnhlib.network.PacketViewDistance; import com.gtnewhorizon.gtnhlib.teams.TeamAdminCommand; import com.gtnewhorizon.gtnhlib.teams.TeamCommand; +import com.gtnewhorizon.gtnhlib.test.block.BlockLightUnderStair; import com.gtnewhorizon.gtnhlib.test.block.BlockRngTest; import com.gtnewhorizon.gtnhlib.test.block.BlockTest; import com.gtnewhorizon.gtnhlib.test.block.BlockTestAo; @@ -74,6 +75,7 @@ public class CommonProxy { GTNHLib.info("GTNHLib version " + Tags.VERSION + " loaded."); if (GTNHLibConfig.enableTestBlocks) { + BlockLightUnderStair.register(); BlockTest.register(); BlockTestAo.register(); BlockTestLectern.register(); diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/client/model/AngelicaHelper.java b/src/main/java/com/gtnewhorizon/gtnhlib/client/model/AngelicaHelper.java new file mode 100644 index 00000000..3578e62a --- /dev/null +++ b/src/main/java/com/gtnewhorizon/gtnhlib/client/model/AngelicaHelper.java @@ -0,0 +1,30 @@ +package com.gtnewhorizon.gtnhlib.client.model; + +import net.minecraft.client.renderer.Tessellator; + +import com.gtnewhorizons.angelica.rendering.StateAwareTessellator; + +class AngelicaHelper { + + /// This method doesn't need to mess with [com.gtnewhorizons.angelica.api.ExtCeleritasRenderBlocks] because that + /// only affects [net.minecraft.client.renderer.RenderBlocks] methods, which we don't use. + static void initAngelicaLighting(Tessellator tesselator) { + if (!(tesselator instanceof StateAwareTessellator saTess)) return; + + saTess.angelica$setAppliedAo(true); + } + + static void quadAngelicaLighting(Tessellator tesselator, boolean ao, boolean noDirectionalShading) { + if (!(tesselator instanceof StateAwareTessellator saTess)) return; + + saTess.angelica$setAppliedAo(ao); + saTess.angelica$setNoDirectionalShading(noDirectionalShading); + } + + static void resetAngelicaLighting(Tessellator tesselator) { + if (!(tesselator instanceof StateAwareTessellator saTess)) return; + + saTess.angelica$setAppliedAo(false); + saTess.angelica$setNoDirectionalShading(false); + } +} diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/client/model/ModelISBRH.java b/src/main/java/com/gtnewhorizon/gtnhlib/client/model/ModelISBRH.java index 2f6854bf..f5097928 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/client/model/ModelISBRH.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/client/model/ModelISBRH.java @@ -5,6 +5,7 @@ import static com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8.unpac import static com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8.unpackZ; import static com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.properties.ModelQuadFacing.DIRECTIONS; import static com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.properties.ModelQuadFacing.VALUES; +import static com.gtnewhorizon.gtnhlib.compat.Mods.ANGELICA; import static java.lang.Float.intBitsToFloat; import static java.lang.Math.max; import static net.minecraftforge.client.IItemRenderer.ItemRenderType.ENTITY; @@ -86,6 +87,8 @@ public class ModelISBRH implements ISimpleBlockRenderingHandler, IItemRenderer { worldContext.set(wrappedContext, x, y, z, RAND); final int meta = wrappedContext.getBlockMetadata(x, y, z); + if (ANGELICA) AngelicaHelper.initAngelicaLighting(tesselator); + final BakedModel model = ModelRegistry.getBakedModel(worldContext); int color = model.getColor(world, x, y, z, block, meta, RAND); @@ -125,11 +128,26 @@ public class ModelISBRH implements ISimpleBlockRenderingHandler, IItemRenderer { final int lm = getLightMap(block, quad, quad.getLightFace(), world, x, y, z, renderer); tesselator.setBrightness(lm); - final float shade = quad.hasDirectionalShading() ? diffuseLight(quad.getComputedFaceNormal()) : 1; + if (ANGELICA) { + AngelicaHelper.quadAngelicaLighting( + tesselator, + quad.hasAmbientOcclusion(), + !quad.hasDirectionalShading()); + } + + final float shade; + if (ANGELICA && quad.hasAmbientOcclusion()) { + shade = 1; + } else { + shade = quad.hasDirectionalShading() ? diffuseLight(quad.getComputedFaceNormal()) : 1; + } + tesselator.setColorOpaque_F(r * shade, g * shade, b * shade); renderQuad(quad, x, y, z, tesselator, renderer.overrideBlockTexture); } } + + if (ANGELICA) AngelicaHelper.resetAngelicaLighting(tesselator); worldContext.reset(); return rendered; } diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/client/renderer/cel/model/quad/ModelQuadView.java b/src/main/java/com/gtnewhorizon/gtnhlib/client/renderer/cel/model/quad/ModelQuadView.java index 748dd708..6e688c57 100644 --- a/src/main/java/com/gtnewhorizon/gtnhlib/client/renderer/cel/model/quad/ModelQuadView.java +++ b/src/main/java/com/gtnewhorizon/gtnhlib/client/renderer/cel/model/quad/ModelQuadView.java @@ -67,11 +67,10 @@ public interface ModelQuadView extends ModelPrimitiveView { */ boolean isTransparent(); - /** - * Not to be confused with ambient occlusion AKA smooth lighting When true, allows this quad to multiply its - * brightness based on the direction it's facing. When this is false, each face derives its brightness directly from - * the light value it's facing without multiplying it. - */ + /// Not to be confused with ambient occlusion AKA smooth lighting. When true, allows this quad to multiply its + /// brightness based on the direction it's facing. When this is false, each face derives its brightness directly + /// from + /// the light value it's facing without multiplying it. boolean hasDirectionalShading(); /** diff --git a/src/main/java/com/gtnewhorizon/gtnhlib/test/block/BlockLightUnderStair.java b/src/main/java/com/gtnewhorizon/gtnhlib/test/block/BlockLightUnderStair.java new file mode 100644 index 00000000..766a5ebe --- /dev/null +++ b/src/main/java/com/gtnewhorizon/gtnhlib/test/block/BlockLightUnderStair.java @@ -0,0 +1,45 @@ +package com.gtnewhorizon.gtnhlib.test.block; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import net.minecraftforge.common.util.ForgeDirection; + +import com.gtnewhorizon.gtnhlib.blockstate.properties.DirectionBlockProperty; +import com.gtnewhorizon.gtnhlib.blockstate.properties.DirectionBlockProperty.AbstractDirectionBlockProperty; +import com.gtnewhorizon.gtnhlib.blockstate.registry.BlockPropertyRegistry; +import com.gtnewhorizon.gtnhlib.util.DirectionUtil; + +import cpw.mods.fml.common.registry.GameRegistry; + +public class BlockLightUnderStair extends Block { + + public final AbstractDirectionBlockProperty FACING_PROP = (AbstractDirectionBlockProperty) DirectionBlockProperty + .facing(); + + public BlockLightUnderStair() { + super(Material.wood); + } + + public static void register() { + final var testBlock = new BlockLightUnderStair(); + testBlock.setBlockName("light_under_stair"); + GameRegistry.registerBlock(testBlock, "light_under_stair"); + + BlockPropertyRegistry.registerBlockItemProperty(testBlock, testBlock.FACING_PROP, ForgeDirection.EAST); + } + + @Override + public boolean isOpaqueCube() { + return false; + } + + @Override + public void onBlockPlacedBy(World worldIn, int x, int y, int z, EntityLivingBase placer, ItemStack itemIn) { + var dir = DirectionUtil.yawToDirection(placer.rotationYaw); + + worldIn.setBlockMetadataWithNotify(x, y, z, FACING_PROP.getMeta(dir, 0), 2); + } +} diff --git a/src/main/resources/assets/gtnhlib/blockstates/light_under_stair.json b/src/main/resources/assets/gtnhlib/blockstates/light_under_stair.json new file mode 100644 index 00000000..4ab2bdce --- /dev/null +++ b/src/main/resources/assets/gtnhlib/blockstates/light_under_stair.json @@ -0,0 +1,8 @@ +{ + "variants": { + "facing=north": { "model": "gtnhlib:blocks/light_test_stairs", "y": 270, "uvlock": true }, + "facing=south": { "model": "gtnhlib:blocks/light_test_stairs", "y": 90, "uvlock": true }, + "facing=west": { "model": "gtnhlib:blocks/light_test_stairs", "y": 0, "uvlock": true }, + "facing=east": { "model": "gtnhlib:blocks/light_test_stairs", "y": 180, "uvlock": true } + } +} diff --git a/src/main/resources/assets/gtnhlib/lang/en_US.lang b/src/main/resources/assets/gtnhlib/lang/en_US.lang index 428e102a..33b032f1 100644 --- a/src/main/resources/assets/gtnhlib/lang/en_US.lang +++ b/src/main/resources/assets/gtnhlib/lang/en_US.lang @@ -1,4 +1,5 @@ # Test block names +tile.light_under_stair.name=Directional Shading Test Block tile.model_test.name=Test Block tile.model_test_tint.name=Test Block (Tint) tile.model_test_tint_mul.name=Test Block (Tint Mul) diff --git a/src/main/resources/assets/gtnhlib/models/blocks/light_test_stairs.json b/src/main/resources/assets/gtnhlib/models/blocks/light_test_stairs.json new file mode 100644 index 00000000..b37d0565 --- /dev/null +++ b/src/main/resources/assets/gtnhlib/models/blocks/light_test_stairs.json @@ -0,0 +1,8 @@ +{ + "parent": "gtnhlib:blocks/light_under_stair", + "textures": { + "bottom": "minecraft:planks_oak", + "side": "minecraft:planks_oak", + "top": "minecraft:planks_oak" + } +} diff --git a/src/main/resources/assets/gtnhlib/models/blocks/light_under_stair.json b/src/main/resources/assets/gtnhlib/models/blocks/light_under_stair.json new file mode 100644 index 00000000..5c667016 --- /dev/null +++ b/src/main/resources/assets/gtnhlib/models/blocks/light_under_stair.json @@ -0,0 +1,49 @@ +{ + "parent": "block/block", + "display": { + "gui": { + "rotation": [ 30, 135, 0 ], + "translation": [ 0, 0, 0], + "scale":[ 0.625, 0.625, 0.625 ] + }, + "head": { + "rotation": [ 0, -90, 0 ], + "translation": [ 0, 0, 0 ], + "scale": [ 1, 1, 1 ] + }, + "thirdperson_lefthand": { + "rotation": [ 75, -135, 0 ], + "translation": [ 0, 2.5, 0], + "scale": [ 0.375, 0.375, 0.375 ] + } + }, + "textures": { + "particle": "#side" + }, + "elements": [ + { + "from": [ 0, 0, 0 ], + "to": [ 16, 8, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#bottom", "cullface": "down" }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#top" }, + "north": { "uv": [ 0, 8, 16, 16 ], "texture": "#side", "cullface": "north" }, + "south": { "uv": [ 0, 8, 16, 16 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 8, 16, 16 ], "texture": "#side", "cullface": "west" }, + "east": { "uv": [ 0, 8, 16, 16 ], "texture": "#side", "cullface": "east" } + }, + "shade": false + }, + { + "from": [ 8, 8, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "up": { "uv": [ 8, 0, 16, 16 ], "texture": "#top", "cullface": "up" }, + "north": { "uv": [ 0, 0, 8, 8 ], "texture": "#side", "cullface": "north" }, + "south": { "uv": [ 8, 0, 16, 8 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 8 ], "texture": "#side" }, + "east": { "uv": [ 0, 0, 16, 8 ], "texture": "#side", "cullface": "east" } + } + } + ] +}