diff --git a/build.gradle.kts b/build.gradle.kts index e6c0b5ff..cde42d92 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper import net.fabricmc.loom.api.LoomGradleExtensionAPI import net.fabricmc.loom.task.RemapJarTask @@ -190,6 +192,10 @@ for (platform in property("enabledPlatforms").toString().split(',')) { } tasks { + withType { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } + processResources { val commonProps by extra { mapOf( "version" to project.version, @@ -200,6 +206,12 @@ for (platform in property("enabledPlatforms").toString().split(',')) { } inputs.properties(commonProps) + + from(fileTree(project(":common").file("src/generated/resources"))) { + val conventionTags = ObjectMapper().readValue(file("convention_tags.json"), object: TypeReference>() {}) + expand(conventionTags) + exclude("**/.cache") + } } shadowJar { diff --git a/common/src/main/java/gripe/_90/megacells/crafting/MEGADecompressionPattern.java b/common/src/main/java/gripe/_90/megacells/crafting/DecompressionPattern.java similarity index 51% rename from common/src/main/java/gripe/_90/megacells/crafting/MEGADecompressionPattern.java rename to common/src/main/java/gripe/_90/megacells/crafting/DecompressionPattern.java index 224977de..ae88cec1 100644 --- a/common/src/main/java/gripe/_90/megacells/crafting/MEGADecompressionPattern.java +++ b/common/src/main/java/gripe/_90/megacells/crafting/DecompressionPattern.java @@ -2,8 +2,6 @@ import java.util.Objects; -import net.minecraft.core.NonNullList; -import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; @@ -11,26 +9,26 @@ import appeng.api.stacks.AEItemKey; import appeng.api.stacks.AEKey; import appeng.api.stacks.GenericStack; -import appeng.items.AEBaseItem; -public class MEGADecompressionPattern implements IPatternDetails { +public class DecompressionPattern implements IPatternDetails { private final AEItemKey definition; - private final AEItemKey input; - private final IInput[] inputs; - private final GenericStack[] outputs; + private final AEItemKey base; + private final AEItemKey variant; + private final int factor; + private final boolean toCompress; - public MEGADecompressionPattern(AEItemKey definition) { + public DecompressionPattern(ItemStack stack) { + this(Objects.requireNonNull(AEItemKey.of(stack))); + } + + public DecompressionPattern(AEItemKey definition) { this.definition = definition; var tag = Objects.requireNonNull(definition.getTag()); - this.input = DecompressionPatternEncoding.getCompressed(tag); - - var decompressed = DecompressionPatternEncoding.getDecompressed(tag); - var factor = DecompressionPatternEncoding.getFactor(tag); - var output = decompressed.toStack(factor); - - this.inputs = new IInput[] {new Input()}; - this.outputs = new GenericStack[] {GenericStack.fromItemStack(output)}; + base = DecompressionPatternEncoding.getBase(tag); + variant = DecompressionPatternEncoding.getVariant(tag); + factor = DecompressionPatternEncoding.getFactor(tag); + toCompress = DecompressionPatternEncoding.getToCompress(tag); } @Override @@ -40,19 +38,19 @@ public AEItemKey getDefinition() { @Override public IInput[] getInputs() { - return inputs; + return new IInput[] {toCompress ? new Input(base, factor) : new Input(variant, 1)}; } @Override public GenericStack[] getOutputs() { - return outputs; + return new GenericStack[] {toCompress ? new GenericStack(variant, 1) : new GenericStack(base, factor)}; } @Override public boolean equals(Object obj) { return obj != null && obj.getClass() == getClass() - && ((MEGADecompressionPattern) obj).definition.equals(definition); + && ((DecompressionPattern) obj).definition.equals(definition); } @Override @@ -60,7 +58,7 @@ public int hashCode() { return definition.hashCode(); } - private class Input implements IInput { + private record Input(AEItemKey input, long multiplier) implements IInput { @Override public GenericStack[] getPossibleInputs() { return new GenericStack[] {new GenericStack(input, 1)}; @@ -68,7 +66,7 @@ public GenericStack[] getPossibleInputs() { @Override public long getMultiplier() { - return 1; + return multiplier; } @Override @@ -81,15 +79,4 @@ public AEKey getRemainingKey(AEKey template) { return null; } } - - public static class Item extends AEBaseItem { - public Item(Properties properties) { - super(properties); - } - - @Override - public void fillItemCategory(CreativeModeTab tab, NonNullList list) { - // Don't show in creative mode, this isn't meant to be used as an item anyway - } - } } diff --git a/common/src/main/java/gripe/_90/megacells/crafting/DecompressionPatternDecoder.java b/common/src/main/java/gripe/_90/megacells/crafting/DecompressionPatternDecoder.java deleted file mode 100644 index 3fc75d95..00000000 --- a/common/src/main/java/gripe/_90/megacells/crafting/DecompressionPatternDecoder.java +++ /dev/null @@ -1,43 +0,0 @@ -package gripe._90.megacells.crafting; - -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.Level; - -import appeng.api.crafting.IPatternDetailsDecoder; -import appeng.api.stacks.AEItemKey; - -import gripe._90.megacells.MEGACells; - -public class DecompressionPatternDecoder implements IPatternDetailsDecoder { - public static final DecompressionPatternDecoder INSTANCE = new DecompressionPatternDecoder(); - - private DecompressionPatternDecoder() {} - - @Override - public boolean isEncodedPattern(ItemStack stack) { - return stack.getItem() instanceof MEGADecompressionPattern.Item; - } - - @Override - public MEGADecompressionPattern decodePattern(AEItemKey what, Level level) { - if (level == null - || what == null - || !(what.getItem() instanceof MEGADecompressionPattern.Item) - || !(what.hasTag())) { - return null; - } - - try { - return new MEGADecompressionPattern(what); - } catch (Exception e) { - MEGACells.LOGGER.warn( - "Could not decode an invalid decompression pattern %s: %s".formatted(what.getTag(), e)); - return null; - } - } - - @Override - public MEGADecompressionPattern decodePattern(ItemStack what, Level level, boolean tryRecovery) { - return decodePattern(AEItemKey.of(what), level); - } -} diff --git a/common/src/main/java/gripe/_90/megacells/crafting/DecompressionPatternEncoding.java b/common/src/main/java/gripe/_90/megacells/crafting/DecompressionPatternEncoding.java index 044c6b67..b7719923 100644 --- a/common/src/main/java/gripe/_90/megacells/crafting/DecompressionPatternEncoding.java +++ b/common/src/main/java/gripe/_90/megacells/crafting/DecompressionPatternEncoding.java @@ -6,19 +6,22 @@ import appeng.api.stacks.AEItemKey; +import gripe._90.megacells.util.CompressionVariant; + public class DecompressionPatternEncoding { - private static final String NBT_COMPRESSED = "compressed"; - private static final String NBT_DECOMPRESSED = "decompressed"; + private static final String NBT_BASE = "base"; + private static final String NBT_VARIANT = "variant"; private static final String NBT_FACTOR = "factor"; + private static final String NBT_TO_COMPRESS = "toCompress"; - public static AEItemKey getCompressed(CompoundTag nbt) { - Objects.requireNonNull(nbt, "Pattern must have a compressed tag."); - return AEItemKey.fromTag(nbt.getCompound(NBT_COMPRESSED)); + public static AEItemKey getBase(CompoundTag nbt) { + Objects.requireNonNull(nbt, "Pattern must have a base tag."); + return AEItemKey.fromTag(nbt.getCompound(NBT_BASE)); } - public static AEItemKey getDecompressed(CompoundTag nbt) { - Objects.requireNonNull(nbt, "Pattern must have a decompressed tag."); - return AEItemKey.fromTag(nbt.getCompound(NBT_DECOMPRESSED)); + public static AEItemKey getVariant(CompoundTag nbt) { + Objects.requireNonNull(nbt, "Pattern must have a variant tag."); + return AEItemKey.fromTag(nbt.getCompound(NBT_VARIANT)); } public static int getFactor(CompoundTag nbt) { @@ -26,9 +29,15 @@ public static int getFactor(CompoundTag nbt) { return nbt.getInt(NBT_FACTOR); } - public static void encode(CompoundTag tag, AEItemKey compressed, AEItemKey decompressed, int factor) { - tag.put(NBT_COMPRESSED, compressed.toTag()); - tag.put(NBT_DECOMPRESSED, decompressed.toTag()); - tag.putInt(NBT_FACTOR, factor); + public static boolean getToCompress(CompoundTag nbt) { + Objects.requireNonNull(nbt, "Pattern must have a toCompress tag."); + return nbt.getBoolean(NBT_TO_COMPRESS); + } + + public static void encode(CompoundTag tag, AEItemKey base, CompressionVariant variant, boolean toCompress) { + tag.put(NBT_VARIANT, variant.item().toTag()); + tag.put(NBT_BASE, base.toTag()); + tag.putInt(NBT_FACTOR, variant.factor()); + tag.putBoolean(NBT_TO_COMPRESS, toCompress); } } diff --git a/common/src/main/java/gripe/_90/megacells/crafting/DecompressionPatternItem.java b/common/src/main/java/gripe/_90/megacells/crafting/DecompressionPatternItem.java new file mode 100644 index 00000000..f147c8c9 --- /dev/null +++ b/common/src/main/java/gripe/_90/megacells/crafting/DecompressionPatternItem.java @@ -0,0 +1,39 @@ +package gripe._90.megacells.crafting; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; + +import appeng.api.stacks.AEItemKey; +import appeng.crafting.pattern.EncodedPatternItem; + +import gripe._90.megacells.MEGACells; + +public class DecompressionPatternItem extends EncodedPatternItem { + public DecompressionPatternItem(Properties properties) { + super(properties); + } + + @Nullable + @Override + public DecompressionPattern decode(ItemStack stack, Level level, boolean tryRecovery) { + return decode(AEItemKey.of(stack), level); + } + + @Nullable + @Override + public DecompressionPattern decode(AEItemKey what, Level level) { + if (what == null || !(what.hasTag())) { + return null; + } + + try { + return new DecompressionPattern(what); + } catch (Exception e) { + MEGACells.LOGGER.warn( + "Could not decode an invalid decompression pattern %s: %s".formatted(what.getTag(), e)); + return null; + } + } +} diff --git a/common/src/main/java/gripe/_90/megacells/service/DecompressionService.java b/common/src/main/java/gripe/_90/megacells/crafting/DecompressionService.java similarity index 59% rename from common/src/main/java/gripe/_90/megacells/service/DecompressionService.java rename to common/src/main/java/gripe/_90/megacells/crafting/DecompressionService.java index ce2ac68b..9133f89f 100644 --- a/common/src/main/java/gripe/_90/megacells/service/DecompressionService.java +++ b/common/src/main/java/gripe/_90/megacells/crafting/DecompressionService.java @@ -1,4 +1,4 @@ -package gripe._90.megacells.service; +package gripe._90.megacells.crafting; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -10,10 +10,11 @@ import net.minecraft.world.item.ItemStack; +import appeng.api.crafting.IPatternDetails; import appeng.api.networking.IGridNode; import appeng.api.networking.IGridService; import appeng.api.networking.IGridServiceProvider; -import appeng.api.stacks.AEItemKey; +import appeng.api.networking.crafting.ICraftingProvider; import appeng.api.storage.MEStorage; import appeng.api.storage.cells.StorageCell; import appeng.blockentity.storage.ChestBlockEntity; @@ -21,12 +22,10 @@ import appeng.me.storage.DelegatingMEInventory; import appeng.me.storage.DriveWatcher; -import gripe._90.megacells.crafting.DecompressionPatternEncoding; import gripe._90.megacells.definition.MEGAItems; import gripe._90.megacells.item.cell.BulkCellInventory; +import gripe._90.megacells.item.part.DecompressionModulePart; -import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; @@ -55,9 +54,10 @@ public class DecompressionService implements IGridService, IGridServiceProvider } } - private final Set> decompressionChains = new ObjectLinkedOpenHashSet<>(); private final List chests = new ObjectArrayList<>(); private final List drives = new ObjectArrayList<>(); + private final List patterns = new ObjectArrayList<>(); + private final List modules = new ObjectArrayList<>(); @Override public void addNode(IGridNode node) { @@ -68,6 +68,10 @@ public void addNode(IGridNode node) { if (node.getOwner() instanceof DriveBlockEntity drive) { drives.add(drive); } + + if (node.getOwner() instanceof DecompressionModulePart module) { + modules.add(module); + } } @Override @@ -79,24 +83,40 @@ public void removeNode(IGridNode node) { if (node.getOwner() instanceof DriveBlockEntity drive) { drives.remove(drive); } + + if (node.getOwner() instanceof DecompressionModulePart module) { + modules.remove(module); + } } @Override public void onServerStartTick() { - decompressionChains.clear(); + patterns.clear(); - try { - for (var chest : chests) { - addChain(getCellByChest(chest)); + for (var chest : chests) { + var cell = getCellByChest(chest); + + if (cell instanceof BulkCellInventory bulkCell && bulkCell.isCompressionEnabled()) { + patterns.addAll(generatePatterns(bulkCell)); } + } + + for (var drive : drives) { + for (var i = 0; i < drive.getCellCount(); i++) { + try { + var cell = getCellByDriveSlot(drive, i); - for (var drive : drives) { - for (int i = 0; i < drive.getCellCount(); i++) { - addChain(getCellByDriveSlot(drive, i)); + if (cell instanceof BulkCellInventory bulkCell && bulkCell.isCompressionEnabled()) { + patterns.addAll(generatePatterns(bulkCell)); + } + } catch (Throwable e) { + throw new RuntimeException(e); } } - } catch (Throwable e) { - throw new RuntimeException("Failed to invoke DecompressionService method handles", e); + } + + for (var module : modules) { + ICraftingProvider.requestUpdate(module.getMainNode()); } } @@ -110,62 +130,44 @@ private StorageCell getCellByDriveSlot(DriveBlockEntity drive, int slot) throws return watchers[slot] != null ? (StorageCell) DRIVE_DELEGATE_HANDLE.invoke(watchers[slot]) : null; } - private Object2IntMap getChain(BulkCellInventory cell) { - if (cell.compressionEnabled) { - return CompressionService.INSTANCE - .getChain(cell.getStoredItem()) - .map(c -> { - var keys = new ObjectArrayList<>(c.keySet()); - Collections.reverse(keys); - - var decompressed = new Object2IntLinkedOpenHashMap(); - var highest = keys.indexOf(cell.getHighestCompressed()); - - if (highest > -1) { - for (var key : keys.subList(highest, keys.size())) { - decompressed.put(key, c.getInt(key)); - } - } - - return decompressed; - }) - .orElseGet(Object2IntLinkedOpenHashMap::new); - } - - return new Object2IntLinkedOpenHashMap<>(); + public List getPatterns() { + return Collections.unmodifiableList(patterns); } - private void addChain(StorageCell cell) { - if (!(cell instanceof BulkCellInventory bulkCell)) { - return; + private Set generatePatterns(BulkCellInventory cell) { + var fullChain = cell.getCompressionChain(); + + if (fullChain.isEmpty()) { + return Set.of(); } - var chain = getChain(bulkCell); + var patterns = new ObjectLinkedOpenHashSet(); + var decompressionChain = fullChain.reversed(); - if (!chain.isEmpty()) { - decompressionChains.add(chain); - } - } + for (var variant : decompressionChain) { + if (variant == decompressionChain.last()) { + continue; + } - public Set> getDecompressionChains() { - return decompressionChains; - } + var pattern = new ItemStack(MEGAItems.DECOMPRESSION_PATTERN); + var decompressed = decompressionChain.get(decompressionChain.indexOf(variant) + 1); + + DecompressionPatternEncoding.encode(pattern.getOrCreateTag(), decompressed.item(), variant, false); + patterns.add(new DecompressionPattern(pattern)); + } - public Set getDecompressionPatterns(Object2IntMap compressionChain) { - var variants = new ObjectArrayList<>(compressionChain.keySet()); - var patterns = new ObjectLinkedOpenHashSet(); + var compressionChain = fullChain.subList(decompressionChain.size() - 1, fullChain.size()); - for (var variant : variants) { - if (variant == variants.get(variants.size() - 1)) { + for (var variant : compressionChain) { + if (variant == compressionChain.get(0)) { continue; } var pattern = new ItemStack(MEGAItems.DECOMPRESSION_PATTERN); - var decompressed = variants.get(variants.indexOf(variant) + 1); - var factor = compressionChain.getInt(decompressed); + var decompressed = compressionChain.get(compressionChain.indexOf(variant) - 1); - DecompressionPatternEncoding.encode(pattern.getOrCreateTag(), variant, decompressed, factor); - patterns.add(AEItemKey.of(pattern)); + DecompressionPatternEncoding.encode(pattern.getOrCreateTag(), decompressed.item(), variant, true); + patterns.add(new DecompressionPattern(pattern)); } return patterns; diff --git a/common/src/main/java/gripe/_90/megacells/definition/MEGAItems.java b/common/src/main/java/gripe/_90/megacells/definition/MEGAItems.java index 41c4ae1e..9217b7e6 100644 --- a/common/src/main/java/gripe/_90/megacells/definition/MEGAItems.java +++ b/common/src/main/java/gripe/_90/megacells/definition/MEGAItems.java @@ -19,7 +19,7 @@ import appeng.menu.me.common.MEStorageMenu; import gripe._90.megacells.MEGACells; -import gripe._90.megacells.crafting.MEGADecompressionPattern; +import gripe._90.megacells.crafting.DecompressionPatternItem; import gripe._90.megacells.item.MEGABulkCell; import gripe._90.megacells.item.MEGAPortableCell; @@ -88,8 +88,8 @@ public static List> getItems() { item("Greater Energy Card", "greater_energy_card", p -> new EnergyCardItem(p, 8)); public static final ItemDefinition COMPRESSION_CARD = item("Compression Card", "compression_card", UpgradeCardItem::new); - public static final ItemDefinition DECOMPRESSION_PATTERN = - item("Decompression Pattern", "decompression_pattern", MEGADecompressionPattern.Item::new); + public static final ItemDefinition DECOMPRESSION_PATTERN = + item("Decompression Pattern", "decompression_pattern", DecompressionPatternItem::new); // spotless:on public static List> getItemCells() { diff --git a/common/src/main/java/gripe/_90/megacells/definition/MEGAParts.java b/common/src/main/java/gripe/_90/megacells/definition/MEGAParts.java index 4498928e..5f8ac929 100644 --- a/common/src/main/java/gripe/_90/megacells/definition/MEGAParts.java +++ b/common/src/main/java/gripe/_90/megacells/definition/MEGAParts.java @@ -11,8 +11,8 @@ import appeng.items.parts.PartItem; import appeng.items.parts.PartModelsHelper; -import gripe._90.megacells.part.DecompressionModulePart; -import gripe._90.megacells.part.MEGAPatternProviderPart; +import gripe._90.megacells.item.part.DecompressionModulePart; +import gripe._90.megacells.item.part.MEGAPatternProviderPart; public final class MEGAParts { diff --git a/common/src/main/java/gripe/_90/megacells/definition/MEGATags.java b/common/src/main/java/gripe/_90/megacells/definition/MEGATags.java index e5672a54..51dd3055 100644 --- a/common/src/main/java/gripe/_90/megacells/definition/MEGATags.java +++ b/common/src/main/java/gripe/_90/megacells/definition/MEGATags.java @@ -1,14 +1,16 @@ package gripe._90.megacells.definition; import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; +import gripe._90.megacells.MEGACells; + public final class MEGATags { - public static final TagKey MEGA_PATTERN_PROVIDER = itemTag("megacells:mega_pattern_provider"); + public static final TagKey MEGA_PATTERN_PROVIDER = itemTag("mega_pattern_provider"); + public static final TagKey COMPRESSION_OVERRIDES = itemTag("compression_overrides"); private static TagKey itemTag(String name) { - return TagKey.create(Registry.ITEM_REGISTRY, new ResourceLocation(name)); + return TagKey.create(Registry.ITEM_REGISTRY, MEGACells.makeId(name)); } } diff --git a/common/src/main/java/gripe/_90/megacells/item/MEGABulkCell.java b/common/src/main/java/gripe/_90/megacells/item/MEGABulkCell.java index 49db5c49..454515c9 100644 --- a/common/src/main/java/gripe/_90/megacells/item/MEGABulkCell.java +++ b/common/src/main/java/gripe/_90/megacells/item/MEGABulkCell.java @@ -75,7 +75,7 @@ public void appendHoverText(ItemStack is, Level level, @NotNull List } lines.add(Tooltips.of(MEGATranslations.Compression.text( - inv.compressionEnabled + inv.isCompressionEnabled() ? MEGATranslations.Enabled.text().withStyle(ChatFormatting.GREEN) : MEGATranslations.Disabled.text().withStyle(ChatFormatting.RED)))); } diff --git a/common/src/main/java/gripe/_90/megacells/item/cell/BulkCellInventory.java b/common/src/main/java/gripe/_90/megacells/item/cell/BulkCellInventory.java index fda62508..fcb4adbc 100644 --- a/common/src/main/java/gripe/_90/megacells/item/cell/BulkCellInventory.java +++ b/common/src/main/java/gripe/_90/megacells/item/cell/BulkCellInventory.java @@ -3,10 +3,6 @@ import static gripe._90.megacells.definition.MEGAItems.COMPRESSION_CARD; import java.math.BigInteger; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.function.Function; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; @@ -20,66 +16,44 @@ import appeng.api.storage.cells.CellState; import appeng.api.storage.cells.ISaveProvider; import appeng.api.storage.cells.StorageCell; -import appeng.util.prioritylist.IPartitionList; import gripe._90.megacells.item.MEGABulkCell; -import gripe._90.megacells.service.CompressionService; - -import it.unimi.dsi.fastutil.Pair; -import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; +import gripe._90.megacells.util.CompressionChain; +import gripe._90.megacells.util.CompressionService; public class BulkCellInventory implements StorageCell { private static final String KEY = "key"; private static final String UNIT_COUNT = "smallestUnitCount"; + private static final long STACK_LIMIT = (long) Math.pow(2, 42); private final ISaveProvider container; - private final ItemStack i; + private final ItemStack stack; private AEItemKey storedItem; private final AEItemKey filterItem; - private final IPartitionList partitionList; - private final Object2IntMap compressed; - private final Object2IntMap decompressed; + private final boolean compressionEnabled; + private final CompressionChain compressionChain; private BigInteger unitCount; - private AEItemKey highestCompressed; - private final long unitFactor; - public final boolean compressionEnabled; + private final BigInteger unitFactor; private boolean isPersisted = true; - public BulkCellInventory(MEGABulkCell cell, ItemStack o, ISaveProvider container) { - this.i = o; + public BulkCellInventory(MEGABulkCell cell, ItemStack stack, ISaveProvider container) { + this.stack = stack; this.container = container; - var config = cell.getConfigInventory(i); - this.filterItem = (AEItemKey) config.getKey(0); - - var builder = IPartitionList.builder(); - builder.addAll(config.keySet()); - this.partitionList = builder.build(); - - this.compressed = CompressionService.INSTANCE.getVariants(filterItem, false); - this.decompressed = CompressionService.INSTANCE.getVariants(filterItem, true); - this.unitFactor = decompressed.values().intStream().asLongStream().reduce(1, Math::multiplyExact); + filterItem = (AEItemKey) cell.getConfigInventory(this.stack).getKey(0); + storedItem = getTag().contains(KEY) ? AEItemKey.fromTag(getTag().getCompound(KEY)) : null; + unitCount = !getTag().getString(UNIT_COUNT).isEmpty() + ? new BigInteger(getTag().getString(UNIT_COUNT)) + : BigInteger.ZERO; - this.storedItem = getTag().contains(KEY) ? AEItemKey.fromTag(getTag().getCompound(KEY)) : null; - this.unitCount = retrieveUnitCount(); - this.highestCompressed = storedItem; - - this.compressionEnabled = cell.getUpgrades(i).isInstalled(COMPRESSION_CARD); - } - - private BigInteger retrieveUnitCount() { - // TODO 1.19.3 / 1.20.0: Remove pre-2.0.0 bulk cell conversion (again) - if (getTag().contains("count")) { - return BigInteger.valueOf(getTag().getLong("count")).multiply(BigInteger.valueOf(unitFactor)); - } else { - return !getTag().getString(UNIT_COUNT).equals("") - ? new BigInteger(getTag().getString(UNIT_COUNT)) - : BigInteger.ZERO; - } + compressionEnabled = cell.getUpgrades(this.stack).isInstalled(COMPRESSION_CARD); + compressionChain = CompressionService.INSTANCE + .getChain(storedItem != null ? storedItem : filterItem) + .orElseGet(CompressionChain::new); + unitFactor = compressionChain.unitFactor(storedItem != null ? storedItem : filterItem); } private long clampedLong(BigInteger toClamp, long limit) { @@ -87,7 +61,7 @@ private long clampedLong(BigInteger toClamp, long limit) { } private CompoundTag getTag() { - return i.getOrCreateTag(); + return stack.getOrCreateTag(); } @Override @@ -108,20 +82,24 @@ public AEItemKey getStoredItem() { } public long getStoredQuantity() { - return clampedLong(unitCount.divide(BigInteger.valueOf(unitFactor)), Long.MAX_VALUE); - } - - public AEItemKey getHighestCompressed() { - return highestCompressed; + return clampedLong(unitCount.divide(unitFactor), Long.MAX_VALUE); } public AEItemKey getFilterItem() { return filterItem; } + public boolean isCompressionEnabled() { + return compressionEnabled; + } + + public CompressionChain getCompressionChain() { + return compressionChain; + } + @Override public double getIdleDrain() { - return 10.0f; + return 5.0f; } @Override @@ -130,23 +108,26 @@ public long insert(AEKey what, long amount, Actionable mode, IActionSource sourc return 0; } - if (!compressionEnabled && (!partitionList.isListed(what) || storedItem != null && !storedItem.equals(what))) { + if (filterItem == null || (storedItem != null && !filterItem.equals(storedItem))) { + return 0; + } + + if (!compressionEnabled && !what.equals(filterItem)) { return 0; } - if (compressionEnabled - && !partitionList.isListed(what) - && !compressed.containsKey(item) - && !decompressed.containsKey(item)) { + if (compressionEnabled && !compressionChain.containsVariant(item)) { return 0; } - var units = BigInteger.valueOf(amount).multiply(compressedTransferFactor(item)); + var factor = compressionChain.unitFactor(item); + var units = BigInteger.valueOf(amount).multiply(factor); if (mode == Actionable.MODULATE) { if (storedItem == null) { storedItem = filterItem; } + unitCount = unitCount.add(units); saveChanges(); } @@ -160,21 +141,20 @@ public long extract(AEKey what, long amount, Actionable mode, IActionSource sour return 0; } - var itemCount = unitCount.divide(BigInteger.valueOf(unitFactor)); - if (!compressionEnabled && (itemCount.signum() < 1 || !storedItem.equals(what))) { + if (filterItem == null || (storedItem != null && !filterItem.equals(storedItem))) { return 0; } - if (compressionEnabled - && !storedItem.equals(what) - && !filterItem.equals(what) - && !compressed.containsKey(item) - && !decompressed.containsKey(item)) { + if (!compressionEnabled && (unitCount.divide(unitFactor).signum() < 1 || !what.equals(storedItem))) { return 0; } - var extractionFactor = compressedTransferFactor(item); - var units = BigInteger.valueOf(amount).multiply(extractionFactor); + if (compressionEnabled && !compressionChain.containsVariant(item)) { + return 0; + } + + var factor = compressionChain.unitFactor(item); + var units = BigInteger.valueOf(amount).multiply(factor); var currentUnitCount = unitCount; if (currentUnitCount.compareTo(units) <= 0) { @@ -183,41 +163,18 @@ public long extract(AEKey what, long amount, Actionable mode, IActionSource sour unitCount = BigInteger.ZERO; saveChanges(); } - return clampedLong(currentUnitCount.divide(extractionFactor), Long.MAX_VALUE); + + return clampedLong(currentUnitCount.divide(factor), Long.MAX_VALUE); } else { if (mode == Actionable.MODULATE) { unitCount = unitCount.subtract(units); saveChanges(); } - return clampedLong(units.divide(extractionFactor), Long.MAX_VALUE); - } - } - private BigInteger compressedTransferFactor(AEItemKey what) { - if (compressed.getInt(what) > 0) { - return compressedTransferFactor(compressed, unitFactor, keys -> Pair.of(0, keys.indexOf(what) + 1)); - } else if (decompressed.getInt(what) > 0) { - return compressedTransferFactor(decompressed, 1, keys -> Pair.of(keys.indexOf(what) + 1, keys.size())); - } else { - return BigInteger.valueOf(unitFactor); + return clampedLong(units.divide(factor), Long.MAX_VALUE); } } - private BigInteger compressedTransferFactor( - Object2IntMap variants, long baseFactor, Function, Pair> subLister) { - var variantKeys = new LinkedList<>(variants.keySet()); - var toStored = new Object2IntLinkedOpenHashMap<>(variants); - - var range = subLister.apply(variantKeys); - toStored.keySet().retainAll(variantKeys.subList(range.first(), range.second())); - - for (var i : toStored.values()) { - baseFactor *= i; - } - - return BigInteger.valueOf(baseFactor); - } - private void saveChanges() { isPersisted = false; @@ -243,52 +200,30 @@ public void persist() { getTag().putString(UNIT_COUNT, unitCount.toString()); } - // remove pre-2.0.0 count tag - getTag().remove("count"); - isPersisted = true; } @Override public void getAvailableStacks(KeyCounter out) { if (storedItem != null) { - var stackLimit = (long) Math.pow(2, 42); - - if (compressionEnabled && storedItem.equals(filterItem)) { - var allVariants = new Object2IntLinkedOpenHashMap(); - - if (!decompressed.isEmpty()) { - var decompressedKeys = new LinkedList<>(decompressed.keySet()); - Collections.reverse(decompressedKeys); - decompressedKeys.forEach(k -> allVariants.put(k, decompressed.getInt(k))); - - allVariants.put(storedItem, decompressed.getInt(decompressedKeys.getLast())); - allVariants.putAll(compressed); - } else if (!compressed.isEmpty()) { - allVariants.put( - storedItem, - compressed.values().intStream().findFirst().orElseThrow()); - allVariants.putAll(compressed); - } else { - allVariants.put(storedItem, 1); - } - + if (compressionEnabled && storedItem.equals(filterItem) && !compressionChain.isEmpty()) { var count = unitCount; + var chain = compressionChain.lastMultiplierSwapped(); - for (var variant : allVariants.keySet()) { - var compressionFactor = BigInteger.valueOf(allVariants.getInt(variant)); + for (var variant : chain) { + var compressionFactor = BigInteger.valueOf(variant.factor()); + var key = variant.item(); - if (count.divide(compressionFactor).signum() == 1 && variant != allVariants.lastKey()) { - out.add(variant, clampedLong(count.remainder(compressionFactor), stackLimit)); + if (count.divide(compressionFactor).signum() == 1 && variant != chain.last()) { + out.add(key, count.remainder(compressionFactor).longValue()); count = count.divide(compressionFactor); } else { - out.add(variant, clampedLong(count, stackLimit)); - highestCompressed = variant; + out.add(key, clampedLong(count, STACK_LIMIT)); break; } } } else { - out.add(storedItem, clampedLong(unitCount.divide(BigInteger.valueOf(unitFactor)), stackLimit)); + out.add(storedItem, clampedLong(unitCount.divide(unitFactor), STACK_LIMIT)); } } } @@ -296,11 +231,11 @@ public void getAvailableStacks(KeyCounter out) { @Override public boolean isPreferredStorageFor(AEKey what, IActionSource source) { return what instanceof AEItemKey item - && (partitionList.isListed(item) || compressed.containsKey(item) || decompressed.containsKey(item)); + && (item.equals(storedItem) || item.equals(filterItem) || compressionChain.containsVariant(item)); } @Override public Component getDescription() { - return i.getHoverName(); + return stack.getHoverName(); } } diff --git a/common/src/main/java/gripe/_90/megacells/part/DecompressionModulePart.java b/common/src/main/java/gripe/_90/megacells/item/part/DecompressionModulePart.java similarity index 61% rename from common/src/main/java/gripe/_90/megacells/part/DecompressionModulePart.java rename to common/src/main/java/gripe/_90/megacells/item/part/DecompressionModulePart.java index bde0d8d9..4de8b625 100644 --- a/common/src/main/java/gripe/_90/megacells/part/DecompressionModulePart.java +++ b/common/src/main/java/gripe/_90/megacells/item/part/DecompressionModulePart.java @@ -1,4 +1,4 @@ -package gripe._90.megacells.part; +package gripe._90.megacells.item.part; import java.util.List; @@ -21,19 +21,16 @@ import appeng.parts.PartModel; import gripe._90.megacells.MEGACells; -import gripe._90.megacells.crafting.DecompressionPatternDecoder; -import gripe._90.megacells.crafting.MEGADecompressionPattern; -import gripe._90.megacells.service.DecompressionService; +import gripe._90.megacells.crafting.DecompressionPattern; +import gripe._90.megacells.crafting.DecompressionService; import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; public class DecompressionModulePart extends AEBasePart implements ICraftingProvider, IGridTickable { @PartModels public static final IPartModel MODEL = new PartModel(MEGACells.makeId("part/decompression_module")); - private final List patterns = new ObjectArrayList<>(); private final Object2LongMap outputs = new Object2LongOpenHashMap<>(); public DecompressionModulePart(IPartItem partItem) { @@ -47,7 +44,8 @@ public DecompressionModulePart(IPartItem partItem) { @Override public List getAvailablePatterns() { - return patterns; + var grid = getMainNode().getGrid(); + return grid != null ? grid.getService(DecompressionService.class).getPatterns() : List.of(); } @Override @@ -57,18 +55,20 @@ public int getPatternPriority() { @Override public boolean pushPattern(IPatternDetails patternDetails, KeyCounter[] inputHolder) { - if (!getMainNode().isActive() || !(patternDetails instanceof MEGADecompressionPattern pattern)) { + if (!getMainNode().isActive() || !(patternDetails instanceof DecompressionPattern pattern)) { return false; } var output = pattern.getPrimaryOutput(); outputs.mergeLong(output.what(), output.amount(), Long::sum); + + getMainNode().ifPresent((grid, node) -> grid.getTickManager().alertDevice(node)); return true; } @Override public boolean isBusy() { - return !outputs.isEmpty(); + return false; } @Override @@ -84,40 +84,23 @@ public IPartModel getStaticModels() { @Override public TickingRequest getTickingRequest(IGridNode node) { - return new TickingRequest(1, 1, false, false); + return new TickingRequest(1, 1, outputs.isEmpty(), true); } @Override public TickRateModulation tickingRequest(IGridNode node, int ticksSinceLastCall) { - patterns.clear(); - var grid = getMainNode().getGrid(); - - if (grid != null) { - var decompressionService = grid.getService(DecompressionService.class); + var storage = node.getGrid().getStorageService().getInventory(); - for (var chain : decompressionService.getDecompressionChains()) { - var patternItems = decompressionService.getDecompressionPatterns(chain); - var decodedPatterns = patternItems.stream() - .map(p -> DecompressionPatternDecoder.INSTANCE.decodePattern(p, getLevel())); - patterns.addAll(decodedPatterns.toList()); - } - - var storage = grid.getStorageService(); + for (var output : outputs.object2LongEntrySet()) { + var what = output.getKey(); + var amount = output.getLongValue(); + var inserted = storage.insert(what, amount, Actionable.MODULATE, IActionSource.ofMachine(this)); - for (var output : outputs.object2LongEntrySet()) { - var what = output.getKey(); - var amount = output.getLongValue(); - var inserted = - storage.getInventory().insert(what, amount, Actionable.MODULATE, IActionSource.ofMachine(this)); - - if (inserted >= amount) { - outputs.removeLong(what); - } else if (inserted > 0) { - outputs.put(what, amount - inserted); - } + if (inserted >= amount) { + outputs.removeLong(what); + } else if (inserted > 0) { + outputs.put(what, amount - inserted); } - - ICraftingProvider.requestUpdate(getMainNode()); } return TickRateModulation.URGENT; diff --git a/common/src/main/java/gripe/_90/megacells/part/MEGAPatternProviderPart.java b/common/src/main/java/gripe/_90/megacells/item/part/MEGAPatternProviderPart.java similarity index 98% rename from common/src/main/java/gripe/_90/megacells/part/MEGAPatternProviderPart.java rename to common/src/main/java/gripe/_90/megacells/item/part/MEGAPatternProviderPart.java index 71128188..2a34b107 100644 --- a/common/src/main/java/gripe/_90/megacells/part/MEGAPatternProviderPart.java +++ b/common/src/main/java/gripe/_90/megacells/item/part/MEGAPatternProviderPart.java @@ -1,4 +1,4 @@ -package gripe._90.megacells.part; +package gripe._90.megacells.item.part; import java.util.List; diff --git a/common/src/main/java/gripe/_90/megacells/service/CompressionService.java b/common/src/main/java/gripe/_90/megacells/service/CompressionService.java deleted file mode 100644 index 1ae9eb69..00000000 --- a/common/src/main/java/gripe/_90/megacells/service/CompressionService.java +++ /dev/null @@ -1,177 +0,0 @@ -package gripe._90.megacells.service; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; - -import net.minecraft.world.item.Item; -import net.minecraft.world.item.crafting.CraftingRecipe; -import net.minecraft.world.item.crafting.RecipeManager; -import net.minecraft.world.item.crafting.RecipeType; - -import appeng.api.stacks.AEItemKey; - -import it.unimi.dsi.fastutil.Pair; -import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; - -public class CompressionService { - public static final CompressionService INSTANCE = new CompressionService(); - - private final Set> compressionChains = new ObjectLinkedOpenHashSet<>(); - - private CompressionService() {} - - public Optional> getChain(AEItemKey key) { - return compressionChains.stream() - .filter(chain -> chain.containsKey(key)) - .findFirst(); - } - - public Object2IntMap getVariants(AEItemKey key, boolean decompress) { - return getChain(key) - .map(chain -> { - var keys = new ObjectArrayList<>(chain.keySet()); - - // Reverse ordering when going from provided storage/filter variant to least-compressed "base unit" - if (decompress) { - Collections.reverse(keys); - } - - // Split variant chain into separate compressed/decompressed chains, omitting the initial variant - // provided - var variants = new Object2IntLinkedOpenHashMap(); - keys.subList(keys.indexOf(key) + 1, keys.size()).forEach(k -> variants.put(k, chain.getInt(k))); - return variants; - }) - .orElseGet(Object2IntLinkedOpenHashMap::new); - } - - public void loadRecipes(RecipeManager recipeManager) { - // Clear old variant cache in case of the server restarting or recipes being reloaded - compressionChains.clear(); - - // Retrieve all available "compression" and "decompression" recipes on the current server (if running) - var allRecipes = recipeManager.getAllRecipesFor(RecipeType.CRAFTING); - var candidates = Stream.concat( - allRecipes.stream().filter(this::isCompressionRecipe), - allRecipes.stream().filter(this::isDecompressionRecipe)) - .toList(); - - // Filter gathered candidate recipes and retain only those that are reversible (i.e. those which can be carried - // out back and forth to compress/decompress a resource without affecting the underlying quantity of it) - var validRecipes = candidates.stream() - .filter(recipe -> { - var compressible = false; - var decompressible = false; - - var input = recipe.getIngredients().get(0); - var output = recipe.getResultItem(); - - var checkAgainst = candidates.stream() - .filter( - isCompressionRecipe(recipe) - ? this::isDecompressionRecipe - : this::isCompressionRecipe) - .toList(); - - for (var candidate : checkAgainst) { - for (var item : candidate.getIngredients().get(0).getItems()) { - if (item.getItem().equals(output.getItem())) { - compressible = true; - } - } - - for (var item : input.getItems()) { - if (item.getItem().equals(candidate.getResultItem().getItem())) { - decompressible = true; - } - } - - if (compressible && decompressible) { - break; - } - } - - return compressible && decompressible; - }) - .toList(); - - var compressed = validRecipes.stream().filter(this::isCompressionRecipe).toList(); - var decompressed = - validRecipes.stream().filter(this::isDecompressionRecipe).toList(); - - // Pull all available compression chains from the recipe shortlist and add these to the handler cache - for (var recipe : compressed) { - var baseVariant = recipe.getResultItem().getItem(); - - if (compressionChains.stream().noneMatch(chain -> chain.containsKey(AEItemKey.of(baseVariant)))) { - var decompressionChain = new Object2IntLinkedOpenHashMap(); - - for (var lowerVariant = getSubsequentVariant(baseVariant, decompressed); lowerVariant != null; ) { - decompressionChain.put(AEItemKey.of(lowerVariant.first()), (int) lowerVariant.second()); - lowerVariant = getSubsequentVariant(lowerVariant.first(), decompressed); - } - - var compressionChain = new Object2IntLinkedOpenHashMap(); - - for (var higherVariant = getSubsequentVariant(baseVariant, compressed); higherVariant != null; ) { - compressionChain.put(AEItemKey.of(higherVariant.first()), (int) higherVariant.second()); - higherVariant = getSubsequentVariant(higherVariant.first(), compressed); - } - - if (compressionChain.isEmpty() && decompressionChain.isEmpty()) { - continue; - } - - // Collate decompression and compression chains together with base variant - var fullChain = new Object2IntLinkedOpenHashMap(); - - var decompressionKeys = new ObjectArrayList<>(decompressionChain.keySet()); - Collections.reverse(decompressionKeys); - decompressionKeys.forEach(k -> fullChain.put(k, decompressionChain.getInt(k))); - - // Retrieve appropriate multiplier for base variant for completion's sake - fullChain.put( - AEItemKey.of(baseVariant), - fullChain.isEmpty() - ? compressionChain.getInt(compressionChain.firstKey()) - : fullChain.getInt(fullChain.lastKey())); - fullChain.putAll(compressionChain); - - compressionChains.add(fullChain); - } - } - } - - private boolean isCompressionRecipe(CraftingRecipe recipe) { - return (recipe.getIngredients().size() == 4 || recipe.getIngredients().size() == 9) - && recipe.getIngredients().stream().distinct().limit(2).count() == 1 - && recipe.getResultItem().getCount() == 1; - } - - private boolean isDecompressionRecipe(CraftingRecipe recipe) { - return (recipe.getResultItem().getCount() == 4 || recipe.getResultItem().getCount() == 9) - && recipe.getIngredients().size() == 1; - } - - private Pair getSubsequentVariant(Item item, List recipes) { - for (var recipe : recipes) { - for (var input : recipe.getIngredients().get(0).getItems()) { - if (input.getItem().equals(item)) { - return Pair.of( - recipe.getResultItem().getItem(), - isCompressionRecipe(recipe) - ? recipe.getIngredients().size() - : recipe.getResultItem().getCount()); - } - } - } - - return null; - } -} diff --git a/common/src/main/java/gripe/_90/megacells/util/CompressionChain.java b/common/src/main/java/gripe/_90/megacells/util/CompressionChain.java new file mode 100644 index 00000000..f1e17f20 --- /dev/null +++ b/common/src/main/java/gripe/_90/megacells/util/CompressionChain.java @@ -0,0 +1,59 @@ +package gripe._90.megacells.util; + +import java.math.BigInteger; +import java.util.Collections; +import java.util.stream.Collectors; + +import appeng.api.stacks.AEItemKey; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +public class CompressionChain extends ObjectArrayList { + public void add(AEItemKey item, int factor) { + this.add(new CompressionVariant(item, factor)); + } + + public boolean containsVariant(AEItemKey item) { + return this.stream().anyMatch(v -> v.item().equals(item)); + } + + public BigInteger unitFactor(AEItemKey item) { + var variant = this.stream().filter(v -> v.item().equals(item)).findFirst(); + + if (variant.isEmpty()) { + return BigInteger.ONE; + } + + var subChain = this.subList(0, indexOf(variant.get()) + 1); + var factor = subChain.stream().map(CompressionVariant::longFactor).reduce(1L, Math::multiplyExact); + return BigInteger.valueOf(factor); + } + + public CompressionVariant last() { + return get(size - 1); + } + + public CompressionChain lastMultiplierSwapped() { + var multipliers = this.stream().map(CompressionVariant::factor).collect(Collectors.toList()); + Collections.rotate(multipliers, -1); + + var items = this.stream().map(CompressionVariant::item).toList(); + var chain = new CompressionChain(); + + for (var i = 0; i < items.size(); i++) { + chain.add(items.get(i), multipliers.get(i)); + } + + return chain; + } + + public CompressionChain reversed() { + var chain = new CompressionChain(); + + for (var i = size - 1; i >= 0; i--) { + chain.add(this.get(i)); + } + + return chain; + } +} diff --git a/common/src/main/java/gripe/_90/megacells/util/CompressionService.java b/common/src/main/java/gripe/_90/megacells/util/CompressionService.java new file mode 100644 index 00000000..cf22aec5 --- /dev/null +++ b/common/src/main/java/gripe/_90/megacells/util/CompressionService.java @@ -0,0 +1,222 @@ +package gripe._90.megacells.util; + +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingRecipe; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.item.crafting.RecipeType; + +import appeng.api.stacks.AEItemKey; + +import gripe._90.megacells.MEGACells; +import gripe._90.megacells.definition.MEGATags; + +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; + +public class CompressionService { + public static final CompressionService INSTANCE = new CompressionService(); + + // Each chain is a list of "variants", where each variant consists of the item itself along with an associated value + // dictating how much of the previous variant's item is needed to compress into that variant. + // This value is typically either 4 or 9 for any given item, or 1 for the smallest base variant. + private final Set compressionChains = new ObjectLinkedOpenHashSet<>(); + + // It may be desirable for some items to be included as variants in a chain in spite of any recipes involving those + // items not being reversible. Hence, we override any reversibility checks and generate a variant for such an item + // based on its usually irreversible recipe. + private final Set overrides = new ObjectLinkedOpenHashSet<>(); + + private CompressionService() {} + + public Optional getChain(AEItemKey item) { + return compressionChains.stream() + .filter(chain -> chain.containsVariant(item)) + .findFirst(); + } + + public void loadRecipes(RecipeManager recipeManager) { + // Clear old chain cache in case of the server restarting or recipes being reloaded + compressionChains.clear(); + overrides.clear(); + + // Retrieve all available "compression" and "decompression" recipes from the current server's recipe manager + var allRecipes = recipeManager.getAllRecipesFor(RecipeType.CRAFTING); + var compressedCandidates = + allRecipes.stream().filter(this::isCompressionRecipe).toList(); + var decompressedCandidates = + allRecipes.stream().filter(this::isDecompressionRecipe).toList(); + + // Filter gathered candidate recipes and retain only those that are reversible (i.e. those which can be carried + // out back and forth to compress/decompress a resource without affecting the underlying quantity of it) + var compressed = compressedCandidates.stream() + .filter(recipe -> isReversibleRecipe(recipe, decompressedCandidates)) + .toList(); + var decompressed = decompressedCandidates.stream() + .filter(recipe -> isReversibleRecipe(recipe, compressedCandidates)) + .toList(); + + // Pull all available compression chains from the recipe shortlist and add these to the cache + Stream.concat(compressed.stream(), decompressed.stream()).forEach(recipe -> { + var baseVariant = recipe.getResultItem().getItem(); + + if (compressionChains.stream().noneMatch(chain -> chain.containsVariant(AEItemKey.of(baseVariant)))) { + compressionChains.add(generateChain(baseVariant, compressed, decompressed)); + } + }); + + if (!compressionChains.isEmpty()) { + MEGACells.LOGGER.info("(Re-)initialised bulk cell compression."); + } + } + + private CompressionChain generateChain( + Item baseVariant, List compressed, List decompressed) { + var variants = new LinkedList(); + var multipliers = new LinkedList(); + + variants.addFirst(baseVariant); + + for (var lower = getNextVariant(baseVariant, decompressed, false); lower != null; ) { + variants.addFirst(lower.item().getItem()); + multipliers.addFirst(lower.factor()); + lower = getNextVariant(lower.item().getItem(), decompressed, false); + } + + multipliers.addFirst(1); + var chain = new CompressionChain(); + + for (var i = 0; i < variants.size(); i++) { + chain.add(AEItemKey.of(variants.get(i)), multipliers.get(i)); + } + + for (var higher = getNextVariant(baseVariant, compressed, true); higher != null; ) { + chain.add(higher); + higher = getNextVariant(higher.item().getItem(), compressed, true); + } + + return chain; + } + + private CompressionVariant getNextVariant(Item item, List recipes, boolean compressed) { + for (var override : overrides) { + if (compressed && override.smaller.equals(item)) { + return new CompressionVariant(override.larger, override.factor); + } + + if (!compressed && override.larger.equals(item)) { + return new CompressionVariant(override.smaller, override.factor); + } + } + + for (var recipe : recipes) { + for (var input : recipe.getIngredients().get(0).getItems()) { + if (input.getItem().equals(item)) { + return new CompressionVariant( + recipe.getResultItem().getItem(), + compressed + ? recipe.getIngredients().size() + : recipe.getResultItem().getCount()); + } + } + } + + return null; + } + + private boolean isDecompressionRecipe(CraftingRecipe recipe) { + return recipe.getIngredients().stream().filter(i -> !i.isEmpty()).count() == 1 + && Set.of(4, 9).contains(recipe.getResultItem().getCount()); + } + + private boolean isCompressionRecipe(CraftingRecipe recipe) { + var ingredients = recipe.getIngredients(); + return recipe.getResultItem().getCount() == 1 + && ingredients.stream().noneMatch(Ingredient::isEmpty) + && Set.of(4, 9).contains(ingredients.size()) + && sameIngredient(ingredients); + } + + private boolean sameIngredient(List ingredients) { + if (ingredients.stream().distinct().count() <= 1) { + return true; + } + + // Check further for any odd cases (e.g. melon blocks having a shapeless recipe instead of a shaped one) + var first = ingredients.get(0).getItems(); + + for (var ingredient : ingredients) { + var stacks = ingredient.getItems(); + + if (stacks.length != first.length) { + return false; + } + + for (var i = 0; i < stacks.length; i++) { + if (!ItemStack.isSameItemSameTags(stacks[i], first[i])) { + return false; + } + } + } + + return true; + } + + private boolean isReversibleRecipe(CraftingRecipe recipe, List candidates) { + if (overrideRecipe(recipe)) { + return true; + } + + var compressible = false; + var decompressible = false; + + var input = recipe.getIngredients().get(0); + var output = recipe.getResultItem(); + + for (var candidate : candidates) { + for (var item : candidate.getIngredients().get(0).getItems()) { + if (item.getItem().equals(output.getItem())) { + compressible = true; + } + } + + for (var item : input.getItems()) { + if (item.getItem().equals(candidate.getResultItem().getItem())) { + decompressible = true; + } + } + + if (compressible && decompressible) return true; + } + + return false; + } + + private boolean overrideRecipe(CraftingRecipe recipe) { + for (var input : recipe.getIngredients().get(0).getItems()) { + if (input.is(MEGATags.COMPRESSION_OVERRIDES)) { + // Less expensive to check for decompression rather than compression, and since this method is only + // being used on recipes that are candidates for either compression or decompression, this is fine. + var compressed = !isDecompressionRecipe(recipe); + var output = recipe.getResultItem(); + + var smaller = compressed ? input.getItem() : output.getItem(); + var larger = compressed ? output.getItem() : input.getItem(); + var factor = compressed ? recipe.getIngredients().size() : output.getCount(); + + overrides.add(new Override(smaller, larger, compressed, factor)); + return true; + } + } + + return false; + } + + private record Override(Item smaller, Item larger, boolean compressed, int factor) {} +} diff --git a/common/src/main/java/gripe/_90/megacells/util/CompressionVariant.java b/common/src/main/java/gripe/_90/megacells/util/CompressionVariant.java new file mode 100644 index 00000000..ac73b848 --- /dev/null +++ b/common/src/main/java/gripe/_90/megacells/util/CompressionVariant.java @@ -0,0 +1,15 @@ +package gripe._90.megacells.util; + +import net.minecraft.world.item.Item; + +import appeng.api.stacks.AEItemKey; + +public record CompressionVariant(AEItemKey item, int factor) { + public CompressionVariant(Item item, int factor) { + this(AEItemKey.of(item), factor); + } + + public long longFactor() { + return factor; + } +} diff --git a/fabric/src/data/java/gripe/_90/megacells/datagen/MEGADataGenerators.java b/fabric/src/data/java/gripe/_90/megacells/datagen/MEGADataGenerators.java index 88a4da03..3a96877f 100644 --- a/fabric/src/data/java/gripe/_90/megacells/datagen/MEGADataGenerators.java +++ b/fabric/src/data/java/gripe/_90/megacells/datagen/MEGADataGenerators.java @@ -6,9 +6,9 @@ public class MEGADataGenerators implements DataGeneratorEntrypoint { @Override public void onInitializeDataGenerator(FabricDataGenerator generator) { - var blockTags = new TagProvider.Blocks(generator); + var blockTags = new TagProvider.BlockTags(generator); generator.addProvider(blockTags); - generator.addProvider(new TagProvider.Items(generator, blockTags)); + generator.addProvider(new TagProvider.ItemTags(generator, blockTags)); generator.addProvider(LootTableProvider::new); generator.addProvider(ModelProvider::new); diff --git a/fabric/src/data/java/gripe/_90/megacells/datagen/TagProvider.java b/fabric/src/data/java/gripe/_90/megacells/datagen/TagProvider.java index 9faf8205..275c6f76 100644 --- a/fabric/src/data/java/gripe/_90/megacells/datagen/TagProvider.java +++ b/fabric/src/data/java/gripe/_90/megacells/datagen/TagProvider.java @@ -2,7 +2,8 @@ import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider; -import net.minecraft.tags.BlockTags; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Items; import appeng.api.features.P2PTunnelAttunement; @@ -11,8 +12,8 @@ import gripe._90.megacells.definition.MEGATags; abstract class TagProvider { - static class Items extends FabricTagProvider.ItemTagProvider { - Items(FabricDataGenerator gen, BlockTagProvider block) { + static class ItemTags extends FabricTagProvider.ItemTagProvider { + ItemTags(FabricDataGenerator gen, BlockTagProvider block) { super(gen, block); } @@ -23,18 +24,27 @@ protected void generateTags() { tag(MEGATags.MEGA_PATTERN_PROVIDER) .add(MEGABlocks.MEGA_PATTERN_PROVIDER.asItem()) .add(MEGAParts.MEGA_PATTERN_PROVIDER.asItem()); + tag(MEGATags.COMPRESSION_OVERRIDES) + .add(Items.QUARTZ) + .add(Items.GLOWSTONE_DUST) + .add(Items.AMETHYST_SHARD) + .add(Items.MAGMA_CREAM) + .add(Items.CLAY_BALL) + .add(Items.MELON_SLICE) + .add(Items.ICE, Items.PACKED_ICE) + .addOptionalTag(new ResourceLocation("functionalstorage", "ignore_crafting_check")); } } - static class Blocks extends FabricTagProvider.BlockTagProvider { - Blocks(FabricDataGenerator gen) { + static class BlockTags extends FabricTagProvider.BlockTagProvider { + BlockTags(FabricDataGenerator gen) { super(gen); } @Override protected void generateTags() { - MEGABlocks.getBlocks() - .forEach(block -> tag(BlockTags.MINEABLE_WITH_PICKAXE).add(block.block())); + MEGABlocks.getBlocks().forEach(block -> tag(net.minecraft.tags.BlockTags.MINEABLE_WITH_PICKAXE) + .add(block.block())); } } } diff --git a/fabric/src/main/java/gripe/_90/megacells/MEGACellsFabric.java b/fabric/src/main/java/gripe/_90/megacells/MEGACellsFabric.java index 1032fd7d..ae8b6126 100644 --- a/fabric/src/main/java/gripe/_90/megacells/MEGACellsFabric.java +++ b/fabric/src/main/java/gripe/_90/megacells/MEGACellsFabric.java @@ -5,13 +5,12 @@ import appeng.api.IAEAddonEntrypoint; import appeng.api.behaviors.GenericInternalInventory; -import appeng.api.crafting.PatternDetailsHelper; import appeng.api.networking.GridServices; import appeng.core.AppEng; import gripe._90.megacells.block.MEGAPatternProviderBlock; import gripe._90.megacells.core.Addons; -import gripe._90.megacells.crafting.DecompressionPatternDecoder; +import gripe._90.megacells.crafting.DecompressionService; import gripe._90.megacells.definition.MEGABlockEntities; import gripe._90.megacells.definition.MEGABlocks; import gripe._90.megacells.definition.MEGAItems; @@ -19,8 +18,7 @@ import gripe._90.megacells.init.InitStorageCells; import gripe._90.megacells.init.InitUpgrades; import gripe._90.megacells.integration.appbot.AppBotItems; -import gripe._90.megacells.service.CompressionService; -import gripe._90.megacells.service.DecompressionService; +import gripe._90.megacells.util.CompressionService; public class MEGACellsFabric implements IAEAddonEntrypoint { @Override @@ -79,6 +77,5 @@ private void initCompression() { }); GridServices.register(DecompressionService.class, DecompressionService.class); - PatternDetailsHelper.registerDecoder(DecompressionPatternDecoder.INSTANCE); } } diff --git a/forge/src/main/java/gripe/_90/megacells/forge/MEGACellsForge.java b/forge/src/main/java/gripe/_90/megacells/forge/MEGACellsForge.java index 77dfe351..f3abf4bc 100644 --- a/forge/src/main/java/gripe/_90/megacells/forge/MEGACellsForge.java +++ b/forge/src/main/java/gripe/_90/megacells/forge/MEGACellsForge.java @@ -12,13 +12,12 @@ import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegisterEvent; -import appeng.api.crafting.PatternDetailsHelper; import appeng.api.networking.GridServices; import appeng.core.AppEng; import gripe._90.megacells.block.MEGAPatternProviderBlock; import gripe._90.megacells.core.Addons; -import gripe._90.megacells.crafting.DecompressionPatternDecoder; +import gripe._90.megacells.crafting.DecompressionService; import gripe._90.megacells.definition.MEGABlockEntities; import gripe._90.megacells.definition.MEGABlocks; import gripe._90.megacells.definition.MEGAItems; @@ -28,8 +27,7 @@ import gripe._90.megacells.integration.appbot.AppBotItems; import gripe._90.megacells.integration.appmek.AppMekIntegration; import gripe._90.megacells.integration.appmek.AppMekItems; -import gripe._90.megacells.service.CompressionService; -import gripe._90.megacells.service.DecompressionService; +import gripe._90.megacells.util.CompressionService; @Mod(gripe._90.megacells.MEGACells.MODID) public class MEGACellsForge { @@ -100,6 +98,5 @@ private void initCompression() { event.getServerResources().getRecipeManager())); GridServices.register(DecompressionService.class, DecompressionService.class); - PatternDetailsHelper.registerDecoder(DecompressionPatternDecoder.INSTANCE); } }