diff --git a/common/src/main/java/dev/architectury/event/events/client/ClientCommandRegistrationEvent.java b/common/src/main/java/dev/architectury/event/events/client/ClientCommandRegistrationEvent.java new file mode 100644 index 00000000..bf81d87c --- /dev/null +++ b/common/src/main/java/dev/architectury/event/events/client/ClientCommandRegistrationEvent.java @@ -0,0 +1,74 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021, 2022 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.event.events.client; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import dev.architectury.event.Event; +import dev.architectury.event.EventFactory; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +import java.util.function.Supplier; + +public interface ClientCommandRegistrationEvent { + /** + * @see ClientCommandRegistrationEvent#register(CommandDispatcher, CommandBuildContext) + */ + Event EVENT = EventFactory.createLoop(); + + /** + * This event is invoked after the client initializes. + * Equivalent to Forge's {@code RegisterClientCommandsEvent} and Fabric's {@code ClientCommandManager}. + * + * @param dispatcher The command dispatcher to register commands to. + * @param context The command build context. + */ + void register(CommandDispatcher dispatcher, CommandBuildContext context); + + static LiteralArgumentBuilder literal(String name) { + return LiteralArgumentBuilder.literal(name); + } + + static RequiredArgumentBuilder argument(String name, ArgumentType type) { + return RequiredArgumentBuilder.argument(name, type); + } + + interface ClientCommandSourceStack extends SharedSuggestionProvider { + void arch$sendSuccess(Supplier message, boolean broadcastToAdmins); + + void arch$sendFailure(Component message); + + LocalPlayer arch$getPlayer(); + + Vec3 arch$getPosition(); + + Vec2 arch$getRotation(); + + ClientLevel arch$getLevel(); + } +} diff --git a/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java b/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java index fd231de3..dc13bc51 100644 --- a/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java +++ b/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java @@ -19,14 +19,13 @@ package dev.architectury.event.fabric; -import dev.architectury.event.events.client.ClientGuiEvent; -import dev.architectury.event.events.client.ClientLifecycleEvent; -import dev.architectury.event.events.client.ClientTickEvent; -import dev.architectury.event.events.client.ClientTooltipEvent; +import com.mojang.brigadier.CommandDispatcher; +import dev.architectury.event.events.client.*; import dev.architectury.event.events.common.*; import dev.architectury.impl.fabric.ChatComponentImpl; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; @@ -58,6 +57,11 @@ public static void registerClient() { ItemTooltipCallback.EVENT.register((itemStack, tooltipFlag, list) -> ClientTooltipEvent.ITEM.invoker().append(itemStack, list, tooltipFlag)); HudRenderCallback.EVENT.register((graphics, tickDelta) -> ClientGuiEvent.RENDER_HUD.invoker().renderHud(graphics, tickDelta)); + + ClientCommandRegistrationCallback.EVENT.register((dispatcher, access) -> { + ClientCommandRegistrationEvent.EVENT.invoker().register((CommandDispatcher) + (CommandDispatcher) dispatcher, access); + }); } public static void registerCommon() { diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinFabricClientCommandSource.java b/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinFabricClientCommandSource.java new file mode 100644 index 00000000..a9e4b555 --- /dev/null +++ b/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinFabricClientCommandSource.java @@ -0,0 +1,64 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021, 2022 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.mixin.fabric.client; + +import dev.architectury.event.events.client.ClientCommandRegistrationEvent; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.chat.Component; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Mixin; + +import java.util.function.Supplier; + +@Mixin(FabricClientCommandSource.class) +public interface MixinFabricClientCommandSource extends ClientCommandRegistrationEvent.ClientCommandSourceStack { + @Override + default void arch$sendSuccess(Supplier message, boolean broadcastToAdmins) { + ((FabricClientCommandSource) this).sendFeedback(message.get()); + } + + @Override + default void arch$sendFailure(Component message) { + ((FabricClientCommandSource) this).sendError(message); + } + + @Override + default LocalPlayer arch$getPlayer() { + return ((FabricClientCommandSource) this).getPlayer(); + } + + @Override + default Vec3 arch$getPosition() { + return ((FabricClientCommandSource) this).getPosition(); + } + + @Override + default Vec2 arch$getRotation() { + return ((FabricClientCommandSource) this).getRotation(); + } + + @Override + default ClientLevel arch$getLevel() { + return ((FabricClientCommandSource) this).getWorld(); + } +} diff --git a/fabric/src/main/resources/architectury.mixins.json b/fabric/src/main/resources/architectury.mixins.json index f8688e50..ac8a1b37 100644 --- a/fabric/src/main/resources/architectury.mixins.json +++ b/fabric/src/main/resources/architectury.mixins.json @@ -12,6 +12,7 @@ "client.MixinClientPacketListener", "client.MixinDebugScreenOverlay", "client.MixinEffectInstance", + "client.MixinFabricClientCommandSource", "client.MixinGameRenderer", "client.MixinGuiGraphics", "client.MixinIntegratedServer", diff --git a/forge/src/main/java/dev/architectury/event/forge/EventHandlerImplClient.java b/forge/src/main/java/dev/architectury/event/forge/EventHandlerImplClient.java index 97edb06a..55265aa1 100644 --- a/forge/src/main/java/dev/architectury/event/forge/EventHandlerImplClient.java +++ b/forge/src/main/java/dev/architectury/event/forge/EventHandlerImplClient.java @@ -19,6 +19,7 @@ package dev.architectury.event.forge; +import com.mojang.brigadier.CommandDispatcher; import dev.architectury.event.CompoundEventResult; import dev.architectury.event.EventResult; import dev.architectury.event.events.client.ClientChatEvent; @@ -323,6 +324,12 @@ public static void eventInputEvent(InputEvent.Key event) { ClientRawInputEvent.KEY_PRESSED.invoker().keyPressed(Minecraft.getInstance(), event.getKey(), event.getScanCode(), event.getAction(), event.getModifiers()); } + @SubscribeEvent(priority = EventPriority.HIGH) + public static void event(RegisterClientCommandsEvent event) { + ClientCommandRegistrationEvent.EVENT.invoker().register((CommandDispatcher) + (CommandDispatcher) event.getDispatcher(), event.getBuildContext()); + } + @OnlyIn(Dist.CLIENT) public static class ModBasedEventHandler { @SubscribeEvent(priority = EventPriority.HIGH) diff --git a/forge/src/main/java/dev/architectury/mixin/forge/client/MixinCommandSourceStack.java b/forge/src/main/java/dev/architectury/mixin/forge/client/MixinCommandSourceStack.java new file mode 100644 index 00000000..364cf5b6 --- /dev/null +++ b/forge/src/main/java/dev/architectury/mixin/forge/client/MixinCommandSourceStack.java @@ -0,0 +1,69 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021, 2022 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.mixin.forge.client; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.architectury.event.events.client.ClientCommandRegistrationEvent; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Mixin; + +import java.util.function.Supplier; + +@Mixin(CommandSourceStack.class) +public abstract class MixinCommandSourceStack implements ClientCommandRegistrationEvent.ClientCommandSourceStack { + @Override + public void arch$sendSuccess(Supplier message, boolean broadcastToAdmins) { + ((CommandSourceStack) (Object) this).sendSuccess(message, broadcastToAdmins); + } + + @Override + public void arch$sendFailure(Component message) { + ((CommandSourceStack) (Object) this).sendFailure(message); + } + + @Override + public LocalPlayer arch$getPlayer() { + try { + return (LocalPlayer) ((CommandSourceStack) (Object) this).getEntityOrException(); + } catch (CommandSyntaxException e) { + throw new RuntimeException(e); + } + } + + @Override + public Vec3 arch$getPosition() { + return ((CommandSourceStack) (Object) this).getPosition(); + } + + @Override + public Vec2 arch$getRotation() { + return ((CommandSourceStack) (Object) this).getRotation(); + } + + @Override + public ClientLevel arch$getLevel() { + return (ClientLevel) ((CommandSourceStack) (Object) this).getUnsidedLevel(); + } +} diff --git a/forge/src/main/resources/architectury.mixins.json b/forge/src/main/resources/architectury.mixins.json index 4242d539..fb773cf5 100644 --- a/forge/src/main/resources/architectury.mixins.json +++ b/forge/src/main/resources/architectury.mixins.json @@ -5,6 +5,7 @@ "compatibilityLevel": "JAVA_16", "minVersion": "0.8", "client": [ + "client.MixinCommandSourceStack", "MixinClientLevel", "MixinMinecraft" ], diff --git a/gradle.properties b/gradle.properties index 7cf2655a..68e908af 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ artifact_type=release archives_base_name=architectury archives_base_name_snapshot=architectury-snapshot -base_version=9.1 +base_version=9.2 maven_group=dev.architectury fabric_loader_version=0.14.21 diff --git a/testmod-common/src/main/java/dev/architectury/test/TestMod.java b/testmod-common/src/main/java/dev/architectury/test/TestMod.java index 5829979b..9fac583c 100644 --- a/testmod-common/src/main/java/dev/architectury/test/TestMod.java +++ b/testmod-common/src/main/java/dev/architectury/test/TestMod.java @@ -19,13 +19,14 @@ package dev.architectury.test; +import com.mojang.brigadier.arguments.StringArgumentType; +import dev.architectury.event.events.client.ClientCommandRegistrationEvent; import dev.architectury.event.events.client.ClientLifecycleEvent; import dev.architectury.registry.client.gui.ClientTooltipComponentRegistry; import dev.architectury.registry.client.level.entity.EntityRendererRegistry; import dev.architectury.test.debug.ConsoleMessageSink; import dev.architectury.test.debug.MessageSink; import dev.architectury.test.debug.client.ClientOverlayMessageSink; -import dev.architectury.test.entity.TestEntity; import dev.architectury.test.events.DebugEvents; import dev.architectury.test.gamerule.TestGameRules; import dev.architectury.test.item.TestBlockInteractions; @@ -43,8 +44,6 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.renderer.entity.CowRenderer; -import net.minecraft.client.renderer.entity.PigRenderer; -import net.minecraft.world.entity.animal.Cow; public class TestMod { public static final MessageSink SINK = EnvExecutor.getEnvSpecific(() -> ClientOverlayMessageSink::new, () -> ConsoleMessageSink::new); @@ -75,6 +74,15 @@ public static void initializeClient() { EntityRendererRegistry.register(TestRegistries.TEST_ENTITY, CowRenderer::new); EntityRendererRegistry.register(TestRegistries.TEST_ENTITY_2, CowRenderer::new); ClientTooltipComponentRegistry.register(ItemWithTooltip.MyTooltipComponent.class, ItemWithTooltip.MyClientTooltipComponent::new); + ClientCommandRegistrationEvent.EVENT.register((dispatcher, access) -> { + dispatcher.register(ClientCommandRegistrationEvent.literal("cool_client") + .then(ClientCommandRegistrationEvent.argument("string", StringArgumentType.string()) + .executes(context -> { + String string = StringArgumentType.getString(context, "string"); + SINK.accept("Cool client command for " + string); + return 0; + }))); + }); } } }