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 50bafb7..3421895 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java @@ -32,7 +32,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); @@ -71,6 +71,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.resources.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..bf246e6 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManagerUtils.java @@ -0,0 +1,115 @@ +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 net.minecraft.world.item.ItemStackTemplate; +import org.geysermc.rainbow.client.mapper.PackMapper; +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 { + 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 ")))).withStyle(ChatFormatting.RED); + + public static boolean startPack(String name, PackManager manager, Minecraft minecraft) { + try { + manager.startPack(name); + minecraft.player.sendSystemMessage( + Component.translatable("feedback.rainbow.pack_create_success", name) + .withStyle(style -> style + .withColor(ChatFormatting.GREEN) + ) + ); + return true; + } catch (IOException e) { + RainbowClient.LOGGER.error("IOException when creating pack.", e); + minecraft.player.sendSystemMessage( + Component.translatable("feedback.rainbow.pack_create_error") + .withStyle(style -> style + .withColor(ChatFormatting.RED) + .withClickEvent(RainbowClient.LOG_CLICK_EVENT) + ) + ); + return false; + } + } + + public static boolean mapItemInHand(PackManager manager, Minecraft minecraft) { + if (!manager.isInProgress()) return false; + + manager.run(pack -> { + ItemStack heldItem = minecraft.player.getMainHandItem(); + if (heldItem.isEmpty()) { + minecraft.player.sendSystemMessage(Component.literal("Must hold an item to map").withStyle(ChatFormatting.RED)); + } else { + switch (RainbowClient.getPackMapper().mapItems(pack, List.of(ItemStackTemplate.fromNonEmptyStack(heldItem))).toSingleResult()) { + case NONE_MAPPED -> minecraft.player.sendSystemMessage(Component.translatable("commands.rainbow.no_item_mapped").withStyle(ChatFormatting.RED)); + case PROBLEMS_OCCURRED -> minecraft.player.sendSystemMessage(Component.translatable("commands.rainbow.mapped_held_item_problems")); + case MAPPED_SUCCESSFULLY -> minecraft.player.sendSystemMessage(Component.translatable("commands.rainbow.mapped_held_item")); + } + } + }); + + return true; + } + + public static boolean mapItemsInInventory(PackManager manager, Minecraft minecraft) { + if (!manager.isInProgress()) return false; + + manager.run(pack -> { + List inventoryStacks = new ArrayList<>(minecraft.player.inventoryMenu.getItems().stream() + .filter(stack -> !stack.isEmpty()) + .map(ItemStackTemplate::fromNonEmptyStack) + .toList()); + if (minecraft.player.hasContainerOpen()) { + inventoryStacks.addAll(minecraft.player.containerMenu.getItems().stream() + .filter(stack -> !stack.isEmpty()) + .map(ItemStackTemplate::fromNonEmptyStack) + .toList()); + } + PackMapper.MappingResults results = RainbowClient.getPackMapper().mapItems(pack, inventoryStacks); + + if (results.itemsMapped() > 0 || results.skullsMapped() > 0) { + if (results.itemsMapped() > 0) { + minecraft.player.sendSystemMessage(Component.translatable("commands.rainbow.mapped_items_from_inventory", results.itemsMapped()).withStyle(ChatFormatting.GREEN)); + if (results.problems() > 0) { + minecraft.player.sendSystemMessage(Component.translatable("commands.rainbow.mapped_items_problems").withStyle(ChatFormatting.YELLOW)); + } + } + if (results.skullsMapped() > 0) { + minecraft.player.sendSystemMessage(Component.translatable("commands.rainbow.mapped_skulls_from_inventory", results.skullsMapped()).withStyle(ChatFormatting.GREEN)); + } + } else { + minecraft.player.sendSystemMessage(Component.translatable("commands.rainbow.no_items_mapped").withStyle(ChatFormatting.RED)); + } + }); + + return true; + } + + public static boolean finishPack(PackManager manager, Minecraft minecraft) { + if (!manager.isInProgress()) return false; + + minecraft.player.sendSystemMessage(Component.translatable("commands.rainbow.pack_finishing")); + Optional exportPath = manager.getExportPath(); + Runnable onFinish = () -> minecraft.player.sendSystemMessage(Component.translatable("commands.rainbow.pack_finished_successfully").withStyle(style + -> style.withUnderlined(true).withClickEvent(new ClickEvent.OpenFile(exportPath.orElseThrow())))); + if (!manager.finish(onFinish)) { + minecraft.player.sendSystemMessage(NO_PACK_CREATED); + } + + 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 061433c..2d98014 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java +++ b/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java @@ -1,29 +1,58 @@ 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.fabricmc.loader.api.FabricLoader; 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")); public static final String MOD_ID = Rainbow.MOD_ID + "-client"; - 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, _) -> 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.getModdedIdentifier("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 283e9ea..1781a84 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,9 +8,11 @@ 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 net.minecraft.world.item.ItemStackTemplate; 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; @@ -22,9 +24,9 @@ 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(ClientCommands.literal("rainbow") @@ -32,54 +34,21 @@ public static void register(CommandDispatcher dispatc .then(ClientCommands.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(ClientCommands.literal("map") .executes(runWithPack(packManager, (source, pack) -> { - ItemStack heldItem = source.getPlayer().getMainHandItem(); - if (heldItem.isEmpty()) { - source.sendError(Component.literal("Must hold an item to map")); - } else { - switch (packMapper.mapItems(pack, List.of(ItemStackTemplate.fromNonEmptyStack(heldItem))).toSingleResult()) { - 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(ClientCommands.literal("mapinventory") .executes(runWithPack(packManager, (source, pack) -> { - List inventoryStacks = new ArrayList<>(); - source.getPlayer().getInventory().forEach(stack -> { - if (!stack.isEmpty()) { - inventoryStacks.add(ItemStackTemplate.fromNonEmptyStack(stack)); - } - }); - PackMapper.MappingResults results = packMapper.mapItems(pack, inventoryStacks); - - if (results.itemsMapped() > 0 || results.skullsMapped() > 0) { - if (results.itemsMapped() > 0) { - source.sendFeedback(Component.translatable("commands.rainbow.mapped_items_from_inventory", results.itemsMapped())); - if (results.problems() > 0) { - source.sendFeedback(Component.translatable("commands.rainbow.mapped_items_problems")); - } - } - if (results.skullsMapped() > 0) { - source.sendFeedback(Component.translatable("commands.rainbow.mapped_skulls_from_inventory", results.skullsMapped())); - } - } else { - source.sendError(Component.translatable("commands.rainbow.no_items_mapped")); - } + PackManagerUtils.mapItemsInInventory(packManager, source.getClient()); })) ) .then(ClientCommands.literal("auto") @@ -104,25 +73,25 @@ public static void register(CommandDispatcher dispatc .then(ClientCommands.literal("inventory") .executes(runWithPack(packManager, (source, _) -> { 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(ClientCommands.literal("stop") .executes(runWithPack(packManager, (source, _) -> { 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(ClientCommands.literal("finish") .executes(context -> { - context.getSource().sendFeedback(Component.translatable("commands.rainbow.pack_finishing")); - 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 c05574b..72beb62 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.ItemStackTemplate; import java.util.Collection; @@ -11,4 +12,6 @@ public interface CustomItemProvider { Collection 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 df8c37a..1a26544 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.ItemStackTemplate; import java.util.Collection; @@ -23,4 +24,9 @@ public Collection nextItems(LocalPlayer player, ClientPacketL 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 4b35a14..585aa4a 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.ItemStackTemplate; @@ -53,4 +54,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 2ab2cc3..3819eea 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 @@ -8,6 +8,7 @@ import net.minecraft.world.item.ItemStackTemplate; import net.minecraft.world.item.component.ResolvableProfile; import org.geysermc.rainbow.client.PackManager; +import org.geysermc.rainbow.client.screen.ManagePackScreen; import org.geysermc.rainbow.pack.BedrockPack; import org.jspecify.annotations.Nullable; @@ -22,11 +23,20 @@ public PackMapper(PackManager packManager) { this.packManager = packManager; } + public CustomItemProvider getItemProvider() { + if (this.itemProvider == null) return NoItemProvider.INSTANCE; + return this.itemProvider; + } + public void setItemProvider(@Nullable 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()); @@ -37,13 +47,13 @@ public void tick(Minecraft minecraft) { player.sendSystemMessage(Component.translatable("chat.rainbow.mapped_items", results.itemsMapped)); } if (results.skullsMapped > 0) { - player.sendSystemMessage(Component.translatable("chat.rainbow.mapped_skulls", results.skullsMapped)); + player.sendSystemMessage(Component.translatable("feedback.rainbow.mapped_skulls", results.skullsMapped)); } if (results.problems > 0) { player.sendSystemMessage(Component.translatable("commands.rainbow.mapped_items_problems")); } if (itemProvider.isDone()) { - player.sendSystemMessage(Component.translatable("chat.rainbow.automatic_mapping_finished")); + player.sendSystemMessage(Component.translatable("feedback.rainbow.automatic_mapping_finished")); 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..5082833 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/screen/CreatePackScreen.java @@ -0,0 +1,60 @@ +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; + 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..af3e0e0 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/screen/ManagePackScreen.java @@ -0,0 +1,78 @@ +package org.geysermc.rainbow.client.screen; + +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.jspecify.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; + manager.runOrElse(pack -> { + this.packName = pack.resources().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(CustomItemProvider::name, 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) -> { + mapper.setItemProvider(value); + }), 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 new file mode 100644 index 0000000..e69de29 diff --git a/client/src/main/resources/rainbow-client.mixins.json b/client/src/main/resources/rainbow-client.mixins.json index cb9a1f8..8bdc0c6 100644 --- a/client/src/main/resources/rainbow-client.mixins.json +++ b/client/src/main/resources/rainbow-client.mixins.json @@ -1,20 +1,21 @@ { - "required": true, - "minVersion": "0.8", - "package": "org.geysermc.rainbow.client.mixin", - "compatibilityLevel": "JAVA_25", - "client": [ - "EntityRenderDispatcherAccessor", - "GuiItemRenderStateMixin", - "ModelManagerMixin", - "PictureInPictureRendererAccessor", - "PictureInPictureRendererMixin", - "SplashRendererAccessor", - "SpriteContentsAnimatedTextureAccessor", - "SpriteContentsClientAccessor" - ], - "injectors": { - "defaultRequire": 1 + "required": true, + "minVersion": "0.8", + "package": "org.geysermc.rainbow.client.mixin", + "compatibilityLevel": "JAVA_25", + "client": [ + "AbstractContainerScreenMixin", + "EntityRenderDispatcherAccessor", + "GuiItemRenderStateMixin", + "ModelManagerMixin", + "PictureInPictureRendererAccessor", + "PictureInPictureRendererMixin", + "SplashRendererAccessor", + "SpriteContentsAnimatedTextureAccessor", + "SpriteContentsClientAccessor" + ], + "injectors": { + "defaultRequire": 1 }, "mixins": [ "ResolvableProfileAccessor"