Skip to content
Merged
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ org.gradle.caching=true
cyclopscore_version=1.26.2-808
integrateddynamics_version=1.32.0-1630
integratedterminalscompat_version=1.0.0-167
integratedcrafting_version=1.4.1-442
integratedcrafting_version=1.5.0-681
integratedtunnels_version=1.8.44-484
commoncapabilities_version=2.9.12-263
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ public class GeneralConfig extends DummyConfig {
@ConfigurableProperty(category = "machine", comment = "The update frequency in milliseconds for the crafting jobs gui.", isCommandable = true)
public static int guiTerminalCraftingJobsUpdateFrequency = 1000;

@ConfigurableProperty(category = "machine", comment = "If a toast should be shown when a crafting job that you requested has been completed.", isCommandable = true, configLocation = ModConfig.Type.CLIENT)
public static boolean craftingJobFinishedToast = true;

@ConfigurableProperty(category = "core", comment = "The number of threads that the crafting plan calculator can use.", minimalValue = 1, requiresMcRestart = true, configLocation = ModConfig.Type.SERVER)
public static int craftingPlannerThreads = 2;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,23 @@ public default ITerminalCraftingPlanFlat<I> deserializeCraftingPlanFlat(HolderLo
* @param player The player that started the crafting job.
* @throws CraftingJobStartException If the crafting job failed to start.
*/
@Deprecated // TODO: rm in next major
public default void startCraftingJob(INetwork network, int channel, ITerminalCraftingPlan<I> craftingPlan,
ServerPlayer player) throws CraftingJobStartException {
startCraftingJob(network, channel, craftingPlan, player, true);
}

/**
* Start the given crafting plan.
* @param network The network in which the plan should be started.
* @param channel The channel to get the options for.
* @param craftingPlan A crafting plan.
* @param player The player that started the crafting job.
* @param notifyOnCompletion If the player wants to be notified once the crafting job is completed.
* @throws CraftingJobStartException If the crafting job failed to start.
*/
public void startCraftingJob(INetwork network, int channel, ITerminalCraftingPlan<I> craftingPlan,
ServerPlayer player) throws CraftingJobStartException;
ServerPlayer player, boolean notifyOnCompletion) throws CraftingJobStartException;

/**
* @param network The network in which the plan should be started.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Checkbox;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
Expand All @@ -18,6 +20,7 @@
import org.cyclops.integratedterminals.client.gui.container.component.GuiCraftingPlanToggler;
import org.cyclops.integratedterminals.core.client.gui.CraftingOptionGuiData;
import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStorageCraftingPlanBase;
import org.cyclops.integratedterminals.network.packet.TerminalStorageCraftingPlanSetNotifyPacket;
import org.cyclops.integratedterminals.network.packet.TerminalStorageIngredientOpenCraftingJobAmountGuiPacket;
import org.lwjgl.glfw.GLFW;

Expand All @@ -39,6 +42,7 @@ public class ContainerScreenTerminalStorageCraftingPlan<L, C extends ContainerTe
private ITerminalCraftingPlan craftingPlan;
private ITerminalCraftingPlanFlat craftingPlanFlat;
private ButtonText buttonConfirm;
private boolean notifyOnCompletion = true;

public ContainerScreenTerminalStorageCraftingPlan(C container, Inventory inventory, Component title) {
super(container, inventory, title);
Expand All @@ -51,7 +55,7 @@ public ContainerScreenTerminalStorageCraftingPlan(C container, Inventory invento
addRenderableWidget(this.guiCraftingPlan);

if (this.craftingPlanFlat != null) {
addRenderableWidget(new ButtonText(leftPos + 8, topPos + 198, 80, 20,
addRenderableWidget(new ButtonText(leftPos + 8, topPos + 198, 62, 20,
Component.translatable("gui.integratedterminals.craftingplan.view.flat"),
Component.translatable("gui.integratedterminals.craftingplan.view.flat").withStyle(ChatFormatting.ITALIC),
(b) -> {
Expand All @@ -66,7 +70,7 @@ public ContainerScreenTerminalStorageCraftingPlan(C container, Inventory invento
addRenderableWidget(this.guiCraftingPlanFlat);

if (this.craftingPlan != null) {
addRenderableWidget(new ButtonText(leftPos + 8, topPos + 198, 80, 20,
addRenderableWidget(new ButtonText(leftPos + 8, topPos + 198, 62, 20,
Component.translatable("gui.integratedterminals.craftingplan.view.tree"),
Component.translatable("gui.integratedterminals.craftingplan.view.tree").withStyle(ChatFormatting.ITALIC),
(b) -> {
Expand Down Expand Up @@ -121,6 +125,13 @@ public void init() {
(b) -> returnToCraftingOptionAmount(),
true));

addRenderableWidget(Checkbox.builder(Component.translatable("gui.integratedterminals.terminal_storage.step.craft.notify"), font)
.pos(leftPos + 72, topPos + 200)
.selected(this.notifyOnCompletion)
.tooltip(Tooltip.create(Component.translatable("gui.integratedterminals.terminal_storage.step.craft.notify.info")))
.onValueChange((widget, selected) -> setNotifyOnCompletion(selected))
.build());

addRenderableWidget(buttonConfirm = new ButtonText(leftPos + 221 + 10 - 50, topPos + 198, 50, 20,
Component.translatable("gui.integratedterminals.terminal_storage.step.craft"),
Component.translatable("gui.integratedterminals.terminal_storage.step.craft").withStyle(ChatFormatting.YELLOW),
Expand All @@ -129,6 +140,12 @@ public void init() {
buttonConfirm.active = (this.guiCraftingPlan != null && this.guiCraftingPlan.isValid()) || (this.guiCraftingPlanFlat != null && this.guiCraftingPlanFlat.isValid());
}

private void setNotifyOnCompletion(boolean notifyOnCompletion) {
this.notifyOnCompletion = notifyOnCompletion;
IntegratedTerminals._instance.getPacketHandler().sendToServer(
new TerminalStorageCraftingPlanSetNotifyPacket(notifyOnCompletion));
Comment thread
rubensworks marked this conversation as resolved.
Outdated
}

@Override
public boolean keyPressed(int typedChar, int keyCode, int modifiers) {
if (typedChar == GLFW.GLFW_KEY_ESCAPE) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.cyclops.integratedterminals.client.gui.toast;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.toasts.Toast;
import net.minecraft.client.gui.components.toasts.ToastComponent;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.world.item.ItemStack;
import org.cyclops.integratedterminals.Reference;

import java.util.List;

/**
* A toast that shows an item icon alongside a title and a wrapping subtitle.
*
* Toasts with an equal token replace each other instead of being queued,
* so the token determines how toasts are grouped.
*
* @author rubensworks
*/
public class CraftingJobToast implements Toast {

private static final ResourceLocation BACKGROUND_SPRITE = ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "toast/crafting_job");
private static final int DISPLAY_MILLIS = 5000;
private static final int MARGIN = 7;
private static final int ICON_LEFT = 7;
private static final int ICON_SIZE = 16;
private static final int TEXT_LEFT = ICON_LEFT + ICON_SIZE + 5;
private static final int LINE_SPACING = 12;

private final Object token;
private final ItemStack icon;
private Component title;
private List<FormattedCharSequence> subtitleLines;
private long lastChangedAt = Long.MIN_VALUE;
private boolean changed = true;

public CraftingJobToast(Object token, ItemStack icon, Component title, Component subtitle) {
this.token = token;
this.icon = icon;
this.title = title;
this.subtitleLines = splitSubtitle(subtitle);
}

/**
* Update the contents of this toast in-place, without queueing a new one.
* @param newTitle The new title.
* @param newSubtitle The new subtitle.
*/
public void reset(Component newTitle, Component newSubtitle) {
this.title = newTitle;
this.subtitleLines = splitSubtitle(newSubtitle);
this.changed = true;
}

private List<FormattedCharSequence> splitSubtitle(Component text) {
return Minecraft.getInstance().font.split(text, width() - TEXT_LEFT - MARGIN);
}

@Override
public int height() {
return 20 + Math.max(1, subtitleLines.size()) * LINE_SPACING;
}

@Override
public Visibility render(GuiGraphics graphics, ToastComponent toastComponent, long timeSinceLastVisible) {
if (changed) {
lastChangedAt = timeSinceLastVisible;
changed = false;
}

graphics.blitSprite(BACKGROUND_SPRITE, 0, 0, width(), height());
graphics.renderItem(icon, ICON_LEFT, 8);

var font = toastComponent.getMinecraft().font;
graphics.drawString(font, title, TEXT_LEFT, 7, 0xFFFFFF, false);
for (int i = 0; i < subtitleLines.size(); i++) {
graphics.drawString(font, subtitleLines.get(i), TEXT_LEFT, 18 + i * LINE_SPACING, 0xAAAAAA, false);
}

return timeSinceLastVisible - lastChangedAt < (long) (DISPLAY_MILLIS * toastComponent.getNotificationDisplayTimeMultiplier())
? Visibility.SHOW
: Visibility.HIDE;
}

@Override
public Object getToken() {
return token;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package org.cyclops.integratedterminals.gametest;

import com.mojang.authlib.GameProfile;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.neoforged.neoforge.gametest.GameTestHolder;
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
import org.apache.commons.lang3.tuple.Triple;
import org.cyclops.commoncapabilities.IngredientComponents;
import org.cyclops.commoncapabilities.api.capability.itemhandler.ItemMatch;
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
import org.cyclops.integratedcrafting.api.crafting.CraftingJob;
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
import org.cyclops.integratedcrafting.core.CraftingHelpers;
import org.cyclops.integratedcrafting.gametest.GameTestHelpersIntegratedCrafting;
import org.cyclops.integratedcrafting.part.PartTypeInterfaceCrafting;
import org.cyclops.integrateddynamics.api.network.INetwork;
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
import org.cyclops.integrateddynamics.core.helper.NetworkHelpers;
import org.cyclops.integratedterminals.Reference;
import org.cyclops.integratedterminals.api.terminalstorage.crafting.ITerminalCraftingPlan;
import org.cyclops.integratedterminals.modcompat.integratedcrafting.TerminalCraftingOptionRecipeDefinition;
import org.cyclops.integratedterminals.modcompat.integratedcrafting.TerminalStorageTabIngredientCraftingHandlerCraftingNetwork;

import java.util.Iterator;
import java.util.UUID;

/**
* Game tests for requesting a notification when a crafting job started from a terminal is completed.
* @author rubensworks
*/
@GameTestHolder(Reference.MOD_ID)
@PrefixGameTestTemplate(false)
public class GameTestCraftingJobNotify {

public static final BlockPos POS = BlockPos.ZERO.offset(2, 0, 2);

/**
* A job started with the notify option enabled carries the initiator and the notify flag.
*/
@GameTest(template = "empty10", templateNamespace = Reference.MOD_ID, timeoutTicks = 2000)
public void testStartCraftingJobWithNotify(GameTestHelper helper) {
testStartCraftingJob(helper, true);
}

/**
* A job started with the notify option disabled carries the initiator, but not the notify flag.
*/
@GameTest(template = "empty10", templateNamespace = Reference.MOD_ID, timeoutTicks = 2000)
public void testStartCraftingJobWithoutNotify(GameTestHelper helper) {
testStartCraftingJob(helper, false);
}

private void testStartCraftingJob(GameTestHelper helper, boolean notifyOnCompletion) {
prepareNetwork(helper);

// This player is deliberately not added to the player list,
// so that no notification packet is sent for the completed job.
ServerPlayer player = new ServerPlayer(helper.getLevel().getServer(), helper.getLevel(),
new GameProfile(UUID.randomUUID(), "test-mock-player"), ClientInformation.createDefault());

helper.startSequence()
.thenIdle(20)
.thenExecute(() -> {
INetwork network = getNetwork(helper);
int channel = IPositionedAddonsNetworkIngredients.DEFAULT_CHANNEL;
TerminalStorageTabIngredientCraftingHandlerCraftingNetwork handler =
new TerminalStorageTabIngredientCraftingHandlerCraftingNetwork();

ITerminalCraftingPlan<Integer> craftingPlan = handler.calculateCraftingPlan(network, channel,
new TerminalCraftingOptionRecipeDefinition<>(IngredientComponents.ITEMSTACK,
getChestRecipe(helper, network, channel)), 1);
try {
handler.startCraftingJob(network, channel, craftingPlan, player, notifyOnCompletion);
} catch (Exception e) {
helper.fail("The crafting job could not be started: " + e.getMessage());
}

CraftingJob craftingJob = getSingleCraftingJob(helper, network, channel);
helper.assertTrue(player.getUUID().toString().equals(craftingJob.getInitiatorUuid()),
"The started job did not carry the initiator");
helper.assertTrue(craftingJob.isNotifyInitiator() == notifyOnCompletion,
"The started job did not carry the expected notify flag");
})
.thenSucceed();
}

private static void prepareNetwork(GameTestHelper helper) {
GameTestHelpersIntegratedCrafting.INetworkPositions<PartTypeInterfaceCrafting.State> positions =
GameTestHelpersIntegratedCrafting.createBasicNetwork(helper, POS);

ChestBlockEntity chest = helper.getBlockEntity(POS.east());
chest.setItem(0, new ItemStack(Items.OAK_PLANKS, 64));

positions.interfaceRecipeAdders().get(0).accept(Triple.of(0, RecipeType.CRAFTING,
ResourceLocation.fromNamespaceAndPath("minecraft", "chest")));
}

private static IRecipeDefinition getChestRecipe(GameTestHelper helper, INetwork network, int channel) {
Iterator<IRecipeDefinition> recipes = CraftingHelpers.getCraftingNetworkChecked(network)
.getRecipeIndex(channel)
.getRecipes(IngredientComponents.ITEMSTACK, new ItemStack(Items.CHEST), ItemMatch.ITEM);
if (!recipes.hasNext()) {
helper.fail("No chest recipe was available in the network");
}
return recipes.next();
}

private static CraftingJob getSingleCraftingJob(GameTestHelper helper, INetwork network, int channel) {
ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
Iterator<CraftingJob> craftingJobs = craftingNetwork.getCraftingJobs(channel);
if (!craftingJobs.hasNext()) {
helper.fail("No crafting job was scheduled");
}
return craftingJobs.next();
}

private static INetwork getNetwork(GameTestHelper helper) {
return NetworkHelpers.getNetwork(helper.getLevel(), helper.absolutePos(POS), null)
.orElseThrow(() -> new IllegalStateException("Could not find a network"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public abstract class ContainerTerminalStorageCraftingPlanBase<L> extends Invent

private boolean calculatedCraftingPlan;
private ITerminalCraftingPlan craftingPlan;
private boolean notifyOnCompletion = true;

public ContainerTerminalStorageCraftingPlanBase(@Nullable MenuType<?> type, int id, Inventory playerInventory,
CraftingOptionGuiData craftingOptionGuiData) {
Expand All @@ -59,6 +60,17 @@ public CraftingOptionGuiData getCraftingOptionGuiData() {
return craftingOptionGuiData;
}

/**
* @return If the player wants to be notified once the crafting job is completed.
*/
public boolean isNotifyOnCompletion() {
return notifyOnCompletion;
}

public void setNotifyOnCompletion(boolean notifyOnCompletion) {
this.notifyOnCompletion = notifyOnCompletion;
}

@Override
public void broadcastChanges() {
super.broadcastChanges();
Expand Down Expand Up @@ -123,7 +135,7 @@ private void startCraftingJob() {
getNetwork().ifPresent(network -> {
try {
craftingOptionGuiData.getCraftingOption().getHandler()
.startCraftingJob(network, craftingOptionGuiData.getChannel(), craftingPlan, (ServerPlayer) player);
.startCraftingJob(network, craftingOptionGuiData.getChannel(), craftingPlan, (ServerPlayer) player, this.notifyOnCompletion);

// Re-open terminal gui
craftingOptionGuiData.getLocation()
Expand Down
Loading
Loading