diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/MultiblockControllerMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/MultiblockControllerMachine.java index 8eb2f162256..4e87b336711 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/MultiblockControllerMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/MultiblockControllerMachine.java @@ -9,6 +9,7 @@ import com.gregtechceu.gtceu.api.machine.property.GTMachineModelProperties; import com.gregtechceu.gtceu.api.machine.trait.MachineTrait; import com.gregtechceu.gtceu.api.machine.trait.multiblock.MultiblockMachineTrait; +import com.gregtechceu.gtceu.api.mui.MultiblockSchemaInfo; import com.gregtechceu.gtceu.api.multiblock.MultiblockWorldSavedData; import com.gregtechceu.gtceu.api.multiblock.pattern.*; import com.gregtechceu.gtceu.api.multiblock.util.AbstractStructureHelper; @@ -522,7 +523,8 @@ public InteractionResult onUse(ExtendedUseOnContext context) { } if (structureHelper != null) { - structureHelper.populate(resultStructure, pattern, null, getFrontFacing(), getUpwardsFacing(), + structureHelper.populate(new MultiblockSchemaInfo(), resultStructure, pattern, null, + getFrontFacing(), getUpwardsFacing(), isFlipped()); Long2ReferenceMap blocks = new Long2ReferenceOpenHashMap<>(); resultStructure.forEach((pos, state) -> blocks.put(pos.asLong(), state.getBlockState())); diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/MultiblockSchemaInfo.java b/src/main/java/com/gregtechceu/gtceu/api/mui/MultiblockSchemaInfo.java index ad0828170b6..29b543dd84a 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/MultiblockSchemaInfo.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/MultiblockSchemaInfo.java @@ -17,6 +17,7 @@ import brachy.modularui.drawable.SchemaRenderer; import brachy.modularui.widgets.SchemaWidget; +import com.google.common.collect.HashBasedTable; import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; @@ -47,9 +48,16 @@ public class MultiblockSchemaInfo { @Setter private SchemaRenderer renderer; @Getter - private final Reference2IntMap blockCounts = new Reference2IntOpenHashMap<>(); + @Setter + private Reference2IntMap blockCounts = new Reference2IntOpenHashMap<>(); @Getter private final Long2ObjectMap userGlobalBlockPreferences = new Long2ObjectOpenHashMap<>(); + @Getter + protected final HashBasedTable blockPreferences = HashBasedTable + .create(); + @Getter + protected final HashBasedTable minMaxPreferences = HashBasedTable + .create(); @Getter private final Int2IntMap userSliceRepeats = new Int2IntArrayMap(); @@ -92,7 +100,7 @@ public void refreshSchema(MultiblockMachineDefinition multiblockDefinition, Dire } } - this.structureHelper.populate(resultStructure, pattern, this.userGlobalBlockPreferences, + this.structureHelper.populate(this, resultStructure, pattern, this.userGlobalBlockPreferences, frontFacing, upFacing, isFlipped); Long2ReferenceMap schemaMap = new Long2ReferenceOpenHashMap<>(); @@ -115,12 +123,24 @@ public void refreshSchema(MultiblockMachineDefinition multiblockDefinition, Dire } } + public int getMinCount(MultiPredicate predicate, BasePredicate basePredicate) { + if (!minMaxPreferences.contains(predicate, basePredicate)) + return basePredicate.getMinCount(); + return minMaxPreferences.get(predicate, basePredicate).leftInt(); + } + + public int getMaxCount(MultiPredicate predicate, BasePredicate basePredicate) { + if (!minMaxPreferences.contains(predicate, basePredicate)) + return basePredicate.getMaxCount(); + return minMaxPreferences.get(predicate, basePredicate).rightInt(); + } + public void clearUserPreferences() { this.userSliceRepeats.clear(); this.userDimensions.clear(); } public void putPredicatePreference(MultiPredicate predicate, BasePredicate basePredicate, BlockInfo info) { - this.structureHelper.getBlockPreferences().put(predicate, basePredicate, info); + this.blockPreferences.put(predicate, basePredicate, info); } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/AbstractStructureHelper.java b/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/AbstractStructureHelper.java index 2207ae75b9f..46e64c2ce7f 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/AbstractStructureHelper.java +++ b/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/AbstractStructureHelper.java @@ -2,6 +2,7 @@ import com.gregtechceu.gtceu.api.block.MetaMachineBlock; import com.gregtechceu.gtceu.api.block.property.GTBlockStateProperties; +import com.gregtechceu.gtceu.api.mui.MultiblockSchemaInfo; import com.gregtechceu.gtceu.api.multiblock.MultiPredicate; import com.gregtechceu.gtceu.api.multiblock.pattern.IBlockPattern; import com.gregtechceu.gtceu.api.multiblock.predicates.BasePredicate; @@ -11,14 +12,10 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.Table; import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import lombok.Getter; import org.jetbrains.annotations.Nullable; import java.util.Map; @@ -28,11 +25,6 @@ public abstract class AbstractStructureHelper { public static final Direction[] DIRECTIONS_IN_ORDER = { Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, Direction.UP, Direction.DOWN }; - @Getter - protected final HashBasedTable blockPreferences = HashBasedTable - .create(); - protected final HashBasedTable minMaxPreferences = HashBasedTable - .create(); protected @Nullable Block controllerBlock; public static AbstractStructureHelper blockPattern(Int2IntMap sliceRepeats) { @@ -43,48 +35,35 @@ public static AbstractStructureHelper expandable(IntList sliceRepeats) { return new ExpandablePatternHelper(sliceRepeats); } - public Table getMinMaxPreferences() { - return this.minMaxPreferences; - } - - public void populate(Map resultStructure, IBlockPattern pattern, + public void populate(MultiblockSchemaInfo info, Map resultStructure, IBlockPattern pattern, @Nullable Long2ObjectMap userBlockPreferences, Direction frontFacing, Direction upFacing, boolean isFlipped) { setup(pattern, frontFacing, upFacing, isFlipped); if (userBlockPreferences != null && !userBlockPreferences.isEmpty()) { - populateWithUserBlockPreferences(resultStructure, pattern, userBlockPreferences, frontFacing, upFacing, + populateWithUserBlockPreferences(info, resultStructure, pattern, userBlockPreferences, frontFacing, + upFacing, isFlipped); } - populateFromPattern(resultStructure, pattern, frontFacing, upFacing, isFlipped); + populateFromPattern(info, resultStructure, pattern, frontFacing, upFacing, isFlipped); fixRotationsAndFacing(resultStructure, frontFacing, upFacing, this.controllerBlock); } protected void setup(IBlockPattern pattern, Direction frontFacing, Direction upFacing, boolean isFlipped) {} - protected abstract void populateWithUserBlockPreferences(Map resultStructure, + protected abstract void populateWithUserBlockPreferences(MultiblockSchemaInfo info, + Map resultStructure, IBlockPattern pattern, Long2ObjectMap userBlockPreferences, Direction frontFacing, Direction upFacing, boolean isFlipped); - protected abstract void populateFromPattern(Map resultStructure, IBlockPattern pattern, + protected abstract void populateFromPattern(MultiblockSchemaInfo info, Map resultStructure, + IBlockPattern pattern, Direction frontFacing, Direction upFacing, boolean isFlipped); public abstract MultiPredicate getPredicateFromPos(IBlockPattern pattern, BlockPos pos, Direction frontFacing, Direction upFacing, boolean isFlipped); - protected int getMinCount(MultiPredicate predicate, BasePredicate basePredicate) { - if (!minMaxPreferences.contains(predicate, basePredicate)) - return basePredicate.getMinCount(); - return minMaxPreferences.get(predicate, basePredicate).leftInt(); - } - - protected int getMaxCount(MultiPredicate predicate, BasePredicate basePredicate) { - if (!minMaxPreferences.contains(predicate, basePredicate)) - return basePredicate.getMaxCount(); - return minMaxPreferences.get(predicate, basePredicate).rightInt(); - } - protected static int countPopulatedGlobal(Map resultStructure, BasePredicate basePredicate) { return (int) resultStructure.values().stream() .filter(blockInfo -> basePredicate.getCandidates().contains(blockInfo)) diff --git a/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/AutobuildHelper.java b/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/AutobuildHelper.java new file mode 100644 index 00000000000..3079033d207 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/AutobuildHelper.java @@ -0,0 +1,164 @@ +package com.gregtechceu.gtceu.api.multiblock.util; + +import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; +import com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine; +import com.gregtechceu.gtceu.api.multiblock.PredicateContext; + +import com.gregtechceu.gtceu.client.renderer.AABBHighlightRenderer; +import net.minecraft.ChatFormatting; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; + +import java.util.Comparator; +import java.util.Map; + +import static com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine.DEFAULT_STRUCTURE; + +public class AutobuildHelper { + + // todo remove + public static Long2ObjectMap readBlockPreferences(CompoundTag tag) { + Long2ObjectMap blockPreferences = new Long2ObjectOpenHashMap<>(); + if (!tag.contains("blockPreferences", CompoundTag.TAG_LIST)) { + return blockPreferences; + } + + var preferences = tag.getList("blockPreferences", CompoundTag.TAG_COMPOUND); + for (int i = 0; i < preferences.size(); i++) { + CompoundTag preference = preferences.getCompound(i); + BlockState state = NbtUtils.readBlockState(BuiltInRegistries.BLOCK.asLookup(), + preference.getCompound("state")); + if (state.isAir()) continue; + blockPreferences.put(preference.getLong("pos"), BlockInfo.fromBlockState(state)); + } + return blockPreferences; + } + + /* + * iterate over every position in the structure + * for each block + * - if that block is part of the structure and valid in that predicate, add to alreadyPlaced + * - if that block can be replaced(air, tall grass, etc? block property replaceable) add to replaceableBlocks + * - if that block CANT be replaced and not valid, add to canNotPlace, + * maybe add what already exists there to another list for reporting(invalidBlocks)? + * + * + * for each replaceableBlock + * - if that candidate from the predicate exists in the inventory, add to some blocksToRemove list + * (small size for chunked building) + * figure out the auto placement action + * - if the candidate does not exist, add to blocksMissing(for later reporting) + */ + + public static void autobuild(ServerPlayer player, ItemStack item, MultiblockMachineDefinition definition, + MultiblockControllerMachine controller, Map blocksToPlace, + AbstractStructureHelper structureHelper) { + Long2ObjectOpenHashMap alreadyValidPlaced = new Long2ObjectOpenHashMap<>(); + Long2ObjectOpenHashMap replaceableBlocks = new Long2ObjectOpenHashMap<>(); + Long2ObjectOpenHashMap canNotPlaceBlocks = new Long2ObjectOpenHashMap<>(); + + Level level = player.level(); + + Block controllerBlock = controller.getDefinition().getBlock(); + BlockPos schemaControllerPos = BlockPos.ZERO; + for (var entry : blocksToPlace.entrySet()) { + if (entry.getValue().getBlockState().is(controllerBlock)) { + schemaControllerPos = entry.getKey(); + break; + } + } + + BlockPos controllerOffset = controller.getBlockPos().subtract(schemaControllerPos); + + PredicateContext cxt = new PredicateContext(null); + cxt.updateLevel(level); + int checkedBlocks = 0; + for (var entry : blocksToPlace.entrySet()) { + BlockPos pos = entry.getKey().offset(controllerOffset); + var blockState = level.getBlockState(pos); + + var predicate = structureHelper.getPredicateFromPos( + definition.getStructurePatterns().get(DEFAULT_STRUCTURE).get(), + entry.getKey(), controller.getFrontFacing(), controller.getUpwardsFacing(), controller.isFlipped()); + + cxt.updatePos(pos); + var innerPredicate = predicate.getPredicateAtPos(cxt); + if (innerPredicate != null) { + alreadyValidPlaced.put(pos.asLong(), blockState); + } else { + if (blockState.canBeReplaced()) { + replaceableBlocks.put(pos.asLong(), blocksToPlace.get(entry.getKey()).getBlockState()); + checkedBlocks++; + } else { + canNotPlaceBlocks.put(pos.asLong(), blockState); + } + } + if (checkedBlocks > 32) { + break; + } + } + + // Step 1. Making the "what we want" list + Map whatWeWant = new Object2IntArrayMap<>(); + for (var entry : replaceableBlocks.long2ObjectEntrySet()) { + whatWeWant.merge(entry.getValue().getBlock().asItem(), 1, Integer::sum); + } + + // Step 2. Try to fetch + Map whatWeHave = new Object2IntArrayMap<>(); + for (var entry : whatWeWant.entrySet()) { + Item desiredItem = entry.getKey(); + int desiredAmount = entry.getValue(); + var slotIndex = player.getInventory().findSlotMatchingItem(new ItemStack(desiredItem)); + if (slotIndex == -1) continue; + var playerSlotStack = player.getInventory().getItem(slotIndex); + int toDeduct = Math.min(playerSlotStack.getCount(), desiredAmount); + playerSlotStack.shrink(toDeduct); + whatWeHave.put(desiredItem, toDeduct); + } + + // Step 3. Place what was fetched + for (var entry : replaceableBlocks.long2ObjectEntrySet() + .stream() + .sorted(Comparator.comparingLong(Long2ObjectMap.Entry::getLongKey)) + .toList()) { + var blockState = entry.getValue(); + var blocksLeft = whatWeHave.merge(blockState.getBlock().asItem(), -1, Integer::sum); + if (blocksLeft < 0) continue; + whatWeWant.merge(blockState.getBlock().asItem(), -1, Integer::sum); + level.setBlockAndUpdate(BlockPos.of(entry.getLongKey()), blockState); + } + + if (!whatWeWant.isEmpty()) { + player.displayClientMessage(Component.translatable("gtceu.autobuild.missing_blocks").withStyle(ChatFormatting.RED), false); + } + for (var entry : whatWeWant.entrySet()) { + if (entry.getValue() > 0) { + player.displayClientMessage(Component.literal(entry.getKey().toString() + " " + entry.getValue()), false); + } + } + + for (var entry : canNotPlaceBlocks.long2ObjectEntrySet()) { + AABBHighlightRenderer.INSTANCE.addHighlight(AABBHighlightRenderer.builder() + .aabb(BlockPos.of(entry.getLongKey())) + .colorARGB(255, 180, 0, 0) + .thickness(0.025) + .durationMillis(10000) + .phaseMillis(750) + .build()); + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/BlockPatternHelper.java b/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/BlockPatternHelper.java index 60ad5e39036..da1eb68ba9d 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/BlockPatternHelper.java +++ b/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/BlockPatternHelper.java @@ -1,6 +1,7 @@ package com.gregtechceu.gtceu.api.multiblock.util; import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.mui.MultiblockSchemaInfo; import com.gregtechceu.gtceu.api.multiblock.MultiPredicate; import com.gregtechceu.gtceu.api.multiblock.Predicates; import com.gregtechceu.gtceu.api.multiblock.pattern.BlockPattern; @@ -50,7 +51,8 @@ protected void setup(IBlockPattern pattern, Direction frontFacing, Direction upF frontFacing, upFacing, isFlipped); } - protected void populateWithUserBlockPreferences(Map resultStructure, IBlockPattern pattern, + protected void populateWithUserBlockPreferences(MultiblockSchemaInfo info, Map resultStructure, + IBlockPattern pattern, Long2ObjectMap userBlockPreferences, Direction frontFacing, Direction upFacing, boolean isFlipped) { BlockPattern blockPattern = (BlockPattern) pattern; @@ -70,7 +72,7 @@ protected void populateWithUserBlockPreferences(Map resultS } char c = this.flattenedBlockPattern[pos.getX()][pos.getY()][pos.getZ()]; MultiPredicate predicate = blockPattern.getPredicates().get(c); - if (!isValidCandidate(resultStructure, predicate, pos, blockInfo, sliceDir)) { + if (!isValidCandidate(info, resultStructure, predicate, pos, blockInfo, sliceDir)) { throw new IllegalStateException("Invalid preference " + blockInfo.getBlockState().getBlock().getName() + " for position " + pos); } @@ -78,7 +80,8 @@ protected void populateWithUserBlockPreferences(Map resultS } } - protected void populateFromPattern(Map resultStructure, IBlockPattern pattern, + protected void populateFromPattern(MultiblockSchemaInfo info, Map resultStructure, + IBlockPattern pattern, Direction frontFacing, Direction upFacing, boolean isFlipped) { // spotless:off // 4. Iterate slice by slice (a slice == one "layer"), then over the other two axes within the slice, @@ -119,8 +122,8 @@ protected void populateFromPattern(Map resultStructure, IBl // Attempts to first place the predicate if the minimum (layer) count isn't satisfied, then the // maximum (layer) count - if (tryMinCount(resultStructure, predicate, pos, sliceDir, sliceCoord)) continue; - if (tryMaxCount(resultStructure, predicate, pos, sliceDir, sliceCoord)) continue; + if (tryMinCount(info, resultStructure, predicate, pos, sliceDir, sliceCoord)) continue; + if (tryMaxCount(info, resultStructure, predicate, pos, sliceDir, sliceCoord)) continue; // If we arrive here, there's nothing we can place that doesn't overflow a max count! throw new IllegalStateException( "Could not place a block without breaking maxCount requirements for character " + c); @@ -129,10 +132,11 @@ protected void populateFromPattern(Map resultStructure, IBl } } - private boolean tryMinCount(Map resultStructure, MultiPredicate predicate, + private boolean tryMinCount(MultiblockSchemaInfo info, Map resultStructure, + MultiPredicate predicate, BlockPos pos, Direction dir, int offset) { for (BasePredicate basePredicate : predicate.predicates()) { - int minCount = getMinCount(predicate, basePredicate); + int minCount = info.getMinCount(predicate, basePredicate); if (minCount == 0) continue; int totalAlreadyPopulated = countPopulatedGlobal(resultStructure, basePredicate); @@ -142,7 +146,7 @@ private boolean tryMinCount(Map resultStructure, MultiPredi if (globalMinMet && sliceMinMet) continue; - BlockInfo toInsert = blockPreferences.get(predicate, basePredicate); + BlockInfo toInsert = info.getBlockPreferences().get(predicate, basePredicate); if (toInsert == null) { toInsert = basePredicate.getFirstCandidate().orElseGet(() -> { GTCEu.LOGGER.warn("Predicate\n\t{}\nhas no candidates to chose from!", basePredicate); @@ -150,7 +154,7 @@ private boolean tryMinCount(Map resultStructure, MultiPredi }); } // TODO: is this needed? doesn't this just do what we're already doing? - if (isValidCandidate(resultStructure, predicate, pos, toInsert, dir)) { + if (isValidCandidate(info, resultStructure, predicate, pos, toInsert, dir)) { resultStructure.put(pos, toInsert); if (this.controllerBlock == null && predicate.isController()) { this.controllerBlock = toInsert.getBlockState().getBlock(); @@ -159,15 +163,16 @@ private boolean tryMinCount(Map resultStructure, MultiPredi } } for (MultiPredicate child : predicate.children()) { - if (tryMinCount(resultStructure, child, pos, dir, offset)) return true; + if (tryMinCount(info, resultStructure, child, pos, dir, offset)) return true; } return false; } - private boolean tryMaxCount(Map resultStructure, MultiPredicate predicate, + private boolean tryMaxCount(MultiblockSchemaInfo info, Map resultStructure, + MultiPredicate predicate, BlockPos pos, Direction dir, int offset) { for (BasePredicate basePredicate : predicate.predicates()) { - int maxCount = getMaxCount(predicate, basePredicate); + int maxCount = info.getMaxCount(predicate, basePredicate); if (maxCount == 0) continue; int totalAlreadyPopulated = countPopulatedGlobal(resultStructure, basePredicate); @@ -175,7 +180,7 @@ private boolean tryMaxCount(Map resultStructure, MultiPredi if (maxCount != -1 && totalAlreadyPopulated >= maxCount) continue; if (!basePredicate.testSliceMax(layerAlreadyPopulated + 1)) continue; - BlockInfo toInsert = blockPreferences.get(predicate, basePredicate); + BlockInfo toInsert = info.getBlockPreferences().get(predicate, basePredicate); if (toInsert == null) { toInsert = basePredicate.getFirstCandidate().orElseGet(() -> { GTCEu.LOGGER.warn("Predicate\n\t{}\nhas no candidates to chose from!", basePredicate); @@ -183,7 +188,7 @@ private boolean tryMaxCount(Map resultStructure, MultiPredi }); } // TODO: is this needed? doesn't this just do what we're already doing? - if (isValidCandidate(resultStructure, predicate, pos, toInsert, dir)) { + if (isValidCandidate(info, resultStructure, predicate, pos, toInsert, dir)) { resultStructure.put(pos, toInsert); if (this.controllerBlock == null && predicate.isController()) { this.controllerBlock = toInsert.getBlockState().getBlock(); @@ -192,12 +197,13 @@ private boolean tryMaxCount(Map resultStructure, MultiPredi } } for (MultiPredicate child : predicate.children()) { - if (tryMaxCount(resultStructure, child, pos, dir, offset)) return true; + if (tryMaxCount(info, resultStructure, child, pos, dir, offset)) return true; } return false; } - private boolean isValidCandidate(Map resultStructure, MultiPredicate predicate, + private boolean isValidCandidate(MultiblockSchemaInfo info, Map resultStructure, + MultiPredicate predicate, BlockPos pos, BlockInfo newInfo, Direction sliceDir) { // force true because idk what to do with this if (newInfo == BlockInfo.EMPTY) return true; @@ -215,7 +221,7 @@ private boolean isValidCandidate(Map resultStructure, Multi */ if (!basePredicate.getCandidates().contains(newInfo)) continue; - int maxCount = getMaxCount(predicate, basePredicate); + int maxCount = info.getMaxCount(predicate, basePredicate); if (maxCount == 0) continue; int totalAlreadyPopulated = countPopulatedGlobal(resultStructure, basePredicate); @@ -227,7 +233,7 @@ private boolean isValidCandidate(Map resultStructure, Multi } } for (MultiPredicate child : predicate.children()) { - if (isValidCandidate(resultStructure, child, pos, newInfo, sliceDir)) { + if (isValidCandidate(info, resultStructure, child, pos, newInfo, sliceDir)) { return true; } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/ExpandablePatternHelper.java b/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/ExpandablePatternHelper.java index a0ef0ac5e42..81dcbafd681 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/ExpandablePatternHelper.java +++ b/src/main/java/com/gregtechceu/gtceu/api/multiblock/util/ExpandablePatternHelper.java @@ -1,5 +1,6 @@ package com.gregtechceu.gtceu.api.multiblock.util; +import com.gregtechceu.gtceu.api.mui.MultiblockSchemaInfo; import com.gregtechceu.gtceu.api.multiblock.MultiPredicate; import com.gregtechceu.gtceu.api.multiblock.pattern.ExpandablePattern; import com.gregtechceu.gtceu.api.multiblock.pattern.IBlockPattern; @@ -51,7 +52,8 @@ private static CornerData getCorners(IntList bounds, } @Override - protected void populateWithUserBlockPreferences(Map resultStructure, IBlockPattern pattern, + protected void populateWithUserBlockPreferences(MultiblockSchemaInfo info, Map resultStructure, + IBlockPattern pattern, Long2ObjectMap userBlockPreferences, Direction frontFacing, Direction upFacing, boolean isFlipped) { ExpandablePattern expandablePattern = (ExpandablePattern) pattern; @@ -77,7 +79,8 @@ protected void populateWithUserBlockPreferences(Map resultS } @Override - public void populateFromPattern(Map resultStructure, IBlockPattern pattern, + public void populateFromPattern(MultiblockSchemaInfo info, Map resultStructure, + IBlockPattern pattern, Direction frontFacing, Direction upFacing, boolean isFlipped) { ExpandablePattern expandablePattern = (ExpandablePattern) pattern; @@ -101,25 +104,26 @@ public void populateFromPattern(Map resultStructure, IBlock // Attempts to first place the predicate if the min (layer) count isn't satisfied, then the // max (layer) count - if (tryMinCount(resultStructure, predicate, mutablePos)) continue; - if (tryMaxCount(resultStructure, predicate, mutablePos)) continue; + if (tryMinCount(info, resultStructure, predicate, mutablePos)) continue; + if (tryMaxCount(info, resultStructure, predicate, mutablePos)) continue; // If we arrive here, there's nothing we can place that doesn't overflow a max count! throw new IllegalStateException("Could not place a block without breaking maxCount requirements"); } } - private boolean tryMinCount(Map resultStructure, MultiPredicate predicate, + private boolean tryMinCount(MultiblockSchemaInfo info, Map resultStructure, + MultiPredicate predicate, BlockPos pos) { for (BasePredicate basePredicate : predicate.predicates()) { - int minCount = getMinCount(predicate, basePredicate); + int minCount = info.getMinCount(predicate, basePredicate); if (minCount == 0) continue; int totalAlreadyPopulated = countPopulatedGlobal(resultStructure, basePredicate); if (minCount == -1 || totalAlreadyPopulated >= minCount) continue; BlockInfo toInsert = null; - if (blockPreferences.contains(predicate, basePredicate)) { - toInsert = blockPreferences.get(predicate, basePredicate); + if (info.getBlockPreferences().contains(predicate, basePredicate)) { + toInsert = info.getBlockPreferences().get(predicate, basePredicate); } else if (!basePredicate.getCandidates().isEmpty()) { toInsert = basePredicate.getCandidates().get(0); } @@ -127,23 +131,24 @@ private boolean tryMinCount(Map resultStructure, MultiPredi return true; } for (MultiPredicate child : predicate.children()) { - if (tryMinCount(resultStructure, child, pos)) return true; + if (tryMinCount(info, resultStructure, child, pos)) return true; } return false; } - private boolean tryMaxCount(Map resultStructure, MultiPredicate predicate, + private boolean tryMaxCount(MultiblockSchemaInfo info, Map resultStructure, + MultiPredicate predicate, BlockPos pos) { for (BasePredicate basePredicate : predicate.predicates()) { - int maxCount = getMaxCount(predicate, basePredicate); + int maxCount = info.getMaxCount(predicate, basePredicate); if (maxCount == 0) continue; int totalAlreadyPopulated = countPopulatedGlobal(resultStructure, basePredicate); if (maxCount != -1 && totalAlreadyPopulated >= maxCount) continue; BlockInfo toInsert = null; - if (blockPreferences.contains(predicate, basePredicate)) { - toInsert = blockPreferences.get(predicate, basePredicate); + if (info.getBlockPreferences().contains(predicate, basePredicate)) { + toInsert = info.getBlockPreferences().get(predicate, basePredicate); } else if (!basePredicate.getCandidates().isEmpty()) { toInsert = basePredicate.getCandidates().get(0); } @@ -151,7 +156,7 @@ private boolean tryMaxCount(Map resultStructure, MultiPredi return true; } for (MultiPredicate child : predicate.children()) { - if (tryMaxCount(resultStructure, child, pos)) return true; + if (tryMaxCount(info, resultStructure, child, pos)) return true; } return false; } diff --git a/src/main/java/com/gregtechceu/gtceu/common/item/behavior/TerminalBehavior.java b/src/main/java/com/gregtechceu/gtceu/common/item/behavior/TerminalBehavior.java index 949cd3d745e..a844e015ce4 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/item/behavior/TerminalBehavior.java +++ b/src/main/java/com/gregtechceu/gtceu/common/item/behavior/TerminalBehavior.java @@ -1,16 +1,36 @@ package com.gregtechceu.gtceu.common.item.behavior; +import com.gregtechceu.gtceu.api.item.component.IAddInformation; import com.gregtechceu.gtceu.api.item.component.IInteractionItem; import com.gregtechceu.gtceu.api.machine.MetaMachine; import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; import com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine; import com.gregtechceu.gtceu.api.mui.IItemUIHolder; import com.gregtechceu.gtceu.api.mui.MultiblockSchemaInfo; +import com.gregtechceu.gtceu.api.multiblock.MultiPredicate; +import com.gregtechceu.gtceu.api.multiblock.pattern.BlockPattern; +import com.gregtechceu.gtceu.api.multiblock.pattern.ExpandablePattern; +import com.gregtechceu.gtceu.api.multiblock.pattern.IBlockPattern; +import com.gregtechceu.gtceu.api.multiblock.pattern.PatternState; +import com.gregtechceu.gtceu.api.multiblock.predicates.BasePredicate; +import com.gregtechceu.gtceu.api.multiblock.util.AbstractStructureHelper; +import com.gregtechceu.gtceu.api.multiblock.util.AutobuildHelper; +import com.gregtechceu.gtceu.api.multiblock.util.BlockInfo; +import com.gregtechceu.gtceu.api.registry.GTRegistries; +import com.gregtechceu.gtceu.common.network.GTNetwork; +import com.gregtechceu.gtceu.common.network.packets.CPacketTerminalSettings; import com.gregtechceu.gtceu.integration.recipeviewer.widgets.MultiblockPreviewWidget; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtUtils; import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; @@ -18,8 +38,11 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; import brachy.modularui.factory.ClientGUI; import brachy.modularui.factory.PlayerInventoryGuiData; @@ -27,60 +50,120 @@ import brachy.modularui.screen.ModularPanel; import brachy.modularui.screen.UISettings; import brachy.modularui.value.sync.PanelSyncManager; +import com.google.common.collect.HashBasedTable; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.*; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import org.jetbrains.annotations.Nullable; -public class TerminalBehavior implements IInteractionItem, IItemUIHolder { +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; - // FIXME these are global for all terminal items rn - private MultiblockMachineDefinition multiblockDefinition = null; - private MultiblockSchemaInfo multiblockSchemaInfo; - private BlockPos controllerPos; - private Direction frontFacing; - private Direction upFacing; - private boolean isFlipped = false; +import static com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine.DEFAULT_STRUCTURE; +import static com.gregtechceu.gtceu.api.multiblock.util.AutobuildHelper.readBlockPreferences; +public class TerminalBehavior implements IInteractionItem, IItemUIHolder, IAddInformation { + + // todo somewhere client panel warning if the structure to be built is invalid @Override public InteractionResult useOn(UseOnContext context) { Player player = context.getPlayer(); Level level = context.getLevel(); BlockPos pos = context.getClickedPos(); + ItemStack stack = context.getItemInHand(); + CompoundTag tag = stack.getOrCreateTag(); if (player == null || !player.isShiftKeyDown()) { return InteractionResult.PASS; } - if (!player.isCreative()) { - return InteractionResult.PASS; - } - if (!(MetaMachine.getMachine(level, pos) instanceof MultiblockControllerMachine controller)) { return InteractionResult.PASS; } - if (controller.getDefaultPatternState().isFormed()) { + if (!controller.getDefinition().getId().equals(ResourceLocation.parse(tag.getString("controller")))) { + // TODO: Log errors in chat return InteractionResult.PASS; } - if (controller.getDefinition() == this.multiblockDefinition && this.multiblockSchemaInfo != null) { - this.refreshSchema(); - } - if (this.multiblockSchemaInfo == null) { + + PatternState state = controller.getDefaultPatternState(); + if (state.isFormed()) { return InteractionResult.PASS; } - if (this.multiblockSchemaInfo.getStructureBlocks() == null || - this.multiblockSchemaInfo.getStructureBlocks().isEmpty()) { + + if (!tag.contains("facing", CompoundTag.TAG_BYTE) || + !tag.contains("upFacing", CompoundTag.TAG_BYTE) || + !tag.contains("flipped", CompoundTag.TAG_BYTE)) { return InteractionResult.PASS; } + Direction frontFacing = controller.getFrontFacing(); + Direction upFacing = controller.getUpwardsFacing(); + boolean flipped = controller.isFlipped(); + + if (!level.isClientSide) { + ServerPlayer serverPlayer = (ServerPlayer) player; + // Partially copy pasted from MultiblockControllerMachine#onUse. + // TODO: Probably extract into helper function + Map resultStructure = new HashMap<>(); + AbstractStructureHelper structureHelper = null; + IBlockPattern pattern = controller.getStructurePatterns().get(DEFAULT_STRUCTURE); + if (pattern instanceof BlockPattern blockPattern) { + Int2IntMap slices = new Int2IntArrayMap(); + for (int i = 0; i < blockPattern.getSlices().length; i++) { + slices.put(i, blockPattern.getSlices()[i].getMinRepeats()); + } + if (tag.contains("sliceRepeatKeys") && tag.contains("sliceRepeatValues")) { + var sliceRepeatKeys = tag.getIntArray("sliceRepeatKeys"); + var sliceRepeatValues = tag.getIntArray("sliceRepeatValues"); + var length = Math.min(sliceRepeatKeys.length, sliceRepeatValues.length); + for (int i = 0; i < length; i++) { + slices.put(sliceRepeatKeys[i], sliceRepeatValues[i]); + } + } + structureHelper = AbstractStructureHelper.blockPattern(slices); + } else if (pattern instanceof ExpandablePattern expandablePattern) { + IntList dims = new IntArrayList(); + if (expandablePattern.getBoundsConstraints() != null) { + expandablePattern.getBoundsConstraints().apply().stream() + .mapToInt(Pair::left) + .forEach(dims::add); + } + structureHelper = AbstractStructureHelper.expandable(dims); + } + + if (structureHelper != null) { + MultiblockSchemaInfo schemaInfo = createSchemaInfoFromTag(stack); - BlockPos controllerOffset = controller.getBlockPos() - .offset(this.multiblockSchemaInfo.getMapSchema().getControllerPos().multiply(-1)); - if (context.getPlayer().isCreative()) { - for (var entry : this.multiblockSchemaInfo.getStructureBlocks().entrySet()) { - level.setBlockAndUpdate(entry.getKey().offset(controllerOffset), entry.getValue().getBlockState()); + structureHelper.populate(schemaInfo, resultStructure, pattern, readBlockPreferences(tag), frontFacing, + upFacing, + flipped); } - if (!level.isClientSide()) { - // needed to force the multiblock to do a clean check, kinda sus - controller.getDefaultPatternState().getCache().clear(); - controller.checkAndFormStructure(); + // Extract controller block offset + Block controllerBlock = controller.getDefinition().getBlock(); + BlockPos schemaControllerPos = BlockPos.ZERO; + for (var entry : resultStructure.entrySet()) { + if (entry.getValue().getBlockState().is(controllerBlock)) { + schemaControllerPos = entry.getKey(); + break; + } } + + BlockPos controllerOffset = controller.getBlockPos().subtract(schemaControllerPos); + if (player.isCreative()) { + for (var entry : resultStructure.entrySet()) { + level.setBlockAndUpdate(entry.getKey().offset(controllerOffset), entry.getValue().getBlockState()); + } + } else if (structureHelper != null) { + AutobuildHelper.autobuild(serverPlayer, context.getItemInHand(), controller.getDefinition(), controller, + resultStructure, structureHelper); + } + + // needed to force the multiblock to do a clean check, kinda sus + controller.getDefaultPatternState().getCache().clear(); + controller.checkAndFormStructure(); } return InteractionResult.sidedSuccess(level.isClientSide); } @@ -94,56 +177,272 @@ public InteractionResult onItemUseFirst(ItemStack itemStack, UseOnContext contex if (!(MetaMachine.getMachine(level, blockPos) instanceof MultiblockControllerMachine controller)) { return InteractionResult.PASS; } - // always load this data (even if shifting); it's required for #useOn to work - if (controller.getDefinition() != this.multiblockDefinition && this.multiblockSchemaInfo != null) { - this.multiblockSchemaInfo = null; - } - this.multiblockDefinition = controller.getDefinition(); - this.controllerPos = controller.getBlockPos(); - this.frontFacing = controller.getFrontFacing(); - this.upFacing = controller.getUpwardsFacing(); - this.isFlipped = controller.isFlipped(); if (player == null || player.isShiftKeyDown()) { return InteractionResult.PASS; } + if (level.isClientSide) { player.displayClientMessage(Component.literal("Loaded controller information"), false); + } else { + writeControllerInfo(itemStack, controller); } return InteractionResult.sidedSuccess(level.isClientSide); } - @Override - public boolean shouldOpenUI() { - return this.multiblockDefinition != null; + public boolean shouldOpenUI(ItemStack item) { + return item.getOrCreateTag().contains("controller"); } @Override public InteractionResultHolder use(Item item, Level level, Player player, InteractionHand usedHand) { - if (!shouldOpenUI()) return IItemUIHolder.super.use(item, level, player, usedHand); + if (!shouldOpenUI(player.getItemInHand(usedHand))) { + if (level.isClientSide) + player.displayClientMessage(Component.literal("No controller information loaded"), false); + return InteractionResultHolder.pass(player.getItemInHand(usedHand)); + } if (level.isClientSide) { PlayerInventoryGuiData guiData = PlayerInventoryGuiData.of(player, InventoryTypes.PLAYER, null, usedHand == InteractionHand.OFF_HAND ? Inventory.SLOT_OFFHAND : player.getInventory().selected); - ModularPanel clientPanel = clientPanel(); - ClientGUI.open(createScreen(guiData, clientPanel)); + Optional> clientPanel = clientPanel(player.getItemInHand(usedHand), usedHand); + if (clientPanel.isEmpty()) { + return InteractionResultHolder.sidedSuccess(player.getItemInHand(usedHand), level.isClientSide); + } + ClientGUI.open(createScreen(guiData, clientPanel.get())); } return InteractionResultHolder.sidedSuccess(player.getItemInHand(usedHand), level.isClientSide); } - private ModularPanel clientPanel() { - MultiblockPreviewWidget previewWidget = new MultiblockPreviewWidget(this.multiblockDefinition, - this.multiblockSchemaInfo, 200, 200) - .setControllerPos(this.controllerPos) - .setFrontFacing(this.frontFacing).setUpFacing(this.upFacing).setFlipped(this.isFlipped); + private Optional> clientPanel(ItemStack item, InteractionHand hand) { + CompoundTag tag = item.getOrCreateTag(); + if (!tag.contains("controller")) { + return Optional.empty(); + } + ResourceLocation controllerLocation = ResourceLocation.parse(tag.getString("controller")); + var definition = GTRegistries.MACHINES.get(controllerLocation); + if (definition == null || !(definition instanceof MultiblockMachineDefinition multiblockDefinition)) { + return Optional.empty(); + } + if (!tag.contains("pos")) { + return Optional.empty(); + } + BlockPos controllerPos = BlockPos.of(tag.getLong("pos")); + + if (!tag.contains("facing", CompoundTag.TAG_BYTE) || + !tag.contains("upFacing", CompoundTag.TAG_BYTE) || + !tag.contains("flipped", CompoundTag.TAG_BYTE)) { + return Optional.empty(); + } + + Direction frontFacing = Direction.values()[tag.getByte("facing")]; + Direction upFacing = Direction.values()[tag.getByte("upFacing")]; + boolean flipped = tag.getBoolean("flipped"); + + MultiblockSchemaInfo info = createSchemaInfoFromTag(item); + + MultiblockPreviewWidget previewWidget = new MultiblockPreviewWidget(multiblockDefinition, info, + 200, 200) + .setControllerPos(controllerPos) + .setFrontFacing(frontFacing).setUpFacing(upFacing).setFlipped(flipped); previewWidget.refreshSchema(); - return ModularPanel.defaultPanel("terminal") + return Optional.of(ModularPanel.defaultPanel("terminal") .coverChildren() - .onCloseAction(() -> { - this.multiblockSchemaInfo = previewWidget.getMultiblockSchemaInfo(); - }) - .child(previewWidget); + .child(previewWidget) + .onCloseAction(() -> writeMultiblockInfo(multiblockDefinition, hand, previewWidget, info))); + } + + private void writeMultiblockInfo(MultiblockMachineDefinition definition, InteractionHand hand, + MultiblockPreviewWidget previewWidget, MultiblockSchemaInfo info) { + MultiblockSchemaInfo schemaInfo = previewWidget.getMultiblockSchemaInfo(); + + Long2ObjectMap blockPreferences = new Long2ObjectOpenHashMap<>(); + for (var entry : schemaInfo.getUserGlobalBlockPreferences().long2ObjectEntrySet()) { + blockPreferences.put(entry.getLongKey(), entry.getValue().getBlockState()); + } + + GTNetwork.sendToServer(new CPacketTerminalSettings(hand, definition, schemaInfo.getUserSliceRepeats(), + schemaInfo.getUserDimensions(), blockPreferences, schemaInfo.getBlockPreferences(), + schemaInfo.getMinMaxPreferences())); + } + + public static void writeControllerInfo(ItemStack item, MultiblockControllerMachine controller) { + // TODO uuid gathering + + CompoundTag tag = item.getOrCreateTag(); + if (!tag.isEmpty()) { // clear tag when trying to open a new machine definition + tag = new CompoundTag(); + } + tag.putString("controller", controller.getDefinition().getId().toString()); + tag.putLong("pos", controller.getBlockPos().asLong()); + tag.putByte("facing", (byte) controller.getFrontFacing().ordinal()); + tag.putByte("upFacing", (byte) controller.getUpwardsFacing().ordinal()); + tag.putBoolean("flipped", controller.isFlipped()); + } + + public static MultiblockSchemaInfo createSchemaInfoFromTag(ItemStack item) { + CompoundTag tag = item.getOrCreateTag(); + // TODO fix when trying to open overwritten info from another controller type + MultiblockSchemaInfo info = new MultiblockSchemaInfo(); + if (tag.contains("sliceRepeatKeys") && tag.contains("sliceRepeatValues")) { + int[] repeatKeys = tag.getIntArray("sliceRepeatKeys"); + int[] repeatValues = tag.getIntArray("sliceRepeatValues"); + for (int i = 0; i < repeatKeys.length; i++) { + info.getUserSliceRepeats().put(repeatKeys[i], repeatValues[i]); + } + } + + if (tag.contains("dimensions")) { + info.getUserDimensions().addAll(IntList.of(tag.getIntArray("dimensions"))); + } + + if (tag.contains("globalPreferences")) { + ListTag preferences = tag.getList("globalPreferences", CompoundTag.TAG_COMPOUND); + for (int i = 0; i < preferences.size(); i++) { + CompoundTag blockTag = preferences.getCompound(i); + long pos = blockTag.getLong("pos"); + BlockState state = NbtUtils.readBlockState(BuiltInRegistries.BLOCK.asLookup(), + blockTag.getCompound("state")); + info.getUserGlobalBlockPreferences().put(pos, BlockInfo.fromBlockState(state)); + } + } + + // TODO maybe move this as part of per pattern type encoding? + if (tag.contains("blockPreferences")) { + ListTag preferences = tag.getList("blockPreferences", CompoundTag.TAG_COMPOUND); + ResourceLocation controllerLocation = ResourceLocation.parse(tag.getString("controller")); + var definition = (MultiblockMachineDefinition) GTRegistries.MACHINES.get(controllerLocation); + BlockPattern blockPattern = (BlockPattern) definition.getStructurePatterns().get(DEFAULT_STRUCTURE).get(); + for (int i = 0; i < preferences.size(); i++) { + CompoundTag inner = preferences.getCompound(i); + char c = (char) inner.getByte("p"); + int baseIndex = inner.getInt("b"); + int candidateIndex = inner.getInt("i"); + + MultiPredicate pred = blockPattern.getPredicates().get(c); + BasePredicate base = pred.predicates().get(baseIndex); + BlockInfo blockInfo = base.getCandidates().get(candidateIndex); + + info.getBlockPreferences().put(pred, base, blockInfo); + } + } + + if (tag.contains("minMaxPreferences")) { + ListTag minMaxPreferences = tag.getList("minMaxPreferences", CompoundTag.TAG_COMPOUND); + ResourceLocation controllerLocation = ResourceLocation.parse(tag.getString("controller")); + var definition = (MultiblockMachineDefinition) GTRegistries.MACHINES.get(controllerLocation); + BlockPattern blockPattern = (BlockPattern) definition.getStructurePatterns().get(DEFAULT_STRUCTURE).get(); + for (int i = 0; i < minMaxPreferences.size(); i++) { + CompoundTag inner = minMaxPreferences.getCompound(i); + char c = (char) inner.getByte("p"); + int baseIndex = inner.getInt("b"); + int min = inner.getInt("min"); + int max = inner.getInt("min"); + + MultiPredicate pred = blockPattern.getPredicates().get(c); + BasePredicate base = pred.predicates().get(baseIndex); + + info.getMinMaxPreferences().put(pred, base, IntIntPair.of(min, max)); + } + } + return info; + } + + public static void applyUserPreferences(ItemStack item, Int2IntMap sliceRepeats, IntList dimensions, + Long2ObjectMap globalPreferences, + HashBasedTable blockPreferences, + HashBasedTable minMaxPreferences) { + CompoundTag tag = item.getOrCreateTag(); + + if (sliceRepeats.isEmpty()) { + tag.remove("sliceRepeatKeys"); + tag.remove("sliceRepeatValues"); + } else { + int[] keys = new int[sliceRepeats.size()]; + int[] values = new int[sliceRepeats.size()]; + int i = 0; + for (var entry : sliceRepeats.int2IntEntrySet()) { + keys[i] = entry.getIntKey(); + values[i] = entry.getIntValue(); + i++; + } + tag.putIntArray("sliceRepeatKeys", keys); + tag.putIntArray("sliceRepeatValues", values); + } + + if (dimensions.isEmpty()) { + tag.remove("dimensions"); + } else { + tag.putIntArray("dimensions", dimensions.toIntArray()); + } + + if (globalPreferences.isEmpty()) { + tag.remove("globalPreferences"); + } else { + ListTag preferences = new ListTag(); + for (var entry : globalPreferences.long2ObjectEntrySet()) { + CompoundTag preference = new CompoundTag(); + preference.putLong("pos", entry.getLongKey()); + // TODO move to base predicate candidate index? + preference.put("state", NbtUtils.writeBlockState(entry.getValue())); + preferences.add(preference); + } + tag.put("globalPreferences", preferences); + } + + if (blockPreferences.isEmpty()) { + tag.remove("blockPreferences"); + } else { + ListTag preferences = new ListTag(); + ResourceLocation controllerLocation = ResourceLocation.parse(tag.getString("controller")); + var definition = (MultiblockMachineDefinition) GTRegistries.MACHINES.get(controllerLocation); + BlockPattern blockPattern = (BlockPattern) definition.getStructurePatterns().get(DEFAULT_STRUCTURE).get(); + for (var entry : blockPreferences.cellSet()) { + CompoundTag preference = new CompoundTag(); + MultiPredicate pred = entry.getRowKey(); + BasePredicate base = entry.getColumnKey(); + + char c = blockPattern.getPredicates().char2ObjectEntrySet() + .stream() + .filter(e -> e.getValue().equals(pred)) + .findFirst() + .get().getCharKey(); + + preference.putByte("p", (byte) c); + preference.putInt("b", pred.predicates().indexOf(base)); + preference.putInt("i", base.getCandidates().indexOf(entry.getValue())); + preferences.add(preference); + } + tag.put("blockPreferences", preferences); + } + + if (minMaxPreferences.isEmpty()) { + tag.remove("minMaxPreferences"); + } else { + ListTag minMaxs = new ListTag(); + ResourceLocation controllerLocation = ResourceLocation.parse(tag.getString("controller")); + var definition = (MultiblockMachineDefinition) GTRegistries.MACHINES.get(controllerLocation); + BlockPattern blockPattern = (BlockPattern) definition.getStructurePatterns().get(DEFAULT_STRUCTURE).get(); + for (var entry : minMaxPreferences.cellSet()) { + CompoundTag inner = new CompoundTag(); + MultiPredicate pred = entry.getRowKey(); + BasePredicate base = entry.getColumnKey(); + + char c = blockPattern.getPredicates().char2ObjectEntrySet() + .stream() + .filter(e -> e.getValue().equals(pred)) + .findFirst() + .get().getCharKey(); + + inner.putByte("p", (byte) c); + inner.putInt("b", pred.predicates().indexOf(base)); + inner.putInt("min", entry.getValue().firstInt()); + inner.putInt("max", entry.getValue().secondInt()); + minMaxs.add(inner); + } + tag.put("minMaxPreferences", minMaxs); + } } @Override @@ -151,7 +450,20 @@ public ModularPanel buildUI(PlayerInventoryGuiData data, PanelSyncManager return null; } - private void refreshSchema() { - this.multiblockSchemaInfo.refreshSchema(multiblockDefinition, frontFacing, upFacing, isFlipped, null); + @Override + public void appendHoverText(ItemStack stack, @Nullable Level level, List tooltipComponents, TooltipFlag isAdvanced) { + CompoundTag tag = stack.getOrCreateTag(); + + if (tag.contains("pos")) { + long blockPos = tag.getLong("pos"); + BlockPos pos = BlockPos.of(blockPos); + tooltipComponents.add(Component.translatable("gtceu.top.buffer_bound_pos", pos.getX(), pos.getY(), pos.getZ()) + .withStyle(ChatFormatting.GOLD)); + } + if (tag.contains("controller")) { + ResourceLocation controllerLocation = ResourceLocation.parse(tag.getString("controller")); + var definition = (MultiblockMachineDefinition) GTRegistries.MACHINES.get(controllerLocation); + tooltipComponents.add(definition.get().getName()); + } } } diff --git a/src/main/java/com/gregtechceu/gtceu/common/network/GTNetwork.java b/src/main/java/com/gregtechceu/gtceu/common/network/GTNetwork.java index 3ec32a23ea8..234e46b7ebe 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/network/GTNetwork.java +++ b/src/main/java/com/gregtechceu/gtceu/common/network/GTNetwork.java @@ -103,6 +103,7 @@ public static void init() { register(SPacketImageResponse.class, SPacketImageResponse::new, NetworkDirection.PLAY_TO_CLIENT); register(CPacketKeyDown.class, CPacketKeyDown::new, NetworkDirection.PLAY_TO_SERVER); + register(CPacketTerminalSettings.class, CPacketTerminalSettings::new, NetworkDirection.PLAY_TO_SERVER); register(SPacketSyncOreVeins.class, SPacketSyncOreVeins::new, NetworkDirection.PLAY_TO_CLIENT); register(SPacketSyncFluidVeins.class, SPacketSyncFluidVeins::new, NetworkDirection.PLAY_TO_CLIENT); diff --git a/src/main/java/com/gregtechceu/gtceu/common/network/packets/CPacketTerminalSettings.java b/src/main/java/com/gregtechceu/gtceu/common/network/packets/CPacketTerminalSettings.java new file mode 100644 index 00000000000..f60842ffaff --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/common/network/packets/CPacketTerminalSettings.java @@ -0,0 +1,188 @@ +package com.gregtechceu.gtceu.common.network.packets; + +import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; +import com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine; +import com.gregtechceu.gtceu.api.multiblock.MultiPredicate; +import com.gregtechceu.gtceu.api.multiblock.pattern.BlockPattern; +import com.gregtechceu.gtceu.api.multiblock.pattern.IBlockPattern; +import com.gregtechceu.gtceu.api.multiblock.predicates.BasePredicate; +import com.gregtechceu.gtceu.api.multiblock.util.BlockInfo; +import com.gregtechceu.gtceu.api.registry.GTRegistries; +import com.gregtechceu.gtceu.common.data.GTItems; +import com.gregtechceu.gtceu.common.item.behavior.TerminalBehavior; +import com.gregtechceu.gtceu.common.network.GTNetwork; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.network.NetworkEvent; + +import com.google.common.collect.HashBasedTable; +import it.unimi.dsi.fastutil.ints.*; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + +import java.nio.charset.StandardCharsets; + +public class CPacketTerminalSettings implements GTNetwork.INetPacket { + + private final InteractionHand hand; + private final MultiblockMachineDefinition machineDefinition; + private final Int2IntMap sliceRepeats; + private final IntList dimensions; + private final Long2ObjectMap globalPreferences; + private final HashBasedTable blockPreferences; + private final HashBasedTable minMaxPreferences; + + public CPacketTerminalSettings(InteractionHand hand, MultiblockMachineDefinition def, Int2IntMap sliceRepeats, + IntList dimensions, + Long2ObjectMap globalPreferences, + HashBasedTable blockPreferences, + HashBasedTable minMaxPreferences) { + this.hand = hand; + this.machineDefinition = def; + this.sliceRepeats = sliceRepeats; + this.dimensions = dimensions; + this.globalPreferences = globalPreferences; + this.blockPreferences = blockPreferences; + this.minMaxPreferences = minMaxPreferences; + } + + public CPacketTerminalSettings(FriendlyByteBuf buf) { + this.hand = buf.readEnum(InteractionHand.class); + + int size = buf.readVarInt(); + ResourceLocation resLoc = ResourceLocation.parse(buf.readCharSequence(size, StandardCharsets.UTF_8).toString()); + this.machineDefinition = (MultiblockMachineDefinition) GTRegistries.MACHINES.get(resLoc); + + int repeatCount = buf.readVarInt(); + this.sliceRepeats = new Int2IntArrayMap(repeatCount); + for (int i = 0; i < repeatCount; i++) { + this.sliceRepeats.put(buf.readVarInt(), buf.readVarInt()); + } + + int dimensionCount = buf.readVarInt(); + this.dimensions = new IntArrayList(dimensionCount); + for (int i = 0; i < dimensionCount; i++) { + this.dimensions.add(buf.readVarInt()); + } + + int globalPreferenceCount = buf.readVarInt(); + this.globalPreferences = new Long2ObjectOpenHashMap<>(globalPreferenceCount); + for (int i = 0; i < globalPreferenceCount; i++) { + long pos = buf.readLong(); + BlockState state = Block.stateById(buf.readVarInt()); + this.globalPreferences.put(pos, state); + } + + blockPreferences = HashBasedTable.create(); + minMaxPreferences = HashBasedTable.create(); + IBlockPattern pattern = machineDefinition.getStructurePatterns() + .get(MultiblockControllerMachine.DEFAULT_STRUCTURE).get(); + if (pattern instanceof BlockPattern blockPattern) { + int preferenceSize = buf.readVarInt(); + for (int i = 0; i < preferenceSize; i++) { + char c = buf.readChar(); + int baseIndex = buf.readVarInt(); + int candidateIndex = buf.readVarInt(); + + MultiPredicate pred = blockPattern.getPredicates().get(c); + BasePredicate base = pred.predicates().get(baseIndex); + BlockInfo info = base.getCandidates().get(candidateIndex); + + this.blockPreferences.put(pred, base, info); + } + + int minMaxSize = buf.readVarInt(); + for (int i = 0; i < minMaxSize; i++) { + char c = buf.readChar(); + int baseIndex = buf.readVarInt(); + + MultiPredicate pred = blockPattern.getPredicates().get(c); + BasePredicate base = pred.predicates().get(baseIndex); + int min = buf.readVarInt(); + int max = buf.readVarInt(); + + this.minMaxPreferences.put(pred, base, IntIntPair.of(min, max)); + } + } + } + + @Override + public void encode(FriendlyByteBuf buf) { + buf.writeEnum(this.hand); + + buf.writeVarInt(this.machineDefinition.getId().toString().length()); + buf.writeCharSequence(this.machineDefinition.getId().toString(), StandardCharsets.UTF_8); + + buf.writeVarInt(this.sliceRepeats.size()); + for (var entry : this.sliceRepeats.int2IntEntrySet()) { + buf.writeVarInt(entry.getIntKey()); + buf.writeVarInt(entry.getIntValue()); + } + + buf.writeVarInt(this.dimensions.size()); + for (int dimension : this.dimensions) { + buf.writeVarInt(dimension); + } + + buf.writeVarInt(this.globalPreferences.size()); + for (var entry : this.globalPreferences.long2ObjectEntrySet()) { + buf.writeLong(entry.getLongKey()); + buf.writeVarInt(Block.getId(entry.getValue())); + } + + IBlockPattern pattern = machineDefinition.getStructurePatterns() + .get(MultiblockControllerMachine.DEFAULT_STRUCTURE).get(); + if (pattern instanceof BlockPattern blockPattern) { + buf.writeVarInt(this.blockPreferences.rowKeySet().size()); + for (var entry : this.blockPreferences.cellSet()) { + MultiPredicate pred = entry.getRowKey(); + BasePredicate base = entry.getColumnKey(); + + char c = blockPattern.getPredicates().char2ObjectEntrySet() + .stream() + .filter(e -> e.getValue().equals(pred)) + .findFirst() + .get().getCharKey(); + buf.writeChar(c); + + buf.writeVarInt(pred.predicates().indexOf(base)); + buf.writeVarInt(base.getCandidates().indexOf(entry.getValue())); + } + + buf.writeVarInt(this.minMaxPreferences.rowKeySet().size()); + for (var entry : this.minMaxPreferences.cellSet()) { + MultiPredicate pred = entry.getRowKey(); + BasePredicate base = entry.getColumnKey(); + + char c = blockPattern.getPredicates().char2ObjectEntrySet() + .stream() + .filter(e -> e.getValue().equals(pred)) + .findFirst() + .get().getCharKey(); + buf.writeChar(c); + + buf.writeVarInt(pred.predicates().indexOf(base)); + buf.writeVarInt(entry.getValue().firstInt()); + buf.writeVarInt(entry.getValue().secondInt()); + } + } + } + + @Override + public void execute(NetworkEvent.Context context) { + ServerPlayer sender = context.getSender(); + if (sender == null) return; + + ItemStack held = sender.getItemInHand(this.hand); + if (!GTItems.TERMINAL.isIn(held)) return; + + TerminalBehavior.applyUserPreferences(held, this.sliceRepeats, this.dimensions, this.globalPreferences, + this.blockPreferences, this.minMaxPreferences); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/core/mixins/mui/ModularUIEmiRecipeAccessor.java b/src/main/java/com/gregtechceu/gtceu/core/mixins/mui/ModularUIEmiRecipeAccessor.java new file mode 100644 index 00000000000..41794b8551c --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/core/mixins/mui/ModularUIEmiRecipeAccessor.java @@ -0,0 +1,15 @@ +package com.gregtechceu.gtceu.core.mixins.mui; + +import brachy.modularui.api.widget.IWidget; +import brachy.modularui.integration.emi.recipe.ModularUIEmiRecipe; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.function.Supplier; + +@Mixin(ModularUIEmiRecipe.class) +public interface ModularUIEmiRecipeAccessor { + + @Accessor + Supplier getRecipeUI(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/data/recipe/misc/MachineRecipeLoader.java b/src/main/java/com/gregtechceu/gtceu/data/recipe/misc/MachineRecipeLoader.java index 5ca473ee574..c6153e927c2 100644 --- a/src/main/java/com/gregtechceu/gtceu/data/recipe/misc/MachineRecipeLoader.java +++ b/src/main/java/com/gregtechceu/gtceu/data/recipe/misc/MachineRecipeLoader.java @@ -1543,6 +1543,8 @@ private static void registerNBTRemoval(Consumer provider) { TOOL_DATA_ORB.asStack()); VanillaRecipeHelper.addShapelessNBTClearingRecipe(provider, "data_module_nbt", TOOL_DATA_MODULE.asStack(), TOOL_DATA_MODULE.asStack()); + VanillaRecipeHelper.addShapelessNBTClearingRecipe(provider, "terminal_nbt", TERMINAL.asStack(), + TERMINAL.asStack()); // Jetpacks VanillaRecipeHelper.addShapelessRecipe(provider, "fluid_jetpack_clear", LIQUID_FUEL_JETPACK.asStack(), diff --git a/src/main/java/com/gregtechceu/gtceu/integration/recipeviewer/emi/MultiblockInfoEmiCategory.java b/src/main/java/com/gregtechceu/gtceu/integration/recipeviewer/emi/MultiblockInfoEmiCategory.java index 6e278b99c02..04de70dca3f 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/recipeviewer/emi/MultiblockInfoEmiCategory.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/recipeviewer/emi/MultiblockInfoEmiCategory.java @@ -2,20 +2,13 @@ import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; -import com.gregtechceu.gtceu.api.multiblock.pattern.BlockPattern; -import com.gregtechceu.gtceu.api.multiblock.pattern.ExpandablePattern; -import com.gregtechceu.gtceu.api.multiblock.pattern.IBlockPattern; -import com.gregtechceu.gtceu.api.multiblock.util.AbstractStructureHelper; -import com.gregtechceu.gtceu.api.multiblock.util.BlockInfo; import com.gregtechceu.gtceu.api.registry.GTRegistries; import com.gregtechceu.gtceu.common.data.machines.GTMultiMachines; +import com.gregtechceu.gtceu.core.mixins.mui.ModularUIEmiRecipeAccessor; import com.gregtechceu.gtceu.integration.recipeviewer.widgets.MultiblockPreviewWidget; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.block.Block; import brachy.modularui.integration.emi.recipe.ModularUIEmiRecipe; import dev.emi.emi.api.EmiRegistry; @@ -23,19 +16,9 @@ import dev.emi.emi.api.stack.EmiIngredient; import dev.emi.emi.api.stack.EmiStack; import dev.emi.emi.api.widget.WidgetHolder; -import it.unimi.dsi.fastutil.Pair; -import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine.DEFAULT_STRUCTURE; +import java.util.*; public class MultiblockInfoEmiCategory extends EmiRecipeCategory { @@ -62,43 +45,10 @@ public Component getName() { public static class MultiblockInfoEmiWrapper extends ModularUIEmiRecipe { private final MultiblockMachineDefinition definition; - private final List containedBlocks = new ArrayList<>(); public MultiblockInfoEmiWrapper(MultiblockMachineDefinition definition) { super(definition.getId(), () -> new MultiblockPreviewWidget(definition, null, 200, 180)); this.definition = definition; - initializeContainedBlocks(); - } - - private void initializeContainedBlocks() { - Map resultStructure = new HashMap<>(); - IBlockPattern pattern = definition.getStructurePatterns().get(DEFAULT_STRUCTURE).get(); - AbstractStructureHelper structureHelper = null; - if (pattern instanceof BlockPattern blockPattern) { - var sliceRepeats = new Int2IntArrayMap(); - for (int i = 0; i < blockPattern.getSlices().length; i++) { - sliceRepeats.put(i, blockPattern.getSlices()[i].getMinRepeats()); - } - structureHelper = AbstractStructureHelper.blockPattern(sliceRepeats); - } else if (pattern instanceof ExpandablePattern expandablePattern) { - var userDimensions = new IntArrayList(); - expandablePattern.getBoundsConstraints().apply().stream() - .mapToInt(Pair::left) - .forEach(userDimensions::add); - structureHelper = AbstractStructureHelper.expandable(userDimensions); - } - if (structureHelper != null) { - structureHelper.populate(resultStructure, pattern, null, - definition.getRotationState().defaultDirection, switch (definition.getRotationState()) { - case Y_AXIS -> Direction.NORTH; - case ALL, NON_Y_AXIS, NONE -> Direction.UP; - }, false); - - Object2IntMap blockCount = new Object2IntOpenHashMap<>(); - resultStructure.forEach( - (pos, state) -> blockCount.mergeInt(state.getBlockState().getBlock(), 1, Integer::sum)); - blockCount.forEach((block, count) -> containedBlocks.add(EmiStack.of(block.asItem(), count))); - } } @Override @@ -118,7 +68,12 @@ public EmiRecipeCategory getCategory() { @Override public List getInputs() { - return containedBlocks; + var recipeUI = ((MultiblockPreviewWidget) ((ModularUIEmiRecipeAccessor) this).getRecipeUI().get()); + if (recipeUI.getMultiblockSchemaInfo() == null) return Collections.emptyList(); + var blockCounts = recipeUI.getMultiblockSchemaInfo().getBlockCounts(); + List inputs = new ArrayList<>(); + blockCounts.forEach((block, count) -> inputs.add(EmiStack.of(block.asItem(), count))); + return inputs; } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/integration/recipeviewer/rei/MultiblockInfoReiCategory.java b/src/main/java/com/gregtechceu/gtceu/integration/recipeviewer/rei/MultiblockInfoReiCategory.java index 42c96d49e5a..b73ac785ce1 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/recipeviewer/rei/MultiblockInfoReiCategory.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/recipeviewer/rei/MultiblockInfoReiCategory.java @@ -2,6 +2,7 @@ import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; +import com.gregtechceu.gtceu.api.mui.MultiblockSchemaInfo; import com.gregtechceu.gtceu.api.multiblock.pattern.BlockPattern; import com.gregtechceu.gtceu.api.multiblock.pattern.ExpandablePattern; import com.gregtechceu.gtceu.api.multiblock.pattern.IBlockPattern; @@ -111,7 +112,7 @@ private void initializeContainedBlocks() { structureHelper = AbstractStructureHelper.expandable(userDimensions); } if (structureHelper != null) { - structureHelper.populate(resultStructure, pattern, null, + structureHelper.populate(new MultiblockSchemaInfo(), resultStructure, pattern, null, definition.getRotationState().defaultDirection, switch (definition.getRotationState()) { case Y_AXIS -> Direction.NORTH; case ALL, NON_Y_AXIS, NONE -> Direction.UP; diff --git a/src/main/java/com/gregtechceu/gtceu/integration/recipeviewer/widgets/MultiblockPreviewWidget.java b/src/main/java/com/gregtechceu/gtceu/integration/recipeviewer/widgets/MultiblockPreviewWidget.java index a249211bdd6..9069d51e26b 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/recipeviewer/widgets/MultiblockPreviewWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/recipeviewer/widgets/MultiblockPreviewWidget.java @@ -26,6 +26,7 @@ import brachy.modularui.api.drawable.IIcon; import brachy.modularui.api.drawable.Text; import brachy.modularui.api.widget.IGuiAction; +import brachy.modularui.drawable.DynamicDrawable; import brachy.modularui.drawable.Icon; import brachy.modularui.drawable.ItemDrawable; import brachy.modularui.drawable.SchemaRenderer; @@ -82,8 +83,7 @@ public class MultiblockPreviewWidget extends ParentWidget> patterns = multiblockDefinition.getStructurePatterns() .entrySet().stream().map(e -> Map.entry(e.getKey(), e.getValue().get())).toList(); - this.multiblockSchemaInfo.getRenderer().camera().setPosAndLookAt(0, 0, -10, + this.multiblockSchemaInfo.getRenderer().camera().setPosAndLookAt(0, 0, 0, this.multiblockSchemaInfo.getMapSchema().getCenter()); SchemaWidget schema = this.multiblockSchemaInfo.getRenderer().asWidget() .listenGuiAction(setBlockOnClick) @@ -176,10 +176,10 @@ public MultiblockPreviewWidget(MultiblockMachineDefinition definition, Multibloc this.multiblockSchemaInfo.setMultiSchema(schema); this.multiblockSchemaInfo.getMultiSchema().getSchemaRenderer().updateRenderFilter((pos, state) -> { - if (yLevel == -1) { + if (yLevel == Integer.MAX_VALUE) { return true; } - return pos.getY() >= yLevel; + return pos.getY() <= yLevel; }); this.coverChildren() @@ -199,6 +199,51 @@ public MultiblockPreviewWidget(MultiblockMachineDefinition definition, Multibloc } return true; })) + // todo serialize these values as part of the schema nbt + .child(new ToggleButton() + .tooltip(r -> r.addLine(Component.literal("Press to flip structure"))) + .left(25) + .value(new BoolValue.Dynamic(() -> isFlipped, v -> { + setFlipped(!isFlipped); + refreshSchema(); + refreshViewWidget(); + }))) + .child(new ButtonWidget<>() + .overlay(new DynamicDrawable(() -> Text.dynamic( + () -> Component.literal(yLevel == Integer.MAX_VALUE ? "A" : String.valueOf(yLevel))) + .asIcon())) + .left(45) + .onMousePressed((c, b) -> { + var bounds = multiblockSchemaInfo.getMapSchema().getBounds(); + int min = bounds.getFirst().getY(); + int max = bounds.getSecond().getY(); + + if (b == InputConstants.MOUSE_BUTTON_LEFT) { + if (yLevel == Integer.MAX_VALUE) yLevel = min - 1; + yLevel += 1; + } else if (b == InputConstants.MOUSE_BUTTON_RIGHT) { + if (yLevel == Integer.MAX_VALUE) yLevel = max; + yLevel -= 1; + } else if (b == InputConstants.MOUSE_BUTTON_MIDDLE) { + yLevel = Integer.MAX_VALUE; + } + if (yLevel >= max) { + yLevel = Integer.MAX_VALUE; + } + if (yLevel < min) { + yLevel = Integer.MAX_VALUE; + } + refreshSchema(); + Reference2IntMap newBlockCounts = new Reference2IntOpenHashMap<>(); + for (var entry : this.multiblockSchemaInfo.getStructureBlocks().entrySet()) { + if (entry.getKey().getY() <= yLevel) { + newBlockCounts.merge(entry.getValue().getBlockState().getBlock(), 1, Integer::sum); + } + } + this.multiblockSchemaInfo.setBlockCounts(newBlockCounts); + refreshViewWidget(); + return true; + })) .child(Flow.col() .name("main") .coverChildren() diff --git a/src/main/resources/gtceu.mixins.json b/src/main/resources/gtceu.mixins.json index 1f303bdafbd..5e2015d92a8 100644 --- a/src/main/resources/gtceu.mixins.json +++ b/src/main/resources/gtceu.mixins.json @@ -101,6 +101,7 @@ "forge.PartialNBTIngredientAccessor", "forge.StrictNBTIngredientAccessor", "jei.FluidHelperMixin", + "mui.ModularUIEmiRecipeAccessor", "registrate.AbstractRegistrateAccessor", "registrate.RegistrateDataProviderAccessor", "rei.InputSlotCrafterMixin",