diff --git a/src/main/java/com/extendedae_plus/api/crafting/IForceCraftStartSync.java b/src/main/java/com/extendedae_plus/api/crafting/IForceCraftStartSync.java new file mode 100644 index 0000000..d624041 --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/crafting/IForceCraftStartSync.java @@ -0,0 +1,7 @@ +package com.extendedae_plus.api.crafting; + +public interface IForceCraftStartSync { + void eap$clientSetForceCraftStart(boolean forceStart); + + boolean eap$consumeForceCraftStartFlag(); +} diff --git a/src/main/java/com/extendedae_plus/api/crafting/IForcedCraftingPlan.java b/src/main/java/com/extendedae_plus/api/crafting/IForcedCraftingPlan.java new file mode 100644 index 0000000..92b6d88 --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/crafting/IForcedCraftingPlan.java @@ -0,0 +1,7 @@ +package com.extendedae_plus.api.crafting; + +import appeng.api.stacks.KeyCounter; + +public interface IForcedCraftingPlan { + KeyCounter eap$getManualMissingItems(); +} diff --git a/src/main/java/com/extendedae_plus/api/crafting/IManualCraftingState.java b/src/main/java/com/extendedae_plus/api/crafting/IManualCraftingState.java new file mode 100644 index 0000000..f8b88e4 --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/crafting/IManualCraftingState.java @@ -0,0 +1,14 @@ +package com.extendedae_plus.api.crafting; + +import appeng.api.stacks.AEKey; +import appeng.api.stacks.KeyCounter; + +import java.util.Map; + +public interface IManualCraftingState { + void eap$setManualWaiting(KeyCounter manualWaiting); + + long eap$getManualWaitingAmount(AEKey what); + + Map eap$getManualWaitingSnapshot(); +} diff --git a/src/main/java/com/extendedae_plus/content/ClientManualCraftingStatusStore.java b/src/main/java/com/extendedae_plus/content/ClientManualCraftingStatusStore.java new file mode 100644 index 0000000..bb59aa9 --- /dev/null +++ b/src/main/java/com/extendedae_plus/content/ClientManualCraftingStatusStore.java @@ -0,0 +1,32 @@ +package com.extendedae_plus.content; + +import appeng.api.stacks.AEKey; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public final class ClientManualCraftingStatusStore { + private static volatile int containerId = -1; + private static volatile Map manualWaiting = Collections.emptyMap(); + + private ClientManualCraftingStatusStore() { + } + + public static void setStatus(int menuContainerId, Map snapshot) { + containerId = menuContainerId; + manualWaiting = Collections.unmodifiableMap(new LinkedHashMap<>(snapshot)); + } + + public static long getManualWaitingAmount(int menuContainerId, AEKey key) { + if (key == null || menuContainerId != containerId) { + return 0; + } + return manualWaiting.getOrDefault(key, 0L); + } + + public static void clear() { + containerId = -1; + manualWaiting = Collections.emptyMap(); + } +} diff --git a/src/main/java/com/extendedae_plus/crafting/ForcedCraftingPlan.java b/src/main/java/com/extendedae_plus/crafting/ForcedCraftingPlan.java new file mode 100644 index 0000000..6ac2bae --- /dev/null +++ b/src/main/java/com/extendedae_plus/crafting/ForcedCraftingPlan.java @@ -0,0 +1,72 @@ +package com.extendedae_plus.crafting; + +import appeng.api.crafting.IPatternDetails; +import appeng.api.networking.crafting.ICraftingPlan; +import appeng.api.stacks.GenericStack; +import appeng.api.stacks.KeyCounter; +import com.extendedae_plus.api.crafting.IForcedCraftingPlan; + +import java.util.Map; + +public class ForcedCraftingPlan implements ICraftingPlan, IForcedCraftingPlan { + private final ICraftingPlan delegate; + private final KeyCounter manualMissingItems; + + public ForcedCraftingPlan(ICraftingPlan delegate) { + this.delegate = delegate; + this.manualMissingItems = copy(delegate.missingItems()); + } + + @Override + public KeyCounter eap$getManualMissingItems() { + return copy(this.manualMissingItems); + } + + @Override + public GenericStack finalOutput() { + return this.delegate.finalOutput(); + } + + @Override + public long bytes() { + return this.delegate.bytes(); + } + + @Override + public boolean simulation() { + return false; + } + + @Override + public boolean multiplePaths() { + return this.delegate.multiplePaths(); + } + + @Override + public KeyCounter usedItems() { + return this.delegate.usedItems(); + } + + @Override + public KeyCounter emittedItems() { + return this.delegate.emittedItems(); + } + + @Override + public KeyCounter missingItems() { + return new KeyCounter(); + } + + @Override + public Map patternTimes() { + return this.delegate.patternTimes(); + } + + private static KeyCounter copy(KeyCounter source) { + var copy = new KeyCounter(); + for (var entry : source) { + copy.add(entry.getKey(), entry.getLongValue()); + } + return copy; + } +} diff --git a/src/main/java/com/extendedae_plus/init/ModNetwork.java b/src/main/java/com/extendedae_plus/init/ModNetwork.java index 694efb9..4d79aa1 100644 --- a/src/main/java/com/extendedae_plus/init/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/init/ModNetwork.java @@ -2,6 +2,8 @@ package com.extendedae_plus.init; import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.network.*; +import com.extendedae_plus.network.crafting.ForceCraftStartFlagC2SPacket; +import com.extendedae_plus.network.crafting.ManualCraftingStatusS2CPacket; import com.extendedae_plus.network.packet.EAPConfigButtonPacket; import com.extendedae_plus.network.upload.EncodeWithShiftFlagC2SPacket; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; @@ -57,6 +59,12 @@ public class ModNetwork { registrar.playToClient(com.extendedae_plus.network.LabelNetworkListS2CPacket.TYPE, com.extendedae_plus.network.LabelNetworkListS2CPacket.STREAM_CODEC, com.extendedae_plus.network.LabelNetworkListS2CPacket::handle); + registrar.playToServer(ForceCraftStartFlagC2SPacket.TYPE, + ForceCraftStartFlagC2SPacket.STREAM_CODEC, + ForceCraftStartFlagC2SPacket::handle); + registrar.playToClient(ManualCraftingStatusS2CPacket.TYPE, + ManualCraftingStatusS2CPacket.STREAM_CODEC, + ManualCraftingStatusS2CPacket::handle); registrar.playToServer(EAPConfigButtonPacket.TYPE, EAPConfigButtonPacket.STREAM_CODEC, EAPConfigButtonPacket::handleOnServer); } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/ExecutingCraftingJobAccessor.java b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/ExecutingCraftingJobAccessor.java index b8212ea..da1ad2d 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/ExecutingCraftingJobAccessor.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/ExecutingCraftingJobAccessor.java @@ -1,6 +1,8 @@ package com.extendedae_plus.mixin.ae2.accessor; import appeng.api.crafting.IPatternDetails; +import appeng.api.stacks.GenericStack; +import appeng.crafting.CraftingLink; import appeng.crafting.execution.ExecutingCraftingJob; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; @@ -11,4 +13,16 @@ import java.util.Map; public interface ExecutingCraftingJobAccessor { @Accessor("tasks") Map eap$getTasks(); + + @Accessor("finalOutput") + GenericStack eap$getFinalOutput(); + + @Accessor("remainingAmount") + long eap$getRemainingAmount(); + + @Accessor("remainingAmount") + void eap$setRemainingAmount(long remainingAmount); + + @Accessor("link") + CraftingLink eap$getLink(); } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/CraftConfirmScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/CraftConfirmScreenMixin.java index b10d51d..6528ccd 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/CraftConfirmScreenMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/CraftConfirmScreenMixin.java @@ -3,14 +3,17 @@ package com.extendedae_plus.mixin.ae2.client.gui; import appeng.client.gui.WidgetContainer; import appeng.client.gui.me.crafting.CraftConfirmScreen; import appeng.core.localization.GuiText; +import appeng.menu.me.crafting.CraftingPlanSummary; import com.extendedae_plus.mixin.ae2.accessor.AEBaseScreenAccessor; import com.extendedae_plus.mixin.ae2.accessor.WidgetContainerAccessor; +import com.extendedae_plus.network.crafting.ForceCraftStartFlagC2SPacket; import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; import net.neoforged.fml.ModList; +import net.neoforged.neoforge.network.PacketDistributor; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -27,22 +30,48 @@ public class CraftConfirmScreenMixin { private static final Component EAP_BOOKMARK_TEXT = Component.translatable("gui.extendedae_plus.add_bookmark"); @Unique private static final Component EAP_BOOKMARK_TOOLTIP = Component.translatable("tooltip.extendedae_plus.add_missing_to_jei_bookmark"); + @Unique + private static final Component EAP_START_TEXT = GuiText.Start.text(); + @Unique + private static final Component EAP_FORCE_START_TEXT = Component.translatable("gui.extendedae_plus.force_start"); + @Unique + private static final Component EAP_FORCE_START_TOOLTIP = Component.translatable("tooltip.extendedae_plus.force_start"); @Inject(method = "updateBeforeRender", at = @At("TAIL"), remap = false) - private void eap$updateCancelButtonText(CallbackInfo ci) { - if (!ModList.get().isLoaded("jei")){ - return; - } - + private void eap$updateButtons(CallbackInfo ci) { CraftConfirmScreen self = (CraftConfirmScreen) (Object) this; try { WidgetContainer widgets = ((AEBaseScreenAccessor) self).eap$getWidgets(); if (widgets == null) return; - AbstractWidget cancelWidget = ((WidgetContainerAccessor) widgets).eap$getWidgetsMap().get("cancel"); - if (!(cancelWidget instanceof Button cancelButton)) return; - + var widgetsMap = ((WidgetContainerAccessor) widgets).eap$getWidgetsMap(); boolean shiftDown = Screen.hasShiftDown(); + CraftingPlanSummary plan = self.getMenu().getPlan(); + boolean forceStart = shiftDown && plan != null && plan.isSimulation(); + + AbstractWidget startWidget = widgetsMap.get("start"); + if (startWidget instanceof Button startButton) { + if (forceStart) { + startButton.active = !self.getMenu().hasNoCPU(); + startButton.setMessage(EAP_FORCE_START_TEXT); + startButton.setTooltip(Tooltip.create(EAP_FORCE_START_TOOLTIP)); + } else { + startButton.setMessage(EAP_START_TEXT); + startButton.setTooltip(null); + } + } + + AbstractWidget selectCpuWidget = widgetsMap.get("selectCpu"); + if (forceStart && selectCpuWidget instanceof Button selectCpuButton) { + selectCpuButton.active = true; + } + + if (!ModList.get().isLoaded("jei")) { + return; + } + + AbstractWidget cancelWidget = widgetsMap.get("cancel"); + if (!(cancelWidget instanceof Button cancelButton)) return; if (shiftDown) { cancelButton.setMessage(EAP_BOOKMARK_TEXT); @@ -54,4 +83,12 @@ public class CraftConfirmScreenMixin { } catch (Throwable ignored) { } } + + @Inject(method = "start", at = @At("HEAD"), remap = false) + private void eap$syncForceStartFlagBeforeStart(CallbackInfo ci) { + CraftConfirmScreen self = (CraftConfirmScreen) (Object) this; + var plan = self.getMenu().getPlan(); + boolean forceStart = Screen.hasShiftDown() && plan != null && plan.isSimulation(); + PacketDistributor.sendToServer(new ForceCraftStartFlagC2SPacket(forceStart)); + } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/CraftingStatusTableRendererMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/CraftingStatusTableRendererMixin.java new file mode 100644 index 0000000..12f0ffd --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/CraftingStatusTableRendererMixin.java @@ -0,0 +1,68 @@ +package com.extendedae_plus.mixin.ae2.client.gui; + +import appeng.api.stacks.AmountFormat; +import appeng.api.util.AEColor; +import appeng.client.gui.me.crafting.CraftingCPUScreen; +import appeng.client.gui.me.crafting.CraftingStatusTableRenderer; +import appeng.core.AEConfig; +import appeng.menu.me.crafting.CraftingStatusEntry; +import com.extendedae_plus.content.ClientManualCraftingStatusStore; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.ArrayList; +import java.util.List; + +@Mixin(value = CraftingStatusTableRenderer.class, remap = false) +public abstract class CraftingStatusTableRendererMixin { + @Unique + private static final int EAP_BACKGROUND_ALPHA = 0x5A000000; + + @Inject(method = "getEntryBackgroundColor", at = @At("RETURN"), cancellable = true) + private void eap$markManualWaitingEntries(CraftingStatusEntry entry, CallbackInfoReturnable cir) { + if (!AEConfig.instance().isUseColoredCraftingStatus()) { + return; + } + + if (this.eap$getManualWaitingAmount(entry) > 0) { + cir.setReturnValue(AEColor.PURPLE.blackVariant | EAP_BACKGROUND_ALPHA); + } + } + + @Inject(method = "getEntryTooltip", at = @At("RETURN"), cancellable = true) + private void eap$appendManualWaitingTooltip(CraftingStatusEntry entry, + CallbackInfoReturnable> cir) { + long manualWaiting = this.eap$getManualWaitingAmount(entry); + if (manualWaiting <= 0 || entry.getWhat() == null) { + return; + } + + List lines = new ArrayList<>(cir.getReturnValue()); + lines.add(Component.translatable( + "tooltip.extendedae_plus.crafting.manual_waiting", + entry.getWhat().formatAmount(manualWaiting, AmountFormat.FULL)).withStyle(ChatFormatting.AQUA)); + cir.setReturnValue(lines); + } + + @Unique + private long eap$getManualWaitingAmount(CraftingStatusEntry entry) { + if (entry == null || entry.getWhat() == null) { + return 0; + } + + var mc = Minecraft.getInstance(); + if (mc == null || !(mc.screen instanceof CraftingCPUScreen craftingScreen)) { + return 0; + } + + return ClientManualCraftingStatusStore.getManualWaitingAmount( + craftingScreen.getMenu().containerId, + entry.getWhat()); + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/crafting/CraftingCpuLogicManualWaitingMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/crafting/CraftingCpuLogicManualWaitingMixin.java new file mode 100644 index 0000000..b5eeaf8 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/crafting/CraftingCpuLogicManualWaitingMixin.java @@ -0,0 +1,171 @@ +package com.extendedae_plus.mixin.ae2.crafting; + +import appeng.api.config.Actionable; +import appeng.api.networking.IGrid; +import appeng.api.networking.crafting.ICraftingPlan; +import appeng.api.networking.crafting.ICraftingRequester; +import appeng.api.networking.crafting.ICraftingSubmitResult; +import appeng.api.networking.security.IActionSource; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.KeyCounter; +import appeng.crafting.execution.CraftingCpuLogic; +import appeng.crafting.execution.ExecutingCraftingJob; +import appeng.crafting.inv.ListCraftingInventory; +import appeng.me.cluster.implementations.CraftingCPUCluster; +import com.extendedae_plus.api.crafting.IForcedCraftingPlan; +import com.extendedae_plus.api.crafting.IManualCraftingState; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +@Mixin(value = CraftingCpuLogic.class, remap = false) +public abstract class CraftingCpuLogicManualWaitingMixin implements IManualCraftingState { + @Shadow + private CraftingCPUCluster cluster; + + @Shadow + private ExecutingCraftingJob job; + + @Shadow + private ListCraftingInventory inventory; + + @Shadow + protected abstract void postChange(AEKey what); + + @Unique + private final Map eap$manualWaitingFor = new LinkedHashMap<>(); + + @Override + public void eap$setManualWaiting(KeyCounter manualWaiting) { + this.eap$clearManualWaitingInternal(false); + for (var entry : manualWaiting) { + if (entry.getKey() != null && entry.getLongValue() > 0) { + this.eap$manualWaitingFor.put(entry.getKey(), entry.getLongValue()); + this.postChange(entry.getKey()); + } + } + if (!this.eap$manualWaitingFor.isEmpty()) { + this.cluster.markDirty(); + } + } + + @Override + public long eap$getManualWaitingAmount(AEKey what) { + if (what == null) { + return 0; + } + return this.eap$manualWaitingFor.getOrDefault(what, 0L); + } + + @Override + public Map eap$getManualWaitingSnapshot() { + return new LinkedHashMap<>(this.eap$manualWaitingFor); + } + + @Inject(method = "trySubmitJob", at = @At("RETURN")) + private void eap$initManualWaitingForForcedPlan(IGrid grid, ICraftingPlan plan, IActionSource src, + @Nullable ICraftingRequester requester, CallbackInfoReturnable cir) { + if (!cir.getReturnValue().successful()) { + return; + } + + if (plan instanceof IForcedCraftingPlan forcedPlan) { + this.eap$setManualWaiting(forcedPlan.eap$getManualMissingItems()); + } else { + this.eap$clearManualWaitingInternal(false); + } + } + + @Inject(method = "insert", at = @At("RETURN"), cancellable = true) + private void eap$consumeManualWaitingAfterVanilla(AEKey what, long amount, Actionable type, + CallbackInfoReturnable cir) { + long vanillaInserted = cir.getReturnValue(); + if (what == null || this.job == null) { + return; + } + + long remainingAmount = amount - vanillaInserted; + if (remainingAmount <= 0) { + return; + } + + long manualWaiting = this.eap$getManualWaitingAmount(what); + if (manualWaiting <= 0) { + return; + } + + long consumed = Math.min(remainingAmount, manualWaiting); + if (type == Actionable.MODULATE) { + this.eap$decreaseManualWaiting(what, consumed); + this.cluster.markDirty(); + this.postChange(what); + this.inventory.insert(what, consumed, Actionable.MODULATE); + } + + cir.setReturnValue(vanillaInserted + consumed); + } + + @Inject(method = "getWaitingFor", at = @At("RETURN"), cancellable = true) + private void eap$appendManualWaitingToStatus(AEKey template, CallbackInfoReturnable cir) { + if (template == null) { + return; + } + long manualWaiting = this.eap$getManualWaitingAmount(template); + if (manualWaiting > 0) { + cir.setReturnValue(cir.getReturnValue() + manualWaiting); + } + } + + @Inject(method = "getAllWaitingFor", at = @At("TAIL")) + private void eap$appendManualWaitingKeys(Set waitingFor, CallbackInfo ci) { + waitingFor.addAll(this.eap$manualWaitingFor.keySet()); + } + + @Inject(method = "getAllItems", at = @At("TAIL")) + private void eap$appendManualWaitingItems(KeyCounter out, CallbackInfo ci) { + for (var entry : this.eap$manualWaitingFor.entrySet()) { + out.add(entry.getKey(), entry.getValue()); + } + } + + @Inject(method = "finishJob", at = @At("HEAD")) + private void eap$clearManualWaitingOnFinish(boolean success, CallbackInfo ci) { + this.eap$clearManualWaitingInternal(true); + } + + @Unique + private void eap$decreaseManualWaiting(AEKey what, long amount) { + long current = this.eap$manualWaitingFor.getOrDefault(what, 0L); + if (current <= amount) { + this.eap$manualWaitingFor.remove(what); + } else { + this.eap$manualWaitingFor.put(what, current - amount); + } + } + + @Unique + private void eap$clearManualWaitingInternal(boolean notifyChanges) { + if (this.eap$manualWaitingFor.isEmpty()) { + return; + } + + var previousKeys = new ArrayList<>(this.eap$manualWaitingFor.keySet()); + this.eap$manualWaitingFor.clear(); + if (notifyChanges) { + for (var key : previousKeys) { + this.postChange(key); + } + } + this.cluster.markDirty(); + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftConfirmMenuForceStartMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftConfirmMenuForceStartMixin.java new file mode 100644 index 0000000..d063ec5 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftConfirmMenuForceStartMixin.java @@ -0,0 +1,62 @@ +package com.extendedae_plus.mixin.ae2.menu; + +import appeng.api.networking.crafting.ICraftingPlan; +import appeng.menu.me.crafting.CraftConfirmMenu; +import com.extendedae_plus.api.crafting.IForceCraftStartSync; +import com.extendedae_plus.crafting.ForcedCraftingPlan; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = CraftConfirmMenu.class, remap = false) +public abstract class CraftConfirmMenuForceStartMixin implements IForceCraftStartSync { + @Shadow + private ICraftingPlan result; + + @Unique + private boolean eap$pendingForceCraftStart; + + @Unique + private ICraftingPlan eap$originalSimulationResult; + + @Override + public void eap$clientSetForceCraftStart(boolean forceStart) { + this.eap$pendingForceCraftStart = forceStart; + } + + @Override + public boolean eap$consumeForceCraftStartFlag() { + boolean flag = this.eap$pendingForceCraftStart; + this.eap$pendingForceCraftStart = false; + return flag; + } + + @Inject(method = "startJob", at = @At("HEAD")) + private void eap$wrapSimulationPlanForForceStart(CallbackInfo ci) { + CraftConfirmMenu self = (CraftConfirmMenu) (Object) this; + if (self.isClientSide()) { + return; + } + if (!this.eap$consumeForceCraftStartFlag()) { + return; + } + if (this.result == null || !this.result.simulation()) { + return; + } + + this.eap$originalSimulationResult = this.result; + this.result = new ForcedCraftingPlan(this.result); + } + + @Inject(method = "startJob", at = @At("RETURN")) + private void eap$restoreOriginalPlanAfterSubmit(CallbackInfo ci) { + if (this.eap$originalSimulationResult == null) { + return; + } + this.result = this.eap$originalSimulationResult; + this.eap$originalSimulationResult = null; + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftingCPUMenuManualStatusMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftingCPUMenuManualStatusMixin.java new file mode 100644 index 0000000..29a3dcf --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftingCPUMenuManualStatusMixin.java @@ -0,0 +1,47 @@ +package com.extendedae_plus.mixin.ae2.menu; + +import appeng.api.stacks.AEKey; +import appeng.me.cluster.implementations.CraftingCPUCluster; +import appeng.menu.me.crafting.CraftingCPUMenu; +import com.extendedae_plus.api.crafting.IManualCraftingState; +import com.extendedae_plus.network.crafting.ManualCraftingStatusS2CPacket; +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +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.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +@Mixin(value = CraftingCPUMenu.class, remap = false) +public abstract class CraftingCPUMenuManualStatusMixin { + @Shadow + private CraftingCPUCluster cpu; + + @Unique + private Map eap$lastManualWaitingSnapshot = Collections.emptyMap(); + + @Inject(method = "broadcastChanges", at = @At("TAIL")) + private void eap$syncManualWaitingStatus(CallbackInfo ci) { + CraftingCPUMenu self = (CraftingCPUMenu) (Object) this; + if (self.isClientSide() || !(self.getPlayer() instanceof ServerPlayer serverPlayer)) { + return; + } + + Map snapshot = Collections.emptyMap(); + if (this.cpu != null && this.cpu.craftingLogic instanceof IManualCraftingState manualState) { + snapshot = manualState.eap$getManualWaitingSnapshot(); + } + + if (snapshot.equals(this.eap$lastManualWaitingSnapshot)) { + return; + } + + this.eap$lastManualWaitingSnapshot = new LinkedHashMap<>(snapshot); + serverPlayer.connection.send(new ManualCraftingStatusS2CPacket(self.containerId, snapshot)); + } +} diff --git a/src/main/java/com/extendedae_plus/network/crafting/ForceCraftStartFlagC2SPacket.java b/src/main/java/com/extendedae_plus/network/crafting/ForceCraftStartFlagC2SPacket.java new file mode 100644 index 0000000..20f3b62 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/crafting/ForceCraftStartFlagC2SPacket.java @@ -0,0 +1,48 @@ +package com.extendedae_plus.network.crafting; + +import appeng.menu.me.crafting.CraftConfirmMenu; +import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.api.crafting.IForceCraftStartSync; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +public class ForceCraftStartFlagC2SPacket implements CustomPacketPayload { + public static final Type TYPE = new Type<>( + ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "force_craft_start_flag")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + (buf, pkt) -> buf.writeBoolean(pkt.forceStart), + buf -> new ForceCraftStartFlagC2SPacket(buf.readBoolean()) + ); + + private final boolean forceStart; + + public ForceCraftStartFlagC2SPacket(boolean forceStart) { + this.forceStart = forceStart; + } + + public boolean forceStart() { + return this.forceStart; + } + + public static void handle(final ForceCraftStartFlagC2SPacket msg, final IPayloadContext ctx) { + ctx.enqueueWork(() -> { + if (!(ctx.player() instanceof ServerPlayer player)) { + return; + } + if (player.containerMenu instanceof CraftConfirmMenu menu + && menu instanceof IForceCraftStartSync sync) { + sync.eap$clientSetForceCraftStart(msg.forceStart()); + } + }); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/com/extendedae_plus/network/crafting/ManualCraftingStatusS2CPacket.java b/src/main/java/com/extendedae_plus/network/crafting/ManualCraftingStatusS2CPacket.java new file mode 100644 index 0000000..01cbb72 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/crafting/ManualCraftingStatusS2CPacket.java @@ -0,0 +1,71 @@ +package com.extendedae_plus.network.crafting; + +import appeng.api.stacks.AEKey; +import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.content.ClientManualCraftingStatusStore; +import net.minecraft.client.Minecraft; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class ManualCraftingStatusS2CPacket implements CustomPacketPayload { + public static final Type TYPE = new Type<>( + ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "manual_crafting_status")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + (buf, pkt) -> { + buf.writeInt(pkt.containerId); + buf.writeVarInt(pkt.manualWaiting.size()); + for (var entry : pkt.manualWaiting.entrySet()) { + AEKey.writeKey(buf, entry.getKey()); + buf.writeVarLong(entry.getValue()); + } + }, + buf -> { + int containerId = buf.readInt(); + int size = buf.readVarInt(); + Map snapshot = new LinkedHashMap<>(size); + for (int i = 0; i < size; i++) { + snapshot.put(AEKey.readKey(buf), buf.readVarLong()); + } + return new ManualCraftingStatusS2CPacket(containerId, snapshot); + } + ); + + private final int containerId; + private final Map manualWaiting; + + public ManualCraftingStatusS2CPacket(int containerId, Map manualWaiting) { + this.containerId = containerId; + this.manualWaiting = manualWaiting; + } + + public static void handle(final ManualCraftingStatusS2CPacket msg, final IPayloadContext ctx) { + ctx.enqueueWork(() -> handleClient(msg)); + } + + @OnlyIn(Dist.CLIENT) + private static void handleClient(ManualCraftingStatusS2CPacket msg) { + var mc = Minecraft.getInstance(); + if (mc == null || mc.player == null || mc.player.containerMenu == null) { + ClientManualCraftingStatusStore.clear(); + return; + } + if (mc.player.containerMenu.containerId != msg.containerId) { + return; + } + ClientManualCraftingStatusStore.setStatus(msg.containerId, msg.manualWaiting); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index f242525..28b3577 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -303,5 +303,8 @@ "extendedae_plus.screen.global_controller_title": "Pattern Provider Status Controller", "gui.extendedae_plus.add_bookmark": "Add Bookmark", - "tooltip.extendedae_plus.add_missing_to_jei_bookmark": "Add missing items to JEI bookmarks" + "tooltip.extendedae_plus.add_missing_to_jei_bookmark": "Add missing items to JEI bookmarks", + "gui.extendedae_plus.force_start": "Force Start", + "tooltip.extendedae_plus.force_start": "Submit this job immediately and keep missing entries as manual waiting", + "tooltip.extendedae_plus.crafting.manual_waiting": "Manual waiting: %s" } diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index 216fd16..71f74f4 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -299,5 +299,8 @@ "extendedae_plus.screen.global_controller_title": "样板供应器状态控制器", "gui.extendedae_plus.add_bookmark": "添加书签", - "tooltip.extendedae_plus.add_missing_to_jei_bookmark": "添加缺失物品到JEI书签" + "tooltip.extendedae_plus.add_missing_to_jei_bookmark": "添加缺失物品到JEI书签", + "gui.extendedae_plus.force_start": "强制开始", + "tooltip.extendedae_plus.force_start": "忽略当前缺失项并直接提交任务", + "tooltip.extendedae_plus.crafting.manual_waiting": "手动等待: %s" } diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index 11911ac..3935b6c 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -46,6 +46,8 @@ "ae2.helpers.PatternProviderLogicUpgradesMixin", "ae2.helpers.patternprovider.PatternProviderLogicTickerMixin", "ae2.menu.AEBaseMenuUpgradesDedupMixin", + "ae2.menu.CraftConfirmMenuForceStartMixin", + "ae2.menu.CraftingCPUMenuManualStatusMixin", "ae2.menu.ContainerPatternEncodingTermMenuMixin", "ae2.menu.MEStorageMenuMixin", "ae2.menu.PatternEncodingTermMenuMixin", @@ -53,6 +55,7 @@ "ae2.menu.PatternProviderMenuDoublingMixin", "ae2.menu.PatternProviderMenuUpgradesMixin", "ae2.network.PatternAccessTerminalPacketMixin", + "ae2.crafting.CraftingCpuLogicManualWaitingMixin", "ae2.parts.automation.IOBusPartChannelCardMixin", "ae2.parts.storagebus.StorageBusPartChannelCardMixin", "ae2WTlib.ContainerUWirelessExPatternTerminalMixin", @@ -81,6 +84,7 @@ "ae2.accessor.WidgetContainerAccessor", "ae2.client.gui.AEBaseScreenMixin", "ae2.client.gui.CraftConfirmScreenMixin", + "ae2.client.gui.CraftingStatusTableRendererMixin", "ae2.client.gui.InterfaceScreenMixin", "ae2.client.gui.PatternEncodingTermScreenMixin", "ae2.client.gui.PatternEncodingTermUploadMixin",