Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
import net.minecraft.world.entity.player.Inventory;
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
import org.cyclops.cyclopscore.client.gui.component.button.ButtonCheckbox;
import org.cyclops.cyclopscore.client.gui.component.button.ButtonImage;
import org.cyclops.cyclopscore.client.gui.component.input.IInputListener;
import org.cyclops.cyclopscore.client.gui.component.input.WidgetArrowedListField;
import org.cyclops.cyclopscore.client.gui.component.input.WidgetNumberField;
import org.cyclops.cyclopscore.client.gui.image.IImage;
import org.cyclops.cyclopscore.helper.Helpers;
import org.cyclops.cyclopscore.helper.L10NHelpers;
import org.cyclops.cyclopscore.helper.ValueNotifierHelpers;
import org.cyclops.integratedcrafting.Reference;
import org.cyclops.integratedcrafting.inventory.container.ContainerPartInterfaceCraftingSettings;
import org.cyclops.integrateddynamics.client.gui.image.Images;
import org.cyclops.integrateddynamics.core.client.gui.WidgetTextFieldDropdown;
import org.cyclops.integrateddynamics.core.client.gui.container.ContainerScreenPartSettings;
import org.lwjgl.glfw.GLFW;
Expand Down Expand Up @@ -95,6 +98,17 @@ protected void onSave() {
public void init() {
super.init();

if (getMenu().getPartType().supportsOffsets()) {
addRenderableWidget(new ButtonImage(this.leftPos - 20, this.topPos + 10, 18, 18,
Component.translatable("gui.integrateddynamics.part_offsets"),
createServerPressable(ContainerPartInterfaceCraftingSettings.BUTTON_OFFSETS, (button) -> {}),
new IImage[]{
Images.BUTTON_BACKGROUND_INACTIVE,
Images.BUTTON_MIDDLE_OFFSET
},
false, 0, 0));
}

ingredientComponentSideSelector = new WidgetArrowedListField<IngredientComponent<?, ?>>(font,
leftPos + 106, topPos + 9, 68, 15, true,
Component.translatable("gui.integratedcrafting.partsettings.ingredient"),
Expand Down Expand Up @@ -202,6 +216,16 @@ protected void renderBg(GuiGraphics guiGraphics, float partialTicks, int mouseX,
checkboxFieldBlockingMode.render(guiGraphics, mouseX, mouseY, partialTicks);
}

@Override
protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) {
super.renderLabels(guiGraphics, mouseX, mouseY);

if (getMenu().getPartType().supportsOffsets() && isHovering(-20, 0 + 10, 18, 18, mouseX, mouseY)) {
drawTooltip(Lists.newArrayList(Component.translatable("gui.integrateddynamics.part_offsets")),
guiGraphics.pose(), mouseX - leftPos, mouseY - topPos);
}
}

@Override
protected int getBaseYSize() {
return 256;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package org.cyclops.integratedcrafting.core.part;

import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import org.apache.commons.lang3.tuple.Triple;
import org.cyclops.cyclopscore.init.ModBase;
import org.cyclops.cyclopscore.network.PacketCodec;
import org.cyclops.integratedcrafting.IntegratedCrafting;
import org.cyclops.integrateddynamics.api.part.IPartContainer;
import org.cyclops.integrateddynamics.api.part.IPartState;
Expand All @@ -21,15 +18,20 @@
import org.cyclops.integrateddynamics.core.helper.PartHelpers;
import org.cyclops.integrateddynamics.core.inventory.container.ContainerPartSettings;
import org.cyclops.integrateddynamics.core.part.PartTypeBase;
import org.cyclops.integrateddynamics.core.part.PartTypeConfigurable;

import javax.annotation.Nullable;
import java.util.Optional;

/**
* Base part for a crafting part.
*
* This extends {@link PartTypeConfigurable} so that crafting parts
* expose the part settings and part offsets guis.
*
* @author rubensworks
*/
public abstract class PartTypeCraftingBase<P extends IPartType<P, S>, S extends IPartState<P>> extends PartTypeBase<P, S> {
public abstract class PartTypeCraftingBase<P extends IPartType<P, S>, S extends IPartState<P>> extends PartTypeConfigurable<P, S> {

public PartTypeCraftingBase(String name) {
super(name, new PartRenderPosition(0.1875F, 0.1875F, 0.625F, 0.625F));
Expand Down Expand Up @@ -64,10 +66,4 @@ public boolean shouldTriggerClientSideContainerClosingOnOpen() {
});
}

@Override
public void writeExtraGuiDataSettings(RegistryFriendlyByteBuf packetBuffer, PartPos pos, ServerPlayer player) {
PacketCodec.write(packetBuffer, pos);
packetBuffer.writeUtf(this.getUniqueName().toString());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,36 @@ protected void addTargetToNetwork(INetwork network, PartTarget pos, S state, boo
});
}

/**
* Update the target of the given part state, and make the crafting network aware of it.
*
* Contrary to {@link #removeTargetFromNetwork(INetwork, PartPos, S)} followed by
* {@link #addTargetToNetwork(INetwork, PartTarget, S, boolean)},
* this retains the network and channel of the part,
* as only the targeted position changes.
*
* @param network The network.
* @param newTarget The new target.
* @param state The part state.
*/
protected void retarget(INetwork network, PartTarget newTarget, S state) {
ICraftingNetwork craftingNetwork = state.getCraftingNetwork();

// Unregister the recipes for the old target from the crafting network.
// This must happen before the recipes are reloaded, as the old recipes are needed for a proper removal.
if (craftingNetwork != null) {
craftingNetwork.removeCraftingInterface(state.getChannelCrafting(), state);
}

// Update the target, and reload all recipes based on this new target.
state.setTarget(newTarget);
state.setNetworks(network, craftingNetwork, NetworkHelpers.getPartNetworkChecked(network), state.getChannel(),
ValueDeseralizationContext.of(newTarget.getCenter().getPos().getLevel(true)), false);

// Re-register to the crafting network, so that the recipes for the new target are picked up.
state.setShouldAddToCraftingNetwork(true);
}

protected void removeTargetFromNetwork(INetwork network, PartPos pos, S state) {
ICraftingNetwork craftingNetwork = state.getCraftingNetwork();
if (craftingNetwork != null) {
Expand Down Expand Up @@ -139,6 +169,13 @@ public void update(INetwork network, IPartNetwork partNetwork, PartTarget target
// This can occur when the part chunk is being reloaded.
if (state.getCraftingNetwork() == null) {
addTargetToNetwork(network, target, state, false);
} else {
// Detect changes to our target, which can occur when the target offset is changed.
// The target is recalculated here, as offset variables may have changed it during this update.
PartTarget currentTarget = getTarget(target.getCenter(), state);
if (!currentTarget.equals(state.getTarget())) {
retarget(network, currentTarget, state);
}
}

int channelCrafting = state.getChannelCrafting();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.google.common.collect.Maps;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import net.minecraft.gametest.framework.GameTestAssertException;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.resources.ResourceLocation;
Expand Down Expand Up @@ -34,6 +35,8 @@
import org.cyclops.integrateddynamics.RegistryEntries;
import org.cyclops.integrateddynamics.api.evaluate.variable.IValue;
import org.cyclops.integrateddynamics.api.evaluate.variable.IValueType;
import org.cyclops.integrateddynamics.api.part.IPartState;
import org.cyclops.integrateddynamics.api.part.IPartType;
import org.cyclops.integrateddynamics.api.part.PartPos;
import org.cyclops.integrateddynamics.api.part.PartTarget;
import org.cyclops.integrateddynamics.api.part.aspect.IAspectWrite;
Expand Down Expand Up @@ -241,6 +244,24 @@ public static <T extends IValueType<V>, V extends IValue> void setCraftingInterf
((PartTypeInterfaceCrafting.State) partStateHolder.getState()).getCraftingJobHandler().setBlockingJobsMode(blocking);
}

/**
* Make the part at the given position target another position via an offset.
*
* This also increases the max offset of the part,
* just like applying part offset enhancement items would do.
*
* @param partPos The (center) position of the part.
* @param offset The target offset.
*/
public static void setPartOffset(PartPos partPos, Vec3i offset) {
PartHelpers.PartStateHolder partStateHolder = PartHelpers.getPart(partPos);
IPartState partState = partStateHolder.getState();
partState.setMaxOffset(Math.max(Math.abs(offset.getX()), Math.max(Math.abs(offset.getY()), Math.abs(offset.getZ()))));
if (!((IPartType) partStateHolder.getPart()).setTargetOffset(partState, partPos, offset)) {
throw new GameTestAssertException("Could not set target offset " + offset + " on the part at " + partPos);
}
}

public static <T extends IValueType<V>, V extends IValue> void setCraftingInterfaceUpdateInterval(PartPos writerPos, int updateInterval) {
PartHelpers.PartStateHolder partStateHolder = PartHelpers.getPart(writerPos);
partStateHolder.getState().setUpdateInterval(updateInterval);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package org.cyclops.integratedcrafting.gametest;

import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.resources.ResourceLocation;
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.Blocks;
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.integratedcrafting.Reference;
import org.cyclops.integratedcrafting.part.PartTypeInterfaceCrafting;
import org.cyclops.integratedcrafting.part.PartTypeInterfaceCraftingAttuned;

import static org.cyclops.integratedcrafting.gametest.GameTestHelpersIntegratedCrafting.createBasicNetwork;
import static org.cyclops.integratedcrafting.gametest.GameTestHelpersIntegratedCrafting.enableRecipeInWriter;
import static org.cyclops.integratedcrafting.gametest.GameTestHelpersIntegratedCrafting.setPartOffset;

/**
* Game tests for crafting interfaces that target machines via a part offset.
*
* @author rubensworks
*/
@GameTestHolder(Reference.MOD_ID)
@PrefixGameTestTemplate(false)
public class GameTestsPartOffsets {

public static final String TEMPLATE_EMPTY = "empty10";
public static final int TIMEOUT = 2000;
public static final BlockPos POS = BlockPos.ZERO.offset(2, 0, 2);

/**
* The crafting interface points at an empty block, and reaches the crafting table via an offset.
*/
@GameTest(template = TEMPLATE_EMPTY, timeoutTicks = TIMEOUT)
public void testItemsCraftChestOffset(GameTestHelper helper) {
GameTestHelpersIntegratedCrafting.INetworkPositions<PartTypeInterfaceCrafting.State> positions = createBasicNetwork(helper, POS);

// Move the crafting table one block away from the crafting interface,
// and make the crafting interface target it via an offset.
helper.setBlock(POS.west(), Blocks.AIR);
helper.setBlock(POS.west().north(), Blocks.CRAFTING_TABLE);
setPartOffset(positions.interfaces().get(0), new Vec3i(0, 0, -1));

// Insert items in interface chest
ChestBlockEntity chestIn = helper.getBlockEntity(POS.east());
chestIn.setItem(0, new ItemStack(Items.OAK_PLANKS, 64));

// Add chest recipe to crafting interface
positions.interfaceRecipeAdders().get(0).accept(Triple.of(0, RecipeType.CRAFTING, ResourceLocation.fromNamespaceAndPath("minecraft", "chest")));

// Enable crafting aspect in crafting writer
enableRecipeInWriter(helper, positions.writer(), new ItemStack(Items.CHEST));

helper.succeedWhen(() -> {
// Check crafting interface state
helper.assertTrue(positions.interfaceStates().get(0).isRecipeSlotValid(0), "Recipe in crafting interface is not valid");

// Check if items have been crafted
helper.assertValueEqual(chestIn.getItem(0).getItem(), Items.OAK_PLANKS, "Slot 0 item is incorrect");
helper.assertValueEqual(chestIn.getItem(0).getCount(), 56, "Slot 0 amount is incorrect");
helper.assertValueEqual(chestIn.getItem(1).getItem(), Items.CHEST, "Slot 1 item is incorrect");
helper.assertValueEqual(chestIn.getItem(1).getCount(), 1, "Slot 1 amount is incorrect");
});
}

/**
* The crafting interface points at a furnace, but must ignore it because an offset makes it target a crafting table.
*/
@GameTest(template = TEMPLATE_EMPTY, timeoutTicks = TIMEOUT)
public void testItemsCraftChestOffsetIgnoresAdjacentMachine(GameTestHelper helper) {
GameTestHelpersIntegratedCrafting.INetworkPositions<PartTypeInterfaceCrafting.State> positions = createBasicNetwork(helper, POS);

// Place a furnace directly in front of the crafting interface,
// and make the crafting interface target a crafting table via an offset.
helper.setBlock(POS.west(), Blocks.FURNACE);
helper.setBlock(POS.west().north(), Blocks.CRAFTING_TABLE);
setPartOffset(positions.interfaces().get(0), new Vec3i(0, 0, -1));

// Insert items in interface chest
ChestBlockEntity chestIn = helper.getBlockEntity(POS.east());
chestIn.setItem(0, new ItemStack(Items.OAK_PLANKS, 64));

// Add chest recipe to crafting interface
positions.interfaceRecipeAdders().get(0).accept(Triple.of(0, RecipeType.CRAFTING, ResourceLocation.fromNamespaceAndPath("minecraft", "chest")));

// Enable crafting aspect in crafting writer
enableRecipeInWriter(helper, positions.writer(), new ItemStack(Items.CHEST));

helper.succeedWhen(() -> {
// Check crafting interface state
helper.assertTrue(positions.interfaceStates().get(0).isRecipeSlotValid(0), "Recipe in crafting interface is not valid");

// Check if items have been crafted
helper.assertValueEqual(chestIn.getItem(0).getItem(), Items.OAK_PLANKS, "Slot 0 item is incorrect");
helper.assertValueEqual(chestIn.getItem(0).getCount(), 56, "Slot 0 amount is incorrect");
helper.assertValueEqual(chestIn.getItem(1).getItem(), Items.CHEST, "Slot 1 item is incorrect");
helper.assertValueEqual(chestIn.getItem(1).getCount(), 1, "Slot 1 amount is incorrect");
});
}

/**
* The attuned crafting interface must read the recipes of the machine it targets via an offset.
*/
@GameTest(template = TEMPLATE_EMPTY, timeoutTicks = TIMEOUT)
public void testItemsCraftAttunedPlanksOffset(GameTestHelper helper) {
GameTestHelpersIntegratedCrafting.INetworkPositions<PartTypeInterfaceCraftingAttuned.State> positions = createBasicNetwork(helper, POS, true);

// Move the crafting table one block away from the crafting interface,
// and make the crafting interface target it via an offset.
helper.setBlock(POS.west(), Blocks.AIR);
helper.setBlock(POS.west().north(), Blocks.CRAFTING_TABLE);
setPartOffset(positions.interfaces().get(0), new Vec3i(0, 0, -1));

// Insert items in interface chest
ChestBlockEntity chestIn = helper.getBlockEntity(POS.east());
chestIn.setItem(0, new ItemStack(Items.OAK_LOG, 64));

// Enable crafting aspect in crafting writer
enableRecipeInWriter(helper, positions.writer(), new ItemStack(Items.OAK_PLANKS));

helper.succeedWhen(() -> {
// Check crafting interface state
helper.assertTrue(positions.interfaceStates().get(0).hasValidTarget(), "Crafting interface has no valid target");

// Check if items have been crafted
helper.assertValueEqual(chestIn.getItem(0).getItem(), Items.OAK_LOG, "Slot 0 item is incorrect");
helper.assertValueEqual(chestIn.getItem(0).getCount(), 63, "Slot 0 amount is incorrect");
helper.assertValueEqual(chestIn.getItem(1).getItem(), Items.OAK_PLANKS, "Slot 1 item is incorrect");
helper.assertValueEqual(chestIn.getItem(1).getCount(), 4, "Slot 1 amount is incorrect");
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.minecraft.core.Direction;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
Expand All @@ -28,6 +29,8 @@
*/
public class ContainerPartInterfaceCraftingSettings extends ContainerPartSettings {

public static final String BUTTON_OFFSETS = "button_offsets";

private final int lastChannelInterfaceCraftingValueId;
private final Map<IngredientComponent<?, ?>, Integer> targetSideOverrideValueIds;
private final int lastDisableCraftingCheckValueId;
Expand All @@ -48,6 +51,14 @@ public ContainerPartInterfaceCraftingSettings(int id, Inventory playerInventory,
}
lastDisableCraftingCheckValueId = getNextValueId();
lastBlockingModeValueId = getNextValueId();

// Expose the offsets gui from within the settings gui,
// as some crafting interfaces (such as the attuned one) show the settings gui as their main gui.
putButtonAction(ContainerPartInterfaceCraftingSettings.BUTTON_OFFSETS, (s, containerExtended) -> {
if (!player.level().isClientSide()) {
PartHelpers.openContainerPartOffsets((ServerPlayer) player, getTarget().getCenter(), getPartType());
}
});
}

@Override
Expand Down
Loading