diff --git a/README.md b/README.md index 5b4c0f91f..03df907f4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## What is Pylon? Pylon is an upcoming Minecraft Java plugin that will hugely expand vanilla gameplay with new content, including electric machines, huge multiblocks, a fully-fledged fluid system, a complex smelting system, extensive automation options and much, an (actually good) research system, and much more. It is intended to supersede Slimefun -Pylon uses an addon system, meaning anyone can add content to Pylon by writing an addon for it! It also comes with a number of really useful features, such as: +Pylon uses an addon system, meaning anyone can add content to Pylon by writing an addon for it! It also comes with a number of really useful features, such as - First-class translation support, meaning each player can select their own language. - Extensive configuration options, including per-machine configuration. - An intuitive and user-friendly guide to help players figure out the plugin. diff --git a/src/main/java/io/github/pylonmc/pylon/base/BaseBlocks.java b/src/main/java/io/github/pylonmc/pylon/base/BaseBlocks.java index 4405697cb..23df02586 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/BaseBlocks.java +++ b/src/main/java/io/github/pylonmc/pylon/base/BaseBlocks.java @@ -5,6 +5,7 @@ import io.github.pylonmc.pylon.base.content.building.Immobilizer; import io.github.pylonmc.pylon.base.content.building.Pedestal; import io.github.pylonmc.pylon.base.content.components.*; +import io.github.pylonmc.pylon.base.content.machines.cargo.*; import io.github.pylonmc.pylon.base.content.components.EnrichedSoulSoil; import io.github.pylonmc.pylon.base.content.machines.cargo.CargoBuffer; import io.github.pylonmc.pylon.base.content.machines.cargo.CargoExtractor; @@ -150,5 +151,9 @@ public static void initialize() { PylonBlock.register(BaseKeys.CARGO_BUFFER, Material.STRUCTURE_VOID, CargoBuffer.class); PylonBlock.register(BaseKeys.CARGO_EXTRACTOR, Material.STRUCTURE_VOID, CargoExtractor.class); PylonBlock.register(BaseKeys.CARGO_INSERTER, Material.STRUCTURE_VOID, CargoInserter.class); + PylonBlock.register(BaseKeys.CARGO_SPLITTER, Material.STRUCTURE_VOID, CargoSplitter.class); + PylonBlock.register(BaseKeys.CARGO_MERGER, Material.STRUCTURE_VOID, CargoMerger.class); + PylonBlock.register(BaseKeys.CARGO_VALVE, Material.STRUCTURE_VOID, CargoValve.class); + PylonBlock.register(BaseKeys.CARGO_FILTER, Material.STRUCTURE_VOID, CargoFilter.class); } } diff --git a/src/main/java/io/github/pylonmc/pylon/base/BaseItems.java b/src/main/java/io/github/pylonmc/pylon/base/BaseItems.java index dcb69e746..5176a8044 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/BaseItems.java +++ b/src/main/java/io/github/pylonmc/pylon/base/BaseItems.java @@ -8,9 +8,7 @@ import io.github.pylonmc.pylon.base.content.combat.IceArrow; import io.github.pylonmc.pylon.base.content.combat.ReactivatedWitherSkull; import io.github.pylonmc.pylon.base.content.combat.RecoilArrow; -import io.github.pylonmc.pylon.base.content.machines.cargo.CargoBuffer; -import io.github.pylonmc.pylon.base.content.machines.cargo.CargoExtractor; -import io.github.pylonmc.pylon.base.content.machines.cargo.CargoInserter; +import io.github.pylonmc.pylon.base.content.machines.cargo.*; import io.github.pylonmc.pylon.base.content.machines.diesel.machines.*; import io.github.pylonmc.pylon.base.content.machines.diesel.production.Biorefinery; import io.github.pylonmc.pylon.base.content.machines.diesel.production.Fermenter; @@ -2021,6 +2019,38 @@ private BaseItems() { BasePages.CARGO.addItem(CARGO_INSERTER); } + public static final ItemStack CARGO_SPLITTER = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.CARGO_SPLITTER) + .set(DataComponentTypes.ITEM_MODEL, Material.STRIPPED_CRIMSON_HYPHAE.getKey()) + .build(); + static { + PylonItem.register(CargoSplitter.Item.class, CARGO_SPLITTER, BaseKeys.CARGO_SPLITTER); + BasePages.CARGO.addItem(CARGO_SPLITTER); + } + + public static final ItemStack CARGO_MERGER = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.CARGO_MERGER) + .set(DataComponentTypes.ITEM_MODEL, Material.STRIPPED_WARPED_HYPHAE.getKey()) + .build(); + static { + PylonItem.register(CargoSplitter.Item.class, CARGO_MERGER, BaseKeys.CARGO_MERGER); + BasePages.CARGO.addItem(CARGO_MERGER); + } + + public static final ItemStack CARGO_VALVE = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.CARGO_VALVE) + .set(DataComponentTypes.ITEM_MODEL, Material.WHITE_CONCRETE.getKey()) + .build(); + static { + PylonItem.register(CargoValve.Item.class, CARGO_VALVE, BaseKeys.CARGO_VALVE); + BasePages.CARGO.addItem(CARGO_VALVE); + } + + public static final ItemStack CARGO_FILTER = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.CARGO_FILTER) + .set(DataComponentTypes.ITEM_MODEL, Material.PINK_TERRACOTTA.getKey()) + .build(); + static { + PylonItem.register(CargoValve.Item.class, CARGO_FILTER, BaseKeys.CARGO_FILTER); + BasePages.CARGO.addItem(CARGO_FILTER); + } + public static final ItemStack HUNGER_TALISMAN_SIMPLE = ItemStackBuilder.pylon(Material.CLAY_BALL, BaseKeys.HUNGER_TALISMAN_SIMPLE) .set(DataComponentTypes.ITEM_MODEL, Objects.requireNonNull(Material.GOLDEN_APPLE.getDefaultData(DataComponentTypes.ITEM_MODEL))) .build(); diff --git a/src/main/java/io/github/pylonmc/pylon/base/BaseKeys.java b/src/main/java/io/github/pylonmc/pylon/base/BaseKeys.java index b0c7f10fd..5ed323dfb 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/BaseKeys.java +++ b/src/main/java/io/github/pylonmc/pylon/base/BaseKeys.java @@ -322,6 +322,10 @@ public class BaseKeys { public static final NamespacedKey CARGO_DUCT = baseKey("cargo_duct"); public static final NamespacedKey CARGO_EXTRACTOR = baseKey("cargo_extractor"); public static final NamespacedKey CARGO_INSERTER = baseKey("cargo_inserter"); + public static final NamespacedKey CARGO_SPLITTER = baseKey("cargo_splitter"); + public static final NamespacedKey CARGO_MERGER = baseKey("cargo_merger"); + public static final NamespacedKey CARGO_VALVE = baseKey("cargo_valve"); + public static final NamespacedKey CARGO_FILTER = baseKey("cargo_filter"); public static final NamespacedKey HUNGER_TALISMAN_SIMPLE = baseKey("hunger_talisman_simple"); public static final NamespacedKey HUNGER_TALISMAN_ADVANCED = baseKey("hunger_talisman_advanced"); diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/components/FluidHatch.java b/src/main/java/io/github/pylonmc/pylon/base/content/components/FluidHatch.java index 019ef606f..c16a3bd3b 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/components/FluidHatch.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/components/FluidHatch.java @@ -161,6 +161,10 @@ public boolean setFluid(@NotNull PylonFluid fluid, double amount) { } public void setFluidType(PylonFluid fluid) { + if (this.fluid == fluid) { + return; + } + if (this.fluid != null) { deleteFluidBuffer(this.fluid); getFluidDisplay().setTransformationMatrix(new TransformBuilder() diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoExtractor.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoExtractor.java index e38ccc4d6..82bb0811c 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoExtractor.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoExtractor.java @@ -1,11 +1,14 @@ package io.github.pylonmc.pylon.base.content.machines.cargo; +import com.google.common.base.Preconditions; +import io.github.pylonmc.pylon.base.util.BaseUtils; import io.github.pylonmc.pylon.core.block.BlockStorage; import io.github.pylonmc.pylon.core.block.PylonBlock; import io.github.pylonmc.pylon.core.block.base.*; import io.github.pylonmc.pylon.core.block.context.BlockCreateContext; import io.github.pylonmc.pylon.core.config.adapter.ConfigAdapter; import io.github.pylonmc.pylon.core.content.cargo.CargoDuct; +import io.github.pylonmc.pylon.core.datatypes.PylonSerializers; import io.github.pylonmc.pylon.core.entity.display.ItemDisplayBuilder; import io.github.pylonmc.pylon.core.entity.display.transform.TransformBuilder; import io.github.pylonmc.pylon.core.event.PylonCargoConnectEvent; @@ -14,26 +17,54 @@ import io.github.pylonmc.pylon.core.item.PylonItem; import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder; import io.github.pylonmc.pylon.core.logistics.LogisticGroup; +import io.github.pylonmc.pylon.core.logistics.LogisticSlot; +import io.github.pylonmc.pylon.core.logistics.LogisticSlotType; +import io.github.pylonmc.pylon.core.util.MachineUpdateReason; import io.github.pylonmc.pylon.core.util.PylonUtils; +import io.github.pylonmc.pylon.core.util.gui.GuiItems; import io.github.pylonmc.pylon.core.util.gui.unit.UnitFormat; -import io.github.pylonmc.pylon.core.util.position.BlockPosition; -import io.github.pylonmc.pylon.core.util.position.ChunkPosition; +import net.kyori.adventure.text.Component; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataContainer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import xyz.xenondevs.invui.gui.Gui; +import xyz.xenondevs.invui.inventory.VirtualInventory; +import xyz.xenondevs.invui.item.ItemProvider; +import xyz.xenondevs.invui.item.impl.AbstractItem; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import static io.github.pylonmc.pylon.base.util.BaseUtils.baseKey; -public class CargoExtractor extends PylonBlock - implements PylonMultiblock, PylonDirectionalBlock, PylonCargoBlock, PylonEntityHolderBlock { + +public class CargoExtractor extends PylonBlock implements + PylonDirectionalBlock, + PylonCargoBlock, + PylonGuiBlock, + PylonTickingBlock { + + public static final NamespacedKey TARGET_LOGISTIC_GROUP_KEY = baseKey("target_logistic_group"); + public static final NamespacedKey ITEMS_TO_FILTER_KEY = baseKey("items_to_filter"); + public static final NamespacedKey IS_WHITELIST_KEY = baseKey("is_whitelist"); + public static final List groupMaterials = List.of( + Material.LIGHT_BLUE_CONCRETE, + Material.CYAN_CONCRETE, + Material.BLUE_CONCRETE, + Material.PURPLE_CONCRETE, + Material.MAGENTA_CONCRETE, + Material.PINK_CONCRETE + ); public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); @@ -44,6 +75,16 @@ public class CargoExtractor extends PylonBlock public final ItemStackBuilder ductStack = ItemStackBuilder.of(Material.GRAY_CONCRETE) .addCustomModelDataString(getKey() + ":duct"); + public final ItemStackBuilder filterGuiStack = ItemStackBuilder.gui(Material.PINK_STAINED_GLASS_PANE, getKey() + "filter") + .name(Component.translatable("pylon.pylonbase.gui.filter")); + + private final VirtualInventory outputInventory = new VirtualInventory(1); + private final VirtualInventory filterInventory = new VirtualInventory(5); + + public @Nullable String targetLogisticGroup = null; + public Set itemsToFilter = new HashSet<>(); + public boolean isWhitelist = false; + public static class Item extends PylonItem { public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); @@ -63,11 +104,81 @@ public Item(@NotNull ItemStack stack) { } } + public class WhitelistToggleItem extends AbstractItem { + + @Override + public ItemProvider getItemProvider() { + return ItemStackBuilder.gui(isWhitelist ? Material.WHITE_CONCRETE : Material.BLACK_CONCRETE, "blacklist-whitelist-toggle") + .name(Component.translatable("pylon.pylonbase.gui.whitelist-blacklist-toggle." + + (isWhitelist ? "whitelist.name" : "blacklist.name") + )) + .lore(Component.translatable("pylon.pylonbase.gui.whitelist-blacklist-toggle." + + (isWhitelist ? "whitelist.lore" : "blacklist.lore") + )); + } + + @Override + public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { + isWhitelist = !isWhitelist; + notifyWindows(); + } + } + + public class InventoryCycleItem extends AbstractItem { + + @Override + public ItemProvider getItemProvider() { + PylonLogisticBlock logisticBlock = getTargetLogisticBlock(); + if (targetLogisticGroup == null || logisticBlock == null) { + return ItemStackBuilder.of(Material.BARRIER) + .name("pylon.pylonbase.gui.no-target-logistic-group"); + } + + List availableGroups = getAvailableLogisticGroups(); + availableGroups.sort(String::compareTo); + + int index = availableGroups.indexOf(targetLogisticGroup); + Preconditions.checkState(index != -1); + Material material = groupMaterials.get(index % groupMaterials.size()); + + PylonBlock pylonBlock = BlockStorage.get(logisticBlock.getBlock()); + Preconditions.checkState(pylonBlock != null); + + ItemStackBuilder builder = ItemStackBuilder.gui(material, "logistic-group:" + index) + .name(Component.translatable("pylon.pylonbase.gui.logistic-group-cycle-item.name") + .arguments(PylonArgument.of( + "inventory", + Component.translatable("pylon." + pylonBlock.getKey().getNamespace() + ".inventory." + targetLogisticGroup) + )) + ); + if (availableGroups.size() > 1) { + builder.lore(Component.translatable("pylon.pylonbase.gui.logistic-group-cycle-item.lore")); + } + return builder; + } + + @Override + public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { + PylonLogisticBlock logisticBlock = getTargetLogisticBlock(); + if (targetLogisticGroup == null || logisticBlock == null) { + return; + } + + List availableGroups = getAvailableLogisticGroups(); + availableGroups.sort(String::compareTo); + + int index = availableGroups.indexOf(targetLogisticGroup); + targetLogisticGroup = availableGroups.get((index + 1) % availableGroups.size()); + notifyWindows(); + } + } + @SuppressWarnings("unused") public CargoExtractor(@NotNull Block block, @NotNull BlockCreateContext context) { super(block, context); setFacing(context.getFacing()); + setTickInterval(transferRate * 20); addCargoLogisticGroup(getFacing(), "output"); for (BlockFace face : PylonUtils.perpendicularImmediateFaces(getFacing())) { @@ -109,30 +220,30 @@ public CargoExtractor(@NotNull Block block, @NotNull BlockCreateContext context) @SuppressWarnings("unused") public CargoExtractor(@NotNull Block block, @NotNull PersistentDataContainer pdc) { super(block, pdc); + targetLogisticGroup = pdc.get(TARGET_LOGISTIC_GROUP_KEY, PylonSerializers.STRING); + itemsToFilter = pdc.get(ITEMS_TO_FILTER_KEY, PylonSerializers.SET.setTypeFrom(PylonSerializers.ITEM_STACK)); + isWhitelist = pdc.get(IS_WHITELIST_KEY, PylonSerializers.BOOLEAN); } @Override - public @NotNull Set<@NotNull ChunkPosition> getChunksOccupied() { - return Set.of(new ChunkPosition(getBlock()), new ChunkPosition(getTarget())); - } - - @Override - public boolean checkFormed() { - return getTargetLogisticBlock() != null; + public void write(@NotNull PersistentDataContainer pdc) { + PylonUtils.setNullable(pdc, TARGET_LOGISTIC_GROUP_KEY, PylonSerializers.STRING, targetLogisticGroup); + pdc.set(ITEMS_TO_FILTER_KEY, PylonSerializers.SET.setTypeFrom(PylonSerializers.ITEM_STACK), itemsToFilter); + pdc.set(IS_WHITELIST_KEY, PylonSerializers.BOOLEAN, isWhitelist); } @Override - public boolean isPartOfMultiblock(@NotNull Block otherBlock) { - return new BlockPosition(otherBlock) - .equals(new BlockPosition(getTarget())); - } + public void postInitialise() { + createLogisticGroup("output", LogisticSlotType.OUTPUT, outputInventory); - @Override - public @NotNull Map getLogisticGroups() { - PylonLogisticBlock logisticBlock = getTargetLogisticBlock(); - return logisticBlock != null - ? logisticBlock.getLogisticGroups() - : Collections.emptyMap(); + filterInventory.setPostUpdateHandler(event -> { + itemsToFilter.clear(); + for (ItemStack stack : filterInventory.getItems()) { + if (stack != null) { + itemsToFilter.add(stack.asOne()); + } + } + }); } @Override @@ -161,12 +272,89 @@ public void onDuctDisconnected(@NotNull PylonCargoDisconnectEvent event) { } } - public @NotNull Block getTarget() { - return getBlock().getRelative(getFacing()); + public @Nullable PylonLogisticBlock getTargetLogisticBlock() { + Block block = getBlock().getRelative(getFacing().getOppositeFace()); + PylonLogisticBlock pylonBlock = BlockStorage.getAs(PylonLogisticBlock.class, block); + return pylonBlock instanceof PylonCargoBlock ? null : pylonBlock; } - public @Nullable PylonLogisticBlock getTargetLogisticBlock() { - PylonLogisticBlock block = BlockStorage.getAs(PylonLogisticBlock.class, getTarget()); - return block instanceof PylonCargoBlock ? null : block; + @Override + public @NotNull Gui createGui() { + return Gui.normal() + .setStructure( + "# # # # O # # # #", + "# b # # o # # i #", + "# # # # O # # # #", + "# # # # # # # # #", + "# F f f f f f F #", + "# # # # # # # # #" + ) + .addIngredient('#', GuiItems.background()) + .addIngredient('o', outputInventory) + .addIngredient('O', GuiItems.output()) + .addIngredient('f', filterInventory) + .addIngredient('F', filterGuiStack) + .addIngredient('b', new WhitelistToggleItem()) + .addIngredient('i', new InventoryCycleItem()) + .build(); + } + + @Override + public void tick() { + PylonLogisticBlock target = getTargetLogisticBlock(); + if (target == null) { + return; + } + + if (targetLogisticGroup == null || target.getLogisticGroup(targetLogisticGroup) == null) { + List availableLogisticGroups = getAvailableLogisticGroups(); + if (availableLogisticGroups.isEmpty()) { + return; + } + targetLogisticGroup = availableLogisticGroups.getFirst(); + } + + LogisticGroup group = target.getLogisticGroup(targetLogisticGroup); + Preconditions.checkState(group != null); + ItemStack output = outputInventory.getItem(0); + if (output != null && output.getAmount() == output.getMaxStackSize()) { + return; + } + + for (LogisticSlot slot : group.getSlots()) { + ItemStack slotStack = slot.getItemStack(); + if (slotStack == null || slot.getAmount() == 0 || (output != null && !output.isSimilar(slotStack))) { + continue; + } + + if ((isWhitelist && !itemsToFilter.contains(slotStack.asOne())) || (!isWhitelist && itemsToFilter.contains(slotStack.asOne()))) { + continue; + } + + if (output == null) { + outputInventory.setItem(new MachineUpdateReason(), 0, slotStack.asOne()); + } else { + outputInventory.setItem(new MachineUpdateReason(), 0, output.add()); + } + slot.set(slotStack, slot.getAmount() - 1); + return; + } + } + + private @NotNull List getAvailableLogisticGroups() { + PylonLogisticBlock target = getTargetLogisticBlock(); + if (target == null) { + return List.of(); + } + + List availableLogisticGroups = new ArrayList<>(); + for (Map.Entry entry : target.getLogisticGroups().entrySet()) { + if (entry.getValue().getSlotType() == LogisticSlotType.BOTH + || entry.getValue().getSlotType() == LogisticSlotType.OUTPUT + ) { + availableLogisticGroups.add(entry.getKey()); + } + } + return availableLogisticGroups; } } diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoFilter.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoFilter.java new file mode 100644 index 000000000..c1dc911ff --- /dev/null +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoFilter.java @@ -0,0 +1,253 @@ +package io.github.pylonmc.pylon.base.content.machines.cargo; + +import io.github.pylonmc.pylon.core.block.PylonBlock; +import io.github.pylonmc.pylon.core.block.base.PylonCargoBlock; +import io.github.pylonmc.pylon.core.block.base.PylonDirectionalBlock; +import io.github.pylonmc.pylon.core.block.base.PylonEntityHolderBlock; +import io.github.pylonmc.pylon.core.block.base.PylonGuiBlock; +import io.github.pylonmc.pylon.core.block.context.BlockCreateContext; +import io.github.pylonmc.pylon.core.config.adapter.ConfigAdapter; +import io.github.pylonmc.pylon.core.entity.display.ItemDisplayBuilder; +import io.github.pylonmc.pylon.core.entity.display.transform.TransformBuilder; +import io.github.pylonmc.pylon.core.i18n.PylonArgument; +import io.github.pylonmc.pylon.core.item.PylonItem; +import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder; +import io.github.pylonmc.pylon.core.logistics.LogisticSlotType; +import io.github.pylonmc.pylon.core.logistics.VirtualInventoryLogisticSlot; +import io.github.pylonmc.pylon.core.util.MachineUpdateReason; +import io.github.pylonmc.pylon.core.util.PylonUtils; +import io.github.pylonmc.pylon.core.util.gui.GuiItems; +import io.github.pylonmc.pylon.core.util.gui.unit.UnitFormat; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; +import xyz.xenondevs.invui.gui.Gui; +import xyz.xenondevs.invui.inventory.VirtualInventory; + +import java.util.List; + + +public class CargoFilter extends PylonBlock + implements PylonDirectionalBlock, PylonGuiBlock, PylonCargoBlock, PylonEntityHolderBlock { + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + + private final VirtualInventory inputInventory = new VirtualInventory(1); + private final VirtualInventory filteredInventory = new VirtualInventory(1); + private final VirtualInventory unfilteredInventory = new VirtualInventory(1); + private final VirtualInventory filterInventory = new VirtualInventory(5); + + public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.LIGHT_GRAY_CONCRETE) + .addCustomModelDataString(getKey() + ":main"); + public final ItemStackBuilder verticalStack = ItemStackBuilder.of(Material.PINK_TERRACOTTA) + .addCustomModelDataString(getKey() + ":vertical"); + public final ItemStackBuilder inputStack = ItemStackBuilder.of(Material.LIME_TERRACOTTA) + .addCustomModelDataString(getKey() + ":input"); + public final ItemStackBuilder filteredStack = ItemStackBuilder.of(Material.ORANGE_TERRACOTTA) + .addCustomModelDataString(getKey() + ":filtered"); + public final ItemStackBuilder unfilteredStack = ItemStackBuilder.of(Material.RED_TERRACOTTA) + .addCustomModelDataString(getKey() + ":unfiltered"); + + public final ItemStackBuilder filteredGuiStack = ItemStackBuilder.gui(Material.ORANGE_STAINED_GLASS_PANE, getKey() + "filtered") + .name(Component.translatable("pylon.pylonbase.gui.filtered")); + public final ItemStackBuilder unfilteredGuiStack = ItemStackBuilder.gui(Material.RED_STAINED_GLASS_PANE, getKey() + "unfiltered") + .name(Component.translatable("pylon.pylonbase.gui.unfiltered")); + public final ItemStackBuilder filterGuiStack = ItemStackBuilder.gui(Material.PINK_STAINED_GLASS_PANE, getKey() + "filter") + .name(Component.translatable("pylon.pylonbase.gui.filter")); + + public static class Item extends PylonItem { + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + + public Item(@NotNull ItemStack stack) { + super(stack); + } + + @Override + public @NotNull List getPlaceholders() { + return List.of( + PylonArgument.of( + "transfer-rate", + UnitFormat.ITEMS_PER_SECOND.format(PylonCargoBlock.cargoItemsTransferredPerSecond(transferRate)) + ) + ); + } + } + + @SuppressWarnings("unused") + public CargoFilter(@NotNull Block block, @NotNull BlockCreateContext context) { + super(block, context); + + setFacing(context.getFacing()); + + addCargoLogisticGroup(getFacing(), "input"); + addCargoLogisticGroup(PylonUtils.rotateFaceToReference(getFacing(), BlockFace.EAST), "filtered"); + addCargoLogisticGroup(PylonUtils.rotateFaceToReference(getFacing(), BlockFace.WEST), "unfiltered"); + setCargoTransferRate(transferRate); + + addEntity("main", new ItemDisplayBuilder() + .itemStack(mainStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .scale(0.65) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("vertical1", new ItemDisplayBuilder() + .itemStack(verticalStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(-0.15, 0, 0) + .scale(0.15, 0.7, 0.5) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("vertical2", new ItemDisplayBuilder() + .itemStack(verticalStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0.15, 0, 0) + .scale(0.15, 0.7, 0.5) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("input", new ItemDisplayBuilder() + .itemStack(inputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, 0.15) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("output_filtered", new ItemDisplayBuilder() + .itemStack(filteredStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(-0.15, 0, 0) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("output_unfiltered", new ItemDisplayBuilder() + .itemStack(unfilteredStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0.15, 0, 0) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + } + + @SuppressWarnings("unused") + public CargoFilter(@NotNull Block block, @NotNull PersistentDataContainer pdc) { + super(block, pdc); + } + + @Override + public @NotNull Gui createGui() { + return Gui.normal() + .setStructure( + "# L # # I # # R #", + "# l # # i # # r #", + "# L # # I # # R #", + "# # # # # # # # #", + "# F f f f f f F #", + "# # # # # # # # #" + ) + .addIngredient('#', GuiItems.background()) + .addIngredient('L', filteredGuiStack) + .addIngredient('l', filteredInventory) + .addIngredient('I', GuiItems.input()) + .addIngredient('i', inputInventory) + .addIngredient('R', unfilteredGuiStack) + .addIngredient('r', unfilteredInventory) + .addIngredient('f', filterInventory) + .addIngredient('F', filterGuiStack) + .build(); + } + + @Override + public void postInitialise() { + createLogisticGroup("input", LogisticSlotType.INPUT, new VirtualInventoryLogisticSlot(inputInventory, 0)); + createLogisticGroup("filtered", LogisticSlotType.OUTPUT, new VirtualInventoryLogisticSlot(filteredInventory, 0)); + createLogisticGroup("unfiltered", LogisticSlotType.OUTPUT, new VirtualInventoryLogisticSlot(unfilteredInventory, 0)); + inputInventory.setPostUpdateHandler(event -> { + if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { + doSplit(); + } + }); + filteredInventory.setPostUpdateHandler(event -> { + if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { + doSplit(); + } + }); + unfilteredInventory.setPostUpdateHandler(event -> { + if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { + doSplit(); + } + }); + filterInventory.setPostUpdateHandler(event -> { + if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { + doSplit(); + } + }); + } + + private void doSplit() { + ItemStack input = inputInventory.getItem(0); + if (input == null) { + return; + } + + boolean matchesFilter = false; + for (ItemStack filterStack : filterInventory.getItems()) { + if (input.isSimilar(filterStack)) { + matchesFilter = true; + break; + } + } + + if (matchesFilter) { + ItemStack filteredStack = filteredInventory.getItem(0); + if (filteredStack == null + || (filteredStack.isSimilar(input) && filteredStack.getAmount() < filteredStack.getMaxStackSize()) + ) { + if (filteredStack == null) { + filteredInventory.setItem(new MachineUpdateReason(), 0, input); + inputInventory.setItem(new MachineUpdateReason(), 0, null); + } else { + int newAmount = Math.min(filteredStack.getMaxStackSize(), filteredStack.getAmount() + input.getAmount()); + int toSubtract = newAmount - filteredStack.getAmount(); + filteredInventory.setItem(new MachineUpdateReason(), 0, filteredStack.asQuantity(newAmount)); + inputInventory.setItem(new MachineUpdateReason(), 0, input.subtract(toSubtract)); + } + } + } else { + ItemStack unfilteredStack = unfilteredInventory.getItem(0); + if (unfilteredStack == null + || (unfilteredStack.isSimilar(input) && unfilteredStack.getAmount() < unfilteredStack.getMaxStackSize()) + ) { + if (unfilteredStack == null) { + unfilteredInventory.setItem(new MachineUpdateReason(), 0, input); + inputInventory.setItem(new MachineUpdateReason(), 0, null); + } else { + int newAmount = Math.min(unfilteredStack.getMaxStackSize(), unfilteredStack.getAmount() + input.getAmount()); + int toSubtract = newAmount - unfilteredStack.getAmount(); + unfilteredInventory.setItem(new MachineUpdateReason(), 0, unfilteredStack.asQuantity(newAmount)); + inputInventory.setItem(new MachineUpdateReason(), 0, input.subtract(toSubtract)); + } + } + } + } +} diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoInserter.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoInserter.java index 050ff46b7..589506019 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoInserter.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoInserter.java @@ -1,11 +1,14 @@ package io.github.pylonmc.pylon.base.content.machines.cargo; +import com.google.common.base.Preconditions; +import io.github.pylonmc.pylon.base.util.BaseUtils; import io.github.pylonmc.pylon.core.block.BlockStorage; import io.github.pylonmc.pylon.core.block.PylonBlock; import io.github.pylonmc.pylon.core.block.base.*; import io.github.pylonmc.pylon.core.block.context.BlockCreateContext; import io.github.pylonmc.pylon.core.config.adapter.ConfigAdapter; import io.github.pylonmc.pylon.core.content.cargo.CargoDuct; +import io.github.pylonmc.pylon.core.datatypes.PylonSerializers; import io.github.pylonmc.pylon.core.entity.display.ItemDisplayBuilder; import io.github.pylonmc.pylon.core.entity.display.transform.TransformBuilder; import io.github.pylonmc.pylon.core.event.PylonCargoConnectEvent; @@ -14,22 +17,38 @@ import io.github.pylonmc.pylon.core.item.PylonItem; import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder; import io.github.pylonmc.pylon.core.logistics.LogisticGroup; +import io.github.pylonmc.pylon.core.logistics.LogisticSlotType; import io.github.pylonmc.pylon.core.util.PylonUtils; +import io.github.pylonmc.pylon.core.util.gui.GuiItems; import io.github.pylonmc.pylon.core.util.gui.unit.UnitFormat; import io.github.pylonmc.pylon.core.util.position.ChunkPosition; +import net.kyori.adventure.text.Component; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataContainer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import xyz.xenondevs.invui.gui.Gui; +import xyz.xenondevs.invui.item.ItemProvider; +import xyz.xenondevs.invui.item.impl.AbstractItem; import java.util.*; -public class CargoInserter extends PylonBlock - implements PylonMultiblock, PylonDirectionalBlock, PylonCargoBlock, PylonEntityHolderBlock { +public class CargoInserter extends PylonBlock implements + PylonDirectionalBlock, + PylonCargoBlock, + PylonEntityHolderBlock, + PylonGuiBlock, + PylonMultiblock { + + public static final NamespacedKey TARGET_LOGISTIC_GROUP_KEY = BaseUtils.baseKey("target_logistic_group"); public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); @@ -40,6 +59,9 @@ public class CargoInserter extends PylonBlock public final ItemStackBuilder ductStack = ItemStackBuilder.of(Material.GRAY_CONCRETE) .addCustomModelDataString(getKey() + ":duct"); + public @Nullable String targetLogisticGroup = null; + + public static class Item extends PylonItem { public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); @@ -59,6 +81,63 @@ public Item(@NotNull ItemStack stack) { } } + public class InventoryCycleItem extends AbstractItem { + + @Override + public ItemProvider getItemProvider() { + PylonLogisticBlock logisticBlock = getTargetLogisticBlock(); + if (targetLogisticGroup == null || logisticBlock == null) { + return ItemStackBuilder.of(Material.BARRIER) + .name("pylon.pylonbase.gui.no-target-logistic-group"); + } + + List availableGroups = getAvailableLogisticGroups(); + availableGroups.sort(String::compareTo); + + int index = availableGroups.indexOf(targetLogisticGroup); + Preconditions.checkState(index != -1); + Material material = CargoExtractor.groupMaterials.get(index % CargoExtractor.groupMaterials.size()); + + PylonBlock pylonBlock = BlockStorage.get(logisticBlock.getBlock()); + Preconditions.checkState(pylonBlock != null); + + ItemStackBuilder builder = ItemStackBuilder.gui(material, "logistic-group:" + index) + .name(Component.translatable("pylon.pylonbase.gui.logistic-group-cycle-item.name") + .arguments(PylonArgument.of( + "inventory", + Component.translatable("pylon." + pylonBlock.getKey().getNamespace() + ".inventory." + targetLogisticGroup) + )) + ); + if (availableGroups.size() > 1) { + builder.lore(Component.translatable("pylon.pylonbase.gui.logistic-group-cycle-item.lore")); + } + return builder; + } + + @Override + public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { + PylonLogisticBlock logisticBlock = getTargetLogisticBlock(); + if (targetLogisticGroup == null || logisticBlock == null) { + return; + } + + List availableGroups = getAvailableLogisticGroups(); + availableGroups.sort(String::compareTo); + + int index = availableGroups.indexOf(targetLogisticGroup); + targetLogisticGroup = availableGroups.get((index + 1) % availableGroups.size()); + + for (BlockFace face : getCargoLogisticGroups().keySet()) { + removeCargoLogisticGroup(face); + if (targetLogisticGroup != null) { + addCargoLogisticGroup(face, targetLogisticGroup); + } + } + + notifyWindows(); + } + } + @SuppressWarnings("unused") public CargoInserter(@NotNull Block block, @NotNull BlockCreateContext context) { super(block, context); @@ -100,29 +179,19 @@ public CargoInserter(@NotNull Block block, @NotNull BlockCreateContext context) ) .build(block.getLocation().toCenterLocation()) ); + + refreshTargetLogisticGroup(); } @SuppressWarnings("unused") public CargoInserter(@NotNull Block block, @NotNull PersistentDataContainer pdc) { super(block, pdc); + targetLogisticGroup = pdc.get(TARGET_LOGISTIC_GROUP_KEY, PylonSerializers.STRING); } @Override - public @NotNull Set getChunksOccupied() { - Set chunks = new HashSet<>(); - chunks.add(new ChunkPosition(getBlock())); - chunks.add(new ChunkPosition(getTarget())); - return chunks; - } - - @Override - public boolean checkFormed() { - return getTargetLogisticBlock() != null; - } - - @Override - public boolean isPartOfMultiblock(@NotNull Block otherBlock) { - return otherBlock.equals(getTarget()); + public void write(@NotNull PersistentDataContainer pdc) { + PylonUtils.setNullable(pdc, TARGET_LOGISTIC_GROUP_KEY, PylonSerializers.STRING, targetLogisticGroup); } @Override @@ -142,7 +211,7 @@ public void onDuctDisconnected(@NotNull PylonCargoDisconnectEvent event) { List faces = PylonUtils.perpendicularImmediateFaces(getFacing()); faces.add(getFacing().getOppositeFace()); for (BlockFace face : faces) { - addCargoLogisticGroup(face, "output"); + addCargoLogisticGroup(face, "input"); } for (BlockFace face : faces) { if (BlockStorage.get(getBlock().getRelative(face)) instanceof CargoDuct duct) { @@ -159,12 +228,82 @@ public void onDuctDisconnected(@NotNull PylonCargoDisconnectEvent event) { : Collections.emptyMap(); } - public @NotNull Block getTarget() { - return getBlock().getRelative(getFacing()); + @Override + public @NotNull Set<@NotNull ChunkPosition> getChunksOccupied() { + return Set.of(new ChunkPosition(getBlock().getRelative(getFacing()).getChunk())); + } + + @Override + public boolean checkFormed() { + return true; + } + + @Override + public boolean isPartOfMultiblock(@NotNull Block otherBlock) { + return getBlock().getRelative(getFacing()).equals(otherBlock); + } + + @Override + public void onMultiblockFormed() { + refreshTargetLogisticGroup(); + } + + @Override + public void onMultiblockUnformed(boolean partUnloaded) { + targetLogisticGroup = null; + } + + @Override + public @NotNull Gui createGui() { + return Gui.normal() + .setStructure("# # # # i # # # #") + .addIngredient('#', GuiItems.background()) + .addIngredient('i', new InventoryCycleItem()) + .build(); + } + + public void refreshTargetLogisticGroup() { + PylonLogisticBlock target = getTargetLogisticBlock(); + if (target == null) { + return; + } + + if (targetLogisticGroup == null || target.getLogisticGroup(targetLogisticGroup) == null) { + List availableLogisticGroups = getAvailableLogisticGroups(); + if (availableLogisticGroups.isEmpty()) { + return; + } + targetLogisticGroup = availableLogisticGroups.getFirst(); + } + + for (BlockFace face : getCargoLogisticGroups().keySet()) { + removeCargoLogisticGroup(face); + if (targetLogisticGroup != null) { + addCargoLogisticGroup(face, targetLogisticGroup); + } + } } public @Nullable PylonLogisticBlock getTargetLogisticBlock() { - PylonLogisticBlock block = BlockStorage.getAs(PylonLogisticBlock.class, getTarget()); - return block instanceof PylonCargoBlock ? null : block; + Block block = getBlock().getRelative(getFacing().getOppositeFace()); + PylonLogisticBlock pylonBlock = BlockStorage.getAs(PylonLogisticBlock.class, block); + return pylonBlock instanceof PylonCargoBlock ? null : pylonBlock; + } + + private @NotNull List getAvailableLogisticGroups() { + PylonLogisticBlock target = getTargetLogisticBlock(); + if (target == null) { + return List.of(); + } + + List availableLogisticGroups = new ArrayList<>(); + for (Map.Entry entry : target.getLogisticGroups().entrySet()) { + if (entry.getValue().getSlotType() == LogisticSlotType.BOTH + || entry.getValue().getSlotType() == LogisticSlotType.INPUT + ) { + availableLogisticGroups.add(entry.getKey()); + } + } + return availableLogisticGroups; } } diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoMerger.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoMerger.java new file mode 100644 index 000000000..5a3c1d4bd --- /dev/null +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoMerger.java @@ -0,0 +1,145 @@ +package io.github.pylonmc.pylon.base.content.machines.cargo; + +import io.github.pylonmc.pylon.core.block.PylonBlock; +import io.github.pylonmc.pylon.core.block.base.PylonCargoBlock; +import io.github.pylonmc.pylon.core.block.base.PylonDirectionalBlock; +import io.github.pylonmc.pylon.core.block.base.PylonEntityHolderBlock; +import io.github.pylonmc.pylon.core.block.base.PylonGuiBlock; +import io.github.pylonmc.pylon.core.block.context.BlockCreateContext; +import io.github.pylonmc.pylon.core.config.adapter.ConfigAdapter; +import io.github.pylonmc.pylon.core.entity.display.ItemDisplayBuilder; +import io.github.pylonmc.pylon.core.entity.display.transform.TransformBuilder; +import io.github.pylonmc.pylon.core.i18n.PylonArgument; +import io.github.pylonmc.pylon.core.item.PylonItem; +import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder; +import io.github.pylonmc.pylon.core.logistics.LogisticSlotType; +import io.github.pylonmc.pylon.core.logistics.VirtualInventoryLogisticSlot; +import io.github.pylonmc.pylon.core.util.PylonUtils; +import io.github.pylonmc.pylon.core.util.gui.GuiItems; +import io.github.pylonmc.pylon.core.util.gui.unit.UnitFormat; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; +import xyz.xenondevs.invui.gui.Gui; +import xyz.xenondevs.invui.inventory.VirtualInventory; + +import java.util.List; + + +public class CargoMerger extends PylonBlock + implements PylonDirectionalBlock, PylonGuiBlock, PylonCargoBlock, PylonEntityHolderBlock { + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + + private final VirtualInventory inventory = new VirtualInventory(1); + + public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.LIGHT_GRAY_CONCRETE) + .addCustomModelDataString(getKey() + ":main"); + public final ItemStackBuilder verticalStack = ItemStackBuilder.of(Material.STRIPPED_WARPED_HYPHAE) + .addCustomModelDataString(getKey() + ":vertical"); + public final ItemStackBuilder inputStack = ItemStackBuilder.of(Material.LIME_TERRACOTTA) + .addCustomModelDataString(getKey() + ":input"); + public final ItemStackBuilder outputStack = ItemStackBuilder.of(Material.RED_TERRACOTTA) + .addCustomModelDataString(getKey() + ":output"); + + public static class Item extends PylonItem { + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + + public Item(@NotNull ItemStack stack) { + super(stack); + } + + @Override + public @NotNull List getPlaceholders() { + return List.of( + PylonArgument.of( + "transfer-rate", + UnitFormat.ITEMS_PER_SECOND.format(PylonCargoBlock.cargoItemsTransferredPerSecond(transferRate)) + ) + ); + } + } + + @SuppressWarnings("unused") + public CargoMerger(@NotNull Block block, @NotNull BlockCreateContext context) { + super(block, context); + + setFacing(context.getFacing()); + + addCargoLogisticGroup(getFacing(), "output"); + addCargoLogisticGroup(PylonUtils.rotateFaceToReference(getFacing(), BlockFace.EAST), "input"); + addCargoLogisticGroup(PylonUtils.rotateFaceToReference(getFacing(), BlockFace.WEST), "input"); + setCargoTransferRate(transferRate); + + addEntity("main", new ItemDisplayBuilder() + .itemStack(mainStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .scale(0.65) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("vertical", new ItemDisplayBuilder() + .itemStack(verticalStack) + .transformation(new TransformBuilder() + .scale(0.5, 0.7, 0.5) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("output", new ItemDisplayBuilder() + .itemStack(outputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, 0.15) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("input_left", new ItemDisplayBuilder() + .itemStack(inputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(-0.15, 0, 0) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("input_right", new ItemDisplayBuilder() + .itemStack(inputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0.15, 0, 0) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + } + + @SuppressWarnings("unused") + public CargoMerger(@NotNull Block block, @NotNull PersistentDataContainer pdc) { + super(block, pdc); + } + + @Override + public @NotNull Gui createGui() { + return Gui.normal() + .setStructure("# # # # x # # # #") + .addIngredient('#', GuiItems.background()) + .addIngredient('x', inventory) + .build(); + } + + @Override + public void postInitialise() { + createLogisticGroup("input", LogisticSlotType.INPUT, new VirtualInventoryLogisticSlot(inventory, 0)); + createLogisticGroup("output", LogisticSlotType.OUTPUT, new VirtualInventoryLogisticSlot(inventory, 0)); + } +} diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoSplitter.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoSplitter.java new file mode 100644 index 000000000..2ec57a2a3 --- /dev/null +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoSplitter.java @@ -0,0 +1,322 @@ +package io.github.pylonmc.pylon.base.content.machines.cargo; + +import io.github.pylonmc.pylon.core.block.PylonBlock; +import io.github.pylonmc.pylon.core.block.base.PylonCargoBlock; +import io.github.pylonmc.pylon.core.block.base.PylonDirectionalBlock; +import io.github.pylonmc.pylon.core.block.base.PylonEntityHolderBlock; +import io.github.pylonmc.pylon.core.block.base.PylonGuiBlock; +import io.github.pylonmc.pylon.core.block.context.BlockCreateContext; +import io.github.pylonmc.pylon.core.config.adapter.ConfigAdapter; +import io.github.pylonmc.pylon.core.datatypes.PylonSerializers; +import io.github.pylonmc.pylon.core.entity.display.ItemDisplayBuilder; +import io.github.pylonmc.pylon.core.entity.display.transform.TransformBuilder; +import io.github.pylonmc.pylon.core.i18n.PylonArgument; +import io.github.pylonmc.pylon.core.item.PylonItem; +import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder; +import io.github.pylonmc.pylon.core.logistics.LogisticSlotType; +import io.github.pylonmc.pylon.core.logistics.VirtualInventoryLogisticSlot; +import io.github.pylonmc.pylon.core.util.MachineUpdateReason; +import io.github.pylonmc.pylon.core.util.PylonUtils; +import io.github.pylonmc.pylon.core.util.gui.GuiItems; +import io.github.pylonmc.pylon.core.util.gui.unit.UnitFormat; +import io.github.pylonmc.pylon.core.waila.WailaDisplay; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.xenondevs.invui.gui.Gui; +import xyz.xenondevs.invui.inventory.VirtualInventory; +import xyz.xenondevs.invui.item.ItemProvider; +import xyz.xenondevs.invui.item.impl.SuppliedItem; +import xyz.xenondevs.invui.item.impl.controlitem.ControlItem; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static io.github.pylonmc.pylon.base.util.BaseUtils.baseKey; + + + +public class CargoSplitter extends PylonBlock + implements PylonDirectionalBlock, PylonGuiBlock, PylonCargoBlock, PylonEntityHolderBlock { + + public static final NamespacedKey RATIO_LEFT_KEY = baseKey("ratio_left"); + public static final NamespacedKey RATIO_RIGHT_KEY = baseKey("ratio_right"); + public static final NamespacedKey IS_LEFT_KEY = baseKey("is_left"); + public static final NamespacedKey ITEMS_REMAINING_KEY = baseKey("items_remaining"); + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + + public int ratioLeft = 1; + public int ratioRight = 1; + public boolean isLeft = true; + public int itemsRemaining = 1; + + private final VirtualInventory inputInventory = new VirtualInventory(1); + private final VirtualInventory leftInventory = new VirtualInventory(1); + private final VirtualInventory rightInventory = new VirtualInventory(1); + + public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.LIGHT_GRAY_CONCRETE) + .addCustomModelDataString(getKey() + ":main"); + public final ItemStackBuilder verticalStack = ItemStackBuilder.of(Material.STRIPPED_CRIMSON_HYPHAE) + .addCustomModelDataString(getKey() + ":vertical"); + public final ItemStackBuilder inputStack = ItemStackBuilder.of(Material.LIME_TERRACOTTA) + .addCustomModelDataString(getKey() + ":input"); + public final ItemStackBuilder outputLeftStack = ItemStackBuilder.of(Material.YELLOW_CONCRETE) + .addCustomModelDataString(getKey() + ":output_left"); + public final ItemStackBuilder outputRightStack = ItemStackBuilder.of(Material.LIGHT_BLUE_CONCRETE) + .addCustomModelDataString(getKey() + ":output_right"); + + public final ItemStackBuilder leftStack = ItemStackBuilder.gui(Material.YELLOW_STAINED_GLASS_PANE, getKey() + "left") + .name(Component.translatable("pylon.pylonbase.gui.left")); + public final ItemStackBuilder rightStack = ItemStackBuilder.gui(Material.LIGHT_BLUE_STAINED_GLASS_PANE, getKey() + "right") + .name(Component.translatable("pylon.pylonbase.gui.right")); + public final ItemStackBuilder ratioStack = ItemStackBuilder.gui(Material.WHITE_CONCRETE, getKey() + "ratio"); + public final ItemStackBuilder leftButtonStack = ItemStackBuilder.gui(Material.YELLOW_STAINED_GLASS_PANE, getKey() + "left_button") + .name(Component.translatable("pylon.pylonbase.gui.left_button.name")) + .lore(Component.translatable("pylon.pylonbase.gui.left_button.lore")); + public final ItemStackBuilder rightButtonStack = ItemStackBuilder.gui(Material.LIGHT_BLUE_STAINED_GLASS_PANE, getKey() + "right_button") + .name(Component.translatable("pylon.pylonbase.gui.right_button.name")) + .lore(Component.translatable("pylon.pylonbase.gui.right_button.lore")); + + public static class RatioButton extends ControlItem { + + private final ItemStackBuilder stack; + private final Supplier getRatio; + private final Consumer setRatio; + + public RatioButton(ItemStackBuilder stack, Supplier getRatio, Consumer setRatio) { + this.stack = stack; + this.getRatio = getRatio; + this.setRatio = setRatio; + } + + @Override + public void handleClick( + @NotNull ClickType clickType, + @NotNull Player player, + @NotNull InventoryClickEvent event + ) { + if (clickType.isLeftClick()) { + setRatio.accept(getRatio.get() + 1); + } else { + setRatio.accept(Math.max(1, getRatio.get() - 1)); + } + getGui().getItem(4, 4).notifyWindows(); + } + + @Override + public ItemProvider getItemProvider(Gui gui) { + return stack; + } + } + + public static class Item extends PylonItem { + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + + public Item(@NotNull ItemStack stack) { + super(stack); + } + + @Override + public @NotNull List getPlaceholders() { + return List.of( + PylonArgument.of( + "transfer-rate", + UnitFormat.ITEMS_PER_SECOND.format(PylonCargoBlock.cargoItemsTransferredPerSecond(transferRate)) + ) + ); + } + } + + @SuppressWarnings("unused") + public CargoSplitter(@NotNull Block block, @NotNull BlockCreateContext context) { + super(block, context); + + setFacing(context.getFacing()); + + addCargoLogisticGroup(getFacing(), "input"); + addCargoLogisticGroup(PylonUtils.rotateFaceToReference(getFacing(), BlockFace.EAST), "left"); + addCargoLogisticGroup(PylonUtils.rotateFaceToReference(getFacing(), BlockFace.WEST), "right"); + setCargoTransferRate(transferRate); + + addEntity("main", new ItemDisplayBuilder() + .itemStack(mainStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .scale(0.65) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("vertical", new ItemDisplayBuilder() + .itemStack(verticalStack) + .transformation(new TransformBuilder() + .scale(0.5, 0.7, 0.5) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("input", new ItemDisplayBuilder() + .itemStack(inputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, 0.15) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("output_left", new ItemDisplayBuilder() + .itemStack(outputLeftStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(-0.15, 0, 0) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("output_right", new ItemDisplayBuilder() + .itemStack(outputRightStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0.15, 0, 0) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + } + + @SuppressWarnings("unused") + public CargoSplitter(@NotNull Block block, @NotNull PersistentDataContainer pdc) { + super(block, pdc); + ratioLeft = pdc.get(RATIO_LEFT_KEY, PylonSerializers.INTEGER); + ratioRight = pdc.get(RATIO_RIGHT_KEY, PylonSerializers.INTEGER); + isLeft = pdc.get(IS_LEFT_KEY, PylonSerializers.BOOLEAN); + itemsRemaining = pdc.get(ITEMS_REMAINING_KEY, PylonSerializers.INTEGER); + } + + @Override + public void write(@NotNull PersistentDataContainer pdc) { + pdc.set(RATIO_LEFT_KEY, PylonSerializers.INTEGER, ratioLeft); + pdc.set(RATIO_RIGHT_KEY, PylonSerializers.INTEGER, ratioRight); + pdc.set(IS_LEFT_KEY, PylonSerializers.BOOLEAN, isLeft); + pdc.set(ITEMS_REMAINING_KEY, PylonSerializers.INTEGER, itemsRemaining); + } + + @Override + public @NotNull Gui createGui() { + return Gui.normal() + .setStructure( + "# L # # I # # R #", + "# l # # i # # r #", + "# L # # I # # R #", + "# # # # # # # # #", + "# # # < a > # # #", + "# # # # # # # # #" + ) + .addIngredient('#', GuiItems.background()) + .addIngredient('L', leftStack) + .addIngredient('l', leftInventory) + .addIngredient('I', GuiItems.input()) + .addIngredient('i', inputInventory) + .addIngredient('R', rightStack) + .addIngredient('r', rightInventory) + .addIngredient('a', new SuppliedItem(() -> ratioStack.clone() + .name(Component.translatable("pylon.pylonbase.gui.ratio").arguments( + PylonArgument.of("left", ratioLeft), + PylonArgument.of("right", ratioRight) + )), + click -> false + )) + .addIngredient('<', new RatioButton(leftButtonStack, () -> ratioLeft, amount -> ratioLeft = amount)) + .addIngredient('>', new RatioButton(rightButtonStack, () -> ratioRight, amount -> ratioRight = amount)) + .build(); + } + + @Override + public void postInitialise() { + createLogisticGroup("input", LogisticSlotType.INPUT, new VirtualInventoryLogisticSlot(inputInventory, 0)); + createLogisticGroup("left", LogisticSlotType.OUTPUT, new VirtualInventoryLogisticSlot(leftInventory, 0)); + createLogisticGroup("right", LogisticSlotType.OUTPUT, new VirtualInventoryLogisticSlot(rightInventory, 0)); + inputInventory.setPostUpdateHandler(event -> { + if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { + doSplit(); + } + }); + leftInventory.setPostUpdateHandler(event -> { + if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { + doSplit(); + } + }); + rightInventory.setPostUpdateHandler(event -> { + if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { + doSplit(); + } + }); + } + + @Override + public @Nullable WailaDisplay getWaila(@NotNull Player player) { + return new WailaDisplay(getDefaultWailaTranslationKey().arguments( + PylonArgument.of("left", ratioLeft), + PylonArgument.of("right", ratioRight), + PylonArgument.of("side", isLeft + ? Component.translatable("pylon.pylonbase.waila.cargo_splitter.left") + : Component.translatable("pylon.pylonbase.waila.cargo_splitter.right") + ) + )); + } + + private void doSplit() { + ItemStack input = inputInventory.getItem(0); + if (input == null) { + return; + } + + if (isLeft) { + ItemStack left = leftInventory.getItem(0); + if (left == null || (left.isSimilar(input) && left.getAmount() < left.getMaxStackSize())) { + if (left == null) { + leftInventory.setItem(new MachineUpdateReason(), 0, input.asOne()); + } else { + leftInventory.setItem(new MachineUpdateReason(), 0, left.add()); + } + inputInventory.setItem(new MachineUpdateReason(), 0, input.subtract()); + itemsRemaining--; + if (itemsRemaining == 0) { + isLeft = !isLeft; + itemsRemaining = ratioRight; + } + doSplit(); + } + } else { + ItemStack right = rightInventory.getItem(0); + if (right == null || (right.isSimilar(input) && right.getAmount() < right.getMaxStackSize())) { + if (right == null) { + rightInventory.setItem(new MachineUpdateReason(), 0, input.asOne()); + } else { + rightInventory.setItem(new MachineUpdateReason(), 0, right.add()); + } + inputInventory.setItem(new MachineUpdateReason(), 0, input.subtract()); + itemsRemaining--; + if (itemsRemaining == 0) { + isLeft = !isLeft; + itemsRemaining = ratioLeft; + } + doSplit(); + } + } + } +} diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoValve.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoValve.java new file mode 100644 index 000000000..c161f6fd2 --- /dev/null +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoValve.java @@ -0,0 +1,213 @@ +package io.github.pylonmc.pylon.base.content.machines.cargo; + +import io.github.pylonmc.pylon.base.util.BaseUtils; +import io.github.pylonmc.pylon.core.block.PylonBlock; +import io.github.pylonmc.pylon.core.block.base.*; +import io.github.pylonmc.pylon.core.block.context.BlockCreateContext; +import io.github.pylonmc.pylon.core.config.adapter.ConfigAdapter; +import io.github.pylonmc.pylon.core.datatypes.PylonSerializers; +import io.github.pylonmc.pylon.core.entity.display.ItemDisplayBuilder; +import io.github.pylonmc.pylon.core.entity.display.transform.TransformBuilder; +import io.github.pylonmc.pylon.core.i18n.PylonArgument; +import io.github.pylonmc.pylon.core.item.PylonItem; +import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder; +import io.github.pylonmc.pylon.core.logistics.LogisticSlotType; +import io.github.pylonmc.pylon.core.logistics.VirtualInventoryLogisticSlot; +import io.github.pylonmc.pylon.core.util.gui.GuiItems; +import io.github.pylonmc.pylon.core.util.gui.unit.UnitFormat; +import io.github.pylonmc.pylon.core.waila.WailaDisplay; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.bukkit.entity.Display; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.xenondevs.invui.gui.Gui; +import xyz.xenondevs.invui.inventory.VirtualInventory; + +import java.util.List; + +import static io.github.pylonmc.pylon.base.util.BaseUtils.baseKey; + + +public class CargoValve extends PylonBlock implements + PylonDirectionalBlock, + PylonGuiBlock, + PylonCargoBlock, + PylonEntityHolderBlock, + PylonInteractBlock { + + public static final NamespacedKey ENABLED_KEY = baseKey("enabled"); + public static final int BRIGHTNESS_OFF = 6; + public static final int BRIGHTNESS_ON = 13; + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + + private final VirtualInventory inventory = new VirtualInventory(1); + + public boolean enabled; + + public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.LIGHT_GRAY_CONCRETE) + .addCustomModelDataString(getKey() + ":main"); + public final ItemStackBuilder sideStack = ItemStackBuilder.of(Material.WHITE_CONCRETE) + .addCustomModelDataString(getKey() + ":side"); + public final ItemStackBuilder inputStack = ItemStackBuilder.of(Material.LIME_TERRACOTTA) + .addCustomModelDataString(getKey() + ":input"); + public final ItemStackBuilder outputStack = ItemStackBuilder.of(Material.RED_TERRACOTTA) + .addCustomModelDataString(getKey() + ":output"); + + public static class Item extends PylonItem { + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + + public Item(@NotNull ItemStack stack) { + super(stack); + } + + @Override + public @NotNull List getPlaceholders() { + return List.of( + PylonArgument.of( + "transfer-rate", + UnitFormat.ITEMS_PER_SECOND.format(PylonCargoBlock.cargoItemsTransferredPerSecond(transferRate)) + ) + ); + } + } + + @SuppressWarnings("unused") + public CargoValve(@NotNull Block block, @NotNull BlockCreateContext context) { + super(block, context); + + setFacing(context.getFacing()); + + addCargoLogisticGroup(getFacing(), "input"); + addCargoLogisticGroup(getFacing().getOppositeFace(), "output"); + setCargoTransferRate(transferRate); + + addEntity("main", new ItemDisplayBuilder() + .itemStack(mainStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .scale(0.65) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("side1", new ItemDisplayBuilder() + .itemStack(sideStack) + .brightness(BRIGHTNESS_OFF) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .scale(0.7, 0.5, 0.5) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("side2", new ItemDisplayBuilder() + .itemStack(sideStack) + .brightness(BRIGHTNESS_OFF) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .scale(0.5, 0.7, 0.5) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("output", new ItemDisplayBuilder() + .itemStack(outputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, -0.15) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("input", new ItemDisplayBuilder() + .itemStack(inputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, 0.15) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + enabled = false; + } + + @SuppressWarnings("unused") + public CargoValve(@NotNull Block block, @NotNull PersistentDataContainer pdc) { + super(block, pdc); + + enabled = pdc.get(ENABLED_KEY, PylonSerializers.BOOLEAN); + } + + @Override + public void write(@NotNull PersistentDataContainer pdc) { + pdc.set(ENABLED_KEY, PylonSerializers.BOOLEAN, enabled); + } + + @Override + public void onInteract(@NotNull PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getHand() != EquipmentSlot.HAND) { + return; + } + + if (!event.getPlayer().isSneaking()) { + PylonGuiBlock.super.onInteract(event); + return; + } + + event.setUseItemInHand(Event.Result.DENY); + + enabled = !enabled; + + + if (enabled) { + setCargoTransferRate(transferRate); + } else { + setCargoTransferRate(0); + } + + getHeldEntityOrThrow(ItemDisplay.class, "side1") + .setBrightness(new Display.Brightness(0, enabled ? BRIGHTNESS_ON : BRIGHTNESS_OFF)); + getHeldEntityOrThrow(ItemDisplay.class, "side2") + .setBrightness(new Display.Brightness(0, enabled ? BRIGHTNESS_ON : BRIGHTNESS_OFF)); + } + + @Override + public @NotNull Gui createGui() { + return Gui.normal() + .setStructure("# # # # x # # # #") + .addIngredient('#', GuiItems.background()) + .addIngredient('x', inventory) + .build(); + } + + @Override + public void postInitialise() { + createLogisticGroup("input", LogisticSlotType.INPUT, new VirtualInventoryLogisticSlot(inventory, 0)); + createLogisticGroup("output", LogisticSlotType.OUTPUT, new VirtualInventoryLogisticSlot(inventory, 0)); + } + + @Override + public @Nullable WailaDisplay getWaila(@NotNull Player player) { + return new WailaDisplay(getDefaultWailaTranslationKey().arguments( + PylonArgument.of("status", Component.translatable( + "pylon.pylonbase.message.valve." + (enabled ? "enabled" : "disabled") + )) + )); + } +} diff --git a/src/main/resources/lang/en.yml b/src/main/resources/lang/en.yml index 494aebf59..86fe4e56e 100644 --- a/src/main/resources/lang/en.yml +++ b/src/main/resources/lang/en.yml @@ -1601,6 +1601,31 @@ item: Inserts items into chests, furnaces, machines, and other compatible blocks Transfer rate: %transfer-rate% + cargo_splitter: + name: "Cargo Splitter" + lore: |- + Splits items between two outputs according to a configurable ratio + Transfer rate: %transfer-rate% + waila: "Cargo Splitter | <#efae15>%left%-<#2386c4>%right% | %side%" + + cargo_merger: + name: "Cargo Merger" + lore: |- + Merges incoming items into one output + Transfer rate: %transfer-rate% + + cargo_valve: + name: "Cargo Valve" + lore: |- + Shift right click a cargo valve to toggle it + waila: "Cargo Valve | %status%" + + cargo_filter: + name: "Cargo Filter" + lore: |- + Sends items matching the filter left + Sends items that do not match the filter right + hunger_talisman_simple: name: "Simple Hunger Talisman" lore: |- @@ -1819,6 +1844,9 @@ waila: filled: "%bar% (%fluid%)" fluid_strainer: straining: " | Straining %item% | %bars%" + cargo_splitter: + left: "<#efae15>Left" + right: "<#2386c4>Right" pit_kiln: invalid_recipe: "Invalid recipe" smelting: "%time% remaining" @@ -1971,6 +1999,31 @@ gui: hammer: "Hammer" pickaxe: "Pickaxe" tool: "Tool" + filtered: "Filtered" + unfiltered: "Unfiltered" + filter: "Items to filter" + left: "Left" + right: "Right" + left_button: + name: "Change ratio left" + lore: |- + Left click to increase + Right click to decrease + right_button: + name: "Change ratio right" + lore: |- + Left click to increase + Right click to decrease + whitelist-blacklist-toggle: + whitelist: + name: "Whitelist mode" + lore: |- + Click to toggle + blacklist: + name: "Blacklist mode" + lore: |- + Click to toggle + ratio: "Ratio: <#efae15>%left%-<#2386c4>%right% (<#efae15>left-<#2386c4>right)" portable-dustbin: "Portable Dustbin" status: name: "Status" @@ -2001,3 +2054,17 @@ gui: text: "%filled_bars%%empty_bars% %progress%" fluid_amount_bar: text: "%filled_bars%%empty_bars% %amount%/%capacity%" + logistic-group-cycle-item: + name: "Current inventory: %inventory%" + lore: |- + Click to cycle + +inventory: + input: "Input" + output: "Output" + hammer: "Hammer" + tool: "Tool" + left: "Left" + right: "Right" + filtered: "Filtered" + unfiltered: "Unfiltered" diff --git a/src/main/resources/settings/cargo_filter.yml b/src/main/resources/settings/cargo_filter.yml new file mode 100644 index 000000000..540bfb074 --- /dev/null +++ b/src/main/resources/settings/cargo_filter.yml @@ -0,0 +1 @@ +transfer-rate: 1 \ No newline at end of file diff --git a/src/main/resources/settings/cargo_merger.yml b/src/main/resources/settings/cargo_merger.yml new file mode 100644 index 000000000..540bfb074 --- /dev/null +++ b/src/main/resources/settings/cargo_merger.yml @@ -0,0 +1 @@ +transfer-rate: 1 \ No newline at end of file diff --git a/src/main/resources/settings/cargo_splitter.yml b/src/main/resources/settings/cargo_splitter.yml new file mode 100644 index 000000000..540bfb074 --- /dev/null +++ b/src/main/resources/settings/cargo_splitter.yml @@ -0,0 +1 @@ +transfer-rate: 1 \ No newline at end of file diff --git a/src/main/resources/settings/cargo_valve.yml b/src/main/resources/settings/cargo_valve.yml new file mode 100644 index 000000000..540bfb074 --- /dev/null +++ b/src/main/resources/settings/cargo_valve.yml @@ -0,0 +1 @@ +transfer-rate: 1 \ No newline at end of file