Use client-side commands for opening computer folders

Forge doesn't run client-side commands from sendUnsignedCommand, so we
still require a mixin there.

We do need to change the command name, as Fabric doesn't properly merge
the two command trees.
This commit is contained in:
Jonathan Coates 2024-01-30 22:00:36 +00:00
parent f284328656
commit 27c72a4571
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
11 changed files with 83 additions and 67 deletions

View File

@ -39,7 +39,6 @@ dependencies {
compileOnly(libs.bundles.externalMods.common)
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
compileOnly(libs.mixin)
annotationProcessorEverywhere(libs.autoService)
testFixturesAnnotationProcessor(libs.autoService)
@ -47,6 +46,7 @@ dependencies {
testImplementation(libs.bundles.test)
testRuntimeOnly(libs.bundles.testRuntime)
testModCompileOnly(libs.mixin)
testModImplementation(testFixtures(project(":core")))
testModImplementation(testFixtures(project(":common")))
testModImplementation(libs.bundles.kotlin)

View File

@ -17,8 +17,6 @@
import dan200.computercraft.client.sound.SpeakerManager;
import dan200.computercraft.shared.CommonHooks;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
@ -28,7 +26,6 @@
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.PauseAwareTimer;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.Util;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
@ -43,7 +40,6 @@
import net.minecraft.world.phys.HitResult;
import javax.annotation.Nullable;
import java.io.File;
import java.util.function.Consumer;
/**
@ -71,10 +67,6 @@ public static void onWorldUnload() {
ClientPocketComputers.reset();
}
public static boolean onChatMessage(String message) {
return handleOpenComputerCommand(message);
}
public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
return CableHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit)
|| MonitorHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit);
@ -109,34 +101,6 @@ public static void onPlayStreaming(SoundEngine engine, Channel channel, AudioStr
SpeakerManager.onPlayStreaming(engine, channel, stream);
}
/**
* Handle the {@link CommandComputerCraft#OPEN_COMPUTER} "clientside command". This isn't a true command, as we
* don't want it to actually be visible to the user.
*
* @param message The current chat message.
* @return Whether to cancel sending this message.
*/
private static boolean handleOpenComputerCommand(String message) {
if (!message.startsWith(CommandComputerCraft.OPEN_COMPUTER)) return false;
var server = Minecraft.getInstance().getSingleplayerServer();
if (server == null) return false;
var idStr = message.substring(CommandComputerCraft.OPEN_COMPUTER.length()).trim();
int id;
try {
id = Integer.parseInt(idStr);
} catch (NumberFormatException ignore) {
return false;
}
var file = new File(ServerContext.get(server).storageDir().toFile(), "computer/" + id);
if (!file.isDirectory()) return false;
Util.getPlatform().openFile(file);
return true;
}
/**
* Add additional information about the currently targeted block to the debug screen.
*

View File

@ -4,9 +4,13 @@
package dan200.computercraft.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.RenderTypes;
@ -16,11 +20,14 @@
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.media.items.TreasureDiskItem;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.color.item.ItemColor;
import net.minecraft.client.gui.screens.MenuScreens;
@ -30,6 +37,7 @@
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
import net.minecraft.client.renderer.item.ClampedItemPropertyFunction;
import net.minecraft.client.renderer.item.ItemProperties;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ResourceProvider;
@ -39,6 +47,7 @@
import net.minecraft.world.level.ItemLike;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@ -181,4 +190,45 @@ public float call(ItemStack stack, @Nullable ClientLevel level, @Nullable Living
return function.unclampedCall(stack, level, entity, layer);
}
}
/**
* Register client-side commands.
*
* @param dispatcher The dispatcher to register the commands to.
* @param sendError A function to send an error message.
* @param <T> The type of the client-side command context.
*/
public static <T> void registerClientCommands(CommandDispatcher<T> dispatcher, BiConsumer<T, Component> sendError) {
dispatcher.register(LiteralArgumentBuilder.<T>literal(CommandComputerCraft.CLIENT_OPEN_FOLDER)
.requires(x -> Minecraft.getInstance().getSingleplayerServer() != null)
.then(RequiredArgumentBuilder.<T, Integer>argument("computer_id", IntegerArgumentType.integer(0))
.executes(c -> handleOpenComputerCommand(c.getSource(), sendError, c.getArgument("computer_id", Integer.class)))
));
}
/**
* Handle the {@link CommandComputerCraft#CLIENT_OPEN_FOLDER} command.
*
* @param context The command context.
* @param sendError A function to send an error message.
* @param id The computer's id.
* @param <T> The type of the client-side command context.
* @return {@code 1} if a folder was opened, {@code 0} otherwise.
*/
private static <T> int handleOpenComputerCommand(T context, BiConsumer<T, Component> sendError, int id) {
var server = Minecraft.getInstance().getSingleplayerServer();
if (server == null) {
sendError.accept(context, Component.literal("Not on a single-player server"));
return 0;
}
var file = new File(ServerContext.get(server).storageDir().toFile(), "computer/" + id);
if (!file.isDirectory()) {
sendError.accept(context, Component.literal("Computer's folder does not exist"));
return 0;
}
Util.getPlatform().openFile(file);
return 1;
}
}

View File

@ -1,13 +0,0 @@
{
"required": true,
"package": "dan200.computercraft.mixin.client",
"minVersion": "0.8",
"compatibilityLevel": "JAVA_17",
"injectors": {
"defaultRequire": 1
},
"client": [
"ClientPacketListenerMixin"
],
"refmap": "client-computercraft.refmap.json"
}

View File

@ -54,7 +54,12 @@
public final class CommandComputerCraft {
public static final UUID SYSTEM_UUID = new UUID(0, 0);
public static final String OPEN_COMPUTER = "computercraft open-computer ";
/**
* The client-side command to open the folder. Ideally this would live under the main {@code computercraft}
* namespace, but unfortunately that overrides commands, rather than merging them.
*/
public static final String CLIENT_OPEN_FOLDER = "computercraft-computer-folder";
private CommandComputerCraft() {
}
@ -389,7 +394,7 @@ private static Component linkPosition(CommandSourceStack context, ServerComputer
return link(
text("\u270E"),
"/" + OPEN_COMPUTER + id,
"/" + CLIENT_OPEN_FOLDER + " " + id,
Component.translatable("commands.computercraft.dump.open_path")
);
}

View File

@ -16,6 +16,8 @@
import dan200.computercraft.shared.platform.FabricConfigFile;
import dan200.computercraft.shared.platform.FabricMessageType;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
@ -81,6 +83,10 @@ public static void init() {
return cable.getCloneItemStack(state, hit, level, pos, player);
});
ClientCommandRegistrationCallback.EVENT.register(
(dispatcher, registryAccess) -> ClientRegistry.registerClientCommands(dispatcher, FabricClientCommandSource::sendError)
);
((FabricConfigFile) ConfigSpec.clientSpec).load(FabricLoader.getInstance().getConfigDir().resolve(ComputerCraftAPI.MOD_ID + "-client.toml"));
}
}

