diff --git a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java index 75b733b..e8e5c91 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java @@ -20,7 +20,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; public final class PackManager { @@ -30,7 +29,7 @@ public final class PackManager { "you are now blinking manually", "you're eligible for a free hug token! <3", "don't mind me!", "hissss", "Gayser and Floodgayte, my favourite plugins.", "meow", "we'll be done here soon™", "got anything else to say?", "we're done now!", "this will be fixed by v6053", "expect it to be done within 180 business days!", "any colour you like", "someone tell Mojang about this", "you can't unbake baked models, so we'll store the unbaked models", "soon fully datagen ready", - "packconverter when", "codecs ftw"); + "packconverter when", "codecs ftw", "we <3 barrels.", "Nothing beats a Jet2 Holiday!"); private static final RandomSource RANDOM = RandomSource.create(); private static final Path EXPORT_DIRECTORY = FabricLoader.getInstance().getGameDir().resolve(Rainbow.MOD_ID); @@ -64,6 +63,10 @@ public void runOrElse(Consumer consumer, Runnable runnable) { currentPack.ifPresentOrElse(consumer, runnable); } + public boolean isInProgress() { + return currentPack.isPresent(); + } + public Optional getExportPath() { return currentPack.map(pack -> EXPORT_DIRECTORY.resolve(pack.name())); } diff --git a/client/src/main/java/org/geysermc/rainbow/client/PackManagerUtils.java b/client/src/main/java/org/geysermc/rainbow/client/PackManagerUtils.java new file mode 100644 index 0000000..c61c6b6 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManagerUtils.java @@ -0,0 +1,119 @@ +package org.geysermc.rainbow.client; + +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.ItemStack; +import org.geysermc.rainbow.pack.BedrockPack; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +// These methods are only ever called if player isn't null, so please stop warning me IntelliJ qwq +@SuppressWarnings("DataFlowIssue") +public class PackManagerUtils { + public static boolean startPack(String name, PackManager manager, Minecraft minecraft) { + try { + manager.startPack(name); + minecraft.player.displayClientMessage( + Component.translatable("feedback.rainbow.pack_create_success", name) + .withStyle(style -> style + .withColor(ChatFormatting.GREEN) + ), + false + ); + return true; + } catch (IOException e) { + RainbowClient.LOGGER.error("IOException when creating pack.", e); + minecraft.player.displayClientMessage( + Component.translatable("feedback.rainbow.pack_create_error") + .withStyle(style -> style + .withColor(ChatFormatting.RED) + .withClickEvent(RainbowClient.LOG_CLICK_EVENT) + ), + false + ); + return false; + } + } + + public static boolean mapItemInHand(PackManager manager, Minecraft minecraft) { + if (!manager.isInProgress()) return false; + + manager.run(pack -> { + ItemStack heldItem = minecraft.player.getMainHandItem(); + switch (pack.map(heldItem)) { + case NONE_MAPPED -> minecraft.player.displayClientMessage(Component.translatable("feedback.rainbow.no_item_mapped").withStyle(ChatFormatting.RED), false); + case PROBLEMS_OCCURRED -> minecraft.player.displayClientMessage(Component.translatable("feedback.rainbow.mapped_item_problems").withStyle(ChatFormatting.YELLOW), false); + case MAPPED_SUCCESSFULLY -> minecraft.player.displayClientMessage(Component.translatable("feedback.rainbow.mapped_item").withStyle(ChatFormatting.GREEN), false); + } + }); + + return true; + } + + public static boolean mapItemsInInventory(PackManager manager, Minecraft minecraft) { + if (!manager.isInProgress()) return false; + + manager.run(pack -> { + int mapped = 0; + boolean errors = false; + + List items; + + if (minecraft.screen instanceof AbstractContainerScreen abstractContainerScreen) { + items = abstractContainerScreen.getMenu().getItems(); + } else { + items = new ArrayList<>(minecraft.player.getInventory().getNonEquipmentItems()); + items.add(minecraft.player.getInventory().getItem(36)); // Boots + items.add(minecraft.player.getInventory().getItem(37)); // Leggings + items.add(minecraft.player.getInventory().getItem(38)); // Chestplate + items.add(minecraft.player.getInventory().getItem(39)); // Helmet + items.add(minecraft.player.getInventory().getItem(40)); // Offhand + } + + for (ItemStack stack : items) { + BedrockPack.MappingResult result = pack.map(stack); + if (result != BedrockPack.MappingResult.NONE_MAPPED) { + mapped++; + if (result == BedrockPack.MappingResult.PROBLEMS_OCCURRED) { + errors = true; + } + } + } + + if (mapped > 0) { + minecraft.player.displayClientMessage(Component.translatable("feedback.rainbow.mapped_items", mapped).withStyle(ChatFormatting.GREEN), false); + if (errors) { + minecraft.player.displayClientMessage(Component.translatable("feedback.rainbow.mapped_items_problems").withStyle(ChatFormatting.YELLOW), false); + } + } else { + minecraft.player.displayClientMessage(Component.translatable("feedback.rainbow.no_items_mapped").withStyle(ChatFormatting.RED), false); + } + }); + + return true; + } + + public static boolean finishPack(PackManager manager, Minecraft minecraft) { + if (!manager.isInProgress()) return false; + + Optional exportPath = manager.getExportPath(); + // Ignore the result of the method, we check above + manager.finish(() -> minecraft.player.displayClientMessage( + Component.translatable("feedback.rainbow.pack_finished_success") + .withStyle(style -> style + .withColor(ChatFormatting.GREEN) + .withClickEvent(new ClickEvent.OpenFile(exportPath.orElseThrow())) + ), + false + )); + + return true; + } +} diff --git a/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java b/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java index 131846b..a898c6d 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java +++ b/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java @@ -1,26 +1,55 @@ package org.geysermc.rainbow.client; +import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.logging.LogUtils; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; import net.minecraft.commands.synchronization.SingletonArgumentInfo; +import net.minecraft.network.chat.ClickEvent; import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.client.command.CommandSuggestionsArgumentType; import org.geysermc.rainbow.client.command.PackGeneratorCommand; import org.geysermc.rainbow.client.mapper.PackMapper; +import org.geysermc.rainbow.client.screen.CreatePackScreen; +import org.geysermc.rainbow.client.screen.ManagePackScreen; +import org.slf4j.Logger; public class RainbowClient implements ClientModInitializer { + public static final Logger LOGGER = LogUtils.getLogger(); + public static final ClickEvent LOG_CLICK_EVENT = new ClickEvent.OpenFile(Minecraft.getInstance().gameDirectory.toPath().resolve("logs/latest.log")); - private final PackManager packManager = new PackManager(); - private final PackMapper packMapper = new PackMapper(packManager); + private static final PackManager packManager = new PackManager(); + private static final PackMapper packMapper = new PackMapper(packManager); + + public static PackManager getPackManager() { + return packManager; + } + public static PackMapper getPackMapper() { + return packMapper; + } // TODO export language overrides @Override public void onInitializeClient() { + KeyMapping openPackScreenKeyBind = KeyBindingHelper.registerKeyBinding(new KeyMapping("key.rainbow.open_pack_screen", InputConstants.KEY_O, KeyMapping.Category.MISC)); + ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> PackGeneratorCommand.register(dispatcher, packManager, packMapper)); ClientTickEvents.START_CLIENT_TICK.register(packMapper::tick); + ClientTickEvents.END_CLIENT_TICK.register(minecraft -> { + if (minecraft.player == null) return; + if (minecraft.screen != null) return; + + if (openPackScreenKeyBind.consumeClick()) { + if (packManager.isInProgress()) minecraft.setScreen(new ManagePackScreen(packManager, packMapper)); + else minecraft.setScreen(new CreatePackScreen(packManager, packMapper)); + } + }); ArgumentTypeRegistry.registerArgumentType(Rainbow.getModdedLocation("command_suggestions"), CommandSuggestionsArgumentType.class, SingletonArgumentInfo.contextFree(CommandSuggestionsArgumentType::new)); diff --git a/client/src/main/java/org/geysermc/rainbow/client/RainbowClientIOHandler.java b/client/src/main/java/org/geysermc/rainbow/client/RainbowClientIOHandler.java index f31f1f8..d03fb6f 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/RainbowClientIOHandler.java +++ b/client/src/main/java/org/geysermc/rainbow/client/RainbowClientIOHandler.java @@ -1,18 +1,22 @@ package org.geysermc.rainbow.client; +import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.components.toasts.SystemToast; import net.minecraft.network.chat.Component; import org.geysermc.rainbow.RainbowIO; import java.io.IOException; public class RainbowClientIOHandler implements RainbowIO.IOExceptionListener { - private static final SystemToast.SystemToastId TOAST_ID = new SystemToast.SystemToastId(); - @Override public void error(IOException exception) { - Minecraft.getInstance().getToastManager().addToast(new SystemToast(TOAST_ID, - Component.translatable("toast.rainbow.io_exception.title"), Component.translatable("toast.rainbow.io_exception.description"))); + Minecraft.getInstance().player.displayClientMessage( + Component.translatable("feedback.rainbow.io_exception") + .withStyle(style -> style + .withColor(ChatFormatting.RED) + .withClickEvent(RainbowClient.LOG_CLICK_EVENT) + ), + false + ); } } diff --git a/client/src/main/java/org/geysermc/rainbow/client/command/PackGeneratorCommand.java b/client/src/main/java/org/geysermc/rainbow/client/command/PackGeneratorCommand.java index 02ca8c5..1358142 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/command/PackGeneratorCommand.java +++ b/client/src/main/java/org/geysermc/rainbow/client/command/PackGeneratorCommand.java @@ -8,22 +8,19 @@ import net.minecraft.ChatFormatting; import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; -import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.item.ItemStack; import org.geysermc.rainbow.client.PackManager; +import org.geysermc.rainbow.client.PackManagerUtils; import org.geysermc.rainbow.client.mapper.InventoryMapper; import org.geysermc.rainbow.client.mapper.PackMapper; import org.geysermc.rainbow.pack.BedrockPack; -import java.nio.file.Path; -import java.util.Optional; import java.util.function.BiConsumer; public class PackGeneratorCommand { - private static final Component NO_PACK_CREATED = Component.translatable("commands.rainbow.no_pack", Component.literal("/rainbow create ") + private static final Component NO_PACK_CREATED = Component.translatable("feedback.rainbow.no_pack", Component.literal("/rainbow create ") .withStyle(style -> style.withColor(ChatFormatting.BLUE).withUnderlined(true) - .withClickEvent(new ClickEvent.SuggestCommand("/rainbow create ")))); + .withClickEvent(new ClickEvent.SuggestCommand("/rainbow create ")))).withStyle(ChatFormatting.RED); public static void register(CommandDispatcher dispatcher, PackManager packManager, PackMapper packMapper) { dispatcher.register(ClientCommandManager.literal("rainbow") @@ -31,51 +28,21 @@ public static void register(CommandDispatcher dispatc .then(ClientCommandManager.argument("name", StringArgumentType.word()) .executes(context -> { String name = StringArgumentType.getString(context, "name"); - try { - packManager.startPack(name); - } catch (Exception exception) { - context.getSource().sendError(Component.translatable("commands.rainbow.create_pack_failed")); - throw new RuntimeException(exception); - } - context.getSource().sendFeedback(Component.translatable("commands.rainbow.pack_created", name)); + + PackManagerUtils.startPack(name, packManager, context.getSource().getClient()); + return 0; }) ) ) .then(ClientCommandManager.literal("map") .executes(runWithPack(packManager, (source, pack) -> { - ItemStack heldItem = source.getPlayer().getMainHandItem(); - switch (pack.map(heldItem)) { - case NONE_MAPPED -> source.sendError(Component.translatable("commands.rainbow.no_item_mapped")); - case PROBLEMS_OCCURRED -> source.sendFeedback(Component.translatable("commands.rainbow.mapped_held_item_problems")); - case MAPPED_SUCCESSFULLY -> source.sendFeedback(Component.translatable("commands.rainbow.mapped_held_item")); - } + PackManagerUtils.mapItemInHand(packManager, source.getClient()); })) ) .then(ClientCommandManager.literal("mapinventory") .executes(runWithPack(packManager, (source, pack) -> { - int mapped = 0; - boolean errors = false; - Inventory inventory = source.getPlayer().getInventory(); - - for (ItemStack stack : inventory) { - BedrockPack.MappingResult result = pack.map(stack); - if (result != BedrockPack.MappingResult.NONE_MAPPED) { - mapped++; - if (result == BedrockPack.MappingResult.PROBLEMS_OCCURRED) { - errors = true; - } - } - } - - if (mapped > 0) { - source.sendFeedback(Component.translatable("commands.rainbow.mapped_items_from_inventory", mapped)); - if (errors) { - source.sendFeedback(Component.translatable("commands.rainbow.mapped_items_problems")); - } - } else { - source.sendError(Component.translatable("commands.rainbow.no_items_mapped")); - } + PackManagerUtils.mapItemsInInventory(packManager, source.getClient()); })) ) .then(ClientCommandManager.literal("auto") @@ -100,24 +67,25 @@ public static void register(CommandDispatcher dispatc .then(ClientCommandManager.literal("inventory") .executes(runWithPack(packManager, (source, pack) -> { packMapper.setItemProvider(InventoryMapper.INSTANCE); - source.sendFeedback(Component.translatable("commands.rainbow.automatic_inventory_mapping")); + source.sendFeedback( + Component.translatable("feedback.rainbow.automatic_mapping_inventory") + .withStyle(ChatFormatting.GREEN) + ); })) ) .then(ClientCommandManager.literal("stop") .executes(runWithPack(packManager, (source, pack) -> { packMapper.setItemProvider(null); - source.sendFeedback(Component.translatable("commands.rainbow.stopped_automatic_mapping")); + source.sendFeedback( + Component.translatable("feedback.rainbow.automatic_mapping_none") + .withStyle(ChatFormatting.GREEN) + ); })) ) ) .then(ClientCommandManager.literal("finish") .executes(context -> { - Optional exportPath = packManager.getExportPath(); - Runnable onFinish = () -> context.getSource().sendFeedback(Component.translatable("commands.rainbow.pack_finished_successfully").withStyle(style - -> style.withUnderlined(true).withClickEvent(new ClickEvent.OpenFile(exportPath.orElseThrow())))); - if (!packManager.finish(onFinish)) { - context.getSource().sendError(NO_PACK_CREATED); - } + PackManagerUtils.finishPack(packManager, context.getSource().getClient()); return 0; }) ) diff --git a/client/src/main/java/org/geysermc/rainbow/client/mapper/CustomItemProvider.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/CustomItemProvider.java index 8708f5d..20cb216 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/mapper/CustomItemProvider.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/CustomItemProvider.java @@ -2,6 +2,7 @@ import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; import java.util.stream.Stream; @@ -11,4 +12,6 @@ public interface CustomItemProvider { Stream nextItems(LocalPlayer player, ClientPacketListener connection); boolean isDone(); + + Component name(); } diff --git a/client/src/main/java/org/geysermc/rainbow/client/mapper/InventoryMapper.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/InventoryMapper.java index 4bac740..d2bd04a 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/mapper/InventoryMapper.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/InventoryMapper.java @@ -2,6 +2,7 @@ import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; import java.util.stream.Stream; @@ -20,4 +21,9 @@ public Stream nextItems(LocalPlayer player, ClientPacketListener conn public boolean isDone() { return false; } + + @Override + public Component name() { + return Component.translatable("menu.rainbow.manage_pack.auto_mapping.inventory"); + } } diff --git a/client/src/main/java/org/geysermc/rainbow/client/mapper/ItemSuggestionProvider.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/ItemSuggestionProvider.java index 33ef127..adb1d21 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/mapper/ItemSuggestionProvider.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/ItemSuggestionProvider.java @@ -2,6 +2,7 @@ import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ServerboundChatCommandPacket; import net.minecraft.world.item.ItemStack; @@ -50,4 +51,9 @@ public int queueSize() { public boolean isDone() { return remainingCommands.isEmpty() && !waitingOnItem && !waitingOnClear; } + + @Override + public Component name() { + return Component.translatable("menu.rainbow.manage_pack.auto_mapping.commands"); + } } diff --git a/client/src/main/java/org/geysermc/rainbow/client/mapper/NoItemProvider.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/NoItemProvider.java new file mode 100644 index 0000000..4af0e40 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/NoItemProvider.java @@ -0,0 +1,29 @@ +package org.geysermc.rainbow.client.mapper; + +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.ItemStack; + +import java.util.stream.Stream; + +public class NoItemProvider implements CustomItemProvider { + public static final NoItemProvider INSTANCE = new NoItemProvider(); + + private NoItemProvider() {} + + @Override + public Stream nextItems(LocalPlayer player, ClientPacketListener connection) { + return Stream.empty(); + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public Component name() { + return Component.translatable("menu.rainbow.manage_pack.auto_mapping.none"); + } +} diff --git a/client/src/main/java/org/geysermc/rainbow/client/mapper/PackMapper.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/PackMapper.java index cb56500..7d8745e 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/mapper/PackMapper.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/PackMapper.java @@ -5,6 +5,7 @@ import net.minecraft.client.player.LocalPlayer; import net.minecraft.network.chat.Component; import org.geysermc.rainbow.client.PackManager; +import org.geysermc.rainbow.client.screen.ManagePackScreen; import org.geysermc.rainbow.pack.BedrockPack; import java.util.Objects; @@ -17,11 +18,20 @@ public PackMapper(PackManager packManager) { this.packManager = packManager; } + public CustomItemProvider getItemProvider() { + if (this.itemProvider == null) return NoItemProvider.INSTANCE; + return this.itemProvider; + } + public void setItemProvider(CustomItemProvider itemProvider) { - this.itemProvider = itemProvider; + if (itemProvider == NoItemProvider.INSTANCE) this.itemProvider = null; + else this.itemProvider = itemProvider; } public void tick(Minecraft minecraft) { + // Don't tick when this screen is open, the user might be toggling settings + if (minecraft.screen instanceof ManagePackScreen) return; + if (itemProvider != null) { LocalPlayer player = Objects.requireNonNull(minecraft.player); ClientPacketListener connection = Objects.requireNonNull(minecraft.getConnection()); @@ -33,10 +43,10 @@ public void tick(Minecraft minecraft) { .filter(result -> result != BedrockPack.MappingResult.NONE_MAPPED) .count(); if (mapped != 0) { - player.displayClientMessage(Component.translatable("chat.rainbow.mapped_items", mapped), false); + player.displayClientMessage(Component.translatable("feedback.rainbow.mapped_items", mapped), false); } if (itemProvider.isDone()) { - player.displayClientMessage(Component.translatable("chat.rainbow.automatic_mapping_finished"), false); + player.displayClientMessage(Component.translatable("feedback.rainbow.automatic_mapping_finished"), false); itemProvider = null; } }, () -> itemProvider = null); diff --git a/client/src/main/java/org/geysermc/rainbow/client/mixin/AbstractContainerScreenMixin.java b/client/src/main/java/org/geysermc/rainbow/client/mixin/AbstractContainerScreenMixin.java new file mode 100644 index 0000000..4f14fd3 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/mixin/AbstractContainerScreenMixin.java @@ -0,0 +1,46 @@ +package org.geysermc.rainbow.client.mixin; + +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.Component; +import org.geysermc.rainbow.client.PackManagerUtils; +import org.geysermc.rainbow.client.RainbowClient; +import org.geysermc.rainbow.client.screen.CreatePackScreen; +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.CallbackInfo; + +import java.util.concurrent.atomic.AtomicReference; + +@Mixin(AbstractContainerScreen.class) +public abstract class AbstractContainerScreenMixin extends Screen { + protected AbstractContainerScreenMixin(Component title) { + super(title); + } + + @Inject( + method = "init()V", + at = @At("TAIL") + ) + private void injectInit(CallbackInfo ci) { + this.addRenderableWidget(Button.builder( + Component.translatable("menu.rainbow.container.map_items"), + (button) -> { + if (!PackManagerUtils.mapItemsInInventory(RainbowClient.getPackManager(), this.minecraft)) { + AtomicReference current = new AtomicReference<>(this.minecraft.screen); + + this.minecraft.setScreen(new CreatePackScreen( + RainbowClient.getPackManager(), + RainbowClient.getPackMapper(), + () -> { + this.minecraft.setScreen(current.get()); + PackManagerUtils.mapItemsInInventory(RainbowClient.getPackManager(), this.minecraft); + } + )); + } + } + ).bounds(5, 5, 74, 20).build()); + } +} diff --git a/client/src/main/java/org/geysermc/rainbow/client/screen/BaseScreen.java b/client/src/main/java/org/geysermc/rainbow/client/screen/BaseScreen.java new file mode 100644 index 0000000..2533aff --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/screen/BaseScreen.java @@ -0,0 +1,28 @@ +package org.geysermc.rainbow.client.screen; + +import net.minecraft.client.gui.components.StringWidget; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +public abstract class BaseScreen extends Screen { + final Runnable onClose; + + protected Component renderTitle; + + protected BaseScreen(Component title, Runnable onClose) { + super(title); + this.renderTitle = title; + this.onClose = onClose; + } + + public void renderTitle() { + int i = this.font.width(this.renderTitle); + this.addRenderableWidget(new StringWidget(this.width / 2 - i / 2, 40, i, 9, this.renderTitle, this.font)); + } + + @Override + public void onClose() { + if (onClose == null) super.onClose(); + else onClose.run(); + } +} diff --git a/client/src/main/java/org/geysermc/rainbow/client/screen/CreatePackScreen.java b/client/src/main/java/org/geysermc/rainbow/client/screen/CreatePackScreen.java new file mode 100644 index 0000000..61e43b9 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/screen/CreatePackScreen.java @@ -0,0 +1,61 @@ +package org.geysermc.rainbow.client.screen; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.layouts.FrameLayout; +import net.minecraft.client.gui.layouts.GridLayout; +import net.minecraft.network.chat.Component; +import org.geysermc.rainbow.client.PackManager; +import org.geysermc.rainbow.client.PackManagerUtils; +import org.geysermc.rainbow.client.mapper.PackMapper; +import org.jetbrains.annotations.Nullable; + +public class CreatePackScreen extends BaseScreen { + private static final Component PACK_NAME = Component.translatable("menu.rainbow.create_pack.pack_name"); + private static final Component CREATE = Component.translatable("menu.rainbow.create_pack.create"); + private static final Component CANCEL = Component.translatable("menu.rainbow.create_pack.cancel"); + + private final PackManager manager; + private final PackMapper mapper; + + @Nullable + private EditBox PACK_NAME_EDIT_BOX; + + public CreatePackScreen(PackManager manager, PackMapper mapper) { + this(manager, mapper, null); + } + + public CreatePackScreen(PackManager manager, PackMapper mapper, Runnable onClose) { + super(Component.translatable("menu.rainbow.create_pack"), onClose); + this.manager = manager; + this.mapper = mapper; + this.minecraft = Minecraft.getInstance(); + if (manager.isInProgress()) this.minecraft.setScreen(new ManagePackScreen(manager, mapper)); + } + + @Override + protected void init() { + renderTitle(); + GridLayout gridLayout = new GridLayout(); + gridLayout.defaultCellSetting().padding(4, 4, 4, 0); + GridLayout.RowHelper rowHelper = gridLayout.createRowHelper(2); + rowHelper.addChild(PACK_NAME_EDIT_BOX = new EditBox(this.minecraft.font, 204, 20, PACK_NAME), 2); + + rowHelper.addChild(Button.builder(CREATE, (button) -> { + if (PackManagerUtils.startPack(PACK_NAME_EDIT_BOX.getValue(), this.manager, this.minecraft)) { + if (this.onClose == null) + this.minecraft.setScreen(new ManagePackScreen(this.manager, this.mapper)); + else this.onClose.run(); + } else this.minecraft.setScreen(null); + }).width(98).build(), 1); + + rowHelper.addChild(Button.builder(CANCEL, (button) -> { + this.minecraft.setScreen(null); + }).width(98).build(), 1); + + gridLayout.arrangeElements(); + FrameLayout.alignInRectangle(gridLayout, 0, 0, this.width, this.height, 0.5F, 0.35F); + gridLayout.visitWidgets(this::addRenderableWidget); + } +} diff --git a/client/src/main/java/org/geysermc/rainbow/client/screen/ManagePackScreen.java b/client/src/main/java/org/geysermc/rainbow/client/screen/ManagePackScreen.java new file mode 100644 index 0000000..38d8d30 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/screen/ManagePackScreen.java @@ -0,0 +1,85 @@ +package org.geysermc.rainbow.client.screen; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.CycleButton; +import net.minecraft.client.gui.layouts.FrameLayout; +import net.minecraft.client.gui.layouts.GridLayout; +import net.minecraft.network.chat.Component; +import org.geysermc.rainbow.client.PackManager; +import org.geysermc.rainbow.client.PackManagerUtils; +import org.geysermc.rainbow.client.mapper.*; +import org.jetbrains.annotations.Nullable; + +public class ManagePackScreen extends BaseScreen { + private static final Component MAP_ITEM_HAND = Component.translatable("menu.rainbow.manage_pack.map_item_hand"); + private static final Component MAP_ITEM_INVENTORY = Component.translatable("menu.rainbow.manage_pack.map_item_inventory"); + private static final Component FINISH_PACK = Component.translatable("menu.rainbow.manage_pack.finish_pack"); + + private final PackManager manager; + private final PackMapper mapper; + + @Nullable + private String packName; + + public ManagePackScreen(PackManager manager, PackMapper mapper) { + this(manager, mapper, null); + } + + public ManagePackScreen(PackManager manager, PackMapper mapper, Runnable onClose) { + super(Component.translatable("menu.rainbow.manage_pack"), onClose); + this.manager = manager; + this.mapper = mapper; + this.minecraft = Minecraft.getInstance(); + manager.runOrElse(pack -> { + this.packName = pack.name(); + }, this::showCreatePackScreen); + this.renderTitle = Component.literal(this.packName); + } + + @Override + protected void init() { + renderTitle(); + GridLayout gridLayout = new GridLayout(); + gridLayout.defaultCellSetting().padding(4, 4, 4, 0); + GridLayout.RowHelper rowHelper = gridLayout.createRowHelper(2); + + rowHelper.addChild(Button.builder(MAP_ITEM_HAND, (button) -> { + if (PackManagerUtils.mapItemInHand(manager, minecraft)) + this.minecraft.setScreen(null); + else this.showCreatePackScreen(); + }).width(204).build(), 2); + + rowHelper.addChild(Button.builder(MAP_ITEM_INVENTORY, (button) -> { + if (PackManagerUtils.mapItemsInInventory(manager, minecraft)) + this.minecraft.setScreen(null); + else this.showCreatePackScreen(); + }).width(204).build(), 2); + + rowHelper.addChild(CycleButton.builder((provider) -> { + CustomItemProvider itemProvider = ((CustomItemProvider) provider); + return itemProvider.name(); + }) + .withInitialValue(mapper.getItemProvider()) + .displayOnlyValue() + // TODO make this list dyanmic, another mod may add a method of mapping + .withValues(NoItemProvider.INSTANCE, InventoryMapper.INSTANCE).create(0, 0, 204, 20, Component.empty(), (button, value) -> { + CustomItemProvider provider = (CustomItemProvider) value; + mapper.setItemProvider(provider); + }), 2); + + rowHelper.addChild(Button.builder(FINISH_PACK, (button) -> { + if (PackManagerUtils.finishPack(manager, minecraft)) + this.minecraft.setScreen(null); + else this.showCreatePackScreen(); + }).width(204).build(), 2); + + gridLayout.arrangeElements(); + FrameLayout.alignInRectangle(gridLayout, 0, 0, this.width, this.height, 0.5F, 0.35F); + gridLayout.visitWidgets(this::addRenderableWidget); + } + + private void showCreatePackScreen() { + this.minecraft.setScreen(new CreatePackScreen(this.manager, this.mapper)); + } +} diff --git a/client/src/main/resources/assets/rainbow/lang/en_us.json b/client/src/main/resources/assets/rainbow/lang/en_us.json index cc89808..615d8bb 100644 --- a/client/src/main/resources/assets/rainbow/lang/en_us.json +++ b/client/src/main/resources/assets/rainbow/lang/en_us.json @@ -1,19 +1,29 @@ { - "chat.rainbow.automatic_mapping_finished": "Finished mapping items from provider", - "chat.rainbow.mapped_items": "Mapped %d items", - "commands.rainbow.automatic_inventory_mapping": "Now watching inventories for custom items to map", - "commands.rainbow.create_pack_failed": "Failed to create new pack!", - "commands.rainbow.mapped_held_item": "The held item was mapped", - "commands.rainbow.mapped_held_item_problems": "The held item was mapped, however problems occurred whilst doing so. Read the pack report after finishing the pack for more information", - "commands.rainbow.mapped_items_from_inventory": "Mapped %d items from your inventory", - "commands.rainbow.mapped_items_problems": "Problems occurred whilst mapping items. Read the pack report after finishing the pack for more information", - "commands.rainbow.no_item_mapped": "No item was mapped. Either no custom item was found, or it was already included in the pack", - "commands.rainbow.no_items_mapped": "No items were mapped. Either no custom items were found, or they were already included in the pack", - "commands.rainbow.no_pack": "Create a pack first: %s", - "commands.rainbow.pack_created": "Created pack with name %s", - "commands.rainbow.pack_finished_error": "Errors occurred whilst writing the pack to disk!", - "commands.rainbow.pack_finished_successfully": "Wrote pack to disk", - "commands.rainbow.stopped_automatic_mapping": "Stopped automatic mapping of custom items", - "toast.rainbow.io_exception.description": "Please check your game logs for more information", - "toast.rainbow.io_exception.title": "A filesystem error occurred in Rainbow!" + "feedback.rainbow.no_pack": "Create a pack first! Run %s", + "feedback.rainbow.pack_create_success": "Created pack with name %s", + "feedback.rainbow.pack_create_error": "Failed to create new pack! Click this message to view client logs for more information.", + "feedback.rainbow.mapped_item": "Mapped item successfully!", + "feedback.rainbow.mapped_item_problems": "Mapped item with one or more issues! Check the report file once conversion has been completed for more information.", + "feedback.rainbow.no_item_mapped": "This item was not mapped, either it is not a custom item, or it was already mapped in this conversion.", + "feedback.rainbow.mapped_items": "Mapped %d items successfully!", + "feedback.rainbow.mapped_items_problems": "Mapped %d items with one or more issues! Check the report file once conversion has been completed for more information.", + "feedback.rainbow.no_items_mapped": "No items were mapped, either they aren't custom items, or they were already mapped in this conversion.", + "feedback.rainbow.automatic_mapping_none": "No longer automatically converting items.", + "feedback.rainbow.automatic_mapping_inventory": "Watching for custom items in the inventory...", + "feedback.rainbow.automatic_mapping_finished": "Automatic mapping completed!", + "feedback.rainbow.pack_finished_success": "Pack created! Click this message to open the pack.", + "feedback.rainbow.io_exception": "A filesystem error occurred in Rainbow! Click this message to view client logs for more information.", + "menu.rainbow.create_pack": "Create Pack", + "menu.rainbow.create_pack.create": "Create", + "menu.rainbow.create_pack.cancel": "Cancel", + "menu.rainbow.create_pack.pack_name": "Pack name", + "menu.rainbow.manage_pack": "Manage Pack", + "menu.rainbow.manage_pack.map_item_hand": "Map item in hand", + "menu.rainbow.manage_pack.map_item_inventory": "Map items in inventory", + "menu.rainbow.manage_pack.auto_mapping.none": "Don't automatically map items", + "menu.rainbow.manage_pack.auto_mapping.commands": "Automatically map items in commands", + "menu.rainbow.manage_pack.auto_mapping.inventory": "Automatically map items in inventory", + "menu.rainbow.manage_pack.finish_pack": "Finish pack", + "menu.rainbow.container.map_items": "Map items", + "key.rainbow.open_pack_screen": "Open pack screen" } \ No newline at end of file diff --git a/client/src/main/resources/rainbow-client.mixins.json b/client/src/main/resources/rainbow-client.mixins.json index a55d09a..1190916 100644 --- a/client/src/main/resources/rainbow-client.mixins.json +++ b/client/src/main/resources/rainbow-client.mixins.json @@ -1,17 +1,18 @@ { - "required": true, - "minVersion": "0.8", - "package": "org.geysermc.rainbow.client.mixin", - "compatibilityLevel": "JAVA_21", - "client": [ - "EntityRenderDispatcherAccessor", - "GuiItemRenderStateMixin", - "ModelManagerMixin", - "PictureInPictureRendererAccessor", - "PictureInPictureRendererMixin", - "SplashRendererAccessor" - ], - "injectors": { - "defaultRequire": 1 + "required": true, + "minVersion": "0.8", + "package": "org.geysermc.rainbow.client.mixin", + "compatibilityLevel": "JAVA_21", + "client": [ + "AbstractContainerScreenMixin", + "EntityRenderDispatcherAccessor", + "GuiItemRenderStateMixin", + "ModelManagerMixin", + "PictureInPictureRendererAccessor", + "PictureInPictureRendererMixin", + "SplashRendererAccessor" + ], + "injectors": { + "defaultRequire": 1 } }