From 877f2268379f5e16caf0bb288fceac4460d7a53b Mon Sep 17 00:00:00 2001 From: pedroksl Date: Tue, 10 Sep 2024 03:26:54 -0300 Subject: [PATCH] Added quantum computer ui and added conditional recipes if mega is loaded --- gradle.properties | 2 +- .../net/pedroksl/advanced_ae/AAEConfig.java | 20 +- .../advanced_ae/client/AAEClient.java | 8 +- .../client/gui/QuantumComputerScreen.java | 25 ++ .../blocks/AAEAbstractCraftingUnitBlock.java | 7 +- .../common/blocks/AAECraftingUnitType.java | 2 +- .../common/definitions/AAEMenus.java | 5 + .../datagen/AAERecipeProvider.java | 11 +- .../quantumcomputer/AdvCpuSelectionList.java | 275 ++++++++++++++++++ .../gui/quantumcomputer/InfoBar.java | 167 +++++++++++ .../quantumcomputer/QuantumComputerMenu.java | 242 +++++++++++++++ .../mixins/cpu/MixinCraftingCPUMenu.java | 2 + .../net/pedroksl/advanced_ae/xmod/Addons.java | 5 + .../assets/ae2/screens/quantum_computer.json | 4 + 14 files changed, 760 insertions(+), 15 deletions(-) create mode 100644 src/main/java/net/pedroksl/advanced_ae/client/gui/QuantumComputerScreen.java create mode 100644 src/main/java/net/pedroksl/advanced_ae/gui/quantumcomputer/AdvCpuSelectionList.java create mode 100644 src/main/java/net/pedroksl/advanced_ae/gui/quantumcomputer/InfoBar.java create mode 100644 src/main/java/net/pedroksl/advanced_ae/gui/quantumcomputer/QuantumComputerMenu.java create mode 100644 src/main/resources/assets/ae2/screens/quantum_computer.json diff --git a/gradle.properties b/gradle.properties index 7e8322a9..6026ebe1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ loader_version_range=[4,) mod_id=advanced_ae mod_name=Advanced AE mod_license=LGPL-3.0 -mod_version=0.4.2-1.21.1 +mod_version=0.4.3-1.21.1 mod_group_id=net.pedroksl.advanced_ae mod_authors=Pedroksl mod_description=This mod aims to expand on the added capabilities of Extended AE. diff --git a/src/main/java/net/pedroksl/advanced_ae/AAEConfig.java b/src/main/java/net/pedroksl/advanced_ae/AAEConfig.java index 2b9e8e84..3c3136b3 100644 --- a/src/main/java/net/pedroksl/advanced_ae/AAEConfig.java +++ b/src/main/java/net/pedroksl/advanced_ae/AAEConfig.java @@ -20,12 +20,16 @@ public int getQuantumComputerMaxSize() { return common.quantumComputerMaxSize.get(); } + public int getQuantumComputerAcceleratorThreads() { + return common.quantumComputerAcceleratorThreads.get(); + } + public int getQuantumComputerMaxMultiThreaders() { return common.quantumComputerMaxMultiThreaders.get(); } public int getQuantumComputermaxDataEntanglers() { - return common.quantumComputermaxDataEntanglers.get(); + return common.quantumComputerMaxDataEntanglers.get(); } public int getQuantumComputerMultiThreaderMultiplication() { @@ -66,8 +70,9 @@ private static class CommonConfig { private final ModConfigSpec spec; public final ModConfigSpec.IntValue quantumComputerMaxSize; + public final ModConfigSpec.IntValue quantumComputerAcceleratorThreads; public final ModConfigSpec.IntValue quantumComputerMaxMultiThreaders; - public final ModConfigSpec.IntValue quantumComputermaxDataEntanglers; + public final ModConfigSpec.IntValue quantumComputerMaxDataEntanglers; public final ModConfigSpec.IntValue quantumComputerMultiThreaderMultiplication; public final ModConfigSpec.IntValue quantumComputerDataEntanglerMultiplication; @@ -80,8 +85,15 @@ public CommonConfig() { "quantumComputerMaxSize", 5, 5, - 16, + 12, "Define the maximum dimensions of the Quantum Computer Multiblock."); + quantumComputerAcceleratorThreads = define( + builder, + "quantumComputerAcceleratorThreads", + 8, + 4, + 16, + "Define the maximum amount of multi threaders per Quantum Computer Multiblock."); quantumComputerMaxMultiThreaders = define( builder, "quantumComputerMaxMultiThreaders", @@ -89,7 +101,7 @@ public CommonConfig() { 0, 2, "Define the maximum amount of multi threaders per Quantum Computer Multiblock."); - quantumComputermaxDataEntanglers = define( + quantumComputerMaxDataEntanglers = define( builder, "quantumComputermaxDataEntanglers", 1, diff --git a/src/main/java/net/pedroksl/advanced_ae/client/AAEClient.java b/src/main/java/net/pedroksl/advanced_ae/client/AAEClient.java index d7cf8027..c5f6f045 100644 --- a/src/main/java/net/pedroksl/advanced_ae/client/AAEClient.java +++ b/src/main/java/net/pedroksl/advanced_ae/client/AAEClient.java @@ -10,10 +10,7 @@ import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent; import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent; import net.pedroksl.advanced_ae.AdvancedAE; -import net.pedroksl.advanced_ae.client.gui.AdvPatternEncoderScreen; -import net.pedroksl.advanced_ae.client.gui.AdvPatternProviderScreen; -import net.pedroksl.advanced_ae.client.gui.ReactionChamberScreen; -import net.pedroksl.advanced_ae.client.gui.SmallAdvPatternProviderScreen; +import net.pedroksl.advanced_ae.client.gui.*; import net.pedroksl.advanced_ae.client.gui.config.OutputDirectionScreen; import net.pedroksl.advanced_ae.client.renderer.AAECraftingUnitModelProvider; import net.pedroksl.advanced_ae.client.renderer.ReactionChamberTESR; @@ -36,6 +33,9 @@ public AAEClient(IEventBus eventBus) { } private static void initScreens(RegisterMenuScreensEvent event) { + InitScreens.register( + event, AAEMenus.QUANTUM_COMPUTER, QuantumComputerScreen::new, "/screens/quantum_computer.json"); + InitScreens.register( event, AAEMenus.ADV_PATTERN_PROVIDER, diff --git a/src/main/java/net/pedroksl/advanced_ae/client/gui/QuantumComputerScreen.java b/src/main/java/net/pedroksl/advanced_ae/client/gui/QuantumComputerScreen.java new file mode 100644 index 00000000..ba1e99c5 --- /dev/null +++ b/src/main/java/net/pedroksl/advanced_ae/client/gui/QuantumComputerScreen.java @@ -0,0 +1,25 @@ +package net.pedroksl.advanced_ae.client.gui; + +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +import net.pedroksl.advanced_ae.gui.quantumcomputer.AdvCpuSelectionList; +import net.pedroksl.advanced_ae.gui.quantumcomputer.QuantumComputerMenu; + +import appeng.client.gui.me.crafting.CraftingCPUScreen; +import appeng.client.gui.style.ScreenStyle; +import appeng.client.gui.widgets.Scrollbar; + +public class QuantumComputerScreen extends CraftingCPUScreen { + public QuantumComputerScreen( + QuantumComputerMenu menu, Inventory playerInventory, Component title, ScreenStyle style) { + super(menu, playerInventory, title, style); + + var scrollbar = widgets.addScrollBar("selectCpuScrollbar", Scrollbar.BIG); + widgets.add("selectCpuList", new AdvCpuSelectionList(menu, scrollbar, style)); + } + + @Override + protected Component getGuiDisplayName(Component in) { + return in; // the cpu name is on the button + } +} diff --git a/src/main/java/net/pedroksl/advanced_ae/common/blocks/AAEAbstractCraftingUnitBlock.java b/src/main/java/net/pedroksl/advanced_ae/common/blocks/AAEAbstractCraftingUnitBlock.java index 7cb544ba..cb27af2a 100644 --- a/src/main/java/net/pedroksl/advanced_ae/common/blocks/AAEAbstractCraftingUnitBlock.java +++ b/src/main/java/net/pedroksl/advanced_ae/common/blocks/AAEAbstractCraftingUnitBlock.java @@ -31,14 +31,13 @@ import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.level.block.state.properties.IntegerProperty; import net.minecraft.world.phys.BlockHitResult; +import net.pedroksl.advanced_ae.common.definitions.AAEMenus; import net.pedroksl.advanced_ae.common.entities.AdvCraftingBlockEntity; import appeng.block.AEBaseEntityBlock; import appeng.block.crafting.ICraftingUnitType; -import appeng.blockentity.crafting.CraftingBlockEntity; import appeng.menu.MenuOpener; import appeng.menu.locator.MenuLocators; -import appeng.menu.me.crafting.CraftingCPUMenu; public abstract class AAEAbstractCraftingUnitBlock extends AEBaseEntityBlock { public static final BooleanProperty FORMED = BooleanProperty.create("formed"); @@ -105,9 +104,9 @@ public void onRemove(BlockState state, Level level, BlockPos pos, BlockState new @Override protected InteractionResult useWithoutItem( BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { - if (level.getBlockEntity(pos) instanceof CraftingBlockEntity be && be.isFormed() && be.isActive()) { + if (level.getBlockEntity(pos) instanceof AdvCraftingBlockEntity be && be.isFormed() && be.isActive()) { if (!level.isClientSide()) { - MenuOpener.open(CraftingCPUMenu.TYPE, player, MenuLocators.forBlockEntity(be)); + MenuOpener.open(AAEMenus.QUANTUM_COMPUTER, player, MenuLocators.forBlockEntity(be)); } return InteractionResult.sidedSuccess(level.isClientSide()); diff --git a/src/main/java/net/pedroksl/advanced_ae/common/blocks/AAECraftingUnitType.java b/src/main/java/net/pedroksl/advanced_ae/common/blocks/AAECraftingUnitType.java index 77bda5a4..e4f153c9 100644 --- a/src/main/java/net/pedroksl/advanced_ae/common/blocks/AAECraftingUnitType.java +++ b/src/main/java/net/pedroksl/advanced_ae/common/blocks/AAECraftingUnitType.java @@ -37,7 +37,7 @@ public int getStorageMultiplier() { @Override public int getAcceleratorThreads() { return switch (this) { - case QUANTUM_ACCELERATOR, QUANTUM_CORE -> 8; + case QUANTUM_ACCELERATOR, QUANTUM_CORE -> AAEConfig.instance().getQuantumComputerAcceleratorThreads(); default -> 0; }; } diff --git a/src/main/java/net/pedroksl/advanced_ae/common/definitions/AAEMenus.java b/src/main/java/net/pedroksl/advanced_ae/common/definitions/AAEMenus.java index aea27820..4530f079 100644 --- a/src/main/java/net/pedroksl/advanced_ae/common/definitions/AAEMenus.java +++ b/src/main/java/net/pedroksl/advanced_ae/common/definitions/AAEMenus.java @@ -4,6 +4,7 @@ import net.minecraft.world.inventory.MenuType; import net.neoforged.neoforge.registries.DeferredRegister; import net.pedroksl.advanced_ae.AdvancedAE; +import net.pedroksl.advanced_ae.common.entities.AdvCraftingBlockEntity; import net.pedroksl.advanced_ae.common.entities.ReactionChamberEntity; import net.pedroksl.advanced_ae.common.inventory.AdvPatternEncoderHost; import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogicHost; @@ -11,6 +12,7 @@ import net.pedroksl.advanced_ae.gui.advpatternprovider.SmallAdvPatternProviderMenu; import net.pedroksl.advanced_ae.gui.config.OutputDirectionMenu; import net.pedroksl.advanced_ae.gui.patternencoder.AdvPatternEncoderContainer; +import net.pedroksl.advanced_ae.gui.quantumcomputer.QuantumComputerMenu; import net.pedroksl.advanced_ae.gui.reactionchamber.ReactionChamberMenu; import appeng.api.storage.ISubMenuHost; @@ -20,6 +22,9 @@ public class AAEMenus { public static final DeferredRegister> DR = DeferredRegister.create(Registries.MENU, AdvancedAE.MOD_ID); + public static final MenuType QUANTUM_COMPUTER = + create("quantum_computer", QuantumComputerMenu::new, AdvCraftingBlockEntity.class); + public static final MenuType ADV_PATTERN_PROVIDER = create("adv_pattern_provider", AdvPatternProviderMenu::new, AdvPatternProviderLogicHost.class); public static final MenuType SMALL_ADV_PATTERN_PROVIDER = diff --git a/src/main/java/net/pedroksl/advanced_ae/datagen/AAERecipeProvider.java b/src/main/java/net/pedroksl/advanced_ae/datagen/AAERecipeProvider.java index ea4c870c..d2748a06 100644 --- a/src/main/java/net/pedroksl/advanced_ae/datagen/AAERecipeProvider.java +++ b/src/main/java/net/pedroksl/advanced_ae/datagen/AAERecipeProvider.java @@ -143,6 +143,15 @@ protected void buildRecipes(@NotNull RecipeOutput c) { .define('U', AAEBlocks.QUANTUM_UNIT) .unlockedBy("hasItem", has(AEItems.SINGULARITY)) .save(c, AdvancedAE.makeId("quantumaccel")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, AAEBlocks.QUANTUM_STORAGE_128M) + .pattern("ECE") + .pattern("CUC") + .pattern("ECE") + .define('E', AAEItems.SHATTERED_SINGULARITY) + .define('C', MEGAItems.BULK_CELL_COMPONENT) + .define('U', AAEBlocks.QUANTUM_UNIT) + .unlockedBy("hasItem", has(AEItems.SINGULARITY)) + .save(Addons.MEGACELLS.conditionalRecipe(c), AdvancedAE.makeId("megaquantumstorage128")); ShapedRecipeBuilder.shaped(RecipeCategory.MISC, AAEBlocks.QUANTUM_STORAGE_128M) .pattern("ECE") .pattern("CUC") @@ -151,7 +160,7 @@ protected void buildRecipes(@NotNull RecipeOutput c) { .define('C', AEItems.CELL_COMPONENT_256K) .define('U', AAEBlocks.QUANTUM_UNIT) .unlockedBy("hasItem", has(AEItems.SINGULARITY)) - .save(c, AdvancedAE.makeId("quantumstorage128")); + .save(Addons.MEGACELLS.notConditionalRecipe(c), AdvancedAE.makeId("quantumstorage128")); ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, AAEBlocks.QUANTUM_STORAGE_256M) .requires(AAEItems.SHATTERED_SINGULARITY) .requires(AAEBlocks.QUANTUM_STORAGE_128M) diff --git a/src/main/java/net/pedroksl/advanced_ae/gui/quantumcomputer/AdvCpuSelectionList.java b/src/main/java/net/pedroksl/advanced_ae/gui/quantumcomputer/AdvCpuSelectionList.java new file mode 100644 index 00000000..d1945234 --- /dev/null +++ b/src/main/java/net/pedroksl/advanced_ae/gui/quantumcomputer/AdvCpuSelectionList.java @@ -0,0 +1,275 @@ +package net.pedroksl.advanced_ae.gui.quantumcomputer; + +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.Rect2i; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; + +import appeng.api.stacks.AmountFormat; +import appeng.client.Point; +import appeng.client.gui.ICompositeWidget; +import appeng.client.gui.Icon; +import appeng.client.gui.Tooltip; +import appeng.client.gui.style.Blitter; +import appeng.client.gui.style.Color; +import appeng.client.gui.style.PaletteColor; +import appeng.client.gui.style.ScreenStyle; +import appeng.client.gui.widgets.Scrollbar; +import appeng.core.localization.ButtonToolTips; +import appeng.core.localization.GuiText; +import appeng.core.localization.Tooltips; +import appeng.menu.me.crafting.CraftingStatusMenu; + +public class AdvCpuSelectionList implements ICompositeWidget { + private static final int ROWS = 6; + + private final Blitter background; + private final Blitter buttonBg; + private final Blitter buttonBgSelected; + private final QuantumComputerMenu menu; + private final Color textColor; + private final int selectedColor; + private final Scrollbar scrollbar; + + // Relative to the origin of the current screen (not window) + private Rect2i bounds = new Rect2i(0, 0, 0, 0); + + public AdvCpuSelectionList(QuantumComputerMenu menu, Scrollbar scrollbar, ScreenStyle style) { + this.menu = menu; + this.scrollbar = scrollbar; + this.background = style.getImage("cpuList"); + this.buttonBg = style.getImage("cpuListButton"); + this.buttonBgSelected = style.getImage("cpuListButtonSelected"); + this.textColor = style.getColor(PaletteColor.DEFAULT_TEXT_COLOR); + this.selectedColor = style.getColor(PaletteColor.SELECTION_COLOR).toARGB(); + this.scrollbar.setCaptureMouseWheel(false); + } + + @Override + public void setPosition(Point position) { + this.bounds = new Rect2i(position.getX(), position.getY(), bounds.getWidth(), bounds.getHeight()); + } + + @Override + public void setSize(int width, int height) { + this.bounds = new Rect2i(bounds.getX(), bounds.getY(), width, height); + } + + @Override + public Rect2i getBounds() { + return bounds; + } + + @Override + public boolean onMouseWheel(Point mousePos, double delta) { + scrollbar.onMouseWheel(mousePos, delta); + return true; + } + + @Nullable + @Override + public Tooltip getTooltip(int mouseX, int mouseY) { + var cpu = hitTestCpu(new Point(mouseX, mouseY)); + if (cpu != null) { + var tooltipLines = new ArrayList(); + tooltipLines.add(getCpuName(cpu)); + + // Show the number of coprocessors if any are installed + var coProcessors = cpu.coProcessors(); + if (coProcessors == 1) { + tooltipLines.add(ButtonToolTips.CpuStatusCoProcessor.text(Tooltips.ofNumber(coProcessors)) + .withStyle(ChatFormatting.GRAY)); + } else if (coProcessors > 1) { + tooltipLines.add(ButtonToolTips.CpuStatusCoProcessors.text(Tooltips.ofNumber(coProcessors)) + .withStyle(ChatFormatting.GRAY)); + } + + // Show the amount of storage in the Crafting CPU + tooltipLines.add(ButtonToolTips.CpuStatusStorage.text(Tooltips.ofBytes(cpu.storage())) + // Vanilla text formatting is broken and inherits the color of the 1st placeholder in the text + .withStyle(ChatFormatting.GRAY)); + + // Show if the CPU is player or automation only + var modeText = + switch (cpu.mode()) { + case PLAYER_ONLY -> ButtonToolTips.CpuSelectionModePlayersOnly.text(); + case MACHINE_ONLY -> ButtonToolTips.CpuSelectionModeAutomationOnly.text(); + default -> null; + }; + if (modeText != null) { + tooltipLines.add(modeText); + } + + // Show info on the currently executing job + var currentJob = cpu.currentJob(); + if (currentJob != null) { + tooltipLines.add(ButtonToolTips.CpuStatusCrafting.text(Tooltips.ofAmount(currentJob)) + .append(" ") + .append(currentJob.what().getDisplayName())); + tooltipLines.add(ButtonToolTips.CpuStatusCraftedIn.text( + Tooltips.ofNumber(cpu.progress()), + Tooltips.ofDuration(cpu.elapsedTimeNanos(), TimeUnit.NANOSECONDS))); + } + return new Tooltip(tooltipLines); + } + return null; + } + + @Override + public boolean onMouseUp(Point mousePos, int button) { + var cpu = hitTestCpu(mousePos); + if (cpu != null) { + menu.selectCpu(cpu.serial()); + return true; + } + + return false; + } + + @Nullable + private CraftingStatusMenu.CraftingCpuListEntry hitTestCpu(Point mousePos) { + var relX = mousePos.getX() - bounds.getX(); + var relY = mousePos.getY() - bounds.getY(); + relX -= 8; + if (relX < 0 || relX >= buttonBg.getSrcWidth()) { + return null; + } + + relY -= 19; + var buttonIdx = scrollbar.getCurrentScroll() + relY / (buttonBg.getSrcHeight() + 1); + if (relY % (buttonBg.getSrcHeight() + 1) == buttonBg.getSrcHeight()) { + // Clicked right between two buttons + return null; + } + if (relY < 0 || buttonIdx >= menu.cpuList.cpus().size()) { + // Clicked above first or below last button + return null; + } + + var cpus = menu.cpuList.cpus(); + if (buttonIdx >= 0 && buttonIdx < cpus.size()) { + return cpus.get(buttonIdx); + } + + return null; + } + + @Override + public void updateBeforeRender() { + var hiddenRows = Math.max(0, menu.cpuList.cpus().size() - ROWS); + scrollbar.setRange(0, hiddenRows, ROWS / 3); + } + + @Override + public void drawBackgroundLayer(GuiGraphics guiGraphics, Rect2i bounds, Point mouse) { + var x = bounds.getX() + this.bounds.getX(); + var y = bounds.getY() + this.bounds.getY(); + background.dest(x, y, this.bounds.getWidth(), this.bounds.getHeight()).blit(guiGraphics); + + // Move to first button + x += 8; + y += 19; + + var pose = guiGraphics.pose(); + + var font = Minecraft.getInstance().font; + var cpus = menu.cpuList + .cpus() + .subList( + Mth.clamp( + scrollbar.getCurrentScroll(), + 0, + menu.cpuList.cpus().size()), + Mth.clamp( + scrollbar.getCurrentScroll() + ROWS, + 0, + menu.cpuList.cpus().size())); + for (var cpu : cpus) { + if (cpu.serial() == menu.getSelectedCpuSerial()) { + buttonBgSelected.dest(x, y).blit(guiGraphics); + } else { + buttonBg.dest(x, y).blit(guiGraphics); + } + + var name = getCpuName(cpu); + pose.pushPose(); + pose.translate(x + 3, y + 2, 0); + pose.scale(0.666f, 0.666f, 1); + guiGraphics.drawString(font, name, 0, 0, textColor.toARGB(), false); + pose.popPose(); + + var infoBar = new InfoBar(); + + var currentJob = cpu.currentJob(); + if (currentJob != null) { + // Show what was initially requested + infoBar.add(Icon.S_CRAFT, 1f, x + 2, y + 9); + var craftAmt = currentJob.what().formatAmount(currentJob.amount(), AmountFormat.SLOT); + infoBar.add(craftAmt, textColor.toARGB(), 0.666f, x + 14, y + 13); + infoBar.add(currentJob.what(), 0.666f, x + 55, y + 9); + + // Draw a bar at the bottom of the button to indicate job progress + var progress = (int) (cpu.progress() * (buttonBg.getSrcWidth() - 1) / Math.max(1, cpu.totalItems())); + guiGraphics.pose().pushPose(); + guiGraphics.pose().translate(1, -1, 0); + guiGraphics.fill( + x, + y + buttonBg.getSrcHeight() - 2, + x + progress, + y + buttonBg.getSrcHeight() - 1, + menu.getSelectedCpuSerial() == cpu.serial() ? 0xFF7da9d2 : (selectedColor)); + guiGraphics.pose().popPose(); + + } else { + infoBar.add(Icon.S_STORAGE, 1f, x + 32, y + 9); + + String storageAmount = formatStorage(cpu); + infoBar.add(storageAmount, textColor.toARGB(), 0.666f, x + 44, y + 13); + + if (cpu.coProcessors() > 0) { + infoBar.add(Icon.S_PROCESSOR, 1f, x + 2, y + 9); + String coProcessorCount = String.valueOf(cpu.coProcessors()); + infoBar.add(coProcessorCount, textColor.toARGB(), 0.666f, x + 14, y + 13); + } + + switch (cpu.mode()) { + case PLAYER_ONLY -> infoBar.add(Icon.S_TERMINAL, 1f, x + 55, y + 9); + case MACHINE_ONLY -> infoBar.add(Icon.S_MACHINE, 1f, x + 55, y + 9); + } + } + + infoBar.render(guiGraphics, x + 2, y + buttonBg.getSrcHeight() - 12); + + y += buttonBg.getSrcHeight() + 1; + } + } + + private String formatStorage(CraftingStatusMenu.CraftingCpuListEntry cpu) { + var storage = cpu.storage(); + var unit = -1; + + while (storage > 1024) { + storage /= 1024; + unit++; + } + + return storage + + switch (unit) { + case 0 -> "k"; + case 1 -> "M"; + case 2 -> "G"; + default -> "T"; + }; + } + + private Component getCpuName(CraftingStatusMenu.CraftingCpuListEntry cpu) { + return cpu.name() != null ? cpu.name() : GuiText.CPUs.text().append(String.format(" #%d", cpu.serial())); + } +} diff --git a/src/main/java/net/pedroksl/advanced_ae/gui/quantumcomputer/InfoBar.java b/src/main/java/net/pedroksl/advanced_ae/gui/quantumcomputer/InfoBar.java new file mode 100644 index 00000000..2524b732 --- /dev/null +++ b/src/main/java/net/pedroksl/advanced_ae/gui/quantumcomputer/InfoBar.java @@ -0,0 +1,167 @@ +package net.pedroksl.advanced_ae.gui.quantumcomputer; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.minecraft.world.level.ItemLike; + +import appeng.api.client.AEKeyRendering; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.AEKey; +import appeng.client.gui.Icon; + +public class InfoBar { + private final List widgets = new ArrayList<>(); + + public void render(GuiGraphics guiGraphics, int x, int y) { + var maxHeight = widgets.stream().mapToInt(Widget::getHeight).max().orElse(0); + + for (var widget : widgets) { + widget.render(guiGraphics, x, Math.round(y + maxHeight / 2.f - widget.getHeight() / 2.f)); + x += widget.getWidth(); + } + } + + interface Widget { + int getWidth(); + + int getHeight(); + + void render(GuiGraphics guiGraphics, int x, int y); + } + + // TODO (RID): Added xPos and yPos to give me better control over render, but the code below might need refactoring + void add(Icon icon, float scale, int xPos, int yPos) { + widgets.add(new IconWidget(icon, scale, xPos, yPos)); + } + + void add(String text, int color, float scale, int xPos, int yPos) { + widgets.add(new TextWidget(Component.literal(text), color, scale, xPos, yPos)); + } + + void add(Component text, int color, float scale, int xPos, int yPos) { + widgets.add(new TextWidget(text, color, scale, xPos, yPos)); + } + + void add(AEKey what, float scale, int xPos, int yPos) { + widgets.add(new StackWidget(what, scale, xPos, yPos)); + } + + void add(ItemLike what, float scale, int xPos, int yPos) { + widgets.add(new StackWidget(AEItemKey.of(what), scale, xPos, yPos)); + } + + void addSpace(int width) { + widgets.add(new SpaceWidget(width)); + } + + private record StackWidget(AEKey what, float scale, int xPos, int yPos) implements Widget { + @Override + public int getWidth() { + return Math.round(16 * scale); + } + + @Override + public int getHeight() { + return Math.round(16 * scale); + } + + @Override + public void render(GuiGraphics guiGraphics, int x, int y) { + var poseStack = guiGraphics.pose(); + poseStack.pushPose(); + poseStack.translate(xPos, yPos, 0); + poseStack.scale(scale, scale, 1); + AEKeyRendering.drawInGui(Minecraft.getInstance(), guiGraphics, 0, 0, what); + poseStack.popPose(); + } + } + + private record IconWidget(Icon icon, float scale, int xPos, int yPos) implements Widget { + @Override + public int getWidth() { + return Math.round(16 * scale); + } + + @Override + public int getHeight() { + return Math.round(16 * scale); + } + + @Override + public void render(GuiGraphics guiGraphics, int x, int y) { + var poseStack = guiGraphics.pose(); + poseStack.pushPose(); + poseStack.translate(xPos, yPos, 0); + poseStack.scale(scale, scale, 1); + icon.getBlitter().dest(0, 0).blit(guiGraphics); + poseStack.popPose(); + } + } + + private static final class TextWidget implements Widget { + private final Component text; + private final int color; + private final float scale; + private final int xPos; + private final int yPos; + private final int width; + private final int height; + + public TextWidget(Component text, int color, float scale, int xPos, int yPos) { + this.text = text; + this.color = color; + this.scale = scale; + this.xPos = xPos; + this.yPos = yPos; + var font = Minecraft.getInstance().font; + this.width = Math.round(font.width(text) * scale); + this.height = Math.round(font.lineHeight * scale); + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public void render(GuiGraphics guiGraphics, int x, int y) { + var poseStack = guiGraphics.pose(); + var font = Minecraft.getInstance().font; + poseStack.pushPose(); + poseStack.translate(xPos, yPos, 0); + poseStack.scale(scale, scale, 1); + guiGraphics.drawString(font, text, 0, 0, color, false); + poseStack.popPose(); + } + } + + private static final class SpaceWidget implements Widget { + private final int width; + + public SpaceWidget(int width) { + this.width = width; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return 0; + } + + @Override + public void render(GuiGraphics guiGraphics, int x, int y) {} + } +} diff --git a/src/main/java/net/pedroksl/advanced_ae/gui/quantumcomputer/QuantumComputerMenu.java b/src/main/java/net/pedroksl/advanced_ae/gui/quantumcomputer/QuantumComputerMenu.java new file mode 100644 index 00000000..621d608d --- /dev/null +++ b/src/main/java/net/pedroksl/advanced_ae/gui/quantumcomputer/QuantumComputerMenu.java @@ -0,0 +1,242 @@ +package net.pedroksl.advanced_ae.gui.quantumcomputer; + +import java.util.*; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.MenuType; +import net.pedroksl.advanced_ae.common.cluster.AdvCraftingCPU; +import net.pedroksl.advanced_ae.common.definitions.AAEMenus; +import net.pedroksl.advanced_ae.common.entities.AdvCraftingBlockEntity; + +import appeng.api.config.CpuSelectionMode; +import appeng.api.networking.crafting.ICraftingCPU; +import appeng.api.stacks.GenericStack; +import appeng.menu.guisync.GuiSync; +import appeng.menu.guisync.PacketWritable; +import appeng.menu.me.crafting.CraftingCPUMenu; +import appeng.menu.me.crafting.CraftingStatusMenu; + +public class QuantumComputerMenu extends CraftingCPUMenu { + + private static final String ACTION_SELECT_CPU = "selectCpu"; + + private WeakHashMap cpuSerialMap = new WeakHashMap<>(); + + private int nextCpuSerial = 1; + + private List lastCpuSet = List.of(); + + private int lastUpdate = 0; + + @GuiSync(8) + public CraftingStatusMenu.CraftingCpuList cpuList = EMPTY_CPU_LIST; + + // This is server-side + @Nullable + private ICraftingCPU selectedCpu = null; + + @GuiSync(9) + private int selectedCpuSerial = -1; + + private final AdvCraftingBlockEntity host; + + public QuantumComputerMenu(int id, Inventory ip, AdvCraftingBlockEntity te) { + this(AAEMenus.QUANTUM_COMPUTER, id, ip, te); + } + + public QuantumComputerMenu(MenuType menuType, int id, Inventory ip, AdvCraftingBlockEntity te) { + super(menuType, id, ip, te); + this.cpuList = EMPTY_CPU_LIST; + this.selectedCpu = null; + this.selectedCpuSerial = -1; + this.host = te; + this.registerClientAction("selectCpu", Integer.class, this::selectCpu); + } + + private static final CraftingStatusMenu.CraftingCpuList EMPTY_CPU_LIST = + new CraftingStatusMenu.CraftingCpuList(Collections.emptyList()); + + private static final Comparator CPU_COMPARATOR = Comparator.comparing( + (CraftingStatusMenu.CraftingCpuListEntry e) -> e.name() == null) + .thenComparing(e -> e.name() != null ? e.name().getString() : "") + .thenComparingInt(CraftingStatusMenu.CraftingCpuListEntry::serial); + + @Override + protected void setCPU(ICraftingCPU c) { + super.setCPU(c); + this.selectedCpuSerial = getOrAssignCpuSerial(c); + } + + @Override + public void broadcastChanges() { + if (this.host.getGridNode() == null) { + super.broadcastChanges(); + return; + } + + if (isServerSide() && this.host.getCluster() != null) { + List newCpuSet = this.host.getCluster().getActiveCPUs(); + newCpuSet.add(this.host.getCluster().getRemainingCapacityCPU()); + if (!lastCpuSet.equals(newCpuSet) + // Always try to update once every second to show job progress + || ++lastUpdate >= 20) { + lastCpuSet = newCpuSet; + cpuList = createCpuList(); + } + } else { + lastUpdate = 20; + if (!lastCpuSet.isEmpty()) { + cpuList = EMPTY_CPU_LIST; + lastCpuSet = List.of(); + } + } + + // Clear selection if CPU is no longer in list + if (selectedCpuSerial != -1) { + if (cpuList.cpus().stream().noneMatch(c -> c.serial() == selectedCpuSerial)) { + selectCpu(-1); + } + } + + // Select a suitable CPU if none is selected + if (selectedCpuSerial == -1) { + // Try busy CPUs first + for (var cpu : cpuList.cpus()) { + if (cpu.currentJob() != null) { + selectCpu(cpu.serial()); + break; + } + } + // If we couldn't find a busy one, just select the first + if (selectedCpuSerial == -1 && !cpuList.cpus().isEmpty()) { + selectCpu(cpuList.cpus().get(0).serial()); + } + } + + super.broadcastChanges(); + } + + private CraftingStatusMenu.CraftingCpuList createCpuList() { + var entries = new ArrayList(lastCpuSet.size()); + for (var cpu : lastCpuSet) { + var serial = getOrAssignCpuSerial(cpu); + var status = cpu.getJobStatus(); + entries.add(new CraftingStatusMenu.CraftingCpuListEntry( + serial, + cpu.getAvailableStorage(), + cpu.getCoProcessors(), + cpu.getName(), + cpu.getSelectionMode(), + status != null ? status.crafting() : null, + status != null ? status.totalItems() : 0, + status != null ? status.progress() : 0, + status != null ? status.elapsedTimeNanos() : 0)); + } + entries.sort(CPU_COMPARATOR); + return new CraftingStatusMenu.CraftingCpuList(entries); + } + + private int getOrAssignCpuSerial(ICraftingCPU cpu) { + if (this.cpuSerialMap == null) { + this.cpuSerialMap = new WeakHashMap<>(); + } + return cpuSerialMap.computeIfAbsent(cpu, ignored -> nextCpuSerial++); + } + + @Override + public boolean allowConfiguration() { + return false; + } + + public void selectCpu(int serial) { + if (isClientSide()) { + selectedCpuSerial = serial; + sendClientAction(ACTION_SELECT_CPU, serial); + } else { + ICraftingCPU newSelectedCpu = null; + if (serial != -1) { + for (var cpu : lastCpuSet) { + if (cpuSerialMap.getOrDefault(cpu, -1) == serial) { + newSelectedCpu = cpu; + break; + } + } + } + + if (newSelectedCpu != selectedCpu) { + setCPU(newSelectedCpu); + } + } + } + + public int getSelectedCpuSerial() { + return selectedCpuSerial; + } + + public record CraftingCpuList(List cpus) implements PacketWritable { + public CraftingCpuList(RegistryFriendlyByteBuf data) { + this(readFromPacket(data)); + } + + private static List readFromPacket(RegistryFriendlyByteBuf data) { + var count = data.readInt(); + var result = new ArrayList(count); + for (int i = 0; i < count; i++) { + result.add(CraftingStatusMenu.CraftingCpuListEntry.readFromPacket(data)); + } + return result; + } + + @Override + public void writeToPacket(RegistryFriendlyByteBuf data) { + data.writeInt(cpus.size()); + for (var entry : cpus) { + entry.writeToPacket(data); + } + } + } + + public record CraftingCpuListEntry( + int serial, + long storage, + int coProcessors, + Component name, + CpuSelectionMode mode, + GenericStack currentJob, + long totalItems, + long progress, + long elapsedTimeNanos) { + public static CraftingStatusMenu.CraftingCpuListEntry readFromPacket(RegistryFriendlyByteBuf data) { + return new CraftingStatusMenu.CraftingCpuListEntry( + data.readInt(), + data.readLong(), + data.readInt(), + data.readBoolean() ? ComponentSerialization.TRUSTED_STREAM_CODEC.decode(data) : null, + data.readEnum(CpuSelectionMode.class), + GenericStack.readBuffer(data), + data.readVarLong(), + data.readVarLong(), + data.readVarLong()); + } + + public void writeToPacket(RegistryFriendlyByteBuf data) { + data.writeInt(serial); + data.writeLong(storage); + data.writeInt(coProcessors); + data.writeBoolean(name != null); + if (name != null) { + ComponentSerialization.TRUSTED_STREAM_CODEC.encode(data, name); + } + data.writeEnum(mode); + GenericStack.writeBuffer(currentJob, data); + data.writeVarLong(totalItems); + data.writeVarLong(progress); + data.writeVarLong(elapsedTimeNanos); + } + } +} diff --git a/src/main/java/net/pedroksl/advanced_ae/mixins/cpu/MixinCraftingCPUMenu.java b/src/main/java/net/pedroksl/advanced_ae/mixins/cpu/MixinCraftingCPUMenu.java index 4122cbc0..28e3ba55 100644 --- a/src/main/java/net/pedroksl/advanced_ae/mixins/cpu/MixinCraftingCPUMenu.java +++ b/src/main/java/net/pedroksl/advanced_ae/mixins/cpu/MixinCraftingCPUMenu.java @@ -60,6 +60,8 @@ protected void setCPU(ICraftingCPU c) {} private void onInit(MenuType menuType, int id, Inventory ip, Object te, CallbackInfo ci) { if (te instanceof AdvCraftingBlockEntity advEntity) { var cluster = advEntity.getCluster(); + if (cluster == null) return; + var active = cluster.getActiveCPUs(); if (!active.isEmpty()) { this.setCPU(active.getFirst()); diff --git a/src/main/java/net/pedroksl/advanced_ae/xmod/Addons.java b/src/main/java/net/pedroksl/advanced_ae/xmod/Addons.java index 9bb53077..12e2f66a 100644 --- a/src/main/java/net/pedroksl/advanced_ae/xmod/Addons.java +++ b/src/main/java/net/pedroksl/advanced_ae/xmod/Addons.java @@ -5,6 +5,7 @@ import net.neoforged.fml.loading.LoadingModList; import net.neoforged.fml.loading.moddiscovery.ModInfo; import net.neoforged.neoforge.common.conditions.ModLoadedCondition; +import net.neoforged.neoforge.common.conditions.NotCondition; public enum Addons { APPFLUX("Applied Flux"), @@ -34,4 +35,8 @@ public boolean isLoaded() { public RecipeOutput conditionalRecipe(RecipeOutput output) { return output.withConditions(new ModLoadedCondition(getModId())); } + + public RecipeOutput notConditionalRecipe(RecipeOutput output) { + return output.withConditions(new NotCondition(new ModLoadedCondition(getModId()))); + } } diff --git a/src/main/resources/assets/ae2/screens/quantum_computer.json b/src/main/resources/assets/ae2/screens/quantum_computer.json new file mode 100644 index 00000000..2f394da9 --- /dev/null +++ b/src/main/resources/assets/ae2/screens/quantum_computer.json @@ -0,0 +1,4 @@ +{ + "$schema": "schema.json", + "includes": ["crafting_status.json"] +}