View File

@ -38,10 +38,6 @@
},
"mixins": [
"computercraft.fabric.mixins.json",
{
"config": "computercraft-client.mixins.json",
"environment": "client"
},
{
"config": "computercraft-client.fabric.mixins.json",
"environment": "client"

View File

@ -106,10 +106,8 @@ minecraft {
}
mixin {
add(sourceSets.main.get(), "computercraft.refmap.json")
add(sourceSets.client.get(), "client-computercraft.refmap.json")
config("computercraft-client.mixins.json")
config("computercraft-client.forge.mixins.json")
}

View File

@ -6,11 +6,9 @@
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.sound.SpeakerSound;
import net.minecraft.commands.CommandSourceStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.CustomizeGuiOverlayEvent;
import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.client.event.RenderHighlightEvent;
import net.minecraftforge.client.event.RenderItemInFrameEvent;
import net.minecraftforge.client.event.*;
import net.minecraftforge.client.event.sound.PlayStreamingSourceEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.level.LevelEvent;
@ -78,4 +76,9 @@ public static void playStreaming(PlayStreamingSourceEvent event) {
if (!(event.getSound() instanceof SpeakerSound sound) || sound.getStream() == null) return;
ClientHooks.onPlayStreaming(event.getEngine(), event.getChannel(), sound.getStream());
}
@SubscribeEvent
public static void registerClientCommands(RegisterClientCommandsEvent event) {
ClientRegistry.registerClientCommands(event.getDispatcher(), CommandSourceStack::sendFailure);
}
}

View File

@ -4,17 +4,23 @@
package dan200.computercraft.mixin.client;
import dan200.computercraft.client.ClientHooks;
import dan200.computercraft.shared.command.CommandComputerCraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraftforge.client.ClientCommandHandler;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
* Allows triggering ComputerCraft's client commands from chat components events.
*/
@Mixin(ClientPacketListener.class)
class ClientPacketListenerMixin {
@Inject(method = "sendUnsignedCommand", at = @At("HEAD"), cancellable = true)
void commandUnsigned(String message, CallbackInfoReturnable<Boolean> ci) {
if (ClientHooks.onChatMessage(message)) ci.setReturnValue(true);
void commandUnsigned(String command, CallbackInfoReturnable<Boolean> ci) {
if (command.startsWith(CommandComputerCraft.CLIENT_OPEN_FOLDER) && ClientCommandHandler.runCommand(command)) {
ci.setReturnValue(true);
}
}
}

View File

@ -7,7 +7,8 @@
"defaultRequire": 1
},
"client": [
"BlockRenderDispatcherMixin"
"BlockRenderDispatcherMixin",
"ClientPacketListenerMixin"
],
"refmap": "client-computercraft.refmap.json"
}