Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow memory cards to copy patterns between pattern providers #6657

Merged
merged 1 commit into from
Oct 6, 2022
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 @@ -92,8 +92,8 @@ public static IPatternDetails decodePattern(ItemStack stack, Level level, boolea
* using some process external to the ME system.
*
* @param out The first element is considered the primary output and must be present
* @throws IllegalArgumentException If either in or out contain only empty ItemStacks, or no primary output
* @return A new encoded pattern.
* @throws IllegalArgumentException If either in or out contain only empty ItemStacks, or no primary output
*/
public static ItemStack encodeProcessingPattern(GenericStack[] in, GenericStack[] out) {
return AEItems.PROCESSING_PATTERN.asItem().encode(in, out);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntityType;
Expand All @@ -39,6 +40,7 @@
import appeng.helpers.iface.PatternProviderLogic;
import appeng.helpers.iface.PatternProviderLogicHost;
import appeng.util.Platform;
import appeng.util.SettingsFrom;

public class PatternProviderBlockEntity extends AENetworkBlockEntity implements PatternProviderLogicHost {
private final PatternProviderLogic logic = new PatternProviderLogic(this.getMainNode(), this);
Expand Down Expand Up @@ -157,6 +159,22 @@ public EnumSet<Direction> getTargets() {
return EnumSet.of(this.getForward());
}

@Override
public void exportSettings(SettingsFrom mode, CompoundTag output,
@org.jetbrains.annotations.Nullable Player player) {
if (mode == SettingsFrom.MEMORY_CARD) {
logic.exportSettings(output);
}
}

@Override
public void importSettings(SettingsFrom mode, CompoundTag input,
@org.jetbrains.annotations.Nullable Player player) {
if (mode == SettingsFrom.MEMORY_CARD) {
logic.importSettings(input, player);
}
}

public boolean isOmniDirectional() {
return this.omniDirectional;
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/appeng/core/localization/PlayerMessages.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public enum PlayerMessages implements LocalizationEnum {
CommunicationError("Error Communicating with Network."),
DeviceNotLinked("Device is not linked."),
DeviceNotPowered("Device is low on power."),
MissingBlankPatterns("Not enough blank pattern to restore patterns (missing %d)."),
MissingUpgrades("Not enough %s to restore upgrades (missing %d)."),
InvalidMachine("Could not restore configuration for an incompatible device."),
InvalidMachinePartiallyRestored("Partially restored configuration for an incompatible device: %s."),
Expand Down
92 changes: 90 additions & 2 deletions src/main/java/appeng/helpers/iface/PatternProviderLogic.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
Expand Down Expand Up @@ -63,19 +64,24 @@
import appeng.api.stacks.GenericStack;
import appeng.api.stacks.KeyCounter;
import appeng.api.util.IConfigManager;
import appeng.core.AELog;
import appeng.core.definitions.AEItems;
import appeng.core.localization.PlayerMessages;
import appeng.core.settings.TickRates;
import appeng.helpers.ICustomNameObject;
import appeng.me.helpers.MachineSource;
import appeng.util.ConfigManager;
import appeng.util.inv.AppEngInternalInventory;
import appeng.util.inv.InternalInventoryHost;
import appeng.util.inv.PlayerInternalInventory;

/**
* Shared code between the pattern provider block and part.
*/
public class PatternProviderLogic implements InternalInventoryHost, ICraftingProvider {

public static final int NUMBER_OF_PATTERN_SLOTS = 9;
public static final String NBT_MEMORY_CARD_PATTERNS = "patterns";

private final PatternProviderLogicHost host;
private final IManagedGridNode mainNode;
Expand Down Expand Up @@ -128,7 +134,7 @@ public void setPriority(int priority) {

public void writeToNBT(CompoundTag tag) {
this.configManager.writeToNBT(tag);
this.patternInventory.writeToNBT(tag, "patterns");
this.patternInventory.writeToNBT(tag, NBT_MEMORY_CARD_PATTERNS);
tag.putInt("priority", this.priority);

ListTag sendListTag = new ListTag();
Expand All @@ -145,7 +151,7 @@ public void writeToNBT(CompoundTag tag) {

public void readFromNBT(CompoundTag tag) {
this.configManager.readFromNBT(tag);
this.patternInventory.readFromNBT(tag, "patterns");
this.patternInventory.readFromNBT(tag, NBT_MEMORY_CARD_PATTERNS);
this.priority = tag.getInt("priority");

ListTag sendListTag = tag.getList("sendList", Tag.TAG_COMPOUND);
Expand Down Expand Up @@ -393,6 +399,88 @@ public PatternProviderReturnInventory getReturnInv() {
return this.returnInv;
}

public void exportSettings(CompoundTag output) {
patternInventory.writeToNBT(output, NBT_MEMORY_CARD_PATTERNS);
}

public void importSettings(CompoundTag input, @org.jetbrains.annotations.Nullable Player player) {
if (player != null && input.contains(NBT_MEMORY_CARD_PATTERNS) && !player.level.isClientSide) {
clearPatternInventory(player);

var desiredPatterns = new AppEngInternalInventory(patternInventory.size());
desiredPatterns.readFromNBT(input, NBT_MEMORY_CARD_PATTERNS);

// Restore from blank patterns in the player inv
var playerInv = player.getInventory();
var blankPatternsAvailable = player.getAbilities().instabuild ? Integer.MAX_VALUE
: playerInv.countItem(AEItems.BLANK_PATTERN.asItem());
var blankPatternsUsed = 0;
for (int i = 0; i < desiredPatterns.size(); i++) {
// Don't restore junk
var pattern = PatternDetailsHelper.decodePattern(desiredPatterns.getStackInSlot(i),
host.getBlockEntity().getLevel(), true);
if (pattern == null) {
continue; // Skip junk / broken recipes
}

// Keep track of how many blank patterns we need
++blankPatternsUsed;
if (blankPatternsAvailable >= blankPatternsUsed) {
if (!patternInventory.addItems(pattern.getDefinition().toStack()).isEmpty()) {
AELog.warn("Failed to add pattern to pattern provider");
blankPatternsUsed--;
}
}
}

// Deduct the used blank patterns
if (blankPatternsUsed > 0 && !player.getAbilities().instabuild) {
new PlayerInternalInventory(playerInv)
.removeItems(blankPatternsUsed, AEItems.BLANK_PATTERN.stack(), null);
}

// Warn about not being able to restore all patterns due to lack of blank patterns
if (blankPatternsUsed > blankPatternsAvailable) {
player.sendSystemMessage(
PlayerMessages.MissingBlankPatterns.text(blankPatternsUsed - blankPatternsAvailable));
}
}
}

// Converts all patterns in this provider to blank patterns and give them to the player
private void clearPatternInventory(Player player) {
// Just clear it for creative mode players
if (player.getAbilities().instabuild) {
for (int i = 0; i < patternInventory.size(); i++) {
patternInventory.setItemDirect(i, ItemStack.EMPTY);
}
return;
}

var playerInv = player.getInventory();

// Clear out any existing patterns and give them to the player
var blankPatternCount = 0;
for (int i = 0; i < patternInventory.size(); i++) {
var pattern = patternInventory.getStackInSlot(i);
// Auto-Clear encoded patterns to allow them to stack
if (pattern.is(AEItems.CRAFTING_PATTERN.asItem())
|| pattern.is(AEItems.PROCESSING_PATTERN.asItem())
|| pattern.is(AEItems.BLANK_PATTERN.asItem())) {
blankPatternCount += pattern.getCount();
} else {
// Give back any non-blank-patterns individually
playerInv.placeItemBackInInventory(pattern);
}
patternInventory.setItemDirect(i, ItemStack.EMPTY);
}

// Place back the removed blank patterns all at once
if (blankPatternCount > 0) {
playerInv.placeItemBackInInventory(AEItems.BLANK_PATTERN.stack(blankPatternCount), false);
}
}

private class Ticker implements IGridTickable {

@Override
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/appeng/parts/crafting/PatternProviderPart.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.util.EnumSet;
import java.util.List;

import org.jetbrains.annotations.Nullable;

import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
Expand All @@ -42,6 +44,7 @@
import appeng.menu.locator.MenuLocators;
import appeng.parts.BasicStatePart;
import appeng.parts.PartModel;
import appeng.util.SettingsFrom;

public class PatternProviderPart extends BasicStatePart implements PatternProviderLogicHost {

Expand Down Expand Up @@ -112,6 +115,20 @@ public float getCableConnectionLength(AECableType cable) {
return 4;
}

@Override
public void exportSettings(SettingsFrom mode, CompoundTag output) {
if (mode == SettingsFrom.MEMORY_CARD) {
logic.exportSettings(output);
}
}

@Override
public void importSettings(SettingsFrom mode, CompoundTag input, @Nullable Player player) {
if (mode == SettingsFrom.MEMORY_CARD) {
logic.importSettings(input, player);
}
}

@Override
public boolean onPartActivate(Player p, InteractionHand hand, Vec3 pos) {
if (!p.getCommandSenderWorld().isClientSide()) {
Expand Down
34 changes: 34 additions & 0 deletions src/main/java/appeng/server/testplots/CraftingPatternHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package appeng.server.testplots;

import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.Level;

import appeng.api.crafting.PatternDetailsHelper;
import appeng.menu.AutoCraftingMenu;

public class CraftingPatternHelper {
public static ItemStack encodeShapelessCraftingRecipe(Level level, ItemStack... inputs) {
var container = new CraftingContainer(new AutoCraftingMenu(), 3, 3);
for (int i = 0; i < inputs.length; i++) {
container.setItem(i, inputs[i].copy());
}

var recipe = level.getRecipeManager().getRecipeFor(RecipeType.CRAFTING, container, level)
.orElseThrow(() -> new RuntimeException("Couldn't get a shapeless recipe for the provided input."));

var actualInputs = new ItemStack[9];
for (int i = 0; i < actualInputs.length; i++) {
actualInputs[i] = i < inputs.length ? inputs[i] : ItemStack.EMPTY;
}

return PatternDetailsHelper.encodeCraftingPattern(
recipe,
actualInputs,
recipe.getResultItem(),
false,
false);
}

}
62 changes: 62 additions & 0 deletions src/main/java/appeng/server/testplots/MemoryCardTestPlots.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,21 @@
import appeng.api.config.SchedulingMode;
import appeng.api.config.Settings;
import appeng.api.config.YesNo;
import appeng.api.crafting.PatternDetailsHelper;
import appeng.api.inventories.InternalInventory;
import appeng.api.stacks.AEFluidKey;
import appeng.api.stacks.AEItemKey;
import appeng.api.stacks.GenericStack;
import appeng.api.upgrades.IUpgradeableObject;
import appeng.api.util.IConfigurableObject;
import appeng.blockentity.crafting.PatternProviderBlockEntity;
import appeng.blockentity.misc.InterfaceBlockEntity;
import appeng.core.definitions.AEBlocks;
import appeng.core.definitions.AEItems;
import appeng.core.definitions.AEParts;
import appeng.items.tools.NetworkToolItem;
import appeng.parts.automation.ExportBusPart;
import appeng.parts.crafting.PatternProviderPart;
import appeng.parts.misc.InterfacePart;
import appeng.server.testworld.PlotBuilder;
import appeng.server.testworld.PlotTestHelper;
Expand Down Expand Up @@ -132,6 +135,65 @@ public static void testInterface(PlotBuilder plot) {
});
}

@TestPlot("memcard_pattern_provider")
public static void testPatternProvider(PlotBuilder plot) {
var origin = BlockPos.ZERO;
plot.cable(origin).part(Direction.WEST, AEParts.PATTERN_PROVIDER);
plot.block(origin.east(), AEBlocks.PATTERN_PROVIDER);

plot.test(helper -> {

// Create arbitrary processing+crafting patterns
var processingPattern = PatternDetailsHelper.encodeProcessingPattern(
new GenericStack[] { new GenericStack(AEFluidKey.of(Fluids.WATER), 1) },
new GenericStack[] { new GenericStack(AEFluidKey.of(Fluids.LAVA), 1) });
var craftingPattern = CraftingPatternHelper.encodeShapelessCraftingRecipe(
helper.getLevel(), Items.OAK_LOG.getDefaultInstance());
var differentCraftingPattern = CraftingPatternHelper.encodeShapelessCraftingRecipe(
helper.getLevel(), Items.SPRUCE_LOG.getDefaultInstance());

var from = (PatternProviderBlockEntity) helper.getBlockEntity(BlockPos.ZERO.east());
var to = helper.getPart(BlockPos.ZERO, Direction.WEST, PatternProviderPart.class);

var player = helper.makeMockPlayer();
player.getInventory().placeItemBackInInventory(AEItems.BLANK_PATTERN.stack(64));

// This should be copied to the other pattern provider
var fromPatternInv = from.getLogic().getPatternInv();
fromPatternInv.addItems(processingPattern);
fromPatternInv.addItems(craftingPattern);
// This should be cleared into a blank pattern and moved to the player
var toPatternInv = to.getLogic().getPatternInv();
toPatternInv.addItems(differentCraftingPattern.copy());
toPatternInv.addItems(differentCraftingPattern.copy());
toPatternInv.addItems(differentCraftingPattern.copy());

var blankPatternsBefore = player.getInventory().countItem(AEItems.BLANK_PATTERN.asItem());

// Export&Import settings
var settings = new CompoundTag();
from.exportSettings(SettingsFrom.MEMORY_CARD, settings, null);
to.importSettings(SettingsFrom.MEMORY_CARD, settings, player);

var blankPatternsAfter = player.getInventory().countItem(AEItems.BLANK_PATTERN.asItem());

// There was one more pattern in "to" than requested, so the player should be given one blank pattern back
helper.check(blankPatternsAfter == blankPatternsBefore + 1,
"Expected player to be given back one blank pattern");

// Compare the pattern inventories
for (int i = 0; i < fromPatternInv.size(); i++) {
var fromItem = fromPatternInv.getStackInSlot(i);
var toItem = toPatternInv.getStackInSlot(i);
if (!ItemStack.isSameItemSameTags(fromItem, toItem)) {
helper.fail("Mismatch in slot " + i, origin.east());
}
}

helper.succeed();
});
}

private static InternalInventory addNetworkToolToPlayer(Player player) {
// Add all upgrades to the player inventory in a network tool, so restoring settings can install them
player.addItem(AEItems.NETWORK_TOOL.stack());
Expand Down